[dhcp6] add helper methods for option parsing and generation (#11551)

This commit introduces new helper methods for DHCPv6 Option parsing
and generation.

- It adds `Option::FindOption()` to search and parse the first DHCPv6
  option with a given code within a specified range of a `Message`.
- It also adds `Option::UpdateOptionLengthInMessage()`, which updates
  the Option length in a message based on the number of  bytes
  appended, simplifying the appending of variable-length options
  (e.g., `IaNaOption`, which can contain multiple sub-options).
- Additionally, `StatusCodeOption::ReadStatusFrom()` is added to read
  the status code from a Status Code Option in a specified range
  within a `Message`. The absence of a Status Code option implies
  success.
- Helper methods to search for and append `RapidCommitOption` are also
  included.

These new helper methods are then used by `Dhcp6::Server` and
`Dhcp6::Client`, simplifying the code, particularly for processing
options in received DHCPv6 messages.

This commit also adds a new `test-036-dhcp-prefix-netdata.py` test to
validate the publishing of prefixes with the DHCP flag in Network
Data and the behavior of the DHCPv6 client and server.
This commit is contained in:
Abtin Keshavarzian
2025-06-02 12:51:48 -07:00
committed by GitHub
parent c2de9f646a
commit 02b8b2cda6
12 changed files with 569 additions and 317 deletions
+2 -1
View File
@@ -547,11 +547,12 @@ openthread_core_files = [
"meshcop/timestamp.hpp",
"net/checksum.cpp",
"net/checksum.hpp",
"net/dhcp6.hpp",
"net/dhcp6_client.cpp",
"net/dhcp6_client.hpp",
"net/dhcp6_server.cpp",
"net/dhcp6_server.hpp",
"net/dhcp6_types.cpp",
"net/dhcp6_types.hpp",
"net/dns_client.cpp",
"net/dns_client.hpp",
"net/dns_dso.cpp",
+1
View File
@@ -170,6 +170,7 @@ set(COMMON_SOURCES
net/checksum.cpp
net/dhcp6_client.cpp
net/dhcp6_server.cpp
net/dhcp6_types.cpp
net/dns_client.cpp
net/dns_dso.cpp
net/dns_platform.cpp
+1 -1
View File
@@ -91,9 +91,9 @@
#include "meshcop/joiner_router.hpp"
#include "meshcop/meshcop_leader.hpp"
#include "meshcop/network_name.hpp"
#include "net/dhcp6.hpp"
#include "net/dhcp6_client.hpp"
#include "net/dhcp6_server.hpp"
#include "net/dhcp6_types.hpp"
#include "net/dns_client.hpp"
#include "net/dns_dso.hpp"
#include "net/dnssd.hpp"
+68 -147
View File
@@ -263,8 +263,6 @@ void Client::Solicit(uint16_t aRloc16)
SuccessOrExit(error = AppendElapsedTimeOption(*message));
SuccessOrExit(error = AppendClientIdOption(*message));
SuccessOrExit(error = AppendIaNaOption(*message, aRloc16));
// specify which prefixes to solicit
SuccessOrExit(error = AppendIaAddressOption(*message, aRloc16));
SuccessOrExit(error = AppendRapidCommitOption(*message));
#if OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT
@@ -322,73 +320,44 @@ Error Client::AppendClientIdOption(Message &aMessage)
Error Client::AppendIaNaOption(Message &aMessage, uint16_t aRloc16)
{
Error error = kErrorNone;
uint8_t count = 0;
uint16_t length = 0;
IaNaOption option;
Error error = kErrorNone;
uint16_t optionOffset;
IaNaOption option;
IaAddressOption addressOption;
VerifyOrExit(mIdentityAssociationCurrent != nullptr, error = kErrorDrop);
for (IdentityAssociation &idAssociation : mIdentityAssociations)
{
if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mStatus == kIaStatusSolicitReplied)
{
continue;
}
if (idAssociation.mPrefixAgentRloc == aRloc16)
{
count++;
}
}
// compute the right length
length = sizeof(IaNaOption) + sizeof(IaAddressOption) * count - sizeof(Option);
optionOffset = aMessage.GetLength();
option.Init();
option.SetLength(length);
option.SetIaid(0);
option.SetT1(0);
option.SetT2(0);
SuccessOrExit(error = aMessage.Append(option));
exit:
return error;
}
// Append `IaAddressOption`s within the `IaNa`
Error Client::AppendIaAddressOption(Message &aMessage, uint16_t aRloc16)
{
Error error = kErrorNone;
IaAddressOption option;
VerifyOrExit(mIdentityAssociationCurrent, error = kErrorDrop);
option.Init();
addressOption.Init();
for (IdentityAssociation &idAssociation : mIdentityAssociations)
{
if ((idAssociation.mStatus == kIaStatusSolicit || idAssociation.mStatus == kIaStatusSoliciting) &&
(idAssociation.mPrefixAgentRloc == aRloc16))
{
option.SetAddress(idAssociation.mNetifAddress.GetAddress());
option.SetPreferredLifetime(0);
option.SetValidLifetime(0);
SuccessOrExit(error = aMessage.Append(option));
addressOption.SetAddress(idAssociation.mNetifAddress.GetAddress());
addressOption.SetPreferredLifetime(0);
addressOption.SetValidLifetime(0);
SuccessOrExit(error = aMessage.Append(addressOption));
}
}
// Update the `IaNaOption` length.
Option::UpdateOptionLengthInMessage(aMessage, optionOffset);
exit:
return error;
}
Error Client::AppendRapidCommitOption(Message &aMessage)
{
RapidCommitOption option;
option.Init();
return aMessage.Append(option);
}
void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
OT_UNUSED_VARIABLE(aMessageInfo);
@@ -407,31 +376,13 @@ exit:
return;
}
void Client::ProcessReply(Message &aMessage)
void Client::ProcessReply(const Message &aMessage)
{
uint16_t offset = aMessage.GetOffset();
uint16_t length = aMessage.GetLength() - aMessage.GetOffset();
uint16_t optionOffset;
if ((optionOffset = FindOption(aMessage, offset, length, Option::kStatusCode)) > 0)
{
SuccessOrExit(ProcessStatusCodeOption(aMessage, optionOffset));
}
// Server Identifier
VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, Option::kServerId)) > 0);
SuccessOrExit(ProcessServerIdOption(aMessage, optionOffset));
// Client Identifier
VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, Option::kClientId)) > 0);
SuccessOrExit(ProcessClientIdOption(aMessage, optionOffset));
// Rapid Commit
VerifyOrExit(FindOption(aMessage, offset, length, Option::kRapidCommit) > 0);
// IA_NA
VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, Option::kIaNa)) > 0);
SuccessOrExit(ProcessIaNaOption(aMessage, optionOffset));
VerifyOrExit(StatusCodeOption::ReadStatusFrom(aMessage) == StatusCodeOption::kSuccess);
SuccessOrExit(ProcessServerIdOption(aMessage));
SuccessOrExit(ProcessClientIdOption(aMessage));
SuccessOrExit(RapidCommitOption::FindIn(aMessage));
SuccessOrExit(ProcessIaNaOption(aMessage));
HandleTrickleTimer();
@@ -439,36 +390,15 @@ exit:
return;
}
uint16_t Client::FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Option::Code aCode)
Error Client::ProcessServerIdOption(const Message &aMessage)
{
uint32_t offset = aOffset;
uint16_t end = aOffset + aLength;
uint16_t rval = 0;
while (offset <= end)
{
Option option;
SuccessOrExit(aMessage.Read(static_cast<uint16_t>(offset), option));
if (option.GetCode() == aCode)
{
ExitNow(rval = static_cast<uint16_t>(offset));
}
offset += sizeof(option) + option.GetLength();
}
exit:
return rval;
}
Error Client::ProcessServerIdOption(Message &aMessage, uint16_t aOffset)
{
Error error = kErrorNone;
Error error;
OffsetRange offsetRange;
ServerIdOption option;
SuccessOrExit(aMessage.Read(aOffset, option));
SuccessOrExit(error = Option::FindOption(aMessage, Option::kServerId, offsetRange));
SuccessOrExit(error = aMessage.Read(offsetRange, option));
VerifyOrExit(((option.GetDuidType() == kDuidLinkLayerAddressPlusTime) &&
(option.GetDuidHardwareType() == kHardwareTypeEthernet)) ||
((option.GetLength() == (sizeof(option) - sizeof(Option))) &&
@@ -479,80 +409,71 @@ exit:
return error;
}
Error Client::ProcessClientIdOption(Message &aMessage, uint16_t aOffset)
Error Client::ProcessClientIdOption(const Message &aMessage)
{
Error error = kErrorNone;
Error error;
OffsetRange offsetRange;
ClientIdOption option;
Mac::ExtAddress eui64;
Get<Radio>().GetIeeeEui64(eui64);
SuccessOrExit(error = aMessage.Read(aOffset, option));
VerifyOrExit(
(option.GetLength() == (sizeof(option) - sizeof(Option))) && (option.GetDuidType() == kDuidLinkLayerAddress) &&
(option.GetDuidHardwareType() == kHardwareTypeEui64) && (option.GetDuidLinkLayerAddress() == eui64),
error = kErrorParse);
SuccessOrExit(error = Option::FindOption(aMessage, Option::kClientId, offsetRange));
SuccessOrExit(error = aMessage.Read(offsetRange, option));
VerifyOrExit(option.GetDuidType() == kDuidLinkLayerAddress, error = kErrorParse);
VerifyOrExit(option.GetDuidHardwareType() == kHardwareTypeEui64, error = kErrorParse);
VerifyOrExit(option.GetDuidLinkLayerAddress() == eui64, error = kErrorParse);
exit:
return error;
}
Error Client::ProcessIaNaOption(Message &aMessage, uint16_t aOffset)
Error Client::ProcessIaNaOption(const Message &aMessage)
{
Error error = kErrorNone;
IaNaOption option;
uint16_t optionOffset;
uint16_t length;
Error error = kErrorNone;
OffsetRange offsetRange;
IaNaOption option;
SuccessOrExit(error = aMessage.Read(aOffset, option));
SuccessOrExit(error = Option::FindOption(aMessage, Option::kIaNa, offsetRange));
SuccessOrExit(error = aMessage.Read(offsetRange, option));
aOffset += sizeof(option);
length = option.GetLength() - (sizeof(option) - sizeof(Option));
offsetRange.AdvanceOffset(sizeof(IaNaOption));
VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = kErrorParse);
// Iterate over and check the sub-options within `IaNaOption`.
if ((optionOffset = FindOption(aMessage, aOffset, length, Option::kStatusCode)) > 0)
VerifyOrExit(StatusCodeOption::ReadStatusFrom(aMessage, offsetRange) == StatusCodeOption::kSuccess,
error = kErrorFailed);
while (!offsetRange.IsEmpty())
{
SuccessOrExit(error = ProcessStatusCodeOption(aMessage, optionOffset));
}
OffsetRange subOffsetRange;
IaAddressOption addressOption;
while (length > 0)
{
if ((optionOffset = FindOption(aMessage, aOffset, length, Option::kIaAddress)) == 0)
error = Option::FindOption(aMessage, offsetRange, Option::kIaAddress, subOffsetRange);
if (error == kErrorNotFound)
{
error = kErrorNone;
ExitNow();
}
SuccessOrExit(error = ProcessIaAddressOption(aMessage, optionOffset));
SuccessOrExit(error);
length -= ((optionOffset - aOffset) + sizeof(IaAddressOption));
aOffset = optionOffset + sizeof(IaAddressOption);
SuccessOrExit(error = aMessage.Read(subOffsetRange, addressOption));
SuccessOrExit(error = ProcessIaAddressOption(addressOption));
// Update `offsetRange` to after the parsed `IaAddressOption`
offsetRange.InitFromRange(subOffsetRange.GetEndOffset(), offsetRange.GetEndOffset());
}
exit:
return error;
}
Error Client::ProcessStatusCodeOption(Message &aMessage, uint16_t aOffset)
Error Client::ProcessIaAddressOption(const IaAddressOption &aOption)
{
Error error = kErrorNone;
StatusCodeOption option;
SuccessOrExit(error = aMessage.Read(aOffset, option));
VerifyOrExit((option.GetLength() >= sizeof(option) - sizeof(Option)) &&
(option.GetStatusCode() == StatusCodeOption::kSuccess),
error = kErrorParse);
exit:
return error;
}
Error Client::ProcessIaAddressOption(Message &aMessage, uint16_t aOffset)
{
Error error;
IaAddressOption option;
SuccessOrExit(error = aMessage.Read(aOffset, option));
VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
Error error = kErrorNone;
for (IdentityAssociation &idAssociation : mIdentityAssociations)
{
@@ -561,15 +482,15 @@ Error Client::ProcessIaAddressOption(Message &aMessage, uint16_t aOffset)
continue;
}
if (idAssociation.mNetifAddress.GetAddress().PrefixMatch(option.GetAddress()) >=
if (idAssociation.mNetifAddress.GetAddress().PrefixMatch(aOption.GetAddress()) >=
idAssociation.mNetifAddress.mPrefixLength)
{
idAssociation.mNetifAddress.mAddress = option.GetAddress();
idAssociation.mPreferredLifetime = option.GetPreferredLifetime();
idAssociation.mValidLifetime = option.GetValidLifetime();
idAssociation.mNetifAddress.mAddress = aOption.GetAddress();
idAssociation.mPreferredLifetime = aOption.GetPreferredLifetime();
idAssociation.mValidLifetime = aOption.GetValidLifetime();
idAssociation.mNetifAddress.mAddressOrigin = Ip6::Netif::kOriginDhcp6;
idAssociation.mNetifAddress.mPreferred = option.GetPreferredLifetime() != 0;
idAssociation.mNetifAddress.mValid = option.GetValidLifetime() != 0;
idAssociation.mNetifAddress.mPreferred = aOption.GetPreferredLifetime() != 0;
idAssociation.mNetifAddress.mValid = aOption.GetValidLifetime() != 0;
idAssociation.mStatus = kIaStatusSolicitReplied;
Get<ThreadNetif>().AddUnicastAddress(idAssociation.mNetifAddress);
ExitNow(error = kErrorNone);
+7 -10
View File
@@ -46,7 +46,7 @@
#include "common/trickle_timer.hpp"
#include "mac/mac.hpp"
#include "mac/mac_types.hpp"
#include "net/dhcp6.hpp"
#include "net/dhcp6_types.hpp"
#include "net/netif.hpp"
#include "net/udp6.hpp"
@@ -110,19 +110,16 @@ private:
Error AppendHeader(Message &aMessage);
Error AppendClientIdOption(Message &aMessage);
Error AppendIaNaOption(Message &aMessage, uint16_t aRloc16);
Error AppendIaAddressOption(Message &aMessage, uint16_t aRloc16);
Error AppendElapsedTimeOption(Message &aMessage);
Error AppendRapidCommitOption(Message &aMessage);
Error AppendRapidCommitOption(Message &aMessage) { return RapidCommitOption::AppendTo(aMessage); }
void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
void ProcessReply(Message &aMessage);
uint16_t FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Option::Code aCode);
Error ProcessServerIdOption(Message &aMessage, uint16_t aOffset);
Error ProcessClientIdOption(Message &aMessage, uint16_t aOffset);
Error ProcessIaNaOption(Message &aMessage, uint16_t aOffset);
Error ProcessStatusCodeOption(Message &aMessage, uint16_t aOffset);
Error ProcessIaAddressOption(Message &aMessage, uint16_t aOffset);
void ProcessReply(const Message &aMessage);
Error ProcessServerIdOption(const Message &aMessage);
Error ProcessClientIdOption(const Message &aMessage);
Error ProcessIaNaOption(const Message &aMessage);
Error ProcessIaAddressOption(const IaAddressOption &aOption);
void HandleNotifierEvents(Events aEvents);
void UpdateAddresses(void);
+95 -122
View File
@@ -191,138 +191,125 @@ exit:
void Server::ProcessSolicit(Message &aMessage, const Ip6::Address &aDst, const TransactionId &aTransactionId)
{
IaNaOption iana;
ClientIdOption clientIdentifier;
uint16_t optionOffset;
uint16_t offset = aMessage.GetOffset();
uint16_t length = aMessage.GetLength() - aMessage.GetOffset();
uint32_t iaid;
ClientIdOption clientIdOption;
OffsetRange offsetRange;
// Client Identifier (discard if not present)
VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, Option::kClientId)) > 0);
SuccessOrExit(ProcessClientIdOption(aMessage, optionOffset, clientIdentifier));
SuccessOrExit(ProcessClientIdOption(aMessage, clientIdOption));
// Server Identifier (assuming Rapid Commit, discard if present)
VerifyOrExit(FindOption(aMessage, offset, length, Option::kServerId) == 0);
VerifyOrExit(Option::FindOption(aMessage, Option::kServerId, offsetRange) == kErrorNotFound);
// Rapid Commit (assuming Rapid Commit, discard if not present)
VerifyOrExit(FindOption(aMessage, offset, length, Option::kRapidCommit) > 0);
SuccessOrExit(RapidCommitOption::FindIn(aMessage));
// Elapsed Time if present
if ((optionOffset = FindOption(aMessage, offset, length, Option::kElapsedTime)) > 0)
{
SuccessOrExit(ProcessElapsedTimeOption(aMessage, optionOffset));
}
SuccessOrExit(ProcessElapsedTimeOption(aMessage));
// IA_NA (discard if not present)
VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, Option::kIaNa)) > 0);
SuccessOrExit(ProcessIaNaOption(aMessage, optionOffset, iana));
SuccessOrExit(ProcessIaNaOption(aMessage, iaid));
SuccessOrExit(SendReply(aDst, aTransactionId, clientIdentifier, iana));
SuccessOrExit(SendReply(aDst, aTransactionId, clientIdOption, iaid));
exit:
return;
}
uint16_t Server::FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Option::Code aCode)
Error Server::ProcessClientIdOption(const Message &aMessage, ClientIdOption &aClientIdOption)
{
uint16_t end = aOffset + aLength;
uint16_t rval = 0;
Error error = kErrorNone;
OffsetRange offsetRange;
while (aOffset <= end)
SuccessOrExit(error = Option::FindOption(aMessage, Option::kClientId, offsetRange));
SuccessOrExit(error = aMessage.Read(offsetRange, aClientIdOption));
VerifyOrExit(aClientIdOption.GetDuidType() == kDuidLinkLayerAddress, error = kErrorParse);
VerifyOrExit(aClientIdOption.GetDuidHardwareType() == kHardwareTypeEui64, error = kErrorParse);
exit:
return error;
}
Error Server::ProcessElapsedTimeOption(const Message &aMessage)
{
Error error = kErrorNone;
OffsetRange offsetRange;
// Check Elapsed Time to be valid only if present.
error = Option::FindOption(aMessage, Option::kElapsedTime, offsetRange);
if (error == kErrorNotFound)
{
Option option;
SuccessOrExit(aMessage.Read(aOffset, option));
if (option.GetCode() == aCode)
{
ExitNow(rval = aOffset);
}
aOffset += sizeof(option) + option.GetLength();
ExitNow();
}
exit:
return rval;
}
Error Server::ProcessClientIdOption(Message &aMessage, uint16_t aOffset, ClientIdOption &aClientIdOption)
{
Error error = kErrorNone;
SuccessOrExit(error);
VerifyOrExit(offsetRange.GetLength() >= sizeof(ElapsedTimeOption), error = kErrorParse);
SuccessOrExit(error = aMessage.Read(aOffset, aClientIdOption));
VerifyOrExit((aClientIdOption.GetLength() == sizeof(aClientIdOption) - sizeof(Option)) &&
(aClientIdOption.GetDuidType() == kDuidLinkLayerAddress) &&
(aClientIdOption.GetDuidHardwareType() == kHardwareTypeEui64),
error = kErrorParse);
exit:
return error;
}
Error Server::ProcessElapsedTimeOption(Message &aMessage, uint16_t aOffset)
Error Server::ProcessIaNaOption(const Message &aMessage, uint32_t &aIaid)
{
Error error = kErrorNone;
ElapsedTimeOption option;
Error error = kErrorNone;
OffsetRange offsetRange;
IaNaOption iaNaOption;
SuccessOrExit(error = aMessage.Read(aOffset, option));
VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
exit:
return error;
}
SuccessOrExit(error = Option::FindOption(aMessage, Option::kIaNa, offsetRange));
SuccessOrExit(error = aMessage.Read(offsetRange, iaNaOption));
Error Server::ProcessIaNaOption(Message &aMessage, uint16_t aOffset, IaNaOption &aIaNaOption)
{
Error error = kErrorNone;
uint16_t optionOffset;
uint16_t length;
aIaid = iaNaOption.GetIaid();
SuccessOrExit(error = aMessage.Read(aOffset, aIaNaOption));
aOffset += sizeof(aIaNaOption);
length = aIaNaOption.GetLength() + sizeof(Option) - sizeof(IaNaOption);
VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = kErrorParse);
offsetRange.AdvanceOffset(sizeof(IaNaOption));
mPrefixAgentsMask = 0;
while (length > 0)
{
VerifyOrExit((optionOffset = FindOption(aMessage, aOffset, length, Option::kIaAddress)) > 0);
SuccessOrExit(error = ProcessIaAddressOption(aMessage, optionOffset));
// Iterate and parse sub-options within `IaNaOption`.
length -= ((optionOffset - aOffset) + sizeof(IaAddressOption));
aOffset = optionOffset + sizeof(IaAddressOption);
while (!offsetRange.IsEmpty())
{
OffsetRange subOffsetRange;
IaAddressOption addressOption;
error = Option::FindOption(aMessage, offsetRange, Option::kIaAddress, subOffsetRange);
if (error == kErrorNotFound)
{
error = kErrorNone;
ExitNow();
}
SuccessOrExit(error);
SuccessOrExit(error = aMessage.Read(subOffsetRange, addressOption));
ProcessIaAddressOption(addressOption);
// Update `offsetRange` to after the parsed `IaAddressOption`
offsetRange.InitFromRange(subOffsetRange.GetEndOffset(), offsetRange.GetEndOffset());
}
exit:
return error;
}
Error Server::ProcessIaAddressOption(Message &aMessage, uint16_t aOffset)
void Server::ProcessIaAddressOption(const IaAddressOption &aAddressOption)
{
Error error = kErrorNone;
IaAddressOption option;
SuccessOrExit(error = aMessage.Read(aOffset, option));
VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
// mask matching prefix
for (uint16_t i = 0; i < GetArrayLength(mPrefixAgents); i++)
{
if (mPrefixAgents[i].IsValid() && mPrefixAgents[i].IsPrefixMatch(option.GetAddress()))
if (mPrefixAgents[i].IsValid() && mPrefixAgents[i].IsPrefixMatch(aAddressOption.GetAddress()))
{
mPrefixAgentsMask |= (1 << i);
break;
}
}
exit:
return error;
}
Error Server::SendReply(const Ip6::Address &aDst,
const TransactionId &aTransactionId,
ClientIdOption &aClientIdOption,
IaNaOption &aIaNaOption)
Error Server::SendReply(const Ip6::Address &aDst,
const TransactionId &aTransactionId,
const ClientIdOption &aClientIdOption,
uint32_t aIaid)
{
Error error = kErrorNone;
Ip6::MessageInfo messageInfo;
@@ -332,9 +319,7 @@ Error Server::SendReply(const Ip6::Address &aDst,
SuccessOrExit(error = AppendHeader(*message, aTransactionId));
SuccessOrExit(error = AppendServerIdOption(*message));
SuccessOrExit(error = AppendClientIdOption(*message, aClientIdOption));
SuccessOrExit(error = AppendIaNaOption(*message, aIaNaOption));
SuccessOrExit(error = AppendStatusCodeOption(*message, StatusCodeOption::kSuccess));
SuccessOrExit(error = AppendIaAddressOption(*message, aClientIdOption));
SuccessOrExit(error = AppendIaNaOption(*message, aIaid, aClientIdOption.GetDuidLinkLayerAddress()));
SuccessOrExit(error = AppendRapidCommitOption(*message));
messageInfo.SetPeerAddr(aDst);
@@ -356,7 +341,7 @@ Error Server::AppendHeader(Message &aMessage, const TransactionId &aTransactionI
return aMessage.Append(header);
}
Error Server::AppendClientIdOption(Message &aMessage, ClientIdOption &aClientIdOption)
Error Server::AppendClientIdOption(Message &aMessage, const ClientIdOption &aClientIdOption)
{
return aMessage.Append(aClientIdOption);
}
@@ -379,32 +364,25 @@ exit:
return error;
}
Error Server::AppendIaNaOption(Message &aMessage, IaNaOption &aIaNaOption)
Error Server::AppendIaNaOption(Message &aMessage, uint32_t aIaid, const Mac::ExtAddress &aClientAddress)
{
Error error = kErrorNone;
uint16_t length = 0;
Error error = kErrorNone;
IaNaOption iaNaOption;
uint16_t optionOffset;
if (mPrefixAgentsMask)
{
for (uint16_t i = 0; i < GetArrayLength(mPrefixAgents); i++)
{
if (mPrefixAgentsMask & (1 << i))
{
length += sizeof(IaAddressOption);
}
}
}
else
{
length += sizeof(IaAddressOption) * mPrefixAgentsCount;
}
optionOffset = aMessage.GetLength();
length += sizeof(IaNaOption) + sizeof(StatusCodeOption) - sizeof(Option);
iaNaOption.Init();
iaNaOption.SetIaid(aIaid);
iaNaOption.SetT1(IaNaOption::kDefaultT1);
iaNaOption.SetT2(IaNaOption::kDefaultT2);
SuccessOrExit(error = aMessage.Append(iaNaOption));
aIaNaOption.SetLength(length);
aIaNaOption.SetT1(IaNaOption::kDefaultT1);
aIaNaOption.SetT2(IaNaOption::kDefaultT2);
SuccessOrExit(error = aMessage.Append(aIaNaOption));
SuccessOrExit(error = AppendStatusCodeOption(aMessage, StatusCodeOption::kSuccess));
SuccessOrExit(error = AppendIaAddressOptions(aMessage, aClientAddress));
// Update IaNaOption length
Option::UpdateOptionLengthInMessage(aMessage, optionOffset);
exit:
return error;
@@ -419,7 +397,7 @@ Error Server::AppendStatusCodeOption(Message &aMessage, StatusCodeOption::Status
return aMessage.Append(option);
}
Error Server::AppendIaAddressOption(Message &aMessage, ClientIdOption &aClientIdOption)
Error Server::AppendIaAddressOptions(Message &aMessage, const Mac::ExtAddress &aClientAddress)
{
Error error = kErrorNone;
@@ -430,8 +408,8 @@ Error Server::AppendIaAddressOption(Message &aMessage, ClientIdOption &aClientId
{
if (mPrefixAgentsMask & (1 << i))
{
SuccessOrExit(error =
AddIaAddressOption(aMessage, mPrefixAgents[i].GetPrefixAsAddress(), aClientIdOption));
SuccessOrExit(
error = AppendIaAddressOption(aMessage, mPrefixAgents[i].GetPrefixAsAddress(), aClientAddress));
}
}
}
@@ -442,7 +420,8 @@ Error Server::AppendIaAddressOption(Message &aMessage, ClientIdOption &aClientId
{
if (prefixAgent.IsValid())
{
SuccessOrExit(error = AddIaAddressOption(aMessage, prefixAgent.GetPrefixAsAddress(), aClientIdOption));
SuccessOrExit(error =
AppendIaAddressOption(aMessage, prefixAgent.GetPrefixAsAddress(), aClientAddress));
}
}
}
@@ -451,14 +430,16 @@ exit:
return error;
}
Error Server::AddIaAddressOption(Message &aMessage, const Ip6::Address &aPrefix, ClientIdOption &aClientIdOption)
Error Server::AppendIaAddressOption(Message &aMessage,
const Ip6::Address &aPrefix,
const Mac::ExtAddress &aClientAddress)
{
Error error = kErrorNone;
IaAddressOption option;
option.Init();
option.GetAddress().SetPrefix(aPrefix.mFields.m8, OT_IP6_PREFIX_BITSIZE);
option.GetAddress().GetIid().SetFromExtAddress(aClientIdOption.GetDuidLinkLayerAddress());
option.GetAddress().GetIid().SetFromExtAddress(aClientAddress);
option.SetPreferredLifetime(IaAddressOption::kDefaultPreferredLifetime);
option.SetValidLifetime(IaAddressOption::kDefaultValidLifetime);
SuccessOrExit(error = aMessage.Append(option));
@@ -467,14 +448,6 @@ exit:
return error;
}
Error Server::AppendRapidCommitOption(Message &aMessage)
{
RapidCommitOption option;
option.Init();
return aMessage.Append(option);
}
} // namespace Dhcp6
} // namespace ot
+19 -26
View File
@@ -43,7 +43,7 @@
#include "common/notifier.hpp"
#include "mac/mac.hpp"
#include "mac/mac_types.hpp"
#include "net/dhcp6.hpp"
#include "net/dhcp6_types.hpp"
#include "net/udp6.hpp"
#include "thread/network_data_leader.hpp"
@@ -163,37 +163,30 @@ private:
static constexpr uint16_t kNumPrefixes = OPENTHREAD_CONFIG_DHCP6_SERVER_NUM_PREFIXES;
void HandleNotifierEvents(Events aEvents);
void UpdateService(void);
void Start(void);
void Stop(void);
void AddPrefixAgent(const Ip6::Prefix &aIp6Prefix, const Lowpan::Context &aContext);
void HandleNotifierEvents(Events aEvents);
void UpdateService(void);
void Start(void);
void Stop(void);
void AddPrefixAgent(const Ip6::Prefix &aIp6Prefix, const Lowpan::Context &aContext);
Error AppendHeader(Message &aMessage, const TransactionId &aTransactionId);
Error AppendClientIdOption(Message &aMessage, ClientIdOption &aClientIdOption);
Error AppendClientIdOption(Message &aMessage, const ClientIdOption &aClientIdOption);
Error AppendServerIdOption(Message &aMessage);
Error AppendIaNaOption(Message &aMessage, IaNaOption &aIaNaOption);
Error AppendIaNaOption(Message &aMessage, uint32_t aIaid, const Mac::ExtAddress &aClientAddress);
Error AppendStatusCodeOption(Message &aMessage, StatusCodeOption::Status aStatusCode);
Error AppendIaAddressOption(Message &aMessage, ClientIdOption &aClientIdOption);
Error AppendRapidCommitOption(Message &aMessage);
Error AppendIaAddressOptions(Message &aMessage, const Mac::ExtAddress &aClientAddress);
Error AppendRapidCommitOption(Message &aMessage) { return RapidCommitOption::AppendTo(aMessage); }
Error AppendVendorSpecificInformation(Message &aMessage);
Error AddIaAddressOption(Message &aMessage, const Ip6::Address &aPrefix, ClientIdOption &aClientIdOption);
Error AppendIaAddressOption(Message &aMessage, const Ip6::Address &aPrefix, const Mac::ExtAddress &aClientAddress);
void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
void ProcessSolicit(Message &aMessage, const Ip6::Address &aDst, const TransactionId &aTransactionId);
uint16_t FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Option::Code aCode);
Error ProcessClientIdOption(Message &aMessage, uint16_t aOffset, ClientIdOption &aClientIdOption);
Error ProcessIaNaOption(Message &aMessage, uint16_t aOffset, IaNaOption &aIaNaOption);
Error ProcessIaAddressOption(Message &aMessage, uint16_t aOffset);
Error ProcessElapsedTimeOption(Message &aMessage, uint16_t aOffset);
Error SendReply(const Ip6::Address &aDst,
const TransactionId &aTransactionId,
ClientIdOption &aClientIdOption,
IaNaOption &aIaNaOption);
Error ProcessClientIdOption(const Message &aMessage, ClientIdOption &aClientIdOption);
Error ProcessIaNaOption(const Message &aMessage, uint32_t &aIaid);
void ProcessIaAddressOption(const IaAddressOption &aAddressOption);
Error ProcessElapsedTimeOption(const Message &aMessage);
Error SendReply(const Ip6::Address &aDst,
const TransactionId &aTransactionId,
const ClientIdOption &aClientIdOption,
uint32_t aIaid);
using ServerSocket = Ip6::Udp::SocketIn<Server, &Server::HandleUdpReceive>;
+152
View File
@@ -0,0 +1,152 @@
/*
* Copyright (c) 2025, 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.
*/
/**
* @file
* This file implements generating and processing of DHCPv6 options.
*/
#include "dhcp6_types.hpp"
#if OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE || OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
namespace ot {
namespace Dhcp6 {
//---------------------------------------------------------------------------------------------------------------------
// Option
Error Option::FindOption(const Message &aMessage, Code aCode, OffsetRange &aOptionOffsetRange)
{
OffsetRange msgOffsetRange;
msgOffsetRange.InitFromMessageOffsetToEnd(aMessage);
return FindOption(aMessage, msgOffsetRange, aCode, aOptionOffsetRange);
}
Error Option::FindOption(const Message &aMessage,
const OffsetRange &aMsgOffsetRange,
Code aCode,
OffsetRange &aOptionOffsetRange)
{
Error error;
OffsetRange offsetRange;
// Restrict `offsetRange to `aMessage.GetLength()`. This way we
// know if option is within `offsetRange` it is also fully
// contained within the `aMessage`.
offsetRange.InitFromRange(aMsgOffsetRange.GetOffset(), Min(aMessage.GetLength(), aMsgOffsetRange.GetEndOffset()));
while (!offsetRange.IsEmpty())
{
Option option;
SuccessOrExit(error = aMessage.Read(offsetRange, option));
VerifyOrExit(offsetRange.Contains(option.GetSize()), error = kErrorParse);
if (option.GetCode() == aCode)
{
aOptionOffsetRange = offsetRange;
aOptionOffsetRange.ShrinkLength(static_cast<uint16_t>(option.GetSize()));
ExitNow();
}
offsetRange.AdvanceOffset(option.GetSize());
}
error = kErrorNotFound;
exit:
return error;
}
void Option::UpdateOptionLengthInMessage(Message &aMessage, uint16_t aOffset)
{
Option option;
IgnoreError(aMessage.Read(aOffset, option));
option.SetLength(aMessage.GetLength() - aOffset - sizeof(Option));
aMessage.Write(aOffset, option);
}
//---------------------------------------------------------------------------------------------------------------------
// StatusCodeOption
StatusCodeOption::Status StatusCodeOption::ReadStatusFrom(const Message &aMessage)
{
OffsetRange msgOffsetRange;
msgOffsetRange.InitFromMessageOffsetToEnd(aMessage);
return ReadStatusFrom(aMessage, msgOffsetRange);
}
StatusCodeOption::Status StatusCodeOption::ReadStatusFrom(const Message &aMessage, const OffsetRange &aMsgOffsetRange)
{
Status status = kSuccess;
StatusCodeOption statusOption;
OffsetRange optionOffsetRange;
// Per RFC 8415, the absence of a Status Code option implies success
SuccessOrExit(FindOption(aMessage, aMsgOffsetRange, kStatusCode, optionOffsetRange));
SuccessOrExit(aMessage.Read(optionOffsetRange, statusOption));
status = static_cast<Status>(statusOption.GetStatusCode());
exit:
return status;
}
//---------------------------------------------------------------------------------------------------------------------
// RapidCommitOption
Error RapidCommitOption::FindIn(const Message &aMessage)
{
OffsetRange optionOffsetRange;
return Option::FindOption(aMessage, Option::kRapidCommit, optionOffsetRange);
}
Error RapidCommitOption::AppendTo(Message &aMessage)
{
Option option;
option.SetCode(Option::kRapidCommit);
option.SetLength(0);
return aMessage.Append(option);
}
} // namespace Dhcp6
} // namespace ot
#endif // #if OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE || OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
@@ -31,8 +31,8 @@
* This file includes definitions for DHCPv6 Service.
*/
#ifndef DHCP6_HPP_
#define DHCP6_HPP_
#ifndef DHCP6_TYPES_HPP_
#define DHCP6_TYPES_HPP_
#include "openthread-core-config.h"
@@ -41,6 +41,7 @@
#include "common/clearable.hpp"
#include "common/debug.hpp"
#include "common/equatable.hpp"
#include "common/message.hpp"
#include "common/random.hpp"
#include "mac/mac_types.hpp"
@@ -205,6 +206,57 @@ public:
*/
void SetLength(uint16_t aLength) { mLength = BigEndian::HostSwap16(aLength); }
/**
* Returns the total size of DHCPv6 option in bytes.
*
* @returns The size of option in bytes (which includes the Code and Length fields).
*/
uint32_t GetSize(void) const { return GetLength() + sizeof(Option); }
/**
* Finds the first DHCPv6 option with a given code in a message.
*
* This method searches the message starting from `aMessage.GetOffset()` to the end.
*
* @param[in] aMessage The message to search.
* @param[in] aCode The option code to find.
* @param[out] aOptionOffsetRange On success, is updated to contain the offset range of the found option.
*
* @retval kErrorNone The option was found successfully.
* @retval kErrorNotFound The option was not found in the message.
* @retval kErrorParse The message is malformed and cannot be parsed.
*/
static Error FindOption(const Message &aMessage, Code aCode, OffsetRange &aOptionOffsetRange);
/**
* Finds the first DHCPv6 option with a given code within a specified range of a message.
*
* @param[in] aMessage The message to search.
* @param[in] aMsgOffsetRange The specific range within `aMessage` to search.
* @param[in] aCode The option code to find.
* @param[out] aOptionOffsetRange On success, is updated to contain the offset range of the found option.
*
* @retval kErrorNone The option was found successfully.
* @retval kErrorNotFound The option was not found in the given message range.
* @retval kErrorParse The message is malformed and cannot be parsed.
*/
static Error FindOption(const Message &aMessage,
const OffsetRange &aMsgOffsetRange,
Code aCode,
OffsetRange &aOptionOffsetRange);
/**
* Updates the Option length in a message.
*
* This method should be called after all option contents are appended to the message. It uses the current
* message length along with @p aOffset to determine the option length and then updates it within the @p aMessage.
* The @p aOffset should point to to the start of the option in @p aMessage.
*
* @param[in] aMessage The message to update.
* @param[in] aOffset The offset to the start of `Option` in @p aMessage.
*/
static void UpdateOptionLengthInMessage(Message &aMessage, uint16_t aOffset);
private:
uint16_t mCode;
uint16_t mLength;
@@ -560,22 +612,68 @@ public:
*/
void SetStatusCode(Status aStatus) { mStatus = BigEndian::HostSwap16(aStatus); }
/**
* Reads the status code from a DHCPv6 message.
*
* This method searches the message (starting from `aMessage.GetOffset()` to the end) for a Status Code option. Per
* RFC 8415, the absence of a Status Code option implies success. Therefore, if no Status Code option is found,
* this method returns `kSuccess`.
*
* @param[in] aMessage The message to read the status code from.
*
* @returns The status code from the first found Status Code option, or `kSuccess` if none is found.
*/
static Status ReadStatusFrom(const Message &aMessage);
/**
* Reads the status code from a specified range within a DHCPv-6 message.
*
* This method searches for a Status Code option only within the given `aMsgOffsetRange`. If no Status Code
* option is found within the range, it is considered a success, and `kSuccess` is returned.
*
* @param[in] aMessage The message to read the status code from.
* @param[in] aMsgOffsetRange The specific range within `aMessage` to search.
*
* @returns The status code from the first found Status Code option within the range, or `kSuccess` if none is
* found.
*/
static Status ReadStatusFrom(const Message &aMessage, const OffsetRange &aMsgOffsetRange);
private:
uint16_t mStatus;
} OT_TOOL_PACKED_END;
/**
* Represents an Rapid Commit DHCPv6 option.
* Implements Rapid Commit DHCPv6 Option generation and parsing.
*/
OT_TOOL_PACKED_BEGIN
class RapidCommitOption : public Option
class RapidCommitOption
{
public:
/**
* Initializes the DHCPv6 Option.
static constexpr uint16_t kCode = Option::kRapidCommit; ///< Rapid Commit Option code.
/*
* Searches in a given message for Rapid Commit Option.
*
* @param[in] aMessage The message to search in.
*
* @retval kErrorNone The option was found successfully.
* @retval kErrorNotFound Did not find the option in @p aMessage.
* @retval kErrorParse Failed to parse the options in @p aMessage (invalid format).
*/
void Init(void) { SetCode(kRapidCommit), SetLength(sizeof(*this) - sizeof(Option)); }
} OT_TOOL_PACKED_END;
static Error FindIn(const Message &aMessage);
/**
* Append a Rapid Commit Option to a message.
*
* The Rapid Commit Option contains no data fields (zero length).
*
* @param[in,out] aMessage The message to append to.
*
* @retval kErrorNone Successfully appended the option.
* @retval kErrorNoBufs Insufficient available buffers to grow the message.
*/
static Error AppendTo(Message &aMessage);
};
/**
* @}
@@ -586,4 +684,4 @@ public:
#endif // #if OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE || OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
#endif // DHCP6_HPP_
#endif // DHCP6_TYPES_HPP_
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
#
# Copyright (c) 2025, 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.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: DHCPv6 prefix in netdata and DHCPv6 client and server behavior
#
# Network topology
#
# r1 ---- r2 ---- r3
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 40
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
r3 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.allowlist_node(r2)
r2.allowlist_node(r1)
r2.allowlist_node(r3)
r3.allowlist_node(r2)
r1.form('dhcp6')
r2.join(r1)
r3.join(r1)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
verify(r3.get_state() == 'router')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Add an on-mesh prefix with dhcp6 flag.
r1.add_prefix('2000:1::/64', 'pdros')
r1.register_netdata()
def check_netdata():
for node in [r1, r2, r3]:
netdata = node.get_netdata()
verify(len(netdata['prefixes']) == 1)
verify(len(netdata['routes']) == 0)
verify_within(check_netdata, 5)
def check_dhcp_addrs():
for node in [r1, r2, r3]:
ip_addrs = node.get_ip_addrs()
for addr in ip_addrs:
if addr.startswith('2000:1:0:0:'):
break
else:
verify(False)
verify_within(check_dhcp_addrs, 5)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
@@ -85,6 +85,10 @@
#define OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE 1
#define OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE 1
#define OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE 1
#define OPENTHREAD_CONFIG_RADIO_STATS_ENABLE 0
#define OPENTHREAD_CONFIG_TREL_MANAGE_DNSSD_ENABLE 0
+1
View File
@@ -201,6 +201,7 @@ if [ "$TORANJ_CLI" = 1 ]; then
run cli/test-033-alt-short-addr-role-transition.py
run cli/test-034-fed-parent-search.py
run cli/test-035-context-id-change-addr-reg.py
run cli/test-036-dhcp-prefix-netdata.py
run cli/test-400-srp-client-server.py
run cli/test-401-srp-server-address-cache-snoop.py
run cli/test-500-two-brs-two-networks.py