E.164, SIP URIs and ENUM: how a dialled number becomes a route
Short version: a phone number is a name, not an address. Every call spends its first milliseconds having that name translated — normalised to E.164, maybe looked up in ENUM or a portability database, wrapped in a URI, and resolved through DNS to an actual host. Most "wrong number" faults are one of those translations going quietly wrong, and the evidence is the Request-URI mutating hop by hop.
E.164: the global numbering plan
ITU-T E.164 defines the shape: at most 15 digits — a country code (1–3 digits),
then a national destination code and subscriber number whose split each country
chooses. +33 6 12 34 56 78 is a French mobile; the +
means "fully qualified, no dialling prefixes". The trouble is that users do not
dial E.164 — they dial 0612345678, or an extension, or with a trunk
access 9 in front. Somewhere, every network runs normalisation rules
turning dialled strings into canonical + numbers, and those rules —
usually regex on an SBC or PBX —
are the most-edited, most-breakable config in voice. A number that works dialled
one way and fails another is a normalisation bug, essentially always.
Two URI shapes for the same number
tel:+33612345678 (RFC 3966) names the number itself, no host —
"someone will have to route this". sip:[email protected];user=phone
names the number at a domain: the user=phone parameter is the
flag that says "the user part is a telephone number, not an account name". Networks
flip between the two constantly, and the choice matters at borders: a
tel: URI forces the next hop to make a routing decision, while a
sip: URI has already made one. Alongside them travels identity:
From is what the caller claimed;
P-Asserted-Identity is what the network verified — which is
why call screening and CLI features trust the latter.
How a SIP URI's domain becomes a host: RFC 3263
| Step | Lookup | Decides |
|---|---|---|
| 1 | NAPTR for the domain | Which transports the domain offers and in what order of preference (SIP+D2T → TCP, SIP+D2U → UDP, SIPS+D2T → TLS), and the SRV name to use next. |
| 2 | SRV (_sip._tcp.domain etc.) | Which hosts serve that transport, with priorities and weights — this is where failover and load-sharing live. |
| 3 | A/AAAA | The actual addresses. |
Two field notes. First, failover behaviour "not working" between SIP peers is very often just a client that skips NAPTR/SRV and does a bare A lookup — perfectly legal, silently different. Second, when a trace shows a request going to a host no configuration mentions, run the RFC 3263 ladder by hand before blaming routing: SRV weights may be doing exactly what they were told.
ENUM: the beautiful idea that half-survived
ENUM (RFC 6116) mapped E.164 into DNS: reverse the digits, dot-separate, append
e164.arpa — 8.7.6.5.4.3.2.1.6.3.3.e164.arpa — and publish
NAPTR records saying "this number is reachable at
sip:[email protected]". Public ENUM assumed subscribers would want
their numbers globally mapped to internet endpoints; regulators, carriers and spam
reality disagreed, and the public tree is essentially empty. But the
mechanism thrives privately: carrier-internal and consortium ENUM roots
are how large operators decide "is this number on-net, and where?", and an
infrastructure ENUM dip is the first step of many interconnect routing engines.
When an on-net call inexplicably breaks out to the PSTN and back, a stale private
ENUM zone is a leading suspect.
Number portability: the database in the middle
Porting broke "the prefix tells you the operator" forever. Someone in the call path
must dip a portability database and learn the routing number of the
current operator; in SIP the result rides tel-URI parameters from RFC 4694 —
rn= (route to this network) and npdi (the dip was done,
do not dip again). A missing npdi causes double dips and
double-charging arguments; a wrong rn sends the call to the donor
network, which may loop it, reject it, or — worst — complete it with wrong billing.
If a ported number misbehaves, find who did the dip and what rn they
attached: it is right there in the Request-URI.
STIR/SHAKEN: signing the caller identity
Because From was always forgeable, RFC 8224 adds an
Identity header: the originating operator signs the calling and called
numbers (a PASSporT — a JWT with a certificate chain rooted in the numbering
authority) with an attestation level: A "this is my subscriber and
their number", B "my subscriber, can't vouch for the number",
C "it merely transited me". Terminating networks verify and feed
the result into spam scoring and the little "verified caller" checkmarks.
Deployment reality: strong in North America by mandate, patchy elsewhere, and
broken signatures at interconnects (a B2BUA that touched a signed field) are a
brand-new failure class riding old plumbing.
Reading a numbering fault in a trace
- Follow the Request-URI hop by hop: dialled string → normalised E.164 →
rn-decorated → final. The hop where it goes wrong names the guilty box. - Compare
From,P-Asserted-Identityand anyIdentityheader — three versions of "who is calling", and the disagreements are informative. - 404/484 from a proxy that should know the number: check digits and
user=phonefirst, portability second, stale ENUM third.
hiccup shows the Request-URI's evolution across every hop and leg in one ladder, so the exact box that mangled the number is a glance away. Self-hosted, free for individual users.
upload a trace