From: Roy Marples Date: Fri, 17 May 2013 23:09:36 +0000 (+0000) Subject: Store IPv6 link local addresses per interface. X-Git-Tag: v5.99.7~58 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=5331b839c9a0feac60ae563b051609eba9a066ab;p=thirdparty%2Fdhcpcd.git Store IPv6 link local addresses per interface. Listen to kernel messages to account them. If we don't have a local link address, delay IPv6RS as it just won#t work until we have a local link address. --- diff --git a/dhcp6.c b/dhcp6.c index aec60393..379964c5 100644 --- a/dhcp6.c +++ b/dhcp6.c @@ -1483,7 +1483,7 @@ dhcp6_delegate_addr(struct interface *ifp, const struct ipv6_addr *prefix, if (b) a->prefix.s6_addr[--i] |= *p; - if (ipv6_makeaddr(&a->addr, ifp->name, &a->prefix, a->prefix_len) == -1) + if (ipv6_makeaddr(&a->addr, ifp, &a->prefix, a->prefix_len) == -1) { ia = inet_ntop(AF_INET6, &a->addr.s6_addr, iabuf, sizeof(iabuf)); diff --git a/dhcpcd.c b/dhcpcd.c index 5fd2b432..545af79d 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -369,8 +369,7 @@ handle_carrier(int action, int flags, const char *ifname) return; ifp = find_interface(ifname); if (ifp == NULL) { - if (options & DHCPCD_LINK) - handle_interface(1, ifname); + handle_interface(1, ifname); return; } if (!(ifp->options->options & DHCPCD_LINK)) @@ -457,18 +456,6 @@ init_state(struct interface *ifp, int argc, char **argv) configure_interface(ifp, argc, argv); ifo = ifp->options; - /* RTM_NEWADDR goes through the link socket as well which we - * need for IPv6 DAD, so we check for DHCPCD_LINK in handle_carrier - * instead */ - if (linkfd == -1) { - linkfd = open_link_socket(); - if (linkfd == -1) { - syslog(LOG_ERR, "open_link_socket: %m"); - ifo->options &= ~DHCPCD_LINK; - } else - eloop_event_add(linkfd, handle_link, NULL); - } - if (ifo->options & DHCPCD_IPV4 && ipv4_init() == -1) { syslog(LOG_ERR, "ipv4_init: %m"); ifo->options &= ~DHCPCD_IPV4; @@ -1142,6 +1129,19 @@ main(int argc, char **argv) if (ifc == 1) options |= DHCPCD_WAITIP; + /* RTM_NEWADDR goes through the link socket as well which we + * need for IPv6 DAD, so we check for DHCPCD_LINK in handle_carrier + * instead. + * We also need to open this before checking for interfaces below + * so that we pickup any new addresses during the discover phase. */ + if (linkfd == -1) { + linkfd = open_link_socket(); + if (linkfd == -1) + syslog(LOG_ERR, "open_link_socket: %m"); + else + eloop_event_add(linkfd, handle_link, NULL); + } + ifaces = discover_interfaces(ifc, ifv); for (i = 0; i < ifc; i++) { if (find_interface(ifv[i]) == NULL) diff --git a/dhcpcd.h b/dhcpcd.h index 4115c6f0..61af4664 100644 --- a/dhcpcd.h +++ b/dhcpcd.h @@ -44,9 +44,10 @@ #define LINK_DOWN -1 #define IF_DATA_DHCP 0 -#define IF_DATA_IPV6RS 1 -#define IF_DATA_DHCP6 2 -#define IF_DATA_MAX 3 +#define IF_DATA_IPV6 1 +#define IF_DATA_IPV6RS 2 +#define IF_DATA_DHCP6 3 +#define IF_DATA_MAX 4 struct interface { TAILQ_ENTRY(interface) next; diff --git a/if-bsd.c b/if-bsd.c index 136fab2d..81632a97 100644 --- a/if-bsd.c +++ b/if-bsd.c @@ -613,8 +613,8 @@ manage_link(int fd) memcpy(ia6.s6_addr, sin6->sin6_addr.s6_addr, sizeof(ia6.s6_addr)); - ipv6_handleifa(rtm->rtm_type, ifname, - &ia6, ifam->ifam_flags); + ipv6_handleifa(rtm->rtm_type, NULL, + ifname, &ia6, ifam->ifam_flags); break; #endif } diff --git a/if-linux.c b/if-linux.c index 0d668e4d..c5622570 100644 --- a/if-linux.c +++ b/if-linux.c @@ -378,7 +378,8 @@ link_addr(struct nlmsghdr *nlm) } rta = RTA_NEXT(rta, len); } - ipv6_handleifa(nlm->nlmsg_type, ifn, &addr6, ifa->ifa_flags); + ipv6_handleifa(nlm->nlmsg_type, NULL, ifn, + &addr6, ifa->ifa_flags); break; #endif } diff --git a/ipv6.c b/ipv6.c index 134c92c7..56458336 100644 --- a/ipv6.c +++ b/ipv6.c @@ -55,6 +55,7 @@ #include "common.h" #include "dhcpcd.h" #include "dhcp6.h" +#include "eloop.h" #include "ipv6.h" #include "ipv6rs.h" @@ -127,62 +128,28 @@ ipv6_printaddr(char *s, ssize_t sl, const uint8_t *d, const char *ifname) return l; } -struct in6_addr * -ipv6_linklocal(const char *ifname) -{ - struct ifaddrs *ifaddrs, *ifa; - struct sockaddr_in6 *sa6; - struct in6_addr *in6; - - if (getifaddrs(&ifaddrs) == -1) - return NULL; - - for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL || - ifa->ifa_addr->sa_family != AF_INET6) - continue; - if (strcmp(ifa->ifa_name, ifname)) - continue; - sa6 = (struct sockaddr_in6 *)(void *)ifa->ifa_addr; - if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) - break; - } - - if (ifa) { - in6 = malloc(sizeof(*in6)); - if (in6 == NULL) { - syslog(LOG_ERR, "%s: %m", __func__); - return NULL; - } - memcpy(in6, &sa6->sin6_addr, sizeof(*in6)); - } else - in6 = NULL; - - freeifaddrs(ifaddrs); - return in6; -} - int -ipv6_makeaddr(struct in6_addr *addr, const char *ifname, +ipv6_makeaddr(struct in6_addr *addr, const struct interface *ifp, const struct in6_addr *prefix, int prefix_len) { - struct in6_addr *lla; + const struct ipv6_state *state; + const struct ll_addr *ap; if (prefix_len < 0 || prefix_len > 64) { errno = EINVAL; return -1; } - lla = ipv6_linklocal(ifname); - if (lla == NULL) { + state = IPV6_CSTATE(ifp); + ap = TAILQ_FIRST(&state->ll_addrs); + if (ap == NULL) { errno = ENOENT; return -1; } memcpy(addr, prefix, sizeof(*prefix)); - addr->s6_addr32[2] = lla->s6_addr32[2]; - addr->s6_addr32[3] = lla->s6_addr32[3]; - free(lla); + addr->s6_addr32[2] = ap->addr.s6_addr32[2]; + addr->s6_addr32[3] = ap->addr.s6_addr32[3]; return 0; } @@ -300,15 +267,152 @@ ipv6_addaddrs(struct ipv6_addrhead *addrs) return i; } +static struct ipv6_state * +ipv6_getstate(struct interface *ifp) +{ + struct ipv6_state *state; + + state = IPV6_STATE(ifp); + if (state == NULL) { + ifp->if_data[IF_DATA_IPV6] = malloc(sizeof(*state)); + state = IPV6_STATE(ifp); + if (state == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); + return NULL; + } + TAILQ_INIT(&state->ll_addrs); + TAILQ_INIT(&state->ll_callbacks); + } + return state; +} + void -ipv6_handleifa(int cmd, const char *ifname, +ipv6_handleifa(int cmd, struct if_head *ifs, const char *ifname, const struct in6_addr *addr, int flags) { + struct interface *ifp; + struct ipv6_state *state; + struct ll_addr *ap; + struct ll_callback *cb; + + /* Safety - ignore tentative announcements */ + if (cmd == RTM_NEWADDR && flags & IN6_IFF_TENTATIVE) + return; + + if (ifs == NULL) + ifs = ifaces; + if (ifs == NULL) { + errno = ESRCH; + return; + } + TAILQ_FOREACH(ifp, ifs, next) { + if (strcmp(ifp->name, ifname) == 0) + break; + } + if (ifp == NULL) { + errno = ESRCH; + return; + } + + if (IN6_IS_ADDR_LINKLOCAL(addr)) { + state = ipv6_getstate(ifp); + if (state == NULL) + return; + TAILQ_FOREACH(ap, &state->ll_addrs, next) { + if (memcmp(ap->addr.s6_addr, + addr->s6_addr, + sizeof(ap->addr.s6_addr)) == 0) + break; + } + switch (cmd) { + case RTM_DELADDR: + if (ap) { + TAILQ_REMOVE(&state->ll_addrs, ap, next); + free(ap); + } + return; + case RTM_NEWADDR: + if (ap == NULL) { + ap = calloc(1, sizeof(*ap)); + memcpy(ap->addr.s6_addr, addr->s6_addr, + sizeof(ap->addr.s6_addr)); + TAILQ_INSERT_TAIL(&state->ll_addrs, + ap, next); + + /* Now run any callbacks. + * Typically IPv6RS or DHCPv6 */ + while ((cb = TAILQ_FIRST(&state->ll_callbacks))) + { + TAILQ_REMOVE(&state->ll_callbacks, + cb, next); + cb->callback(cb->arg); + free(cb); + } + } + return; + default: + return; + } + } ipv6rs_handleifa(cmd, ifname, addr, flags); dhcp6_handleifa(cmd, ifname, addr, flags); } +int +ipv6_interfacehaslinklocal(const struct interface *ifp) +{ + const struct ipv6_state *state; + + state = IPV6_CSTATE(ifp); + return state && TAILQ_FIRST(&state->ll_addrs) ? 1 : 0; +} + +int ipv6_addlinklocalcallback(struct interface *ifp, + void (*callback)(void *), void *arg) +{ + struct ipv6_state *state; + struct ll_callback *cb; + + state = ipv6_getstate(ifp); + TAILQ_FOREACH(cb, &state->ll_callbacks, next) { + if (cb->callback == callback && cb->arg == arg) + break; + } + if (cb == NULL) { + cb = malloc(sizeof(*cb)); + if (cb == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); + return -1; + } + cb->callback = callback; + cb->arg = arg; + TAILQ_INSERT_TAIL(&state->ll_callbacks, cb, next); + } + return 0; +} + +void +ipv6_free(struct interface *ifp) +{ + struct ipv6_state *state; + struct ll_addr *ap; + struct ll_callback *cb; + + state = IPV6_STATE(ifp); + if (state) { + while ((ap = TAILQ_FIRST(&state->ll_addrs))) { + TAILQ_REMOVE(&state->ll_addrs, ap, next); + free(ap); + } + while ((cb = TAILQ_FIRST(&state->ll_callbacks))) { + TAILQ_REMOVE(&state->ll_callbacks, cb, next); + free(cb); + } + free(state); + } +} + int ipv6_handleifa_addrs(int cmd, struct ipv6_addrhead *addrs, const struct in6_addr *addr, int flags) diff --git a/ipv6.h b/ipv6.h index 1c019194..96f968b5 100644 --- a/ipv6.h +++ b/ipv6.h @@ -38,6 +38,7 @@ #define ROUNDUP8(a) (1 + (((a) - 1) | 7)) +#ifdef INET6 /* * BSD kernels don't inform userland of DAD results. * Also, for RTM_NEWADDR messages the address flags could be @@ -101,25 +102,52 @@ struct rt6 { }; TAILQ_HEAD(rt6head, rt6); -#ifdef INET6 +struct ll_addr { + TAILQ_ENTRY(ll_addr) next; + struct in6_addr addr; +}; + +TAILQ_HEAD(ll_addr_head, ll_addr); + +struct ll_callback { + TAILQ_ENTRY(ll_callback) next; + void (*callback)(void *); + void *arg; +}; +TAILQ_HEAD(ll_callback_head, ll_callback); + +struct ipv6_state { + struct ll_addr_head ll_addrs; + struct ll_callback_head ll_callbacks; +}; + +#define IPV6_STATE(ifp) \ + ((struct ipv6_state *)(ifp)->if_data[IF_DATA_IPV6]) +#define IPV6_CSTATE(ifp) \ + ((const struct ipv6_state *)(ifp)->if_data[IF_DATA_IPV6]) + int ipv6_init(void); ssize_t ipv6_printaddr(char *, ssize_t, const uint8_t *, const char *); -struct in6_addr *ipv6_linklocal(const char *); -int ipv6_makeaddr(struct in6_addr *, const char *, +int ipv6_makeaddr(struct in6_addr *, const struct interface *, const struct in6_addr *, int); int ipv6_makeprefix(struct in6_addr *, const struct in6_addr *, int); int ipv6_mask(struct in6_addr *, int); int ipv6_prefixlen(const struct in6_addr *); int ipv6_addaddr(struct ipv6_addr *); ssize_t ipv6_addaddrs(struct ipv6_addrhead *); -void ipv6_handleifa(int, const char *, const struct in6_addr *, int); +void ipv6_handleifa(int, struct if_head *, + const char *, const struct in6_addr *, int); int ipv6_handleifa_addrs(int, struct ipv6_addrhead *, const struct in6_addr *, int); +int ipv6_interfacehaslinklocal(const struct interface *); +int ipv6_addlinklocalcallback(struct interface *, void (*)(void *), void *); +void ipv6_free(struct interface *); int ipv6_removesubnet(const struct interface *, struct ipv6_addr *); void ipv6_buildroutes(void); void ipv6_drop(struct interface *); #else #define ipv6_init() -1 +#define ipv6_free(a) #endif #endif diff --git a/ipv6rs.c b/ipv6rs.c index ca69fe2a..34473b43 100644 --- a/ipv6rs.c +++ b/ipv6rs.c @@ -232,6 +232,14 @@ ipv6rs_sendprobe(void *arg) struct in6_pktinfo pi; int hoplimit = HOPLIMIT; + if (!ipv6_interfacehaslinklocal(ifp)) { + syslog(LOG_DEBUG, + "%s: delaying Router Soliciation for LL address", + ifp->name); + ipv6_addlinklocalcallback(ifp, ipv6rs_sendprobe, ifp); + return; + } + dst = allrouters; dst.sin6_scope_id = ifp->index; @@ -730,7 +738,7 @@ ipv6rs_handledata(__unused void *arg) if (pi->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) { - ipv6_makeaddr(&ap->addr, ifp->name, + ipv6_makeaddr(&ap->addr, ifp, &ap->prefix, pi->nd_opt_pi_prefix_len); cbp = inet_ntop(AF_INET6, diff --git a/net.c b/net.c index b96f09fd..f6e6e846 100644 --- a/net.c +++ b/net.c @@ -42,6 +42,12 @@ # include #endif +#include +#ifdef __linux__ +# include /* for systems with broken headers */ +# include +#endif + #include #include #include @@ -127,6 +133,7 @@ free_interface(struct interface *ifp) if (ifp == NULL) return; dhcp_free(ifp); + ipv6_free(ifp); dhcp6_free(ifp); ipv6rs_free(ifp); free_options(ifp->options); @@ -224,6 +231,9 @@ discover_interfaces(int argc, char * const *argv) #elif AF_PACKET const struct sockaddr_ll *sll; #endif +#ifdef INET6 + const struct sockaddr_in6 *sin6; +#endif if (getifaddrs(&ifaddrs) == -1) return NULL; @@ -417,6 +427,22 @@ discover_interfaces(int argc, char * const *argv) TAILQ_INSERT_TAIL(ifs, ifp, next); } + +#ifdef INET6 + /* Capture local link addresses */ + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr != NULL && + ifa->ifa_addr->sa_family == AF_INET6) + { + sin6 = (const struct sockaddr_in6 *)ifa->ifa_addr; + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) + /* XXX: Check tentative, etc? */ + ipv6_handleifa(RTM_NEWADDR, ifs, ifa->ifa_name, + &sin6->sin6_addr, 0); + } + } +#endif + freeifaddrs(ifaddrs); #ifdef IFLR_ACTIVE