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

Reverse-spec · Services

neutron-fwaas — Interactions

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 tableRowColumns writtenWhenDefined at
Port_Groupone 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), portsgroup created (pre-commit), recreated on update or repairneutron_fwaas/services/firewall/service_drivers/ovn/acl.py:219
Port_Group.portsthe Logical_Switch_Port named after the Neutron router interface port id (router-type LSP)added/removed by pg_add_ports / pg_del_portsgroup create/updateneutron_fwaas/services/firewall/service_drivers/ovn/acl.py:152
ACL (referenced from Port_Group.acls)4 default rows per Port_Grouppriority 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 fourneutron_fwaas/services/firewall/service_drivers/ovn/acl.py:228
ACLone per enabled rule per direction in which the rule's policy is attachedpriority = 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, insertneutron_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 callOVSDB transactionsContentDefined at
POST group1 (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 commitas aboveneutron_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 missingpg_add_ports/pg_del_ports; on a policy change, db_set Port_Group acls=<4 default uuids> then re-add the rule ACLsneutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:251, neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:126
DELETE group1pg_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_rule1 per group using the policyreset to the defaults, then add all rule ACLs of the group's two policiesneutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:157
remove_rule, rule disabled1 per (direction, group)pg_acl_del(pg, direction, priority, match) of the matching ACLneutron_fwaas/services/firewall/service_drivers/ovn/acl.py:211
rule enabled1 per grouppg_acl_add of the rule onlyneutron_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

TableRoleWritten byRead by
firewall_rules_v2rules (ports stored as min/max integers; standard_attr_id → Neutron standardattributes)rule create/update/delete, default-group creationevery rule, policy and group operation; RPC payloads
firewall_policies_v2policies (rule_count column never written)policy create/update/delete, insert/remove (audited)groups, RPC
firewall_policy_rule_associations_v2ordered 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_v2groups (status, FKs to the two policies without ON DELETE)group create/update/delete, status RPCeverything
firewall_group_port_associations_v2group ↔ port (PK group+port, UNIQUE port_id, FK ports.id ON DELETE CASCADE)group create/updateport filters, agents, logging
default_firewall_groupsproject → 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_WRITER or CONTEXT_READER (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:307), and Neutron's retry_db_errors wraps 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 one INSERT per rule (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:136). There is no SELECT … 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, then SELECT * 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_v2 by ingress and by egress id (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:447).

RabbitMQ (agent driver only)

Topology

Exchange / queueTypeProducerConsumer
q-firewall-plugin (topic queue, plus q-firewall-plugin.<server host>)RPC topicL3 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 fanoutneutron-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.infonotificationsneutron-server (Neutron base controller)ceilometer or any listener

RPC methods

MethodDirectionModeTopic / serverVersionArgumentsResponseDefined at
create_firewall_groupserver → agentscast fanoutfirewall_agentprepared 1.0 (legacy dicts)firewall_group (dict), host—neutron_fwaas/services/firewall/service_drivers/agents/agents.py:149
update_firewall_groupserver → agentscast fanoutfirewall_agent1.0firewall_group, host—neutron_fwaas/services/firewall/service_drivers/agents/agents.py:153
delete_firewall_groupserver → agentscast fanoutfirewall_agent1.0firewall_group, host—neutron_fwaas/services/firewall/service_drivers/agents/agents.py:157 (no caller)
set_firewall_group_statusagent → servercallq-firewall-plugin1.1fwg_id, status, host, rpc_versionboolneutron_fwaas/services/firewall/service_drivers/agents/agents.py:51
firewall_group_deletedagent → servercallq-firewall-plugin1.1fwg_id, host, rpc_versionboolneutron_fwaas/services/firewall/service_drivers/agents/agents.py:71
get_firewall_groups_for_projectL3 agent → servercallq-firewall-plugin1.1host, 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_groupsL3 agent → servercallq-firewall-plugin1.1host, rpc_versionlist of project idsneutron_fwaas/services/firewall/service_drivers/agents/agents.py:106
get_firewall_group_for_portL2 agent → servercallq-firewall-plugin1.1port_id, host, rpc_versiongroup or nullneutron_fwaas/services/firewall/service_drivers/agents/agents.py:115
set_firewall_status, firewall_deletedagent → servercallq-firewall-plugin—FWaaS v1no 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 / eventPublished byConsumed by
firewall_group AFTER_CREATE / AFTER_UPDATE / AFTER_DELETEneutron_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:183FWaaS 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:271nobody in neutron-fwaas
firewall_rule AFTER_CREATE / AFTER_UPDATE / AFTER_DELETEneutron_fwaas/services/firewall/service_drivers/driver_api.py:331nobody in neutron-fwaas
port AFTER_UPDATENeutron coreneutron_fwaas/services/firewall/fwaas_plugin_v2.py:257, neutron_fwaas/services/logapi/common/port_callback.py:26

Core plugin (in-process Python)

CallWhenDefined at
core_plugin.get_port(context, port_id)validation of each port of a group (twice for compute ports), and port details for agent payloadsneutron_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'].objfirst OVN accessneutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:61
ServiceTypeManager.add_provider_configuration, service_base.load_driversplugin startneutron_fwaas/services/firewall/fwaas_plugin_v2.py:52

Binaries and kernel (agent driver)

WhatHowWhereDefined at
iptables / ip6tablesNeutron IptablesManager of the router namespace (iptables-save/restore, rootwrap)L3 agentneutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:190
conntrackip netns exec <ns> conntrack -D [filters] through privsep (privsep_exec=True), driver conntrackL3 agentneutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/legacy_conntrack.py:65
conntrack over netlinklibnetfilter_conntrack through ctypes in privsep, driver netlink_conntrackL3 agentneutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/netlink_conntrack.py:30
NFLOGlibnetfilter_log reader process per router namespace; packets logged to [network_log] local_output_log_baseL3 agent (fwaas_v2_log)neutron_fwaas/services/logapi/agents/drivers/iptables/log.py:225
OpenFlowbr-int flows in tables 64–69 (OVS L2 driver)OVS agentneutron_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.