Skip to content

feat: several tunnels at once - #12

Merged
dennisklein merged 6 commits into
claude/project-thread-kmxtqs-tproxyfrom
claude/project-thread-kmxtqs-multi
Oct 11, 2026
Merged

dennisklein merged 6 commits into
claude/project-thread-kmxtqs-tproxyfrom
claude/project-thread-kmxtqs-multi

Conversation

@dennisklein

@dennisklein dennisklein commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Requested by Dennis · project thread

Before: one nm-sshuttle VPN at a time. Every name the plugin used was fixed (nmss0, the guard table, the tunnel unit, the bus name), so a second profile could not be switched on next to the first.

After: up to 8 nm-sshuttle VPNs can be on together, each with its own toggle, reconnects, guard, DNS link and tunnel. A profile that would fight an active one (overlapping networks, both dns=all, a shared DNS domain, or a loop of jump hosts) is refused, and the journal and nm-sshuttle check name the other profile. One tunnel's ssh can run over another's, and it comes back at once after the other's gap.

It implements docs/later/multiple-tunnels.md and is stacked on #11 (tproxy), so its base is that branch. Merge #11 first.

How: the .name file sets supports-multiple-connections=true, so NM passes a bus name per activation. The shim hands it to a new D-Bus activated dispatcher. The dispatcher picks the lowest free of 8 slots, writes the name to /run/nm-sshuttle/N/bus-name and starts nm-sshuttle@N.service. That instance owns the name and sends READY=1. It serves one activation and then exits. Everything it creates carries the slot: nmssN, nm-sshuttle-guard-N, nm-sshuttle-tunnel@N.service, the spec file, the pinned ssh config /run/nm-sshuttle-ssh/ssh_config-N, and the tproxy mark, table and rule. At Connect, before it creates anything, it checks its claim against the other slots' claims under a lock. The health check counts only listeners in its own tunnel unit's cgroup. cleanup takes --slot N or --all; --all also removes the single-tunnel names after an upgrade. list marks active profiles.

Found by the VM runs and fixed here:

  • Bus policy: dbus-broker denied NM's calls to the dispatcher under a send_destination_prefix rule, so the policy names the dispatcher outright. NM does pass --bus-name once multiple connections are on.
  • sshuttle can outlive its ssh (fix: reconnect when sshuttle outlives its ssh): in about one MT-02 run in three, corp's gap ended the lab's ssh, but the lab's sshuttle kept listening and the plugin saw nothing wrong. The health check now wants an ssh process in the tunnel unit and reconnects without one. Unit tests cover it; the VM runs since have not hit the case, so the VM has not yet seen it fire.

Results:

  • Unit and D-Bus tests: 345 pass. The editor's C tests pass.
  • VM: 563 passed, 0 failed on this head, after two earlier runs of 546 passed and 0 failed. It includes MT-01 (two tunnels with their own DNS, a reconnect of one, a refused overlap, a kill, an NM restart), MT-02 (the lab over corp; back 657–783 ms after corp), MT-03 (start-up: 267 ms cold, 132 ms warm) and main's M2-29 with the per-slot ssh config. The single-tunnel scenarios follow the newest instance's slot.
  • Not automated: the upgrade from a running single-tunnel version, and suspend with two tunnels.

Profile keys: none change. The GNOME editor needs nothing for this PR.

🤖 Generated with Claude Code

@dennisklein dennisklein self-assigned this Oct 11, 2026
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch from 843dc3b to bea5536 Compare October 11, 2026 12:21
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch from bea5536 to c5bd8ac Compare October 11, 2026 12:27
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch 3 times, most recently from 30de84f to b721e4a Compare October 11, 2026 12:40
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch 2 times, most recently from e342781 to 4a86ac1 Compare October 11, 2026 12:56
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch from 4a86ac1 to b714f82 Compare October 11, 2026 13:04
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch 2 times, most recently from 15b51f1 to efab1e7 Compare October 11, 2026 13:18
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch from efab1e7 to 0943e6e Compare October 11, 2026 13:36
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch 2 times, most recently from 417f0ec to f50798e Compare October 11, 2026 13:42
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch from f50798e to 7942504 Compare October 11, 2026 13:54
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch 2 times, most recently from 2693c3b to 8ddcfd9 Compare October 11, 2026 14:11
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch from 8ddcfd9 to 5931504 Compare October 11, 2026 14:12
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch 5 times, most recently from 5d8a2f2 to baa558f Compare October 11, 2026 15:36
@dennisklein
dennisklein marked this pull request as ready for review October 11, 2026 16:07
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch from baa558f to 86e20b5 Compare October 11, 2026 19:52
- Set supports-multiple-connections in the .name file; the shim passes
  NM's per-activation bus name to a new D-Bus activated dispatcher
- The dispatcher picks the lowest free of 8 slots, writes the bus name to
  /run/nm-sshuttle/N/bus-name and starts nm-sshuttle@N.service
- Name the link, the guard table, the tunnel unit, the spec file, the
  pinned ssh config and the tproxy mark, table and rule after the slot
- An instance under a per-activation name serves one activation and then
  exits; it sends READY=1 once it owns the name, and releases the name
  first when NetworkManager goes away
- Count only the listeners in the tunnel unit's own cgroup in the health
  check
- Add cleanup --slot N and --all; --all also removes what a
  single-tunnel version left, and the dispatcher runs it after an upgrade
- Keep nmss0 to nmss7 unmanaged; the packages restart the dispatcher on
  update and end every tunnel on removal
- At Connect, once the first hop is known and before anything is
  created, check the profile against the other active ones under a lock
  and write its claim to /run/nm-sshuttle/N/claim.json
- Refuse it when the networks left after exclude overlap, when both use
  dns=all, when both list the same DNS domain, or when the first hops
  would run in a loop; the reason names the other profile
- Check the loop again when a roam changes the first hop
- Release the claim on STOPPED and in cleanup --slot after a kill
- Run the same checks in nm-sshuttle check, and mark active profiles in
  list
- Report the activated VPN connections from the NetworkManager watcher
- In a gap, a VPN that has just become activated may be the tunnel this
  one's ssh runs over; try at once instead of waiting out the backoff
- sshuttle 1.3 keeps listening after its ssh exits, so its rules and
  listener look healthy while nothing carries the connections
- the health check now looks for an ssh process in the tunnel unit's
  cgroup and drops the tunnel when none is left
- a cgroup it cannot read counts as healthy, as before
- Take the link, guard, units and bus name from the newest running
  plugin instance; "nothing left" now means nothing in any slot
- Expect one instance per activation in M2-09, M2-10 and M2-11, and a
  bus name of its own from NetworkManager in M2-02
- Let the lab DNS server answer another zone, for a second network with
  its own DNS server, web server and sshd inside 10.99.0.0/24
- MT-01: two tunnels at once, a reconnect of one, a refused overlap, a
  kill and an NM restart; MT-02: the lab over corp; MT-03: start-up time
- Mark the design as implemented and record what differs from it
- Note the per-slot names in the design's architecture and add the work
  to the roadmap
- Add a section on several VPNs at once to the user guide and lift the
  one-tunnel limit
@dennisklein
dennisklein force-pushed the claude/project-thread-kmxtqs-multi branch from 86e20b5 to 7f3efae Compare October 11, 2026 19:56
@dennisklein
dennisklein added this pull request to stack #16 October 11, 2026 19:59
@dennisklein
dennisklein merged commit 2ed049c into main Oct 11, 2026
8 of 15 checks passed
@dennisklein
dennisklein deleted the claude/project-thread-kmxtqs-multi branch October 11, 2026 20:21
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.

2 participants