Configuring MPLS and LDP on Linux with FRRouting: Build a Label-Switched Network in Your HomeLab

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

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.

Share: