[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[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 @@
           &lt;/dhcp&gt;
         &lt;/ip&gt;
         &lt;ip family="ipv6" address="2001:db8:ca2:2::1" prefix="64" /&gt;
+        &lt;ip family="ipv6" address="2001:db9:ca1:1::" prefix="64" via="2001:db8:ca2:2::2" /&gt;
       &lt;/network&gt;</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 @@
         &lt;/ip&gt;
       &lt;/network&gt;</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>
+      &lt;network&gt;
+        &lt;name&gt;net7&lt;/name&gt;
+        &lt;bridge name="virbr7" /&gt;
+        &lt;forward mode="route"/&gt;
+        &lt;ip family="ipv6" address="2001:db8:ca2:7::1" prefix="64" &gt;
+          &lt;dhcp&gt;
+            &lt;range start="2001:db8:ca2:7::100" end="2001:db8:ca2::1ff" /&gt;
+            &lt;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" /&gt;
+          &lt;/dhcp&gt;
+        &lt;/ip&gt;
+        &lt;ip family="ipv6" address="2001:db8:ca2:8::" prefix="64" via="2001:db8:ca2::4" &gt;
+        &lt;/ip&gt;
+      &lt;/network&gt;</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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]