ascension

Migrate DNS zones to the GNU Name System
Log | Files | Refs | README | LICENSE

commit d63e299e150ad557300a404e604fc922bf1ebb7f
parent 5c5592c90668fa30046e8255ad9ad10ef44bf27f
Author: rexxnor <rexxnor+gnunet@brief.li>
Date:   Wed,  1 May 2019 16:45:55 +0200

fixed bugs due to refactoring, changed classmethods to regular ones

Diffstat:
Mascension/ascension.py | 248+++++++++++++++++++++++++++++--------------------------------------------------
1 file changed, 92 insertions(+), 156 deletions(-)

diff --git a/ascension/ascension.py b/ascension/ascension.py @@ -82,43 +82,33 @@ class Ascender(): """ Class that provides migration for any given domain """ - @classmethod - def __init__(cls, domain, transferns, port, flags, minimum): - cls.domain = domain + def __init__(self, domain, transferns, port, flags, minimum): + self.domain = domain if domain[-1] == '.': - cls.domain = cls.domain[:-1] - cls.port = int(port) - cls.transferns = transferns - cls.soa = None - cls.tld = cls.domain.split(".")[::-1][0] - cls.zone = None - cls.zonegenerator = None - cls.nscache = dict() - cls.flags = flags - cls.minimum = int(minimum) - cls.subzonedict = dict() - - @classmethod - def initial_zone_transfer(cls, serial=None): - """ - Initialize the zone transfer generator - :param serial: The serial to base the transfer on - """ - - @classmethod - def bootstrap_zone(cls): + self.domain = self.domain[:-1] + self.port = int(port) + self.transferns = transferns + self.soa = None + self.tld = self.domain.split(".")[::-1][0] + self.zone = None + self.zonegenerator = None + self.nscache = dict() + self.flags = flags + self.minimum = int(minimum) + self.subzonedict = dict() + + def bootstrap_zone(self): """ Creates the zone in gnunet """ try: ret = sp.run([GNUNET_ZONE_CREATION_COMMAND, - '-C', cls.domain]) + '-C', self.domain]) logging.info("executed command: %s", " ".join(ret.args)) except sp.CalledProcessError: - logging.info("Zone %s already exists!", cls.domain) + logging.info("Zone %s already exists!", self.domain) - @classmethod - def get_dns_zone_serial(cls, domain, resolver=None): + def get_dns_zone_serial(self, domain, resolver=None): """ Gets the current serial for a given zone :param domain: Domain to query for in DNS @@ -131,7 +121,7 @@ class Ascender(): # compared to AXFR/IXFR - changed to respect this try: soa_answer = dns.resolver.query(domain, 'SOA') - master_answer = myresolver.query(soa_answer[0].mname, 'A') + master_answer = dns.resolver.query(soa_answer[0].mname, 'A') except dns.resolver.NoAnswer: logging.warning("The domain '%s' is not publicly resolvable.", domain) @@ -141,11 +131,11 @@ class Ascender(): try: if resolver: zone = dns.zone.from_xfr(dns.query.xfr( - resolver, domain, port=cls.port)) + resolver, domain, port=self.port)) else: zone = dns.zone.from_xfr(dns.query.xfr( master_answer[0].address, domain, - port=cls.port)) + port=self.port)) except dns.resolver.NoAnswer: logging.error("Nameserver for '%s' did not answer.", domain) except dns.exception.FormError: @@ -156,17 +146,15 @@ class Ascender(): return None for soa_record in zone.iterate_rdatas(rdtype=dns.rdatatype.SOA): - if not cls.transferns: + if not self.transferns: mname = soa_record[2].mname - if cls.domain not in mname: - cls.transferns = str(soa_record[2].mname) + "." + domain + if self.domain not in mname: + self.transferns = str(soa_record[2].mname) + "." + domain else: - cls.transferns = str(soa_record[2].mname) + self.transferns = str(soa_record[2].mname) return soa_record[2].serial - - @classmethod - def add_records_to_gns(cls): + def add_records_to_gns(self): """ Extracts records from zone and adds them to GNS :raises AttributeError: When getting incomplete data @@ -192,18 +180,17 @@ class Ascender(): # execute thing to run on item label, listofrdatasets = labelrecords subzones = label.split('.') - domain = cls.domain + domain = self.domain if len(subzones) > 1: - ttl = cls.get_zone_refresh_time() label = subzones[0] subdomains = ".".join(subzones[1:]) subzone = "%s.%s" % (subdomains, domain) fqdn = "%s.%s.%s" % (label, subdomains, domain) - if fqdn in cls.subzonedict.keys(): + if fqdn in self.subzonedict.keys(): label = "@" domain = fqdn - elif subzone in cls.subzonedict.keys(): + elif subzone in self.subzonedict.keys(): domain = subzone for rdataset in listofrdatasets: @@ -213,12 +200,12 @@ class Ascender(): continue try: - if rdataset.ttl <= cls.minimum: - ttl = cls.minimum + if rdataset.ttl <= self.minimum: + ttl = self.minimum else: ttl = rdataset.ttl except AttributeError: - ttl = cls.minimum + ttl = self.minimum value = str(record) @@ -228,7 +215,7 @@ class Ascender(): # modify value to fit gns syntax rdtype, value, label = \ - cls.transform_to_gns_format(record, + self.transform_to_gns_format(record, rdtype, domain, label) @@ -243,7 +230,7 @@ class Ascender(): recordline.append('%d %s %s %s' % (int(ttl), rdtype, - cls.flags, + self.flags, element)) else: # build recordline @@ -255,19 +242,19 @@ class Ascender(): recordline.append('%d %s %s %s' % (int(ttl), rdtype, - cls.flags, + self.flags, value)) # add recordline to gns and filter out empty lines if len(recordline) > 1: - cls.add_recordline_to_gns(recordline, + self.add_recordline_to_gns(recordline, domain, label) taskqueue.task_done() # Check if there is zone has already been migrated - nsrecords = cls.zone.iterate_rdatas(dns.rdatatype.NS) + nsrecords = self.zone.iterate_rdatas(dns.rdatatype.NS) # This is broken if your NS is for ns.foo.YOURZONE as you add # the PKEY to YOURZONE instead of to the foo.YOURZONE subzone. @@ -275,7 +262,7 @@ class Ascender(): # bob NS IN ns.bob # carol NS IN ns.alice # => carol GNS2DNS GNS ns.alice@$IP - # dave.foo NS IN gns--pkey--$KEY.bob + # dave.foo NS IN gns--pkey--$KEY.bob # => dave.foo PKEY GNS $KEY # foo.bar A IN 1.2.3.4 # => bar PKEY GNS $NEWKEY + mapping: bar => $NEWKEY @@ -286,22 +273,22 @@ class Ascender(): ttl = gnspkey[0][1] pkey = str(gnspkey[0][2]) # TODO Check this check - if not cls.transferns in ['127.0.0.1', '::1', 'localhost']: + if not self.transferns in ['127.0.0.1', '::1', 'localhost']: logging.warning("zone exists in GNS, adding it to local store") - cls.add_pkey_record_to_zone(pkey[11:], cls.domain, + self.add_pkey_record_to_zone(pkey[11:], self.domain, label, ttl) return # Unify all records under same label into datastructure customrdataset = dict() - for remaining in cls.zone.iterate_rdatasets(): + for remaining in self.zone.iterate_rdatasets(): # build lookup table for later GNS2DNS records - domain = "%s.%s" % (str(remaining[0]), cls.domain) + domain = "%s.%s" % (str(remaining[0]), self.domain) elementlist = [] for element in remaining[1]: if dns.rdatatype.to_text(element.rdtype) in ['A', 'AAAA']: elementlist.append(str(element)) - cls.nscache[str(domain)] = elementlist + self.nscache[str(domain)] = elementlist rdataset = remaining[1] if customrdataset.get(str(remaining[0])) is None: work = list() @@ -317,21 +304,23 @@ class Ascender(): subzones = label.split('.') label = subzones[0] subdomain = ".".join(subzones[1:]) - zonename = "%s.%s" % (subdomain, cls.domain) + zonename = "%s.%s" % (subdomain, self.domain) - refresh = cls.get_zone_refresh_time() - if refresh <= cls.minimum: - ttl = cls.minimum - else: - ttl = refresh + try: + if value.ttl <= self.minimum: + ttl = self.minimum + else: + ttl = rdataset.ttl + except AttributeError: + ttl = self.minimum if len(subzones) > 1: - if cls.subzonedict.get(zonename): + if self.subzonedict.get(zonename): continue else: - cls.subzonedict[zonename] = (False, ttl) + self.subzonedict[zonename] = (False, ttl) - cls.create_zone_hierarchy() + self.create_zone_hierarchy() # Create one thread thread = threading.Thread(target=worker) @@ -353,7 +342,8 @@ class Ascender(): logging.critical("thread join timed out, still running") # Add soa record to GNS once completed (updates the previous one) - cls.add_soa_record_to_gns(cls.soa) + self.add_soa_record_to_gns(self.soa) + logging.info("All records have been added!") @staticmethod @@ -380,8 +370,7 @@ class Ascender(): logging.info("successfully added record with command %s", ' '.join(ret.args)) - @classmethod - def transform_to_gns_format(cls, record, rdtype, zonename, label): + def transform_to_gns_format(self, record, rdtype, zonename, label): """ Transforms value of record to GNS compatible format :param record: record to transform @@ -401,8 +390,8 @@ class Ascender(): if owner[-1] == '.': owner = owner[:-1] # hacky and might cause bugs - authns += cls.tld - owner += cls.tld + authns += self.tld + owner += self.tld value = "rname=%s.%s mname=%s.%s %d,%d,%d,%d,%d" % ( authns, zonename, owner, zonename, int(serial), int(refresh), int(retry), @@ -423,10 +412,10 @@ class Ascender(): zonename = zonename[:-1] if nameserver[-1] == ".": dnsresolver = nameserver[:-1] - dnsresolver = cls.nscache.get(dnsresolver, dnsresolver) + dnsresolver = self.nscache.get(dnsresolver, dnsresolver) else: dnsresolver = "%s.%s" % (nameserver, zonename) - dnsresolver = cls.nscache.get(dnsresolver, dnsresolver) + dnsresolver = self.nscache.get(dnsresolver, dnsresolver) if isinstance(dnsresolver, list): value = [] for nsip in dnsresolver: @@ -480,8 +469,7 @@ class Ascender(): logging.info("Did not transform record of type: %s", rdtype) return (rdtype, value, label) - @classmethod - def get_gns_zone_serial(cls): + def get_gns_zone_serial(self): """ Fetches the zones serial from GNS :returns: serial of the SOA record in GNS @@ -489,7 +477,7 @@ class Ascender(): try: serial = sp.check_output([GNUNET_GNS_COMMAND, '-t', 'SOA', - '-u', '@.%s' % cls.domain,]) + '-u', '@.%s' % self.domain,]) serial = serial.decode() except sp.CalledProcessError: serial = "" @@ -501,58 +489,13 @@ class Ascender(): soa_serial = None return soa_serial - @classmethod - def get_zone_soa_expiry(cls): + def get_zone_soa_expiry(self): """ Extracts the current serial from the class SOA :returns: refresh time of the current SOA record """ ttlpattern = re.compile(r'.+\s\d+\s(\d+)\s\d+\s\d+\s\d+', re.M) - return re.findall(ttlpattern, str(cls.soa[2])) - - @classmethod - def get_zone_refresh_time(cls): - """ - @deprecated (use from DNS) - Extracts the current refresh time of the zone from GNS - :returns: refresh time of the current SOA record - """ - try: - serial = sp.check_output([GNUNET_GNS_COMMAND, - '-t', 'SOA', - '-u', '@.%s' % cls.domain]) - serial = serial.decode() - except sp.CalledProcessError: - serial = "" - refresh = None - soapattern = re.compile(r'.+\s\d+,(\d+),\d+,\d+,\d+', re.M) - if re.findall(soapattern, serial): - refresh = re.findall(soapattern, serial)[0] - else: - refresh = None - return int(refresh) - - @classmethod - def get_zone_retry_time(cls): - """ - @deprecated (use from DNS) - Extracts the current retry time of the zone from GNS - :returns: retry time of the current SOA record - """ - try: - serial = sp.check_output([GNUNET_GNS_COMMAND, - '-t', 'SOA', - '-u', '@.%s' % cls.domain]) - serial = serial.decode() - except sp.CalledProcessError: - serial = "" - retry = 300 - soapattern = re.compile(r'.+\s\d+,\d+,(\d+),\d+,\d+', re.M) - if re.findall(soapattern, serial): - retry = re.findall(soapattern, serial)[0] - else: - retry = 300 - return retry + return re.findall(ttlpattern, str(self.soa[2])) @staticmethod def get_zone_soa(zone): @@ -567,8 +510,7 @@ class Ascender(): soa = soarecord return soa - @classmethod - def add_soa_record_to_gns(cls, record): + def add_soa_record_to_gns(self, record): """ Adds a SOA record to GNS :param record: The record to add @@ -579,11 +521,11 @@ class Ascender(): if authns[-1] == '.': authns = authns[:-1] else: - authns = "%s.%s" % (authns, cls.domain) + authns = "%s.%s" % (authns, self.domain) if owner[-1] == '.': owner = owner[:-1] else: - owner = "%s.%s" % (owner, cls.domain) + owner = "%s.%s" % (owner, self.domain) value = "rname=%s mname=%s %s,%s,%s,%s,%s" % (authns, owner, @@ -592,9 +534,9 @@ class Ascender(): retry, expiry, irefresh) - recordval = '%s %s %s %s' % (ttl, "SOA", cls.flags, str(value)) + recordval = '%s %s %s %s' % (ttl, "SOA", self.flags, str(value)) recordline = ['-R', recordval] - cls.add_recordline_to_gns(recordline, cls.domain, str(label)) + self.add_recordline_to_gns(recordline, self.domain, str(label)) @staticmethod def create_zone_and_get_pkey(zonestring): @@ -658,25 +600,24 @@ class Ascender(): label, domain) #logging.warning("PKEY record %s already exists in %s", label, domain) - @classmethod - def create_zone_hierarchy(cls): + def create_zone_hierarchy(self): """ Creates the zone hierarchy in GNS for label :param label: the split record to create zones for """ - domain = cls.domain + domain = self.domain - zonelist = cls.subzonedict.items() + zonelist = self.subzonedict.items() sortedlist = sorted(zonelist, key=lambda s: len(str(s).split('.'))) for zone, pkeyttltuple in sortedlist: pkey, ttl = pkeyttltuple if not pkey: domain = ".".join(zone.split('.')[1::]) label = zone.split('.')[0] - pkey = cls.create_zone_and_get_pkey(zone) + pkey = self.create_zone_and_get_pkey(zone) logging.info("adding zone %s with %s pkey into %s", zone, pkey, domain) - cls.add_pkey_record_to_zone(pkey, domain, label, pkeyttltuple[1]) - cls.subzonedict[zone] = (pkey, ttl) + self.add_pkey_record_to_zone(pkey, domain, label, pkeyttltuple[1]) + self.subzonedict[zone] = (pkey, ttl) def main(): """ @@ -724,32 +665,38 @@ def main(): ascender.zonegenerator = dns.query.xfr(ascender.transferns, ascender.domain, port=ascender.port) - dns_zone_serial = int(ascender.get_dns_zone_serial(ascender.domain, ascender.transferns)) - if int(gns_zone_serial) == dns_zone_serial: + dns_zone_serial = ascender.get_dns_zone_serial(ascender.domain, ascender.transferns) + if not dns_zone_serial: + logging.error("Could not get DNS zone serial") + if standalone: + return 1 + time.sleep(retry) + continue + if not gns_zone_serial: + logging.info("GNS zone does not exist yet, performing full transfer.") + ascender.bootstrap_zone() + elif int(gns_zone_serial) == int(dns_zone_serial): logging.info("GNS zone is up to date.") if standalone: return 0 time.sleep(refresh) continue - if int(gns_zone_serial) > dns_zone_serial: + elif int(gns_zone_serial) > int(dns_zone_serial): logging.critical("SOA serial in GNS is bigger than SOA serial in DNS?") logging.critical("GNS zone: %s, DNS zone: %s", gns_zone_serial, dns_zone_serial) if standalone: return 1 time.sleep(retry) continue - if not gns_zone_serial: - logging.info("GNS zone does not exist yet, performing full transfer.") - ascender.bootstrap_zone() - else + else: logging.info("GNS zone is out of date, performing incremental transfer.") + try: ascender.zone = dns.zone.from_xfr(ascender.zonegenerator, check_origin=False) ascender.soa = ascender.get_zone_soa(ascender.zone) - # FIXME: use DNS SOA (ascender.soa), from above, if fail retry time, if success refresh time - refresh = int(ascender.get_zone_refresh_time()) - retry = int(ascender.get_zone_retry_time()) + refresh = str(ascender.soa[2]).split(" ")[4] + retry = str(ascender.soa[2]).split(" ")[5] except dns.zone.BadZone: logging.critical("Malformed DNS Zone '%s'", ascender.domain) if standalone: @@ -757,19 +704,8 @@ def main(): time.sleep(retry) continue - # FIXME: return value (or exception) to observe success/failure of operation! ascender.add_records_to_gns() logging.info("Finished migration of the zone %s", ascender.domain) - if standalone: - # FIXME: return non-zero on errors! - return 0 - if not refresh: # FIXME: if not successful with add_records_to_gns! - logging.info("Unable to refresh zone, retrying in %ds", retry) - time.sleep(retry) - else: - logging.info("Refreshing zone in %ds", refresh) - print("Refreshing zone in %ds" % refresh) - time.sleep(refresh) if __name__ == '__main__': main()