플라네타리움 엔지니어링 스낵

예제로 보는 TURN

(한국어English)

안녕하세요. 플라네타리움 개발팀 문성원입니다. 지난 시간에 NAT 통과 기법에 대해서 소개해 드렸었는데요. 오늘은 그중에서 TURN을 살펴볼까 합니다.

“Traversal Using Relays around NAT”라는 이름에서 알 수 있듯이 TURN은 공인 IP를 가진 서버가 통신을 원하는 단말들을 중계하는 방식을 일컫습니다. 단순히 기능이나 순서를 나열하는 것으론 이해가 어려울 수 있으니, 저희가 만들고 있는 Libplanet이 실제로 TURN을 사용하는 방식을 예로 설명하도록 하겠습니다.

블록 체인과 NAT

Libplanet은 블록체인 기술을 게임 개발에 쉽게 사용할 수 있게끔 하는 라이브러리입니다. 많은 블록체인 구현체들은 분산된 노드(node)간의 통신에 P2P 형태의 네트워크를 사용하며, 이는 저희 Libplanet도 마찬가지입니다. 다만 다른 블록체인 구현체들과는 다르게, Libplanet을 통해 블록체인이 구현되는 애플리케이션은 게임입니다. 일반적으로 이러한 게임은 개인용 컴퓨터나 거치형 콘솔, 그리고 스마트폰과 같은 개인용 장비에서 실행되는데, 이런 장비의 대부분은 NAT로 구성된 네트워크 위에서 별도의 공인 IP를 가지지 않습니다. 즉 이러한 장비에서 실행되는 애플리케이션이 P2P 통신을 하려면 NAT를 통과하지 않을 수 없습니다.

이때문에 Libplanet은 0.2.0부터 TURN을 이용한 릴레이를 지원합니다. 바로 다음과 같은 과정을 통해서요.

포트 할당

TURN을 통한 릴레이의 첫 단계는 포트 할당이라고 알려진 단계입니다. NAT 바깥에서 중계를 원하는 노드는 (필요하다면 적절한 인증 정보와 함께) 포트 할당 요청(allocation request)을 TURN 서버로 보냅니다. 적당히 간추리면 대충 이런 내용이겠죠.

노드: 공인 IP와 포트를 통해 중계를 받고 싶습니다. 적당한 IP와 포트를 할당해주세요.

TURN 서버는 요청이 올바르다면 설정에 따라 적당한 IP와 포트를 골라서 연결을 받을 수 있게 열어 두고 아래와 같은 응답을 보낼 것입니다.

TURN 서버: 포트를 할당하였습니다. 앞으로 중계할 할 주소는 54.12.1.3:65002입니다. (논스: xyz)

이렇게 포트 할당을 요청한 연결은 제어 연결(control connection)로 불리며, 이후 TURN 서버와 노드의 통신에 사용됩니다. 여기서 한 가지 주의할 점이 TURN 서버가 응답에 포함하는 논스(nonce)인데요. 이 논스는 제어 연결마다 고유하며, 획득한 논스를 이 제어 연결의 모든 요청에 같이 보내줘야 합니다. (혹은 논스 불일치(stale nonce) 에러를 받고 다시 요청하는 방법도 있습니다.)

권한 요청 및 승인

이렇게 할당 받은 IP와 포트(54.12.1.3:65002)로 다른 노드가 바로 접속할 수 있으면 좋겠지만 아직 한 단계가 더 필요합니다. 바로 권한 요청인데요. 이는 포트를 할당받은 노드가 할당 받은 포트를 어떤 IP의 노드에게 접속 가능하게끔 TURN 서버에 요청하는 것입니다. 권한 요청이 되지 않은 연결은 모두 차단되는데, 이는 중계를 원하는 노드가 접속을 원하는 노드의 IP 정보를 사전에 알고 있어야 함을 뜻합니다.

하지만 실생활에서 많은 사용자는 자신의 공인 IP를 알고 있지 않거나 신경쓰지 않기 때문에, 이러한 정보를 직접 전달해야한다는 건 무척이나 어려운 일입니다. 이를 해결하기 위해서 WebRTC 같은 사용 예에서는 시그널링(signalling) 단계에서 접속할 노드의 IP 정보를 미리 알아내곤 합니다. 한편 Libplanet에서는 STUN 프로토콜을 통해 NAT 뒤에 있는 지를 확인하고, 만약 그렇다면 STUN을 통해 확인된 IP를 공인 IP로 릴레이 된 IP/포트와 함께 다른 노드에 전파합니다. 이러한 정보를 받은 노드는, 릴레이 된 정보로 접속하기에 앞서서 먼저 공인 IP를 통해 권한 요청을 하는 절차를 거칩니다.

이렇게 노드가 접속을 원하는 다른 노드의 공인 IP(10.1.1.1)을 알고 있다면, 권한 요청은 아마 아래와 같을 것입니다.

노드: 10.1.1.1에서 54.12.1.3:65002로 오는 요청을 허가해주세요.

이런 접근 권한은 300초의 유효기간(lifetime)이 지나면 만료되는데, 이를 방지하기 위해서는 권한을 요청했던 노드가 다시 TURN 서버에게 권한 요청을 해야 합니다.

연결 알림 및 새 연결 요청하기

이렇게 권한 요청 및 승인까지 끝마치고 나면, 드디어 할당 받은 IP와 포트로 승인된 다른 노드가 접속할 수 있습니다. 다른 노드가 할당된 IP와 포트를 통해 접속하면 TURN 서버는 이를 감지하여 다음과 같은 메시지를 제어 연결로 보냅니다.

TURN 서버: 10.1.1.1에서 54.12.1.3:65002로 연결 시도가 있었습니다. (연결 ID: 1234)

TURN 서버에 중계를 요청한 노드가 이 연결을 수락하려면, 제어 연결이 아닌 새로운 연결을 만들어서 TURN 서버에 요청하면 됩니다. 이 때 요청 받은 외부 연결을 구분하기 위해서 연결 알림 메시지의 연결 ID(connection ID)를 보내야 합니다.

노드: 연결 1234에 대한 데이터는 앞으로 이 연결로 전달해주세요.

이렇게 새롭게 생긴 연결을 데이터 연결(data connection)이라고 합니다. 이후 54.12.1.3:65002로 보내는 요청은 이 데이터 연결을 통해 전달되며, 해당 IP/포트에 연결된 노드에 응답하고 싶을때도 이 데이터 연결에 전송하면 TURN 서버를 거쳐서 노드에 전달됩니다.

남은 일들

실제로 응답을 제대로 보내기 위해서는, 앞서 만든 데이터 연결과 Libplanet 노드의 요청–응답용 연결을 다시 중계 할 필요가 있습니다. 이 부분은 노드 안에서만 잘 처리되면 되기 때문에 별도의 프로토콜이나 공개 표준은 정해지지 않았습니다. 만약 TURN 클라이언트가 웹 서버와 별도의 프로세스로 실행된다면 IPC, 같은 프로세스라면 스레드 간 통신 등을 이용해 이를 적절히 처리해주시면 됩니다. Libplanet에서는 별도 TCP 프록시를 로컬에서 실행하여 이를 릴레이합니다.