발생

마인크래프트 영상을 보다가 예전에 친구들이랑 게임하다가 포트 포워딩 해서 서버를 매번 여는게 엄청 귀찮았던 기억이 있었는데 요즘 같으면 더 간편한 방법이 있지 않았을까 싶어서 찾아봤다가 알게된 사실을 정리해본다.

포트 포워딩은 왜 귀찮았을까?

예전에 내가 로컬에 버킷 서버라는 마인크래프트 서버를 여는 프로그램을 실행해서 운영을 했었는데 버킷 서버를 여는거 까지는 그다지 어렵지 않았다. 근데 외부에서 내 서버로 접속을 시키기 위해서는 내 서버가 열린 주소와 포트로 외부에서 요청 및 응답을 할 수 있도록 만들었어야 했는데 이때 포트 포워딩이 필요했었다.

외부에서는 공유기의 공인 IP만 보이기 때문에 공유기가 어떤 내부 PC로 요청을 전달할지 규칙을 직접 지정해야 한다. 서버 PC의 사설 IP가 바뀌거나 공유기가 바뀌면 이 규칙도 다시 설정해야 해서 번거로웠다.

flowchart LR
    Friend[친구의 게임 클라이언트] -->|공인 IP:25565로 접속| Router

    subgraph Home[내 집 네트워크]
        Router[공유기
공인 IP:203.0.113.10]
        Rule[포트 포워딩 규칙
TCP 25565 → 192.168.0.10:25565]
        Server[내 PC의 마인크래프트 서버
사설 IP:192.168.0.10
포트:25565]

        Router -->|외부 요청 수신| Rule
        Rule -->|목적지 주소·포트 변환| Server
        Server -->|응답| Router
    end

    Router -->|공인 IP에서 응답 전송| Friend

대충 그 당시 포트 포워딩을 하던 구조는 위와 같았고 이 포트 포워딩 규칙을 집에 있는 공유기 설정 페이지에 들어가서 조작 했어야 하는데 어렸을 때 공유기의 설정 페이지에 접속하는 관리자 계정을 알 수 있는 방법도 없고 억지로 들어갈 수 있는 방법도 잘 모르고 정확한 원리도 몰라서 포트 포워딩에 실패하는 경우도 많았었다. 이 포트 포워딩 하던 과정만 해결하면 PC의 인바운드 아웃바운드 설정은 그다지 어렵지 않게 설정해서 진행했었는데 그 당시에는 부모님이 통신사를 바꾸거나 이사를 하면 매번 공유기가 바뀌어서 다시 포트 포워딩 세팅하기가 참 번거로웠던 기억이 난다. 특히 U+ 가 굉장히 번거로웠던걸로 기억한다.

더 쉬운 방법

지금 와서 생각해보면 가장 간단한 방법은 프록시 서버로 관리하기 쉬운 클라우드 인스턴스 하나를 사서 그 서버를 통해서 접속을 하면 될 것 같은데 그 당시의 빈곤한 학생의 관점에서 가장 쉽고 돈 없이 서버를 친구들과 함께 쓸 수 있게 만드는 방법을 고민해 보았는데 그 당시 내가 자주 사용하던 하마치(LogMein Hamachi)라는 서비스를 떠올리게 되었다.

그 당시 하마치는 무료 사용자는 최대 인원 제한이 있기도 하고 성능도 그닥 좋지 않았던 기억이 있었는데 이 하마치를 간소화 시켜 제한없는 무료 형태로 사용하게 된다면 간편하게 친구들끼리 하는 간단한 서버를 만들 수 있지 않을까란 생각을 했다. 아래는 하마치를 사용하던 서버의 구조이다.

flowchart LR
    Control[하마치 중앙 서버<br/>인증 · 피어 발견 · NAT 탐색]
    Relay[하마치 릴레이 서버<br/>직접 연결 실패 시 중계]

    subgraph HostHome[서버를 여는 내 집 네트워크]
        HostPC[내 PC<br/>하마치 가상 어댑터<br/>마인크래프트 서버]
        HostNAT[공유기 NAT]
        HostPC -->|암호화된 UDP 패킷을 먼저 전송| HostNAT
    end

    subgraph FriendHome[친구의 네트워크]
        FriendNAT[친구 공유기 NAT]
        FriendPC[친구 PC<br/>하마치 가상 어댑터<br/>게임 클라이언트]
        FriendNAT -->|암호화된 UDP 패킷을 먼저 전송| FriendPC
    end

    HostPC -.->|로그인 · 상대의 외부 주소/포트 확인| Control
    FriendPC -.->|로그인 · 상대의 외부 주소/포트 확인| Control
    HostNAT <-->|UDP 홀 펀칭으로 만든 P2P 터널| FriendNAT
    HostPC -.->|직접 터널이 막힌 경우| Relay
    FriendPC -.->|릴레이 터널| Relay

두 PC 모두 먼저 바깥으로 UDP 패킷을 보내 NAT에 임시 통로를 만들고 중앙 서버는 서로가 그 통로를 찾도록 돕는다. 그래서 직접 연결에 성공하면 게임 트래픽은 중앙 서버가 아니라 두 PC 사이를 오가며 엄격한 NAT나 방화벽 때문에 실패했을 때만 릴레이 서버를 경유한다.

위 과정을 중앙 및 릴레이 서버 운영 없이 각자 로컬 프로그램만 설치해서 연결 할 수 있는 무료로 사용 가능한 프로그램을 만들 수 있을 것이라는 아이디어가 정립이 되었고 이걸 만들어보기로 했다.

P2P 와 NAT Hole Punching에 대하여

내가 만드려는 프로그램의 구조는 가장 최소한의 역할만 할 수 있도록 아래와 같이 잡았다.

flowchart LR
    subgraph Participants[참가자 PC]
        direction TB
        MinecraftA[참가자 A
마인크래프트] -->|TCP| FreeP2PA[FreeP2P]
        MinecraftB[참가자 B
마인크래프트] -->|TCP| FreeP2PB[FreeP2P]
    end

    subgraph Host[호스트 PC]
        direction TB
        HostFreeP2P[호스트 FreeP2P
UDP 포트 하나] -->|TCP| MinecraftHost[마인크래프트
LAN 서버]
    end

    FreeP2PA <-->|직접 UDP 터널| HostFreeP2P
    FreeP2PB <-->|직접 UDP 터널| HostFreeP2P

구조에서 보이는 직접 UDP 터널 저 부분이 핵심이다. 해당 부분에서 NAT Hole Punching이 이용되는건데 이 NAT Hole Punching이 P2P 연결의 핵심이라고 보면된다.

원리

P2P 연결의 이론적인 배경인 NAT Hole Punching의 기초적인 구조는 대략 3가지로 나눠서 설명 할 수 있다.

  1. 클라이언트
  2. 공유기
  3. STUN 서버