blob: eaab1b4dd92ffb5064f0cfd70e335f04de4e1b10 [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 Server.
*/
#include "dhcp6_server.hpp"
#if OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE
#include "common/array.hpp"
#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 "thread/mle.hpp"
#include "thread/thread_netif.hpp"
namespace ot {
namespace Dhcp6 {
RegisterLogModule("Dhcp6Server");
Server::Server(Instance &aInstance)
: InstanceLocator(aInstance)
, mSocket(aInstance)
, mPrefixAgentsCount(0)
, mPrefixAgentsMask(0)
{
memset(mPrefixAgents, 0, sizeof(mPrefixAgents));
}
Error Server::UpdateService(void)
{
Error error = kErrorNone;
uint16_t rloc16 = Get<Mle::MleRouter>().GetRloc16();
NetworkData::Iterator iterator;
NetworkData::OnMeshPrefixConfig config;
Lowpan::Context lowpanContext;
// remove dhcp agent aloc and prefix delegation
for (PrefixAgent &prefixAgent : mPrefixAgents)
{
bool found = false;
if (!prefixAgent.IsValid())
{
continue;
}
iterator = NetworkData::kIteratorInit;
while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, rloc16, config) == kErrorNone)
{
if (!(config.mDhcp || config.mConfigure))
{
continue;
}
error = Get<NetworkData::Leader>().GetContext(prefixAgent.GetPrefixAsAddress(), lowpanContext);
if ((error == kErrorNone) && (prefixAgent.GetContextId() == lowpanContext.mContextId))
{
// still in network data
found = true;
break;
}
}
if (!found)
{
Get<ThreadNetif>().RemoveUnicastAddress(prefixAgent.GetAloc());
prefixAgent.Clear();
mPrefixAgentsCount--;
}
}
// add dhcp agent aloc and prefix delegation
iterator = NetworkData::kIteratorInit;
while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, rloc16, config) == kErrorNone)
{
if (!(config.mDhcp || config.mConfigure))
{
continue;
}
error = Get<NetworkData::Leader>().GetContext(AsCoreType(&config.mPrefix.mPrefix), lowpanContext);
if (error == kErrorNone)
{
AddPrefixAgent(config.GetPrefix(), lowpanContext);
}
}
if (mPrefixAgentsCount > 0)
{
Start();
}
else
{
Stop();
}
return error;
}
void Server::Start(void)
{
VerifyOrExit(!mSocket.IsOpen());
IgnoreError(mSocket.Open(&Server::HandleUdpReceive, this));
IgnoreError(mSocket.Bind(kDhcpServerPort));
exit:
return;
}
void Server::Stop(void)
{
IgnoreError(mSocket.Close());
}
void Server::AddPrefixAgent(const Ip6::Prefix &aIp6Prefix, const Lowpan::Context &aContext)
{
Error error = kErrorNone;
PrefixAgent *newEntry = nullptr;
for (PrefixAgent &prefixAgent : mPrefixAgents)
{
if (!prefixAgent.IsValid())
{
newEntry = &prefixAgent;
}
else if (prefixAgent.GetPrefix() == aIp6Prefix)
{
// already added
ExitNow();
}
}
VerifyOrExit(newEntry != nullptr, error = kErrorNoBufs);
newEntry->Set(aIp6Prefix, Get<Mle::MleRouter>().GetMeshLocalPrefix(), aContext.mContextId);
Get<ThreadNetif>().AddUnicastAddress(newEntry->GetAloc());
mPrefixAgentsCount++;
exit:
if (error != kErrorNone)
{
LogNote("Failed to add DHCPv6 prefix agent: %s", ErrorToString(error));
}
}
void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
static_cast<Server *>(aContext)->HandleUdpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo));
}
void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Header header;
SuccessOrExit(aMessage.Read(aMessage.GetOffset(), header));
aMessage.MoveOffset(sizeof(header));
// discard if not solicit type
VerifyOrExit((header.GetType() == kTypeSolicit));
ProcessSolicit(aMessage, aMessageInfo.GetPeerAddr(), header.GetTransactionId());
exit:
return;
}
void Server::ProcessSolicit(Message &aMessage, const Ip6::Address &aDst, const TransactionId &aTransactionId)
{
IaNa iana;
ClientIdentifier clientIdentifier;
uint16_t optionOffset;
uint16_t offset = aMessage.GetOffset();
uint16_t length = aMessage.GetLength() - aMessage.GetOffset();
// Client Identifier (discard if not present)
VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionClientIdentifier)) > 0);
SuccessOrExit(ProcessClientIdentifier(aMessage, optionOffset, clientIdentifier));
// Server Identifier (assuming Rapid Commit, discard if present)
VerifyOrExit(FindOption(aMessage, offset, length, kOptionServerIdentifier) == 0);
// Rapid Commit (assuming Rapid Commit, discard if not present)
VerifyOrExit(FindOption(aMessage, offset, length, kOptionRapidCommit) > 0);
// Elapsed Time if present
if ((optionOffset = FindOption(aMessage, offset, length, kOptionElapsedTime)) > 0)
{
SuccessOrExit(ProcessElapsedTime(aMessage, optionOffset));
}
// IA_NA (discard if not present)
VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionIaNa)) > 0);
SuccessOrExit(ProcessIaNa(aMessage, optionOffset, iana));
SuccessOrExit(SendReply(aDst, aTransactionId, clientIdentifier, iana));
exit:
return;
}
uint16_t Server::FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Code aCode)
{
uint16_t end = aOffset + aLength;
uint16_t rval = 0;
while (aOffset <= end)
{
Option option;
SuccessOrExit(aMessage.Read(aOffset, option));
if (option.GetCode() == aCode)
{
ExitNow(rval = aOffset);
}
aOffset += sizeof(option) + option.GetLength();
}
exit:
return rval;
}
Error Server::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset, ClientIdentifier &aClientId)
{
Error error = kErrorNone;
SuccessOrExit(error = aMessage.Read(aOffset, aClientId));
VerifyOrExit((aClientId.GetLength() == sizeof(aClientId) - sizeof(Option)) &&
(aClientId.GetDuidType() == kDuidLinkLayerAddress) &&
(aClientId.GetDuidHardwareType() == kHardwareTypeEui64),
error = kErrorParse);
exit:
return error;
}
Error Server::ProcessElapsedTime(Message &aMessage, uint16_t aOffset)
{
Error error = kErrorNone;
ElapsedTime option;
SuccessOrExit(error = aMessage.Read(aOffset, option));
VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
exit:
return error;
}
Error Server::ProcessIaNa(Message &aMessage, uint16_t aOffset, IaNa &aIaNa)
{
Error error = kErrorNone;
uint16_t optionOffset;
uint16_t length;
SuccessOrExit(error = aMessage.Read(aOffset, aIaNa));
aOffset += sizeof(aIaNa);
length = aIaNa.GetLength() + sizeof(Option) - sizeof(IaNa);
VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = kErrorParse);
mPrefixAgentsMask = 0;
while (length > 0)
{
VerifyOrExit((optionOffset = FindOption(aMessage, aOffset, length, kOptionIaAddress)) > 0);
SuccessOrExit(error = ProcessIaAddress(aMessage, optionOffset));
length -= ((optionOffset - aOffset) + sizeof(IaAddress));
aOffset = optionOffset + sizeof(IaAddress);
}
exit:
return error;
}
Error Server::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
{
Error error = kErrorNone;
IaAddress 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()))
{
mPrefixAgentsMask |= (1 << i);
break;
}
}
exit:
return error;
}
Error Server::SendReply(const Ip6::Address & aDst,
const TransactionId &aTransactionId,
ClientIdentifier & aClientId,
IaNa & aIaNa)
{
Error error = kErrorNone;
Ip6::MessageInfo messageInfo;
Message * message;
VerifyOrExit((message = mSocket.NewMessage(0)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = AppendHeader(*message, aTransactionId));
SuccessOrExit(error = AppendServerIdentifier(*message));
SuccessOrExit(error = AppendClientIdentifier(*message, aClientId));
SuccessOrExit(error = AppendIaNa(*message, aIaNa));
SuccessOrExit(error = AppendStatusCode(*message, kStatusSuccess));
SuccessOrExit(error = AppendIaAddress(*message, aClientId));
SuccessOrExit(error = AppendRapidCommit(*message));
messageInfo.SetPeerAddr(aDst);
messageInfo.SetPeerPort(kDhcpClientPort);
SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
exit:
FreeMessageOnError(message, error);
return error;
}
Error Server::AppendHeader(Message &aMessage, const TransactionId &aTransactionId)
{
Header header;
header.Clear();
header.SetType(kTypeReply);
header.SetTransactionId(aTransactionId);
return aMessage.Append(header);
}
Error Server::AppendClientIdentifier(Message &aMessage, ClientIdentifier &aClientId)
{
return aMessage.Append(aClientId);
}
Error Server::AppendServerIdentifier(Message &aMessage)
{
Error error = kErrorNone;
ServerIdentifier option;
Mac::ExtAddress eui64;
Get<Radio>().GetIeeeEui64(eui64);
option.Init();
option.SetDuidType(kDuidLinkLayerAddress);
option.SetDuidHardwareType(kHardwareTypeEui64);
option.SetDuidLinkLayerAddress(eui64);
SuccessOrExit(error = aMessage.Append(option));
exit:
return error;
}
Error Server::AppendIaNa(Message &aMessage, IaNa &aIaNa)
{
Error error = kErrorNone;
uint16_t length = 0;
if (mPrefixAgentsMask)
{
for (uint16_t i = 0; i < GetArrayLength(mPrefixAgents); i++)
{
if (mPrefixAgentsMask & (1 << i))
{
length += sizeof(IaAddress);
}
}
}
else
{
length += sizeof(IaAddress) * mPrefixAgentsCount;
}
length += sizeof(IaNa) + sizeof(StatusCode) - sizeof(Option);
aIaNa.SetLength(length);
aIaNa.SetT1(IaNa::kDefaultT1);
aIaNa.SetT2(IaNa::kDefaultT2);
SuccessOrExit(error = aMessage.Append(aIaNa));
exit:
return error;
}
Error Server::AppendStatusCode(Message &aMessage, Status aStatusCode)
{
StatusCode option;
option.Init();
option.SetStatusCode(aStatusCode);
return aMessage.Append(option);
}
Error Server::AppendIaAddress(Message &aMessage, ClientIdentifier &aClientId)
{
Error error = kErrorNone;
if (mPrefixAgentsMask)
{
// if specified, only apply specified prefixes
for (uint16_t i = 0; i < GetArrayLength(mPrefixAgents); i++)
{
if (mPrefixAgentsMask & (1 << i))
{
SuccessOrExit(error = AddIaAddress(aMessage, mPrefixAgents[i].GetPrefixAsAddress(), aClientId));
}
}
}
else
{
// if not specified, apply all configured prefixes
for (const PrefixAgent &prefixAgent : mPrefixAgents)
{
if (prefixAgent.IsValid())
{
SuccessOrExit(error = AddIaAddress(aMessage, prefixAgent.GetPrefixAsAddress(), aClientId));
}
}
}
exit:
return error;
}
Error Server::AddIaAddress(Message &aMessage, const Ip6::Address &aPrefix, ClientIdentifier &aClientId)
{
Error error = kErrorNone;
IaAddress option;
option.Init();
option.GetAddress().SetPrefix(aPrefix.mFields.m8, OT_IP6_PREFIX_BITSIZE);
option.GetAddress().GetIid().SetFromExtAddress(aClientId.GetDuidLinkLayerAddress());
option.SetPreferredLifetime(IaAddress::kDefaultPreferredLifetime);
option.SetValidLifetime(IaAddress::kDefaultValidLiftetime);
SuccessOrExit(error = aMessage.Append(option));
exit:
return error;
}
Error Server::AppendRapidCommit(Message &aMessage)
{
RapidCommit option;
option.Init();
return aMessage.Append(option);
}
void Server::ApplyMeshLocalPrefix(void)
{
for (PrefixAgent &prefixAgent : mPrefixAgents)
{
if (prefixAgent.IsValid())
{
PrefixAgent *entry = &prefixAgent;
Get<ThreadNetif>().RemoveUnicastAddress(entry->GetAloc());
entry->GetAloc().GetAddress().SetPrefix(Get<Mle::MleRouter>().GetMeshLocalPrefix());
Get<ThreadNetif>().AddUnicastAddress(entry->GetAloc());
}
}
}
} // namespace Dhcp6
} // namespace ot
#endif // OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE