changeset 5567:4fe5c2a71254 draft

hooks: add NOCARRIER_ROAMING reason This is given when the OS supports the concept of wireless roaming or the IP setup can be persisted when the carrier drops. When this happens, routes are moved to a higher metric (if supported) to support non preferred but non roaming routes. The `interface_order` hook variable will now order the interfaces according to priority and move roaming interfaces to the back of the list. If resolvconf is present then it is called with the -C option to deprecate DNS and if carrier comes back it is called again with the -c option to activate it once more. As part of this change, default route metrics have been changed to support a larger number of interfaces. base metric 1000 (was 200) wireless offset 2000 (was 100) IPv4LL offset 1000000 (was 10000) roaming offset 2000000
author Roy Marples <roy@marples.name>
date Sun, 27 Dec 2020 19:53:31 +0000
parents e90bef3160d7
children cebc093fd611
files BUILDING.md hooks/20-resolv.conf hooks/dhcpcd-run-hooks.8.in src/dhcpcd.c src/if.c src/ipv4ll.c src/route.c src/route.h src/script.c
diffstat 9 files changed, 95 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/BUILDING.md	Sat Dec 26 20:45:08 2020 +0100
+++ b/BUILDING.md	Sun Dec 27 19:53:31 2020 +0000
@@ -148,3 +148,11 @@
 The configure program attempts to find hooks for systems you have installed.
 To add more simply
 `./configure -with-hook=ntp.conf`
+
+If using resolvconf, the `20-resolv.conf` hook now requires a version with the
+`-C` and `-c` options to deprecate and activate interfaces to support wireless
+roaming (Linux) or carrier just drops (NetBSD).
+If your resolvconf does not support this then you will see a warning
+about an illegal option when the carrier changes, but things should still work.
+In this instance the DNS information cannot be Deprecated and may not
+be optimal for multi-homed hosts.
--- a/hooks/20-resolv.conf	Sat Dec 26 20:45:08 2020 +0100
+++ b/hooks/20-resolv.conf	Sun Dec 27 19:53:31 2020 +0000
@@ -10,6 +10,11 @@
 NL="
 "
 : ${resolvconf:=resolvconf}
+if type "$resolvconf" >/dev/null 2>&1; then
+	have_resolvconf=true
+else
+	have_resolvconf=false
+fi
 
 build_resolv_conf()
 {
@@ -164,7 +169,7 @@
 	for x in ${new_domain_name_servers}; do
 		conf="${conf}nameserver $x$NL"
 	done
-	if type "$resolvconf" >/dev/null 2>&1; then
+	if $have_resolvconf; then
 		[ -n "$ifmetric" ] && export IF_METRIC="$ifmetric"
 		printf %s "$conf" | "$resolvconf" -a "$ifname"
 		return $?
@@ -180,7 +185,7 @@
 
 remove_resolv_conf()
 {
-	if type "$resolvconf" >/dev/null 2>&1; then
+	if $have_resolvconf; then
 		"$resolvconf" -d "$ifname" -f
 	else
 		if [ -e "$resolv_conf_dir/$ifname" ]; then
@@ -199,7 +204,11 @@
 esac
 
 if $if_configured; then
-	if $if_up || [ "$reason" = ROUTERADVERT ]; then
+	if $have_resolvconf && [ "$reason" = NOCARRIER_ROAMING ]; then
+		"$resolvconf" -C "$interface.*"
+	elif $have_resolvconf && [ "$reason" = CARRIER ]; then
+		"$resolvconf" -c "$interface.*"
+	elif $if_up || [ "$reason" = ROUTERADVERT ]; then
 		add_resolv_conf
 	elif $if_down; then
 		remove_resolv_conf
--- a/hooks/dhcpcd-run-hooks.8.in	Sat Dec 26 20:45:08 2020 +0100
+++ b/hooks/dhcpcd-run-hooks.8.in	Sun Dec 27 19:53:31 2020 +0000
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd May 24, 2020
+.Dd December 27, 2020
 .Dt DHCPCD-RUN-HOOKS 8
 .Os
 .Sh NAME
@@ -92,6 +92,9 @@
 .It Dv NOCARRIER
 dhcpcd lost the carrier.
 The cable may have been unplugged or association to the wireless point lost.
+.It Dv NOCARRIER_ROAMING
+dhcpcd lost the carrier but the interface configuration is persisted.
+The OS has to support wireless roaming or IP Persistance for this to happen.
 .It Dv INFORM | Dv INFORM6
 dhcpcd informed a DHCP server about its address and obtained other
 configuration details.
@@ -147,10 +150,6 @@
 The following variables will then be set, along with any protocol supplied
 ones.
 .Bl -tag -width xnew_delegated_dhcp6_prefix
-.It Ev $chroot
-the directory where
-.Nm dhcpcd
-is chrooted.
 .It Ev $interface
 the name of the interface.
 .It Ev $protocol
@@ -193,12 +192,14 @@
 .Ev interface
 is up, otherwise
 .Dv false .
+This is more than IFF_UP and may not be equal.
 .It Ev $if_down
 .Dv true
 if the
 .Ev interface
 is down, otherwise
 .Dv false .
+This is more than IFF_UP and may not be equal.
 .It Ev $af_waiting
 Address family waiting for, as defined in
 .Xr dhcpcd.conf 5 .
--- a/src/dhcpcd.c	Sat Dec 26 20:45:08 2020 +0100
+++ b/src/dhcpcd.c	Sun Dec 27 19:53:31 2020 +0000
@@ -700,26 +700,7 @@
 {
 
 	loginfox("%s: carrier lost - roaming", ifp->name);
-
-	/*
-	 * XXX We should pass something like NOCARRIER_ROAMING
-	 * and set if_up=true; ifdown=false; so that the hook scripts
-	 * can make a decision to keep or discard the interface information.
-	 *
-	 * Currently they discard it (no carrier after all) which is
-	 * generally fine as new connections won't work and current
-	 * connections try to chug along as best as.
-	 * dhcpcd has been doing this since NetBSD-7 at least.
-	 *
-	 * However, for slow roaming this is poor for say web browsing
-	 * as new lookups will fail quickly giving a poor user experience.
-	 * We should improve this, but the hooks will require some work first
-	 * as we need to introduce a mechanism to sort interfaces by
-	 * carrier > roaming > nocarrier. Then the hooks know in which
-	 * order to apply their data, if at all.
-	 * This probably should be a user toggle.
-	 */
-	script_runreason(ifp, "NOCARRIER");
+	script_runreason(ifp, "NOCARRIER_ROAMING");
 
 #ifdef ARP
 	arp_drop(ifp);
--- a/src/if.c	Sat Dec 26 20:45:08 2020 +0100
+++ b/src/if.c	Sun Dec 27 19:53:31 2020 +0000
@@ -697,12 +697,11 @@
 			ifp->metric = (unsigned int)ifr.ifr_metric;
 		if_getssid(ifp);
 #else
-		/* We reserve the 100 range for virtual interfaces, if and when
-		 * we can work them out. */
-		ifp->metric = 200 + ifp->index;
+		/* Leave a low portion for user config */
+		ifp->metric = RTMETRIC_BASE + ifp->index;
 		if (if_getssid(ifp) != -1) {
 			ifp->wireless = true;
-			ifp->metric += 100;
+			ifp->metric += RTMETRIC_WIRELESS;
 		}
 #endif
 
--- a/src/ipv4ll.c	Sat Dec 26 20:45:08 2020 +0100
+++ b/src/ipv4ll.c	Sun Dec 27 19:53:31 2020 +0000
@@ -137,7 +137,7 @@
 	sa_in_init(&rt->rt_ifa, &state->addr->addr);
 	rt->rt_dflags |= RTDF_IPV4LL;
 #ifdef HAVE_ROUTE_METRIC
-	rt->rt_metric += 10000;
+	rt->rt_metric += RTMETRIC_IPV4LL;
 #endif
 	return rt_proto_add(routes, rt) ? 1 : 0;
 }
--- a/src/route.c	Sat Dec 26 20:45:08 2020 +0100
+++ b/src/route.c	Sun Dec 27 19:53:31 2020 +0000
@@ -168,6 +168,15 @@
 	if (c != 0)
 		return -c;
 
+	/* Prefer roaming over non roaming if both carriers are down. */
+	if (ifp1->carrier == LINK_DOWN && ifp2->carrier == LINK_DOWN) {
+		bool roam1 = if_roaming(ifp1);
+		bool roam2 = if_roaming(ifp2);
+
+		if (roam1 != roam2)
+			return roam1 ? 1 : -1;
+	}
+
 #ifdef INET
 	/* IPv4LL routes always come last */
 	if (rt1->rt_dflags & RTDF_IPV4LL && !(rt2->rt_dflags & RTDF_IPV4LL))
@@ -374,6 +383,8 @@
 	rt->rt_ifp = ifp;
 #ifdef HAVE_ROUTE_METRIC
 	rt->rt_metric = ifp->metric;
+	if (if_roaming(ifp))
+		rt->rt_metric += RTMETRIC_ROAM;
 #endif
 }
 
--- a/src/route.h	Sat Dec 26 20:45:08 2020 +0100
+++ b/src/route.h	Sun Dec 27 19:53:31 2020 +0000
@@ -93,6 +93,15 @@
 #ifdef HAVE_ROUTE_METRIC
 	unsigned int		rt_metric;
 #endif
+/* Maximum interface index is generally USHORT_MAX or 65535.
+ * Add some padding for other stuff and we get offsets for the
+ * below that should work automatically.
+ * This is only an issue if the user defines higher metrics in
+ * their configuration, but then they might wish to override also. */
+#define	RTMETRIC_BASE		   1000U
+#define	RTMETRIC_WIRELESS	   2000U
+#define	RTMETRIC_IPV4LL		1000000U
+#define	RTMETRIC_ROAM		2000000U
 #ifdef HAVE_ROUTE_PREF
 	int			rt_pref;
 #endif
--- a/src/script.c	Sat Dec 26 20:45:08 2020 +0100
+++ b/src/script.c	Sun Dec 27 19:53:31 2020 +0000
@@ -74,6 +74,9 @@
 	NULL
 };
 
+static const char * true_str = "true";
+static const char * false_str = "false";
+
 void
 if_printoptions(void)
 {
@@ -228,6 +231,10 @@
 	const struct if_options *ifo;
 	const struct interface *ifp2;
 	int af;
+	bool is_stdin = ifp->name[0] == '\0';
+	const char *if_up, *if_down;
+	rb_tree_t ifaces;
+	struct rt *rt;
 #ifdef INET
 	const struct dhcp_state *state;
 #ifdef IPV4LL
@@ -237,7 +244,6 @@
 #ifdef DHCP6
 	const struct dhcp6_state *d6_state;
 #endif
-	bool is_stdin = ifp->name[0] == '\0';
 
 #ifdef HAVE_OPEN_MEMSTREAM
 	if (ctx->script_fp == NULL) {
@@ -276,6 +282,7 @@
 		if (efprintf(fp, "pid=%d", getpid()) == -1)
 			goto eexit;
 	}
+
 	if (!is_stdin) {
 		if (efprintf(fp, "reason=%s", reason) == -1)
 			goto eexit;
@@ -326,6 +333,7 @@
 	else if (strcmp(reason, "PREINIT") == 0 ||
 	    strcmp(reason, "CARRIER") == 0 ||
 	    strcmp(reason, "NOCARRIER") == 0 ||
+	    strcmp(reason, "NOCARRIER_ROAMING") == 0 ||
 	    strcmp(reason, "UNKNOWN") == 0 ||
 	    strcmp(reason, "DEPARTED") == 0 ||
 	    strcmp(reason, "STOPPED") == 0)
@@ -382,34 +390,43 @@
 	if (ifp->ctx->options & DHCPCD_DUMPLEASE)
 		goto dumplease;
 
+	rb_tree_init(&ifaces, &rt_compare_proto_ops);
+	TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) {
+		rt = rt_new(UNCONST(ifp2));
+		if (rt == NULL)
+			goto eexit;
+		if (rb_tree_insert_node(&ifaces, rt) != rt)
+			goto eexit;
+	}
 	if (fprintf(fp, "interface_order=") == -1)
 		goto eexit;
-	TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) {
-		if (ifp2 != TAILQ_FIRST(ifp->ctx->ifaces)) {
-			if (fputc(' ', fp) == EOF)
-				return -1;
-		}
-		if (fprintf(fp, "%s", ifp2->name) == -1)
-			return -1;
+	RB_TREE_FOREACH(rt, &ifaces) {
+		if (rt != RB_TREE_MIN(&ifaces) &&
+		    fprintf(fp, "%s", " ") == -1)
+			goto eexit;
+		if (fprintf(fp, "%s", rt->rt_ifp->name) == -1)
+			goto eexit;
 	}
+	rt_headclear(&ifaces, AF_UNSPEC);
 	if (fputc('\0', fp) == EOF)
-		return -1;
+		goto eexit;
 
 	if (strcmp(reason, "STOPPED") == 0) {
-		if (efprintf(fp, "if_up=false") == -1)
-			goto eexit;
-		if (efprintf(fp, "if_down=%s",
-		    ifo->options & DHCPCD_RELEASE ? "true" : "false") == -1)
-			goto eexit;
+		if_up = false_str;
+		if_down = ifo->options & DHCPCD_RELEASE ? true_str : false_str;
 	} else if (strcmp(reason, "TEST") == 0 ||
 	    strcmp(reason, "PREINIT") == 0 ||
 	    strcmp(reason, "CARRIER") == 0 ||
 	    strcmp(reason, "UNKNOWN") == 0)
 	{
-		if (efprintf(fp, "if_up=false") == -1)
-			goto eexit;
-		if (efprintf(fp, "if_down=false") == -1)
-			goto eexit;
+		if_up = false_str;
+		if_down = false_str;
+	} else if (strcmp(reason, "NOCARRIER") == 0) {
+		if_up = false_str;
+		if_down = true_str;
+	} else if (strcmp(reason, "NOCARRIER_ROAMING") == 0) {
+		if_up = true_str;
+		if_down = false_str;
 	} else if (1 == 2 /* appease ifdefs */
 #ifdef INET
 	    || (protocol == PROTO_DHCP && state && state->new)
@@ -426,16 +443,17 @@
 #endif
 	    )
 	{
-		if (efprintf(fp, "if_up=true") == -1)
-			goto eexit;
-		if (efprintf(fp, "if_down=false") == -1)
-			goto eexit;
+		if_up = true_str;
+		if_down = false_str;
 	} else {
-		if (efprintf(fp, "if_up=false") == -1)
-			goto eexit;
-		if (efprintf(fp, "if_down=true") == -1)
-			goto eexit;
+		if_up = false_str;
+		if_down = true_str;
 	}
+	if (efprintf(fp, "if_up=%s", if_up) == -1)
+		goto eexit;
+	if (efprintf(fp, "if_down=%s", if_down) == -1)
+		goto eexit;
+
 	if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) {
 		if (efprintf(fp, "if_afwaiting=%d", af) == -1)
 			goto eexit;