사이트 간 터널에 IPv6 프리픽스를 추가해도 패킷은 그림이 암시하는 경로로 가지 않는다. 로컬 게이트웨이가 IPv6 패킷 전체를 가져와 암호화하고, 그 앞에 외부 IP 헤더를 붙인다. 주소 패밀리는 내부 내용이 아니라 끝점 주소가 결정한다. 원격 게이트웨이는 그 외부 헤더를 벗기고 원래 패킷을 자기 LAN으로 전달한다. 거기서 다시 평범한 IPv6 패킷이 된다. 외부 터널과 내부 트래픽은 키 하나, 끝점 쌍 하나만 공유하는 별개의 대화다.

두 헤더, 두 주소 패밀리

IPsec 터널 모드에서 내부 IPv6 패킷은 ESP 헤더 뒤의 페이로드가 된다. 그 ESP 패킷을 나르는 외부 헤더는 IPv4 또는 IPv6일 수 있고, local_addrs 과 remote_addrs 을 따른다. WireGuard도 같은 일을 더 적은 의식으로 한다. 내부 패킷은 피어의 끝점으로 가는 UDP 데이터그램의 페이로드다. 둘 다 내부 헤더를 건드리지 않으므로 원격 측은 캡슐 해제 후에도 그것을 라우팅할 수 있다.

사이트 사이 경로가 보는 것은 UDP 4500(IPsec이 NAT 통과를 쓸 때) 또는 기본 WireGuard 리스너의 UDP 51820뿐이다. 경로 위의 어떤 라우터든 IPv4 전용일 수 있고 IPv6 트래픽은 그래도 지나간다, 그 경로에서 IPv6 패킷으로 나타나는 일이 없기 때문이다. 이것이 깔끔한 첫 구분을 준다. tcpdump -ni eth0 udp port 51820 는 암호화된 경로를, tcpdump -ni wg0 ip6 는 내부 트래픽을 보여준다. 바깥 캡처는 바쁘고 안쪽은 조용하다면 커널이 IPv6를 터널에 넣지 않는 것이다. 그것은 라우팅이나 정책 문제이지 링크 상태가 아니다.

게이트웨이는 전달할 때 내부 Hop Limit도 줄이므로, 끝점은 IPv6 경로에서 라우터로 계산된다. 외부 홉 수와 내부 Hop Limit은 독립적으로 움직이고, 원격 게이트웨이에서 죽는 패킷은 포워딩에서 죽은 것이지 암호화에서 죽은 것이 아니다.

셀렉터와 AllowedIPs가 곧 허용 목록이다

IKEv2는 child SA가 어떤 내부 프리픽스를 실을 수 있는지 협상한다. strongSwan 5.9 이후에서 swanctl.conf 에 적으면 이렇게 된다.

children {
  net6 {
    local_ts  = 2001:db8:aa::/48
    remote_ts = 2001:db8:bb::/48
    mode = tunnel
    esp_proposals = aes256gcm16-prfsha384-ecp384
    start_action = trap
  }
}

local_ts 과 remote_ts 을 생략하면 strongSwan이 선택한 인터페이스의 서브넷에서 그것들을 유도한다. 듀얼 스택 호스트에서는 이 지름길이 보통 IPv4만 있는 쌍을 준다. SA는 올라오지만 IPv6는 ESP 코드에 닿기 전에 거부된다. 두 프리픽스를 모두 적으면 SA가 그것들을 실을 자격을 갖추고, start_action = trap 이 첫 일치 패킷에서 SA를 올린다.

WireGuard의 목록 하나가 두 가지 일을 한다. 피어 블록에서 AllowedIPs = 2001:db8:bb::/48, fd00:bb::/48 은 보낼 때 설치되는 라우트이자 받을 때 적용되는 필터다. 내부 출발지가 그 피어 목록 밖에 있는 복호화된 패킷은 조용히 버려진다. wg-quick 은 항목마다 라우트를 추가하므로, 아래 피어 블록에는 별도 ip -6 route 명령이 필요 없다.

[Interface]
Address = 2001:db8:aa:ff::1/64, fd00:aa:ff::1/64
ListenPort = 51820

[Peer]
PublicKey = <site-b-public-key>
Endpoint = 203.0.113.20:51820
AllowedIPs = 2001:db8:bb::/48, fd00:bb::/48

두 목록은 거울처럼 같아야 하며, 사이트 B는 사이트 A의 프리픽스를 원격 셀렉터로 받아들이고 되돌려 라우팅하고, 터널은 두 방향이 일치할 때만 동작한다. 비대칭 목록은 가장 많은 시간을 잡아먹는 증상을 만든다. 정상적인 핸드셰이크, 한쪽에서만 움직이는 카운터, 그리고 A에서 B로 가는 ping은 성공하는데 B에서 A로는 아무것도 도달하지 않는 상태.

포워딩, 라우트, 반환 프리픽스

리눅스는 말해 주기 전까지 IPv6를 포워딩하지 않으며, 설정은 인터페이스가 아니라 네임스페이스 단위다.

sysctl -w net.ipv6.conf.all.forwarding=1
ip -6 route add 2001:db8:bb::/48 dev wg0
ip -6 route get 2001:db8:bb::10 from 2001:db8:aa::10

두 번째 줄이 바로 라우트 기반 설계에서 사람들이 잊는 부분이다. 정책 기반 IPsec은 프리픽스에 맞는 XFRM 정책을 설치하지만, vti6 이나 WireGuard를 쓰는 라우트 기반 구성에는 그 장치를 가리키는 실제 IPv6 라우트가 필요하다. sysctl을 /etc/sysctl.d/ 아래에 영구 저장하라. 그렇지 않으면 재부팅이 포워딩을 조용히 꺼 버린다.

포워딩에는 알아 둘 부작용이 있다. 커널은 그 뒤로 net.ipv6.conf.<wan>.accept_ra 이 2 이 아니면 라우터 광고를 무시하므로, WAN 주소를 SLAAC로 받는 게이트웨이는 포워딩을 켜는 순간 기본 라우트를 잃을 수 있다. 같은 파일에서 그 인터페이스에 accept_ra = 2 을 설정하면 라우트가 살아남는다. 사이트 B의 코어도 터널 게이트웨이에서 2001:db8:aa::/48 을 배워야 한다(정적 라우트나 터널 위 라우팅 프로토콜로). 배우지 않으면 응답은 B 자신의 네트워크 안에서 죽는다.

끊긴 지점을 찾는 네 단계 점검

이 순서로 진행하고 처음 실패한 단계에서 멈춰라. 이후 단계는 모두 앞 단계에 의존한다.

  1. 셀렉터: swanctl --list-sas 은 child SA의 IPv6 프리픽스 쌍을 보여 줘야 하고, 또는 wg show wg0 allowed-ips 은 원격 프리픽스와 최신 latest-handshakes 을 보여 줘야 한다.
  2. 커널 라우트: ip -6 route get 2001:db8:bb::10 은 WAN 인터페이스가 아니라 터널 장치로 응답해야 한다.
  3. 내부 캡처: 실제 LAN 호스트 사이에서 ping하면서 tcpdump -ni wg0 ip6 를 실행한다. 나가기만 하고 돌아오지 않는 패킷은 B의 라우트나 방화벽을 가리킨다. 패킷이 전혀 없으면 1단계나 2단계로 돌아간다.
  4. 목적지: B 호스트에서 ss -6 -ltn 이 서비스가 IPv6에서 수신 중임을 확인하고, 로컬 방화벽 카운터가 패킷이 수락되었는지 버려졌는지 보여 준다.

MTU도 같은 회차에 본다. IPv6 라우터는 단편화하지 않으므로 너무 큰 패킷은 버려지고 Packet Too Big이 송신자에게 돌아온다. 핸드셰이크와 작은 ping은 되는데 파일 전송이 멈춘다면 통과할 때까지 페이로드를 줄여라. ping -6 -s 1400 -c 3 2001:db8:bb::10. WireGuard 인터페이스의 기본 MTU는 1420바이트이고 IPv4 위 IPsec은 더 깎으므로, 내부 MTU를 설정하는 편이 ICMPv6를 버리는 방화벽을 지나는 경로 MTU 탐색을 기대하는 것보다 낫다.

방화벽 문제처럼 보이는 고장 유형

가장 흔한 것은 방화벽이 아예 아니다. 서비스가 :: 이 아니라 0.0.0.0 에 바인딩되고, AAAA 레코드는 존재하며, 클라이언트는 IPv6로 빠른 거절을 받는데, 듀얼 스택 브라우저는 IPv4로 되돌아가 그것을 감춘다. 일부 앱만 실패한다면 규칙 집합을 건드리기 전에 수신 주소 패밀리를 확인하라.

두 번째는 ICMPv6 필터링이다. Neighbor Discovery와 Packet Too Big은 정상 운영의 일부이지 진단이 아니다. 모든 ICMPv6를 버리는 정책은 연결되고 인증된 뒤 작은 패킷보다 큰 모든 것에서 멈추는 터널을 만든다. 경로가 의존하는 유형을 허용하고, 버려짐이 보이도록 카운터를 켜 두어라.

세 번째는 터널에는 추가되었지만 그것을 소유한 사이트의 주소 계획에는 결코 추가되지 않은 프리픽스다. 그래서 어떤 라우터도 그것을 광고하지 않고 어떤 호스트도 그것으로 응답할 수 없다. 프리픽스 하나와 호스트 쌍 하나로 시작해 네 단계로 양방향을 증명한 뒤 나머지를 더하면 되고, 깨진 프리픽스는 재설계가 아니라 설정 한 줄로 남는다. 열어 둘 가치가 있는 유일한 공식 참고 문헌은 RFC 8200 이며, 터널이 나르는 헤더를 정의한다.

관련 글