SP-A MPLS Transport: LDP Label Ranges, OSPF Sync, Session Protection, and a little more

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.

  1. A link flap occurs – OSPF converges in approximately 2 seconds (with fast hello timers and BFD, usually less).
  2. OSPF installs a new best path and updates the FIB.
  3. 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