Skip to content

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

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

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:

  • resolveLanAdvertisedHost → resolveLanAdvertisedHosts: 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 endpoints in desktop and web

  • Replaces single-LAN-endpoint logic with resolveLanAdvertisedHosts in DesktopServerExposure.ts, which iterates all interfaces, filters to non-loopback IPv4 addresses, deduplicates by IP, and returns LAN addresses followed by Tailscale addresses.
  • resolveDesktopCoreAdvertisedEndpoints now emits one AdvertisedEndpoint per resolved LAN host; labels include the interface name (e.g. "Local network (en0)") when multiple non-Tailscale interfaces are present.
  • getAdvertisedEndpoints re-reads interfaces on every call to reflect VPN/NIC changes without reconfiguration, and deduplicates LAN entries that share an IP with a Tailscale endpoint, transferring the isDefault flag when needed.
  • Adds isIpv4Family helper in DesktopNetworkInterfaces.ts to treat numeric family 4 as IPv4, fixing Tailscale IP discovery in those environments.
  • Adds endpointDefaultPreferenceKey in ConnectionsSettings.logic.ts that encodes host information in preference keys to avoid collisions between multiple LAN or Tailscale-IP endpoints sharing the same label.

Macroscope summarized 3ab2ef5.


Note

Medium Risk
Changes how advertised LAN endpoints and stored default preferences are computed; multi-NIC users may see more endpoints and old single-key LAN defaults may not match until they pick a default again—pairing URL selection behavior is user-visible but bounded to network exposure settings.

Overview
Network-accessible desktop now discovers every usable IPv4 (non-internal, non-link-local, deduped), not just the first interface—so VPN + LAN machines get separate Local network rows (with interface names when there are multiple) and pairing/copy URLs can target the address peers can actually reach.

Desktop exposure re-resolves interfaces on each getAdvertisedEndpoints read (VPN connect/disconnect), orders non-Tailscale addresses before tailnet IPs for the primary/default host, and drops duplicate LAN rows when Tailscale already advertises the same URL—moving isDefault to the Tailscale entry. isIpv4Family centralizes handling of Node’s numeric family: 4.

Web Connections moves pairing/default helpers into ConnectionsSettings.logic and changes endpointDefaultPreferenceKey to include host:port per desktop-lan / tailscale-ip so each interface can be saved as default; legacy keys fall back to the marked default endpoint.

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

@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: e666ce5f-4f66-4b1a-8e1a-ca185e3d7a0b

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
@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 be408765dc46133c4e0939e42758301e3e99e290. 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 significant runtime behavior changes—advertising multiple network interfaces instead of one, changing UI display, and altering preference key format. From an unvouched contributor, these user-facing changes warrant human review.

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

…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
…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.
…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.

Copy link
Copy Markdown
Member

Note

This comment is posted by Julius' dot

Closing under the one-problem rule. Alongside advertising missing LAN interfaces, endpointDefaultPreferenceKey now fixes collisions between manually configured HTTPS endpoints with the same label. That defect exists without multiple NICs; your reply also identifies it as pre-existing.

For reconsideration, split the manual-endpoint key change and its regression test into a separate PR. Keep the LAN enumeration, its required per-interface keys, and related tests together.

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

2 participants