Index: tcp_input.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_input.c,v
retrieving revision 1.227
diff -u -p -r1.227 tcp_input.c
--- tcp_input.c	18 Feb 2004 00:04:52 -0000	1.227
+++ tcp_input.c	21 Feb 2004 20:48:36 -0000
@@ -58,6 +58,8 @@
 
 #include <machine/cpu.h>	/* before tcp_seq.h, for tcp_random18() */
 
+#include <vm/uma.h>
+
 #include <net/if.h>
 #include <net/route.h>
 
@@ -98,8 +100,6 @@
 
 #include <machine/in_cksum.h>
 
-MALLOC_DEFINE(M_TSEGQ, "tseg_qent", "TCP segment queue entry");
-
 static const int tcprexmtthresh = 3;
 tcp_cc	tcp_ccgen;
 
@@ -135,6 +135,24 @@ SYSCTL_INT(_net_inet_tcp, OID_AUTO, rfc3
     &tcp_do_rfc3390, 0,
     "Enable RFC 3390 (Increasing TCP's Initial Congestion Window)");
 
+SYSCTL_NODE(_net_inet_tcp, OID_AUTO, reass, CTLFLAG_RW, 0,
+	    "TCP Segment Reassembly Queue");
+
+static int tcp_reass_maxseg = 0;
+SYSCTL_INT(_net_inet_tcp_reass, OID_AUTO, maxsegements, CTLFLAG_RDTUN,
+	   &tcp_reass_maxseg, 0,
+	   "Global maximum number of TCP Segements in Reassembly Queue");
+
+int tcp_reass_qsize = 0;
+SYSCTL_INT(_net_inet_tcp_reass, OID_AUTO, cursegements, CTLFLAG_RD,
+	   &tcp_reass_qsize, 0,
+	   "Current number of TCP Segements in Reassembly Queue");
+
+int tcp_reass_lifetime = 0;
+SYSCTL_PROC(_net_inet_tcp_reass, OID_AUTO, lifetime, CTLTYPE_INT|CTLFLAG_RW,
+	   &tcp_reass_lifetime, 0, sysctl_msec_to_ticks, "I",
+	   "Maximum TCP reassembly queue segment lifetime");
+
 struct inpcbhead tcb;
 #define	tcb6	tcb  /* for KAME src sync over BSD*'s */
 struct inpcbinfo tcbinfo;
@@ -175,6 +193,20 @@ 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;
+void
+tcp_reass_init()
+{
+	tcp_reass_lifetime = TCPTV_MSL;		/* default 1*msl */
+	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;
@@ -185,25 +217,51 @@ tcp_reass(tp, th, tlenp, m)
 	struct tseg_qent *q;
 	struct tseg_qent *p = NULL;
 	struct tseg_qent *nq;
-	struct tseg_qent *te;
+	struct tseg_qent *te = NULL;
 	struct socket *so = tp->t_inpcb->inp_socket;
 	int flags;
 
 	/*
+	 * 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.
+	 */
+
+	/*
 	 * Call with th==0 after become established to
 	 * force pre-ESTABLISHED data up to user socket.
 	 */
 	if (th == 0)
 		goto present;
 
-	/* Allocate a new queue entry. If we can't, just drop the pkt. XXX */
-	MALLOC(te, struct tseg_qent *, sizeof (struct tseg_qent), M_TSEGQ,
-	       M_NOWAIT);
+	/*
+	 * Limit the number of segments in the reassembly queue to receive
+	 * window divided by max segment size 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 >= tp->rcv_wnd / tp->t_maxseg)) {
+		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.
+	 */
+	te = uma_zalloc(tcp_reass_zone, M_NOWAIT);
 	if (te == NULL) {
 		tcpstat.tcps_rcvmemdrop++;
 		m_freem(m);
 		return (0);
 	}
+	tp->t_segqlen++;
+	tcp_reass_qsize++;
 
 	/*
 	 * Find a segment which begins after this one does.
@@ -228,7 +286,9 @@ tcp_reass(tp, th, tlenp, m)
 				tcpstat.tcps_rcvduppack++;
 				tcpstat.tcps_rcvdupbyte += *tlenp;
 				m_freem(m);
-				FREE(te, M_TSEGQ);
+				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.
@@ -263,7 +323,9 @@ tcp_reass(tp, th, tlenp, m)
 		nq = LIST_NEXT(q, tqe_q);
 		LIST_REMOVE(q, tqe_q);
 		m_freem(q->tqe_m);
-		FREE(q, M_TSEGQ);
+		uma_zfree(tcp_reass_zone, te);
+		tp->t_segqlen--;
+		tcp_reass_qsize--;
 		q = nq;
 	}
 
@@ -274,6 +336,9 @@ tcp_reass(tp, th, tlenp, m)
 
 	if (p == NULL) {
 		LIST_INSERT_HEAD(&tp->t_segq, te, tqe_q);
+		if (!callout_pending(tp->tt_reass))
+			callout_reset(tp->tt_reass, tcp_reass_lifetime,
+					tcp_timer_reass, tp);
 	} else {
 		LIST_INSERT_AFTER(p, te, tqe_q);
 	}
@@ -297,9 +362,12 @@ present:
 			m_freem(q->tqe_m);
 		else
 			sbappendstream(&so->so_rcv, q->tqe_m);
-		FREE(q, M_TSEGQ);
+		uma_zfree(tcp_reass_zone, te);
+		tp->t_segqlen--;
+		tcp_reass_qsize--;
 		q = nq;
 	} while (q && q->tqe_th->th_seq == tp->rcv_nxt);
+	callout_stop(tp->tt_reass);
 	ND6_HINT(tp);
 	sorwakeup(so);
 	return (flags);
Index: tcp_subr.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_subr.c,v
retrieving revision 1.181
diff -u -p -r1.181 tcp_subr.c
--- tcp_subr.c	19 Feb 2004 14:13:21 -0000	1.181
+++ tcp_subr.c	21 Feb 2004 20:48:36 -0000
@@ -227,7 +227,7 @@ static void	tcp_discardcb(struct tcpcb *
 struct	tcpcb_mem {
 	struct	tcpcb tcb;
 	struct	callout tcpcb_mem_rexmt, tcpcb_mem_persist, tcpcb_mem_keep;
-	struct	callout tcpcb_mem_2msl, tcpcb_mem_delack;
+	struct	callout tcpcb_mem_2msl, tcpcb_mem_delack, tcpcb_mem_reass;
 };
 
 static uma_zone_t tcpcb_zone;
@@ -289,6 +289,7 @@ tcp_init()
 	tcp_timer_init();
 	syncache_init();
 	tcp_hc_init();
+	tcp_reass_init();
 }
 
 /*
@@ -577,6 +578,7 @@ tcp_newtcpcb(inp)
 	callout_init(tp->tt_keep = &tm->tcpcb_mem_keep, 0);
 	callout_init(tp->tt_2msl = &tm->tcpcb_mem_2msl, 0);
 	callout_init(tp->tt_delack = &tm->tcpcb_mem_delack, 0);
+	callout_init(tp->tt_reass = &tm->tcpcb_mem_reass, 0);
 
 	if (tcp_do_rfc1323)
 		tp->t_flags = (TF_REQ_SCALE|TF_REQ_TSTMP);
@@ -651,6 +653,7 @@ tcp_discardcb(tp)
 	callout_stop(tp->tt_keep);
 	callout_stop(tp->tt_2msl);
 	callout_stop(tp->tt_delack);
+	callout_stop(tp->tt_reass);
 
 	/*
 	 * If we got enough samples through the srtt filter,
@@ -711,7 +714,9 @@ tcp_discardcb(tp)
 	while ((q = LIST_FIRST(&tp->t_segq)) != NULL) {
 		LIST_REMOVE(q, tqe_q);
 		m_freem(q->tqe_m);
-		FREE(q, M_TSEGQ);
+		uma_zfree(tcp_reass_zone, q);
+		tp->t_segqlen--;
+		tcp_reass_qsize--;
 	}
 	inp->inp_ppcb = NULL;
 	tp->t_inpcb = NULL;
@@ -768,11 +773,14 @@ tcp_drain()
 				continue;
 			INP_LOCK(inpb);
 			if ((tcpb = intotcpcb(inpb))) {
+				callout_stop(tcpb->tt_reass);
 				while ((te = LIST_FIRST(&tcpb->t_segq))
 			            != NULL) {
 					LIST_REMOVE(te, tqe_q);
 					m_freem(te->tqe_m);
-					FREE(te, M_TSEGQ);
+					uma_zfree(tcp_reass_zone, te);
+					tcpb->t_segqlen--;
+					tcp_reass_qsize--;
 				}
 			}
 			INP_UNLOCK(inpb);
Index: tcp_timer.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_timer.c,v
retrieving revision 1.63
diff -u -p -r1.63 tcp_timer.c
--- tcp_timer.c	20 Nov 2003 20:07:38 -0000	1.63
+++ tcp_timer.c	21 Feb 2004 20:48:37 -0000
@@ -48,6 +48,8 @@
 #include <sys/sysctl.h>
 #include <sys/systm.h>
 
+#include <vm/uma.h>
+
 #include <net/route.h>
 
 #include <netinet/in.h>
@@ -66,7 +68,7 @@
 #include <netinet/tcp_debug.h>
 #endif
 
-static int
+int
 sysctl_msec_to_ticks(SYSCTL_HANDLER_ARGS)
 {
 	int error, s, tt;
@@ -154,6 +156,7 @@ tcp_canceltimers(tp)
 	callout_stop(tp->tt_persist);
 	callout_stop(tp->tt_keep);
 	callout_stop(tp->tt_rexmt);
+	callout_stop(tp->tt_reass);
 }
 
 int	tcp_syn_backoff[TCP_MAXRXTSHIFT + 1] =
@@ -196,6 +199,48 @@ tcp_timer_delack(xtp)
 	tp->t_flags |= TF_ACKNOW;
 	tcpstat.tcps_delack++;
 	(void) tcp_output(tp);
+	INP_UNLOCK(inp);
+	splx(s);
+}
+
+void
+tcp_timer_reass(xtp)
+	void *xtp;
+{
+	struct tcpcb *tp = xtp;
+	int s;
+	struct inpcb *inp;
+
+	struct tseg_qent *q;
+
+	s = splnet();
+	INP_INFO_RLOCK(&tcbinfo);
+	inp = tp->t_inpcb;
+	if (!inp) {
+		INP_INFO_WUNLOCK(&tcbinfo);
+		splx(s);
+		return;
+	}
+	INP_LOCK(inp);
+	INP_INFO_RUNLOCK(&tcbinfo);
+	if (callout_pending(tp->tt_reass) || !callout_active(tp->tt_reass)) {
+		INP_UNLOCK(tp->t_inpcb);
+		INP_INFO_WUNLOCK(&tcbinfo);
+		splx(s);
+		return;
+	}
+	callout_deactivate(tp->tt_reass);
+	callout_stop(tp->tt_reass);
+
+	/* free the reassembly queue */
+	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--;
+        }
+
 	INP_UNLOCK(inp);
 	splx(s);
 }
Index: tcp_timer.h
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_timer.h,v
retrieving revision 1.24
diff -u -p -r1.24 tcp_timer.h
--- tcp_timer.h	8 Mar 2003 22:06:20 -0000	1.24
+++ tcp_timer.h	21 Feb 2004 20:48:37 -0000
@@ -163,6 +163,7 @@ void	tcp_timer_keep(void *xtp);
 void	tcp_timer_persist(void *xtp);
 void	tcp_timer_rexmt(void *xtp);
 void	tcp_timer_delack(void *xtp);
+void	tcp_timer_reass(void *xtp);
 
 #endif /* _KERNEL */
 
Index: tcp_var.h
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_var.h,v
retrieving revision 1.97
diff -u -p -r1.97 tcp_var.h
--- tcp_var.h	13 Feb 2004 18:21:45 -0000	1.97
+++ tcp_var.h	21 Feb 2004 20:48:37 -0000
@@ -54,8 +54,9 @@ struct tseg_qent {
 	struct	mbuf	*tqe_m;		/* mbuf contains packet */
 };
 LIST_HEAD(tsegqe_head, tseg_qent);
-#ifdef MALLOC_DECLARE
-MALLOC_DECLARE(M_TSEGQ);
+extern int	tcp_reass_qsize;
+#ifdef VM_UMA_H
+extern uma_zone_t tcp_reass_zone;
 #endif
 
 struct tcptemp {
@@ -70,7 +71,8 @@ struct tcptemp {
  * Organized for 16 byte cacheline efficiency.
  */
 struct tcpcb {
-	struct	tsegqe_head t_segq;
+	struct	tsegqe_head t_segq;	/* segment reassembly queue */
+	int	t_segqlen;		/* segment reassembly queue length */
 	int	t_dupacks;		/* consecutive dup acks recd */
 	struct	tcptemp	*unused;	/* unused */
 
@@ -79,6 +81,7 @@ struct tcpcb {
 	struct	callout *tt_keep;	/* keepalive */
 	struct	callout *tt_2msl;	/* 2*msl TIME_WAIT timer */
 	struct	callout *tt_delack;	/* delayed ACK timer */
+	struct	callout	*tt_reass;	/* reassembly queue flush timer */
 
 	struct	inpcb *t_inpcb;		/* back pointer to internet pcb */
 	int	t_state;		/* state of this connection */
@@ -505,6 +508,10 @@ extern	int path_mtu_discovery;
 extern	int ss_fltsz;
 extern	int ss_fltsz_local;
 
+#ifdef SYSCTL_HANDLER_ARGS
+int	 sysctl_msec_to_ticks(SYSCTL_HANDLER_ARGS);
+#endif
+
 void	 tcp_canceltimers(struct tcpcb *);
 struct tcpcb *
 	 tcp_close(struct tcpcb *);
@@ -519,6 +526,7 @@ struct tcpcb *
 void	 tcp_drain(void);
 void	 tcp_fasttimo(void);
 void	 tcp_init(void);
+void	 tcp_reass_init(void);
 void	 tcp_input(struct mbuf *, int);
 u_long	 tcp_maxmtu(struct in_conninfo *);
 u_long	 tcp_maxmtu6(struct in_conninfo *);
