터널로 사설 서브넷 하나만 보내고 나머지는 평소 경로로 보내는 일은 작은 요구지만 잘못 만들 방법이 아주 많습니다. 결과를 좌우하는 것은 split tunneling 이라는 이름의 확인란이 아니라 결정이 어디에서 강제되는가입니다. 클라이언트 자신의 경로 테이블인지, 서버가 밀어 넣는 경로인지, 양 끝이 협상하는 정책인지, 계층을 잘못 고르면 원하지 않던 전체 터널이 되거나, 반대편이 받아들이지 않는 터널로 향하는 경로가 됩니다. 저는 어떤 설정을 열기 전에 세 가지 조건으로 판단한 뒤 아래 방법 중 하나를 고릅니다.

설계를 결정하는 세 가지 조건

첫째, 네트워크 소유자에게서 정확한 프리픽스와 마스크를 받아야 하는데, 10.x 네트워크는 프리픽스가 아니며 10.20.0.0/16 이 프리픽스입니다. 집의 LAN, Docker 브리지, 또는 직접 돌리는 다른 VPN과 겹치지 않는지 확인하십시오. 겹치면 경로가 모호해지고 커널은 의도하지 않은 항목을 고릅니다.

둘째, 돌아오는 트래픽에 누가 응답하는가입니다. 패킷을 터널로 보내는 것만으로는 VPN 서버까지만 도달합니다. 서브넷이 클라이언트 풀로 돌아가는 경로가 없는 다른 라우터 뒤에 있다면, 서버가 트래픽을 마스커레이드해야 하거나 그 라우터가 풀을 알아야 합니다. 클라이언트 쪽 경로로는 빠진 반환 경로를 고칠 수 없습니다.

셋째, 강제 지점입니다. 어떤 클라이언트는 경로를 설치하고 운영 체제가 나머지를 하며, 다른 클라이언트는 정책을 협상합니다. 게이트웨이가 받아들이지 않는 정책은 터널 자체를 만들지 않고, 패킷은 뚜렷한 오류 없이 버려집니다. 무언가를 쓰기 전에 둘 중 어느 쪽인지 확인하십시오.

WireGuard: AllowedIPs 가 경로 테이블입니다

클라이언트에서 AllowedIPs 은 터널로 들어올 수 있는 목적지를 나열하고, wg-quick 은 각 항목을 평범한 경로로 바꿉니다. 따라서 이 작업의 클라이언트 쪽 절반은 한 줄입니다.

[Peer]
PublicKey = ...
Endpoint = vpn.example.net:51820
AllowedIPs = 10.20.0.0/16

이제 같은 클라이언트에 대한 서버 쪽 항목과 비교해 보십시오. 그것은 서버가 클라이언트로 보낼 내용을 설명하며, 보통은 사설 서브넷이 아니라 클라이언트의 터널 주소입니다.

[Peer]
PublicKey = ...
AllowedIPs = 10.99.0.2/32

이 둘을 바꿔 넣는 것이 제가 가장 자주 보는 실수입니다. 10.20.0.0/16 을 서버의 peer 절 아래에 두면 서브넷 전체가 그 클라이언트 뒤에 있다고 선언하는 셈이며, 이는 사이트 간 주장이지 허가가 아닙니다. 어떤 peer 의 AllowedIPs 와도 맞지 않는 패킷은 조용히 버려지므로, 잘못된 항목은 방화벽 문제와 똑같이 보입니다.

경로를 직접 관리한다면 [Interface] 아래에 Table = off 을 설정하고 원하는 경로를 추가하십시오. 그러면 자동으로 추가되는 것이 없으므로 경로는 인터페이스를 올리는 쪽에 속해야 합니다.

OpenVPN: 경로를 밀어 넣고 실제로 도착하는지 확인하십시오

일반 서버에서는 변경이 서버 설정에서 클라이언트 풀 옆의 지시문 하나입니다.

push "route 10.20.0.0 255.255.0.0"

클라이언트는 연결할 때 이것을 추가합니다. 실패 모드는 같은 프로필이 redirect-gateway 도 밀어 넣거나, 누군가 이미 route-nopull 을 추가했을 때 나타나는데, 앞의 경우는 세션을 전체 터널로 만들고, 뒤의 경우는 자신의 경로를 포함해 밀어 넣은 모든 경로를 버립니다.

전체 터널 서버를 상대로 좁은 동작이 필요한 클라이언트는 원하지 않는 부분만 무시해야 합니다. OpenVPN 2.4 이후에는 바로 그 목적을 위한 pull-filter 이 있습니다.

pull-filter ignore "redirect-gateway"
route 10.20.0.0 255.255.0.0

주소 체계를 분리해 두십시오, 밀어 넣은 IPv4 경로는 IPv6 로 해석되는 내부 서비스에 아무 효과가 없고, 그 서비스만 터널에서 실패하며 나머지는 멀쩡해 보입니다.

사설 서브넷이 VPN 서버가 아니라 다른 클라이언트 뒤에 있을 때는 서버에 알려야 합니다. client-config-dir 을 통해 iroute 10.20.0.0 255.255.0.0 을 담은 클라이언트별 파일을 추가하고, 다른 클라이언트가 경로를 배우도록 대응하는 route 도 넣으십시오. iroute 이 없으면 서버가 그 프리픽스를 바로 그 터널로 보낼 이유가 없습니다.

IPsec: 트래픽 선택자가 일치해야 합니다

IPsec 에는 밀어 넣을 경로가 없습니다. 양 끝이 터널 안에 어떤 주소가 있는지 협상하며, 합의된 선택자 밖의 트래픽은 암호화되지도 그 터널로 전송되지도 않습니다. strongSwan 클라이언트에서는 선택자를 명시합니다.

leftsubnet=192.168.1.0/24
rightsubnet=10.20.0.0/16

게이트웨이가 양쪽 모두 0.0.0.0/0 으로 설정되어 있으면 IKEv2 축소가 보통 자식 SA 를 두 제안의 교집합으로 줄이며, 이것이 원하는 결과입니다. 반대로 설정된 선택자와 맞지 않는 제안을 거부하는 게이트웨이도 있고, 그러면 로그에는 라우팅 불만이 아니라 실패한 자식 SA 가 나타납니다. 그럴 때 변경은 게이트웨이 쪽 일이며, 클라이언트 경로로는 반대편이 받아들이지 않을 선택자의 터널을 만들 수 없습니다.

Windows 에서 기본 클라이언트는 같은 결정을 두 설정 뒤에 감춥니다. 터널 분할이 켜져 있어야 하고, 프리픽스가 프로필에 등록되어야 합니다.

Set-VpnConnection -Name Office -SplitTunneling $true
Add-VpnConnectionRoute -ConnectionName Office -DestinationPrefix 10.20.0.0/16

프리픽스를 등록하는 것이 재연결 후에도 남게 하는 일이며, 손으로 추가한 경로는 그렇지 않습니다.

엔드포인트와 DNS 는 결정 밖에 두십시오

VPN 엔드포인트는 터널 없이도 닿을 수 있어야 하며, 그렇지 않으면 터널이 스스로 무너집니다. 10.20.0.0/16 만 라우팅할 때는 엔드포인트가 공인 주소이므로 자동입니다. 전체 터널 프로필에서는 그렇지 않으며, 터널이 올라오기 전에 클라이언트에 엔드포인트로 향하는 명시적 호스트 경로가 필요합니다.

DNS 는 경로 밀어 넣기가 대신 결정해 주지 않는 두 번째 판단입니다. 내부 이름은 보통 사설 네트워크에서만 닿을 수 있는 리졸버로 해석됩니다. 서버가 DNS 서버를 밀어 넣으면 모든 이름이 그리로 가고, 넣지 않으면 내부 이름이 실패하거나 공개적으로 의도하지 않은 무언가로 해석됩니다. 분할 DNS 는 내부 리졸버에 검색 도메인 하나를 두지만 클라이언트 지원에 달려 있으므로, 라우팅의 부수 효과가 아니라 별도 작업으로 다루십시오.

확인하고, 재부팅한 뒤 다시 확인하십시오

세 가지 점검이 라우팅 문제를 덮습니다. 커널에 원격 주소가 어디로 갈지 물어보고, 서브넷 안쪽에서 무언가를 가져오고, 평범한 공개 요청이 여전히 평소 연결로 나가는지 확인하십시오.

ip route get 10.20.5.7
curl -sS http://10.20.5.7/health
curl -sS https://ifconfig.me

첫 줄은 인터페이스 이름을 보여 줍니다. 물리 인터페이스가 나오면 경로가 들어가지 않은 것입니다. Windows 에서는 Find-NetRoute -RemoteIPAddress 10.20.5.7 이 같은 질문에 답하며, 마지막 점검은 VPN 서버가 아니라 평소 주소를 보여 주어야 합니다.

그다음 재부팅하십시오. 손으로 추가한 경로는 사라지고, 프로필이나 밀어 넣은 설정, 또는 AllowedIPs 에서 오는 경로는 돌아옵니다. 재시작 전에는 좁은 접근이 되고 후에는 실패한다면 원인은 경로가 아니라 터널이 시작되는 방식에 있습니다.

끝났다고 하기 전에 프리픽스가 네트워크 소유자에게서 왔고 로컬의 어떤 것과도 겹치지 않는지, 반환 경로가 있거나 마스커레이드 규칙이 대신하는지, 설정한 방식이 자기 플랫폼에서 라우팅을 강제하는 방식인지, 깨끗하게 재부팅한 뒤에도 같은 점검이 통과하는지 네 가지를 확인하십시오. 반환 경로가 계속 실패하는 부분이라면 strongSwan 의 포워딩 관련 문서 도 읽을 만합니다.

관련 글