Skip to content

MPLS TE Tunnels

On top of YAML-based topologies and Traffic Engineering attributes, a diagram can declare MPLS TE tunnels (RSVP-TE or SR-TE style) in a top-level lsps: section. Topolograph runs CSPF (Constrained Shortest Path First) placement over them — same bandwidth/affinity/SRLG constraints as a real router, without any signaling — and visualizes the result.

MPLS TE tunnels: LSP table and placed paths on the graph

The LSP tunnels tab lists every path with its placement status, the reason it could not be placed, bandwidth and priorities; selecting a node shows the tunnels that are ingress, transit or egress on it, and the placed paths are drawn on the network diagram.

YAML diagrams only, for now

lsps: is available on YAML-based diagrams. Support for tunnels reported live by a watcher is planned for a later release.

A tunnel in YAML

lsps:
  TUN_R1_R3:                    # key = tunnel name
    src: 10.10.10.1
    dst: 10.10.10.3
    metric_type: te             # igp (default) | te
    bandwidth: 2G                # applies to every path unless overridden
    setup_priority: 7
    admin_groups:
      exclude-any: [red]
    color: "#ff9900"
    autoroute: false             # see "autoroute" below
    paths:                       # = LSPs; omit entirely for one dynamic primary
      primary:
        ero:
          - 10.10.10.2                        # plain string = loose hop
          - {node: 10.10.10.3, hop: strict}    # explicit form for a strict hop
      secondary:
        role: standby
        bandwidth: 1G            # overrides the tunnel-level default
        srlg_exclude: [1001]

Key reference

key level values / format meaning
lsps top-level dict, tunnel name → body optional section next to nodes/edges
src, dst tunnel node name (IP-address format) tunnel endpoints
metric_type tunnel igp (default) | te which metric CSPF optimizes for
bandwidth tunnel/path 2G, 500M, or a raw bps number required bandwidth; a path overrides the tunnel default
setup_priority tunnel/path 07, default 7 RSVP-TE admission pool (0 is the strongest)
hold_priority tunnel/path 07, defaults to setup_priority pool the reservation is held in; may not be weaker than the setup priority
admin_groups tunnel/path dict: exclude-any / include-any / include-all → list of names affinity filter
srlg_exclude path list of int SRLG constraint
color tunnel CSS color highlight color on the network diagram
autoroute tunnel bool, default false see below
paths tunnel dict, path name → body; omitted = one dynamic primary the tunnel's LSPs
role path primary (default) | secondary | standby path role
ero path list: 10.10.10.2 (loose hop) or {node: ..., hop: strict} explicit route

A path inherits bandwidth, setup_priority, hold_priority and admin_groups from the tunnel when it omits them. srlg_exclude and ero are not inherited — declaring srlg_exclude at tunnel level has no effect, set it on each path that needs it.

Edges declare the same TE attributes covered on the Traffic Engineering page (temetric, max_rsrv_link_bw, admin_group/affinity, srlg, unreserved_bw_0unreserved_bw_7) — no separate naming for MPLS purposes.

autoroute

A signaled LSP does not redirect traffic on its own — that matches real RSVP-TE/SR-TE behavior: without autoroute announce (or an explicit static route pointing at the tunnel), a tunnel is just reserved bandwidth, invisible to IGP-style path computation. Set autoroute: true on a tunnel to have it act as a forwarding shortcut in end-to-end path queries (see with_lsps below) — the real-world equivalent of turning on autoroute on the headend.

Setup and holding priority

0 is the strongest priority and 7 the weakest. Leave hold_priority out and it follows setup_priority, the same as a router's priority <setup> with the second value omitted.

A tunnel set up at a strong priority but held at a weak one would be preemptable the moment it is signaled, so that combination is rejected: hold_priority must be at least as strong as setup_priority (setup_priority: 0 with hold_priority: 7 is a validation error, setup_priority: 7 with hold_priority: 0 is fine).

Unsupported (operational) keys

rro, oper_status, active_lsp_name, and any label_* key are not supported in lsps: — they describe live signaling state (Record Route, current status, active path), not declared intent, and only make sense once a real watcher reports them. A validation error is raised if you include one.

CSPF path calculation

On every recalculation, Topolograph processes each path in setup_priority order (RSVP-TE convention: 0 highest), same rules a real router would apply:

  • filters out edges that don't have enough bandwidth at the requested setup_priority pool, don't satisfy the affinity filter, or are in an excluded SRLG;
  • runs the shortest path over what's left, honoring any ero (a strict hop must be a direct edge from the previous hop — the LSP fails rather than being silently routed around it);
  • subtracts the placed bandwidth from that pool (and every lower-priority pool) before placing the next path.

Placement never mutates the advertised TE attributes (unreserved_bw_*) — the consumed capacity is tracked separately, so re-running placement always starts from the real advertised numbers.

ECMP ties are broken deterministically: fewest hops, then lexicographic order of node names.

Retrieving LSP placement results

GET /api/graph/{graph_time}/lsps and GET /api/graph/{graph_time}/lsps/{name} return each path's placement outcome alongside its declared config:

{
  "name": "TUN_R1_R3",
  "src": "10.10.10.1",
  "dst": "10.10.10.3",
  "paths": {
    "primary": {
      "placed": true,
      "reason": null,
      "cost": 20,
      "path": ["10.10.10.1", "10.10.10.2", "10.10.10.3"]
    },
    "secondary": {
      "placed": false,
      "reason": "insufficient bandwidth",
      "cost": null,
      "path": []
    }
  }
}

reason explains, in words, why a path could not be placed. Alongside it, reason_code gives the machine-readable category and binding_constraints names what is actually blocking:

reason_code meaning what to do
disconnected no path exists even with every constraint lifted repair the topology
constraints_unsatisfiable a path exists, the request is too strict relax the constraints in binding_constraints (bandwidth, affinity, srlg)
ero_strict_hop_unreachable a strict hop has no edge from the previous hop fix the ero
endpoint_not_found src/dst is not in the graph fix the endpoint

Several entries in binding_constraints mean lifting any one of them is enough to place the LSP.

Useful filters on the list endpoint:

# Which tunnels failed to place, and why
graph.lsps_list(status="unplaced")

# Which tunnels cross a given node or link (pre-maintenance impact check)
graph.lsps_list(via_node="10.10.10.2")
graph.lsps_list(via_edge="10.10.10.1,10.10.10.2")

How much TE bandwidth is left on a link, after accounting for every placed tunnel:

graph.edges_list(include=["lsp_left_bw"])
# -> ..., "lsp_left_bw_7": ..., "lsp_reserved_bw": "7Gbps",
#    "lsp_left_bw": "3Gbps", "lsp_bandwidth_usage": "7Gbps/3Gbps"

CSPF path, without declaring a tunnel

cspf_path answers "which path satisfies these constraints, and at what cost" — a constraint-filtered shortest-path computation, the same class of query as a plain shortest path. Nothing is created or persisted:

result = graph.cspf_path(
    "10.10.10.1", "10.10.10.7",
    bandwidth="5G",
    admin_exclude_any=["red"],
)
# {'path': [...], 'cost': 42, 'reason': ''}
# or, if nothing fits: {'path': [], 'cost': None, 'reason': 'no path ... satisfies the requested constraints: ...'}

The answer accounts for bandwidth the already declared tunnels hold: on a topology with an lsps: section the check runs against what each link has left after placement, not against an aggregate figure, so it never returns a path over capacity that is already taken. Constraints are evaluated per setup priority, so a link can be full at one priority and still have room at a stronger one.

Affinity constraints (admin_exclude_any, admin_include_any, admin_include_all) match affinity names on the link. Topologies ingested from a live network advertise the admin group as a bitmask, so a name-based include-any/include-all on those graphs matches nothing and the answer is "no path" — use exclude-any or a YAML topology with named affinities there.

End-to-end path via tunnels (with_lsps)

By default, graph.paths.shortest(src, dst) is a plain IGP path — unaffected by any tunnel in the graph, same as real IP forwarding without autoroute. Pass with_lsps=True to account for autoroute: true tunnels as forwarding shortcuts:

graph.paths.shortest("10.10.10.1", "10.10.10.4")               # plain IGP path
graph.paths.shortest("10.10.10.1", "10.10.10.4", with_lsps=True) # via active autoroute tunnels

edge_failure_reaction predicts the whole-network impact of one or more links failing — the links that lose traffic and the links that pick it up:

graph.paths.edge_failure_reaction([("10.10.10.1", "10.10.10.2")])
# {'isGraphStillConnected': True, 'affectedLinks': {...}, 'disjointedNodes': []}

For a per-tunnel view of the same question, combine it with lsps_list(via_edge=...) to see which tunnels traverse the link before checking its failure impact.


Related: YAML-based Topologies · Traffic Engineering attributes · Python SDK