Interactions — 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 runs inside other processes: neutron-server (service plugin), neutron-l3-agent and neutron-openvswitch-agent (extensions), and neutron-ovn-db-sync-util (sync plugin). It opens no connection of its own: it uses Neutron's DB engine, Neutron's oslo.messaging transport, and the ML2/OVN mechanism driver's Northbound IDL. It makes no outbound REST call.
OVN Northbound DB (OVN driver)
Connection: the OVN driver borrows mech_driver.nb_ovn, the ovsdbapp OvsdbNbOvnIdl of the ovn (or ovn-sync) ML2 mechanism driver (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:52, neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:56). The endpoint is [ovn] ovn_nb_connection of Neutron. All writes go through nb_ovn.transaction(check_error=True) (OVSDB transact over JSON-RPC). Reads (get_port_group, pg_acl_list) come from the IDL's in-memory replica.
Objects written
| NB table | Row | Columns written | When | Defined at |
|---|---|---|---|---|
| Port_Group | one per firewall group, name = pg_<group uuid with '-' → '_'> (neutron:neutron/common/ovn/utils.py:367) | name, external_ids = {"neutron:firewall_group_id": <uuid>}, acls (initially empty), ports | group created (pre-commit), recreated on update or repair | neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:219 |
| Port_Group.ports | the Logical_Switch_Port named after the Neutron router interface port id (router-type LSP) | added/removed by pg_add_ports / pg_del_ports | group create/update | neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:152 |
| ACL (referenced from Port_Group.acls) | 4 default rows per Port_Group | priority 1001, action drop, direction from-lport and to-lport, match "inport == @pg && ip4" and the ip6 and outport variants, external_ids:neutron:firewall_rule_id = default_rule, log false, name [], severity [] | Port_Group initialised; policy changes reset to these four | neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:228 |
| ACL | one per enabled rule per direction in which the rule's policy is attached | priority = 2000 - position, action ∈ {allow-stateless, drop, reject}, direction, match (see below), external_ids:neutron:firewall_rule_id = <rule uuid> | group create/update, policy change, rule enable/update, insert | neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:126 |
ovsdbapp's pg_acl_add writes the external_ids entry from the extra keyword neutron:firewall_rule_id, and may_exist=True makes re-adding an identical ACL a no-op (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:195) [inferred from the ovsdbapp API: **external_ids keyword arguments].
ACL match grammar
The match is built in this order (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:143):
<dir> == @<pg> dir = inport (ingress) | outport (egress) neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:27
[ && ip4 | && ip6 ] from rule.ip_version neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:35
[ && ipN.src == <source_ip_address> ] [ && ipN.dst == <destination_ip_address> ] neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:50
[ && tcp|udp|sctp protocol in TRANSPORT_PROTOCOLS neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:82
[ && P.src == n | && P.src >= a && P.src <= b ]
[ && P.dst == n | && P.dst >= a && P.dst <= b ] ]
[ && icmp4 | && icmp6 ] protocol icmp or ipv6-icmp, chosen by ip_version
(no clause) any other protocol name (gre, ah, esp, vrrp…) or null
Each rule's position is its index inside the policy (0-based), counting disabled rules too; disabled rules are skipped but still consume a number (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:102). Constants: neutron_fwaas/services/firewall/service_drivers/ovn/constants.py:18.
Sample ACL row (from _rewrite/cases-ovn-acl_stable-2026.2.json, case 0066):
{"port_group": "pg_68344b9e_77a2_4c35_8563_e2f6cfdbc18e", "priority": 2000, "action": "drop",
"direction": "to-lport", "log": false, "name": [], "severity": [],
"match": "outport == @pg_68344b9e_77a2_4c35_8563_e2f6cfdbc18e && ip4 && ip4.src == 1.1.1.1 && ip4.dst == 2.2.2.2 && icmp4",
"neutron:firewall_rule_id": "8590e864-b913-442a-bb93-8e250b80226b"}
Operations per API call
| API call | OVSDB transactions | Content | Defined at |
|---|---|---|---|
| POST group | 1 (pg_add) + 1 (4 default ACLs) in pre-commit; then 1 (rule ACLs + ports) in post-commit; on failure 1 (pg_del). All before the DB commit | as above | neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:199, neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:210 |
| PUT group (ports/policies changed) | 1, or 3 when the Port_Group is missing | pg_add_ports/pg_del_ports; on a policy change, db_set Port_Group acls=<4 default uuids> then re-add the rule ACLs | neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:251, neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:126 |
| DELETE group | 1 | pg_del (the ACLs are garbage-collected by OVSDB as non-root rows) | neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:305 |
| PUT policy (rules changed), insert_rule | 1 per group using the policy | reset to the defaults, then add all rule ACLs of the group's two policies | neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:157 |
| remove_rule, rule disabled | 1 per (direction, group) | pg_acl_del(pg, direction, priority, match) of the matching ACL | neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:211 |
| rule enabled | 1 per group | pg_acl_add of the rule only | neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:169 |
| rule changed (still enabled) | 1 per (direction, group) | db_set ACL <uuid> match=… action=… | neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:206 |
Error handling: check_error=True turns an OVSDB failure into an exception. None is caught except in group-create post-commit (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:228). There is no retry, and no revision-number bookkeeping (unlike Neutron's own OVN resources). Timeouts are those of the shared IDL ([ovn] ovsdb_connection_timeout).
Interaction with Neutron's security groups: FWaaS ACLs sit on router-type LSPs. Neutron's own ACLs live in the pg_<sg> Port_Groups and the neutron_pg_drop group of VM ports, so the two sets do not share ports [inferred: router ports have port security disabled in ML2/OVN]. FWaaS uses priorities 1001 and 1001–2000, inside the same range as Neutron's security-group ACLs (1001–1002). That only matters if a port belonged to both kinds of Port_Group.
Database (MariaDB/MySQL through Neutron's oslo.db engine)
Schema from the real migrations: _rewrite/schema-mysql-migrated_stable-2026.2.sql. Models: neutron_fwaas/db/firewall/v2/models.py:25. Alembic branch neutron-fwaas, version table alembic_version_fwaas, heads 1007f519ea46 (contract) and 2a0d33e9ef63 (expand) (neutron_fwaas/db/migration/alembic_migrations/versions/CONTRACT_HEAD, neutron_fwaas/db/migration/alembic_migrations/versions/EXPAND_HEAD).
Tables
| Table | Role | Written by | Read by |
|---|---|---|---|
firewall_rules_v2 | rules (ports stored as min/max integers; standard_attr_id → Neutron standardattributes) | rule create/update/delete, default-group creation | every rule, policy and group operation; RPC payloads |
firewall_policies_v2 | policies (rule_count column never written) | policy create/update/delete, insert/remove (audited) | groups, RPC |
firewall_policy_rule_associations_v2 | ordered rule list (PK policy+rule, position 1..n) | policy create/update, insert/remove (delete all + re-insert) | ordered rule lists, "policies using rule" |
firewall_groups_v2 | groups (status, FKs to the two policies without ON DELETE) | group create/update/delete, status RPC | everything |
firewall_group_port_associations_v2 | group ↔ port (PK group+port, UNIQUE port_id, FK ports.id ON DELETE CASCADE) | group create/update | port filters, agents, logging |
default_firewall_groups | project → its default group (PK project_id, FK CASCADE) | default-group creation | — (presence used as a lock) |
Neutron tables touched: standardattributes (one row per rule, policy or group: created_at, updated_at, revision_number, description), tags (tag support), quotas/reservations/quotausages (quota engine), ports (read through the core plugin; a port deletion cascades to the association).
Notable transactions
- API calls: each plugin method runs in
CONTEXT_WRITERorCONTEXT_READER(neutron_fwaas/services/firewall/fwaas_plugin_v2.py:307), and Neutron'sretry_db_errorswraps member actions (neutron:neutron/api/v2/base.py:226). - Driver hooks: the DB mixin names its hooks pre-commit and post-commit (
neutron_fwaas/services/firewall/service_drivers/driver_api.py:143). However, every plugin write method is itself wrapped in@db_api.CONTEXT_WRITER(neutron_fwaas/services/firewall/fwaas_plugin_v2.py:307,neutron_fwaas/services/firewall/fwaas_plugin_v2.py:358), and the driver's inner transaction joins it. So both hooks run before the DB commit, and an exception in a post-commit hook rolls the DB change back:- OVN: its OVSDB writes are already committed and diverge;
- agent driver: the RPC cast is sent before the commit, so an agent may read the group before the new state is visible [inferred from the decorator nesting].
- Rule list rewrite:
DELETE FROM firewall_policy_rule_associations_v2 WHERE firewall_policy_id=?, then oneINSERTper rule (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:136). There is noSELECT … FOR UPDATE, so two concurrent inserts into the same policy can lose one rule [inferred]. - Status update (agent RPC):
UPDATE firewall_groups_v2 SET status=? WHERE id=? AND status NOT IN (?)(neutron_fwaas/objects/firewall_v2.py:276). - Default group creation: a separate admin transaction. A concurrent duplicate is detected through the PK of
default_firewall_groups(neutron_fwaas/db/firewall/v2/firewall_db_v2.py:698). - Representative reads:
- ordered rules of a policy:
SELECT * FROM firewall_policy_rule_associations_v2 WHERE firewall_policy_id=?, sorted by position in Python, thenSELECT * FROM firewall_rules_v2 WHERE id IN (…)(neutron_fwaas/db/firewall/v2/firewall_db_v2.py:77); - groups using a policy: two queries on
firewall_groups_v2by ingress and by egress id (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:447).
- ordered rules of a policy:
RabbitMQ (agent driver only)
Topology
| Exchange / queue | Type | Producer | Consumer |
|---|---|---|---|
q-firewall-plugin (topic queue, plus q-firewall-plugin.<server host>) | RPC topic | L3 and L2 agent extensions (call) | neutron-server RPC worker (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:203) |
firewall_agent_fanout (fanout exchange, one queue per consumer) | RPC fanout | neutron-server (cast, fanout=True) | every L3 agent with fwaas_v2 and every OVS agent with fwaas_v2 (neutron_fwaas/services/firewall/service_drivers/agents/l3reference/firewall_l3_agent_v2.py:105, neutron_fwaas/services/firewall/service_drivers/agents/l2/fwaas_v2.py:101) |
notifications.info | notifications | neutron-server (Neutron base controller) | ceilometer or any listener |
RPC methods
| Method | Direction | Mode | Topic / server | Version | Arguments | Response | Defined at |
|---|---|---|---|---|---|---|---|
| create_firewall_group | server → agents | cast fanout | firewall_agent | prepared 1.0 (legacy dicts) | firewall_group (dict), host | — | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:149 |
| update_firewall_group | server → agents | cast fanout | firewall_agent | 1.0 | firewall_group, host | — | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:153 |
| delete_firewall_group | server → agents | cast fanout | firewall_agent | 1.0 | firewall_group, host | — | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:157 (no caller) |
| set_firewall_group_status | agent → server | call | q-firewall-plugin | 1.1 | fwg_id, status, host, rpc_version | bool | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:51 |
| firewall_group_deleted | agent → server | call | q-firewall-plugin | 1.1 | fwg_id, host, rpc_version | bool | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:71 |
| get_firewall_groups_for_project | L3 agent → server | call | q-firewall-plugin | 1.1 | host, rpc_version (project from the context) | list of groups (format per rpc_version) | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:92 |
| get_projects_with_firewall_groups | L3 agent → server | call | q-firewall-plugin | 1.1 | host, rpc_version | list of project ids | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:106 |
| get_firewall_group_for_port | L2 agent → server | call | q-firewall-plugin | 1.1 | port_id, host, rpc_version | group or null | neutron_fwaas/services/firewall/service_drivers/agents/agents.py:115 |
| set_firewall_status, firewall_deleted | agent → server | call | q-firewall-plugin | — | FWaaS v1 | no server endpoint (dead client code) | neutron_fwaas/services/firewall/service_drivers/agents/firewall_agent_api.py:61 |
Versions: the server endpoint is 1.1. The server still casts in 1.0 (legacy dict) so that 1.0 agents keep working (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:142); agents accept both (neutron_fwaas/services/firewall/rpc/serialization.py:149). Agents call in 1.1 and pass rpc_version='1.1' explicitly, and the server answers in the requested format (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:94). Timeout: oslo.messaging rpc_response_timeout (60 s by default) for the calls.
Sample frame — fanout cast update_firewall_group (version 1.0 payload)
Built by neutron_fwaas/services/firewall/rpc/serialization.py:79:
{"oslo.version": "2.0", "oslo.message": {
"method": "update_firewall_group", "version": "1.0",
"args": {"host": "controller-1", "firewall_group": {
"id": "6f…", "project_id": "a1…", "name": "fwg1", "status": "PENDING_UPDATE",
"admin_state_up": true, "shared": false, "ports": ["c3…"],
"ingress_firewall_policy_id": "9b…", "egress_firewall_policy_id": null,
"ingress_rule_list": [{"id": "e4…", "protocol": "tcp", "ip_version": 4, "action": "allow",
"enabled": true, "source_ip_address": null, "destination_ip_address": "10.0.0.0/24",
"source_port": null, "destination_port": "22", "shared": false, "firewall_policy_id": null, "…": "…"}],
"egress_rule_list": [],
"add-port-ids": ["c3…"], "del-port-ids": [], "last-port": false,
"port_details": {"c3…": {"device_owner": "network:router_interface", "device": "c3…",
"network_id": "…", "fixed_ips": [{"subnet_id": "…", "ip_address": "10.0.0.1"}],
"allowed_address_pairs": [], "port_security_enabled": true, "id": "c3…", "status": "ACTIVE"}}}},
"_context_project_id": "a1…", "_context_user_id": "…", "…": "…"}}
For compute ports, port_details also carries host (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:248). In version 1.1 replies, the group and rules are versioned_object primitives of FirewallGroup 1.0 and FirewallRuleV2 1.0 (neutron_fwaas/objects/firewall_v2.py:212, neutron_fwaas/objects/firewall_v2.py:48).
Notifications
Emitted by Neutron's controller, not by neutron-fwaas: firewall_group.create.start, firewall_group.create.end and the update/delete pairs, likewise for firewall_policy and firewall_rule (neutron:neutron/api/v2/base.py:511). Payload: the resource dict as returned by the API. Publisher network.<host>, priority INFO.
In-process callbacks (neutron-lib registry)
| Resource / event | Published by | Consumed by |
|---|---|---|
firewall_group AFTER_CREATE / AFTER_UPDATE / AFTER_DELETE | neutron_fwaas/services/firewall/service_drivers/driver_api.py:158, neutron_fwaas/services/firewall/service_drivers/driver_api.py:219, neutron_fwaas/services/firewall/service_drivers/driver_api.py:183 | FWaaS logging (neutron_fwaas/services/logapi/common/fwg_callback.py:27) |
firewall_policy AFTER_CREATE / AFTER_UPDATE (also sent on delete) | neutron_fwaas/services/firewall/service_drivers/driver_api.py:249, neutron_fwaas/services/firewall/service_drivers/driver_api.py:271 | nobody in neutron-fwaas |
firewall_rule AFTER_CREATE / AFTER_UPDATE / AFTER_DELETE | neutron_fwaas/services/firewall/service_drivers/driver_api.py:331 | nobody in neutron-fwaas |
port AFTER_UPDATE | Neutron core | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:257, neutron_fwaas/services/logapi/common/port_callback.py:26 |
Core plugin (in-process Python)
| Call | When | Defined at |
|---|---|---|
core_plugin.get_port(context, port_id) | validation of each port of a group (twice for compute ports), and port details for agent payloads | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:157, neutron_fwaas/services/firewall/service_drivers/agents/agents.py:233 |
core_plugin.mechanism_manager.mech_drivers['ovn' or 'ovn-sync'].obj | first OVN access | neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:61 |
ServiceTypeManager.add_provider_configuration, service_base.load_drivers | plugin start | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:52 |
Binaries and kernel (agent driver)
| What | How | Where | Defined at |
|---|---|---|---|
| iptables / ip6tables | Neutron IptablesManager of the router namespace (iptables-save/restore, rootwrap) | L3 agent | neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:190 |
| conntrack | ip netns exec <ns> conntrack -D [filters] through privsep (privsep_exec=True), driver conntrack | L3 agent | neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/legacy_conntrack.py:65 |
| conntrack over netlink | libnetfilter_conntrack through ctypes in privsep, driver netlink_conntrack | L3 agent | neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/netlink_conntrack.py:30 |
| NFLOG | libnetfilter_log reader process per router namespace; packets logged to [network_log] local_output_log_base | L3 agent (fwaas_v2_log) | neutron_fwaas/services/logapi/agents/drivers/iptables/log.py:225 |
| OpenFlow | br-int flows in tables 64–69 (OVS L2 driver) | OVS agent | neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/l2/openvswitch_firewall/firewall.py:216 |
The rootwrap filter etc/neutron/rootwrap.d/fwaas-privsep.filters allows the privsep helper.
Outbound REST
None. neutron-fwaas calls neither Keystone, Nova nor any other service.
Source: reverse-spec-neutron-fwaas/03-interactions_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.