blob: 15c47707fe9113dda34bcd651d1fe62db64b840e [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.
*/
#define WPP_NAME "dhcp6_server.tmh"
#include <openthread/config.h>
#include "dhcp6_server.hpp"
#include <openthread/types.h>
#include "common/code_utils.hpp"
#include "common/encoding.hpp"
#include "common/logging.hpp"
#include "thread/mle.hpp"
#include "thread/thread_netif.hpp"
#if OPENTHREAD_ENABLE_DHCP6_SERVER
using ot::Encoding::BigEndian::HostSwap16;
using ot::Encoding::BigEndian::HostSwap32;
namespace ot {
namespace Dhcp6 {
Dhcp6Server::Dhcp6Server(ThreadNetif &aThreadNetif):
ThreadNetifLocator(aThreadNetif),
mSocket(aThreadNetif.GetIp6().mUdp)
{
for (uint8_t i = 0; i < OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES; i++)
{
memset(&(mPrefixAgents[i]), 0, sizeof(PrefixAgent));
memset(&mAgentsAloc[i], 0, sizeof(mAgentsAloc[i]));
}
mPrefixAgentsCount = 0;
mPrefixAgentsMask = 0;
}
otError Dhcp6Server::UpdateService(void)
{
ThreadNetif &netif = GetNetif();
otError error = OT_ERROR_NONE;
bool found;
uint8_t i;
uint16_t rloc16 = netif.GetMle().GetRloc16();
Ip6::Address *address = NULL;
otNetworkDataIterator iterator;
otBorderRouterConfig config;
Lowpan::Context lowpanContext;
// remove dhcp agent aloc and prefix delegation
for (i = 0; i < OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES; i++)
{
found = false;
if (!mAgentsAloc[i].mValid)
{
continue;
}
address = &(mAgentsAloc[i].GetAddress());
iterator = OT_NETWORK_DATA_ITERATOR_INIT;
while (netif.GetNetworkDataLeader().GetNextOnMeshPrefix(&iterator, rloc16, &config) == OT_ERROR_NONE)
{
if (!config.mDhcp)
{
continue;
}
netif.GetNetworkDataLeader().GetContext(*static_cast<const Ip6::Address *>(&(config.mPrefix.mPrefix)),
lowpanContext);
if (address->mFields.m8[15] == lowpanContext.mContextId)
{
// still in network data
found = true;
break;
}
}
if (!found)
{
netif.GetNetworkDataLeader().GetContext(address->mFields.m8[15], lowpanContext);
netif.RemoveUnicastAddress(mAgentsAloc[i]);
mAgentsAloc[i].mValid = false;
RemovePrefixAgent(lowpanContext.mPrefix);
}
}
// add dhcp agent aloc and prefix delegation
iterator = OT_NETWORK_DATA_ITERATOR_INIT;
while (netif.GetNetworkDataLeader().GetNextOnMeshPrefix(&iterator, rloc16, &config) == OT_ERROR_NONE)
{
found = false;
if (!config.mDhcp)
{
continue;
}
netif.GetNetworkDataLeader().GetContext(*static_cast<const Ip6::Address *>(&config.mPrefix.mPrefix),
lowpanContext);
for (i = 0; i < OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES; i++)
{
address = &(mAgentsAloc[i].GetAddress());
if ((mAgentsAloc[i].mValid) && (address->mFields.m8[15] == lowpanContext.mContextId))
{
found = true;
break;
}
}
// alreay added
if (found)
{
continue;
}
for (i = 0; i < OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES; i++)
{
if (!mAgentsAloc[i].mValid)
{
address = &(mAgentsAloc[i].GetAddress());
memcpy(address, netif.GetMle().GetMeshLocalPrefix(), 8);
address->mFields.m16[4] = HostSwap16(0x0000);
address->mFields.m16[5] = HostSwap16(0x00ff);
address->mFields.m16[6] = HostSwap16(0xfe00);
address->mFields.m8[14] = Ip6::Address::kAloc16Mask;
address->mFields.m8[15] = lowpanContext.mContextId;
mAgentsAloc[i].mPrefixLength = 128;
mAgentsAloc[i].mPreferred = true;
mAgentsAloc[i].mValid = true;
netif.AddUnicastAddress(mAgentsAloc[i]);
AddPrefixAgent(config.mPrefix);
break;
}
}
// if no available Dhcp Agent Aloc resources
if (i == OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES)
{
ExitNow(error = OT_ERROR_NO_BUFS);
}
}
if (mPrefixAgentsCount > 0)
{
Start();
}
else
{
Stop();
}
exit:
return error;
}
otError Dhcp6Server::Start(void)
{
Ip6::SockAddr sockaddr;
sockaddr.mPort = kDhcpServerPort;
mSocket.Open(&Dhcp6Server::HandleUdpReceive, this);
mSocket.Bind(sockaddr);
return OT_ERROR_NONE;
}
otError Dhcp6Server::Stop(void)
{
mSocket.Close();
return OT_ERROR_NONE;
}
otError Dhcp6Server::AddPrefixAgent(otIp6Prefix &aIp6Prefix)
{
otError error = OT_ERROR_NO_BUFS;
for (uint8_t i = 0; i < OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES; i++)
{
if (mPrefixAgents[i].GetPrefix()->mLength != 0)
{
continue;
}
mPrefixAgents[i].SetPrefix(aIp6Prefix);
mPrefixAgentsCount++;
ExitNow(error = OT_ERROR_NONE);
}
exit:
return error;
}
otError Dhcp6Server::RemovePrefixAgent(const uint8_t *aIp6Address)
{
otError error = OT_ERROR_NOT_FOUND;
otIp6Prefix *prefix = NULL;
for (uint8_t i = 0; i < OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES; i++)
{
prefix = mPrefixAgents[i].GetPrefix();
if (prefix->mLength == 0)
{
continue;
}
if (otIp6PrefixMatch(&(prefix->mPrefix), reinterpret_cast<const otIp6Address *>(aIp6Address)) >= prefix->mLength)
{
memset(&(mPrefixAgents[i]), 0, sizeof(PrefixAgent));
mPrefixAgentsCount--;
ExitNow(error = OT_ERROR_NONE);
}
}
exit:
return error;
}
void Dhcp6Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
Dhcp6Server *obj = static_cast<Dhcp6Server *>(aContext);
obj->HandleUdpReceive(*static_cast<Message *>(aMessage), *static_cast<const Ip6::MessageInfo *>(aMessageInfo));
}
void Dhcp6Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Dhcp6Header header;
otIp6Address dst = aMessageInfo.mPeerAddr;
VerifyOrExit(aMessage.Read(aMessage.GetOffset(), sizeof(header), &header) == sizeof(header));
aMessage.MoveOffset(sizeof(header));
// discard if not solicit type
VerifyOrExit((header.GetType() == kTypeSolicit));
ProcessSolicit(aMessage, dst, header.GetTransactionId());
exit:
return;
}
void Dhcp6Server::ProcessSolicit(Message &aMessage, otIp6Address &aDst, uint8_t *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 Dhcp6Server::FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, 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 Dhcp6Server::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset, ClientIdentifier &aClient)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(((aMessage.Read(aOffset, sizeof(aClient), &aClient) == sizeof(aClient)) &&
(aClient.GetLength() == (sizeof(aClient) - sizeof(Dhcp6Option))) &&
(aClient.GetDuidType() == kDuidLL) &&
(aClient.GetDuidHardwareType() == kHardwareTypeEui64)),
error = OT_ERROR_PARSE);
exit:
return error;
}
otError Dhcp6Server::ProcessElapsedTime(Message &aMessage, uint16_t aOffset)
{
otError error = OT_ERROR_NONE;
ElapsedTime option;
VerifyOrExit(((aMessage.Read(aOffset, sizeof(option), &option) == sizeof(option)) &&
(option.GetLength() == ((sizeof(option) - sizeof(Dhcp6Option))))),
error = OT_ERROR_PARSE);
exit:
return error;
}
otError Dhcp6Server::ProcessIaNa(Message &aMessage, uint16_t aOffset, IaNa &aIaNa)
{
otError error = OT_ERROR_NONE;
uint16_t optionOffset;
uint16_t length;
VerifyOrExit((aMessage.Read(aOffset, sizeof(aIaNa), &aIaNa) == sizeof(aIaNa)), error = OT_ERROR_PARSE);
aOffset += sizeof(aIaNa);
length = aIaNa.GetLength() + sizeof(Dhcp6Option) - sizeof(IaNa);
VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = OT_ERROR_PARSE);
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;
}
otError Dhcp6Server::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
{
otError error = OT_ERROR_NONE;
otIp6Prefix *prefix = NULL;
IaAddress option;
VerifyOrExit(((aMessage.Read(aOffset, sizeof(option), &option) == sizeof(option)) &&
option.GetLength() == (sizeof(option) - sizeof(Dhcp6Option))),
error = OT_ERROR_PARSE);
// mask matching prefix
for (uint8_t i = 0; i < OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES; i++)
{
prefix = mPrefixAgents[i].GetPrefix();
if (prefix->mLength == 0)
{
continue;
}
if (otIp6PrefixMatch(option.GetAddress(), &(prefix->mPrefix)) >= prefix->mLength)
{
mPrefixAgentsMask |= (1 << i);
break;
}
}
exit:
return error;
}
otError Dhcp6Server::SendReply(otIp6Address &aDst, uint8_t *aTransactionId, ClientIdentifier &aClient, IaNa &aIaNa)
{
otError error = OT_ERROR_NONE;
Ip6::MessageInfo messageInfo;
Message *message;
VerifyOrExit((message = mSocket.NewMessage(0)) != NULL, error = OT_ERROR_NO_BUFS);
SuccessOrExit(error = AppendHeader(*message, aTransactionId));
SuccessOrExit(error = AppendServerIdentifier(*message));
SuccessOrExit(error = AppendClientIdentifier(*message, aClient));
SuccessOrExit(error = AppendIaNa(*message, aIaNa));
SuccessOrExit(error = AppendStatusCode(*message, kStatusSuccess));
SuccessOrExit(error = AppendIaAddress(*message, aClient));
SuccessOrExit(error = AppendRapidCommit(*message));
memset(&messageInfo, 0, sizeof(messageInfo));
memcpy(&messageInfo.GetPeerAddr().mFields.m8, &aDst, sizeof(otIp6Address));
messageInfo.mPeerPort = kDhcpClientPort;
SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
exit:
if (message != NULL && error != OT_ERROR_NONE)
{
message->Free();
}
return error;
}
otError Dhcp6Server::AppendHeader(Message &aMessage, uint8_t *aTransactionId)
{
Dhcp6Header header;
header.Init();
header.SetType(kTypeReply);
header.SetTransactionId(aTransactionId);
return aMessage.Append(&header, sizeof(header));
}
otError Dhcp6Server::AppendClientIdentifier(Message &aMessage, ClientIdentifier &aClient)
{
return aMessage.Append(&aClient, sizeof(aClient));
}
otError Dhcp6Server::AppendServerIdentifier(Message &aMessage)
{
otError error = OT_ERROR_NONE;
ServerIdentifier option;
option.Init();
option.SetDuidType(kDuidLL);
option.SetDuidHardwareType(kHardwareTypeEui64);
option.SetDuidLinkLayerAddress(GetNetif().GetMac().GetExtAddress());
SuccessOrExit(error = aMessage.Append(&option, sizeof(option)));
exit:
return error;
}
otError Dhcp6Server::AppendIaNa(Message &aMessage, IaNa &aIaNa)
{
otError error = OT_ERROR_NONE;
uint16_t length = 0;
if (mPrefixAgentsMask)
{
for (uint8_t i = 0; i < OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES; i++)
{
if ((mPrefixAgentsMask & (1 << i)))
{
length += sizeof(IaAddress);
}
}
}
else
{
length += sizeof(IaAddress) * mPrefixAgentsCount;
}
length += sizeof(IaNa) + sizeof(StatusCode) - sizeof(Dhcp6Option);
aIaNa.SetLength(length);
aIaNa.SetT1(OT_DHCP6_DEFAULT_IA_NA_T1);
aIaNa.SetT2(OT_DHCP6_DEFAULT_IA_NA_T2);
SuccessOrExit(error = aMessage.Append(&aIaNa, sizeof(IaNa)));
exit:
return error;
}
otError Dhcp6Server::AppendStatusCode(Message &aMessage, Status aStatus)
{
StatusCode option;
option.Init();
option.SetStatusCode(aStatus);
return aMessage.Append(&option, sizeof(option));
}
otError Dhcp6Server::AppendIaAddress(Message &aMessage, ClientIdentifier &aClient)
{
otError error = OT_ERROR_NONE;
otIp6Prefix *prefix = NULL;
// if specified, only apply specified prefixes
if (mPrefixAgentsMask)
{
for (uint8_t i = 0; i < OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES; i++)
{
if (mPrefixAgentsMask & (1 << i))
{
prefix = mPrefixAgents[i].GetPrefix();
SuccessOrExit(error = AddIaAddress(aMessage, *prefix, aClient));
}
}
}
else // if not specified, apply all configured prefixes
{
for (uint8_t i = 0; i < OPENTHREAD_CONFIG_NUM_DHCP_PREFIXES; i++)
{
prefix = mPrefixAgents[i].GetPrefix();
if (prefix->mLength == 0)
{
continue;
}
SuccessOrExit(error = AddIaAddress(aMessage, *prefix, aClient));
}
}
exit:
return error;
}
otError Dhcp6Server::AddIaAddress(Message &aMessage, otIp6Prefix &aIp6Prefix, ClientIdentifier &aClient)
{
otError error = OT_ERROR_NONE;
IaAddress option;
option.Init();
memcpy((option.GetAddress()->mFields.m8), &(aIp6Prefix.mPrefix), 8);
memcpy(&(option.GetAddress()->mFields.m8[8]), aClient.GetDuidLinkLayerAddress(), sizeof(Mac::ExtAddress));
option.SetPreferredLifetime(OT_DHCP6_DEFAULT_PREFERRED_LIFETIME);
option.SetValidLifetime(OT_DHCP6_DEFAULT_VALID_LIFETIME);
SuccessOrExit(error = aMessage.Append(&option, sizeof(option)));
exit:
return error;
}
otError Dhcp6Server::AppendRapidCommit(Message &aMessage)
{
RapidCommit option;
option.Init();
return aMessage.Append(&option, sizeof(option));
}
} // namespace Dhcp6
} // namespace ot
#endif // OPENTHREAD_ENABLE_DHCP6_SERVER