Reverse DNS record creation fails to take into account aliasing and zone delegations. It fails to follow DNAME and CNAME to find the final PTR name to update.
As an example, the current implementation resolves 172.16.230.161 to 161.230.16.172.in-addr.arpa. and will look for the zone 230.16.172.in-addr.arpa. no matter what.
But our provider is routing only 172.16.230.160/27 to us, and delegates 160-27.230.16.172.in-addr.arpa. to our nameservers and hosts CNAME under his zone 230.16.172.in-addr.arpa. pointing to ours. In this scenario, IPA fails to find out the correct PTR record to be 161.160-27.230.16.172.in-addr.arpa. pointed to by a CNAME 161.230.16.172.in-addr.arpa elsewhere.
To cover real usecases the procedure is more involved: compute the canonical reverse lookup name, then query DNS for any such CNAME. If the response, even with NXDOMAIN set, carries a CNAME, then substitute the canonical reverse lookup name using the CNAME target.
To implement this, ipalib should do the DNS query in ipalib.plugins.dns.get_reverse_zone. Unfortunately dns.resolver.Resolver.query() used by ipalib eats the answer when it encounters NXDOMAIN. I propose to alter dns.resolver.Resolver.query() to add the response to the exception so that ipalib can inspect it.
I've implemented this by subclassing dns.resolver.Resolver in ipalib/plugins/dns.py with the second patch below, and the following patch on get_reverse_zone:
--- dns.py.orig 2015-05-24 00:41:20.861545139 +0200 +++ dns.py.1 2015-05-24 00:57:14.094508551 +0200 @@ -521,7 +521,32 @@ def get_reverse_zone(ipaddr, prefixlen=None): ip = netaddr.IPAddress(str(ipaddr)) - revdns = DNSName(unicode(ip.reverse_dns)) + revdns_name = unicode(ip.reverse_dns) + revdns = DNSName(revdns_name) + + resolver = Resolver() + rdtype = _CNAME = dns.rdatatype.from_text('CNAME') + try: + dns_rrset = None + try: + dns_answer = resolver.query(revdns_name, rdtype, + dns.rdataclass.IN, + raise_on_no_answer=False) + dns_rrset = dns_answer.rrset + except dns.resolver.NXDOMAIN as e_nx: + if e_nx.message is None or not len(e_nx.message.answer): + raise + dns_rrset = e_nx.message.answer + for rrset in dns_rrset: + if rrset.rdtype == _CNAME: + for rrset_item in rrset.items: + revdns = DNSName(unicode(rrset_item.target)) + prefixlen = None + except (dns.resolver.NXDOMAIN, + dns.resolver.YXDOMAIN, + dns.resolver.NoNameservers, + dns.resolver.Timeout) as e: + pass if prefixlen is None: revzone = None }}} And this is the patch for dnspython: --- resolver.py 2014-07-24 17:11:36.000000000 +0200 +++ resolver.py.orig 2015-05-24 01:02:02.328497488 +0200 @@ -902,12 +902,11 @@ sleep_time = min(timeout, backoff) backoff *= 2 time.sleep(sleep_time) - if response.rcode() == dns.rcode.NXDOMAIN: - continue - all_nxdomain = False + if response.rcode() != dns.rcode.NXDOMAIN: + all_nxdomain = False break if all_nxdomain: - raise NXDOMAIN + raise NXDOMAIN(response) answer = Answer(qname, rdtype, rdclass, response, raise_on_no_answer) if self.cache:
--- dns.py.orig 2015-05-24 00:41:20.861545139 +0200 +++ dns.py.1 2015-05-24 00:57:14.094508551 +0200 @@ -521,7 +521,32 @@ def get_reverse_zone(ipaddr, prefixlen=None): ip = netaddr.IPAddress(str(ipaddr)) - revdns = DNSName(unicode(ip.reverse_dns)) + revdns_name = unicode(ip.reverse_dns) + revdns = DNSName(revdns_name) + + resolver = Resolver() + rdtype = _CNAME = dns.rdatatype.from_text('CNAME') + try: + try: + dns_answer = resolver.query(revdns_name, rdtype, + dns.rdataclass.IN, + raise_on_no_answer=False) + dns_rrset = (dns_answer.rrset,) + except dns.resolver.NXDOMAIN as e_nx: + if e_nx.message is None or not len(e_nx.message.answer): + raise + dns_rrset = e_nx.message.answer + for rrset in dns_rrset: + if rrset.rdtype == _CNAME: + for rrset_item in rrset.items: + revdns = DNSName(unicode(rrset_item.target)) + prefixlen = None + except (dns.resolver.NXDOMAIN, + dns.resolver.YXDOMAIN, + dns.resolver.NoNameservers, + dns.resolver.Timeout) as e: + pass if prefixlen is None: revzone = None
Hello!
Thank you for yor feedback. I can see that you are touching python-dns/dnspython library. This library is not controlled by FreeIPA project - please contact dnspython upstream here: https://github.com/rthalley/dnspython/issues
python-dns
dnspython
Feel free to post link here so we can want the progress and consider further integration. Thank you!
There we go: https://github.com/rthalley/dnspython/issues/100
Steps to reproduce: 1. Create reverse zone 2.0.192.in-addr.arpa. 2. Create CNAME record 1 in zone 2.0.192.in-addr.arpa. pointing to target.test. 3. Attempt to use option --a-create-reverse while adding A record with value 192.0.2.1 to some other zone.
2.0.192.in-addr.arpa.
1
target.test.
--a-create-reverse
A
192.0.2.1
IPA should detect that 1.2.0.192.in-addr.arpa. is an alias and update the target name if it is under IPA's control.
1.2.0.192.in-addr.arpa.
Metadata Update from @clauluck: - Issue assigned to someone - Issue set to the milestone: FreeIPA 4.5 backlog
Log in to comment on this ticket.