NAT 종류에 따른 홀펀칭

 

NAT의 종류를 알고 있더라도 실제 WebRTC 에서의 홀펀칭을 대입해 보면 생각보다 복잡하고, 머릿속에서 잘 정리되지 않습니다.

이때 가장 쉽게 이해하는 법은 NAT의 매핑 테이블을 기준으로 홀펀칭을 이해하는 방법이 가장 좋다고 생각합니다.

각 NAT별 홀펀칭을 설명하기에 앞서 Peer에서 UDP 소켓을 생성하여 패킷을 전송하고 수신하는 코드를 잠시 살펴보겠습니다.

 

Java를 예제로 한 샘플 코드입니다.

import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;

public class UdpClientExample {
    public static void main(String[] args) {
        try {
            // 원격 서버 정보 설정
            InetAddress serverAddress = InetAddress.getByName("remote.server.com"); // 원격지 주소
            int serverPort = 12345; // 원격지 포트 번호

            // 데이터 전송을 위한 메시지 생성
            String message = "Hello, Server!";
            byte[] sendData = message.getBytes();

            // UDP 소켓 생성 및 데이터 전송
            DatagramSocket socket = new DatagramSocket();
            DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, serverAddress, serverPort);
            socket.send(sendPacket);
            System.out.println("데이터를 전송하였습니다: " + message);

            // 응답 수신을 위한 버퍼 생성
            byte[] receiveData = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);

            // 응답 수신 (대기)
            socket.receive(receivePacket);

            // 수신한 데이터 출력
            String receivedMessage = new String(receivePacket.getData(), 0, receivePacket.getLength());
            System.out.println("서버로부터 수신한 메시지: " + receivedMessage);

            // 소켓 닫기
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

이처럼 양쪽 peer에서 UDP 데이터를 주고 받기 위해서는 위와 같이 UDP 소켓을 생성하고, 패킷을 송수신 한다는 것을 먼저 알고 있어야 다음에 나오는 홀펀칭을 이해하는데 도움이 됩니다.

 

WebRTC 라이브러리에서 제공하는 C++ 코드는 다음과 같이 되어 있다고 ChatGPT 가 친절히 알려주네요.

// stun_port.cc

void StunPort::PrepareAddress() {
  // STUN 서버에 바인딩 요청을 보냅니다.
  SendStunBindingRequest();
}

void StunPort::SendStunBindingRequest() {
  // STUN 바인딩 요청을 생성합니다.
  std::unique_ptr<StunMessage> request = StunMessage::CreateBindingRequest();

  // STUN 서버 주소 설정 및 소켓으로 요청 전송
  rtc::SocketAddress server_addr(stun_server_address_);
  request->AddAttribute(std::make_unique<StunAttributeUsername>(username_));
  request->AddAttribute(std::make_unique<StunAttributeMessageIntegrity>(password_));

  // 요청을 UDP 소켓을 통해 서버로 보냅니다.
  SendTo(server_addr, request->ToBytes(), request->length());
}

 

 

Full Cone NAT

Full Cone NAT 매핑 테이블의 특징은 Remote IP, Remote Port 를 항상 Any로 유지합니다.

그림을 보면서 설명해 보도록 하겠습니다.

 

  • PeerA
    1. 자신의 공용 아이피와 포트를 알기 위해서 NAT를 경유하여 STUN 서버로 요청을 보냅니다.
    2. 공용 아이피와 포트 11.11.11.11:2001 정보를 시그널링 서버에게 전송하여 PeerB에게 전달합니다.
    3. PeerB의 공용 아이피와 포트(22.22.22.22:2001)로 UDP 패킷을 전송합니다. 이때 패킷의 소스 아이피와 포트는 11.11.11.11:2001 입니다.
    4. UDP 패킷이 22.22.22.22:2001 NAT를 경유하여 PeerB에게 도달합니다.
  • PeerB
    1. 자신의 공용 아이피와 포트를 알기 위해서 NAT를 경유하여 STUN 서버로 요청을 보냅니다.
    2. 공용 아이피와 포트 22.22.22.22:2001 정보를 시그널링 서버에게 전송하여 PeerA에게 전달합니다.
    3. PeerA의 공용 아이피와 포트(11.11.11.11:2001)로 UDP 패킷을 전송합니다. 이때 패킷의 소스 아이피와 포트는 22.22.22.22:2001 입니다.
    4. UDP 패킷이 11.11.11.11:2001 NAT를 경유하여 PeerA에게 도달합니다.

 

이제 PeerA ↔ PeerB 가 서로 홀펀칭이 잘되었습니다.

이후부터는 서로 UDP 패킷을 주고 받으면 됩니다.

그럼 이 상황에서 NAT(A)와 NAT(B)의 매핑 테이블은 어떻게 되어 있을까요?

 

여기서 한 가지 알아 두셔야 할 것이 있습니다.

WebRTC ICE 과정에서 PeerA가 STUN 서버로의 연결에 사용한 소켓(포트 1001)은 보통 그대로 유지됩니다.

즉, PeerA는 STUN 서버와의 연결에 사용했던 동일한 UDP 소켓(포트 1001)을 PeerB와의 통신을 위한 홀 펀칭 시도에도 재사용합니다.

ICE 연결 설정에서 새로운 소켓을 생성하지 않고 재사용 하는 이유는 NAT 테이블의 포트 매핑을 유지하기 위함입니다.

동일 소켓을 통해 패킷을 전송할 때 NAT 장치는 이전 STUN 연결에서 매핑한 공인 IP 및 포트 정보를 재사용할 가능성이 높아집니다. 이렇게 하면 NAT 환경에서 외부 피어(PeerB)와의 직접 통신이 원활하게 이루어질 수 있습니다.

 

Restricted Cone NAT

Restricted Cone NAT 매핑 테이블의 특징은 Remote IP 에는 대상이 되는 아이피를 기록하고, Remote Port 는 항상 Any로 유지합니다.

그림을 보면서 설명해 보도록 하겠습니다.

 

  • PeerA
    • (1) 자신의 공용 아이피와 포트를 알기 위해서 NAT를 경유하여 STUN 서버로 요청을 보냅니다.
    • (2) 공용 아이피와 포트 11.11.11.11:2001 정보를 시그널링 서버에게 전송하여 PeerB에게 전달합니다.
    • (3) PeerB의 공용 아이피와 포트(22.22.22.22:2001)로 UDP 패킷을 전송합니다. 이때 패킷의 소스 아이피와 포트는 11.11.11.11:2001 입니다. 앗! 22.22.22.22:2001 NAT 는 해당 패킷을 폐기시킵니다. 이유는 NAT (B) 매핑 테이블에 11.11.11.11 아이피가 등록되어 있지 않기 때문입니다.
  • PeerB
    • (1) 자신의 공용 아이피와 포트를 알기 위해서 NAT를 경유하여 STUN 서버로 요청을 보냅니다.
    • (2) 공용 아이피와 포트 22.22.22.22:2001 정보를 시그널링 서버에게 전송하여 PeerA에게 전달합니다.
    • (4) PeerA의 공용 아이피와 포트(11.11.11.11:2001)로 UDP 패킷을 전송합니다. 패킷이 PeerA로 잘 전달됩니다. 이때 패킷의 소스 아이피와 포트는 22.22.22.22:2001 입니다. 패킷이 잘 전송되는 이유는 PeerA의 3번 과정이 있었기 때문입니다. 해당 과정에서 NAT (A)에 22.22.22.22 아이피가 기록되었습니다.

 

이 상황을 놓고 봤을 때 PeerB는 PeerA는 서로 UDP 홀펀칭이 되었다고 판단 할 수 있습니다.

그 이유는 NAT (A)의 매핑 테이블에 22.22.22.22 아이피가 기록되었고, NAT (B)의 매핑 테이블에는 11.11.11.11 아이피가 기록되었기 때문입니다.

하지만 위의 상황에서 PeerA는 PeerB에게 패킷을 전송하였지만 NAT (B)가 해당 패킷을 폐기시켰습니다.

그래도 괜찮습니다.

ICE는 동일한 후보군으로 여러번 연결 요청을 재시도 합니다.

즉, PeerA에서 PeerB로 패킷 전송이 최초에는 실패했지만 재전송 메커니즘을 통해 PeerB로까지 패킷이 전달되게 됩니다.

ICE는 처음에는 동일한 후보군으로 여러 번 연결 요청을 재시도합니다. 이는 NAT가 일부 상황에서 첫 연결 시도에서는 트래픽을 차단하지만, 추가 요청에서는 허용할 가능성이 있기 때문입니다.

 

그럼 이 상황에서 NAT(A)와 NAT(B)의 매핑 테이블은 어떻게 되어 있을까요?

 

보시는 것처럼 NAT (A) 에는 22.22.22.22 아이피가 등록되어 있고, NAT (B) 에는 11.11.11.11 아이피가 등록되어 있습니다.

 

 

Port Restricted Cone NAT

Port Restricted Cone NAT 매핑 테이블의 특징은 Remote IP 에는 대상이 되는 아이피를 기록하고, Remote Port 에는 대상이 되는 포트를 기록하여 유지됩니다.

Restricted Cone NAT 와 동작하는 방식이 같으므로 자세한 내용은 생략하겠습니다.

 

단, 매핑 테이블에 Remote Port 정보가 기록 관리되고 있음을 알 수 있습니다.

 

 

Symmetric NAT

Port Restricted Cone NAT와 유사하게 동작하지만 Symmetric NAT는 연결되는 대상에 따라 외부 포트를 지정하게 됩니다.

그림을 보면서 설명해 보도록 하겠습니다.

 

  • PeerA
    • (1) 자신의 공용 아이피와 포트를 알기 위해서 NAT를 경유하여 STUN 서버로 요청을 보냅니다.
    • (2) 공용 아이피와 포트 11.11.11.11:2001 정보를 시그널링 서버에게 전송하여 PeerB에게 전달합니다.
    • (3) PeerB의 공용 아이피와 포트(22.22.22.22:2001)로 UDP 패킷을 전송합니다. 이때 패킷의 소스 아이피와 포트는 11.11.11.11:2002 입니다. 다른 NAT에서는 포트가 안 바뀌어서 전송이 되었지만 Symmetric NAT는 대상 서버가 달라졌기 때문에 새로운 포트(2002)를 할당 받아서 패킷을 전송하게 됩니다. 앗! 22.22.22.22:2001 NAT 는 해당 패킷을 폐기시킵니다. 이유는 NAT 매핑 테이블에 11.11.11.11:2002 아이피/포트가 등록되어 있지 않기 때문입니다.
  • PeerB
    • (1) 자신의 공용 아이피와 포트를 알기 위해서 NAT를 경유하여 STUN 서버로 요청을 보냅니다.
    • (2) 공용 아이피와 포트 22.22.22.22:2001 정보를 시그널링 서버에게 전송하여 PeerA에게 전달합니다.
    • (4) PeerA의 공용 아이피와 포트(11.11.11.11:2001)로 UDP 패킷을 전송합니다. 이때 패킷의 소스 아이피와 포트는 22.22.22.22:2002 입니다. 다른 NAT에서는 포트가 안 바뀌어서 전송이 되었지만 Symmetric NAT는 대상 서버가 달라졌기 때문에 새로운 포트(2002)를 할당 받아서 패킷을 전송하게 됩니다. 앗! 11.11.11.11:2001 NAT 는 해당 패킷을 폐기시킵니다. 이유는 NAT 매핑 테이블에 22.22.22.22:2002 아이피/포트가 등록되어 있지 않기 때문입니다.

 

그럼 이 상황에서 NAT(A)와 NAT(B)의 매핑 테이블은 어떻게 되어 있을까요?

 

좀 더 이해할 수 있도록 추가 설명을 드려보겠습니다.

PeerA 기준으로 다시 설명해 드리자면 PeerA 가 STUN 서버로 NAT의 공인 아이피 포트를 질의하고, 이를 PeerA가 수신합니다. 이때 PeerA 가 수신한 정보는 11.11.11.11:2001 이 되겠죠.

11.11.11.11:2001 정보를 시그널링 서버를 통해 PeerB에게 전달합니다.

그럼 PeerB는 11.11.11.11:2001로 패킷을 전송합니다. 이때 해당 패킷의 소스 정보는 22.22.22.22:2002 로 되어 있습니다.

여기가 핵심입니다. 패킷의 소스 포트가 2002로 되어 있다는 것입니다.

Symmetric NAT 매핑 테이블에 22.22.22.22:2002 정보가 기록되어 있을까요? 기록되어 있지 않습니다. 그리고 앞으로도 기록될 가능성이 제로 입니다. 그 이유는 PeerB가 PeerA에게 전달한 정보는 22.22.22.22:2001 이기 때문입니다.

Symmetric NAT 에서는 P2P 연결이 어려운 이유입니다.

 

 

WebRTC에서 홀펀칭 성공은 어떻게 판단하는가?

궁금한게 있습니다.

PeerA에서 PeerB로 패킷을 전송하게 되면 NAT 종류에 따라서 매핑 테이블이 데이터를 기록하고 관리하는 방법이 다르다는 것을 이해했습니다.

그럼 매핑 테이블에 기록된다고 해서 홀펀칭이 됐다라고 결론지으면 될까요?

그렇지 않습니다.

WebRTC 라이브러리에서는 홀펀칭 성공 여부를 다음과 같이 판단하게 됩니다.

PeerB가 PeerA의 후보군 IP와 포트로 UDP 패킷을 전송하게 되는데 이때 STUN Binding Request 패킷을 전송합니다.

PeerA가 STUN 요청을 수신할 수 있다면 PeerA는 Binding Response를 통해 응답을 보냅니다.

PeerB가 PeerA로부터 STUN Binding Response를 수신하게되면 후보군 연결이 성공했다고 판단합니다. 만약 응답을 받지 못하면 PeerA는 다른 후보군으로 홀펀칭을 재시도 합니다.

 

 

NAT 타입에 따른 WebRTC 홀펀칭에 대해서 알아보았습니다.

읽어주셔서 감사합니다.

'WebRTC' 카테고리의 다른 글

NAS에 TURN 서버 구축하기  (1) 2024.12.20
TURN 서버를 통한 Relay 통신 원리  (0) 2024.12.10
NAT 종류  (0) 2024.11.03
WebRTC 테스트를 위한 Docker 환경 구성  (0) 2024.09.22