,

[WebRTC] 연결의 6단계 — Offer/Answer부터 ICE Connected까지

결론 먼저 — 단계별 실패 위치를 알면 디버깅이 빨라진다

단계이름대표 실패 원인
1Signaling 채널 수립WebSocket 인증/CORS/방화벽
2SDP Offer/Answer 교환codec 미스매치, m-line 순서
3ICE candidate 수집STUN 서버 도달 실패
4ICE connectivity check방화벽, NAT 종류 비대칭
5DTLS handshake인증서, 시간 동기 (NTP)
6SRTP 키 교환 → 미디어 흐름codec 협상 미스

“연결이 안 된다”는 신고는 6단계 중 어디서 멈췄는지부터 좁혀야 한다. 그래야 점검 시간이 분 단위가 된다.

단계 1 — Signaling 채널 수립

WebRTC 표준은 시그널링 프로토콜을 정해두지 않았다. 보통 WebSocket이나 HTTP long-poll을 쓴다. 이 단계에서 실패하면 “방을 만들 수도 없다”가 된다. 가장 흔한 원인은 WebSocket 인증 토큰 만료, CORS 미스, 회사 방화벽이 80/443 외 포트를 차단하는 경우다.

점검: 브라우저 DevTools의 Network 탭에서 WebSocket이 101 Switching Protocols까지 갔는지만 확인하면 된다.

단계 2 — SDP Offer / Answer 교환

Caller가 createOffer() → SDP를 시그널링 채널로 보냄. Callee가 setRemoteDescription()createAnswer(). 이 단계의 핵심은 양쪽이 합의한 codec, payload type, m-line 순서다.

실패 패턴 둘:

  • 한쪽이 Opus만 지원하는데 다른 쪽이 G.711만 지원하면 audio m-line이 합의되지 않는다.
  • renegotiation 시 m-line 순서가 바뀌면 Chromium이 거부한다 (InvalidModificationError).

단계 3 — ICE candidate 수집

setLocalDescription()이 끝나면 ICE 에이전트가 후보를 수집하기 시작한다. 후보의 종류는 세 가지다.

  • host — 단말의 로컬 IP
  • srflx (server reflexive) — STUN 서버가 본 단말의 공인 IP
  • relay — TURN 서버를 경유하는 주소

이 단계에서 흔한 실패는 STUN 서버에 도달하지 못하는 경우다. 사내 네트워크에서 외부 UDP가 막혀있으면 srflx 후보가 비고, NAT 뒤 단말끼리 연결할 길이 사라진다.

단계 4 — ICE connectivity check

양쪽이 후보를 교환한 뒤, 모든 (local × remote) 페어에 대해 STUN Binding Request를 주고받으며 실제로 통신이 되는 페어를 찾는다. 처음 성공한 페어가 nominated pair가 되고, 거기로 미디어가 흐른다.

이 단계에서 멈추는 경우가 가장 디버깅이 까다롭다. 보통 NAT 종류의 비대칭(예: 한쪽 Symmetric NAT)이나 UDP 차단이 원인이고, TURN으로 fall-through 해야 풀린다. 다음 글에서 NAT 종류별 성공률 매트릭스를 다룬다.

단계 5 — DTLS handshake

ICE 페어가 정해지면 그 위에서 DTLS 핸드셰이크가 시작된다. WebRTC는 self-signed 인증서를 쓰는 게 표준이고, 인증서 fingerprint를 SDP에 박아 양쪽이 비교한다.

의외로 시간 동기 (NTP)가 안 맞는 서버에서 DTLS 협상이 실패하는 경우가 있다. 인증서 not-before/not-after를 검증할 때 시계가 어긋나면 거부된다.

단계 6 — SRTP 키 교환 → 미디어 흐름

DTLS-SRTP extension으로 SRTP 마스터 키를 도출한다. 이때부터 RTP 패킷이 흐르기 시작한다. 이 단계에서 실패하면 보통 codec 협상이 잘못된 경우다. SDP에는 합의돼 있는데 실제 패킷의 payload type이 다르거나, MIME이 어긋났을 때 수신측 디코더가 침묵한다.

개인 메모 — “어디서 멈췄나”부터 묻는 습관

WebRTC를 처음 만졌을 때 가장 어려웠던 점은 “안 됨”이라는 신호가 너무 모호하다는 것이었다. 화면이 검은 채로 멈춰 있고 콘솔에는 별다른 에러가 없는 상황이 흔했다. 그때 누군가가 “그게 ICE에서 멈춘 거냐, DTLS에서 멈춘 거냐”고 물었고, 그 질문 한 줄이 디버깅의 결을 완전히 바꿔놓았다.

그 뒤로는 데모가 안 붙을 때마다 코드를 보기 전에 peerConnection.iceConnectionState의 천이 로그부터 본다. checking → failed에서 멈췄다면 단계 4의 NAT 문제, connected 직후 검정 화면이라면 단계 6의 codec 문제, new에서 안 움직이면 단계 3의 STUN 문제. 이 한 가지 습관만 들여도 디버깅 시간이 눈에 띄게 짧아진다.

참고

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다