WebRTC

유승현·2025년 8월 26일

배경지식

RFC(Request for Comments)
직역하면 비평을 기다리는 문서. IETF는 RFC로 발표된 일부 제안을 인터넷 표준으로 채택함. 모든 내용이 표준인 것은 아님.

https://en.wikipedia.org/wiki/Request_for_Comments

IETF(Internet Engineering Task Force)
인터넷 표준 기구. 프로토콜을 구성하는 기술 표준을 담당함.

https://en.wikipedia.org/wiki/Internet_Engineering_Task_Force

인터넷 표준(Internet Standard, STD)
인터넷에 적용되는 기술이나 방법론을 표준으로 제정한 규격.

참고 : https://en.wikipedia.org/wiki/Internet_Standard
공식문서 : https://www.rfc-editor.org/standards

NAT (Network Address Translation)
여러 대의 컴퓨터가 IP 주소를 공유하도록 하는 기술 : https://developer.mozilla.org/ko/docs/Glossary/NAT
공인 IP 부족을 해결하는 기술 : https://en.wikipedia.org/wiki/Network_address_translation

but. 얘 때문에 P2P 연결이 까다로워짐.


ICE (Interactive Connectivity Establishment)

https://datatracker.ietf.org/doc/html/rfc8445

P2P 네트워킹에서 두 컴퓨터가 가능한 한 직접적으로 서로 통신할 수 있는 방법을 찾기 위해 컴퓨터 네트워킹에 사용되는 기술

공식문서 기반 Gemini 설명

ICE란 무엇인가? (초간단 비유)

일단 개념부터 잡고 가자. ICE는 Interactive Connectivity Establishment의 약자야. 우리말로 하면 '상호 연결성 확립' 정도 되겠네.

쉽게 비유를 들어볼게.

네가 아파트 단지(사설망, Private Network)에 사는 친구(Peer)에게 전화를 걸고 싶다고 해보자.

  1. 직접 전화 (Host Candidate): 친구의 집 전화번호(내부 IP)를 알면 바로 걸 수 있겠지. 하지만 너도 다른 아파트 단지에 산다면, 같은 동네가 아닌 이상 그 번호는 소용이 없어.
  2. 경비실 통해 연결 (Server-Reflexive Candidate): 친구 집 전화번호를 모르니, 친구네 아파트 경비실(STUN 서버)에 전화를 걸어서 "XX동 OOO호 사는 친구 좀 연결해주세요"라고 하는 거야. 그럼 경비실이 친구 집으로 연결해주면서, 외부에서 전화할 수 있는 대표 번호(공인 IP 주소)를 알려주지.
  3. 교환원 통해 연결 (Relayed Candidate): 경비실을 통해서도 연결이 안 되는 아주 보안이 철저한 아파트(대칭형 NAT 등)도 있어. 이럴 땐 외부 통신을 중계해주는 교환원(TURN 서버)을 쓰는 거야. 너는 교환원에게 전화하고, 교환원이 다시 친구에게 전화를 걸어서 서로 통화하게 해주는 거지. 이건 확실하지만, 교환원을 거치니 조금 느리고 비용(서버 트래픽)이 발생해.

ICE는 바로 이 모든 방법을 다 시도해서, 너와 친구가 통신할 수 있는 가장 빠르고 효율적인 경로를 찾아내는 프로토콜이야. 핵심은 '될 때까지 다 해본다'는 아주 적극적인 방식이라는 거지.


ICE의 작동 원리 (4단계)

ICE는 크게 4단계로 동작해. RFC 8445 문서의 핵심 내용이기도 하지.

1단계: 후보 수집 (Candidate Gathering)

각 클라이언트(Peer)는 자신이 통신에 사용할 수 있는 모든 주소, 즉 후보(Candidate)들을 수집해. 위 비유에서 말한 방법들이 다 동원돼.

  • Host Candidate: 자신의 네트워크 카드에 할당된 IP 주소 (내부 IP). 같은 네트워크에 있다면 가장 빠른 경로야.
  • Server-Reflexive Candidate: STUN 서버에 요청을 보내서 알아낸 자신의 공인 IP 주소. 대부분의 가정집이나 사무실 환경(NAT 환경)에서 필요해.
  • Relayed Candidate: TURN 서버를 통해 할당받은 중계 주소. 가장 강력한 최후의 보루야. 어떤 네트워크 환경에서도 통신을 가능하게 해주지.

이렇게 수집한 후보 리스트를 만들어. 각 후보는 우선순위(priority)를 가지는데, 보통 Host > Server-Reflexive > Relayed 순으로 높아. 직접 연결하는 게 가장 좋으니까.

2단계: 후보 교환 (Candidate Exchange)

이제 각자 수집한 후보 리스트를 상대방에게 알려줘야 해. 이건 ICE 프로토콜 자체의 역할은 아니고, SIP나 WebSocket 같은 신호(Signaling) 프로토콜을 통해 이뤄져. 보통 SDP(Session Description Protocol)라는 형식에 담아서 교환하지.

나(Peer A): "안녕? 나랑 통화하려면 여기로 연락해봐. 내 후보들은 A, B, C야."

너(Peer B): "오케이! 내 후보들은 X, Y, Z야. 너도 참고해."

이제 양쪽 모두 자신과 상대방의 모든 후보 주소 목록을 갖게 됐어.

3단계: 연결성 검사 (Connectivity Checks)

이 단계가 ICE의 핵심이야. 서로의 후보들을 조합해서 후보 쌍(Candidate Pair)을 만들어. 예를 들면 (A, X), (A, Y), (B, X), (B, Y) ... 이런 식으로 모든 조합을 만드는 거지.

그리고 우선순위가 높은 후보 쌍부터 차례대로 STUN 프로토콜을 이용해서 연결을 시도해봐.

나(A) -> 너(X) 에게: "STUN 요청 보낸다! 들리니?"

너(X) -> 나(A) 에게: "어, 잘 받았어! STUN 응답 보낸다!"

이 요청과 응답이 성공적으로 오가면, 해당 후보 쌍은 유효(Valid)하다고 판단하고 '유효 리스트(Valid List)'에 추가해. 이 과정에서 새로운 후보가 발견되기도 하는데, 이걸 Peer-Reflexive Candidate라고 해. 내가 보낸 요청을 받은 상대방이 "어? 네가 나한테 보낸 주소는 내가 아는 네 공인 주소랑 다른데? 실제로는 이 주소로 왔어" 라고 알려주는 거지. 이것도 아주 중요한 통신 경로가 될 수 있어.

4단계: 후보 쌍 지명 및 ICE 종료 (Nominating Pairs & Concluding)

연결성 검사를 통해 유효한 경로들을 여러 개 찾았을 수 있잖아? 그럼 이제 둘 중 하나의 제어 에이전트(Controlling Agent)가 최종적으로 사용할 경로를 하나 지명(Nominate) 해야 해.

나(Controlling): "여러 경로를 테스트해보니 (B, Y) 경로가 제일 좋은 것 같아. 앞으로 이걸로 통신하자!"

제어 에이전트는 STUN 요청에 USE-CANDIDATE 라는 특별한 플래그를 붙여서 보내. 이 요청을 받은 상대방(Controlled Agent)도 동의하면, 이 후보 쌍이 선택된 쌍(Selected Pair)이 되고, 앞으로 모든 데이터는 이 경로를 통해 오가게 돼.

ICE 프로세스가 성공적으로 끝나면, 다른 후보들은 정리하고 선택된 경로로만 통신을 유지하는 거야.


왜 이런 기능이 필요할까? (의문점 파헤치기)

RFC 문서를 읽다 보면 '이건 왜 이렇게 복잡하게 만들었지?' 싶은 부분이 있을 거야. 몇 가지 중요한 것들을 짚어줄게.

❓ 왜 후보마다 복잡한 우선순위(Priority)를 계산할까?

  • 이유: 효율성 때문이야. 수십 개의 후보 쌍을 무작위로 테스트하면 가장 좋은 경로를 찾기까지 너무 오래 걸려. 그래서 RFC 5.1.2.1에 나오는 공식 priority = (2^24)*(type preference) + (2^8)*(local preference) + (2^0)*(256 - component ID) 을 사용해.
  • 상황: 이 공식을 통해 양쪽 피어가 거의 동일한 순서로 테스트를 진행하게 돼. Host(직접) > Peer-Reflexive > Server-Reflexive(NAT 통과) > Relayed(중계) 순으로 우선순위를 부여해서, 가장 빠르고 비용이 적게 드는 경로를 먼저 시도하게 만드는 거지. 이렇게 함으로써 통화 연결 시간을 단축시킬 수 있어.

❓ 왜 주기적으로 Keepalive 메시지를 보낼까?

  • 이유: NAT 장비가 길을 잊어버리기 때문이야. NAT는 일정 시간 동안 트래픽이 없으면 만들어뒀던 '내부 IP <-> 공인 IP' 매핑 정보를 삭제해버려. 이걸 NAT 바인딩 타임아웃이라고 해.
  • 상황: 통화 중에 잠시 말을 안 하면(Silence Suppression) 데이터 전송이 멈추겠지? 그때 NAT가 연결을 끊어버리면 상대방 목소리가 다시 들리지 않게 돼. 이걸 막기 위해 RFC 11장에 따라 보통 15초마다 작은 STUN 패킷(Binding Indication)을 보내서 "나 아직 이 경로 쓰고 있어!"라고 NAT에게 알려주는 거야.

Lite 구현과 Full 구현은 왜 나뉘어 있나?

  • 이유: 모든 장치가 복잡한 ICE 로직을 다 구현할 필요는 없기 때문이야.
  • 상황: 예를 들어, 항상 공인 IP를 사용하는 서버(미디어 서버 등)가 있다고 해보자. 이 서버는 NAT 뒤에 있는 클라이언트처럼 복잡하게 자기 주소를 찾을 필요가 없어. 이런 서버는 Lite 버전만 구현해서, 들어오는 연결성 검사에 응답만 잘 해주면 돼. 반면, 일반 사용자 PC나 스마트폰은 언제 어디서든 접속해야 하니 모든 기능을 갖춘 Full 버전을 구현해야 하는 거지. 이렇게 역할을 나눠서 구현의 부담을 줄여주는 거야.

ice2 옵션은 무엇이고 왜 필요한가?

  • 이유: 프로토콜 버전 관리 및 하위 호환성 때문이야.
  • 상황: 현재 표준인 RFC 8445는 이전 버전인 RFC 5245에서 일부 기능을 개선하고 변경했어. 가장 큰 차이는 '공격적 지명(Aggressive Nomination)'이라는 절차가 사라진 거야. ice2 옵션은 후보 교환 시 "나는 새로운 RFC 8445 규격을 따르는 에이전트야"라고 상대방에게 알려주는 신호야. 이 신호를 받은 구버전 에이전트는 호환되지 않는 기능을 사용하지 않게 되어 통신 오류를 막을 수 있어.

❓ STUN 사용자 이름(Username)은 왜 그렇게 중요할까? (보안 그 이상)

  • 이유: 이건 단순히 인증을 넘어, 잘못된 상대와 연결되는 것을 막기 위한 핵심 장치야.
  • 상황: (RFC 부록 B.4 참조) A, B 두 회사가 있다고 치자. 두 회사 모두 내부망에서 192.168.1.10 이라는 IP를 쓰는 직원이 있을 수 있어. A사의 '철수'가 B사의 '영희'에게 연결을 시도하는데, 신호가 잘못 전달되어 같은 A사에 있는 '민수'의 후보 정보가 전달됐다고 생각해봐. 철수는 영희의 주소라고 생각하고 192.168.1.10으로 연결성 검사를 보냈는데, 그게 민수에게 도착할 수 있어. 이때, 후보 교환 시 주고받은 무작위 username 값이 다르면 민수의 ICE 클라이언트는 "어? 이거 내 거 아닌데?" 하고 요청을 거부해. 만약 이 장치가 없다면, 철수는 영희가 아닌 민수와 연결될 뻔한 거지!

ICE는 네트워크 환경의 불확실성을 극복하기 위해 만들어진 아주 똑똑하고 실용적인 프로토콜이야. 처음에는 용어도 많고 절차도 복잡해 보이지만, 각 단계와 기능이 왜 필요한지 이해하고 나면 전체 그림이 보일 거야. 궁금한 거 있으면 언제든지 다시 물어보라고.

WebRTC

추가 참고 공식문서
https://www.rfc-editor.org/rfc/rfc7742.html

profile
커피를 넣으면 코드가 나옵니다.

0개의 댓글