From c9a641f1e51def862e7d160b23440252f220149b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= <cbosdonnat@suse.com>
Date: Thu, 24 Jul 2014 12:16:28 +0200
Subject: [PATCH 12/17] Domain network devices can now have a <route> element

Network interfaces devices and host devices with net capabilities can
now have IPv4 and/or an IPv6 routes configured.
---
 docs/formatdomain.html.in            |  19 ++++-
 docs/schemas/domaincommon.rng        |  31 ++++++++
 src/conf/domain_conf.c               | 135 ++++++++++++++++++++++++++++++++++-
 src/conf/domain_conf.h               |  12 ++++
 src/util/virnetdev.c                 |  31 +++++++-
 src/util/virnetdev.h                 |   2 +-
 src/util/virsocketaddr.h             |   2 +
 tests/lxcxml2xmldata/lxc-hostdev.xml |   2 +
 tests/lxcxml2xmldata/lxc-idmap.xml   |   2 +
 9 files changed, 230 insertions(+), 6 deletions(-)

Index: libvirt-1.2.5/docs/formatdomain.html.in
===================================================================
--- libvirt-1.2.5.orig/docs/formatdomain.html.in
+++ libvirt-1.2.5/docs/formatdomain.html.in
@@ -3935,14 +3935,18 @@ qemu-kvm -net nic,model=? /dev/null
     &lt;interface type='network'&gt;
       &lt;source network='default'/&gt;
       &lt;target dev='vnet0'/&gt;
-      <b>&lt;ip family='ipv4' address='192.168.122.5' prefix='24'/&gt;</b>
+      <b>&lt;ip address='192.168.122.5' prefix='24'/&gt;</b>
+      <b>&lt;route family='ipv4' address='192.168.122.0' prefix='24' via='192.168.122.1'/&gt;</b>
+      <b>&lt;route family='ipv4' via='192.168.122.1'/&gt;</b>
     &lt;/interface&gt;
     ...
     &lt;hostdev mode='capabilities' type='net'&gt;
       &lt;source&gt;
         &lt;interface&gt;eth0&lt;/interface&gt;
       &lt;/source&gt;
-      <b>&lt;ip family='ipv4' address='192.168.122.6' prefix='24'/&gt;</b>
+      <b>&lt;ip address='192.168.122.6' prefix='24'/&gt;</b>
+      <b>&lt;route family='ipv4' address='192.168.122.0' prefix='24' via='192.168.122.1'/&gt;</b>
+      <b>&lt;route family='ipv4' via='192.168.122.1'/&gt;</b>
     &lt;/hostdev&gt;
 
   &lt;/devices&gt;
@@ -3959,6 +3963,17 @@ qemu-kvm -net nic,model=? /dev/null
     is not mandatory since some hypervisors do not handle it.
     </p>
 
+    <p>
+    <span class="since">Since 1.2.12</span> route elements can also be added
+    to define the network routes to use for the network device. This element
+    has a <code>family</code> attribute set either to <code>ipv4</code> or
+    <code>ipv6</code>, a mandatory <code>via</code> attribute defining the
+    IP address to route throught and optional <code>address</code> and <code>prefix</code>
+    attributes defining the target network range. If those aren't given, then
+    a default route will be set.
+    This is only used by the LXC driver.
+    </p>
+
     <h4><a name="elementsInput">Input devices</a></h4>
 
     <p>
Index: libvirt-1.2.5/docs/schemas/domaincommon.rng
===================================================================
--- libvirt-1.2.5.orig/docs/schemas/domaincommon.rng
+++ libvirt-1.2.5/docs/schemas/domaincommon.rng
@@ -2207,6 +2207,11 @@
           <empty/>
         </element>
       </zeroOrMore>
+      <zeroOrMore>
+        <element name="route">
+          <ref name="route"/>
+        </element>
+      </zeroOrMore>
       <optional>
         <element name="script">
           <attribute name="path">
@@ -3383,6 +3388,27 @@
     </element>
   </define>
 
+  <define name="route">
+    <interleave>
+      <attribute name="family">
+        <ref name="addr-family"/>
+      </attribute>
+      <attribute name="via">
+        <ref name="ipAddr"/>
+      </attribute>
+      <optional>
+        <attribute name="address">
+          <ref name="ipAddr"/>
+        </attribute>
+      </optional>
+      <optional>
+        <attribute name="prefix">
+          <ref name="ipPrefix"/>
+        </attribute>
+      </optional>
+    </interleave>
+  </define>
+
   <define name="hostdev">
     <element name="hostdev">
       <interleave>
@@ -3579,6 +3605,11 @@
           <empty/>
         </element>
       </zeroOrMore>
+      <zeroOrMore>
+        <element name="route">
+          <ref name="route"/>
+        </element>
+      </zeroOrMore>
     </interleave>
   </define>
 
Index: libvirt-1.2.5/src/conf/domain_conf.c
===================================================================
--- libvirt-1.2.5.orig/src/conf/domain_conf.c
+++ libvirt-1.2.5/src/conf/domain_conf.c
@@ -1422,7 +1422,11 @@ void virDomainNetDefFree(virDomainNetDef
         VIR_FREE(def->ips[i]);
     VIR_FREE(def->ips);
 
-    virDomainDeviceInfoClear(&def->info);
+    for (i = 0; i < def->nroutes; i++)
+        VIR_FREE(def->routes[i]);
+    VIR_FREE(def->routes);
+
+        virDomainDeviceInfoClear(&def->info);
 
     VIR_FREE(def->filter);
     virNWFilterHashTableFree(def->filterparams);
@@ -1771,6 +1775,9 @@ void virDomainHostdevDefClear(virDomainH
             for (i = 0; i < def->source.caps.u.net.nips; i++)
                 VIR_FREE(def->source.caps.u.net.ips[i]);
             VIR_FREE(def->source.caps.u.net.ips);
+            for (i = 0; i < def->source.caps.u.net.nroutes; i++)
+                VIR_FREE(def->source.caps.u.net.routes[i]);
+            VIR_FREE(def->source.caps.u.net.routes);
             break;
         }
         break;
@@ -4458,6 +4465,64 @@ virDomainNetIpParseXML(xmlNodePtr node)
     return NULL;
 }
 
+static virDomainNetRouteDefPtr
+virDomainNetRouteParse(xmlNodePtr node)
+{
+    virDomainNetRouteDefPtr route = NULL;
+    char *familyStr = NULL;
+    int family = AF_UNSPEC;
+    char *via = NULL;
+    char *to = NULL;
+    char *prefixStr = NULL;
+
+    to = virXMLPropString(node, "address");
+    if (!(via = virXMLPropString(node, "via"))) {
+        virReportError(VIR_ERR_INVALID_ARG, "%s",
+                       _("Missing route address"));
+        goto error;
+    }
+
+    familyStr = virXMLPropString(node, "family");
+    if (familyStr && STREQ(familyStr, "ipv4"))
+        family = AF_INET;
+    else if (familyStr && STREQ(familyStr, "ipv6"))
+        family = AF_INET6;
+    else
+        family = virSocketAddrNumericFamily(via);
+
+    if (VIR_ALLOC(route) < 0)
+        goto error;
+
+    if (virSocketAddrParse(&route->via, via, family) < 0) {
+        virReportError(VIR_ERR_INVALID_ARG,
+                       _("Failed to parse IP address: '%s'"),
+                       via);
+        goto error;
+    }
+
+    if (to && virSocketAddrParse(&route->to, to, family) < 0) {
+        virReportError(VIR_ERR_INVALID_ARG,
+                       _("Failed to parse IP address: '%s'"),
+                       to);
+        goto error;
+    }
+
+    if (!(prefixStr = virXMLPropString(node, "prefix")) ||
+        (virStrToLong_ui(prefixStr, NULL, 10, &route->prefix) < 0)) {
+    }
+
+    return route;
+
+ error:
+    VIR_FREE(familyStr);
+    VIR_FREE(via);
+    VIR_FREE(to);
+    VIR_FREE(prefixStr);
+    VIR_FREE(route);
+
+    return NULL;
+}
+
 static int
 virDomainHostdevDefParseXMLCaps(xmlNodePtr node ATTRIBUTE_UNUSED,
                                 xmlXPathContextPtr ctxt,
@@ -4467,6 +4532,8 @@ virDomainHostdevDefParseXMLCaps(xmlNodeP
     xmlNodePtr sourcenode;
     xmlNodePtr *ipnodes = NULL;
     int nipnodes;
+    xmlNodePtr *routenodes = NULL;
+    int nroutenodes;
     int ret = -1;
 
     /* @type is passed in from the caller rather than read from the
@@ -4541,6 +4608,26 @@ virDomainHostdevDefParseXMLCaps(xmlNodeP
                 }
             }
         }
+
+        /* Look for possible gateways */
+        if ((nroutenodes = virXPathNodeSet("./route", ctxt, &routenodes)) < 0)
+            goto error;
+
+        if (nroutenodes) {
+            size_t i;
+            for (i = 0; i < nroutenodes; i++) {
+                virDomainNetRouteDefPtr route = virDomainNetRouteParse(routenodes[i]);
+
+                if (!route)
+                    goto error;
+
+                if (VIR_APPEND_ELEMENT(def->source.caps.u.net.routes,
+                                       def->source.caps.u.net.nroutes, route) < 0) {
+                    VIR_FREE(route);
+                    goto error;
+                }
+            }
+        }
         break;
     default:
         virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
@@ -4551,6 +4638,7 @@ virDomainHostdevDefParseXMLCaps(xmlNodeP
     ret = 0;
  error:
     VIR_FREE(ipnodes);
+    VIR_FREE(routenodes);
     return ret;
 }
 
@@ -6756,6 +6844,8 @@ virDomainNetDefParseXML(virDomainXMLOpti
     size_t i;
     size_t nips = 0;
     virDomainNetIpDefPtr *ips = NULL;
+    size_t nroutes = 0;
+    virDomainNetRouteDefPtr *routes = NULL;
 
     if (VIR_ALLOC(def) < 0)
         return NULL;
@@ -6836,6 +6926,13 @@ virDomainNetDefParseXML(virDomainXMLOpti
 
                 if (VIR_APPEND_ELEMENT(ips, nips, ip) < 0)
                     goto error;
+            } else if (xmlStrEqual(cur->name, BAD_CAST "route")) {
+                virDomainNetRouteDefPtr route = NULL;
+                if (!(route = virDomainNetRouteParse(cur)))
+                    goto error;
+
+                if (VIR_APPEND_ELEMENT(routes, nroutes, route) < 0)
+                    goto error;
             } else if (!ifname &&
                        xmlStrEqual(cur->name, BAD_CAST "target")) {
                 ifname = virXMLPropString(cur, "dev");
@@ -7078,6 +7175,8 @@ virDomainNetDefParseXML(virDomainXMLOpti
         if (VIR_APPEND_ELEMENT(def->ips, def->nips, ips[i]) < 0)
             goto error;
     }
+    def->nroutes = nroutes;
+    def->routes = routes;
 
     if (script != NULL) {
         def->script = script;
@@ -15682,6 +15781,37 @@ virDomainNetIpsFormat(virBufferPtr buf,
     }
 }
 
+static void
+virDomainNetRoutesFormat(virBufferPtr buf,
+                         virDomainNetRouteDefPtr *routes,
+                         size_t nroutes)
+{
+    size_t i;
+
+    for (i = 0; i < nroutes; i++) {
+        virDomainNetRouteDefPtr route = routes[i];
+        const char *familyStr = NULL;
+        char *via = virSocketAddrFormat(&route->via);
+        char *to = NULL;
+
+        if (VIR_SOCKET_ADDR_IS_FAMILY(&route->via, AF_INET6))
+            familyStr = "ipv6";
+        else if (VIR_SOCKET_ADDR_IS_FAMILY(&route->via, AF_INET))
+            familyStr = "ipv4";
+        virBufferAsprintf(buf, "<route family='%s' via='%s'", familyStr, via);
+
+        if (VIR_SOCKET_ADDR_VALID(&route->to)) {
+            to = virSocketAddrFormat(&route->to);
+            virBufferAsprintf(buf, " address='%s'", to);
+        }
+
+        if (route->prefix > 0)
+            virBufferAsprintf(buf, " prefix='%d'", route->prefix);
+
+        virBufferAddLit(buf, "/>\n");
+    }
+}
+
 static int
 virDomainHostdevDefFormatSubsys(virBufferPtr buf,
                                 virDomainHostdevDefPtr def,
@@ -15813,6 +15943,8 @@ virDomainHostdevDefFormatCaps(virBufferP
     if (def->source.caps.type == VIR_DOMAIN_HOSTDEV_CAPS_TYPE_NET) {
         virDomainNetIpsFormat(buf, def->source.caps.u.net.ips,
                               def->source.caps.u.net.nips);
+        virDomainNetRoutesFormat(buf, def->source.caps.u.net.routes,
+                                 def->source.caps.u.net.nroutes);
     }
 
     return 0;
@@ -16064,6 +16196,7 @@ virDomainNetDefFormat(virBufferPtr buf,
     }
 
     virDomainNetIpsFormat(buf, def->ips, def->nips);
+    virDomainNetRoutesFormat(buf, def->routes, def->nroutes);
 
     virBufferEscapeString(buf, "<script path='%s'/>\n",
                           def->script);
Index: libvirt-1.2.5/src/conf/domain_conf.h
===================================================================
--- libvirt-1.2.5.orig/src/conf/domain_conf.h
+++ libvirt-1.2.5/src/conf/domain_conf.h
@@ -434,6 +434,14 @@ struct _virDomainNetIpDef {
     unsigned int prefix; /* number of 1 bits in the net mask */
 };
 
+typedef struct _virDomainNetRouteDef virDomainNetRouteDef;
+typedef virDomainNetRouteDef *virDomainNetRouteDefPtr;
+struct _virDomainNetRouteDef {
+    virSocketAddr via;
+    virSocketAddr to;
+    unsigned int prefix;
+};
+
 typedef struct _virDomainHostdevCaps virDomainHostdevCaps;
 typedef virDomainHostdevCaps *virDomainHostdevCapsPtr;
 struct _virDomainHostdevCaps {
@@ -449,6 +457,8 @@ struct _virDomainHostdevCaps {
             char *iface;
             size_t nips;
             virDomainNetIpDefPtr *ips;
+            size_t nroutes;
+            virDomainNetRouteDefPtr *routes;
         } net;
     } u;
 };
@@ -934,6 +944,8 @@ struct _virDomainNetDef {
     int linkstate;
     size_t nips;
     virDomainNetIpDefPtr *ips;
+    size_t nroutes;
+    virDomainNetRouteDefPtr *routes;
 };
 
 /* Used for prefix of ifname of any network name generated dynamically
Index: libvirt-1.2.5/src/util/virnetdev.c
===================================================================
--- libvirt-1.2.5.orig/src/util/virnetdev.c
+++ libvirt-1.2.5/src/util/virnetdev.c
@@ -955,8 +955,33 @@ virNetDevAddRoute(const char *ifname,
     void *addrData = NULL;
     size_t addrDataLen;
     int errCode;
+    virSocketAddr defaultAddr;
+    virSocketAddrPtr actualAddr;
+    char *toStr = NULL;
+    char *viaStr = NULL;
+
+    actualAddr = addr;
+
+    /* If we have no valid network address, then use the default one */
+    if (!addr || !VIR_SOCKET_ADDR_VALID(addr)) {
+        VIR_DEBUG("computing default address");
+        int family = VIR_SOCKET_ADDR_FAMILY(gateway);
+        if (family == AF_INET) {
+            if (virSocketAddrParseIPv4(&defaultAddr, VIR_SOCKET_ADDR_IPV4_ALL) < 0)
+                goto cleanup;
+        } else {
+            if (virSocketAddrParseIPv6(&defaultAddr, VIR_SOCKET_ADDR_IPV6_ALL) < 0)
+                goto cleanup;
+        }
 
-    if (virNetDevGetIPAddressBinary(addr, &addrData, &addrDataLen) < 0 ||
+        actualAddr = &defaultAddr;
+    }
+
+    toStr = virSocketAddrFormat(actualAddr);
+    viaStr = virSocketAddrFormat(gateway);
+    VIR_DEBUG("Adding route %s/%d via %s", toStr, prefix, viaStr);
+
+    if (virNetDevGetIPAddressBinary(actualAddr, &addrData, &addrDataLen) < 0 ||
         virNetDevGetIPAddressBinary(gateway, &gatewayData, &addrDataLen) < 0)
         goto cleanup;
 
@@ -973,7 +998,7 @@ virNetDevAddRoute(const char *ifname,
 
     memset(&rtmsg, 0, sizeof(rtmsg));
 
-    rtmsg.rtm_family = VIR_SOCKET_ADDR_FAMILY(addr);
+    rtmsg.rtm_family = VIR_SOCKET_ADDR_FAMILY(gateway);
     rtmsg.rtm_table = RT_TABLE_MAIN;
     rtmsg.rtm_scope = RT_SCOPE_UNIVERSE;
     rtmsg.rtm_protocol = RTPROT_BOOT;
@@ -1006,6 +1031,8 @@ virNetDevAddRoute(const char *ifname,
 
     ret = 0;
  cleanup:
+    VIR_FREE(toStr);
+    VIR_FREE(viaStr);
     nlmsg_free(nlmsg);
     return ret;
 
Index: libvirt-1.2.5/src/util/virnetdev.h
===================================================================
--- libvirt-1.2.5.orig/src/util/virnetdev.h
+++ libvirt-1.2.5/src/util/virnetdev.h
@@ -59,7 +59,7 @@ int virNetDevAddRoute(const char *ifname
                       unsigned int prefix,
                       virSocketAddrPtr gateway,
                       unsigned int metric)
-    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(4)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(4)
     ATTRIBUTE_RETURN_CHECK;
 int virNetDevClearIPAddress(const char *ifname,
                             virSocketAddr *addr,
Index: libvirt-1.2.5/src/util/virsocketaddr.h
===================================================================
--- libvirt-1.2.5.orig/src/util/virsocketaddr.h
+++ libvirt-1.2.5/src/util/virsocketaddr.h
@@ -55,6 +55,8 @@ typedef struct {
     ((s)->data.sa.sa_family)
 
 # define VIR_SOCKET_ADDR_DEFAULT_PREFIX 24
+# define VIR_SOCKET_ADDR_IPV4_ALL "0.0.0.0"
+# define VIR_SOCKET_ADDR_IPV6_ALL "::"
 
 typedef virSocketAddr *virSocketAddrPtr;
 
Index: libvirt-1.2.5/tests/lxcxml2xmldata/lxc-hostdev.xml
===================================================================
--- libvirt-1.2.5.orig/tests/lxcxml2xmldata/lxc-hostdev.xml
+++ libvirt-1.2.5/tests/lxcxml2xmldata/lxc-hostdev.xml
@@ -37,6 +37,8 @@
       </source>
       <ip address='192.168.122.2' family='ipv4'/>
       <ip address='2003:db8:1:0:214:1234:fe0b:3596' family='ipv6' prefix='24'/>
+      <route family='ipv4' via='192.168.122.1'/>
+      <route family='ipv6' via='2003:db8:1:0:214:1234:fe0b:3595'/>
     </hostdev>
   </devices>
 </domain>
Index: libvirt-1.2.5/tests/lxcxml2xmldata/lxc-idmap.xml
===================================================================
--- libvirt-1.2.5.orig/tests/lxcxml2xmldata/lxc-idmap.xml
+++ libvirt-1.2.5/tests/lxcxml2xmldata/lxc-idmap.xml
@@ -30,6 +30,8 @@
       <source bridge='bri0'/>
       <ip address='192.168.122.12' family='ipv4' prefix='24'/>
       <ip address='192.168.122.13' family='ipv4' prefix='24'/>
+      <route family='ipv4' via='192.168.122.1'/>
+      <route family='ipv4' via='192.168.124.1' address='192.168.124.0' prefix='24'/>
       <target dev='veth0'/>
       <guest dev='eth2'/>
     </interface>
