What Is MPLS L3VPN and Why Does It Matter?
If you’ve ever needed to carry multiple customers’ traffic across a shared provider backbone — or segment corporate divisions over the same WAN infrastructure — MPLS Layer 3 VPNs (L3VPNs) are the gold standard. They give each VPN its own routing table, forward packets via labels instead of destination IP lookups, and scale to thousands of VRFs without blowing up the core. This guide walks through a complete end-to-end MPLS L3VPN lab on Cisco IOS-XE: LDP label distribution, VRF definitions, BGP VPNv4 peering between PE routers, and PE-CE routing with both static and OSPF options. All CLI is real IOS-XE 17.x syntax.
If you’re new to the underlying BGP mechanics, start with the BGP fundamentals guide before diving in here. And if your environment runs a mix of IOS versions, the IOS vs IOS-XE vs IOS-XR breakdown will save you confusion when command syntax varies.
Lab Topology
The lab uses five routers:
- P1 — Provider core router (no VRF awareness)
- PE1 — Provider Edge, left side (connects to CE1)
- PE2 — Provider Edge, right side (connects to CE2)
- CE1 — Customer Edge, Site A (10.1.1.0/24)
- CE2 — Customer Edge, Site B (10.2.2.0/24)
The underlay IGP is OSPF area 0 between P1, PE1, and PE2. LDP runs over the same links. BGP VPNv4 is a direct iBGP peering between PE1 and PE2 (or you’d use a route reflector in production). PE-CE routing in this example uses OSPF for one customer and static routes for a second, showing both patterns.
CE1 (10.1.1.1)
|
PE1 (10.0.12.1) -------- P1 (10.0.12.2 / 10.0.23.1) -------- PE2 (10.0.23.2)
|
CE2 (10.2.2.1)
Step 1: Enable MPLS and LDP on the Provider Core
MPLS label forwarding requires CEF (on by default in IOS-XE) and LDP enabled per-interface on every core-facing link. Start on P1:
P1# conf t
P1(config)# mpls ip
P1(config)# mpls label protocol ldp
P1(config)# interface GigabitEthernet0/0
P1(config-if)# description TO_PE1
P1(config-if)# ip address 10.0.12.2 255.255.255.0
P1(config-if)# mpls ip
P1(config-if)# exit
P1(config)# interface GigabitEthernet0/1
P1(config-if)# description TO_PE2
P1(config-if)# ip address 10.0.23.1 255.255.255.0
P1(config-if)# mpls ip
P1(config-if)# end
Repeat the same mpls ip enablement on PE1 (Gi0/0 toward P1) and PE2 (Gi0/0 toward P1). The PE’s customer-facing interfaces do not get mpls ip — they live in a VRF and forward based on the inner VPN label only.
Verify LDP neighbor adjacency:
PE1# show mpls ldp neighbor
Peer LDP Ident: 10.0.12.2:0; Local LDP Ident 10.0.12.1:0
TCP connection: 10.0.12.2.646 - 10.0.12.1.22843
State: Oper; Msgs sent/rcvd: 42/41; Downstream
Up time: 00:12:33
LDP discovery sources:
GigabitEthernet0/0, Src IP addr: 10.0.12.2
Addresses bound to peer LDP Ident:
10.0.12.2 10.0.23.1
PE1# show mpls forwarding-table
Local Outgoing Prefix Bytes Label Outgoing Next Hop
Label Label or VC or Tunnel Id Switched interface
16 17 10.0.23.0/24 0 Gi0/0 10.0.12.2
17 Pop Label 10.0.12.0/24 0 Gi0/0 10.0.12.2
If you see Pop Label (PHP — Penultimate Hop Popping), that’s normal and expected. The last P router pops the outer label before handing the packet to the PE, leaving only the inner VPN label for the PE to process.
Step 2: Define VRFs on the PE Routers
Each customer gets a VRF. The two mandatory knobs are the Route Distinguisher (RD) — a 64-bit value that makes identical customer prefixes globally unique inside BGP — and the Route Target (RT) — an extended community that controls which VRFs import and export which routes.
On PE1:
PE1(config)# ip vrf CUSTOMER_A
PE1(config-vrf)# rd 65000:100
PE1(config-vrf)# route-target export 65000:100
PE1(config-vrf)# route-target import 65000:100
PE1(config-vrf)# exit
PE1(config)# interface GigabitEthernet0/1
PE1(config-if)# description TO_CE1
PE1(config-if)# ip vrf forwarding CUSTOMER_A
PE1(config-if)# ip address 192.168.1.1 255.255.255.252
PE1(config-if)# no shutdown
Note: Assigning ip vrf forwarding to an interface removes its IP address — you must re-assign it immediately after. IOS-XE will warn you with: % Interface GigabitEthernet0/1 IP address 192.168.1.1 removed due to enabling VRF.
On PE2, create the matching VRF with the same RD and RT:
PE2(config)# ip vrf CUSTOMER_A
PE2(config-vrf)# rd 65000:100
PE2(config-vrf)# route-target export 65000:100
PE2(config-vrf)# route-target import 65000:100
PE2(config-vrf)# exit
PE2(config)# interface GigabitEthernet0/1
PE2(config-if)# description TO_CE2
PE2(config-if)# ip vrf forwarding CUSTOMER_A
PE2(config-if)# ip address 192.168.2.1 255.255.255.252
PE2(config-if)# no shutdown
Verify the VRF routing tables are isolated from the global table:
PE1# show ip route vrf CUSTOMER_A
Routing Table: CUSTOMER_A
...
C 192.168.1.0/30 is directly connected, GigabitEthernet0/1
Step 3: BGP VPNv4 Peering Between PE Routers
This is the control-plane glue. PE1 and PE2 establish an iBGP session using the vpnv4 address family to exchange labeled customer routes. In production you’d use a Route Reflector, but for a two-PE lab, a direct peer is sufficient.
On PE1:
PE1(config)# router bgp 65000
PE1(config-router)# bgp router-id 1.1.1.1
PE1(config-router)# no bgp default ipv4-unicast
PE1(config-router)# neighbor 2.2.2.2 remote-as 65000
PE1(config-router)# neighbor 2.2.2.2 update-source Loopback0
PE1(config-router)# neighbor 2.2.2.2 description PE2_VPNv4
PE1(config-router)#
PE1(config-router)# address-family vpnv4
PE1(config-router-af)# neighbor 2.2.2.2 activate
PE1(config-router-af)# neighbor 2.2.2.2 send-community extended
PE1(config-router-af)# exit-address-family
On PE2 (mirror config, neighbor points to PE1’s loopback 1.1.1.1):
PE2(config)# router bgp 65000
PE2(config-router)# bgp router-id 2.2.2.2
PE2(config-router)# no bgp default ipv4-unicast
PE2(config-router)# neighbor 1.1.1.1 remote-as 65000
PE2(config-router)# neighbor 1.1.1.1 update-source Loopback0
PE2(config-router)#
PE2(config-router)# address-family vpnv4
PE2(config-router-af)# neighbor 1.1.1.1 activate
PE2(config-router-af)# neighbor 1.1.1.1 send-community extended
PE2(config-router-af)# exit-address-family
The send-community extended line is mandatory — without it, Route Target communities are stripped and the receiving PE has no way to know which VRF to import the prefix into.
Check BGP summary to confirm the session is up:
PE1# show bgp vpnv4 unicast all summary
BGP router identifier 1.1.1.1, local AS number 65000
...
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
2.2.2.2 4 65000 28 27 5 0 0 00:18:04 2
Two prefixes received — that’s your remote site’s VPN routes being exchanged correctly.
Step 4: PE-CE Routing — Option A: Static Routes
The simplest PE-CE option is static routes pointing into the VRF, redistributed into BGP. Use this when the customer site has a small, stable set of prefixes.
On PE1, add a VRF static route for CE1’s LAN and redistribute into BGP:
PE1(config)# ip route vrf CUSTOMER_A 10.1.1.0 255.255.255.0 192.168.1.2
PE1(config)# router bgp 65000
PE1(config-router)# address-family ipv4 vrf CUSTOMER_A
PE1(config-router-af)# redistribute static
PE1(config-router-af)# exit-address-family
Do the same on PE2 for CE2’s LAN (10.2.2.0/24, next-hop 192.168.2.2), then verify both sites appear in the VRF table on each PE:
PE1# show ip route vrf CUSTOMER_A
...
S 10.1.1.0/24 [1/0] via 192.168.1.2
B 10.2.2.0/24 [200/0] via 2.2.2.2, 00:02:11, MPLS label 1000
PE2# show ip route vrf CUSTOMER_A
B 10.1.1.0/24 [200/0] via 1.1.1.1, 00:02:11, MPLS label 1001
S 10.2.2.0/24 [1/0] via 192.168.2.2
Step 5: PE-CE Routing — Option B: OSPF
For larger customer sites with many subnets, running OSPF between the PE and CE is much cleaner than maintaining static routes. Each VRF gets its own OSPF process — this is critical; never mix VRF and global OSPF processes on the same router.
On PE1:
PE1(config)# router ospf 10 vrf CUSTOMER_A
PE1(config-router)# router-id 192.168.1.1
PE1(config-router)# redistribute bgp 65000 subnets metric-type 1
PE1(config-router)# network 192.168.1.0 0.0.0.3 area 0
PE1(config-router)# exit
PE1(config)# router bgp 65000
PE1(config-router)# address-family ipv4 vrf CUSTOMER_A
PE1(config-router-af)# redistribute ospf 10 match internal external 1 external 2
PE1(config-router-af)# exit-address-family
On CE1:
CE1(config)# router ospf 1
CE1(config-router)# router-id 192.168.1.2
CE1(config-router)# network 192.168.1.0 0.0.0.3 area 0
CE1(config-router)# network 10.1.1.0 0.0.0.255 area 0
Verify the OSPF adjacency forms correctly within the VRF context:
PE1# show ip ospf 10 neighbor
Neighbor ID Pri State Dead Time Address Interface
192.168.1.2 1 FULL/DR 00:00:39 192.168.1.2 GigabitEthernet0/1
CE1’s LAN prefix will now appear as an OSPF route in the VRF table, get redistributed into BGP VPNv4 on PE1, and be exported with RT 65000:100 to PE2.
Step 6: End-to-End Verification
From CE1, ping CE2’s LAN prefix using a source on the customer subnet:
CE1# ping 10.2.2.1 source 10.1.1.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.2.2.1, timeout is 2 seconds:
Packet sent with a source address of 10.1.1.1
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 2/3/4 ms
Trace the label stack from PE1 to confirm both labels are present:
PE1# traceroute vrf CUSTOMER_A 10.2.2.1 source 192.168.1.1
Type escape sequence to abort.
Tracing the route to 10.2.2.1
VRF info: (vrf in name/id, vrf out name/id)
1 10.0.12.2 [MPLS: Labels 16/1000 Exp 0] 4 msec 3 msec 4 msec
2 10.0.23.2 [MPLS: Label 1000 Exp 0] 3 msec 3 msec 3 msec
3 10.2.2.1 5 msec 4 msec 5 msec
Two labels in the stack: the outer label (16) is the LDP transport label to reach PE2’s loopback, and the inner label (1000) is the VPN label identifying the CUSTOMER_A VRF on PE2. P1 only sees and swaps the outer label — it has zero awareness of the customer’s routing table or address space.
VRF Route Leaking: Sharing Services Between VRFs
A common enterprise use case is leaking shared services (DNS, NTP, monitoring) from a central VRF into customer VRFs without fully merging them. This is done by importing additional Route Targets selectively.
Create a SHARED_SERVICES VRF with RT 65000:999, then import it into CUSTOMER_A:
PE1(config)# ip vrf SHARED_SERVICES
PE1(config-vrf)# rd 65000:999
PE1(config-vrf)# route-target export 65000:999
PE1(config-vrf)# route-target import 65000:999
PE1(config-vrf)# exit
PE1(config)# ip vrf CUSTOMER_A
PE1(config-vrf)# route-target import 65000:999
PE1(config-vrf)# exit
Now CUSTOMER_A’s routing table on PE1 will automatically receive any prefix exported by SHARED_SERVICES — without CUSTOMER_A’s routes leaking back into SHARED_SERVICES (unless you add a symmetric import). This is sometimes called a “hub-and-spoke” RT design. For fine-grained control over exactly which prefixes leak, combine RT import with inbound route-maps on the BGP VPNv4 neighbor statement.
Useful Troubleshooting Commands
When something isn’t working end-to-end in an MPLS L3VPN, work the stack from the bottom up:
! 1. LDP — are labels being distributed?
PE1# show mpls ldp bindings
PE1# show mpls ldp neighbor
! 2. VRF routing table — is the remote prefix present?
PE1# show ip route vrf CUSTOMER_A 10.2.2.0
! 3. BGP VPNv4 — is the prefix in BGP with correct RT?
PE1# show bgp vpnv4 unicast all 10.2.2.0/24
PE1# show bgp vpnv4 unicast all neighbors 2.2.2.2 received-routes
! 4. LFIB — is the label stack programmed?
PE1# show mpls forwarding-table vrf CUSTOMER_A detail
! 5. CEF — is the adjacency resolved?
PE1# show ip cef vrf CUSTOMER_A 10.2.2.0/24 detail
The most common failure modes are: (1) mismatched Route Targets between PE1 and PE2 — double-check with show bgp vpnv4 unicast all 10.2.2.0 and look for the RT extended community in the path attributes; (2) LDP sessions not forming because the PE loopback isn’t reachable via the OSPF underlay; (3) recursive BGP next-hop resolution failing when the remote PE loopback isn’t in the global routing table.
For large-scale deployments where you need to run these checks across dozens of PEs simultaneously, the Python network automation guide with Netmiko and NAPALM shows how to script exactly these verification steps — essential when operating a real provider backbone.
Production Considerations
Route Reflectors: In any real deployment with more than two PEs, use BGP Route Reflectors rather than a full iBGP mesh of VPNv4 sessions. The RR participates in the VPNv4 address family but doesn’t need VRFs itself — it reflects labeled routes between PEs without installing them locally.
RD Uniqueness: Route Distinguishers should be unique per VRF per PE to avoid best-path selection ambiguity in the BGP table. A common convention is AS:loopback_last_octet or AS:customer_id_with_PE_offset.
MTU: MPLS adds 4 bytes per label to every packet. A two-label stack adds 8 bytes. If your customer expects a 1500-byte MTU end-to-end, your provider links need an MTU of at least 1508. Mismatched MTU causes silent black-holing for large packets — test with:
PE1# ping vrf CUSTOMER_A 10.2.2.1 df-bit size 1500 source 192.168.1.1
MPLS TE: For traffic engineering (steering flows onto specific LSPs to avoid congested paths), look at RSVP-TE on IOS-XE. It integrates with L3VPN by using TE tunnels as BGP next-hop resolution paths, letting you route VPN traffic over engineered paths independent of the IGP’s best path.
Wrapping Up
MPLS L3VPN is one of those technologies that feels intimidating on paper but clicks quickly once you run through a lab end-to-end. The key insight is the separation of planes: LDP handles the transport label (getting packets across the core), BGP VPNv4 handles the VPN label and route distribution (which VRF gets which prefix), and per-VRF routing handles the customer’s own prefix learning. Once you can trace a packet through all three layers, troubleshooting becomes systematic rather than guesswork.
If you want to extend this lab further, try adding a second customer VRF with overlapping address space (e.g., both using 10.1.0.0/8 internally) and confirming that the RD keeps their routes separate in the BGP table — that’s the classic MPLS L3VPN demo that usually convinces skeptics the technology actually works.
Leave a Reply