Skip to content

egress: log every session, with an optional consumer hello - #454

Draft
AGMETEOR wants to merge 1 commit into
mainfrom
allan/consumer-hello
Draft

AGMETEOR wants to merge 1 commit into
mainfrom
allan/consumer-hello

Conversation

@AGMETEOR

@AGMETEOR AGMETEOR commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

context

  • part of https://github.com/getlantern/engineering/issues/3702: from server data we can't tell whether a given client's unbounded connection reached the egress
  • egress.websocket_session is sampled at 1% (about 72 spans/day, so roughly 7k sessions/day), and nothing on it identifies the consumer: the egress only sees the donor, the random CSID and an optional country
  • draft for design feedback, see open questions

summary

Per-session log line (sessionlog.go)

  • one Info line per WebSocket session at teardown: CSID prefix, bytes, QUIC streams, duration, teardown reason, consumer and donor country, plus the hello fields when present
  • keys match the span's broflake.* attribute names, so one query works on either

Consumer hello (common/hello.go, egress/hello.go)

  • a small JSON hello (tag, client_version, platform) sent once per QUIC connection on a unidirectional stream
    • bidirectional streams are the proxied traffic the SOCKS5 server consumes, so the hello never touches or delays them
    • an egress that predates this never accepts a uni stream, so the hello sits unread and nothing else changes
  • it rides inside QUIC, so the donor can't read it, unlike the subprotocol header (CSID, country, activation ID)
  • capped at 1 KB; fields are truncated to 128 runes and stripped of unprintable characters, since they're logged; unknown JSON fields are ignored, so fields can be added later
  • the egress registers a slot per dialed connection and waits up to 30s for the hello; the session reads the slot at teardown, so a hello that lands mid-session or after a migration still reaches the log line; the slot is dropped when the connection's context ends

Client (clientcore/quic.go)

  • QUICLayer.SetConsumerHello makes every accepted connection send the hello in a goroutine, best-effort with a 10s timeout
  • lantern-box is the caller; its draft follows

Tests

  • hello format: round trip, rejects non-hello streams, oversize, unknown fields ignored, sanitization
  • egress: hello reaches the session slot; no hello leaves proxying intact; slot dropped on close; log line contents
  • client: sendConsumerHello over a real loopback QUIC pair, consumer as server like production
  • go test -race ./... passes; the go vet warnings in egresslib.go and clientcore are pre-existing on main

open questions

  1. Log volume. otellogs.go keeps exported Info lines bounded by construction (~10k/day). This line is one per session, so it grows with traffic: ~7k/day today. OK as-is, or should it be sampled/rate-limited, or kept unexported?

  2. Hook. Is a uni stream inside QUIC the right place for consumer → egress metadata, or is there a better one?

  3. Device ID. This sends no device ID yet. The device ID is already a telemetry attribute elsewhere (lantern-cloud's config.device_id, and lantern-box sends it to other proxies via tracker/clientcontext), so logging it here is in line with existing practice. The requirement is that it only travels to an authenticated egress. The hello is encrypted inside QUIC, so a passive donor can't read it, but nothing authenticates the egress today (next point), so a malicious donor could impersonate the egress and read it.

  4. Egress authentication. The consumer verifies the egress only when the track sets egress_ca and egress_server_name, and every egress build generates a new self-signed cert on each start (GenerateSelfSignedTLSConfig(true) in cmd/sing-box, cmd/http, cmd/socks5), so production can't currently verify it. Proposal:

    • egress loads a stable cert/key when configured, unchanged behaviour otherwise: egress: present a stable QUIC certificate when configured #455
    • lantern-cloud issues and deploys the cert, then sets egress_ca / egress_server_name on the unbounded track. Current clients already honour those options, so every egress must present the cert before the track options are set
    • lantern-box includes the device ID in the hello only when verification is on, so an unverified track never sends it

    Does that match how you'd want to do egress authentication?

deploy

The egress can ship first through lantern-cloud's deploy/egress/VERSION. Consumers on an old egress are unaffected, and old consumers simply send no hello.

🤖 Generated with Claude Code

The egress.websocket_session span is sampled at 1% and carries nothing
that identifies the consumer, so the egress cannot say whether a given
client reached it.

- egress: one Info line per WebSocket session at teardown (CSID prefix,
  bytes, streams, duration, teardown reason, countries), keyed like the
  span's attributes.
- consumer hello: once per QUIC connection the consumer can send a small
  JSON hello (outbound tag, client version, platform) on a
  unidirectional stream. Bidirectional streams are the proxied traffic
  the SOCKS5 server consumes, so the hello never touches them, and an
  egress that predates it never accepts the stream. It rides inside
  QUIC, out of the donor's sight.
- clientcore: QUICLayer.SetConsumerHello sends it on every accepted
  connection.

Part of getlantern/engineering#3702.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 7, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

This branch has not been deployed

No deployments
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.

1 participant