Skip to content

tor: fix circuit/stream ID misparsed as a control-port status code - #5731

Open
corporategoth wants to merge 1 commit into
opnsense:masterfrom
corporategoth:fix-tor-diag-circuit-id-parsing
Open

corporategoth wants to merge 1 commit into
opnsense:masterfrom
corporategoth:fix-tor-diag-circuit-id-parsing

Conversation

@corporategoth

Copy link
Copy Markdown

Summary

The Diagnostics > Circuits / Streams page for the Tor plugin only ever shows one row, no matter how many circuits are actually open, once the bridge/relay has been running long enough to allocate a 3-digit circuit ID.

Root cause

TorCTL#send_query in tor_diag strips a leading 3-digit token from every line it reads, on the assumption it's always a control-protocol status code (250 OK, 250-foo, 250+bar). But inside a 250+key= multiline reply, the data lines are not prefixed with a status code at all — per Tor's control-spec, only the introducer and terminator carry codes. circuit-status/stream-status data lines legitimately start with a numeric circuit/stream ID, and once that ID reaches 100-999 (routine after a few hours of uptime) it is misidentified as a status code and stripped.

Downstream, TorCTL#get_circuit's data.shift.to_i then shifts off the literal word "BUILT" instead of the (now-missing) ID, and "BUILT".to_i is 0. Every circuit collides on hash key 0, so only the last one processed survives in the JSON the GUI renders.

Reproduced live on a bridge relay: GETINFO circuit-status genuinely returning 2 circuits, tor_diag -c (and the GUI's /api/tor/service/circuits) reporting only 1.

Fix

Track whether send_query is inside a 250+-introduced data block and skip status-code detection entirely for lines read while inside it, matching the protocol's actual line framing. No change to the public data format returned to callers.

Testing

  • ruby -c syntax check
  • Ran the patched script live against a bridge with 2 concurrent circuits (previously collapsed to 1 under the old code) via both direct invocation and configctl tor circuit (the actual code path the GUI's API controller uses) — both now correctly return both circuit IDs with accurate fields.
  • Confirmed configctl tor streams still returns valid (empty) JSON, unaffected by the change.

send_query() strips a leading 3-digit token from every line it reads,
assuming it is always a Tor control-protocol status code (e.g. "250 OK",
"250-foo", "250+bar"). Inside a "250+key=" multiline reply, the raw
data lines are not prefixed with a status code at all, but circuit-status
and stream-status data lines legitimately start with a numeric circuit or
stream ID. Once a bridge/relay has been up long enough for its Tor process
to allocate a 3-digit circuit ID (100-999 -- routine after a few hours of
uptime), that ID is misidentified as a status code and stripped, and the
now-missing ID makes get_circuit()'s `data.shift.to_i` shift off the
literal word "BUILT" instead, which .to_i's to 0. Every circuit then
collides on hash key 0, so the Diagnostics > Circuits/Streams page in the
GUI shows only the single most-recently-processed circuit, no matter how
many are actually open.

Fix: track whether we are inside a "250+"-introduced multiline data block
and skip the status-code detection entirely for lines read while inside it,
matching the control-spec's actual framing (data lines in a multiline reply
carry no status-code prefix). Verified against a live bridge showing 2
concurrent circuits that were previously collapsing to 1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant