Of course! Creating a Python traceroute is a fantastic project that teaches you about network protocols, sockets, and ICMP. I'll guide you through it, starting with a simple version and then showing a more robust, cross-platform one using a popular library.

Understanding How Traceroute Works
At its core, traceroute works by exploiting a feature of the IP protocol: Time-To-Live (TTL).
- TTL: Every IP packet has a TTL field. It's a counter that starts at a certain value (e.g., 64) and is decremented by one by each router it passes through. When the TTL reaches zero, the router discards the packet and sends an ICMP "Time Exceeded" message back to the source.
- The Process:
- To find the first router, you send a packet with a TTL of 1. The first router will decrement it to 0, discard it, and send back an "Time Exceeded" message. You now know the first hop.
- To find the second router, you send a packet with a TTL of 2. The first router will decrement it to 1 and forward it. The second router will decrement it to 0, discard it, and send back the "Time Exceeded" message. You now know the second hop.
- This process repeats, incrementing the TTL by 1 each time, until you either reach the destination or the destination explicitly responds (which happens with a TTL higher than what's needed to reach it).
Method 1: The "From Scratch" Python Traceroute (Linux/macOS)
This version is great for understanding the fundamentals. It uses raw sockets to build and send IP/UDP packets and listen for ICMP responses.
⚠️ Important: This code will only work on Linux or macOS because it requires creating a raw socket, which is restricted on Windows. You'll likely need to run it with sudo permissions.
import socket
import sys
import os
import time
import select
from typing import List, Tuple
# The destination port. We use a port that is likely not open to force
# an ICMP "Port Unreachable" message from the final destination.
DEST_PORT = 33434
def traceroute(hostname: str, max_hops: int = 30, timeout: float = 2.0) -> None:
"""
Performs a traceroute to a given hostname.
"""
try:
# Resolve the hostname to an IP address
dest_addr = socket.gethostbyname(hostname)
except socket.gaierror:
print(f"Error: Unable to resolve hostname '{hostname}'")
return
print(f"Tracing route to {hostname} [{dest_addr}] with max {max_hops} hops:\n")
# Create a raw socket for sending and receiving
# We use IPPROTO_ICMP to receive ICMP messages
# We use IPPROTO_UDP to send UDP packets
try:
recv_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
except PermissionError:
print("Error: This script requires root/administrator privileges to create raw sockets.")
return
except Exception as e:
print(f"Error creating sockets: {e}")
return
recv_socket.bind(("", 0)) # Bind to any available port
recv_socket.settimeout(timeout)
for ttl in range(1, max_hops + 1):
send_socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)
# Create a new socket pair for each TTL to avoid issues with port reuse
# This is a simplified approach; a more robust solution would use a different source port
# or handle port reuse more carefully.
# For this example, we'll just send and receive.
start_time = time.time()
try:
# Send a UDP packet to the destination
send_socket.sendto(b"", (dest_addr, DEST_PORT))
except Exception as e:
print(f"Error sending packet for TTL {ttl}: {e}")
break
current_hop = None
rtt = None
done = False
# Wait for a response with a timeout
while not done:
ready = select.select([recv_socket], [], [], timeout)
if ready[0]:
try:
packet, addr = recv_socket.recvfrom(512)
current_hop = addr[0]
end_time = time.time()
rtt = (end_time - start_time) * 1000 # in milliseconds
done = True
except socket.timeout:
print(f" {ttl}. * * * Request timed out.")
break
except Exception as e:
print(f" {ttl}. * * * Error receiving: {e}")
break
else:
print(f" {ttl}. * * * Request timed out.")
break
if current_hop:
print(f" {ttl}. {current_hop} {rtt:.1f} ms")
# If we reached the destination, we can stop
if current_hop == dest_addr:
print("\nTrace complete.")
break
recv_socket.close()
send_socket.close()
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 traceroute.py <hostname>")
sys.exit(1)
target = sys.argv[1]
traceroute(target)
How to Run It:
- Save the code as
traceroute.py. - Open your terminal.
- Run it with
sudo:sudo python3 traceroute.py google.com
Method 2: The Cross-Platform & Robust Way (Using a Library)
Writing a robust traceroute is complex. Handling different operating systems, packet types (UDP, TCP, ICMP), and edge cases is a lot of work. The best approach for real-world applications is to use a well-tested library.

The pythontraceroute library is a great choice. It's cross-platform and handles the complexities for you.
Step 1: Install the Library
pip install pythontraceroute
Step 2: Use the Library
The library's API is very simple and clean.
from pythontraceroute import Traceroute
import time
def run_traceroute(hostname: str):
"""
Performs a traceroute using the pythontraceroute library.
"""
print(f"--- Tracing route to {hostname} ---")
start_time = time.time()
# The Traceroute object handles all the complexity
tracer = Traceroute(hostname)
# The run() method executes the traceroute and returns a list of hops
hops = tracer.run()
end_time = time.time()
total_time = end_time - start_time
print(f"\nTraceroute completed in {total_time:.2f} seconds.\n")
for i, hop in enumerate(hops):
# hop is a dictionary with 'ip', 'hostname', 'rtt1', 'rtt2', 'rtt3'
ip = hop.get('ip', '* * *')
hostname = hop.get('hostname', '* * *')
# Print the hop number and IP/hostname
print(f"{i + 1}. {ip} ({hostname})")
# Print the round-trip times
for j in range(1, 4):
rtt = hop.get(f'rtt{j}')
if rtt:
print(f" {rtt:.3f} ms")
else:
print(f" * * *")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 traceroute_lib.py <hostname>")
sys.exit(1)
target = sys.argv[1]
run_traceroute(target)
How to Run It:
- Make sure you have the library installed (
pip install pythontraceroute). - Save the code as
traceroute_lib.py. - Run it from your terminal:
python3 traceroute_lib.py google.com
Comparison of Methods
| Feature | Method 1 (From Scratch) | Method 2 (Library) |
|---|---|---|
| Dependencies | None (built-in socket) |
pip install pythontraceroute |
| Platform | Linux/macOS only | Cross-platform (Windows, Linux, macOS) |
| Permissions | Requires sudo |
Does not require sudo |
| Robustness | Low. Handles basic cases. May fail on complex networks or OSes. | High. Handles packet loss, multiple probes, OS differences. |
| Code Complexity | High. You manage sockets, TTL, ICMP, and timeouts. | Low. A few lines of code to get a full-featured traceroute. |
| Learning Value | Excellent. Teaches the core networking concepts. | Low. Hides the complexity, good for quick results. |
Recommendation
- For learning and understanding: Use Method 1. It's a fantastic exercise that demystifies what
tracerouteandtracertare doing under the hood. - For a real application or script: Use Method 2. It's reliable, portable, and saves you from reinventing the wheel and dealing with platform-specific bugs.

