#include "lwip/memp.h"
#include "lwip/snmp.h"
#include "lwip/tcp.h"
+#include "lwip/tcp_impl.h"
+#include "lwip/debug.h"
+#include "lwip/stats.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/nd6.h"
#include <string.h>
+#ifndef TCP_LOCAL_PORT_RANGE_START
+/* From http://www.iana.org/assignments/port-numbers:
+ "The Dynamic and/or Private Ports are those from 49152 through 65535" */
+#define TCP_LOCAL_PORT_RANGE_START 0xc000
+#define TCP_LOCAL_PORT_RANGE_END 0xffff
+#define TCP_ENSURE_LOCAL_PORT_RANGE(port) (((port) & ~TCP_LOCAL_PORT_RANGE_START) + TCP_LOCAL_PORT_RANGE_START)
+#endif
+
+#if LWIP_TCP_KEEPALIVE
+#define TCP_KEEP_DUR(pcb) ((pcb)->keep_cnt * (pcb)->keep_intvl)
+#define TCP_KEEP_INTVL(pcb) ((pcb)->keep_intvl)
+#else /* LWIP_TCP_KEEPALIVE */
+#define TCP_KEEP_DUR(pcb) TCP_MAXIDLE
+#define TCP_KEEP_INTVL(pcb) TCP_KEEPINTVL_DEFAULT
+#endif /* LWIP_TCP_KEEPALIVE */
+
+const char * const tcp_state_str[] = {
+ "CLOSED",
+ "LISTEN",
+ "SYN_SENT",
+ "SYN_RCVD",
+ "ESTABLISHED",
+ "FIN_WAIT_1",
+ "FIN_WAIT_2",
+ "CLOSE_WAIT",
+ "CLOSING",
+ "LAST_ACK",
+ "TIME_WAIT"
+};
+
+/* last local TCP port */
+static u16_t tcp_port = TCP_LOCAL_PORT_RANGE_START;
+
/* Incremented every coarse grained timer shot (typically every 500 ms). */
u32_t tcp_ticks;
const u8_t tcp_backoff[13] =
{ 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7};
+ /* Times per slowtmr hits */
+const u8_t tcp_persist_backoff[7] = { 3, 6, 12, 24, 48, 96, 120 };
/* The TCP PCB lists. */
/** List of all TCP PCBs bound but not yet (connected || listening) */
-struct tcp_pcb *tcp_bound_pcbs;
+struct tcp_pcb *tcp_bound_pcbs;
/** List of all TCP PCBs in LISTEN state */
union tcp_listen_pcbs_t tcp_listen_pcbs;
/** List of all TCP PCBs that are in a state in which
* they accept or send data. */
-struct tcp_pcb *tcp_active_pcbs;
+struct tcp_pcb *tcp_active_pcbs;
/** List of all TCP PCBs in TIME-WAIT state */
struct tcp_pcb *tcp_tw_pcbs;
+#define NUM_TCP_PCB_LISTS 4
+#define NUM_TCP_PCB_LISTS_NO_TIME_WAIT 3
+/** An array with all (non-temporary) PCB lists, mainly used for smaller code size */
+struct tcp_pcb ** const tcp_pcb_lists[] = {&tcp_listen_pcbs.pcbs, &tcp_bound_pcbs,
+ &tcp_active_pcbs, &tcp_tw_pcbs};
+
+/** Only used for temporary storage. */
struct tcp_pcb *tcp_tmp_pcb;
+u8_t tcp_active_pcbs_changed;
+
+/** Timer counter to handle calling slow-timer from tcp_tmr() */
static u8_t tcp_timer;
+static u8_t tcp_timer_ctr;
static u16_t tcp_new_port(void);
+/**
+ * Initialize this module.
+ */
+void
+tcp_init(void)
+{
+#if LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS && defined(LWIP_RAND)
+ tcp_port = TCP_ENSURE_LOCAL_PORT_RANGE(LWIP_RAND());
+#endif /* LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS && defined(LWIP_RAND) */
+}
+
/**
* Called periodically to dispatch TCP timers.
- *
*/
void
tcp_tmr(void)
}
/**
- * Closes the connection held by the PCB.
+ * Closes the TX side of a connection held by the PCB.
+ * For tcp_close(), a RST is sent if the application didn't receive all data
+ * (tcp_recved() not called for all data passed to recv callback).
*
* Listening pcbs are freed and may not be referenced any more.
* Connection pcbs are freed if not yet connected and may not be referenced
* @return ERR_OK if connection has been closed
* another err_t if closing failed and pcb is not freed
*/
-err_t
-tcp_close(struct tcp_pcb *pcb)
+static err_t
+tcp_close_shutdown(struct tcp_pcb *pcb, u8_t rst_on_unacked_data)
{
err_t err;
-#if TCP_DEBUG
- LWIP_DEBUGF(TCP_DEBUG, ("tcp_close: closing in "));
- tcp_debug_print_state(pcb->state);
-#endif /* TCP_DEBUG */
+ if (rst_on_unacked_data && (pcb->state != LISTEN)) {
+ if ((pcb->refused_data != NULL) || (pcb->rcv_wnd != TCP_WND)) {
+ /* Not all data received by application, send RST to tell the remote
+ side about this. */
+ LWIP_ASSERT("pcb->flags & TF_RXCLOSED", pcb->flags & TF_RXCLOSED);
+
+ /* don't call tcp_abort here: we must not deallocate the pcb since
+ that might not be expected when calling tcp_close */
+ tcp_rst(pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,
+ pcb->local_port, pcb->remote_port, PCB_ISIPV6(pcb));
+
+ tcp_pcb_purge(pcb);
+
+ /* TODO: to which state do we move now? */
+
+ /* move to TIME_WAIT since we close actively */
+ TCP_RMV_ACTIVE(pcb);
+ pcb->state = TIME_WAIT;
+ TCP_REG(&tcp_tw_pcbs, pcb);
+
+ return ERR_OK;
+ }
+ }
switch (pcb->state) {
case CLOSED:
* is erroneous, but this should never happen as the pcb has in those cases
* been freed, and so any remaining handles are bogus. */
err = ERR_OK;
- TCP_RMV(&tcp_bound_pcbs, pcb);
+ if (pcb->local_port != 0) {
+ TCP_RMV(&tcp_bound_pcbs, pcb);
+ }
memp_free(MEMP_TCP_PCB, pcb);
pcb = NULL;
break;
case LISTEN:
err = ERR_OK;
- tcp_pcb_remove((struct tcp_pcb **)&tcp_listen_pcbs.pcbs, pcb);
+ tcp_pcb_remove(&tcp_listen_pcbs.pcbs, pcb);
memp_free(MEMP_TCP_PCB_LISTEN, pcb);
pcb = NULL;
break;
case SYN_SENT:
err = ERR_OK;
- tcp_pcb_remove(&tcp_active_pcbs, pcb);
+ TCP_PCB_REMOVE_ACTIVE(pcb);
memp_free(MEMP_TCP_PCB, pcb);
pcb = NULL;
snmp_inc_tcpattemptfails();
break;
case SYN_RCVD:
- err = tcp_send_ctrl(pcb, TCP_FIN);
+ err = tcp_send_fin(pcb);
if (err == ERR_OK) {
snmp_inc_tcpattemptfails();
pcb->state = FIN_WAIT_1;
}
break;
case ESTABLISHED:
- err = tcp_send_ctrl(pcb, TCP_FIN);
+ err = tcp_send_fin(pcb);
if (err == ERR_OK) {
snmp_inc_tcpestabresets();
pcb->state = FIN_WAIT_1;
}
break;
case CLOSE_WAIT:
- err = tcp_send_ctrl(pcb, TCP_FIN);
+ err = tcp_send_fin(pcb);
if (err == ERR_OK) {
snmp_inc_tcpestabresets();
pcb->state = LAST_ACK;
returns (unsent data is sent from tcp timer functions, also), we don't care
for the return value of tcp_output for now. */
/* @todo: When implementing SO_LINGER, this must be changed somehow:
- If SOF_LINGER is set, the data should be sent when tcp_close returns. */
+ If SOF_LINGER is set, the data should be sent and acked before close returns.
+ This can only be valid for sequential APIs, not for the raw API. */
tcp_output(pcb);
}
return err;
}
/**
- * Aborts a connection by sending a RST to the remote host and deletes
- * the local protocol control block. This is done when a connection is
- * killed because of shortage of memory.
+ * Closes the connection held by the PCB.
+ *
+ * Listening pcbs are freed and may not be referenced any more.
+ * Connection pcbs are freed if not yet connected and may not be referenced
+ * any more. If a connection is established (at least SYN received or in
+ * a closing state), the connection is closed, and put in a closing state.
+ * The pcb is then automatically freed in tcp_slowtmr(). It is therefore
+ * unsafe to reference it (unless an error is returned).
+ *
+ * @param pcb the tcp_pcb to close
+ * @return ERR_OK if connection has been closed
+ * another err_t if closing failed and pcb is not freed
+ */
+err_t
+tcp_close(struct tcp_pcb *pcb)
+{
+#if TCP_DEBUG
+ LWIP_DEBUGF(TCP_DEBUG, ("tcp_close: closing in "));
+ tcp_debug_print_state(pcb->state);
+#endif /* TCP_DEBUG */
+
+ if (pcb->state != LISTEN) {
+ /* Set a flag not to receive any more data... */
+ pcb->flags |= TF_RXCLOSED;
+ }
+ /* ... and close */
+ return tcp_close_shutdown(pcb, 1);
+}
+
+/**
+ * Causes all or part of a full-duplex connection of this PCB to be shut down.
+ * This doesn't deallocate the PCB!
+ *
+ * @param pcb PCB to shutdown
+ * @param shut_rx shut down receive side if this is != 0
+ * @param shut_tx shut down send side if this is != 0
+ * @return ERR_OK if shutdown succeeded (or the PCB has already been shut down)
+ * another err_t on error.
+ */
+err_t
+tcp_shutdown(struct tcp_pcb *pcb, int shut_rx, int shut_tx)
+{
+ if (pcb->state == LISTEN) {
+ return ERR_CONN;
+ }
+ if (shut_rx) {
+ /* shut down the receive side: free buffered data... */
+ if (pcb->refused_data != NULL) {
+ pbuf_free(pcb->refused_data);
+ pcb->refused_data = NULL;
+ }
+ /* ... and set a flag not to receive any more data */
+ pcb->flags |= TF_RXCLOSED;
+ }
+ if (shut_tx) {
+ /* This can't happen twice since if it succeeds, the pcb's state is changed.
+ Only close in these states as the others directly deallocate the PCB */
+ switch (pcb->state) {
+ case SYN_RCVD:
+ case ESTABLISHED:
+ case CLOSE_WAIT:
+ return tcp_close_shutdown(pcb, 0);
+ default:
+ /* don't shut down other states */
+ break;
+ }
+ }
+ /* @todo: return another err_t if not in correct state or already shut? */
+ return ERR_OK;
+}
+
+/**
+ * Abandons a connection and optionally sends a RST to the remote
+ * host. Deletes the local protocol control block. This is done when
+ * a connection is killed because of shortage of memory.
*
* @param pcb the tcp_pcb to abort
+ * @param reset boolean to indicate whether a reset should be sent
*/
void
-tcp_abort(struct tcp_pcb *pcb)
+tcp_abandon(struct tcp_pcb *pcb, int reset)
{
u32_t seqno, ackno;
- u16_t remote_port, local_port;
- struct ip_addr remote_ip, local_ip;
#if LWIP_CALLBACK_API
- void (* errf)(void *arg, err_t err);
+ tcp_err_fn errf;
#endif /* LWIP_CALLBACK_API */
void *errf_arg;
-
+ /* pcb->state LISTEN not allowed here */
+ LWIP_ASSERT("don't call tcp_abort/tcp_abandon for listen-pcbs",
+ pcb->state != LISTEN);
/* Figure out on which TCP PCB list we are, and remove us. If we
are in an active state, call the receive function associated with
the PCB with a NULL argument, and send an RST to the remote end. */
} else {
seqno = pcb->snd_nxt;
ackno = pcb->rcv_nxt;
- ip_addr_set(&local_ip, &(pcb->local_ip));
- ip_addr_set(&remote_ip, &(pcb->remote_ip));
- local_port = pcb->local_port;
- remote_port = pcb->remote_port;
#if LWIP_CALLBACK_API
errf = pcb->errf;
#endif /* LWIP_CALLBACK_API */
errf_arg = pcb->callback_arg;
- tcp_pcb_remove(&tcp_active_pcbs, pcb);
+ TCP_PCB_REMOVE_ACTIVE(pcb);
if (pcb->unacked != NULL) {
tcp_segs_free(pcb->unacked);
}
tcp_segs_free(pcb->ooseq);
}
#endif /* TCP_QUEUE_OOSEQ */
+ if (reset) {
+ LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_abandon: sending RST\n"));
+ tcp_rst(seqno, ackno, &pcb->local_ip, &pcb->remote_ip, pcb->local_port, pcb->remote_port, PCB_ISIPV6(pcb));
+ }
memp_free(MEMP_TCP_PCB, pcb);
TCP_EVENT_ERR(errf, errf_arg, ERR_ABRT);
- LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_abort: sending RST\n"));
- tcp_rst(seqno, ackno, &local_ip, &remote_ip, local_port, remote_port);
}
}
+/**
+ * Aborts the connection by sending a RST (reset) segment to the remote
+ * host. The pcb is deallocated. This function never fails.
+ *
+ * ATTENTION: When calling this from one of the TCP callbacks, make
+ * sure you always return ERR_ABRT (and never return ERR_ABRT otherwise
+ * or you will risk accessing deallocated memory or memory leaks!
+ *
+ * @param pcb the tcp pcb to abort
+ */
+void
+tcp_abort(struct tcp_pcb *pcb)
+{
+ tcp_abandon(pcb, 1);
+}
+
/**
* Binds the connection to a local portnumber and IP address. If the
* IP address is not given (i.e., ipaddr == NULL), the IP address of
* to any local address
* @param port the local port to bind to
* @return ERR_USE if the port is already in use
+ * ERR_VAL if bind failed because the PCB is not in a valid state
* ERR_OK if bound
*/
err_t
-tcp_bind(struct tcp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)
+tcp_bind(struct tcp_pcb *pcb, ip_addr_t *ipaddr, u16_t port)
{
+ int i;
+ int max_pcb_list = NUM_TCP_PCB_LISTS;
struct tcp_pcb *cpcb;
- LWIP_ERROR("tcp_connect: can only bind in state CLOSED", pcb->state == CLOSED, return ERR_ISCONN);
+ LWIP_ERROR("tcp_bind: can only bind in state CLOSED", pcb->state == CLOSED, return ERR_VAL);
+
+#if SO_REUSE
+ /* Unless the REUSEADDR flag is set,
+ we have to check the pcbs in TIME-WAIT state, also.
+ We do not dump TIME_WAIT pcb's; they can still be matched by incoming
+ packets using both local and remote IP addresses and ports to distinguish.
+ */
+ if (ip_get_option(pcb, SOF_REUSEADDR)) {
+ max_pcb_list = NUM_TCP_PCB_LISTS_NO_TIME_WAIT;
+ }
+#endif /* SO_REUSE */
if (port == 0) {
port = tcp_new_port();
- }
- /* Check if the address already is in use. */
- /* Check the listen pcbs. */
- for(cpcb = (struct tcp_pcb *)tcp_listen_pcbs.pcbs;
- cpcb != NULL; cpcb = cpcb->next) {
- if (cpcb->local_port == port) {
- if (ip_addr_isany(&(cpcb->local_ip)) ||
- ip_addr_isany(ipaddr) ||
- ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {
- return ERR_USE;
- }
+ if (port == 0) {
+ return ERR_BUF;
}
}
- /* Check the connected pcbs. */
- for(cpcb = tcp_active_pcbs;
- cpcb != NULL; cpcb = cpcb->next) {
- if (cpcb->local_port == port) {
- if (ip_addr_isany(&(cpcb->local_ip)) ||
- ip_addr_isany(ipaddr) ||
- ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {
- return ERR_USE;
- }
- }
- }
- /* Check the bound, not yet connected pcbs. */
- for(cpcb = tcp_bound_pcbs; cpcb != NULL; cpcb = cpcb->next) {
- if (cpcb->local_port == port) {
- if (ip_addr_isany(&(cpcb->local_ip)) ||
- ip_addr_isany(ipaddr) ||
- ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {
- return ERR_USE;
- }
- }
- }
- /* @todo: until SO_REUSEADDR is implemented (see task #6995 on savannah),
- * we have to check the pcbs in TIME-WAIT state, also: */
- for(cpcb = tcp_tw_pcbs; cpcb != NULL; cpcb = cpcb->next) {
- if (cpcb->local_port == port) {
- if (ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {
- return ERR_USE;
+
+ /* Check if the address already is in use (on all lists) */
+ for (i = 0; i < max_pcb_list; i++) {
+ for(cpcb = *tcp_pcb_lists[i]; cpcb != NULL; cpcb = cpcb->next) {
+ if (cpcb->local_port == port) {
+#if SO_REUSE
+ /* Omit checking for the same port if both pcbs have REUSEADDR set.
+ For SO_REUSEADDR, the duplicate-check for a 5-tuple is done in
+ tcp_connect. */
+ if (!ip_get_option(pcb, SOF_REUSEADDR) ||
+ !ip_get_option(cpcb, SOF_REUSEADDR))
+#endif /* SO_REUSE */
+ {
+ /* @todo: check accept_any_ip_version */
+ if (IP_PCB_IPVER_EQ(pcb, cpcb) &&
+ (ipX_addr_isany(PCB_ISIPV6(pcb), &cpcb->local_ip) ||
+ ipX_addr_isany(PCB_ISIPV6(pcb), ip_2_ipX(ipaddr)) ||
+ ipX_addr_cmp(PCB_ISIPV6(pcb), &cpcb->local_ip, ip_2_ipX(ipaddr)))) {
+ return ERR_USE;
+ }
+ }
}
}
}
- if (!ip_addr_isany(ipaddr)) {
- pcb->local_ip = *ipaddr;
+ if (!ipX_addr_isany(PCB_ISIPV6(pcb), ip_2_ipX(ipaddr))) {
+ ipX_addr_set(PCB_ISIPV6(pcb), &pcb->local_ip, ip_2_ipX(ipaddr));
}
pcb->local_port = port;
TCP_REG(&tcp_bound_pcbs, pcb);
* connection to LISTEN is an irreversible process.
*
* @param pcb the original tcp_pcb
+ * @param backlog the incoming connections queue limit
* @return tcp_pcb used for listening, consumes less memory.
*
* @note The original tcp_pcb is freed. This function therefore has to be
* tpcb = tcp_listen(tpcb);
*/
struct tcp_pcb *
-tcp_listen(struct tcp_pcb *pcb)
+tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog)
{
struct tcp_pcb_listen *lpcb;
+ LWIP_UNUSED_ARG(backlog);
LWIP_ERROR("tcp_listen: pcb already connected", pcb->state == CLOSED, return NULL);
/* already listening? */
if (pcb->state == LISTEN) {
return pcb;
}
- lpcb = memp_malloc(MEMP_TCP_PCB_LISTEN);
+#if SO_REUSE
+ if (ip_get_option(pcb, SOF_REUSEADDR)) {
+ /* Since SOF_REUSEADDR allows reusing a local address before the pcb's usage
+ is declared (listen-/connection-pcb), we have to make sure now that
+ this port is only used once for every local IP. */
+ for(lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
+ if ((lpcb->local_port == pcb->local_port) &&
+ IP_PCB_IPVER_EQ(pcb, lpcb)) {
+ if (ipX_addr_cmp(PCB_ISIPV6(pcb), &lpcb->local_ip, &pcb->local_ip)) {
+ /* this address/port is already used */
+ return NULL;
+ }
+ }
+ }
+ }
+#endif /* SO_REUSE */
+ lpcb = (struct tcp_pcb_listen *)memp_malloc(MEMP_TCP_PCB_LISTEN);
if (lpcb == NULL) {
return NULL;
}
lpcb->callback_arg = pcb->callback_arg;
lpcb->local_port = pcb->local_port;
lpcb->state = LISTEN;
+ lpcb->prio = pcb->prio;
lpcb->so_options = pcb->so_options;
- lpcb->so_options |= SOF_ACCEPTCONN;
+ ip_set_option(lpcb, SOF_ACCEPTCONN);
lpcb->ttl = pcb->ttl;
lpcb->tos = pcb->tos;
- ip_addr_set(&lpcb->local_ip, &pcb->local_ip);
- TCP_RMV(&tcp_bound_pcbs, pcb);
+#if LWIP_IPV6
+ PCB_ISIPV6(lpcb) = PCB_ISIPV6(pcb);
+ lpcb->accept_any_ip_version = 0;
+#endif /* LWIP_IPV6 */
+ ipX_addr_copy(PCB_ISIPV6(pcb), lpcb->local_ip, pcb->local_ip);
+ if (pcb->local_port != 0) {
+ TCP_RMV(&tcp_bound_pcbs, pcb);
+ }
memp_free(MEMP_TCP_PCB, pcb);
#if LWIP_CALLBACK_API
lpcb->accept = tcp_accept_null;
#endif /* LWIP_CALLBACK_API */
- TCP_REG(&tcp_listen_pcbs.listen_pcbs, lpcb);
+#if TCP_LISTEN_BACKLOG
+ lpcb->accepts_pending = 0;
+ lpcb->backlog = (backlog ? backlog : 1);
+#endif /* TCP_LISTEN_BACKLOG */
+ TCP_REG(&tcp_listen_pcbs.pcbs, (struct tcp_pcb *)lpcb);
return (struct tcp_pcb *)lpcb;
}
+#if LWIP_IPV6
+/**
+ * Same as tcp_listen_with_backlog, but allows to accept IPv4 and IPv6
+ * connections, if the pcb's local address is set to ANY.
+ */
+struct tcp_pcb *
+tcp_listen_dual_with_backlog(struct tcp_pcb *pcb, u8_t backlog)
+{
+ struct tcp_pcb *lpcb;
+
+ if (!ipX_addr_isany(PCB_ISIPV6(pcb), &pcb->local_ip)) {
+ return NULL;
+ }
+ lpcb = tcp_listen_with_backlog(pcb, backlog);
+ if (lpcb != NULL) {
+ ((struct tcp_pcb_listen*)lpcb)->accept_any_ip_version = 1;
+ }
+ return lpcb;
+}
+#endif /* LWIP_IPV6 */
+
+/**
+ * Update the state that tracks the available window space to advertise.
+ *
+ * Returns how much extra window would be advertised if we sent an
+ * update now.
+ */
+u32_t tcp_update_rcv_ann_wnd(struct tcp_pcb *pcb)
+{
+ u32_t new_right_edge = pcb->rcv_nxt + pcb->rcv_wnd;
+
+ if (TCP_SEQ_GEQ(new_right_edge, pcb->rcv_ann_right_edge + LWIP_MIN((TCP_WND / 2), pcb->mss))) {
+ /* we can advertise more window */
+ pcb->rcv_ann_wnd = pcb->rcv_wnd;
+ return new_right_edge - pcb->rcv_ann_right_edge;
+ } else {
+ if (TCP_SEQ_GT(pcb->rcv_nxt, pcb->rcv_ann_right_edge)) {
+ /* Can happen due to other end sending out of advertised window,
+ * but within actual available (but not yet advertised) window */
+ pcb->rcv_ann_wnd = 0;
+ } else {
+ /* keep the right edge of window constant */
+ u32_t new_rcv_ann_wnd = pcb->rcv_ann_right_edge - pcb->rcv_nxt;
+ LWIP_ASSERT("new_rcv_ann_wnd <= 0xffff", new_rcv_ann_wnd <= 0xffff);
+ pcb->rcv_ann_wnd = (u16_t)new_rcv_ann_wnd;
+ }
+ return 0;
+ }
+}
+
/**
* This function should be called by the application when it has
* processed the data. The purpose is to advertise a larger window
void
tcp_recved(struct tcp_pcb *pcb, u16_t len)
{
- if ((u32_t)pcb->rcv_wnd + len > TCP_WND) {
+ int wnd_inflation;
+
+ /* pcb->state LISTEN not allowed here */
+ LWIP_ASSERT("don't call tcp_recved for listen-pcbs",
+ pcb->state != LISTEN);
+ LWIP_ASSERT("tcp_recved: len would wrap rcv_wnd\n",
+ len <= 0xffff - pcb->rcv_wnd );
+
+ pcb->rcv_wnd += len;
+ if (pcb->rcv_wnd > TCP_WND) {
pcb->rcv_wnd = TCP_WND;
- } else {
- pcb->rcv_wnd += len;
}
- if (!(pcb->flags & TF_ACK_DELAY) &&
- !(pcb->flags & TF_ACK_NOW)) {
- /*
- * We send an ACK here (if one is not already pending, hence
- * the above tests) as tcp_recved() implies that the application
- * has processed some data, and so we can open the receiver's
- * window to allow more to be transmitted. This could result in
- * two ACKs being sent for each received packet in some limited cases
- * (where the application is only receiving data, and is slow to
- * process it) but it is necessary to guarantee that the sender can
- * continue to transmit.
- */
- tcp_ack(pcb);
- }
- else if (pcb->flags & TF_ACK_DELAY && pcb->rcv_wnd >= TCP_WND/2) {
- /* If we can send a window update such that there is a full
- * segment available in the window, do so now. This is sort of
- * nagle-like in its goals, and tries to hit a compromise between
- * sending acks each time the window is updated, and only sending
- * window updates when a timer expires. The "threshold" used
- * above (currently TCP_WND/2) can be tuned to be more or less
- * aggressive */
+
+ wnd_inflation = tcp_update_rcv_ann_wnd(pcb);
+
+ /* If the change in the right edge of window is significant (default
+ * watermark is TCP_WND/4), then send an explicit update now.
+ * Otherwise wait for a packet to be sent in the normal course of
+ * events (or more window to be available later) */
+ if (wnd_inflation >= TCP_WND_UPDATE_THRESHOLD) {
tcp_ack_now(pcb);
+ tcp_output(pcb);
}
LWIP_DEBUGF(TCP_DEBUG, ("tcp_recved: recveived %"U16_F" bytes, wnd %"U16_F" (%"U16_F").\n",
}
/**
- * A nastly hack featuring 'goto' statements that allocates a
- * new TCP local port.
+ * Allocate a new local TCP port.
*
* @return a new (free) local TCP port number
*/
static u16_t
tcp_new_port(void)
{
+ u8_t i;
+ u16_t n = 0;
struct tcp_pcb *pcb;
-#ifndef TCP_LOCAL_PORT_RANGE_START
-#define TCP_LOCAL_PORT_RANGE_START 4096
-#define TCP_LOCAL_PORT_RANGE_END 0x7fff
-#endif
- static u16_t port = TCP_LOCAL_PORT_RANGE_START;
- again:
- if (++port > TCP_LOCAL_PORT_RANGE_END) {
- port = TCP_LOCAL_PORT_RANGE_START;
- }
-
- for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
- if (pcb->local_port == port) {
- goto again;
- }
+again:
+ if (tcp_port++ == TCP_LOCAL_PORT_RANGE_END) {
+ tcp_port = TCP_LOCAL_PORT_RANGE_START;
}
- for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
- if (pcb->local_port == port) {
- goto again;
- }
- }
- for(pcb = (struct tcp_pcb *)tcp_listen_pcbs.pcbs; pcb != NULL; pcb = pcb->next) {
- if (pcb->local_port == port) {
- goto again;
+ /* Check all PCB lists. */
+ for (i = 0; i < NUM_TCP_PCB_LISTS; i++) {
+ for(pcb = *tcp_pcb_lists[i]; pcb != NULL; pcb = pcb->next) {
+ if (pcb->local_port == tcp_port) {
+ if (++n > (TCP_LOCAL_PORT_RANGE_END - TCP_LOCAL_PORT_RANGE_START)) {
+ return 0;
+ }
+ goto again;
+ }
}
}
- return port;
+ return tcp_port;
}
/**
* other err_t values if connect request couldn't be sent
*/
err_t
-tcp_connect(struct tcp_pcb *pcb, struct ip_addr *ipaddr, u16_t port,
- err_t (* connected)(void *arg, struct tcp_pcb *tpcb, err_t err))
+tcp_connect(struct tcp_pcb *pcb, ip_addr_t *ipaddr, u16_t port,
+ tcp_connected_fn connected)
{
- u32_t optdata;
err_t ret;
u32_t iss;
+ u16_t old_local_port;
- LWIP_ERROR("tcp_connect: can only connected from state CLOSED", pcb->state == CLOSED, return ERR_ISCONN);
+ LWIP_ERROR("tcp_connect: can only connect from state CLOSED", pcb->state == CLOSED, return ERR_ISCONN);
LWIP_DEBUGF(TCP_DEBUG, ("tcp_connect to port %"U16_F"\n", port));
if (ipaddr != NULL) {
- pcb->remote_ip = *ipaddr;
+ ipX_addr_set(PCB_ISIPV6(pcb), &pcb->remote_ip, ip_2_ipX(ipaddr));
} else {
return ERR_VAL;
}
pcb->remote_port = port;
+
+ /* check if we have a route to the remote host */
+ if (ipX_addr_isany(PCB_ISIPV6(pcb), &pcb->local_ip)) {
+ /* no local IP address set, yet. */
+ struct netif *netif;
+ ipX_addr_t *local_ip;
+ ipX_route_get_local_ipX(PCB_ISIPV6(pcb), &pcb->local_ip, &pcb->remote_ip, netif, local_ip);
+ if ((netif == NULL) || (local_ip == NULL)) {
+ /* Don't even try to send a SYN packet if we have no route
+ since that will fail. */
+ return ERR_RTE;
+ }
+ /* Use the address as local address of the pcb. */
+ ipX_addr_copy(PCB_ISIPV6(pcb), pcb->local_ip, *local_ip);
+ }
+
+ old_local_port = pcb->local_port;
if (pcb->local_port == 0) {
pcb->local_port = tcp_new_port();
+ if (pcb->local_port == 0) {
+ return ERR_BUF;
+ }
}
+#if SO_REUSE
+ if (ip_get_option(pcb, SOF_REUSEADDR)) {
+ /* Since SOF_REUSEADDR allows reusing a local address, we have to make sure
+ now that the 5-tuple is unique. */
+ struct tcp_pcb *cpcb;
+ int i;
+ /* Don't check listen- and bound-PCBs, check active- and TIME-WAIT PCBs. */
+ for (i = 2; i < NUM_TCP_PCB_LISTS; i++) {
+ for(cpcb = *tcp_pcb_lists[i]; cpcb != NULL; cpcb = cpcb->next) {
+ if ((cpcb->local_port == pcb->local_port) &&
+ (cpcb->remote_port == port) &&
+ IP_PCB_IPVER_EQ(cpcb, pcb) &&
+ ipX_addr_cmp(PCB_ISIPV6(pcb), &cpcb->local_ip, &pcb->local_ip) &&
+ ipX_addr_cmp(PCB_ISIPV6(pcb), &cpcb->remote_ip, ip_2_ipX(ipaddr))) {
+ /* linux returns EISCONN here, but ERR_USE should be OK for us */
+ return ERR_USE;
+ }
+ }
+ }
+ }
+#endif /* SO_REUSE */
iss = tcp_next_iss();
pcb->rcv_nxt = 0;
pcb->snd_nxt = iss;
pcb->lastack = iss - 1;
pcb->snd_lbb = iss - 1;
pcb->rcv_wnd = TCP_WND;
+ pcb->rcv_ann_wnd = TCP_WND;
+ pcb->rcv_ann_right_edge = pcb->rcv_nxt;
pcb->snd_wnd = TCP_WND;
- /* The send MSS is updated when an MSS option is received. */
+ /* As initial send MSS, we use TCP_MSS but limit it to 536.
+ The send MSS is updated when an MSS option is received. */
pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS;
-#if LWIP_CALCULATE_EFF_SEND_MSS
- pcb->mss = tcp_eff_send_mss(pcb->mss, ipaddr);
-#endif /* LWIP_CALCULATE_EFF_SEND_MSS */
+#if TCP_CALCULATE_EFF_SEND_MSS
+ pcb->mss = tcp_eff_send_mss(pcb->mss, &pcb->local_ip, &pcb->remote_ip, PCB_ISIPV6(pcb));
+#endif /* TCP_CALCULATE_EFF_SEND_MSS */
pcb->cwnd = 1;
pcb->ssthresh = pcb->mss * 10;
- pcb->state = SYN_SENT;
-#if LWIP_CALLBACK_API
+#if LWIP_CALLBACK_API
pcb->connected = connected;
+#else /* LWIP_CALLBACK_API */
+ LWIP_UNUSED_ARG(connected);
#endif /* LWIP_CALLBACK_API */
- TCP_RMV(&tcp_bound_pcbs, pcb);
- TCP_REG(&tcp_active_pcbs, pcb);
- snmp_inc_tcpactiveopens();
-
- /* Build an MSS option */
- optdata = TCP_BUILD_MSS_OPTION();
+ /* Send a SYN together with the MSS option. */
+ ret = tcp_enqueue_flags(pcb, TCP_SYN);
+ if (ret == ERR_OK) {
+ /* SYN segment was enqueued, changed the pcbs state now */
+ pcb->state = SYN_SENT;
+ if (old_local_port != 0) {
+ TCP_RMV(&tcp_bound_pcbs, pcb);
+ }
+ TCP_REG_ACTIVE(pcb);
+ snmp_inc_tcpactiveopens();
- ret = tcp_enqueue(pcb, NULL, 0, TCP_SYN, 0, (u8_t *)&optdata, 4);
- if (ret == ERR_OK) {
tcp_output(pcb);
}
return ret;
-}
+}
/**
* Called every 500 ms and implements the retransmission timer and the timer that
void
tcp_slowtmr(void)
{
- struct tcp_pcb *pcb, *pcb2, *prev;
+ struct tcp_pcb *pcb, *prev;
u16_t eff_wnd;
u8_t pcb_remove; /* flag if a PCB should be removed */
+ u8_t pcb_reset; /* flag if a RST should be sent when removing */
err_t err;
err = ERR_OK;
++tcp_ticks;
+ ++tcp_timer_ctr;
+tcp_slowtmr_start:
/* Steps through all of the active PCBs. */
prev = NULL;
pcb = tcp_active_pcbs;
LWIP_ASSERT("tcp_slowtmr: active pcb->state != CLOSED\n", pcb->state != CLOSED);
LWIP_ASSERT("tcp_slowtmr: active pcb->state != LISTEN\n", pcb->state != LISTEN);
LWIP_ASSERT("tcp_slowtmr: active pcb->state != TIME-WAIT\n", pcb->state != TIME_WAIT);
+ if (pcb->last_timer == tcp_timer_ctr) {
+ /* skip this pcb, we have already processed it */
+ pcb = pcb->next;
+ continue;
+ }
+ pcb->last_timer = tcp_timer_ctr;
pcb_remove = 0;
+ pcb_reset = 0;
if (pcb->state == SYN_SENT && pcb->nrtx == TCP_SYNMAXRTX) {
++pcb_remove;
++pcb_remove;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max DATA retries reached\n"));
} else {
- /* Increase the retransmission timer if it is running */
- if(pcb->rtime >= 0)
- ++pcb->rtime;
-
- if (pcb->unacked != NULL && pcb->rtime >= pcb->rto) {
- /* Time for a retransmission. */
- LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_slowtmr: rtime %"S16_F" pcb->rto %"S16_F"\n",
- pcb->rtime, pcb->rto));
-
- /* Double retransmission time-out unless we are trying to
- * connect to somebody (i.e., we are in SYN_SENT). */
- if (pcb->state != SYN_SENT) {
- pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx];
+ if (pcb->persist_backoff > 0) {
+ /* If snd_wnd is zero, use persist timer to send 1 byte probes
+ * instead of using the standard retransmission mechanism. */
+ pcb->persist_cnt++;
+ if (pcb->persist_cnt >= tcp_persist_backoff[pcb->persist_backoff-1]) {
+ pcb->persist_cnt = 0;
+ if (pcb->persist_backoff < sizeof(tcp_persist_backoff)) {
+ pcb->persist_backoff++;
+ }
+ tcp_zero_window_probe(pcb);
}
-
- /* Reset the retransmission timer. */
- pcb->rtime = 0;
-
- /* Reduce congestion window and ssthresh. */
- eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);
- pcb->ssthresh = eff_wnd >> 1;
- if (pcb->ssthresh < pcb->mss) {
- pcb->ssthresh = pcb->mss * 2;
+ } else {
+ /* Increase the retransmission timer if it is running */
+ if(pcb->rtime >= 0) {
+ ++pcb->rtime;
}
- pcb->cwnd = pcb->mss;
- LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: cwnd %"U16_F" ssthresh %"U16_F"\n",
- pcb->cwnd, pcb->ssthresh));
+
+ if (pcb->unacked != NULL && pcb->rtime >= pcb->rto) {
+ /* Time for a retransmission. */
+ LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_slowtmr: rtime %"S16_F
+ " pcb->rto %"S16_F"\n",
+ pcb->rtime, pcb->rto));
+
+ /* Double retransmission time-out unless we are trying to
+ * connect to somebody (i.e., we are in SYN_SENT). */
+ if (pcb->state != SYN_SENT) {
+ pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx];
+ }
+
+ /* Reset the retransmission timer. */
+ pcb->rtime = 0;
+
+ /* Reduce congestion window and ssthresh. */
+ eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);
+ pcb->ssthresh = eff_wnd >> 1;
+ if (pcb->ssthresh < (pcb->mss << 1)) {
+ pcb->ssthresh = (pcb->mss << 1);
+ }
+ pcb->cwnd = pcb->mss;
+ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: cwnd %"U16_F
+ " ssthresh %"U16_F"\n",
+ pcb->cwnd, pcb->ssthresh));
- /* The following needs to be called AFTER cwnd is set to one mss - STJ */
- tcp_rexmit_rto(pcb);
+ /* The following needs to be called AFTER cwnd is set to one
+ mss - STJ */
+ tcp_rexmit_rto(pcb);
+ }
}
}
/* Check if this PCB has stayed too long in FIN-WAIT-2 */
if (pcb->state == FIN_WAIT_2) {
- if ((u32_t)(tcp_ticks - pcb->tmr) >
- TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {
- ++pcb_remove;
- LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in FIN-WAIT-2\n"));
+ /* If this PCB is in FIN_WAIT_2 because of SHUT_WR don't let it time out. */
+ if (pcb->flags & TF_RXCLOSED) {
+ /* PCB was fully closed (either through close() or SHUT_RDWR):
+ normal FIN-WAIT timeout handling. */
+ if ((u32_t)(tcp_ticks - pcb->tmr) >
+ TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {
+ ++pcb_remove;
+ LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in FIN-WAIT-2\n"));
+ }
}
}
- /* Check if KEEPALIVE should be sent */
- if((pcb->so_options & SOF_KEEPALIVE) && ((pcb->state == ESTABLISHED) || (pcb->state == CLOSE_WAIT))) {
-#if LWIP_TCP_KEEPALIVE
- if((u32_t)(tcp_ticks - pcb->tmr) > (pcb->keep_idle + (pcb->keep_cnt*pcb->keep_intvl)) / TCP_SLOW_INTERVAL) {
-#else
- if((u32_t)(tcp_ticks - pcb->tmr) > (pcb->keep_idle + TCP_MAXIDLE ) / TCP_SLOW_INTERVAL) {
-#endif /* LWIP_TCP_KEEPALIVE */
- LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: KEEPALIVE timeout. Aborting connection to %"U16_F".%"U16_F".%"U16_F".%"U16_F".\n",
- ip4_addr1(&pcb->remote_ip), ip4_addr2(&pcb->remote_ip),
- ip4_addr3(&pcb->remote_ip), ip4_addr4(&pcb->remote_ip)));
-
- tcp_abort(pcb);
+ /* Check if KEEPALIVE should be sent */
+ if(ip_get_option(pcb, SOF_KEEPALIVE) &&
+ ((pcb->state == ESTABLISHED) ||
+ (pcb->state == CLOSE_WAIT))) {
+ if((u32_t)(tcp_ticks - pcb->tmr) >
+ (pcb->keep_idle + TCP_KEEP_DUR(pcb)) / TCP_SLOW_INTERVAL)
+ {
+ LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: KEEPALIVE timeout. Aborting connection to "));
+ ipX_addr_debug_print(PCB_ISIPV6(pcb), TCP_DEBUG, &pcb->remote_ip);
+ LWIP_DEBUGF(TCP_DEBUG, ("\n"));
+
+ ++pcb_remove;
+ ++pcb_reset;
}
-#if LWIP_TCP_KEEPALIVE
- else if((u32_t)(tcp_ticks - pcb->tmr) > (pcb->keep_idle + pcb->keep_cnt_sent * pcb->keep_intvl) / TCP_SLOW_INTERVAL) {
-#else
- else if((u32_t)(tcp_ticks - pcb->tmr) > (pcb->keep_idle + pcb->keep_cnt_sent * TCP_KEEPINTVL_DEFAULT) / TCP_SLOW_INTERVAL) {
-#endif /* LWIP_TCP_KEEPALIVE */
- tcp_keepalive(pcb);
- pcb->keep_cnt_sent++;
+ else if((u32_t)(tcp_ticks - pcb->tmr) >
+ (pcb->keep_idle + pcb->keep_cnt_sent * TCP_KEEP_INTVL(pcb))
+ / TCP_SLOW_INTERVAL)
+ {
+ tcp_keepalive(pcb);
+ pcb->keep_cnt_sent++;
}
- }
+ }
/* If this PCB has queued out of sequence data, but has been
inactive for too long, will drop the data (it will eventually
be retransmitted). */
-#if TCP_QUEUE_OOSEQ
+#if TCP_QUEUE_OOSEQ
if (pcb->ooseq != NULL &&
- (u32_t)tcp_ticks - pcb->tmr >=
- pcb->rto * TCP_OOSEQ_TIMEOUT) {
+ (u32_t)tcp_ticks - pcb->tmr >= pcb->rto * TCP_OOSEQ_TIMEOUT) {
tcp_segs_free(pcb->ooseq);
pcb->ooseq = NULL;
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: dropping OOSEQ queued data\n"));
/* Check if this PCB has stayed too long in SYN-RCVD */
if (pcb->state == SYN_RCVD) {
if ((u32_t)(tcp_ticks - pcb->tmr) >
- TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {
+ TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {
++pcb_remove;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in SYN-RCVD\n"));
}
/* If the PCB should be removed, do it. */
if (pcb_remove) {
- tcp_pcb_purge(pcb);
+ struct tcp_pcb *pcb2;
+ tcp_err_fn err_fn;
+ void *err_arg;
+ tcp_pcb_purge(pcb);
/* Remove PCB from tcp_active_pcbs list. */
if (prev != NULL) {
LWIP_ASSERT("tcp_slowtmr: middle tcp != tcp_active_pcbs", pcb != tcp_active_pcbs);
tcp_active_pcbs = pcb->next;
}
- TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_ABRT);
+ if (pcb_reset) {
+ tcp_rst(pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,
+ pcb->local_port, pcb->remote_port, PCB_ISIPV6(pcb));
+ }
- pcb2 = pcb->next;
- memp_free(MEMP_TCP_PCB, pcb);
- pcb = pcb2;
+ err_fn = pcb->errf;
+ err_arg = pcb->callback_arg;
+ pcb2 = pcb;
+ pcb = pcb->next;
+ memp_free(MEMP_TCP_PCB, pcb2);
+
+ tcp_active_pcbs_changed = 0;
+ TCP_EVENT_ERR(err_fn, err_arg, ERR_ABRT);
+ if (tcp_active_pcbs_changed) {
+ goto tcp_slowtmr_start;
+ }
} else {
+ /* get the 'next' element now and work with 'prev' below (in case of abort) */
+ prev = pcb;
+ pcb = pcb->next;
/* We check if we should poll the connection. */
- ++pcb->polltmr;
- if (pcb->polltmr >= pcb->pollinterval) {
- pcb->polltmr = 0;
+ ++prev->polltmr;
+ if (prev->polltmr >= prev->pollinterval) {
+ prev->polltmr = 0;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: polling application\n"));
- TCP_EVENT_POLL(pcb, err);
+ tcp_active_pcbs_changed = 0;
+ TCP_EVENT_POLL(prev, err);
+ if (tcp_active_pcbs_changed) {
+ goto tcp_slowtmr_start;
+ }
+ /* if err == ERR_ABRT, 'prev' is already deallocated */
if (err == ERR_OK) {
- tcp_output(pcb);
+ tcp_output(prev);
}
}
-
- prev = pcb;
- pcb = pcb->next;
}
}
/* Steps through all of the TIME-WAIT PCBs. */
- prev = NULL;
+ prev = NULL;
pcb = tcp_tw_pcbs;
while (pcb != NULL) {
LWIP_ASSERT("tcp_slowtmr: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT);
/* If the PCB should be removed, do it. */
if (pcb_remove) {
- tcp_pcb_purge(pcb);
+ struct tcp_pcb *pcb2;
+ tcp_pcb_purge(pcb);
/* Remove PCB from tcp_tw_pcbs list. */
if (prev != NULL) {
LWIP_ASSERT("tcp_slowtmr: middle tcp != tcp_tw_pcbs", pcb != tcp_tw_pcbs);
LWIP_ASSERT("tcp_slowtmr: first pcb == tcp_tw_pcbs", tcp_tw_pcbs == pcb);
tcp_tw_pcbs = pcb->next;
}
- pcb2 = pcb->next;
- memp_free(MEMP_TCP_PCB, pcb);
- pcb = pcb2;
+ pcb2 = pcb;
+ pcb = pcb->next;
+ memp_free(MEMP_TCP_PCB, pcb2);
} else {
prev = pcb;
pcb = pcb->next;
}
/**
- * Is called every TCP_FAST_INTERVAL (250 ms) and sends delayed ACKs.
+ * Is called every TCP_FAST_INTERVAL (250 ms) and process data previously
+ * "refused" by upper layer (application) and sends delayed ACKs.
*
* Automatically called from tcp_tmr().
*/
{
struct tcp_pcb *pcb;
- /* send delayed ACKs */
- for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
- if (pcb->flags & TF_ACK_DELAY) {
- LWIP_DEBUGF(TCP_DEBUG, ("tcp_fasttmr: delayed ACK\n"));
- tcp_ack_now(pcb);
- pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
+ ++tcp_timer_ctr;
+
+tcp_fasttmr_start:
+ pcb = tcp_active_pcbs;
+
+ while(pcb != NULL) {
+ if (pcb->last_timer != tcp_timer_ctr) {
+ struct tcp_pcb *next;
+ pcb->last_timer = tcp_timer_ctr;
+ /* send delayed ACKs */
+ if (pcb->flags & TF_ACK_DELAY) {
+ LWIP_DEBUGF(TCP_DEBUG, ("tcp_fasttmr: delayed ACK\n"));
+ tcp_ack_now(pcb);
+ tcp_output(pcb);
+ pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
+ }
+
+ next = pcb->next;
+
+ /* If there is data which was previously "refused" by upper layer */
+ if (pcb->refused_data != NULL) {
+ tcp_active_pcbs_changed = 0;
+ tcp_process_refused_data(pcb);
+ if (tcp_active_pcbs_changed) {
+ /* application callback has changed the pcb list: restart the loop */
+ goto tcp_fasttmr_start;
+ }
+ }
+ pcb = next;
}
}
}
+/** Pass pcb->refused_data to the recv callback */
+err_t
+tcp_process_refused_data(struct tcp_pcb *pcb)
+{
+ err_t err;
+ u8_t refused_flags = pcb->refused_data->flags;
+ /* set pcb->refused_data to NULL in case the callback frees it and then
+ closes the pcb */
+ struct pbuf *refused_data = pcb->refused_data;
+ pcb->refused_data = NULL;
+ /* Notify again application with data previously received. */
+ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: notify kept packet\n"));
+ TCP_EVENT_RECV(pcb, refused_data, ERR_OK, err);
+ if (err == ERR_OK) {
+ /* did refused_data include a FIN? */
+ if (refused_flags & PBUF_FLAG_TCP_FIN) {
+ /* correct rcv_wnd as the application won't call tcp_recved()
+ for the FIN's seqno */
+ if (pcb->rcv_wnd != TCP_WND) {
+ pcb->rcv_wnd++;
+ }
+ TCP_EVENT_CLOSED(pcb, err);
+ if (err == ERR_ABRT) {
+ return ERR_ABRT;
+ }
+ }
+ } else if (err == ERR_ABRT) {
+ /* if err == ERR_ABRT, 'pcb' is already deallocated */
+ /* Drop incoming packets because pcb is "full" (only if the incoming
+ segment contains data). */
+ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: drop incoming packets, because pcb is \"full\"\n"));
+ return ERR_ABRT;
+ } else {
+ /* data is still refused, pbuf is still valid (go on for ACK-only packets) */
+ pcb->refused_data = refused_data;
+ }
+ return ERR_OK;
+}
+
/**
* Deallocates a list of TCP segments (tcp_seg structures).
*
* @param seg tcp_seg list of TCP segments to free
- * @return the number of pbufs that were deallocated
*/
-u8_t
+void
tcp_segs_free(struct tcp_seg *seg)
{
- u8_t count = 0;
- struct tcp_seg *next;
while (seg != NULL) {
- next = seg->next;
- count += tcp_seg_free(seg);
+ struct tcp_seg *next = seg->next;
+ tcp_seg_free(seg);
seg = next;
}
- return count;
}
/**
* Frees a TCP segment (tcp_seg structure).
*
* @param seg single tcp_seg to free
- * @return the number of pbufs that were deallocated
*/
-u8_t
+void
tcp_seg_free(struct tcp_seg *seg)
{
- u8_t count = 0;
-
if (seg != NULL) {
if (seg->p != NULL) {
- count = pbuf_free(seg->p);
+ pbuf_free(seg->p);
#if TCP_DEBUG
seg->p = NULL;
#endif /* TCP_DEBUG */
}
memp_free(MEMP_TCP_SEG, seg);
}
- return count;
}
/**
{
pcb->prio = prio;
}
-#if TCP_QUEUE_OOSEQ
+#if TCP_QUEUE_OOSEQ
/**
* Returns a copy of the given TCP segment.
* The pbuf and data are not copied, only the pointers
{
struct tcp_seg *cseg;
- cseg = memp_malloc(MEMP_TCP_SEG);
+ cseg = (struct tcp_seg *)memp_malloc(MEMP_TCP_SEG);
if (cseg == NULL) {
return NULL;
}
pbuf_ref(cseg->p);
return cseg;
}
-#endif
+#endif /* TCP_QUEUE_OOSEQ */
#if LWIP_CALLBACK_API
/**
* Default receive callback that is called if the user didn't register
* a recv callback for the pcb.
*/
-static err_t
+err_t
tcp_recv_null(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
- arg = arg;
+ LWIP_UNUSED_ARG(arg);
if (p != NULL) {
+ tcp_recved(pcb, p->tot_len);
pbuf_free(p);
} else if (err == ERR_OK) {
return tcp_close(pcb);
#endif /* LWIP_CALLBACK_API */
/**
- * Kills the oldest active connection that has lower priority than prio.
+ * Kills the oldest active connection that has the same or lower priority than
+ * 'prio'.
*
* @param prio minimum priority
*/
LWIP_DEBUGF(TCP_DEBUG, ("tcp_kill_prio: killing oldest PCB %p (%"S32_F")\n",
(void *)inactive, inactivity));
tcp_abort(inactive);
- }
+ }
}
/**
LWIP_DEBUGF(TCP_DEBUG, ("tcp_kill_timewait: killing oldest TIME-WAIT PCB %p (%"S32_F")\n",
(void *)inactive, inactivity));
tcp_abort(inactive);
- }
+ }
}
/**
struct tcp_pcb *pcb;
u32_t iss;
- pcb = memp_malloc(MEMP_TCP_PCB);
+ pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
if (pcb == NULL) {
/* Try killing oldest connection in TIME-WAIT. */
LWIP_DEBUGF(TCP_DEBUG, ("tcp_alloc: killing off oldest TIME-WAIT connection\n"));
tcp_kill_timewait();
/* Try to allocate a tcp_pcb again. */
- pcb = memp_malloc(MEMP_TCP_PCB);
+ pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
if (pcb == NULL) {
/* Try killing active connections with lower priority than the new one. */
+ LWIP_DEBUGF(TCP_DEBUG, ("tcp_alloc: killing connection with prio lower than %d\n", prio));
tcp_kill_prio(prio);
/* Try to allocate a tcp_pcb again. */
- pcb = memp_malloc(MEMP_TCP_PCB);
+ pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
+ if (pcb != NULL) {
+ /* adjust err stats: memp_malloc failed twice before */
+ MEMP_STATS_DEC(err, MEMP_TCP_PCB);
+ }
+ }
+ if (pcb != NULL) {
+ /* adjust err stats: timewait PCB was freed above */
+ MEMP_STATS_DEC(err, MEMP_TCP_PCB);
}
}
if (pcb != NULL) {
memset(pcb, 0, sizeof(struct tcp_pcb));
- pcb->prio = TCP_PRIO_NORMAL;
+ pcb->prio = prio;
pcb->snd_buf = TCP_SND_BUF;
pcb->snd_queuelen = 0;
pcb->rcv_wnd = TCP_WND;
+ pcb->rcv_ann_wnd = TCP_WND;
pcb->tos = 0;
pcb->ttl = TCP_TTL;
- /* The send MSS is updated when an MSS option is received. */
+ /* As initial send MSS, we use TCP_MSS but limit it to 536.
+ The send MSS is updated when an MSS option is received. */
pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS;
pcb->rto = 3000 / TCP_SLOW_INTERVAL;
pcb->sa = 0;
iss = tcp_next_iss();
pcb->snd_wl2 = iss;
pcb->snd_nxt = iss;
- pcb->snd_max = iss;
pcb->lastack = iss;
pcb->snd_lbb = iss;
pcb->tmr = tcp_ticks;
+ pcb->last_timer = tcp_timer_ctr;
pcb->polltmr = 0;
return tcp_alloc(TCP_PRIO_NORMAL);
}
+#if LWIP_IPV6
+/**
+ * Creates a new TCP-over-IPv6 protocol control block but doesn't
+ * place it on any of the TCP PCB lists.
+ * The pcb is not put on any list until binding using tcp_bind().
+ *
+ * @return a new tcp_pcb that initially is in state CLOSED
+ */
+struct tcp_pcb *
+tcp_new_ip6(void)
+{
+ struct tcp_pcb * pcb;
+ pcb = tcp_alloc(TCP_PRIO_NORMAL);
+ ip_set_v6(pcb, 1);
+ return pcb;
+}
+#endif /* LWIP_IPV6 */
+
/**
* Used to specify the argument that should be passed callback
* functions.
*/
void
tcp_arg(struct tcp_pcb *pcb, void *arg)
-{
+{
+ /* This function is allowed to be called for both listen pcbs and
+ connection pcbs. */
pcb->callback_arg = arg;
}
#if LWIP_CALLBACK_API
* @param recv callback function to call for this pcb when data is received
*/
void
-tcp_recv(struct tcp_pcb *pcb,
- err_t (* recv)(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err))
+tcp_recv(struct tcp_pcb *pcb, tcp_recv_fn recv)
{
+ LWIP_ASSERT("invalid socket state for recv callback", pcb->state != LISTEN);
pcb->recv = recv;
}
* @param sent callback function to call for this pcb when data is successfully sent
*/
void
-tcp_sent(struct tcp_pcb *pcb,
- err_t (* sent)(void *arg, struct tcp_pcb *tpcb, u16_t len))
+tcp_sent(struct tcp_pcb *pcb, tcp_sent_fn sent)
{
+ LWIP_ASSERT("invalid socket state for sent callback", pcb->state != LISTEN);
pcb->sent = sent;
}
* has occured on the connection.
*
* @param pcb tcp_pcb to set the err callback
- * @param errf callback function to call for this pcb when a fatal error
+ * @param err callback function to call for this pcb when a fatal error
* has occured on the connection
*/
void
-tcp_err(struct tcp_pcb *pcb,
- void (* errf)(void *arg, err_t err))
+tcp_err(struct tcp_pcb *pcb, tcp_err_fn err)
{
- pcb->errf = errf;
+ LWIP_ASSERT("invalid socket state for err callback", pcb->state != LISTEN);
+ pcb->errf = err;
}
/**
* connection has been connected to another host
*/
void
-tcp_accept(struct tcp_pcb *pcb,
- err_t (* accept)(void *arg, struct tcp_pcb *newpcb, err_t err))
+tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept)
{
- ((struct tcp_pcb_listen *)pcb)->accept = accept;
+ /* This function is allowed to be called for both listen pcbs and
+ connection pcbs. */
+ pcb->accept = accept;
}
#endif /* LWIP_CALLBACK_API */
*
*/
void
-tcp_poll(struct tcp_pcb *pcb,
- err_t (* poll)(void *arg, struct tcp_pcb *tpcb), u8_t interval)
+tcp_poll(struct tcp_pcb *pcb, tcp_poll_fn poll, u8_t interval)
{
+ LWIP_ASSERT("invalid socket state for poll", pcb->state != LISTEN);
#if LWIP_CALLBACK_API
pcb->poll = poll;
+#else /* LWIP_CALLBACK_API */
+ LWIP_UNUSED_ARG(poll);
#endif /* LWIP_CALLBACK_API */
pcb->pollinterval = interval;
}
pcb->state != LISTEN) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge\n"));
-
- if (pcb->unsent != NULL) {
+
+#if TCP_LISTEN_BACKLOG
+ if (pcb->state == SYN_RCVD) {
+ /* Need to find the corresponding listen_pcb and decrease its accepts_pending */
+ struct tcp_pcb_listen *lpcb;
+ LWIP_ASSERT("tcp_pcb_purge: pcb->state == SYN_RCVD but tcp_listen_pcbs is NULL",
+ tcp_listen_pcbs.listen_pcbs != NULL);
+ for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
+ if ((lpcb->local_port == pcb->local_port) &&
+ IP_PCB_IPVER_EQ(pcb, lpcb) &&
+ (ipX_addr_isany(PCB_ISIPV6(lpcb), &lpcb->local_ip) ||
+ ipX_addr_cmp(PCB_ISIPV6(lpcb), &pcb->local_ip, &lpcb->local_ip))) {
+ /* port and address of the listen pcb match the timed-out pcb */
+ LWIP_ASSERT("tcp_pcb_purge: listen pcb does not have accepts pending",
+ lpcb->accepts_pending > 0);
+ lpcb->accepts_pending--;
+ break;
+ }
+ }
+ }
+#endif /* TCP_LISTEN_BACKLOG */
+
+
+ if (pcb->refused_data != NULL) {
+ LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge: data left on ->refused_data\n"));
+ pbuf_free(pcb->refused_data);
+ pcb->refused_data = NULL;
+ }
+ if (pcb->unsent != NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge: not all data sent\n"));
}
- if (pcb->unacked != NULL) {
+ if (pcb->unacked != NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge: data left on ->unacked\n"));
}
-#if TCP_QUEUE_OOSEQ /* LW */
- if (pcb->ooseq != NULL) {
+#if TCP_QUEUE_OOSEQ
+ if (pcb->ooseq != NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge: data left on ->ooseq\n"));
}
+ tcp_segs_free(pcb->ooseq);
+ pcb->ooseq = NULL;
+#endif /* TCP_QUEUE_OOSEQ */
/* Stop the retransmission timer as it will expect data on unacked
queue if it fires */
pcb->rtime = -1;
- tcp_segs_free(pcb->ooseq);
- pcb->ooseq = NULL;
-#endif /* TCP_QUEUE_OOSEQ */
tcp_segs_free(pcb->unsent);
tcp_segs_free(pcb->unacked);
pcb->unacked = pcb->unsent = NULL;
+#if TCP_OVERSIZE
+ pcb->unsent_oversize = 0;
+#endif /* TCP_OVERSIZE */
}
}
* Purges the PCB and removes it from a PCB list. Any delayed ACKs are sent first.
*
* @param pcblist PCB list to purge.
- * @param pcb tcp_pcb to purge. The pcb itself is also deallocated!
+ * @param pcb tcp_pcb to purge. The pcb itself is NOT deallocated!
*/
void
tcp_pcb_remove(struct tcp_pcb **pcblist, struct tcp_pcb *pcb)
pcb->flags & TF_ACK_DELAY) {
pcb->flags |= TF_ACK_NOW;
tcp_output(pcb);
- }
+ }
+
+ if (pcb->state != LISTEN) {
+ LWIP_ASSERT("unsent segments leaking", pcb->unsent == NULL);
+ LWIP_ASSERT("unacked segments leaking", pcb->unacked == NULL);
+#if TCP_QUEUE_OOSEQ
+ LWIP_ASSERT("ooseq segments leaking", pcb->ooseq == NULL);
+#endif /* TCP_QUEUE_OOSEQ */
+ }
+
pcb->state = CLOSED;
LWIP_ASSERT("tcp_pcb_remove: tcp_pcbs_sane()", tcp_pcbs_sane());
return iss;
}
-#if LWIP_CALCULATE_EFF_SEND_MSS
+#if TCP_CALCULATE_EFF_SEND_MSS
/**
* Calcluates the effective send mss that can be used for a specific IP address
* by using ip_route to determin the netif used to send to the address and
* calculating the minimum of TCP_MSS and that netif's mtu (if set).
*/
u16_t
-tcp_eff_send_mss(u16_t sendmss, struct ip_addr *addr)
+tcp_eff_send_mss_impl(u16_t sendmss, ipX_addr_t *dest
+#if LWIP_IPV6
+ , ipX_addr_t *src, u8_t isipv6
+#endif /* LWIP_IPV6 */
+ )
{
u16_t mss_s;
struct netif *outif;
+ s16_t mtu;
+
+ outif = ipX_route(isipv6, src, dest);
+#if LWIP_IPV6
+ if (isipv6) {
+ /* First look in destination cache, to see if there is a Path MTU. */
+ mtu = nd6_get_destination_mtu(ipX_2_ip6(dest), outif);
+ } else
+#endif /* LWIP_IPV6 */
+ {
+ if (outif == NULL) {
+ return sendmss;
+ }
+ mtu = outif->mtu;
+ }
- outif = ip_route(addr);
- if ((outif != NULL) && (outif->mtu != 0)) {
- mss_s = outif->mtu - IP_HLEN - TCP_HLEN;
+ if (mtu != 0) {
+ mss_s = mtu - IP_HLEN - TCP_HLEN;
+#if LWIP_IPV6
+ /* for IPv6, substract the difference in header size */
+ mss_s -= (IP6_HLEN - IP_HLEN);
+#endif /* LWIP_IPV6 */
/* RFC 1122, chap 4.2.2.6:
* Eff.snd.MSS = min(SendMSS+20, MMS_S) - TCPhdrsize - IPoptionsize
- * but we only send options with SYN and that is never filled with data! */
+ * We correct for TCP options in tcp_write(), and don't support IP options.
+ */
sendmss = LWIP_MIN(sendmss, mss_s);
}
return sendmss;
}
-#endif /* LWIP_CALCULATE_EFF_SEND_MSS */
+#endif /* TCP_CALCULATE_EFF_SEND_MSS */
+
+const char*
+tcp_debug_state_str(enum tcp_state s)
+{
+ return tcp_state_str[s];
+}
#if TCP_DEBUG || TCP_INPUT_DEBUG || TCP_OUTPUT_DEBUG
/**
void
tcp_debug_print_state(enum tcp_state s)
{
- LWIP_DEBUGF(TCP_DEBUG, ("State: "));
- switch (s) {
- case CLOSED:
- LWIP_DEBUGF(TCP_DEBUG, ("CLOSED\n"));
- break;
- case LISTEN:
- LWIP_DEBUGF(TCP_DEBUG, ("LISTEN\n"));
- break;
- case SYN_SENT:
- LWIP_DEBUGF(TCP_DEBUG, ("SYN_SENT\n"));
- break;
- case SYN_RCVD:
- LWIP_DEBUGF(TCP_DEBUG, ("SYN_RCVD\n"));
- break;
- case ESTABLISHED:
- LWIP_DEBUGF(TCP_DEBUG, ("ESTABLISHED\n"));
- break;
- case FIN_WAIT_1:
- LWIP_DEBUGF(TCP_DEBUG, ("FIN_WAIT_1\n"));
- break;
- case FIN_WAIT_2:
- LWIP_DEBUGF(TCP_DEBUG, ("FIN_WAIT_2\n"));
- break;
- case CLOSE_WAIT:
- LWIP_DEBUGF(TCP_DEBUG, ("CLOSE_WAIT\n"));
- break;
- case CLOSING:
- LWIP_DEBUGF(TCP_DEBUG, ("CLOSING\n"));
- break;
- case LAST_ACK:
- LWIP_DEBUGF(TCP_DEBUG, ("LAST_ACK\n"));
- break;
- case TIME_WAIT:
- LWIP_DEBUGF(TCP_DEBUG, ("TIME_WAIT\n"));
- break;
- }
+ LWIP_DEBUGF(TCP_DEBUG, ("State: %s\n", tcp_state_str[s]));
}
/**
if (flags & TCP_CWR) {
LWIP_DEBUGF(TCP_DEBUG, ("CWR "));
}
+ LWIP_DEBUGF(TCP_DEBUG, ("\n"));
}
/**