blob: 9c3e77278a730ed144428a1446d62e747dadb70b [file] [log] [blame]
/*
* Copyright (c) 2016, 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 DHCPv6 Client.
*/
#include "dhcp6_client.hpp"
#if OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
#include "common/as_core_type.hpp"
#include "common/code_utils.hpp"
#include "common/encoding.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/log.hpp"
#include "mac/mac.hpp"
#include "net/dhcp6.hpp"
#include "thread/thread_netif.hpp"
namespace ot {
namespace Dhcp6 {
RegisterLogModule("Dhcp6Client");
Client::Client(Instance &aInstance)
: InstanceLocator(aInstance)
, mSocket(aInstance)
, mTrickleTimer(aInstance, Client::HandleTrickleTimer)
, mStartTime(0)
, mIdentityAssociationCurrent(nullptr)
{
memset(mIdentityAssociations, 0, sizeof(mIdentityAssociations));
}
bool Client::MatchNetifAddressWithPrefix(const Ip6::Netif::UnicastAddress &aNetifAddress, const Ip6::Prefix &aIp6Prefix)
{
return aNetifAddress.HasPrefix(aIp6Prefix);
}
void Client::UpdateAddresses(void)
{
bool found = false;
bool doesAgentExist = false;
NetworkData::Iterator iterator;
NetworkData::OnMeshPrefixConfig config;
// remove addresses directly if prefix not valid in network data
for (IdentityAssociation &idAssociation : mIdentityAssociations)
{
if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mValidLifetime == 0)
{
continue;
}
found = false;
iterator = NetworkData::kIteratorInit;
while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == kErrorNone)
{
if (!config.mDhcp)
{
continue;
}
if (MatchNetifAddressWithPrefix(idAssociation.mNetifAddress, config.GetPrefix()))
{
found = true;
break;
}
}
if (!found)
{
Get<ThreadNetif>().RemoveUnicastAddress(idAssociation.mNetifAddress);
idAssociation.mStatus = kIaStatusInvalid;
}
}
// add IdentityAssociation for new configured prefix
iterator = NetworkData::kIteratorInit;
while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == kErrorNone)
{
IdentityAssociation *idAssociation = nullptr;
if (!config.mDhcp)
{
continue;
}
doesAgentExist = true;
found = false;
for (IdentityAssociation &ia : mIdentityAssociations)
{
if (ia.mStatus == kIaStatusInvalid)
{
// record an available IdentityAssociation
if (idAssociation == nullptr)
{
idAssociation = &ia;
}
}
else if (MatchNetifAddressWithPrefix(ia.mNetifAddress, config.GetPrefix()))
{
found = true;
idAssociation = &ia;
break;
}
}
if (!found)
{
if (idAssociation != nullptr)
{
idAssociation->mNetifAddress.mAddress = config.mPrefix.mPrefix;
idAssociation->mNetifAddress.mPrefixLength = config.mPrefix.mLength;
idAssociation->mStatus = kIaStatusSolicit;
idAssociation->mValidLifetime = 0;
}
else
{
LogWarn("Insufficient memory for new DHCP prefix");
continue;
}
}
idAssociation->mPrefixAgentRloc = config.mRloc16;
}
if (doesAgentExist)
{
Start();
}
else
{
Stop();
}
}
void Client::Start(void)
{
VerifyOrExit(!mSocket.IsBound());
IgnoreError(mSocket.Open(&Client::HandleUdpReceive, this));
IgnoreError(mSocket.Bind(kDhcpClientPort));
ProcessNextIdentityAssociation();
exit:
return;
}
void Client::Stop(void)
{
mTrickleTimer.Stop();
IgnoreError(mSocket.Close());
}
bool Client::ProcessNextIdentityAssociation(void)
{
bool rval = false;
// not interrupt in-progress solicit
VerifyOrExit(mIdentityAssociationCurrent == nullptr || mIdentityAssociationCurrent->mStatus != kIaStatusSoliciting);
mTrickleTimer.Stop();
for (IdentityAssociation &idAssociation : mIdentityAssociations)
{
if (idAssociation.mStatus != kIaStatusSolicit)
{
continue;
}
// new transaction id
IgnoreError(mTransactionId.GenerateRandom());
mIdentityAssociationCurrent = &idAssociation;
mTrickleTimer.Start(TrickleTimer::kModeTrickle, Time::SecToMsec(kTrickleTimerImin),
Time::SecToMsec(kTrickleTimerImax));
mTrickleTimer.IndicateInconsistent();
ExitNow(rval = true);
}
exit:
return rval;
}
void Client::HandleTrickleTimer(TrickleTimer &aTrickleTimer)
{
aTrickleTimer.Get<Client>().HandleTrickleTimer();
}
void Client::HandleTrickleTimer(void)
{
OT_ASSERT(mSocket.IsBound());
VerifyOrExit(mIdentityAssociationCurrent != nullptr, mTrickleTimer.Stop());
switch (mIdentityAssociationCurrent->mStatus)
{
case kIaStatusSolicit:
mStartTime = TimerMilli::GetNow();
mIdentityAssociationCurrent->mStatus = kIaStatusSoliciting;
OT_FALL_THROUGH;
case kIaStatusSoliciting:
Solicit(mIdentityAssociationCurrent->mPrefixAgentRloc);
break;
case kIaStatusSolicitReplied:
mIdentityAssociationCurrent = nullptr;
if (!ProcessNextIdentityAssociation())
{
Stop();
mTrickleTimer.Stop();
}
break;
default:
break;
}
exit:
return;
}
void Client::Solicit(uint16_t aRloc16)
{
Error error = kErrorNone;
Message * message;
Ip6::MessageInfo messageInfo;
VerifyOrExit((message = mSocket.NewMessage(0)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = AppendHeader(*message));
SuccessOrExit(error = AppendElapsedTime(*message));
SuccessOrExit(error = AppendClientIdentifier(*message));
SuccessOrExit(error = AppendIaNa(*message, aRloc16));
// specify which prefixes to solicit
SuccessOrExit(error = AppendIaAddress(*message, aRloc16));
SuccessOrExit(error = AppendRapidCommit(*message));
#if OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT
messageInfo.GetPeerAddr().SetToRealmLocalAllRoutersMulticast();
#else
messageInfo.GetPeerAddr().SetToRoutingLocator(Get<Mle::MleRouter>().GetMeshLocalPrefix(), aRloc16);
#endif
messageInfo.SetSockAddr(Get<Mle::MleRouter>().GetMeshLocal16());
messageInfo.mPeerPort = kDhcpServerPort;
SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
LogInfo("solicit");
exit:
if (error != kErrorNone)
{
FreeMessage(message);
LogWarn("Failed to send DHCPv6 Solicit: %s", ErrorToString(error));
}
}
Error Client::AppendHeader(Message &aMessage)
{
Header header;
header.Clear();
header.SetType(kTypeSolicit);
header.SetTransactionId(mTransactionId);
return aMessage.Append(header);
}
Error Client::AppendElapsedTime(Message &aMessage)
{
ElapsedTime option;
option.Init();
option.SetElapsedTime(static_cast<uint16_t>(Time::MsecToSec(TimerMilli::GetNow() - mStartTime)));
return aMessage.Append(option);
}
Error Client::AppendClientIdentifier(Message &aMessage)
{
ClientIdentifier option;
Mac::ExtAddress eui64;
Get<Radio>().GetIeeeEui64(eui64);
option.Init();
option.SetDuidType(kDuidLinkLayerAddress);
option.SetDuidHardwareType(kHardwareTypeEui64);
option.SetDuidLinkLayerAddress(eui64);
return aMessage.Append(option);
}
Error Client::AppendIaNa(Message &aMessage, uint16_t aRloc16)
{
Error error = kErrorNone;
uint8_t count = 0;
uint16_t length = 0;
IaNa option;
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(IaNa) + sizeof(IaAddress) * count - sizeof(Option);
option.Init();
option.SetLength(length);
option.SetIaid(0);
option.SetT1(0);
option.SetT2(0);
SuccessOrExit(error = aMessage.Append(option));
exit:
return error;
}
Error Client::AppendIaAddress(Message &aMessage, uint16_t aRloc16)
{
Error error = kErrorNone;
IaAddress option;
VerifyOrExit(mIdentityAssociationCurrent, error = kErrorDrop);
option.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));
}
}
exit:
return error;
}
Error Client::AppendRapidCommit(Message &aMessage)
{
RapidCommit option;
option.Init();
return aMessage.Append(option);
}
void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
static_cast<Client *>(aContext)->HandleUdpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo));
}
void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
OT_UNUSED_VARIABLE(aMessageInfo);
Header header;
SuccessOrExit(aMessage.Read(aMessage.GetOffset(), header));
aMessage.MoveOffset(sizeof(header));
if ((header.GetType() == kTypeReply) && (header.GetTransactionId() == mTransactionId))
{
ProcessReply(aMessage);
}
exit:
return;
}
void Client::ProcessReply(Message &aMessage)
{
uint16_t offset = aMessage.GetOffset();
uint16_t length = aMessage.GetLength() - aMessage.GetOffset();
uint16_t optionOffset;
if ((optionOffset = FindOption(aMessage, offset, length, kOptionStatusCode)) > 0)
{
SuccessOrExit(ProcessStatusCode(aMessage, optionOffset));
}
// Server Identifier
VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionServerIdentifier)) > 0);
SuccessOrExit(ProcessServerIdentifier(aMessage, optionOffset));
// Client Identifier
VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionClientIdentifier)) > 0);
SuccessOrExit(ProcessClientIdentifier(aMessage, optionOffset));
// Rapid Commit
VerifyOrExit(FindOption(aMessage, offset, length, kOptionRapidCommit) > 0);
// IA_NA
VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionIaNa)) > 0);
SuccessOrExit(ProcessIaNa(aMessage, optionOffset));
HandleTrickleTimer();
exit:
return;
}
uint16_t Client::FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Dhcp6::Code aCode)
{
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::ProcessServerIdentifier(Message &aMessage, uint16_t aOffset)
{
Error error = kErrorNone;
ServerIdentifier option;
SuccessOrExit(aMessage.Read(aOffset, option));
VerifyOrExit(((option.GetDuidType() == kDuidLinkLayerAddressPlusTime) &&
(option.GetDuidHardwareType() == kHardwareTypeEthernet)) ||
((option.GetLength() == (sizeof(option) - sizeof(Option))) &&
(option.GetDuidType() == kDuidLinkLayerAddress) &&
(option.GetDuidHardwareType() == kHardwareTypeEui64)),
error = kErrorParse);
exit:
return error;
}
Error Client::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset)
{
Error error = kErrorNone;
ClientIdentifier 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);
exit:
return error;
}
Error Client::ProcessIaNa(Message &aMessage, uint16_t aOffset)
{
Error error = kErrorNone;
IaNa option;
uint16_t optionOffset;
uint16_t length;
SuccessOrExit(error = aMessage.Read(aOffset, option));
aOffset += sizeof(option);
length = option.GetLength() - (sizeof(option) - sizeof(Option));
VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = kErrorParse);
if ((optionOffset = FindOption(aMessage, aOffset, length, kOptionStatusCode)) > 0)
{
SuccessOrExit(error = ProcessStatusCode(aMessage, optionOffset));
}
while (length > 0)
{
if ((optionOffset = FindOption(aMessage, aOffset, length, kOptionIaAddress)) == 0)
{
ExitNow();
}
SuccessOrExit(error = ProcessIaAddress(aMessage, optionOffset));
length -= ((optionOffset - aOffset) + sizeof(IaAddress));
aOffset = optionOffset + sizeof(IaAddress);
}
exit:
return error;
}
Error Client::ProcessStatusCode(Message &aMessage, uint16_t aOffset)
{
Error error = kErrorNone;
StatusCode option;
SuccessOrExit(error = aMessage.Read(aOffset, option));
VerifyOrExit((option.GetLength() >= sizeof(option) - sizeof(Option)) && (option.GetStatusCode() == kStatusSuccess),
error = kErrorParse);
exit:
return error;
}
Error Client::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
{
Error error;
IaAddress option;
SuccessOrExit(error = aMessage.Read(aOffset, option));
VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
for (IdentityAssociation &idAssociation : mIdentityAssociations)
{
if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mValidLifetime != 0)
{
continue;
}
if (idAssociation.mNetifAddress.GetAddress().PrefixMatch(option.GetAddress()) >=
idAssociation.mNetifAddress.mPrefixLength)
{
idAssociation.mNetifAddress.mAddress = option.GetAddress();
idAssociation.mPreferredLifetime = option.GetPreferredLifetime();
idAssociation.mValidLifetime = option.GetValidLifetime();
idAssociation.mNetifAddress.mAddressOrigin = Ip6::Netif::kOriginDhcp6;
idAssociation.mNetifAddress.mPreferred = option.GetPreferredLifetime() != 0;
idAssociation.mNetifAddress.mValid = option.GetValidLifetime() != 0;
idAssociation.mStatus = kIaStatusSolicitReplied;
Get<ThreadNetif>().AddUnicastAddress(idAssociation.mNetifAddress);
ExitNow(error = kErrorNone);
}
}
error = kErrorNotFound;
exit:
return error;
}
} // namespace Dhcp6
} // namespace ot
#endif // OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE