changeset 2059:54a7c8ee7485 draft

Detect link address changes on Linux. Only NetBSD emits RTM_CHGADDR for link address changes. Sadly no other BSD emits anything for link address changes so we have to do a full discovery on carrier up. When a link address does change, simply carry on as we are, no need to drop any existing lease as the carrier change will do that for us.
author Roy Marples <roy@marples.name>
date Tue, 25 Jun 2013 08:31:11 +0000
parents 6f42edb32d59
children e4c1b1a31d67
files dhcpcd.c dhcpcd.h if-bsd.c if-linux.c
diffstat 4 files changed, 69 insertions(+), 53 deletions(-) [+]
line wrap: on
line diff
--- a/dhcpcd.c	Sat Jun 22 09:16:37 2013 +0000
+++ b/dhcpcd.c	Tue Jun 25 08:31:11 2013 +0000
@@ -412,6 +412,12 @@
 		if (ifp->carrier != LINK_UP) {
 			syslog(LOG_INFO, "%s: carrier acquired", ifp->name);
 			ifp->carrier = LINK_UP;
+#if !defined(__linux__) && !defined(__NetBSD__)
+			/* BSD does not emit RTM_NEWADDR or RTM_CHGADDR when the
+			 * hardware address changes so we have to go
+			 * through the disovery process to work it out. */
+			handle_interface(0, ifp->name);
+#endif
 			if (ifp->wireless)
 				getifssid(ifp->name, ifp->ssid);
 			configure_interface(ifp, margc, margv);
@@ -560,8 +566,10 @@
 			TAILQ_REMOVE(ifs, ifp, next);
 			TAILQ_INSERT_TAIL(ifaces, ifp, next);
 		}
-		init_state(ifp, margc, margv);
-		start_interface(ifp);
+		if (action == 1) {
+			init_state(ifp, margc, margv);
+			start_interface(ifp);
+		}
 	}
 
 	/* Free our discovered list */
@@ -572,47 +580,29 @@
 	free(ifs);
 }
 
-#ifdef RTM_CHGADDR
 void
-handle_hwaddr(const char *ifname, unsigned char *hwaddr, size_t hwlen)
+handle_hwaddr(const char *ifname, const uint8_t *hwaddr, size_t hwlen)
 {
 	struct interface *ifp;
-	struct if_options *ifo;
-	struct dhcp_state *state;
+
+	ifp = find_interface(ifname);
+	if (ifp == NULL)
+		return;
 
-	TAILQ_FOREACH(ifp, ifaces, next) {
-		if (strcmp(ifp->name, ifname) == 0 && ifp->hwlen <= hwlen) {
-			state = D_STATE(ifp);
-			if (state == NULL)
-				continue;
-			ifo = ifp->options;
-			if (!(ifo->options &
-			    (DHCPCD_INFORM | DHCPCD_STATIC | DHCPCD_CLIENTID))
-			    && state->new != NULL &&
-			    state->new->cookie == htonl(MAGIC_COOKIE))
-			{
-				syslog(LOG_INFO,
-				    "%s: expiring for new hardware address",
-				    ifp->name);
-				dhcp_drop(ifp, "EXPIRE");
-			}
-			memcpy(ifp->hwaddr, hwaddr, hwlen);
-			ifp->hwlen = hwlen;
-			if (!(ifo->options &
-			    (DHCPCD_INFORM | DHCPCD_STATIC | DHCPCD_CLIENTID)))
-			{
-				syslog(LOG_DEBUG, "%s: using hwaddr %s",
-				    ifp->name,
-				    hwaddr_ntoa(ifp->hwaddr, ifp->hwlen));
-				state->interval = 0;
-				state->nakoff = 0;
-				start_interface(ifp);
-			}
-		}
+	if (hwlen > sizeof(ifp->hwaddr)) {
+		errno = ENOBUFS;
+		syslog(LOG_ERR, "%s: %s: %m", ifp->name, __func__);
+		return;
 	}
-	free(hwaddr);
+
+	if (ifp->hwlen == hwlen && memcmp(ifp->hwaddr, hwaddr, hwlen) == 0)
+		return;
+
+	syslog(LOG_INFO, "%s: new hardware address: %s", ifp->name,
+	    hwaddr_ntoa(hwaddr, hwlen));
+	ifp->hwlen = hwlen;
+	memcpy(ifp->hwaddr, hwaddr, hwlen);
 }
-#endif
 
 static void
 if_reboot(struct interface *ifp, int argc, char **argv)
--- a/dhcpcd.h	Sat Jun 22 09:16:37 2013 +0000
+++ b/dhcpcd.h	Tue Jun 25 08:31:11 2013 +0000
@@ -84,7 +84,7 @@
 int handle_args(struct fd_list *, int, char **);
 void handle_carrier(int, int, const char *);
 void handle_interface(int, const char *);
-void handle_hwaddr(const char *, unsigned char *, size_t);
+void handle_hwaddr(const char *, const unsigned char *, size_t);
 void drop_interface(struct interface *, const char *);
 int select_profile(struct interface *, const char *);
 
--- a/if-bsd.c	Sat Jun 22 09:16:37 2013 +0000
+++ b/if-bsd.c	Tue Jun 25 08:31:11 2013 +0000
@@ -79,6 +79,10 @@
 	sin.s6_addr = ((sa) != NULL) ?					      \
 	    (((struct sockaddr_in6 *)(void *)sa)->sin6_addr).s6_addr : 0
 
+#ifndef CLLADDR
+#  define CLLADDR(s) ((const char *)((s)->sdl_data + (s)->sdl_nlen))
+#endif
+
 static int r_fd = -1;
 static char *link_buf;
 static ssize_t link_buflen;
@@ -531,10 +535,7 @@
 	struct ifa_msghdr *ifam;
 	struct sockaddr *sa, *rti_info[RTAX_MAX];
 	int len;
-#ifdef RTM_CHGADDR
 	struct sockaddr_dl sdl;
-	unsigned char *hwaddr;
-#endif
 #ifdef INET
 	struct rt rt;
 #endif
@@ -637,23 +638,20 @@
 				if (rti_info[RTAX_IFA] == NULL)
 					break;
 				switch (rti_info[RTAX_IFA]->sa_family) {
+				case AF_LINK:
 #ifdef RTM_CHGADDR
-				case AF_LINK:
 					if (rtm->rtm_type != RTM_CHGADDR)
 						break;
+#else
+					if (rtm->rtm_type != RTM_NEWADDR)
+						break;
+#endif
 					memcpy(&sdl, rti_info[RTAX_IFA],
 					    rti_info[RTAX_IFA]->sa_len);
-					hwaddr = malloc(sdl.sdl_alen);
-					if (hwaddr) {
-						memcpy(hwaddr, LLADDR(&sdl),
-						    sdl.sdl_alen);
-						handle_hwaddr(ifname, hwaddr,
-						    sdl.sdl_alen);
-					} else
-						syslog(LOG_ERR, "%s: %m",
-						    __func__);
+					handle_hwaddr(ifname,
+					    (const unsigned char*)CLLADDR(&sdl),
+					    sdl.sdl_alen);
 					break;
-#endif
 #ifdef INET
 				case AF_INET:
 				case 255: /* FIXME: Why 255? */
--- a/if-linux.c	Sat Jun 22 09:16:37 2013 +0000
+++ b/if-linux.c	Tue Jun 25 08:31:11 2013 +0000
@@ -386,11 +386,28 @@
 	return 1;
 }
 
+static short l2addr_len(unsigned short if_type)
+{
+
+	switch (if_type) {
+	case ARPHRD_ETHER: /* FALLTHROUGH */
+	case ARPHRD_IEEE802: /*FALLTHROUGH */
+	case ARPHRD_IEEE80211:
+		return 6;
+	case ARPHRD_IEEE1394:
+		return 8;
+	case ARPHRD_INFINIBAND:
+		return 20;
+	default:
+		return -1;
+	}
+}
+
 static int
 link_netlink(struct nlmsghdr *nlm)
 {
 	int len;
-	struct rtattr *rta;
+	struct rtattr *rta, *hwaddr;
 	struct ifinfomsg *ifi;
 	char ifn[IF_NAMESIZE + 1];
 
@@ -414,6 +431,7 @@
 	rta = (struct rtattr *)(void *)((char *)ifi +NLMSG_ALIGN(sizeof(*ifi)));
 	len = NLMSG_PAYLOAD(nlm, sizeof(*ifi));
 	*ifn = '\0';
+	hwaddr = NULL;
 	while (RTA_OK(rta, len)) {
 		switch (rta->rta_type) {
 		case IFLA_WIRELESS:
@@ -425,6 +443,9 @@
 		case IFLA_IFNAME:
 			strlcpy(ifn, RTA_DATA(rta), sizeof(ifn));
 			break;
+		case IFLA_ADDRESS:
+			hwaddr = rta;
+			break;
 		}
 		rta = RTA_NEXT(rta, len);
 	}
@@ -443,6 +464,13 @@
 		return 1;
 	}
 
+	/* Re-read hardware address and friends */
+	if (!(ifi->ifi_flags & IFF_UP) && hwaddr) {
+		len = l2addr_len(ifi->ifi_type);
+		if (hwaddr->rta_len == RTA_LENGTH(len))
+			handle_hwaddr(ifn, RTA_DATA(hwaddr), len);
+	}
+
 	handle_carrier(ifi->ifi_flags & IFF_RUNNING ? LINK_UP : LINK_DOWN,
 	    ifi->ifi_flags, ifn);
 	return 1;