diff --git a/ADR.md b/ADR.md index d7abe18..750b72e 100644 --- a/ADR.md +++ b/ADR.md @@ -1176,7 +1176,8 @@ Decision: `vpn-router-resolver.service` runs dnsmasq, bound to the addresses of only (`bind-interfaces`), forwarding to resolved's stub on 127.0.0.53, without `/etc/hosts` and without forwarding names that have no dot (`domain-needed`). resolved keeps its loopback stubs only and stays the router's own resolver, with the upstream servers of the platform. Setup writes -`/etc/vpn-router/dnsmasq.conf` while IKEv2 or OpenVPN runs and removes the earlier drop-in. +`/etc/vpn-router/dnsmasq.conf` while IKEv2, OpenVPN or WireGuard runs, opens port 53 there for +their client networks, and removes the earlier drop-in. Consequences: Clients resolve the names resolved resolves, Azure's internal names included, but not the router's synthesized ones. The package depends on dnsmasq-base. PowerDNS and the clients' diff --git a/README.md b/README.md index 3d44abe..434b682 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ must have the type shown below; an unknown section or setting is an error. | `site_to_site` | | object | | The site-to-site connections, each a name and an object of the settings below | | `p2s` | `enabled` | boolean | `false` | Road-warrior access | | `p2s` | `address_pool` | string | | CIDR assigned to road-warrior clients | -| `p2s` | `dns_listen` | string | `internal` | The addresses of the resolver the road-warrior and OpenVPN clients are given: `internal`, `external` or `both` | +| `p2s` | `dns_listen` | string | `internal` | The addresses of the resolver the road-warrior, OpenVPN and WireGuard clients are given: `internal`, `external` or `both` | | `wireguard` | `enabled` | boolean | `false` | WireGuard endpoint | | `wireguard` | `address` | string | | The router's own address on `wg0` with the prefix of the peers' network, for example `192.168.200.1/24`; the network and the broadcast address of a network larger than /31 are refused | | `wireguard` | `listen_port` | integer | `51820` | UDP port | @@ -872,10 +872,11 @@ deploying. which is how a deferred install is expressed. `generic` is the module to choose when the router should be configured but the platform adds nothing. -The shipped modules currently add nothing. Road-warrior and OpenVPN clients are given the +The shipped modules currently add nothing. Road-warrior, OpenVPN and WireGuard clients are given the router's addresses of `p2s.dns_listen` as their DNS servers, by default the internal address, +the WireGuard clients in the configuration the Router Manager makes for a device (`DNS =`), where `vpn-router-resolver.service`, dnsmasq with `/etc/vpn-router/dnsmasq.conf`, answers and -the firewall opens port 53 for the client pools, so no provider resolver has to be advertised or +the firewall opens port 53 for the client pools and the WireGuard network, so no provider resolver has to be advertised or routed through the tunnel on any platform. dnsmasq forwards to `systemd-resolved` on 127.0.0.53, which keeps only its loopback stubs, and forwards no name without a dot, so the router's own host name, `_gateway` and `localhost` are not answered to the clients. The clients reach @@ -1310,7 +1311,7 @@ OpenVPN (`openvpn`) and WireGuard (`wireguard`), with the same token as `/users` | Request | Body | Answer | | --- | --- | --- | -| `GET /remote-access` | | `{"p2s", "openvpn", "wireguard", "running", "local_fqdn", "wireguard_server"}`; `wireguard_server` is `{"public_key", "endpoint", "allowed_ips"}`: the router's WireGuard public key (`null` before setup made it), `:` (`null` without a name) and the networks a peer reaches through the tunnel, the router's local CIDRs and the WireGuard network | +| `GET /remote-access` | | `{"p2s", "openvpn", "wireguard", "running", "local_fqdn", "wireguard_server"}`; `wireguard_server` is `{"public_key", "endpoint", "allowed_ips", "dns"}`: the router's WireGuard public key (`null` before setup made it), `:` (`null` without a name), the networks a peer reaches through the tunnel, the router's local CIDRs and the WireGuard network, and the addresses of the remote access clients' resolver (`[]` while it does not run) | | `PUT /remote-access/` | any of the settings of the section `p2s`, `openvpn` or `wireguard` | the section | `running` tells for each system whether setup runs it. IKEv2 and OpenVPN run once enabled with an @@ -1575,7 +1576,7 @@ missing, without the recommended bind backend. Records added with `/dns` or the Router Manager (A, AAAA, CNAME, TXT, MX, SRV, CAA) are served too, but not those of a name and type the zone's own records have. Setup writes the zone at each -run; the SOA's serial grows only when the records change. While P2S or OpenVPN is enabled, +run; the SOA's serial grows only when the records change. While P2S, OpenVPN or WireGuard is enabled, dnsmasq answers their clients on port 53 of the addresses of `p2s.dns_listen`, by default the internal address, so `dns.listen` must not share an interface with it. The parent zone must delegate `dns.zone` to `dns.nameserver`, with glue records for a name server inside the zone. diff --git a/debian-package/debian/changelog b/debian-package/debian/changelog index 5dab883..34e691c 100644 --- a/debian-package/debian/changelog +++ b/debian-package/debian/changelog @@ -1,3 +1,17 @@ +vpn-router (0.31.3-1) unstable; urgency=medium + + fix: WireGuard clients may use the router's DNS resolver, which runs while WireGuard does, and /remote-access names its addresses for their configuration. + + * The firewall opens port 53 of the remote access clients' resolver for + the WireGuard network, as it does for the IKEv2 and OpenVPN pools. + * The resolver runs while WireGuard is the only remote access enabled. + * GET /remote-access answers the resolver's addresses as + wireguard_server.dns. + * dns.listen may not share an interface with p2s.dns_listen while + WireGuard is enabled either. + + -- Slawomir Koszewski Tue, 27 Oct 2026 12:00:00 +0100 + vpn-router (0.31.2-1) unstable; urgency=medium fix: dnsmasq answers the remote access clients' DNS queries, so they no longer get the router's own host name with all its addresses from systemd-resolved. diff --git a/debian-package/src/usr/lib/vpn-router/setup b/debian-package/src/usr/lib/vpn-router/setup index 0f3ec4f..dae83f9 100755 --- a/debian-package/src/usr/lib/vpn-router/setup +++ b/debian-package/src/usr/lib/vpn-router/setup @@ -243,7 +243,8 @@ def filter_block(ctx): lines += ['# OPENVPN RULES END'] pools = [pool for ready, pool in ((vpnrouter.p2s_ready(ctx), ctx['p2s_address_pool']), - (vpnrouter.openvpn_ready(ctx), ctx['openvpn_pool'])) if ready] + (vpnrouter.openvpn_ready(ctx), ctx['openvpn_pool']), + (vpnrouter.wg_ready(ctx), ctx['wg_network'])) if ready] # The remote access clients' resolver answers on the addresses of p2s.dns_listen. if pools and ctx['p2s_resolver_addrs']: lines += ['', '# P2S DNS RULES START'] @@ -953,6 +954,7 @@ def refresh_web(cfg, ctx): 'internal_address': ctx['int_addr'], 'internal_gateway': ctx['int_gateway_ip'], 'local_cidrs': ctx['local_cidr_list'], + 'client_dns': ctx['p2s_resolver_addrs'] if vpnrouter.RESOLVER_CONF.exists() else [], 'site_to_site': [{ 'name': connection['name'], 'local_identity': connection['local_id'], diff --git a/debian-package/src/usr/lib/vpn-router/vpnrouter.py b/debian-package/src/usr/lib/vpn-router/vpnrouter.py index 750d092..4da13c8 100644 --- a/debian-package/src/usr/lib/vpn-router/vpnrouter.py +++ b/debian-package/src/usr/lib/vpn-router/vpnrouter.py @@ -588,11 +588,11 @@ def validate(cfg): if resolver_listen not in LISTEN_CHOICES: raise ConfigError(f'p2s.dns_listen: {resolver_listen!r} is not one of {", ".join(LISTEN_CHOICES)}') sides = {'internal': {'internal'}, 'external': {'external'}, 'both': {'internal', 'external'}} - if dns['enabled'] and (_get(cfg, 'p2s', 'enabled') or _get(cfg, 'openvpn', 'enabled')) and \ - sides[dns['listen']] & sides[resolver_listen]: + remote_access = any(_get(cfg, system, 'enabled') for system in ('p2s', 'openvpn', 'wireguard')) + if dns['enabled'] and remote_access and sides[dns['listen']] & sides[resolver_listen]: raise ConfigError(f'dns.listen: port 53 of the {" and ".join(sorted(sides[dns["listen"]] & sides[resolver_listen]))} ' f'addresses is the resolver of the remote access clients (p2s.dns_listen {resolver_listen}) ' - 'while p2s.enabled or openvpn.enabled is true') + 'while p2s.enabled, openvpn.enabled or wireguard.enabled is true') for option in ('root_name', 'p2s_name', 's2s_name'): value = _get(cfg, 'pki', option) @@ -993,6 +993,8 @@ def build_context(cfg, public_ip=None): 'p2s_dns': _join(resolver_addrs, _split_list(extras['EXTRA_P2S_DNS'])), 'wg_enabled': _get(cfg, 'wireguard', 'enabled'), 'wg_address': _get(cfg, 'wireguard', 'address'), + 'wg_network': str(ipaddress.ip_interface(_get(cfg, 'wireguard', 'address')).network) + if _get(cfg, 'wireguard', 'address') else '', 'wg_listen_port': _get(cfg, 'wireguard', 'listen_port'), 'wg_peers_begin': WG_PEERS_BEGIN, 'wg_peers_end': WG_PEERS_END, @@ -1236,7 +1238,7 @@ def configure(cfg=None, public_ip=None): # its loopback stub only, so its records of the router's own name stay on the router. if remove(P2S_FORWARDER): changed.add('resolved') - if (p2s_ready(ctx) or openvpn_ready(ctx)) and ctx['p2s_resolver_addrs']: + if (p2s_ready(ctx) or openvpn_ready(ctx) or wg_ready(ctx)) and ctx['p2s_resolver_addrs']: if render(env, ctx, 'dnsmasq.conf.j2', RESOLVER_CONF, 0o644): changed.add('resolver') elif remove(RESOLVER_CONF): diff --git a/debian-package/src/usr/lib/vpn-router/web b/debian-package/src/usr/lib/vpn-router/web index eaca11c..6db13e3 100644 --- a/debian-package/src/usr/lib/vpn-router/web +++ b/debian-package/src/usr/lib/vpn-router/web @@ -888,9 +888,10 @@ def backup(method, path, body): def wireguard_server(cfg, state): - """What a WireGuard peer's configuration needs of the router: {public_key, endpoint, allowed_ips}; public_key is - null before setup made the key pair, endpoint, :, null without a name, and - allowed_ips the networks reached through the tunnel, the router's local CIDRs and the WireGuard network.""" + """What a WireGuard peer's configuration needs of the router: {public_key, endpoint, allowed_ips, dns}; public_key is + null before setup made the key pair, endpoint, :, null without a name, + allowed_ips the networks reached through the tunnel, the router's local CIDRs and the WireGuard network, and dns the + addresses of the remote access clients' resolver, [] while it does not run.""" try: public_key = vpnrouter.WG_PUBLIC_KEY.read_text(encoding='utf-8').strip() or None except OSError: @@ -901,8 +902,10 @@ def wireguard_server(cfg, state): allowed = [cidr for cidr in networks if isinstance(cidr, str)] if isinstance(networks, list) else [] if address: allowed.append(str(ipaddress.ip_interface(address).network)) + dns = section(state, 'network').get('client_dns') return {'public_key': public_key, 'endpoint': f'{fqdn}:{cfg["wireguard"]["listen_port"]}' if fqdn else None, - 'allowed_ips': list(dict.fromkeys(allowed))} + 'allowed_ips': list(dict.fromkeys(allowed)), + 'dns': [address for address in dns if isinstance(address, str)] if isinstance(dns, list) else []} def remote_access(state, method, path, body):