From: Roy Marples Date: Tue, 25 Jun 2013 08:31:11 +0000 (+0000) Subject: Detect link address changes on Linux. X-Git-Tag: v6.0.2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ecf297d6c7930bc56ea7433063473de3ce7b2996;p=thirdparty%2Fdhcpcd.git 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. --- diff --git a/dhcpcd.c b/dhcpcd.c index 627085c7..c2cb2eac 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -412,6 +412,12 @@ handle_carrier(int carrier, int flags, const char *ifname) 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 @@ handle_interface(int action, const char *ifname) 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 @@ handle_interface(int action, const char *ifname) 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; - 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); - } - } + ifp = find_interface(ifname); + if (ifp == NULL) + return; + + 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) diff --git a/dhcpcd.h b/dhcpcd.h index 50b0174b..0fad506a 100644 --- a/dhcpcd.h +++ b/dhcpcd.h @@ -84,7 +84,7 @@ struct interface *find_interface(const char *); 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 *); diff --git a/if-bsd.c b/if-bsd.c index 96f483d9..4cf87187 100644 --- a/if-bsd.c +++ b/if-bsd.c @@ -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 @@ manage_link(int fd) 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 @@ manage_link(int fd) if (rti_info[RTAX_IFA] == NULL) break; switch (rti_info[RTAX_IFA]->sa_family) { -#ifdef RTM_CHGADDR case AF_LINK: +#ifdef RTM_CHGADDR 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? */ diff --git a/if-linux.c b/if-linux.c index fc3da199..899ce6d0 100644 --- a/if-linux.c +++ b/if-linux.c @@ -386,11 +386,28 @@ link_addr(struct nlmsghdr *nlm) 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 @@ link_netlink(struct nlmsghdr *nlm) 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 @@ link_netlink(struct nlmsghdr *nlm) 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 @@ link_netlink(struct nlmsghdr *nlm) 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;