You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
6.3 KiB
203 lines
6.3 KiB
#!/usr/bin/env python3
|
|
import re
|
|
|
|
ORIGINS = {
|
|
'db.cslabs': 'cslabs.clarkson.edu',
|
|
'db.cosi': 'cosi.clarkson.edu',
|
|
#'db.csprojects': 'csprojects.clarkson.edu',
|
|
}
|
|
|
|
REVERSE = {
|
|
#'db.cslabs.rvs': ?,
|
|
'db.cslabs.rvs.144': '128.153.144.',
|
|
'db.cslabs.rvs.145': '128.153.145.',
|
|
'db.cslabs.rvs.146': '128.153.146.',
|
|
'db.cslabs.rvs.c051': '2605:6480:c051:',
|
|
}
|
|
|
|
RECPAT = re.compile(r'''
|
|
^\s*
|
|
(?P<name>[a-zA-Z0-9_\.-]+)
|
|
(?:\s+
|
|
(?P<class>[a-zA-Z]+))?
|
|
\s+
|
|
(?P<type>[a-zA-Z]+)
|
|
\s+
|
|
(?P<value>.*$)
|
|
''', re.X)
|
|
|
|
class Record(object):
|
|
__slots__ = ['file', 'line', 'name', 'cls', 'tp', 'value']
|
|
|
|
def __init__(self, file, line, name, cls, tp, value):
|
|
self.file = file
|
|
self.line = line
|
|
self.name = name
|
|
self.cls = cls
|
|
self.tp = tp
|
|
self.value = value
|
|
|
|
@classmethod
|
|
def from_match(cls, file, line, match):
|
|
return cls(
|
|
file, line,
|
|
*match.group('name', 'class', 'type', 'value')
|
|
)
|
|
|
|
def __repr__(self):
|
|
return f'Record(file={self.file!r}, line={self.line!r}, name={self.name!r}, cls={self.cls!r}, tp={self.tp!r}, value={self.value!r})'
|
|
|
|
def __hash__(self):
|
|
return hash((self.name, self.cls, self.tp, self.value))
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, Record):
|
|
return False
|
|
return (self.name, self.cls, self.tp, self.value) == (other.name, other.cls, other.tp, other.value)
|
|
|
|
def loc(self):
|
|
return f'{self.file}:{self.line}'
|
|
|
|
FWD = {} # zonefile -> [record]
|
|
RVS = {} # id
|
|
|
|
def get_recs_from_file(f):
|
|
rv = []
|
|
for lno, line in enumerate(open(fn)):
|
|
line = line.partition(';')[0].strip()
|
|
if not line:
|
|
continue
|
|
match = RECPAT.match(line)
|
|
if not match:
|
|
#print(f'rejected line: {line!r}')
|
|
continue
|
|
rec = Record.from_match(fn, lno + 1, match)
|
|
rv.append(rec)
|
|
return rv
|
|
|
|
def expand_ip6(s):
|
|
groups = s.split(':')
|
|
zs = 8 - len(list(filter(None, groups)))
|
|
if zs > 0:
|
|
fi = groups.index('')
|
|
groups = groups[:fi] + ['0000'] * zs + groups[fi+1:]
|
|
return ':'.join(i.rjust(4, '0') for i in groups)
|
|
|
|
def into_digits(addr):
|
|
return [c for c in addr if c != ':']
|
|
|
|
def canon_ip6(digits):
|
|
groups = [''.join(digits[i*4:(i+1)*4]) for i in range(8)]
|
|
fz, lz = None, None
|
|
try:
|
|
fz = groups.index('0000')
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
lz = len(groups) - list(reversed(groups)).index('0000')
|
|
groups = [i.lstrip('0') for i in groups]
|
|
if fz is not None:
|
|
return ':'.join(groups[:fz]) + '::' + ':'.join(groups[lz:])
|
|
return ':'.join(groups)
|
|
|
|
def cons_addr(rec, org):
|
|
if rec.tp != 'PTR':
|
|
raise ValueError(rec)
|
|
if '.' in org:
|
|
parts = '.'.join(reversed(rec.name.split('.')))
|
|
return org + parts
|
|
elif ':' in org:
|
|
digits = list(reversed(rec.name.split('.')))
|
|
digits = [i for i in org if i != ':'] + digits
|
|
return canon_ip6(digits)
|
|
|
|
|
|
for fn, zone in ORIGINS.items():
|
|
FWD[fn] = get_recs_from_file(open(fn))
|
|
|
|
for fn, rv in REVERSE.items():
|
|
RVS[fn] = get_recs_from_file(open(fn))
|
|
print('\n# Comparisons')
|
|
for za in ORIGINS.keys():
|
|
for zb in ORIGINS.keys():
|
|
if za <= zb:
|
|
continue
|
|
print(f'\n## Comparison between `{za}` and `{zb}`')
|
|
sa = set(FWD[za])
|
|
sb = set(FWD[zb])
|
|
ma = sb - sa
|
|
mb = sa - sb
|
|
print(f'\nMissing from `{za}`:\n')
|
|
for i in ma:
|
|
print(f'- `{i}`')
|
|
print(f'\nMissing from `{zb}`:\n')
|
|
for i in mb:
|
|
print(f'- `{i}`')
|
|
|
|
mrvs = {} # canonaddr -> rec
|
|
seen_addrs_cls = {'A': {}, 'AAAA': {}} # class -> addr -> oldname
|
|
seen_names_cls = {'A': {}, 'AAAA': {}} # name -> firstaddr
|
|
for z in ORIGINS.keys():
|
|
for rec in FWD[z]:
|
|
if rec.tp == 'A':
|
|
mrvs[rec.value] = rec
|
|
elif rec.tp == 'AAAA':
|
|
mrvs[canon_ip6(into_digits(expand_ip6(rec.value)))] = rec
|
|
print('\n# Reverse records')
|
|
for r, addr in REVERSE.items():
|
|
cls = 'AAAA' if ':' in addr else 'A'
|
|
seen_addrs = seen_addrs_cls[cls]
|
|
seen_names = seen_names_cls[cls]
|
|
for rec in RVS[r]:
|
|
if rec.tp != 'PTR':
|
|
continue
|
|
a = cons_addr(rec, addr)
|
|
if a in seen_addrs:
|
|
print(f'- `{rec.loc()}`: Rvs `{a}` (for `{rec.value}`) has already been seen as `{seen_addrs[a]}`')
|
|
else:
|
|
try:
|
|
del mrvs[a]
|
|
except KeyError:
|
|
print(f'- `{rec.loc()}`: Rvs `{a}` (supposedly `{rec.value}`) doesn\'t correspond to any forward record')
|
|
seen_addrs[a] = rec.value
|
|
if rec.value in seen_names:
|
|
if a == seen_names[rec.value]:
|
|
print(f'- `{rec.loc()}`: Redundant record for `{rec.value}` to `{a}`')
|
|
else:
|
|
print(f'- `{rec.loc()}`: Name `{rec.value}` (for `{a}`) has already been seen as `{seen_names[rec.value]}`')
|
|
else:
|
|
seen_names[rec.value] = a
|
|
|
|
for addr, rec in mrvs.items():
|
|
print(f'- `{rec.loc()}`: No rvs for `{addr}` (`{rec.name}.{ORIGINS[rec.file]}`)')
|
|
|
|
mfwd = {'A': {}, 'AAAA': {}} # class -> nm -> (addr, rec)
|
|
for z in ORIGINS.keys():
|
|
for rec in FWD[z]:
|
|
if rec.tp == 'A':
|
|
mfwd[rec.tp][rec.name + '.' + ORIGINS[z]] = (rec.value, rec)
|
|
elif rec.tp == 'AAAA':
|
|
mfwd[rec.tp][rec.name + '.' + ORIGINS[z]] = (canon_ip6(into_digits(expand_ip6(rec.value))), rec)
|
|
print('\n# V4/V6')
|
|
names_v4 = set(mfwd['A'].keys())
|
|
names_v6 = set(mfwd['AAAA'].keys())
|
|
print('\n## Names not yet added to IPv6')
|
|
for nm in names_v4 - names_v6:
|
|
print(f'- `{nm}` (at `{mfwd["A"][nm][0]}`)')
|
|
print('\n## Names exclusive to IPv6')
|
|
for nm in names_v6 - names_v4:
|
|
print(f'- `{nm}` (at `{mfwd["AAAA"][nm][0]}`)')
|
|
print('\n## Names in both')
|
|
for nm in names_v4 & names_v6:
|
|
print(f'- `{nm}` (at `{mfwd["A"][nm][0]}` and `{mfwd["AAAA"][nm][0]}`)')
|
|
|
|
#for fn, recs in FWD.items():
|
|
# for rec in recs:
|
|
# print(fn, ':', rec)
|
|
# if rec.tp == 'AAAA':
|
|
# print(canon_ip6(into_digits(expand_ip6(rec.value))))
|
|
#for fn, recs in RVS.items():
|
|
# for rec in recs:
|
|
# print(fn, ':', rec)
|
|
# if rec.tp == 'PTR':
|
|
# print(cons_addr(rec, REVERSE[fn]))
|