blob: 0a35fc3ba67a7fb103f75ccfea6b12e593a07efa [file]
/*
* Copyright (c) 2018, 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 mDNS publisher based on mDNSResponder.
*/
#define OTBR_LOG_TAG "MDNS"
#include "mdns/mdns_mdnssd.hpp"
#include <algorithm>
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common/code_utils.hpp"
#include "common/dns_utils.hpp"
#include "common/logging.hpp"
#include "common/time.hpp"
namespace otbr {
namespace Mdns {
static const char kDomain[] = "local.";
static otbrError DNSErrorToOtbrError(DNSServiceErrorType aError)
{
otbrError error;
switch (aError)
{
case kDNSServiceErr_NoError:
error = OTBR_ERROR_NONE;
break;
case kDNSServiceErr_NoSuchKey:
case kDNSServiceErr_NoSuchName:
case kDNSServiceErr_NoSuchRecord:
error = OTBR_ERROR_NOT_FOUND;
break;
case kDNSServiceErr_Invalid:
case kDNSServiceErr_BadParam:
case kDNSServiceErr_BadFlags:
case kDNSServiceErr_BadInterfaceIndex:
error = OTBR_ERROR_INVALID_ARGS;
break;
case kDNSServiceErr_NameConflict:
error = OTBR_ERROR_DUPLICATED;
break;
case kDNSServiceErr_Unsupported:
error = OTBR_ERROR_NOT_IMPLEMENTED;
break;
case kDNSServiceErr_ServiceNotRunning:
error = OTBR_ERROR_INVALID_STATE;
break;
default:
error = OTBR_ERROR_MDNS;
break;
}
return error;
}
static const char *DNSErrorToString(DNSServiceErrorType aError)
{
switch (aError)
{
case kDNSServiceErr_NoError:
return "OK";
case kDNSServiceErr_Unknown:
// 0xFFFE FFFF
return "Unknown";
case kDNSServiceErr_NoSuchName:
return "No Such Name";
case kDNSServiceErr_NoMemory:
return "No Memory";
case kDNSServiceErr_BadParam:
return "Bad Param";
case kDNSServiceErr_BadReference:
return "Bad Reference";
case kDNSServiceErr_BadState:
return "Bad State";
case kDNSServiceErr_BadFlags:
return "Bad Flags";
case kDNSServiceErr_Unsupported:
return "Unsupported";
case kDNSServiceErr_NotInitialized:
return "Not Initialized";
case kDNSServiceErr_AlreadyRegistered:
return "Already Registered";
case kDNSServiceErr_NameConflict:
return "Name Conflict";
case kDNSServiceErr_Invalid:
return "Invalid";
case kDNSServiceErr_Firewall:
return "Firewall";
case kDNSServiceErr_Incompatible:
// client library incompatible with daemon
return "Incompatible";
case kDNSServiceErr_BadInterfaceIndex:
return "Bad Interface Index";
case kDNSServiceErr_Refused:
return "Refused";
case kDNSServiceErr_NoSuchRecord:
return "No Such Record";
case kDNSServiceErr_NoAuth:
return "No Auth";
case kDNSServiceErr_NoSuchKey:
return "No Such Key";
case kDNSServiceErr_NATTraversal:
return "NAT Traversal";
case kDNSServiceErr_DoubleNAT:
return "Double NAT";
case kDNSServiceErr_BadTime:
// Codes up to here existed in Tiger
return "Bad Time";
case kDNSServiceErr_BadSig:
return "Bad Sig";
case kDNSServiceErr_BadKey:
return "Bad Key";
case kDNSServiceErr_Transient:
return "Transient";
case kDNSServiceErr_ServiceNotRunning:
// Background daemon not running
return "Service Not Running";
case kDNSServiceErr_NATPortMappingUnsupported:
// NAT doesn't support NAT-PMP or UPnP
return "NAT Port Mapping Unsupported";
case kDNSServiceErr_NATPortMappingDisabled:
// NAT supports NAT-PMP or UPnP but it's disabled by the administrator
return "NAT Port Mapping Disabled";
case kDNSServiceErr_NoRouter:
// No router currently configured (probably no network connectivity)
return "No Router";
case kDNSServiceErr_PollingMode:
return "Polling Mode";
case kDNSServiceErr_Timeout:
return "Timeout";
default:
assert(false);
return nullptr;
}
}
PublisherMDnsSd::PublisherMDnsSd(StateCallback aCallback)
: mHostsRef(nullptr)
, mState(State::kIdle)
, mStateCallback(std::move(aCallback))
{
}
PublisherMDnsSd::~PublisherMDnsSd(void)
{
Stop(kNormalStop);
}
otbrError PublisherMDnsSd::Start(void)
{
mState = State::kReady;
mStateCallback(State::kReady);
return OTBR_ERROR_NONE;
}
bool PublisherMDnsSd::IsStarted(void) const
{
return mState == State::kReady;
}
void PublisherMDnsSd::Stop(StopMode aStopMode)
{
VerifyOrExit(mState == State::kReady);
// If we get a `kDNSServiceErr_ServiceNotRunning` and need to
// restart the `Publisher`, we should immediately de-allocate
// all `ServiceRef`. Otherwise, we first clear the `Registrations`
// list so that `DnssdHostRegisteration` destructor gets the chance
// to update registered records if needed.
switch (aStopMode)
{
case kNormalStop:
break;
case kStopOnServiceNotRunningError:
DeallocateHostsRef();
break;
}
mServiceRegistrations.clear();
mHostRegistrations.clear();
mKeyRegistrations.clear();
DeallocateHostsRef();
mSubscribedServices.clear();
mSubscribedHosts.clear();
mState = State::kIdle;
exit:
return;
}
DNSServiceErrorType PublisherMDnsSd::CreateSharedHostsRef(void)
{
DNSServiceErrorType dnsError = kDNSServiceErr_NoError;
VerifyOrExit(mHostsRef == nullptr);
dnsError = DNSServiceCreateConnection(&mHostsRef);
otbrLogDebug("Created new shared DNSServiceRef: %p", mHostsRef);
exit:
return dnsError;
}
void PublisherMDnsSd::DeallocateHostsRef(void)
{
VerifyOrExit(mHostsRef != nullptr);
HandleServiceRefDeallocating(mHostsRef);
DNSServiceRefDeallocate(mHostsRef);
otbrLogDebug("Deallocated DNSServiceRef for hosts: %p", mHostsRef);
mHostsRef = nullptr;
exit:
return;
}
void PublisherMDnsSd::Update(MainloopContext &aMainloop)
{
for (auto &kv : mServiceRegistrations)
{
auto &serviceReg = static_cast<DnssdServiceRegistration &>(*kv.second);
serviceReg.Update(aMainloop);
}
if (mHostsRef != nullptr)
{
int fd = DNSServiceRefSockFD(mHostsRef);
assert(fd != -1);
aMainloop.AddFdToReadSet(fd);
}
for (const auto &service : mSubscribedServices)
{
service->UpdateAll(aMainloop);
}
for (const auto &host : mSubscribedHosts)
{
host->Update(aMainloop);
}
}
void PublisherMDnsSd::Process(const MainloopContext &aMainloop)
{
mServiceRefsToProcess.clear();
for (auto &kv : mServiceRegistrations)
{
auto &serviceReg = static_cast<DnssdServiceRegistration &>(*kv.second);
serviceReg.Process(aMainloop, mServiceRefsToProcess);
}
if (mHostsRef != nullptr)
{
int fd = DNSServiceRefSockFD(mHostsRef);
if (FD_ISSET(fd, &aMainloop.mReadFdSet))
{
mServiceRefsToProcess.push_back(mHostsRef);
}
}
for (const auto &service : mSubscribedServices)
{
service->ProcessAll(aMainloop, mServiceRefsToProcess);
}
for (const auto &host : mSubscribedHosts)
{
host->Process(aMainloop, mServiceRefsToProcess);
}
for (DNSServiceRef serviceRef : mServiceRefsToProcess)
{
DNSServiceErrorType error;
// As we go through the list of `mServiceRefsToProcess` the call
// to `DNSServiceProcessResult()` can itself invoke callbacks
// into `PublisherMDnsSd` and OT, which in turn, may change the
// state of `Publisher` and potentially trigger a previously
// valid `ServiceRef` in the list to be deallocated. We use
// `HandleServiceRefDeallocating()` which is called whenever a
// `ServiceRef` is being deallocated and from this we update
// the entry in `mServiceRefsToProcess` list to `nullptr` so to
// avoid calling `DNSServiceProcessResult()` on an already
// freed `ServiceRef`.
if (serviceRef == nullptr)
{
continue;
}
error = DNSServiceProcessResult(serviceRef);
if (error != kDNSServiceErr_NoError)
{
otbrLogLevel logLevel = (error == kDNSServiceErr_BadReference) ? OTBR_LOG_INFO : OTBR_LOG_WARNING;
otbrLog(logLevel, OTBR_LOG_TAG, "DNSServiceProcessResult failed: %s (serviceRef = %p)",
DNSErrorToString(error), serviceRef);
}
if (error == kDNSServiceErr_ServiceNotRunning)
{
otbrLogWarning("Need to reconnect to mdnsd");
Stop(kStopOnServiceNotRunningError);
Start();
ExitNow();
}
}
exit:
return;
}
void PublisherMDnsSd::HandleServiceRefDeallocating(const DNSServiceRef &aServiceRef)
{
for (DNSServiceRef &entry : mServiceRefsToProcess)
{
if (entry == aServiceRef)
{
entry = nullptr;
}
}
}
void PublisherMDnsSd::DnssdServiceRegistration::Update(MainloopContext &aMainloop) const
{
int fd;
VerifyOrExit(mServiceRef != nullptr);
fd = DNSServiceRefSockFD(mServiceRef);
VerifyOrExit(fd != -1);
aMainloop.AddFdToReadSet(fd);
exit:
return;
}
void PublisherMDnsSd::DnssdServiceRegistration::Process(const MainloopContext &aMainloop,
std::vector<DNSServiceRef> &aReadyServices) const
{
int fd;
VerifyOrExit(mServiceRef != nullptr);
fd = DNSServiceRefSockFD(mServiceRef);
VerifyOrExit(fd != -1);
VerifyOrExit(FD_ISSET(fd, &aMainloop.mReadFdSet));
aReadyServices.push_back(mServiceRef);
exit:
return;
}
otbrError PublisherMDnsSd::DnssdServiceRegistration::Register(void)
{
std::string fullHostName;
std::string regType = MakeRegType(mType, mSubTypeList);
const char *hostNameCString = nullptr;
const char *serviceNameCString = nullptr;
DnssdKeyRegistration *keyReg;
DNSServiceErrorType dnsError;
if (!mHostName.empty())
{
fullHostName = MakeFullHostName(mHostName);
hostNameCString = fullHostName.c_str();
}
if (!mName.empty())
{
serviceNameCString = mName.c_str();
}
keyReg = static_cast<DnssdKeyRegistration *>(GetPublisher().FindKeyRegistration(mName, mType));
if (keyReg != nullptr)
{
keyReg->Unregister();
}
otbrLogInfo("Registering service %s.%s", mName.c_str(), regType.c_str());
VerifyOrExit(GetPublisher().IsStarted(), dnsError = kDNSServiceErr_ServiceNotRunning);
dnsError = DNSServiceRegister(&mServiceRef, kDNSServiceFlagsNoAutoRename, kDNSServiceInterfaceIndexAny,
serviceNameCString, regType.c_str(),
/* domain */ nullptr, hostNameCString, htons(mPort), mTxtData.size(), mTxtData.data(),
HandleRegisterResult, this);
if (dnsError != kDNSServiceErr_NoError)
{
HandleRegisterResult(/* aFlags */ 0, dnsError);
}
if (keyReg != nullptr)
{
keyReg->Register();
}
exit:
return GetPublisher().DnsErrorToOtbrError(dnsError);
}
void PublisherMDnsSd::DnssdServiceRegistration::Unregister(void)
{
DnssdKeyRegistration *keyReg = mRelatedKeyReg;
VerifyOrExit(GetPublisher().IsStarted());
VerifyOrExit(mServiceRef != nullptr);
// If we have a related key registration associated with this
// service registration, we first unregister it and after we
// deallocated the `mServiceRef` try to register it again
// (which will add it as an individual record not tied to a
// service registration). Note that the `keyReg->Unregister()`
// will clear the `mRelatedKeyReg` field, so we need to keep
// a local copy to it in `keyReg`.
if (keyReg != nullptr)
{
keyReg->Unregister();
}
GetPublisher().HandleServiceRefDeallocating(mServiceRef);
DNSServiceRefDeallocate(mServiceRef);
mServiceRef = nullptr;
if (keyReg != nullptr)
{
keyReg->Register();
}
exit:
return;
}
void PublisherMDnsSd::DnssdServiceRegistration::HandleRegisterResult(DNSServiceRef aServiceRef,
DNSServiceFlags aFlags,
DNSServiceErrorType aError,
const char *aName,
const char *aType,
const char *aDomain,
void *aContext)
{
OTBR_UNUSED_VARIABLE(aServiceRef);
OTBR_UNUSED_VARIABLE(aName);
OTBR_UNUSED_VARIABLE(aType);
OTBR_UNUSED_VARIABLE(aDomain);
static_cast<DnssdServiceRegistration *>(aContext)->HandleRegisterResult(aFlags, aError);
}
void PublisherMDnsSd::DnssdServiceRegistration::HandleRegisterResult(DNSServiceFlags aFlags, DNSServiceErrorType aError)
{
if (mRelatedKeyReg != nullptr)
{
mRelatedKeyReg->HandleRegisterResult(aError);
}
if ((aError == kDNSServiceErr_NoError) && (aFlags & kDNSServiceFlagsAdd))
{
otbrLogInfo("Successfully registered service %s.%s", mName.c_str(), mType.c_str());
Complete(OTBR_ERROR_NONE);
}
else
{
otbrLogErr("Failed to register service %s.%s: %s", mName.c_str(), mType.c_str(), DNSErrorToString(aError));
GetPublisher().RemoveServiceRegistration(mName, mType, DNSErrorToOtbrError(aError));
}
}
otbrError PublisherMDnsSd::DnssdHostRegistration::Register(void)
{
DNSServiceErrorType dnsError = kDNSServiceErr_NoError;
otbrLogInfo("Registering new host %s", mName.c_str());
VerifyOrExit(GetPublisher().IsStarted(), dnsError = kDNSServiceErr_ServiceNotRunning);
for (const Ip6Address &address : mAddresses)
{
DNSRecordRef recordRef = nullptr;
dnsError = GetPublisher().CreateSharedHostsRef();
VerifyOrExit(dnsError == kDNSServiceErr_NoError);
dnsError = DNSServiceRegisterRecord(GetPublisher().mHostsRef, &recordRef, kDNSServiceFlagsShared,
kDNSServiceInterfaceIndexAny, MakeFullHostName(mName).c_str(),
kDNSServiceType_AAAA, kDNSServiceClass_IN, sizeof(address.m8), address.m8,
/* ttl */ 0, HandleRegisterResult, this);
VerifyOrExit(dnsError == kDNSServiceErr_NoError);
mAddrRecordRefs.push_back(recordRef);
mAddrRegistered.push_back(false);
}
exit:
if ((dnsError != kDNSServiceErr_NoError) || mAddresses.empty())
{
HandleRegisterResult(/* aRecordRef */ nullptr, dnsError);
}
return GetPublisher().DnsErrorToOtbrError(dnsError);
}
void PublisherMDnsSd::DnssdHostRegistration::Unregister(void)
{
DNSServiceErrorType dnsError;
VerifyOrExit(GetPublisher().IsStarted());
VerifyOrExit(GetPublisher().mHostsRef != nullptr);
for (size_t index = 0; index < mAddrRecordRefs.size(); index++)
{
const Ip6Address &address = mAddresses[index];
if (mAddrRegistered[index])
{
// The Bonjour mDNSResponder somehow doesn't send goodbye message for the AAAA record when it is
// removed by `DNSServiceRemoveRecord`. Per RFC 6762, a goodbye message of a record sets its TTL
// to zero but the receiver should record the TTL of 1 and flushes the cache 1 second later. Here
// we remove the AAAA record after updating its TTL to 1 second. This has the same effect as
// sending a goodbye message.
// TODO: resolve the goodbye issue with Bonjour mDNSResponder.
dnsError = DNSServiceUpdateRecord(GetPublisher().mHostsRef, mAddrRecordRefs[index], kDNSServiceFlagsUnique,
sizeof(address.m8), address.m8, /* ttl */ 1);
otbrLogResult(DNSErrorToOtbrError(dnsError), "Send goodbye message for host %s address %s: %s",
MakeFullHostName(mName).c_str(), address.ToString().c_str(), DNSErrorToString(dnsError));
}
dnsError = DNSServiceRemoveRecord(GetPublisher().mHostsRef, mAddrRecordRefs[index], /* flags */ 0);
otbrLogResult(DNSErrorToOtbrError(dnsError), "Remove record for host %s address %s: %s",
MakeFullHostName(mName).c_str(), address.ToString().c_str(), DNSErrorToString(dnsError));
}
exit:
mAddrRegistered.clear();
mAddrRecordRefs.clear();
}
void PublisherMDnsSd::DnssdHostRegistration::HandleRegisterResult(DNSServiceRef aServiceRef,
DNSRecordRef aRecordRef,
DNSServiceFlags aFlags,
DNSServiceErrorType aError,
void *aContext)
{
OTBR_UNUSED_VARIABLE(aServiceRef);
OTBR_UNUSED_VARIABLE(aFlags);
static_cast<DnssdHostRegistration *>(aContext)->HandleRegisterResult(aRecordRef, aError);
}
void PublisherMDnsSd::DnssdHostRegistration::HandleRegisterResult(DNSRecordRef aRecordRef, DNSServiceErrorType aError)
{
if (aError != kDNSServiceErr_NoError)
{
otbrLogErr("Failed to register host %s: %s", mName.c_str(), DNSErrorToString(aError));
GetPublisher().RemoveHostRegistration(mName, DNSErrorToOtbrError(aError));
}
else
{
bool shouldComplete = !IsCompleted();
for (size_t index = 0; index < mAddrRecordRefs.size(); index++)
{
if ((mAddrRecordRefs[index] == aRecordRef) && !mAddrRegistered[index])
{
mAddrRegistered[index] = true;
otbrLogInfo("Successfully registered host %s address %s", mName.c_str(),
mAddresses[index].ToString().c_str());
}
if (!mAddrRegistered[index])
{
shouldComplete = false;
}
}
if (shouldComplete)
{
otbrLogInfo("Successfully registered all host %s addresses", mName.c_str());
Complete(OTBR_ERROR_NONE);
}
}
}
otbrError PublisherMDnsSd::DnssdKeyRegistration::Register(void)
{
DNSServiceErrorType dnsError = kDNSServiceErr_NoError;
DnssdServiceRegistration *serviceReg;
otbrLogInfo("Registering new key %s", mName.c_str());
VerifyOrExit(GetPublisher().IsStarted(), dnsError = kDNSServiceErr_ServiceNotRunning);
serviceReg = static_cast<DnssdServiceRegistration *>(GetPublisher().FindServiceRegistration(mName));
if ((serviceReg != nullptr) && (serviceReg->mServiceRef != nullptr))
{
otbrLogInfo("Key %s is being registered as a record of an existing service registration", mName.c_str());
dnsError = DNSServiceAddRecord(serviceReg->mServiceRef, &mRecordRef, kDNSServiceFlagsUnique,
kDNSServiceType_KEY, mKeyData.size(), mKeyData.data(), /* ttl */ 0);
VerifyOrExit(dnsError == kDNSServiceErr_NoError);
mRelatedServiceReg = serviceReg;
serviceReg->mRelatedKeyReg = this;
if (mRelatedServiceReg->IsCompleted())
{
HandleRegisterResult(kDNSServiceErr_NoError);
}
// If related service registration is not yet finished,
// we wait for service registration completion to signal
// key record registration as well.
}
else
{
otbrLogInfo("Key %s is being registered individually", mName.c_str());
dnsError = GetPublisher().CreateSharedHostsRef();
VerifyOrExit(dnsError == kDNSServiceErr_NoError);
dnsError = DNSServiceRegisterRecord(GetPublisher().mHostsRef, &mRecordRef, kDNSServiceFlagsUnique,
kDNSServiceInterfaceIndexAny, MakeFullKeyName(mName).c_str(),
kDNSServiceType_KEY, kDNSServiceClass_IN, mKeyData.size(), mKeyData.data(),
/* ttl */ 0, HandleRegisterResult, this);
VerifyOrExit(dnsError == kDNSServiceErr_NoError);
}
exit:
if (dnsError != kDNSServiceErr_NoError)
{
HandleRegisterResult(dnsError);
}
return GetPublisher().DnsErrorToOtbrError(dnsError);
}
void PublisherMDnsSd::DnssdKeyRegistration::Unregister(void)
{
DNSServiceErrorType dnsError;
DNSServiceRef serviceRef;
VerifyOrExit(mRecordRef != nullptr);
if (mRelatedServiceReg != nullptr)
{
serviceRef = mRelatedServiceReg->mServiceRef;
mRelatedServiceReg->mRelatedKeyReg = nullptr;
mRelatedServiceReg = nullptr;
otbrLogInfo("Unregistering key %s (was registered as a record of a service)", mName.c_str());
}
else
{
serviceRef = GetPublisher().mHostsRef;
otbrLogInfo("Unregistering key %s (was registered individually)", mName.c_str());
}
VerifyOrExit(GetPublisher().IsStarted(), dnsError = kDNSServiceErr_ServiceNotRunning);
VerifyOrExit(serviceRef != nullptr);
dnsError = DNSServiceRemoveRecord(serviceRef, mRecordRef, /* flags */ 0);
otbrLogInfo("Unregistered key %s: error:%s", mName.c_str(), DNSErrorToString(dnsError));
exit:
return;
}
void PublisherMDnsSd::DnssdKeyRegistration::HandleRegisterResult(DNSServiceRef aServiceRef,
DNSRecordRef aRecordRef,
DNSServiceFlags aFlags,
DNSServiceErrorType aError,
void *aContext)
{
OTBR_UNUSED_VARIABLE(aServiceRef);
OTBR_UNUSED_VARIABLE(aRecordRef);
OTBR_UNUSED_VARIABLE(aFlags);
static_cast<DnssdKeyRegistration *>(aContext)->HandleRegisterResult(aError);
}
void PublisherMDnsSd::DnssdKeyRegistration::HandleRegisterResult(DNSServiceErrorType aError)
{
if (aError != kDNSServiceErr_NoError)
{
otbrLogErr("Failed to register key %s: %s", mName.c_str(), DNSErrorToString(aError));
GetPublisher().RemoveKeyRegistration(mName, DNSErrorToOtbrError(aError));
}
else
{
otbrLogInfo("Successfully registered key %s", mName.c_str());
Complete(OTBR_ERROR_NONE);
}
}
otbrError PublisherMDnsSd::PublishServiceImpl(const std::string &aHostName,
const std::string &aName,
const std::string &aType,
const SubTypeList &aSubTypeList,
uint16_t aPort,
const TxtData &aTxtData,
ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
SubTypeList sortedSubTypeList = SortSubTypeList(aSubTypeList);
std::string regType = MakeRegType(aType, sortedSubTypeList);
DnssdServiceRegistration *serviceReg;
if (mState != State::kReady)
{
error = OTBR_ERROR_INVALID_STATE;
std::move(aCallback)(error);
ExitNow();
}
aCallback = HandleDuplicateServiceRegistration(aHostName, aName, aType, sortedSubTypeList, aPort, aTxtData,
std::move(aCallback));
VerifyOrExit(!aCallback.IsNull());
serviceReg = new DnssdServiceRegistration(aHostName, aName, aType, sortedSubTypeList, aPort, aTxtData,
std::move(aCallback), this);
AddServiceRegistration(std::unique_ptr<DnssdServiceRegistration>(serviceReg));
error = serviceReg->Register();
exit:
return error;
}
void PublisherMDnsSd::UnpublishService(const std::string &aName, const std::string &aType, ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
VerifyOrExit(mState == Publisher::State::kReady, error = OTBR_ERROR_INVALID_STATE);
RemoveServiceRegistration(aName, aType, OTBR_ERROR_ABORTED);
exit:
std::move(aCallback)(error);
}
otbrError PublisherMDnsSd::PublishHostImpl(const std::string &aName,
const AddressList &aAddresses,
ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
DnssdHostRegistration *hostReg;
if (mState != State::kReady)
{
error = OTBR_ERROR_INVALID_STATE;
std::move(aCallback)(error);
ExitNow();
}
aCallback = HandleDuplicateHostRegistration(aName, aAddresses, std::move(aCallback));
VerifyOrExit(!aCallback.IsNull());
hostReg = new DnssdHostRegistration(aName, aAddresses, std::move(aCallback), this);
AddHostRegistration(std::unique_ptr<DnssdHostRegistration>(hostReg));
error = hostReg->Register();
exit:
return error;
}
void PublisherMDnsSd::UnpublishHost(const std::string &aName, ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
VerifyOrExit(mState == Publisher::State::kReady, error = OTBR_ERROR_INVALID_STATE);
RemoveHostRegistration(aName, OTBR_ERROR_ABORTED);
exit:
// We may failed to unregister the host from underlying mDNS publishers, but
// it usually means that the mDNS publisher is already not functioning. So it's
// okay to return success directly since the service is not advertised anyway.
std::move(aCallback)(error);
}
otbrError PublisherMDnsSd::PublishKeyImpl(const std::string &aName, const KeyData &aKeyData, ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
DnssdKeyRegistration *keyReg;
if (mState != State::kReady)
{
error = OTBR_ERROR_INVALID_STATE;
std::move(aCallback)(error);
ExitNow();
}
aCallback = HandleDuplicateKeyRegistration(aName, aKeyData, std::move(aCallback));
VerifyOrExit(!aCallback.IsNull());
keyReg = new DnssdKeyRegistration(aName, aKeyData, std::move(aCallback), this);
AddKeyRegistration(std::unique_ptr<DnssdKeyRegistration>(keyReg));
error = keyReg->Register();
exit:
return error;
}
void PublisherMDnsSd::UnpublishKey(const std::string &aName, ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
VerifyOrExit(mState == Publisher::State::kReady, error = OTBR_ERROR_INVALID_STATE);
RemoveKeyRegistration(aName, OTBR_ERROR_ABORTED);
exit:
std::move(aCallback)(error);
}
// See `regtype` parameter of the DNSServiceRegister() function for more information.
std::string PublisherMDnsSd::MakeRegType(const std::string &aType, SubTypeList aSubTypeList)
{
std::string regType = aType;
std::sort(aSubTypeList.begin(), aSubTypeList.end());
for (const auto &subType : aSubTypeList)
{
regType += "," + subType;
}
return regType;
}
void PublisherMDnsSd::SubscribeService(const std::string &aType, const std::string &aInstanceName)
{
VerifyOrExit(mState == Publisher::State::kReady);
mSubscribedServices.push_back(MakeUnique<ServiceSubscription>(*this, aType, aInstanceName));
otbrLogInfo("Subscribe service %s.%s (total %zu)", aInstanceName.c_str(), aType.c_str(),
mSubscribedServices.size());
if (aInstanceName.empty())
{
mSubscribedServices.back()->Browse();
}
else
{
mSubscribedServices.back()->Resolve(kDNSServiceInterfaceIndexAny, aInstanceName, aType, kDomain);
}
exit:
return;
}
void PublisherMDnsSd::UnsubscribeService(const std::string &aType, const std::string &aInstanceName)
{
ServiceSubscriptionList::iterator it;
VerifyOrExit(mState == Publisher::State::kReady);
it = std::find_if(mSubscribedServices.begin(), mSubscribedServices.end(),
[&aType, &aInstanceName](const std::unique_ptr<ServiceSubscription> &aService) {
return aService->mType == aType && aService->mInstanceName == aInstanceName;
});
VerifyOrExit(it != mSubscribedServices.end());
mSubscribedServices.erase(it);
otbrLogInfo("Unsubscribe service %s.%s (left %zu)", aInstanceName.c_str(), aType.c_str(),
mSubscribedServices.size());
exit:
return;
}
void PublisherMDnsSd::OnServiceResolveFailedImpl(const std::string &aType,
const std::string &aInstanceName,
int32_t aErrorCode)
{
otbrLogWarning("Resolve service %s.%s failed: code=%" PRId32, aInstanceName.c_str(), aType.c_str(), aErrorCode);
}
void PublisherMDnsSd::OnHostResolveFailedImpl(const std::string &aHostName, int32_t aErrorCode)
{
otbrLogWarning("Resolve host %s failed: code=%" PRId32, aHostName.c_str(), aErrorCode);
}
otbrError PublisherMDnsSd::DnsErrorToOtbrError(int32_t aErrorCode)
{
return otbr::Mdns::DNSErrorToOtbrError(aErrorCode);
}
void PublisherMDnsSd::SubscribeHost(const std::string &aHostName)
{
VerifyOrExit(mState == State::kReady);
mSubscribedHosts.push_back(MakeUnique<HostSubscription>(*this, aHostName));
otbrLogInfo("Subscribe host %s (total %zu)", aHostName.c_str(), mSubscribedHosts.size());
mSubscribedHosts.back()->Resolve();
exit:
return;
}
void PublisherMDnsSd::UnsubscribeHost(const std::string &aHostName)
{
HostSubscriptionList ::iterator it;
VerifyOrExit(mState == Publisher::State::kReady);
it = std::find_if(
mSubscribedHosts.begin(), mSubscribedHosts.end(),
[&aHostName](const std::unique_ptr<HostSubscription> &aHost) { return aHost->mHostName == aHostName; });
VerifyOrExit(it != mSubscribedHosts.end());
mSubscribedHosts.erase(it);
otbrLogInfo("Unsubscribe host %s (remaining %d)", aHostName.c_str(), mSubscribedHosts.size());
exit:
return;
}
Publisher *Publisher::Create(StateCallback aCallback)
{
return new PublisherMDnsSd(aCallback);
}
void Publisher::Destroy(Publisher *aPublisher)
{
delete static_cast<PublisherMDnsSd *>(aPublisher);
}
void PublisherMDnsSd::ServiceRef::Release(void)
{
DeallocateServiceRef();
}
void PublisherMDnsSd::ServiceRef::DeallocateServiceRef(void)
{
if (mServiceRef != nullptr)
{
mPublisher.HandleServiceRefDeallocating(mServiceRef);
DNSServiceRefDeallocate(mServiceRef);
mServiceRef = nullptr;
}
}
void PublisherMDnsSd::ServiceRef::Update(MainloopContext &aMainloop) const
{
int fd;
VerifyOrExit(mServiceRef != nullptr);
fd = DNSServiceRefSockFD(mServiceRef);
assert(fd != -1);
aMainloop.AddFdToReadSet(fd);
exit:
return;
}
void PublisherMDnsSd::ServiceRef::Process(const MainloopContext &aMainloop,
std::vector<DNSServiceRef> &aReadyServices) const
{
int fd;
VerifyOrExit(mServiceRef != nullptr);
fd = DNSServiceRefSockFD(mServiceRef);
assert(fd != -1);
if (FD_ISSET(fd, &aMainloop.mReadFdSet))
{
aReadyServices.push_back(mServiceRef);
}
exit:
return;
}
void PublisherMDnsSd::ServiceSubscription::Browse(void)
{
assert(mServiceRef == nullptr);
otbrLogInfo("DNSServiceBrowse %s", mType.c_str());
DNSServiceBrowse(&mServiceRef, /* flags */ 0, kDNSServiceInterfaceIndexAny, mType.c_str(),
/* domain */ nullptr, HandleBrowseResult, this);
}
void PublisherMDnsSd::ServiceSubscription::HandleBrowseResult(DNSServiceRef aServiceRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char *aInstanceName,
const char *aType,
const char *aDomain,
void *aContext)
{
static_cast<ServiceSubscription *>(aContext)->HandleBrowseResult(aServiceRef, aFlags, aInterfaceIndex, aErrorCode,
aInstanceName, aType, aDomain);
}
void PublisherMDnsSd::ServiceSubscription::HandleBrowseResult(DNSServiceRef aServiceRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char *aInstanceName,
const char *aType,
const char *aDomain)
{
OTBR_UNUSED_VARIABLE(aServiceRef);
OTBR_UNUSED_VARIABLE(aDomain);
otbrLogInfo("DNSServiceBrowse reply: %s %s.%s inf %" PRIu32 ", flags=%" PRIu32 ", error=%" PRId32,
aFlags & kDNSServiceFlagsAdd ? "add" : "remove", aInstanceName, aType, aInterfaceIndex, aFlags,
aErrorCode);
VerifyOrExit(aErrorCode == kDNSServiceErr_NoError);
if (aFlags & kDNSServiceFlagsAdd)
{
Resolve(aInterfaceIndex, aInstanceName, aType, aDomain);
}
else
{
mPublisher.OnServiceRemoved(aInterfaceIndex, mType, aInstanceName);
}
exit:
if (aErrorCode != kDNSServiceErr_NoError)
{
mPublisher.OnServiceResolveFailed(mType, mInstanceName, aErrorCode);
Release();
}
}
void PublisherMDnsSd::ServiceSubscription::Resolve(uint32_t aInterfaceIndex,
const std::string &aInstanceName,
const std::string &aType,
const std::string &aDomain)
{
mResolvingInstances.push_back(
MakeUnique<ServiceInstanceResolution>(*this, aInstanceName, aType, aDomain, aInterfaceIndex));
mResolvingInstances.back()->Resolve();
}
void PublisherMDnsSd::ServiceSubscription::UpdateAll(MainloopContext &aMainloop) const
{
Update(aMainloop);
for (const auto &instance : mResolvingInstances)
{
instance->Update(aMainloop);
}
}
void PublisherMDnsSd::ServiceSubscription::ProcessAll(const MainloopContext &aMainloop,
std::vector<DNSServiceRef> &aReadyServices) const
{
Process(aMainloop, aReadyServices);
for (const auto &instance : mResolvingInstances)
{
instance->Process(aMainloop, aReadyServices);
}
}
void PublisherMDnsSd::ServiceInstanceResolution::Resolve(void)
{
assert(mServiceRef == nullptr);
mSubscription->mPublisher.mServiceInstanceResolutionBeginTime[std::make_pair(mInstanceName, mType)] = Clock::now();
otbrLogInfo("DNSServiceResolve %s %s inf %u", mInstanceName.c_str(), mType.c_str(), mNetifIndex);
DNSServiceResolve(&mServiceRef, /* flags */ kDNSServiceFlagsTimeout, mNetifIndex, mInstanceName.c_str(),
mType.c_str(), mDomain.c_str(), HandleResolveResult, this);
}
void PublisherMDnsSd::ServiceInstanceResolution::HandleResolveResult(DNSServiceRef aServiceRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char *aFullName,
const char *aHostTarget,
uint16_t aPort,
uint16_t aTxtLen,
const unsigned char *aTxtRecord,
void *aContext)
{
static_cast<ServiceInstanceResolution *>(aContext)->HandleResolveResult(
aServiceRef, aFlags, aInterfaceIndex, aErrorCode, aFullName, aHostTarget, aPort, aTxtLen, aTxtRecord);
}
void PublisherMDnsSd::ServiceInstanceResolution::HandleResolveResult(DNSServiceRef aServiceRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char *aFullName,
const char *aHostTarget,
uint16_t aPort,
uint16_t aTxtLen,
const unsigned char *aTxtRecord)
{
OTBR_UNUSED_VARIABLE(aServiceRef);
std::string instanceName, type, domain;
otbrError error = OTBR_ERROR_NONE;
otbrLogInfo("DNSServiceResolve reply: %s host %s:%d, TXT=%dB inf %u, flags=%u", aFullName, aHostTarget, aPort,
aTxtLen, aInterfaceIndex, aFlags);
VerifyOrExit(aErrorCode == kDNSServiceErr_NoError);
SuccessOrExit(error = SplitFullServiceInstanceName(aFullName, instanceName, type, domain));
mInstanceInfo.mNetifIndex = aInterfaceIndex;
mInstanceInfo.mName = instanceName;
mInstanceInfo.mHostName = aHostTarget;
mInstanceInfo.mPort = ntohs(aPort);
mInstanceInfo.mTxtData.assign(aTxtRecord, aTxtRecord + aTxtLen);
// priority and weight are not given in the reply
mInstanceInfo.mPriority = 0;
mInstanceInfo.mWeight = 0;
DeallocateServiceRef();
error = GetAddrInfo(aInterfaceIndex);
exit:
if (error != OTBR_ERROR_NONE)
{
otbrLogWarning("Failed to resolve service instance %s", aFullName);
}
if (aErrorCode != kDNSServiceErr_NoError || error != OTBR_ERROR_NONE)
{
mSubscription->mPublisher.OnServiceResolveFailed(mSubscription->mType, mInstanceName, aErrorCode);
FinishResolution();
}
}
otbrError PublisherMDnsSd::ServiceInstanceResolution::GetAddrInfo(uint32_t aInterfaceIndex)
{
DNSServiceErrorType dnsError;
assert(mServiceRef == nullptr);
otbrLogInfo("DNSServiceGetAddrInfo %s inf %d", mInstanceInfo.mHostName.c_str(), aInterfaceIndex);
dnsError = DNSServiceGetAddrInfo(&mServiceRef, /* flags */ 0, aInterfaceIndex,
kDNSServiceProtocol_IPv6 | kDNSServiceProtocol_IPv4,
mInstanceInfo.mHostName.c_str(), HandleGetAddrInfoResult, this);
if (dnsError != kDNSServiceErr_NoError)
{
otbrLogWarning("DNSServiceGetAddrInfo failed: %s", DNSErrorToString(dnsError));
}
return dnsError == kDNSServiceErr_NoError ? OTBR_ERROR_NONE : OTBR_ERROR_MDNS;
}
void PublisherMDnsSd::ServiceInstanceResolution::HandleGetAddrInfoResult(DNSServiceRef aServiceRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char *aHostName,
const struct sockaddr *aAddress,
uint32_t aTtl,
void *aContext)
{
static_cast<ServiceInstanceResolution *>(aContext)->HandleGetAddrInfoResult(aServiceRef, aFlags, aInterfaceIndex,
aErrorCode, aHostName, aAddress, aTtl);
}
void PublisherMDnsSd::ServiceInstanceResolution::HandleGetAddrInfoResult(DNSServiceRef aServiceRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char *aHostName,
const struct sockaddr *aAddress,
uint32_t aTtl)
{
OTBR_UNUSED_VARIABLE(aServiceRef);
OTBR_UNUSED_VARIABLE(aInterfaceIndex);
Ip6Address address;
bool isAdd = (aFlags & kDNSServiceFlagsAdd) != 0;
bool moreComing = (aFlags & kDNSServiceFlagsMoreComing) != 0;
otbrLog(aErrorCode == kDNSServiceErr_NoError ? OTBR_LOG_INFO : OTBR_LOG_WARNING, OTBR_LOG_TAG,
"DNSServiceGetAddrInfo reply: flags=%" PRIu32 ", host=%s, sa_family=%u, error=%" PRId32, aFlags, aHostName,
static_cast<unsigned int>(aAddress->sa_family), aErrorCode);
VerifyOrExit(aErrorCode == kDNSServiceErr_NoError);
VerifyOrExit(aAddress->sa_family == AF_INET6);
address.CopyFrom(*reinterpret_cast<const struct sockaddr_in6 *>(aAddress));
VerifyOrExit(!address.IsUnspecified() && !address.IsMulticast() && !address.IsLoopback(),
otbrLogDebug("DNSServiceGetAddrInfo ignores address %s", address.ToString().c_str()));
otbrLogInfo("DNSServiceGetAddrInfo reply: %s address=%s, ttl=%" PRIu32, isAdd ? "add" : "remove",
address.ToString().c_str(), aTtl);
if (isAdd)
{
mInstanceInfo.AddAddress(address);
}
else
{
mInstanceInfo.RemoveAddress(address);
}
mInstanceInfo.mTtl = aTtl;
exit:
if ((!mInstanceInfo.mAddresses.empty() && !moreComing) || aErrorCode != kDNSServiceErr_NoError)
{
FinishResolution();
}
}
void PublisherMDnsSd::ServiceInstanceResolution::FinishResolution(void)
{
ServiceSubscription *subscription = mSubscription;
std::string serviceName = mSubscription->mType;
DiscoveredInstanceInfo instanceInfo = mInstanceInfo;
// NOTE: The `ServiceSubscription` object may be freed in `OnServiceResolved`.
subscription->mPublisher.OnServiceResolved(serviceName, instanceInfo);
}
void PublisherMDnsSd::HostSubscription::Resolve(void)
{
std::string fullHostName = MakeFullHostName(mHostName);
assert(mServiceRef == nullptr);
mPublisher.mHostResolutionBeginTime[mHostName] = Clock::now();
otbrLogInfo("DNSServiceGetAddrInfo %s inf %d", fullHostName.c_str(), kDNSServiceInterfaceIndexAny);
DNSServiceGetAddrInfo(&mServiceRef, /* flags */ 0, kDNSServiceInterfaceIndexAny,
kDNSServiceProtocol_IPv6 | kDNSServiceProtocol_IPv4, fullHostName.c_str(),
HandleResolveResult, this);
}
void PublisherMDnsSd::HostSubscription::HandleResolveResult(DNSServiceRef aServiceRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char *aHostName,
const struct sockaddr *aAddress,
uint32_t aTtl,
void *aContext)
{
static_cast<HostSubscription *>(aContext)->HandleResolveResult(aServiceRef, aFlags, aInterfaceIndex, aErrorCode,
aHostName, aAddress, aTtl);
}
void PublisherMDnsSd::HostSubscription::HandleResolveResult(DNSServiceRef aServiceRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char *aHostName,
const struct sockaddr *aAddress,
uint32_t aTtl)
{
OTBR_UNUSED_VARIABLE(aServiceRef);
Ip6Address address;
bool isAdd = (aFlags & kDNSServiceFlagsAdd) != 0;
bool moreComing = (aFlags & kDNSServiceFlagsMoreComing) != 0;
otbrLog(aErrorCode == kDNSServiceErr_NoError ? OTBR_LOG_INFO : OTBR_LOG_WARNING, OTBR_LOG_TAG,
"DNSServiceGetAddrInfo reply: flags=%" PRIu32 ", host=%s, sa_family=%u, error=%" PRId32, aFlags, aHostName,
static_cast<unsigned int>(aAddress->sa_family), aErrorCode);
VerifyOrExit(aErrorCode == kDNSServiceErr_NoError);
VerifyOrExit(aAddress->sa_family == AF_INET6);
address.CopyFrom(*reinterpret_cast<const struct sockaddr_in6 *>(aAddress));
otbrLogInfo("DNSServiceGetAddrInfo reply: %s address=%s, ttl=%" PRIu32, isAdd ? "add" : "remove",
address.ToString().c_str(), aTtl);
if (isAdd)
{
mHostInfo.AddAddress(address);
}
else
{
mHostInfo.RemoveAddress(address);
}
mHostInfo.mHostName = aHostName;
mHostInfo.mNetifIndex = aInterfaceIndex;
mHostInfo.mTtl = aTtl;
exit:
if (aErrorCode != kDNSServiceErr_NoError)
{
mPublisher.OnHostResolveFailed(aHostName, aErrorCode);
}
else if (!moreComing && !mHostInfo.mAddresses.empty())
{
mPublisher.OnHostResolved(mHostName, mHostInfo);
}
}
} // namespace Mdns
} // namespace otbr