tech-net archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

TCP timestamp starting value (wa: A strange TCP timestamp problem?)



With TCP timestamps enabled, NetBSD counts them (at 2Hz) starting from 1 for 
each connection individually. While this behaviour is in perfect accordance 
with the RFC, existing peers (in our case, some IBM load balancing software) 
seems to get upset either by repeatedly seing value 1 from the same IP address 
(perhaps regarding this as some form of attack), or by seing decreasing 
timestamps from one IP address. In our case, the peer seems, after some grace 
period, to discard SYN packets resulting in the three-way-handshake to take 
6 seconds.
The starting value of 1 was chosen (over some form or uptime as other OSes do) 
in order not to leak any information about the system's uptime. The same can 
be aceived by using something proportional to real time.
The attached patch implements that (with an arbitrary offset to prevent 
near-time 32-bit-overflow). The same could be achieved, of course, by simply 
sampling real time at TCP stack initialization; however, the suggested patch 
is less intrusive and would allow for run-time tweaking.

The patch made our problem disappear.

Any objections or suggestions?
Index: sys/netinet/tcp_input.c
===================================================================
RCS file: /cvsroot/src/sys/netinet/tcp_input.c,v
retrieving revision 1.321.2.1
diff -u -p -r1.321.2.1 tcp_input.c
--- sys/netinet/tcp_input.c	24 Jul 2015 07:40:17 -0000	1.321.2.1
+++ sys/netinet/tcp_input.c	20 Jul 2016 16:33:01 -0000
@@ -4344,6 +4344,8 @@ syn_cache_add(struct sockaddr *src, stru
 	struct mbuf *ipopts;
 	struct tcp_opt_info opti;
 	int s;
+	struct timeval tv; /* for sc_timebase */
+	u_int32_t timebase;
 
 	tp = sototcpcb(so);
 
@@ -4469,7 +4471,11 @@ syn_cache_add(struct sockaddr *src, stru
 						m->m_pkthdr.rcvif : NULL,
 						sc->sc_src.sa.sa_family);
 	sc->sc_win = win;
-	sc->sc_timebase = tcp_now - 1;	/* see tcp_newtcpcb() */
+	/* see tcp_newtcpcb() */
+	getmicrotime(&tv);
+	timebase = (tv.tv_sec - TIMESTAMP_EPOCH) * 2;
+	if (tv.tv_usec >= 500000) timebase++;
+	sc->sc_timebase = tcp_now - timebase;
 	sc->sc_timestamp = tb.ts_recent;
 	if ((tb.t_flags & (TF_REQ_TSTMP|TF_RCVD_TSTMP)) ==
 	    (TF_REQ_TSTMP|TF_RCVD_TSTMP))
Index: sys/netinet/tcp_subr.c
===================================================================
RCS file: /cvsroot/src/sys/netinet/tcp_subr.c,v
retrieving revision 1.246.2.1
diff -u -p -r1.246.2.1 tcp_subr.c
--- sys/netinet/tcp_subr.c	31 Oct 2012 17:30:20 -0000	1.246.2.1
+++ sys/netinet/tcp_subr.c	20 Jul 2016 16:33:02 -0000
@@ -112,6 +112,7 @@ __KERNEL_RCSID(0, "$NetBSD: tcp_subr.c,v
 #include <sys/pool.h>
 #include <sys/md5.h>
 #include <sys/cprng.h>
+#include <sys/time.h>
 
 #include <net/route.h>
 #include <net/if.h>
@@ -1011,6 +1012,8 @@ tcp_newtcpcb(int family, void *aux)
 #endif
 	struct tcpcb *tp;
 	int i;
+	struct timeval tv; /* for ts_timebase */
+	u_int32_t timebase;
 
 	/* XXX Consider using a pool_cache for speed. */
 	tp = pool_get(&tcpcb_pool, PR_NOWAIT);	/* splsoftnet via tcp_usrreq */
@@ -1069,16 +1072,22 @@ tcp_newtcpcb(int family, void *aux)
 
 	/*
 	 * Initialize our timebase.  When we send timestamps, we take
-	 * the delta from tcp_now -- this means each connection always
-	 * gets a timebase of 1, which makes it, among other things,
+	 * the delta from tcp_now -- initialize this so that said delta
+	 * will give half-seconds since TIMESTAMP_EPOCH in order to make it
 	 * more difficult to determine how long a system has been up,
-	 * and thus how many TCP sequence increments have occurred.
-	 *
-	 * We start with 1, because 0 doesn't work with linux, which
-	 * considers timestamp 0 in a SYN packet as a bug and disables
-	 * timestamps.
+	 * and thus how many TCP sequence increments have occurred while
+	 * on the other hand ensure increasing timestamps for the whole
+	 * system, not only this connection. This is not mandated by the
+	 * RFC, but existing peers are known to discard packets otherwise.
+	 */
+	getmicrotime(&tv);
+	timebase = (tv.tv_sec - TIMESTAMP_EPOCH) * 2;
+	if (tv.tv_usec >= 500000) timebase++;
+	/*
+	 * this will underflow, but subtracting it from an incremented tcp_now
+	 * is guaranteed to DTRT.
 	 */
-	tp->ts_timebase = tcp_now - 1;
+	tp->ts_timebase = tcp_now - timebase;
 	
 	tcp_congctl_select(tp, tcp_congctl_global_name);
 
Index: sys/netinet/tcp_var.h
===================================================================
RCS file: /cvsroot/src/sys/netinet/tcp_var.h,v
retrieving revision 1.169
diff -u -p -r1.169 tcp_var.h
--- sys/netinet/tcp_var.h	2 Feb 2012 19:43:08 -0000	1.169
+++ sys/netinet/tcp_var.h	20 Jul 2016 16:33:02 -0000
@@ -307,6 +307,7 @@ struct tcpcb {
 	u_char	requested_s_scale;
 	u_int32_t ts_recent;		/* timestamp echo data */
 	u_int32_t ts_recent_age;	/* when last updated */
+#define TIMESTAMP_EPOCH 1469021566 /* UTC time this line was added -- to postpone overflows */
 	u_int32_t ts_timebase;		/* our timebase */
 	tcp_seq	last_ack_sent;
 


Home | Main Index | Thread Index | Old Index