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: