blob: 931631307b3cd07d2ff4d909dfd19d70661899aa [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "DnssdImpl.h"
#include "MdnsError.h"
#include <cstdio>
#include <sstream>
#include <string.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CHIPMemString.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/CHIPDeviceLayer.h>
using namespace chip::Dnssd;
namespace {
constexpr const char * kLocalDot = "local.";
constexpr const char * kProtocolTcp = "._tcp";
constexpr const char * kProtocolUdp = "._udp";
constexpr uint8_t kDnssdKeyMaxSize = 32;
bool IsSupportedProtocol(DnssdServiceProtocol protocol)
{
return (protocol == DnssdServiceProtocol::kDnssdProtocolUdp) || (protocol == DnssdServiceProtocol::kDnssdProtocolTcp);
}
uint32_t GetInterfaceId(chip::Inet::InterfaceId interfaceId)
{
return interfaceId.IsPresent() ? interfaceId.GetPlatformInterface() : kDNSServiceInterfaceIndexAny;
}
std::string GetFullType(const char * type, DnssdServiceProtocol protocol)
{
std::ostringstream typeBuilder;
typeBuilder << type;
typeBuilder << (protocol == DnssdServiceProtocol::kDnssdProtocolUdp ? kProtocolUdp : kProtocolTcp);
return typeBuilder.str();
}
std::string GetFullTypeWithSubTypes(const char * type, DnssdServiceProtocol protocol, const char * subTypes[], size_t subTypeSize)
{
std::ostringstream typeBuilder;
typeBuilder << type;
typeBuilder << (protocol == DnssdServiceProtocol::kDnssdProtocolUdp ? kProtocolUdp : kProtocolTcp);
for (int i = 0; i < (int) subTypeSize; i++)
{
typeBuilder << ",";
typeBuilder << subTypes[i];
}
return typeBuilder.str();
}
} // namespace
namespace chip {
namespace Dnssd {
MdnsContexts MdnsContexts::sInstance;
void MdnsContexts::Delete(GenericContext * context)
{
if (context->type == ContextType::GetAddrInfo)
{
GetAddrInfoContext * addrInfoContext = reinterpret_cast<GetAddrInfoContext *>(context);
std::vector<TextEntry>::iterator textEntry;
for (textEntry = addrInfoContext->textEntries.begin(); textEntry != addrInfoContext->textEntries.end(); textEntry++)
{
free(const_cast<char *>(textEntry->mKey));
free(const_cast<uint8_t *>(textEntry->mData));
}
}
if (context->serviceRef != nullptr)
{
DNSServiceRefDeallocate(context->serviceRef);
}
chip::Platform::Delete(context);
}
MdnsContexts::~MdnsContexts()
{
std::vector<GenericContext *>::const_iterator iter = mContexts.cbegin();
while (iter != mContexts.cend())
{
Delete(*iter);
mContexts.erase(iter);
}
}
CHIP_ERROR MdnsContexts::Add(GenericContext * context, DNSServiceRef sdRef)
{
VerifyOrReturnError(context != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(sdRef != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
context->serviceRef = sdRef;
mContexts.push_back(context);
return CHIP_NO_ERROR;
}
CHIP_ERROR MdnsContexts::Remove(GenericContext * context)
{
bool found = false;
std::vector<GenericContext *>::const_iterator iter = mContexts.cbegin();
while (iter != mContexts.cend())
{
if (*iter != context)
{
iter++;
continue;
}
Delete(*iter);
mContexts.erase(iter);
found = true;
break;
}
return found ? CHIP_NO_ERROR : CHIP_ERROR_KEY_NOT_FOUND;
}
CHIP_ERROR MdnsContexts::Removes(ContextType type)
{
bool found = false;
std::vector<GenericContext *>::const_iterator iter = mContexts.cbegin();
while (iter != mContexts.cend())
{
if ((*iter)->type != type)
{
iter++;
continue;
}
Delete(*iter);
mContexts.erase(iter);
found = true;
}
return found ? CHIP_NO_ERROR : CHIP_ERROR_KEY_NOT_FOUND;
}
CHIP_ERROR MdnsContexts::Get(ContextType type, GenericContext ** context)
{
bool found = false;
std::vector<GenericContext *>::iterator iter;
for (iter = mContexts.begin(); iter != mContexts.end(); iter++)
{
if ((*iter)->type == type)
{
*context = *iter;
found = true;
break;
}
}
return found ? CHIP_NO_ERROR : CHIP_ERROR_KEY_NOT_FOUND;
}
CHIP_ERROR MdnsContexts::GetRegisterType(const char * type, GenericContext ** context)
{
bool found = false;
std::vector<GenericContext *>::iterator iter;
for (iter = mContexts.begin(); iter != mContexts.end(); iter++)
{
if ((*iter)->type == ContextType::Register && ((RegisterContext *) (*iter))->matches(type))
{
*context = *iter;
found = true;
break;
}
}
return found ? CHIP_NO_ERROR : CHIP_ERROR_KEY_NOT_FOUND;
}
void MdnsContexts::PrepareSelect(fd_set & readFdSet, fd_set & writeFdSet, fd_set & errorFdSet, int & maxFd, timeval & timeout) {}
void MdnsContexts::HandleSelectResult(fd_set & readFdSet, fd_set & writeFdSet, fd_set & errorFdSet) {}
CHIP_ERROR PopulateTextRecord(TXTRecordRef * record, char * buffer, uint16_t bufferLen, TextEntry * textEntries,
size_t textEntrySize)
{
VerifyOrReturnError(textEntrySize <= kDnssdTextMaxSize, CHIP_ERROR_INVALID_ARGUMENT);
DNSServiceErrorType err;
TXTRecordCreate(record, bufferLen, buffer);
for (size_t i = 0; i < textEntrySize; i++)
{
TextEntry entry = textEntries[i];
VerifyOrReturnError(chip::CanCastTo<uint8_t>(entry.mDataSize), CHIP_ERROR_INVALID_ARGUMENT);
err = TXTRecordSetValue(record, entry.mKey, static_cast<uint8_t>(entry.mDataSize), entry.mData);
VerifyOrReturnError(err == kDNSServiceErr_NoError, CHIP_ERROR_INVALID_ARGUMENT);
}
return CHIP_NO_ERROR;
}
bool CheckForSuccess(GenericContext * context, const char * name, DNSServiceErrorType err, bool useCallback = false)
{
if (context == nullptr)
{
ChipLogError(DeviceLayer, "%s (%s)", name, "Mdns context is null.");
return false;
}
if (kDNSServiceErr_NoError != err)
{
ChipLogError(DeviceLayer, "%s (%s)", name, Error::ToString(err));
if (useCallback)
{
switch (context->type)
{
case ContextType::Register:
// Nothing special to do. Maybe ChipDnssdPublishService should take a callback ?
break;
case ContextType::Browse: {
BrowseContext * browseContext = reinterpret_cast<BrowseContext *>(context);
browseContext->callback(browseContext->context, nullptr, 0, CHIP_ERROR_INTERNAL);
break;
}
case ContextType::Resolve: {
ResolveContext * resolveContext = reinterpret_cast<ResolveContext *>(context);
resolveContext->callback(resolveContext->context, nullptr, CHIP_ERROR_INTERNAL);
break;
}
case ContextType::GetAddrInfo: {
GetAddrInfoContext * resolveContext = reinterpret_cast<GetAddrInfoContext *>(context);
resolveContext->callback(resolveContext->context, nullptr, CHIP_ERROR_INTERNAL);
break;
}
}
}
if (CHIP_ERROR_KEY_NOT_FOUND == MdnsContexts::GetInstance().Remove(context))
{
chip::Platform::Delete(context);
}
return false;
}
return true;
}
static void OnRegister(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType err, const char * name, const char * type,
const char * domain, void * context)
{
RegisterContext * sdCtx = reinterpret_cast<RegisterContext *>(context);
VerifyOrReturn(CheckForSuccess(sdCtx, __func__, err));
ChipLogDetail(DeviceLayer, "Mdns: %s name: %s, type: %s, domain: %s, flags: %d", __func__, name, type, domain, flags);
};
CHIP_ERROR Register(uint32_t interfaceId, const char * type, const char * name, uint16_t port, TXTRecordRef * recordRef)
{
DNSServiceErrorType err;
DNSServiceRef sdRef;
GenericContext * sdCtx = nullptr;
uint16_t recordLen = TXTRecordGetLength(recordRef);
const void * recordBytesPtr = TXTRecordGetBytesPtr(recordRef);
if (CHIP_NO_ERROR == MdnsContexts::GetInstance().GetRegisterType(type, &sdCtx))
{
err = DNSServiceUpdateRecord(sdCtx->serviceRef, NULL, 0 /* flags */, recordLen, recordBytesPtr, 0 /* ttl */);
TXTRecordDeallocate(recordRef);
VerifyOrReturnError(CheckForSuccess(sdCtx, __func__, err), CHIP_ERROR_INTERNAL);
return CHIP_NO_ERROR;
}
sdCtx = chip::Platform::New<RegisterContext>(type, nullptr);
err = DNSServiceRegister(&sdRef, 0 /* flags */, interfaceId, name, type, kLocalDot, NULL, ntohs(port), recordLen,
recordBytesPtr, OnRegister, sdCtx);
TXTRecordDeallocate(recordRef);
VerifyOrReturnError(CheckForSuccess(sdCtx, __func__, err), CHIP_ERROR_INTERNAL);
err = DNSServiceSetDispatchQueue(sdRef, chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue());
VerifyOrReturnError(CheckForSuccess(sdCtx, __func__, err, true), CHIP_ERROR_INTERNAL);
return MdnsContexts::GetInstance().Add(sdCtx, sdRef);
}
void OnBrowseAdd(BrowseContext * context, const char * name, const char * type, const char * domain,
chip::Inet::InterfaceId interfaceId)
{
ChipLogDetail(DeviceLayer, "Mdns: %s name: %s, type: %s, domain: %s, interface: %d", __func__, name, type, domain,
interfaceId.GetPlatformInterface());
VerifyOrReturn(strcmp(kLocalDot, domain) == 0);
DnssdService service = {};
service.mInterface = interfaceId;
service.mProtocol = context->protocol;
Platform::CopyString(service.mName, name);
Platform::CopyString(service.mType, type);
// only the first token after '.' should be included in the type
for (char * p = service.mType; *p != '\0'; p++)
{
if (*p == '.')
{
*p = '\0';
break;
}
}
context->services.push_back(service);
}
void OnBrowseRemove(BrowseContext * context, const char * name, const char * type, const char * domain,
chip::Inet::InterfaceId interfaceId)
{
ChipLogDetail(DeviceLayer, "Mdns: %s name: %s, type: %s, domain: %s, interface: %d", __func__, name, type, domain,
interfaceId.GetPlatformInterface());
VerifyOrReturn(strcmp(kLocalDot, domain) == 0);
std::remove_if(context->services.begin(), context->services.end(), [name, type, interfaceId](const DnssdService & service) {
return strcmp(name, service.mName) == 0 && type == GetFullType(service.mType, service.mProtocol) &&
service.mInterface == interfaceId;
});
}
static void OnBrowse(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceId, DNSServiceErrorType err, const char * name,
const char * type, const char * domain, void * context)
{
BrowseContext * sdCtx = reinterpret_cast<BrowseContext *>(context);
VerifyOrReturn(CheckForSuccess(sdCtx, __func__, err, true));
(flags & kDNSServiceFlagsAdd) ? OnBrowseAdd(sdCtx, name, type, domain, Inet::InterfaceId(interfaceId))
: OnBrowseRemove(sdCtx, name, type, domain, Inet::InterfaceId(interfaceId));
if (!(flags & kDNSServiceFlagsMoreComing))
{
sdCtx->callback(sdCtx->context, sdCtx->services.data(), sdCtx->services.size(), CHIP_NO_ERROR);
MdnsContexts::GetInstance().Remove(sdCtx);
}
}
CHIP_ERROR Browse(void * context, DnssdBrowseCallback callback, uint32_t interfaceId, const char * type,
DnssdServiceProtocol protocol)
{
DNSServiceErrorType err;
DNSServiceRef sdRef;
BrowseContext * sdCtx;
std::string regtype(type);
std::string subtypeDelimiter = "._sub.";
size_t position = regtype.find(subtypeDelimiter);
if (position != std::string::npos)
{
regtype = regtype.substr(position + subtypeDelimiter.size()) + "," + regtype.substr(0, position);
}
sdCtx = chip::Platform::New<BrowseContext>(context, callback, protocol);
err = DNSServiceBrowse(&sdRef, 0 /* flags */, interfaceId, regtype.c_str(), kLocalDot, OnBrowse, sdCtx);
VerifyOrReturnError(CheckForSuccess(sdCtx, __func__, err), CHIP_ERROR_INTERNAL);
err = DNSServiceSetDispatchQueue(sdRef, chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue());
VerifyOrReturnError(CheckForSuccess(sdCtx, __func__, err, true), CHIP_ERROR_INTERNAL);
return MdnsContexts::GetInstance().Add(sdCtx, sdRef);
}
static void OnGetAddrInfo(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceId, DNSServiceErrorType err,
const char * hostname, const struct sockaddr * address, uint32_t ttl, void * context)
{
GetAddrInfoContext * sdCtx = reinterpret_cast<GetAddrInfoContext *>(context);
VerifyOrReturn(CheckForSuccess(sdCtx, __func__, err, true));
ChipLogDetail(DeviceLayer, "Mdns: %s hostname:%s", __func__, hostname);
DnssdService service = {};
service.mPort = sdCtx->port;
service.mTextEntries = sdCtx->textEntries.empty() ? nullptr : sdCtx->textEntries.data();
service.mTextEntrySize = sdCtx->textEntries.empty() ? 0 : sdCtx->textEntries.size();
chip::Inet::IPAddress ip;
CHIP_ERROR status = chip::Inet::IPAddress::GetIPAddressFromSockAddr(*address, ip);
service.mAddress.SetValue(ip);
Platform::CopyString(service.mName, sdCtx->name);
Platform::CopyString(service.mHostName, hostname);
service.mInterface = Inet::InterfaceId(sdCtx->interfaceId);
sdCtx->callback(sdCtx->context, &service, status);
MdnsContexts::GetInstance().Remove(sdCtx);
}
static CHIP_ERROR GetAddrInfo(void * context, DnssdResolveCallback callback, uint32_t interfaceId,
chip::Inet::IPAddressType addressType, const char * name, const char * hostname, uint16_t port,
uint16_t txtLen, const unsigned char * txtRecord)
{
DNSServiceErrorType err;
DNSServiceRef sdRef;
GetAddrInfoContext * sdCtx;
sdCtx = chip::Platform::New<GetAddrInfoContext>(context, callback, name, interfaceId, port);
char key[kDnssdKeyMaxSize];
char value[kDnssdTextMaxSize];
uint8_t valueLen;
const void * valuePtr;
uint16_t recordCount = TXTRecordGetCount(txtLen, txtRecord);
for (uint16_t i = 0; i < recordCount; i++)
{
err = TXTRecordGetItemAtIndex(txtLen, txtRecord, i, kDnssdKeyMaxSize, key, &valueLen, &valuePtr);
VerifyOrReturnError(CheckForSuccess(sdCtx, __func__, err, true), CHIP_ERROR_INTERNAL);
if (valueLen >= sizeof(value))
{
// Truncation, but nothing better we can do
valueLen = sizeof(value) - 1;
}
memcpy(value, valuePtr, valueLen);
value[valueLen] = 0;
sdCtx->textEntries.push_back(TextEntry{ strdup(key), reinterpret_cast<const uint8_t *>(strdup(value)), valueLen });
}
DNSServiceProtocol protocol;
#if INET_CONFIG_ENABLE_IPV4
if (addressType == chip::Inet::IPAddressType::kIPv4)
{
protocol = kDNSServiceProtocol_IPv4;
}
else if (addressType == chip::Inet::IPAddressType::kIPv6)
{
protocol = kDNSServiceProtocol_IPv6;
}
else
{
protocol = kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6;
}
#else
// without IPv4, IPv6 is the only option
protocol = kDNSServiceProtocol_IPv6;
#endif
if (interfaceId != kDNSServiceInterfaceIndexLocalOnly)
{
// -1 is the local only interface. If we're not on that interface, we need to get the address for the given hostname.
err = DNSServiceGetAddrInfo(&sdRef, 0 /* flags */, interfaceId, protocol, hostname, OnGetAddrInfo, sdCtx);
VerifyOrReturnError(CheckForSuccess(sdCtx, __func__, err, true), CHIP_ERROR_INTERNAL);
err = DNSServiceSetDispatchQueue(sdRef, chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue());
VerifyOrReturnError(CheckForSuccess(sdCtx, __func__, err, true), CHIP_ERROR_INTERNAL);
return MdnsContexts::GetInstance().Add(sdCtx, sdRef);
}
else
{
sockaddr_in6 sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin6_len = sizeof(sockaddr);
sockaddr.sin6_family = AF_INET6;
sockaddr.sin6_addr = in6addr_loopback;
sockaddr.sin6_port = htons((unsigned short) port);
uint32_t ttl = 120; // default TTL for records with hostnames is 120 seconds
uint32_t interface = 0; // Set interface to ANY (0) - network stack can decide how to route this.
OnGetAddrInfo(nullptr, 0 /* flags */, interface, kDNSServiceErr_NoError, hostname,
reinterpret_cast<struct sockaddr *>(&sockaddr), ttl, sdCtx);
// Don't leak memory.
sdCtx->serviceRef = nullptr;
MdnsContexts::GetInstance().Delete(sdCtx);
return CHIP_NO_ERROR;
}
}
static void OnResolve(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceId, DNSServiceErrorType err,
const char * fullname, const char * hostname, uint16_t port, uint16_t txtLen, const unsigned char * txtRecord,
void * context)
{
ChipLogDetail(DeviceLayer, "Resolved interface id: %u", interfaceId);
ResolveContext * sdCtx = reinterpret_cast<ResolveContext *>(context);
VerifyOrReturn(CheckForSuccess(sdCtx, __func__, err, true));
GetAddrInfo(sdCtx->context, sdCtx->callback, interfaceId, sdCtx->addressType, sdCtx->name, hostname, ntohs(port), txtLen,
txtRecord);
MdnsContexts::GetInstance().Remove(sdCtx);
}
static CHIP_ERROR Resolve(void * context, DnssdResolveCallback callback, uint32_t interfaceId,
chip::Inet::IPAddressType addressType, const char * type, const char * name)
{
DNSServiceErrorType err;
DNSServiceRef sdRef;
ResolveContext * sdCtx;
sdCtx = chip::Platform::New<ResolveContext>(context, callback, name, addressType);
err = DNSServiceResolve(&sdRef, 0 /* flags */, interfaceId, name, type, kLocalDot, OnResolve, sdCtx);
VerifyOrReturnError(CheckForSuccess(sdCtx, __func__, err), CHIP_ERROR_INTERNAL);
err = DNSServiceSetDispatchQueue(sdRef, chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue());
VerifyOrReturnError(CheckForSuccess(sdCtx, __func__, err, true), CHIP_ERROR_INTERNAL);
return MdnsContexts::GetInstance().Add(sdCtx, sdRef);
}
CHIP_ERROR ChipDnssdInit(DnssdAsyncReturnCallback successCallback, DnssdAsyncReturnCallback errorCallback, void * context)
{
VerifyOrReturnError(successCallback != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(errorCallback != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
successCallback(context, CHIP_NO_ERROR);
return CHIP_NO_ERROR;
}
CHIP_ERROR ChipDnssdShutdown()
{
return CHIP_NO_ERROR;
}
CHIP_ERROR ChipDnssdPublishService(const DnssdService * service)
{
VerifyOrReturnError(service != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(IsSupportedProtocol(service->mProtocol), CHIP_ERROR_INVALID_ARGUMENT);
if (strcmp(service->mHostName, "") != 0)
{
MdnsContexts::GetInstance().SetHostname(service->mHostName);
}
std::string regtype = GetFullTypeWithSubTypes(service->mType, service->mProtocol, service->mSubTypes, service->mSubTypeSize);
uint32_t interfaceId = GetInterfaceId(service->mInterface);
TXTRecordRef record;
char buffer[kDnssdTextMaxSize];
ReturnErrorOnFailure(PopulateTextRecord(&record, buffer, sizeof(buffer), service->mTextEntries, service->mTextEntrySize));
ChipLogProgress(DeviceLayer, "Publishing service %s on port %u with type: %s on interface id: %" PRIu32, service->mName,
service->mPort, regtype.c_str(), interfaceId);
return Register(interfaceId, regtype.c_str(), service->mName, service->mPort, &record);
}
CHIP_ERROR ChipDnssdRemoveServices()
{
GenericContext * sdCtx = nullptr;
if (CHIP_ERROR_KEY_NOT_FOUND == MdnsContexts::GetInstance().Get(ContextType::Register, &sdCtx))
{
return CHIP_NO_ERROR;
}
return MdnsContexts::GetInstance().Removes(ContextType::Register);
}
CHIP_ERROR ChipDnssdFinalizeServiceUpdate()
{
return CHIP_NO_ERROR;
}
CHIP_ERROR ChipDnssdBrowse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType,
chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context)
{
VerifyOrReturnError(type != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(callback != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(IsSupportedProtocol(protocol), CHIP_ERROR_INVALID_ARGUMENT);
std::string regtype = GetFullType(type, protocol);
uint32_t interfaceId = GetInterfaceId(interface);
return Browse(context, callback, interfaceId, regtype.c_str(), protocol);
}
CHIP_ERROR ChipDnssdResolve(DnssdService * service, chip::Inet::InterfaceId interface, DnssdResolveCallback callback,
void * context)
{
VerifyOrReturnError(service != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(IsSupportedProtocol(service->mProtocol), CHIP_ERROR_INVALID_ARGUMENT);
std::string regtype = GetFullType(service->mType, service->mProtocol);
uint32_t interfaceId = GetInterfaceId(interface);
return Resolve(context, callback, interfaceId, service->mAddressType, regtype.c_str(), service->mName);
}
} // namespace Dnssd
} // namespace chip