0

I'm trying to implement a multi-ping code in python, but the problem is that in the following code both the sockets I defined seems to receive the SAME PACKET from the SAME DESTINATION! Why does it happen? O.o

------------------- UPDATE -------------------

I noticed that the received packet is always the "most fast" one [that is the one with the smaller RTT]! For example:

dests_list = ['173.194.41.70','213.92.16.101'] -> DEST: 173.194.41.70
dests_list = ['213.92.16.101','173.194.41.70'] -> DEST: 173.194.41.70
dests_list = ['213.92.16.191','95.141.47.7'] -> DEST: 213.92.16.191

with:

RTT_213.92.16.191=20ms  RTT_173.194.41.70=8ms  RTT_95.141.47.7=28ms

------------------- UPDATE -------------------

CODE --> [at the end of the post there's a downloadable version to avoid formatting issues]

#!/usr/bin/python
# -*- coding: utf-8 -*-

from exceptions import Exception
import random
import select
import socket
import struct
import sys
import time

ICMP_ECHO_REQUEST = 8  # Seems to be the same on Solaris.
PACKET_SIZE = 32
IS_WIN = False
my_socket_list = []
dests_list = ['173.194.41.70','213.92.16.101']
N_DESTS = 2
seq_num = 1

#----------------------------------------------------------------------------------------#
#----> CHECKSUM: calculate checksum
#----------------------------------------------------------------------------------------#

def checksum(source_string):
    """
    I'm not too confident that this is right but testing seems
    to suggest that it gives the same answers as in_cksum in ping.c
    """

    sum = 0
    countTo = len(source_string) / 2 * 2
    count = 0
    while count < countTo:
        thisVal = ord(source_string[count + 1]) * 256 + ord(source_string[count])
        sum = sum + thisVal
        sum = sum & 0xffffffff  # Necessary?
        count = count + 2

    if countTo < len(source_string):
        sum = sum + ord(source_string[len(source_string) - 1])
        sum = sum & 0xffffffff  # Necessary?

    sum = (sum >> 16) + (sum & 65535)
    sum = sum + (sum >> 16)
    answer = ~sum
    answer = answer & 65535

    # Swap bytes. Bugger me if I know why.

    answer = answer >> 8 | answer << 8 & 0xff00

    return answer


#----------------------------------------------------------------------------------------#
#----> RECEIVE PINGS: polling on all open sockets
#----------------------------------------------------------------------------------------#

def receive_pings():
    """
    Receive the ping from the socket.
    """

    whatReady = select.select(my_socket_list, [], [], 1000)

        if whatReady[0] != []:
        print whatReady[0]
        for skt in whatReady[0]:

            # time evaluation 
            if IS_WIN:
                timeReceived = time.clock()
            else:
                timeReceived = time.time()

            # get datas
            (recPacket, addr) = skt.recvfrom(PACKET_SIZE + 64)
            icmpHeader = recPacket[20:28]
            (type, code, checksum, packetID, sequence) = struct.unpack('bbHHh', icmpHeader)
            print addr
            if type == 0:
            bytesInDouble = struct.calcsize('d')
            timeSent = struct.unpack('d', recPacket[28:28 + bytesInDouble])[0]
            print "DEST: %s - RTT: %s"%(addr[0],str(timeReceived-timeSent))
           elif type == 3:
            codes = {
                        0: 'Net Unreachable',
                    1: 'Host Unreachable',
                    2: 'Protocol Unreachable',
                3: 'Port Unreachable',
                }
            raise Exception(codes[code])
            break


#----------------------------------------------------------------------------------------#
#----> SEND PING
#----------------------------------------------------------------------------------------#

def send_one_ping(my_socket, dest_addr, ID):

    # Header is type (8bit), code (8bit), checksum (16bit), id (16bit), sequence (16bit)

    my_checksum = 0

    # Make a dummy heder with a 0 checksum.

    header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, my_checksum, ID, 1)
    bytesInDouble = struct.calcsize('d')
    data = (PACKET_SIZE - len(header) - bytesInDouble) * 'x'

    if IS_WIN:
        start = time.clock()
    else:
        start = time.time()

    data = struct.pack('d', start) + data

    # Calculate the checksum on the data and the dummy header.

    my_checksum = checksum(header + data)

    # Now that we have the right checksum, we put that in. It's just easier
    # to make up a new header than to stuff it into the dummy.

    header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), ID, 1)
    packet = header + data
    print my_socket 
    while packet:
        sent = my_socket.sendto(packet, (dest_addr, 1))  # Don't know about the 1
        packet = packet[sent:]      
        print "PING SENT TO %s"%dest_addr 


#----------------------------------------------------------------------------------------#
#----> DO ONE: socket creation and ping sending
#----------------------------------------------------------------------------------------#

def send_pings():

    i = 1
    for skt in my_socket_list:
        print i
        send_one_ping(skt, dests_list[i-1], i)
        i+=1

    seq_num=+1


#----------------------------------------------------------------------------------------#
#----> SCHEDULE PING: open socket towards a specific destination
#----------------------------------------------------------------------------------------#

def sockets_opening(Ndests):
    try:

        if sys.platform[0:-2] == 'win':
                IS_WIN = True
        else:
                IS_WIN = False

        for i in range(0,Ndests):
            icmp = socket.getprotobyname('icmp')
            my_socket_list.append(socket.socket(socket.AF_INET, socket.SOCK_RAW,icmp))
            print "O2: %s"%my_socket_list[i]

    except socket.error, (errno, msg):
        if errno == 1:      # operation not permitted
            msg = msg + ' - Note that ICMP messages can only be sent from processes running as root.'
                raise socket.error(msg)
            raise   # raise the original error

def sockets_closing():
    try:
        for skt in my_socket_list:
            print "C2: %s"%skt      
            skt.close()
    except socket.error, (errno, msg):
        print "ERR:%d -> %s"%(errno,msg)        
        raise

#----------------------------------------------------------------------------------------#
#----> MAIN
#----------------------------------------------------------------------------------------#

if __name__ == '__main__':
    print
    sockets_opening(N_DESTS)
    send_pings()
    #time.sleep(3)
    receive_pings()
    sockets_closing()

OUTPUT-->

CREATED-SOCKETS:[<socket._socketobject object at 0xb7267df4>, <socket._socketobject object at 0xb7267e2c>]

PING SENT TO 173.194.41.70
PING SENT TO 213.92.16.101

READY-SOCKETS:[<socket._socketobject object at 0xb7267df4>, <socket._socketobject object at 0xb7267e2c>]
DEST: 173.194.41.70 - RTT: 0.00797414779663
DEST: 173.194.41.70 - RTT: 0.00811314582825

C2: <socket._socketobject object at 0xb7267df4>
C2: <socket._socketobject object at 0xb7267e2c>

CODE LINK: PingSKT.py

NotoAnonimo
  • 151
  • 2
  • 7
  • I'm confused. I don't recognize `select.poll(my_socket_list, [], [], 1000)`. Shouldn't that be `select.select(...)`? If so, how does your code even run? – Jon-Eric Feb 13 '13 at 16:13
  • There's this instruction: `whatReady = select.select(my_socket_list, [], [], 1000)` – NotoAnonimo Feb 13 '13 at 17:08
  • Thank you for editing the code. The functions help! The indentation of the new version looks incorrect (which matters in Python), specifically the loop body in `receive_pings()`. Since I can't distinguish between indentation errors in your actual code and errors introduced in the reproduction, could you please edit your question so the indentation matches exactly what you are running? (Perhaps replace tabs with spaces.) – Jon-Eric Feb 13 '13 at 17:41
  • I added the full code at the end of the post [it was easier! ;P] Enjoy! ^_^ – NotoAnonimo Feb 15 '13 at 10:16

1 Answers1

0

That's how raw sockets work.

From TCP/IP Raw Sockets (Windows) (emphasis mine):

Received datagrams are copied into all SOCK_RAW sockets that satisfy the following conditions:

  • The protocol number specified in the protocol parameter when the socket was created should match the protocol number in the IP header of the received datagram.

  • If a local IP address is defined for the socket, it should correspond to the destination address as specified in the IP header of the received datagram. An application may specify the local IP address by calling the bind function. If no local IP address is specified for the socket, the datagrams are copied into the socket regardless of the destination IP address in the IP header of the received datagram.

  • If a foreign address is defined for the socket, it should correspond to the source address as specified in the IP header of the received datagram. An application may specify the foreign IP address by calling the connect or WSAConnect function. If no foreign IP address is specified for the socket, the datagrams are copied into the socket regardless of the source IP address in the IP header of the received datagram.

It is important to understand that some sockets of type SOCK_RAW may receive many unexpected datagrams. For example, a PING program may create a socket of type SOCK_RAW to send ICMP echo requests and receive responses. While the application is expecting ICMP echo responses, all other ICMP messages (such as ICMP HOST_UNREACHABLE) may also be delivered to this application. Moreover, if several SOCK_RAW sockets are open on a computer at the same time, the same datagrams may be delivered to all the open sockets. An application must have a mechanism to recognize the datagrams of interest and to ignore all others. For a PING program, such a mechanism might include inspecting the received IP header for unique identifiers in the ICMP header (the application's process ID, for example).

That page seems to imply that calling connect on a socket will cause it to receive datagrams from only the connected address, however calling connect on a raw socket failed for me (on OS X) with [Errno 56] Socket is already connected.

So it looks like you'll have to sort out the replies manually.

Jon-Eric
  • 16,977
  • 9
  • 65
  • 97