blob: ce3b28a50633664c8c4d71db7c0f40dd1f13ffd4 [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.
*/
#define WPP_NAME "dhcp6_client.tmh"
#include <openthread/config.h>
#include "dhcp6_client.hpp"
#include <openthread/types.h>
#include <openthread/platform/random.h>
#include "common/code_utils.hpp"
#include "common/encoding.hpp"
#include "common/logging.hpp"
#include "mac/mac.hpp"
#include "net/dhcp6.hpp"
#include "thread/thread_netif.hpp"
#if OPENTHREAD_ENABLE_DHCP6_CLIENT
using ot::Encoding::BigEndian::HostSwap16;
using ot::Encoding::BigEndian::HostSwap32;
namespace ot {
namespace Dhcp6 {
Dhcp6Client::Dhcp6Client(ThreadNetif &aThreadNetif) :
ThreadNetifLocator(aThreadNetif),
mTrickleTimer(aThreadNetif.GetIp6().mTimerScheduler, &Dhcp6Client::HandleTrickleTimer, NULL, this),
mSocket(aThreadNetif.GetIp6().mUdp),
mStartTime(0),
mAddresses(NULL),
mNumAddresses(0)
{
memset(mIdentityAssociations, 0, sizeof(IdentityAssociation));
for (uint8_t i = 0; i < (OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES - 1); i++)
{
mIdentityAssociations[i].SetNext(&(mIdentityAssociations[i + 1]));
}
mIdentityAssociationHead = NULL;
mIdentityAssociationAvail = &mIdentityAssociations[0];
}
void Dhcp6Client::UpdateAddresses(otInstance *aInstance, otDhcpAddress *aAddresses, uint32_t aNumAddresses,
void *aContext)
{
OT_UNUSED_VARIABLE(aContext);
bool found = false;
bool newAgent = false;
otDhcpAddress *address = NULL;
otNetworkDataIterator iterator;
otBorderRouterConfig config;
mAddresses = aAddresses;
mNumAddresses = aNumAddresses;
// remove addresses directly if prefix not valid in network data
for (uint8_t i = 0; i < mNumAddresses; i++)
{
address = &mAddresses[i];
if (address->mValidLifetime == 0)
{
continue;
}
found = false;
iterator = OT_NETWORK_DATA_ITERATOR_INIT;
while ((otNetDataGetNextOnMeshPrefix(aInstance, &iterator, &config)) == OT_ERROR_NONE)
{
if (!config.mDhcp)
{
continue;
}
if ((otIp6PrefixMatch(&(address->mAddress.mAddress), &(config.mPrefix.mPrefix)) >=
address->mAddress.mPrefixLength) &&
(config.mPrefix.mLength == address->mAddress.mPrefixLength))
{
found = true;
break;
}
}
if (!found)
{
otIp6RemoveUnicastAddress(aInstance, &(address->mAddress.mAddress));
RemoveIdentityAssociation(config.mRloc16, config.mPrefix);
memset(address, 0, sizeof(*address));
}
}
// add IdentityAssociation for new configured prefix
iterator = OT_NETWORK_DATA_ITERATOR_INIT;
while (otNetDataGetNextOnMeshPrefix(aInstance, &iterator, &config) == OT_ERROR_NONE)
{
if (!config.mDhcp)
{
continue;
}
found = false;
for (uint8_t i = 0; i < mNumAddresses; i++)
{
address = &mAddresses[i];
if (address->mAddress.mPrefixLength == 0)
{
continue;
}
if ((otIp6PrefixMatch(&(config.mPrefix.mPrefix), &(address->mAddress.mAddress)) >=
config.mPrefix.mLength) &&
(config.mPrefix.mLength == address->mAddress.mPrefixLength))
{
found = true;
break;
}
}
if (!found)
{
for (size_t i = 0; i < mNumAddresses; i++)
{
address = &mAddresses[i];
if (address->mAddress.mPrefixLength != 0)
{
continue;
}
memset(address, 0, sizeof(*address));
// suppose all configured prefix are ::/64
memcpy(address->mAddress.mAddress.mFields.m8, config.mPrefix.mPrefix.mFields.m8, 8);
address->mAddress.mPrefixLength = config.mPrefix.mLength;
AddIdentityAssociation(config.mRloc16, config.mPrefix);
newAgent = true;
break;
}
}
}
if (newAgent)
{
Start();
}
else
{
Stop();
}
}
void Dhcp6Client::AddIdentityAssociation(uint16_t aRloc16, otIp6Prefix &aIp6Prefix)
{
IdentityAssociation *identityAssociation = NULL;
IdentityAssociation *identityAssociationCursor = NULL;
VerifyOrExit(mIdentityAssociationAvail);
identityAssociation = mIdentityAssociationAvail;
mIdentityAssociationAvail = mIdentityAssociationAvail->GetNext();
identityAssociation->SetPrefixAgentRloc(aRloc16);
identityAssociation->SetPrefix(aIp6Prefix);
identityAssociation->SetStatus(IdentityAssociation::kStatusSolicit);
identityAssociation->SetNext(NULL);
if (mIdentityAssociationHead)
{
// append the new identityassociation to the tail of used list
for (identityAssociationCursor = mIdentityAssociationHead; identityAssociationCursor->GetNext();
identityAssociationCursor = identityAssociationCursor->GetNext()) {}
identityAssociationCursor->SetNext(identityAssociation);
}
else
{
mIdentityAssociationHead = identityAssociation;
}
exit:
return;
}
void Dhcp6Client::RemoveIdentityAssociation(uint16_t aRloc16, otIp6Prefix &aIp6Prefix)
{
IdentityAssociation *prevIdentityAssociation = NULL;
IdentityAssociation *identityAssociation = NULL;
VerifyOrExit(mIdentityAssociationHead);
for (identityAssociation = mIdentityAssociationHead; identityAssociation;
prevIdentityAssociation = identityAssociation, identityAssociation = identityAssociation->GetNext())
{
if (identityAssociation->GetPrefixAgentRloc() != aRloc16)
{
continue;
}
if (otIp6PrefixMatch(&(aIp6Prefix.mPrefix), &(identityAssociation->GetPrefix()->mPrefix)) < aIp6Prefix.mLength)
{
continue;
}
// remove from used list
if (prevIdentityAssociation)
{
prevIdentityAssociation->SetNext(identityAssociation->GetNext());
}
else
{
mIdentityAssociationHead = identityAssociation->GetNext();
}
// return to available list
memset(identityAssociation, 0, sizeof(*identityAssociation));
identityAssociation->SetNext(mIdentityAssociationAvail);
mIdentityAssociationAvail = identityAssociation;
break;
}
exit:
return;
}
otError Dhcp6Client::Start(void)
{
Ip6::SockAddr sockaddr;
sockaddr.mPort = kDhcpClientPort;
mSocket.Open(&Dhcp6Client::HandleUdpReceive, this);
mSocket.Bind(sockaddr);
ProcessNextIdentityAssociation();
return OT_ERROR_NONE;
}
otError Dhcp6Client::Stop(void)
{
mSocket.Close();
return OT_ERROR_NONE;
}
bool Dhcp6Client::ProcessNextIdentityAssociation()
{
bool rval = false;
IdentityAssociation *prevIdentityAssociation = NULL;
IdentityAssociation *identityAssociation = NULL;
VerifyOrExit(mIdentityAssociationHead);
// not interrupt in-progress solicit
VerifyOrExit((mIdentityAssociationHead->GetStatus() != IdentityAssociation::kStatusSoliciting));
mTrickleTimer.Stop();
for (identityAssociation = mIdentityAssociationHead; identityAssociation;
prevIdentityAssociation = identityAssociation, identityAssociation = identityAssociation->GetNext())
{
if (identityAssociation->GetStatus() != IdentityAssociation::kStatusSolicit)
{
continue;
}
// new transaction id
for (uint8_t i = 0; i < kTransactionIdSize; i++)
{
mTransactionId[i] = static_cast<uint8_t>(otPlatRandomGet());
}
// ensure mIdentityAssociationHead is the prefix agent to solicit.
if (prevIdentityAssociation)
{
prevIdentityAssociation->SetNext(identityAssociation->GetNext());
identityAssociation->SetNext(mIdentityAssociationHead);
mIdentityAssociationHead = identityAssociation;
}
mTrickleTimer.Start(
Timer::SecToMsec(kTrickleTimerImin),
Timer::SecToMsec(kTrickleTimerImax),
TrickleTimer::kModeNormal);
mTrickleTimer.IndicateInconsistent();
ExitNow(rval = true);
}
exit:
return rval;
}
bool Dhcp6Client::HandleTrickleTimer(TrickleTimer &aTrickleTimer)
{
return GetOwner(aTrickleTimer).HandleTrickleTimer();
}
bool Dhcp6Client::HandleTrickleTimer(void)
{
bool rval = true;
VerifyOrExit(mIdentityAssociationHead, rval = false);
switch (mIdentityAssociationHead->GetStatus())
{
case IdentityAssociation::kStatusSolicit:
mStartTime = otPlatAlarmGetNow();
mIdentityAssociationHead->SetStatus(IdentityAssociation::kStatusSoliciting);
// fall through
case IdentityAssociation::kStatusSoliciting:
Solicit(mIdentityAssociationHead->GetPrefixAgentRloc());
break;
case IdentityAssociation::kStatusSolicitReplied:
if (!ProcessNextIdentityAssociation())
{
mTrickleTimer.Stop();
Stop();
rval = false;
}
break;
default:
break;
}
exit:
return rval;
}
otError Dhcp6Client::Solicit(uint16_t aRloc16)
{
ThreadNetif &netif = GetNetif();
otError error = OT_ERROR_NONE;
Message *message;
Ip6::MessageInfo messageInfo;
VerifyOrExit((message = mSocket.NewMessage(0)) != NULL, error = OT_ERROR_NO_BUFS);
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));
memset(&messageInfo, 0, sizeof(messageInfo));
memcpy(messageInfo.GetPeerAddr().mFields.m8, netif.GetMle().GetMeshLocalPrefix(), 8);
messageInfo.GetPeerAddr().mFields.m16[4] = HostSwap16(0x0000);
messageInfo.GetPeerAddr().mFields.m16[5] = HostSwap16(0x00ff);
messageInfo.GetPeerAddr().mFields.m16[6] = HostSwap16(0xfe00);
messageInfo.GetPeerAddr().mFields.m16[7] = HostSwap16(aRloc16);
messageInfo.SetSockAddr(netif.GetMle().GetMeshLocal16());
messageInfo.mPeerPort = kDhcpServerPort;
messageInfo.mInterfaceId = netif.GetInterfaceId();
SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
otLogInfoIp6(GetInstance(), "solicit");
exit:
if (message != NULL && error != OT_ERROR_NONE)
{
message->Free();
}
return error;
}
otError Dhcp6Client::AppendHeader(Message &aMessage)
{
Dhcp6Header header;
header.Init();
header.SetType(kTypeSolicit);
header.SetTransactionId(mTransactionId);
return aMessage.Append(&header, sizeof(header));
}
otError Dhcp6Client::AppendElapsedTime(Message &aMessage)
{
ElapsedTime option;
option.Init();
option.SetElapsedTime(static_cast<uint16_t>(Timer::MsecToSec(otPlatAlarmGetNow() - mStartTime)));
return aMessage.Append(&option, sizeof(option));
}
otError Dhcp6Client::AppendClientIdentifier(Message &aMessage)
{
ClientIdentifier option;
option.Init();
option.SetDuidType(kDuidLL);
option.SetDuidHardwareType(kHardwareTypeEui64);
option.SetDuidLinkLayerAddress(GetNetif().GetMac().GetExtAddress());
return aMessage.Append(&option, sizeof(option));
}
otError Dhcp6Client::AppendIaNa(Message &aMessage, uint16_t aRloc16)
{
otError error = OT_ERROR_NONE;
uint8_t count = 0;
uint16_t length = 0;
IdentityAssociation *identityAssociation = NULL;
IaNa option;
VerifyOrExit(mIdentityAssociationHead, error = OT_ERROR_DROP);
for (identityAssociation = mIdentityAssociationHead; identityAssociation;
identityAssociation = identityAssociation->GetNext())
{
if (identityAssociation->GetStatus() == IdentityAssociation::kStatusSolicitReplied)
{
continue;
}
if (identityAssociation->GetPrefixAgentRloc() == aRloc16)
{
count++;
}
}
// compute the right length
length = sizeof(IaNa) + sizeof(IaAddress) * count - sizeof(Dhcp6Option);
option.Init();
option.SetLength(length);
option.SetIaid(0);
option.SetT1(0);
option.SetT2(0);
SuccessOrExit(error = aMessage.Append(&option, sizeof(IaNa)));
exit:
return error;
}
otError Dhcp6Client::AppendIaAddress(Message &aMessage, uint16_t aRloc16)
{
otError error = OT_ERROR_NONE;
IdentityAssociation *identityAssociation = NULL;
IaAddress option;
VerifyOrExit(mIdentityAssociationHead, error = OT_ERROR_DROP);
option.Init();
for (identityAssociation = mIdentityAssociationHead; identityAssociation;
identityAssociation = identityAssociation->GetNext())
{
if ((identityAssociation->GetStatus() != IdentityAssociation::kStatusSolicitReplied) &&
(identityAssociation->GetPrefixAgentRloc() == aRloc16))
{
option.SetAddress(identityAssociation->GetPrefix()->mPrefix);
option.SetPreferredLifetime(0);
option.SetValidLifetime(0);
SuccessOrExit(error = aMessage.Append(&option, sizeof(option)));
}
}
exit:
return error;
}
otError Dhcp6Client::AppendRapidCommit(Message &aMessage)
{
RapidCommit option;
option.Init();
return aMessage.Append(&option, sizeof(option));
}
void Dhcp6Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
Dhcp6Client *obj = static_cast<Dhcp6Client *>(aContext);
obj->HandleUdpReceive(*static_cast<Message *>(aMessage), *static_cast<const Ip6::MessageInfo *>(aMessageInfo));
}
void Dhcp6Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Dhcp6Header header;
OT_UNUSED_VARIABLE(aMessageInfo);
VerifyOrExit(aMessage.Read(aMessage.GetOffset(), sizeof(header), &header) == sizeof(header));
aMessage.MoveOffset(sizeof(header));
if ((header.GetType() == kTypeReply) && (!memcmp(header.GetTransactionId(), mTransactionId, kTransactionIdSize)))
{
ProcessReply(aMessage);
}
exit:
return;
}
void Dhcp6Client::ProcessReply(Message &aMessage)
{
uint16_t offset = aMessage.GetOffset();
uint16_t length = aMessage.GetLength() - aMessage.GetOffset();
uint16_t 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 Dhcp6Client::FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Dhcp6::Code aCode)
{
uint16_t end = aOffset + aLength;
while (aOffset <= end)
{
Dhcp6Option option;
VerifyOrExit(aMessage.Read(aOffset, sizeof(option), &option) == sizeof(option));
if (option.GetCode() == (aCode))
{
return aOffset;
}
aOffset += sizeof(option) + option.GetLength();
}
exit:
return 0;
}
otError Dhcp6Client::ProcessServerIdentifier(Message &aMessage, uint16_t aOffset)
{
otError error = OT_ERROR_NONE;
ServerIdentifier option;
VerifyOrExit(((aMessage.Read(aOffset, sizeof(option), &option) == sizeof(option)) &&
(option.GetLength() == (sizeof(option) - sizeof(Dhcp6Option))) &&
(option.GetDuidType() == kDuidLL) &&
(option.GetDuidHardwareType() == kHardwareTypeEui64)),
error = OT_ERROR_PARSE);
exit:
return error;
}
otError Dhcp6Client::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset)
{
otError error = OT_ERROR_NONE;
ClientIdentifier option;
VerifyOrExit((((aMessage.Read(aOffset, sizeof(option), &option) == sizeof(option)) &&
(option.GetLength() == (sizeof(option) - sizeof(Dhcp6Option))) &&
(option.GetDuidType() == kDuidLL) &&
(option.GetDuidHardwareType() == kHardwareTypeEui64)) &&
(!memcmp(option.GetDuidLinkLayerAddress(), GetNetif().GetMac().GetExtAddress(),
sizeof(Mac::ExtAddress)))),
error = OT_ERROR_PARSE);
exit:
return error;
}
otError Dhcp6Client::ProcessIaNa(Message &aMessage, uint16_t aOffset)
{
otError error = OT_ERROR_NONE;
IaNa option;
uint16_t optionOffset;
uint16_t length;
VerifyOrExit(aMessage.Read(aOffset, sizeof(option), &option) == sizeof(option), error = OT_ERROR_PARSE);
aOffset += sizeof(option);
length = option.GetLength() - (sizeof(option) - sizeof(Dhcp6Option));
VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = OT_ERROR_PARSE);
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;
}
otError Dhcp6Client::ProcessStatusCode(Message &aMessage, uint16_t aOffset)
{
otError error = OT_ERROR_NONE;
StatusCode option;
VerifyOrExit(((aMessage.Read(aOffset, sizeof(option), &option) == sizeof(option)) &&
(option.GetLength() == (sizeof(option) - sizeof(Dhcp6Option))) &&
(option.GetStatusCode() == kStatusSuccess)),
error = OT_ERROR_PARSE);
exit:
return error;
}
otError Dhcp6Client::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
{
otError error = OT_ERROR_NONE;
IdentityAssociation *identityAssociation = NULL;
otDhcpAddress *address = NULL;
otIp6Prefix *prefix = NULL;
IaAddress option;
VerifyOrExit(((aMessage.Read(aOffset, sizeof(option), &option) == sizeof(option)) &&
(option.GetLength() == (sizeof(option) - sizeof(Dhcp6Option)))),
error = OT_ERROR_PARSE);
for (uint8_t i = 0; i < mNumAddresses; i++)
{
address = &mAddresses[i];
if (address->mValidLifetime != 0)
{
continue;
}
if (otIp6PrefixMatch(&(address->mAddress.mAddress), option.GetAddress()) >= address->mAddress.mPrefixLength)
{
memcpy(address->mAddress.mAddress.mFields.m8, option.GetAddress()->mFields.m8, sizeof(otIp6Address));
address->mPreferredLifetime = option.GetPreferredLifetime();
address->mValidLifetime = option.GetValidLifetime();
address->mAddress.mPreferred = address->mPreferredLifetime != 0;
address->mAddress.mValid = address->mValidLifetime != 0;
otIp6AddUnicastAddress(GetNetif().GetInstance(), &address->mAddress);
break;
}
}
// mark IdentityAssociation as replied
for (identityAssociation = mIdentityAssociationHead; identityAssociation;
identityAssociation = identityAssociation->GetNext())
{
prefix = identityAssociation->GetPrefix();
if (otIp6PrefixMatch(option.GetAddress(), &(prefix->mPrefix)) >= prefix->mLength)
{
identityAssociation->SetStatus(IdentityAssociation::kStatusSolicitReplied);
break;
}
}
exit:
return error;
}
Dhcp6Client &Dhcp6Client::GetOwner(const Context &aContext)
{
#if OPENTHREAD_ENABLE_MULTIPLE_INSTANCES
Dhcp6Client &client = *static_cast<Dhcp6Client *>(aContext.GetContext());
#else
Dhcp6Client &client = otGetThreadNetif().GetDhcp6Client();
OT_UNUSED_VARIABLE(aContext);
#endif
return client;
}
} // namespace Dhcp6
} // namespace ot
#endif //OPENTHREAD_ENABLE_DHCP6_CLIENT