뻘소리2021. 11. 17. 11:24

Peer-to-Peer Communication Across Network Address Translators

 

홀 펀칭(hole punching)기술에 대하여

이 저작물은 크리에이티브 커먼즈 저작자표시-비영리-변경금지 2.0 대한민국 라이선스에 따라 이용할 수 있습니다. 이용허락조건을 보려면, http://creativecommons.org/licenses/by-nc-nd/2.0/kr/ 에서 확인하세요.

 

 

번역자: 리안 (양영욱)

youngwook.yang@gmail.com

http://blog.naver.com/ryanii

http://young-writing.blogspot.com/

 

원문: http://www.brynosaurus.com/pub/net/p2pnat/

 

 

 

 

 

Abstract

 

네트워크 주소 변환(NAT) P2P통신에 있어서 잘 알려진 어려움들을 야기한다. 이는 관련 Peer들이 임의의 글로벌 IP주소로부터 연결되지 않을 수도 있기 때문이다. 몇몇 NAT 선회 기법들이 알려져 있으나 문서화가 미미하고 또한 그들의 확실성 혹은 상대적인 장점 등에 관한 데이터는 더욱 불충분하다. 이 문서는 이들 기법 중 가장 간단하나 가장 확실하고 실질적인 NAT 선회 기법인 "홀 펀칭" 을 분석한 것 이다. 홀 펀칭은 UDP 통신에 대해서는 어느 정도 이해되어 있지만, 우리는 이것이 어떻게 TCP 통신에서도 신뢰성 있게 이용되는지를 보일 것 이다. 다양하게 출시 되어있는 NAT들을 상대로 이 기법의 신뢰도 데이터를 모은 결과, 테스트된 NAT들 중 82% UDP에 대한 홀 펀칭을 지원하고 약 64% TCP에 대한 홀 펀칭을 지원했다. NAT 벤더들이 점점 Voice over IP와 온라인 게임 프로토콜과 같은 중요한 P2P 어플리케이션의 필요를 알아감에 따라 미래에는 홀 펀칭에 대한 지원이 증가하게 될 것이다.

 

 

 

1. Introduction

 

인터넷의 거대한 성장과 방대한 보안 과제들이 결합되어 생긴 압력이 많은 어플리케이션들의 삶을 힘들게 만드는 방향으로 인터넷이 진화하게끔 압박해왔다. 인터넷 본래의 유니폼 주소 체계, 다시 말해 모든 노드들이 고유의 글로벌 IP주소를 가지고 모든 다른 노드들과 직접적으로 통신을 할 수 있는 방식은 사실상 지금 현존하는 주소 체계로 바뀌게 되었다. 이 방식은 글로벌 주소들로 이루어진 세계와 private 주소들로 이루어진 세계들이 네트워크 주소 변환(NAT)를 통하여 연결되는 주소 체계이다. 이 새로운 주소 체계에서는 그림 1에서 보여지는 바와 같이 오직 "main"에 있는 노드들, 즉 글로벌 주소 세계에 있는 노드들 만이 네트워크 상의 다른 노드들과 쉽게 연결된다. 이것은 오직 이들만이 글로벌하게 전달 가능한(routable) IP 주소들을 가지기 때문이다. Pirvate에 있는 노드들은 같은 네트워크 안에 있는 노드들과 연결을 할 수 있으며, 글로벌 주소 세계에 사는 "잘 알려진" 노드와 TCP 혹은 UDP 연결을 할 수 있다. 이 과정에서 NAT들은 밖으로 향하는 연결에 대하여 임시적으로 public 종점을 할당하고 이 세션을 구성하는 패킷들에 있는 주소와 포트를 변환한다. 이와 달리, 일반적으로 안으로 향하는 모든 트래픽에 대해선 특별한 설정이 되어있지 않는 한 차단한다.

 

 

 

Figure 1: Public and private IP address domains

 

 

 

이 현존하는 새 주소 구조는 클라이언트가 Private 네트워크에 있고 서버가 글로벌 주소 세계에 존재하는 전형적인 상황에 대한 클라이언트/서버 통신에 적합하다. 반면에, 텔레컨퍼런싱이나 온라인 게임에 사용되는 P2P 통신 프로토콜에 중요한, 즉 서로 다른 Private 네트워크에 존재하는 두 노드가 직접적으로 통신하게 하는 것을 어렵게 한다. 당연히 NAT들이 존재하는 이 상황에서도 이런 프로토콜들을 잘 작동하게 하는 방법이 필요하다.

 

서로 다른 Private 네트워크에 존재하는 호스트들 간의 P2P 연결을 구성하는 가장 효과적인 방법 중 하나가 "홀 펀칭"이라는 이름으로 알려져 있다. 이 방법은 이미 UDP기반의 어플리케이션들 사이에서 널리 이용되고 있으며 또한 본질적으로 TCP에도 적용된다. 이름이 주는 인상과 달리 홀 펀칭은 Private 네트워크의 보안을 해치지 않는다. 대신, 홀 펀칭은 대부분의 NAT들의 기본 보안 정책 안에서 어플리이케이션이 작동하도록 한다. 경로 상의 NAT들에게 효과적으로 신호를 보내 P2P 통신의 세션들을 "요청"하게 함으로써 이 세션들이 수락되게 한다. 이 문서는 UDP TCP에 대한 홀 펀칭을 위한 것이며 홀 펀칭을 가능하게 하는데 중요한 어플리이케이션의 요소와 NAT 방식에 대해 세세히 살펴볼 것이다.

 

불행하게도, 모든 NAT에서 작동하는 선회 방법은 없다. 왜냐하면 NAT 작동 방식이 표준화되어 있지 않기 때문이다. 이 문서는 현존하는 NAT들의 홀 펀칭 지원에 대한 실험의 결과를 제공한다. 우리의 데이터는 유저들이 인터넷을 통해 서로 다른 벤더들의 다양한 NAT들을 넘어 우리의 "NAT Check" 툴을 실행하여 제공된 결과에서 나온 것이다. 이 정보들은 자발적으로 선택된 사용자들로부터 모아진 관계로 실제로 인터넷에 출시된 NAT들의 분포상황을 나타내지 않는다. 하지만 결과는 전반적으로 고무적이다.

 

기본적인 홀 펀칭을 알아보는 한편, 우리는 또한 보다 복잡함의 대가를 치르고서 더욱 다양한 NAT들에 대해 홀 펀칭이 작동하도록 하는 변수들도 짚어 볼 것이다. 하지만 우리의 주 관심사는 합당한 네트워크 망 상에서 "잘 작동하는" NAT들에 대해 명확하고 튼튼히 작동하는 가장 간단한 홀 펀칭 기법을 개발하는 것이다. 우리는 의도적으로 과도하게 영리한 기법들을 다루지 않는다. 이들이 비록 "깨진" NAT들에 대해 일시적인 호환성을 제공할지 모르나 이는 항상 동작하는 것도 아니거니와 장기적으로 볼 때 추가적인 예측 불가능성과 네트워크 취약성을 일으킬 수 있기 때문이다.

 

비록 IPv6 [3] 의 보다 큰 주소 공간이 결국에는 NAT의 필요성을 줄일지도 모르나, 단기적으로 오히려 이는 NAT의 필요성을 더욱 증가 시킬 것이다.  왜냐하면 NAT 자체가 바로 IPv4 IPv6주소 도메인 사이의 상호 운영성을 이룰 수 있는 가장 쉬운 방법을 제공하기 때문이다 [24]. 거기다 더해, Private 네트워크 상에 있는 호스트들의 익명성과 비접근성은 보안과 비공개의 장점들로 여겨진다. 충분한 IP 주소들이 있다 하여도 방화벽이 사라지진 않을 것이다. IPv6 방화벽들도 내부로 향하는 요청되지 않은 트래픽을 기본적으로 막을 것이고 이는 홀 펀칭을 IPv6 어플리케이션들 에게도 유용하게 만들 것이다.

 

이 문서의 나머지는 다음과 같이 정리되어 있다. 섹션 2는 기본적이 용어와 NAT 선회에 대한 개념을 소개한다. 섹션 3 UDP에 대한 홀 펀칭에 대해 자세히 알아보고 섹션 4 TCP에 대한 홀 펀칭을 소개한다. 섹션 5 NAT가 홀 펀칭을 위해서 반드시 가져야 하는 중요한 특성들을 요약한다. 섹션 6은 대중적인 NAT들의 홀 펀칭 지원 여부에 대한 실험 결과를 보여주고 섹션 7은 관련된 연구들에 대해 논의하며 섹션 8에서 결론을 짓는다.

 

 

 

 

 

2. General Concepts

 

이 섹션에서는 이 문서에서 사용되는 기본적인 NAT 용어들을 소개한다. 그리고 UDP TCP에 같이 적용되는 일반적인 NAT 선회기법들을 개괄한다.

 

 

 

2.1 NAT Terminology

 

이 문서는 RFC 2663 [21]에 정의된 NAT 용어들과 분류법, 그리고 RFC 3489 [19]에 최근 추가로 정의된 용어들을 사용한다.

 

특별히 중요한 개념은 세션이다. TCP 혹은 UDP를 위한 세션 종점은 IP 주소와 포트 번호로 이루어진 하나의 쌍이며 하나의 고유한 세션은 두 개의 세션 종점들로 이루어진다. 연관된 호스트들 중 한 호스트의 관점에서 보면, 세션은 4개의 요소(로컬 IP, 로컬 포트, 리모트 IP, 리모트 포트)들로 효과적으로 식별된다. 세션의 방향은 보통 세션을 처음 시작한 패킷의 흐름 방향이다. , TCP의 경우 초기 SYN 패킷이고 UDP의 경우 첫 번째 유저 데이터 그램이다.

 

다양한 NAT의 방식 중 가장 일반적인 형은 traditional 혹은 outbound NAT 이다. 이 방식은 private 네트워크와 public 네트워크 사이의 비대칭(asymmetric) 다리를 제공한다. 기본적으로 outbound NAT는 오직 외부로 향하는 (outbound) 세션들만이 NAT를 선회할 수 있게 허용한다. 내부로 향하는 패킷들의 경우, NAT가 이 패킷들이 속한 세션이 자신의 private 네트워크에서 시작된 세션이라고 식별하지 않는 한 차단된다. Outbound NAT P2P 프로토콜들과 충돌을 일으키는데 이는 두 peer들이 서로 다른 두 NAT "뒤에" 존재하는 상황에서 서로 통신을 하려 할 때, 누가 세션을 시작하려고 하든 나머지 peer NAT가 이를 거절하기 때문이다. NAT 선회는 양쪽 NAT들에게 P2P 세션들이 "밖으로 향하는" 세션들로 보이도록 만든다.

 

Outbound NAT는 두 개의 변형을 가진다. Basic NAT는 오직 IP 주소만을 변환하고 Network Address/Port Translation(NAPT) 는 전체 세션 종점을 변환한다. 더 일반적인 형인 NAPT는 가장 보편화가 되었는데 이는 private 네트워크에 있는 호스트들이 하나의 public IP주소를 공유할 수 있게 해주기 때문이다. 이 문서에서 우리는 NAT NAPT라고 가정할 것이나, 우리가 논의할 기본 원리와 기법들은 Basic NAT에도 똑같이 잘 (혹은 때때로 차이를 무시해도 상관없이) 적용된다.

 

 

 

2.2 Relaying

 

NAT 를 넘나드는 가장 확실한 (그러나 가장 비효과적인) P2P 통신 방법은 중계(relaying)를 통해서 간단하게 표준 클라이언트/서버 모델처럼 만드는 것 이다. 두 호스트 A B가 잘 알려진 서버 S UDP 혹은 TCP 연결을 시작했다고 하자. 이 때 S IP 주소는 18.181.0.31, 포트 번호는 1234라 하자. 그림2에서 보이듯, 두 클라이언트는 서로 다른 private 네트워크 상에 존재하며 각각의 상응하는 NAT가 상대 클라이언트와 직접적으로 연결을 시작하는 것을 막고 있다. 직접 연결하는 대신에 두 클라이언트는 둘 사이의 메시지를 중계해주는 역할로 서버 S를 이용할 수 있다. 예를 들어, 클라이언트 B에게 메시지를 보내려면 클라이언트 A는 그냥 그 메시지를 서버 S로 이미 구성된 클라이언트/서버 연결을 통하여 보낸다. 그러면 서버 S는 이미 존재하는 B와의 클라이언트/서버 연결을 이용해 이를 클라이언트 B로 전달한다.

 

 

 

 

 

Figure 2: NAT Traversal by Relaying

 

 

 

두 클라이언트가 서버에 연결할 수 있는 한 중계는 항상 작동한다. 이 기법의 단점은 서버의 프로세싱 파워와 네트워크 대역폭을 소모한다는 것이다. 또한 아무리 서버가 잘 연결되어 있다 하더라도 두 클라이언트 사이의 통신 지연이 늘어날 가능성이 있다. 그럼에도 불구하고, 모든 NAT들을 상대로 확실히 동작하는 효과적인 기법이 없기 때문에, 최대의 확실성이 요구된다면 중계는 유용한 대책이다. TURN 프로토콜 [18]은 중계를 상대적으로 안전하게 구현하는 법을 정의한다.

 

 

 

2.3 Connection Reversal

 

어떤 P2P 어플리케이션들은 보다 명확하지만 제한적인 기법을 사용한다. 이 기법은 "거꾸로 연결"(connection reversal) 이라고 알려져 있는데, 그림 3에 나오듯, 두 호스트가 모두 잘 알려진 랑데뷰 서버 S에 연결되어 있고 오직 하나의 피어(peer)만이 NAT 뒤에 있을 경우 통신을 가능케 한다. 만약에 A B를 향해 연결을 시작하려 하면 이 직접 연결 시도는 자동적으로 작동한다. 왜냐하면 B NAT 뒤에 있지 않고 A NAT는 이 연결을 밖으로 향하는 세션으로 해석하기 때문이다. 하지만, 만약 B A를 향해서 연결을 시작하려 하면, A를 향한 어떤 직접 연결 시도든 A NAT에 의해 차단 당하게 된다. 그 대신에 B는 잘 알려진 서버 S를 통해서 연결 요청을 중계할 수 있다. 다시 말해 A에게 B를 향해 거꾸로 연결을 시도하라고 요청하는 것 이다. 이 기법의 명확한 한계와 상관없이, 랑데뷰 서버를 이용해서 P2P 연결의 셋업을 돕는 핵심 아이디어는 다음에 기술되는 더 일반적인 홀 펀칭 기법의 기본이 된다.

 

 

 

 

 

Figure 3: NAT Traversal by Connection Reversal

 

 

 

 

 

3. UDP Hole Punching

 

UDP 홀 펀칭은 두 클라이언트가 모두 NAT 뒤에 있다고 하더라도 잘 알려진 랑데뷰 서버를 통해서 직접적인 P2P UDP 세션을 구성하게 하는 기법이다. 이 기법은 RFC 3027 5.1 섹션 [10]에 언급되었으며, 이 웹 [13]에서 더 완전히 문서화가 되었고 최근의 실험적인 인터넷 프로토콜들 [17,11]에서 사용되었다. 또한 온라인 게임을 위한 다양한 고유의 프로토콜들도 역시 UDP 홀 펀칭을 사용한다.

 

 

 

3.1 The Rendezvous Server

 

홀 펀칭은 이미 두 클라이언트 A B가 랑데뷰 서버 S와 활성화된 UDP 세션을 가지고 있다고 가정한다. 클라이언트가 S에 등록할 때, 서버는 해당 클라이언트를 위한 두 종점을 기록한다. 하나는 S와 이야기하기 위해 클라이언트가 믿는 자신의 종점(IP 주소, UDP 포트)이고 다른 하나는 서버가 보는 클라이언트의 종점(IP 주소, UDP 포트)이다. 우리는 첫 번째 종점을 클라이언트의 private 종점이라 하고 두 번째를 클라이언트의 public 종점이라 한다. 서버는 클라이언트의 private 종점을 클라이언트에서 보내지는 등록 메시지 본체의 한 필드에서 얻을 수 있을 것 이고, public 종점의 경우 등록 메시지의 IP UDP 헤더에 있는 소스 IP 주소와 소스 UDP 포트 필드들로부터 얻을 수 있을 것이다. 만약에 클라이언트가 NAT 뒤에 있지 않다면, private public 종점은 반드시 같을 것이다.

 

불완전한 몇몇 NAT들은 IP 주소처럼 보이는 UDP 데이터그램의 4바이트 필드를 스캔하여 그것이 IP헤더에 있는 IP 주소 인 것처럼 해석하는 것으로 알려져 있다. 이런 동작에 대해 안전하게 대처하기 위해, 어플리이케이션들은 메시지 본체에 들어있는 IP주소를 흐리게 만들기를 원할지도 모른다. 예를 들어, IP 주소 자체 대신에 IP주소의 1의 보수로 변환하는 식으로 말이다. 물론, 어플리이케이션이 자신의 메시지들을 암호화한다면 이런 동작이 문제가 될 소지는 없을 것이다.

 

 

 

3.2 Establishing Peer-to-Peer Sessions

 

클라이언트 A가 클라이언트 B와 직접적으로 UDP 세션을 구성하고 싶다고 하자. 홀 펀칭은 다음과 같이 진행된다.

 

 

 

1. 처음에 A는 어떻게 B에 닿아야 할지 모른다. 따라서 A S에게 B UDP 세션을 구성하기 위해 도움을 요청한다.

 

 

 

2. S A에게 B public private 종점을 담은 메시지로 답한다. 그와 동시에, S B와 연결된 UDP 세션을 이용해서 B에게 A public private 종점을 담은 연결 요청 메시지를 보낸다. 이 메시지들이 수신되고 나면, A B는 서로의 public private 종점들을 알게 된다.

 

 

 

3. A B public private 종점들을 S로 부터 받으면, A UDP 패킷을 두 종점 모두에게 보내기 시작한다. 다음으로 어떤 종점이든 첫 번째로 B로 부터 유효한 답변을 이끌어 낸 종점과 고정되어 붙는다. 이와 비슷하게, S로부터 전될된 연결 요청 메시지에 있는 A public private 종점들을 B가 받게 되면, B A의 각각의 종점들에게 UDP 패킷을 보내기 시작하며 첫 번째로 동작하는 종점과 고정되어 붙게 된다. 이 메시지들이 비동기적이라는 조건하에 순서와 타이밍은 중요하지 않다.

 

 

 

이제 우리는 UDP 홀 펀칭이 3가지 각각 특정의 네트워크 상황을 어떻게 다루는지 생각할 것이다. "쉬운" 경우라고 할 수 있는 첫 번째 상황은 바로 두 클라이언트가 같은 NAT 뒤에 존재하는 경우이다. 두 번째 경우는 가장 일반적인 것으로 클라이언트들이 서로 다른 NAT 뒤에 사는 것이다. 세 번째 경우는 각각의 클라이언트가 두 레벨의 NAT 뒤에 있는 상황이다. 인터넷 서비스 제공 업체에서 배치된 일반적인 "첫 번째 레벨" NAT와 홈 네트워크를 위한 별개의 "두 번째 레벨" NAT 라우터들이 예가 된다.

 

어플리케이션 자신이 정확하게 물리적인 네트워크 레이아웃을 판별하기란 대개 어렵거나 불가능하다. 따라서 주어진 시간에 위의 상황들 (혹은 다른 많은 가능한 상황들) 식별하는 것도 마찬가지다. STUN [19] 과 같은 프로토콜들이 통신 경로 상의 NAT들에 대한 일정 정보를 제공할 수 있지만 이런 정보들이 항상 완전하거나 믿을만한 것은 아니다. 특히 다중 레벨의 NAT들이 연관되었을 때 그렇다. 이런 상황에도 불구하고, 홀 펀칭은 이 모든 경우에 대해 어플리케이션이 특정 네트워크 구성을 알 필요가 없이 자동적으로 동작한다. , 연관된 NAT들이 합리적인 방식으로 동작할 경우에 한해서다. (NAT "합리적인" 동작들에 대해서 섹션 5에서 설명될 것이다.)

 

 

 

3.3 Peers Behind a Common NAT

 

첫 번째로 생각해 볼 상황은 두 클라이언트가 (아마도 모르게) 같은 NAT 뒤에 사는 경우이다. 따라서 그림 4에서 보여지듯, 이들은 같은 private IP 주소 세상에 자리하고 있게 된다. 클라이언트 A는 서버 S UDP 세션을 구성하였고 NAT는 자신만의 public 포트 번호 62000을 이에 할당 하였다. 클라이언트 B도 비슷하게 S UDP연결을 구성했으며 NAT는 이에 public 포트 번호 62005를 할당 하였다.

 

 

 

 


 

Figure 4: UDP Hole Punching, Peers Behind a Common NAT

 

 

 

클라이언트 A가 위에서 개괄된 홀 펀칭 기법을 사용하여 서버 S를 소개자로 이용, B UDP 세션을 구성한다고 해보자. 클라이언트 A S에게 B에 대한 연결 요청 메시지를 보낸다. S A에게 B public private 종점들로 답을 하고 또한 A public private 종점들을 B로 전달한다. 이후 양쪽 클라이언트 모두 이들 종점들을 향해서 UDP 데이터그램을 상대방에게 직접적으로 보내길 시도한다. public 종점으로 향하는 메시지들은 그들의 종착지에 갈 수도 있고 못 갈수도 있는데 이는 해당 NAT가 아래 섹션 3.5에서 기술되는 hairpin 변환을 지원하냐 안하냐에 달려있다. 반면에, private 종점들로 향하는 메시지들은 그들의 종착지에 도달한다. 그리고 또한 private 네트워크를 통한 이 직접 루트가 NAT를 통하는 간접 루트보다 빠를 것 이므로, 클라이언트들은 이후의 정식 연결을 위해서 십중팔구 private 종점들을 선택할 것이다.

 

NAT hairpin 변환을 지원한다고 가정하면, private 종점들에게도 public 종점들과 같이 연결을 시도해야 하는 복잡함을 없앨 수도 있다. 이는 어플리케이션이 같은 NAT 뒤에 있는 로컬 연결을 해당 NAT를 불필요하게 거쳐가게 하는 것을 대가를 치러야 한다. 하지만 섹션 6에서 우리의 결과가 보여주듯, hairpin 변환은 다른 "P2P에 친근한" NAT 동작들에 비해 아주 적게 퍼져있다. 따라서, 지금 당장은 어플리케이션들이 public private 종점들을 모두 사용함으로써 실제의 이득을 얻을 것 같다.

 

 

 

3.4 Peers Behind Different NATs

 

그림 5에 서 나오듯, 클라이언트 A B가 서로 다른 NAT들 뒤에 private 주소를 가진다고 해보자. A B는 각자 그들의 로컬 포트 4321에서 서버 S의 포트 1234 UDP 연결을 시작했다.  outbound 세션들을 다루기 위해, NAT A S와 연결된 A의 세션에 포트 62000을 자신의 public IP, 155.99.25.11에 할당 하였다. NAT B S와 연결된 B세션에 포트 31000을 자신의 IP주소, 138.76.29.7에 할당했다.

 

 

 

 

 

 

 

Figure 5: UDP Hole Punching, Peers Behind Different NATs

 

 

 

A 는 자신의 private 종점 10.0.0.1:4321을 자신의 등록 메시지를 통해 서버로 보낸다. 이때 10.0.0.1은 자신의 private 네트워크 상에서의 IP주소이다. S는 전달된 A private 종점과 함께 자신이 보는 A public 종점도 함께 기록한다. 이 경우에, A public 종점은 155.99.25.11:62000이다. 이는 NAT에 의해 해당 세션에 임의로 할당된 종점이다. 이와 비슷하게 B가 등록을 할 경우에 S B private 종점인 10.1.1.3:4321 B public 종점인 138.76.29.7:31000을 기록한다.

 

이제 클라이언트 A B와의 직접적인 UDP 통신 세션을 만들기 위해 위에서 설명된 홀 펀칭 방식을 따르게 된다. 먼저, A S에게 B와의 연결을 위한 도움을 요청하는 메시지를 보낸다. S는 이에 대한 답으로 B public private 종점을 A에게 보낸다. 또한 A public private 종점을 B로 보낸다. A B는 각각 서로의 종점들을 향해 UDP 데이터그램을 보내기를 시작한다.

 

A  B가 서로 다른 private 네트워크 상에 있기 때문에 그들 각각의 private IP주소는 전역적으로 전달 가능하지 않다. 따라서 이들 종점으로 보내진 메시지들은 잘못된 호스트에 도달하거나 아니면 어떤 호스트에도 도달하지 않을 것이다. 많은 NAT들이 DHCP서버로서의 역할을 하고 있는데 주로 NAT벤더들에 의해 기본으로 선택된 private 주소 pool로 부터 아주 결정론적인 방식으로 IP주소들을 할당하는 식이다. 이 때문에 실질적으로는, B private 종점으로 향하는 A의 메시지는 A private 네트워크 상에 있는 B와 같은 private IP주소를 가지게 된 어떤 다른 호스트로 전달될 것이다. 따라서 어플리케이션은 반드시 모든 메시지들을 무슨 방식으로든 인증함으로써 이런 길 잃은 메시지들을 강하게 걸러내야 한다.

 

그럼, 이제 그림 5에서 보여지듯, B public 종점으로 보내진 A의 첫 번째 메시지를 생각해보자. 이 밖으로 향하는 메시지가 A NAT를 통과함에 따라, NAT는 이 메시지가 밖으로 향하는 새로운 세션의 첫 번째 UDP 패킷임을 알게 된다. 이 새로운 세션의 시작 종점(10.0.0.1:4321) A S사이에 이미 존재하는 세션과 같다. 하지만 도착 종점은 다르다. 만약 NAT A가 잘 작동한다면, A private 종점의 ID를 보존한다. 이는 private 시작 종점인 10.0.0.1:4321 주소로부터 이 해당되는 public 시작 종점인 155.99.25.11:62000 주소로 일관적으로 변환하는 ID이다. 따라서 B public 종점으로 향하는 A의 첫 번째 메시지는 사실상 A NAT "구멍을 뚫은 것이다." 이 홀 펀칭은 바로 새로운 세션을 위한 것인데, 이 세션은 A private 네트워크상에서 두 종점들 (10.0.0.1:4321 - A private 주소, 138.76.29.7:31000 - B public 주소)로 정의된다. 또한 이 세션은 메인 인터넷 상에서 두 종점들(155.99.25.11:62000 - A public 주소, 138.76.29.7:31000 - B public 주소)로 정의된다.

 

만약 B public 종점으로 향하는 이 A의 메시지가 B NAT이 도달했는데 아직 A를 향하는 B의 첫 번째 메시지가 B NAT를 건너기 전 이라고 하자. 이 경우 B NAT는 내부로 향하는 A의 메시지를 요청되지 않은 트래픽으로 분류, 걸러낼 것이다. 반면에, A public 주소로 향하는 B의 첫 번째 메시지는 앞서 설명과 마찬가지로 B NAT에 구멍을 열게 된다. 이는 새로운 세션을 위한 것으로 B private 네트워크에서 두 종점들(10.1.1.3:4321 - B private 주소, 155.99.25.11:62000 - A public 주소)로 정의됨과 동시에 인터넷 상에서는 (138.76.29.7:31000 - B public 주소, 155.99.25.11:62000 - A public 주소)로 정의된다. A B가 각자 자신들의 NAT들을 한번 건너기만 하면, 구멍들이 각각의 방향으로 열리고 UDP 통신이 정상적으로 진행될 수 있다. 클라이언트들은 public 종점들이 작동한다는 것을 한번 확인하고 나면 또 다른 방편이었던 private 종점들로 메시지들을 보내는 것을 멈출 수 있다.

 

 

 

3.5 Peers Behind Multiple Levels of NAT

 

다중 NAT 장치들에 연관된 어떤 망들에서는, 두 클라이언트가 그 망의 특정한 지식이 없이는 "최적의" P2P연결을 구성을 할 수가 없다. 그림 6에 나와있는 마지막 상황을 생각해보자. 인터넷 서비스 제공 업체 (ISP)가 많은 고객들을 적은 public IP주소 만으로 다중 수신하기 위해 큰 기업형 NAT C를 설치했다고 해보자. 여기에 해당 인터넷 업체의 고객들이 집에서 그들의 홈 네트워크를 구성하기 위해 각자의 ISP 제공 IP주소에 NAT A B를 설치했다고 해보자. 서버 S NAT C만이 오직 전역적으로 전달 가능한 IP 주소를 가지고 있다. NAT A B public 주소는 사실 ISP 주소 세계 안에 있는 private 주소이다. 한편, 클라이언트 A B의 주소는 NAT A B의 세계에 있는 private 주소인 것이다. 전과 마찬가지로 각각의 클라이언트가 서버를 향해 밖으로 향하는 연결을 시작한다. 이는 NAT A B 각각 하나의 public/private 변환을 생성하며 또한 NAT C가 이 각각의 세션에 해당하는 public/private 세션 구성을 야기한다.

 

 

 

 

 

Figure 6: UDP Hole Punching, Peers Behind Multiple Levels of NAT

 

 

 

그럼, 이제 A B가 홀 펀칭을 통해서 직접 P2P 연결 구성을 시도한다고 해보자. 아마 최적의 라우팅 방식은 클라이언트 A NAT B에 있는 클라이언트 B "semi-public" 주소, 10.0.1.2:55000 (ISP 주소 공간)으로 메시지를 보내고 클라이언트 B역시 NAT A에 있는 클라이언트 A "semi-public" 주소, 10.0.1.2:45000으로 보내는 방식일 것이다. 하지만 불행히도, A B가 이 주소를 알 방법은 없다. 왜냐하면 서버 S는 오직 클라이언트의 진정한 전역 public 주소만 보기 때문이다. 또한 A B가 이 주소들을 어떻게든 알았다 하여도 이들이 사용가능 할지는 미지수이다. 이는 ISP private 주소 세계의 IP주소 할당과 클라이언트의 private 세계에서의 IP 주소 할당이 충돌할 수 있기 때문이다. (예를 들어서, NAT C에서 NAT A로 할당한 IP주소가 NAT B에서 클라이언트 B로 할당한 주소인 10.1.1.3 일지도 모르는 것이다. (역주: 이러면 B는 자기 자신에게 메시지를 보내는 결과가 나와버린다.)

 

따라서 P2P통신을 위해 클라이언트들은 서버 S에게 보여지는 그들의 전역 public 주소를 이용하는 수 밖에는 없다. 그리고 NAT C에서 제공하는 hairpin 혹은 loopback 변환에 의존해야 한다. A B의 전역 종점, 155.99.25.11:62005으로 UDP 데이터그램을 보낼 때 NAT A는 먼저 데이터그램의 시작 종점을 10.0.0.1:4321 10.0.1.1:45000으로 변환한다. 이제 이 데이터그램은 NAT C에 도착하고 이 데이터그램의 도착 주소가 NAT C의 변환된 public 종점들 중에 하나라는 것을 알아차린다. 만약 NAT C가 잘 동작한다면, 데이터그램의 시작과 도착 종점을 모두 변환하고 이 데이터그램을 "돌려서" private 네트워크로 보낸다. 이제 데이터그램의 시작 종점은 155.99.25.11:62000 이고 도착 종점은 10.0.1.2:55000 이다. 끝으로 B private 네트워크로 이 데이터그램이 들어가면서 NAT B가 이 데이터그램의 도착 주소를 변환, B에 도착하게 된다. A로 가는 길도 비슷하게 작동한다. 많은 NAT들은 아직 hairpin 변환을 지원하지 않는다. 하지만 NAT 벤더들이 이 문제를 인지함에 따라서 점점 보편화 될 것이다.

 

 

 

3.6 UDP Idle Timeouts

 

UDP 통신 프로토콜은 NAT를 가로지르는 세션의 수명을 알 수 있는 어플리케이션 독립적이고 믿을 만한 방법을 NAT에게 제공하지 않는다. 이 때문에 대부분의 NAT들은 단순하게 UDP 변환들과 타이머를 연관시켜서, 만약 특정 시간 동안 어떤 트래픽도 없으면 구멍(hole)을 닫아 버린다. 불행히도 이 특정 시간에 대한 어떤 표준 값도 없다. 어떤 NAT들은 20초 정도로 짧은 시간을 가진다. 만약 어플리케이션이 홀 펀칭을 통해서 구성한 세션을 계속해서 살아있게 유지하려면, 반드시 주기적으로 "keep-alive" 패킷들을 보내서 NAT에 있는 해당 주소 변환의 상태가 사라지지 않게 해야 한다.

 

불행히도 많은 NAT들은 특정한 두 종점들로 정의된 세션과 UDP Idle 타이머를 연관시키기 때문에, 한 세션으로만 keep-alive 패킷들을 보내는 것으로 다른 세션들을 살아있게 유지할 수 없다. 비록 이 모든 세션들이 같은 private 종점들에서 시작되었다고 하더라도 말이다. 이렇듯 다른 많은 P2P세션들로 keep-alive 패킷을 보내는 대신에, UDP세션이 더 이상 작동하지 않는 것을 감지하고 "필요에 따라" 다시 원래의 홀 펀칭 방법을 실행하는 것으로 어플리케이션은 지나친 keep-alive 트래픽을 막을 수 있다.

 

 

 

 

 

4. TCP Hole Punching

 

NAT들 뒤에 있는 호스트들 사이의 P2P TCP 연결을 구성하는 것은 UDP보다 약간 복잡하다. 하지만 프로토콜 레벨에서 보면 TCP 홀 펀칭은 UDP와 아주 흡사하다. UDP 홀 펀칭 만큼 널리 알려지지 않았기에, 적은 NAT들만이 TCP 홀 펀칭을 지원한다. 하지만 관련된 NAT들이 이를 지원하기만 한다면, TCP 홀 펀칭 역시 UDP 홀 펀칭 만큼 빠르고 믿을만하다. 사실 잘 동작하는 NAT들 사이를 가로지르는 P2P TCP 연결이 UDP 통신 보다 튼실하다. 왜냐하면 UDP와 달리 TCP 프로토콜의 상태 기계가 NAT들에게 특정 TCP 세션의 수명을 정확하게 결정할 수 있는 표준 방법을 제공하기 때문이다.

 

 

 

4.1 Sockets and TCP Port Reuse

 

TCP 홀 펀칭을 구현하는데 가장 걸림돌이 되는 것은 프로토콜 문제가 아니라 API 문제이다. 표준 버클리 소켓 API가 클라이언트/서버 패러다임을 기반으로 디자인되었기 때문에,  API는 한 소켓이 connect()로 밖으로 향하는 연결을 시작하거나 혹은 listen() accept()을 통해서 안으로 향하는 연결들을 들을 수 있다. 하지만 둘 다는 안 된다. 거기다 더해, TCP 소켓들은 대개 로컬 호스트 상에서 TCP 포트와 일대일 관계를 가진다. 어플리케이션이 한 소켓에 특정 로컬 TCP 포트를 할당하고 나면, 두 번째 소켓을 같은 포트에 할당하려는 시도는 실패한다.

 

하지만 TCP 홀 펀칭이 작동하려면, 우리는 하나의 포트로 안으로 향하는 TCP 연결들을 들어야 함과 동시에 밖으로 향하는 다수의 TCP 연결을 시작해야 한다. 다행히도 모든 메이저 운영 체제들이 대개SO_REUSEADDR라고 불리는 특별한 TCP 소켓 옵션을 지원하는데, 이는 어플리케이션이 다수의 소켓을 같은 로컬 종점으로 바인딩 할 수 있게 해준다. , 모든 소켓들이 이 옵션을 켜놓은 상태여야 한다. BSD 시스템들은 SO_REUSEPORT 옵션을 소개했는데, 이는 주소 재사용과 별개로 포트 재사용을 제어할 수 있게 해준다. 이런 시스템에서는 두 옵션 모두 반드시 켜져 있어야 한다.

 

 

 

4.2 Opening Peer-to-Peer TCP Streams

 

클라이언트 A가 클라이언트 B TCP 연결을 설정하기를 원한다고 해보자. 우리는 이미 두 클라이언트 A B가 잘 알려진 랑데뷰 서버 S와 연결되어 있다고 가정한다. UDP의 경우와 마찬가지로, 서버는 각각 클라이언트의 public 종점과 private 종점을 기록하고 있다. 프로토콜 레벨에서 보면 TCP 홀 펀칭은 UDP의 경우와 거의 동일하게 동작한다.

 

 

 

1. 클라이언트 A S와 연결된 TCP 세션을 사용하여 S에게 B와 연결을 위한 도움을 요청한다.

 

 

 

2. S A에게 B public  private TCP 종점들을 보내주고 동시에 A public private 종점들을 B에게 보낸다.

 

 

 

3. S에게 등록하기 위해 사용했던 똑같은 로컬 TCP 포트들로부터, A B는 각자 비동기적으로 S에게 보고되어있던 상대방의 public private 종점들을 향해 밖으로 향하는 연결 만들기를 시도한다. 동시에 그들 각자의 로컬 TCP 포트들에서 안으로 향하는 연결을 듣고 있는다.

 

 

 

4. A B는 밖으로 향하는 연결 시도가 성공하거나 또는 안으로 향하는 연결이 나타날 때까지 기다린다. 만약 밖으로 향하는 연결 시도가 "connection reset" 이나 "host unreachable"등의 에러로 인해 실패하게 되면, 짧은 시간 뒤에 (예를 들어, 1초 뒤에) 그냥 다시 연결을 시도한다. 이를 어플리케이션이 정한 최대 timeout까지 계속한다.

 

 

 

5. TCP 연결이 만들어지면, 호스트들은 각자가 서로 의도했던 호스트와 연결되었는지를 확인하기 위해 인증을 한다. 만약 인증이 실패하면, 클라이언트들은 연결을 종료하고 다른 것들이 성공할 때까지 기다린다. 클라이언트들은 이 과정을 통해서 첫 번째로 성공적으로 인증된 TCP 연결을 사용한다.

 

 

 

동시에 서버 S와 임의의 개수의 상대방과의 통신을 하기 위해 딱 하나의 소켓만을 필요로 하는 UDP와 달리, 각각의 TCP 클라이언트 어플리케이션은 그림 7에 나오듯 하나의 로컬 TCP 포트에 바운드된 여러 개의 소켓을 다뤄야 한다. 각각의 클라이언트들은 서버 S와의 연결을 나타내는 소켓, 안으로 향하는 연결을 수락하기 위한 수신 소켓, 그리고 상대방의 public private TCP 종점들을 향해 밖으로 향하는 연결을 시작하기 위한 최소한 두 개 이상의 소켓이 필요하다.

 

 

 

 

 

Figure 7: Sockets versus Ports for TCP Hole Punching

 

 

 

그림 5에 나오듯, A B가 서로 다른 NAT들 뒤에 있는 일반적인 경우를 따져보자. 포트 번호들이 UDP 포트가 아니라 TCP 포트라고 해보자. A B private 종점으로 향하는 연결들은 실패하거나 잘못된 호스트와 연결된다. UDP의 경우와 같이, TCP 어플리케이션들도 그들의 P2P 세션들을 인증하는 것이 중요하다. 로컬 네트워크 상에 있는 호스트가 원래 연결하고자 하는 원격 private 네트워크상의 호스트와 똑같은 private IP를 가지는 경우에, 실수로 잘못된 연결이 생길 수 있는 가능성이 있기 때문이다.

 

반면에 각자의 public 종점으로 향하는 연결들의 경우, 이에 해당하는 NAT들로 하여금 A B의 직접적인 TCP연결을 활성화 시켜주는 "구멍"을 열게 한다. 만약 이 NAT들이 잘 작동한다면, 그들 사이의 새로운 P2P TCP 연결이 자동적으로 형성된다. 만약 B의 첫 번째 SYN패킷(A로 향하는) B NAT에 닿기 전에, A의 첫 번째 SYN 패킷(B로 향하는) B NAT에 닿으면, B NAT는 이를 요구되지 않은 안으로 향하는 연결로 간주하고 이 패킷을 버린다. 하지만, 이후에 B의 첫 번째 SYN 패킷(A로 향하는)은 통과되게 되는데, 이는 A NAT가 이 SYN B로의 향하는 연결의 한 부분으로 간주하기 때문이다. B로의 밖으로 향하는 연결을 A SYN이 시작했다고 보는 것이다. (역주: 간단히 말해 A SYN 패킷이 이미 A NAT "구멍"을 뚫어 놓은 것이다.)

 

 

 

4.3 Behavior Observed by the Application

 

TCP 홀 펀칭 와중에 클라이언트 어플리케이션이 보게 되는 그들의 소켓에 벌어질 일들은 타이밍과 더불어 해당 TCP구현에 달려있다. B public 종점으로 향하는 A의 첫 번째 outbound SYN 패킷이 NAT B에 의해 버려졌지만, 그 뒤에 A public 종점으로 향하는 B의 첫 번째 SYN 패킷이 A TCP SYN을 다시 보내기 전에 A에 도착했다고 해보자. 운영 체제에 따라서 두 가지 중 한 가지의 경우가 일어난다.

 

A TCP 구현은 들어오는 SYN의 세션 종점들이 A가 시작하려 했던 밖으로 향하는 세션들의 그것들과 짝임을 알게 된다. 따라서 A TCP 스택은 이 새로운 세션을 A B로 향해 connect()를 사용했던 로컬 어플리케이션 상의 소켓에 연관시킨다. 어플리케이션의 비동기 connect() 호출은 성공하고 listen 소켓에서는 아무 일도 일어나지 않는다.

 

수신된 SYN 패킷이 전에 A가 보낸 outbound SYN에 대한 ACK를 포함하고 있지 않기 때문에, A TCP B public 종점을 향해서 SYN-ACK 패킷을 회신한다. 이 패킷의 SYN 부분은 단지 똑같은 시퀀스 번호를 사용하는 A의 원래 outbound SYN의 반복이다. B TCP A SYN-ACK를 받기만 하면, A SYN을 위한 자신의 ACK로 답변을 하고 TCP 세션은 양쪽 상에 모두 연결된 상태로 돌입하게 된다.

 

또 다른 경우에는, A TCP 구현이 A가 안으로 향하는 연결 시도에 대한 활성화된 listen 소켓이 있다는 것을 감지할 지도 모른다. B SYN이 안으로 향하는 연결 시도로 보이기 때문에, A TCP는 새로운 TCP 세션과 이와 연관되는 새 stream 소켓을 생성하고 이 소켓을 어플리케이션이 자신의 listen 소켓에 accept() 호출하는 것을 통해 전달한다. 다음으로 A TCP B에게 위의 경우와 같이 SYN-ACK를 회신하고 TCP연결 설정은 보통의 클라이언트/서버 스타일 형식으로 나아간다.

 

B를 향한 A의 이전 outbound connect()가 사용했던 source destination 종점들이, 이제 아까 막 accept()를 통해서 어플리케이션으로 넘겨진 다른 소켓에 의해 사용되고 있기 때문에, A의 비동기 connect()는 반드시 어떤 시점에서 대개 "address in use" 에러와 함께 실패한다. 그럼에도 불구하고 어플리케이션은 B와의 P2P통신을 위한 작동하는 stream 소켓을 가지고 있으므로 이 실패를 무시한다.

 

첫 번째 경우는 대개 BSD를 기반으로 하는 운영체제들 상에서 나타나고, 두 번째 경우는 Linux Windows경우에 보다 자주 나타난다.

 

 

 

4.4 Simultaneous TCP Open

 

홀 펀칭 과정 중, 다수의 연결 시도들의 타이밍이 맞아 떨어져서, 두 클라이언트 모두의 밖으로 향하는 최초의 SYN 패킷들이 상대방의 NAT에 닿기 전에 자신의 해당 로컬 NAT에 당도하였다고 하자. 이 경우 각각의 NAT는 밖으로 향하는 TCP 세션들을 열게 된다. 이런 "운 좋은" 경우에는,  NAT들은 어떤 초기 (역주: 안으로 향하는) SYN 패킷도 거절하지 않게 되고 이 초기 SYN 패킷들은 NAT들 사이의 선상을 가로지른다. 이 경우에, 클라이언트들은 "simultaneous TCP open"이 라는 현상을 보게 된다. 이는 각각의 peer들이 모두 SYN-ACK를 기다리는 와중에 "생짜(raw)" SYN을 받은 것이다.  peer들의 TCP들은 SYN-ACK로 회신을 하며, 이때 SYN은 이전에 보낸 것과 같은 것이고 ACK 부분은 다른 peer로 부터 받은 SYN에 대한 수신 확인이다.

 

이 경우 각각의 어플리케이션들이 겪게 될 상황은 앞에서 설명된 바와 같이 TCP 구현이 어떻게 동작하냐에 따라 달라진다. 만약에 두 클라이언트 모두 앞에서 설명한 두 번째 경우와 같이 구현되었다면, 모든 비동기 connect() 함수 호출은 실패할 것이다. 하지만 그럼에도 불구하고 각각의 클라이언트에서 돌아가는 어플리케이션들은 accept()를 통해서 P2P TCP통신이 작동하는 새 연결을 가진다. 이것은 마치 마법과 같이 이 TCP 연결이 선상에서 "스스로 만들어져서" 서로의 종점에서 수동적으로 받아들여지기만 한 것과 같은 것이다! 어플리케이션이 P2P TCP 소켓들이 connect()에서 생겼는지 accept()에서 생겼는지 상관하지 않는다면, 이 과정은 RFC 793 [23]에 명시된 표준 TCP 상태 기계를 제대로 구현한 어떤 TCP에서도 동작하는 결과를 낳게 된다.

 

UDP를 위한 섹션 3에서 토의된 여러 다른 네트워크 구성에 따른 시나리오들은 TCP의 경우도 똑같이 적용된다. 예를 들어, TCP 홀 펀칭은 그림 6에서 나오는 다중 레벨의 NAT 경우에 동작한다. 관련된 NAT들이 잘 작동한다면 말이다.

 

 

 

4.5 Sequential Hole Punching

 

위의 TCP 홀 펀칭 방식의 변형으로 NatTrav 라이브러리 [4] 가 구현한 것은, 클라이언트들이 동시가 아닌 서로 순차적으로 접속을 시도한다. 예를 들어, (1)A S를 통해서 B에게 연결하고 싶다고 말하지만 A의 로컬 포트로 동시에 연결을 기다리진(listening) 않는다. (2)B A를 향해 connect()를 시도하고 이는 B NAT에 구멍을 연다. 하지만 이후 타임아웃이나 A NAT, 혹은 A 자신으로부터의 RST로 인해 호출은 실패한다. (3)B S의 연결을 닫고 자신의 로컬 포트에 listen()을 한다. (4)S는 다음으로 A와의 연결을 끊고, 이는 A에게 B로 직접 connect()를 시도하라는 신호가 된다.

 

이 순차적 방법은 simultaneous TCP open을 제대로 구현하지 못한 XP 서비스 팩 2 이전의 윈도우에서 특히 유용할 수 있다. 혹은 SO_REUSEADDR 기능을 제대로 지원하지 못하는 소켓 API의 경우도 마찬가지이다. 하지만, 순차적 방법은 보다 타이밍 의존적이고 일반적인 경우에 느릴 수 있으며 일반적이지 않은 상황에서는 덜 튼튼하다. 예를 들어, 스텝 (2)에서 B는 반드시 "실패하기로 되어있는" connect() 시도에게 충분한 시간을 주어서 적어도 하나의 SYN 패킷이 자신 쪽의 네트워크에 있는 모든 NAT들을 다 돌아다닐 수 있게 해야 한다. 너무 짧은 지연 시간은 이 과정을 실패하게 하는 잃어버린 SYN의 위험을 초래할 것이고, 너무 긴 지연 시간은 홀 펀칭을 위해 요구되는 전체 시간을 증가 시킬 것이다. 또한 순차적 홀 펀칭 방식은 양쪽 클라이언트에서 서버 S로 연결을 "소모"한다. 이는 새로 만들어져야 할 P2P 연결마다 클라이언트들이 S를 향해 새로운 연결을 열어야 하기 때문이다. 반대로, 평행 홀 펀칭 방식은 전형적으로 양쪽 클라이언트가 밖으로 향하는 connect() 시도를 하자마자 완료된다. 또한 각각의 클라이언트가 S와의 단일 연결을 무기한으로 유지하고 재사용할 수 있게 한다.

 

 

 

 

 

5. Properties of P2P-Friendly NATs

 

이 섹션은 NAT가 위에서 설명된 홀 펀칭 기법이 제대로 작동하기 위해서 반드시 가져야 하는 주요 속성들에 대해 설명할 것이다. 모든 NAT 구현들이 이 속성들을 만족시키진 않지만, 많은 NAT 구현들이 그러하다. 또한 NAT 벤더들이 voice over IP와 온라인 게임과 같이 P2P 프로토콜의 요구를 알아감에 따라 NAT들이 점차적으로 더 "P2P와 사이 좋게" 되고 있다.

 

 

 

이 섹션은 NAT들이 "반드시" 이래야 한다는 완벽한 또는 결정적인 명세를 의도하는 것이 아니다. 우리는 단지 P2P 홀 펀칭을 가능하게 하거나 또는 작동하지 않게 하는 가장 일반적으로 관찰되는 방식들에 대한 정보를 제공할 뿐이다. IETF NAT의 작동 방식에 대해 공식적인 "현존하는 최고의 방식들"을 정의하기 위해 BEHAVE라는 새로운 작업 그룹을 시작했다. 당연히 NAT벤더들은 공식적인 작동 방식 표준이 체계화 됨에 따라 IETF 작업 그룹을 직접 따라야 한다.

 

 

 

5.1 Consistent Endpoint Translation

 

여기서 설명된 홀 펀칭 기법은 오직 NAT private 네트워크 상에 주어진 TCP혹은 UDP의 소스의 종점을 NAT에 의해 조정되는 "하나" public 주소로 일관되게 연관을 시킬 경우에만 자동적으로 작동한다. 이렇게 작동하는 NAT cone NAT라고 RFC 3489 [19]와 다른 곳에서 일컬어진다. 왜냐하면 NAT가 하나의 private 종점에 기반하는 모든 세션을 NAT상의 같은 public 종점으로 "모으기" 때문이다. (역주: cone = 깔때기)

 

예를 들어, 그림 5의 시나리오를 다시 생각해보자. A가 처음 잘 알려진 서버 S로 연결 할 때, NAT A가 자신만의 IP주소인 155.99.25.11에 포트 62000 A의 종점을 나타내는 임시 public 종점으로 선택했다. 나중에 A가 똑같은 local private 종점으로부터 B public 종점으로 메시지를 보냄으로써 B를 향해서 P2P 세션을 구성하려고 시도할 때, A NAT A가 이 private 종점의 정체성을 유지하며 존재하는 public 종점 155.99.25.11:62000을 재사용하는 것에 의존한다. 왜냐하면, A를 위한 그 public 종점이 바로 B가 자신의 답신 메시지들을 보낼 곳이기 때문이다.

 

오직 클라이언트/서버 프로토콜들만을 지원하기 위해 디자인된 NAT들은 이런 방식으로 private의 종점들의 정체성을 보존할 필요가 없다. 이런 NAT들이 RFC 3489 용어법에 있는 symmetric NAT이다. 예를 들어, S와 연결된 클라이언트 A의 세션에 public 종점 155.99.25.11:62000을 할당하고 난 이후에,  NAT 155.99.25.11:62001과 같은 다른 public 종점을 B와 연결을 시작하려는 P2P 세션에 할당할지도 모른다. 이런 경우, 홀 펀칭은 연결성 제공에 실패한다. 이는 이후에 B에서 들어오는 메시지들이 잘못된 포트 번호로 NAT에 도착하기 때문이다.

 

많은 symmetric NAT들이 쉽게 예상할 수 있는 방식으로 연속되는 세션들에 대해서 포트 번호들을 할당한다. 이 사실을 이용해서, 홀 펀칭 알고리즘들의 변형들이 [9,1] symmetric NAT들까지 넘나들며 "많은 경우에" 작동하게 만들어질 수 있다. 이는 STUN [19] 과 같은 프로토콜을 이용해서 먼저 NAT의 작동 방식을 탐색하고 이후에 이 결과를 이용해서 NAT가 다음 새 세션에 할당할 public 포트 번호를 "예측"하는 것이다. 하지만 이런 예측 기법들은 움직이는 대상을 추적하는 것과 매한가지 이고 이 와중에 많은 것들이 잘못될 수 있다. 예를 들어, 예측된 포트가 이미 사용 중 이라면 NAT는 다른 포트 번호로 점프할 것이다. 또는 같은 NAT뒤에 있는 다른 어플리케이션이 잘못된 타이밍에 세션을 시작하여 이 예측된 포트가 할당될 수 있다. 예측 기법이 기존에 제대로 작동하지 않는 NAT들을 상대로 최대한의 호환성을 성취하는데 유용할 수 있지만, 이것이 장기적으로 확실한 답이 되지는 않는다. Symmetric NAT들이 세션당 트래픽 필터링을 하는 cone NAT 보다 더 높은 보안을 제공하지 않기 때문에, NAT 벤더들이 P2P 프로토콜들을 지원하기 위한 그들의 알고리즘을 적용함에 따라 symmetric NAT들은 점점 덜 일반적이 되고 있다.

 

 

 

5.2 Handling Unsolicited TCP Connections

 

NAT가 자신의 public 쪽에서 SYN 패킷을 받았는데 이것이 요청되지 않은 내부로 향하는 연결 시도라면, 그냥 조용히 이 SYN packet을 버리는 것이 중요하다. 어떤 NAT들은 대신에 TCP RST (역주: connection reset) 혹은 ICMP 에러 보고까지 되돌려 보내면서 이런 안으로 향하는 연결을 적극적으로 거부한다. 이는 TCP 홀 펀칭을 방해하게 된다. 섹션 4.2에 있는 홀 펀칭 과정의 네 번째 스텝에 서술된 바와 같이, 이런 방식은 어플리케이션들이 밖으로 향하는 연결을 다시 시도하는 한 아주 치명적이지는 않다. 하지만 일시적인 에러들이 발생하는 것이 홀 펀칭 과정을 길게 만들 수 있다.

 

 

 

5.3 Leaving Payloads Alone

 

적은 수의 현존하는 NAT들은 IP주소처럼 보이는 4byte 값을 패킷으로 부터 "맹목적으로" 스캔하고 이를 패킷 헤더에 있는 IP 주소마냥 해석하는 것으로 알려져 있다. 사용중인 어플리케이션 프로토콜의 관해서 아무것도 알지 못한 체 말이다. 이런 나쁜 방식은 다행히 일반적이지 않고 어플리케이션들이 자신이 보내는 메시지들에 있는 IP 주소들을 알아보지 못하게 만드는 것으로 방어가 가능하다. 예를 들어, 원하는 IP 주소의 2의 보수를 보내는 식이다.

 

 

 

5.4 Hairpin Translation

 

섹션 3.5에서 설명했듯이, 어떤 다중 레벨 NAT의 경우에 TCP 혹은 UDP 홀 펀칭이 작동하기 위해서는 hairpin 변환 지원을 요구한다. 예를 들어, 그림 6에서 나온 시나리오는 NAT C hairpin 변환을 지원하느냐에 달려있다. 불행히도 현재 NAT들 사이에서는 hairpin 변환이 드물다. 그러나 다행히도 이것을 요구하는 네트워크 시나리오 역시 드물다. 하지만 IPv4 주소 공간 고갈이 계속됨에 따라 다중 레벨 NAT가 점점 일반적이 되고 있다. 따라서 미래의 NAT 구현에서 hairpin 변환 지원은 중요하다.

 

 

 

 

 

6. Evaluation of Existing NATs

 

이 문서에서 설명된 TCP  UDP 홀 펀칭 기법의 현존하는 다양한 NAT들에 대한 튼실함을 평가하기 위해, 우리는 NAT Check [16]라고 불리는 테스트 프로그램을 개발하고 배포하였다. 또한 인터넷 사용자들에게 그들의 NAT에 관한 데이터를 요청하였다.

 

NAT Check의 주요 목적은 안정적인 UDP TCP 홀 펀칭을 위해 가장 중요한 두 가지 행동 속성들을 테스트하는 것이다. 종점 변환 시 일관된 정체성 보존(섹션 5.1)과 요구되지 않은 내부로 향하는 TCP SYN들을 RST ICMP 에러로 거부하는 대신에 그냥 조용히 버리는 것(섹션 5.2)이 이 두 가지이다. 여기다 더해, NAT Check는 따로 NAT hairpin 변환(섹션 5.4)을 지원하는지, 그리고 NAT가 요구되지 않은 내부로 향하는 트래픽을 완전히 차단하는지 여부도 테스트한다. 마지막 속성은 홀 펀칭에 영향을 주지는 않지만 NAT 방화벽 규정에 대해 유용한 정보를 제공한다.

 

NAT Check NAT 행동 개개의 모든 면을 테스트하려고 하지 않는다. 이들은 매우 다양하고 미묘한 차이점들이 있음이 알려져 있고, 또한 그 중의 어떤 것들은 믿을 만한 테스트를 하기가 어렵다. [12] 대신에, NAT Check는 단지 "제안된 홀 펀칭 기법들이 전형적인 네트워크 조건들 아래, 이미 출시된 NAT들에서 얼마나 일반적으로 작동할 것인가?" 라는 질문에 답변하려고 한다.

 

 

 

6.1 Test Method

 

NAT Check는 테스트 하려는 NAT 뒤에 있는 컴퓨터에서 실행되는 클라이언트 프로그램과 각각 다른 전역 IP 주소를 가진 세 개의 잘 알려진 서버들로 구성되어있다. 클라이언트는 TCP UDP 홀 펀칭에 관련된 NAT 작동 방식을 체크하기 위해 세 개의 서버와 협동한다. 클라이언트 프로그램은 작고 상대적으로 이식성이 있다. 현재 Windows, Linux, BSD, 그리고 Mac OS X 에서 실행된다. 잘 알려진 서버들을 실행하는 컴퓨터들은 모두 FreeBSD를 사용한다.

 

 

 

6.1.1 UDP Test

 

UDP를 위한 NAT의 행동 방식을 테스트하기 위해서, 클라이언트는 소켓을 하나 열고 지역 UDP 포트를 할당한다. 그리고 그림 8에서 나온 바와 같이 연속해서 ping과 같은 요청을 서버 1과 서버 2에 보낸다. 이 서버들은 각각 클라이언트의 ping에 대한 클라이언트의 public UDP 종점(서버에서 보는 클라이언트의 IP 주소와 UDP 포트 번호)을 담은 답변을 회신한다. 만약 두 서버가 모두 같은 public 종점을 클라이언트에게 보고하면, NAT Check NAT가 클라이언트의 private 종점에 대한 정체성을 적절하게 보존하고 있다고 여긴다. 믿을 만한 UDP 홀 펀칭을 위한 이 주요 선행조건이 만족된다고 말이다.

 

 

 

 

 

Figure 8: NAT Check Test Method for UDP

 

 

 

서버 2 UDP 요청을 클라이언트로부터 받았을 때, 클라이언트에게 직접 회신하는 것과 함께 해당 요청을 서버 3에게 보낸다. 이 다음에 서버 3은 자신의 IP 주소로부터 클라이언트로 회신을 한다. 만약 NAT의 방화벽이 제대로 각각의 세션에 대해서 "요청되지 않은" 내부로 향하는 트래픽을 차단한다면, 비록 이 답신이 서버 1과 서버 2에서 오는 답신과 똑같은 public 포트로 향한다고 하더라도 클라이언트 절대 서버 3으로부터의 답신을 볼 수가 없다.

 

NAT hairpin 변환을 지원하는지 테스트하기 위해서, 클라이언트는 간단히 두 번째 UDP 소켓을 다른 포트번호로 열고 이를 사용해서 클라이언트의 첫 번째 UDP 소켓의 public 종점 (서버 2가 알려준) 으로 향해 메시지를 보낸다. 만약 이 메시지가 클라이언트의 첫 번째 private 종점에 도착하면 이는 NAT hairpin 변환을 지원한다는 것이다.

 

 

 

6.1.2 TCP Test

 

TCP 테스트도 UDP와 비슷한 형식을 취한다. 클라이언트는 하나의 로컬 TCP 포트를 이용하여 서버 1  2에게 밖으로 향하는 세션을 시작한다. 그리고 서버 1 2에서 보고되는 public 종점들이 같은지를 확인한다. 이는 믿을 만한 TCP 홀 펀칭을 위한 첫 번째 선행 조건이다.

 

이 조건을 만족한다고 하여도 안으로 향하는 요청되지 않은 연결 시도에 대해 NAT가 반응하는 방식이 TCP 홀 펀칭의 속도와 신뢰성에 영향을 미치기 때문에, NAT Check는 이 또한 테스트한다. 서버 2가 클라이언트의 요청을 받았을 때, 클라이언트로 바로 답변을 보내는 대신에 이 요청을 서버 3에게 전달하고 서버 3 "이제 출발" 신호를 보낼 때까지 기다린다. 서버 3은 이 전달된 요청을 받으면 클라이언트의 public TCP 종점을 향해서 안으로 향하는 연결을 시도 하고 이 연결이 성공하거나 실패할 때까지 5초간 기다린다. 이후 서버 2에게 "이제 출발"이라는 신호를 보내고 최대 20초까지 다시 기다리기를 계속한다. 클라이언트가 마침내 서버 2의 답변(서버 2가 서버 3 "이제 출발" 신호를 기다리며 지연시킨) 을 받게 되면, 클라이언트는 서버 3에게 밖으로 향하는 연결을 시도하고 이는 결과적으로 서버 3과의 simultaneous TCP oepn을 발생시킨다.

 

이 테스트 과정 중에 어떤 일이 일어나는지는 다음에 나오는 NAT의 방식에 따라 달라진다. 만약 NAT가 제대로 서버 3의 안으로 향하는 "요청되지 않은" SYN 패킷을 버려준다면, 서버 2가 클라이언트에게 답신하기 전의 5초 동안에 클라이언트의 listen 소켓에서는 아무 일도 일어나지 않는다. 클라이언트가 마침내 서버 3으로 향하는 자신만의 연결을 시작했을 때, NAT를 통하는 구멍을 열면서 이 연결 시도는 바로 성공하게 된다. 이와 달리, 만약 NAT가 서버 3 SYN을 버리지 않고 그냥 통과시켜주면 (홀 펀칭을 위해선 좋지만 보안을 위해서는 이상적이지 않다.), 서버 2의 답신을 받기 전에 클라이언트는 자신의 listen 소켓에서 이 안으로 향하는 TCP 연결을 받게 된다. 끝으로, 만약 NAT가 적극적으로 서버 3 SYN TCP RST 패킷들을 보내면서 거부하게 되면, 서버 3은 포기를 하고 클라이언트의 이어지는 서버 3으로의 연결시도도 실패한다.

 

TCP 경우의 hairpin 변환을 테스트하기 위해, UDP의 경우와 동일한 방법으로 클라이언트는 두 번째 local TCP port를 이용해서 자신의 첫 번째 TCP 포트에 해당하는 public 종점을 향해 연결을 시도한다.

 

 

 

6.2 Test Results

 

우리가 모은 NAT Check 데이터는 68개의 다른 벤더들로부터 나온 다양한 NAT들을 포함하는 380개의 리포트로 구성되어있다. 또한 NAT 기능들은 여러 다른 버전의 8개의 인기 있는 운영체제에 구현되어있다. 오직 이중에 335개의 리포트만 UDP hairpin 변환에 대한 결과를 포함하고 있고 286개만이 TCP를 위한 결과를 가지고 있다. 이는 우리가 이미 결과들을 모으기 시작한 이후에 이 기능을 NAT Check의 후기 버전에서 구현했기 때문이다. 데이터 결과는 NAT 벤더 별로 테이블 1에 정리되어있다. 이 테이블은 최소 5개 이상의 데이터가 유효한 개개의 벤더들만 보여준다. 이 결과에서 주어진 하나의 벤더에 대한 다양성은 여러 가지 요소들로부터 설명될 수 있다. 예를 들어, 같은 벤더의 다른 NAT 디바이스 또는 다른 상용 제품 라인, 같은 NAT 구현에 다른 소프트웨어 혹은 다른 펌웨어 버전, 다른 구성, 그리고 혹은 가끔 일어나는 NAT Check의 테스트, 보고 에러 등이 이런 요소들이다.

 

 

 

 

 

Table 1: User Reports of NAT Support for UDP and TCP Hole Punching

 

 

 

UDP에 해당하는 전체 380개의 데이터 중 310(82%)의 경우에서 NAT가 일관적으로 클라이언트의 private 종점을 변환하는 것으로 나타났다. UDP 홀 펀칭과의 기본 호환성을 보여주는 것이다. 하지만 이에 비해 hairpin 변환의 경우 일반성이 매우 떨어지는데, UDP hairpin 변환의 결과를 포함하는 335개의 데이터 중에 오직 80 (24%) 만이 hairpin 변환을 지원한다고 나왔다.

 

TCP에 해당하는 전체 286개의 데이터 중에 184 (64%) TCP 홀 펀칭과의 호환성을 보여준다. 다시 말해서 NAT가 일관적으로 클라이언트의 private TCP 종점을 변환하고 안으로 향하는 요청되지 않은 연결에 대해서 RST 패킷을 회신하지 않는 다는 것이다. Hairpin 변환은 역시 일반성이 많이 떨어지는데 오직 37 (13%) 결과만이 TCP에 대한 hairpin 지원을 한다고 나왔다.

 

이 결과들은 지원자들의 "스스로 선택한" 커뮤니티에 의해서 만들어졌기 때문에, 이들은 랜덤한 샘플들로 이루어져 있지도 않고 따라서 일반적으로 쓰이는 NAT들의 분포 상황을 꼭 맞게 보여주지도 않는다. 그럼에도 불구하고 이 결과들은 고무적이다. 대다수의 일반적으로 출시된 NAT들이 이미 적어도 싱글 NAT 레벨의 경우에 UDP TCP 홀 펀칭을 지원하는 것으로 나타나기 때문이다.

 

 

 

6.3 Testing Limitations

 

NAT Check의 현재 테스트 프로토콜은 어떤 경우에 결과를 잘못 이끌 수 있는 몇 가지 한계점을 가지고 있다. 먼저 우리는 최근에 몇몇 NAT 구현들이 알지도 못하는 어플리케이션의 메시지에서 맹목적으로 IP 주소를 변환한다는 것을 알게 되었다. 현재 NAT Check 프로토콜은 전달하는 IP 주소를 암호화하지 않으므로 이런 현상으로부터 보호되지 않는다.

 

둘째로, NAT Check의 현재 hairpin 변환 체크는 불필요하게 부정적인 결과를 초래할 수도 있다. 이는 이 테스트를 위해 완전한 양방향 홀 펀칭 방식을 이용하지 않기 때문이다. 현재 NAT Check hairpin 변환을 지원하는 NAT NAT public 쪽에서 오는 안으로 향하는 연결을 차단하는 것과 다르게 자신의 private 네트워크에서 오는 hairpin 연결은 차단하지 않는다고 가정한다. 왜냐하면 이런 차단은 보완을 위해 불필요하기 때문이다. 하지만 나중에 우리는 NAT가 단순히 모든 자신의 public 포트로 향하는 트래픽을 이것의 진원지와는 상관없이 "믿을 수 없는" 것으로 여길지도 모른다는 것을 알게 되었다. 우리는 아직 어떤 방식이 보다 일반적인지 모른다.

 

끝으로, NAT에 클라이언트가 오직 하나만 있을 때는 클라이언트의 private 종점을 특정한 private 포트로 일관적으로 변환하지만, 만약 두 개 혹은 그 이상의 클라이언트들이 private 네트워크에서 다른 IP 주소를 가지고 같은 private 포트 번호로 NAT를 통해서 통신하려고 할 때는 symmetric NAT 혹은 이 보다 더 안 좋은 방식으로 바뀌는 NAT 구현들이 존재한다. NAT Check는 사용자에게 두 개 혹은 그 이상의 클라이언트 호스트를 NAT 뒤에서 동시에 돌릴 것을 요구해야만 이런 방식을 감지할 수 있다. 하지만 NAT뒤에 하나의 사용 가능한 PC만 가지고 있는 사용자에게는 불가능한 일이다. 그럼에도 불구하고, 우리는 이 실험 기능을 미래의 NAT Check 버전에 옵션으로 구현할 계획을 하고 있다.

 

 

 

6.4 Corroboration of Results

 

위에서 나온 것과 같은 테스트의 어려움에도 불구하고, 우리의 결과들은 전체적으로 큰 ISP에 의해서 보강되었다.  ISP는 그들의 네트워크에 86% NAT를 차지하고 있는 3개의 라우터 NAT 벤더들이 모두 UDP 홀 펀칭과 호환이 되는 NAT들을 생산하고 있다는 것을 발견했다 [25]. UDP 종속적인 STUN 프로토콜 [12]과 이것의 TCP 확장 버전인 STUNT [8,9] 를 사용하여 추가로 얻어진 독립적인 결과들 또한 우리의 결과와 일치하는 것으로 드러났다. 이런 후기 연구들은 NAT Check가 하듯이 단순히 기본적인 홀 펀칭 호환성만을 테스트하는 대신에, 보다 폭 넓게 다양한 방식들을 하나하나 테스트하여, 각각의 NAT에 대해 보다 많은 정보를 제공한다. 하지만 이렇게 확장된 테스트들은 다수의 클라이언트들이 NAT뒤에서 협력해야 하고 따라서 실행되기가 어렵기 때문에, 현재까지는 보다 제한된 NAT들에 대해서만 이 결과들이 유효하다.

 

 

 

 

 

7. Related Work

 

UDP 홀 펀칭은 최초로 Dan Kegel [13] 에 의해서 조사되고 공개적으로 문서화되었으며 이는 지금까지 P2P 어플리케이션 커뮤니티에 널리 알려져 있다. UDP 홀 펀칭의 중요한 면들 역시 여러 실험적인 프로토콜들의 명세서에서 간접적으로 문서화가 되어왔다. 이런 프로토콜들로는 STUN[19], ICE [17] 그리고 Teredo [11] 등이 있다. 하지만 우리는 홀 펀칭을 완전히 분석하거나 다중 레벨 NAT (섹션 3.5)에서의 hairpin 변환 문제 등을 지적하는 공개된 작업 물은 지금까지 없는 것으로 안다.

 

또한 우리 이전에 TCP 홀 펀칭을 여기서 설명한 것과 같이 symmetric 방식으로 개발한 작업 물도 없는 것으로 알고 있다. 심지어 버클리 소켓 API에 있는 SO_REUSEADDR/SO_REUSEPORT 옵션들의 중요성조차도 P2P 어플리케이션 개발자들 사이에서 거의 알려지지 않았다. NatTrav [4] 가 비슷하지만 이는 앞서 섹션 4.5에서 언급한 asymmetric TCP 홀 펀칭을 구현했다. NUTSS [9]  NATBLASTER [1] 는 섹션 5에서 언급 된 나쁜 NAT 작동 방식에 대해서도 동작이 되는 보다 복잡한 TCP 홀 펀칭을 구현했다. 하지만 이들은 랑데뷰 서버가 소스 IP 주소들을 속여야 하며 또한 클라이언트 어플리케이션들이 "생짜(raw)" 소켓들을 만져야 한다. 이는 대개 오직 루트나 관리자의 특권이 있어야 가능한 것이다.

 

SOCKS [14], UPnP [26], 그리고 MIDCOM [22] 과 같은 프로토콜들은 명시적으로 NAT와 협동을 통해서 어플리케이션들이 NAT를 선회할 수 있게 해준다. 하지만 이 프로토콜들은 NAT 벤더들에 의해 일관적 혹은 널리 지원되지 않고 있다. 또한 증가하고 있는 다중 레벨 NAT 경우를 해결하는 것으로 보이지 않는다. 명시적으로 NAT를 조정하는 것은 어플리케이션에게 NAT를 찾고, 아마 스스로 인증 할 것을 요구할 것이다. 이는 대개 사용자 구성과 연관되어있다. 반대로 홀 펀칭이 작동하는 경우에는, 아무런 사용자 간섭 없이 작동한다.

 

HIP [15]  FARA [2] 같은 최근의 제안들은 호스트의 주체성(identity)과 위치의 연관성을 끊는 방식으로 인터넷의 기본 구조를 확장한다 [20]. IPNL [7], UIP [5,6] 그리고 DOA [27] 는 이런 식의 구조에서 NAT를 가로지르는 방식을 제안한다. 길게 보면 아마 이런 확장성들이 필요할 것이지만, 홀 펀칭은 지금 현존하는 네트워크 기반에서 어떤 프로토콜 스택의 업그레이드 없이 어플리케이션이 작동하게 해준다. 그리고 어플리케이션이 정의하는 "호스트 주체성(identity)"이라는 개념을 남겨둔다.

 

 

 

8. Conclusion

 

홀 펀칭은 NAT가 존재할 때 P2P 연결을 구성하는 다목적의 기술이다. 연관된 NAT가 요구되는 몇 개의 방식만 따라준다면, 홀 펀칭은 TCP UDP 모두의 경우에 일관적이고 튼실하게 작동한다. 그리고 어떤 특별한 권한이나 특수한 네트워크 망과 상관없이 일반적인 어플리케이션들에 의해 구현될 수 있다. 홀 펀칭은 완전한 투명성을 유지하는데 이는 가장 중요한 NAT의 특징과 장점들 중에 하나다. 또한 다중 레벨의 NAT에서도 작동한다. (비록 몇몇 문제 상황이 아직 널리 구현되어있지 않은 NAT의 특성인 hairpin 변환을 요구하지만.)

 

 

 

Acknowledgments

 

우리 작가들은 섹션 6에 나오는 결과들을 모으는데 중요한 지원을 해준 Dave Andersen에게 감사를 전한다. 또한 Henrik Nordstrom, Christian Huitema, Justin Uberti, Mema Roussopoulos 그리고 익명의 USENIX 리뷰어들에게도 이 문서의 초본에 대해 값진 피드백을 준 것에 감사한다. 끝으로 NAT Check를 사용해서 우리에게 결과를 보내준 많은 지원자들 모두에게 감사한다.

 

 

 

Bibliography

 

1. Andrew Biggadike, Daniel Ferullo, Geoffrey Wilson, and Adrian Perrig.

 

NATBLASTER: Establishing TCP connections between hosts behind NATs.

 

In ACM SIGCOMM Asia Workshop, Beijing, China, April 2005.

 

2. David Clark, Robert Braden, Aaron Falk, and Venkata Pingali.

 

FARA: Reorganizing the addressing architecture.

 

In ACM SIGCOMM FDNA Workshop, August 2003.

 

3. S. Deering and R. Hinden.

 

Internet protocol, version 6 (IPv6) specification, December 1998.

 

RFC 2460.

 

4. Jeffrey L. Eppinger.

 

TCP connections for P2P apps: A software approach to solving the NAT problem.

 

Technical Report CMU-ISRI-05-104, Carnegie Mellon University, January 2005.

 

5. Bryan Ford.

 

Scalable Internet routing on topology-independent node identities.

 

Technical Report MIT-LCS-TR-926, MIT Laboratory for Computer Science, October 2003.

 

6. Bryan Ford.

 

Unmanaged internet protocol: Taming the edge network management crisis.

 

In Second Workshop on Hot Topics in Networks, Cambridge, MA, November 2003.

 

7. Paul Francis and Ramakrishna Gummadi.

 

IPNL: A NAT-extended Internet architecture.

 

In ACM SIGCOMM, August 2002.

 

8. Saikat Guha and Paul Francis.

 

Simple traversal of UDP through NATs and TCP too (STUNT).

 

http://nutss.gforge.cis.cornell.edu/.

 

9. Saikat Guha, Yutaka Takeday, and Paul Francis.

 

NUTSS: A SIP-based approach to UDP and TCP network connectivity.

 

In SIGCOMM 2004 Workshops, August 2004.

 

10. M. Holdrege and P. Srisuresh.

 

Protocol complications with the IP network address translator, January 2001.

 

RFC 3027.

 

11. C. Huitema.

 

Teredo: Tunneling IPv6 over UDP through NATs, March 2004.

 

Internet-Draft (Work in Progress).

 

12. C. Jennings.

 

NAT classification results using STUN, October 2004.

 

Internet-Draft (Work in Progress).

 

13. Dan Kegel.

 

NAT and peer-to-peer networking, July 1999.

 

http://www.alumni.caltech.edu/~dank/peer-nat.html.

 

14. M. Leech et al.

 

SOCKS protocol, March 1996.

 

RFC 1928.

 

15. R. Moskowitz and P. Nikander.

 

Host identity protocol architecture, April 2003.

 

Internet-Draft (Work in Progress).

 

16. NAT check.

 

http://midcom-p2p.sourceforge.net/.

 

17. J. Rosenberg.

 

Interactive connectivity establishment (ICE), October 2003.

 

Internet-Draft (Work in Progress).

 

18. J. Rosenberg, C. Huitema, and R. Mahy.

 

Traversal using relay NAT (TURN), October 2003.

 

Internet-Draft (Work in Progress).

 

19. J. Rosenberg, J. Weinberger, C. Huitema, and R. Mahy.

 

STUN - simple traversal of user datagram protocol (UDP) through network address translators (NATs), March 2003.

 

RFC 3489.

 

20. J. Saltzer.

 

On the naming and binding of network destinations.

 

In P. Ravasio et al., editor, Local Computer Networks, pages 311-317. North-Holland, Amsterdam, 1982.

 

RFC 1498.

 

21. P. Srisuresh and M. Holdrege.

 

IP network address translator (NAT) terminology and considerations, August 1999.

 

RFC 2663.

 

22. P. Srisuresh, J. Kuthan, J. Rosenberg, A. Molitor, and A. Rayhan.

 

Middlebox communication architecture and framework, August 2002.

 

RFC 3303.

 

23. Transmission control protocol, September 1981.

 

RFC 793.

 

24. G. Tsirtsis and P. Srisuresh.

 

Network address translation - protocol translation (NAT-PT), February 2000.

 

RFC 2766.

 

25. Justin Uberti.

 

E-mail on IETF MIDCOM mailing list, February 2004.

 

Message-ID: <402CEB11.1060906@aol.com>.

 

26. UPnP Forum.

 

Internet gateway device (IGD) standardized device control protocol, November 2001.

 

http://www.upnp.org/.

 

27. Michael Walfish, Jeremy Stribling, Maxwell Krohn, Hari Balakrishnan, Robert Morris, and Scott Shenker.

 

Middleboxes no longer considered harmful.

 

In USENIX Symposium on Operating Systems Design and Implementation, San Francisco, CA, December 2004.

 

 



출처: https://willeeproject.tistory.com/191 [Willee Project]

Posted by 멜데스
뻘소리2020. 11. 12. 23:52

배포받을 원본프로젝트 vs버전이 2015인것 같은데 2017로 가져왔는데 

빌드하려보니 인증이 만료되어 빌드가 되지 않았다.

어차피 프로젝트를 배포받은거라 그냥 속성 -> 서명 -> ClickOnce 매니페스트 서명

발급대상 발급자를 테스트 인증서 만들기로 내 PC로 변경했다.

이렇게 발급받은 테스트 인증서는 기한이 1년이다.

Posted by 멜데스
뻘소리2020. 11. 12. 20:17

좀 오래전 일이였는데 과거 자료 정리하다 나와서 올림.

예전에 DB 리스토어 한다고 새로 셋팅한적이 있었음.

그 당시 서버 환경이 windows server 2012 std였고 Sql server 2008 r2 였었는데

SQL 리스토어링 스튜디오에서 끝내고 DB 다 올렸는데

LMS(Local Management Server)에서 SQL에 커넥션이 안되는거임.

MS-SQL을 써본적이 없었기 때문에 분명 뭔가 옵션이 있을거다라는 생각으로 한 20분 정도 삽질을 했다.

smss를 썼었는데 다 뒤져봤는데 못찾았다. 뭐지? 하고 인터넷켜서 검색하려고 윈도우 키 눌렀는데 

시작프로그램 목록에 sql server configuration manager 프로그램이 반짝 반짝..

실행해보니 원하던거 바로 찾음.

SQL server Network Configuration -> Protocols for MSSQLSERVER -> TCP/IP 프로토콜 Disable 되어 있었음.

Enable로 바꾸고 연결됨..

근데 지금 생각해보면 이걸 모르고 DB 리스토어링을 어떻게 한거지? 서비스 관리를 여기서 하는데 오래전 기억이다보니 몇몇 부분이 왜곡이 된것 같기도 하다.

그 이후로 뭔가를 설치하면 설치한 프로그램의 루트 디렉터리부터 구조를 먼저 한번 쑥 훑어보는 습관이 여기서 시작됬던것 같다.

해치웠나... 외부에서 Register 하는 순간 Network Exception Failed 뜨는거임. 어림도 없지 ㅋㅋ

근데 생각해보니 방화벽을 건든적이 없는데 당연히 외부에서 접속 안되지. 

방화벽이라는걸 염두해두고 런처 디버깅을 해보니 sql open에서 10초간 대기타다 exception으로 빠지는순간

추측이 확신으로 포트 개방하고 namedpipe Enable 하고 다시 register해보니

webaccount 테이블로 접근이 됬다.

물어봐도 됬지만 바쁘기도 했고 모른다고도 말을 하지 않은 무모했던 자신이 기억난다. 

예상보다 빨리 끝내 자연스럽게 흘러가서 그냥 평온한 일상처럼 보였지만 나 혼자만의 해프닝이였다.

Posted by 멜데스
뻘소리2020. 6. 2. 13:12

C# 4에서는 명명된 인수와 선택적 인수가 도입되었습니다. 명명된 인수를 사용하면 인수를 매개 변수 목록 내의 매개 변수 위치가 아니라 매개 변수 이름과 연결하여 특정 매개 변수에 대한 인수를 지정할 수 있습니다. 선택적 인수를 사용하면 일부 매개 변수에 대한 인수를 생략할 수 있습니다. 두 기법 모두 메서드, 인덱서, 생성자 및 대리자에 사용할 수 있습니다.

명명된 인수와 선택적 인수를 사용하는 경우 매개 변수 목록이 아니라 인수 목록에 표시되는 순서대로 인수가 평가됩니다.

명명된 매개 변수와 선택적 매개 변수를 함께 사용하는 경우 선택적 매개 변수 목록에서 몇 개의 매개 변수에 대해서만 인수를 제공할 수 있습니다. 이 기능은 Microsoft Office 자동화 API와 같은 COM 인터페이스에 대한 호출에 큰 도움이 됩니다.

명명된 인수

명명된 인수를 사용하면 호출된 메서드의 매개 변수 목록에서 매개 변수의 순서를 기억하거나 조회할 필요가 없습니다. 각 인수에 대한 매개 변수를 매개 변수 이름으로 지정할 수 있습니다. 예를 들어 함수에 정의된 순서의 위치로 인수를 보내서 표준 방법으로 주문 세부 정보(예: 판매자 이름, 주문 번호 및 제품 이름)를 호출할 수 있습니다.

PrintOrderDetails("Gift Shop", 31, "Red Mug");

매개 변수의 순서를 기억하지 못하지만 해당 이름을 알고 있는 경우 임의의 순서로 인수를 보낼 수 있습니다.

PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop");

PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31);

또한 명명된 인수는 각 인수가 무엇을 나타내는지를 식별하여 코드의 가독성을 향상합니다. 아래 예제 메서드에서 sellerName은 null 또는 공백일 수 없습니다. sellerName  productName은 모두 문자열 형식이므로, 위치로 인수를 보내는 대신, 명명된 인수를 사용하여 두 코드를 구분하고 코드를 읽는 사람의 혼동을 줄일 수 있습니다.

위치 인수와 함께 사용된 명명된 인수는

  • 그 다음에 위치 인수가 없거나

PrintOrderDetails("Gift Shop", 31, productName: "Red Mug");

  • C# 7.2로 시작하고 올바른 위치에 사용되는 한 유효합니다. 아래 예제에서 orderNum 매개 변수는 올바른 위치에 있지만 명시적으로 명명되지 않습니다.

PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug");

class NamedExample
{
    static void Main(string[] args)
    {
        // The method can be called in the normal way, by using positional arguments.
        PrintOrderDetails("Gift Shop", 31, "Red Mug");

        // Named arguments can be supplied for the parameters in any order.
        PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop");
        PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31);

        // Named arguments mixed with positional arguments are valid
        // as long as they are used in their correct position.
        PrintOrderDetails("Gift Shop", 31, productName: "Red Mug");
        PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug");    // C# 7.2 onwards
        PrintOrderDetails("Gift Shop", orderNum: 31, "Red Mug");                   // C# 7.2 onwards

        // However, mixed arguments are invalid if used out-of-order.
        // The following statements will cause a compiler error.
        // PrintOrderDetails(productName: "Red Mug", 31, "Gift Shop");
        // PrintOrderDetails(31, sellerName: "Gift Shop", "Red Mug");
        // PrintOrderDetails(31, "Red Mug", sellerName: "Gift Shop");
    }

    static void PrintOrderDetails(string sellerName, int orderNum, string productName)
    {
        if (string.IsNullOrWhiteSpace(sellerName))
        {
            throw new ArgumentException(message: "Seller name cannot be null or empty.", paramName: nameof(sellerName));
        }

        Console.WriteLine($"Seller: {sellerName}, Order #: {orderNum}, Product: {productName}");
    }
}

 

선택적 인수

메서드, 생성자, 인덱서 또는 대리자의 정의에서 해당 매개 변수를 필수 또는 선택 사항으로 지정할 수 있습니다. 호출 시 모든 필수 매개 변수에 대한 인수를 제공해야 하지만 선택적 매개 변수에 대한 인수는 생략할 수 있습니다.

각 선택적 매개 변수에는 해당 정의의 일부로 기본값이 있습니다. 해당 매개 변수에 대한 인수가 전송되지 않은 경우 기본값이 사용됩니다. 기본값은 다음 유형의 식 중 하나여야 합니다.

  • 상수 식

  • new ValType() 형태의 식. 여기서 ValType은 enum 또는 struct와 같은 값 형식입니다.

  • default(ValType) 형태의 식. 여기서 ValType은 값 형식입니다.

선택적 매개 변수는 매개 변수 목록의 끝에서 모든 필수 매개 변수 다음에 정의됩니다. 호출자가 연속된 선택적 매개 변수 중 하나에 대한 인수를 제공하는 경우 이전의 모든 선택적 매개 변수에 대한 인수를 제공해야 합니다. 인수 목록에서 쉼표로 구분된 간격은 지원되지 않습니다. 예를 들어 다음 코드에서 인스턴스 메서드 ExampleMethod는 필수 매개 변수 하나와 선택적 매개 변수 두 개로 정의됩니다.

namespace OptionalNamespace
{
    class OptionalExample
    {
        static void Main(string[] args)
        {
            // Instance anExample does not send an argument for the constructor's
            // optional parameter.
            ExampleClass anExample = new ExampleClass();
            anExample.ExampleMethod(1, "One", 1);
            anExample.ExampleMethod(2, "Two");
            anExample.ExampleMethod(3);

            // Instance anotherExample sends an argument for the constructor's
            // optional parameter.
            ExampleClass anotherExample = new ExampleClass("Provided name");
            anotherExample.ExampleMethod(1, "One", 1);
            anotherExample.ExampleMethod(2, "Two");
            anotherExample.ExampleMethod(3);

            // The following statements produce compiler errors.

            // An argument must be supplied for the first parameter, and it
            // must be an integer.
            //anExample.ExampleMethod("One", 1);
            //anExample.ExampleMethod();

            // You cannot leave a gap in the provided arguments.
            //anExample.ExampleMethod(3, ,4);
            //anExample.ExampleMethod(3, 4);

            // You can use a named parameter to make the previous
            // statement work.
            anExample.ExampleMethod(3, optionalint: 4);
        }
    }

    class ExampleClass
    {
        private string _name;

        // Because the parameter for the constructor, name, has a default
        // value assigned to it, it is optional.
        public ExampleClass(string name = "Default name")
        {
            _name = name;
        }

        // The first parameter, required, has no default value assigned
        // to it. Therefore, it is not optional. Both optionalstr and
        // optionalint have default values assigned to them. They are optional.
        public void ExampleMethod(int required, string optionalstr = "default string",
            int optionalint = 10)
        {
            Console.WriteLine("{0}: {1}, {2}, and {3}.", _name, required, optionalstr,
                optionalint);
        }
    }

    // The output from this example is the following:
    // Default name: 1, One, and 1.
    // Default name: 2, Two, and 10.
    // Default name: 3, default string, and 10.
    // Provided name: 1, One, and 1.
    // Provided name: 2, Two, and 10.
    // Provided name: 3, default string, and 10.
    // Default name: 3, default string, and 4.
}

COM 인터페이스

동적 개체 및 기타 향상된 기능에 대한 지원과 더불어 명명된 인수와 선택적 인수는 Office 자동화 API와 같은 COM API와의 상호 운용성을 크게 향상합니다.

예를 들어 AutoFormatMicrosoft Office ExcelRange 인터페이스의 메서드에는 모두 선택 사항인 매개 변수 7개가 있습니다. 해당 매개 변수는 다음 그림에 표시됩니다.

C# 3.0 및 이전 버전에서는 다음 예제와 같이 각 매개 변수에 대한 인수가 필요합니다.

// In C# 3.0 and earlier versions, you need to supply an argument for

// every parameter. The following call specifies a value for the first

// parameter, and sends a placeholder value for the other six. The

// default values are used for those parameters.

var excelApp = new Microsoft.Office.Interop.Excel.Application();

excelApp.Workbooks.Add();

excelApp.Visible = true;

var myFormat = Microsoft.Office.Interop.Excel.XlRangeAutoFormat.xlRangeAutoFormatAccounting1;

excelApp.get_Range("A1", "B4").AutoFormat(myFormat, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);

 

그러나 C# 4.0에서 도입된 명명된 인수와 선택적 인수를 사용하면 AutoFormat 호출을 훨씬 간소화할 수 있습니다. 명명된 인수와 선택적 인수를 사용하면 매개 변수의 기본값을 변경하지 않으려는 경우 선택적 매개 변수에 대한 인수를 생략할 수 있습니다. 다음 호출에서는 7개 매개 변수 중 하나에 대한 값만 지정되었습니다.

 

excelApp.Range["A1", "B4"].AutoFormat( Format: myFormat );

 

Overload Resolution

명명된 인수 및 선택적 인수를 사용하면 다음과 같은 방법으로 오버로드 확인에 영향을 줍니다.

  • 메서드, 인덱서 또는 생성자는 해당 매개 변수가 각각 선택 사항이거나 이름 또는 위치로 호출하는 문의 단일 인수에 해당하고 이 인수를 매개 변수의 형식으로 변환할 수 있는 경우 실행 후보가 됩니다.

  • 둘 이상의 인증서가 있으면 기본 설정 변환에 대한 오버로드 확인 규칙이 명시적으로 지정된 인수에 적용됩니다. 선택적 매개 변수에 대해 생략된 인수는 무시됩니다.

  • 두 후보가 똑같이 정상이라고 판단되는 경우 기본적으로 호출에서 인수가 생략된 선택적 매개 변수가 없는 후보가 설정됩니다. 이는 매개 변수가 적은 후보에 대한 오버로드 확인에서 일반적인 기본 설정의 결과입니다.

Posted by 멜데스
뻘소리2019. 12. 29. 04:10

옛날에 디제이맥스 온라인때 재밌게 했던 기억덕분에 리스펙트를 깔아보기로 했다.

컬렉션 프로필

반응형 웹 디자인 느낌이 물씬든다. 깔끔한데 탭을 넘기는 오른쪽 쉬프트키는 하도 안써서 그런지 안익숙하다..

1시간 40분정도 플레이 했는데 821개의 보상을 채우려면 몇시간이 걸릴진 모르겠다. 레벨은 쑥쑥 올라가더라

갖고있는 곡 중에 개인적으로 좋았던 곡들..

Only for you
U.A.D
메오비
방금 뜬곡인데 갓곡이였다. 20레벨찍으면 주는거같다.

-후기-

F8을 누르면 해당곡의 랭킹이 나온다.

354154로 랭킹이 31위로 되있긴한데 한칸올려보니
????

랭킹은 딱히 의미가 없다는걸 깨달았다 ㅋㅋㅋ

Posted by 멜데스
뻘소리2019. 11. 24. 04:38

Qt보다 가볍고 구현, 호환성 좋은데 디자인은 이게 좀 더 어려울거같다라고 생각함

Posted by 멜데스
뻘소리2019. 11. 24. 02:06

ImGui 도킹 윈도우를 지원한다고 얼핏 들어서 도큐보고 해보려했는데

ImGuiConfigFlags_ViewportsEnable, ImGuiConfigFlags_DockingEnable이 없네?

착각인가 싶어서 imgui 예제랑 imgui.h에 있는 컨피그 값을 살펴보니 없다.

한 20분간 파일을 뒤지다가 안되서 깃헙에 가보니 브랜치로 도킹이 따로 있었넵.. Fuck

Posted by 멜데스
뻘소리2019. 11. 6. 00:55

크로스 플랫폼 배포시 Premake가 쉽고 편한데 하지만 VS노예는 CMake인건가?..

Posted by 멜데스
뻘소리2018. 1. 15. 16:11

루프문내에 적용된 로직을 좀 더 빠르게 개선한다.

보통 루프를 돌때 for문의 경우 ++1씩 인덱스를 증가시키면서 순회하는 코드가 많은데

특정 수만큼 index연산을 직접하고 ++특정수로 루프를 타면 끝날때 루프를 다시 돌건지에 대한 체크를 줄여

오버헤드를 감소시키는 방법이다. 물론 그만큼 직접 루프문에 기입할 코드가 늘어나서 약간의 코드 사이즈가 늘어난다.

그리고 성능향상되는 상황을 프로그래머가 모두 고려하기 어렵기 때문에 테스트 해봐서 성능향상이 있을 경우에만 쓰는게 좋을 것 같다.

물론 성능향상이 필요한 경우엔 디버깅 환경이 편하거나 프로파일링이 잘 구성되어있거나 코드에 대한 이해도가 높다면 환경에 맞게 SIMD 명령어들로 리팩터링하는 것도 나쁘지 않을것 같다. 그럼 또 작업이 불어나겠지만..

Posted by 멜데스
뻘소리2018. 1. 12. 18:30

방안이 3개정도 있었다.

1근데 상용프로그램을 사용해서 브랜치따서 관리를 하자니 금전적으로 타격받고..

2국가별로 ifdef로 나누자니 코드가 더러워지고..

3그렇다고 솔루션을 국가별로 나누자니 일의 효율이 엄청 떨어지고 (국가마다 빌드하면 시간도 잡아먹고)


3개를 다 커버칠 방안이 없었다.

1번 방법은 일단 제거. 금전적 타격은 없다.

3번 방법도 제거. 일의 효율이 너무 떨어져서 빌드만 하다가 작업은 못하겠다. 미국은 이거 들어가고 중국은 이거 안들어가고.. 대만은 저거 들어가고 하면

빌드도 다 따로하고 소스도 제각각이라..

2번 방법에서 좀 더 쉽게 가는 방법을 택했다.


오래전에 약간 비슷한 상황이 있었다.

사람별로 자기가 한 것만 빌드되게 할려고 ifdef 나눈적이 있었는데 딱 자기가 한 일들을 구분시켜놔야 했었던 상황이였다. (마지막까지 각자의 ifdef를 남겨놓고 실행이 되야되는 끔찍한 상황)

그때는 공용으로 쓰는 Common defines(나누는 시점은 처음이니까 아무것도 없는) 헤더파일에 자기가 작업한 내용을 #define OO작업이라고 선언하고 ifdef로 걸어서 작업을 했다. 

이 상태로 머지를 하면 결국 모든 사람들의 작업물이 define이 걸려서 결국 최종본이 실행되서 내 작업만은 볼 수가 없다.

그렇다고 업데이트 받을때마다 내껏만 남기고 다른 사람 define 지우고 빌드를 해서 볼 수도 없는 노릇이다.

그럼 커밋할때 common defines만 빼고 커밋을 하면 내 common defines는 계속 유지되니까 참조해서 써도 되긴 된다.

그렇게 할 경우엔 svn 상에서는 common defines는 빈 헤더파일로 지속되는데 작업하는 중간과정을 볼때마다 임시로 모든 작업한 common defines를 수동으로 합쳐서 결과물을 봐야한다. 그때 커밋을 해서 중간결과물을 보면 여태까지 common defines를 비우고 쓴게 의미가 없다.

그래서 svn상에서 common defines헤더파일이 사람들이 작업한 defines가 계속 올라가면서 내것만 볼려고 배치파일을 하나 만들었다.

외부에 폴더를 하나 만든 뒤  if exist commondefines헤더파일이면 그냥 스킵, else robocopy commondefines헤더파일, 외부폴더 common defines파일을 다시 프로젝트 내의 common defines로 robocopy 하는 배치파일을 만들었다.

그리고 빌드 전 이벤트에 이 배치파일을 걸어 놓고 실행시키게 했다.


간단하게 요약하면 

1. commondefines 헤더 파일에 define으로 내작업 다 쓰고 #ifdef로 실제 작업함.

2. 작업 다 끝났으니까 빌드 전 이벤트에 배치파일 걸고 빌드함. 내꺼 결과물 확인하고 외부폴더에 commondefines 헤더 파일 생김.(내 작업만 들어있음)

3. 다했으니까 업데이트 커밋검. 다른 사람들define작업물도 포함된 commondefines 헤더파일이 생김.

4. 다시 빌드해서 원래 commondefines.h로 돌리거나 외부에 내작업만 들어있는 commondefines.h에만 작업 define을 선언.(그냥 빌드했음. 작업할때 헷갈려서)

5. 또 작업끝나면 빌드해서 실행하고 내 작업만 확인한 후 업데이트 커밋. 


이랬었었다. 


여기에 착안해서 파이썬으로 국가별 선택할수 있는 다이얼로그를 하나 만들어서 빌드 때 국가별로 선택해서 빌드가 될 수 있게 하면 좋지않을까 싶다. 물론 내가 결정할수 있는 사항은 아니다...



'뻘소리' 카테고리의 다른 글

아직까진 Premake가 CMake보다 편하다..  (0) 2019.11.06
Loop Unrolling  (0) 2018.01.15
Multi/Sub Object Material  (0) 2018.01.04
플로킹  (0) 2018.01.02
TLS  (0) 2017.12.28
Posted by 멜데스