Docker의 PPTP는 인증은 되지만 트래픽을 나르지 않는다
PPTP는 제어에 TCP 1723, 페이로드에 GRE가 필요하다. 컨테이너에서는 conntrack 도우미가 없는 경우가 많아 로그인은 되고 나머지는 안 된다.
클라이언트가 인증하고 PPP가 주소를 협상한 다음 아무것도 움직이지 않는다. Ping이 실패하고 브라우저도 실패하며 컨테이너는 내내 연결된 것처럼 보인다. 이 패턴은 라우팅 테이블 문제가 아니다, 왜냐하면 라우팅 문제는 보통 제어 연결도 끊기 때문이다. 이것은 페이로드 경로이고, PPTP에서 페이로드는 방금 성공한 프로토콜과 다른 프로토콜이다.
가장 많은 시간을 아끼는 순서로 다섯 가지 확인.
확인 1: 제어 채널과 페이로드를 분리한다
PPTP는 TCP 1723으로 인증한 뒤 PPP 페이로드를 GRE, IP 프로토콜 47 안에 실어 나른다. GRE는 TCP도 UDP도 아니므로 제어 포트를 전달하는 모든 규칙은 GRE에 대해 아무 말도 하지 않는다. 1723을 통과시키는 NAT나 방화벽이 프로토콜 47을 조용히 버릴 수 있고, 세션은 성공처럼 보이는 방식으로 반쯤 만들어진 채 남는다.
로그인이 되고 패킷이 돌아오지 않으면 먼저 GRE를 의심하고 그다음 증명한다.
확인 2: 호스트 커널의 도우미를 확인한다
연결 추적은 도우미 없이 TCP 제어 연결을 GRE 흐름에 연결할 수 없다. 모듈은 nf_conntrack_pptp이고 호스트 커널에 살며, 컨테이너 이미지 안에서 로드해도 아무 소용이 없다, 모듈은 호스트 전체에 적용되기 때문이다. Docker 호스트에서 lsmod | grep nf_conntrack_pptp이 바로 그 확인이다. 없다면 modprobe nf_conntrack_pptp이 실행 중인 커널에 로드하지만 다음 부팅에 대해서는 아무 말도 하지 않으므로 호스트 설정에도 추가한다.
재부팅 때 사라지는 모듈은 다음 달에 같은 티켓이 된다.
확인 3: 그것을 끄는 커널 정책을 읽는다
모듈이 있어도 4.7 이후 커널은 net.netfilter.nf_conntrack_helper을 기본값 0으로 두어 모든 프로토콜의 자동 도우미 할당을 끈다. 이 기본값 때문에 올바르게 설치된 모듈도 아무 일을 하지 않는다: 코드는 있고 정책이 쓰지 말라고 한다. sysctl net.netfilter.nf_conntrack_helper은 현재 값을 보고하고, 실행 중에 1로 설정하면 도우미가 다시 사용 가능해진다. 설계가 허용한다면 호스트의 sysctl 설정으로 값을 유지한다.
자동 할당은 트래픽이 암묵적으로 기대를 만들게 하며, 기본값이 바뀐 이유가 바로 그것이다. 신뢰하는 호스트로 제한하거나 도우미를 대상으로 하는 명시적 규칙을 사용한다.
확인 4: 컨테이너 쪽 경로를 따로 본다
로드된 도우미는 경로를 만들지 않는다. 컨테이너에는 PPTP 서버로 가는 경로가 필요하고 호스트 포워딩은 양방향으로 GRE를 허용해야 한다: iptables -L FORWARD -v -n은 프로토콜 47이 허용되는지, 카운터가 움직이는지 보여 준다. Docker의 masquerade 규칙은 TCP와 UDP를 변환하고 프로토콜 47은 변환하지 않으므로, 브리지 컨테이너에는 명시적 forward 허용과 일부 호스트에서는 자체 변환 규칙이 필요할 수 있다. 컨테이너가 이미지가 기대하는 PPP 장치 지원을 갖추었는지도 확인한다.
규칙의 문구가 아니라 카운터를 읽는다. 존재하지만 아무것도 맞지 않는 규칙은 장식이다.
확인 5: GRE가 추적되는지 증명하고, 그다음 PPTP를 판단한다
클라이언트를 켠 채 추적 테이블을 읽는다. PPTP 제어 항목이 성립되고 관련 GRE 항목이 있으면 도우미가 흐름을 잇고 있으며 남은 결함은 다른 곳에 있다. GRE 항목 없는 제어 항목은 페이로드가 버려지거나 추적되지 않는다는 뜻이며, 확인 2와 3으로 돌아가게 한다.
PPTP 자체가 구식이고 MS-CHAPv2는 약하다. 서버가 L2TP/IPsec, IKEv2 또는 WireGuard도 제공한다면 그중 하나로 옮기면 이 문제 부류 전체가 사라진다.
PPTP를 유지해야 한다면 클라이언트를 컨테이너 안이 아니라 호스트에서 실행하는 것이 NAT와 필터링 계층 하나를 없애며, 흔히 더 저렴한 결정이다.