spf

package
v0.0.0-...-dc082c2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

Documentation

Overview

Package spf computes IS-IS shortest paths via Dijkstra over the LSDB graph and installs the resulting routes.

Index

Constants

View Source
const DefaultAdminDistance uint8 = 115

DefaultAdminDistance is the IS-IS administrative distance set on every locrib.Path (classical default 115). sysrib overrides it from the rib.admin-distance.isis leaf via effectivePriority; this is the value placed on the Path so that, absent config, IS-IS ranks at 115 against other protocols.

View Source
const DefaultDebounce = 200 * time.Millisecond

DefaultDebounce is the SPF debounce window: an LSDB change marks the level dirty and arms this timer; a burst of changes within the window collapses to one SPF run per level (spec R-3 / AC-9, research guide sec 5). A few hundred ms balances convergence latency against thrash on a flapping link.

View Source
const MaxPathMetric uint64 = 0xFE000000

MaxPathMetric is RFC 5305 / RFC 5308 MAX_PATH_METRIC. A prefix advertised with a metric >= this value is unreachable and excluded from SPF; an accumulated path cost is clamped here so a sum of wide metrics never wraps (spec "Total path metric" boundary, RFC 5305 sec 3 / RFC 5308 sec 2).

View Source
const MaxV6PathMetric uint64 = 0xFE000000

MaxV6PathMetric is RFC 5308 sec 2 MAX_V6_PATH_METRIC. A TLV 236 prefix advertised with a metric STRICTLY GREATER than this value MUST NOT be considered during normal IPv6 SPF (it can be advertised for purposes other than building the normal routing table). It equals the RFC 5305 MAX_PATH_METRIC.

Variables

This section is empty.

Functions

func IndexByPrefix

func IndexByPrefix(routes []RouteEntry) map[netip.Prefix]RouteEntry

IndexByPrefix turns a route slice into a prefix-keyed map for diffing and for the installed-set snapshot the install layer retains between runs.

func ProtocolID

func ProtocolID() redistevents.ProtocolID

ProtocolID returns the registered IS-IS Loc-RIB / redistribute protocol ID. Exposed so the redistribution spec (isis-11) reuses the SAME identity rather than registering a second one (the registry is idempotent on name, but a single accessor keeps the contract explicit).

Types

type Computer

type Computer struct {
	// contains filtered or unexported fields
}

Computer runs SPF per level and installs the result into the Loc-RIB. It is safe for concurrent Trigger calls (the debounce + run are serialized internally). One Computer instance serves one IS-IS engine.

func NewComputer

func NewComputer(cfg Config) *Computer

NewComputer constructs a Computer from cfg. A zero Debounce uses DefaultDebounce. A nil Installer is created with a nil Loc-RIB (install no-ops); callers wanting real install pass NewInstaller(locrib.Default()).

func (*Computer) LeakResult

func (c *Computer) LeakResult() LeakResult

LeakResult returns the most recent inter-level leak set computed by Run (the prefixes leaked into each level). Empty on a single-level node or before the first Run. A copy of the slices is not made (the engine treats them as read-only); they are replaced wholesale on the next Run.

func (*Computer) ResetSPFLog

func (c *Computer) ResetSPFLog()

ResetSPFLog clears the SPF-run history. `clear isis counters` calls it to reset observational state without disturbing the route set (spec-isis-13 AC-8).

func (*Computer) Routes

func (c *Computer) Routes() []RouteEntry

Routes returns a copy of the currently installed IPv4 route set (for tests and the engine snapshot). Order is unspecified.

func (*Computer) RoutesV6

func (c *Computer) RoutesV6() []RouteEntry

RoutesV6 returns a copy of the currently installed IPv6 route set (isis-12).

func (*Computer) Run

func (c *Computer) Run() RouteDelta

Run computes SPF for every configured level NOW (bypassing the debounce) and installs the arbitrated route set into the Loc-RIB. It is the synchronous core the debounce timer calls; tests call it directly. It returns the applied delta. Concurrent Run calls are serialized by the run lock so two timers (or a Run plus a timer) never interleave a half-applied set.

func (*Computer) SPFLog

func (c *Computer) SPFLog() []SPFLogEntry

SPFLog returns the recorded SPF-run history, newest first (the `show isis spf-log` rows, spec-isis-13 AC-6). It never exposes the live ring.

func (*Computer) SetLevels

func (c *Computer) SetLevels(levels []Level)

SetLevels updates the levels SPF computes (the node's configured level: L1, L2, or both). An empty slice leaves both levels. Called from setConfig once the level is known; safe before any Trigger.

func (*Computer) SetMetrics

func (c *Computer) SetMetrics(reg metrics.Registry)

SetMetrics registers the SPF-owned Prometheus series (this spec OWNS exactly these rows from the umbrella canonical Metrics table) and forwards the registry to the Installer for ze_isis_routes_installed. A nil registry is ignored.

func (*Computer) SetOnChange

func (c *Computer) SetOnChange(fn func(RouteDelta))

SetOnChange installs the redistribution read callback (spec-isis-11): after every Run that produced a non-empty delta, the Computer calls fn with the applied delta so the engine can emit redistevents batches (export IS-IS -> BGP). nil disables it. Safe to call before any Trigger; the callback runs outside the run lock so it may re-Trigger without deadlocking. This is SEPARATE from the FIB install (the Installer): redistribution NEVER installs to the kernel.

func (*Computer) SetOnChangeV6

func (c *Computer) SetOnChangeV6(fn func(RouteDelta))

SetOnChangeV6 installs the IPv6 redistribution read callback (isis-12): after every Run that produced a non-empty IPv6 delta, the Computer calls fn so the engine can emit redistevents batches at AFI=2 (export IS-IS IPv6 -> BGP). nil disables it. Separate from SetOnChange so the IPv4 and IPv6 deltas are emitted with their own family. Like SetOnChange this is the REDISTRIBUTION path only, never the FIB install.

func (*Computer) SetOnLeak

func (c *Computer) SetOnLeak(fn func(LeakResult))

SetOnLeak installs the RFC 2966 inter-level leak callback (isis-9 AC-4/AC-5): after every Run, the Computer calls fn with the prefixes to re-originate into each level (L2->L1 down, L1->L2 up). The engine stores the set and re-originates only when it changed; because the leak skips down-bit prefixes, the re-origination's SPF run recomputes the same set and the feedback loop terminates in one pass (no churn). nil disables leaking. Safe to call before any Trigger; the callback runs outside the run lock so it may re-Trigger without deadlocking. This is SEPARATE from the FIB install and the redistribution seam: leaking only re-originates the node's own LSP.

func (*Computer) SetRoot

func (c *Computer) SetRoot(root types.SystemID)

SetRoot updates the System ID SPF is rooted at (the node's own ID). Called once the config resolves. Safe to call before any Trigger.

func (*Computer) SetSPFLogTrigger

func (c *Computer) SetSPFLogTrigger(reason string)

SetSPFLogTrigger records the reason the next recorded SPF run will report in `show isis spf-log` (spec-isis-13). The engine debounce path passes "lsdb-change"; a direct Run with no trigger set reports "manual". Safe to call concurrently with Run.

func (*Computer) Snapshot

func (c *Computer) Snapshot() []RouteSnapshotEntry

Snapshot returns the `show isis route` view of the currently installed route set (rendered by isis-13). It reflects the last Run.

func (*Computer) SnapshotV6

func (c *Computer) SnapshotV6() []RouteSnapshotEntry

SnapshotV6 returns the `show isis route ipv6` view of the currently installed IPv6 route set (isis-12), reflecting the last Run.

func (*Computer) Stop

func (c *Computer) Stop()

Stop forward-removes every installed IS-IS route from the Loc-RIB, cancels any pending debounce timer, and marks the Computer stopped so a debounce callback that already fired (or a later Trigger/Run) cannot re-install routes (engine shutdown / NET removal -> no stale FIB). It then drains any in-flight timer goroutine OUTSIDE the lock so the engine can sequence Stop before transport.Close with the guarantee that no SPF run is still touching the Loc-RIB. After Stop the Computer holds no routes and stays stopped.

func (*Computer) Trigger

func (c *Computer) Trigger()

Trigger marks SPF dirty and arms the debounce timer if one is not already pending; a burst of Triggers within the window results in a single Run (spec AC-9). It returns immediately (non-blocking); the actual SPF runs from the timer callback on a separate goroutine. A nil afterFunc (never in production) would run synchronously.

type Config

type Config struct {
	Source    Source
	Resolver  NextHopResolver
	Root      types.SystemID
	Levels    []Level
	Debounce  time.Duration
	Installer *Installer
	// ResolverV6 / InstallerV6 enable the IPv6 install pass (isis-12). Both nil
	// (an IPv4-only node) skips IPv6 entirely. The SPF tree is shared; the IPv6
	// pass only adds TLV 236 leaf extraction + IPv6-family Loc-RIB insertion.
	ResolverV6  NextHopResolverV6
	InstallerV6 *Installer
}

Config configures a Computer. src and resolver are mandatory (a nil Source yields empty graphs; a nil resolver drops every next-hop). Levels lists the levels to compute (L1, L2, or both for an L1L2 node). Loc may be nil (forked subprocess; install no-ops).

type Edge

type Edge struct {
	// To is the neighbor Source ID (a router with pseudonode 0, or a LAN
	// pseudo-node with pseudonode != 0).
	To types.SourceID
	// Metric is the 24-bit IS-reachability edge metric (RFC 5305 sec 3). A
	// pseudo-node edge is 0.
	Metric uint32
}

Edge is one directed adjacency in the graph: from the node that advertised it (the LSP originator) to a neighbor, weighted by the TLV 22 wide metric. A pseudo-node's edges to its members carry metric 0 (the LAN is free).

type Graph

type Graph struct {
	// Nodes is every vertex keyed by its Source ID. A router and its
	// pseudo-nodes are distinct entries.
	Nodes map[types.SourceID]*Node
}

Graph is a parsed, per-level snapshot of the link-state database: the set of nodes keyed by Source ID. SPF runs over it without touching raw LSP bytes.

func BuildGraph

func BuildGraph(src Source, level Level) *Graph

BuildGraph constructs the directed graph for one level from src. Each LSP contributes its originator's edges (TLV 22), prefixes (TLV 135), and interface addresses (TLV 132); the overload bit (RFC 3787) marks the node transit- excluded. Pseudo-node edges keep their advertised metric (a pseudo-node advertises metric 0 to its members per ISO/IEC 10589 clause 7.2.5; a router advertises its circuit metric to the pseudo-node). A TLV whose decode fails is skipped without aborting the build (one bad LSP excludes one contribution).

func NewGraph

func NewGraph() *Graph

NewGraph returns an empty graph.

type Installer

type Installer struct {
	// contains filtered or unexported fields
}

Installer inserts SPF routes into the shared Loc-RIB and tracks the installed set so a subsequent run's diff can add/change/remove precisely. It mirrors the BGP install shape: InsertForward on add/change, Remove (forward-remove) on loss. With neither a local Loc-RIB nor a remote sink (an unwired test), every operation is a no-op, exactly as the BGP RIB is nil-safe; a forked subprocess gets a RouteSink (SetRemoteSink) so ops reach the engine over RPC.

func NewInstaller

func NewInstaller(loc *locrib.RIB) *Installer

NewInstaller constructs an Installer for the IPv4-unicast family over loc (may be nil in a forked subprocess). distance is the admin distance to stamp on each Path (DefaultAdminDistance unless overridden). Metrics start as no-ops until SetMetrics wires a registry.

func NewInstallerV6

func NewInstallerV6(loc *locrib.RIB) *Installer

NewInstallerV6 constructs an Installer for the IPv6-unicast family (isis-12). It is identical to the IPv4 Installer except for the Loc-RIB family and the ze_isis_routes_installed afi label; the Loc-RIB insertion path, admin distance (115), ECMP Instance handling, and diff logic are shared.

func (*Installer) Apply

func (in *Installer) Apply(cur []RouteEntry) RouteDelta

Apply installs the route set cur, diffing against the previously installed set: added/changed prefixes are inserted (one locrib.Path per equal-cost next-hop, distinct Instance, mirroring BGP rib_bestchange.go:813); removed prefixes (and dropped ECMP next-hops on a shrinking set) are forward-removed. It returns the applied delta for the caller's logging/metrics. Safe with a nil Loc-RIB (every Insert/Remove is skipped, the installed set is still tracked for the snapshot).

func (*Installer) Installed

func (in *Installer) Installed() []RouteEntry

Installed returns the current installed route set (a copy of the entries) for the `show isis route` snapshot and for tests. Order is unspecified; the snapshot sorts.

func (*Installer) RemoveAll

func (in *Installer) RemoveAll()

RemoveAll forward-removes every installed prefix (engine shutdown / NET removal) so IS-IS leaves no stale routes in the Loc-RIB.

func (*Installer) SetMetrics

func (in *Installer) SetMetrics(reg metrics.Registry)

SetMetrics registers the ze_isis_routes_installed{level,afi} gauge owned by this spec (umbrella canonical Metrics table). Other ze_isis_* SPF series are registered on the Computer (SetMetrics there). A nil registry is ignored.

func (*Installer) SetRemoteSink

func (in *Installer) SetRemoteSink(sink RouteSink)

SetRemoteSink installs the forked route sink used when the local Loc-RIB is nil (locrib.Default() returned nil in a forked subprocess). If loc is non-nil (in-process), the local RIB is always preferred and the sink stays unused.

type LSPRecord

type LSPRecord struct {
	// Source is the LSP originator's Source ID (System ID + pseudonode). A
	// pseudo-node LSP has a non-zero pseudonode number.
	Source types.SourceID
	// Overload is the OL bit from the LSP type block (fragment 0; RFC 3787). The
	// builder ORs it across fragments so any fragment carrying it marks the node.
	Overload bool
	// LSP is the decoded LSP whose TLV 22 / 132 / 135 the builder reads. The TLV
	// value slices may alias the LSDB's stable raw bytes (lazy decode).
	LSP packet.LSP
}

LSPRecord is one LSP the graph builder consumes: its originator Source ID, the overload bit, and the decoded TLVs. The Source interface yields these so SPF reads the LSDB through a narrow, testable boundary (a hand-built slice in unit tests; the engine's LSDB in production).

type LeakResult

type LeakResult struct {
	// IntoL1 are the prefixes leaked DOWN from L2 into L1 (RFC 2966: up/down bit
	// set). IPv4 (TLV 135).
	IntoL1 []LeakedPrefix
	// IntoL2 are the prefixes leaked UP from L1 into L2 (up/down bit clear). IPv4.
	IntoL2 []LeakedPrefix
	// IntoL1V6 / IntoL2V6 are the IPv6 (TLV 236) equivalents over the SHARED SPF
	// tree (RFC 5308 sec 5): identical leak rules, IPv6 prefixes.
	IntoL1V6 []LeakedPrefix
	// IntoL2V6 are the IPv6 prefixes leaked UP from L1 into L2.
	IntoL2V6 []LeakedPrefix
}

LeakResult is the per-target-level set of prefixes to re-originate. A nil/empty slice for a level means nothing leaks into it. Only an L1L2 router (both levels present in the SPF result) produces a non-empty result; a single-level node leaks nothing (there is no other level to leak from).

func LeakPrefixes

func LeakPrefixes(results []*Result, graphs map[Level]*Graph) LeakResult

LeakPrefixes computes the RFC 2966 inter-level leak for an L1L2 router from the per-level SPF results and graphs. For each target level it collects the OTHER level's reachable IS-IS prefixes (a node reachable as a forwarding destination in that level), skipping:

  • the root's own prefixes (already advertised at both levels by the connected / redistribution origination, never "leaked");
  • any source prefix that ALREADY carries the up/down (down) bit -- the loop-prevention rule (RFC 2966): a down-bit prefix MUST NOT be leaked back up, and re-leaking a down-bit prefix downward is pointless churn. Skipping it in BOTH directions makes the leak a one-pass fixpoint, so the re-origination it triggers does not loop.

The leaked entry's up/down bit is set for the L2->L1 (down) direction and clear for the L1->L2 (up) direction. The leaked metric is the full source-level path cost (node distance + prefix metric), clamped at MaxPathMetric.

results must contain both Level1 and Level2 for any leak to occur; a result set with a single level returns an empty LeakResult (a single-level node has no other level to leak from). The function is pure (no engine state) so it is fully unit-testable on a hand-built topology.

func (LeakResult) Empty

func (r LeakResult) Empty() bool

Empty reports whether nothing leaks in any direction or family.

type LeakedPrefix

type LeakedPrefix struct {
	// Prefix is the leaked destination prefix (IPv4 for TLV 135, IPv6 for TLV 236).
	Prefix netip.Prefix
	// Metric is the full path cost to the prefix in the SOURCE level (node distance
	// + prefix metric), clamped at MaxPathMetric so a wide-metric sum never wraps.
	// It fits in 32 bits because MaxPathMetric (0xFE000000) is below 2^32.
	Metric uint32
	// UpDown is the RFC 2966 up/down bit to stamp on the re-originated entry: set
	// for an L2->L1 (down) leak, clear for an L1->L2 (up) leak.
	UpDown bool
}

LeakedPrefix is one prefix an L1L2 router re-originates into a level after leaking it from the other level. The engine turns it into a TLV 135 (IPv4) or TLV 236 (IPv6) entry in this level's own LSP, with UpDown reflecting the RFC 2966 down bit (set for an L2->L1 down leak, clear for an L1->L2 up leak).

type Level

type Level uint8

Level is the routing level a graph/SPF run covers (1 or 2). It mirrors the LSDB level; an L1L2 node runs SPF once per level.

const (
	Level1 Level = 1
	Level2 Level = 2
)

Graph levels.

func (Level) String

func (l Level) String() string

String renders the level as the canonical metric/CLI token "l1"/"l2".

type NextHop

type NextHop struct {
	// Addr is the next-hop IP address (the adjacent neighbor's interface address,
	// TLV 132). Invalid means the next-hop could not be resolved and the entry is
	// dropped.
	Addr netip.Addr
	// Interface is the outgoing interface name the adjacency is on. Empty when
	// unknown (the kernel then resolves the egress from the route table).
	Interface string
}

NextHop is one resolved equal-cost next-hop for an installed prefix: the IP address to forward to and the outgoing interface. SPF resolves the first-hop System ID to this via the NextHopResolver (the local adjacency table).

type NextHopResolver

type NextHopResolver interface {
	ResolveNextHop(level Level, neighbor types.SystemID) (NextHop, bool)
}

NextHopResolver maps a first-hop neighbor System ID (a directly-adjacent router) to its next-hop address and outgoing interface for one level. The engine implements it from the per-circuit adjacency tables (Shared Contracts "Next-hop derivation for SPF"): the neighbor's learned IPv4 interface address (TLV 132) and the circuit it is adjacent on. Returns ok=false when the neighbor has no usable adjacency (the next-hop is then skipped).

type NextHopResolverV6

type NextHopResolverV6 interface {
	ResolveNextHopV6(level Level, neighbor types.SystemID) (NextHop, bool)
}

NextHopResolverV6 maps a first-hop neighbor System ID to its IPv6 next-hop and outgoing interface for one level. The engine implements it from the per-circuit adjacency tables (Shared Contracts "Next-hop derivation for SPF"): the neighbor's learned IPv6 LINK-LOCAL address (from its IIH TLV 232) and the circuit it is adjacent on. A link-local next-hop is only meaningful with its interface, so the interface MUST be set for a link-local address (R-2). Returns ok=false when the neighbor has no usable IPv6 adjacency (the next-hop is then skipped, so a route is never installed pointing at an unresolvable next-hop).

type Node

type Node struct {
	// ID is the node Source ID (System ID + pseudonode number).
	ID types.SourceID
	// Edges are the outgoing adjacencies (TLV 22 entries, pseudo-node edges 0).
	Edges []Edge
	// Prefixes are the IP reachabilities the node originates (TLV 135).
	Prefixes []Prefix
	// PrefixesV6 are the IPv6 reachabilities the node originates (TLV 236,
	// RFC 5308 sec 2). Populated only when IPv6 is in use; the IPv6 route builder
	// (ipv6.go) attaches them over the same shared SPF tree.
	PrefixesV6 []Prefix
	// Addrs are the node's own IPv4 interface addresses (TLV 132). Used only as a
	// fallback next-hop source; the primary next-hop comes from the local
	// adjacency table (Shared Contracts "Next-hop derivation for SPF").
	Addrs []netip.Addr
	// AddrsV6 are the node's own IPv6 interface addresses (TLV 232, RFC 5308
	// sec 3). The LSP carries only non-link-local addresses; the link-local
	// next-hop comes from the local adjacency table (neighbor IIH TLV 232).
	AddrsV6 []netip.Addr
	// Overload is the LSP-database-overload (OL) bit (RFC 3787): the node is a
	// valid destination but excluded as a transit node in SPF.
	Overload bool
}

Node is one vertex: a router or a LAN pseudo-node. It holds the edges it advertises (TLV 22), the prefixes it originates (TLV 135), its own interface addresses (TLV 132, the next-hop source for a directly adjacent neighbor), and the overload flag (RFC 3787, routers only).

func (Node) IsPseudonode

func (n Node) IsPseudonode() bool

IsPseudonode reports whether this vertex is a LAN pseudo-node (pseudonode != 0) rather than a real router.

type NodeResult

type NodeResult struct {
	// ID is the reached node's Source ID.
	ID types.SourceID
	// Metric is the total path cost from the root (clamped at MaxPathMetric).
	Metric uint64
	// FirstHops are the System IDs of the directly-adjacent neighbors on the
	// equal-cost shortest paths to ID. The root itself has an empty set (it is
	// directly connected, no next-hop). One entry for a single path; multiple for
	// ECMP. A reachable node always has at least one first-hop unless it is the
	// root.
	FirstHops []types.SystemID
}

NodeResult is the SPF outcome for one reachable node: its total distance from the root and the set of equal-cost first-hops toward it (ECMP). The first-hop is a directly-adjacent neighbor System ID (a real router, never a pseudo-node); the route builder resolves it to a next-hop address + interface via the local adjacency table.

type Prefix

type Prefix struct {
	// Prefix is the advertised IPv4 prefix.
	Prefix netip.Prefix
	// Metric is the 32-bit prefix metric (RFC 5305 sec 4); read in full, never
	// capped at 24-bit.
	Metric uint32
	// UpDown is the up/down bit from the TLV 135 control octet (RFC 5305 sec 4.1
	// / RFC 2966): set means the prefix was leaked DOWN a level (L2 -> L1). A
	// prefix with this bit set MUST NOT be re-leaked UP into L2 (loop prevention).
	UpDown bool
}

Prefix is one IP reachability advertised by a node (TLV 135). SPF attaches it to the destination at the node's computed distance plus this prefix metric.

type Result

type Result struct {
	// Root is the System ID SPF was rooted at.
	Root types.SystemID
	// Level is the level this result covers.
	Level Level
	// Nodes maps each reachable node's Source ID to its result.
	Nodes map[types.SourceID]*NodeResult
}

Result is the full SPF output for one level: the reachable nodes keyed by Source ID, with the root's own entry at distance 0.

func Compute

func Compute(g *Graph, root types.SystemID, level Level) *Result

Compute runs Dijkstra over g rooted at the router with System ID root, for the given level. It returns the distance and ECMP first-hops to every reachable node. An overloaded node (RFC 3787) is reached as a destination but its outgoing edges are not relaxed, so it never serves as transit. A node absent from the graph (a dangling edge target) is skipped. The accumulator is 64-bit and clamped at MaxPathMetric so wide-metric sums never wrap (RFC 5305 sec 3).

type RouteDelta

type RouteDelta struct {
	// Added are prefixes new since the previous run.
	Added []RouteEntry
	// Changed are prefixes present in both runs whose metric / next-hop set /
	// level differs.
	Changed []RouteEntry
	// Removed are prefixes present in the previous run but gone now.
	Removed []netip.Prefix
}

RouteDelta is the difference between two successive route sets: the prefixes to add or change (the new RouteEntry) and the prefixes to remove (gone since the last run). The install layer (install.go) applies Added/Changed via InsertForward and Removed via a forward-remove.

func DiffRoutes

func DiffRoutes(prev, cur map[netip.Prefix]RouteEntry) RouteDelta

DiffRoutes computes the add/change/remove delta from the previously installed set prev to the newly computed set cur. Both are indexed by prefix. A prefix in cur but not prev is Added; in both with a differing route is Changed; in prev but not cur is Removed. Equality compares metric, level, up/down, and the (sorted) next-hop set, so a pure next-hop change is a Change (R-4: stale routes are removed when a neighbor is lost).

func (RouteDelta) Empty

func (d RouteDelta) Empty() bool

Empty reports whether the delta carries no changes.

type RouteEntry

type RouteEntry struct {
	// Prefix is the destination IPv4 prefix (masked to its length).
	Prefix netip.Prefix
	// Metric is the total IS-IS path cost (node distance + prefix metric),
	// clamped at MaxPathMetric.
	Metric uint64
	// Level is the level the winning route came from (1 or 2).
	Level Level
	// UpDown is the up/down bit of the winning advertisement (RFC 2966): set when
	// the prefix was leaked down a level. Carried for the snapshot / diagnostics;
	// it does not change the installed next-hop.
	UpDown bool
	// NextHops are the resolved equal-cost next-hops (ECMP). At least one for a
	// route that is published; sorted by address for a stable diff.
	NextHops []NextHop
}

RouteEntry is one prefix SPF resolved to install: the destination, the total IS-IS metric, the level it was selected from, the up/down state, and the equal-cost next-hops. Exactly one RouteEntry is published per prefix after the multi-level arbitration (the L1-over-L2 / up-down order is resolved here, NOT in sysrib).

func BuildRoutes

func BuildRoutes(results []*Result, graphs map[Level]*Graph, resolver NextHopResolver) []RouteEntry

BuildRoutes turns the per-level SPF results into the arbitrated set of routes to install. For each level it attaches every reachable node's TLV 135 prefixes at (node distance + prefix metric), resolving the node's first-hops to next-hops via res; a prefix advertised with a metric at or above MaxPathMetric is unreachable and skipped (RFC 5305 sec 4 / RFC 5308 sec 2). When the same prefix is reachable from more than one level / up-down state, the single winner is chosen by the RFC 5308 sec 5 preference order (L1-up > L2-up > L2-down > L1-down, then metric). The root's own connected prefixes (distance 0, empty first-hop set) are skipped here: they are installed by the connected route source, not IS-IS (avoids IS-IS claiming a directly-connected prefix).

results may hold one or both levels; resolver is the live next-hop source.

func BuildRoutesV6

func BuildRoutesV6(results []*Result, graphs map[Level]*Graph, resolver NextHopResolverV6) []RouteEntry

BuildRoutesV6 turns the per-level SPF results into the arbitrated set of IPv6 routes to install. It is the IPv6 twin of BuildRoutes: it walks the SAME SPF results / graphs (the shared tree), but attaches each reachable node's TLV 236 (IPv6) prefixes instead of TLV 135, resolves next-hops via the IPv6 resolver, and applies the RFC 5308 sec 2 MAX_V6_PATH_METRIC filter. The multi-level arbitration (preferenceRank / candidate.better) is shared with IPv4. The root's own connected prefixes (distance 0, no first-hop) are skipped, exactly as IPv4.

type RouteSink

type RouteSink interface {
	InsertForward(fam family.Family, prefix netip.Prefix, p locrib.Path)
	Remove(fam family.Family, prefix netip.Prefix, source redistevents.ProtocolID, instance uint32)
	// Flush sends buffered ops to the engine; the installer calls it once per
	// Apply/RemoveAll so a whole delta travels in one RPC batch (R-1).
	Flush()
}

RouteSink receives Loc-RIB install/remove operations when the installer has no local Loc-RIB (a forked subprocess, where locrib.Default() returns nil). The forked wiring installs one via SetRemoteSink; it ships each op to the engine over RPC (internal/core/rib/routeinstall). In-process (loc != nil) the local RIB is always preferred and the sink is unused.

type RouteSnapshotEntry

type RouteSnapshotEntry struct {
	Prefix   string             `json:"prefix"`
	Metric   uint64             `json:"metric"`
	Level    string             `json:"level"`
	UpDown   bool               `json:"up-down,omitempty"`
	NextHops []RouteSnapshotHop `json:"next-hops"`
}

RouteSnapshotEntry is one row of the `show isis route` view: the prefix, metric, level, up/down flag, and the resolved next-hops (address + interface). Flat value with no pointers so it crosses the CLI/RPC boundary cleanly.

func Snapshot

func Snapshot(routes []RouteEntry) []RouteSnapshotEntry

Snapshot renders a route set as the `show isis route` view, sorted by prefix. It is a pure projection of the installed RouteEntry set (the engine holds the set and calls this for the CLI).

type RouteSnapshotHop

type RouteSnapshotHop struct {
	NextHop   string `json:"next-hop"`
	Interface string `json:"interface,omitempty"`
}

RouteSnapshotHop is one next-hop in a snapshot row.

type SPFLogEntry

type SPFLogEntry struct {
	// Time is the run start time as a Unix timestamp (seconds). The renderer
	// formats it; the engine keeps it numeric so the value is locale-free.
	TimeUnix int64 `json:"time-unix"`
	// Level is the routing level computed ("l1" | "l2").
	Level string `json:"level"`
	// Trigger names why SPF ran (e.g. "lsdb-change", "manual"). The debounced
	// engine path records "lsdb-change"; a direct Run records "manual".
	Trigger string `json:"trigger"`
	// DurationSeconds is how long the Dijkstra pass took for this level.
	DurationSeconds float64 `json:"duration-seconds"`
	// Nodes is the number of nodes in the level graph this run built.
	Nodes int `json:"nodes"`
}

SPFLogEntry is one recorded SPF run at one level (the `show isis spf-log` row). It is a flat value with no pointers so it crosses the CLI/RPC boundary cleanly. DurationSeconds is the wall-clock cost of the Dijkstra pass; Nodes is the node count of the graph it ran over; Trigger names why the run happened.

type Source

type Source interface {
	// Records returns every LSP held at the level. A purged (zero-lifetime) LSP
	// is omitted by the implementation; a malformed LSP excludes that one node,
	// not the whole run (security review: error isolation).
	Records(level Level) []LSPRecord
}

Source yields the LSPs of one level for graph construction. The engine's LSDB implements it; tests supply a stub. It is read ONCE per SPF run (spec A-9): a burst of LSP arrivals is coalesced by the debounce before BuildGraph runs.