본문 바로가기
기본카테고리

홀펀칭(Hole punching)

by Quandis 2011. 4. 27.
728x90
반응형

1. 랑데뷰 서버

홀펀칭은 클라이언트 AB가 이미 랑데뷰 서버 S active UDP 세션을 가지고 있다고 가정하고 시작한다.
클라이언트가 서버 S에 등록되면, 서버는 클라이언트의 2개의 endpoint를 기록한다
.
(one : private ip/port, two : public ip/port)

클라이언트는 서버에게 자신의 private ip/port를 포함한 등록 패킷을 보내면
,
서버는 패킷에 포함된 private ip/port와 실제 UDP 통신상에서 알아낸 public ip/port 2개의 endpoint를 모두 획득하게 된다
.
만약, 클라이언트가 NAT 밑에 있지 않은, 즉 공인 IP를 쓴다면 private ip/port public ip/port와 일치하게 된다.

 

 

2. P2P 세션 연결 시나리오

 

클라이언트 AB와 직접 UDP 세션을 열고 싶어한다고 가정해 보자. 홀펀칭은 다음과 같이 진행된다.

1.
최초 AB에게 어떻게 도달해야 하는지 모른다. 따라서 AS에게 B UDP 세션 연결을 요청한다.


2. S
A에게 B private/public endpoints 정보를 전송해 준다. 이와 동시에, SB와 연결된 UDP 세션을 통해 A private/public endpoints 정보를 포함하는 접속 요청 패킷을 전송한다.

3. A
B endpoints 정보를 S로부터 받으면 A private/public endpoints UDP 패킷을 전송하기 시작하며, 두 개의 endpoints 중 어느 쪽으로든 올바른 응답이 B로부터 오기를 대기한다
.
이와 유사하게, BA private/public endpoints 정보를 받으면, B역시 A UDP 패킷을 전송하기 시작한다
.

이제 세 가지의 네트워크 토폴로지에 대해 UDP 홀펀칭이 어떤 식으로 이루어지는 지 확인할 필요가 있다
.

1.
두 클라이언트가 같은 NAT 밑에 있는, 하나의 private network에 소속되어 있는경우
.

2.
대부분의 경우인데, 두 클라이언트가 서로 다른 NAT 밑에 있는 경우
.

3.
두 클라이언트가 2단계의 NAT 들 밑에 있는 경우 : 통상적으로 첫번째 NAT ISP의 그것이
...
두번째는 각기 사용하는 home-networking을 위한 가정용 NAT 이다
.

일반적으로 어플리케이션 스스로 물리 계층에서의 네크워크가 어떻게 구성되어 있는지 알아내기란 쉽지 않다. STUN과 같은 프로토콜이 통신 통로상에서 NAT 구성에 대한 정보를 줄수도 있으나, 이 정보는 반드시 신뢰될 만한 것이 아니며, 특히 NAT가 다단계인 경우 더더욱 그러하다
.
그럼에도 불구하고, NAT가 대체적으로 RFC를 충족하도록 구현되어 있다면, 어플리케이션 상에서 어떤 식으로 네크워크가 구성되어 있는지 몰라도 홀펀칭은 대개의 경우 잘 동작한다
.


3. Peers behind a common NAT

 

 

두 클라이언트가 같은 NAT 밑에 있는 경우 위 그림과 같이 당연하게도 외부에서 보기에는 public ip가 똑같다.

A
S UDP 세션을 연결하였고, NATA-S 연결에 62000번 포트를 할당, 맵핑하였다
.
B
S랑 세션을 연결하였고, NATB-S 연결에 62500번 포트를 할당, 맵핑하였다
.

A
S를 이용하여, B와의 UDP 세션을 연결하기 위해 홀펀칭을 시도한다고 가정해보자
.

1. A
S에게 B와의 연결 요청을 전송한다
.

2. S
A에게 B private/public endpoints 정보를, B에게 Aprivate/public endpoints 정보를 전송한다
.

3.
두 클라이언트 모두 private/public endpoints UDP 패킷을 서로에게 직접 전송하기 시작한다
.

4.
두 클라이언트 간 public ip/port로의 UDP 패킷 통신은

NAT
hairpin translation을 지원하느냐 하지 않느냐에 따라 서로에게 도착할 수도 그렇지 않을 수도 있다
.

5.
private endpoint 간 메시지는 서로 도착할 것이며, 두 클라이언트 간 다이렉트로 전송되는 것이

NAT
를 거쳐가는(through public endpoint) 것보다 대부분 빠르므로, 어플리케이션은 private endpoint 통신을 선호한다.


4. Peers behind different NATs (most case)

 

 

 

그림과 같이 AB가 서로 다른 NAT 밑에 있는 경우에 대해 알아보자.

A
B 4321 포트에서 S 1234 포트로 UDP 세션을 초기화시켰다
.
outbound 과정을 핸들링하면서 NAT-AA-S 세션에 대해 155.99.25.11:62000 포트를 할당하였고
,
NAT-B
B-S 세션에 대해 138.76.29.7:31000 포트를 할당하였다
.

1. A
S에 등록하는 과정에서, A는 자신의 private endpoint 10.0.0.1:4321(10.0.0.1 NAT-A에서의 A ip)임을 알리고
,
등록과정에서 S가 파악한 A public endpoint(155.99.25.11:62000, NAT-A가 할당한)와 함께 이를 A의 정보로 저장한다
.

2. B
역시 S에 등록하는 과정에서 자신의 private endpoint 10.1.1.3:4321임을 알리면
,
등록과정에서 S가 파악한 B public endpoint(138.76.29.7:31000)와 함께 B의 정보로 저장한다
.

3.
이제 A는 위 그림의 "The hole Punching Process"를 따라 B와 직접적인 세션을 연결한다
.

4.
우선, AS에게 B와 직접 연결을 요청한다. 이에 대한 응답으로 SA에게 B private/public endpoints에 대한 정보를, B에게 A private/public endpoints에 대한 정보를 각각 전송해 준다
.

5. A
B는 서로의 two endpoints에 대해 직접적으로 UDP 패킷을 전송하기 시작한다
.

6. A
의 첫 메시지가 B public endpoint로 발송되었다고 가정해보자
.

이 메시지는 NAT-A를 통과하기 때문에, NAT-A는 이 패킷이 새로운 세션(A-B)에 대한 첫 UDP 패킷임을 인지한다
.
새로운 세션의 source-endpoint(10.0.0.1:4321)A-S 연결의 source endpoint같지만, destinationA-S의 그것과 다르다.


여기에서 NAT type 별 양상이 달라지는 데 간단하게 정리하면 다음과 같다.

7.1 Full cone:
한 번내부(private endpoint)에서패킷이 전송된 적이 있는 source(public) endpoint로는 외부에서도 자유롭게 패킷을 전송할 수 있다
.

7.2 Restricted cone :
한 번 내부에서 destination ip로 패킷이 전송된 적이 있는source endpoint로는 해당 destination ip로부터의 패킷을 전송 받을 수 있다
.

7.3 Port restricted cone :
한 번 내부에서 destination ip:port로 패킷이 전송된 적이 있는 source endpoint로는 해당 destination ip:port로부터의 패킷을 전송 받을 수 있다
.

7.4 Symmetric cone :
내부에서 외부로 패킷이 나갈 때 destination이 달라질 때마다 NAT가 할당하는 포트가 바뀐다.

예를 들어 1.1.1.1:1000에서 222.222.222.222:1234로 패킷이 나갈 때 NAT20000번을 할당한다면...
1.1.1.1:1000
에서111.111.111.111:4321로 패킷이 나갈 땐private endpoint가 같을 지언정,NAT다른 포트를 할당하여 결과적으로 public endpoint가 달라지게 된다.


8.A
B 모두 full cone이 아니라고 가정해 보자.

만약, B->A로의 첫 패킷이 NAT-B를 통과하지 전에,A의 패킷이 NAT-B에 먼저 도착하면
,
NAT-B
A incoming message에 대해 unsolicited message라고 판단하여 버려(drop) 버린다
.

이 후 A->B, B->A 1번씩은 서로 cross하게 NATs를 통해 전달되고 난 이후에야
,
A
B는 직접적으로 열린 hole을 통해 UDP 통신이 정상적으로 이루어 진다
.

 

5. Peers behind multiple levels of NATs

 

 

NAT-C는 소수의 ip 주소로 수많은 사용자들을 받아들이기 위한, ISP에 의해 설치된 거대한 산업용 NAT이라고 치고, NAT-A NAT-B NAT-C로부터 할당받은 ip를 소규모 단위로, 홈 네트워크 구성 등의 이유로 쓰이는 NATs라고 치자.

이 상황에서 서버 S NAT-C만이 public ip를 가지고 있다
.
, NAT-A NAT-B에서 쓰이는 "public" ip실제로 NAT-C 하에서 private 하며
,
private NAT-A NAT-B 밑에 A B가 놓여있다
.

이제 AB가 홀펀칭을 통해서 직접 P2P 연결 구성을 시도한다고 해보자
.
아마 최적의 라우팅 방식은A NAT-B에 있는B "semi-public" 주소, 10.0.1.2:55000으로 메세지를 보내고, B 역시 NAT-A에 있는 A "semin-public" 주소, 10.0.1.2:45000 으로 보내는 방식일 것이다
.

하지만 불행히도, AB가 이 주소를 알 방법은 없다
.
왜냐하면 서버 S는 오직 클라이언트의 진정한 전역 public 주소만 보기 때문이다
.
또한 AB가 이 주소들을 어떻게든 알았다 하여도 이들이 사용 가능할지는 미지수이다
.
이는 ISP prviate 주소 세계의 IP주소 할당과 클라이언트의 private 세계에서의 IP 주소 할당이 충돌할 수 있기 때문이다
.

예를 들어서, NAT-C에서 NAT-A로 할당한 IP주소가 NAT-B에서 B로 할당한 주소인 10.1.1.3 일지도 모르는 것이다.

이러면 B는 자기 자신에게 메세지를 보내는 결과가 나와버린다.


따라서, P2P 통신을 위해 클라이언트들은S에게 보여지는 그들의 전역 public 주소를 이용하는 수 밖에는 없다.
그리고 이는 NAT-C에서 제공하는 hairpin 혹은 loopback 변환에 의존해야한다
.

1. A
B public endpoint(155.99.25.11:62005)으로 UDP 패킷을 보낼 때

NAT-A
는 먼저 패킷의 source endpoint10.0.0.1:4321 -> 10.0.1.1:45000으로 변환한다
.

2.
이 패킷은 NAT-C에 도착하고 이 패킷의 destination NAT-C의 변환된 public endpoints 중에 하나라는 것을 알아차린다
.

3.
만약 NAT-C가 잘 동작한다면, 패킷의 source endpoint destination을 모두 변환하고 이 패킷을 "돌려서" private 네트워크로 보낸다
.

4.
이제 패킷의 source endpoint155.99.25.11:62000이고 destination 10.0.1.2:55000 이다
.

5.B
private 네트워크로 이 패킷이 들어가면서 NAT-B가 이 패킷의 도착 주소를 변환, B에 도착하게 된다
.


위의 procedure NAT hairpin을 지원할 때 가능한 시나리오이다
.
조사에 의하면 거의 대부분의 NAT hairpin을 지원하지 않는다고 한다. (특히 가정용은 절망적인
...)
따라서 사실상 거의 대부분의 multiple level NAT하에 있는 peer-peer 통신은 불가능한 경우가 많다
.


6.UDP Idle timeout

 

NAT하에서 어플리케이션과 관계없이 UDP 통신은 타임 아웃이 있다.
이 타임 아웃은 일정 기간 서로 통신이 없으면 hole을 닫아버리는 것을 의미한다
.

불행하게도 이 타임 아웃에 대한 기준은 없으며, NAT 별로 다 다르다
.
심지어 어떤 NAT 3-4초 동안만 통신이 없어도 hole 이 닫혀버려 다시 홀펀칭을 해야할 수도 있다
.

따라서,서로 홀펀칭된 것을 유지하려면 열려 있는 세션들끼리의 최소한의 keep-alive는 보장이 되어야 한다
.
UDP
를 빈번한 패킷 전송에 사용하는 경우에라도 어느 정도통신이 없는 경우를 대비한 하트비팅이 필요할 듯 하다.

 

댓글