Theoretical foundations and architecture
MPLS (Multiprotocol Label Switching) is a cornerstone technology in service provider networks that cleanly separates transport infrastructure (underlay) from customer services (overlay). While originally developed to accelerate forwarding in hardware ASICs using fixed 20-bit label lookups instead of expensive Longest Prefix Match (LPM) in FIB tables, its modern primary role is enabling a BGP-Free Core. Core transit (P) routers forward packets solely using outer transport labels (signaled by LDP or Segment Routing) and only maintain reachability to edge PE loopbacks. P routers do not need customer VRF routes or the global Internet routing table (over 900,000 routes), delivering massive scalability. At ingress PE routers, incoming packets are encapsulated with a label stack: an outer transport label directs the packet across the core to the egress PE, while an inner service label identifies the customer VRF or pseudowire. MPLS provides logical route isolation through VRFs and labels rather than cryptographic encryption; where confidentiality is required, IPsec or MACsec must be layered on top.
Why labels appeared: history and the real motivation
Label switching was born in 1996-1997 (Ipsilon IP Switching, Cisco Tag Switching, IBM ARIS, Toshiba CSR), when backbones were built on ATM and Frame Relay and software-based IP routers could not keep up with growing routing tables. The IETF merged these approaches into the MPLS architecture (RFC 3031) and the label stack encoding (RFC 3032). It is important to understand that the argument "a label is faster than LPM" is mostly historical today: modern ASICs perform Longest Prefix Match over a million prefixes at line rate (TCAM or algorithmic tries). Operators keep MPLS for other reasons: offloading the core from external routes, multiple services on one infrastructure, controlled paths (traffic engineering, fast reroute FRR) and predictable customer isolation.
Label lookup versus prefix lookup: what differs
IPv4 forwarding looks up the destination address in the FIB by the longest matching prefix: a 32-bit key, masks from /0 to /32, and the result depends on whether more specific entries exist. A label is a 20-bit integer (0..1 048 575) used as an exact-match key in the LFIB (on Cisco it is the mpls forwarding-table, on Junos the mpls.0 table). The result of the lookup is an operation (push, swap, pop) plus an outgoing interface, a next hop and a new label. A label has local significance only: every LSR chooses a label for a given FEC (Forwarding Equivalence Class) on its own and advertises it to its upstream neighbor, so on the path PE1 -> P1 -> PE2 the label values differ on every hop. Labels do not need to be "negotiated" across the network; all that is required is a binding distribution protocol (LDP, RSVP-TE, BGP-LU, IS-IS/OSPF for Segment Routing).
BGP-Free Core: the main reason
Without MPLS every transit router has to know where to send a packet destined to an arbitrary external address, otherwise it drops it: the iBGP next hop (for example PE2 10.255.0.2) is resolved recursively through the IGP, but the forwarding decision for the customer destination address is taken from the BGP table. So all transit nodes are forced to carry the full table (on the order of a million IPv4 prefixes plus IPv6) and to participate in iBGP. With MPLS, PE1 looks up the customer address in the VRF or the global table, obtains the BGP next hop (the PE2 loopback) and then forwards the packet along the LSP to that loopback. The transit P1 looks only at the top label: it needs IGP routes and LDP bindings for the PE /32 loopbacks, and it holds no customer or Internet routes. State in the core grows with the number of PEs, not with the number of customers and prefixes; BGP is not needed on P nodes.
This gives a hard requirement: every PE loopback must have an LDP binding (or an SR label). If there is no LSP to the BGP next hop, VPN packets are dropped even though an IP ping works.
MPLS header format and the label stack
One stack entry (RFC 3032) takes 4 bytes: Label 20 bits, TC (Traffic Class, formerly EXP) 3 bits, S (Bottom of Stack) 1 bit, TTL 8 bits. Entries sit between the L2 header and the L3 header (hence the occasional "layer 2.5"): the top label comes first, and the bottom one has S=1. The frame ethertype is 0x8847 for unicast and 0x8848 for multicast. Overhead: 4 bytes per label, so 8 bytes for L3VPN (transport + service), 12 when an FRR/TE label is added and more with nesting (Inter-AS, CSC). Therefore the core MTU must be raised (usually 1600 or more), otherwise 1500-byte customer packets are fragmented or silently dropped when DF=1 (a separate lesson covers MTU).
TTL is copied from IP into the label on push (propagate) and back on pop; in the core it can be hidden (no mpls ip propagate-ttl on Cisco, no-propagate-ttl on Junos). Then a customer traceroute does not show the internal P nodes. If TTL is copied, an ICMP time exceeded generated by a transit LSR is delivered to the egress PE along the LSP and only then returned to the sender, so the RTT to P nodes includes the path to the end of the LSP.
Transport label and service label
A VPN network uses at least two labels. The outer (transport) label is allocated by LDP/SR/RSVP and carries the packet through the core to the egress PE loopback. The inner (service, VPN) label is allocated by BGP (VPNv4/EVPN) or by pseudowire signaling and tells the egress PE which VRF or pseudowire to deliver the packet to. The core never reads the service label: that is why the same transport serves L3VPN, L2VPN, EVPN and plain Internet. With PHP (Penultimate Hop Popping) the penultimate node removes the transport label and the egress PE receives the packet with only the service label, avoiding a double lookup.
Reserved labels and ranges
Labels 0-15 are reserved (RFC 3032 and later): 0 - IPv4 Explicit Null, 1 - Router Alert, 2 - IPv6 Explicit Null, 3 - Implicit Null (never sent in a frame, it signals PHP), 7 - Entropy Label Indicator (RFC 6790), 13 - Generic Associated Channel Label GAL (RFC 5586), 14 - OAM Alert (RFC 3429), 15 - Extension Label (RFC 7274); the rest of the range is unassigned for now. Dynamic labels start at 16. Platforms allocate them from different blocks: Cisco IOS-XE uses a range starting at 16 by default (the upper bound depends on the platform, and a change of mpls label range takes effect after a reload), Junos usually hands out LDP labels around 299776 and above, Huawei VRP starts at 1024. The block 16000-23999 is often reserved for the Segment Routing SRGB, so the LDP and SR ranges are best kept apart.
MPLS does not encrypt
The label is a plain-text field, and VPN isolation is purely logical: in the core customers are told apart by labels, on the PE by VRF tables. Anyone who can sniff a backbone link or get onto an operator node sees customer data in the clear. Protection against spoofing: the PE port facing the CE must not accept labeled packets (MPLS is not enabled on the customer port). If confidentiality is required, IPsec (between CEs) or MACsec (on links) is applied on top of MPLS; MPLS itself only separates traffic.
Comparison with alternatives
Plain IP with iBGP on every node: no customer isolation, and the core stores all routes. GRE tunnels: 24 bytes of overhead for IPv4/GRE (20 bytes of outer IP + 4 of GRE), point-to-point, a full mesh needs N*(N-1)/2 tunnels configured by hand, no signaling; but it works over any IP network, including the Internet. VRF-lite: separate VRFs and a subinterface/VLAN on every segment between every pair of nodes, the number of customers is bound by 4094 VLANs and by manual configuration on every transit router. MPLS L3VPN puts VRFs only on PEs and the core sees one transport label: adding a customer changes the configuration only on two PEs, and nothing changes on the P nodes.
Where MPLS pays off and where it does not
It pays off when an operator has many customers and services (L3VPN, VPWS/VPLS, EVPN, Internet), needs load management and fast protection (RSVP-TE FRR, TI-LFA in SR), or has to get away from a full mesh of BGP. It does not pay off in a small enterprise network of 2-5 routers where OSPF/BGP and VRF-lite are enough; in a data center where the standard is VXLAN/EVPN over an IP fabric; and in networks without a skilled team (the price is operational complexity: LDP/IGP sync, MTU, diagnostics through LSP ping/traceroute). The modern variant is Segment Routing: it removes LDP and RSVP while keeping the same MPLS forwarding plane (SR-MPLS) or replacing it with IPv6 (SRv6).
Typical failures
No LSP to the PE loopback: on Huawei labels are built only for /32 host routes by default, so a loopback with a /24 mask gets no label. mpls ip is disabled on one segment: the LSP breaks in the middle and packets are lost although the IP route exists. MTU: a core node with MTU 1500 drops a labeled packet of 1504 bytes. IGP and LDP out of sync: the IGP adjacency came up before the LDP session and traffic is black-holed (cured by LDP-IGP sync). The LDP session is built to the transport address (the LSR ID by default), so it must be reachable through the IGP.
Architecture Diagram & Topology
+-------------------+ +-------------------------+ +-------------------------+ +-------------------+
| CE-1 (Customer) | ---> | PE-1 (Ingress LER) | ---> | P-1 (Core LSR) | ---> | PE-2 (Egress) | ---> [CE-2]
| 192.168.10.1/24 | | Gi0/0: 10.0.0.1 (LDP) | | Gi0/1: 10.0.0.2 (Swap) | | Gi0/2: 10.255.0.2 |
+-------------------+ +-------------------------+ +-------------------------+ +-------------------+Traffic Flow & Label Stack Scheme
1. CE-1 ---> PE-1: [IP Header: 192.168.10.10 -> 192.168.20.10 | Payload] (Native IP)
2. PE-1 ---> P-1: [Transport: 16002 | S=0] [VPN: 24001 | S=1] [IP Header | Payload] (PUSH x2)
3. P-1 ---> PE-2: [VPN: 24001 | S=1] [IP Header | Payload] (PHP: P-1 pops the transport label, PE-2 advertised Implicit Null)
4. PE-2 ---> CE-2: [IP Header: 192.168.10.10 -> 192.168.20.10 | Payload] (POP VPN label, lookup in the customer VRF)
(In a longer core every transit P router would SWAP the transport label; only the last one pops it.)State & Parameter Comparison Table
| Parameter / Feature | Traditional IP Routing | MPLS Label Switching (LSR) |
|---|---|---|
| Route Lookup | Longest Prefix Match (LPM) in FIB | Exact match on the 20-bit label in LFIB |
| Core Router (P) State | Holds all external routes (Internet Full View, 900k+) | BGP-Free Core: only IGP routes (PE loopbacks and core links) |
| Service Isolation (VPN) | GRE tunnels / VRF-lite on every hop | Native multi-service overlays (L3VPN/EVPN) at the PE edge |
| Forwarding Speed | Modern ASICs forward IP at line rate | Same: the MPLS benefit is a simpler core and services, not raw speed |
Shared course lab
Prerequisites are CCNA-level IPv4/IPv6, OSPF/IS-IS, iBGP/eBGP and CLI. Use EVE-NG/GNS3; Containerlab requires a compatible image with MPLS forwarding. Obtain images and licences legally. Lesson minutes estimate theory only; labs need additional time and resources.
Topology: CE-A1 — PE1 — P1 — P2 — PE2 — CE-A2, with RR connected to the core. Add a higher-metric PE1–P2 backup and customer B on separate PE ports. Loopbacks: PE1 10.255.0.1/32, P1 .11/32, P2 .12/32, PE2 .2/32, RR .100/32. Provider AS65000. Links: PE1–P1 10.0.0.0/30 (.1/.2), P1–P2 10.0.0.4/30 (.5/.6), P2–PE2 10.0.0.8/30 (.9/.10), PE1–P2 10.0.0.12/30 (.13/.14). Give RR a separate core /30 and advertise its loopback.
Customer A: RT 65000:100, PE1 RD 65000:1, PE2 RD 65000:2. B: RT 65000:200 and RD 65000:3/4. Both use site1 192.168.10.0/24 and site2 192.168.20.0/24. A PE–CE links are 172.16.1.0/30 and 172.16.2.0/30; B uses 172.16.3.0/30 and 172.16.4.0/30. PEs use .1, CEs .2. Intentional overlap tests VRF isolation. All router IDs are unique.
Architectural model and principles
In real service provider networks, MPLS unifies equipment from multiple vendors. Below are practical configuration and operational verification examples across four platforms: Cisco IOS-XE/XR, Huawei VRP, Juniper Junos, and MikroTik RouterOS v7. Pay close attention to syntax nuances and label distribution mechanisms.
Practical Configuration (Cisco, Huawei, Juniper, MikroTik)
Cisco (IOS-XE/XR)
! PE1: label range, labels only for host routes, hide core TTL
mpls label range 20000 49999
mpls label protocol ldp
mpls ldp router-id Loopback0 force
mpls ldp label
allocate global host-routes
no mpls ip propagate-ttl forwarded
interface Loopback0
ip address 10.255.0.1 255.255.255.255
interface GigabitEthernet1
description to-P1
ip address 10.0.0.1 255.255.255.252
no shutdown
mtu 1600
mpls ipThe block of global commands sets the dynamic label range (mpls label range, effective after a reload), the LDP protocol and the LSR ID taken from Loopback0. allocate global host-routes limits labels to host /32 routes (PE loopbacks), which is the BGP-free core idea, and no mpls ip propagate-ttl forwarded hides the core from the customer traceroute. The port toward P1 gets an MTU of 1600 for the label stack, and mpls ip enables LDP and label processing on the link.
Huawei (VRP)
system-view
sysname PE1
interface LoopBack0
ip address 10.255.0.1 255.255.255.255
quit
mpls lsr-id 10.255.0.1
mpls
lsp-trigger host
quit
mpls ldp
quit
interface GigabitEthernet0/0/1
undo shutdown
ip address 10.0.0.1 255.255.255.252
mpls
mpls ldp
quitmpls lsr-id sets the LSR identifier from the loopback, and mpls in system-view enables MPLS globally; lsp-trigger host keeps LSPs only for host /32 routes (BGP-free core behavior). The global mpls ldp starts the protocol, then mpls and mpls ldp are enabled again on port Gi0/0/1 - without them no LDP adjacency forms. The loopback must have a /32 mask, otherwise under lsp-trigger host it gets no label.
Juniper (Junos)
set interfaces lo0 unit 0 family inet address 10.255.0.1/32
set interfaces ge-0/0/1 mtu 1600
set interfaces ge-0/0/1 unit 0 family inet address 10.0.0.1/30
set interfaces ge-0/0/1 unit 0 family mpls
set routing-options router-id 10.255.0.1
set protocols mpls no-propagate-ttl
set protocols mpls interface ge-0/0/1.0
set protocols ldp interface ge-0/0/1.0
set protocols ldp interface lo0.0The physical MTU of 1600 is needed for the label stack, and family mpls on unit 0 permits label processing on the interface. protocols mpls interface admits the port to MPLS, and no-propagate-ttl hides the core from the customer traceroute. LDP is enabled on the link toward P1 and on lo0.0: without lo0.0 the loopback does not appear as a FEC and there is no LSP to it.
MikroTik (RouterOS v7)
/interface/bridge/add name=lo0
/ip/address/add address=10.255.0.1/32 interface=lo0
/mpls/ldp/add lsr-id=10.255.0.1 transport-addresses=10.255.0.1
/mpls/ldp/interface/add interface=ether1In RouterOS v7 a loopback is emulated by a bridge without ports that carries a /32 address. The /mpls/ldp entry sets the LSR ID and the transport address (preferably the loopback), and /mpls/ldp/interface enables sending LDP Hello on ether1 and thereby labels on that link. The label range and TTL hiding are not touched here.
Operational Verification & LFIB (Cisco, Huawei, Juniper, MikroTik)
Cisco (IOS-XE/XR)
show mpls label range
show mpls interfaces
show mpls ldp parameters
show mpls forwarding-table 10.255.0.2 255.255.255.255 detailshow mpls label range prints the active label range, and show mpls interfaces must show Gi1 as Yes (ldp) and Operational Yes. In show mpls forwarding-table an outgoing label (not No Label) with interface Gi1 and next hop P1 is expected for 10.255.0.2/32, and the detail output contains Label Stack{...}.
Huawei (VRP)
display mpls interface
display mpls ldp session
display mpls lsp
display fib 10.255.0.2display mpls ldp session must contain a session with P1 in Operational status, DU mode and an Active/Passive role. In display mpls lsp the FEC 10.255.0.2/32 shows an incoming label NULL and an outgoing label, and display fib shows the P1 next hop; label 3 on the local FEC means Implicit Null.
Juniper (Junos)
show mpls interface
show ldp session
show route table mpls.0
show route 10.255.0.2 table inet.3show ldp session must show neighbor 10.255.0.11 in the Operational state with DU mode. The mpls.0 table contains the reserved labels 0, 1, 2 and 13 with the Receive action, and in inet.3 the route 10.255.0.2/32 has protocol LDP and a Push action with the P1 label.
MikroTik (RouterOS v7)
/mpls/ldp/interface/print
/mpls/ldp/neighbor/print
/mpls/ldp/local-mapping/print
/mpls/forwarding-table/printIn /mpls/ldp/neighbor/print the neighbor must be operational, local-mapping contains a label for 10.255.0.1/32, and /mpls/forwarding-table/print shows entries with an outgoing label and interface ether1.
Common architectural or configuration pitfall
set protocols mpls interface lo0.0
An engineer builds a core from Cisco P routers and Huawei PEs. The PE2 loopback on Huawei is configured as 10.255.0.2/24 and advertised in OSPF with that mask. Cisco LDP allocates a label for any IGP prefix by default and the session comes up, but Huawei applies lsp-trigger host: LSPs are built only for host /32 routes. No binding is created for the prefix 10.255.0.0/24, display mpls lsp has no entry for 10.255.0.2, and the BGP VPNv4 next hop of PE2 points to a nonexistent LSP. The symptom is treacherous: the IGP works, ping 10.255.0.2 from PE1 succeeds, the LDP session is Operational, the BGP session is Established and VPNv4 routes are visible, yet customer VPN traffic is lost and CE-to-CE ping fails. Diagnosis: show mpls forwarding-table 10.255.0.2 255.255.255.255 shows no outgoing label (No Label) or an empty output, and on Junos show route 10.255.0.2 table inet.3 returns no LDP route. Fix: make the loopback a /32 (or explicitly widen lsp-trigger with an ip-prefix) and check the LSP with ping mpls or traceroute mpls. Lesson: before enabling services, make sure every PE loopback has a label on every LSR along the whole path.
Verification commands across 4 vendors (Cisco, Huawei, Juniper, MikroTik)
Cisco (IOS-XE/XR)
show mpls ldp neighbor
show mpls forwarding-table
show mpls ldp bindings 10.255.0.2 32Huawei (VRP)
display mpls ldp session
display mpls lsp
display mpls ldp lspJuniper (Junos)
show ldp session
show route table inet.3
show ldp databaseMikroTik (RouterOS v7)
/mpls/ldp/session/print
/mpls/forwarding-table/print
/mpls/ldp/binding/printVerification commands are tailored for leading carrier platforms: Cisco IOS-XE/XR, Huawei VRP, Juniper Junos, and MikroTik RouterOS v7. Always verify label bindings against active LFIB on your lab routers.
Expected output
Cisco, PE1 (abbreviated):
PE1#show mpls label range
Downstream Generic label region: Min/Max label: 20000/49999
PE1#show mpls interfaces
Interface IP Tunnel BGP Static Operational
GigabitEthernet1 Yes (ldp) No No No Yes
PE1#show mpls forwarding-table 10.255.0.2 255.255.255.255 detail
Local Outgoing Prefix Bytes Label Outgoing Next Hop
Label Label or Tunnel Id Switched interface
20005 16012 10.255.0.2/32 0 Gi1 10.0.0.2
MAC/Encaps=14/18, MRU=1500, Label Stack{16012}The outgoing label 16012 was allocated by neighbor P1; PE1 also has its own local label 20005 from the range 20000-49999. If No Label appears instead of a label, there is no LDP binding.
Juniper, PE1 (abbreviated):
user@PE1> show ldp session
Address State Connection Hold time Adv. Mode
10.255.0.11 Operational Open 28 DU
user@PE1> show route table mpls.0
mpls.0: 6 destinations, 6 routes (6 active, 0 holddown, 0 hidden)
0 *[MPLS/0] 00:42:11, metric 1
Receive
1 *[MPLS/0] 00:42:11, metric 1
Receive
2 *[MPLS/0] 00:42:11, metric 1
Receive
13 *[MPLS/0] 00:42:11, metric 1
Receive
user@PE1> show route 10.255.0.2 table inet.3
inet.3: 2 destinations, 2 routes (2 active, 0 holddown, 0 hidden)
10.255.0.2/32 *[LDP/9] 00:05:12, metric 1
> to 10.0.0.2 via ge-0/0/1.0, Push 299808Four reserved labels (0, 1, 2, 13) are handled locally in mpls.0 (Receive). Push 299808 is the label allocated by P1 for the FEC 10.255.0.2/32; values differ between platforms.
Huawei, PE1 (abbreviated):
<PE1>display mpls ldp session
PeerID Status LAM SsnRole SsnAge KASent/Rcv
------------------------------------------------------------------------------
10.255.0.11:0 Operational DU Passive 0000:00:12 49/49
TOTAL: 1 session(s) Found.
<PE1>display mpls lsp
LSP Information: LDP LSP
FEC In/Out Label In/Out IF Vrf Name
10.255.0.1/32 3/NULL -/-
10.255.0.2/32 NULL/1025 -/GE0/0/1The row NULL/1025 means: PE1 is the ingress for 10.255.0.2/32 (there is no incoming label) and pushes label 1025; 3/NULL on the local FEC is Implicit Null. The output format and label numbers depend on the VRP version.
Common errors and troubleshooting
| Symptom | Cause | Check | Fix |
|---|---|---|---|
| LDP session does not come up | mpls ip / mpls ldp is not enabled on the port, or the loopback transport is unreachable | show mpls interfaces, display mpls interface, ping loopback to loopback | Enable MPLS on both ends of the link, check the IGP route to the LSR ID |
| Session is up, but there is no label for the PE loopback | A /24 loopback with lsp-trigger host on Huawei, or a binding filter | display mpls lsp, show mpls ldp bindings 10.255.0.2 32 | Make the loopback a /32 or widen lsp-trigger |
| Ping works, but VPN traffic is lost | No LSP to the BGP next hop, the transport breaks in the middle | show mpls forwarding-table, show route table inet.3 | Check MPLS on every transit port |
| Large packets are lost, small ones pass | The core MTU does not account for 4 bytes per label | ping size 1500 df-bit, show interfaces | Raise the MTU on all core links (for example 1600) |
| P nodes are visible in the customer traceroute | TTL is copied into the label | traceroute from the CE | Disable propagate-ttl on the PE (no mpls ip propagate-ttl, no-propagate-ttl) |
| LDP labels and the SRGB overlap | The dynamic range covers 16000-23999 | show mpls label range | Change the range and reload the node |
Self-check list
- I can name the four fields of a stack entry (Label 20, TC 3, S 1, TTL 8) and the ethertype 0x8847.
- I can explain why the BGP-free core offloads P nodes and what they need to know (the IGP and labels for the PE /32 loopbacks).
- I can tell the transport label from the service label and I know why PHP is needed.
- I can name the reserved labels 0, 1, 2, 3, 7, 13 and the start of the dynamic range (16).
- I can calculate the stack overhead and the MTU required for L3VPN.
- I can explain that MPLS does not encrypt, and how VRF-lite and GRE differ from MPLS L3VPN.
- I can verify the LSP to the PE loopback with
show mpls forwarding-table(display mpls lsp,show route table inet.3).
Working in the lab
A learning model explains a rule; it is not Cisco/Huawei CLI. Predict the result first, then prove it using relevant tables and customer traffic. Select commands by their documented scope and image support, not merely by a familiar name. Keep control-plane state and actual forwarding evidence separate.
- Save configuration and a healthy baseline: interfaces, adjacencies, tables, counters and timestamps.
- Verify the prerequisites. Restore a broken lower layer before changing an upper layer.
- Explain the causal relationship between inputs and outcome in example 1.
- Compare example 2, identify changed conditions and explain the different result.
- Complete the text exercises, then reproduce applicable behaviour on an isolated lab network.
- Test permitted and prohibited scenarios. One successful flow does not prove isolation from another VPN.
- Inject one fault, predict its symptom and collect evidence. Restore the original state afterwards.
Evidence to retain
Record platform/image, topology, initial conditions, commands, predicted and observed results. For the data plane, retain a flow, capture or scoped counters. For the control plane, inspect specific routes or adjacencies rather than a general green status. Separate failure detection, local repair and later convergence in your measurements. Compare both traffic directions and select the correct VRF and source address. A working return path is part of the proof, not an assumption.
If the image lacks a required feature, record the limitation and the supported platform needed. Do not silently substitute a different protocol. Counter deltas need a known measurement interval; absolute totals can include earlier traffic. Packet loss, latency and throughput are separate outcomes and require appropriate measurements. Keep credentials outside configuration repositories and retain management access during changes.
Completion criteria
Explain both examples in your own words, restore the missing fields, demonstrate the relevant checks and reproduce rollback. Label numbers are illustrative and locally assigned in a real LFIB. Never apply educational fragments to production. The complete module lab is in its fourth lesson and depends on the earlier three. Preserve your report so a second engineer can reproduce the result without guessing hidden prerequisites.