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

Reverse-spec · Services

neutron-vpnaas — Summary

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-lib stable/2026.2 @ 1ee72052251a (API definitions, VPN exceptions); neutron: neutron stable/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-vpnaas plugin, IPsecOvnVPNDriver, neutron-ovn-vpn-agent; the OpenKeel target) and legacy (vpnaas plugin, 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.
FileContent
01-rest-api_stable-2026.2.md29 operations, functional and technical sheets, lifecycles
02-errors_stable-2026.2.mdexception catalogue with situations, asynchronous errors, logs
03-interactions_stable-2026.2.mddatabase, RabbitMQ RPC with frames, OVN SB/OVSDB, Neutron plugins, host commands and files
04-scenarios_stable-2026.2.md6 sequence diagrams (OVN create, legacy create, status polling, OVN failover, OVN delete, OVN agent start/heartbeat)
05-async-processing_stable-2026.2.md13 triggers outside the API
07-rewrite-pack_stable-2026.2.mdrewrite pack: contracts in _rewrite/, algorithms, client expectations, strategy, checklist
_verification_stable-2026.2.mdevery 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| peer

Processes

ProcessEntry pointRoleListens 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 objectsHTTP; 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 RPCRPC 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 reschedulingtimer, NB lock
neutron-ovn-vpn-agentneutron_vpnaas.cmd.ovn_agent:main (pyproject.toml:49, neutron_vpnaas/services/vpn/ovn_agent.py:65-72)OVN data plane: namespaces, ports, strongSwan/LibreswanRPC topic ipsec_agent.<host>; SB SB_Global, own Chassis_Private; timer report_status
neutron-l3-agent + vpnaas extensionneutron.agent.l3.extensions vpnaas → neutron_vpnaas/services/vpn/agent.py:108 (pyproject.toml:51-52)legacy data plane in router namespacesRPC topic ipsec_agent.<host>; L3 agent router events; timer
neutron-vpn-netns-wrapperneutron_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-mountedCLI (called by the agents through rootwrap)

External dependencies

SystemProtocolLibraryConfigurationUsage
MariaDB (Neutron DB)SQLoslo.db / SQLAlchemyNeutron [database]9 VPN tables + Neutron tables
RabbitMQAMQP RPC + notificationsoslo.messaging via neutron-libtransport_url, rpc_response_timeoutserver ↔ agents
OVN SouthboundOVSDB 6642ovsdbapp[ovn] ovn_sb_connection + SSLagent registration/heartbeat, server events
OVN NorthboundOVSDB 6641via ML2/OVNNeutron [ovn]ports, routes (indirect); liveness ping
Local Open vSwitchOVSDBovsdbapp[ovs] ovsdb_connectionchassis name, port plugging
strongSwan / Libreswanipsec CLIsubprocess via rootwrap[vpnagent] vpn_device_driver, [strongswan], [libreswan], [pluto], [ipsec]IKE/IPsec daemon
DNSgetaddrinfoPythonsystem resolverFQDN 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 // 2 s), 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

  1. **vpn-aes-ctr is advertised but CTR algorithms are rejected** (400 aes-128-ctr is not in valid_values, observed on both profiles): vpn-no-sha1-3des replaces the encryption list after vpn-aes-ctr and its list is derived from the base list without CTR (01 § Effective attribute validation, 07 § 3.1).
  2. **OVN accepts ah-esp IPsec 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 render ah-esp.
  3. OVN: the router gateway can be cleared while VPN services use it (observed 200): check_router_in_use is overridden with a no-op (neutron_vpnaas/services/vpn/ovn_plugin.py:53-54); legacy returns 409 RouterInUse.
  4. **OVN: a failed POST /vpn/vpnservices leaves the service in the database with status ERROR** while returning 409 (no VPN agent) or another error (observed); clients must delete it. A VPN gateway in ERROR blocks the router for any new service (VPNGatewayInError) and no API can repair it.
  5. 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.
  6. PSK in clear in the database, in every GET response (is_visible: true), as a filter, and in RPC payloads.
  7. Updates do not change status and are refused while pending: a fresh VPN service without connection stays PENDING_CREATE and cannot even be renamed (observed); a connection PUT keeps ACTIVE/DOWN until the next agent report.
  8. No periodic resync on the agents: a lost vpnservice_updated cast is only repaired by another change, an agent restart or (OVN) an SB reconnection; a lost update_status is not repeated because the agent updates its cache before the call.
  9. OVN failover is off by default (allow_automatic_vpnagent_failover = false); with the default vpn_auto_schedule = true, every unhosted router goes to whichever agent syncs first, ignoring load.
  10. Synchronous DNS for FQDN peers in the API request, resolved once (DNS changes ignored).
  11. EndpointGroupInUse is a 400 while every other "in use" error is a 409; MissingEndpointForEndpointGroup embeds the whole request body in its message.
  12. ipsecpeercidrs.cidr is VARCHAR(32), too short for long IPv6 CIDRs [inferred: DB error → 500].
  13. api-ref (neutron-lib) still documents sha1/3des and 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; ipsec output 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-existent router.vpn_agents), the vpnaas.VPNStateInvalidToUpdate branch of set_vpnservice_status.

Source: reverse-spec-neutron-vpnaas/00-summary_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.