Skip to content

feat(tunnel): add per-subnet DNS proxy - #3279

Merged
MattDHill merged 4 commits into
masterfrom
feat/tunnel-dns-proxy
Jun 1, 2026
Merged

feat(tunnel): add per-subnet DNS proxy#3279
MattDHill merged 4 commits into
masterfrom
feat/tunnel-dns-proxy

Conversation

@MattDHill

@MattDHill MattDHill commented May 29, 2026

Copy link
Copy Markdown
Member

Summary

Two related changes that give StartTunnel DNS and unify domain UX across gateway types.

1. Per-subnet DNS proxy in StartTunnel

Operators pick a DNS mode per WireGuard subnet, and clients resolve DNS through the tunnel.

Modes (per subnet):

  • default — forward to the VPS's own system resolvers (/run/systemd/resolve/resolv.conf, with the 127.0.0.53 stub filtered out to avoid a loop)
  • device — forward to a selected device on the subnet (its WireGuard IP)
  • custom — forward to up to 3 operator-entered servers (IP, optional :port)

How it works

  • Client configs now emit DNS = <subnet .1>, so clients send DNS into the tunnel.
  • A lightweight forward-only hickory proxy (one root ForwardAuthority per subnet) binds to that subnet's in-tunnel address (10.59.x.1:53, UDP + TCP). Binding to the in-tunnel address — never 0.0.0.0 — keeps it reachable only over WireGuard, never the WAN (no open resolver).
  • The proxy rebinds after wg-quick whenever subnets change; subnet set-dns re-syncs only the proxy (no tunnel bounce, since the DNS= line is mode-independent).
  • No new dependency — hickory-server already links into the binary; the forwarding path mirrors StartOS's net/dns.rs.

New DnsConfig model on WgSubnetConfig and a subnet set-dns RPC/CLI command, with regenerated TS bindings, i18n (all 5 locales), and man pages. No migration needed — #[serde(default)] covers existing data. Web: DNS is configured in the add/edit subnet form (overflow action is now "Edit"); the subnets table shows a DNS column; all StartTunnel select dropdowns disable the textfield clear-✕ (tuiTextfieldCleaner), matching StartOS's form-select.

2. Private domains on StartTunnel gateways (StartOS UI)

Private domains were gated to router gateways because StartTunnel had no DNS. Now that StartTunnel proxies DNS (above), a private domain resolves for tunnel clients — their DNS forwards to this server, which answers with the gateway's tunnel address. The backend already accepted any gateway (no type check in add_private_domain; check_dns works over the tunnel interface), so this drops the UI-only restriction:

  • All gateways now use the public/private domain picker (removed the wireguard-only "Add Public Domain" branch; removed the now-unused isWireguard flag).
  • The "DNS Server Config" guidance is gateway-aware: for a tunnel gateway it tells the user to point the StartTunnel subnet's DNS at this server (the new DNS settings), rather than the router-centric "LAN IP" wording.

Upgrade note

Existing subnets migrate to default. Already-deployed client .conf files are unaffected until re-downloaded; once re-downloaded they begin sending DNS into the tunnel (→ the VPS's resolvers). To resolve private domains on a tunnel gateway, set that subnet's DNS to this server in StartTunnel.

🤖 Generated with Claude Code

MattDHill and others added 2 commits May 29, 2026 13:28
Each WireGuard subnet picks a DNS mode — default (the VPS's own resolvers),
device (a selected device on the subnet), or custom (up to 3 servers) — and
clients receive `DNS = <subnet .1>` so they resolve through the tunnel. A
forward-only hickory proxy binds per subnet to the in-tunnel address
(10.59.x.1:53), reachable only over WireGuard, and forwards to the configured
upstreams. DNS is configured in the add/edit subnet form and surfaced in the
subnets table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Private domains were gated to router gateways because StartTunnel had no
DNS. Now that StartTunnel proxies DNS per subnet, a private domain resolves
for tunnel clients (their DNS forwards to this server, which answers with the
gateway's tunnel address). The backend already accepted any gateway, so drop
the UI-only restriction: all gateways now use the public/private domain
picker. The "DNS Server Config" guidance is now gateway-aware — for a tunnel
gateway it instructs the user to point the StartTunnel subnet's DNS at this
server. Removes the now-unused isWireguard flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MattDHill
MattDHill requested a review from dr-bonez May 29, 2026 19:58

@dr-bonez dr-bonez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We might need handling for if the system doesn't use systemd-resolved (probably just fall back to parsing /etc/resolv.conf)

The "default" DNS mode prefers systemd-resolved's upstreams
(/run/systemd/resolve/resolv.conf, the real servers rather than the
127.0.0.53 stub) and now falls back to /etc/resolv.conf (via
read_system_conf) whenever that file is absent, unreadable, or yields no
usable servers — covering systems that don't run systemd-resolved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MattDHill
MattDHill requested a review from dr-bonez June 1, 2026 17:32
dr-bonez
dr-bonez previously approved these changes Jun 1, 2026
# Conflicts:
#	web/projects/shared/src/i18n/dictionaries/de.ts
#	web/projects/shared/src/i18n/dictionaries/en.ts
#	web/projects/shared/src/i18n/dictionaries/es.ts
#	web/projects/shared/src/i18n/dictionaries/fr.ts
#	web/projects/shared/src/i18n/dictionaries/pl.ts
@MattDHill
MattDHill merged commit 5a770dd into master Jun 1, 2026
15 checks passed
@MattDHill
MattDHill deleted the feat/tunnel-dns-proxy branch June 1, 2026 17:55
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