킬 스위치는 트래픽이 흐르기 전에 VPN 서버를 차단한다
킬 스위치가 어느 단계를 차단하는지: 엔드포인트 조회, 외부 경로, 반환 경로, 오래된 피어 예외를 추측이 아닌 카운터로 판별한다.
킬 스위치는 사람들이 예상하지 못한 방향에서 실패한다 — 터널이 내려가 있는 동안 트래픽을 차단하도록 만들어졌지만, 터널을 만드는 패킷 자체가 트래픽이다. 이것이 버그의 전부다. 터널로 나가지 않는 모든 것을 버리는 규칙 집합은 터널을 올릴 핸드셰이크까지 버리므로, 클라이언트는 루프에서 다시 시도하고 오류는 먼저 확인한 단계의 탓이 된다. 방화벽을 넓히기 전에 세 단계 중 어느 것이 거부되는지 알아내라 — 이름 조회인지, 피어로 가는 외부 패킷인지, 돌아오는 응답인지.
터널과 그 자체의 전송이 서로 의존하는 방식
VPN 클라이언트를 하나의 소켓 경로를 공유하는 두 개의 네트워크 상태로 읽어라. 터널이 생기기 전에 클라이언트는 물리 어댑터에서 소켓을 열고 암호화된 프레임을 피어의 주소와 포트로 보낸다. 그 교환이 성공한 뒤에야 터널 장치를 만들고 경로를 설치한다. 킬 스위치는 두 번째 상태에 관한 정책이지만, 둘 다에 적용될 만큼 넓게 작성되어 있다.
인터페이스 기반 규칙이 보통의 형태다 — 다음을 통해 나가는 것을 받아들이고,tun0또는wg0, 나머지 출력 경로를 버린다. 그 표현은 클라이언트 자신의 서버 연결을 예외로 둘 수 없으므로, 올바른 구현은 그 예외를 명시적으로 추가하며, 손으로 규칙을 쓸 때도 같은 일을 한다. Windows와 macOS의 애플리케이션별 규칙은 인터페이스 대신 프로세스를 일치시켜 이 문제의 일부를 피하지만, 프로세스는 여전히 터널이 생기기 전에 빠져나가야 한다.
세 단계, 일어나는 순서대로
첫 단계는 확인이다. 프로필이 문자 그대로의 주소 대신 호스트 이름을 저장하면, 무언가를 보내기 전에 클라이언트는 DNS 응답이 필요하다. DNS를 터널 확인자로 향하게 하는 프로필은 교착을 만든다 — 확인자는 조회가 가능하게 해야 할 터널을 통해서만 닿을 수 있다. 터널 밖에서 확인하거나 엔드포인트를 주소에 고정하면 이 모호함이 사라진다.
두 번째 단계는 외부 패킷이며, 이 순서대로 먼저 라우팅을, 그다음 방화벽을 통과해야 한다. wireguard-tools는 WireGuard를 위해 이 라우팅 절반을 담당한다 — 자체 패킷을 표시하고 방금 설치한 기본 경로를 건너뛰도록 정책 규칙을 추가하는데,wg-quick 매뉴얼 페이지가 그 규칙과 사용하는 테이블을 설명한다. 그 라우팅을 손으로 다시 만들거나 다른 도구가 규칙을 다시 쓰게 하면, 표시된 패킷이 만들어야 할 터널로 되돌아갈 수 있다.
세 번째 단계는 반환 경로다. 응답 없는 핸드셰이크는 외부로의 폐기 증거가 아니다 — 서버의 인바운드 규칙이나 매핑을 잊은 NAT 장치와도 똑같이 들어맞는다. 클라이언트를 건드리기 전에 이것들을 분리하라.
| What you see | Stage | Where to look |
|---|---|---|
| The client log repeats a resolve step and no packets appear on the wire | 1, name lookup | Resolver path while the tunnel is down |
| The send counter on the physical adapter stays flat | 2, routing | Peer route and policy rules |
| The send counter rises and a firewall drop counter rises | 2, firewall | Rule order, mark, and interface match |
| Packets leave and nothing comes back | 3, return path | Server input rules, NAT, listening port |
클라이언트는 감지한 증상을 기록하지, 실패한 단계를 기록하지 않는다 — 그래서 표가 로그보다 보통 빠르다.
카운터가 로그보다 빨리 결정한다
Linux에서는,nft list ruleset또는iptables -vnL OUTPUT가 규칙마다 패킷 카운터를 출력한다. 한 번 연결하고 실패하게 둔 다음 카운터를 다시 읽어라. 카운터가 움직인 규칙이 핸드셰이크가 부딪힌 규칙이다 — 어디에서도 아무것도 움직이지 않았다면 패킷은 방화벽에 닿지 않았다. 규칙 집합이 자기 핸드셰이크를 버리는 것을 본 적이 있는데, 그것을 말한 것은 카운터뿐이었다.
방화벽에 닿지 않은 것과 호스트를 떠나지 않은 것을 구분하려면 인터페이스 자체를 보라.ip -s link show dev wlan0는 보낸 패킷을 보고하고,tcpdump -ni wlan0 udp port 51820는 프레임을 보여준다. 클라이언트가 다시 시도하는 동안 평평하게 유지된다면 결함은 netfilter 위에 있다 — 피어 경로 누락, 아직 없는 장치를 가리키는 정책 규칙, 또는 클라이언트가 삼키는 소켓 오류다.
예외는 문서용 주소로 시험하라 — 예를 들어203.0.113.10, 실수로 다른 사람의 호스트를 조사하지 않도록.
오래된 엔드포인트 예외는 서버 장애처럼 읽힌다
가장 오해를 부르는 버전은 피어 주소가 바뀐 뒤에 나타난다 — 동적 주소 서비스가 옮겼거나, 호스트가 다시 만들어졌거나, 네트워크의 외부 주소가 다르다.예외는 여전히 옛 주소를 가리키고,새 주소로 가는 패킷이 폐기 규칙에 걸린다. 같은 구성이 어제는 동작했기에 사람들은 서버 조사부터 시작한다.
방화벽 규칙 안의 호스트 이름은 구해주지 않는다. nftables에서는 규칙 집합을 읽을 때 이름이 확인되므로, 그 순간 존재하던 주소를 고정하고 문자 그대로의 주소처럼 낡는다. 오래 가는 해법은 엔드포인트가 바뀔 때 예외를 갱신하고 좁게 유지하는 것이다 — 피어 주소 하나, 전송 포트 하나. 주소 변경을 견딜 만큼 넓은 범위는 무관한 트래픽도 내보낼 만큼 넓다.
킬 스위치가 서버 호스트에서 실행될 때
같은 의존이 반대편에도 있고, 거기서는 놓치기 쉽다. VPN 서버 역할을 하는 기계가 킬 스위치가 있는 데스크톱 클라이언트나 노트북에서 복사한 방화벽 프로필도 실행한다면, 그 폐기 규칙은 터널을 종료하는 서비스에 적용된다. 클라이언트 핸드셰이크에 대한 응답이 버려지므로, 서버는 건강해 보이고 터널 인터페이스는 쓰이지 않는 채로 모든 클라이언트가 연결에 실패한다.
그 호스트는 폐기 전에 세 가지 좁은 허용이 필요하다 — 물리 인터페이스의 수신 포트, 복호화된 트래픽을 위한 터널 인터페이스, 그리고 터널에서 클라이언트가 도달해야 할 곳으로의 전달이다. 설치된 집합은 다음으로 읽어라,nft list ruleset,iptables -S, 또는pfctl -sr무엇이든 바꾸기 전에, 그리고 저장된 규칙 집합은 재부팅을 넘어 살아남는다는 것을 기억하라 — 그곳의 실수는 그것을 만든 프로세스보다 오래 산다.
기계를 닫아 둔 채로 복구하는 순서
프로필에서 바깥으로 작업하라. 클라이언트가 쓰는 피어 주소와 포트를 읽고, 터널 없이 호스트 이름이 확인되는지 확인한 다음, 물리 어댑터에서만 그 주소와 포트에 대한 예외를 하나 추가하라. 다시 연결하고 보내기 카운터를 보라 — 핸드셰이크가 완료되면 두 번째 단계가 고쳐졌다는 신호다.
시험하는 동안 폐기 정책의 나머지는 그대로 두라. 무해한 확인은 무관한 문서용 주소로 연결을 시도해 여전히 시간 초과가 되는지 보는 것이다 — 킬 스위치가 꺼진 것이 아니라 동작한다는 뜻이다. 터널이 안정되면 예외를 좁히고, 편하다고 클라이언트 바이너리에 영구적인 전체 허용을 남기지 마라.
터널이 올라와도 애플리케이션이 계속 실패한다면 나머지 단계는 이것이 아니다 — 분할 DNS, MTU, 애플리케이션별 라우팅은 각각 연결된 터널과 깨진 트래픽을 만들고, 각각 첫 확인이 다르다.