#include "if.h"
#include "ipv4.h"
#include "ipv6.h"
+#include "ipv6nd.h"
#define bpf_insn sock_filter
#define BPF_SKIPTYPE
snl.nl_groups |= RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR;
#endif
#ifdef INET6
- snl.nl_groups |= RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR;
+ snl.nl_groups |= RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_NEIGH;
#endif
return _open_link_socket(&snl);
return 0;
}
+#ifdef INET6
+static int
+link_neigh(struct dhcpcd_ctx *ctx, struct nlmsghdr *nlm)
+{
+ struct ndmsg *r;
+ struct rtattr *rta;
+ size_t len;
+ struct in6_addr addr6;
+ int flags;
+
+ if (nlm->nlmsg_type != RTM_NEWNEIGH && nlm->nlmsg_type != RTM_DELNEIGH)
+ return 0;
+ if (nlm->nlmsg_len < sizeof(*r))
+ return -1;
+
+ r = NLMSG_DATA(nlm);
+ rta = (struct rtattr *)RTM_RTA(r);
+ len = RTM_PAYLOAD(nlm);
+ if (r->ndm_family == AF_INET6) {
+ flags = 0;
+ if (r->ndm_flags & NTF_ROUTER)
+ flags |= IPV6ND_ROUTER;
+ if (nlm->nlmsg_type == RTM_NEWNEIGH &&
+ r->ndm_state &
+ (NUD_REACHABLE | NUD_STALE | NUD_DELAY | NUD_PROBE |
+ NUD_PERMANENT))
+ flags |= IPV6ND_REACHABLE;
+ memset(&addr6, 0, sizeof(addr6));
+ while (RTA_OK(rta, len)) {
+ switch (rta->rta_type) {
+ case NDA_DST:
+ memcpy(&addr6.s6_addr, RTA_DATA(rta),
+ sizeof(addr6.s6_addr));
+ break;
+ }
+ rta = RTA_NEXT(rta, len);
+ }
+ ipv6nd_neighbour(ctx, &addr6, flags);
+ }
+
+ return 1;
+}
+#endif
+
static int
link_netlink(struct dhcpcd_ctx *ctx, struct nlmsghdr *nlm)
{
r = link_addr(ctx, nlm);
if (r != 0)
return r;
+#ifdef INET6
+ r = link_neigh(ctx, nlm);
+ if (r != 0)
+ return r;
+#endif
if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK)
return 0;
return -1;
}
-int
-if_nd6reachable(__unused const char *ifname, __unused struct in6_addr *addr)
-{
-
- /* Assume reachable until I work out how to obtain reachability */
- return 1;
-}
-
static const char *prefix = "/proc/sys/net/ipv6/conf";
void
syslog(LOG_WARNING, "%s: no IPv6 Routers available", ifp->name);
}
+static void
+ipv6nd_reachable(struct ra *rap, int flags)
+{
+
+ if (flags & IPV6ND_REACHABLE) {
+ if (rap->lifetime && rap->expired) {
+ syslog(LOG_INFO, "%s: %s is reachable again",
+ rap->iface->name, rap->sfrom);
+ rap->expired = 0;
+ ipv6_buildroutes(rap->iface->ctx);
+ /* XXX Not really an RA */
+ script_runreason(rap->iface, "ROUTERADVERT");
+ }
+ } else {
+ /* Any error means it's really gone from the kernel
+ * neighbour database */
+ if (rap->lifetime && !rap->expired) {
+ syslog(LOG_WARNING,
+ "%s: %s is unreachable, expiring it",
+ rap->iface->name, rap->sfrom);
+ rap->expired = 1;
+ ipv6_buildroutes(rap->iface->ctx);
+ /* XXX Not really an RA */
+ script_runreason(rap->iface, "ROUTERADVERT");
+ }
+ }
+}
+
+#ifdef HAVE_RTM_GETNEIGH
+void
+ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, int flags)
+{
+ struct ra *rap;
+
+ TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) {
+ if (IN6_ARE_ADDR_EQUAL(&rap->from, addr)) {
+ ipv6nd_reachable(rap, flags);
+ break;
+ }
+ }
+}
+
+#else
+
static void
ipv6nd_checkreachablerouters(void *arg)
{
struct dhcpcd_ctx *ctx = arg;
struct ra *rap;
+ int flags;
TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) {
- if (if_nd6reachable(rap->iface->name, &rap->from) == 1) {
- if (rap->lifetime && rap->expired) {
- syslog(LOG_INFO, "%s: %s is reachable again",
- rap->iface->name, rap->sfrom);
- rap->expired = 0;
- ipv6_buildroutes(ctx);
- /* XXX Not really an RA */
- script_runreason(rap->iface, "ROUTERADVERT");
- }
- } else {
- /* Any error means it's really gone from the kernel
- * neighbour database */
- if (rap->lifetime && !rap->expired) {
- syslog(LOG_WARNING,
- "%s: %s is unreachable, expiring it",
- rap->iface->name, rap->sfrom);
- rap->expired = 1;
- ipv6_buildroutes(ctx);
- /* XXX Not really an RA */
- script_runreason(rap->iface, "ROUTERADVERT");
- }
+ flags = if_nd6reachable(rap->iface->name, &rap->from);
+ if (flags == -1) {
+ /* An error occured, so it's unreachable */
+ flags = 0;
}
+ ipv6nd_reachable(rap, flags);
}
eloop_timeout_add_sec(ctx->eloop, ND6REACHABLE_TIMER,
ipv6nd_checkreachablerouters, ctx);
}
+#endif
static void
ipv6nd_free_opts(struct ra *rap)
eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap);
if (!drop)
TAILQ_REMOVE(rap->iface->ctx->ipv6->ra_routers, rap, next);
+#ifndef HAVE_RTM_GETNEIGH
if (TAILQ_FIRST(rap->iface->ctx->ipv6->ra_routers) == NULL)
eloop_timeout_delete(rap->iface->ctx->eloop,
ipv6nd_checkreachablerouters, rap->iface->ctx);
+#endif
ipv6_freedrop_addrs(&rap->addrs, drop, NULL);
ipv6nd_free_opts(rap);
free(rap->data);
/* Expire should be called last as the rap object could be destroyed */
ipv6nd_expirera(ifp);
+#ifndef HAVE_RTM_GETNEIGH
/* Start our reachability tests now */
ipv6nd_checkreachablerouters(ifp->ctx);
+#endif
}
int
#define RETRANS_TIMER 1000 /* milliseconds */
#define DELAY_FIRST_PROBE_TIME 5 /* seconds */
+#define IPV6ND_REACHABLE (1 << 0)
+#define IPV6ND_ROUTER (1 << 1)
+
#ifdef INET6
void ipv6nd_startrs(struct interface *);
ssize_t ipv6nd_env(char **, const char *, const struct interface *);
void ipv6nd_handleifa(struct dhcpcd_ctx *, int,
const char *, const struct in6_addr *, int);
void ipv6nd_drop(struct interface *);
+
+#ifdef HAVE_RTM_GETNEIGH
+void ipv6nd_neighbour(struct dhcpcd_ctx *, struct in6_addr *, int);
+#endif
#else
#define ipv6nd_startrs(a) {}
#define ipv6nd_addrexists(a, b) (0)