Index: tcp_input.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_input.c,v
retrieving revision 1.261
diff -u -p -r1.261 tcp_input.c
--- tcp_input.c	28 Nov 2004 11:06:22 -0000	1.261
+++ tcp_input.c	13 Dec 2004 17:25:32 -0000
@@ -1,6 +1,8 @@
 /*
  * Copyright (c) 1982, 1986, 1988, 1990, 1993, 1994, 1995
  *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 2004
+ *	Andre Oppermann, Internet Business Solutions AG
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -202,153 +204,377 @@ do { \
 	    (tp->t_flags & TF_RXWIN0SENT) == 0) &&			\
 	    (tcp_delack_enabled || (tp->t_flags & TF_NEEDSYN)))
 
-/* Initialize TCP reassembly queue */
-uma_zone_t	tcp_reass_zone;
+/* Initialize TCP reassembly queue. */
 void
 tcp_reass_init()
 {
 	tcp_reass_maxseg = nmbclusters / 16;
 	TUNABLE_INT_FETCH("net.inet.tcp.reass.maxsegments",
 	    &tcp_reass_maxseg);
-	tcp_reass_zone = uma_zcreate("tcpreass", sizeof (struct tseg_qent),
-	    NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE);
-	uma_zone_set_max(tcp_reass_zone, tcp_reass_maxseg);
 }
 
 static int
 tcp_reass(tp, th, tlenp, m)
-	register struct tcpcb *tp;
-	register struct tcphdr *th;
+	struct tcpcb *tp;
+	struct tcphdr *th;
 	int *tlenp;
 	struct mbuf *m;
 {
-	struct tseg_qent *q;
-	struct tseg_qent *p = NULL;
-	struct tseg_qent *nq;
-	struct tseg_qent *te = NULL;
+	struct trq *mtrq, *mtrqprev, *mtrqnext;
+	struct mbuf *mm;
 	struct socket *so = tp->t_inpcb->inp_socket;
-	int flags;
+	tcp_seq seq_start, seq_end;
+	int trim, flags, seg_flags;
+
+#define	mtrqp(m) ((struct trq *)(mtod(m, char *) - M_LEADINGSPACE(m)))
 
 	INP_LOCK_ASSERT(tp->t_inpcb);
 
 	/*
-	 * XXX: tcp_reass() is rather inefficient with its data structures
-	 * and should be rewritten (see NetBSD for optimizations).  While
-	 * doing that it should move to its own file tcp_reass.c.
+	 * m->m_data points to the actual data start,
+	 * th points to the tcp header in front of the data,
+	 * tlenp is the payload length the preceeding TCP code has identified.
+	 *
+	 * Call with th==NULL after become established to force
+	 * pre-ESTABLISHED data up to user socket.
 	 */
+	if (!th || !tlenp || !m)
+		goto present;
+
+	M_ASSERTVALID(m);
+	M_ASSERTPKTHDR(m);
 
+	KASSERT(M_WRITABLE(m), ("%s: mbuf not writeable!", __func__));
+	KASSERT(M_LEADINGSPACE(m) >= sizeof(struct trq),
+	    ("%s: not enough leading space in mbuf for struct trq!", __func__));
+	/* The normal TCP code should have taken care of the next two... */
+	KASSERT(SEQ_GEQ(th->th_seq, tp->rcv_nxt),
+		("%s: data in segment below window!", __func__));
+	KASSERT(SEQ_LEQ(th->th_seq, tp->rcv_adv + 1),
+		("%s: data in segment above window!", __func__));
+
+	/*
+	 * Some mbuf manipulation functions do not update m_pkthdr.len properly.
+	 * Do it now to avoid getting confused later on.
+	 */
+	m->m_pkthdr.len = *tlenp;
+
+	seq_start = th->th_seq;			/* First byte in segment */
+	seq_end = th->th_seq + *tlenp - 1;	/* Last byte in segment */
+	seg_flags = th->th_flags;		/* TCP flags */
+
+	/* Initialize queue entries within mbufs. */
+	mtrq = mtrqp(m);
+	bzero(mtrq, sizeof(struct trq));	/* Clean out to be sure */
+	mtrq->trq_m = m;			/* Backpointer to mbuf */
+	mtrq->seq_start = seq_start;
+	mtrq->seq_end = seq_end;
+	mtrq->seg_flags = seg_flags;
+	mtrq->seq_cont_start = seq_start;
+	mtrq->seq_cont_end = seq_end;
+	mtrq->trq_cont_first = mtrq;
+	mtrq->trq_cont_last = mtrq;
+	/*
+	 * MUST NOT access *th or seg_[start|end] after this.
+	 * The TCP header may get overwritten by the reassembly
+	 * housekeeping structure at the beginning of the data
+	 * segment of the mbuf.
+	 */
+	th = NULL;
+	seq_start = seq_end = 0;
+
+	/* Reassembly queue was empty, just put this segment in and be done. */
+	if (tp->trqhead == NULL) {
+		tp->trqhead = mtrq;
+		tp->trqtail = mtrq;
+		if (tp->rcv_nxt == mtrq->seq_start)
+			goto present;
+		tp->trqlen++;
+		tcp_reass_qsize++;
+		tcpstat.tcps_rcvoopack++;
+		tcpstat.tcps_rcvoobyte += m->m_pkthdr.len;
+		return (0);
+	}
+	
 	/*
-	 * Call with th==NULL after become established to
-	 * force pre-ESTABLISHED data up to user socket.
+	 * Go through TCP reassembly queue to find the proper place for the
+	 * fresh segment to be linked in and stored.
+	 *
+	 * The different checks are sorted by probability of matching.
+	 *
+	 * First check if it appends to the last mbuf.
 	 */
-	if (th == NULL)
-		goto present;
-
+	if (tp->trqtail->seq_cont_end + 1 == mtrq->seq_start) {
+		/* Limit the number of segments in the queue. */
+		if (tcp_reass_qsize + 1 >= tcp_reass_maxseg ||
+		    tp->trqlen >= tcp_reass_maxqlen) {
+			tcp_reass_overflows++;
+			tcpstat.tcps_rcvmemdrop++;
+			m_freem(m);
+			return (0);
+		}
+		/*
+		 * Perfect fit.
+		 * Append packet or copy into remaining space of previous packet.
+		 */
+		tp->trqtail->trq_m->m_nextpkt = m;
+		mtrqprev = tp->trqtail;
+		tp->trqtail = mtrq;	/* This is the new trailing segment. */
+		/* Update segment chain fields. */
+		tp->trqtail->seq_cont_start = mtrqprev->trq_cont_first->seq_start;
+		tp->trqtail->trq_cont_first = mtrqprev->trq_cont_first;
+		tp->trqtail->trq_cont_first->seq_cont_end = tp->trqtail->seq_end;
+		tp->trqtail->trq_cont_first->trq_cont_last = tp->trqtail;
+		tp->trqlen++;
+		tcp_reass_qsize++;
+		tcpstat.tcps_rcvoopack++;
+		tcpstat.tcps_rcvoobyte += m->m_pkthdr.len;
+		return (0);
+	}
 	/*
-	 * Limit the number of segments in the reassembly queue to prevent
-	 * holding on to too many segments (and thus running out of mbufs).
-	 * Make sure to let the missing segment through which caused this
-	 * queue.  Always keep one global queue entry spare to be able to
-	 * process the missing segment.
-	 */
-	if (th->th_seq != tp->rcv_nxt &&
-	    (tcp_reass_qsize + 1 >= tcp_reass_maxseg ||
-	     tp->t_segqlen >= tcp_reass_maxqlen)) {
+	 * Next check if this is the missing first segment.
+	 * tcp_input() must have trimmed anything up to rcv_nxt.
+	 */
+	if (tp->rcv_nxt == mtrq->seq_start) {
+		if (mtrq->seq_end + 1 == tp->trqhead->seq_cont_start) {
+			/* Perfect fit. */
+			m->m_nextpkt = tp->trqhead->trq_m;
+			tp->trqhead = mtrq;
+			tp->trqlen++;
+			tcp_reass_qsize++;
+			tcpstat.tcps_rcvoopack++;
+			tcpstat.tcps_rcvoobyte += m->m_pkthdr.len;
+			goto present;
+		}
+		if (SEQ_GT(mtrq->seq_end + 1, tp->trqhead->seq_cont_start)) {
+			/* Overlaps into first queued seg, trim right edge. */
+			trim = SEQ_DELTA(tp->trqhead->seq_cont_start,
+					 mtrq->seq_end + 1);
+			KASSERT(trim < 0 && trim > -*tlenp,
+				("%s: trimming mbuf tail but trim bad!",
+				__func__));
+			m_adj(m, trim);		/* Trim off tail. */
+			*tlenp = m->m_pkthdr.len;
+			mtrq->seq_end += trim;
+			mtrq->seq_cont_end = mtrq->seq_end;
+			m->m_nextpkt = tp->trqhead->trq_m;
+			tp->trqhead = mtrq;
+			tp->trqlen++;
+			tcp_reass_qsize++;
+			tcpstat.tcps_rcvoopack++;
+			tcpstat.tcps_rcvoobyte += m->m_pkthdr.len;
+			goto present;
+		}
+		/* Else it doesn't reach the end but present it anyway. */
+		m->m_nextpkt = tp->trqhead->trq_m;
+		tp->trqhead = mtrq;
+		tp->trqlen++;
+		tcp_reass_qsize++;
+		tcpstat.tcps_rcvoopack++;
+		tcpstat.tcps_rcvoobyte += m->m_pkthdr.len;
+		goto present;
+	}
+	/* Limit the number of segments in the queue. */
+	if (tcp_reass_qsize + 1 >= tcp_reass_maxseg ||
+	    tp->trqlen >= tcp_reass_maxqlen) {
 		tcp_reass_overflows++;
 		tcpstat.tcps_rcvmemdrop++;
 		m_freem(m);
 		return (0);
 	}
-
 	/*
-	 * Allocate a new queue entry. If we can't, or hit the zone limit
-	 * just drop the pkt.
+	 * Check if it is between rcv_nxt and first queue entry.
 	 */
-	te = uma_zalloc(tcp_reass_zone, M_NOWAIT);
-	if (te == NULL) {
-		tcpstat.tcps_rcvmemdrop++;
-		m_freem(m);
+	if (SEQ_GT(mtrq->seq_start, tp->rcv_nxt) &&
+	    SEQ_LT(mtrq->seq_start, tp->trqhead->seq_cont_start)) {
+		/* Does the end overlap into the next segment chain? */
+		if (SEQ_GEQ(mtrq->seq_end + 1, tp->trqhead->seq_cont_start)) {
+			if (SEQ_GT(mtrq->seq_end + 1,
+				   tp->trqhead->seq_cont_start)) {
+				/* Trim right edge off. */
+				trim = SEQ_DELTA(tp->trqhead->seq_cont_start,
+						 mtrq->seq_end + 1);
+				KASSERT(trim < 0 && trim > -*tlenp,
+				    ("%s: trimming mbuf tail but trim bad!",
+				     __func__));
+				m_adj(m, trim);		/* Trim off tail. */
+				*tlenp = m->m_pkthdr.len;
+				mtrq->seq_end += trim;
+				mtrq->seq_cont_end = mtrq->seq_end;
+			}
+			/* Merge as head of next continous segment chain. */
+			mtrq->seq_cont_end = tp->trqhead->seq_cont_end;
+			mtrq->trq_cont_last = tp->trqhead->trq_cont_last;
+			tp->trqhead->trq_cont_last->seq_cont_start =
+				mtrq->seq_start;
+			tp->trqhead->trq_cont_last->trq_cont_first = mtrq;
+		}
+		/*
+		 * Make this the new first segment,
+		 * but do not link back to itself.
+		 */
+		if (m != tp->trqhead->trq_m)
+			m->m_nextpkt = tp->trqhead->trq_m;
+		tp->trqhead = mtrq;
+		tp->trqlen++;
+		tcp_reass_qsize++;
+		tcpstat.tcps_rcvoopack++;
+		tcpstat.tcps_rcvoobyte += m->m_pkthdr.len;
 		return (0);
 	}
-	tp->t_segqlen++;
-	tcp_reass_qsize++;
-
 	/*
-	 * Find a segment which begins after this one does.
+	 * Then start cycling from the end of the
+	 * first segment chain in the queue.
 	 */
-	LIST_FOREACH(q, &tp->t_segq, tqe_q) {
-		if (SEQ_GT(q->tqe_th->th_seq, th->th_seq))
+	mtrqprev = tp->trqhead->trq_cont_last;
+	do {
+		/* Do we have another segment chain behind this one? */
+		if (mtrqprev->trq_m->m_nextpkt) {
+			KASSERT(m != m->m_nextpkt,
+				("%s: m_nextpkt pointing to self!", __func__));
+			mtrqnext = mtrqp(mtrqprev->trq_m->m_nextpkt);
+		} else {
+			mtrqnext = NULL;
+		}
+		if (mtrqprev == mtrqnext) {
+			printf(" mtrqprev == mtrqnext, aborting\n");
 			break;
-		p = q;
-	}
-
-	/*
-	 * If there is a preceding segment, it may provide some of
-	 * our data already.  If so, drop the data from the incoming
-	 * segment.  If it provides all of our data, drop us.
-	 */
-	if (p != NULL) {
-		register int i;
-		/* conversion to int (in i) handles seq wraparound */
-		i = p->tqe_th->th_seq + p->tqe_len - th->th_seq;
-		if (i > 0) {
-			if (i >= *tlenp) {
-				tcpstat.tcps_rcvduppack++;
-				tcpstat.tcps_rcvdupbyte += *tlenp;
-				m_freem(m);
-				uma_zfree(tcp_reass_zone, te);
-				tp->t_segqlen--;
-				tcp_reass_qsize--;
-				/*
-				 * Try to present any queued data
-				 * at the left window edge to the user.
-				 * This is needed after the 3-WHS
-				 * completes.
-				 */
-				goto present;	/* ??? */
+		}
+		/* Jump over segment chains if we can't possibly match. */
+		if (SEQ_GT(mtrq->seq_start, mtrqprev->seq_cont_end + 1)) {
+			if (!mtrqnext)
+				break;
+			if (mtrqnext && SEQ_LT(mtrq->seq_end + 1,
+						mtrqnext->seq_cont_start))
+				break;
+			if (mtrqnext && SEQ_GEQ(mtrq->seq_start + 1,
+						mtrqnext->seq_cont_start)) {
+				mtrqprev = mtrqp(mtrqprev->trq_m->m_nextpkt)->trq_cont_last;
+				continue;
 			}
-			m_adj(m, i);
-			*tlenp -= i;
-			th->th_seq += i;
+			/* fallthrough. */
 		}
-	}
+		/* The probability is high for completely overlapping segments. */
+		if (SEQ_GEQ(mtrq->seq_start, mtrqprev->seq_cont_start) &&
+		    SEQ_LEQ(mtrq->seq_end, mtrqprev->seq_cont_end)) {
+			/* Completely overlapping, free current segment. */
+			tcpstat.tcps_rcvduppack++;
+			tcpstat.tcps_rcvdupbyte += m->m_pkthdr.len;
+			m_freem(m);
+			return (0);
+		}
+		/* Or that it fits into a hole between two consecutive segment chains. */
+		if (mtrqnext &&
+		    SEQ_LEQ(mtrq->seq_start, mtrqprev->seq_cont_end + 1) &&
+		    SEQ_GEQ(mtrq->seq_end + 1, mtrqnext->seq_cont_start)) {
+			/* Trim left edge. */
+			trim = SEQ_DELTA(mtrqprev->seq_cont_end + 1, mtrq->seq_start);
+			if (trim) {
+				KASSERT(trim > 0 && trim < *tlenp,
+					("%s: trimming mbuf head but trim bad!",
+					__func__));
+				m_adj(m, trim);		/* Trim off tail. */
+				*tlenp = m->m_pkthdr.len;
+				mtrq->seq_start += trim;
+				mtrq->seq_cont_start = mtrq->seq_start;
+			}
+			/* Trim right edge. */
+			trim = SEQ_DELTA(mtrqnext->seq_cont_start, mtrq->seq_end + 1);
+			if (trim) {
+				KASSERT(trim < 0 && trim > -*tlenp,
+					("%s: trimming mbuf tail but trim bad!",
+					__func__));
+				m_adj(m, trim);		/* Trim off head. */
+				*tlenp = m->m_pkthdr.len;
+				mtrq->seq_end += trim;
+				mtrq->seq_cont_end = mtrq->seq_end;
+			}
+			mtrqprev->trq_m->m_nextpkt = m;
+			m->m_nextpkt = mtrqnext->trq_m;
+			/* Merge both continous segment chains into one. */
+			mtrqprev->trq_cont_first->seq_cont_end = mtrqnext->seq_cont_end;
+			mtrqprev->trq_cont_first->trq_cont_last = mtrqnext->trq_cont_last;
+			mtrqnext->trq_cont_last->seq_cont_start = mtrqprev->seq_cont_start;
+			mtrqnext->trq_cont_last->trq_cont_first = mtrqprev->trq_cont_first;
+			tp->trqlen++;
+			tcp_reass_qsize++;
+			tcpstat.tcps_rcvoopack++;
+			tcpstat.tcps_rcvoobyte += m->m_pkthdr.len;
+			return (0);
+		}
+		/* Or it fits to the right of previous segment chain. */
+		if (SEQ_GT(mtrq->seq_end, mtrqprev->seq_cont_end) &&
+		    SEQ_LEQ(mtrq->seq_start, mtrqprev->seq_cont_end + 1)) {
+			/* Trim left edge. */
+			trim = SEQ_DELTA(mtrqprev->seq_cont_end + 1,
+					 mtrq->seq_start);
+			if (trim) {
+				KASSERT(trim > 0 && trim < *tlenp,
+					("%s: trimming mbuf head but trim bad!",
+					__func__));
+				m_adj(m, trim);		/* Trim off head. */
+				*tlenp = m->m_pkthdr.len;
+				mtrq->seq_start += trim;
+				mtrq->seq_cont_start = mtrq->seq_start;
+			}
+			/* Append to chain. */
+			m->m_nextpkt = mtrqprev->trq_m->m_nextpkt;
+			mtrqprev->trq_m->m_nextpkt = m;
+			mtrqprev->trq_cont_first->seq_cont_end = mtrq->seq_end;
+			mtrqprev->trq_cont_first->trq_cont_last = mtrq;
+			mtrq->seq_cont_start = mtrqprev->seq_cont_start;
+			mtrq->trq_cont_first = mtrqprev->trq_cont_first;
+			tp->trqlen++;
+			tcp_reass_qsize++;
+			tcpstat.tcps_rcvoopack++;
+			tcpstat.tcps_rcvoobyte += m->m_pkthdr.len;
+			return (0);
+		}
+		/* Or it fits to the left of next segment chain. */
+		if (mtrqnext &&
+		    SEQ_LT(mtrq->seq_start, mtrqnext->seq_cont_start) &&
+		    SEQ_GEQ(mtrq->seq_end + 1, mtrqnext->seq_cont_start)) {
+			/* Trim right edge. */
+			trim = SEQ_DELTA(mtrqnext->seq_cont_start,
+					 mtrq->seq_end + 1);
+			if (trim) {
+				KASSERT(trim < 0 && trim > -*tlenp,
+					("%s: trimming mbuf tail but trim bad!",
+					__func__));
+				m_adj(m, trim);		/* Trim off tail. */
+				*tlenp = m->m_pkthdr.len;
+				mtrq->seq_end += trim;
+				mtrq->seq_cont_end = mtrq->seq_end;
+			}
+			/* Prepend to chain. */
+			m->m_nextpkt = mtrqnext->trq_m;
+			mtrqprev->trq_m->m_nextpkt = m;
+			mtrq->seq_cont_end = mtrqnext->seq_cont_end;
+			mtrq->trq_cont_last = mtrqnext->trq_cont_last;
+			mtrq->trq_cont_last->seq_cont_start = mtrq->seq_start;
+			mtrq->trq_cont_last->trq_cont_first = mtrq;
+			tp->trqlen++;
+			tcp_reass_qsize++;
+			tcpstat.tcps_rcvoopack++;
+			tcpstat.tcps_rcvoobyte += m->m_pkthdr.len;
+			return (0);
+		}
+		if (mtrqnext) {
+			mtrqprev = mtrqp(mtrqprev->trq_m->m_nextpkt)->trq_cont_last;
+		}
+	} while (mtrqnext);
+	/* Nothing overlapping or matching found.  Too bad, new hole. */
+	mtrqprev->trq_m->m_nextpkt = m;
+	if (mtrqnext)
+		m->m_nextpkt = mtrqnext->trq_m;
+	else
+		tp->trqtail = mtrq;
+	/* All other fields are already properly initialized. */
+	tp->trqlen++;
+	tcp_reass_qsize++;
 	tcpstat.tcps_rcvoopack++;
-	tcpstat.tcps_rcvoobyte += *tlenp;
-
-	/*
-	 * While we overlap succeeding segments trim them or,
-	 * if they are completely covered, dequeue them.
-	 */
-	while (q) {
-		register int i = (th->th_seq + *tlenp) - q->tqe_th->th_seq;
-		if (i <= 0)
-			break;
-		if (i < q->tqe_len) {
-			q->tqe_th->th_seq += i;
-			q->tqe_len -= i;
-			m_adj(q->tqe_m, i);
-			break;
-		}
-
-		nq = LIST_NEXT(q, tqe_q);
-		LIST_REMOVE(q, tqe_q);
-		m_freem(q->tqe_m);
-		uma_zfree(tcp_reass_zone, q);
-		tp->t_segqlen--;
-		tcp_reass_qsize--;
-		q = nq;
-	}
-
-	/* Insert the new segment queue entry into place. */
-	te->tqe_m = m;
-	te->tqe_th = th;
-	te->tqe_len = *tlenp;
-
-	if (p == NULL) {
-		LIST_INSERT_HEAD(&tp->t_segq, te, tqe_q);
-	} else {
-		LIST_INSERT_AFTER(p, te, tqe_q);
-	}
+	tcpstat.tcps_rcvoobyte += m->m_pkthdr.len;
+	return (0);
 
 present:
 	/*
@@ -357,29 +583,42 @@ present:
 	 */
 	if (!TCPS_HAVEESTABLISHED(tp->t_state))
 		return (0);
-	q = LIST_FIRST(&tp->t_segq);
-	if (!q || q->tqe_th->th_seq != tp->rcv_nxt)
+	if (!tp->trqhead || !tp->trqtail)
+		return (0);
+	KASSERT(SEQ_GEQ(tp->trqhead->seq_start, tp->rcv_nxt),
+		("%s: segment in queue head below rcv_nxt!", __func__));
+	if (tp->trqhead->seq_start != tp->rcv_nxt)
 		return (0);
 	SOCKBUF_LOCK(&so->so_rcv);
 	do {
-		tp->rcv_nxt += q->tqe_len;
-		flags = q->tqe_th->th_flags & TH_FIN;
-		nq = LIST_NEXT(q, tqe_q);
-		LIST_REMOVE(q, tqe_q);
+		mtrq = tp->trqhead;
+		mm = mtrq->trq_m;
+		if (mm->m_nextpkt) {
+			KASSERT(mm != mm->m_nextpkt,
+				("%s: m_nextpkt pointing to self!", __func__));
+			tp->trqhead = mtrqp(mm->m_nextpkt);
+		} else {
+			tp->trqhead = NULL;
+			tp->trqtail = NULL;
+		}
+		mm->m_nextpkt = NULL;
+		tp->trqlen--;
+		tcp_reass_qsize--;
+		KASSERT(SEQ_GEQ(mtrq->seq_start, tp->rcv_nxt),
+			("%s: segment dequeued is below rcv_nxt!", __func__));
+		tp->rcv_nxt += (mtrq->seq_end + 1) - mtrq->seq_start;
+		flags = mtrq->seg_flags & TH_FIN;
 		if (so->so_rcv.sb_state & SBS_CANTRCVMORE)
-			m_freem(q->tqe_m);
+			m_freem(mm);
 		else
-			sbappendstream_locked(&so->so_rcv, q->tqe_m);
-		uma_zfree(tcp_reass_zone, q);
-		tp->t_segqlen--;
-		tcp_reass_qsize--;
-		q = nq;
-	} while (q && q->tqe_th->th_seq == tp->rcv_nxt);
+			sbappendstream_locked(&so->so_rcv, mm);
+	} while (tp->trqhead && tp->rcv_nxt == tp->trqhead->seq_start);
 	ND6_HINT(tp);
 	sorwakeup_locked(so);
 	return (flags);
 }
 
+
 /*
  * TCP input routine, follows pages 65-76 of the
  * protocol specification dated September, 1981 very closely.
@@ -1237,7 +1476,7 @@ after_listen:
 				goto check_delack;
 			}
 		} else if (th->th_ack == tp->snd_una &&
-		    LIST_EMPTY(&tp->t_segq) &&
+		    !tp->trqhead &&
 		    tlen <= sbspace(&so->so_rcv)) {
 			KASSERT(headlocked, ("headlocked"));
 			/*
@@ -2276,7 +2515,7 @@ dodata:							/* XXX */
 		 * fast retransmit can work).
 		 */
 		if (th->th_seq == tp->rcv_nxt &&
-		    LIST_EMPTY(&tp->t_segq) &&
+		    !tp->trqhead &&
 		    TCPS_HAVEESTABLISHED(tp->t_state)) {
 			if (DELAY_ACK(tp))
 				tp->t_flags |= TF_DELACK;
Index: tcp_seq.h
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_seq.h,v
retrieving revision 1.23
diff -u -p -r1.23 tcp_seq.h
--- tcp_seq.h	2 Nov 2004 22:22:21 -0000	1.23
+++ tcp_seq.h	13 Dec 2004 17:25:32 -0000
@@ -41,6 +41,7 @@
 #define	SEQ_LEQ(a,b)	((int)((a)-(b)) <= 0)
 #define	SEQ_GT(a,b)	((int)((a)-(b)) > 0)
 #define	SEQ_GEQ(a,b)	((int)((a)-(b)) >= 0)
+#define	SEQ_DELTA(a,b)	((int)((a)-(b)))
 
 #define	SEQ_MIN(a, b)	((SEQ_LT(a, b)) ? (a) : (b))
 #define	SEQ_MAX(a, b)	((SEQ_GT(a, b)) ? (a) : (b))
Index: tcp_subr.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_subr.c,v
retrieving revision 1.212
diff -u -p -r1.212 tcp_subr.c
--- tcp_subr.c	5 Dec 2004 22:27:53 -0000	1.212
+++ tcp_subr.c	13 Dec 2004 17:25:32 -0000
@@ -665,9 +665,9 @@ static void
 tcp_discardcb(tp)
 	struct tcpcb *tp;
 {
-	struct tseg_qent *q;
 	struct inpcb *inp = tp->t_inpcb;
 	struct socket *so = inp->inp_socket;
+	struct mbuf *m, *n;
 #ifdef INET6
 	int isipv6 = (inp->inp_vflag & INP_IPV6) != 0;
 #endif /* INET6 */
@@ -739,13 +739,19 @@ tcp_discardcb(tp)
 		tcp_hc_update(&inp->inp_inc, &metrics);
 	}
 
-	/* free the reassembly queue, if any */
-	while ((q = LIST_FIRST(&tp->t_segq)) != NULL) {
-		LIST_REMOVE(q, tqe_q);
-		m_freem(q->tqe_m);
-		uma_zfree(tcp_reass_zone, q);
-		tp->t_segqlen--;
-		tcp_reass_qsize--;
+	/* Free the reassembly queue, if any. */
+	if (tp->trqhead) {
+		m = tp->trqhead->trq_m;
+		while (m) {
+			n = m->m_nextpkt;
+			m_freem(m);
+			tp->trqlen--;
+			tcp_reass_qsize--;
+			m = n;
+		}
+		tp->trqhead = NULL;
+		tp->trqtail = NULL;
+		KASSERT(tp->trqlen == 0, ("%s: tcp reassembly queue length != 0", __func__));
 	}
 	tcp_free_sackholes(tp);
 	inp->inp_ppcb = NULL;
@@ -789,7 +795,7 @@ tcp_drain()
 	{
 		struct inpcb *inpb;
 		struct tcpcb *tcpb;
-		struct tseg_qent *te;
+		struct mbuf *m, *n;
 
 	/*
 	 * Walk the tcpbs, if existing, and flush the reassembly queue,
@@ -798,6 +804,7 @@ tcp_drain()
 	 *      reassembly queue should be flushed, but in a situation
 	 *	where we're really low on mbufs, this is potentially
 	 *	usefull.
+	 * XXX: This breaks with SACK!
 	 */
 		INP_INFO_RLOCK(&tcbinfo);
 		LIST_FOREACH(inpb, tcbinfo.listhead, inp_list) {
@@ -805,13 +812,20 @@ tcp_drain()
 				continue;
 			INP_LOCK(inpb);
 			if ((tcpb = intotcpcb(inpb)) != NULL) {
-				while ((te = LIST_FIRST(&tcpb->t_segq))
-			            != NULL) {
-					LIST_REMOVE(te, tqe_q);
-					m_freem(te->tqe_m);
-					uma_zfree(tcp_reass_zone, te);
-					tcpb->t_segqlen--;
-					tcp_reass_qsize--;
+				if (tcpb->trqhead) {
+					m = tcpb->trqhead->trq_m;
+					while (m) {
+						n = m->m_nextpkt;
+						m_freem(m);
+						tcpb->trqlen--;
+						tcp_reass_qsize--;
+						m = n;
+					}
+					tcpb->trqhead = NULL;
+					tcpb->trqtail = NULL;
+					KASSERT(tcpb->trqlen == 0,
+					    ("%s: tcp reassembly queue length != 0",
+					     __func__));
 				}
 			}
 			INP_UNLOCK(inpb);
@@ -1381,11 +1395,13 @@ tcp_quench(inp, errno)
 	struct inpcb *inp;
 	int errno;
 {
+#if 0	/* XXX: Deprecated and no longer supported. */
 	struct tcpcb *tp = intotcpcb(inp);
 
 	INP_LOCK_ASSERT(inp);
 	if (tp != NULL)
 		tp->snd_cwnd = tp->t_maxseg;
+#endif
 	return (inp);
 }
 
Index: tcp_var.h
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_var.h,v
retrieving revision 1.112
diff -u -p -r1.112 tcp_var.h
--- tcp_var.h	2 Nov 2004 22:22:21 -0000	1.112
+++ tcp_var.h	13 Dec 2004 17:25:33 -0000
@@ -40,16 +40,22 @@
  */
 extern int	tcp_do_rfc1323;
 
-/* TCP segment queue entry */
-struct tseg_qent {
-	LIST_ENTRY(tseg_qent) tqe_q;
-	int	tqe_len;		/* TCP segment data length */
-	struct	tcphdr *tqe_th;		/* a pointer to tcp header */
-	struct	mbuf	*tqe_m;		/* mbuf contains packet */
-};
-LIST_HEAD(tsegqe_head, tseg_qent);
-extern int	tcp_reass_qsize;
-extern struct uma_zone *tcp_reass_zone;
+/*
+ * TCP reassembly queue structure embedded into mbuf.
+ */
+struct trq {
+	/* Always valid. */
+	struct mbuf *trq_m;
+	tcp_seq seq_start;
+	tcp_seq seq_end;
+	int seg_flags;
+	/* Only valid when first or last packet in segment chain. */
+	tcp_seq seq_cont_start;
+	tcp_seq seq_cont_end;
+	struct trq *trq_cont_first;
+	struct trq *trq_cont_last;
+};
+extern int	tcp_reass_qsize;	/* global reassembly limit */
 
 struct sackblk {
 	tcp_seq start;		/* start seq no. of sack block */
@@ -75,8 +81,10 @@ struct tcptemp {
  * Organized for 16 byte cacheline efficiency.
  */
 struct tcpcb {
-	struct	tsegqe_head t_segq;	/* segment reassembly queue */
-	int	t_segqlen;		/* segment reassembly queue length */
+	struct	trq *trqhead;		/* head of reassembly queue */
+	struct	trq *trqtail;		/* tail of reassembly queue */
+	int	trqlen;			/* segment reassembly queue length */
+
 	int	t_dupacks;		/* consecutive dup acks recd */
 
 	struct	callout *tt_rexmt;	/* retransmit timer */
