7
votes

I am trying to create a raw socket in Python that listens for UDP packets only:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
s.bind(('0.0.0.0', 1337))
while True:
    print s.recvfrom(65535)

This needs to be run as root, and creates a raw socket on port 1337, which listens for UDP packets and prints them whenever they are received; no problems there.

Now let's make a little client to test if this works:

import socket
c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
c.connect(('127.0.0.1', 1337))
c.send('message 1')
c.send('message 2')
c.send('message 3')
c.send('message 4')
c.send('message 5')
c.send('message 6')

Consistently, only the first, third, and fifth message (message 1, message 3 and message 5) will get through and be printed in the server output. The second, fourth, and sixth messages do not show up on the server output, and instead the client gets an exception:

>>> c.send('message 2')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
socket.error: [Errno 111] Connection refused

Running this in Wireshark shows that it is getting an ICMP reply for "Destination unreachable". I have been able to reproduce this on 3 distinct machines (all running Linux though). Am I missing something? Is this expected behaviour for UDP to consistently drop packets, since protocols using it are supposed to be tolerant of packet loss? Even so, why would packets be dropped when sent on the local interface?

Binding the server to 127.0.0.1 instead of 0.0.0.0 has the same result.

1
Sometimes strange things happen with the message stack. The ethernet adapter in this case uses loopback, but still, the packets "phsically" still go on the card and then are interpreted by the driver as to how to order the message queue. just some research points to help out.FlavorScape
@Etienne, FlavorScape I get the error every other packet, which seems far too regular to be a network error...brice
Yes, it happens every two packets, meaning that if you send 2*n packets, all packets of the form 2*i+1 will not get through. I don't think this can be packet loss, since this is all on the local interface.Etienne Perot
I suspect it's because you're using RAW <-> DGRAM or something similar.brice
Confirmed. If you use a socket.SOCK_DGRAM server-side, it works as expected.brice

1 Answers

11
votes

Solved it in kind of a silly manner; please let me know if there is another way, and I will change the accepted answer.

The solution is simply to use two sockets bound on the same port; one raw, one not raw:

import socket, select
s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s1.bind(('0.0.0.0', 1337))
s2 = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
s2.bind(('0.0.0.0', 1337))
while True:
    r, w, x = select.select([s1, s2], [], [])
    for i in r:
        print i, i.recvfrom(131072)

This makes the "Destination unreachable" ICMP packets go away and makes all packets go through fine. I think the operating system wants a non-raw socket listening on the port for things to go well, and then any raw sockets listening on that same port will receive copies of the packets.