Skip to content

v1.6.2 - upstream parity (carrier/logs/status), backhaul 1:1 guard, doctor fix - #5

Merged
loopy-iri merged 5 commits into
mainfrom
feature/dual-core-hub
Jul 30, 2026
Merged

v1.6.2 - upstream parity (carrier/logs/status), backhaul 1:1 guard, doctor fix#5
loopy-iri merged 5 commits into
mainfrom
feature/dual-core-hub

Conversation

@loopy-iri

Copy link
Copy Markdown
Owner

Why

Two production incidents drove this:

  1. doctor lied. It reported WS to 200/OK while every tunnel was down, because the probe hit only / without forcing HTTP/1.1 - under HTTP/2 nginx strips the Upgrade header, the $http_upgrade map falls through to the fake site, and you get a false 200. It also never probed the real secret control_path.

  2. Multi-machine backhaul silently destroys tunnels. Backhaul is 1:1 - one server, one global token, one ports set. Put two foreign machines on it and the first connects, then adding the second drops both: they fight over the single control channel. gRPC (Pasargad on 62050) dies first because its handshake/cert never matches the wrong peer. The signature is a duplicate inbound_port across backhaul nodes.

Upstreams were also second-class: only kcp among transports, and no way at all to see their logs or tunnel mode.

What changed

Upstream parity - every upstream now has the same surface as the main tunnel, and several upstreams work side by side:

  • ratholenode upstream <plain|noise|ws> <id> ..., upstream status <id>, upstream logs <id> [n]
  • per-upstream carrier <select> in the hub, unique ids per upstream
  • backhaul is deliberately absent there - it is a 1:1 core scoped to the whole Iran server

Backhaul - backhaul node <name> on now refuses before mutating state when another backhaul node holds the same inbound, naming the clash. doctor reports an existing violation; the hub lists which nodes are on backhaul and warns in red. backhaul logs added on both sides.

Safety - adding the first SNI/game node now confirms first, stating that 443 flips to stream/L4 and how many normal nodes are affected.

Verification

  • 26 unit tests pass (+8): argv shape, pubkey validation, injection rejection, WRITE_ACTIONS membership
  • gen_up_client executed in a sandbox for all three modes - plain emits tls = false, noise emits type = "noise" with the pubkey, ws emits tls = true with SNI
  • render functions evaluated against mock overviews, including the reporter's real broken state (correctly flags 1101, 62050) and a healthy one (silent)
  • bash -n, py_compile, node --check clean

The WS probe hit only https://$domain/ without forcing HTTP/1.1, so curl
could negotiate h2 — nginx drops the Upgrade header under h2, the $http_upgrade
map falls to the fake site, and doctor reported a false 200 while tunnels were
down. Now: force --http1.1, keep the / fallback line, and additionally probe the
real secret control_path (/_rh/<hex>) where 101 means the control channel is healthy.
…PC timeouts)

backhaul is 1:1 — one server, one global token, one 'ports' set. Putting two
different foreign machines on backhaul collapses them into a single namespace,
so the server cannot know which connection belongs to which machine and
mis-routes them. Duplicate inbound_port across backhaul nodes is the signature
(e.g. three nodes on 62050 = three machines each running Pasargad there), and
gRPC dies first because its handshake/cert never matches the wrong peer.

Now 'backhaul node <name> on' warns on duplicate inbounds and doctor reports it.
…us, backhaul logs

Upstreams were second-class: only kcp among transports, no way to see their
logs or tunnel mode. gen_up_client already understood ws/kcp/plain/noise — only
the commands and UI were missing. Now every upstream has the same carrier
surface as the main tunnel, and multiple upstreams each get their own.

node (ratholenode):
- upstream plain <id> on <host:port> | off
- upstream noise <id> on <host:port> <pubkey> [pattern] | off
- upstream ws <id>            (reset any carrier back to the default)
- upstream status <id>        (carrier + per-mode detail + unit state)
- upstream logs <id> [n]      (rathole-client@<id> + rathole-kcp-up-<id>)
- backhaul logs               (rathole-backhaul-client)
iran (ratholectl):
- backhaul logs               (rathole-backhaul-server)
hub:
- upstream_{plain,noise}_{on,off}, upstream_ws (writes), upstream_{logs,status}
  and backhaul_logs (read-only), all RE-validated argv
- per-upstream <select> mirroring the main-tunnel carrier select, unique ids so
  several upstreams render side by side; backhaul is deliberately absent there
  (it is a 1:1 core scoped to the whole Iran server, not per upstream)
- backhaul status/logs buttons on both the Iran and node pages
- i18n fa/en: logs, bh_logs, upcarrier_confirm, upcarrier_hint

tests: 26 pass (+8) — argv shape, pubkey validation, injection, WRITE_ACTIONS
The user confirmed the failure mode: with two machines on backhaul the FIRST
one connects, then adding the SECOND drops BOTH — they fight over the single
control channel because backhaul has one global token and one 'ports' set.

- ratholectl: 'backhaul node <name> on' now refuses (die) BEFORE mutating state
  when another backhaul node already holds the same inbound_port, naming the
  clashing node. Previously it only warned after the fact.
- hub: the "Available carriers" section lists which nodes are on backhaul and
  shows a red warning with the duplicate ports when the 1:1 rule is violated.
- hub: adding the FIRST SNI/game node now asks for confirmation, stating that
  443 flips to stream/L4 and how many normal nodes route through the new path.
- i18n fa/en: bh_users, bh_dup_warn, cf_game_l4
@loopy-iri
loopy-iri merged commit 2fec5bb into main Jul 30, 2026
1 check passed
@loopy-iri
loopy-iri deleted the feature/dual-core-hub branch July 31, 2026 07:35
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