Linux 호스트 하나에 두 번째 OpenVPN 클라이언트를 겹쳐 놓으면 두 곳 중 한 곳에서 실패한다. 바깥 터널이 그 전송을 가로챘기 때문에 안쪽 클라이언트가 자기 서버에 도달하지 못하거나, 두 번째 클라이언트가 라우팅 테이블을 다시 써서 첫 터널이 동작을 멈춘다. 두 문제 모두 암호화가 아니라 라우팅에 있으며, 새 터널이 어떤 애플리케이션 트래픽도 나르기 전에 해결된다. 여기 주소는 예시다. tun0는 바깥 터널, 203.0.113.9는 안쪽 서버, TCP 9999는 안쪽 경로를 써야 하는 흐름이다.

두 가지 실패 모드, 하나의 라우팅 테이블

안쪽 클라이언트는 자기 서버로 UDP 소켓을 열고, 그 소켓은 다른 소켓과 마찬가지로 현재 라우팅 테이블을 따른다. 바깥 터널이 이미 기본 경로를 차지하고 있으면 핸드셰이크 패킷은 바깥 터널로 들어간다. 이는 바깥 서버가 안쪽 서버에 도달할 수 있고 반대편이 그 흐름을 나르기로 동의할 때만 동작한다. 그렇지 않으면 핸드셰이크가 멈추고 클라이언트가 다시 시도한다.

두 번째 실패는 서버 push에서 온다. 보통의 push는 redirect-gateway def1이며, 기본 경로를 대체하는 대신 0.0.0.0/1과 128.0.0.0/1을 설치한다. 따라서 각자 자기 /1 쌍을 설치하는 두 클라이언트는 충돌하고, 거의 모든 목적지에서 가장 최근 쌍이 이긴다. 두 번째가 연결된 뒤 터널이 죽으면 테이블에서 그 두 프리픽스를 찾아보라. 기본 경로 자체는 한 번도 바뀌지 않았을 수 있다.

흐름에 표시를 하고 테이블을 준다

선택한 트래픽을 mangle 테이블에서 표시하고 정책 규칙에서 그 표시와 일치시켜 유도한다. 규칙은 기본 경로가 안쪽 장치인 전용 테이블을 가리키므로 표시된 패킷만 안쪽 경로를 탄다.

iptables -t mangle -A PREROUTING -p tcp -m multiport --dport 9999 -j MARK --set-mark 0x9999
ip rule add fwmark 0x9999 table 200 pref 1000
ip route add default dev tun1 table 200

이 규칙 집합의 실패 대부분은 두 가지 실수에서 온다. 방화벽이 설정한 표시 값과 규칙이 일치시키는 값은 같아야 한다. 어긋나면 오류도 효과도 없기 때문이다. 그리고 PREROUTING은 밖에서 들어오는 패킷만 보므로, 이 호스트에서 시작한 연결은 표시되기 전에 같은 일치 조건의 OUTPUT 규칙이 필요하다.

두 번째 클라이언트에게서 경로 소유권을 빼앗는다

두 가지 옵션이 두 번째 클라이언트의 테이블 편집을 막으며, 서로 다른 지점에서 작동한다. route-nopull은 push된 경로 옵션을 무시하므로 아무것도 설치되지 않는다. route-noexec는 push된 옵션을 클라이언트 환경으로 받아들이지만 테이블 변경은 훅에 맡긴다. 서버가 무엇을 제안했는지 봐야 할 때 원하는 것이 바로 이것이다.

훅은 push된 값을 환경에 두고 실행되며, 승인한 항목만 추가해야 한다. 멱등하게 만들어라. 자기가 소유한 테이블을 다시 만들기 전에 비우고, 이전 실행이 깨끗한 상태를 남겼다고 가정하지 마라. 재연결 때 훅이 다시 호출되며, 비우지 않고 추가하는 훅은 중복 경로를 쌓는다.

소유자를 하나 고르고 적어 두어라. 두 옵션을 모두 설정하는 프로필이나 수동 스크립트와 경쟁하는 훅은 나중에 아무도 설명할 수 없는 테이블을 만든다.

두 서버 주소를 고정한다

안쪽 클라이언트가 연결되기 전에 안쪽 서버에는 바깥 장치를 지나는 호스트 경로가 필요하다. 그 항목이 안쪽 전송을 기본 경로로 흘려보내지 않고 바깥 터널 안에 붙잡아 둔다.

ip route add 203.0.113.9/32 dev tun0
ip route get 203.0.113.9

바깥 서버에도 맞는 호스트 경로를 메인 테이블에 추가하라. 그렇지 않으면 재연결 때 바깥 클라이언트 자신의 전송이 자기 터널로 들어갈 수 있다. 순서는 선택 사항이 아니다. 이 항목들은 해당 인터페이스가 존재한 뒤에 추가해야 하며, 이는 연결 후에 실행되는 훅이나 스크립트를 뜻한다. 내려간 장치를 가리키는 경로는 제거되거나 무시되므로 부팅 시에 쓴 항목은 가정한 순서를 견디지 못한다.

테이블 전체를 읽는 대신 조회를 확인하라. 경로 조회 출력은 커널이 사용할 장치와 원본 주소를 알려 주며, 그것이 클라이언트가 소켓을 열 때 얻는 것과 정확히 같다.

연쇄를 시험하고 실패를 눈에 보이게 하라

안쪽 클라이언트가 연결되는 동안 바깥 인터페이스에서 캡처하라. 안쪽 핸드셰이크는 바깥 터널 안의 암호화된 페이로드로 나타나야 한다. 203.0.113.9로 가는 평문 UDP로 나타나면 호스트 경로가 없고 연쇄는 만들어진 적이 없다.

조용히 물리 업링크로 되돌아가는 연쇄가 최악의 결과다. 인터페이스 상태가 아무것도 알리지 않고 안쪽 트래픽이 잘못된 원본 주소로 나가기 때문이다. 바깥 터널이 끊길 때 두 번째 클라이언트가 직접 재연결할 수 있다면 그 경로를 끄거나 바깥 인터페이스에 묶어 실패가 보이게 하라.

두 흐름과 규칙 덤프 하나로 마무리하라. 표시된 연결과 표시되지 않은 연결은 서로 다른 경로를 타야 하고, ip rule show는 의도한 우선순위를 나열해야 한다. 두 흐름이 같은 경로를 탄다면 표시가 생각하는 곳에 적용되지 않고 있다.

관련 글