changeset 4925:74b140568feb draft

DHCP6: Calulate ReTransmission using milliseconds This is the exact formula in RFC8415 Section 15, the prior one was not so exact. This makes the code a lot simpler and removes the need for complicated timespec handling.
author Roy Marples <roy@marples.name>
date Tue, 07 Jan 2020 22:15:09 +0000
parents 7c1a365b1e2d
children 5aae8c49e9f3
files src/common.h src/dhcp6.c src/dhcp6.h src/eloop.c src/eloop.h
diffstat 5 files changed, 46 insertions(+), 74 deletions(-) [+]
line wrap: on
line diff
--- a/src/common.h	Tue Jan 07 14:15:14 2020 +0000
+++ b/src/common.h	Tue Jan 07 22:15:09 2020 +0000
@@ -55,7 +55,7 @@
 #define USEC_PER_NSEC		1000L
 #define NSEC_PER_SEC		1000000000L
 #define NSEC_PER_MSEC		1000000L
-#define MSEC_PER_SEC		1000L
+#define MSEC_PER_SEC		1000
 #define CSEC_PER_SEC		100L
 #define NSEC_PER_CSEC		10000000L
 
--- a/src/dhcp6.c	Tue Jan 07 14:15:14 2020 +0000
+++ b/src/dhcp6.c	Tue Jan 07 22:15:09 2020 +0000
@@ -1162,10 +1162,7 @@
 	    .sin6_family = AF_INET6,
 	    .sin6_port = htons(DHCP6_SERVER_PORT),
 	};
-	struct timespec RTprev;
-	double rnd;
-	time_t ms;
-	uint8_t neg;
+	unsigned int RT;
 	const char *broad_uni;
 	const struct in6_addr alldhcp = IN6ADDR_LINKLOCAL_ALLDHCP_INIT;
 	struct ipv6_addr *lla;
@@ -1214,60 +1211,33 @@
 		    !(ifp->options->options & DHCPCD_INITIAL_DELAY))
 			state->IMD = 0;
 		if (state->IMD) {
+			state->RT = state->IMD * MSEC_PER_SEC;
 			/* Some buggy PPP servers close the link too early
 			 * after sending an invalid status in their reply
 			 * which means this host won't see it.
 			 * 1 second grace seems to be the sweet spot. */
 			if (ifp->flags & IFF_POINTOPOINT)
-				state->RT.tv_sec = 1;
-			else
-				state->RT.tv_sec = 0;
-			state->RT.tv_nsec = (suseconds_t)arc4random_uniform(
-			    (uint32_t)(state->IMD * NSEC_PER_SEC));
-			timespecnorm(&state->RT);
+				state->RT += MSEC_PER_SEC;
 			broad_uni = "delaying";
-			goto logsend;
-		}
-		if (state->RTC == 0) {
-			RTprev.tv_sec = state->IRT;
-			RTprev.tv_nsec = 0;
-			state->RT.tv_sec = RTprev.tv_sec;
-			state->RT.tv_nsec = 0;
-		} else {
-			RTprev = state->RT;
-			timespecadd(&state->RT, &state->RT, &state->RT);
+		} else if (state->RTC == 0)
+			state->RT = state->IRT * MSEC_PER_SEC;
+
+		if (state->MRT != 0) {
+			unsigned int mrt = state->MRT * MSEC_PER_SEC;
+
+			if (state->RT > mrt)
+				state->RT = mrt;
 		}
 
-		rnd = DHCP6_RAND_MIN;
-		rnd += (suseconds_t)arc4random_uniform(
-		    DHCP6_RAND_MAX - DHCP6_RAND_MIN);
-		rnd /= MSEC_PER_SEC;
-		neg = (rnd < 0.0);
-		if (neg)
-			rnd = -rnd;
-		ts_to_ms(ms, &RTprev);
-		ms = (time_t)((double)ms * rnd);
-		ms_to_ts(&RTprev, ms);
-		if (neg)
-			timespecsub(&state->RT, &RTprev, &state->RT);
-		else
-			timespecadd(&state->RT, &RTprev, &state->RT);
-
-		if (state->MRT != 0 && state->RT.tv_sec > state->MRT) {
-			RTprev.tv_sec = state->MRT;
-			RTprev.tv_nsec = 0;
-			state->RT.tv_sec = state->MRT;
-			state->RT.tv_nsec = 0;
-			ts_to_ms(ms, &RTprev);
-			ms = (time_t)((double)ms * rnd);
-			ms_to_ts(&RTprev, ms);
-			if (neg)
-				timespecsub(&state->RT, &RTprev, &state->RT);
-			else
-				timespecadd(&state->RT, &RTprev, &state->RT);
-		}
-
-logsend:
+		/* Add -.1 to .1 * RT randomness as per RFC8415 section 15 */
+		uint32_t lru = arc4random_uniform(
+		    state->RTC == 0 ? DHCP6_RAND_MAX
+		    : DHCP6_RAND_MAX - DHCP6_RAND_MIN);
+		int lr = (int)lru - (state->RTC == 0 ? 0 : DHCP6_RAND_MAX);
+		RT = state->RT
+		    + (unsigned int)((float)state->RT
+		    * ((float)lr / DHCP6_RAND_DIV));
+
 		if (ifp->carrier > LINK_DOWN)
 			logdebugx("%s: %s %s (xid 0x%02x%02x%02x),"
 			    " next in %0.1f seconds",
@@ -1277,17 +1247,12 @@
 			    state->send->xid[0],
 			    state->send->xid[1],
 			    state->send->xid[2],
-			    timespec_to_double(&state->RT));
-
-		/* This sometimes happens when we delegate to this interface
-		 * AND run DHCPv6 on it normally. */
-		assert(timespec_to_double(&state->RT) != 0);
+			    (float)RT / MSEC_PER_SEC);
 
 		/* Wait the initial delay */
 		if (state->IMD != 0) {
 			state->IMD = 0;
-			eloop_timeout_add_tv(ctx->eloop,
-			    &state->RT, callback, ifp);
+			eloop_timeout_add_msec(ctx->eloop, RT, callback, ifp);
 			return 0;
 		}
 	}
@@ -1376,14 +1341,17 @@
 #ifdef PRIVSEP
 sent:
 #endif
+	state->RT = RT * 2;
+	if (state->RT < RT) /* Check overflow */
+		state->RT = RT;
 	state->RTC++;
 	if (callback) {
 		if (state->MRC == 0 || state->RTC < state->MRC)
-			eloop_timeout_add_tv(ctx->eloop,
-			    &state->RT, callback, ifp);
+			eloop_timeout_add_msec(ctx->eloop,
+			    RT, callback, ifp);
 		else if (state->MRC != 0 && state->MRCcallback)
-			eloop_timeout_add_tv(ctx->eloop,
-			    &state->RT, state->MRCcallback, ifp);
+			eloop_timeout_add_msec(ctx->eloop,
+			    RT, state->MRCcallback, ifp);
 		else
 			logwarnx("%s: sent %d times with no reply",
 			    ifp->name, state->RTC);
--- a/src/dhcp6.h	Tue Jan 07 14:15:14 2020 +0000
+++ b/src/dhcp6.h	Tue Jan 07 22:15:09 2020 +0000
@@ -151,8 +151,10 @@
 #define IRT_DEFAULT		86400
 #define IRT_MINIMUM		600
 
-#define DHCP6_RAND_MIN		-100
-#define DHCP6_RAND_MAX		100
+/* These should give -.1 to .1 randomness */
+#define	DHCP6_RAND_MIN		-100
+#define	DHCP6_RAND_MAX		100
+#define	DHCP6_RAND_DIV		1000.0f
 
 enum DH6S {
 	DH6S_INIT,
@@ -177,16 +179,18 @@
 	enum DH6S state;
 	struct timespec started;
 
-	/* Message retransmission timings */
-	struct timespec RT;
+	/* Message retransmission timings in seconds */
 	unsigned int IMD;
 	unsigned int RTC;
-	time_t IRT;
+	unsigned int IRT;
 	unsigned int MRC;
-	time_t MRT;
+	unsigned int MRT;
 	void (*MRCcallback)(void *);
-	time_t sol_max_rt;
-	time_t inf_max_rt;
+	unsigned int sol_max_rt;
+	unsigned int inf_max_rt;
+	unsigned int RT;	/* retransmission timer in milliseconds
+				 * maximal RT is 1 day + RAND,
+				 * so should be enough */
 
 	struct dhcp6_message *send;
 	size_t send_len;
--- a/src/eloop.c	Tue Jan 07 14:15:14 2020 +0000
+++ b/src/eloop.c	Tue Jan 07 22:15:09 2020 +0000
@@ -96,7 +96,7 @@
 #endif
 
 #ifndef MSEC_PER_SEC
-#define MSEC_PER_SEC	1000L
+#define MSEC_PER_SEC	1000
 #define NSEC_PER_MSEC	1000000L
 #define NSEC_PER_SEC	1000000000U
 #endif
@@ -671,10 +671,10 @@
 }
 
 int
-eloop_q_timeout_add_msec(struct eloop *eloop, int queue, long when,
+eloop_q_timeout_add_msec(struct eloop *eloop, int queue, unsigned long when,
     void (*callback)(void *), void *arg)
 {
-	long seconds, nseconds;
+	unsigned long seconds, nseconds;
 
 	seconds = when / MSEC_PER_SEC;
 	if (seconds > UINT_MAX) {
--- a/src/eloop.h	Tue Jan 07 14:15:14 2020 +0000
+++ b/src/eloop.h	Tue Jan 07 22:15:09 2020 +0000
@@ -98,7 +98,7 @@
 int eloop_q_timeout_add_sec(struct eloop *, int,
     unsigned int, void (*)(void *), void *);
 int eloop_q_timeout_add_msec(struct eloop *, int,
-    long, void (*)(void *), void *);
+    unsigned long, void (*)(void *), void *);
 int eloop_q_timeout_delete(struct eloop *, int, void (*)(void *), void *);
 
 int eloop_signal_set_cb(struct eloop *, const int *, size_t,