diff --git a/docs/formatnetwork.html.in b/docs/formatnetwork.html.in index d72bd0a9f36dfff1a7609de33e851d28f562bc32..a1198ce6beecadc65e35d512c1c92ed5c6d6e04a 100644 --- a/docs/formatnetwork.html.in +++ b/docs/formatnetwork.html.in @@ -546,6 +546,62 @@ starting.

+
Static Routes
+

+ Static route definitions are used to provide routing information + to the virtualization host for networks which are not directly + reachable from the virtualization host, but *are* reachable from + a guest domain that is itself reachable from the + host since 1.0.6. +

+ +

+ As shown in this + example, it is possible to define a virtual network + interface with no IPv4 or IPv6 addresses. Such networks are + useful to provide host connectivity to networks which are only + reachable via a guest. A guest with connectivity both to the + guest-only network and to another network that is directly + reachable from the host can act as a gateway between the + networks. A static route added to the "host-visible" network + definition provides the routing information so that IP packets + can be sent from the virtualization host to guests on the hidden + network. +

+ +

+ Here is a fragment of a definition which shows the static + route specification as well as the IPv4 and IPv6 definitions + for network addresses which are referred to in the + gateway gateway address specifications. Note + that the third static route specification includes the + metric attribute specification with a value of 2. + This particular route would *not* be preferred if there was + another existing rout on the system with the same address and + prefix but with a lower value for the metric. If there is a + route in the host system configuration that should be overriden + by a route in a virtual network whenever the virtual network is + running, the configuration for the system-defined route should + be modified to have a higher metric, and the route on the + virtual network given a lower metric (for example, the default + metric of "1"). +

+ +
+      ...
+        <ip address="192.168.122.1" netmask="255.255.255.0">
+          <dhcp>
+            <range start="192.168.122.128" end="192.168.122.254" />
+          </dhcp>
+        </ip>
+        <route address="192.168.222.0" prefix="24" gateway="192.168.122.2" />
+        <ip family="ipv6" address="2001:db8:ca2:2::1" prefix="64" />
+        <route family="ipv6" address="2001:db8:ca2:3::" prefix="64" gateway="2001:db8:ca2:2::2"/>
+        <route family="ipv6" address="2001:db9:4:1::" prefix="64" gateway="2001:db8:ca2:2::3" metric='2'>
+        </route>
+      ...
+    
+

Addressing

@@ -577,6 +633,7 @@ </dhcp> </ip> <ip family="ipv6" address="2001:db8:ca2:2::1" prefix="64" /> + <route family="ipv6" address="2001:db9:ca1:1::" prefix="64" gateway="2001:db8:ca2:2::2" /> </network>

@@ -826,6 +883,33 @@ </ip> </network> +

+ Below is yet another IPv6 variation. This variation has only + IPv6 defined with DHCPv6 on the primary IPv6 network. A static + link if defined for a second IPv6 network which will not be + directly visible on the bridge interface but there will be a + static route defined for this network via the specified + gateway. Note that the gateway address must be directly + reachable via (on the same subnet as) one of the <ip> + addresses defined for this <network>. + Since 1.0.6 +

+ +
+      <network>
+        <name>net7</name>
+        <bridge name="virbr7" />
+        <forward mode="route"/>
+        <ip family="ipv6" address="2001:db8:ca2:7::1" prefix="64" >
+          <dhcp>
+            <range start="2001:db8:ca2:7::100" end="2001:db8:ca2::1ff" />
+            <host id="0:4:7e:7d:f0:7d:a8:bc:c5:d2:13:32:11:ed:16:ea:84:63" name="lucas" ip="2001:db8:ca2:2:3::4" />
+          </dhcp>
+        </ip>
+        <route family="ipv6" address="2001:db8:ca2:8::" prefix="64" gateway="2001:db8:ca2:7::4" >
+        </route>
+      </network>
+

Isolated network config

diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 493edaeb6489be0dc565461e9bffc344dd4e9c7c..ded858092aebf5e1bd097cf42d597b5a51f40c26 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -316,6 +316,28 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 0c0e58eb6e89ce9ab1d8404e6ebbc01d0bbd8165..eb10c0a82bbc021624ed6f326a300d2618ca231e 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -142,6 +142,12 @@ virNetworkIpDefClear(virNetworkIpDefPtr def) VIR_FREE(def->bootfile); } +static void +virNetworkRouteDefClear(virNetworkRouteDefPtr def) +{ + VIR_FREE(def->family); +} + static void virNetworkDNSTxtDefClear(virNetworkDNSTxtDefPtr def) { @@ -221,6 +227,11 @@ virNetworkDefFree(virNetworkDefPtr def) } VIR_FREE(def->ips); + for (ii = 0 ; ii < def->nroutes && def->routes ; ii++) { + virNetworkRouteDefClear(&def->routes[ii]); + } + VIR_FREE(def->routes); + for (ii = 0; ii < def->nPortGroups && def->portGroups; ii++) { virPortGroupDefClear(&def->portGroups[ii]); } @@ -1269,6 +1280,233 @@ cleanup: return result; } +static int +virNetworkRouteDefParseXML(const char *networkName, + xmlNodePtr node, + xmlXPathContextPtr ctxt, + virNetworkRouteDefPtr def) +{ + /* + * virNetworkRouteDef object is already allocated as part + * of an array. On failure clear: it out, but don't free it. + */ + + xmlNodePtr save; + char *address = NULL, *netmask = NULL; + char *gateway = NULL; + unsigned long prefix = 0, metric = 0; + int result = -1; + int prefixRc, metricRc; + virSocketAddr testAddr; + + save = ctxt->node; + ctxt->node = node; + + /* grab raw data from XML */ + def->family = virXPathString("string(./@family)", ctxt); + address = virXPathString("string(./@address)", ctxt); + netmask = virXPathString("string(./@netmask)", ctxt); + gateway = virXPathString("string(./@gateway)", ctxt); + prefixRc = virXPathULong("string(./@prefix)", ctxt, &prefix); + if (prefixRc == -2) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid prefix specified " + "in route definition of network '%s'"), + networkName); + goto cleanup; + } + def->has_prefix = (prefixRc == 0); + def->prefix = prefix; + metricRc = virXPathULong("string(./@metric)", ctxt, &metric); + if (metricRc == -2) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid metric specified " + "in route definition of network '%s'"), + networkName); + goto cleanup; + } + if (metricRc == 0) { + def->has_metric = true; + if (metric == 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid metric value, must be > 0 " + "in route definition of network '%s'"), + networkName); + goto cleanup; + } + } + def->metric = metric; + + /* Note: both network and gateway addresses must be specified */ + + if (!address) { + virReportError(VIR_ERR_XML_ERROR, + _("Missing required address attribute " + "in route definition of network '%s'"), + networkName); + goto cleanup; + } + + if (!gateway) { + virReportError(VIR_ERR_XML_ERROR, + _("Missing required gateway attribute " + "in route definition of network '%s'"), + networkName); + goto cleanup; + } + + if (virSocketAddrParse(&def->address, address, AF_UNSPEC) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Bad network address '%s' " + "in route definition of network '%s'"), + address, networkName); + goto cleanup; + } + + if (virSocketAddrParse(&def->gateway, gateway, AF_UNSPEC) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Bad gateway address '%s' " + "in route definition of network '%s'"), + gateway, networkName); + goto cleanup; + } + + /* validate network address, etc. for each family */ + if ((def->family == NULL) || (STREQ(def->family, "ipv4"))) { + if (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) || + VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_UNSPEC))) { + virReportError(VIR_ERR_XML_ERROR, + def->family == NULL ? + _("No family specified for non-IPv4 address '%s' " + "in route definition of network '%s'") : + _("IPv4 family specified for non-IPv4 address '%s' " + "in route definition of network '%s'"), + address, networkName); + goto cleanup; + } + if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->gateway, AF_INET)) { + virReportError(VIR_ERR_XML_ERROR, + def->family == NULL ? + _("No family specified for non-IPv4 gateway '%s' " + "in route definition of network '%s'") : + _("IPv4 family specified for non-IPv4 gateway '%s' " + "in route definition of network '%s'"), + address, networkName); + goto cleanup; + } + if (netmask) { + if (virSocketAddrParse(&def->netmask, netmask, AF_UNSPEC) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Bad netmask address '%s' " + "in route definition of network '%s'"), + netmask, networkName); + goto cleanup; + } + if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->netmask, AF_INET)) { + virReportError(VIR_ERR_XML_ERROR, + _("Network '%s' has invalid netmask '%s' " + "for address '%s' (both must be IPv4)"), + networkName, netmask, address); + goto cleanup; + } + if (def->has_prefix) { + /* can't have both netmask and prefix at the same time */ + virReportError(VIR_ERR_XML_ERROR, + _("Route definition '%s' cannot have both " + "a prefix and a netmask"), + networkName); + goto cleanup; + } + } + if (def->prefix > 32) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid prefix %u specified " + "in route definition of network '%s', " + "must be 0 - 32"), + def->prefix, networkName); + goto cleanup; + } + } else if (STREQ(def->family, "ipv6")) { + if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6)) { + virReportError(VIR_ERR_XML_ERROR, + _("ipv6 family specified for non-IPv6 address '%s' " + "in route definition of network '%s'"), + address, networkName); + goto cleanup; + } + if (netmask) { + virReportError(VIR_ERR_XML_ERROR, + _("Specifying netmask invalid for IPv6 address '%s' " + "in route definition of network '%s'"), + address, networkName); + goto cleanup; + } + if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->gateway, AF_INET6)) { + virReportError(VIR_ERR_XML_ERROR, + _("ipv6 specified for non-IPv6 gateway address '%s' " + "in route definition of network '%s'"), + gateway, networkName); + goto cleanup; + } + if (def->prefix > 128) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid prefix %u specified " + "in route definition of network '%s', " + "must be 0 - 128"), + def->prefix, networkName); + goto cleanup; + } + } else { + virReportError(VIR_ERR_XML_ERROR, + _("Unrecognized family '%s' " + "in route definition of network'%s'"), + def->family, networkName); + goto cleanup; + } + + /* make sure the address is a network address */ + if (netmask) { + if (virSocketAddrMask(&def->address, &def->netmask, &testAddr) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("error converting address '%s' with netmask '%s' " + "to network-address " + "in route definition of network '%s'"), + address, netmask, networkName); + goto cleanup; + } + } else { + if (virSocketAddrMaskByPrefix(&def->address, + def->prefix, &testAddr) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("error converting address '%s' with prefix %u " + "to network-address " + "in route definition of network '%s'"), + address, def->prefix, networkName); + goto cleanup; + } + } + if (!virSocketAddrEqual(&def->address, &testAddr)) { + virReportError(VIR_ERR_XML_ERROR, + _("address '%s' in route definition of network '%s' " + "is not a network address"), + address, networkName); + goto cleanup; + } + + result = 0; + +cleanup: + if (result < 0) { + virNetworkRouteDefClear(def); + } + VIR_FREE(address); + VIR_FREE(netmask); + VIR_FREE(gateway); + + ctxt->node = save; + return result; +} + static int virNetworkPortGroupParseXML(virPortGroupDefPtr def, xmlNodePtr node, @@ -1684,8 +1922,9 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt) char *tmp; char *stp = NULL; xmlNodePtr *ipNodes = NULL; + xmlNodePtr *routeNodes = NULL; xmlNodePtr *portGroupNodes = NULL; - int nIps, nPortGroups; + int nIps, nPortGroups, nRoutes; xmlNodePtr dnsNode = NULL; xmlNodePtr virtPortNode = NULL; xmlNodePtr forwardNode = NULL; @@ -1839,6 +2078,69 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt) } VIR_FREE(ipNodes); + nRoutes = virXPathNodeSet("./route", ctxt, &routeNodes); + if (nRoutes < 0) + goto error; + + if (nRoutes > 0) { + int ii; + + /* allocate array to hold all the route definitions */ + if (VIR_ALLOC_N(def->routes, nRoutes) < 0) { + virReportOOMError(); + goto error; + } + /* parse each definition */ + for (ii = 0; ii < nRoutes; ii++) { + int ret = virNetworkRouteDefParseXML(def->name, routeNodes[ii], + ctxt, &def->routes[ii]); + if (ret < 0) + goto error; + def->nroutes++; + } + + /* now validate the correctness of any static route gateways specified + * + * note: the parameters within each definition are verified/assumed valid; + * the question being asked and answered here is if the specified gateway + * is directly reachable from this bridge. + */ + nRoutes = def->nroutes; + nIps = def->nips; + for (ii = 0; ii < nRoutes; ii++) { + int jj; + virSocketAddr testAddr, testGw; + bool addrMatch; + virNetworkRouteDefPtr gwdef = &def->routes[ii]; + addrMatch = false; + for (jj = 0; jj < nIps; jj++) { + virNetworkIpDefPtr def2 = &def->ips[jj]; + if (VIR_SOCKET_ADDR_FAMILY(&gwdef->gateway) + != VIR_SOCKET_ADDR_FAMILY(&def2->address)) { + continue; + } + int prefix = virNetworkIpDefPrefix(def2); + virSocketAddrMaskByPrefix(&def2->address, prefix, &testAddr); + virSocketAddrMaskByPrefix(&gwdef->gateway, prefix, &testGw); + if (VIR_SOCKET_ADDR_VALID(&testAddr) && + VIR_SOCKET_ADDR_VALID(&testGw) && + virSocketAddrEqual(&testAddr, &testGw)) { + addrMatch = true; + break; + } + } + if (!addrMatch) { + char *gw = virSocketAddrFormat(&gwdef->gateway); + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unreachable static route gateway '%s' specified for network '%s'"), + gw, def->name); + VIR_FREE(gw); + goto error; + } + } + } + VIR_FREE(routeNodes); + forwardNode = virXPathNode("./forward", ctxt); if (forwardNode && virNetworkForwardDefParseXML(def->name, forwardNode, ctxt, &def->forward) < 0) { @@ -1911,6 +2213,7 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt) return def; error: + VIR_FREE(routeNodes); VIR_FREE(stp); virNetworkDefFree(def); VIR_FREE(ipNodes); @@ -2135,6 +2438,53 @@ error: return result; } +static int +virNetworkRouteDefFormat(virBufferPtr buf, + const virNetworkRouteDefPtr def) +{ + int result = -1; + + virBufferAddLit(buf, "family) { + virBufferAsprintf(buf, " family='%s'", def->family); + } + if (VIR_SOCKET_ADDR_VALID(&def->address)) { + char *addr = virSocketAddrFormat(&def->address); + + if (!addr) + goto error; + virBufferAsprintf(buf, " address='%s'", addr); + VIR_FREE(addr); + } + if (VIR_SOCKET_ADDR_VALID(&def->netmask)) { + char *addr = virSocketAddrFormat(&def->netmask); + + if (!addr) + goto error; + virBufferAsprintf(buf, " netmask='%s'", addr); + VIR_FREE(addr); + } + if (def->has_prefix) { + virBufferAsprintf(buf," prefix='%u'", def->prefix); + } + if (VIR_SOCKET_ADDR_VALID(&def->gateway)) { + char *addr = virSocketAddrFormat(&def->gateway); + if (!addr) + goto error; + virBufferAsprintf(buf, " gateway='%s'", addr); + VIR_FREE(addr); + } + if (def->has_metric && def->metric > 0) { + virBufferAsprintf(buf," metric='%u'", def->metric); + } + virBufferAddLit(buf, "/>\n"); + + result = 0; +error: + return result; +} + static int virPortGroupDefFormat(virBufferPtr buf, const virPortGroupDefPtr def) @@ -2347,6 +2697,11 @@ virNetworkDefFormatInternal(virBufferPtr buf, goto error; } + for (ii = 0; ii < def->nroutes; ii++) { + if (virNetworkRouteDefFormat(buf, &def->routes[ii]) < 0) + goto error; + } + if (virNetDevVPortProfileFormat(def->virtPortProfile, buf) < 0) goto error; diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index e187f0588cc2faae9a0d990ed2be29527b0e8b1c..43f80d4ebdbb85c0a239d489d6bdd0dcf58ee706 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -149,6 +149,25 @@ struct _virNetworkIpDef { virSocketAddr bootserver; }; +typedef struct _virNetworkRouteDef virNetworkRouteDef; +typedef virNetworkRouteDef *virNetworkRouteDefPtr; +struct _virNetworkRouteDef { + char *family; /* ipv4 or ipv6 - default is ipv4 */ + virSocketAddr address; /* Routed Network IP address */ + + /* One or the other of the following two will be used for a given + * Network address, but never both. The parser guarantees this. + * The virSocketAddrGetIpPrefix() can be used to get a + * valid prefix. + */ + virSocketAddr netmask; /* ipv4 - either netmask or prefix specified */ + unsigned int prefix; /* ipv6 - only prefix allowed */ + bool has_prefix; /* prefix= was specified */ + unsigned int metric; /* value for metric (defaults to 1) */ + bool has_metric; /* metric= was specified */ + virSocketAddr gateway; /* gateway IP address for ip-route */ + }; + typedef struct _virNetworkForwardIfDef virNetworkForwardIfDef; typedef virNetworkForwardIfDef *virNetworkForwardIfDefPtr; struct _virNetworkForwardIfDef { @@ -224,6 +243,9 @@ struct _virNetworkDef { size_t nips; virNetworkIpDefPtr ips; /* ptr to array of IP addresses on this network */ + size_t nroutes; + virNetworkRouteDefPtr routes; /* ptr to array of static routes on this interface */ + virNetworkDNSDef dns; /* dns related configuration */ virNetDevVPortProfilePtr virtPortProfile; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 2e2387e420a4f3e0701615ae1b57f6fbb530efb6..94c02abfe05f33befbb071a33e8a1a7f2a3dae00 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1498,6 +1498,7 @@ virMacAddrSetRaw; # util/virnetdev.h +virNetDevAddRoute; virNetDevClearIPv4Address; virNetDevExists; virNetDevGetIndex; diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 99c1316e0baf64b2b71a2292ea08cb08fd73824b..6c6ce6dd4c335ad7ad98e4df9719a0f26c90e9cc 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -2390,6 +2390,7 @@ out: return ret; } +/* add an IP address to a bridge */ static int networkAddAddrToBridge(virNetworkObjPtr network, virNetworkIpDefPtr ipdef) @@ -2410,6 +2411,55 @@ networkAddAddrToBridge(virNetworkObjPtr network, return 0; } +/* add an IP (static) route to a bridge */ +static int +networkAddRouteToBridge(virNetworkObjPtr network, + virNetworkRouteDefPtr routedef) +{ + int prefix = 0; + unsigned int metric; + virSocketAddrPtr addr = &routedef->address; + virSocketAddrPtr mask = &routedef->netmask; + virSocketAddr zero; + + /* this creates an all-0 address of the appropriate family */ + ignore_value(virSocketAddrParse(&zero, + (VIR_SOCKET_ADDR_IS_FAMILY(addr,AF_INET) + ? "0.0.0.0" : "::"), + VIR_SOCKET_ADDR_FAMILY(addr))); + + if (virSocketAddrEqual(addr, &zero)) { + if (routedef->has_prefix && routedef->prefix == 0) + prefix = 0; + else if ((VIR_SOCKET_ADDR_IS_FAMILY(mask, AF_INET) && + virSocketAddrEqual(mask, &zero))) + prefix = 0; + else + prefix = virSocketAddrGetIpPrefix(addr, mask, routedef->prefix); + } else { + prefix = virSocketAddrGetIpPrefix(addr, mask, routedef->prefix); + } + + if (prefix < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("network '%s' has an invalid netmask " + "or IP address in route definition"), + network->def->name); + return -1; + } + + if (routedef->has_metric && routedef->metric > 0) + metric = routedef->metric; + else + metric = 1; + + if (virNetDevAddRoute(network->def->bridge, &routedef->address, + prefix, &routedef->gateway, metric) < 0) { + return -1; + } + return 0; +} + static int networkStartNetworkVirtual(struct network_driver *driver, virNetworkObjPtr network) @@ -2418,6 +2468,7 @@ networkStartNetworkVirtual(struct network_driver *driver, bool v4present = false, v6present = false; virErrorPtr save_err = NULL; virNetworkIpDefPtr ipdef; + virNetworkRouteDefPtr routedef; char *macTapIfName = NULL; int tapfd = -1; @@ -2494,6 +2545,19 @@ networkStartNetworkVirtual(struct network_driver *driver, if (virNetDevSetOnline(network->def->bridge, 1) < 0) goto err2; + for (ii = 0; ii < network->def->nroutes; ii++) { + routedef = &network->def->routes[ii]; + /* Add the IP route to the bridge */ + /* ignore errors, error msg will be generated */ + /* but libvirt will not know and net-destroy will work. */ + if (VIR_SOCKET_ADDR_VALID(&routedef->gateway)) { + if (networkAddRouteToBridge(network, routedef) < 0) { + /* an error occurred adding the static route */ + continue; /* for now, do nothing */ + } + } + } + /* If forward.type != NONE, turn on global IP forwarding */ if (network->def->forward.type != VIR_NETWORK_FORWARD_NONE && networkEnableIpForwarding(v4present, v6present) < 0) { diff --git a/src/util/virnetdev.c b/src/util/virnetdev.c index 251a66ae6cfb8539d6d7f540a51af41e6a63a084..cee40013ad38387c9e96072c2dbd39f5b00d6f65 100644 --- a/src/util/virnetdev.c +++ b/src/util/virnetdev.c @@ -821,6 +821,52 @@ cleanup: return ret; } +/** + * virNetDevAddRoute: + * @ifname: the interface name + * @addr: the IP network address (IPv4 or IPv6) + * @prefix: number of 1 bits in the netmask + * @gateway: via address for route (same as @addr) + * + * Add a route for a network IP address to an interface. This function + * *does not* remove any previously added IP static routes. + * + * Returns 0 in case of success or -1 in case of error. + */ + +int +virNetDevAddRoute(const char *ifname, + virSocketAddrPtr addr, + unsigned int prefix, + virSocketAddrPtr gateway, + unsigned int metric) +{ + virCommandPtr cmd = NULL; + char *addrstr = NULL, *gatewaystr = NULL; + int ret = -1; + + if (!(addrstr = virSocketAddrFormat(addr))) + goto cleanup; + if (!(gatewaystr = virSocketAddrFormat(gateway))) + goto cleanup; + cmd = virCommandNew(IP_PATH); + virCommandAddArgList(cmd, "route", "add", NULL); + virCommandAddArgFormat(cmd, "%s/%u", addrstr, prefix); + virCommandAddArgList(cmd, "via", gatewaystr, "dev", ifname, + "proto", "static", "metric", NULL); + virCommandAddArgFormat(cmd, "%u", metric); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + ret = 0; +cleanup: + VIR_FREE(addrstr); + VIR_FREE(gatewaystr); + virCommandFree(cmd); + return ret; +} + /** * virNetDevClearIPv4Address: * @ifname: the interface name diff --git a/src/util/virnetdev.h b/src/util/virnetdev.h index 551ea222278a45f997000e4bcbf00eee630850df..0b394ad9828c5da2af6feabdd7088e16180d668b 100644 --- a/src/util/virnetdev.h +++ b/src/util/virnetdev.h @@ -42,6 +42,13 @@ int virNetDevSetIPv4Address(const char *ifname, virSocketAddr *addr, unsigned int prefix) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; +int virNetDevAddRoute(const char *ifname, + virSocketAddrPtr addr, + unsigned int prefix, + virSocketAddrPtr gateway, + unsigned int metric) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(4) + ATTRIBUTE_NONNULL(5) ATTRIBUTE_RETURN_CHECK; int virNetDevClearIPv4Address(const char *ifname, virSocketAddr *addr, unsigned int prefix) diff --git a/tests/networkxml2xmlin/dhcp6host-routed-network.xml b/tests/networkxml2xmlin/dhcp6host-routed-network.xml index 2693d872fc3c4da71f9d5caa819424962dd66f3c..40f6dfe90f8e4d2b7e1bde2949b24fa28a41e743 100644 --- a/tests/networkxml2xmlin/dhcp6host-routed-network.xml +++ b/tests/networkxml2xmlin/dhcp6host-routed-network.xml @@ -19,4 +19,6 @@ + + diff --git a/tests/networkxml2xmlout/dhcp6host-routed-network.xml b/tests/networkxml2xmlout/dhcp6host-routed-network.xml index 1d3035ba37fd9547a74a640810e697d8d3525e69..fc8666b2580bff96f394ed4b7f16ba4b5bf22d1f 100644 --- a/tests/networkxml2xmlout/dhcp6host-routed-network.xml +++ b/tests/networkxml2xmlout/dhcp6host-routed-network.xml @@ -21,4 +21,6 @@ + +