§ 2.2 Module 2 — Naming the Session: SDP, SIP, and Offer/Answer
SIP is an application-layer control (signaling) protocol for creating, modifying, and terminating sessions with one or more participants.— RFC 3261, June 2002
By the end of this lesson
Historical note
In February 1995, VocalTec Communications of Herzliya, Israel — founded by Alon Cohen and Lior Haramaty — shipped Internet Phone, the first mass-market software that let two PC owners talk across the public internet. It proved the demand and exposed the vacuum: nothing standardized how one endpoint should find, ring, and negotiate with another. Two bodies raced to fill it. The ITU-T's Study Group 16 approved H.323 in 1996, a packet-network adaptation of its H.320 ISDN videoconferencing suite: binary messages encoded in ASN.1, call signaling (H.225.0) copied from ISDN's Q.931, a separate capability-exchange protocol (H.245), and a central gatekeeper admitting endpoints to the network. At the IETF, Mark Handley and Henning Schulzrinne circulated 1996 drafts of a much smaller idea — the Session Initiation Protocol, a text protocol whose requests looked like HTTP and routed like email — which, with contributions from Eve Schooler and Jonathan Rosenberg, became RFC 2543 in March 1999. When 3GPP selected SIP in 2000 as the signaling protocol for its IP Multimedia Subsystem, the war was effectively decided. WebRTC, arriving in 2011, kept SIP's most consequential habit — a session described by SDP carried in the message body, negotiated by offer/answer — while discarding SIP itself, and it inherited along with that habit a race condition called glare that this lesson closes on and Module 4 must cure.
H.323 and SIP solved the same problem — how does a call begin? — from irreconcilable premises, because they were written by people who trusted different networks. The ITU-T's authors had spent careers on the switched telephone network, where the network is smart, endpoints are dumb, and correctness is negotiated in committee before deployment. The IETF's authors had grown up on SMTP and HTTP, where the network is dumb, endpoints are smart, and correctness is whatever interoperates in practice.
H.323 was, in the phrase this course will keep returning to, a telephone network in packet clothing. Its call-signaling channel, H.225.0, reused message structures from Q.931 — the ISDN signaling protocol of the 1980s — down to the Setup, Alerting, and Connect messages. Capabilities were exchanged over a second protocol, H.245, complete with a master/slave determination handshake. Everything was serialized in ASN.1 packed encoding rules: compact on the wire, but unreadable without a decoder generated from the formal grammar. A gatekeeper handled registration, admission, and bandwidth control over a third channel (RAS). The result worked — it shipped in NetMeeting and in most first-generation IP-PBX gear — but a version-1 call could burn half a dozen round trips across multiple TCP connections before the first RTP packet flowed.
SIP inverted every one of those choices. Messages are plain text, deliberately shaped like HTTP: a request line with a method and a URI (INVITE sip:bob@biloxi.example.com SIP/2.0), then headers, a blank line, and a body. Responses carry three-digit status codes with HTTP's class semantics — 100 Trying, 180 Ringing, 200 OK, 404 Not Found, 486 Busy Here. Addresses are URIs that look like email addresses, and requests route the way mail does: through proxies that consult location databases and forward, each one stamping a Via header the way an SMTP relay stamps Received. An engineer with a packet capture could read a SIP dialog with bare eyes and reproduce it with telnet. That debuggability was not a side effect; it was the argument.
Why did SIP win? Partly extensibility — new methods and headers could be minted without recompiling an ASN.1 grammar, and the dot-com generation of implementers already spoke HTTP. Partly momentum at the decisive moment: 3GPP's 2000 decision to build the IP Multimedia Subsystem on SIP committed the world's mobile carriers; Microsoft shipped SIP in Windows Messenger with Windows XP in 2001; and Mark Spencer's open-source Asterisk PBX (started 1999) made SIP trunking cheap enough to gut the proprietary PBX market from below. By the mid-2000s H.323 survived mainly in installed videoconferencing rooms.
Two ironies temper the victory. First, SIP did not stay simple: RFC 3261 is 269 pages, and the extension RFCs number in the hundreds. Second, while the standards bodies fought, Skype — Niklas Zennström and Janus Friis, 2003 — ignored both protocols, shipped a closed P2P system, and took the consumer market anyway. Standards wars decide what the industry builds on; they do not decide what users install. WebRTC's designers in 2011 absorbed both lessons: they took SIP's session-description model without SIP's protocol machinery, and left signaling itself deliberately unstandardized.
RFC 3261 defines six core methods: INVITE (start or modify a session), ACK (confirm a final INVITE response), BYE (end), CANCEL (abandon a pending request), REGISTER (bind your URI to your current location), and OPTIONS (ask about capabilities). The one that concerns this module is INVITE, because an INVITE carries the session description. Here is one, essentially as RFC 3261's own examples give it:
INVITE sip:bob@biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP pc33.atlanta.example.com;branch=z9hG4bK776asdhds
Max-Forwards: 70
To: Bob <sip:bob@biloxi.example.com>
From: Alice <sip:alice@atlanta.example.com>;tag=1928301774
Call-ID: a84b4c76e66710@pc33.atlanta.example.com
CSeq: 314159 INVITE
Contact: <sip:alice@pc33.atlanta.example.com>
Content-Type: application/sdp
Content-Length: 127
v=0
o=alice 2890844526 2890844526 IN IP4 pc33.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
Listing 2.2 — A SIP INVITE. Everything above the blank line is SIP; everything below it is SDP, an opaque MIME body as far as SIP is concerned.
The headers each earn their place. Via records the path a request has taken so the response can retrace it; each hop prepends its own entry, and the branch parameter identifies the transaction. From and To name the logical parties, and their tag parameters, together with Call-ID, form the three-part key that identifies a dialog — the persistent call relationship all later requests belong to. CSeq orders requests within that dialog. Contact gives the address where this endpoint can be reached directly, letting subsequent requests bypass the proxies. Max-Forwards is a hop counter against routing loops, borrowed conceptually from IP's TTL.
Then the payload. Content-Type: application/sdp declares that the body is a session description, and SIP treats it exactly the way SMTP treats an attachment: as MIME cargo it transports but never interprets. The negotiation semantics — the offer/answer model, in which one side proposes a set of media streams, codecs, and addresses and the other returns a pruned, compatible subset — live in a separate document, RFC 3264 (Rosenberg and Schulzrinne, June 2002). That separation is the single most important inheritance in this course: WebRTC's createOffer and createAnswer implement RFC 3264 semantics almost verbatim, on SDP bodies that Lesson 2.3 will dissect line by line.
Two syntactic details matter for anyone parsing these messages, and both come from SIP's email ancestry (RFC 5322's grammar, formerly RFC 822). Lines end in \r\n, not bare \n. And a long header may be folded across lines: a line beginning with a space or tab is a continuation of the previous header, not a new one. Parsers that split naively on newlines and colons corrupt real traffic — a lesson the exercise below makes concrete.
A common error
"SDP is a protocol that WebRTC sends over the network." It is not, and it never was — not even in SIP. SDP is an inert description, a string; SIP was merely one courier for it, the way an envelope is not the letter. WebRTC (via JSEP, RFC 8829) deliberately standardizes no courier at all: your application must carry the SDP from one peer to the other by whatever means it likes — WebSocket, HTTP, a QR code, carrier pigeon. When your "serverless" demo in Lesson 2.5 works by copy-pasting SDP between two browser tabs, that is not a trick; that is the architecture.
Check your understanding
Auto-graded check
3 auto-graded questions with an explanation for every wrong answer. Requires JavaScript. (m2-l2-check1)
You cannot hand a classic SIP endpoint's SDP to setRemoteDescription(). It will lack a=fingerprint and a=setup (so no DTLS), lack a=ice-ufrag/a=ice-pwd (so no ICE), lack a=mid and a=group:BUNDLE, use the RTP/AVP profile instead of UDP/TLS/RTP/SAVPF, and often offer a=crypto (SDES) which browsers removed years ago. Chrome will throw or, worse, accept it and then never complete a handshake.
Put a B2BUA or a media gateway in the path — Asterisk, FreeSWITCH, Kamailio with rtpengine, or a commercial SBC. Do not attempt to translate SDP in JavaScript; the media plane (SRTP keying, RTCP translation, codec transcoding) is the actual work and it cannot live in the browser.
RFC 3261's examples draw a picture the industry came to call the SIP trapezoid: signaling climbs from Alice's user agent up through her domain's proxy, across to Bob's domain's proxy, and down to Bob — while the media, once negotiated, cuts straight across the bottom, endpoint to endpoint. The RTP packets flow to the address and port advertised in the SDP's c= and m= lines; the proxies never see a single one of them.
Because the two planes take different paths, they fail independently — and that gives every VoIP engineer since 1999 a first question that sorts most outages: which plane broke? Three canonical symptoms:
The call never rings. No alerting means the INVITE never reached a user agent willing to answer, or its response never made it back: a signaling-plane failure. Look at the signaling first — the proxy's log, the SIP trace. Did the INVITE arrive? Was the callee's registration current, or had its binding expired? Did a proxy return 408, 480, or nothing at all? The media plane is not yet a suspect, because no media was ever negotiated.
The call rings, connects — silence in both directions. Signaling manifestly worked: INVITE out, 200 OK back, ACK sent. The failure is in the media plane, and the first artifact is the SDP itself: what address did each side advertise in its c= line, and can the other side actually reach it? The classic culprit is NAT — an endpoint advertising its private 192.168.x.x address to a peer across the internet, a doomed promise. A packet capture showing RTP leaving one host and never arriving at the other settles it.
One-way audio. Alice hears Bob; Bob hears nothing. Still the media plane — signaling has no "half-working" mode that produces this — but now direction-specific: one RTP flow traverses, the other is dropped, almost always by a NAT that permits outbound packets while discarding unsolicited inbound ones. Capture on the silent direction and find where the packets die.
The axis survives intact into WebRTC; only the artifact names change. Your signaling plane is the WebSocket or HTTP server your application uses to ferry SDP and ICE candidates, and its failures live in your server logs. Your media plane is the ICE/DTLS/SRTP machinery inside RTCPeerConnection, and its failures live in getStats and webrtc-internals. A call that never "rings" points at your signaling server; a connection that reaches connected but shows black video points at the media path. Module 6 builds a whole forensic method on this one distinction.
Interactive 3D instrument
The signaling wars — two structures, one call setup
A 3D instrument you drive yourself, one variable at a time. It needs JavaScript and WebGL, so it is not shown in this static copy of the page.
Offer/answer has a hidden assumption: at any moment, only one side is offering. A SIP dialog is long-lived, and either party may send a re-INVITE to change the session — put the call on hold, switch codecs, add video. What happens when both send one at the same instant? Each party now holds an outstanding offer of its own and an incoming offer from the peer, and RFC 3264's state machine has no legal move: you cannot answer an offer while your own offer is pending. The RFC 3261 authors named this collision glare, borrowing a term from telephone trunk engineering, where it meant two switches seizing the same trunk from both ends at once.
SIP's remedy is honest but crude. A user agent that receives a re-INVITE while its own is pending answers 491 Request Pending, both sides back off for randomized intervals — deliberately drawn from different ranges depending on who owns the dialog's Call-ID, so the retries interleave — and one side eventually wins. Detection, rejection, randomized retry: a protocol-level shrug.
WebRTC inherited the state machine but not the remedy. An RTCPeerConnection in the have-local-offer signaling state will throw InvalidStateError if you apply a remote offer — the browser enforces RFC 3264's rule — but there is no 491, no retry timer, no arbitration, because there is no standardized signaling channel to carry them. The application must resolve glare itself. The canonical solution, perfect negotiation, assigns the peers asymmetric roles — one polite, rolling back its own offer when a collision hits; one impolite, pressing on — and Module 4 derives it step by step. When you meet it there, remember where the disease came from: two switches seizing the same trunk, sixty years before the browser.
The war's outcome, then, is best stated as a bequest. From SIP, WebRTC took the text-based session description, the offer/answer algebra, the SDP-in-the-body convention, and glare. From H.323 it took a cautionary tale. And from Skype it took the ambition that a call should be something a consumer never has to think about — which is why the next lesson descends into the one artifact every WebRTC engineer must eventually read raw: the SDP itself.
One parser to write against a real INVITE, and three outages to triage by plane.
Lab 1
Dissect an INVITE
Graded in the browser against 5 assertions; the editor and harness require JavaScript.
Check your understanding
Auto-graded check
4 auto-graded questions with an explanation for every wrong answer. Requires JavaScript. (m2-l2-ex2)
What the specifications say, and what the browsers actually do, are two different documents. These are the divergences that cost production teams their weekends.
The classic SIP hold is a=sendonly or a=inactive with the connection address zeroed. A browser has no concept of "hold": it accepts the description, then ICE has nothing to connect to and the transport eventually fails. You get a torn-down session instead of a held call, several seconds later, with no obviously related error.
Have the gateway keep a real address and express hold purely with direction attributes, or model hold entirely in your application layer (stop sending, show UI) and never let a zeroed c= reach setRemoteDescription.
H.264 in WebRTC is negotiated with a=fmtp carrying profile-level-id and packetization-mode. Mode 0 is single-NAL-per-packet: no fragmentation units, so any NAL larger than the MTU cannot be sent. Chrome will happily negotiate mode 0 with a legacy endpoint and then be unable to transmit keyframes at anything above low resolution. Video appears to work at 320x240 and fails at 720p, which looks like a bandwidth problem.
Require packetization-mode=1 in your gateway's offer and reject mode-0-only peers explicitly rather than degrading. Also check profile-level-id: 42e01f is constrained baseline level 3.1, which caps you near 720p30 — a legacy peer offering level 1.3 will silently limit resolution.
RTCDTMFSender hangs off RTCRtpSender.dtmf and only works if telephone-event (RFC 4733, usually payload type 101 or 126) was negotiated on that m-line. If it was not, dtmf is null or canInsertDTMF is false and insertDTMF() does nothing — no exception. Chrome also clamps duration to a 40–6000 ms range and interToneGap to a 30 ms minimum, so a tight loop of tones is stretched and your IVR timing assumptions break.
Check sender.dtmf?.canInsertDTMF before offering a keypad in the UI, and confirm the gateway includes telephone-event in its answer. For IVRs, send the whole string in one insertDTMF() call and let the browser pace it rather than driving tones from a timer.
The Impossible Call · Module 2, Lesson 2