Skip to content

Routing

The routing app. Catalogs are tenant-wide and named; rule rows belong to their list and carry no tenant of their own (they are scoped through it, like an interface through its device); device-bound rows are site-scoped through the device. Every model has a UUID primary key; catalogs and device-bound rows carry numid, custom fields and tags.

Catalogs

Shared shape: tenant FK, name char(128), description text, unique (tenant, name).

Model Own fields Rules
PrefixList family (ipv4/ipv6) PrefixListRule: sequence, action, prefix (normalised CIDR), prefix_obj FK → Prefix (optional), ge, le, description; unique (prefix_list, sequence)
Community value (unique per tenant), kind (standard/large/extended) -
CommunityList kind (standard/expanded/large/extended) CommunityListRule: sequence, action, communities M2M, regex, description
ASPathList - ASPathListRule: sequence, action, regex, description
RoutingPolicy - RoutingPolicyRule: sequence, action, description; match: match_prefix_lists, match_community_lists, match_as_path_lists M2M, match_next_hop FK → PrefixList, match_extra JSON; set: set_local_pref, set_med, set_weight, set_origin, set_next_hop, set_as_path_prepend, set_communities M2M, set_communities_additive, set_metric_type, set_extra JSON; continue_seq
BFDProfile catalog; min_tx, min_rx (ms, ≥ 1), multiplier (≥ 1), echo (tenant, name)
RoutingKeychain algorithm (md5/sha1/sha256/hmac-sha-256); SecretBackedPSK - psk_secret_provider, psk_secret_path reference the key in the secret store -

Static routes

StaticRoute

Field Type Notes
tenant FK → Tenant
device FK → Device CASCADE
vrf FK → VRF null = global table
prefix char(64) normalised with ip_network(strict=True)
prefix_obj FK → Prefix optional, SET_NULL
kind nexthop / interface / blackhole / reject an interface route needs next_hop_interface and no address
next_hop char(64) normalised address; blank for a blackhole/reject
next_hop_interface FK → Interface must be on device
next_hop_vrf FK → VRF route leaking
distance, metric, tag int nullable
bfd bool
status FK → Status scope staticroute

Unique (device, vrf, prefix, next_hop, next_hop_interface) with nulls_distinct=False, so the same path twice is refused while the same prefix through another next hop (ECMP) is not.

BGP

Model Fields Unique
BGPInstance device, vrf (null = global), asn FK → ASN, router_id, cluster_id, graceful_restart, distance_ebgp/distance_ibgp/distance_local (all three or none), bestpath_multipath_relax, MPLS L3VPN on a per-VRF instance: vpn_export, vpn_import, vpn_label_export (auto or a number), vpn_nexthop_export; bfd, status (scope routinginstance), description, extra JSON (device, vrf), nulls not distinct
BGPAddressFamily instance, afi_safi, networks JSON [CIDR], maximum_paths, maximum_paths_ibgp, import_policy, export_policy, extra (instance, afi_safi)
Redistribution one parent (bgp_af; OSPF/IS-IS instances follow), source, policy, metric, level + family (IS-IS only; blank = the instance's level, IPv4), extra -
BGPPeerGroup catalog + the shared knobs; remote_asn int, remote_asn_mode (asn/external/internal), local_asn FK, update_source text (tenant, name)
BGPSession instance, name, peer_group, remote_asn (+ mode), local_asn, local_address FK → IPAddress, remote_address text xor interface FK, remote_address_obj (auto-linked), peer_device, peer_session one-to-one, the shared knobs (all nullable = inherit), status (scope bgpsession), description (instance, remote_address) / (instance, interface) conditional

The shared knobs (_PeerKnobs): address_families JSON list, import_policy, export_policy, bfd, ebgp_multihop, next_hop_self, route_reflector_client, send_community, keepalive, hold_time, keychain, default_originate + default_originate_policy, capability_extended_nexthop (RFC 5549), ttl_security_hops (GTSM), extra. BGPSession.effective() resolves session → group → instance (bfd), merges extra, and derives update_source from the local address's interface.

OSPF and IS-IS

Model Fields Unique
OSPFArea catalog; area_id (normalised: a number stays a number, a quad is a quad), kind (tenant, name)
OSPFInstance device, vrf, process_id text, version ⅔, router_id, reference_bandwidth, passive_by_default, default_originate, bfd, status (routinginstance), description, extra; redistribution rows (device, vrf, version, process_id)
OSPFInterface instance, interface (same device), area FK, cost, network_type, passive (null = instance default), priority, hello, dead, bfd, mtu_ignore, authentication + keychain (instance, interface)
ISISInstance device, vrf, process, net (checked), level, metric_style, bfd, authentication + keychain, lsp_gen_interval, spf_interval, lsp_mtu, spf_init_delay/spf_short_delay/spf_long_delay/spf_holddown/spf_time_to_learn (spf-delay-ietf: all five or none), log_adjacency_changes, default_originate_ipv4/_ipv6 (""/on/always), status, description, extra; redistribution rows (device, process)
ISISInterface instance, interface, families JSON (ipv4/ipv6, defaults to ipv4), level, metric, metric_l2, network_type, passive, hello_interval, hello_multiplier, bfd, authentication + keychain (instance, interface)

| EIGRPInstance | device, vrf, asn (1-65535), name (named mode), router_id, k_values (five 0-255, normalised), variance, maximum_paths, passive_by_default, stub, bfd, status, description, extra; redistribution rows | (device, vrf, asn) | | EIGRPInterface | instance, interface (same device), passive, split_horizon (null = default), hello_interval, hold_time, bandwidth_percent, summary_addresses JSON (checked networks), bfd, authentication + keychain | (instance, interface) |

Every _DeviceInstance, _IGPInterface row and _PeerKnobs carrier has bfd_profile (SET_NULL) beside bfd; BGPSession.effective() resolves it session → group → instance.

Redistribution has exactly one parent - bgp_af, ospf_instance, isis_instance or eigrp_instance (CheckConstraint); source gains eigrp.

Overlay

Model Fields Unique
api.L2VPN (extended) vrf FK, allowed on EVPN_TYPES only (clean()); vtep_memberships reverse (tenant, identifier) for type in VXLAN_TYPES (uniq_l2vpn_vxlan_vni)
VTEP device OneToOne, source_interface (same device), source_ip, anycast_ip (assigned to the device), anycast_gateway_mac (normalised lower-case), arp_suppression, status (vtep), description, extra device
VTEPMembership vtep, l2vpn (VXLAN type), vlan (at the device's site), rd, ingress_replication, mcast_group, extra (vtep, l2vpn)

| EthernetSegment | catalog; esi (type 0, ten octets) or es_id + sys_mac (type 3, normalised), df_preference, interfaces M2M → api.Interface - the LAG on each leaf that shares the segment, so the ports are on different devices by design | (tenant, name) | | api.Interface.evpn_mh_uplink | evpn mh uplink on a fabric-facing port of a multihomed leaf | - |

MPLS

Model Fields Unique
LDPInstance device (one per device; vrf must stay null - LDP labels the global table), router_id, transport_address (blank = the router id), label_allocation (host-routes/all), interfaces M2M (same device), bfd, status, description, extra device

The VPN side of an L3VPN sits on the per-VRF BGPInstance (vpn_*); the RD and route targets come from the VRF.

resolve_membership_vlan(m) is the leaf's VLAN for a VNI: own vlan, else the L2VPN's termination at the device's site, else its sole VLAN termination. FHRPGroup gains the anycast protocol for the shared SVI address. Migrations api/0165 (L2VPN.vrf, VNI uniqueness, l2vpn statuses) and routing/0006.

Rendering

routing/render.py:routing_context(device) builds the routing block the config renderer and the Ansible inventory carry - plain dicts, sorted, every row with its id, never a secret. Derived keys a template would otherwise compute for itself: level_frr on an IS-IS instance, interface row and redistribution (level-1 / level-1-2 / level-2-only), spf_delay_ietf as one object or None, default_originate as {family: mode}, a BGP instance's distance as one object or None, a session's remote_asn_effective (the number a running config shows - the local AS for internal) and route_reflector_client_from_group (the group already says it, so print it once), and used_bfd_profiles (only the profiles this device names; bfd_profiles stays the whole catalog). Multihoming and MPLS: by_interface[<port>].es (the segment this port is in, with every member on every device), by_interface[<port>].evpn_mh_uplink, ethernet_segments + es_count per device, ldp (one block or None), and a per-VRF BGP instance's vpn object or None. keychains[].placeholder is <keychain:NAME>, the contract form a push tool substitutes (routing.render.KEYCHAIN_PLACEHOLDER_RE). It is registered through api.export_templates.register_context_provider("routing", …) from the app's ready(), so api never imports routing.

Registries

auth_api/object_types.py (group Routing; routingkeychain honours reveal), audit/apps.py, api/search_index.py, api/status_registry.py (staticroute, bgpsession, routinginstance, l2vpn, vtep), auth_api/site_paths.py (device-bound rows → device__site, instance rows → instance__device__site, vtepmembership → vtep__device__site), the router in api/api_urls.py under routing/.