Skip to content

Add AmneziaWG cable driver#4097

Open
sanek9 wants to merge 1 commit into
submariner-io:develfrom
sanek9:feat/amneziawg-cable-driver
Open

Add AmneziaWG cable driver#4097
sanek9 wants to merge 1 commit into
submariner-io:develfrom
sanek9:feat/amneziawg-cable-driver

Conversation

@sanek9

@sanek9 sanek9 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What this PR does / why we need it

Adds an AmneziaWG cable driver (WireGuard-compatible with DPI obfuscation) for environments where plain WireGuard is blocked or fingerprintable.

Also makes AmneziaWG obfuscation parameters (and peer keepalive) configurable via the gateway environment variable SUBMARINER_CABLEDRIVEROPTIONS (JSON map). When unset/empty, built-in defaults are used so older operators remain compatible.

More details

  • New package pkg/cable/amneziawg registered in the cable engine (same pattern as WireGuard).
  • Implementation embeds the MIT-licensed amneziawg-go userspace stack
    (linked into submariner-gateway; no separate binary and no AmneziaWG kernel module on nodes).
    MIT is compatible with this Apache-2.0 project.
  • Runtime needs match what the gateway DaemonSet already has: privileged + NET_ADMIN (and host network
    path as today), which is enough to create a TUN device — no extra RBAC/securityContext changes.
  • Obfuscation keys: jc, jmin, jmax, s1s4, h1h4, i1i5.
  • Other driver keys: keepalive (seconds; default 10; 0 disables).
  • Invalid JSON, unknown keys, and invalid option values fail fast. All configuration problems from a
    single parse are returned together so they can be fixed in one pass.
  • H1H4 / S1S4 must match across peers in the mesh; Jc / I* may differ per gateway.
  • Companion PRs (operator/subctl) introduce spec.cableDriverOptions and --cable-driver-option to populate the env var.

Related issues

Checklist

  • Unit tests added/updated
  • Docs (cable driver name / options) — follow-up OK if maintainers prefer website PR

Summary by CodeRabbit

  • New Features
    • Added AmneziaWG as a supported cable driver, including userspace startup, peer connect/disconnect, cleanup, and connection status/traffic monitoring.
    • Introduced environment-configurable obfuscation parameters with validation and safe defaults.
    • Added driver option parsing utilities to read and apply configuration from the environment.
    • Registered the AmneziaWG driver for automatic recognition in the cable engine.
  • Tests
    • Added comprehensive AmneziaWG driver and obfuscation test coverage, plus driver option parsing tests.
  • Chores
    • Updated Go dependencies, including WireGuard-related components.

@submariner-bot

Copy link
Copy Markdown
Contributor

🤖 Created branch: z_pr4097/sanek9/feat/amneziawg-cable-driver
🚀 Full E2E won't run until the "ready-to-test" label is applied. I will add it automatically once the PR has 2 approvals, or you can add it manually.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an AmneziaWG cable driver with userspace device management, configurable obfuscation, peer lifecycle handling, connection monitoring, driver registration, dependency updates, and mocked lifecycle tests.

Changes

AmneziaWG cable driver

Layer / File(s) Summary
Configuration and AWG client
go.mod, pkg/cable/options.go, pkg/cable/options_test.go, pkg/cable/amneziawg/client.go, pkg/cable/amneziawg/obfuscation.go, pkg/cable/amneziawg/obfuscation_test.go
Adds AWG dependencies, JSON-based driver options, an AWG client wrapper, and validated AmneziaWG obfuscation settings with tests.
Userspace daemon lifecycle
pkg/cable/amneziawg/daemon.go
Creates and closes the TUN device, UAPI listener, and embedded AmneziaWG device.
Driver registration and peer lifecycle
pkg/cable/amneziawg/driver.go, pkg/cableengine/cableengine.go
Registers the driver and implements initialization, peer connection, disconnection, verification, and cleanup.
Connection polling and status tracking
pkg/cable/amneziawg/getconnections.go
Tracks peers, evaluates handshake and traffic state, removes unknown peers, and records traffic metrics.
Driver integration tests
pkg/cable/amneziawg/amneziawg_test.go
Tests configuration, option overrides, initialization, peer operations, connection reporting, and cleanup using fake clients and netlink devices.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LocalEndpoint
  participant amneziawgDriver
  participant startUserspaceDevice
  participant AWGClient
  participant Netlink
  LocalEndpoint->>amneziawgDriver: provide endpoint specification
  amneziawgDriver->>startUserspaceDevice: create userspace interface
  amneziawgDriver->>AWGClient: configure device
  amneziawgDriver->>LocalEndpoint: store device public key
  amneziawgDriver->>Netlink: activate interface
  amneziawgDriver->>AWGClient: configure peer
Loading

Suggested labels: ready-to-test

Suggested reviewers: tpantelis, aswinsuryan, dfarrell07, oats87, skitt

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Actionable Comments Resolved ✅ Passed No unresolved TODO/FIXME/follow-up markers were found in the touched AmneziaWG files, and the new test suite covers the driver changes.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a new AmneziaWG cable driver.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sanek9

sanek9 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Part of the proposal overview: #4101

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
pkg/cable/amneziawg/obfuscation.go (1)

28-30: 🩺 Stability & Availability | 🔵 Trivial

Cross-cluster consistency risk for H1-H4/S1-S4 via per-gateway env overrides.

The comment correctly notes H1-H4/S1-S4 must be identical on every peer, but applyCableDriverObfuscation sources overrides from each gateway's own SUBMARINER_CABLEDRIVEROPTIONS independently. In a multi-cluster mesh, if different clusters/gateways set different override values, handshakes will silently fail (or behave inconsistently) across the connection with no explicit cross-cluster validation or error surfaced. Worth documenting this operational requirement prominently for operators (e.g. in the CRD/API docs for cableDriverOptions), since a mismatch here is only discoverable at runtime as connectivity failures.

Also applies to: 79-81

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cable/amneziawg/obfuscation.go` around lines 28 - 30, Document in the
CRD/API documentation for cableDriverOptions that H1-H4 and S1-S4 overrides must
be identical across every gateway and cluster in the mesh. Clearly distinguish
these shared parameters from Jc/I* values, which may differ, and warn that
inconsistent per-gateway SUBMARINER_CABLEDRIVEROPTIONS values cause runtime
connectivity failures.
pkg/cable/amneziawg/amneziawg_test.go (2)

162-181: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pre-populated fake peer has no effect and isn't verified.

The stale peer seeded on t.client.devices[amneziawg.DefaultDeviceName] (lines 163-165) is wiped out by ConfigureDevice's ReplacePeers: true handling (lines 354-356 in this same file) before the It block runs, and device.Peers is never asserted. This setup currently does nothing and could mislead a reader into thinking ReplacePeers behavior is exercised here.

Either drop the dead setup or assert on it to get real coverage of the replace-peers path.

Suggested fix
 	It("should configure the device with obfuscation parameters and public key", func() {
 		device := t.client.devices[amneziawg.DefaultDeviceName]
 		Expect(device).ToNot(BeNil())
 		Expect(device.ListenPort).To(Equal(listenPort))
 		Expect(device.PublicKey).ToNot(Equal(wgtypes.Key{}))
 		Expect(device.PrivateKey).ToNot(Equal(wgtypes.Key{}))
 		Expect(device.Jc).To(Equal(7))
 		Expect(device.H1).To(Equal("200000000-280000000"))
 		Expect(device.S3).To(Equal(35))
 		Expect(device.I1).To(Equal("<r 40>"))
+		Expect(device.Peers).To(BeEmpty(), "ReplacePeers should have cleared the stale peer")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cable/amneziawg/amneziawg_test.go` around lines 162 - 181, Remove the
pre-populated peer setup from the BeforeEach block, or update the test to
explicitly assert that ConfigureDevice replaces the seeded peer. Ensure the test
meaningfully covers ReplacePeers behavior rather than retaining an unused
device.Peers fixture.

94-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unused error-injection scaffolding leaves NewDriver error paths untested.

checkNewDriverErr (fields at lines 100, default no-op at lines 142-144) and fakeClient.configureDeviceErr (line 315) are never overridden by any test, so branches such as the PSK validation failure (w.spec.PSK == "" || w.spec.PSK == "default psk") and ConfigureDevice failure propagation in NewDriver (per driver.go snippet) go untested.

Consider adding a negative-path test using these hooks, or remove the scaffolding if it's not intended to be exercised.

Also applies to: 142-144, 313-317

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cable/amneziawg/amneziawg_test.go` around lines 94 - 101, Remove the
unused NewDriver error-injection scaffolding, including
testDriver.checkNewDriverErr and fakeClient.configureDeviceErr, unless you add
negative-path tests that override these hooks and assert PSK validation and
ConfigureDevice failures propagate through NewDriver. Ensure the resulting tests
explicitly cover those error branches if retaining the hooks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/cable/amneziawg/driver.go`:
- Around line 91-172: Register failure cleanup immediately after setupDevice
succeeds, before NewClient can fail. Extend the existing NewDriver error defer
to close w.userspaceDevice and delete w.link when err != nil, while preserving
client cleanup and nil assignment. Ensure all subsequent initialization failures
reclaim the userspace device, its goroutine/socket resources, and the netlink
link.

In `@pkg/cable/amneziawg/obfuscation.go`:
- Around line 83-132: Extend obfuscationFromOptions and setStringOption to
validate string options before assigning them: enforce the supported N-M syntax
for h1-h4 and AWG mini-syntax for i1-i5, warning and retaining defaults for
invalid values. After applying integer overrides, validate that Jmin is not
greater than Jmax; warn and ignore the invalid override(s), preserving a valid
fallback configuration.

---

Nitpick comments:
In `@pkg/cable/amneziawg/amneziawg_test.go`:
- Around line 162-181: Remove the pre-populated peer setup from the BeforeEach
block, or update the test to explicitly assert that ConfigureDevice replaces the
seeded peer. Ensure the test meaningfully covers ReplacePeers behavior rather
than retaining an unused device.Peers fixture.
- Around line 94-101: Remove the unused NewDriver error-injection scaffolding,
including testDriver.checkNewDriverErr and fakeClient.configureDeviceErr, unless
you add negative-path tests that override these hooks and assert PSK validation
and ConfigureDevice failures propagate through NewDriver. Ensure the resulting
tests explicitly cover those error branches if retaining the hooks.

In `@pkg/cable/amneziawg/obfuscation.go`:
- Around line 28-30: Document in the CRD/API documentation for
cableDriverOptions that H1-H4 and S1-S4 overrides must be identical across every
gateway and cluster in the mesh. Clearly distinguish these shared parameters
from Jc/I* values, which may differ, and warn that inconsistent per-gateway
SUBMARINER_CABLEDRIVEROPTIONS values cause runtime connectivity failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9fc8758a-4d17-49ec-9307-c6c6a904b75d

📥 Commits

Reviewing files that changed from the base of the PR and between 9fbf16f and e43da17.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • go.mod
  • pkg/cable/amneziawg/amneziawg_test.go
  • pkg/cable/amneziawg/client.go
  • pkg/cable/amneziawg/daemon.go
  • pkg/cable/amneziawg/driver.go
  • pkg/cable/amneziawg/getconnections.go
  • pkg/cable/amneziawg/obfuscation.go
  • pkg/cable/options.go
  • pkg/cable/options_test.go
  • pkg/cableengine/cableengine.go

Comment thread pkg/cable/amneziawg/driver.go
Comment thread pkg/cable/amneziawg/obfuscation.go Outdated
@sanek9

sanek9 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@sanek9

sanek9 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
pkg/cable/amneziawg/driver.go (2)

242-242: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Discarded removePeer errors leave no diagnostic trail.

Both call sites (ConnectToEndpoint on peer-key change, DisconnectFromEndpoint) discard the removePeer error via _ = without even a warning log. If peer removal genuinely fails (e.g., device not reachable), there's no signal of a stale peer remaining configured.

Also applies to: 317-317

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cable/amneziawg/driver.go` at line 242, Update the removePeer call sites
in ConnectToEndpoint and DisconnectFromEndpoint to handle removal errors instead
of discarding them. Log a warning containing the error and enough peer context
to diagnose a potentially stale configured peer, while preserving the existing
control flow.

336-341: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

setupDevice treats any LinkByName error as "no existing link", unlike cleanupDevice.

cleanupDevice (lines 461-468) correctly distinguishes IsLinkNotFoundError from a genuine lookup failure, but setupDevice here silently proceeds on any non-nil error from LinkByName, including permission/netlink errors unrelated to "not found". This could mask a real lookup problem instead of surfacing it.

🛠️ Proposed fix
-	if link, err := w.netLink.LinkByName(DefaultDeviceName); err == nil {
-		if err := w.netLink.LinkDel(link); err != nil {
-			return errors.Wrap(err, "failed to delete existing AmneziaWG device")
-		}
-	}
+	link, err := w.netLink.LinkByName(DefaultDeviceName)
+	switch {
+	case err == nil:
+		if err := w.netLink.LinkDel(link); err != nil {
+			return errors.Wrap(err, "failed to delete existing AmneziaWG device")
+		}
+	case !netlinkAPI.IsLinkNotFoundError(err):
+		return errors.Wrapf(err, "error checking for existing AmneziaWG device %q", DefaultDeviceName)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cable/amneziawg/driver.go` around lines 336 - 341, Update setupDevice’s
LinkByName error handling to ignore only IsLinkNotFoundError, matching
cleanupDevice; return or wrap any other lookup error, while preserving deletion
and existing delete-error handling when the link is found.
pkg/cable/amneziawg/amneziawg_test.go (1)

319-397: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fake client only tracks a subset of AmneziaWG obfuscation fields.

ConfigureDevice copies/tracks only Jc, S3, H1, I1 (plus key/peer fields). Per the PR's documented option set (jc, jmin, jmax, s1-s4, h1-h4, i1-i5), the remaining fields are never captured, so testNewDriver's default/override assertions can't catch regressions in those defaults.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cable/amneziawg/amneziawg_test.go` around lines 319 - 397, Extend
fakeClient.ConfigureDevice to copy every AmneziaWG obfuscation field from
wgtypes.Config into the stored device, including Jmin, Jmax, S1-S4, H1-H4, and
I1-I5 alongside the existing fields. Preserve the current handling of keys,
listen port, peers, and existing obfuscation values so testNewDriver can verify
both defaults and overrides for the complete option set.
pkg/cable/amneziawg/getconnections.go (1)

61-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Compare keys directly in connectionByKey. wgtypes.Key is comparable, so == avoids repeated string conversions in the loop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cable/amneziawg/getconnections.go` around lines 61 - 70, Update
amneziawgDriver.connectionByKey to compare the input key directly with the key
returned by keyFromSpec, using wgtypes.Key equality instead of converting both
keys to strings. Preserve the existing iteration and nil return behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/cable/amneziawg/daemon.go`:
- Around line 75-90: Close fileUAPI in the UAPIListen error branch before
returning. Update the failure handling around ipc.UAPIListen to release the
descriptor alongside tdev and dev, while preserving the existing wrapped error
return.

In `@pkg/cable/amneziawg/driver.go`:
- Around line 285-292: The connection flow after verifyNewPeer should not record
a successful connection when verification fails. Update the error branch
handling around verifyNewPeer to return or propagate the error before reaching
the success log and cable.RecordConnection call, preserving the existing
connected path only for successful verification.

---

Nitpick comments:
In `@pkg/cable/amneziawg/amneziawg_test.go`:
- Around line 319-397: Extend fakeClient.ConfigureDevice to copy every AmneziaWG
obfuscation field from wgtypes.Config into the stored device, including Jmin,
Jmax, S1-S4, H1-H4, and I1-I5 alongside the existing fields. Preserve the
current handling of keys, listen port, peers, and existing obfuscation values so
testNewDriver can verify both defaults and overrides for the complete option
set.

In `@pkg/cable/amneziawg/driver.go`:
- Line 242: Update the removePeer call sites in ConnectToEndpoint and
DisconnectFromEndpoint to handle removal errors instead of discarding them. Log
a warning containing the error and enough peer context to diagnose a potentially
stale configured peer, while preserving the existing control flow.
- Around line 336-341: Update setupDevice’s LinkByName error handling to ignore
only IsLinkNotFoundError, matching cleanupDevice; return or wrap any other
lookup error, while preserving deletion and existing delete-error handling when
the link is found.

In `@pkg/cable/amneziawg/getconnections.go`:
- Around line 61-70: Update amneziawgDriver.connectionByKey to compare the input
key directly with the key returned by keyFromSpec, using wgtypes.Key equality
instead of converting both keys to strings. Preserve the existing iteration and
nil return behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5608666e-e67a-4a9e-8052-8d7e705325ae

📥 Commits

Reviewing files that changed from the base of the PR and between 9fbf16f and 84ab919.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (11)
  • go.mod
  • pkg/cable/amneziawg/amneziawg_test.go
  • pkg/cable/amneziawg/client.go
  • pkg/cable/amneziawg/daemon.go
  • pkg/cable/amneziawg/driver.go
  • pkg/cable/amneziawg/getconnections.go
  • pkg/cable/amneziawg/obfuscation.go
  • pkg/cable/amneziawg/obfuscation_test.go
  • pkg/cable/options.go
  • pkg/cable/options_test.go
  • pkg/cableengine/cableengine.go

Comment thread pkg/cable/amneziawg/daemon.go
Comment thread pkg/cable/amneziawg/driver.go Outdated
@sanek9
sanek9 force-pushed the feat/amneziawg-cable-driver branch from 77787aa to 70af8b7 Compare July 20, 2026 17:49
@sanek9
sanek9 force-pushed the feat/amneziawg-cable-driver branch from 70af8b7 to 4945ece Compare July 20, 2026 17:54

@tpantelis tpantelis 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.

Please squash the commits. Also there's linting and unit test errors (see the failed checks).

Comment thread pkg/cable/amneziawg/amneziawg_test.go Outdated
Comment on lines +51 to +66
func init() {
kzerolog.AddFlags(nil)
}

var _ = BeforeSuite(func() {
flags := flag.NewFlagSet("kzerolog", flag.ExitOnError)
kzerolog.AddFlags(flags)
_ = flags.Parse([]string{"-v=4"})

kzerolog.InitK8sLogging()
})

func TestAmneziaWG(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "AmneziaWG Suite")
}

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.

Since this package isn't trivial and there's more than one _test.go file, this should go in a separate amneziawg_suite_test.go file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread pkg/cable/amneziawg/daemon.go Outdated
type UserspaceDevice = userspaceDevice

// StartUserspaceDeviceForTest overrides userspace device startup in unit tests.
var StartUserspaceDeviceForTest func(ifaceName string) (UserspaceDevice, error)

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.

Having both startUserspaceDevice and StartUserspaceDeviceForTest seems redundant. To simplify, you could make startUserspaceDevice public and remove StartUserspaceDeviceForTest entirely.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread pkg/cable/amneziawg/driver.go Outdated
return &w, nil
}

func (w *amneziawgDriver) Init(_ context.Context) error {

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.

Nit: it seems w was borrowed from wireguard - let's use a

Suggested change
func (w *amneziawgDriver) Init(_ context.Context) error {
func (a *amneziawgDriver) Init(_ context.Context) error {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. receiver renamed from w to a throughout.

Comment thread pkg/cable/amneziawg/driver.go Outdated
}

defer func() {
if err == nil {

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.

This is a bit fragile since it relies on err being the last assigned error variable before any return and variable shadowing with := can break this assumption (which line 144 does). At worst cleanup would be skipped on error which isn't catastrophic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. cleanup no longer depends on the last assigned err. NewDriver uses an explicit setupOK flag; the defer tears down only when setup did not complete:

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/driver.go#L109-L127

Success path sets the flag before returning:

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/driver.go#L186-L188

(Also removed the err shadowing on the backend-port path that made the old defer fragile)

Comment thread pkg/cable/amneziawg/driver.go Outdated
return nil, errors.Wrap(err, "error generating private key")
}

port, err := localEndpoint.Spec().GetBackendPort(v1.UDPPortConfig, w.spec.NATTPort)

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.

Don't shadow err here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. no longer shadows err with := on that path:

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/driver.go#L149-L154

Together with the setupOK cleanup flag, this keeps failure teardown reliable in NewDriver.

Comment thread pkg/cable/amneziawg/obfuscation_test.go Outdated
. "github.com/onsi/gomega"
)

func TestIsValidHeaderValue(t *testing.T) {

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.

Please use Ginkgo for all unit tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. converted to Ginkgo (Describe / DescribeTable).

I also split driver options from obfuscation parameters: obfuscation knobs stay in obfuscation.go, non-obfuscation driver options (currently just keepalive) and the assemble/apply/validate path live in options.go. Internal tests for both are in options_internal_test.go, e.g.:

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/options_internal_test.go#L33-L55

Comment thread pkg/cable/amneziawg/obfuscation_test.go Outdated
@@ -0,0 +1,146 @@
/*

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.

Since these tests access private functions, it should be named obfuscation_internal_test.go.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. same-package *_internal_test.go for unexported helpers.

I intentionally split driver options from obfuscation parameters into separate tables/files:

  • Obfuscation knobs (jc, jmin/jmax, s1..s4, h1..h4, i1..i5) live in obfuscation.go:

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/obfuscation.go#L30-L66

  • Cross-field obfuscation checks (jmin/jmax, overlapping h*) are also there:

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/obfuscation.go#L68-L77

  • Non-obfuscation driver options live in options.go. Right now that table has a single option, keepalive:

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/options.go#L45-L51

  • options.go also owns the shared option type, assembles both tables, applies values, and invokes validations (including validateObfuscationOptions):

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/options.go#L40-L42

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/options.go#L87-L126

Because of that layout I named the internal tests options_internal_test.go (rather than obfuscation_internal_test.go). they cover the combined apply/validate path plus the obfuscation parsers:

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/options_internal_test.go#L80-L84

Comment thread pkg/cable/options_test.go Outdated
"github.com/submariner-io/submariner/pkg/cable"
)

func TestGetDriverOptions(t *testing.T) {

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.

Please use Ginkgo.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread pkg/cable/options.go Outdated

options := map[string]string{}
if err := json.Unmarshal([]byte(raw), &options); err != nil {
logger.Warningf("Ignoring invalid %s value: %v", CableDriverOptionsEnv, err)

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.

I think we should propagate user configuration errors and fail-fast to provide clear indication that their configuration was rejected, instead of logging warning that they likely won't see.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. invalid configuration fails fast instead of warn-and-ignore.

Invalid JSON from the env returns an error:

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/options.go#L31-L45

On the AmneziaWG side I also split driver options from obfuscation parameters. Obfuscation stays in obfuscation.go; options.go holds the shared option machinery, the non-obfuscation table (currently just keepalive), assembles both tables, applies values, and invokes validations.. unknown/invalid keys are collected and returned together:

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/options.go#L45-L51

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/options.go#L78-L126

NewDriver surfaces that as a hard failure:

https://github.com/sanek9/submariner/blob/d46bc5e4e6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/driver.go#L164-L166

Comment thread pkg/cable/amneziawg/daemon.go
@sanek9
sanek9 force-pushed the feat/amneziawg-cable-driver branch from 4945ece to e7fc459 Compare July 22, 2026 01:10
@sanek9

sanek9 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@tpantelis
Thanks. I tried to address everything here.
Summary of the review fixes:

  • Moved the Ginkgo suite into amneziawg_suite_test.go
  • Made StartUserspaceDevice public and dropped the test-only wrapper
  • Renamed the driver receiver from w to a
  • Fixed NewDriver error cleanup (setupOK / no err shadowing)
  • Add the connection to the map only after a successful connect path
  • Converted options/obfuscation tests to Ginkgo; renamed internal tests accordingly
  • Fail-fast on invalid cable driver options (no more warn-and-ignore)
  • Log non-net.ErrClosed UAPI accept errors
  • Squashed commits and fixed lint / unit failures

Separately, I also reworked option parsing for stricter validation, and so that all configuration errors are reported together instead of one at a time.

Sorry if I misunderstood... after squashing and force-pushing, it’s hard to see what specifically changed for the review comments.

@sanek9
sanek9 force-pushed the feat/amneziawg-cable-driver branch from e7fc459 to d46bc5e Compare July 22, 2026 14:36
@tpantelis

Copy link
Copy Markdown
Contributor

... after squashing and force-pushing, it’s hard to see what specifically changed for the review comments.

Actually I can see the changes via the Compare link provided by GitHub after each push.

Comment thread pkg/cable/amneziawg/amneziawg_test.go Outdated

It("should use the configured keepalive when connecting", func() {
natInfo := newNATInfo("remote", "172.16.0.0/16")
_, err := t.driver.ConnectToEndpoint(natInfo)

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.

The outer context for this test case is intended to test NewDriver so I technically don't think we should call ConnectToEndpoint here. This test case should be in testConnectToEndpoint.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Moved this case into testConnectToEndpoint:

https://github.com/sanek9/submariner/blob/b14bae6fe6ae19f587ae39b815df3ff2320ccc35/pkg/cable/amneziawg/amneziawg_test.go

(Exact lines on tip after push; search for keepalive cable driver option is set under testConnectToEndpoint.)

@tpantelis

Copy link
Copy Markdown
Contributor

@sanek9 Would it be feasible to add the new cable driver to the CI E2E test matrix?

Add an AmneziaWG-based cable driver with obfuscation options,
stricter multi-error option validation, and connection status
handling aligned with the WireGuard driver.

Also add amneziawg to the CI E2E cable-driver matrix.

Signed-off-by: sanek9 <sanya0996@gmail.com>
@sanek9
sanek9 force-pushed the feat/amneziawg-cable-driver branch from c3592c1 to b7210fc Compare July 23, 2026 23:59
@sanek9

sanek9 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@tpantelis
Added it. Hope everything is done correctly...

Man, these AI tools are so smart... My employer has been paying for my Cursor subscription for half a year now, and I only started using it a week ago. All I really needed here was an AmneziaWG tunnel... and guess what?

It found some repository, created another pull request, and didn't even ask me. Then it told me it wouldn't work without it and that we need to get it merged. It even wanted to leave a comment here on its own.
submariner-io/shipyard#2492

You just give it an idea, and it runs the lab, writes the report, and then generates the code with tests. I really hope the code quality is good, though... I learned Go about 7 years ago, but I haven't had to write much in it since...

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants