blob: 3e7f0d6fa77b1cbce5436375ed052a959601bd30 [file] [log] [blame]
/*
* Copyright (c) 2017, 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
* The file implements the Thread border agent.
*/
#include "agent/border_agent.hpp"
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "agent/border_agent.hpp"
#include "agent/ncp.hpp"
#include "agent/uris.hpp"
#include "common/code_utils.hpp"
#include "common/logging.hpp"
#include "common/tlv.hpp"
#include "common/types.hpp"
#include "utils/hex.hpp"
#include "utils/strcpy_utils.hpp"
namespace otbr {
#if OTBR_ENABLE_NCP_OPENTHREAD
static const uint16_t kThreadVersion11 = 2; ///< Thread Version 1.1
static const uint16_t kThreadVersion12 = 3; ///< Thread Version 1.2
#endif
static const char kBorderAgentServiceType[] = "_meshcop._udp."; ///< Border agent service type of mDNS
static const size_t kMaxSizeOfPacket = 1500; ///< Max size of packet in bytes.
/**
* Locators
*
*/
enum
{
kAloc16Leader = 0xfc00, ///< leader anycast locator.
kInvalidLocator = 0xffff, ///< invalid locator.
};
/**
* UDP ports
*
*/
enum
{
kBorderAgentUdpPort = 49191, ///< Thread commissioning port.
};
BorderAgent::BorderAgent(Ncp::Controller *aNcp)
#if OTBR_ENABLE_MDNS_AVAHI || OTBR_ENABLE_MDNS_MDNSSD || OTBR_ENABLE_MDNS_MOJO
: mPublisher(Mdns::Publisher::Create(AF_UNSPEC, NULL, NULL, HandleMdnsState, this))
#else
: mPublisher(NULL)
#endif
, mNcp(aNcp)
#if OTBR_ENABLE_NCP_WPANTUND
, mSocket(-1)
#endif
, mThreadStarted(false)
{
}
void BorderAgent::Init(void)
{
memset(mNetworkName, 0, sizeof(mNetworkName));
memset(mExtPanId, 0, sizeof(mExtPanId));
mExtPanIdInitialized = false;
mThreadVersion = 0;
#if OTBR_ENABLE_NCP_WPANTUND
mNcp->On(Ncp::kEventUdpForwardStream, SendToCommissioner, this);
#endif
#if OTBR_ENABLE_MDNS_AVAHI || OTBR_ENABLE_MDNS_MDNSSD || OTBR_ENABLE_MDNS_MOJO
mNcp->On(Ncp::kEventExtPanId, HandleExtPanId, this);
mNcp->On(Ncp::kEventNetworkName, HandleNetworkName, this);
mNcp->On(Ncp::kEventThreadVersion, HandleThreadVersion, this);
#endif
mNcp->On(Ncp::kEventThreadState, HandleThreadState, this);
mNcp->On(Ncp::kEventPSKc, HandlePSKc, this);
otbrLogResult("Check if Thread is up", mNcp->RequestEvent(Ncp::kEventThreadState));
otbrLogResult("Check if PSKc is initialized", mNcp->RequestEvent(Ncp::kEventPSKc));
}
otbrError BorderAgent::Start(void)
{
otbrError error = OTBR_ERROR_NONE;
VerifyOrExit(mThreadStarted && mPSKcInitialized, errno = EAGAIN, error = OTBR_ERROR_ERRNO);
// In case we didn't receive Thread down event.
Stop();
#if OTBR_ENABLE_NCP_WPANTUND
struct sockaddr_in6 sin6;
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_port = htons(kBorderAgentUdpPort);
mSocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
VerifyOrExit(mSocket != -1, error = OTBR_ERROR_ERRNO);
VerifyOrExit(bind(mSocket, reinterpret_cast<struct sockaddr *>(&sin6), sizeof(sin6)) == 0,
error = OTBR_ERROR_ERRNO);
#endif
#if OTBR_ENABLE_MDNS_AVAHI || OTBR_ENABLE_MDNS_MDNSSD || OTBR_ENABLE_MDNS_MOJO
SuccessOrExit(error = mNcp->RequestEvent(Ncp::kEventNetworkName));
SuccessOrExit(error = mNcp->RequestEvent(Ncp::kEventExtPanId));
// Currently supports only NCP_OPENTHREAD
#if OTBR_ENABLE_NCP_OPENTHREAD
SuccessOrExit(error = mNcp->RequestEvent(Ncp::kEventThreadVersion));
#endif // OTBR_ENABLE_NCP_OPENTHREAD
StartPublishService();
#endif // OTBR_ENABLE_MDNS_AVAHI || OTBR_ENABLE_MDNS_MDNSSD || OTBR_ENABLE_MDNS_MOJO
// Suppress unused warning of label exit
ExitNow();
exit:
otbrLogResult("Start Thread Border Agent", error);
return error;
}
void BorderAgent::Stop(void)
{
#if OTBR_ENABLE_NCP_WPANTUND
if (mSocket != -1)
{
close(mSocket);
mSocket = -1;
}
#endif // OTBR_ENABLE_NCP_WPANTUND
#if OTBR_ENABLE_MDNS_AVAHI || OTBR_ENABLE_MDNS_MDNSSD || OTBR_ENABLE_MDNS_MOJO
StopPublishService();
#endif
}
BorderAgent::~BorderAgent(void)
{
Stop();
if (mPublisher != NULL)
{
delete mPublisher;
mPublisher = NULL;
}
}
void BorderAgent::HandleMdnsState(Mdns::State aState)
{
switch (aState)
{
case Mdns::kStateReady:
PublishService();
break;
default:
otbrLog(OTBR_LOG_WARNING, "MDNS service not available!");
break;
}
}
#if OTBR_ENABLE_NCP_WPANTUND
void BorderAgent::SendToCommissioner(void *aContext, int aEvent, va_list aArguments)
{
struct sockaddr_in6 sin6;
const uint8_t * packet = va_arg(aArguments, const uint8_t *);
uint16_t length = static_cast<uint16_t>(va_arg(aArguments, unsigned int));
uint16_t peerPort = static_cast<uint16_t>(va_arg(aArguments, unsigned int));
const in6_addr * addr = va_arg(aArguments, const in6_addr *);
uint16_t sockPort = static_cast<uint16_t>(va_arg(aArguments, unsigned int));
BorderAgent * borderAgent = static_cast<BorderAgent *>(aContext);
(void)aEvent;
assert(aEvent == Ncp::kEventUdpForwardStream);
VerifyOrExit(sockPort == kBorderAgentUdpPort);
VerifyOrExit(borderAgent->mSocket != -1);
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
memcpy(sin6.sin6_addr.s6_addr, addr->s6_addr, sizeof(sin6.sin6_addr));
sin6.sin6_port = htons(peerPort);
{
ssize_t sent =
sendto(borderAgent->mSocket, packet, length, 0, reinterpret_cast<const sockaddr *>(&sin6), sizeof(sin6));
VerifyOrExit(sent == static_cast<ssize_t>(length), perror("send to commissioner"));
}
otbrLog(OTBR_LOG_DEBUG, "Sent to commissioner");
exit:
return;
}
#endif // OTBR_ENABLE_NCP_WPANTUND
void BorderAgent::UpdateFdSet(fd_set & aReadFdSet,
fd_set & aWriteFdSet,
fd_set & aErrorFdSet,
int & aMaxFd,
timeval &aTimeout)
{
if (mPublisher != NULL)
{
mPublisher->UpdateFdSet(aReadFdSet, aWriteFdSet, aErrorFdSet, aMaxFd, aTimeout);
}
#if OTBR_ENABLE_NCP_WPANTUND
if (mSocket != -1)
{
FD_SET(mSocket, &aReadFdSet);
if (mSocket > aMaxFd)
{
aMaxFd = mSocket;
}
}
#endif // OTBR_ENABLE_NCP_WPANTUND
}
void BorderAgent::Process(const fd_set &aReadFdSet, const fd_set &aWriteFdSet, const fd_set &aErrorFdSet)
{
if (mPublisher != NULL)
{
mPublisher->Process(aReadFdSet, aWriteFdSet, aErrorFdSet);
}
#if OTBR_ENABLE_NCP_WPANTUND
uint8_t packet[kMaxSizeOfPacket];
struct sockaddr_in6 sin6;
ssize_t len = sizeof(packet);
socklen_t socklen = sizeof(sin6);
VerifyOrExit(mSocket != -1 && FD_ISSET(mSocket, &aReadFdSet));
len = recvfrom(mSocket, packet, sizeof(packet), 0, reinterpret_cast<struct sockaddr *>(&sin6), &socklen);
VerifyOrExit(len > 0);
mNcp->UdpForwardSend(packet, static_cast<uint16_t>(len), ntohs(sin6.sin6_port), sin6.sin6_addr,
kBorderAgentUdpPort);
exit:
#endif
return;
}
#if OTBR_ENABLE_NCP_OPENTHREAD
static const char *ThreadVersionToString(uint16_t aThreadVersion)
{
switch (aThreadVersion)
{
case kThreadVersion11:
return "1.1.1";
case kThreadVersion12:
return "1.2.0";
default:
otbrLog(OTBR_LOG_ERR, "unexpected thread version %hu", aThreadVersion);
abort();
}
}
#endif
void BorderAgent::PublishService(void)
{
char xpanid[sizeof(mExtPanId) * 2 + 1];
assert(mNetworkName[0] != '\0');
assert(mExtPanIdInitialized);
Utils::Bytes2Hex(mExtPanId, sizeof(mExtPanId), xpanid);
#if OTBR_ENABLE_NCP_OPENTHREAD
assert(mThreadVersion != 0);
mPublisher->PublishService(kBorderAgentUdpPort, mNetworkName, kBorderAgentServiceType, "nn", mNetworkName, "xp",
xpanid, "tv", ThreadVersionToString(mThreadVersion), NULL);
#else
mPublisher->PublishService(kBorderAgentUdpPort, mNetworkName, kBorderAgentServiceType, "nn", mNetworkName, "xp",
xpanid, NULL);
#endif
}
void BorderAgent::StartPublishService(void)
{
VerifyOrExit(mNetworkName[0] != '\0');
VerifyOrExit(mExtPanIdInitialized);
#if OTBR_ENABLE_NCP_OPENTHREAD
VerifyOrExit(mThreadVersion != 0);
#endif
if (mPublisher->IsStarted())
{
PublishService();
}
else
{
mPublisher->Start();
}
exit:
otbrLog(OTBR_LOG_INFO, "Start publishing service");
}
void BorderAgent::StopPublishService(void)
{
VerifyOrExit(mPublisher != NULL);
if (mPublisher->IsStarted())
{
mPublisher->Stop();
}
exit:
otbrLog(OTBR_LOG_INFO, "Stop publishing service");
}
void BorderAgent::SetNetworkName(const char *aNetworkName)
{
strcpy_safe(mNetworkName, sizeof(mNetworkName), aNetworkName);
#if OTBR_ENABLE_MDNS_AVAHI || OTBR_ENABLE_MDNS_MDNSSD || OTBR_ENABLE_MDNS_MOJO
if (mThreadStarted)
{
// Restart publisher to publish new service name.
mPublisher->Stop();
StartPublishService();
}
#endif
}
void BorderAgent::SetExtPanId(const uint8_t *aExtPanId)
{
memcpy(mExtPanId, aExtPanId, sizeof(mExtPanId));
mExtPanIdInitialized = true;
#if OTBR_ENABLE_MDNS_AVAHI || OTBR_ENABLE_MDNS_MDNSSD || OTBR_ENABLE_MDNS_MOJO
if (mThreadStarted)
{
StartPublishService();
}
#endif
}
void BorderAgent::SetThreadVersion(uint16_t aThreadVersion)
{
mThreadVersion = aThreadVersion;
#if OTBR_ENABLE_MDNS_AVAHI || OTBR_ENABLE_MDNS_MDNSSD || OTBR_ENABLE_MDNS_MOJO
if (mThreadStarted)
{
StartPublishService();
}
#endif
}
void BorderAgent::HandlePSKc(void *aContext, int aEvent, va_list aArguments)
{
assert(aEvent == Ncp::kEventPSKc);
static_cast<BorderAgent *>(aContext)->HandlePSKc(va_arg(aArguments, const uint8_t *));
}
void BorderAgent::HandlePSKc(const uint8_t *aPSKc)
{
mPSKcInitialized = false;
for (size_t i = 0; i < kSizePSKc; ++i)
{
if (aPSKc[i] != 0)
{
mPSKcInitialized = true;
break;
}
}
if (mPSKcInitialized)
{
Start();
}
else
{
Stop();
}
otbrLog(OTBR_LOG_INFO, "PSKc is %s", (mPSKcInitialized ? "initialized" : "not initialized"));
}
void BorderAgent::HandleThreadState(bool aStarted)
{
VerifyOrExit(mThreadStarted != aStarted);
mThreadStarted = aStarted;
if (aStarted)
{
SuccessOrExit(mNcp->RequestEvent(Ncp::kEventPSKc));
Start();
}
else
{
Stop();
}
exit:
otbrLog(OTBR_LOG_INFO, "Thread is %s", (aStarted ? "up" : "down"));
}
void BorderAgent::HandleThreadState(void *aContext, int aEvent, va_list aArguments)
{
assert(aEvent == Ncp::kEventThreadState);
int started = va_arg(aArguments, int);
static_cast<BorderAgent *>(aContext)->HandleThreadState(started);
}
void BorderAgent::HandleNetworkName(void *aContext, int aEvent, va_list aArguments)
{
assert(aEvent == Ncp::kEventNetworkName);
const char *networkName = va_arg(aArguments, const char *);
static_cast<BorderAgent *>(aContext)->SetNetworkName(networkName);
}
void BorderAgent::HandleExtPanId(void *aContext, int aEvent, va_list aArguments)
{
assert(aEvent == Ncp::kEventExtPanId);
const uint8_t *xpanid = va_arg(aArguments, const uint8_t *);
static_cast<BorderAgent *>(aContext)->SetExtPanId(xpanid);
}
void BorderAgent::HandleThreadVersion(void *aContext, int aEvent, va_list aArguments)
{
assert(aEvent == Ncp::kEventThreadVersion);
// `uint16_t` has been promoted to `int`.
uint16_t threadVersion = static_cast<uint16_t>(va_arg(aArguments, int));
static_cast<BorderAgent *>(aContext)->SetThreadVersion(threadVersion);
}
} // namespace otbr