Index: conf/options
===================================================================
RCS file: /home/dcvs/src/sys/conf/options,v
retrieving revision 1.22
diff -u -p -r1.22 options
--- conf/options	24 Jun 2004 08:15:11 -0000	1.22
+++ conf/options	26 Jun 2004 17:35:04 -0000
@@ -516,3 +516,6 @@ HIFN_NO_RNG		opt_hifn.h
 
 # ed driver
 ED_NO_MIIBUS		opt_ed.h
+
+# sockbuf debuging
+SOCKBUF_DEBUG		opt_global.h
Index: kern/uipc_socket.c
===================================================================
RCS file: /home/dcvs/src/sys/kern/uipc_socket.c,v
retrieving revision 1.22
diff -u -p -r1.22 uipc_socket.c
--- kern/uipc_socket.c	6 Jun 2004 19:16:06 -0000	1.22
+++ kern/uipc_socket.c	26 Jun 2004 17:35:12 -0000
@@ -828,6 +828,8 @@ restart:
 			error = EWOULDBLOCK;
 			goto release;
 		}
+		SBLASTRECORDCHK(&so->so_rcv, "soreceive sbwait 1");
+		SBLASTMBUFCHK(&so->so_rcv, "soreceive sbwait 1");
 		sbunlock(&so->so_rcv);
 		error = sbwait(&so->so_rcv);
 		splx(s);
@@ -836,6 +838,14 @@ restart:
 		goto restart;
 	}
 dontblock:
+	/*
+	 * On entry here, m points to the first record of the socket buffer.
+	 * While we process the initial mbufs containing address and control
+	 * info, we save a copy of m->m_nextpkt into nextrecord.
+	 */
+	KKASSERT(m == so->so_rcv.sb_mb);
+	SBLASTRECORDCHK(&so->so_rcv, "soreceive 1");
+	SBLASTMBUFCHK(&so->so_rcv, "soreceive 1");
 	if (uio->uio_td && uio->uio_td->td_proc)
 		uio->uio_td->td_proc->p_stats->p_ru.ru_msgrcv++;
 	nextrecord = m->m_nextpkt;
@@ -878,13 +888,38 @@ dontblock:
 			controlp = &(*controlp)->m_next;
 		}
 	}
+	/*
+	 * If m is non-NULL, we have some data to read.  From now on,
+	 * make sure to keep sb_lastrecord consistent when working on
+	 * the last packet on the chain (nextrecord == NULL) and we
+	 * change m->m_nextpkt.
+	 */
 	if (m) {
-		if ((flags & MSG_PEEK) == 0)
+		if ((flags & MSG_PEEK) == 0) {
 			m->m_nextpkt = nextrecord;
+			/*
+			 * If nextrecord == NULL (this is a single chain),
+			 * then sb_lastrecord may not be valid here if m
+			 * was changed earlier.
+			 */
+			if (nextrecord == NULL) {
+				KKASSERT(so->so_rcv.sb_mb == m);
+				so->so_rcv.sb_lastrecord = m;
+			}
+		}
 		type = m->m_type;
 		if (type == MT_OOBDATA)
 			flags |= MSG_OOB;
 	}
+	else {
+		if ((flags & MSG_PEEK) == 0) {
+			KKASSERT(so->so_rcv.sb_mb == m);
+			so->so_rcv.sb_mb = nextrecord;
+			SB_UPDATE_TAIL(&so->so_rcv);
+		}
+	}
+	SBLASTRECORDCHK(&so->so_rcv, "soreceive 2");
+	SBLASTMBUFCHK(&so->so_rcv, "soreceive 2");
 	moff = 0;
 	offset = 0;
 	while (m && uio->uio_resid > 0 && error == 0) {
@@ -911,6 +946,8 @@ dontblock:
 		 * block interrupts again.
 		 */
 		if (mp == 0) {
+			SBLASTRECORDCHK(&so->so_rcv, "soreceive uiomove");
+			SBLASTMBUFCHK(&so->so_rcv, "soreceive uiomove");
 			splx(s);
 			error = uiomove(mtod(m, caddr_t) + moff, (int)len, uio);
 			s = splnet();
@@ -935,8 +972,22 @@ dontblock:
 				} else {
 					so->so_rcv.sb_mb = m = m_free(m);
 				}
-				if (m)
+				if (m) {
+					/*
+					 * If m != NULL, we also know that
+					 * so->so_rcv.sb_mb != NULL.
+					 */
+					KKASSERT(so->so_rcv.sb_mb == m);
 					m->m_nextpkt = nextrecord;
+					if (nextrecord == NULL)
+						so->so_rcv.sb_lastrecord = m;
+				}
+				else {
+					so->so_rcv.sb_mb = nextrecord;
+					SB_UPDATE_TAIL(&so->so_rcv);
+				}
+				SBLASTRECORDCHK(&so->so_rcv, "soreceive 3");
+				SBLASTMBUFCHK(&so->so_rcv, "soreceive 3");
 			}
 		} else {
 			if (flags & MSG_PEEK)
@@ -983,6 +1034,8 @@ dontblock:
 			 */
 			if (pr->pr_flags & PR_WANTRCVD && so->so_pcb)
 				so_pru_rcvd(so, flags);
+			SBLASTRECORDCHK(&so->so_rcv, "soreceive sbwait 2");
+			SBLASTMBUFCHK(&so->so_rcv, "soreceive sbwait 2");
 			error = sbwait(&so->so_rcv);
 			if (error) {
 				sbunlock(&so->so_rcv);
@@ -1001,8 +1054,23 @@ dontblock:
 			(void) sbdroprecord(&so->so_rcv);
 	}
 	if ((flags & MSG_PEEK) == 0) {
-		if (m == 0)
+		/*
+		 * First part is an inline SB_UPDATE_TAIL().  Second
+		 * part makes sure sb_lastrecord is up-to-date if
+		 * there is still data in the socket buffer.
+		 */
+		if (m == 0) {
 			so->so_rcv.sb_mb = nextrecord;
+			if (so->so_rcv.sb_mb == NULL) {
+				so->so_rcv.sb_mbtail = NULL;
+				so->so_rcv.sb_lastrecord = NULL;
+			}
+			else if (nextrecord->m_nextpkt == NULL) {
+				so->so_rcv.sb_lastrecord = nextrecord;
+			}
+		}
+		SBLASTRECORDCHK(&so->so_rcv, "soreceive 4");
+		SBLASTMBUFCHK(&so->so_rcv, "soreceive 4");
 		if (pr->pr_flags & PR_WANTRCVD && so->so_pcb)
 			so_pru_rcvd(so, flags);
 	}
Index: kern/uipc_socket2.c
===================================================================
RCS file: /home/dcvs/src/sys/kern/uipc_socket2.c,v
retrieving revision 1.13
diff -u -p -r1.13 uipc_socket2.c
--- kern/uipc_socket2.c	6 Jun 2004 19:16:06 -0000	1.13
+++ kern/uipc_socket2.c	26 Jun 2004 17:35:12 -0000
@@ -467,6 +467,61 @@ sbrelease(sb, so)
  * or sbdroprecord() when the data is acknowledged by the peer.
  */
 
+#ifdef SOCKBUF_DEBUG
+void
+sblastrecordchk(struct sockbuf *sb, const char *where)
+{
+	struct mbuf *m = sb->sb_mb;
+
+	while (m && m->m_nextpkt)
+		m = m->m_nextpkt;
+
+	if (m != sb->sb_lastrecord) {
+		printf("sblastrecordchk: sb_mb %p sb_lastrecord %p last %p\n",
+		    sb->sb_mb, sb->sb_lastrecord, m);
+		printf("packet chain:\n");
+		for (m = sb->sb_mb; m != NULL; m = m->m_nextpkt)
+			printf("\t%p\n", m);
+		panic("sblastrecordchk from %s\n", where);
+	}
+}
+
+void
+sblastmbufchk(struct sockbuf *sb, const char *where)
+{
+	struct mbuf *m = sb->sb_mb;
+	struct mbuf *n;
+
+	while (m && m->m_nextpkt)
+		m = m->m_nextpkt;
+
+	while (m && m->m_next) 
+		m = m->m_next;
+
+	if (m != sb->sb_mbtail) {
+		printf("sblastmbufchk: sb_mb %p sb_mbtail %p last %p\n",
+		    sb->sb_mb, sb->sb_mbtail, m);
+		printf("packet tree:\n");
+		for (m = sb->sb_mb; m != NULL; m = m->m_nextpkt) {
+			printf("\t");
+			for (n = m; n != NULL; n = n->m_next)
+				printf("%p ", n);
+			printf("\n");
+		}
+		panic("sblastmbufchk from %s", where);
+	}
+}
+#endif /* SOCKBUF_DEBUG */
+
+#define SBLINKRECORD(sb, m0)						\
+	do {								\
+		if ((sb)->sb_lastrecord != NULL)			\
+			(sb)->sb_lastrecord->m_nextpkt = (m0);		\
+		else							\
+			(sb)->sb_mb = (m0);				\
+		(sb)->sb_lastrecord = (m0);				\
+	} while (0)
+
 /*
  * Append mbuf chain m to the last record in the
  * socket buffer sb.  The additional space associated
@@ -483,9 +538,12 @@ sbappend(sb, m)
 	if (m == 0)
 		return;
 	n = sb->sb_mb;
-	if (n) {
-		while (n->m_nextpkt)
-			n = n->m_nextpkt;
+	if ((n = sb->sb_lastrecord) != NULL) {
+		/*
+		 * XXX Would like to simply use sb_mbtail here, but
+		 * XXX I need to verify that I won't miss an EOR that
+		 * XXX way.
+		 */
 		do {
 			if (n->m_flags & M_EOR) {
 				sbappendrecord(sb, m); /* XXXXXX!!!! */
@@ -493,7 +551,34 @@ sbappend(sb, m)
 			}
 		} while (n->m_next && (n = n->m_next));
 	}
+	else {
+		/*
+		 * If this is the first record in the socket buffer, it's also
+		 * the last record.
+		 */
+		sb->sb_lastrecord = m;
+	}
 	sbcompress(sb, m, n);
+	SBLASTRECORDCHK(sb, "sbappend 2");
+}
+
+/*
+ * This version of sbappend() should only be used when the caller
+ * absolutely knows that there will never be more than one record
+ * in the socket buffer, that is, a stream protocol (such as TCP).
+ */
+void
+sbappend_stream(struct sockbuf *sb, struct mbuf *m)
+{
+	KKASSERT(m->m_nextpkt == NULL);
+	KKASSERT(sb->sb_mb == sb->sb_lastrecord);
+	
+	SBLASTMBUFCHK(sb, __func__);
+	
+	sbcompress(sb, m, sb->sb_mbtail);
+	
+	sb->sb_lastrecord = sb->sb_mb;
+	SBLASTRECORDCHK(sb, __func__);
 }
 
 #ifdef SOCKBUF_DEBUG
@@ -506,13 +591,13 @@ sbcheck(sb)
 	u_long len = 0, mbcnt = 0;
 
 	for (m = sb->sb_mb; m; m = n) {
-	    n = m->m_nextpkt;
-	    for (; m; m = m->m_next) {
-		len += m->m_len;
-		mbcnt += MSIZE;
-		if (m->m_flags & M_EXT) /*XXX*/ /* pretty sure this is bogus */
-			mbcnt += m->m_ext.ext_size;
-	    }
+		n = m->m_nextpkt;
+		for (; m; m = m->m_next) {
+			len += m->m_len;
+			mbcnt += MSIZE;
+			if (m->m_flags & M_EXT) /*XXX*/ /* pretty sure this is bogus */
+				mbcnt += m->m_ext.ext_size;
+		}
 	}
 	if (len != sb->sb_cc || mbcnt != sb->sb_mbcnt) {
 		printf("cc %ld != %ld || mbcnt %ld != %ld\n", len, sb->sb_cc,
@@ -535,19 +620,14 @@ sbappendrecord(sb, m0)
 
 	if (m0 == 0)
 		return;
-	m = sb->sb_mb;
-	if (m)
-		while (m->m_nextpkt)
-			m = m->m_nextpkt;
+
 	/*
 	 * Put the first mbuf on the queue.
 	 * Note this permits zero length records.
 	 */
 	sballoc(sb, m0);
-	if (m)
-		m->m_nextpkt = m0;
-	else
-		sb->sb_mb = m0;
+	SBLASTRECORDCHK(sb, "sbappendrecord 1");
+	SBLINKRECORD(sb, m0);
 	m = m0->m_next;
 	m0->m_next = 0;
 	if (m && (m0->m_flags & M_EOR)) {
@@ -555,6 +635,7 @@ sbappendrecord(sb, m0)
 		m->m_flags |= M_EOR;
 	}
 	sbcompress(sb, m, m0);
+	SBLASTRECORDCHK(sb, "sbappendrecord 2");
 }
 
 /*
@@ -572,6 +653,9 @@ sbinsertoob(sb, m0)
 
 	if (m0 == 0)
 		return;
+
+	SBLASTRECORDCHK(sb, "sbinsertoob 1");
+
 	for (mp = &sb->sb_mb; *mp ; mp = &((*mp)->m_nextpkt)) {
 	    m = *mp;
 	    again:
@@ -593,6 +677,10 @@ sbinsertoob(sb, m0)
 	 */
 	sballoc(sb, m0);
 	m0->m_nextpkt = *mp;
+	if (*mp == NULL) {
+		/* m0 is actually the new tail */
+		sb->sb_lastrecord = m0;
+	}
 	*mp = m0;
 	m = m0->m_next;
 	m0->m_next = 0;
@@ -601,6 +689,7 @@ sbinsertoob(sb, m0)
 		m->m_flags |= M_EOR;
 	}
 	sbcompress(sb, m, m0);
+	SBLASTRECORDCHK(sb, "sbinsertoob 2");
 }
 
 /*
@@ -615,7 +704,7 @@ sbappendaddr(sb, asa, m0, control)
 	struct sockaddr *asa;
 	struct mbuf *m0, *control;
 {
-	struct mbuf *m, *n;
+	struct mbuf *m, *n, *nlast;
 	int space = asa->sa_len;
 
 	if (m0 && (m0->m_flags & M_PKTHDR) == 0)
@@ -642,15 +731,18 @@ sbappendaddr(sb, asa, m0, control)
 	else
 		control = m0;
 	m->m_next = control;
-	for (n = m; n; n = n->m_next)
+	SBLASTRECORDCHK(sb, "sbappendaddr 1");
+	for (n = m; n->m_next != NULL; n = n->m_next)
 		sballoc(sb, n);
-	n = sb->sb_mb;
-	if (n) {
-		while (n->m_nextpkt)
-			n = n->m_nextpkt;
-		n->m_nextpkt = m;
-	} else
-		sb->sb_mb = m;
+	sballoc(sb, n);
+	nlast = n;
+	SBLINKRECORD(sb, m);
+
+	sb->sb_mbtail = nlast;
+	SBLASTMBUFCHK(sb, "sbappendaddr");
+
+	SBLASTRECORDCHK(sb, "sbappendaddr 2");
+
 	return (1);
 }
 
@@ -659,7 +751,7 @@ sbappendcontrol(sb, m0, control)
 	struct sockbuf *sb;
 	struct mbuf *control, *m0;
 {
-	struct mbuf *m, *n;
+	struct mbuf *m, *n, *mlast;
 	int space = 0;
 
 	if (control == 0)
@@ -675,15 +767,20 @@ sbappendcontrol(sb, m0, control)
 	if (space > sbspace(sb))
 		return (0);
 	n->m_next = m0;			/* concatenate data to control */
-	for (m = control; m; m = m->m_next)
+
+	SBLASTRECORDCHK(sb, "sbappendcontrol 1");
+
+	for (m = control; m->m_next != NULL; m = m->m_next)
 		sballoc(sb, m);
-	n = sb->sb_mb;
-	if (n) {
-		while (n->m_nextpkt)
-			n = n->m_nextpkt;
-		n->m_nextpkt = control;
-	} else
-		sb->sb_mb = control;
+	sballoc(sb, m);
+	mlast = m;
+	SBLINKRECORD(sb, control);
+
+	sb->sb_mbtail = mlast;
+	SBLASTMBUFCHK(sb, "sbappendcontrol");
+
+	SBLASTRECORDCHK(sb, "sbappendcontrol 2");
+
 	return (1);
 }
 
@@ -725,6 +822,7 @@ sbcompress(sb, m, n)
 			n->m_next = m;
 		else
 			sb->sb_mb = m;
+		sb->sb_mbtail = m;
 		sballoc(sb, m);
 		n = m;
 		m->m_flags &= ~M_EOR;
@@ -737,6 +835,7 @@ sbcompress(sb, m, n)
 		else
 			printf("semi-panic: sbcompress");
 	}
+	SBLASTMBUFCHK(sb, __func__);
 }
 
 /*
@@ -747,9 +846,7 @@ void
 sbflush(sb)
 	struct sockbuf *sb;
 {
-
-	if (sb->sb_flags & SB_LOCK)
-		panic("sbflush: locked");
+	KKASSERT((sb->sb_flags & SB_LOCK) == 0);
 	while (sb->sb_mbcnt) {
 		/*
 		 * Don't call sbdrop(sb, 0) if the leading mbuf is non-empty:
@@ -759,8 +856,11 @@ sbflush(sb)
 			break;
 		sbdrop(sb, (int)sb->sb_cc);
 	}
-	if (sb->sb_cc || sb->sb_mb || sb->sb_mbcnt)
-		panic("sbflush: cc %ld || mb %p || mbcnt %ld", sb->sb_cc, (void *)sb->sb_mb, sb->sb_mbcnt);
+	KKASSERT(sb->sb_cc == 0);
+	KKASSERT(sb->sb_mb == NULL);
+	KKASSERT(sb->sb_mbcnt == NULL);
+	KKASSERT(sb->sb_mbtail == NULL);
+	KKASSERT(sb->sb_lastrecord == NULL);
 }
 
 /*
@@ -802,6 +902,17 @@ sbdrop(sb, len)
 		m->m_nextpkt = next;
 	} else
 		sb->sb_mb = next;
+		/*
+		 * First part is an inline SB_UPDATE_TAIL().  Second part
+		 * makes sure sb_lastrecord is up-to-date if we dropped
+		 * part of the last record.
+		 */
+		m = sb->sb_mb;
+		if (m == NULL) {
+			sb->sb_mbtail = NULL;
+			sb->sb_lastrecord = NULL;
+		} else if (m->m_nextpkt == NULL)
+			sb->sb_lastrecord = m;
 }
 
 /*
@@ -822,6 +933,7 @@ sbdroprecord(sb)
 			m = m_free(m);
 		} while (m);
 	}
+	SB_UPDATE_TAIL(sb);
 }
 
 /*
Index: netinet/tcp_input.c
===================================================================
RCS file: /home/dcvs/src/sys/netinet/tcp_input.c,v
retrieving revision 1.29
diff -u -p -r1.29 tcp_input.c
--- netinet/tcp_input.c	6 Jun 2004 05:38:58 -0000	1.29
+++ netinet/tcp_input.c	26 Jun 2004 17:35:14 -0000
@@ -330,7 +330,7 @@ present:
 		if (so->so_state & SS_CANTRCVMORE)
 			m_freem(q->tqe_m);
 		else
-			sbappend(&so->so_rcv, q->tqe_m);
+			sbappend_stream(&so->so_rcv, q->tqe_m);
 		free(q, M_TSEGQ);
 		tcp_reass_qsize--;
 		q = nq;
@@ -1102,7 +1102,7 @@ after_listen:
 				m_freem(m);
 			} else {
 				m_adj(m, drop_hdrlen);	/* delayed header drop */
-				sbappend(&so->so_rcv, m);
+				sbappend_stream(&so->so_rcv, m);
 			}
 			sorwakeup(so);
 			if (DELAY_ACK(tp)) {
@@ -2199,7 +2199,7 @@ dodata:							/* XXX */
 			if (so->so_state & SS_CANTRCVMORE)
 				m_freem(m);
 			else
-				sbappend(&so->so_rcv, m);
+				sbappend_stream(&so->so_rcv, m);
 			sorwakeup(so);
 		} else {
 			thflags = tcp_reass(tp, th, &tlen, m);
Index: netinet/tcp_usrreq.c
===================================================================
RCS file: /home/dcvs/src/sys/netinet/tcp_usrreq.c,v
retrieving revision 1.23
diff -u -p -r1.23 tcp_usrreq.c
--- netinet/tcp_usrreq.c	20 May 2004 04:32:59 -0000	1.23
+++ netinet/tcp_usrreq.c	26 Jun 2004 17:35:14 -0000
@@ -601,7 +601,7 @@ tcp_usr_send(struct socket *so, int flag
 		m_freem(control);	/* empty control, just free it */
 	}
 	if(!(flags & PRUS_OOB)) {
-		sbappend(&so->so_snd, m);
+		sbappend_stream(&so->so_snd, m);
 		if (nam && tp->t_state < TCPS_SYN_SENT) {
 			/*
 			 * Do implied connect if not yet connected,
@@ -650,7 +650,7 @@ tcp_usr_send(struct socket *so, int flag
 		 * of data past the urgent section.
 		 * Otherwise, snd_up should be one lower.
 		 */
-		sbappend(&so->so_snd, m);
+		sbappend_stream(&so->so_snd, m);
 		if (nam && tp->t_state < TCPS_SYN_SENT) {
 			/*
 			 * Do implied connect if not yet connected,
Index: sys/socketvar.h
===================================================================
RCS file: /home/dcvs/src/sys/sys/socketvar.h,v
retrieving revision 1.13
diff -u -p -r1.13 socketvar.h
--- sys/socketvar.h	6 Jun 2004 19:16:10 -0000	1.13
+++ sys/socketvar.h	26 Jun 2004 17:35:14 -0000
@@ -93,6 +93,9 @@ struct socket {
 		u_long	sb_mbmax;	/* max chars of mbufs to use */
 		long	sb_lowat;	/* low water mark */
 		struct	mbuf *sb_mb;	/* the mbuf chain */
+		struct	mbuf *sb_mbtail; /* the tail of the mbuf chain */
+		struct	mbuf *sb_lastrecord; /* first mbuf of last record in
+					     socket buffer */
 		struct	selinfo sb_sel;	/* process selecting read/write */
 		short	sb_flags;	/* flags, see below */
 		short	sb_timeo;	/* timeout for read/write */
@@ -122,6 +125,14 @@ struct socket {
 	} *so_accf;
 };
 
+#define SB_UPDATE_TAIL(sb)						\
+	do {								\
+		if ((sb)->sb_mb == NULL) {				\
+			(sb)->sb_mbtail = NULL;				\
+			(sb)->sb_lastrecord = NULL;			\
+		}							\
+	} while (0)
+
 /*
  * Socket state bits.
  */
@@ -327,6 +338,7 @@ struct	sockaddr *dup_sockaddr (struct so
 int	holdsock (struct filedesc *fdp, int fdes, struct file **fpp);
 int	getsockaddr (struct sockaddr **namp, caddr_t uaddr, size_t len);
 void	sbappend (struct sockbuf *sb, struct mbuf *m);
+void	sbappend_stream (struct sockbuf *sb, struct mbuf *m);
 int	sbappendaddr (struct sockbuf *sb, struct sockaddr *asa,
 	    struct mbuf *m0, struct mbuf *control);
 int	sbappendcontrol (struct sockbuf *sb, struct mbuf *m0,
@@ -405,6 +417,16 @@ int accept_filt_generic_mod_event (modul
 SYSCTL_DECL(_net_inet_accf);
 #endif /* ACCEPT_FILTER_MOD */
 
+#ifdef SOCKBUF_DEBUG
+void	sblastrecordchk(struct sockbuf *, const char *);
+#define	SBLASTRECORDCHK(sb, where)	sblastrecordchk((sb), (where))
+void	sblastmbufchk(struct sockbuf *, const char *);
+#define	SBLASTMBUFCHK(sb, where)	sblastmbufchk((sb), (where))
+#else
+#define	SBLASTRECORDCHK(sb, where)	/* nothing */
+#define	SBLASTMBUFCHK(sb, where)	/* nothing */
+#endif /* SOCKBUF_DEBUG */
+
 #endif /* _KERNEL */
 
 #endif /* !_SYS_SOCKETVAR_H_ */
