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

See 6/7 for more details. Version 2: - Patch 2/7 no longer tries (or pretends) to be a refactor. Instead it unifies they way we check for systemd services. - requireSystemd argument of virSystemdHasService was dropped Jiri Denemark (7): util: Unify 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 | 164 ++++++++++++++++++++++++++--------- src/util/virsystemd.h | 7 ++ src/util/virsystemdpriv.h | 1 + tests/virsystemdtest.c | 147 +++++++++++++++++++++++++++++++ 13 files changed, 419 insertions(+), 41 deletions(-) -- 2.43.0

When checking for machined we do not really care whether systemd itself is running, we just need machined to be either running or socket activated by systemd. That is, exactly the same we do for logind. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/util/virsystemd.c | 74 ++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index cd4de0eef8..36820e81fe 100644 --- a/src/util/virsystemd.c +++ b/src/util/virsystemd.c @@ -141,57 +141,35 @@ 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 is + * running or will be started on demand by a running systemd1 service. + * + * Returns + * -2 when service is not supported on this machine + * -1 on error + * 0 when service is available */ -int -virSystemdHasMachined(void) -{ - int ret; - int val; - - val = g_atomic_int_get(&virSystemdHasMachinedCachedValue); - if (val != -1) - return val; - - if ((ret = virGDBusIsServiceEnabled("org.freedesktop.machine1")) < 0) { - if (ret == -2) - g_atomic_int_set(&virSystemdHasMachinedCachedValue, -2); - return ret; - } - - if ((ret = virGDBusIsServiceRegistered("org.freedesktop.systemd1")) == -1) - return ret; - g_atomic_int_set(&virSystemdHasMachinedCachedValue, ret); - return ret; -} - -int -virSystemdHasLogind(void) +static int +virSystemdHasService(const char *service, + int *cached) { int ret; int val; - val = g_atomic_int_get(&virSystemdHasLogindCachedValue); + val = g_atomic_int_get(cached); if (val != -1) return val; - ret = virGDBusIsServiceEnabled("org.freedesktop.login1"); - if (ret < 0) { + if ((ret = virGDBusIsServiceEnabled(service)) < 0) { if (ret == -2) - g_atomic_int_set(&virSystemdHasLogindCachedValue, -2); + g_atomic_int_set(cached, -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) + if ((ret = virGDBusIsServiceRegistered(service)) == -1) return ret; if (ret == -2) { @@ -199,11 +177,27 @@ virSystemdHasLogind(void) return ret; } - g_atomic_int_set(&virSystemdHasLogindCachedValue, ret); + g_atomic_int_set(cached, ret); return ret; } +int +virSystemdHasMachined(void) +{ + return virSystemdHasService("org.freedesktop.machine1", + &virSystemdHasMachinedCachedValue); +} + + +int +virSystemdHasLogind(void) +{ + return virSystemdHasService("org.freedesktop.login1", + &virSystemdHasLogindCachedValue); +} + + /** * virSystemdGetMachineByPID: * @conn: dbus connection -- 2.43.0

On Mon, Feb 05, 2024 at 03:14:08PM +0100, Jiri Denemark wrote:
When checking for machined we do not really care whether systemd itself is running, we just need machined to be either running or socket activated by systemd. That is, exactly the same we do for logind.
That's not right. We very much *do* care whether systemd is running. If systemd-machined is installed on the host but the OS is booted into sysvinit, then DBus will report that machined can be activated, but if it is activated then it certainly won't actually work.
Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/util/virsystemd.c | 74 ++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 40 deletions(-)
With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Mon, Feb 05, 2024 at 14:42:09 +0000, Daniel P. Berrangé wrote:
On Mon, Feb 05, 2024 at 03:14:08PM +0100, Jiri Denemark wrote:
When checking for machined we do not really care whether systemd itself is running, we just need machined to be either running or socket activated by systemd. That is, exactly the same we do for logind.
That's not right. We very much *do* care whether systemd is running.
If systemd-machined is installed on the host but the OS is booted into sysvinit, then DBus will report that machined can be activated, but if it is activated then it certainly won't actually work.
I guess I didn't explain myself correctly, but this just changes our machind service check to do the same we do for logind. That is, machine1 service must be enabled (listed by ListActivatableNames). If it is, it must already be running (reported by ListNames) or systemd must be running. Are you saying we should properly handle situations when a service we want to talk to is already running, but cannot work because systemd is not running? I would expect the service never be running in such case as it would just fail to start without systemd. Jirka

On Mon, Feb 05, 2024 at 04:02:55PM +0100, Jiri Denemark wrote:
On Mon, Feb 05, 2024 at 14:42:09 +0000, Daniel P. Berrangé wrote:
On Mon, Feb 05, 2024 at 03:14:08PM +0100, Jiri Denemark wrote:
When checking for machined we do not really care whether systemd itself is running, we just need machined to be either running or socket activated by systemd. That is, exactly the same we do for logind.
That's not right. We very much *do* care whether systemd is running.
If systemd-machined is installed on the host but the OS is booted into sysvinit, then DBus will report that machined can be activated, but if it is activated then it certainly won't actually work.
I guess I didn't explain myself correctly, but this just changes our machind service check to do the same we do for logind. That is, machine1 service must be enabled (listed by ListActivatableNames). If it is, it must already be running (reported by ListNames) or systemd must be running.
Are you saying we should properly handle situations when a service we want to talk to is already running, but cannot work because systemd is not running? I would expect the service never be running in such case as it would just fail to start without systemd.
Hmm, I guess that's safe enough actually. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On a Monday in 2024, Jiri Denemark wrote:
When checking for machined we do not really care whether systemd itself is running, we just need machined to be either running or socket activated by systemd. That is, exactly the same we do for logind.
Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- src/util/virsystemd.c | 74 ++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 40 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

Signed-off-by: Jiri Denemark <jdenemar@redhat.com> Reviewed-by: Ján Tomko <jtomko@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 c398371734..c336d06fb3 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3489,6 +3489,8 @@ virSystemdHasLogind; virSystemdHasLogindResetCachedValue; virSystemdHasMachined; virSystemdHasMachinedResetCachedValue; +virSystemdHasResolved; +virSystemdHasResolvedResetCachedValue; virSystemdMakeScopeName; virSystemdMakeSliceName; virSystemdNotifyStartup; diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index 36820e81fe..fd2b1db3ee 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: @@ -198,6 +205,14 @@ virSystemdHasLogind(void) } +int +virSystemdHasResolved(void) +{ + return virSystemdHasService("org.freedesktop.resolve1", + &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> Reviewed-by: Ján Tomko <jtomko@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 c336d06fb3..5fe7ebe9d2 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3378,6 +3378,7 @@ virSocketSendMsgWithFDs; # 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> Reviewed-by: Ján Tomko <jtomko@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 5fe7ebe9d2..396bd80844 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3495,6 +3495,7 @@ virSystemdHasResolvedResetCachedValue; virSystemdMakeScopeName; virSystemdMakeSliceName; virSystemdNotifyStartup; +virSystemdResolvedRegisterNameServer; virSystemdTerminateMachine; diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index fd2b1db3ee..5b772e29dd 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 @@ -1040,3 +1039,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> Reviewed-by: Ján Tomko <jtomko@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> Reviewed-by: Ján Tomko <jtomko@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> Reviewed-by: Ján Tomko <jtomko@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
participants (3)
-
Daniel P. Berrangé
-
Jiri Denemark
-
Ján Tomko