*
* These functions are generally called in the order (ip_input() ->)
* tcp_input() -> * tcp_process() -> tcp_receive() (-> application).
- *
+ *
*/
/*
#if LWIP_TCP /* don't build if not configured for use in lwipopts.h */
-#include "lwip/tcp_impl.h"
+#include "lwip/priv/tcp_priv.h"
#include "lwip/def.h"
#include "lwip/ip_addr.h"
#include "lwip/netif.h"
#include "lwip/memp.h"
#include "lwip/inet_chksum.h"
#include "lwip/stats.h"
-#include "lwip/snmp.h"
-#include "arch/perf.h"
#include "lwip/ip6.h"
#include "lwip/ip6_addr.h"
#include "lwip/inet_chksum.h"
#include "lwip/nd6.h"
#endif /* LWIP_ND6_TCP_REACHABILITY_HINTS */
+/** Initial CWND calculation as defined RFC 2581 */
+#define LWIP_TCP_CALC_INITIAL_CWND(mss) LWIP_MIN((4U * (mss)), LWIP_MAX((2U * (mss)), 4380U));
+/** Initial slow start threshold value: we use the full window */
+#define LWIP_TCP_INITIAL_SSTHRESH(pcb) ((pcb)->snd_wnd)
+
/* These variables are global to all functions involved in the input
processing of TCP segments. They are set by the tcp_input()
function. */
static struct tcp_seg inseg;
static struct tcp_hdr *tcphdr;
+static u16_t tcphdr_opt1len;
+static u8_t* tcphdr_opt2;
+static u16_t tcp_optidx;
static u32_t seqno, ackno;
static u8_t flags;
static u16_t tcplen;
#endif /* SO_REUSE */
u8_t hdrlen;
err_t err;
-#if CHECKSUM_CHECK_TCP
- u16_t chksum;
-#endif /* CHECKSUM_CHECK_TCP */
+
+ LWIP_UNUSED_ARG(inp);
PERF_START;
TCP_STATS_INC(tcp.recv);
- snmp_inc_tcpinsegs();
+ MIB2_STATS_INC(mib2.tcpinsegs);
tcphdr = (struct tcp_hdr *)p->payload;
}
/* Don't even process incoming broadcasts/multicasts. */
- if ((!ip_current_is_v6() && ip_addr_isbroadcast(ip_current_dest_addr(), inp)) ||
- ipX_addr_ismulticast(ip_current_is_v6(), ipX_current_dest_addr())) {
+ if (
+#if LWIP_IPV4
+ (!ip_current_is_v6() && ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif())) ||
+#endif /* LWIP_IPV4 */
+ ip_addr_ismulticast(ip_current_dest_addr())) {
TCP_STATS_INC(tcp.proterr);
goto dropped;
}
#if CHECKSUM_CHECK_TCP
- /* Verify TCP checksum. */
- chksum = ipX_chksum_pseudo(ip_current_is_v6(), p, IP_PROTO_TCP, p->tot_len,
- ipX_current_src_addr(), ipX_current_dest_addr());
- if (chksum != 0) {
- LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packet discarded due to failing checksum 0x%04"X16_F"\n",
- chksum));
- tcp_debug_print(tcphdr);
- TCP_STATS_INC(tcp.chkerr);
- goto dropped;
+ IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_TCP) {
+ /* Verify TCP checksum. */
+ u16_t chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len,
+ ip_current_src_addr(), ip_current_dest_addr());
+ if (chksum != 0) {
+ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packet discarded due to failing checksum 0x%04"X16_F"\n",
+ chksum));
+ tcp_debug_print(tcphdr);
+ TCP_STATS_INC(tcp.chkerr);
+ goto dropped;
+ }
}
#endif /* CHECKSUM_CHECK_TCP */
/* Move the payload pointer in the pbuf so that it points to the
TCP data instead of the TCP header. */
hdrlen = TCPH_HDRLEN(tcphdr);
- if(pbuf_header(p, -(hdrlen * 4))){
- /* drop short packets */
- LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: short packet\n"));
- TCP_STATS_INC(tcp.lenerr);
- goto dropped;
+ tcphdr_opt1len = (hdrlen * 4) - TCP_HLEN;
+ tcphdr_opt2 = NULL;
+ if (p->len < hdrlen * 4) {
+ if (p->len >= TCP_HLEN) {
+ /* TCP header fits into first pbuf, options don't - data is in the next pbuf */
+ u16_t optlen = tcphdr_opt1len;
+ pbuf_header(p, -TCP_HLEN); /* cannot fail */
+ LWIP_ASSERT("tcphdr_opt1len >= p->len", tcphdr_opt1len >= p->len);
+ LWIP_ASSERT("p->next != NULL", p->next != NULL);
+ tcphdr_opt1len = p->len;
+ if (optlen > tcphdr_opt1len) {
+ s16_t opt2len;
+ /* options continue in the next pbuf: set p to zero length and hide the
+ options in the next pbuf (adjusting p->tot_len) */
+ u8_t phret = pbuf_header(p, -(s16_t)tcphdr_opt1len);
+ LWIP_ASSERT("phret == 0", phret == 0);
+ tcphdr_opt2 = (u8_t*)p->next->payload;
+ opt2len = optlen - tcphdr_opt1len;
+ phret = pbuf_header(p->next, -opt2len);
+ LWIP_ASSERT("phret == 0", phret == 0);
+ /* p->next->payload now points to the TCP data */
+ /* manually adjust p->tot_len to changed p->next->tot_len change */
+ p->tot_len -= opt2len;
+ }
+ LWIP_ASSERT("p->len == 0", p->len == 0);
+ } else {
+ /* drop short packets */
+ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: short packet\n"));
+ TCP_STATS_INC(tcp.lenerr);
+ goto dropped;
+ }
+ } else {
+ pbuf_header(p, -(hdrlen * 4)); /* cannot fail */
}
/* Convert fields in TCP header to host byte order. */
for an active connection. */
prev = NULL;
-
- for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
+ for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
LWIP_ASSERT("tcp_input: active pcb->state != CLOSED", pcb->state != CLOSED);
LWIP_ASSERT("tcp_input: active pcb->state != TIME-WAIT", pcb->state != TIME_WAIT);
LWIP_ASSERT("tcp_input: active pcb->state != LISTEN", pcb->state != LISTEN);
if (pcb->remote_port == tcphdr->src &&
pcb->local_port == tcphdr->dest &&
- IP_PCB_IPVER_INPUT_MATCH(pcb) &&
- ipX_addr_cmp(ip_current_is_v6(), &pcb->remote_ip, ipX_current_src_addr()) &&
- ipX_addr_cmp(ip_current_is_v6(),&pcb->local_ip, ipX_current_dest_addr())) {
+ ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) &&
+ ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) {
/* Move this PCB to the front of the list so that subsequent
lookups will be faster (we exploit locality in TCP segment
arrivals). */
prev->next = pcb->next;
pcb->next = tcp_active_pcbs;
tcp_active_pcbs = pcb;
+ } else {
+ TCP_STATS_INC(tcp.cachehit);
}
LWIP_ASSERT("tcp_input: pcb->next != pcb (after cache)", pcb->next != pcb);
break;
if (pcb == NULL) {
/* If it did not go to an active connection, we check the connections
in the TIME-WAIT state. */
- for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
+ for (pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
LWIP_ASSERT("tcp_input: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT);
if (pcb->remote_port == tcphdr->src &&
pcb->local_port == tcphdr->dest &&
- IP_PCB_IPVER_INPUT_MATCH(pcb) &&
- ipX_addr_cmp(ip_current_is_v6(), &pcb->remote_ip, ipX_current_src_addr()) &&
- ipX_addr_cmp(ip_current_is_v6(),&pcb->local_ip, ipX_current_dest_addr())) {
+ ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) &&
+ ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) {
/* We don't really care enough to move this PCB to the front
of the list since we are not very likely to receive that
many segments for connections in TIME-WAIT. */
/* Finally, if we still did not get a match, we check all PCBs that
are LISTENing for incoming connections. */
prev = NULL;
- for(lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
+ for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
if (lpcb->local_port == tcphdr->dest) {
-#if LWIP_IPV6
+#if LWIP_IPV4 && LWIP_IPV6
if (lpcb->accept_any_ip_version) {
/* found an ANY-match */
#if SO_REUSE
break;
#endif /* SO_REUSE */
} else
-#endif /* LWIP_IPV6 */
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
if (IP_PCB_IPVER_INPUT_MATCH(lpcb)) {
- if (ipX_addr_cmp(ip_current_is_v6(), &lpcb->local_ip, ipX_current_dest_addr())) {
+ if (ip_addr_cmp(&lpcb->local_ip, ip_current_dest_addr())) {
/* found an exact match */
break;
- } else if (ipX_addr_isany(ip_current_is_v6(), &lpcb->local_ip)) {
+ } else if (ip_addr_isany(&lpcb->local_ip)) {
/* found an ANY-match */
#if SO_REUSE
lpcb_any = lpcb;
lpcb->next = tcp_listen_pcbs.listen_pcbs;
/* put this listening pcb at the head of the listening list */
tcp_listen_pcbs.listen_pcbs = lpcb;
+ } else {
+ TCP_STATS_INC(tcp.cachehit);
}
-
+
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for LISTENing connection.\n"));
tcp_listen_input(lpcb);
pbuf_free(p);
recv_data = NULL;
recv_flags = 0;
+ if (flags & TCP_PSH) {
+ p->flags |= PBUF_FLAG_PUSH;
+ }
+
/* If there is data which was previously "refused" by upper layer */
if (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, pcb->refused_data, ERR_OK, err);
- if (err == ERR_OK) {
- pcb->refused_data = NULL;
- } else if ((err == ERR_ABRT) || (tcplen > 0)) {
- /* 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"));
- goto dropped;
+ if ((tcp_process_refused_data(pcb) == ERR_ABRT) ||
+ ((pcb->refused_data != NULL) && (tcplen > 0))) {
+ /* pcb has been aborted or refused data is still refused and the new
+ segment contains data */
+ TCP_STATS_INC(tcp.drop);
+ MIB2_STATS_INC(mib2.tcpinerrs);
+ goto aborted;
}
}
tcp_input_pcb = pcb;
TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_RST);
tcp_pcb_remove(&tcp_active_pcbs, pcb);
memp_free(MEMP_TCP_PCB, pcb);
- } else if (recv_flags & TF_CLOSED) {
- /* The connection has been closed and we will deallocate the
- PCB. */
- tcp_pcb_remove(&tcp_active_pcbs, pcb);
- memp_free(MEMP_TCP_PCB, pcb);
} else {
err = ERR_OK;
/* If the application has registered a "sent" function to be
called when new send buffer space is available, we call it
now. */
if (pcb->acked > 0) {
- TCP_EVENT_SENT(pcb, pcb->acked, err);
- if (err == ERR_ABRT) {
- goto aborted;
+ u16_t acked;
+#if LWIP_WND_SCALE
+ /* pcb->acked is u32_t but the sent callback only takes a u16_t,
+ so we might have to call it multiple times. */
+ u32_t pcb_acked = pcb->acked;
+ while (pcb_acked > 0) {
+ acked = (u16_t)LWIP_MIN(pcb_acked, 0xffffu);
+ pcb_acked -= acked;
+#else
+ {
+ acked = pcb->acked;
+#endif
+ TCP_EVENT_SENT(pcb, (u16_t)acked, err);
+ if (err == ERR_ABRT) {
+ goto aborted;
+ }
}
}
-
+ if (recv_flags & TF_CLOSED) {
+ /* The connection has been closed and we will deallocate the
+ PCB. */
+ if (!(pcb->flags & TF_RXCLOSED)) {
+ /* Connection closed although the application has only shut down the
+ tx side: call the PCB's err callback and indicate the closure to
+ ensure the application doesn't continue using the PCB. */
+ TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_CLSD);
+ }
+ tcp_pcb_remove(&tcp_active_pcbs, pcb);
+ memp_free(MEMP_TCP_PCB, pcb);
+ goto aborted;
+ }
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+ while (recv_data != NULL) {
+ struct pbuf *rest = NULL;
+ pbuf_split_64k(recv_data, &rest);
+#else /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
if (recv_data != NULL) {
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+
LWIP_ASSERT("pcb->refused_data == NULL", pcb->refused_data == NULL);
if (pcb->flags & TF_RXCLOSED) {
/* received data although already closed -> abort (send RST) to
notify the remote host that not all data has been processed */
pbuf_free(recv_data);
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+ if (rest != NULL) {
+ pbuf_free(rest);
+ }
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
tcp_abort(pcb);
goto aborted;
}
- if (flags & TCP_PSH) {
- recv_data->flags |= PBUF_FLAG_PUSH;
- }
/* Notify application that data has been received. */
TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
if (err == ERR_ABRT) {
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+ if (rest != NULL) {
+ pbuf_free(rest);
+ }
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
goto aborted;
}
/* If the upper layer can't receive this data, store it */
if (err != ERR_OK) {
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+ if (rest != NULL) {
+ pbuf_cat(recv_data, rest);
+ }
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
pcb->refused_data = recv_data;
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is \"full\"\n"));
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+ break;
+ } else {
+ /* Upper layer received the data, go on with the rest if > 64K */
+ recv_data = rest;
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
}
}
/* If a FIN segment was received, we call the callback
function with a NULL buffer to indicate EOF. */
if (recv_flags & TF_GOT_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) {
- goto aborted;
+ if (pcb->refused_data != NULL) {
+ /* Delay this if we have refused data. */
+ pcb->refused_data->flags |= PBUF_FLAG_TCP_FIN;
+ } else {
+ /* correct rcv_wnd as the application won't call tcp_recved()
+ for the FIN's seqno */
+ if (pcb->rcv_wnd != TCP_WND_MAX(pcb)) {
+ pcb->rcv_wnd++;
+ }
+ TCP_EVENT_CLOSED(pcb, err);
+ if (err == ERR_ABRT) {
+ goto aborted;
+ }
}
}
if (!(TCPH_FLAGS(tcphdr) & TCP_RST)) {
TCP_STATS_INC(tcp.proterr);
TCP_STATS_INC(tcp.drop);
- tcp_rst(ackno, seqno + tcplen, ipX_current_dest_addr(),
- ipX_current_src_addr(), tcphdr->dest, tcphdr->src, ip_current_is_v6());
+ tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(),
+ ip_current_src_addr(), tcphdr->dest, tcphdr->src);
}
pbuf_free(p);
}
return;
dropped:
TCP_STATS_INC(tcp.drop);
- snmp_inc_tcpinerrs();
+ MIB2_STATS_INC(mib2.tcpinerrs);
pbuf_free(p);
}
struct tcp_pcb *npcb;
err_t rc;
+ if (flags & TCP_RST) {
+ /* An incoming RST should be ignored. Return. */
+ return ERR_OK;
+ }
+
/* In the LISTEN state, we check for incoming SYN segments,
creates a new PCB, and responds with a SYN|ACK. */
if (flags & TCP_ACK) {
/* For incoming segments with the ACK flag set, respond with a
RST. */
LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_listen_input: ACK in LISTEN, sending reset\n"));
- tcp_rst(ackno + 1, seqno + tcplen, ipX_current_dest_addr(),
- ipX_current_src_addr(), tcphdr->dest, tcphdr->src, ip_current_is_v6());
+ tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(),
+ ip_current_src_addr(), tcphdr->dest, tcphdr->src);
} else if (flags & TCP_SYN) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection request %"U16_F" -> %"U16_F".\n", tcphdr->src, tcphdr->dest));
#if TCP_LISTEN_BACKLOG
pcb->accepts_pending++;
#endif /* TCP_LISTEN_BACKLOG */
/* Set up the new PCB. */
-#if LWIP_IPV6
+#if LWIP_IPV4 && LWIP_IPV6
PCB_ISIPV6(npcb) = ip_current_is_v6();
-#endif /* LWIP_IPV6 */
- ipX_addr_copy(ip_current_is_v6(), npcb->local_ip, *ipX_current_dest_addr());
- ipX_addr_copy(ip_current_is_v6(), npcb->remote_ip, *ipX_current_src_addr());
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+ ip_addr_copy(npcb->local_ip, *ip_current_dest_addr());
+ ip_addr_copy(npcb->remote_ip, *ip_current_src_addr());
npcb->local_port = pcb->local_port;
npcb->remote_port = tcphdr->src;
npcb->state = SYN_RCVD;
npcb->rcv_nxt = seqno + 1;
npcb->rcv_ann_right_edge = npcb->rcv_nxt;
- npcb->snd_wnd = tcphdr->wnd;
- npcb->ssthresh = npcb->snd_wnd;
npcb->snd_wl1 = seqno - 1;/* initialise to seqno-1 to force window update */
npcb->callback_arg = pcb->callback_arg;
#if LWIP_CALLBACK_API
npcb->so_options = pcb->so_options & SOF_INHERITED;
/* Register the new PCB so that we can begin receiving segments
for it. */
- TCP_REG(&tcp_active_pcbs, npcb);
+ TCP_REG_ACTIVE(npcb);
/* Parse any options in the SYN. */
tcp_parseopt(npcb);
+ npcb->snd_wnd = SND_WND_SCALE(npcb, tcphdr->wnd);
+ npcb->snd_wnd_max = npcb->snd_wnd;
+ npcb->ssthresh = LWIP_TCP_INITIAL_SSTHRESH(npcb);
+
#if TCP_CALCULATE_EFF_SEND_MSS
npcb->mss = tcp_eff_send_mss(npcb->mss, &npcb->local_ip,
&npcb->remote_ip, PCB_ISIPV6(npcb));
#endif /* TCP_CALCULATE_EFF_SEND_MSS */
- snmp_inc_tcppassiveopens();
+ MIB2_STATS_INC(mib2.tcppassiveopens);
/* Send a SYN|ACK together with the MSS option. */
rc = tcp_enqueue_flags(npcb, TCP_SYN | TCP_ACK);
* - first check sequence number - we skip that one in TIME_WAIT (always
* acceptable since we only send ACKs)
* - second check the RST bit (... return) */
- if (flags & TCP_RST) {
+ if (flags & TCP_RST) {
return ERR_OK;
}
/* - fourth, check the SYN bit, */
if (flags & TCP_SYN) {
/* If an incoming segment is not acceptable, an acknowledgment
should be sent in reply */
- if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt+pcb->rcv_wnd)) {
+ if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd)) {
/* If the SYN is in the window it is an error, send a reset */
- tcp_rst(ackno, seqno + tcplen, ipX_current_dest_addr(),
- ipX_current_src_addr(), tcphdr->dest, tcphdr->src, ip_current_is_v6());
+ tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(),
+ ip_current_src_addr(), tcphdr->dest, tcphdr->src);
return ERR_OK;
}
} else if (flags & TCP_FIN) {
pcb->tmr = tcp_ticks;
}
- if ((tcplen > 0)) {
+ if ((tcplen > 0)) {
/* Acknowledge data, FIN or out-of-window SYN */
pcb->flags |= TF_ACK_NOW;
return tcp_output(pcb);
acceptable = 1;
}
} else {
- if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
- pcb->rcv_nxt+pcb->rcv_wnd)) {
+ if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
+ pcb->rcv_nxt + pcb->rcv_wnd)) {
acceptable = 1;
}
}
}
}
- if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)) {
+ if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)) {
/* Cope with new connection attempt after remote end crashed */
tcp_ack_now(pcb);
return ERR_OK;
}
-
+
if ((pcb->flags & TF_RXCLOSED) == 0) {
/* Update the PCB (in)activity timer unless rx is closed (see tcp_shutdown) */
pcb->tmr = tcp_ticks;
pcb->rcv_nxt = seqno + 1;
pcb->rcv_ann_right_edge = pcb->rcv_nxt;
pcb->lastack = ackno;
- pcb->snd_wnd = tcphdr->wnd;
+ pcb->snd_wnd = SND_WND_SCALE(pcb, tcphdr->wnd);
+ pcb->snd_wnd_max = pcb->snd_wnd;
pcb->snd_wl1 = seqno - 1; /* initialise to seqno - 1 to force window update */
pcb->state = ESTABLISHED;
PCB_ISIPV6(pcb));
#endif /* TCP_CALCULATE_EFF_SEND_MSS */
- /* Set ssthresh again after changing pcb->mss (already set in tcp_connect
- * but for the default value of pcb->mss) */
- pcb->ssthresh = pcb->mss * 10;
+ /* Set ssthresh again after changing 'mss' and 'snd_wnd' */
+ pcb->ssthresh = LWIP_TCP_INITIAL_SSTHRESH(pcb);
- pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);
+ pcb->cwnd = LWIP_TCP_CALC_INITIAL_CWND(pcb->mss);
+ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_process (SENT): cwnd %"TCPWNDSIZE_F
+ " ssthresh %"TCPWNDSIZE_F"\n",
+ pcb->cwnd, pcb->ssthresh));
LWIP_ASSERT("pcb->snd_queuelen > 0", (pcb->snd_queuelen > 0));
--pcb->snd_queuelen;
- LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_process: SYN-SENT --queuelen %"U16_F"\n", (u16_t)pcb->snd_queuelen));
+ LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_process: SYN-SENT --queuelen %"TCPWNDSIZE_F"\n", (tcpwnd_size_t)pcb->snd_queuelen));
rseg = pcb->unacked;
pcb->unacked = rseg->next;
+ tcp_seg_free(rseg);
/* If there's nothing left to acknowledge, stop the retransmit
timer, otherwise reset it to start again */
- if(pcb->unacked == NULL)
+ if (pcb->unacked == NULL) {
pcb->rtime = -1;
- else {
+ } else {
pcb->rtime = 0;
pcb->nrtx = 0;
}
- tcp_seg_free(rseg);
-
- /* Call the user specified function to call when sucessfully
+ /* Call the user specified function to call when successfully
* connected. */
TCP_EVENT_CONNECTED(pcb, ERR_OK, err);
if (err == ERR_ABRT) {
/* received ACK? possibly a half-open connection */
else if (flags & TCP_ACK) {
/* send a RST to bring the other side in a non-synchronized state. */
- tcp_rst(ackno, seqno + tcplen, ipX_current_dest_addr(),
- ipX_current_src_addr(), tcphdr->dest, tcphdr->src, ip_current_is_v6());
+ tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(),
+ ip_current_src_addr(), tcphdr->dest, tcphdr->src);
}
break;
case SYN_RCVD:
if (flags & TCP_ACK) {
/* expected ACK number? */
if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {
- u16_t old_cwnd;
pcb->state = ESTABLISHED;
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection established %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
#if LWIP_CALLBACK_API
}
return ERR_ABRT;
}
- old_cwnd = pcb->cwnd;
/* If there was any data contained within this ACK,
* we'd better pass it on to the application as well. */
tcp_receive(pcb);
+ /* passive open: update initial ssthresh now that the correct window is
+ known: if the remote side supports window scaling, the window sent
+ with the initial SYN can be smaller than the one used later */
+ pcb->ssthresh = LWIP_TCP_INITIAL_SSTHRESH(pcb);
+
/* Prevent ACK for SYN to generate a sent event */
if (pcb->acked != 0) {
pcb->acked--;
}
- pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss);
+ pcb->cwnd = LWIP_TCP_CALC_INITIAL_CWND(pcb->mss);
+ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_process (SYN_RCVD): cwnd %"TCPWNDSIZE_F
+ " ssthresh %"TCPWNDSIZE_F"\n",
+ pcb->cwnd, pcb->ssthresh));
if (recv_flags & TF_GOT_FIN) {
tcp_ack_now(pcb);
}
} else {
/* incorrect ACK number, send RST */
- tcp_rst(ackno, seqno + tcplen, ipX_current_dest_addr(),
- ipX_current_src_addr(), tcphdr->dest, tcphdr->src, ip_current_is_v6());
+ tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(),
+ ip_current_src_addr(), tcphdr->dest, tcphdr->src);
}
} else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)) {
/* Looks like another copy of the SYN - retransmit our SYN-ACK */
("TCP connection closed: FIN_WAIT_1 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_ack_now(pcb);
tcp_pcb_purge(pcb);
- TCP_RMV(&tcp_active_pcbs, pcb);
+ TCP_RMV_ACTIVE(pcb);
pcb->state = TIME_WAIT;
TCP_REG(&tcp_tw_pcbs, pcb);
} else {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: FIN_WAIT_2 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_ack_now(pcb);
tcp_pcb_purge(pcb);
- TCP_RMV(&tcp_active_pcbs, pcb);
+ TCP_RMV_ACTIVE(pcb);
pcb->state = TIME_WAIT;
TCP_REG(&tcp_tw_pcbs, pcb);
}
if (flags & TCP_ACK && ackno == pcb->snd_nxt) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: CLOSING %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_pcb_purge(pcb);
- TCP_RMV(&tcp_active_pcbs, pcb);
+ TCP_RMV_ACTIVE(pcb);
pcb->state = TIME_WAIT;
TCP_REG(&tcp_tw_pcbs, pcb);
}
/* received segment overlaps all following segments */
tcp_segs_free(next);
next = NULL;
- }
- else {
+ } else {
/* delete some following segments
oos queue may have segments with FIN flag */
while (next &&
/**
* Called by tcp_process. Checks if the given segment is an ACK for outstanding
- * data, and if so frees the memory of the buffered data. Next, is places the
+ * data, and if so frees the memory of the buffered data. Next, it places the
* segment on any of the receive queues (pcb->recved or pcb->ooseq). If the segment
* is buffered, the pbuf is referenced by pbuf_ref so that it will not be freed until
- * i it has been removed from the buffer.
+ * it has been removed from the buffer.
*
* If the incoming segment constitutes an ACK for a segment that was used for RTT
* estimation, the RTT is estimated here as well.
u32_t right_wnd_edge;
u16_t new_tot_len;
int found_dupack = 0;
+#if TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS
+ u32_t ooseq_blen;
+ u16_t ooseq_qlen;
+#endif /* TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS */
+
+ LWIP_ASSERT("tcp_receive: wrong state", pcb->state >= ESTABLISHED);
if (flags & TCP_ACK) {
right_wnd_edge = pcb->snd_wnd + pcb->snd_wl2;
/* Update window. */
if (TCP_SEQ_LT(pcb->snd_wl1, seqno) ||
(pcb->snd_wl1 == seqno && TCP_SEQ_LT(pcb->snd_wl2, ackno)) ||
- (pcb->snd_wl2 == ackno && tcphdr->wnd > pcb->snd_wnd)) {
- pcb->snd_wnd = tcphdr->wnd;
+ (pcb->snd_wl2 == ackno && (u32_t)SND_WND_SCALE(pcb, tcphdr->wnd) > pcb->snd_wnd)) {
+ pcb->snd_wnd = SND_WND_SCALE(pcb, tcphdr->wnd);
+ /* keep track of the biggest window announced by the remote host to calculate
+ the maximum segment size */
+ if (pcb->snd_wnd_max < pcb->snd_wnd) {
+ pcb->snd_wnd_max = pcb->snd_wnd;
+ }
pcb->snd_wl1 = seqno;
pcb->snd_wl2 = ackno;
- if (pcb->snd_wnd > 0 && pcb->persist_backoff > 0) {
+ if (pcb->snd_wnd == 0) {
+ if (pcb->persist_backoff == 0) {
+ /* start persist timer */
+ pcb->persist_cnt = 0;
+ pcb->persist_backoff = 1;
+ }
+ } else if (pcb->persist_backoff > 0) {
+ /* stop persist timer */
pcb->persist_backoff = 0;
}
- LWIP_DEBUGF(TCP_WND_DEBUG, ("tcp_receive: window update %"U16_F"\n", pcb->snd_wnd));
+ LWIP_DEBUGF(TCP_WND_DEBUG, ("tcp_receive: window update %"TCPWNDSIZE_F"\n", pcb->snd_wnd));
#if TCP_WND_DEBUG
} else {
- if (pcb->snd_wnd != tcphdr->wnd) {
- LWIP_DEBUGF(TCP_WND_DEBUG,
+ if (pcb->snd_wnd != (tcpwnd_size_t)SND_WND_SCALE(pcb, tcphdr->wnd)) {
+ LWIP_DEBUGF(TCP_WND_DEBUG,
("tcp_receive: no window update lastack %"U32_F" ackno %"
U32_F" wl1 %"U32_F" seqno %"U32_F" wl2 %"U32_F"\n",
pcb->lastack, ackno, pcb->snd_wl1, seqno, pcb->snd_wl2));
/* (From Stevens TCP/IP Illustrated Vol II, p970.) Its only a
* duplicate ack if:
- * 1) It doesn't ACK new data
- * 2) length of received packet is zero (i.e. no payload)
- * 3) the advertised window hasn't changed
+ * 1) It doesn't ACK new data
+ * 2) length of received packet is zero (i.e. no payload)
+ * 3) the advertised window hasn't changed
* 4) There is outstanding unacknowledged data (retransmission timer running)
* 5) The ACK is == biggest ACK sequence number so far seen (snd_una)
- *
- * If it passes all five, should process as a dupack:
- * a) dupacks < 3: do nothing
- * b) dupacks == 3: fast retransmit
- * c) dupacks > 3: increase cwnd
- *
+ *
+ * If it passes all five, should process as a dupack:
+ * a) dupacks < 3: do nothing
+ * b) dupacks == 3: fast retransmit
+ * c) dupacks > 3: increase cwnd
+ *
* If it only passes 1-3, should reset dupack counter (and add to
* stats, which we don't do in lwIP)
*
/* Clause 2 */
if (tcplen == 0) {
/* Clause 3 */
- if (pcb->snd_wl2 + pcb->snd_wnd == right_wnd_edge){
+ if (pcb->snd_wl2 + pcb->snd_wnd == right_wnd_edge) {
/* Clause 4 */
if (pcb->rtime >= 0) {
/* Clause 5 */
if (pcb->lastack == ackno) {
found_dupack = 1;
- if (pcb->dupacks + 1 > pcb->dupacks)
+ if ((u8_t)(pcb->dupacks + 1) > pcb->dupacks) {
++pcb->dupacks;
+ }
if (pcb->dupacks > 3) {
/* Inflate the congestion window, but not if it means that
the value overflows. */
- if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
+ if ((tcpwnd_size_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
pcb->cwnd += pcb->mss;
}
} else if (pcb->dupacks == 3) {
if (!found_dupack) {
pcb->dupacks = 0;
}
- } else if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)){
+ } else if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {
/* We come here when the ACK acknowledges new data. */
/* Reset the "IN Fast Retransmit" flag, since we are no longer
/* Reset the retransmission time-out. */
pcb->rto = (pcb->sa >> 3) + pcb->sv;
- /* Update the send buffer space. Diff between the two can never exceed 64K? */
- pcb->acked = (u16_t)(ackno - pcb->lastack);
+ /* Update the send buffer space. Diff between the two can never exceed 64K
+ unless window scaling is used. */
+ pcb->acked = (tcpwnd_size_t)(ackno - pcb->lastack);
pcb->snd_buf += pcb->acked;
ssthresh). */
if (pcb->state >= ESTABLISHED) {
if (pcb->cwnd < pcb->ssthresh) {
- if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
+ if ((tcpwnd_size_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
pcb->cwnd += pcb->mss;
}
- LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %"U16_F"\n", pcb->cwnd));
+ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %"TCPWNDSIZE_F"\n", pcb->cwnd));
} else {
- u16_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd);
+ tcpwnd_size_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd);
if (new_cwnd > pcb->cwnd) {
pcb->cwnd = new_cwnd;
}
- LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: congestion avoidance cwnd %"U16_F"\n", pcb->cwnd));
+ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: congestion avoidance cwnd %"TCPWNDSIZE_F"\n", pcb->cwnd));
}
}
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: ACK for %"U32_F", unacked->seqno %"U32_F":%"U32_F"\n",
ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked): 0));
/* Remove segment from the unacknowledged list if the incoming
- ACK acknowlegdes them. */
+ ACK acknowledges them. */
while (pcb->unacked != NULL &&
TCP_SEQ_LEQ(ntohl(pcb->unacked->tcphdr->seqno) +
TCP_TCPLEN(pcb->unacked), ackno)) {
next = pcb->unacked;
pcb->unacked = pcb->unacked->next;
- LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_receive: queuelen %"U16_F" ... ", (u16_t)pcb->snd_queuelen));
+ LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_receive: queuelen %"TCPWNDSIZE_F" ... ", (tcpwnd_size_t)pcb->snd_queuelen));
LWIP_ASSERT("pcb->snd_queuelen >= pbuf_clen(next->p)", (pcb->snd_queuelen >= pbuf_clen(next->p)));
/* Prevent ACK for FIN to generate a sent event */
if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0)) {
pcb->snd_queuelen -= pbuf_clen(next->p);
tcp_seg_free(next);
- LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"U16_F" (after freeing unacked)\n", (u16_t)pcb->snd_queuelen));
+ LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"TCPWNDSIZE_F" (after freeing unacked)\n", (tcpwnd_size_t)pcb->snd_queuelen));
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_receive: valid queue length", pcb->unacked != NULL ||
pcb->unsent != NULL);
/* If there's nothing left to acknowledge, stop the retransmit
timer, otherwise reset it to start again */
- if(pcb->unacked == NULL)
+ if (pcb->unacked == NULL) {
pcb->rtime = -1;
- else
+ } else {
pcb->rtime = 0;
+ }
pcb->polltmr = 0;
}
#endif /* LWIP_IPV6 && LWIP_ND6_TCP_REACHABILITY_HINTS*/
} else {
- /* Fix bug bug #21582: out of sequence ACK, didn't really ack anything */
+ /* Out of sequence ACK, didn't really ack anything */
pcb->acked = 0;
+ tcp_send_empty_ack(pcb);
}
/* We go through the ->unsent list to see if any of the segments
->unsent list after a retransmission, so these segments may
in fact have been sent once. */
while (pcb->unsent != NULL &&
- TCP_SEQ_BETWEEN(ackno, ntohl(pcb->unsent->tcphdr->seqno) +
+ TCP_SEQ_BETWEEN(ackno, ntohl(pcb->unsent->tcphdr->seqno) +
TCP_TCPLEN(pcb->unsent), pcb->snd_nxt)) {
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: removing %"U32_F":%"U32_F" from pcb->unsent\n",
ntohl(pcb->unsent->tcphdr->seqno), ntohl(pcb->unsent->tcphdr->seqno) +
next = pcb->unsent;
pcb->unsent = pcb->unsent->next;
- LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_receive: queuelen %"U16_F" ... ", (u16_t)pcb->snd_queuelen));
+#if TCP_OVERSIZE
+ if (pcb->unsent == NULL) {
+ pcb->unsent_oversize = 0;
+ }
+#endif /* TCP_OVERSIZE */
+ LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_receive: queuelen %"TCPWNDSIZE_F" ... ", (tcpwnd_size_t)pcb->snd_queuelen));
LWIP_ASSERT("pcb->snd_queuelen >= pbuf_clen(next->p)", (pcb->snd_queuelen >= pbuf_clen(next->p)));
/* Prevent ACK for FIN to generate a sent event */
if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0)) {
}
pcb->snd_queuelen -= pbuf_clen(next->p);
tcp_seg_free(next);
- LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"U16_F" (after freeing unsent)\n", (u16_t)pcb->snd_queuelen));
+ LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"TCPWNDSIZE_F" (after freeing unsent)\n", (tcpwnd_size_t)pcb->snd_queuelen));
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_receive: valid queue length",
pcb->unacked != NULL || pcb->unsent != NULL);
}
/* If the incoming segment contains data, we must process it
- further. */
- if (tcplen > 0) {
+ further unless the pcb already received a FIN.
+ (RFC 793, chapter 3.9, "SEGMENT ARRIVES" in states CLOSE-WAIT, CLOSING,
+ LAST-ACK and TIME-WAIT: "Ignore the segment text.") */
+ if ((tcplen > 0) && (pcb->state < CLOSE_WAIT)) {
/* This code basically does three things:
+) If the incoming segment contains data that is the next
this if the sequence number of the incoming segment is less
than rcv_nxt, and the sequence number plus the length of the
segment is larger than rcv_nxt. */
- /* if (TCP_SEQ_LT(seqno, pcb->rcv_nxt)){
+ /* if (TCP_SEQ_LT(seqno, pcb->rcv_nxt)) {
if (TCP_SEQ_LT(pcb->rcv_nxt, seqno + tcplen)) {*/
- if (TCP_SEQ_BETWEEN(pcb->rcv_nxt, seqno + 1, seqno + tcplen - 1)){
+ if (TCP_SEQ_BETWEEN(pcb->rcv_nxt, seqno + 1, seqno + tcplen - 1)) {
/* Trimming the first edge is done by pushing the payload
pointer in the pbuf downwards. This is somewhat tricky since
we do not want to discard the full contents of the pbuf up to
p->len = 0;
p = p->next;
}
- if(pbuf_header(p, (s16_t)-off)) {
+ if (pbuf_header(p, (s16_t)-off)) {
/* Do we need to cope with this failing? Assert for now */
LWIP_ASSERT("pbuf_header failed", 0);
}
} else {
- if(pbuf_header(inseg.p, (s16_t)-off)) {
+ if (pbuf_header(inseg.p, (s16_t)-off)) {
/* Do we need to cope with this failing? Assert for now */
LWIP_ASSERT("pbuf_header failed", 0);
}
inseg.tcphdr->seqno = seqno = pcb->rcv_nxt;
}
else {
- if (TCP_SEQ_LT(seqno, pcb->rcv_nxt)){
+ if (TCP_SEQ_LT(seqno, pcb->rcv_nxt)) {
/* the whole segment is < rcv_nxt */
/* must be a duplicate of a packet that has already been correctly handled */
/* The sequence number must be within the window (above rcv_nxt
and below rcv_nxt + rcv_wnd) in order to be further
processed. */
- if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
- pcb->rcv_nxt + pcb->rcv_wnd - 1)){
+ if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
+ pcb->rcv_nxt + pcb->rcv_wnd - 1)) {
if (pcb->rcv_nxt == seqno) {
/* The incoming segment is the next in sequence. We check if
we have to trim the end of the segment and update rcv_nxt
tcplen = TCP_TCPLEN(&inseg);
if (tcplen > pcb->rcv_wnd) {
- LWIP_DEBUGF(TCP_INPUT_DEBUG,
+ LWIP_DEBUGF(TCP_INPUT_DEBUG,
("tcp_receive: other end overran receive window"
"seqno %"U32_F" len %"U16_F" right edge %"U32_F"\n",
seqno, tcplen, pcb->rcv_nxt + pcb->rcv_wnd));
if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {
- /* Must remove the FIN from the header as we're trimming
+ /* Must remove the FIN from the header as we're trimming
* that byte of sequence-space from the packet */
- TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) &~ TCP_FIN);
+ TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) & ~(unsigned int)TCP_FIN);
}
/* Adjust length of segment to fit in the window. */
- inseg.len = pcb->rcv_wnd;
+ TCPWND_CHECK16(pcb->rcv_wnd);
+ inseg.len = (u16_t)pcb->rcv_wnd;
if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) {
inseg.len -= 1;
}
- inseq overlaps with ooseq */
if (pcb->ooseq != NULL) {
if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {
- LWIP_DEBUGF(TCP_INPUT_DEBUG,
+ LWIP_DEBUGF(TCP_INPUT_DEBUG,
("tcp_receive: received in-order FIN, binning ooseq queue\n"));
/* Received in-order FIN means anything that was received
* out of order must now have been received in-order, so
pcb->ooseq = pcb->ooseq->next;
tcp_seg_free(old_ooseq);
}
- }
- else {
+ } else {
next = pcb->ooseq;
/* Remove all segments on ooseq that are covered by inseg already.
* FIN is copied from ooseq to inseg if present. */
if (cseg->p->tot_len > 0) {
/* Chain this pbuf onto the pbuf that we will pass to
the application. */
+ /* With window scaling, this can overflow recv_data->tot_len, but
+ that's not a problem since we explicitly fix that before passing
+ recv_data to the application. */
if (recv_data) {
pbuf_cat(recv_data, cseg->p);
} else {
recv_flags |= TF_GOT_FIN;
if (pcb->state == ESTABLISHED) { /* force passive close or we can move to active close */
pcb->state = CLOSE_WAIT;
- }
+ }
}
pcb->ooseq = cseg->next;
contains less data. */
prev = NULL;
- for(next = pcb->ooseq; next != NULL; next = next->next) {
+ for (next = pcb->ooseq; next != NULL; next = next->next) {
if (seqno == next->tcphdr->seqno) {
/* The sequence number of the incoming segment is the
same as the sequence number of the segment on
}
break;
} else {
- /* Either the lenghts are the same or the incoming
+ /* Either the lengths are the same or the incoming
segment was smaller than the old one; in either
case, we ditch the incoming segment. */
break;
pbuf_realloc(next->p, next->len);
}
/* check if the remote side overruns our receive window */
- if ((u32_t)tcplen + seqno > pcb->rcv_nxt + (u32_t)pcb->rcv_wnd) {
- LWIP_DEBUGF(TCP_INPUT_DEBUG,
+ if (TCP_SEQ_GT((u32_t)tcplen + seqno, pcb->rcv_nxt + (u32_t)pcb->rcv_wnd)) {
+ LWIP_DEBUGF(TCP_INPUT_DEBUG,
("tcp_receive: other end overran receive window"
"seqno %"U32_F" len %"U16_F" right edge %"U32_F"\n",
seqno, tcplen, pcb->rcv_nxt + pcb->rcv_wnd));
if (TCPH_FLAGS(next->next->tcphdr) & TCP_FIN) {
- /* Must remove the FIN from the header as we're trimming
+ /* Must remove the FIN from the header as we're trimming
* that byte of sequence-space from the packet */
- TCPH_FLAGS_SET(next->next->tcphdr, TCPH_FLAGS(next->next->tcphdr) &~ TCP_FIN);
+ TCPH_FLAGS_SET(next->next->tcphdr, TCPH_FLAGS(next->next->tcphdr) & ~TCP_FIN);
}
/* Adjust length of segment to fit in the window. */
- next->next->len = pcb->rcv_nxt + pcb->rcv_wnd - seqno;
+ next->next->len = (u16_t)(pcb->rcv_nxt + pcb->rcv_wnd - seqno);
pbuf_realloc(next->next->p, next->next->len);
tcplen = TCP_TCPLEN(next->next);
LWIP_ASSERT("tcp_receive: segment not trimmed correctly to rcv_wnd\n",
prev = next;
}
}
+#if TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS
+ /* Check that the data on ooseq doesn't exceed one of the limits
+ and throw away everything above that limit. */
+ ooseq_blen = 0;
+ ooseq_qlen = 0;
+ prev = NULL;
+ for (next = pcb->ooseq; next != NULL; prev = next, next = next->next) {
+ struct pbuf *p = next->p;
+ ooseq_blen += p->tot_len;
+ ooseq_qlen += pbuf_clen(p);
+ if ((ooseq_blen > TCP_OOSEQ_MAX_BYTES) ||
+ (ooseq_qlen > TCP_OOSEQ_MAX_PBUFS)) {
+ /* too much ooseq data, dump this and everything after it */
+ tcp_segs_free(next);
+ if (prev == NULL) {
+ /* first ooseq segment is too much, dump the whole queue */
+ pcb->ooseq = NULL;
+ } else {
+ /* just dump 'next' and everything after it */
+ prev->next = NULL;
+ }
+ break;
+ }
+ }
+#endif /* TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS */
#endif /* TCP_QUEUE_OOSEQ */
-
}
} else {
- /* The incoming segment is not withing the window. */
+ /* The incoming segment is not within the window. */
tcp_send_empty_ack(pcb);
}
} else {
/* Segments with length 0 is taken care of here. Segments that
fall out of the window are ACKed. */
- /*if (TCP_SEQ_GT(pcb->rcv_nxt, seqno) ||
- TCP_SEQ_GEQ(seqno, pcb->rcv_nxt + pcb->rcv_wnd)) {*/
- if(!TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd-1)){
+ if (!TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd - 1)) {
tcp_ack_now(pcb);
}
}
}
+static u8_t
+tcp_getoptbyte(void)
+{
+ if ((tcphdr_opt2 == NULL) || (tcp_optidx < tcphdr_opt1len)) {
+ u8_t* opts = (u8_t *)tcphdr + TCP_HLEN;
+ return opts[tcp_optidx++];
+ } else {
+ u8_t idx = (u8_t)(tcp_optidx++ - tcphdr_opt1len);
+ return tcphdr_opt2[idx];
+ }
+}
+
/**
- * Parses the options contained in the incoming segment.
+ * Parses the options contained in the incoming segment.
*
* Called from tcp_listen_input() and tcp_process().
* Currently, only the MSS option is supported!
static void
tcp_parseopt(struct tcp_pcb *pcb)
{
- u16_t c, max_c;
+ u8_t data;
u16_t mss;
- u8_t *opts, opt;
#if LWIP_TCP_TIMESTAMPS
u32_t tsval;
#endif
- opts = (u8_t *)tcphdr + TCP_HLEN;
-
/* Parse the TCP MSS option, if present. */
- if(TCPH_HDRLEN(tcphdr) > 0x5) {
- max_c = (TCPH_HDRLEN(tcphdr) - 5) << 2;
- for (c = 0; c < max_c; ) {
- opt = opts[c];
+ if (TCPH_HDRLEN(tcphdr) > 0x5) {
+ u16_t max_c = (TCPH_HDRLEN(tcphdr) - 5) << 2;
+ for (tcp_optidx = 0; tcp_optidx < max_c; ) {
+ u8_t opt = tcp_getoptbyte();
switch (opt) {
- case 0x00:
+ case LWIP_TCP_OPT_EOL:
/* End of options. */
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: EOL\n"));
return;
- case 0x01:
+ case LWIP_TCP_OPT_NOP:
/* NOP option. */
- ++c;
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: NOP\n"));
break;
- case 0x02:
+ case LWIP_TCP_OPT_MSS:
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: MSS\n"));
- if (opts[c + 1] != 0x04 || c + 0x04 > max_c) {
+ if (tcp_getoptbyte() != LWIP_TCP_OPT_LEN_MSS || (tcp_optidx - 2 + LWIP_TCP_OPT_LEN_MSS) > max_c) {
/* Bad length */
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad length\n"));
return;
}
/* An MSS option with the right option length. */
- mss = (opts[c + 2] << 8) | opts[c + 3];
+ mss = (tcp_getoptbyte() << 8);
+ mss |= tcp_getoptbyte();
/* Limit the mss to the configured TCP_MSS and prevent division by zero */
pcb->mss = ((mss > TCP_MSS) || (mss == 0)) ? TCP_MSS : mss;
- /* Advance to next option */
- c += 0x04;
break;
+#if LWIP_WND_SCALE
+ case LWIP_TCP_OPT_WS:
+ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: WND_SCALE\n"));
+ if (tcp_getoptbyte() != LWIP_TCP_OPT_LEN_WS || (tcp_optidx - 2 + LWIP_TCP_OPT_LEN_WS) > max_c) {
+ /* Bad length */
+ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad length\n"));
+ return;
+ }
+ /* If syn was received with wnd scale option,
+ activate wnd scale opt, but only if this is not a retransmission */
+ if ((flags & TCP_SYN) && !(pcb->flags & TF_WND_SCALE)) {
+ /* An WND_SCALE option with the right option length. */
+ data = tcp_getoptbyte();
+ pcb->snd_scale = data;
+ if (pcb->snd_scale > 14U) {
+ pcb->snd_scale = 14U;
+ }
+ pcb->rcv_scale = TCP_RCV_SCALE;
+ pcb->flags |= TF_WND_SCALE;
+ /* window scaling is enabled, we can use the full receive window */
+ LWIP_ASSERT("window not at default value", pcb->rcv_wnd == TCPWND_MIN16(TCP_WND));
+ LWIP_ASSERT("window not at default value", pcb->rcv_ann_wnd == TCPWND_MIN16(TCP_WND));
+ pcb->rcv_wnd = pcb->rcv_ann_wnd = TCP_WND;
+ }
+ break;
+#endif
#if LWIP_TCP_TIMESTAMPS
- case 0x08:
+ case LWIP_TCP_OPT_TS:
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: TS\n"));
- if (opts[c + 1] != 0x0A || c + 0x0A > max_c) {
+ if (tcp_getoptbyte() != LWIP_TCP_OPT_LEN_TS || (tcp_optidx - 2 + LWIP_TCP_OPT_LEN_TS) > max_c) {
/* Bad length */
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad length\n"));
return;
}
/* TCP timestamp option with valid length */
- tsval = (opts[c+2]) | (opts[c+3] << 8) |
- (opts[c+4] << 16) | (opts[c+5] << 24);
+ tsval = tcp_getoptbyte();
+ tsval |= (tcp_getoptbyte() << 8);
+ tsval |= (tcp_getoptbyte() << 16);
+ tsval |= (tcp_getoptbyte() << 24);
if (flags & TCP_SYN) {
pcb->ts_recent = ntohl(tsval);
+ /* Enable sending timestamps in every segment now that we know
+ the remote host supports it. */
pcb->flags |= TF_TIMESTAMP;
} else if (TCP_SEQ_BETWEEN(pcb->ts_lastacksent, seqno, seqno+tcplen)) {
pcb->ts_recent = ntohl(tsval);
}
- /* Advance to next option */
- c += 0x0A;
+ /* Advance to next option (6 bytes already read) */
+ tcp_optidx += LWIP_TCP_OPT_LEN_TS - 6;
break;
#endif
default:
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: other\n"));
- if (opts[c + 1] == 0) {
+ data = tcp_getoptbyte();
+ if (data < 2) {
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad length\n"));
/* If the length field is zero, the options are malformed
and we don't process them further. */
}
/* All other options have a length field, so that we easily
can skip past them. */
- c += opts[c + 1];
+ tcp_optidx += data - 2;
}
}
}
}
+void
+tcp_trigger_input_pcb_close(void)
+{
+ recv_flags |= TF_CLOSED;
+}
+
#endif /* LWIP_TCP */