commit 535d9420a3ea67eb719c874e5e3dcbe1fe0e58f4 Author: Jan Philipp Timme Date: Sun Jun 2 18:23:59 2019 +0200 Initial import diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d30208 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# bird2-roa-generator + +This script reads data from a local git clone of the dn42 registry and generates roa rules for bird2. +It requires python3.7 or greater to run. + +## How to use + +* Clone dn42 registry repository (or refresh your existing clone with `git pull`) +* `./generate.py '/path/to/your/registry-clone'` +* Énjoy your two new roa files! :-) diff --git a/generate.py b/generate.py new file mode 100755 index 0000000..8b4900e --- /dev/null +++ b/generate.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +import os +import re +import sys +import collections +import ipaddress + +# This script requires python3.7 or higher! + +def parse_ipv4_filter_rules(registry_path): + with open(registry_path + '/data/filter.txt') as f: + filter_data = f.read() + filter_regex = re.compile(r'(?P\d+)(\s+)(?Ppermit|deny)(\s+)(?P\d+\.\d+\.\d+\.\d+\/\d+)(\s+)(?P\d+)(\s+)(?P\d+)(\s+)#(\s+)(?P.*)') + ipv4_rules = collections.OrderedDict() + for filter_line in filter_data.split('\n'): + if filter_line.startswith('#'): continue + filter_rule = filter_regex.match(filter_line) + if filter_rule == None: continue + parsed_rule = { + 'filter_num': int(filter_rule.group('filter_num')), + 'route_allowed': (True if filter_rule.group('route_allowed') == 'permit' else False), + 'network': filter_rule.group('network'), + 'min_length': int(filter_rule.group('min_length')), + 'max_length': int(filter_rule.group('max_length')), + 'comment': filter_rule.group('comment') + } + ipv4_rules[parsed_rule['filter_num']] = parsed_rule + return ipv4_rules + +def parse_ipv6_filter_rules(registry_path): + with open(registry_path + '/data/filter6.txt') as f: + filter_data = f.read() + filter_regex = re.compile(r'(?P\d+)(\s+)(?Ppermit|deny)(\s+)(?P([0-9a-f:]*)\/\d+)(\s+)(?P\d+)(\s+)(?P\d+)(\s+)#(\s+)(?P.*)') + ipv6_rules = collections.OrderedDict() + for filter_line in filter_data.split('\n'): + if filter_line.startswith('#'): continue + filter_rule = filter_regex.match(filter_line) + if filter_rule == None: continue + parsed_rule = { + 'filter_num': int(filter_rule.group('filter_num')), + 'route_allowed': (True if filter_rule.group('route_allowed') == 'permit' else False), + 'network': filter_rule.group('network'), + 'min_length': int(filter_rule.group('min_length')), + 'max_length': int(filter_rule.group('max_length')), + 'comment': filter_rule.group('comment') + } + ipv6_rules[parsed_rule['filter_num']] = parsed_rule + return ipv6_rules + +def parse_route_objects(folder_path): + route_objects = [] + for root, dirs, files in os.walk(folder_path): + for filename in files: + current_file = str(root) + str(filename) + route_object = read_route_object_file(current_file) + route_objects.append(route_object) + return route_objects + +def read_route_object_file(file_path): + with open(file_path) as f: + data = f.read() + route_object = {} + for line in data.split('\n'): + if line.strip() == '': continue + items = line.split(':', 1) + key = items[0].strip() + value = items[1].strip() + if key in route_object.keys(): + if isinstance(route_object[key], list): + route_object[key].append(value) + else: + route_object[key] = [route_object[key], value] + else: + route_object[key] = value + return route_object + +def create_roa_entries(route_objects, filter_rules, mode, f): + for route_object in route_objects: + # Convert single value origin attribute to list + if not isinstance(route_object['origin'], list): + route_object['origin'] = [route_object['origin']] + # Check if route is permitted by filter rules + if mode == 'ipv4': + route_network = ipaddress.IPv4Network(route_object['route']) + elif mode == 'ipv6': + route_network = ipaddress.IPv6Network(route_object['route6']) + matching_filter_rule = None + for id, filter_rule in filter_rules.items(): + if mode == 'ipv4': + filter_network = ipaddress.IPv4Network(filter_rule['network']) + elif mode == 'ipv6': + filter_network = ipaddress.IPv6Network(filter_rule['network']) + if route_network.subnet_of(filter_network): + # First final rule match. Take it and leave! + #print('# ' + str(filter_rule) + ' matched for ' + str(route_object)) + matching_filter_rule = filter_rule + break + # Now check if it permits or denies + if matching_filter_rule['route_allowed'] == False: + # Skip non-permitted routes + f.write('# route object not permitted: ' + str(route_network) + "\n") + f.write('# route object was denied by filter rule: ' + str(matching_filter_rule) + "\n") + continue + # Figure out max-length (filter rule wins over route object) + if 'max-length' in route_object.keys(): + allowed_max_len = min(int(matching_filter_rule['max_length']), int(route_object['max-length'])) + else: + allowed_max_len = int(matching_filter_rule['max_length']) + # Create roa for every origin of this route + for origin in route_object['origin']: + # Make sure to strip "AS" prefix from AS value + origin = origin.lstrip('AS') + if mode == 'ipv4': + f.write('route ' + route_object['route'] + ' max ' + str(allowed_max_len) + ' as ' + origin + ';' + "\n") + elif mode == 'ipv6': + f.write('route ' + route_object['route6'] + ' max ' + str(allowed_max_len) + ' as ' + origin + ';' + "\n") + + +if __name__ == '__main__': + # Get registry path over commandline argument + registry_path = sys.argv[1] + # Read filter rules + ipv4_filter_rules = parse_ipv4_filter_rules(registry_path) + ipv6_filter_rules = parse_ipv6_filter_rules(registry_path) + # Read all route objects + ipv4_route_objects = parse_route_objects(registry_path + '/data/route/') + ipv6_route_objects = parse_route_objects(registry_path + '/data/route6/') + # Create routes out of ipv4 route objects + with open('./roa_ipv4.conf', 'w') as target_file: + create_roa_entries(ipv4_route_objects, ipv4_filter_rules, 'ipv4', target_file) + # Create routes out of ipv6 route objects + with open('./roa_ipv6.conf', 'w') as target_file: + create_roa_entries(ipv6_route_objects, ipv6_filter_rules, 'ipv6', target_file)