diff --git a/sys/net/if.c b/sys/net/if.c --- a/sys/net/if.c +++ b/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; diff --git a/sys/net/if_llatbl.h b/sys/net/if_llatbl.h --- a/sys/net/if_llatbl.h +++ b/sys/net/if_llatbl.h @@ -164,11 +164,13 @@ typedef int (llt_foreach_cb_t)(struct lltable *, struct llentry *, void *); typedef int (llt_foreach_entry_t)(struct lltable *, llt_foreach_cb_t *, void *); +typedef bool (llt_match_cb_t)(struct lltable *, struct llentry *, void *); 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 +196,11 @@ MALLOC_DECLARE(M_LLTABLE); +/* + * LLtable flags + */ +#define LLT_ADDEDPROXY 0x01 /* added a proxy llentry */ + /* * LLentry flags */ @@ -258,6 +265,9 @@ int lltable_foreach_lle(struct lltable *llt, llt_foreach_cb_t *f, void *farg); +void lltable_delete_conditional(struct lltable *llt, llt_match_cb_t *func, + void *farg); + /* * Generic link layer address lookup function. */ diff --git a/sys/net/if_llatbl.c b/sys/net/if_llatbl.c --- a/sys/net/if_llatbl.c +++ b/sys/net/if_llatbl.c @@ -730,6 +730,51 @@ LLTABLE_LIST_RUNLOCK(); } +/* + * Delete llentries that func() returns true. + */ +struct lle_match_data { + struct llentries dchain; + llt_match_cb_t *func; + void *farg; +}; + +static int +lltable_delete_conditional_cb(struct lltable *llt, struct llentry *lle, + void *farg) +{ + struct lle_match_data *lmd; + + lmd = (struct lle_match_data *)farg; + if (lmd->func(llt, lle, lmd->farg)) { + LLE_WLOCK(lle); + CK_LIST_INSERT_HEAD(&lmd->dchain, lle, lle_chain); + } + + return (0); +} + +void +lltable_delete_conditional(struct lltable *llt, llt_match_cb_t *func, + void *farg) +{ + struct llentry *lle, *next; + struct lle_match_data lmd; + + bzero(&lmd, sizeof(lmd)); + CK_LIST_INIT(&lmd.dchain); + lmd.func = func; + lmd.farg = farg; + + IF_AFDATA_WLOCK(llt->llt_ifp); + lltable_foreach_lle(llt, lltable_delete_conditional_cb, &lmd); + llentries_unlink(llt, &lmd.dchain); + IF_AFDATA_WUNLOCK(llt->llt_ifp); + + CK_LIST_FOREACH_SAFE(lle, &lmd.dchain, lle_chain, next) + llt->llt_delete_entry(llt, lle); +} + struct lltable * lltable_allocate_htbl(uint32_t hsize) { @@ -955,6 +1000,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) { diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c --- a/sys/netinet6/in6.c +++ b/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_join_proxy_ndp_mc(struct ifnet *, const struct in6_addr *); +static void in6_leave_proxy_ndp_mc(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,10 @@ { lle->la_flags |= LLE_DELETED; + + /* Leave the solicited multicast group. */ + if ((lle->la_flags & LLE_PUB) != 0) + in6_leave_proxy_ndp_mc(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 +2483,9 @@ 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_join_proxy_ndp_mc(llt->llt_ifp, &lle->r_l3addr.addr6); } static struct lltable * @@ -2621,3 +2643,72 @@ free(*nam, M_SONAME); *nam = (struct sockaddr *)sin6_p; } + +/* + * Join/leave the solicited multicast groups for proxy NDP entries. + */ +static void +in6_join_proxy_ndp_mc(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. */ + + 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)); + } +} + +static void +in6_leave_proxy_ndp_mc(struct ifnet *ifp, const struct in6_addr *dst) +{ + struct epoch_tracker et; + struct in6_multi *inm; + struct in6_addr mltaddr; + char ip6buf[INET6_ADDRSTRLEN]; + + if (in6_solicited_node_maddr(&mltaddr, ifp, dst) != 0) + return; /* error logged in in6_solicited_node_maddr. */ + + NET_EPOCH_ENTER(et); + inm = in6m_lookup(ifp, &mltaddr); + 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, &mltaddr), if_name(ifp))); +} + +static bool +in6_lle_match_pub(struct lltable *llt, struct llentry *lle, void *farg) +{ + return ((lle->la_flags & LLE_PUB) != 0); +} + +void +in6_purge_proxy_ndp(struct ifnet *ifp) +{ + struct lltable *llt; + bool need_purge; + + llt = LLTABLE6(ifp); + IF_AFDATA_WLOCK(ifp); + need_purge = ((llt->llt_flags & LLT_ADDEDPROXY) != 0); + IF_AFDATA_WUNLOCK(ifp); + + /* + * Ever added proxy ndp entries, leave solicited node multicast + * before deleting the llentry. + */ + if (need_purge) + lltable_delete_conditional(llt, in6_lle_match_pub, NULL); +} diff --git a/sys/netinet6/in6_var.h b/sys/netinet6/in6_var.h --- a/sys/netinet6/in6_var.h +++ b/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. */ diff --git a/sys/netinet6/nd6_nbr.c b/sys/netinet6/nd6_nbr.c --- a/sys/netinet6/nd6_nbr.c +++ b/sys/netinet6/nd6_nbr.c @@ -96,6 +96,9 @@ static void nd6_ns_output_fib(struct ifnet *, const struct in6_addr *, const struct in6_addr *, const struct in6_addr *, uint8_t *, u_int); +static struct ifaddr *nd6_proxy_fill_sdl(struct ifnet *, + const struct in6_addr *, struct sockaddr_dl *); + VNET_DEFINE_STATIC(int, dad_enhanced) = 1; #define V_dad_enhanced VNET(dad_enhanced) @@ -255,34 +258,8 @@ /* (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); - ifa = (struct ifaddr *)in6ifa_ifpforlinklocal( - ifp, IN6_IFF_NOTREADY|IN6_IFF_ANYCAST); - if (ifa) - proxy = 1; - } - } + if ((ifa = nd6_proxy_fill_sdl(ifp, &taddr6, &proxydl)) != NULL) + proxy = 1; } if (ifa == NULL) { /* @@ -386,6 +363,30 @@ m_freem(m); } +static struct ifaddr * +nd6_proxy_fill_sdl(struct ifnet *ifp, const struct in6_addr *taddr6, + struct sockaddr_dl *sdl) +{ + struct ifaddr *ifa; + struct llentry *ln; + + ifa = NULL; + ln = nd6_lookup(taddr6, LLE_SF(AF_INET6, 0), ifp); + if (ln == NULL) + return (ifa); + if ((ln->la_flags & (LLE_PUB | LLE_VALID)) == (LLE_PUB | LLE_VALID)) { + link_init_sdl(ifp, (struct sockaddr *)sdl, ifp->if_type); + sdl->sdl_alen = ifp->if_addrlen; + bcopy(ln->ll_addr, &sdl->sdl_data, ifp->if_addrlen); + LLE_RUNLOCK(ln); + ifa = (struct ifaddr *)in6ifa_ifpforlinklocal(ifp, + IN6_IFF_NOTREADY|IN6_IFF_ANYCAST); + } else + LLE_RUNLOCK(ln); + + return (ifa); +} + /* * Output a Neighbor Solicitation Message. Caller specifies: * - ICMP6 header source IP6 address diff --git a/tests/sys/netinet6/Makefile b/tests/sys/netinet6/Makefile --- a/tests/sys/netinet6/Makefile +++ b/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 diff --git a/tests/sys/netinet6/proxy_ndp.sh b/tests/sys/netinet6/proxy_ndp.sh new file mode 100755 --- /dev/null +++ b/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 +