nf_conntrack 헬퍼 & ALG

Linux 커널 Application Layer Gateway(ALG) 내부 구조, FTP/SIP/H.323/RTSP/PPTP conntrack 헬퍼, 커스텀 헬퍼 작성, ctnetlink 이벤트, 클라우드/컨테이너 환경 ALG 이슈 종합 가이드. 커널 내부 데이터 경로, 핵심 자료구조/API, 운영 환경 튜닝 포인트와 장애 디버깅 절차까지 실무 관점으로 다룹니다.

전제 조건: Netfilter 프레임워크, 네트워크 스택, NAT 심화 문서를 먼저 읽으세요. conntrack 헬퍼는 Netfilter의 연결 추적(conntrack)과 NAT가 함께 동작하는 고급 주제입니다.
일상 비유: ALG는 구식 전화교환수와 같습니다. FTP처럼 제어 채널과 데이터 채널이 분리된 프로토콜에서, 교환수(ALG)가 제어 채널의 대화를 엿들어 데이터 채널 연결 요청을 미리 파악하고, 방화벽에 "이 번호로 전화가 올 거니까 통과시켜줘"라고 자동으로 지시합니다.

핵심 요약

  • ALG 필요성 — FTP·SIP 등 제어/데이터 채널이 분리된 프로토콜은 방화벽이 페이로드를 분석해야 데이터 채널을 자동 허용할 수 있습니다.
  • conntrack expectnf_conntrack_expect는 "예상 연결"을 등록해두고, 실제 연결이 도착하면 기존 세션과 자동 연결합니다.
  • 헬퍼 등록nf_conntrack_helper 구조체의 .help 콜백이 패킷 페이로드를 파싱하고 expect를 등록합니다.
  • FTP 헬퍼 — PORT/PASV 명령 파싱으로 데이터 채널 IP·포트를 추출해 expect를 등록합니다.
  • SIP 헬퍼 — SIP INVITE의 SDP 바디에서 RTP 포트를 파싱해 미디어 채널 expect를 등록합니다.
  • H.323/RTSP 헬퍼 — H.323은 Q.931(TCP 1720), H.245 협상, RTP 미디어 등 다중 채널을 순차적으로 협상하며 각 채널마다 expect를 등록합니다.
  • PPTP/GRE 헬퍼 — PPTP는 TCP 1723 제어 채널에서 Call ID를 추출해 GRE 프로토콜 데이터 채널 expect를 등록합니다.
  • 헬퍼 보안 이슈 — 자동 헬퍼 할당(nf_conntrack_helper=1)은 CVE-2014-8160 등 보안 취약점의 원인이며, 현재 기본값은 0(비활성)입니다.
  • 타임아웃 정책nft ct timeout 객체로 서비스별 커스텀 타임아웃을 설정해 conntrack 테이블 효율을 최적화합니다.
  • 커스텀 헬퍼nf_ct_helper_init()nf_conntrack_helper_register()로 자체 프로토콜 헬퍼를 커널 모듈로 구현할 수 있습니다.
  • ctnetlink/conntrackd — Netlink 기반 이벤트 채널로 NEW/UPDATE/DESTROY 이벤트를 수신해 conntrack 상태를 HA 장비 간에 동기화합니다.
  • 클라우드 이슈 — Kubernetes·AWS·GCP 환경에서는 ALG가 오히려 SIP/FTP를 깨뜨리는 경우가 많아 비활성화가 권장됩니다.

단계별 이해

  1. conntrack 기초 확인
    conntrack -L로 기존 연결 추적 테이블을 확인하고, ESTABLISHED·RELATED 상태를 구분합니다.
  2. 헬퍼 모듈 로드
    modprobe nf_conntrack_ftp로 FTP 헬퍼를 로드하고, lsmod | grep nf_conntrack으로 로드된 헬퍼 목록을 확인합니다.
  3. expect 테이블 관찰
    FTP PASV 연결 시 conntrack -L expect로 등록된 expect 엔트리를 실시간으로 확인합니다.
  4. iptables 헬퍼 연결
    iptables -t raw -A PREROUTING -p tcp --dport 21 -j CT --helper ftp로 특정 트래픽에 헬퍼를 명시적으로 연결합니다.
  5. SIP ALG 문제 진단
    SIP 통화가 한쪽만 들리는 경우 sysctl net.netfilter.nf_conntrack_helper로 ALG 활성화 여부를 확인하고 비활성화를 검토합니다.
  6. 보안 강화
    nf_conntrack_helper=0 상태에서 nftables로 수동 헬퍼 할당(ct helper set "ftp")을 통해 의도한 트래픽에만 ALG를 적용합니다.
  7. 타임아웃 최적화
    nft ct timeout 객체를 생성해 서비스별 TCP/UDP 타임아웃을 커스터마이즈하고, conntrack -S로 드롭 통계를 모니터링합니다.
  8. 커스텀 헬퍼 개발
    nf_conntrack_helper 구조체를 구현하고, nf_conntrack_helper_register()로 등록해 자체 프로토콜의 연관 채널을 자동 추적합니다.
관련 표준: RFC 959 (FTP), RFC 3261 (SIP), RFC 2326 (RTSP), RFC 2637 (PPTP) — conntrack 헬퍼는 이 표준 프로토콜의 제어 채널을 파싱해 연관 데이터 채널 연결을 자동 허용합니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

개요: ALG와 conntrack 헬퍼

리눅스 Netfilter의 conntrack 헬퍼(Connection Tracking Helper)Application Layer Gateway(ALG)를 구현하는 커널 메커니즘입니다. 일반적인 TCP/UDP 연결은 방화벽이 5-튜플(프로토콜·출발지 IP·출발지 포트·목적지 IP·목적지 포트)만으로 추적할 수 있지만, FTP·SIP·H.323처럼 제어 채널과 데이터 채널이 분리된 프로토콜은 페이로드를 파싱해야 연관 연결을 파악할 수 있습니다.

구분단순 프로토콜 (HTTP, SSH)복합 프로토콜 (FTP, SIP)
채널 구성 단일 연결 제어 채널 + 동적 데이터 채널
방화벽 처리 5-튜플만으로 추적 가능 페이로드 파싱 없이 데이터 채널 차단
ALG 필요 여부 불필요 필수 (데이터 채널 자동 허용)
conntrack 상태 ESTABLISHED 제어: ESTABLISHED, 데이터: RELATED

conntrack 헬퍼는 제어 채널 패킷을 가로채 페이로드를 파싱하고, nf_conntrack_expect를 통해 예상 연결(expected connection)을 등록합니다. 이후 데이터 채널 연결이 도착하면 커널이 이를 RELATED 상태로 분류해 방화벽 규칙 없이 자동으로 허용합니다.

RELATED 상태: iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 한 줄로 ALG가 등록한 모든 데이터 채널을 자동 허용할 수 있습니다. ALG 없이는 각 데이터 포트를 개별 허용해야 합니다.
FTP 클라이언트 192.168.1.100 방화벽 / ALG conntrack core nf_conntrack_in() FTP 헬퍼 nf_conntrack_ftp.c .help = ftp_help() expect 테이블 nf_conntrack_expect dst: 203.0.113.5:49152 NAT 모듈 페이로드 IP 재작성 FTP 서버 203.0.113.5 제어 채널 :21 PORT 192,168,1,100,192,1 nf_ct_expect_related() 데이터 채널 :49153 (임의 포트) 데이터 채널 :20 (서버 포트) RELATED 자동 허용 expect 매칭 → RELATED 상태로 분류 방화벽 규칙 없이 데이터 채널 자동 통과

ALG(Application Layer Gateway) 개념

ALG는 OSI 7계층(애플리케이션 계층)의 프로토콜 내용을 분석해 방화벽·NAT 장비가 연관 연결을 자동으로 처리할 수 있도록 도와주는 기술입니다. 리눅스 커널에서 ALG는 nf_conntrack_helper 구조체를 통해 구현됩니다.

문제: FTP PASV 모드와 방화벽

FTP는 두 가지 모드로 데이터를 전송합니다.

모드데이터 채널 방향방화벽 문제
Active (PORT) 서버 → 클라이언트 (서버가 연결 개시) 클라이언트 방화벽이 인바운드 연결 차단
Passive (PASV) 클라이언트 → 서버 (클라이언트가 연결 개시) 서버 방화벽이 임의 포트 인바운드 차단

PASV 모드에서 서버는 제어 채널(포트 21)에 227 Entering Passive Mode (a,b,c,d,p1,p2)를 응답하며, 클라이언트는 (p1*256)+p2 포트로 새 연결을 엽니다. 방화벽은 이 포트를 알 수 없으므로 ALG 없이는 차단합니다.

conntrack expect 메커니즘

nf_conntrack_expect는 "아직 도착하지 않은 연관 연결"을 사전에 등록하는 자료구조입니다. 헬퍼가 제어 채널에서 데이터 채널 정보를 파싱하면 다음 과정이 진행됩니다.

  1. nf_ct_expect_alloc(ct) — 현재 conntrack에 연결된 expect 객체 할당
  2. nf_ct_expect_init(exp, ...) — 예상 연결의 5-튜플·마스크 설정
  3. nf_ct_expect_related(exp) — expect 해시 테이블에 등록 (만료 타이머 포함)
  4. 실제 연결 도착 시 nf_conntrack_in()이 expect 테이블을 조회하고, 매칭되면 IP_CT_RELATED 상태 부여

nf_conntrack_helper 구조체

/* include/net/netfilter/nf_conntrack_helper.h */
struct nf_conntrack_helper {
    struct hlist_node   hnode;          /* 헬퍼 해시 테이블 노드 */
    char                name[NF_CT_HELPER_NAME_LEN]; /* "ftp", "sip" 등 */
    refcount_t          refcnt;
    struct module       *me;            /* THIS_MODULE */
    const struct nf_conntrack_expect_policy *expect_policy;
    struct nf_conntrack_tuple tuple;    /* 헬퍼가 처리할 트래픽 5-튜플 */

    /* 핵심 콜백: 패킷마다 호출, NF_ACCEPT/NF_DROP 반환 */
    int (*help)(struct sk_buff *skb,
                unsigned int protoff,
                struct nf_conn *ct,
                enum ip_conntrack_info conntrackinfo);

    /* NAT 연동: 페이로드의 주소/포트를 재작성 */
    void (*destroy)(struct nf_conn *ct);
    int (*from_nlattr)(struct nlattr *attr, struct nf_conn *ct);

    unsigned int        flags;
    unsigned int        expect_class_max;
};
헬퍼 자동 로드: 커널 4.7 이후 net.netfilter.nf_conntrack_helper=0이 기본값입니다. 헬퍼를 활성화하려면 iptables -t raw -A PREROUTING -p tcp --dport 21 -j CT --helper ftp처럼 명시적으로 트래픽과 헬퍼를 연결해야 합니다.

내장 conntrack 헬퍼

리눅스 커널은 다음 프로토콜에 대한 내장 conntrack 헬퍼를 제공합니다. 각 헬퍼는 net/netfilter/ 디렉터리의 별도 파일로 구현됩니다.

헬퍼 모듈프로토콜기본 포트기능소스 파일
nf_conntrack_ftp FTP TCP 21 PORT/PASV 명령 파싱 → 데이터 채널 expect 등록 nf_conntrack_ftp.c
nf_conntrack_tftp TFTP UDP 69 UDP 데이터 전송 채널 expect 등록 nf_conntrack_tftp.c
nf_conntrack_sip SIP UDP/TCP 5060 SDP 파싱 → RTP/RTCP 미디어 채널 expect 등록 nf_conntrack_sip.c
nf_conntrack_h323 H.323 TCP 1720 H.245 시그널링 채널 및 RTP 채널 expect 등록 nf_conntrack_h323.c
nf_conntrack_rtsp RTSP TCP 554 SETUP 메시지 파싱 → RTP/RTCP 채널 expect 등록 nf_conntrack_rtsp.c
nf_conntrack_pptp PPTP TCP 1723 GRE 터널 연결 expect 등록 (Call ID 기반) nf_conntrack_pptp.c
nf_conntrack_amanda Amanda UDP 10080 백업 소프트웨어 데이터 채널 expect 등록 nf_conntrack_amanda.c
nf_conntrack_irc IRC TCP 6667 DCC(Direct Client Connection) 전송 expect 등록 nf_conntrack_irc.c

헬퍼 확인 및 관리 명령

# 로드된 conntrack 헬퍼 모듈 확인
lsmod | grep nf_conntrack

# 등록된 헬퍼 목록 확인 (conntrack -L expect와 병행)
cat /proc/net/nf_conntrack_expect

# FTP 헬퍼 수동 로드
modprobe nf_conntrack_ftp

# 헬퍼 자동 로드 활성화 (보안 취약, 권장 안 함)
sysctl -w net.netfilter.nf_conntrack_helper=1

# 권장: iptables raw 테이블로 명시적 연결
iptables -t raw -A PREROUTING -p tcp --dport 21 -j CT --helper ftp
iptables -t raw -A OUTPUT     -p tcp --dport 21 -j CT --helper ftp

# expect 테이블 실시간 확인 (FTP PASV 연결 후 빠르게 확인)
conntrack -L expect
보안 주의: nf_conntrack_helper=1 (자동 로드)은 커널 4.7 이전의 기본값이었지만, 임의 트래픽에 헬퍼를 자동 적용해 의도치 않은 RELATED 허용을 유발할 수 있습니다. 최신 커널에서는 기본값이 0이며, iptables CT 타깃으로 명시적 연결을 권장합니다.

FTP 헬퍼 심화

nf_conntrack_ftp는 가장 널리 사용되는 conntrack 헬퍼로, FTP 제어 채널의 PORT/PASV 명령을 파싱합니다. 소스 파일: net/netfilter/nf_conntrack_ftp.c

Active 모드 (PORT 명령) 처리

클라이언트가 서버에 데이터 채널 주소를 알리는 PORT 명령을 파싱합니다.

/* PORT a,b,c,d,p1,p2 형식 파싱 (pseudocode) */

/* FTP 제어 채널 패킷에서 PORT 명령 탐지 */
static int ftp_help(struct sk_buff *skb, unsigned int protoff,
                    struct nf_conn *ct, enum ip_conntrack_info ctinfo)
{
    /* 패킷에서 FTP 페이로드 위치 계산 */
    const unsigned char *data;
    unsigned int dataoff = protoff + sizeof(struct tcphdr);

    /* "PORT " 문자열 탐지 */
    /* PORT 192,168,1,100,192,1 파싱:
     *   IP = 192.168.1.100
     *   port = 192*256 + 1 = 49153  */
    __be32 ip = MKIPADDR(a, b, c, d);
    __be16 port = htons(p1 * 256 + p2);

    /* expect 할당 및 등록 */
    struct nf_conntrack_expect *exp = nf_ct_expect_alloc(ct);
    nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT,
                      AF_INET, NULL, &ip, IPPROTO_TCP, NULL, &port);
    nf_ct_expect_related(exp);   /* expect 해시 테이블에 등록 */
    nf_ct_expect_put(exp);

    return NF_ACCEPT;
}

Passive 모드 (PASV 응답) 처리

서버가 클라이언트에 임시 포트를 알리는 PASV 응답을 파싱합니다.

/* 서버 PASV 응답: "227 Entering Passive Mode (a,b,c,d,p1,p2)"
 * 예: "227 Entering Passive Mode (203,0,113,5,192,0)"
 *   → 서버 IP = 203.0.113.5
 *   → 데이터 포트 = 192*256 + 0 = 49152
 *
 * expect 등록:
 *   tuple.src = 클라이언트 IP (임의)
 *   tuple.dst = 서버 IP : 49152
 * 클라이언트가 이 포트로 SYN을 보내면 RELATED로 분류됨  */

/* NAT 연동: PASV 응답의 IP 주소도 재작성 */
/* nf_nat_ftp.c: NF_CT_HELPER_F_NAT_AWARE 플래그로 페이로드 수정 */

NAT 환경에서의 FTP ALG

NAT 뒤에 FTP 클라이언트가 있는 경우, 헬퍼는 페이로드의 사설 IP를 공인 IP로 재작성해야 합니다. nf_nat_ftp.c가 이 역할을 담당하며, TCP 시퀀스 번호 조정(nf_nat_mangle_tcp_packet())도 수행합니다.

# NAT + FTP 설정 예시
# 1. FTP 헬퍼 로드
modprobe nf_conntrack_ftp
modprobe nf_nat_ftp          # NAT 페이로드 재작성 모듈

# 2. 제어 채널에 헬퍼 연결
iptables -t raw -A PREROUTING -p tcp --dport 21 -j CT --helper ftp

# 3. RELATED 상태 허용 (데이터 채널 자동 허용)
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# 4. 마스커레이드 (SNAT)
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
FTP 포트 범위 설정: FTP 서버의 PASV 포트 범위를 고정하면 ALG 없이 방화벽 규칙만으로도 대응할 수 있습니다. vsftpd.confpasv_min_port/pasv_max_port 옵션으로 범위를 설정하고, 해당 포트 범위를 방화벽에서 직접 허용하는 방법이 더 간단하고 안전한 경우가 많습니다.

find_pattern() 함수와 페이로드 스캔

nf_conntrack_ftp.c의 핵심은 find_pattern() 함수로, FTP 페이로드에서 명령 패턴을 탐색합니다.

/* net/netfilter/nf_conntrack_ftp.c — find_pattern() 핵심 로직 (pseudocode) */

/* FTP 패턴 테이블: 파싱할 명령 목록 */
static const struct ftp_search {
    const char *pattern;       /* 탐색 문자열: "PORT ", "227 " 등 */
    size_t      plen;          /* 패턴 길이 */
    char        skip;          /* 구분 문자 (','나 공백) */
    char        term;          /* 종료 문자 ('\r' 또는 '\n') */
    enum nf_ct_ftp_type type;  /* ACTIVE_PORT, PASV_227 등 */
    int         (*getnum)(const char *, size_t, struct nf_conntrack_man *, char);
} search[NF_CT_FTP_MAX][2] = {
    /* 클라이언트 → 서버: PORT 명령 (Active mode) */
    [NF_CT_FTP_PORT][IP_CT_DIR_ORIGINAL] = {
        .pattern = "PORT", .plen = 4,
        .skip = ',', .term = '\r',
        .type = NF_CT_FTP_PORT,
        .getnum = try_number,   /* a,b,c,d,p1,p2 파싱 */
    },
    /* 서버 → 클라이언트: PASV 응답 (Passive mode) */
    [NF_CT_FTP_PASV][IP_CT_DIR_REPLY] = {
        .pattern = "227 ", .plen = 4,
        .skip = '(', .term = ')',
        .type = NF_CT_FTP_PASV,
        .getnum = try_number,
    },
    /* IPv6 확장: EPRT (Active), EPSV (Passive) */
    [NF_CT_FTP_EPRT][IP_CT_DIR_ORIGINAL] = {
        .pattern = "EPRT", .plen = 4,
        .skip = '|', .term = '|',
        .type = NF_CT_FTP_EPRT,
        .getnum = try_eprt,     /* |AF|address|port| 파싱 */
    },
    [NF_CT_FTP_EPSV][IP_CT_DIR_REPLY] = {
        .pattern = "229 ", .plen = 4,
        .skip = '(', .term = ')',
        .type = NF_CT_FTP_EPSV,
        .getnum = try_epsv_response, /* (|||port|) 파싱 */
    },
};

Active vs Passive FTP 모드 완전 비교

항목Active 모드 (PORT/EPRT)Passive 모드 (PASV/EPSV)
데이터 채널 개시 서버가 클라이언트로 연결 (SYN) 클라이언트가 서버로 연결 (SYN)
FTP 명령 PORT a,b,c,d,p1,p2 227 Entering Passive Mode (a,b,c,d,p1,p2)
IPv6 명령 EPRT |2|::1|49152| 229 Entering Extended Passive Mode (|||49152|)
헬퍼 파싱 방향 IP_CT_DIR_ORIGINAL (클라이언트→서버) IP_CT_DIR_REPLY (서버→클라이언트)
NAT 재작성 대상 클라이언트 IP:PORT → 공인 IP:PORT 서버 IP:PORT (NAT 뒤 서버일 때)
방화벽 문제 클라이언트 방화벽이 인바운드 차단 서버 방화벽의 임의 포트 인바운드 차단

FTPS (FTP over TLS) — 헬퍼가 작동하지 않는 이유

FTPS(Explicit TLS: AUTH TLS 또는 Implicit FTPS: 포트 990)는 제어 채널이 TLS로 암호화되어 FTP 헬퍼가 PORT/PASV 명령을 파싱할 수 없습니다.

FTPS와 ALG: TLS 핸드셰이크 이후의 FTP 명령은 암호화되어 있으므로 nf_conntrack_ftp가 페이로드를 읽을 수 없습니다. FTPS 환경에서는 PASV 포트 범위를 고정(pasv_min_port/pasv_max_port)하고 방화벽 규칙에 명시하는 것이 유일한 방법입니다.
# FTPS 환경 권장 설정 (vsftpd.conf)
pasv_enable=YES
pasv_min_port=50000
pasv_max_port=51000
ssl_enable=YES

# 방화벽: PASV 포트 범위 직접 허용 (ALG 없이)
iptables -A FORWARD -p tcp --dport 50000:51000 -j ACCEPT
# 또는 nftables
nft add rule ip filter forward tcp dport 50000-51000 accept

FTP 헬퍼 디버깅

# conntrack FTP 추적 로그 활성화
sysctl -w net.netfilter.nf_log_all_netns=1

# FTP 연결 시 conntrack 상태 확인
conntrack -L | grep ftp

# FTP PASV 연결 직후 expect 엔트리 확인
conntrack -L expect
# 출력 예:
# tcp      6 29 ESTABLISHED src=192.168.1.100 dst=203.0.113.5
#          sport=0 dport=49152 src=203.0.113.5 dst=192.168.1.100
#          sport=49152 dport=0 mark=0 use=2

# conntrack 이벤트 실시간 모니터링
conntrack -E

# FTP 패킷 덤프와 병행 (제어 채널 패킷 확인)
tcpdump -i eth0 -A 'tcp port 21 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'

SIP 헬퍼 심화

nf_conntrack_sip는 SIP(Session Initiation Protocol) VoIP 트래픽을 위한 conntrack 헬퍼입니다. SIP는 시그널링 채널(포트 5060)과 미디어 채널(RTP, 동적 포트)이 분리되어 있어 ALG가 필수입니다. 소스 파일: net/netfilter/nf_conntrack_sip.c

SIP 세션 설정 흐름과 ALG 개입

/* SIP INVITE 처리 흐름 (pseudocode) */

/* 1. SIP INVITE 메시지 감지 */
static int sip_help(struct sk_buff *skb, ...) {
    /* SIP 메서드 파싱 (INVITE, REGISTER, BYE 등) */
    if (strncmp(msg, "INVITE", 6) == 0) {
        /* 2. SDP 바디 파싱 */
        /* SDP 예시:
         * v=0
         * o=- 12345 1 IN IP4 192.168.1.100
         * c=IN IP4 192.168.1.100          ← RTP 목적지 IP
         * m=audio 49170 RTP/AVP 0         ← RTP 포트 49170
         */
        parse_sdp_connection(&rtp_ip, &rtp_port);
        parse_sdp_media(&rtp_port);

        /* 3. RTP expect 등록 */
        nf_ct_expect_init(exp_rtp,  ..., &rtp_ip, IPPROTO_UDP, NULL, &rtp_port);
        nf_ct_expect_related(exp_rtp);

        /* 4. RTCP expect 등록 (RTP 포트 + 1) */
        rtcp_port = htons(ntohs(rtp_port) + 1);
        nf_ct_expect_init(exp_rtcp, ..., &rtp_ip, IPPROTO_UDP, NULL, &rtcp_port);
        nf_ct_expect_related(exp_rtcp);
    }
    return NF_ACCEPT;
}

SIP ALG의 주요 문제점

SIP ALG는 의도와 달리 VoIP 통화를 방해하는 경우가 많습니다.

문제원인증상
Contact 헤더 변조 ALG가 SIP 패킷의 Contact/Via 헤더를 공인 IP로 재작성 (잘못된 경우) REGISTER 실패, 통화 연결 불가
SRTP 페이로드 파싱 실패 TLS(SIPS) 또는 암호화된 SIP를 ALG가 파싱하지 못함 미디어 채널 expect 미등록 → RTP 차단
SIP trunk 충돌 ALG의 Via 헤더 수정이 SIP 프록시 경로와 충돌 일방향 음성 (one-way audio)
타이밍 문제 expect 만료 전 RTP 채널이 시작되지 않을 경우 간헐적 통화 실패

SIP ALG 비활성화 권장 시나리오

# SIP ALG 비활성화 (권장: 전용 SIP 프록시 사용 환경)
# 방법 1: 헬퍼 모듈 언로드
rmmod nf_conntrack_sip
rmmod nf_nat_sip

# 방법 2: 특정 트래픽에만 헬퍼 미적용 (CT --helper 미설정)
# nf_conntrack_helper=0 상태에서 SIP 포트에 CT 타깃 미설정

# 방법 3: nftables로 특정 흐름에서 헬퍼 제거
nft add rule ip filter PREROUTING \
    tcp dport 5060 ct helper unset

# SIP ALG 대안: STUN/TURN 서버 또는 SIP-aware 프록시 사용
# (Asterisk, Kamailio 등은 자체 NAT traversal 처리)

RTP/RTCP expect 등록 메커니즘

nf_nat_sip_expected()는 SIP expect가 실제 RTP 연결로 매칭될 때 호출되는 콜백으로, NAT 환경에서 미디어 채널에 적절한 DNAT/SNAT 규칙을 자동 추가합니다.

/* net/netfilter/nf_nat_sip.c — nf_nat_sip_expected() 동작 흐름 */

/* SIP expect → RTP 연결 수립 시 호출 */
static void nf_nat_sip_expected(struct nf_conn *ct,
                                 struct nf_conntrack_expect *exp)
{
    struct nf_nat_range2 range;

    /* exp->saved_proto: 원래 협상된 RTP 포트 */
    /* exp->tuple.dst.u.udp.port: NAT 이후 실제 포트 */

    /* 미디어 채널에 DNAT 규칙 적용 (NAT 뒤 SIP 단말로 전달) */
    memset(&range, 0, sizeof(range));
    range.flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
    range.min_proto = range.max_proto = exp->saved_proto;

    nf_nat_setup_info(ct, &range, NF_NAT_MANIP_DST);

    /* SNAT: 미디어 패킷의 출발지 IP를 공인 IP로 변경 */
    range.flags = NF_NAT_RANGE_MAP_IPS;
    range.min_addr = range.max_addr = ct->tuplehash[!exp->dir].tuple.dst.u3;
    nf_nat_setup_info(ct, &range, NF_NAT_MANIP_SRC);
}

SIP over TCP 처리

SIP는 UDP(포트 5060)와 TCP(포트 5060/5061) 양쪽을 지원합니다. TCP 위의 SIP는 여러 SIP 메시지가 하나의 TCP 스트림에 담길 수 있어 헬퍼가 세그먼트 경계를 처리해야 합니다.

/* SIP over TCP: 메시지 경계 처리 */
/* Content-Length 헤더로 SIP 메시지 크기를 파악하고
 * TCP 세그먼트 경계를 넘는 SIP 메시지를 재조합 */

/* sip_help_tcp()는 sip_help_udp()와 달리:
 * 1. TCP 스트림에서 Content-Length 파싱
 * 2. 불완전한 SIP 메시지는 NF_ACCEPT 후 다음 패킷 대기
 * 3. 완전한 SIP 메시지 수신 후 SDP 파싱 수행
 */

/* nftables로 TCP SIP에 헬퍼 연결 */
nft add table ip sip_alg
nft add chain ip sip_alg prerouting \
    '{ type filter hook prerouting priority raw; }'

# TCP SIP (5060)
nft add rule ip sip_alg prerouting \
    tcp dport 5060 ct helper set "sip"
# TLS SIP (5061) — 암호화로 파싱 불가, 헬퍼 미설정 권장
# UDP SIP (5060)
nft add rule ip sip_alg prerouting \
    udp dport 5060 ct helper set "sip"

SIP B2BUA 시나리오와 ALG 충돌

B2BUA(Back-to-Back User Agent)는 Asterisk, FreeSWITCH 같은 SIP 프록시로, SIP 다이얼로그를 종단 간에 완전히 재생성합니다. 이 환경에서 SIP ALG는 이미 B2BUA가 처리한 SIP 헤더를 이중으로 재작성하여 문제를 발생시킵니다.

시나리오SIP ALG 영향권장 조치
B2BUA (Asterisk/FreeSWITCH) Via/Contact 헤더 이중 재작성 → 라우팅 루프 SIP ALG 완전 비활성화
SIP 등록 서버 (kamailio) REGISTER Contact 헤더 변조 → 등록 실패 ALG 비활성화 + SIP 프록시에서 NAT 처리
SRTP/SIPS (TLS) 암호화로 SDP 파싱 불가 → RTP expect 미등록 STUN/TURN 사용, ALG 불필요
WebRTC (SIP over WebSocket) WebSocket 페이로드 파싱 미지원 → 무동작 ICE/STUN/TURN으로 NAT 처리
모던 VoIP 환경: WebRTC·ICE/STUN/TURN을 사용하는 현대 VoIP 솔루션은 ALG 없이도 NAT를 처리합니다. SIP ALG를 활성화한 상태에서 이들 솔루션을 사용하면 오히려 헤더가 이중 변조되어 통화 실패가 발생할 수 있습니다.

커스텀 conntrack 헬퍼 작성

표준 ALG로 지원되지 않는 사내 프로토콜이나 특수 목적 프로토콜에 대해 커널 모듈로 커스텀 conntrack 헬퍼를 작성할 수 있습니다.

커스텀 헬퍼 골격 코드

/* my_proto_helper.c — 커스텀 conntrack 헬퍼 예시
 *
 * 가정: TCP 포트 9999를 제어 채널로 사용하는 프로토콜
 * "DATAPORT <port>\r\n" 명령으로 데이터 채널 포트를 협상함
 */

#include <linux/module.h>
#include <linux/netfilter.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <net/netfilter/nf_conntrack_expect.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example");

#define MY_PROTO_PORT  9999
#define MY_PROTO_NAME  "myproto"

/* expect 정책: 최대 1개 연관 연결, 타임아웃 60초 */
static const struct nf_conntrack_expect_policy my_exp_policy = {
    .max_expected = 1,
    .timeout      = 60,
    .name         = "myproto-data",
};

/* 헬퍼 콜백: 제어 채널 패킷마다 호출 */
static int my_helper_cb(struct sk_buff *skb,
                         unsigned int protoff,
                         struct nf_conn *ct,
                         enum ip_conntrack_info ctinfo)
{
    struct nf_conntrack_expect *exp;
    const struct tcphdr *th;
    unsigned int dataoff;
    const char *payload;
    unsigned int paylen;
    __be16 data_port;
    int port;

    /* 서버 → 클라이언트 방향 패킷만 처리 */
    if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL)
        return NF_ACCEPT;

    /* TCP 헤더 후 페이로드 위치 계산 */
    th = skb_header_pointer(skb, protoff, sizeof(*th), NULL);
    if (!th)
        return NF_ACCEPT;
    dataoff = protoff + th->doff * 4;
    paylen  = skb->len - dataoff;
    if (paylen < 10)
        return NF_ACCEPT;

    payload = skb_header_pointer(skb, dataoff, paylen, NULL);
    if (!payload)
        return NF_ACCEPT;

    /* "DATAPORT <port>" 파싱 */
    if (strncmp(payload, "DATAPORT ", 9) != 0)
        return NF_ACCEPT;

    if (sscanf(payload + 9, "%d", &port) != 1)
        return NF_ACCEPT;

    if (port <= 0 || port > 65535)
        return NF_ACCEPT;

    data_port = htons(port);

    /* expect 할당 및 초기화 */
    exp = nf_ct_expect_alloc(ct);
    if (!exp)
        return NF_ACCEPT;

    /* 클라이언트 IP, data_port로 expect 설정 */
    nf_ct_expect_init(exp,
                      NF_CT_EXPECT_CLASS_DEFAULT,
                      nf_ct_l3num(ct),
                      /* 출발지: 임의 (마스크 = 0) */
                      NULL,
                      /* 목적지: 서버 IP */
                      &ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3,
                      IPPROTO_TCP,
                      NULL,
                      &data_port);

    /* expect 등록 */
    if (nf_ct_expect_related(exp) != 0)
        pr_debug("myproto: expect_related failed\n");

    nf_ct_expect_put(exp);
    return NF_ACCEPT;
}

/* 헬퍼 구조체 */
static struct nf_conntrack_helper my_helper __read_mostly = {
    .name           = MY_PROTO_NAME,
    .me             = THIS_MODULE,
    .tuple.src.l3num = AF_INET,
    .tuple.src.u.tcp.port = cpu_to_be16(MY_PROTO_PORT),
    .tuple.dst.protonum    = IPPROTO_TCP,
    .expect_policy  = &my_exp_policy,
    .help           = my_helper_cb,
};

static int __init my_helper_init(void)
{
    /* nf_ct_helper_init()으로 tuple 초기화 후 등록 */
    return nf_conntrack_helper_register(&my_helper);
}

static void __exit my_helper_fini(void)
{
    nf_conntrack_helper_unregister(&my_helper);
}

module_init(my_helper_init);
module_exit(my_helper_fini);

Makefile 예시

="macro">obj-m := my_proto_helper.o

KDIR  := /lib/modules/="fn">$(shell uname -r)/build
PWD   := ="fn">$(shell pwd)

all:
	="fn">$(MAKE) -C ="fn">$(KDIR) M=="fn">$(PWD) modules

clean:
	="fn">$(MAKE) -C ="fn">$(KDIR) M=="fn">$(PWD) clean

헬퍼 빌드 및 테스트

# 빌드
make

# 로드 (nf_conntrack 의존성 자동 처리)
insmod my_proto_helper.ko

# 특정 트래픽에 헬퍼 연결
iptables -t raw -A PREROUTING -p tcp --dport 9999 -j CT --helper myproto

# 헬퍼 등록 확인
cat /proc/net/nf_conntrack | grep myproto

# 테스트 후 정리
iptables -t raw -D PREROUTING -p tcp --dport 9999 -j CT --helper myproto
rmmod my_proto_helper

헬퍼 내부 API 상세

커스텀 헬퍼 작성 시 사용하는 핵심 API를 단계별로 살펴봅니다.

/* 1. nf_ct_helper_ext_add(): 헬퍼 전용 확장 데이터 할당
 *    conntrack 엔트리에 헬퍼가 사용할 사적 데이터 공간을 추가 */
struct myproto_state *state;
state = nf_ct_helper_ext_add(ct, GFP_ATOMIC);
if (!state)
    return NF_DROP;
state->sequence = 0;  /* 프로토콜 상태 초기화 */

/* 2. nf_ct_expect_alloc(): expect 객체 할당
 *    현재 conntrack에 연결된 expect 슬롯 할당 (refcount 포함) */
struct nf_conntrack_expect *exp = nf_ct_expect_alloc(ct);
if (!exp)
    return NF_ACCEPT;  /* 실패 시 패킷은 통과 */

/* 3. nf_ct_expect_init(): expect 5-튜플 설정
 *    Parameters:
 *      exp     — expect 객체
 *      class   — NF_CT_EXPECT_CLASS_DEFAULT (대부분의 헬퍼)
 *      family  — AF_INET 또는 AF_INET6
 *      saddr   — 예상 출발지 주소 (NULL = 마스크 전체)
 *      daddr   — 예상 목적지 주소
 *      proto   — IPPROTO_TCP, IPPROTO_UDP, IPPROTO_GRE 등
 *      sport   — 예상 출발지 포트 (NULL = 임의)
 *      dport   — 예상 목적지 포트 (필수) */
nf_ct_expect_init(exp,
                  NF_CT_EXPECT_CLASS_DEFAULT,
                  AF_INET,
                  NULL,                          /* saddr: 임의 */
                  &ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3,
                  IPPROTO_TCP,
                  NULL,                          /* sport: 임의 */
                  &data_port);                   /* dport: 협상된 포트 */

/* 4. nf_ct_expect_related(): expect 해시 테이블에 등록
 *    Returns: 0 = 성공, -EBUSY = 이미 등록됨, -ENOMEM = 메모리 부족 */
int ret = nf_ct_expect_related(exp);
if (ret)
    pr_warn("myproto: expect 등록 실패: %d\n", ret);

/* 5. nf_ct_expect_put(): expect refcount 감소
 *    등록 성공 후 반드시 호출 (헬퍼의 참조 해제) */
nf_ct_expect_put(exp);

/* 6. nf_conntrack_helper_register(): 헬퍼 등록
 *    반환: 0 = 성공, 음수 = 오류 (-EEXIST: 이름 중복) */
ret = nf_conntrack_helper_register(&my_helper);

/* 7. nf_conntrack_helper_unregister(): 헬퍼 등록 해제
 *    모든 연결된 conntrack가 헬퍼 참조를 해제할 때까지 대기 */
nf_conntrack_helper_unregister(&my_helper);

IPv4/IPv6 듀얼스택 헬퍼 등록

/* IPv4와 IPv6 각각 별도 헬퍼 구조체 등록 */
static struct nf_conntrack_helper my_helpers[2] __read_mostly;

static int __init my_helper_init(void)
{
    int ret;

    /* IPv4 헬퍼 */
    nf_ct_helper_init(&my_helpers[0], AF_INET, IPPROTO_TCP,
                      MY_PROTO_NAME, MY_PROTO_PORT,
                      0,              /* priv_data_len: 없음 */
                      1,              /* expect_class_max */
                      &my_exp_policy,
                      my_helper_cb);

    /* IPv6 헬퍼 */
    nf_ct_helper_init(&my_helpers[1], AF_INET6, IPPROTO_TCP,
                      MY_PROTO_NAME, MY_PROTO_PORT,
                      0, 1,
                      &my_exp_policy,
                      my_helper_cb);

    ret = nf_conntrack_helpers_register(my_helpers, ARRAY_SIZE(my_helpers));
    if (ret)
        pr_err("myproto: 헬퍼 등록 실패: %d\n", ret);

    return ret;
}

static void __exit my_helper_fini(void)
{
    nf_conntrack_helpers_unregister(my_helpers, ARRAY_SIZE(my_helpers));
}
사용자공간 헬퍼: 커널 3.6+에서는 NFCT_HELPER 넷링크 인터페이스를 통해 사용자공간에서 conntrack 헬퍼를 구현할 수도 있습니다(nfct-helper 도구 참조). 복잡한 프로토콜 파싱을 커널 모듈 없이 처리할 수 있어 개발·디버깅이 편리합니다.

ctnetlink는 Netfilter conntrack 정보를 사용자공간으로 전달하는 Netlink 인터페이스입니다. NFCT 이벤트를 구독해 conntrack 테이블 변경을 실시간으로 처리하거나, HA(고가용성) 환경에서 장비 간 conntrack 상태를 동기화하는 데 사용합니다.

이벤트상수발생 시점
NEW IPCTNL_MSG_CT_NEW 새 conntrack 엔트리 생성 시
UPDATE IPCTNL_MSG_CT_NEW + NLM_F_REPLACE 상태 변경 (ESTABLISHED, CLOSE 등)
DESTROY IPCTNL_MSG_CT_DELETE conntrack 엔트리 만료·삭제 시
EXP_NEW IPCTNL_MSG_EXP_NEW 새 expect 엔트리 등록 시
EXP_DESTROY IPCTNL_MSG_EXP_DELETE expect 엔트리 만료·삭제 시

libnetfilter_conntrack을 이용한 이벤트 수신

/* ctnetlink 이벤트 수신 예시 (C, libnetfilter_conntrack 사용) */
#include <libnetfilter_conntrack/libnetfilter_conntrack.h>

static int event_cb(enum nf_conntrack_msg_type type,
                    struct nf_conntrack *ct, void *data)
{
    char buf[1024];
    nfct_snprintf(buf, sizeof(buf), ct, type, NFCT_O_DEFAULT, NFCT_OF_SHOW_LAYER3);

    switch (type) {
    case NFCT_T_NEW:
        printf("[NEW]     %s\n", buf);
        break;
    case NFCT_T_UPDATE:
        printf("[UPDATE]  %s\n", buf);
        break;
    case NFCT_T_DESTROY:
        printf("[DESTROY] %s\n", buf);
        break;
    default:
        break;
    }
    return NFCT_CB_CONTINUE;
}

int main(void)
{
    struct nfct_handle *h = nfct_open(CONNTRACK,
                                       NF_NETLINK_CONNTRACK_NEW |
                                       NF_NETLINK_CONNTRACK_UPDATE |
                                       NF_NETLINK_CONNTRACK_DESTROY);
    if (!h) {
        perror("nfct_open");
        return 1;
    }

    nfct_callback_register(h, NFCT_T_ALL, event_cb, NULL);

    /* 이벤트 루프 (블로킹) */
    nfct_catch(h);

    nfct_close(h);
    return 0;
}
# 컴파일
gcc -o ct_events ct_events.c -lnetfilter_conntrack

# 실행 (root 권한 필요)
./ct_events

# conntrackd를 사용한 HA 동기화 (패키지 설치 후)
apt install conntrack conntrackd

# /etc/conntrackd/conntrackd.conf 설정 후:
conntrackd -d   # 데몬 시작
conntrackd -s   # 통계 확인

conntrackd를 이용한 HA 동기화

conntrackd는 ctnetlink 이벤트를 구독하고 네트워크를 통해 다른 장비의 conntrackd와 상태를 동기화합니다. 장비 장애 발생 시 페일오버 장비가 기존 연결 상태를 이어받아 세션 중단 없는 페일오버를 구현합니다.

# conntrackd.conf 핵심 설정 예시
Sync {
    Mode FTFW {         # Fault-Tolerant Failover Watchdog
        ResendQueueSize 131072;
        PurgeTimeout    5;
        FaultZeroDelay  on;
        MaxAttempts     10;
        CheckSum        on;
    }
    UDP {
        IPv4_address 192.168.1.1;    # 로컬 동기화 주소
        IPv4_Destination_Address 192.168.1.2;  # 피어 주소
        Port 3780;
        Interface eth1;
        Checksum on;
    }
}

General {
    Hashsize 32768;
    MaxEntries 65535;
    LogFile /var/log/conntrackd.log;
    LockFile /var/lock/conntrack.lock;
    UNIX {
        Path /var/run/conntrackd.ctl;
        Backlog 20;
    }
}

클라우드/컨테이너 환경 이슈

클라우드 네이티브 환경(Kubernetes, AWS, GCP)에서는 ALG가 오히려 애플리케이션을 방해하는 경우가 많습니다. 다음은 주요 이슈와 권장 대응 방법입니다.

Kubernetes에서의 SIP ALG 문제

# 문제: kube-proxy의 iptables 규칙과 SIP ALG 충돌
# kube-proxy가 Service IP를 DNAT하면, SIP ALG가 잘못된 IP로
# Contact/Via 헤더를 재작성해 SIP 등록 실패

# 확인 방법
sysctl net.netfilter.nf_conntrack_helper
# 값이 1이면 자동 ALG 적용 중

# 해결: ALG 비활성화
sysctl -w net.netfilter.nf_conntrack_helper=0

# 영구 설정 (/etc/sysctl.d/99-conntrack.conf)
echo "net.netfilter.nf_conntrack_helper=0" >> /etc/sysctl.d/99-conntrack.conf

# Kubernetes DaemonSet으로 노드 전체 적용
# (securityContext.privileged: true 필요)

AWS/GCP ALG 정책

클라우드 환경ALG 정책권장 대응
AWS EC2 Security Group은 ALG 미지원. RELATED 상태 개념 없음 FTP PASV 포트 범위를 Security Group에 직접 명시
AWS NLB L4 로드밸런서 — ALG 불가 FTP 서버를 고정 PASV 포트 범위로 설정
GCP VPC 방화벽 Stateless 규칙 기반 — ALG 미지원 허용 포트를 방화벽 규칙에 명시
온프레미스 Linux nf_conntrack_helper 활용 가능 nf_conntrack_helper=0 + 명시적 CT 연결 권장
컨테이너 (Docker) 호스트 nf_conntrack_helper 설정 상속 호스트에서 비활성화 후 컨테이너별 명시 설정

ALG 활성화/비활성화 비교

항목ALG 활성화ALG 비활성화
FTP PASV 자동 허용 (데이터 채널) PASV 포트 범위를 방화벽에 명시해야 함
SIP/VoIP 자동 RTP 허용 (단, 헤더 변조 위험) STUN/TURN 또는 SIP 프록시로 처리
보안 RELATED 자동 허용 — 공격 벡터 가능 명시적 규칙만 허용 — 더 안전
성능 페이로드 파싱 오버헤드 (소규모 영향) 페이로드 파싱 불필요 — 약간 빠름
암호화 트래픽 TLS/SRTP 페이로드 파싱 불가 — 무용지물 해당 없음 (애플리케이션 레벨에서 처리)
클라우드 호환성 클라우드 로드밸런서와 충돌 가능 클라우드 환경 친화적

컨테이너 환경 conntrack 최적화

# Kubernetes 노드 conntrack 설정
# /etc/sysctl.d/99-kubernetes-conntrack.conf

# 1. ALG 자동 로드 비활성화
net.netfilter.nf_conntrack_helper = 0

# 2. conntrack 테이블 크기 확대 (대규모 클러스터)
net.netfilter.nf_conntrack_max = 1048576
net.nf_conntrack_max = 1048576

# 3. TCP/UDP 타임아웃 조정
net.netfilter.nf_conntrack_tcp_timeout_established = 86400
net.netfilter.nf_conntrack_udp_timeout = 30
net.netfilter.nf_conntrack_udp_timeout_stream = 60

# 4. expect 타임아웃 축소 (expect 테이블 낭비 방지)
net.netfilter.nf_conntrack_expect_max = 1024

# 적용
sysctl --system
conntrack 테이블 고갈: 대규모 Kubernetes 클러스터에서 conntrack 테이블이 가득 차면 새 연결이 nf_conntrack: table full, dropping packet 오류와 함께 드롭됩니다. conntrack -C로 현재 사용량을 모니터링하고 nf_conntrack_max를 충분히 설정하세요.

H.323 / RTSP / Amanda 헬퍼 심화

H.323·RTSP·Amanda는 FTP/SIP보다 더 복잡한 다중 채널 구조를 가지며, 각 헬퍼는 제어 채널에서 여러 단계의 협상을 거쳐 미디어 채널 포트를 결정합니다.

H.323: 다중 채널 협상 구조

H.323은 ITU-T 표준 화상회의 프로토콜로, 단일 연결이 아닌 여러 단계의 채널 협상을 통해 미디어를 전송합니다. nf_conntrack_h323.c는 이 복잡한 구조를 처리하기 위해 ASN.1 PER 인코딩을 파싱합니다.

채널프로토콜/포트역할헬퍼 함수
RAS (Registration, Admission, Status) UDP 1719 게이트키퍼 등록·승인·상태 관리 expect_q931()에서 RAS expect 등록
Q.931 (Call Signaling) TCP 1720 H.323 호 설정·해제 (TPKT 캡슐화) h323_help()expect_h245()
H.245 (Control) TCP 동적 포트 미디어 능력 교환·채널 개방 협상 h245_help() → RTP/RTCP expect
RTP 미디어 UDP 동적 포트 오디오/비디오 스트림 전송 H.245 OpenLogicalChannel 파싱 후 등록
RTCP 제어 UDP (RTP 포트+1) RTP 품질 모니터링·통계 RTP expect와 함께 자동 등록
/* net/netfilter/nf_conntrack_h323.c — 핵심 함수 흐름 */

/* 1단계: Q.931 제어 채널(TCP 1720) 감시 */
static int h323_help(struct sk_buff *skb, unsigned int protoff,
                     struct nf_conn *ct, enum ip_conntrack_info ctinfo)
{
    /* TPKT 헤더 파싱 (H.323 메시지 경계 구분) */
    /* TPKT: 0x03 0x00 length(2) data... */

    /* H.225.0 Q.931 메시지 파싱 (ASN.1 PER 인코딩) */
    /* SETUP 메시지에서 H.245 주소 추출 */
    /* h245Addr: TransportAddress (IP + 포트) */

    return expect_h245(skb, ct, ctinfo, protoff, &h245_addr);
}

/* 2단계: H.245 포트에 대한 expect 등록 */
static int expect_h245(struct sk_buff *skb, struct nf_conn *ct,
                       enum ip_conntrack_info ctinfo,
                       unsigned int protoff,
                       struct nf_conntrack_man *exp_addr)
{
    struct nf_conntrack_expect *exp;

    exp = nf_ct_expect_alloc(ct);
    /* H.245 TCP 포트 expect 등록 */
    nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT,
                      nf_ct_l3num(ct),
                      NULL, &exp_addr->u3,
                      IPPROTO_TCP, NULL, &exp_addr->u.tcp.port);

    /* H.245 연결 수립 시 호출될 콜백 등록 */
    exp->expectfn = nat_h245;   /* NAT 환경에서 포트 재작성 */
    nf_ct_expect_related(exp);
    nf_ct_expect_put(exp);
    return NF_ACCEPT;
}

/* 3단계: H.245 채널에서 RTP/RTCP 포트 협상 감시 */
static int h245_help(struct sk_buff *skb, unsigned int protoff,
                     struct nf_conn *ct, enum ip_conntrack_info ctinfo)
{
    /* OpenLogicalChannel 메시지 파싱 */
    /* mediaChannel: 미디어 전송 주소 (UDP 포트) */
    /* mediaControlChannel: RTCP 제어 주소 (UDP 포트) */

    /* RTP expect 등록 */
    expect_rtp_rtcp(skb, ct, ctinfo, protoff, &rtp_addr, &rtcp_addr);
    return NF_ACCEPT;
}
H.323 클라이언트 192.168.1.100 방화벽 / H.323 ALG h323_help() Q.931 파싱·감시 h245_help() OpenLogicalChannel 파싱 expect 테이블 TCP: H.245 포트 UDP: RTP 포트 UDP: RTCP 포트 nat_h245() H.245 주소 재작성 H.323 서버 203.0.113.5 ① Q.931 TCP:1720 ② H.245 TCP:동적 ③ RTP UDP:동적 ④ RTCP UDP:동적+1 Q.931 연결 H.245 연결 RTP 스트림 RTCP 제어 H.323 채널 협상 순서: RAS → Q.931 → H.245 → RTP/RTCP 각 단계마다 nf_conntrack_expect 등록 → RELATED 자동 허용 ASN.1 PER 인코딩 파싱 (nf_conntrack_h323.c) CVE-2020-14305: out-of-bounds 취약점 수정 (5.10.x)

RTSP 헬퍼 동작

RTSP(Real Time Streaming Protocol, RFC 2326)는 미디어 스트리밍 제어 프로토콜입니다. nf_conntrack_rtsp.c는 RTSP SETUP 메시지의 Transport 헤더를 파싱해 RTP/RTCP 채널 포트를 추출합니다.

/* RTSP SETUP 메시지 예시:
 * SETUP rtsp://server/stream/track1 RTSP/1.0
 * CSeq: 3
 * Transport: RTP/AVP;unicast;client_port=49170-49171
 *            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 *            클라이언트가 수신할 RTP(49170)/RTCP(49171) 포트
 *
 * 서버 응답:
 * RTSP/1.0 200 OK
 * Transport: RTP/AVP;unicast;client_port=49170-49171;server_port=6970-6971
 *            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 *            서버가 전송할 RTP(6970)/RTCP(6971) 포트
 */

/* rtsp_parse_transport() — Transport 헤더 파싱 */
static int rtsp_parse_transport(const char *value, size_t len,
                                 struct rtsp_session *sess)
{
    /* "client_port=NNNN-MMMM" 파싱 */
    /* RTP 포트: NNNN, RTCP 포트: MMMM (= NNNN+1) */

    /* 서버 측 RTP expect 등록: client_port로 들어오는 패킷 허용 */
    nf_ct_expect_init(exp_rtp,  ..., IPPROTO_UDP, NULL, &client_rtp_port);
    nf_ct_expect_related(exp_rtp);

    /* 서버 측 RTCP expect 등록 */
    nf_ct_expect_init(exp_rtcp, ..., IPPROTO_UDP, NULL, &client_rtcp_port);
    nf_ct_expect_related(exp_rtcp);
}

Amanda 헬퍼 동작

Amanda(Advanced Maryland Automatic Network Disk Archiver)는 네트워크 백업 소프트웨어로, UDP 10080(amandaidx)으로 인덱스 서버에 연결하고, 실제 백업 데이터는 동적 TCP 포트를 협상합니다.

/* Amanda 프로토콜 협상 (단순 텍스트 기반):
 * 클라이언트 → 서버 (UDP 10080):
 *   "amanda 2 DATA 192.168.1.100 49152 INDEX 192.168.1.100 49153"
 *   DATA 포트: 백업 데이터 전송
 *   INDEX 포트: 파일 인덱스 전송
 */

/* nf_conntrack_amanda.c — 포트 파싱 */
static int amanda_help(struct sk_buff *skb, ...)
{
    /* "DATA" 토큰 뒤의 IP:PORT 파싱 */
    /* "INDEX" 토큰 뒤의 IP:PORT 파싱 */

    /* 각각 TCP expect 등록 */
    nf_ct_expect_init(exp_data,  ..., IPPROTO_TCP, NULL, &data_port);
    nf_ct_expect_related(exp_data);

    nf_ct_expect_init(exp_index, ..., IPPROTO_TCP, NULL, &index_port);
    nf_ct_expect_related(exp_index);
}

헬퍼 특성 비교 표

헬퍼제어 채널데이터 채널 수포트 개방 패턴복잡도NAT 지원
FTP TCP 21 1 (데이터) PORT/PASV 명령 파싱 낮음 nf_nat_ftp.c
SIP UDP/TCP 5060 2 (RTP + RTCP) SDP m= 라인 파싱 중간 nf_nat_sip.c
H.323 TCP 1720 + UDP 1719 3+ (H.245 + RTP + RTCP) ASN.1 PER 다단계 파싱 매우 높음 nf_nat_h323.c
RTSP TCP 554 2 (RTP + RTCP) Transport 헤더 파싱 중간 nf_nat_rtsp.c
Amanda UDP 10080 2 (DATA + INDEX) 텍스트 토큰 파싱 낮음 없음
PPTP TCP 1723 1 (GRE 프로토콜) Call ID 기반 GRE expect 중간 nf_nat_pptp.c

PPTP/GRE 헬퍼와 VPN 통과

PPTP(Point-to-Point Tunneling Protocol, RFC 2637)는 구형 VPN 프로토콜로, TCP 1723의 제어 채널과 GRE(프로토콜 번호 47) 데이터 채널의 두 채널로 구성됩니다. nf_conntrack_pptp.c는 PPTP 제어 채널을 파싱해 GRE 채널에 대한 expect를 등록합니다.

PPTP 제어 채널 파싱

/* PPTP 메시지 구조 (RFC 2637)
 * 모든 PPTP 메시지는 TCP 1723으로 전송
 *
 * PPTP Start-Control-Connection-Request (SCCRQ)
 * PPTP Start-Control-Connection-Reply  (SCCRP)
 * PPTP Outgoing-Call-Request (OCRQ): 클라이언트 Call ID
 * PPTP Outgoing-Call-Reply   (OCRP): 서버 Peer Call ID
 * PPTP Incoming-Call-Request (ICRQ): 서버 Call ID
 */

/* pptp_outbound_pkt(): 클라이언트→서버 패킷 처리 */
static int pptp_outbound_pkt(struct sk_buff *skb, struct nf_conn *ct,
                              enum ip_conntrack_info ctinfo,
                              struct PptpControlHeader *ctlh,
                              union pptp_ctrl_union *pptpReq)
{
    struct nf_ct_pptp_master *info = nfct_help_data(ct);
    __be16 msg_type = ctlh->messageType;

    switch (msg_type) {
    case PPTP_OUT_CALL_REQUEST:
        /* 클라이언트 Call ID 저장 */
        info->pns_call_id = pptpReq->ocreq.callID;
        /* GRE 데이터 채널 expect 등록 준비 */
        break;
    }
    return NF_ACCEPT;
}

/* pptp_inbound_pkt(): 서버→클라이언트 패킷 처리 */
static int pptp_inbound_pkt(struct sk_buff *skb, struct nf_conn *ct,
                             enum ip_conntrack_info ctinfo,
                             struct PptpControlHeader *ctlh,
                             union pptp_ctrl_union *pptpReq)
{
    struct nf_ct_pptp_master *info = nfct_help_data(ct);

    switch (ctlh->messageType) {
    case PPTP_OUT_CALL_REPLY:
        /* 서버 Peer Call ID 획득 */
        info->pac_call_id = pptpReq->ocrep.peersCallID;

        /* GRE 데이터 채널 expect 등록:
         * GRE 프로토콜 (47), Call ID 기반 식별 */
        exp_orig = nf_ct_expect_alloc(ct);
        nf_ct_expect_init(exp_orig, NF_CT_EXPECT_CLASS_DEFAULT,
                          nf_ct_l3num(ct),
                          &ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3,
                          &ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3,
                          IPPROTO_GRE,
                          &info->pac_call_id,   /* GRE key = Call ID */
                          &info->pns_call_id);
        nf_ct_expect_related(exp_orig);

        /* 역방향 GRE expect (서버→클라이언트) */
        exp_reply = nf_ct_expect_alloc(ct);
        /* ... 역방향 설정 ... */
        nf_ct_expect_related(exp_reply);
        break;
    }
    return NF_ACCEPT;
}

GRE 특수 처리: nf_conntrack_gre.c

GRE는 포트 번호가 없는 프로토콜이므로, nf_conntrack_gre.c는 GRE 헤더의 Key 필드(Call ID)를 포트 번호처럼 사용해 연결을 구분합니다.

/* net/netfilter/nf_conntrack_proto_gre.c */

/* GRE 4-튜플: (src_ip, dst_ip, src_callid, dst_callid) */
static bool gre_pkt_to_tuple(const struct sk_buff *skb, unsigned int dataoff,
                              struct net *net, struct nf_conntrack_tuple *tuple)
{
    const struct gre_hdr_pptp *pgrehdr;

    pgrehdr = skb_header_pointer(skb, dataoff, sizeof(*pgrehdr), &_pgrehdr);

    /* GRE Key 필드 = PPTP Call ID */
    tuple->src.u.gre.key = pgrehdr->callid;  /* 클라이언트 Call ID */
    tuple->dst.u.gre.key = 0;               /* 초기에는 0, expect에서 설정 */

    return true;
}

/* GRE conntrack 타임아웃 (기본값) */
static const unsigned int gre_timeouts[GRE_CT_MAX] = {
    [GRE_CT_UNREPLIED] = 30 * HZ,   /* 30초: 응답 없는 GRE */
    [GRE_CT_REPLIED]   = 180 * HZ,  /* 3분: 응답 있는 GRE 세션 */
};

NAT + PPTP: nf_nat_pptp.c의 Call ID 재작성

NAT 환경에서 여러 클라이언트가 동일한 PPTP 서버에 연결할 때, Call ID가 충돌하지 않도록 nf_nat_pptp.c가 Call ID를 재작성합니다.

/* nf_nat_pptp.c — Call ID 재작성 */
static void pptp_nat_expected(struct nf_conn *ct,
                               struct nf_conntrack_expect *exp)
{
    /* 새 GRE 연결의 Call ID를 NAT 후 값으로 수정 */
    /* PPTP 제어 채널의 페이로드(Outgoing-Call-Request)도 수정 */

    /* GRE Key 필드 = 재할당된 Call ID로 SNAT */
    range.flags = NF_NAT_RANGE_PROTO_SPECIFIED;
    range.min_proto.gre.key = range.max_proto.gre.key = new_callid;
    nf_nat_setup_info(ct, &range, NF_NAT_MANIP_SRC);
}

PPTP 보안 취약성과 대안

VPN 프로토콜보안 강도conntrack 헬퍼현재 권고
PPTP 취약 (MS-CHAPv2 크랙 가능, GRE 평문) nf_conntrack_pptp + nf_conntrack_gre 사용 금지 (RFC 2637 구형)
L2TP/IPsec 양호 (IPsec ESP 암호화) 헬퍼 불필요 (UDP 500/4500 고정) 레거시 환경에서 사용 가능
WireGuard 우수 (ChaCha20-Poly1305, Curve25519) 불필요 (UDP 51820 단일 포트) 현대 환경 권장
OpenVPN 양호 (TLS 1.3) 불필요 (UDP/TCP 1194 단일 포트) 광범위 호환성 필요 시 사용
IPsec/XFRM 우수 (AES-GCM, PFS) 불필요 (ESP 프로토콜 47, UDP 500/4500) 기업 환경 표준
# PPTP 헬퍼 비활성화 (클라우드/보안 환경 권장)
# PPTP 헬퍼 모듈 차단
echo "install nf_conntrack_pptp /bin/true" >> /etc/modprobe.d/disable-pptp.conf
echo "install nf_nat_pptp /bin/true"       >> /etc/modprobe.d/disable-pptp.conf
echo "install nf_conntrack_gre /bin/true"  >> /etc/modprobe.d/disable-pptp.conf

# 이미 로드된 경우 언로드
rmmod nf_nat_pptp
rmmod nf_conntrack_pptp
rmmod nf_conntrack_gre

# PPTP 대신 WireGuard 설정 (헬퍼 불필요, UDP 51820만 허용)
iptables -A INPUT  -p udp --dport 51820 -j ACCEPT
iptables -A OUTPUT -p udp --sport 51820 -j ACCEPT
# RELATED 규칙 불필요 — WireGuard는 단일 UDP 포트 사용
PPTP 사용 금지: PPTP는 2012년 Microsoft의 MS-CHAPv2 권고 폐기 이후 보안상 안전하지 않습니다. GRE 데이터 채널은 암호화되지 않아 트래픽 노출 위험이 있습니다. nf_conntrack_pptp 모듈은 클라우드·기업 환경에서 로드하지 않는 것을 강력히 권장합니다.

ALG/헬퍼 보안 이슈

conntrack 헬퍼는 강력한 기능을 제공하지만, 페이로드를 파싱해 방화벽 규칙을 자동으로 우회시키는 특성상 다수의 보안 취약점이 발견되었습니다. 자동 헬퍼 할당(nf_conntrack_helper=1)은 특히 위험합니다.

주요 CVE 취약점

CVE모듈취약점 유형영향수정 버전
CVE-2014-8160 nf_conntrack_sip 자동 헬퍼 할당으로 임의 포트 개방 방화벽 규칙 우회 — 공격자가 임의 포트를 RELATED로 열 수 있음 3.18.x
CVE-2020-14305 nf_conntrack_h323 out-of-bounds write (ASN.1 파싱) 커널 패닉 또는 권한 상승 가능 5.10.x
CVE-2021-3773 nf_conntrack_ftp FTP 페이로드 조작으로 RELATED 허용 우회 방화벽 규칙을 우회해 임의 연결을 RELATED로 위장 5.15.x
CVE-2019-19922 nf_conntrack_sip SIP 메시지 조작 → 잘못된 RTP expect 등록 공격자 제어 포트가 RELATED로 허용 5.4.x

자동 헬퍼 할당 보안 문제

/* 취약 시나리오: nf_conntrack_helper=1 (구형 기본값)
 *
 * 공격자가 방화벽 내부 호스트와 통신하면서
 * SIP 포트(5060)로 조작된 SDP 메시지를 전송:
 *
 * 조작된 SDP:
 *   m=audio 22 RTP/AVP 0       ← 포트 22 (SSH)를 RTP 포트로 위장
 *
 * 결과: 방화벽이 포트 22를 RELATED로 자동 허용
 * → SSH 방화벽 규칙 우회 가능
 */

# 위험한 설정 (사용 금지)
sysctl -w net.netfilter.nf_conntrack_helper=1  # 자동 헬퍼 할당

# 안전한 설정 (커널 4.7+ 기본값)
sysctl -w net.netfilter.nf_conntrack_helper=0  # 수동 헬퍼 할당만 허용

수동 헬퍼 할당: nftables 방식 (권장)

#!/usr/sbin/nft -f
# /etc/nftables.d/alg-helpers.conf
# 안전한 수동 헬퍼 할당 (특정 IP/포트에만 적용)

table ip alg_helpers {
    # 헬퍼 객체 정의
    ct helper ftp_helper {
        type "ftp" protocol tcp;
        l3proto ip;
    }

    ct helper sip_helper {
        type "sip" protocol udp;
        l3proto ip;
    }

    chain prerouting {
        type filter hook prerouting priority raw; policy accept;

        # FTP 서버 트래픽에만 FTP 헬퍼 적용 (특정 서버 IP 지정 권장)
        ip daddr 10.0.0.10 tcp dport 21 ct helper set "ftp_helper"

        # SIP 트래픽에만 SIP 헬퍼 적용 (신뢰할 수 있는 SIP 프록시만)
        ip saddr 10.0.0.20 udp dport 5060 ct helper set "sip_helper"

        # 나머지는 헬퍼 미적용 (자동 허용 없음)
    }
}

헬퍼별 최대 expect 수 제한

/* nf_conntrack_sip.c — 동시 대화 최대 수 제한 */
static unsigned int sip_max_pending_dialogs __read_mostly = 8;
module_param(sip_max_pending_dialogs, uint, 0644);

/* 각 헬퍼의 expect_policy.max_expected:
 * FTP:    max_expected = 1  (데이터 채널 1개)
 * SIP:    max_expected = 8  (동시 통화 수, sip_max_pending_dialogs)
 * H.323:  max_expected = 4  (H.245 + RTP + RTCP × 2)
 * RTSP:   max_expected = 2  (RTP + RTCP)
 */

/* 제한 이유: expect 테이블 고갈 방지 + DoS 완화 */
/* 공격자가 대량의 SIP INVITE를 전송해 expect 테이블을 가득 채우는 공격 방어 */

# SIP 동시 대화 수 제한 (모듈 로드 시)
modprobe nf_conntrack_sip sip_max_pending_dialogs=4

NGFW에서 ALG 비활성화 전략

# 단계적 ALG 비활성화 전략

# 1단계: ALG 자동 로드 비활성화 (즉시 적용)
sysctl -w net.netfilter.nf_conntrack_helper=0

# 2단계: 불필요한 헬퍼 모듈 블랙리스트 등록
cat >> /etc/modprobe.d/conntrack-security.conf << 'EOF'
# PPTP — 보안 취약, 사용 금지
install nf_conntrack_pptp /bin/true
install nf_nat_pptp /bin/true
install nf_conntrack_gre /bin/true

# H.323 — CVE-2020-14305 이전 커널에서 특히 위험
# install nf_conntrack_h323 /bin/true  # 필요 시 활성화

# Amanda — 백업 트래픽이 없는 환경
install nf_conntrack_amanda /bin/true

# IRC DCC — 레거시, 보안 환경에서 불필요
install nf_conntrack_irc /bin/true
EOF

# 3단계: 필요한 헬퍼만 명시적 할당 (위 nftables 설정 적용)
# 4단계: 헬퍼 없이 PASV 포트 범위 직접 허용 (FTP 대안)
nft add rule ip filter forward tcp dport 50000-51000 accept

SELinux/AppArmor와 헬퍼 연동

# SELinux에서 conntrack 헬퍼 관련 정책 확인
# conntrack 헬퍼는 커널 모듈이므로 SELinux module_t 도메인

# AVC 거부 로그 확인 (conntrack 관련)
ausearch -m AVC -ts recent | grep conntrack

# conntrack 관련 SELinux 부울 확인
getsebool -a | grep nf_
# nf_conntrack_ftp_ports -- on/off

# FTP ALG 특정 포트 허용 (SELinux 정책)
semanage port -a -t ftp_data_port_t -p tcp 50000-51000

# AppArmor: nf_conntrack 모듈 로드 권한
# /etc/apparmor.d/abstractions/base 에서
# capability net_admin 필요 (modprobe 실행 시)

타임아웃 정책 심화

conntrack 엔트리의 타임아웃은 연결 추적 테이블의 메모리 사용량과 세션 지속성을 결정하는 핵심 파라미터입니다. 프로토콜과 상태별로 다른 기본값이 설정되어 있으며, nftables의 ct timeout 객체로 세밀하게 커스터마이즈할 수 있습니다.

프로토콜별 기본 타임아웃

프로토콜상태기본 타임아웃sysctl 키
TCP SYN_SENT 120초 nf_conntrack_tcp_timeout_syn_sent
SYN_RECV 60초 nf_conntrack_tcp_timeout_syn_recv
ESTABLISHED 432000초 (5일) nf_conntrack_tcp_timeout_established
FIN_WAIT 120초 nf_conntrack_tcp_timeout_fin_wait
CLOSE_WAIT 60초 nf_conntrack_tcp_timeout_close_wait
LAST_ACK 30초 nf_conntrack_tcp_timeout_last_ack
TIME_WAIT 120초 nf_conntrack_tcp_timeout_time_wait
CLOSE 10초 nf_conntrack_tcp_timeout_close
UDP UNREPLIED 30초 nf_conntrack_udp_timeout
ASSURED (양방향) 180초 nf_conntrack_udp_timeout_stream
ICMP 기본 30초 nf_conntrack_icmp_timeout
ICMPv6 30초 nf_conntrack_icmpv6_timeout
GRE UNREPLIED 30초 nf_conntrack_gre_timeout
REPLIED 180초 nf_conntrack_gre_timeout_stream

nftables ct timeout 객체로 커스텀 정책 설정

#!/usr/sbin/nft -f
# /etc/nftables.d/ct-timeout.conf
# 서비스별 conntrack 타임아웃 커스터마이즈

table ip ct_timeouts {

    # 1. 일반 웹 서비스용 타임아웃 (기본보다 짧게)
    ct timeout web_timeout {
        protocol tcp;
        l3proto ip;
        policy = {
            established: 1800,    # 30분 (기본 5일 → 30분으로 단축)
            fin_wait:     30,
            close_wait:   15,
            last_ack:     15,
            time_wait:    30,
            close:        5,
        }
    }

    # 2. 대용량 파일 전송(FTP/SFTP)용 타임아웃 (길게)
    ct timeout bulk_transfer_timeout {
        protocol tcp;
        l3proto ip;
        policy = {
            established: 86400,   # 24시간 (대용량 전송 중단 방지)
            syn_sent:    300,     # 5분
            syn_recv:    120,
            fin_wait:    300,
            close_wait:  120,
            last_ack:    60,
            time_wait:   240,
        }
    }

    # 3. 실시간 스트리밍용 UDP 타임아웃
    ct timeout streaming_udp_timeout {
        protocol udp;
        l3proto ip;
        policy = {
            unreplied: 10,     # 10초 (응답 없는 UDP 빠르게 제거)
            replied:   30,     # 30초 (RTP 스트림: 짧게 설정)
        }
    }

    # 4. DNS 쿼리용 초단시간 타임아웃
    ct timeout dns_timeout {
        protocol udp;
        l3proto ip;
        policy = {
            unreplied: 5,
            replied:   10,
        }
    }

    chain prerouting {
        type filter hook prerouting priority raw; policy accept;

        # 웹 트래픽에 web_timeout 적용
        tcp dport { 80, 443 } ct timeout set "web_timeout"

        # FTP 제어/데이터 채널에 bulk_transfer_timeout 적용
        tcp dport { 20, 21 } ct timeout set "bulk_transfer_timeout"
        tcp sport { 20, 21 } ct timeout set "bulk_transfer_timeout"

        # RTP 포트 범위에 streaming 타임아웃 적용
        udp dport 10000-20000 ct timeout set "streaming_udp_timeout"

        # DNS 타임아웃
        udp dport 53 ct timeout set "dns_timeout"
    }
}

헬퍼 연결의 expect 타임아웃

nf_ct_expect_set_timeout()으로 각 expect 엔트리의 만료 시간을 개별 설정할 수 있습니다. 기본 타임아웃은 헬퍼의 expect_policy.timeout 필드에서 결정됩니다.

/* expect 타임아웃 기본값 (각 헬퍼의 expect_policy.timeout 필드) */
/* FTP:    60초 — PASV 응답 후 60초 내 데이터 연결 없으면 제거 */
/* SIP:    181초 — SIP 다이얼로그 타이머와 맞춤 */
/* H.323:  300초 — H.245 협상 완료까지 여유 시간 */
/* RTSP:   300초 — SETUP 후 PLAY까지 허용 시간 */
/* PPTP:   30초  — GRE 채널 expect 빠른 만료 */

/* 커스텀 헬퍼에서 타임아웃 동적 조정 */
static int my_helper_cb(struct sk_buff *skb, ...)
{
    struct nf_conntrack_expect *exp = nf_ct_expect_alloc(ct);
    /* ... expect 초기화 ... */

    /* 타임아웃을 패킷 내용에 따라 동적 설정 */
    unsigned int timeout = 120;  /* 기본 2분 */
    if (is_large_transfer)
        timeout = 600;  /* 대용량 전송 시 10분 */

    exp->timeout.expires = jiffies + timeout * HZ;

    nf_ct_expect_related(exp);
    nf_ct_expect_put(exp);
    return NF_ACCEPT;
}

타임아웃 만료 후 conntrack GC 동작

/* 타임아웃 만료 시 커널 내부 동작 흐름:
 *
 * 1. 타이머 만료: death_by_timeout() 호출
 * 2. conntrack 상태를 "dying" 마크
 * 3. ctnetlink DESTROY 이벤트 발생 → conntrackd에 통보
 * 4. 해시 테이블에서 제거 → GC 큐에 추가
 * 5. nf_ct_gc_worker() 주기적 실행으로 메모리 해제
 *
 * GC 설정:
 */
# GC 개입 임계치 (conntrack 테이블이 이 비율 이상 채워지면 GC 강제 실행)
cat /proc/sys/net/netfilter/nf_conntrack_buckets

# 고부하 환경 최적화 sysctl 설정
# /etc/sysctl.d/99-conntrack-tuning.conf

# TCP ESTABLISHED 타임아웃: 5일 → 1시간 (단기 연결 중심 환경)
net.netfilter.nf_conntrack_tcp_timeout_established = 3600

# TCP TIME_WAIT 단축 (TIME_WAIT 엔트리 빠른 재활용)
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30

# UDP 타임아웃 단축 (UDP 기반 서비스가 없는 환경)
net.netfilter.nf_conntrack_udp_timeout = 15
net.netfilter.nf_conntrack_udp_timeout_stream = 60

# conntrack 최대 버킷 수 증가 (해시 충돌 감소)
# 주의: nf_conntrack_buckets는 부팅 시에만 변경 가능 (또는 모듈 파라미터)
# modprobe nf_conntrack hashsize=131072

# conntrack 최대 엔트리 수 (hashsize의 8배 권장)
net.netfilter.nf_conntrack_max = 1048576

conntrackd는 ctnetlink 이벤트를 구독하고 Primary/Backup 서버 간 conntrack 상태를 동기화하는 데몬입니다. Pacemaker 같은 클러스터 매니저와 연동해 세션 중단 없는 장애 복구(Stateful HA Failover)를 구현합니다.

conntrackd 아키텍처

/* conntrackd 내부 구조 (사용자공간 데몬):
 *
 * [Primary NGFW]                    [Backup NGFW]
 * ┌─────────────────────────────┐   ┌─────────────────────────────┐
 * │ conntrackd (Primary)        │   │ conntrackd (Backup)         │
 * │  ┌────────────────────────┐ │   │  ┌────────────────────────┐ │
 * │  │ ctnetlink 이벤트 수신  │ │   │  │ ctnetlink 이벤트 수신  │ │
 * │  │ (NFCT_T_NEW/UPD/DEL)  │ │   │  │ (로컬 상태 갱신)       │ │
 * │  └──────────┬─────────────┘ │   │  └──────────┬─────────────┘ │
 * │  ┌──────────▼─────────────┐ │   │  ┌──────────▼─────────────┐ │
 * │  │ State Sync Engine      │◄───►│  │ State Sync Engine      │ │
 * │  │ (FTFW 프로토콜)        │ │   │  │ (수신 + 로컬 CT 갱신)  │ │
 * │  └────────────────────────┘ │   │  └────────────────────────┘ │
 * └─────────────────────────────┘   └─────────────────────────────┘
 *              │  UDP 멀티캐스트/유니캐스트 동기화 채널 │
 *              └──────────────────────────────────────────┘
 */

conntrackd.conf 완전 예제

# /etc/conntrackd/conntrackd.conf
# Primary NGFW 설정 (Backup은 IPv4_address ↔ IPv4_Destination_Address 교환)

Sync {
    # FTFW: Fault-Tolerant Failover Watchdog — 패킷 손실 허용, 재전송 지원
    Mode FTFW {
        ResendQueueSize 131072;   # 재전송 큐 크기 (패킷 단위)
        PurgeTimeout    5;        # 확인 안 된 엔트리 제거 대기 (초)
        FaultZeroDelay  on;       # 장애 감지 즉시 백업으로 전환
        MaxAttempts     10;       # 최대 재전송 시도 횟수
        CheckSum        on;       # UDP 동기화 패킷 체크섬 검증
    }

    # UNICAST 동기화 (멀티캐스트 불가 환경)
    UDP {
        IPv4_address            192.168.100.1;   # 로컬 동기화 인터페이스 IP
        IPv4_Destination_Address 192.168.100.2;  # Backup 서버 IP
        Port      3780;                          # 동기화 포트 (방화벽 허용 필요)
        Interface bond0;                         # 동기화 전용 인터페이스
        Checksum  on;
        SndSocketBuffer 1249280;  # 송신 소켓 버퍼 크기
        RcvSocketBuffer 1249280;  # 수신 소켓 버퍼 크기
    }

    # 또는 MULTICAST 동기화 (동일 L2 세그먼트)
    # Multicast {
    #     IPv4_address  225.0.0.50;
    #     Group         3780;
    #     IPv4_interface 192.168.100.1;
    #     Interface     bond0;
    #     Checksum      on;
    # }
}

General {
    Hashsize    32768;               # 내부 해시 테이블 크기
    MaxEntries  65535;               # 최대 동기화 엔트리 수
    LogFile     /var/log/conntrackd.log;
    Syslog      yes;
    LockFile    /var/lock/conntrack.lock;
    StripNAT    off;                 # NAT 정보도 동기화 (on이면 제거)

    UNIX {
        Path    /var/run/conntrackd.ctl;
        Backlog 20;
    }

    # 동기화 필터: 불필요한 프로토콜 무시
    Filter From Kernelspace {
        # ICMP는 동기화 제외 (짧은 수명, 동기화 불필요)
        Protocol Accept {
            TCP;
            UDP;
            # GRE;  # PPTP 환경에서는 포함
        }
        # FTP 데이터 채널(포트 20)도 동기화 제외
        # (헬퍼 정보는 어차피 동기화 불가)
        Address Ignore {
            IPv4_address 127.0.0.1;         # 루프백 제외
            IPv4_address 169.254.0.0/16;    # link-local 제외
        }
    }
}

NFCT 이벤트 처리 흐름

/* conntrackd가 처리하는 ctnetlink 이벤트 */

/* NFCT_T_NEW: 새 conntrack 엔트리 생성 */
/* → Backup에 ADD 메시지 전송 → Backup이 로컬 CT 테이블에 추가 */

/* NFCT_T_UPDATE: 상태 변경 (SYN_RECV → ESTABLISHED 등) */
/* → Backup에 UPDATE 메시지 전송 → Backup이 로컬 엔트리 업데이트 */

/* NFCT_T_DESTROY: 엔트리 만료/삭제 */
/* → Backup에 DELETE 메시지 전송 → Backup이 로컬 엔트리 제거 */

/* 헬퍼 상태 동기화 한계:
 * conntrackd는 기본 conntrack 5-튜플과 상태만 동기화
 * 헬퍼의 내부 상태 (expect 엔트리, 헬퍼별 사적 데이터)는 동기화 불가
 *
 * 예: FTP PASV expect는 Backup으로 전송되지 않음
 *     장애 복구 후 진행 중인 FTP 전송은 데이터 채널이 끊길 수 있음
 *     → FTP는 세션 재시작 필요 (통상 수용 가능)
 */

# 동기화 상태 실시간 확인
conntrackd -s             # 전체 통계
conntrackd -s network     # 네트워크 동기화 통계
conntrackd -s cache       # 캐시 동기화 통계
conntrackd -s runtime     # 런타임 통계
conntrackd -s queue       # 큐 통계

Pacemaker + conntrackd HA 구성

Primary NGFW 192.168.1.1 (Active) conntrack 테이블 TCP ESTABLISHED × 50,000 conntrackd FTFW 모드 (Primary) Pacemaker / Corosync VIP: 203.0.113.1 (보유) Backup NGFW 192.168.1.2 (Standby) conntrack 테이블 (복제) TCP ESTABLISHED × 50,000 (동기화) conntrackd FTFW 모드 (Backup) Pacemaker / Corosync VIP: 대기 중 동기화 채널 UDP 멀티캐스트/유니캐스트 포트 3780 (FTFW 프로토콜) NEW/UPDATE/DESTROY 이벤트 연속 동기화 (실시간) Corosync Heartbeat 장애 복구 시나리오 ① Pacemaker: Primary 장애 감지 (heartbeat 소실) → VIP 203.0.113.1을 Backup으로 이전 ② conntrackd: Backup이 Primary로 승격 → 동기화된 conntrack 테이블로 기존 세션 유지 ③ 기존 TCP 세션 유지 (단, 헬퍼 expect 손실로 FTP/SIP 데이터 채널은 재협상 필요)
# Pacemaker + conntrackd 리소스 정의 (crm 형식)
primitive p_conntrackd ocf:heartbeat:conntrackd \
    params ctd_config="/etc/conntrackd/conntrackd.conf" \
           ctd_binary="/usr/sbin/conntrackd" \
    op start   timeout="30s" \
    op stop    timeout="30s" \
    op monitor interval="10s" timeout="30s"

# VIP 리소스
primitive p_vip IPaddr2 \
    params ip="203.0.113.1" cidr_netmask="24" nic="eth0" \
    op monitor interval="5s"

# 동일 노드에서 VIP와 conntrackd 함께 실행
colocation co_vip_conntrackd inf: p_vip p_conntrackd
order ord_conntrackd_before_vip mandatory: p_conntrackd p_vip

# 노드 순서 (Primary 우선)
location loc_primary p_vip 100: primary-node
# conntrackd 페일오버 스크립트 (Pacemaker notify action)
# /usr/lib/ocf/resource.d/heartbeat/conntrackd

# Primary → Backup 전환 시:
conntrackd -t          # 내부 캐시 → 커널 CT 테이블 커밋 (Backup 승격 시)
conntrackd -f          # 커널 CT 테이블 → 내부 캐시 동기화 (Primary 복귀 시)

# 운영 명령
conntrackd -R          # 커널로부터 CT 상태 다시 읽기 (리셋)
conntrackd -B          # Bulk send: 전체 CT 상태를 Backup으로 일괄 전송
conntrackd -s stats    # 상세 통계 출력
헬퍼 상태 동기화 한계: conntrackd는 기본 conntrack 5-튜플과 TCP/UDP 상태만 동기화합니다. FTP PASV expect, SIP RTP expect 같은 헬퍼 내부 상태는 동기화되지 않습니다. 따라서 장애 복구 후 진행 중인 FTP 파일 전송이나 활성 SIP 통화는 미디어 채널 재협상이 필요할 수 있습니다. 장기 세션이 많은 환경에서는 이 제한을 운영 정책에 반영해야 합니다.

커널 소스 구조

conntrack 헬퍼 관련 커널 소스는 net/netfilter/include/net/netfilter/에 위치합니다.

파일 경로역할
net/netfilter/nf_conntrack_core.c conntrack 코어: 해시 테이블, 상태 머신, 타이머
net/netfilter/nf_conntrack_helper.c 헬퍼 등록/조회 API (nf_conntrack_helper_register())
net/netfilter/nf_conntrack_expect.c expect 테이블 관리 (nf_ct_expect_related())
net/netfilter/nf_conntrack_ftp.c FTP ALG 구현 (PORT/PASV 파싱)
net/netfilter/nf_conntrack_sip.c SIP ALG 구현 (SDP 파싱, RTP expect)
net/netfilter/nf_conntrack_h323.c H.323 ALG (복잡한 ASN.1 파싱 포함)
net/netfilter/nf_conntrack_pptp.c PPTP ALG (GRE Call ID 기반 expect)
net/netfilter/nf_nat_ftp.c FTP NAT 페이로드 재작성 (TCP 시퀀스 조정 포함)
net/netfilter/nf_nat_sip.c SIP NAT 페이로드 재작성 (Contact/Via 헤더 수정)
net/netfilter/nf_conntrack_netlink.c ctnetlink 구현 (Netlink 기반 이벤트 전달)
include/net/netfilter/nf_conntrack_helper.h nf_conntrack_helper 구조체 정의
include/net/netfilter/nf_conntrack_expect.h nf_conntrack_expect 구조체 정의

nf_conntrack_expect 구조체

/* include/net/netfilter/nf_conntrack_expect.h */
struct nf_conntrack_expect {
    /* expect 해시 테이블 노드 */
    struct hlist_node   hnode;
    struct list_head    lnode;  /* master ct의 expect 리스트 */

    /* 예상 연결의 5-튜플과 마스크 */
    struct nf_conntrack_tuple  tuple;
    struct nf_conntrack_tuple  mask;

    /* 이 expect를 등록한 master conntrack */
    struct nf_conn             *master;

    /* expect 만료 타이머 */
    struct timer_list          timeout;

    /* 연결된 헬퍼 */
    struct nf_conntrack_helper *helper;

    /* expect 클래스 (헬퍼당 여러 클래스 가능) */
    unsigned int               class;

    /* expect 매칭 시 호출되는 콜백 (NAT 연동 등) */
    void (*expectfn)(struct nf_conn *new,
                     struct nf_conntrack_expect *this);

    unsigned int               flags;
    unsigned int               id;
};

헬퍼 → NAT 연동 경로

/* 헬퍼가 expect를 등록할 때 NAT 연동 콜백 설정 예 (FTP) */
exp->expectfn = nf_nat_ftp_expected;

/* nf_nat_ftp_expected()는 데이터 채널 연결 수립 시 호출되어:
 * 1. 데이터 채널에 SNAT/DNAT 규칙 자동 추가
 * 2. NAT IP:PORT를 데이터 채널 연결에 반영
 */

진단

conntrack 헬퍼 관련 문제를 진단하는 주요 도구와 명령을 정리합니다.

기본 진단 명령

# 1. conntrack 테이블 전체 조회 (RELATED 엔트리 확인)
conntrack -L | grep RELATED

# 2. expect 테이블 확인 (헬퍼가 등록한 예상 연결)
conntrack -L expect

# 3. conntrack 통계 확인 (드롭·insert_failed 확인)
conntrack -S

# 4. 특정 IP의 conntrack 엔트리 조회
conntrack -L --src-nat --orig-src 192.168.1.100

# 5. conntrack 테이블 현재 사용량 / 최대값 확인
conntrack -C                              # 현재 엔트리 수
cat /proc/sys/net/netfilter/nf_conntrack_max  # 최대값
cat /proc/sys/net/netfilter/nf_conntrack_count  # 현재 사용수

# 6. 헬퍼 등록 상태 확인
cat /proc/net/nf_conntrack_helper 2>/dev/null || \
  nf-queue --list-helper 2>/dev/null

# 7. 실시간 conntrack 이벤트 모니터링
conntrack -E

# 8. FTP 연결 추적 (tcpdump와 병행)
tcpdump -i eth0 -A 'tcp port 21' &
conntrack -E -p tcp --dport 21

iptables 헬퍼 연결 확인

# raw 테이블에서 CT 타깃 설정 확인
iptables -t raw -L -v -n

# 헬퍼가 올바르게 연결된 경우:
# Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
#  pkts bytes target  prot  opt  in   out  source  destination
#   123  4567  CT      tcp   --   *    *    0.0.0.0/0  0.0.0.0/0  tcp dpt:21 CT helper ftp

# nftables로 확인
nft list table ip raw

커널 디버그 로그 활성화

# conntrack 디버그 로그 활성화 (동적 디버그)
echo 'module nf_conntrack +p'       | tee /sys/kernel/debug/dynamic_debug/control
echo 'module nf_conntrack_ftp +p'   | tee /sys/kernel/debug/dynamic_debug/control
echo 'module nf_conntrack_sip +p'   | tee /sys/kernel/debug/dynamic_debug/control

# 로그 확인
dmesg -w | grep -E 'conntrack|nf_ct'

# 또는 /proc/net/nf_conntrack 직접 확인
watch -n 1 'cat /proc/net/nf_conntrack | grep -i ftp'

# 다이나믹 디버그 해제
echo 'module nf_conntrack_ftp -p' | tee /sys/kernel/debug/dynamic_debug/control

bpftrace를 이용한 헬퍼 추적

# nf_ct_expect_related() 호출 추적
bpftrace -e '
kprobe:nf_ct_expect_related {
    printf("[expect_related] pid=%d comm=%s\n", pid, comm);
}
kprobe:nf_conntrack_helper_register {
    printf("[helper_register] pid=%d comm=%s\n", pid, comm);
}'

# conntrack 테이블 고갈 감지
bpftrace -e '
tracepoint:netfilter:nf_conntrack_alloc_overflow {
    printf("[OVERFLOW] conntrack table full! time=%lu\n", nsecs);
}'

일반적인 문제와 해결책

증상원인해결책
FTP 파일 전송 실패 (연결 거부) FTP 헬퍼 미로드 또는 CT 연결 미설정 modprobe nf_conntrack_ftp + CT 타깃 설정
SIP 통화 한쪽만 들림 SIP ALG가 Contact 헤더를 잘못 재작성 rmmod nf_conntrack_sip + STUN/TURN 사용
nf_conntrack: table full conntrack 테이블 고갈 nf_conntrack_max 증가 + 타임아웃 단축
expect 등록됐으나 RELATED 미분류 expect 만료(타임아웃) 또는 5-튜플 불일치 타임아웃 늘리기, expect 마스크 확인
헬퍼 로드됐으나 동작 안 함 nf_conntrack_helper=0 + CT 타깃 미설정 iptables CT 타깃으로 명시적 연결
Kubernetes Pod 간 FTP 실패 kube-proxy DNAT + ALG 충돌 ALG 비활성화 + PASV 포트 범위 고정

conntrack 헬퍼와 ALG를 더 깊이 이해하려면 다음 문서를 참고하세요.