mirror of
https://github.com/espressif/openthread.git
synced 2026-08-06 18:57:47 +00:00
[dua] implement ND Proxy callbacks (#5595)
This commit:
- Add callback for Domain Prefix events
- Add callback for ND Proxy events
- Add corresponding test to verify ND Proxy works
- Some misc enhancements to the Backbone tests
- Run sudo modprobe ip6table_filter in Docker host so that ip6tables
works in ubuntu docker instances.
- Use -DOTBR_REST=OFF -DOTBR_WEB=OFF for building OTBR Docker image
since these features are not used in tests.
- Setup radvd on Host to send Router Advertisements for the Domain
Prefix using configuration: AdvOnLink on; AdvAutonomous off;
AdvRouterAddr off;
- Set sysctl net.ipv6.conf.eth0.accept_ra=2 for BBR and Host
This commit is contained in:
@@ -251,7 +251,6 @@ jobs:
|
||||
MULTIPLY: 1
|
||||
PYTHONUNBUFFERED: 1
|
||||
VERBOSE: 1
|
||||
OTBR_COMMIT: "3cf6ff1126dca96803252514691f40d0554046bf" # Date: Mon Oct 26 15:27:04 2020 +0800
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build OTBR Docker
|
||||
|
||||
@@ -313,6 +313,103 @@ otError otBackboneRouterMulticastListenerGetNext(otInstance *
|
||||
otBackboneRouterMulticastListenerIterator *aIterator,
|
||||
otBackboneRouterMulticastListenerInfo * aListenerInfo);
|
||||
|
||||
/**
|
||||
* Represents the ND Proxy events.
|
||||
*
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
OT_BACKBONE_ROUTER_NDPROXY_ADDED = 0, ///< ND Proxy was added.
|
||||
OT_BACKBONE_ROUTER_NDPROXY_REMOVED = 1, ///< ND Proxy was removed.
|
||||
OT_BACKBONE_ROUTER_NDPROXY_RENEWED = 2, ///< ND Proxy was renewed.
|
||||
OT_BACKBONE_ROUTER_NDPROXY_CLEARED = 3, ///< All ND Proxies were cleared.
|
||||
} otBackboneRouterNdProxyEvent;
|
||||
|
||||
/**
|
||||
* This function pointer is called whenever the Nd Proxy changed.
|
||||
*
|
||||
* @param[in] aContext The user context pointer.
|
||||
* @param[in] aEvent The ND Proxy event.
|
||||
* @param[in] aDua The Domain Unicast Address of the ND Proxy, or `nullptr` if @p aEvent is
|
||||
* `OT_BACKBONE_ROUTER_NDPROXY_CLEARED`.
|
||||
*
|
||||
*/
|
||||
typedef void (*otBackboneRouterNdProxyCallback)(void * aContext,
|
||||
otBackboneRouterNdProxyEvent aEvent,
|
||||
const otIp6Address * aDua);
|
||||
|
||||
/**
|
||||
* This method sets the Backbone Router ND Proxy callback.
|
||||
*
|
||||
* @param[in] aInstance A pointer to an OpenThread instance.
|
||||
* @param[in] aCallback A pointer to the ND Proxy callback.
|
||||
* @param[in] aContext A user context pointer.
|
||||
*
|
||||
*/
|
||||
void otBackboneRouterSetNdProxyCallback(otInstance * aInstance,
|
||||
otBackboneRouterNdProxyCallback aCallback,
|
||||
void * aContext);
|
||||
|
||||
/**
|
||||
* Represents the Backbone Router ND Proxy info.
|
||||
*
|
||||
*/
|
||||
struct otBackboneRouterNdProxyInfo
|
||||
{
|
||||
otIp6InterfaceIdentifier *mMeshLocalIid; ///< Mesh-local IID
|
||||
uint32_t mTimeSinceLastTransaction; ///< Time since last transaction (Seconds)
|
||||
uint16_t mRloc16; ///< RLOC16
|
||||
};
|
||||
|
||||
/**
|
||||
* This method gets the Backbone Router ND Proxy info.
|
||||
*
|
||||
* @param[in] aInstance A pointer to an OpenThread instance.
|
||||
* @param[in] aDua The Domain Unicast Address.
|
||||
* @param[out] aNdProxyInfo A pointer to the ND Proxy info.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully got the ND Proxy info.
|
||||
* @retval OT_ERROR_NOT_FOUND Failed to find the Domain Unicast Address in the ND Proxy table.
|
||||
*
|
||||
*/
|
||||
otError otBackboneRouterGetNdProxyInfo(otInstance * aInstance,
|
||||
const otIp6Address * aDua,
|
||||
otBackboneRouterNdProxyInfo *aNdProxyInfo);
|
||||
|
||||
/**
|
||||
* Represents the Domain Prefix events.
|
||||
*
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
OT_BACKBONE_ROUTER_DOMAIN_PREFIX_ADDED = 0, ///< Domain Prefix was added.
|
||||
OT_BACKBONE_ROUTER_DOMAIN_PREFIX_REMOVED = 1, ///< Domain Prefix was removed.
|
||||
OT_BACKBONE_ROUTER_DOMAIN_PREFIX_CHANGED = 2, ///< Domain Prefix was changed.
|
||||
} otBackboneRouterDomainPrefixEvent;
|
||||
|
||||
/**
|
||||
* This function pointer is called whenever the Domain Prefix changed.
|
||||
*
|
||||
* @param[in] aContext The user context pointer.
|
||||
* @param[in] aEvent The Domain Prefix event.
|
||||
* @param[in] aDomainPrefix The new Domain Prefix if added or changed, nullptr otherwise.
|
||||
*
|
||||
*/
|
||||
typedef void (*otBackboneRouterDomainPrefixCallback)(void * aContext,
|
||||
otBackboneRouterDomainPrefixEvent aEvent,
|
||||
const otIp6Prefix * aDomainPrefix);
|
||||
/**
|
||||
* This method sets the Backbone Router Domain Prefix callback.
|
||||
*
|
||||
* @param[in] aInstance A pointer to an OpenThread instance.
|
||||
* @param[in] aCallback A pointer to the Domain Prefix callback.
|
||||
* @param[in] aContext A user context pointer.
|
||||
*
|
||||
*/
|
||||
void otBackboneRouterSetDomainPrefixCallback(otInstance * aInstance,
|
||||
otBackboneRouterDomainPrefixCallback aCallback,
|
||||
void * aContext);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
|
||||
@@ -53,7 +53,7 @@ extern "C" {
|
||||
* @note This number versions both OpenThread platform and user APIs.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_API_VERSION (44)
|
||||
#define OPENTHREAD_API_VERSION (45)
|
||||
|
||||
/**
|
||||
* @addtogroup api-instance
|
||||
|
||||
+3
-5
@@ -279,8 +279,8 @@ do_build_otbr_docker()
|
||||
echo "Building OTBR Docker ..."
|
||||
local otdir
|
||||
local otbrdir
|
||||
local otbr_options="-DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON"
|
||||
local otbr_commit=${OTBR_COMMIT:-master}
|
||||
local otbr_options="-DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON -DOTBR_REST=OFF -DOTBR_WEB=OFF"
|
||||
local otbr_commit=${OTBR_COMMIT:-8d0b6ccd857441208f56ed29050a9367d8d74a62} # [backbone-router] implement Backbone ND proxy #581
|
||||
local otbr_docker_image=${OTBR_DOCKER_IMAGE:-otbr-ot12-backbone-ci}
|
||||
|
||||
otbrdir=$(mktemp -d -t otbr_XXXXXX)
|
||||
@@ -289,10 +289,8 @@ do_build_otbr_docker()
|
||||
(
|
||||
cd "${otbrdir}"
|
||||
git init
|
||||
git remote add origin https://github.com/openthread/ot-br-posix.git
|
||||
git fetch origin "${otbr_commit}" --depth 1
|
||||
git fetch https://github.com/openthread/ot-br-posix.git "${otbr_commit}" --depth 1
|
||||
git reset --hard FETCH_HEAD
|
||||
git submodule update --init --recursive --depth 1
|
||||
rm -rf third_party/openthread/repo
|
||||
cp -r "${otdir}" third_party/openthread/repo
|
||||
rm -rf .git
|
||||
|
||||
@@ -182,4 +182,32 @@ otError otBackboneRouterMulticastListenerGetNext(otInstance *
|
||||
}
|
||||
#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
|
||||
|
||||
void otBackboneRouterSetNdProxyCallback(otInstance * aInstance,
|
||||
otBackboneRouterNdProxyCallback aCallback,
|
||||
void * aContext)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
instance.Get<BackboneRouter::NdProxyTable>().SetCallback(aCallback, aContext);
|
||||
}
|
||||
|
||||
otError otBackboneRouterGetNdProxyInfo(otInstance * aInstance,
|
||||
const otIp6Address * aDua,
|
||||
otBackboneRouterNdProxyInfo *aNdProxyInfo)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.Get<BackboneRouter::NdProxyTable>().GetInfo(reinterpret_cast<const Ip6::Address &>(*aDua),
|
||||
*aNdProxyInfo);
|
||||
}
|
||||
|
||||
void otBackboneRouterSetDomainPrefixCallback(otInstance * aInstance,
|
||||
otBackboneRouterDomainPrefixCallback aCallback,
|
||||
void * aContext)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.Get<BackboneRouter::Local>().SetDomainPrefixCallback(aCallback, aContext);
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
|
||||
|
||||
@@ -301,7 +301,7 @@ void Leader::UpdateDomainPrefixConfig(void)
|
||||
LogDomainPrefix(state, mDomainPrefix);
|
||||
|
||||
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
|
||||
Get<Local>().UpdateAllDomainBackboneRouters(state);
|
||||
Get<Local>().HandleDomainPrefixUpdate(state);
|
||||
Get<NdProxyTable>().HandleDomainPrefixUpdate(state);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ Local::Local(Instance &aInstance)
|
||||
, mSequenceNumber(Random::NonCrypto::GetUint8())
|
||||
, mRegistrationJitter(Mle::kBackboneRouterRegistrationJitter)
|
||||
, mIsServiceAdded(false)
|
||||
, mDomainPrefixCallback(nullptr)
|
||||
, mDomainPrefixCallbackContext(nullptr)
|
||||
{
|
||||
mDomainPrefixConfig.GetPrefix().SetLength(0);
|
||||
|
||||
@@ -359,7 +361,7 @@ exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void Local::UpdateAllDomainBackboneRouters(Leader::DomainPrefixState aState)
|
||||
void Local::HandleDomainPrefixUpdate(Leader::DomainPrefixState aState)
|
||||
{
|
||||
if (!IsEnabled())
|
||||
{
|
||||
@@ -377,6 +379,27 @@ void Local::UpdateAllDomainBackboneRouters(Leader::DomainPrefixState aState)
|
||||
Get<BackboneTmfAgent>().SubscribeMulticast(mAllDomainBackboneRouters);
|
||||
}
|
||||
|
||||
if (mDomainPrefixCallback != nullptr)
|
||||
{
|
||||
switch (aState)
|
||||
{
|
||||
case Leader::kDomainPrefixAdded:
|
||||
mDomainPrefixCallback(mDomainPrefixCallbackContext, OT_BACKBONE_ROUTER_DOMAIN_PREFIX_ADDED,
|
||||
Get<Leader>().GetDomainPrefix());
|
||||
break;
|
||||
case Leader::kDomainPrefixRemoved:
|
||||
mDomainPrefixCallback(mDomainPrefixCallbackContext, OT_BACKBONE_ROUTER_DOMAIN_PREFIX_REMOVED,
|
||||
Get<Leader>().GetDomainPrefix());
|
||||
break;
|
||||
case Leader::kDomainPrefixRefreshed:
|
||||
mDomainPrefixCallback(mDomainPrefixCallbackContext, OT_BACKBONE_ROUTER_DOMAIN_PREFIX_CHANGED,
|
||||
Get<Leader>().GetDomainPrefix());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
@@ -419,6 +442,12 @@ void Local::LogBackboneRouterService(const char *aAction, otError aError)
|
||||
}
|
||||
#endif
|
||||
|
||||
void Local::SetDomainPrefixCallback(otBackboneRouterDomainPrefixCallback aCallback, void *aContext)
|
||||
{
|
||||
mDomainPrefixCallback = aCallback;
|
||||
mDomainPrefixCallbackContext = aContext;
|
||||
}
|
||||
|
||||
} // namespace BackboneRouter
|
||||
|
||||
} // namespace ot
|
||||
|
||||
@@ -227,7 +227,16 @@ public:
|
||||
* @param[in] aState The Domain Prefix state or state change.
|
||||
*
|
||||
*/
|
||||
void UpdateAllDomainBackboneRouters(Leader::DomainPrefixState aState);
|
||||
void HandleDomainPrefixUpdate(Leader::DomainPrefixState aState);
|
||||
|
||||
/**
|
||||
* This method sets the Domain Prefix callback.
|
||||
*
|
||||
* @param[in] aCallback The callback function.
|
||||
* @param[in] aContext A user context pointer.
|
||||
*
|
||||
*/
|
||||
void SetDomainPrefixCallback(otBackboneRouterDomainPrefixCallback aCallback, void *aContext);
|
||||
|
||||
private:
|
||||
void SetState(BackboneRouterState aState);
|
||||
@@ -255,9 +264,11 @@ private:
|
||||
|
||||
NetworkData::OnMeshPrefixConfig mDomainPrefixConfig;
|
||||
|
||||
Ip6::NetifUnicastAddress mBackboneRouterPrimaryAloc;
|
||||
Ip6::Address mAllNetworkBackboneRouters;
|
||||
Ip6::Address mAllDomainBackboneRouters;
|
||||
Ip6::NetifUnicastAddress mBackboneRouterPrimaryAloc;
|
||||
Ip6::Address mAllNetworkBackboneRouters;
|
||||
Ip6::Address mAllDomainBackboneRouters;
|
||||
otBackboneRouterDomainPrefixCallback mDomainPrefixCallback;
|
||||
void * mDomainPrefixCallbackContext;
|
||||
};
|
||||
|
||||
} // namespace BackboneRouter
|
||||
|
||||
@@ -141,6 +141,11 @@ void NdProxyTable::Clear(void)
|
||||
proxy.Clear();
|
||||
}
|
||||
|
||||
if (mCallback != nullptr)
|
||||
{
|
||||
mCallback(mCallbackContext, OT_BACKBONE_ROUTER_NDPROXY_CLEARED, nullptr);
|
||||
}
|
||||
|
||||
otLogNoteBbr("NdProxyTable::Clear!");
|
||||
}
|
||||
|
||||
@@ -158,13 +163,14 @@ otError NdProxyTable::Register(const Ip6::InterfaceIdentifier &aAddressIid,
|
||||
VerifyOrExit(proxy->mMeshLocalIid == aMeshLocalIid, error = OT_ERROR_DUPLICATED);
|
||||
|
||||
proxy->Update(aRloc16, timeSinceLastTransaction);
|
||||
NotifyDuaRegistrationOnBackboneLink(*proxy);
|
||||
NotifyDuaRegistrationOnBackboneLink(*proxy, /* aIsRenew */ true);
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
proxy = FindByMeshLocalIid(aMeshLocalIid);
|
||||
if (proxy != nullptr)
|
||||
{
|
||||
TriggerCallback(OT_BACKBONE_ROUTER_NDPROXY_REMOVED, proxy->mAddressIid);
|
||||
Erase(*proxy);
|
||||
}
|
||||
else
|
||||
@@ -245,7 +251,7 @@ void NdProxyTable::HandleTimer(void)
|
||||
if (proxy.IsDadAttamptsComplete())
|
||||
{
|
||||
proxy.mDadFlag = false;
|
||||
NotifyDuaRegistrationOnBackboneLink(proxy);
|
||||
NotifyDuaRegistrationOnBackboneLink(proxy, /* aIsRenew */ false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -262,6 +268,31 @@ exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void NdProxyTable::SetCallback(otBackboneRouterNdProxyCallback aCallback, void *aContext)
|
||||
{
|
||||
mCallback = aCallback;
|
||||
mCallbackContext = aContext;
|
||||
}
|
||||
|
||||
void NdProxyTable::TriggerCallback(otBackboneRouterNdProxyEvent aEvent,
|
||||
const Ip6::InterfaceIdentifier &aAddressIid) const
|
||||
{
|
||||
Ip6::Address dua;
|
||||
const Ip6::Prefix *prefix = Get<BackboneRouter::Leader>().GetDomainPrefix();
|
||||
|
||||
VerifyOrExit(mCallback != nullptr);
|
||||
|
||||
OT_ASSERT(prefix != nullptr);
|
||||
|
||||
dua.SetPrefix(*prefix);
|
||||
dua.SetIid(aAddressIid);
|
||||
|
||||
mCallback(mCallbackContext, aEvent, &dua);
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void NdProxyTable::NotifyDadComplete(NdProxyTable::NdProxy &aNdProxy, bool aDuplicated)
|
||||
{
|
||||
if (aDuplicated)
|
||||
@@ -292,15 +323,40 @@ NdProxyTable::NdProxy *NdProxyTable::ResolveDua(const Ip6::Address &aDua)
|
||||
return Get<Leader>().IsDomainUnicast(aDua) ? FindByAddressIid(aDua.GetIid()) : nullptr;
|
||||
}
|
||||
|
||||
void NdProxyTable::NotifyDuaRegistrationOnBackboneLink(NdProxyTable::NdProxy &aNdProxy)
|
||||
void NdProxyTable::NotifyDuaRegistrationOnBackboneLink(NdProxyTable::NdProxy &aNdProxy, bool aIsRenew)
|
||||
{
|
||||
if (!aNdProxy.mDadFlag)
|
||||
{
|
||||
TriggerCallback(aIsRenew ? OT_BACKBONE_ROUTER_NDPROXY_RENEWED : OT_BACKBONE_ROUTER_NDPROXY_ADDED,
|
||||
aNdProxy.mAddressIid);
|
||||
|
||||
IgnoreError(Get<BackboneRouter::Manager>().SendProactiveBackboneNotification(
|
||||
GetDua(aNdProxy), aNdProxy.GetMeshLocalIid(), aNdProxy.GetTimeSinceLastTransaction()));
|
||||
}
|
||||
}
|
||||
|
||||
otError NdProxyTable::GetInfo(const Ip6::Address &aDua, otBackboneRouterNdProxyInfo &aNdProxyInfo)
|
||||
{
|
||||
otError error = OT_ERROR_NOT_FOUND;
|
||||
|
||||
VerifyOrExit(Get<Leader>().IsDomainUnicast(aDua), error = OT_ERROR_INVALID_ARGS);
|
||||
|
||||
for (NdProxy &proxy : Iterate(kFilterValid))
|
||||
{
|
||||
if (proxy.mAddressIid == aDua.GetIid())
|
||||
{
|
||||
aNdProxyInfo.mMeshLocalIid = &proxy.mMeshLocalIid;
|
||||
aNdProxyInfo.mTimeSinceLastTransaction = proxy.GetTimeSinceLastTransaction();
|
||||
aNdProxyInfo.mRloc16 = proxy.mRloc16;
|
||||
|
||||
ExitNow(error = OT_ERROR_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
} // namespace BackboneRouter
|
||||
|
||||
} // namespace ot
|
||||
|
||||
@@ -132,6 +132,8 @@ public:
|
||||
*/
|
||||
explicit NdProxyTable(Instance &aInstance)
|
||||
: InstanceLocator(aInstance)
|
||||
, mCallback(nullptr)
|
||||
, mCallbackContext(nullptr)
|
||||
, mIsAnyDadInProcess(false)
|
||||
{
|
||||
}
|
||||
@@ -206,6 +208,27 @@ public:
|
||||
*/
|
||||
static void Erase(NdProxy &aNdProxy);
|
||||
|
||||
/*
|
||||
* This method sets the ND Proxy callback.
|
||||
*
|
||||
* @param[in] aCallback The callback function.
|
||||
* @param[in] aContext A user context pointer.
|
||||
*
|
||||
*/
|
||||
void SetCallback(otBackboneRouterNdProxyCallback aCallback, void *aContext);
|
||||
|
||||
/**
|
||||
* This method retrieves the ND Proxy info of the Domain Unicast Address.
|
||||
*
|
||||
* @param[in] aDua The Domain Unicast Address to get info.
|
||||
* @param[in] aNdProxyInfo A pointer to the ND Proxy info.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully retrieve the ND Proxy info.
|
||||
* @retval OT_ERROR_NOT_FOUND Failed to find the Domain Unicast Address in the ND Proxy table.
|
||||
*
|
||||
*/
|
||||
otError GetInfo(const Ip6::Address &aDua, otBackboneRouterNdProxyInfo &aNdProxyInfo);
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
@@ -275,10 +298,13 @@ private:
|
||||
NdProxy * FindByMeshLocalIid(const Ip6::InterfaceIdentifier &aMeshLocalIid);
|
||||
NdProxy * FindInvalid(void);
|
||||
Ip6::Address GetDua(NdProxy &aNdProxy);
|
||||
void NotifyDuaRegistrationOnBackboneLink(NdProxy &aNdProxy);
|
||||
void NotifyDuaRegistrationOnBackboneLink(NdProxy &aNdProxy, bool aIsRenew);
|
||||
void TriggerCallback(otBackboneRouterNdProxyEvent aEvent, const Ip6::InterfaceIdentifier &aAddressIid) const;
|
||||
|
||||
NdProxy mProxies[kMaxNdProxyNum];
|
||||
bool mIsAnyDadInProcess : 1;
|
||||
NdProxy mProxies[kMaxNdProxyNum];
|
||||
otBackboneRouterNdProxyCallback mCallback;
|
||||
void * mCallbackContext;
|
||||
bool mIsAnyDadInProcess : 1;
|
||||
};
|
||||
|
||||
} // namespace BackboneRouter
|
||||
|
||||
@@ -122,8 +122,8 @@ class BBR_5_11_01(thread_cert.TestCase):
|
||||
backbone=True))
|
||||
|
||||
# Step 23: Host sends ping packet to destination Dg
|
||||
self.assertFalse(self.nodes[HOST].ping(Dg,
|
||||
backbone=True)) # Must fail since ND Proxying is not implemented yet
|
||||
# TODO: (DUA) implement DUA routing on OTBR
|
||||
self.assertFalse(self.nodes[HOST].ping(Dg, backbone=True))
|
||||
|
||||
def verify(self, pv: PacketVerifier):
|
||||
pkts = pv.pkts
|
||||
@@ -161,12 +161,10 @@ class BBR_5_11_01(thread_cert.TestCase):
|
||||
pkts.filter_eth_src(Host_ETH).filter_ipv6_src_dst(Host_BGUA, BR_1_BGUA).filter_ping_reply().must_next()
|
||||
|
||||
# Step 16: Host: Queries DUA, Dg, with ND-NS
|
||||
# TODO: setup radvd on Host
|
||||
pkts.filter_eth_src(Host_ETH).filter_icmpv6_nd_ns(Dg).must_not_next()
|
||||
pkts.filter_eth_src(Host_ETH).filter_icmpv6_nd_ns(Dg).must_next()
|
||||
|
||||
# Step 17: BR_1: Responds with a neighbor advertisement.
|
||||
# TODO: (DUA) implement ND proxy on PBBR
|
||||
pkts.filter_eth_src(BR_1_ETH).filter_icmpv6_nd_na(Dg).must_not_next()
|
||||
pkts.filter_eth_src(BR_1_ETH).filter_icmpv6_nd_na(Dg).must_next()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2020, The OpenThread Authors.
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
# 3. Neither the name of the copyright holder nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
|
||||
#
|
||||
# This test verifies PBBR's ND Proxy feature:
|
||||
# 1. Sends ICMPv6 Neighbor Advertisement (NA) for ND Proxy
|
||||
# 2. Receive and handle ICMPv6 Neighbor Solicitation (NS) and sends NA accordingly.
|
||||
#
|
||||
import ipaddress
|
||||
import unittest
|
||||
|
||||
import config
|
||||
import thread_cert
|
||||
from pktverify.packet_verifier import PacketVerifier
|
||||
from pktverify.consts import DUA_RECENT_TIME
|
||||
|
||||
PBBR = 1
|
||||
SBBR = 2
|
||||
ROUTER1 = 3
|
||||
HOST = 4
|
||||
|
||||
REREG_DELAY = 5 # Seconds
|
||||
MLR_TIMEOUT = 300 # Seconds
|
||||
WAIT_REDUNDANCE = 3
|
||||
|
||||
|
||||
class TestNdProxy(thread_cert.TestCase):
|
||||
USE_MESSAGE_FACTORY = False
|
||||
|
||||
# Topology:
|
||||
# ---- -(eth)------------
|
||||
# | | |
|
||||
# PBBR----SBBR HOST
|
||||
# \ /
|
||||
# Router1
|
||||
#
|
||||
TOPOLOGY = {
|
||||
PBBR: {
|
||||
'name': 'PBBR',
|
||||
'allowlist': [SBBR, ROUTER1],
|
||||
'is_otbr': True,
|
||||
'version': '1.2',
|
||||
'router_selection_jitter': 1,
|
||||
},
|
||||
SBBR: {
|
||||
'name': 'SBBR',
|
||||
'allowlist': [PBBR, ROUTER1],
|
||||
'is_otbr': True,
|
||||
'version': '1.2',
|
||||
'router_selection_jitter': 1,
|
||||
},
|
||||
ROUTER1: {
|
||||
'name': 'ROUTER1',
|
||||
'allowlist': [PBBR, SBBR],
|
||||
'version': '1.2',
|
||||
'router_selection_jitter': 1,
|
||||
},
|
||||
HOST: {
|
||||
'name': 'HOST',
|
||||
'is_host': True
|
||||
},
|
||||
}
|
||||
|
||||
def test(self):
|
||||
# Bring up HOST
|
||||
self.nodes[HOST].start()
|
||||
|
||||
# Bring up PBBR
|
||||
self.nodes[PBBR].start()
|
||||
self.simulator.go(5)
|
||||
self.assertEqual('leader', self.nodes[PBBR].get_state())
|
||||
self.wait_node_state(PBBR, 'leader', 5)
|
||||
|
||||
self.nodes[PBBR].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT)
|
||||
self.nodes[PBBR].enable_backbone_router()
|
||||
self.nodes[PBBR].set_domain_prefix(config.DOMAIN_PREFIX, 'prosD')
|
||||
self.simulator.go(5)
|
||||
self.assertTrue(self.nodes[PBBR].is_primary_backbone_router)
|
||||
self.assertIsNotNone(self.nodes[PBBR].get_ip6_address(config.ADDRESS_TYPE.DUA))
|
||||
|
||||
# Bring up SBBR
|
||||
self.nodes[SBBR].start()
|
||||
self.simulator.go(5)
|
||||
self.assertEqual('router', self.nodes[SBBR].get_state())
|
||||
|
||||
self.nodes[SBBR].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT)
|
||||
self.nodes[SBBR].enable_backbone_router()
|
||||
self.simulator.go(5)
|
||||
self.assertFalse(self.nodes[SBBR].is_primary_backbone_router)
|
||||
self.assertIsNotNone(self.nodes[SBBR].get_ip6_address(config.ADDRESS_TYPE.DUA))
|
||||
|
||||
# Bring up ROUTER1
|
||||
self.nodes[ROUTER1].start()
|
||||
self.simulator.go(5)
|
||||
self.assertEqual('router', self.nodes[ROUTER1].get_state())
|
||||
self.assertIsNotNone(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.DUA))
|
||||
|
||||
self.collect_ipaddrs()
|
||||
self.collect_rloc16s()
|
||||
|
||||
# Wait for DUA_RECENT_TIME
|
||||
self.simulator.go(DUA_RECENT_TIME + WAIT_REDUNDANCE)
|
||||
|
||||
self._test_neighbor_solicitation(PBBR)
|
||||
self._test_neighbor_solicitation(SBBR)
|
||||
self._test_neighbor_solicitation(ROUTER1)
|
||||
|
||||
def _test_neighbor_solicitation(self, nodeid):
|
||||
dua = self.nodes[nodeid].get_ip6_address(config.ADDRESS_TYPE.DUA)
|
||||
print('Node DUA: ', dua)
|
||||
self.nodes[HOST].ip_neighbors_flush()
|
||||
self.nodes[HOST].ping(dua, backbone=True)
|
||||
self.simulator.go(WAIT_REDUNDANCE)
|
||||
|
||||
def verify(self, pv: PacketVerifier):
|
||||
pkts = pv.pkts
|
||||
pv.add_common_vars()
|
||||
pv.summary.show()
|
||||
|
||||
PBBR_DUA = pv.vars['PBBR_DUA']
|
||||
SBBR_DUA = pv.vars['SBBR_DUA']
|
||||
ROUTER1_DUA = pv.vars['ROUTER1_DUA']
|
||||
PBBR_ETH = pv.vars['PBBR_ETH']
|
||||
SBBR_ETH = pv.vars['SBBR_ETH']
|
||||
HOST_ETH = pv.vars['HOST_ETH']
|
||||
HOST_BGUA = pv.vars['HOST_BGUA']
|
||||
|
||||
# SBBR must not send NA for any DUA
|
||||
pkts.filter_eth_src(SBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(PBBR_DUA).must_not_next()
|
||||
pkts.filter_eth_src(SBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(SBBR_DUA).must_not_next()
|
||||
pkts.filter_eth_src(SBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(ROUTER1_DUA).must_not_next()
|
||||
|
||||
# PBBR must send unsolicited NA for PBBR's DUA
|
||||
pkts.filter_eth_src(PBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(PBBR_DUA).filter(
|
||||
'icmpv6.nd.na.flag.s == 0').must_next().must_verify("""
|
||||
icmpv6.opt.linkaddr == {PBBR_ETH}
|
||||
and icmpv6.nd.na.flag.r == 1
|
||||
and icmpv6.nd.na.flag.o == 1
|
||||
and icmpv6.nd.na.flag.rsv == 0
|
||||
""",
|
||||
PBBR_ETH=PBBR_ETH)
|
||||
|
||||
# PBBR must send unsolicited NA for SBBR's DUA
|
||||
pkts.filter_eth_src(PBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(SBBR_DUA).filter(
|
||||
'icmpv6.nd.na.flag.s == 0').must_next().must_verify("""
|
||||
icmpv6.opt.linkaddr == {PBBR_ETH}
|
||||
and icmpv6.nd.na.flag.r == 1
|
||||
and icmpv6.nd.na.flag.o == 1
|
||||
and icmpv6.nd.na.flag.rsv == 0
|
||||
""",
|
||||
PBBR_ETH=PBBR_ETH)
|
||||
|
||||
# PBBR must send unsolicited NA for ROUTER1's DUA
|
||||
pkts.filter_eth_src(PBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(ROUTER1_DUA).filter(
|
||||
'icmpv6.nd.na.flag.s == 0').must_next().must_verify("""
|
||||
icmpv6.opt.linkaddr == {PBBR_ETH}
|
||||
and icmpv6.nd.na.flag.r == 1
|
||||
and icmpv6.nd.na.flag.o == 1
|
||||
and icmpv6.nd.na.flag.rsv == 0
|
||||
""",
|
||||
PBBR_ETH=PBBR_ETH)
|
||||
|
||||
# HOST should send NS for PBBR's DUA
|
||||
pkts.filter_eth_src(HOST_ETH).filter_icmpv6_nd_ns(PBBR_DUA).must_next()
|
||||
# PBBR must send solicited NA for PBBR's DUA
|
||||
pkts.filter_eth_src(PBBR_ETH).filter_ipv6_dst(HOST_BGUA).filter_icmpv6_nd_na(PBBR_DUA).filter(
|
||||
'icmpv6.nd.na.flag.s == 1').must_next().must_verify("""
|
||||
icmpv6.opt.linkaddr == {PBBR_ETH}
|
||||
and icmpv6.nd.na.flag.r == 1
|
||||
and icmpv6.nd.na.flag.o == 0
|
||||
and icmpv6.nd.na.flag.rsv == 0
|
||||
""",
|
||||
PBBR_ETH=PBBR_ETH)
|
||||
|
||||
# Host should send NS for SBBR's DUA
|
||||
pkts.filter_eth_src(HOST_ETH).filter_icmpv6_nd_ns(SBBR_DUA).must_next()
|
||||
# PBBR must send solicited NA for SBBR's DUA
|
||||
pkts.filter_eth_src(PBBR_ETH).filter_ipv6_dst(HOST_BGUA).filter_icmpv6_nd_na(SBBR_DUA).filter(
|
||||
'icmpv6.nd.na.flag.s == 1').must_next().must_verify("""
|
||||
icmpv6.opt.linkaddr == {PBBR_ETH}
|
||||
and icmpv6.nd.na.flag.r == 1
|
||||
and icmpv6.nd.na.flag.o == 0
|
||||
and icmpv6.nd.na.flag.rsv == 0
|
||||
""",
|
||||
PBBR_ETH=PBBR_ETH)
|
||||
|
||||
# Host should send NS for ROUTER1's DUA
|
||||
pkts.filter_eth_src(HOST_ETH).filter_icmpv6_nd_ns(ROUTER1_DUA).must_next()
|
||||
# PBBR must send solicited NA for ROUTER1's DUA
|
||||
pkts.filter_eth_src(PBBR_ETH).filter_ipv6_dst(HOST_BGUA).filter_icmpv6_nd_na(ROUTER1_DUA).filter(
|
||||
'icmpv6.nd.na.flag.s == 1').must_next().must_verify("""
|
||||
icmpv6.opt.linkaddr == {PBBR_ETH}
|
||||
and icmpv6.nd.na.flag.r == 1
|
||||
and icmpv6.nd.na.flag.o == 0
|
||||
and icmpv6.nd.na.flag.rsv == 0
|
||||
""",
|
||||
PBBR_ETH=PBBR_ETH)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -217,6 +217,9 @@ class OtbrDocker:
|
||||
else:
|
||||
return lines
|
||||
|
||||
def _setup_sysctl(self):
|
||||
self.bash(f'sysctl net.ipv6.conf.{self.ETH_DEV}.accept_ra=2')
|
||||
|
||||
|
||||
class OtCli:
|
||||
|
||||
@@ -2018,6 +2021,14 @@ class LinuxHost():
|
||||
else:
|
||||
return super().ping(*args, **kwargs)
|
||||
|
||||
def ip_neighbors_flush(self):
|
||||
# clear neigh cache on linux
|
||||
self.bash(f'ip -6 neigh list dev {self.ETH_DEV}')
|
||||
self.bash(f'ip -6 neigh flush nud all nud failed nud noarp dev {self.ETH_DEV}')
|
||||
self.bash('ip -6 neigh list nud all dev %s | cut -d " " -f1 | sudo xargs -I{} ip -6 neigh delete {} dev %s' %
|
||||
(self.ETH_DEV, self.ETH_DEV))
|
||||
self.bash(f'ip -6 neigh list dev {self.ETH_DEV}')
|
||||
|
||||
|
||||
class OtbrNode(LinuxHost, NodeImpl, OtbrDocker):
|
||||
is_otbr = True
|
||||
@@ -2030,6 +2041,10 @@ class OtbrNode(LinuxHost, NodeImpl, OtbrDocker):
|
||||
def get_addrs(self) -> List[str]:
|
||||
return super().get_addrs() + self.get_ether_addrs()
|
||||
|
||||
def start(self):
|
||||
self._setup_sysctl()
|
||||
super().start()
|
||||
|
||||
|
||||
class HostNode(LinuxHost, OtbrDocker):
|
||||
is_host = True
|
||||
@@ -2040,11 +2055,11 @@ class HostNode(LinuxHost, OtbrDocker):
|
||||
super().__init__(nodeid, **kwargs)
|
||||
|
||||
def start(self):
|
||||
# TODO: Use radvd to advertise the Domain Prefix on the Backbone link.
|
||||
pass
|
||||
self._setup_sysctl()
|
||||
self._service_radvd_start()
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
self._service_radvd_stop()
|
||||
|
||||
def get_addrs(self) -> List[str]:
|
||||
return self.get_ether_addrs()
|
||||
@@ -2068,6 +2083,31 @@ class HostNode(LinuxHost, OtbrDocker):
|
||||
else:
|
||||
return None
|
||||
|
||||
def _service_radvd_start(self):
|
||||
self.bash("""cat >/etc/radvd.conf <<EOF
|
||||
interface eth0
|
||||
{
|
||||
AdvSendAdvert on;
|
||||
|
||||
MinRtrAdvInterval 3;
|
||||
MaxRtrAdvInterval 30;
|
||||
AdvDefaultPreference low;
|
||||
|
||||
prefix %s
|
||||
{
|
||||
AdvOnLink on;
|
||||
AdvAutonomous off;
|
||||
AdvRouterAddr off;
|
||||
};
|
||||
};
|
||||
EOF
|
||||
""" % config.DOMAIN_PREFIX)
|
||||
self.bash('service radvd start')
|
||||
self.bash('service radvd status') # Make sure radvd service is running
|
||||
|
||||
def _service_radvd_stop(self):
|
||||
self.bash('service radvd stop')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -81,7 +81,7 @@ class Ipv6Addr(Bytes):
|
||||
|
||||
super().__init__(addr)
|
||||
if len(self) != 16:
|
||||
raise ValueError((addr, self))
|
||||
raise ValueError(addr)
|
||||
self._addr = ipaddress.IPv6Address(self)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
@@ -85,6 +85,7 @@ def cleanup_env():
|
||||
|
||||
|
||||
def setup_env():
|
||||
bash('sudo modprobe ip6table_filter')
|
||||
bash(f'docker image inspect {config.OTBR_DOCKER_IMAGE} >/dev/null')
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user