-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
281 additions
and
3 deletions.
There are no files selected for viewing
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
| 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"] | ||
|
|
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
| 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') | ||
|
|
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
| 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> |
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
| 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> |
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