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

Reverse-spec · Services

ovn-bgp-agent — Interactions

Interactions — 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 External references: ovsdbapp: = ovsdbapp stable/2026.2 @ 0fb5a1b9a675; neutron-lib: = neutron-lib stable/2026.2 @ 1ee72052251a.

The agent uses no database, no message bus, no cache and no REST API. It talks to six local or remote systems:

SystemProtocolLibraryDirectionSync/asyncConfiguration
Local Open vSwitch databaseOVSDB JSON-RPC (RFC 7047) over unix/tcp/sslovs.db.idl + ovsdbapp OvsdbIdlread (+ add-br in EVPN NB mode)sync[DEFAULT] ovsdb_connection (default unix:/usr/local/var/run/openvswitch/db.sock)
OVN Southbound database (SB drivers)OVSDB JSON-RPC, monitor_condovsdbapp OvnSbApiIdlImplread only, notificationsasync (events) + sync reads from the IDL cache[ovn] ovn_sb_connection or Open_vSwitch external_ids:ovn-remote; SSL files [ovn] ovn_sb_*
OVN Northbound database (NB driver)OVSDB JSON-RPCovsdbapp OvnNbApiIdlImplread only, notificationsasync + sync[ovn] ovn_nb_connection or external_ids:ovn-nb-remote; SSL files [ovn] ovn_nb_*
Node-local OVN NB (exposing_method = ovn)OVSDB JSON-RPC, leader onlyovsdbappread/write (transactions)sync[local_ovn_cluster] ovn_nb_connection (default unix:/var/run/ovn/ovnnb_db.sock)
FRRvtysh CLI over the vty socket directory /run/frr/processutils.execute in privsepwrite (config), read (show … json)syncbgp_AS, bgp_router_id, bgp_vrf, EVPN annotations
Linux kernelrtnetlink (links, addresses, routes, rules, neighbours, bridge ports), sysctl, ip -6 nei … proxypyroute2 in privsepread/writesyncbgp_nic, bgp_vrf, bgp_vrf_table_id, bridge mappings
Open vSwitch switchovs-vsctl, ovs-ofctl (OpenFlow 1.0, fallback -O OpenFlow13), ovs-appctlprocessutils.execute in privsepread/writesyncbridge mappings

1. Privilege separation

All writes to the kernel, OVS and FRR go through oslo.privsep daemons started with [agent] root_helper (default sudo, ovn_bgp_agent/config.py:148-154, ovn_bgp_agent/config.py:258-259):

ContextConfig sectionCapabilitiesUsed forDefined at
default[privsep]CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_NET_ADMIN, CAP_SYS_ADMINnetlink writes, sysctl, ip -6 nei proxy, /etc/iproute2/rt_tablesovn_bgp_agent/privileged/__init__.py:18
ovs_vsctl_cmd[privsep_ovs_vsctl]CAP_SYS_ADMIN, CAP_NET_ADMINovs-vsctl, ovs-ofctl, ovs-appctlovn_bgp_agent/privileged/__init__.py:28
vtysh_cmd[privsep_vtysh]CAP_SYS_ADMIN, CAP_NET_ADMIN/usr/bin/vtyshovn_bgp_agent/privileged/__init__.py:36

The privsep daemons receive whole requests (a route dict, a list of IPs, a command line). Read-only netlink dumps (get_exposed_ips, get_ovn_ip_rules, _get_table_routes…) run unprivileged in the agent process (ovn_bgp_agent/utils/linux_net.py:472, ovn_bgp_agent/utils/linux_net.py:539).

2. Local Open vSwitch database

  • Connection: idl.Idl on Open_vSwitch, Bridge, Port, Interface, probe interval 60 s, ovsdbapp connection timeout 180 s (hard-coded, not ovsdb_connection_timeout) (ovn_bgp_agent/drivers/openstack/utils/ovs.py:274-284).
  • Keys read in Open_vSwitch . external_ids (ovn_bgp_agent/drivers/openstack/utils/ovs.py:286-330):
KeyUseMissing →
system-idchassis identity (SB Chassis.name, LRP status:hosting-chassis)KeyError at start (agent cannot start)
hostnamechassis name compared with LSP requested-chassis / neutron:host_id (NB driver)socket.gethostname()
ovn-remoteSB connection string when [ovn] ovn_sb_connection is emptyKeyError at start
ovn-nb-remoteNB connection string when [ovn] ovn_nb_connection is emptyKeyError at start
ovn-bridge-mappingsphysnet:bridge,… list of provider networks wired by this node[] (nothing exposed)
ovn-bridge-mappings-<bgp_chassis_id>mappings of the node-local OVN cluster (exposing_method = ovn)[]
  • Write: add-br <bridge> through the IDL for EVPN NB wiring (ovn_bgp_agent/drivers/openstack/utils/wire.py:111).

3. OVN Southbound (SB drivers)

3.1 Connection and monitored tables

  • OvnSbIdl builds the schema helper from the server, registers only the needed tables and, when a chassis is given, sets a monitor condition name == <system-id> on Chassis_Private (or Chassis), so only the own chassis row is received (ovn_bgp_agent/drivers/openstack/utils/ovn.py:95-114). SSL key/cert/CA are set process-wide with Stream.ssl_set_* when the remote starts with ssl (ovn_bgp_agent/drivers/openstack/utils/ovn.py:119-131). Transaction timeout ovsdb_connection_timeout (180 s); probe interval 60 s (ovn_bgp_agent/drivers/openstack/utils/ovn.py:133-139, ovn_bgp_agent/drivers/openstack/utils/ovn.py:466-468).
DriverTablesDefined at
ovn_bgp_driverPort_Binding, Chassis, Datapath_Binding, Load_Balancer, Chassis_Private, Logical_DP_Groupovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:42
ovn_evpn_driverPort_Binding, Chassis, Datapath_Binding, Chassis_Privateovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:40
ovn_stretched_l2_bgp_driverPort_Binding, Chassis, Datapath_Binding, Chassis_Privateovn_bgp_agent/drivers/openstack/ovn_stretched_l2_bgp_driver.py:39

Port_Binding and Load_Balancer are monitored unconditionally: every node receives the whole table.

3.2 Columns and conventions read

Table.columnMeaning for the agentRead at
Port_Binding.logical_portport name; prefixes cr-lrp- (gateway), lrp- (router side of a subnet) carry meaningovn_bgp_agent/drivers/openstack/utils/ovn.py:559-577
Port_Binding.type"" VM, virtual, chassisredirect, patch, localnetovn_bgp_agent/constants.py:20-27
Port_Binding.mac["<mac> <ip/cidr> <ip/cidr>…"]; ["unknown"] for config-drive portsovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:275-293
Port_Binding.chassisbinding; chassis[0].name compared with system-idovn_bgp_agent/drivers/openstack/utils/ovn.py:542-544
Port_Binding.up[true] once ovn-controller set itovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:40
Port_Binding.datapathnetwork or router; a datapath with a localnet port is a provider networkovn_bgp_agent/drivers/openstack/utils/ovn.py:494-503
Port_Binding.nat_addresseson patch ports: "<mac> <ip>… is_chassis_resident(\"cr-lrp-<id>\")"ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:117-134
Port_Binding.optionspeer (other side of a patch), network_name (localnet), chassis-redirect-portovn_bgp_agent/drivers/openstack/utils/ovn.py:583-589
Port_Binding.tagVLAN of a localnet portovn_bgp_agent/drivers/openstack/utils/ovn.py:600-608
Port_Binding.external_idsNeutron keys (§ 5) and EVPN keys (neutron_bgpvpn:vni, neutron_bgpvpn:as)ovn_bgp_agent/drivers/openstack/utils/ovn.py:673-685
Load_Balancer.name, datapaths, ls_datapath_group, datapath_group, lr_datapath_groupLB members' switches and routersovn_bgp_agent/utils/helpers.py:30-47
Chassis(_Private).nameown row only (reconnection detection)ovn_bgp_agent/drivers/openstack/watchers/base_watcher.py:78-94

3.3 Read queries (client-side, on the IDL cache)

All queries are db_find_rows/db_list_rows evaluated in the agent's replica; they cost CPU, not round trips. Main ones:

FunctionQueryDefined at
get_ports_on_chassisall Port_Binding, filter chassis[0].name == <chassis>ovn_bgp_agent/drivers/openstack/utils/ovn.py:542
is_provider_networkPort_Binding where datapath = D and type = localnet; ValueError (unknown datapath) → DatapathNotFoundovn_bgp_agent/drivers/openstack/utils/ovn.py:494
get_fip_associatedall type = patch rows, first whose nat_addresses mention the port nameovn_bgp_agent/drivers/openstack/utils/ovn.py:517
get_cr_lrp_nat_addresses_infopatch port named after the cr-lrp; IPs of nat_addresses entries whose VM port is bound hereovn_bgp_agent/drivers/openstack/utils/ovn.py:559
get_provider_ovn_lbs_on_cr_lrpVIP ports: datapath = provider, type = "", chassis = [], mac = [], up = false; LB name from neutron:port_name minus ovn-lb-vip-ovn_bgp_agent/drivers/openstack/utils/ovn.py:705
get_network_name_and_taglocalnet port of D whose network_name is in the bridge mappingsovn_bgp_agent/drivers/openstack/utils/ovn.py:583

4. OVN Northbound (NB driver)

4.1 Connection and tables

  • Tables Logical_Switch_Port, NAT, Logical_Switch, Logical_Router, Logical_Router_Port, Load_Balancer, DHCP_Options, NB_Global, no monitor condition (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:44-46, ovn_bgp_agent/drivers/openstack/utils/ovn.py:53-92).
  • Node-local cluster (exposing_method = ovn): Logical_Switch, Logical_Switch_Port, Logical_Router, Logical_Router_Port, Logical_Router_Policy, Logical_Router_Static_Route, Gateway_Chassis, Static_MAC_Binding (+ BFD used by bfd_add) with leader_only=True (ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:47-51, ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:240-245). Note: BFD is not in the registered list; bfd_add on an unregistered table is expected to fail [inferred, not run].

4.2 Columns read

Table.columnMeaningRead at
Logical_Switch_Port.addresses["<mac> <ip> <ip>…"]ovn_bgp_agent/drivers/openstack/utils/port.py:35-59
Logical_Switch_Port.type"", virtual, router, localnetovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:53
Logical_Switch_Port.upport upovn_bgp_agent/drivers/openstack/utils/ovn.py:354-362
Logical_Switch_Port.options:requested-chassishostname(s), comma-separated during live migrationovn_bgp_agent/drivers/openstack/utils/driver_utils.py:43-68
Logical_Switch_Port.options:network_name, taglocalnet → bridge mapping and VLANovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:439-447
Logical_Router_Port.networks, mac, status:hosting-chassisgateway IPs, MAC and hosting chassis (OVN ≥ 23.09)ovn_bgp_agent/drivers/openstack/utils/ovn.py:364-377
NAT.type, external_ip, external_mac, logical_ip, logical_port, gateway_port, external_ids:neutron:fip_network_idFIPs; SNAT checkovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:673-694
Load_Balancer.name, vips, external_ids (lr_ref, neutron:router_name, neutron:vip, neutron:vip_fip, neutron:vip_port_id)VIPs and FIPs of LBsovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:1138-1266
Logical_Switch.external_ids:neutron_bgpvpn:type/vni/rt/rd/it/et, portsEVPN settings per provider networkovn_bgp_agent/drivers/openstack/utils/ovn.py:326-340
DHCP_Options.cidr, options:router, dns_server, dhcpv6_statelessgateway IPs and IPv6 RA for EVPNovn_bgp_agent/drivers/openstack/utils/evpn.py:325-355
NB_Global.external_ids:neutron:fip-distributed"True"/"False"ovn_bgp_agent/drivers/openstack/utils/ovn.py:436-455

4.3 Writes to the node-local NB (exposing_method = ovn)

OperationWhenDefined at
lr_add bgp-router (may_exist)every syncovn_bgp_agent/drivers/openstack/utils/wire.py:296
lr_policy_add bgp-router 10 'inport=="bgp-router-openstack"' reroute nexthop(s)=peer_ipsevery syncovn_bgp_agent/drivers/openstack/utils/wire.py:428
bfd_add logical_port=bgp-router-<network> dst_ip=<peer> + lr_route_add bgp-router 0.0.0.0/0 <peer> ecmp bfdevery syncovn_bgp_agent/drivers/openstack/utils/wire.py:281, ovn_bgp_agent/drivers/openstack/utils/wire.py:442
ls_add <network> + localnet LSP <network>-localnet (addresses=unknown, network_name)every syncovn_bgp_agent/drivers/openstack/utils/wire.py:300
lrp_add bgp-router-openstack 40:44:00:00:00:06 <provider prefixes> + LSP openstack-bgp-router (router-port, arp_proxy) + gateway chassisevery syncovn_bgp_agent/drivers/openstack/utils/wire.py:317
lrp_add bgp-router-<network> <nic mac> <nic ip> + LSP <network>-bgp-router + gateway chassisevery syncovn_bgp_agent/drivers/openstack/utils/wire.py:366
Static_MAC_Binding add (override_dynamic_mac, may_exist) / delete on bgp-router-openstack for each exposed IPexpose / withdrawovn_bgp_agent/drivers/openstack/utils/ovn.py:173-253, ovn_bgp_agent/drivers/openstack/utils/wire.py:731-807

4.4 Sample OVSDB frames

Monitor request sent by the SB IDL (abridged, one table shown) [inferred from ovs.db.idl behaviour; not captured]:

{"id": 3, "method": "monitor_cond_since",
 "params": ["OVN_Southbound", ["monid", "OVN_Southbound"],
  {"Chassis_Private": [{"columns": ["name", "chassis", "external_ids", "nb_cfg"],
                        "where": [["name", "==", "8f6c2c3e-…-system-id"]]}],
   "Port_Binding": [{"columns": ["logical_port", "type", "mac", "chassis", "up",
                                  "datapath", "nat_addresses", "options", "tag", "external_ids", "…"]}]},
  "00000000-0000-0000-0000-000000000000"]}

Update that fires PortBindingChassisCreatedEvent on chassis 8f6c… (VM bound and up):

{"method": "update3", "params": [["monid", "OVN_Southbound"], "…txn-id…",
 {"Port_Binding": {"5b1c…": {"modify": {
     "chassis": ["uuid", "a3d4…(row of Chassis 8f6c…)"],
     "up": true}}}}]}

5. Neutron conventions consumed (external_ids)

The agent relies on keys written by Neutron's OVN driver (and networking-bgpvpn). A re-implementation must read exactly these keys (ovn_bgp_agent/constants.py:33-125):

KeyWhereMeaning
neutron:cidrsLSP / Port_Binding"<ip>/<len> …" of the port; used when MAC is unknown, for LB VIP ports, for router interfaces
neutron:network_nameLSP, LRPneutron-<network uuid> of the switch
neutron:router_nameLRP, LBrouter of a gateway port or port-forwarding LB (neutron- prefix stripped)
neutron:device_id, neutron:device_ownerLSProuter id; network:router_interface identifies subnet attachments
neutron:port_namePort_Bindingovn-lb-vip-<lb> identifies an LB VIP port
neutron:port_fipLSPFIP associated with the port (NB distributed mode)
neutron:fip_network_idNATprovider network of the FIP (switch neutron-<id>)
neutron:host_idLSPhost of a virtual port
neutron:vip, neutron:vip_fip, neutron:vip_port_id, lr_refLoad_BalancerOctavia OVN provider LB
neutron:subnet_pool_addr_scope4, …6LSP / Port_Bindingaddress scopes (filter)
neutron:fip-distributedNB_GlobalFIP mode
neutron_bgpvpn:vni, :as, :type, :rt, :rd, :it, :etPort_Binding / Logical_SwitchEVPN settings; list values are Python literals ("['64999:1']") parsed with ast.literal_eval (ovn_bgp_agent/drivers/openstack/utils/wire.py:140-144)

6. FRR (vtysh)

  • Invocation: /usr/bin/vtysh --vty_socket /run/frr/ -f <tmpfile> to apply configuration; /usr/bin/vtysh --vty_socket /run/frr/ -c '<command>' to query (ovn_bgp_agent/privileged/vtysh.py:25-45, ovn_bgp_agent/constants.py:57). The temporary file is written by the unprivileged agent and read by the privileged daemon (ovn_bgp_agent/drivers/openstack/utils/frr.py:122-138).
  • Query: show ip bgp summary json → ipv4Unicast.routerId, used when bgp_router_id is not configured (ovn_bgp_agent/drivers/openstack/utils/frr.py:116-119).
  • Assumption: FRR runs with an integrated configuration and a pre-existing router bgp <bgp_AS> with its neighbours (see etc/frr/frr.conf); the agent never configures peers.

Configuration fragments applied (Jinja2, ovn_bgp_agent/drivers/openstack/utils/frr.py:31-113). Rendered LEAK_VRF_TEMPLATE with defaults (bgp_AS=64999, bgp_vrf=bgp-vrf, router-id 172.24.4.1, redistribute connected):

router bgp 64999
  address-family ipv4 unicast
    import vrf bgp-vrf
  exit-address-family

  address-family ipv6 unicast
    import vrf bgp-vrf
  exit-address-family

router bgp 64999 vrf bgp-vrf
  bgp router-id 172.24.4.1
  address-family ipv4 unicast
    redistribute connected
  exit-address-family

  address-family ipv6 unicast
    redistribute connected
  exit-address-family

ADD_VRF_TEMPLATE (EVPN, vni=1001, local_ip=10.0.0.1, no RD, RT 64999:1001):

vrf vrf-1001
  vni 1001
exit-vrf

router bgp 64999 vrf vrf-1001
  address-family ipv4 unicast
    redistribute connected
  exit-address-family
  address-family ipv6 unicast
    redistribute connected
  exit-address-family
  address-family l2vpn evpn
    advertise ipv4 unicast
    advertise ipv6 unicast
    rd 10.0.0.1:1001
    route-target import 64999:1001
    route-target export 64999:1001
  exit-address-family

The real texts also contain the blank lines left by the Jinja tags (ignored by vtysh); with advertisement_method_tenant_networks = subnet both redistribute connected and redistribute kernel appear. Without evpn_local_ip, the RD line is rendered as rd None:<vni> (defect D15, 02 § 4). DEL_VRF_TEMPLATE: no vrf vrf-<vni> / no interface veth-vrf-<vni> / no router bgp <as> vrf vrf-<vni>. CONFIGURE_ND_TEMPLATE: interface <veth> / ipv6 nd managed-config-flag / ipv6 nd rdnss <dns> / ipv6 nd prefix <prefix> [no-autoconfig] / no ipv6 nd suppress-ra. Exact renderings are recorded in _rewrite/side-effects_stable-2026.2.json (see 07).

7. Linux kernel

7.1 Devices

DeviceKindCreated whenDefined at
bgp-vrf (bgp_vrf)vrf, table bgp_vrf_table_idstart, every frr_syncovn_bgp_agent/privileged/linux_net.py:59
bgp-nic (bgp_nic)dummy, master bgp-vrfstart, every frr_syncovn_bgp_agent/utils/linux_net.py:130
<bridge>bridge (only if absent; normally the OVS internal port already exists)syncovn_bgp_agent/privileged/linux_net.py:68
<bridge[:10]>.<vlan>vlan on the bridgesync, first route via a VLANovn_bgp_agent/privileged/linux_net.py:109
vrf-<vni>, br-<vni>, vxlan-<vni>, lo-<vni>, veth-vrf-<vni>/veth-ovs-<vni>, vlan-<vni>EVPN SB drivergateway exposureovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:562
vrf-<vni>, br-<vni>, vxlan-<vni>, vrf<uuid11>/ovs<uuid11>EVPN NB (vrf)syncovn_bgp_agent/drivers/openstack/utils/evpn.py:68, ovn_bgp_agent/drivers/openstack/utils/evpn.py:198

Interface names are truncated to 15 characters (neutron-lib:neutron_lib/constants.py:324); VLAN device names keep 10 characters of the bridge name so that .<4094> fits (ovn_bgp_agent/constants.py:97-98).

7.2 Addresses, routes, rules, neighbours (underlay)

For a provider IP 172.24.100.10 on network public → br-ex, table 200 (ip equivalents):

ip rule add to 172.24.100.10/32 table 200                       # rule_create   ovn_bgp_agent/utils/linux_net.py:703
ip route replace 172.24.100.10/32 dev br-ex table 200 scope link proto boot   # route_create  ovn_bgp_agent/utils/linux_net.py:753
ip addr add 172.24.100.10/32 dev bgp-nic                          # BGP announcement  ovn_bgp_agent/privileged/linux_net.py:586

For a router gateway (cr-lrp) 172.24.100.5, MAC fa:16:3e:aa:bb:cc, VLAN 100: same rule/route on br-ex.100, plus ip neigh replace 172.24.100.5 lladdr fa:16:3e:aa:bb:cc dev br-ex.100 nud permanent (ovn_bgp_agent/utils/linux_net.py:703-719) and, for IPv6, ip -6 nei add proxy <prefix> dev br-ex.100 (ovn_bgp_agent/privileged/linux_net.py:204-216). For a tenant subnet 10.0.0.0/24 behind that gateway: ip rule add to 10.0.0.0/24 table 200 and ip route replace 10.0.0.0/24 via 172.24.100.5 dev br-ex.100 table 200 proto boot; with advertisement_method_tenant_networks = subnet, also ip route replace 10.0.0.0/24 via 172.24.100.5 dev bgp-nic table 10 (ovn_bgp_agent/drivers/openstack/utils/wire.py:831-872).

  • Routes are created with proto 3 (boot) and replace (ovn_bgp_agent/utils/linux_net.py:785-799, ovn_bgp_agent/privileged/linux_net.py:153-158); routes of proto 186 (FRR's BGP routes) are never considered leftovers (ovn_bgp_agent/utils/linux_net.py:618-623).
  • Per-bridge routing tables are **declared in /etc/iproute2/rt_tables** (<number> <bridge>), created from /usr/share/iproute2/rt_tables if missing (ovn_bgp_agent/privileged/linux_net.py:328-348). Numbers are chosen at random in 1–252 among free ones; exhaustion calls sys.exit(1) (ovn_bgp_agent/utils/linux_net.py:256-265).

7.3 sysctl

net.ipv4.conf.<dev>.proxy_arp=1, net.ipv6.conf.<dev>.proxy_ndp=1 on bridges and VLAN devices (ovn_bgp_agent/utils/linux_net.py:439-446); EVPN NB also sets net.ipv4.ip_forward, net.ipv4.conf.all.forwarding, net.ipv6.conf.all.forwarding and per-interface forwarding (ovn_bgp_agent/utils/linux_net.py:449-463). Executed as sysctl -w <flag>=<value> (ovn_bgp_agent/privileged/linux_net.py:171-177).

8. Open vSwitch switch

CommandWhenDefined at
ovs-vsctl list-ports <br>; ovs-vsctl get Interface <port> ofport (retried 1 s for 5 s while [])sync, wiringovn_bgp_agent/drivers/openstack/utils/ovs.py:53-91
ovs-ofctl dump-flows <br> cookie=999/-1syncovn_bgp_agent/drivers/openstack/utils/ovs.py:45-50
ovs-ofctl add-flow <br> cookie=999,priority=900,ip,in_port=<patch>,actions=mod_dl_dst:<br mac>,NORMAL (+ ipv6)sync, first VM on a provider networkovn_bgp_agent/drivers/openstack/utils/ovs.py:94-120
ovs-ofctl del-flows <br> cookie=999/-1,…sync (extra flows)ovn_bgp_agent/drivers/openstack/utils/ovs.py:123-147
ovs-ofctl add-flow <br> cookie=998,priority=1000,ip,in_port=<patch>,dl_src:<router mac>,nw_src=<cidr>actions=mod_dl_dst:<vrf dev mac>,[strip_vlan,]output=<vrf port>EVPN subnetovn_bgp_agent/drivers/openstack/utils/ovs.py:154-177
ovs-vsctl --may-exist add-port <br> <dev> [tag=<vlan>], … -- set interface <dev> type=internal, --if-exists del-portEVPNovn_bgp_agent/drivers/openstack/utils/ovs.py:212-233
ovs-ofctl add-flow <br> cookie=999,priority=1000,ip,nw_dst=<prefix>,in_port=<nic>, actions=mod_dl_dst:<mac>,output=<patch>exposing_method = ovnovn_bgp_agent/drivers/openstack/utils/wire.py:452-490
ovs-appctl fdb/add <br> <br> 0 <mac>exposing_method = ovnovn_bgp_agent/drivers/openstack/utils/ovs.py:244-247

The MAC-tweak flow is what makes egress work in underlay mode: packets leaving OVN towards the provider bridge get the bridge's own MAC as destination, so the kernel routes them (through the per-bridge table and its default route) instead of OVS switching them. ovs-ofctl is retried once with -O OpenFlow13 on failure (ovn_bgp_agent/privileged/ovs_vsctl.py:40-44).

9. Files

FileAccessDefined at
/etc/iproute2/rt_tablesread (unprivileged), append (privileged), created from /usr/share/iproute2/rt_tablesovn_bgp_agent/utils/linux_net.py:240, ovn_bgp_agent/privileged/linux_net.py:346
temporary file (Python tempfile, default /tmp)FRR configuration fragment, read by the privileged vtyshovn_bgp_agent/drivers/openstack/utils/frr.py:122-138
/etc/pki/tls/private/ovn_bgp_agent.key, /etc/pki/tls/certs/ovn_bgp_agent.crt, /etc/ipa/ca.crtOVN SSL defaultsovn_bgp_agent/config.py:157-198

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