From 05b87f8802c6daba83259f7c0561364568f75dbc Mon Sep 17 00:00:00 2001 From: florian Date: Mon, 23 Jul 2018 06:14:14 +0000 Subject: [PATCH] Handle duplicate address detection failures. We get notified when duplication is detected on the route socket. For privacy addresses simply generate a new random address. If we have soii enabled increase the dad counter on the prefix and generate a new address. For eui64 addresses nothing can be done. --- sbin/slaacd/engine.c | 116 ++++++++++++++++++++++++++++++----------- sbin/slaacd/frontend.c | 67 +++++++++++++++++++++++- sbin/slaacd/slaacd.c | 4 +- sbin/slaacd/slaacd.h | 8 ++- 4 files changed, 161 insertions(+), 34 deletions(-) diff --git a/sbin/slaacd/engine.c b/sbin/slaacd/engine.c index 961e1b115b6..b2955ea2ff5 100644 --- a/sbin/slaacd/engine.c +++ b/sbin/slaacd/engine.c @@ -1,4 +1,4 @@ -/* $OpenBSD: engine.c,v 1.27 2018/06/20 14:55:29 florian Exp $ */ +/* $OpenBSD: engine.c,v 1.28 2018/07/23 06:14:14 florian Exp $ */ /* * Copyright (c) 2017 Florian Obser @@ -106,6 +106,7 @@ enum proposal_state { PROPOSAL_CONFIGURED, PROPOSAL_NEARLY_EXPIRED, PROPOSAL_WITHDRAWN, + PROPOSAL_DUPLICATED, }; const char* proposal_state_name[] = { @@ -114,6 +115,7 @@ const char* proposal_state_name[] = { "CONFIGURED", "NEARLY_EXPIRED", "WITHDRAWN", + "DUPLICATED", }; const char* rpref_name[] = { @@ -130,6 +132,7 @@ struct radv_prefix { int autonomous; uint32_t vltime; uint32_t pltime; + int dad_counter; }; struct radv_rdns { @@ -261,11 +264,10 @@ struct dfr_proposal *find_dfr_proposal_by_id(struct slaacd_iface *, int64_t); struct dfr_proposal *find_dfr_proposal_by_gw(struct slaacd_iface *, struct sockaddr_in6 *); -void find_prefix(struct slaacd_iface *, struct - address_proposal *, struct radv **, struct - radv_prefix **); +struct radv_prefix *find_prefix(struct radv *, struct radv_prefix *); int engine_imsg_compose_main(int, pid_t, void *, uint16_t); uint32_t real_lifetime(struct timespec *, uint32_t); +void merge_dad_couters(struct radv *, struct radv *); struct imsgev *iev_frontend; struct imsgev *iev_main; @@ -392,6 +394,8 @@ engine_dispatch_frontend(int fd, short event, void *bula) struct dfr_proposal *dfr_proposal = NULL; struct imsg_del_addr del_addr; struct imsg_del_route del_route; + struct imsg_dup_addr dup_addr; + struct timeval tv; ssize_t n; int shut = 0; #ifndef SMALL @@ -546,6 +550,31 @@ engine_dispatch_frontend(int fd, short event, void *bula) start_probe(iface); } break; + case IMSG_DUP_ADDRESS: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(dup_addr)) + fatal("%s: IMSG_DUP_ADDRESS wrong length: %d", + __func__, imsg.hdr.len); + memcpy(&dup_addr, imsg.data, sizeof(dup_addr)); + iface = get_slaacd_iface_by_id(dup_addr.if_index); + if (iface == NULL) { + log_debug("IMSG_DUP_ADDRESS: unknown interface" + ", ignoring"); + break; + } + + addr_proposal = find_address_proposal_by_addr(iface, + &dup_addr.addr); + + if (addr_proposal) { + /* XXX should we inform netcfgd? */ + addr_proposal->state = PROPOSAL_DUPLICATED; + tv.tv_sec = 0; + tv.tv_usec = arc4random_uniform(1000000); + addr_proposal->next_timeout = 0; + evtimer_add(&addr_proposal->timer, &tv); + } + break; default: log_debug("%s: unexpected imsg %d", __func__, imsg.hdr.type); @@ -1261,7 +1290,6 @@ gen_addr(struct slaacd_iface *iface, struct radv_prefix *prefix, struct { SHA2_CTX ctx; struct in6_addr iid; - int dad_counter = 0; /* XXX not used */ int i; u_int8_t digest[SHA512_DIGEST_LENGTH]; @@ -1293,7 +1321,8 @@ gen_addr(struct slaacd_iface *iface, struct radv_prefix *prefix, struct sizeof(prefix->prefix)); SHA512Update(&ctx, &iface->hw_address, sizeof(iface->hw_address)); - SHA512Update(&ctx, &dad_counter, sizeof(dad_counter)); + SHA512Update(&ctx, &prefix->dad_counter, + sizeof(prefix->dad_counter)); SHA512Update(&ctx, addr_proposal->soiikey, sizeof(addr_proposal->soiikey)); SHA512Final(digest, &ctx); @@ -1601,13 +1630,16 @@ void update_iface_ra(struct slaacd_iface *iface, struct radv *ra) struct address_proposal *addr_proposal; struct dfr_proposal *dfr_proposal, *tmp; uint32_t remaining_lifetime; - int found, found_privacy; + int found, found_privacy, duplicate_found; const char *hbuf; if ((old_ra = find_ra(iface, &ra->from)) == NULL) LIST_INSERT_HEAD(&iface->radvs, ra, entries); else { LIST_REPLACE(old_ra, ra, entries); + + merge_dad_couters(old_ra, ra); + free_ra(old_ra); } if (ra->router_lifetime == 0) { @@ -1674,6 +1706,8 @@ void update_iface_ra(struct slaacd_iface *iface, struct radv *ra) continue; found = 0; found_privacy = 0; + duplicate_found = 0; + LIST_FOREACH(addr_proposal, &iface->addr_proposals, entries) { if (prefix->prefix_len == @@ -1699,7 +1733,9 @@ void update_iface_ra(struct slaacd_iface *iface, struct radv *ra) * expires */ if (addr_proposal->state != - PROPOSAL_NEARLY_EXPIRED) + PROPOSAL_NEARLY_EXPIRED && + addr_proposal->state != + PROPOSAL_DUPLICATED) found_privacy = 1; if (!iface->autoconfprivacy) @@ -1715,6 +1751,12 @@ void update_iface_ra(struct slaacd_iface *iface, struct radv *ra) continue; } + if (addr_proposal->state == + PROPOSAL_DUPLICATED) { + duplicate_found = 1; + continue; + } + found = 1; remaining_lifetime = @@ -1751,6 +1793,12 @@ void update_iface_ra(struct slaacd_iface *iface, struct radv *ra) } } + if (!found && duplicate_found && iface->soii) { + prefix->dad_counter++; + log_debug("%s dad_counter: %d", + __func__, prefix->dad_counter); + } + if (!found && (iface->soii || prefix->prefix_len <= 64)) /* new proposal */ @@ -2117,6 +2165,13 @@ address_proposal_timeout(int fd, short events, void *arg) log_debug("%s: scheduling new timeout in %llds.%06ld", __func__, tv.tv_sec, tv.tv_usec); break; + case PROPOSAL_DUPLICATED: + engine_imsg_compose_frontend(IMSG_CTL_SEND_SOLICITATION, + 0, &addr_proposal->if_index, + sizeof(addr_proposal->if_index)); + log_debug("%s: address duplicated", + __func__); + break; default: log_debug("%s: unhandled state: %s", __func__, proposal_state_name[addr_proposal->state]); @@ -2304,32 +2359,19 @@ find_dfr_proposal_by_gw(struct slaacd_iface *iface, struct sockaddr_in6 } -/* XXX currently unused */ -void -find_prefix(struct slaacd_iface *iface, struct address_proposal *addr_proposal, - struct radv **result_ra, struct radv_prefix **result_prefix) +struct radv_prefix * +find_prefix(struct radv *ra, struct radv_prefix *prefix) { - struct radv *ra; - struct radv_prefix *prefix; - uint32_t lifetime, max_lifetime = 0; + struct radv_prefix *result; - *result_ra = NULL; - *result_prefix = NULL; - LIST_FOREACH(ra, &iface->radvs, entries) { - LIST_FOREACH(prefix, &ra->prefixes, entries) { - if (memcmp(&prefix->prefix, &addr_proposal->prefix, - sizeof(addr_proposal->prefix)) != 0) - continue; - lifetime = real_lifetime(&ra->uptime, - prefix->vltime); - if (lifetime > max_lifetime) { - max_lifetime = lifetime; - *result_ra = ra; - *result_prefix = prefix; - } - } + LIST_FOREACH(result, &ra->prefixes, entries) { + if (memcmp(&result->prefix, &prefix->prefix, + sizeof(prefix->prefix)) == 0 && result->prefix_len == + prefix->prefix_len) + return (result); } + return (NULL); } uint32_t @@ -2350,3 +2392,17 @@ real_lifetime(struct timespec *received_uptime, uint32_t ltime) return (remaining); } + +void +merge_dad_couters(struct radv *old_ra, struct radv *new_ra) +{ + + struct radv_prefix *old_prefix, *new_prefix; + + LIST_FOREACH(old_prefix, &old_ra->prefixes, entries) { + if (!old_prefix->dad_counter) + continue; + if ((new_prefix = find_prefix(new_ra, old_prefix)) != NULL) + new_prefix->dad_counter = old_prefix->dad_counter; + } +} diff --git a/sbin/slaacd/frontend.c b/sbin/slaacd/frontend.c index 96d69b8b661..cdbe7d11b5b 100644 --- a/sbin/slaacd/frontend.c +++ b/sbin/slaacd/frontend.c @@ -1,4 +1,4 @@ -/* $OpenBSD: frontend.c,v 1.20 2018/06/06 14:08:28 florian Exp $ */ +/* $OpenBSD: frontend.c,v 1.21 2018/07/23 06:14:14 florian Exp $ */ /* * Copyright (c) 2017 Florian Obser @@ -73,6 +73,7 @@ void get_lladdr(char *, struct ether_addr *, struct sockaddr_in6 *); void send_solicitation(uint32_t); #ifndef SMALL void update_autoconf_addresses(uint32_t, char*); +const char *flags_to_str(int); #endif /* SMALL */ struct imsgev *iev_main; @@ -592,6 +593,31 @@ update_autoconf_addresses(uint32_t if_index, char* if_name) } freeifaddrs(ifap); } + +const char* +flags_to_str(int flags) +{ + static char buf[sizeof(" anycast tentative duplicated detached " + "deprecated autoconf autoconfprivacy")]; + + buf[0] = '\0'; + if (flags & IN6_IFF_ANYCAST) + (void)strlcat(buf, " anycast", sizeof(buf)); + if (flags & IN6_IFF_TENTATIVE) + (void)strlcat(buf, " tentative", sizeof(buf)); + if (flags & IN6_IFF_DUPLICATED) + (void)strlcat(buf, " duplicated", sizeof(buf)); + if (flags & IN6_IFF_DETACHED) + (void)strlcat(buf, " detached", sizeof(buf)); + if (flags & IN6_IFF_DEPRECATED) + (void)strlcat(buf, " deprecated", sizeof(buf)); + if (flags & IN6_IFF_AUTOCONF) + (void)strlcat(buf, " autoconf", sizeof(buf)); + if (flags & IN6_IFF_PRIVACY) + (void)strlcat(buf, " autoconfprivacy", sizeof(buf)); + + return (buf); +} #endif /* SMALL */ void @@ -673,7 +699,10 @@ handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info) struct imsg_proposal_ack proposal_ack; struct imsg_del_addr del_addr; struct imsg_del_route del_route; + struct imsg_dup_addr dup_addr; struct sockaddr_rtlabel *rl; + struct sockaddr_in6 *sin6; + struct in6_ifreq ifr6; struct in6_addr *in6; int64_t id, pid; int xflags, if_index; @@ -728,6 +757,42 @@ handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info) ifm->ifm_index); } break; + case RTM_CHGADDRATTR: + ifm = (struct if_msghdr *)rtm; + if_name = if_indextoname(ifm->ifm_index, ifnamebuf); + if (rtm->rtm_addrs & RTA_IFA && rti_info[RTAX_IFA]->sa_family + == AF_INET6) { + sin6 = (struct sockaddr_in6 *) rti_info[RTAX_IFA]; + + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) + break; + + memset(&ifr6, 0, sizeof(ifr6)); + (void) strlcpy(ifr6.ifr_name, if_name, + sizeof(ifr6.ifr_name)); + memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr)); + + if (ioctl(ioctlsock, SIOCGIFAFLAG_IN6, (caddr_t)&ifr6) + < 0) { + log_warn("SIOCGIFAFLAG_IN6"); + break; + } + +#ifndef SMALL + log_debug("RTM_CHGADDRATTR: %s -%s", + sin6_to_str(sin6), + flags_to_str(ifr6.ifr_ifru.ifru_flags6)); +#endif /* SMALL */ + + if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DUPLICATED) { + dup_addr.if_index = ifm->ifm_index; + dup_addr.addr = *sin6; + frontend_imsg_compose_engine(IMSG_DUP_ADDRESS, + 0, 0, &dup_addr, sizeof(dup_addr)); + } + + } + break; case RTM_DELETE: ifm = (struct if_msghdr *)rtm; if ((rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY | RTA_LABEL)) != diff --git a/sbin/slaacd/slaacd.c b/sbin/slaacd/slaacd.c index 6778659fa10..633864bf566 100644 --- a/sbin/slaacd/slaacd.c +++ b/sbin/slaacd/slaacd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: slaacd.c,v 1.25 2018/07/09 07:41:15 florian Exp $ */ +/* $OpenBSD: slaacd.c,v 1.26 2018/07/23 06:14:14 florian Exp $ */ /* * Copyright (c) 2017 Florian Obser @@ -275,7 +275,7 @@ main(int argc, char *argv[]) rtfilter = ROUTE_FILTER(RTM_IFINFO) | ROUTE_FILTER(RTM_NEWADDR) | ROUTE_FILTER(RTM_DELADDR) | ROUTE_FILTER(RTM_PROPOSAL) | - ROUTE_FILTER(RTM_DELETE); + ROUTE_FILTER(RTM_DELETE) | ROUTE_FILTER(RTM_CHGADDRATTR); if (setsockopt(frontend_routesock, PF_ROUTE, ROUTE_MSGFILTER, &rtfilter, sizeof(rtfilter)) < 0) fatal("setsockopt(ROUTE_MSGFILTER)"); diff --git a/sbin/slaacd/slaacd.h b/sbin/slaacd/slaacd.h index 910be91e687..1659c564abf 100644 --- a/sbin/slaacd/slaacd.h +++ b/sbin/slaacd/slaacd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: slaacd.h,v 1.17 2018/07/09 07:41:15 florian Exp $ */ +/* $OpenBSD: slaacd.h,v 1.18 2018/07/23 06:14:14 florian Exp $ */ /* * Copyright (c) 2017 Florian Obser @@ -74,6 +74,7 @@ enum imsg_type { IMSG_FAKE_ACK, IMSG_CONFIGURE_DFR, IMSG_WITHDRAW_DFR, + IMSG_DUP_ADDRESS, }; enum { @@ -202,6 +203,11 @@ struct imsg_ra { uint8_t packet[1500]; }; +struct imsg_dup_addr { + uint32_t if_index; + struct sockaddr_in6 addr; +}; + /* slaacd.c */ void imsg_event_add(struct imsgev *); int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t, -- 2.20.1