OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Services

ovn-bgp-agent — Drivers and exposure

Drivers, exposure methods and configuration — ovn-bgp-agent — branch stable/2026.2

Branch stable/2026.2 @ f770768b41dc (7.0.0-2-gf770768) — repository https://opendev.org/openstack/ovn-bgp-agent — analysed on 2026-10-28

ovn-bgp-agent exposes no REST API. Its "interface" is: the configuration file, the choice of a driver (which OVN database it watches and how it reacts), the choice of an exposing method (how a route reaches BGP and how traffic is steered into OVN), and the set of OVN objects it makes reachable from the BGP fabric. This file replaces the endpoint catalogue of a service with these three things; the triggers are detailed in 05-async-processing.

1. What the agent is for

In an OVN-based OpenStack where the provider networks are not stretched at L2 across racks, the leaf switches must learn where each public IP lives. The agent runs on every node that hosts VMs or router gateways (compute and network nodes). For every IP that is reachable through this node — a VM on a provider network bound here, a floating IP whose VM or router gateway is here, a router gateway hosted here and, optionally, the tenant networks behind it — it:

  1. wires the kernel/OVS so that traffic for that IP arriving from the fabric is handed to OVN (ingress), and traffic leaving OVN is routed by the kernel (egress);
  2. announces the IP through the local FRR, which already peers with the fabric (the agent never configures BGP neighbours). When the IP moves away or disappears, it does the reverse, announcement first.

2. Drivers

[DEFAULT] driver, loaded through stevedore namespace ovn_bgp_agent.drivers (ovn_bgp_agent/drivers/driver_api.py:25-32, setup.cfg).

DriverOVN DB watchedSupported exposing methods (code)Declared status (doc/source/bgp_supportability_matrix.rst)Class
ovn_bgp_driver (default)Southboundunderlay only in practice (wiring goes through wire_utils, which only has underlay/vrf/ovn branches; the SB driver builds no EVPN devices) [inferred]Underlay: supportedovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:46
nb_ovn_bgp_driverNorthbound (+ node-local NB for ovn)underlay (broken on this branch by defect D16: provider IPs are not wired, sync fails, see 02 § 4), vrf (EVPN type-5), ovn; l2vni and dynamic raise UnsupportedWiringConfig at every sync (ovn_bgp_agent/drivers/openstack/utils/wire.py:34-44)Underlay, VRF, OVN supported; L2VNI, Dynamic not supportedovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:115
ovn_evpn_driverSouthboundits own EVPN plumbing per router (VNI from Neutron annotations)VRF: not supportedovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:46
ovn_stretched_l2_bgp_driverSouthboundsubnet routes via gateway in the BGP VRF tableUnderlay: supportedovn_bgp_agent/drivers/openstack/ovn_stretched_l2_bgp_driver.py:49

All drivers implement the abstract AgentDriverBase (expose_ip, withdraw_ip, expose_remote_ip, withdraw_remote_ip, expose_subnet, withdraw_subnet, ovn_bgp_agent/drivers/driver_api.py:19-56) plus start, sync and frr_sync, which BGPAgent calls (ovn_bgp_agent/agent.py:38-64). The signatures differ between drivers (for example expose_ip(ips, row, associated_port) in SB, expose_ip(ips, ips_info) in NB, expose_ip(row, cr_lrp) in EVPN): the base class is a naming convention, not a common contract.

3. Exposing methods ([DEFAULT] exposing_method)

MethodHow an IP is announcedIngress (fabric → OVN)Egress (OVN → fabric)Code
underlay (default)/32 or /128 added to dummy bgp_nic in VRF bgp_vrf; FRR redistributes connected in that VRF and leaks it into the default BGP instanceip rule to <ip> lookup <bridge table> + route <ip> dev <bridge>[.vlan] in the per-bridge table; proxy ARP/NDP on the bridge; static neighbour for router gatewaysOVS flow cookie 999 rewriting the destination MAC to the bridge MAC → kernel routing via the per-bridge default routeovn_bgp_agent/drivers/openstack/utils/bgp.py:29-67, ovn_bgp_agent/drivers/openstack/utils/wire.py:670-707
vrf (NB only)route in the EVPN VRF vrf-<vni> (table = VNI) out of a veth into the provider bridge; FRR advertises type-5 routesVXLAN vxlan-<vni> in bridge br-<vni> in the VRF; veth to OVSMAC-tweak flow towards the veth's anycast MACovn_bgp_agent/drivers/openstack/utils/wire.py:87-183, ovn_bgp_agent/drivers/openstack/utils/evpn.py:39
ovn (NB only)as underlay (bgp_nic)node-local OVN router bgp-router with ARP proxy for the provider prefixes and ingress flows from the physical NICnode-local OVN policy rerouting to peer_ips (ECMP + BFD)ovn_bgp_agent/drivers/openstack/utils/wire.py:186-278
l2vni, dynamic———accepted by ovn_bgp_agent/config.py:130-145, rejected at sync by ovn_bgp_agent/drivers/openstack/utils/wire.py:44

4. What is exposed (functional catalogue)

Each row is the "operation" a reader would look for in a service's API catalogue. "Exposed by" means: which node announces it.

Object exposedConditionExposed bySB driverNB driverEVPN driverStretched driver
VM IP on a provider networkport bound and up on the nodenode hosting the VMyesyesnono
Virtual-port IP (VRRP/Octavia amphora VIP) on a provider networkvirtual port bound here; NB: neutron:host_id = this hostnode hosting the portyes (+ IPv6 NDP proxy for its CIDR)yesnono
Floating IP of a VMSB: patch port nat_addresses + cr-lrp here, or VM here with FIP; NB distributed: VM here with neutron:port_fip; NB centralized: NAT gateway_port hosted hereSB/NB-centralized: node hosting the router gateway; NB-distributed: node hosting the VMyes (IPv4 only)yesnono
Router gateway IP (cr-lrp)gateway port hosted on the nodegateway nodeyesyesyes (into the router's VRF)routes on every node
Tenant subnet (CIDR)expose_tenant_networks (or GUA mode for IPv6 GUA subnets), router gateway here, address scope allowedgateway nodeyes (rule + route)yes (subnet method: prefix announced; host method: wiring only)yes (VRF route + flow)yes (route via gateway IP on every node)
Tenant VM IPsame + VM bound anywheregateway nodeyesyes with advertisement_method_tenant_networks = hostyes (IP on lo-<vni>)no
OVN LB VIP on a provider networkLB members behind a router whose gateway is heregateway nodeyesyesnono
OVN LB VIP on a tenant networktenant exposure ongateway nodeyesyesnono
OVN LB VIP FIP (neutron:vip_fip)router gateway heregateway nodenoyesnono
Port-forwarding FIP (pf-floatingip-* LB)router gateway heregateway nodenoyesnono

4.1 Provider VM IP (reference flow, NB driver)

  • Purpose: make a VM plugged directly on a provider network reachable without stretching the VLAN across racks.
  • Actor: Neutron/Nova (port creation and binding), ovn-controller (sets up).
  • Preconditions: the switch has a localnet port whose network_name is in this node's ovn-bridge-mappings (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:471-493); the port has an IP in addresses.
  • Business rules: the host is requested-chassis (or neutron:host_id for virtual ports), comparing with the OVS hostname; during live migration both hosts are listed and both may expose (ovn_bgp_agent/drivers/openstack/utils/driver_utils.py:43-68); IPs already exposed are skipped (ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:84).
  • Flow:
    1. (event) LSP update with up = true on this host → LogicalSwitchPortProviderCreateEvent.
    2. Wire: ip rule + route in the bridge table (VLAN device if the localnet has a tag), MAC-tweak flow for the localnet patch port if not yet present (ovn_bgp_agent/drivers/openstack/utils/wire.py:660-707).
    3. Announce: address on bgp_nic → FRR advertises <ip>/32.
    4. Record _exposed_ips[<switch>][<ip>].
  • Outcome: the fabric routes <ip>/32 to this node; the kernel hands it to br-ex[.vlan]; OVN delivers it.
  • Withdraw: port deleted, down without additional binding, or bound elsewhere → address removed first, then rule/route; the IPv6 NDP proxy for the CIDR only when the switch has no virtual port and no active local port remains (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:599-609).
  • Limitations: one localnet per switch assumed (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:670); a switch first seen as tenant is cached as tenant until the next sync (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:482-484).

4.2 Router gateway and tenant networks (NB driver)

  • Purpose: let the fabric reach tenant subnets (no SNAT) or at least the gateway IP, through the node that hosts the router's gateway.
  • Preconditions: OVN ≥ 23.09 (Logical_Router_Port.status with hosting-chassis; without it, a WARNING and no gateway is exposed, ovn_bgp_agent/drivers/openstack/utils/ovn.py:369-374); expose_tenant_networks (or expose_ipv6_gua_tenant_networks for IPv6 GUA only); with require_snat_disabled_for_tenant_networks, subnets that are SNATed by the router are skipped (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:873-895); with address_scopes, the subnet's scope must be listed.
  • Flow: gateway moves here (ChassisRedirectCreateEvent) → gateway IPs wired with a permanent neighbour (router MAC) and announced → each router-interface LSP of that router: rule + route <cidr> via <gateway ip> in the bridge table → subnet method: route <cidr> via <gateway ip> dev bgp-nic in table bgp_vrf_table_id (FRR must redistribute kernel, which ensure_base_bgp_configuration turns on, ovn_bgp_agent/drivers/openstack/utils/bgp.py:80-81); host method: each up VM IP of the subnet is added to bgp_nic → LBs of the router.
  • Outcome: <cidr> (or each VM /32) announced by the gateway node only.

4.3 Floating IPs: distributed vs centralized (NB driver)

NB_Global external_ids:neutron:fip-distributed decides who announces a FIP: the node hosting the VM (distributed, the agent's default when the key is absent) or the node hosting the router gateway (centralized, Neutron's default). The two event sets are swapped at run time when the flag changes (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:127-187). A WARNING at start says that the agent's default differs from Neutron's (ovn_bgp_agent/drivers/openstack/utils/ovn.py:446-454). With distributed FIPs, the FIP is wired with the NAT external_mac; centralized, with the gateway port's MAC (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:89-105).

4.4 EVPN per router (SB ovn_evpn_driver)

  • Purpose: put each router's networks in an L3 EVPN (VNI) instead of the underlay.
  • Preconditions: the router's patch port carries neutron_bgpvpn:vni and neutron_bgpvpn:as; the router has at least one FIP (nat_addresses), because the provider datapath is found through it (ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:199-202) — a router without FIPs is never exposed [inferred from the code path].
  • Flow and outcome: see 05 § 4.

4.5 Stretched L2 (ovn_stretched_l2_bgp_driver)

Every node installs, in table bgp_vrf_table_id, a route to the gateway's network and one route per attached subnet via the gateway IP, for every router of the SB; FRR redistributes kernel only. Tenant VM IPs and FIPs are not handled (ovn_bgp_agent/drivers/openstack/ovn_stretched_l2_bgp_driver.py:534-539).

5. Configuration reference

Registered by ovn_bgp_agent/config.py:236-240; sample generator etc/oslo-config-generator/bgp-agent.conf (namespaces oslo.concurrency, oslo.log, oslo.privsep, ovnbgpagent). The command line is parsed with project='bgp-agent' (ovn_bgp_agent/config.py:243-244), so default config files are searched as bgp-agent.conf.

[DEFAULT]

OptionTypeDefaultEffectDefined at
reconcile_intervalint300period of the full syncovn_bgp_agent/config.py:26
frr_reconcile_intervalint15period of frr_syncovn_bgp_agent/config.py:29
expose_tenant_networksboolfalseexpose tenant subnets/VM IPs through the gateway node; takes precedence over GUA modeovn_bgp_agent/config.py:33
advertisement_method_tenant_networkshost|subnethostNB: announce tenant VMs as /32 or the subnet prefixovn_bgp_agent/config.py:39
require_snat_disabled_for_tenant_networksboolfalseNB: do not expose subnets SNATed by the routerovn_bgp_agent/config.py:48
expose_ipv6_gua_tenant_networksboolfalseexpose only global-unicast IPv6 tenant addressesovn_bgp_agent/config.py:57
driverchoiceovn_bgp_driversee § 2ovn_bgp_agent/config.py:64
ovsdb_connectionstr (tcp:/ssl:/unix:)unix:/usr/local/var/run/openvswitch/db.socklocal OVS DBovn_bgp_agent/config.py:69
ovsdb_connection_timeoutint180OVN IDL transaction timeout (not used for the OVS IDL)ovn_bgp_agent/config.py:75
bgp_ASstr64999AS of router bgp used in FRR fragmentsovn_bgp_agent/config.py:78
bgp_router_idstrnonerouter-id of the VRF instance; read from FRR if emptyovn_bgp_agent/config.py:82
evpn_local_ipIPnoneVXLAN source; mandatory for NB vrfovn_bgp_agent/config.py:86
evpn_nicstrnoneSB EVPN: take the VXLAN source from this NICovn_bgp_agent/config.py:92
evpn_udp_dstportport4789VXLAN UDP portovn_bgp_agent/config.py:98
clear_vrf_routes_on_startupboolfalseflush table bgp_vrf_table_id at startovn_bgp_agent/config.py:102
delete_vrf_on_disconnectbooltrueNB vrf: delete the VRF and its FRR config when unusedovn_bgp_agent/config.py:106
bgp_nicstrbgp-nicdummy device holding announced IPsovn_bgp_agent/config.py:112
bgp_vrfstrbgp-vrfVRF of bgp_nicovn_bgp_agent/config.py:116
bgp_vrf_table_idint10table of bgp_vrfovn_bgp_agent/config.py:120
address_scopeslistnoneallowed Neutron address scopesovn_bgp_agent/config.py:125
exposing_methodunderlay|l2vni|vrf|dynamic|ovnunderlaysee § 3ovn_bgp_agent/config.py:130

[agent]

OptionTypeDefaultEffectDefined at
root_helperstrsudoprefix of privsep-helperovn_bgp_agent/config.py:149

[ovn] (all also accepted in [DEFAULT], deprecated)

OptionDefaultDefined at
ovn_sb_private_key, ovn_nb_private_key/etc/pki/tls/private/ovn_bgp_agent.keyovn_bgp_agent/config.py:157, ovn_bgp_agent/config.py:178
ovn_sb_certificate, ovn_nb_certificate/etc/pki/tls/certs/ovn_bgp_agent.crtovn_bgp_agent/config.py:162, ovn_bgp_agent/config.py:183
ovn_sb_ca_cert, ovn_nb_ca_cert/etc/ipa/ca.crtovn_bgp_agent/config.py:167, ovn_bgp_agent/config.py:188
ovn_sb_connection, ovn_nb_connectionnone (taken from OVS external_ids)ovn_bgp_agent/config.py:172, ovn_bgp_agent/config.py:193

[local_ovn_cluster] (exposing_method = ovn)

OptionDefaultEffectDefined at
ovn_nb_connectionunix:/var/run/ovn/ovnnb_db.socknode-local NBovn_bgp_agent/config.py:202
external_nics[]NICs used for ingress flowsovn_bgp_agent/config.py:208
peer_ips[]next hops (ECMP)ovn_bgp_agent/config.py:212
provider_networks_pool_prefixes['192.168.0.0/16']prefixes routed into OpenStackovn_bgp_agent/config.py:216
bgp_chassis_idbgpchassis of the local ovn-controller; suffix of ovn-bridge-mappings-<id>ovn_bgp_agent/config.py:219

Also: oslo.log options, [privsep], [privsep_ovs_vsctl], [privsep_vtysh] (oslo.privsep), [oslo_concurrency]. The full sample is _rewrite/config_stable-2026.2.conf (07).

6. Lifecycle of an exposed IP

stateDiagram-v2
  [*] --> Unknown
  Unknown --> Wired: event match (bound here + up) or sync
  Wired --> Announced: address added to bgp_nic (FRR advertises)
  Wired --> Unknown: wiring failed (exception logged), retried by next sync
  Announced --> Withdrawn: event (deleted / down / moved) — address removed first
  Withdrawn --> Unknown: rule/route/neighbour removed
  Announced --> Announced: sync (idempotent re-apply)
  Announced --> Unknown: sync finds it no longer expected (cleanup of bgp_nic, rules, routes)

Transitions triggered by events are described per object in 05; the sync transitions in § 2.2 and § 3.2 there.

Source: reverse-spec-ovn-bgp-agent/01-drivers-and-exposure_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.