ascension

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

commit 8e08bdf35704a93f6266bf0d39569c8b7703f04c
parent 8d952a71d8e466f39e541df97be90186f77a2704
Author: rexxnor <rexxnor+gnunet@brief.li>
Date:   Fri,  9 Nov 2018 15:01:59 +0100

updated gnsmigrator and removed c rebuilds

Diffstat:
Mgnsmigrator/gnsmigrator.py | 364+++++++++++++++++++++++++++++++++++++------------------------------------------
1 file changed, 169 insertions(+), 195 deletions(-)

diff --git a/gnsmigrator/gnsmigrator.py b/gnsmigrator/gnsmigrator.py @@ -19,12 +19,6 @@ Options: """ # imports -from ctypes import c_size_t -from ctypes import c_uint32 -from ctypes import c_uint64 -from ctypes import c_void_p -from enum import Enum -from dataclasses import dataclass import logging import queue import re @@ -42,26 +36,6 @@ GNUNET_NAMESTORE_COMMAND = 'gnunet-namestore' GNUNET_GNS_COMMAND = 'gnunet-gns' GNUNET_ARM_COMMAND = 'gnunet-arm' -class GNUnetGNSRecordFlags(Enum): - """ - Flags that can be set for a record. - """ - GNUNET_GNSRECORD_RF_NONE = 0 - GNUNET_GNSRECORD_RF_PRIVATE = 2 - GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION = 8 - GNUNET_GNSRECORD_RF_SHADOW_RECORD = 16 - -@dataclass -class GNUnetGNSRecordData(): - """ - Representation of a GNS Record in python - """ - data: c_void_p - expiration_time: c_uint64 - data_size: c_size_t - record_type: c_uint32 - flags: GNUnetGNSRecordFlags - class GNSMigrator(): """ Class that provides migration for any given domain @@ -155,6 +129,175 @@ class GNSMigrator(): counter += 1 + @classmethod + def get_current_serial(cls, domain, resolver=None): + """ + Gets the current serial for a given zone + """ + # SOA is different if taken directly from SOA record + # compared to AXFR/IXFR - changed to respect this + try: + soa_answer = dns.resolver.query(domain, 'SOA') + except dns.resolver.NoAnswer: + logging.warning("the domain '%s' does not exist") + master_answer = dns.resolver.query(soa_answer[0].mname, 'A') + try: + if resolver: + zone = dns.zone.from_xfr(dns.query.xfr( + resolver, domain, port=cls.port)) + else: + zone = dns.zone.from_xfr(dns.query.xfr( + master_answer[0].address, domain, + port=cls.port)) + except dns.resolver.NoAnswer: + logging.warning("nameserver for '%s' did not answer", domain) + except dns.exception.FormError: + logging.warning("domain '%s' does not allow xfr requests", domain) + for soa_record in zone.iterate_rdatas(rdtype=dns.rdatatype.SOA): + if not cls.transferns: + mname = soa_record[2].mname + if cls.domain not in mname: + cls.transferns = str(soa_record[2].mname) + "." + domain + else: + cls.transferns = str(soa_record[2].mname) + return soa_record[2].serial + + @classmethod + def mirror_zone(cls): + """ + Extract necessary information from Generator + """ + currentserial = int(cls.get_current_serial(cls.domain, cls.transferns)) + zoneserial = int(cls.get_zone_serial(cls.domain[:-1])) + if zoneserial == 0: + cls.initial_zone_transfer() + cls.zone = dns.zone.from_xfr(cls.zonegenerator) + cls.soa = cls.get_zone_soa(cls.zone) + elif zoneserial < currentserial: + cls.initial_zone_transfer(serial=zoneserial) + cls.zone = dns.zone.from_xfr(cls.zonegenerator) + cls.soa = cls.get_zone_soa(cls.zone) + return + elif zoneserial == currentserial: + logging.warning("Nothing to do!") + sys.exit(0) + # should be unnecessary but AXFR SOA is not equal to direct SOA + else: + logging.critical("SOA serial is bigger than zone serial?") + print(zoneserial, currentserial) + sys.exit(1) + + @classmethod + def add_records_to_gns(cls): + """ + Extracts records from zone and adds them to GNS + """ + logging.info("Starting to add records into GNS...") + zonename = cls.get_lowest_domain_part(cls.domain) + + # Defining FIFO Queue + taskqueue = queue.Queue(maxsize=5) + # Defining worker + def worker(): + while True: + record = taskqueue.get() + if record is None: + break + # execute thing to run on item + _, _, authns = record + if str(authns)[:-1] == ".": + authns = str(authns)[:-1] + else: + authns = "%s.%s" % (authns, zonename) + + # building gns record struct + # GNUnetGNSRecordData() + cls.add_record_to_gns(record, zonename, cls.domain) + + taskqueue.task_done() + + # Create one thread + thread = threading.Thread(target=worker) + thread.start() + + # Give worker stuff to do + for record in cls.zone.iterate_rdatas(): + taskqueue.put(record) + + # Block until all tasks are done + taskqueue.join() + + # Stop workers + taskqueue.put(None) + thread.join() + + # Add soa record to GNS once completed (updates the previous one) + soa = cls.get_zone_soa(cls.zone) + cls.add_record_to_gns(soa, zonename, cls.domain) + logging.info("All records have been added!") + + @staticmethod + def add_record_to_gns(record, zonename, domain): + """ + Checks if records are present and adds them if not + :param record: record to add + :param zonename: zonename of zone to add records to + :param domain: full domain of zone + """ + dnsname_str = str(record[0]) + rtype_str = dns.rdatatype.to_text(record[2].rdtype) + logging.info("adding %s record with name %s", rtype_str, dnsname_str) + if dnsname_str == '@': + if rtype_str == 'SOA': + GNSMigrator.add_soa_record_to_gns(record, zonename, domain) + else: + if rtype_str == 'NS' and dnsname_str != '@': + GNSMigrator.add_ns_record_to_gns(record, zonename, domain) + elif rtype_str == 'MX': + GNSMigrator.add_mx_record_to_gns(record, zonename) + #elif rtype_str == 'SRV': + # GNSMigrator.add_srv_record_to_gns(record, zonename) + elif rtype_str in ['A', 'AAAA']: + GNSMigrator.add_a_aaaa_record_to_gns(record, zonename, domain) + elif rtype_str in ['TXT', 'CNAME']: + GNSMigrator.add_gen_record_to_gns(record, zonename) + else: + logging.warning("Unsupported record type: %s", rtype_str) + + @staticmethod + def get_zone_serial(zonename): + """ + Extracts the current serial from a given zone + """ + if zonename[-1] == '.': + zonename = zonename[:-1] + try: + serial = sp.check_output([GNUNET_GNS_COMMAND, + '-t', 'SOA', + '-u', '@.%s' % zonename]) + serial = serial.decode() + except sp.CalledProcessError: + serial = "" + soa_serial = 0 + soapattern = re.compile(r'.+\s(\d+),\d+,+\d+,\d+,\d+', re.M) + if re.findall(soapattern, serial): + soa_serial = re.findall(soapattern, serial)[0] + else: + soa_serial = 0 + return soa_serial + + @staticmethod + def get_zone_soa(zone): + """ + Fetches soa record from zone + """ + ret = None + for soarecord in zone.iterate_rdatas(rdtype=dns.rdatatype.SOA): + if str(soarecord[0]) == '@': + ret = soarecord + break + return ret + @staticmethod def add_gen_record_to_gns(record, zonename): """ @@ -342,175 +485,6 @@ class GNSMigrator(): """ return domain.split('.')[0] - @classmethod - def get_current_serial(cls, domain, resolver=None): - """ - Gets the current serial for a given zone - """ - # SOA is different if taken directly from SOA record - # compared to AXFR/IXFR - changed to respect this - try: - soa_answer = dns.resolver.query(domain, 'SOA') - except dns.resolver.NoAnswer: - logging.warning("the domain '%s' does not exist") - master_answer = dns.resolver.query(soa_answer[0].mname, 'A') - try: - if resolver: - zone = dns.zone.from_xfr(dns.query.xfr( - resolver, domain, port=cls.port)) - else: - zone = dns.zone.from_xfr(dns.query.xfr( - master_answer[0].address, domain, - port=cls.port)) - except dns.resolver.NoAnswer: - logging.warning("nameserver for '%s' did not answer", domain) - except dns.exception.FormError: - logging.warning("domain '%s' does not allow xfr requests", domain) - for soa_record in zone.iterate_rdatas(rdtype=dns.rdatatype.SOA): - if not cls.transferns: - mname = soa_record[2].mname - if cls.domain not in mname: - cls.transferns = str(soa_record[2].mname) + "." + domain - else: - cls.transferns = str(soa_record[2].mname) - return soa_record[2].serial - - @staticmethod - def get_zone_serial(zonename): - """ - Extracts the current serial from a given zone - """ - if zonename[-1] == '.': - zonename = zonename[:-1] - try: - serial = sp.check_output([GNUNET_GNS_COMMAND, - '-t', 'SOA', - '-u', '@.%s' % zonename]) - serial = serial.decode() - except sp.CalledProcessError: - serial = "" - soa_serial = 0 - soapattern = re.compile(r'.+\s(\d+),\d+,+\d+,\d+,\d+', re.M) - if re.findall(soapattern, serial): - soa_serial = re.findall(soapattern, serial)[0] - else: - soa_serial = 0 - return soa_serial - - @staticmethod - def get_zone_soa(zone): - """ - Fetches soa record from zone - """ - ret = None - for soarecord in zone.iterate_rdatas(rdtype=dns.rdatatype.SOA): - if str(soarecord[0]) == '@': - ret = soarecord - break - return ret - - @classmethod - def mirror_zone(cls): - """ - Extract necessary information from Generator - """ - currentserial = int(cls.get_current_serial(cls.domain, cls.transferns)) - zoneserial = int(cls.get_zone_serial(cls.domain[:-1])) - if zoneserial == 0: - cls.initial_zone_transfer() - cls.zone = dns.zone.from_xfr(cls.zonegenerator) - cls.soa = cls.get_zone_soa(cls.zone) - elif zoneserial < currentserial: - cls.initial_zone_transfer(serial=zoneserial) - cls.zone = dns.zone.from_xfr(cls.zonegenerator) - cls.soa = cls.get_zone_soa(cls.zone) - return - elif zoneserial == currentserial: - logging.warning("Nothing to do!") - sys.exit(0) - # should be unnecessary but AXFR SOA is not equal to direct SOA - else: - logging.critical("SOA serial is bigger than zone serial?") - print(zoneserial, currentserial) - sys.exit(1) - - @classmethod - def add_records_to_gns(cls): - """ - Extracts records from zone and adds them to GNS - """ - logging.info("Starting to add records into GNS...") - zonename = cls.get_lowest_domain_part(cls.domain) - - # Defining FIFO Queue - taskqueue = queue.Queue(maxsize=5) - # Defining worker - def worker(): - while True: - record = taskqueue.get() - if record is None: - break - # execute thing to run on item - _, _, authns = record - if str(authns)[:-1] == ".": - authns = str(authns)[:-1] - else: - authns = "%s.%s" % (authns, zonename) - - # building gns record struct - # GNUnetGNSRecordData() - cls.add_record_to_gns(record, zonename, cls.domain) - - taskqueue.task_done() - - # Create one thread - thread = threading.Thread(target=worker) - thread.start() - - # Give worker stuff to do - for record in cls.zone.iterate_rdatas(): - taskqueue.put(record) - - # Block until all tasks are done - taskqueue.join() - - # Stop workers - taskqueue.put(None) - thread.join() - - # Add soa record to GNS once completed (updates the previous one) - soa = cls.get_zone_soa(cls.zone) - cls.add_record_to_gns(soa, zonename, cls.domain) - logging.info("All records have been added!") - - @staticmethod - def add_record_to_gns(record, zonename, domain): - """ - Checks if records are present and adds them if not - :param record: record to add - :param zonename: zonename of zone to add records to - :param domain: full domain of zone - """ - dnsname_str = str(record[0]) - rtype_str = dns.rdatatype.to_text(record[2].rdtype) - logging.info("adding %s record with name %s", rtype_str, dnsname_str) - if dnsname_str == '@': - if rtype_str == 'SOA': - GNSMigrator.add_soa_record_to_gns(record, zonename, domain) - else: - if rtype_str == 'NS' and dnsname_str != '@': - GNSMigrator.add_ns_record_to_gns(record, zonename, domain) - elif rtype_str == 'MX': - GNSMigrator.add_mx_record_to_gns(record, zonename) - #elif rtype_str == 'SRV': - # GNSMigrator.add_srv_record_to_gns(record, zonename) - elif rtype_str in ['A', 'AAAA']: - GNSMigrator.add_a_aaaa_record_to_gns(record, zonename, domain) - elif rtype_str in ['TXT', 'CNAME']: - GNSMigrator.add_gen_record_to_gns(record, zonename) - else: - logging.warning("Unsupported record type: %s", rtype_str) - def main(): """ Initializes object and handles arguments