From d48f957ab78310d864b356b4a25b9a29722ca736 Mon Sep 17 00:00:00 2001
From: Alberto Ruiz <aruiz@gnome.org>
Date: Thu, 6 Oct 2016 21:29:40 +0100
Subject: [PATCH] leasetime support per <dhcp> and <host>

Support for custom dhcp wide and per host leasetime.

It is specified as a child tag for <dhcp>:
<dhcp>
  <leasetime>24h</leasetime>
  ...
</dhcp>

And as an attribute for <host>:
<dhcp>
  <host leasetime="7d" .../>
</dhcp>

These are the different notations:

-1   (infinite/unlimited lease)
120  (seconds are the default unit, 120 seconds is the minimum, if less is specified it will use 120)
300s (seconds)
5m   (minutes)
24h  (hours)
7d   (days)
---
 docs/schemas/basictypes.rng                        |   5 +
 docs/schemas/network.rng                           |   8 ++
 src/conf/network_conf.c                            |  86 +++++++++++++-
 src/conf/network_conf.h                            |   4 +-
 src/libvirt_private.syms                           |   1 +
 src/network/bridge_driver.c                        | 132 +++++++++++++++++----
 src/network/bridge_driver.h                        |   1 +
 src/util/virdnsmasq.c                              | 106 +++++++++++------
 src/util/virdnsmasq.h                              |   2 +
 .../dhcp6-nat-network.hostsfile                    |   7 ++
 tests/networkxml2confdata/dhcp6-network.hostsfile  |   5 +
 .../dhcp6host-routed-network.hostsfile             |   7 ++
 tests/networkxml2confdata/leasetime-days.conf      |  17 +++
 tests/networkxml2confdata/leasetime-days.xml       |  18 +++
 tests/networkxml2confdata/leasetime-host.conf      |  16 +++
 tests/networkxml2confdata/leasetime-host.hostsfile |   6 +
 tests/networkxml2confdata/leasetime-host.xml       |  22 ++++
 tests/networkxml2confdata/leasetime-hours.conf     |  17 +++
 tests/networkxml2confdata/leasetime-hours.xml      |  18 +++
 tests/networkxml2confdata/leasetime-infinite.conf  |  17 +++
 tests/networkxml2confdata/leasetime-infinite.xml   |  18 +++
 tests/networkxml2confdata/leasetime-minutes.conf   |  17 +++
 tests/networkxml2confdata/leasetime-minutes.xml    |  18 +++
 tests/networkxml2confdata/leasetime-seconds.conf   |  17 +++
 tests/networkxml2confdata/leasetime-seconds.xml    |  18 +++
 tests/networkxml2confdata/leasetime.conf           |  17 +++
 tests/networkxml2confdata/leasetime.xml            |  18 +++
 .../nat-network-dns-srv-record-minimal.hostsfile   |   2 +
 .../nat-network-dns-srv-record.hostsfile           |   2 +
 .../nat-network-dns-txt-record.hostsfile           |   2 +
 .../nat-network-name-with-quotes.hostsfile         |   2 +
 tests/networkxml2confdata/nat-network.hostsfile    |   2 +
 tests/networkxml2conftest.c                        |  47 ++++++--
 33 files changed, 597 insertions(+), 78 deletions(-)
 create mode 100644 tests/networkxml2confdata/dhcp6-nat-network.hostsfile
 create mode 100644 tests/networkxml2confdata/dhcp6-network.hostsfile
 create mode 100644 tests/networkxml2confdata/dhcp6host-routed-network.hostsfile
 create mode 100644 tests/networkxml2confdata/leasetime-days.conf
 create mode 100644 tests/networkxml2confdata/leasetime-days.xml
 create mode 100644 tests/networkxml2confdata/leasetime-host.conf
 create mode 100644 tests/networkxml2confdata/leasetime-host.hostsfile
 create mode 100644 tests/networkxml2confdata/leasetime-host.xml
 create mode 100644 tests/networkxml2confdata/leasetime-hours.conf
 create mode 100644 tests/networkxml2confdata/leasetime-hours.xml
 create mode 100644 tests/networkxml2confdata/leasetime-infinite.conf
 create mode 100644 tests/networkxml2confdata/leasetime-infinite.xml
 create mode 100644 tests/networkxml2confdata/leasetime-minutes.conf
 create mode 100644 tests/networkxml2confdata/leasetime-minutes.xml
 create mode 100644 tests/networkxml2confdata/leasetime-seconds.conf
 create mode 100644 tests/networkxml2confdata/leasetime-seconds.xml
 create mode 100644 tests/networkxml2confdata/leasetime.conf
 create mode 100644 tests/networkxml2confdata/leasetime.xml
 create mode 100644 tests/networkxml2confdata/nat-network-dns-srv-record-minimal.hostsfile
 create mode 100644 tests/networkxml2confdata/nat-network-dns-srv-record.hostsfile
 create mode 100644 tests/networkxml2confdata/nat-network-dns-txt-record.hostsfile
 create mode 100644 tests/networkxml2confdata/nat-network-name-with-quotes.hostsfile
 create mode 100644 tests/networkxml2confdata/nat-network.hostsfile

diff --git a/docs/schemas/basictypes.rng b/docs/schemas/basictypes.rng
index 1b4f980..11fbe2b 100644
--- a/docs/schemas/basictypes.rng
+++ b/docs/schemas/basictypes.rng
@@ -518,4 +518,9 @@
     </element>
   </define>
 
+  <define name="leaseTime">
+    <data type="string">
+      <param name="pattern">-?[0-9]*[smhd]?</param>
+    </data>
+  </define>
 </grammar>
diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng
index 1a18e64..e6ddb63 100644
--- a/docs/schemas/network.rng
+++ b/docs/schemas/network.rng
@@ -340,6 +340,11 @@
               <!-- Define the range(s) of IP addresses that the DHCP
                    server should hand out -->
               <element name="dhcp">
+                <optional>
+                  <element name="leasetime">
+                    <ref name="leaseTime"/>
+                  </element>
+                </optional>
                 <zeroOrMore>
                   <element name="range">
                     <attribute name="start"><ref name="ipAddr"/></attribute>
@@ -361,6 +366,9 @@
                       <attribute name="name"><text/></attribute>
                     </choice>
                     <attribute name="ip"><ref name="ipAddr"/></attribute>
+                    <optional>
+                      <attribute name="leasetime"><ref name="leaseTime"/></attribute>
+                    </optional>
                   </element>
                 </zeroOrMore>
                 <optional>
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c
index aa39776..bcc4bbb 100644
--- a/src/conf/network_conf.c
+++ b/src/conf/network_conf.c
@@ -30,6 +30,8 @@
 #include <fcntl.h>
 #include <string.h>
 #include <dirent.h>
+#include <stdlib.h>
+#include <inttypes.h>
 
 #include "virerror.h"
 #include "datatypes.h"
@@ -911,16 +913,83 @@ virSocketAddrRangeParseXML(const char *networkName,
     return ret;
 }
 
+static int64_t
+virNetworkDHCPDefGetLeaseTime (xmlNodePtr node,
+                               xmlXPathContextPtr ctxt,
+                               const char* query,
+                               bool *error)
+{
+    int64_t multiplier = 1, result = -1;
+    char *leaseString;
+    xmlNodePtr save;
+    size_t len;
+
+    *error = 0;
+
+    save = ctxt->node;
+    ctxt->node = node;
+
+    leaseString = virXPathString (query, ctxt);
+
+    /* If value is not present we set the value to -2 */
+    if (leaseString == NULL) {
+        result = -2;
+        goto cleanup;
+    }
+
+    len = strlen (leaseString) - 1;
+
+    if (leaseString[len] == 'm')
+        multiplier = 60;
+    else if (leaseString[len] == 'h')
+        multiplier = 60 * 60;
+    else if (leaseString[len] == 'd')
+        multiplier = 60 * 60 * 24;
+
+    /* Remove the time unit suffix */
+    if (leaseString[len] < 48 || leaseString[len] > 57)
+        leaseString[len] = '\0';
+
+    errno = 0;
+    result = (int64_t) strtoll((const char*)leaseString, NULL, 10);
+
+    /* Report any errors parsing the string */
+    if (errno != 0) {
+        virReportError(VIR_ERR_XML_ERROR,
+                       _("<leasetime> value could not be converted to a signed integer: %s"),
+                      leaseString);
+        *error = 1;
+        goto cleanup;
+    }
+
+    result = result * multiplier;
+
+    if (result > UINT32_MAX || result < -1) {
+        virReportError(VIR_ERR_XML_ERROR,
+                       _("<leasetime> value cannot be greater than the equivalent of %" PRIo32 " seconds or less than -1: %" PRId64),
+                       UINT32_MAX, result);
+        *error = 1;
+    }
+
+cleanup:
+    VIR_FREE(leaseString);
+    ctxt->node = save;
+    return result;
+}
+
 static int
 virNetworkDHCPHostDefParseXML(const char *networkName,
                               virNetworkIPDefPtr def,
                               xmlNodePtr node,
+                              xmlXPathContextPtr ctxt,
                               virNetworkDHCPHostDefPtr host,
                               bool partialOkay)
 {
     char *mac = NULL, *name = NULL, *ip = NULL, *id = NULL;
     virMacAddr addr;
     virSocketAddr inaddr;
+    int64_t leasetime;
+    bool error;
     int ret = -1;
 
     mac = virXMLPropString(node, "mac");
@@ -1013,6 +1082,10 @@ virNetworkDHCPHostDefParseXML(const char *networkName,
         }
     }
 
+    leasetime = virNetworkDHCPDefGetLeaseTime (node, ctxt, "string(./@leasetime)", &error);
+    if (error)
+        goto cleanup;
+
     host->mac = mac;
     mac = NULL;
     host->id = id;
@@ -1021,6 +1094,7 @@ virNetworkDHCPHostDefParseXML(const char *networkName,
     name = NULL;
     if (ip)
         host->ip = inaddr;
+    host->leasetime = leasetime;
     ret = 0;
 
  cleanup:
@@ -1034,9 +1108,11 @@ virNetworkDHCPHostDefParseXML(const char *networkName,
 static int
 virNetworkDHCPDefParseXML(const char *networkName,
                           xmlNodePtr node,
+                          xmlXPathContextPtr ctxt,
                           virNetworkIPDefPtr def)
 {
     int ret = -1;
+    bool error;
     xmlNodePtr cur;
     virSocketAddrRange range;
     virNetworkDHCPHostDef host;
@@ -1044,6 +1120,10 @@ virNetworkDHCPDefParseXML(const char *networkName,
     memset(&range, 0, sizeof(range));
     memset(&host, 0, sizeof(host));
 
+    def->leasetime = virNetworkDHCPDefGetLeaseTime (node, ctxt, "string(./leasetime/text())", &error);
+    if (error)
+        goto cleanup;
+
     cur = node->children;
     while (cur != NULL) {
         if (cur->type == XML_ELEMENT_NODE &&
@@ -1057,7 +1137,7 @@ virNetworkDHCPDefParseXML(const char *networkName,
         } else if (cur->type == XML_ELEMENT_NODE &&
             xmlStrEqual(cur->name, BAD_CAST "host")) {
 
-            if (virNetworkDHCPHostDefParseXML(networkName, def, cur,
+            if (virNetworkDHCPHostDefParseXML(networkName, def, cur, ctxt,
                                               &host, false) < 0)
                 goto cleanup;
             if (VIR_APPEND_ELEMENT(def->hosts, def->nhosts, host) < 0)
@@ -1607,7 +1687,7 @@ virNetworkIPDefParseXML(const char *networkName,
     while (cur != NULL) {
         if (cur->type == XML_ELEMENT_NODE &&
             xmlStrEqual(cur->name, BAD_CAST "dhcp")) {
-            if (virNetworkDHCPDefParseXML(networkName, cur, def) < 0)
+            if (virNetworkDHCPDefParseXML(networkName, cur, ctxt, def) < 0)
                 goto cleanup;
         } else if (cur->type == XML_ELEMENT_NODE &&
                    xmlStrEqual(cur->name, BAD_CAST "tftp")) {
@@ -3659,7 +3739,7 @@ virNetworkDefUpdateIPDHCPHost(virNetworkDefPtr def,
     if (!ipdef)
         goto cleanup;
 
-    if (virNetworkDHCPHostDefParseXML(def->name, ipdef, ctxt->node,
+    if (virNetworkDHCPHostDefParseXML(def->name, ipdef, ctxt->node, ctxt,
                                       &host, partialOkay) < 0)
         goto cleanup;
 
diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h
index 3b227db..df687df 100644
--- a/src/conf/network_conf.h
+++ b/src/conf/network_conf.h
@@ -96,6 +96,7 @@ struct _virNetworkDHCPHostDef {
     char *id;
     char *name;
     virSocketAddr ip;
+    int64_t leasetime;
 };
 
 typedef struct _virNetworkDNSTxtDef virNetworkDNSTxtDef;
@@ -170,7 +171,8 @@ struct _virNetworkIPDef {
     char *tftproot;
     char *bootfile;
     virSocketAddr bootserver;
-   };
+    int64_t leasetime;
+};
 
 typedef struct _virNetworkForwardIfDef virNetworkForwardIfDef;
 typedef virNetworkForwardIfDef *virNetworkForwardIfDefPtr;
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index b88e903..84fb62e 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1497,6 +1497,7 @@ dnsmasqCapsRefresh;
 dnsmasqContextFree;
 dnsmasqContextNew;
 dnsmasqDelete;
+dnsmasqDhcpHostsToString;
 dnsmasqReload;
 dnsmasqSave;
 
diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
index 7b99aca..dccf743 100644
--- a/src/network/bridge_driver.c
+++ b/src/network/bridge_driver.c
@@ -41,6 +41,8 @@
 #include <sys/ioctl.h>
 #include <net/if.h>
 #include <dirent.h>
+#include <inttypes.h>
+#include <stdint.h>
 #if HAVE_SYS_SYSCTL_H
 # include <sys/sysctl.h>
 #endif
@@ -859,30 +861,6 @@ networkKillDaemon(pid_t pid, const char *daemonName, const char *networkName)
     return ret;
 }
 
-/* the following does not build a file, it builds a list
- * which is later saved into a file
- */
-
-static int
-networkBuildDnsmasqDhcpHostsList(dnsmasqContext *dctx,
-                                 virNetworkIPDefPtr ipdef)
-{
-    size_t i;
-    bool ipv6 = false;
-
-    if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6))
-        ipv6 = true;
-    for (i = 0; i < ipdef->nhosts; i++) {
-        virNetworkDHCPHostDefPtr host = &(ipdef->hosts[i]);
-        if (VIR_SOCKET_ADDR_VALID(&host->ip))
-            if (dnsmasqAddDhcpHost(dctx, host->mac, &host->ip,
-                                   host->name, host->id, ipv6) < 0)
-                return -1;
-    }
-
-    return 0;
-}
-
 static int
 networkBuildDnsmasqHostsList(dnsmasqContext *dctx,
                              virNetworkDNSDefPtr dnsdef)
@@ -903,11 +881,93 @@ networkBuildDnsmasqHostsList(dnsmasqContext *dctx,
     return 0;
 }
 
+/* translates the leasetime value into a dnsmasq configuration string for dhcp-range/host */
+static char *
+networkDnsmasqConfLeaseValueToString (int64_t leasetime)
+{
+    char *result = NULL;
+    virBuffer leasebuf = VIR_BUFFER_INITIALIZER;
+
+    /* Leasetime parameter set on the XML */
+    /* Less than -1 means there was no value set */
+    if (leasetime < -1) {
+        virBufferAsprintf(&leasebuf, "%s", "");
+    }
+    /* -1 means no expiration */
+    else if (leasetime == -1)
+        virBufferAsprintf(&leasebuf, ",infinite");
+    /* Minimum expiry value is 120 */
+    /* TODO: Discuss if we should up as we do here or just forward whatever value to dnsmasq */
+    else if (leasetime < 120)
+        virBufferAsprintf(&leasebuf, ",120");
+    /* DHCP value for lease time is a unsigned four octect integer */
+    else if (leasetime <= UINT32_MAX)
+        virBufferAsprintf(&leasebuf, ",%" PRId64, leasetime);
+    /* TODO: Discuss the use of "deprecated" for ipv6*/
+    /* TODO: Discuss what is the default value that we want as dnsmasq's is 1 hour */
+    /* TODO: Discuss what to do if value exceeds maximum, use default value for now */
+    else {
+        virBufferAsprintf(&leasebuf, "%s", "");
+    }
+
+    result = virBufferContentAndReset(&leasebuf);
+    virBufferFreeAndReset (&leasebuf);
+
+    return result;
+}
+
+/* the following does not build a file, it builds a list
+ * which is later saved into a file
+ */
+
+static int
+networkBuildDnsmasqDhcpHostsList(dnsmasqContext *dctx,
+                                 virNetworkIPDefPtr ipdef)
+{
+    int ret = -1;
+    size_t i;
+    bool ipv6 = false;
+    char *leasetime = networkDnsmasqConfLeaseValueToString(ipdef->leasetime);
+
+    if (!leasetime)
+        goto cleanup;
+
+    if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6))
+        ipv6 = true;
+    for (i = 0; i < ipdef->nhosts; i++) {
+        virNetworkDHCPHostDefPtr host = &(ipdef->hosts[i]);
+        if (VIR_SOCKET_ADDR_VALID(&host->ip)) {
+            /* If the host has its own leasetime we get its specific string */
+            if (host->leasetime > -2) {
+                char *hostlease = networkDnsmasqConfLeaseValueToString(host->leasetime);
+                if (!hostlease)
+                    goto cleanup;
+                if (dnsmasqAddDhcpHost(dctx, host->mac, &host->ip,
+                                       host->name, host->id, hostlease, ipv6) < 0) {
+                    VIR_FREE(hostlease);
+                    goto cleanup;
+                }
+                VIR_FREE(hostlease);
+            } else {
+            /* Otherwise we use the leasetime from dhcp */
+                if (dnsmasqAddDhcpHost(dctx, host->mac, &host->ip,
+                                       host->name, host->id, leasetime, ipv6) < 0)
+                    goto cleanup;
+            }
+        }
+    }
+
+    ret = 0;
+cleanup:
+    VIR_FREE(leasetime);
+    return ret;
+}
 
 int
 networkDnsmasqConfContents(virNetworkObjPtr network,
                            const char *pidfile,
                            char **configstr,
+                           char **hostsfilestr,
                            dnsmasqContext *dctx,
                            dnsmasqCapsPtr caps ATTRIBUTE_UNUSED)
 {
@@ -1213,6 +1273,7 @@ networkDnsmasqConfContents(virNetworkObjPtr network,
         }
         for (r = 0; r < ipdef->nranges; r++) {
             int thisRange;
+            char *leasestr;
 
             if (!(saddr = virSocketAddrFormat(&ipdef->ranges[r].start)) ||
                 !(eaddr = virSocketAddrFormat(&ipdef->ranges[r].end)))
@@ -1220,12 +1281,22 @@ networkDnsmasqConfContents(virNetworkObjPtr network,
 
             virBufferAsprintf(&configbuf, "dhcp-range=%s,%s",
                               saddr, eaddr);
-            if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6))
+
+            /* Add ipv6 prefix length parameter if needed */
+            if (ipdef == ipv6def)
                 virBufferAsprintf(&configbuf, ",%d", prefix);
+
+            leasestr = networkDnsmasqConfLeaseValueToString (ipdef->leasetime);
+            if (!leasestr)
+                goto cleanup;
+            virBufferAsprintf(&configbuf, "%s", leasestr);
+
+            /* Add the newline */
             virBufferAddLit(&configbuf, "\n");
 
             VIR_FREE(saddr);
             VIR_FREE(eaddr);
+            VIR_FREE(leasestr);
             thisRange = virSocketAddrGetRange(&ipdef->ranges[r].start,
                                               &ipdef->ranges[r].end,
                                               &ipdef->address,
@@ -1256,6 +1327,15 @@ networkDnsmasqConfContents(virNetworkObjPtr network,
         if (networkBuildDnsmasqDhcpHostsList(dctx, ipdef) < 0)
             goto cleanup;
 
+        /* Return the contents of the hostsfile if requested */
+        if (hostsfilestr) {
+            *hostsfilestr = dnsmasqDhcpHostsToString (dctx->hostsfile->hosts,
+                                                      dctx->hostsfile->nhosts);
+
+            if (!hostsfilestr)
+                goto cleanup;
+        }
+
         /* Note: the following is IPv4 only */
         if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET)) {
             if (ipdef->nranges || ipdef->nhosts)
@@ -1355,7 +1435,7 @@ networkBuildDhcpDaemonCommandLine(virNetworkDriverStatePtr driver,
 
     network->dnsmasqPid = -1;
 
-    if (networkDnsmasqConfContents(network, pidfile, &configstr,
+    if (networkDnsmasqConfContents(network, pidfile, &configstr, NULL,
                                    dctx, dnsmasq_caps) < 0)
         goto cleanup;
     if (!configstr)
diff --git a/src/network/bridge_driver.h b/src/network/bridge_driver.h
index ff7f921..c653c50 100644
--- a/src/network/bridge_driver.h
+++ b/src/network/bridge_driver.h
@@ -53,6 +53,7 @@ int networkGetActualType(virDomainNetDefPtr iface)
 int networkDnsmasqConfContents(virNetworkObjPtr network,
                         const char *pidfile,
                         char **configstr,
+                        char **hostsfilestr,
                         dnsmasqContext *dctx,
                         dnsmasqCapsPtr caps);
 
diff --git a/src/util/virdnsmasq.c b/src/util/virdnsmasq.c
index 1b78c1f..94c9a3b 100644
--- a/src/util/virdnsmasq.c
+++ b/src/util/virdnsmasq.c
@@ -308,52 +308,47 @@ hostsfileAdd(dnsmasqHostsfile *hostsfile,
              virSocketAddr *ip,
              const char *name,
              const char *id,
+             const char *leasetime,
              bool ipv6)
 {
+    int ret = -1;
     char *ipstr = NULL;
+    virBuffer hostbuf = VIR_BUFFER_INITIALIZER;
+
     if (VIR_REALLOC_N(hostsfile->hosts, hostsfile->nhosts + 1) < 0)
         goto error;
 
     if (!(ipstr = virSocketAddrFormat(ip)))
-        return -1;
+        goto error;
 
     /* the first test determines if it is a dhcpv6 host */
     if (ipv6) {
-        if (name && id) {
-            if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host,
-                            "id:%s,%s,[%s]", id, name, ipstr) < 0)
-                goto error;
-        } else if (name && !id) {
-            if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host,
-                            "%s,[%s]", name, ipstr) < 0)
-                goto error;
-        } else if (!name && id) {
-            if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host,
-                            "id:%s,[%s]", id, ipstr) < 0)
-                goto error;
-        }
+        if (name && id)
+            virBufferAsprintf(&hostbuf, "id:%s,%s,[%s]", id, name, ipstr);
+        else if (name && !id)
+            virBufferAsprintf(&hostbuf, "%s,[%s]", name, ipstr);
+        else if (!name && id)
+            virBufferAsprintf(&hostbuf, "id:%s,[%s]", id, ipstr);
     } else if (name && mac) {
-        if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host, "%s,%s,%s",
-                        mac, ipstr, name) < 0)
-            goto error;
+        virBufferAsprintf(&hostbuf, "%s,%s,%s", mac, ipstr, name);
     } else if (name && !mac) {
-        if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host, "%s,%s",
-                        name, ipstr) < 0)
-            goto error;
+        virBufferAsprintf(&hostbuf, "%s,%s", name, ipstr);
     } else {
-        if (virAsprintf(&hostsfile->hosts[hostsfile->nhosts].host, "%s,%s",
-                        mac, ipstr) < 0)
-            goto error;
+        virBufferAsprintf(&hostbuf, "%s,%s", mac, ipstr);
     }
-    VIR_FREE(ipstr);
 
-    hostsfile->nhosts++;
+    /* The leasetime string already includes comma if there's any value at all */
+    virBufferAsprintf(&hostbuf, "%s", leasetime);
 
-    return 0;
+    if (!(hostsfile->hosts[hostsfile->nhosts].host = virBufferContentAndReset (&hostbuf)))
+      goto error;
 
+    hostsfile->nhosts++;
+    ret = 0;
  error:
+    virBufferFreeAndReset(&hostbuf);
     VIR_FREE(ipstr);
-    return -1;
+    return ret;
 }
 
 static dnsmasqHostsfile *
@@ -391,10 +386,9 @@ hostsfileWrite(const char *path,
                dnsmasqDhcpHost *hosts,
                unsigned int nhosts)
 {
-    char *tmp;
+    char *tmp, *content = NULL;
     FILE *f;
     bool istmp = true;
-    size_t i;
     int rc = 0;
 
     /* even if there are 0 hosts, create a 0 length file, to allow
@@ -412,17 +406,21 @@ hostsfileWrite(const char *path,
         }
     }
 
-    for (i = 0; i < nhosts; i++) {
-        if (fputs(hosts[i].host, f) == EOF || fputc('\n', f) == EOF) {
-            rc = -errno;
-            VIR_FORCE_FCLOSE(f);
+    if (!(content = dnsmasqDhcpHostsToString(hosts, nhosts))) {
+        rc = -ENOMEM;
+        goto cleanup;
+    }
 
-            if (istmp)
-                unlink(tmp);
+    if (fputs(content, f) == EOF) {
+        rc = -errno;
+        VIR_FORCE_FCLOSE(f);
+
+        if (istmp)
+            unlink(tmp);
+
+        goto cleanup;
+     }
 
-            goto cleanup;
-        }
-    }
 
     if (VIR_FCLOSE(f) == EOF) {
         rc = -errno;
@@ -436,6 +434,7 @@ hostsfileWrite(const char *path,
     }
 
  cleanup:
+    VIR_FREE(content);
     VIR_FREE(tmp);
 
     return rc;
@@ -524,9 +523,10 @@ dnsmasqAddDhcpHost(dnsmasqContext *ctx,
                    virSocketAddr *ip,
                    const char *name,
                    const char *id,
+                   const char *leasetime,
                    bool ipv6)
 {
-    return hostsfileAdd(ctx->hostsfile, mac, ip, name, id, ipv6);
+    return hostsfileAdd(ctx->hostsfile, mac, ip, name, id, leasetime, ipv6);
 }
 
 /*
@@ -892,3 +892,31 @@ dnsmasqCapsGet(dnsmasqCapsPtr caps, dnsmasqCapsFlags flag)
 
     return caps && virBitmapIsBitSet(caps->flags, flag);
 }
+
+/** dnsmasqDhcpHostsToString:
+ *
+ *   Turns a vector of dnsmasqDhcpHost into the string that is ought to be
+ *   stored in the hostsfile, this functionality is split to make hostsfiles
+ *   testable. Returs NULL if nhosts is 0.
+ */
+char *
+dnsmasqDhcpHostsToString (dnsmasqDhcpHost *hosts,
+                          unsigned int nhosts)
+{
+    int i;
+    char *result = NULL;
+    virBuffer hostsfilebuf = VIR_BUFFER_INITIALIZER;
+
+    if (nhosts == 0)
+       goto cleanup;
+
+    for (i = 0; i < nhosts; i++) {
+        virBufferAsprintf(&hostsfilebuf, "%s\n", hosts[i].host);
+    }
+
+    result = virBufferContentAndReset(&hostsfilebuf);
+
+cleanup:
+    virBufferFreeAndReset(&hostsfilebuf);
+    return result;
+}
diff --git a/src/util/virdnsmasq.h b/src/util/virdnsmasq.h
index f47bea3..1795bc8 100644
--- a/src/util/virdnsmasq.h
+++ b/src/util/virdnsmasq.h
@@ -88,6 +88,7 @@ int              dnsmasqAddDhcpHost(dnsmasqContext *ctx,
                                     virSocketAddr *ip,
                                     const char *name,
                                     const char *id,
+                                    const char *leastime,
                                     bool ipv6);
 int              dnsmasqAddHost(dnsmasqContext *ctx,
                                 virSocketAddr *ip,
@@ -105,6 +106,7 @@ int dnsmasqCapsRefresh(dnsmasqCapsPtr *caps, const char *binaryPath);
 bool dnsmasqCapsGet(dnsmasqCapsPtr caps, dnsmasqCapsFlags flag);
 const char *dnsmasqCapsGetBinaryPath(dnsmasqCapsPtr caps);
 unsigned long dnsmasqCapsGetVersion(dnsmasqCapsPtr caps);
+char *dnsmasqDhcpHostsToString(dnsmasqDhcpHost *hosts, unsigned int nhosts);
 
 # define DNSMASQ_DHCPv6_MAJOR_REQD 2
 # define DNSMASQ_DHCPv6_MINOR_REQD 64
diff --git a/tests/networkxml2confdata/dhcp6-nat-network.hostsfile b/tests/networkxml2confdata/dhcp6-nat-network.hostsfile
new file mode 100644
index 0000000..de659b9
--- /dev/null
+++ b/tests/networkxml2confdata/dhcp6-nat-network.hostsfile
@@ -0,0 +1,7 @@
+00:16:3e:77:e2:ed,192.168.122.10,a.example.com
+00:16:3e:3e:a9:1a,192.168.122.11,b.example.com
+id:0:4:7e:7d:f0:7d:a8:bc:c5:d2:13:32:11:ed:16:ea:84:63,[2001:db8:ac10:fd01::1:20]
+paul,[2001:db8:ac10:fd01::1:21]
+id:0:3:0:1:0:16:3e:11:22:33,peter.xyz,[2001:db8:ac10:fd01::1:22]
+id:0:3:0:1:0:16:3e:44:55:33,[2001:db8:ac10:fd01::1:23]
+id:0:1:0:1:18:aa:62:fe:0:16:3e:44:55:66,badbob,[2001:db8:ac10:fd01::1:24]
diff --git a/tests/networkxml2confdata/dhcp6-network.hostsfile b/tests/networkxml2confdata/dhcp6-network.hostsfile
new file mode 100644
index 0000000..9dfb172
--- /dev/null
+++ b/tests/networkxml2confdata/dhcp6-network.hostsfile
@@ -0,0 +1,5 @@
+id:0:4:7e:7d:f0:7d:a8:bc:c5:d2:13:32:11:ed:16:ea:84:63,[2001:db8:ac10:fd01::1:20]
+paul,[2001:db8:ac10:fd01::1:21]
+id:0:3:0:1:0:16:3e:11:22:33,peter.xyz,[2001:db8:ac10:fd01::1:22]
+id:0:3:0:1:0:16:3e:44:55:33,[2001:db8:ac10:fd01::1:23]
+id:0:1:0:1:18:aa:62:fe:0:16:3e:44:55:66,badbob,[2001:db8:ac10:fd01::1:24]
diff --git a/tests/networkxml2confdata/dhcp6host-routed-network.hostsfile b/tests/networkxml2confdata/dhcp6host-routed-network.hostsfile
new file mode 100644
index 0000000..de659b9
--- /dev/null
+++ b/tests/networkxml2confdata/dhcp6host-routed-network.hostsfile
@@ -0,0 +1,7 @@
+00:16:3e:77:e2:ed,192.168.122.10,a.example.com
+00:16:3e:3e:a9:1a,192.168.122.11,b.example.com
+id:0:4:7e:7d:f0:7d:a8:bc:c5:d2:13:32:11:ed:16:ea:84:63,[2001:db8:ac10:fd01::1:20]
+paul,[2001:db8:ac10:fd01::1:21]
+id:0:3:0:1:0:16:3e:11:22:33,peter.xyz,[2001:db8:ac10:fd01::1:22]
+id:0:3:0:1:0:16:3e:44:55:33,[2001:db8:ac10:fd01::1:23]
+id:0:1:0:1:18:aa:62:fe:0:16:3e:44:55:66,badbob,[2001:db8:ac10:fd01::1:24]
diff --git a/tests/networkxml2confdata/leasetime-days.conf b/tests/networkxml2confdata/leasetime-days.conf
new file mode 100644
index 0000000..9501e2f
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-days.conf
@@ -0,0 +1,17 @@
+##WARNING:  THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
+##OVERWRITTEN AND LOST.  Changes to this configuration should be made using:
+##    virsh net-edit default
+## or other application using the libvirt API.
+##
+## dnsmasq conf file created by libvirt
+strict-order
+except-interface=lo
+bind-dynamic
+interface=virbr0
+dhcp-range=192.168.122.2,192.168.122.254,86400
+dhcp-no-override
+dhcp-range=2001:db8:ac10:fd01::1:10,2001:db8:ac10:fd01::1:ff,64,86400
+dhcp-lease-max=493
+dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile
+addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts
+enable-ra
diff --git a/tests/networkxml2confdata/leasetime-days.xml b/tests/networkxml2confdata/leasetime-days.xml
new file mode 100644
index 0000000..1a507d5
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-days.xml
@@ -0,0 +1,18 @@
+<network>
+  <name>default</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <forward dev='eth1' mode='nat'/>
+  <bridge name='virbr0' stp='on' delay='0'/>
+  <ip address='192.168.122.1' netmask='255.255.255.0'>
+    <dhcp>
+      <leasetime>1d</leasetime>
+      <range start='192.168.122.2' end='192.168.122.254'/>
+    </dhcp>
+  </ip>
+  <ip family='ipv6' address='2001:db8:ac10:fd01::1' prefix='64'>
+    <dhcp>
+      <leasetime>1d</leasetime>
+      <range start='2001:db8:ac10:fd01::1:10' end='2001:db8:ac10:fd01::1:ff'/>
+    </dhcp>
+  </ip>
+</network>
diff --git a/tests/networkxml2confdata/leasetime-host.conf b/tests/networkxml2confdata/leasetime-host.conf
new file mode 100644
index 0000000..7bd2054
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-host.conf
@@ -0,0 +1,16 @@
+##WARNING:  THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
+##OVERWRITTEN AND LOST.  Changes to this configuration should be made using:
+##    virsh net-edit default
+## or other application using the libvirt API.
+##
+## dnsmasq conf file created by libvirt
+strict-order
+except-interface=lo
+bind-dynamic
+interface=virbr0
+srv-host=_name._tcp
+dhcp-range=192.168.122.2,192.168.122.254
+dhcp-no-override
+dhcp-lease-max=253
+dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile
+addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts
diff --git a/tests/networkxml2confdata/leasetime-host.hostsfile b/tests/networkxml2confdata/leasetime-host.hostsfile
new file mode 100644
index 0000000..fd20ca2
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-host.hostsfile
@@ -0,0 +1,6 @@
+00:16:3e:3e:a9:01,192.168.122.11,a.example.com,3600
+00:16:3e:3e:a9:02,192.168.122.12,b.example.com,3600
+00:16:3e:3e:a9:03,192.168.122.13,c.example.com,3600
+00:16:3e:3e:a9:04,192.168.122.14,d.example.com,86400
+00:16:3e:3e:a9:05,192.168.122.15,e.example.com,3600
+00:16:3e:3e:a9:06,192.168.122.16,f.example.com,infinite
diff --git a/tests/networkxml2confdata/leasetime-host.xml b/tests/networkxml2confdata/leasetime-host.xml
new file mode 100644
index 0000000..276d3e5
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-host.xml
@@ -0,0 +1,22 @@
+<network>
+  <name>default</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <forward dev='eth1' mode='nat'>
+    <interface dev='eth1'/>
+  </forward>
+  <bridge name='virbr0' stp='on' delay='0'/>
+  <dns>
+    <srv service='name' protocol='tcp'/>
+  </dns>
+  <ip address='192.168.122.1' netmask='255.255.255.0'>
+    <dhcp>
+      <range start='192.168.122.2' end='192.168.122.254'/>
+      <host mac='00:16:3e:3e:a9:1a' name='a.example.com' ip='192.168.122.11' leasetime="3600s"/>
+      <host mac='00:16:3e:3e:a9:1b' name='b.example.com' ip='192.168.122.12' leasetime="60m"/>
+      <host mac='00:16:3e:3e:a9:1c' name='c.example.com' ip='192.168.122.13' leasetime="1h"/>
+      <host mac='00:16:3e:3e:a9:1d' name='d.example.com' ip='192.168.122.14' leasetime="1d"/>
+      <host mac='00:16:3e:3e:a9:1e' name='e.example.com' ip='192.168.122.15' leasetime="3600"/>
+      <host mac='00:16:3e:3e:a9:1f' name='f.example.com' ip='192.168.122.16' leasetime="-1"/>
+    </dhcp>
+  </ip>
+</network>
diff --git a/tests/networkxml2confdata/leasetime-hours.conf b/tests/networkxml2confdata/leasetime-hours.conf
new file mode 100644
index 0000000..021a769
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-hours.conf
@@ -0,0 +1,17 @@
+##WARNING:  THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
+##OVERWRITTEN AND LOST.  Changes to this configuration should be made using:
+##    virsh net-edit default
+## or other application using the libvirt API.
+##
+## dnsmasq conf file created by libvirt
+strict-order
+except-interface=lo
+bind-dynamic
+interface=virbr0
+dhcp-range=192.168.122.2,192.168.122.254,3600
+dhcp-no-override
+dhcp-range=2001:db8:ac10:fd01::1:10,2001:db8:ac10:fd01::1:ff,64,3600
+dhcp-lease-max=493
+dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile
+addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts
+enable-ra
diff --git a/tests/networkxml2confdata/leasetime-hours.xml b/tests/networkxml2confdata/leasetime-hours.xml
new file mode 100644
index 0000000..36dc600
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-hours.xml
@@ -0,0 +1,18 @@
+<network>
+  <name>default</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <forward dev='eth1' mode='nat'/>
+  <bridge name='virbr0' stp='on' delay='0'/>
+  <ip address='192.168.122.1' netmask='255.255.255.0'>
+    <dhcp>
+      <leasetime>1h</leasetime>
+      <range start='192.168.122.2' end='192.168.122.254'/>
+    </dhcp>
+  </ip>
+  <ip family='ipv6' address='2001:db8:ac10:fd01::1' prefix='64'>
+    <dhcp>
+      <leasetime>1h</leasetime>
+      <range start='2001:db8:ac10:fd01::1:10' end='2001:db8:ac10:fd01::1:ff'/>
+    </dhcp>
+  </ip>
+</network>
diff --git a/tests/networkxml2confdata/leasetime-infinite.conf b/tests/networkxml2confdata/leasetime-infinite.conf
new file mode 100644
index 0000000..cc21135
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-infinite.conf
@@ -0,0 +1,17 @@
+##WARNING:  THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
+##OVERWRITTEN AND LOST.  Changes to this configuration should be made using:
+##    virsh net-edit default
+## or other application using the libvirt API.
+##
+## dnsmasq conf file created by libvirt
+strict-order
+except-interface=lo
+bind-dynamic
+interface=virbr0
+dhcp-range=192.168.122.2,192.168.122.254,infinite
+dhcp-no-override
+dhcp-range=2001:db8:ac10:fd01::1:10,2001:db8:ac10:fd01::1:ff,64,infinite
+dhcp-lease-max=493
+dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile
+addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts
+enable-ra
diff --git a/tests/networkxml2confdata/leasetime-infinite.xml b/tests/networkxml2confdata/leasetime-infinite.xml
new file mode 100644
index 0000000..bc8740e
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-infinite.xml
@@ -0,0 +1,18 @@
+<network>
+  <name>default</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <forward dev='eth1' mode='nat'/>
+  <bridge name='virbr0' stp='on' delay='0'/>
+  <ip address='192.168.122.1' netmask='255.255.255.0'>
+    <dhcp>
+      <leasetime>-1</leasetime>
+      <range start='192.168.122.2' end='192.168.122.254'/>
+    </dhcp>
+  </ip>
+  <ip family='ipv6' address='2001:db8:ac10:fd01::1' prefix='64'>
+    <dhcp>
+      <leasetime>-1</leasetime>
+      <range start='2001:db8:ac10:fd01::1:10' end='2001:db8:ac10:fd01::1:ff'/>
+    </dhcp>
+  </ip>
+</network>
diff --git a/tests/networkxml2confdata/leasetime-minutes.conf b/tests/networkxml2confdata/leasetime-minutes.conf
new file mode 100644
index 0000000..db68895
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-minutes.conf
@@ -0,0 +1,17 @@
+##WARNING:  THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
+##OVERWRITTEN AND LOST.  Changes to this configuration should be made using:
+##    virsh net-edit default
+## or other application using the libvirt API.
+##
+## dnsmasq conf file created by libvirt
+strict-order
+except-interface=lo
+bind-dynamic
+interface=virbr0
+dhcp-range=192.168.122.2,192.168.122.254,300
+dhcp-no-override
+dhcp-range=2001:db8:ac10:fd01::1:10,2001:db8:ac10:fd01::1:ff,64,300
+dhcp-lease-max=493
+dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile
+addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts
+enable-ra
diff --git a/tests/networkxml2confdata/leasetime-minutes.xml b/tests/networkxml2confdata/leasetime-minutes.xml
new file mode 100644
index 0000000..7c1df25
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-minutes.xml
@@ -0,0 +1,18 @@
+<network>
+  <name>default</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <forward dev='eth1' mode='nat'/>
+  <bridge name='virbr0' stp='on' delay='0'/>
+  <ip address='192.168.122.1' netmask='255.255.255.0'>
+    <dhcp>
+      <leasetime>5m</leasetime>
+      <range start='192.168.122.2' end='192.168.122.254'/>
+    </dhcp>
+  </ip>
+  <ip family='ipv6' address='2001:db8:ac10:fd01::1' prefix='64'>
+    <dhcp>
+      <leasetime>5m</leasetime>
+      <range start='2001:db8:ac10:fd01::1:10' end='2001:db8:ac10:fd01::1:ff'/>
+    </dhcp>
+  </ip>
+</network>
diff --git a/tests/networkxml2confdata/leasetime-seconds.conf b/tests/networkxml2confdata/leasetime-seconds.conf
new file mode 100644
index 0000000..635896b
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-seconds.conf
@@ -0,0 +1,17 @@
+##WARNING:  THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
+##OVERWRITTEN AND LOST.  Changes to this configuration should be made using:
+##    virsh net-edit default
+## or other application using the libvirt API.
+##
+## dnsmasq conf file created by libvirt
+strict-order
+except-interface=lo
+bind-dynamic
+interface=virbr0
+dhcp-range=192.168.122.2,192.168.122.254,125
+dhcp-no-override
+dhcp-range=2001:db8:ac10:fd01::1:10,2001:db8:ac10:fd01::1:ff,64,125
+dhcp-lease-max=493
+dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile
+addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts
+enable-ra
diff --git a/tests/networkxml2confdata/leasetime-seconds.xml b/tests/networkxml2confdata/leasetime-seconds.xml
new file mode 100644
index 0000000..dcb3f91
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime-seconds.xml
@@ -0,0 +1,18 @@
+<network>
+  <name>default</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <forward dev='eth1' mode='nat'/>
+  <bridge name='virbr0' stp='on' delay='0'/>
+  <ip address='192.168.122.1' netmask='255.255.255.0'>
+    <dhcp>
+      <leasetime>125s</leasetime>
+      <range start='192.168.122.2' end='192.168.122.254'/>
+    </dhcp>
+  </ip>
+  <ip family='ipv6' address='2001:db8:ac10:fd01::1' prefix='64'>
+    <dhcp>
+      <leasetime>125s</leasetime>
+      <range start='2001:db8:ac10:fd01::1:10' end='2001:db8:ac10:fd01::1:ff'/>
+    </dhcp>
+  </ip>
+</network>
diff --git a/tests/networkxml2confdata/leasetime.conf b/tests/networkxml2confdata/leasetime.conf
new file mode 100644
index 0000000..72a2f69
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime.conf
@@ -0,0 +1,17 @@
+##WARNING:  THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
+##OVERWRITTEN AND LOST.  Changes to this configuration should be made using:
+##    virsh net-edit default
+## or other application using the libvirt API.
+##
+## dnsmasq conf file created by libvirt
+strict-order
+except-interface=lo
+bind-dynamic
+interface=virbr0
+dhcp-range=192.168.122.2,192.168.122.254,122
+dhcp-no-override
+dhcp-range=2001:db8:ac10:fd01::1:10,2001:db8:ac10:fd01::1:ff,64,121
+dhcp-lease-max=493
+dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile
+addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts
+enable-ra
diff --git a/tests/networkxml2confdata/leasetime.xml b/tests/networkxml2confdata/leasetime.xml
new file mode 100644
index 0000000..fdbb15f
--- /dev/null
+++ b/tests/networkxml2confdata/leasetime.xml
@@ -0,0 +1,18 @@
+<network>
+  <name>default</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <forward dev='eth1' mode='nat'/>
+  <bridge name='virbr0' stp='on' delay='0'/>
+  <ip address='192.168.122.1' netmask='255.255.255.0'>
+    <dhcp>
+      <leasetime>122</leasetime>
+      <range start='192.168.122.2' end='192.168.122.254'/>
+    </dhcp>
+  </ip>
+  <ip family='ipv6' address='2001:db8:ac10:fd01::1' prefix='64'>
+    <dhcp>
+      <leasetime>121</leasetime>
+      <range start='2001:db8:ac10:fd01::1:10' end='2001:db8:ac10:fd01::1:ff'/>
+    </dhcp>
+  </ip>
+</network>
diff --git a/tests/networkxml2confdata/nat-network-dns-srv-record-minimal.hostsfile b/tests/networkxml2confdata/nat-network-dns-srv-record-minimal.hostsfile
new file mode 100644
index 0000000..deb3f00
--- /dev/null
+++ b/tests/networkxml2confdata/nat-network-dns-srv-record-minimal.hostsfile
@@ -0,0 +1,2 @@
+00:16:3e:77:e2:ed,192.168.122.10,a.example.com
+00:16:3e:3e:a9:1a,192.168.122.11,b.example.com
diff --git a/tests/networkxml2confdata/nat-network-dns-srv-record.hostsfile b/tests/networkxml2confdata/nat-network-dns-srv-record.hostsfile
new file mode 100644
index 0000000..deb3f00
--- /dev/null
+++ b/tests/networkxml2confdata/nat-network-dns-srv-record.hostsfile
@@ -0,0 +1,2 @@
+00:16:3e:77:e2:ed,192.168.122.10,a.example.com
+00:16:3e:3e:a9:1a,192.168.122.11,b.example.com
diff --git a/tests/networkxml2confdata/nat-network-dns-txt-record.hostsfile b/tests/networkxml2confdata/nat-network-dns-txt-record.hostsfile
new file mode 100644
index 0000000..deb3f00
--- /dev/null
+++ b/tests/networkxml2confdata/nat-network-dns-txt-record.hostsfile
@@ -0,0 +1,2 @@
+00:16:3e:77:e2:ed,192.168.122.10,a.example.com
+00:16:3e:3e:a9:1a,192.168.122.11,b.example.com
diff --git a/tests/networkxml2confdata/nat-network-name-with-quotes.hostsfile b/tests/networkxml2confdata/nat-network-name-with-quotes.hostsfile
new file mode 100644
index 0000000..deb3f00
--- /dev/null
+++ b/tests/networkxml2confdata/nat-network-name-with-quotes.hostsfile
@@ -0,0 +1,2 @@
+00:16:3e:77:e2:ed,192.168.122.10,a.example.com
+00:16:3e:3e:a9:1a,192.168.122.11,b.example.com
diff --git a/tests/networkxml2confdata/nat-network.hostsfile b/tests/networkxml2confdata/nat-network.hostsfile
new file mode 100644
index 0000000..deb3f00
--- /dev/null
+++ b/tests/networkxml2confdata/nat-network.hostsfile
@@ -0,0 +1,2 @@
+00:16:3e:77:e2:ed,192.168.122.10,a.example.com
+00:16:3e:3e:a9:1a,192.168.122.11,b.example.com
diff --git a/tests/networkxml2conftest.c b/tests/networkxml2conftest.c
index 65a0e32..8e1f8be 100644
--- a/tests/networkxml2conftest.c
+++ b/tests/networkxml2conftest.c
@@ -19,9 +19,13 @@
 #define VIR_FROM_THIS VIR_FROM_NONE
 
 static int
-testCompareXMLToConfFiles(const char *inxml, const char *outconf, dnsmasqCapsPtr caps)
+testCompareXMLToConfFiles(const char *inxml,
+                          const char *outconf,
+                          const char *outhostsfile,
+                          dnsmasqCapsPtr caps)
 {
-    char *actual = NULL;
+    char *actualconf = NULL;
+    char *actualhosts = NULL;
     int ret = -1;
     virNetworkDefPtr dev = NULL;
     virNetworkObjPtr obj = NULL;
@@ -41,17 +45,30 @@ testCompareXMLToConfFiles(const char *inxml, const char *outconf, dnsmasqCapsPtr
     if (dctx == NULL)
         goto fail;
 
-    if (networkDnsmasqConfContents(obj, pidfile, &actual,
+    if (networkDnsmasqConfContents(obj, pidfile, &actualconf, &actualhosts,
                         dctx, caps) < 0)
         goto fail;
 
-    if (virTestCompareToFile(actual, outconf) < 0)
+    if (virTestCompareToFile(actualconf, outconf) < 0)
         goto fail;
 
+    if (virFileExists(outhostsfile)) {
+        if (!actualhosts) {
+            fprintf(stderr, "%s: hostsfile exists but the configuration did not specify any host", outhostsfile);
+            goto fail;
+        } else if (virTestCompareToFile(actualhosts, outhostsfile) < 0) {
+            goto fail;
+        }
+    } else if (actualhosts) {
+        fprintf(stderr, "%s: file does not exist but actual data was expected", outhostsfile);
+        goto fail;
+    }
+
     ret = 0;
 
  fail:
-    VIR_FREE(actual);
+    VIR_FREE(actualconf);
+    VIR_FREE(actualhosts);
     VIR_FREE(pidfile);
     virCommandFree(cmd);
     virObjectUnref(obj);
@@ -70,20 +87,24 @@ testCompareXMLToConfHelper(const void *data)
     int result = -1;
     const testInfo *info = data;
     char *inxml = NULL;
-    char *outxml = NULL;
+    char *outconf = NULL;
+    char *outhostsfile = NULL;
 
     if (virAsprintf(&inxml, "%s/networkxml2confdata/%s.xml",
                     abs_srcdir, info->name) < 0 ||
-        virAsprintf(&outxml, "%s/networkxml2confdata/%s.conf",
+        virAsprintf(&outconf, "%s/networkxml2confdata/%s.conf",
+                    abs_srcdir, info->name) < 0 ||
+        virAsprintf(&outhostsfile, "%s/networkxml2confdata/%s.hostsfile",
                     abs_srcdir, info->name) < 0) {
         goto cleanup;
     }
 
-    result = testCompareXMLToConfFiles(inxml, outxml, info->caps);
+    result = testCompareXMLToConfFiles(inxml, outconf, outhostsfile, info->caps);
 
  cleanup:
     VIR_FREE(inxml);
-    VIR_FREE(outxml);
+    VIR_FREE(outconf);
+    VIR_FREE(outhostsfile);
 
     return result;
 }
@@ -129,6 +150,14 @@ mymain(void)
     DO_TEST("dhcp6-network", dhcpv6);
     DO_TEST("dhcp6-nat-network", dhcpv6);
     DO_TEST("dhcp6host-routed-network", dhcpv6);
+    DO_TEST("leasetime", dhcpv6);
+    DO_TEST("leasetime-seconds", dhcpv6);
+    DO_TEST("leasetime-hours", dhcpv6);
+    DO_TEST("leasetime-minutes", dhcpv6);
+    DO_TEST("leasetime-hours", dhcpv6);
+    DO_TEST("leasetime-days", dhcpv6);
+    DO_TEST("leasetime-infinite", dhcpv6);
+    DO_TEST("leasetime-host", dhcpv6);
 
     virObjectUnref(dhcpv6);
     virObjectUnref(full);
-- 
2.9.3

