Skip to content
SIP Mastery

Part 3 · SIP mechanics / Module 10

Routing: Via, Record-Route, Route

How a SIP message finds its next hop — responses follow the Via headers back, requests follow Route and the Request-URI, and Record-Route decides which proxies see the rest of the call. Also NAT and rport, strict routing, outbound proxies, loops, and proxies with two interfaces.

AdvancedNOC coreDEV coredraft

After this module, you can

  • Say which headers decide where a request goes, and which decide where a response goes.
  • Follow the Via stack through a call, and find where each response is sent, with received and rport.
  • Explain why a proxy adds Record-Route, and predict which proxies see the ACK, BYE, and re-INVITE.
  • Build the route set on both sides, and the Request-URI and Route of a request inside the dialog.
  • Tell loose routing from strict routing, and spot a missing lr.
  • Explain the outbound proxy, the pre-loaded Route, and why Request-URI and To differ.
  • Tell a loop from a spiral, and explain 482 and 483.
  • Explain double Record-Route on a proxy with two interfaces.

10.1 Two paths: responses follow Via, requests follow Route

Every SIP element that handles a message answers one question: where does it go next? The answer comes from different headers for requests and for responses.

A request A response
Next hop The first Route value; with no Route, the Request-URI The top Via
What each proxy changes Adds a Via; removes its own Route value; may replace the Request-URI; may add Record-Route Removes its own Via
Path Can be different for each request Always the path of its request, in reverse
RFC 3261 §16.12 · Summary of Proxy Route Processing
“The proxy will forward the request to the resource indicated by the URI in the topmost Route header field value or in the Request-URI if no Route header field is present.”
Read the section ↗
RFC 3261 §8.1.1.7 · Via
“The Via header field indicates the transport used for the transaction and identifies the location where the response is to be sent.”
Read the section ↗

The first INVITE finds Bob with DNS and the location service (Modules 3 and 11). Its responses retrace that path exactly. But the requests that come later in the dialog — the ACK for the 2xx, the BYE, a re-INVITE — do not follow the INVITE. They follow the route set, which comes from Record-Route (10.4). Mixing up the two paths is the most common routing mistake.

10.2 The Via stack

Each element that sends a request puts its own Via on top of the Vias already there. Each element that forwards a response takes its own Via off the top. The Vias are a stack: the response pops them in the reverse order of the request.

RFC 3261 §16.6 · Request Forwarding
“The proxy MUST insert a Via header field value into the copy before the existing Via header field values.”
Read the section ↗
RFC 3261 §16.7 · Response Processing
“The proxy removes the topmost Via header field value from the response. If no Via header field values remain in the response, the response was meant for this element and MUST NOT be forwarded.”
Read the section ↗

One Via value holds:

  • Transport and sent-by: SIP/2.0/UDP 198.51.100.10:5060 — the transport and the address where the sender wants the response.
  • branch: the transaction ID, starting with z9hG4bK (Module 8). Each hop has its own transaction, so each Via has its own branch.
  • received and rport: what the next hop really saw — added by the next hop, not by the sender (10.3).

A Via belongs to one transaction. A new request — the ACK for a 2xx, a BYE — starts again with one Via, and gets a new stack along its own path. Step through the call below. Watch the stack grow on the INVITE and shrink on the 200 OK, and see where each message goes.

Via stack

Via headers stack up and come off

  • +Added at this hop
  • −Removed at this hop
Alice192.168.1.20 · NATProxy A198.51.100.10Proxy B203.0.113.10Bob203.0.113.2001INVITE02100 Trying03INVITE04100 Trying05INVITE06200 OK07200 OK08200 OK09ACK10ACK
01/10

Alice's phone adds the first Via: its private address, and an empty rport. A NAT router changes the source to 192.0.2.10:40112.

INVITEAlice → Proxy A · request

Alice creates the request, so it has one Via: its own.

  1. +

    Alice

    UDP 192.168.1.20:5060

    branch=z9hG4bKv1a7rport (empty)

Sent to

sip:proxy.atlanta.example;lr

from the first Route header

10.3 received and rport: the response path behind NAT

The sender writes its own address into sent-by. Behind a NAT router, that is a private address, such as 192.168.1.20:5060. A response sent there never arrives. So the next hop writes what it actually saw into the Via:

RFC 3261 §18.2.1 · Receiving Requests
“If the host portion of the "sent-by" parameter contains a domain name, or if it contains an IP address that differs from the packet source address, the server MUST add a "received" parameter to that Via header field value.”
Read the section ↗

received fixes the address, but not the port. A NAT router also changes the UDP source port. RFC 3581 adds rport: the sender adds it with no value, and the next hop fills in the source port.

RFC 3581 §4 · Server Behavior
“If this Via header field value contains an "rport" parameter with no value, it MUST set the value of the parameter to the source port of the request.”
Read the section ↗
RFC 3581 §4 · Server Behavior
“In fact, the server MUST insert a "received" parameter containing the source IP address that the request came from, even if it is identical to the value of the "sent-by" component.”
Read the section ↗

The server then picks the response address from the top Via, in this order:

The top Via has… The response goes to…
TCP or TLS transport The same connection the request came on
UDP, received and rport received address : rport port
UDP, received only received address : sent-by port (5060 if none)
UDP, neither The sent-by address and port
RFC 3261 §18.2.2 · Sending Responses
“If the "sent-protocol" is a reliable transport protocol such as TCP or SCTP, or TLS over those, the response MUST be sent using the existing connection to the source of the original request that created the transaction, if that connection is still open.”
Read the section ↗
RFC 3261 §18.2.2 · Sending Responses
“Otherwise (for unreliable unicast transports), if the top Via has a "received" parameter, the response MUST be sent to the address in the "received" parameter, using the port indicated in the "sent-by" value, or using port 5060 if none is specified explicitly.”
Read the section ↗
RFC 3581 §4 · Server Behavior
“If the "sent-protocol" component indicates an unreliable unicast transport protocol, such as UDP, and there is no "maddr" parameter, but there is both a "received" parameter and an "rport" parameter, the response MUST be sent to the IP address listed in the "received" parameter, and the port in the "rport" parameter. The response MUST be sent from the same address and port that the corresponding request was received on.”
Read the section ↗

In the Via stack above, step 2 shows this. Proxy A sends the 100 Trying to 192.0.2.10:40112 — the public side of Alice’s NAT router — and not to the private address that Alice wrote. A proxy keeps the received and rport of the Vias below its own; it removes only its own Via. Module 20 covers the rest of NAT: keepalives, Contact rewriting, and SIP Outbound.

10.4 Record-Route: staying in the path

After the first INVITE, a proxy is out of the call — unless it asks to stay. Some proxies must see every request in the dialog: a billing proxy needs the BYE to stop counting, a proxy that handles NAT must relay requests to a phone behind a NAT router, and an SBC at the network edge must see everything. Such a proxy adds Record-Route to the request that creates the dialog:

RFC 3261 §16.6 · Request Forwarding
“If this proxy wishes to remain on the path of future requests in a dialog created by this request (assuming the request creates a dialog), it MUST insert a Record-Route header field value into the copy before any existing Record-Route header field values, even if a Route header field is already present.”
Read the section ↗
RFC 3261 §16.6 · Request Forwarding
“Each proxy in the path of a request chooses whether to add a Record-Route header field value independently - the presence of a Record-Route header field in a request does not obligate this proxy to add a value.”
Read the section ↗

The UAS copies the Record-Route values into its 2xx (and its 101–199 responses), in the same order, so both sides learn the list:

RFC 3261 §12.1.1 · UAS behavior
“When a UAS responds to a request with a response that establishes a dialog (such as a 2xx to INVITE), the UAS MUST copy all Record-Route header field values from the request into the response (including the URIs, URI parameters, and any Record-Route header field parameters, whether they are known or unknown to the UAS) and MUST maintain the order of those values.”
Read the section ↗

Record-Route has a cost. Every request in the dialog passes through the proxy, and a dialog-stateful proxy keeps state for every call. So a proxy record-routes only when a service needs it:

RFC 3261 §16.6 · Request Forwarding
“Record-routing may be required by certain services where the proxy needs to observe all messages in a dialog. However, it slows down processing and impairs scalability and thus proxies should only record-route if required for a particular service.”
Read the section ↗

Turn Record-Route on and off for each proxy below, and choose a later request. The INVITE and its 200 OK always pass both proxies. Only the later request changes its path.

Routing visualiser

Who adds Record-Route decides who sees the rest of the call

Adds Record-Route
Later request
INVITEfollows Route, then Request-URIAliceProxy AProxy BBobINVITE: Alice → Proxy AINVITE: Proxy A → Proxy BINVITE: Proxy B → Bob200 OKfollows Via, top firstAliceProxy AProxy BBob200 OK: Bob → Proxy B200 OK: Proxy B → Proxy A200 OK: Proxy A → AliceBYE from Alicefollows Alice's route setAliceProxy AProxy BBobBYE: Alice → Proxy ABYE: Proxy A → Proxy BBYE: Proxy B → Bob

Alice's route setRecord-Route, reversed

  1. sip:proxy.atlanta.example;lr
  2. sip:proxy.biloxi.example;lr

Bob's route setRecord-Route, in order

  1. sip:proxy.biloxi.example;lr
  2. sip:proxy.atlanta.example;lr
BYE from Alice, hop by hop
HopRequest-URIRoute
bob@203.0.113.20:5060proxy.atlanta.exampleproxy.biloxi.example
bob@203.0.113.20:5060proxy.biloxi.example
bob@203.0.113.20:5060none
Alice192.0.2.10Proxy Aproxy.atlanta.exampleProxy Bproxy.biloxi.exampleBob203.0.113.2001INVITE02INVITE03INVITE04200 OK05200 OK06200 OK07ACK08ACK09ACK10BYE11BYE12BYE13200 OK14200 OK15200 OK
10/15

Alice hangs up. The BYE follows her route set: Proxy A, then Proxy B, then Bob.

Try these:

  • Both off. The BYE goes straight from Alice to Bob. Neither proxy knows that the call ended.
  • Only Proxy B on. Alice’s ACK and BYE skip Proxy A — her own outbound proxy — and go straight to Proxy B.
  • Both on, re-INVITE from Bob. Bob’s request visits Proxy B first, then Proxy A: his route set is in the other order.

10.5 Building the route set

Each proxy puts its Record-Route value on top, so the last proxy before Bob is first in the list. When the INVITE reaches Bob, it has:

Record-Route: <sip:proxy.biloxi.example;lr>
Record-Route: <sip:proxy.atlanta.example;lr>

Each side wants its nearest proxy first. Bob’s nearest proxy is Proxy B, already on top, so he keeps the order. Alice’s nearest proxy is Proxy A, at the bottom, so she reverses the list:

Takes Record-Route from Order Route set in the example
Bob (UAS) The request As received proxy.biloxi.example, proxy.atlanta.example
Alice (UAC) The 2xx response Reversed proxy.atlanta.example, proxy.biloxi.example
RFC 3261 §12.1.1 · UAS behavior
“The route set MUST be set to the list of URIs in the Record-Route header field from the request, taken in order and preserving all URI parameters.”
Read the section ↗
RFC 3261 §12.1.2 · UAC Behavior
“The route set MUST be set to the list of URIs in the Record-Route header field from the response, taken in reverse order and preserving all URI parameters.”
Read the section ↗

The route set is fixed once the dialog is confirmed. A re-INVITE can change the remote target, but not the route set:

RFC 3261 §12.2 · Requests within a Dialog
“Requests within a dialog MAY contain Record-Route and Contact header fields. However, these requests do not cause the dialog's route set to be modified, although they may modify the remote target URI.”
Read the section ↗

To send a request in the dialog, the UA puts the remote target in the Request-URI and the route set in Route:

RFC 3261 §12.2.1.1 · Generating the Request
“If the route set is not empty, and the first URI in the route set contains the lr parameter (see Section 19.1.1), the UAC MUST place the remote target URI into the Request-URI and MUST include a Route header field containing the route set values in order, including all parameters.”
Read the section ↗

Each proxy on the way finds itself in the top Route value and removes it. When no Route value is left, the request goes to the Request-URI: the remote target.

RFC 3261 §16.4 · Route Information Preprocessing
“If the first value in the Route header field indicates this proxy, the proxy MUST remove that value from the request.”
Read the section ↗

10.6 Loose routing (;lr) and strict routing

RFC 3261 separates two things: where the request is going (the Request-URI) and which proxies it must visit on the way (Route). This is loose routing. The lr parameter in a URI says “this element is a loose router”:

RFC 3261 §6 · Definitions (Loose Routing)
“A proxy is said to be loose routing if it follows the procedures defined in this specification for processing of the Route header field. These procedures separate the destination of the request (present in the Request-URI) from the set of proxies that need to be visited along the way (present in the Route header field).”
Read the section ↗
RFC 3261 §19.1.1 · SIP and SIPS URI Components
“The lr parameter, when present, indicates that the element responsible for this resource implements the routing mechanisms specified in this document.”
Read the section ↗

The older RFC 2543 used strict routing: the next hop always went into the Request-URI, and the real target moved to the end of Route.

RFC 3261 §6 · Definitions (Strict Routing)
“That rule caused proxies to destroy the contents of the Request-URI when a Route header field was present. Strict routing behavior is not used in this specification, in favor of a loose routing behavior.”
Read the section ↗

A UA still supports strict routers. If the first URI of the route set has no lr, it builds the request the old way. The same BYE, with a route set of one proxy:

First route has ;lr (loose) First route has no lr (strict)
Request-URI sip:bob@203.0.113.20:5060 — the remote target sip:proxy.biloxi.example — the first route
Route <sip:proxy.biloxi.example;lr> <sip:bob@203.0.113.20:5060> — the remote target, last
RFC 3261 §12.2.1.1 · Generating the Request
“If the route set is not empty, and its first URI does not contain the lr parameter, the UAC MUST place the first URI from the route set into the Request-URI, stripping any parameters that are not allowed in a Request-URI. The UAC MUST add a Route header field containing the remainder of the route set values in order, including all parameters. The UAC MUST then place the remote target URI into the Route header field as the last value.”
Read the section ↗

An RFC 3261 proxy that receives a request with its own Record-Route URI in the Request-URI repairs it. It moves the last Route value back into the Request-URI:

RFC 3261 §16.4 · Route Information Preprocessing
“If the Request-URI of the request contains a value this proxy previously placed into a Record-Route header field (see Section 16.6 item 4), the proxy MUST replace the Request-URI in the request with the last value from the Route header field, and remove that value from the Route header field.”
Read the section ↗

So every modern proxy must put lr in its Record-Route URI. A missing lr makes every UA use strict routing for the whole dialog (see Common mistakes).

RFC 3261 §16.6 · Request Forwarding
“The URI placed in the Record-Route header field value MUST be a SIP or SIPS URI. This URI MUST contain an lr parameter (see Section 19.1.1).”
Read the section ↗

10.7 Outbound proxy and pre-loaded Route

A phone usually sends its requests to a proxy of its provider, its outbound proxy, whatever the destination. RFC 3261 recommends that the phone holds the outbound proxy as a pre-loaded Route — a route set of one URI that it uses for requests outside a dialog:

RFC 3261 §8.1.1.1 · Request-URI
“When a provider wishes to configure a UA with an outbound proxy, it is RECOMMENDED that this be done by providing it with a pre-existing route set with a single URI, that of the outbound proxy.”
Read the section ↗

The Request-URI stays the real target, sip:bob@biloxi.example. The outbound proxy finds its own URI in Route, removes it, and routes on the Request-URI. Inside the dialog, the phone uses the route set from Record-Route, not its outbound proxy. An outbound proxy that does not record-route drops out of the call:

RFC 3261 §8.1.2 · Sending the Request
“In particular, a UAC configured with an outbound proxy SHOULD attempt to send the request to the location indicated in the first Route header field value instead of adopting the policy of sending all messages to the outbound proxy. This ensures that outbound proxies that do not add Record-Route header field values will drop out of the path of subsequent requests.”
Read the section ↗

Call flow · outbound proxy with a pre-loaded Route

Outbound proxy and pre-loaded Route

  • SIP
Alice192.0.2.10Proxy Aproxy.atlanta.exampleProxy Bproxy.biloxi.exampleBob203.0.113.2001INVITE02INVITE03INVITE04200 OK05200 OK06200 OK07ACK08ACK
01/08

Alice's phone has one pre-loaded Route: its outbound proxy. The Request-URI stays Bob's AOR.

All steps as text
  1. Alice → Proxy A: INVITE. Alice's phone has one pre-loaded Route: its outbound proxy. The Request-URI stays Bob's AOR.
  2. Proxy A → Proxy B: INVITE. Proxy A removes its own Route entry. No Route is left, so it routes on the Request-URI: biloxi.example.
  3. Proxy B → Bob: INVITE. Proxy B adds Record-Route and replaces the Request-URI with Bob's Contact.
  4. Bob → Proxy B: 200 OK. Bob answers. The Record-Route names only Proxy B.
  5. Proxy B → Proxy A: 200 OK. Proxy B removes its Via and sends the 200 OK to Proxy A.
  6. Proxy A → Alice: 200 OK. Responses follow Via, so Proxy A still sees the 200 OK. Alice's route set is Proxy B only.
  7. Alice → Proxy B: ACK. Alice sends the ACK to the first Route, Proxy B, not to her outbound proxy.
  8. Proxy B → Bob: ACK. Proxy B removes its Route entry and forwards the ACK to Bob.

Many phones have an “outbound proxy” setting that instead sends every request to one address, with no pre-loaded Route. RFC 3261 allows this, but does not recommend it. Then the requests inside the dialog also go to the outbound proxy, even when it is not in the route set, and it must forward them by their Route headers.

10.8 Request-URI and To: why they can be different

The first INVITE starts with the same URI in the Request-URI and in To:

RFC 3261 §8.1.1.1 · Request-URI
“The initial Request-URI of the message SHOULD be set to the value of the URI in the To field.”
Read the section ↗

After that, they separate. To names the user that the caller wanted, and no proxy changes it. The Request-URI names where the request goes now, and each proxy can replace it:

RFC 3261 §8.1.1.2 · To
“The To header field first and foremost specifies the desired "logical" recipient of the request, or the address-of-record of the user or resource that is the target of this request. This may or may not be the ultimate recipient of the request.”
Read the section ↗
RFC 3261 §16.6 · Request Forwarding
“The Request-URI in the copy's start line MUST be replaced with the URI for this target.”
Read the section ↗
Where Request-URI To
Alice’s INVITE sip:bob@biloxi.example Bob <sip:bob@biloxi.example>
After Proxy B (location service) sip:bob@203.0.113.20:5060 — Bob’s Contact unchanged
After call forwarding to Carol sip:carol@biloxi.example unchanged: still Bob
A BYE inside the dialog The remote target (Bob’s Contact) Bob, with his tag
A request to a strict router The strict router’s URI (10.6) unchanged

Route on the Request-URI, never on To. A proxy or B2BUA that routes on To sends a forwarded call back to the original user, and a UAS that rejects a request because To is not its own address breaks call forwarding and support lines.

RFC 3261 §16.12 · Summary of Proxy Route Processing
“If no strict-routing elements are encountered on the path of the request, the Request-URI will always indicate the target of the request.”
Read the section ↗

10.9 Loops, spirals, Max-Forwards, 482, 483

A request can reach the same proxy twice. Whether that is an error depends on what changed:

RFC 3261 §6 · Definitions (Loop)
“A request that arrives at a proxy, is forwarded, and later arrives back at the same proxy. When it arrives the second time, its Request-URI is identical to the first time, and other header fields that affect proxy operation are unchanged, so that the proxy would make the same processing decision on the request it made the first time.”
Read the section ↗
RFC 3261 §6 · Definitions (Spiral)
“A spiral is a SIP request that is routed to a proxy, forwarded onwards, and arrives once again at that proxy, but this time differs in a way that will result in a different processing decision than the original request. Typically, this means that the request's Request-URI differs from its previous arrival. A spiral is not an error condition, unlike a loop.”
Read the section ↗

A spiral is normal. Bob’s phone forwards the call to Carol: it sends the INVITE back to Proxy B with the Request-URI sip:carol@biloxi.example. Proxy B sees the request a second time, but with a new target. A loop is a fault, usually two routing tables that point at each other. A proxy that checks for loops looks for its own address in the Via headers:

RFC 3261 §16.3 · Request Validation
“If the request contains a Via header field with a sent-by value that equals a value placed into previous requests by the proxy, the request has been forwarded by this element before. The request has either looped or is legitimately spiraling through the element.”
Read the section ↗
RFC 3261 §16.3 · Request Validation
“If a loop is detected, the element MAY return a 482 (Loop Detected) response.”
Read the section ↗

Loop detection is optional. The safety net that always works is Max-Forwards: the UA starts at 70, every proxy decrements it, and the element that receives 0 stops the request.

RFC 3261 §16.6 · Request Forwarding
“If the copy contains a Max-Forwards header field, the proxy MUST decrement its value by one (1).”
Read the section ↗
RFC 3261 §16.3 · Request Validation
“If the request contains a Max-Forwards header field with a field value of zero (0), the element MUST NOT forward the request. If the request was for OPTIONS, the element MAY act as the final recipient and respond per Section 11. Otherwise, the element MUST return a 483 (Too many hops) response.”
Read the section ↗

A low Max-Forwards also works like traceroute: a request with Max-Forwards 1 is stopped at the second hop. A B2BUA starts new requests with only its own Via, so Via-based loop detection cannot see through it. It should carry Max-Forwards across, one lower (RFC 7332). If it resets Max-Forwards to 70, a loop through two B2BUAs does not stop (Module 25).

Loop: 482

A loop between two proxies: 482

  • SIP
  • ⚠Problem
Alice192.0.2.10Proxy A198.51.100.10Proxy B203.0.113.1001INVITE02100 Trying03INVITE04100 Trying05INVITE⚠06482 Loop Detected07ACK08482 Loop Detected09ACK10482 Loop Detected11ACK
01/11

Alice calls a phone number. Her phone sends the INVITE to Proxy A.

All steps as text
  1. Alice → Proxy A: INVITE. Alice calls a phone number. Her phone sends the INVITE to Proxy A.
  2. Proxy A → Alice: 100 Trying. Proxy A answers 100 Trying on this hop.
  3. Proxy A → Proxy B: INVITE. Proxy A sends calls to +1 202 numbers to Proxy B. It adds its Via.
  4. Proxy B → Proxy A: 100 Trying. Proxy B answers 100 Trying on this hop.
  5. Proxy B → Proxy A: INVITE. Proxy B has a wrong route for this number: Proxy A. The Request-URI does not change. Problem: The same request comes back to Proxy A. Without a check, it would go round until Max-Forwards reaches 0.
  6. Proxy A → Proxy B: 482 Loop Detected. Proxy A finds its own address in a Via, and nothing that affects routing has changed. That is a loop.
  7. Proxy B → Proxy A: ACK. Proxy B ACKs the 482 on this hop, with the branch of its INVITE.
  8. Proxy B → Proxy A: 482 Loop Detected. Proxy B forwards the 482 back along the Via headers.
  9. Proxy A → Proxy B: ACK. Proxy A ACKs the 482 on this hop.
  10. Proxy A → Alice: 482 Loop Detected. The 482 reaches Alice. The call fails, and the misrouted number shows in the logs.
  11. Alice → Proxy A: ACK. Alice's phone ACKs the 482.

10.10 Double Record-Route on multi-homed proxies

A multi-homed proxy has interfaces in two networks — for example, 10.1.0.1 on an office LAN and 198.51.100.10 on the Internet. One Record-Route URI can hold only one address, and each side can reach only one of them. RFC 3261 lets the proxy rewrite its Record-Route value in the response, so each side sees a different URI:

RFC 3261 §16.7 · Response Processing
“If the selected response contains a Record-Route header field value originally provided by this proxy, the proxy MAY choose to rewrite the value before forwarding the response. This allows the proxy to provide different URIs for itself to the next upstream and downstream elements. A proxy may choose to use this mechanism for any reason. For instance, it is useful for multi-homed hosts.”
Read the section ↗

RFC 5658 recommends a simpler way. The proxy adds two Record-Route values, one for each interface. Nothing changes in the response, and both sides keep the same list:

RFC 5658 §4 · Record-Route Rewriting
“Therefore, this document recommends using the double Record-Route approach to avoid rewriting the Record-Route.”
Read the section ↗
RFC 5658 §5 · Double Record-Routing
“This technique consists of inserting before any existing Record-Route header, a Record-Route header with the URI reflecting to the input interface, including schemes and/or URI parameters, and secondly, a Record-Route header with the URI reflecting to the output interface.”
Read the section ↗

Each side’s route set now has both URIs, and the reachable one comes first: Bob gets the outside address first, Alice the inside address. When a request arrives, the proxy finds itself in both top Route values and removes both:

RFC 5658 §5 · Double Record-Routing
“So, this means, in Section 16.4, "Route Information Preprocessing" [RFC3261], implementors can choose that a proxy MAY remove two Route headers instead of one when using the double Record-Routing.”
Read the section ↗

Call flow · double Record-Route

Fixed: double Record-Route

  • SIP
Alice10.1.0.20 · office LANProxy A10.1.0.1 · 198.51.100.10Proxy Bproxy.biloxi.exampleBob203.0.113.2001INVITE02INVITE03INVITE04200 OK05200 OK06200 OK07ACK08ACK09BYE10BYE11200 OK12200 OK
01/12

Alice's outbound proxy is Proxy A, on its inside address 10.1.0.1.

All steps as text
  1. Alice → Proxy A: INVITE. Alice's outbound proxy is Proxy A, on its inside address 10.1.0.1.
  2. Proxy A → Proxy B: INVITE. Proxy A adds two Record-Routes: the outside address on top, then the inside address.
  3. Proxy B → Bob: INVITE. Proxy B forwards the INVITE to Bob's Contact. It does not record-route.
  4. Bob → Proxy B: 200 OK. Bob answers. His route set is the Record-Route list in order: outside address first.
  5. Proxy B → Proxy A: 200 OK. Proxy B removes its Via and forwards the 200 OK.
  6. Proxy A → Alice: 200 OK. Alice's route set is the list reversed: inside address first. Both sides reach Proxy A.
  7. Alice → Proxy A: ACK. Alice sends the ACK to the inside address of Proxy A.
  8. Proxy A → Bob: ACK. Proxy A removes both of its Route entries and sends the ACK to Bob.
  9. Bob → Proxy A: BYE. Bob hangs up. The BYE goes to the first Route: the outside address, 198.51.100.10.
  10. Proxy A → Alice: BYE. Proxy A removes both of its Route entries and sends the BYE out of its inside interface.
  11. Alice → Proxy A: 200 OK. Alice ends the call.
  12. Proxy A → Bob: 200 OK. Proxy A forwards the 200 OK to Bob.

Open-source proxies such as Kamailio and OpenSIPS add the second Record-Route value by themselves when a request leaves on a different interface or transport (the enable_double_rr setting of their rr modules).

Common mistakes

Confusing Via with Record-Route

Both headers list proxies, so they are easy to mix up. They answer different questions:

Via Record-Route
Answers Where does the response to this request go? Which proxies see the later requests in the dialog?
Who adds it Every element that sends the request, always Only proxies that want to stay, only on the request that creates the dialog
Lifetime One transaction The whole dialog
Used by Responses, hop by hop The route set, then Route in each later request

Remember: a 200 OK passes every proxy, because it follows Via. That does not mean the BYE will. To predict the BYE, read the Record-Route of the 200 OK, not its Vias.

A proxy that does not Record-Route never sees the BYE

The proxy sees the INVITE and the 200 OK, starts a call record, and never hears of the call again. The usual symptoms: billing records with no end time or with a fixed maximum duration, calls that a dialog-stateful proxy counts as active long after they ended, and requests to a phone behind NAT that never reach it.

Broken

Broken: no Record-Route, so the BYE skips Proxy B

  • SIP
  • ⚠Problem
Alice192.0.2.10Proxy B203.0.113.10Bob203.0.113.2001INVITE02INVITE03200 OK04200 OK05ACK06BYE⚠07200 OK
01/07

Alice calls Bob. Proxy B, the billing proxy of biloxi.example, gets the INVITE.

All steps as text
  1. Alice → Proxy B: INVITE. Alice calls Bob. Proxy B, the billing proxy of biloxi.example, gets the INVITE.
  2. Proxy B → Bob: INVITE. Proxy B starts a call record and forwards the INVITE. It does not add Record-Route.
  3. Bob → Proxy B: 200 OK. Bob answers.
  4. Proxy B → Alice: 200 OK. Proxy B forwards the 200 OK. Alice's route set is empty.
  5. Alice → Bob: ACK. Alice sends the ACK straight to Bob's Contact. Proxy B does not see it.
  6. Alice → Bob: BYE. After 3 minutes, Alice hangs up. The BYE goes straight to Bob. Problem: Proxy B never sees the BYE. Its call record stays open, and the bill shows the wrong duration.
  7. Bob → Alice: 200 OK. Bob ends the call. Proxy B still counts minutes.

A Record-Route without ;lr

The proxy works for the first INVITE. But every UA now uses strict routing for the whole dialog: the proxy’s URI goes into the Request-URI, and the remote target goes to the end of Route. An RFC 3261 proxy that recognises its own URI repairs the request. A proxy that does not — for example, one behind a load balancer whose address it writes into Record-Route — routes the BYE by its Request-URI: back to itself, or to the load balancer. The call never ends cleanly.

Broken

Broken: Record-Route without lr

  • SIP
  • ⚠Problem
Alice192.0.2.10Proxy B203.0.113.10Bob203.0.113.2001INVITE02INVITE⚠03200 OK04200 OK05ACK06ACK07BYE08BYE⚠09200 OK10200 OK
01/10

Alice calls Bob. Proxy B, the billing proxy of biloxi.example, gets the INVITE.

All steps as text
  1. Alice → Proxy B: INVITE. Alice calls Bob. Proxy B, the billing proxy of biloxi.example, gets the INVITE.
  2. Proxy B → Bob: INVITE. Proxy B starts a call record and adds Record-Route, but the URI has no lr parameter. Problem: RFC 3261 requires lr in every Record-Route URI. Without it, in-dialog requests use strict routing.
  3. Bob → Proxy B: 200 OK. Bob answers. The 200 OK copies the Record-Route.
  4. Proxy B → Alice: 200 OK. Alice's route set is Proxy B, and its URI has no lr. Alice must use strict routing.
  5. Alice → Proxy B: ACK. Strict routing: Proxy B goes in the Request-URI, and Bob's Contact goes last in Route.
  6. Proxy B → Bob: ACK. Proxy B finds its own URI in the Request-URI. It moves the last Route value back into the Request-URI.
  7. Alice → Proxy B: BYE. After 3 minutes, Alice hangs up. The BYE follows the route set to Proxy B.
  8. Proxy B → Bob: BYE. Proxy B repairs the BYE again and closes the call record. A proxy that does not recognise its URI fails here. Problem: This works only if Proxy B recognises its own Record-Route URI. Many elements and B2BUAs do not.
  9. Bob → Proxy B: 200 OK. Bob ends the call.
  10. Proxy B → Alice: 200 OK. Proxy B forwards the 200 OK to Alice.

Remember: look at the Request-URI of the in-dialog requests. A proxy URI there, and the Contact at the end of Route, means strict routing — find the Record-Route URI without lr.

One Record-Route on a proxy with two interfaces

The proxy writes the address of one interface into Record-Route. The UA on the other side cannot reach that address, so its in-dialog requests are lost. Calls set up fine, then the BYE from one side never arrives — often only when that side hangs up.

Broken

Broken: one Record-Route on two interfaces

  • SIP
  • ⚠Problem
Alice10.1.0.20 · office LANProxy A10.1.0.1 · 198.51.100.10Proxy Bproxy.biloxi.exampleBob203.0.113.2001INVITE02INVITE03INVITE04200 OK05200 OK06200 OK07ACK08ACK09✕BYE⚠10✕BYE (again)⚠
01/10

Alice's outbound proxy is Proxy A, on its inside address 10.1.0.1.

All steps as text
  1. Alice → Proxy A: INVITE. Alice's outbound proxy is Proxy A, on its inside address 10.1.0.1.
  2. Proxy A → Proxy B: INVITE. Proxy A adds one Record-Route with its inside address, 10.1.0.1. The INVITE leaves on the outside interface.
  3. Proxy B → Bob: INVITE. Proxy B forwards the INVITE to Bob's Contact. It does not record-route.
  4. Bob → Proxy B: 200 OK. Bob answers. His route set is one URI: 10.1.0.1, a private address.
  5. Proxy B → Proxy A: 200 OK. Proxy B removes its Via and forwards the 200 OK.
  6. Proxy A → Alice: 200 OK. Alice's route set is 10.1.0.1. That works for her: she is on the inside.
  7. Alice → Proxy A: ACK. Alice sends the ACK to the inside address of Proxy A.
  8. Proxy A → Bob: ACK. Proxy A removes its Route entry and sends the ACK to Bob.
  9. Bob → Proxy A: BYE. Bob hangs up. His route set says 10.1.0.1: Proxy A's inside address. Problem: 10.1.0.1 is a private address. No router on the Internet can deliver the BYE.
  10. Bob → Proxy A: BYE (again). Bob's phone sends the BYE again for 32 seconds, then ends the dialog. Problem: Alice's phone still shows the call. Proxy A never sees the BYE.

Summary

  • Responses follow Via. Each element that sends a request adds a Via on top; each element that forwards the response removes its own.
  • Requests follow Route, then the Request-URI. Each proxy removes its own Route value and may replace the Request-URI.
  • Behind NAT, the next hop adds received and fills in rport. A UDP response goes to received:rport.
  • A proxy that adds Record-Route to the request that creates the dialog stays in the path of every later request. Without it, the ACK, BYE, and re-INVITE skip the proxy.
  • The callee takes the route set from Record-Route in order; the caller reverses it. Each side has its nearest proxy first.
  • Every Record-Route URI needs ;lr. Without it, the UAs use strict routing, and the request works only if the proxy repairs it.
  • An outbound proxy is best set as a pre-loaded Route. It stays in the dialog only if it record-routes.
  • To keeps the user the caller wanted; the Request-URI is where the request goes now. Route on the Request-URI.
  • A spiral is normal; a loop is a fault. Loop detection gives 482; Max-Forwards at 0 gives 483.
  • A multi-homed proxy adds two Record-Route values, one for each interface, so each side can reach it.