요청은 언제나 같은 문장의 변형입니다. 브라우징은 VPN으로 두되, SSH나 백업 작업만 일반 연결을 쓰게 해 달라는 것입니다. 이때 흔히 도는 조언인 iptables NAT 규칙으로 포트를 포워딩하는 방법은 그 일을 하지 않습니다. NAT 규칙은 흐름 안의 주소와 포트를 다시 쓸 뿐, 패킷이 어느 인터페이스로 나가는지는 결코 고르지 않습니다. 그 결정은 라우팅 조회의 몫이고, 라우팅 조회가 포트를 보려면 도움이 필요합니다.

평범한 라우트가 포트와 일치할 수 없는 이유

Linux는 정책 규칙으로 후보 테이블을 걸러 낸 뒤, 목적지 주소를 테이블에서 조회해 경로를 고릅니다. 그 규칙은 방화벽 마크, 출발지 주소, 들어오는 인터페이스, UID와 일치할 수 있습니다. 경로 항목 자체에는 포트 필드가 없습니다. 전송 헤더가 중요해지기 전에 경로가 선택되기 때문입니다.

현재의 iproute2와 커널은 규칙 선택자를 넓혔습니다. ipproto, sport 그리고 dport 이 허용되므로 ip rule add ipproto tcp dport 22 table 100 은 의도를 한 줄로 표현합니다. 실제 호스트에서 ip rule add help 을 실행해 빌드가 dport 을 나열하는지 확인하십시오. 나열하면 명령 하나로 끝입니다. 나열하지 않으면 아래의 마크 경로가 같은 역할을 합니다.

어느 쪽이든 규칙은 포인터일 뿐입니다. 테이블 100에는 여전히 경로가 필요하고, 사람들이 잊는 부분이 바로 그것입니다.

세 조각, 아니면 하나

현대적인 형태는 위의 단일 규칙과 대체 테이블의 경로입니다. 고전적인 형태는 중간에 netfilter를 넣습니다. 패킷에 마크를 붙이고, 그 마크를 규칙에서 일치시킵니다. 둘 다 같은 조회에서 끝납니다. 차이는 포트를 어디서 읽는가입니다.

마크 방식이 있는 이유는 netfilter는 전송 헤더를 보지만 라우팅 코드는 역사적으로 보지 못했기 때문입니다. 메커니즘은 이것이 전부이며, 대안이 영리한 명령 하나가 아니라 협력하는 세 조각인 이유도 여기에 있습니다.

세 조각의 순서

먼저 우회할 흐름에 마크를 붙입니다. 경로 결정 전에 마크가 존재하도록 충분히 이른 체인에서 수행합니다.

nft add table inet vpnexempt
nft 'add chain inet vpnexempt out { type route hook output priority mangle; }'
nft add rule inet vpnexempt out tcp dport 22 meta mark set 0x1

다음으로 마크된 패킷을 별도 테이블로 보냅니다.

ip rule add fwmark 0x1/0x1 table 100

셋째, 그 테이블에 물리 링크를 통한 기본 경로를 줍니다. 주소는 예시이니 자신의 것으로 바꾸십시오.

ip route add default via 192.168.1.1 dev wlan0 table 100

셋째 단계를 건너뛰면 마크된 패킷은 테이블 100에서 경로를 찾지 못해 실패합니다. 증상은 규칙이 네트워크를 망가뜨린 것처럼 보이지만, 실제로는 패킷에 남은 유일한 길을 없앤 것입니다.

첫 패킷 이후에도 계속 동작하게 하는 것

마크는 나가는 패킷에 적용됩니다. 응답은 그 링크가 부여한 주소로 물리 인터페이스에 도착하고, 메인 테이블은 그 목적지에 여전히 터널을 선호할 수 있습니다. 두 가지 메커니즘이 응답 경로를 일관되게 유지합니다. 링크 주소에 대한 출발지 기반 규칙과, 흐름의 수명 동안 결정을 보존하는 연결 추적입니다. 마크를 conntrack에 저장하고 응답에서 복원하는 것이 상태 기반 예외를 안정시키는 일반적인 방법입니다.

역방향 경로 필터링은 또 다른 조용한 파괴자입니다. 커널이 반환 경로를 엄격히 검증하는데 응답이 메인 테이블이 고르지 않을 인터페이스로 도착하면, 패킷은 아무 메시지 없이 버려집니다. rp_filter 의 기본값은 배포판과 인터페이스 포워딩 여부에 따라 다르므로, 꺼져 있다고 가정하지 말고 실제 값을 읽으십시오.

터널 자체의 전송을 마크하지 마십시오. WireGuard나 UDP OpenVPN 터널은 TCP 22가 아니므로 위의 방법은 안전합니다. 하지만 터널 목적지만 빼고 전부라는 식으로 쓴 예외는 암호화된 패킷을 잡아 라우팅 루프처럼 보이는 방식으로 터널을 깨뜨릴 수 있습니다.

가정하지 말고 확인하십시오

목적지와 포트에 대해 커널이 어느 경로를 택할지 물은 다음, 실제 전송을 실행하십시오.

ip route get 203.0.113.10
ip rule show
nft list ruleset

마크는 있는데 카운터가 전혀 움직이지 않으면 규칙이 일치하지 않는 것입니다. 카운터는 작성한 설정과 커널이 적용 중인 정책의 차이입니다.

이것이 잘못된 도구일 때

포트 기반 예외는 본질적으로 약합니다. 서비스는 포트를 옮기고, HTTP/3는 UDP 443으로 오며, 한 애플리케이션이 서로 다른 종류의 흐름을 여러 개 열 수 있습니다. 진짜 요구가 이 프로그램이 일반 링크를 쓰는 것이라면 라우팅 계층은 잘못된 자리입니다. 네트워크 네임스페이스나 애플리케이션별 프록시가 더 맞습니다.

요구가 이 서브넷이 일반 링크를 쓰는 것이라면 마크를 완전히 생략하고 더 구체적인 경로를 추가하십시오. 더 단순하고, 테이블 하나에서 보이며, 다음 사람이 규칙 집합을 읽지 않고도 감사할 수 있습니다.

방화벽을 건드리기 전에 세 요구 중 무엇이 실제로 필요한지 적으십시오. 위의 메커니즘은 정밀하고, 잘못된 계층에 적용된 정밀함은 그저 더 긴 장애일 뿐입니다.

관련 글