Basic Python Flask web app for enhancing traceroute output with data from IPInfo Lite like country and ASN.
import re
import requests
from flask import Flask, request, render_template_string
import urllib.parse
import os
app = Flask(__name__)
# Hardcoded IPINFO Lite API Token
API_TOKEN = "TOKEN" # <<<--- PLEASE REPLACE WITH YOUR ACTUAL TOKEN
# The HTML template for the main page and the results.
# It uses Tailwind CSS from a CDN for styling.
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Traceroute Enhancer</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f3f4f6;
min-height: 100vh;
}
.mono-pre {
font-family: monospace;
white-space: pre-wrap;
word-wrap: break-word;
}
</style>
</head>
<body class="p-8 flex items-center justify-center bg-gray-100 min-h-screen">
<div class="container mx-auto max-w-4xl bg-white p-8 rounded-xl shadow-lg border border-gray-200">
<div class="text-center mb-10">
<h1 class="text-4xl font-extrabold text-gray-800 mb-2">Traceroute Enhancer</h1>
<p class="text-gray-500 font-medium">Paste your traceroute output to see the path enhanced.</p>
</div>
<!-- Input Form -->
<form action="/visualize" method="post" class="space-y-6 mb-10">
<div>
<label for="traceroute_output" class="block text-sm font-medium text-gray-700 mb-2">Traceroute Output</label>
<textarea id="traceroute_output" name="traceroute_output" rows="10" placeholder="Paste your traceroute output here..." required class="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500 transition-colors"></textarea>
</div>
<div class="flex items-center justify-center">
<button type="submit" class="w-full sm:w-auto px-6 py-3 bg-indigo-600 text-white font-bold rounded-full shadow-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-all transform hover:scale-105">
Enhance
</button>
</div>
</form>
<!-- Visualization Results -->
<div id="results">
<div class="mono-pre bg-gray-50 p-4 rounded-lg border border-gray-200 text-sm text-gray-800 overflow-x-auto">{{ results | safe }}</div>
</div>
</div>
</body>
</html>
"""
def parse_traceroute(output):
"""
Parses traceroute output to extract unique IP addresses and their corresponding lines.
This function is updated to return a list of tuples (line, ip) to preserve
the original line formatting for later replacement.
"""
ips_and_lines = []
# Regex to find an IP address. This is more flexible than the original.
ip_pattern = re.compile(r'\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b')
lines = output.strip().split('\n')
for line in lines:
match = ip_pattern.search(line)
if match:
ip = match.group(1)
# Exclude private, loopback, and broadcast IPs
if not ip.startswith(('10.', '172.16', '192.168', '127.', '0.', '255.')):
# Only add unique IPs to the list
is_duplicate = any(ip == item[1] for item in ips_and_lines)
if not is_duplicate:
ips_and_lines.append((line, ip))
return ips_and_lines
def get_ip_info(ip, token):
"""
Fetches IP information from the IPINFO Lite API.
"""
url = f"https://api.ipinfo.io/lite/{ip}?token={token}"
try:
response = requests.get(url, timeout=5)
response.raise_for_status() # Raise an error for bad status codes
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching data for IP {ip}: {e}")
return None
def generate_formatted_output(original_output, ip_data_map):
"""
Generates the new, simplified traceroute output.
"""
output_lines = []
ip_pattern = re.compile(r'\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b')
for line in original_output.strip().split('\n'):
match = ip_pattern.search(line)
if match:
ip = match.group(1)
info = ip_data_map.get(ip)
if info:
country_code = info.get("country_code")
as_number = info.get("asn", "N/A")
as_name = info.get("as_name", "N/A")
# Create a hyperlink for the AS number
if as_number != "N/A":
as_link = f"<a href='https://ipinfo.io/{as_number}' class='text-blue-600 hover:underline' target='_blank'>{as_number}</a>"
else:
as_link = as_number
as_info = f"{as_link} {as_name}"
flag_emoji = ""
if country_code:
flag_emoji = ''.join([chr(0x1F1E6 + (ord(c) - ord('A'))) for c in country_code.upper()])
# Replace the hop number and IP with the new formatted string
# Find the start of the hop number to replace it.
new_line = line
# Regex to find hop number and IP to replace it.
hop_ip_pattern = re.compile(r'^\s*(\d+)\s+.*?(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)')
hop_ip_match = hop_ip_pattern.search(line)
if hop_ip_match:
hop_num = hop_ip_match.group(1)
original_ip = hop_ip_match.group(2)
# Create the replacement string with emoji and AS info
replacement_ip = f"{original_ip} [{as_info}]"
# Replace the first occurrence of the original IP in the line
new_line = new_line.replace(original_ip, replacement_ip, 1)
# Insert the flag emoji after the hop number
new_line = new_line.replace(f" {hop_num} ", f" {hop_num} {flag_emoji} ", 1)
output_lines.append(new_line)
else:
output_lines.append(line)
else:
output_lines.append(line)
return "\n".join(output_lines)
@app.route("/", methods=["GET"])
def index():
"""
Renders the initial form page.
"""
return render_template_string(HTML_TEMPLATE, results="")
@app.route("/visualize", methods=["POST"])
def visualize():
"""
Handles the form submission, processes the traceroute,
and returns the visualization.
"""
traceroute_output = request.form.get("traceroute_output", "")
# 1. Parse the traceroute output to get IPs and their corresponding lines
ips_and_lines = parse_traceroute(traceroute_output)
# 2. Fetch IP information and store in a map for quick lookup
ip_data_map = {}
for line, ip in ips_and_lines:
info = get_ip_info(ip, API_TOKEN)
if info:
ip_data_map[ip] = info
# 3. Generate the new formatted output
formatted_output = generate_formatted_output(traceroute_output, ip_data_map)
return render_template_string(HTML_TEMPLATE, results=formatted_output)
if __name__ == "__main__":
app.run(debug=True)