[libvirt PATCH 0/7] Configure systemd-resolved when starting networks

See 6/7 for more details. Jiri Denemark (7): util: Refactor virSystemdHas{Machined,Logind} util: Introduce virSystemdHasResolved util: Introduce virSocketAddrBytes util: Introduce virSystemdResolvedRegisterNameServer tests: Add tests for virSystemdResolvedRegisterNameServer network: Make virtual domains resolvable from the host NEWS: Mention systemd-resolved support in network driver NEWS.rst | 7 ++ docs/formatnetwork.rst | 9 +- src/conf/network_conf.c | 18 ++++ src/conf/network_conf.h | 1 + src/conf/schemas/network.rng | 3 + src/libvirt_private.syms | 4 + src/network/bridge_driver.c | 32 ++++++- src/util/virsocketaddr.c | 63 +++++++++++++ src/util/virsocketaddr.h | 4 + src/util/virsystemd.c | 166 +++++++++++++++++++++++++++-------- src/util/virsystemd.h | 7 ++ src/util/virsystemdpriv.h | 1 + tests/virsystemdtest.c | 147 +++++++++++++++++++++++++++++++ 13 files changed, 421 insertions(+), 41 deletions(-) -- 2.43.0

To reduce code duplication both function now use a common virSystemdHasService helper. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/util/virsystemd.c | 76 ++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index cd4de0eef8..21892aca02 100644 --- a/src/util/virsystemd.c +++ b/src/util/virsystemd.c @@ -141,66 +141,62 @@ void virSystemdHasLogindResetCachedValue(void) } -/* -2 = machine1 is not supported on this machine - * -1 = error - * 0 = machine1 is available +/** + * virSystemdHasService: + * + * Check whether a DBus @service is enabled and either the service itself or + * systemd1 service is registered. If @requireSystemd == true, the systemd1 + * service has to be registered even if @service is registered. + * + * Returns + * -2 when service is not supported on this machine + * -1 on error + * 0 when service is available */ -int -virSystemdHasMachined(void) +static int +virSystemdHasService(const char *service, + bool requireSystemd, + int *cached) { int ret; int val; - val = g_atomic_int_get(&virSystemdHasMachinedCachedValue); + val = g_atomic_int_get(cached); if (val != -1) return val; - if ((ret = virGDBusIsServiceEnabled("org.freedesktop.machine1")) < 0) { + if ((ret = virGDBusIsServiceEnabled(service)) < 0) { if (ret == -2) - g_atomic_int_set(&virSystemdHasMachinedCachedValue, -2); + g_atomic_int_set(cached, -2); return ret; } - if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) + if ((ret = virGDBusIsServiceRegistered(service)) == -1) return ret; - g_atomic_int_set(&virSystemdHasMachinedCachedValue, ret); + + if (requireSystemd || ret == -2) { + if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) + return ret; + } + + g_atomic_int_set(cached, ret); return ret; } + int -virSystemdHasLogind(void) +virSystemdHasMachined(void) { - int ret; - int val; - - val = g_atomic_int_get(&virSystemdHasLogindCachedValue); - if (val != -1) - return val; - - ret = virGDBusIsServiceEnabled("org.freedesktop.login1"); - if (ret < 0) { - if (ret == -2) - g_atomic_int_set(&virSystemdHasLogindCachedValue, -2); - return ret; - } - - /* - * Want to use logind if: - * - logind is already running - * Or - * - logind is not running, but this is a systemd host - * (rely on dbus activation) - */ - if ((ret = virGDBusIsServiceRegistered("org.freedesktop.login1")) == -1) - return ret; + return virSystemdHasService("org.freedesktop.machine1", true, + &virSystemdHasMachinedCachedValue); +} - if (ret == -2) { - if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) - return ret; - } - g_atomic_int_set(&virSystemdHasLogindCachedValue, ret); - return ret; +int +virSystemdHasLogind(void) +{ + return virSystemdHasService("org.freedesktop.login1", false, + &virSystemdHasLogindCachedValue); } -- 2.43.0

On a Thursday in 2024, Jiri Denemark wrote:
To reduce code duplication both function now use a common virSystemdHasService helper.
Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/util/virsystemd.c | 76 ++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 40 deletions(-)
diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index cd4de0eef8..21892aca02 100644 --- a/src/util/virsystemd.c +++ b/src/util/virsystemd.c @@ -141,66 +141,62 @@ void virSystemdHasLogindResetCachedValue(void) }
-/* -2 = machine1 is not supported on this machine - * -1 = error - * 0 = machine1 is available +/** + * virSystemdHasService: + * + * Check whether a DBus @service is enabled and either the service itself or + * systemd1 service is registered. If @requireSystemd == true, the systemd1 + * service has to be registered even if @service is registered. + * + * Returns + * -2 when service is not supported on this machine + * -1 on error + * 0 when service is available */ -int -virSystemdHasMachined(void) +static int +virSystemdHasService(const char *service, + bool requireSystemd, + int *cached) { int ret; int val;
- val = g_atomic_int_get(&virSystemdHasMachinedCachedValue); + val = g_atomic_int_get(cached); if (val != -1) return val;
- if ((ret = virGDBusIsServiceEnabled("org.freedesktop.machine1")) < 0) { + if ((ret = virGDBusIsServiceEnabled(service)) < 0) { if (ret == -2) - g_atomic_int_set(&virSystemdHasMachinedCachedValue, -2); + g_atomic_int_set(cached, -2); return ret; }
- if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) + if ((ret = virGDBusIsServiceRegistered(service)) == -1) return ret;
From the 'refactor' point of view, this adds the extra check whether machine1 is also registered. So the bool should rather be 'skipRunningCheck' or something like that. From a practical point of view, this seems pointless if we discard the result anyway. Admittedly, the terminology I added in: commit 12ee0b98d3ddcb2c71cdb2352512153791ebcb7e Check if systemd is running before creating machines is a bit confusing. But if "machine1" is "Enabled" (i.e. in "ListActivatableNames"), that could have meant that there is systemd installed but the system was not booted with it. If it returns -1, the following (unreachable) call would likely return -1 as well. If it returns 0, we don't need to check whether systemd1 is registered, because we already know machined is running. If it returns -2, we did not really learn anything new.
- g_atomic_int_set(&virSystemdHasMachinedCachedValue, ret); + + if (requireSystemd || ret == -2) { + if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) + return ret; + } + + g_atomic_int_set(cached, ret); return ret; }
+ int -virSystemdHasLogind(void) +virSystemdHasMachined(void) { - int ret; - int val; - - val = g_atomic_int_get(&virSystemdHasLogindCachedValue); - if (val != -1) - return val; - - ret = virGDBusIsServiceEnabled("org.freedesktop.login1"); - if (ret < 0) { - if (ret == -2) - g_atomic_int_set(&virSystemdHasLogindCachedValue, -2); - return ret; - } - - /* - * Want to use logind if: - * - logind is already running - * Or - * - logind is not running, but this is a systemd host - * (rely on dbus activation) - */ - if ((ret = virGDBusIsServiceRegistered("org.freedesktop.login1")) == -1) - return ret;
Is there a possibility of logind running on a host not booted with systemd? If not, this check is also pointless with the fallback below. (Same question applies to resolved, but is not relevant to this patch) Jano
+ return virSystemdHasService("org.freedesktop.machine1", true, + &virSystemdHasMachinedCachedValue); +}
- if (ret == -2) { - if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) - return ret; - }
- g_atomic_int_set(&virSystemdHasLogindCachedValue, ret); - return ret; +int +virSystemdHasLogind(void) +{ + return virSystemdHasService("org.freedesktop.login1", false, + &virSystemdHasLogindCachedValue); }
-- 2.43.0 _______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org

On Fri, Feb 02, 2024 at 01:57:37PM +0100, Ján Tomko wrote:
On a Thursday in 2024, Jiri Denemark wrote:
To reduce code duplication both function now use a common virSystemdHasService helper.
Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/util/virsystemd.c | 76 ++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 40 deletions(-)
diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index cd4de0eef8..21892aca02 100644 --- a/src/util/virsystemd.c +++ b/src/util/virsystemd.c @@ -141,66 +141,62 @@ void virSystemdHasLogindResetCachedValue(void) }
-/* -2 = machine1 is not supported on this machine - * -1 = error - * 0 = machine1 is available +/** + * virSystemdHasService: + * + * Check whether a DBus @service is enabled and either the service itself or + * systemd1 service is registered. If @requireSystemd == true, the systemd1 + * service has to be registered even if @service is registered. + * + * Returns + * -2 when service is not supported on this machine + * -1 on error + * 0 when service is available */ -int -virSystemdHasMachined(void) +static int +virSystemdHasService(const char *service, + bool requireSystemd, + int *cached) { int ret; int val;
- val = g_atomic_int_get(&virSystemdHasMachinedCachedValue); + val = g_atomic_int_get(cached); if (val != -1) return val;
- if ((ret = virGDBusIsServiceEnabled("org.freedesktop.machine1")) < 0) { + if ((ret = virGDBusIsServiceEnabled(service)) < 0) { if (ret == -2) - g_atomic_int_set(&virSystemdHasMachinedCachedValue, -2); + g_atomic_int_set(cached, -2); return ret; }
- if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) + if ((ret = virGDBusIsServiceRegistered(service)) == -1) return ret;
From the 'refactor' point of view, this adds the extra check whether machine1 is also registered. So the bool should rather be 'skipRunningCheck' or something like that.
From a practical point of view, this seems pointless if we discard the result anyway. Admittedly, the terminology I added in: commit 12ee0b98d3ddcb2c71cdb2352512153791ebcb7e Check if systemd is running before creating machines is a bit confusing. But if "machine1" is "Enabled" (i.e. in "ListActivatableNames"), that could have meant that there is systemd installed but the system was not booted with it.
If it returns -1, the following (unreachable) call would likely return -1 as well. If it returns 0, we don't need to check whether systemd1 is registered, because we already know machined is running. If it returns -2, we did not really learn anything new.
- g_atomic_int_set(&virSystemdHasMachinedCachedValue, ret); + + if (requireSystemd || ret == -2) { + if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) + return ret; + } + + g_atomic_int_set(cached, ret); return ret; }
+ int -virSystemdHasLogind(void) +virSystemdHasMachined(void) { - int ret; - int val; - - val = g_atomic_int_get(&virSystemdHasLogindCachedValue); - if (val != -1) - return val; - - ret = virGDBusIsServiceEnabled("org.freedesktop.login1"); - if (ret < 0) { - if (ret == -2) - g_atomic_int_set(&virSystemdHasLogindCachedValue, -2); - return ret; - } - - /* - * Want to use logind if: - * - logind is already running - * Or - * - logind is not running, but this is a systemd host - * (rely on dbus activation) - */ - if ((ret = virGDBusIsServiceRegistered("org.freedesktop.login1")) == -1) - return ret;
Is there a possibility of logind running on a host not booted with systemd? If not, this check is also pointless with the fallback below.
Sure, yes, I believe so.
(Same question applies to resolved, but is not relevant to this patch)
But I'm not sure about resolved. From another point of view, someone could just write that (or maybe use the systemd one) as a drop-in replacement that does not need systemd. I'd rather check for something that we think "can't ever be false" than coming back to the issue later on. Martin
Jano
+ return virSystemdHasService("org.freedesktop.machine1", true, + &virSystemdHasMachinedCachedValue); +}
- if (ret == -2) { - if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) - return ret; - }
- g_atomic_int_set(&virSystemdHasLogindCachedValue, ret); - return ret; +int +virSystemdHasLogind(void) +{ + return virSystemdHasService("org.freedesktop.login1", false, + &virSystemdHasLogindCachedValue); }
-- 2.43.0 _______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org
_______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org

On Fri, Feb 02, 2024 at 13:57:37 +0100, Ján Tomko wrote:
On a Thursday in 2024, Jiri Denemark wrote:
To reduce code duplication both function now use a common virSystemdHasService helper.
Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/util/virsystemd.c | 76 ++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 40 deletions(-)
diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index cd4de0eef8..21892aca02 100644 --- a/src/util/virsystemd.c +++ b/src/util/virsystemd.c @@ -141,66 +141,62 @@ void virSystemdHasLogindResetCachedValue(void) }
-/* -2 = machine1 is not supported on this machine - * -1 = error - * 0 = machine1 is available +/** + * virSystemdHasService: + * + * Check whether a DBus @service is enabled and either the service itself or + * systemd1 service is registered. If @requireSystemd == true, the systemd1 + * service has to be registered even if @service is registered. + * + * Returns + * -2 when service is not supported on this machine + * -1 on error + * 0 when service is available */ -int -virSystemdHasMachined(void) +static int +virSystemdHasService(const char *service, + bool requireSystemd, + int *cached) { int ret; int val;
- val = g_atomic_int_get(&virSystemdHasMachinedCachedValue); + val = g_atomic_int_get(cached); if (val != -1) return val;
- if ((ret = virGDBusIsServiceEnabled("org.freedesktop.machine1")) < 0) { + if ((ret = virGDBusIsServiceEnabled(service)) < 0) { if (ret == -2) - g_atomic_int_set(&virSystemdHasMachinedCachedValue, -2); + g_atomic_int_set(cached, -2); return ret; }
- if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) + if ((ret = virGDBusIsServiceRegistered(service)) == -1) return ret;
From the 'refactor' point of view, this adds the extra check whether machine1 is also registered. So the bool should rather be 'skipRunningCheck' or something like that.
From a practical point of view, this seems pointless if we discard the result anyway. Admittedly, the terminology I added in: commit 12ee0b98d3ddcb2c71cdb2352512153791ebcb7e Check if systemd is running before creating machines is a bit confusing. But if "machine1" is "Enabled" (i.e. in "ListActivatableNames"), that could have meant that there is systemd installed but the system was not booted with it.
If it returns -1, the following (unreachable) call would likely return -1 as well. If it returns 0, we don't need to check whether systemd1 is registered, because we already know machined is running. If it returns -2, we did not really learn anything new.
I see, so we actually do not care about systemd running, we just need machined service to be either running or started by socket activation, that is the same thing we do for other systemd services.
- g_atomic_int_set(&virSystemdHasMachinedCachedValue, ret); + + if (requireSystemd || ret == -2) { + if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) + return ret; + } + + g_atomic_int_set(cached, ret); return ret; }
+ int -virSystemdHasLogind(void) +virSystemdHasMachined(void) { - int ret; - int val; - - val = g_atomic_int_get(&virSystemdHasLogindCachedValue); - if (val != -1) - return val; - - ret = virGDBusIsServiceEnabled("org.freedesktop.login1"); - if (ret < 0) { - if (ret == -2) - g_atomic_int_set(&virSystemdHasLogindCachedValue, -2); - return ret; - } - - /* - * Want to use logind if: - * - logind is already running - * Or - * - logind is not running, but this is a systemd host - * (rely on dbus activation) - */ - if ((ret = virGDBusIsServiceRegistered("org.freedesktop.login1")) == -1) - return ret;
Is there a possibility of logind running on a host not booted with systemd? If not, this check is also pointless with the fallback below.
Yes, it is. For example Gentoo provides a separate logind daemon for openrc based installations.
(Same question applies to resolved, but is not relevant to this patch)
I don't know, but I guess it could exist similarly to logind. Jirka

Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/libvirt_private.syms | 2 ++ src/util/virsystemd.c | 15 +++++++++++++++ src/util/virsystemd.h | 2 ++ src/util/virsystemdpriv.h | 1 + 4 files changed, 20 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 2ebc0de212..6b4fe17e20 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3479,6 +3479,8 @@ virSystemdHasLogind; virSystemdHasLogindResetCachedValue; virSystemdHasMachined; virSystemdHasMachinedResetCachedValue; +virSystemdHasResolved; +virSystemdHasResolvedResetCachedValue; virSystemdMakeScopeName; virSystemdMakeSliceName; virSystemdNotifyStartup; diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index 21892aca02..0dfc4398d0 100644 --- a/src/util/virsystemd.c +++ b/src/util/virsystemd.c @@ -127,6 +127,7 @@ char *virSystemdMakeSliceName(const char *partition) static int virSystemdHasMachinedCachedValue = -1; static int virSystemdHasLogindCachedValue = -1; +static int virSystemdHasResolvedCachedValue = -1; /* Reset the cache from tests for testing the underlying dbus calls * as well */ @@ -140,6 +141,12 @@ void virSystemdHasLogindResetCachedValue(void) virSystemdHasLogindCachedValue = -1; } +void +virSystemdHasResolvedResetCachedValue(void) +{ + virSystemdHasResolvedCachedValue = -1; +} + /** * virSystemdHasService: @@ -200,6 +207,14 @@ virSystemdHasLogind(void) } +int +virSystemdHasResolved(void) +{ + return virSystemdHasService("org.freedesktop.resolve1", false, + &virSystemdHasResolvedCachedValue); +} + + /** * virSystemdGetMachineByPID: * @conn: dbus connection diff --git a/src/util/virsystemd.h b/src/util/virsystemd.h index 19fb714132..65add8b5b9 100644 --- a/src/util/virsystemd.h +++ b/src/util/virsystemd.h @@ -49,6 +49,8 @@ int virSystemdHasMachined(void); int virSystemdHasLogind(void); +int virSystemdHasResolved(void); + int virSystemdCanSuspend(bool *result); int virSystemdCanHibernate(bool *result); diff --git a/src/util/virsystemdpriv.h b/src/util/virsystemdpriv.h index 736c53d3fa..1f1dda456c 100644 --- a/src/util/virsystemdpriv.h +++ b/src/util/virsystemdpriv.h @@ -29,3 +29,4 @@ void virSystemdHasMachinedResetCachedValue(void); void virSystemdHasLogindResetCachedValue(void); +void virSystemdHasResolvedResetCachedValue(void); -- 2.43.0

Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virsocketaddr.c | 63 ++++++++++++++++++++++++++++++++++++++++ src/util/virsocketaddr.h | 4 +++ 3 files changed, 68 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 6b4fe17e20..0be273511d 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3368,6 +3368,7 @@ virSocketSendFD; # util/virsocketaddr.h virSocketAddrBroadcast; virSocketAddrBroadcastByPrefix; +virSocketAddrBytes; virSocketAddrCheckNetmask; virSocketAddrEqual; virSocketAddrFormat; diff --git a/src/util/virsocketaddr.c b/src/util/virsocketaddr.c index fbda858cfe..0a1ae1ac5f 100644 --- a/src/util/virsocketaddr.c +++ b/src/util/virsocketaddr.c @@ -30,6 +30,7 @@ */ typedef unsigned char virSocketAddrIPv4[4]; typedef unsigned short virSocketAddrIPv6[8]; +typedef unsigned char virSocketAddrIPv6Bytes[16]; typedef unsigned char virSocketAddrIPv6Nibbles[32]; static int @@ -68,6 +69,23 @@ virSocketAddrGetIPv6Addr(const virSocketAddr *addr, virSocketAddrIPv6 *tab) return 0; } + +static int +virSocketAddrGetIPv6Bytes(const virSocketAddr *addr, + virSocketAddrIPv6Bytes *tab) +{ + size_t i; + + if (!addr || !tab || addr->data.stor.ss_family != AF_INET6) + return -1; + + for (i = 0; i < 16; i++) + (*tab)[i] = addr->data.inet6.sin6_addr.s6_addr[i]; + + return 0; +} + + static int virSocketAddrGetIPv6Nibbles(const virSocketAddr *addr, virSocketAddrIPv6Nibbles *tab) @@ -1331,6 +1349,51 @@ virSocketAddrPTRDomain(const virSocketAddr *addr, return 0; } + +/** + * virSocketAddrBytes: + * @addr: address to convert to byte array + * @bytes: a preallocated array to store the address bytes to + * @maxBytes: the size of @bytes + * + * Extracts individual bytes of an IPv4 or IPv6 address in the provided @bytes + * array, which should be large enough to store 16 bytes (the size of an IPv6 + * address). Bytes are stored in network order. + * + * Returns the number of bytes stored in @bytes on success or 0 when @bytes + * array is not big enough or @addr is not IPv4 or IPv6. + */ +int +virSocketAddrBytes(const virSocketAddr *addr, + unsigned char *bytes, + int maxBytes) +{ + if (VIR_SOCKET_ADDR_IS_FAMILY(addr, AF_INET6)) { + virSocketAddrIPv6Bytes ip; + + if (maxBytes < sizeof(ip)) + return 0; + + virSocketAddrGetIPv6Bytes(addr, &ip); + memcpy(bytes, ip, sizeof(ip)); + return sizeof(ip); + } + + if (VIR_SOCKET_ADDR_IS_FAMILY(addr, AF_INET)) { + virSocketAddrIPv4 ip; + + if (maxBytes < sizeof(ip)) + return 0; + + virSocketAddrGetIPv4Addr(addr, &ip); + memcpy(bytes, ip, sizeof(ip)); + return sizeof(ip); + } + + return 0; +} + + void virSocketAddrFree(virSocketAddr *addr) { diff --git a/src/util/virsocketaddr.h b/src/util/virsocketaddr.h index ec265d6e44..47b8effa85 100644 --- a/src/util/virsocketaddr.h +++ b/src/util/virsocketaddr.h @@ -143,6 +143,10 @@ int virSocketAddrPTRDomain(const virSocketAddr *addr, char **ptr) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); +int virSocketAddrBytes(const virSocketAddr *addr, + unsigned char *bytes, + int maxBytes); + void virSocketAddrFree(virSocketAddr *addr); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virSocketAddr, virSocketAddrFree); -- 2.43.0

Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virsystemd.c | 79 +++++++++++++++++++++++++++++++++++++++- src/util/virsystemd.h | 5 +++ 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0be273511d..d8c566b458 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3485,6 +3485,7 @@ virSystemdHasResolvedResetCachedValue; virSystemdMakeScopeName; virSystemdMakeSliceName; virSystemdNotifyStartup; +virSystemdResolvedRegisterNameServer; virSystemdTerminateMachine; diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index 0dfc4398d0..68699929d3 100644 --- a/src/util/virsystemd.c +++ b/src/util/virsystemd.c @@ -34,7 +34,6 @@ #include "virerror.h" #include "virfile.h" #include "virhash.h" -#include "virsocketaddr.h" #define VIR_FROM_THIS VIR_FROM_SYSTEMD @@ -1042,3 +1041,81 @@ virSystemdActivationFree(virSystemdActivation *act) g_free(act); } + + +/** + * virSystemdResolvedRegisterNameServer: + * @link: network interface ID + * @domain: registered domain + * @addr: address the DNS server is listening on + * + * Talk to systemd-resolved and register a DNS server listening on @addr + * as a resolver for @domain. This configuration is bound to @link interface + * and automatically dropped when the interface goes away. + * + * Returns -2 when systemd-resolved is unavailable, + * -1 on error, + * 0 on success. + */ +int +virSystemdResolvedRegisterNameServer(int link, + const char *domain, + virSocketAddr *addr) +{ + int rc; + GDBusConnection *conn; + GVariant *params; + GVariant *byteArray; + unsigned char addrBytes[16]; + int nBytes; + + if ((rc = virSystemdHasResolved()) < 0) + return rc; + + if (!(conn = virGDBusGetSystemBus())) + return -1; + + /* + * SetLinkDomains(in i ifindex, + * in a(sb) domains); + */ + params = g_variant_new_parsed("(%i, [(%s, true)])", + (gint32) link, + domain); + + rc = virGDBusCallMethod(conn, NULL, NULL, NULL, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "SetLinkDomains", + params); + g_variant_unref(params); + + if (rc < 0) + return -1; + + /* + * SetLinkDNS(in i ifindex, + * in a(iay) addresses); + */ + nBytes = virSocketAddrBytes(addr, addrBytes, sizeof(addrBytes)); + byteArray = g_variant_new_fixed_array(G_VARIANT_TYPE("y"), + addrBytes, nBytes, 1); + params = g_variant_new_parsed("(%i, [(%i, %@ay)])", + (gint32) link, + VIR_SOCKET_ADDR_FAMILY(addr), + byteArray); + + rc = virGDBusCallMethod(conn, NULL, NULL, NULL, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "SetLinkDNS", + params); + g_variant_unref(params); + + if (rc < 0) + return -1; + + return 0; +} diff --git a/src/util/virsystemd.h b/src/util/virsystemd.h index 65add8b5b9..b7fdaf67df 100644 --- a/src/util/virsystemd.h +++ b/src/util/virsystemd.h @@ -22,6 +22,7 @@ #pragma once #include "internal.h" +#include "virsocketaddr.h" typedef struct _virSystemdActivation virSystemdActivation; @@ -76,3 +77,7 @@ void virSystemdActivationClaimFDs(virSystemdActivation *act, void virSystemdActivationFree(virSystemdActivation *act); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virSystemdActivation, virSystemdActivationFree); + +int virSystemdResolvedRegisterNameServer(int link, + const char *domain, + virSocketAddr *addr); -- 2.43.0

Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- tests/virsystemdtest.c | 147 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/tests/virsystemdtest.c b/tests/virsystemdtest.c index fcd76514e1..004b0549ce 100644 --- a/tests/virsystemdtest.c +++ b/tests/virsystemdtest.c @@ -87,6 +87,34 @@ VIR_MOCK_WRAP_RET_ARGS(g_dbus_connection_call_sync, reply = g_variant_new("()"); } } + } else if (STREQ(bus_name, "org.freedesktop.resolve1")) { + g_autofree char *actual = NULL; + + if (getenv("FAIL_BAD_SERVICE")) { + *error = g_dbus_error_new_for_dbus_error("org.freedesktop.systemd.badthing", + "Contacting resolved failed"); + } else if (STREQ(method_name, "SetLinkDomains")) { + const char *expected = getenv("TEST_RESOLVED_LINK_DOMAINS"); + actual = g_variant_print(params, FALSE); + + if (virTestCompareToString(expected, actual) < 0) + *error = g_dbus_error_new_for_dbus_error("org.freedesktop.systemd.badthing", + "Unexpected params to SetLinkDomains"); + else + reply = g_variant_new("()"); + } else if (STREQ(method_name, "SetLinkDNS")) { + const char *expected = getenv("TEST_RESOLVED_LINK_DNS"); + actual = g_variant_print(params, FALSE); + + if (virTestCompareToString(expected, actual) < 0) + *error = g_dbus_error_new_for_dbus_error("org.freedesktop.systemd.badthing", + "Unexpected params to SetLinkDNS"); + else + reply = g_variant_new("()"); + } else { + *error = g_dbus_error_new_for_dbus_error("org.freedesktop.systemd.badthing", + "Unknown resolved method"); + } } else if (STREQ(bus_name, "org.freedesktop.login1")) { reply = g_variant_new("(s)", getenv("RESULT_SUPPORT")); } else if (STREQ(bus_name, "org.freedesktop.DBus") && @@ -100,6 +128,7 @@ VIR_MOCK_WRAP_RET_ARGS(g_dbus_connection_call_sync, if (!getenv("FAIL_NO_SERVICE")) { g_variant_builder_add(&builder, "s", "org.freedesktop.machine1"); g_variant_builder_add(&builder, "s", "org.freedesktop.login1"); + g_variant_builder_add(&builder, "s", "org.freedesktop.resolve1"); } reply = g_variant_new("(@as)", g_variant_builder_end(&builder)); @@ -114,6 +143,7 @@ VIR_MOCK_WRAP_RET_ARGS(g_dbus_connection_call_sync, if (!getenv("FAIL_NO_SERVICE") && !getenv("FAIL_NOT_REGISTERED")) { g_variant_builder_add(&builder, "s", "org.freedesktop.systemd1"); g_variant_builder_add(&builder, "s", "org.freedesktop.login1"); + g_variant_builder_add(&builder, "s", "org.freedesktop.resolve1"); } reply = g_variant_new("(@as)", g_variant_builder_end(&builder)); @@ -613,6 +643,83 @@ testActivationEmpty(const void *opaque G_GNUC_UNUSED) return 0; } + +struct testResolvedData { + int link; + const char *env; + const char *dom; + const char *addr; + const char *expDomains; + const char *expDNS; +}; + + +static int +testResolvedFailed(const void *opaque) +{ + const struct testResolvedData *data = opaque; + virSocketAddr addr = {0}; + int ret = -1; + int expected = -2; + int rv; + + if (!data->env) { + fprintf(stderr, "%s", + "testResolvedFailed called without environment variable name"); + return -1; + } + + if (data->addr && + virSocketAddrParse(&addr, data->addr, AF_UNSPEC) < 0) + return -1; + + if (STREQ(data->env, "FAIL_BAD_SERVICE")) + expected = -1; + + g_setenv(data->env, "1", TRUE); + + rv = virSystemdResolvedRegisterNameServer(data->link, data->dom, &addr); + + if (rv == 0) + fprintf(stderr, "%s", "Updating resolved succeeded unexpectedly\n"); + else if (rv != expected) + fprintf(stderr, "%s", "Updating resolved failed unexpectedly\n"); + else + ret = 0; + + g_unsetenv(data->env); + return ret; +} + + +static int +testResolved(const void *opaque) +{ + const struct testResolvedData *data = opaque; + virSocketAddr addr = {0}; + int ret = -1; + + if (data->addr && + virSocketAddrParse(&addr, data->addr, AF_UNSPEC) < 0) + return -1; + + g_setenv("TEST_RESOLVED_LINK_DOMAINS", data->expDomains, TRUE); + g_setenv("TEST_RESOLVED_LINK_DNS", data->expDNS, TRUE); + + if (virSystemdResolvedRegisterNameServer(data->link, data->dom, &addr) < 0) { + fprintf(stderr, "%s", "Updating resolved failed unexpectedly\n"); + goto cleanup; + } + + ret = 0; + + cleanup: + g_unsetenv("TEST_RESOLVED_LINK_DOMAINS"); + g_unsetenv("TEST_RESOLVED_LINK_DNS"); + return ret; +} + + static int mymain(void) { @@ -732,6 +839,46 @@ mymain(void) VIR_INFO("Skipping activation tests as FD 3/4/5 is open"); } +# define TEST_RESOLVED_HELPER(name, func, linkId, variable, domain, ip, \ + expectedDomains, expectedDNS) \ + do { \ + struct testResolvedData data = { \ + .link = linkId, \ + .env = variable, \ + .dom = domain, \ + .addr = ip, \ + .expDomains = expectedDomains, \ + .expDNS = expectedDNS, \ + }; \ + if (virTestRun("Test resolved " name, func, &data) < 0) \ + ret = -1; \ + virSystemdHasResolvedResetCachedValue(); \ + } while (0) + +# define TEST_RESOLVED_FAILURE(name, variable, ip) \ + TEST_RESOLVED_HELPER(name, testResolvedFailed, 42, variable, "virt", ip, NULL, NULL) + +# define TEST_RESOLVED_EXP(name, linkId, domain, ip, ipFamily, ipBytes) \ + TEST_RESOLVED_HELPER(name, testResolved, linkId, NULL, domain, ip, \ + "(" #linkId ", [('" domain "', true)])", \ + "(" #linkId ", [(" #ipFamily ", [" ipBytes "])])") + +# define TEST_RESOLVED(name, linkId, domain, ip, ipFamily, ipBytes) \ + TEST_RESOLVED_EXP(name, linkId, domain, ip, ipFamily, ipBytes) + + TEST_RESOLVED_FAILURE("not enabled", "FAIL_NO_SERVICE", NULL); + TEST_RESOLVED_FAILURE("not registered", "FAIL_NOT_REGISTERED", NULL); + TEST_RESOLVED_FAILURE("bad service", "FAIL_BAD_SERVICE", "1.2.3.4"); + + TEST_RESOLVED("IPv4", 84, "private", + "172.17.18.19", AF_INET, + "0xac, 0x11, 0x12, 0x13"); + + TEST_RESOLVED("IPv6", 21, "virt.local", + "e8f7:ae03:b089:d4e1:764a:53ec:658b:2547", AF_INET6, + "0xe8, 0xf7, 0xae, 0x03, 0xb0, 0x89, 0xd4, 0xe1, " + "0x76, 0x4a, 0x53, 0xec, 0x65, 0x8b, 0x25, 0x47"); + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } -- 2.43.0

This patch adds a new attribute "register" to the <domain> element. If set to "yes", the DNS server created for the virtual network is registered with systemd-resolved as a name server for the associated domain. The names known to the dnsmasq process serving DNS and DHCP requests for the virtual network will then be resolvable from the host by appending the domain name to them. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- docs/formatnetwork.rst | 9 ++++++++- src/conf/network_conf.c | 18 ++++++++++++++++++ src/conf/network_conf.h | 1 + src/conf/schemas/network.rng | 3 +++ src/network/bridge_driver.c | 32 +++++++++++++++++++++++++++++++- 5 files changed, 61 insertions(+), 2 deletions(-) diff --git a/docs/formatnetwork.rst b/docs/formatnetwork.rst index 16e81246fa..dcdaf1e5a5 100644 --- a/docs/formatnetwork.rst +++ b/docs/formatnetwork.rst @@ -88,7 +88,7 @@ to the physical LAN (if at all). ... <bridge name="virbr0" stp="on" delay="5" macTableManager="libvirt"/> <mtu size="9000"/> - <domain name="example.com" localOnly="no"/> + <domain name="example.com" localOnly="no" register="no"/> <forward mode="nat" dev="eth0"/> ... @@ -162,6 +162,13 @@ to the physical LAN (if at all). DNS server. If ``localOnly`` is "no", and by default, unresolved requests **will** be forwarded. :since:`Since 1.2.12` + :since:`Since 10.1.0` the optional ``register`` attribute can be used to + request registering the DNS server for resolving this domain with the host's + DNS resolver. When set to "yes", the host resolver will forward all requests + for domain names from this domain to the DNS server created for this virtual + network. To avoid DNS loops ``localOnly`` has to be set to "yes" as well. + This feature requires ``systemd-resolved`` to be running on the host. + ``forward`` Inclusion of the ``forward`` element indicates that the virtual network is to be connected to the physical LAN. :since:`Since 0.3.0.` The ``mode`` diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index ef3415cd89..cc92ed0b03 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -1582,6 +1582,19 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt, &def->domainLocalOnly) < 0) return NULL; + if (virXMLPropTristateBool(domain_node, "register", + VIR_XML_PROP_NONE, + &def->domainRegister) < 0) + return NULL; + + if (def->domainRegister == VIR_TRISTATE_BOOL_YES && + def->domainLocalOnly != VIR_TRISTATE_BOOL_YES) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("attribute 'register=yes' in <domain> element requires 'localOnly=yes' in network %1$s"), + def->name); + return NULL; + } + if ((bandwidthNode = virXPathNode("./bandwidth", ctxt)) && virNetDevBandwidthParse(&def->bandwidth, NULL, bandwidthNode, false) < 0) return NULL; @@ -2405,6 +2418,11 @@ virNetworkDefFormatBuf(virBuffer *buf, virBufferAsprintf(buf, " localOnly='%s'", local); } + if (def->domainRegister) { + virBufferAsprintf(buf, " register='%s'", + virTristateBoolTypeToString(def->domainRegister)); + } + virBufferAddLit(buf, "/>\n"); } diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 1d7fd3ab6a..c2a4198abc 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -245,6 +245,7 @@ struct _virNetworkDef { int macTableManager; /* enum virNetworkBridgeMACTableManager */ char *domain; virTristateBool domainLocalOnly; /* yes disables dns forwarding */ + virTristateBool domainRegister; unsigned long delay; /* Bridge forward delay (ms) */ bool stp; /* Spanning tree protocol */ unsigned int mtu; /* MTU for bridge, 0 means "default" i.e. unset in config */ diff --git a/src/conf/schemas/network.rng b/src/conf/schemas/network.rng index e56e07d130..b7c8551fad 100644 --- a/src/conf/schemas/network.rng +++ b/src/conf/schemas/network.rng @@ -258,6 +258,9 @@ <optional> <attribute name="localOnly"><ref name="virYesNo"/></attribute> </optional> + <optional> + <attribute name="register"><ref name="virYesNo"/></attribute> + </optional> </element> </optional> diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 9921c7cd14..d89700c6ee 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -63,7 +63,7 @@ #include "virjson.h" #include "virnetworkportdef.h" #include "virutil.h" - +#include "virsystemd.h" #include "netdev_bandwidth_conf.h" #define VIR_FROM_THIS VIR_FROM_NETWORK @@ -1902,6 +1902,7 @@ networkStartNetworkVirtual(virNetworkDriverState *driver, bool dnsmasqStarted = false; bool devOnline = false; bool firewalRulesAdded = false; + virSocketAddr *dnsServer = NULL; /* Check to see if any network IP collides with an existing route */ if (networkCheckRouteCollision(def) < 0) @@ -1958,6 +1959,9 @@ networkStartNetworkVirtual(virNetworkDriverState *driver, if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6)) v6present = true; + if (!dnsServer) + dnsServer = &ipdef->address; + /* Add the IP address/netmask to the bridge */ if (networkAddAddrToBridge(obj, ipdef) < 0) goto error; @@ -2011,6 +2015,32 @@ networkStartNetworkVirtual(virNetworkDriverState *driver, goto error; dnsmasqStarted = true; + + if (def->domain && def->domainRegister && dnsServer) { + unsigned int link; + int rc; + + if ((link = if_nametoindex(def->bridge)) == 0) { + virReportSystemError(ENODEV, + _("unable to get interface index for %1$s"), + def->bridge); + goto error; + } + + rc = virSystemdResolvedRegisterNameServer(link, def->domain, + dnsServer); + if (rc == -2) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("failed to register name server: systemd-resolved is not available")); + goto error; + } + + if (rc < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to register name server")); + goto error; + } + } } if (virNetDevBandwidthSet(def->bridge, def->bandwidth, true, true) < 0) -- 2.43.0

Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- NEWS.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index e2796fd8b2..15b0da31b6 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -23,6 +23,13 @@ v10.1.0 (unreleased) Additionally, if CPU clusters are present in the host topology, they will be reported as part of the capabilities XML. + * network: Make virtual domains resolvable from the host + + When starting a virtual network with a new ``register='yes'`` attribute + in the ``<domain>`` element, libvirt will configure ``systemd-resolved`` + to resolve names of the connected guests using the name server started + for this network. + * **Improvements** * **Bug fixes** -- 2.43.0

On a Thursday in 2024, Jiri Denemark wrote:
See 6/7 for more details.
Jiri Denemark (7): util: Refactor virSystemdHas{Machined,Logind} util: Introduce virSystemdHasResolved util: Introduce virSocketAddrBytes util: Introduce virSystemdResolvedRegisterNameServer tests: Add tests for virSystemdResolvedRegisterNameServer network: Make virtual domains resolvable from the host NEWS: Mention systemd-resolved support in network driver
NEWS.rst | 7 ++ docs/formatnetwork.rst | 9 +- src/conf/network_conf.c | 18 ++++ src/conf/network_conf.h | 1 + src/conf/schemas/network.rng | 3 + src/libvirt_private.syms | 4 + src/network/bridge_driver.c | 32 ++++++- src/util/virsocketaddr.c | 63 +++++++++++++ src/util/virsocketaddr.h | 4 + src/util/virsystemd.c | 166 +++++++++++++++++++++++++++-------- src/util/virsystemd.h | 7 ++ src/util/virsystemdpriv.h | 1 + tests/virsystemdtest.c | 147 +++++++++++++++++++++++++++++++ 13 files changed, 421 insertions(+), 41 deletions(-)
With 1/7 at least acknowledging the change in behavior: Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano
participants (3)
-
Jiri Denemark
-
Ján Tomko
-
Martin Kletzander