연결

연결 #

왜 WebRTC에는 전용 ‘연결’ 하위 시스템이 필요한가요? #

대부분의 애플리케이션은 클라이언트/서버 연결을 사용합니다. 이 모델에서는 서버가 안정적인 전송 주소(IP/포트)를 가져야 하며, 클라이언트가 서버에 요청하면 서버가 응답합니다.

WebRTC는 클라이언트/서버 모델이 아니라 P2P(Peer-to-Peer) 연결을 수립합니다. P2P에서는 연결 수립의 책임이 양쪽 피어에 균등하게 분산됩니다. 이는 WebRTC에서의 전송 주소(IP/포트)를 미리 가정할 수 없고, 심지어 세션 중에도 바뀔 수 있기 때문입니다. WebRTC는 가능한 모든 정보를 수집해 두 WebRTC 에이전트 간 양방향 통신을 성사시키기 위해 많은 과정을 수행합니다.

그러나 P2P 연결을 수립하는 일은 쉽지 않습니다. 서로 다른 네트워크에 있어서 직접 연결이 불가능할 수 있습니다. 직접 연결이 가능한 상황에서도 문제가 생길 수 있습니다. 어떤 경우에는 두 클라이언트가 같은 네트워크 프로토콜(UDP <-> TCP)을 쓰지 않거나, 서로 다른 IP 버전(IPv4 <-> IPv6)을 사용할 수 있습니다.

이러한 어려움에도 불구하고, WebRTC가 제공하는 특성 덕분에 전통적인 클라이언트/서버 기술 대비 다음과 같은 장점을 얻게 됩니다.

대역폭 비용 절감 #

미디어가 피어 간에 직접 전달되므로, 미디어를 중계하는 별도 서버를 운영하거나 비용을 지불할 필요가 없습니다.

더 낮은 지연 #

직접 통신이 더 빠릅니다! 모든 트래픽을 서버를 경유해 보내야 하면 전송이 느려집니다.

안전한 종단 간(E2E) 통신 #

직접 통신은 더 안전합니다. 데이터가 서버를 경유하지 않으므로, 심지어 서버 운영자를 신뢰할 필요도 줄어듭니다.

어떻게 동작하나요? #

위 과정을 표준화한 것이 ICE(Interactive Connectivity Establishment, RFC 8445)입니다. WebRTC 이전부터 존재하던 프로토콜입니다.

ICE는 두 ICE 에이전트가 통신하기에 최적의 경로를 찾는 프로토콜입니다. 각 ICE 에이전트는 자신에 도달 가능한 방법들을 공개하는데, 이를 후보(candidate)라 부릅니다. 후보는 다른 피어가 접근할 수 있다고 예상하는 전송 주소입니다. ICE는 이 후보들 중 최적의 쌍을 선택합니다.

구체적인 ICE 동작은 이 장 후반에서 다룹니다. 먼저 ICE가 왜 필요한지, 극복해야 하는 네트워크 행태를 이해하는 것이 도움이 됩니다.

실제 네트워크의 제약 #

ICE는 현실 세계 네트워크의 제약을 극복하는 데 초점을 둡니다. 해결책을 보기 전에 문제를 먼저 짚어봅시다.

같은 네트워크가 아님 #

대부분의 경우 상대 WebRTC 에이전트는 같은 네트워크에 있지 않습니다. 일반적인 통화는 서로 다른 네트워크에 있는 두 에이전트 간에 이뤄지며, 직접 연결이 없습니다.

아래는 공용 인터넷으로 연결된 두 개의 서로 다른 네트워크를 보여줍니다. 각 네트워크에는 두 개의 호스트가 있습니다.

Two networks

같은 네트워크 내 호스트끼리는 연결이 쉽습니다. 192.168.0.1 -> 192.168.0.2 간 통신은 어렵지 않습니다! 외부 도움 없이도 가능합니다.

하지만 Router B 뒤의 호스트는 Router A 뒤의 대상에 직접 접근할 방법이 없습니다. Router A 뒤의 192.168.0.1Router B 뒤의 동일한 IP를 어떻게 구분할까요? 둘 다 사설 IP이기 때문입니다! Router B의 호스트가 Router A로 트래픽을 보낼 수는 있지만, 요청은 라우터에서 끝납니다. Router A는 어떤 호스트로 전달해야 할지 알 수 없습니다.

프로토콜 제한 #

어떤 네트워크는 UDP를 전혀 허용하지 않거나, TCP를 허용하지 않을 수 있습니다. MTU(최대 전송 단위)가 매우 낮을 수도 있습니다. 네트워크 관리자가 바꿀 수 있는 변수가 많아 통신을 어렵게 만들 수 있습니다.

방화벽/IDS 규칙 #

깊은 패킷 검사(DPI) 등 지능형 필터링을 수행하는 경우도 있습니다. 일부 관리자는 모든 패킷을 해석하려는 소프트웨어를 사용합니다. 이 소프트웨어가 WebRTC를 이해하지 못하면, 화이트리스트에 없는 임의 포트의 UDP 패킷처럼 보인다는 이유로 차단할 수 있습니다.

NAT 매핑 #

NAT(Network Address Translation) 매핑은 WebRTC 연결을 가능케 하는 핵심 요소입니다. 이는 앞서 언급한 “같은 네트워크가 아님” 문제를 해결하여, 완전히 다른 서브넷의 두 피어가 통신하도록 합니다. 새로운 과제를 만들기도 하지만, 먼저 NAT 매핑이 어떻게 동작하는지 살펴봅시다.

릴레이나 프록시, 서버를 사용하지 않습니다. Agent 1Agent 2가 서로 다른 네트워크에 있지만, 트래픽은 완전히 통과합니다. 시각화하면 다음과 같습니다.

NAT mapping

이 통신을 가능하게 하려면 NAT 매핑을 수립합니다. Agent 1이 포트 7000을 사용해 Agent 2와 WebRTC 연결을 수립하면, 192.168.0.1:70005.0.0.1:7000 바인딩이 생성됩니다. 이렇게 되면 Agent 2는 이 매핑으로 Agent 1에 트래픽을 보낼 수 있습니다. NAT의 동작은 매핑 정책에 따라 달라질 수 있으며, 주소/포트 의존적일 수도 있습니다. 이로 인해 직접 통신이 항상 가능한 것은 아니며, 이후에 설명할 TURN이 필요할 수 있습니다.

STUN #

STUN(Session Traversal Utilities for NAT, RFC 8489)은 NAT 매핑을 알아내는 표준 프로토콜입니다. STUN 패킷은 고정 크기 헤더와 속성 목록으로 구성되며, 핵심 메시지 타입은 다음과 같습니다.

  • Binding Request - 0x0001
  • Binding Response - 0x0101

Binding Request를 STUN 서버로 보내면, 응답으로 Binding Response를 받습니다. 이 응답에 포함된 XOR-MAPPED-ADDRESS(0x0020) 속성이 바로 생성된 NAT 매핑의 공인 IP/포트입니다. 이를 ‘Server Reflexive 후보’라고도 부릅니다.

NAT 유형 판별 #

안타깝게도 매핑이 항상 유용한 것은 아닙니다. 매핑이 주소 의존적(Address Dependent)인 경우, STUN 서버만 응답을 보낼 수 있고 다른 피어의 트래픽은 드롭될 수 있습니다. 이 경우 직접 통신에는 쓸모가 없습니다. RFC 5780은 NAT 유형을 판별하는 시험 방법을 정의합니다. 미리 직접 연결 가능성을 판단하는 데 유용합니다. 직접이 어렵다면 아래의 TURN을 사용합니다.

TURN #

TURN(Traversal Using Relays around NAT, RFC 8656)은 직접 연결이 불가능할 때의 해결책입니다. 서로 호환되지 않는 NAT 유형이거나, 동일한 프로토콜을 사용할 수 없는 경우에도 사용할 수 있습니다. 프라이버시를 위해서도 TURN을 쓸 수 있습니다. 모든 통신을 TURN을 통해 보내면 클라이언트의 실제 주소를 숨길 수 있습니다.

TURN은 전용 서버를 사용합니다. 클라이언트는 TURN 서버에 연결해 ‘할당(Allocation)’을 생성합니다. 그러면 임시 IP/포트/프로토콜인 RELAYED-ADDRESS를 부여받고, 여기에 들어오는 트래픽은 클라이언트로 포워딩됩니다. 각 원격 피어에 대해 통신을 허용하려면 ‘Permission’을 생성해야 합니다.

TURN 수명주기 #

TURN 할당을 만들려면 다음을 수행합니다.

  • 사용자 이름/비밀번호: TURN 할당에는 인증이 필요합니다.
  • 할당 전송 방식: 릴레이와 피어 간 전송 프로토콜(UDP 또는 TCP)
  • Even-Port: 연속 포트를 요청(일반 WebRTC에는 크게 중요하지 않음)

성공 시 응답의 속성에는 다음이 포함됩니다.

  • XOR-MAPPED-ADDRESS: TURN 클라이언트의 매핑 주소. 릴레이 주소로 들어온 트래픽의 포워딩 대상
  • RELAYED-ADDRESS: 다른 클라이언트에게 제공할 주소(이 주소로 오면 클라이언트로 릴레이)
  • LIFETIME: 할당 만료 시간. Refresh 요청으로 연장 가능

ICE #

ICE(Interactive Connectivity Establishment)는 WebRTC가 두 에이전트를 연결하는 방법입니다(RFC 8445). 두 피어 사이의 가능한 모든 경로를 파악하고, 연결 유지까지 책임지는 프로토콜입니다. 후보쌍(candidate pair)은 로컬/원격 전송 주소의 쌍이며, 여기서 STUN과 TURN이 사용됩니다. 각 측은 사용할 주소들을 수집해 교환하고, 연결을 시도합니다.

ICE 에이전트 간에는 연결성 검사(connectivity checks)라 불리는 ICE 핑(STUN 기반)을 주고받아 연결을 수립합니다. 연결이 성립하면 일반 소켓처럼 임의의 데이터를 보낼 수 있습니다.

ICE 에이전트 생성 #

ICE 에이전트는 Controlling 또는 Controlled 역할 중 하나입니다. Controlling 에이전트가 최종 선택된 후보쌍을 결정합니다. 일반적으로 오퍼를 보내는 쪽이 Controlling입니다.

양쪽 모두 user fragmentpassword를 가져야 하며, 연결성 검사를 시작하기 전에 교환되어야 합니다. user fragment는 평문으로 전송되어 여러 ICE 세션을 디멕스하는 데 유용합니다. passwordMESSAGE-INTEGRITY 계산에 사용됩니다. 각 STUN 패킷 끝에는 패킷 전체의 해시가 포함되며, 키로 password를 사용합니다. 이는 패킷 인증과 변조 방지에 쓰입니다. WebRTC에서는 이 값들을 앞 장에서 설명한 세션 설명을 통해 교환합니다.

후보 수집 #

이제 도달 가능한 모든 주소(후보)를 수집합니다.

Host #

로컬 인터페이스에서 직접 수신하는 후보입니다(UDP 또는 TCP).

mDNS #

Host 후보와 유사하지만 IP를 가리지 않고 UUID 호스트명을 제공합니다. 멀티캐스트 리스너를 띄우고, 공개한 UUID 질의에 응답합니다. 같은 네트워크면 멀티캐스트로 서로를 찾을 수 있지만, 네트워크가 다르면 일반적으로 연결되지 않습니다(관리자가 멀티캐스트 트래버설을 허용하지 않는 한). 로컬 IP 노출을 막아 프라이버시를 향상할 수 있습니다.

Server Reflexive #

STUN Binding Request/Response를 통해 얻은 XOR-MAPPED-ADDRESS로 생성되는 후보입니다.

Peer Reflexive #

알려지지 않은 주소에서 유효한(인증된) 트래픽을 받은 경우 생성되는 후보입니다. 예컨대 Host 후보 ↔ Server Reflexive 후보 간 통신에서 서브넷 외부와 통신하며 새 NAT 매핑이 생기는 경우가 이에 해당합니다. STUN 응답 형식은 피어 반사 주소를 자연스럽게 보고할 수 있습니다.

Relay #

TURN 서버에서 핸드셰이크 후 부여되는 RELAYED-ADDRESS로 생성되는 후보입니다.

연결성 검사 #

이제 원격 에이전트의 user fragment, password, 후보를 모두 알게 되었으니 연결을 시도합니다! 모든 후보는 서로 페어링됩니다. 각 측에 후보가 3개라면 9개의 후보쌍이 만들어집니다.

시각화하면 다음과 같습니다.

Connectivity checks

후보 선택 #

Controlling/Controlled 에이전트는 모든 후보쌍에 트래픽을 시도합니다. 한 에이전트가 주소 의존 매핑 뒤에 있는 경우, 이 과정에서 Peer Reflexive 후보가 생길 수 있기 때문입니다.

트래픽이 오간 후보쌍은 Valid Candidate로 승격됩니다. Controlling 에이전트는 Valid 후보쌍 중 하나를 지명(nominate)하고, 이 쌍을 대상으로 양방향 통신을 한 번 더 시도합니다. 성공하면 Selected Candidate Pair가 되며, 세션 내내 이 쌍이 사용됩니다.

재시작 #

선택된 후보쌍이 어떤 이유로든 동작을 멈추면(NAT 매핑 만료, TURN 장애 등) ICE 에이전트는 Failed 상태로 전이합니다. 양측 모두 재시작하여 전체 과정을 다시 수행할 수 있습니다.