intro
conntrack 은 netfilter에서 사용하는 네트워크 상태를 추적하기 위해 도구로 destination IP주소, port 정pair 정보, Protocol, state등을 담고 있다. 이러한 정보를 기반으로 stateful한 연결을 지원해주는 도구이다.
Basic
conntrack, conntraion tracking system은 말그대로 연결의 상태에 대한 정보를 저장한다. 해당 정보에는 목적지, 출발와 port, protocol, state, timeout과 같은 정보가 포함되어있다.
이러한 정보를 통해 statefule한 연결을 만들어주는 도구인데요, 백문이 불여일견이라고, 실제 conntrack entry를 보면 아래와 같습니다.
# conntrack - L
udp 17 24 src=10.0.137.153 dst=10.0.0.2 sport=55102 dport=53 src=10.0.0.2 dst=10.0.137.153 sport=53 dport=55102 mark=128 secctx=system_u:object_r:unlabeled_t:s0 use=1
tcp 6 29 TIME_WAIT src=169.254.20.10 dst=169.254.20.10 sport=57204 dport=8080 src=169.254.20.10 dst=169.254.20.10 sport=8080 dport=57204 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 use=1
conntrack이 어떻게 동작하는지 확인하기전에, kernel내 connection에는 4개의 상태를 가진다.
-
=NEW=: 연결이 시작됨을 의미한다. TCP SYN 패킷을 수신받거나 등 유효한 패킷을 전달받거나 연결이 한방향으로만 전달되었을 때 설정된다. (한쪽으로만 나가고 아직 들어오지 않았을 때)
-
=ESTABLISHED=: 연결이 수립되었음을 의미한다. 연결이 방화벽을 타고 나갔다 들어오면, TCP 기준 SYN → ← ACK을 받으면 연결 수립 상태로 넘어간다.
-
=RELATED=: 문서에는 exptected connection이라고 되어있다. 몇몇 어플리케이션은 들어가고 나가는 포트가 다른 경우가 있다. 예를들어 FTP 패시브 모드는 데이터 요청을 21번포트로 하는데 1024-65535 포트대역으로 데이터를 받는다. conntrack 모듈을 helper 개념이 있어서, 어디서 왔는지()
-
=INVALID=: 특수한 상태로, 예상되는 밖의 연결에 마킹하는 기능이다. 실제로 conntrack은 자체로 필터링을 하지 않기 때문에, 특수 목적을 위해 마킹한다고 이해하면 된다.
INVALID가 조금 이해가 안갔는데, 예시로 보면 다음과 같다. 실제로 kube-proxy에서 아래와 같이 INVALID state에 대해 DROP하는 rule을 볼 수 있습니다.1
if !proxier.conntrackTCPLiberal {
rule := []string{
"-A", string(kubeForwardChain),
"-m", "conntrack",
"--ctstate", "INVALID",
}
rule = append(rule,
"-j", "DROP",
)
위 코드 주석을 보면, nf_conntrack_tcp_be_liberal가 설정되지않으면 (기본 0) 예상치못한 DROP이 발생할 수 있음을 안내하고 있다. nf_conntrack_tcp_be_liberal에 대해 살펴보면 기본적으로 out-of-window에 대한 패킷을 INVALID로 표기하게 됩니다. 2
Be conservative in what you do, be liberal in what you accept from others. If it’s non-zero, we mark only out of window RST segments as INVALID.
과거에는 kube-proxy가 invalid state 패킷을 드랍하지 않아, 할 수 없는 패킷에 대해 rst 패킷을 전송하는 문제가 있었고, 현재는 fix되었습니다. 3
이처럼 conntrack에서 INVALID와 같이 “상태”만 기록할 뿐이고, 실제로 DROP의 행위는 nftables/iptables에서 수행하게 됩니다.
Tip
wireshark로 tcp.analysis.out_of_order 이나 tcp.analysis.spurious_retransmission 가 증가하는지 확인해보고, nf_conntrack_tcp_be_liberal=1 로 설정할지, 결정해볼 수 있겠다.
이런 conntrack과 netfilter가 어떻게 동작하는 지는 다음 유명한 이미지로 이해해볼 수 있다.
그럼 실제로 conntrack이 도대채 어디서 동작하는지 확인해보기 위해서 유명한 아래 그림1을 참고할 수 있다.
[그림 1. packet flow int netfilter and general networking ]
그림1 에서, inbound packet에 대해서는 raw 테이블의 PREROUTING 이후, 나가는 패킷에 대해서는 raw 테이블의 OUTPUT이후 conntrack을 거치는 것으로 되어있다.
실제로는 prerouting, output hook function에서 conntrack hook function이 실행된다.
이때 ct table에서 해당 테이블을 조회하고, trackking중인 연결인지 등 상태를 나타내게 된다. 만약 조회했을 때, entry에 포함되지 않으면 unconfirmed list에 우선 넣어두게 된다. 4
ct state new와 같은 nftables 명령어 사용하면 unconfirmed list에서 해당 커넥션을 필터링할 때 사용하게된다.
그럼 conntrack을 조회할 때 어떤 정보를 확인할 수 있을까? 맨처음 예시를 본것과 같이 conntrack -L 과 같이 conntrack enty를 조회할 수 있고, filter를 추가하여 선택적으로도 조회할 수 있다.
tcp 6 29 TIME_WAIT src=169.254.20.10 dst=169.254.20.10 sport=57204 dport=8080 src=169.254.20.10 dst=169.254.20.10 sport=8080 dport=57204 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 use=1
TCP 와 같은 TCP 정보와 상태, src/dst 정보, security mark들은 직관적으로 이해가 되지만, ASSURED와 같이 conntrack status를 나타내는 부분은 조금 이해하기 어려웠다.
이때 다음 netfilter 코드5 설명을 찾았고 ASSURED는 성공적으로 연결이 이루어졌음을 나타냄을 확인했다..
/* Set ASSURED if we see valid ack in ESTABLISHED
after SYN_RECV or a valid answer for a picked up
connection. */
전체 status는 다음 코드에서 확인할 수 있었다. 6 conntrack entry를 보고 문제를 파악할 수 있어야 겠다.
Footnotes
-
https://github.com/kubernetes/kubernetes/blob/9bfe52e1fe0d3135f47d2e3c198b4d88fefb566c/pkg/proxy/iptables/proxier.go#L1330-L1339 ↩
-
https://docs.kernel.org/networking/nf_conntrack-sysctl.html ↩
-
https://thermalcircle.de/doku.php?id=blog:linux:connection_tracking_1_modules_and_hooks ↩
-
https://elixir.bootlin.com/linux/v5.10.19/source/net/netfilter/nf_conntrack_proto_tcp.c#L1165 ↩
-
https://elixir.bootlin.com/linux/v5.10.19/source/include/uapi/linux/netfilter/nf_conntrack_common.h#L42 ↩