이 앱을 제외하는 스위치는 없다: 프로세스에 자체 소스를 부여하라
라우팅은 주소를 대조하지, 프로세스 이름을 대조하지 않습니다. 제외할 프로그램에 자체 소스와 테이블을 주거나, 결정을 라우터로 옮기십시오. 그곳에서는 정직합니다.
한 프로그램만 일반 연결을 쓰고 나머지는 모두 터널에 남아야 하는데, 클라이언트에는 그런 스위치가 없다. 숨겨진 메뉴도 찾을 수 없다. 경로는 패킷 내부의 필드에서 선택되며, 패킷은 주소와 포트를 나를 뿐 그것을 만든 프로세스의 정체성을 나르지 않으므로 제외는 그 필드 가운데 하나로 표현해야 한다. 패킷을 한 번 따라가 보면 선택지는 더 이상 수수께끼가 아니다.
클라이언트가 그것을 제공할 수 없는 이유
목적지 라우팅은 목적지 주소를 프리픽스와 비교하고 가장 긴 일치를 유지한다. 그 순간 모든 패킷은 무엇이 만들었든 똑같이 보인다. 소스 정책 라우팅은 비교 대상을 소스 주소로 넓히는데, 이것이 모든 실용적 우회가 쓰는 고리이며, 소스를 바꾸면 다른 테이블이 후보가 된다.
포트는 도움이 되지 않는다. 포트 기준 제외는 목적지 조회 전에 평가되어야 하는데, 그 결정에 포트는 아무 역할도 하지 않는다. 포트 기반 제외라고 광고되는 것은 실제로 다른 필드로 제외하면서 그것을 느슨하게 설명하는 것에 불과하다.
진짜 앱 단위 라우팅은 플랫폼이 Windows의 필터 콜아웃이나 Android의 VPN 서비스 바인딩처럼 그에 대한 장치를 추가한 곳에만 있다. 데스크톱 OpenVPN 인스턴스에는 그런 것이 없고, 어떤 설정 플래그로도 만들어낼 수 없다.
Linux: 프로세스에 표시를 하고, 그 표시로 라우팅하라
가장 깔끔한 방법은 결정을 소켓을 소유한 프로세스에 묶는 것이다. OUTPUT 체인에서 로컬로 생성된 트래픽에는 owner 일치가 작동하므로, 거기서 적용한 표시로 별도 테이블을 선택할 수 있다:
iptables -t mangle -A OUTPUT -m owner --uid-owner 1001 -j MARK --set-mark 0x1
ip rule add fwmark 0x1 table 200
ip route add 192.168.1.0/24 dev eth0 src 192.168.1.0 table 200
ip route add default via 192.168.1.1 dev eth0 table 200
사용자 1001의 패킷은 그다음 테이블 200을 참조하고 일반 게이트웨이로 나가며, 기본 테이블은 나머지 전부를 여전히 터널로 보낸다. owner 일치는 로컬로 생성된 패킷에만 적용되며, 그것이 바로 여기서 중요한 트래픽이고, 다른 호스트로 전달되는 패킷은 분류할 수 없다. 의도가 아니라 도구로 확인하라: ss -tnp는 설정된 연결의 로컬 주소와 소유 프로세스를 보여 주고, ip route get 1.1.1.1 mark 0x1는 표시가 어느 테이블을 선택하는지 보여 준다.
네트워크 네임스페이스는 자립형 프로그램에 더 튼튼한 답으로, 그 안에서 만들어진 모든 소켓은 구조상 거기에 묶이므로, 헬퍼 프로세스와 자식 연결이 분류되지 않고도 동작을 물려받는다. 대가는 배관 작업이다: veth 쌍, 양 끝의 주소, 그리고 네임스페이스 안에서 호스트를 가리키는 기본 경로가 필요하다.
ip netns add novpn
ip link add veth-host type veth peer name veth-ns
ip link set veth-ns netns novpn
ip addr add 10.200.0.1/24 dev veth-host
ip netns exec novpn ip addr add 10.200.0.2/24 dev veth-ns
ip netns exec novpn ip route add default via 10.200.0.1
Windows: 목적지가 여전히 결정한다
Windows는 목적지로 나가는 인터페이스를 고르므로, 다른 경로를 원하는 프로그램은 그 선택 전에 스택이 참조하는 무언가를 바꿔야 한다. ForceBindIP는 지정한 로컬 주소에 소켓이 이미 묶인 상태로 프로그램을 실행하는 식으로 앞부분을 담당한다. 그것만으로는 경로를 고르는 것이 여전히 목적지이기 때문에 패킷이 어느 인터페이스로 나가는지 바뀌지 않는다. 기본 경로가 터널을 가리키는 노트북에서 소스를 묶어도 패킷은 어쨌든 터널로 들어간다.
증상은 한 번 알면 읽을 수 있는 형태를 갖는다. 선택한 인터페이스가 직접 닿는 LAN 주소에서는 성립하고, 경로가 터널을 가리키는 인터넷의 모든 것에서는 실패한다. 프로그램이 작업을 헬퍼에게 넘기기 전까지는, 자기 소켓을 여는 헬퍼에게 묶임에 대해 전혀 알려지지 않았기 때문에, 동작하는 것처럼도 보인다.
하나의 프로세스가 잘못된 단위일 때 결정을 옮겨라
진짜 요구가 프로그램이 아니라 기기라면, 전용 SSID나 VLAN에 두고 라우터가 결정하게 하라: 그 소스는 정상 경로를 쓰고 다른 소스는 터널을 쓴다. 전용 게이트웨이 머신도 VPN을 유지하고 라우터가 보내는 목적지만 받는, 또 하나의 정직한 설계다. VLAN에서는 Windows 방식을 깨뜨린 헬퍼 프로세스를 포함해 기기가 여는 모든 흐름이 정책에 포함된다. 교환은 범위다: 같은 기기의 두 프로그램을 더는 다르게 다룰 수 없다.
둘 다 한 문장으로 말할 수 있는 범위를 가진 소스 또는 목적지 정책이다. 어느 것도 앱 단위가 아니며, 앱 단위라고 내세우는 설계는 프로그램이 자기 일을 두 번째 프로세스에 위임하는 첫 순간 무너진다.