After three stages of IGP work, SP-A has a clean, dual-stack OSPF core with only loopbacks in the routing table. How does traffic traverse the core without an IP lookup at every hop? The answer is Label Distribution Protocol, and this stage puts LDP on every SP-A node in a way that’s designed to be readable during live troubleshooting.
Before configuring LDP, I decided to post the final state after the configuration was completed. Here is A-P1’s LFIB:
A-P1#show mpls forwarding-table
Local Outgoing Prefix Bytes Label Outgoing Next Hop
Label Label or Tunnel Id Switched interface
1100 Pop Label 10.1.0.2/32 216728 Gi2 10.1.1.2
1101 Pop Label 10.1.0.3/32 217696 Gi3 10.1.1.18
1102 Pop Label 10.1.0.4/32 218912 Gi4 10.1.1.14
1103 Pop Label 10.1.0.5/32 20354 Gi5 10.1.1.22
1104 1404 10.1.0.6/32 11692 Gi4 10.1.1.14
1304 10.1.0.6/32 0 Gi3 10.1.1.18
1105 Pop Label 10.1.0.7/32 0 Gi6 10.1.1.38
1106 Pop Label 10.1.0.8/32 216978 Gi7 10.1.1.42
A-P1 uses labels in the 1100 range. From the label value alone, we know immediately that this is local binding. Label 1104 is the one transit entry: a swap to A-P4’s 1404 (or A-P3’s 1304) toward A-PE2, the only loopback here not directly connected to A-P1. Everything else (1100-1103, 1105, 1106) is a penultimate-hop pop toward a directly connected neighbor.
Topology file: topology.clab.yml · Addressing: ipam.md · Stage configs, all eight SP-A nodes: stage_configs/lab01-s2a-spa-ldp/
The Label Scheme: N×100+1000
The lab uses a per-node label range: each SP-A node gets a 100-label window starting at N×100+1000, where N is the node’s router ID’s last octet.
| Node | Loopback | Label Range |
|---|---|---|
| A-P1 | 10.1.0.1 | 1100-1199 |
| A-P2 | 10.1.0.2 | 1200-1299 |
| A-P3 | 10.1.0.3 | 1300-1399 |
| A-P4 | 10.1.0.4 | 1400-1499 |
| A-PE1 | 10.1.0.5 | 1500-1599 |
| A-PE2 | 10.1.0.6 | 1600-1699 |
| A-RR | 10.1.0.7 | 16700-16799 (explanation below) |
| A-ASBR | 10.1.0.8 | 1800-1899 |
Outgoing label 1404 anywhere in the network will show that A-P4 assigned it. On IOS-XE that range is a single command:
mpls label range 1100 1199
LDP then assigns labels from that base for locally significant FEC-to-label bindings. The LIB is populated through label distribution – each neighbor advertises bindings for every prefix it knows. The LFIB installs one of them per FEC: the label advertised by the downstream LSR, which is the neighbor the FIB already picked as next-hop toward that prefix.
The OSPF Foundation
LDP uses the IP routing table as its FEC database. Each prefix in the routing table becomes a FEC for which LDP allocates a local label and distributes bindings to neighbors. The prerequisite is therefore IP reachability – specifically, loopback-to-loopback reachability, which previous configuration stage established.
The LDP router-ID follows the same convention as OSPF: Loopback0. With mpls ldp router-id Loopback0 force, the LDP identifier is pinned to the loopback address regardless of which interface comes up first at boot. Without force, LDP may briefly form a session under a different address until OSPF converges, which can cause unnecessary session resets on reload.
mpls ldp router-id Loopback0 force
Label Filtering: /32 Hosts Only
By default, LDP allocates a local label for every prefix in the routing table and advertises the binding to every peer. The OSPF RIB carries only loopbacks, however, the label filter is configured anyway:
mpls ldp label
allocate global host-routes
The command filters at allocation. A non-/32 FEC gets no local label, and with no local label there is no binding to advertise.
The previous stage keeps the transit /30s out of the OSPF RIB, so in my case this doesn’t change anything.
A-ASBR shows eight LIB entries, one per SP-A loopback.
A-ASBR#show mpls ldp bindings | i 10.1.0
lib entry: 10.1.0.1/32, rev 2
remote binding: lsr: 10.1.0.1:0, label: imp-null
remote binding: lsr: 10.1.0.4:0, label: 1400
lib entry: 10.1.0.2/32, rev 4
remote binding: lsr: 10.1.0.1:0, label: 1100
remote binding: lsr: 10.1.0.4:0, label: 1401
lib entry: 10.1.0.3/32, rev 6
remote binding: lsr: 10.1.0.1:0, label: 1101
remote binding: lsr: 10.1.0.4:0, label: 1402
lib entry: 10.1.0.4/32, rev 8
remote binding: lsr: 10.1.0.1:0, label: 1102
remote binding: lsr: 10.1.0.4:0, label: imp-null
lib entry: 10.1.0.5/32, rev 10
remote binding: lsr: 10.1.0.1:0, label: 1103
remote binding: lsr: 10.1.0.4:0, label: 1403
lib entry: 10.1.0.6/32, rev 12
remote binding: lsr: 10.1.0.1:0, label: 1104
remote binding: lsr: 10.1.0.4:0, label: 1404
lib entry: 10.1.0.7/32, rev 14
remote binding: lsr: 10.1.0.1:0, label: 1105
remote binding: lsr: 10.1.0.4:0, label: 1405
lib entry: 10.1.0.8/32, rev 16
remote binding: lsr: 10.1.0.1:0, label: 1106
remote binding: lsr: 10.1.0.4:0, label: 1406
Since A-ASBR has two uplinks, we see specific labels assigned to the loopbacks. A-P1 bindings start with 1100, A-P4 bindings start with 1400. imp-null for directly connected loopbacks.
OSPF-LDP Synchronization: Fixing the Black-Hole Window
Without LDP-IGP synchronization, there is a convergence window during which traffic can be dropped.
- A link flap occurs – OSPF converges in approximately 2 seconds (with fast hello timers and BFD, usually less).
- OSPF installs a new best path and updates the FIB.
- LDP has not yet re-established a session or exchanged bindings over the new path – this takes a few seconds.
OSPF-LDP synchronization addresses this by holding the OSPF adjacency in a “holddown” state – advertising the link with maximum cost (0xFFFF) – until LDP signals that the session is established and label bindings are exchanged. The router is present in the topology but not used as a transit path until MPLS forwarding is ready.
router ospf 1
mpls ldp sync
Applied to all MPLS-enabled interfaces in SP-A. Verification from A-P1:
A-P1#show mpls ldp igp sync
GigabitEthernet2:
LDP configured; LDP-IGP Synchronization enabled.
Sync status: sync achieved; peer reachable.
Sync delay time: 0 seconds (0 seconds left)
IGP holddown time: infinite.
Peer LDP Ident: 10.1.0.2:0
IGP enabled: OSPF 1
GigabitEthernet3:
LDP configured; LDP-IGP Synchronization enabled.
Sync status: sync achieved; peer reachable.
Sync delay time: 0 seconds (0 seconds left)
IGP holddown time: infinite.
Peer LDP Ident: 10.1.0.3:0
IGP enabled: OSPF 1
GigabitEthernet4:
LDP configured; LDP-IGP Synchronization enabled.
Sync status: sync achieved; peer reachable.
Sync delay time: 0 seconds (0 seconds left)
IGP holddown time: infinite.
Peer LDP Ident: 10.1.0.4:0
IGP enabled: OSPF 1
GigabitEthernet5:
LDP configured; LDP-IGP Synchronization enabled.
Sync status: sync achieved; peer reachable.
Sync delay time: 0 seconds (0 seconds left)
IGP holddown time: infinite.
Peer LDP Ident: 10.1.0.5:0
IGP enabled: OSPF 1
GigabitEthernet6:
LDP configured; LDP-IGP Synchronization enabled.
Sync status: sync achieved; peer reachable.
Sync delay time: 0 seconds (0 seconds left)
IGP holddown time: infinite.
Peer LDP Ident: 10.1.0.7:0
IGP enabled: OSPF 1
GigabitEthernet7:
LDP configured; LDP-IGP Synchronization enabled.
Sync status: sync achieved; peer reachable.
Sync delay time: 0 seconds (0 seconds left)
IGP holddown time: infinite.
Peer LDP Ident: 10.1.0.8:0
IGP enabled: OSPF 1
A-P1#
All six MPLS-enabled interfaces on A-P1 report sync achieved. IGP holddown time: infinite means the link cost penalty persists until LDP explicitly signals sync – there’s no timer that would allow traffic to return before MPLS forwarding is ready.
Session Protection: Targeted Hellos
LDP discovery uses link-local multicast hellos. When a physical link goes down the hello adjacency is lost, the session riding on it tears down, and every binding learned from that peer is flushed from the LIB and the LFIB. They are relearned only after the session comes back and the bindings are exchanged again.
Session protection keeps the LDP session alive across link failures by maintaining a targeted (unicast) hello between peers in addition to the link-local hello. A targeted hello uses the router’s loopback as the source and destination, and it survives as long as the loopbacks are reachable through any path – even if the direct link is down. The result: when the direct link recovers, LDP sync is achieved almost immediately because the session never dropped and the bindings were preserved.
mpls ldp session protection
This single command enables session protection for all peers. The router sends a targeted hello toward each LDP neighbor it discovers via link-local hellos. When the link goes down, it maintains the targeted hello via the IGP-computed alternate path.
That is the whole configuration. Session protection already permits the targeted hellos belonging to the peers it protects; the standalone accept control is for hellos arriving from routers you have no protected session with. On IOS-XR, this also lives somewhere else – under mpls ldp, then address-family ipv4, then discovery targeted-hello accept. It is a sub-mode command there and a global on IOS-XE.
A-P1’s targeted hello state, showing both active (initiated by this router) and passive (accepted from the peer) sides. This capture comes from the first verification run, before the A-RR problem was fixed.
A-P1#show mpls ldp neighbor detail | include Targeted
Targeted Hello 10.1.0.1 -> 10.1.0.8, active, passive;
Targeted Hello 10.1.0.1 -> 10.1.0.2, active, passive;
Targeted Hello 10.1.0.1 -> 10.1.0.3, active, passive;
Targeted Hello 10.1.0.1 -> 10.1.0.4, active, passive;
Targeted Hello 10.1.0.1 -> 10.1.0.5, active, passive;
active means A-P1 is sending; passive means it’s also accepting hellos from the peer’s side. Both sides maintain it, so session protection is symmetric across every peer listed.
Five targeted hellos. A-P1 has six MPLS-enabled interfaces. 10.1.0.7 is A-RR, and this is the first place where the problem was seen. Session protection targets the peers it has discovered through link-local hellos. A peer whose session never came up is never discovered, so nothing is ever targeted toward it.
OSPF IP FRR: Syntax, Scope, and Single-Link Limits
With LDP providing labeled transport, OSPF IP FRR (Loop-Free Alternates) provides sub-50ms convergence for labeled paths by pre-computing backup routes. The IOS-XE configuration:
router ospf 1
fast-reroute per-prefix enable area 0 prefix-priority high
Three things matter here:
The area parameter must match the node’s actual OSPF area. The line above is A-P1’s, and area 0 scopes the LFA computation to prefixes learned through Area 0. SP-A is multi-area, so a node outside the core needs its own area number in that position. Name an area the router does not participate in and OSPF computes zero protected prefixes. The command is accepted either way, the config reads as if protection is on, and show ip ospf fast-reroute is the only place that it will be visible.
prefix-priority high limits coverage to /32 host routes. In IOS-XE OSPF, prefix priorities map to route types: high = host routes (/32 and /128), low = everything else. For SP-A’s MPLS core, the only forwarding entries that matter for labeled transport are loopbacks – that’s where LSPs terminate. Computing LFA backups for transit subnets would add overhead.
A single-uplink node gets no LFA, so it gets no LFA config either. Classic LFA needs a neighbor whose own shortest path to the destination does not come back through this router, and A-RR has exactly one neighbor – A-P1. There is no alternate path to compute, so no fast-reroute line was applied to A-RR. This is a limitation of classic LFA. RLFA or TI-LFA would cover it. TI-LFA needs SR-MPLS, which we’re planning to run inside SP-B. Single-uplink nodes in this provider have no pre-computed backup.
From A-P1 after all the configuration was applied:
A-P1#show ip ospf fast-reroute
OSPF Router with ID (10.1.0.1) (Process ID 1)
Loop-free Fast Reroute protected prefixes:
Area Topology name Priority Remote LFA Enabled TI-LFA Enabled
0 Base High No No
Repair path selection policy tiebreaks (built-in default policy):
0 post-convergence
10 primary-path
20 interface-disjoint
30 lowest-metric
40 linecard-disjoint
50 broadcast-interface-disjoint
256 load-sharing
OSPF/RIB notifications:
Topology Base: Notification Disabled, Callback Not Registered
Last SPF calculation started 04:41:49 ago and was running for 2 ms.
The LFA computation is scoped to Area 0 and restricted to high-priority prefixes, which are the /32 loopbacks.
Issue: A-RR LDP Session Never Formed
The first verification run after applying configs showed A-RR’s LDP session with A-P1 was not established:
show mpls ldp neigh returns nothing:
RP/0/RP0/CPU0:A-RR#show mpls ldp neigh
Fri May 1 18:50:02.774 UTC
RP/0/RP0/CPU0:A-RR#show mpls ldp bind | i 10.1.0
Fri May 1 18:50:06.620 UTC
10.1.0.1/32, rev 12
10.1.0.2/32, rev 13
10.1.0.3/32, rev 14
10.1.0.4/32, rev 15
10.1.0.5/32, rev 17
10.1.0.6/32, rev 18
10.1.0.7/32, rev 2
10.1.0.8/32, rev 16
RP/0/RP0/CPU0:A-RR#show mpls ldp igp sync
Fri May 1 18:50:10.025 UTC
GigabitEthernet0/0/0/0:
VRF: 'default' (0x60000000)
Sync delay: Disabled
Sync status: Not ready (No peer session)
RP/0/RP0/CPU0:A-RR#show mpls forwar
Fri May 1 18:50:15.304 UTC
Local Outgoing Prefix Outgoing Next Hop Bytes
Label Label or ID Interface Switched
------ ----------- ------------------ ------------ --------------- ------------
16700 Unlabelled 10.1.0.1/32 Gi0/0/0/0 10.1.1.37 111696
16701 Unlabelled 10.1.0.2/32 Gi0/0/0/0 10.1.1.37 0
16702 Unlabelled 10.1.0.3/32 Gi0/0/0/0 10.1.1.37 0
16703 Unlabelled 10.1.0.4/32 Gi0/0/0/0 10.1.1.37 0
16704 Unlabelled 10.1.0.8/32 Gi0/0/0/0 10.1.1.37 0
16705 Unlabelled 10.1.0.5/32 Gi0/0/0/0 10.1.1.37 0
16706 Unlabelled 10.1.0.6/32 Gi0/0/0/0 10.1.1.37 0
A-RR knew the routes and had allocated its own labels from the 16700 block. Every outgoing label reads Unlabelled, so those seven prefixes were being IP-forwarded.
A-P1’s side of the same link, from the pre-fix capture. Gi6 is the interface toward A-RR; Gi7 toward A-ASBR:
GigabitEthernet6:
LDP configured; LDP-IGP Synchronization enabled.
Sync status: sync not achieved; peer reachable.
Sync delay time: 0 seconds (0 seconds left)
IGP holddown time: infinite.
IGP enabled: OSPF 1
GigabitEthernet7:
LDP configured; LDP-IGP Synchronization enabled.
Sync status: sync achieved; peer reachable.
Sync delay time: 0 seconds (0 seconds left)
IGP holddown time: infinite.
Peer LDP Ident: 10.1.0.8:0
IGP enabled: OSPF 1
Gi6 says sync not achieved and carries no Peer LDP Ident line. The field is absent, where Gi7, below has Peer LDP Ident: 10.1.0.8:0. LDP is configured, the IGP neighbor is reachable, and there is no LDP identity on the far end. A-P1’s forwarding table agreed:
A-P1#show mpls forwarding-table
Local Outgoing Prefix Bytes Label Outgoing Next Hop
Label Label or Tunnel Id Switched interface
1100 Pop Label 10.1.0.2/32 199692 Gi2 10.1.1.2
1101 Pop Label 10.1.0.3/32 200752 Gi3 10.1.1.18
1102 Pop Label 10.1.0.4/32 202042 Gi4 10.1.1.14
1103 Pop Label 10.1.0.5/32 0 Gi5 10.1.1.22
1104 1404 10.1.0.6/32 0 Gi4 10.1.1.14
1304 10.1.0.6/32 0 Gi3 10.1.1.18
1105 No Label 10.1.0.7/32 0 Gi6 10.1.1.38
1106 Pop Label 10.1.0.8/32 200106 Gi7 10.1.1.42
1105 No Label 10.1.0.7/32 – A-P1 allocated a local label for A-RR’s loopback and had nothing to swap it to. The link was up, OSPF was established, and LDP was not forming.
Root cause: All seven IOS-XE nodes in SP-A have mpls ldp password fallback LDP_AUTH, which mandates TCP MD5 authentication on all LDP sessions. A-RR (IOS-XR) had no LDP password configured. The TCP handshake for the LDP session fails with an MD5 authentication mismatch before the LDP session can form.
The key platform difference: IOS-XR has no password fallback command. The equivalent is a default neighbor block:
mpls ldp
neighbor
password clear LDP_AUTH
The neighbor keyword without an IP address creates a default configuration block that applies to all LDP peers. A per-neighbor entry (neighbor 10.1.0.1 password clear LDP_AUTH) would also work but requires listing every peer explicitly. This is the exact equivalent of IOS-XE’s fallback.
In a mixed XE/XR domain, password fallback on the XE side blocks any XR peer without matching authentication. OSPF stays up on the link, so every interface-level check looks healthy. It shows in one place: show mpls ldp igp sync has no Peer LDP Ident line for that interface.
After the fix, A-RR came up correctly:
RP/0/RP0/CPU0:A-RR#show mpls ldp neighbor
Fri May 1 19:01:31.535 UTC
Peer LDP Identifier: 10.1.0.1:0
TCP connection: 10.1.0.1:646 - 10.1.0.7:64436; MD5 on
Graceful Restart: No
Session Holdtime: 180 sec
State: Oper; Msgs sent/rcvd: 12/11; Downstream-Unsolicited
Up time: 00:00:24
LDP Discovery Sources:
IPv4: (2)
GigabitEthernet0/0/0/0
Targeted Hello (10.1.0.7 -> 10.1.0.1, active)
IPv6: (0)
Addresses bound to this peer:
IPv4: (7)
10.1.0.1 10.1.1.1 10.1.1.13 10.1.1.17
10.1.1.21 10.1.1.37 10.1.1.41
IPv6: (0)
MD5 on confirms the authentication is active. A-P1’s Gi6 flipped to sync, and the forwarding entry for 10.1.0.7/32 changed from No Label to Pop Label (PHP, since A-RR is directly connected).
Post-fix A-RR LFIB:
RP/0/RP0/CPU0:A-RR#show mpls forwarding
Fri May 1 19:05:26.608 UTC
Local Outgoing Prefix Outgoing Next Hop Bytes
Label Label or ID Interface Switched
------ ----------- ------------------ ------------ --------------- ------------
16700 Pop 10.1.0.1/32 Gi0/0/0/0 10.1.1.37 2656
16701 1100 10.1.0.2/32 Gi0/0/0/0 10.1.1.37 0
16702 1101 10.1.0.3/32 Gi0/0/0/0 10.1.1.37 0
16703 1102 10.1.0.4/32 Gi0/0/0/0 10.1.1.37 0
16704 1106 10.1.0.8/32 Gi0/0/0/0 10.1.1.37 0
16705 1103 10.1.0.5/32 Gi0/0/0/0 10.1.1.37 0
16706 1104 10.1.0.6/32 Gi0/0/0/0 10.1.1.37 0
All seven entries now carry an outgoing label. The local labels are A-RR’s own 16700 block, which is the next thing to clarify.
Issue: IOS-XR Label Range Minimum Is 16000
The per-node scheme puts A-RR at N=7, which should give 1700-1799. A-RR would not take it:
RP/0/RP0/CPU0:A-RR(config)#mpls label range ?
<16000-1048575> Minimum label value
table Specify label table
IOS-XR reserves everything below 16000 for static and special-purpose labels, where IOS-XE starts at 16. A-RR uses 16700-16799, so N=7 still reads off the label – 16700 through 16706 are its local labels in the LFIB above.
End-to-End LSP Verification
With all sessions up and synced, the full LSP is verified by MPLS ping and traceroute from A-PE1 to A-PE2:
A-PE1#ping mpls ipv4 10.1.0.6/32 repeat 100 source 10.1.0.5
Success rate is 100 percent (100/100), round-trip min/avg/max = 1/1/5 ms
A-PE1#traceroute mpls ipv4 10.1.0.6/32 source 10.1.0.5
0 10.1.1.22 MRU 1500 [Labels: 1104 Exp: 0]
L 1 10.1.1.21 MRU 1500 [Labels: 1404 Exp: 0] 19 ms
L 2 10.1.1.14 MRU 1500 [Labels: implicit-null Exp: 0] 15 ms
! 3 10.1.1.34 2 ms
Reading the label path:
- Hop 0 (A-PE1): imposes label 1104 – A-P1’s binding for 10.1.0.6/32
- Hop 1 (A-P1, 10.1.1.21): swaps to label 1404 – A-P4’s binding for 10.1.0.6/32
- Hop 2 (A-P4, 10.1.1.14): pops the label, because A-PE2 advertised implicit-null for its own loopback (PHP)
- Hop 3 (A-PE2, 10.1.1.34): receives IP packet
A-P4 pops the label because A-PE2 is directly connected – PHP. The traceroute path is A-PE1 → A-P1 → A-P4 → A-PE2. It was easy to read the traceroute due to the label schema, where each router had its own range
The symmetric direction from A-PE2:
A-PE2#traceroute mpls ipv4 10.1.0.5/32 source 10.1.0.6
0 10.1.1.30 MRU 1500 [Labels: 1303 Exp: 0]
L 1 10.1.1.29 MRU 1500 [Labels: 1103 Exp: 0] 15 ms
L 2 10.1.1.17 MRU 1500 [Labels: implicit-null Exp: 0] 4 ms
! 3 10.1.1.22 3 ms
A-PE2 → A-P3 → A-P1 → A-PE1. Labels 1303 (A-P3’s binding), 1103 (A-P1’s binding), then implicit-null advertised by A-PE1 so A-P1 pops. ECMP is available on the A-P3/A-P1 segment, but this traceroute followed the single-path hash.
What’s Next
Every PE-to-PE path in SP-A is now a label-switched path: dedicated label ranges, bindings for loopbacks only, OSPF-LDP synchronization, and session protection are configured.
The next stage is Segment Routing on SP-B. LDP distributes labels hop-by-hop, and every node picks its own value, which is why I decided to make those values readable. SR has no separate label distribution protocol. IS-IS floods the Prefix-SID sub-TLV inside its Extended IP Reachability TLV, and because every SP-B node shares the same SRGB base, a given loopback has the same label on every router.
Leave a comment