Python Traceroute
Introduction This is my first tutorial on here so I do apologise if it is poorly written. Feel free to correct me on anything I do explain wrongly. It needs to be run as root on *nix as works with raw sockets. If you do not understand any part of this, post all questions publicly so I can answer for everyone else with the same question. Of course it must be mentioned, in any real task use the proper tool, this is purely an exercise to get more comfortable using python and sockets. Perhaps even gaining better knowledge of networking as a result or hopefully peaking interest into the field. Writing this will help me learn by making sure I understand it all, please let me know if I make any mistakes.
Theory Packets get passed from your computer, to your router, then forwarded to switches and routers etc on their way to the server you wish to contact. Network administrators often need to find where there are unnecessary delays in networks or find where the packets get interrupted so that they can narrow down the search for broken links in their network. Some systems won't respond to the request as it can sometimes be used as part of a DDoS attack. In most cases it is highly unlikely to need more than 30 stops on the way so the script uses 30 'hops' as a default limit. The application that this script is a much simpler version of is traceroute on *nix or tracert on windows. to execute them simply type into a command line "traceroute google.com" or "tracert google.com".
Simplified Pseudocode:
Call imports
Create variables
Create Loop
Set variables for this iteration
Send packet
Receive response
Print next line in table
End loop if over 30 hops or address reached
Python Script Outline
#!/usr/bin/python
*Imports*
def main(dest_name):
*Socket variables*
*Timing Variables*
*Print table headers*
while True:
*Create Sockets*
*Set Timer*
*Send packet*
*Wait for Response*
*Print findings*
*End Loop*
if __name__ == "__main__":
*Take Arguments*
main(dest_name)
Add Imports There are 3 imports used in this script. The first is socket, used to create the network connections used in this script. To find if there is lag in the network, rather than just finding dead links, this script returns the time taken for each packet to find where there is undue lag. To do this the time must be imported. The final import used is sys, used to handle arguments passed to the script.
import socket
import time
import sys
Take Argument Using the sys module, you can accept arguments into your python script. Sys allows you to access variables used by the interpreter functions to interact with them. Arguments passed are stored in list 'argv'. argv[0] is the filename of the script, depending on OS it will either be the file name or the full file path. argv[1] is the first variable passed. To find the length of a list in python it is easiest to use "len(sys.argv)". If a variable has been passed to the script the length will be 2, otherwise 1.
if __name__ == "__main__":
dest_name = "google.com"
if len(sys.argv)>1:
dest_name = sys.argv[1]
main(dest_name)
Setting up Socket Sockets that we will use are recv_socket and send_socket. To create a socket you must pass the variables "Address Family", we will stick to ipv4 in this case which is "AF_INET". Then we pass a variable to state which of the 7 layers of the network you are using, for future reference layer 1 is physical. We will be using level 3 (Network Layer) with "socket.SOCK_RAW". The final variable is the protocol used which for the recv_socket is ICMP. For the send_socket we use "socket.SOCK_DGRAM" instead of raw so that we can send an UDP packet. We create the variables "curr_addr" and "curr_name". settimeout is set to 5 seconds to make sure the script doesn't get left hanging when a switch or router etc doesn't respond to the packets timing out. Sendto sends an empty packet as content is an empty string in this case.
recv_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
recv_socket.settimeout(5)
recv_socket.bind(("",port))
send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, udp)
send_socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)
curr_addr = None
curr_name = None
send_socket.sendto("",(dest_name, port))
Receive Responses This script uses a basic try-except. Try is simply listening for a response, simply jumps out with 'pass' if and exception occurs, and finally closes the sockets. I do not see any reason to explain the rest of this segment as it is quite clear that it prints to the console. Any confusion over the print function please RTFM
try:
_, curr_addr = recv_socket.recvfrom(512)
curr_addr = curr_addr[0]
except socket.error:
pass
finally:
send_socket.close()
recv_socket.close()
finish = time.time() * 1000
if curr_addr is not None:
curr_name = socket.getfqdn(curr_addr)
curr_host = "%s (%s)" % (curr_name, curr_addr)
print "%d\t%s\t%dms" % (ttl,curr_host,finish-start)
else:
curr_host = "*"
print "%d\t*No ICMP Response*" % ttl
Timing Using 'time, we take note of the start time as "start = time.time()*1000" to get the time in millisecond. Once a response has been received we use "finish = time.time()*1000" and subtract the variable start to get the time taken in milliseconds.
Summary This is the full code with some tidying. The only call I haven't explained this far is "socket.getfqdn(curr_addr)" which stands for "Fully Qualified Domain Name".
#!/usr/bin/python
import socket
import time
import sys
def main(dest_name):
dest_addr = socket.gethostbyname(dest_name)
port = 33434
icmp = socket.getprotobyname('icmp')
udp = socket.getprotobyname('udp')
ttl = 1
max_hops = 30
start = time.time()
total_time = 0
jump_time = 0
print "TTL\tHost Details\t\tTime"
while True:
recv_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
recv_socket.settimeout(5)
send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, udp)
send_socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)
recv_socket.bind(("",port))
curr_addr = None
curr_name = None
start = time.time() * 1000
finish = time.time()
send_socket.sendto("",(dest_name, port))
try:
_, curr_addr = recv_socket.recvfrom(512)
curr_addr = curr_addr[0]
except socket.error:
pass
finally:
send_socket.close()
recv_socket.close()
finish = time.time() * 1000
if curr_addr is not None:
curr_name = socket.getfqdn(curr_addr)
curr_host = "%s (%s)" % (curr_name, curr_addr)
print "%d\t%s\t%dms" % (ttl,curr_host,finish-start)
else:
curr_host = "*"
print "%d\t*No ICMP Response*" % ttl
ttl += 1
if curr_addr == dest_addr or ttl > max_hops:
break
if __name__ == "__main__":
dest_name = "google.com"
if len(sys.argv)>1:
dest_name = sys.argv[1]
main(dest_name)