Skip to content

NSX: add native route-based Site-to-Site VPN support - #13766

Open
Dogface2k wants to merge 4 commits into
apache:mainfrom
Dogface2k:feature/nsx-native-site-to-site-vpn
Open

NSX: add native route-based Site-to-Site VPN support#13766
Dogface2k wants to merge 4 commits into
apache:mainfrom
Dogface2k:feature/nsx-native-site-to-site-vpn

Conversation

@Dogface2k

@Dogface2k Dogface2k commented Aug 1, 2026

Copy link
Copy Markdown

Description

This PR implements native, route-based Site-to-Site VPN for NSX NAT-mode VPCs. The VPN terminates on the VPC's NSX Tier-1 gateway; the CloudStack virtual router remains outside the IPsec data path.

The implementation makes the existing built-in Vpn/Nsx service mapping functional and supersedes the capability-removal approach in the closed #13765. It does not change seeded offerings or the database schema.

The main lifecycle is provider-dispatched through Site2SiteVpnServiceProvider instead of adding another provider-specific branch to Site2SiteVpnManagerImpl:

  • gateway creation validates or acquires a dedicated public IP, records whether NSX owns that allocation, and creates a deterministic Tier-1 IPSecVpnService and local endpoint;
  • connection start validates CloudStack crypto policy, creates NSX IKE/tunnel/DPD profiles, a RouteBasedIPSecVpnSession, and a VTI named default-tunnel-interface;
  • each remote CIDR receives a Tier-1 static route and a NO_SNAT rule so traffic is not rewritten by the VPC's catch-all source NAT before entering the tunnel;
  • stop disables the session and removes connection routes/NAT exemptions; restart recreates them; permanent deletion also removes the session and profiles;
  • gateway and VPC teardown remove VPN objects before the Tier-1 gateway is deleted;
  • a scheduled status poll maps NSX detailed session status back to CloudStack connection state without allowing a transient query failure to corrupt the last known state.

The public endpoint is intentionally separate from the VPC source-NAT address. NSX requires the IPsec local endpoint to differ from the Tier-1 uplink address. Auto-acquired endpoint addresses are released on gateway deletion; operator-supplied addresses are detached but not released.

Compatibility and failure handling are explicit:

  • the new provider SPI methods have defaults, so existing virtual-router and Netris implementations retain their behavior;
  • legacy gateway rows without an ownership marker retain the historical virtual-router fallback;
  • persisted provider ownership prevents an offering change from redirecting an existing gateway to a different implementation;
  • gateway creation refuses to adopt a pre-existing deterministic NSX service and reports ambiguous rollback state instead of silently releasing an address that may already be active;
  • Tier-1 VPN mutations share a per-gateway lock with Tier-1 teardown;
  • PSKs are excluded from CloudStack command logging while remaining present on the agent wire payload;
  • unsupported algorithms, invalid lifetimes, oversized PSKs, missing VPC/gateway rows, and deterministic VTI collisions fail with specific errors.

The Tier-0 must redistribute TIER1_IPSEC_LOCAL_ENDPOINT routes for the dedicated gateway address to be externally reachable.

Related context: #13764 and the superseded closed PR #13765.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

Screenshots (if appropriate):

Not applicable.

How Has This Been Tested?

The exact upstream-clean commit was built and tested from current main at 4f117071c9397b1e4714c8fb61c384883c872842 with Java 17:

NSX plugin suite:
Tests run: 179, Failures: 0, Errors: 0, Skipped: 0

Full server suite:
Tests run: 3625, Failures: 0, Errors: 0, Skipped: 9

Site2SiteVpnManagerImplTest (included in the server total):
Tests run: 51, Failures: 0, Errors: 0, Skipped: 0

The deployed 4.22.1.0 focused NSX run after the lifecycle hardening reported 206/206 tests passing, including testAddVpnConnectionRoutesRetriesMarkedForDeletion. This is an additional backport result; the local checkout used for review has no JDK/Maven executable, so repository CI remains the authoritative build check.\n\nThe changed api, server, and NSX plugin modules also passed:

Checkstyle: 0 violations in all three modules
Apache RAT: BUILD SUCCESS for all three modules
Packaging: BUILD SUCCESS for all three modules
git diff --check: clean

The NSX suite covers all six management-server/agent command paths, including a real CloudStack Request/Gson round trip proving that the PSK remains on the wire while @LogLevel(Off) excludes it from log serialization.

An earlier 4.22.1 backport/prototype was exercised against NSX 4.2.4. Live validation exposed five integration defects that are addressed in this branch: virtual-router command leakage, the virtual-router monitor overwriting NSX state, NSX marked-for-deletion races, idle tunnels being removed by periodic DPD, and catch-all SNAT rewriting VPN traffic.

Post-deploy live control-plane validation was run on 2026-08-02 against an isolated test VPC on 4.22.1.0/NSX 4.2.4: a fresh NSX VPN gateway, customer gateway, and connection were created, then reset immediately; the reset job succeeded. The disposable connection, customer gateway, VPN gateway, and VPC were then deleted successfully. Production connection 8 remained Connected before and after the run. This validates the management-plane lifecycle only: no tunnel-traffic success is claimed from the intentionally unreachable RFC 5737 peer, and no production connection was modified. The earlier marked-for-deletion 431 occurred on a pre-deploy build and is not presented as a current-build failure. Full peer traffic and Tier-0 endpoint-route redistribution remain environment-dependent validation prerequisites.

How did you try to break this feature and the system with this change?

The negative and compatibility coverage exercises the failure boundaries rather than only the happy path:

  • requested endpoint IPs are rejected if they are outside the VPC, inactive, source-NAT/system addresses, or already used by static NAT, firewall, port-forwarding, or load-balancing rules;
  • auto-acquired and operator-supplied endpoint addresses have different, verified release behavior;
  • partial and ambiguous gateway creation failures preserve enough ownership state for safe operator recovery and never adopt an unrelated deterministic NSX service;
  • connection creation rolls back partial sessions, routes, and NAT exemptions;
  • changed peer CIDR lists do not strand old routes or NO_SNAT rules during deletion;
  • deterministic VTI address collisions are detected against live Tier-1 sessions and fail closed;
  • invalid/unsupported cipher, hash, DH group, IKE version, lifetime, and PSK values are rejected before an NSX mutation;
  • stop, restart, reset, permanent connection deletion, gateway deletion, missing-row cleanup, and full VPC teardown are separately covered;
  • source-NAT rule demotion preserves the complete original rule and sequence, and the exact sequence is restored when the final VPN exemption is removed;
  • operator-created VPN services on the Tier-1 are not swept during CloudStack teardown;
  • provider ownership remains stable if an offering mapping changes after gateway creation;
  • legacy virtual-router VPN selection and command generation remain covered, while NSX-owned connections are excluded from virtual-router commands and monitoring;
  • repeated status-query failures alert without forcing a false state transition, and successful UP, DOWN, degraded, and not-found results map to the intended CloudStack states;
  • missing VPC or gateway state fails start/stop explicitly instead of reporting a false success, while permanent deletion remains idempotent for cleanup.

No generic CloudStack command proxy, broad exception suppression, schema migration, retry of an ambiguous CloudStack mutation, or unrelated UI/CKS/network-offering change is included.

@Dogface2k

Copy link
Copy Markdown
Author

Follow-up validation and lifecycle hardening

Commit 98cd4ed6f4 adds the lifecycle hardening identified by live NSX validation.

  • The existing marked-for-deletion retry is now shared by route-based session creation and the complete static-route/NO_SNAT programming operation. It remains bounded at 24 attempts with a 15-second interval.
  • NsxApiClientTest.testAddVpnConnectionRoutesRetriesMarkedForDeletion forces the NSX marked-for-deletion response during route creation and verifies the deterministic route is retried before NO_SNAT programming continues.
  • State changes use a read-modify-write of the existing route-based session, preserving the mandatory tunnel_interfaces field implicated by the pre-deploy failure.
  • VPN connection, gateway, customer-gateway, and VPC lifecycle operations use VPC-scoped synchronization and deterministic cleanup.

Live validation on 4.22.1.0 with NSX 4.2.4 used an intentionally unreachable RFC 5737 test peer. The connection reached Disconnected; the first reset reproduced the marked-for-deletion 431, the same reset succeeded after the purge window, and connection/customer-gateway/VPN-gateway/VPC deletion completed with the dedicated test IP released. No production connection was modified, and no tunnel-traffic success is claimed from this test peer.

The supplied pre-deploy tunnel_interfaces error is a distinct historical failure; the current read-modify-write path passed the stop half of the live reset test. The local checkout has no JDK/Maven executable, so the new focused test must be run by repository CI; git diff --check is clean.

@Dogface2k
Dogface2k marked this pull request as ready for review August 2, 2026 05:20
@Dogface2k

Dogface2k commented Aug 2, 2026

Copy link
Copy Markdown
Author

Additional live data-plane evidence

Operator-side testing between 192.168.46.5 and 10.35.0.1 succeeded in both directions over the documented FortiGate/NSX path. The original direction returned 2/2 ICMP replies with 0% loss and 17.45 ms average latency; traceroute showed 6x.xxx.xxx.xxx (FortiGate) followed by 10.35.0.1.

CloudStack connection 8 remained Connected before and after. This is positive bidirectional endpoint-reachability evidence through the VPN path.

@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.61172% with 753 lines in your changes missing coverage. Please review.
✅ Project coverage is 19.80%. Comparing base (4f11707) to head (4cea137).

Files with missing lines Patch % Lines
...va/org/apache/cloudstack/service/NsxApiClient.java 56.24% 321 Missing and 26 partials ⚠️
...com/cloud/network/vpn/Site2SiteVpnManagerImpl.java 61.57% 56 Missing and 27 partials ⚠️
.../org/apache/cloudstack/service/NsxServiceImpl.java 48.10% 65 Missing and 17 partials ⚠️
...java/org/apache/cloudstack/service/NsxElement.java 68.98% 45 Missing and 22 partials ⚠️
...va/org/apache/cloudstack/resource/NsxResource.java 80.35% 32 Missing and 1 partial ⚠️
...stack/agent/api/CreateNsxVpnConnectionCommand.java 77.08% 21 Missing and 1 partial ⚠️
.../agent/api/UpdateNsxVpnConnectionStateCommand.java 46.87% 16 Missing and 1 partial ⚠️
...stack/agent/api/DeleteNsxVpnConnectionCommand.java 40.74% 15 Missing and 1 partial ⚠️
...stack/agent/api/GetNsxVpnSessionStatusCommand.java 40.74% 15 Missing and 1 partial ⚠️
...oudstack/agent/api/DeleteNsxVpnGatewayCommand.java 31.81% 14 Missing and 1 partial ⚠️
... and 6 more
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #13766      +/-   ##
============================================
+ Coverage     19.65%   19.80%   +0.15%     
- Complexity    19792    20051     +259     
============================================
  Files          6368     6377       +9     
  Lines        574881   576796    +1915     
  Branches      70351    70563     +212     
============================================
+ Hits         112970   114247    +1277     
- Misses       449639   450155     +516     
- Partials      12272    12394     +122     
Flag Coverage Δ
uitests 3.41% <ø> (ø)
unittests 21.09% <62.61%> (+0.16%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds native, route-based Site-to-Site VPN support for NSX NAT-mode VPCs by terminating IPsec on the NSX Tier-1 gateway (keeping the CloudStack virtual router out of the data path) and by making the existing Vpn/Nsx service mapping functional via provider-dispatched lifecycle handling.

Changes:

  • Extends the Site-to-Site VPN provider SPI to support provider-owned gateway IP acquisition/release and persisted ownership-based provider selection.
  • Implements NSX VPN gateway/connection lifecycle (gateway creation, route/NAT exemptions, session state updates, and teardown) plus a scheduled management-server poller mapping NSX session status back to CloudStack state.
  • Adds extensive unit tests across api, server, and NSX plugin modules to cover lifecycle hardening, rollback/ambiguity handling, and sensitive-data logging behavior.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
server/src/test/java/com/cloud/network/vpn/Site2SiteVpnManagerImplTest.java Expands S2S manager tests for provider selection/ownership, locking, and lifecycle error handling.
server/src/test/java/com/cloud/network/vpn/RemoteAccessVpnManagerImplTest.java Adds tests ensuring Remote Access VPN provider mapping is validated before persistence/start.
server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java Routes S2S lifecycle through the owning provider, supports provider-owned gateway IPs, and hardens connection locking/state transitions.
server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java Selects a single mapped Remote Access VPN provider and fails fast when none is available.
plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/utils/NsxVpnCryptoUtilsTest.java Adds unit tests for CloudStack-to-NSX VPN crypto policy mapping/validation.
plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/utils/NsxHelperTest.java Adds tests for deterministic VTI /30 address derivation.
plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/utils/NsxControllerUtilsTest.java Tests failed-answer behavior for “throwing” vs “returning” NSX command helpers.
plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxServiceImplTest.java Adds tests for status polling behavior and VPN gateway failure result propagation.
plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxElementTest.java Adds tests for NSX gateway IP acquisition/release semantics and S2S VPN lifecycle dispatch.
plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxApiClientTest.java Adds extensive tests for NSX Policy object ordering, rollback, retries, and cleanup behavior.
plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/resource/NsxResourceTest.java Adds tests validating agent-side command dispatch, rollback semantics, and PSK log redaction on the wire/loggers.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxVpnCryptoUtils.java Implements crypto parameter mapping/validation for NSX constraints.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxHelper.java Adds deterministic VTI address allocation helper for NSX route-based VPN sessions.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxControllerUtils.java Adds VPN naming helpers and splits “throw on failure” vs “return failed answer” command sending.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxServiceImpl.java Adds NSX VPN lifecycle command dispatch and a scheduled poller to sync NSX session state to CloudStack.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java Implements Site2SiteVpnServiceProvider for NSX, including gateway IP ownership markers and NSX-side lifecycle operations.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxResource.java Adds agent handling for NSX VPN commands and introduces per-Tier1 locking for VPN mutations/teardown ordering.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/NsxAnswer.java Extends NSX answer payload with structured “endpoint may be in use” failure signaling.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/UpdateNsxVpnConnectionStateCommand.java New agent command for enabling/disabling an NSX VPN session.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/GetNsxVpnSessionStatusCommand.java New agent command for fetching NSX session status.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/DeleteNsxVpnGatewayCommand.java New agent command for deleting an NSX VPN service on Tier-1.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/DeleteNsxVpnConnectionCommand.java New agent command for deleting an NSX VPN session.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/CreateNsxVpnGatewayCommand.java New agent command for creating an NSX VPN service/local endpoint on Tier-1.
plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/CreateNsxVpnConnectionCommand.java New agent command for creating a route-based VPN session, VTI, routes, and NAT exemptions (with PSK log redaction).
api/src/test/java/org/apache/cloudstack/api/command/user/vpn/VpnConnectionLifecycleCmdTest.java Adds tests ensuring VPN reset/delete commands synchronize on VPC.
api/src/main/java/org/apache/cloudstack/api/command/user/vpn/ResetVpnConnectionCmd.java Synchronizes reset jobs on VPC to serialize Tier-1/VPN mutations.
api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnGatewayCmd.java Synchronizes gateway deletion jobs on VPC.
api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnConnectionCmd.java Synchronizes connection deletion jobs on VPC.
api/src/main/java/com/cloud/network/nsx/NsxVpnGatewayResult.java New API DTO for structured NSX gateway creation outcomes.
api/src/main/java/com/cloud/network/nsx/NsxService.java Extends NSX service interface with VPN gateway/connection lifecycle operations.
api/src/main/java/com/cloud/network/element/Site2SiteVpnServiceProvider.java Extends provider SPI with gateway IP acquisition/release, ownership, and customer-gateway validation hooks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@DaanHoogland

Copy link
Copy Markdown
Contributor

@Dogface2k , want to address the co-pilot comments ?

@Dogface2k

Copy link
Copy Markdown
Author

@DaanHoogland Addressed both Copilot findings in 4cea137: the poller now uses a state-filtered DAO query, and its executor is restart-safe across start/stop/start. Added focused DAO, lifecycle, and status-mapping tests; the complete affected suites pass (engine/schema 385/385 and NSX plugin 202/202), with checkstyle clean. Both review threads are now resolved.

@DaanHoogland
DaanHoogland requested a review from Copilot August 3, 2026 07:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.

Suppressed comments (5)

server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java:1

  • Connection deletion/reset now depends on getVpnGatewayForConnection(), which throws when the gateway row is missing. That makes delete/cleanup non-idempotent: a partially-deleted environment (missing gateway/VPC) will prevent connection teardown and can strand the DB row/lock-based workflows. Consider making the delete path tolerant to a missing gateway (e.g., in stopVpnConnectionLocked when deleting==true, skip provider-side stop/delete if the gateway is absent, proceed with DB removal; or make getVpnGatewayForConnection optionally return null and handle it in delete/stop flows).
    server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java:1
  • Connection deletion/reset now depends on getVpnGatewayForConnection(), which throws when the gateway row is missing. That makes delete/cleanup non-idempotent: a partially-deleted environment (missing gateway/VPC) will prevent connection teardown and can strand the DB row/lock-based workflows. Consider making the delete path tolerant to a missing gateway (e.g., in stopVpnConnectionLocked when deleting==true, skip provider-side stop/delete if the gateway is absent, proceed with DB removal; or make getVpnGatewayForConnection optionally return null and handle it in delete/stop flows).
    server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java:1
  • These validation methods were widened from private to package-visible to support testing. This increases the callable surface area of internal validation logic and makes it easier for other production classes in the package to start depending on them. If possible, keep them private and test via the public entrypoints; alternatively, clearly document they are package-visible only for tests (and keep tests in the same package) to reduce the risk of accidental reuse.
    server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java:1
  • These validation methods were widened from private to package-visible to support testing. This increases the callable surface area of internal validation logic and makes it easier for other production classes in the package to start depending on them. If possible, keep them private and test via the public entrypoints; alternatively, clearly document they are package-visible only for tests (and keep tests in the same package) to reduce the risk of accidental reuse.
    plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxControllerUtils.java:58
  • When NSX returns a failed (but non-null) NsxAnswer, the log and exception discard useful troubleshooting context (command type, zone, and the answer details/error). Include cmd.getClass()/cmd.toString(), zoneId, and answer.getDetails() (and any structured flags like endpointMayBeInUse/objectExists) in the log message and/or exception text so operators can diagnose failures without enabling deeper debug logging.
        NsxAnswer answer = sendNsxCommandForResult(cmd, zoneId);
        if (!answer.getResult()) {
            logger.error("NSX API Command failed");
            throw new InvalidParameterValueException("Failed API call to NSX controller");
        }

Comment on lines +1129 to +1136
private void releaseAutoAcquiredVpnGatewayIp(IPAddressVO ip) {
boolean disassociated = ipAddressManager.disassociatePublicIpAddress(ip, CallContext.current().getCallingUserId(),
CallContext.current().getCallingAccount());
if (!disassociated) {
throw new CloudRuntimeException(String.format("Failed to disassociate auto-acquired VPN gateway IP %s", ip.getAddress()));
}
userIpAddressDetailsDao.removeDetail(ip.getId(), NSX_VPN_GATEWAY_IP_DETAIL);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Dogface2k , I’ve seen this feedback from co-pilot before . I don’t have an assessment of its value. But as a generic comment it makes sense. The question you should ask is, “would any reconnect or restart have this call as an asynchronous result. If so, take the comment serious.

@Dogface2k Dogface2k Aug 3, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DaanHoogland Thanks I traced that specific possibility. Reset, reconnect, restart and stop operate only on the VPN connection/session and never call releaseVpnGatewayIp. The release path is reached only during gateway-creation rollback, explicit gateway deletion, or VPC teardown. The background VPC teardown runs as a ManagedContextRunnable using the system user/account, and CallContext.current() already registers a system context when none exists. API-triggered deletion retains its propagated caller context. The ownership marker is also removed only after disassociation succeeds, so a failed release remains recoverable. I therefore don't think an additional fallback or provider-SPI signature change is warranted here. (false positive)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants