]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Store IPv6 link local addresses per interface.
authorRoy Marples <roy@marples.name>
Fri, 17 May 2013 23:09:36 +0000 (23:09 +0000)
committerRoy Marples <roy@marples.name>
Fri, 17 May 2013 23:09:36 +0000 (23:09 +0000)
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.

dhcp6.c
dhcpcd.c
dhcpcd.h
if-bsd.c
if-linux.c
ipv6.c
ipv6.h
ipv6rs.c
net.c

diff --git a/dhcp6.c b/dhcp6.c
index aec603936ed1f2412b47f8dea00bd36326f8ecf1..379964c5788dffc5b59a3c815831c3776701688f 100644 (file)
--- 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));
index 5fd2b4320469e01f64e928f49cac28d1feb708f4..545af79d7973b9df639e668e2ef9fddcd22cee75 100644 (file)
--- 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)
index 4115c6f0fda147691075f12b23229095d3a10a82..61af4664690500439664e10ae47fd40ec49a5dda 100644 (file)
--- a/dhcpcd.h
+++ b/dhcpcd.h
 #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;
index 136fab2db9f516241a88bd478571529e490892e0..81632a97a45ec0fb0f186615ee0e1e0f7ece2b6c 100644 (file)
--- 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
                                }
index 0d668e4d8f18fd5950cd5a52c3ef4f1af7d03f51..c56225706f53719e49e0cecd0947cde3b27c4d50 100644 (file)
@@ -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 134c92c7a8da674f46f48ab28883c6fcf9e1a4a6..564583366311abb8e5e8b5885385c7425e6fe687 100644 (file)
--- 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 1c0191941461020a95bceff3bc83a09ba9063707..96f968b536b50683774dbe1a9345e52fd7627c90 100644 (file)
--- 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
index ca69fe2adfab77520df1a69b56f0155d43d0506c..34473b43c32bbf57c96a1a3eacf76512ddf9ee8b 100644 (file)
--- 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 b96f09fd5e90c7a8139fe93448db79ddb21490f9..f6e6e8468442d6767acc42200e7cb8d4bda7c2cd 100644 (file)
--- a/net.c
+++ b/net.c
 #  include <net/if_media.h>
 #endif
 
+#include <net/route.h>
+#ifdef __linux__
+#  include <asm/types.h> /* for systems with broken headers */
+#  include <linux/rtnetlink.h>
+#endif
+
 #include <ctype.h>
 #include <errno.h>
 #include <ifaddrs.h>
@@ -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