When hardware socket 0 is not in use (as is the case when DHCP is not enabled), accepting connections on a listening socket results in an unused socket being incorrectly tagged as 'reserved', blocking it from ever being used.
The server starts out listening on socket 1. The first accepted connection gets assigned to socket 1, and the server starts listening on socket 0 but reserves socket 2, effectively blocking it from ever being used.
I've tracked the issue down to the socket swap that happens in socket.accept(), specifically here:
new_listen_socknum, addr = _the_interface.socket_accept(self._socknum)
current_socknum = self._socknum
# Create a new socket object and swap socket nums, so we can continue listening
client_sock = socket()
client_sock._socknum = current_socknum # pylint: disable=protected-access
self._socknum = new_listen_socknum
new_listen_socknum is assigned inside the call to WIZNET5K.socket_accept() by the line next_socknum = self.get_socket(). Note that the call to get_socket() doesn't specify that a reserved socket is required, and since hardware socket 0 is available, that's what is assigned.
current_socknum is set to the listening socket's number, in this case hardware socket 1
client_sock = socket() creates a new socket object, which automatically reserves a hardware socket due to the self._socknum = _the_interface.get_socket(reserve_socket=True) line inside socket.__init__(). The newly created socket object is assigned hardware socket 2 which gets marked as reserved.
The new socket object then gets reassigned to hardware socket 1 via client_sock._socknum = current_socknum, leaving hardware socket 2 unused but marked as reserved.
I believe I've fixed the issue by ignoring the next_socknum returned by socket_accept() and using the socket number originally assigned to the new socket instead:
_, addr = _the_interface.socket_accept(self._socknum)
current_socknum = self._socknum
# Create a new socket object and swap socket nums, so we can continue listening
client_sock = socket()
self._socknum = client_sock._socknum
client_sock._socknum = current_socknum # pylint: disable=protected-access
Note that this prevents hardware socket 0 from being used to listen for incoming connections, but I think that's how it's supposed to work anyway.
It also looks like next_socknum can be stripped completely out of WIZNET5K.socket_accept() as the only place it was used was in socket.accept(), but that changes the API and I wasn't sure if that would be problematic.
When hardware socket 0 is not in use (as is the case when DHCP is not enabled), accepting connections on a listening socket results in an unused socket being incorrectly tagged as 'reserved', blocking it from ever being used.
I wrote a script to help troubleshoot this (and other) socket issues, which is available at https://github.com/fasteddy516/CircuitPython_Wiznet5k_Socket_Visualizer. The output is below:
The server starts out listening on socket 1. The first accepted connection gets assigned to socket 1, and the server starts listening on socket 0 but reserves socket 2, effectively blocking it from ever being used.
I've tracked the issue down to the socket swap that happens in
socket.accept()
, specifically here:new_listen_socknum
is assigned inside the call toWIZNET5K.socket_accept()
by the linenext_socknum = self.get_socket()
. Note that the call toget_socket()
doesn't specify that a reserved socket is required, and since hardware socket 0 is available, that's what is assigned.current_socknum
is set to the listening socket's number, in this case hardware socket 1client_sock = socket()
creates a new socket object, which automatically reserves a hardware socket due to theself._socknum = _the_interface.get_socket(reserve_socket=True)
line insidesocket.__init__()
. The newly created socket object is assigned hardware socket 2 which gets marked as reserved.The new socket object then gets reassigned to hardware socket 1 via
client_sock._socknum = current_socknum
, leaving hardware socket 2 unused but marked as reserved.I believe I've fixed the issue by ignoring the
next_socknum
returned bysocket_accept()
and using the socket number originally assigned to the new socket instead:Note that this prevents hardware socket 0 from being used to listen for incoming connections, but I think that's how it's supposed to work anyway.
It also looks like
next_socknum
can be stripped completely out ofWIZNET5K.socket_accept()
as the only place it was used was insocket.accept()
, but that changes the API and I wasn't sure if that would be problematic.