Skip to content

Commit e333d72

Browse files
committed
extmod/modlwip: Fix non-blocking socket ENOTCONN handling.
Move the STATE_LISTENING check to the top of lwip_tcp_receive() to ensure consistent error handling for both blocking and non-blocking sockets. This prevents non-blocking sockets from incorrectly returning EAGAIN instead of ENOTCONN when recv() is called on a listening socket. Also extend the test to cover both blocking and non-blocking modes with various listen() arguments to ensure the fix works correctly in all cases. Signed-off-by: Andrew Leech <[email protected]>
1 parent cf751cf commit e333d72

File tree

2 files changed

+52
-43
lines changed

2 files changed

+52
-43
lines changed

extmod/modlwip.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,12 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_
825825
// Check for any pending errors
826826
STREAM_ERROR_CHECK(socket);
827827

828+
if (socket->state == STATE_LISTENING) {
829+
// original socket in listening state, not the accepted connection.
830+
*_errno = MP_ENOTCONN;
831+
return -1;
832+
}
833+
828834
if (socket->incoming.tcp.pbuf == NULL) {
829835

830836
// Non-blocking socket or flag
@@ -860,12 +866,6 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_
860866
}
861867
}
862868

863-
if (socket->state == STATE_LISTENING) {
864-
// original socket in listening state, not the accepted connection.
865-
*_errno = MP_ENOTCONN;
866-
return -1;
867-
}
868-
869869
MICROPY_PY_LWIP_ENTER
870870

871871
assert(socket->pcb.tcp != NULL);

tests/multi_net/tcp_accept_recv.py

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,52 +13,61 @@ def instance0():
1313
multitest.globals(IP=multitest.get_network_ip())
1414
multitest.next()
1515

16-
for i, listen_arg in enumerate(LISTEN_ARGS):
17-
s = socket.socket()
18-
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
19-
s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1])
16+
test_num = 0
17+
for blocking_mode in [True, False]:
18+
for listen_arg in LISTEN_ARGS:
19+
test_num += 1
20+
s = socket.socket()
21+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
22+
s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1])
2023

21-
# Call listen with or without argument based on test case
22-
if listen_arg is None:
23-
print(f"Test case {i + 1}/{len(LISTEN_ARGS)}: listen()")
24-
s.listen()
25-
else:
26-
print(f"Test case {i + 1}/{len(LISTEN_ARGS)}: listen({listen_arg})")
27-
s.listen(listen_arg)
24+
# Call listen with or without argument based on test case
25+
if listen_arg is None:
26+
print(f"Test case {test_num}/8: listen() blocking={blocking_mode}")
27+
s.listen()
28+
else:
29+
print(f"Test case {test_num}/8: listen({listen_arg}) blocking={blocking_mode}")
30+
s.listen(listen_arg)
2831

29-
# Signal client that server is ready
30-
multitest.broadcast(f"server_ready_{i}")
32+
# Signal client that server is ready
33+
multitest.broadcast(f"server_ready_{test_num}")
3134

32-
# Wait for client connection
33-
c, _ = s.accept()
35+
# Wait for client connection
36+
c, _ = s.accept()
3437

35-
try:
36-
print("recv", s.recv(10)) # should raise Errno 107 ENOTCONN
37-
except OSError as er:
38-
# Verify the error code is either 107 (ENOTCONN) or 128 (ENOTCONN on Windows)
39-
print(er.errno in (107, 128))
38+
# Set blocking mode after accept
39+
s.setblocking(blocking_mode)
4040

41-
# Cleanup
42-
c.close()
43-
s.close()
41+
try:
42+
print("recv", s.recv(10)) # should raise Errno 107 ENOTCONN
43+
except OSError as er:
44+
# Verify the error code is either 107 (ENOTCONN) or 128 (ENOTCONN on Windows)
45+
print(er.errno in (107, 128))
4446

45-
# Signal client we're done with this test case
46-
multitest.broadcast(f"server_done_{i}")
47+
# Cleanup
48+
c.close()
49+
s.close()
50+
51+
# Signal client we're done with this test case
52+
multitest.broadcast(f"server_done_{test_num}")
4753

4854

4955
# Client
5056
def instance1():
5157
multitest.next()
5258

53-
for i, _ in enumerate(LISTEN_ARGS):
54-
# Wait for server to be ready
55-
multitest.wait(f"server_ready_{i}")
56-
57-
# Connect to server
58-
s = socket.socket()
59-
s.connect(socket.getaddrinfo(IP, PORT)[0][-1])
60-
s.send(b"GET / HTTP/1.0\r\n\r\n")
61-
s.close()
62-
63-
# Wait for server to finish this test case
64-
multitest.wait(f"server_done_{i}")
59+
test_num = 0
60+
for blocking_mode in [True, False]:
61+
for _ in LISTEN_ARGS:
62+
test_num += 1
63+
# Wait for server to be ready
64+
multitest.wait(f"server_ready_{test_num}")
65+
66+
# Connect to server
67+
s = socket.socket()
68+
s.connect(socket.getaddrinfo(IP, PORT)[0][-1])
69+
s.send(b"GET / HTTP/1.0\r\n\r\n")
70+
s.close()
71+
72+
# Wait for server to finish this test case
73+
multitest.wait(f"server_done_{test_num}")

0 commit comments

Comments
 (0)