├── .gitignore ├── LICENSE ├── README.md ├── requirements.txt ├── rpki_as0_bogons ├── __init__.py └── slurm.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2020, Massimiliano Stucchi 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rpki-as0-bogons 2 | 3 | SLURM file generator for bogons with AS0 as origin. 4 | 5 | This script generates a JSON file compatible with [RFC8416](https://www.rfc-editor.org/rfc/rfc8416.txt) to be used for a local validator. 6 | 7 | The script takes bogon files from the [Team Cymru Bogon Reference](https://www.team-cymru.com/bogon-reference.html) or builds a list of all the networks not assigned according to the official [NRO Delegated Statistics](https://www.nro.net/about/rirs/statistics/) file, and turns them into a SLURM file. All the networks are added to the SLURM file with origin: AS0 and with a default MaxPrefix of 32 for IPv4 and 128 for IPv6. 8 | 9 | Once loaded in a validator, this file will suggest the validating software to create "fake" ROAs for these networks. If your network performs origin validation and applies "Invalid: Reject" policies, any BGP announcement of these networks coming from your peers or upstreams should be discarded. 10 | 11 | ## Installation 12 | 13 | You can find the software on PyPi, so you can install it easily via pip. 14 | 15 | ```shell 16 | # pip3 install rpki-as0-bogons 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```shell 22 | usage: rpki-as0-bogons [-h] [-f DEST_FILE] [-P] (-N | -C) 23 | 24 | A script to generate a SLURM file for all bogons with origin AS0 25 | 26 | optional arguments: 27 | -h, --help show this help message and exit 28 | -f DEST_FILE File to be created with all the SLURM content (default is 29 | /usr/local/etc/slurm.json) 30 | -P Include the list of IXP LANs from PeeringDB. While some of 31 | them already have AS0 ROAs, not all of them do. Overlapping 32 | ROAs are fine, so it will be okay to generate them anyway 33 | -N Use the NRO delegated stats 34 | -C Use the Team Cymru's bogons list 35 | 36 | Version 0.3.1 37 | ``` 38 | 39 | You have to specify if you want to use the Team Cymru lists (`-C`) or the NRO delegated stats (`-N`). For bogons only, use the Team Cymru lists, but if you want to include any network that's not assigned or allocated at the moment, it's better to use the NRO file. 40 | 41 | ## Using it with a validator 42 | 43 | ### Routinator 44 | 45 | You should start routinator with the *-x* switch, providing the path to the file (the file is saved by the tool into */usr/local/etc/slurm.json*) 46 | 47 | ### RIPE NCC Validator 3 48 | 49 | You can use curl to supply the file to the validator: 50 | 51 | ```shell 52 | /usr/local/bin/curl -X POST -F "file=@slurm.json" localhost:8080/api/slurm/upload 53 | ``` 54 | 55 | ### Forth 56 | 57 | Use the *--slurm* option when running the software. 58 | 59 | ## Recommendations 60 | 61 | Since the bogon files are updated daily, a daily run via cron is suggested for this tool. 62 | 63 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /rpki_as0_bogons/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.3.2" 2 | __author__ = "Massimiliano Stucchi" 3 | __author_email__ = "max@stucchi.ch" 4 | __copyright__ = "Copyright 2020-2023, Massimiliano Stucchi" 5 | __license__ = "BSD" 6 | __status__ = "Stable" 7 | __url__ = "https://github.com/stucchimax/rpki-as0-bogons" 8 | -------------------------------------------------------------------------------- /rpki_as0_bogons/slurm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2020, Massimiliano Stucchi 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | #2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | #AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | #IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | #DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | #FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | #DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | #SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | #CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | #OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | #OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | import argparse 27 | import ipaddress 28 | import json 29 | import requests 30 | 31 | def main(): 32 | 33 | parser = argparse.ArgumentParser( 34 | description='A script to generate a SLURM file for all bogons with origin AS0', 35 | epilog="Version 0.3.1") 36 | 37 | parser.add_argument("-f", 38 | dest='dest_file', 39 | default="/usr/local/etc/slurm.json", 40 | help="File to be created with all the SLURM content (default is /usr/local/etc/slurm.json)") 41 | 42 | parser.add_argument("-P", 43 | dest='peeringDB_lans', 44 | default=False, 45 | action='store_true', 46 | help="Include the list of IXP LANs from PeeringDB. \ 47 | While some of them already have AS0 ROAs, not all of them do. \ 48 | Overlapping ROAs are fine, so it will be okay to generate them anyway") 49 | 50 | 51 | group = parser.add_mutually_exclusive_group(required=True) 52 | 53 | group.add_argument("-N", 54 | dest='use_delegated_stats', 55 | default=False, 56 | action='store_true', 57 | help="Use the NRO delegated stats") 58 | 59 | group.add_argument("-C", 60 | dest='use_team_cymru', 61 | default=False, 62 | action='store_true', 63 | help="Use the Team Cymru's bogons list") 64 | 65 | args = parser.parse_args() 66 | 67 | roas = [] 68 | if args.use_delegated_stats: 69 | 70 | delegated_stats_url = "https://www.nro.net/wp-content/uploads/apnic-uploads/delegated-extended" 71 | roas = nro_as0_roas(delegated_stats_url) 72 | elif args.use_team_cymru: 73 | 74 | ipv4_cymru_bogons_url = "https://www.team-cymru.org/Services/Bogons/fullbogons-ipv4.txt" 75 | ipv6_cymru_bogons_url = "https://www.team-cymru.org/Services/Bogons/fullbogons-ipv6.txt" 76 | 77 | roas = cymru_as0_roas(ipv4_cymru_bogons_url, 32) + cymru_as0_roas(ipv6_cymru_bogons_url, 128) 78 | 79 | if args.peeringDB_lans: 80 | roas += peeringDB_roas(32,128) 81 | 82 | ### Now build the output file structure, and add the ROAs at the end 83 | 84 | output = {} 85 | 86 | output['slurmVersion'] = 1 87 | output["validationOutputFilters"] = {} 88 | output["validationOutputFilters"]["prefixFilters"] = [] 89 | output["validationOutputFilters"]["bgpsecFilters"] = [] 90 | output["locallyAddedAssertions"] = {} 91 | output["locallyAddedAssertions"]["prefixAssertions"] = [] 92 | output["locallyAddedAssertions"]["bgpsecAssertions"] = [] 93 | 94 | output['locallyAddedAssertions']["prefixAssertions"] = roas 95 | 96 | with open(args.dest_file, "w") as f: 97 | f.write(json.dumps(output, indent=2)) 98 | 99 | def cymru_as0_roas(url, maxLength): 100 | bogons = requests.get(url).text.split("\n") 101 | # Remove the first and the last line 102 | bogons.pop(0) # # last updated 1581670201 (Fri Feb 14 08:50:01 2020 GMT) 103 | bogons.pop(0) # # Know your network! Please rigorously test all filters! 104 | bogons.pop() # 105 | 106 | return as0_roas_for(bogons, maxLength) 107 | 108 | def nro_as0_roas(url): 109 | delegations = requests.get(url).text.split("\n") 110 | # Remove header and summaries 111 | delegations.pop(0) # 2|nro|20200214|574416|19821213|20200214|+0000 112 | delegations.pop(0) # nro|*|asn|*|91534|summary 113 | delegations.pop(0) # nro|*|ipv4|*|214428|summary 114 | delegations.pop(0) # nro|*|ipv6|*|268454|summary 115 | delegations.pop() # 116 | 117 | roas = [] 118 | 119 | for line in delegations: 120 | delegation = line.split("|") 121 | type = delegation[2] 122 | value = delegation[3] 123 | length = int(delegation[4]) 124 | status = delegation[6] 125 | 126 | if status == "available" or status == "ianapool" or status == "ietf" or status == "reserved": # meaning !assigned 127 | if type == "ipv4": 128 | v4networks = ipaddress.summarize_address_range(ipaddress.IPv4Address(value), ipaddress.IPv4Address(value)+(length-1)) 129 | roas += as0_roas_for(v4networks, 32) 130 | 131 | if type == "ipv6": 132 | v6networks = [ipaddress.IPv6Network(value+"/"+str(length))] 133 | roas += as0_roas_for(v6networks, 128) 134 | 135 | return roas 136 | 137 | 138 | def peeringDB_roas(maxLength_v4, maxLength_v6): 139 | 140 | roas = [] 141 | 142 | url = "https://peeringdb.com/api/ixlan?depth=2" 143 | 144 | s = requests.get(url) 145 | 146 | for i in s.json()['data']: 147 | 148 | for l in i['ixpfx_set']: 149 | new_entry = {} 150 | new_entry['asn'] = 0 151 | new_entry['prefix'] = str(l['prefix']) 152 | if l['protocol'] == "IPv6": 153 | new_entry['maxPrefixLength'] = maxLength_v6 154 | elif l['protocol'] == "IPv4": 155 | new_entry['maxPrefixLength'] = maxLength_v4 156 | 157 | roas.append(new_entry) 158 | 159 | return roas 160 | 161 | 162 | def as0_roas_for(bogons, maxLength): 163 | as0_roas = [] 164 | 165 | for network in bogons: 166 | new_entry = {} 167 | new_entry['asn'] = 0 168 | new_entry['prefix'] = str(network) 169 | new_entry['maxPrefixLength'] = maxLength 170 | as0_roas.append(new_entry) 171 | 172 | return as0_roas 173 | 174 | 175 | if __name__ == "__main__": 176 | main() 177 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2020-2023, Massimiliano Stucchi 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | #2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | #AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | #IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | #DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | #FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | #DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | #SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | #CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | #OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | #OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | 27 | 28 | import rpki_as0_bogons 29 | version = rpki_as0_bogons.__version__ 30 | 31 | import codecs 32 | import os 33 | import sys 34 | 35 | from os.path import abspath, dirname, join 36 | from setuptools import setup, find_packages 37 | 38 | here = abspath(dirname(__file__)) 39 | 40 | 41 | def parse_requirements(filename): 42 | """ load requirements from a pip requirements file """ 43 | lineiter = (line.strip() for line in open(filename)) 44 | return [line for line in lineiter if line and not line.startswith("#")] 45 | 46 | with codecs.open(join(here, 'README.md'), encoding='utf-8') as f: 47 | README = f.read() 48 | 49 | if sys.argv[-1] == 'publish': 50 | os.system('python3 setup.py sdist upload') 51 | print("You probably want to also tag the version now:") 52 | print((" git tag -a %s -m 'version %s'" % (version, version))) 53 | print(" git push --tags") 54 | sys.exit() 55 | 56 | install_reqs = parse_requirements('requirements.txt') 57 | reqs = install_reqs 58 | 59 | setup( 60 | name='rpki-as0-bogons', 61 | version=version, 62 | maintainer="Massimiliano Stucchi", 63 | maintainer_email='max@stucchi.ch', 64 | url='https://github.com/stucchimax/rpki-as0-bogons', 65 | description='RPKI AS0 Slurm file generator for bogons', 66 | long_description=README, 67 | long_description_content_type="text/markdown", 68 | license='BSD', 69 | keywords='rpki prefix routing networking', 70 | setup_requires=reqs, 71 | install_requires=reqs, 72 | classifiers=[ 73 | 'Intended Audience :: Developers', 74 | 'Topic :: Software Development :: Libraries :: Python Modules', 75 | 'Topic :: System :: Networking', 76 | 'License :: OSI Approved :: BSD License', 77 | 'Programming Language :: Python :: 3 :: Only' 78 | ], 79 | packages=find_packages(exclude=['tests', 'tests.*']), 80 | entry_points={'console_scripts': 81 | ['rpki-as0-bogons = rpki_as0_bogons.slurm:main']}, 82 | ) 83 | --------------------------------------------------------------------------------