/** * @file * Application layered TCP connection API (to be used from TCPIP thread)\n * This interface mimics the tcp callback API to the application while preventing * direct linking (much like virtual functions). * This way, an application can make use of other application layer protocols * on top of TCP without knowing the details (e.g. TLS, proxy connection). * * This file contains the base implementation calling into tcp. */ /* * Copyright (c) 2017 Simon Goldschmidt * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * This file is part of the lwIP TCP/IP stack. * * Author: Simon Goldschmidt * */ #include "lwip/opt.h" #if LWIP_ALTCP /* don't build if not configured for use in lwipopts.h */ #include "lwip/altcp.h" #include "lwip/altcp_tcp.h" #include "lwip/priv/altcp_priv.h" #include "lwip/tcp.h" #include "lwip/priv/tcp_priv.h" #include "lwip/mem.h" #include #define ALTCP_TCP_ASSERT_CONN(conn) do { \ LWIP_ASSERT("conn->inner_conn == NULL", (conn)->inner_conn == NULL); \ LWIP_UNUSED_ARG(conn); /* for LWIP_NOASSERT */ } while(0) #define ALTCP_TCP_ASSERT_CONN_PCB(conn, tpcb) do { \ LWIP_ASSERT("pcb mismatch", (conn)->state == tpcb); \ LWIP_UNUSED_ARG(tpcb); /* for LWIP_NOASSERT */ \ ALTCP_TCP_ASSERT_CONN(conn); } while(0) /* Variable prototype, the actual declaration is at the end of this file since it contains pointers to static functions declared here */ extern const struct altcp_functions altcp_tcp_functions; static void altcp_tcp_setup(struct altcp_pcb *conn, struct tcp_pcb *tpcb); /* callback functions for TCP */ static err_t altcp_tcp_accept(void *arg, struct tcp_pcb *new_tpcb, err_t err) { struct altcp_pcb *listen_conn = (struct altcp_pcb *)arg; if (listen_conn && listen_conn->accept) { /* create a new altcp_conn to pass to the next 'accept' callback */ struct altcp_pcb *new_conn = altcp_alloc(); if (new_conn == NULL) { return ERR_MEM; } altcp_tcp_setup(new_conn, new_tpcb); return listen_conn->accept(listen_conn->arg, new_conn, err); } return ERR_ARG; } static err_t altcp_tcp_connected(void *arg, struct tcp_pcb *tpcb, err_t err) { struct altcp_pcb *conn = (struct altcp_pcb *)arg; if (conn) { ALTCP_TCP_ASSERT_CONN_PCB(conn, tpcb); if (conn->connected) { return conn->connected(conn->arg, conn, err); } } return ERR_OK; } static err_t altcp_tcp_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { struct altcp_pcb *conn = (struct altcp_pcb *)arg; if (conn) { ALTCP_TCP_ASSERT_CONN_PCB(conn, tpcb); if (conn->recv) { return conn->recv(conn->arg, conn, p, err); } } if (p != NULL) { /* prevent memory leaks */ pbuf_free(p); } return ERR_OK; } static err_t altcp_tcp_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) { struct altcp_pcb *conn = (struct altcp_pcb *)arg; if (conn) { ALTCP_TCP_ASSERT_CONN_PCB(conn, tpcb); if (conn->sent) { return conn->sent(conn->arg, conn, len); } } return ERR_OK; } static err_t altcp_tcp_poll(void *arg, struct tcp_pcb *tpcb) { struct altcp_pcb *conn = (struct altcp_pcb *)arg; if (conn) { ALTCP_TCP_ASSERT_CONN_PCB(conn, tpcb); if (conn->poll) { return conn->poll(conn->arg, conn); } } return ERR_OK; } static void altcp_tcp_err(void *arg, err_t err) { struct altcp_pcb *conn = (struct altcp_pcb *)arg; if (conn) { conn->state = NULL; /* already freed */ if (conn->err) { conn->err(conn->arg, err); } altcp_free(conn); } } /* setup functions */ static void altcp_tcp_remove_callbacks(struct tcp_pcb *tpcb) { tcp_arg(tpcb, NULL); if (tpcb->state != LISTEN) { tcp_recv(tpcb, NULL); tcp_sent(tpcb, NULL); tcp_err(tpcb, NULL); tcp_poll(tpcb, NULL, tpcb->pollinterval); } } static void altcp_tcp_setup_callbacks(struct altcp_pcb *conn, struct tcp_pcb *tpcb) { tcp_arg(tpcb, conn); /* this might be called for LISTN when close fails... */ if (tpcb->state != LISTEN) { tcp_recv(tpcb, altcp_tcp_recv); tcp_sent(tpcb, altcp_tcp_sent); tcp_err(tpcb, altcp_tcp_err); /* tcp_poll is set when interval is set by application */ } } static void altcp_tcp_setup(struct altcp_pcb *conn, struct tcp_pcb *tpcb) { altcp_tcp_setup_callbacks(conn, tpcb); conn->state = tpcb; conn->fns = &altcp_tcp_functions; } struct altcp_pcb * altcp_tcp_new_ip_type(u8_t ip_type) { /* Allocate the tcp pcb first to invoke the priority handling code if we're out of pcbs */ struct tcp_pcb *tpcb = tcp_new_ip_type(ip_type); if (tpcb != NULL) { struct altcp_pcb *ret = altcp_alloc(); if (ret != NULL) { altcp_tcp_setup(ret, tpcb); return ret; } else { /* altcp_pcb allocation failed -> free the tcp_pcb too */ tcp_close(tpcb); } } return NULL; } /** altcp_tcp allocator function fitting to @ref altcp_allocator_t / @ref altcp_new. * * arg pointer is not used for TCP. */ struct altcp_pcb * altcp_tcp_alloc(void *arg, u8_t ip_type) { LWIP_UNUSED_ARG(arg); return altcp_tcp_new_ip_type(ip_type); } struct altcp_pcb * altcp_tcp_wrap(struct tcp_pcb *tpcb) { if (tpcb != NULL) { struct altcp_pcb *ret = altcp_alloc(); if (ret != NULL) { altcp_tcp_setup(ret, tpcb); return ret; } } return NULL; } /* "virtual" functions calling into tcp */ static void altcp_tcp_set_poll(struct altcp_pcb *conn, u8_t interval) { if (conn != NULL) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); tcp_poll(pcb, altcp_tcp_poll, interval); } } static void altcp_tcp_recved(struct altcp_pcb *conn, u16_t len) { if (conn != NULL) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); tcp_recved(pcb, len); } } static err_t altcp_tcp_bind(struct altcp_pcb *conn, const ip_addr_t *ipaddr, u16_t port) { struct tcp_pcb *pcb; if (conn == NULL) { return ERR_VAL; } ALTCP_TCP_ASSERT_CONN(conn); pcb = (struct tcp_pcb *)conn->state; return tcp_bind(pcb, ipaddr, port); } static err_t altcp_tcp_connect(struct altcp_pcb *conn, const ip_addr_t *ipaddr, u16_t port, altcp_connected_fn connected) { struct tcp_pcb *pcb; if (conn == NULL) { return ERR_VAL; } ALTCP_TCP_ASSERT_CONN(conn); conn->connected = connected; pcb = (struct tcp_pcb *)conn->state; return tcp_connect(pcb, ipaddr, port, altcp_tcp_connected); } static struct altcp_pcb * altcp_tcp_listen(struct altcp_pcb *conn, u8_t backlog, err_t *err) { struct tcp_pcb *pcb; struct tcp_pcb *lpcb; if (conn == NULL) { return NULL; } ALTCP_TCP_ASSERT_CONN(conn); pcb = (struct tcp_pcb *)conn->state; lpcb = tcp_listen_with_backlog_and_err(pcb, backlog, err); if (lpcb != NULL) { conn->state = lpcb; tcp_accept(lpcb, altcp_tcp_accept); return conn; } return NULL; } static void altcp_tcp_abort(struct altcp_pcb *conn) { if (conn != NULL) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); if (pcb) { tcp_abort(pcb); } } } static err_t altcp_tcp_close(struct altcp_pcb *conn) { struct tcp_pcb *pcb; if (conn == NULL) { return ERR_VAL; } ALTCP_TCP_ASSERT_CONN(conn); pcb = (struct tcp_pcb *)conn->state; if (pcb) { err_t err; tcp_poll_fn oldpoll = pcb->poll; altcp_tcp_remove_callbacks(pcb); err = tcp_close(pcb); if (err != ERR_OK) { /* not closed, set up all callbacks again */ altcp_tcp_setup_callbacks(conn, pcb); /* poll callback is not included in the above */ tcp_poll(pcb, oldpoll, pcb->pollinterval); return err; } conn->state = NULL; /* unsafe to reference pcb after tcp_close(). */ } altcp_free(conn); return ERR_OK; } static err_t altcp_tcp_shutdown(struct altcp_pcb *conn, int shut_rx, int shut_tx) { struct tcp_pcb *pcb; if (conn == NULL) { return ERR_VAL; } ALTCP_TCP_ASSERT_CONN(conn); pcb = (struct tcp_pcb *)conn->state; return tcp_shutdown(pcb, shut_rx, shut_tx); } static err_t altcp_tcp_write(struct altcp_pcb *conn, const void *dataptr, u16_t len, u8_t apiflags) { struct tcp_pcb *pcb; if (conn == NULL) { return ERR_VAL; } ALTCP_TCP_ASSERT_CONN(conn); pcb = (struct tcp_pcb *)conn->state; return tcp_write(pcb, dataptr, len, apiflags); } static err_t altcp_tcp_output(struct altcp_pcb *conn) { struct tcp_pcb *pcb; if (conn == NULL) { return ERR_VAL; } ALTCP_TCP_ASSERT_CONN(conn); pcb = (struct tcp_pcb *)conn->state; return tcp_output(pcb); } static u16_t altcp_tcp_mss(struct altcp_pcb *conn) { struct tcp_pcb *pcb; if (conn == NULL) { return 0; } ALTCP_TCP_ASSERT_CONN(conn); pcb = (struct tcp_pcb *)conn->state; return tcp_mss(pcb); } static u16_t altcp_tcp_sndbuf(struct altcp_pcb *conn) { struct tcp_pcb *pcb; if (conn == NULL) { return 0; } ALTCP_TCP_ASSERT_CONN(conn); pcb = (struct tcp_pcb *)conn->state; return tcp_sndbuf(pcb); } static u16_t altcp_tcp_sndqueuelen(struct altcp_pcb *conn) { struct tcp_pcb *pcb; if (conn == NULL) { return 0; } ALTCP_TCP_ASSERT_CONN(conn); pcb = (struct tcp_pcb *)conn->state; return tcp_sndqueuelen(pcb); } static void altcp_tcp_nagle_disable(struct altcp_pcb *conn) { if (conn && conn->state) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); tcp_nagle_disable(pcb); } } static void altcp_tcp_nagle_enable(struct altcp_pcb *conn) { if (conn && conn->state) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); tcp_nagle_enable(pcb); } } static int altcp_tcp_nagle_disabled(struct altcp_pcb *conn) { if (conn && conn->state) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); return tcp_nagle_disabled(pcb); } return 0; } static void altcp_tcp_setprio(struct altcp_pcb *conn, u8_t prio) { if (conn != NULL) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); tcp_setprio(pcb, prio); } } #if LWIP_TCP_KEEPALIVE static void altcp_tcp_keepalive_disable(struct altcp_pcb *conn) { if (conn && conn->state) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); ip_reset_option(pcb, SOF_KEEPALIVE); } } static void altcp_tcp_keepalive_enable(struct altcp_pcb *conn, u32_t idle, u32_t intvl, u32_t cnt) { if (conn && conn->state) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); ip_set_option(pcb, SOF_KEEPALIVE); pcb->keep_idle = idle ? idle : TCP_KEEPIDLE_DEFAULT; pcb->keep_intvl = intvl ? intvl : TCP_KEEPINTVL_DEFAULT; pcb->keep_cnt = cnt ? cnt : TCP_KEEPCNT_DEFAULT; } } #endif static void altcp_tcp_dealloc(struct altcp_pcb *conn) { LWIP_UNUSED_ARG(conn); ALTCP_TCP_ASSERT_CONN(conn); /* no private state to clean up */ } static err_t altcp_tcp_get_tcp_addrinfo(struct altcp_pcb *conn, int local, ip_addr_t *addr, u16_t *port) { if (conn) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); return tcp_tcp_get_tcp_addrinfo(pcb, local, addr, port); } return ERR_VAL; } static ip_addr_t * altcp_tcp_get_ip(struct altcp_pcb *conn, int local) { if (conn) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); if (pcb) { if (local) { return &pcb->local_ip; } else { return &pcb->remote_ip; } } } return NULL; } static u16_t altcp_tcp_get_port(struct altcp_pcb *conn, int local) { if (conn) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); if (pcb) { if (local) { return pcb->local_port; } else { return pcb->remote_port; } } } return 0; } #ifdef LWIP_DEBUG static enum tcp_state altcp_tcp_dbg_get_tcp_state(struct altcp_pcb *conn) { if (conn) { struct tcp_pcb *pcb = (struct tcp_pcb *)conn->state; ALTCP_TCP_ASSERT_CONN(conn); if (pcb) { return pcb->state; } } return CLOSED; } #endif const struct altcp_functions altcp_tcp_functions = { altcp_tcp_set_poll, altcp_tcp_recved, altcp_tcp_bind, altcp_tcp_connect, altcp_tcp_listen, altcp_tcp_abort, altcp_tcp_close, altcp_tcp_shutdown, altcp_tcp_write, altcp_tcp_output, altcp_tcp_mss, altcp_tcp_sndbuf, altcp_tcp_sndqueuelen, altcp_tcp_nagle_disable, altcp_tcp_nagle_enable, altcp_tcp_nagle_disabled, altcp_tcp_setprio, altcp_tcp_dealloc, altcp_tcp_get_tcp_addrinfo, altcp_tcp_get_ip, altcp_tcp_get_port #if LWIP_TCP_KEEPALIVE , altcp_tcp_keepalive_disable , altcp_tcp_keepalive_enable #endif #ifdef LWIP_DEBUG , altcp_tcp_dbg_get_tcp_state #endif }; #endif /* LWIP_ALTCP */