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.
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));
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))
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;
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)
#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;
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
}
}
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
}
#include "common.h"
#include "dhcpcd.h"
#include "dhcp6.h"
+#include "eloop.h"
#include "ipv6.h"
#include "ipv6rs.h"
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;
}
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)
#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
};
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
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;
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,
# 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>
if (ifp == NULL)
return;
dhcp_free(ifp);
+ ipv6_free(ifp);
dhcp6_free(ifp);
ipv6rs_free(ifp);
free_options(ifp->options);
#elif AF_PACKET
const struct sockaddr_ll *sll;
#endif
+#ifdef INET6
+ const struct sockaddr_in6 *sin6;
+#endif
if (getifaddrs(&ifaddrs) == -1)
return NULL;
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