Quick Start: MPLS + LDP Running in Under 5 Minutes
If you just want to see MPLS forwarding and LDP neighbors come up before reading the theory, here’s the minimal path. You need FRRouting 8.x or later, a Linux kernel 4.4+, and at least two VMs or network namespaces talking to each other.
Step 1 — Enable MPLS in the Linux Kernel
FRRouting handles the control plane, but the Linux kernel does the actual label forwarding. You need to load two modules and enable MPLS input on every interface that will carry labeled traffic.
# Load MPLS forwarding modules
modprobe mpls_router
modprobe mpls_iptunnel
# Enable MPLS input on your transit interfaces
sysctl -w net.mpls.conf.eth0.input=1
sysctl -w net.mpls.conf.eth1.input=1
# Raise the platform label limit (default is 0 — MPLS won't work without this)
sysctl -w net.mpls.platform_labels=100000
Make these persistent across reboots:
# /etc/sysctl.d/mpls.conf
net.mpls.platform_labels = 100000
net.mpls.conf.eth0.input = 1
net.mpls.conf.eth1.input = 1
Also add the modules to /etc/modules so they load on boot:
echo -e "mpls_router\nmpls_iptunnel" >> /etc/modules
Step 2 — Enable the LDP Daemon in FRRouting
# Edit /etc/frr/daemons
ospfd=yes
ldpd=yes
systemctl restart frr
Step 3 — Minimal LDP Config in vtysh
vtysh
router ospf
network 10.0.12.0/30 area 0
network 1.1.1.1/32 area 0
!
mpls ldp
router-id 1.1.1.1
!
address-family ipv4
discovery transport-address 1.1.1.1
!
interface eth0
!
!
That’s all it takes to get LDP running. Now let’s understand what’s actually happening under the hood.
Deep Dive: How MPLS Label Switching and LDP Actually Work
MPLS is foundational to service provider networks and any serious VPN deployment. Seeing how a 20-bit label replaces a full IP lookup at every transit hop shifts how you think about packet forwarding — suddenly the SP world starts making a lot more sense.
Traditional IP routing examines the destination address at every hop and performs a longest-prefix match — expensive at scale. MPLS replaces that with a 20-bit label lookup at every transit router. The label is pushed onto the packet at the network edge, swapped at each transit hop, and popped before delivery. Three operations, every router picks one:
- PUSH — ingress LSR adds a label to the packet
- SWAP — transit LSR replaces the incoming label with a new one
- POP — egress LSR removes the label and delivers the IP packet
LDP (Label Distribution Protocol) handles label assignment automatically. Routers discover neighbors via UDP 646 multicasts, then establish TCP sessions on port 646 to exchange label-to-FEC bindings. A FEC (Forwarding Equivalence Class) is just a destination prefix. LDP uses the IGP routing table as its foundation — which is why OSPF (or IS-IS) must be running and converged before LDP can do anything useful.
The 3-Router Lab Topology
Here’s the topology we’ll build and verify end-to-end:
PC1 --- R1 (Ingress LSR) --- R2 (Transit LSR) --- R3 (Egress LSR) --- PC2
Lo: 1.1.1.1/32 Lo: 2.2.2.2/32 Lo: 3.3.3.3/32
10.0.12.1/30 10.0.12.2/30
10.0.23.1/30 10.0.23.2/30
Full FRR Configuration for All Three Routers
R1 — Ingress LSR:
frr version 9.1
hostname R1
!
interface eth0
ip address 10.0.12.1/30
!
interface lo0
ip address 1.1.1.1/32
!
router ospf
ospf router-id 1.1.1.1
network 1.1.1.1/32 area 0
network 10.0.12.0/30 area 0
!
mpls ldp
router-id 1.1.1.1
!
address-family ipv4
discovery transport-address 1.1.1.1
!
interface eth0
!
!
R2 — Transit LSR:
frr version 9.1
hostname R2
!
interface eth0
ip address 10.0.12.2/30
!
interface eth1
ip address 10.0.23.1/30
!
interface lo0
ip address 2.2.2.2/32
!
router ospf
ospf router-id 2.2.2.2
network 2.2.2.2/32 area 0
network 10.0.12.0/30 area 0
network 10.0.23.0/30 area 0
!
mpls ldp
router-id 2.2.2.2
!
address-family ipv4
discovery transport-address 2.2.2.2
!
interface eth0
interface eth1
!
!
R3 — Egress LSR:
frr version 9.1
hostname R3
!
interface eth0
ip address 10.0.23.2/30
!
interface lo0
ip address 3.3.3.3/32
!
router ospf
ospf router-id 3.3.3.3
network 3.3.3.3/32 area 0
network 10.0.23.0/30 area 0
!
mpls ldp
router-id 3.3.3.3
!
address-family ipv4
discovery transport-address 3.3.3.3
!
interface eth0
!
!
Advanced Usage: Verifying the Label-Switched Path
Config is table stakes. Here’s how I verify that labels are actually being pushed, swapped, and popped in the data plane — end to end.
Check LDP Neighbor State
vtysh -c "show mpls ldp neighbor"
AF ID State Remote Address Uptime
ipv4 2.2.2.2 OPERATIONAL 2.2.2.2 00:08:41
ipv4 3.3.3.3 OPERATIONAL 3.3.3.3 00:08:39
OPERATIONAL on both neighbors confirms LDP sessions are up and label bindings have been exchanged.
Inspect the Label Information Base
vtysh -c "show mpls ldp binding"
AF Destination Nexthop Local Label Remote Label In Use
ipv4 1.1.1.1/32 10.0.12.1 imp-null 16 yes
ipv4 2.2.2.2/32 - 16 imp-null yes
ipv4 3.3.3.3/32 10.0.23.2 17 16 yes
The imp-null entries are PHP (Penultimate Hop Popping) — the router before egress pops the label instead of swapping it. This is default behavior and completely normal.
Check the Kernel-Level MPLS Forwarding Table
# FRR's view
vtysh -c "show mpls table"
# Linux kernel's view
ip -f mpls route show
Capture Labeled Packets with tcpdump
# On R2's transit interface, watch labels being swapped in real time
tcpdump -i eth0 -n -e mpls
When the MPLS label shows up in tcpdump output, you know the data plane is actually working. That 4-byte label header sits between the Ethernet frame and the IP header — invisible to endpoints, but the only thing transit LSRs care about.
End-to-End Connectivity Check
# From R1, ping R3's loopback through the LSP
ping 3.3.3.3 -I 1.1.1.1
If OSPF is converged and LDP has distributed bindings, this traverses the full label-switched path.
Practical Tips: Lessons from Debugging Real Failures
1. Kernel Modules Must Load Before FRR
I spent an embarrassing amount of time troubleshooting why LDP sessions formed but no labels appeared in the forwarding table. The culprit was mpls_router not being loaded before FRR started. Always verify:
lsmod | grep mpls
2. MPLS Input Is Per-Interface — Don’t Forget New Interfaces
Add a new interface to a running router and forget net.mpls.conf.<iface>.input=1, and labeled packets drop silently. No error. No warning. I now have a small script that enables MPLS on any new interface at creation time.
3. Transport Address Must Be Reachable via IGP
The discovery transport-address — always set to the loopback in my setups — must be advertised into OSPF. If OSPF isn’t redistributing your loopback prefix, LDP will send Hello packets but TCP sessions for label exchange will never establish. Check OSPF first when LDP neighbors stay in INITIALIZED state.
4. Use Network Namespaces for HomeLab Testing
You don’t need three physical machines or even three VMs. Linux network namespaces let you run isolated FRR instances on a single host:
# Create isolated namespaces
ip netns add R1
ip netns add R2
ip netns add R3
# Create a veth pair between R1 and R2
ip link add r1-eth0 netns R1 type veth peer name r2-eth0 netns R2
# Assign addresses inside each namespace
ip netns exec R1 ip addr add 10.0.12.1/30 dev r1-eth0
ip netns exec R1 ip link set r1-eth0 up
ip netns exec R2 ip addr add 10.0.12.2/30 dev r2-eth0
ip netns exec R2 ip link set r2-eth0 up
# Enable MPLS inside the namespace
ip netns exec R1 sysctl -w net.mpls.conf.r1-eth0.input=1
ip netns exec R1 sysctl -w net.mpls.platform_labels=100000
Each namespace gets its own FRR config directory and daemon set. Teardown is just ip netns del R1. I test all my MPLS configs this way before touching anything with real traffic.
5. Debug LDP Session Negotiation When Things Go Wrong
# Enable LDP debug output inside vtysh
debug mpls ldp
debug mpls ldp discovery
debug mpls ldp messages recv
debug mpls ldp messages sent
# Tail the FRR log and filter for LDP events
tail -f /var/log/frr/frr.log | grep -i ldp
The most common failure modes are transport address unreachability and mismatched router-IDs. The debug output makes both obvious within seconds.
With this baseline solid, the obvious next challenge is MPLS L3VPNs: MP-BGP carrying VPNv4 routes between PE routers, with LDP providing the transport labels underneath. That’s how service providers keep customer traffic isolated on shared physical infrastructure. Bigger topic — but this lab is exactly the prerequisite.

