IPSec & xfrm

Linux xfrm/IPSec 스택을 심층 분석합니다. SPD/SAD와 selector·reqid·if_id·mark의 결합 방식, ESP/AH/IPCOMP 처리 경로, 터널/트랜스포트/BEET 모드, NAT-T와 route-based VPN, ACQUIRE/EXPIRE/NEWAE 이벤트, Crypto API 및 NIC 오프로드, strongSwan/Libreswan 운영 시 발생하는 MTU·재전송·정책 불일치 문제의 진단 포인트까지 실무 관점으로 정리합니다.

전제 조건: 네트워크 스택, Netlink, Linux Crypto Framework 문서를 먼저 읽으세요. xfrm은 "정책 결정(SPD) + 상태(SA/SAD) + 암복호화(Crypto API)"가 동시에 맞물려야 정상 동작하므로, 한 축만 이해하면 실제 장애를 해석하기 어렵습니다.
일상 비유: 이 주제는 출입 규정(SPD) + 출입증(SA) + 봉인 포장(ESP/AH)과 비슷합니다. 어떤 화물을 봉인해야 하는지는 규정이 결정하고, 실제로 어떤 봉인을 어떤 번호로 붙일지는 출입증이 결정하며, 현장에서는 봉인이 훼손되지 않았는지와 재사용 여부를 anti-replay로 검사합니다.

핵심 요약

  • SPD — 어떤 트래픽을 보호, 우회, 차단할지 결정하는 정책 데이터베이스입니다.
  • SAD / SA — SPI, 키, 알고리즘, 수명, anti-replay 윈도우를 가진 실제 변환 상태입니다.
  • selector — 주소, 프로토콜, 포트, mark, if_id 같은 매칭 조건이며 정책과 상태를 연결합니다.
  • reqid / bundle — 여러 변환(IPCOMP → ESP 등)을 한 묶음으로 만들고 policy tmpl과 state를 연결합니다.
  • NAT-T / if_id / output-mark — 운영 환경에서는 NAT, route-based VPN, 정책 라우팅과 함께 봐야 합니다.

단계별 이해

  1. 정책 선택
    패킷이 SPD에서 allow/block/protect 중 무엇으로 판정되는지부터 확인합니다.
  2. 상태 확보
    보호가 필요하지만 SA가 없으면 커널이 XFRM_MSG_ACQUIRE를 올리고, IKE 데몬이 협상 후 SA/SP를 주입합니다.
  3. 변환 적용
    송신은 xfrm_lookup()과 bundle 생성 후 ESP/AH/IPCOMP를 적용하고, 수신은 SPI 기반 SA 조회 후 anti-replay와 ICV 검증을 수행합니다.
  4. 정책 재검증
    복호화된 수신 패킷은 다시 수신 정책과 대조되어, "복호화는 성공했지만 정책이 틀린" 패킷을 차단합니다.
  5. 관측/튜닝
    ip xfrm, /proc/net/xfrm_stat, ip xfrm monitor, NIC 오프로드 통계로 병목과 불일치를 좁혀갑니다.
관련 표준: RFC 4301-4309 (IPsec) — 네트워크 보안 프로토콜 표준입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

xfrm 프레임워크와 IPSec 심화

xfrm(transform)은 리눅스 커널의 IPSec 구현 프레임워크입니다. 패킷의 암호화, 인증, 압축, 캡슐화를 처리하며, SA(Security Association)와 SP(Security Policy) 데이터베이스로 관리됩니다.

xfrm 패킷 처리 흐름 개요 — TX / RX TX 경로 Application Socket ip_output() xfrm_output() SPD/SA 정책 적용 esp_output() 암호화 + MAC NIC TX SAD / SPD SPD: 보안 정책 DB SAD: SA(보안 연계) DB xfrm_state_lookup() Crypto API AES-GCM / ChaCha20 / SHA-2 ↔ Internet (암호화된 IPsec 패킷) ↔ RX 경로 Application Socket ip_local_deliver() xfrm_input() SA 매칭 + 정책 검증 esp_input() 복호화 + MAC 검증 NIC RX
TX: 평문 패킷 → SPD 정책 조회 → SA(SAD) 적용 → ESP 암호화 → 송신
RX: 수신 → ESP 복호화/검증 → SA 매칭 → SP 정책 검증 → 평문 전달

xfrm 아키텍처

User Space IKE Daemon (strongSwan/Libreswan) XFRM Netlink (AF_KEY/XFRM) SPD SAD xfrm_state (SA: 키, 알고리즘, SPI) xfrm_policy (SP: 셀렉터, 방향) ESP / AH / IPCOMP Security Policy DB Security Association DB Netfilter (PREROUTING / POSTROUTING) → xfrm lookup → encrypt/decrypt → route net/xfrm/xfrm_input.c | net/xfrm/xfrm_output.c | net/ipv4/esp4.c | net/ipv6/esp6.c

selector, policy, state의 결합 모델

xfrm을 가장 빠르게 이해하는 방법은 패킷의 selectorSPD 정책을 고르고, 정책의 tmpl이 필요한 SA(xfrm_state)를 찾거나 생성하게 만든다고 보는 것입니다. 운영 중에 자주 보는 reqid, mark, if_id, output-mark는 모두 이 결합을 더 정밀하게 만드는 식별자입니다.

selector → policy → tmpl → state → bundle 패킷 selector src / dst prefix L4 proto / sport / dport mark / ifindex / uid direction: in / out / fwd xfrm_policy (SPD entry) priority / action / family selector + mark + if_id action=block 이면 즉시 차단 tmpl이 있으면 보호 경로 선택 없으면 우회/기본 처리 xfrm_tmpl proto: esp / ah / comp mode: transport / tunnel / beet reqid: policy와 SA 결속 level: required / use optional bit로 획득 정책 조정 xfrm_state (SAD / SA) id = (dst, spi, proto) alg / key / salt / encap props.reqid / props.mode / if_id lifetime / replay / ESN / offload 유효하지 않으면 ACQUIRE 또는 drop xfrm bundle / dst cache / 라우팅 결합 정책과 SA가 맞으면 bundle이 만들어져 dst_entry에 캐시되고, 후속 패킷은 이를 재사용합니다. 이 단계에서 output-mark, xfrmi if_id, packet offload 가능 여부가 실제 송신 경로를 바꿉니다.
정책은 "무엇을 보호할지"를, 상태는 "어떻게 보호할지"를, bundle은 "이번 패킷에서 실제로 어떤 변환을 적용할지"를 결정합니다.
개념핵심 필드운영 관점 의미
selector 주소, 포트, L4 프로토콜, mark, ifindex, uid 정책이 어떤 패킷에 적용되는지 결정합니다. tunnel mode에서도 내부 평문 selector가 중요합니다.
xfrm_policy dir, priority, action, mark, if_id, xfrm_vec[] 정책 우선순위 충돌과 route-based VPN 동작은 대부분 여기서 갈립니다. if_id가 다르면 동일 selector도 공존할 수 있습니다.
xfrm_tmpl proto, mode, reqid, level, optional 정책이 요구하는 SA의 "형태"를 정의합니다. level use는 IPCOMP 같은 예외 처리에서 의미가 큽니다.
xfrm_state SPI, 알고리즘, 키, 수명, replay, if_id, encap 실제 암복호화 엔진입니다. 수명 만료, SPI 충돌, replay-window, NAT-T 캡슐화 여부가 모두 state에 들어갑니다.
bundle / dst cache state 체인, outer route, output-mark 정책 검색 비용을 줄이고 후속 패킷을 빠르게 보냅니다. 라우팅 변경이나 SA 만료 시 캐시가 무효화됩니다.

특히 reqid는 사람이 보기엔 사소한 숫자처럼 보이지만, IKE 데몬이 양방향 SA 쌍과 정책 템플릿을 안정적으로 묶는 데 핵심입니다. 반대로 mark, output-mark, if_id는 라우팅 도메인과 터널 인터페이스를 분리할 때 중요하며, 강한 정책 분리 없이 default route 하나로 xfrmi를 붙이면 IKE/ESP 루프를 만들기 쉽습니다.

IPSec 프로토콜 비교

프로토콜IP 번호기능보호 범위주의사항
ESP 50 기밀성 + 무결성 + anti-replay 페이로드 전체 (터널: 원본 IP 헤더 포함) 현대 배포의 기본값. AEAD(AES-GCM) 또는 ENC+AUTH 조합 사용. NAT 환경에서는 NAT-T 필요
AH 51 인증만 (암호화 없음) IP 헤더 포함 전체 패킷 (변경 가능 필드 제외) NAT와 호환 불가 (IP 헤더가 인증 범위). 현대 환경에서 거의 미사용
IPCOMP 108 페이로드 압축 ESP/AH 전에 페이로드 압축 작아질 때만 전송. 압축 효과가 없으면 원문 전송되므로 receiver 정책에서 level use 검토가 필요할 수 있음

터널 모드 vs 트랜스포트 모드

# 트랜스포트 모드: 호스트-to-호스트, 원본 IP 헤더 유지
# [IP Header][ESP Header][Payload (encrypted)][ESP Trailer][ESP Auth]
ip xfrm state add src 10.0.0.1 dst 10.0.0.2     proto esp spi 0x100 mode transport     enc "aes" 0x$(openssl rand -hex 16)     auth "hmac(sha256)" 0x$(openssl rand -hex 32)

# 터널 모드: 게이트웨이-to-게이트웨이, 원본 패킷 전체 캡슐화
# [New IP][ESP Header][Original IP][Payload (encrypted)][ESP Trailer][Auth]
ip xfrm state add src 203.0.113.1 dst 198.51.100.1     proto esp spi 0x200 mode tunnel     enc "aes" 0x$(openssl rand -hex 16)     auth "hmac(sha256)" 0x$(openssl rand -hex 32)

# Security Policy (어떤 트래픽에 IPSec 적용할지)
ip xfrm policy add src 192.168.1.0/24 dst 192.168.2.0/24 dir out     tmpl src 203.0.113.1 dst 198.51.100.1 proto esp mode tunnel

# 현재 SA/SP 확인
ip xfrm state list    # SA 목록 (키, SPI, 알고리즘)
ip xfrm policy list   # SP 목록 (셀렉터, 방향)
ip xfrm monitor       # 실시간 xfrm 이벤트 모니터링
모드와 level을 함께 보세요:
  • transport — 호스트 대 호스트 보호에 적합합니다. 원래 IP 헤더를 유지하므로 디버깅은 쉽지만 주소 은닉 효과는 없습니다.
  • tunnel — 게이트웨이 대 게이트웨이, route-based VPN, 다중 서브넷 보호의 기본값입니다. 내부 패킷 전체가 ESP 안으로 들어갑니다.
  • BEETip xfrm와 커널은 지원하지만 실제 일반 VPN 배포에서는 드뭅니다. 주소 고정 비용을 줄이려는 특수 시나리오에 가깝습니다.
  • level required — 정책에 맞는 SA가 없거나 필요한 변환이 빠지면 드롭합니다. 기본값으로 생각하면 됩니다.
  • level use — SA가 있으면 사용하되 없어도 통과를 허용합니다. IPCOMP의 비압축 패킷 처리나 점진적 마이그레이션 같은 제한적 상황에서만 신중히 사용하세요.

BEET 모드 상세

BEET(Bound End-to-End Tunnel) 모드는 transport 모드와 tunnel 모드의 하이브리드입니다. 와이어 상에서는 터널 모드처럼 외부 IP 헤더를 추가하지만, 엔드포인트에서는 트랜스포트 모드처럼 동작하여 내부 주소와 외부 주소가 고정된 1:1 매핑을 갖습니다. HIP(Host Identity Protocol, RFC 5206)과 함께 사용되며, 일반 VPN 배포에서는 거의 사용되지 않습니다.

Transport / Tunnel / BEET 모드 패킷 구조 비교 Transport IP Hdr (수정됨) ESP Hdr TCP/UDP + Payload (encrypted) + Trailer ICV 주소 유지 Tunnel New IP Hdr ESP Hdr Orig IP Hdr TCP/UDP + Payload (encrypted) + Trailer ICV 주소 은닉 BEET Outer IP Hdr ESP Hdr TCP/UDP + Payload (encrypted) + Trailer ICV Inner IP 없음 (1:1 매핑) BEET: 와이어에서는 외부 IP가 있지만, 내부 IP 헤더를 생략하여 터널 모드보다 20B 절약. 엔드포인트에서 내부 IP는 SA의 selector 주소로 자동 재구성. HIP 프로토콜에서 주로 사용.
BEET는 터널 오버헤드를 줄이면서 주소 매핑을 제공하지만, 일반 VPN에서는 거의 사용되지 않습니다.
특성TransportTunnelBEET
외부 IP 헤더 없음 (원본 수정) 추가 (+20B IPv4) 추가 (+20B IPv4)
내부 IP 헤더 N/A 암호화 영역에 포함 생략 (SA에서 재구성)
오버헤드 최소 최대 중간 (터널 대비 -20B)
주소 은닉 불가 완전 은닉 1:1 매핑 (부분 은닉)
다중 서브넷 불가 가능 불가 (1:1 매핑)
주요 용도 호스트-to-호스트 게이트웨이 VPN HIP, 모빌리티
커널 지원 완전 완전 지원 (mode beet)
IKE 데몬 지원 strongSwan, Libreswan strongSwan, Libreswan 제한적 (HIP 데몬)
# BEET 모드 SA 설정 예시
# 내부 주소 10.0.0.1 ↔ 외부 주소 203.0.113.1 (1:1 매핑)
ip xfrm state add src 203.0.113.1 dst 198.51.100.1 \
    proto esp spi 0xBEE7 mode beet \
    sel src 10.0.0.1/32 dst 10.0.0.2/32 \
    aead 'rfc4106(gcm(aes))' 0x$(openssl rand -hex 20) 128

ip xfrm policy add src 10.0.0.1/32 dst 10.0.0.2/32 dir out \
    tmpl src 203.0.113.1 dst 198.51.100.1 proto esp mode beet
BEET 모드를 쓸 이유가 있는가? 일반 site-to-site VPN이나 로드워리어 VPN에서는 tunnel 모드가 항상 올바른 선택입니다. BEET는 HIP(Host Identity Protocol)처럼 호스트 식별자와 위치 식별자를 분리하는 특수 프로토콜에서 주소 매핑 오버헤드를 줄이기 위해 설계되었습니다. 현재 일반 배포에서는 사용할 이유가 없으며, strongSwan/Libreswan의 표준 설정에서도 BEET 옵션은 노출되지 않습니다.

커널 xfrm 내부 구조

/* net/xfrm/xfrm_state.c — Security Association */
struct xfrm_state {
    struct xfrm_id id;            /* (daddr, spi, proto) */
    struct xfrm_selector sel;     /* SA에 연결된 selector */
    struct xfrm_mark mark;       /* fwmark 기반 분리 */
    u32 if_id;                  /* xfrm interface 식별자 */
    struct xfrm_lifetime_cfg lft; /* soft/hard lifetime */

    struct {
        u32 reqid;             /* policy tmpl과의 결속 키 */
        u8 mode;              /* transport / tunnel / beet */
        u8 replay_window;
        xfrm_address_t saddr; /* 터널 외부 소스 주소 */
    } props;

    struct xfrm_algo_auth *aalg;  /* HMAC-SHA2 등 */
    struct xfrm_algo *ealg;       /* AES-CBC 등 */
    struct xfrm_algo *calg;       /* deflate 등 */
    struct xfrm_algo_aead *aead;  /* AES-GCM, ChaCha20-Poly1305 */
    struct xfrm_replay_state_esn *replay_esn;

    /* H/W offload state */
    struct xfrm_dev_offload xso;
};

/* net/ipv4/esp4.c — ESP 패킷 처리 */
/* esp_output(): 송신 패킷 암호화 */
/* esp_input():  수신 패킷 복호화 */

위 코드는 핵심 필드만 발췌한 축약본입니다. 실제 커널 구조체에는 lock/refcount, state cache, 보안 컨텍스트, offload 통계, garbage-collection 연결 정보까지 들어갑니다. 중요한 점은 state에도 selector, mark, if_id, reqid가 남아 있다는 것이고, 그래서 "정책만 맞고 상태는 다른 터널에 붙는" 오동작을 막을 수 있습니다.

권장 알고리즘 조합

배포 기준암호화인증비고
신규 기본값 AES-GCM-128/256 (내장) RFC 8221에서 ENCR_AES_GCM_16이 MUST. HW 오프로드와 상호운용성이 가장 좋음
상호운용 기본값 AES-CBC-128/256 HMAC-SHA2-256-128 RFC 8221 기준으로 AES-CBC는 MUST, HMAC-SHA2-256-128도 MUST. 레거시 장비와 가장 무난
AES 가속이 약한 CPU ChaCha20-Poly1305 (내장) RFC 8221에서 SHOULD. AES-NI가 없는 x86, 일부 ARM/가상화 환경에서 유리할 수 있음
레거시만 허용 3DES, ENCR_NULL HMAC-SHA1-96 이론상 상호운용 때문에 남아 있지만 신규 배포 기준으로는 선택하지 마세요
사용 금지 DES, Blowfish, 수동 키잉 + GCM/CTR/CCM/ChaCha MD5 RFC 8221 기준. 수동 키잉 환경에서는 nonce 재사용 위험 때문에 AEAD/CTR 계열을 쓰면 안 됩니다
RFC 8221 관점 정리: 신규 VPN에서는 AES-GCM-16을 먼저 보고, 상호운용이 우선이면 AES-CBC + HMAC-SHA2-256-128을 fallback으로 두는 구성이 실무적으로 가장 안정적입니다. RFC 4106의 AES-GCM-ESP는 8/12/16바이트 ICV를 허용하지만, 구현·운영 관점에서 16바이트가 가장 흔하고 안전한 기본값입니다.

IPSec/xfrm 주의사항

IPSec 운영 시 핵심 고려사항:
  • MTU/PMTUD — ESP 캡슐화로 패킷 크기 증가 (ESP: +36~73바이트, 터널 모드: +20 추가). PMTUD 실패 시 블랙홀 발생. ip link set dev ipsec0 mtu 1400 또는 MSS clamping 필요
  • NAT Traversal — ESP는 IP 프로토콜이라 NAT 통과 불가. NAT-T(UDP 4500 캡슐화)를 IKE에서 자동 감지/활성화해야 함
  • Anti-replay 윈도우 — 기본 32패킷. 고대역 환경에서 패킷 재정렬로 정상 패킷이 드롭될 수 있음. replay-window 1024 이상 권장
  • SA 수명 관리 — 키 재생성(rekey) 시 트래픽 순간 단절 가능. IKEv2의 CHILD_SA rekey가 seamless 하지만 구현 의존
  • CPU 오버헤드 — 소프트웨어 ESP 암호화는 CPU 집약적. 10Gbps 환경에서 CPU 포화 가능. QAT/SmartNIC 오프로드 활용
  • conntrack 상호작용 — ESP 패킷의 conntrack 처리. 터널 모드에서는 외부/내부 패킷 각각 conntrack 엔트리 생성
  • Policy routing 충돌 — xfrm policy와 ip rule/route의 우선순위 상호작용 주의. ip xfrm policy list로 정책 순서 확인
  • VTI vs xfrm interface — VTI(가상 터널 인터페이스)는 레거시. 커널 4.19+의 xfrm interface(if_id 기반)가 더 유연하고 netns 지원

MTU / PMTUD와 단편화 상세

ESP 캡슐화는 패킷 크기를 증가시킵니다. 이 오버헤드를 고려하지 않으면 PMTUD(Path MTU Discovery) 블랙홀이 발생하여 대용량 전송이 실패하는 흔한 장애가 생깁니다. 오버헤드 크기는 알고리즘, 모드, NAT-T 여부에 따라 달라집니다.

ESP 오버헤드 계산 — 터널 모드 + AES-GCM-128 원본 패킷 (1500B MTU 기준) IP Header (20B) TCP/UDP + Payload (1480B) ESP 캡슐화 후 (터널 모드, AES-GCM-128) New IP (20B) ESP Hdr (8B) IV (8B) Orig IP (20B) Payload (가변) Pad+NH ICV (16B) 오버헤드 계산 터널 + AES-GCM-128: 외부 IP(20) + ESP Header(8) + IV(8) + Padding(0~15) + PadLen+NH(2) + ICV(16) = 54~69B 터널 + AES-CBC + HMAC-SHA256: 외부 IP(20) + ESP(8) + IV(16) + Padding(0~15) + PadLen+NH(2) + ICV(16) = 62~77B 터널 + NAT-T + AES-GCM-128: 위 + UDP Header(8) = 62~77B (가장 큰 오버헤드) 권장 내부 MTU = 경로 MTU - ESP 오버헤드 1500B 경로: 터널 AES-GCM → MTU 1400~1420 | NAT-T 추가 → MTU 1380~1400
ESP 오버헤드는 최소 54B(터널 AES-GCM)에서 최대 77B(터널 NAT-T AES-CBC)까지 변합니다. 안전한 기본값은 MTU 1400입니다.
구성ESP 오버헤드 (바이트)1500B 경로 시 내부 MTU
Transport + AES-GCM-128 34~49 1451~1466
Tunnel + AES-GCM-128 54~69 1431~1446
Tunnel + AES-CBC + HMAC-SHA256 62~77 1423~1438
Tunnel + NAT-T + AES-GCM-128 62~77 1423~1438
Tunnel + NAT-T + AES-CBC + HMAC-SHA256 70~85 1415~1430
# MTU 설정 방법들

# 1. xfrm interface MTU 직접 설정 (가장 권장)
ip link set dev xfrm0 mtu 1400

# 2. MSS clamping (TCP만, 가장 안정적인 PMTUD 우회)
iptables -t mangle -A FORWARD -o xfrm0 -p tcp \
    --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

# 3. PMTUD 동작 확인
ping -M do -s 1400 192.168.2.1  # DF bit 설정으로 단편화 금지
# "Frag needed" ICMP가 돌아와야 정상

# 4. PMTUD 블랙홀 테스트
tracepath 192.168.2.1  # PMTU 탐색 경로 표시

# 5. strongSwan에서 자동 MTU 처리
# swanctl.conf: connections.*.children.*.set_mark_out = 0x42
# → xfrm interface MTU가 자동으로 적용됨

# 6. 커널의 자동 단편화 동작
# 터널 모드에서 내부 패킷이 ESP 후 MTU를 초과하면:
# - DF=1: ICMP Frag Needed를 원본 발신자에게 전송
# - DF=0: ESP 전에 내부 패킷을 단편화한 후 각각 ESP 캡슐화
#         (비효율적: 각 fragment마다 ESP 오버헤드 추가)
PMTUD 블랙홀 진단:
  • 증상: 작은 패킷(ping, DNS)은 통과하지만 대용량 전송(HTTP, SCP)이 멈춤
  • 원인: 경로 상의 장비가 "ICMP Frag Needed"를 차단하여 TCP가 MSS를 줄이지 못함
  • 해결: MSS clamping이 가장 안정적. 또는 sysctl net.ipv4.tcp_mtu_probing=1로 TCP의 PLPMTUD 활성화
  • IPv6: IPv6는 중간 라우터가 단편화하지 않으므로 PMTUD가 필수. ICMPv6 Packet Too Big이 반드시 통과해야 함

IPSec 디버깅

# xfrm 통계 — 오류 원인 파악
ip -s xfrm state   # SA별 패킷/바이트 카운터
ip -s xfrm policy  # SP별 매칭 카운터
cat /proc/net/xfrm_stat
# XfrmInError: 복호화/인증 실패
# XfrmInNoStates: 매칭 SA 없음
# XfrmOutPolBlock: SP에 의해 차단
# XfrmOutBundleGenError: SA 번들 생성 실패

# 패킷 캡처 (ESP 헤더 확인)
tcpdump -i eth0 esp
tcpdump -i eth0 'ip proto 50'  # ESP
tcpdump -i eth0 'ip proto 51'  # AH

# IKE 데몬 로그 (strongSwan)
swanctl --log --level 2
명령무엇을 보는가대표 증상
ip -s xfrm state SA별 패킷/바이트/오류 카운터 한쪽 방향만 bytes가 늘면 selector, 라우팅, NAT-T 대칭성이 틀어졌을 가능성이 큽니다.
ip -s xfrm policy 정책 hit 카운터 state는 존재하는데 hit가 0이면 mark, if_id, direction, port selector를 의심하세요.
ip xfrm monitor all-nsid ACQUIRE / EXPIRE / SA / policy / aevent 실시간 이벤트 trap policy, rekey, netns 간 이벤트를 가장 빠르게 확인하는 방법입니다.
cat /proc/net/xfrm_stat 커널 XFRM private MIB XfrmInNoStates, XfrmInNoPols, XfrmInTmplMismatch, XfrmOutBundleGenError를 분리해서 봐야 합니다.
ip -d link show xfrm0 xfrmi의 if_id, master, 링크 속성 route-based VPN에서 if_id 불일치, VRF 연결 누락을 확인하기 좋습니다.
ethtool -k/-S eth0 ESP offload capability와 드라이버 통계 HW offload가 기대대로 동작하지 않으면 capability 미지원이나 fallback 흔적이 드러납니다.
/proc/net/xfrm_stat 해석 팁: 커널 문서의 xfrm_procXfrmInNoStates를 "SPI/address/proto로 맞는 SA를 찾지 못한 경우", XfrmInStateProtoError를 "키 또는 프로토콜별 무결성 오류", XfrmInNoPols를 "SA는 맞지만 수신 정책이 없는 경우"로 구분합니다. 이 세 값을 섞어 보면 원인 분리가 잘못됩니다.

xfrm_stat 카운터 완전 해설

/proc/net/xfrm_stat은 커널 xfrm 서브시스템의 MIB(Management Information Base) 카운터를 노출합니다. 이 카운터들은 IPSec 장애의 원인을 정확하게 분류하는 핵심 도구이며, 각 카운터의 의미를 혼동하면 엉뚱한 방향으로 디버깅하게 됩니다.

카운터방향의미진단 포인트
XfrmInError IN ESP/AH 복호화 또는 인증 실패 (일반 오류) 키 불일치, 알고리즘 mismatch, 패킷 손상. tcpdump로 SPI 확인 후 양쪽 SA 비교
XfrmInBufferError IN 메모리 할당 실패 (skb 부족) 시스템 메모리 부족. dmesg에 OOM 메시지 확인
XfrmInHdrError IN IP 헤더 또는 ESP/AH 헤더 파싱 오류 단편화된 ESP 패킷, 잘린 패킷. MTU/단편화 문제 의심
XfrmInNoStates IN (daddr, SPI, proto)로 SA를 찾지 못함 SA 만료/삭제, SPI 불일치, 방향(in/out) 착오. ip xfrm state로 SPI 존재 여부 확인
XfrmInStateProtoError IN 프로토콜별 무결성 검증 실패 (ICV mismatch) 키 불일치 또는 전송 중 패킷 변조. AES-GCM 태그 검증 실패가 여기에 들어감
XfrmInStateModeError IN SA 모드(transport/tunnel)와 패킷 형태 불일치 한쪽은 tunnel, 다른 쪽은 transport로 설정된 경우
XfrmInStateSeqError IN 시퀀스 번호 오류 (ESN 상위 비트 불일치) ESN 활성/비활성 불일치 또는 HA failover 후 시퀀스 점프
XfrmInStateExpired IN 만료된 SA로 수신한 패킷 rekey 지연. soft expire → hard expire 사이에 트래픽이 여전히 오래된 SA로 들어옴
XfrmInStateMismatch IN SA가 있지만 패킷의 selector와 불일치 와일드카드 셀렉터 문제. 의도하지 않은 SA에 매칭된 경우
XfrmInStateInvalid IN SA가 유효하지 않은 상태 (larval, dead) ACQUIRE 후 아직 키가 설치되지 않은 SA에 패킷이 도착
XfrmInTmplMismatch IN 수신 정책의 tmpl과 실제 적용된 SA가 불일치 정책은 ESP를 요구하는데 AH로 도착했거나, reqid/mode가 틀린 경우
XfrmInNoPols IN SA는 매칭되었으나 수신 정책(POLICY_IN)이 없음 정책 누락. SA만 있고 inbound policy가 빠진 불완전한 설정
XfrmInPolBlock IN 수신 정책이 BLOCK으로 판정 의도적 차단이 아니라면 정책 우선순위 검토
XfrmInPolError IN 수신 정책 검색 중 오류 내부 커널 오류. 드물지만 메모리 부족 시 발생 가능
XfrmInSeqOutOfWindow IN 시퀀스 번호가 anti-replay 윈도우 밖 가장 흔한 카운터. 윈도우 크기 부족, RSS 재정렬, 멀티패스. replay-window 확대 필요
XfrmInStateReplay IN 중복 시퀀스 번호 (replay 공격 또는 중복 전송) 실제 공격이 아니라면 네트워크 중복(bonding, ECMP 등) 의심
XfrmOutError OUT ESP/AH 암호화 처리 중 일반 오류 Crypto API 오류, 키 길이 불일치
XfrmOutBundleGenError OUT SA bundle 생성 실패 정책은 있지만 SA가 없거나 조합이 틀린 경우. ACQUIRE 전 단계에서 발생
XfrmOutBundleCheckError OUT 기존 bundle 검증 실패 (캐시 무효) SA 만료 후 캐시된 bundle이 아직 참조됨. 일시적
XfrmOutNoStates OUT 송신 시 사용할 SA 없음 ACQUIRE가 실패했거나 타임아웃. IKE 데몬 상태 확인
XfrmOutStateProtoError OUT 암호화 처리 중 프로토콜 오류 알고리즘 초기화 실패 (모듈 미로드 등)
XfrmOutPolBlock OUT 송신 정책이 BLOCK으로 판정 명시적 차단 정책 또는 우선순위 문제
XfrmOutPolDead OUT 매칭된 정책이 이미 삭제/만료됨 race condition. 정책 삭제와 패킷 처리가 겹친 경우
XfrmOutPolError OUT 송신 정책 검색 중 오류 내부 커널 오류
XfrmFwdHdrError FWD 포워딩 경로에서 헤더 오류 라우터 역할의 IPSec 게이트웨이에서 발생
XfrmOutStateInvalid OUT 송신 SA가 유효하지 않은 상태 SA dead 상태에서 아직 참조되는 경우
XfrmAcquireError OUT ACQUIRE 메시지 전송 실패 IKE 데몬이 연결되지 않았거나 Netlink 소켓 오류
# xfrm_stat 전체 카운터 확인
cat /proc/net/xfrm_stat

# 특정 카운터만 모니터링 (watch로 변화 추적)
watch -d -n1 'cat /proc/net/xfrm_stat | grep -E "InNoStates|InSeqOutOfWindow|OutPolBlock"'

# Prometheus node_exporter로 수집 (textfile collector)
# /etc/node_exporter/textfile/xfrm.prom 생성 스크립트:
awk '{print "xfrm_stat_" tolower($1) " " $2}' /proc/net/xfrm_stat \
    > /etc/node_exporter/textfile/xfrm.prom

# 카운터 리셋 (네트워크 네임스페이스 재생성 외에는 불가)
# → 누적 카운터이므로 delta 방식으로 모니터링해야 함
진단 우선순위 패턴:
  1. XfrmInNoStates 증가 → SA가 없음. ip xfrm state로 SPI 확인, IKE 데몬 로그 점검
  2. XfrmInSeqOutOfWindow 증가 → replay-window 확대 (1024 또는 2048)
  3. XfrmInStateProtoError 증가 → 키/알고리즘 불일치. 양쪽 SA 비교
  4. XfrmInNoPols 증가 → inbound 정책 누락. ip xfrm policy에서 dir in 확인
  5. XfrmOutBundleGenError 증가 → 정책은 있지만 SA 매칭 실패. reqid, if_id, mark 검토

실전 트러블슈팅 시나리오

IPSec 장애는 원인이 다양하고 증상이 모호한 경우가 많습니다. 아래는 실무에서 빈번하게 발생하는 문제와 체계적인 진단 경로를 정리한 것입니다.

증상가능한 원인진단 명령해결 방법
터널 수립 후 트래픽 없음 (ping 실패) 라우팅 누락, selector 불일치, 방화벽 차단 ip -s xfrm state (카운터 0?)
ip route get 192.168.2.1
tcpdump -i eth0 esp
트래픽이 xfrm policy에 매칭되는지 확인. 라우팅 테이블에 터널 경로 추가. iptables INPUT/FORWARD 규칙 검토
한 방향만 통신 (A→B OK, B→A 실패) 비대칭 SA/SP, NAT-T 비대칭, reverse path filter ip -s xfrm state (한쪽만 bytes 증가?)
sysctl net.ipv4.conf.all.rp_filter
양쪽 SA의 SPI/키 쌍 확인. rp_filter=2(loose) 또는 0으로 완화. NAT-T 포트 매핑 확인
대용량 파일 전송 실패 (작은 패킷 OK) PMTUD 블랙홀, MSS 미조정 ping -M do -s 1400 peer
tcpdump -i eth0 'icmp and icmp[0]=3'
MTU 1400 설정 또는 MSS clamping: iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
간헐적 패킷 드롭 anti-replay 윈도우 부족, SA 만료 경쟁 cat /proc/net/xfrm_stat | grep SeqOutOfWindow
ip xfrm monitor
replay-window 2048 설정. rekey 마진 확대
IKE 협상 실패 (NO_PROPOSAL_CHOSEN) 알고리즘 제안 불일치 swanctl --log --level 2
양쪽 proposals 비교
양쪽 IKE/ESP proposals를 동일하게 맞춤. 최소 aes256gcm128-x25519-sha256
rekey 후 트래픽 단절 old/new SA 전환 실패, DPD 타이밍 문제 ip xfrm monitor (EXPIRE/NEWSA 순서 확인)
swanctl --list-sas
rekey_time과 life_time 간격 확인. make-before-break가 동작하는지 로그 확인
ESP 패킷이 방화벽에서 차단 NAT-T 미활성, ISP가 proto 50 차단 tcpdump -i eth0 'ip proto 50 or udp port 4500' NAT-T 강제 활성화 (strongSwan: forceencaps=yes). TCP 캡슐화(ESP-in-TCP) 검토
성능 저하 (throughput 기대치 미달) 소프트웨어 ESP 병목, GRO/GSO 비활성, CPU affinity 미설정 perf top -C 0-7
ethtool -k eth0 | grep esp
mpstat -P ALL 1
GRO/GSO 활성화, CPU affinity 설정, HW offload 검토, AES-GCM 사용 확인
# 체계적 IPSec 진단 워크플로

# Step 1: SA/SP 존재 여부 확인
ip xfrm state list   # SA가 있는가? SPI, 알고리즘, 수명 확인
ip xfrm policy list  # SP가 있는가? selector, direction, priority 확인

# Step 2: 카운터 확인 (어느 단계에서 실패하는가?)
ip -s xfrm state     # SA별 패킷/바이트 카운터
ip -s xfrm policy    # 정책 hit 카운터
cat /proc/net/xfrm_stat  # 전체 오류 카운터

# Step 3: 실시간 이벤트 관찰
ip xfrm monitor all  # ACQUIRE, EXPIRE, NEWSA 이벤트 스트림

# Step 4: 패킷 캡처 (ESP 패킷이 나가는가? 들어오는가?)
tcpdump -i eth0 -n 'esp or udp port 4500 or udp port 500' -c 20

# Step 5: IKE 데몬 로그 (협상 실패 원인)
journalctl -u strongswan --since "5 min ago" | tail -50

# Step 6: 라우팅 확인 (트래픽이 올바른 경로로 가는가?)
ip route get 192.168.2.1 mark 0x42  # xfrm mark까지 포함한 경로

# Step 7: Netfilter 간섭 확인
iptables -L -v -n | grep -E 'esp|ipsec|xfrm'
iptables -t mangle -L -v -n
흔한 실수 Top 5:
  1. 양방향 SA/SP 누락: IPSec은 단방향이므로 in/out 양쪽 모두 SA와 SP가 필요합니다. IKE 데몬이 자동 생성하지만, 수동 설정 시 빠뜨리기 쉽습니다.
  2. rp_filter 충돌: 터널 모드에서 내부 소스 주소가 수신 인터페이스의 서브넷에 없으면 rp_filter=1(strict)이 패킷을 드롭합니다.
  3. firewall에서 ESP/IKE 미허용: proto 50(ESP), udp 500(IKE), udp 4500(NAT-T) 모두 열어야 합니다.
  4. MTU 미조정: ESP 오버헤드(50~73바이트)를 고려하지 않으면 PMTUD 블랙홀로 대용량 전송이 실패합니다.
  5. 시간 동기화 미비: 인증서 기반 IKE에서 시간이 어긋나면 인증서 검증이 실패합니다. NTP 필수.

ESP 패킷 형식 상세

ESP(Encapsulating Security Payload, IP 프로토콜 50)는 현대 IPSec의 핵심 프로토콜입니다. 트랜스포트 모드와 터널 모드에서 패킷 구조가 다르며, AEAD 알고리즘 사용 여부에 따라 내부 처리도 달라집니다.

ESP 패킷 구조 — 터널 모드 New IP Hdr (20B) ESP Header SPI(4B) + Seq#(4B) IV (8~16B) Orig IP Hdr (20B) Payload (가변) ESP Trailer Pad + PadLen + NH ICV (8/12/16B) ← 암호화 범위 (Encrypted) → ← 인증 범위 (Authenticated) → ESP 패킷 구조 — 트랜스포트 모드 Orig IP Hdr (수정됨) ESP Header SPI(4B) + Seq#(4B) IV (8~16B) TCP/UDP + Payload (가변) ESP Trailer Pad + PadLen + NH ICV (8/12/16B) 트랜스포트 모드: 원본 IP 헤더 유지 (protocol 필드만 50으로 변경) 터널 모드: 새 IP 헤더 추가, 원본 패킷 전체 암호화 AEAD(AES-GCM): IV = Salt(4B) + Nonce(8B), ICV는 보통 16B, Pad/PadLen/NH도 인증 범위에 포함
/* ESP 헤더 (RFC 4303) — include/uapi/linux/ip.h */
struct ip_esp_hdr {
    __be32 spi;        /* Security Parameters Index — SA 식별 */
    __be32 seq_no;     /* 시퀀스 번호 (Anti-replay용, 단조 증가) */
    __u8   enc_data[]; /* 가변 길이: IV + 암호화된 페이로드 */
};

/* ESP Trailer (암호화 영역 끝에 위치) */
/*   [Padding (0~255 bytes)]     — 블록 정렬용 */
/*   [Pad Length (1 byte)]       — 패딩 바이트 수 */
/*   [Next Header (1 byte)]      — 원본 프로토콜 (TCP=6, UDP=17 등) */
/*   [ICV (8/12/16 bytes)]       — Integrity Check Value (MAC) */

/* ESN (Extended Sequence Number, RFC 4304) */
/* 32비트 시퀀스 번호는 10Gbps에서 ~7분 만에 소진 */
/* ESN은 64비트로 확장: 상위 32비트는 패킷에 미포함, ICV 계산에만 사용 */
struct xfrm_replay_state_esn {
    __u32 bmp_len;     /* 비트맵 길이 (워드 수) */
    __u32 oseq;        /* 송신 시퀀스 (하위 32비트) */
    __u32 seq;         /* 수신 시퀀스 (하위 32비트) */
    __u32 oseq_hi;     /* 송신 시퀀스 (상위 32비트) */
    __u32 seq_hi;      /* 수신 시퀀스 (상위 32비트) */
    __u32 replay_window; /* Anti-replay 윈도우 크기 */
    __u32 bmp[];       /* 수신 비트맵 (가변 길이) */
};
AEAD vs 개별 암호화+인증: AEAD(AES-GCM, ChaCha20-Poly1305)는 단일 패스로 암호화와 인증을 동시 수행하여 성능 우수. 개별 모드(AES-CBC + HMAC-SHA256)는 encrypt-then-MAC 순서로 두 번 처리. 커널에서 AEAD는 crypto_aead API를, 개별 모드는 crypto_skcipher + crypto_ahash를 사용합니다. AES-GCM의 IV는 Salt(4B, SA 생성 시 고정) + Nonce(8B, 패킷마다 증가)로 구성되며, RFC 4106 기준 ICV는 8/12/16바이트가 가능하지만 일반적인 배포와 하드웨어 구현은 16바이트(128비트)를 기본값으로 씁니다.

AH 패킷 형식과 한계

AH(Authentication Header, IP 프로토콜 51)는 패킷의 무결성과 인증을 제공하지만 암호화는 하지 않습니다. IP 헤더를 포함한 전체 패킷이 인증 범위에 포함되는 것이 ESP와의 핵심 차이점이며, 이것이 NAT 환경과 호환되지 않는 근본 원인입니다.

/* AH 헤더 (RFC 4302) — include/uapi/linux/ip_auth.h */
struct ip_auth_hdr {
    __u8   nexthdr;     /* 다음 헤더 (TCP=6, ESP=50 등) */
    __u8   hdrlen;      /* 헤더 길이 (32비트 워드 단위 - 2) */
    __be16 reserved;    /* 예약 (0) */
    __be32 spi;         /* Security Parameters Index */
    __be32 seq_no;      /* 시퀀스 번호 */
    __u8   auth_data[]; /* ICV — 가변 길이 (알고리즘에 따라) */
};

/* AH 인증 범위: IP 헤더 전체 + AH 헤더 + 페이로드 */
/* 단, 변경 가능(mutable) 필드는 0으로 치환 후 MAC 계산: */
/*   - TTL (hop마다 감소)          */
/*   - Header Checksum (TTL 변경 시 재계산) */
/*   - TOS/DSCP (라우터가 변경 가능) */
/*   - Flags (Fragment offset)     */
AH가 현대 환경에서 폐기된 이유:
  • NAT 비호환 — NAT는 IP 헤더의 src/dst 주소를 변경하는데, AH는 IP 헤더를 인증 범위에 포함. NAT 통과 시 ICV 검증 실패. NAT-T(UDP 캡슐화)도 AH에는 적용 불가
  • 암호화 부재 — AH는 인증만 제공. ESP는 인증+암호화 모두 가능하므로 AH가 할 수 있는 것을 ESP가 모두 포함(ESP의 NULL 암호화 + 인증 = AH 동등)
  • 성능 패널티 — mutable 필드를 0으로 치환하는 추가 처리. ESP 대비 실질적 이점 없이 복잡도만 증가
  • RFC 7321 — IPSec 알고리즘 요구사항에서 AH를 MAY(선택)로 격하. IKEv2 구현에서 AH 지원은 필수가 아님

IPCOMP 프로토콜 상세

IPCOMP(IP Payload Compression, RFC 3173, IP 프로토콜 108)는 ESP/AH 암호화 전에 페이로드를 압축하여 대역폭을 절약하는 프로토콜입니다. 암호화된 데이터는 높은 엔트로피로 인해 압축이 불가능하므로, 반드시 IPCOMP → ESP 순서로 적용해야 합니다. 리눅스 커널에서는 SA 번들(bundle)로 IPCOMP와 ESP를 체이닝합니다.

IPCOMP + ESP 변환 파이프라인 (송신) 평문 패킷 1400B payload IPCOMP 압축 deflate/lzs/lzjh CPI(2B) + 압축 데이터 압축 효과? YES IPCOMP 헤더 proto=108 + 압축 payload NO 원본 그대로 IPCOMP 헤더 미삽입 ESP 암호화 AES-GCM IPCOMP 헤더 구조 Next Header (1B) Flags (1B) CPI (2B) Compressed Payload (가변) CPI(Compression Parameter Index): 사용 알고리즘 식별 (deflate=2, lzs=3, lzjh=4) 압축 후 원본보다 커지면 IPCOMP 헤더 없이 원본을 ESP에 전달 (비압축 전송) 수신 측: ESP 복호화 → Next Header=108이면 IPCOMP 해제, 아니면 바로 전달
IPCOMP는 압축 효과가 있을 때만 적용됩니다. 수신 측 정책에서 level use를 설정해야 비압축 패킷도 수락합니다.
/* IPCOMP 헤더 (RFC 3173) — include/uapi/linux/ip_comp.h */
struct ip_comp_hdr {
    __u8   nexthdr;    /* 원본 프로토콜 (TCP=6, UDP=17 등) */
    __u8   flags;      /* 예약 (0) */
    __be16 cpi;        /* Compression Parameter Index */
};

/* net/ipv4/ipcomp.c — IPCOMP 송신 처리 */
static int ipcomp_output(struct xfrm_state *x, struct sk_buff *skb)
{
    /* 1. 페이로드를 deflate로 압축 */
    /* 2. 압축 결과가 원본보다 작은지 확인 */
    if (compressed_len >= orig_len) {
        /* 압축 효과 없음 → IPCOMP 헤더 없이 원본 전달 */
        /* 수신 측은 ESP의 Next Header로 직접 판단 */
        return 0;
    }
    /* 3. IPCOMP 헤더 삽입 (nexthdr, cpi) */
    /* 4. 압축된 페이로드로 교체 → 다음 SA(ESP)에 전달 */
}

/* SA bundle에서 IPCOMP + ESP 체이닝 */
/* policy tmpl 예시:
 * tmpl[0]: proto=comp, mode=transport, reqid=1, level=use
 * tmpl[1]: proto=esp,  mode=tunnel,    reqid=1, level=required
 * → IPCOMP 적용 시도 → ESP 암호화 (필수)
 */
# IPCOMP + ESP 터널 설정 예시
# 1. IPCOMP SA (CPI 기반)
ip xfrm state add src 203.0.113.1 dst 198.51.100.1 \
    proto comp spi 0x1234 mode transport \
    comp deflate reqid 100

# 2. ESP SA (같은 reqid로 번들링)
ip xfrm state add src 203.0.113.1 dst 198.51.100.1 \
    proto esp spi 0x5678 mode tunnel \
    aead 'rfc4106(gcm(aes))' 0x$(openssl rand -hex 20) 128 \
    reqid 100

# 3. 정책: IPCOMP(use) + ESP(required) 템플릿
ip xfrm policy add src 192.168.1.0/24 dst 192.168.2.0/24 dir out \
    tmpl src 203.0.113.1 dst 198.51.100.1 proto comp mode transport \
         reqid 100 level use \
    tmpl src 203.0.113.1 dst 198.51.100.1 proto esp mode tunnel \
         reqid 100 level required
IPCOMP 실무 주의사항:
  • 효과가 제한적: 이미 압축된 데이터(HTTPS, SSH, 미디어)에는 효과가 없습니다. 텍스트/로그 위주 트래픽에서만 의미 있습니다.
  • CPU 오버헤드: deflate 압축은 CPU를 소모하며, 효과가 없어도 압축 시도 비용은 발생합니다.
  • level use 필수: 수신 정책에서 IPCOMP tmpl의 level을 use로 설정해야 비압축 패킷도 수락합니다. required로 설정하면 비압축 패킷이 드롭됩니다.
  • strongSwan 기본 동작: strongSwan은 기본적으로 IPCOMP를 제안(proposal)에 포함합니다. 불필요하면 compress = no로 비활성화하세요.
  • HW offload 미지원: 현재 알려진 NIC에서 IPCOMP 하드웨어 오프로드를 지원하는 제품은 없습니다.

IKE 프로토콜과 SA 협상

IKE(Internet Key Exchange)는 IPSec SA의 자동 협상 프로토콜입니다. 커널의 xfrm 프레임워크는 데이터 평면(패킷 암호화/복호화)만 처리하며, SA 생성/삭제/갱신의 제어 평면은 유저스페이스 IKE 데몬(strongSwan, Libreswan)이 Netlink를 통해 커널에 주입합니다.

특성IKEv1 (RFC 2409)IKEv2 (RFC 7296)
교환 횟수 Phase 1: 6~9 메시지 (Main/Aggressive)
Phase 2: 3 메시지 (Quick Mode)
IKE_SA_INIT: 2 메시지
IKE_AUTH: 2 메시지
총 4 메시지로 완료
NAT-T 지원 확장(RFC 3947)으로 추가, 복잡 프로토콜에 내장 (NAT Detection payload)
인증 방식 PSK, RSA Signature, XAUTH(확장) PSK, RSA/ECDSA Signature, EAP (내장)
DPD (Dead Peer) 확장(RFC 3706), 선택적 구현 내장 (Informational Exchange)
MOBIKE 미지원 RFC 4555: IP 변경 시 SA 유지 (로밍)
CHILD_SA rekey Phase 2 재협상 (일시 중단 가능) CREATE_CHILD_SA로 무중단 rekey
상태 레거시, 신규 배포 권장하지 않음 현행 표준, 모든 신규 배포 권장
IKEv2 교환 흐름 Initiator Responder IKE_SA _INIT SAi1, KEi (DH), Ni (nonce) SAr1, KEr (DH), Nr, [CERTREQ] → SKEYSEED = PRF(Ni|Nr, DH_shared_secret) → SK_d, SK_ai, SK_ar, SK_ei, SK_er 파생 IKE_ AUTH [encrypted] IDi, [CERT], AUTH, SAi2, TSi, TSr IDr, [CERT], AUTH, SAr2, TSi, TSr → IKE SA + 첫 번째 CHILD SA (IPSec SA) 동시 생성 완료 CREATE_ CHILD_SA SA, Ni, [KEi], TSi, TSr (rekey/추가 SA) SA, Nr, [KEr], TSi, TSr PFS: KEi/KEr 포함 시 새 DH 교환 → CHILD SA마다 독립 키 (Forward Secrecy)

Diffie-Hellman 그룹과 PFS: IKE_SA_INIT에서 DH 교환으로 공유 비밀 생성. PFS(Perfect Forward Secrecy)를 활성화하면 CREATE_CHILD_SA에서도 새로운 DH 교환을 수행하여, IKE SA 키가 노출되더라도 개별 CHILD SA(IPSec SA)의 트래픽 키는 보호됩니다. 주요 DH 그룹:

그룹알고리즘강도권장 여부
14MODP 2048-bit~112비트최소 권장
19ECP 256-bit (NIST P-256)~128비트권장
20ECP 384-bit (NIST P-384)~192비트고보안
21ECP 521-bit (NIST P-521)~256비트고보안
31Curve25519~128비트권장 (고성능)

커널과 IKE 데몬 상호작용: IKE 데몬은 AF_NETLINK/NETLINK_XFRM 소켓을 통해 커널 xfrm 서브시스템과 통신합니다. 주요 Netlink 메시지:

/* include/uapi/linux/xfrm.h — 주요 XFRM Netlink 메시지 타입 */
/* SA (Security Association) 관리 */
XFRM_MSG_NEWSA      /* IKE → 커널: SA 생성 (키, 알고리즘, SPI, 모드) */
XFRM_MSG_DELSA      /* IKE → 커널: SA 삭제 */
XFRM_MSG_GETSA      /* IKE → 커널: SA 조회 */
XFRM_MSG_UPDSA      /* IKE → 커널: SA 갱신 (rekey) */

/* SP (Security Policy) 관리 */
XFRM_MSG_NEWPOLICY  /* IKE → 커널: 정책 생성 (셀렉터, 방향, 액션) */
XFRM_MSG_DELPOLICY  /* IKE → 커널: 정책 삭제 */

/* 커널 → IKE 이벤트 (비동기 알림) */
XFRM_MSG_ACQUIRE    /* 커널 → IKE: 매칭 SA 없음, 새 SA 생성 요청 */
XFRM_MSG_EXPIRE     /* 커널 → IKE: SA 수명 만료 (soft/hard) */
XFRM_MSG_MIGRATE    /* MOBIKE: SA를 새 주소로 마이그레이션 */
XFRM_MSG_MAPPING    /* NAT-T: NAT 매핑 변경 알림 */

/* 워크플로 예시:
 * 1. 패킷 도착 → xfrm_policy 매칭 → 해당 SA 없음
 * 2. 커널이 XFRM_MSG_ACQUIRE 전송 → IKE 데몬 수신
 * 3. IKE 데몬이 피어와 IKEv2 교환 수행
 * 4. IKE 데몬이 XFRM_MSG_NEWSA + XFRM_MSG_NEWPOLICY로 SA/SP 커널에 주입
 * 5. 대기 중이던 패킷 처리 재개
 */
DPD와 MOBIKE: DPD(Dead Peer Detection)는 주기적으로 Informational Exchange를 보내 피어 생존을 확인합니다. 응답 없으면 SA를 정리하고 재협상합니다. MOBIKE(RFC 4555)는 모바일 클라이언트가 네트워크를 변경(Wi-Fi → LTE)할 때 IKE SA와 CHILD SA의 주소를 갱신하여 터널을 유지합니다. 커널에서는 XFRM_MSG_MIGRATE로 SA의 주소를 동적으로 변경합니다.

SA 수명주기와 커널 이벤트

실제 운영에서는 "패킷이 왜 지금은 통과하고 1시간 뒤에는 안 통과하는가"가 중요합니다. 그 답은 대부분 SA 수명주기와 Netlink 이벤트에 있습니다. 커널은 패킷을 보다가 SA가 필요하면 ACQUIRE를 올리고, 수명이 다가오면 EXPIRE를 올리며, HA 동기화가 필요한 환경에서는 replay/lifetime 값을 NEWAE로 흘려보냅니다.

ACQUIRE / NEWSA / EXPIRE / NEWAE 흐름 1. outbound packet policy는 PROTECT usable SA 없음 2. 커널 → XFRM_MSG_ACQUIRE selector, reqid, proto 전달 larval state 또는 대기 3. IKE 데몬 IKE_SA_INIT / IKE_AUTH 또는 CREATE_CHILD_SA rekey Netlink로 SA/SP 설치 4. NEWSA / NEWPOLICY state valid, bundle 생성 트래픽 재개 5. soft expire XFRM_MSG_EXPIRE(hard=0) IKE 데몬이 rekey 준비 겹치는 SA 기간이 중요 6. rekey / 교체 새 CHILD_SA 생성 old/new SA 동시 존재 가능 cutover 뒤 기존 SA 삭제 7. NEWAE / HA 동기화 replay counter, lifetime byte 값을 listener에 전달 threshold: xfrm_aevent_rseqth, xfrm_aevent_etime active/standby 장비의 SA 상태를 최대한 가깝게 유지
정상 운영에서는 soft expire 이전에 rekey가 끝나야 하고, HA에서는 NEWAE 이벤트까지 수집해야 replay/lifetime 불일치가 줄어듭니다.
이벤트발생 시점실무 해석
XFRM_MSG_ACQUIRE 정책은 있는데 사용할 SA가 없을 때 IKE 데몬이 trap policy를 받아 새 CHILD_SA를 만들어야 합니다. net.core.xfrm_acq_expires가 지나면 대기 패킷은 실패합니다.
XFRM_MSG_EXPIRE soft/hard lifetime 도달 soft는 rekey 신호, hard는 더 이상 사용 불가입니다. hard만 보고 있으면 순간 단절을 피하기 어렵습니다.
XFRM_MSG_NEWAE replay/lifetime 임계값 초과 또는 타이머 만료 HA 동기화용입니다. active 장비가 얼마나 bytes/seq를 썼는지 standby가 따라가야 failover 후 replay 오류를 줄일 수 있습니다.
XFRM_MSG_MIGRATE MOBIKE나 주소 변경 시 IP가 바뀌어도 IKE SA와 CHILD_SA를 유지하려는 시나리오입니다. NAT-T, 로밍, 멀티홈 환경에서 중요합니다.
HA / 이중화 팁: 커널 문서의 xfrm_sync는 replay 카운터와 lifetime byte 값을 listener에게 보내 active/standby 장비를 동기화하는 메커니즘을 설명합니다. 기본 sysctl은 xfrm_aevent_etime 1초, xfrm_aevent_rseqth 2패킷이며, listener가 없으면 이벤트를 꺼 두는 것이 기본 동작입니다.

IPSec 고가용성 (HA) 상세

IPSec VPN 게이트웨이의 고가용성은 SA 상태 동기화가 핵심입니다. active 장비가 장애 시 standby가 동일한 SA(키, 시퀀스 번호, replay 윈도우)를 가지고 있어야 기존 터널이 재협상 없이 유지됩니다. 리눅스 커널의 NEWAE(Anti-replay Event) 메커니즘과 IKE 데몬의 HA 플러그인이 이를 지원합니다.

IPSec HA — Active/Standby SA 동기화 Active 노드 IKE Daemon (HA) 커널 xfrm SPD/SAD (활성) XFRM_MSG_NEWAE 리스너 replay seq, lifetime bytes/packets 수집 SA 상태 패킷 SPI, seq_hi:seq, oseq_hi:oseq, lifetime bytes, replay bitmap HA 동기화 채널 (TCP/UDP) Standby 노드 IKE Daemon (HA) 커널 xfrm SPD/SAD (미러) SA 상태 수신 + UPDSA XFRM_MSG_UPDSA로 seq/lifetime 갱신 Failover 시 VIP 인계 + SA 활성화 → 터널 무중단 유지 HA 동기화 채널 (TCP/UDP) SA 동기화 동기화 간격이 짧을수록 failover 시 replay 오류 위험이 줄어들지만, 동기화 트래픽이 증가합니다.
NEWAE 이벤트로 replay/lifetime 상태를 실시간 동기화하면, failover 시 기존 SA로 터널이 즉시 재개됩니다.
HA 방식동기화 대상장점단점
SA 상태 동기화 SA 키, SPI, 시퀀스 번호, replay 비트맵, lifetime 카운터 무중단 failover, 피어 재협상 불필요 동기화 지연 시 replay 윈도우 불일치 가능. 구현 복잡
IKE SA만 동기화 IKE SA 키와 상태만 동기화, CHILD_SA는 재협상 구현 단순. 동기화 데이터 적음 failover 시 1~3초 단절 (CHILD_SA 재협상)
재협상 방식 동기화 없음. standby가 인계 후 IKE부터 재협상 가장 단순. 동기화 인프라 불필요 5~10초 단절. DPD 타임아웃 대기 필요
# HA 관련 sysctl 파라미터
# NEWAE 이벤트 발생 조건 (replay 시퀀스 임계값)
sysctl -w net.core.xfrm_aevent_rseqth=2    # 2패킷마다 이벤트
# NEWAE 이벤트 발생 조건 (시간 임계값)
sysctl -w net.core.xfrm_aevent_etime=1000  # 1초(1000ms)마다 이벤트

# NEWAE 이벤트 모니터링
ip xfrm monitor aevent  # anti-replay 이벤트만 관측

# SA 상태를 standby에 주입 (UPDSA)
# 실제로는 IKE 데몬의 HA 플러그인이 자동 처리
# strongSwan: ha 플러그인 (charon.plugins.ha)
# 설정 예:
# charon.plugins.ha.local = 10.0.0.1
# charon.plugins.ha.remote = 10.0.0.2
# charon.plugins.ha.segment_count = 2
# charon.plugins.ha.fifo_interface = yes

# Keepalived + IPSec HA 연동
# VRRP로 VIP failover → notify_master 스크립트에서 SA 활성화
HA 동기화의 한계:
  • 동기화 지연: active에서 100패킷을 처리한 뒤 standby에 seq=50까지만 동기화된 상태에서 failover되면, standby가 seq=51부터 보내는데 피어는 이미 seq=100까지 수신했으므로 replay 체크를 통과하지만, 피어가 보낸 seq=51~100 패킷은 standby에서 이미 처리된 것으로 판정될 수 있습니다.
  • 시퀀스 점프: 이를 완화하려면 failover 시 시퀀스를 N만큼 점프시키는 방법이 있습니다. strongSwan HA 플러그인은 segment_count를 사용하여 시퀀스 공간을 분할합니다.
  • 키 노출 위험: SA 키가 네트워크를 통해 동기화되므로, HA 채널 자체도 암호화(별도 IPSec 또는 MACsec)하거나 전용 네트워크로 격리해야 합니다.

xfrm과 네트워크 스택 통합

IPSec/xfrm 프레임워크는 Linux 네트워크 스택에 깊숙이 통합되어 있습니다. 다음 다이어그램은 전체 네트워크 플로우에서 xfrm이 어떻게 위치하고, Netfilter 훅 및 라우팅과 어떻게 상호작용하는지 보여줍니다.

IPSec/xfrm 통합 개요 패킷 수신/송신 xfrm 상태(SA)/정책(SPD) Netfilter/라우팅 RX: 복호화/검증 후 정책 확인 TX: 정책 매칭 후 ESP/AH 캡슐화 아래 상세도는 훅 단위 호출 순서를 표시
xfrm이 끼어드는 위치를 먼저 잡고, 아래 상세도에서 함수/훅 순서를 따라가세요.
IPSec/xfrm과 네트워크 스택 통합 ← 수신 경로 (IPSec VPN) NIC → NAPI → netif_receive_skb() L2/L3 프로토콜 처리 (IP) ESP 수신 처리 esp_input() — 복호화 + 인증 검증 xfrm_input() SA 검색 (SAD) + anti-replay 체크 Netfilter: PREROUTING xfrm_policy_check() SPD 검색 — 정책 검증 라우팅 결정 Netfilter: INPUT → 로컬 프로세스 송신 경로 (IPSec VPN) → 로컬 프로세스 (sendmsg) L4/L3 프로토콜 처리 (TCP→IP) Netfilter: OUTPUT 라우팅 선택 xfrm_lookup() SPD 검색 → SA 조회 → bundle 생성 xfrm_output() 터널 모드: 외부 IP 헤더 추가 ESP 송신 처리 esp_output() — 암호화 + 인증 MAC POSTROUTING → TC/Qdisc → NIC xfrm 통합 포인트 RX: ESP 복호화 → PREROUTING 전 RX: policy 검증 → 라우팅 전 TX: policy 검색 → 라우팅 후 TX: ESP 암호화 → POSTROUTING 전 → Netfilter 훅과 독립적 처리 → 라우팅과 긴밀한 통합
IPSec/xfrm과 네트워크 스택 통합: Netfilter 훅, 라우팅과의 상호작용
처리 단계송신 (TX)수신 (RX)
Netfilter 훅 위치 OUTPUT → xfrm → POSTROUTING ESP 복호화 → PREROUTING → xfrm policy 검증
라우팅 타이밍 라우팅 xfrm_lookup() 호출 xfrm policy 검증 라우팅 결정
xfrm 주요 함수 xfrm_lookup() → SPD 검색
xfrm_output() → SA 적용
esp_output() → 암호화
esp_input() → 복호화
xfrm_input() → SA 매칭
xfrm_policy_check() → SPD 검증
패킷 변환 평문 IP → ESP 캡슐화
터널 모드: 외부 IP 헤더 추가
ESP → 평문 IP 추출
터널 모드: 외부 IP 헤더 제거
sk_buff 메타데이터 skb_dst(skb)->xfrm에 SA 저장 skb->sp (secpath)에 처리된 SA 기록
정책 매칭 출력 인터페이스, 목적지 IP/포트로
SPD 검색 (셀렉터 매칭)
복호화 후 내부 IP/포트로
SPD 검증 (inbound policy)
실패 처리 SA 없음 → XFRM_MSG_ACQUIRE 전송
IKE 데몬에게 협상 요청
SA 없음 → 패킷 드롭
Policy 불일치 → 드롭
💡
xfrm과 Netfilter의 관계:
  • 독립적 처리: xfrm은 Netfilter 훅과 별도로 동작하며, ESP 암/복호화는 Netfilter 규칙보다 먼저 실행됩니다
  • 수신 경로: ESP 패킷은 복호화 → PREROUTING 훅 → policy 검증 순서로 처리됩니다. PREROUTING에서 보이는 패킷은 이미 복호화된 평문입니다
  • 송신 경로: OUTPUT 훅 통과 → 라우팅 → xfrm_lookup (정책 검색) → ESP 암호화 → POSTROUTING 훅 순서입니다
  • 방화벽 규칙: IPSec 터널 내부 트래픽을 필터링하려면 INPUT/OUTPUT 훅을 사용하세요 (복호화 후 평문 상태)
  • NAT 주의: DNAT는 PREROUTING에서, SNAT는 POSTROUTING에서 처리되므로 IPSec과 NAT를 함께 사용할 때 순서 주의 필요

IPSec과 GRO/GSO 통합

고성능 IPSec에서는 NIC의 GRO(Generic Receive Offload)와 GSO(Generic Segmentation Offload)가 ESP 처리와 어떻게 상호작용하는지가 중요합니다. 커널 4.18+에서는 ESP 전용 GRO/GSO 경로가 도입되어 대형 패킷을 세그먼트 단위로 분할하지 않고 한 번에 암복호화할 수 있습니다.

ESP GRO (수신) / GSO (송신) 처리 흐름 GRO 수신 경로 NIC RX ESP 패킷 수신 esp4_gro_receive() 동일 SA의 ESP 패킷 병합 Super-packet (64KB) GRO 병합된 대형 ESP 패킷 esp_input() 한 번에 복호화 (배치) TCP 평문 전달 GSO 송신 경로 TCP (평문) 64KB super-packet xfrm_output() GSO가 ESP 세그먼트 분할 연기 esp_xmit() per-segment ESP 헤더 + 암호화 NIC TX HW TSO 또는 SW GSO GRO/GSO 없는 경우 (레거시) - 패킷 1500B마다 개별 esp_input/output 호출 - per-packet crypto 초기화 오버헤드 - 높은 interrupt rate → CPU 포화 ~3-5 Gbps 한계 (소프트웨어 ESP) GRO/GSO 활성화 (커널 4.18+) - 64KB super-packet 단위 배치 처리 - crypto 초기화 1회 → 다수 세그먼트 처리 - interrupt coalescing 효과 극대화 ~15-25 Gbps 가능 (소프트웨어 ESP)
GRO/GSO를 통해 ESP 처리의 per-packet 오버헤드를 줄이면 소프트웨어 IPSec 성능이 3~5배 향상됩니다.
# ESP GRO/GSO 활성화 확인
ethtool -k eth0 | grep -i esp
# tx-esp-segmentation: on   → ESP GSO 활성
# rx-gro-hw: on             → HW GRO 활성
# esp-hw-offload: on        → ESP 오프로드 활성

# GSO 최대 크기 확인/조정
ip -d link show eth0 | grep gso_max
# gso_max_size 65536  gso_max_segs 65535

# ESP GRO 통계 확인
ethtool -S eth0 | grep -i esp
# rx_esp_input_pkts, tx_esp_output_pkts 등 (드라이버 의존)

# SW GRO/GSO만으로도 성능 향상 가능 (HW 지원 불필요)
ethtool -K eth0 gro on gso on tso on
ESP GRO/GSO 구현 상세:
  • GRO 수신: esp4_gro_receive()는 동일 SA(SPI)의 연속 ESP 패킷을 하나의 super-packet으로 병합합니다. 병합된 패킷은 esp_input()에서 한 번의 crypto 컨텍스트로 처리됩니다.
  • GSO 송신: TCP가 64KB super-packet을 내려보내면, esp_xmit()에서 MSS 크기 세그먼트별로 ESP 헤더와 IV를 삽입하고 개별 암호화합니다. 시퀀스 번호는 세그먼트마다 증가합니다.
  • 제한 사항: UDP ESP 패킷은 GRO 병합이 제한적이며, NAT-T(UDP 캡슐화) 환경에서도 GRO가 동작하지만 효율이 다소 떨어질 수 있습니다.
  • HW ESP offload + GSO: NIC가 inline crypto를 지원하면 GSO 세그먼트가 HW에서 암호화되어 CPU 사용이 거의 0에 가까워집니다.

xfrm 패킷 처리 경로 상세

xfrm의 패킷 처리는 Netfilter 훅과 밀접하게 통합되어 있습니다. 송신 경로에서는 라우팅 후 xfrm 정책 검색을 수행하고, 수신 경로에서는 ESP 복호화 후 정책 검증을 거칩니다.

xfrm 송신/수신 패킷 처리 경로 송신 (TX) ip_output() xfrm_lookup() xfrm_output() bundle 적용 esp_output() 암호화+MAC ip_finish_output NIC TX SPD 검색: 셀렉터 매칭 → SA 조회 수신 (RX) NIC RX ip_input() esp_input() 복호화+검증 xfrm_input() SA 매칭 xfrm_policy_check 정책 검증 ip_local_deliver (daddr, spi, proto) → SAD 해시 검색
/* net/xfrm/xfrm_output.c — 송신 경로 핵심 */
static int xfrm_output_one(struct sk_buff *skb, int err)
{
    struct xfrm_state *x = skb_dst(skb)->xfrm;
    /* 1. 시퀀스 번호 할당 (ESN 지원) */
    err = x->outer_mode.output(x, skb);    /* 터널: 외부 IP 헤더 추가 */
    err = x->type->output(x, skb);         /* ESP: esp_output() 호출 */
    /* 2. skb→dst를 외부 라우팅 엔트리로 교체 */
    /* 3. 중첩 SA가 있으면 다음 xfrm_state에 대해 반복 (bundle) */
}

/* net/ipv4/esp4.c — ESP 암호화 처리 */
static int esp_output(struct xfrm_state *x, struct sk_buff *skb)
{
    struct crypto_aead *aead = x->data;
    /* 1. ESP 헤더 (SPI + Seq#) 삽입 */
    /* 2. IV 생성 (AEAD: salt + seq_no) */
    /* 3. 패딩 추가 (블록 크기 정렬) */
    /* 4. aead_request 생성 → crypto_aead_encrypt() */
    /*    → 비동기 완료: esp_output_done() 콜백 */
    /* 5. ICV 첨부 */
}

/* net/xfrm/xfrm_input.c — 수신 경로 핵심 */
int xfrm_input(struct sk_buff *skb, int nexthdr,
              __be32 spi, int encap_type)
{
    /* 1. (daddr, spi, proto)로 SAD 해시 테이블 검색 */
    x = xfrm_state_lookup(net, &daddr, spi, nexthdr, family);
    /* 2. anti-replay 검사 */
    xfrm_replay_check(x, skb, seq);
    /* 3. ESP 복호화: x→type→input() → esp_input() */
    /* 4. anti-replay 윈도우 업데이트 */
    xfrm_replay_advance(x, seq);
    /* 5. 정책 검증: 복호화된 패킷이 SP와 일치하는지 확인 */
    /*    (수신 정책 없으면 드롭 — XfrmInNoPols) */
}

/* xfrm_state 해시 테이블 검색 — O(1) 평균 */
/* 키: (daddr, spi, proto) → 해시 버킷 → 체인 순회 */
/* 대규모 SA 환경에서도 검색 성능 보장 */
xfrm_bundle과 캐싱: xfrm_lookup()에서 정책에 매칭되면 xfrm_bundle_create()가 호출되어 SA 체인(여러 SA를 순서대로 적용: 예컨대 IPCOMP → ESP)을 생성합니다. 이 번들은 dst_entry에 캐싱되어 동일 흐름의 후속 패킷은 정책 검색 없이 바로 SA를 적용합니다. 라우팅 테이블 변경이나 SA 만료 시 캐시가 무효화됩니다.

xfrm_policy 내부 구조

/* include/net/xfrm.h — Security Policy 핵심 구조체 */
struct xfrm_policy {
    struct hlist_node bydst;       /* dst 주소별 해시 체인 */
    struct hlist_node byidx;       /* 인덱스별 해시 체인 */

    struct xfrm_selector selector;  /* 트래픽 셀렉터 (아래 상세) */
    struct xfrm_lifetime_cfg lft;   /* 수명: 바이트/패킷/시간 */
    struct xfrm_lifetime_cur curlft; /* 현재 사용량 카운터 */

    u8 type;           /* XFRM_POLICY_TYPE_MAIN / SUB */
    u8 action;         /* XFRM_POLICY_ALLOW / BLOCK */
    u8 flags;          /* XFRM_POLICY_LOCALOK, ICMP 등 */
    u8 xfrm_nr;        /* tmpl 배열 크기 (최대 6) */
    u16 family;        /* AF_INET / AF_INET6 */
    u32 priority;      /* 정책 우선순위 (낮을수록 높음) */
    u32 if_id;         /* xfrm interface ID (4.19+) */

    struct xfrm_tmpl xfrm_vec[XFRM_MAX_DEPTH]; /* SA 템플릿 */
    /* tmpl: 요구하는 SA의 속성 (proto, mode, reqid, level) */
};

/* 트래픽 셀렉터 — 어떤 패킷에 정책을 적용할지 결정 */
struct xfrm_selector {
    xfrm_address_t daddr;    /* 목적지 주소 */
    xfrm_address_t saddr;    /* 소스 주소 */
    __be16 dport;             /* 목적지 포트 */
    __be16 dport_mask;        /* 포트 마스크 (0xFFFF = exact) */
    __be16 sport;             /* 소스 포트 */
    __be16 sport_mask;
    __u16 family;             /* AF_INET / AF_INET6 */
    __u8 prefixlen_d;         /* 목적지 서브넷 길이 */
    __u8 prefixlen_s;         /* 소스 서브넷 길이 */
    __u8 proto;               /* 프로토콜 (6=TCP, 17=UDP, 0=all) */
    int ifindex;              /* 인터페이스 바인딩 */
    __kernel_uid32_t user;   /* UID 기반 정책 (Android) */
};

SPD 검색 알고리즘: 정책 검색은 3개의 방향(in/out/fwd)별로 독립된 해시 테이블에서 수행됩니다. 패킷의 (src, dst, proto, sport, dport)를 셀렉터와 매칭하며, 여러 정책이 매칭되면 priority가 가장 낮은(= 우선순위 높은) 정책이 선택됩니다.

정책 수가 많을 때: ip xfrm policy set hthresh4 LBITS RBITS, ip xfrm policy set hthresh6 LBITS RBITS로 해시 임계값을 조정할 수 있습니다. prefix가 짧은 광범위 정책은 inexact chain에 남기 쉽기 때문에, 대규모 SPD에서는 broad selector를 최소화하는 편이 좋습니다.
/* net/xfrm/xfrm_policy.c — SPD 검색 핵심 */
static struct xfrm_policy *
xfrm_policy_lookup_bytype(struct net *net, u8 type,
    const struct flowi *fl, u16 family, u8 dir)
{
    /* 1. (dst_addr, family) 기반 해시 버킷 선택 */
    /* 2. 버킷 내 정책 순회 → 셀렉터 매칭 검사 */
    /*    xfrm_selector_match(sel, fl, family) */
    /* 3. 매칭된 정책 중 priority 최소값 반환 */
    /* 4. action == BLOCK이면 패킷 드롭 (XfrmOutPolBlock) */
    /* 5. action == ALLOW이면 tmpl 배열로 SA 검색 */
}

/* 정책 방향 (dir) */
XFRM_POLICY_IN   0  /* 수신: 복호화 후 정책 검증 */
XFRM_POLICY_OUT  1  /* 송신: 패킷 나가기 전 정책 검색 */
XFRM_POLICY_FWD  2  /* 포워딩: 라우터 역할 시 터널 간 전달 */
xfrm_policy_check()와 Netfilter 통합: 수신 경로에서 xfrm_policy_check()는 Netfilter의 NF_INET_PRE_ROUTING 이후, ip_local_deliver() 이전에 호출됩니다. 복호화된 패킷의 셀렉터가 수신 정책(XFRM_POLICY_IN)과 일치하지 않으면 패킷이 드롭되어, 정책 우회 공격을 방지합니다. 이는 "수신 시에도 반드시 정책 검증"이라는 IPSec의 보안 원칙을 구현합니다.

커널 xfrm 서브시스템과 유저스페이스 IKE 데몬 간 통신에는 두 가지 API가 있습니다. PF_KEY(RFC 2367)는 BSD 유래의 레거시 인터페이스이고, NETLINK_XFRM은 리눅스 전용의 현대 인터페이스입니다. 현재 모든 주요 IKE 데몬은 NETLINK_XFRM을 기본으로 사용합니다.

특성PF_KEY (AF_KEY)NETLINK_XFRM
표준 RFC 2367 (1998) 리눅스 전용 (include/uapi/linux/xfrm.h)
소켓 타입 socket(PF_KEY, SOCK_RAW, PF_KEY_V2) socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM)
기능 범위 SA 관리 (SADB_*), 제한적 SPD SA + SPD + MIGRATE + NEWAE + 모니터링 + offload 전체
if_id 지원 미지원 지원 (xfrm interface, route-based VPN)
mark/output-mark 미지원 지원
HW offload 미지원 지원 (XFRMA_OFFLOAD_DEV)
ESN 제한적 (커널 확장) 완전 지원 (XFRMA_REPLAY_ESN_VAL)
IKE 데몬 Racoon (ipsec-tools, 폐기됨) strongSwan, Libreswan, iproute2
커널 코드 net/key/af_key.c net/xfrm/xfrm_user.c
권장 여부 레거시, 비권장 현행 표준, 모든 신규 배포 권장
/* NETLINK_XFRM 주요 메시지 속성 (NLA — Netlink Attribute) */
/* include/uapi/linux/xfrm.h */

/* SA 생성 시 포함되는 주요 속성 */
XFRMA_ALG_AEAD         /* AEAD 알고리즘 (AES-GCM) */
XFRMA_ALG_AUTH_TRUNC   /* 인증 알고리즘 + truncation 길이 */
XFRMA_ALG_CRYPT        /* 암호화 알고리즘 */
XFRMA_ENCAP            /* NAT-T 캡슐화 정보 */
XFRMA_REPLAY_ESN_VAL   /* ESN 상태/윈도우 */
XFRMA_OFFLOAD_DEV      /* H/W offload 대상 디바이스 */
XFRMA_IF_ID            /* xfrm interface 식별자 */
XFRMA_SET_MARK         /* SA에 적용할 fwmark */
XFRMA_SET_MARK_MASK    /* fwmark 마스크 */
XFRMA_SA_PCPU          /* per-CPU SA 분산 (6.7+) */

/* PF_KEY → NETLINK_XFRM 매핑 예시 */
/*   SADB_ADD       → XFRM_MSG_NEWSA     */
/*   SADB_DELETE     → XFRM_MSG_DELSA     */
/*   SADB_ACQUIRE    → XFRM_MSG_ACQUIRE   */
/*   SADB_X_SPDADD   → XFRM_MSG_NEWPOLICY */
/*   SADB_EXPIRE     → XFRM_MSG_EXPIRE    */
PF_KEY 폐기 상태: CONFIG_NET_KEY는 커널에 남아 있지만 새로운 xfrm 기능(if_id, packet offload, per-CPU SA, ESN 확장 등)은 NETLINK_XFRM에만 추가됩니다. PF_KEY를 사용하는 Racoon/ipsec-tools는 2015년 이후 사실상 관리되지 않으며, 최신 커널의 기능을 활용할 수 없습니다. 레거시 시스템에서 마이그레이션 시 strongSwan 또는 Libreswan으로 전환하세요.

NAT Traversal (NAT-T) 상세

ESP는 IP 프로토콜 번호 50을 사용하므로, 포트 번호가 없어 일반 NAT가 처리할 수 없습니다. NAT-T(NAT Traversal, RFC 3948)는 ESP 패킷을 UDP 4500 포트로 캡슐화하여 NAT 장비를 통과할 수 있게 합니다.

UDP 4500 위에서는 세 가지가 공존합니다. IKE는 UDP 헤더 뒤에 4바이트 Non-ESP Marker(0x00000000)를 두고, NAT keepalive는 1바이트 0xFF만 보내며, UDP-encapsulated ESP는 곧바로 ESP Header의 0이 아닌 SPI가 시작됩니다.

NAT-T 캡슐화 패킷 구조 일반 ESP: IP (proto=50) ESP Header Encrypted Payload + Trailer + ICV NAT-T ESP: IP (proto=17) UDP (4500→4500) ESP Header Encrypted Payload + Trailer + ICV Keep-alive: IP (proto=17) UDP (4500→4500) 0xFF (1 byte) NAT-T 판별: SPI의 첫 4바이트가 0x00000000이면 IKE, 0xFF 1바이트면 Keep-alive, 그 외 ESP
/* NAT-T 감지: IKEv2 NAT_DETECTION_*_IP payload */
/* IKE_SA_INIT 교환에서 양쪽이 NAT 감지 해시 전송:
 *   HASH = SHA-1(SPIi | SPIr | IP | port)
 * 수신 측에서 자신의 IP/port로 재계산한 해시와 비교
 * → 불일치하면 경로 상에 NAT 존재 → NAT-T 활성화
 */

/* 커널 NAT-T 처리: net/ipv4/esp4.c + net/ipv4/udp.c */
/* 수신: UDP 4500 소켓에 ESP-in-UDP 핸들러 등록 */
static int esp4_rcv_cb(struct sk_buff *skb)
{
    /* 1. UDP 헤더 제거 */
    /* 2. SPI로 xfrm_state 검색 */
    /* 3. encap_type = UDP_ENCAP_ESPINUDP 설정 */
    /* 4. esp_input()으로 복호화 진행 */
}

/* 송신: xfrm_state에 encap 정보가 있으면 UDP 래핑 */
struct xfrm_encap_tmpl {
    __u16 encap_type;   /* UDP_ENCAP_ESPINUDP (2) */
    __be16 encap_sport; /* 로컬 UDP 포트 (4500) */
    __be16 encap_dport; /* 원격 UDP 포트 (4500) */
    xfrm_address_t encap_oa; /* 원본 주소 (NAT 이전) */
};
NAT 유형별 문제:
  • Full Cone NAT — NAT-T로 문제 없이 통과
  • Restricted/Port Restricted NAT — Keep-alive 패킷(20~30초 간격)으로 NAT 매핑 유지 필요
  • Symmetric NAT — 목적지마다 다른 외부 포트 할당. IKE에서 감지한 포트와 ESP의 실제 매핑이 다를 수 있어 연결 실패 가능. MOBIKE의 주소 업데이트로 완화
  • 이중 NAT — 양쪽 모두 NAT 뒤에 있는 경우. NAT-T 필수이며, 양쪽 IKE 데몬이 모두 NAT를 감지해야 함

xfrm interface vs VTI

리눅스에서 route-based VPN을 구현하는 두 가지 방법이 있습니다: 레거시 VTI(Virtual Tunnel Interface)와 커널 4.19에서 도입된 xfrm interface입니다. xfrm interface는 VTI의 한계를 해결하고 현대 VPN 아키텍처에 필수적인 기능을 제공합니다.

특성VTI (ip_vti)xfrm interface (커널 4.19+)
인터페이스 생성 ip tunnel add vti0 mode vti ... ip link add xfrm0 type xfrm ...
SA 바인딩 터널 src/dst IP 주소로 매칭 if_id 정수값으로 매칭 (IP 무관)
다중 터널 동일 피어에 하나의 VTI만 가능 서로 다른 if_id로 다중 터널 가능
네트워크 네임스페이스 제한적 (SA와 같은 netns에만) 완전 지원 (인터페이스와 SA 분리 가능)
IPv4/IPv6 통합 vti (IPv4), vti6 (IPv6) 별도 단일 인터페이스로 IPv4/IPv6 모두 처리
멀티 테넌트 비실용적 VRF + netns + if_id 조합으로 완전 격리
라우팅 통합 기본적 완전한 route-based VPN (BGP/OSPF over IPSec)
# xfrm interface 생성 및 설정
# 1. xfrm interface 생성 (if_id=42로 SA와 바인딩)
ip link add xfrm0 type xfrm dev eth0 if_id 42
ip addr add 10.10.0.1/30 dev xfrm0
ip link set xfrm0 up

# 2. SA에 if_id 지정
ip xfrm state add src 203.0.113.1 dst 198.51.100.1 \
    proto esp spi 0x1000 mode tunnel if_id 42 \
    aead 'rfc4106(gcm(aes))' 0x$(openssl rand -hex 20) 128

# 3. 정책에 if_id 지정
ip xfrm policy add dir out if_id 42 \
    src 0.0.0.0/0 dst 0.0.0.0/0 \
    tmpl src 203.0.113.1 dst 198.51.100.1 proto esp mode tunnel

# 4. 라우팅: xfrm interface를 통해 터널 트래픽 라우팅
ip route add 192.168.2.0/24 dev xfrm0

# 멀티 터널 시나리오 (서로 다른 피어에 대해 별도 xfrm interface)
ip link add xfrm1 type xfrm dev eth0 if_id 43
ip link add xfrm2 type xfrm dev eth0 if_id 44
# → BGP/OSPF 동적 라우팅을 각 xfrm interface에서 실행 가능

# 네임스페이스 격리 (멀티 테넌트)
ip netns add tenant1
ip link set xfrm0 netns tenant1
ip netns exec tenant1 ip addr add 10.10.0.1/30 dev xfrm0
ip netns exec tenant1 ip link set xfrm0 up
# → tenant1 네임스페이스 내에서만 IPSec 터널 접근 가능
Route-based vs Policy-based VPN: Policy-based VPN은 ip xfrm policy의 셀렉터로 트래픽을 직접 매칭합니다. 설정이 간단하지만 동적 라우팅과 호환이 어렵습니다. Route-based VPN은 xfrm interface에 라우팅 엔트리를 추가하여 트래픽을 유도합니다. BGP/OSPF 같은 동적 라우팅 프로토콜을 IPSec 위에서 실행할 수 있어 대규모 사이트 간 VPN(수백 개 터널)에 필수적입니다.
xfrmi 라우팅 루프 주의: strongSwan 공식 문서는 install_routes_xfrmi를 사용할 때 IKE/ESP 패킷 자체가 xfrm interface로 다시 라우팅되지 않도록 fwmark 또는 peer에 대한 throw route를 별도로 두라고 권장합니다. 일반적인 패턴은 socket-default.fwmarkset_mark_out으로 IKE/ESP를 표시하고, table 220 같은 xfrmi 전용 라우팅 테이블에서 그 mark를 제외하는 방식입니다.

xfrm 네임스페이스와 네트워크 격리

리눅스 네트워크 네임스페이스(netns)는 xfrm SPD/SAD를 완전히 격리합니다. 각 네임스페이스는 독립된 정책/상태 테이블, 통계 카운터, sysctl 파라미터를 가지며, xfrm interface는 네임스페이스 간 이동이 가능하여 멀티 테넌트 VPN 아키텍처를 구현할 수 있습니다.

xfrm 네임스페이스 격리 — 멀티 테넌트 VPN Host Network Namespace (init_net) eth0 (물리 NIC) IKE Daemon (strongSwan charon) SPD / SAD (호스트 정책/SA) xfrm0 (if_id=42) xfrm1 (if_id=43) xfrm2 (if_id=44) ip link set netns Tenant A netns 독립 SPD/SAD | 독립 라우팅 | 192.168.10.0/24 xfrm0 Tenant B netns 독립 SPD/SAD | 독립 라우팅 | 192.168.20.0/24 xfrm1 Tenant C netns 독립 SPD/SAD | 독립 라우팅 | 192.168.30.0/24 xfrm2 격리 보장 - 각 netns: 독립 SPD, SAD, xfrm_stat - SA/SP는 호스트 netns에 설치 - xfrm interface를 테넌트 netns로 이동 - 테넌트는 평문만 보임 (ESP 불가시) - VRF 결합으로 라우팅도 완전 격리 - ip xfrm monitor all-nsid로 통합 관측 IKE는 호스트 netns에서만 실행, 보안 경계 분리
호스트 netns에서 IKE와 SA/SP를 관리하고, xfrm interface를 테넌트 netns로 이동하면 테넌트는 암호화를 의식하지 않고 평문 트래픽만 다룹니다.
# 멀티 테넌트 VPN 네임스페이스 설정 예시

# 1. 테넌트 네임스페이스 생성
ip netns add tenant-a
ip netns add tenant-b

# 2. xfrm interface 생성 (호스트 netns)
ip link add xfrm-a type xfrm dev eth0 if_id 100
ip link add xfrm-b type xfrm dev eth0 if_id 200

# 3. xfrm interface를 테넌트 netns로 이동
ip link set xfrm-a netns tenant-a
ip link set xfrm-b netns tenant-b

# 4. 테넌트별 주소/라우팅 설정
ip netns exec tenant-a bash -c '
    ip addr add 10.10.1.1/30 dev xfrm-a
    ip link set xfrm-a up
    ip route add 192.168.100.0/24 dev xfrm-a
'
ip netns exec tenant-b bash -c '
    ip addr add 10.10.2.1/30 dev xfrm-b
    ip link set xfrm-b up
    ip route add 192.168.200.0/24 dev xfrm-b
'

# 5. SA/SP는 호스트 netns에서 설치 (if_id로 바인딩)
ip xfrm state add src 203.0.113.1 dst 198.51.100.1 \
    proto esp spi 0x100 mode tunnel if_id 100 \
    aead 'rfc4106(gcm(aes))' 0x$(openssl rand -hex 20) 128

ip xfrm policy add dir out if_id 100 \
    src 0.0.0.0/0 dst 0.0.0.0/0 \
    tmpl src 203.0.113.1 dst 198.51.100.1 proto esp mode tunnel

# 6. 네임스페이스 간 xfrm 이벤트 모니터링
ip xfrm monitor all-nsid  # 모든 netns의 xfrm 이벤트 통합 관측

# 7. 테넌트별 xfrm 통계 확인
ip netns exec tenant-a cat /proc/net/xfrm_stat
컨테이너/쿠버네티스 환경:
  • Cilium은 노드 간 Pod 트래픽을 IPSec(ESP)로 암호화합니다. 커널 xfrm을 사용하며, per-node SA를 자동 관리합니다. cilium encrypt status로 확인.
  • Calico도 IPSec 모드(ipsecMode: Always)를 지원하며, LibreSwan을 IKE 데몬으로 사용합니다.
  • WireGuard 대안: Cilium은 커널 5.6+에서 WireGuard도 지원하며, xfrm 대비 설정이 단순하고 성능이 좋을 수 있습니다.
  • Service Mesh: mTLS(Istio/Linkerd)는 L7에서 암호화하므로 IPSec과 중복 적용하면 이중 암호화 오버헤드가 발생합니다.
  • 네임스페이스 격리: Pod별 netns에서 xfrm 통계가 독립되므로, 장애 진단 시 올바른 netns에서 /proc/net/xfrm_stat을 확인해야 합니다.

Anti-replay 메커니즘

Anti-replay는 공격자가 캡처한 ESP 패킷을 재전송하는 것을 방지합니다. 수신 측은 슬라이딩 윈도우 비트맵을 유지하여 이미 처리한 시퀀스 번호의 패킷을 거부합니다.

Anti-replay 슬라이딩 윈도우 비트맵 1 seq 90 1 91 0 92 1 93 1 94 0 95 ··· 1 118 0 119 1 120 ← 윈도우 왼쪽 경계 ← 최신 수신 seq = 수신 완료 (비트 1) = 미수신/패킷 손실 (비트 0) seq < 윈도우 왼쪽 → 드롭 (너무 오래된 패킷) seq > 120 → 윈도우 오른쪽으로 슬라이드, 새 비트 설정 윈도우 내 비트=1 → 드롭 (중복/재전송 패킷)
/* net/xfrm/xfrm_replay.c — anti-replay 검사 (ESN 모드) */
static int xfrm_replay_check_esn(struct xfrm_state *x,
    struct sk_buff *skb, __be32 net_seq)
{
    u32 seq = ntohl(net_seq);
    struct xfrm_replay_state_esn *replay_esn = x->replay_esn;
    u32 wsize = replay_esn->replay_window;
    u32 top = replay_esn->seq;         /* 최신 수신 시퀀스 */
    u32 bottom = top - wsize + 1;     /* 윈도우 왼쪽 경계 */

    /* Case 1: 윈도우 오른쪽 밖 → 새 패킷, 수락 */
    if (likely(seq > top))
        return 0;

    /* Case 2: 윈도우 왼쪽 밖 → 너무 오래된 패킷, 드롭 */
    if (seq < bottom)
        return -EINVAL;  /* XfrmInSeqOutOfWindow */

    /* Case 3: 윈도우 내 → 비트맵 검사 */
    u32 diff = top - seq;
    u32 pos = diff / 32;
    u32 bit = 1 << (diff % 32);
    if (replay_esn->bmp[pos] & bit)
        return -EINVAL;  /* XfrmInStateReplay — 중복 패킷 */

    return 0;  /* 윈도우 내 미수신 패킷, 수락 */
}

/* 윈도우 업데이트: 패킷 수락 후 비트맵 갱신 */
static void xfrm_replay_advance_esn(struct xfrm_state *x, __be32 net_seq)
{
    /* seq > top이면 윈도우 오른쪽으로 슬라이드 */
    /* 이동 과정에서 벗어난 비트들은 0으로 클리어 */
    /* 새 seq 위치의 비트를 1로 설정 */
}
고대역 환경 윈도우 크기 튜닝: 기본 윈도우 크기는 32패킷으로 10Gbps 환경에서는 패킷 재정렬(RSS, 멀티큐)로 인해 정상 패킷이 윈도우 밖으로 밀려나 드롭될 수 있습니다. ip xfrm state add ... replay-window 2048로 확대하거나, ESN 활성화 시 최대 4096까지 설정 가능합니다. /proc/net/xfrm_statXfrmInSeqOutOfWindow 카운터가 증가하면 윈도우 확대가 필요합니다.

RFC 4301은 서로 다른 QoS/재정렬 특성을 가진 트래픽을 같은 selector 하나로 몰아넣지 말라고 봅니다. 대역폭이 높고 RSS/ECMP 재정렬이 심한 환경이라면 윈도우만 키우는 것보다, 트래픽 클래스를 SA 단위로 나누는 쪽이 더 안정적일 수 있습니다.

IPSec 하드웨어 오프로드

소프트웨어 ESP 처리는 CPU 집약적이어서 10Gbps 이상 환경에서 병목이 됩니다. 리눅스 커널의 공식 XFRM device API는 두 가지 오프로드만 정의합니다: crypto offloadpacket offload입니다. 업계에서 말하는 "inline crypto", "full offload", "DPU offload"는 대개 이 둘, 특히 packet offload의 구현 형태를 가리키는 벤더 용어입니다.

커널 모드하드웨어가 하는 일커널이 계속 하는 일대표 장비비고
Crypto offload encrypt/decrypt만 수행 ESP 헤더 추가/제거, 시퀀스 번호, replay, 정책 판단, 수명 관리는 커널이 담당 Intel QAT, 일부 SmartNIC/NIC의 crypto mode ip xfrm state ... offload dev eth0. 실패 시 소프트웨어 fallback이 가능한 경우가 많음
Packet offload encrypt/decrypt + encapsulation, 그리고 SA/policy 상태 일부를 HW가 유지 커널은 key manager와 정책/상태 동기화 주체로 남음 NVIDIA ConnectX 계열, Intel E810, 일부 DPU offload packet 필요. state뿐 아니라 policy offload까지 함께 다뤄야 함
용어 정리: 커널 문서상으로는 crypto offloadpacket offload 두 가지뿐입니다. 벤더가 말하는 inline crypto는 대개 packet offload의 데이터 경로 구현을, full offload / DPU offload는 packet offload에 스위칭/steering까지 결합한 상품 형태를 뜻합니다.
💡

Crypto API 관점의 IPsec 오프로드: xfrmdev_ops 콜백, crypto vs packet offload 선택 기준, kTLS/MACsec과의 비교, PCI 가속기(QAT) 연동은 Crypto Framework — 네트워크 암호화 오프로드에서 종합적으로 다룹니다.

/* include/net/xfrm.h — H/W 오프로드 구조체 */
struct xfrm_dev_offload {
    struct net_device *dev;     /* 오프로드 대상 NIC */
    struct net_device *real_dev; /* bond/vlan 하위 실제 디바이스 */
    unsigned long offload_handle; /* 드라이버 전용 핸들 */
    u8 dir : 2;               /* XFRM_DEV_OFFLOAD_IN / OUT */
    u8 type : 2;              /* CRYPTO / PACKET */
    u8 flags : 2;             /* XFRM_DEV_OFFLOAD_FLAG_ACE 등 */
};

/* NIC 드라이버가 구현하는 xdo_dev_* 콜백 */
struct xfrmdev_ops {
    int (*xdo_dev_state_add)(struct net_device *dev,
                             struct xfrm_state *x,
                             struct netlink_ext_ack *extack);
    void (*xdo_dev_state_delete)(struct net_device *dev,
                                        struct xfrm_state *x);
    void (*xdo_dev_state_free)(struct net_device *dev,
                                      struct xfrm_state *x);
    bool (*xdo_dev_offload_ok)(struct sk_buff *skb,
                              struct xfrm_state *x);
    /* packet offload는 정책 콜백도 필요 */
    int (*xdo_dev_policy_add)(struct xfrm_policy *p,
                              struct netlink_ext_ack *extack);
};
# Inline crypto offload 설정 (NVIDIA ConnectX-6 Dx 예시)
# 1. crypto offload: 암복호화만 HW
ip xfrm state add src 203.0.113.1 dst 198.51.100.1 \
    proto esp spi 0x1000 mode tunnel \
    aead 'rfc4106(gcm(aes))' 0x$(openssl rand -hex 20) 128 \
    offload dev eth0 dir out

# 2. packet offload: state + policy를 HW와 동기화
ip xfrm state add src 203.0.113.1 dst 198.51.100.1 \
    proto esp spi 0x2000 mode transport \
    aead 'rfc4106(gcm(aes))' 0x$(openssl rand -hex 20) 128 \
    sel src 203.0.113.1/32 dst 198.51.100.1/32 proto tcp dport 443 \
    offload packet dev eth0 dir out

ip xfrm policy add src 203.0.113.1/32 dst 198.51.100.1/32 proto tcp dport 443 \
    dir out offload packet dev eth0 \
    tmpl src 203.0.113.1 dst 198.51.100.1 proto esp mode transport

# 3. 오프로드 상태 확인
ip xfrm state list
ip xfrm policy list
# → "offload packet dev eth0" 또는 "offload dev eth0" 표시

# Intel QAT crypto offload (AEAD)
# QAT 드라이버 로드 → openssl engine → strongSwan에서 QAT 플러그인 사용
modprobe qat_4xxx                    # Intel 4세대 QAT 디바이스
# strongSwan: charon.plugins.openssl.engine_id = qatengine

# 오프로드 실패 시 자동 소프트웨어 폴백
# ethtool -k eth0 | grep esp
# esp-hw-offload: on → 하드웨어 오프로드 활성화됨
오프로드 선택 가이드:
  • Crypto offload: 도입이 가장 쉽습니다. 커널 의미론이 거의 그대로 유지되어 디버깅과 fallback이 단순합니다.
  • Packet offload: 성능은 가장 좋지만 driver가 policy/state/lifetime 통계를 정확히 올려야 합니다. unsupported면 단순 fallback이 어려울 수 있습니다.
  • DPU/inline/full: 커널 generic 타입이 아니라 packet offload 위에 벤더 데이터 경로를 얹은 형태로 이해하는 편이 정확합니다.

strongSwan/Libreswan 실전 설정

IKE 데몬은 커널 xfrm과 협력하여 SA의 자동 생성/갱신/삭제를 처리합니다. 현대 리눅스 환경에서는 strongSwan(swanctl)과 Libreswan(ipsec.conf)이 주로 사용됩니다.

# /etc/swanctl/swanctl.conf — strongSwan site-to-site 설정
connections {
    site-to-site {
        version = 2                    # IKEv2 전용
        local_addrs = 203.0.113.1
        remote_addrs = 198.51.100.1

        local {
            auth = pubkey                # X.509 인증서 인증
            certs = server.pem
            id = vpn.example.com
        }
        remote {
            auth = pubkey
            id = vpn.peer.com
        }

        proposals = aes256gcm128-x25519-sha256  # IKE SA 암호 스위트
        dpd_delay = 30s                 # DPD 간격

        children {
            lan-to-lan {
                local_ts = 192.168.1.0/24   # 로컬 트래픽 셀렉터
                remote_ts = 192.168.2.0/24  # 원격 트래픽 셀렉터
                esp_proposals = aes256gcm128-x25519  # CHILD SA 암호 스위트
                rekey_time = 3600s          # 1시간마다 rekey
                replay_window = 2048       # Anti-replay 윈도우
                start_action = start        # 부팅 시 자동 연결
                dpd_action = restart        # DPD 실패 시 재연결
                # hw_offload = packet      # inline crypto 오프로드 (지원 NIC)
            }
        }
    }
}

# Road Warrior (모바일 클라이언트) 설정
connections {
    roadwarrior {
        version = 2
        local_addrs = %any              # 서버: 모든 주소에서 수신
        pools = pool-ipv4               # 클라이언트에게 IP 할당

        local {
            auth = pubkey
            certs = server.pem
        }
        remote {
            auth = eap-mschapv2         # EAP 인증 (사용자/비밀번호)
            eap_id = %any
        }

        children {
            rw-child {
                local_ts = 0.0.0.0/0    # 모든 트래픽 터널링
            }
        }
    }
}

pools {
    pool-ipv4 {
        addrs = 10.10.0.0/24
        dns = 8.8.8.8, 8.8.4.4
    }
}
작업strongSwan (swanctl)Libreswan (ipsec)
설정 로드 swanctl --load-all ipsec auto --add conn-name
연결 시작 swanctl --initiate --child lan-to-lan ipsec auto --up conn-name
SA 목록 swanctl --list-sas ipsec whack --trafficstatus
연결 종료 swanctl --terminate --child lan-to-lan ipsec auto --down conn-name
디버그 로그 swanctl --log --level 2 ipsec whack --debug-all
인증서 목록 swanctl --list-certs ipsec whack --listcerts
설정 파일 /etc/swanctl/swanctl.conf /etc/ipsec.conf + /etc/ipsec.secrets
커널 연동 charon.plugins.kernel-netlink pluto 데몬 → NETLINK_XFRM
strongSwan 커널 연동 상세: strongSwan의 charon 데몬은 kernel-netlink 플러그인으로 NETLINK_XFRM 소켓을 통해 커널과 통신합니다. swanctl --load-all 실행 시 설정이 charon에 로드되고, IKEv2 교환 완료 후 XFRM_MSG_NEWSA/XFRM_MSG_NEWPOLICY로 SA/SP를 커널에 주입합니다. kernel-netlink 플러그인 설정: charon.plugins.kernel-netlink.xfrm_acq_expires = 165 (ACQUIRE 타임아웃), charon.plugins.kernel-netlink.set_mark = yes (fwmark 연동).

IPSec 성능 튜닝

IPSec 성능은 암호 알고리즘, CPU 아키텍처, 패킷 크기, NIC 설정에 크게 의존합니다. 고성능 환경에서는 체계적인 벤치마크와 프로파일링이 필수적입니다.

알고리즘x86_64 (AES-NI)ARM64 (NEON/CE)비고
AES-128-GCM ~40 Gbps ~8 Gbps (ARMv8 CE) AES-NI + CLMUL 하드웨어 가속. 가장 보편적
AES-256-GCM ~32 Gbps ~6 Gbps AES-128 대비 ~20% 느림 (4 라운드 추가)
ChaCha20-Poly1305 ~15 Gbps ~10 Gbps (NEON) AES-NI 없는 환경에서 고성능. ARM에서 AES-GCM보다 빠를 수 있음
AES-256-CBC + HMAC-SHA256 ~12 Gbps ~3 Gbps 2-pass 처리. 레거시 호환용. AEAD 대비 ~60% 느림
패킷 크기별 throughput 특성: 작은 패킷(64B)은 per-packet 오버헤드(ESP 헤더, 암호화 초기화, DMA 설정)가 지배적이어서 throughput이 크게 저하됩니다. 1500B 패킷 대비 64B 패킷은 ~10배 낮은 throughput을 보입니다. VoIP 등 소형 패킷 위주 트래픽에서는 하드웨어 오프로드 효과가 극대화됩니다.
# CPU affinity와 RPS/RFS 최적화
# ESP 처리를 특정 CPU에 고정하여 캐시 효율 극대화

# 1. NIC 인터럽트를 특정 CPU에 바인딩
# (CPU 0~3: 일반 트래픽, CPU 4~7: ESP 처리)
for i in /proc/irq/*/smp_affinity_list; do
    irq=$(echo $i | grep -oP '/proc/irq/\K[0-9]+')
    cat /proc/irq/$irq/actions | grep -q eth0 && echo "4-7" > $i
done

# 2. RPS (Receive Packet Steering) — 소프트웨어 수신 분산
echo f0 > /sys/class/net/eth0/queues/rx-0/rps_cpus   # CPU 4-7
echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

# 3. xfrm 관련 sysctl 파라미터
sysctl -w net.core.xfrm_larval_drop=1    # SA 미완성 시 패킷 즉시 드롭 (대기 안 함)
sysctl -w net.core.xfrm_acq_expires=30   # ACQUIRE 타임아웃 (초)
sysctl -w net.core.xfrm_aevent_rseqth=2  # replay 이벤트 시퀀스 임계값
sysctl -w net.ipv4.xfrm4_gc_thresh=32768 # xfrm dst 가비지 컬렉션 임계값

# perf 프로파일링 — ESP 처리 핫스팟 식별
perf top -C 4-7 -g                        # ESP 처리 CPU에서 실시간 프로파일
perf record -C 4-7 -g -- sleep 10        # 10초 샘플링
perf report --sort=dso,sym                # 심볼별 CPU 사용량
# 주요 핫스팟: gcm_hash_crypt_*, aesni_ctr_enc, esp_output/input

# 암호 알고리즘 벤치마크 (커널 crypto API 테스트)
modprobe tcrypt sec=1 mode=211           # AES-GCM 벤치마크
dmesg | tail -50                          # 결과 확인
대규모 터널(수백~수천 SA) 환경 최적화:
  • SAD 해시 테이블 — SA 수가 많으면 해시 충돌 증가. xfrm4_gc_thresh를 SA 수의 2배 이상으로 설정
  • SPD 검색 — 정책이 많으면 선형 검색이 병목. 셀렉터를 최대한 구체적으로 설정하고, 불필요한 정책 제거
  • CHILD_SA rekey 폭풍 — 모든 터널이 동시에 rekey되면 CPU 스파이크. rekey_timerand_time을 추가하여 분산: rand_time = 600s
  • NAPI 배치 처리 — ESP 복호화가 비동기(crypto_aead)이므로 NAPI 폴링과 상호작용. net.core.busy_poll으로 레이턴시 최적화 가능
  • PCPU xfrm 캐시 — 커널 4.14+에서 per-CPU xfrm 정책 캐시 도입. 멀티코어 환경에서 lock contention 감소
  • 모니터링/proc/net/xfrm_stat의 각 카운터를 Prometheus 등으로 수집하여 이상 징후(XfrmInError 급증 등) 조기 감지

IPSec과 QoS / DSCP 처리

터널 모드에서는 원본 IP 헤더가 ESP 안으로 들어가므로, 외부 IP 헤더의 TOS/DSCP 필드를 어떻게 설정할지가 QoS 정책에 직접 영향을 줍니다. 리눅스 xfrm은 세 가지 모드를 지원합니다.

터널 모드 DSCP / ECN 전파 흐름 송신 (Encapsulation) Inner IP Header DSCP=EF (0x2E) xfrm_output() DSCP 정책 copy / set / inherit 결정 Outer IP Header DSCP=EF (복사) / 0 (고정) ISP QoS DSCP 기반 분류 수신 (Decapsulation) Outer IP Header ECN=CE (혼잡 경험) xfrm_input() ECN 전파 RFC 6040: CE를 내부로 복사 Inner IP Header ECN=CE (전파됨) RFC 4301 §5.1.2: 기본 동작은 inner DSCP를 outer에 복사 (copy mode) RFC 6040: 수신 시 외부 ECN CE 비트를 반드시 내부 헤더로 전파해야 함 보안 관점: DSCP 복사가 내부 트래픽 유형을 외부에 노출할 수 있음 (traffic analysis)
DSCP 복사는 QoS에 유리하지만, 외부에서 내부 트래픽 유형을 추론하는 사이드 채널이 될 수 있습니다.
DSCP 모드동작설정 방법용도
Copy (기본값) 내부 IP의 DSCP를 외부 IP에 복사 ip xfrm state ... flag noecn 미설정 (기본) ISP QoS 정책이 DSCP를 존중하는 환경. VoIP/영상 트래픽 우선 처리
Set (고정값) 외부 IP DSCP를 특정 값으로 고정 (tc/iptables로) iptables -t mangle -A POSTROUTING -o eth0 -p esp -j DSCP --set-dscp-class CS1 트래픽 분석 방지. 모든 ESP 패킷이 동일한 DSCP를 갖게 하여 내부 트래픽 유형 은닉
Map 내부 DSCP를 외부 DSCP로 매핑 (1:1이 아닌 매핑) tc filter ... action skbedit priority N와 조합 내부 DSCP 6개를 외부 DSCP 2개로 축소하는 등의 정책
# DSCP 복사 확인 — 터널 모드 송신 패킷 캡처
tcpdump -i eth0 -v esp | grep -i tos
# TOS 0xb8 (DSCP EF) → 내부 DSCP가 외부에 복사됨

# 외부 DSCP를 고정값으로 변경 (트래픽 분석 방지)
iptables -t mangle -A POSTROUTING -o eth0 -p esp \
    -j DSCP --set-dscp 0

# ECN 전파 확인
sysctl net.ipv4.tunnel4.ecn    # 1이면 ECN 전파 활성

# tc로 ESP 트래픽 QoS 클래스 지정
tc qdisc add dev eth0 root handle 1: htb default 30
tc class add dev eth0 parent 1: classid 1:10 htb rate 500mbit
tc filter add dev eth0 parent 1: protocol ip u32 \
    match ip protocol 50 0xff flowid 1:10  # ESP=proto 50
ECN과 IPSec (RFC 6040): ECN(Explicit Congestion Notification) 비트는 DSCP와 달리 특별한 전파 규칙이 있습니다. 터널 encapsulation 시 외부 헤더에 ECT(0)/ECT(1)을 복사하고, decapsulation 시 외부 CE(Congestion Experienced) 비트를 내부 헤더로 반드시 전파해야 합니다. 이렇게 해야 터널 안쪽의 TCP가 혼잡 신호를 올바르게 수신하여 흐름 제어를 할 수 있습니다. Linux xfrm은 RFC 6040을 구현하며, flag noecn으로 ECN 전파를 비활성화할 수 있지만 권장하지 않습니다.

커널 빌드 옵션 (CONFIG_XFRM)

IPSec/xfrm 기능은 커널 빌드 시 여러 CONFIG 옵션으로 제어됩니다. 모듈로 빌드하면 필요할 때만 로드할 수 있지만, 고성능 환경에서는 built-in이 유리합니다.

CONFIG 옵션기본값설명의존성
CONFIG_XFRM y xfrm 프레임워크 코어. 이것이 없으면 IPSec 전체가 비활성화됩니다 NET
CONFIG_XFRM_USER m NETLINK_XFRM 유저스페이스 인터페이스. IKE 데몬 통신에 필수 XFRM
CONFIG_XFRM_INTERFACE m xfrm interface (if_id 기반 route-based VPN). 커널 4.19+ XFRM, NET_L3_MASTER_DEV
CONFIG_XFRM_SUB_POLICY n 서브 정책 지원. 일반 환경에서는 불필요하며 성능 영향 있음 XFRM
CONFIG_XFRM_MIGRATE n MOBIKE SA 마이그레이션 지원 XFRM
CONFIG_XFRM_STATISTICS y /proc/net/xfrm_stat 통계 카운터. 디버깅에 필수이므로 항상 활성화 권장 XFRM, PROC_FS
CONFIG_XFRM_ESPINTCP n TCP 캡슐화(ESP-in-TCP). 매우 제한적인 방화벽 환경에서 ESP/UDP 모두 차단 시 사용 XFRM, INET_ESPINTCP
CONFIG_INET_ESP m IPv4 ESP 프로토콜 처리 (esp4.c) XFRM, CRYPTO_AEAD
CONFIG_INET6_ESP m IPv6 ESP 프로토콜 처리 (esp6.c) XFRM, IPV6, CRYPTO_AEAD
CONFIG_INET_AH m IPv4 AH 프로토콜. 현대 환경에서는 거의 불필요 XFRM, CRYPTO_HASH
CONFIG_INET_IPCOMP m IPv4 IPCOMP 압축. strongSwan 기본 제안에 포함될 수 있음 XFRM, CRYPTO_DEFLATE
CONFIG_NET_KEY m PF_KEY 소켓 인터페이스 (레거시). Racoon 등 오래된 IKE에만 필요 XFRM
CONFIG_CRYPTO_GCM m AES-GCM AEAD. 현대 IPSec의 사실상 필수 알고리즘 CRYPTO_AEAD, CRYPTO_AES
CONFIG_CRYPTO_CHACHA20POLY1305 m ChaCha20-Poly1305 AEAD. AES-NI 없는 환경의 대안 CRYPTO_AEAD
# 현재 커널의 xfrm 관련 설정 확인
zgrep CONFIG_XFRM /proc/config.gz 2>/dev/null || \
    grep CONFIG_XFRM /boot/config-$(uname -r)

# 로드된 xfrm 모듈 확인
lsmod | grep -E 'xfrm|esp[46]|ah[46]|ipcomp|af_key'

# ESP 모듈 수동 로드
modprobe esp4
modprobe esp6

# Crypto 알고리즘 가용성 확인
cat /proc/crypto | grep -A4 'gcm(aes)'
# driver: generic vs aesni → 하드웨어 가속 여부 확인
최소 빌드 체크리스트: IPSec VPN을 운영하려면 최소한 CONFIG_XFRM, CONFIG_XFRM_USER, CONFIG_INET_ESP, CONFIG_CRYPTO_GCM, CONFIG_XFRM_STATISTICS가 필요합니다. Route-based VPN이라면 CONFIG_XFRM_INTERFACE를, MOBIKE가 필요하면 CONFIG_XFRM_MIGRATE를 추가하세요. 임베디드/컨테이너 커널에서는 CONFIG_INET_AHCONFIG_NET_KEY를 빼서 공격 표면을 줄일 수 있습니다.

주요 1차 자료