Skip to content

fix(desktop,web): advertise all usable network interfaces as selectable endpoints - #5165

Open
William-BnCRocks wants to merge 3 commits into
pingdotgg:mainfrom
William-BnCRocks:t3code/multi-nic-lan-endpoints
Open

fix(desktop,web): advertise all usable network interfaces as selectable endpoints#5165
William-BnCRocks wants to merge 3 commits into
pingdotgg:mainfrom
William-BnCRocks:t3code/multi-nic-lan-endpoints

Conversation

@William-BnCRocks

@William-BnCRocks William-BnCRocks commented Aug 1, 2026

Copy link
Copy Markdown

What Changed

Settings → Connections → Network access advertised a single "Local network" endpoint: the first non-internal IPv4 that os.networkInterfaces() happened to enumerate. On multi-homed machines (VPN + LAN) the VPN adapter often wins, and the addresses other devices can actually reach are never shown or selectable.

Now every usable IPv4 interface is advertised as its own selectable endpoint:

  • resolveLanAdvertisedHostresolveLanAdvertisedHosts: enumerates all non-internal, non-loopback, non-link-local IPv4 addresses with their interface names, deduped. Regular LAN/VPN addresses order ahead of Tailscale CGNAT addresses, so a real NIC wins the default while Tailnet-only machines keep working.
  • One desktop-lan: endpoint per address, labeled with the interface name — "Local network (en0)" — when more than one would show; isDefault stays on the first.
  • Endpoints are re-resolved from live interfaces on every getAdvertisedEndpoints read, so a VPN connecting or dropping after launch is reflected on the settings UI's normal refresh. The backend already binds 0.0.0.0 in network-accessible mode, so any currently-present address is reachable.
  • A Tailnet address that would appear as both "Local network" and "Tailscale IP" keeps only the Tailscale entry; the default marker transfers with it if it was the default.
  • Web: endpointDefaultPreferenceKey now embeds host:port for desktop-lan:/tailscale-ip: endpoints so each interface can individually be set as default. Previously all LAN endpoints collapsed to one key, so with several rows every one would have matched the stored default. A stored legacy key matches nothing and falls back to the isDefault endpoint — no migration needed.
  • Accepts numeric family: 4 from os.networkInterfaces(), matching the existing normalizations in startupAccess.ts:41 and DesktopBackendConfiguration.ts:348.

The wire contract is unchanged: DesktopServerExposureState.advertisedHost/endpointUrl still carry the first host, T3CODE_DESKTOP_LAN_HOST still overrides to a single host, and "no usable address → fall back to local-only" behaves identically. Only the advertised-endpoints list grew.

Verification: 5 new desktop tests (per-interface enumeration/labels/default, numeric family, tailnet dedupe with default transfer, cross-interface address dedupe, live interface changes) and 3 new web tests for the preference keys; full desktop suite 411 passing, web unit suite passing, typecheck and vp check clean.

Why

Fixes #2031. On hosts with several interfaces (VPN/WireGuard + LAN), the auto-picked address is frequently one other devices can't reach, so the "Reachable at" note and copied pairing URLs need hand-editing. #2031 proposed a manual hostname override (#2086, closed as superseded by the endpoint catalog); the catalog can instead offer all interfaces directly, which keeps the copy/pairing flows working with zero typing and lets the user pick a different default per machine.

UI Changes

Before/after screenshots coming before this leaves draft: the Network access row previously listed one "Local network" entry; it now lists one entry per interface with the interface name, each selectable as default.

Before After

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes (n/a)

Note

Advertise all usable network interfaces as selectable LAN endpoints

  • Replaces single-host LAN endpoint advertisement with per-interface discovery: all non-internal, non-duplicate IPv4 addresses are now advertised as distinct endpoints, with non-Tailscale addresses ordered first.
  • Adds isIpv4Family helper in DesktopNetworkInterfaces.ts to normalize Node's numeric 4 family value alongside the string 'IPv4', fixing Tailscale IP discovery when Node uses numeric families.
  • Deduplicates LAN and Tailscale endpoints by httpBaseUrl, transferring the default marker to the surviving Tailscale entry when a LAN endpoint is dropped.
  • Endpoint preference keys in ConnectionsSettings.logic.ts now include the host for desktop-lan and tailscale-ip endpoints, so each address gets a distinct stored default.
  • Behavioral Change: users with multiple NICs will see additional selectable endpoints; previously stored single-host preference keys for LAN/Tailscale-IP endpoints may not match the new host-specific keys.

Macroscope summarized 2ec4b61.


Note

Medium Risk
Changes how LAN endpoints are discovered and how default pairing URLs are chosen, but exposure state and bind behavior stay compatible and the PR adds broad tests plus an intentional one-time preference fallback.

Overview
Multi-homed machines (VPN + LAN) previously surfaced only the first enumerated IPv4 as Local network, so pairing URLs often pointed at an unreachable address (#2031).

Desktop now enumerates every usable non-internal IPv4 via resolveLanAdvertisedHosts, emits one desktop-lan: endpoint per address (labels like Local network (en0) when several NICs qualify), and re-reads os.networkInterfaces() on each getAdvertisedEndpoints so VPN connect/disconnect updates the list without a relaunch. Tailscale CGNAT addresses are ordered after regular LAN IPs for the default host; duplicate URLs drop the plain LAN row and move isDefault to the Tailscale entry. isIpv4Family accepts numeric family: 4 from Node.

Web moves pairing helpers into ConnectionsSettings.logic and makes endpointDefaultPreferenceKey host-specific for LAN and Tailscale IP rows so each interface can be stored as default. Legacy keys like desktop-core:lan:http no longer match and fall back to the isDefault endpoint once.

Reviewed by Cursor Bugbot for commit 2ec4b61. Bugbot is set up for automated code reviews on this repo. Configure here.

…le endpoints

Network access previously advertised only the first non-internal IPv4
that os.networkInterfaces() enumerated, which on multi-homed machines
(VPN + LAN) is often an address other devices cannot reach. Advertise
one selectable endpoint per usable interface instead, re-resolved from
live interfaces on each read, and give each endpoint a distinct default
preference key so any interface can be chosen as the default.

Fixes pingdotgg#2031
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bdee4d4b-60ec-4edc-a498-9e01b866ac73

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 1, 2026
Comment thread apps/web/src/components/settings/ConnectionsSettings.logic.ts Outdated
…keys

Two configured HTTPS endpoints both labeled "Custom HTTPS" produced
identical default-preference keys, so a stored default always resolved
back to the first one. Include the endpoint host in the fallback key,
matching the desktop-lan and tailscale-ip key shapes.
@William-BnCRocks
William-BnCRocks marked this pull request as ready for review August 1, 2026 07:56

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit be40876. Configure here.

Comment thread apps/desktop/src/backend/DesktopServerExposure.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces new user-facing functionality by allowing users to select from all usable network interfaces instead of just one. Changes to endpoint discovery, deduplication logic, and dynamic interface re-resolution represent meaningful runtime behavior changes that warrant human review.

You can customize Macroscope's approvability policy. Learn more.

…ale endpoint provider

Both enumeration sites now use the same isIpv4Family helper, so a tailnet
address reported with a numeric family resolves as its tailscale-ip entry
and dedupes instead of surfacing as a generic LAN endpoint.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Better support for having multiple NICs/IPs

1 participant