NAT (Network Address Translation)

Linux 커널의 NAT는 별도 마법이 아니라 nf_natnf_conntrack가 결합된 상태 기반 주소 변환 경로입니다. 이 문서는 SNAT·DNAT·MASQUERADE·REDIRECT, hairpin NAT, helper/ALG, CGNAT, NAT64와 NPTv6의 차이, nftables flowtable 가속, 튜닝 포인트와 실제 장애 분석 절차를 공식 문서와 커널 소스 기준으로 다시 정리합니다.

전제 조건: 네트워크 스택, 라우팅, Netfilter 문서를 먼저 읽으세요. NAT는 라우팅 앞뒤 어느 훅에서 어떤 필드가 바뀌는지 이해하지 못하면 규칙이 맞아 보여도 실제 패킷 경로를 잘못 해석하기 쉽습니다.
일상 비유: 이 개념은 건물 대표 번호와 교환실과 비슷합니다. 외부에서는 대표 번호 하나만 보지만, 교환실은 내부 방 번호와 통화 상태를 기억해 두고 올바른 방으로 연결합니다. NAT도 첫 패킷에서 변환 표를 만들고, 이후 패킷은 그 표를 따라갑니다.

핵심 요약

  • conntrack — 같은 흐름인지 판별하고 원본/응답 튜플을 기억하는 상태 엔진입니다.
  • NAT 바인딩 — 첫 패킷에서 선택된 주소·포트 변환 결과입니다.
  • SNAT — 출발지 주소와 포트를 바꿔 외부로 나갈 수 있게 합니다.
  • DNAT — 목적지 주소와 포트를 바꿔 내부 서비스로 포워딩합니다.
  • Hairpin NAT — 내부 클라이언트가 외부 공개 주소로 같은 내부 서버에 접근할 때 필요한 역경로 유지 기법입니다.

단계별 이해

  1. 첫 패킷을 찾기
    룰셋이 아니라 “어떤 훅에서 최초 바인딩이 만들어졌는가”를 먼저 봅니다.
  2. 라우팅 전후를 나누기
    PREROUTING의 DNAT는 라우팅 결정에 영향을 주고, POSTROUTING의 SNAT는 출력 경로 직전에 적용됩니다.
  3. conntrack 상태를 확인하기
    후속 패킷은 규칙을 다시 찾지 않으므로, 실패 원인은 룰보다 상태 테이블과 타이머에 있는 경우가 많습니다.
  4. 대칭 경로를 검증하기
    hairpin, 멀티홈, 컨테이너 환경에서는 응답이 반드시 같은 번역기를 거쳐 돌아오도록 설계해야 합니다.
문서 범위: 이 페이지는 Linux 메인라인의 nf_nat, nf_conntrack, nftables/iptables NAT 경로를 중심으로 설명합니다. RFC 4787·5382·5508·6888·6146·6296은 상호 운용성 요구사항과 번역 모델을 설명하는 기준선으로 사용합니다. 중요한 점은 NAT는 방화벽 그 자체가 아니라 주소 변환이라는 점입니다. 외부 노출 정도와 허용 여부는 별도 필터 규칙과 라우팅 정책이 결정합니다.

NAT 개요

NAT는 IPv4 주소 부족 때문에 널리 쓰였지만, 커널 관점에서 더 본질적인 정의는 패킷 헤더의 주소·포트 필드를 한 지점에서 다른 값으로 안정적으로 재기록하고, 응답 경로에서 그 역변환을 보장하는 상태 기계입니다. Linux에서는 이 상태 기계를 nf_conntrack가 유지하고, 실제 필드 수정을 nf_nat가 수행합니다.

중요한 오해 하나를 먼저 지우면 좋습니다. NAT는 흔히 “외부에서 못 들어오게 하니까 보안”으로 설명되지만, 그 효과는 대개 기본 드롭 정책과 상태 기반 필터가 함께 있을 때 생깁니다. NAT만 있다고 해서 보안 정책이 자동으로 완성되지는 않습니다. 반대로 포트 포워딩과 hairpin NAT처럼, NAT는 오히려 외부 노출 경로를 만드는 도구이기도 합니다.

형태무엇을 바꾸는가주로 쓰는 훅대표 용도실무 메모
SNAT 출발지 IP와 포트 POSTROUTING 사설망의 인터넷 접속 고정 공인 IP 또는 공인 IP 풀을 명시적으로 운용할 때 적합합니다.
MASQUERADE 출발지 IP와 포트 POSTROUTING 동적 WAN 주소 환경 출구 인터페이스의 현재 주소를 자동 사용합니다. PPPoE·DHCP uplink에 많이 씁니다.
DNAT 목적지 IP와 포트 PREROUTING, OUTPUT 포트 포워딩, 서비스 공개 PREROUTING DNAT는 라우팅 결정 전에 일어나므로 목적지 변화가 경로 선택에 직접 반영됩니다.
REDIRECT 목적지 IP를 로컬 호스트로 PREROUTING, OUTPUT 투명 프록시, 로컬 프록시 강제 DNAT의 특수형입니다. 목적지를 “현재 장비 자신”으로 바꿉니다.
NETMAP / prefix 대역 또는 프리픽스 PREROUTING, POSTROUTING 1:1 대역 매핑, 프리픽스 교체 결정적 매핑을 원할 때 유용합니다. IPv6에서는 NPTv6 논의와 이어집니다.
첫 패킷만 룰을 본다: nftables NAT 문서가 명시하듯 stateful NAT는 흐름의 첫 번째 패킷으로 바인딩을 만들고, 이후 패킷은 conntrack에 저장된 NAT 정보를 사용합니다. 그래서 “규칙을 고쳤는데 이미 열려 있는 연결은 왜 안 바뀌지?”라는 현상은 정상입니다.

NAT 아키텍처와 커널 경로

NAT의 핵심은 “룰 평가”와 “패킷 재기록”을 분리하는 데 있습니다. 첫 패킷에서 nf_nat_setup_info()가 새 튜플을 고르고 reply 방향 튜플을 바꿉니다. 그 다음 패킷부터는 nf_nat_packet()이 이미 저장된 상태만 보고 헤더를 수정합니다. NAT는 매 패킷마다 규칙 전체를 재평가하는 시스템이 아닙니다.

새 패킷 도착 NEW 흐름, 아직 NAT 바인딩 없음 conntrack lookup / create nf_conntrack_in() NAT rule lookup 첫 패킷만 NAT chain 평가 nf_nat_setup_info() 고유 튜플 선택, reply tuple 변경 라우팅 결정 POSTROUTING SNAT 또는 OUTPUT/PREROUTING DNAT conntrack confirm 해시 테이블에 상태 확정 후속 패킷 nf_nat_packet()으로 바로 재기록 conntrack에 저장되는 것 Original tuple: 192.168.10.20:53000 → 198.51.100.10:443 Reply tuple: 198.51.100.10:443 → 203.0.113.10:40001 상태 비트 IPS_SRC_NAT / IPS_DST_NAT IPS_SRC_NAT_DONE / IPS_DST_NAT_DONE IPS_CONFIRMED 핵심 의미 첫 패킷에서만 새 바인딩을 만든다. 그 다음부터는 규칙 검색이 아니라 상태 기반 재기록이다. 응답의 역변환도 reply tuple 덕분에 자동으로 일어난다.
/* net/netfilter/nf_nat_core.c, 개념 파악용 축약 */
if (nf_ct_is_confirmed(ct))
    return NF_ACCEPT;

nf_ct_invert_tuple(&curr_tuple,
                   &ct->tuplehash[IP_CT_DIR_REPLY].tuple);
get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype);

if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {
    nf_ct_invert_tuple(&reply, &new_tuple);
    nf_conntrack_alter_reply(ct, &reply);
    ct->status |= (maniptype == NF_NAT_MANIP_SRC) ?
                  IPS_SRC_NAT : IPS_DST_NAT;
}

if (ct->status & statusbit)
    verdict = nf_nat_manip_pkt(skb, ct, mtype, dir);
소스 레벨 해석: nf_nat_setup_info()는 아직 confirm되지 않은 연결에서만 NAT 정보를 세팅하고, reply tuple을 바꿔 응답 역변환을 준비합니다. 이후 nf_nat_packet()은 이미 설정된 상태 비트를 보고 실제 헤더를 수정합니다.

SNAT와 MASQUERADE

SNAT는 출발지 주소와 포트를 바꿔서 내부 호스트 여러 대가 외부와 통신하도록 만드는 가장 흔한 NAT 형태입니다. 리눅스에서 보통 POSTROUTING 훅에 두며, nftables manpage 기준으로 snatpostroutinginput nat chain에서 유효합니다. 실무에서는 거의 항상 POSTROUTING을 봅니다.

항목SNATMASQUERADE
외부 주소 지정 규칙에 공인 IP 또는 IP 풀을 직접 적음 출구 인터페이스의 현재 주소를 자동 사용
적합한 환경 고정 공인 IP, CGNAT, 로드 분산형 주소 풀 DHCP, PPPoE, 빈번한 WAN 주소 변경
추가 특징 persistent, 포트 범위, 풀 운용이 용이 인터페이스 다운/주소 제거 이벤트와 연동해 stale mapping 정리에 유리
주의할 점 공인 IP 변경 시 규칙을 직접 바꿔야 함 대규모 고정 주소 풀 운용에는 덜 명시적

커널 소스 nf_nat_masquerade.c를 보면 MASQUERADE는 NETDEV_DOWN와 주소 제거 이벤트를 감시해 해당 인터페이스에 묶인 conntrack 엔트리를 정리합니다. 그래서 주소가 바뀌는 uplink에서는 단순 SNAT보다 운용상 안전합니다.

table inet nat {
  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;

    # 고정 공인 IP 풀 사용
    oifname "wan0" ip saddr 10.10.0.0/16 \
      snat to 203.0.113.10-203.0.113.13 persistent,fully-random

    # 동적 주소 uplink
    oifname "ppp0" ip saddr 192.168.0.0/24 masquerade
  }
}

persistent는 가능한 한 같은 내부 클라이언트에 같은 외부 매핑을 유지하려는 플래그이고, fully-random은 포트 선택을 완전히 무작위화합니다. nftables manpage에 따르면 커널 5.0 이상에서는 randomfully-random이 같은 의미입니다.

포트 고갈: SNAT는 “외부 포트 공간”을 소비합니다. 하나의 공인 IPv4 주소와 하나의 전송 프로토콜 조합이 제공하는 동시 매핑 수는 유한하므로, 고밀도 환경에서는 주소 풀 확장, 포트 블록 설계, per-subscriber 제한을 함께 고려해야 합니다.

DNAT, REDIRECT, 그리고 hairpin NAT

DNAT는 목적지 주소와 포트를 바꿔 패킷을 내부 서버나 다른 경로로 보냅니다. 가장 전형적인 예는 포트 포워딩입니다. nftables 기준으로 dnatredirectpreroutingoutput nat chain에서 유효합니다. 로컬 프로세스가 생성한 패킷을 다른 로컬 소켓으로 돌리는 경우에는 OUTPUT REDIRECT가 자연스럽습니다.

table inet nat {
  chain prerouting {
    type nat hook prerouting priority dstnat; policy accept;

    iifname "wan0" tcp dport 443 dnat to 192.168.10.30:8443
    iifname "wan0" tcp dport 22 redirect to :2222
  }

  chain output {
    type nat hook output priority -100; policy accept;

    tcp dport 853 redirect to :10053
  }
}

REDIRECT는 DNAT의 특수형이며 목적지 주소를 현재 로컬 장비로 바꿉니다. 커널 소스 nf_nat_redirect.c는 이 경로가 NF_INET_PRE_ROUTINGNF_INET_LOCAL_OUT에서만 사용되어야 함을 분명히 드러냅니다.

실무에서 자주 놓치는 부분은 hairpin NAT입니다. 내부 클라이언트가 외부 공개 주소(203.0.113.10:443)로 같은 내부 서버(192.168.10.30:8443)에 접근하면 DNAT만으로는 끝나지 않는 경우가 많습니다. 서버가 클라이언트에게 직접 응답해 버리면 경로가 비대칭이 되고, 클라이언트는 “내가 접속한 외부 주소”가 아니라 “내부 서버 주소”에서 답이 돌아와 세션이 깨질 수 있습니다. 그래서 hairpin에서는 반환 경로를 번역기 자신으로 묶기 위한 추가 SNAT/masquerade가 흔히 필요합니다.

내부 클라이언트 192.168.10.20 공개 주소로 접속 시도 NAT 게이트웨이 LAN: 192.168.10.1 / WAN 공인 주소: 203.0.113.10 PREROUTING DNAT LAN 방향 추가 SNAT 내부 서버 192.168.10.30:8443 포트 포워딩 대상 dst=203.0.113.10:443 DNAT → 192.168.10.30:8443 reply가 반드시 게이트웨이로 되돌아와야 함 클라이언트는 계속 203.0.113.10과 통신한다고 인식 hairpin에서 DNAT만 적용하면 서버가 클라이언트에게 직접 답해 비대칭 경로가 생길 수 있으므로, 보통 LAN 방향 SNAT/masquerade를 같이 둡니다.
hairpin 요구사항: RFC 4787, RFC 5382, RFC 5508은 각각 UDP, TCP, ICMP 계열에서 hairpin 지원을 중요 요구사항으로 다룹니다. Linux에서도 hairpin 여부는 “NAT가 가능한가”보다 “DNAT와 반환 경로용 SNAT가 함께 설계됐는가”에 달려 있습니다.

conntrack과 NAT 상태

NAT를 이해할 때 가장 중요한 자료구조는 struct nf_conn입니다. 여기에 원본 방향 tuple, reply 방향 tuple, 상태 비트, 필요시 NAT 확장과 helper 확장이 연결됩니다. NAT가 “연결 기반”처럼 보이는 이유는 실제로 이 상태가 모든 후속 패킷의 판단 기준이 되기 때문입니다.

커널/사용자 지표의미현재 문서 기본값/성격왜 중요한가
nf_conntrack_buckets conntrack 해시 버킷 수 메모리 기준으로 계산, 초기 netns에서만 변경 가능 충돌과 탐색 비용에 직접 영향이 있습니다.
nf_conntrack_count 현재 할당된 흐름 수 읽기 전용 테이블 포화 임박 여부를 보는 1차 지표입니다.
nf_conntrack_max 허용 최대 흐름 수 기본값은 nf_conntrack_buckets 초과 시 새 연결이 실패합니다.
nf_conntrack_tcp_timeout_established ESTABLISHED TCP 타임아웃 432000초 (5일) 긴 유휴 TCP 세션 유지 비용을 좌우합니다.
nf_conntrack_udp_timeout 일반 UDP 타임아웃 30초 짧은 UDP 흐름은 빨리 비우지만, NAT keepalive가 없는 애플리케이션에는 짧을 수 있습니다.
nf_conntrack_udp_timeout_stream 양방향 UDP 타임아웃 120초 RTP·QUIC 같은 장시간 UDP에서 체감에 직접 연결됩니다.
nf_flowtable_tcp_timeout flowtable의 TCP fastpath 타임아웃 30초 오프로드 경로에서 얼마 동안 캐시를 유지할지 결정합니다.
nf_flowtable_udp_timeout flowtable의 UDP fastpath 타임아웃 30초 짧은 burst 트래픽에서 오프로드 hit 비율에 영향이 큽니다.

여기서 흥미로운 점은 RFC 4787이 일반 UDP NAT 매핑 타이머에 대해 “2분 미만으로 끝나면 안 된다”고 요구한다는 점입니다. 반면 현재 Linux nf_conntrack_udp_timeout 기본값은 30초입니다. 이는 리눅스 기본값이 곧바로 CPE/CGN 상호 운용성 요구사항을 만족한다는 뜻이 아님을 보여줍니다. 인터넷 경계 NAT 장비로 쓸 때는 역할에 맞게 타임아웃을 직접 조정해야 합니다. 이 문장은 RFC와 커널 문서를 함께 읽은 해석입니다.

# 현재 사용량과 한계
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max

# 상태 보기
conntrack -L -o extended
conntrack -E

# 특정 흐름만 보기
conntrack -L -p tcp --orig-src 192.168.10.20 --dport 443
룰 변경과 기존 세션: NAT 규칙을 수정해도 이미 확정된 흐름은 예전 바인딩을 계속 쓸 수 있습니다. 문제를 재현할 때는 관련 conntrack 엔트리를 지우거나 새 연결로 다시 시작해야 합니다.

NAT helper와 ALG

FTP, SIP, 일부 레거시 제어 프로토콜처럼 “제어 채널 안에 다음 데이터 채널 주소와 포트가 실려 오는” 프로토콜은 NAT에 불리합니다. NAT는 L3/L4 헤더만 바꿔서는 충분하지 않고, 애플리케이션 페이로드를 읽어 RELATED 흐름을 예상해야 합니다. 이 역할이 conntrack helper입니다.

현재 nftables 모델은 iptables 시절의 “자동으로 붙어주는 helper” 관성보다 훨씬 명시적입니다. nftables manpage도 helper는 ct helper set으로 연결에 명시적으로 붙여야 하며, conntrack lookup 뒤에서만 유효하다고 설명합니다. 즉, helper는 가능한 한 좁은 범위에 붙여야 합니다.

table inet helpers {
  ct helper ftp-standard {
    type "ftp" protocol tcp;
  }

  chain prerouting {
    type filter hook prerouting priority filter; policy accept;
    ip daddr 192.168.10.30 tcp dport 21 ct helper set "ftp-standard"
  }

  chain forward {
    type filter hook forward priority filter; policy drop;
    ct state established,related accept
    ip daddr 192.168.10.30 ct helper "ftp" tcp dport 1024-65535 accept
  }
}
보안상 주의: nftables helper 문서는 helper를 특정 daddr와 서비스에 엄격히 묶지 않으면 원치 않는 RELATED 포트 개방 효과가 생길 수 있다고 경고합니다. TLS로 보호되는 제어 채널에서는 helper가 페이로드를 읽을 수 없으므로, “helper가 안 먹는다”보다 “암호화 때문에 원래 불가능하다”가 정확한 설명일 때가 많습니다.

더 긴 설명은 conntrack 헬퍼 & ALG 문서를 참고하세요. NAT 문서에서는 helper를 “예외적 필요”로 보는 편이 정확합니다. 현대 프로토콜은 가능하면 애플리케이션 계층 NAT 의존을 줄이는 방향으로 설계됩니다.

RFC 동작 모델: mapping, filtering, hairpin

“full cone”, “restricted cone”, “symmetric NAT” 같은 표현은 여전히 현장에서 자주 쓰이지만, RFC 4787 이후 문서는 이를 mapping behavior, filtering behavior, hairpin behavior, timer behavior로 더 정밀하게 분해합니다. Linux NAT를 이 별명 하나로 규정하려고 하면 거의 항상 과도한 단순화가 됩니다.

RFC 요구사항핵심 내용Linux NAT를 읽을 때의 의미
RFC 4787 REQ-1 / RFC 5382 REQ-1 UDP와 TCP에서 Endpoint-Independent Mapping 요구 외부 목적지가 달라도 같은 내부 endpoint에 같은 외부 매핑을 유지하는 방향이 상호 운용성에 유리합니다.
RFC 4787 REQ-8 / RFC 5382 REQ-3 투명성이 중요하면 Endpoint-Independent Filtering 권고 필터링은 NAT 그 자체보다 방화벽 정책과 더 강하게 연결됩니다. NAT 규칙만 보고 외부 허용 범위를 단정하면 안 됩니다.
RFC 4787 REQ-9 / RFC 5382 REQ-8 / RFC 5508 REQ-7 UDP·TCP·ICMP hairpin 지원 포트 포워딩을 내부에서도 같은 공개 주소로 쓰려면 hairpin 경로를 설계해야 합니다.
RFC 4787 REQ-5 UDP 매핑 타이머는 일반적으로 2분 미만이면 안 됨 Linux 기본 UDP timeout은 이보다 짧을 수 있으므로, CPE/CGN 역할이면 운영자가 조정해야 합니다.
RFC 5508 ICMP Query/Error도 NAT traversal과 hairpin 고려 필요 PMTUD와 에러 전달 문제를 “그냥 ICMP 막힘”으로 뭉개면 원인 분석이 막힙니다.

정리하면, Linux NAT를 “기본적으로 port-restricted cone NAT다” 같은 한 줄로 정의하는 것은 정확하지 않습니다. 외부에서 관찰되는 동작은 NAT 바인딩 정책, 필터 규칙, helper 사용 여부, hairpin 설계, 타이머 설정이 합쳐져 결정됩니다.

CGNAT (Carrier-Grade NAT)

CGNAT는 가정용 공유기의 NAT를 그대로 크게 키운 것이 아니라, 수많은 가입자가 한정된 공인 IPv4 주소 공간을 공유하도록 만드는 통신사업자급 상태 시스템입니다. 그래서 단순한 address rewrite보다 공정성, 로그량, 포트 할당, 메모리 예산, 장애 반경이 핵심 문제가 됩니다.

RFC 6598은 CGN과 CPE 사이에서 사용할 공유 주소 공간으로 100.64.0.0/10을 예약했습니다. 이 공간은 RFC 1918 사설 주소와 비슷하게 외부 라우팅 대상이 아니지만, 목적이 다릅니다. 통신사업자 내부에서 CPE와 CGN 사이 구간을 번호 매기기 위한 별도 공간으로 이해하는 편이 정확합니다.

CGN 운영 포인트RFC 6888 관점실무 의미
주소 풀링 기본은 paired pooling 권고 가능하면 가입자당 같은 외부 IP를 유지해 애플리케이션 혼란과 로그 복잡도를 줄입니다.
포트 제한 가입자별 외부 포트 수 제한 필요 한 가입자가 포트를 독식해 전체 CGN을 고갈시키지 않게 합니다.
상태 메모리 메모리 사용량 제한과 속도 제한 필요 대규모 봇 감염이나 비정상 burst가 전체 장비를 무너뜨리지 않게 합니다.
로그 목적지 주소/포트 로깅은 가능한 한 피해야 함 개인정보와 저장 비용 모두 커지므로, 필요한 최소 정보만 남기는 것이 원칙입니다.
포트 할당 알고리즘 포트 효율, 로그 최소화, 예측 난이도 사이 균형 결정적 포트 블록은 로그를 줄이지만 포트 활용률과 분산성에 trade-off가 있습니다.
명시적 제어 가입자가 매핑을 제어할 프로토콜 필요 PCP 같은 외부 매핑 제어 체계가 없으면 일부 응용 프로그램이 급격히 불리해집니다.
CGNAT는 “큰 NAT”가 아니다: 가정용 기본값을 그대로 확대하면 곧바로 로그 폭증, 포트 경쟁, 특정 가입자 폭주, 관측 부재 문제가 드러납니다. CGN 설계는 NAT 기능보다 자원 예산과 운영 자동화가 더 큰 주제입니다.

NAT64와 NPTv6

IPv6 전환 문맥에서 “NAT”라는 단어는 NAT44 하나만 가리키지 않습니다. RFC 6146의 stateful NAT64는 IPv6 클라이언트와 IPv4 서버를 이어 주는 주소·프로토콜 번역기이고, RFC 6296의 NPTv6는 IPv6-to-IPv6 프리픽스 번역기입니다. 둘은 목표도, 상태 모델도, 운용상의 함정도 다릅니다.

항목NAT44Stateful NAT64NPTv6
주소 패밀리 IPv4 ↔ IPv4 IPv6 ↔ IPv4 IPv6 ↔ IPv6
상태 보통 stateful stateful stateless
핵심 테이블 conntrack + NAT binding BIB + session table 프리픽스 매핑 규칙
포트 변환 필요 시 수행 주로 수행 하지 않음
RFC 포인트 4787/5382/5508/6888 6146 6296
운영 핵심 주소 절약, 포워딩, hairpin IPv6 전용 클라이언트의 IPv4 접근 멀티홈, 프리픽스 교체, 상태 없는 주소 독립성
IPv6 클라이언트 2001:db8:1::20 Stateful NAT64 BIB: TCP / UDP / ICMP Query Session table IPv6 주소와 IPv4 transport address를 매핑 IPv4 서버 198.51.100.40:443 내부 IPv6 프리픽스 fd00:10::/48 NPTv6 상태 없음, 포트 변환 없음 checksum-neutral prefix swap 주로 멀티홈·프리픽스 독립성 문제를 해결 외부 IPv6 프리픽스 2001:db8:200::/48

RFC 6146는 stateful NAT64가 Endpoint-Independent Mapping을 사용하고 TCP, UDP, ICMP Query마다 별도 BIB와 session table을 가진다고 설명합니다. 또 hairpin을 고려하며, end-to-end IPsec과는 기본적으로 양립하지 않는다고 적시합니다. 즉 NAT64는 단순한 dnat/snat 규칙의 변형이 아니라, 별도의 주소 패밀리 변환 모델입니다.

반면 RFC 6296의 NPTv6는 상태가 없고 checksum-neutral하며, 포트를 건드리지 않습니다. 그래서 NAT44보다 훨씬 단순하지만, 보안 기능을 제공하는 것은 아닙니다. RFC 6296도 NPTv6만으로 방화벽이 되는 것은 아니며, 필요한 경우 별도 필터링이 병행되어야 한다고 읽는 편이 맞습니다.

실무 구분: 이 페이지의 중심은 메인라인 Netfilter NAT입니다. RFC 6146 수준의 NAT64는 별도의 BIB/session 모델을 요구하므로, 단순 NAT44 문법과 같은 수준으로 생각하면 구현과 운영 요구사항을 과소평가하게 됩니다.

Stateless NAT와 prefix rewrite

stateful NAT가 conntrack을 기반으로 흐름 상태를 유지하는 반면, stateless NAT는 패킷마다 규칙대로 헤더를 바꾸고 별도 상태를 유지하지 않습니다. nftables NAT 위키가 강조하듯, 이런 방식은 1:1 변환이나 매우 통제된 환경에서는 빠르고 단순할 수 있지만, 일반적인 인터넷 경계 NAT로는 함정이 많습니다.

핵심 제약은 conntrack을 끄고 사용해야 한다는 점입니다. 공식 문서는 notrack를 raw 우선순위 또는 그보다 이른 훅에 배치하지 않으면 conntrack lookup이 먼저 일어나므로 stateless 설계가 성립하지 않는다고 설명합니다.

table inet rawnat {
  chain prerouting {
    type filter hook prerouting priority raw; policy accept;
    tcp dport 443 ip daddr set 192.0.2.10 tcp dport set 8443 notrack
  }
}
권장 범위: stateless NAT는 “상태가 없어서 빠르다”보다 “상태가 없어서 안전장치도 없다”가 먼저 떠올라야 합니다. 역방향 경로, ICMP 오류, PMTUD, 다중 세션 충돌까지 모두 운영자가 직접 책임져야 하므로, 정말 1:1 결정적 변환이 필요한 경우에만 제한적으로 사용하는 편이 좋습니다.

nf_flowtable과 NAT fastpath

nf_flowtable은 “conntrack을 버리는 기능”이 아니라, 한 번 conntrack/NAT로 검증된 흐름을 더 짧은 경로로 전달하는 fastpath입니다. 커널 문서가 분명히 적듯 첫 패킷은 기존 IP forwarding path를 통과해야 하고, flowtable 엔트리는 보통 첫 응답 패킷을 본 뒤 만들어집니다. 이후 패킷은 ingress 훅에서 flowtable lookup을 하고, hit이면 neigh_xmit()로 바로 전송할 수 있습니다.

여기서 중요한 사실은 flowtable 엔트리도 NAT 구성을 저장한다는 점입니다. 따라서 flowtable hit 경로에서도 SNAT/DNAT는 계속 적용됩니다. 이것이 “NAT가 있으면 fastpath를 못 탄다”는 단순화가 틀린 이유입니다. 다만 fragment, helper, 복잡한 예외 경로, 미지원 드라이버는 여전히 classic path에 남을 수 있습니다.

첫 패킷 classic path 진입 PREROUTING → routing → FORWARD conntrack / NAT / policy 검사 첫 응답 패킷 flowtable entry 생성 가능 후속 패킷 ingress lookup으로 fastpath Software flowtable ingress에서 tuple lookup NAT config 포함, TTL/hoplimit 감소 hit 시 neigh_xmit()로 classic path 우회 fragment처럼 transport header가 없으면 lookup 불가 Hardware offload NIC가 지원하면 flowtable flags offload 사용 conntrack 출력에서 [OFFLOAD], [HW_OFFLOAD] 확인 복잡한 rules/helper/tunnel은 종종 미오프로드 상세 한계는 드라이버와 NIC capability에 좌우
table inet fastpath {
  flowtable f {
    hook ingress priority 0;
    devices = { lan0, wan0 };
    flags offload;
  }

  chain forward {
    type filter hook forward priority filter; policy drop;
    ct state established,related flow offload @f accept
    tcp dport { 80, 443 } accept
    udp dport 443 accept
  }
}
관측 포인트: 커널 문서 기준으로 소프트웨어 fastpath는 [OFFLOAD], 하드웨어 오프로드는 [HW_OFFLOAD] 태그로 conntrack 출력에서 확인할 수 있습니다. 더 깊은 내용은 Netfilter Flowtable 문서를 참고하세요.

성능과 용량 설계

NAT 성능 문제는 대개 “룰이 많아서 느리다”보다 다음 네 축에서 터집니다. 상태 수, 포트 수, 경로 대칭성, 예외 프로토콜입니다. 근거 없는 Mpps 숫자를 외우기보다, 어떤 자원이 먼저 고갈되는지 모델링하는 편이 훨씬 실전적입니다.

병목 후보전형적 증상첫 확인 항목대응 방향
conntrack 테이블 포화 새 연결만 실패, 기존 연결은 유지 nf_conntrack_count, nf_conntrack_max 버킷/최대치 조정, timeout 재설계, 비정상 흐름 차단
외부 포트 고갈 특정 공인 IP에서만 신규 세션 실패 SNAT 주소 풀, 가입자당 포트 사용량 공인 IP 풀 확장, deterministic port block 설계, per-user quota
비대칭 라우팅 요청은 나가는데 응답이 INVALID 또는 미도달 ip route get, ECMP/정책 라우팅, firewall node 수 왕복이 같은 번역기를 지나게 설계
helper/ALG 일부 레거시 프로토콜만 비정상 helper attach 여부, 제어 채널 암호화 여부 helper 범위 축소, 애플리케이션 설계 변경
PMTU/MSS 문제 큰 패킷만 멈춤, HTTPS 일부 응답만 끊김 ICMP frag-needed, TCP MSS, 터널 중첩 여부 MSS clamp, ICMP 경로 보존, MTU 재설계
오프로드 미적중 평소보다 CPU 사용 급증, 특정 플로우만 느림 [OFFLOAD]/[HW_OFFLOAD], fragment 여부 flowtable 대상 흐름 선별, 미지원 프로토콜 분리

운영 튜닝의 기본 원칙은 단순합니다. 세션 수를 예측하고, 세션당 메모리와 타이머를 계산하고, 주소/포트 풀을 그보다 넉넉하게 설계하면 됩니다. 반대로 “일단 nf_conntrack_max만 크게 올리자” 접근은 메모리 압박과 GC 지연을 다른 형태로 되돌려 받을 가능성이 큽니다.

디버깅 절차

NAT 문제를 디버깅할 때는 항상 질문 순서를 고정하는 편이 좋습니다. 1) 룰이 맞는가, 2) 첫 패킷이 어떤 체인에서 어떤 verdict를 받는가, 3) conntrack 엔트리가 어떤 tuple로 확정됐는가, 4) 응답이 같은 번역기를 지나오는가. 이 순서를 지키면 문제 공간이 빠르게 줄어듭니다.

# 1. 현재 룰셋과 체인 우선순위 확인
nft list ruleset

# 2. 실제 첫 패킷 추적
nft monitor trace

# 3. conntrack 상태/이벤트 확인
conntrack -L -o extended
conntrack -E

# 4. 경로 확인
ip route get 198.51.100.10 from 192.168.10.20 iif lan0

# 5. 양쪽 인터페이스에서 동시에 관찰
tcpdump -ni lan0 host 192.168.10.30
tcpdump -ni wan0 host 203.0.113.10
증상유력 원인가장 먼저 볼 것
외부로는 나가지만 응답이 안 돌아옴 SNAT 누락, 역방향 경로 비대칭, upstream reverse path 문제 POSTROUTING hit 여부, reply tuple, 상류 라우팅
포트 포워딩은 외부에서 되는데 내부에서만 안 됨 hairpin용 SNAT/masquerade 누락 LAN→공개주소 트래픽의 DNAT 이후 반환 경로
유휴 후 UDP 세션이 끊김 timeout이 애플리케이션 keepalive보다 짧음 nf_conntrack_udp_timeout, keepalive 주기
FTP/SIP 같은 일부 프로토콜만 실패 helper 미부착 또는 암호화로 인한 ALG 불가 ct helper, RELATED 허용 규칙
규칙을 고쳐도 문제 재현이 그대로 기존 conntrack 엔트리가 계속 살아 있음 관련 엔트리 삭제 후 재시험
flow offload가 기대만큼 안 걸림 첫 응답 패킷 미관측, fragment, helper, 미지원 경로 conntrack 태그, nft flowtable 대상 조건
MSS/PMTU: NAT 자체가 MTU를 줄이는 것은 아니지만, 터널·TPROXY·오버레이와 겹치면 “작은 패킷은 되는데 큰 패킷만 멈춤” 현상이 흔합니다. 이때는 방화벽보다 먼저 PMTUD, ICMP frag-needed 전달, TCP MSS clamp를 점검하세요.

컨테이너와 네임스페이스에서의 NAT

컨테이너 환경의 NAT는 대개 “컨테이너 안”이 아니라 호스트 네임스페이스의 브리지 출구에서 일어납니다. Docker 기본 브리지 모델을 떠올리면 이해가 쉽습니다. 컨테이너는 veth로 브리지에 붙고, 호스트의 POSTROUTING MASQUERADE가 외부 통신을 담당합니다.

table ip nat {
  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    ip saddr 172.17.0.0/16 oifname "eth0" masquerade
  }
}

네트워크 네임스페이스를 쓰면 NAT 규칙과 conntrack 상태도 네임스페이스 경계와 함께 관리됩니다. 다만 실제로 어느 네임스페이스의 NAT가 적용되는지는 “패킷이 어느 네임스페이스의 훅을 지나는가”로 결정되므로, 브리지·veth·hostNetwork·CNI 정책을 같이 봐야 합니다. Kubernetes도 CNI에 따라 iptables/nftables NAT, eBPF 기반 SNAT, 또는 NAT 최소화 모델까지 다양합니다.

관련 학습: 컨테이너 NAT 자체보다 네임스페이스 경로가 더 헷갈리는 경우가 많습니다. 네트워크 네임스페이스Linux Containers 문서를 함께 보세요.

참고자료