Skip to content

Home network: send beacons on every home-network adapter, so sync works with a VPN on (#222) - #223

Merged
bfieser merged 12 commits into
mainfrom
sync/222-lan-every-adapter
Nov 7, 2026
Merged

bfieser merged 12 commits into
mainfrom
sync/222-lan-every-adapter

Conversation

@bfieser

@bfieser bfieser commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

Closes #222. Stacked on #221, which is itself stacked on #218. Please merge in order: #218, then #221, then this one; each retargets to main as the one below merges.

Why

Home-network beacons go to 255.255.255.255. On Windows that leaves through the lowest-metric route, which with a VPN on is usually the VPN's adapter (a Hyper-V/WSL adapter can do the same). So two machines on the same Wi-Fi never hear each other, even when the VPN allows local traffic. That was Brian's case when we first linked machines.

What changes

  • Beacons also go to each home subnet. Every beacon is also sent to the directed broadcast address of each Wi-Fi/Ethernet subnet (e.g. 192.168.1.255). That follows the subnet's own route out of the Wi-Fi/Ethernet adapter, past the VPN's default route.
  • Nothing changes for older builds. They receive these as they are, since they listen on 0.0.0.0. Replies and pulls were already unicast to the peer's address.
  • On Windows, only adapters Windows has described count. Many VPNs name their adapter "Ethernet 3" and say what they are only in the description. Each candidate adapter is described once per launch (PowerShell, at background priority, one run at a time), so no beacon goes into a work VPN. If PowerShell is blocked, it works as before.
  • The adapter list is worked out again at most every minute, when the internet route changes, and after a network look. This adds no new timer; it rides the existing 10 s beacon.
  • The diagnosis catches up:
    • A VPN on the broadcast route is now only a guess, vpn_blocks: "Patch Pilot also sends straight onto your home network, so the VPN is most likely blocking local traffic". It ranks below sure causes (Local Network off, Public network) and isn't named while the other machine may be on another network or hears this one.
    • A virtual adapter is no longer a reason.
    • Reports (Release 0.4.5: all templates in one click, big files asked for automatically, layout pass #124) carry direct, so the other machine words it the same way.

A VPN that blocks all local traffic (kill switch, "block LAN") still stops sync. The app then says so.

Decision

#125 is Proposed. It's the last row of docs/decisions.md.

Review

  • One multi-agent review round: 11 confirmed findings, all fixed.
  • A final check of those fixes found 2 more issues and the regression check 5 more; all are fixed in the last commit.

Testing

  • cargo fmt --check, clippy -D warnings, cargo test on both workspaces: 489 core tests, 7 new. App tests pass; npm run build passes.
  • Real network: on Brian's Windows laptop, the new manual test picks only the Wi-Fi subnet (10.0.0.255, WSL adapter left out), and the beacon arrives at a listener bound like Patch Pilot's. Run it with:
    cargo test -p patchpilot-local directed -- --ignored --nocapture
  • Not tested yet: with the VPN actually connected (GlobalProtect was off), two machines end to end, and macOS.

🤖 Generated with Claude Code

bfieser and others added 11 commits November 6, 2026 10:47
When Home network sync can't find a linked machine, the Sync & backup
line now names it and, when this machine can tell, why: a VPN or virtual
adapter carrying the broadcasts, no network, Windows calling the network
Public, macOS Local Network access off, another Patch Pilot version, or
another rig's Patch Pilot on the network. It no longer says "In sync"
while nobody is here. The details list each machine and a checklist.

- patchpilot_local::lan_diagnosis: pure diagnose() over gathered facts,
  adapter classification (name and Windows description), egress kind,
  bind error wording; tested with hand-built facts.
- patchpilot_local::netinfo: read-only probes (UDP connect + local_addr,
  sysinfo interfaces, PowerShell adapter description and profile with a
  5 s deadline at below-normal priority).
- lan::read tells this rig, another version, another rig and noise apart.
- sync.rs keeps the relay's list of linked machines, so machines added
  from an export file or removed never show as missing.
- lan.rs gathers facts on the existing 10 s beacon thread, looks at the
  network only while a machine is missing and none is here, and tells
  the UI only when the diagnosis changes.

Decision #123 (amends #94).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each linked machine keeps a small sealed net/<machine id> report on the
relay: Home network on, its subnets, whether its broadcasts leave through a
VPN, and which linked machines it hears. Sent on change and, only while a
machine is missing or can't hear this one, every 5 minutes. The others use
it to say why they can't find that machine: Home network off there, asleep
or closed, VPN there, a different network, one-way hearing (firewall), or
the same network with neither hearing the other. Decision #124 (Proposed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A matching subnet alone no longer counts as the same network: most
  routers use the same few ranges. The relay now answers a PUT with the
  client's address, and "blocked" needs the same public address too
  (same family, neither side's internet through a VPN); a different one
  means "different network".
- Report age is judged by when it reached the relay, on this machine's
  clock (relay x-age header), never the other machine's clock.
- "Can't hear this machine" waits until the other machine had listened
  30 s when it made its report, so a quick restart isn't deafness.
- Peers count as here by the wall clock (a Mac's Instant stops in sleep).
- A port that won't open is "can't listen", not "Home network off".
- A definite reason from the other machine beats this machine's guesses;
  the deaf line names only this machine's own VPN/virtual adapter.
- A report counts as sent only once the relay took it, so failures retry.
- Network facts older than 90 s are left out of reports, and a machine
  heard here lately is never called quiet.
- sync no longer assumes the home-network state exists at startup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A missing machine's report names a reason only if it reached the relay
  after this machine last heard that machine, and while this machine is
  online: a report made before it slept, quit or moved said "hears this
  machine" and was read as a firewall problem.
- "It hears this machine" and "blocked" need its own sends known fine (no
  VPN, no failed broadcast) and a report made since this machine started
  listening.
- A virtual adapter there is its own reason, not "a VPN".
- An unknown report age (older relay) names only "off"; "last checked in"
  counts from when the machine was last heard if that's later.
- Machines whose report says Home network is off no longer keep the
  others heartbeating or looking at their network.
- Windows adapter descriptions are looked up whenever the network is
  looked at (cached 10 min), so a VPN called "Ethernet 4" isn't missed.
- Network facts and their schedule use the wall clock; listening_since is
  fixed once, so a healthy machine's report doesn't look changed.
- The public address is forgotten when subnets change, and a report is
  sent again at once when an upload reveals a new one; no report is asked
  for while offline (the session start sends one).
- Line and details agree on which cause or reason is shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- "It hears this machine" needs a report made at least 55 s after this
  machine last heard it and after this run started (its list keeps a
  machine 45 s); other reasons need 20 s, two beacons, not one.
- "Different network" by subnets comes before "it hears this machine", so
  a machine that moved isn't read as a firewall problem.
- "Blocked" and "it hears this machine" also need this machine's own
  broadcasts to go out fine; a send that fails without an OS code counts.
- The public address is forgotten at the start of each relay session.
- The live connection counts as offline when a ping goes unanswered, so
  reports aren't trusted over a connection that died silently; a stale
  report nudge doesn't put the same report twice.
- This machine's network facts older than 90 s are ignored by the
  diagnosis too; a clock stepped back means "look again".
- An adapter Windows couldn't describe counts as unknown (retried each
  minute), not the home network.
- A cause here gives way when every missing machine has a reason on its
  own side (incl. another network), so a laptop at a café isn't told to
  make its Wi-Fi Private; one that gave way marks nothing on the checklist.
- Missing and deaf at once is said plainly; the deaf cause is found then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- This machine's VPN explains a machine here that can't hear it, in its
  own field; it's no longer named as why another machine is missing.
- Advice about this machine's network (Public, a VPN for the internet) is
  left out while a missing machine reports from a network this machine
  can't confirm is its own: a laptop at a café isn't told to make the
  café's Wi-Fi Private, even when the addresses can't be compared.
- Waking from sleep (a beacon round over 30 s late) drops the reports read
  before and restarts the relay session at once; nothing from before
  sleep counts as current until it's read again.
- One unanswered ping is enough to count the connection as dead (timed by
  the ticks themselves).
- A VPN carrying the other machine's internet traffic, on this range and
  hearing nothing, is named as a likely reason; "it hears this machine"
  needs no VPN for the internet on either side.
- While a machine here can't hear this one, reports go out every minute,
  and a fresh report that doesn't list this machine prompts a network look
  straight away, so "a firewall here" shows in about a minute, not six.
- A Windows adapter with no description counts as unknown even when its
  network profile is known.
- Machines whose report says Home network is off aren't "in sync"; with
  someone here, each missing machine's own reason is in the line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Beacons still go to 255.255.255.255 and now also to the directed broadcast
address of each Wi-Fi/Ethernet subnet (e.g. 192.168.1.255). On Windows the
limited broadcast follows the lowest-metric route, usually a VPN's or a
Hyper-V/WSL adapter's, so machines on the same Wi-Fi never heard each other;
a directed broadcast follows its subnet's own route out of that adapter.
Sync now works with a VPN on unless it blocks local traffic. Older builds
receive these unchanged.

The subnet list is one interface listing, refreshed at most every minute on
the existing beacon. With direct sends a VPN on the broadcast route is only
the likely reason ("most likely blocking local traffic", vpn_blocks) and a
virtual adapter none; reports say `direct` so the other side words it the
same. Decision #125 (Proposed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A VPN on the broadcast route is only a guess with direct sends
  (vpn_blocks): ranked after Local Network off and a Public network, left
  out while a missing machine may be elsewhere or hears this one, and never
  given as why a machine heard here can't hear this one.
- The Public/Private profile is read from the home adapter direct sends
  use when a VPN holds the broadcast route.
- Direct sends that all fail count as a failed broadcast (macOS Local
  Network off).
- On Windows only adapters Windows has described get direct sends (each
  asked about once per launch), so no beacon goes into a VPN named
  "Ethernet 3"; the list is worked out again when the internet route's
  source changes and after looking at the network.
- The other machine's VPN with direct sends is named after "another
  network"; a virtual adapter on the route doesn't stop "blocked".
- Changelog no longer promises virtual-adapter reasons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Looking at the network marks the direct list stale instead of dropping
  it, so the diagnosis and report in the same round don't think direct
  sends stopped (which briefly brought back the sure VPN reasons).
- The other machine's VPN isn't named while it hears this machine.
- With direct sends, Public is named only when Windows says so of every
  home adapter (a Public Dante NIC isn't the Wi-Fi the beacons use).
- A direct send the OS accepted clears a failed 255.255.255.255 send.
- Adapter descriptions: one PowerShell run at a time, failures retried
  after 10 minutes, and a network look no longer drops descriptions
  learned meanwhile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bfieser
bfieser changed the base branch from sync/219-lan-compare to main November 7, 2026 02:57
@bfieser
bfieser merged commit 59a7db4 into main Nov 7, 2026
5 checks passed
@bfieser
bfieser deleted the sync/222-lan-every-adapter branch November 7, 2026 03:04
joshuadav86 pushed a commit that referenced this pull request Nov 9, 2026
Brings in the home-network PRs (#218, #221, #223), which took decisions
#123-#125; this change is now decision #126.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Se1kg5gSsJs5yj2HeacfG
joshuadav86 added a commit that referenced this pull request Nov 9, 2026
Version bump and CHANGELOG entry for the Primary machine fix (#220,
decision #126) and the home-network PRs #218, #221 and #223 (decisions
#123-#125).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Se1kg5gSsJs5yj2HeacfG
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.

Home network: send beacons on every home-network adapter, so sync works with a VPN on

1 participant