본문 바로가기
게임 공부/게임 개발 일지

[Gomoku] 중개 서버를 통해 NAT 뒤의 두 클라이언트를 연결시키려면 | 홀펀칭(hole punching)

by woohyeon 2020. 11. 12.
반응형

게임을 거의 완성했는데.. 네트워크 연결이 문제다. 방을 생성한 사람이 host가 되고 방에 입장하는 사람이 peer(client)가 되도록 했다. 그리고 TCP 프로토콜을 사용한다. 개발할 땐 단순히 피어가 연결하려는 상대의 IP주소와 포트를 localhost 주소와 정해놓은 포트 번호를 사용했다.

그런데 서로 다른 네트워크를 가진 호스트를 연결시키려면 중개 서버가 필요하다. 왜? 방에 입장하려는 사람이 방을 생성한 사람의 IP 주소와 포트를 모르니까. 그래서 중개 서버를 띄우고, 호스트들이 게임을 실행하면 가장 먼저 중개 서버에게 패킷을 전달하여 자신들의 IP 주소와 포트 정보를 주고, 방에 입장하는 사람은 중개 서버가 가진 IP 주소를 이용하여 입장하도록 하면 된다. 근데 또 이게 말처럼 간단하지가 않다.

요즘 대부분의 호스트(일반 PC 사용자)들은 NAT(Network Address Translation) 환경 아래 존재한다. 우리가 다른 호스트와 네트워크 통신을 하려면 전 세계에서 unique(유일)한 IP 주소가 필요하다. 유일하지 않으면 패킷이 원하지 않는 사람에게 갈 수도 있으니 이는 당연하다. 그리고 이 유일한 IP 주소를 공인IP(Public IP)라고 한다.

그런데 현재 우리가 대부분 사용하는 IP 주소는 IPv4로 32bit이며, 약 43억개 정도의 서로 다른 IP 주소를 가질 수 있다. 그러나 전 세계에서 사용하는 네트워크 기기 수가 이보다 적을까? 훨씬 많을 것이다.

하지만 이러한 공인IP가 모든 네트워크 기기 하나하나마다 필요하진 않다. 공유기가 있으니까. 공유기는 하나의 공인 IP를 가진다. (공유기가 가지는 IP가 공인 IP가 아닐 수도 있지만, 기본적인 이해를 위해 가정.) 대부분의 집에 공유기가 하나씩은 있을 것이다. 그리고 노트북, 핸드폰 등이 이 공유기의 와이파이를 사용한다. 공유기는 와이파이에 연결된 노트북, 핸드폰과 같은 장비에게 별도로 IP 주소를 하나씩 부여해준다. 이 IP 주소를 사설IP(Private IP)라 한다. 사설 IP는 공인 IP와 달리 전 세계에서 유일하지 않다. 하지만 같은 네트워크 대역(공인IP 아래) 내에선 유일해야 한다. 주로 192.168.x.x와 같은 익숙한 IP 주소들이 여기에 해당한다. 

공인 IP를 각 네트워크 장비마다 부여하기 힘들 수 있기 때문에 라우터, 공유기와 같은 장비를 통해 여러 네트워크 기기가 하나의 공인 IP를 사용하도록 한다. 따라서 다른 호스트가 해당 공인 IP로 패킷을 보내면 공유기에서 패킷을 받아 내부 네트워크 기기들 중 적절한 기기에 전달해준다. 이러한 기술을 NAT이라 한다. 그리고 이러한 이유로 공인 IP를 외부 IP, 사설 IP를 내부 IP라고도 부른다. 아래는 적절한 이해를 돕기 위해 가져온 사진이다. 그리고 궁금하다면 네이버에 '공인IP' 라고 검색해 보면 자신의 공인 IP를 보여준다. 같은 와이파이를 사용한다면 핸드폰, 노트북 등이 같은 IP 주소를 보일 것이다. 하지만 cmd에서 ipconfig을 치면 나오는 IPv4 주소는 서로 다를 것이다.

http://gotocloud.co.kr/?p=320

 

이제 다시 오목 게임으로 돌아가서, 플레이어가 생성된 방에 입장하려면 방을 생성한 사람의 사설 IP가 아닌 공인 IP를 알아야 한다. 위와 같이 서버가 하나로 고정된 경우 그냥 주어진 IP대로 접속하면 되니 문제가 없다. 하지만 각 유저가 서버(host) 역할을 하게 되면 얘기가 달라진다. 우선 대부분의 유저는 위의 서버와 달리 NAT 환경일 확률이 높다. 즉 다음과 같은 형태가 될 것이다. 각 NAT은 대략 공유기 또는 라우터라 생각하면 된다.

 

출처: 멀티 플레이어 프로그래밍 [도서]

호스트 A가 방을 생성했다. 호스트 B가 호스트 A의 공인 IP 주소를 알고 있다 치고 연결을 시도했지만 A는 연결을 받아주지 못한다. 더 자세히 말하면 호스트 A는 받아주고 싶지만 NAT A가 NAT B에 대한 정보가 없기 때문에 패킷을 사전에 폐기한다. 즉 공유기는 고마운 장비지만 이러한 경우 패킷을 차단하는 장벽이 된다. 아까 살펴 본 NAT 환경이 아닌 서버와 같은 경우는 라우터 또는 공유기와 같은 장비가 없기 때문에 서버가 직접 패킷을 받아줄 수 있다.

NAT 환경에서 이를 해결할 수 있는 방법으로 포트포워딩 또는 홀 펀칭(Hole punching)이 있다. 홀펀칭은 프로토콜에 따라 STUN(Simple Traversal UDP through NAT)라고도 불린다. 여기선 홀 펀칭에 대해서만 알아본다. 홀펀칭은 중개 서버를 통해 라우터 뒤에 있는 호스트 A와 B를 연결시켜 주는 것이다. NAT A와 NAT B 사이에 중개 서버가 개입하면 다음과 같은 형태가 된다.

 

출처: 멀티 플레이어 프로그래밍 [도서]

중개 서버(호스트 N)는 여러 단계를 거쳐 NAT B가 NAT A에 패킷을 보내는데 성공하도록 길을 뚫어 주는 것이 목표이다. 여기선 UDP 통신으로 설명한다. 최종적으로 두 호스트는 300번 포트를 통해 통신을 할 것이다.

우선 호스트 A가 서버로 데이터그램(UDP에서의 패킷을 의미) 전송한다. 데이터그램의 발신 주소는 192.168.10.2며 목적지 주소는 4.6.5.10이다. 데이터그램은 라우터(공유기)를 거치면서 발신 주소가 18.19.20.21로 변경된다. 즉 사설 IP에서 공인 IP로 변경된다. 그리고 라우터의 테이블에 데이터그램의 발신지 주소와 목적지 주소가 함께 등록된다. 여기에 등록된 목적지에서 보내는 데이터그램은 이제 라우터에서 통과시켜 준다. 즉 라우터는 이 테이블을 이용하여 수신한 패킷을 폐기할지 전달할지 결정한다.

서버가 데이터그램을 받는 데 성공하면 그 데이터그램의 발신지 주소는 A의 공인 IP 주소가 될 것이다. 그리고 이 주소를 따로 저장해둔다. 이제 호스트 B도 동일하게 서버에 데이터그램을 전송한다. 이제 서버는 A와 B의 공인 IP 주소를 가지고 있다. 여기서 만약 서버가 B에게 호스트 A의 공인 IP 주소를 데이터그램에 담아서 전송하면 호스트 B는 수신한 주소를 통해 A에게 데이터그램을 전송할 수 있을까? 할 수 있다. 하지만 A가 그것을 받는다는 보장은 하지 못한다. 그 이유는 라우터 A의 테이블엔 B의 정보가 여전히 없기 때문에 데이터그램을 수신해도 폐기 처리할 것이다.

그러면 라우터 A의 테이블에 어떻게 호스트 B의 정보를 저장할까? 아까 호스트 A가 서버에 데이터그램을 송신할 때 라우터를 거치면서 목적지 주소로 서버의 주소를 저장했다. 즉 호스트 A가 호스트 B에게 데이터그램을 전송하도록 하면 된다. B가 수신을 하지 못해도 상관없다. 라우터 A에 B의 정보를 저장하기만 하면 된다.

이를 위해 서버는 호스트 A에게 호스트 B의 공인 IP 주소가 담긴 데이터그램을 전송한다. 라우터 A의 테이블에 서버의 주소가 존재하므로 데이터그램을 수신하여 호스트 A에게 전달한다. 호스트 A는 목적지 주소를 12.12.6.5로 하여 데이터그램을 전송한다. 라우터를 거치면서 테이블엔 호스트 B의 정보가 추가된다. 이제 호스트 B가 A에게 데이터를 송신하면 라우터 A에서 통과 시켜줄 것이다.

데이터그램은 라우터 B에 도착했지만 라우터 B엔 A에 대한 정보가 없어 데이터그램을 폐기한다. 하지만 상관없다. 이미 목적은 달성했다. 이제 서버가 호스트 B에게 호스트 A의 공인 IP 주소를 보내준다. 호스트 B가 수신하면 목적지 주소를 호스트 A의 공인 IP 주소로 하여 데이터그램을 송신한다. 라우터 B를 거치며 A의 정보를 저장한다. 라우터 A에 도착한 데이터그램은 이제 호스트 A에게 전달된다. 그리고 라우터 B 또한 A가 전송한 데이터그램을 수신할 수 있다. 이후부턴 중개서버 없이 A와 B가 직접적으로 통신할 수 있게 된다.

이러한 과정들은 연결 없이 IP주소 및 포트 번호만 알면 통신이 가능한 UDP 특성 덕분에 가능하다. 하지만 TCP의 경우 과정이 더 복잡하다.. 

 

참고자료




댓글