1P by GN⁺ | ★ favorite | 댓글 1개
  • NAT Traversal은 NAT와 방화벽 뒤에 있는 장치끼리 UDP 패킷을 직접 교환하게 만들어, Tailscale이 WireGuard 터널을 중앙 허브 없이 연결할 수 있게 하는 기반 기술임
  • 핵심 전제는 프로토콜이 UDP 기반이고, NAT 탐색용 패킷과 실제 통신 패킷을 같은 네트워크 소켓에서 주고받을 수 있어야 한다는 점임
  • 상태 저장 방화벽은 먼저 나간 UDP 패킷과 맞는 응답만 허용하므로, 피어들이 서로의 ip:port를 알고 거의 동시에 패킷을 보내면 방화벽 상태를 열 수 있음
  • NAT는 소스 IP와 포트를 바꾸기 때문에 STUN, 포트 매핑, NAT64 처리, 생일 역설 기반 포트 탐색, 릴레이 같은 보완 기법이 필요함
  • ICE는 가능한 후보 경로를 동시에 시험해 가장 나은 경로를 고르며, Tailscale은 DERP 릴레이로 즉시 연결한 뒤 더 좋은 직접 경로가 발견되면 투명하게 전환함

NAT Traversal의 기본 조건

  • 목표는 두 장치 사이에 양방향 UDP 패킷 흐름을 만들고, 그 위에서 WireGuard, QUIC, WebRTC 같은 프로토콜이 동작하게 하는 것임
  • 직접 구현하려면 두 가지 조건이 중요함
    • 프로토콜은 UDP 기반이어야 함
      • TCP로도 가능하지만 복잡도가 높고, 구현 방식에 따라 커널 수정이 필요할 수 있음
      • 스트림 지향 연결이 필요하면 UDP 위에서 동작하는 QUIC을 고려할 수 있음
    • 프로그램이 패킷을 보내고 받는 네트워크 소켓을 직접 제어해야 함
      • NAT Traversal은 본 프로토콜 외의 추가 패킷을 보내야 하므로 기존 네트워크 라이브러리에 단순히 덧붙이기 어려움
      • NAT Traversal 로직과 본 프로토콜이 같은 소켓을 공유하며 병렬로 동작하는 구조가 유용함
  • 직접 소켓 접근이 어렵다면 로컬 프록시를 둘 수 있음
    • 원래 프로토콜은 프록시와 통신함
    • 프록시는 NAT Traversal과 피어로의 패킷 릴레이를 담당함

상태 저장 방화벽 관통

  • 상태 저장 방화벽은 과거에 본 패킷을 기억해 새 패킷 허용 여부를 결정함
    • Windows Defender firewall, Ubuntu ufw, BSD pf, macOS의 pf, AWS Security Groups 같은 형태가 있음
    • 흔한 설정은 모든 outbound 연결은 허용하고 모든 inbound 연결은 차단하는 방식임
  • UDP에서는 규칙이 단순함
    • 방화벽이 2.2.2.2:1234에서 5.5.5.5:5678로 나간 UDP 패킷을 봤다면, 반대로 5.5.5.5:5678에서 2.2.2.2:1234로 들어오는 패킷을 허용함
    • 일부 느슨한 방화벽은 한 번 통신한 로컬 포트로 어디서든 들어오는 트래픽을 허용할 수 있지만 점점 드묾
  • 서버와 클라이언트 구조에서는 방화벽 뒤 장치가 먼저 연결을 시작하면 되므로 문제가 작음
    • VPN에서는 방화벽이 없는 허브와 방화벽 뒤 스포크가 연결되는 hub-and-spoke 구조가 됨
  • 두 클라이언트가 직접 통신하려면 양쪽 방화벽이 서로를 막는 상황이 생김
    • 양쪽 모두 먼저 나가야 응답을 받을 수 있지만 상대도 같은 조건에 놓임
    • 사용자가 포트 개방을 직접 설정하는 방식은 불편하고, Tailscale 같은 메시 네트워크에서는 확장성이 낮음
    • 공항이나 카페 라우터처럼 사용자가 제어할 수 없는 방화벽도 많음
  • 해결의 핵심은 UDP 방화벽 규칙이 실제 응답 관계를 검증하지 않고 IP와 포트 조합만 본다는 점임
    • 양쪽 피어가 상대의 ip:port를 미리 알고 동시에 UDP 패킷을 보내면 처음 일부 패킷은 차단될 수 있어도 방화벽 상태가 열림
    • 이후 상대가 보낸 패킷은 응답처럼 보이면서 통과함
  • 이 방식에는 사이드 채널이 필요함
    • 양쪽 끝점이 거의 동시에 통신을 시도해야 함
    • 지연이 몇 초 있어도 되고 수천 바이트만 전달하면 되는 통신 경로면 충분함
    • WebRTC는 시그널링 채널을 요구하고, Tailscale은 coordination server와 DERP 서버를 사이드 채널로 사용함
  • 방화벽 상태는 영구적이지 않음
    • UDP 세션 타임아웃의 흔한 값은 30초
    • 연결을 유지하려면 주기적으로 패킷을 보내거나, 필요할 때 out-of-band 방식으로 연결을 다시 시작해야 함
  • 상태 저장 방화벽이 몇 겹 있더라도 outbound를 허용한다면 동시 전송 방식으로 통과할 수 있음

NAT가 문제를 더 어렵게 만드는 방식

  • NAT(Network Address Translator)는 상태 저장 방화벽처럼 동작하면서 패킷의 IP 주소나 포트까지 바꿈
  • NAT Traversal에서 문제가 되는 것은 주로 Source NAT(SNAT)
    • SNAT는 여러 장치가 더 적은 수의 IP 주소, 흔히 하나의 공인 IPv4 주소를 공유하게 함
    • DNAT도 존재하지만 여기서 다루는 NAT Traversal 문제와는 관련이 작음
  • 예를 들어 노트북이 192.168.0.20:1234에서 인터넷 서버 7.7.7.7:5678로 UDP 패킷을 보내면, 홈 라우터는 공인 IP의 빈 포트인 2.2.2.2:4242를 고름
    • 라우터는 192.168.0.20:12342.2.2.2:4242가 같다는 NAT mapping을 만듦
    • 이후 나가는 패킷은 2.2.2.2:4242에서 온 것처럼 바뀜
    • 들어오는 응답은 다시 192.168.0.20:1234로 바뀜
  • 기업 네트워크에서도 같은 원리가 적용됨
    • NAT 계층이 고가용성이나 용량 때문에 여러 장비로 구성될 수 있고, 여러 공인 IP를 가질 수 있다는 점이 다름

STUN과 NAT 매핑 발견

  • NAT 뒤의 피어는 상대가 볼 수 있는 자신의 공인 ip:port를 알 수 없고, NAT 매핑은 보통 인터넷으로 나가는 트래픽이 생겨야 만들어짐
  • STUN은 NAT 뒤 클라이언트가 인터넷에서 어떻게 보이는지 알아내는 프로토콜임
    • 클라이언트가 STUN 서버에 “내 endpoint가 너에게 어떻게 보이냐”고 물음
    • STUN 서버는 UDP 패킷이 온 공인 ip:port를 응답함
  • STUN이 알려준 공인 ip:port를 피어와 공유하면, 방화벽 관통에서 사용한 동시 전송 기법을 적용할 수 있음
  • NAT Traversal 로직과 실제 통신 프로토콜이 같은 소켓을 써야 하는 이유도 여기에 있음
    • 소켓마다 NAT 장비에서 다른 매핑이 생김
    • 실제 통신에 쓸 소켓이 아닌 다른 소켓으로 STUN을 수행하면 쓸모없는 ip:port를 얻게 됨
  • STUN만으로 모든 NAT를 해결할 수는 없음
    • 대부분의 홈 라우터에서는 작동할 수 있음
    • 일부 기업 NAT 게이트웨이에서는 실패할 수 있음
    • STUN으로 보이는 2.2.2.2:4242가 인터넷 전체에서 같은 의미를 갖는다는 가정이 항상 맞지는 않음

쉬운 NAT와 어려운 NAT

  • NAT 장비는 목적지에 따라 매핑을 다르게 만들 수도 있고, 목적지와 무관하게 같은 매핑을 유지할 수도 있음
  • RFC 4787은 목적지와 무관하게 매핑이 유지되는 쉬운 형태를 Endpoint-Independent Mapping(EIM) 이라고 부름
  • 목적지에 따라 매핑이 달라지는 어려운 형태는 Endpoint-Dependent Mapping(EDM)
    • 목적지 IP만 기준으로 달라질 수도 있고, 목적지 IP와 포트를 함께 기준으로 달라질 수도 있음
    • NAT Traversal 관점에서는 둘 다 좋지 않음
  • 과거 용어인 Full Cone, Restricted Cone, Port-Restricted Cone, Symmetric NAT는 NAT 매핑 동작과 방화벽 동작을 섞어서 표현함
    • 실용적인 구현에서는 “Symmetric 대 나머지” 또는 EIM 대 EDM 구분이 더 중요함
  • 동시 전송 기법은 여러 형태의 방화벽을 통과할 수 있음
    • 실제 환경에서는 IP와 포트에 종속적인 방화벽이 압도적으로 많음
    • 하지만 경로 어딘가에 hard NAT가 하나만 있어도 STUN과 동시 전송만으로는 문제가 생김

직접 연결 실패 시 릴레이

  • 직접 연결은 모든 기법을 써도 실패할 수 있음
    • NAT가 어렵거나, UC Berkeley guest Wi-Fi처럼 DNS를 제외한 outbound UDP를 차단하는 네트워크에서는 NAT 기법으로 해결할 수 없음
  • 이 경우 양쪽이 접근 가능한 릴레이를 통해 패킷을 주고받을 수 있음
    • 직접 연결보다 좋지는 않지만, 릴레이가 경로에 충분히 가깝고 대역폭이 충분하면 연결 품질 저하는 크지 않을 수 있음
    • 지연이 늘거나 대역폭이 줄 수 있어도 연결이 전혀 없는 것보다는 나음
  • 전통적인 릴레이 프로토콜은 TURN
    • 클라이언트가 TURN 서버에 인증함
    • TURN 서버는 릴레이용 ip:port를 할당함
    • 피어는 그 ip:port로 통신함
  • Tailscale은 TURN 대신 DERP(Detoured Encrypted Routing Protocol) 를 만듦
    • DERP는 HTTP 위에서 동작함
    • 엄격한 outbound 규칙이 있는 네트워크에서 유용함
    • 목적지의 공개 키를 기준으로 암호화된 페이로드를 릴레이함
  • DERP는 두 역할을 맡음
    • NAT Traversal 실패 시 데이터 릴레이
    • NAT Traversal을 돕는 사이드 채널
  • STUN, 동시 전송, 릴레이까지 구현하면 90% 이상은 직접 연결이 가능하고, 릴레이가 항상 어떤 형태의 연결성을 보장할 수 있다고 추정함

Hard NAT를 위한 추가 기법

  • hard NAT에서 쉬운 쪽 피어는 어려운 쪽 NAT가 어떤 포트를 열었는지 모름
    • STUN으로 IP는 대체로 맞다고 가정할 수 있음
    • 모르는 것은 포트이며 가능한 값은 65,535개임
  • 단순히 모든 포트를 훑으면 100패킷/초 기준 최악의 경우 약 10분이 걸리고, 포트 스캔처럼 보임
  • 생일 역설을 이용하면 탐색 비용을 줄일 수 있음
    • hard NAT 쪽에서 256개 소켓으로 256개 포트를 열고, 쉬운 NAT 쪽에서 임의의 대상 포트를 탐색함
    • 256개 포트가 열려 있다고 가정하면 성공 확률은 다음과 같음
      • 174개 임의 탐색: 50%
      • 256개 임의 탐색: 64%
      • 1024개 임의 탐색: 98%
      • 2048개 임의 탐색: 99.9%
    • 100포트/초라면 절반은 2초 안에 통과하고, 20초쯤이면 전체 공간의 4% 미만을 탐색하고도 거의 성공함
  • 양쪽 모두 hard NAT이면 훨씬 어려움
    • 이제 {source port, destination port} 쌍이 맞아야 함
    • 같은 조건에서 20초 뒤 성공 확률은 0.01%임
    • 99.9% 성공 확률에는 양쪽이 각각 170,000개 probe를 보내야 하며, 100패킷/초 기준 28분이 걸림
  • 이 방법은 홈-오피스, 홈-클라우드, 일부 오피스-클라우드나 클라우드-클라우드 시나리오의 연결성을 개선할 수 있음
    • 홈 라우터는 쉬운 NAT인 경향이 있고, hard NAT는 오피스 라우터나 클라우드 NAT 게이트웨이인 경향이 있음

포트 매핑 프로토콜

  • NAT에 직접 “이 WAN 포트를 이 LAN ip:port로 전달해 달라”고 요청하는 프로토콜들이 있음
  • 대표적인 세 가지는 다음과 같음
    • UPnP IGD: 1990년대 말에 나온 프로토콜로 XML, SOAP, UDP 위 multicast HTTP 같은 기술을 사용하며 구현과 보안이 까다로움
    • NAT-PMP: Apple이 만든 NAT Port Mapping Protocol로 포트 포워딩만 수행하며 단순함
    • PCP: NAT-PMP v2가 Port Control Protocol로 이어진 형태임
  • 로컬 기본 게이트웨이에 UPnP IGD, NAT-PMP, PCP를 시도해 응답이 있으면 공인 포트 매핑을 요청할 수 있음
    • 성공하면 STUN처럼 공인 ip:port를 알 수 있을 뿐 아니라, NAT가 해당 포트에 대해 더 관대하게 동작하게 만들 수 있음
    • 어디서 온 패킷이든 매핑된 포트로 도착하면 내부 장치로 전달됨
  • 이 프로토콜들에 의존할 수는 없음
    • 장비에 구현되어 있지 않을 수 있음
    • 기본적으로 꺼져 있을 수 있음
    • 정책상 비활성화되어 있을 수 있음
  • UPnP의 과거 취약점 때문에 정책상 꺼지는 경우가 있음
    • 일부 장비는 하나의 “UPnP” 체크박스로 UPnP, NAT-PMP, PCP를 모두 함께 끄기도 함
  • 사용할 수 있다면 데이터 경로에서 NAT 하나가 사실상 사라져 연결이 쉬워짐

Double NAT와 CGNAT

  • 한 장치 앞에 NAT가 두 겹 있는 double NAT에서는 바깥쪽, 즉 인터넷 직전 NAT의 동작이 가장 중요함
    • 여러 겹의 상태 저장 방화벽과 마찬가지로 추가 NAT 계층은 대체로 보이지 않음
    • 기존 기법은 NAT 계층 수와 무관하게 동작할 수 있음
  • double NAT가 크게 깨뜨리는 것은 포트 매핑 프로토콜임
    • 포트 매핑은 클라이언트에 가장 가까운 NAT 계층에 작용함
    • 하지만 원격 피어가 통과해야 하는 것은 가장 바깥쪽 NAT임
    • 결과로 얻은 ip:port는 중간 네트워크의 주소라 원격 피어가 도달할 수 없음
  • double NAT는 명시적 NAT Traversal을 하지 않는 대부분의 일반 애플리케이션에는 보이지 않음
    • 하지만 많은 게임의 멀티플레이를 악화시킬 수 있고, IPv6를 제거해 NAT 없는 연결 선택지를 줄일 수 있음
  • CGNAT(Carrier-Grade NAT) 는 ISP가 IPv4 주소 부족을 해결하기 위해 SNAT를 한 번 더 적용하는 구조임
    • 홈 라우터가 장치를 중간 IP로 SNAT함
    • ISP 네트워크 안의 두 번째 NAT 계층이 중간 IP들을 더 적은 공인 IP로 매핑함
  • CGNAT에서는 사용자가 ISP의 NAT를 재설정할 수 없음
    • 과거에는 고급 사용자가 홈 라우터의 포트 포워딩으로 문제를 피할 수 있었지만, CGNAT에서는 그 방법이 막힘
  • CGNAT도 기본적으로 double NAT이므로 기존 기법 대부분은 계속 작동함
    • 포트 매핑 프로토콜은 예외적으로 한계가 있음

Hairpinning 문제

  • 같은 CGNAT 뒤에 있지만 서로 다른 홈 NAT 뒤에 있는 두 피어는 특별한 문제를 만남
    • STUN 서버는 인터넷 바깥에서 본 공인 ip:port를 알려줌
    • 하지만 두 피어가 실제로 필요한 것은 CGNAT 내부의 중간 네트워크에서 통하는 ip:port
  • 홈 NAT 중 하나라도 포트 매핑 프로토콜을 지원하면 연결이 쉬워질 수 있음
    • double NAT 때문에 포트 매핑 프로토콜이 중간 네트워크의 ip:port를 알려주는 것이 오히려 도움이 됨
  • 포트 매핑을 사용할 수 없으면 hairpinning이 필요함
    • 예를 들어 peer A가 STUN으로 얻은 peer B의 2.2.2.2:5678로 패킷을 보냄
    • CGNAT가 이 패킷을 바깥 인터넷으로 보내지 않고, 내부에서 peer B의 NAT 매핑으로 되돌려 보내야 함
  • 많은 NAT는 hairpinning을 지원하지 않음
    • 내부 네트워크에서 비내부 IP로 가는 패킷은 항상 인터넷으로 나간다고 가정하는 장비가 있음
    • 이런 가정은 라우팅 실리콘에 박혀 새 하드웨어 없이는 고칠 수 없을 수도 있음
  • CGNAT이 개입하면 hairpinning은 연결성에 중요해짐
    • hairpinning과 포트 매핑이 모두 실패하면 릴레이를 써야 함

IPv6와 NAT64

  • IPv6만 있는 세계라면 NAT 문제는 훨씬 단순해짐
    • 모든 장치가 NAT 없이 도달 가능한 주소를 가질 수 있음
    • 하지만 상태 저장 방화벽은 여전히 남으므로 방화벽 관통과 사이드 채널은 필요함
    • outbound UDP를 막는 네트워크를 위해 HTTP 같은 프로토콜을 쓰는 fallback relay도 여전히 유용함
  • IPv6만으로는 아직 충분하지 않음
    • 세계는 대부분 IPv4이고 약 33% IPv6
    • IPv6 배포는 균일하지 않아 피어 조합에 따라 100% IPv6일 수도, 0% IPv6일 수도 있음
    • 무조건 연결을 목표로 하면 IPv4+NAT 처리를 계속 해야 함
  • IPv6와 IPv4 공존은 NAT64라는 추가 사례를 만듦
    • NAT44는 IPv4를 다른 IPv4로 변환함
    • NAT64는 내부 IPv6를 외부 IPv4로 변환함
    • DNS64와 함께 쓰면 단말에는 IPv6-only 네트워크처럼 보이면서 IPv4 인터넷 접근을 제공함
  • DNS 이름만 쓰는 애플리케이션은 NAT64를 거의 의식하지 않아도 됨
    • 하지만 NAT Traversal은 구체적인 IP와 포트를 직접 다루므로 별도 처리가 필요함
  • 장치가 CLAT(Customer-side translator) 를 지원하면 운영체제가 직접 IPv4 연결이 있는 것처럼 보이게 해 NAT64를 뒤에서 처리함
    • CLAT는 모바일 장치에는 흔함
    • 데스크톱, 노트북, 서버에는 드묾
  • CLAT가 없으면 NAT64+DNS64를 직접 감지해야 함
    • ipv4only.arpa.에 DNS 요청을 보냄
    • 이 이름은 알려진 고정 IPv4 주소로만 해석됨
    • IPv6 주소가 돌아오면 DNS64가 변환한 것이므로 NAT64 prefix를 알아낼 수 있음
  • 이후 IPv4 주소와 통신하려면 {NAT64 prefix + IPv4 address}로 IPv6 패킷을 보내면 됨
    • NAT64를 통해 STUN을 수행해 공인 ip:port를 찾으면 다시 일반 NAT Traversal 문제로 돌아감

ICE로 후보 경로 통합하기

  • 모든 기법 중 무엇을 써야 할지 미리 정확히 분류하는 방식은 확장성이 낮음
    • 네트워크 엔지니어와 NAT 장비 구현자는 다양한 동작을 만들기 때문임
  • ICE(Interactive Connectivity Establishment) 의 핵심은 가능한 모든 것을 동시에 시도하고, 작동하는 것 중 가장 좋은 경로를 고르는 알고리듬임
  • 통신 시작 시 로컬 소켓에 대한 후보 endpoint 목록을 모음
    • IPv6 ip:ports
    • IPv4 LAN ip:ports
    • STUN으로 발견한 IPv4 WAN ip:ports
    • NAT64 translator를 통해 발견한 IPv4 WAN ip:ports
    • 포트 매핑 프로토콜로 할당받은 IPv4 WAN ip:port
    • 정적으로 설정된 포트 포워딩 같은 운영자 제공 endpoint
  • 이후 사이드 채널을 통해 후보 목록을 교환하고, 상대가 준 모든 endpoint에 probe 패킷을 보냄
    • probe 패킷은 방화벽과 NAT를 열기 위한 패킷 역할을 함
    • 동시에 ping/pong 형태의 상태 확인 역할도 함
  • 일정 시간이 지난 뒤 작동이 확인된 후보 경로 중 휴리스틱상 가장 좋은 경로를 선택함
    • ICE는 보통 LAN > WAN > WAN+NAT 같은 사전 점수를 사용함
    • Tailscale은 v0.100.0부터 하드코딩된 선호 순서 대신 왕복 지연 시간을 사용함
  • Tailscale은 연결을 엄격한 probe 단계와 통신 단계로 나누지 않음
    • 모든 연결은 DERP가 미리 선택된 상태로 시작함
    • 사용자는 fallback 경로로 즉시 연결을 쓸 수 있음
    • 경로 탐색은 병렬로 돌고, 몇 초 뒤 더 나은 경로가 발견되면 투명하게 업그레이드됨

운영 중 경로 유지와 보안

  • 비대칭 경로에 주의해야 함
    • ICE는 양쪽 피어가 같은 네트워크 경로를 골라 양방향 패킷 흐름이 유지되도록 노력함
    • 같은 수준의 절차를 구현하지 않더라도 사용 중인 모든 경로에 양방향 트래픽이 있어야 함
    • 주기적인 ping/pong probe만으로도 이를 유지할 수 있음
  • 현재 선택된 경로가 실패할 수도 있음
    • NAT 유지보수로 상태가 사라지는 경우가 예시임
    • 가능한 모든 경로를 계속 probe해 warm fallback을 유지할 수 있음
    • 하지만 downgrade는 드물기 때문에 마지막 수단 릴레이로 떨어진 뒤 경로 탐색을 다시 시작하는 편이 더 효율적일 수 있음
  • 상위 프로토콜이 자체 보안을 제공한다는 전제가 중요함
    • QUIC은 TLS 인증서를 사용함
    • WireGuard는 자체 공개 키를 사용함
  • 동적으로 경로를 전환하면 IP 기반 보안은 의미가 없어짐
    • 최소한 end-to-end 인증이 필요함
  • 상위 계층에 end-to-end 보안이 있으면 ping/pong probe가 스푸핑 가능해도 최악의 경우 공격자가 트래픽을 자신을 경유하도록 유도하는 정도임
    • 그래도 경로 탐색 패킷도 인증하고 암호화하는 편이 좋음

견고한 NAT Traversal 구성 요소

  • 견고한 NAT Traversal에는 다음 요소가 필요함
    • UDP 기반으로 확장할 프로토콜
    • 프로그램 안에서 직접 접근 가능한 소켓
    • 피어와 통신할 사이드 채널
    • STUN 서버 몇 개
    • 선택 사항이지만 강력히 권장되는 fallback relay 네트워크
  • 수행 단계는 다음과 같음
    • 직접 연결된 인터페이스에서 소켓의 모든 ip:ports를 열거함
    • STUN 서버에 질의해 WAN ip:ports와 NAT 난이도를 찾음
    • 포트 매핑 프로토콜로 추가 WAN ip:ports를 찾음
    • NAT64가 있으면 이를 감지하고 그 경로로도 WAN ip:port를 찾음
    • 사이드 채널로 모든 ip:ports와 암호화 키를 피어와 교환함
    • 빠른 연결 수립을 위해 fallback relay로 먼저 통신할 수 있음
    • 상대의 모든 ip:ports를 probe하고, 필요하면 hard NAT를 통과하기 위해 생일 역설 기반 탐색을 수행함
    • 현재 경로보다 나은 연결 경로를 찾으면 투명하게 업그레이드함
    • 활성 경로가 멈추면 필요에 따라 downgrade해 연결성을 유지함
    • 모든 통신은 end-to-end로 암호화되고 인증되어야 함

댓글과 토론

Hacker News 의견들
  • 훌륭한 글임. 흔히 TCP 기반 홀 펀칭은 UDP보다 어렵기 때문에 하지 말라는 식의 암묵지가 있는데, 실제로는 이미 복잡한 UDP 흐름에 비해 추가 복잡도가 크지 않아 보임
    글에서도 TCP NAT 통과는 가능하지만 복잡성이 더해지고, 깊게 들어가면 커널 수정이 필요할 수도 있다고 인정함. 하지만 원시 UDP 패킷으로 연결을 시작하는 부분을 TCP SYN 패킷과 동시 열기(simultaneous open) 지원으로 바꾸면 된다고 봄
    특히 UC Berkeley 게스트 Wi‑Fi처럼 DNS 외의 모든 UDP 송신을 막는 네트워크가 있다는 점을 생각하면, TCP 홀 펀칭을 “UDP보다 어렵다”는 이유만으로 대충 넘기는 건 아쉬움. 거의 비슷하게 실현 가능하고 추가 복잡도도 제한적이라고 봄
    https://ttcplinux.sourceforge.net/documents/one/tcpstate/tcp...

    • 상태 추적 방화벽이 존재하고, 대부분의 NAT 필터가 EIF가 아니라 EDF라는 점 때문에 UDP에서도 동시 열기, 즉 동시 송신이 필요함
      그래서 TCP로 동시 열기를 하는 추가 복잡도는 꽤 작음. 핵심 난점은 공개 매핑을 전달하고 “동시” 펀칭/열기를 조율하는 것인데, 이건 보통 UDP에서도 필요함
      TCP에서 하나 더 복잡한 점은 가짜 TCP SYN 패킷을 만들기보다 실제 connect() 호출을 해야 한다는 것임. 일부 방화벽은 순서 번호를 보기 때문임
    • 정말 좋은 지적임. 직접 TCP 홀 펀칭을 구현해 봤고 지금은 꽤 괜찮은 구현을 갖고 있는데, TCP를 쓰면 구멍이 열린 뒤 UDP 위에 가난한 버전의 TCP를 다시 얹지 않아도 된다는 큰 장점이 있음
      다만 TCP 홀 펀칭은 UDP 패킷보다 SYN flood와 훨씬 비슷하게 보일 수 있어서 일부 네트워크에서는 성공률이 낮아질 수 있음. 실제로는 아직 필터링을 많이 보진 못했음
      TCP 홀 펀칭은 꽤 재미있음. 여러 번의 NTP 측정으로 시스템 시계가 NTP와 얼마나 어긋났는지 “시계 오프셋”을 계산하고, 시작자가 NTP 기준의 미래 만남 시간을 정하는 방식으로 구현함. 생각보다 정확하고, 같은 인터페이스의 소켓끼리 TCP 홀 펀칭도 동작함
      이런 이상한 로컬 기반 펀칭 모드를 지원한 이유는, 호스트 내부 펀칭이 그 정도 효율로 성공하면 LAN과 인터넷에서도 충분히 빠를 가능성이 높기 때문임. 코드는 Python이고, 첫 시도는 꽤 충격적이었음. TCP 홀 펀칭이 타이밍에 민감해서 Python에서 예전식 직접 소켓 관리, 스레딩, C 소켓 경험을 바탕으로 만든 허술한 이벤트 루프를 쓰니 실패가 났음
      그 코드를 동작시키려면 Python 프로세스 우선순위를 높여서 다른 프로세스가 펀칭 시도 사이에 지연을 만들지 못하게 해야 했음. 비효율적인 구현에서는 그만큼 시간에 민감함. 현재 구현은 각자 이벤트 루프를 가진 프로세스 풀을 쓰고, 시간에 분산된 작업 목록을 만든 뒤 각 작업이 같은 소켓을 재사용해 연결을 여는 방식임. 주요 운영체제에서 테스트한 뒤 Python에서는 이 접근이 가장 낫다고 판단함
      TCP와 UDP 홀 펀칭의 난이도가 비슷하다는 데 동의함. 둘 다 가장 어려운 부분은 NAT 예측 단계임. 아직 대칭형 NAT 우회 코드는 쓰지 않았지만, 통합하거나 새 플러그인으로 만들 방법이 보이기 시작함
    • TCP 펀칭이 UDP보다 불리한 또 다른 단점이 떠올랐음. TCP는 라우터가 연결 상태를 기록해야 함
      라우터의 상태 테이블은 매우 작고, 일부 펀칭 기법은 꽤 공격적임. 예를 들어 대칭형 NAT 우회를 시도하는 알고리즘처럼 수백 개의 TCP 연결을 열면 라우터를 서비스 거부 상태로 만들 수도 있음
      UDP는 상태 관리 최적화 덕분에 펀칭 때문에 라우터 전체가 먹통이 될 가능성이 더 낮을 수 있음. 다만 이건 추측임
  • 효과는 흥미로울 정도로 좋지만, 이걸 프로덕션 기업망에 넣자는 말이 나오면 왠지 불안해짐
    전통적인 NAT와 방화벽을 우회하고, 대신 소프트웨어 ACL 하나에만 의존하는 느낌이라 위험해 보임. 예를 들어 AWS 테스트 환경의 방치된 VM에 Tailscale이 있고 공격자가 거기에 접근하면, 내부 기업망의 노트북까지 커널을 지나 사용자 공간의 Tailscale ACL 코드만이 허용/차단을 결정하는 경로가 생기는 것처럼 보임
    허가되지 않은 누군가가 그 지점까지 들어와도 알 수 있을지 모르겠음

    • 그래서 많은 사람들이 NAT는 보안 메커니즘이 아니다라고 계속 반복하는 것임
      NAT와 그와 관련된 대부분의 상태 추적 필터를 뚫는 건 매우 쉬움. 실제 기업 프로덕션 환경에서 판매용 제품으로 이런 것을 구현해 봤고, 마법이 아니라 실무자들에게 잘 알려진 기술임
      진짜 패킷 필터링, 즉 방화벽을 원한다면 NAT와 별개의 방화벽 인스턴스를 배치하고 적절한 규칙을 둬야 함. 다만 그것도 주로 트래픽 양을 줄이는 데 도움이 될 뿐, 방화벽 자체의 실제 보안 이득은 이제 작음. 대부분의 공격은 HTTP/HTTPS, POP/IMAP 같은 상위 계층을 통해 들어오기 때문임
    • 공정하게 말하면 모두가 NAT를 보안 메커니즘으로 오해하는 이유는, 전통적으로 NAT가 상태 추적 방화벽과 함께 배포됐기 때문임
      실제로는 상태 추적 방화벽이 대부분의 일을 하고 있는데 NAT가 공을 가져가는 셈임. Tailscale은 방화벽을 없애는 게 아니라, 올바른 ACL 기반의 훨씬 포괄적인 설정을 제공함
      다만 Tailscale의 ACL 도구는 크게 개선될 여지가 있다는 점은 인정함
    • 네트워킹은 오래전부터 보안과 잘못된 설정의 독성 폐기장 같은 영역이었음. 여기에 컨테이너용 최신 호스트 기반 네트워킹 모델까지 섞였음
      그 영향으로 Windows 네트워크 스택도 상당히 달라졌고 더 복잡해졌음. WireGuard가 Linux에 들어간 뒤로는 누구나 어딘가에서 VPS에 연결하는 VPN을 하나쯤 갖고 있음. 모르는 걸 모른다는 점 때문에, 실제 상황은 생각보다 더 나쁠 가능성이 큼
    • 이건 IPv4 주소 부족을 우회하도록 만들어진 장비인 NAT 통과를 위한 것임
      방화벽은 다른 개념임. 다만 연결성과 보안을 엮어 말하자면, 인터넷 보안이 늘 목적지 포트를 기준으로 패킷을 막는 방식에 기대어 왔다는 점이 슬프고 불안함
      올바른 것보다 쉬운 것을 하면서도 “전문적인 솔루션”이라는 이름이 붙는 현실임
    • VoIP는 예전부터 모두 이런 방식으로 동작했고, 이를 쉽게 하기 위한 표준 공개 인프라도 많음. ICE, TURN 같은 것들임
      그래도 내부의 무언가가 먼저 외부로 말을 걸어야 하므로, 실제 방화벽은 송신과 수신 연결을 모두 허용 목록으로 관리해야 함
      다시 말해 경계 보안에 의존한다면, 누군가 결국 자기 조직판 “형광 조끼”가 무엇인지 찾아내는 건 시간문제임
  • 애플리케이션 계층에서 이미 암호화하는 장치들을 위해, 연결 암호화가 없는 Tailscale 비슷한 대안이 있으면 좋겠음. 인터넷의 거의 전체가 그런 식으로 동작하듯, 항상 하위 계층까지 암호화할 필요는 없음
    저전력 장치, 예를 들어 Tailscale 비슷한 터널을 돌리는 IoT 장치에서는 계산 비용이 특히 큼
    GRE 터널은 있고 실제로 많이 쓰지만, UDP 홀 펀칭이 처리되지 않으므로 허브-스포크 구조가 필요함. GRE, 즉 ip fou로는 피어 간 메시를 만들 수 없음
    신원 확인을 위한 암호화 핸드셰이크 뒤에 UDP 홀 펀칭과 암호화되지 않은 GRE 터널을 제공하는 라이브러리가 있는지 궁금함

    • 이 분야의 확립된 표준은 ICE(Interactive Connectivity Establishment) 로, WebRTC가 의존함. 이를 구현하거나 일부 요소를 구현한 괜찮은 라이브러리들이 있음
      더 범용 연결성에 맞춘 것을 원한다면 libp2p가 원하는 것에 가까울 수 있음
      https://datatracker.ietf.org/doc/html/rfc8445
      https://github.com/pion/webrtc
      https://github.com/algesten/str0m
      https://libp2p.io
    • UDP는 아니지만 여기서 TCP 홀 펀칭과 다른 주요 NAT 통과 방법들을 구현했음: https://github.com/robertsdotpm/p2pd
      Python으로 작성했음. 다만 대부분의 네트워크 코드처럼 기본 인터페이스 사용을 전제로 하지 않음. 원하는 어떤 인터페이스에서든 서비스를 돌릴 수 있게 해서 더 다양하고 유용한 것을 만들 수 있게 하고 싶었음
      대부분 표준 라이브러리 모듈 기반임. C 확장은 크로스 플랫폼 패키지를 자주 깨뜨려서 싫어함
    • VoIP에서 홀 펀칭을 하는 것이 TURN, STUN, ICE이므로 그쪽 라이브러리를 재사용할 수 있음
    • Teredo를 되살려 보는 방법도 있음
  • 피어들이 상대가 쓰는 ip:port를 미리 알아야 하고, 이를 동기화하기 위해 조정 서버를 만들었다는 부분을 보면 SIP가 이름값을 했으면 좋겠다는 생각이 듦
    SIP는 Session Initiation Protocol, 즉 VPN 같은 임의의 세션도 시작할 수 있어야 할 이름인데, 실제로는 너무 복잡한 난장판이라 감수할 가치가 작아졌음. 원래는 P2P RTP 스트림을 수립하기 위한 통신용 부채널로 만들어졌다고 봄

    • SIP는 너무 많은 일을 해서 전부 머릿속에 동시에 올리기 무서울 정도임
      HTTP 같지만 상태가 있고, 양방향이며, 연합형이고, UDP 위에서도 동작함
      baresip가 겨우 SIP를 하기 위해 구현하는 양을 보면 TLS over UDP까지 포함해 엄청남. 심지어 부풀려진 것도 아니고, 그 기능들이 실제로 필요함
  • 2020년 글임. 이전 논의는 다음과 같음
    2022년: https://news.ycombinator.com/item?id=30707711
    2020년: https://news.ycombinator.com/item?id=24241105

  • NAT 통과를 설명할 때 사람들에게 보내던 글이 바로 이 글임
    우리가 P2P 앱을 만들 때 계속 이 방식에 의존하게 될지도 모름. IPv6는 충분한 추진력을 얻지 못했고, NAT와 SNI 라우팅이 대부분의 사람에게 대부분의 문제를 해결해 주기 때문임
    ISP 입장에서도 그 상황이 바뀌도록 만들 유인이 별로 없음

  • 인터넷 전체에서 NAT 통과를 다룬 글 중 가장 상세한 축에 든다고 봄. 다만 델타 동작에 대한 정보가 빠져 있음
    복잡한 얘기는 아니고, 일부 NAT가 연속된 외부 포트를 배정할 때 관찰 가능한 패턴을 갖는다는 뜻임. 가장 흔한 패턴은 출발지 포트를 보존하는 것이고, 이전 매핑에서 1씩 증가시키는 식의 패턴도 있을 수 있음
    이론적으로는 아주 좋은 글이지만, 소프트웨어 엔지니어가 실제로 어느 정도 활용할 수 있을지는 궁금함. 많은 내용을 설명하지만 알고리즘을 작성할 만큼 세부적이지는 않을 수 있음. 예를 들어 이 글만 보고 NAT 유형을 테스트하는 알고리즘을 작성하거나 자기 홀 펀칭 코드를 조정할 수 있을지는 모르겠음
    개인적으로는 이렇게 긴 글보다 단순한 표가 더 유용했던 논문들도 봤음. 그래도 좋은 출발점은 될 수 있음
    글의 마지막 절은 특히 중요함. 모바일 시스템에서 쓰이는 대칭형 NAT를 우회할 가능성이 있음. 최신 NAT 통과 연구도 비슷한 기법을 쓰며 거의 100%에 가까운 성공률을 주장함

  • 과거를 떠올리게 하는 흥미로운 글임. 2010년에 이런 방식을 쓰는 망각형 P2P 메시 네트워크를 만들었음
    당시에는 사람들이 우리가 생각한 것만큼 보안을 신경 쓰지 않았고, 지금도 여전히 충분히 신경 쓰지 않음. 장치는 늘고 가치도 커졌지만 여전히 꽤 안전하지 않음
    하드웨어 신뢰 루트, 인증/인가를 위한 안전한 신뢰 사슬, 최소한의 임시 권한을 갖춘 진짜 안전한 종단점은 아직도 어렵고, 가정망·기업망·대형 프로덕션 데이터센터망에서도 네트워크 경계 보안극은 계속되고 있음
    이것들이 보안 침해의 주된 근본 원인으로 보이지 않는 유일한 이유는, 더 쉬운 공격 경로가 아직도 널려 있기 때문임

  • 약간 주제에서 벗어나지만, 몇 주 전 이 분야를 전혀 모르던 상태에서 조금 읽어봤음
    받은 인상으로는 IPv6가 이 모든 것을 없애고 NAT 통과도 더 이상 필요 없게 만드는 것 같음. 그렇다면 왜 IPv6는 더 널리 쓰이지 않고, 홈 네트워크와 Tailscale VPN에서 시작하려면 어떻게 해야 하는지 궁금함

    • IPv6가 덜 인기 있는 이유 중 하나가 얼마나 큰 비중인지는 모르겠지만, 사람이 쓰기 어렵다는 점은 늘 도전 과제였음
      사업적 유인도 부족함
  • IPv6 대신 이런 것이 등장했다는 사실 자체가 충분히 괜찮은 해킹의 힘을 잘 보여줌