- 정전 뒤 ISP 쪽 IPv4 연결만 끊기고 IPv6는 살아 있는 상황에서, IPv4-only 사이트 접근을 되살리기 위해 IPv4/IPv6를 모두 가진 Hetzner VPS와 WireGuard 터널을 사용함
- 장애는 CG-NAT 계층에서 IPv4 패킷이 제대로 변환되지 않아 드롭된 것으로 보였고, IPv6를 지원하는 Google·Meta 같은 서비스는 계속 접속 가능했음
- VPS에 WireGuard 서버를 두고 클라이언트가 VPS의 IPv6 주소를 엔드포인트로 쓰면, IPv4 트래픽을 VPS 경유로 보내 일반 웹 브라우징을 복구할 수 있음
- 업무용 VPN과 Docker는 별도 처리가 필요해, vopono 기반 네트워크 네임스페이스와
unshare·/sys bind mount 우회로 같은 네임스페이스 안에서 실행함
- 일부 사이트만 로드되지 않는 증상은 WireGuard MTU가 너무 큰 탓이었고, IPv6 최소 MTU인 1280으로 낮추자 즉시 해결됨
정전 뒤 IPv4만 끊긴 장애
- 정전 뒤 차단기를 복구했지만 GitHub와 여러 웹사이트에 접속할 수 없었고, Google과 Meta는 정상 동작했음
- 로컬 머신과 라우터 진단 페이지에서
ping -6, traceroute를 확인한 결과 문제는 IPv4 서버 접속에만 있었음
- ISP는 기사 방문이 필요할 수 있고 주말 이후 며칠이 걸릴 수 있다고 했으며, 업무 접근과 논문 작업 때문에 장애를 기다리기 어려웠음
- 기존 Hetzner VPS에는 정적 IPv4와 IPv6 주소가 있었고, Hetzner 웹사이트가 IPv6를 지원해 콘솔에 접근해 설정을 진행할 수 있었음
NAT와 CG-NAT가 만든 IPv4 의존성
- IPv4 주소는 32비트이며 예약 블록을 제외하면 공개 IPv4 주소가 약 37억 개뿐이라 모든 인터넷 연결 기기에 직접 주소를 줄 수 없음
- NAT는 여러 기기가 하나의 공개 IP를 공유하도록 만듦
- 홈 라우터는 내부 기기의
192.168.1.xxx 같은 로컬 IP를 자신의 공개 IPv4로 바꿈
- Linux의 conntrack은 원래 출발지 IP와 포트, 변환된 포트를 매핑으로 저장함
- 응답 패킷이 해당 포트로 오면 conntrack이 목적지를 원래 내부 IP와 포트로 되돌림
- Linux에서는
conntrack-tools의 conntrack -L로 저장된 매핑을 볼 수 있음
- NAT는 암묵적 방화벽처럼 동작해, 라우터 뒤 로컬 기기의 서비스는 명시적 포트 포워딩 없이는 외부에서 접근하기 어려움
- ISP는 IPv4 부족 때문에 내부에서도 NAT를 한 번 더 적용할 수 있고, 이를 Carrier Grade NAT(CG-NAT) 라고 부름
- 홈 라우터가 여러 로컬 기기를 NAT하듯, ISP 라우터는 여러 홈 라우터를 NAT함
- ISP가 가진 IPv4 주소 수와 할당 정책에 따라 지역 단위 등 여러 계층으로 반복될 수 있음
- 이번 장애는 CG-NAT 계층 어딘가에서 IPv4 패킷이 올바르게 NAT되지 않아 드롭되면서 IPv4 트래픽이 완전히 끊긴 상황으로 보였음
- NAT traversal 우회 방법은 Tailscale의 How NAT Traversal Works를 참고할 만함
IPv6가 계속 동작한 이유
- IPv6 주소는 128비트이며 예약 블록을 감안해도 약 3.4E38개 주소를 제공함
- 가정용 라우터가
/64 서브넷을 받는 경우가 흔하고, 이는 1.84E19개 주소를 의미함
- IPv6에서는 홈 라우터에서 NAT를 쓰지 않아도 각 기기가 인터넷에서 직접 주소를 가질 수 있음
- 포트 포워딩 문제는 줄어듦
- 대신 라우터나 각 기기는 임의의 외부 신규 연결을 막는 적절한 방화벽 규칙이 필요함
- IPv6에는 CG-NAT가 적용되지 않았기 때문에 이번 장애의 영향을 받지 않았음
- GitHub처럼 여전히 IPv6로 접근할 수 없는 웹서버가 있어, IPv6 연결만으로는 전체 인터넷을 직접 사용할 수 없었음
WireGuard로 IPv6 위에 IPv4 터널 만들기
- 해결책은 VPS에 WireGuard를 설치하고, 클라이언트가 VPS의 IPv6 주소를 엔드포인트로 사용해 터널을 구성하는 방식이었음
- 터널이 성립되면 IPv4 트래픽은 VPS를 경유해 정상 동작함
- VPS 경유로 지연은 늘어남
- 동작 방식은 직접 구성한 Dual-Stack Lite에 가까움
- 서버는 기존에 vps2arch로 Arch Linux를 설치해 둔 Hetzner VPS였고, Hetzner의 최신 Debian 이미지를 기반으로 사용했음
- WireGuard 설정은 ArchWiki의 WireGuard 특정 VPN 서버 예시를 바탕으로 IPv6 트래픽을 추가한 형태였음
- 서버 설정은 다음을 포함함
Address = 10.200.200.1/24, fd42:42:42::1/64, 2001:db8:abcd:1234::1/128
- IPv4는
iptables의 MASQUERADE로 포워딩
- IPv6 ULA는
ip6tables의 SNAT --to-source로 NAT
- IPv4/IPv6 forwarding 활성화
- peer는 직접 IPv6 Global Unicast Address를 쓰는
foo와 NAT된 IPv6 ULA를 쓰는 bar 예시로 나뉨
wg-quick은 .ini 스타일 설정과 달리 PostUp과 PostDown을 여러 번 지정할 수 있고, 각 명령을 순서대로 실행함
IPv6 NAT, SNAT, 클라이언트 설정
- IPv6는 꼭 NAT할 필요가 없으며, Hetzner처럼 VPS에
/64 IPv6 블록이 있으면 peer에 직접 Global Unicast Address(GUA) 를 줄 수 있음
- 직접 주소 방식을 쓰려면 peer와 인터페이스의 Unique Local Address(ULA)를 공개 IPv6 주소로 바꾸고
ip6tables MASQUERADE 규칙을 제거함
- 각 peer는 할당된 IPv6 주소로 인터넷에서 직접 주소 지정 가능해짐
- 여러 기기에서 자체 서비스를 포워딩하려면 이 방식이 적합함
- VPS 방화벽이 들어오는 트래픽을 올바르게 처리해야 함
- VPS의 IP 주소가 정적이고 바뀌지 않는다고 확신하면
MASQUERADE 대신 SNAT을 사용할 수 있음
MASQUERADE는 런타임에 인터페이스 IP를 조회함
SNAT은 주소를 직접 지정하므로 약간 더 효율적임
- 클라이언트 설정에서는 서버의 IPv6 주소를
Endpoint = [2001:db8:abcd:1234::1]:51820처럼 대괄호로 감싸야 함
AllowedIPs = 0.0.0.0/0, ::/0로 전체 IPv4/IPv6 트래픽을 터널로 보냄
- 양쪽에서 실행한 뒤 일반 브라우징은 정상화됐고, 터널 로컬 IPv4와 IPv6 주소로 서버에 직접 SSH 접속도 가능했음
- Linux에서 아내의 머신에도 WireGuard 클라이언트를 간단히 설치할 수 있었음
업무 VPN은 네트워크 네임스페이스로 분리
- WireGuard 연결 위에서 업무용 VPN을 바로 연결하면 충돌이 발생해 사용할 수 없었음
- vopono를 사용해 업무 VPN과 필요한 애플리케이션을 네트워크 네임스페이스 안에서 실행함
- 핵심은 MASQUERADE 규칙이 실제 네트워크 인터페이스가 아니라 실행 중인 WireGuard 인터페이스(
foo 또는 bar)로 트래픽을 전달하도록 만드는 것임
- 네임스페이스 내부 트래픽은 호스트의 WireGuard
nftables 규칙을 직접 의식하지 않지만, 실제로는 WireGuard 터널을 통해 라우팅됨
wg-quick은 사용 가능할 때 iptables보다 nftables를 선호하지만, Docker의 표준 iptables 규칙과 충돌을 피함
- vopono 실행 예시는 다음과 같음
$ vopono -v exec --create-netns-only --provider None --protocol None -i bar bash
$ sudo ip netns exec vo_none_none bash
$ (inside netns) ./vpn.sh
/etc/netns/vo_none_none/는 ip netns exec에 의해 /etc로 마운트되므로, 해당 네임스페이스 전용 resolv.conf를 둘 수 있음
- IPv4 DNS 해석을 우선하고 싶다면 같은 방식으로
gai.conf도 조정 가능함
- 업무 VPN 연결 후 내부 DNS 서버를
/etc/netns/vo_none_none/resolv.conf에 넣으면 이후 해당 네임스페이스에서 실행한 애플리케이션이 정상 동작함
- 일반 사용자 권한으로 Chrome 같은 애플리케이션을 네임스페이스 안에서 실행할 수 있음
$ vopono -v exec -i bar --provider None --protocol None google-chrome-stable
Docker를 같은 네임스페이스에서 실행하기
- Docker는 단순히 다른 애플리케이션처럼 네트워크 네임스페이스에서 실행해도 동작하지 않음
- systemd로 활성화된 Docker socket이 네임스페이스 밖에서 생성됐기 때문임
- 내부 연결성이 확보되지 않음
- 외부 Docker를 멈추고 네임스페이스 안에서
dockerd와 socket을 새로 만들려고 해도 바로 해결되지 않음
ip netns exec가 mount namespace를 만들고 /sys를 다시 마운트함
- 이 때문에 호스트의
/sys/fs/cgroup이 보이지 않음
- 이 경우 다음 오류가 발생할 수 있음
Error: OCI runtime error: runc: runc create failed: no cgroup mount found in mountinfo
- 우회는
unshare로 /sys를 bind mount로 만들고, ip netns exec가 만든 내부 /sys 마운트를 해제하는 방식임
- 예시 명령은 다음과 같음
$ sudo systemctl stop docker && sudo systemctl stop docker.socket
$ sudo -E unshare -m sh -c 'mount --bind /sys /sys; exec ip netns exec vo_none_none sudo --user youruser --preserve-env bash'
$ sudo umount /sys
$ sudo dockerd --host=unix:///var/run/docker-netns.sock --data-root=/var/lib/docker-netns
$ DOCKER_OPTS="--dns=YOURDNSHERE" DOCKER_HOST=unix:///var/run/docker-netns.sock sudo --user youruser --preserve-env docker ...
dockerd와 Docker 명령을 같은 세션 안에서 실행하면 동일한 네트워크 네임스페이스와 mount namespace를 공유함
- Docker DNS 설정은
/etc/netns/vo_none_none/docker/daemon.json에도 둘 수 있음
- 이 방식은 보조 컨테이너와 Docker 네트워크로 연결하는 컨테이너까지 필요한 작업에는 충분했지만, 브리지 등이 필요한 더 복잡한 Docker 구성에서는 그대로 동작하지 않을 수 있음
WireGuard MTU 문제 디버깅
- 재부팅 뒤 WireGuard 연결은 살아 있는 것처럼 보였지만 일부 페이지만 로드되고 GitHub 같은 사이트는 열리지 않았음
ping, ping -6, wg show는 정상이라 원인을 찾기 어려웠음
- 서로 다른 크기의 ping으로 확인한 결과 큰 패킷이 실패했음
$ ping6 -s 1400 fd42:42:42::1
$ ping6 -s 1200 fd42:42:42::1
$ ping6 -s 800 fd42:42:42::1
1400 크기는 실패하고 1200, 800은 성공해 MTU 설정이 원인임을 확인함
- 로컬 WireGuard 인터페이스의 MTU를 낮추면 커널 IP 스택이 이보다 큰 패킷을 만들지 않음
- WireGuard의 캡슐화 오버헤드가 추가된 최종 UDP 패킷도 경로상의 작은 MTU 링크에서 드롭되지 않게 됨
- 경로상의 각 라우터는 자체 MTU를 가지며, 가장 작은 MTU보다 큰 패킷은 드롭될 수 있음
- 터널 트래픽은 WireGuard 캡슐화로 약 32바이트 오버헤드가 추가되어 MTU 문제가 더 커질 수 있음
- Path MTU Discovery 메시지는 중간 라우터에서 ICMP로 전달될 수 있지만, 방화벽이 ICMP를 드롭하는 경우가 많아 자동 조정이 되지 않을 수 있음
- IPv6 명세의 최소 MTU는 1280이므로, IPv6 위 WireGuard 터널에서는 이 값이 항상 동작해야 함
복구 뒤 남은 운영 선택지
- 구성 결과는 다음을 포함함
- IPv4와 IPv6를 모두 가진 VPS에 WireGuard VPN 서버 구성
- 직접 IPv6와 NAT된 IPv6 트래픽 모두 지원
- 네트워크 네임스페이스에서 업무 VPN 실행
unshare 우회로 Docker를 같은 네트워크 네임스페이스 안에서 실행
- WireGuard MTU 문제 디버깅
- 원격 근무에서는 인터넷 연결 문제가 항상 위험 요소이며, 이번 경우 Linux 도구들로 ISP 설정 복구를 기다리지 않고 우회할 수 있었음
- Hetzner VPS는 WireGuard 터널과 합법적인 일반 사용을 지원하며, 포트 스캐닝·트래픽 스푸핑·암호화폐 채굴은 허용되지 않음
- AirVPN, ProtonVPN, AzireVPN처럼 포트 포워딩을 지원하는 VPN도 홈서버 포트를 ISP에 의존하지 않고 포워딩하는 대안이 될 수 있음
- OpenWRT 라우터를 쓰면 라우터 쪽 디버깅을 더 많이 할 수 있고, 이런 우회 구성을 각 기기에 따로 설정하지 않고 라우터에서 직접 WireGuard로 처리할 수 있음