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

Reverse-spec · Services

neutron-fwaas — Async processing

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

ProcessTriggerTypePurposeReadsWrites / emitsDefined at
neutron-server (agent driver)set_firewall_group_statusRPC call received, topic q-firewall-pluginrecord the status the agent computed—UPDATE firewall_groups_v2.statusneutron_fwaas/services/firewall/service_drivers/agents/agents.py:51
neutron-server (agent driver)firewall_group_deletedRPC call receivedfinish a deletion confirmed by the agentfirewall_groups_v2DELETE or status ERRORneutron_fwaas/services/firewall/service_drivers/agents/agents.py:71
neutron-server (agent driver)get_firewall_groups_for_projectRPC call receivedgive an agent every group visible in the request context, with rulesgroups, 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_groupsRPC call receivedlist 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_portRPC call receivedgive the L2 agent the group of one portassociations, group, rules—neutron_fwaas/services/firewall/service_drivers/agents/agents.py:115
neutron-serverport AFTER_UPDATENeutron callbackattach a newly bound VM port to the project's default groupgroupsupdate_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_UPDATENeutron callbackpush logging config to agents when router ports join a group or change statuslogs, portsNeutron logapi RPC RESOURCE_UPDATEneutron_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_groupRPC fanout cast received, topic firewall_agentprogram iptables on the router ports hosted herecast payload, local router namespacesiptables, conntrack; RPC set_firewall_group_status / firewall_group_deletedneutron_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_routerL3 agent extension hookre-apply the groups of a router that appears or changes on this agentRPC get_firewall_groups_for_projectiptables; status RPCneutron_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_syncresync hook, never calledfull resync after RPC failuresRPCiptablesneutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:345
neutron-openvswitch-agent (fwaas_v2 L2 extension)create/update/delete_firewall_groupRPC fanout cast received, topic firewall_agentprogram OVS flows for the VM ports of this hostcast payloadOVS flows (tables 64–69); status RPCneutron_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_portL2 agent extension hookapply or remove the group of a port that (dis)appears on the bridgeRPC get_firewall_group_for_portOVS flows; status RPCneutron_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 RPCL3 agent extensionNFLOG logging of accepted/dropped packets on FWaaS chainslog info RPCiptables NFLOG rules, nflog process, log fileneutron_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_syncoperator commandcompare and repair FWaaS Port_Groups/ACLs in the OVN NB DBfirewall_groups_v2…, NB Port_GroupNB 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, arguments fwg_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:
    1. Sanitise: ACTIVE, DOWN and INACTIVE are kept; anything else becomes ERROR (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:54).
    2. 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).
    3. Return True if a row was updated and the status is not ERROR.
  • 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 ERROR from one agent can be overwritten by an ACTIVE from another [inferred].
  • Errors: none raised; an unknown id updates 0 rows and returns False.

firewall_group_deleted

  • Trigger: RPC call with fwg_id, host.
  • Purpose: the agent removed the group's rules; delete the row if a deletion was pending.
  • Flow:
    1. If the group is PENDING_DELETE or ERROR, delete it (default-group rules apply) and return True (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:76).
    2. Otherwise log "unexpectedly deleted by agent", set the status to ERROR and return False.
    3. An unknown group returns True.
  • Note: the agents call this method from three places:
    • the L3 agent's delete_firewall_group endpoint (neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:552);
    • the L3 router resync of a PENDING_DELETE group (neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:222);
    • the L2 delete_firewall_group endpoint (neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:293). On this branch the server neither sends a delete_firewall_group cast nor sets PENDING_DELETE (see § L3 agent), so in practice this endpoint is unused [inferred from the absence of callers].

get_firewall_groups_for_project

  • Trigger: RPC call from the L3 agent with rpc_version='1.1' (neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:43). The request context carries project_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_v2 sorted by position, then firewall_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 is process_services_sync, which is never invoked (below).

get_firewall_group_for_port

  • Trigger: RPC call from the L2 extension with port_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 return None.

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_owner starts with compute:), whose previous binding:vif_type was unbound, 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, call update_firewall_group with ports = previous ports + [port], which goes through the driver and may cast to agents (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:296). A FirewallGroupPortInUse is 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 pushes RESOURCE_UPDATE for 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):
    1. Deserialise (OVO or legacy).
    2. Keep the add-port-ids hosted in a local router namespace (agent_api.get_router_hosting_port). With none, return without answering (another agent will answer).
    3. Call the iptables driver: build per-group chains iv4<fwg>, ov4<fwg>, iv6<fwg> and ov6<fwg>, starting with drop INVALID and accept RELATED,ESTABLISHED, then one rule per enabled policy rule in order. Add FORWARD jumps -o qr-<port> (ingress) and -i qr-<port> (egress), then a final jump to fwaas-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). With admin_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).
    4. Flush conntrack in the namespace (conntrack -D through 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).
    5. call set_firewall_group_status(ACTIVE | DOWN | ERROR).
  • Errors: FirewallInternalDriverError gives ERROR. An RPC failure sets services_sync_needed = True and 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 carries add-port-ids, del-port-ids, port_details and last-port.
  • Flow (neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:421):
    1. Remove the chains from the local del-port-ids; the status becomes INACTIVE when last-port is set.
    2. 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).
    3. Answer the status. When all ports are foreign, return without answering.
  • Point of attention: pre_firewall is 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 calls firewall_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):
    1. If the router is in a local namespace, call get_firewall_groups_for_project with the router's project.
    2. 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.
  • 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 invokes process_services_sync [verified: no caller in either repository]. The flag services_sync_needed is therefore write-only, and a group whose status RPC failed stays PENDING_* 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_details that 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: ERROR on failure, DOWN when admin-down, ACTIVE with ports and a policy, otherwise INACTIVE (neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:157). Unlike the L3 path, an update with no local port **reports INACTIVE** (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 triggers call 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 point pyproject.toml:83). Neutron calls do_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_provider to 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_Group with external_ids:neutron:firewall_group_id (neutron_fwaas/services/firewall/service_drivers/ovn/ovn_db_sync.py:94).
  • Flow (neutron_fwaas/services/firewall/service_drivers/ovn/ovn_db_sync.py:129):
    1. Log the counts and lists as WARNING whenever there is anything to sync. Every non-default group is always counted as "to sync".
    2. log mode stops here; migrate mode is refused (debug log) and off does nothing (neutron_fwaas/services/firewall/service_drivers/ovn/ovn_db_sync.py:67).
    3. 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.
    4. 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_Group table 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.