Docs Encapsulations

Encapsulations

Which link types, EtherTypes and tunnels sipnab can read a SIP dialog out of, which it cannot, and what it reports when a frame does not decode.

On this page

What sipnab can read a SIP dialog out of, what it cannot, and — the part that matters most — what it tells you when it cannot.

Sources. The IEEE Registration Authority assigns EtherTypes, not IANA. RFC 9542 states it plainly:

Neither EtherTypes nor LSAPs are assigned by IANA; they are assigned by the IEEE Registration Authority.

Values below come from the IEEE RA public listing at https://standards-oui.ieee.org/ethertype/eth.txt, retrieved 2026-08-04, cross-checked against IANA’s informational mirror at https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml, whose own header reads “Not assigned by IANA.”

This is the one place the project’s “when in doubt, use the RFC” rule does not resolve on its own: for EtherTypes the RFC hands the question to a different standards body. Use the IEEE RA listing for the value and the current RFC for the semantics.

Two errors in the IEEE listing

Registries are no more infallible than RFCs, and both of these would mislead someone implementing from the listing alone.

  • 0x8848 carries the wrong protocol text. The listing gives it "8847: MPLS (multiprotocol label switching) label stack - unicast" — the 0x8847 entry duplicated, wrong identifier and wrong cast. RFC 5332 assigns 0x8848 to MPLS with an upstream-assigned label.
  • 0x894F cites a superseded draft, draft-ietf-sfc-nsh-18, published as RFC 8300 in January 2018. That one is stale rather than wrong — the draft and the RFC define the same header — and the two cases do not belong in the same bucket.

Why this page exists

sipnab used to answer a question it had not understood. Given a capture whose frames it could not decode, it reported:

sipnab: 49 packets captured, 0 SIP messages, 0 RTP packets across 0 streams
No SIP traffic found. Check that the capture contains SIP packets (typically UDP port 5060-5061).

That output was identical whether sipnab had read every frame and found no SIP, or had failed to read a single frame of a capture full of INVITEs. Two such captures were in this repository’s own test corpus.

A missed encapsulation is now counted and named, with the number that identifies it — the link type, the EtherType, the IP protocol. See Troubleshooting for what each reason means.

The rule that governs decapsulation

Over-eager decapsulation is worse than the silence it replaces.

A silent drop loses a call. A false decapsulation invents one — inner addresses, inner ports, an inner dialog — assembled from bytes that never described any of it, with nothing marking the flow fictional.

This bites hardest on port-keyed tunnels. A UDP port is not a protocol identity: 2152, 4789 and 6081 all occur as the ephemeral source port of ordinary RTP media. So every decapsulator validates structurally — reserved bits, version fields, length self-consistency, the shape of the first inner header — and declines the moment anything disagrees. Each port-keyed decoder has a test proving it rejects a realistic RTP packet arriving on its own port.

When the choice is between missing a tunnel and fabricating a flow, sipnab misses the tunnel. A miss stays visible. A fabrication does not.

DLTNameStatus
1Ethernetdecoded
0BSD loopback (DLT_NULL)decoded — address family in host byte order
108OpenBSD loopback (DLT_LOOP)decoded — address family always big-endian
12Raw IP (DLT_RAW)decoded — the version nibble picks v4 or v6
113Linux cooked v1 (SLL)decoded
276Linux cooked v2 (SLL2)decoded
9PPP (DLT_PPP)decoded — with or without RFC 1662 HDLC-like framing
50PPP in HDLC-like framing (DLT_PPP_SERIAL)decoded
51PPPoE session (DLT_PPP_ETHER)decoded
228bare IPv4 (DLT_IPV4)decoded — sipnab checks the version nibble against the link type and rejects a mismatch
229bare IPv6 (DLT_IPV6)decoded — same check, the other way
any othercounted and named as unsupported link type N

Loopback is DLT_EN10MB on Linux but DLT_NULL on macOS and BSD, which is why 0 and 108 matter for the common “SIP server listening on loopback” case.

The parser dispatches on that closed set rather than on the raw number, and the cheap --cores shard key dispatches on the same one. Adding a link type is a compile error in both until each has an arm, so the two walks cannot come to know different sets.

EtherTypes

EtherTypeProtocolReferenceStatus
0x0800IPv4RFC 894decoded
0x86DDIPv6RFC 2464decoded
0x8100C-VLAN tagIEEE 802.1Qskipped to reach IP
0x88A8S-VLAN tagIEEE 802.1Qskipped to reach IP
0x9100legacy QinQunregisteredskipped to reach IP
0x8864PPPoE SessionRFC 2516decapsulated
0x8863PPPoE DiscoveryRFC 2516never decapsulated — counted and named like any EtherType sipnab does not walk
0x8847MPLS unicastRFC 5332decapsulated
0x8848MPLS upstream-assignedRFC 5332decapsulated
0x894FNSHRFC 8300decapsulated
0x88E7PBB I-TAGIEEE 802.1Qdecapsulated to the customer frame
0x88E5MACsecIEEE 802.1AEsee below
any othercounted and named with its hex value

0x9100 is genuinely absent from the IEEE RA listing — no assignee, no protocol text — while every neighbor has both. No standards body ever registered it: it is legacy vendor usage, still common on older carrier gear. Supporting it concedes to deployed equipment rather than to any specification. Do not “correct” it away after checking a registry and finding nothing.

PPPoE Discovery is deliberately never decapsulated: its payload is TLV tags, so reading it as an IP header would report addresses the wire never carried.

MACsec is not always encrypted

The SecTAG’s E and C bits distinguish confidentiality from integrity. sipnab decodes an integrity-protected frame that carries no encryption as the ordinary readable plaintext it is. Only a genuinely encrypted payload gets reported as encrypted. Treating all of 0x88E5 as opaque would write off recoverable calls.

IEEE 802.1AE’s own Annex C conformance vectors settled those bit positions, because Figure 9-4 of the 2018 standard is defective — it labels two bits with names appearing nowhere else in the document.

A cooked capture reaches fewer of them

The table above describes the Ethernet walk. A Linux cooked capture — SLL or SLL2 — runs a shorter one: VLAN tags, then IPv4, IPv6 or PPPoE Session, and nothing else. That matters more than it sounds, because SLL2 is what the any pseudo-device produces, and any is what sipnab opens on Linux when no -d names an interface.

EtherTypeEthernet (-d eth0)Cooked (any, -i any files)
0x8100 / 0x88A8 / 0x9100 VLANskipped to reach IPskipped to reach IP
0x0800 / 0x86DD IPdecodeddecoded
0x8864 PPPoE Sessiondecapsulateddecapsulated
0x8847 / 0x8848 MPLSdecapsulatednot walked
0x894F NSHdecapsulatednot walked
0x88E7 PBB I-TAGdecapsulatednot walked
0x88E5 MACsecdecoded, or named as encryptednot walked

So SIP inside an MPLS label stack reaches the parser from -d eth0 and does not from the default device. A frame the cooked walk declines invents nothing — it fails to decode and joins the undecodable count with its own reason — but the remedy is the capture device rather than the filter. Name the interface when the link carries any of the bottom four.

The same asymmetry applies to a saved file: a capture somebody took with tcpdump -i any carries the cooked link type, and no sipnab flag can put back what the walk does not follow.

EncapsulationKeyReferenceStatus
IP-in-IP / 6-in-4IP proto 4 / 41RFC 2003 / RFC 4213decoded
GREIP proto 47RFC 2784decoded
GRE Transparent Ethernet BridgingGRE proto 0x6558RFC 7637 section 3.2decoded
MPLS-in-IPIP proto 137RFC 4023decoded
AHIP proto 51RFC 4302traversed — AH authenticates without encrypting, so the payload is readable
ESPIP proto 50RFC 4303encrypted — sipnab names it, never guesses
GTP-UUDP 21523GPP TS 29.281decoded
VXLANUDP 4789RFC 7348decoded
GENEVEUDP 6081RFC 8926decoded
TeredoUDP 3544RFC 4380decoded
UDP-encapsulated ESPUDP 4500RFC 3948encrypted — sipnab names it, never guesses
L2TPv2UDP 1701RFC 2661data messages only
L2TPv3 over UDPUDP 1701RFC 3931refused — see below

L2TPv3 over UDP is deliberately not decoded. RFC 3931 section 4.1 says:

The Session ID alone provides the necessary context for all further packet processing, including the presence, size, and value of the Cookie.

The cookie runs to 0, 4 or 8 octets, and only the control channel carries that length — along with the pseudowire type. Guessing between those is precisely how a decapsulator invents a flow.

One shared budget bounds nesting. It covers every layer of a frame, so a frame combining MACsec, MPLS, GTP-U and IP-in-IP cannot walk further than one using a single encapsulation repeatedly. sipnab refuses an over-nested frame rather than following it.

Live capture needs the tunnel-aware filter

Give sipnab no BPF expression of your own — the trailing argument, or a file named by --bpf-file — and it generates one that matches SIP inside VLAN, QinQ, PPPoE Session and MPLS as well as untagged traffic. Do not confuse that with --filter, which is sipnab’s own matching language over messages sipnab already decoded, long after the kernel has made its decision.

UDP-tunneled SIP is not covered by default: BPF cannot parse a variable-length GTP-U header to reach the inner port, so covering those means capturing every packet on those ports. Use --capture-tunnels to opt in — see the CLI reference.

The encapsulated arm compiles on SLL and SLL2 as well as on Ethernet, because it selects the encapsulation through libpcap’s ether proto, which libpcap resolves to the right offset for each link type while compiling. So the kernel hands those frames up whichever device you opened. What sipnab makes of them afterwards still depends on the link type — an MPLS frame arrives on the default device and the cooked walk does not follow it, as the table above says.

Writing your own expression replaces the generated one whole. sipnab never edits it, so the encapsulated arm goes away and --capture-tunnels turns inert. Two warnings cover that: one when a port-based expression shows no sign of handling encapsulation, and one naming --capture-tunnels as ignored when you passed it alongside a filter of your own.

One limit worth knowing before you rely on a live capture: on the encapsulated arm, an IPv4 header carrying options stays unmatched, because a BPF index has to be constant and the arm cannot multiply the IHL nibble. The untagged arm handles those.

STUN and TURN

sipnab reads STUN (RFC 5389) and TURN (RFC 5766) because they are the first link in a one-way-audio chain, not because it is an ICE agent. What it takes from them is what changes a diagnosis: whether an endpoint asked, whether anything answered, and which address the answer named.

Three protocols share these ports, and they separate cleanly on the two high bits of the first byte — STUN 00, TURN ChannelData 01, RTP 10 — so checking STUN before RTP cannot swallow media.

ReadWhy
Header: type, length, magic cookie, transaction IDThe parser checks the cookie before believing anything else, which is what makes it safe to offer it every UDP payload
Class and methodBinding and TURN’s Allocate, Refresh, Send, Data, CreatePermission, ChannelBind. An unknown method reports as its number rather than as “unknown”, because the number is the thing to look up
XOR-MAPPED-ADDRESSThe answer the endpoint asked for, and what it then writes into its SDP
XOR-RELAYED-ADDRESS, XOR-PEER-ADDRESS, LIFETIMEThe TURN equivalents: the address a relay allocated, who a permission is about, and how long it lasts
ERROR-CODESo a refusal reads as a refusal. A server that says no is reachable, which is a different fault from silence
SOFTWARENames the stack, which tells one vendor’s retransmission pattern from the next
MAPPED-ADDRESS (legacy)The pre-RFC5389 form. Servers older than the cookie still answer with it, and without reading it their successful response reads as no answer at all. When both forms arrive the XOR one wins, whatever order they come in
ALTERNATE-SERVERSo a 300 redirect names where it points instead of reading as a dead end
REALM, NONCEA 401 with a realm is an authentication challenge, not a blocked path, and each sends you somewhere different. sipnab records the nonce as present only: its value is a server-chosen opaque string that means nothing to an observer
FINGERPRINTVerified. A CRC-32 over the message with no key involved, so a passive reader can check it honestly – and it is what separates a real STUN message from a payload that merely carried the cookie bytes. Reported as verified, present-and-wrong, or absent, which stay distinct
ICE USE-CANDIDATE, PRIORITY, ICE-CONTROLLING/ICE-CONTROLLEDThe nomination is the finding: without it, an ICE exchange that converged and one that never did look alike. Both sides claiming controlling is a role conflict whose only other symptom is media that never starts
TURN CHANNEL-NUMBER, REQUESTED-TRANSPORTSo sipnab can describe a relay path, not merely detect one
ChannelData framingsipnab recognizes and unwraps it, so RTP relayed through TURN reaches reconstruction. Without that a relayed call reports as having no media — the same answer sipnab gives for a call that carried none, which are opposite findings

sipnab tracks transactions, so it reports a request that never came back along with the number of attempts. A retransmission is one unanswered question, not several. An error response counts as ANSWERED.

What is deliberately not read

MESSAGE-INTEGRITY and MESSAGE-INTEGRITY-SHA256 decide whether a message is authentic, and a passive observer has no credentials to check them with. Reading them and reporting anything about them would be claiming a verification that did not happen — the same confident-wrong-answer this codebase refuses elsewhere. sipnab reads USERNAME, REALM and NONCE for what they SAY — an authentication challenge is a different fault from a blocked path — never as evidence that authentication succeeded.

sipnab is not an ICE agent and does not evaluate candidate pairs, compute priorities, or decide nominations. It reports what it saw on the wire.

What “not supported” means here

sipnab drops nothing on this page silently. An encapsulation sipnab does not decode still increments a counter keyed by the number it did not recognize, and that count reaches the run summary, --report, --json, /v1/stats, MCP capture_status, and the Prometheus capture family. A capture producing no SIP and no undecodable frames is a finding. One producing no SIP and a pile of undecodable frames says something else entirely, and sipnab says it differently.

How this page stays true

An end-to-end test backs every “decoded” row for a tunnel, in tests/tunnel_integration_test.rs that carries a real INVITE through parse_packet into the SIP parser and asserts the method and Call-ID — MPLS, MPLS-in-IP, NSH, PBB, MACsec (integrity-only), VXLAN, GTP-U, GRE-TEB and AH each have one. PPPoE and the loopback link types have their own suites. GENEVE, Teredo and L2TP rely on unit tests over crafted frames rather than an end-to-end INVITE. That is a weaker proof, and this page says so rather than letting the table imply otherwise.

No measurement on this page comes from a live NIC.