meek: add a first-class meek server inbound#283
Conversation
Make meek a registered sing-box inbound so meek-servers can be provisioned through the track/bandit pipeline (one origin VM per route) instead of the hand-deployed standalone cmd/meek-server — and so a meek track has real routes to contribute bandit arms. The inbound serves the existing meek-v1 Server (HTTP polling) on a sing-box listener (plain HTTP — a CDN/Caddy terminates TLS + fronting in front). The bundled meek outbound opens each session with a SOCKS5 CONNECT, so the inbound runs a tiny in-process SOCKS5 acceptor on loopback as the meek server's upstream: it terminates the CONNECT and routes the stream via the sing-box router. This drops the external microsocks dependency — routing rules, the configured outbound, and metrics now apply to meek traffic natively. - option: MeekInboundOptions (ListenOptions + meek tunables + http timeouts) - protocol/meek/inbound.go: Inbound adapter + SOCKS5-terminate-and-route glue - protocol/register.go: register the meek inbound - tests: SOCKS5 CONNECT routes to the right destination + pipes bytes; non-CONNECT is refused, not routed Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds Meek inbound configuration, registers the inbound protocol, implements the HTTP and loopback SOCKS bridge, and adds tests for SOCKS5 CONNECT routing and rejection paths. ChangesMeek inbound registration and routing
Sequence Diagram(s)sequenceDiagram
participant Inbound
participant httpServer as http.Server
participant loopbackSocks as loopback SOCKS listener
participant meekServer as meek server
participant router as adapter.ConnectionRouterEx
Inbound->>httpServer: Serve meek handler
httpServer->>meekServer: handle HTTP requests
Inbound->>loopbackSocks: accept SOCKS connections
loopbackSocks->>router: RouteConnectionEx after CONNECT
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Introduces a first-class meek inbound to lantern-box/sing-box so meek servers can be provisioned via the standard track/bandit pipeline (instead of relying on the standalone cmd/meek-server deployment model).
Changes:
- Registers a new
"meek"inbound protocol alongside existing inbounds. - Adds
MeekInboundOptionsto configure a plain-HTTP meek-v1 server endpoint and HTTP server timeouts. - Implements the meek inbound adapter, including an in-process loopback SOCKS5 acceptor that terminates CONNECT and routes via
RouteConnectionEx, plus tests for the SOCKS5 routing/refusal behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| protocol/register.go | Registers the meek inbound in the inbound registry. |
| protocol/meek/inbound.go | Adds the meek inbound implementation and loopback SOCKS5-to-router glue. |
| protocol/meek/inbound_test.go | Adds tests validating CONNECT routing and non-CONNECT refusal. |
| option/meek.go | Adds MeekInboundOptions for configuring meek inbound + HTTP timeouts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
protocol/meek/inbound_test.go (1)
90-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd one pipelined CONNECT-plus-payload test.
bufferedConnis here to preserve bytes already read past the SOCKS request, but the current success test sends app data only after the CONNECT handshake completes. A client can coalesce the CONNECT request and first tunneled bytes into one TCP write; that is the path most likely to regress here, and it is still untested.Also applies to: 152-176
🤖 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 `@protocol/meek/inbound_test.go` around lines 90 - 100, The current inbound SOCKS test only verifies app data sent after the CONNECT handshake, so it misses the bufferedConn path where CONNECT and tunneled payload arrive in one TCP write. Add a new test around the existing inbound test flow in connect handling to send the SOCKS CONNECT request plus initial payload together, then verify the payload is preserved and read back correctly after readConnectReply and the routed conn setup. Use the existing bufferedConn, WriteRequest, and readConnectReply flow as the reference points for where to extend coverage.
🤖 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 `@protocol/meek/inbound.go`:
- Around line 112-118: The meek inbound provisioning path currently passes
options.AuthToken into NewServer without enforcing it, which allows the default
empty-token test mode. Update the inbound setup in the meek server creation flow
to reject empty auth_token values for provisioned/public origins, or add an
explicit opt-in flag for no-auth mode and wire that through NewServer. Use the
NewServer call in inbound.go and the AuthToken field in ServerConfig as the main
points to adjust.
- Around line 151-156: The SOCKS accept loop in acceptSocks currently exits on
any Accept error, which can leave the inbound half-alive while the HTTP side
keeps running. Update acceptSocks on Inbound to only return when the listener is
actually shutting down, and for unexpected socksLn.Accept failures, log the
error and continue the loop instead of permanently stopping. Use the existing
acceptSocks and socksLn symbols to keep the change localized.
---
Nitpick comments:
In `@protocol/meek/inbound_test.go`:
- Around line 90-100: The current inbound SOCKS test only verifies app data sent
after the CONNECT handshake, so it misses the bufferedConn path where CONNECT
and tunneled payload arrive in one TCP write. Add a new test around the existing
inbound test flow in connect handling to send the SOCKS CONNECT request plus
initial payload together, then verify the payload is preserved and read back
correctly after readConnectReply and the routed conn setup. Use the existing
bufferedConn, WriteRequest, and readConnectReply flow as the reference points
for where to extend coverage.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4ff2f0f5-2ed1-4d65-86b3-0fd52d5e3517
📒 Files selected for processing (4)
option/meek.goprotocol/meek/inbound.goprotocol/meek/inbound_test.goprotocol/register.go
… auth) - Route meek-server logs through the inbound's sing-box logger via log.NewLogHandler (matches WATER) instead of slog.Default(), so levels/routing stay consistent. (Copilot) - acceptSocks: only return on net.ErrClosed; log + continue on transient Accept errors so a temporary failure can't permanently kill routing while the HTTP endpoint keeps serving. (Copilot + CodeRabbit) - Require auth_token by default — a meek inbound is a public/fronted relay into sing-box, so an empty token is an open relay. New allow_unauthenticated opt-in for deliberate no-auth (test/private). Added a test. (CodeRabbit, security) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@protocol/meek/inbound_test.go`:
- Around line 30-35: The opt-in test path leaks live sockets because NewInbound
returns an open inbound after creating the loopback SOCKS listener and TCP
listener. Capture the returned inbound from NewInbound in inbound_test.go and
register cleanup to close it when AllowUnauthenticated is true, using the
NewInbound result instead of discarding it so the test does not leave listeners
open.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b41db719-5c35-43ba-b4b5-f27a47878e1f
📒 Files selected for processing (3)
option/meek.goprotocol/meek/inbound.goprotocol/meek/inbound_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
- protocol/meek/inbound.go
- option/meek.go
- TestNewInbound_RequiresAuthToken: capture + t.Cleanup the inbound on the allow_unauthenticated path so it doesn't leak the listeners it opens. - TestInbound_HandleSocks_RoutesConnect: bounded select on the router channel so a routing failure fails fast instead of hanging to the test timeout. - TestInbound_HandleSocks_RejectsNonConnect: make `routed` an atomic.Bool — it's written by the handler goroutine and read by the test (data race under -race). Verified with `go test -race`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
…ound 3) Add a default case so an unexpected address type in the CONNECT reply fails the test instead of silently reading 0 bytes and passing on a malformed reply. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
…d 4) Add a capped backoff (failures*10ms, max 1s, reset on success) so a persistent non-close Accept error (e.g. FD exhaustion) can't spin the loop hot and flood the log, while transient errors still recover. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
What
Makes meek a first-class sing-box inbound, so meek-servers can be provisioned through the lantern-cloud track/bandit pipeline (one origin VM per route) instead of only the hand-deployed standalone
cmd/meek-server.Why now
A meek bandit track needs ≥1 route to be assignable — the bandit catalog reads arms from
vps_routesand bails when there are zero launch configs, so a routeless connect-only track is never assigned (verified: a 0-routemeek_0.0.1track returned meek in 0/6/config-newprobes). A first-class inbound gives meek real, provisionable routes, and is also the mechanism to stand meek-servers up behind additional CDNs (CloudFront/Aliyun).Data path
sequenceDiagram autonumber participant C as spark client<br/>meek outbound participant CDN as CDN edge<br/>Akamai/CF/Aliyun participant TLS as Caddy<br/>TLS term participant IN as meek inbound<br/>inbound.go participant SK as loopback SOCKS5<br/>handleSocks participant R as sing-box router participant D as destination C->>CDN: HTTPS POST, fronted SNI + per-CDN inner Host CDN->>TLS: forward to origin TLS->>IN: plain HTTP meek-v1 poll Note over IN: meek Server pipes session bytes<br/>to Upstream = loopback SOCKS5 IN->>SK: dial loopback, per session Note over C,SK: client opened the session<br/>with a SOCKS5 CONNECT SK->>SK: terminate CONNECT, extract dst SK->>R: RouteConnectionEx dst R->>D: dial via configured outbound D-->>C: bytes flow back through the poll loopDesign
Serverunchanged (sessions, seq-dedup, holdoff). The inbound just serves it over a sing-box listener and swaps what itsUpstreampoints at.handleSocks): it terminates the CONNECT, extracts the destination, and hands the post-CONNECT stream torouter.RouteConnectionEx. So the data plane is native sing-box — routing rules, the configured outbound, and metrics all apply.constant.TypeMeek("meek") as the outbound — inbound and outbound live in separate registries, mirroring samizdat.Changes
option/meek.go—MeekInboundOptions(ListenOptions + meek tunables + http.Server timeouts).protocol/meek/inbound.go—Inboundadapter + the SOCKS5-terminate-and-route glue.protocol/register.go— register the meek inbound (registerInbounds).protocol/meek/inbound_test.go— a SOCKS5 CONNECT routes to the right destination and pipes bytes; a non-CONNECT command is refused, not routed.Testing
go build ./...clean;go vet ./protocol/meek/clean;go test ./protocol/meek/ ./protocol/pass (incl. the new tests and theregister.goinit that collectssupportedProtocols).Follow-ups (not in this PR)
pcfg.generateMeekto emit this inbound's launch config (non-empty) so the meek track provisions real origin routes → makesmeek-akamai-freeassignable.cmd/meek-serveronce tracks provision origins.🤖 Generated with Claude Code
https://claude.ai/code/session_01UNCcmviAbCgJtG32Ry3ia2
Summary by CodeRabbit