Theoretical foundations and architecture
The foundational Data Plane operations in MPLS are Push, Swap, and Pop. Push occurs at the Ingress PE: an inner service label (signaled by MP-BGP) is imposed, followed by an outer transport label (signaled by LDP or SR). Swap occurs on transit P routers: the LSR inspects the incoming top label in the LFIB and swaps it with the outgoing label bound to the next hop. Pop strips the top label. A vital core optimization is Penultimate Hop Popping (PHP, RFC 3031): the second-to-last router removes the outer transport label before handing the packet to the egress PE. This is triggered when the egress PE advertises an Implicit Null label (value 3). Consequently, the egress PE receives the packet with only the inner service label (or raw IP), eliminating a double lookup cycle. If end-to-end QoS TC preservation is mandatory, the egress PE advertises Explicit Null (value 0 for IPv4, 2 for IPv6), preserving the outer shim header on the final link.
Three operations on the label stack
MPLS forwards a packet based on the top entry of the stack (architecture: RFC 3031, 32-bit entry format: RFC 3032 - Label 20 bits, TC 3 bits, S 1 bit, TTL 8 bits). Every LSR decision boils down to one of three operations:
| Operation | Where it happens | What it does to the stack | Where to look |
|---|---|---|---|
| Push | Ingress LER (PE1); also the head end of a tunnel (TE, FRR) | Adds one or more entries on top of the IP packet or of the existing stack | Ingress FIB/CEF: the prefix has an outgoing label |
| Swap | Transit LSR (P1) | Replaces the top label with the one the next neighbor (downstream) advertised for this FEC | LFIB: incoming label -> outgoing label, interface, next hop |
| Pop | Penultimate LSR (PHP) or egress | Removes the top entry; if it was the last one (S=1), the packet leaves as plain IP | LFIB: outgoing value Pop Label / Pop / 3 |
Push is decided by the FIB (longest prefix match on the IP address); Swap and Pop are decided by the LFIB (exact match on the label). This is why a transit P router does not need a BGP table: the LFIB is enough.
Reserved labels 0-15
Labels 0-15 are not allocated to FECs (RFC 3032, RFC 7274):
| Value | Name | Meaning |
|---|---|---|
| 0 | IPv4 Explicit Null | The label must be removed, then an IPv4 lookup is done; the entry stays on the wire |
| 1 | Router Alert | Hand the packet to the control plane, then forward it further if needed |
| 2 | IPv6 Explicit Null | The same for IPv6 |
| 3 | Implicit Null | Never appears in a packet; a signaling hint in LDP/RSVP: "upstream, pop the label yourself" |
| 7 | ELI | Entropy Label Indicator (RFC 6790) |
| 13 | GAL | Generic Associated Channel Label (RFC 5586) |
| 15 | Extension | Label space extension (RFC 7274) |
Label 3 is "virtual": in an LDP Label Mapping (message type 0x0400) it is carried in the Generic Label TLV (type 0x0200) as an ordinary 20-bit value, but it never exists in packets on the wire. Dynamic labels by default start at 16 on Cisco IOS-XE, around 299776 for LDP on Junos, and at 1024 on Huawei VRP; the exact numbers in your lab differ, so verify the chain incoming -> outgoing from your own bindings.
PHP: what happens on the penultimate hop
The egress LSR for a FEC (here PE2 for 10.255.0.2/32) chooses which label to advertise to its upstream neighbor in the Label Mapping. For its own connected prefixes, including loopbacks, Implicit Null (3) is advertised by default. The penultimate node P2, having received a mapping with label 3, programs an LFIB entry "incoming label -> Pop, next hop PE2". When a packet arrives at P2, it removes the label and sends the packet to PE2 without it (if another entry, for example a VPN label, was underneath, it is now on top).
Why it exists. Without PHP the egress PE2 would receive a frame with the transport label, perform lookup 1 in the LFIB (label -> "pop and look at IP") and then lookup 2 in the FIB (IP). With PHP only one lookup remains. In an L3VPN the gain is more visible: PE2 receives a frame with only the VPN label and goes straight to the right VRF without spending a pass on the transport label.
The "per FEC and per advertiser" principle: the egress for a given FEC decides which label type is advertised. PE2's loopback is advertised with label 3, PE1's loopback also with 3, but different nodes perform PHP: for 10.255.0.2/32 it is P2, for 10.255.0.1/32 it is P1. Therefore enabling explicit null only on PE2 changes behavior toward PE2 and does not affect traffic toward PE1.
Explicit Null: what changes on the wire
If the egress advertised label 0 (IPv4 Explicit Null), P2 does not pop the label but performs a Swap to 0. On the P2-PE2 link the packet goes out as [0 | IP]: EtherType 0x8847, one MPLS entry with Label=0, S=1, TTL decremented by one relative to the incoming entry. PE2 sees label 0, removes it and does an IP lookup; the TC and TTL of that entry are available to it. If another entry lies below the null label (S=0 on label 0), PE2 handles the next one after removing it.
Cost: the egress performs an extra pipeline pass, and 4 bytes per packet are added to the P2-PE2 link (watch the MTU). Benefit: the egress receives the TC (and TTL) of the outer entry.
TC/QoS: why explicit null is needed
With PHP the outer entry is removed on P2 together with its TC field (3 bits), so the egress cannot classify the packet by the provider TC of the transport label. DiffServ tunneling models (RFC 3270):
- Uniform: TC is copied from DSCP on Push and back into DSCP on Pop. Works with PHP, but the customer marking may be altered by the provider.
- Short Pipe: the egress classifies by the customer DSCP (the IP header or the inner layer); the provider TC is not needed. Compatible with PHP; the most common option for L3VPN.
- Pipe: the egress must see the provider TC to apply an outgoing-interface policy by the operator's class without changing the customer DSCP. PHP does not fit this: explicit null is needed, label 0 carries the TC to the egress.
TTL on push, swap and pop
The ingress copies the IP TTL into the MPLS entry (propagate), decremented by 1; every swap decrements the TTL by 1; on pop (PHP) the value is returned to the IP TTL. Consequence for traceroute: in Uniform mode the core (P1, P2) is visible as hops. If propagation is disabled (Cisco no mpls ip propagate-ttl, Junos no-propagate-ttl), traceroute does not show the P routers. When TTL reaches 0 on a transit LSR the packet is not silently dropped: P generates ICMP Time Exceeded.
How to observe the operations
- Ingress PE1 FIB: 10.255.0.2/32 has an outgoing label (CEF shows the label in the nexthop; on Junos it is
Pushin inet.3) - this is Push. - P1 LFIB: incoming label -> outgoing label, interface and next hop - this is Swap.
- P2 LFIB: outgoing value
Pop Label(Cisco),Pop(Junos),3(Huawei) - this is PHP; with explicit null instead of Pop:explicit-n(Cisco),Swap 0(Junos),0(Huawei). - LDP bindings: on PE2 the local binding is
imp-null(exp-nullafter the change), on P2 the remote binding from PE2 equals 3 (0 after the change). - Capture (Wireshark, filter
mpls): on PE1-P1 entry [17], on P1-P2 entry [18], on P2-PE2 without explicit null a frame without MPLS (EtherType 0x0800), with explicit null an entry with Label=0. TTL drops by one on each segment.
Platforms and limitations
- Cisco IOS-XE:
mpls ldp explicit-nullin global mode; it applies to labels the router advertises as egress. The command accepts a prefix filter (for <prefix-acl>); check the syntax in your release. - Juniper Junos:
set protocols ldp explicit-null. The commandset protocols mpls explicit-nullbelongs to RSVP signaling; do not confuse them. - Huawei VRP: egress behavior is set by
label advertise { explicit-null | implicit-null | non-null };non-nullmeans a real label and no PHP. - MikroTik RouterOS v7: the
use-explicit-nullparameter in/mpls/ldp(default no).
Failure modes
- A capture on the last link shows "bare" IP and the engineer decides MPLS is broken: this is normal with PHP; look at the previous link.
- Explicit null is enabled on only one PE: the TC reaches the egress in one direction only, QoS becomes asymmetric.
- PE2 advertises not Implicit Null but an ordinary label (
non-nullon Huawei): P2 does Swap instead of Pop, and the link carries 4 extra bytes and an additional lookup. - A P2-PE2 link with MTU 1500 and no headroom: after switching to explicit null every packet grows by 4 bytes, and large frames with DF=1 start to be lost.
- Comparing with illustrations: label numbers differ between platforms; verify the logic of the chain, not the numbers.
Architecture Diagram & Topology
+---------+ +----------------------+ +--------------------+ +--------------------+ +--------------------+ +---------+
| CE-1 |-->| PE-1 (Ingress) |-->| P-1 (Transit) |-->| P-2 (Penultimate) |-->| PE-2 (Egress) |-->| CE-2 |
| Cust IP | | PUSH Service: 24001 | | SWAP: 16002->16012 | | POP transport(PHP) | | POP Service: 24001 | | Cust IP |
| | | PUSH Transport:16002 | | Core LSR switching | | sends [24001 | IP] | | Lookup in VRF | | |
+---------+ +----------------------+ +--------------------+ +--------------------+ +--------------------+ +---------+