Skip to content

Commit

Permalink
improved error reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
ssw committed Jan 6, 2023
1 parent 1bd9fbe commit 175db2f
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 3 deletions.
10 changes: 10 additions & 0 deletions docker_container_version/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.10-slim-bullseye

COPY . /app
COPY templates /app/templates
WORKDIR /app

RUN pip install flask ipaddress requests

CMD ["python", "main.py"]

158 changes: 158 additions & 0 deletions docker_container_version/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
from flask import Flask, request, render_template
import ipaddress
import re
import requests
import json
import os

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:]
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):
""" given a prefix, determine if it is covered by an existing ROA"""
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):
""" given a prefix, return the more specific prefixes and their origins as seen by RIPEstat"""
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 roa_maxlen >= ip_prefix.prefixlen and roa_asn == 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
if re.match(r'^[Aa][Ss]\d+$', asn):
return True
# Check if the ASN is just numbers
elif asn.isdigit():
return True
else:
return False

def is_valid_prefix_maxlength(ip_prefix, prefix_maxlength):
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):

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('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
# Get the user input
roa_ip_prefix = request.form['ip_prefix']
roa_ip_prefix = roa_ip_prefix.strip()
# Validate the IP prefix
if not is_valid_prefix(roa_ip_prefix):
return 'Invalid IP prefix'

origin_asn = request.form['origin_asn']
origin_asn = origin_asn.strip()
# Validate the origin ASN
if not is_valid_asn(origin_asn):
return 'Invalid origin ASN'
roa_prefix_maxlength = request.form['prefix_maxlength']
roa_prefix_maxlength = roa_prefix_maxlength.strip()
# Validate the prefix maxlength
if not is_valid_prefix_maxlength(roa_ip_prefix, roa_prefix_maxlength):
return 'Invalid prefix maxlength'

roa_ip_prefix = request.form['ip_prefix']
roa_prefix_maxlength = int(request.form['prefix_maxlength'])
roa_origin_asn = request.form['origin_asn']
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')

if __name__ == '__main__':
app.run(port=8000, host='0.0.0.0')

56 changes: 56 additions & 0 deletions docker_container_version/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!doctype html>
<html>
<head>
<title>IP Prefix Form</title>
<style>
body {
font-family: Arial, sans-serif;
}
form {
max-width: 400px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 10px;
}
label {
display: block;
margin-bottom: 8px;
}
input[type="text"],
input[type="number"] {
width: 100%;
padding: 12px 20px;
margin-bottom: 20px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
}
input[type="submit"] {
width: 100%;
background-color: #4caf50;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type="submit"]:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<p>This app queries the stat.ripe.net to determine if a RPKI-ROA created with the following information would likely agree (i.e., not evaluate as invalid) for routes currently seen in the Internet</p>
<form method="post">
<label for="ip_prefix">IP Prefix:</label>
<input type="text" name="ip_prefix" required>
<label for="prefix_maxlength">Prefix Maxlength:</label>
<input type="number" name="prefix_maxlength" required>
<label for="origin_asn">Origin ASN:</label>
<input type="text" name="origin_asn" required>
<input type="submit" value="Submit">
</form>
</body>
</html>
51 changes: 51 additions & 0 deletions docker_container_version/templates/render.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<p>Proposed ROA Prefix = {{ roa_info[0] }}</p>
<p>Max Length = {{ roa_info[1]}}</p>
<p>and origin ASN = {{roa_info[2] }}</p>
</div>

<table>
<thead>
<tr>
<th style="white-space pre-line;text-align: left">The first row is the prefix specified in the proposed ROA.
Subsequent lines list more specifics that are seen in the global routing table.</th>
<th style="white-space pre-line;text-align: left">If you create the proposed ROA, this is how it would be evaluated for each prefix.</th>
<th style="white-space pre-line;text-align: left">For this prefix, this is the origin ASN as seen in the global Internet.</th>
<th style="white-space pre-line;text-align: left">This is the status of this prefix due to an existing ROA. </th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td style="text-align: center; border: 1px solid black;">{{ item[0] }}</td>

{% if item[1] == "invalid" %}
<td style="text-align: center; border: 1px solid black; background-color:red;">{{ item[1] }}</td>
{% elif item[1] =="valid" %}
<td style="text-align: center; border: 1px solid black; background-color:green">{{ item[1] }}</td>
{% else %}
<td style="text-align: center; border: 1px solid black; background-color:yellow">{{ item[1] }}</td>
{% endif %}
<td style="text-align: center; border: 1px solid black;">{{ item[2] }}</td>
{% if item[3] == "valid" %}
<td style="text-align: center; border: 1px solid black; background-color:green">{{ item[3] }}</td>
{% elif item[3] == "invalid" %}
<td style="text-align: center; border: 1px solid black; background-color:red">{{ item[3] }}</td>
{% else %}
<td style="text-align: center; border: 1px solid black; background-color:yellow;">{{ item[3] }}</td>
{% endif %}

</tr>
{% endfor %}
</tbody>
</table>

</body>
</html>
9 changes: 6 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,21 @@ def index():
if request.method == 'POST':
# Get the user input
roa_ip_prefix = request.form['ip_prefix']
roa_ip_prefix = roa_ip_prefix.strip()
# Validate the IP prefix
if not is_valid_prefix(roa_ip_prefix):
return 'Invalid IP prefix'
return f"\"{roa_ip_prefix}\" is an Invalid IP prefix"

origin_asn = request.form['origin_asn']
origin_asn = origin_asn.strip()
# Validate the origin ASN
if not is_valid_asn(origin_asn):
return 'Invalid origin ASN'
return f"\"{origin_asn}\" is an Invalid origin ASN"
roa_prefix_maxlength = request.form['prefix_maxlength']
roa_prefix_maxlength = roa_prefix_maxlength.strip()
# Validate the prefix maxlength
if not is_valid_prefix_maxlength(roa_ip_prefix, roa_prefix_maxlength):
return 'Invalid prefix maxlength'
return f"\"{roa_prefix_maxlength}\" is an Invalid prefix maxlength"

roa_ip_prefix = request.form['ip_prefix']
roa_prefix_maxlength = int(request.form['prefix_maxlength'])
Expand Down

0 comments on commit 175db2f

Please sign in to comment.