On Windows, additional configuration on the socket itself is required to make this work, the fifth example on the official documentation contains the following that served as the hints for making this ping server work:
# the public network interface
HOST = socket.gethostbyname(socket.gethostname())
s.bind((HOST, 0))
# Include IP headers
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
# receive all packets
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
With that in mind, receiver.bind(("", 33434))
will not work as the hostname of the host must be passed instead of empty string, and that IP_TTL
is configured using socket.IPPROTO_IP
actually. Since implementation of something like traceroute
was mentioned as an objective in the question, a rough implementation of that (that doesn't guard against other random non-related ICMP that might come back) might look something like this:
import os
import socket
import sys
# Create a UDP and ICMP sockets
receiver = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname('ICMP'))
sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if os.name == 'nt':
# include additional configuration required for Windows
host = socket.gethostbyname(socket.gethostname())
receiver.bind((host, 0))
receiver.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
receiver.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
host = sys.argv[1] if sys.argv[1:] else 'example.com'
server_address = socket.gethostbyname(host)
print("tracing host %r ip %s" % (host, server_address))
try:
# Send data
for ttl in range(1, 11):
sender.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl)
# send UDP to traceroute port 33434
sent = sender.sendto(b"", (server_address, 33434))
data, addr = receiver.recvfrom(512)
print('received %d bytes from %r' % (len(data), addr[0]))
finally:
print('closing socket')
sender.close()
receiver.close()
Usage is simply like this (incidentally the machine that ran this was exactly 10 hops away from the example host - the range of TTL the above script will send is from 1 to 10):
$ sudo python script.py 'example.com'
tracing host 'example.com' ip 93.184.216.34
received 56 bytes from '192.168.178.1'
...
received 56 bytes from '93.184.216.34'
closing socket
Note that with this modification, socket.bind
is no longer called for Linux (as demoed above) but this will still work correctly there. Port isn't actually used for ICMP so this is left as 0
. Also, due to the usage of raw sockets, administrative privileges is typically required. Oh right, the question is above Windows, let's try that:
C:\Windows\system32>python E:\script.py example.com
tracing host 'example.com' ip 93.184.216.34
received 56 bytes from '10.0.2.2'
...
received 56 bytes from '93.184.216.34'
closing socket
Note that I had to turn the firewall off (Windows Defender) in order to be able receive the raw packets, and that cmd.exe
was opened as Administrator in order to gain usage of raw sockets.