Skip to content

Commit a7cd418

Browse files
committed
extmod/modlwip: Keep TCP data on remote RST.
This commit fixes a long standing bug/deficiency in the lwIP socket code, whereby it would abandon all incoming TCP data if the remote sent a TCP RST. This behaviour it tested by the existing `tests/multi_net/tcp_client_rst.py` and `tests/multi_net/asyncio_tcp_client_rst.py` tests, and they both fail on boards like PYBD_SFx and RPI_PICO_W due to the deficiency. With the fix here, both of those tests now pass on lwIP targets, along with all existing socket tests. Signed-off-by: Damien George <damien@micropython.org>
1 parent c696ca9 commit a7cd418

1 file changed

Lines changed: 36 additions & 20 deletions

File tree

extmod/modlwip.c

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,9 @@ typedef struct _lwip_socket_obj_t {
348348
#define STATE_LISTENING 1
349349
#define STATE_CONNECTING 2
350350
#define STATE_CONNECTED 3
351-
#define STATE_PEER_CLOSED 4
352-
#define STATE_ACTIVE_UDP 5
351+
#define STATE_ACTIVE_UDP 4
352+
#define STATE_PEER_CLOSED 5 // Values higher than this must also be closed by peer
353+
#define STATE_PEER_RST_HANDLED 6
353354
// Negative value is lwIP error
354355
int8_t state;
355356
} lwip_socket_obj_t;
@@ -370,10 +371,10 @@ static struct tcp_pcb *volatile *lwip_socket_incoming_array(lwip_socket_obj_t *s
370371
}
371372
}
372373

373-
static void lwip_socket_free_incoming(lwip_socket_obj_t *socket) {
374+
static void lwip_socket_free_incoming(lwip_socket_obj_t *socket, bool free_queued_stream_data) {
374375
if (socket->state != STATE_LISTENING) {
375376
if (socket->type == MOD_NETWORK_SOCK_STREAM) {
376-
if (socket->incoming.tcp.pbuf != NULL) {
377+
if (free_queued_stream_data && socket->incoming.tcp.pbuf != NULL) {
377378
pbuf_free(socket->incoming.tcp.pbuf);
378379
socket->incoming.tcp.pbuf = NULL;
379380
}
@@ -399,6 +400,8 @@ static void lwip_socket_free_incoming(lwip_socket_obj_t *socket) {
399400
tcp_array[i] = NULL;
400401
}
401402
}
403+
// This socket is now a non-listening stream, so clear the relevant state.
404+
socket->incoming.tcp.pbuf = NULL;
402405
}
403406
}
404407

@@ -487,8 +490,9 @@ static void _lwip_udp_incoming(void *arg, struct udp_pcb *upcb, struct pbuf *p,
487490
static void _lwip_tcp_error(void *arg, err_t err) {
488491
lwip_socket_obj_t *socket = (lwip_socket_obj_t *)arg;
489492

490-
// Free any incoming buffers or connections that are stored
491-
lwip_socket_free_incoming(socket);
493+
// Free any incoming buffers or connections that are stored, but keep potential
494+
// queued TCP data in case it's read later. Will be freed by MP_STREAM_CLOSE.
495+
lwip_socket_free_incoming(socket, false);
492496
// Pass the error code back via the connection variable.
493497
socket->state = err;
494498
// If we got here, the lwIP stack either has deallocated or will deallocate the pcb.
@@ -818,20 +822,27 @@ static mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui
818822

819823
// Helper function for recv/recvfrom to handle TCP packets
820824
static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, mp_int_t flags, int *_errno) {
821-
// Check for any pending errors
822-
STREAM_ERROR_CHECK(socket);
823-
824825
if (socket->state == STATE_LISTENING) {
825826
// original socket in listening state, not the accepted connection.
826827
*_errno = MP_ENOTCONN;
827828
return -1;
828829
}
829830

830831
if (socket->incoming.tcp.pbuf == NULL) {
832+
// Check for any pending errors that should propagate out on socket read.
833+
if (socket->state < 0) {
834+
*_errno = error_lookup_table[-socket->state];
835+
if (*_errno == MP_ECONNRESET) {
836+
socket->state = STATE_PEER_RST_HANDLED;
837+
} else {
838+
socket->state = _ERR_BADF;
839+
}
840+
return MP_STREAM_ERROR;
841+
}
831842

832843
// Non-blocking socket or flag
833844
if (socket->timeout == 0 || (flags & MSG_DONTWAIT)) {
834-
if (socket->state == STATE_PEER_CLOSED) {
845+
if (socket->state >= STATE_PEER_CLOSED) {
835846
return 0;
836847
}
837848
*_errno = MP_EAGAIN;
@@ -847,7 +858,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_
847858
poll_sockets();
848859
}
849860

850-
if (socket->state == STATE_PEER_CLOSED) {
861+
if (socket->state >= STATE_PEER_CLOSED) {
851862
if (socket->incoming.tcp.pbuf == NULL) {
852863
// socket closed and no data left in buffer
853864
return 0;
@@ -864,8 +875,6 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_
864875

865876
MICROPY_PY_LWIP_ENTER
866877

867-
assert(socket->pcb.tcp != NULL);
868-
869878
struct pbuf *p = socket->incoming.tcp.pbuf;
870879

871880
mp_uint_t remaining = p->len - socket->recv_offset;
@@ -888,7 +897,9 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_
888897
} else {
889898
socket->recv_offset += len;
890899
}
891-
tcp_recved(socket->pcb.tcp, len);
900+
if (socket->pcb.tcp != NULL) {
901+
tcp_recved(socket->pcb.tcp, len);
902+
}
892903
}
893904

894905
MICROPY_PY_LWIP_EXIT
@@ -1292,8 +1303,6 @@ static mp_obj_t lwip_socket_recv_common(size_t n_args, const mp_obj_t *args, ip_
12921303
vstr_t vstr;
12931304
mp_uint_t ret = 0;
12941305

1295-
lwip_socket_check_connected(socket);
1296-
12971306
vstr_init_len(&vstr, len);
12981307

12991308
switch (socket->type) {
@@ -1308,6 +1317,7 @@ static mp_obj_t lwip_socket_recv_common(size_t n_args, const mp_obj_t *args, ip_
13081317
#if MICROPY_PY_LWIP_SOCK_RAW
13091318
case MOD_NETWORK_SOCK_RAW:
13101319
#endif
1320+
lwip_socket_check_connected(socket);
13111321
ret = lwip_raw_udp_receive(socket, (byte *)vstr.buf, len, flags, ip, port, &_errno);
13121322
break;
13131323
}
@@ -1580,7 +1590,10 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_
15801590
}
15811591
} else if (socket->type == MOD_NETWORK_SOCK_STREAM) {
15821592
// For TCP sockets there is just one slot for incoming data
1583-
if (socket->incoming.tcp.pbuf != NULL) {
1593+
// The socket is also readable when in RST state
1594+
if (socket->incoming.tcp.pbuf != NULL
1595+
|| socket->state == ERR_RST
1596+
|| socket->state == STATE_PEER_RST_HANDLED) {
15841597
ret |= MP_STREAM_POLL_RD;
15851598
}
15861599
} else {
@@ -1619,6 +1632,8 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_
16191632
} else if (socket->state == ERR_RST) {
16201633
// Socket was reset by peer, a write will return an error
16211634
ret |= flags & MP_STREAM_POLL_WR;
1635+
ret |= MP_STREAM_POLL_ERR | MP_STREAM_POLL_HUP;
1636+
} else if (socket->state == STATE_PEER_RST_HANDLED) {
16221637
ret |= MP_STREAM_POLL_HUP;
16231638
} else if (socket->state == _ERR_BADF) {
16241639
ret |= MP_STREAM_POLL_NVAL;
@@ -1629,14 +1644,15 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_
16291644
}
16301645

16311646
} else if (request == MP_STREAM_CLOSE) {
1647+
// Free any incoming buffers or connections that are stored
1648+
lwip_socket_free_incoming(socket, true);
1649+
16321650
if (socket->pcb.tcp == NULL) {
1651+
socket->state = _ERR_BADF;
16331652
MICROPY_PY_LWIP_EXIT
16341653
return 0;
16351654
}
16361655

1637-
// Free any incoming buffers or connections that are stored
1638-
lwip_socket_free_incoming(socket);
1639-
16401656
switch (socket->type) {
16411657
case MOD_NETWORK_SOCK_STREAM: {
16421658
// Deregister callback (pcb.tcp is set to NULL below so must deregister now)

0 commit comments

Comments
 (0)