[libvirt] [PATCH v2 5/5] network: Add support for local PTR domains

Laine Stump laine at laine.org
Thu Dec 15 18:55:26 UTC 2016


On 12/13/2016 08:52 AM, Jiri Denemark wrote:
> Similarly to localOnly DNS domain, local PTR domains can be used to tell
> the DNS server not to forward reverse lookups for unknown IPs which
> belong to the virtual network.

What's here is useful, but the <ptr> element doesn't fit with the 
purpose of a PTR record in an actual DNS server. We should implement 
<ptr> according to RFC 1035 so that it can be used to fill in a 
"ptr-record" in dnsmasq.conf. An example from there is:

     10.IN-ADDR.ARPA.           PTR MILNET-GW.ISI.EDU.

(note the terminating "."s - those are *very* important!)

To support that our XML syntax would at least need two attributes, but I 
think it's the *2nd* one that should more properly called "domain". I'm 
trying to find a reference to a good name for the 1st attribute, but 
it's been about 15 years since I configured bind, and I can't seem to 
concentrate long enough to follow the links in google. Anyway, it would 
need to be something like this:

     <ptr name='10.IN-ADDR.ARPA." domain="MILNET-GW.ISI.EDU."/>

(I think in the RFC, the first is the RDATA, and the 2nd is the 
PTRDNAME/"domain name pointer"/domain-name).

I *think* this directly translates to:

     ptr-record=10.IN-ADDR.ARPA.,MILNET-GW.ISI.EDU.

in dnsmasq.conf, but I'm completely extrapolating from examples I've 
found, and haven't tested it, so I may be totally wrong.

(you'll notice that the item you've called "domain" is *not* what I 
called "domain" in my suggested syntax)

A subset of that could be a <ptr> that has a "name" (or whatever we call 
it) but no domain, and that might have the effect of making that 
particular range local-only (so it would fail if it couldn't be 
resolved.). We should make sure there is no other valid use of a PTR 
with no domain though.

Note that it should already be possible to forward PTR requests for 
particular ranges to an external server (i.e. the opposite of 
"localPtr") by adding a "<forwarder domain='0.10.in-addr.arpa.' 
addr='8.8.8.8'/>".

In the meantime, could you separate the parts of this patch that 
implement "localPtr='yes'" from the <ptr> parts? I think that can be 
pushed as it is so it's not held up by disscussion of the full <ptr>.

> Signed-off-by: Jiri Denemark <jdenemar at redhat.com>
> ---
>   docs/formatnetwork.html.in                      | 37 ++++++++++---
>   docs/schemas/basictypes.rng                     |  6 +++
>   docs/schemas/network.rng                        |  8 +++
>   src/conf/network_conf.c                         | 72 ++++++++++++++++++++++++-
>   src/conf/network_conf.h                         |  4 ++
>   src/network/bridge_driver.c                     | 47 ++++++++++++++++
>   tests/networkxml2confdata/ptr-domains-auto.conf | 20 +++++++
>   tests/networkxml2confdata/ptr-domains-auto.xml  | 21 ++++++++
>   tests/networkxml2confdata/ptr-domains.conf      | 24 +++++++++
>   tests/networkxml2confdata/ptr-domains.xml       | 24 +++++++++
>   tests/networkxml2conftest.c                     |  2 +
>   11 files changed, 258 insertions(+), 7 deletions(-)
>   create mode 100644 tests/networkxml2confdata/ptr-domains-auto.conf
>   create mode 100644 tests/networkxml2confdata/ptr-domains-auto.xml
>   create mode 100644 tests/networkxml2confdata/ptr-domains.conf
>   create mode 100644 tests/networkxml2confdata/ptr-domains.xml
>
> diff --git a/docs/formatnetwork.html.in b/docs/formatnetwork.html.in
> index 9cf940052..3c9414779 100644
> --- a/docs/formatnetwork.html.in
> +++ b/docs/formatnetwork.html.in
> @@ -855,7 +855,7 @@
>       <hostname>myhostalias</hostname>
>     </host>
>   </dns>
> -<ip address="192.168.122.1" netmask="255.255.255.0">
> +<ip address="192.168.122.1" netmask="255.255.255.0" localPtr="yes">
>     <dhcp>
>       <range start="192.168.122.100" end="192.168.122.254"/>
>       <host mac="00:16:3e:77:e2:ed" name="foo.example.com" ip="192.168.122.10"/>
> @@ -863,6 +863,10 @@
>     </dhcp>
>   </ip>
>   <ip family="ipv6" address="2001:db8:ca2:2::1" prefix="64"/>
> +<ip family="ipv6" address="fec0::1" prefix="31" localPtr="yes">
> +  <ptr domain="0.0.0.0.0.c.e.f.ip6.arpa"/>
> +  <ptr domain="1.0.0.0.0.c.e.f.ip6.arpa"/>
> +</ip>
>   <route family="ipv6" address="2001:db9:ca1:1::" prefix="64" gateway="2001:db8:ca2:2::2"/>
>   </pre>
>   
> @@ -983,11 +987,20 @@
>           to specify the type of address — <code>ipv4</code> or
>           <code>ipv6</code>; if no <code>family</code> is given,
>           <code>ipv4</code> is assumed. More than one address of each family can
> -        be defined for a network. The <code>ip</code> element is supported
> -        <span class="since">since 0.3.0</span>. IPv6, multiple addresses on a
> -        single network, <code>family</code>, and <code>prefix</code> are
> -        supported <span class="since">since 0.8.7</span>. The <code>ip</code>
> -        element may contain the following elements:
> +        be defined for a network. The optional <code>localPtr</code> attribute
> +        (<span class="since">since 3.0.0</span>) configures the DNS server not
> +        to forward any reverse DNS requests for IP addresses from the network
> +        configured by the <code>address</code> and
> +        <code>netmask</code>/<code>prefix</code> attributes. For some unusual
> +        network prefixes (not divisible by 8 for IPv4 or not divisible by 4 for
> +        IPv6) libvirt may be unable to compute the PTR domain automatically,
> +        in which case the domain has to be specified explicitly by the
> +        <code>ptr</code> element described below. The <code>ip</code> element
> +        is supported <span class="since">since 0.3.0</span>. IPv6, multiple
> +        addresses on a single network, <code>family</code>, and
> +        <code>prefix</code> are supported <span class="since">since
> +        0.8.7</span>. The <code>ip</code> element may contain the following
> +        elements:
>   
>           <dl>
>             <dt><code>tftp</code></dt>
> @@ -1051,6 +1064,18 @@
>                 </dd>
>               </dl>
>             </dd>
> +
> +          <dt><code>ptr</code></dt>
> +          <dd>The <code>domain</code> attribute of the optional
> +            <code>ptr</code> element specifies the PTR domain used for reverse
> +            DNS lookups. The domain has to end with ".in-addr.arpa" for IPv4
> +            and ".ip6.arpa" for IPv6. Each <code>ip</code> element may contain
> +            zero or more <code>ptr</code> elements. When
> +            <code>localPtr='yes'</code> attribute is set in the parent
> +            <code>ip</code> element the DNS server for this network will be
> +            configured not to forward any reverse lookups within the specified
> +            domains. <span class="since">Since 3.0.0</span>
> +          </dd>
>           </dl>
>         </dd>
>       </dl>
> diff --git a/docs/schemas/basictypes.rng b/docs/schemas/basictypes.rng
> index 1b4f980e7..f99c76392 100644
> --- a/docs/schemas/basictypes.rng
> +++ b/docs/schemas/basictypes.rng
> @@ -239,6 +239,12 @@
>       </data>
>     </define>
>   
> +  <define name="ptrName">
> +    <data type="string">
> +      <param name="pattern">(((((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([1-9][0-9])|([0-9]))\.){1,3}in-addr)|((([0-9a-fA-F]\.){1,31})ip6)).arpa</param>
> +    </data>
> +  </define>
> +
>     <define name="deviceName">
>       <data type="string">
>         <param name="pattern">[a-zA-Z0-9_\.\-\\:/]+</param>
> diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng
> index 986119596..1a3705a1f 100644
> --- a/docs/schemas/network.rng
> +++ b/docs/schemas/network.rng
> @@ -339,6 +339,9 @@
>               <optional>
>                 <attribute name="family"><ref name="addr-family"/></attribute>
>               </optional>
> +            <optional>
> +              <attribute name="localPtr"><ref name="virYesNo"/></attribute>
> +            </optional>
>               <interleave>
>                 <optional>
>                   <element name="tftp">
> @@ -384,6 +387,11 @@
>                     </interleave>
>                   </element>
>                 </optional>
> +              <zeroOrMore>
> +                <element name='ptr'>
> +                  <attribute name='domain'><ref name="ptrName"/></attribute>
> +                </element>
> +              </zeroOrMore>
>               </interleave>
>             </element>
>           </zeroOrMore>
> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c
> index b6849ceab..ce0bd0260 100644
> --- a/src/conf/network_conf.c
> +++ b/src/conf/network_conf.c
> @@ -315,6 +315,10 @@ static void
>   virNetworkIPDefClear(virNetworkIPDefPtr def)
>   {
>       VIR_FREE(def->family);
> +
> +    virStringListFreeCount(def->ptrs, def->nptrs);
> +    def->ptrs = NULL;
> +
>       VIR_FREE(def->ranges);
>   
>       while (def->nhosts)
> @@ -1507,6 +1511,10 @@ virNetworkIPDefParseXML(const char *networkName,
>       unsigned long prefix = 0;
>       int prefixRc;
>       int result = -1;
> +    char *localPtr = NULL;
> +    xmlNodePtr *nodes = NULL;
> +    size_t i;
> +    int n;
>   
>       save = ctxt->node;
>       ctxt->node = node;
> @@ -1549,6 +1557,17 @@ virNetworkIPDefParseXML(const char *networkName,
>       else
>           def->prefix = prefix;
>   
> +    localPtr = virXPathString("string(./@localPtr)", ctxt);
> +    if (localPtr) {
> +        def->localPTR = virTristateBoolTypeFromString(localPtr);
> +        if (def->localPTR <= 0) {
> +            virReportError(VIR_ERR_XML_ERROR,
> +                           _("Invalid localPtr value '%s' in network '%s'"),
> +                           localPtr, networkName);
> +            goto cleanup;
> +        }
> +    }
> +
>       /* validate address, etc. for each family */
>       if ((def->family == NULL) || (STREQ(def->family, "ipv4"))) {
>           if (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) ||
> @@ -1604,6 +1623,46 @@ virNetworkIPDefParseXML(const char *networkName,
>           goto cleanup;
>       }
>   
> +    if ((n = virXPathNodeSet("./ptr", ctxt, &nodes)) < 0)
> +        goto cleanup;
> +
> +    if (n > 0) {
> +        if (VIR_ALLOC_N(def->ptrs, n) < 0)
> +            goto cleanup;
> +        def->nptrs = n;
> +    }
> +    for (i = 0; i < n; i++) {
> +        char *domain;
> +        const char *suffix;
> +        size_t len;
> +        size_t suflen;
> +
> +        if (!(domain = virXMLPropString(nodes[i], "domain"))) {
> +            virReportError(VIR_ERR_XML_ERROR,
> +                           _("Missing PTR domain in network '%s'"),
> +                           networkName);
> +            goto cleanup;
> +        }
> +
> +        if (VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET))
> +            suffix = VIR_SOCKET_ADDR_IPV4_ARPA;
> +        else
> +            suffix = VIR_SOCKET_ADDR_IPV6_ARPA;
> +
> +        len = strlen(domain);
> +        suflen = strlen(suffix);
> +        if (len <= suflen ||
> +            STRNEQ(domain + len - suflen, suffix) ||
> +            domain[len - suflen - 1] != '.') {
> +            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
> +                           _("Invalid PTR domain '%s' in network '%s'"),
> +                           domain, networkName);
> +            VIR_FREE(domain);
> +            goto cleanup;
> +        }
> +        def->ptrs[i] = domain;
> +    }
> +
>       if ((dhcp = virXPathNode("./dhcp[1]", ctxt)) &&
>           virNetworkDHCPDefParseXML(networkName, dhcp, def) < 0)
>           goto cleanup;
> @@ -1627,6 +1686,8 @@ virNetworkIPDefParseXML(const char *networkName,
>           virNetworkIPDefClear(def);
>       VIR_FREE(address);
>       VIR_FREE(netmask);
> +    VIR_FREE(localPtr);
> +    VIR_FREE(nodes);
>   
>       ctxt->node = save;
>       return result;
> @@ -2630,6 +2691,7 @@ static int
>   virNetworkIPDefFormat(virBufferPtr buf,
>                         const virNetworkIPDef *def)
>   {
> +    size_t i;
>       int result = -1;
>   
>       virBufferAddLit(buf, "<ip");
> @@ -2652,15 +2714,23 @@ virNetworkIPDefFormat(virBufferPtr buf,
>       }
>       if (def->prefix > 0)
>           virBufferAsprintf(buf, " prefix='%u'", def->prefix);
> +
> +    if (def->localPTR) {
> +        virBufferAsprintf(buf, " localPtr='%s'",
> +                          virTristateBoolTypeToString(def->localPTR));
> +    }
> +
>       virBufferAddLit(buf, ">\n");
>       virBufferAdjustIndent(buf, 2);
>   
> +    for (i = 0; i < def->nptrs; i++)
> +        virBufferAsprintf(buf, "<ptr domain='%s'/>\n", def->ptrs[i]);
> +
>       if (def->tftproot) {
>           virBufferEscapeString(buf, "<tftp root='%s'/>\n",
>                                 def->tftproot);
>       }
>       if ((def->nranges || def->nhosts)) {
> -        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 09e091616..edf1a1abd 100644
> --- a/src/conf/network_conf.h
> +++ b/src/conf/network_conf.h
> @@ -162,6 +162,10 @@ struct _virNetworkIPDef {
>       unsigned int prefix;        /* ipv6 - only prefix allowed */
>       virSocketAddr netmask;      /* ipv4 - either netmask or prefix specified */
>   
> +    int localPTR; /* virTristateBool */
> +    size_t nptrs;
> +    char **ptrs;
> +
>       size_t nranges;             /* Zero or more dhcp ranges */
>       virSocketAddrRangePtr ranges;
>   
> diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
> index ae1589d8c..49753737f 100644
> --- a/src/network/bridge_driver.c
> +++ b/src/network/bridge_driver.c
> @@ -994,6 +994,49 @@ networkBuildDnsmasqHostsList(dnsmasqContext *dctx,
>   }
>   
>   
> +static int
> +networkDnsmasqConfLocalPTRs(virBufferPtr buf,
> +                            virNetworkDefPtr def)
> +{
> +    virNetworkIPDefPtr ip;
> +    size_t i, j;
> +    char *ptr = NULL;
> +    int rc;
> +
> +    for (i = 0; i < def->nips; i++) {
> +        ip = def->ips + i;
> +
> +        if (ip->localPTR != VIR_TRISTATE_BOOL_YES)
> +            continue;
> +
> +        for (j = 0; j < ip->nptrs; j++)
> +            virBufferAsprintf(buf, "local=/%s/\n", ip->ptrs[j]);
> +
> +        if (ip->nptrs > 0)
> +            continue;
> +
> +        if ((rc = virSocketAddrPTRDomain(&ip->address,
> +                                         virNetworkIPDefPrefix(ip),
> +                                         &ptr)) < 0) {
> +            if (rc == -2) {
> +                int family = VIR_SOCKET_ADDR_FAMILY(&ip->address);
> +                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
> +                               _("PTR domain for %s network with prefix %u "
> +                                 "cannot be automatically created"),
> +                               (family == AF_INET) ? "IPv4" : "IPv6",
> +                               virNetworkIPDefPrefix(ip));
> +            }
> +            return -1;
> +        }
> +
> +        virBufferAsprintf(buf, "local=/%s/\n", ptr);
> +        VIR_FREE(ptr);
> +    }
> +
> +    return 0;
> +}
> +
> +
>   int
>   networkDnsmasqConfContents(virNetworkObjPtr network,
>                              const char *pidfile,
> @@ -1079,6 +1122,10 @@ networkDnsmasqConfContents(virNetworkObjPtr network,
>                             network->def->domain);
>       }
>   
> +    if (wantDNS &&
> +        networkDnsmasqConfLocalPTRs(&configbuf, network->def) < 0)
> +        goto cleanup;
> +
>       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
> diff --git a/tests/networkxml2confdata/ptr-domains-auto.conf b/tests/networkxml2confdata/ptr-domains-auto.conf
> new file mode 100644
> index 000000000..7f1a393dd
> --- /dev/null
> +++ b/tests/networkxml2confdata/ptr-domains-auto.conf
> @@ -0,0 +1,20 @@
> +##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
> +local=/122.168.192.in-addr.arpa/
> +local=/1.0.e.f.0.1.c.a.8.b.d.0.1.0.0.2.ip6.arpa/
> +except-interface=lo
> +bind-dynamic
> +interface=virbr0
> +dhcp-range=192.168.122.2,192.168.122.254
> +dhcp-no-override
> +dhcp-authoritative
> +dhcp-lease-max=253
> +dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile
> +addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts
> +dhcp-range=2001:db8:ac10:fe01::1,ra-only
> +dhcp-range=2001:db8:ac10:fd01::1,ra-only
> diff --git a/tests/networkxml2confdata/ptr-domains-auto.xml b/tests/networkxml2confdata/ptr-domains-auto.xml
> new file mode 100644
> index 000000000..7fe12dc67
> --- /dev/null
> +++ b/tests/networkxml2confdata/ptr-domains-auto.xml
> @@ -0,0 +1,21 @@
> +<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' localPtr='yes'>
> +    <dhcp>
> +      <range start='192.168.122.2' end='192.168.122.254'/>
> +      <host mac='00:16:3e:77:e2:ed' name='a.example.com' ip='192.168.122.10'/>
> +      <host mac='00:16:3e:3e:a9:1a' name='b.example.com' ip='192.168.122.11'/>
> +    </dhcp>
> +  </ip>
> +  <ip family='ipv4' address='192.168.123.1' netmask='255.255.255.0' localPtr='no'>
> +  </ip>
> +  <ip family='ipv6' address='2001:db8:ac10:fe01::1' prefix='64' localPtr='yes'>
> +  </ip>
> +  <ip family='ipv6' address='2001:db8:ac10:fd01::1' prefix='64'>
> +  </ip>
> +  <ip family='ipv4' address='10.24.10.1'>
> +  </ip>
> +</network>
> diff --git a/tests/networkxml2confdata/ptr-domains.conf b/tests/networkxml2confdata/ptr-domains.conf
> new file mode 100644
> index 000000000..2900eebce
> --- /dev/null
> +++ b/tests/networkxml2confdata/ptr-domains.conf
> @@ -0,0 +1,24 @@
> +##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 test
> +## or other application using the libvirt API.
> +##
> +## dnsmasq conf file created by libvirt
> +strict-order
> +local=/30.20.10.in-addr.arpa/
> +local=/31.20.10.in-addr.arpa/
> +local=/2.0.0.0.0.c.e.f.ip6.arpa/
> +local=/0.0.0.0.0.c.e.f.ip6.arpa/
> +local=/1.0.0.0.0.c.e.f.ip6.arpa/
> +local=/1.0.f.e.d.c.b.a.4.0.0.0.0.c.e.f.ip6.arpa/
> +except-interface=lo
> +bind-dynamic
> +interface=virbr2
> +dhcp-range=10.20.30.1,static
> +dhcp-no-override
> +dhcp-authoritative
> +dhcp-hostsfile=/var/lib/libvirt/dnsmasq/test.hostsfile
> +addn-hosts=/var/lib/libvirt/dnsmasq/test.addnhosts
> +dhcp-range=fec0:2::1,ra-only
> +dhcp-range=fec0::1,ra-only
> +dhcp-range=fec0:4:abcd:ef01::1,ra-only
> diff --git a/tests/networkxml2confdata/ptr-domains.xml b/tests/networkxml2confdata/ptr-domains.xml
> new file mode 100644
> index 000000000..64a329676
> --- /dev/null
> +++ b/tests/networkxml2confdata/ptr-domains.xml
> @@ -0,0 +1,24 @@
> +<network>
> +  <name>test</name>
> +  <uuid>cafecafe-cafe-cafe-cafe-cafecafecafe</uuid>
> +  <forward mode='nat'/>
> +  <bridge name='virbr2' stp='on' delay='0'/>
> +  <mac address='52:54:00:eb:c2:e8'/>
> +  <ip address='10.20.30.1' prefix='23' localPtr='yes'>
> +    <ptr domain='30.20.10.in-addr.arpa'/>
> +    <ptr domain='31.20.10.in-addr.arpa'/>
> +    <dhcp>
> +      <host mac='ca:fe:ca:fe:ca:fe' name='ble' ip='10.20.30.2'/>
> +      <host mac='ca:fe:ca:fe:ca:fe' name='bla' ip='10.20.30.3'/>
> +      <host mac='52:54:00:16:c6:1a' name='pxe' ip='10.20.30.4'/>
> +    </dhcp>
> +  </ip>
> +  <ip family='ipv6' address='fec0:2::1' prefix='32' localPtr='yes'>
> +  </ip>
> +  <ip family='ipv6' address='fec0::1' prefix='31' localPtr='yes'>
> +    <ptr domain='0.0.0.0.0.c.e.f.ip6.arpa'/>
> +    <ptr domain='1.0.0.0.0.c.e.f.ip6.arpa'/>
> +  </ip>
> +  <ip family='ipv6' address='fec0:4:abcd:ef01::1' prefix='64' localPtr='yes'>
> +  </ip>
> +</network>
> diff --git a/tests/networkxml2conftest.c b/tests/networkxml2conftest.c
> index 65a0e3218..b6c967c45 100644
> --- a/tests/networkxml2conftest.c
> +++ b/tests/networkxml2conftest.c
> @@ -129,6 +129,8 @@ mymain(void)
>       DO_TEST("dhcp6-network", dhcpv6);
>       DO_TEST("dhcp6-nat-network", dhcpv6);
>       DO_TEST("dhcp6host-routed-network", dhcpv6);
> +    DO_TEST("ptr-domains", dhcpv6);
> +    DO_TEST("ptr-domains-auto", dhcpv6);
>   
>       virObjectUnref(dhcpv6);
>       virObjectUnref(full);





More information about the libvir-list mailing list