/** @file * @brief TCP handler * * Handle TCP connections. */ /* * Copyright (c) 2016 Intel Corporation * Copyright 2011-2015 by Andrey Butok. FNET Community. * Copyright 2008-2010 by Andrey Butok. Freescale Semiconductor, Inc. * Copyright 2003 by Alexey Shervashidze, Andrey Butok. Motorola SPS. * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_tcp, CONFIG_NET_TCP_LOG_LEVEL); #include #include #include #include #include #include #include #include #include "connection.h" #include "net_private.h" #include "ipv6.h" #include "ipv4.h" #include "tcp_internal.h" #include "net_stats.h" #define ALLOC_TIMEOUT K_MSEC(500) static int net_tcp_queue_pkt(struct net_context *context, struct net_pkt *pkt); /* * Each TCP connection needs to be tracked by net_context, so * we need to allocate equal number of control structures here. */ #define NET_MAX_TCP_CONTEXT CONFIG_NET_MAX_CONTEXTS static struct net_tcp tcp_context[NET_MAX_TCP_CONTEXT]; static struct tcp_backlog_entry { struct net_tcp *tcp; uint32_t send_seq; uint32_t send_ack; struct k_delayed_work ack_timer; struct sockaddr remote; uint16_t send_mss; } tcp_backlog[CONFIG_NET_TCP_BACKLOG_SIZE]; #if defined(CONFIG_NET_TCP_ACK_TIMEOUT) #define ACK_TIMEOUT_MS CONFIG_NET_TCP_ACK_TIMEOUT #define ACK_TIMEOUT K_MSEC(ACK_TIMEOUT_MS) #else #define ACK_TIMEOUT_MS (1 * MSEC_PER_SEC) #define ACK_TIMEOUT K_MSEC(MSEC_PER_SEC) #endif #define FIN_TIMEOUT_MS (1 * MSEC_PER_SEC) #define FIN_TIMEOUT K_MSEC(MSEC_PER_SEC) /* Declares a wrapper function for a net_conn callback that refs the * context around the invocation (to protect it from premature * deletion). Long term would be nice to see this feature be part of * the connection type itself, but right now it has opaque "user_data" * pointers and doesn't understand what a net_context is. */ #define NET_CONN_CB(name) \ static enum net_verdict _##name(struct net_conn *conn, \ struct net_pkt *pkt, \ union net_ip_header *ip_hdr, \ union net_proto_header *proto_hdr, \ void *user_data); \ static enum net_verdict name(struct net_conn *conn, \ struct net_pkt *pkt, \ union net_ip_header *ip_hdr, \ union net_proto_header *proto_hdr, \ void *user_data) \ { \ enum net_verdict result; \ \ net_context_ref(user_data); \ result = _##name(conn, pkt, ip_hdr, \ proto_hdr, user_data); \ net_context_unref(user_data); \ return result; \ } \ static enum net_verdict _##name(struct net_conn *conn, \ struct net_pkt *pkt, \ union net_ip_header *ip_hdr, \ union net_proto_header *proto_hdr, \ void *user_data) \ struct tcp_segment { uint32_t seq; uint32_t ack; uint16_t wnd; uint8_t flags; uint8_t optlen; void *options; struct sockaddr_ptr *src_addr; const struct sockaddr *dst_addr; }; static char upper_if_set(char chr, bool set) { if (set) { return chr & ~0x20; } return chr | 0x20; } static void net_tcp_trace(struct net_pkt *pkt, struct net_tcp *tcp, struct net_tcp_hdr *tcp_hdr) { uint32_t rel_ack, ack; uint8_t flags; if (CONFIG_NET_TCP_LOG_LEVEL < LOG_LEVEL_DBG) { return; } flags = NET_TCP_FLAGS(tcp_hdr); ack = sys_get_be32(tcp_hdr->ack); if (!tcp->sent_ack) { rel_ack = 0U; } else { rel_ack = ack ? ack - tcp->sent_ack : 0; } NET_DBG("[%p] pkt %p src %u dst %u", tcp, pkt, ntohs(tcp_hdr->src_port), ntohs(tcp_hdr->dst_port)); NET_DBG(" seq 0x%04x (%u) ack 0x%04x (%u/%u)", sys_get_be32(tcp_hdr->seq), sys_get_be32(tcp_hdr->seq), ack, ack, /* This tells how many bytes we are acking now */ rel_ack); NET_DBG(" flags %c%c%c%c%c%c", upper_if_set('u', flags & NET_TCP_URG), upper_if_set('a', flags & NET_TCP_ACK), upper_if_set('p', flags & NET_TCP_PSH), upper_if_set('r', flags & NET_TCP_RST), upper_if_set('s', flags & NET_TCP_SYN), upper_if_set('f', flags & NET_TCP_FIN)); NET_DBG(" win %u chk 0x%04x", sys_get_be16(tcp_hdr->wnd), ntohs(tcp_hdr->chksum)); } static inline k_timeout_t retry_timeout(const struct net_tcp *tcp) { return K_MSEC(((uint32_t)1 << tcp->retry_timeout_shift) * CONFIG_NET_TCP_INIT_RETRANSMISSION_TIMEOUT); } #define is_6lo_technology(pkt) \ (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6 && \ ((IS_ENABLED(CONFIG_NET_L2_BT) && \ net_pkt_lladdr_dst(pkt)->type == NET_LINK_BLUETOOTH) || \ (IS_ENABLED(CONFIG_NET_L2_IEEE802154) && \ net_pkt_lladdr_dst(pkt)->type == NET_LINK_IEEE802154) || \ (IS_ENABLED(CONFIG_NET_L2_CANBUS) && \ net_pkt_lladdr_dst(pkt)->type == NET_LINK_CANBUS))) /* The ref should not be done for Bluetooth and IEEE 802.15.4 which use * IPv6 header compression (6lo). For BT and 802.15.4 we copy the pkt * chain we are about to send so it is fine if the network driver * releases it. As we have our own copy of the sent data, we do not * need to take a reference of it. See also net_tcp_send_pkt(). * * Note that this is macro so that we get information who called the * net_pkt_ref() if memory debugging is active. */ #define do_ref_if_needed(tcp, pkt) \ do { \ if (!is_6lo_technology(pkt)) { \ NET_DBG("[%p] ref pkt %p new ref %d (%s:%d)", \ tcp, pkt, atomic_get(&pkt->atomic_ref) + 1, \ __func__, __LINE__); \ pkt = net_pkt_ref(pkt); \ } \ } while (0) static void abort_connection(struct net_tcp *tcp) { struct net_context *ctx = tcp->context; NET_DBG("[%p] segment retransmission exceeds %d, resetting context %p", tcp, CONFIG_NET_TCP_RETRY_COUNT, ctx); if (ctx->recv_cb) { ctx->recv_cb(ctx, NULL, NULL, NULL, -ECONNRESET, tcp->recv_user_data); } net_context_unref(ctx); } static void tcp_retry_expired(struct k_work *work) { struct net_tcp *tcp = CONTAINER_OF(work, struct net_tcp, retry_timer); struct net_pkt *pkt; /* Double the retry period for exponential backoff and resend * the first (only the first!) unack'd packet. */ if (!sys_slist_is_empty(&tcp->sent_list)) { tcp->retry_timeout_shift++; if (tcp->retry_timeout_shift > CONFIG_NET_TCP_RETRY_COUNT) { abort_connection(tcp); return; } k_delayed_work_submit(&tcp->retry_timer, retry_timeout(tcp)); pkt = CONTAINER_OF(sys_slist_peek_head(&tcp->sent_list), struct net_pkt, sent_list); if (k_work_pending(net_pkt_work(pkt))) { /* If the packet is still pending in TX queue, then do * not try to resend it again. This can happen if the * device is so busy that the TX thread has not yet * finished previous sending of this packet. */ NET_DBG("[%p] pkt %p still pending in TX queue", tcp, pkt); return; } net_pkt_set_queued(pkt, true); net_pkt_set_tcp_1st_msg(pkt, false); /* The ref here is for the initial reference which was lost * when the pkt was sent. Typically the ref count should be 2 * at this point if the pkt is being sent by the driver. */ if (!is_6lo_technology(pkt)) { net_pkt_ref(pkt); } if (net_tcp_send_pkt(pkt) < 0 && !is_6lo_technology(pkt)) { NET_DBG("retry %u: [%p] pkt %p send failed", tcp->retry_timeout_shift, tcp, pkt); /* Undo the ref done above */ net_pkt_unref(pkt); } else { NET_DBG("retry %u: [%p] sent pkt %p", tcp->retry_timeout_shift, tcp, pkt); if (IS_ENABLED(CONFIG_NET_STATISTICS_TCP) && !is_6lo_technology(pkt)) { net_stats_update_tcp_seg_rexmit( net_pkt_iface(pkt)); } } } else if (CONFIG_NET_TCP_TIME_WAIT_DELAY != 0) { if (tcp->fin_sent && tcp->fin_rcvd) { NET_DBG("[%p] Closing connection (context %p)", tcp, tcp->context); net_context_unref(tcp->context); } } } struct net_tcp *net_tcp_alloc(struct net_context *context) { int i, key; key = irq_lock(); for (i = 0; i < NET_MAX_TCP_CONTEXT; i++) { if (!net_tcp_is_used(&tcp_context[i])) { tcp_context[i].flags |= NET_TCP_IN_USE; break; } } irq_unlock(key); if (i >= NET_MAX_TCP_CONTEXT) { return NULL; } (void)memset(&tcp_context[i], 0, sizeof(struct net_tcp)); tcp_context[i].flags = NET_TCP_IN_USE; tcp_context[i].state = NET_TCP_CLOSED; tcp_context[i].context = context; tcp_context[i].send_seq = tcp_init_isn(); tcp_context[i].recv_wnd = MIN(NET_TCP_MAX_WIN, NET_TCP_BUF_MAX_LEN); tcp_context[i].send_mss = NET_TCP_DEFAULT_MSS; tcp_context[i].accept_cb = NULL; k_delayed_work_init(&tcp_context[i].retry_timer, tcp_retry_expired); k_sem_init(&tcp_context[i].connect_wait, 0, UINT_MAX); return &tcp_context[i]; } static void ack_timer_cancel(struct net_tcp *tcp) { k_delayed_work_cancel(&tcp->ack_timer); } static void fin_timer_cancel(struct net_tcp *tcp) { k_delayed_work_cancel(&tcp->fin_timer); } static void retry_timer_cancel(struct net_tcp *tcp) { k_delayed_work_cancel(&tcp->retry_timer); } static void timewait_timer_cancel(struct net_tcp *tcp) { k_delayed_work_cancel(&tcp->timewait_timer); } int net_tcp_release(struct net_tcp *tcp) { struct net_pkt *pkt; struct net_pkt *tmp; unsigned int key; if (!PART_OF_ARRAY(tcp_context, tcp)) { return -EINVAL; } retry_timer_cancel(tcp); k_sem_reset(&tcp->connect_wait); ack_timer_cancel(tcp); fin_timer_cancel(tcp); timewait_timer_cancel(tcp); net_tcp_change_state(tcp, NET_TCP_CLOSED); SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tcp->sent_list, pkt, tmp, sent_list) { int refcount; sys_slist_remove(&tcp->sent_list, NULL, &pkt->sent_list); /* The packet might get freed when sending it, so if that is * so, just skip it. */ if (atomic_get(&pkt->atomic_ref) == 0) { continue; } /* Make sure we undo the reference done in net_tcp_queue_pkt() */ net_pkt_unref(pkt); /* Release the packet fully unless it is still pending */ refcount = atomic_get(&pkt->atomic_ref); if (refcount > 0) { /* If the pkt was already placed to TX queue, let * it go as it will be released by L2 after it is * sent. */ if (k_work_pending(net_pkt_work(pkt)) || net_pkt_sent(pkt)) { refcount--; } while (refcount) { net_pkt_unref(pkt); refcount--; } } } tcp->context = NULL; key = irq_lock(); tcp->flags &= ~(NET_TCP_IN_USE | NET_TCP_RECV_MSS_SET); irq_unlock(key); NET_DBG("[%p] Disposed of TCP connection state", tcp); return 0; } static int finalize_segment(struct net_pkt *pkt) { net_pkt_cursor_init(pkt); if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) { return net_ipv4_finalize(pkt, IPPROTO_TCP); } else if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) { return net_ipv6_finalize(pkt, IPPROTO_TCP); } return -EINVAL; } static int prepare_segment(struct net_tcp *tcp, struct tcp_segment *segment, struct net_pkt *pkt, struct net_pkt **out_pkt) { NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct net_tcp_hdr); struct net_context *context = tcp->context; struct net_buf *tail = NULL; struct net_tcp_hdr *tcp_hdr; uint16_t dst_port, src_port; bool pkt_allocated; uint8_t optlen = 0U; int status; NET_ASSERT(context); if (pkt) { /* TCP transmit data comes in with a pre-allocated * net_pkt at the head (so that net_context_send can find * the context), and the data after. Rejigger so we * can insert a TCP header cleanly */ tail = pkt->buffer; pkt->buffer = NULL; pkt_allocated = false; status = net_pkt_alloc_buffer(pkt, segment->optlen, IPPROTO_TCP, ALLOC_TIMEOUT); if (status) { goto fail; } } else { pkt = net_pkt_alloc_with_buffer(net_context_get_iface(context), segment->optlen, net_context_get_family(context), IPPROTO_TCP, ALLOC_TIMEOUT); if (!pkt) { return -ENOMEM; } net_pkt_set_context(pkt, context); pkt_allocated = true; } net_pkt_set_tcp_1st_msg(pkt, true); net_pkt_set_sent(pkt, false); if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) { status = net_context_create_ipv4_new(context, pkt, net_sin_ptr(segment->src_addr)->sin_addr, &(net_sin(segment->dst_addr)->sin_addr)); if (status < 0) { goto fail; } dst_port = net_sin(segment->dst_addr)->sin_port; src_port = ((struct sockaddr_in_ptr *)&context->local)-> sin_port; } else if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) { status = net_context_create_ipv6_new(context, pkt, net_sin6_ptr(segment->src_addr)->sin6_addr, &(net_sin6(segment->dst_addr)->sin6_addr)); if (status < 0) { goto fail; } dst_port = net_sin6(segment->dst_addr)->sin6_port; src_port = ((struct sockaddr_in6_ptr *)&context->local)-> sin6_port; } else { NET_DBG("[%p] Protocol family %d not supported", tcp, net_pkt_family(pkt)); status = -EINVAL; goto fail; } tcp_hdr = (struct net_tcp_hdr *)net_pkt_get_data(pkt, &tcp_access); if (!tcp_hdr) { status = -ENOBUFS; goto fail; } if (segment->options && segment->optlen) { /* Set the length (this value is saved in 4-byte words format) */ if ((segment->optlen & 0x3u) != 0u) { optlen = (segment->optlen & 0xfffCu) + 4u; } else { optlen = segment->optlen; } } memset(tcp_hdr, 0, NET_TCPH_LEN); tcp_hdr->src_port = src_port; tcp_hdr->dst_port = dst_port; sys_put_be32(segment->seq, tcp_hdr->seq); sys_put_be32(segment->ack, tcp_hdr->ack); tcp_hdr->offset = (NET_TCPH_LEN + optlen) << 2; tcp_hdr->flags = segment->flags; sys_put_be16(segment->wnd, tcp_hdr->wnd); tcp_hdr->chksum = 0U; tcp_hdr->urg[0] = 0U; tcp_hdr->urg[1] = 0U; net_pkt_set_data(pkt, &tcp_access); if (optlen && net_pkt_write(pkt, segment->options, segment->optlen)) { goto fail; } if (tail) { net_pkt_append_buffer(pkt, tail); } status = finalize_segment(pkt); if (status < 0) { if (pkt_allocated) { net_pkt_unref(pkt); } return status; } net_tcp_trace(pkt, tcp, tcp_hdr); *out_pkt = pkt; return 0; fail: if (pkt_allocated) { net_pkt_unref(pkt); } else { net_buf_unref(pkt->buffer); pkt->buffer = tail; } return status; } uint32_t net_tcp_get_recv_wnd(const struct net_tcp *tcp) { return tcp->recv_wnd; } int net_tcp_prepare_segment(struct net_tcp *tcp, uint8_t flags, void *options, size_t optlen, const struct sockaddr_ptr *local, const struct sockaddr *remote, struct net_pkt **send_pkt) { struct tcp_segment segment = { 0 }; uint32_t seq; uint16_t wnd; int status; if (!local) { local = &tcp->context->local; } seq = tcp->send_seq; if (flags & NET_TCP_ACK) { if (net_tcp_get_state(tcp) == NET_TCP_FIN_WAIT_1) { if (flags & NET_TCP_FIN) { /* FIN is used here only to determine which * state to go to next; it's not to be used * in the sent segment. */ flags &= ~NET_TCP_FIN; net_tcp_change_state(tcp, NET_TCP_TIME_WAIT); } else { net_tcp_change_state(tcp, NET_TCP_CLOSING); } } else if (net_tcp_get_state(tcp) == NET_TCP_FIN_WAIT_2) { net_tcp_change_state(tcp, NET_TCP_TIME_WAIT); } else if (net_tcp_get_state(tcp) == NET_TCP_CLOSE_WAIT) { tcp->flags |= NET_TCP_IS_SHUTDOWN; flags |= NET_TCP_FIN; net_tcp_change_state(tcp, NET_TCP_LAST_ACK); } } if (flags & NET_TCP_FIN) { /* RFC793 says about ACK bit: "Once a connection is * established this is always sent." as teardown * happens when connection is established, it must * have ACK set. */ flags |= NET_TCP_ACK; seq++; if (net_tcp_get_state(tcp) == NET_TCP_ESTABLISHED || net_tcp_get_state(tcp) == NET_TCP_SYN_RCVD) { net_tcp_change_state(tcp, NET_TCP_FIN_WAIT_1); } } wnd = net_tcp_get_recv_wnd(tcp); segment.src_addr = (struct sockaddr_ptr *)local; segment.dst_addr = remote; segment.seq = tcp->send_seq; segment.ack = tcp->send_ack; segment.flags = flags; segment.wnd = wnd; segment.options = options; segment.optlen = optlen; status = prepare_segment(tcp, &segment, *send_pkt, send_pkt); if (status < 0) { return status; } tcp->send_seq = seq; return 0; } static inline uint32_t get_size(uint32_t pos1, uint32_t pos2) { uint32_t size; if (pos1 <= pos2) { size = pos2 - pos1; } else { size = NET_TCP_MAX_SEQ - pos1 + pos2 + 1; } return size; } #if defined(CONFIG_NET_IPV4) #ifndef NET_IP_MAX_PACKET #define NET_IP_MAX_PACKET (10 * 1024) #endif #define NET_IP_MAX_OPTIONS 40 /* Maximum option field length */ static inline size_t ip_max_packet_len(struct in_addr *dest_ip) { ARG_UNUSED(dest_ip); return (NET_IP_MAX_PACKET - (NET_IP_MAX_OPTIONS + sizeof(struct net_ipv4_hdr))) & (~0x3LU); } #else /* CONFIG_NET_IPV4 */ #define ip_max_packet_len(...) 0 #endif /* CONFIG_NET_IPV4 */ uint16_t net_tcp_get_recv_mss(const struct net_tcp *tcp) { sa_family_t family = net_context_get_family(tcp->context); if (family == AF_INET) { #if defined(CONFIG_NET_IPV4) struct net_if *iface = net_context_get_iface(tcp->context); if (iface && net_if_get_mtu(iface) >= NET_IPV4TCPH_LEN) { /* Detect MSS based on interface MTU minus "TCP,IP * header size" */ return net_if_get_mtu(iface) - NET_IPV4TCPH_LEN; } #else return 0; #endif /* CONFIG_NET_IPV4 */ } #if defined(CONFIG_NET_IPV6) else if (family == AF_INET6) { struct net_if *iface = net_context_get_iface(tcp->context); int mss = 0; if (iface && net_if_get_mtu(iface) >= NET_IPV6TCPH_LEN) { /* Detect MSS based on interface MTU minus "TCP,IP * header size" */ mss = net_if_get_mtu(iface) - NET_IPV6TCPH_LEN; } if (mss < NET_IPV6_MTU) { mss = NET_IPV6_MTU; } return mss; } #endif /* CONFIG_NET_IPV6 */ return 0; } static void net_tcp_set_syn_opt(struct net_tcp *tcp, uint8_t *options, uint8_t *optionlen) { uint32_t recv_mss; *optionlen = 0U; if (!(tcp->flags & NET_TCP_RECV_MSS_SET)) { recv_mss = net_tcp_get_recv_mss(tcp); tcp->flags |= NET_TCP_RECV_MSS_SET; } else { recv_mss = 0U; } recv_mss |= (NET_TCP_MSS_OPT << 24) | (NET_TCP_MSS_SIZE << 16); UNALIGNED_PUT(htonl(recv_mss), (uint32_t *)(options + *optionlen)); *optionlen += NET_TCP_MSS_SIZE; } int net_tcp_prepare_ack(struct net_tcp *tcp, const struct sockaddr *remote, struct net_pkt **pkt) { uint8_t options[NET_TCP_MAX_OPT_SIZE]; uint8_t optionlen; switch (net_tcp_get_state(tcp)) { case NET_TCP_SYN_RCVD: /* In the SYN_RCVD state acknowledgment must be with the * SYN flag. */ net_tcp_set_syn_opt(tcp, options, &optionlen); return net_tcp_prepare_segment(tcp, NET_TCP_SYN | NET_TCP_ACK, options, optionlen, NULL, remote, pkt); case NET_TCP_FIN_WAIT_1: case NET_TCP_LAST_ACK: /* In the FIN_WAIT_1 and LAST_ACK states acknowledgment must * be with the FIN flag. */ return net_tcp_prepare_segment(tcp, NET_TCP_FIN | NET_TCP_ACK, 0, 0, NULL, remote, pkt); default: return net_tcp_prepare_segment(tcp, NET_TCP_ACK, 0, 0, NULL, remote, pkt); } return -EINVAL; } static inline void copy_sockaddr_to_sockaddr_ptr(struct net_tcp *tcp, const struct sockaddr *local, struct sockaddr_ptr *addr) { (void)memset(addr, 0, sizeof(struct sockaddr_ptr)); #if defined(CONFIG_NET_IPV4) if (local->sa_family == AF_INET) { net_sin_ptr(addr)->sin_family = AF_INET; net_sin_ptr(addr)->sin_port = net_sin(local)->sin_port; net_sin_ptr(addr)->sin_addr = &net_sin(local)->sin_addr; } #endif #if defined(CONFIG_NET_IPV6) if (local->sa_family == AF_INET6) { net_sin6_ptr(addr)->sin6_family = AF_INET6; net_sin6_ptr(addr)->sin6_port = net_sin6(local)->sin6_port; net_sin6_ptr(addr)->sin6_addr = &net_sin6(local)->sin6_addr; } #endif } int net_tcp_prepare_reset(struct net_tcp *tcp, const struct sockaddr *local, const struct sockaddr *remote, struct net_pkt **pkt) { struct tcp_segment segment = { 0 }; int status = 0; struct sockaddr_ptr src_addr_ptr; if ((net_context_get_state(tcp->context) != NET_CONTEXT_UNCONNECTED) && (net_tcp_get_state(tcp) != NET_TCP_SYN_SENT) && (net_tcp_get_state(tcp) != NET_TCP_TIME_WAIT)) { /* Send the reset segment always with acknowledgment. */ segment.ack = tcp->send_ack; segment.flags = NET_TCP_RST | NET_TCP_ACK; segment.seq = tcp->send_seq; if (!local) { segment.src_addr = &tcp->context->local; } else { copy_sockaddr_to_sockaddr_ptr(tcp, local, &src_addr_ptr); segment.src_addr = &src_addr_ptr; } segment.dst_addr = remote; segment.wnd = 0U; segment.options = NULL; segment.optlen = 0U; status = prepare_segment(tcp, &segment, NULL, pkt); } return status; } const char *net_tcp_state_str(enum net_tcp_state state) { #if (CONFIG_NET_TCP_LOG_LEVEL >= LOG_LEVEL_DBG) || defined(CONFIG_NET_SHELL) switch (state) { case NET_TCP_CLOSED: return "CLOSED"; case NET_TCP_LISTEN: return "LISTEN"; case NET_TCP_SYN_SENT: return "SYN_SENT"; case NET_TCP_SYN_RCVD: return "SYN_RCVD"; case NET_TCP_ESTABLISHED: return "ESTABLISHED"; case NET_TCP_CLOSE_WAIT: return "CLOSE_WAIT"; case NET_TCP_LAST_ACK: return "LAST_ACK"; case NET_TCP_FIN_WAIT_1: return "FIN_WAIT_1"; case NET_TCP_FIN_WAIT_2: return "FIN_WAIT_2"; case NET_TCP_TIME_WAIT: return "TIME_WAIT"; case NET_TCP_CLOSING: return "CLOSING"; } #else ARG_UNUSED(state); #endif return ""; } int net_tcp_queue_data(struct net_context *context, struct net_pkt *pkt) { struct net_conn *conn = (struct net_conn *)context->conn_handler; size_t data_len = net_pkt_get_len(pkt); int ret; NET_DBG("[%p] Queue %p len %zd", context->tcp, pkt, data_len); if (net_context_get_state(context) != NET_CONTEXT_CONNECTED) { return -ENOTCONN; } NET_ASSERT(context->tcp); if (context->tcp->flags & NET_TCP_IS_SHUTDOWN) { return -ESHUTDOWN; } /* Set PSH on all packets, our window is so small that there's * no point in the remote side trying to finesse things and * coalesce packets. */ ret = net_tcp_prepare_segment(context->tcp, NET_TCP_PSH | NET_TCP_ACK, NULL, 0, NULL, &conn->remote_addr, &pkt); if (ret) { return ret; } context->tcp->send_seq += data_len; net_stats_update_tcp_sent(net_pkt_iface(pkt), data_len); return net_tcp_queue_pkt(context, pkt); } /* This function is the sole point of *adding* packets to tcp->sent_list, * and should remain such. */ static int net_tcp_queue_pkt(struct net_context *context, struct net_pkt *pkt) { sys_slist_append(&context->tcp->sent_list, &pkt->sent_list); /* We need to restart retry_timer if it is stopped. */ if (k_delayed_work_remaining_get(&context->tcp->retry_timer) == 0) { k_delayed_work_submit(&context->tcp->retry_timer, retry_timeout(context->tcp)); } /* Increase the ref count so that we do not lose the packet and * can resend later if needed. The pkt will be released after we * have received the ACK or the TCP stream is removed. This is only * done for non-6lo technologies that will keep the data until ACK * is received or timeout happens. */ do_ref_if_needed(context->tcp, pkt); return 0; } int net_tcp_send_pkt(struct net_pkt *pkt) { NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct net_tcp_hdr); struct net_context *ctx = net_pkt_context(pkt); struct net_tcp_hdr *tcp_hdr; bool calc_chksum = false; int ret; if (!ctx || !ctx->tcp) { NET_ERR("%scontext is not set on pkt %p", !ctx ? "" : "TCP ", pkt); return -EINVAL; } net_pkt_cursor_init(pkt); net_pkt_set_overwrite(pkt, true); if (net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt) + net_pkt_ip_opts_len(pkt))) { return -EMSGSIZE; } tcp_hdr = (struct net_tcp_hdr *)net_pkt_get_data(pkt, &tcp_access); if (!tcp_hdr) { NET_ERR("Packet %p does not contain TCP header", pkt); return -EMSGSIZE; } if (sys_get_be32(tcp_hdr->ack) != ctx->tcp->send_ack) { sys_put_be32(ctx->tcp->send_ack, tcp_hdr->ack); tcp_hdr->chksum = 0U; calc_chksum = true; } /* The data stream code always sets this flag, because * existing stacks (Linux, anyway) seem to ignore data packets * without a valid-but-already-transmitted ACK. But set it * anyway if we know we need it just to sanify edge cases. */ if (ctx->tcp->sent_ack != ctx->tcp->send_ack && (tcp_hdr->flags & NET_TCP_ACK) == 0U) { tcp_hdr->flags |= NET_TCP_ACK; tcp_hdr->chksum = 0U; calc_chksum = true; } /* As we modified the header, we need to write it back. */ net_pkt_set_data(pkt, &tcp_access); if (calc_chksum) { net_pkt_cursor_init(pkt); net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt) + net_pkt_ip_opts_len(pkt)); /* No need to get tcp_hdr again */ tcp_hdr->chksum = net_calc_chksum_tcp(pkt); net_pkt_set_data(pkt, &tcp_access); } if (tcp_hdr->flags & NET_TCP_FIN) { ctx->tcp->fin_sent = 1U; } ctx->tcp->sent_ack = ctx->tcp->send_ack; /* We must have special handling for some network technologies that * tweak the IP protocol headers during packet sending. This happens * with Bluetooth and IEEE 802.15.4 which use IPv6 header compression * (6lo) and alter the sent network packet. So in order to avoid any * corruption of the original data buffer, we must copy the sent data. * For Bluetooth, its fragmentation code will even mangle the data * part of the message so we need to copy those too. */ if (is_6lo_technology(pkt)) { struct net_pkt *new_pkt, *check_pkt; bool pkt_in_slist = false; /* * There are users of this function that don't add pkt to TCP * sent_list. (See send_ack() in net_context.c) In these cases, * we should avoid the extra 6lowpan specific buffer copy * below. */ SYS_SLIST_FOR_EACH_CONTAINER(&ctx->tcp->sent_list, check_pkt, sent_list) { if (check_pkt == pkt) { pkt_in_slist = true; break; } } if (pkt_in_slist) { new_pkt = net_pkt_clone(pkt, ALLOC_TIMEOUT); if (!new_pkt) { return -ENOMEM; } /* This function is called from net_context.c and if we * return < 0, the caller will unref the original pkt. * This would leak the new_pkt so remove it here. */ ret = net_send_data(new_pkt); if (ret < 0) { net_pkt_unref(new_pkt); } else { net_stats_update_tcp_seg_rexmit( net_pkt_iface(pkt)); net_pkt_set_sent(pkt, true); } return ret; } } ret = net_send_data(pkt); if (ret == 0) { net_pkt_set_sent(pkt, true); } return ret; } static void flush_queue(struct net_context *context) { (void)net_tcp_send_data(context, NULL, NULL); } static void restart_timer(struct net_tcp *tcp) { if (!sys_slist_is_empty(&tcp->sent_list)) { tcp->flags |= NET_TCP_RETRYING; tcp->retry_timeout_shift = 0U; k_delayed_work_submit(&tcp->retry_timer, retry_timeout(tcp)); } else if (CONFIG_NET_TCP_TIME_WAIT_DELAY != 0 && (tcp->fin_sent && tcp->fin_rcvd)) { /* We know sent_list is empty, which means if * fin_sent is true it must have been ACKd */ k_delayed_work_submit(&tcp->retry_timer, K_MSEC(CONFIG_NET_TCP_TIME_WAIT_DELAY)); net_context_ref(tcp->context); } else { k_delayed_work_cancel(&tcp->retry_timer); tcp->flags &= ~NET_TCP_RETRYING; } } int net_tcp_send_data(struct net_context *context, net_context_send_cb_t cb, void *user_data) { struct net_pkt *pkt; int ret; /* For now, just send all queued data synchronously. Need to * add window handling and retry/ACK logic. */ SYS_SLIST_FOR_EACH_CONTAINER(&context->tcp->sent_list, pkt, sent_list) { /* Do not resend packets that were sent by expire timer */ if (net_pkt_queued(pkt)) { NET_DBG("[%p] Skipping pkt %p because it was already " "sent.", context->tcp, pkt); continue; } /* If this pkt is the first one (not a resend), then we do * not need to increase the ref count as it is 1 already. * For a resent packet, the ref count is only 1 atm, and * the packet would be freed in driver if we do not increase * it here. This is only done for non-6lo technologies where * we keep the original packet (by referencing it) for possible * re-send (if ACK is not received on time). */ if (!is_6lo_technology(pkt)) { if (!net_pkt_tcp_1st_msg(pkt)) { net_pkt_ref(pkt); } } NET_DBG("[%p] Sending pkt %p (%zd bytes)", context->tcp, pkt, net_pkt_get_len(pkt)); ret = net_tcp_send_pkt(pkt); if (ret < 0) { NET_DBG("[%p] pkt %p not sent (%d)", context->tcp, pkt, ret); if (!is_6lo_technology(pkt)) { net_pkt_unref(pkt); } return ret; } net_pkt_set_queued(pkt, true); net_pkt_set_tcp_1st_msg(pkt, false); } /* Just make the callback synchronously even if it didn't * go over the wire. In theory it would be nice to track * specific ACK locations in the stream and make the * callback at that time, but there's nowhere to store the * user_data value right now. */ if (cb) { cb(context, 0, user_data); } return 0; } bool net_tcp_ack_received(struct net_context *ctx, uint32_t ack) { struct net_tcp *tcp = ctx->tcp; sys_slist_t *list = &ctx->tcp->sent_list; sys_snode_t *head; struct net_pkt *pkt; bool valid_ack = false; if (net_tcp_seq_greater(ack, ctx->tcp->send_seq)) { NET_ERR("ctx %p: ACK for unsent data", ctx); net_stats_update_tcp_seg_ackerr(net_context_get_iface(ctx)); /* RFC 793 doesn't say that invalid ack sequence is an error * in the general case, but we implement tighter checking, * and consider entire packet invalid. */ return false; } while (!sys_slist_is_empty(list)) { NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct net_tcp_hdr); struct net_tcp_hdr *tcp_hdr; uint32_t last_seq; uint32_t seq_len; head = sys_slist_peek_head(list); pkt = CONTAINER_OF(head, struct net_pkt, sent_list); net_pkt_cursor_init(pkt); net_pkt_set_overwrite(pkt, true); if (net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt) + net_pkt_ip_opts_len(pkt))) { sys_slist_remove(list, NULL, head); net_pkt_unref(pkt); continue; } tcp_hdr = (struct net_tcp_hdr *)net_pkt_get_data(pkt, &tcp_access); if (!tcp_hdr) { /* The pkt does not contain TCP header, this should * not happen. */ NET_ERR("pkt %p has no TCP header", pkt); sys_slist_remove(list, NULL, head); net_pkt_unref(pkt); continue; } net_pkt_acknowledge_data(pkt, &tcp_access); seq_len = net_pkt_remaining_data(pkt); /* Each of SYN and FIN flags are counted * as one sequence number. */ if (tcp_hdr->flags & NET_TCP_SYN) { seq_len += 1U; } if (tcp_hdr->flags & NET_TCP_FIN) { seq_len += 1U; } /* Last sequence number in this packet. */ last_seq = sys_get_be32(tcp_hdr->seq) + seq_len - 1; /* Ack number should be strictly greater to acknowledged numbers * below it. For example, ack no. 10 acknowledges all numbers up * to and including 9. */ if (!net_tcp_seq_greater(ack, last_seq)) { break; } if (tcp_hdr->flags & NET_TCP_FIN) { enum net_tcp_state s = net_tcp_get_state(tcp); if (s == NET_TCP_FIN_WAIT_1) { net_tcp_change_state(tcp, NET_TCP_FIN_WAIT_2); } else if (s == NET_TCP_CLOSING) { net_tcp_change_state(tcp, NET_TCP_TIME_WAIT); } } NET_DBG("[%p] Received ACK pkt %p (len %zd bytes)", ctx->tcp, pkt, net_pkt_get_len(pkt)); sys_slist_remove(list, NULL, head); /* If we receive a valid ACK, then we need to undo the ref * set in net_tcp_queue_pkt() (when using non-6lo technology) * or the ref set in packet creation (for 6lo packet) in order * to release the pkt. */ net_pkt_set_sent(pkt, false); net_pkt_unref(pkt); valid_ack = true; } /* Restart the timer (if needed) on a valid inbound ACK. This isn't * quite the same behavior as per-packet retry timers, but is close in * practice (it starts retries one timer period after the connection * "got stuck") and avoids the need to track per-packet timers or * sent times. */ if (valid_ack) { restart_timer(ctx->tcp); /* Flush anything pending. This is important as if there * is FIN waiting in the queue, it gets sent asap. */ flush_queue(ctx); } return true; } void net_tcp_init(void) { } #if CONFIG_NET_TCP_LOG_LEVEL >= LOG_LEVEL_DBG static void validate_state_transition(enum net_tcp_state current, enum net_tcp_state new) { static const uint16_t valid_transitions[] = { [NET_TCP_CLOSED] = 1 << NET_TCP_LISTEN | 1 << NET_TCP_SYN_SENT | /* Initial transition from closed->established when * socket is accepted. */ 1 << NET_TCP_ESTABLISHED, [NET_TCP_LISTEN] = 1 << NET_TCP_SYN_RCVD | 1 << NET_TCP_SYN_SENT | 1 << NET_TCP_CLOSED, [NET_TCP_SYN_RCVD] = 1 << NET_TCP_FIN_WAIT_1 | 1 << NET_TCP_ESTABLISHED | 1 << NET_TCP_LISTEN | 1 << NET_TCP_CLOSED, [NET_TCP_SYN_SENT] = 1 << NET_TCP_CLOSED | 1 << NET_TCP_ESTABLISHED | 1 << NET_TCP_SYN_RCVD | 1 << NET_TCP_CLOSED, [NET_TCP_ESTABLISHED] = 1 << NET_TCP_CLOSE_WAIT | 1 << NET_TCP_FIN_WAIT_1 | 1 << NET_TCP_CLOSED, [NET_TCP_CLOSE_WAIT] = 1 << NET_TCP_LAST_ACK | 1 << NET_TCP_CLOSED, [NET_TCP_LAST_ACK] = 1 << NET_TCP_CLOSED, [NET_TCP_FIN_WAIT_1] = 1 << NET_TCP_CLOSING | 1 << NET_TCP_FIN_WAIT_2 | 1 << NET_TCP_TIME_WAIT | 1 << NET_TCP_CLOSED, [NET_TCP_FIN_WAIT_2] = 1 << NET_TCP_TIME_WAIT | 1 << NET_TCP_CLOSED, [NET_TCP_CLOSING] = 1 << NET_TCP_TIME_WAIT | 1 << NET_TCP_CLOSED, [NET_TCP_TIME_WAIT] = 1 << NET_TCP_CLOSED }; if (!(valid_transitions[current] & 1 << new)) { NET_DBG("Invalid state transition: %s (%d) => %s (%d)", net_tcp_state_str(current), current, net_tcp_state_str(new), new); } } #else static inline void validate_state_transition(enum net_tcp_state current, enum net_tcp_state new) { ARG_UNUSED(current); ARG_UNUSED(new); } #endif void net_tcp_change_state(struct net_tcp *tcp, enum net_tcp_state new_state) { NET_ASSERT(tcp); if (net_tcp_get_state(tcp) == new_state) { return; } NET_ASSERT(new_state >= NET_TCP_CLOSED && new_state <= NET_TCP_CLOSING); NET_DBG("[%p] state %s (%d) => %s (%d)", tcp, net_tcp_state_str(tcp->state), tcp->state, net_tcp_state_str(new_state), new_state); validate_state_transition(tcp->state, new_state); tcp->state = new_state; if (net_tcp_get_state(tcp) != NET_TCP_CLOSED) { return; } if (!tcp->context) { return; } /* Remove any port handlers if we are closing */ if (tcp->context->conn_handler) { net_tcp_unregister(tcp->context->conn_handler); tcp->context->conn_handler = NULL; } if (tcp->accept_cb) { tcp->accept_cb(tcp->context, &tcp->context->remote, sizeof(struct sockaddr), -ENETRESET, tcp->context->user_data); } } void net_tcp_foreach(net_tcp_cb_t cb, void *user_data) { int i, key; key = irq_lock(); for (i = 0; i < NET_MAX_TCP_CONTEXT; i++) { if (!net_tcp_is_used(&tcp_context[i])) { continue; } irq_unlock(key); cb(&tcp_context[i], user_data); key = irq_lock(); } irq_unlock(key); } bool net_tcp_validate_seq(struct net_tcp *tcp, struct net_tcp_hdr *tcp_hdr) { return (net_tcp_seq_cmp(sys_get_be32(tcp_hdr->seq), tcp->send_ack) >= 0) && (net_tcp_seq_cmp(sys_get_be32(tcp_hdr->seq), tcp->send_ack + net_tcp_get_recv_wnd(tcp)) < 0); } int net_tcp_finalize(struct net_pkt *pkt) { NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct net_tcp_hdr); struct net_tcp_hdr *tcp_hdr; tcp_hdr = (struct net_tcp_hdr *)net_pkt_get_data(pkt, &tcp_access); if (!tcp_hdr) { return -ENOBUFS; } tcp_hdr->chksum = 0U; if (net_if_need_calc_tx_checksum(net_pkt_iface(pkt))) { tcp_hdr->chksum = net_calc_chksum_tcp(pkt); } return net_pkt_set_data(pkt, &tcp_access); } int net_tcp_parse_opts(struct net_pkt *pkt, int opt_totlen, struct net_tcp_options *opts) { uint8_t opt, optlen; while (opt_totlen) { if (net_pkt_read_u8(pkt, &opt)) { optlen = 0U; goto error; } opt_totlen--; /* https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml#tcp-parameters-1 */ /* "Options 0 and 1 are exactly one octet which is their * kind field. All other options have their one octet * kind field, followed by a one octet length field, * followed by length-2 octets of option data." */ if (opt == NET_TCP_END_OPT) { break; } else if (opt == NET_TCP_NOP_OPT) { continue; } if (!opt_totlen) { optlen = 0U; goto error; } if (net_pkt_read_u8(pkt, &optlen) || optlen < 2) { goto error; } opt_totlen--; /* Subtract opt/optlen size now to avoid doing this * repeatedly. */ optlen -= 2U; if (opt_totlen < optlen) { goto error; } switch (opt) { case NET_TCP_MSS_OPT: if (optlen != 2U) { goto error; } if (net_pkt_read_be16(pkt, &opts->mss)) { goto error; } break; default: if (net_pkt_skip(pkt, optlen)) { goto error; } break; } opt_totlen -= optlen; } return 0; error: NET_ERR("Invalid TCP opt: %d len: %d", opt, optlen); return -EINVAL; } int net_tcp_recv(struct net_context *context, net_context_recv_cb_t cb, void *user_data) { NET_ASSERT(context->tcp); if (context->tcp->flags & NET_TCP_IS_SHUTDOWN) { return -ESHUTDOWN; } else if (net_context_get_state(context) != NET_CONTEXT_CONNECTED) { return -ENOTCONN; } context->recv_cb = cb; context->tcp->recv_user_data = user_data; return 0; } static void queue_fin(struct net_context *ctx) { struct net_pkt *pkt = NULL; bool flush = false; int ret; ret = net_tcp_prepare_segment(ctx->tcp, NET_TCP_FIN, NULL, 0, NULL, &ctx->remote, &pkt); if (ret || !pkt) { return; } if (sys_slist_is_empty(&ctx->tcp->sent_list)) { flush = true; } net_tcp_queue_pkt(ctx, pkt); if (flush) { flush_queue(ctx); } } int net_tcp_put(struct net_context *context) { if (net_context_get_ip_proto(context) == IPPROTO_TCP) { if (net_context_get_state(context) == NET_CONTEXT_CONNECTED && context->tcp && !context->tcp->fin_rcvd) { NET_DBG("TCP connection in active close, not " "disposing yet (waiting %dms)", FIN_TIMEOUT_MS); k_delayed_work_submit(&context->tcp->fin_timer, FIN_TIMEOUT); queue_fin(context); return 0; } /* A listening context is only used to establish connections. * Since once the connection is established it is not handled * directly by the listening context but rather by the child it * spawned, it is not needed to send FIN when closing such * contexts. */ if (context->tcp && net_context_get_state(context) == NET_CONTEXT_LISTENING) { net_context_unref(context); return 0; } if (context->tcp && net_tcp_get_state(context->tcp) == NET_TCP_SYN_SENT) { net_context_unref(context); } return -ENOTCONN; } return -EOPNOTSUPP; } int net_tcp_listen(struct net_context *context) { if (net_context_get_ip_proto(context) == IPPROTO_TCP) { net_tcp_change_state(context->tcp, NET_TCP_LISTEN); net_context_set_state(context, NET_CONTEXT_LISTENING); return 0; } return -EOPNOTSUPP; } int net_tcp_update_recv_wnd(struct net_context *context, int32_t delta) { int32_t new_win; if (!context->tcp) { NET_ERR("context->tcp == NULL"); return -EPROTOTYPE; } new_win = context->tcp->recv_wnd + delta; if (new_win < 0 || new_win > UINT16_MAX) { return -EINVAL; } context->tcp->recv_wnd = new_win; return 0; } static int send_reset(struct net_context *context, struct sockaddr *local, struct sockaddr *remote); static void backlog_ack_timeout(struct k_work *work) { struct tcp_backlog_entry *backlog = CONTAINER_OF(work, struct tcp_backlog_entry, ack_timer); NET_DBG("Did not receive ACK in %dms", ACK_TIMEOUT_MS); /* TODO: If net_context is bound to unspecified IPv6 address * and some port number, local address is not available. * RST packet might be invalid. Cache local address * and use it in RST message preparation. */ send_reset(backlog->tcp->context, NULL, &backlog->remote); (void)memset(backlog, 0, sizeof(struct tcp_backlog_entry)); } static void tcp_copy_ip_addr_from_hdr(sa_family_t family, union net_ip_header *ip_hdr, struct net_tcp_hdr *tcp_hdr, struct sockaddr *addr, bool is_src_addr) { uint16_t port; if (is_src_addr) { port = tcp_hdr->src_port; } else { port = tcp_hdr->dst_port; } if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { struct sockaddr_in *addr4 = net_sin(addr); if (is_src_addr) { net_ipaddr_copy(&addr4->sin_addr, &ip_hdr->ipv4->src); } else { net_ipaddr_copy(&addr4->sin_addr, &ip_hdr->ipv4->dst); } addr4->sin_port = port; addr->sa_family = AF_INET; } if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { struct sockaddr_in6 *addr6 = net_sin6(addr); if (is_src_addr) { net_ipaddr_copy(&addr6->sin6_addr, &ip_hdr->ipv6->src); } else { net_ipaddr_copy(&addr6->sin6_addr, &ip_hdr->ipv6->dst); } addr6->sin6_port = port; addr->sa_family = AF_INET6; } } static int tcp_backlog_find(struct net_pkt *pkt, union net_ip_header *ip_hdr, struct net_tcp_hdr *tcp_hdr, int *empty_slot) { int i, empty = -1; for (i = 0; i < CONFIG_NET_TCP_BACKLOG_SIZE; i++) { if (tcp_backlog[i].tcp == NULL && empty < 0) { empty = i; continue; } if (net_pkt_family(pkt) != tcp_backlog[i].remote.sa_family) { continue; } if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) { if (net_sin(&tcp_backlog[i].remote)->sin_port != tcp_hdr->src_port) { continue; } if (memcmp(&net_sin(&tcp_backlog[i].remote)->sin_addr, &ip_hdr->ipv4->src, sizeof(struct in_addr))) { continue; } } else if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) { if (net_sin6(&tcp_backlog[i].remote)->sin6_port != tcp_hdr->src_port) { continue; } if (memcmp(&net_sin6(&tcp_backlog[i].remote)->sin6_addr, &ip_hdr->ipv6->src, sizeof(struct in6_addr))) { continue; } } return i; } if (empty_slot) { *empty_slot = empty; } return -EADDRNOTAVAIL; } static int tcp_backlog_syn(struct net_pkt *pkt, union net_ip_header *ip_hdr, struct net_tcp_hdr *tcp_hdr, struct net_context *context, uint16_t send_mss) { int empty_slot = -1; if (tcp_backlog_find(pkt, ip_hdr, tcp_hdr, &empty_slot) >= 0) { return -EADDRINUSE; } if (empty_slot < 0) { return -ENOSPC; } tcp_backlog[empty_slot].tcp = context->tcp; tcp_copy_ip_addr_from_hdr(net_pkt_family(pkt), ip_hdr, tcp_hdr, &tcp_backlog[empty_slot].remote, true); tcp_backlog[empty_slot].send_seq = context->tcp->send_seq; tcp_backlog[empty_slot].send_ack = context->tcp->send_ack; tcp_backlog[empty_slot].send_mss = send_mss; k_delayed_work_init(&tcp_backlog[empty_slot].ack_timer, backlog_ack_timeout); k_delayed_work_submit(&tcp_backlog[empty_slot].ack_timer, ACK_TIMEOUT); return 0; } static int tcp_backlog_ack(struct net_pkt *pkt, union net_ip_header *ip_hdr, struct net_tcp_hdr *tcp_hdr, struct net_context *context) { int r; r = tcp_backlog_find(pkt, ip_hdr, tcp_hdr, NULL); if (r < 0) { return r; } /* Sent SEQ + 1 needs to be the same as the received ACK */ if (tcp_backlog[r].send_seq + 1 != sys_get_be32(tcp_hdr->ack)) { return -EINVAL; } memcpy(&context->remote, &tcp_backlog[r].remote, sizeof(struct sockaddr)); context->tcp->send_seq = tcp_backlog[r].send_seq + 1; context->tcp->send_ack = tcp_backlog[r].send_ack; context->tcp->send_mss = tcp_backlog[r].send_mss; k_delayed_work_cancel(&tcp_backlog[r].ack_timer); (void)memset(&tcp_backlog[r], 0, sizeof(struct tcp_backlog_entry)); return 0; } static int tcp_backlog_rst(struct net_pkt *pkt, union net_ip_header *ip_hdr, struct net_tcp_hdr *tcp_hdr) { int r; r = tcp_backlog_find(pkt, ip_hdr, tcp_hdr, NULL); if (r < 0) { return r; } /* The ACK sent needs to be the same as the received SEQ */ if (tcp_backlog[r].send_ack != sys_get_be32(tcp_hdr->seq)) { return -EINVAL; } k_delayed_work_cancel(&tcp_backlog[r].ack_timer); (void)memset(&tcp_backlog[r], 0, sizeof(struct tcp_backlog_entry)); return 0; } static void handle_fin_timeout(struct k_work *work) { struct net_tcp *tcp = CONTAINER_OF(work, struct net_tcp, fin_timer); NET_DBG("Did not receive FIN in %dms", FIN_TIMEOUT_MS); net_context_unref(tcp->context); } static void handle_ack_timeout(struct k_work *work) { /* This means that we did not receive ACK response in time. */ struct net_tcp *tcp = CONTAINER_OF(work, struct net_tcp, ack_timer); NET_DBG("Did not receive ACK in %dms while in %s", ACK_TIMEOUT_MS, net_tcp_state_str(net_tcp_get_state(tcp))); if (net_tcp_get_state(tcp) == NET_TCP_LAST_ACK) { /* We did not receive the last ACK on time. We can only * close the connection at this point. We will not send * anything to peer in this last state, but will go directly * to to CLOSED state. */ net_tcp_change_state(tcp, NET_TCP_CLOSED); if (tcp->context->recv_cb) { tcp->context->recv_cb(tcp->context, NULL, NULL, NULL, 0, tcp->recv_user_data); } net_context_unref(tcp->context); } } static void handle_timewait_timeout(struct k_work *work) { struct net_tcp *tcp = CONTAINER_OF(work, struct net_tcp, timewait_timer); NET_DBG("Timewait expired in %dms", CONFIG_NET_TCP_TIME_WAIT_DELAY); if (net_tcp_get_state(tcp) == NET_TCP_TIME_WAIT) { net_tcp_change_state(tcp, NET_TCP_CLOSED); if (tcp->context->recv_cb) { tcp->context->recv_cb(tcp->context, NULL, NULL, NULL, 0, tcp->recv_user_data); } net_context_unref(tcp->context); } } int net_tcp_get(struct net_context *context) { context->tcp = net_tcp_alloc(context); if (!context->tcp) { NET_ASSERT(context->tcp, "Cannot allocate TCP context"); return -ENOBUFS; } k_delayed_work_init(&context->tcp->ack_timer, handle_ack_timeout); k_delayed_work_init(&context->tcp->fin_timer, handle_fin_timeout); k_delayed_work_init(&context->tcp->timewait_timer, handle_timewait_timeout); return 0; } int net_tcp_unref(struct net_context *context) { int i; if (!context->tcp) return 0; /* Clear the backlog for this TCP context. */ for (i = 0; i < CONFIG_NET_TCP_BACKLOG_SIZE; i++) { if (tcp_backlog[i].tcp != context->tcp) { continue; } k_delayed_work_cancel(&tcp_backlog[i].ack_timer); (void)memset(&tcp_backlog[i], 0, sizeof(tcp_backlog[i])); } net_tcp_release(context->tcp); context->tcp = NULL; return 0; } /** **/ #define net_tcp_print_recv_info(str, pkt, port) \ if (IS_ENABLED(CONFIG_NET_TCP_LOG_LEVEL_DBG)) { \ if (net_pkt_family(pkt) == AF_INET6) { \ NET_DBG("%s received from %s port %d", str, \ log_strdup(net_sprint_ipv6_addr( \ &NET_IPV6_HDR(pkt)->src)), \ ntohs(port)); \ } else if (net_pkt_family(pkt) == AF_INET) {\ NET_DBG("%s received from %s port %d", str, \ log_strdup(net_sprint_ipv4_addr( \ &NET_IPV4_HDR(pkt)->src)), \ ntohs(port)); \ } \ } #define net_tcp_print_send_info(str, pkt, port) \ if (IS_ENABLED(CONFIG_NET_TCP_LOG_LEVEL_DBG)) { \ if (net_pkt_family(pkt) == AF_INET6) { \ NET_DBG("%s sent to %s port %d", str, \ log_strdup(net_sprint_ipv6_addr( \ &NET_IPV6_HDR(pkt)->dst)), \ ntohs(port)); \ } else if (net_pkt_family(pkt) == AF_INET) { \ NET_DBG("%s sent to %s port %d", str, \ log_strdup(net_sprint_ipv4_addr( \ &NET_IPV4_HDR(pkt)->dst)), \ ntohs(port)); \ } \ } static void print_send_info(struct net_pkt *pkt, const char *msg, const struct sockaddr *remote) { if (CONFIG_NET_TCP_LOG_LEVEL >= LOG_LEVEL_DBG) { uint16_t port = 0U; if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) { struct sockaddr_in *addr4 = net_sin(remote); port = addr4->sin_port; } if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) { struct sockaddr_in6 *addr6 = net_sin6(remote); port = addr6->sin6_port; } net_tcp_print_send_info(msg, pkt, port); } } /* Send SYN or SYN/ACK. */ static inline int send_syn_segment(struct net_context *context, const struct sockaddr_ptr *local, const struct sockaddr *remote, int flags, const char *msg) { struct net_pkt *pkt = NULL; int ret; uint8_t options[NET_TCP_MAX_OPT_SIZE]; uint8_t optionlen = 0U; if (flags == NET_TCP_SYN) { net_tcp_set_syn_opt(context->tcp, options, &optionlen); } ret = net_tcp_prepare_segment(context->tcp, flags, options, optionlen, local, remote, &pkt); if (ret) { return ret; } print_send_info(pkt, msg, remote); ret = net_send_data(pkt); if (ret < 0) { net_pkt_unref(pkt); return ret; } net_pkt_set_sent(pkt, true); context->tcp->send_seq++; return ret; } static inline int send_syn(struct net_context *context, const struct sockaddr *remote) { net_tcp_change_state(context->tcp, NET_TCP_SYN_SENT); return send_syn_segment(context, NULL, remote, NET_TCP_SYN, "SYN"); } static inline int send_syn_ack(struct net_context *context, struct sockaddr_ptr *local, struct sockaddr *remote) { return send_syn_segment(context, local, remote, NET_TCP_SYN | NET_TCP_ACK, "SYN_ACK"); } static int send_ack(struct net_context *context, struct sockaddr *remote, bool force) { struct net_pkt *pkt = NULL; int ret; /* Something (e.g. a data transmission under the user * callback) already sent the ACK, no need */ if (!force && context->tcp->send_ack == context->tcp->sent_ack) { return 0; } ret = net_tcp_prepare_ack(context->tcp, remote, &pkt); if (ret) { return ret; } print_send_info(pkt, "ACK", remote); ret = net_tcp_send_pkt(pkt); if (ret < 0) { net_pkt_unref(pkt); } return ret; } static int send_reset(struct net_context *context, struct sockaddr *local, struct sockaddr *remote) { struct net_pkt *pkt = NULL; int ret; ret = net_tcp_prepare_reset(context->tcp, local, remote, &pkt); if (ret || !pkt) { return ret; } print_send_info(pkt, "RST", remote); ret = net_send_data(pkt); if (ret < 0) { net_pkt_unref(pkt); } net_pkt_set_sent(pkt, true); return ret; } static uint16_t adjust_data_len(struct net_pkt *pkt, struct net_tcp_hdr *tcp_hdr, uint16_t data_len) { uint8_t offset = tcp_hdr->offset >> 4; /* We need to adjust the length of the data part if there * are TCP options. */ if ((offset << 2) > sizeof(struct net_tcp_hdr)) { net_pkt_skip(pkt, (offset << 2) - sizeof(struct net_tcp_hdr)); data_len -= (offset << 2) - sizeof(struct net_tcp_hdr); } return data_len; } /* This is called when we receive data after the connection has been * established. The core TCP logic is located here. * * Prototype: * enum net_verdict tcp_established(struct net_conn *conn, * union net_ip_header *ip_hdr, * union net_proto_header *proto_hdr, * struct net_pkt *pkt, * void *user_data) */ NET_CONN_CB(tcp_established) { struct net_context *context = (struct net_context *)user_data; struct net_tcp_hdr *tcp_hdr = proto_hdr->tcp; enum net_verdict ret = NET_OK; bool do_not_send_ack = false; uint8_t tcp_flags; uint16_t data_len; k_mutex_lock(&context->lock, K_FOREVER); NET_ASSERT(context && context->tcp); if (net_tcp_get_state(context->tcp) < NET_TCP_ESTABLISHED) { NET_ERR("Context %p in wrong state %d", context, net_tcp_get_state(context->tcp)); ret = NET_DROP; goto unlock; } net_tcp_print_recv_info("DATA", pkt, tcp_hdr->src_port); tcp_flags = NET_TCP_FLAGS(tcp_hdr); if (net_tcp_seq_cmp(sys_get_be32(tcp_hdr->seq), context->tcp->send_ack) < 0) { /* Peer sent us packet we've already seen. Apparently, * our ack was lost. */ /* RFC793 specifies that "highest" (i.e. current from our PoV) * ack # value can/should be sent, so we just force resend. */ resend_ack: send_ack(context, &conn->remote_addr, true); ret = NET_DROP; goto unlock; } if (net_tcp_seq_cmp(sys_get_be32(tcp_hdr->seq), context->tcp->send_ack) > 0) { /* Don't try to reorder packets. If it doesn't * match the next segment exactly, drop and wait for * retransmit */ ret = NET_DROP; goto unlock; } /* * If we receive RST here, we close the socket. See RFC 793 chapter * called "Reset Processing" for details. */ if (tcp_flags & NET_TCP_RST) { /* We only accept RST packet that has valid seq field. */ if (!net_tcp_validate_seq(context->tcp, tcp_hdr)) { net_stats_update_tcp_seg_rsterr(net_pkt_iface(pkt)); ret = NET_DROP; goto unlock; } net_stats_update_tcp_seg_rst(net_pkt_iface(pkt)); net_tcp_print_recv_info("RST", pkt, tcp_hdr->src_port); if (context->recv_cb) { context->recv_cb(context, NULL, NULL, NULL, -ECONNRESET, context->tcp->recv_user_data); } net_context_unref(context); ret = NET_DROP; goto unlock; } /* Handle TCP state transition */ if (tcp_flags & NET_TCP_ACK) { if (!net_tcp_ack_received(context, sys_get_be32(tcp_hdr->ack))) { ret = NET_DROP; goto unlock; } /* TCP state might be changed after maintaining the sent pkt * list, e.g., an ack of FIN is received. */ if (net_tcp_get_state(context->tcp) == NET_TCP_FIN_WAIT_1) { /* Active close: step to FIN_WAIT_2 */ net_tcp_change_state(context->tcp, NET_TCP_FIN_WAIT_2); } else if (net_tcp_get_state(context->tcp) == NET_TCP_LAST_ACK) { /* Passive close: step to CLOSED */ net_tcp_change_state(context->tcp, NET_TCP_CLOSED); /* Release the pkt before clean up */ net_pkt_unref(pkt); goto clean_up; } } if (tcp_flags & NET_TCP_FIN) { if (net_tcp_get_state(context->tcp) == NET_TCP_ESTABLISHED) { /* Passive close: step to CLOSE_WAIT */ net_tcp_change_state(context->tcp, NET_TCP_CLOSE_WAIT); /* We should receive ACK next in order to get rid of * LAST_ACK state that we are entering in a short while. * But we need to be prepared to NOT to receive it as * otherwise the connection would be stuck forever. */ k_delayed_work_submit(&context->tcp->ack_timer, ACK_TIMEOUT); net_context_set_closing(context, true); } else if (net_tcp_get_state(context->tcp) == NET_TCP_FIN_WAIT_2) { /* Received FIN on FIN_WAIT_2, so cancel the timer */ k_delayed_work_cancel(&context->tcp->fin_timer); /* Active close: step to TIME_WAIT */ net_tcp_change_state(context->tcp, NET_TCP_TIME_WAIT); } context->tcp->fin_rcvd = 1U; } if (!IS_ENABLED(CONFIG_NET_TCP_AUTO_ACCEPT) && net_context_is_accepting(context)) { data_len = 0; do_not_send_ack = true; } else { data_len = net_pkt_remaining_data(pkt); } if (data_len > net_tcp_get_recv_wnd(context->tcp)) { /* In case we have zero window, we should still accept * Zero Window Probes from peer, which per convention * come with len=1. Note that normally we need to check * for net_tcp_get_recv_wnd(context->tcp) == 0, but * given the if above, we know that if data_len == 1, * then net_tcp_get_recv_wnd(context->tcp) can be only 0 * here. */ if (data_len == 1U) { goto resend_ack; } NET_ERR("Context %p: overflow of recv window (%d vs %d), " "pkt dropped", context, net_tcp_get_recv_wnd(context->tcp), data_len); ret = NET_DROP; goto unlock; } /* If the pkt has data, notify the recv callback which should * release the pkt. Otherwise, release the pkt immediately. */ if (data_len > 0) { data_len = adjust_data_len(pkt, tcp_hdr, data_len); ret = net_context_packet_received(conn, pkt, ip_hdr, proto_hdr, context->tcp->recv_user_data); } else if (data_len == 0U) { net_pkt_unref(pkt); } if (do_not_send_ack == false) { /* Increment the ack */ context->tcp->send_ack += data_len; if (tcp_flags & NET_TCP_FIN) { context->tcp->send_ack += 1U; } send_ack(context, &conn->remote_addr, false); } clean_up: if (net_tcp_get_state(context->tcp) == NET_TCP_TIME_WAIT) { k_delayed_work_submit(&context->tcp->timewait_timer, K_MSEC(CONFIG_NET_TCP_TIME_WAIT_DELAY)); } if (net_tcp_get_state(context->tcp) == NET_TCP_CLOSED) { if (context->recv_cb) { context->recv_cb(context, NULL, NULL, NULL, 0, context->tcp->recv_user_data); } net_context_unref(context); } unlock: k_mutex_unlock(&context->lock); return ret; } /* * Prototype: * enum net_verdict tcp_synack_received(struct net_conn *conn, * struct net_pkt *pkt, * union net_ip_header *ip_hdr, * union net_proto_header *proto_hdr, * void *user_data) */ NET_CONN_CB(tcp_synack_received) { struct net_context *context = (struct net_context *)user_data; struct net_tcp_hdr *tcp_hdr = proto_hdr->tcp; int ret; NET_ASSERT(context && context->tcp); switch (net_tcp_get_state(context->tcp)) { case NET_TCP_SYN_SENT: net_context_set_iface(context, net_pkt_iface(pkt)); break; default: NET_DBG("Context %p in wrong state %d", context, net_tcp_get_state(context->tcp)); return NET_DROP; } net_pkt_set_context(pkt, context); NET_ASSERT(net_pkt_iface(pkt)); if (NET_TCP_FLAGS(tcp_hdr) & NET_TCP_RST) { /* We only accept RST packet that has valid seq field. */ if (!net_tcp_validate_seq(context->tcp, tcp_hdr)) { net_stats_update_tcp_seg_rsterr(net_pkt_iface(pkt)); return NET_DROP; } net_stats_update_tcp_seg_rst(net_pkt_iface(pkt)); k_sem_give(&context->tcp->connect_wait); if (context->connect_cb) { context->connect_cb(context, -ECONNREFUSED, context->user_data); } return NET_DROP; } if (NET_TCP_FLAGS(tcp_hdr) & NET_TCP_SYN) { context->tcp->send_ack = sys_get_be32(tcp_hdr->seq) + 1; } /* * If we receive SYN, we send SYN-ACK and go to SYN_RCVD state. */ if (NET_TCP_FLAGS(tcp_hdr) == (NET_TCP_SYN | NET_TCP_ACK)) { /* Remove the temporary connection handler and register * a proper now as we have an established connection. */ struct sockaddr local_addr; struct sockaddr remote_addr; tcp_copy_ip_addr_from_hdr(net_pkt_family(pkt), ip_hdr, tcp_hdr, &remote_addr, true); tcp_copy_ip_addr_from_hdr(net_pkt_family(pkt), ip_hdr, tcp_hdr, &local_addr, false); net_tcp_unregister(context->conn_handler); ret = net_tcp_register(net_pkt_family(pkt), &remote_addr, &local_addr, ntohs(tcp_hdr->src_port), ntohs(tcp_hdr->dst_port), tcp_established, context, &context->conn_handler); if (ret < 0) { NET_DBG("Cannot register TCP handler (%d)", ret); send_reset(context, &local_addr, &remote_addr); return NET_DROP; } net_tcp_change_state(context->tcp, NET_TCP_ESTABLISHED); net_context_set_state(context, NET_CONTEXT_CONNECTED); send_ack(context, &remote_addr, false); k_sem_give(&context->tcp->connect_wait); if (context->connect_cb) { context->connect_cb(context, 0, context->user_data); } } return NET_DROP; } static void get_sockaddr_ptr(union net_ip_header *ip_hdr, struct net_tcp_hdr *tcp_hdr, sa_family_t family, struct sockaddr_ptr *addr) { (void)memset(addr, 0, sizeof(*addr)); if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { struct sockaddr_in_ptr *addr4 = net_sin_ptr(addr); addr4->sin_family = AF_INET; addr4->sin_port = tcp_hdr->dst_port; addr4->sin_addr = &ip_hdr->ipv4->dst; } if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { struct sockaddr_in6_ptr *addr6 = net_sin6_ptr(addr); addr6->sin6_family = AF_INET6; addr6->sin6_port = tcp_hdr->dst_port; addr6->sin6_addr = &ip_hdr->ipv6->dst; } } #if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) static inline void copy_pool_vars(struct net_context *new_context, struct net_context *listen_context) { new_context->tx_slab = listen_context->tx_slab; new_context->data_pool = listen_context->data_pool; } #else #define copy_pool_vars(...) #endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */ /* This callback is called when we are waiting connections and we receive * a packet. We need to check if we are receiving proper msg (SYN) here. * The ACK could also be received, in which case we have an established * connection. * * Prototype: * enum net_verdict tcp_syn_rcvd(struct net_conn *conn, * struct net_pkt *pkt, * union net_ip_header *ip_hdr, * union net_proto_header *proto_hdr, * void *user_data) */ NET_CONN_CB(tcp_syn_rcvd) { struct net_context *context = (struct net_context *)user_data; struct net_tcp_hdr *tcp_hdr = proto_hdr->tcp; struct net_tcp *tcp; struct sockaddr_ptr pkt_src_addr; struct sockaddr local_addr; struct sockaddr remote_addr; NET_ASSERT(context && context->tcp); tcp = context->tcp; switch (net_tcp_get_state(tcp)) { case NET_TCP_LISTEN: net_context_set_iface(context, net_pkt_iface(pkt)); break; case NET_TCP_SYN_RCVD: if (net_pkt_iface(pkt) != net_context_get_iface(context)) { return NET_DROP; } break; default: NET_DBG("Context %p in wrong state %d", context, tcp->state); return NET_DROP; } net_pkt_set_context(pkt, context); NET_ASSERT(net_pkt_iface(pkt)); tcp_copy_ip_addr_from_hdr(net_pkt_family(pkt), ip_hdr, tcp_hdr, &remote_addr, true); tcp_copy_ip_addr_from_hdr(net_pkt_family(pkt), ip_hdr, tcp_hdr, &local_addr, false); /* * If we receive SYN, we send SYN-ACK and go to SYN_RCVD state. */ if (NET_TCP_FLAGS(tcp_hdr) == NET_TCP_SYN) { struct net_tcp_options tcp_opts = { .mss = NET_TCP_DEFAULT_MSS, }; int opt_totlen; int r; net_tcp_print_recv_info("SYN", pkt, tcp_hdr->src_port); opt_totlen = NET_TCP_HDR_LEN(tcp_hdr) - sizeof(struct net_tcp_hdr); /* We expect MSS option to be present (opt_totlen > 0), * so call unconditionally. */ if (net_tcp_parse_opts(pkt, opt_totlen, &tcp_opts) < 0) { return NET_DROP; } net_tcp_change_state(tcp, NET_TCP_SYN_RCVD); /* Set TCP seq and ack which are then stored in the backlog */ context->tcp->send_seq = tcp_init_isn(); context->tcp->send_ack = sys_get_be32(tcp_hdr->seq) + 1; /* Get MSS from TCP options here*/ r = tcp_backlog_syn(pkt, ip_hdr, tcp_hdr, context, tcp_opts.mss); if (r < 0) { if (r == -EADDRINUSE) { NET_DBG("TCP connection already exists"); } else { NET_DBG("No free TCP backlog entries"); } return NET_DROP; } get_sockaddr_ptr(ip_hdr, tcp_hdr, net_context_get_family(context), &pkt_src_addr); send_syn_ack(context, &pkt_src_addr, &remote_addr); net_pkt_unref(pkt); return NET_OK; } /* * See RFC 793 chapter 3.4 "Reset Processing" and RFC 793, page 65 * for more details. */ if (NET_TCP_FLAGS(tcp_hdr) & NET_TCP_RST) { if (tcp_backlog_rst(pkt, ip_hdr, tcp_hdr) < 0) { net_stats_update_tcp_seg_rsterr(net_pkt_iface(pkt)); return NET_DROP; } net_stats_update_tcp_seg_rst(net_pkt_iface(pkt)); net_tcp_print_recv_info("RST", pkt, tcp_hdr->src_port); return NET_DROP; } /* * If we receive ACK, we go to ESTABLISHED state. */ if (NET_TCP_FLAGS(tcp_hdr) & NET_TCP_ACK) { struct net_context *new_context; socklen_t addrlen; int ret; net_tcp_print_recv_info("ACK", pkt, tcp_hdr->src_port); if (!context->tcp->accept_cb) { NET_DBG("No accept callback, connection reset."); goto reset; } /* We create a new context that starts to wait data. */ ret = net_context_get(net_pkt_family(pkt), SOCK_STREAM, IPPROTO_TCP, &new_context); if (ret < 0) { NET_DBG("Cannot get accepted context, " "connection reset"); goto conndrop; } ret = tcp_backlog_ack(pkt, ip_hdr, tcp_hdr, new_context); if (ret < 0) { NET_DBG("Cannot find context from TCP backlog"); net_context_unref(new_context); goto conndrop; } ret = net_context_bind(new_context, &local_addr, sizeof(local_addr)); if (ret < 0) { NET_DBG("Cannot bind accepted context, " "connection reset"); net_context_unref(new_context); goto conndrop; } new_context->flags |= NET_CONTEXT_REMOTE_ADDR_SET; memcpy(&new_context->remote, &remote_addr, sizeof(remote_addr)); ret = net_tcp_register(net_pkt_family(pkt), &new_context->remote, &local_addr, ntohs(net_sin(&new_context->remote)->sin_port), ntohs(net_sin(&local_addr)->sin_port), tcp_established, new_context, &new_context->conn_handler); if (ret < 0) { NET_DBG("Cannot register accepted TCP handler (%d)", ret); net_context_unref(new_context); goto conndrop; } /* Swap the newly-created TCP states with the one that * was used to establish this connection. The old TCP * must be listening to accept other connections. */ copy_pool_vars(new_context, context); net_tcp_change_state(tcp, NET_TCP_LISTEN); net_tcp_change_state(new_context->tcp, NET_TCP_ESTABLISHED); /* Mark the new context to be still accepting so that we * can do proper cleanup if connection is closed before * we have called accept() */ net_context_set_accepting(new_context, true); net_context_set_state(new_context, NET_CONTEXT_CONNECTED); if (new_context->remote.sa_family == AF_INET) { addrlen = sizeof(struct sockaddr_in); } else if (new_context->remote.sa_family == AF_INET6) { addrlen = sizeof(struct sockaddr_in6); } else { NET_ASSERT(false, "Invalid protocol family %d", new_context->remote.sa_family); net_context_unref(new_context); return NET_DROP; } context->tcp->accept_cb(new_context, &new_context->remote, addrlen, 0, context->user_data); net_pkt_unref(pkt); return NET_OK; } return NET_DROP; conndrop: net_stats_update_tcp_seg_conndrop(net_pkt_iface(pkt)); reset: send_reset(tcp->context, &local_addr, &remote_addr); return NET_DROP; } int net_tcp_accept(struct net_context *context, net_tcp_accept_cb_t cb, void *user_data) { struct sockaddr local_addr; struct sockaddr *laddr = NULL; uint16_t lport = 0U; int ret; NET_ASSERT(context->tcp); if (net_tcp_get_state(context->tcp) != NET_TCP_LISTEN) { NET_DBG("Context %p in wrong state %d, should be %d", context, context->tcp->state, NET_TCP_LISTEN); return -EINVAL; } if (cb == NULL) { /* The context is being shut down */ if (net_context_get_ip_proto(context) == IPPROTO_TCP) { context->tcp->accept_cb = NULL; return 0; } } local_addr.sa_family = net_context_get_family(context); #if defined(CONFIG_NET_IPV6) if (net_context_get_family(context) == AF_INET6) { if (net_sin6_ptr(&context->local)->sin6_addr) { net_ipaddr_copy(&net_sin6(&local_addr)->sin6_addr, net_sin6_ptr(&context->local)->sin6_addr); laddr = &local_addr; } net_sin6(&local_addr)->sin6_port = lport = net_sin6((struct sockaddr *)&context->local)->sin6_port; } #endif /* CONFIG_NET_IPV6 */ #if defined(CONFIG_NET_IPV4) if (net_context_get_family(context) == AF_INET) { if (net_sin_ptr(&context->local)->sin_addr) { net_ipaddr_copy(&net_sin(&local_addr)->sin_addr, net_sin_ptr(&context->local)->sin_addr); laddr = &local_addr; } net_sin(&local_addr)->sin_port = lport = net_sin((struct sockaddr *)&context->local)->sin_port; } #endif /* CONFIG_NET_IPV4 */ ret = net_tcp_register(net_context_get_family(context), context->flags & NET_CONTEXT_REMOTE_ADDR_SET ? &context->remote : NULL, laddr, ntohs(net_sin(&context->remote)->sin_port), ntohs(lport), tcp_syn_rcvd, context, &context->conn_handler); if (ret < 0) { return ret; } context->user_data = user_data; /* accept callback is only valid for TCP contexts */ if (net_context_get_ip_proto(context) == IPPROTO_TCP) { context->tcp->accept_cb = cb; } return 0; } int net_tcp_connect(struct net_context *context, const struct sockaddr *addr, struct sockaddr *laddr, uint16_t rport, uint16_t lport, k_timeout_t timeout, net_context_connect_cb_t cb, void *user_data) { int ret; NET_ASSERT(context->tcp); if (net_context_get_type(context) != SOCK_STREAM) { return -ENOTSUP; } /* We need to register a handler, otherwise the SYN-ACK * packet would not be received. */ ret = net_tcp_register(net_context_get_family(context), addr, laddr, ntohs(rport), ntohs(lport), tcp_synack_received, context, &context->conn_handler); if (ret < 0) { return ret; } context->connect_cb = cb; context->user_data = user_data; net_context_set_state(context, NET_CONTEXT_CONNECTING); send_syn(context, addr); /* in tcp_synack_received() we give back this semaphore */ if (!K_TIMEOUT_EQ(timeout, K_NO_WAIT) && k_sem_take(&context->tcp->connect_wait, timeout)) { return -ETIMEDOUT; } return 0; } struct net_tcp_hdr *net_tcp_input(struct net_pkt *pkt, struct net_pkt_data_access *tcp_access) { struct net_tcp_hdr *tcp_hdr; if (IS_ENABLED(CONFIG_NET_TCP_CHECKSUM) && net_if_need_calc_rx_checksum(net_pkt_iface(pkt)) && net_calc_chksum_tcp(pkt) != 0U) { NET_DBG("DROP: checksum mismatch"); goto drop; } tcp_hdr = (struct net_tcp_hdr *)net_pkt_get_data(pkt, tcp_access); if (tcp_hdr && !net_pkt_set_data(pkt, tcp_access)) { return tcp_hdr; } drop: net_stats_update_tcp_seg_chkerr(net_pkt_iface(pkt)); return NULL; }