Reverse-specification of neutron-vpnaas — branch stable/2026.2
- Repository: https://opendev.org/openstack/neutron-vpnaas (local clone
neutron-vpnaas/) — specified branch:stable/2026.2@6ce5da102cf1(29.0.0-5-g6ce5da102, 2026-10-15) — analysed on: 2026-10-29 - Related repositories cited:
neutron-lib:neutron-libstable/2026.2@1ee72052251a(API definitions, VPN exceptions);neutron:neutronstable/2026.2@eef1a0f7ee36(API framework, ML2/OVN agent cache and maintenance);openstacksdk:stable/2026.2@fbdc40381(client expectations). - Scope: the whole project, both deployment profiles — OVN (
ovn-vpnaasplugin,IPsecOvnVPNDriver,neutron-ovn-vpn-agent; the OpenKeel target) and legacy (vpnaasplugin,IPsecVPNDriver, L3 agent extension) — REST API, RPC between service drivers and agents, OVSDB events, device drivers (strongSwan, Libreswan), periodic status reporting, dead-agent rescheduling, errors, database. Rewrite pack (step 8) included; change history (step 7) not requested.
| File | Content |
|---|---|
| 01-rest-api_stable-2026.2.md | 29 operations, functional and technical sheets, lifecycles |
| 02-errors_stable-2026.2.md | exception catalogue with situations, asynchronous errors, logs |
| 03-interactions_stable-2026.2.md | database, RabbitMQ RPC with frames, OVN SB/OVSDB, Neutron plugins, host commands and files |
| 04-scenarios_stable-2026.2.md | 6 sequence diagrams (OVN create, legacy create, status polling, OVN failover, OVN delete, OVN agent start/heartbeat) |
| 05-async-processing_stable-2026.2.md | 13 triggers outside the API |
| 07-rewrite-pack_stable-2026.2.md | rewrite pack: contracts in _rewrite/, algorithms, client expectations, strategy, checklist |
| _verification_stable-2026.2.md | every file:line reference mapped to its enclosing function |
Overview
neutron-vpnaas adds site-to-site IPsec VPN (pre-shared keys) to Neutron. It is not a separate API service: it is a Neutron service plugin loaded by neutron-server, which exposes five REST resources under /v2.0/vpn (IKE policies, IPsec policies, endpoint groups, VPN services, IPsec site connections) whose attribute maps are defined in neutron-lib. The API only stores intent in the Neutron database with status PENDING_CREATE; a service driver then notifies the agent hosting the router over RabbitMQ (vpnservice_updated), the agent pulls the full configuration (get_vpn_services_on_host), renders strongSwan or Libreswan configuration, runs the ipsec daemon inside a network namespace, and reports tunnel statuses back every 60 s (update_status). On the legacy path the agent is the Neutron L3 agent (extension vpnaas) and the daemon runs in the router namespace; on the OVN path a dedicated neutron-ovn-vpn-agent runs the daemon in a qvpn-<router> namespace connected to the router through a per-router transit network (169.254.0.0/28), with its own public IP on the external network, and registers itself in the OVN Southbound database so that Neutron sees it as an agent. On OVN, the server also schedules routers to VPN agents and, if enabled, moves them away from dead agents.
Architecture
flowchart LR
client[Client / OSC / Horizon] -->|HTTPS REST /v2.0/vpn| api[neutron-server<br/>VPN service plugin + service driver]
api -->|SQL| db[(MariaDB<br/>Neutron DB + 9 VPN tables)]
api -->|in-process| core[Neutron core / L3 plugins<br/>ML2/OVN]
core -->|OVSDB| nb[(OVN NB)]
api -->|OVSDB monitor Chassis_Private| sb[(OVN SB)]
api -->|AMQP cast vpnservice_updated<br/>call vpnservice_added_to_agent| mq{{RabbitMQ<br/>ipsec_agent.host / ipsec_driver}}
mq --> ovnagent[neutron-ovn-vpn-agent<br/>qvpn-router + strongSwan]
mq --> l3agent[neutron-l3-agent + vpnaas extension<br/>qrouter-router + strongSwan/Libreswan]
ovnagent -->|AMQP call get_vpn_services_on_host,<br/>get_vpn_transit_network_details, update_status| mq
l3agent -->|AMQP call get_vpn_services_on_host, update_status| mq
ovnagent -->|OVSDB Chassis_Private external_ids heartbeat| sb
ovnagent -->|OVSDB system-id, veth on br-int| ovs[(local OVS)]
ovnagent -->|IKE/ESP UDP 500/4500| peer[Remote VPN gateway]
l3agent -->|IKE/ESP| peerProcesses
| Process | Entry point | Role | Listens to / consumes |
|---|---|---|---|
| neutron-server (API workers) | Neutron, plugin neutron.service_plugins vpnaas → neutron_vpnaas/services/vpn/plugin.py:56, ovn-vpnaas → neutron_vpnaas/services/vpn/ovn_plugin.py:77 (pyproject.toml:57-60) | REST API, validation, DB, notifications, OVN gateway objects | HTTP; legacy: RPC topic ipsec_driver (consumer created in each worker, neutron_vpnaas/services/vpn/service_drivers/ipsec.py:34-39); OVN: SB Chassis_Private events |
| neutron-server (RPC workers) | start_rpc_listeners (neutron_vpnaas/services/vpn/plugin.py:80-85) | OVN: agent-facing RPC | RPC topic ipsec_driver (neutron_vpnaas/services/vpn/service_drivers/ovn_ipsec.py:510-515) |
| neutron-server (OVN maintenance worker) | periodic check_vpn_agents (neutron_vpnaas/services/vpn/ovn/maintenance.py:20-35) | dead VPN agent rescheduling | timer, NB lock |
| neutron-ovn-vpn-agent | neutron_vpnaas.cmd.ovn_agent:main (pyproject.toml:49, neutron_vpnaas/services/vpn/ovn_agent.py:65-72) | OVN data plane: namespaces, ports, strongSwan/Libreswan | RPC topic ipsec_agent.<host>; SB SB_Global, own Chassis_Private; timer report_status |
neutron-l3-agent + vpnaas extension | neutron.agent.l3.extensions vpnaas → neutron_vpnaas/services/vpn/agent.py:108 (pyproject.toml:51-52) | legacy data plane in router namespaces | RPC topic ipsec_agent.<host>; L3 agent router events; timer |
| neutron-vpn-netns-wrapper | neutron_vpnaas/services/vpn/common/netns_wrapper.py:159 (pyproject.toml:48) | runs one ipsec command inside a namespace with /etc and the pid directory bind-mounted | CLI (called by the agents through rootwrap) |
External dependencies
| System | Protocol | Library | Configuration | Usage |
|---|---|---|---|---|
| MariaDB (Neutron DB) | SQL | oslo.db / SQLAlchemy | Neutron [database] | 9 VPN tables + Neutron tables |
| RabbitMQ | AMQP RPC + notifications | oslo.messaging via neutron-lib | transport_url, rpc_response_timeout | server ↔ agents |
| OVN Southbound | OVSDB 6642 | ovsdbapp | [ovn] ovn_sb_connection + SSL | agent registration/heartbeat, server events |
| OVN Northbound | OVSDB 6641 | via ML2/OVN | Neutron [ovn] | ports, routes (indirect); liveness ping |
| Local Open vSwitch | OVSDB | ovsdbapp | [ovs] ovsdb_connection | chassis name, port plugging |
| strongSwan / Libreswan | ipsec CLI | subprocess via rootwrap | [vpnagent] vpn_device_driver, [strongswan], [libreswan], [pluto], [ipsec] | IKE/IPsec daemon |
| DNS | getaddrinfo | Python | system resolver | FQDN peer_address |
Key figures
- REST: 25 VPN operations (5 resources × CRUD) + 4 scheduler operations (OVN only); 6 more scheduler routes generated without implementation (500); 20 shadowed duplicate routes.
- Non-HTTP triggers: 8 RPC methods (3 agent→server calls common to both profiles, 2 more on OVN; 3 server→agent messages), 2 timers (agent status every 60 s, server failover every
agent_down_time // 2s), 3 OVSDB events, 4 L3-agent hooks, 7 Neutron callback subscriptions. - Errors: 35 neutron-lib VPN exception classes (3 of them not raised: neutron-vpnaas raises its own copies of
IpsecValidationFailure,FlavorsPluginNotLoaded,NoProviderFoundForFlavor), 14 classes defined locally, plus Neutron/neutron-lib exceptions reused (RouterInUse, ServicePortInUse, SubnetInUse, flavor errors); 49 distinct error responses observed. - Database: 9 tables; models identical to migrations (heads
e50641731f1a,842256a43ce2). - Policies: 20 rules; none for the scheduler API.
- Evidence: 193 HTTP exchanges and 31 VPN RPC frames recorded from the real server (
_rewrite/exchanges-*).
Points of attention
- **
vpn-aes-ctris advertised but CTR algorithms are rejected** (400aes-128-ctr is not in valid_values, observed on both profiles):vpn-no-sha1-3desreplaces the encryption list aftervpn-aes-ctrand its list is derived from the base list without CTR (01 § Effective attribute validation, 07 § 3.1). - **OVN accepts
ah-espIPsec policies in connections** (201, observed) while the legacy driver refuses them (400): the OVN driver uses the no-op base validator (neutron_vpnaas/services/vpn/service_drivers/__init__.py:31-35). The strongSwan template cannot renderah-esp. - OVN: the router gateway can be cleared while VPN services use it (observed 200):
check_router_in_useis overridden with a no-op (neutron_vpnaas/services/vpn/ovn_plugin.py:53-54); legacy returns 409RouterInUse. - **OVN: a failed
POST /vpn/vpnservicesleaves the service in the database with statusERROR** while returning 409 (no VPN agent) or another error (observed); clients must delete it. A VPN gateway inERRORblocks the router for any new service (VPNGatewayInError) and no API can repair it. - Scheduler API: non-admins get **500
PolicyCheckError** instead of 403 (no policy rules registered, observed); six routes without controller methods answer 500; re-adding the same binding returns 201. - PSK in clear in the database, in every
GETresponse (is_visible: true), as a filter, and in RPC payloads. - Updates do not change status and are refused while pending: a fresh VPN service without connection stays
PENDING_CREATEand cannot even be renamed (observed); a connectionPUTkeepsACTIVE/DOWNuntil the next agent report. - No periodic resync on the agents: a lost
vpnservice_updatedcast is only repaired by another change, an agent restart or (OVN) an SB reconnection; a lostupdate_statusis not repeated because the agent updates its cache before the call. - OVN failover is off by default (
allow_automatic_vpnagent_failover = false); with the defaultvpn_auto_schedule = true, every unhosted router goes to whichever agent syncs first, ignoring load. - Synchronous DNS for FQDN peers in the API request, resolved once (DNS changes ignored).
EndpointGroupInUseis a 400 while every other "in use" error is a 409;MissingEndpointForEndpointGroupembeds the whole request body in its message.ipsecpeercidrs.cidrisVARCHAR(32), too short for long IPv6 CIDRs [inferred: DB error → 500].- api-ref (neutron-lib) still documents
sha1/3desand omits several statuses and the scheduler API.
Blind spots / [inferred]
- The agents' data plane was not executed (no Linux namespaces on the analysis host): command lines, namespace layout and status parsing come from the code;
ipsecoutput samples are inferred from the regular expressions. - The OVN profile ran the OVN service plugin/driver on an ML2/openvswitch core: OVN-specific liveness (
AgentCache,nb_cfg) and the NB translation of ports/routes come from reading Neutron's code, not from execution. - RabbitMQ envelopes are reconstructed (messages were intercepted at the oslo.messaging client); payloads are real.
- Concurrency paths (duplicate gateway creation, concurrent scheduling, retries) and timeouts were not exercised.
- Neutron's base controller (attribute validation, pagination, bulk) is described from its observed behaviour, not re-specified; see the Neutron reverse-spec (
reverse-spec-neutron/). - Dead code identified:
RouterHasVPNExternal, Libreswan_get_nexthop/_resolve_fqdn,agent_updated(no agent handler),get_router_availability_zones(uses a non-existentrouter.vpn_agents), thevpnaas.VPNStateInvalidToUpdatebranch ofset_vpnservice_status.
Source: reverse-spec-neutron-vpnaas/00-summary_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.