? tcpminmss-4stable-20040107.diff
Index: tcp.h
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp.h,v
retrieving revision 1.13.2.3
diff -u -p -r1.13.2.3 tcp.h
--- tcp.h	1 Mar 2001 22:08:42 -0000	1.13.2.3
+++ tcp.h	7 Jan 2004 22:01:08 -0000
@@ -101,11 +101,29 @@ struct tcphdr {
 
 /*
  * Default maximum segment size for TCP.
- * With an IP MSS of 576, this is 536,
+ * With an IP MTU of 576, this is 536,
  * but 512 is probably more convenient.
  * This should be defined as MIN(512, IP_MSS - sizeof (struct tcpiphdr)).
  */
 #define	TCP_MSS	512
+/*
+ * TCP_MINMSS is defined to be 256 which is fine for the smallest
+ * link MTU (296 bytes, SLIP interface) in the Internet.
+ * However it is very unlikely to come across such low MTU interfaces
+ * these days (anno dato 2003).
+ * Probably it can be set to 512 without ill effects. But we play safe.
+ * See tcp_subr.c tcp_minmss SYSCTL declaration for more comments.
+ * Setting this to "0" disables the minmss check.
+ */
+#define	TCP_MINMSS 256
+/*
+ * TCP_MINMSSOVERLOAD is defined to be 1000 which should cover any type
+ * of interactive TCP session.
+ * See tcp_subr.c tcp_minmssoverload SYSCTL declaration and tcp_input.c
+ * for more comments.
+ * Setting this to "0" disables the minmssoverload check.
+ */
+#define	TCP_MINMSSOVERLOAD 1000
 
 /*
  * Default maximum segment size for TCP6.
Index: tcp_input.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_input.c,v
retrieving revision 1.107.2.38
diff -u -p -r1.107.2.38 tcp_input.c
--- tcp_input.c	21 May 2003 04:46:41 -0000	1.107.2.38
+++ tcp_input.c	7 Jan 2004 22:01:08 -0000
@@ -867,6 +867,61 @@ after_listen:
 		panic("tcp_input: TCPS_LISTEN");
 
 	/*
+	 * This is the second part of the MSS DoS prevention code (after
+	 * minmss on the sending side) and it deals with too many too small
+	 * tcp packets in a too short timeframe (1 second).
+	 *
+	 * For every full second we count the number of received packets
+	 * and bytes. If we get a lot of packets per second for this connection
+	 * (tcp_minmssoverload) we take a closer look at it and compute the
+	 * average packet size for the past second. If that is less than
+	 * tcp_minmss we get too many packets with very small payload which
+	 * is not good and burdens our system (and every packet generates
+	 * a wakeup to the process connected to our socket). We can reasonable
+	 * expect this to be small packet DoS attack to exhaust our CPU
+	 * cycles.
+	 *
+	 * Care has to be taken for the minimum packet overload value. This
+	 * value defines the minimum number of packets per second before we
+	 * start to worry. This must not be too low to avoid killing for
+	 * example interactive connections with many small packets like
+	 * telnet or SSH.
+	 *
+	 * Setting either tcp_minmssoverload or tcp_minmss to "0" disables
+	 * this check.
+	 *
+	 * Account for packet if payload packet, skip over ACK, etc.
+	 */
+	if (tcp_minmss && tcp_minmssoverload &&
+	    tp->t_state == TCPS_ESTABLISHED && tlen > 0) {
+		if (tp->rcv_second > ticks) {
+			tp->rcv_pps++;
+			tp->rcv_byps += tlen + off;
+			if (tp->rcv_pps > tcp_minmssoverload) {
+				if ((tp->rcv_byps / tp->rcv_pps) < tcp_minmss) {
+					printf("too many small tcp packets from "
+					       "%s:%u, av. %lubyte/packet, "
+					       "dropping connection\n",
+#ifdef INET6
+						isipv6 ?
+						ip6_sprintf(&inp->inp_inc.inc6_faddr) :
+#endif
+						inet_ntoa(inp->inp_inc.inc_faddr),
+						inp->inp_inc.inc_fport,
+						tp->rcv_byps / tp->rcv_pps);
+					tp = tcp_drop(tp, ECONNRESET);
+					tcpstat.tcps_minmssdrops++;
+					goto drop;
+				}
+			}
+		} else {
+			tp->rcv_second = ticks + hz;
+			tp->rcv_pps = 1;
+			tp->rcv_byps = tlen + off;
+		}
+	}
+
+	/*
 	 * Segment received on connection.
 	 * Reset idle time and keep-alive timer.
 	 */
@@ -2539,8 +2594,11 @@ tcp_mss(tp, offer)
 		 * enough to allow some data on segments even is the
 		 * all the option space is used (40bytes).  Otherwise
 		 * funny things may happen in tcp_output.
+		 *
+		 * Prevent DoS attack with too small MSS. Round up
+		 * to at least minmss.
 		 */
-		offer = max(offer, 64);
+		offer = max(offer, max(64, tcp_minmss));
 	taop->tao_mssopt = offer;
 
 	/*
Index: tcp_subr.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_subr.c,v
retrieving revision 1.73.2.31
diff -u -p -r1.73.2.31 tcp_subr.c
--- tcp_subr.c	24 Jan 2003 05:11:34 -0000	1.73.2.31
+++ tcp_subr.c	7 Jan 2004 22:01:08 -0000
@@ -119,6 +119,30 @@ SYSCTL_INT(_net_inet_tcp, TCPCTL_V6MSSDF
 	"Default TCP Maximum Segment Size for IPv6");
 #endif
 
+/*
+ * Minimum MSS we accept and use. This prevents DoS attacks where
+ * we are forced to a ridiculous low MSS like 20 and send hundreds
+ * of packets instead of one. The effect scales with the available
+ * bandwidth and quickly saturates the CPU and network interface
+ * with packet generation and sending. Set to zero to disable MINMSS
+ * checking. This setting prevents us from sending too small packets.
+ */
+int	tcp_minmss = TCP_MINMSS;
+SYSCTL_INT(_net_inet_tcp, OID_AUTO, minmss, CTLFLAG_RW,
+    &tcp_minmss , 0, "Minmum TCP Maximum Segment Size");
+/*
+ * Number of TCP segments per second we accept from remote host
+ * before we start to calculate average segment size. If average
+ * segment size drops below the minimum TCP MSS we assume a DoS
+ * attack and reset+drop the connection. Care has to be taken not to
+ * set this value too small to not kill interactive type connections
+ * (telnet, SSH) which send many small packets.
+ */
+int     tcp_minmssoverload = TCP_MINMSSOVERLOAD;
+SYSCTL_INT(_net_inet_tcp, OID_AUTO, minmssoverload, CTLFLAG_RW,
+    &tcp_minmssoverload , 0, "Number of TCP Segments per Second allowed to"
+    "be under the MINMSS Size");
+
 #if 0
 static int 	tcp_rttdflt = TCPTV_SRTTDFLT / PR_SLOWHZ;
 SYSCTL_INT(_net_inet_tcp, TCPCTL_RTTDFLT, rttdflt, CTLFLAG_RW, 
Index: tcp_usrreq.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_usrreq.c,v
retrieving revision 1.51.2.17
diff -u -p -r1.51.2.17 tcp_usrreq.c
--- tcp_usrreq.c	11 Oct 2002 11:46:44 -0000	1.51.2.17
+++ tcp_usrreq.c	7 Jan 2004 22:01:08 -0000
@@ -952,7 +952,8 @@ tcp_ctloutput(so, sopt)
 			if (error)
 				break;
 
-			if (optval > 0 && optval <= tp->t_maxseg)
+			if (optval > 0 && optval <= tp->t_maxseg &&
+			    optval + 40 >= tcp_minmss)
 				tp->t_maxseg = optval;
 			else
 				error = EINVAL;
Index: tcp_var.h
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_var.h,v
retrieving revision 1.56.2.13
diff -u -p -r1.56.2.13 tcp_var.h
--- tcp_var.h	3 Feb 2003 02:34:07 -0000	1.56.2.13
+++ tcp_var.h	7 Jan 2004 22:01:08 -0000
@@ -176,6 +176,10 @@ struct tcpcb {
 	u_long	snd_ssthresh_prev;	/* ssthresh prior to retransmit */
 	tcp_seq snd_high_prev;		/* snd_high prior to retransmit */
 	u_long	t_badrxtwin;		/* window for retransmit recovery */
+/* anti DoS counters */
+	u_long	rcv_second;		/* start of interval second */
+	u_long	rcv_pps;		/* received packets per second */
+	u_long	rcv_byps;		/* received bytes per second */
 };
 
 /*
@@ -301,6 +305,7 @@ struct	tcpstat {
 	u_long	tcps_connects;		/* connections established */
 	u_long	tcps_drops;		/* connections dropped */
 	u_long	tcps_conndrops;		/* embryonic connections dropped */
+	u_long	tcps_minmssdrops;	/* average minmss too low drops */
 	u_long	tcps_closed;		/* conn. closed (includes drops) */
 	u_long	tcps_segstimed;		/* segs where we tried to get rtt */
 	u_long	tcps_rttupdated;	/* times we succeeded */
@@ -437,6 +442,8 @@ extern	struct inpcbhead tcb;		/* head of
 extern	struct inpcbinfo tcbinfo;
 extern	struct tcpstat tcpstat;	/* tcp statistics */
 extern	int tcp_mssdflt;	/* XXX */
+extern	int tcp_minmss;
+extern	int tcp_minmssoverload;
 extern	int tcp_delack_enabled;
 extern	int tcp_do_newreno;
 extern	int path_mtu_discovery;
