Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,15 @@ Accepts a connection on a listening socket.
- addrlen: Length of address structure
- Returns: New socket descriptor or negative error code

```c
int wolfIP_sock_abort(struct wolfIP *s, int sockfd);
```
Abortive close, like `SO_LINGER` with a zero timeout: sends an RST in `SYN_RCVD`, `ESTABLISHED`, `CLOSE_WAIT`, `FIN_WAIT_1` and `FIN_WAIT_2` (other states, such as `CLOSING` and `LAST_ACK`, are released without one), and releases the socket at once instead of waiting for a FIN exchange the peer may never complete. Also valid on a socket whose `wolfIP_sock_close()` returned `-WOLFIP_EAGAIN`, as long as no socket has been created or accepted since (see the return values under Data Transfer).
- Parameters:
- s: wolfIP instance
- sockfd: TCP socket descriptor
- Returns: 0 on success, `-WOLFIP_EINVAL` for a bad or non-TCP descriptor

### Data Transfer
```c
int wolfIP_sock_send(struct wolfIP *s, int sockfd, const void *buf, size_t len, int flags);
Expand All @@ -180,6 +189,18 @@ int wolfIP_sock_recvfrom(struct wolfIP *s, int sockfd, void *buf, size_t len, in
Send/receive data on a datagram socket.
- Parameters similar to send/recv with additional address parameters

wolfIP never blocks, so every call above can ask the caller to retry. On a TCP socket they return:

| Value | Meaning |
|-------|---------|
| `> 0` | Bytes transferred, possibly fewer than requested |
| `0` | End of stream: the peer closed and nothing is left to read |
| `-WOLFIP_EAGAIN` | Retry later: no data queued, no transmit space, or the socket is still connecting (`SYN_SENT`/`SYN_RCVD`) |
| `-WOLFIP_EINVAL` | Bad descriptor or arguments |
| `-1` | The operation cannot succeed on this socket (a listener, or a closing state) |

`wolfIP_sock_close()` follows the same convention: on a connected socket it starts the FIN exchange and returns `-WOLFIP_EAGAIN`. The stack then releases the descriptor by itself, without notification, once the exchange completes, the peer resets, or the close times out, and the next `wolfIP_sock_socket()` or `wolfIP_sock_accept()` can hand out the same number. Calling `wolfIP_sock_close()` or `wolfIP_sock_abort()` on it again is therefore only safe while no socket has been created or accepted since; after that, the call acts on the new socket.

## Stack Interface Functions

```c
Expand Down
18 changes: 9 additions & 9 deletions src/port/wolfssl_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ static int wolfIP_io_recv(WOLFSSL* ssl, char* buf, int sz, void* ctx)
return WOLFSSL_CBIO_ERR_GENERAL;

ret = wolfIP_sock_recv(desc->stack, desc->fd, buf, sz, 0);
/* Only -WOLFIP_EAGAIN means "would block": wolfIP_sock_recvfrom returns it
* (via queue_pop) for an established socket with an empty RX queue. A -1 is
* the "not established" / torn-down case and must be reported as a fatal
* close, otherwise wolfSSL keeps retrying a dead connection forever and the
* owning session is never released. */
/* Only -WOLFIP_EAGAIN means "would block": an empty RX queue, or a socket
* still connecting (SYN_SENT/SYN_RCVD). A -1 is a listener or a torn-down
* stream and must be reported as a fatal close, otherwise wolfSSL keeps
* retrying a dead connection forever and the owning session is never
* released. */
if (ret == -WOLFIP_EAGAIN)
return WOLFSSL_CBIO_ERR_WANT_READ;
if (ret <= 0)
Expand All @@ -94,10 +94,10 @@ static int wolfIP_io_send(WOLFSSL* ssl, char* buf, int sz, void* ctx)
return WOLFSSL_CBIO_ERR_GENERAL;

ret = wolfIP_sock_send(desc->stack, desc->fd, buf, sz, 0);
/* Only -WOLFIP_EAGAIN means "would block" (TX buffer full, nothing queued).
* A -1 is the "not established" / torn-down case from wolfIP_sock_sendto and
* must be reported as a fatal close, otherwise wolfSSL retries the dead
* connection forever and its session is never released. */
/* Only -WOLFIP_EAGAIN means "would block" (TX buffer full, or a socket
* still connecting). A -1 is a listener or a torn-down stream and must be
* reported as a fatal close, otherwise wolfSSL retries the dead connection
* forever and its session is never released. */
if (ret == -WOLFIP_EAGAIN)
return WOLFSSL_CBIO_ERR_WANT_WRITE;
if (ret <= 0)
Expand Down
21 changes: 21 additions & 0 deletions src/test/unit/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,26 @@ Suite *wolf_suite(void)
tcase_add_test(tc_utils, test_tcp_listener_preaccept_handoff_reemits_queued_ack);
tcase_add_test(tc_utils, test_tcp_listener_closed_while_pending_is_not_readable);
tcase_add_test(tc_utils, test_tcp_listener_preaccept_timeout_reverts_port);
tcase_add_test(tc_utils, test_tcp_listener_preaccept_timeout_resets_peer);
tcase_add_test(tc_utils, test_tcp_listener_preaccept_accept_no_socket_resets_peer);
tcase_add_test(tc_utils, test_tcp_listener_preaccept_rst_keeps_listener);
tcase_add_test(tc_utils, test_tcp_listener_closed_preaccept_rst_frees_fin_wait_1);
tcase_add_test(tc_utils, test_tcp_listener_closed_preaccept_rst_frees_last_ack);
tcase_add_test(tc_utils, test_tcp_listener_preaccept_close_wait_timeout_reverts_port);
tcase_add_test(tc_utils, test_sock_abort_established_resets_and_frees);
tcase_add_test(tc_utils, test_sock_abort_after_close_eagain_frees);
tcase_add_test(tc_utils, test_sock_abort_without_peer_sends_nothing);
tcase_add_test(tc_utils, test_sock_abort_fin_wait_1_rst_covers_sent_fin);
tcase_add_test(tc_utils, test_sock_abort_fin_wait_2_rst_covers_acked_fin);
tcase_add_test(tc_utils, test_sock_abort_syn_rcvd_rst_covers_syn);
tcase_add_test(tc_utils, test_sock_abort_queued_data_rst_at_snd_una);
tcase_add_test(tc_utils, test_sock_abort_preaccept_listener_stops_listening);
tcase_add_test(tc_utils, test_sock_abort_after_data_rto_rst_covers_sent_data);
tcase_add_test(tc_utils, test_sock_abort_fin_behind_unacked_data_covers_fin);
tcase_add_test(tc_utils, test_sock_abort_requeued_fin_covers_fin);
tcase_add_test(tc_utils, test_sock_abort_close_wait_resets_peer);
tcase_add_test(tc_utils, test_sock_abort_last_ack_sends_nothing);
tcase_add_test(tc_utils, test_sock_abort_syn_rcvd_listener_resets_and_stops_listening);
tcase_add_test(tc_utils, test_tcp_listener_preaccept_revert_drains_connection_state);
tcase_add_test(tc_utils, test_tcp_listener_preaccept_close_rto_retransmits_finack);
tcase_add_test(tc_utils, test_tcp_fin_in_close_wait_does_not_advance_ack);
Expand Down Expand Up @@ -1281,6 +1301,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_core, test_sock_sendto_tcp_established_sends_data);
tcase_add_test(tc_core, test_sock_sendto_tcp_invalid_fd);
tcase_add_test(tc_core, test_sock_sendto_tcp_syn_rcvd_returns_eagain);
tcase_add_test(tc_core, test_sock_tcp_listener_rejects_data_io);
tcase_add_test(tc_core, test_sock_sendto_tcp_close_wait_sends_data);
#if WOLFIP_RAWSOCKETS
tcase_add_test(tc_core, test_sock_sendto_raw_null_dest_uses_stored_remote_ip);
Expand Down
3 changes: 3 additions & 0 deletions src/test/unit/unit_tests_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -3768,7 +3768,10 @@ START_TEST(test_sock_sendto_tcp_not_established)
ck_assert_int_gt(tcp_sd, 0);
ts = &s.tcpsockets[SOCKET_UNMARK(tcp_sd)];
ts->sock.tcp.state = TCP_SYN_SENT;
ck_assert_int_eq(wolfIP_sock_sendto(&s, tcp_sd, buf, sizeof(buf), 0, NULL, 0),
-WOLFIP_EAGAIN);

ts->sock.tcp.state = TCP_LISTEN;
ck_assert_int_eq(wolfIP_sock_sendto(&s, tcp_sd, buf, sizeof(buf), 0, NULL, 0), -1);
}
END_TEST
Expand Down
4 changes: 2 additions & 2 deletions src/test/unit/unit_tests_dns_dhcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ START_TEST(test_sock_recvfrom_tcp_states)

ts->sock.tcp.state = TCP_SYN_SENT;
ret = wolfIP_sock_recvfrom(&s, tcp_sd, buf, sizeof(buf), 0, NULL, 0);
ck_assert_int_eq(ret, -1);
ck_assert_int_eq(ret, -WOLFIP_EAGAIN);

ts->sock.tcp.state = TCP_CLOSE_WAIT;
queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, 0);
Expand Down Expand Up @@ -1666,7 +1666,7 @@ START_TEST(test_sock_sendto_more_error_paths)
ts->remote_ip = 0x0A000002U;
fifo_init(&ts->sock.tcp.txbuf, tiny, sizeof(tiny));
ret = wolfIP_sock_sendto(&s, tcp_sd, buf, sizeof(buf), 0, NULL, 0);
ck_assert_int_eq(ret, -1);
ck_assert_int_eq(ret, -WOLFIP_EAGAIN);

udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP);
ck_assert_int_gt(udp_sd, 0);
Expand Down
51 changes: 46 additions & 5 deletions src/test/unit/unit_tests_socket_api_arms.c
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,40 @@ START_TEST(test_sock_sendto_tcp_syn_rcvd_returns_eagain)
}
END_TEST

START_TEST(test_sock_tcp_listener_rejects_data_io)
{
static const int states[] = {
TCP_LISTEN, TCP_SYN_RCVD, TCP_ESTABLISHED, TCP_CLOSE_WAIT
};
struct wolfIP s;
int sd;
size_t i;
struct tsocket *ts;
uint8_t buf[8] = {0};
uint8_t payload[4] = {1, 2, 3, 4};

for (i = 0; i < sizeof(states) / sizeof(states[0]); i++) {
wolfIP_init(&s);
mock_link_init(&s);
sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0);
ck_assert_int_ge(sd, 0);
ts = &s.tcpsockets[SOCKET_UNMARK(sd)];
ts->sock.tcp.state = states[i];
ts->sock.tcp.is_listener = 1;
ck_assert_int_eq(queue_insert(&ts->sock.tcp.rxbuf, payload, 0,
sizeof(payload)), 0);

ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, buf, sizeof(buf), 0,
NULL, 0), -1);
ck_assert_uint_eq(fifo_len(&ts->sock.tcp.txbuf), 0);
ck_assert_int_eq(wolfIP_sock_can_write(&s, sd), 1);
ck_assert_int_eq(wolfIP_sock_recvfrom(&s, sd, buf, sizeof(buf), 0,
NULL, NULL), -1);
ck_assert_uint_eq(queue_len(&ts->sock.tcp.rxbuf), sizeof(payload));
}
}
END_TEST

START_TEST(test_sock_sendto_tcp_close_wait_sends_data)
{
struct wolfIP s;
Expand Down Expand Up @@ -1403,12 +1437,19 @@ START_TEST(test_sock_recvfrom_tcp_not_established)
sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0);
ck_assert_int_ge(sd, 0);
ts = &s.tcpsockets[SOCKET_UNMARK(sd)];
/* A genuinely not-established stream (mid-connect) reports an error.
* TCP_CLOSED is intentionally NOT used here: a torn-down/closed stream now
* reports EOF (0), covered by
* test_tcp_input_syn_rcvd_rst_nullcb_recv_reports_eof. */
/* Mid-connect is retryable; a listener has no stream to read. TCP_CLOSED
* reports EOF, see test_tcp_input_syn_rcvd_rst_nullcb_recv_reports_eof. */
ts->sock.tcp.state = TCP_SYN_SENT;

ck_assert_int_eq(wolfIP_sock_recvfrom(&s, sd, buf, sizeof(buf), 0,
NULL, NULL), -WOLFIP_EAGAIN);
ts->sock.tcp.state = TCP_SYN_RCVD;
ck_assert_int_eq(wolfIP_sock_recvfrom(&s, sd, buf, sizeof(buf), 0,
NULL, NULL), -WOLFIP_EAGAIN);
ts->sock.tcp.state = TCP_LISTEN;
ck_assert_int_eq(wolfIP_sock_recvfrom(&s, sd, buf, sizeof(buf), 0,
NULL, NULL), -1);
ts->sock.tcp.is_listener = 1;
ts->sock.tcp.state = TCP_SYN_RCVD;
ck_assert_int_eq(wolfIP_sock_recvfrom(&s, sd, buf, sizeof(buf), 0,
NULL, NULL), -1);
}
Expand Down
Loading
Loading