SSH로 접속하는 머신에서 전체 터널 OpenVPN 클라이언트를 켜면 세션이 찰나에 끊길 수 있고, 머신 쪽에는 잘못이 없습니다. 터널은 패킷이 서버에 도착하는 방식을 바꾸지 않으며, 바뀌는 것은 응답이 나가는 방식입니다. 기본 경로가 tun0을 가리키는 순간 SSH 클라이언트로 가는 응답은 제공자 네트워크로 들어가고, 그 네트워크에는 당신의 주소로 돌아오는 경로가 없습니다. 연결이 멈추고, 다음 시도는 시간 초과되며, 데몬은 그동안 계속 22번 포트에서 듣고 있습니다.

VPN 로그를 읽기 전에 라우팅 테이블을 읽으세요. 로그는 터널이 올라왔다고 말할 것이고, 그 말이 맞습니다. 문제는 각 응답이 어느 인터페이스로 넘어가는가이며, 그것은 터널이 방금 다시 쓴 테이블에 대한 패킷별 조회입니다.

응답이 엉뚱한 문으로 나가는 이유

들어오는 패킷은 여전히 물리 인터페이스로 도착하는데, VPN 클라이언트는 인터넷이 서버의 공인 주소에 도달하는 방식을 바꿀 수 없기 때문입니다. 응답은 별개의 결정으로, 패킷마다 커널이 목적지를 조회하고 --redirect-gateway 이후에는 기본 항목이 터널을 가리킵니다. 제공자의 출구는 자기가 서비스하지 않는 목적지를 보고 패킷을 버립니다.

OpenVPN 자체의 보호는 정확히 한 목적지만을 덮습니다. 2.6 설명서는 --redirect-gateway를 하나의 절차로 설명하는데, 터널 이전에 쓰던 게이트웨이를 통해 VPN 피어에 대한 호스트 경로를 추가해 암호화된 트래픽이 자기 자신으로 돌아가지 못하게 한 다음 기본 경로를 교체합니다. 그 호스트 경로는 피어만을 위한 것입니다. 당신의 SSH 클라이언트는 그저 또 하나의 인터넷 주소이며 아무 예외도 받지 않습니다.

접두사 실수 하나는 일찍 배제할 가치가 있습니다. 길이 없는 ip route add -net 0.0.0.0 gw 10.8.0.1 dev tun0는 단일 주소 0.0.0.0과 일치하며 인터넷과는 일치하지 않으므로 0.0.0.0/0여야 합니다. 그리고 기본 경로를 받아들이는 것이 터널 자체의 전송을 깨뜨린다면, 물리 인터페이스를 통한 호스트 경로로 끝점을 추가하세요, 제공자의 실제 주소를 사용해서.

즉시 고치려면 클라이언트 주소를 고정하세요

가장 빠른 복구는 터널이 생기기 전에 서버가 쓰던 게이트웨이를 통한 클라이언트 주소의 호스트 경로입니다. 당신 쪽에서 서버가 보게 될 주소를 찾고, 그다음 서버의 테이블을 읽어 예전 게이트웨이를 확인하세요:

curl -s https://ifconfig.co
ip route

서버에서는 클라이언트가 시작되기 전에 예외를 추가합니다. 여기 주소는 자리 표시자이며, 모양이 중요합니다:

sudo ip route add 80.128.240.55 via 192.168.178.1

이것은 보고된 사례에서 쓰인 복구이며 즉시 동작합니다. 예측 가능한 두 가지 면에서 취약하기도 한데, 재부팅하면 경로가 사라지고 ISP가 다른 주소를 주는 순간 틀린 경로가 됩니다. 긴급 조치로 취급해 의도적으로 지속되게 만들되, 네트워크 유닛이나 디스패처 훅을 쓰거나, 임시라는 점을 인정하고 복구 경로를 적어 두세요.

클라이언트가 기본 경로를 가져가지 못하게 하세요

더 깔끔한 선택은 터널이 기본 경로를 아예 바꾸지 못하게 하는 것입니다. route-nopull를 --client 또는 --pull와 함께 쓰면 경로, block-outside-dns, DNS 서버 같은 DHCP 방식 옵션을 제외한 밀어낸 옵션을 받아들이므로 필요한 접두사만 더하게 됩니다. 더 좁은 도구는 풀 필터로, 문제가 되는 지시 하나를 버리고 나머지는 유지합니다:

route-nopull
pull-filter ignore "redirect-gateway"

둘 중 하나만 쓰고 둘 다 쓰지는 말며, 무엇이 바뀌는지 분명히 하세요. 터널을 통한 기본 경로가 없으면 서버는 더 이상 제공자를 통해 인터넷을 탐색하지 않고, 명시적으로 라우팅한 곳에만 닿습니다. 전체 터널이 설정의 전부였다면 이것은 잘못된 손잡이며 위의 호스트 경로가 맞습니다.

돌아오는 경로를 출발지나 연결로 나누세요

들어오는 주소가 안정적이라면 출발지 규칙이 터널 자체의 경로를 건드리지 않고 응답을 로컬에 유지합니다. 물리 인터페이스 주소에 LAN 접두사와 물리 기본 경로를 담은 자체 테이블을 주고, 그 테이블을 그곳에서 오는 트래픽에 선택하세요:

ip rule add from 192.168.1.11 table 128
ip route add table 128 to 192.168.1.0/24 dev eth0
ip route add table 128 default via 192.168.1.1

이것이 동작하는 이유는 들어오는 SSH 연결에 대한 응답이 클라이언트가 접속한 주소를 출발지로 하기 때문입니다. 들어오는 모든 서비스가 계속 동작해야 했던 보고 사례가 정확히 이 모양을 썼습니다. 들어오는 주소가 유동적이거나 경로가 동적이면 대신 연결에 표시를 하되, 물리 인터페이스에서 오는 첫 패킷에 conntrack 표시를 달고 OUTPUT에서 응답 패킷에 복사한 뒤, 표시된 패킷을 물리 게이트웨이를 가리키는 테이블로 라우팅합니다.

iptables -t mangle -A PREROUTING -i eth0 -m conntrack --ctstate NEW -j CONNMARK --set-mark 1234
iptables -t mangle -A OUTPUT -m connmark --mark 1234 -j MARK --set-mark 4321
ip route add default via 192.168.1.1 dev eth0 table 3412
ip rule add fwmark 4321 table 3412

시험하는 동안 연결 표시, 방화벽 표시, 테이블 번호를 서로 다르게 유지하세요. 하나의 번호를 두 역할에 재사용하면 올바로 보이면서 잘못된 패킷에 걸리는 규칙이 됩니다. 테이블의 기본 항목은 일반 이더넷 링크에서 LAN 게이트웨이와 함께 via도 필요합니다. 맨 dev eth0는 인터페이스가 점대점이거나 다음 홉이 다른 방식으로 해석될 때만 동작하며, 보고된 Jessie 호스트는 맨 형태가 동작한 링크에 우연히 있었는데 그것은 따라 할 배포판 규칙이 아닙니다.

스스로 잠기지 않고 확인하세요

연결을 끊어서 시험하지 마세요. 첫 세션을 열어 두고 터널을 올린 다음, 당신의 주소가 어느 경로를 택할지 커널에 물으세요:

ip route get 80.128.240.55

답은 터널이 아니라 물리 인터페이스와 예전 게이트웨이를 말해야 합니다. 이어서 두 번째 SSH 연결을 열면 그것이 진짜 시험이며, 예전 세션은 새 연결이 재현할 수 없는 상태 위에서 살아남을 수 있습니다. 두 번째가 멈추는데 첫 번째가 여전히 응답한다면 규칙이 중요한 트래픽에 한 번도 걸리지 않은 것이고, 볼 곳은 표시나 출발지 주소입니다.

작업 내내 대역 밖 콘솔을 준비해 두세요. 유일한 접근 경로가 방금 끊어 버린 터널인 서버는 그 터널로 고칠 수 없고, 추가한 테이블 번호와 접두사를 적어 두면 되돌리기도 쉽습니다.

관련 글