[PATCH 0/6] qemu: fix a passt bug, add a passt feature
The first patch cleans up a dangling socket that was hanging around after we finished using a passt process in vhostuser mode. The rest are to support specifying the default route for a passt interface with <route gateway='x.x.x.x'/> Laine Stump (6): qemu: delete passt "repair" socket when appropriate qemu: reorganize qemuValidateDomainDeviceDefNetwork() qemu: only limit IPv4 prefix for slirp util: make the route arg to virNetDevIPRouteGetPrefix const conf/util: fix non-specification of IP route destination address qemu: support setting default route for passt interfaces inside the guest src/conf/networkcommon_conf.c | 74 ++++++++------- src/conf/schemas/networkcommon.rng | 4 +- src/qemu/qemu_passt.c | 30 ++++++- src/qemu/qemu_validate.c | 140 +++++++++++++++++++---------- src/util/virnetdevip.c | 14 ++- src/util/virnetdevip.h | 2 +- 6 files changed, 170 insertions(+), 94 deletions(-) -- 2.52.0
From: Laine Stump <laine@redhat.com> When using a vhost-user connection between passt and QEMU, passt will autocreate a socket called ${socketname}.repair, but doesn't delete this socket when it exits, so to be a good citizen, libvirtshould delete it when we are tearing down the passt device plumbing. Resolves: https://issues.redhat.com/browse/RHEL-80285 Signed-off-by: Laine Stump <laine@redhat.com> --- src/qemu/qemu_passt.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/qemu/qemu_passt.c b/src/qemu/qemu_passt.c index 520eb1244a..56d048d585 100644 --- a/src/qemu/qemu_passt.c +++ b/src/qemu/qemu_passt.c @@ -121,7 +121,7 @@ qemuPasstAddNetProps(virDomainObj *vm, static void -qemuPasstKill(const char *pidfile, const char *passtSocketName) +qemuPasstKill(const virDomainNetDef *net, const char *pidfile, const char *passtSocketName) { virErrorPtr orig_err; pid_t pid = 0; @@ -135,6 +135,14 @@ qemuPasstKill(const char *pidfile, const char *passtSocketName) unlink(passtSocketName); + /* repair socket is (always) created by passt only for vhostuser mode */ + if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_VHOSTUSER) { + g_autofree char *passtRepairSocketName + = g_strdup_printf("%s.repair", passtSocketName); + + unlink(passtRepairSocketName); + } + virErrorRestore(&orig_err); } @@ -146,7 +154,7 @@ qemuPasstStop(virDomainObj *vm, g_autofree char *pidfile = qemuPasstCreatePidFilename(vm, net); g_autofree char *passtSocketName = qemuPasstCreateSocketPath(vm, net); - qemuPasstKill(pidfile, passtSocketName); + qemuPasstKill(net, pidfile, passtSocketName); } @@ -351,6 +359,6 @@ qemuPasstStart(virDomainObj *vm, return 0; error: - qemuPasstKill(pidfile, passtSocketName); + qemuPasstKill(net, pidfile, passtSocketName); return -1; } -- 2.52.0
On 2/24/26 08:52, Laine Stump via Devel wrote:
From: Laine Stump <laine@redhat.com>
When using a vhost-user connection between passt and QEMU, passt will autocreate a socket called ${socketname}.repair, but doesn't delete this socket when it exits, so to be a good citizen, libvirtshould
s/libvirtshould/libvirt should/
delete it when we are tearing down the passt device plumbing.
Resolves: https://issues.redhat.com/browse/RHEL-80285 Signed-off-by: Laine Stump <laine@redhat.com> --- src/qemu/qemu_passt.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/qemu/qemu_passt.c b/src/qemu/qemu_passt.c index 520eb1244a..56d048d585 100644 --- a/src/qemu/qemu_passt.c +++ b/src/qemu/qemu_passt.c @@ -121,7 +121,7 @@ qemuPasstAddNetProps(virDomainObj *vm,
static void -qemuPasstKill(const char *pidfile, const char *passtSocketName) +qemuPasstKill(const virDomainNetDef *net, const char *pidfile, const char *passtSocketName) { virErrorPtr orig_err; pid_t pid = 0; @@ -135,6 +135,14 @@ qemuPasstKill(const char *pidfile, const char *passtSocketName)
unlink(passtSocketName);
+ /* repair socket is (always) created by passt only for vhostuser mode */ + if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_VHOSTUSER) { + g_autofree char *passtRepairSocketName + = g_strdup_printf("%s.repair", passtSocketName);
NITPICK, I'm not sure what our coding style says about this (or if it says anything at all). In cases like this what I usually do is split declaration and initialization into two: g_autofree char *passtRepairSocketName = NULL; passtRepairSocketName = g_strdup_printf("%s.repair", passtSocketName); Do with this information whatever you like.
+ + unlink(passtRepairSocketName); + } + virErrorRestore(&orig_err); }
@@ -146,7 +154,7 @@ qemuPasstStop(virDomainObj *vm, g_autofree char *pidfile = qemuPasstCreatePidFilename(vm, net); g_autofree char *passtSocketName = qemuPasstCreateSocketPath(vm, net);
- qemuPasstKill(pidfile, passtSocketName); + qemuPasstKill(net, pidfile, passtSocketName); }
@@ -351,6 +359,6 @@ qemuPasstStart(virDomainObj *vm, return 0;
error: - qemuPasstKill(pidfile, passtSocketName); + qemuPasstKill(net, pidfile, passtSocketName); return -1; }
Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Michal
On a Tuesday in 2026, Laine Stump via Devel wrote:
From: Laine Stump <laine@redhat.com>
When using a vhost-user connection between passt and QEMU, passt will autocreate a socket called ${socketname}.repair, but doesn't delete this socket when it exits, so to be a good citizen, libvirtshould
Missing space.
delete it when we are tearing down the passt device plumbing.
Resolves: https://issues.redhat.com/browse/RHEL-80285 Signed-off-by: Laine Stump <laine@redhat.com> --- src/qemu/qemu_passt.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/qemu/qemu_passt.c b/src/qemu/qemu_passt.c index 520eb1244a..56d048d585 100644 --- a/src/qemu/qemu_passt.c +++ b/src/qemu/qemu_passt.c @@ -121,7 +121,7 @@ qemuPasstAddNetProps(virDomainObj *vm,
static void -qemuPasstKill(const char *pidfile, const char *passtSocketName) +qemuPasstKill(const virDomainNetDef *net, const char *pidfile, const char *passtSocketName) { virErrorPtr orig_err; pid_t pid = 0; @@ -135,6 +135,14 @@ qemuPasstKill(const char *pidfile, const char *passtSocketName)
unlink(passtSocketName);
+ /* repair socket is (always) created by passt only for vhostuser mode */ + if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_VHOSTUSER) { + g_autofree char *passtRepairSocketName + = g_strdup_printf("%s.repair", passtSocketName);
If you drop 'passt' from the variable name, it might fit on one line. Jano
+ + unlink(passtRepairSocketName); + } + virErrorRestore(&orig_err); }
@@ -146,7 +154,7 @@ qemuPasstStop(virDomainObj *vm, g_autofree char *pidfile = qemuPasstCreatePidFilename(vm, net); g_autofree char *passtSocketName = qemuPasstCreateSocketPath(vm, net);
- qemuPasstKill(pidfile, passtSocketName); + qemuPasstKill(net, pidfile, passtSocketName); }
@@ -351,6 +359,6 @@ qemuPasstStart(virDomainObj *vm, return 0;
error: - qemuPasstKill(pidfile, passtSocketName); + qemuPasstKill(net, pidfile, passtSocketName); return -1; } -- 2.52.0
From: Laine Stump <laine@redhat.com> This will make existing code more consistent with upcoming validation for routes. Signed-off-by: Laine Stump <laine@redhat.com> --- src/qemu/qemu_validate.c | 95 +++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c index 16dd5fef1a..a0ddf512f1 100644 --- a/src/qemu/qemu_validate.c +++ b/src/qemu/qemu_validate.c @@ -1882,16 +1882,10 @@ qemuValidateDomainDeviceDefNetwork(const virDomainNetDef *net, const virDomainDef *def, virQEMUCaps *qemuCaps) { - bool hasIPv4 = false; - bool hasIPv6 = false; + bool hasV4Addr = false; + bool hasV6Addr = false; size_t i; - if (net->guestIP.nroutes) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("Invalid attempt to set network interface guest-side IP route, not supported by QEMU")); - return -1; - } - if (net->type == VIR_DOMAIN_NET_TYPE_USER || (net->type == VIR_DOMAIN_NET_TYPE_VHOSTUSER && net->backend.type == VIR_DOMAIN_NET_BACKEND_PASST)) { @@ -1906,57 +1900,66 @@ qemuValidateDomainDeviceDefNetwork(const virDomainNetDef *net, virDomainNetBackendTypeToString(net->backend.type)); return -1; } + } + + for (i = 0; i < net->guestIP.nips; i++) { + const virNetDevIPAddr *ip = net->guestIP.ips[i]; + + if (net->type != VIR_DOMAIN_NET_TYPE_USER && + net->backend.type != VIR_DOMAIN_NET_BACKEND_PASST) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Invalid attempt to set network interface guest-side IP address info, not supported for this interface type/backend")); + return -1; + } - for (i = 0; i < net->guestIP.nips; i++) { - const virNetDevIPAddr *ip = net->guestIP.ips[i]; + if (VIR_SOCKET_ADDR_VALID(&net->guestIP.ips[i]->peer)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Invalid attempt to set peer IP for guest")); + return -1; + } - if (VIR_SOCKET_ADDR_VALID(&net->guestIP.ips[i]->peer)) { + if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET)) { + if (hasV4Addr) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("Invalid attempt to set peer IP for guest")); + _("Only one IPv4 address per interface is allowed")); return -1; } + hasV4Addr = true; - if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET)) { - if (hasIPv4) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("Only one IPv4 address per interface is allowed")); - return -1; - } - hasIPv4 = true; - - if (ip->prefix > 0 && - (ip->prefix < 4 || ip->prefix > 27)) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("invalid prefix, must be in range of 4-27")); - return -1; - } + if (ip->prefix > 0 && + (ip->prefix < 4 || ip->prefix > 27)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("invalid prefix, must be in range of 4-27")); + return -1; } + } - if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET6)) { - if (hasIPv6) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("Only one IPv6 address per interface is allowed")); - return -1; - } - hasIPv6 = true; + if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET6)) { + if (hasV6Addr) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Only one IPv6 address per interface is allowed")); + return -1; + } + hasV6Addr = true; - if (ip->prefix && ip->prefix != 64) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("unsupported IPv6 address prefix='%1$u' - must be 64"), - ip->prefix); - return -1; - } + if (ip->prefix && ip->prefix != 64) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unsupported IPv6 address prefix='%1$u' - must be 64"), + ip->prefix); + return -1; + } - if (ip->prefix > 120) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("prefix too long")); - return -1; - } + if (ip->prefix > 120) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("prefix too long")); + return -1; } } - } else if (net->guestIP.nips) { + } + + if (net->guestIP.nroutes) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("Invalid attempt to set network interface guest-side IP address info, not supported by QEMU")); + _("Invalid attempt to set network interface guest-side IP route, not supported by QEMU")); return -1; } -- 2.52.0
On 2/24/26 08:52, Laine Stump via Devel wrote:
From: Laine Stump <laine@redhat.com>
This will make existing code more consistent with upcoming validation for routes.
Signed-off-by: Laine Stump <laine@redhat.com> --- src/qemu/qemu_validate.c | 95 +++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 46 deletions(-)
Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Michal
From: Laine Stump <laine@redhat.com> The slirp backend is limited in what the netmask/prefix of a user-specified IP address can be, but passt doesn't have these artificial limitations - any valid prefix is okay with passt, so we shouldn't reject them Signed-off-by: Laine Stump <laine@redhat.com> --- src/qemu/qemu_validate.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c index a0ddf512f1..1c6662751b 100644 --- a/src/qemu/qemu_validate.c +++ b/src/qemu/qemu_validate.c @@ -1926,8 +1926,9 @@ qemuValidateDomainDeviceDefNetwork(const virDomainNetDef *net, } hasV4Addr = true; - if (ip->prefix > 0 && - (ip->prefix < 4 || ip->prefix > 27)) { + if (net->backend.type != VIR_DOMAIN_NET_BACKEND_PASST && + (ip->prefix > 0 && + (ip->prefix < 4 || ip->prefix > 27))) { virReportError(VIR_ERR_XML_ERROR, "%s", _("invalid prefix, must be in range of 4-27")); return -1; -- 2.52.0
On 2/24/26 08:52, Laine Stump via Devel wrote:
From: Laine Stump <laine@redhat.com>
The slirp backend is limited in what the netmask/prefix of a user-specified IP address can be, but passt doesn't have these artificial limitations - any valid prefix is okay with passt, so we shouldn't reject them
Signed-off-by: Laine Stump <laine@redhat.com> --- src/qemu/qemu_validate.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c index a0ddf512f1..1c6662751b 100644 --- a/src/qemu/qemu_validate.c +++ b/src/qemu/qemu_validate.c @@ -1926,8 +1926,9 @@ qemuValidateDomainDeviceDefNetwork(const virDomainNetDef *net, } hasV4Addr = true;
- if (ip->prefix > 0 && - (ip->prefix < 4 || ip->prefix > 27)) { + if (net->backend.type != VIR_DOMAIN_NET_BACKEND_PASST && + (ip->prefix > 0 && + (ip->prefix < 4 || ip->prefix > 27))) { virReportError(VIR_ERR_XML_ERROR, "%s", _("invalid prefix, must be in range of 4-27")); return -1;
This block of code starts with: for (i = 0; i < net->guestIP.nips; i++) { const virNetDevIPAddr *ip = net->guestIP.ips[i]; if (net->type != VIR_DOMAIN_NET_TYPE_USER && net->backend.type != VIR_DOMAIN_NET_BACKEND_PASST) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Invalid attempt to set network interface guest-side IP address info, not supported for this interface type/backend")); return -1; } IOW this patch is a dead code, effectively. Michal
From: Laine Stump <laine@redhat.com> Signed-off-by: Laine Stump <laine@redhat.com> --- src/util/virnetdevip.c | 2 +- src/util/virnetdevip.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/virnetdevip.c b/src/util/virnetdevip.c index d4f023ca5c..5ffd2bf398 100644 --- a/src/util/virnetdevip.c +++ b/src/util/virnetdevip.c @@ -776,7 +776,7 @@ virNetDevIPRouteGetAddress(virNetDevIPRoute *def) } int -virNetDevIPRouteGetPrefix(virNetDevIPRoute *def) +virNetDevIPRouteGetPrefix(const virNetDevIPRoute *def) { int prefix = 0; virSocketAddr zero; diff --git a/src/util/virnetdevip.h b/src/util/virnetdevip.h index f2ee90be2f..8771e04ba7 100644 --- a/src/util/virnetdevip.h +++ b/src/util/virnetdevip.h @@ -79,7 +79,7 @@ void virNetDevIPAddrFree(virNetDevIPAddr *ip); /* virNetDevIPRoute object */ void virNetDevIPRouteFree(virNetDevIPRoute *def); virSocketAddr *virNetDevIPRouteGetAddress(virNetDevIPRoute *def); -int virNetDevIPRouteGetPrefix(virNetDevIPRoute *def); +int virNetDevIPRouteGetPrefix(const virNetDevIPRoute *def); unsigned int virNetDevIPRouteGetMetric(virNetDevIPRoute *def); virSocketAddr *virNetDevIPRouteGetGateway(virNetDevIPRoute *def); -- 2.52.0
On 2/24/26 08:52, Laine Stump via Devel wrote:
From: Laine Stump <laine@redhat.com>
Signed-off-by: Laine Stump <laine@redhat.com> --- src/util/virnetdevip.c | 2 +- src/util/virnetdevip.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-)
Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Michal
From: Laine Stump <laine@redhat.com> The Linux/libnl version of virNetDevIPRouteAdd() has always had code that would use "0.0.0.0" (or "::" for IPv6) for the route's destination address if none was specified, but 1) our validation code has always required it to be specified anyway, 2) the FreeBSD version of virnertDevIPRouteAdd() expected that it would be specified, and 3) virNetDevIPRouteFormat() also expected route->address to be valid. This patch fixes those 3 deficiencies, so that this XML now works: <route gateway='1.2.3.4'/> i.e. it is the same as: <route address='0.0.0.0' prefix='0' gateway='1.2.3.4'/> Signed-off-by: Laine Stump <laine@redhat.com> --- src/conf/networkcommon_conf.c | 74 ++++++++++++++---------------- src/conf/schemas/networkcommon.rng | 4 +- src/util/virnetdevip.c | 12 ++++- 3 files changed, 48 insertions(+), 42 deletions(-) diff --git a/src/conf/networkcommon_conf.c b/src/conf/networkcommon_conf.c index 6f88d173c6..bfa58a26a8 100644 --- a/src/conf/networkcommon_conf.c +++ b/src/conf/networkcommon_conf.c @@ -50,15 +50,6 @@ virNetDevIPRouteCreate(const char *errorDetail, def->metric = metric; def->has_metric = hasMetric; - /* Note: both network and gateway addresses must be specified */ - - if (!address) { - virReportError(VIR_ERR_XML_ERROR, - _("%1$s: Missing required address attribute in route definition"), - errorDetail); - return NULL; - } - if (!gateway) { virReportError(VIR_ERR_XML_ERROR, _("%1$s: Missing required gateway attribute in route definition"), @@ -66,17 +57,17 @@ virNetDevIPRouteCreate(const char *errorDetail, return NULL; } - if (virSocketAddrParse(&def->address, address, AF_UNSPEC) < 0) { + if (virSocketAddrParse(&def->gateway, gateway, AF_UNSPEC) < 0) { virReportError(VIR_ERR_XML_ERROR, - _("%1$s: Bad network address '%2$s' in route definition"), - errorDetail, address); + _("%1$s: Bad gateway address '%2$s' in route definition"), + errorDetail, gateway); return NULL; } - if (virSocketAddrParse(&def->gateway, gateway, AF_UNSPEC) < 0) { + if (address && virSocketAddrParse(&def->address, address, AF_UNSPEC) < 0) { virReportError(VIR_ERR_XML_ERROR, - _("%1$s: Bad gateway address '%2$s' in route definition"), - errorDetail, gateway); + _("%1$s: Bad network address '%2$s' in route definition"), + errorDetail, address); return NULL; } @@ -127,7 +118,8 @@ virNetDevIPRouteCreate(const char *errorDetail, return NULL; } } else if (STREQ(def->family, "ipv6")) { - if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6)) { + if (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6) || + VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_UNSPEC))) { virReportError(VIR_ERR_XML_ERROR, _("%1$s: ipv6 family specified for non-IPv6 address '%2$s' in route definition"), errorDetail, address); @@ -158,29 +150,32 @@ virNetDevIPRouteCreate(const char *errorDetail, return NULL; } - /* make sure the address is a network address */ - if (netmask) { - if (virSocketAddrMask(&def->address, &def->netmask, &testAddr) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("%1$s: Error converting address '%2$s' with netmask '%3$s' to network-address in route definition"), - errorDetail, address, netmask); - return NULL; + if (address) { + /* make sure the address is a network address */ + + if (netmask) { + if (virSocketAddrMask(&def->address, &def->netmask, &testAddr) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%1$s: Error converting address '%2$s' with netmask '%3$s' to network-address in route definition"), + errorDetail, address, netmask); + return NULL; + } + } else { + if (virSocketAddrMaskByPrefix(&def->address, + def->prefix, &testAddr) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%1$s: Error converting address '%2$s' with prefix %3$u to network-address in route definition"), + errorDetail, address, def->prefix); + return NULL; + } } - } else { - if (virSocketAddrMaskByPrefix(&def->address, - def->prefix, &testAddr) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("%1$s: Error converting address '%2$s' with prefix %3$u to network-address in route definition"), - errorDetail, address, def->prefix); + if (!virSocketAddrEqual(&def->address, &testAddr)) { + virReportError(VIR_ERR_XML_ERROR, + _("%1$s: Address '%2$s' in route definition is not a network address"), + errorDetail, address); return NULL; } } - if (!virSocketAddrEqual(&def->address, &testAddr)) { - virReportError(VIR_ERR_XML_ERROR, - _("%1$s: Address '%2$s' in route definition is not a network address"), - errorDetail, address); - return NULL; - } return g_steal_pointer(&def); } @@ -229,10 +224,11 @@ virNetDevIPRouteFormat(virBuffer *buf, if (def->family) virBufferAsprintf(buf, " family='%s'", def->family); - if (!(address = virSocketAddrFormat(&def->address))) - return -1; - virBufferAsprintf(buf, " address='%s'", address); - + if (VIR_SOCKET_ADDR_VALID(&def->address)) { + if (!(address = virSocketAddrFormat(&def->address))) + return -1; + virBufferAsprintf(buf, " address='%s'", address); + } if (VIR_SOCKET_ADDR_VALID(&def->netmask)) { if (!(netmask = virSocketAddrFormat(&def->netmask))) return -1; diff --git a/src/conf/schemas/networkcommon.rng b/src/conf/schemas/networkcommon.rng index 28424f9abd..49149a3d4b 100644 --- a/src/conf/schemas/networkcommon.rng +++ b/src/conf/schemas/networkcommon.rng @@ -253,7 +253,9 @@ <optional> <attribute name="family"><ref name="addr-family"/></attribute> </optional> - <attribute name="address"><ref name="ipAddr"/></attribute> + <optional> + <attribute name="address"><ref name="ipAddr"/></attribute> + </optional> <optional> <choice> <attribute name="netmask"><ref name="ipv4Addr"/></attribute> diff --git a/src/util/virnetdevip.c b/src/util/virnetdevip.c index 5ffd2bf398..8786bb236e 100644 --- a/src/util/virnetdevip.c +++ b/src/util/virnetdevip.c @@ -465,8 +465,16 @@ virNetDevIPRouteAdd(const char *ifname, g_autofree char *addrstr = NULL; g_autofree char *gatewaystr = NULL; - if (!(addrstr = virSocketAddrFormat(addr))) - return -1; + if (VIR_SOCKET_ADDR_VALID(addr)) { + if (!(addrstr = virSocketAddrFormat(addr))) + return -1; + } else { + if (VIR_SOCKET_ADDR_IS_FAMILY(gateway, AF_INET6)) + addrstr = g_strdup(VIR_SOCKET_ADDR_IPV6_ALL); + else + addrstr = g_strdup(VIR_SOCKET_ADDR_IPV4_ALL); + } + if (!(gatewaystr = virSocketAddrFormat(gateway))) return -1; cmd = virCommandNew("ip"); -- 2.52.0
On 2/24/26 08:52, Laine Stump via Devel wrote:
From: Laine Stump <laine@redhat.com>
The Linux/libnl version of virNetDevIPRouteAdd() has always had code that would use "0.0.0.0" (or "::" for IPv6) for the route's destination address if none was specified, but 1) our validation code has always required it to be specified anyway, 2) the FreeBSD version of virnertDevIPRouteAdd() expected that it would be specified, and 3) virNetDevIPRouteFormat() also expected route->address to be valid. This patch fixes those 3 deficiencies, so that this XML now works:
<route gateway='1.2.3.4'/>
i.e. it is the same as:
<route address='0.0.0.0' prefix='0' gateway='1.2.3.4'/>
Signed-off-by: Laine Stump <laine@redhat.com> --- src/conf/networkcommon_conf.c | 74 ++++++++++++++---------------- src/conf/schemas/networkcommon.rng | 4 +- src/util/virnetdevip.c | 12 ++++- 3 files changed, 48 insertions(+), 42 deletions(-)
Either this patch or the next one should mention this in the docs: https://libvirt.org/formatdomain.html#ip-configuration Esp. the part about "This is used by the LXC driver." Michal
On 2/24/26 8:13 AM, Michal Prívozník via Devel wrote:
On 2/24/26 08:52, Laine Stump via Devel wrote:
From: Laine Stump <laine@redhat.com>
The Linux/libnl version of virNetDevIPRouteAdd() has always had code that would use "0.0.0.0" (or "::" for IPv6) for the route's destination address if none was specified, but 1) our validation code has always required it to be specified anyway, 2) the FreeBSD version of virnertDevIPRouteAdd() expected that it would be specified, and 3) virNetDevIPRouteFormat() also expected route->address to be valid. This patch fixes those 3 deficiencies, so that this XML now works:
<route gateway='1.2.3.4'/>
i.e. it is the same as:
<route address='0.0.0.0' prefix='0' gateway='1.2.3.4'/>
Signed-off-by: Laine Stump <laine@redhat.com> --- src/conf/networkcommon_conf.c | 74 ++++++++++++++---------------- src/conf/schemas/networkcommon.rng | 4 +- src/util/virnetdevip.c | 12 ++++- 3 files changed, 48 insertions(+), 42 deletions(-)
Either this patch or the next one should mention this in the docs:
https://libvirt.org/formatdomain.html#ip-configuration
Esp. the part about "This is used by the LXC driver."
Good point! Actually each of them needs a tweak to the docs - this one should point out that address is optional (and defaults to the "all" address for the given family if not specified), and the next should say that it's used for the QEMU driver with a passt-backed interface.
Michal
From: Laine Stump <laine@redhat.com> libvirt's <interface> element has for a long time supported adding <route> sub-elements to specify arbitrary routes to be added to the guest OS networking, but historically this has only worked for LXC guests. If you tried to add <route> to the interface of a QEMU guest, it would be rejected. passt networking doesn't support setting *any arbitrary* route but it does support setting a default route (using the passt commandline "--gateway" parameter). A default route is really just a "route with unspecified destination/prefix", so a default route can be specified in libvirt XML with: <route gateway='192.168.0.1'/> Attempts to give a specified destination, prefix, or metric will result in a validation error. Resolves: https://issues.redhat.com/browse/RHEL-46602 Signed-off-by: Laine Stump <laine@redhat.com> --- src/qemu/qemu_passt.c | 16 +++++++++++++ src/qemu/qemu_validate.c | 50 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/qemu/qemu_passt.c b/src/qemu/qemu_passt.c index 56d048d585..125227747d 100644 --- a/src/qemu/qemu_passt.c +++ b/src/qemu/qemu_passt.c @@ -263,6 +263,22 @@ qemuPasstBuildCommand(char **socketName, } } + /* Add default route(s) */ + for (i = 0; i < net->guestIP.nroutes; i++) { + const virNetDevIPRoute *route = net->guestIP.routes[i]; + g_autofree char *gateway = NULL; + + if (!(gateway = virSocketAddrFormat(&route->gateway))) + return NULL; + + /* validation has already guaranteed that there is at most 1 + * IPv4 and 1 IPv6 route, and that they are only default + * routes (i.e. destination 0.0.0.0/0) + */ + + virCommandAddArgList(cmd, "--gateway", gateway, NULL); + } + /* Add port forwarding info */ for (i = 0; i < net->nPortForwards; i++) { diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c index 1c6662751b..c194a8a7cd 100644 --- a/src/qemu/qemu_validate.c +++ b/src/qemu/qemu_validate.c @@ -1884,6 +1884,8 @@ qemuValidateDomainDeviceDefNetwork(const virDomainNetDef *net, { bool hasV4Addr = false; bool hasV6Addr = false; + bool hasV4Route = false; + bool hasV6Route = false; size_t i; if (net->type == VIR_DOMAIN_NET_TYPE_USER || @@ -1958,10 +1960,50 @@ qemuValidateDomainDeviceDefNetwork(const virDomainNetDef *net, } } - if (net->guestIP.nroutes) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("Invalid attempt to set network interface guest-side IP route, not supported by QEMU")); - return -1; + + for (i = 0; i < net->guestIP.nroutes; i++) { + const virNetDevIPRoute *route = net->guestIP.routes[i]; + + if (net->backend.type != VIR_DOMAIN_NET_BACKEND_PASST) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Invalid attempt to set network interface guest-side IP route, not supported for this interface type/backend")); + return -1; + } + + switch (VIR_SOCKET_ADDR_FAMILY(&route->gateway)) { + case AF_INET: + if (hasV4Route) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("only one IPv4 default route can be specified for an interface using the passt backend")); + return -1; + } + hasV4Route = true; + break; + case AF_INET6: + if (hasV6Route) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("only one IPv6 default route can be specified for an interface using the passt backend")); + return -1; + } + hasV6Route = true; + break; + default: + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("All <route> elements of an interface using the passt backend must be default routes, with an IPv4 or IPv6 gateway specified")); + return -1; + } + + /* the only type of route that can be specified for passt is + * the default route, so none of the parameters except gateway + * are acceptable + */ + if (VIR_SOCKET_ADDR_VALID(&route->address) || + virNetDevIPRouteGetPrefix(route) != 0 || + route->has_metric) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("<route> elements of an interface using the passt backend must be default routes, with only a gateway specified")); + return -1; + } } if (net->type == VIR_DOMAIN_NET_TYPE_VDPA) { -- 2.52.0
On 2/24/26 08:52, Laine Stump via Devel wrote:
From: Laine Stump <laine@redhat.com>
libvirt's <interface> element has for a long time supported adding <route> sub-elements to specify arbitrary routes to be added to the guest OS networking, but historically this has only worked for LXC guests. If you tried to add <route> to the interface of a QEMU guest, it would be rejected.
passt networking doesn't support setting *any arbitrary* route but it does support setting a default route (using the passt commandline "--gateway" parameter). A default route is really just a "route with unspecified destination/prefix", so a default route can be specified in libvirt XML with:
<route gateway='192.168.0.1'/>
Attempts to give a specified destination, prefix, or metric will result in a validation error.
Resolves: https://issues.redhat.com/browse/RHEL-46602 Signed-off-by: Laine Stump <laine@redhat.com> --- src/qemu/qemu_passt.c | 16 +++++++++++++ src/qemu/qemu_validate.c | 50 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 4 deletions(-)
Please, squash in the following: diff --git i/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.passt0.args w/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.passt0.args index b0f26d8089..182d8ae054 100644 --- i/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.passt0.args +++ w/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.passt0.args @@ -9,6 +9,7 @@ passt \ --address 172.17.2.0 \ --netmask 24 \ --address 2001:db8:ac10:fd01::feed \ +--gateway 192.168.0.1 \ --tcp-ports '2001:db8:ac10:fd01::1:10/22:2022,1000-1050,~1020,~1030-1040' \ --udp-ports '1.2.3.4%eth0/5000-5020:6000-6020,~5010-5015' \ --tcp-ports 80 \ diff --git i/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.xml w/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.xml index 77da297936..ecad9a40b5 100644 --- i/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.xml +++ w/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.xml @@ -33,6 +33,7 @@ <source dev='eth42'/> <ip address='172.17.2.0' family='ipv4' prefix='24'/> <ip address='2001:db8:ac10:fd01::feed' family='ipv6'/> + <route gateway='192.168.0.1'/> <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10'> <range start='22' to='2022'/> <range start='1000' end='1050'/> diff --git i/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.passt0.args w/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.passt0.args index b0f26d8089..182d8ae054 100644 --- i/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.passt0.args +++ w/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.passt0.args @@ -9,6 +9,7 @@ passt \ --address 172.17.2.0 \ --netmask 24 \ --address 2001:db8:ac10:fd01::feed \ +--gateway 192.168.0.1 \ --tcp-ports '2001:db8:ac10:fd01::1:10/22:2022,1000-1050,~1020,~1030-1040' \ --udp-ports '1.2.3.4%eth0/5000-5020:6000-6020,~5010-5015' \ --tcp-ports 80 \ diff --git i/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.xml w/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.xml index 917a9edaa0..fc5a5e37aa 100644 --- i/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.xml +++ w/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.xml @@ -33,6 +33,7 @@ <source dev='eth42'/> <ip address='172.17.2.0' family='ipv4' prefix='24'/> <ip address='2001:db8:ac10:fd01::feed' family='ipv6'/> + <route gateway='192.168.0.1'/> <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10'> <range start='22' to='2022'/> <range start='1000' end='1050'/> diff --git i/tests/qemuxmlconfdata/net-user-passt.xml w/tests/qemuxmlconfdata/net-user-passt.xml index 80d15de2ed..3edad133aa 100644 --- i/tests/qemuxmlconfdata/net-user-passt.xml +++ w/tests/qemuxmlconfdata/net-user-passt.xml @@ -30,6 +30,7 @@ <source dev='eth42'/> <ip address='172.17.2.0' family='ipv4' prefix='24'/> <ip address='2001:db8:ac10:fd01::feed' family='ipv6'/> + <route gateway='192.168.0.1'/> <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10'> <range start='22' to='2022'/> <range start='1000' end='1050'/> Michal
On 2/24/26 8:12 AM, Michal Prívozník via Devel wrote:
On 2/24/26 08:52, Laine Stump via Devel wrote:
From: Laine Stump <laine@redhat.com>
libvirt's <interface> element has for a long time supported adding <route> sub-elements to specify arbitrary routes to be added to the guest OS networking, but historically this has only worked for LXC guests. If you tried to add <route> to the interface of a QEMU guest, it would be rejected.
passt networking doesn't support setting *any arbitrary* route but it does support setting a default route (using the passt commandline "--gateway" parameter). A default route is really just a "route with unspecified destination/prefix", so a default route can be specified in libvirt XML with:
<route gateway='192.168.0.1'/>
Attempts to give a specified destination, prefix, or metric will result in a validation error.
Resolves: https://issues.redhat.com/browse/RHEL-46602 Signed-off-by: Laine Stump <laine@redhat.com> --- src/qemu/qemu_passt.c | 16 +++++++++++++ src/qemu/qemu_validate.c | 50 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 4 deletions(-)
Please, squash in the following:
This (forgetting to add docs and a test case) is what happens when I post something at 3AM just so I can get it out of my hair! :-). I'll do you one better - I'll make one of the cases add an IPv6 default route.
diff --git i/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.passt0.args w/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.passt0.args index b0f26d8089..182d8ae054 100644 --- i/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.passt0.args +++ w/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.passt0.args @@ -9,6 +9,7 @@ passt \ --address 172.17.2.0 \ --netmask 24 \ --address 2001:db8:ac10:fd01::feed \ +--gateway 192.168.0.1 \ --tcp-ports '2001:db8:ac10:fd01::1:10/22:2022,1000-1050,~1020,~1030-1040' \ --udp-ports '1.2.3.4%eth0/5000-5020:6000-6020,~5010-5015' \ --tcp-ports 80 \ diff --git i/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.xml w/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.xml index 77da297936..ecad9a40b5 100644 --- i/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.xml +++ w/tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.xml @@ -33,6 +33,7 @@ <source dev='eth42'/> <ip address='172.17.2.0' family='ipv4' prefix='24'/> <ip address='2001:db8:ac10:fd01::feed' family='ipv6'/> + <route gateway='192.168.0.1'/> <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10'> <range start='22' to='2022'/> <range start='1000' end='1050'/> diff --git i/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.passt0.args w/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.passt0.args index b0f26d8089..182d8ae054 100644 --- i/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.passt0.args +++ w/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.passt0.args @@ -9,6 +9,7 @@ passt \ --address 172.17.2.0 \ --netmask 24 \ --address 2001:db8:ac10:fd01::feed \ +--gateway 192.168.0.1 \ --tcp-ports '2001:db8:ac10:fd01::1:10/22:2022,1000-1050,~1020,~1030-1040' \ --udp-ports '1.2.3.4%eth0/5000-5020:6000-6020,~5010-5015' \ --tcp-ports 80 \ diff --git i/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.xml w/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.xml index 917a9edaa0..fc5a5e37aa 100644 --- i/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.xml +++ w/tests/qemuxmlconfdata/net-user-passt.x86_64-latest.xml @@ -33,6 +33,7 @@ <source dev='eth42'/> <ip address='172.17.2.0' family='ipv4' prefix='24'/> <ip address='2001:db8:ac10:fd01::feed' family='ipv6'/> + <route gateway='192.168.0.1'/> <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10'> <range start='22' to='2022'/> <range start='1000' end='1050'/> diff --git i/tests/qemuxmlconfdata/net-user-passt.xml w/tests/qemuxmlconfdata/net-user-passt.xml index 80d15de2ed..3edad133aa 100644 --- i/tests/qemuxmlconfdata/net-user-passt.xml +++ w/tests/qemuxmlconfdata/net-user-passt.xml @@ -30,6 +30,7 @@ <source dev='eth42'/> <ip address='172.17.2.0' family='ipv4' prefix='24'/> <ip address='2001:db8:ac10:fd01::feed' family='ipv6'/> + <route gateway='192.168.0.1'/> <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10'> <range start='22' to='2022'/> <range start='1000' end='1050'/>
Michal
On a Tuesday in 2026, Laine Stump via Devel wrote:
The first patch cleans up a dangling socket that was hanging around after we finished using a passt process in vhostuser mode. The rest are to support specifying the default route for a passt interface with
<route gateway='x.x.x.x'/>
Laine Stump (6): qemu: delete passt "repair" socket when appropriate qemu: reorganize qemuValidateDomainDeviceDefNetwork() qemu: only limit IPv4 prefix for slirp util: make the route arg to virNetDevIPRouteGetPrefix const conf/util: fix non-specification of IP route destination address qemu: support setting default route for passt interfaces inside the guest
src/conf/networkcommon_conf.c | 74 ++++++++------- src/conf/schemas/networkcommon.rng | 4 +- src/qemu/qemu_passt.c | 30 ++++++- src/qemu/qemu_validate.c | 140 +++++++++++++++++++---------- src/util/virnetdevip.c | 14 ++- src/util/virnetdevip.h | 2 +- 6 files changed, 170 insertions(+), 94 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano
participants (3)
-
Ján Tomko -
Laine Stump -
Michal Prívozník