/** * Insert segment into the list (segments covered with new one will be deleted) * * Called from tcp_receive() */ static void tcp_oos_insert_segment(struct tcp_seg *cseg, struct tcp_seg *next) { struct tcp_seg *old_seg; if (TCPH_FLAGS(cseg->tcphdr) & TCP_FIN) { /* received segment overlaps all following segments */ tcp_segs_free(next); next = NULL; } else { /* delete some following segments oos queue may have segments with FIN flag */ while (next && TCP_SEQ_GEQ((seqno + cseg->len), (next->tcphdr->seqno + next->len))) { /* cseg with FIN already processed */ if (TCPH_FLAGS(next->tcphdr) & TCP_FIN) { TCPH_FLAGS_SET(cseg->tcphdr, TCPH_FLAGS(cseg->tcphdr) | TCP_FIN); } old_seg = next; next = next->next; tcp_seg_free(old_seg); } if (next && TCP_SEQ_GT(seqno + cseg->len, next->tcphdr->seqno)) { /* We need to trim the incoming segment. */ cseg->len = (u16_t)(next->tcphdr->seqno - seqno); pbuf_realloc(cseg->p, cseg->len); } } cseg->next = next; }
/** * Called by tcp_input() when a segment arrives for a connection in * TIME_WAIT. * * @param pcb the tcp_pcb for which a segment arrived * * @note the segment which arrived is saved in global variables, therefore only the pcb * involved is passed as a parameter to this function */ static err_t tcp_timewait_input(struct tcp_pcb *pcb) { if (TCP_SEQ_GT(seqno + tcplen, pcb->rcv_nxt)) { pcb->rcv_nxt = seqno + tcplen; } if (tcplen > 0) { tcp_ack_now(pcb); } return tcp_output(pcb); }
/** * 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 */ pcb->rcv_ann_wnd = pcb->rcv_ann_right_edge - pcb->rcv_nxt; } return 0; } }
/*----------------------------------------------------------------------------*/ static inline int ValidateSequence(mtcp_manager_t mtcp, tcp_stream *cur_stream, uint32_t cur_ts, struct tcphdr *tcph, uint32_t seq, uint32_t ack_seq, int payloadlen) { /* Protect Against Wrapped Sequence number (PAWS) */ if (!tcph->rst && cur_stream->saw_timestamp) { struct tcp_timestamp ts; if (!ParseTCPTimestamp(cur_stream, &ts, (uint8_t *)tcph + TCP_HEADER_LEN, (tcph->doff << 2) - TCP_HEADER_LEN)) { /* if there is no timestamp */ /* TODO: implement here */ TRACE_DBG("No timestamp found.\n"); return FALSE; } /* RFC1323: if SEG.TSval < TS.Recent, drop and send ack */ if (TCP_SEQ_LT(ts.ts_val, cur_stream->rcvvar->ts_recent)) { /* TODO: ts_recent should be invalidated before timestamp wraparound for long idle flow */ TRACE_DBG("PAWS Detect wrong timestamp. " "seq: %u, ts_val: %u, prev: %u\n", seq, ts.ts_val, cur_stream->rcvvar->ts_recent); EnqueueACK(mtcp, cur_stream, cur_ts, ACK_OPT_NOW); return FALSE; } else { /* valid timestamp */ if (TCP_SEQ_GT(ts.ts_val, cur_stream->rcvvar->ts_recent)) { TRACE_TSTAMP("Timestamp update. cur: %u, prior: %u " "(time diff: %uus)\n", ts.ts_val, cur_stream->rcvvar->ts_recent, TS_TO_USEC(cur_ts - cur_stream->rcvvar->ts_last_ts_upd)); cur_stream->rcvvar->ts_last_ts_upd = cur_ts; } cur_stream->rcvvar->ts_recent = ts.ts_val; cur_stream->rcvvar->ts_lastack_rcvd = ts.ts_ref; } } /* TCP sequence validation */ if (!TCP_SEQ_BETWEEN(seq + payloadlen, cur_stream->rcv_nxt, cur_stream->rcv_nxt + cur_stream->rcvvar->rcv_wnd)) { /* if RST bit is set, ignore the segment */ if (tcph->rst) return FALSE; if (cur_stream->state == TCP_ST_ESTABLISHED) { /* check if it is to get window advertisement */ if (seq + 1 == cur_stream->rcv_nxt) { #if 0 TRACE_DBG("Window update request. (seq: %u, rcv_wnd: %u)\n", seq, cur_stream->rcvvar->rcv_wnd); #endif EnqueueACK(mtcp, cur_stream, cur_ts, ACK_OPT_AGGREGATE); return FALSE; } if (TCP_SEQ_LEQ(seq, cur_stream->rcv_nxt)) { EnqueueACK(mtcp, cur_stream, cur_ts, ACK_OPT_AGGREGATE); } else { EnqueueACK(mtcp, cur_stream, cur_ts, ACK_OPT_NOW); } } else { if (cur_stream->state == TCP_ST_TIME_WAIT) { TRACE_DBG("Stream %d: tw expire update to %u\n", cur_stream->id, cur_stream->rcvvar->ts_tw_expire); AddtoTimewaitList(mtcp, cur_stream, cur_ts); } AddtoControlList(mtcp, cur_stream, cur_ts); } return FALSE; } return TRUE; }
END_TEST START_TEST(test_tcp_rto_tracking) { struct netif netif; struct test_tcp_txcounters txcounters; struct test_tcp_counters counters; struct tcp_pcb* pcb; struct pbuf* p; err_t err; size_t i; u16_t sent_total = 0; LWIP_UNUSED_ARG(_i); for (i = 0; i < sizeof(tx_data); i++) { tx_data[i] = (u8_t)i; } /* initialize local vars */ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); memset(&counters, 0, sizeof(counters)); /* create and initialize the pcb */ tcp_ticks = SEQNO1 - ISS; pcb = test_tcp_new_counters_pcb(&counters); EXPECT_RET(pcb != NULL); tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); pcb->mss = TCP_MSS; /* Set congestion window large enough to send all our segments */ pcb->cwnd = 5*TCP_MSS; /* send 5 mss-sized segments */ for (i = 0; i < 5; i++) { err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY); EXPECT_RET(err == ERR_OK); sent_total += TCP_MSS; } check_seqnos(pcb->unsent, 5, seqnos); EXPECT(pcb->unacked == NULL); err = tcp_output(pcb); EXPECT(txcounters.num_tx_calls == 5); EXPECT(txcounters.num_tx_bytes == 5 * (TCP_MSS + 40U)); memset(&txcounters, 0, sizeof(txcounters)); /* Check all 5 are in-flight */ EXPECT(pcb->unsent == NULL); check_seqnos(pcb->unacked, 5, seqnos); /* Force us into retransmisson timeout */ while (!(pcb->flags & TF_RTO)) { test_tcp_tmr(); } /* Ensure 4 remaining segments are back on unsent, ready for retransmission */ check_seqnos(pcb->unsent, 4, &seqnos[1]); /* Ensure 1st segment is on unacked (already retransmitted) */ check_seqnos(pcb->unacked, 1, seqnos); EXPECT(txcounters.num_tx_calls == 1); EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U); memset(&txcounters, 0, sizeof(txcounters)); /* Ensure rto_end points to next byte */ EXPECT(pcb->rto_end == seqnos[5]); EXPECT(pcb->rto_end == pcb->snd_nxt); /* Check cwnd was reset */ EXPECT(pcb->cwnd == pcb->mss); /* Add another segment to send buffer which is outside of RTO */ err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY); EXPECT_RET(err == ERR_OK); sent_total += TCP_MSS; check_seqnos(pcb->unsent, 5, &seqnos[1]); /* Ensure no new data was sent */ EXPECT(txcounters.num_tx_calls == 0); EXPECT(txcounters.num_tx_bytes == 0); EXPECT(pcb->rto_end == pcb->snd_nxt); /* ACK first segment */ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); test_tcp_input(p, &netif); /* Next two retranmissions should go out, due to cwnd in slow start */ EXPECT(txcounters.num_tx_calls == 2); EXPECT(txcounters.num_tx_bytes == 2 * (TCP_MSS + 40U)); memset(&txcounters, 0, sizeof(txcounters)); check_seqnos(pcb->unacked, 2, &seqnos[1]); check_seqnos(pcb->unsent, 3, &seqnos[3]); /* RTO should still be marked */ EXPECT(pcb->flags & TF_RTO); /* cwnd should have only grown by 1 MSS */ EXPECT(pcb->cwnd == (tcpwnd_size_t)(2 * pcb->mss)); /* Ensure no new data was sent */ EXPECT(pcb->rto_end == pcb->snd_nxt); /* ACK the next two segments */ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 2*TCP_MSS, TCP_ACK); test_tcp_input(p, &netif); /* Final 2 retransmissions and 1 new data should go out */ EXPECT(txcounters.num_tx_calls == 3); EXPECT(txcounters.num_tx_bytes == 3 * (TCP_MSS + 40U)); memset(&txcounters, 0, sizeof(txcounters)); check_seqnos(pcb->unacked, 3, &seqnos[3]); EXPECT(pcb->unsent == NULL); /* RTO should still be marked */ EXPECT(pcb->flags & TF_RTO); /* cwnd should have only grown by 1 MSS */ EXPECT(pcb->cwnd == (tcpwnd_size_t)(3 * pcb->mss)); /* snd_nxt should have been advanced past rto_end */ EXPECT(TCP_SEQ_GT(pcb->snd_nxt, pcb->rto_end)); /* ACK the next two segments, finishing our RTO, leaving new segment unacked */ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 2*TCP_MSS, TCP_ACK); test_tcp_input(p, &netif); EXPECT(!(pcb->flags & TF_RTO)); check_seqnos(pcb->unacked, 1, &seqnos[5]); /* We should be in ABC congestion avoidance, so no change in cwnd */ EXPECT(pcb->cwnd == (tcpwnd_size_t)(3 * pcb->mss)); EXPECT(pcb->cwnd >= pcb->ssthresh); /* Ensure ABC congestion avoidance is tracking bytes acked */ EXPECT(pcb->bytes_acked == (tcpwnd_size_t)(2 * pcb->mss)); /* make sure the pcb is freed */ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); tcp_abort(pcb); EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); }