Berik Ashimov // senior.it.engineer
Download CV
POSTS / MIKROTIK

MPLS and LDP on RouterOS 7: Labels on a Budget

// series · MikroTik RouterOS 7 Guide part 16 of 35
  1. ... 13 earlier
  2. Routing Filters in RouterOS 7: A Real Language, With One Nasty Trap
  3. VRFs on RouterOS 7: Real Separation, Not Firewall Hope
  4. MPLS and LDP on RouterOS 7: Labels on a Budget
  5. VPLS on RouterOS 7: Layer 2 Over MPLS, With the MTU Math Done Right
  6. VXLAN on RouterOS 7: Layer 2 Over Any IP Network
  7. ... 17 later
view the full series →

MPLS on MikroTik has a reputation problem. Mention it and someone will tell you MPLS is a carrier technology that has no business running on a router that costs less than a night out. They’re wrong, and the WISP industry proves it daily: thousands of small operators run MPLS backbones on MikroTik because it’s the cheapest way to get real L2VPN transport — VPLS pseudowires that survive link failures because the underlay reroutes them, instead of EoIP tunnels that just go dark.

That’s the honest reason to run MPLS on RouterOS. Not label-switching performance — on MikroTik, MPLS forwarding is done in software, so the “labels are faster than IP lookups” argument from the 1990s doesn’t apply. You run MPLS on MikroTik because you want the services built on top of it: VPLS today, maybe traffic engineering tomorrow. Plain packet transport gains you nothing.

And there is exactly one thing that takes down more MikroTik MPLS networks than everything else combined: MTU. Pings work, OSPF is up, LDP sessions are Operational, small packets flow — and then a customer pushes a full-size frame and it silently vanishes because one core link couldn’t carry 1500 bytes plus labels. We’ll spend real time on that, because getting it right on day one is a ten-minute job and getting it wrong is a week of intermittent-fault archaeology.

I covered the label-switching fundamentals — push/swap/pop, LSPs, penultimate hop popping — in the MPLS introduction of the VyOS Guide on this site. This article assumes you know what a label is and gets straight to the RouterOS 7 specifics.

The Underlay: Loopbacks and OSPF

LDP doesn’t build topology. It follows your IGP: for every route in the routing table, LDP maps a label, and the label path follows the IGP’s best path. A broken underlay means broken LSPs, so build the OSPF part properly first.

RouterOS has no dedicated loopback interface type — the convention is an empty bridge. Every MPLS router gets one, with a /32 that will serve as router ID, LSR ID, and LDP transport address:

Terminal window
# On PE1
/interface bridge add name=lo
/ip address add address=10.255.0.1/32 interface=lo
# OSPF underlay — RouterOS 7 syntax: instance, area, interface-template
/routing ospf instance add name=backbone version=2 router-id=10.255.0.1
/routing ospf area add name=area0 area-id=0.0.0.0 instance=backbone
/routing ospf interface-template add area=area0 interfaces=ether1,ether2 type=ptp
/routing ospf interface-template add area=area0 interfaces=lo passive

The loopback matters more here than in plain OSPF designs. There is one LDP session between any two routers regardless of how many links connect them, and anchoring the session to a loopback means it survives interface flaps and address changes. The official LDP documentation says as much — treat it as a requirement, not advice.

Topology for the rest of this article:

[PE1] ──── [P1] ──── [PE2]
10.255.0.1 10.255.0.2 10.255.0.3

Verify the underlay before touching MPLS. Loopback-to-loopback reachability is the test that matters, because that’s what LDP and every service on top will use:

Terminal window
/routing ospf neighbor print
/ping 10.255.0.3 src-address=10.255.0.1 count=3

Enabling LDP

RouterOS 7 restructured LDP into per-instance configuration under /mpls ldp — you add an instance instead of flipping a global enabled switch like v6. Two commands per router:

Terminal window
# On PE1
/mpls ldp add afi=ip lsr-id=10.255.0.1 transport-addresses=10.255.0.1
/mpls ldp interface add interface=ether1
# On P1 — LDP on both core-facing interfaces
/mpls ldp add afi=ip lsr-id=10.255.0.2 transport-addresses=10.255.0.2
/mpls ldp interface add interface=ether1
/mpls ldp interface add interface=ether2

lsr-id identifies the router in LDP; transport-addresses is what the TCP session (port 646) is actually built to, and what gets advertised to neighbors as the session target. Set both to the loopback. If you skip transport-addresses, the session may source from a physical interface address, and then it dies with that interface.

Two instance options worth knowing: distribute-for-default=yes maps a label for the default route (off by default, and leave it off unless you know why you need it), and use-explicit-null replaces penultimate hop popping with explicit-null labels — useful when you want QoS markings preserved to the egress router, noise otherwise. LDP is per-VRF in v7 (vrf=main by default), and IPv6 LDP is supported with afi=ipv6.

Only enable LDP on core-facing interfaces. Customer ports have no business speaking LDP, and every interface you add is an interface where someone can try to form a session with you.

Verifying Sessions and Label Bindings

Sessions first:

Terminal window
/mpls ldp neighbor print
# Flags: D - dynamic, O - operational
# TRANSPORT LOCAL-TRANSPORT PEER SEND
# 10.255.0.2 10.255.0.1 10.255.0.2:0 ...

You want the operational flag on every expected neighbor. A neighbor stuck without it is almost always a transport problem: the loopbacks can’t reach each other, or a firewall filter is eating TCP/UDP 646. Remember LDP discovery is UDP 646 to 224.0.0.2 and the session is TCP 646 between transport addresses — both must pass your input chain on core interfaces.

Then the label bindings — this is where MPLS stops being abstract. Local mappings are labels this router assigned to prefixes and advertised to neighbors; remote mappings are what neighbors advertised to us:

Terminal window
/mpls ldp local-mapping print
# PREFIX LABEL
# 10.255.0.1/32 impl-null # our own loopback: PHP, pop before delivery
# 10.255.0.3/32 24 # transit: we tell neighbors "send me label 24"
/mpls ldp remote-mapping print
# PREFIX NEXTHOP LABEL PEER
# 10.255.0.3/32 10.0.12.2 31 10.255.0.2:0

impl-null for a router’s own connected routes is penultimate hop popping doing its job — the upstream router removes the label instead of forwarding a labeled packet that would be popped anyway.

The actual forwarding decisions live in the forwarding table:

Terminal window
/mpls forwarding-table print
# LABEL VRF PREFIX NEXTHOPS
# 24 main 10.255.0.3/32 { label=31 nh=10.0.12.2 interface=ether1 }

Read it as: packets arriving with label 24 get the label swapped to 31 and go out ether1. If a prefix you expect is missing here, work backwards — is it in the routing table, does a remote mapping exist for it, is the LDP session up.

By default LDP allocates labels liberally. On a bigger network you can restrict what gets labels with /mpls ldp advertise-filter and accept-filter — the only prefixes that genuinely need labels are the loopbacks that terminate services. Filtering everything else keeps the forwarding table small and convergence fast.

MPLS MTU: The Outage You Will Cause

Here’s the failure mode. Each MPLS label is 4 bytes. A 1500-byte IP packet entering an LSP becomes 1504 bytes on the wire. A VPLS frame carries two labels plus the Ethernet header it’s transporting — a 1500-byte customer payload becomes roughly 1526 bytes of MPLS payload. If any link in the path can’t carry that, the packet is dropped. Not fragmented — MPLS is not fragmented in flight, and there’s no ICMP “too big” going back to a customer inside a pseudowire. Small packets work. SSH works. Ping works. Large HTTPS frames die. You will chase this as an application problem for days if you don’t know to look for it.

RouterOS controls this through /mpls interface:

Terminal window
/mpls interface print
# INTERFACE MPLS-MTU
# all 1508

The default entry gives every interface an MPLS MTU of 1508 — enough for two labels over a 1500-byte IP packet, not enough for VPLS. The selection logic per the docs: use the configured mpls-mtu if the interface matches an entry, fall back to L2MTU, fall back to L3 MTU. The catch is that mpls-mtu is a promise, not a capability — the interface must actually be able to carry frames that big, which on most RouterBOARD Ethernet means raising l2mtu:

Terminal window
# On every MPLS-facing interface, every router in the path:
/interface ethernet set ether1 l2mtu=1580
/mpls interface add interface=ether1 mpls-mtu=1526

1526 covers VPLS with two labels and a control word; 1580 of L2MTU gives headroom for a label stack you’ll grow into (TE, future services). Most MikroTik hardware accepts l2mtu up to 1580 or beyond — check /interface print for the l2mtu column and the max your model supports. And if there’s a non-MikroTik switch between two MPLS routers, it needs jumbo/baby-giant frames enabled too. The switch nobody thought about is where these outages live.

Test it end to end before a customer does. From a PE, sweep packet sizes across an LSP:

Terminal window
/ping 10.255.0.3 src-address=10.255.0.1 size=1500 do-not-fragment count=3

If 1500-byte pings between loopbacks pass but the service on top drops large frames, your service-layer overhead doesn’t fit — recheck every hop’s mpls-mtu and l2mtu, not just the endpoints.

Verifying the LSP Itself

Traceroute on RouterOS is label-aware — transit hops show the label stack, which is the quickest proof that traffic is actually label-switched rather than falling back to plain IP routing:

Terminal window
/tool traceroute 10.255.0.3 src-address=10.255.0.1
# 1 10.0.12.2 <mpls label 31>
# 2 10.255.0.3

If you see no labels, the packets are being IP-routed — usually a missing remote mapping or LDP interface. Also worth watching during changes:

Terminal window
/mpls forwarding-table print interval=1 # live view while failing over a link
/mpls ldp neighbor print

Kill a core link while pinging across the LSP. LDP re-follows OSPF, so convergence is IGP convergence plus label propagation — tune OSPF timers or add BFD on the underlay if you need it fast.

Where the Fast Path Isn’t: Hardware Reality

Be clear-eyed about what forwards your labels. No current MikroTik switch chip does MPLS label switching in hardware. L3 hardware offload on CRS3xx/CRS5xx switches and CCR2116/CCR2216 routers accelerates plain IP routing — it does not extend to MPLS. Every labeled packet through a MikroTik box is handled by the CPU, in the software forwarding path.

In practice that’s less scary than it sounds. RouterOS 7’s MPLS implementation was rewritten and forwards labeled traffic efficiently in software; ARM64 CCRs push MPLS at rates that are perfectly adequate for the multi-gigabit backbones this gear gets deployed in. But plan capacity around CPU forwarding: a CRS326 that routes line-rate with L3HW will do nothing of the sort as an MPLS P router. If your design needs 100G label switching, that’s not a MikroTik conversation.

Closing Thoughts

MPLS on RouterOS 7 in one sitting: loopbacks on every router, OSPF underlay, /mpls ldp add with lsr-id and transport-addresses pointed at the loopback, LDP only on core interfaces, then verify with neighbor print, the mapping tables, and a label-aware traceroute. Fix MTU before you build anything on top — l2mtu=1580, mpls-mtu=1526, every hop, tested with full-size do-not-fragment pings.

Running LDP by itself gets you nothing a plain IGP doesn’t. The payoff is the next article: VPLS, which turns this label transport into L2 circuits between sites — and immediately cashes in the MTU discipline you just built.

// share