net/tcp: Stop TCP state machine breaking when sending locally
On any target, running a TCP server and a net shell can show the issue:
net tcp connect local_ip port
will fail. Usally it ends up by consumming all tcp connection memory.
This is because in tcp_in(), state changes will most of the time lead to
sending SYN/ACK/etc... packets under the same thread, which will run all
through net_send_data(), back to tcp_in(). Thus a forever loop on SYN ->
SYN|ACK -> SYN -> SYN|ACK until tcp connection cannot be allocated
anymore.
Fixing it by scheduling any local packet to be sent on the queue.
Fixes #38576
Signed-off-by: Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
diff --git a/subsys/net/ip/tcp2.c b/subsys/net/ip/tcp2.c
index 7bfed94..ca16d01 100644
--- a/subsys/net/ip/tcp2.c
+++ b/subsys/net/ip/tcp2.c
@@ -826,6 +826,25 @@
return net_pkt_set_data(pkt, &mss_option);
}
+static bool is_destination_local(struct net_pkt *pkt)
+{
+ if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) {
+ if (net_ipv4_is_addr_loopback(&NET_IPV4_HDR(pkt)->dst) ||
+ net_ipv4_is_my_addr(&NET_IPV4_HDR(pkt)->dst)) {
+ return true;
+ }
+ }
+
+ if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) {
+ if (net_ipv6_is_addr_loopback(&NET_IPV6_HDR(pkt)->dst) ||
+ net_ipv6_is_my_addr(&NET_IPV6_HDR(pkt)->dst)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
static int tcp_out_ext(struct tcp *conn, uint8_t flags, struct net_pkt *data,
uint32_t seq)
{
@@ -884,7 +903,14 @@
sys_slist_append(&conn->send_queue, &pkt->next);
- if (tcp_send_process_no_lock(conn)) {
+ if (is_destination_local(pkt)) {
+ /* If the destination is local, we have to let the current
+ * thread to finish with any state-machine changes before
+ * sending the packet, or it might lead to state unconsistencies
+ */
+ k_work_schedule_for_queue(&tcp_work_q,
+ &conn->send_timer, K_NO_WAIT);
+ } else if (tcp_send_process_no_lock(conn)) {
tcp_conn_unref(conn);
}
out: