if (valid)
dhcpcd_daemonise(ifp->ctx);
}
+ ipv6nd_advertise(ia);
}
}
}
dhcp6_drop(ifp, "EXPIRE6");
}
+void
+dhcp6_abort(struct interface *ifp)
+{
+ struct dhcp6_state *state;
+ struct ipv6_addr *ia;
+
+ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_start1, ifp);
+ state = D6_STATE(ifp);
+ if (state == NULL)
+ return;
+ TAILQ_FOREACH(ia, &state->addrs, next) {
+ ipv6nd_advertise(ia);
+ }
+}
+
void
dhcp6_handleifa(int cmd, struct ipv6_addr *ia, pid_t pid)
{
void dhcp6_free(struct interface *);
void dhcp6_handleifa(int, struct ipv6_addr *, pid_t);
int dhcp6_dadcompleted(const struct interface *);
+void dhcp6_abort(struct interface *);
void dhcp6_drop(struct interface *, const char *);
void dhcp6_dropnondelegates(struct interface *ifp);
int dhcp6_dump(struct interface *);
#endif
dhcp_abort(ifp);
ipv6nd_expire(ifp, 0);
+ dhcp6_abort(ifp);
#else
dhcpcd_drop(ifp, 0);
#endif
#ifdef INET6
#if (defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV)) || \
- defined(IPV6CTL_USETEMPADDR) || defined(IPV6CTL_TEMPVLTIME)
+ defined(IPV6CTL_USETEMPADDR) || defined(IPV6CTL_TEMPVLTIME) || \
+ defined(IPV6CTL_FORWARDING)
#define get_inet6_sysctl(code) inet6_sysctl(code, 0, 0)
#define set_inet6_sysctl(code, val) inet6_sysctl(code, val, 1)
static int
}
#endif
+int
+ip6_forwarding(__unused const char *ifname)
+{
+ int val;
+
+#ifdef IPV6CTL_FORWARDING
+ val = get_inet6_sysctl(IPV6CTL_FORWARDING);
+#else
+ val = get_inet6_sysctlbyname("net.inet6.ip6.forwarding");
+#endif
+ return val < 0 ? 0 : val;
+}
+
#ifdef SIOCIFAFATTACH
static int
af_attach(int s, const struct interface *ifp, int af)
return val < 0 ? TEMP_VALID_LIFETIME : val;
}
#endif /* IPV6_MANAGETEMPADDR */
+
+int
+ip6_forwarding(const char *ifname)
+{
+ char path[256];
+ int val;
+
+ if (ifname == NULL)
+ ifname = "all";
+ snprintf(path, sizeof(path), "%s/%s/forwarding", prefix, ifname);
+ val = check_proc_int(path);
+ return val == -1 ? 0 : val;
+}
+
#endif /* INET6 */
#else
#define ip6_use_tempaddr(a) (0)
#endif
+int ip6_forwarding(const char *ifname);
int if_address6(unsigned char, const struct ipv6_addr *);
int if_addrflags6(const struct interface *, const struct in6_addr *,
break;
}
}
+
+ /* Advertise the address if it exists on another interface. */
+ ipv6nd_advertise(ia);
}
static int
ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now)
{
struct interface *ifp;
- struct ipv6_state *state;
- struct ipv6_addr *ia2;
uint32_t pltime, vltime;
+ bool vltime_was_zero;
__printflike(1, 2) void (*logfunc)(const char *, ...);
- /* Ensure no other interface has this address */
- TAILQ_FOREACH(ifp, ia->iface->ctx->ifaces, next) {
- if (ifp == ia->iface)
- continue;
- state = IPV6_STATE(ifp);
- if (state == NULL)
- continue;
- TAILQ_FOREACH(ia2, &state->addrs, next) {
- if (IN6_ARE_ADDR_EQUAL(&ia2->addr, &ia->addr)) {
- ipv6_deleteaddr(ia2);
- break;
- }
- }
- }
-
/* Remember the interface of the address. */
ifp = ia->iface;
" seconds",
ifp->name, ia->prefix_pltime, ia->prefix_vltime);
-
+ vltime_was_zero = ia->prefix_vltime == 0;
if (if_address6(RTM_NEWADDR, ia) == -1) {
logerr(__func__);
/* Restore real pltime and vltime */
}
#endif
+ /* Re-advertise the preferred address to be safe. */
+ if (!vltime_was_zero)
+ ipv6nd_advertise(ia);
+
return 0;
}
ssize_t
ipv6_addaddrs(struct ipv6_addrhead *addrs)
{
- struct ipv6_addr *ap, *apn, *apf;
+ struct ipv6_addr *ap, *apn;
ssize_t i;
struct timespec now;
} else if (!(ap->flags & IPV6_AF_STALE) &&
!IN6_IS_ADDR_UNSPECIFIED(&ap->addr))
{
- apf = ipv6_findaddr(ap->iface->ctx,
- &ap->addr, IPV6_AF_ADDED);
- if (apf && apf->iface != ap->iface) {
- if (apf->iface->metric <= ap->iface->metric) {
- loginfox("%s: preferring %s on %s",
- ap->iface->name,
- ap->saddr,
- apf->iface->name);
- continue;
- }
- loginfox("%s: preferring %s on %s",
- apf->iface->name,
- ap->saddr,
- ap->iface->name);
- if (if_address6(RTM_DELADDR, apf) == -1 &&
- errno != EADDRNOTAVAIL && errno != ENXIO)
- logerr(__func__);
- apf->flags &=
- ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED);
- } else if (apf)
- apf->flags &= ~IPV6_AF_ADDED;
if (ap->flags & IPV6_AF_NEW)
i++;
if (!timespecisset(&now))
}
eloop_q_timeout_delete(ia->iface->ctx->eloop, 0, NULL, ia);
+ free(ia->na);
free(ia);
}
case RTM_DELADDR:
if (ia != NULL) {
TAILQ_REMOVE(&state->addrs, ia, next);
+ /* Advertise the address if it exists on
+ * another interface. */
+ ipv6nd_advertise(ia);
/* We'll free it at the end of the function. */
}
break;
# endif
#endif
-#define ALLROUTERS "ff02::2"
+#define ALLNODES "ff02::1"
+#define ALLROUTERS "ff02::2"
#define EUI64_GBIT 0x01
#define EUI64_UBIT 0x02
void (*dadcallback)(void *);
int dadcounter;
+ struct nd_neighbor_advert *na;
+ size_t na_len;
+ int na_count;
+
#ifdef ALIAS_ADDR
char alias[IF_NAMESIZE];
#endif
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
+#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
}
}
+static void
+ipv6nd_sendadvertisement(void *arg)
+{
+ struct ipv6_addr *ia = arg;
+ struct interface *ifp = ia->iface;
+ struct dhcpcd_ctx *ctx = ifp->ctx;
+ struct sockaddr_in6 dst;
+ struct cmsghdr *cm;
+ struct in6_pktinfo pi;
+ const struct rs_state *state = RS_CSTATE(ifp);
+
+ if (state == NULL || ifp->carrier == LINK_DOWN)
+ goto freeit;
+
+ memset(&dst, 0, sizeof(dst));
+ dst.sin6_family = AF_INET6;
+#ifdef SIN6_LEN
+ dst.sin6_len = sizeof(dst);
+#endif
+ dst.sin6_scope_id = ifp->index;
+ if (inet_pton(AF_INET6, ALLNODES, &dst.sin6_addr) != 1) {
+ logerr(__func__);
+ return;
+ }
+
+ ctx->sndhdr.msg_name = (void *)&dst;
+ ctx->sndhdr.msg_iov[0].iov_base = ia->na;
+ ctx->sndhdr.msg_iov[0].iov_len = ia->na_len;
+
+ /* Set the outbound interface. */
+ cm = CMSG_FIRSTHDR(&ctx->sndhdr);
+ assert(cm != NULL);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(pi));
+ memset(&pi, 0, sizeof(pi));
+ pi.ipi6_ifindex = ifp->index;
+ memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
+
+ logdebugx("%s: sending NA for %s", ifp->name, ia->saddr);
+ if (sendmsg(ctx->nd_fd, &ctx->sndhdr, 0) == -1)
+ logerr(__func__);
+
+ if (++ia->na_count < MAX_NEIGHBOR_ADVERTISEMENT) {
+ eloop_timeout_add_sec(ctx->eloop,
+ state->retrans / 1000, ipv6nd_sendadvertisement, ia);
+ return;
+ }
+
+freeit:
+ free(ia->na);
+ ia->na = NULL;
+ ia->na_count = 0;
+}
+
+void
+ipv6nd_advertise(struct ipv6_addr *ia)
+{
+ struct dhcpcd_ctx *ctx;
+ struct interface *ifp;
+ struct ipv6_state *state;
+ struct ipv6_addr *iap, *iaf;
+ struct nd_neighbor_advert *na;
+
+ if (IN6_IS_ADDR_MULTICAST(&ia->addr))
+ return;
+
+ ctx = ia->iface->ctx;
+ if_sortinterfaces(ctx);
+ /* Find the most preferred address to advertise. */
+ iaf = NULL;
+ TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+ state = IPV6_STATE(ifp);
+ if (state == NULL || ifp->carrier == LINK_DOWN)
+ continue;
+
+ TAILQ_FOREACH(iap, &state->addrs, next) {
+ if (!IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr))
+ continue;
+
+ /* Cancel any current advertisement. */
+ eloop_timeout_delete(ctx->eloop,
+ ipv6nd_sendadvertisement, iap);
+
+ /* Don't advertise what we can't use. */
+ if (iap->prefix_vltime == 0 ||
+ iap->addr_flags & IN6_IFF_NOTUSEABLE)
+ continue;
+
+ if (iaf == NULL)
+ iaf = iap;
+ }
+ }
+ if (iaf == NULL)
+ return;
+
+ /* Make the packet. */
+ ifp = iaf->iface;
+ iaf->na_len = sizeof(*na);
+ if (ifp->hwlen != 0)
+ iaf->na_len += (size_t)ROUNDUP8(ifp->hwlen + 2);
+ na = calloc(1, iaf->na_len);
+ if (na == NULL) {
+ logerr(__func__);
+ return;
+ }
+
+ na->nd_na_type = ND_NEIGHBOR_ADVERT;
+ na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE;
+ if (ip6_forwarding(ifp->name) == 1)
+ na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
+ na->nd_na_target = ia->addr;
+
+ if (ifp->hwlen != 0) {
+ struct nd_opt_hdr *opt;
+
+ opt = (struct nd_opt_hdr *)(na + 1);
+ opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
+ opt->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3);
+ memcpy(opt + 1, ifp->hwaddr, ifp->hwlen);
+ }
+
+ iaf->na_count = 0;
+ free(iaf->na);
+ iaf->na = na;
+ eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iaf);
+ ipv6nd_sendadvertisement(iaf);
+}
+
void
ipv6nd_expire(struct interface *ifp, uint32_t seconds)
{
return;
}
}
+ ipv6nd_advertise(ia);
}
}
if (rap->reachable > MAX_REACHABLE_TIME)
rap->reachable = 0;
}
- if (nd_ra->nd_ra_retransmit)
- rap->retrans = ntohl(nd_ra->nd_ra_retransmit);
+ if (nd_ra->nd_ra_retransmit) {
+ struct rs_state *state = RS_STATE(ifp);
+
+ state->retrans = rap->retrans = ntohl(nd_ra->nd_ra_retransmit);
+ }
if (rap->lifetime)
rap->expired = 0;
rap->hasdns = 0;
return;
}
+ state->retrans = RETRANS_TIMER;
state->rsprobes = 0;
ipv6nd_sendrsprobe(ifp);
}
struct nd_router_solicit *rs;
size_t rslen;
int rsprobes;
+ uint32_t retrans;
};
#define RS_STATE(a) ((struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND])
+#define RS_CSTATE(a) ((const struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND])
#define RS_STATE_RUNNING(a) (ipv6nd_hasra((a)) && ipv6nd_dadcompleted((a)))
#ifndef MAX_RTR_SOLICITATION_DELAY
#define MAX_UNICAST_SOLICIT 3 /* 3 transmissions */
#define RTR_SOLICITATION_INTERVAL 4 /* seconds */
#define MAX_RTR_SOLICITATIONS 3 /* times */
+#define MAX_NEIGHBOR_ADVERTISEMENT 3 /* 3 transmissions */
#endif
/* On carrier up, expire known routers after RTR_CARRIER_EXPIRE seconds. */
int ipv6nd_hasradhcp(const struct interface *);
void ipv6nd_handleifa(int, struct ipv6_addr *, pid_t);
int ipv6nd_dadcompleted(const struct interface *);
+void ipv6nd_advertise(struct ipv6_addr *);
void ipv6nd_expire(struct interface *, uint32_t);
void ipv6nd_drop(struct interface *);
void ipv6nd_neighbour(struct dhcpcd_ctx *, struct in6_addr *, int);