Index: sys/net/if.c =================================================================== --- sys/net/if.c +++ sys/net/if.c @@ -1017,6 +1017,16 @@ { struct ifaddr *ifa; +#ifdef INET6 + /* + * Need to leave multicast addresses of proxy NDP llentries + * before in6_purgeifaddr() because the llentries are keys + * for in6_multi objects of proxy NDP entries. + * in6_purgeifaddr()s clean up llentries including proxy NDPs + * then we would lose the keys if they are called earlier. + */ + in6_purge_proxy_ndp(ifp); +#endif while (1) { struct epoch_tracker et; Index: sys/net/if_llatbl.h =================================================================== --- sys/net/if_llatbl.h +++ sys/net/if_llatbl.h @@ -168,7 +168,8 @@ struct lltable { SLIST_ENTRY(lltable) llt_link; sa_family_t llt_af; - uint8_t llt_spare[3]; + uint8_t llt_flags; + uint8_t llt_spare[2]; int llt_hsize; int llt_entries; int llt_maxentries; @@ -194,6 +195,11 @@ MALLOC_DECLARE(M_LLTABLE); +/* + * LLtable flags + */ +#define LLT_ADDEDPROXY 0x01 /* added a proxy llentry */ + /* * LLentry flags */ @@ -246,6 +252,9 @@ void lltable_free_entry(struct lltable *llt, struct llentry *lle); int lltable_delete_addr(struct lltable *llt, u_int flags, const struct sockaddr *l3addr); +void lltable_flags_free(struct lltable *llt, u_int flags, + void (*cleanup_cb)(struct lltable *llt, struct llentry *lle, void *arg), + void *farg); int lltable_link_entry(struct lltable *llt, struct llentry *lle); int lltable_unlink_entry(struct lltable *llt, struct llentry *lle); void lltable_link_child_entry(struct llentry *parent_lle, struct llentry *child_lle); Index: sys/net/if_llatbl.c =================================================================== --- sys/net/if_llatbl.c +++ sys/net/if_llatbl.c @@ -730,6 +730,53 @@ LLTABLE_LIST_RUNLOCK(); } +/* + * Free llentries that the flags bit in llentry.la_flags set. + * Call cleanup_cb unless NULL before free. + */ +struct flags_match_data { + struct llentries dchain; + u_int flags; +}; + +static int +lltable_flags_free_cb(struct lltable *llt, struct llentry *lle, void *farg) +{ + struct flags_match_data *fmd; + + fmd = (struct flags_match_data *)farg; + if ((lle->la_flags & fmd->flags) == fmd->flags) { + LLE_WLOCK(lle); + CK_LIST_INSERT_HEAD(&fmd->dchain, lle, lle_chain); + } + + return (0); +} + +void +lltable_flags_free(struct lltable *llt, u_int flags, + void (*cleanup_cb)(struct lltable *llt, struct llentry *lle, void *arg), + void *farg) +{ + struct llentry *lle, *next; + struct flags_match_data fmd; + + bzero(&fmd, sizeof(fmd)); + CK_LIST_INIT(&fmd.dchain); + fmd.flags = flags; + + IF_AFDATA_WLOCK(llt->llt_ifp); + lltable_foreach_lle(llt, lltable_flags_free_cb, &fmd); + llentries_unlink(llt, &fmd.dchain); + IF_AFDATA_WUNLOCK(llt->llt_ifp); + + CK_LIST_FOREACH_SAFE(lle, &fmd.dchain, lle_chain, next) { + if (cleanup_cb != NULL) + cleanup_cb(llt, lle, farg); + lltable_free_entry(llt, lle); + } +} + struct lltable * lltable_allocate_htbl(uint32_t hsize) { @@ -955,6 +1002,9 @@ lltable_unlink_entry(llt, lle_tmp); } lltable_link_entry(llt, lle); + if ((lle->la_flags & LLE_PUB) != 0 && + (llt->llt_flags & LLT_ADDEDPROXY) == 0) + llt->llt_flags |= LLT_ADDEDPROXY; IF_AFDATA_WUNLOCK(ifp); if (lle_tmp != NULL) { Index: sys/netinet6/in6.c =================================================================== --- sys/netinet6/in6.c +++ sys/netinet6/in6.c @@ -162,6 +162,9 @@ static int in6_broadcast_ifa(struct ifnet *, struct in6_aliasreq *, struct in6_ifaddr *, int); +static void in6_handle_proxy_ndp_mc(int, struct ifnet *, + const struct in6_addr *); + #define ifa2ia6(ifa) ((struct in6_ifaddr *)(ifa)) #define ia62ifa(ia6) (&((ia6)->ia_ifa)) @@ -741,6 +744,26 @@ return (imm); } + +static int +in6_solicited_node_maddr(struct in6_addr *maddr, + struct ifnet *ifp, const struct in6_addr *base) +{ + int error; + + bzero(maddr, sizeof(struct in6_addr)); + maddr->s6_addr32[0] = IPV6_ADDR_INT32_MLL; + maddr->s6_addr32[2] = htonl(1); + maddr->s6_addr32[3] = base->s6_addr32[3]; + maddr->s6_addr8[12] = 0xff; + if ((error = in6_setscope(maddr, ifp, NULL)) != 0) { + /* XXX: should not happen */ + log(LOG_ERR, "%s: in6_setscope failed\n", __func__); + } + + return error; +} + /* * Join necessary multicast groups. Factored out from in6_update_ifa(). * This entire work should only be done once, for the default FIB. @@ -757,16 +780,9 @@ KASSERT(in6m_sol != NULL, ("%s: in6m_sol is NULL", __func__)); /* Join solicited multicast addr for new host id. */ - bzero(&mltaddr, sizeof(struct in6_addr)); - mltaddr.s6_addr32[0] = IPV6_ADDR_INT32_MLL; - mltaddr.s6_addr32[2] = htonl(1); - mltaddr.s6_addr32[3] = ifra->ifra_addr.sin6_addr.s6_addr32[3]; - mltaddr.s6_addr8[12] = 0xff; - if ((error = in6_setscope(&mltaddr, ifp, NULL)) != 0) { - /* XXX: should not happen */ - log(LOG_ERR, "%s: in6_setscope failed\n", __func__); + if ((error = in6_solicited_node_maddr(&mltaddr, ifp, + &ifra->ifra_addr.sin6_addr)) != 0) goto cleanup; - } delay = error = 0; if ((flags & IN6_IFAUPDATE_DADDELAY)) { /* @@ -2285,6 +2301,11 @@ { lle->la_flags |= LLE_DELETED; + + /* Leave the solicited multicast group. */ + if ((lle->la_flags & LLE_PUB) != 0) + in6_handle_proxy_ndp_mc(RTM_DELETE, llt->llt_ifp, + &lle->r_l3addr.addr6); EVENTHANDLER_INVOKE(lle_event, lle, LLENTRY_DELETED); #ifdef DIAGNOSTIC log(LOG_INFO, "ifaddr cache = %p is deleted\n", lle); @@ -2463,7 +2484,10 @@ static void in6_lltable_post_resolved(struct lltable *llt, struct llentry *lle) { - /* Handle proxy NDP entries (not yet). */ + /* Join the solicited multicast group for dst. */ + if ((lle->la_flags & LLE_PUB) == LLE_PUB) + in6_handle_proxy_ndp_mc(RTM_ADD, llt->llt_ifp, + &lle->r_l3addr.addr6); } static struct lltable * @@ -2621,3 +2645,82 @@ free(*nam, M_SONAME); *nam = (struct sockaddr *)sin6_p; } + +/* + * Join/leave the solicited multicast groups for proxy NDP entries. + */ +static void +in6_leavegroup_maddr(struct ifnet *ifp, struct in6_addr *maddr, + struct in6_mfilter *imf) +{ + struct epoch_tracker et; + struct in6_multi *inm; + char ip6buf[INET6_ADDRSTRLEN]; + + NET_EPOCH_ENTER(et); + inm = in6m_lookup(ifp, maddr); + NET_EPOCH_EXIT(et); + if (inm != NULL) + in6_leavegroup(inm, NULL); + else + nd6log((LOG_WARNING, "%s: in6m_lookup failed for %s on %s\n", + __func__, ip6_sprintf(ip6buf, maddr), if_name(ifp))); +} + +static void +in6_handle_proxy_ndp_mc(int cmd, struct ifnet *ifp, const struct in6_addr *dst) +{ + struct in6_multi *inm; + struct in6_addr mltaddr; + char ip6buf[INET6_ADDRSTRLEN]; + int error; + + if (in6_solicited_node_maddr(&mltaddr, ifp, dst) != 0) + return; /* error logged in in6_solicited_node_maddr. */ + + if (cmd == RTM_ADD) { + error = in6_joingroup(ifp, &mltaddr, NULL, &inm, 0); + if (error != 0) { + nd6log((LOG_WARNING, + "%s: in6_joingroup failed for %s on %s " + "(errno=%d)\n", __func__, + ip6_sprintf(ip6buf, &mltaddr), if_name(ifp), + error)); + return; + } + } else + in6_leavegroup_maddr(ifp, &mltaddr, NULL); +} + +static void +in6_purge_proxy_ndp_cb(struct lltable *llt, struct llentry *lle, void *arg) +{ + struct ifnet *ifp; + struct in6_addr maddr; + + ifp = llt->llt_ifp; + if (in6_solicited_node_maddr(&maddr, ifp, &lle->r_l3addr.addr6) != 0) + return; + + in6_leavegroup_maddr(ifp, &maddr, NULL); +} + +void +in6_purge_proxy_ndp(struct ifnet *ifp) +{ + struct lltable *llt; + + llt = LLTABLE6(ifp); + IF_AFDATA_WLOCK(ifp); + if ((llt->llt_flags & LLT_ADDEDPROXY) == 0) { + IF_AFDATA_WUNLOCK(ifp); + return; + } + IF_AFDATA_WUNLOCK(ifp); + + /* + * Ever added proxy ndp entries, leave solicited node multicast + * before freeing the llentry. + */ + lltable_flags_free(llt, LLE_PUB, in6_purge_proxy_ndp_cb, NULL); +} Index: sys/netinet6/in6_var.h =================================================================== --- sys/netinet6/in6_var.h +++ sys/netinet6/in6_var.h @@ -916,6 +916,8 @@ int in6_src_ioctl(u_long, caddr_t); void in6_newaddrmsg(struct in6_ifaddr *, int); + +void in6_purge_proxy_ndp(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,23 @@ /* (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) { + if ((ln->la_flags & (LLE_PUB | LLE_VALID)) + == (LLE_PUB | LLE_VALID)) { + 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; } + LLE_RUNLOCK(ln); } } if (ifa == NULL) { Index: tests/sys/netinet6/Makefile =================================================================== --- tests/sys/netinet6/Makefile +++ tests/sys/netinet6/Makefile @@ -15,7 +15,8 @@ output6 \ lpm6 \ fibs6 \ - ndp + ndp \ + proxy_ndp TEST_METADATA.output6+= required_programs="python" ${PACKAGE}FILES+= exthdr.py Index: tests/sys/netinet6/proxy_ndp.sh =================================================================== --- /dev/null +++ tests/sys/netinet6/proxy_ndp.sh @@ -0,0 +1,222 @@ +#!/usr/bin/env atf-sh +#- +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2022 KUROSAWA Takahiro +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ +# + +. $(atf_get_srcdir)/../common/vnet.subr + +atf_test_case "pndp_add_gu_success" "cleanup" +pndp_add_gu_success_head() { + atf_set descr 'Test proxy ndp record addition' + atf_set require.user root +} + +pndp_add_gu_success_body() { + + vnet_init + + jname="v6t-pndp_add_success" + + epair0=$(vnet_mkepair) + + vnet_mkjail ${jname} ${epair0}a + jexec ${jname} ndp -i ${epair0}a -- -disabled + jexec ${jname} ifconfig ${epair0}a up + + jexec ${jname} ifconfig ${epair0}a inet6 2001:db8::1/64 + proxy_mac=`jexec ${jname} ifconfig ${epair0}a ether | awk '$1~/ether/{print$2}'` + + # wait for DAD to complete + while [ `jexec ${jname} ifconfig | grep inet6 | grep -c tentative` != "0" ]; do + sleep 0.1 + done + + atf_check jexec ${jname} ndp -s 2001:db8::2 ${proxy_mac} proxy + while [ `jexec ${jname} ifmcstat | grep -c undefined` != "0" ]; do + sleep 0.1 + done + + # checking the output of ndp -an is covered by ndp.sh. + # we check the output of ifmcstat output here. + t=`jexec ${jname} ifmcstat -i ${epair0}a -f inet6 | grep -A1 'group ff02::1:ff00:2'` + atf_check -o match:'mcast-macaddr 33:33:ff:00:00:02' echo $t +} + +pndp_add_gu_success_cleanup() { + vnet_cleanup +} + +atf_test_case "pndp_del_gu_success" "cleanup" +pndp_del_gu_success_head() { + atf_set descr 'Test proxy ndp record deletion' + atf_set require.user root +} + +pndp_del_gu_success_body() { + + vnet_init + + jname="v6t-pndp_del_gu_success" + + epair0=$(vnet_mkepair) + + vnet_mkjail ${jname} ${epair0}a + + jexec ${jname} ndp -i ${epair0}a -- -disabled + jexec ${jname} ifconfig ${epair0}a up + + jexec ${jname} ifconfig ${epair0}a inet6 2001:db8::1/64 + proxy_mac=`jexec ${jname} ifconfig ${epair0}a ether | awk '$1~/ether/{print$2}'` + + # wait for DAD to complete + while [ `jexec ${jname} ifconfig | grep inet6 | grep -c tentative` != "0" ]; do + sleep 0.1 + done + + atf_check jexec ${jname} ndp -s 2001:db8::2 ${proxy_mac} proxy + while [ `jexec ${jname} ifmcstat | grep -c undefined` != "0" ]; do + sleep 0.1 + done + jexec ${jname} ping -c1 -t1 2001:db8::2 + + atf_check -o match:"2001:db8::2 \(2001:db8::2\) deleted" jexec ${jname} ndp -nd 2001:db8::2 + while [ `jexec ${jname} ifmcstat | grep -c undefined` != "0" ]; do + sleep 0.1 + done + atf_check \ + -o not-match:'group ff02::1:ff00:2' \ + -o not-match:'mcast-macaddr 33:33:ff:00:00:02' \ + jexec ${jname} ifmcstat -i ${epair0}a -f inet6 +} + +pndp_del_gu_success_cleanup() { + vnet_cleanup +} + +atf_test_case "pndp_ifdestroy_success" "cleanup" +pndp_ifdetroy_success_head() { + atf_set descr 'Test interface destruction with proxy ndp' + atf_set require.user root +} + +pndp_ifdestroy_success_body() { + + vnet_init + + jname="v6t-pndp_ifdestroy_success" + + epair0=$(vnet_mkepair) + + vnet_mkjail ${jname} ${epair0}a + + jexec ${jname} ndp -i ${epair0}a -- -disabled + jexec ${jname} ifconfig ${epair0}a up + + jexec ${jname} ifconfig ${epair0}a inet6 2001:db8::1/64 + proxy_mac=`jexec ${jname} ifconfig ${epair0}a ether | awk '$1~/ether/{print$2}'` + + # wait for DAD to complete + while [ `jexec ${jname} ifconfig | grep inet6 | grep -c tentative` != "0" ]; do + sleep 0.1 + done + + atf_check jexec ${jname} ndp -s 2001:db8::2 ${proxy_mac} proxy + while [ `jexec ${jname} ifmcstat | grep -c undefined` != "0" ]; do + sleep 0.1 + done + + atf_check jexec ${jname} ifconfig ${epair0}a destroy +} + +pndp_ifdestroy_success_cleanup() { + vnet_cleanup +} + +atf_test_case "pndp_neighbor_advert" "cleanup" +pndp_neighbor_advert_head() { + atf_set descr 'Test Neighbor Advertisement for proxy ndp' + atf_set require.user root +} + +pndp_neighbor_advert_body() { + + vnet_init + + jname_a="v6t-pndp_neighbor_advert_a" # NA sender (w/proxy ndp entry) + jname_b="v6t-pndp_neighbor_advert_b" # NA receiver (checker) + proxy_addr="2001:db8::aaaa" + + epair0=$(vnet_mkepair) + + vnet_mkjail ${jname_a} ${epair0}a + jexec ${jname_a} ndp -i ${epair0}a -- -disabled + jexec ${jname_a} ifconfig ${epair0}a up + jexec ${jname_a} ifconfig ${epair0}a inet6 2001:db8::1/64 + proxy_mac=`jexec ${jname_a} ifconfig ${epair0}a ether | awk '$1~/ether/{print$2}'` + # wait for DAD to complete + while [ `jexec ${jname_a} ifconfig | grep inet6 | grep -c tentative` != "0" ]; do + sleep 0.1 + done + atf_check jexec ${jname_a} ndp -s ${proxy_addr} ${proxy_mac} proxy + while [ `jexec ${jname_a} ifmcstat | grep -c undefined` != "0" ]; do + sleep 0.1 + done + + vnet_mkjail ${jname_b} ${epair0}b + jexec ${jname_b} ndp -i ${epair0}b -- -disabled + jexec ${jname_b} ifconfig ${epair0}b up + jexec ${jname_b} ifconfig ${epair0}b inet6 2001:db8::2/64 + # wait for DAD to complete + while [ `jexec ${jname_b} ifconfig | grep inet6 | grep -c tentative` != "0" ]; do + sleep 0.1 + done + + jexec ${jname_b} ndp -nc + # jname_b sends a NS before ICMPv6 Echo Request for the proxy address. + # jname_a responds with a NA resolving the proxy address. + # Then there must be a NDP entry of the proxy address in jname_b. + jexec ${jname_b} ping -c1 -t1 ${proxy_addr} + atf_check -o match:"${proxy_addr} +${proxy_mac} +${epair0}b" \ + jexec ${jname_b} ndp -an +} + +pndp_neighbor_advert_cleanup() { + vnet_cleanup +} + +atf_init_test_cases() +{ + + atf_add_test_case "pndp_add_gu_success" + atf_add_test_case "pndp_del_gu_success" + atf_add_test_case "pndp_ifdestroy_success" + atf_add_test_case "pndp_neighbor_advert" +} + +# end +