pythonpython-3.7sctp

How to use sctpsocket_udp class in pysctp library


I am attempting to set up a basic SCTP client/server example using the pysctp library. I would like to use the sctp.sctpsocket_udp class since according to the relevant doc strings:

A UDP-style SCTP socket can hold several associations via a single socket.

...and this is functionality that I need. Here is what I have so far:

server_sock = sctp.sctpsocket_udp(socket.AF_INET)
server_sock.bind((str(ip_address), port))
server_sock.listen(16)
server_sock.settimeout(1)

while not done:
    time.sleep(3)
    fromaddr, flags, msg, notif = server_sock.sctp_recv(3*1024*1024)

I have of course omitted things like imports just for the sake of brevity. And the bottom line is that when this is executed I get the following error:

BlockingIOError: [Errno 11] Resource temporarily unavailable

The corresponding stack trace indicates that the problem is arising on the underlying call to _sctp.sctp_recv_msg(...)

Output from strace shows the following:

1032 socket(PF_INET, SOCK_SEQPACKET|SOCK_CLOEXEC, IPPROTO_SCTP) = 3
1033 getsockopt(3, 0x84 /* SOL_??? */, 2, "\n\0\377\377\10\0`\352", [8]) = 0
1034 getsockopt(3, 0x84 /* SOL_??? */, 11, "\0\0\0\0\0\0\0\0\0\0", [10]) = 0
1035 bind(3, {sa_family=AF_INET, sin_port=htons(9000), in_addr=inet_addr("127.0.0.1")}, 16) = 0
1036 listen(3, 16)                           = 0
1037 ioctl(3, FIONBIO, [1])                  = 0
1038 mmap(NULL, 3149824, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd621646000
1039 recvmsg(3, 0x7ffe1a59ba40, 0)           = -1 EAGAIN (Resource temporarily unavailable)

I attempted to restart my computer in case there was some resource hung open but this did not help anything.


Solution

  • Figured out the issue with this one.

    Under the hood, calling socket.settimeout(1) or similarly calling socket.setblocking(False) which I also experimented with, results in the underlying socket getting set to non-blocking. The reason this becomes a problem, is that while most socket operations (for instance, just the normal socket.recvmsg that you call on a sctp.sctpsocket_udp object get delegated to the python socket object, the sctp_* calls do not, they are handled via the _sctp.c C extension component of pysctp. What this means, is that when you do something that causes the underlying socket to be nonblocking such as socket.settimeout(1), and then use one of the sctp_* functions, rather than timing out and then getting a socket.timeout exception, you end up in this part of the underlying C extension:

    size = sctp_recvmsg(...)
    if(size < 0){
        free(msg);
        PyErr_SetFromErrno(PyExc_IOError);
        return ret;
    }
    

    and thus, get the BlockingIOError referenced above. The solution is to use non-blocking mode rather than bothering with a timeout, and then to catch BlockingIOError instead of socket.timeout and just check the errno attribute of the exception to make sure it was EAGAIN thus indicating no data available at that point, so sleep or something and then try again.