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:= ovsdbappstable/2026.2@0fb5a1b9a675;neutron-lib:= neutron-libstable/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:
| System | Protocol | Library | Direction | Sync/async | Configuration |
|---|---|---|---|---|---|
| Local Open vSwitch database | OVSDB JSON-RPC (RFC 7047) over unix/tcp/ssl | ovs.db.idl + ovsdbapp OvsdbIdl | read (+ 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_cond | ovsdbapp OvnSbApiIdlImpl | read only, notifications | async (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-RPC | ovsdbapp OvnNbApiIdlImpl | read only, notifications | async + 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 only | ovsdbapp | read/write (transactions) | sync | [local_ovn_cluster] ovn_nb_connection (default unix:/var/run/ovn/ovnnb_db.sock) |
| FRR | vtysh CLI over the vty socket directory /run/frr/ | processutils.execute in privsep | write (config), read (show … json) | sync | bgp_AS, bgp_router_id, bgp_vrf, EVPN annotations |
| Linux kernel | rtnetlink (links, addresses, routes, rules, neighbours, bridge ports), sysctl, ip -6 nei … proxy | pyroute2 in privsep | read/write | sync | bgp_nic, bgp_vrf, bgp_vrf_table_id, bridge mappings |
| Open vSwitch switch | ovs-vsctl, ovs-ofctl (OpenFlow 1.0, fallback -O OpenFlow13), ovs-appctl | processutils.execute in privsep | read/write | sync | bridge 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):
| Context | Config section | Capabilities | Used for | Defined at |
|---|---|---|---|---|
default | [privsep] | CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_NET_ADMIN, CAP_SYS_ADMIN | netlink writes, sysctl, ip -6 nei proxy, /etc/iproute2/rt_tables | ovn_bgp_agent/privileged/__init__.py:18 |
ovs_vsctl_cmd | [privsep_ovs_vsctl] | CAP_SYS_ADMIN, CAP_NET_ADMIN | ovs-vsctl, ovs-ofctl, ovs-appctl | ovn_bgp_agent/privileged/__init__.py:28 |
vtysh_cmd | [privsep_vtysh] | CAP_SYS_ADMIN, CAP_NET_ADMIN | /usr/bin/vtysh | ovn_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.IdlonOpen_vSwitch, Bridge, Port, Interface, probe interval 60 s, ovsdbapp connection timeout 180 s (hard-coded, notovsdb_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):
| Key | Use | Missing → |
|---|---|---|
system-id | chassis identity (SB Chassis.name, LRP status:hosting-chassis) | KeyError at start (agent cannot start) |
hostname | chassis name compared with LSP requested-chassis / neutron:host_id (NB driver) | socket.gethostname() |
ovn-remote | SB connection string when [ovn] ovn_sb_connection is empty | KeyError at start |
ovn-nb-remote | NB connection string when [ovn] ovn_nb_connection is empty | KeyError at start |
ovn-bridge-mappings | physnet: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
OvnSbIdlbuilds the schema helper from the server, registers only the needed tables and, when a chassis is given, sets a monitor conditionname == <system-id>onChassis_Private(orChassis), 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 withStream.ssl_set_*when the remote starts withssl(ovn_bgp_agent/drivers/openstack/utils/ovn.py:119-131). Transaction timeoutovsdb_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).
| Driver | Tables | Defined at |
|---|---|---|
ovn_bgp_driver | Port_Binding, Chassis, Datapath_Binding, Load_Balancer, Chassis_Private, Logical_DP_Group | ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:42 |
ovn_evpn_driver | Port_Binding, Chassis, Datapath_Binding, Chassis_Private | ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:40 |
ovn_stretched_l2_bgp_driver | Port_Binding, Chassis, Datapath_Binding, Chassis_Private | ovn_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.column | Meaning for the agent | Read at |
|---|---|---|
Port_Binding.logical_port | port name; prefixes cr-lrp- (gateway), lrp- (router side of a subnet) carry meaning | ovn_bgp_agent/drivers/openstack/utils/ovn.py:559-577 |
Port_Binding.type | "" VM, virtual, chassisredirect, patch, localnet | ovn_bgp_agent/constants.py:20-27 |
Port_Binding.mac | ["<mac> <ip/cidr> <ip/cidr>…"]; ["unknown"] for config-drive ports | ovn_bgp_agent/drivers/openstack/ovn_bgp_driver.py:275-293 |
Port_Binding.chassis | binding; chassis[0].name compared with system-id | ovn_bgp_agent/drivers/openstack/utils/ovn.py:542-544 |
Port_Binding.up | [true] once ovn-controller set it | ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:40 |
Port_Binding.datapath | network or router; a datapath with a localnet port is a provider network | ovn_bgp_agent/drivers/openstack/utils/ovn.py:494-503 |
Port_Binding.nat_addresses | on patch ports: "<mac> <ip>… is_chassis_resident(\"cr-lrp-<id>\")" | ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py:117-134 |
Port_Binding.options | peer (other side of a patch), network_name (localnet), chassis-redirect-port | ovn_bgp_agent/drivers/openstack/utils/ovn.py:583-589 |
Port_Binding.tag | VLAN of a localnet port | ovn_bgp_agent/drivers/openstack/utils/ovn.py:600-608 |
Port_Binding.external_ids | Neutron 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_group | LB members' switches and routers | ovn_bgp_agent/utils/helpers.py:30-47 |
Chassis(_Private).name | own 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:
| Function | Query | Defined at |
|---|---|---|
get_ports_on_chassis | all Port_Binding, filter chassis[0].name == <chassis> | ovn_bgp_agent/drivers/openstack/utils/ovn.py:542 |
is_provider_network | Port_Binding where datapath = D and type = localnet; ValueError (unknown datapath) → DatapathNotFound | ovn_bgp_agent/drivers/openstack/utils/ovn.py:494 |
get_fip_associated | all type = patch rows, first whose nat_addresses mention the port name | ovn_bgp_agent/drivers/openstack/utils/ovn.py:517 |
get_cr_lrp_nat_addresses_info | patch port named after the cr-lrp; IPs of nat_addresses entries whose VM port is bound here | ovn_bgp_agent/drivers/openstack/utils/ovn.py:559 |
get_provider_ovn_lbs_on_cr_lrp | VIP 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_tag | localnet port of D whose network_name is in the bridge mappings | ovn_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(+BFDused bybfd_add) withleader_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:BFDis not in the registered list;bfd_addon an unregistered table is expected to fail [inferred, not run].
4.2 Columns read
| Table.column | Meaning | Read at |
|---|---|---|
Logical_Switch_Port.addresses | ["<mac> <ip> <ip>…"] | ovn_bgp_agent/drivers/openstack/utils/port.py:35-59 |
Logical_Switch_Port.type | "", virtual, router, localnet | ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py:53 |
Logical_Switch_Port.up | port up | ovn_bgp_agent/drivers/openstack/utils/ovn.py:354-362 |
Logical_Switch_Port.options:requested-chassis | hostname(s), comma-separated during live migration | ovn_bgp_agent/drivers/openstack/utils/driver_utils.py:43-68 |
Logical_Switch_Port.options:network_name, tag | localnet → bridge mapping and VLAN | ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:439-447 |
Logical_Router_Port.networks, mac, status:hosting-chassis | gateway 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_id | FIPs; SNAT check | ovn_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 LBs | ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py:1138-1266 |
Logical_Switch.external_ids:neutron_bgpvpn:type/vni/rt/rd/it/et, ports | EVPN settings per provider network | ovn_bgp_agent/drivers/openstack/utils/ovn.py:326-340 |
DHCP_Options.cidr, options:router, dns_server, dhcpv6_stateless | gateway IPs and IPv6 RA for EVPN | ovn_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)
| Operation | When | Defined at |
|---|---|---|
lr_add bgp-router (may_exist) | every sync | ovn_bgp_agent/drivers/openstack/utils/wire.py:296 |
lr_policy_add bgp-router 10 'inport=="bgp-router-openstack"' reroute nexthop(s)=peer_ips | every sync | ovn_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 bfd | every sync | ovn_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 sync | ovn_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 chassis | every sync | ovn_bgp_agent/drivers/openstack/utils/wire.py:317 |
lrp_add bgp-router-<network> <nic mac> <nic ip> + LSP <network>-bgp-router + gateway chassis | every sync | ovn_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 IP | expose / withdraw | ovn_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):
| Key | Where | Meaning |
|---|---|---|
neutron:cidrs | LSP / Port_Binding | "<ip>/<len> …" of the port; used when MAC is unknown, for LB VIP ports, for router interfaces |
neutron:network_name | LSP, LRP | neutron-<network uuid> of the switch |
neutron:router_name | LRP, LB | router of a gateway port or port-forwarding LB (neutron- prefix stripped) |
neutron:device_id, neutron:device_owner | LSP | router id; network:router_interface identifies subnet attachments |
neutron:port_name | Port_Binding | ovn-lb-vip-<lb> identifies an LB VIP port |
neutron:port_fip | LSP | FIP associated with the port (NB distributed mode) |
neutron:fip_network_id | NAT | provider network of the FIP (switch neutron-<id>) |
neutron:host_id | LSP | host of a virtual port |
neutron:vip, neutron:vip_fip, neutron:vip_port_id, lr_ref | Load_Balancer | Octavia OVN provider LB |
neutron:subnet_pool_addr_scope4, …6 | LSP / Port_Binding | address scopes (filter) |
neutron:fip-distributed | NB_Global | FIP mode |
neutron_bgpvpn:vni, :as, :type, :rt, :rd, :it, :et | Port_Binding / Logical_Switch | EVPN 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 whenbgp_router_idis 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 (seeetc/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
| Device | Kind | Created when | Defined at |
|---|---|---|---|
bgp-vrf (bgp_vrf) | vrf, table bgp_vrf_table_id | start, every frr_sync | ovn_bgp_agent/privileged/linux_net.py:59 |
bgp-nic (bgp_nic) | dummy, master bgp-vrf | start, every frr_sync | ovn_bgp_agent/utils/linux_net.py:130 |
<bridge> | bridge (only if absent; normally the OVS internal port already exists) | sync | ovn_bgp_agent/privileged/linux_net.py:68 |
<bridge[:10]>.<vlan> | vlan on the bridge | sync, first route via a VLAN | ovn_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 driver | gateway exposure | ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py:562 |
vrf-<vni>, br-<vni>, vxlan-<vni>, vrf<uuid11>/ovs<uuid11> | EVPN NB (vrf) | sync | ovn_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) andreplace(ovn_bgp_agent/utils/linux_net.py:785-799,ovn_bgp_agent/privileged/linux_net.py:153-158); routes ofproto 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_tablesif missing (ovn_bgp_agent/privileged/linux_net.py:328-348). Numbers are chosen at random in 1–252 among free ones; exhaustion callssys.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
| Command | When | Defined at |
|---|---|---|
ovs-vsctl list-ports <br>; ovs-vsctl get Interface <port> ofport (retried 1 s for 5 s while []) | sync, wiring | ovn_bgp_agent/drivers/openstack/utils/ovs.py:53-91 |
ovs-ofctl dump-flows <br> cookie=999/-1 | sync | ovn_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 network | ovn_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 subnet | ovn_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-port | EVPN | ovn_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 = ovn | ovn_bgp_agent/drivers/openstack/utils/wire.py:452-490 |
ovs-appctl fdb/add <br> <br> 0 <mac> | exposing_method = ovn | ovn_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
| File | Access | Defined at |
|---|---|---|
/etc/iproute2/rt_tables | read (unprivileged), append (privileged), created from /usr/share/iproute2/rt_tables | ovn_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 vtysh | ovn_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.crt | OVN SSL defaults | ovn_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.