[IPinfoHack] IPInfo Lite Traceroute Enhancer

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)

Hi,

Thank you for sharing the code. For web applications, we kindly ask participants to host a live demo (at least for the duration) of the hackathon so our community can test it out.

We also require the code to be hosted on GitHub for us to review it properly at the end of the hackathon. Thank you very much. Looking forward to your proper submission.

— Abdullah | DevRel, IPinfo