import sys
sys.path.insert(0, os.path.dirname(sys.argv[0]))
-if os.name != 'nt':
- if '@PYTHON_INSTALL_DIR@': # value of --with-python-install-dir
- sys.path.insert(1, '@PYTHON_INSTALL_DIR@')
+if os.name != "nt":
+ if "@PYTHON_INSTALL_DIR@": # value of --with-python-install-dir
+ sys.path.insert(1, "@PYTHON_INSTALL_DIR@")
else:
- sys.path.insert(1, os.path.join('@prefix@', 'lib',
- 'python' + sys.version[:3], 'site-packages'))
+ sys.path.insert(
+ 1,
+ os.path.join(
+ "@prefix@", "lib", "python" + sys.version[:3], "site-packages"
+ ),
+ )
import isc.checkds
import sys
sys.path.insert(0, os.path.dirname(sys.argv[0]))
-if os.name != 'nt':
- if '@PYTHON_INSTALL_DIR@': # value of --with-python-install-dir
- sys.path.insert(1, '@PYTHON_INSTALL_DIR@')
+if os.name != "nt":
+ if "@PYTHON_INSTALL_DIR@": # value of --with-python-install-dir
+ sys.path.insert(1, "@PYTHON_INSTALL_DIR@")
else:
- sys.path.insert(1, os.path.join('@prefix@', 'lib',
- 'python' + sys.version[:3], 'site-packages'))
+ sys.path.insert(
+ 1,
+ os.path.join(
+ "@prefix@", "lib", "python" + sys.version[:3], "site-packages"
+ ),
+ )
import isc.coverage
import sys
sys.path.insert(0, os.path.dirname(sys.argv[0]))
-if os.name != 'nt':
- if '@PYTHON_INSTALL_DIR@': # value of --with-python-install-dir
- sys.path.insert(1, '@PYTHON_INSTALL_DIR@')
+if os.name != "nt":
+ if "@PYTHON_INSTALL_DIR@": # value of --with-python-install-dir
+ sys.path.insert(1, "@PYTHON_INSTALL_DIR@")
else:
- sys.path.insert(1, os.path.join('@prefix@', 'lib',
- 'python' + sys.version[:3], 'site-packages'))
+ sys.path.insert(
+ 1,
+ os.path.join(
+ "@prefix@", "lib", "python" + sys.version[:3], "site-packages"
+ ),
+ )
import isc.keymgr
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
-__all__ = ['checkds', 'coverage', 'keymgr', 'dnskey', 'eventlist',
- 'keydict', 'keyevent', 'keyseries', 'keyzone', 'policy',
- 'parsetab', 'rndc', 'utils']
+__all__ = [
+ "checkds",
+ "coverage",
+ "keymgr",
+ "dnskey",
+ "eventlist",
+ "keydict",
+ "keyevent",
+ "keyseries",
+ "keyzone",
+ "policy",
+ "parsetab",
+ "rndc",
+ "utils",
+]
from isc.dnskey import *
from isc.eventlist import *
import sys
from subprocess import Popen, PIPE
-from isc.utils import prefix,version
+from isc.utils import prefix, version
-prog = 'dnssec-checkds'
+prog = "dnssec-checkds"
############################################################################
# Class for DS resource record
############################################################################
class SECRR:
- hashalgs = {1: 'SHA-1', 2: 'SHA-256', 3: 'GOST', 4: 'SHA-384'}
- rrname = ''
- rrclass = 'IN'
+ hashalgs = {1: "SHA-1", 2: "SHA-256", 3: "GOST", 4: "SHA-384"}
+ rrname = ""
+ rrclass = "IN"
keyid = None
keyalg = None
hashalg = None
- digest = ''
+ digest = ""
ttl = 0
def __init__(self, rrtext):
# 'str' does not have decode method in python3
if type(rrtext) is not str:
- fields = rrtext.decode('ascii').split()
+ fields = rrtext.decode("ascii").split()
else:
fields = rrtext.split()
if len(fields) < 7:
self.rrname = fields[0].lower()
fields = fields[1:]
- if fields[0].upper() in ['IN', 'CH', 'HS']:
+ if fields[0].upper() in ["IN", "CH", "HS"]:
self.rrclass = fields[0].upper()
fields = fields[1:]
else:
fields = fields[2:]
if fields[0].upper() != self.rrtype:
- raise Exception('%s does not match %s' %
- (fields[0].upper(), self.rrtype))
+ raise Exception("%s does not match %s" % (fields[0].upper(), self.rrtype))
self.keyid, self.keyalg, self.hashalg = map(int, fields[1:4])
- self.digest = ''.join(fields[4:]).upper()
+ self.digest = "".join(fields[4:]).upper()
def __repr__(self):
- return '%s %s %s %d %d %d %s' % \
- (self.rrname, self.rrclass, self.rrtype,
- self.keyid, self.keyalg, self.hashalg, self.digest)
+ return "%s %s %s %d %d %d %s" % (
+ self.rrname,
+ self.rrclass,
+ self.rrtype,
+ self.keyid,
+ self.keyalg,
+ self.hashalg,
+ self.digest,
+ )
def __eq__(self, other):
return self.__repr__() == other.__repr__()
for line in fp.splitlines():
if type(line) is not str:
- line = line.decode('ascii')
+ line = line.decode("ascii")
rrlist.append(SECRR(line))
rrlist = sorted(rrlist, key=lambda rr: (rr.keyid, rr.keyalg, rr.hashalg))
cmd = [args.dsfromkey]
for algo in args.algo:
- cmd += ['-a', algo]
+ cmd += ["-a", algo]
if args.masterfile:
cmd += ["-f", args.masterfile, zone]
fp, _ = Popen(cmd, stdout=PIPE).communicate()
else:
- intods, _ = Popen([args.dig, "+noall", "+answer", "-t", "dnskey",
- "-q", zone], stdout=PIPE).communicate()
+ intods, _ = Popen(
+ [args.dig, "+noall", "+answer", "-t", "dnskey", "-q", zone], stdout=PIPE
+ ).communicate()
cmd += ["-f", "-", zone]
fp, _ = Popen(cmd, stdin=PIPE, stdout=PIPE).communicate(intods)
for line in fp.splitlines():
if type(line) is not str:
- line = line.decode('ascii')
+ line = line.decode("ascii")
klist.append(SECRR(line))
if len(klist) < 1:
match = True
for rr in rrlist:
if rr not in klist:
- print("KSK for %s %s/%03d/%05d (%s) missing from child" %
- (rr.rrtype, rr.rrname.strip('.'), rr.keyalg,
- rr.keyid, SECRR.hashalgs[rr.hashalg]))
+ print(
+ "KSK for %s %s/%03d/%05d (%s) missing from child"
+ % (
+ rr.rrtype,
+ rr.rrname.strip("."),
+ rr.keyalg,
+ rr.keyid,
+ SECRR.hashalgs[rr.hashalg],
+ )
+ )
match = False
for rr in klist:
if rr not in rrlist:
- print("%s for KSK %s/%03d/%05d (%s) missing from parent" %
- (rr.rrtype, rr.rrname.strip('.'), rr.keyalg,
- rr.keyid, SECRR.hashalgs[rr.hashalg]))
+ print(
+ "%s for KSK %s/%03d/%05d (%s) missing from parent"
+ % (
+ rr.rrtype,
+ rr.rrname.strip("."),
+ rr.keyalg,
+ rr.keyid,
+ SECRR.hashalgs[rr.hashalg],
+ )
+ )
match = False
for rr in klist:
if rr in rrlist:
- print("%s for KSK %s/%03d/%05d (%s) found in parent" %
- (rr.rrtype, rr.rrname.strip('.'), rr.keyalg,
- rr.keyid, SECRR.hashalgs[rr.hashalg]))
+ print(
+ "%s for KSK %s/%03d/%05d (%s) found in parent"
+ % (
+ rr.rrtype,
+ rr.rrname.strip("."),
+ rr.keyalg,
+ rr.keyid,
+ SECRR.hashalgs[rr.hashalg],
+ )
+ )
return match
# Read command line arguments, set global 'args' structure
############################################################################
def parse_args():
- parser = argparse.ArgumentParser(description=prog + ': checks DS coverage')
-
- bindir = 'bin'
- sbindir = 'bin' if os.name == 'nt' else 'sbin'
-
- parser.add_argument('zone', type=str, help='zone to check')
- parser.add_argument('-a', '--algo', dest='algo', action='append',
- default=[], type=str, help='DS digest algorithm')
- parser.add_argument('-d', '--dig', dest='dig',
- default=os.path.join(prefix(bindir), 'dig'),
- type=str, help='path to \'dig\'')
- parser.add_argument('-D', '--dsfromkey', dest='dsfromkey',
- default=os.path.join(prefix(sbindir),
- 'dnssec-dsfromkey'),
- type=str, help='path to \'dnssec-dsfromkey\'')
- parser.add_argument('-f', '--file', dest='masterfile', type=str,
- help='zone master file')
- parser.add_argument('-s', '--dsset', dest='dssetfile', type=str,
- help='prepared DSset file')
- parser.add_argument('-v', '--version', action='version',
- version=version)
+ parser = argparse.ArgumentParser(description=prog + ": checks DS coverage")
+
+ bindir = "bin"
+ sbindir = "bin" if os.name == "nt" else "sbin"
+
+ parser.add_argument("zone", type=str, help="zone to check")
+ parser.add_argument(
+ "-a",
+ "--algo",
+ dest="algo",
+ action="append",
+ default=[],
+ type=str,
+ help="DS digest algorithm",
+ )
+ parser.add_argument(
+ "-d",
+ "--dig",
+ dest="dig",
+ default=os.path.join(prefix(bindir), "dig"),
+ type=str,
+ help="path to 'dig'",
+ )
+ parser.add_argument(
+ "-D",
+ "--dsfromkey",
+ dest="dsfromkey",
+ default=os.path.join(prefix(sbindir), "dnssec-dsfromkey"),
+ type=str,
+ help="path to 'dnssec-dsfromkey'",
+ )
+ parser.add_argument(
+ "-f", "--file", dest="masterfile", type=str, help="zone master file"
+ )
+ parser.add_argument(
+ "-s", "--dsset", dest="dssetfile", type=str, help="prepared DSset file"
+ )
+ parser.add_argument("-v", "--version", action="version", version=version)
args = parser.parse_args()
- args.zone = args.zone.strip('.')
+ args.zone = args.zone.strip(".")
return args
import pprint
from collections import defaultdict
-prog = 'dnssec-coverage'
+prog = "dnssec-coverage"
from isc import dnskey, eventlist, keydict, keyevent, keyzone, utils
# output:
############################################################################
_firstline = True
+
+
def output(*args, **kwargs):
"""output text, adding a vertical space this is *not* the first
first section being printed since a call to vreset()"""
global _firstline
- if 'skip' in kwargs:
- skip = kwargs['skip']
- kwargs.pop('skip', None)
+ if "skip" in kwargs:
+ skip = kwargs["skip"]
+ kwargs.pop("skip", None)
else:
skip = True
if _firstline:
_firstline = False
elif skip:
- print('')
+ print("")
if args:
print(*args, **kwargs)
# parse_time
############################################################################
def parse_time(s):
- """ convert a formatted time (e.g., 1y, 6mo, 15mi, etc) into seconds
+ """convert a formatted time (e.g., 1y, 6mo, 15mi, etc) into seconds
:param s: String with some text representing a time interval
:return: Integer with the number of seconds in the time interval
"""
pass
# try to parse as a number with a suffix indicating unit of time
- r = re.compile(r'([0-9][0-9]*)\s*([A-Za-z]*)')
+ r = re.compile(r"([0-9][0-9]*)\s*([A-Za-z]*)")
m = r.match(s)
if not m:
raise ValueError("Cannot parse %s" % s)
n, unit = m.groups()
n = int(n)
unit = unit.lower()
- if unit.startswith('y'):
+ if unit.startswith("y"):
return n * 31536000
- elif unit.startswith('mo'):
+ elif unit.startswith("mo"):
return n * 2592000
- elif unit.startswith('w'):
+ elif unit.startswith("w"):
return n * 604800
- elif unit.startswith('d'):
+ elif unit.startswith("d"):
return n * 86400
- elif unit.startswith('h'):
+ elif unit.startswith("h"):
return n * 3600
- elif unit.startswith('mi'):
+ elif unit.startswith("mi"):
return n * 60
- elif unit.startswith('s'):
+ elif unit.startswith("s"):
return n
else:
raise ValueError("Invalid suffix %s" % unit)
# set_path:
############################################################################
def set_path(command, default=None):
- """ find the location of a specified command. if a default is supplied
+ """find the location of a specified command. if a default is supplied
and it works, we use it; otherwise we search PATH for a match.
:param command: string with a command to look for in the path
:param default: default location to use
############################################################################
def parse_args():
"""Read command line arguments, set global 'args' structure"""
- compilezone = set_path('named-compilezone',
- os.path.join(utils.prefix('sbin'),
- 'named-compilezone'))
-
- parser = argparse.ArgumentParser(description=prog + ': checks future ' +
- 'DNSKEY coverage for a zone')
-
- parser.add_argument('zone', type=str, nargs='*', default=None,
- help='zone(s) to check' +
- '(default: all zones in the directory)')
- parser.add_argument('-K', dest='path', default='.', type=str,
- help='a directory containing keys to process',
- metavar='dir')
- parser.add_argument('-f', dest='filename', type=str,
- help='zone master file', metavar='file')
- parser.add_argument('-m', dest='maxttl', type=str,
- help='the longest TTL in the zone(s)',
- metavar='time')
- parser.add_argument('-d', dest='keyttl', type=str,
- help='the DNSKEY TTL', metavar='time')
- parser.add_argument('-r', dest='resign', default='1944000',
- type=str, help='the RRSIG refresh interval '
- 'in seconds [default: 22.5 days]',
- metavar='time')
- parser.add_argument('-c', dest='compilezone',
- default=compilezone, type=str,
- help='path to \'named-compilezone\'',
- metavar='path')
- parser.add_argument('-l', dest='checklimit',
- type=str, default='0',
- help='Length of time to check for '
- 'DNSSEC coverage [default: 0 (unlimited)]',
- metavar='time')
- parser.add_argument('-z', dest='no_ksk',
- action='store_true', default=False,
- help='Only check zone-signing keys (ZSKs)')
- parser.add_argument('-k', dest='no_zsk',
- action='store_true', default=False,
- help='Only check key-signing keys (KSKs)')
- parser.add_argument('-D', '--debug', dest='debug_mode',
- action='store_true', default=False,
- help='Turn on debugging output')
- parser.add_argument('-v', '--version', action='version',
- version=utils.version)
+ compilezone = set_path(
+ "named-compilezone", os.path.join(utils.prefix("sbin"), "named-compilezone")
+ )
+
+ parser = argparse.ArgumentParser(
+ description=prog + ": checks future " + "DNSKEY coverage for a zone"
+ )
+
+ parser.add_argument(
+ "zone",
+ type=str,
+ nargs="*",
+ default=None,
+ help="zone(s) to check" + "(default: all zones in the directory)",
+ )
+ parser.add_argument(
+ "-K",
+ dest="path",
+ default=".",
+ type=str,
+ help="a directory containing keys to process",
+ metavar="dir",
+ )
+ parser.add_argument(
+ "-f", dest="filename", type=str, help="zone master file", metavar="file"
+ )
+ parser.add_argument(
+ "-m",
+ dest="maxttl",
+ type=str,
+ help="the longest TTL in the zone(s)",
+ metavar="time",
+ )
+ parser.add_argument(
+ "-d", dest="keyttl", type=str, help="the DNSKEY TTL", metavar="time"
+ )
+ parser.add_argument(
+ "-r",
+ dest="resign",
+ default="1944000",
+ type=str,
+ help="the RRSIG refresh interval " "in seconds [default: 22.5 days]",
+ metavar="time",
+ )
+ parser.add_argument(
+ "-c",
+ dest="compilezone",
+ default=compilezone,
+ type=str,
+ help="path to 'named-compilezone'",
+ metavar="path",
+ )
+ parser.add_argument(
+ "-l",
+ dest="checklimit",
+ type=str,
+ default="0",
+ help="Length of time to check for " "DNSSEC coverage [default: 0 (unlimited)]",
+ metavar="time",
+ )
+ parser.add_argument(
+ "-z",
+ dest="no_ksk",
+ action="store_true",
+ default=False,
+ help="Only check zone-signing keys (ZSKs)",
+ )
+ parser.add_argument(
+ "-k",
+ dest="no_zsk",
+ action="store_true",
+ default=False,
+ help="Only check key-signing keys (KSKs)",
+ )
+ parser.add_argument(
+ "-D",
+ "--debug",
+ dest="debug_mode",
+ action="store_true",
+ default=False,
+ help="Turn on debugging output",
+ )
+ parser.add_argument("-v", "--version", action="version", version=utils.version)
args = parser.parse_args()
fatal("ERROR: -f can only be used with one zone.")
# strip trailing dots if any
- args.zone = [x[:-1] if (len(x) > 1 and x[-1] == '.') else x
- for x in args.zone]
+ args.zone = [x[:-1] if (len(x) > 1 and x[-1] == ".") else x for x in args.zone]
# convert from time arguments to seconds
try:
print("Unable to load zone data from %s: " % args.filename, e)
if not args.maxttl:
- output("WARNING: Maximum TTL value was not specified. Using 1 week\n"
- "\t (604800 seconds); re-run with the -m option to get more\n"
- "\t accurate results.")
+ output(
+ "WARNING: Maximum TTL value was not specified. Using 1 week\n"
+ "\t (604800 seconds); re-run with the -m option to get more\n"
+ "\t accurate results."
+ )
args.maxttl = 604800
return args
+
############################################################################
# Main
############################################################################
try:
kd = keydict(path=args.path, zones=args.zone, keyttl=args.keyttl)
except Exception as e:
- fatal('ERROR: Unable to build key dictionary: ' + str(e))
+ fatal("ERROR: Unable to build key dictionary: " + str(e))
for key in kd:
key.check_prepub(output)
try:
elist = eventlist(kd)
except Exception as e:
- fatal('ERROR: Unable to build event list: ' + str(e))
+ fatal("ERROR: Unable to build event list: " + str(e))
errors = False
if not args.zone:
else:
for zone in args.zone:
try:
- if not elist.coverage(zone, args.keytype,
- args.checklimit, output):
+ if not elist.coverage(zone, args.keytype, args.checklimit, output):
errors = True
except:
- output('ERROR: Coverage check failed for zone ' + zone)
+ output("ERROR: Coverage check failed for zone " + zone)
sys.exit(1 if errors else 0)
########################################################################
class TimePast(Exception):
def __init__(self, key, prop, value):
- super(TimePast, self).__init__('%s time for key %s (%d) is already past'
- % (prop, key, value))
+ super(TimePast, self).__init__(
+ "%s time for key %s (%d) is already past" % (prop, key, value)
+ )
+
class dnskey:
"""An individual DNSSEC key. Identified by path, name, algorithm, keyid.
Contains a dictionary of metadata events."""
- _PROPS = ('Created', 'Publish', 'Activate', 'Inactive', 'Delete',
- 'Revoke', 'DSPublish', 'SyncPublish', 'SyncDelete')
- _OPTS = (None, '-P', '-A', '-I', '-D', '-R', None, '-Psync', '-Dsync')
-
- _ALGNAMES = (None, 'RSAMD5', 'DH', 'DSA', None, 'RSASHA1',
- 'NSEC3DSA', 'NSEC3RSASHA1', 'RSASHA256', None,
- 'RSASHA512', None, 'ECCGOST', 'ECDSAP256SHA256',
- 'ECDSAP384SHA384', 'ED25519', 'ED448')
+ _PROPS = (
+ "Created",
+ "Publish",
+ "Activate",
+ "Inactive",
+ "Delete",
+ "Revoke",
+ "DSPublish",
+ "SyncPublish",
+ "SyncDelete",
+ )
+ _OPTS = (None, "-P", "-A", "-I", "-D", "-R", None, "-Psync", "-Dsync")
+
+ _ALGNAMES = (
+ None,
+ "RSAMD5",
+ "DH",
+ "DSA",
+ None,
+ "RSASHA1",
+ "NSEC3DSA",
+ "NSEC3RSASHA1",
+ "RSASHA256",
+ None,
+ "RSASHA512",
+ None,
+ "ECCGOST",
+ "ECDSAP256SHA256",
+ "ECDSAP384SHA384",
+ "ED25519",
+ "ED448",
+ )
def __init__(self, key, directory=None, keyttl=None):
# this makes it possible to use algname as a class or instance method
if isinstance(key, tuple) and len(key) == 3:
- self._dir = directory or '.'
+ self._dir = directory or "."
(name, alg, keyid) = key
self.fromtuple(name, alg, keyid, keyttl)
- self._dir = directory or os.path.dirname(key) or '.'
+ self._dir = directory or os.path.dirname(key) or "."
key = os.path.basename(key)
- (name, alg, keyid) = key.split('+')
+ (name, alg, keyid) = key.split("+")
name = name[1:-1]
alg = int(alg)
- keyid = int(keyid.split('.')[0])
+ keyid = int(keyid.split(".")[0])
self.fromtuple(name, alg, keyid, keyttl)
def fromtuple(self, name, alg, keyid, keyttl):
- if name.endswith('.'):
+ if name.endswith("."):
fullname = name
- name = name.rstrip('.')
+ name = name.rstrip(".")
else:
- fullname = name + '.'
+ fullname = name + "."
keystr = "K%s+%03d+%05d" % (fullname, alg, keyid)
- key_file = self._dir + (self._dir and os.sep or '') + keystr + ".key"
- private_file = (self._dir + (self._dir and os.sep or '') +
- keystr + ".private")
+ key_file = self._dir + (self._dir and os.sep or "") + keystr + ".key"
+ private_file = self._dir + (self._dir and os.sep or "") + keystr + ".private"
self.keystr = keystr
kfp = open(key_file, "r")
for line in kfp:
- if line[0] == ';':
+ if line[0] == ";":
continue
tokens = line.split()
if not tokens:
continue
- if tokens[1].lower() in ('in', 'ch', 'hs'):
+ if tokens[1].lower() in ("in", "ch", "hs"):
septoken = 3
self.ttl = keyttl
else:
for line in pfp:
line = line.strip()
- if not line or line[0] in ('!#'):
+ if not line or line[0] in ("!#"):
continue
- punctuation = [line.find(c) for c in ':= '] + [len(line)]
+ punctuation = [line.find(c) for c in ":= "] + [len(line)]
found = min([pos for pos in punctuation if pos != -1])
name = line[:found].rstrip()
value = line[found:].lstrip(":= ").rstrip()
pfp.close()
def commit(self, settime_bin, **kwargs):
- quiet = kwargs.get('quiet', False)
+ quiet = kwargs.get("quiet", False)
cmd = []
first = True
if prop in self._delete and self._delete[prop]:
delete = True
- when = 'none' if delete else self._fmttime[prop]
+ when = "none" if delete else self._fmttime[prop]
cmd += [opt, when]
first = False
if cmd:
- fullcmd = [settime_bin, "-K", self._dir] + cmd + [self.keystr,]
+ fullcmd = (
+ [settime_bin, "-K", self._dir]
+ + cmd
+ + [
+ self.keystr,
+ ]
+ )
if not quiet:
- print('# ' + ' '.join(fullcmd))
+ print("# " + " ".join(fullcmd))
try:
p = Popen(fullcmd, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
if stderr:
raise Exception(str(stderr))
except Exception as e:
- raise Exception('unable to run %s: %s' %
- (settime_bin, str(e)))
+ raise Exception("unable to run %s: %s" % (settime_bin, str(e)))
self._origttl = None
for prop in dnskey._PROPS:
self._original[prop] = self._timestamps[prop]
self._changed[prop] = False
@classmethod
- def generate(cls, keygen_bin, randomdev, keys_dir, name, alg, keysize, sep,
- ttl, publish=None, activate=None, **kwargs):
- quiet = kwargs.get('quiet', False)
+ def generate(
+ cls,
+ keygen_bin,
+ randomdev,
+ keys_dir,
+ name,
+ alg,
+ keysize,
+ sep,
+ ttl,
+ publish=None,
+ activate=None,
+ **kwargs
+ ):
+ quiet = kwargs.get("quiet", False)
keygen_cmd = [keygen_bin, "-q", "-K", keys_dir, "-L", str(ttl)]
keygen_cmd.append(name)
if not quiet:
- print('# ' + ' '.join(keygen_cmd))
+ print("# " + " ".join(keygen_cmd))
p = Popen(keygen_cmd, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
if stderr:
- raise Exception('unable to generate key: ' + str(stderr))
+ raise Exception("unable to generate key: " + str(stderr))
try:
- keystr = stdout.splitlines()[0].decode('ascii')
+ keystr = stdout.splitlines()[0].decode("ascii")
newkey = dnskey(keystr, keys_dir, ttl)
return newkey
except Exception as e:
- raise Exception('unable to parse generated key: %s' % str(e))
+ raise Exception("unable to parse generated key: %s" % str(e))
def generate_successor(self, keygen_bin, randomdev, prepublish, **kwargs):
- quiet = kwargs.get('quiet', False)
+ quiet = kwargs.get("quiet", False)
if not self.inactive():
raise Exception("predecessor key %s has no inactive date" % self)
keygen_cmd += ["-i", str(prepublish)]
if not quiet:
- print('# ' + ' '.join(keygen_cmd))
+ print("# " + " ".join(keygen_cmd))
p = Popen(keygen_cmd, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
if stderr:
- raise Exception('unable to generate key: ' + stderr)
+ raise Exception("unable to generate key: " + stderr)
try:
- keystr = stdout.splitlines()[0].decode('ascii')
+ keystr = stdout.splitlines()[0].decode("ascii")
newkey = dnskey(keystr, self._dir, self.ttl)
return newkey
except:
- raise Exception('unable to generate successor for key %s' % self)
+ raise Exception("unable to generate successor for key %s" % self)
@staticmethod
def algstr(alg):
return time.strftime("%Y%m%d%H%M%S", t)
def setmeta(self, prop, secs, now, **kwargs):
- force = kwargs.get('force', False)
+ force = kwargs.get("force", False)
if self._timestamps[prop] == secs:
return
- if self._original[prop] is not None and \
- self._original[prop] < now and not force:
+ if (
+ self._original[prop] is not None
+ and self._original[prop] < now
+ and not force
+ ):
raise TimePast(self, prop, self._original[prop])
if secs is None:
- self._changed[prop] = False \
- if self._original[prop] is None else True
+ self._changed[prop] = False if self._original[prop] is None else True
self._delete[prop] = True
self._timestamps[prop] = None
self._timestamps[prop] = secs
self._times[prop] = t
self._fmttime[prop] = self.formattime(t)
- self._changed[prop] = False if \
- self._original[prop] == self._timestamps[prop] else True
+ self._changed[prop] = (
+ False if self._original[prop] == self._timestamps[prop] else True
+ )
def gettime(self, prop):
return self._times[prop]
self.ttl = ttl
def keytype(self):
- return ("KSK" if self.sep else "ZSK")
+ return "KSK" if self.sep else "ZSK"
def __str__(self):
- return ("%s/%s/%05d"
- % (self.name, self.algname(), self.keyid))
+ return "%s/%s/%05d" % (self.name, self.algname(), self.keyid)
def __repr__(self):
- return ("%s/%s/%05d (%s)"
- % (self.name, self.algname(), self.keyid,
- ("KSK" if self.sep else "ZSK")))
+ return "%s/%s/%05d (%s)" % (
+ self.name,
+ self.algname(),
+ self.keyid,
+ ("KSK" if self.sep else "ZSK"),
+ )
def date(self):
- return (self.activate() or self.publish() or self.created())
+ return self.activate() or self.publish() or self.created()
# keys are sorted first by zone name, then by algorithm. within
# the same name/algorithm, they are sorted according to their
return self.date() < other.date()
def check_prepub(self, output=None):
- def noop(*args, **kwargs): pass
+ def noop(*args, **kwargs):
+ pass
+
if not output:
output = noop
if not p:
if a > now:
- output("WARNING: Key %s is scheduled for\n"
- "\t activation but not for publication."
- % repr(self))
+ output(
+ "WARNING: Key %s is scheduled for\n"
+ "\t activation but not for publication." % repr(self)
+ )
return False
if p <= now and a <= now:
return True
if p == a:
- output("WARNING: %s is scheduled to be\n"
- "\t published and activated at the same time. This\n"
- "\t could result in a coverage gap if the zone was\n"
- "\t previously signed. Activation should be at least\n"
- "\t %s after publication."
- % (repr(self),
- dnskey.duration(self.ttl) or 'one DNSKEY TTL'))
+ output(
+ "WARNING: %s is scheduled to be\n"
+ "\t published and activated at the same time. This\n"
+ "\t could result in a coverage gap if the zone was\n"
+ "\t previously signed. Activation should be at least\n"
+ "\t %s after publication."
+ % (repr(self), dnskey.duration(self.ttl) or "one DNSKEY TTL")
+ )
return True
if a < p:
- output("WARNING: Key %s is active before it is published"
- % repr(self))
+ output("WARNING: Key %s is active before it is published" % repr(self))
return False
if self.ttl is not None and a - p < self.ttl:
- output("WARNING: Key %s is activated too soon\n"
- "\t after publication; this could result in coverage \n"
- "\t gaps due to resolver caches containing old data.\n"
- "\t Activation should be at least %s after\n"
- "\t publication."
- % (repr(self),
- dnskey.duration(self.ttl) or 'one DNSKEY TTL'))
+ output(
+ "WARNING: Key %s is activated too soon\n"
+ "\t after publication; this could result in coverage \n"
+ "\t gaps due to resolver caches containing old data.\n"
+ "\t Activation should be at least %s after\n"
+ "\t publication."
+ % (repr(self), dnskey.duration(self.ttl) or "one DNSKEY TTL")
+ )
return False
return True
- def check_postpub(self, output = None, timespan = None):
- def noop(*args, **kwargs): pass
+ def check_postpub(self, output=None, timespan=None):
+ def noop(*args, **kwargs):
+ pass
+
if output is None:
output = noop
if timespan is None:
output("WARNING: Key %s using default TTL." % repr(self))
- timespan = (60*60*24)
+ timespan = 60 * 60 * 24
now = time.time()
d = self.delete()
if not i:
if d > now:
- output("WARNING: Key %s is scheduled for\n"
- "\t deletion but not for inactivation." % repr(self))
+ output(
+ "WARNING: Key %s is scheduled for\n"
+ "\t deletion but not for inactivation." % repr(self)
+ )
return False
if d < now and i < now:
return True
if d < i:
- output("WARNING: Key %s is scheduled for\n"
- "\t deletion before inactivation."
- % repr(self))
+ output(
+ "WARNING: Key %s is scheduled for\n"
+ "\t deletion before inactivation." % repr(self)
+ )
return False
if d - i < timespan:
- output("WARNING: Key %s scheduled for\n"
- "\t deletion too soon after deactivation; this may \n"
- "\t result in coverage gaps due to resolver caches\n"
- "\t containing old data. Deletion should be at least\n"
- "\t %s after inactivation."
- % (repr(self), dnskey.duration(timespan)))
+ output(
+ "WARNING: Key %s scheduled for\n"
+ "\t deletion too soon after deactivation; this may \n"
+ "\t result in coverage gaps due to resolver caches\n"
+ "\t containing old data. Deletion should be at least\n"
+ "\t %s after inactivation." % (repr(self), dnskey.duration(timespan))
+ )
return False
return True
if not secs:
return None
- units = [("year", 60*60*24*365),
- ("month", 60*60*24*30),
- ("day", 60*60*24),
- ("hour", 60*60),
- ("minute", 60),
- ("second", 1)]
+ units = [
+ ("year", 60 * 60 * 24 * 365),
+ ("month", 60 * 60 * 24 * 30),
+ ("day", 60 * 60 * 24),
+ ("hour", 60 * 60),
+ ("minute", 60),
+ ("second", 1),
+ ]
output = []
for unit in units:
output.append("%d %s%s" % (v, unit[0], "s" if v > 1 else ""))
return ", ".join(output)
-
_kdict = None
def __init__(self, kdict):
- properties = ["SyncPublish", "Publish", "SyncDelete",
- "Activate", "Inactive", "Delete"]
+ properties = [
+ "SyncPublish",
+ "Publish",
+ "SyncDelete",
+ "Activate",
+ "Inactive",
+ "Delete",
+ ]
self._kdict = kdict
for zone in kdict.zones():
self._zones.add(zone)
else:
self._Z[zone][alg].append(e)
- self._K[zone][alg] = sorted(self._K[zone][alg],
- key=lambda event: event.when)
- self._Z[zone][alg] = sorted(self._Z[zone][alg],
- key=lambda event: event.when)
+ self._K[zone][alg] = sorted(
+ self._K[zone][alg], key=lambda event: event.when
+ )
+ self._Z[zone][alg] = sorted(
+ self._Z[zone][alg], key=lambda event: event.when
+ )
# scan events per zone, algorithm, and key type, in order of
# occurrence, noting inconsistent states when found
- def coverage(self, zone, keytype, until, output = None):
- def noop(*args, **kwargs): pass
+ def coverage(self, zone, keytype, until, output=None):
+ def noop(*args, **kwargs):
+ pass
+
if not output:
output = noop
output("ERROR: No key events found")
return False
- return (kok and zok)
+ return kok and zok
def checkzone(self, zone, keytype, until, output):
allok = True
kz = self._Z[zone]
for alg in kz.keys():
- output("Checking scheduled %s events for zone %s, "
- "algorithm %s..." %
- (keytype, zone, dnskey.algstr(alg)))
+ output(
+ "Checking scheduled %s events for zone %s, "
+ "algorithm %s..." % (keytype, zone, dnskey.algstr(alg))
+ )
ok = eventlist.checkset(kz[alg], keytype, until, output)
if ok:
output("No errors found")
eventsfound = True
# add event to current group
- if (not group or group[0].when == event.when):
+ if not group or group[0].when == event.when:
group.append(event)
# if we're at the end of the list, we're done. if
# we've found an event with a later time, start a new group
- if (group[0].when != event.when):
+ if group[0].when != event.when:
groups.append(group)
group = list()
group.append(event)
active = published = None
for group in groups:
- if (until and calendar.timegm(group[0].when) > until):
- output("Ignoring events after %s" %
- time.strftime("%a %b %d %H:%M:%S UTC %Y",
- time.gmtime(until)))
+ if until and calendar.timegm(group[0].when) > until:
+ output(
+ "Ignoring events after %s"
+ % time.strftime("%a %b %d %H:%M:%S UTC %Y", time.gmtime(until))
+ )
return True
for event in group:
output("ERROR: No %s's are active after this event" % keytype)
return False
elif not published:
- output("ERROR: No %s's are published after this event"
- % keytype)
+ output("ERROR: No %s's are published after this event" % keytype)
return False
elif not published.intersection(active):
- output("ERROR: No %s's are both active and published "
- "after this event" % keytype)
+ output(
+ "ERROR: No %s's are both active and published "
+ "after this event" % keytype
+ )
return False
return True
-
# Class keydict
########################################################################
class keydict:
- """ A dictionary of keys, indexed by name, algorithm, and key id """
+ """A dictionary of keys, indexed by name, algorithm, and key id"""
_keydict = defaultdict(lambda: defaultdict(dict))
_defttl = None
_missing = []
def __init__(self, dp=None, **kwargs):
- self._defttl = kwargs.get('keyttl', None)
- zones = kwargs.get('zones', None)
+ self._defttl = kwargs.get("keyttl", None)
+ zones = kwargs.get("zones", None)
if not zones:
- path = kwargs.get('path',None) or '.'
+ path = kwargs.get("path", None) or "."
self.readall(path)
else:
for zone in zones:
- if 'path' in kwargs and kwargs['path'] is not None:
- path = kwargs['path']
+ if "path" in kwargs and kwargs["path"] is not None:
+ path = kwargs["path"]
else:
- path = dp and dp.policy(zone).directory or '.'
+ path = dp and dp.policy(zone).directory or "."
if not self.readone(path, zone):
self._missing.append(zone)
def readall(self, path):
- files = glob.glob(os.path.join(path, '*.private'))
+ files = glob.glob(os.path.join(path, "*.private"))
for infile in files:
key = dnskey(infile, path, self._defttl)
self._keydict[key.name][key.alg][key.keyid] = key
def readone(self, path, zone):
- if not zone.endswith('.'):
- zone += '.'
- match='K' + zone + '+*.private'
+ if not zone.endswith("."):
+ zone += "."
+ match = "K" + zone + "+*.private"
files = glob.glob(os.path.join(path, match))
found = False
for infile in files:
key = dnskey(infile, path, self._defttl)
- if key.fullname != zone: # shouldn't ever happen
+ if key.fullname != zone: # shouldn't ever happen
continue
- keyname=key.name if zone != '.' else '.'
+ keyname = key.name if zone != "." else "."
self._keydict[keyname][key.alg][key.keyid] = key
found = True
return self._keydict[name]
def zones(self):
- return (self._keydict.keys())
+ return self._keydict.keys()
def algorithms(self, zone):
- return (self._keydict[zone].keys())
+ return self._keydict[zone].keys()
def keys(self, zone, alg):
- return (self._keydict[zone][alg].keys())
+ return self._keydict[zone][alg].keys()
def missing(self):
- return (self._missing)
+ return self._missing
# Class keyevent
########################################################################
class keyevent:
- """ A discrete key event, e.g., Publish, Activate, Inactive, Delete,
+ """A discrete key event, e.g., Publish, Activate, Inactive, Delete,
etc. Stores the date of the event, and identifying information
about the key to which the event will occur."""
self.keyid = key.keyid
def __repr__(self):
- return repr((self.when, self.what, self.keyid, self.sep,
- self.zone, self.alg))
+ return repr((self.when, self.what, self.keyid, self.sep, self.zone, self.alg))
def showtime(self):
return time.strftime("%a %b %d %H:%M:%S UTC %Y", self.when)
# update sets of active and published keys, based on
# the contents of this keyevent
- def status(self, active, published, output = None):
- def noop(*args, **kwargs): pass
+ def status(self, active, published, output=None):
+ def noop(*args, **kwargs):
+ pass
+
if not output:
output = noop
published.add(self.keyid)
elif self.what == "Inactive":
if self.keyid not in active:
- output("\tWARNING: %s scheduled to become inactive "
- "before it is active"
- % repr(self.key))
+ output(
+ "\tWARNING: %s scheduled to become inactive "
+ "before it is active" % repr(self.key)
+ )
else:
active.remove(self.keyid)
elif self.what == "Delete":
if self.keyid in published:
published.remove(self.keyid)
else:
- output("WARNING: key %s is scheduled for deletion "
- "before it is published" % repr(self.key))
+ output(
+ "WARNING: key %s is scheduled for deletion "
+ "before it is published" % repr(self.key)
+ )
elif self.what == "Revoke":
# We don't need to worry about the logic of this one;
# just stop counting this key as either active or published
import os, sys, argparse, glob, re, time, calendar, pprint
from collections import defaultdict
-prog='dnssec-keymgr'
+prog = "dnssec-keymgr"
from isc import dnskey, keydict, keyseries, policy, parsetab, utils
print(*args, **kwargs)
sys.exit(1)
+
############################################################################
# find the location of an external command
############################################################################
def set_path(command, default=None):
- """ find the location of a specified command. If a default is supplied,
+ """find the location of a specified command. If a default is supplied,
exists and it's an executable, we use it; otherwise we search PATH
for an alternative.
:param command: command to look for
return fpath
+
############################################################################
# parse arguments
############################################################################
def parse_args():
- """ Read command line arguments, returns 'args' object
+ """Read command line arguments, returns 'args' object
:return: args object properly prepared
"""
- keygen = set_path('dnssec-keygen',
- os.path.join(utils.prefix('sbin'), 'dnssec-keygen'))
- settime = set_path('dnssec-settime',
- os.path.join(utils.prefix('sbin'), 'dnssec-settime'))
-
- parser = argparse.ArgumentParser(description=prog + ': schedule '
- 'DNSSEC key rollovers according to a '
- 'pre-defined policy')
-
- parser.add_argument('zone', type=str, nargs='*', default=None,
- help='Zone(s) to which the policy should be applied ' +
- '(default: all zones in the directory)')
- parser.add_argument('-K', dest='path', type=str,
- help='Directory containing keys', metavar='dir')
- parser.add_argument('-c', dest='policyfile', type=str,
- help='Policy definition file', metavar='file')
- parser.add_argument('-g', dest='keygen', default=keygen, type=str,
- help='Path to \'dnssec-keygen\'',
- metavar='path')
- parser.add_argument('-r', dest='randomdev', type=str, default=None,
- help='DEPRECATED',
- metavar='path')
- parser.add_argument('-s', dest='settime', default=settime, type=str,
- help='Path to \'dnssec-settime\'',
- metavar='path')
- parser.add_argument('-k', dest='no_zsk',
- action='store_true', default=False,
- help='Only apply policy to key-signing keys (KSKs)')
- parser.add_argument('-z', dest='no_ksk',
- action='store_true', default=False,
- help='Only apply policy to zone-signing keys (ZSKs)')
- parser.add_argument('-f', '--force', dest='force', action='store_true',
- default=False, help='Force updates to key events '+
- 'even if they are in the past')
- parser.add_argument('-q', '--quiet', dest='quiet', action='store_true',
- default=False, help='Update keys silently')
- parser.add_argument('-v', '--version', action='version',
- version=utils.version)
+ keygen = set_path(
+ "dnssec-keygen", os.path.join(utils.prefix("sbin"), "dnssec-keygen")
+ )
+ settime = set_path(
+ "dnssec-settime", os.path.join(utils.prefix("sbin"), "dnssec-settime")
+ )
+
+ parser = argparse.ArgumentParser(
+ description=prog + ": schedule "
+ "DNSSEC key rollovers according to a "
+ "pre-defined policy"
+ )
+
+ parser.add_argument(
+ "zone",
+ type=str,
+ nargs="*",
+ default=None,
+ help="Zone(s) to which the policy should be applied "
+ + "(default: all zones in the directory)",
+ )
+ parser.add_argument(
+ "-K", dest="path", type=str, help="Directory containing keys", metavar="dir"
+ )
+ parser.add_argument(
+ "-c", dest="policyfile", type=str, help="Policy definition file", metavar="file"
+ )
+ parser.add_argument(
+ "-g",
+ dest="keygen",
+ default=keygen,
+ type=str,
+ help="Path to 'dnssec-keygen'",
+ metavar="path",
+ )
+ parser.add_argument(
+ "-r",
+ dest="randomdev",
+ type=str,
+ default=None,
+ help="DEPRECATED",
+ metavar="path",
+ )
+ parser.add_argument(
+ "-s",
+ dest="settime",
+ default=settime,
+ type=str,
+ help="Path to 'dnssec-settime'",
+ metavar="path",
+ )
+ parser.add_argument(
+ "-k",
+ dest="no_zsk",
+ action="store_true",
+ default=False,
+ help="Only apply policy to key-signing keys (KSKs)",
+ )
+ parser.add_argument(
+ "-z",
+ dest="no_ksk",
+ action="store_true",
+ default=False,
+ help="Only apply policy to zone-signing keys (ZSKs)",
+ )
+ parser.add_argument(
+ "-f",
+ "--force",
+ dest="force",
+ action="store_true",
+ default=False,
+ help="Force updates to key events " + "even if they are in the past",
+ )
+ parser.add_argument(
+ "-q",
+ "--quiet",
+ dest="quiet",
+ action="store_true",
+ default=False,
+ help="Update keys silently",
+ )
+ parser.add_argument("-v", "--version", action="version", version=utils.version)
args = parser.parse_args()
if args.randomdev:
fatal("ERROR: -r option has been deprecated.")
-
+
if args.no_zsk and args.no_ksk:
fatal("ERROR: -z and -k cannot be used together.")
if not os.path.exists(args.policyfile):
fatal('ERROR: Policy file "%s" not found' % args.policyfile)
else:
- args.policyfile = os.path.join(utils.sysconfdir,
- 'dnssec-policy.conf')
+ args.policyfile = os.path.join(utils.sysconfdir, "dnssec-policy.conf")
if not os.path.exists(args.policyfile):
args.policyfile = None
return args
+
############################################################################
# main
############################################################################
# As we may have specific locations for the binaries, we put that info
# into a context object that can be passed around
- context = {'keygen_path': args.keygen,
- 'settime_path': args.settime,
- 'keys_path': args.path,
- 'randomdev': args.randomdev}
+ context = {
+ "keygen_path": args.keygen,
+ "settime_path": args.settime,
+ "keys_path": args.path,
+ "randomdev": args.randomdev,
+ }
try:
dp = policy.dnssec_policy(args.policyfile)
except Exception as e:
- fatal('Unable to load DNSSEC policy: ' + str(e))
+ fatal("Unable to load DNSSEC policy: " + str(e))
try:
kd = keydict(dp, path=args.path, zones=args.zone)
except Exception as e:
- fatal('Unable to build key dictionary: ' + str(e))
+ fatal("Unable to build key dictionary: " + str(e))
try:
ks = keyseries(kd, context=context)
except Exception as e:
- fatal('Unable to build key series: ' + str(e))
+ fatal("Unable to build key series: " + str(e))
try:
- ks.enforce_policy(dp, ksk=args.no_zsk, zsk=args.no_ksk,
- force=args.force, quiet=args.quiet)
+ ks.enforce_policy(
+ dp, ksk=args.no_zsk, zsk=args.no_ksk, force=args.force, quiet=args.quiet
+ )
except Exception as e:
- fatal('Unable to apply policy: ' + str(e))
+ fatal("Unable to apply policy: " + str(e))
for k in keys.values():
if k.sep:
if not (k.delete() and k.delete() < now):
- self._K[zone][alg].append(k)
+ self._K[zone][alg].append(k)
else:
if not (k.delete() and k.delete() < now):
- self._Z[zone][alg].append(k)
+ self._Z[zone][alg].append(k)
self._K[zone][alg].sort()
self._Z[zone][alg].sort()
print("%s" % repr(k))
def fixseries(self, keys, policy, now, **kwargs):
- force = kwargs.get('force', False)
+ force = kwargs.get("force", False)
if not keys:
return
prev = key
# if we haven't got sufficient coverage, create successor key(s)
- while rp and prev.inactive() and \
- prev.inactive() < now + policy.coverage:
+ while rp and prev.inactive() and prev.inactive() < now + policy.coverage:
# commit changes to predecessor: a successor can only be
# generated if Inactive has been set in the predecessor key
- prev.commit(self._context['settime_path'], **kwargs)
- key = prev.generate_successor(self._context['keygen_path'],
- self._context['randomdev'],
- prepub, **kwargs)
+ prev.commit(self._context["settime_path"], **kwargs)
+ key = prev.generate_successor(
+ self._context["keygen_path"],
+ self._context["randomdev"],
+ prepub,
+ **kwargs
+ )
key.setinactive(key.activate() + rp, **kwargs)
key.setdelete(key.inactive() + postpub, **kwargs)
# commit changes
for key in keys:
- key.commit(self._context['settime_path'], **kwargs)
-
+ key.commit(self._context["settime_path"], **kwargs)
def enforce_policy(self, policies, now=time.time(), **kwargs):
# If zones is provided as a parameter, use that list.
# If not, use what we have in this object
- zones = kwargs.get('zones', self._zones)
- keys_dir = kwargs.get('dir', self._context.get('keys_path', None))
- force = kwargs.get('force', False)
+ zones = kwargs.get("zones", self._zones)
+ keys_dir = kwargs.get("dir", self._context.get("keys_path", None))
+ force = kwargs.get("force", False)
for zone in zones:
collections = []
policy = policies.policy(zone)
- keys_dir = keys_dir or policy.directory or '.'
+ keys_dir = keys_dir or policy.directory or "."
alg = policy.algorithm
algnum = dnskey.algnum(alg)
- if 'ksk' not in kwargs or not kwargs['ksk']:
+ if "ksk" not in kwargs or not kwargs["ksk"]:
if len(self._Z[zone][algnum]) == 0:
- k = dnskey.generate(self._context['keygen_path'],
- self._context['randomdev'],
- keys_dir, zone, alg,
- policy.zsk_keysize, False,
- policy.keyttl or 3600,
- **kwargs)
+ k = dnskey.generate(
+ self._context["keygen_path"],
+ self._context["randomdev"],
+ keys_dir,
+ zone,
+ alg,
+ policy.zsk_keysize,
+ False,
+ policy.keyttl or 3600,
+ **kwargs
+ )
self._Z[zone][algnum].append(k)
collections.append(self._Z[zone])
- if 'zsk' not in kwargs or not kwargs['zsk']:
+ if "zsk" not in kwargs or not kwargs["zsk"]:
if len(self._K[zone][algnum]) == 0:
- k = dnskey.generate(self._context['keygen_path'],
- self._context['randomdev'],
- keys_dir, zone, alg,
- policy.ksk_keysize, True,
- policy.keyttl or 3600,
- **kwargs)
+ k = dnskey.generate(
+ self._context["keygen_path"],
+ self._context["randomdev"],
+ keys_dir,
+ zone,
+ alg,
+ policy.ksk_keysize,
+ True,
+ policy.keyttl or 3600,
+ **kwargs
+ )
self._K[zone][algnum].append(k)
collections.append(self._K[zone])
try:
self.fixseries(keys, policy, now, **kwargs)
except Exception as e:
- raise Exception('%s/%s: %s' %
- (zone, dnskey.algstr(algnum), str(e)))
+ raise Exception(
+ "%s/%s: %s" % (zone, dnskey.algstr(algnum), str(e))
+ )
class KeyZoneException(Exception):
pass
+
########################################################################
# class keyzone
########################################################################
if not name:
return
- if not czpath or not os.path.isfile(czpath) \
- or not os.access(czpath, os.X_OK):
+ if not czpath or not os.path.isfile(czpath) or not os.access(czpath, os.X_OK):
raise KeyZoneException('"named-compilezone" not found')
return
maxttl = keyttl = None
- fp, _ = Popen([czpath, "-o", "-", name, filename],
- stdout=PIPE, stderr=PIPE).communicate()
+ fp, _ = Popen(
+ [czpath, "-o", "-", name, filename], stdout=PIPE, stderr=PIPE
+ ).communicate()
for line in fp.splitlines():
if type(line) is not str:
- line = line.decode('ascii')
- if re.search('^[:space:]*;', line):
+ line = line.decode("ascii")
+ if re.search("^[:space:]*;", line):
continue
fields = line.split()
if not maxttl or int(fields[1]) > maxttl:
# PolicyLex: a lexer for the policy file syntax.
############################################################################
class PolicyLex:
- reserved = ('POLICY',
- 'ALGORITHM_POLICY',
- 'ZONE',
- 'ALGORITHM',
- 'DIRECTORY',
- 'KEYTTL',
- 'KEY_SIZE',
- 'ROLL_PERIOD',
- 'PRE_PUBLISH',
- 'POST_PUBLISH',
- 'COVERAGE',
- 'STANDBY',
- 'NONE')
-
- tokens = reserved + ('DATESUFFIX',
- 'KEYTYPE',
- 'ALGNAME',
- 'STR',
- 'QSTRING',
- 'NUMBER',
- 'LBRACE',
- 'RBRACE',
- 'SEMI')
+ reserved = (
+ "POLICY",
+ "ALGORITHM_POLICY",
+ "ZONE",
+ "ALGORITHM",
+ "DIRECTORY",
+ "KEYTTL",
+ "KEY_SIZE",
+ "ROLL_PERIOD",
+ "PRE_PUBLISH",
+ "POST_PUBLISH",
+ "COVERAGE",
+ "STANDBY",
+ "NONE",
+ )
+
+ tokens = reserved + (
+ "DATESUFFIX",
+ "KEYTYPE",
+ "ALGNAME",
+ "STR",
+ "QSTRING",
+ "NUMBER",
+ "LBRACE",
+ "RBRACE",
+ "SEMI",
+ )
reserved_map = {}
- t_ignore = ' \t'
- t_ignore_olcomment = r'(//|\#).*'
+ t_ignore = " \t"
+ t_ignore_olcomment = r"(//|\#).*"
- t_LBRACE = r'\{'
- t_RBRACE = r'\}'
- t_SEMI = r';';
+ t_LBRACE = r"\{"
+ t_RBRACE = r"\}"
+ t_SEMI = r";"
def t_newline(self, t):
- r'\n+'
+ r"\n+"
t.lexer.lineno += t.value.count("\n")
def t_comment(self, t):
- r'/\*(.|\n)*?\*/'
- t.lexer.lineno += t.value.count('\n')
+ r"/\*(.|\n)*?\*/"
+ t.lexer.lineno += t.value.count("\n")
def t_DATESUFFIX(self, t):
- r'(?<=[0-9 \t])(y(?:ears|ear|ea|e)?|mo(?:nths|nth|nt|n)?|w(?:eeks|eek|ee|e)?|d(?:ays|ay|a)?|h(?:ours|our|ou|o)?|mi(?:nutes|nute|nut|nu|n)?|s(?:econds|econd|econ|eco|ec|e)?)\b'
- t.value = re.match(r'(y|mo|w|d|h|mi|s)([a-z]*)', t.value, re.IGNORECASE).group(1).lower()
+ r"(?<=[0-9 \t])(y(?:ears|ear|ea|e)?|mo(?:nths|nth|nt|n)?|w(?:eeks|eek|ee|e)?|d(?:ays|ay|a)?|h(?:ours|our|ou|o)?|mi(?:nutes|nute|nut|nu|n)?|s(?:econds|econd|econ|eco|ec|e)?)\b"
+ t.value = (
+ re.match(r"(y|mo|w|d|h|mi|s)([a-z]*)", t.value, re.IGNORECASE)
+ .group(1)
+ .lower()
+ )
return t
def t_KEYTYPE(self, t):
- r'\b(KSK|ZSK)\b'
+ r"\b(KSK|ZSK)\b"
t.value = t.value.upper()
return t
def t_ALGNAME(self, t):
- r'\b(DH|ECC|RSASHA1|NSEC3RSASHA1|RSASHA256|RSASHA512|ECDSAP256SHA256|ECDSAP384SHA384|ED25519|ED448)\b'
+ r"\b(DH|ECC|RSASHA1|NSEC3RSASHA1|RSASHA256|RSASHA512|ECDSAP256SHA256|ECDSAP384SHA384|ED25519|ED448)\b"
t.value = t.value.upper()
return t
def t_STR(self, t):
- r'[A-Za-z._-][\w._-]*'
+ r"[A-Za-z._-][\w._-]*"
t.type = self.reserved_map.get(t.value, "STR")
return t
return t
def t_NUMBER(self, t):
- r'\d+'
+ r"\d+"
t.value = int(t.value)
return t
t.lexer.skip(1)
def __init__(self, **kwargs):
- if 'maketrans' in dir(str):
- trans = str.maketrans('_', '-')
+ if "maketrans" in dir(str):
+ trans = str.maketrans("_", "-")
else:
- trans = maketrans('_', '-')
+ trans = maketrans("_", "-")
for r in self.reserved:
self.reserved_map[r.lower().translate(trans)] = r
- self.lexer = lex.lex(object=self, reflags=re.VERBOSE|re.IGNORECASE, **kwargs)
+ self.lexer = lex.lex(object=self, reflags=re.VERBOSE | re.IGNORECASE, **kwargs)
def test(self, text):
self.lexer.input(text)
break
print(t)
+
############################################################################
# Policy: this object holds a set of DNSSEC policy settings.
############################################################################
keyttl = None
coverage = None
directory = None
- valid_key_sz_per_algo = {'RSASHA1': [1024, 4096],
- 'NSEC3RSASHA1': [512, 4096],
- 'RSASHA256': [1024, 4096],
- 'RSASHA512': [1024, 4096],
- 'ECDSAP256SHA256': None,
- 'ECDSAP384SHA384': None,
- 'ED25519': None,
- 'ED448': None}
+ valid_key_sz_per_algo = {
+ "RSASHA1": [1024, 4096],
+ "NSEC3RSASHA1": [512, 4096],
+ "RSASHA256": [1024, 4096],
+ "RSASHA512": [1024, 4096],
+ "ECDSAP256SHA256": None,
+ "ECDSAP384SHA384": None,
+ "ED25519": None,
+ "ED448": None,
+ }
def __init__(self, name=None, algorithm=None, parent=None):
self.name = name
pass
def __repr__(self):
- return ("%spolicy %s:\n"
- "\tinherits %s\n"
- "\tdirectory %s\n"
- "\talgorithm %s\n"
- "\tcoverage %s\n"
- "\tksk_keysize %s\n"
- "\tzsk_keysize %s\n"
- "\tksk_rollperiod %s\n"
- "\tzsk_rollperiod %s\n"
- "\tksk_prepublish %s\n"
- "\tksk_postpublish %s\n"
- "\tzsk_prepublish %s\n"
- "\tzsk_postpublish %s\n"
- "\tksk_standby %s\n"
- "\tzsk_standby %s\n"
- "\tkeyttl %s\n"
- %
- ((self.is_constructed and 'constructed ' or \
- self.is_zone and 'zone ' or \
- self.is_alg and 'algorithm ' or ''),
- self.name or 'UNKNOWN',
- self.parent and self.parent.name or 'None',
- self.directory and ('"' + str(self.directory) + '"') or 'None',
- self.algorithm or 'None',
- self.coverage and str(self.coverage) or 'None',
- self.ksk_keysize and str(self.ksk_keysize) or 'None',
- self.zsk_keysize and str(self.zsk_keysize) or 'None',
- self.ksk_rollperiod and str(self.ksk_rollperiod) or 'None',
- self.zsk_rollperiod and str(self.zsk_rollperiod) or 'None',
- self.ksk_prepublish and str(self.ksk_prepublish) or 'None',
- self.ksk_postpublish and str(self.ksk_postpublish) or 'None',
- self.zsk_prepublish and str(self.zsk_prepublish) or 'None',
- self.zsk_postpublish and str(self.zsk_postpublish) or 'None',
- self.ksk_standby and str(self.ksk_standby) or 'None',
- self.zsk_standby and str(self.zsk_standby) or 'None',
- self.keyttl and str(self.keyttl) or 'None'))
+ return (
+ "%spolicy %s:\n"
+ "\tinherits %s\n"
+ "\tdirectory %s\n"
+ "\talgorithm %s\n"
+ "\tcoverage %s\n"
+ "\tksk_keysize %s\n"
+ "\tzsk_keysize %s\n"
+ "\tksk_rollperiod %s\n"
+ "\tzsk_rollperiod %s\n"
+ "\tksk_prepublish %s\n"
+ "\tksk_postpublish %s\n"
+ "\tzsk_prepublish %s\n"
+ "\tzsk_postpublish %s\n"
+ "\tksk_standby %s\n"
+ "\tzsk_standby %s\n"
+ "\tkeyttl %s\n"
+ % (
+ (
+ self.is_constructed
+ and "constructed "
+ or self.is_zone
+ and "zone "
+ or self.is_alg
+ and "algorithm "
+ or ""
+ ),
+ self.name or "UNKNOWN",
+ self.parent and self.parent.name or "None",
+ self.directory and ('"' + str(self.directory) + '"') or "None",
+ self.algorithm or "None",
+ self.coverage and str(self.coverage) or "None",
+ self.ksk_keysize and str(self.ksk_keysize) or "None",
+ self.zsk_keysize and str(self.zsk_keysize) or "None",
+ self.ksk_rollperiod and str(self.ksk_rollperiod) or "None",
+ self.zsk_rollperiod and str(self.zsk_rollperiod) or "None",
+ self.ksk_prepublish and str(self.ksk_prepublish) or "None",
+ self.ksk_postpublish and str(self.ksk_postpublish) or "None",
+ self.zsk_prepublish and str(self.zsk_prepublish) or "None",
+ self.zsk_postpublish and str(self.zsk_postpublish) or "None",
+ self.ksk_standby and str(self.ksk_standby) or "None",
+ self.zsk_standby and str(self.zsk_standby) or "None",
+ self.keyttl and str(self.keyttl) or "None",
+ )
+ )
def __verify_size(self, key_size, size_range):
- return (size_range[0] <= key_size <= size_range[1])
+ return size_range[0] <= key_size <= size_range[1]
def get_name(self):
return self.name
return self.is_constructed
def validate(self):
- """ Check if the values in the policy make sense
+ """Check if the values in the policy make sense
:return: True/False if the policy passes validation
"""
- if self.ksk_rollperiod and \
- self.ksk_prepublish is not None and \
- self.ksk_prepublish > self.ksk_rollperiod:
+ if (
+ self.ksk_rollperiod
+ and self.ksk_prepublish is not None
+ and self.ksk_prepublish > self.ksk_rollperiod
+ ):
print(self.ksk_rollperiod)
- return (False,
- ('KSK pre-publish period (%d) exceeds rollover period %d'
- % (self.ksk_prepublish, self.ksk_rollperiod)))
-
- if self.ksk_rollperiod and \
- self.ksk_postpublish is not None and \
- self.ksk_postpublish > self.ksk_rollperiod:
- return (False,
- ('KSK post-publish period (%d) exceeds rollover period %d'
- % (self.ksk_postpublish, self.ksk_rollperiod)))
-
- if self.zsk_rollperiod and \
- self.zsk_prepublish is not None and \
- self.zsk_prepublish >= self.zsk_rollperiod:
- return (False,
- ('ZSK pre-publish period (%d) exceeds rollover period %d'
- % (self.zsk_prepublish, self.zsk_rollperiod)))
-
- if self.zsk_rollperiod and \
- self.zsk_postpublish is not None and \
- self.zsk_postpublish >= self.zsk_rollperiod:
- return (False,
- ('ZSK post-publish period (%d) exceeds rollover period %d'
- % (self.zsk_postpublish, self.zsk_rollperiod)))
-
- if self.ksk_rollperiod and \
- self.ksk_prepublish and self.ksk_postpublish and \
- self.ksk_prepublish + self.ksk_postpublish >= self.ksk_rollperiod:
- return (False,
- (('KSK pre/post-publish periods (%d/%d) ' +
- 'combined exceed rollover period %d') %
- (self.ksk_prepublish,
- self.ksk_postpublish,
- self.ksk_rollperiod)))
-
- if self.zsk_rollperiod and \
- self.zsk_prepublish and self.zsk_postpublish and \
- self.zsk_prepublish + self.zsk_postpublish >= self.zsk_rollperiod:
- return (False,
- (('ZSK pre/post-publish periods (%d/%d) ' +
- 'combined exceed rollover period %d') %
- (self.zsk_prepublish,
- self.zsk_postpublish,
- self.zsk_rollperiod)))
+ return (
+ False,
+ (
+ "KSK pre-publish period (%d) exceeds rollover period %d"
+ % (self.ksk_prepublish, self.ksk_rollperiod)
+ ),
+ )
+
+ if (
+ self.ksk_rollperiod
+ and self.ksk_postpublish is not None
+ and self.ksk_postpublish > self.ksk_rollperiod
+ ):
+ return (
+ False,
+ (
+ "KSK post-publish period (%d) exceeds rollover period %d"
+ % (self.ksk_postpublish, self.ksk_rollperiod)
+ ),
+ )
+
+ if (
+ self.zsk_rollperiod
+ and self.zsk_prepublish is not None
+ and self.zsk_prepublish >= self.zsk_rollperiod
+ ):
+ return (
+ False,
+ (
+ "ZSK pre-publish period (%d) exceeds rollover period %d"
+ % (self.zsk_prepublish, self.zsk_rollperiod)
+ ),
+ )
+
+ if (
+ self.zsk_rollperiod
+ and self.zsk_postpublish is not None
+ and self.zsk_postpublish >= self.zsk_rollperiod
+ ):
+ return (
+ False,
+ (
+ "ZSK post-publish period (%d) exceeds rollover period %d"
+ % (self.zsk_postpublish, self.zsk_rollperiod)
+ ),
+ )
+
+ if (
+ self.ksk_rollperiod
+ and self.ksk_prepublish
+ and self.ksk_postpublish
+ and self.ksk_prepublish + self.ksk_postpublish >= self.ksk_rollperiod
+ ):
+ return (
+ False,
+ (
+ (
+ "KSK pre/post-publish periods (%d/%d) "
+ + "combined exceed rollover period %d"
+ )
+ % (self.ksk_prepublish, self.ksk_postpublish, self.ksk_rollperiod)
+ ),
+ )
+
+ if (
+ self.zsk_rollperiod
+ and self.zsk_prepublish
+ and self.zsk_postpublish
+ and self.zsk_prepublish + self.zsk_postpublish >= self.zsk_rollperiod
+ ):
+ return (
+ False,
+ (
+ (
+ "ZSK pre/post-publish periods (%d/%d) "
+ + "combined exceed rollover period %d"
+ )
+ % (self.zsk_prepublish, self.zsk_postpublish, self.zsk_rollperiod)
+ ),
+ )
if self.algorithm is not None:
# Validate the key size
if key_sz_range is not None:
# Verify KSK
if not self.__verify_size(self.ksk_keysize, key_sz_range):
- return False, 'KSK key size %d outside valid range %s' \
- % (self.ksk_keysize, key_sz_range)
+ return False, "KSK key size %d outside valid range %s" % (
+ self.ksk_keysize,
+ key_sz_range,
+ )
# Verify ZSK
if not self.__verify_size(self.zsk_keysize, key_sz_range):
- return False, 'ZSK key size %d outside valid range %s' \
- % (self.zsk_keysize, key_sz_range)
-
- if self.algorithm in ['ECDSAP256SHA256', \
- 'ECDSAP384SHA384', \
- 'ED25519', \
- 'ED448']:
+ return False, "ZSK key size %d outside valid range %s" % (
+ self.zsk_keysize,
+ key_sz_range,
+ )
+
+ if self.algorithm in [
+ "ECDSAP256SHA256",
+ "ECDSAP384SHA384",
+ "ED25519",
+ "ED448",
+ ]:
self.ksk_keysize = None
self.zsk_keysize = None
- return True, ''
+ return True, ""
+
############################################################################
# dnssec_policy:
class PolicyException(Exception):
pass
+
class dnssec_policy:
alg_policy = {}
named_policy = {}
def __init__(self, filename=None, **kwargs):
self.plex = PolicyLex()
self.tokens = self.plex.tokens
- if 'debug' not in kwargs:
- kwargs['debug'] = False
- if 'write_tables' not in kwargs:
- kwargs['write_tables'] = False
+ if "debug" not in kwargs:
+ kwargs["debug"] = False
+ if "write_tables" not in kwargs:
+ kwargs["write_tables"] = False
self.parser = yacc.yacc(module=self, **kwargs)
# set defaults
- self.setup('''policy global { algorithm rsasha256;
+ self.setup(
+ """policy global { algorithm rsasha256;
key-size ksk 2048;
key-size zsk 2048;
roll-period ksk 0;
standby zsk 0;
keyttl 1h;
coverage 6mo; };
- policy default { policy global; };''')
+ policy default { policy global; };"""
+ )
p = Policy()
p.algorithm = None
p.is_alg = True
- p.ksk_keysize = 2048;
- p.zsk_keysize = 2048;
+ p.ksk_keysize = 2048
+ p.zsk_keysize = 2048
# set default algorithm policies
# these can use default settings
- self.alg_policy['RSASHA1'] = copy(p)
- self.alg_policy['RSASHA1'].algorithm = "RSASHA1"
- self.alg_policy['RSASHA1'].name = "RSASHA1"
-
- self.alg_policy['NSEC3RSASHA1'] = copy(p)
- self.alg_policy['NSEC3RSASHA1'].algorithm = "NSEC3RSASHA1"
- self.alg_policy['NSEC3RSASHA1'].name = "NSEC3RSASHA1"
-
- self.alg_policy['RSASHA256'] = copy(p)
- self.alg_policy['RSASHA256'].algorithm = "RSASHA256"
- self.alg_policy['RSASHA256'].name = "RSASHA256"
-
- self.alg_policy['RSASHA512'] = copy(p)
- self.alg_policy['RSASHA512'].algorithm = "RSASHA512"
- self.alg_policy['RSASHA512'].name = "RSASHA512"
-
- self.alg_policy['ECDSAP256SHA256'] = copy(p)
- self.alg_policy['ECDSAP256SHA256'].algorithm = "ECDSAP256SHA256"
- self.alg_policy['ECDSAP256SHA256'].name = "ECDSAP256SHA256"
- self.alg_policy['ECDSAP256SHA256'].ksk_keysize = None;
- self.alg_policy['ECDSAP256SHA256'].zsk_keysize = None;
-
- self.alg_policy['ECDSAP384SHA384'] = copy(p)
- self.alg_policy['ECDSAP384SHA384'].algorithm = "ECDSAP384SHA384"
- self.alg_policy['ECDSAP384SHA384'].name = "ECDSAP384SHA384"
- self.alg_policy['ECDSAP384SHA384'].ksk_keysize = None;
- self.alg_policy['ECDSAP384SHA384'].zsk_keysize = None;
-
- self.alg_policy['ED25519'] = copy(p)
- self.alg_policy['ED25519'].algorithm = "ED25519"
- self.alg_policy['ED25519'].name = "ED25519"
- self.alg_policy['ED25519'].ksk_keysize = None;
- self.alg_policy['ED25519'].zsk_keysize = None;
-
- self.alg_policy['ED448'] = copy(p)
- self.alg_policy['ED448'].algorithm = "ED448"
- self.alg_policy['ED448'].name = "ED448"
- self.alg_policy['ED448'].ksk_keysize = None;
- self.alg_policy['ED448'].zsk_keysize = None;
+ self.alg_policy["RSASHA1"] = copy(p)
+ self.alg_policy["RSASHA1"].algorithm = "RSASHA1"
+ self.alg_policy["RSASHA1"].name = "RSASHA1"
+
+ self.alg_policy["NSEC3RSASHA1"] = copy(p)
+ self.alg_policy["NSEC3RSASHA1"].algorithm = "NSEC3RSASHA1"
+ self.alg_policy["NSEC3RSASHA1"].name = "NSEC3RSASHA1"
+
+ self.alg_policy["RSASHA256"] = copy(p)
+ self.alg_policy["RSASHA256"].algorithm = "RSASHA256"
+ self.alg_policy["RSASHA256"].name = "RSASHA256"
+
+ self.alg_policy["RSASHA512"] = copy(p)
+ self.alg_policy["RSASHA512"].algorithm = "RSASHA512"
+ self.alg_policy["RSASHA512"].name = "RSASHA512"
+
+ self.alg_policy["ECDSAP256SHA256"] = copy(p)
+ self.alg_policy["ECDSAP256SHA256"].algorithm = "ECDSAP256SHA256"
+ self.alg_policy["ECDSAP256SHA256"].name = "ECDSAP256SHA256"
+ self.alg_policy["ECDSAP256SHA256"].ksk_keysize = None
+ self.alg_policy["ECDSAP256SHA256"].zsk_keysize = None
+
+ self.alg_policy["ECDSAP384SHA384"] = copy(p)
+ self.alg_policy["ECDSAP384SHA384"].algorithm = "ECDSAP384SHA384"
+ self.alg_policy["ECDSAP384SHA384"].name = "ECDSAP384SHA384"
+ self.alg_policy["ECDSAP384SHA384"].ksk_keysize = None
+ self.alg_policy["ECDSAP384SHA384"].zsk_keysize = None
+
+ self.alg_policy["ED25519"] = copy(p)
+ self.alg_policy["ED25519"].algorithm = "ED25519"
+ self.alg_policy["ED25519"].name = "ED25519"
+ self.alg_policy["ED25519"].ksk_keysize = None
+ self.alg_policy["ED25519"].zsk_keysize = None
+
+ self.alg_policy["ED448"] = copy(p)
+ self.alg_policy["ED448"].algorithm = "ED448"
+ self.alg_policy["ED448"].name = "ED448"
+ self.alg_policy["ED448"].ksk_keysize = None
+ self.alg_policy["ED448"].zsk_keysize = None
if filename:
self.load(filename)
p = self.zone_policy[z]
if p is None:
- p = copy(self.named_policy['default'])
+ p = copy(self.named_policy["default"])
p.name = zone
p.is_constructed = True
if p.algorithm is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent and not parent.algorithm:
parent = parent.parent
p.algorithm = parent and parent.algorithm or None
if p.algorithm in self.alg_policy:
ap = self.alg_policy[p.algorithm]
else:
- raise PolicyException('algorithm not found')
+ raise PolicyException("algorithm not found")
if p.directory is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent is not None and not parent.directory:
parent = parent.parent
p.directory = parent and parent.directory
if p.coverage is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent and not parent.coverage:
parent = parent.parent
p.coverage = parent and parent.coverage or ap.coverage
if p.ksk_keysize is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent.parent and not parent.ksk_keysize:
parent = parent.parent
p.ksk_keysize = parent and parent.ksk_keysize or ap.ksk_keysize
if p.zsk_keysize is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent.parent and not parent.zsk_keysize:
parent = parent.parent
p.zsk_keysize = parent and parent.zsk_keysize or ap.zsk_keysize
if p.ksk_rollperiod is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent.parent and not parent.ksk_rollperiod:
parent = parent.parent
- p.ksk_rollperiod = parent and \
- parent.ksk_rollperiod or ap.ksk_rollperiod
+ p.ksk_rollperiod = parent and parent.ksk_rollperiod or ap.ksk_rollperiod
if p.zsk_rollperiod is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent.parent and not parent.zsk_rollperiod:
parent = parent.parent
- p.zsk_rollperiod = parent and \
- parent.zsk_rollperiod or ap.zsk_rollperiod
+ p.zsk_rollperiod = parent and parent.zsk_rollperiod or ap.zsk_rollperiod
if p.ksk_prepublish is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent.parent and not parent.ksk_prepublish:
parent = parent.parent
- p.ksk_prepublish = parent and \
- parent.ksk_prepublish or ap.ksk_prepublish
+ p.ksk_prepublish = parent and parent.ksk_prepublish or ap.ksk_prepublish
if p.zsk_prepublish is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent.parent and not parent.zsk_prepublish:
parent = parent.parent
- p.zsk_prepublish = parent and \
- parent.zsk_prepublish or ap.zsk_prepublish
+ p.zsk_prepublish = parent and parent.zsk_prepublish or ap.zsk_prepublish
if p.ksk_postpublish is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent.parent and not parent.ksk_postpublish:
parent = parent.parent
- p.ksk_postpublish = parent and \
- parent.ksk_postpublish or ap.ksk_postpublish
+ p.ksk_postpublish = parent and parent.ksk_postpublish or ap.ksk_postpublish
if p.zsk_postpublish is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent.parent and not parent.zsk_postpublish:
parent = parent.parent
- p.zsk_postpublish = parent and \
- parent.zsk_postpublish or ap.zsk_postpublish
+ p.zsk_postpublish = parent and parent.zsk_postpublish or ap.zsk_postpublish
if p.keyttl is None:
- parent = p.parent or self.named_policy['default']
+ parent = p.parent or self.named_policy["default"]
while parent is not None and not parent.keyttl:
parent = parent.parent
p.keyttl = parent and parent.keyttl
- if 'novalidate' not in kwargs or not kwargs['novalidate']:
+ if "novalidate" not in kwargs or not kwargs["novalidate"]:
(valid, msg) = p.validate()
if not valid:
raise PolicyException(msg)
return p
-
def p_policylist(self, p):
- '''policylist : init policy
- | policylist policy'''
+ """policylist : init policy
+ | policylist policy"""
pass
def p_init(self, p):
self.initial = False
def p_policy(self, p):
- '''policy : alg_policy
- | zone_policy
- | named_policy'''
+ """policy : alg_policy
+ | zone_policy
+ | named_policy"""
pass
def p_name(self, p):
- '''name : STR
- | KEYTYPE
- | DATESUFFIX'''
+ """name : STR
+ | KEYTYPE
+ | DATESUFFIX"""
p[0] = p[1]
pass
def p_domain(self, p):
- '''domain : STR
- | QSTRING
- | KEYTYPE
- | DATESUFFIX'''
+ """domain : STR
+ | QSTRING
+ | KEYTYPE
+ | DATESUFFIX"""
p[0] = p[1].strip()
- if not re.match(r'^[\w.-][\w.-]*$', p[0]):
- raise PolicyException('invalid domain')
+ if not re.match(r"^[\w.-][\w.-]*$", p[0]):
+ raise PolicyException("invalid domain")
pass
def p_new_policy(self, p):
def p_zone_policy(self, p):
"zone_policy : ZONE domain new_policy policy_option_group SEMI"
- self.current.name = p[2].rstrip('.')
+ self.current.name = p[2].rstrip(".")
self.current.is_zone = True
- self.zone_policy[p[2].rstrip('.').lower()] = self.current
+ self.zone_policy[p[2].rstrip(".").lower()] = self.current
pass
def p_named_policy(self, p):
def p_duration_3(self, p):
"duration : NUMBER DATESUFFIX"
if p[2] == "y":
- p[0] = p[1] * 31536000 # year
+ p[0] = p[1] * 31536000 # year
elif p[2] == "mo":
p[0] = p[1] * 2592000 # month
elif p[2] == "w":
- p[0] = p[1] * 604800 # week
+ p[0] = p[1] * 604800 # week
elif p[2] == "d":
- p[0] = p[1] * 86400 # day
+ p[0] = p[1] * 86400 # day
elif p[2] == "h":
- p[0] = p[1] * 3600 # hour
+ p[0] = p[1] * 3600 # hour
elif p[2] == "mi":
- p[0] = p[1] * 60 # minute
+ p[0] = p[1] * 60 # minute
elif p[2] == "s":
- p[0] = p[1] # second
+ p[0] = p[1] # second
else:
- raise PolicyException('invalid duration')
+ raise PolicyException("invalid duration")
def p_policy_option_group(self, p):
"policy_option_group : LBRACE policy_option_list RBRACE"
pass
def p_policy_option_list(self, p):
- '''policy_option_list : policy_option SEMI
- | policy_option_list policy_option SEMI'''
+ """policy_option_list : policy_option SEMI
+ | policy_option_list policy_option SEMI"""
pass
def p_policy_option(self, p):
- '''policy_option : parent_option
- | directory_option
- | coverage_option
- | rollperiod_option
- | prepublish_option
- | postpublish_option
- | keysize_option
- | algorithm_option
- | keyttl_option
- | standby_option'''
+ """policy_option : parent_option
+ | directory_option
+ | coverage_option
+ | rollperiod_option
+ | prepublish_option
+ | postpublish_option
+ | keysize_option
+ | algorithm_option
+ | keyttl_option
+ | standby_option"""
pass
def p_alg_option_group(self, p):
pass
def p_alg_option_list(self, p):
- '''alg_option_list : alg_option SEMI
- | alg_option_list alg_option SEMI'''
+ """alg_option_list : alg_option SEMI
+ | alg_option_list alg_option SEMI"""
pass
def p_alg_option(self, p):
- '''alg_option : coverage_option
- | rollperiod_option
- | prepublish_option
- | postpublish_option
- | keyttl_option
- | keysize_option
- | standby_option'''
+ """alg_option : coverage_option
+ | rollperiod_option
+ | prepublish_option
+ | postpublish_option
+ | keyttl_option
+ | keysize_option
+ | standby_option"""
pass
def p_parent_option(self, p):
def p_error(self, p):
if p:
- print("%s%s%d:syntax error near '%s'" %
- (self.filename or "", ":" if self.filename else "",
- p.lineno, p.value))
+ print(
+ "%s%s%d:syntax error near '%s'"
+ % (self.filename or "", ":" if self.filename else "", p.lineno, p.value)
+ )
else:
if not self.initial:
- raise PolicyException("%s%s%d:unexpected end of input" %
- (self.filename or "", ":" if self.filename else "",
- p and p.lineno or 0))
+ raise PolicyException(
+ "%s%s%d:unexpected end of input"
+ % (
+ self.filename or "",
+ ":" if self.filename else "",
+ p and p.lineno or 0,
+ )
+ )
+
if __name__ == "__main__":
import sys
+
if sys.argv[1] == "lex":
file = open(sys.argv[2])
text = file.read()
elif sys.argv[1] == "parse":
try:
pp = dnssec_policy(sys.argv[2], write_tables=True, debug=True)
- print(pp.named_policy['default'])
+ print(pp.named_policy["default"])
print(pp.policy("nonexistent.zone"))
except Exception as e:
print(e.args[0])
class rndc(object):
"""RNDC protocol client library"""
- __algos = {'md5': 157,
- 'sha1': 161,
- 'sha224': 162,
- 'sha256': 163,
- 'sha384': 164,
- 'sha512': 165}
+
+ __algos = {
+ "md5": 157,
+ "sha1": 161,
+ "sha224": 162,
+ "sha256": 163,
+ "sha384": 164,
+ "sha512": 165,
+ }
def __init__(self, host, algo, secret):
"""Creates a persistent connection to RNDC and logs in
secret - HMAC secret, base64 encoded"""
self.host = host
algo = algo.lower()
- if algo.startswith('hmac-'):
+ if algo.startswith("hmac-"):
algo = algo[5:]
self.algo = algo
self.hlalgo = getattr(hashlib, algo)
"""Call a RNDC command, all parsing is done on the server side
cmd - a complete string with a command (eg 'reload zone example.com')
"""
- return dict(self.__command(type=cmd)['_data'])
+ return dict(self.__command(type=cmd)["_data"])
def __serialize_dict(self, data, ignore_auth=False):
rv = bytearray()
for k, v in data.items():
- if ignore_auth and k == '_auth':
+ if ignore_auth and k == "_auth":
continue
- rv += struct.pack('B', len(k)) + k.encode('ascii')
+ rv += struct.pack("B", len(k)) + k.encode("ascii")
if type(v) == str:
- rv += struct.pack('>BI', 1, len(v)) + v.encode('ascii')
+ rv += struct.pack(">BI", 1, len(v)) + v.encode("ascii")
elif type(v) == bytes:
- rv += struct.pack('>BI', 1, len(v)) + v
+ rv += struct.pack(">BI", 1, len(v)) + v
elif type(v) == bytearray:
- rv += struct.pack('>BI', 1, len(v)) + v
+ rv += struct.pack(">BI", 1, len(v)) + v
elif type(v) == OrderedDict:
sd = self.__serialize_dict(v)
- rv += struct.pack('>BI', 2, len(sd)) + sd
+ rv += struct.pack(">BI", 2, len(sd)) + sd
else:
- raise NotImplementedError('Cannot serialize element of type %s'
- % type(v))
+ raise NotImplementedError(
+ "Cannot serialize element of type %s" % type(v)
+ )
return rv
def __prep_message(self, *args, **kwargs):
data = OrderedDict(*args, **kwargs)
d = OrderedDict()
- d['_auth'] = OrderedDict()
- d['_ctrl'] = OrderedDict()
- d['_ctrl']['_ser'] = str(self.ser)
- d['_ctrl']['_tim'] = str(now)
- d['_ctrl']['_exp'] = str(now+60)
+ d["_auth"] = OrderedDict()
+ d["_ctrl"] = OrderedDict()
+ d["_ctrl"]["_ser"] = str(self.ser)
+ d["_ctrl"]["_tim"] = str(now)
+ d["_ctrl"]["_exp"] = str(now + 60)
if self.nonce is not None:
- d['_ctrl']['_nonce'] = self.nonce
- d['_data'] = data
+ d["_ctrl"]["_nonce"] = self.nonce
+ d["_data"] = data
msg = self.__serialize_dict(d, ignore_auth=True)
hash = hmac.new(self.secret, msg, self.hlalgo).digest()
bhash = base64.b64encode(hash)
- if self.algo == 'md5':
- d['_auth']['hmd5'] = struct.pack('22s', bhash)
+ if self.algo == "md5":
+ d["_auth"]["hmd5"] = struct.pack("22s", bhash)
else:
- d['_auth']['hsha'] = bytearray(struct.pack('B88s',
- self.__algos[self.algo], bhash))
+ d["_auth"]["hsha"] = bytearray(
+ struct.pack("B88s", self.__algos[self.algo], bhash)
+ )
msg = self.__serialize_dict(d)
- msg = struct.pack('>II', len(msg) + 4, 1) + msg
+ msg = struct.pack(">II", len(msg) + 4, 1) + msg
return msg
def __verify_msg(self, msg):
- if self.nonce is not None and msg['_ctrl']['_nonce'] != self.nonce:
+ if self.nonce is not None and msg["_ctrl"]["_nonce"] != self.nonce:
return False
- if self.algo == 'md5':
- bhash = msg['_auth']['hmd5']
+ if self.algo == "md5":
+ bhash = msg["_auth"]["hmd5"]
else:
- bhash = msg['_auth']['hsha'][1:]
+ bhash = msg["_auth"]["hsha"][1:]
if type(bhash) == bytes:
- bhash = bhash.decode('ascii')
- bhash += '=' * (4 - (len(bhash) % 4))
+ bhash = bhash.decode("ascii")
+ bhash += "=" * (4 - (len(bhash) % 4))
remote_hash = base64.b64decode(bhash)
my_msg = self.__serialize_dict(msg, ignore_auth=True)
my_hash = hmac.new(self.secret, my_msg, self.hlalgo).digest()
- return (my_hash == remote_hash)
+ return my_hash == remote_hash
def __command(self, *args, **kwargs):
msg = self.__prep_message(*args, **kwargs)
# What should we throw here? Bad auth can cause this...
raise IOError("Can't read response header")
- length, version = struct.unpack('>II', header)
+ length, version = struct.unpack(">II", header)
if version != 1:
- raise NotImplementedError('Wrong message version %d' % version)
+ raise NotImplementedError("Wrong message version %d" % version)
# it includes the header
length -= 4
def __connect_login(self):
self.socket = socket.create_connection(self.host)
self.nonce = None
- msg = self.__command(type='null')
- self.nonce = msg['_ctrl']['_nonce']
+ msg = self.__command(type="null")
+ self.nonce = msg["_ctrl"]["_nonce"]
def __parse_element(self, input):
pos = 0
labellen = input[pos]
pos += 1
- label = input[pos:pos+labellen].decode('ascii')
+ label = input[pos : pos + labellen].decode("ascii")
pos += labellen
type = input[pos]
pos += 1
- datalen = struct.unpack('>I', input[pos:pos+4])[0]
+ datalen = struct.unpack(">I", input[pos : pos + 4])[0]
pos += 4
- data = input[pos:pos+datalen]
+ data = input[pos : pos + datalen]
pos += datalen
rest = input[pos:]
- if type == 1: # raw binary value
+ if type == 1: # raw binary value
return label, data, rest
- elif type == 2: # dictionary
+ elif type == 2: # dictionary
d = OrderedDict()
while len(data) > 0:
ilabel, value, data = self.__parse_element(data)
return label, d, rest
# TODO type 3 - list
else:
- raise NotImplementedError('Unknown element type %d' % type)
+ raise NotImplementedError("Unknown element type %d" % type)
def __parse_message(self, input):
rv = OrderedDict()
import sys
import unittest
-sys.path.append('../..')
+
+sys.path.append("../..")
from isc import *
kdict = None
def getkey():
global kdict
if not kdict:
- kd = keydict(path='testdata')
+ kd = keydict(path="testdata")
for key in kd:
return key
def test_fmttime(self):
key = getkey()
- self.assertEqual(key.getfmttime('Created'), '20151120214047')
- self.assertEqual(key.getfmttime('Publish'), '20151021214154')
- self.assertEqual(key.getfmttime('Activate'), '20151120214154')
- self.assertEqual(key.getfmttime('Revoke'), '20161119214154')
- self.assertEqual(key.getfmttime('Inactive'), '20171119214154')
- self.assertEqual(key.getfmttime('Delete'), '20181119214154')
- self.assertEqual(key.getfmttime('SyncPublish'), '20150921214154')
- self.assertEqual(key.getfmttime('SyncDelete'), '20151130214154')
+ self.assertEqual(key.getfmttime("Created"), "20151120214047")
+ self.assertEqual(key.getfmttime("Publish"), "20151021214154")
+ self.assertEqual(key.getfmttime("Activate"), "20151120214154")
+ self.assertEqual(key.getfmttime("Revoke"), "20161119214154")
+ self.assertEqual(key.getfmttime("Inactive"), "20171119214154")
+ self.assertEqual(key.getfmttime("Delete"), "20181119214154")
+ self.assertEqual(key.getfmttime("SyncPublish"), "20150921214154")
+ self.assertEqual(key.getfmttime("SyncDelete"), "20151130214154")
+
if __name__ == "__main__":
unittest.main()
import sys
import unittest
-sys.path.append('../..')
+
+sys.path.append("../..")
from isc import *
class PolicyTest(unittest.TestCase):
def test_keysize(self):
pol = policy.dnssec_policy()
- pol.load('test-policies/01-keysize.pol')
+ pol.load("test-policies/01-keysize.pol")
- p = pol.policy('good_rsa.test', novalidate=True)
+ p = pol.policy("good_rsa.test", novalidate=True)
self.assertEqual(p.get_name(), "good_rsa.test")
self.assertEqual(p.constructed(), False)
self.assertEqual(p.validate(), (True, ""))
def test_prepublish(self):
pol = policy.dnssec_policy()
- pol.load('test-policies/02-prepublish.pol')
- p = pol.policy('good_prepublish.test', novalidate=True)
+ pol.load("test-policies/02-prepublish.pol")
+ p = pol.policy("good_prepublish.test", novalidate=True)
self.assertEqual(p.validate(), (True, ""))
- p = pol.policy('bad_prepublish.test', novalidate=True)
- self.assertEqual(p.validate(),
- (False, 'KSK pre/post-publish periods '
- '(10368000/5184000) combined exceed '
- 'rollover period 10368000'))
+ p = pol.policy("bad_prepublish.test", novalidate=True)
+ self.assertEqual(
+ p.validate(),
+ (
+ False,
+ "KSK pre/post-publish periods "
+ "(10368000/5184000) combined exceed "
+ "rollover period 10368000",
+ ),
+ )
def test_postpublish(self):
pol = policy.dnssec_policy()
- pol.load('test-policies/03-postpublish.pol')
+ pol.load("test-policies/03-postpublish.pol")
- p = pol.policy('good_postpublish.test', novalidate=True)
+ p = pol.policy("good_postpublish.test", novalidate=True)
self.assertEqual(p.validate(), (True, ""))
- p = pol.policy('bad_postpublish.test', novalidate=True)
- self.assertEqual(p.validate(),
- (False, 'KSK pre/post-publish periods '
- '(10368000/5184000) combined exceed '
- 'rollover period 10368000'))
+ p = pol.policy("bad_postpublish.test", novalidate=True)
+ self.assertEqual(
+ p.validate(),
+ (
+ False,
+ "KSK pre/post-publish periods "
+ "(10368000/5184000) combined exceed "
+ "rollover period 10368000",
+ ),
+ )
def test_combined_pre_post(self):
pol = policy.dnssec_policy()
- pol.load('test-policies/04-combined-pre-post.pol')
+ pol.load("test-policies/04-combined-pre-post.pol")
- p = pol.policy('good_combined_pre_post_ksk.test', novalidate=True)
+ p = pol.policy("good_combined_pre_post_ksk.test", novalidate=True)
self.assertEqual(p.validate(), (True, ""))
- p = pol.policy('bad_combined_pre_post_ksk.test', novalidate=True)
- self.assertEqual(p.validate(),
- (False, 'KSK pre/post-publish periods '
- '(5184000/5184000) combined exceed '
- 'rollover period 10368000'))
-
- p = pol.policy('good_combined_pre_post_zsk.test', novalidate=True)
- self.assertEqual(p.validate(),
- (True, ""))
- p = pol.policy('bad_combined_pre_post_zsk.test', novalidate=True)
- self.assertEqual(p.validate(),
- (False, 'ZSK pre/post-publish periods '
- '(5184000/5184000) combined exceed '
- 'rollover period 7776000'))
+ p = pol.policy("bad_combined_pre_post_ksk.test", novalidate=True)
+ self.assertEqual(
+ p.validate(),
+ (
+ False,
+ "KSK pre/post-publish periods "
+ "(5184000/5184000) combined exceed "
+ "rollover period 10368000",
+ ),
+ )
+
+ p = pol.policy("good_combined_pre_post_zsk.test", novalidate=True)
+ self.assertEqual(p.validate(), (True, ""))
+ p = pol.policy("bad_combined_pre_post_zsk.test", novalidate=True)
+ self.assertEqual(
+ p.validate(),
+ (
+ False,
+ "ZSK pre/post-publish periods "
+ "(5184000/5184000) combined exceed "
+ "rollover period 7776000",
+ ),
+ )
def test_numeric_zone(self):
pol = policy.dnssec_policy()
- pol.load('test-policies/05-numeric-zone.pol')
+ pol.load("test-policies/05-numeric-zone.pol")
- p = pol.policy('99example.test', novalidate=True)
+ p = pol.policy("99example.test", novalidate=True)
self.assertEqual(p.validate(), (True, ""))
+
if __name__ == "__main__":
unittest.main()
import os
# These routines permit platform-independent location of BIND 9 tools
-if os.name == 'nt':
+if os.name == "nt":
import win32con
import win32api
-def prefix(bindir=''):
- if os.name != 'nt':
- return os.path.join('@prefix@', bindir)
+def prefix(bindir=""):
+ if os.name != "nt":
+ return os.path.join("@prefix@", bindir)
hklm = win32con.HKEY_LOCAL_MACHINE
bind_subkey = "Software\\ISC\\BIND"
def shellquote(s):
- if os.name == 'nt':
+ if os.name == "nt":
return '"' + s.replace('"', '"\\"') + '"'
return "'" + s.replace("'", "'\\''") + "'"
-version = '@BIND9_VERSION@'
-if os.name != 'nt':
- sysconfdir = '@expanded_sysconfdir@'
+version = "@BIND9_VERSION@"
+if os.name != "nt":
+ sysconfdir = "@expanded_sysconfdir@"
else:
- sysconfdir = prefix('etc')
+ sysconfdir = prefix("etc")
# pylint: disable=deprecated-module
from distutils.core import setup
-setup(name='isc',
- version='2.0',
- description='Python functions to support BIND utilities',
- url='https://www.isc.org/bind',
- author='Internet Systems Consortium, Inc',
- author_email='info@isc.org',
- license='MPL',
- requires=['ply'],
- packages=['isc'])
+setup(
+ name="isc",
+ version="2.0",
+ description="Python functions to support BIND utilities",
+ url="https://www.isc.org/bind",
+ author="Internet Systems Consortium, Inc",
+ author_email="info@isc.org",
+ license="MPL",
+ requires=["ply"],
+ packages=["isc"],
+)
def run_rndc(server, rndc_command):
- '''
+ """
Send the specified 'rndc_command' to 'server' with a timeout of 10 seconds
- '''
- rndc = os.getenv('RNDC')
- port = os.getenv('CONTROLPORT')
+ """
+ rndc = os.getenv("RNDC")
+ port = os.getenv("CONTROLPORT")
- cmdline = [rndc, '-c', '../common/rndc.conf', '-p', port, '-s', server]
+ cmdline = [rndc, "-c", "../common/rndc.conf", "-p", port, "-s", server]
cmdline.extend(rndc_command)
subprocess.check_output(cmdline, stderr=subprocess.STDOUT, timeout=10)
def rndc_loop(test_state, domain):
- '''
+ """
Run "rndc addzone", "rndc modzone", and "rndc delzone" in a tight loop
until the test is considered finished, ignoring errors
- '''
+ """
rndc_commands = [
- ['addzone', domain,
- '{ type master; file "example.db"; };'],
- ['modzone', domain,
- '{ type master; file "example.db"; allow-transfer { any; }; };'],
- ['delzone', domain],
+ ["addzone", domain, '{ type master; file "example.db"; };'],
+ [
+ "modzone",
+ domain,
+ '{ type master; file "example.db"; allow-transfer { any; }; };',
+ ],
+ ["delzone", domain],
]
- while not test_state['finished']:
+ while not test_state["finished"]:
for command in rndc_commands:
try:
- run_rndc('10.53.0.3', command)
+ run_rndc("10.53.0.3", command)
except subprocess.SubprocessError:
pass
def check_if_server_is_responsive():
- '''
+ """
Check if server status can be successfully retrieved using "rndc status"
- '''
+ """
try:
- run_rndc('10.53.0.3', ['status'])
+ run_rndc("10.53.0.3", ["status"])
return True
except subprocess.SubprocessError:
return False
def test_rndc_deadlock():
- '''
+ """
Test whether running "rndc addzone", "rndc modzone", and "rndc delzone"
commands concurrently does not trigger a deadlock
- '''
- test_state = {'finished': False}
+ """
+ test_state = {"finished": False}
# Create 4 worker threads running "rndc" commands in a loop.
with concurrent.futures.ThreadPoolExecutor() as executor:
for i in range(1, 5):
- domain = 'example%d' % i
+ domain = "example%d" % i
executor.submit(rndc_loop, test_state, domain)
# Run "rndc status" 10 times, with 1-second pauses between attempts.
time.sleep(1)
# Signal worker threads that the test is finished.
- test_state['finished'] = True
+ test_state["finished"] = True
# Check whether all "rndc status" commands succeeded.
assert server_is_responsive
############################################################################
actions = []
rrs = []
+
+
def ctl_channel(msg):
global actions, rrs
msg = msg.splitlines().pop(0)
- print ('received control message: %s' % msg)
+ print("received control message: %s" % msg)
- msg = msg.split(b'|')
+ msg = msg.split(b"|")
if len(msg) == 0:
return
- actions = [x.strip() for x in msg[0].split(b',')]
- n = functools.reduce(lambda n, act: (n + (2 if act == b'dname' else 1)), [0] + actions)
+ actions = [x.strip() for x in msg[0].split(b",")]
+ n = functools.reduce(
+ lambda n, act: (n + (2 if act == b"dname" else 1)), [0] + actions
+ )
if len(msg) == 1:
rrs = []
rrs.append((i, b))
return
- rlist = [x.strip() for x in msg[1].split(b',')]
+ rlist = [x.strip() for x in msg[1].split(b",")]
rrs = []
for item in rlist:
- if item[0] == b's'[0]:
+ if item[0] == b"s"[0]:
i = int(item[1:].strip()) - 1
if i > n:
- print ('invalid index %d' + (i + 1))
+ print("invalid index %d" + (i + 1))
continue
rrs.append((int(item[1:]) - 1, True))
else:
i = int(item) - 1
if i > n:
- print ('invalid index %d' % (i + 1))
+ print("invalid index %d" % (i + 1))
continue
rrs.append((i, False))
+
############################################################################
# Respond to a DNS query.
############################################################################
def create_response(msg):
m = dns.message.from_wire(msg)
qname = m.question[0].name.to_text()
- labels = qname.lower().split('.')
+ labels = qname.lower().split(".")
wantsigs = True if m.ednsflags & dns.flags.DO else False
# get qtype
# - sld is 'example'
# - tld is 'com.'
name = labels.pop(0)
- domain = '.'.join(labels)
+ domain = ".".join(labels)
sld = labels.pop(0)
- tld = '.'.join(labels)
+ tld = ".".join(labels)
- print ('query: ' + qname + '/' + typename)
- print ('domain: ' + domain)
+ print("query: " + qname + "/" + typename)
+ print("domain: " + domain)
# default answers, depending on QTYPE.
# currently only A, AAAA, TXT and NS are supported.
ttl = 86400
- additionalA = '10.53.0.4'
- additionalAAAA = 'fd92:7065:b8e:ffff::4'
- if typename == 'A':
- final = '10.53.0.4'
- elif typename == 'AAAA':
- final = 'fd92:7065:b8e:ffff::4'
- elif typename == 'TXT':
- final = 'Some\ text\ here'
- elif typename == 'NS':
+ additionalA = "10.53.0.4"
+ additionalAAAA = "fd92:7065:b8e:ffff::4"
+ if typename == "A":
+ final = "10.53.0.4"
+ elif typename == "AAAA":
+ final = "fd92:7065:b8e:ffff::4"
+ elif typename == "TXT":
+ final = "Some\ text\ here"
+ elif typename == "NS":
domain = qname
- final = ('ns1.%s' % domain)
+ final = "ns1.%s" % domain
else:
final = None
delta = timedelta(30)
t1 = t - delta
t2 = t + delta
- inception=t1.strftime('%Y%m%d000000')
- expiry=t2.strftime('%Y%m%d000000')
- sigdata='OCXH2De0yE4NMTl9UykvOsJ4IBGs/ZIpff2rpaVJrVG7jQfmj50otBAp A0Zo7dpBU4ofv0N/F2Ar6LznCncIojkWptEJIAKA5tHegf/jY39arEpO cevbGp6DKxFhlkLXNcw7k9o7DSw14OaRmgAjXdTFbrl4AiAa0zAttFko Tso='
+ inception = t1.strftime("%Y%m%d000000")
+ expiry = t2.strftime("%Y%m%d000000")
+ sigdata = "OCXH2De0yE4NMTl9UykvOsJ4IBGs/ZIpff2rpaVJrVG7jQfmj50otBAp A0Zo7dpBU4ofv0N/F2Ar6LznCncIojkWptEJIAKA5tHegf/jY39arEpO cevbGp6DKxFhlkLXNcw7k9o7DSw14OaRmgAjXdTFbrl4AiAa0zAttFko Tso="
# construct answer set.
answers = []
i = 0
for action in actions:
- if name != 'test':
+ if name != "test":
continue
- if action == b'xname':
- owner = curname + '.' + curdom
- newname = 'cname%d' % i
+ if action == b"xname":
+ owner = curname + "." + curdom
+ newname = "cname%d" % i
i += 1
- newdom = 'domain%d.%s' % (i, tld)
+ newdom = "domain%d.%s" % (i, tld)
i += 1
- target = newname + '.' + newdom
- print ('add external CNAME %s to %s' % (owner, target))
+ target = newname + "." + newdom
+ print("add external CNAME %s to %s" % (owner, target))
answers.append(dns.rrset.from_text(owner, ttl, IN, CNAME, target))
- rrsig = 'CNAME 5 3 %d %s %s 12345 %s %s' % \
- (ttl, expiry, inception, domain, sigdata)
- print ('add external RRISG(CNAME) %s to %s' % (owner, target))
+ rrsig = "CNAME 5 3 %d %s %s 12345 %s %s" % (
+ ttl,
+ expiry,
+ inception,
+ domain,
+ sigdata,
+ )
+ print("add external RRISG(CNAME) %s to %s" % (owner, target))
sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig))
curname = newname
curdom = newdom
continue
- if action == b'cname':
- owner = curname + '.' + curdom
- newname = 'cname%d' % i
- target = newname + '.' + curdom
+ if action == b"cname":
+ owner = curname + "." + curdom
+ newname = "cname%d" % i
+ target = newname + "." + curdom
i += 1
- print ('add CNAME %s to %s' % (owner, target))
+ print("add CNAME %s to %s" % (owner, target))
answers.append(dns.rrset.from_text(owner, ttl, IN, CNAME, target))
- rrsig = 'CNAME 5 3 %d %s %s 12345 %s %s' % \
- (ttl, expiry, inception, domain, sigdata)
- print ('add RRSIG(CNAME) %s to %s' % (owner, target))
+ rrsig = "CNAME 5 3 %d %s %s 12345 %s %s" % (
+ ttl,
+ expiry,
+ inception,
+ domain,
+ sigdata,
+ )
+ print("add RRSIG(CNAME) %s to %s" % (owner, target))
sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig))
curname = newname
continue
- if action == b'dname':
+ if action == b"dname":
owner = curdom
- newdom = 'domain%d.%s' % (i, tld)
+ newdom = "domain%d.%s" % (i, tld)
i += 1
- print ('add DNAME %s to %s' % (owner, newdom))
+ print("add DNAME %s to %s" % (owner, newdom))
answers.append(dns.rrset.from_text(owner, ttl, IN, DNAME, newdom))
- rrsig = 'DNAME 5 3 %d %s %s 12345 %s %s' % \
- (ttl, expiry, inception, domain, sigdata)
- print ('add RRSIG(DNAME) %s to %s' % (owner, newdom))
+ rrsig = "DNAME 5 3 %d %s %s 12345 %s %s" % (
+ ttl,
+ expiry,
+ inception,
+ domain,
+ sigdata,
+ )
+ print("add RRSIG(DNAME) %s to %s" % (owner, newdom))
sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig))
- owner = curname + '.' + curdom
- target = curname + '.' + newdom
- print ('add synthesized CNAME %s to %s' % (owner, target))
+ owner = curname + "." + curdom
+ target = curname + "." + newdom
+ print("add synthesized CNAME %s to %s" % (owner, target))
answers.append(dns.rrset.from_text(owner, ttl, IN, CNAME, target))
- rrsig = 'CNAME 5 3 %d %s %s 12345 %s %s' % \
- (ttl, expiry, inception, domain, sigdata)
- print ('add synthesized RRSIG(CNAME) %s to %s' % (owner, target))
+ rrsig = "CNAME 5 3 %d %s %s 12345 %s %s" % (
+ ttl,
+ expiry,
+ inception,
+ domain,
+ sigdata,
+ )
+ print("add synthesized RRSIG(CNAME) %s to %s" % (owner, target))
sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig))
curdom = newdom
continue
# now add the final answer
- owner = curname + '.' + curdom
+ owner = curname + "." + curdom
answers.append(dns.rrset.from_text(owner, ttl, IN, rrtype, final))
- rrsig = '%s 5 3 %d %s %s 12345 %s %s' % \
- (typename, ttl, expiry, inception, domain, sigdata)
+ rrsig = "%s 5 3 %d %s %s 12345 %s %s" % (
+ typename,
+ ttl,
+ expiry,
+ inception,
+ domain,
+ sigdata,
+ )
sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig))
# prepare the response and convert to wire format
r = dns.message.make_response(m)
- if name != 'test':
+ if name != "test":
r.answer.append(answers[-1])
if wantsigs:
r.answer.append(sigs[-1])
else:
r.answer.append(answers[i])
- if typename != 'NS':
- r.authority.append(dns.rrset.from_text(domain, ttl, IN, "NS",
- ("ns1.%s" % domain)))
- r.additional.append(dns.rrset.from_text(('ns1.%s' % domain), 86400,
- IN, A, additionalA))
- r.additional.append(dns.rrset.from_text(('ns1.%s' % domain), 86400,
- IN, AAAA, additionalAAAA))
+ if typename != "NS":
+ r.authority.append(
+ dns.rrset.from_text(domain, ttl, IN, "NS", ("ns1.%s" % domain))
+ )
+ r.additional.append(
+ dns.rrset.from_text(("ns1.%s" % domain), 86400, IN, A, additionalA)
+ )
+ r.additional.append(
+ dns.rrset.from_text(("ns1.%s" % domain), 86400, IN, AAAA, additionalAAAA)
+ )
r.flags |= dns.flags.AA
r.use_edns()
return r.to_wire()
+
def sigterm(signum, frame):
- print ("Shutting down now...")
- os.remove('ans.pid')
+ print("Shutting down now...")
+ os.remove("ans.pid")
running = False
sys.exit(0)
+
############################################################################
# Main
#
ip4 = "10.53.0.4"
ip6 = "fd92:7065:b8e:ffff::4"
-try: port=int(os.environ['PORT'])
-except: port=5300
+try:
+ port = int(os.environ["PORT"])
+except:
+ port = 5300
-try: ctrlport=int(os.environ['EXTRAPORT1'])
-except: ctrlport=5300
+try:
+ ctrlport = int(os.environ["EXTRAPORT1"])
+except:
+ ctrlport = 5300
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_socket.bind((ip4, port))
signal.signal(signal.SIGTERM, sigterm)
-f = open('ans.pid', 'w')
+f = open("ans.pid", "w")
pid = os.getpid()
-print (pid, file=f)
+print(pid, file=f)
f.close()
running = True
-print ("Listening on %s port %d" % (ip4, port))
+print("Listening on %s port %d" % (ip4, port))
if havev6:
- print ("Listening on %s port %d" % (ip6, port))
-print ("Control channel on %s port %d" % (ip4, ctrlport))
-print ("Ctrl-c to quit")
+ print("Listening on %s port %d" % (ip6, port))
+print("Control channel on %s port %d" % (ip4, ctrlport))
+print("Ctrl-c to quit")
if havev6:
input = [query4_socket, query6_socket, ctrl_socket]
if s == ctrl_socket:
# Handle control channel input
conn, addr = s.accept()
- print ("Control channel connected")
+ print("Control channel connected")
while True:
msg = conn.recv(65535)
if not msg:
ctl_channel(msg)
conn.close()
if s == query4_socket or s == query6_socket:
- print ("Query received on %s" %
- (ip4 if s == query4_socket else ip6))
+ print("Query received on %s" % (ip4 if s == query4_socket else ip6))
# Handle incoming queries
msg = s.recvfrom(65535)
rsp = create_response(msg[0])
import pytest
-pytest.importorskip('dns', minversion='2.0.0')
+pytest.importorskip("dns", minversion="2.0.0")
import dns.exception
import dns.message
import dns.name
def do_query(server, qname, qtype, tcp=False):
- query = dns.message.make_query(qname, qtype, use_edns=True,
- want_dnssec=True)
+ query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True)
try:
if tcp:
- response = dns.query.tcp(query, server.nameservers[0], timeout=3,
- port=server.port)
+ response = dns.query.tcp(
+ query, server.nameservers[0], timeout=3, port=server.port
+ )
else:
- response = dns.query.udp(query, server.nameservers[0], timeout=3,
- port=server.port)
+ response = dns.query.udp(
+ query, server.nameservers[0], timeout=3, port=server.port
+ )
except dns.exception.Timeout:
- print("error: query timeout for query {} {} to {}".format(
- qname, qtype, server.nameservers[0]))
+ print(
+ "error: query timeout for query {} {} to {}".format(
+ qname, qtype, server.nameservers[0]
+ )
+ )
return None
return response
assert verify is not None
filename = "{}out".format(zone)
- with open(filename, 'w', encoding='utf-8') as file:
+ with open(filename, "w", encoding="utf-8") as file:
for rr in transfer.answer:
file.write(rr.to_text())
- file.write('\n')
+ file.write("\n")
# dnssec-verify command with default arguments.
verify_cmd = [verify, "-z", "-o", zone, filename]
if response.rcode() == dns.rcode.NOERROR:
# fetch key id from response.
for rr in response.answer:
- if rr.match(dns.name.from_text(zone), dns.rdataclass.IN,
- dns.rdatatype.DS, dns.rdatatype.NONE):
+ if rr.match(
+ dns.name.from_text(zone),
+ dns.rdataclass.IN,
+ dns.rdatatype.DS,
+ dns.rdatatype.NONE,
+ ):
if count == 0:
keyid = list(dict(rr.items).items())[0][0].key_tag
count += 1
if count != 1:
- print("error: expected a single DS in response for {} from {},"
- "got {}".format(zone, addr, count))
+ print(
+ "error: expected a single DS in response for {} from {},"
+ "got {}".format(zone, addr, count)
+ )
return {}
else:
- print("error: {} response for {} DNSKEY from {}".format(
- dns.rcode.to_text(response.rcode()), zone, addr))
+ print(
+ "error: {} response for {} DNSKEY from {}".format(
+ dns.rcode.to_text(response.rcode()), zone, addr
+ )
+ )
return {}
filename = "ns9/K{}+013+{:05d}.state".format(zone, keyid)
print("read state file {}".format(filename))
try:
- with open(filename, 'r', encoding='utf-8') as file:
+ with open(filename, "r", encoding="utf-8") as file:
for line in file:
- if line.startswith(';'):
+ if line.startswith(";"):
continue
- key, val = line.strip().split(':', 1)
+ key, val = line.strip().split(":", 1)
state[key.strip()] = val.strip()
except FileNotFoundError:
# wait until zone is fully signed.
signed = False
for _ in range(10):
- response = do_query(server, zone, 'NSEC')
+ response = do_query(server, zone, "NSEC")
if not isinstance(response, dns.message.Message):
print("error: no response for {} NSEC from {}".format(zone, addr))
elif response.rcode() == dns.rcode.NOERROR:
signed = has_signed_apex_nsec(zone, response)
else:
- print("error: {} response for {} NSEC from {}".format(
- dns.rcode.to_text(response.rcode()), zone, addr))
+ print(
+ "error: {} response for {} NSEC from {}".format(
+ dns.rcode.to_text(response.rcode()), zone, addr
+ )
+ )
if signed:
break
# check if zone if DNSSEC valid.
verified = False
- transfer = do_query(server, zone, 'AXFR', tcp=True)
+ transfer = do_query(server, zone, "AXFR", tcp=True)
if not isinstance(transfer, dns.message.Message):
print("error: no response for {} AXFR from {}".format(zone, addr))
elif transfer.rcode() == dns.rcode.NOERROR:
verified = verify_zone(zone, transfer)
else:
- print("error: {} response for {} AXFR from {}".format(
- dns.rcode.to_text(transfer.rcode()), zone, addr))
+ print(
+ "error: {} response for {} AXFR from {}".format(
+ dns.rcode.to_text(transfer.rcode()), zone, addr
+ )
+ )
assert verified
deny = False
search = key
- if key.startswith('!'):
+ if key.startswith("!"):
deny = True
search = key[1:]
print("read log file {}".format(filename))
try:
- with open(filename, 'r', encoding='utf-8') as file:
+ with open(filename, "r", encoding="utf-8") as file:
s = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
if s.find(bytes(log, "ascii")) != -1:
found = True
# DS correctly published in parent.
zone_check(server, "dspublished.checkds.")
- wait_for_log("ns9/named.run",
- "zone dspublished.checkds/IN (signed): checkds: "
- "DS response from 10.53.0.2")
+ wait_for_log(
+ "ns9/named.run",
+ "zone dspublished.checkds/IN (signed): checkds: " "DS response from 10.53.0.2",
+ )
keystate_check(parent, "dspublished.checkds.", "DSPublish")
# DS correctly published in parent (reference to parental-agent).
zone_check(server, "reference.checkds.")
- wait_for_log("ns9/named.run",
- "zone reference.checkds/IN (signed): checkds: "
- "DS response from 10.53.0.2")
+ wait_for_log(
+ "ns9/named.run",
+ "zone reference.checkds/IN (signed): checkds: " "DS response from 10.53.0.2",
+ )
keystate_check(parent, "reference.checkds.", "DSPublish")
# DS not published in parent.
zone_check(server, "missing-dspublished.checkds.")
- wait_for_log("ns9/named.run",
- "zone missing-dspublished.checkds/IN (signed): checkds: "
- "empty DS response from 10.53.0.5")
+ wait_for_log(
+ "ns9/named.run",
+ "zone missing-dspublished.checkds/IN (signed): checkds: "
+ "empty DS response from 10.53.0.5",
+ )
keystate_check(parent, "missing-dspublished.checkds.", "!DSPublish")
# Badly configured parent.
zone_check(server, "bad-dspublished.checkds.")
- wait_for_log("ns9/named.run",
- "zone bad-dspublished.checkds/IN (signed): checkds: "
- "bad DS response from 10.53.0.6")
+ wait_for_log(
+ "ns9/named.run",
+ "zone bad-dspublished.checkds/IN (signed): checkds: "
+ "bad DS response from 10.53.0.6",
+ )
keystate_check(parent, "bad-dspublished.checkds.", "!DSPublish")
# TBD: DS published in parent, but bogus signature.
# DS correctly published in all parents.
zone_check(server, "multiple-dspublished.checkds.")
- wait_for_log("ns9/named.run",
- "zone multiple-dspublished.checkds/IN (signed): checkds: "
- "DS response from 10.53.0.2")
- wait_for_log("ns9/named.run",
- "zone multiple-dspublished.checkds/IN (signed): checkds: "
- "DS response from 10.53.0.4")
+ wait_for_log(
+ "ns9/named.run",
+ "zone multiple-dspublished.checkds/IN (signed): checkds: "
+ "DS response from 10.53.0.2",
+ )
+ wait_for_log(
+ "ns9/named.run",
+ "zone multiple-dspublished.checkds/IN (signed): checkds: "
+ "DS response from 10.53.0.4",
+ )
keystate_check(parent, "multiple-dspublished.checkds.", "DSPublish")
# DS published in only one of multiple parents.
zone_check(server, "incomplete-dspublished.checkds.")
- wait_for_log("ns9/named.run",
- "zone incomplete-dspublished.checkds/IN (signed): checkds: "
- "DS response from 10.53.0.2")
- wait_for_log("ns9/named.run",
- "zone incomplete-dspublished.checkds/IN (signed): checkds: "
- "DS response from 10.53.0.4")
- wait_for_log("ns9/named.run",
- "zone incomplete-dspublished.checkds/IN (signed): checkds: "
- "empty DS response from 10.53.0.5")
+ wait_for_log(
+ "ns9/named.run",
+ "zone incomplete-dspublished.checkds/IN (signed): checkds: "
+ "DS response from 10.53.0.2",
+ )
+ wait_for_log(
+ "ns9/named.run",
+ "zone incomplete-dspublished.checkds/IN (signed): checkds: "
+ "DS response from 10.53.0.4",
+ )
+ wait_for_log(
+ "ns9/named.run",
+ "zone incomplete-dspublished.checkds/IN (signed): checkds: "
+ "empty DS response from 10.53.0.5",
+ )
keystate_check(parent, "incomplete-dspublished.checkds.", "!DSPublish")
# One of the parents is badly configured.
- wait_for_log("ns9/named.run",
- "zone bad2-dspublished.checkds/IN (signed): checkds: "
- "DS response from 10.53.0.2")
- wait_for_log("ns9/named.run",
- "zone bad2-dspublished.checkds/IN (signed): checkds: "
- "DS response from 10.53.0.4")
- wait_for_log("ns9/named.run",
- "zone bad2-dspublished.checkds/IN (signed): checkds: "
- "bad DS response from 10.53.0.6")
+ wait_for_log(
+ "ns9/named.run",
+ "zone bad2-dspublished.checkds/IN (signed): checkds: "
+ "DS response from 10.53.0.2",
+ )
+ wait_for_log(
+ "ns9/named.run",
+ "zone bad2-dspublished.checkds/IN (signed): checkds: "
+ "DS response from 10.53.0.4",
+ )
+ wait_for_log(
+ "ns9/named.run",
+ "zone bad2-dspublished.checkds/IN (signed): checkds: "
+ "bad DS response from 10.53.0.6",
+ )
keystate_check(parent, "bad2-dspublished.checkds.", "!DSPublish")
# TBD: DS published in all parents, but one has bogus signature.
# DS correctly published in single parent.
zone_check(server, "dswithdrawn.checkds.")
- wait_for_log("ns9/named.run",
- "zone dswithdrawn.checkds/IN (signed): checkds: "
- "empty DS response from 10.53.0.5")
+ wait_for_log(
+ "ns9/named.run",
+ "zone dswithdrawn.checkds/IN (signed): checkds: "
+ "empty DS response from 10.53.0.5",
+ )
keystate_check(parent, "dswithdrawn.checkds.", "DSRemoved")
# DS not withdrawn from parent.
zone_check(server, "missing-dswithdrawn.checkds.")
- wait_for_log("ns9/named.run",
- "zone missing-dswithdrawn.checkds/IN (signed): checkds: "
- "DS response from 10.53.0.2")
+ wait_for_log(
+ "ns9/named.run",
+ "zone missing-dswithdrawn.checkds/IN (signed): checkds: "
+ "DS response from 10.53.0.2",
+ )
keystate_check(parent, "missing-dswithdrawn.checkds.", "!DSRemoved")
# Badly configured parent.
zone_check(server, "bad-dswithdrawn.checkds.")
- wait_for_log("ns9/named.run",
- "zone bad-dswithdrawn.checkds/IN (signed): checkds: "
- "bad DS response from 10.53.0.6")
+ wait_for_log(
+ "ns9/named.run",
+ "zone bad-dswithdrawn.checkds/IN (signed): checkds: "
+ "bad DS response from 10.53.0.6",
+ )
keystate_check(parent, "bad-dswithdrawn.checkds.", "!DSRemoved")
# TBD: DS published in parent, but bogus signature.
# DS correctly withdrawn from all parents.
zone_check(server, "multiple-dswithdrawn.checkds.")
- wait_for_log("ns9/named.run",
- "zone multiple-dswithdrawn.checkds/IN (signed): checkds: "
- "empty DS response from 10.53.0.5")
- wait_for_log("ns9/named.run",
- "zone multiple-dswithdrawn.checkds/IN (signed): checkds: "
- "empty DS response from 10.53.0.7")
+ wait_for_log(
+ "ns9/named.run",
+ "zone multiple-dswithdrawn.checkds/IN (signed): checkds: "
+ "empty DS response from 10.53.0.5",
+ )
+ wait_for_log(
+ "ns9/named.run",
+ "zone multiple-dswithdrawn.checkds/IN (signed): checkds: "
+ "empty DS response from 10.53.0.7",
+ )
keystate_check(parent, "multiple-dswithdrawn.checkds.", "DSRemoved")
# DS withdrawn from only one of multiple parents.
zone_check(server, "incomplete-dswithdrawn.checkds.")
- wait_for_log("ns9/named.run",
- "zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
- "DS response from 10.53.0.2")
- wait_for_log("ns9/named.run",
- "zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
- "empty DS response from 10.53.0.5")
- wait_for_log("ns9/named.run",
- "zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
- "empty DS response from 10.53.0.7")
+ wait_for_log(
+ "ns9/named.run",
+ "zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
+ "DS response from 10.53.0.2",
+ )
+ wait_for_log(
+ "ns9/named.run",
+ "zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
+ "empty DS response from 10.53.0.5",
+ )
+ wait_for_log(
+ "ns9/named.run",
+ "zone incomplete-dswithdrawn.checkds/IN (signed): checkds: "
+ "empty DS response from 10.53.0.7",
+ )
keystate_check(parent, "incomplete-dswithdrawn.checkds.", "!DSRemoved")
# One of the parents is badly configured.
- wait_for_log("ns9/named.run",
- "zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
- "empty DS response from 10.53.0.5")
- wait_for_log("ns9/named.run",
- "zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
- "empty DS response from 10.53.0.7")
- wait_for_log("ns9/named.run",
- "zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
- "bad DS response from 10.53.0.6")
+ wait_for_log(
+ "ns9/named.run",
+ "zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
+ "empty DS response from 10.53.0.5",
+ )
+ wait_for_log(
+ "ns9/named.run",
+ "zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
+ "empty DS response from 10.53.0.7",
+ )
+ wait_for_log(
+ "ns9/named.run",
+ "zone bad2-dswithdrawn.checkds/IN (signed): checkds: "
+ "bad DS response from 10.53.0.6",
+ )
keystate_check(parent, "bad2-dswithdrawn.checkds.", "!DSRemoved")
# TBD: DS withdrawn from all parents, but one has bogus signature.
import pytest
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def named_port():
- return int(os.environ.get('PORT', default=5300))
+ return int(os.environ.get("PORT", default=5300))
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def named_tlsport():
- return int(os.environ.get('TLSPORT', default=8853))
+ return int(os.environ.get("TLSPORT", default=8853))
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def control_port():
- return int(os.environ.get('CONTROLPORT', default=9953))
+ return int(os.environ.get("CONTROLPORT", default=9953))
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
+
# DNS 2.0 keyring specifies the algorithm
try:
- keyring = dns.tsigkeyring.from_text({ "foo" : {
- "hmac-sha256",
- "aaaaaaaaaaaa"
- } ,
- "fake" : {
- "hmac-sha256",
- "aaaaaaaaaaaa"
- }
- })
+ keyring = dns.tsigkeyring.from_text(
+ {
+ "foo": {"hmac-sha256", "aaaaaaaaaaaa"},
+ "fake": {"hmac-sha256", "aaaaaaaaaaaa"},
+ }
+ )
except:
- keyring = dns.tsigkeyring.from_text({ "foo" : "aaaaaaaaaaaa",
- "fake" : "aaaaaaaaaaaa" })
+ keyring = dns.tsigkeyring.from_text({"foo": "aaaaaaaaaaaa", "fake": "aaaaaaaaaaaa"})
dopass2 = False
m = dns.message.from_wire(msg, keyring=keyring)
qname = m.question[0].name.to_text()
lqname = qname.lower()
- labels = lqname.split('.')
+ labels = lqname.split(".")
rrtype = m.question[0].rdtype
typename = dns.rdatatype.to_text(rrtype)
# Add a server cookie to the response
if labels[0] != "nocookie":
for o in m.options:
- if o.otype == 10: # Use 10 instead of COOKIE
- if first and labels[0] == "withtsig" and not tcp:
- r.use_tsig(keyring = keyring,
- keyname = dns.name.from_text("fake"),
- algorithm = HMAC_SHA256)
- elif labels[0] != "tcponly" or tcp:
- cookie = o
- if len(o.data) == 8:
- cookie.data = o.data + o.data
- else:
- cookie.data = o.data
- r.use_edns(options=[cookie])
+ if o.otype == 10: # Use 10 instead of COOKIE
+ if first and labels[0] == "withtsig" and not tcp:
+ r.use_tsig(
+ keyring=keyring,
+ keyname=dns.name.from_text("fake"),
+ algorithm=HMAC_SHA256,
+ )
+ elif labels[0] != "tcponly" or tcp:
+ cookie = o
+ if len(o.data) == 8:
+ cookie.data = o.data + o.data
+ else:
+ cookie.data = o.data
+ r.use_edns(options=[cookie])
r.flags |= dns.flags.AA
return r
+
def sigterm(signum, frame):
- print ("Shutting down now...")
- os.remove('ans.pid')
+ print("Shutting down now...")
+ os.remove("ans.pid")
running = False
sys.exit(0)
+
############################################################################
# Main
#
ip6_addr1 = "fd92:7065:b8e:ffff::9"
ip6_addr2 = "fd92:7065:b8e:ffff::10"
-try: port=int(os.environ['PORT'])
-except: port=5300
+try:
+ port = int(os.environ["PORT"])
+except:
+ port = 5300
query4_udp1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_udp1.bind((ip4_addr1, port))
signal.signal(signal.SIGTERM, sigterm)
-f = open('ans.pid', 'w')
+f = open("ans.pid", "w")
pid = os.getpid()
-print (pid, file=f)
+print(pid, file=f)
f.close()
running = True
-print ("Using DNS version %s" % dns.version.version)
-print ("Listening on %s port %d" % (ip4_addr1, port))
-print ("Listening on %s port %d" % (ip4_addr2, port))
+print("Using DNS version %s" % dns.version.version)
+print("Listening on %s port %d" % (ip4_addr1, port))
+print("Listening on %s port %d" % (ip4_addr2, port))
if havev6:
- print ("Listening on %s port %d" % (ip6_addr1, port))
- print ("Listening on %s port %d" % (ip6_addr2, port))
-print ("Ctrl-c to quit")
+ print("Listening on %s port %d" % (ip6_addr1, port))
+ print("Listening on %s port %d" % (ip6_addr2, port))
+print("Ctrl-c to quit")
if havev6:
- input = [query4_udp1, query6_udp1, query4_tcp1, query6_tcp1,
- query4_udp2, query6_udp2, query4_tcp2, query6_tcp2]
+ input = [
+ query4_udp1,
+ query6_udp1,
+ query4_tcp1,
+ query6_tcp1,
+ query4_udp2,
+ query6_udp2,
+ query4_tcp2,
+ query6_tcp2,
+ ]
else:
input = [query4_udp1, query4_tcp1, query4_udp2, query4_tcp2]
for s in inputready:
ns10 = False
- if s == query4_udp1 or s == query6_udp1 or \
- s == query4_udp2 or s == query6_udp2:
+ if s == query4_udp1 or s == query6_udp1 or s == query4_udp2 or s == query6_udp2:
if s == query4_udp1 or s == query6_udp1:
- print ("UDP Query received on %s" %
- (ip4_addr1 if s == query4_udp1 else ip6_addr1), end=" ")
+ print(
+ "UDP Query received on %s"
+ % (ip4_addr1 if s == query4_udp1 else ip6_addr1),
+ end=" ",
+ )
if s == query4_udp2 or s == query6_udp2:
- print ("UDP Query received on %s" %
- (ip4_addr2 if s == query4_udp2 else ip6_addr2), end=" ")
+ print(
+ "UDP Query received on %s"
+ % (ip4_addr2 if s == query4_udp2 else ip6_addr2),
+ end=" ",
+ )
ns10 = True
# Handle incoming queries
msg = s.recvfrom(65535)
print(dns.rcode.to_text(rsp.rcode()))
s.sendto(rsp.to_wire(), msg[1])
if dopass2:
- print ("Sending second UDP response without TSIG", end=" ")
+ print("Sending second UDP response without TSIG", end=" ")
rsp = create_response(msg[0], False, False, ns10)
s.sendto(rsp.to_wire(), msg[1])
print(dns.rcode.to_text(rsp.rcode()))
- if s == query4_tcp1 or s == query6_tcp1 or \
- s == query4_tcp2 or s == query6_tcp2:
+ if s == query4_tcp1 or s == query6_tcp1 or s == query4_tcp2 or s == query6_tcp2:
try:
(cs, _) = s.accept()
if s == query4_tcp1 or s == query6_tcp1:
- print ("TCP Query received on %s" %
- (ip4_addr1 if s == query4_tcp1 else ip6_addr1), end=" ")
+ print(
+ "TCP Query received on %s"
+ % (ip4_addr1 if s == query4_tcp1 else ip6_addr1),
+ end=" ",
+ )
if s == query4_tcp2 or s == query6_tcp2:
- print ("TCP Query received on %s" %
- (ip4_addr2 if s == query4_tcp2 else ip6_addr2), end=" ")
+ print(
+ "TCP Query received on %s"
+ % (ip4_addr2 if s == query4_tcp2 else ip6_addr2),
+ end=" ",
+ )
ns10 = True
# get TCP message length
buf = cs.recv(2)
- length = struct.unpack('>H', buf[:2])[0]
+ length = struct.unpack(">H", buf[:2])[0]
# grep DNS message
msg = cs.recv(length)
rsp = create_response(msg, True, True, ns10)
print(dns.rcode.to_text(rsp.rcode()))
wire = rsp.to_wire()
- cs.send(struct.pack('>H', len(wire)))
+ cs.send(struct.pack(">H", len(wire)))
cs.send(wire)
cs.close()
except s.timeout:
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
+
############################################################################
# Respond to a DNS query.
# SOA gets a unsigned response.
now = datetime.today()
expire = now + timedelta(days=30)
inception = now - timedelta(days=1)
- rrsig = "A 13 2 60 " + expire.strftime("%Y%m%d%H%M%S") + " " + \
- inception.strftime("%Y%m%d%H%M%S") + " 12345 " + qname + \
- " gB+eISXAhSPZU2i/II0W9ZUhC2SCIrb94mlNvP5092WAeXxqN/vG43/1nmDl" + \
- "y2Qs7y5VCjSMOGn85bnaMoAc7w=="
+ rrsig = (
+ "A 13 2 60 "
+ + expire.strftime("%Y%m%d%H%M%S")
+ + " "
+ + inception.strftime("%Y%m%d%H%M%S")
+ + " 12345 "
+ + qname
+ + " gB+eISXAhSPZU2i/II0W9ZUhC2SCIrb94mlNvP5092WAeXxqN/vG43/1nmDl"
+ + "y2Qs7y5VCjSMOGn85bnaMoAc7w=="
+ )
r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
r.answer.append(dns.rrset.from_text(qname, 1, IN, RRSIG, rrsig))
elif rrtype == NS:
r.flags |= dns.flags.AA
return r
+
def sigterm(signum, frame):
- print ("Shutting down now...")
- os.remove('ans.pid')
+ print("Shutting down now...")
+ os.remove("ans.pid")
running = False
sys.exit(0)
+
############################################################################
# Main
#
ip4 = "10.53.0.10"
ip6 = "fd92:7065:b8e:ffff::10"
-try: port=int(os.environ['PORT'])
-except: port=5300
+try:
+ port = int(os.environ["PORT"])
+except:
+ port = 5300
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_socket.bind((ip4, port))
havev6 = False
signal.signal(signal.SIGTERM, sigterm)
-f = open('ans.pid', 'w')
+f = open("ans.pid", "w")
pid = os.getpid()
-print (pid, file=f)
+print(pid, file=f)
f.close()
running = True
-print ("Listening on %s port %d" % (ip4, port))
+print("Listening on %s port %d" % (ip4, port))
if havev6:
- print ("Listening on %s port %d" % (ip6, port))
-print ("Ctrl-c to quit")
+ print("Listening on %s port %d" % (ip6, port))
+print("Ctrl-c to quit")
if havev6:
input = [query4_socket, query6_socket]
for s in inputready:
if s == query4_socket or s == query6_socket:
- print ("Query received on %s" %
- (ip4 if s == query4_socket else ip6), end=" ")
+ print(
+ "Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
+ )
# Handle incoming queries
msg = s.recvfrom(65535)
rsp = create_response(msg[0])
DNSTAP_READ = sys.argv[1]
DATAFILE = sys.argv[2]
-ARGS = [DNSTAP_READ, '-y', DATAFILE]
+ARGS = [DNSTAP_READ, "-y", DATAFILE]
with subprocess.Popen(ARGS, stdout=subprocess.PIPE) as f:
for y in yaml.load_all(f.stdout, Loader=yaml.SafeLoader):
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
+
############################################################################
# Respond to a DNS query.
############################################################################
r = dns.message.make_response(m)
r.set_rcode(NOERROR)
if rrtype == A:
- tld=qname.split('.')[-2] + '.'
- ns="local." + tld
+ tld = qname.split(".")[-2] + "."
+ ns = "local." + tld
r.answer.append(dns.rrset.from_text(qname, 300, IN, A, "10.53.0.11"))
r.answer.append(dns.rrset.from_text(tld, 300, IN, NS, "local." + tld))
r.additional.append(dns.rrset.from_text(ns, 300, IN, A, "10.53.0.11"))
r.flags |= dns.flags.AA
return r
+
def sigterm(signum, frame):
- print ("Shutting down now...")
- os.remove('ans.pid')
+ print("Shutting down now...")
+ os.remove("ans.pid")
running = False
sys.exit(0)
+
############################################################################
# Main
#
ip4 = "10.53.0.11"
ip6 = "fd92:7065:b8e:ffff::11"
-try: port=int(os.environ['PORT'])
-except: port=5300
+try:
+ port = int(os.environ["PORT"])
+except:
+ port = 5300
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_socket.bind((ip4, port))
havev6 = False
signal.signal(signal.SIGTERM, sigterm)
-f = open('ans.pid', 'w')
+f = open("ans.pid", "w")
pid = os.getpid()
-print (pid, file=f)
+print(pid, file=f)
f.close()
running = True
-print ("Listening on %s port %d" % (ip4, port))
+print("Listening on %s port %d" % (ip4, port))
if havev6:
- print ("Listening on %s port %d" % (ip6, port))
-print ("Ctrl-c to quit")
+ print("Listening on %s port %d" % (ip6, port))
+print("Ctrl-c to quit")
if havev6:
input = [query4_socket, query6_socket]
for s in inputready:
if s == query4_socket or s == query6_socket:
- print ("Query received on %s" %
- (ip4 if s == query4_socket else ip6), end=" ")
+ print(
+ "Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
+ )
# Handle incoming queries
msg = s.recvfrom(65535)
rsp = create_response(msg[0])
class RawFormatHeader(dict):
- '''
+ """
A dictionary of raw-format header fields read from a zone file.
- '''
+ """
fields = [
- 'format',
- 'version',
- 'dumptime',
- 'flags',
- 'sourceserial',
- 'lastxfrin',
+ "format",
+ "version",
+ "dumptime",
+ "flags",
+ "sourceserial",
+ "lastxfrin",
]
def __init__(self, file_name):
- header = struct.Struct('>IIIIII')
- with open(file_name, 'rb') as data:
+ header = struct.Struct(">IIIIII")
+ with open(file_name, "rb") as data:
header_data = data.read(header.size)
super().__init__(zip(self.fields, header.unpack_from(header_data)))
def test_unsigned_serial_number():
- '''
+ """
Check whether all signed zone files in the "ns8" subdirectory contain the
serial number of the unsigned version of the zone in the raw-format header.
The test assumes that all "*.signed" files in the "ns8" subdirectory are in
- example[0-9][0-9].com.db.signed files are initially signed by
dnssec-signzone while the others - by named.
- '''
+ """
zones_with_unsigned_serial_missing = []
- for signed_zone in sorted(glob.glob('ns8/*.signed')):
+ for signed_zone in sorted(glob.glob("ns8/*.signed")):
raw_header = RawFormatHeader(signed_zone)
# Ensure the unsigned serial number is placed where it is expected.
- assert raw_header['format'] == 2
- assert raw_header['version'] == 1
+ assert raw_header["format"] == 2
+ assert raw_header["version"] == 1
# Check whether the header flags indicate that the unsigned serial
# number is set and that the latter is indeed set.
- if raw_header['flags'] & 0x02 == 0 or raw_header['sourceserial'] == 0:
+ if raw_header["flags"] & 0x02 == 0 or raw_header["sourceserial"] == 0:
zones_with_unsigned_serial_missing.append(signed_zone)
assert not zones_with_unsigned_serial_missing
PP = policy.dnssec_policy()
# print the unmodified default and a generated zone policy
-print(PP.named_policy['default'])
-print(PP.named_policy['global'])
-print(PP.policy('example.com'))
+print(PP.named_policy["default"])
+print(PP.named_policy["global"])
+print(PP.policy("example.com"))
if len(sys.argv) > 0:
for policy_file in sys.argv[1:]:
PP.load(policy_file)
# now print the modified default and generated zone policies
- print(PP.named_policy['default'])
- print(PP.policy('example.com'))
- print(PP.policy('example.org'))
- print(PP.policy('example.net'))
+ print(PP.named_policy["default"])
+ print(PP.policy("example.com"))
+ print(PP.policy("example.org"))
+ print(PP.policy("example.net"))
# print algorithm policies
- print(PP.alg_policy['RSASHA1'])
- print(PP.alg_policy['RSASHA256'])
- print(PP.alg_policy['ECDSAP256SHA256'])
+ print(PP.alg_policy["RSASHA1"])
+ print(PP.alg_policy["RSASHA256"])
+ print(PP.alg_policy["ECDSAP256SHA256"])
# print another named policy
- print(PP.named_policy['extra'])
+ print(PP.named_policy["extra"])
else:
print("ERROR: Please provide an input file")
DELAY = 0.5
THREADS = []
+
def log(msg):
- print(datetime.datetime.now().strftime('%d-%b-%Y %H:%M:%S.%f ') + msg)
+ print(datetime.datetime.now().strftime("%d-%b-%Y %H:%M:%S.%f ") + msg)
def sigterm(*_):
- log('SIGTERM received, shutting down')
+ log("SIGTERM received, shutting down")
for thread in THREADS:
thread.close()
thread.join()
- os.remove('ans.pid')
+ os.remove("ans.pid")
sys.exit(0)
+
class TCPDelayer(threading.Thread):
- """ For a given TCP connection conn we open a connection to (ip, port),
- and then we delay each incoming packet by DELAY by putting it in a
- queue.
- In the pipelined test TCP should not be used, but it's here for
- completnes.
+ """For a given TCP connection conn we open a connection to (ip, port),
+ and then we delay each incoming packet by DELAY by putting it in a
+ queue.
+ In the pipelined test TCP should not be used, but it's here for
+ completnes.
"""
+
def __init__(self, conn, ip, port):
threading.Thread.__init__(self)
self.conn = conn
while self.running:
curr_timeout = 0.5
try:
- curr_timeout = self.queue[0][0]-time.time()
+ curr_timeout = self.queue[0][0] - time.time()
except StopIteration:
pass
if curr_timeout > 0:
if curr_timeout == 0:
curr_timeout = 0.5
- rfds, _, _ = select.select([self.conn, self.cconn], [], [], curr_timeout)
+ rfds, _, _ = select.select(
+ [self.conn, self.cconn], [], [], curr_timeout
+ )
if self.conn in rfds:
data = self.conn.recv(65535)
if not data:
return
self.conn.send(data)
try:
- while self.queue[0][0]-time.time() < 0:
+ while self.queue[0][0] - time.time() < 0:
_, data = self.queue.pop(0)
self.cconn.send(data)
except StopIteration:
pass
+
class UDPDelayer(threading.Thread):
- """ Every incoming UDP packet is put in a queue for DELAY time, then
- it's sent to (ip, port). We remember the query id to send the
- response we get to a proper source, responses are not delayed.
+ """Every incoming UDP packet is put in a queue for DELAY time, then
+ it's sent to (ip, port). We remember the query id to send the
+ response we get to a proper source, responses are not delayed.
"""
+
def __init__(self, usock, ip, port):
threading.Thread.__init__(self)
self.sock = usock
while self.running:
curr_timeout = 0.5
if self.queue:
- curr_timeout = self.queue[0][0]-time.time()
+ curr_timeout = self.queue[0][0] - time.time()
if curr_timeout >= 0:
if curr_timeout == 0:
curr_timeout = 0.5
- rfds, _, _ = select.select([self.sock, self.csock], [], [], curr_timeout)
+ rfds, _, _ = select.select(
+ [self.sock, self.csock], [], [], curr_timeout
+ )
if self.sock in rfds:
data, addr = self.sock.recvfrom(65535)
if not data:
return
self.queue.append((time.time() + DELAY, data))
- qid = struct.unpack('>H', data[:2])[0]
- log('Received a query from %s, queryid %d' % (str(addr), qid))
+ qid = struct.unpack(">H", data[:2])[0]
+ log("Received a query from %s, queryid %d" % (str(addr), qid))
self.qid_mapping[qid] = addr
if self.csock in rfds:
data, addr = self.csock.recvfrom(65535)
if not data:
return
- qid = struct.unpack('>H', data[:2])[0]
+ qid = struct.unpack(">H", data[:2])[0]
dst = self.qid_mapping.get(qid)
if dst is not None:
self.sock.sendto(data, dst)
- log('Received a response from %s, queryid %d, sending to %s' % (str(addr), qid, str(dst)))
- while self.queue and self.queue[0][0]-time.time() < 0:
+ log(
+ "Received a response from %s, queryid %d, sending to %s"
+ % (str(addr), qid, str(dst))
+ )
+ while self.queue and self.queue[0][0] - time.time() < 0:
_, data = self.queue.pop(0)
- qid = struct.unpack('>H', data[:2])[0]
- log('Sending a query to %s, queryid %d' % (str(self.dst), qid))
+ qid = struct.unpack(">H", data[:2])[0]
+ log("Sending a query to %s, queryid %d" % (str(self.dst), qid))
self.csock.sendto(data, self.dst)
+
def main():
signal.signal(signal.SIGTERM, sigterm)
signal.signal(signal.SIGINT, sigterm)
- with open('ans.pid', 'w') as pidfile:
+ with open("ans.pid", "w") as pidfile:
print(os.getpid(), file=pidfile)
- listenip = '10.53.0.5'
- serverip = '10.53.0.2'
+ listenip = "10.53.0.5"
+ serverip = "10.53.0.2"
try:
- port = int(os.environ['PORT'])
+ port = int(os.environ["PORT"])
except KeyError:
port = 5300
- log('Listening on %s:%d' % (listenip, port))
+ log("Listening on %s:%d" % (listenip, port))
usock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
usock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
while True:
try:
(clientsock, _) = sock.accept()
- log('Accepted connection from %s' % clientsock)
+ log("Accepted connection from %s" % clientsock)
thread = TCPDelayer(clientsock, serverip, port)
thread.start()
THREADS.append(thread)
except socket.timeout:
pass
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
import pytest
-long_test = pytest.mark.skipif(not os.environ.get('CI_ENABLE_ALL_TESTS'),
- reason='CI_ENABLE_ALL_TESTS not set')
+long_test = pytest.mark.skipif(
+ not os.environ.get("CI_ENABLE_ALL_TESTS"), reason="CI_ENABLE_ALL_TESTS not set"
+)
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
+
def endswith(domain, labels):
return domain.endswith("." + labels) or domain == labels
+
############################################################################
# Respond to a DNS query.
# For good. it serves:
m = dns.message.from_wire(msg)
qname = m.question[0].name.to_text()
lqname = qname.lower()
- labels = lqname.split('.')
+ labels = lqname.split(".")
# get qtype
rrtype = m.question[0].rdtype
# Direct query - give direct answer
if endswith(lqname, "8.2.6.0.1.0.0.2.ip6.arpa."):
# Delegate to ns3
- r.authority.append(dns.rrset.from_text("8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns3.good."))
- r.additional.append(dns.rrset.from_text("ns3.good.", 60, IN, A, "10.53.0.3"))
- elif lqname == "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa." and rrtype == PTR:
+ r.authority.append(
+ dns.rrset.from_text(
+ "8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns3.good."
+ )
+ )
+ r.additional.append(
+ dns.rrset.from_text("ns3.good.", 60, IN, A, "10.53.0.3")
+ )
+ elif (
+ lqname
+ == "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa."
+ and rrtype == PTR
+ ):
# Direct query - give direct answer
- r.answer.append(dns.rrset.from_text("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.", 1, IN, PTR, "nee.com."))
+ r.answer.append(
+ dns.rrset.from_text(
+ "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.",
+ 1,
+ IN,
+ PTR,
+ "nee.com.",
+ )
+ )
r.flags |= dns.flags.AA
elif lqname == "1.0.0.2.ip6.arpa." and rrtype == NS:
# NS query at the apex
- r.answer.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, NS, "ns2.good."))
+ r.answer.append(
+ dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, NS, "ns2.good.")
+ )
r.flags |= dns.flags.AA
- elif endswith("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.", lqname):
+ elif endswith(
+ "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.",
+ lqname,
+ ):
# NODATA answer
- r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
+ r.authority.append(
+ dns.rrset.from_text(
+ "1.0.0.2.ip6.arpa.",
+ 30,
+ IN,
+ SOA,
+ "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
+ )
+ )
else:
# NXDOMAIN
- r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
+ r.authority.append(
+ dns.rrset.from_text(
+ "1.0.0.2.ip6.arpa.",
+ 30,
+ IN,
+ SOA,
+ "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
+ )
+ )
r.set_rcode(NXDOMAIN)
return r
elif endswith(lqname, "ip6.arpa."):
r.flags |= dns.flags.AA
elif endswith("1.0.0.2.ip6.arpa.", lqname):
# NODATA answer
- r.authority.append(dns.rrset.from_text("ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
+ r.authority.append(
+ dns.rrset.from_text(
+ "ip6.arpa.",
+ 30,
+ IN,
+ SOA,
+ "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
+ )
+ )
else:
# NXDOMAIN
- r.authority.append(dns.rrset.from_text("ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
+ r.authority.append(
+ dns.rrset.from_text(
+ "ip6.arpa.",
+ 30,
+ IN,
+ SOA,
+ "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
+ )
+ )
r.set_rcode(NXDOMAIN)
return r
elif endswith(lqname, "stale."):
if endswith(lqname, "a.b.stale."):
# Delegate to ns.a.b.stale.
- r.authority.append(dns.rrset.from_text("a.b.stale.", 2, IN, NS, "ns.a.b.stale."))
- r.additional.append(dns.rrset.from_text("ns.a.b.stale.", 2, IN, A, "10.53.0.3"))
+ r.authority.append(
+ dns.rrset.from_text("a.b.stale.", 2, IN, NS, "ns.a.b.stale.")
+ )
+ r.additional.append(
+ dns.rrset.from_text("ns.a.b.stale.", 2, IN, A, "10.53.0.3")
+ )
elif endswith(lqname, "b.stale."):
# Delegate to ns.b.stale.
- r.authority.append(dns.rrset.from_text("b.stale.", 2, IN, NS, "ns.b.stale."))
- r.additional.append(dns.rrset.from_text("ns.b.stale.", 2, IN, A, "10.53.0.4"))
+ r.authority.append(
+ dns.rrset.from_text("b.stale.", 2, IN, NS, "ns.b.stale.")
+ )
+ r.additional.append(
+ dns.rrset.from_text("ns.b.stale.", 2, IN, A, "10.53.0.4")
+ )
elif lqname == "stale." and rrtype == NS:
# NS query at the apex.
r.answer.append(dns.rrset.from_text("stale.", 2, IN, NS, "ns2.stale."))
r.flags |= dns.flags.AA
elif lqname == "stale." and rrtype == SOA:
# SOA query at the apex.
- r.answer.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.stale. 1 2 3 4 5"))
+ r.answer.append(
+ dns.rrset.from_text(
+ "stale.", 2, IN, SOA, "ns2.stale. hostmaster.stale. 1 2 3 4 5"
+ )
+ )
r.flags |= dns.flags.AA
elif lqname == "stale.":
# NODATA answer
- r.authority.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"))
+ r.authority.append(
+ dns.rrset.from_text(
+ "stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"
+ )
+ )
else:
# NXDOMAIN
- r.authority.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"))
+ r.authority.append(
+ dns.rrset.from_text(
+ "stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"
+ )
+ )
r.set_rcode(NXDOMAIN)
return r
elif endswith(lqname, "bad."):
# Good/bad/ugly differs only in how we treat non-empty terminals
if endswith(lqname, "zoop.boing."):
- r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, NS, "ns3." + suffix))
- elif lqname == "many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z." and rrtype == A:
+ r.authority.append(
+ dns.rrset.from_text("zoop.boing." + suffix, 1, IN, NS, "ns3." + suffix)
+ )
+ elif (
+ lqname == "many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z."
+ and rrtype == A
+ ):
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2"))
r.flags |= dns.flags.AA
elif lqname == "" and rrtype == NS:
r.answer.append(dns.rrset.from_text(suffix, 30, IN, NS, "ns2." + suffix))
r.flags |= dns.flags.AA
elif lqname == "ns2." and rrtype == A:
- r.answer.append(dns.rrset.from_text("ns2."+suffix, 30, IN, A, "10.53.0.2"))
+ r.answer.append(dns.rrset.from_text("ns2." + suffix, 30, IN, A, "10.53.0.2"))
r.flags |= dns.flags.AA
elif lqname == "ns2." and rrtype == AAAA:
- r.answer.append(dns.rrset.from_text("ns2."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::2"))
+ r.answer.append(
+ dns.rrset.from_text("ns2." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::2")
+ )
r.flags |= dns.flags.AA
elif lqname == "ns3." and rrtype == A:
- r.answer.append(dns.rrset.from_text("ns3."+suffix, 30, IN, A, "10.53.0.3"))
+ r.answer.append(dns.rrset.from_text("ns3." + suffix, 30, IN, A, "10.53.0.3"))
r.flags |= dns.flags.AA
elif lqname == "ns3." and rrtype == AAAA:
- r.answer.append(dns.rrset.from_text("ns3."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::3"))
+ r.answer.append(
+ dns.rrset.from_text("ns3." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::3")
+ )
r.flags |= dns.flags.AA
elif lqname == "ns4." and rrtype == A:
- r.answer.append(dns.rrset.from_text("ns4."+suffix, 30, IN, A, "10.53.0.4"))
+ r.answer.append(dns.rrset.from_text("ns4." + suffix, 30, IN, A, "10.53.0.4"))
r.flags |= dns.flags.AA
elif lqname == "ns4." and rrtype == AAAA:
- r.answer.append(dns.rrset.from_text("ns4."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::4"))
+ r.answer.append(
+ dns.rrset.from_text("ns4." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::4")
+ )
r.flags |= dns.flags.AA
elif lqname == "a.bit.longer.ns.name." and rrtype == A:
- r.answer.append(dns.rrset.from_text("a.bit.longer.ns.name."+suffix, 1, IN, A, "10.53.0.4"))
+ r.answer.append(
+ dns.rrset.from_text("a.bit.longer.ns.name." + suffix, 1, IN, A, "10.53.0.4")
+ )
r.flags |= dns.flags.AA
elif lqname == "a.bit.longer.ns.name." and rrtype == AAAA:
- r.answer.append(dns.rrset.from_text("a.bit.longer.ns.name."+suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::4"))
+ r.answer.append(
+ dns.rrset.from_text(
+ "a.bit.longer.ns.name." + suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::4"
+ )
+ )
r.flags |= dns.flags.AA
else:
- r.authority.append(dns.rrset.from_text(suffix, 1, IN, SOA, "ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
- if bad or not \
- (endswith("icky.icky.icky.ptang.zoop.boing.", lqname) or \
- endswith("many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.", lqname) or \
- endswith("a.bit.longer.ns.name.", lqname)):
+ r.authority.append(
+ dns.rrset.from_text(
+ suffix,
+ 1,
+ IN,
+ SOA,
+ "ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
+ )
+ )
+ if bad or not (
+ endswith("icky.icky.icky.ptang.zoop.boing.", lqname)
+ or endswith(
+ "many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.",
+ lqname,
+ )
+ or endswith("a.bit.longer.ns.name.", lqname)
+ ):
r.set_rcode(NXDOMAIN)
if ugly:
r.set_rcode(FORMERR)
def sigterm(signum, frame):
- print ("Shutting down now...")
- os.remove('ans.pid')
+ print("Shutting down now...")
+ os.remove("ans.pid")
running = False
sys.exit(0)
+
############################################################################
# Main
#
ip4 = "10.53.0.2"
ip6 = "fd92:7065:b8e:ffff::2"
-try: port=int(os.environ['PORT'])
-except: port=5300
+try:
+ port = int(os.environ["PORT"])
+except:
+ port = 5300
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_socket.bind((ip4, port))
signal.signal(signal.SIGTERM, sigterm)
-f = open('ans.pid', 'w')
+f = open("ans.pid", "w")
pid = os.getpid()
-print (pid, file=f)
+print(pid, file=f)
f.close()
running = True
-print ("Listening on %s port %d" % (ip4, port))
+print("Listening on %s port %d" % (ip4, port))
if havev6:
- print ("Listening on %s port %d" % (ip6, port))
-print ("Ctrl-c to quit")
+ print("Listening on %s port %d" % (ip6, port))
+print("Ctrl-c to quit")
if havev6:
input = [query4_socket, query6_socket]
for s in inputready:
if s == query4_socket or s == query6_socket:
- print ("Query received on %s" %
- (ip4 if s == query4_socket else ip6), end=" ")
+ print(
+ "Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
+ )
# Handle incoming queries
msg = s.recvfrom(65535)
rsp = create_response(msg[0])
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
+
def endswith(domain, labels):
return domain.endswith("." + labels) or domain == labels
+
############################################################################
# Respond to a DNS query.
# For good. it serves:
m = dns.message.from_wire(msg)
qname = m.question[0].name.to_text()
lqname = qname.lower()
- labels = lqname.split('.')
+ labels = lqname.split(".")
# get qtype
rrtype = m.question[0].rdtype
elif rrtype == NS:
# NS a.b.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.a.b.stale."))
- r.additional.append(dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3"))
+ r.additional.append(
+ dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
+ )
r.flags |= dns.flags.AA
elif rrtype == SOA:
# SOA a.b.
- r.answer.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
+ r.answer.append(
+ dns.rrset.from_text(
+ lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
+ )
+ )
r.flags |= dns.flags.AA
else:
# NODATA.
- r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
+ r.authority.append(
+ dns.rrset.from_text(
+ lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
+ )
+ )
else:
- r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
+ r.authority.append(
+ dns.rrset.from_text(
+ lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
+ )
+ )
r.set_rcode(NXDOMAIN)
# NXDOMAIN.
return r
# Good/bad differs only in how we treat non-empty terminals
if lqname == "zoop.boing." and rrtype == NS:
- r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, NS, "ns3."+suffix))
+ r.answer.append(
+ dns.rrset.from_text(lqname + suffix, 1, IN, NS, "ns3." + suffix)
+ )
r.flags |= dns.flags.AA
elif endswith(lqname, "icky.ptang.zoop.boing."):
- r.authority.append(dns.rrset.from_text("icky.ptang.zoop.boing." + suffix, 1, IN, NS, "a.bit.longer.ns.name." + suffix))
+ r.authority.append(
+ dns.rrset.from_text(
+ "icky.ptang.zoop.boing." + suffix,
+ 1,
+ IN,
+ NS,
+ "a.bit.longer.ns.name." + suffix,
+ )
+ )
elif endswith("icky.ptang.zoop.boing.", lqname):
- r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
+ r.authority.append(
+ dns.rrset.from_text(
+ "zoop.boing." + suffix,
+ 1,
+ IN,
+ SOA,
+ "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
+ )
+ )
if bad:
r.set_rcode(NXDOMAIN)
if ugly:
r.set_rcode(FORMERR)
elif endswith(lqname, "zoop.boing."):
- r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
+ r.authority.append(
+ dns.rrset.from_text(
+ "zoop.boing." + suffix,
+ 1,
+ IN,
+ SOA,
+ "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
+ )
+ )
r.set_rcode(NXDOMAIN)
elif ip6req:
- r.authority.append(dns.rrset.from_text("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns4.good."))
+ r.authority.append(
+ dns.rrset.from_text(
+ "1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns4.good."
+ )
+ )
r.additional.append(dns.rrset.from_text("ns4.good.", 60, IN, A, "10.53.0.4"))
else:
r.set_rcode(REFUSED)
def sigterm(signum, frame):
- print ("Shutting down now...")
- os.remove('ans.pid')
+ print("Shutting down now...")
+ os.remove("ans.pid")
running = False
sys.exit(0)
+
############################################################################
# Main
#
ip4 = "10.53.0.3"
ip6 = "fd92:7065:b8e:ffff::3"
-try: port=int(os.environ['PORT'])
-except: port=5300
+try:
+ port = int(os.environ["PORT"])
+except:
+ port = 5300
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_socket.bind((ip4, port))
signal.signal(signal.SIGTERM, sigterm)
-f = open('ans.pid', 'w')
+f = open("ans.pid", "w")
pid = os.getpid()
-print (pid, file=f)
+print(pid, file=f)
f.close()
running = True
-print ("Listening on %s port %d" % (ip4, port))
+print("Listening on %s port %d" % (ip4, port))
if havev6:
- print ("Listening on %s port %d" % (ip6, port))
-print ("Ctrl-c to quit")
+ print("Listening on %s port %d" % (ip6, port))
+print("Ctrl-c to quit")
if havev6:
input = [query4_socket, query6_socket]
for s in inputready:
if s == query4_socket or s == query6_socket:
- print ("Query received on %s" %
- (ip4 if s == query4_socket else ip6), end=" ")
+ print(
+ "Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
+ )
# Handle incoming queries
msg = s.recvfrom(65535)
rsp = create_response(msg[0])
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
+
def endswith(domain, labels):
return domain.endswith("." + labels) or domain == labels
+
############################################################################
# Respond to a DNS query.
# For good. it serves:
m = dns.message.from_wire(msg)
qname = m.question[0].name.to_text()
lqname = qname.lower()
- labels = lqname.split('.')
+ labels = lqname.split(".")
# get qtype
rrtype = m.question[0].rdtype
elif rrtype == NS:
# NS a.b.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.a.b.stale."))
- r.additional.append(dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3"))
+ r.additional.append(
+ dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
+ )
r.flags |= dns.flags.AA
elif rrtype == SOA:
# SOA a.b.
- r.answer.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
+ r.answer.append(
+ dns.rrset.from_text(
+ lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
+ )
+ )
r.flags |= dns.flags.AA
else:
# NODATA.
- r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
+ r.authority.append(
+ dns.rrset.from_text(
+ lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
+ )
+ )
elif lqname == "b.stale.":
if rrtype == NS:
# NS b.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.b.stale."))
- r.additional.append(dns.rrset.from_text("ns.b.stale.", 1, IN, A, "10.53.0.4"))
+ r.additional.append(
+ dns.rrset.from_text("ns.b.stale.", 1, IN, A, "10.53.0.4")
+ )
r.flags |= dns.flags.AA
elif rrtype == SOA:
# SOA b.
- r.answer.append(dns.rrset.from_text(lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"))
+ r.answer.append(
+ dns.rrset.from_text(
+ lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
+ )
+ )
r.flags |= dns.flags.AA
else:
# NODATA.
- r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"))
+ r.authority.append(
+ dns.rrset.from_text(
+ lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
+ )
+ )
else:
- r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"))
+ r.authority.append(
+ dns.rrset.from_text(
+ lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
+ )
+ )
r.set_rcode(NXDOMAIN)
# NXDOMAIN.
return r
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2"))
r.flags |= dns.flags.AA
elif lqname == "icky.ptang.zoop.boing." and rrtype == NS:
- r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, NS, "a.bit.longer.ns.name."+suffix))
+ r.answer.append(
+ dns.rrset.from_text(
+ lqname + suffix, 1, IN, NS, "a.bit.longer.ns.name." + suffix
+ )
+ )
r.flags |= dns.flags.AA
elif endswith(lqname, "icky.ptang.zoop.boing."):
- r.authority.append(dns.rrset.from_text("icky.ptang.zoop.boing." + suffix, 1, IN, SOA, "ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
+ r.authority.append(
+ dns.rrset.from_text(
+ "icky.ptang.zoop.boing." + suffix,
+ 1,
+ IN,
+ SOA,
+ "ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
+ )
+ )
if bad or not endswith("more.icky.icky.icky.ptang.zoop.boing.", lqname):
r.set_rcode(NXDOMAIN)
if ugly:
r.set_rcode(FORMERR)
elif ip6req:
r.flags |= dns.flags.AA
- if lqname == "test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa." and rrtype == TXT:
- r.answer.append(dns.rrset.from_text("test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 1, IN, TXT, "long_ip6_name"))
- elif endswith("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", lqname):
- #NODATA answer
- r.authority.append(dns.rrset.from_text("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, SOA, "ns4.good. hostmaster.arpa. 2018050100 120 30 320 16"))
+ if (
+ lqname
+ == "test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa."
+ and rrtype == TXT
+ ):
+ r.answer.append(
+ dns.rrset.from_text(
+ "test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
+ 1,
+ IN,
+ TXT,
+ "long_ip6_name",
+ )
+ )
+ elif endswith(
+ "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
+ lqname,
+ ):
+ # NODATA answer
+ r.authority.append(
+ dns.rrset.from_text(
+ "1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
+ 60,
+ IN,
+ SOA,
+ "ns4.good. hostmaster.arpa. 2018050100 120 30 320 16",
+ )
+ )
else:
# NXDOMAIN
- r.authority.append(dns.rrset.from_text("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, SOA, "ns4.good. hostmaster.arpa. 2018050100 120 30 320 16"))
+ r.authority.append(
+ dns.rrset.from_text(
+ "1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
+ 60,
+ IN,
+ SOA,
+ "ns4.good. hostmaster.arpa. 2018050100 120 30 320 16",
+ )
+ )
r.set_rcode(NXDOMAIN)
else:
r.set_rcode(REFUSED)
def sigterm(signum, frame):
- print ("Shutting down now...")
- os.remove('ans.pid')
+ print("Shutting down now...")
+ os.remove("ans.pid")
running = False
sys.exit(0)
+
############################################################################
# Main
#
ip4 = "10.53.0.4"
ip6 = "fd92:7065:b8e:ffff::4"
-try: port=int(os.environ['PORT'])
-except: port=5300
+try:
+ port = int(os.environ["PORT"])
+except:
+ port = 5300
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_socket.bind((ip4, port))
signal.signal(signal.SIGTERM, sigterm)
-f = open('ans.pid', 'w')
+f = open("ans.pid", "w")
pid = os.getpid()
-print (pid, file=f)
+print(pid, file=f)
f.close()
running = True
-print ("Listening on %s port %d" % (ip4, port))
+print("Listening on %s port %d" % (ip4, port))
if havev6:
- print ("Listening on %s port %d" % (ip6, port))
-print ("Ctrl-c to quit")
+ print("Listening on %s port %d" % (ip6, port))
+print("Ctrl-c to quit")
if havev6:
input = [query4_socket, query6_socket]
for s in inputready:
if s == query4_socket or s == query6_socket:
- print ("Query received on %s" %
- (ip4 if s == query4_socket else ip6), end=" ")
+ print(
+ "Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
+ )
# Handle incoming queries
msg = s.recvfrom(65535)
rsp = create_response(msg[0])
import pytest
-pytest.importorskip('dns')
+pytest.importorskip("dns")
import dns.exception
import dns.resolver
def do_work(named_proc, resolver, rndc_cmd, kill_method, n_workers, n_queries):
"""Creates a number of A queries to run in parallel
- in order simulate a slightly more realistic test scenario.
+ in order simulate a slightly more realistic test scenario.
- The main idea of this function is to create and send a bunch
- of A queries to a target named instance and during this process
- a request for shutting down named will be issued.
+ The main idea of this function is to create and send a bunch
+ of A queries to a target named instance and during this process
+ a request for shutting down named will be issued.
- In the process of shutting down named, a couple control connections
- are created (by launching rndc) to ensure that the crash was fixed.
+ In the process of shutting down named, a couple control connections
+ are created (by launching rndc) to ensure that the crash was fixed.
- if kill_method=="rndc" named will be asked to shutdown by
- means of rndc stop.
- if kill_method=="sigterm" named will be killed by SIGTERM on
- POSIX systems or by TerminateProcess() on Windows systems.
+ if kill_method=="rndc" named will be asked to shutdown by
+ means of rndc stop.
+ if kill_method=="sigterm" named will be killed by SIGTERM on
+ POSIX systems or by TerminateProcess() on Windows systems.
- :param named_proc: named process instance
- :type named_proc: subprocess.Popen
+ :param named_proc: named process instance
+ :type named_proc: subprocess.Popen
- :param resolver: target resolver
- :type resolver: dns.resolver.Resolver
+ :param resolver: target resolver
+ :type resolver: dns.resolver.Resolver
- :param rndc_cmd: rndc command with default arguments
- :type rndc_cmd: list of strings, e.g. ["rndc", "-p", "23750"]
+ :param rndc_cmd: rndc command with default arguments
+ :type rndc_cmd: list of strings, e.g. ["rndc", "-p", "23750"]
- :kill_method: "rndc" or "sigterm"
- :type kill_method: str
+ :kill_method: "rndc" or "sigterm"
+ :type kill_method: str
- :param n_workers: Number of worker threads to create
- :type n_workers: int
+ :param n_workers: Number of worker threads to create
+ :type n_workers: int
- :param n_queries: Total number of queries to send
- :type n_queries: int
+ :param n_queries: Total number of queries to send
+ :type n_queries: int
"""
-# pylint: disable-msg=too-many-arguments
-# pylint: disable-msg=too-many-locals
+ # pylint: disable-msg=too-many-arguments
+ # pylint: disable-msg=too-many-locals
# helper function, args must be a list or tuple with arguments to rndc.
def launch_rndc(args):
else:
tag = "bad"
length = random.randint(4, 10)
- relname = "".join(letters[
- random.randrange(len(letters))] for i in range(length))
+ relname = "".join(
+ letters[random.randrange(len(letters))] for i in range(length)
+ )
qname = relname + ".test"
- futures[executor.submit(resolver.query, qname, 'A')] = tag
+ futures[executor.submit(resolver.query, qname, "A")] = tag
elif shutdown: # We attempt to stop named in the middle
shutdown = False
if kill_method == "rndc":
- futures[executor.submit(launch_rndc, ['stop'])] = 'stop'
+ futures[executor.submit(launch_rndc, ["stop"])] = "stop"
else:
- futures[executor.submit(named_proc.terminate)] = 'kill'
+ futures[executor.submit(named_proc.terminate)] = "kill"
else:
# We attempt to send couple rndc commands while named is
# being shutdown
- futures[executor.submit(launch_rndc, ['status'])] = 'status'
+ futures[executor.submit(launch_rndc, ["status"])] = "status"
ret_code = -1
for future in as_completed(futures):
if futures[future] == "stop":
ret_code = result
- except (dns.resolver.NXDOMAIN,
- dns.resolver.NoNameservers,
- dns.exception.Timeout):
+ except (
+ dns.resolver.NXDOMAIN,
+ dns.resolver.NoNameservers,
+ dns.exception.Timeout,
+ ):
pass
if kill_method == "rndc":
assert os.path.isfile(rndc_cfg)
# rndc command with default arguments.
- rndc_cmd = [rndc, "-c", rndc_cfg, "-p", str(control_port),
- "-s", "10.53.0.3"]
+ rndc_cmd = [rndc, "-c", rndc_cfg, "-p", str(control_port), "-s", "10.53.0.3"]
# We create a resolver instance that will be used to send queries.
resolver = dns.resolver.Resolver()
- resolver.nameservers = ['10.53.0.3']
+ resolver.nameservers = ["10.53.0.3"]
resolver.port = named_port
# We test named shutting down using two methods:
# wait for named to finish loading
for _ in range(10):
try:
- resolver.query('version.bind', 'TXT', 'CH')
+ resolver.query("version.bind", "TXT", "CH")
break
except (dns.resolver.NoNameservers, dns.exception.Timeout):
time.sleep(1)
- do_work(named_proc, resolver, rndc_cmd,
- kill_method, n_workers=12, n_queries=16)
+ do_work(
+ named_proc, resolver, rndc_cmd, kill_method, n_workers=12, n_queries=16
+ )
# Wait named to exit for a maximum of MAX_TIMEOUT seconds.
MAX_TIMEOUT = 10
# ISO datetime format without msec
-fmt = '%Y-%m-%dT%H:%M:%SZ'
+fmt = "%Y-%m-%dT%H:%M:%SZ"
# The constants were taken from BIND 9 source code (lib/dns/zone.c)
max_refresh = timedelta(seconds=2419200) # 4 weeks
def test_zone_timers_primary(fetch_zones, load_timers, **kwargs):
- statsip = kwargs['statsip']
- statsport = kwargs['statsport']
- zonedir = kwargs['zonedir']
+ statsip = kwargs["statsip"]
+ statsport = kwargs["statsport"]
+ zonedir = kwargs["zonedir"]
zones = fetch_zones(statsip, statsport)
def test_zone_timers_secondary(fetch_zones, load_timers, **kwargs):
- statsip = kwargs['statsip']
- statsport = kwargs['statsport']
- zonedir = kwargs['zonedir']
+ statsip = kwargs["statsip"]
+ statsport = kwargs["statsport"]
+ zonedir = kwargs["zonedir"]
zones = fetch_zones(statsip, statsport)
def test_zone_with_many_keys(fetch_zones, load_zone, **kwargs):
- statsip = kwargs['statsip']
- statsport = kwargs['statsport']
+ statsip = kwargs["statsip"]
+ statsport = kwargs["statsport"]
zones = fetch_zones(statsip, statsport)
for zone in zones:
name = load_zone(zone)
- if name == 'manykeys':
+ if name == "manykeys":
check_manykeys(name)
def create_msg(qname, qtype):
- msg = dns.message.make_query(qname, qtype, want_dnssec=True,
- use_edns=0, payload=4096)
+ msg = dns.message.make_query(
+ qname, qtype, want_dnssec=True, use_edns=0, payload=4096
+ )
return msg
def create_expected(data):
- expected = {"dns-tcp-requests-sizes-received-ipv4": defaultdict(int),
- "dns-tcp-responses-sizes-sent-ipv4": defaultdict(int),
- "dns-tcp-requests-sizes-received-ipv6": defaultdict(int),
- "dns-tcp-responses-sizes-sent-ipv6": defaultdict(int),
- "dns-udp-requests-sizes-received-ipv4": defaultdict(int),
- "dns-udp-requests-sizes-received-ipv6": defaultdict(int),
- "dns-udp-responses-sizes-sent-ipv4": defaultdict(int),
- "dns-udp-responses-sizes-sent-ipv6": defaultdict(int),
- }
+ expected = {
+ "dns-tcp-requests-sizes-received-ipv4": defaultdict(int),
+ "dns-tcp-responses-sizes-sent-ipv4": defaultdict(int),
+ "dns-tcp-requests-sizes-received-ipv6": defaultdict(int),
+ "dns-tcp-responses-sizes-sent-ipv6": defaultdict(int),
+ "dns-udp-requests-sizes-received-ipv4": defaultdict(int),
+ "dns-udp-requests-sizes-received-ipv6": defaultdict(int),
+ "dns-udp-responses-sizes-sent-ipv4": defaultdict(int),
+ "dns-udp-responses-sizes-sent-ipv6": defaultdict(int),
+ }
for k, v in data.items():
for kk, vv in v.items():
def test_traffic(fetch_traffic, **kwargs):
- statsip = kwargs['statsip']
- statsport = kwargs['statsport']
- port = kwargs['port']
+ statsip = kwargs["statsip"]
+ statsport = kwargs["statsport"]
+ port = kwargs["port"]
data = fetch_traffic(statsip, statsport)
exp = create_expected(data)
import generic
-pytestmark = pytest.mark.skipif(not os.environ.get('HAVEJSONSTATS'),
- reason='json-c support disabled in the build')
-requests = pytest.importorskip('requests')
+pytestmark = pytest.mark.skipif(
+ not os.environ.get("HAVEJSONSTATS"), reason="json-c support disabled in the build"
+)
+requests = pytest.importorskip("requests")
# JSON helper functions
def fetch_zones_json(statsip, statsport):
- r = requests.get("http://{}:{}/json/v1/zones".format(statsip, statsport),
- timeout=600)
+ r = requests.get(
+ "http://{}:{}/json/v1/zones".format(statsip, statsport), timeout=600
+ )
assert r.status_code == 200
data = r.json()
def fetch_traffic_json(statsip, statsport):
- r = requests.get("http://{}:{}/json/v1/traffic".format(statsip, statsport),
- timeout=600)
+ r = requests.get(
+ "http://{}:{}/json/v1/traffic".format(statsip, statsport), timeout=600
+ )
assert r.status_code == 200
data = r.json()
def load_timers_json(zone, primary=True):
- name = zone['name']
+ name = zone["name"]
# Check if the primary zone timer exists
- assert 'loaded' in zone
- loaded = datetime.strptime(zone['loaded'], generic.fmt)
+ assert "loaded" in zone
+ loaded = datetime.strptime(zone["loaded"], generic.fmt)
if primary:
# Check if the secondary zone timers does not exist
- assert 'expires' not in zone
- assert 'refresh' not in zone
+ assert "expires" not in zone
+ assert "refresh" not in zone
expires = None
refresh = None
else:
- assert 'expires' in zone
- assert 'refresh' in zone
- expires = datetime.strptime(zone['expires'], generic.fmt)
- refresh = datetime.strptime(zone['refresh'], generic.fmt)
+ assert "expires" in zone
+ assert "refresh" in zone
+ expires = datetime.strptime(zone["expires"], generic.fmt)
+ refresh = datetime.strptime(zone["refresh"], generic.fmt)
return (name, loaded, expires, refresh)
def load_zone_json(zone):
- name = zone['name']
+ name = zone["name"]
return name
def test_zone_timers_primary_json(statsport):
- generic.test_zone_timers_primary(fetch_zones_json, load_timers_json,
- statsip="10.53.0.1", statsport=statsport,
- zonedir="ns1")
+ generic.test_zone_timers_primary(
+ fetch_zones_json,
+ load_timers_json,
+ statsip="10.53.0.1",
+ statsport=statsport,
+ zonedir="ns1",
+ )
def test_zone_timers_secondary_json(statsport):
- generic.test_zone_timers_secondary(fetch_zones_json, load_timers_json,
- statsip="10.53.0.3", statsport=statsport,
- zonedir="ns3")
+ generic.test_zone_timers_secondary(
+ fetch_zones_json,
+ load_timers_json,
+ statsip="10.53.0.3",
+ statsport=statsport,
+ zonedir="ns3",
+ )
def test_zone_with_many_keys_json(statsport):
- generic.test_zone_with_many_keys(fetch_zones_json, load_zone_json,
- statsip="10.53.0.2", statsport=statsport)
+ generic.test_zone_with_many_keys(
+ fetch_zones_json, load_zone_json, statsip="10.53.0.2", statsport=statsport
+ )
def test_traffic_json(named_port, statsport):
- generic_dnspython = pytest.importorskip('generic_dnspython')
- generic_dnspython.test_traffic(fetch_traffic_json,
- statsip="10.53.0.2", statsport=statsport,
- port=named_port)
+ generic_dnspython = pytest.importorskip("generic_dnspython")
+ generic_dnspython.test_traffic(
+ fetch_traffic_json, statsip="10.53.0.2", statsport=statsport, port=named_port
+ )
import generic
-pytestmark = pytest.mark.skipif(not os.environ.get('HAVEXMLSTATS'),
- reason='libxml2 support disabled in the build')
-requests = pytest.importorskip('requests')
+pytestmark = pytest.mark.skipif(
+ not os.environ.get("HAVEXMLSTATS"), reason="libxml2 support disabled in the build"
+)
+requests = pytest.importorskip("requests")
# XML helper functions
def fetch_zones_xml(statsip, statsport):
- r = requests.get("http://{}:{}/xml/v3/zones".format(statsip, statsport),
- timeout=600)
+ r = requests.get(
+ "http://{}:{}/xml/v3/zones".format(statsip, statsport), timeout=600
+ )
assert r.status_code == 200
root = ET.fromstring(r.text)
default_view = None
- for view in root.find('views').iter('view'):
- if view.attrib['name'] == "_default":
+ for view in root.find("views").iter("view"):
+ if view.attrib["name"] == "_default":
default_view = view
break
assert default_view is not None
- return default_view.find('zones').findall('zone')
+ return default_view.find("zones").findall("zone")
def fetch_traffic_xml(statsip, statsport):
-
def load_counters(data):
out = {}
for counter in data.findall("counter"):
- out[counter.attrib['name']] = int(counter.text)
+ out[counter.attrib["name"]] = int(counter.text)
return out
- r = requests.get("http://{}:{}/xml/v3/traffic".format(statsip, statsport),
- timeout=600)
+ r = requests.get(
+ "http://{}:{}/xml/v3/traffic".format(statsip, statsport), timeout=600
+ )
assert r.status_code == 200
root = ET.fromstring(r.text)
for proto in ["udp", "tcp"]:
proto_root = root.find("traffic").find(ip).find(proto)
for counters in proto_root.findall("counters"):
- if counters.attrib['type'] == "request-size":
+ if counters.attrib["type"] == "request-size":
key = "dns-{}-requests-sizes-received-{}".format(proto, ip)
else:
key = "dns-{}-responses-sizes-sent-{}".format(proto, ip)
def load_timers_xml(zone, primary=True):
- name = zone.attrib['name']
+ name = zone.attrib["name"]
- loaded_el = zone.find('loaded')
+ loaded_el = zone.find("loaded")
assert loaded_el is not None
loaded = datetime.strptime(loaded_el.text, generic.fmt)
- expires_el = zone.find('expires')
- refresh_el = zone.find('refresh')
+ expires_el = zone.find("expires")
+ refresh_el = zone.find("refresh")
if primary:
assert expires_el is None
assert refresh_el is None
def load_zone_xml(zone):
- name = zone.attrib['name']
+ name = zone.attrib["name"]
return name
def test_zone_timers_primary_xml(statsport):
- generic.test_zone_timers_primary(fetch_zones_xml, load_timers_xml,
- statsip="10.53.0.1", statsport=statsport,
- zonedir="ns1")
+ generic.test_zone_timers_primary(
+ fetch_zones_xml,
+ load_timers_xml,
+ statsip="10.53.0.1",
+ statsport=statsport,
+ zonedir="ns1",
+ )
def test_zone_timers_secondary_xml(statsport):
- generic.test_zone_timers_secondary(fetch_zones_xml, load_timers_xml,
- statsip="10.53.0.3", statsport=statsport,
- zonedir="ns3")
+ generic.test_zone_timers_secondary(
+ fetch_zones_xml,
+ load_timers_xml,
+ statsip="10.53.0.3",
+ statsport=statsport,
+ zonedir="ns3",
+ )
def test_zone_with_many_keys_xml(statsport):
- generic.test_zone_with_many_keys(fetch_zones_xml, load_zone_xml,
- statsip="10.53.0.2", statsport=statsport)
+ generic.test_zone_with_many_keys(
+ fetch_zones_xml, load_zone_xml, statsip="10.53.0.2", statsport=statsport
+ )
def test_traffic_xml(named_port, statsport):
- generic_dnspython = pytest.importorskip('generic_dnspython')
- generic_dnspython.test_traffic(fetch_traffic_xml,
- statsip="10.53.0.2", statsport=statsport,
- port=named_port)
+ generic_dnspython = pytest.importorskip("generic_dnspython")
+ generic_dnspython.test_traffic(
+ fetch_traffic_xml, statsip="10.53.0.2", statsport=statsport, port=named_port
+ )
# Timeout for establishing all connections requested by a single 'open' command.
OPEN_TIMEOUT = 2
-VERSION_QUERY = b'\x00\x1e\xaf\xb8\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07version\x04bind\x00\x00\x10\x00\x03'
+VERSION_QUERY = b"\x00\x1e\xaf\xb8\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07version\x04bind\x00\x00\x10\x00\x03"
+
def log(msg):
- print(datetime.datetime.now().strftime('%d-%b-%Y %H:%M:%S.%f ') + msg)
+ print(datetime.datetime.now().strftime("%d-%b-%Y %H:%M:%S.%f ") + msg)
def open_connections(active_conns, count, host, port):
except socket.error:
family = socket.AF_INET6
- log('Opening %d connections...' % count)
+ log("Opening %d connections..." % count)
for _ in range(count):
sock = socket.socket(family, socket.SOCK_STREAM)
sock.setblocking(0)
err = sock.connect_ex((host, port))
if err not in (0, errno.EINPROGRESS):
- log('%s on connect for socket %s' % (errno.errorcode[err], sock))
+ log("%s on connect for socket %s" % (errno.errorcode[err], sock))
errors.append(sock)
else:
queued.append(sock)
queued.remove(sock)
err = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
if err:
- log('%s for socket %s' % (errno.errorcode[err], sock))
+ log("%s for socket %s" % (errno.errorcode[err], sock))
errors.append(sock)
else:
sock.send(VERSION_QUERY)
active_conns.append(sock)
if errors:
- log('result=FAIL: %d connection(s) failed' % len(errors))
+ log("result=FAIL: %d connection(s) failed" % len(errors))
elif queued:
- log('result=FAIL: Timed out, aborting %d pending connections' % len(queued))
+ log("result=FAIL: Timed out, aborting %d pending connections" % len(queued))
for sock in queued:
sock.close()
else:
- log('result=OK: Successfully opened %d connections' % count)
+ log("result=OK: Successfully opened %d connections" % count)
def close_connections(active_conns, count):
- log('Closing %s connections...' % "all" if count == 0 else str(count))
+ log("Closing %s connections..." % "all" if count == 0 else str(count))
if count == 0:
count = len(active_conns)
for _ in range(count):
sock = active_conns.pop(0)
sock.close()
- log('result=OK: Successfully closed %d connections' % count)
+ log("result=OK: Successfully closed %d connections" % count)
def sigterm(*_):
- log('SIGTERM received, shutting down')
- os.remove('ans.pid')
+ log("SIGTERM received, shutting down")
+ os.remove("ans.pid")
sys.exit(0)
signal.signal(signal.SIGTERM, sigterm)
- with open('ans.pid', 'w') as pidfile:
+ with open("ans.pid", "w") as pidfile:
print(os.getpid(), file=pidfile)
- listenip = '10.53.0.6'
+ listenip = "10.53.0.6"
try:
- port = int(os.environ['CONTROLPORT'])
+ port = int(os.environ["CONTROLPORT"])
except KeyError:
port = 5309
- log('Listening on %s:%d' % (listenip, port))
+ log("Listening on %s:%d" % (listenip, port))
ctlsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ctlsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
while True:
(clientsock, _) = ctlsock.accept()
- log('Accepted control connection from %s' % clientsock)
- cmdline = clientsock.recv(512).decode('ascii').strip()
+ log("Accepted control connection from %s" % clientsock)
+ cmdline = clientsock.recv(512).decode("ascii").strip()
if cmdline:
- log('Received command: %s' % cmdline)
+ log("Received command: %s" % cmdline)
cmd = cmdline.split()
- if cmd[0] == 'open':
+ if cmd[0] == "open":
count, host, port = cmd[1:]
open_connections(active_conns, int(count), host, int(port))
- elif cmd[0] == 'close':
- (count, ) = cmd[1:]
+ elif cmd[0] == "close":
+ (count,) = cmd[1:]
close_connections(active_conns, int(count))
else:
- log('result=FAIL: Unknown command')
+ log("result=FAIL: Unknown command")
clientsock.close()
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
import pytest
-pytest.importorskip('dns', minversion='2.0.0')
+pytest.importorskip("dns", minversion="2.0.0")
import dns.message
import dns.query
(sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
(response, rtime) = dns.query.receive_tcp(sock, timeout())
- msg = dns.message.make_query("a.example.", "A", use_edns=0,
- payload=1232)
+ msg = dns.message.make_query("a.example.", "A", use_edns=0, payload=1232)
(sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
# Shutdown the socket, but ignore the other side closing the socket
import pytest
-pytest.importorskip('dns', minversion='2.0.0')
+pytest.importorskip("dns", minversion="2.0.0")
import dns.edns
import dns.message
import dns.name
def create_msg(qname, qtype):
- msg = dns.message.make_query(qname, qtype, want_dnssec=True,
- use_edns=0, payload=4096)
+ msg = dns.message.make_query(
+ qname, qtype, want_dnssec=True, use_edns=0, payload=4096
+ )
return msg
# Keepalive is 7 seconds, so the third message should succeed.
#
msg = create_msg("example.", "A")
- kopt = dns.edns.GenericOption(11, b'\x00')
+ kopt = dns.edns.GenericOption(11, b"\x00")
msg.use_edns(edns=True, payload=4096, options=[kopt])
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
(sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
# Receive the initial DNS message with SOA
- (response, rtime) = dns.query.receive_tcp(sock, timeout(),
- one_rr_per_rrset=True)
- soa = response.get_rrset(dns.message.ANSWER, name,
- dns.rdataclass.IN, dns.rdatatype.SOA)
+ (response, rtime) = dns.query.receive_tcp(
+ sock, timeout(), one_rr_per_rrset=True
+ )
+ soa = response.get_rrset(
+ dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
+ )
assert soa is not None
# Pull DNS message from wire until the second SOA is received
while True:
- (response, rtime) = dns.query.receive_tcp(sock, timeout(),
- one_rr_per_rrset=True)
- soa = response.get_rrset(dns.message.ANSWER, name,
- dns.rdataclass.IN, dns.rdatatype.SOA)
+ (response, rtime) = dns.query.receive_tcp(
+ sock, timeout(), one_rr_per_rrset=True
+ )
+ soa = response.get_rrset(
+ dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
+ )
if soa is not None:
break
assert soa is not None
(sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
# Receive the initial DNS message with SOA
- (response, rtime) = dns.query.receive_tcp(sock, timeout(),
- one_rr_per_rrset=True)
- soa = response.get_rrset(dns.message.ANSWER, name,
- dns.rdataclass.IN, dns.rdatatype.SOA)
+ (response, rtime) = dns.query.receive_tcp(
+ sock, timeout(), one_rr_per_rrset=True
+ )
+ soa = response.get_rrset(
+ dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
+ )
assert soa is not None
time.sleep(61) # max-transfer-idle-out is 1 minute
with pytest.raises(ConnectionResetError):
# Process queued TCP messages
while True:
- (response, rtime) = \
- dns.query.receive_tcp(sock, timeout(),
- one_rr_per_rrset=True)
- soa = response.get_rrset(dns.message.ANSWER, name,
- dns.rdataclass.IN, dns.rdatatype.SOA)
+ (response, rtime) = dns.query.receive_tcp(
+ sock, timeout(), one_rr_per_rrset=True
+ )
+ soa = response.get_rrset(
+ dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
+ )
if soa is not None:
break
assert soa is None
(sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
# Receive the initial DNS message with SOA
- (response, rtime) = dns.query.receive_tcp(sock, timeout(),
- one_rr_per_rrset=True)
- soa = response.get_rrset(dns.message.ANSWER, name,
- dns.rdataclass.IN, dns.rdatatype.SOA)
+ (response, rtime) = dns.query.receive_tcp(
+ sock, timeout(), one_rr_per_rrset=True
+ )
+ soa = response.get_rrset(
+ dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
+ )
assert soa is not None
# The loop should timeout at the 5 minutes (max-transfer-time-out)
with pytest.raises(EOFError):
while True:
time.sleep(1)
- (response, rtime) = \
- dns.query.receive_tcp(sock, timeout(),
- one_rr_per_rrset=True)
- soa = response.get_rrset(dns.message.ANSWER, name,
- dns.rdataclass.IN, dns.rdatatype.SOA)
+ (response, rtime) = dns.query.receive_tcp(
+ sock, timeout(), one_rr_per_rrset=True
+ )
+ soa = response.get_rrset(
+ dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
+ )
if soa is not None:
break
assert soa is None
# labels of a zone with * A 192.0.2.1 wildcard
-WILDCARD_ZONE = ('allwild', 'test', '')
+WILDCARD_ZONE = ("allwild", "test", "")
WILDCARD_RDTYPE = dns.rdatatype.A
-WILDCARD_RDATA = '192.0.2.1'
-IPADDR = '10.53.0.1'
+WILDCARD_RDATA = "192.0.2.1"
+IPADDR = "10.53.0.1"
TIMEOUT = 5 # seconds, just a sanity check
# Helpers
def is_nonexpanding_rdtype(rdtype):
"""skip meta types to avoid weird rcodes caused by AXFR etc.; RFC 6895"""
- return not(rdtype == WILDCARD_RDTYPE
- or dns.rdatatype.is_metatype(rdtype) # known metatypes: OPT ...
- or 128 <= rdtype <= 255) # unknown meta types
+ return not (
+ rdtype == WILDCARD_RDTYPE
+ or dns.rdatatype.is_metatype(rdtype) # known metatypes: OPT ...
+ or 128 <= rdtype <= 255
+ ) # unknown meta types
def tcp_query(where, port, qname, qtype):
def query(where, port, label, rdtype):
- labels = (label, ) + WILDCARD_ZONE
+ labels = (label,) + WILDCARD_ZONE
qname = dns.name.Name(labels)
return tcp_query(where, port, qname, rdtype)
# Tests
-@given(label=binary(min_size=1, max_size=63),
- rdtype=integers(min_value=0, max_value=65535).filter(
- is_nonexpanding_rdtype))
+@given(
+ label=binary(min_size=1, max_size=63),
+ rdtype=integers(min_value=0, max_value=65535).filter(is_nonexpanding_rdtype),
+)
def test_wildcard_rdtype_mismatch(label, rdtype, named_port):
"""any label non-matching rdtype must result in to NODATA"""
check_answer_nodata(*query(IPADDR, named_port, label, rdtype))
assert querymsg.is_response(answer), str(answer)
assert answer.rcode() == dns.rcode.NOERROR, str(answer)
assert len(querymsg.question) == 1, str(answer)
- expected_answer = [dns.rrset.from_text(
- querymsg.question[0].name,
- 300, # TTL, ignored by dnspython comparison
- dns.rdataclass.IN,
- WILDCARD_RDTYPE,
- WILDCARD_RDATA)]
+ expected_answer = [
+ dns.rrset.from_text(
+ querymsg.question[0].name,
+ 300, # TTL, ignored by dnspython comparison
+ dns.rdataclass.IN,
+ WILDCARD_RDTYPE,
+ WILDCARD_RDATA,
+ )
+ ]
assert answer.answer == expected_answer, str(answer)
class KTLex:
# pylint: disable=invalid-name
- tokens = ('P', 'T', 'Y', 'M', 'D', 'H', 'S', 'NUM')
+ tokens = ("P", "T", "Y", "M", "D", "H", "S", "NUM")
- t_P = r'(?i)P'
- t_T = r'(?i)T'
- t_Y = r'(?i)Y'
- t_M = r'(?i)M'
- t_D = r'(?i)D'
- t_H = r'(?i)H'
- t_S = r'(?i)S'
+ t_P = r"(?i)P"
+ t_T = r"(?i)T"
+ t_Y = r"(?i)Y"
+ t_M = r"(?i)M"
+ t_D = r"(?i)D"
+ t_H = r"(?i)H"
+ t_S = r"(?i)S"
@staticmethod
def t_NUM(t):
- r'\d+'
+ r"\d+"
t.value = int(t.value)
return t
@staticmethod
def p_period(p):
- '''period : NUM Y
- | NUM M
- | NUM D'''
- if p[2].lower() == 'y':
+ """period : NUM Y
+ | NUM M
+ | NUM D"""
+ if p[2].lower() == "y":
p[0] = int(p[1]) * 31536000
- elif p[2].lower() == 'm':
+ elif p[2].lower() == "m":
p[0] = int(p[1]) * 2592000
- elif p[2].lower() == 'd':
+ elif p[2].lower() == "d":
p[0] += int(p[1]) * 86400
@staticmethod
def p_time(p):
- '''time : NUM H
- | NUM M
- | NUM S'''
- if p[2].lower() == 'h':
+ """time : NUM H
+ | NUM M
+ | NUM S"""
+ if p[2].lower() == "h":
p[0] = int(p[1]) * 3600
- elif p[2].lower() == 'm':
+ elif p[2].lower() == "m":
p[0] = int(p[1]) * 60
- elif p[2].lower() == 's':
+ elif p[2].lower() == "s":
p[0] = int(p[1])
@staticmethod
############################################################################
# Load the contents of a KASP XML file as a python dictionary
############################################################################
-class Kasp():
+class Kasp:
# pylint: disable=invalid-name
@staticmethod
k = {k: v[0] if len(v) == 1 else v for k, v in dd.items()}
d = {t.tag: k}
if t.attrib:
- d[t.tag].update(('@' + k, v) for k, v in t.attrib.iteritems())
+ d[t.tag].update(("@" + k, v) for k, v in t.attrib.iteritems())
if t.text:
text = t.text.strip()
if children or t.attrib:
if text:
- d[t.tag]['#text'] = text
+ d[t.tag]["#text"] = text
else:
d[t.tag] = text
return d
KT = KaspTime()
FIRST = True
- for policy in KINFO['KASP']['Policy']:
- if not policy['@name'] or not policy['Keys']:
+ for policy in KINFO["KASP"]["Policy"]:
+ if not policy["@name"] or not policy["Keys"]:
continue
if not FIRST:
print("")
FIRST = False
- if policy['Description']:
- desc = policy['Description'].strip()
+ if policy["Description"]:
+ desc = policy["Description"].strip()
print("# %s" % re.sub(r"\n\s*", "\n# ", desc))
- print("policy %s {" % policy['@name'])
- ksk = policy['Keys']['KSK']
- zsk = policy['Keys']['ZSK']
- kalg = ksk['Algorithm']
- zalg = zsk['Algorithm']
- algnum = kalg['#text'] or zalg['#text']
+ print("policy %s {" % policy["@name"])
+ ksk = policy["Keys"]["KSK"]
+ zsk = policy["Keys"]["ZSK"]
+ kalg = ksk["Algorithm"]
+ zalg = zsk["Algorithm"]
+ algnum = kalg["#text"] or zalg["#text"]
if algnum:
print("\talgorithm %s;" % dnskey.algstr(int(algnum)))
- if policy['Keys']['TTL']:
- print("\tkeyttl %d;" % KT.parse(policy['Keys']['TTL']))
- if kalg['@length']:
- print("\tkey-size ksk %d;" % int(kalg['@length']))
- if zalg['@length']:
- print("\tkey-size zsk %d;" % int(zalg['@length']))
- if ksk['Lifetime']:
- print("\troll-period ksk %d;" % KT.parse(ksk['Lifetime']))
- if zsk['Lifetime']:
- print("\troll-period zsk %d;" % KT.parse(zsk['Lifetime']))
- if ksk['Standby']:
- print("\tstandby ksk %d;" % int(ksk['Standby']))
- if zsk['Standby']:
- print("\tstandby zsk %d;" % int(zsk['Standby']))
+ if policy["Keys"]["TTL"]:
+ print("\tkeyttl %d;" % KT.parse(policy["Keys"]["TTL"]))
+ if kalg["@length"]:
+ print("\tkey-size ksk %d;" % int(kalg["@length"]))
+ if zalg["@length"]:
+ print("\tkey-size zsk %d;" % int(zalg["@length"]))
+ if ksk["Lifetime"]:
+ print("\troll-period ksk %d;" % KT.parse(ksk["Lifetime"]))
+ if zsk["Lifetime"]:
+ print("\troll-period zsk %d;" % KT.parse(zsk["Lifetime"]))
+ if ksk["Standby"]:
+ print("\tstandby ksk %d;" % int(ksk["Standby"]))
+ if zsk["Standby"]:
+ print("\tstandby zsk %d;" % int(zsk["Standby"]))
print("};")
# Helper functions and variables
+
def added_lines(target_branch, paths):
import subprocess
- subprocess.check_output(['/usr/bin/git', 'fetch', '--depth', '1', 'origin',
- target_branch])
- diff = subprocess.check_output(['/usr/bin/git', 'diff', 'FETCH_HEAD..',
- '--'] + paths)
+
+ subprocess.check_output(
+ ["/usr/bin/git", "fetch", "--depth", "1", "origin", target_branch]
+ )
+ diff = subprocess.check_output(
+ ["/usr/bin/git", "diff", "FETCH_HEAD..", "--"] + paths
+ )
added_lines = []
for line in diff.splitlines():
- if line.startswith(b'+') and not line.startswith(b'+++'):
+ if line.startswith(b"+") and not line.startswith(b"+++"):
added_lines.append(line)
return added_lines
+
def lines_containing(lines, string):
- return [l for l in lines if bytes(string, 'utf-8') in l]
+ return [l for l in lines if bytes(string, "utf-8") in l]
+
-changes_issue_or_mr_id_regex = re.compile(br'\[(GL [#!]|RT #)[0-9]+\]')
-relnotes_issue_or_mr_id_regex = re.compile(br':gl:`[#!][0-9]+`')
-release_notes_regex = re.compile(r'doc/(arm|notes)/notes-.*\.(rst|xml)')
+changes_issue_or_mr_id_regex = re.compile(rb"\[(GL [#!]|RT #)[0-9]+\]")
+relnotes_issue_or_mr_id_regex = re.compile(rb":gl:`[#!][0-9]+`")
+release_notes_regex = re.compile(r"doc/(arm|notes)/notes-.*\.(rst|xml)")
modified_files = danger.git.modified_files
mr_labels = danger.gitlab.mr.labels
for commit in danger.git.commits:
message_lines = commit.message.splitlines()
subject = message_lines[0]
- if (not fixup_error_logged and
- (subject.startswith('fixup!') or
- subject.startswith('Apply suggestion'))):
- fail('Fixup commits are still present in this merge request. '
- 'Please squash them before merging.')
+ if not fixup_error_logged and (
+ subject.startswith("fixup!") or subject.startswith("Apply suggestion")
+ ):
+ fail(
+ "Fixup commits are still present in this merge request. "
+ "Please squash them before merging."
+ )
fixup_error_logged = True
- if len(subject) > 72 and not subject.startswith('Merge branch '):
+ if len(subject) > 72 and not subject.startswith("Merge branch "):
warn(
- f'Subject line for commit {commit.sha} is too long: '
- f'```{subject}``` ({len(subject)} > 72 characters).'
+ f"Subject line for commit {commit.sha} is too long: "
+ f"```{subject}``` ({len(subject)} > 72 characters)."
)
- if subject[-1] == '.':
- fail(f'Trailing dot found in the subject of commit {commit.sha}.')
+ if subject[-1] == ".":
+ fail(f"Trailing dot found in the subject of commit {commit.sha}.")
if len(message_lines) > 1 and message_lines[1]:
- fail(f'No empty line after subject for commit {commit.sha}.')
- if (len(message_lines) < 3 and
- 'fixup! ' not in subject and
- ' CHANGES ' not in subject and
- ' release note' not in subject):
- warn(f'Please write a log message for commit {commit.sha}.')
+ fail(f"No empty line after subject for commit {commit.sha}.")
+ if (
+ len(message_lines) < 3
+ and "fixup! " not in subject
+ and " CHANGES " not in subject
+ and " release note" not in subject
+ ):
+ warn(f"Please write a log message for commit {commit.sha}.")
for line in message_lines[2:]:
- if (len(line) > 72 and
- not line.startswith(' ') and
- not re.match(r'\[[0-9]+\]', line)):
+ if (
+ len(line) > 72
+ and not line.startswith(" ")
+ and not re.match(r"\[[0-9]+\]", line)
+ ):
warn(
- f'Line too long in log message for commit {commit.sha}: '
- f'```{line}``` ({len(line)} > 72 characters).'
+ f"Line too long in log message for commit {commit.sha}: "
+ f"```{line}``` ({len(line)} > 72 characters)."
)
###############################################################################
# FAIL if the merge request is not assigned to any milestone.
if not danger.gitlab.mr.milestone:
- fail('Please assign this merge request to a milestone.')
+ fail("Please assign this merge request to a milestone.")
###############################################################################
# VERSION LABELS
# request is not a backport, version labels are used for indicating
# backporting preferences.)
-backport_label_set = 'Backport' in mr_labels
-version_labels = [l for l in mr_labels if l.startswith('v9.')]
+backport_label_set = "Backport" in mr_labels
+version_labels = [l for l in mr_labels if l.startswith("v9.")]
if backport_label_set and len(version_labels) != 1:
- fail('The *Backport* label is set for this merge request. '
- 'Please also set exactly one version label (*v9.x*).')
+ fail(
+ "The *Backport* label is set for this merge request. "
+ "Please also set exactly one version label (*v9.x*)."
+ )
if not backport_label_set and not version_labels:
- fail('If this merge request is a backport, set the *Backport* label and '
- 'a single version label (*v9.x*) indicating the target branch. '
- 'If not, set version labels for all targeted backport branches.')
+ fail(
+ "If this merge request is a backport, set the *Backport* label and "
+ "a single version label (*v9.x*) indicating the target branch. "
+ "If not, set version labels for all targeted backport branches."
+ )
###############################################################################
# OTHER LABELS
# remind developers about the need to set the latter on merge requests which
# passed review.)
-if 'Review' not in mr_labels:
- warn('This merge request does not have the *Review* label set. '
- 'Please set it if you would like the merge request to be reviewed.')
-elif 'LGTM (Merge OK)' not in mr_labels:
- warn('This merge request is currently in review. '
- 'It should not be merged until it is marked with the *LGTM* label.')
+if "Review" not in mr_labels:
+ warn(
+ "This merge request does not have the *Review* label set. "
+ "Please set it if you would like the merge request to be reviewed."
+ )
+elif "LGTM (Merge OK)" not in mr_labels:
+ warn(
+ "This merge request is currently in review. "
+ "It should not be merged until it is marked with the *LGTM* label."
+ )
###############################################################################
# 'CHANGES' FILE
# * The merge request adds a new CHANGES entry that is not a placeholder and
# does not contain any GitLab/RT issue/MR identifiers.
-changes_modified = 'CHANGES' in modified_files
-no_changes_label_set = 'No CHANGES' in mr_labels
+changes_modified = "CHANGES" in modified_files
+no_changes_label_set = "No CHANGES" in mr_labels
if not changes_modified and not no_changes_label_set:
- fail('This merge request does not modify `CHANGES`. '
- 'Add a `CHANGES` entry or set the *No CHANGES* label.')
+ fail(
+ "This merge request does not modify `CHANGES`. "
+ "Add a `CHANGES` entry or set the *No CHANGES* label."
+ )
if changes_modified and no_changes_label_set:
- fail('This merge request modifies `CHANGES`. '
- 'Revert `CHANGES` modifications or unset the *No Changes* label.')
+ fail(
+ "This merge request modifies `CHANGES`. "
+ "Revert `CHANGES` modifications or unset the *No Changes* label."
+ )
-changes_added_lines = added_lines(target_branch, ['CHANGES'])
-placeholders_added = lines_containing(changes_added_lines, '[placeholder]')
+changes_added_lines = added_lines(target_branch, ["CHANGES"])
+placeholders_added = lines_containing(changes_added_lines, "[placeholder]")
identifiers_found = filter(changes_issue_or_mr_id_regex.search, changes_added_lines)
if changes_added_lines:
if placeholders_added:
- if target_branch != 'main':
- fail('This MR adds at least one placeholder entry to `CHANGES`. '
- 'It should be targeting the `main` branch.')
+ if target_branch != "main":
+ fail(
+ "This MR adds at least one placeholder entry to `CHANGES`. "
+ "It should be targeting the `main` branch."
+ )
elif not any(identifiers_found):
- fail('No valid issue/MR identifiers found in added `CHANGES` entries.')
+ fail("No valid issue/MR identifiers found in added `CHANGES` entries.")
###############################################################################
# RELEASE NOTES
# identifiers are found in the lines added to the release notes by this
# MR.
-release_notes_regex = re.compile(r'doc/(arm|notes)/notes-.*\.(rst|xml)')
+release_notes_regex = re.compile(r"doc/(arm|notes)/notes-.*\.(rst|xml)")
release_notes_changed = list(filter(release_notes_regex.match, modified_files))
-release_notes_label_set = 'Release Notes' in mr_labels
+release_notes_label_set = "Release Notes" in mr_labels
if not release_notes_changed:
if release_notes_label_set:
- fail('This merge request has the *Release Notes* label set. '
- 'Add a release note or unset the *Release Notes* label.')
- elif 'Customer' in mr_labels:
- warn('This merge request has the *Customer* label set. '
- 'Add a release note unless the changes introduced are trivial.')
+ fail(
+ "This merge request has the *Release Notes* label set. "
+ "Add a release note or unset the *Release Notes* label."
+ )
+ elif "Customer" in mr_labels:
+ warn(
+ "This merge request has the *Customer* label set. "
+ "Add a release note unless the changes introduced are trivial."
+ )
if release_notes_changed and not release_notes_label_set:
- fail('This merge request modifies release notes. '
- 'Revert release note modifications or set the *Release Notes* label.')
+ fail(
+ "This merge request modifies release notes. "
+ "Revert release note modifications or set the *Release Notes* label."
+ )
if release_notes_changed:
notes_added_lines = added_lines(target_branch, release_notes_changed)
identifiers_found = filter(relnotes_issue_or_mr_id_regex.search, notes_added_lines)
if notes_added_lines and not any(identifiers_found):
- warn('No valid issue/MR identifiers found in added release notes.')
+ warn("No valid issue/MR identifiers found in added release notes.")
else:
notes_added_lines = []
# identifier is missing from either the added CHANGES entry or the added
# release note.
-if lines_containing(changes_added_lines, '[security]'):
- if not lines_containing(changes_added_lines, '(CVE-20'):
- fail('This merge request fixes a security issue. '
- 'Please add a CHANGES entry which includes a CVE identifier.')
- if not lines_containing(notes_added_lines, 'CVE-20'):
- fail('This merge request fixes a security issue. '
- 'Please add a release note which includes a CVE identifier.')
+if lines_containing(changes_added_lines, "[security]"):
+ if not lines_containing(changes_added_lines, "(CVE-20"):
+ fail(
+ "This merge request fixes a security issue. "
+ "Please add a CHANGES entry which includes a CVE identifier."
+ )
+ if not lines_containing(notes_added_lines, "CVE-20"):
+ fail(
+ "This merge request fixes a security issue. "
+ "Please add a release note which includes a CVE identifier."
+ )
###############################################################################
# PAIRWISE TESTING
# FAIL if the merge request adds any new ./configure switch without an
# associated annotation used for pairwise testing.
-configure_added_lines = added_lines(target_branch, ['configure.ac'])
-switches_added = (lines_containing(configure_added_lines, 'AC_ARG_ENABLE') +
- lines_containing(configure_added_lines, 'AC_ARG_WITH'))
-annotations_added = lines_containing(configure_added_lines, '# [pairwise: ')
+configure_added_lines = added_lines(target_branch, ["configure.ac"])
+switches_added = lines_containing(
+ configure_added_lines, "AC_ARG_ENABLE"
+) + lines_containing(configure_added_lines, "AC_ARG_WITH")
+annotations_added = lines_containing(configure_added_lines, "# [pairwise: ")
if len(switches_added) > len(annotations_added):
- fail('This merge request adds at least one new `./configure` switch that '
- 'is not annotated for pairwise testing purposes.')
+ fail(
+ "This merge request adds at least one new `./configure` switch that "
+ "is not annotated for pairwise testing purposes."
+ )
###############################################################################
# USER-VISIBLE LOG LEVELS
# WARN if the merge request adds new user-visible log messages (INFO or above)
user_visible_log_levels = [
- 'ISC_LOG_INFO',
- 'ISC_LOG_NOTICE',
- 'ISC_LOG_WARNING',
- 'ISC_LOG_ERROR',
- 'ISC_LOG_CRITICAL',
+ "ISC_LOG_INFO",
+ "ISC_LOG_NOTICE",
+ "ISC_LOG_WARNING",
+ "ISC_LOG_ERROR",
+ "ISC_LOG_CRITICAL",
]
-source_added_lines = added_lines(target_branch, ['*.[ch]'])
+source_added_lines = added_lines(target_branch, ["*.[ch]"])
for log_level in user_visible_log_levels:
- if (lines_containing(source_added_lines, log_level)):
- warn('This merge request adds new user-visible log messages with '
- 'level INFO or above. Please double-check log levels and make '
- 'sure none of the messages added is a leftover debug message.')
+ if lines_containing(source_added_lines, log_level):
+ warn(
+ "This merge request adds new user-visible log messages with "
+ "level INFO or above. Please double-check log levels and make "
+ "sure none of the messages added is a leftover debug message."
+ )
break
except ImportError:
# pylint: disable=too-few-public-methods
class ReferenceRole(roles.GenericRole):
- '''
+ """
The ReferenceRole class (used as a base class by GitLabRefRole
below) is only defined in Sphinx >= 2.0.0. For older Sphinx
versions, this stub version of the ReferenceRole class is used
instead.
- '''
+ """
+
def __init__(self):
- super().__init__('', nodes.strong)
+ super().__init__("", nodes.strong)
-GITLAB_BASE_URL = 'https://gitlab.isc.org/isc-projects/bind9/-/'
+GITLAB_BASE_URL = "https://gitlab.isc.org/isc-projects/bind9/-/"
# Custom Sphinx role enabling automatic hyperlinking to GitLab issues/MRs.
super().__init__()
def run(self) -> Tuple[List[Node], List[system_message]]:
- gl_identifier = '[GL %s]' % self.target
+ gl_identifier = "[GL %s]" % self.target
- target_id = 'index-%s' % self.env.new_serialno('index')
- entries = [('single', 'GitLab; ' + gl_identifier, target_id, '', None)]
+ target_id = "index-%s" % self.env.new_serialno("index")
+ entries = [("single", "GitLab; " + gl_identifier, target_id, "", None)]
index = addnodes.index(entries=entries)
- target = nodes.target('', '', ids=[target_id])
+ target = nodes.target("", "", ids=[target_id])
self.inliner.document.note_explicit_target(target)
try:
refuri = self.build_uri()
- reference = nodes.reference('', '', internal=False, refuri=refuri,
- classes=['gl'])
+ reference = nodes.reference(
+ "", "", internal=False, refuri=refuri, classes=["gl"]
+ )
if self.has_explicit_title:
reference += nodes.strong(self.title, self.title)
else:
reference += nodes.strong(gl_identifier, gl_identifier)
except ValueError:
- error_text = 'invalid GitLab identifier %s' % self.target
+ error_text = "invalid GitLab identifier %s" % self.target
msg = self.inliner.reporter.error(error_text, line=self.lineno)
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
return [prb], [msg]
return [index, target, reference], []
def build_uri(self):
- if self.target[0] == '#':
- return self.base_url + 'issues/%d' % int(self.target[1:])
- if self.target[0] == '!':
- return self.base_url + 'merge_requests/%d' % int(self.target[1:])
+ if self.target[0] == "#":
+ return self.base_url + "issues/%d" % int(self.target[1:])
+ if self.target[0] == "!":
+ return self.base_url + "merge_requests/%d" % int(self.target[1:])
raise ValueError
def setup(app):
- roles.register_local_role('gl', GitLabRefRole(GITLAB_BASE_URL))
- app.add_crossref_type('iscman', 'iscman', 'pair: %s; manual page')
+ roles.register_local_role("gl", GitLabRefRole(GITLAB_BASE_URL))
+ app.add_crossref_type("iscman", "iscman", "pair: %s; manual page")
# ignore :option: references to simplify doc backports to v9_16 branch
- app.add_role_to_domain('std', 'option', roles.code_role)
+ app.add_role_to_domain("std", "option", roles.code_role)
+
#
# Configuration file for the Sphinx documentation builder.
# -- Project information -----------------------------------------------------
-project = 'BIND 9'
+project = "BIND 9"
# pylint: disable=redefined-builtin
-copyright = '2022, Internet Systems Consortium'
-author = 'Internet Systems Consortium'
+copyright = "2022, Internet Systems Consortium"
+author = "Internet Systems Consortium"
version_vars = {}
-with open('../../version', encoding='utf-8') as version_file:
+with open("../../version", encoding="utf-8") as version_file:
for line in version_file:
- match = re.match(r'(?P<key>[A-Z]+)=(?P<val>.*)', line)
+ match = re.match(r"(?P<key>[A-Z]+)=(?P<val>.*)", line)
if match:
- version_vars[match.group('key')] = match.group('val')
-
-version = '%s.%s.%s%s%s%s' % (
- version_vars['MAJORVER'],
- version_vars['MINORVER'],
- version_vars['PATCHVER'],
- version_vars['RELEASETYPE'],
- version_vars['RELEASEVER'],
- version_vars['EXTENSIONS'],
+ version_vars[match.group("key")] = match.group("val")
+
+version = "%s.%s.%s%s%s%s" % (
+ version_vars["MAJORVER"],
+ version_vars["MINORVER"],
+ version_vars["PATCHVER"],
+ version_vars["RELEASETYPE"],
+ version_vars["RELEASEVER"],
+ version_vars["EXTENSIONS"],
)
release = version
extensions = []
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = [
- '_build',
- 'Thumbs.db',
- '.DS_Store',
- '*.grammar.rst',
- '*.zoneopts.rst',
- 'build.rst',
- 'catz.rst',
- 'dlz.rst',
- 'dnssec.rst',
- 'dyndb.rst',
- 'logging-categories.rst',
- 'managed-keys.rst',
- 'pkcs11.rst',
- 'platforms.rst',
- 'plugins.rst'
- ]
+ "_build",
+ "Thumbs.db",
+ ".DS_Store",
+ "*.grammar.rst",
+ "*.zoneopts.rst",
+ "build.rst",
+ "catz.rst",
+ "dlz.rst",
+ "dnssec.rst",
+ "dyndb.rst",
+ "logging-categories.rst",
+ "managed-keys.rst",
+ "pkcs11.rst",
+ "platforms.rst",
+ "plugins.rst",
+]
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
-html_theme = 'sphinx_rtd_theme'
-html_static_path = ['_static']
-html_css_files = [
- 'custom.css'
-]
+html_theme = "sphinx_rtd_theme"
+html_static_path = ["_static"]
+html_css_files = ["custom.css"]
# -- Options for LaTeX output ------------------------------------------------
-latex_engine = 'xelatex'
+latex_engine = "xelatex"
# pylint disable=line-too-long
latex_documents = [
- (master_doc, 'Bv9ARM.tex', 'BIND 9 Administrator Reference Manual', author, 'manual'),
- ]
+ (
+ master_doc,
+ "Bv9ARM.tex",
+ "BIND 9 Administrator Reference Manual",
+ author,
+ "manual",
+ ),
+]
latex_logo = "isc-logo.pdf"
# -- Project information -----------------------------------------------------
-project = 'BIND 9'
+project = "BIND 9"
# pylint: disable=wrong-import-position
year = datetime.datetime.now().year
# pylint: disable=redefined-builtin
copyright = "%d, Internet Systems Consortium" % year
-author = 'Internet Systems Consortium'
+author = "Internet Systems Consortium"
# -- General configuration ---------------------------------------------------
extensions = []
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['../arm/_templates']
+templates_path = ["../arm/_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = [
- '_build',
- 'Thumbs.db',
- '.DS_Store',
- ]
+ "_build",
+ "Thumbs.db",
+ ".DS_Store",
+]
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# pylint: disable=line-too-long
man_pages = [
- ('arpaname', 'arpaname', 'translate IP addresses to the corresponding ARPA names', author, 1),
- ('ddns-confgen', 'ddns-confgen', 'ddns key generation tool', author, 8),
- ('delv', 'delv', 'DNS lookup and validation utility', author, 1),
- ('dig', 'dig', 'DNS lookup utility', author, 1),
- ('dnssec-cds', 'dnssec-cds', 'change DS records for a child zone based on CDS/CDNSKEY', author, 8),
- ('dnssec-checkds', 'dnssec-checkds', 'DNSSEC delegation consistency checking tool', author, 8),
- ('dnssec-coverage', 'dnssec-coverage', 'checks future DNSKEY coverage for a zone', author, 8),
- ('dnssec-dsfromkey', 'dnssec-dsfromkey', 'DNSSEC DS RR generation tool', author, 8),
- ('dnssec-importkey', 'dnssec-importkey', 'import DNSKEY records from external systems so they can be managed', author, 8),
- ('dnssec-keyfromlabel', 'dnssec-keyfromlabel', 'DNSSEC key generation tool', author, 8),
- ('dnssec-keygen', 'dnssec-keygen', 'DNSSEC key generation tool', author, 8),
- ('dnssec-keymgr', 'dnssec-keymgr', 'ensure correct DNSKEY coverage based on a defined policy', author, 8),
- ('dnssec-revoke', 'dnssec-revoke', 'set the REVOKED bit on a DNSSEC key', author, 8),
- ('dnssec-settime', 'dnssec-settime', 'set the key timing metadata for a DNSSEC key', author, 8),
- ('dnssec-signzone', 'dnssec-signzone', 'DNSSEC zone signing tool', author, 8),
- ('dnssec-verify', 'dnssec-verify', 'DNSSEC zone verification tool', author, 8),
- ('dnstap-read', 'dnstap-read', 'print dnstap data in human-readable form', author, 1),
- ('filter-aaaa', 'filter-aaaa', 'filter AAAA in DNS responses when A is present', author, 8),
- ('host', 'host', 'DNS lookup utility', author, 1),
- ('mdig', 'mdig', 'DNS pipelined lookup utility', author, 1),
- ('named-checkconf', 'named-checkconf', 'named configuration file syntax checking tool', author, 8),
- ('named-checkzone', 'named-checkzone', 'zone file validity checking or converting tool', author, 8),
- ('named-compilezone', 'named-compilezone', 'zone file validity checking or converting tool', author, 8),
- ('named-journalprint', 'named-journalprint', 'print zone journal in human-readable form', author, 8),
- ('named-nzd2nzf', 'named-nzd2nzf', 'convert an NZD database to NZF text format', author, 8),
- ('named-rrchecker', 'named-rrchecker', 'syntax checker for individual DNS resource records', author, 1),
- ('named.conf', 'named.conf', 'configuration file for **named**', author, 5),
- ('named', 'named', 'Internet domain name server', author, 8),
- ('nsec3hash', 'nsec3hash', 'generate NSEC3 hash', author, 8),
- ('nslookup', 'nslookup', 'query Internet name servers interactively', author, 1),
- ('nsupdate', 'nsupdate', 'dynamic DNS update utility', author, 1),
- ('pkcs11-destroy', 'pkcs11-destroy', 'destroy PKCS#11 objects', author, 8),
- ('pkcs11-keygen', 'pkcs11-keygen', 'generate keys on a PKCS#11 device', author, 8),
- ('pkcs11-list', 'pkcs11-list', 'list PKCS#11 objects', author, 8),
- ('pkcs11-tokens', 'pkcs11-tokens', 'list PKCS#11 available tokens', author, 8),
- ('rndc-confgen', 'rndc-confgen', 'rndc key generation tool', author, 8),
- ('rndc.conf', 'rndc.conf', 'rndc configuration file', author, 5),
- ('rndc', 'rndc', 'name server control utility', author, 8),
- ('tsig-keygen', 'tsig-keygen', 'TSIG key generation tool', author, 8),
- ]
+ (
+ "arpaname",
+ "arpaname",
+ "translate IP addresses to the corresponding ARPA names",
+ author,
+ 1,
+ ),
+ ("ddns-confgen", "ddns-confgen", "ddns key generation tool", author, 8),
+ ("delv", "delv", "DNS lookup and validation utility", author, 1),
+ ("dig", "dig", "DNS lookup utility", author, 1),
+ (
+ "dnssec-cds",
+ "dnssec-cds",
+ "change DS records for a child zone based on CDS/CDNSKEY",
+ author,
+ 8,
+ ),
+ (
+ "dnssec-checkds",
+ "dnssec-checkds",
+ "DNSSEC delegation consistency checking tool",
+ author,
+ 8,
+ ),
+ (
+ "dnssec-coverage",
+ "dnssec-coverage",
+ "checks future DNSKEY coverage for a zone",
+ author,
+ 8,
+ ),
+ ("dnssec-dsfromkey", "dnssec-dsfromkey", "DNSSEC DS RR generation tool", author, 8),
+ (
+ "dnssec-importkey",
+ "dnssec-importkey",
+ "import DNSKEY records from external systems so they can be managed",
+ author,
+ 8,
+ ),
+ (
+ "dnssec-keyfromlabel",
+ "dnssec-keyfromlabel",
+ "DNSSEC key generation tool",
+ author,
+ 8,
+ ),
+ ("dnssec-keygen", "dnssec-keygen", "DNSSEC key generation tool", author, 8),
+ (
+ "dnssec-keymgr",
+ "dnssec-keymgr",
+ "ensure correct DNSKEY coverage based on a defined policy",
+ author,
+ 8,
+ ),
+ (
+ "dnssec-revoke",
+ "dnssec-revoke",
+ "set the REVOKED bit on a DNSSEC key",
+ author,
+ 8,
+ ),
+ (
+ "dnssec-settime",
+ "dnssec-settime",
+ "set the key timing metadata for a DNSSEC key",
+ author,
+ 8,
+ ),
+ ("dnssec-signzone", "dnssec-signzone", "DNSSEC zone signing tool", author, 8),
+ ("dnssec-verify", "dnssec-verify", "DNSSEC zone verification tool", author, 8),
+ (
+ "dnstap-read",
+ "dnstap-read",
+ "print dnstap data in human-readable form",
+ author,
+ 1,
+ ),
+ (
+ "filter-aaaa",
+ "filter-aaaa",
+ "filter AAAA in DNS responses when A is present",
+ author,
+ 8,
+ ),
+ ("host", "host", "DNS lookup utility", author, 1),
+ ("mdig", "mdig", "DNS pipelined lookup utility", author, 1),
+ (
+ "named-checkconf",
+ "named-checkconf",
+ "named configuration file syntax checking tool",
+ author,
+ 8,
+ ),
+ (
+ "named-checkzone",
+ "named-checkzone",
+ "zone file validity checking or converting tool",
+ author,
+ 8,
+ ),
+ (
+ "named-compilezone",
+ "named-compilezone",
+ "zone file validity checking or converting tool",
+ author,
+ 8,
+ ),
+ (
+ "named-journalprint",
+ "named-journalprint",
+ "print zone journal in human-readable form",
+ author,
+ 8,
+ ),
+ (
+ "named-nzd2nzf",
+ "named-nzd2nzf",
+ "convert an NZD database to NZF text format",
+ author,
+ 8,
+ ),
+ (
+ "named-rrchecker",
+ "named-rrchecker",
+ "syntax checker for individual DNS resource records",
+ author,
+ 1,
+ ),
+ ("named.conf", "named.conf", "configuration file for **named**", author, 5),
+ ("named", "named", "Internet domain name server", author, 8),
+ ("nsec3hash", "nsec3hash", "generate NSEC3 hash", author, 8),
+ ("nslookup", "nslookup", "query Internet name servers interactively", author, 1),
+ ("nsupdate", "nsupdate", "dynamic DNS update utility", author, 1),
+ ("pkcs11-destroy", "pkcs11-destroy", "destroy PKCS#11 objects", author, 8),
+ ("pkcs11-keygen", "pkcs11-keygen", "generate keys on a PKCS#11 device", author, 8),
+ ("pkcs11-list", "pkcs11-list", "list PKCS#11 objects", author, 8),
+ ("pkcs11-tokens", "pkcs11-tokens", "list PKCS#11 available tokens", author, 8),
+ ("rndc-confgen", "rndc-confgen", "rndc key generation tool", author, 8),
+ ("rndc.conf", "rndc.conf", "rndc configuration file", author, 5),
+ ("rndc", "rndc", "name server control utility", author, 8),
+ ("tsig-keygen", "tsig-keygen", "TSIG key generation tool", author, 8),
+]
+
def setup(app):
- app.add_crossref_type('iscman', 'iscman', 'pair: %s; manual page')
+ app.add_crossref_type("iscman", "iscman", "pair: %s; manual page")
# ignore :option: references to simplify doc backports to v9_16 branch
- app.add_role_to_domain('std', 'option', roles.code_role)
+ app.add_role_to_domain("std", "option", roles.code_role)
S = State()
-with open(sys.argv[1], "r", encoding='utf-8') as f:
+with open(sys.argv[1], "r", encoding="utf-8") as f:
for line in f.readlines():
if line == "==================\n":
if not S.inside:
S.inside = True
else:
- DNAME = sha256(S.last_line.encode('utf-8')).hexdigest()
+ DNAME = sha256(S.last_line.encode("utf-8")).hexdigest()
DNAME = os.path.join(OUT, DNAME)
if not os.path.isdir(DNAME):
os.mkdir(DNAME)
- FNAME = sha256(S.block.encode('utf-8')).hexdigest() + ".txt"
+ FNAME = sha256(S.block.encode("utf-8")).hexdigest() + ".txt"
FNAME = os.path.join(DNAME, FNAME)
if not os.path.isfile(FNAME):
- with open(FNAME, "w", encoding='utf-8') as w:
+ with open(FNAME, "w", encoding="utf-8") as w:
w.write(S.block)
S.reset()
else: