[PATCH 1/7] conf: add XML config for autoaddr networks

Laine Stump posted 7 patches 3 months, 2 weeks ago
[PATCH 1/7] conf: add XML config for autoaddr networks
Posted by Laine Stump 3 months, 2 weeks ago
If the <ip> element of a network has the attribute autoaddr='yes', any
specified IP address, netmask, prefix, or dhcp range will be used as a
hint, but if the current network is already present in the host's
routing table (i.e. it is in use) then libvirt will instead
automatically find an unused subnet "somewhere", and use that instead.

Since this patch is just the XML config bits, it has none of
the details about how an unused subnet is found. That is coming later.

autoaddr='yes' is currently only supported for IPv4. IPv6 autoaddr
should work differently (rather than selecting from a manually
configured range of networks, I *think* it should semi-randomly select
a network ala RFC 4193, and anyway conflicting IPv6 networks hasn't
been an issue up to now).

Signed-off-by: Laine Stump <laine@redhat.com>
---
 docs/formatnetwork.rst                        | 42 ++++++++++-
 src/conf/network_conf.c                       | 75 ++++++++++++++-----
 src/conf/network_conf.h                       |  7 ++
 src/conf/schemas/network.rng                  |  5 ++
 .../networkxml2xmlin/nat-network-autoaddr.xml | 11 +++
 .../nat-network-autoaddr.xml                  | 11 +++
 tests/networkxml2xmltest.c                    |  1 +
 7 files changed, 132 insertions(+), 20 deletions(-)
 create mode 100644 tests/networkxml2xmlin/nat-network-autoaddr.xml
 create mode 100644 tests/networkxml2xmlout/nat-network-autoaddr.xml

diff --git a/docs/formatnetwork.rst b/docs/formatnetwork.rst
index 9b4ecbf31d..9c5e974002 100644
--- a/docs/formatnetwork.rst
+++ b/docs/formatnetwork.rst
@@ -805,8 +805,27 @@ of 'route' or 'nat'.
    divisible by 4 for IPv6) libvirt may be unable to compute the PTR domain
    automatically. The ``ip`` element is supported :since:`since 0.3.0`. IPv6,
    multiple addresses on a single network, ``family``, and ``prefix`` are
-   supported :since:`since 0.8.7`. The ``ip`` element may contain the following
-   elements:
+   supported :since:`since 0.8.7`.
+
+   Instead of (or in addition to) an IPv4 address and prefix (or
+   netmask), the attribute ``autoaddr`` can be set to ``yes``, and in
+   this case libvirt will search through a range of IPv4 subnets
+   (configured with the autoaddr_* settings in
+   /etc/libvirt/network.conf) to find an unused subnet :since: `since
+   10.7.0`. If an address has been specified in addition to
+   ``autoaddr='yes'`` then that address will be tried first, before
+   looking to the range given in network.conf. In any case, once an
+   unused subnet has been found, any dhcp range, static IP addresses,
+   or tftp boot servers will have the network part of their address
+   changed to the new subnet (if no ``address`` was given in the
+   config, the address will be set to the ".1" address on the newly
+   chosen network, and if there is an empty ``<dhcp/>`` subelement, it
+   will be given a range equal to the entire subnet exceot the ".1"
+   address). Once this is done, the updated network config is saved by
+   libvirt so that libvirt can attempt to use the same subnet the next
+   time the network is started.
+
+   The ``ip`` element may contain the following sub-elements:
 
    ``tftp``
       The optional ``tftp`` element and its mandatory ``root`` attribute enable
@@ -859,6 +878,11 @@ of 'route' or 'nat'.
       except when setting an infinite lease time (``expiry='0'``).
       :since:`Since 6.3.0`
 
+      If the ``ip`` element has ``autoaddr='yes'``, then the ``dhcp``
+      element can be empty, and in this case libvirt automatically
+      adds a ``range`` equivalent to the entire subnet (minus the .1
+      address for the bridge device itself). :since: `Since 10.7.0`
+
 Network namespaces
 ~~~~~~~~~~~~~~~~~~
 
@@ -928,6 +952,20 @@ definition.
      </ip>
    </network>
 
+And here is a NAT network that has its subnet (and thus its ip address
+and dhcp range) chosen automatically by libvirt from the range of
+"autoaddr" subnets configured in network.conf:
+
+::
+
+   <network>
+     <name>automagic</name>
+     <forward mode="nat"/>
+     <ip autoaddr="yes">
+       <dhcp/>
+     </ip>
+   </network>
+
 IPv6 NAT based network
 ~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c
index 3af4e1d036..2a6b95e7ac 100644
--- a/src/conf/network_conf.c
+++ b/src/conf/network_conf.c
@@ -599,7 +599,8 @@ virNetworkDHCPHostDefParseXML(const char *networkName,
 static int
 virNetworkDHCPDefParseXML(const char *networkName,
                           xmlNodePtr node,
-                          virNetworkIPDef *def)
+                          virNetworkIPDef *def,
+                          bool isIPv4)
 {
 
     g_autoptr(GPtrArray) rangeNodes = virXMLNodeGetSubelementList(node, "range");
@@ -607,6 +608,7 @@ virNetworkDHCPDefParseXML(const char *networkName,
     xmlNodePtr bootp = virXMLNodeGetSubelement(node, "bootp");
     size_t i;
 
+    def->dhcp = true;
     for (i = 0; i < rangeNodes->len; i++) {
         virNetworkDHCPRangeDef range = { 0 };
 
@@ -629,8 +631,7 @@ virNetworkDHCPDefParseXML(const char *networkName,
         VIR_APPEND_ELEMENT(def->hosts, def->nhosts, host);
     }
 
-    if (bootp &&
-        VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) {
+    if (bootp && isIPv4) {
         g_autofree char *server = virXMLPropString(bootp, "server");
 
         if (!(def->bootfile = virXMLPropStringRequired(bootp, "file")))
@@ -991,24 +992,28 @@ virNetworkIPDefParseXML(const char *networkName,
     g_autofree char *address = NULL;
     g_autofree char *netmask = NULL;
     int ret = -1;
+    bool isIPv4 = true;
 
     ctxt->node = node;
 
     /* grab raw data from XML */
     def->family = virXPathString("string(./@family)", ctxt);
+    if (STREQ_NULLABLE(def->family, "ipv6"))
+        isIPv4 = false;
 
-    address = virXPathString("string(./@address)", ctxt);
-    if (!address) {
-        virReportError(VIR_ERR_XML_ERROR,
-                       _("Missing required address attribute in network '%1$s'"),
-                       networkName);
-        goto cleanup;
-    }
-    if (virSocketAddrParse(&def->address, address, AF_UNSPEC) < 0) {
-        virReportError(VIR_ERR_XML_ERROR,
-                       _("Invalid address '%1$s' in network '%2$s'"),
-                       address, networkName);
+    if (virXMLPropTristateBool(node, "autoaddr", VIR_XML_PROP_NONE, &def->autoaddr) < 0)
         goto cleanup;
+
+    address = virXPathString("string(./@address)", ctxt);
+
+    if (address) {
+        if (virSocketAddrParse(&def->address, address, AF_UNSPEC) < 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Invalid address '%1$s' in network '%2$s'"),
+                           address, networkName);
+            goto cleanup;
+        }
+        isIPv4 = VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET);
     }
 
     netmask = virXPathString("string(./@netmask)", ctxt);
@@ -1028,6 +1033,27 @@ virNetworkIPDefParseXML(const char *networkName,
                                &def->localPTR) < 0)
         goto cleanup;
 
+    switch (def->autoaddr) {
+    case VIR_TRISTATE_BOOL_NO:
+    case VIR_TRISTATE_BOOL_ABSENT:
+    case VIR_TRISTATE_BOOL_LAST:
+        if (!address) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Missing required address attribute in network '%1$s'"),
+                           networkName);
+            goto cleanup;
+        }
+        break;
+    case VIR_TRISTATE_BOOL_YES:
+        if (!isIPv4) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("autoaddr='yes' is only supported for IPv4 in network '%1$s'"),
+                           networkName);
+            goto cleanup;
+        }
+        break;
+    }
+
     /* validate address, etc. for each family */
     if ((def->family == NULL) || (STREQ(def->family, "ipv4"))) {
         if (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) ||
@@ -1057,7 +1083,8 @@ virNetworkIPDefParseXML(const char *networkName,
             goto cleanup;
         }
     } else if (STREQ(def->family, "ipv6")) {
-        if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6)) {
+        if (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6) ||
+              VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_UNSPEC))) {
             virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                            _("Family 'ipv6' specified for non-IPv6 address '%1$s' in network '%2$s'"),
                            address, networkName);
@@ -1083,11 +1110,11 @@ virNetworkIPDefParseXML(const char *networkName,
     }
 
     if ((dhcp = virXPathNode("./dhcp[1]", ctxt)) &&
-        virNetworkDHCPDefParseXML(networkName, dhcp, def) < 0)
+        virNetworkDHCPDefParseXML(networkName, dhcp, def, isIPv4) < 0)
         goto cleanup;
 
     if (virXPathNode("./tftp[1]", ctxt)) {
-        if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) {
+        if (!isIPv4) {
             virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                            _("Unsupported <tftp> element in an IPv6 element in network '%1$s'"),
                            networkName);
@@ -2045,6 +2072,11 @@ virNetworkIPDefFormat(virBuffer *buf,
 
     if (def->family)
         virBufferAsprintf(buf, " family='%s'", def->family);
+
+    if (def->autoaddr != VIR_TRISTATE_BOOL_ABSENT)
+        virBufferAsprintf(buf, " autoaddr='%s'",
+                          virTristateBoolTypeToString(def->autoaddr));
+
     if (VIR_SOCKET_ADDR_VALID(&def->address)) {
         g_autofree char *addr = virSocketAddrFormat(&def->address);
         if (!addr)
@@ -2072,7 +2104,14 @@ virNetworkIPDefFormat(virBuffer *buf,
         virBufferEscapeString(buf, "<tftp root='%s'/>\n",
                               def->tftproot);
     }
-    if ((def->nranges || def->nhosts)) {
+
+    if (!(def->nranges || def->nhosts)) {
+        /* in case an empty <dhcp/> (with no subelements)
+         * was specified
+         */
+        if (def->dhcp)
+            virBufferAddLit(buf, "<dhcp/>\n");
+    } else {
         size_t i;
         virBufferAddLit(buf, "<dhcp>\n");
         virBufferAdjustIndent(buf, 2);
diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h
index c2a4198abc..d44984e71a 100644
--- a/src/conf/network_conf.h
+++ b/src/conf/network_conf.h
@@ -158,6 +158,12 @@ struct _virNetworkDNSDef {
 typedef struct _virNetworkIPDef virNetworkIPDef;
 struct _virNetworkIPDef {
     char *family;               /* ipv4 or ipv6 - default is ipv4 */
+
+    /* automatically determine IP address when the network is started.
+     * default is 'no'
+     */
+    virTristateBool autoaddr;
+
     virSocketAddr address;      /* Bridge IP address */
 
     /* One or the other of the following two will be used for a given
@@ -171,6 +177,7 @@ struct _virNetworkIPDef {
 
     virTristateBool localPTR;
 
+    bool dhcp;                  /* true if there is a <dhcp> element */
     size_t nranges;             /* Zero or more dhcp ranges */
     virNetworkDHCPRangeDef *ranges;
 
diff --git a/src/conf/schemas/network.rng b/src/conf/schemas/network.rng
index b7c8551fad..64d4818e96 100644
--- a/src/conf/schemas/network.rng
+++ b/src/conf/schemas/network.rng
@@ -354,6 +354,11 @@
             <optional>
               <attribute name="address"><ref name="ipAddr"/></attribute>
             </optional>
+            <optional>
+              <attribute name="autoaddr">
+                <ref name="virYesNo"/>
+              </attribute>
+            </optional>
             <optional>
               <choice>
                 <attribute name="netmask"><ref name="ipv4Addr"/></attribute>
diff --git a/tests/networkxml2xmlin/nat-network-autoaddr.xml b/tests/networkxml2xmlin/nat-network-autoaddr.xml
new file mode 100644
index 0000000000..71167285e6
--- /dev/null
+++ b/tests/networkxml2xmlin/nat-network-autoaddr.xml
@@ -0,0 +1,11 @@
+<network>
+  <name>default</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <bridge name="virbr0"/>
+  <forward mode="nat"/>
+  <ip family='ipv4' autoaddr='yes'>
+    <dhcp/>
+  </ip>
+  <ip family="ipv4" autoaddr='yes' address="10.24.10.1">
+  </ip>
+</network>
diff --git a/tests/networkxml2xmlout/nat-network-autoaddr.xml b/tests/networkxml2xmlout/nat-network-autoaddr.xml
new file mode 100644
index 0000000000..629710175c
--- /dev/null
+++ b/tests/networkxml2xmlout/nat-network-autoaddr.xml
@@ -0,0 +1,11 @@
+<network>
+  <name>default</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <forward mode='nat'/>
+  <bridge name='virbr0' stp='on' delay='0'/>
+  <ip family='ipv4' autoaddr='yes'>
+    <dhcp/>
+  </ip>
+  <ip family='ipv4' autoaddr='yes' address='10.24.10.1'>
+  </ip>
+</network>
diff --git a/tests/networkxml2xmltest.c b/tests/networkxml2xmltest.c
index 0783d84915..de8abefe42 100644
--- a/tests/networkxml2xmltest.c
+++ b/tests/networkxml2xmltest.c
@@ -137,6 +137,7 @@ mymain(void)
     DO_TEST("nat-network-forward-nat-address");
     DO_TEST("nat-network-forward-nat-no-address");
     DO_TEST("nat-network-mtu");
+    DO_TEST("nat-network-autoaddr");
     DO_TEST("8021Qbh-net");
     DO_TEST("direct-net");
     DO_TEST("host-bridge-net");
-- 
2.45.2