Python socket.send on localhost alternates success and failure

57 views Asked by At

I am experiencing some weird behavior when calling socket.send(), which alternatively succeeds and fails with a "connection refused" error:

import socket, time

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
dest = ("127.0.0.1", 1111)
sock.connect(dest)

counter = 1
while True:
    try:
        sent = sock.send(b"message\n")
        print("counter {}, sent {}".format(counter, sent))
    except Exception as e:
        print("counter {}, exception {}".format(counter, e))
    finally:
        counter += 1
    
    time.sleep(0.5)

Which outputs:

counter 1, sent 8
counter 2, exception [Errno 111] Connection refused
counter 3, sent 8
counter 4, exception [Errno 111] Connection refused
counter 5, sent 8
counter 6, exception [Errno 111] Connection refused
counter 7, sent 8
counter 8, exception [Errno 111] Connection refused
counter 9, sent 8
...

I expected the send to always succeed.

  • I want the code to run regardless of whether there is another process listening on that port or not (if there is one, e.g. Netcat nc -u -l 1111, send() in my example always succeeds).

  • the intermittent failure made me think of a buffering issue, but changing the buffersize (setsockopt) did not affect anything (but the kernel imposes a minimum buffer size, so one cannot really set it to 0). Furthermore:

  • Using sendto() instead of send() (without the initial connect()) works as expected, i.e., sending never fails regardless of the listener being active or not.

  • Using a plain socket in C also works as expected, with both send() and sendto().

I am totally puzzled, what is happening here?

I could not find any documentation about this behavior in the python docs, nor on the C manual pages, nor searching the web. I am sorry if I am missing something obvious.

Python 3.10.12 on Linux (Ubuntu)

1

There are 1 answers

1
Steffen Ullrich On
counter 1, sent 8

The first send just puts the data into the socket buffer. This will succeed, so no error will be returned.

The OS then will try to deliver the data in the socket buffer to the target. If there is no socket in the target or some firewall is blocking access, then the target mit generate an ICMP messsage "Destination unreachable". This ICMP message will result in an error stored for the socket.

counter 2, exception [Errno 111] Connection refused

This error will be delivered to the application on the next system call related to the socket, i.e. the next send in your application. That's why this next send will return with an error. Because of the error on the socket the send will also not put the data in the socket buffer, so no data will be delivered to the target.

counter 3, sent 8

Returning the error clears the error on the socket. That's why the next send on the socket can succeed again without error.

The data will again be put in the socket buffer, the OS will try to deliver it which again will result in an ICMP unreachable which again will be delivered as error in the next send and so on ....

counter 4, exception [Errno 111] Connection refused
counter 5, sent 8
counter 6, exception [Errno 111] Connection refused
...