tcp: persist timer re-work (bug #50837)
This re-works the persist timer to have the following behavior:
1) Only start persist timer when a buffered segment doesn't fit within
the current window and there is no in-fligh data. Previously, the
persist timer was always started when the window went to zero even
if there was no buffered data (since timer was managed in receive
pathway rather than transmit pathway)
2) Upon first fire of persist timer, fill the remaining window if
non-zero by splitting the unsent segment. If split segment is sent,
persist timer is stopped, RTO timer is now ensuring reliable window
updates
3) If window is already zero when persist timer fires, send 1 byte probe
4) Persist timer and zero window probe should only be active when the
following are true:
* no in-flight data (pcb->unacked == NULL)
* when there is buffered data (pcb->unsent != NULL)
* when pcb->unsent->len > pcb->snd_wnd
This commit is contained in:
@@ -687,7 +687,9 @@ static void test_tcp_tx_full_window_lost(u8_t zero_window_probe_from_unsent)
|
||||
/* ensure this didn't trigger any transmission */
|
||||
EXPECT(txcounters.num_tx_calls == 0);
|
||||
EXPECT(txcounters.num_tx_bytes == 0);
|
||||
EXPECT(pcb->persist_backoff == 1);
|
||||
/* window is completely full, but persist timer is off since send buffer is empty */
|
||||
EXPECT(pcb->snd_wnd == 0);
|
||||
EXPECT(pcb->persist_backoff == 0);
|
||||
}
|
||||
|
||||
/* send one byte more (out of window) -> persist timer starts */
|
||||
@@ -991,7 +993,8 @@ START_TEST(test_tcp_zwp_timeout)
|
||||
test_tcp_input(p, &netif);
|
||||
EXPECT(pcb->unacked == NULL);
|
||||
EXPECT(pcb->unsent == NULL);
|
||||
EXPECT(pcb->persist_backoff == 1);
|
||||
/* send buffer empty, persist should be off */
|
||||
EXPECT(pcb->persist_backoff == 0);
|
||||
EXPECT(pcb->snd_wnd == 0);
|
||||
|
||||
/* send second segment, should be buffered */
|
||||
@@ -1000,11 +1003,12 @@ START_TEST(test_tcp_zwp_timeout)
|
||||
err = tcp_output(pcb);
|
||||
EXPECT(err == ERR_OK);
|
||||
|
||||
/* ensure it is buffered */
|
||||
/* ensure it is buffered and persist timer started */
|
||||
EXPECT(pcb->unacked == NULL);
|
||||
check_seqnos(pcb->unsent, 1, &seqnos[1]);
|
||||
EXPECT(txcounters.num_tx_calls == 0);
|
||||
EXPECT(txcounters.num_tx_bytes == 0);
|
||||
EXPECT(pcb->persist_backoff == 1);
|
||||
|
||||
/* ensure no errors have been recorded */
|
||||
EXPECT(counters.err_calls == 0);
|
||||
@@ -1051,6 +1055,152 @@ START_TEST(test_tcp_zwp_timeout)
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_tcp_persist_split)
|
||||
{
|
||||
struct netif netif;
|
||||
struct test_tcp_txcounters txcounters;
|
||||
struct test_tcp_counters counters;
|
||||
struct tcp_pcb *pcb;
|
||||
struct pbuf* p;
|
||||
err_t err;
|
||||
u16_t i;
|
||||
LWIP_UNUSED_ARG(_i);
|
||||
|
||||
/* Setup data for four segments */
|
||||
for (i = 0; i < 4 * TCP_MSS; 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 window to three segments */
|
||||
pcb->cwnd = 3 * TCP_MSS;
|
||||
pcb->snd_wnd = 3 * TCP_MSS;
|
||||
pcb->snd_wnd_max = 3 * TCP_MSS;
|
||||
|
||||
/* send three segments */
|
||||
err = tcp_write(pcb, &tx_data[0], 3 * TCP_MSS, TCP_WRITE_FLAG_COPY);
|
||||
EXPECT(err == ERR_OK);
|
||||
err = tcp_output(pcb);
|
||||
EXPECT(err == ERR_OK);
|
||||
|
||||
/* verify segments are in-flight */
|
||||
EXPECT(pcb->unsent == NULL);
|
||||
EXPECT(pcb->unacked != NULL);
|
||||
check_seqnos(pcb->unacked, 3, seqnos);
|
||||
EXPECT(txcounters.num_tx_calls == 3);
|
||||
EXPECT(txcounters.num_tx_bytes == 3 * (TCP_MSS + 40U));
|
||||
memset(&txcounters, 0, sizeof(txcounters));
|
||||
|
||||
/* ACK the segments and update the window to only 1/2 TCP_MSS */
|
||||
p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, 3 * TCP_MSS, TCP_ACK, TCP_MSS / 2);
|
||||
test_tcp_input(p, &netif);
|
||||
EXPECT(pcb->unacked == NULL);
|
||||
EXPECT(pcb->unsent == NULL);
|
||||
EXPECT(pcb->persist_backoff == 0);
|
||||
EXPECT(pcb->snd_wnd == TCP_MSS / 2);
|
||||
|
||||
/* send fourth segment, which is larger than snd_wnd */
|
||||
err = tcp_write(pcb, &tx_data[3 * TCP_MSS], TCP_MSS, TCP_WRITE_FLAG_COPY);
|
||||
EXPECT(err == ERR_OK);
|
||||
err = tcp_output(pcb);
|
||||
EXPECT(err == ERR_OK);
|
||||
|
||||
/* ensure it is buffered and persist timer started */
|
||||
EXPECT(pcb->unacked == NULL);
|
||||
EXPECT(pcb->unsent != NULL);
|
||||
check_seqnos(pcb->unsent, 1, &seqnos[3]);
|
||||
EXPECT(txcounters.num_tx_calls == 0);
|
||||
EXPECT(txcounters.num_tx_bytes == 0);
|
||||
EXPECT(pcb->persist_backoff == 1);
|
||||
|
||||
/* ensure no errors have been recorded */
|
||||
EXPECT(counters.err_calls == 0);
|
||||
EXPECT(counters.last_err == ERR_OK);
|
||||
|
||||
/* call tcp_timer some more times to let persist timer count up */
|
||||
for (i = 0; i < 4; i++) {
|
||||
test_tcp_tmr();
|
||||
EXPECT(txcounters.num_tx_calls == 0);
|
||||
EXPECT(txcounters.num_tx_bytes == 0);
|
||||
}
|
||||
|
||||
/* this should be the first timer shot, which should split the
|
||||
* segment and send a runt (of the remaining window size) */
|
||||
txcounters.copy_tx_packets = 1;
|
||||
test_tcp_tmr();
|
||||
txcounters.copy_tx_packets = 0;
|
||||
/* persist will be disabled as RTO timer takes over */
|
||||
EXPECT(pcb->persist_backoff == 0);
|
||||
EXPECT(txcounters.num_tx_calls == 1);
|
||||
EXPECT(txcounters.num_tx_bytes == ((TCP_MSS /2) + 40U));
|
||||
/* verify half segment sent, half still buffered */
|
||||
EXPECT(pcb->unsent != NULL);
|
||||
EXPECT(pcb->unsent->len == TCP_MSS / 2);
|
||||
EXPECT(pcb->unacked != NULL);
|
||||
EXPECT(pcb->unacked->len == TCP_MSS / 2);
|
||||
|
||||
/* verify first half segment */
|
||||
EXPECT(txcounters.tx_packets != NULL);
|
||||
if (txcounters.tx_packets != NULL) {
|
||||
u8_t sent[TCP_MSS / 2];
|
||||
u16_t ret;
|
||||
ret = pbuf_copy_partial(txcounters.tx_packets, &sent, TCP_MSS / 2, 40U);
|
||||
EXPECT(ret == TCP_MSS / 2);
|
||||
EXPECT(memcmp(sent, &tx_data[3 * TCP_MSS], TCP_MSS / 2) == 0);
|
||||
}
|
||||
if (txcounters.tx_packets != NULL) {
|
||||
pbuf_free(txcounters.tx_packets);
|
||||
txcounters.tx_packets = NULL;
|
||||
}
|
||||
memset(&txcounters, 0, sizeof(txcounters));
|
||||
|
||||
/* ACK the half segment, leave window at half segment */
|
||||
p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, TCP_MSS / 2, TCP_ACK, TCP_MSS / 2);
|
||||
txcounters.copy_tx_packets = 1;
|
||||
test_tcp_input(p, &netif);
|
||||
txcounters.copy_tx_packets = 0;
|
||||
/* ensure remaining half segment was sent */
|
||||
EXPECT(txcounters.num_tx_calls == 1);
|
||||
EXPECT(txcounters.num_tx_bytes == ((TCP_MSS /2 ) + 40U));
|
||||
EXPECT(pcb->unsent == NULL);
|
||||
EXPECT(pcb->unacked != NULL);
|
||||
EXPECT(pcb->unacked->len == TCP_MSS / 2);
|
||||
EXPECT(pcb->snd_wnd == TCP_MSS / 2);
|
||||
|
||||
/* verify second half segment */
|
||||
EXPECT(txcounters.tx_packets != NULL);
|
||||
if (txcounters.tx_packets != NULL) {
|
||||
u8_t sent[TCP_MSS / 2];
|
||||
u16_t ret;
|
||||
ret = pbuf_copy_partial(txcounters.tx_packets, &sent, TCP_MSS / 2, 40U);
|
||||
EXPECT(ret == TCP_MSS / 2);
|
||||
EXPECT(memcmp(sent, &tx_data[(3 * TCP_MSS) + TCP_MSS / 2], TCP_MSS / 2) == 0);
|
||||
}
|
||||
if (txcounters.tx_packets != NULL) {
|
||||
pbuf_free(txcounters.tx_packets);
|
||||
txcounters.tx_packets = NULL;
|
||||
}
|
||||
|
||||
/* ensure no errors have been recorded */
|
||||
EXPECT(counters.err_calls == 0);
|
||||
EXPECT(counters.last_err == ERR_OK);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/** Create the suite including all tests for this module */
|
||||
Suite *
|
||||
tcp_suite(void)
|
||||
@@ -1067,7 +1217,8 @@ tcp_suite(void)
|
||||
TESTFUNC(test_tcp_tx_full_window_lost_from_unsent),
|
||||
TESTFUNC(test_tcp_rto_tracking),
|
||||
TESTFUNC(test_tcp_rto_timeout),
|
||||
TESTFUNC(test_tcp_zwp_timeout)
|
||||
TESTFUNC(test_tcp_zwp_timeout),
|
||||
TESTFUNC(test_tcp_persist_split)
|
||||
};
|
||||
return create_suite("TCP", tests, sizeof(tests)/sizeof(testfunc), tcp_setup, tcp_teardown);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user