Created
October 13, 2025 06:32
-
-
Save dmarzzz/e3acfeb5a73fcb0f44ae38d8ecd04dab to your computer and use it in GitHub Desktop.
Traceroute AS parsing
This file contains hidden or 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
| # Run "traceroute -n google.com | tee traceroute_output.txt" in your terminal | |
| # Paste your traceroute/tracert output between the triple quotes and run this cell. | |
| TR_TEXT = """ | |
| <PASTE YOUR TRACEROUTE OUTPUT HERE> | |
| """ | |
| import re, requests, ipaddress, sys | |
| def extract_hops(text: str): | |
| """ | |
| Works for macOS/Linux `traceroute` and Windows `tracert`. | |
| Returns an ordered list of hop IPs (one per hop), skipping '*' lines. | |
| """ | |
| hops = [] | |
| for line in text.splitlines(): | |
| line = line.strip() | |
| if not line: | |
| continue | |
| # Grab the first IPv4 address on the line (good proxy for the hop's responding router) | |
| m = re.search(r'(\d{1,3}(?:\.\d{1,3}){3})', line) | |
| if not m: | |
| continue | |
| ip = m.group(1) | |
| # Skip placeholder '*' hops | |
| if ip == '*': | |
| continue | |
| # Keep order by hop (don't dedupe globally yet; traceroute already has 1 IP per hop line typically) | |
| hops.append(ip) | |
| # Some traceroute variants may echo the same IP on multiple adjacent lines; collapse consecutive duplicates. | |
| deduped = [] | |
| for ip in hops: | |
| if not deduped or deduped[-1] != ip: | |
| deduped.append(ip) | |
| return deduped | |
| def ip_class(ip): | |
| try: | |
| addr = ipaddress.ip_address(ip) | |
| if addr.is_private: return "Private" | |
| if addr.is_loopback: return "Loopback" | |
| if addr.is_link_local: return "Link-local" | |
| if addr.is_reserved: return "Reserved" | |
| if addr.is_multicast: return "Multicast" | |
| if ip.startswith("100.64.") or ip.startswith("100.65.") or ip.startswith("100."): # CGNAT (approx) | |
| # ipaddress has .is_private True for CGNAT; keep label explicit for readability: | |
| return "CGNAT/Private" | |
| return "Public" | |
| except ValueError: | |
| return "Unknown" | |
| def ripe_asn_lookup(ip): | |
| """ | |
| Query RIPEstat for announcing ASN and prefix for an IP. | |
| Returns dict with asn, holder, prefix, rir (or minimal info if not found). | |
| """ | |
| try: | |
| r = requests.get( | |
| "https://stat.ripe.net/data/prefix-overview/data.json", | |
| params={"resource": ip}, | |
| timeout=10, | |
| ) | |
| r.raise_for_status() | |
| d = r.json().get("data", {}) | |
| asns = d.get("asns", []) or [] | |
| if asns: | |
| a = asns[0] | |
| return { | |
| "asn": f"AS{a.get('asn')}", | |
| "holder": a.get("holder") or "", | |
| "prefix": d.get("prefix") or "", | |
| "rir": d.get("rir") or "", | |
| } | |
| # Fallback: still return prefix if present | |
| return { | |
| "asn": "", | |
| "holder": "", | |
| "prefix": d.get("prefix") or "", | |
| "rir": d.get("rir") or "", | |
| } | |
| except Exception: | |
| return {"asn": "", "holder": "", "prefix": "", "rir": ""} | |
| # Process | |
| hops = extract_hops(TR_TEXT) | |
| # Build rows with ASN mapping (cache lookups) | |
| cache = {} | |
| rows = [] | |
| for i, ip in enumerate(hops, start=1): | |
| kind = ip_class(ip) | |
| if kind != "Public": | |
| rows.append({"hop": i, "ip": ip, "type": kind, "asn": "", "holder": "", "prefix": "", "rir": ""}) | |
| continue | |
| if ip not in cache: | |
| cache[ip] = ripe_asn_lookup(ip) | |
| info = cache[ip] | |
| rows.append({ | |
| "hop": i, "ip": ip, "type": kind, | |
| "asn": info.get("asn",""), "holder": info.get("holder",""), | |
| "prefix": info.get("prefix",""), "rir": info.get("rir","") | |
| }) | |
| # Pretty print as Markdown table | |
| def to_markdown_table(rows): | |
| headers = ["Hop", "IP", "Type", "ASN", "Holder", "Prefix", "RIR"] | |
| out = ["| " + " | ".join(headers) + " |", "|" + "|".join(["---"]*len(headers)) + "|"] | |
| for r in rows: | |
| out.append("| " + " | ".join(str(r.get(k.lower(), r.get(k, ""))) for k in ["hop","ip","type","asn","holder","prefix","rir"]) + " |") | |
| return "\n".join(out) | |
| md = to_markdown_table(rows) | |
| print(md) | |
| # If pandas is available, also show a nice table | |
| try: | |
| import pandas as pd | |
| df = pd.DataFrame(rows, columns=["hop","ip","type","asn","holder","prefix","rir"]) | |
| from IPython.display import display | |
| display(df) | |
| except Exception as e: | |
| print("\n(pandas not available or display failed; Markdown table printed above.)") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment