Add lanturn outbound type#257
Conversation
lanturn is a Lantern circumvention transport that mimics WebRTC TURN- relayed media flow on plain UDP/3478 with self-hosted coturn on Lantern's international VPS fleet. See https://github.com/getlantern/lanturn for the protocol details. What this PR adds: - constant.TypeLanturn = "lanturn" - option.LanturnOutboundOptions / LanturnInboundOptions / LanturnCoturnEndpoint - protocol/lanturn/ package — outbound implementation that wraps github.com/getlantern/lanturn/pkg/lanturn.Dial as a sing-box outbound. MVP opens a fresh TURN session per DialContext call; follow-up will multiplex destinations over a persistent session via SOCKS5-over-lanturn (matching the Unbounded pattern). - protocol/register.go updated to register the outbound + add "lanturn" to supportedProtocols - README.md — Lanturn section with protocol overview, when-to-use guidance (Iran primary, Russia experimental, China not v0.1 per lanturn design §11), config schema, server config note about the separate egress binary Status: alpha. Working code in github.com/getlantern/lanturn at the pkg/lanturn package; spike binaries cmd/lanturn-phase{0..4} validate the wire-format and behavioral mimicry layers separately. Hard rule recorded in lanturn README + design: the inner DTLS handshake must use covert-dtls fingerprint randomization in production (the TSPU March 2026 pion-default-DTLS matcher). MVP outbound passes fingerprint_mode=mimic by default; do NOT deploy with mode=none to Russia / China. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new lanturn outbound type to lantern-box (a TURN-as-cover, WebRTC-shaped transport) and documents its intended deployment/configuration.
Changes:
- Register
lanturnas a supported custom protocol and outbound type. - Introduce
LanturnOutboundOptionsand a newprotocol/lanturnoutbound implementation backed bygithub.com/getlantern/lanturn. - Extend README with a new Lanturn section including config examples and operational notes.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents Lanturn’s threat model, deployment, and JSON config fields. |
| protocol/register.go | Registers the new lanturn outbound and advertises it in supportedProtocols. |
| protocol/lanturn/outbound.go | Implements the sing-box outbound adapter for Lanturn (MVP per-dial session). |
| option/lanturn.go | Adds JSON option structs for the Lanturn outbound (and placeholder inbound options). |
| constant/proxy.go | Adds TypeLanturn constant. |
| go.mod | Adds the github.com/getlantern/lanturn dependency and bumps the Go toolchain version. |
| go.sum | Records checksums for the new dependency set (incl. lanturn + pion/rtp bump). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot's review on PR #257 flagged that the v0.1 outbound was advertising config fields and behavior it didn't actually deliver. Specifically: 1. Package doc claimed "calls lanturn.Dial at Start time to establish a persistent connection" — actual impl dials per-DialContext, no Start. 2. LanturnOutboundOptions exposed fingerprint_mode, session_duration_secs, idle_gap_*, udp_timeout_ms, prefer_transport — none wired through to the upstream pkg/lanturn.ClientConfig MVP. Misleading config schema. 3. Network advertisement included UDP but DialContext rejected it. Could cause sing-box to route UDP traffic to a guaranteed-failing outbound. 4. DialContext returned a real net.Conn without conveying the destination to the egress — would silently forward bytes to a hardcoded egress destination instead of the caller's. Correctness bug. 5. README config example listed unsupported fields. 6. option doc said Credential/FleetSelector/Logger come "via context at service startup" — actually hardcoded in NewOutbound. 7. No unit tests for option validation / DialContext behavior. Fixes: - option/lanturn.go: trim unsupported fields (fingerprint_mode, session_duration_secs, idle_gap_*, udp_timeout_ms, prefer_transport); rewrite doc to honestly describe what v0.1 honors. Profile kept but documented as "v0.1 falls back to Opus regardless." - protocol/lanturn/outbound.go: - Package doc rewritten to accurately describe per-DialContext sessions and the v0.1-alpha "not yet operational" status - Network advertisement: TCP-only (was [TCP, UDP]) - DialContext returns clear "not yet implemented" error with PR URL reference instead of returning a misroutable net.Conn - README.md: scope trim — remove unsupported fields from JSON example and options table; add prominent alpha warning callout above config; rewrite rollout note to enumerate what's wired vs deferred - protocol/lanturn/outbound_test.go (new): 6 tests covering required-field validation, valid config produces TCP-only adapter, DialContext fails fast with the expected message, UDP DialContext rejected with TCP-only message - protocol/register_test.go: parallel TestSupportedProtocolsIncludesLanturn matching the Unbounded test (same register-without-slice regression hazard) What's still deferred to follow-up PRs (clearly documented now): - Destination forwarding via SOCKS5 CONNECT over lanturn — the load- bearing missing piece - Multi-profile selection (vp8/vp9/screen-share) - covert-dtls fingerprint randomization - Session rotation, TURNS-on-5349 fallback, recency-weighted fleet All 6 new lanturn outbound tests pass. Existing tests in ./protocol/... unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pkg/lanturn now takes destination as a Dial parameter and prepends the sing-box-native M.SocksaddrSerializer wire format to the first inner- stream bytes — same address-prefix pattern trojan / anytls / vmess / shadowsocks use. Replaces the planned SOCKS5-CONNECT-over-lanturn approach (which was overkill since we control both ends of the wire and don't need SOCKS5's version-negotiation / auth-method ceremony). This PR's outbound changes: - DialContext now actually dials. Opens a fresh lanturn session per call, passes destination to upstream.Dial, returns the resulting net.Conn directly. Removed the "not yet implemented" fail-fast. - Bumped pkg/lanturn dep to v0.0.0-...-9d796d3 (the destination- forwarding commit upstream). - TestDialContext_AlphaError → TestDialContext_UnreachableCoturn. Asserts the dial fails cleanly when coturn is unreachable (127.0.0.1:1) with the expected "lanturn dial ..." wrap. End-to-end success path tested by test/e2e/lanturn_test.go (Phase-5 follow-up, in-process). - README: removed "not yet operational" warnings. Rewrote rollout note to enumerate what the v0.1 outbound DOES do today (heavy fresh-session per dial, no covert-dtls, no profile selection, no rotation, no fallback) vs what's still next-milestone work. All 6 unit tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@myleshorton — picking this back up. The earlier review feedback is all addressed; this is a status snapshot + the test-coverage gap analysis I'd want a second pair of eyes on before merge. What landed
CI: e2e + build both green. Test coverage as it stands7 tests, all passing, ~0.5s:
What's deferred (in #264)Four follow-up tests not blocking this PR but worth tracking:
I think shipping the seven existing tests is the right v0.1 call given the Iran context — they cover all the negative contracts you flagged, plus the Re-requesting review for a final pass + merge. Happy to address anything new you spot. |
Summary
Adds
lanturnas a sing-box outbound type — a TURN-as-cover circumvention transport that mimics WebRTC TURN-relayed media flow on plain UDP/3478 with self-hosted coturn on Lantern's international VPS fleet.Full protocol design + validation-spike sequence (Phases 0-5): getlantern/lanturn. Integration guide: docs/INTEGRATION.md. Design draft v0.2 is in
circumvention-corpus-private(private).Why a new transport: existing Lantern transports (samizdat / reflex / tlsmasq) all live in the TLS-at-byte-0 wire-shape envelope — the most-attacked surface in 2026 (SNI extraction, JA3/JA4 fingerprinting, fully-encrypted-traffic detection). lanturn moves to a wire-distinct shape (STUN magic cookie at offset 4 + ChannelData on UDP/3478) without losing collateral-freedom protection — wholesale-blocking UDP/3478 breaks Twilio Video, Cloudflare Calls, Microsoft Teams' relay fallback, every video-conferencing vendor's hostile-NAT path.
⚠ v0.1 alpha status — not yet operational
The outbound registers cleanly and validates options at config-time, but
DialContextreturns a clear error rather than a tunnel. This is deliberate: the destination-forwarding handshake between the lanturn client and the egress (planned as SOCKS5 CONNECT over the lanturn conn) is not yet implemented inpkg/lanturn. Without it, dialed bytes would silently forward to a hardcoded egress destination instead of the caller's actual destination — a correctness bug. Failing fast avoids that misrouting.What this PR's outbound DOES do today:
lanturnas a sing-box outbound typecoturn_endpoints,peer_addr,lanturn_auth_secret) at config-timeListenPacketis unimplemented)upstream.ClientConfigagainstpkg/lanturn"not yet implemented"error fromDialContextinstead of a misroutablenet.ConnWhat's deferred to follow-up PRs (working code lives in
getlantern/lanturn/cmd/lanturn-phase{2,3,4}/main.go):What this adds
constant.TypeLanturn = "lanturn"option.LanturnOutboundOptions(trimmed to honored fields only:coturn_endpoints,peer_addr,profile,lanturn_auth_secret) +LanturnCoturnEndpointprotocol/lanturn/package — outbound implementation + unit testsprotocol/register.goregisters the outbound + adds"lanturn"tosupportedProtocolsprotocol/register_test.go— parallelTestSupportedProtocolsIncludesLanturnregression testprotocol/lanturn/outbound_test.go(new) — 6 tests covering option validation, TCP-only advertisement, fail-fast DialContext, UDP rejectionPer-jurisdiction guidance (per lanturn design §11)
Test plan
go build ./...)go test ./protocol/lanturn/)go test ./protocol/...)pkg/lanturn🤖 Generated with Claude Code