Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
190 lines (167 sloc)
7.3 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from flask import Flask, request, render_template | |
import ipaddress | |
import re | |
import requests | |
import json | |
import os | |
# point to templates directory relative to home directory | |
template_dir = template_dir = os.path.join(os.path.dirname(__file__), 'templates') | |
app = Flask(__name__, template_folder=template_dir) | |
def get_asn_from_as(asn): | |
# Remove the "AS" from the beginning of the ASN | |
if asn.startswith("AS") or asn.startswith("as"): | |
asn = asn[2:] | |
asn = int(asn) | |
return asn | |
def get_more_specifics(data): | |
""" | |
:param data: json data from RIPEstat API | |
:return: list of more specific prefixes and their origins | |
""" | |
prefixes = [] | |
origins = [] | |
for prefix_origin in data["data"]["more_specifics"]: | |
prefixes.append(prefix_origin['prefix']) | |
origins.append(prefix_origin['origin']) | |
return [prefixes, origins] | |
def get_prefix_roa_status(prefix, origin): | |
""" | |
:param prefix: prefix to check | |
:param origin: origin ASN to check | |
:return: ROV status of the prefix based on RIPEstat ROA data | |
""" | |
url = f"https://stat.ripe.net/data/rpki-validation/data.json?resource={origin}&prefix={prefix}" | |
response = requests.get(url) | |
data = json.loads(response.text) | |
data_str = json.dumps(data, indent=4) | |
# print(data_str) | |
try: | |
validation_status = data["data"]["status"] | |
except KeyError: | |
return None | |
return(validation_status) | |
def get_prefix_info(prefix): | |
""" | |
:param prefix: covering prefix to check for more specific prefixes | |
:return: list of more specific prefixes and their origins | |
""" | |
url = "https://stat.ripe.net/data/routing-status/data.json?resource=" + prefix | |
try: | |
response = requests.get(url) | |
except requests.exceptions.RequestException: | |
return None | |
data = json.loads(response.text) | |
try: | |
if "prefix" in data["data"]["last_seen"]: | |
seen_origin = data["data"]["last_seen"]["origin"] | |
more_specifics, more_specific_origins = get_more_specifics(data) | |
return [prefix, seen_origin, more_specifics, more_specific_origins] | |
except KeyError: | |
return None | |
def return_rov_status(roa_prefix, roa_maxlen, roa_asn, prefix, origin_asn): | |
""" given a prefix and an origin ASN, as well as a proposed ROA (prefix, maxlen, asn), | |
determine if the ROA covers the prefix and origin ASN | |
:param roa_prefix: prefix from the ROA | |
:param roa_maxlen: maximum length of the prefix from the ROA | |
:param roa_asn: ASN from the ROA | |
:param prefix: prefix from the routing table | |
:param origin_asn: origin ASN from the routing table | |
:return: ROV status of the prefix""" | |
roa_ip_prefix = ipaddress.ip_network(roa_prefix) | |
ip_prefix = ipaddress.ip_network(prefix) | |
if not ip_prefix.subnet_of(roa_ip_prefix): | |
return "error: prefix not covered by ROA" | |
if ip_prefix.subnet_of(roa_ip_prefix) and int(roa_maxlen) >= int(ip_prefix.prefixlen) and int(roa_asn) == int(origin_asn): | |
return "valid" | |
else: | |
return "invalid" | |
def is_valid_prefix(prefix): | |
# Check if the prefix is a valid IPv4 or IPv6 prefix | |
try: | |
# Try to parse the prefix as an IPv4 or IPv6 prefix | |
ip_network = ipaddress.ip_network(prefix) | |
return True | |
except ValueError: | |
return False | |
def is_valid_asn(asn): | |
# Check if the ASN is a string starting with "AS", followed by numbers or just a number | |
if asn.isdigit(): | |
return True | |
if re.match(r'^[Aa][Ss]\d+$', asn): | |
return True | |
else: | |
return False | |
def is_valid_prefix_maxlength(ip_prefix, prefix_maxlength): | |
""" check to see prefix_maxlength is a valid value for the prefix | |
:param ip_prefix: prefix to check | |
:param prefix_maxlength: maximum length of the prefix | |
:return: True if prefix_maxlength is valid, False otherwise | |
""" | |
try: | |
# Try to parse the prefix maxlength as an integer | |
prefix_maxlength = int(prefix_maxlength) | |
except ValueError: | |
return False | |
# Check if the prefix maxlength is in the valid range based on the type of ip_prefix | |
if ipaddress.ip_network(ip_prefix).version == 4: | |
return 1 <= prefix_maxlength <= 32 and prefix_maxlength >= ipaddress.ip_network(ip_prefix).prefixlen | |
elif ipaddress.ip_network(ip_prefix).version == 6: | |
return 1 <= prefix_maxlength <= 128 and prefix_maxlength >= ipaddress.ip_network(ip_prefix).prefixlen | |
def check_list_of_prefixes_against_ROA(origin, prefixes, origins, roa_prefix, roa_maxlen, roa_asn): | |
""" given a list of prefixes and their origins, check if the ROA covers them | |
:param origin: origin ASN | |
:param prefixes: list of prefixes | |
:param origins: list of origins | |
:param roa_prefix: prefix from the ROA | |
:param roa_maxlen: maximum length of the prefix from the ROA | |
:param roa_asn: ASN from the ROA | |
:return: list output lines to be displayed on the web page | |
""" | |
messages = [] | |
existing_roa_status = get_prefix_roa_status(roa_prefix, origin) | |
messages.append([roa_prefix, return_rov_status(roa_prefix, roa_maxlen, roa_asn, roa_prefix, origin), origin, | |
existing_roa_status]) | |
prefix_origin = zip(prefixes, origins) | |
for prefix, origin in prefix_origin: | |
existing_roa_status = get_prefix_roa_status(prefix, origin) | |
messages.append([prefix, return_rov_status(roa_prefix, roa_maxlen, roa_asn, prefix, origin), origin, | |
existing_roa_status]) | |
return messages | |
@app.route('/results', methods=['GET']) | |
def results(): | |
""" process the form data and return the results """ | |
if request.method == 'GET': | |
roa_ip_prefix = request.args.get('ip_prefix') | |
roa_ip_prefix = roa_ip_prefix.strip() | |
origin_asn = request.args.get('origin_asn') | |
origin_asn = origin_asn.strip() | |
origin_asn = get_asn_from_as(origin_asn) | |
roa_origin_asn = origin_asn | |
roa_prefix_maxlength = request.args.get('prefix_maxlength') | |
roa_prefix_maxlength = roa_prefix_maxlength.strip() | |
# Validate the IP prefix | |
if not is_valid_prefix(roa_ip_prefix): | |
return f"\"{roa_ip_prefix}\" is an Invalid IP prefix" | |
# Validate the origin ASN | |
if not is_valid_asn(origin_asn): | |
return f"\"{origin_asn}\" is an Invalid origin ASN" | |
# Validate the prefix maxlength | |
if not is_valid_prefix_maxlength(roa_ip_prefix, roa_prefix_maxlength): | |
return f"\"{roa_prefix_maxlength}\" is an Invalid prefix maxlength" | |
roa_prefix_maxlength = int(roa_prefix_maxlength) | |
prefix_info = get_prefix_info(roa_ip_prefix) | |
if prefix_info is None: | |
return "Prefix not found or problems with RIPEstat API" | |
prefix, seen_origin, more_specifics, more_specific_origins = prefix_info | |
items = check_list_of_prefixes_against_ROA(seen_origin, more_specifics, more_specific_origins, roa_ip_prefix, | |
roa_prefix_maxlength, roa_origin_asn) | |
roa_info = [roa_ip_prefix, roa_prefix_maxlength, roa_origin_asn] | |
return render_template('render.html', items=items, roa_info=roa_info) | |
else: | |
return render_template('index.html') | |
@app.route('/', methods=['GET']) | |
def index(): | |
return render_template('index.html') | |
if __name__ == '__main__': | |
print("new version - ssw") | |
app.run(port=8000, host='0.0.0.0') |