1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-13 10:02:38 +00:00

netinet6: fix ndp proxying

We could insert proxy NDP entries by the ndp command, but the host
with proxy ndp entries had not responded to Neighbor Solicitations.
Change the following points for proxy NDP to work as expected:
* join solicited-node multicast addresses for proxy NDP entries
  in order to receive Neighbor Solicitations.
* look up proxy NDP entries not on the routing table but on the
  link-level address table when receiving Neighbor Solicitations.

Reviewed By: melifaro
Differential Revision: https://reviews.freebsd.org/D35307
MFC after:	2 weeks
This commit is contained in:
KUROSAWA Takahiro 2022-05-30 07:51:15 +00:00 committed by Alexander V. Chernikov
parent 77001f9b6d
commit d6cd20cc5c
8 changed files with 425 additions and 40 deletions

View File

@ -1017,6 +1017,16 @@ if_purgeaddrs(struct ifnet *ifp)
{
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;

View File

@ -730,6 +730,51 @@ lltable_prefix_free(int af, struct sockaddr *addr, struct sockaddr *mask,
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 @@ lla_rt_output(struct rt_msghdr *rtm, struct rt_addrinfo *info)
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) {

View File

@ -164,11 +164,13 @@ typedef void (llt_post_resolved_t)(struct lltable *, struct llentry *);
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 @@ struct lltable {
MALLOC_DECLARE(M_LLTABLE);
/*
* LLtable flags
*/
#define LLT_ADDEDPROXY 0x01 /* added a proxy llentry */
/*
* LLentry flags
*/
@ -258,6 +265,9 @@ bool lltable_acquire_wlock(struct ifnet *ifp, struct llentry *lle);
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.
*/

View File

@ -162,6 +162,9 @@ static int in6_update_ifa_internal(struct ifnet *, struct in6_aliasreq *,
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 @@ in6_joingroup_legacy(struct ifnet *ifp, const struct in6_addr *mcaddr,
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 @@ in6_update_ifa_join_mc(struct ifnet *ifp, struct in6_aliasreq *ifra,
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 @@ in6_lltable_delete_entry(struct lltable *llt, struct llentry *lle)
{
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 @@ in6_lltable_dump_entry(struct lltable *llt, struct llentry *lle,
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 @@ in6_sin_2_v4mapsin6_in_sock(struct sockaddr **nam)
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);
}

View File

@ -916,6 +916,8 @@ int in6_is_addr_deprecated(struct sockaddr_in6 *);
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.
*/

View File

@ -96,6 +96,9 @@ static void nd6_na_output_fib(struct ifnet *, const struct in6_addr *,
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 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
/* (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 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
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

View File

@ -15,7 +15,8 @@ ATF_TESTS_SH= \
output6 \
lpm6 \
fibs6 \
ndp
ndp \
proxy_ndp
TEST_METADATA.output6+= required_programs="python"
${PACKAGE}FILES+= exthdr.py

222
tests/sys/netinet6/proxy_ndp.sh Executable file
View File

@ -0,0 +1,222 @@
#!/usr/bin/env atf-sh
#-
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2022 KUROSAWA Takahiro <takahiro.kurosawa@gmail.com>
#
# 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