[libvirt] [PATCH] v1:Support for adding a static route to a bridge

This patch adds support for adding a static route for a network. The "via" specifies the gateway's IP address. Both IPv4 and IPv6 static routes are supported although it is expected that this functionality will have more use with IPv6. Extensive tests are done to validate that the input definitions are correct. For example, for a static route ip definition, the address must be for a network and not a host. Signed-off-by: Gene Czarcinski <gene@czarc.net> --- docs/formatnetwork.html.in | 32 ++++++- docs/schemas/network.rng | 3 + src/conf/network_conf.c | 104 ++++++++++++++++++++- src/conf/network_conf.h | 2 + src/libvirt_private.syms | 1 + src/network/bridge_driver.c | 38 ++++++++ src/util/virnetdev.c | 47 ++++++++++ src/util/virnetdev.h | 5 + .../networkxml2xmlin/dhcp6host-routed-network.xml | 4 + .../networkxml2xmlout/dhcp6host-routed-network.xml | 4 + 10 files changed, 236 insertions(+), 4 deletions(-) diff --git a/docs/formatnetwork.html.in b/docs/formatnetwork.html.in index 4dd0415..f0cadf0 100644 --- a/docs/formatnetwork.html.in +++ b/docs/formatnetwork.html.in @@ -537,7 +537,9 @@ associated with the virtual network, and optionally enable DHCP services. These elements are only valid for isolated networks (no <code>forward</code> element specified), and for those with - a forward mode of 'route' or 'nat'. + a forward mode of 'route' or 'nat'. Another optional addressing + element <code>via</code> can be used to establish a static + route for IPv4 or IPv6 networks. </p> <pre> @@ -560,6 +562,7 @@ </dhcp> </ip> <ip family="ipv6" address="2001:db8:ca2:2::1" prefix="64" /> + <ip family="ipv6" address="2001:db9:ca1:1::" prefix="64" via="2001:db8:ca2:2::2" /> </network></pre> <dl> @@ -633,7 +636,10 @@ IPv6, multiple addresses on a single network, <code>family</code>, and <code>prefix</code> are support <span class="since">Since 0.8.7</span>. Similar to IPv4, one IPv6 address per network can also have - a <code>dhcp</code> definition. <span class="since">Since 1.0.1</span> + a <code>dhcp</code> definition. <span class="since">Since 1.0.1</span> The <code>via</code> + can be used to establish a static route for IPv4 or IPv6 networks. It is an error + to specify <code>via</code> and <code>dhcp</code> for the same IP address. + <span class="since">Since 1.0.4</span> <dl> <dt><code>tftp</code></dt> @@ -809,6 +815,28 @@ </ip> </network></pre> + <p> + Below is yet another IPv6 variation. This variation has only IPv6 + defined with DHCPv6 on the primary IPv6 network. A second IPv6 + network has a static link to a host on the first (virtual) IPv6 + network. <span class="since">Since 1.0.4</span> + </p> + + <pre> + <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> + <ip family="ipv6" address="2001:db8:ca2:8::" prefix="64" via="2001:db8:ca2::4" > + </ip> + </network></pre> + <h3><a name="examplesPrivate">Isolated network config</a></h3> <p> diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 6c3fae2..c1cca23 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -262,6 +262,9 @@ <attribute name="family"><ref name="addr-family"/></attribute> </optional> <optional> + <attribute name="via"><ref name="ipAddr"/></attribute> + </optional> + <optional> <element name="tftp"> <attribute name="root"><text/></attribute> </element> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index c022fe4..a5f7be2 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -1149,8 +1149,10 @@ virNetworkIPDefParseXML(const char *networkName, xmlNodePtr cur, save; char *address = NULL, *netmask = NULL; - unsigned long prefix; + char *gateway = NULL; + unsigned long prefix = 0; int result = -1; + virSocketAddr testAddr; save = ctxt->node; ctxt->node = node; @@ -1162,6 +1164,7 @@ virNetworkIPDefParseXML(const char *networkName, def->prefix = 0; else def->prefix = prefix; + gateway = virXPathString("string(./@via)", ctxt); netmask = virXPathString("string(./@netmask)", ctxt); @@ -1175,7 +1178,17 @@ virNetworkIPDefParseXML(const char *networkName, } - /* validate family vs. address */ + if (gateway) { + if (virSocketAddrParse(&def->gateway, gateway, AF_UNSPEC) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Bad gateway address '%s' in definition of network '%s'"), + gateway, networkName); + goto cleanup; + } + + } + + /* validate family vs. address (and gateway address too) */ if (def->family == NULL) { if (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) || VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_UNSPEC))) { @@ -1184,6 +1197,14 @@ virNetworkIPDefParseXML(const char *networkName, address, networkName); goto cleanup; } + if (gateway && + (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) || + VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_UNSPEC)))) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("no family specified for non-IPv4 gateway address '%s' in network '%s'"), + address, networkName); + goto cleanup; + } } else if (STREQ(def->family, "ipv4")) { if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, @@ -1191,6 +1212,12 @@ virNetworkIPDefParseXML(const char *networkName, address, networkName); goto cleanup; } + if (gateway && (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET))) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("family 'ipv4' specified for non-IPv4 gateway address '%s' in network '%s'"), + address, networkName); + goto cleanup; + } } else if (STREQ(def->family, "ipv6")) { if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, @@ -1198,6 +1225,24 @@ virNetworkIPDefParseXML(const char *networkName, address, networkName); goto cleanup; } + if (def->prefix == 0 || def->prefix > 64) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("invalid prefix=%u specified for IPv6 address '%s' in network '%s'"), + def->prefix, address, networkName); + goto cleanup; + } + if (netmask) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("specifying netmask invalid for IPv6 address '%s' in network '%s'"), + address, networkName); + goto cleanup; + } + if (gateway && (!VIR_SOCKET_ADDR_IS_FAMILY(&def->gateway, AF_INET6))) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("family 'ipv6' specified for non-IPv6 gateway address '%s' in network '%s'"), + gateway, networkName); + goto cleanup; + } } else { virReportError(VIR_ERR_XML_ERROR, _("Unrecognized family '%s' in definition of network '%s'"), @@ -1241,16 +1286,55 @@ virNetworkIPDefParseXML(const char *networkName, } } + /* if static route gateway specified, make sure the address is a network address */ + if (gateway) { + if (def->prefix > 0) { + if (virSocketAddrMaskByPrefix(&def->address, def->prefix, &testAddr) < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("error converting address '%s' with prefix=%u to network-address in network '%s'"), + address, def->prefix, networkName); + goto cleanup; + } + } + if (netmask) { + if (virSocketAddrMask(&def->address, &def->netmask, &testAddr) < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("error converting address '%s' with netmask '%s' to network-address in network '%s'"), + address, netmask, networkName); + goto cleanup; + } + } + if (!virSocketAddrEqual(&def->address, &testAddr)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("address '%s' in network '%s' is not a network-address"), + address, networkName); + goto cleanup; + } + } + cur = node->children; while (cur != NULL) { if (cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "dhcp")) { + if (gateway) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("<dhcp> element unsupported when static route (via) previously defined in network '%s'"), + networkName); + goto cleanup; + } if (virNetworkDHCPDefParseXML(networkName, cur, def) < 0) goto cleanup; } else if (cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "tftp")) { char *root; + if (gateway) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("<tftp> element unsupported when static route (via) previously defined in network '%s'"), + networkName); + goto cleanup; + } + if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Unsupported <tftp> element in an IPv6 element in network '%s'"), @@ -1274,6 +1358,7 @@ cleanup: } VIR_FREE(address); VIR_FREE(netmask); + VIR_FREE(gateway); ctxt->node = save; return result; @@ -2108,6 +2193,7 @@ virNetworkIpDefFormat(virBufferPtr buf, const virNetworkIpDefPtr def) { int result = -1; + bool routeFlg = false; virBufferAddLit(buf, "<ip"); @@ -2131,15 +2217,29 @@ virNetworkIpDefFormat(virBufferPtr buf, if (def->prefix > 0) { virBufferAsprintf(buf," prefix='%u'", def->prefix); } + if (VIR_SOCKET_ADDR_VALID(&def->gateway)) { + char *addr = virSocketAddrFormat(&def->gateway); + if (!addr) + goto error; + routeFlg = true; + virBufferAsprintf(buf, " via='%s'", addr); + VIR_FREE(addr); + } virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 2); if (def->tftproot) { + if (routeFlg) /* this should not occur */ + goto error; /* but in case it does, better to error out */ virBufferEscapeString(buf, "<tftp root='%s' />\n", def->tftproot); } if ((def->nranges || def->nhosts)) { int ii; + + if (routeFlg) /* this should not occur */ + goto error; /* but in case it does, better to error out */ + virBufferAddLit(buf, "<dhcp>\n"); virBufferAdjustIndent(buf, 2); diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 6ce9a00..91aff5f 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -115,6 +115,8 @@ struct _virNetworkIpDef { char *family; /* ipv4 or ipv6 - default is ipv4 */ virSocketAddr address; /* Bridge IP address */ + virSocketAddr gateway; /* gateway IP address for ip-route */ + /* One or the other of the following two will be used for a given * IP address, but never both. The parser guarantees this. * Use virNetworkIpDefPrefix/virNetworkIpDefNetmask rather diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index ed46479..988b97a 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1470,6 +1470,7 @@ virNetDevReplaceMacAddress; virNetDevReplaceNetConfig; virNetDevRestoreMacAddress; virNetDevRestoreNetConfig; +virNetDevSetGateway; virNetDevSetIPv4Address; virNetDevSetMAC; virNetDevSetMTU; diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 31c8585..646a480 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -2360,6 +2360,7 @@ out: return ret; } +/* add an IP address to a bridge */ static int networkAddAddrToBridge(virNetworkObjPtr network, virNetworkIpDefPtr ipdef) @@ -2380,6 +2381,28 @@ networkAddAddrToBridge(virNetworkObjPtr network, return 0; } +/* add an IP route to a bridge */ +static int +networkAddRouteToBridge(virNetworkObjPtr network, + virNetworkIpDefPtr ipdef) +{ + int prefix = virNetworkIpDefPrefix(ipdef); + + if (prefix < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("bridge '%s' has an invalid netmask or IP address"), + network->def->bridge); + return -1; + } + + if (virNetDevSetGateway(network->def->bridge, + &ipdef->address, + prefix, + &ipdef->gateway) < 0) + return -1; + return 0; +} + static int networkStartNetworkVirtual(struct network_driver *driver, virNetworkObjPtr network) @@ -2446,9 +2469,12 @@ networkStartNetworkVirtual(struct network_driver *driver, if (networkAddIptablesRules(driver, network) < 0) goto err1; + /* first, do all of the addresses */ for (ii = 0; (ipdef = virNetworkDefGetIpByIndex(network->def, AF_UNSPEC, ii)); ii++) { + if (VIR_SOCKET_ADDR_VALID(&ipdef->gateway)) + continue; /* this is a static route specification */ if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET)) v4present = true; if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6)) @@ -2464,6 +2490,18 @@ networkStartNetworkVirtual(struct network_driver *driver, if (virNetDevSetOnline(network->def->bridge, 1) < 0) goto err2; + /* then do all of the static routes */ + for (ii = 0; + (ipdef = virNetworkDefGetIpByIndex(network->def, AF_UNSPEC, ii)); + ii++) { + + /* Add the IP route to the bridge */ + if (VIR_SOCKET_ADDR_VALID(&ipdef->gateway)) { + if (networkAddRouteToBridge(network, ipdef) < 0) + goto err2; + } + } + /* 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 296871c..c90b3d2 100644 --- a/src/util/virnetdev.c +++ b/src/util/virnetdev.c @@ -729,6 +729,7 @@ int virNetDevGetVLanID(const char *ifname ATTRIBUTE_UNUSED, * Add an IP address to an interface. This function *does not* remove * any previously added IP addresses - that must be done separately with * brDelInetAddress. + * TODO: what is "brDelInetAddress"? * * Returns 0 in case of success or -1 in case of error. */ @@ -769,6 +770,52 @@ cleanup: } /** + * virNetDevSetGateway: + * @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 virNetDevSetGateway(const char *ifname, + virSocketAddr *addr, + unsigned int prefix, + virSocketAddr *gateway) +{ + 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", NULL); + virCommandAddArgFormat(cmd, "%s", gatewaystr); + virCommandAddArgList(cmd, "dev", ifname, NULL); + virCommandAddArgList(cmd, "proto", "static", "metric", NULL); + virCommandAddArgFormat(cmd, "%u", 1); + + 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 * @addr: the IP address (IPv4 or IPv6) diff --git a/src/util/virnetdev.h b/src/util/virnetdev.h index 06d0650..8b94ea8 100644 --- a/src/util/virnetdev.h +++ b/src/util/virnetdev.h @@ -42,6 +42,11 @@ int virNetDevSetIPv4Address(const char *ifname, virSocketAddr *addr, unsigned int prefix) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; +int virNetDevSetGateway(const char *ifname, + virSocketAddr *addr, + unsigned int prefix, + virSocketAddr *gateway) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) 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 2693d87..dcad62d 100644 --- a/tests/networkxml2xmlin/dhcp6host-routed-network.xml +++ b/tests/networkxml2xmlin/dhcp6host-routed-network.xml @@ -19,4 +19,8 @@ <host id='0:1:0:1:18:aa:62:fe:0:16:3e:44:55:66' name='badbob' ip='2001:db8:ac10:fd01::1:24' /> </dhcp> </ip> + <ip address="192.168.222.0" netmask="255.255.255.0" via="192.168.122.10"> + </ip> + <ip family="ipv6" address="2001:db8:ac10:fc00::" prefix="64" via="2001:db8:ac10:fd01::1:24"> + </ip> </network> diff --git a/tests/networkxml2xmlout/dhcp6host-routed-network.xml b/tests/networkxml2xmlout/dhcp6host-routed-network.xml index 7305043..880c2dd 100644 --- a/tests/networkxml2xmlout/dhcp6host-routed-network.xml +++ b/tests/networkxml2xmlout/dhcp6host-routed-network.xml @@ -21,4 +21,8 @@ <host id='0:1:0:1:18:aa:62:fe:0:16:3e:44:55:66' name='badbob' ip='2001:db8:ac10:fd01::1:24' /> </dhcp> </ip> + <ip address='192.168.222.0' netmask='255.255.255.0' via='192.168.122.10'> + </ip> + <ip family='ipv6' address='2001:db8:ac10:fc00::' prefix='64' via='2001:db8:ac10:fd01::1:24'> + </ip> </network> -- 1.8.1.4

On 03/13/2013 04:04 PM, Gene Czarcinski wrote:
This patch adds support for adding a static route for a network. The "via" specifies the gateway's IP address. Both IPv4 and IPv6 static routes are supported although it is expected that this functionality will have more use with IPv6.
Extensive tests are done to validate that the input definitions are correct. For example, for a static route ip definition, the address must be for a network and not a host. I have been doing some thinking about the submitted patch and, while it works when everything is as it should be, I believe that there should be some additional checks to ensure that no unexpected code paths are taken (such as if someone would edit a network xml file with vi rather than using virsh net-nedit).
Gene

On 03/13/2013 09:30 PM, Gene Czarcinski wrote:
This patch adds support for adding a static route for a network. The "via" specifies the gateway's IP address. Both IPv4 and IPv6 static routes are supported although it is expected that this functionality will have more use with IPv6.
Extensive tests are done to validate that the input definitions are correct. For example, for a static route ip definition, the address must be for a network and not a host. I have been doing some thinking about the submitted patch and, while it works when everything is as it should be, I believe that there should be some additional checks to ensure that no unexpected code
On 03/13/2013 04:04 PM, Gene Czarcinski wrote: paths are taken (such as if someone would edit a network xml file with vi rather than using virsh net-nedit). OK, I have been doing more testing (and learning). If you do everything "right", then the static route stuff works as it should and that is good.
However, if you do not do everything "right", then it should not screw everything up. My first "don't do that" test was to specify a gateway address (IPv4 and IPv6 work the same) where the network was not defined on that device. When you do the virsh net-start, an error message appears in syslog and on the terminal which gives the entire command and says the network is (surprise) unreachable. Ok, that is fine. BUT, the bridge interface is left in a bad state ... it is still there but virsh net-destroy does not work but after correcting the definition, virsh net-start does not work either because the device is still in use. A reboot was necessary to clear the condition. My solution: when the network static route is added, ignore any errors. virsh & libvirt can now destroy the network and, after correcting the gateway address, virsh net-start works. While the error message is still sent to syslog, it no longer appears on the terminal window. Can someone point to me how I can get the error message displayed but without having any other effect. This also brings up the question as to whether the error handling (err3) in NetworkStartNetworkVirtual() is correct. I will also be adding a bunch of code to network_conf.c to attempt verifying that, if a static route is specified, an address has also been specified for the bridge and that the gateway address is on the network specified. Lots of code but also meaningful error messages can then be generated. Gene
participants (1)
-
Gene Czarcinski