|
|
@ -4,7 +4,7 @@ import ipaddress |
|
|
|
|
|
|
|
def Parse(entries): |
|
|
|
for line in sys.stdin: |
|
|
|
line = line.split('#')[0].rstrip(' \r\n') |
|
|
|
line = line.split('#')[0].lstrip(' ').rstrip(' \r\n') |
|
|
|
prefix, asn = line.split(' ') |
|
|
|
assert(len(asn) > 2 and asn[:2] == "AS") |
|
|
|
network = ipaddress.ip_network(prefix) |
|
|
@ -48,66 +48,104 @@ def PrependPrefix(tree, bits): |
|
|
|
tree = [tree, None] |
|
|
|
return tree |
|
|
|
|
|
|
|
def CompactTree(tree): |
|
|
|
def CompactTree(tree, approx=True): |
|
|
|
num = 0 |
|
|
|
if tree is None: |
|
|
|
return (tree, set()) |
|
|
|
if isinstance(tree, int): |
|
|
|
return (tree, set([tree])) |
|
|
|
tree[0], leftas = CompactTree(tree[0]) |
|
|
|
tree[1], rightas = CompactTree(tree[1]) |
|
|
|
tree[0], leftas = CompactTree(tree[0], approx) |
|
|
|
tree[1], rightas = CompactTree(tree[1], approx) |
|
|
|
allas = leftas | rightas |
|
|
|
if len(allas) == 0: |
|
|
|
return (None, allas) |
|
|
|
if len(allas) == 1: |
|
|
|
if approx and len(allas) == 1: |
|
|
|
return (list(allas)[0], allas) |
|
|
|
if isinstance(tree[0], int) and isinstance(tree[1], int) and tree[0] == tree[1]: |
|
|
|
return tree[0], set([tree[0]]) |
|
|
|
return (tree, allas) |
|
|
|
|
|
|
|
def DictMax(d): |
|
|
|
mk = None |
|
|
|
mv = None |
|
|
|
for k, v in d.items(): |
|
|
|
if mv is None or v > mv: |
|
|
|
mk, mv = k, v |
|
|
|
return mk, mv |
|
|
|
|
|
|
|
def PropTree(tree, approx=True): |
|
|
|
if tree is None: |
|
|
|
return (tree, {}, True) |
|
|
|
if isinstance(tree, int): |
|
|
|
return (tree, {tree: 1}, False) |
|
|
|
tree[0], leftcnt, leftnone = PropTree(tree[0], approx) |
|
|
|
tree[1], rightcnt, rightnone = PropTree(tree[1], approx) |
|
|
|
allcnt = {k: leftcnt.get(k, 0) + rightcnt.get(k, 0) for k in set(leftcnt) | set(rightcnt)} |
|
|
|
allnone = leftnone | rightnone |
|
|
|
maxasn, maxcount = DictMax(allcnt) |
|
|
|
if maxcount is not None and maxcount >= 2 and (approx or not allnone): |
|
|
|
return ([tree[0], tree[1], maxasn], {maxasn: 1}, allnone) |
|
|
|
return (tree, allcnt, allnone) |
|
|
|
|
|
|
|
ZEROES = [0 for _ in range(129)] |
|
|
|
|
|
|
|
def TreeSize(tree, depth=0): |
|
|
|
if tree is None: |
|
|
|
return (0, 0, set()) |
|
|
|
def TreeSize(tree, default = None): |
|
|
|
if tree is None or tree == default: |
|
|
|
return (0, 0, 0, set()) |
|
|
|
if isinstance(tree, int): |
|
|
|
return (1, 0, set([tree])) |
|
|
|
left_as, left_node, left_set = TreeSize(tree[0], depth + 1) |
|
|
|
right_as, right_node, right_set = TreeSize(tree[1], depth + 1) |
|
|
|
return (left_as + right_as, left_node + right_node + 1, left_set | right_set) |
|
|
|
return (1, 0, 0, set([tree])) |
|
|
|
this_as = 0 |
|
|
|
this_set = set() |
|
|
|
if len(tree) > 2 and tree[2] != default: |
|
|
|
this_as = 1 |
|
|
|
default = tree[2] |
|
|
|
this_set = set([default]) |
|
|
|
left_as, left_inas, left_node, left_set = TreeSize(tree[0], default) |
|
|
|
right_as, right_inas, right_node, right_set = TreeSize(tree[1], default) |
|
|
|
return (left_as + right_as, this_as + left_inas + right_inas, left_node + right_node + 1, left_set | right_set | this_set) |
|
|
|
|
|
|
|
def TreeSer(tree): |
|
|
|
# 0: 3 byte ASN ollows |
|
|
|
# 1: 4-byte ASN follows |
|
|
|
def TreeSer(tree, default): |
|
|
|
# 0: 4-byte ASN ollows |
|
|
|
# 1: 4-byte default ASN follows |
|
|
|
# 2-3: next bit is x |
|
|
|
# 4-7: next 2 bits are xx |
|
|
|
# 64-127: next 6 bits are xxxxxx |
|
|
|
# 128-131: N-byte jump offset follows |
|
|
|
# 132-239: jump offset 3-110 |
|
|
|
# 240-255: 2 byte ASN follows (with high 4 bits in header) |
|
|
|
# 240-247: 2 byte ASN follows (with high 3 bits in header) |
|
|
|
# 248-255: 2 byte default ASN follows (with high 3 bits in header) |
|
|
|
assert(tree is not None) |
|
|
|
assert(not (isinstance(tree, int) and tree == default)) |
|
|
|
bits = 0 |
|
|
|
nbits = 0 |
|
|
|
while nbits < 6 and isinstance(tree, list): |
|
|
|
if tree[0] is None: |
|
|
|
if tree[0] is None or tree[0] == default: |
|
|
|
bits = bits * 2 + 1 |
|
|
|
nbits += 1 |
|
|
|
tree = tree[1] |
|
|
|
elif tree[1] is None: |
|
|
|
elif tree[1] is None or tree[1] == default: |
|
|
|
bits = bits * 2 |
|
|
|
nbits += 1 |
|
|
|
tree = tree[0] |
|
|
|
else: |
|
|
|
break |
|
|
|
if nbits > 0: |
|
|
|
return bytes([bits + (1 << nbits)]) + TreeSer(tree) |
|
|
|
return bytes([bits + (1 << nbits)]) + TreeSer(tree, default) |
|
|
|
if isinstance(tree, int): |
|
|
|
asn = tree |
|
|
|
if asn >= 2**24: |
|
|
|
return bytes([1]) + asn.to_bytes(4, 'little') |
|
|
|
if asn >= 2**20: |
|
|
|
return bytes([0]) + asn.to_bytes(3, 'little') |
|
|
|
if asn >= 2**19: |
|
|
|
return bytes([0]) + asn.to_bytes(4, 'little') |
|
|
|
return bytes([240 + (asn >> 16), asn & 0xFF, (asn >> 8) & 0xFF]) |
|
|
|
left = TreeSer(tree[0]) |
|
|
|
right = TreeSer(tree[1]) |
|
|
|
newdef = bytes() |
|
|
|
if len(tree) > 2 and tree[2] != default: |
|
|
|
default = tree[2] |
|
|
|
if default >= 2**19: |
|
|
|
newdef = bytes([1]) + default.to_bytes(4, 'little') |
|
|
|
else: |
|
|
|
newdef = bytes([248 + (default >> 16), default & 0xFF, (default >> 8) & 0xFF]) |
|
|
|
return newdef + TreeSer(tree, default) |
|
|
|
left = TreeSer(tree[0], default) |
|
|
|
right = TreeSer(tree[1], default) |
|
|
|
leftlen = len(left) |
|
|
|
assert(leftlen >= 3) |
|
|
|
if leftlen <= 110: |
|
|
@ -117,21 +155,28 @@ def TreeSer(tree): |
|
|
|
assert(leftlennum <= 4) |
|
|
|
return bytes([127 + leftlennum]) + leftlen.to_bytes(leftlennum, 'little') + left + right |
|
|
|
|
|
|
|
def BuildTree(entries): |
|
|
|
tree, _ = CompactTree(UpdateTree([None, None], 128, entries)) |
|
|
|
def BuildTree(entries, approx=True): |
|
|
|
tree = [None, None] |
|
|
|
tree = UpdateTree(tree, 128, entries) |
|
|
|
return tree |
|
|
|
|
|
|
|
entries = [] |
|
|
|
print("[INFO] Loading") |
|
|
|
print("[INFO] Loading", file=sys.stderr) |
|
|
|
Parse(entries) |
|
|
|
print("[INFO] Read %i prefixes" % len(entries), file=sys.stderr) |
|
|
|
print("[INFO] Constructing trie", file=sys.stderr) |
|
|
|
tree = BuildTree(entries) |
|
|
|
as_count, node_count, as_set = TreeSize(tree) |
|
|
|
print("[INFO] Number of prefixes: %i" % as_count, file=sys.stderr) |
|
|
|
print("[INFO] Number of distinct AS values: %i" % len(as_set), file=sys.stderr) |
|
|
|
print("[INFO] Number of decision nodes: %i" % node_count, file=sys.stderr) |
|
|
|
bs = TreeSer(tree) |
|
|
|
as_count, inas_count, node_count, as_set = TreeSize(tree) |
|
|
|
print("[INFO] Trie stats: %i inner, %i prefixes (%i leaf), %i distinct AS" % (node_count, as_count + inas_count, as_count, len(as_set)), file=sys.stderr) |
|
|
|
print("[INFO] Compacting tree", file=sys.stderr) |
|
|
|
tree, _ = CompactTree(tree, True) |
|
|
|
as_count, inas_count, node_count, as_set = TreeSize(tree) |
|
|
|
print("[INFO] Trie stats: %i inner, %i prefixes (%i leaf), %i distinct AS" % (node_count, as_count + inas_count, as_count, len(as_set)), file=sys.stderr) |
|
|
|
print("[INFO] Computing inner prefixes", file=sys.stderr) |
|
|
|
tree, _, _ = PropTree(tree, True) |
|
|
|
as_count, inas_count, node_count, as_set = TreeSize(tree) |
|
|
|
print("[INFO] Trie stats: %i inner, %i prefixes (%i leaf), %i distinct AS" % (node_count, as_count + inas_count, as_count, len(as_set)), file=sys.stderr) |
|
|
|
bs = TreeSer(tree, None) |
|
|
|
print("[INFO] Serialized trie is %i bytes" % len(bs), file=sys.stderr) |
|
|
|
print("[INFO] Writing trie to stdout", file=sys.stderr) |
|
|
|
sys.stdout.buffer.write(bs) |
|
|
|