Repository navigation
peer: report direct-sharing relayed bytes in a snapshot - #689
Merged
Merged
Conversation
Contributor
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
⛔ Files ignored due to path filters: 1
📒 Files selected for processing: 9
✨ Finishing Touches
Comment |
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Connection accounting and session lifecycle handling can produce incorrect totals and retain departed peers indefinitely.
3 open issues
What changed in this PR
Adds direct-sharing traffic snapshots so the app can display session and per-client byte totals alongside Unbounded statistics.
Changes:
- Tracks live and closed connection counters by session.
- Exposes snapshots through the backend and
/peer/snapshotIPC endpoint. - Adds accounting tests and updates supporting dependencies.
| File | Description |
|---|---|
| peer/snapshot.go | Implements relay accounting and snapshots. |
| peer/snapshot_test.go | Tests totals, retention, epochs, and client integration. |
| peer/peer.go | Connects accounting to session and connection events. |
| ipc/server.go | Adds the snapshot endpoint. |
| ipc/client.go | Adds the snapshot client method. |
| go.sum | Updates dependency checksums. |
| go.mod | Updates counter-supporting dependencies. |
| backend/radiance_test.go | Extends peer controller test doubles. |
| backend/peer_share.go | Exposes snapshots through the backend. |
Review effort: Balanced
Advanced stats can show speed, session data and per-country bytes for Unbounded but not for direct sharing, because radiance had no byte counts for peer connections. lantern-box now attaches a live peerconn.Counter to each samizdat connection; the peer client adds them up per session and per client IP, and serves the totals at /peer/snapshot (ipc.Client.PeerRelaySnapshot) in the shape of unbounded.Snapshot, so the app can fold both with the same code. Each session gets its own epoch and counts from zero. A departed client's totals stay for 60s so the app's 1s poller sees its final count, and a stopped session's totals stay readable until the next Start. Verifier connections are already filtered before they reach the tally. Bumps lantern-box to the commit carrying the counter (getlantern/lantern-box#371) and sing-box-minimal to the version that lantern-box requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39
samizdat multiplexes streams over one TCP conn, so open streams can share a source; keyed by source, a second stream overwrote the first's counter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39
Departed clients were pruned only when a snapshot was taken, so a session no one polled kept every client IP it saw. They are now also pruned as new connections arrive. Stop drops the close events of connections still open, so a stopped session's are retired by time: read live for the retention window, then folded into the totals and dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39
Counting verifier streams per source balances the per-IP totals but can hide an ordinary stream's close in place of the verifier's, which left that stream's counter in the relay tally for the rest of the session. Events now carry the stream's counter, so a close is hidden exactly when its own accept was; the per-source count remains for events without one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39
myleshorton
force-pushed
the
fisk/peer-relay-snapshot
branch
from
October 19, 2026 19:20
c6fc94d to
8c3aa4a
Compare
A listener callback can pass the drain check, stall, and resume after Stop→Start has begun a new relay session, recording the old session's counter into the new epoch. Each session now has a generation the listener captures, and events from any other generation are ignored. Addresses review on #689. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39
myleshorton
pushed a commit
to getlantern/lantern
that referenced
this pull request
Oct 19, 2026
Advanced stats showed speed, session data and per-country bytes only for Unbounded; direct sharing (SmC) got a note saying data was unavailable. radiance now serves a direct-sharing relay snapshot at /peer/snapshot in the shape of Unbounded's, so lantern-core polls it every second as peer-snapshot and the share notifier folds it with the same relay code. Each snapshot source gets its own counter tracker and sample clock: both arrive every second with separate epochs, and a shared tracker would treat every alternation as a restart. Peer snapshots are folded in every mode, so bytes a session relays as it stops still count, but drive the speed only while SmC is active, and an idle Unbounded snapshot no longer zeroes SmC's speed. People still come from peer-connection events, so the snapshot's arrivals are not counted again. Bumps radiance to the snapshot commit (getlantern/radiance#689), with lantern-box and sing-box-minimal to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39
A Stop and Start between two of the app's polls lost the stopped session's unpolled bytes, because begin reset the totals under a fresh epoch. Like Unbounded's, the epoch is now fixed for the process and totals carry across sessions; connections a session left open fold in at the next begin. And end now advances the generation, so a verifier accept that resumes after Stop, when isVerifierConn no longer recognizes it, is not counted. Addresses review on #689 and #9181. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39
closeBox can return before the old box finishes closing, so a stopped session's connections can still move bytes after the next begin, which folded their counters once and dropped them. They are now retired instead: no longer listed as peers, read live until relayRetention, then folded. Addresses review on #689. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39
The release carrying peerconn.Counter (getlantern/lantern-box#371). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39
myleshorton
pushed a commit
to getlantern/lantern
that referenced
this pull request
Oct 19, 2026
Advanced stats showed speed, session data and per-country bytes only for Unbounded; direct sharing (SmC) got a note saying data was unavailable. radiance now serves a direct-sharing relay snapshot at /peer/snapshot in the shape of Unbounded's, so lantern-core polls it every second as peer-snapshot and the share notifier folds it with the same relay code. Each snapshot source gets its own counter tracker and sample clock: both arrive every second with separate epochs, and a shared tracker would treat every alternation as a restart. Peer snapshots are folded in every mode, so bytes a session relays as it stops still count, but drive the speed only while SmC is active, and an idle Unbounded snapshot no longer zeroes SmC's speed. People still come from peer-connection events, so the snapshot's arrivals are not counted again. Bumps radiance to the snapshot commit (getlantern/radiance#689), with lantern-box and sing-box-minimal to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39
myleshorton
added a commit
to getlantern/lantern
that referenced
this pull request
Oct 19, 2026
* Action Mode: show relayed data for direct sharing Advanced stats showed speed, session data and per-country bytes only for Unbounded; direct sharing (SmC) got a note saying data was unavailable. radiance now serves a direct-sharing relay snapshot at /peer/snapshot in the shape of Unbounded's, so lantern-core polls it every second as peer-snapshot and the share notifier folds it with the same relay code. Each snapshot source gets its own counter tracker and sample clock: both arrive every second with separate epochs, and a shared tracker would treat every alternation as a restart. Peer snapshots are folded in every mode, so bytes a session relays as it stops still count, but drive the speed only while SmC is active, and an idle Unbounded snapshot no longer zeroes SmC's speed. People still come from peer-connection events, so the snapshot's arrivals are not counted again. Bumps radiance to the snapshot commit (getlantern/radiance#689), with lantern-box and sing-box-minimal to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39 * Action Mode: persist the direct-sharing relay watermark Without it, a restarted app's first SmC snapshot only set a baseline, so bytes relayed while the app was closed never reached the all-time total. The peer feed now saves and restores its own epoch and counters alongside Unbounded's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39 * Action Mode: zero SmC speed on outage, keep the save throttle When the backend is unreachable peer snapshots stop, but only Unbounded's outage reset the speed, so direct sharing kept showing its last figure. And the SmC branch of the Unbounded snapshot forced a save every second, which with SmC snapshots now marking the stats dirty bypassed the 30s throttle; it now forces only when Unbounded's counters moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39 * deps: pin lantern-box v0.0.166 and radiance 79f0a5bd8 lantern-box v0.0.166 carries peerconn.Counter (getlantern/lantern-box#371); radiance 79f0a5bd8 is main with the relay snapshot merged (getlantern/radiance#688, #689) and already contains a0b72470 from #9182. sing-box-minimal moves to the version lantern-box v0.0.166 requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KqE58aYaai3u8CFZe5nXdE --------- Co-authored-by: Adam Fisk <a@lantern.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Stacked on #688. Merge that first, and GitHub will retarget this PR to
main.Why
Advanced stats shows speed, session data and per-country bytes for Unbounded volunteers, but nothing for direct sharing (Share My Connection): radiance had no byte counts for peer connections. getlantern/lantern-box#371 attaches a live
peerconn.Counterto every samizdat connection, and this PR turns those counters into something the app can display.What changes
peer.relayStatssums the counters per session and per client IP. Open connections are read live. A close folds in the connection's final count, so the totals keep it after the connection ends.*Counter, not by source. samizdat multiplexes streams over one TCP connection, so several open streams can share anip:port. The same counter returns on each stream's close, so verifier streams are now matched by counter too, and peer: hide lantern-cloud's verify callback from connection events #688's per-source count is only the fallback for events without one.end()starts a retention timer for connections still open, whose closes are dropped while draining. They're read live for 60s, then folded into the totals and released.Client.RelaySnapshot()→LocalBackend.PeerRelaySnapshot()→GET /peer/snapshot→ipc.Client.PeerRelaySnapshot(). The JSON matchesunbounded.Snapshot(epoch,running,peers,bytesDown,bytesUp,peerBytes[{ip,down,up}]), so the app can fold it with its existing relay counter code.begin. Each session has a generation that also advances atend, so a listener callback that resumes after Stop (or after Stop and Start) isn't counted.unbounded.peerRetention, so the app's 1s poller sees its final count.sequenceDiagram autonumber participant SB as samizdat inbound<br/>lantern-box#371 participant L as peerconn listener<br/>peer.go participant R as relayStats<br/>snapshot.go participant IPC as /peer/snapshot<br/>ipc/server.go participant App as lantern-core poller SB->>L: accept {Source, Destination, Bytes: *Counter} L->>L: isVerifierConn? drop (#688) L->>R: record(+1, source, counter) ⚠️ Note over SB: wrapConn Read/Write<br/>add to the counter App->>IPC: GET every 1s IPC->>R: snapshot(): closed totals + live counters SB->>L: close {Source, Bytes} L->>R: record(-1): fold final countDependency bump
lantern-boxgoes from v0.0.154 to v0.0.166 (the release with #371), and thesing-box-minimalreplace moves to the version that lantern-boxmainrequires (mux.ContextWithSessionConn). This picks up lantern-box v0.0.155 through v0.0.165 as well.go mod tidywas run, and go.mod and go.sum are committed together. #371 is merged and released as v0.0.166, which this now pins.Testing
relayStatstests: live and closed counts; per-IP sums across one client's connections; retention pruning; a new epoch per session; the totals surviving Stop; uncounted and unmatched events ignored.TestClient_RelaySnapshot: end to end through a started client, with the verifier excluded and a poll after Stop still seeing the bytes.go test -race ./peer/ ./ipc/ ./backend/passes, andgo testpasses for every package exceptcmd/lantern, whoseipc.NewClientcall is already broken onmain.Pre-PR review (local Codex gate)
CODEX GATE: verdict=approve critical=0 important=0 minor=0 reviewed=23approveip:port, so a second multiplexed stream from one source overwrote the first one's counter (found while rebasing on peer: hide lantern-cloud's verify callback from connection events #688's fix). Now keyed by counter, withTestRelayStats_StreamsSharingASource.TestRelayStats_PrunesWithoutSnapshots.end()plus time-based retirement, withTestRelayStats_RetiresOpenConnectionsAfterStop.TestClient_OrdinaryCloseBeforeVerifierOnSharedSource(fails on the old code).🤖 Generated with Claude Code
https://claude.ai/code/session_01LQG2dRhaocR2UY5RPamm39
Summary by CodeRabbit