Index: sys/net/if_llatbl.c =================================================================== --- sys/net/if_llatbl.c +++ sys/net/if_llatbl.c @@ -708,6 +708,13 @@ lltable_unlink_entry(llt, lle); IF_AFDATA_WUNLOCK(ifp); +#ifdef INET6 + /* Leave the solicited multicast group for dst. */ + if ((lle->la_flags & (LLE_PUB | LLE_STATIC | LLE_IFADDR)) == + (LLE_PUB | LLE_STATIC) + && l3addr->sa_family == AF_INET6) + in6_handle_proxy_ndp_mc(RTM_DELETE, ifp, l3addr); +#endif llt->llt_delete_entry(llt, lle); @@ -978,6 +985,12 @@ &((struct sockaddr_in *)dst)->sin_addr, (u_char *)LLADDR(dl)); #endif +#ifdef INET6 + /* Join the solicited multicast group for dst. */ + if ((laflags & (LLE_PUB | LLE_STATIC)) == (LLE_PUB | LLE_STATIC) + && dst->sa_family == AF_INET6) + in6_handle_proxy_ndp_mc(rtm->rtm_type, ifp, dst); +#endif break; Index: sys/netinet6/in6.c =================================================================== --- sys/netinet6/in6.c +++ sys/netinet6/in6.c @@ -2614,3 +2614,116 @@ free(*nam, M_SONAME); *nam = (struct sockaddr *)sin6_p; } + +/* + * Join/leave the solicited multicast groups for proxy NDP entries. + */ +static struct mtx pndp_mc_list_mtx; +MTX_SYSINIT(pndp_mc_list_mtx, &pndp_mc_list_mtx, "pndp_mc_listmtx", MTX_DEF); +#define PNDP_MC_LIST_LOCK() mtx_lock(&pndp_mc_list_mtx) +#define PNDP_MC_LIST_UNLOCK() mtx_unlock(&pndp_mc_list_mtx) + +static LIST_HEAD(in6_pndp_mc_list, in6_multi_mship) pndp_mc_list; + +static void in6_pndp_mc_list_add(struct in6_multi_mship *imm); +static void in6_pndp_mc_list_delete(struct in6_multi *inm); + +void +in6_handle_proxy_ndp_mc(int cmd, struct ifnet *ifp, const struct sockaddr *sa) +{ + const struct sockaddr_in6 *sin6; + struct in6_multi_mship *imm; + struct in6_multi *inm; + struct in6_addr mltaddr; + struct epoch_tracker et; + char ip6buf[INET6_ADDRSTRLEN]; + int error; + + sin6 = (const struct sockaddr_in6 *)sa; + bzero(&mltaddr, sizeof(struct in6_addr)); + mltaddr.s6_addr32[0] = IPV6_ADDR_INT32_MLL; + mltaddr.s6_addr32[2] = htonl(1); + mltaddr.s6_addr32[3] = sin6->sin6_addr.s6_addr32[3]; + mltaddr.s6_addr8[12] = 0xff; + if ((error = in6_setscope(&mltaddr, ifp, NULL)) != 0) { + nd6log((LOG_ERR, "%s: in6_setscope failed\n", __func__)); + return; + } + if (cmd == RTM_ADD) { + imm = in6_joingroup_legacy(ifp, &mltaddr, &error, 0); + if (imm != NULL) + in6_pndp_mc_list_add(imm); + else { + nd6log((LOG_WARNING, + "%s: in6_joingroup_legacy failed for %s on %s " + "(errno=%d)\n", __func__, + ip6_sprintf(ip6buf, &mltaddr), if_name(ifp), + error)); + return; + } + } else { + NET_EPOCH_ENTER(et); + inm = in6m_lookup(ifp, &mltaddr); + NET_EPOCH_EXIT(et); + if (inm != NULL) { + in6_pndp_mc_list_delete(inm); + in6_leavegroup(inm, NULL); + } else { + nd6log((LOG_WARNING, + "%s: in6_lookup failed for %s on %s\n", + __func__, ip6_sprintf(ip6buf, &mltaddr), + if_name(ifp))); + } + } +} + +void +in6_purge_proxy_ndp_mc(struct ifnet *ifp) +{ + struct in6_pndp_mc_list removed; + struct in6_multi_mship *imm; + + LIST_INIT(&removed); + PNDP_MC_LIST_LOCK(); + LIST_FOREACH(imm, &pndp_mc_list, i6mm_chain) { + if (imm->i6mm_maddr->in6m_ifp == ifp) { + LIST_REMOVE(imm, i6mm_chain); + LIST_INSERT_HEAD(&removed, imm, i6mm_chain); + } + } + PNDP_MC_LIST_UNLOCK(); + + while ((imm = LIST_FIRST(&removed)) != NULL) { + LIST_REMOVE(imm, i6mm_chain); + in6_leavegroup(imm->i6mm_maddr, NULL); + free(imm, M_IP6MADDR); + } +} + +static void +in6_pndp_mc_list_add(struct in6_multi_mship *imm) +{ + PNDP_MC_LIST_LOCK(); + LIST_INSERT_HEAD(&pndp_mc_list, imm, i6mm_chain); + PNDP_MC_LIST_UNLOCK(); +} + +static void +in6_pndp_mc_list_delete(struct in6_multi *inm) +{ + struct in6_multi_mship *imm; + + PNDP_MC_LIST_LOCK(); + LIST_FOREACH(imm, &pndp_mc_list, i6mm_chain) { + if (imm->i6mm_maddr == inm) { + LIST_REMOVE(imm, i6mm_chain); + break; + } + } + PNDP_MC_LIST_UNLOCK(); + if (imm != NULL) + free(imm, M_IP6MADDR); + else + nd6log((LOG_WARNING, + "in6_multi %p not found on proxy ndp list.\n", inm)); +} Index: sys/netinet6/in6_ifattach.c =================================================================== --- sys/netinet6/in6_ifattach.c +++ sys/netinet6/in6_ifattach.c @@ -861,6 +861,7 @@ { struct in6_multi_head inmh; + in6_purge_proxy_ndp_mc(ifp); SLIST_INIT(&inmh); IN6_MULTI_LOCK(); IN6_MULTI_LIST_LOCK(); Index: sys/netinet6/in6_var.h =================================================================== --- sys/netinet6/in6_var.h +++ sys/netinet6/in6_var.h @@ -916,6 +916,9 @@ int in6_src_ioctl(u_long, caddr_t); void in6_newaddrmsg(struct in6_ifaddr *, int); + +void in6_handle_proxy_ndp_mc(int, struct ifnet *, const struct sockaddr *); +void in6_purge_proxy_ndp_mc(struct ifnet *); /* * Extended API for IPv6 FIB support. */ Index: sys/netinet6/nd6_nbr.c =================================================================== --- sys/netinet6/nd6_nbr.c +++ sys/netinet6/nd6_nbr.c @@ -255,33 +255,29 @@ /* (2) check. */ proxy = 0; if (ifa == NULL) { - struct sockaddr_dl rt_gateway; - struct rt_addrinfo info; - struct sockaddr_in6 dst6; - - bzero(&dst6, sizeof(dst6)); - dst6.sin6_len = sizeof(struct sockaddr_in6); - dst6.sin6_family = AF_INET6; - dst6.sin6_addr = taddr6; - - bzero(&rt_gateway, sizeof(rt_gateway)); - rt_gateway.sdl_len = sizeof(rt_gateway); - bzero(&info, sizeof(info)); - info.rti_info[RTAX_GATEWAY] = (struct sockaddr *)&rt_gateway; - - if (rib_lookup_info(ifp->if_fib, (struct sockaddr *)&dst6, - 0, 0, &info) == 0) { - if ((info.rti_flags & RTF_ANNOUNCE) != 0 && - rt_gateway.sdl_family == AF_LINK) { - /* - * proxy NDP for single entry - */ - proxydl = *SDL(&rt_gateway); + struct llentry *ln; + + ln = nd6_lookup(&taddr6, LLE_SF(AF_INET6, 0), ifp); + if (ln != NULL) { +#define LLE_PROXY_ND (LLE_PUB | LLE_STATIC | LLE_VALID) + /* + * ifa == NULL here should be sufficient for proxy, + * but we can check by !LLE_IFADDR also. + */ + if ((ln->la_flags & (LLE_PROXY_ND | LLE_IFADDR)) + == LLE_PROXY_ND) { + link_init_sdl(ifp, (struct sockaddr *)&proxydl, + ifp->if_type); + proxydl.sdl_alen = ifp->if_addrlen; + bcopy(ln->ll_addr, &proxydl.sdl_data, + ifp->if_addrlen); ifa = (struct ifaddr *)in6ifa_ifpforlinklocal( ifp, IN6_IFF_NOTREADY|IN6_IFF_ANYCAST); if (ifa) proxy = 1; +#undef LLE_PROXY_ND } + LLE_RUNLOCK(ln); } } if (ifa == NULL) {