| /* |
| * Copyright (c) 2018-2019 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(net_tcp, CONFIG_NET_TCP_LOG_LEVEL); |
| |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <zephyr.h> |
| #include <net/net_pkt.h> |
| #include <net/net_context.h> |
| #include "connection.h" |
| #include "net_stats.h" |
| #include "net_private.h" |
| #include "tcp2_priv.h" |
| |
| static int tcp_rto = 500; /* Retransmission timeout, msec */ |
| static int tcp_retries = 3; |
| static int tcp_window = NET_IPV6_MTU; |
| static bool tcp_echo; |
| |
| static sys_slist_t tcp_conns = SYS_SLIST_STATIC_INIT(&tcp_conns); |
| |
| static K_MEM_SLAB_DEFINE(tcp_conns_slab, sizeof(struct tcp), |
| CONFIG_NET_MAX_CONTEXTS, 4); |
| |
| NET_BUF_POOL_DEFINE(tcp_nbufs, 64/*count*/, 128/*size*/, 0, NULL); |
| |
| static void tcp_in(struct tcp *conn, struct net_pkt *pkt); |
| |
| int (*tcp_send_cb)(struct net_pkt *pkt) = NULL; |
| |
| /* TODO: IPv4 options may enlarge the IPv4 header */ |
| static struct tcphdr *th_get(struct net_pkt *pkt) |
| { |
| struct tcphdr *th = NULL; |
| ssize_t len; |
| |
| if (pkt == NULL) { |
| goto out; |
| } |
| |
| len = net_pkt_get_len(pkt); |
| |
| switch (pkt->family) { |
| case AF_INET: |
| if (len < (sizeof(struct net_ipv4_hdr) + |
| sizeof(struct tcphdr))) { |
| NET_WARN("Undersized IPv4 packet: %zd byte(s)", len); |
| goto out; |
| } |
| th = (struct tcphdr *)(ip_get(pkt) + 1); |
| break; |
| case AF_INET6: |
| if (len < (sizeof(struct net_ipv6_hdr) + |
| sizeof(struct tcphdr))) { |
| NET_WARN("Undersized IPv6 packet: %zd byte(s)", len); |
| goto out; |
| } |
| th = (struct tcphdr *)((u8_t *)ip6_get(pkt) + 1); |
| break; |
| default: |
| break; |
| } |
| out: |
| return th; |
| } |
| |
| static size_t tcp_endpoint_len(sa_family_t af) |
| { |
| return (af == AF_INET) ? sizeof(struct sockaddr_in) : |
| sizeof(struct sockaddr_in6); |
| } |
| |
| static union tcp_endpoint *tcp_endpoint_new(struct net_pkt *pkt, int src) |
| { |
| sa_family_t af = net_pkt_family(pkt); |
| union tcp_endpoint *ep = tcp_calloc(1, tcp_endpoint_len(af)); |
| |
| ep->sa.sa_family = af; |
| |
| switch (af) { |
| case AF_INET: { |
| struct net_ipv4_hdr *ip = ip_get(pkt); |
| struct tcphdr *th = th_get(pkt); |
| |
| ep->sin.sin_port = src ? th->th_sport : th->th_dport; |
| |
| ep->sin.sin_addr = src ? ip->src : ip->dst; |
| |
| break; |
| } |
| case AF_INET6: { |
| struct net_ipv6_hdr *ip = (void *)ip_get(pkt); |
| struct tcphdr *th = (void *)(ip + 1); |
| |
| ep->sin6.sin6_port = src ? th->th_sport : th->th_dport; |
| |
| ep->sin6.sin6_addr = src ? ip->src : ip->dst; |
| |
| break; |
| } |
| default: |
| NET_ERR("Unknown address family: %hu", af); |
| } |
| |
| return ep; |
| } |
| |
| static char *tcp_endpoint_to_string(union tcp_endpoint *ep) |
| { |
| #define NBUFS 2 |
| #define BUF_SIZE 80 |
| sa_family_t af = ep->sa.sa_family; |
| static char buf[NBUFS][BUF_SIZE]; |
| char addr[INET6_ADDRSTRLEN]; |
| static int i; |
| char *s = buf[++i % NBUFS]; |
| |
| switch (af) { |
| case 0: |
| snprintf(s, BUF_SIZE, ":%hu", ntohs(ep->sin.sin_port)); |
| break; |
| case AF_INET: case AF_INET6: |
| net_addr_ntop(af, &ep->sin.sin_addr, addr, sizeof(addr)); |
| snprintf(s, BUF_SIZE, "%s:%hu", addr, ntohs(ep->sin.sin_port)); |
| break; |
| default: |
| s = NULL; |
| NET_ERR("Unknown address family: %hu", af); |
| } |
| #undef BUF_SIZE |
| #undef NBUFS |
| return s; |
| } |
| |
| static const char *tcp_flags(u8_t fl) |
| { |
| #define BUF_SIZE 80 |
| static char buf[BUF_SIZE]; |
| size_t buf_size = BUF_SIZE; |
| char *s = buf; |
| *s = '\0'; |
| |
| if (fl) { |
| if (fl & SYN) { |
| s += snprintf(s, buf_size, "SYN,"); |
| buf_size -= s - buf; |
| } |
| if (fl & FIN) { |
| s += snprintf(s, buf_size, "FIN,"); |
| buf_size -= s - buf; |
| } |
| if (fl & ACK) { |
| s += snprintf(s, buf_size, "ACK,"); |
| buf_size -= s - buf; |
| } |
| if (fl & PSH) { |
| s += snprintf(s, buf_size, "PSH,"); |
| buf_size -= s - buf; |
| } |
| if (fl & RST) { |
| s += snprintf(s, buf_size, "RST,"); |
| buf_size -= s - buf; |
| } |
| if (fl & URG) { |
| s += snprintf(s, buf_size, "URG,"); |
| buf_size -= s - buf; |
| } |
| s[strlen(s) - 1] = '\0'; |
| s--; |
| } |
| #undef BUF_SIZE |
| return buf; |
| } |
| |
| static const char *tcp_th(struct net_pkt *pkt) |
| { |
| #define BUF_SIZE 80 |
| static char buf[BUF_SIZE]; |
| size_t buf_size = BUF_SIZE; |
| char *s = buf; |
| struct net_ipv4_hdr *ip = ip_get(pkt); |
| struct tcphdr *th = th_get(pkt); |
| u8_t fl = th->th_flags; |
| ssize_t data_len = ntohs(ip->len) - sizeof(*ip) - th->th_off * 4; |
| |
| *s = '\0'; |
| |
| if (th->th_off < 5) { |
| s += snprintf(s, buf_size, "Bogus th_off: %hu", th->th_off); |
| buf_size -= s - buf; |
| goto end; |
| } |
| |
| if (fl) { |
| if (fl & SYN) { |
| s += snprintf(s, buf_size, "SYN=%u,", th_seq(th)); |
| buf_size -= s - buf; |
| } |
| if (fl & FIN) { |
| s += snprintf(s, buf_size, "FIN=%u,", th_seq(th)); |
| buf_size -= s - buf; |
| } |
| if (fl & ACK) { |
| s += snprintf(s, buf_size, "ACK=%u,", th_ack(th)); |
| buf_size -= s - buf; |
| } |
| if (fl & PSH) { |
| s += snprintf(s, buf_size, "PSH,"); |
| buf_size -= s - buf; |
| } |
| if (fl & RST) { |
| s += snprintf(s, buf_size, "RST,"); |
| buf_size -= s - buf; |
| } |
| if (fl & URG) { |
| s += snprintf(s, buf_size, "URG,"); |
| buf_size -= s - buf; |
| } |
| s[strlen(s) - 1] = '\0'; |
| s--; |
| } |
| |
| if (data_len) { |
| s += snprintf(s, buf_size, ", len=%zd", data_len); |
| buf_size -= s - buf; |
| } |
| |
| if (((bool)(PSH & fl)) != (data_len > 0)) { |
| NET_WARN("Invalid TCP packet: %s, data_len=%zd", buf, data_len); |
| } |
| end: |
| return buf; |
| #undef BUF_SIZE |
| } |
| |
| static void tcp_send(struct net_pkt *pkt) |
| { |
| NET_DBG("%s", tcp_th(pkt)); |
| |
| tcp_pkt_ref(pkt); |
| |
| if (tcp_send_cb) { |
| if (tcp_send_cb(pkt) < 0) { |
| NET_ERR("net_send_data()"); |
| tcp_pkt_unref(pkt); |
| } |
| goto out; |
| } |
| |
| if (net_send_data(pkt) < 0) { |
| NET_ERR("net_send_data()"); |
| tcp_pkt_unref(pkt); |
| } |
| out: |
| tcp_pkt_unref(pkt); |
| } |
| |
| static void tcp_send_queue_flush(struct tcp *conn) |
| { |
| struct net_pkt *pkt; |
| |
| if (is_timer_subscribed(&conn->send_timer)) { |
| k_timer_stop(&conn->send_timer); |
| } |
| |
| while ((pkt = tcp_slist(&conn->send_queue, get, |
| struct net_pkt, next))) { |
| tcp_pkt_unref(pkt); |
| } |
| } |
| |
| static void tcp_win_free(struct tcp_win *w, const char *name) |
| { |
| struct net_buf *buf; |
| |
| while ((buf = tcp_slist(&w->bufs, get, struct net_buf, user_data))) { |
| NET_DBG("%s %p len=%d", name, buf, buf->len); |
| tcp_nbuf_unref(buf); |
| } |
| |
| tcp_free(w); |
| } |
| |
| static int tcp_conn_unref(struct tcp *conn) |
| { |
| int ref_count = atomic_dec(&conn->ref_count) - 1; |
| int key; |
| |
| NET_DBG("conn: %p, ref_count=%d", conn, ref_count); |
| |
| if (ref_count) { |
| tp_out(conn->iface, "TP_TRACE", "event", "CONN_DELETE"); |
| goto out; |
| } |
| |
| key = irq_lock(); |
| |
| if (conn->context->conn_handler) { |
| net_conn_unregister(conn->context->conn_handler); |
| conn->context->conn_handler = NULL; |
| } |
| |
| if (conn->context->recv_cb) { |
| conn->context->recv_cb(conn->context, NULL, NULL, NULL, |
| -ECONNRESET, conn->recv_user_data); |
| } |
| |
| conn->context->tcp = NULL; |
| |
| net_context_unref(conn->context); |
| |
| tcp_send_queue_flush(conn); |
| |
| tcp_win_free(conn->snd, "SND"); |
| tcp_win_free(conn->rcv, "RCV"); |
| |
| tcp_free(conn->src); |
| tcp_free(conn->dst); |
| |
| sys_slist_find_and_remove(&tcp_conns, (sys_snode_t *)conn); |
| |
| k_mem_slab_free(&tcp_conns_slab, (void **)&conn); |
| |
| memset(conn, 0, sizeof(*conn)); |
| |
| irq_unlock(key); |
| out: |
| return ref_count; |
| } |
| |
| int net_tcp_unref(struct net_context *context) |
| { |
| int ref_count = 0; |
| |
| NET_DBG("context: %p, conn: %p", context, context->tcp); |
| |
| if (context->tcp) { |
| ref_count = tcp_conn_unref(context->tcp); |
| } |
| |
| return ref_count; |
| } |
| |
| static void tcp_send_process(struct k_timer *timer) |
| { |
| struct tcp *conn = k_timer_user_data_get(timer); |
| struct net_pkt *pkt = tcp_slist(&conn->send_queue, peek_head, |
| struct net_pkt, next); |
| |
| NET_DBG("%s %s", tcp_th(pkt), conn->in_retransmission ? |
| "in_retransmission" : ""); |
| |
| if (conn->in_retransmission) { |
| if (conn->send_retries > 0) { |
| tcp_send(tcp_pkt_clone(pkt)); |
| conn->send_retries--; |
| } else { |
| tcp_conn_unref(conn); |
| conn = NULL; |
| } |
| } else { |
| u8_t fl = th_get(pkt)->th_flags; |
| bool forget = ACK == fl || PSH == fl || (ACK | PSH) == fl || |
| RST & fl; |
| |
| pkt = forget ? tcp_slist(&conn->send_queue, get, struct net_pkt, |
| next) : tcp_pkt_clone(pkt); |
| tcp_send(pkt); |
| |
| if (forget == false && is_timer_subscribed( |
| &conn->send_timer) == false) { |
| conn->send_retries = tcp_retries; |
| conn->in_retransmission = true; |
| } |
| } |
| |
| if (conn && conn->in_retransmission) { |
| k_timer_start(&conn->send_timer, K_MSEC(tcp_rto), K_NO_WAIT); |
| } |
| } |
| |
| static void tcp_send_timer_cancel(struct tcp *conn) |
| { |
| NET_ASSERT_INFO(conn->in_retransmission == true, |
| "Not in retransmission"); |
| |
| k_timer_stop(&conn->send_timer); |
| |
| { |
| struct net_pkt *pkt = tcp_slist(&conn->send_queue, get, |
| struct net_pkt, next); |
| NET_DBG("%s", tcp_th(pkt)); |
| tcp_pkt_unref(pkt); |
| } |
| |
| if (sys_slist_is_empty(&conn->send_queue)) { |
| conn->in_retransmission = false; |
| } else { |
| conn->send_retries = tcp_retries; |
| k_timer_start(&conn->send_timer, K_MSEC(tcp_rto), K_NO_WAIT); |
| } |
| } |
| |
| static struct tcp_win *tcp_win_new(void) |
| { |
| struct tcp_win *w = tcp_calloc(1, sizeof(struct tcp_win)); |
| |
| sys_slist_init(&w->bufs); |
| |
| return w; |
| } |
| |
| static const char *tcp_state_to_str(enum tcp_state state, bool prefix) |
| { |
| const char *s = NULL; |
| #define _(_x) case _x: do { s = #_x; goto out; } while (0) |
| switch (state) { |
| _(TCP_LISTEN); |
| _(TCP_SYN_SENT); |
| _(TCP_SYN_RECEIVED); |
| _(TCP_ESTABLISHED); |
| _(TCP_FIN_WAIT1); |
| _(TCP_FIN_WAIT2); |
| _(TCP_CLOSE_WAIT); |
| _(TCP_CLOSING); |
| _(TCP_LAST_ACK); |
| _(TCP_TIME_WAIT); |
| _(TCP_CLOSED); |
| } |
| #undef _ |
| NET_ASSERT_INFO(s, "Invalid TCP state: %u", state); |
| out: |
| return prefix ? s : (s + 4); |
| } |
| |
| static void tcp_win_append(struct tcp_win *w, const char *name, |
| const void *data, size_t len) |
| { |
| struct net_buf *buf = tcp_nbuf_alloc(&tcp_nbufs, len); |
| size_t prev_len = w->len; |
| |
| NET_ASSERT_INFO(len, "Zero length data"); |
| |
| memcpy(net_buf_add(buf, len), data, len); |
| |
| sys_slist_append(&w->bufs, (void *)&buf->user_data); |
| |
| w->len += len; |
| |
| NET_DBG("%s %p %zu->%zu byte(s)", name, buf, prev_len, w->len); |
| } |
| |
| static struct net_buf *tcp_win_peek(struct tcp_win *w, const char *name, |
| size_t len) |
| { |
| struct net_buf *out = tcp_nbuf_alloc(&tcp_nbufs, len); |
| struct net_buf *buf = tcp_slist(&w->bufs, peek_head, struct net_buf, |
| user_data); |
| while (buf) { |
| |
| if (len <= 0) { |
| break; |
| } |
| |
| memcpy(net_buf_add(out, buf->len), buf->data, buf->len); |
| |
| len -= buf->len; |
| |
| buf = tcp_slist((sys_snode_t *)&buf->user_data, peek_next, |
| struct net_buf, user_data); |
| } |
| |
| NET_ASSERT_INFO(len == 0, "Unfulfilled request, len: %zu", len); |
| |
| NET_DBG("%s len=%zu", name, net_buf_frags_len(out)); |
| |
| return out; |
| } |
| |
| static const char *tcp_conn_state(struct tcp *conn, struct net_pkt *pkt) |
| { |
| #define BUF_SIZE 64 |
| static char buf[BUF_SIZE]; |
| |
| snprintf(buf, BUF_SIZE, "%s %s %u/%u", pkt ? tcp_th(pkt) : "", |
| tcp_state_to_str(conn->state, false), |
| conn->seq, conn->ack); |
| #undef BUF_SIZE |
| return buf; |
| } |
| |
| static bool tcp_options_check(void *buf, ssize_t len) |
| { |
| bool result = len > 0 && ((len % 4) == 0) ? true : false; |
| u8_t *options = buf, opt, opt_len; |
| |
| NET_DBG("len=%zd", len); |
| |
| for ( ; len >= 2; options += opt_len, len -= opt_len) { |
| opt = options[0]; |
| opt_len = options[1]; |
| |
| NET_DBG("opt: %hu, opt_len: %hu", opt, opt_len); |
| |
| if (opt == TCPOPT_PAD) { |
| break; |
| } |
| if (opt == TCPOPT_NOP) { |
| opt_len = 1; |
| } else if (opt_len < 2 || opt_len > len) { |
| break; |
| } |
| |
| switch (opt) { |
| case TCPOPT_MAXSEG: |
| if (opt_len != 4) { |
| result = false; |
| goto end; |
| } |
| break; |
| case TCPOPT_WINDOW: |
| if (opt_len != 3) { |
| result = false; |
| goto end; |
| } |
| break; |
| default: |
| continue; |
| } |
| } |
| end: |
| if (false == result) { |
| NET_WARN("Invalid TCP options"); |
| } |
| |
| return result; |
| } |
| |
| static size_t tcp_data_len(struct net_pkt *pkt) |
| { |
| struct net_ipv4_hdr *ip = ip_get(pkt); |
| struct tcphdr *th = th_get(pkt); |
| u8_t off = th->th_off; |
| ssize_t data_len = ntohs(ip->len) - sizeof(*ip) - off * 4; |
| |
| if (off > 5 && false == tcp_options_check((th + 1), (off - 5) * 4)) { |
| data_len = 0; |
| } |
| |
| return data_len > 0 ? data_len : 0; |
| } |
| |
| static size_t tcp_data_get(struct tcp *conn, struct net_pkt *pkt) |
| { |
| struct net_ipv4_hdr *ip = ip_get(pkt); |
| struct tcphdr *th = th_get(pkt); |
| ssize_t len = tcp_data_len(pkt); |
| |
| if (len > 0) { |
| void *buf = tcp_malloc(len); |
| |
| net_pkt_skip(pkt, sizeof(*ip) + th->th_off * 4); |
| |
| net_pkt_read(pkt, buf, len); |
| |
| tcp_win_append(conn->rcv, "RCV", buf, len); |
| |
| if (tcp_echo) { |
| tcp_win_append(conn->snd, "SND", buf, len); |
| } |
| |
| tcp_free(buf); |
| |
| if (conn->context->recv_cb) { |
| struct net_pkt *up = net_pkt_clone(pkt, K_NO_WAIT); |
| |
| net_pkt_cursor_init(up); |
| net_pkt_set_overwrite(up, true); |
| net_pkt_skip(up, 40); |
| |
| net_context_packet_received( |
| (struct net_conn *)conn->context->conn_handler, |
| up, NULL, NULL, conn->recv_user_data); |
| } |
| } |
| |
| return len; |
| } |
| |
| static void tcp_adj(struct net_pkt *pkt, int req_len) |
| { |
| struct net_ipv4_hdr *ip = ip_get(pkt); |
| u16_t len = ntohs(ip->len) + req_len; |
| |
| ip->len = htons(len); |
| } |
| |
| static struct net_pkt *tcp_pkt_make(struct tcp *conn, u8_t flags) |
| { |
| const size_t len = 40; |
| struct net_pkt *pkt = tcp_pkt_alloc(len); |
| struct net_ipv4_hdr *ip = ip_get(pkt); |
| struct tcphdr *th = (void *) (ip + 1); |
| |
| memset(ip, 0, len); |
| |
| ip->vhl = 0x45; |
| ip->ttl = 64; |
| ip->proto = IPPROTO_TCP; |
| ip->len = htons(len); |
| |
| ip->src = conn->src->sin.sin_addr; |
| ip->dst = conn->dst->sin.sin_addr; |
| |
| th->th_sport = conn->src->sin.sin_port; |
| th->th_dport = conn->dst->sin.sin_port; |
| |
| th->th_off = 5; |
| th->th_flags = flags; |
| th->th_win = htons(conn->win); |
| th->th_seq = htonl(conn->seq); |
| |
| if (ACK & flags) { |
| th->th_ack = htonl(conn->ack); |
| } |
| |
| pkt->iface = conn->iface; |
| |
| return pkt; |
| } |
| |
| static u32_t sum(void *data, size_t len) |
| { |
| u32_t s = 0; |
| |
| for ( ; len > 1; len -= 2, data = (u8_t *)data + 2) { |
| s += *((u16_t *)data); |
| } |
| |
| if (len) { |
| s += *((u8_t *)data); |
| } |
| |
| return s; |
| } |
| |
| static uint16_t cs(int32_t s) |
| { |
| return ~((s & 0xFFFF) + (s >> 16)); |
| } |
| |
| static void tcp_csum(struct net_pkt *pkt) |
| { |
| struct net_ipv4_hdr *ip = ip_get(pkt); |
| struct tcphdr *th = (void *) (ip + 1); |
| u16_t len = ntohs(ip->len) - 20; |
| u32_t s; |
| |
| ip->chksum = cs(sum(ip, sizeof(*ip))); |
| |
| s = sum(&ip->src, sizeof(struct in_addr) * 2); |
| s += ntohs(ip->proto + len); |
| |
| th->th_sum = 0; |
| s += sum(th, len); |
| |
| th->th_sum = cs(s); |
| } |
| |
| static struct net_pkt *tcp_pkt_linearize(struct net_pkt *pkt) |
| { |
| struct net_pkt *new = tcp_pkt_alloc(0); |
| struct net_buf *tmp, *buf = net_pkt_get_frag(new, K_NO_WAIT); |
| |
| for (tmp = pkt->frags; tmp; tmp = tmp->frags) { |
| memcpy(net_buf_add(buf, tmp->len), tmp->data, tmp->len); |
| } |
| |
| net_pkt_frag_add(new, buf); |
| |
| new->iface = pkt->iface; |
| |
| tcp_pkt_unref(pkt); |
| |
| return new; |
| } |
| |
| static void tcp_chain_free(struct net_buf *head) |
| { |
| struct net_buf *next; |
| |
| for ( ; head; head = next) { |
| next = head->frags; |
| head->frags = NULL; |
| tcp_nbuf_unref(head); |
| } |
| } |
| |
| static void tcp_chain(struct net_pkt *pkt, struct net_buf *head) |
| { |
| struct net_buf *buf; |
| |
| for ( ; head; head = head->frags) { |
| buf = net_pkt_get_frag(pkt, K_NO_WAIT); |
| memcpy(net_buf_add(buf, head->len), head->data, head->len); |
| net_pkt_frag_add(pkt, buf); |
| } |
| } |
| |
| static void tcp_out(struct tcp *conn, u8_t flags, ...) |
| { |
| struct net_pkt *pkt = tcp_pkt_make(conn, flags); |
| |
| if (PSH & flags) { |
| size_t len = conn->snd->len; |
| struct net_buf *buf = tcp_win_peek(conn->snd, "SND", len); |
| |
| { |
| va_list ap; |
| ssize_t *out_len; |
| |
| va_start(ap, flags); |
| out_len = va_arg(ap, ssize_t *); |
| *out_len = len; |
| va_end(ap); |
| } |
| |
| tcp_chain(pkt, buf); |
| |
| tcp_chain_free(buf); |
| |
| tcp_adj(pkt, len); |
| } |
| |
| pkt = tcp_pkt_linearize(pkt); |
| |
| tcp_csum(pkt); |
| |
| NET_DBG("%s", tcp_th(pkt)); |
| |
| if (tcp_send_cb) { |
| tcp_send_cb(pkt); |
| goto out; |
| } |
| |
| sys_slist_append(&conn->send_queue, &pkt->next); |
| |
| tcp_send_process(&conn->send_timer); |
| out: |
| return; |
| } |
| |
| static void tcp_conn_ref(struct tcp *conn) |
| { |
| int ref_count = atomic_inc(&conn->ref_count) + 1; |
| |
| NET_DBG("conn: %p, ref_count: %d", conn, ref_count); |
| } |
| |
| static struct tcp *tcp_conn_alloc(void) |
| { |
| struct tcp *conn = NULL; |
| int ret; |
| |
| ret = k_mem_slab_alloc(&tcp_conns_slab, (void **)&conn, K_NO_WAIT); |
| if (ret) { |
| goto out; |
| } |
| |
| memset(conn, 0, sizeof(*conn)); |
| |
| conn->state = TCP_LISTEN; |
| |
| conn->win = tcp_window; |
| |
| conn->rcv = tcp_win_new(); |
| conn->snd = tcp_win_new(); |
| |
| sys_slist_init(&conn->send_queue); |
| |
| k_timer_init(&conn->send_timer, tcp_send_process, NULL); |
| k_timer_user_data_set(&conn->send_timer, conn); |
| |
| tcp_conn_ref(conn); |
| |
| sys_slist_append(&tcp_conns, (sys_snode_t *)conn); |
| out: |
| NET_DBG("conn: %p", conn); |
| |
| return conn; |
| } |
| |
| int net_tcp_get(struct net_context *context) |
| { |
| int ret = 0, key = irq_lock(); |
| struct tcp *conn; |
| |
| conn = tcp_conn_alloc(); |
| if (conn == NULL) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| /* Mutually link the net_context and tcp connection */ |
| conn->context = context; |
| context->tcp = conn; |
| out: |
| irq_unlock(key); |
| |
| NET_DBG("context: %p (local: %s, remote: %s), conn: %p", context, |
| tcp_endpoint_to_string((void *)&context->local), |
| tcp_endpoint_to_string((void *)&context->remote), conn); |
| |
| return ret; |
| } |
| |
| static bool tcp_endpoint_cmp(union tcp_endpoint *ep, struct net_pkt *pkt, |
| int which) |
| { |
| union tcp_endpoint *ep_new = tcp_endpoint_new(pkt, which); |
| bool is_equal = memcmp(ep, ep_new, tcp_endpoint_len(ep->sa.sa_family)) ? |
| false : true; |
| |
| tcp_free(ep_new); |
| |
| return is_equal; |
| } |
| |
| static bool tcp_conn_cmp(struct tcp *conn, struct net_pkt *pkt) |
| { |
| return tcp_endpoint_cmp(conn->src, pkt, DST) && |
| tcp_endpoint_cmp(conn->dst, pkt, SRC); |
| } |
| |
| static struct tcp *tcp_conn_search(struct net_pkt *pkt) |
| { |
| bool found = false; |
| struct tcp *conn; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&tcp_conns, conn, next) { |
| |
| if (NULL == conn->src || NULL == conn->dst) { |
| continue; |
| } |
| |
| found = tcp_conn_cmp(conn, pkt); |
| |
| if (found) { |
| break; |
| } |
| } |
| |
| return found ? conn : NULL; |
| } |
| |
| void tcp_input(struct net_pkt *pkt) |
| { |
| struct tcphdr *th = /*tp_tap_input(pkt) ? NULL :*/ th_get(pkt); |
| |
| if (th) { |
| struct tcp *conn = tcp_conn_search(pkt); |
| |
| if (conn == NULL && SYN == th->th_flags) { |
| struct net_context *context = |
| tcp_calloc(1, sizeof(struct net_context)); |
| net_tcp_get(context); |
| conn = context->tcp; |
| conn->dst = tcp_endpoint_new(pkt, SRC); |
| conn->src = tcp_endpoint_new(pkt, DST); |
| /* Make an extra reference, the sanity check suite |
| * will delete the connection explicitly |
| */ |
| tcp_conn_ref(conn); |
| } |
| |
| if (conn) { |
| conn->iface = pkt->iface; |
| tcp_in(conn, pkt); |
| } |
| } |
| } |
| |
| static struct tcp *tcp_conn_new(struct net_pkt *pkt); |
| |
| static enum net_verdict tcp_pkt_received(struct net_conn *net_conn, |
| struct net_pkt *pkt, |
| union net_ip_header *ip, |
| union net_proto_header *proto, |
| void *user_data) |
| { |
| struct tcp *conn = ((struct net_context *)user_data)->tcp; |
| u8_t vhl = ip->ipv4->vhl; |
| |
| ARG_UNUSED(net_conn); |
| ARG_UNUSED(proto); |
| |
| if (vhl != 0x45) { |
| NET_ERR("conn: %p, Unsupported IP version: 0x%hx", conn, vhl); |
| goto out; |
| } |
| |
| NET_DBG("conn: %p, %s", conn, tcp_th(pkt)); |
| |
| if (conn && TCP_LISTEN == conn->state) { |
| struct tcp *conn_old = conn; |
| |
| conn = tcp_conn_new(pkt); |
| |
| conn->context->iface = conn_old->context->iface; |
| conn->context->user_data = conn_old->context->user_data; |
| |
| conn_old->context->remote = conn->dst->sa; |
| |
| conn_old->accept_cb(conn->context, |
| &conn_old->context->remote, |
| sizeof(struct sockaddr), 0, |
| conn_old->context); |
| } |
| |
| if (conn) { |
| tcp_in(conn, pkt); |
| } |
| out: |
| return NET_DROP; |
| } |
| |
| /* Create a new tcp connection, as a part of it, create and register |
| * net_context |
| */ |
| static struct tcp *tcp_conn_new(struct net_pkt *pkt) |
| { |
| struct tcp *conn = NULL; |
| struct net_context *context = NULL; |
| sa_family_t af = net_pkt_family(pkt); |
| int ret; |
| |
| ret = net_context_get(af, SOCK_STREAM, IPPROTO_TCP, &context); |
| if (ret < 0) { |
| NET_ERR("net_context_get(): %d", ret); |
| goto err; |
| } |
| |
| conn = context->tcp; |
| conn->iface = pkt->iface; |
| |
| conn->dst = tcp_endpoint_new(pkt, SRC); |
| conn->src = tcp_endpoint_new(pkt, DST); |
| |
| NET_DBG("conn: src: %s, dst: %s", tcp_endpoint_to_string(conn->src), |
| tcp_endpoint_to_string(conn->dst)); |
| |
| memcpy(&context->remote, conn->dst, sizeof(context->remote)); |
| context->flags |= NET_CONTEXT_REMOTE_ADDR_SET; |
| |
| ((struct sockaddr_in *)&context->local)->sin_family = af; |
| |
| NET_DBG("context: local: %s, remote: %s", |
| tcp_endpoint_to_string((void *)&context->local), |
| tcp_endpoint_to_string((void *)&context->remote)); |
| |
| ret = net_conn_register(IPPROTO_TCP, af, |
| &context->remote, (void *)&context->local, |
| ntohs(conn->dst->sin.sin_port),/* local port */ |
| ntohs(conn->src->sin.sin_port),/* remote port */ |
| tcp_pkt_received, context, |
| &context->conn_handler); |
| if (ret < 0) { |
| NET_ERR("net_conn_register(): %d", ret); |
| net_context_unref(context); |
| conn = NULL; |
| goto err; |
| } |
| err: |
| return conn; |
| } |
| |
| /* TCP state machine, everything happens here */ |
| static void tcp_in(struct tcp *conn, struct net_pkt *pkt) |
| { |
| struct tcphdr *th = th_get(pkt); |
| u8_t next = 0, fl = th ? th->th_flags : 0; |
| |
| NET_DBG("%s", tcp_conn_state(conn, pkt)); |
| |
| if (th && th->th_off < 5) { |
| tcp_out(conn, RST); |
| conn_state(conn, TCP_CLOSED); |
| goto next_state; |
| } |
| |
| if (FL(&fl, &, RST)) { |
| conn_state(conn, TCP_CLOSED); |
| } |
| next_state: |
| switch (conn->state) { |
| case TCP_LISTEN: |
| if (FL(&fl, ==, SYN)) { |
| conn_ack(conn, th_seq(th) + 1); /* capture peer's isn */ |
| tcp_out(conn, SYN | ACK); |
| conn_seq(conn, + 1); |
| next = TCP_SYN_RECEIVED; |
| } else { |
| tcp_out(conn, SYN); |
| conn_seq(conn, + 1); |
| next = TCP_SYN_SENT; |
| } |
| break; |
| case TCP_SYN_RECEIVED: |
| if (FL(&fl, &, ACK, th_ack(th) == conn->seq)) { |
| tcp_send_timer_cancel(conn); |
| next = TCP_ESTABLISHED; |
| if (FL(&fl, &, PSH)) { |
| tcp_data_get(conn, pkt); |
| } |
| } |
| break; |
| case TCP_SYN_SENT: |
| /* if we are in SYN SENT and receive only a SYN without an |
| * ACK , shouldn't we go to SYN RECEIVED state? See Figure |
| * 6 of RFC 793 |
| */ |
| if (FL(&fl, &, ACK, th_seq(th) == conn->ack)) { |
| tcp_send_timer_cancel(conn); |
| next = TCP_ESTABLISHED; |
| if (FL(&fl, &, PSH)) { |
| tcp_data_get(conn, pkt); |
| } |
| if (FL(&fl, &, SYN)) { |
| conn_ack(conn, th_seq(th) + 1); |
| tcp_out(conn, ACK); |
| } |
| } |
| break; |
| case TCP_ESTABLISHED: |
| net_context_set_state(conn->context, NET_CONTEXT_CONNECTED); |
| if (!th && conn->snd->len) { /* TODO: Out of the loop */ |
| ssize_t data_len; |
| |
| tcp_out(conn, PSH, &data_len); |
| conn_seq(conn, + data_len); |
| break; |
| } |
| /* full-close */ |
| if (FL(&fl, ==, (FIN | ACK), th_seq(th) == conn->ack)) { |
| conn_ack(conn, + 1); |
| tcp_out(conn, ACK); |
| next = TCP_CLOSE_WAIT; |
| break; |
| } |
| if (FL(&fl, &, PSH, th_seq(th) < conn->ack)) { |
| tcp_out(conn, ACK); /* peer has resent */ |
| break; |
| } |
| if (FL(&fl, &, PSH, th_seq(th) > conn->ack)) { |
| tcp_out(conn, RST); |
| next = TCP_CLOSED; |
| break; |
| } |
| /* Non piggybacking version for clarity now */ |
| if (FL(&fl, &, PSH, th_seq(th) == conn->ack)) { |
| ssize_t len = tcp_data_get(conn, pkt); |
| |
| if (len) { |
| conn_ack(conn, + len); |
| tcp_out(conn, ACK); |
| |
| if (tcp_echo) { /* TODO: Out of the loop? */ |
| tcp_out(conn, PSH, &len); |
| conn_seq(conn, + len); |
| } |
| } else { |
| tcp_out(conn, RST); |
| next = TCP_CLOSED; |
| break; |
| } |
| } |
| if (FL(&fl, ==, ACK, th_ack(th) == conn->seq)) { |
| tcp_win_free(conn->snd, "SND"); |
| conn->snd = tcp_win_new(); |
| } |
| break; /* TODO: Catch all the rest here */ |
| case TCP_CLOSE_WAIT: |
| tcp_out(conn, FIN | ACK); |
| next = TCP_LAST_ACK; |
| break; |
| case TCP_LAST_ACK: |
| if (FL(&fl, ==, ACK, th_seq(th) == conn->ack)) { |
| tcp_send_timer_cancel(conn); |
| next = TCP_CLOSED; |
| } |
| break; |
| case TCP_CLOSED: |
| fl = 0; |
| tcp_conn_unref(conn); |
| break; |
| case TCP_TIME_WAIT: |
| case TCP_CLOSING: |
| case TCP_FIN_WAIT1: |
| case TCP_FIN_WAIT2: |
| default: |
| NET_ASSERT_INFO(false, "%s is unimplemented", |
| tcp_state_to_str(conn->state, true)); |
| } |
| |
| if (fl) { |
| th = NULL; |
| NET_WARN("Unconsumed flags: %s (%s) %s", |
| log_strdup(tcp_flags(fl)), |
| log_strdup(tcp_th(pkt)), |
| log_strdup(tcp_conn_state(conn, NULL))); |
| tcp_out(conn, RST); |
| conn_state(conn, TCP_CLOSED); |
| next = 0; |
| goto next_state; |
| } |
| |
| if (next) { |
| th = NULL; |
| conn_state(conn, next); |
| next = 0; |
| goto next_state; |
| } |
| } |
| |
| static ssize_t _tcp_send(struct tcp *conn, const void *buf, size_t len, |
| int flags) |
| { |
| tcp_win_append(conn->snd, "SND", buf, len); |
| |
| tcp_in(conn, NULL); |
| |
| return len; |
| } |
| |
| /* close() has been called on the socket */ |
| int net_tcp_put(struct net_context *context) |
| { |
| struct tcp *conn = context->tcp; |
| |
| NET_DBG("%s", conn ? tcp_conn_state(conn, NULL) : ""); |
| |
| if (conn) { |
| conn->state = TCP_CLOSE_WAIT; |
| tcp_in(conn, NULL); |
| } |
| |
| net_context_unref(context); |
| |
| return 0; |
| } |
| |
| int net_tcp_listen(struct net_context *context) |
| { |
| /* when created, tcp connections are in state TCP_LISTEN */ |
| net_context_set_state(context, NET_CONTEXT_LISTENING); |
| |
| return 0; |
| } |
| |
| int net_tcp_update_recv_wnd(struct net_context *context, s32_t delta) |
| { |
| ARG_UNUSED(context); |
| ARG_UNUSED(delta); |
| |
| return -EPROTONOSUPPORT; |
| } |
| |
| int net_tcp_queue(struct net_context *context, const void *buf, size_t len, |
| const struct msghdr *msghdr) |
| { |
| struct tcp *conn = context->tcp; |
| ssize_t ret = 0; |
| |
| NET_DBG("conn: %p, buf: %p, len: %zu", conn, buf, len); |
| |
| if (conn == NULL) { |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| if (msghdr && msghdr->msg_iovlen > 0) { |
| int i; |
| |
| for (i = 0; i < msghdr->msg_iovlen; i++) { |
| ret = _tcp_send(conn, msghdr->msg_iov[i].iov_base, |
| msghdr->msg_iov[i].iov_len, 0); |
| |
| if (ret < 0) { |
| break; |
| } |
| } |
| } else { |
| ret = _tcp_send(conn, buf, len, 0); |
| } |
| out: |
| NET_DBG("conn: %p, ret: %zd", conn, ret); |
| |
| return ret; |
| } |
| |
| /* net context wants to queue data for the TCP connection - not used */ |
| int net_tcp_queue_data(struct net_context *context, struct net_pkt *pkt) |
| { |
| ARG_UNUSED(context); |
| ARG_UNUSED(pkt); |
| |
| return 0; |
| } |
| |
| /* net context is about to send out queued data - inform caller only */ |
| int net_tcp_send_data(struct net_context *context, net_context_send_cb_t cb, |
| void *user_data) |
| { |
| if (cb) { |
| cb(context, 0, user_data); |
| } |
| |
| return 0; |
| } |
| |
| /* When connect() is called on a TCP socket, register the socket for incoming |
| * traffic with net context and give the TCP packet receiving function, which |
| * in turn will call tcp_in() to deliver the TCP packet to the stack |
| */ |
| int net_tcp_connect(struct net_context *context, |
| const struct sockaddr *remote_addr, |
| struct sockaddr *local_addr, |
| u16_t remote_port, u16_t local_port, |
| s32_t timeout, net_context_connect_cb_t cb, void *user_data) |
| { |
| struct tcp *conn = context->tcp; |
| int ret; |
| |
| switch (net_context_get_family(context)) { |
| case AF_INET: |
| net_sin(&conn->src->sa)->sin_port = local_port; |
| net_sin(&conn->dst->sa)->sin_port = remote_port; |
| break; |
| |
| case AF_INET6: |
| net_sin6(&conn->src->sa)->sin6_port = local_port; |
| net_sin6(&conn->dst->sa)->sin6_port = remote_port; |
| break; |
| |
| default: |
| return -EPROTONOSUPPORT; |
| } |
| |
| conn->src->sa = *local_addr; |
| conn->dst->sa = *remote_addr; |
| |
| net_context_set_state(context, NET_CONTEXT_CONNECTING); |
| |
| ret = net_conn_register(net_context_get_ip_proto(context), |
| net_context_get_family(context), |
| remote_addr, local_addr, |
| ntohs(remote_port), ntohs(local_port), |
| tcp_pkt_received, context, |
| &context->conn_handler); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Input of a (nonexistent) packet with no flags set will cause |
| * a TCP connection to be established |
| */ |
| tcp_in(conn, NULL); |
| |
| return 0; |
| } |
| |
| int net_tcp_accept(struct net_context *context, net_tcp_accept_cb_t cb, |
| void *user_data) |
| { |
| struct tcp *conn = context->tcp; |
| struct sockaddr local_addr = { }; |
| u16_t local_port, remote_port; |
| |
| NET_DBG("context: %p, tcp: %p, cb: %p", context, conn, cb); |
| |
| conn->accept_cb = cb; |
| |
| if (!conn || conn->state != TCP_LISTEN) { |
| return -EINVAL; |
| } |
| |
| local_addr.sa_family = net_context_get_family(context); |
| |
| switch (local_addr.sa_family) { |
| struct sockaddr_in *in; |
| struct sockaddr_in6 *in6; |
| |
| case AF_INET: |
| in = (struct sockaddr_in *)&local_addr; |
| |
| if (net_sin_ptr(&context->local)->sin_addr) { |
| net_ipaddr_copy(&in->sin_addr, |
| net_sin_ptr(&context->local)->sin_addr); |
| } |
| |
| in->sin_port = |
| net_sin((struct sockaddr *)&context->local)->sin_port; |
| local_port = ntohs(in->sin_port); |
| remote_port = ntohs(net_sin(&context->remote)->sin_port); |
| |
| break; |
| |
| case AF_INET6: |
| in6 = (struct sockaddr_in6 *)&local_addr; |
| |
| if (net_sin6_ptr(&context->local)->sin6_addr) { |
| net_ipaddr_copy(&in6->sin6_addr, |
| net_sin6_ptr(&context->local)->sin6_addr); |
| } |
| |
| in6->sin6_port = |
| net_sin6((struct sockaddr *)&context->local)->sin6_port; |
| local_port = ntohs(in6->sin6_port); |
| remote_port = ntohs(net_sin6(&context->remote)->sin6_port); |
| |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| context->user_data = user_data; |
| |
| return net_conn_register(net_context_get_ip_proto(context), |
| local_addr.sa_family, |
| context->flags & NET_CONTEXT_REMOTE_ADDR_SET ? |
| &context->remote : NULL, |
| &local_addr, |
| remote_port, local_port, |
| tcp_pkt_received, context, |
| &context->conn_handler); |
| } |
| |
| int net_tcp_recv(struct net_context *context, net_context_recv_cb_t cb, |
| void *user_data) |
| { |
| struct tcp *conn = context->tcp; |
| |
| NET_DBG("context: %p, cb: %p, user_data: %p", context, cb, user_data); |
| |
| context->recv_cb = cb; |
| |
| if (conn) { |
| conn->recv_user_data = user_data; |
| } |
| |
| return 0; |
| } |
| |
| void net_tcp_init(void) |
| { |
| /* nothing to do here */ |
| } |
| |
| 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); |
| } |
| |
| 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; |
| } |
| |
| #if defined(CONFIG_NET_TEST_PROTOCOL) |
| static sys_slist_t tp_q = SYS_SLIST_STATIC_INIT(&tp_q); |
| |
| static struct net_buf *tcp_win_pop(struct tcp_win *w, const char *name, |
| size_t len) |
| { |
| struct net_buf *buf, *out = NULL; |
| |
| NET_ASSERT_INFO(len, "Invalid request, len: %zu", len); |
| |
| NET_ASSERT_INFO(len <= w->len, "Insufficient window length, " |
| "len: %zu, req: %zu", w->len, len); |
| while (len) { |
| |
| buf = tcp_slist(&w->bufs, get, struct net_buf, user_data); |
| |
| w->len -= buf->len; |
| |
| out = out ? net_buf_frag_add(out, buf) : buf; |
| |
| len -= buf->len; |
| } |
| |
| NET_ASSERT_INFO(len == 0, "Unfulfilled request, len: %zu", len); |
| |
| NET_DBG("%s len=%zu", name, net_buf_frags_len(out)); |
| |
| return out; |
| } |
| |
| static ssize_t tcp_recv(int fd, void *buf, size_t len, int flags) |
| { |
| struct tcp *conn = (void *)sys_slist_peek_head(&tcp_conns); |
| ssize_t bytes_received = conn->rcv->len; |
| struct net_buf *data = tcp_win_pop(conn->rcv, "RCV", bytes_received); |
| |
| NET_ASSERT_INFO(bytes_received <= len, "Unimplemented"); |
| |
| net_buf_linearize(buf, len, data, 0, net_buf_frags_len(data)); |
| |
| tcp_chain_free(data); |
| |
| return bytes_received; |
| } |
| |
| static void tcp_step(void) |
| { |
| struct net_pkt *pkt = (void *) sys_slist_get(&tp_q); |
| |
| if (pkt) { |
| struct tcp *conn = tcp_conn_search(pkt); |
| |
| if (conn == NULL) { |
| /* conn = tcp_conn_new(pkt); */ |
| } |
| |
| tcp_in(conn, pkt); |
| } |
| } |
| |
| static void tp_init(struct tcp *conn, struct tp *tp) |
| { |
| struct tp out = { |
| .msg = "", |
| .status = "", |
| .state = tcp_state_to_str(conn->state, true), |
| .seq = conn->seq, |
| .ack = conn->ack, |
| .rcv = "", |
| .data = "", |
| .op = "", |
| }; |
| |
| *tp = out; |
| } |
| |
| static void tcp_to_json(struct tcp *conn, void *data, size_t *data_len) |
| { |
| struct tp tp; |
| |
| tp_init(conn, &tp); |
| |
| tp_encode(&tp, data, data_len); |
| } |
| |
| bool tp_input(struct net_pkt *pkt) |
| { |
| struct net_ipv4_hdr *ip = ip_get(pkt); |
| struct net_udp_hdr *uh = (void *) (ip + 1); |
| size_t data_len = ntohs(uh->len) - sizeof(*uh); |
| struct tcp *conn = tcp_conn_search(pkt); |
| size_t json_len = 0; |
| struct tp *tp; |
| struct tp_new *tp_new; |
| enum tp_type type; |
| bool responded = false; |
| static char buf[512]; |
| |
| if (ip->proto != IPPROTO_UDP || 4242 != ntohs(uh->dst_port)) { |
| return false; |
| } |
| |
| net_pkt_skip(pkt, sizeof(*ip) + sizeof(*uh)); |
| net_pkt_read(pkt, buf, data_len); |
| buf[data_len] = '\0'; |
| data_len += 1; |
| |
| type = json_decode_msg(buf, data_len); |
| |
| data_len = ntohs(uh->len) - sizeof(*uh); |
| net_pkt_cursor_init(pkt); |
| net_pkt_skip(pkt, sizeof(*ip) + sizeof(*uh)); |
| net_pkt_read(pkt, buf, data_len); |
| buf[data_len] = '\0'; |
| data_len += 1; |
| |
| switch (type) { |
| case TP_CONFIG_REQUEST: |
| tp_new = json_to_tp_new(buf, data_len); |
| break; |
| default: |
| tp = json_to_tp(buf, data_len); |
| break; |
| } |
| |
| switch (type) { |
| case TP_COMMAND: |
| if (is("CONNECT", tp->op)) { |
| u8_t data_to_send[128]; |
| size_t len = tp_str_to_hex(data_to_send, |
| sizeof(data_to_send), tp->data); |
| tp_output(pkt->iface, buf, 1); |
| responded = true; |
| |
| { |
| struct net_context *context = tcp_calloc(1, |
| sizeof(struct net_context)); |
| net_tcp_get(context); |
| conn = context->tcp; |
| conn->dst = tcp_endpoint_new(pkt, SRC); |
| conn->src = tcp_endpoint_new(pkt, DST); |
| conn->iface = pkt->iface; |
| tcp_conn_ref(conn); |
| } |
| conn->seq = tp->seq; |
| if (len > 0) { |
| tcp_win_append(conn->snd, "SND", data_to_send, |
| len); |
| } |
| tcp_in(conn, NULL); |
| } |
| if (is("CLOSE", tp->op)) { |
| tp_trace = false; |
| { |
| struct net_context *context; |
| |
| conn = (void *)sys_slist_peek_head(&tcp_conns); |
| context = conn->context; |
| tcp_conn_unref(conn); |
| tcp_conn_unref(conn); |
| tcp_free(context); |
| } |
| tp_mem_stat(); |
| tp_nbuf_stat(); |
| tp_pkt_stat(); |
| tp_seq_stat(); |
| } |
| if (is("CLOSE2", tp->op)) { |
| struct tcp *conn = |
| (void *)sys_slist_peek_head(&tcp_conns); |
| net_tcp_put(conn->context); |
| } |
| if (is("RECV", tp->op)) { |
| #define HEXSTR_SIZE 64 |
| char hexstr[HEXSTR_SIZE]; |
| ssize_t len = tcp_recv(0, buf, sizeof(buf), 0); |
| |
| tp_init(conn, tp); |
| bin2hex(buf, len, hexstr, HEXSTR_SIZE); |
| tp->data = hexstr; |
| NET_DBG("%zd = tcp_recv(\"%s\")", len, tp->data); |
| json_len = sizeof(buf); |
| tp_encode(tp, buf, &json_len); |
| } |
| if (is("SEND", tp->op)) { |
| ssize_t len = tp_str_to_hex(buf, sizeof(buf), tp->data); |
| struct tcp *conn = |
| (void *)sys_slist_peek_head(&tcp_conns); |
| |
| tp_output(pkt->iface, buf, 1); |
| responded = true; |
| NET_DBG("tcp_send(\"%s\")", tp->data); |
| _tcp_send(conn, buf, len, 0); |
| } |
| break; |
| case TP_CONFIG_REQUEST: |
| tp_new_find_and_apply(tp_new, "tcp_rto", &tcp_rto, TP_INT); |
| tp_new_find_and_apply(tp_new, "tcp_retries", &tcp_retries, |
| TP_INT); |
| tp_new_find_and_apply(tp_new, "tcp_window", &tcp_window, |
| TP_INT); |
| tp_new_find_and_apply(tp_new, "tp_trace", &tp_trace, TP_BOOL); |
| tp_new_find_and_apply(tp_new, "tcp_echo", &tcp_echo, TP_BOOL); |
| break; |
| case TP_INTROSPECT_REQUEST: |
| json_len = sizeof(buf); |
| conn = (void *)sys_slist_peek_head(&tcp_conns); |
| tcp_to_json(conn, buf, &json_len); |
| break; |
| case TP_DEBUG_STOP: case TP_DEBUG_CONTINUE: |
| tp_state = tp->type; |
| break; |
| case TP_DEBUG_STEP: |
| tcp_step(); |
| break; |
| default: |
| NET_ASSERT_INFO(false, "Unimplemented tp command: %s", tp->msg); |
| } |
| |
| if (json_len) { |
| tp_output(pkt->iface, buf, json_len); |
| } else if ((TP_CONFIG_REQUEST == type || TP_COMMAND == type) |
| && responded == false) { |
| tp_output(pkt->iface, buf, 1); |
| } |
| |
| return true; |
| } |
| #endif /* end of IS_ENABLED(CONFIG_NET_TEST_PROTOCOL) */ |