The issue I raised upstream has now been closed [1], after Roy produced a new 
release of openresolv [2] with support for systemd-resolved.


Unfortunately, IMHO the direction taken in openresolv doesn't help us much with 
the present bug, for the following reasons:

- It violates the principle of least surprise (from the POV of network 
interface configuration), by placing all name servers into the global pool, 
rather than assigning servers and their routing domains to specific network 
interfaces.
I am sure there are valid arguments for doing it that way. But it is completely 
at odds with how resolved is "normally" configured by all the other networking 
tools such as systemd-networkd, NetworkManager and dhclient.
By contrast, almost anyone installing systemd-resolved on Debian is going to 
expect per-interface configuration.

- In any case, an openresolv-based solution still wouldn't circumvent the 
package management issues we currently have in Debian, namely that the 
systemd-resolved package conflicts with anything providing resolvconf.
there's also the problem that we would have to duplicate any new openresolv 
behaviour into the resolvconf package to keep the two in step.


For the above reasons I've given up on the resolvconf approach, decided to eat 
my earlier words and reconsider the "hacky script" technique once more:

I have ported the resolved configuration script (originally borrowed from 
Ubuntu) from dhclient [3] to work with dhcpcd.
This turned out to be completely trivial, since the hook script environments 
provided by dhclient and dhcpcd are so similar.
Basically, it should just work.


See the attached patch.


Functionally speaking, this bespoke script solution has a lot to commend it:

- It configures resolved in the "expected" way, i.e. per interface, the same 
way as NetworkManager and the rest do.

- It integrates with ifupdown settings configured in /etc/network/interfaces:
Specifically, dns-nameservers, dns-search and dns-default-route can optionally 
be set to manually override what comes from DHCP.

- It is low-impact and follows the solution already in use, so we would hope 
for little or nothing in the way of unexpected breakage.


So what's not to like?  Well I have never been too keen on the implementation 
details:

- There is code duplication and shared state between the ifupdown, dhclient and 
(now) dhcpcd packages.
The script needs to be maintained in step between all of these packages, which 
implies fragility.
Strictly speaking, we could avoid this interdependency, but at the cost of 
removing the useful ability to override things in /etc/network/interfaces. 
I think it would be preferable if the same common script could be used by all 
of the above packages:
Probably the central place to put it would be inside the ifupdown package. This 
would not necessarily require a package dependency, if things were handled 
correctly.

- The script modifies some internal resolved state files under 
/run/systemd/resolve/netif/.
I think the only reason for doing this is in case DNS gets configured before 
resolved has started up: I would have thought that might be better handled with 
systemd service dependencies or suchlike?

- There are some other minor things, such as the use of mktemp, which I would 
have preferred to avoid.


Notwithstanding the above wrinkles, I do think it would be worthwhile getting 
something along these lines into trixie:
It will allow someone who has installed a base system with ifupdown to simply 
"apt install systemd-resolved" and still have things work as expected.

If there is any enthusiasm for pursuing this further then I would be prepared 
to do some work on tweaking the solution to order.
Suggestions welcome...


[1] 
https://github.com/NetworkConfiguration/dhcpcd/issues/500#issuecomment-2831237831
[2] https://github.com/NetworkConfiguration/openresolv/releases/tag/v3.15.0
[3] 
https://salsa.debian.org/debian/isc-dhcp/-/commit/ab10fa481ee012c52de95adfc92691c882bf78ac
diff --git a/debian/dhcpcd-base.install b/debian/dhcpcd-base.install
index e941a3bb..ccf78bf5 100644
--- a/debian/dhcpcd-base.install
+++ b/debian/dhcpcd-base.install
@@ -1 +1,2 @@
 debian/tmp/*  /
+debian/hooks/* /usr/lib/dhcpcd/dhcpcd-hooks/
diff --git a/debian/hooks/19-resolved b/debian/hooks/19-resolved
new file mode 100644
index 00000000..46284034
--- /dev/null
+++ b/debian/hooks/19-resolved
@@ -0,0 +1,138 @@
+#!/bin/sh
+## Sourced, not exec'ed, but helps syntax highlighting
+#
+# Script fragment to make dhcpcd supply nameserver information to resolved
+#
+
+# IMPORTANT:
+# This script needs to be maintained in step with the following other files:
+# /etc/network/if-down.d/resolved from the ifupdown package
+# /etc/dhcp/dhclient-exit-hooks.d/resolved from the isc-dhcp-client package
+
+configure_resolved()
+{
+    local mystatedir statedir ifindex
+
+    if [ ! "$interface" ] ; then
+        return
+    fi
+    ifindex=$(cat "/sys/class/net/$interface/ifindex")
+    if [ ! "$ifindex" ]; then
+        return
+    fi
+    mystatedir=/run/network
+    mkdir -p $mystatedir
+
+    statedir=/run/systemd/resolve/netif
+    mkdir -p $statedir
+    chown systemd-resolve:systemd-resolve $statedir
+
+    local oldstate="$(mktemp)"
+    # ignore errors due to nonexistent file
+    md5sum "$mystatedir/isc-dhcp-v4-$interface" "$mystatedir/isc-dhcp-v6-$interface" "$mystatedir/ifupdown-inet-$interface" "$mystatedir/ifupdown-inet6-$interface" > "$oldstate" 2> /dev/null || true
+
+    case "$reason" in
+      BOUND|RENEW|REBIND|REBOOT|TIMEOUT|BOUND6|RENEW6|REBIND6)
+        if [ -n "$new_domain_name_servers" ] ; then
+            cat <<EOF >"$mystatedir/isc-dhcp-v4-$interface"
+DNS="$new_domain_name_servers"
+EOF
+            if [ -n "$new_domain_name" ] || [ -n "$new_domain_search" ] ; then
+                cat <<EOF >>"$mystatedir/isc-dhcp-v4-$interface"
+DOMAINS="$new_domain_search $new_domain_name"
+EOF
+            fi
+        fi
+        if [ -n "$new_dhcp6_name_servers" ] ; then
+            cat <<EOF >"$mystatedir/isc-dhcp-v6-$interface"
+DNS6="$new_dhcp6_name_servers"
+EOF
+            if [ -n "$new_dhcp6_domain_search" ] ; then
+                cat <<EOF >>"$mystatedir/isc-dhcp-v6-$interface"
+DOMAINS6="$new_dhcp6_domain_search"
+EOF
+            fi
+        fi
+        ;;
+
+      EXPIRE|FAIL|RELEASE|STOP)
+        rm -f "/run/network/isc-dhcp-v4-$interface"
+        ;;
+      EXPIRE6|RELEASE6|STOP6)
+        rm -f "/run/network/isc-dhcp-v6-$interface"
+        ;;
+    esac
+
+    local newstate="$(mktemp)"
+    # ignore errors due to nonexistent file
+    md5sum "$mystatedir/isc-dhcp-v4-$interface" "$mystatedir/isc-dhcp-v6-$interface" "$mystatedir/ifupdown-inet-$interface" "$mystatedir/ifupdown-inet6-$interface" > "$newstate" 2> /dev/null || true
+    if ! cmp --silent "$oldstate" "$newstate" 2>/dev/null; then
+        local DNS DNS6 DOMAINS DOMAINS6 DEFAULT_ROUTE
+        # v4 first
+        if [ -e "$mystatedir/isc-dhcp-v4-$interface" ]; then
+            . "$mystatedir/isc-dhcp-v4-$interface"
+        fi
+        # v4 manual config overrides
+        if [ -e "$mystatedir/ifupdown-inet-$interface" ]; then
+            . "$mystatedir/ifupdown-inet-$interface"
+        fi
+        # v6 preffered
+        if [ -e "$mystatedir/isc-dhcp-v6-$interface" ]; then
+            . "$mystatedir/isc-dhcp-v6-$interface"
+        fi
+        # v6 manual config overrides
+        if [ -e "$mystatedir/ifupdown-inet6-$interface" ]; then
+            . "$mystatedir/ifupdown-inet6-$interface"
+        fi
+        local resolvectl_failed=
+        if [ "$DNS" ] || [ "$DNS6" ] ; then
+            cat <<EOF >"$statedir/$ifindex"
+# This is private data. Do not parse.
+LLMNR=yes
+MDNS=no
+SERVERS=$(echo $DNS6 $DNS)
+DOMAINS=$(echo $DOMAINS6 $DOMAINS)
+EOF
+            if [ -n "$DEFAULT_ROUTE" ]; then
+                cat <<EOF >>"$statedir/$ifindex"
+DEFAULT_ROUTE=$DEFAULT_ROUTE
+EOF
+            fi
+            chown systemd-resolve:systemd-resolve "$statedir/$ifindex"
+            # In addition to creating the state file (needed if we run before
+            # resolved is started), also feed the information directly to
+            # resolved.
+            if systemctl --quiet is-active systemd-resolved; then
+                resolvectl llmnr "$ifindex" yes || resolvectl_failed=$?
+                resolvectl mdns "$ifindex" no || resolvectl_failed=$?
+                if [ "$DOMAINS6" ] || [ "$DOMAINS" ]; then
+                    resolvectl domain "$ifindex" $DOMAINS6 $DOMAINS || resolvectl_failed=$?
+                else
+                    resolvectl domain "$ifindex" "" || resolvectl_failed=$?
+                fi
+                resolvectl dns "$ifindex" $DNS6 $DNS || resolvectl_failed=$?
+                if [ "$DEFAULT_ROUTE" ]; then
+                    resolvectl default-route "$ifindex" $DEFAULT_ROUTE || resolvectl_failed=$?
+                fi
+            fi
+        else
+            rm -f "$statedir/$ifindex"
+            if systemctl --quiet is-active systemd-resolved; then
+                resolvectl revert "$ifindex" || resolvectl_failed=$?
+            fi
+        fi
+
+        # resolved was running, but without dbus, it means state files
+        # will not be read & resolvectl commands failed, restart it
+        if [ "$resolvectl_failed" ]; then
+                systemctl try-restart systemd-resolved
+        fi
+    fi
+    rm -f "$oldstate" "$newstate"
+}
+
+if systemctl is-enabled systemd-resolved > /dev/null 2>&1; then
+	configure_resolved
+	# Skip the resolv.conf hook so that we avoid running resolvconf or writing to /etc/resolv.conf
+	skip_hooks="${skip_hooks:-}${skip_hooks+ }resolv.conf"
+fi

Reply via email to