blob: 446e8a0852acd751afbb408b50bb1473ae59f6d2 [file] [log] [blame]
/*
* Copyright (c) 2016, Nest Labs, Inc.
* 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 Thread's EID-to-RLOC mapping and caching.
*/
#include <coap/coap_header.hpp>
#include <common/code_utils.hpp>
#include <common/debug.hpp>
#include <common/logging.hpp>
#include <common/encoding.hpp>
#include <mac/mac_frame.hpp>
#include <platform/random.h>
#include <thread/address_resolver.hpp>
#include <thread/mesh_forwarder.hpp>
#include <thread/mle_router.hpp>
#include <thread/thread_netif.hpp>
#include <thread/thread_tlvs.hpp>
#include <thread/thread_uris.hpp>
using Thread::Encoding::BigEndian::HostSwap16;
namespace Thread {
AddressResolver::AddressResolver(ThreadNetif &aThreadNetif) :
mAddressError(OPENTHREAD_URI_ADDRESS_ERROR, &HandleAddressError, this),
mAddressQuery(OPENTHREAD_URI_ADDRESS_QUERY, &HandleAddressQuery, this),
mAddressNotification(OPENTHREAD_URI_ADDRESS_NOTIFY, &HandleAddressNotification, this),
mIcmpHandler(&HandleDstUnreach, this),
mTimer(&HandleTimer, this),
mMeshForwarder(aThreadNetif.GetMeshForwarder()),
mCoapServer(aThreadNetif.GetCoapServer()),
mMle(aThreadNetif.GetMle()),
mNetif(aThreadNetif)
{
memset(&mCache, 0, sizeof(mCache));
mCoapServer.AddResource(mAddressError);
mCoapServer.AddResource(mAddressQuery);
mCoapServer.AddResource(mAddressNotification);
mCoapMessageId = otPlatRandomGet();
Ip6::Icmp::RegisterCallbacks(mIcmpHandler);
}
void AddressResolver::Clear()
{
memset(&mCache, 0, sizeof(mCache));
}
void AddressResolver::Remove(uint8_t routerId)
{
for (int i = 0; i < kCacheEntries; i++)
{
if ((mCache[i].mRloc16 >> 10) == routerId)
{
mCache[i].mState = Cache::kStateInvalid;
}
}
}
ThreadError AddressResolver::Resolve(const Ip6::Address &aEid, uint16_t &aRloc16)
{
ThreadError error = kThreadError_None;
Cache *entry = NULL;
for (int i = 0; i < kCacheEntries; i++)
{
if (mCache[i].mState != Cache::kStateInvalid)
{
if (memcmp(&mCache[i].mTarget, &aEid, sizeof(mCache[i].mTarget)) == 0)
{
entry = &mCache[i];
break;
}
}
else if (entry == NULL)
{
entry = &mCache[i];
}
}
VerifyOrExit(entry != NULL, error = kThreadError_NoBufs);
switch (entry->mState)
{
case Cache::kStateInvalid:
entry->mTarget = aEid;
entry->mRloc16 = Mac::kShortAddrInvalid;
entry->mTimeout = kAddressQueryTimeout;
entry->mFailures = 0;
entry->mRetryTimeout = kAddressQueryInitialRetryDelay;
entry->mState = Cache::kStateQuery;
SendAddressQuery(aEid);
error = kThreadError_AddressQuery;
break;
case Cache::kStateQuery:
if (entry->mTimeout > 0)
{
error = kThreadError_AddressQuery;
}
else if (entry->mTimeout == 0 && entry->mRetryTimeout == 0)
{
entry->mTimeout = kAddressQueryTimeout;
SendAddressQuery(aEid);
error = kThreadError_AddressQuery;
}
else
{
error = kThreadError_Drop;
}
break;
case Cache::kStateCached:
aRloc16 = entry->mRloc16;
break;
}
exit:
return error;
}
ThreadError AddressResolver::SendAddressQuery(const Ip6::Address &aEid)
{
ThreadError error;
Ip6::SockAddr sockaddr;
Message *message;
Coap::Header header;
ThreadTargetTlv targetTlv;
Ip6::MessageInfo messageInfo;
sockaddr.mPort = kCoapUdpPort;
mSocket.Open(&HandleUdpReceive, this);
mSocket.Bind(sockaddr);
for (size_t i = 0; i < sizeof(mCoapToken); i++)
{
mCoapToken[i] = otPlatRandomGet();
}
VerifyOrExit((message = Ip6::Udp::NewMessage(0)) != NULL, error = kThreadError_NoBufs);
header.Init();
header.SetVersion(1);
header.SetType(Coap::Header::kTypeNonConfirmable);
header.SetCode(Coap::Header::kCodePost);
header.SetMessageId(++mCoapMessageId);
header.SetToken(NULL, 0);
header.AppendUriPathOptions(OPENTHREAD_URI_ADDRESS_QUERY);
header.AppendContentFormatOption(Coap::Header::kApplicationOctetStream);
header.Finalize();
SuccessOrExit(error = message->Append(header.GetBytes(), header.GetLength()));
targetTlv.Init();
targetTlv.SetTarget(aEid);
SuccessOrExit(error = message->Append(&targetTlv, sizeof(targetTlv)));
memset(&messageInfo, 0, sizeof(messageInfo));
messageInfo.GetPeerAddr().m16[0] = HostSwap16(0xff03);
messageInfo.GetPeerAddr().m16[7] = HostSwap16(0x0002);
messageInfo.mPeerPort = kCoapUdpPort;
messageInfo.mInterfaceId = mNetif.GetInterfaceId();
SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
otLogInfoArp("Sent address query\n");
exit:
if (mTimer.IsRunning() == false)
{
mTimer.Start(kStateUpdatePeriod);
}
if (error != kThreadError_None && message != NULL)
{
Message::Free(*message);
}
return error;
}
void AddressResolver::HandleUdpReceive(void *aContext, otMessage aMessage, const otMessageInfo *aMessageInfo)
{
}
void AddressResolver::HandleAddressNotification(void *aContext, Coap::Header &aHeader, Message &aMessage,
const Ip6::MessageInfo &aMessageInfo)
{
AddressResolver *obj = reinterpret_cast<AddressResolver *>(aContext);
obj->HandleAddressNotification(aHeader, aMessage, aMessageInfo);
}
void AddressResolver::HandleAddressNotification(Coap::Header &aHeader, Message &aMessage,
const Ip6::MessageInfo &aMessageInfo)
{
ThreadTargetTlv targetTlv;
ThreadMeshLocalEidTlv mlIidTlv;
ThreadRloc16Tlv rloc16Tlv;
VerifyOrExit(aHeader.GetType() == Coap::Header::kTypeConfirmable &&
aHeader.GetCode() == Coap::Header::kCodePost, ;);
otLogInfoArp("Received address notification from %04x\n", HostSwap16(aMessageInfo.GetPeerAddr().m16[7]));
SuccessOrExit(ThreadTlv::GetTlv(aMessage, ThreadTlv::kTarget, sizeof(targetTlv), targetTlv));
VerifyOrExit(targetTlv.IsValid(), ;);
SuccessOrExit(ThreadTlv::GetTlv(aMessage, ThreadTlv::kMeshLocalEid, sizeof(mlIidTlv), mlIidTlv));
VerifyOrExit(mlIidTlv.IsValid(), ;);
SuccessOrExit(ThreadTlv::GetTlv(aMessage, ThreadTlv::kRloc16, sizeof(rloc16Tlv), rloc16Tlv));
VerifyOrExit(rloc16Tlv.IsValid(), ;);
for (int i = 0; i < kCacheEntries; i++)
{
if (mCache[i].mTarget == *targetTlv.GetTarget())
{
if (mCache[i].mState != Cache::kStateCached ||
memcmp(mCache[i].mMeshLocalIid, mlIidTlv.GetIid(), sizeof(mCache[i].mMeshLocalIid)) == 0)
{
memcpy(mCache[i].mMeshLocalIid, mlIidTlv.GetIid(), sizeof(mCache[i].mMeshLocalIid));
mCache[i].mRloc16 = rloc16Tlv.GetRloc16();
mCache[i].mRetryTimeout = 0;
mCache[i].mTimeout = 0;
mCache[i].mFailures = 0;
mCache[i].mState = Cache::kStateCached;
SendAddressNotificationResponse(aHeader, aMessageInfo);
mMeshForwarder.HandleResolved(*targetTlv.GetTarget(), kThreadError_None);
}
else
{
SendAddressError(targetTlv, mlIidTlv, NULL);
}
ExitNow();
}
}
ExitNow();
exit:
{}
}
void AddressResolver::SendAddressNotificationResponse(const Coap::Header &aRequestHeader,
const Ip6::MessageInfo &aRequestInfo)
{
ThreadError error;
Message *message;
Coap::Header responseHeader;
Ip6::MessageInfo responseInfo;
VerifyOrExit((message = Ip6::Udp::NewMessage(0)) != NULL, error = kThreadError_NoBufs);
responseHeader.Init();
responseHeader.SetVersion(1);
responseHeader.SetType(Coap::Header::kTypeAcknowledgment);
responseHeader.SetCode(Coap::Header::kCodeChanged);
responseHeader.SetMessageId(aRequestHeader.GetMessageId());
responseHeader.SetToken(aRequestHeader.GetToken(), aRequestHeader.GetTokenLength());
responseHeader.Finalize();
SuccessOrExit(error = message->Append(responseHeader.GetBytes(), responseHeader.GetLength()));
memcpy(&responseInfo, &aRequestInfo, sizeof(responseInfo));
memset(&responseInfo.mSockAddr, 0, sizeof(responseInfo.mSockAddr));
SuccessOrExit(error = mCoapServer.SendMessage(*message, responseInfo));
otLogInfoArp("Sent address notification acknowledgment\n");
exit:
if (error != kThreadError_None && message != NULL)
{
Message::Free(*message);
}
}
ThreadError AddressResolver::SendAddressError(const ThreadTargetTlv &aTarget, const ThreadMeshLocalEidTlv &aEid,
const Ip6::Address *aDestination)
{
ThreadError error;
Message *message;
Coap::Header header;
Ip6::MessageInfo messageInfo;
Ip6::SockAddr sockaddr;
sockaddr.mPort = kCoapUdpPort;
mSocket.Open(&HandleUdpReceive, this);
mSocket.Bind(sockaddr);
for (size_t i = 0; i < sizeof(mCoapToken); i++)
{
mCoapToken[i] = otPlatRandomGet();
}
VerifyOrExit((message = Ip6::Udp::NewMessage(0)) != NULL, error = kThreadError_NoBufs);
header.Init();
header.SetVersion(1);
header.SetType(Coap::Header::kTypeNonConfirmable);
header.SetCode(Coap::Header::kCodePost);
header.SetMessageId(++mCoapMessageId);
header.SetToken(NULL, 0);
header.AppendUriPathOptions(OPENTHREAD_URI_ADDRESS_ERROR);
header.AppendContentFormatOption(Coap::Header::kApplicationOctetStream);
header.Finalize();
SuccessOrExit(error = message->Append(header.GetBytes(), header.GetLength()));
SuccessOrExit(error = message->Append(&aTarget, sizeof(aTarget)));
SuccessOrExit(error = message->Append(&aEid, sizeof(aEid)));
memset(&messageInfo, 0, sizeof(messageInfo));
if (aDestination == NULL)
{
messageInfo.GetPeerAddr().m16[0] = HostSwap16(0xff03);
messageInfo.GetPeerAddr().m16[7] = HostSwap16(0x0002);
}
else
{
memcpy(&messageInfo.GetPeerAddr(), aDestination, sizeof(messageInfo.GetPeerAddr()));
}
messageInfo.mPeerPort = kCoapUdpPort;
messageInfo.mInterfaceId = mNetif.GetInterfaceId();
SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
otLogInfoArp("Sent address error\n");
exit:
if (error != kThreadError_None && message != NULL)
{
Message::Free(*message);
}
return error;
}
void AddressResolver::HandleAddressError(void *aContext, Coap::Header &aHeader,
Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
AddressResolver *obj = reinterpret_cast<AddressResolver *>(aContext);
obj->HandleAddressError(aHeader, aMessage, aMessageInfo);
}
void AddressResolver::HandleAddressError(Coap::Header &aHeader, Message &aMessage,
const Ip6::MessageInfo &aMessageInfo)
{
ThreadError error = kThreadError_None;
ThreadTargetTlv targetTlv;
ThreadMeshLocalEidTlv mlIidTlv;
Child *children;
uint8_t numChildren;
Mac::ExtAddress macAddr;
Ip6::Address destination;
VerifyOrExit(aHeader.GetCode() == Coap::Header::kCodePost, error = kThreadError_Drop);
otLogInfoArp("Received address error notification\n");
SuccessOrExit(error = ThreadTlv::GetTlv(aMessage, ThreadTlv::kTarget, sizeof(targetTlv), targetTlv));
VerifyOrExit(targetTlv.IsValid(), error = kThreadError_Parse);
SuccessOrExit(error = ThreadTlv::GetTlv(aMessage, ThreadTlv::kMeshLocalEid, sizeof(mlIidTlv), mlIidTlv));
VerifyOrExit(mlIidTlv.IsValid(), error = kThreadError_Parse);
for (const Ip6::NetifUnicastAddress *address = mNetif.GetUnicastAddresses(); address; address = address->GetNext())
{
if (memcmp(&address->mAddress, targetTlv.GetTarget(), sizeof(address->mAddress)) == 0 &&
memcmp(mMle.GetMeshLocal64()->GetIid(), mlIidTlv.GetIid(), 8))
{
// Target EID matches address and Mesh Local EID differs
mNetif.RemoveUnicastAddress(*address);
ExitNow();
}
}
children = mMle.GetChildren(&numChildren);
memcpy(&macAddr, mlIidTlv.GetIid(), sizeof(macAddr));
macAddr.m8[0] ^= 0x2;
for (int i = 0; i < Mle::kMaxChildren; i++)
{
if (children[i].mState != Neighbor::kStateValid || (children[i].mMode & Mle::ModeTlv::kModeFFD) != 0)
{
continue;
}
for (int j = 0; j < Child::kMaxIp6AddressPerChild; j++)
{
if (memcmp(&children[i].mIp6Address[j], targetTlv.GetTarget(), sizeof(children[i].mIp6Address[j])) == 0 &&
memcmp(&children[i].mMacAddr, &macAddr, sizeof(children[i].mMacAddr)))
{
// Target EID matches child address and Mesh Local EID differs on child
memset(&children[i].mIp6Address[j], 0, sizeof(children[i].mIp6Address[j]));
memset(&destination, 0, sizeof(destination));
destination.m16[0] = HostSwap16(0xfe80);
destination.SetIid(children[i].mMacAddr);
SendAddressError(targetTlv, mlIidTlv, &destination);
ExitNow();
}
}
}
exit:
{}
}
void AddressResolver::HandleAddressQuery(void *aContext, Coap::Header &aHeader, Message &aMessage,
const Ip6::MessageInfo &aMessageInfo)
{
AddressResolver *obj = reinterpret_cast<AddressResolver *>(aContext);
obj->HandleAddressQuery(aHeader, aMessage, aMessageInfo);
}
void AddressResolver::HandleAddressQuery(Coap::Header &aHeader, Message &aMessage,
const Ip6::MessageInfo &aMessageInfo)
{
ThreadTargetTlv targetTlv;
ThreadMeshLocalEidTlv mlIidTlv;
ThreadLastTransactionTimeTlv lastTransactionTimeTlv;
Child *children;
uint8_t numChildren;
VerifyOrExit(aHeader.GetType() == Coap::Header::kTypeNonConfirmable &&
aHeader.GetCode() == Coap::Header::kCodePost, ;);
otLogInfoArp("Received address query from %04x\n", HostSwap16(aMessageInfo.GetPeerAddr().m16[7]));
SuccessOrExit(ThreadTlv::GetTlv(aMessage, ThreadTlv::kTarget, sizeof(targetTlv), targetTlv));
VerifyOrExit(targetTlv.IsValid(), ;);
mlIidTlv.Init();
lastTransactionTimeTlv.Init();
if (mNetif.IsUnicastAddress(*targetTlv.GetTarget()))
{
mlIidTlv.SetIid(mMle.GetMeshLocal64()->GetIid());
SendAddressQueryResponse(targetTlv, mlIidTlv, NULL, aMessageInfo.GetPeerAddr());
ExitNow();
}
children = mMle.GetChildren(&numChildren);
for (int i = 0; i < Mle::kMaxChildren; i++)
{
if (children[i].mState != Neighbor::kStateValid || (children[i].mMode & Mle::ModeTlv::kModeFFD) != 0)
{
continue;
}
for (int j = 0; j < Child::kMaxIp6AddressPerChild; j++)
{
if (memcmp(&children[i].mIp6Address[j], targetTlv.GetTarget(), sizeof(children[i].mIp6Address[j])))
{
continue;
}
children[i].mMacAddr.m8[0] ^= 0x2;
mlIidTlv.SetIid(children[i].mMacAddr.m8);
children[i].mMacAddr.m8[0] ^= 0x2;
lastTransactionTimeTlv.SetTime(Timer::GetNow() - children[i].mLastHeard);
SendAddressQueryResponse(targetTlv, mlIidTlv, &lastTransactionTimeTlv, aMessageInfo.GetPeerAddr());
ExitNow();
}
}
exit:
{}
}
void AddressResolver::SendAddressQueryResponse(const ThreadTargetTlv &aTargetTlv,
const ThreadMeshLocalEidTlv &aMlIidTlv,
const ThreadLastTransactionTimeTlv *aLastTransactionTimeTlv,
const Ip6::Address &aDestination)
{
ThreadError error;
Message *message;
Coap::Header header;
ThreadRloc16Tlv rloc16Tlv;
Ip6::MessageInfo messageInfo;
VerifyOrExit((message = Ip6::Udp::NewMessage(0)) != NULL, error = kThreadError_NoBufs);
header.Init();
header.SetVersion(1);
header.SetType(Coap::Header::kTypeConfirmable);
header.SetCode(Coap::Header::kCodePost);
header.SetMessageId(++mCoapMessageId);
header.SetToken(NULL, 0);
header.AppendUriPathOptions(OPENTHREAD_URI_ADDRESS_NOTIFY);
header.AppendContentFormatOption(Coap::Header::kApplicationOctetStream);
header.Finalize();
SuccessOrExit(error = message->Append(header.GetBytes(), header.GetLength()));
SuccessOrExit(error = message->Append(&aTargetTlv, sizeof(aTargetTlv)));
SuccessOrExit(error = message->Append(&aMlIidTlv, sizeof(aMlIidTlv)));
rloc16Tlv.Init();
rloc16Tlv.SetRloc16(mMle.GetRloc16());
SuccessOrExit(error = message->Append(&rloc16Tlv, sizeof(rloc16Tlv)));
if (aLastTransactionTimeTlv != NULL)
{
SuccessOrExit(error = message->Append(aLastTransactionTimeTlv, sizeof(*aLastTransactionTimeTlv)));
}
memset(&messageInfo, 0, sizeof(messageInfo));
memcpy(&messageInfo.GetPeerAddr(), &aDestination, sizeof(messageInfo.GetPeerAddr()));
messageInfo.mInterfaceId = messageInfo.mInterfaceId;
messageInfo.mPeerPort = kCoapUdpPort;
SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
otLogInfoArp("Sent address notification\n");
exit:
if (error != kThreadError_None && message != NULL)
{
Message::Free(*message);
}
}
void AddressResolver::HandleTimer(void *aContext)
{
AddressResolver *obj = reinterpret_cast<AddressResolver *>(aContext);
obj->HandleTimer();
}
void AddressResolver::HandleTimer()
{
bool continueTimer = false;
for (int i = 0; i < kCacheEntries; i++)
{
if (mCache[i].mState != Cache::kStateQuery)
{
continue;
}
continueTimer = true;
if (mCache[i].mTimeout > 0)
{
mCache[i].mTimeout--;
if (mCache[i].mTimeout == 0)
{
mCache[i].mFailures++;
mCache[i].mRetryTimeout = kAddressQueryInitialRetryDelay * (1 << mCache[i].mFailures);
if (mCache[i].mRetryTimeout > kAddressQueryMaxRetryDelay)
{
mCache[i].mRetryTimeout = kAddressQueryMaxRetryDelay;
mCache[i].mFailures--;
}
mMeshForwarder.HandleResolved(mCache[i].mTarget, kThreadError_Drop);
}
}
else if (mCache[i].mRetryTimeout > 0)
{
mCache[i].mRetryTimeout--;
}
}
if (continueTimer)
{
mTimer.Start(kStateUpdatePeriod);
}
}
void AddressResolver::HandleDstUnreach(void *aContext, Message &aMessage, const Ip6::MessageInfo &aMessageInfo,
const Ip6::IcmpHeader &aIcmpHeader)
{
AddressResolver *obj = reinterpret_cast<AddressResolver *>(aContext);
obj->HandleDstUnreach(aMessage, aMessageInfo, aIcmpHeader);
}
void AddressResolver::HandleDstUnreach(Message &aMessage, const Ip6::MessageInfo &aMessageInfo,
const Ip6::IcmpHeader &aIcmpHeader)
{
Ip6::Header ip6Header;
VerifyOrExit(aIcmpHeader.GetCode() == Ip6::IcmpHeader::kCodeDstUnreachNoRoute, ;);
VerifyOrExit(aMessage.Read(aMessage.GetOffset(), sizeof(ip6Header), &ip6Header) == sizeof(ip6Header), ;);
for (int i = 0; i < kCacheEntries; i++)
{
if (mCache[i].mState != Cache::kStateInvalid &&
memcmp(&mCache[i].mTarget, &ip6Header.GetDestination(), sizeof(mCache[i].mTarget)) == 0)
{
mCache[i].mState = Cache::kStateInvalid;
otLogInfoArp("cache entry removed!\n");
break;
}
}
exit:
{}
}
} // namespace Thread