| /** @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 <logging/log.h> |
| LOG_MODULE_REGISTER(net_tcp, CONFIG_NET_TCP_LOG_LEVEL); |
| |
| #include <kernel.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| |
| #include <net/net_pkt.h> |
| #include <net/net_ip.h> |
| #include <net/net_context.h> |
| #include <sys/byteorder.h> |
| |
| #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; |
| u32_t send_seq; |
| u32_t send_ack; |
| struct k_delayed_work ack_timer; |
| struct sockaddr remote; |
| u16_t send_mss; |
| } tcp_backlog[CONFIG_NET_TCP_BACKLOG_SIZE]; |
| |
| #if defined(CONFIG_NET_TCP_ACK_TIMEOUT) |
| #define ACK_TIMEOUT CONFIG_NET_TCP_ACK_TIMEOUT |
| #else |
| #define ACK_TIMEOUT K_SECONDS(1) |
| #endif |
| |
| #define FIN_TIMEOUT K_SECONDS(1) |
| |
| /* 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 { |
| u32_t seq; |
| u32_t ack; |
| u16_t wnd; |
| u8_t flags; |
| u8_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) |
| { |
| u32_t rel_ack, ack; |
| u8_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 u32_t retry_timeout(const struct net_tcp *tcp) |
| { |
| return ((u32_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 (net_pkt_sent(pkt)) { |
| do_ref_if_needed(tcp, pkt); |
| net_pkt_set_sent(pkt, false); |
| } |
| |
| net_pkt_set_queued(pkt, true); |
| |
| 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); |
| 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; |
| } |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tcp->sent_list, pkt, tmp, |
| sent_list) { |
| sys_slist_remove(&tcp->sent_list, NULL, &pkt->sent_list); |
| net_pkt_unref(pkt); |
| } |
| |
| 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); |
| 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; |
| u16_t dst_port, src_port; |
| bool pkt_allocated; |
| u8_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; |
| } |
| |
| 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; |
| } |
| |
| u32_t net_tcp_get_recv_wnd(const struct net_tcp *tcp) |
| { |
| return tcp->recv_wnd; |
| } |
| |
| int net_tcp_prepare_segment(struct net_tcp *tcp, u8_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 }; |
| u32_t seq; |
| u16_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 u32_t get_size(u32_t pos1, u32_t pos2) |
| { |
| u32_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 */ |
| |
| u16_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, u8_t *options, |
| u8_t *optionlen) |
| { |
| u32_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), |
| (u32_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) |
| { |
| u8_t options[NET_TCP_MAX_OPT_SIZE]; |
| u8_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)); |
| } |
| |
| 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; |
| |
| 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_ipv6_ext_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_ipv6_ext_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; |
| int ret; |
| 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)); |
| } |
| |
| return ret; |
| } |
| } |
| |
| return net_send_data(pkt); |
| } |
| |
| 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, |
| 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; |
| |
| /* 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 (!net_pkt_sent(pkt)) { |
| int ret; |
| |
| 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 && !is_6lo_technology(pkt)) { |
| NET_DBG("[%p] pkt %p not sent (%d)", |
| context->tcp, pkt, ret); |
| net_pkt_unref(pkt); |
| } |
| |
| net_pkt_set_queued(pkt, true); |
| } |
| } |
| |
| /* 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, u32_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; |
| u32_t last_seq; |
| u32_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_ipv6_ext_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); |
| } |
| } |
| |
| sys_slist_remove(list, NULL, head); |
| 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); |
| } |
| |
| 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 u16_t valid_transitions[] = { |
| [NET_TCP_CLOSED] = 1 << NET_TCP_LISTEN | |
| 1 << NET_TCP_SYN_SENT, |
| [NET_TCP_LISTEN] = 1 << NET_TCP_SYN_RCVD | |
| 1 << NET_TCP_SYN_SENT, |
| [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) |
| { |
| u8_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; |
| int ret; |
| |
| ret = net_tcp_prepare_segment(ctx->tcp, NET_TCP_FIN, NULL, 0, |
| NULL, &ctx->remote, &pkt); |
| if (ret || !pkt) { |
| return; |
| } |
| |
| net_tcp_queue_pkt(ctx, pkt); |
| } |
| |
| 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 || |
| net_context_get_state(context) == NET_CONTEXT_LISTENING) |
| && !context->tcp->fin_rcvd) { |
| NET_DBG("TCP connection in active close, not " |
| "disposing yet (waiting %dms)", FIN_TIMEOUT); |
| k_delayed_work_submit(&context->tcp->fin_timer, |
| FIN_TIMEOUT); |
| queue_fin(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, s32_t delta) |
| { |
| s32_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); |
| |
| /* 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) |
| { |
| u16_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, |
| u16_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); |
| |
| 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, |
| 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_INFO(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) { |
| u16_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; |
| u8_t options[NET_TCP_MAX_OPT_SIZE]; |
| u8_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; |
| } |
| |
| 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); |
| } |
| |
| return ret; |
| } |
| |
| static u16_t adjust_data_len(struct net_pkt *pkt, struct net_tcp_hdr *tcp_hdr, |
| u16_t data_len) |
| { |
| u8_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; |
| u8_t tcp_flags; |
| u16_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); |
| } 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; |
| } |
| |
| 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); |
| } |
| |
| /* 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, |
| 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: |
| if (net_context_get_state(context) != NET_CONTEXT_LISTENING) { |
| NET_DBG("Context %p is not listening", context); |
| return NET_DROP; |
| } |
| |
| 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); |
| |
| /* We cannot use net_tcp_change_state() here as that will |
| * check the state transitions. So set the state directly. |
| */ |
| new_context->tcp->state = NET_TCP_ESTABLISHED; |
| |
| 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_INFO(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); |
| |
| /* Set the context in CONNECTED state, so that it can not |
| * accept any new connections. If application is ready to |
| * accept the connection, zsock_accept_ctx() will set |
| * the state back to LISTENING. |
| */ |
| net_context_set_state(context, NET_CONTEXT_CONNECTED); |
| |
| 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; |
| u16_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, |
| u16_t rport, |
| u16_t lport, |
| s32_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 (timeout != 0 && 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; |
| } |