[libvirt] [PATCH 2/3] network: allow disabling dnsmasq's DNS server

Laine Stump laine at laine.org
Fri Aug 12 02:41:46 UTC 2016


If you define a libvirt virtual network with one or more IP addresses,
it starts up an instance of dnsmasq. It's always been possible to
avoid dnsmasq's dhcp server (simply don't include a <dhcp> element),
but until now it wasn't possible to avoid having the DNS server
listening; even if the network has no <dns> element, it is started
using default settings.

This patch adds a new attribute to <dns>: enable='yes|no'. For
backward compatibility, it defaults to 'yes', but if you don't want a
DNS server created for the network, you can simply add:

   <dns enable='no'/>

to the network configuration, and next time the network is started
there will be no dns server created (if there is dhcp configuration,
dnsmasq will be started with "port=0" which disables the DNS server;
if there is no dhcp configuration, dnsmasq won't be started at all).
---
 docs/formatnetwork.html.in                         |  12 ++
 docs/schemas/network.rng                           |   5 +
 src/conf/network_conf.c                            |  36 ++++-
 src/conf/network_conf.h                            |   1 +
 src/network/bridge_driver.c                        | 146 ++++++++++++---------
 .../networkxml2confdata/routed-network-no-dns.conf |  11 ++
 .../networkxml2confdata/routed-network-no-dns.xml  |  10 ++
 tests/networkxml2conftest.c                        |   1 +
 tests/networkxml2xmlin/routed-network-no-dns.xml   |  10 ++
 tests/networkxml2xmlout/routed-network-no-dns.xml  |  12 ++
 tests/networkxml2xmltest.c                         |   1 +
 11 files changed, 179 insertions(+), 66 deletions(-)
 create mode 100644 tests/networkxml2confdata/routed-network-no-dns.conf
 create mode 100644 tests/networkxml2confdata/routed-network-no-dns.xml
 create mode 100644 tests/networkxml2xmlin/routed-network-no-dns.xml
 create mode 100644 tests/networkxml2xmlout/routed-network-no-dns.xml

diff --git a/docs/formatnetwork.html.in b/docs/formatnetwork.html.in
index 12d1bed..e103dd7 100644
--- a/docs/formatnetwork.html.in
+++ b/docs/formatnetwork.html.in
@@ -886,6 +886,18 @@
         server <span class="since">Since 0.9.3</span>.
 
         <p>
+          The dns element can have an optional <code>enable</code>
+          attribute <span class="since">Since 2.2.0</span>.
+          If <code>enable</code> is "no", then no DNS server will be
+          setup by libvirt for this network (and any other
+          configuration in <code><dns></code> will be ignored).
+          If <code>enable</code> is "yes" or unspecified (including
+          the complete absence of any <code><dns></code>
+          element) then a DNS server will be setup by libvirt to
+          listen on all IP addresses specified in the network's
+          configuration.
+        </p>
+        <p>
           The dns element
           can have an optional <code>forwardPlainNames</code>
           attribute <span class="since">Since 1.1.2</span>.
diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng
index 621f16e..12d4b34 100644
--- a/docs/schemas/network.rng
+++ b/docs/schemas/network.rng
@@ -248,6 +248,11 @@
         <optional>
           <element name="dns">
             <optional>
+              <attribute name="enable">
+                <ref name="virYesNo"/>
+              </attribute>
+            </optional>
+            <optional>
               <attribute name="forwardPlainNames">
                 <ref name="virYesNo"/>
               </attribute>
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c
index 6820bde..490574f 100644
--- a/src/conf/network_conf.c
+++ b/src/conf/network_conf.c
@@ -1335,6 +1335,7 @@ virNetworkDNSDefParseXML(const char *networkName,
     xmlNodePtr *txtNodes = NULL;
     xmlNodePtr *fwdNodes = NULL;
     char *forwardPlainNames = NULL;
+    char *enable = NULL;
     int nhosts, nsrvs, ntxts, nfwds;
     size_t i;
     int ret = -1;
@@ -1342,6 +1343,18 @@ virNetworkDNSDefParseXML(const char *networkName,
 
     ctxt->node = node;
 
+    enable = virXPathString("string(./@enable)", ctxt);
+    if (enable) {
+        def->enable = virTristateBoolTypeFromString(enable);
+        if (def->enable <= 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Invalid dns enable setting '%s' "
+                             "in network '%s'"),
+                           enable, networkName);
+            goto cleanup;
+        }
+    }
+
     forwardPlainNames = virXPathString("string(./@forwardPlainNames)", ctxt);
     if (forwardPlainNames) {
         def->forwardPlainNames = virTristateBoolTypeFromString(forwardPlainNames);
@@ -1440,6 +1453,7 @@ virNetworkDNSDefParseXML(const char *networkName,
 
     ret = 0;
  cleanup:
+    VIR_FREE(enable);
     VIR_FREE(forwardPlainNames);
     VIR_FREE(fwdNodes);
     VIR_FREE(hostNodes);
@@ -2496,12 +2510,22 @@ virNetworkDNSDefFormat(virBufferPtr buf,
 {
     size_t i, j;
 
-    if (!(def->forwardPlainNames || def->nfwds || def->nhosts ||
+    if (!(def->enable || def->forwardPlainNames || def->nfwds || def->nhosts ||
           def->nsrvs || def->ntxts))
         return 0;
 
     virBufferAddLit(buf, "<dns");
-    /* default to "yes", but don't format it in the XML */
+    if (def->enable) {
+        const char *fwd = virTristateBoolTypeToString(def->enable);
+
+        if (!fwd) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Unknown enable type %d in network"),
+                           def->enable);
+            return -1;
+        }
+        virBufferAsprintf(buf, " enable='%s'", fwd);
+    }
     if (def->forwardPlainNames) {
         const char *fwd = virTristateBoolTypeToString(def->forwardPlainNames);
 
@@ -2512,10 +2536,10 @@ virNetworkDNSDefFormat(virBufferPtr buf,
             return -1;
         }
         virBufferAsprintf(buf, " forwardPlainNames='%s'", fwd);
-        if (!(def->nfwds || def->nhosts || def->nsrvs || def->ntxts)) {
-            virBufferAddLit(buf, "/>\n");
-            return 0;
-        }
+    }
+    if (!(def->nfwds || def->nhosts || def->nsrvs || def->ntxts)) {
+        virBufferAddLit(buf, "/>\n");
+        return 0;
     }
 
     virBufferAddLit(buf, ">\n");
diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h
index 1ce4257..9ebd4a7 100644
--- a/src/conf/network_conf.h
+++ b/src/conf/network_conf.h
@@ -128,6 +128,7 @@ struct _virNetworkDNSHostDef {
 typedef struct _virNetworkDNSDef virNetworkDNSDef;
 typedef virNetworkDNSDef *virNetworkDNSDefPtr;
 struct _virNetworkDNSDef {
+    int enable;            /* enum virTristateBool */
     int forwardPlainNames; /* enum virTristateBool */
     size_t ntxts;
     virNetworkDNSTxtDefPtr txts;
diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
index 23036e8..49c0a2f 100644
--- a/src/network/bridge_driver.c
+++ b/src/network/bridge_driver.c
@@ -916,6 +916,7 @@ networkDnsmasqConfContents(virNetworkObjPtr network,
     int nbleases = 0;
     size_t i;
     virNetworkDNSDefPtr dns = &network->def->dns;
+    bool wantDNS = dns->enable != VIR_TRISTATE_BOOL_NO;
     virNetworkIPDefPtr tmpipdef, ipdef, ipv4def, ipv6def;
     bool ipv6SLAAC;
     char *saddr = NULL, *eaddr = NULL;
@@ -948,7 +949,13 @@ networkDnsmasqConfContents(virNetworkObjPtr network,
                       "strict-order\n",
                       network->def->name);
 
-    if (network->def->dns.forwarders) {
+    /* if dns is disabled, set its listening port to 0, which
+     * tells dnsmasq to not listen
+     */
+    if (!wantDNS)
+        virBufferAddLit(&configbuf, "port=0\n");
+
+    if (wantDNS && network->def->dns.forwarders) {
         virBufferAddLit(&configbuf, "no-resolv\n");
         for (i = 0; i < network->def->dns.nfwds; i++) {
             virBufferAsprintf(&configbuf, "server=%s\n",
@@ -968,7 +975,7 @@ networkDnsmasqConfContents(virNetworkObjPtr network,
                           network->def->domain);
     }
 
-    if (network->def->dns.forwardPlainNames == VIR_TRISTATE_BOOL_NO) {
+    if (wantDNS && network->def->dns.forwardPlainNames == VIR_TRISTATE_BOOL_NO) {
         virBufferAddLit(&configbuf, "domain-needed\n");
         /* need to specify local=// whether or not a domain is
          * specified, unless the config says we should forward "plain"
@@ -1061,64 +1068,66 @@ networkDnsmasqConfContents(virNetworkObjPtr network,
         }
     }
 
-    for (i = 0; i < dns->ntxts; i++) {
-        virBufferAsprintf(&configbuf, "txt-record=%s,%s\n",
-                          dns->txts[i].name,
-                          dns->txts[i].value);
-    }
-
-    for (i = 0; i < dns->nsrvs; i++) {
-        /* service/protocol are required, and should have been validated
-         * by the parser.
-         */
-        if (!dns->srvs[i].service) {
-            virReportError(VIR_ERR_INTERNAL_ERROR,
-                           _("Missing required 'service' "
-                             "attribute in SRV record of network '%s'"),
-                           network->def->name);
-            goto cleanup;
+    if (wantDNS) {
+        for (i = 0; i < dns->ntxts; i++) {
+            virBufferAsprintf(&configbuf, "txt-record=%s,%s\n",
+                              dns->txts[i].name,
+                              dns->txts[i].value);
         }
-        if (!dns->srvs[i].protocol) {
-            virReportError(VIR_ERR_INTERNAL_ERROR,
-                           _("Missing required 'service' "
-                             "attribute in SRV record of network '%s'"),
-                           network->def->name);
-            goto cleanup;
-        }
-        /* RFC2782 requires that service and protocol be preceded by
-         * an underscore.
-         */
-        virBufferAsprintf(&configbuf, "srv-host=_%s._%s",
-                          dns->srvs[i].service, dns->srvs[i].protocol);
 
-        /* domain is optional - it defaults to the domain of this network */
-        if (dns->srvs[i].domain)
-            virBufferAsprintf(&configbuf, ".%s", dns->srvs[i].domain);
+        for (i = 0; i < dns->nsrvs; i++) {
+            /* service/protocol are required, and should have been validated
+             * by the parser.
+             */
+            if (!dns->srvs[i].service) {
+                virReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("Missing required 'service' "
+                                 "attribute in SRV record of network '%s'"),
+                               network->def->name);
+                goto cleanup;
+            }
+            if (!dns->srvs[i].protocol) {
+                virReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("Missing required 'service' "
+                                 "attribute in SRV record of network '%s'"),
+                               network->def->name);
+                goto cleanup;
+            }
+            /* RFC2782 requires that service and protocol be preceded by
+             * an underscore.
+             */
+            virBufferAsprintf(&configbuf, "srv-host=_%s._%s",
+                              dns->srvs[i].service, dns->srvs[i].protocol);
 
-        /* If target is empty or ".", that means "the service is
-         * decidedly not available at this domain" (RFC2782). In that
-         * case, any port, priority, or weight is irrelevant.
-         */
-        if (dns->srvs[i].target && STRNEQ(dns->srvs[i].target, ".")) {
-
-            virBufferAsprintf(&configbuf, ",%s", dns->srvs[i].target);
-            /* port, priority, and weight are optional, but are
-             * identified by their position in the line. If an item is
-             * unspecified, but something later in the line *is*
-             * specified, we need to give the default value for the
-             * unspecified item. (According to the dnsmasq manpage,
-             * the default for port is 1).
+            /* domain is optional - it defaults to the domain of this network */
+            if (dns->srvs[i].domain)
+                virBufferAsprintf(&configbuf, ".%s", dns->srvs[i].domain);
+
+            /* If target is empty or ".", that means "the service is
+             * decidedly not available at this domain" (RFC2782). In that
+             * case, any port, priority, or weight is irrelevant.
              */
-            if (dns->srvs[i].port ||
-                dns->srvs[i].priority || dns->srvs[i].weight)
-                virBufferAsprintf(&configbuf, ",%d",
-                                  dns->srvs[i].port ? dns->srvs[i].port : 1);
-            if (dns->srvs[i].priority || dns->srvs[i].weight)
-                virBufferAsprintf(&configbuf, ",%d", dns->srvs[i].priority);
-            if (dns->srvs[i].weight)
-                virBufferAsprintf(&configbuf, ",%d", dns->srvs[i].weight);
+            if (dns->srvs[i].target && STRNEQ(dns->srvs[i].target, ".")) {
+
+                virBufferAsprintf(&configbuf, ",%s", dns->srvs[i].target);
+                /* port, priority, and weight are optional, but are
+                 * identified by their position in the line. If an item is
+                 * unspecified, but something later in the line *is*
+                 * specified, we need to give the default value for the
+                 * unspecified item. (According to the dnsmasq manpage,
+                 * the default for port is 1).
+                 */
+                if (dns->srvs[i].port ||
+                    dns->srvs[i].priority || dns->srvs[i].weight)
+                    virBufferAsprintf(&configbuf, ",%d",
+                                      dns->srvs[i].port ? dns->srvs[i].port : 1);
+                if (dns->srvs[i].priority || dns->srvs[i].weight)
+                    virBufferAsprintf(&configbuf, ",%d", dns->srvs[i].priority);
+                if (dns->srvs[i].weight)
+                    virBufferAsprintf(&configbuf, ",%d", dns->srvs[i].weight);
+            }
+            virBufferAddLit(&configbuf, "\n");
         }
-        virBufferAddLit(&configbuf, "\n");
     }
 
     /* Find the first dhcp for both IPv4 and IPv6 */
@@ -1198,7 +1207,7 @@ networkDnsmasqConfContents(virNetworkObjPtr network,
             virBufferAsprintf(&configbuf, "dhcp-range=%s,%s",
                               saddr, eaddr);
             if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6))
-               virBufferAsprintf(&configbuf, ",%d", prefix);
+                virBufferAsprintf(&configbuf, ",%d", prefix);
             virBufferAddLit(&configbuf, "\n");
 
             VIR_FREE(saddr);
@@ -1225,7 +1234,7 @@ networkDnsmasqConfContents(virNetworkObjPtr network,
             virBufferAsprintf(&configbuf, "dhcp-range=%s,static",
                               bridgeaddr);
             if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6))
-               virBufferAsprintf(&configbuf, ",%d", prefix);
+                virBufferAsprintf(&configbuf, ",%d", prefix);
             virBufferAddLit(&configbuf, "\n");
             VIR_FREE(bridgeaddr);
         }
@@ -1278,8 +1287,10 @@ networkDnsmasqConfContents(virNetworkObjPtr network,
     /* Likewise, always create this file and put it on the
      * commandline, to allow for runtime additions.
      */
-    virBufferAsprintf(&configbuf, "addn-hosts=%s\n",
-                      dctx->addnhostsfile->path);
+    if (wantDNS) {
+        virBufferAsprintf(&configbuf, "addn-hosts=%s\n",
+                          dctx->addnhostsfile->path);
+    }
 
     /* Are we doing RA instead of radvd? */
     if (DNSMASQ_RA_SUPPORT(caps)) {
@@ -1375,17 +1386,32 @@ static int
 networkStartDhcpDaemon(virNetworkDriverStatePtr driver,
                        virNetworkObjPtr network)
 {
+    virNetworkIPDefPtr ipdef;
+    size_t i;
+    bool needDnsmasq = false;
     virCommandPtr cmd = NULL;
     char *pidfile = NULL;
     int ret = -1;
     dnsmasqContext *dctx = NULL;
 
-    if (!virNetworkDefGetIPByIndex(network->def, AF_UNSPEC, 0)) {
+    if (!(ipdef = virNetworkDefGetIPByIndex(network->def, AF_UNSPEC, 0))) {
         /* no IP addresses, so we don't need to run */
         ret = 0;
         goto cleanup;
     }
 
+    /* see if there are any IP addresses that need a dhcp server */
+    for (i = 0; ipdef && !needDnsmasq;
+         ipdef = virNetworkDefGetIPByIndex(network->def, AF_UNSPEC, i + 1)) {
+        if (ipdef->nranges || ipdef->nhosts)
+            needDnsmasq = true;
+    }
+
+    if (!needDnsmasq && network->def->dns.enable == VIR_TRISTATE_BOOL_NO) {
+        ret = 0;
+        goto cleanup;
+    }
+
     if (virFileMakePath(driver->pidDir) < 0) {
         virReportSystemError(errno,
                              _("cannot create directory %s"),
diff --git a/tests/networkxml2confdata/routed-network-no-dns.conf b/tests/networkxml2confdata/routed-network-no-dns.conf
new file mode 100644
index 0000000..83cc85e
--- /dev/null
+++ b/tests/networkxml2confdata/routed-network-no-dns.conf
@@ -0,0 +1,11 @@
+##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 local
+## or other application using the libvirt API.
+##
+## dnsmasq conf file created by libvirt
+strict-order
+port=0
+except-interface=lo
+bind-dynamic
+interface=virbr1
diff --git a/tests/networkxml2confdata/routed-network-no-dns.xml b/tests/networkxml2confdata/routed-network-no-dns.xml
new file mode 100644
index 0000000..70d0417
--- /dev/null
+++ b/tests/networkxml2confdata/routed-network-no-dns.xml
@@ -0,0 +1,10 @@
+<network>
+  <name>local</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <bridge name="virbr1"/>
+  <mac address='12:34:56:78:9A:BC'/>
+  <forward mode="route" dev="eth1"/>
+  <dns enable='no'/>
+  <ip address="192.168.122.1" netmask="255.255.255.0">
+  </ip>
+</network>
diff --git a/tests/networkxml2conftest.c b/tests/networkxml2conftest.c
index 77acc53..5c71c79 100644
--- a/tests/networkxml2conftest.c
+++ b/tests/networkxml2conftest.c
@@ -117,6 +117,7 @@ mymain(void)
     DO_TEST("nat-network-dns-srv-record-minimal", restricted);
     DO_TEST("nat-network-name-with-quotes", restricted);
     DO_TEST("routed-network", full);
+    DO_TEST("routed-network-no-dns", full);
     DO_TEST("open-network", full);
     DO_TEST("nat-network", dhcpv6);
     DO_TEST("nat-network-dns-txt-record", full);
diff --git a/tests/networkxml2xmlin/routed-network-no-dns.xml b/tests/networkxml2xmlin/routed-network-no-dns.xml
new file mode 100644
index 0000000..70d0417
--- /dev/null
+++ b/tests/networkxml2xmlin/routed-network-no-dns.xml
@@ -0,0 +1,10 @@
+<network>
+  <name>local</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <bridge name="virbr1"/>
+  <mac address='12:34:56:78:9A:BC'/>
+  <forward mode="route" dev="eth1"/>
+  <dns enable='no'/>
+  <ip address="192.168.122.1" netmask="255.255.255.0">
+  </ip>
+</network>
diff --git a/tests/networkxml2xmlout/routed-network-no-dns.xml b/tests/networkxml2xmlout/routed-network-no-dns.xml
new file mode 100644
index 0000000..f68ce8a
--- /dev/null
+++ b/tests/networkxml2xmlout/routed-network-no-dns.xml
@@ -0,0 +1,12 @@
+<network>
+  <name>local</name>
+  <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
+  <forward dev='eth1' mode='route'>
+    <interface dev='eth1'/>
+  </forward>
+  <bridge name='virbr1' stp='on' delay='0'/>
+  <mac address='12:34:56:78:9a:bc'/>
+  <dns enable='no'/>
+  <ip address='192.168.122.1' netmask='255.255.255.0'>
+  </ip>
+</network>
diff --git a/tests/networkxml2xmltest.c b/tests/networkxml2xmltest.c
index 32544d0..b17674e 100644
--- a/tests/networkxml2xmltest.c
+++ b/tests/networkxml2xmltest.c
@@ -127,6 +127,7 @@ mymain(void)
     DO_TEST("empty-allow-ipv6");
     DO_TEST("isolated-network");
     DO_TEST("routed-network");
+    DO_TEST("routed-network-no-dns");
     DO_TEST("open-network");
     DO_TEST_PARSE_ERROR("open-network-with-forward-dev");
     DO_TEST("nat-network");
-- 
2.7.4




More information about the libvir-list mailing list