터널을 통한 기본 경로를 추가하면 터널 자체의 패킷도 그 경로를 따릅니다. 그러면 라우팅 테이블에는 스스로를 무력화하는 규칙이 들어갑니다. 서버로 가는 암호화된 연결이 자신이 맺으려는 연결 안쪽으로 지나가려고 합니다.

두 가지 메커니즘이 그 루프를 막습니다. 하나는 라우팅 테이블에 보이고, 다른 하나는 애플리케이션 내부에 있습니다.

내부 트래픽과 외부 트래픽은 다른 결정입니다

애플리케이션 패킷이 가상 인터페이스로 들어가 캡슐화됩니다. 결과는 서버의 공용 주소로 향하는 새 패킷입니다. 그 어떤 부분도 터널로 들어가서는 안 되므로 그 패킷은 물리 네트워크상의 경로가 필요합니다.

둘 다 라우팅 결정이며 터널에 속하는 것은 첫 번째뿐입니다. 클라이언트가 서버까지 포함하는 기본 경로를 설치하면 외부 패킷도 함께 잡힙니다.

증상은 즉각적이고 완전합니다. 핸드셰이크가 끝난 뒤 아무것도 흐르지 않거나, 경로가 설치되는 순간 연결이 끊어집니다. 어느 경우든 keepalive는 더 이상 나갈 수 없습니다.

첫 번째 옵션: 엔드포인트를 위한 호스트 경로

직접 방식은 원래 게이트웨이를 통해 서버 주소에 대한 경로를 추가합니다. 한 줄이면 됩니다. 테이블을 읽는 누구나 볼 수 있습니다. 변경 기록에서 설명하기도 쉽습니다.

그 약점은 기반 경로가 바뀔 때 드러납니다. 저장된 경로는 예전 네트워크의 게이트웨이와 인터페이스를 지정합니다. Wi-Fi를 전환한 뒤에는 그 게이트웨이가 없을 수 있고, 경로는 남아서 올바른 것처럼 보이지만 아무 일도 하지 않습니다.

# before the tunnel: which path reaches the endpoint?
ip route get 198.51.100.7

# pin it through the current gateway
ip route add 198.51.100.7/32 via 192.168.1.1 dev wlan0

# after the tunnel is up, confirm the pin survived
ip route get 198.51.100.7

호스트 이름은 이것을 더 어렵게 만듭니다. 엔드포인트가 여러 주소로 확인되는 이름이라면, 예외는 실제로 사용 중인 주소를 포함해야 합니다. 나중 조회는 경로가 한 번도 포함하지 않았던 것을 반환할 수 있습니다.

두 번째 옵션: 소켓 보호

다른 방식은 라우팅 테이블을 전혀 건드리지 않습니다. 클라이언트가 터널을 운반하는 소켓을 표시하거나 보호하므로, 운영체제가 정책에 따라 그 소켓의 트래픽을 터널 밖으로 보냅니다. Android는 이 메커니즘을 VPN 애플리케이션에 직접 제공합니다. 데스크톱 클라이언트는 방화벽 표시나 장치 바인딩 호출로 동등한 기능을 구현합니다.

장점은 예외가 저장된 게이트웨이가 아니라 연결을 따라간다는 것입니다. 네트워크가 바뀌면 기반 경로가 다시 쓰이고, 보호된 소켓은 주소에 고정된 적이 없으므로 함께 이동합니다. 라우팅 테이블은 깨끗하게 유지됩니다.

대가는 가시성입니다. 라우팅 테이블에는 예외를 보여 주는 것이 없으므로 호스트 경로를 찾는 검토자는 아무것도 찾지 못하고 설계가 안전하지 않다고 결론지을 수 있습니다. 보호는 클라이언트 코드와 그 코드가 설치하는 정책 규칙 안에 있습니다.

둘 사이의 선택

클라이언트가 지원하고 고정된 경로를 깨뜨리는 네트워크 변경을 견디므로 소켓 수준 메커니즘을 선택하십시오. 예외가 다른 도구에 보여야 하거나 클라이언트가 더 나은 것을 제공하지 않는다면 호스트 경로를 선택하십시오.

어느 메커니즘도 서버 주소가 애초에 접근 가능해야 한다는 필요를 없애지 않습니다. 사설 게이트웨이에는 공용 수신 주소가 있고, 터널 밖에 남아야 하는 것이 바로 그 주소입니다. 그 뒤의 내부 네트워크는 터널이 존재한 뒤에야 접근 가능해집니다.

무엇을 선택하든 터널을 소유한 구성 요소 옆에 두십시오. 손으로 추가한 떠돌이 호스트 경로는 다음 네트워크까지 작동하고, 그다음에는 내일의 불가사의한 장애가 됩니다.

관련 글