]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
qemu: support setting default route for passt interfaces inside the guest
authorLaine Stump <laine@redhat.com>
Thu, 12 Feb 2026 16:23:10 +0000 (11:23 -0500)
committerLaine Stump <laine@redhat.com>
Mon, 9 Mar 2026 05:15:00 +0000 (01:15 -0400)
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>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
docs/formatdomain.rst
src/qemu/qemu_passt.c
src/qemu/qemu_validate.c
tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.passt0.args
tests/qemuxmlconfdata/net-user-passt.x86_64-7.2.0.xml
tests/qemuxmlconfdata/net-user-passt.x86_64-latest.passt0.args
tests/qemuxmlconfdata/net-user-passt.x86_64-latest.xml
tests/qemuxmlconfdata/net-user-passt.xml
tests/qemuxmlconfdata/net-vhostuser-passt.x86_64-latest.passt0.args
tests/qemuxmlconfdata/net-vhostuser-passt.x86_64-latest.xml
tests/qemuxmlconfdata/net-vhostuser-passt.xml

index b4e28e99ef1a2c0dc79f5155b48c14e6240dee67..9f245293e6a8dc2859e8741025187c30ab6cfe39 100644 (file)
@@ -6689,11 +6689,14 @@ IPv6 the default prefix is 64. The optional ``peer`` attribute holds the IP
 address of the other end of a point-to-point network device
 :since:`(since 2.1.0)`.
 
-:since:`Since 1.2.12` route elements can also be added to define IP routes to
-add in the guest. The attributes of this element are described in the
-documentation for the ``route`` element in `network
-definitions <formatnetwork.html#static-routes>`__. This is used by the LXC
-driver.
+:since:`Since 1.2.12` route elements can also be added to define IP
+routes to add in the guest. The attributes of this element are
+described in the documentation for the ``route`` element in `network
+definitions <formatnetwork.html#static-routes>`__. This is used by the
+LXC driver for adding general routes within the container. :since:
+'Since 12.2.0' ``route`` elements are also user by the QEMU driver only in the
+case of a passt-based interface (``<backend type='passt'/>``) and only
+for default routes (done by specifying just the ``gateway``).
 
 ::
 
@@ -6799,6 +6802,7 @@ setting guest-side IP addresses with ``<ip>`` and port forwarding with
        <mac address='52:54:00:3b:83:1a'/>
        <source dev='enp1s0'/>
        <ip address='10.30.0.5' prefix='24'/>
+       <route gateway='10.30.0.1'/>
      </interface>
    </devices>
    ...
index 746eef3a0ff8eb4f359481a2c36572aa7fabc09f..a142620b37684fc6040058a3adfd62dac1569ec8 100644 (file)
@@ -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++) {
index 5c664d549b7bd3c8273ef281a7f5196b36fed109..bb3b2fee7eaa132471bdc136e43cd3fa48847656 100644 (file)
@@ -1904,6 +1904,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 ||
@@ -1978,10 +1980,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) {
index b0f26d8089d94e06827dc2adfa21b06bfda4cedf..73ffcfe405b884581e706a9ae119e57871dda40c 100644 (file)
@@ -9,6 +9,7 @@ passt \
 --address 172.17.2.0 \
 --netmask 24 \
 --address 2001:db8:ac10:fd01::feed \
+--gateway 172.17.2.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 \
index 77da2979367d8328a270494fbc8e562e0c0972f1..401b1ac0a28769b0818f50769865f4447e75fe3d 100644 (file)
@@ -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='172.17.2.1'/>
       <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10'>
         <range start='22' to='2022'/>
         <range start='1000' end='1050'/>
index b0f26d8089d94e06827dc2adfa21b06bfda4cedf..73ffcfe405b884581e706a9ae119e57871dda40c 100644 (file)
@@ -9,6 +9,7 @@ passt \
 --address 172.17.2.0 \
 --netmask 24 \
 --address 2001:db8:ac10:fd01::feed \
+--gateway 172.17.2.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 \
index 917a9edaa0c3e054427a906e242970631a15f1d1..625c656ca195c7ad415443a586e41894fef169d1 100644 (file)
@@ -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='172.17.2.1'/>
       <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10'>
         <range start='22' to='2022'/>
         <range start='1000' end='1050'/>
index 80d15de2edceaa825278b102ff34e0b4e1bad033..8c1565e7fe723ceef399cbddab6eb73ad75bb748 100644 (file)
@@ -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='172.17.2.1'/>
       <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10'>
         <range start='22' to='2022'/>
         <range start='1000' end='1050'/>
index bd176fd3557cb398aa36e4f8da33938e29965c19..71209c3cfc6918fc60b16b845cea10f0a4e50e86 100644 (file)
@@ -10,6 +10,7 @@ passt \
 --address 172.17.2.0 \
 --netmask 24 \
 --address 2001:db8:ac10:fd01::feed \
+--gateway 2001:db8:ac10:fd01::beef \
 --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 \
index 5802754c4bc5f266f907828a986a01b87a6d8ec4..f14fa49317efb16a0288ed4628928b230fa09ee8 100644 (file)
@@ -36,6 +36,7 @@
       <source dev='eth42'/>
       <ip address='172.17.2.0' family='ipv4' prefix='24'/>
       <ip address='2001:db8:ac10:fd01::feed' family='ipv6'/>
+      <route family='ipv6' gateway='2001:db8:ac10:fd01::beef'/>
       <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10'>
         <range start='22' to='2022'/>
         <range start='1000' end='1050'/>
index 0a37511a0fdb95fb2fbdb319dcf1079aeed2ec1e..417903b7dcd74abf9f0895f3c1accb34c9592581 100644 (file)
@@ -32,6 +32,7 @@
       <mac address='00:11:22:33:44:55'/>
       <ip address='172.17.2.0' family='ipv4' prefix='24'/>
       <ip address='2001:db8:ac10:fd01::feed' family='ipv6'/>
+      <route family='ipv6' gateway='2001:db8:ac10:fd01::beef'/>
       <source dev='eth42'/>
       <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10'>
         <range start='22' to='2022'/>