Compare commits

...
1 Commits
13 changed files with 111 additions and 24 deletions

No files matched your search

+24 -3
View File
@@ -1144,9 +1144,9 @@ Status: Accepted
Context: Sites get subdomains of a selected domain, delegated to the router, without a registrar
change per site. Ubuntu has BIND, Knot and PowerDNS; only PowerDNS's gpgsql backend reads records
from PostgreSQL, so changes are rows, without zone files or reloads. systemd-resolved's stub holds
127.0.0.53 and 127.0.0.54, and its extra stub answered the remote access clients on the external
addresses, the addresses an authoritative server needs. The extra stub answers on the internal
address just as well, since the clients reach it through the tunnel when it is in `local.cidrs`.
127.0.0.53 and 127.0.0.54, and the remote access clients' resolver answered on the external
addresses, the addresses an authoritative server needs. The clients reach the internal address
just as well, through the tunnel when it is in `local.cidrs` (ADR-43).
Decision: While `dns.enabled` is true, setup runs `pdns.service` with
`/etc/powerdns/pdns.d/vpn-router.conf`, written before the packages are installed, on the
@@ -1160,3 +1160,24 @@ shares an interface with it is refused while P2S or OpenVPN is enabled.
Consequences: The zone follows the sites at each setup run. One zone only, without DNSSEC or
transfers; the delegation is made at the parent zone by hand.
## ADR-43: The remote access clients' resolver is dnsmasq, not systemd-resolved
Status: Accepted
Context: IKEv2 and OpenVPN clients were given the router as their DNS server, answered by
systemd-resolved through a `DNSStubListenerExtra` drop-in. resolved synthesizes records for the
machine's own host name, all its addresses, the Docker bridge and the tunnel addresses included,
and for `_gateway` and `localhost`, on every stub, and systemd 255 has no setting that turns this
off. Its extra stub also took port 53 of the addresses PowerDNS needs. dnsmasq-base is in main,
a single binary without a service of its own, and forwards to one upstream server.
Decision: `vpn-router-resolver.service` runs dnsmasq, bound to the addresses of `p2s.dns_listen`
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.
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'
resolver share no address.
+10 -8
View File
@@ -402,7 +402,7 @@ firewall rules. It provisions the user directory in `slapd` while OpenVPN or roa
access is configured. It then starts `strongswan`, `wg-quick@wg0` and
`openvpn-server@vpn-router` if the corresponding feature is configured, reloading or
restarting them only when their inputs changed, and
restarts `systemd-resolved` only when the road-warrior DNS drop-in changed. Turning a
restarts the remote access clients' resolver only when its configuration changed. Turning a
feature off in the configuration stops its service and removes its files. Every step is
idempotent, so restarting with no changes does nothing.
@@ -874,9 +874,11 @@ router should be configured but the platform adds nothing.
The shipped modules currently add nothing. Road-warrior and OpenVPN clients are given the
router's addresses of `p2s.dns_listen` as their DNS servers, by default the internal address,
where the drop-in `/etc/systemd/resolved.conf.d/p2s-forwarder.conf` makes the router's own
`systemd-resolved` answer and the firewall opens port 53 for the client pools, so no provider
resolver has to be advertised or routed through the tunnel on any platform. The clients reach
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
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
the internal address when it is inside `local.cidrs`, which they route through the tunnel. Port 53
of the other addresses stays free for the name server (see Name server).
The modules exist as the documented place for platform specifics if that changes.
@@ -1111,8 +1113,8 @@ read at each request:
| `GET /logs/file?path=<path>&lines=<n>` | `{"lines": [...]}`: the last lines of a file `web.log_files` lists |
The units are the package's own (`vpn-router-setup`, `-web`, `-metrics`, `-pki`, `-backup`,
`-caddy`), `vpn-router-manager`, `caddy`, `slapd`, `nslcd`, `postgresql`, `strongswan`,
`wg-quick@wg0`, `openvpn-server@vpn-router` and `ssh`. `lines` is 1 to 1000, 200 without it. A
`-caddy`, `-resolver`, `-webdav`), `vpn-router-manager`, `caddy`, `slapd`, `nslcd`, `postgresql`,
`strongswan`, `wg-quick@wg0`, `openvpn-server@vpn-router`, `ssh` and `pdns`. `lines` is 1 to 1000, 200 without it. A
file is read from at most its last MiB, so very long lines give fewer lines. A unit or a file not
in the lists is 404, as is a listed file that does not exist.
@@ -1574,8 +1576,8 @@ 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,
systemd-resolved 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
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.
```sh
+15
View File
@@ -1,3 +1,18 @@
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.
* vpn-router-resolver.service runs dnsmasq on the addresses of
p2s.dns_listen, forwarding to systemd-resolved's stub on 127.0.0.53;
names without a dot are not forwarded. The package depends on
dnsmasq-base.
* systemd-resolved keeps its loopback stubs only; setup removes the
drop-in that made it listen for the clients.
* /logs shows the journal of vpn-router-resolver, vpn-router-webdav and
pdns.
-- Slawomir Koszewski <slawek@koszewscy.waw.pl> Tue, 27 Oct 2026 11:00:00 +0100
vpn-router (0.31.1-1) unstable; urgency=medium
fix: the remote access clients' resolver answers on the internal address by default, chosen by p2s.dns_listen, so the name server can take port 53 of the external addresses.
+1
View File
@@ -38,6 +38,7 @@ Depends: ${misc:Depends},
openvpn-auth-ldap,
iproute2,
systemd-resolved,
dnsmasq-base,
zstd
Recommends: bash-completion
Suggests: caddy,
+1
View File
@@ -46,6 +46,7 @@ case "$1" in
rm -f /etc/swanctl/conf.d/site-to-site.conf \
/etc/swanctl/conf.d/road-warrior.conf \
/etc/systemd/resolved.conf.d/p2s-forwarder.conf \
/etc/vpn-router/dnsmasq.conf \
/etc/wireguard/wg0.conf \
/etc/openvpn/server/vpn-router.conf \
/etc/openvpn/server/vpn-router-ldap.conf \
+1
View File
@@ -12,4 +12,5 @@ override_dh_installsystemd:
dh_installsystemd --name=vpn-router-pki --no-enable --no-start
dh_installsystemd --name=vpn-router-caddy --no-enable --no-start
dh_installsystemd --name=vpn-router-webdav --no-enable --no-start
dh_installsystemd --name=vpn-router-resolver --no-enable --no-start
dh_installsystemd --name=vpn-router-install@ --no-enable --no-start
@@ -0,0 +1,15 @@
[Unit]
Description=VPN Router DNS resolver of the remote access clients
Documentation=file:/usr/share/doc/vpn-router/README.md
After=network-online.target systemd-resolved.service
Wants=network-online.target
ConditionPathExists=/etc/vpn-router/dnsmasq.conf
[Service]
Type=simple
ExecStart=/usr/sbin/dnsmasq --keep-in-foreground --pid-file= --log-facility=- --conf-file=/etc/vpn-router/dnsmasq.conf
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
@@ -81,6 +81,9 @@ CADDY_PKI_DIR = pathlib.Path('/etc/caddy/vpn-router-pki')
#: The certificates and keys of the sites of tls file, <site>/cert.pem and key.pem.
CADDY_SITES_DIR = pathlib.Path('/etc/caddy/vpn-router-sites')
#: The unit of dnsmasq, the resolver of the remote access clients.
RESOLVER_UNIT = 'vpn-router-resolver.service'
#: The unit of rclone's WebDAV server of the sites' folders.
WEBDAV_UNIT = 'vpn-router-webdav.service'
@@ -676,6 +679,19 @@ def caddy_address(host, port, tls):
return f'{"http://" if tls == "none" else ""}{host}:{port}'
def manage_resolver(changed):
"""Run dnsmasq, the resolver of the remote access clients, while configure wrote its configuration, restarting it when
that changed, and stop it when not. It starts after systemd-resolved has left the addresses it takes."""
if not vpnrouter.RESOLVER_CONF.exists():
if unit_is_active(RESOLVER_UNIT):
run(['systemctl', 'disable', '--now', RESOLVER_UNIT], check=False)
return
if not unit_is_active(RESOLVER_UNIT):
run(['systemctl', 'enable', '--now', RESOLVER_UNIT], check=False)
elif 'resolver' in changed:
run(['systemctl', 'restart', RESOLVER_UNIT], check=False)
def manage_caddy(cfg, ctx):
"""Run Caddy, the HTTPS proxy of the router's services and the server of the web sites, while either is wanted, and
stop it when not.
@@ -1062,6 +1078,8 @@ def main():
if 'resolved' in changed:
progress('running', 'resolver')
run(['systemctl', 'restart', 'systemd-resolved'], check=False)
progress('running', 'resolver of the remote access clients')
manage_resolver(changed)
progress('running', 'platform module')
run_platform_setup(cfg, ctx)
@@ -36,7 +36,10 @@ TEMPLATE_DIR = pathlib.Path('/usr/share/vpn-router/templates')
SWANCTL_CONF_D = pathlib.Path('/etc/swanctl/conf.d')
SITE_TO_SITE = SWANCTL_CONF_D / 'site-to-site.conf'
ROAD_WARRIOR = SWANCTL_CONF_D / 'road-warrior.conf'
#: The drop-in of systemd-resolved that earlier versions wrote for the remote access clients; removed when found.
P2S_FORWARDER = pathlib.Path('/etc/systemd/resolved.conf.d/p2s-forwarder.conf')
#: The configuration of dnsmasq, the resolver of the remote access clients (vpn-router-resolver.service).
RESOLVER_CONF = pathlib.Path('/etc/vpn-router/dnsmasq.conf')
WG_CONF = pathlib.Path('/etc/wireguard/wg0.conf')
#: The router's WireGuard public key, which setup writes with its private key.
WG_PUBLIC_KEY = pathlib.Path('/etc/wireguard/wg0.pub')
@@ -1172,7 +1175,7 @@ def configure(cfg=None, public_ip=None):
"""Render configuration files and provision PKI.
Returns (ctx, changed) where changed is a set naming what needs reloading:
'swanctl', 'resolved', 'wireguard', 'openvpn'.
'swanctl', 'resolved', 'resolver', 'wireguard', 'openvpn'.
"""
if cfg is None:
cfg = load_config()
@@ -1229,12 +1232,15 @@ def configure(cfg=None, public_ip=None):
elif remove(ROAD_WARRIOR):
changed.add('swanctl')
# The resolver of the remote access clients answers on the addresses of p2s.dns_listen only.
if (p2s_ready(ctx) or openvpn_ready(ctx)) and ctx['p2s_resolver_addrs']:
if render(env, ctx, 'p2s-forwarder.conf.j2', P2S_FORWARDER, 0o644):
changed.add('resolved')
elif remove(P2S_FORWARDER):
# The resolver of the remote access clients is dnsmasq on the addresses of p2s.dns_listen; systemd-resolved keeps
# 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 render(env, ctx, 'dnsmasq.conf.j2', RESOLVER_CONF, 0o644):
changed.add('resolver')
elif remove(RESOLVER_CONF):
changed.add('resolver')
if wg_ready(ctx):
if render_wg_conf(env, ctx):
@@ -4,8 +4,8 @@ No platform-specific configuration is currently required.
The Azure platform resolver 168.63.129.16 is deliberately *not* advertised to
road-warrior clients and *not* added to the traffic selectors. Clients are
given the router's addresses of p2s.dns_listen as their DNS servers, and the router
forwards through its own systemd-resolved via the p2s-forwarder drop-in. That resolves
given the router's addresses of p2s.dns_listen as their DNS servers, where dnsmasq
forwards to the router's own systemd-resolved. That resolves
Azure-internal names without routing the wireserver address through the tunnel
and without a second resolver in the pool.
"""
+1 -1
View File
@@ -171,7 +171,7 @@ POSTGRESQL_SETTINGS = ('server_version', 'listen_addresses', 'port', 'max_connec
#: The units whose journal /logs/journal shows, besides the whole journal: the package's own and the services it runs.
JOURNAL_UNITS = ('vpn-router-setup', 'vpn-router-web', 'vpn-router-metrics', 'vpn-router-pki', 'vpn-router-backup',
'vpn-router-caddy', 'vpn-router-manager', 'caddy', 'slapd', 'nslcd', 'postgresql', 'strongswan',
'wg-quick@wg0', 'openvpn-server@vpn-router', 'ssh')
'wg-quick@wg0', 'openvpn-server@vpn-router', 'ssh', 'vpn-router-resolver', 'vpn-router-webdav', 'pdns')
#: The most lines /logs answers, the number it answers without lines, and the most bytes it reads from the end of a file.
LOG_LINES_MAX = 1000
@@ -0,0 +1,11 @@
# Written by vpn-router-setup from /etc/vpn-router/vpn-router.json; edits are overwritten.
# The resolver of the remote access clients, on the addresses of p2s.dns_listen, forwarding to systemd-resolved's stub.
{% for address in p2s_resolver_addrs -%}
listen-address={{ address }}
{% endfor -%}
bind-interfaces
no-resolv
server=127.0.0.53
domain-needed
no-hosts
cache-size=1000
@@ -1,4 +0,0 @@
[Resolve]
{% for address in p2s_resolver_addrs -%}
DNSStubListenerExtra={{ address }}
{% endfor -%}