e2e: live-machine Unbounded test on a DO droplet#242
Conversation
Adds a Go binary at test/e2e/unbounded-rig/ that runs freddie + egress +
a native broflake widget in one process, plus a new Test Unbounded step
in .github/workflows/e2e.yaml that:
1. Builds the rig
2. Uploads it to the existing throwaway DigitalOcean droplet
3. Starts it alongside the four sing-box protocol servers
4. Runs a lantern-box client with an unbounded outbound and curls
through it for both HTTP and HTTPS
This validates what the in-process TestUnboundedE2E cannot:
- real TLS handshakes (freddie TLS + inner QUIC TLS)
- real public STUN + ICE over a public network interface
- real quic-go behavior at internet MTU/latency
- pion/transport/v4 + pion/dtls running in two distinct processes
Also adds a test-only escape hatch to UnboundedOutboundOptions:
InsecureDoNotVerifyDiscoveryCert. Parallels the existing
InsecureDoNotVerifyClientCert. Production freddie always presents a
real cert, so this flag is a no-op there.
Fork/prod behavior preserved: in production the signalingClient prefers
the direct-transport RoundTripper injected via the context (radiance's
kindling), which carries its own verification policy. The
insecureSkipDiscoveryVerify arg is only consulted on the fallback path.
Reverts this branch's broflake pin from fisk/quic-go-v0.59 back to
fisk/pion-v4.2 — the v0.59 bump has a transitive conflict with
sing-box-minimal's sagernet/quic-go fork (qpack API break) that needs
to be resolved separately.
Refs getlantern/engineering#3233
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a live-network end-to-end test for the Unbounded protocol by deploying a single “unbounded-rig” binary (freddie + egress + widget) to the existing DigitalOcean droplet used by other protocol e2e tests, and wires a test-only TLS-verification escape hatch for freddie’s self-signed cert.
Changes:
- Add
test/e2e/unbounded-rigGo binary to run freddie + broflake egress + widget in one process on the droplet. - Extend
.github/workflows/e2e.yamlto build/deploy the rig, generate a self-signed cert, gate on ports 9000/8000, and run HTTP/HTTPS curl assertions through the Unbounded outbound. - Add
InsecureDoNotVerifyDiscoveryCertoption and plumb it into the Unbounded outbound’s signaling HTTP client.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
test/e2e/unbounded-rig/main.go |
New droplet-side rig binary providing the Unbounded server topology for live e2e. |
protocol/unbounded/outbound.go |
Plumbs new discovery TLS skip-verify option into signaling HTTP client. |
protocol/unbounded/outbound_test.go |
Updates signaling client unit tests for new function signature. |
option/unbounded.go |
Adds insecure_do_not_verify_discovery_cert JSON option. |
.github/workflows/e2e.yaml |
Builds/deploys rig + cert and adds a Unbounded live e2e step. |
go.mod / go.sum |
Updates broflake pin and reconciles indirect deps (notably QUIC/qpack versions). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…hatches - waitForFreddie now log.Fatalfs when freddie doesn't come up within the timeout. Previously it logged and returned, letting the widget spin against a dead freddie and only surfacing the problem when the client test finally timed out. - All three InsecureSkipVerify sites (widget HTTP client, readiness poll, signalingClient fallback) carry an inline rationale and a //nolint:gosec suppression so future security lints don't flag them and reviewers immediately see they're intentional test/dev escape hatches, not general-purpose TLS relaxations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stacked PRs against feature branches (the Unbounded outbound stack being the current motivator) should get live-machine coverage before merging into main. The path filter still gates the workflow so doc-only PRs don't burn droplets. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Heads up @noahlevenson this integrates a full e2e test of unbounded with freddie, the egress server, and peer running on a live droplet. It runs against unbounded that's updated to the latest pion and uses |
fisk/pion-v4.2 and fisk/covert-dtls were siblings off main, so the previous pin to pion-v4.2 left the live e2e blind to the covert-dtls ClientHello hooks (the whole reason for the rig in the first place). The new branch merges both so the rig's widget now emits randomized / mimicked browser ClientHellos instead of the default pion fingerprint. This is what the Russian DPI would see in production; the live e2e now actually validates that the widget-side DTLS handshake succeeds through that hook path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pretty cool! |
… fisk/unbounded-live-e2e # Conflicts: # go.mod # go.sum
Today's run (#25599661331) failed Reflex + Unbounded with DNS lookup
timeouts on the droplet, *after* the earlier ALGeneva/Samizdat/WATER
servers had successfully resolved example.com against the droplet's
default systemd-resolved → DO infrastructure path. The artifact
download confirms the bisect:
algeneva-server.log +9s dns: exchanged example.com NOERROR (50ms)
reflex-server.log +29s dns: lookup failed for example.com:
(exchange6: context deadline exceeded |
exchange4: context deadline exceeded)
The unbounded-rig.log also shows STUN candidate gather failing because
stun.l.google.com couldn't resolve, which is why the consumer/widget
pairing never completes for the Unbounded test downstream.
Pin /etc/resolv.conf to 1.1.1.1 + 8.8.8.8 directly and disable
systemd-resolved so it can't reclaim resolv.conf on a service restart.
Add a `getent hosts example.com` sanity check so a future DNS flake
fails fast at setup instead of mid-test, where it's harder to
diagnose. options timeout:2 attempts:2 keeps lookups snappy if either
resolver is briefly unhappy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Closes getlantern/engineering#3233.
Adds a live-network e2e test for Unbounded, stacked on top of lantern-box#241 (target: `fisk/unbounded-outbound-v2`). Same DigitalOcean-droplet pattern the existing samizdat/algeneva/water/reflex tests use — this adds a fifth protocol.
What's new
`test/e2e/unbounded-rig/`
Small Go binary that runs three things in one process:
One process so the droplet only runs one extra daemon. All three components are reused via direct imports from `github.com/getlantern/broflake`.
`.github/workflows/e2e.yaml`
New option flag
`UnboundedOutboundOptions.InsecureDoNotVerifyDiscoveryCert` — test-only escape hatch for skipping TLS verification of freddie's self-signed cert. Parallels the existing `InsecureDoNotVerifyClientCert`. No effect in production (where the `signalingClient` prefers the direct-transport RoundTripper injected via context).
Revert: broflake pin back to `fisk/pion-v4.2`
This branch was cut from the PR #241 branch, which had pinned broflake to `fisk/quic-go-v0.59`. That pin forces `quic-go v0.59` + `qpack v0.6.0`, which is incompatible with `sagernet/quic-go@v0.52.0-sing-box-mod.3`'s older `qpack v0.5.1` API (DecodeFull removed, NewDecoder signature changed). Result: lantern-box's production binary can't build with `-tags with_quic`, which CI's `Build lantern-box` step does.
Bumping `sagernet/sing-quic` cascades further (needs newer `sing/common/tls.Config`), putting that change outside this PR's scope.
Pulling back to `fisk/pion-v4.2` — the pion bump lands cleanly and the quic-go bump (getlantern/unbounded#352) can be resurrected once sing-box-minimal's deps are ready for it.
What the live e2e catches that the in-process test cannot
Test plan
🤖 Generated with Claude Code