fix: WireGuard clients may use the router's DNS resolver, which runs while WireGuard does, and /remote-access names its addresses for their configuration.
Release / release (push) Successful in 14s

This commit is contained in:
slawek committed 2026-10-26 18:33:03 +01:00
1 parent e14bbe2ba1
commit 770332728e
6 files changed
+38 -15

No files matched your search

+2 -1
View File
@@ -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 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 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 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 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' not the router's synthesized ones. The package depends on dnsmasq-base. PowerDNS and the clients'
+6 -5
View File
@@ -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 | | `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` | `enabled` | boolean | `false` | Road-warrior access |
| `p2s` | `address_pool` | string | | CIDR assigned to road-warrior clients | | `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` | `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` | `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 | | `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 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. 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, 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 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 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 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 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 | | 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), `<wan.local_fqdn>:<wireguard.listen_port>` (`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), `<wan.local_fqdn>:<wireguard.listen_port>` (`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/<system>` | any of the settings of the section `p2s`, `openvpn` or `wireguard` | the section | | `PUT /remote-access/<system>` | 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 `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 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 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 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 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. a name server inside the zone.
+14
View File
@@ -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 <slawek@koszewscy.waw.pl> Tue, 27 Oct 2026 12:00:00 +0100
vpn-router (0.31.2-1) unstable; urgency=medium 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. 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.
+3 -1
View File
@@ -243,7 +243,8 @@ def filter_block(ctx):
lines += ['# OPENVPN RULES END'] lines += ['# OPENVPN RULES END']
pools = [pool for ready, pool in ((vpnrouter.p2s_ready(ctx), ctx['p2s_address_pool']), 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. # The remote access clients' resolver answers on the addresses of p2s.dns_listen.
if pools and ctx['p2s_resolver_addrs']: if pools and ctx['p2s_resolver_addrs']:
lines += ['', '# P2S DNS RULES START'] lines += ['', '# P2S DNS RULES START']
@@ -953,6 +954,7 @@ def refresh_web(cfg, ctx):
'internal_address': ctx['int_addr'], 'internal_address': ctx['int_addr'],
'internal_gateway': ctx['int_gateway_ip'], 'internal_gateway': ctx['int_gateway_ip'],
'local_cidrs': ctx['local_cidr_list'], 'local_cidrs': ctx['local_cidr_list'],
'client_dns': ctx['p2s_resolver_addrs'] if vpnrouter.RESOLVER_CONF.exists() else [],
'site_to_site': [{ 'site_to_site': [{
'name': connection['name'], 'name': connection['name'],
'local_identity': connection['local_id'], 'local_identity': connection['local_id'],
@@ -588,11 +588,11 @@ def validate(cfg):
if resolver_listen not in LISTEN_CHOICES: if resolver_listen not in LISTEN_CHOICES:
raise ConfigError(f'p2s.dns_listen: {resolver_listen!r} is not one of {", ".join(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'}} sides = {'internal': {'internal'}, 'external': {'external'}, 'both': {'internal', 'external'}}
if dns['enabled'] and (_get(cfg, 'p2s', 'enabled') or _get(cfg, 'openvpn', 'enabled')) and \ remote_access = any(_get(cfg, system, 'enabled') for system in ('p2s', 'openvpn', 'wireguard'))
sides[dns['listen']] & sides[resolver_listen]: 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]))} ' 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}) ' 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'): for option in ('root_name', 'p2s_name', 's2s_name'):
value = _get(cfg, 'pki', option) 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'])), 'p2s_dns': _join(resolver_addrs, _split_list(extras['EXTRA_P2S_DNS'])),
'wg_enabled': _get(cfg, 'wireguard', 'enabled'), 'wg_enabled': _get(cfg, 'wireguard', 'enabled'),
'wg_address': _get(cfg, 'wireguard', 'address'), '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_listen_port': _get(cfg, 'wireguard', 'listen_port'),
'wg_peers_begin': WG_PEERS_BEGIN, 'wg_peers_begin': WG_PEERS_BEGIN,
'wg_peers_end': WG_PEERS_END, '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. # its loopback stub only, so its records of the router's own name stay on the router.
if remove(P2S_FORWARDER): if remove(P2S_FORWARDER):
changed.add('resolved') 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): if render(env, ctx, 'dnsmasq.conf.j2', RESOLVER_CONF, 0o644):
changed.add('resolver') changed.add('resolver')
elif remove(RESOLVER_CONF): elif remove(RESOLVER_CONF):
+7 -4
View File
@@ -888,9 +888,10 @@ def backup(method, path, body):
def wireguard_server(cfg, state): def wireguard_server(cfg, state):
"""What a WireGuard peer's configuration needs of the router: {public_key, endpoint, allowed_ips}; public_key is """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, <wan.local_fqdn>:<wireguard.listen_port>, null without a name, and null before setup made the key pair, endpoint, <wan.local_fqdn>:<wireguard.listen_port>, null without a name,
allowed_ips the networks reached through the tunnel, the router's local CIDRs and the WireGuard network.""" 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: try:
public_key = vpnrouter.WG_PUBLIC_KEY.read_text(encoding='utf-8').strip() or None public_key = vpnrouter.WG_PUBLIC_KEY.read_text(encoding='utf-8').strip() or None
except OSError: 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 [] allowed = [cidr for cidr in networks if isinstance(cidr, str)] if isinstance(networks, list) else []
if address: if address:
allowed.append(str(ipaddress.ip_interface(address).network)) 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, 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): def remote_access(state, method, path, body):