2

I'm trying to make traceroute implementation in python 3.5. On linux this code works fine. On Windows 10 I have infinity waiting of ICMP packet. Wireshark see ICMP packet in Windows.

import socket

# 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)
sender.setsockopt(socket.SOL_IP, socket.IP_TTL, 1)

server_address = (socket.gethostbyname('google.com'), 33434)

try:
    # Send data
    receiver.bind(("", 33434))
    sent = sender.sendto(b"", server_address)
    data, server = receiver.recvfrom(512)
    print('received "%s"\n"%s"' % (data, server[0]))
finally:
    print('closing socket')
    sender.close()
    receiver.close()
Kutase Chitsu
  • 45
  • 1
  • 6

1 Answers1

1

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.

metatoaster
  • 17,419
  • 5
  • 55
  • 66