network: allow disabling dnsmasq's DNS server

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).
This commit is contained in:
Laine Stump 2016-08-11 17:29:43 -04:00
parent 25e8112d7c
commit 9065cfaa88
12 changed files with 201 additions and 67 deletions

View File

@ -885,6 +885,18 @@
information for the virtual network's DNS
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>&lt;dns&gt;</code> will be ignored).
If <code>enable</code> is "yes" or unspecified (including
the complete absence of any <code>&lt;dns&gt;</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>

View File

@ -247,6 +247,11 @@
and other features in the <dns> element -->
<optional>
<element name="dns">
<optional>
<attribute name="enable">
<ref name="virYesNo"/>
</attribute>
</optional>
<optional>
<attribute name="forwardPlainNames">
<ref name="virYesNo"/>

View File

@ -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);
@ -1438,8 +1451,17 @@ virNetworkDNSDefParseXML(const char *networkName,
}
}
if (def->enable == VIR_TRISTATE_BOOL_NO &&
(nfwds || nhosts || nsrvs || ntxts)) {
virReportError(VIR_ERR_XML_ERROR,
_("Extra data in disabled network '%s'"),
networkName);
goto cleanup;
}
ret = 0;
cleanup:
VIR_FREE(enable);
VIR_FREE(forwardPlainNames);
VIR_FREE(fwdNodes);
VIR_FREE(hostNodes);
@ -2496,12 +2518,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 +2544,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");

View File

@ -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;

View File

@ -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);
/* 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).
for (i = 0; i < dns->nsrvs; i++) {
/* service/protocol are required, and should have been validated
* by the parser.
*/
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].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);
/* 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].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"),

View File

@ -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

View File

@ -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>

View File

@ -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);

View File

@ -0,0 +1,12 @@
<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'>
<txt name='example' value='example value'/>
</dns>
<ip address="192.168.122.1" netmask="255.255.255.0">
</ip>
</network>

View File

@ -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>

View File

@ -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>

View File

@ -127,6 +127,8 @@ mymain(void)
DO_TEST("empty-allow-ipv6");
DO_TEST("isolated-network");
DO_TEST("routed-network");
DO_TEST("routed-network-no-dns");
DO_TEST_PARSE_ERROR("routed-network-no-dns-extra-elements");
DO_TEST("open-network");
DO_TEST_PARSE_ERROR("open-network-with-forward-dev");
DO_TEST("nat-network");