SOCKS5 환경의 OpenVPN: 버전·전송·DNS 맞추기
프록시 버전, 전송 방식, DNS 경로가 어긋나면 SOCKS 체인이 실패합니다. SOCKS5를 맞추고 TCP로 나르며 체인을 하나의 네임스페이스 안에 가두세요.
이 워크스테이션에서 나가는 경로 하나는 SOCKS 프록시이고, 그 뒤의 OpenVPN은 쓸모 있는 정보 없이 연결 시간 초과를 보고합니다. 같은 머신의 브라우저는 그 프록시로 동작하고 자격 증명도 맞으며 포트도 응답합니다. 자주 어긋나는 세 가지를 먼저 확인하세요: 프록시가 쓰는 SOCKS 버전, 클라이언트가 고른 전송 방식, 그리고 이름 해석이 일어나는 위치입니다. 방화벽 탓으로 돌리기 전에 각각을 확인하세요.
방화벽보다 먼저 SOCKS 버전을 맞춘다
커뮤니티 OpenVPN은 socks-proxy에 SOCKS5를 씁니다. SOCKS4만 제공하는 프록시로 돌리면, 상대가 끝내 해석하지 않는 인사말의 응답을 클라이언트가 기다리기 때문에 TCP 연결이 열린 뒤 멈춥니다. 밖에서 보면 이 정지는 필터된 포트와 똑같이 보이며, 그래서 버전 문제가 어떤 방화벽 규칙보다 먼저 옵니다. SOCKS를 아는 가져오기 한 번이면 명령 하나로 판정됩니다.
curl --socks5-hostname 127.0.0.1:9050 gw.lab.example
완료된 요청은 동시에 두 가지를 증명합니다: 프록시가 SOCKS 요청을 받아들였고 이름을 해석했다는 것입니다. URL 스킴은 필요 없습니다, curl이 맨 호스트를 HTTP로 취급하며 이 시험에는 그것으로 충분하기 때문입니다.
그 가져오기는 클라이언트 로그가 감추는 실패도 배제합니다. 프록시는 브라우저를 허용하면서 VPN 목적지는 거부할 수 있고, 그때 거부는 CONNECT 단계에서 일어나며 어떤 VPN 인증보다 앞섭니다. OpenVPN은 시간 초과만 보고하므로 프록시 자신의 응답이 당신에게 필요한 증거입니다.
전송은 양쪽 모두 TCP여야 한다
여기서 SOCKS5는 TCP 스트림을 나르므로 터널 자체에 proto tcp-client가 필요합니다. 아직 UDP로 설정된 프로파일은, 클라이언트가 이 프로토콜의 UDP 연결 모드를 쓰지 않기 때문에, 이 경로로 동작하지 않습니다. 프로파일을 뒤집는 것은 형식이 아니라 실제 변경입니다: 원격 쪽이 같은 포트에서 TCP를 들어야 합니다. 그러면 페이로드는 TCP 속의 TCP가 되고, 잃어버린 세그먼트는 두 번 재전송되며 흐름이 나빠지는데, 대화형 세션은 이를 큰 전송보다 훨씬 크게 느낍니다.
proto tcp-client
remote gw.lab.example 8443
socks-proxy 127.0.0.1 9050
이 지시문은 프록시 호스트, 선택적 포트, 선택적 자격 증명 파일을 받습니다. 다른 곳에 둔 사본도 똑같이 동작하도록 이들을 프로파일 자체에 넣으세요.
체인 전체를 컨테이너 하나에 가둔다
체인을 호스트에서 하면 기본 경로를 빼앗고 리졸버를 나중에 아무도 설명할 수 없는 상태로 남깁니다. 대신 네임스페이스나 컨테이너가 범위를 정하므로, 호스트는 자기 경로를 지키고 SOCKS 홉과 터널은 함께 삽니다. 그 안에서 무언가 뜨려면 터널 장치, 그 장치를 설정할 권한, 도달 가능한 프록시가 필요합니다. Compose 서비스는 cap_add: NET_ADMIN와 bind 마운트한 /dev/net/tun로 그것을 얻습니다.
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
이 짝은 컨테이너를 privileged로 실행하는 것보다 좁으며, 필요한 장치 접근의 전부입니다. Tor 클라이언트나 별도 프록시 컨테이너가 리스너를 제공할 수 있습니다. 어느 경우든 loopback에 묶고, 프록시를 네트워크에 노출하는 대신 네임스페이스에서 호스트를 거쳐 도달하세요.
격리가 깨지는 곳은 이름 해석이다
패킷을 나르면서 호스트 리졸버에 묻는 터널은 아무것도 격리하지 못했습니다. 격리를 진짜로 만드는 두 가지는 네임스페이스가 자기 리졸버를 갖는 것, 그리고 그 질의가 프록시나 터널을 지나며 물리 인터페이스로는 절대 지나지 않는 것입니다. 시험에는 터널 뒤에만 존재하는 이름과, 같은 경로를 따라야 할 공개 이름, 두 이름을 쓰세요.
docker compose exec vpn cat /etc/resolv.conf
docker compose exec vpn getent hosts internal.lab.example
원격 이름은 해석되고 공개 이름은 해석되지 않는다면 체인은 고장이 아니라 선택적이며, 그 차이가 각 질의가 지난 경로를 알려줍니다. 둘 다 호스트에서 해석되지만 컨테이너 안에서는 어느 것도 해석되지 않는다면, 컨테이너는 아직 호스트 리졸버를 빌리고 있습니다. 찾은 리졸버 파일을 기록하세요, 바꿔야 할 설정이 바로 그것입니다.
각 홉을 순서대로 증명한다
목록은 짧습니다. SOCKS5 요청에 응답이 오고, 터널이 프록시를 거쳐 리스너에 닿고, 네임스페이스를 떠난 패킷이 원격이 기대하는 주소를 지닙니다. 설정을 더 쌓는 대신 하나씩 확인하세요. 클라이언트가 연결됐다는데 아무것도 움직이지 않는다면, 끊긴 곳은 거의 항상 두 번째나 세 번째 사실입니다.
curl --socks5-hostname 127.0.0.1:9050 gw.lab.example
docker compose exec vpn ip route get 203.0.113.10
docker compose exec vpn getent hosts internal.lab.example
통한 조합을 적어 두세요: SOCKS 버전, 전송, 리졸버, 네임스페이스 설정입니다. 이 체인을 기억으로 다시 푸는 비용은 메모보다 큽니다. 그 네 가지 사실은 채팅 스레드가 아니라 티켓에 남기세요.
연결됐다고 보고하면서 아무것도 통과시키지 않는 체인은 대개 아직 증명하지 않은 홉에서 실패했습니다. 다음 층을 더하기 전에 그 홉을 시험하세요.