Bug 199287 - Missing TCP retransmit timer reset
Summary: Missing TCP retransmit timer reset
Status: Closed Overcome By Events
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: 9.3-RELEASE
Hardware: Any Any
: --- Affects Only Me
Assignee: freebsd-net (Nobody)
Depends on:
Reported: 2015-04-08 08:17 UTC by sebastian.huber
Modified: 2020-05-26 06:19 UTC (History)
4 users (show)

See Also:


Note You need to log in before you can comment on or make changes to this bug.
Description sebastian.huber 2015-04-08 08:17:17 UTC
There is a potential problem in the TCP implementation.  I don't use FreeBSD directly, instead I use a port of the FreeBSD 9.3 network stack to the RTEMS real-time operating system.  Nonetheless, this problem might exist in the real FreeBSD system.

During tests under heavy load conditions the following situation occurred. I transfer data from my development hosts /dev/zero to the targets (RTEMS running the FreeBSD network stack) /dev/null via TCP. Thus the target sends only ACKs after the TCP connection setup. The interface driver uses an equal count for the rx and tx DMA descriptors.  I use a small value to provoke packet loss at the rx side.  This packet loss is dealt via SACK, so the transfer rate is nearly constant and at a high level for the target.  In this setup the tx queue overflows occasionally, thus in ip_output() we end up at

	 * Verify that we have any chance at all of being able to queue the
	 * packet or packet fragments, unless ALTQ is enabled on the given
	 * interface in which case packetdrop should be done by queueing.
	n = ip->ip_len / mtu + 1; /* how many fragments ? */
	if (
#ifdef ALTQ
	    (!ALTQ_IS_ENABLED(&ifp->if_snd)) &&
#endif /* ALTQ */
	    (ifp->if_snd.ifq_len + n) >= ifp->if_snd.ifq_maxlen ) {
		error = ENOBUFS;
		ifp->if_snd.ifq_drops += n;
		goto bad;

and return ENOBUFS.  This leads to tcp_ouput()

		SOCKBUF_UNLOCK_ASSERT(&so->so_snd);	/* Check gotos. */
		switch (error) {
		case EPERM:
			tp->t_softerror = error;
			return (error);
		case ENOBUFS:
	                if (!tcp_timer_active(tp, TT_REXMT) &&
			    !tcp_timer_active(tp, TT_PERSIST))
	                        tcp_timer_activate(tp, TT_REXMT, tp->t_rxtcur);
			tp->snd_cwnd = tp->t_maxseg;
			return (0);

So here we start the retransmit timer.  An occasional drop of ACK-only packets doesn't hurt in this scenario so we don't observe any problems and on the rx side we nearly always end up in tcp_do_segment() with tlen != 0

	 * Header prediction: check for the two common cases
	 * of a uni-directional data xfer.  If the packet has
	 * no control flags, is in-sequence, the window didn't
	 * change and we're not retransmitting, it's a
	 * candidate.  If the length is zero and the ack moved
	 * forward, we're the sender side of the xfer.  Just
	 * free the data acked & wake any higher level process
	 * that was blocked waiting for space.  If the length
	 * is non-zero and the ack didn't move, we're the
	 * receiver side.  If we're getting packets in-order
	 * (the reassembly queue is empty), add the data to
	 * the socket buffer and note that we need a delayed ack.
	 * Make sure that the hidden state-flags are also off.
	 * Since we check for TCPS_ESTABLISHED first, it can only
	 * be TH_NEEDSYN.
	if (tp->t_state == TCPS_ESTABLISHED &&
	    th->th_seq == tp->rcv_nxt &&
	    (thflags & (TH_SYN|TH_FIN|TH_RST|TH_URG|TH_ACK)) == TH_ACK &&
	    tp->snd_nxt == tp->snd_max &&
	    tiwin && tiwin == tp->snd_wnd && 
	    ((tp->t_flags & (TF_NEEDSYN|TF_NEEDFIN)) == 0) &&
	    LIST_EMPTY(&tp->t_segq) &&
	    ((to.to_flags & TOF_TS) == 0 ||
	     TSTMP_GEQ(to.to_tsval, tp->ts_recent)) ) {

In this path the retransmit timer is never reset, so in the end the connection is dropped by tcp_timer_rexmt() even though there is no real connection problem.
Comment 1 Hiren Panchasara freebsd_committer 2015-12-01 07:07:35 UTC
(In reply to sebastian.huber from comment #0)
Hi Sebastian,

A few questions:
1) Is this a theory or are you seeing a real problem in your setup? 
2) if answer to 1) is yes, does it only happen when you drop acks?
3) when you say the connection is dropped finally by tcp_Timer_rexmt(), is it because it has backed off > TCP_MAXRXTSHIFT times? (because timer was never reset as per the theory?)

About resetting the retransmit timer, don't we do that in tcp_do_segment() at following place?

                 * If all outstanding data is acked, stop retransmit
                 * timer and remember to restart (more output or persist).
                 * If there is more data to be acked, restart retransmit
                 * timer, using current (possibly backed-off) value.
                if (th->th_ack == tp->snd_max) {
                        tcp_timer_activate(tp, TT_REXMT, 0);
                        needoutput = 1; 
                } else if (!tcp_timer_active(tp, TT_PERSIST))
                        tcp_timer_activate(tp, TT_REXMT, tp->t_rxtcur);

Or am I missing something? I'd love to understand this situation better.
Comment 2 sebastian.huber 2015-12-04 07:02:40 UTC
(In reply to Hiren Panchasara from comment #1)

1) Yes, this was a real problem. It didn't show up, however, after I increased the DMA descriptor count.

For your other questions I have to debug the target again. It will be next year before I can do this.
Comment 3 Hiren Panchasara freebsd_committer 2016-12-23 08:02:59 UTC
Ping - to see if you got a chance to look into the details. :-)
Comment 4 sebastian.huber 2016-12-23 09:57:20 UTC
(In reply to Hiren Panchasara from comment #3)

Sorry, I am even more busy than last year. I didn't forget about this, but I have to set priorities. We update currently to FreeBSD HEAD.
Comment 5 Hiren Panchasara freebsd_committer 2016-12-23 19:42:22 UTC
(In reply to sebastian.huber from comment #4)
Much better if you can test of -HEAD and report back whenever time permits. Cheers.
Comment 6 Richard Scheffenegger freebsd_committer 2020-05-24 18:15:21 UTC
Can this bug report closed as historic?
Comment 7 sebastian.huber 2020-05-26 05:04:44 UTC
(In reply to Richard Scheffenegger from comment #6)
Yes, please close it as historic. The code probably changed in the last five years.