Processing outside the API — neutron-fwaas — branch stable/2026.2
Branch
stable/2026.2@5303a4deed1e— repository https://opendev.org/openstack/neutron-fwaas — analysed on 2026-10-28.neutron-lib:= neutron-lib @1ee72052251a;neutron:= neutron @eef1a0f7ee36.
neutron-fwaas has no periodic task, no SQL polling and no OVSDB event handler. Its non-HTTP processing falls into four families:
- the RPC server in neutron-server, which receives agent status reports (agent driver only);
- the agent extensions, which consume the server's fanout casts and the router and port events of their host agent;
- the in-process Neutron callbacks: port binding and logging;
- the OVN DB sync plugin, run by an operator command.
With the OVN driver, everything is synchronous in the API request. The server starts no RPC listener (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:73), no agent is involved, and nothing outside the API repairs the OVN Northbound DB except neutron-ovn-db-sync-util.
Summary
| Process | Trigger | Type | Purpose | Reads | Writes / emits | Defined at |
|---|---|---|---|---|---|---|
| neutron-server (agent driver) | set_firewall_group_status | RPC call received, topic q-firewall-plugin | record the status the agent computed | — | UPDATE firewall_groups_v2.status | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:51 |
| neutron-server (agent driver) | firewall_group_deleted | RPC call received | finish a deletion confirmed by the agent | firewall_groups_v2 | DELETE or status ERROR | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:71 |
| neutron-server (agent driver) | get_firewall_groups_for_project | RPC call received | give an agent every group visible in the request context, with rules | groups, policies, rules, associations | — (may create the default group) | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:92 |
| neutron-server (agent driver) | get_projects_with_firewall_groups | RPC call received | list projects that own groups (agent resync) | firewall_groups_v2 | — | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:106 |
| neutron-server (agent driver) | get_firewall_group_for_port | RPC call received | give the L2 agent the group of one port | associations, group, rules | — | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:115 |
| neutron-server | port AFTER_UPDATE | Neutron callback | attach a newly bound VM port to the project's default group | groups | update_firewall_group (DB, driver) | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:257 |
| neutron-server (agent driver + log plugin) | firewall_group AFTER_CREATE/UPDATE, port AFTER_UPDATE | Neutron callback | push logging config to agents when router ports join a group or change status | logs, ports | Neutron logapi RPC RESOURCE_UPDATE | neutron_fwaas/services/logapi/common/fwg_callback.py:27, neutron_fwaas/services/logapi/common/port_callback.py:26 |
neutron-l3-agent (fwaas_v2 extension) | create_firewall_group, update_firewall_group, delete_firewall_group | RPC fanout cast received, topic firewall_agent | program iptables on the router ports hosted here | cast payload, local router namespaces | iptables, conntrack; RPC set_firewall_group_status / firewall_group_deleted | neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:376, neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:421, neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:515 |
neutron-l3-agent (fwaas_v2) | add_router, update_router | L3 agent extension hook | re-apply the groups of a router that appears or changes on this agent | RPC get_firewall_groups_for_project | iptables; status RPC | neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:303, neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:317 |
neutron-l3-agent (fwaas_v2) | process_services_sync | resync hook, never called | full resync after RPC failures | RPC | iptables | neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:345 |
neutron-openvswitch-agent (fwaas_v2 L2 extension) | create/update/delete_firewall_group | RPC fanout cast received, topic firewall_agent | program OVS flows for the VM ports of this host | cast payload | OVS flows (tables 64–69); status RPC | neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:300, neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:334, neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:317 |
neutron-openvswitch-agent (fwaas_v2 L2) | handle_port, delete_port | L2 agent extension hook | apply or remove the group of a port that (dis)appears on the bridge | RPC get_firewall_group_for_port | OVS flows; status RPC | neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:354, neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:390 |
neutron-l3-agent (fwaas_v2_log) | Neutron logapi RPC | L3 agent extension | NFLOG logging of accepted/dropped packets on FWaaS chains | log info RPC | iptables NFLOG rules, nflog process, log file | neutron_fwaas/services/logapi/agents/l3/fwg_log.py:25, neutron_fwaas/services/logapi/agents/drivers/iptables/log.py:225 |
| neutron-ovn-db-sync-util (CLI) | --sync_plugin neutron_fwaas_nb_sync | operator command | compare and repair FWaaS Port_Groups/ACLs in the OVN NB DB | firewall_groups_v2…, NB Port_Group | NB Port_Group, ACL (repair mode) | neutron_fwaas/services/firewall/service_drivers/ovn/ovn_db_sync.py:61 |
neutron-server: RPC endpoints (agent driver only)
The listener is started only when the configured driver is FirewallDriverRPCMixin (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:73). It runs in Neutron's RPC worker, RpcWorker([self], worker_process_count=0), which calls start_rpc_listeners (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:77). The listener consumes the topic q-firewall-plugin (neutron_fwaas/common/fwaas_constants.py:20) with server cfg.CONF.host (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:203, neutron-lib:neutron_lib/rpc.py:346). Endpoint version 1.1 (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:44). Every method runs in a DB writer transaction (@db_api.CONTEXT_WRITER).
set_firewall_group_status
- Trigger: RPC
call, argumentsfwg_id,status,host,rpc_version(neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:76,neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:61). - Emitter: the L3 or L2 agent extension, after applying a cast or a router or port event.
- Purpose: move a group out of
PENDING_*to its real state. - Flow:
- Sanitise:
ACTIVE,DOWNandINACTIVEare kept; anything else becomesERROR(neutron_fwaas/services/firewall/service_drivers/agents/agents.py:54). - Conditional update:
UPDATE firewall_groups_v2 SET status=:s WHERE id=:id AND status NOT IN ('PENDING_DELETE')(neutron_fwaas/objects/firewall_v2.py:274). - Return
Trueif a row was updated and the status is notERROR.
- Sanitise:
- Concurrency and idempotency: idempotent (last write wins). Several agents answer the same fanout cast: each L3 agent hosting some of the ports sends its own status, so the last one wins and an
ERRORfrom one agent can be overwritten by anACTIVEfrom another [inferred]. - Errors: none raised; an unknown id updates 0 rows and returns
False.
firewall_group_deleted
- Trigger: RPC
callwithfwg_id,host. - Purpose: the agent removed the group's rules; delete the row if a deletion was pending.
- Flow:
- If the group is
PENDING_DELETEorERROR, delete it (default-group rules apply) and returnTrue(neutron_fwaas/services/firewall/service_drivers/agents/agents.py:76). - Otherwise log "unexpectedly deleted by agent", set the status to
ERRORand returnFalse. - An unknown group returns
True.
- If the group is
- Note: the agents call this method from three places:
- the L3 agent's
delete_firewall_groupendpoint (neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:552); - the L3 router resync of a
PENDING_DELETEgroup (neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:222); - the L2
delete_firewall_groupendpoint (neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:293). On this branch the server neither sends adelete_firewall_groupcast nor setsPENDING_DELETE(see § L3 agent), so in practice this endpoint is unused [inferred from the absence of callers].
- the L3 agent's
get_firewall_groups_for_project
- Trigger: RPC
callfrom the L3 agent withrpc_version='1.1'(neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:43). The request context carriesproject_id= the router's project and no user (neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:271). - Data read:
get_firewall_groups(context)without filters, so the groups the context can see. That call first ensures the context's project has a default group (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:853), so an RPC read can create rows. Then, for each group: its ports (firewall_group_port_associations_v2), its ordered ingress and egress rules (firewall_policy_rule_associations_v2sorted byposition, thenfirewall_rules_v2) (neutron_fwaas/services/firewall/rpc/serialization.py:58). - Response: a list in the format of the requested version.
1.1: OVO primitives{"firewall_group": {versioned_object…}, "ingress_rules": [...], "egress_rules": [...], "add_port_ids", "del_port_ids"}.1.0: legacy dicts (neutron_fwaas/services/firewall/rpc/serialization.py:141). - Cost: 1 + 3 queries per group, plus the default-group check.
get_projects_with_firewall_groups
- Reads all groups with an admin context and returns the distinct project ids (
neutron_fwaas/services/firewall/service_drivers/agents/agents.py:106). Its only caller isprocess_services_sync, which is never invoked (below).
get_firewall_group_for_port
- Trigger: RPC
callfrom the L2 extension withport_id(neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:44). - Flow: look up the association with an elevated context (
neutron_fwaas/services/firewall/service_drivers/agents/agents.py:119). If exactly one group holds the port, return it with its rules in the requested format; otherwise returnNone.
neutron-server: in-process callbacks
handle_update_port (port AFTER_UPDATE)
- Trigger: Neutron registry event
port AFTER_UPDATE(neutron_fwaas/services/firewall/fwaas_plugin_v2.py:257). - Filter: compute ports only (
device_ownerstarts withcompute:), whose previousbinding:vif_typewasunbound, so the first binding, and which the driver supports as L2 ports. - Flow: find the project's group named
default. If there is exactly one, callupdate_firewall_groupwithports = previous ports + [port], which goes through the driver and may cast to agents (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:296). AFirewallGroupPortInUseis logged and ignored. - OVN: never acts, because the OVN driver supports no L2 port (
neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:42). - Concurrency: two VMs bound at the same time read the same port list and the last update wins, so one port can be dropped from the default group [inferred: no lock and no compare-and-swap around the read-modify-write].
Logging callbacks (log plugin + agent driver)
FirewallGroupCallBack.handle_event(neutron_fwaas/services/logapi/common/fwg_callback.py:27) runs on group create and update. It computes the ports added or removed. If one of them is a router interface, it pushesRESOURCE_UPDATEfor the logs bound to the group through Neutron's logapi (neutron_fwaas/services/logapi/common/fwg_callback.py:46).NeutronPortCallBack.handle_event(neutron_fwaas/services/logapi/common/port_callback.py:26) runs when a router interface port changes status, and pushes its logs.- Both are registered when the agent driver registers its logging driver (
neutron_fwaas/services/firewall/service_drivers/agents/agents.py:174,neutron_fwaas/services/logapi/agents/drivers/iptables/driver.py:48). The OVN driver registers none (neutron_fwaas/services/firewall/service_drivers/driver_api.py:53), so FWaaS logging is not available on OVN.
neutron-l3-agent: fwaas_v2 extension
Loaded by [agent] extensions = fwaas_v2 (entry point pyproject.toml:69) when [fwaas] enabled = True and [fwaas] driver = iptables_v2 (neutron_fwaas/services/firewall/service_drivers/agents/firewall_agent_api.py:26, neutron_fwaas/services/firewall/service_drivers/agents/firewall_service.py:31). It consumes the topic firewall_agent with server = host (neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:101), endpoint version 1.1 (neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:84). The server casts with fanout=True (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:145), so every L3 agent, and every OVS agent with the L2 extension, receives every cast. Each one filters on what it hosts locally.
create_firewall_group (L3 agent)
- Trigger: fanout cast
create_firewall_group(firewall_group=<legacy dict>, host=<server host>), sent on group creation with ports and a policy (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:268). - Flow (
neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:376):- Deserialise (OVO or legacy).
- Keep the
add-port-idshosted in a local router namespace (agent_api.get_router_hosting_port). With none, return without answering (another agent will answer). - Call the iptables driver: build per-group chains
iv4<fwg>,ov4<fwg>,iv6<fwg>andov6<fwg>, starting with drop INVALID and accept RELATED,ESTABLISHED, then one rule per enabled policy rule in order. AddFORWARDjumps-o qr-<port>(ingress) and-i qr-<port>(egress), then a final jump tofwaas-default-policy(drop) (neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:219,neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:425). Withadmin_state_up=false, only the drop-all default policy is installed (neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:158). DVR: the rules go to the SNAT namespace (sg-interfaces) and/or the router namespace (rfp-interfaces) (neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:92). - Flush conntrack in the namespace (
conntrack -Dthrough privsep, or netlink) (neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:304,neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/legacy_conntrack.py:50). call set_firewall_group_status(ACTIVE | DOWN | ERROR).
- Errors:
FirewallInternalDriverErrorgivesERROR. An RPC failure setsservices_sync_needed = Trueand logs. - Cost: one iptables-restore per namespace, one conntrack flush per router, one RPC.
update_firewall_group (L3 agent)
- Trigger: fanout cast on group update, policy update, rule update and insert/remove (
neutron_fwaas/services/firewall/service_drivers/agents/agents.py:219,neutron_fwaas/services/firewall/service_drivers/agents/agents.py:344). The payload carriesadd-port-ids,del-port-ids,port_detailsandlast-port. - Flow (
neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:421):- Remove the chains from the local
del-port-ids; the status becomesINACTIVEwhenlast-portis set. - Re-apply on the local
add-port-ids. Conntrack entries are deleted only for changed, added or removed rules, compared with the previous group the driver remembers (pre_firewall, one per driver instance, not per group,neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:143). - Answer the status. When all ports are foreign, return without answering.
- Remove the chains from the local
- Point of attention:
pre_firewallis shared by every group handled by the agent, so the conntrack cleanup of an update compares with whichever group was applied last [inferred].
delete_firewall_group (L3 agent)
- Trigger: fanout cast
delete_firewall_group. The server never sends it:FirewallAgentApi.delete_firewall_group(neutron_fwaas/services/firewall/service_drivers/agents/agents.py:157) has no caller, because a group can only be deleted once it has no ports. The endpoint is kept for compatibility, and its handler callsfirewall_group_deleted(neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:515).
add_router / update_router
- Trigger: the L3 agent's extension manager, when a router is added to or updated on this agent (
neutron:neutron/agent/l3/agent.py:638,neutron:neutron/agent/l3/agent.py:698). - Flow (
neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:255):- If the router is in a local namespace,
call get_firewall_groups_for_projectwith the router's project. - For each group whose ports intersect the router's interfaces, apply it (delete if
PENDING_DELETE, else update) and send the status if it differs (neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:203). A port found in two groups is skipped with a warning.
- If the router is in a local namespace,
- Errors: any exception is logged and sets
services_sync_needed. - Cost: one RPC call per router event, whose cost grows with the number of groups of the project.
process_services_sync
- Meant to resync every project's groups after an RPC failure (
neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:345). Nothing calls it: neither neutron-fwaas nor Neutron's L3 agent invokesprocess_services_sync[verified: no caller in either repository]. The flagservices_sync_neededis therefore write-only, and a group whose status RPC failed staysPENDING_*until the next router event.
delete_router, update_network, ha_state_change
- No-ops (
neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:333). Deleting the namespace removes the iptables rules.
neutron-openvswitch-agent: fwaas_v2 L2 extension
Loaded by [agent] extensions = fwaas_v2 on the OVS agent with [fwaas] firewall_l2_driver = ovs (pyproject.toml:62, neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:75). It subscribes to the same firewall_agent topic (neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:98). Handlers are serialised by lockutils.synchronized('fwg') and ('fwg-port').
- create / update / delete_firewall_group: keep the ports of
port_detailsthat are compute ports on this host (neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:123). Program OVS flows in tables 64, 65, 66, 68 and 69 of br-int (neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/l2/openvswitch_firewall/constants.py:37), using conntrack and conjunctions (neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/l2/openvswitch_firewall/firewall.py:216). Then report a status computed by_compute_status:ERRORon failure,DOWNwhen admin-down,ACTIVEwith ports and a policy, otherwiseINACTIVE(neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:157). Unlike the L3 path, an update with no local port **reportsINACTIVE** (neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:255), which can overwrite the status reported by the agent that does host the ports [inferred]. - handle_port (
neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:354): called by the OVS agent for each new or updated port (neutron:neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py:2150). Trusted ports (network:*) get pass-through flows. A compute port without a known group triggerscall get_firewall_group_for_port, then the group is applied and its status reported. - delete_port (
neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:390): removes the port's flows and reports the group's status.
neutron-ovn-db-sync-util: FWaaS plugin
- Trigger: an operator runs
neutron-ovn-db-sync-util --sync_mode {log,repair} --sync_plugin neutron_fwaas_nb_sync(entry pointpyproject.toml:83). Neutron callsdo_sync()on each loaded sync plugin (neutron:neutron/cmd/ovn/neutron_ovn_db_sync_util.py:235). It is not run by neutron-server's OVN maintenance task. - Preparation: forces
[service_providers] service_providerto the OVN driver for the command's process (neutron_fwaas/services/firewall/service_drivers/ovn/ovn_db_sync.py:56). - Data read:
- every group of the Neutron DB except those named
default(neutron_fwaas/services/firewall/service_drivers/ovn/ovn_db_sync.py:85); - every NB
Port_Groupwithexternal_ids:neutron:firewall_group_id(neutron_fwaas/services/firewall/service_drivers/ovn/ovn_db_sync.py:94).
- every group of the Neutron DB except those named
- Flow (
neutron_fwaas/services/firewall/service_drivers/ovn/ovn_db_sync.py:129):- Log the counts and lists as WARNING whenever there is anything to sync. Every non-default group is always counted as "to sync".
logmode stops here;migratemode is refused (debug log) andoffdoes nothing (neutron_fwaas/services/firewall/service_drivers/ovn/ovn_db_sync.py:67).repair: for each Neutron group, create the Port_Group if missing (may_exist), add the four default ACLs and the rule ACLs (may_exist), and add the group's ports.- Delete the Port_Groups whose group no longer exists.
- Limitations [verified on the code]:
- stale ACLs inside an existing Port_Group are not removed (rule deleted or changed while OVN was out of sync);
- extra ports are not removed from the Port_Group;
- the default group's Port_Group, if it exists, is treated as orphaned and deleted (default groups are excluded from the Neutron side).
- Cost: one NB
Port_Grouptable scan, and per group one DB read of the rules plus one transaction.
Source: reverse-spec-neutron-fwaas/05-async-processing_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.