blob: 2a1dec2de1176baf89dd262be00c4f6182d3ac3d [file] [log] [blame]
/*
*
* Copyright (c) 2021-2022 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 "dns_sd.h"
#include "dnssd_ipc.h"
#include <inet/IPAddress.h>
#include <lib/support/CHIPMemString.h>
#include <lwip/inet.h>
#include <lwip/ip4_addr.h>
#include <lwip/ip6_addr.h>
#include <platform/CHIPDeviceLayer.h>
using namespace chip::Dnssd;
namespace {
constexpr uint8_t kDnssdKeyMaxSize = 32;
constexpr uint8_t kDnssdTxtRecordMaxEntries = 20;
std::string GetHostNameWithoutDomain(const char * hostnameWithDomain)
{
std::string hostname(hostnameWithDomain);
size_t position = hostname.find(".");
if (position != std::string::npos)
{
hostname.erase(position);
}
return hostname;
}
std::string GetFullTypeWithoutSubTypes(std::string fullType)
{
size_t position = fullType.find(",");
if (position != std::string::npos)
{
fullType.erase(position);
}
return fullType;
}
void GetTextEntries(DnssdService & service, const unsigned char * data, uint16_t len)
{
uint16_t recordCount = TXTRecordGetCount(len, data);
service.mTextEntrySize = recordCount;
service.mTextEntries = static_cast<TextEntry *>(chip::Platform::MemoryCalloc(kDnssdTxtRecordMaxEntries, sizeof(TextEntry)));
for (uint16_t i = 0; i < recordCount; i++)
{
char key[kDnssdKeyMaxSize];
uint8_t valueLen;
const void * valuePtr;
auto err = TXTRecordGetItemAtIndex(len, data, i, kDnssdKeyMaxSize, key, &valueLen, &valuePtr);
if (kDNSServiceErr_NoError != err)
{
// If there is an error with a txt record stop the parsing here.
service.mTextEntrySize = i;
break;
}
if (valueLen >= chip::Dnssd::kDnssdTextMaxSize)
{
// Truncation, but nothing better we can do
valueLen = chip::Dnssd::kDnssdTextMaxSize - 1;
}
char value[chip::Dnssd::kDnssdTextMaxSize];
memcpy(value, valuePtr, valueLen);
value[valueLen] = 0;
auto & textEntry = service.mTextEntries[i];
textEntry.mKey = strdup(key);
textEntry.mData = reinterpret_cast<const uint8_t *>(strdup(value));
textEntry.mDataSize = valueLen;
}
}
DNSServiceProtocol GetProtocol(const chip::Inet::IPAddressType & addressType)
{
#if INET_CONFIG_ENABLE_IPV4
if (addressType == chip::Inet::IPAddressType::kIPv4)
{
return kDNSServiceProtocol_IPv4;
}
if (addressType == chip::Inet::IPAddressType::kIPv6)
{
return kDNSServiceProtocol_IPv6;
}
return kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6;
#else
// without IPv4, IPv6 is the only option
return kDNSServiceProtocol_IPv6;
#endif
}
} // namespace
namespace chip {
namespace Dnssd {
CHIP_ERROR GenericContext::Finalize(DNSServiceErrorType err)
{
if (MdnsContexts::GetInstance().Has(this) == CHIP_NO_ERROR)
{
if (kDNSServiceErr_NoError == err)
{
DispatchSuccess();
}
else
{
DispatchFailure(err);
}
}
else
{
chip::Platform::Delete(this);
}
return Error::ToChipError(err);
}
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);
if (sdRef == nullptr)
{
chip::Platform::Delete(context);
return 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::RemoveAllOfType(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;
}
void MdnsContexts::Delete(GenericContext * context)
{
if (context->serviceRef != nullptr)
{
DNSServiceRefDeallocate(context->serviceRef);
}
chip::Platform::Delete(context);
}
CHIP_ERROR MdnsContexts::Has(GenericContext * context)
{
std::vector<GenericContext *>::iterator iter;
for (iter = mContexts.begin(); iter != mContexts.end(); iter++)
{
if ((*iter) == context)
{
return CHIP_NO_ERROR;
}
}
return CHIP_ERROR_KEY_NOT_FOUND;
}
GenericContext * MdnsContexts::GetBySockFd(int fd)
{
std::vector<GenericContext *>::iterator iter;
for (iter = mContexts.begin(); iter != mContexts.end(); iter++)
{
if (fd == DNSServiceRefSockFD((*iter)->serviceRef))
{
return *iter;
}
}
return NULL;
}
int MdnsContexts::GetSelectFd(fd_set * pSelectFd)
{
int maxFd = 0;
std::vector<GenericContext *>::iterator iter;
FD_ZERO(pSelectFd);
for (iter = mContexts.begin(); iter != mContexts.end(); iter++)
{
int curFd = DNSServiceRefSockFD((*iter)->serviceRef);
(*iter)->mSelectCount += 1;
FD_SET(curFd, pSelectFd);
if (curFd > maxFd)
maxFd = curFd;
}
return maxFd;
}
BrowseContext::BrowseContext(void * cbContext, DnssdBrowseCallback cb, DnssdServiceProtocol cbContextProtocol)
{
type = ContextType::Browse;
context = cbContext;
callback = cb;
protocol = cbContextProtocol;
mSelectCount = 0;
}
void BrowseContext::DispatchFailure(DNSServiceErrorType err)
{
ChipLogError(Discovery, "Mdns: Browse failure (%s)", Error::ToString(err));
callback(context, nullptr, 0, true, Error::ToChipError(err));
MdnsContexts::GetInstance().Remove(this);
}
void BrowseContext::DispatchSuccess()
{
callback(context, services.data(), services.size(), true, CHIP_NO_ERROR);
MdnsContexts::GetInstance().Remove(this);
}
ResolveContext::ResolveContext(void * cbContext, DnssdResolveCallback cb, chip::Inet::IPAddressType cbAddressType)
{
type = ContextType::Resolve;
context = cbContext;
callback = cb;
protocol = GetProtocol(cbAddressType);
mSelectCount = 0;
}
ResolveContext::~ResolveContext() {}
void ResolveContext::DispatchFailure(DNSServiceErrorType err)
{
ChipLogError(Discovery, "Mdns: Resolve failure (%s)", Error::ToString(err));
callback(context, nullptr, Span<Inet::IPAddress>(), Error::ToChipError(err));
MdnsContexts::GetInstance().Remove(this);
}
void ResolveContext::DispatchSuccess()
{
for (auto & interface : interfaces)
{
auto & ips = interface.second.addresses;
// Some interface may not have any ips, just ignore them.
if (ips.size() == 0)
{
continue;
}
ChipLogDetail(Discovery, "Mdns: Resolve success on interface %" PRIu32, interface.first);
callback(context, &interface.second.service, Span<Inet::IPAddress>(ips.data(), ips.size()), CHIP_NO_ERROR);
break;
}
MdnsContexts::GetInstance().Remove(this);
}
CHIP_ERROR ResolveContext::OnNewAddress(uint32_t interfaceId, const struct sockaddr * address)
{
CHIP_ERROR err = CHIP_NO_ERROR;
chip::Inet::IPAddress ip;
// ReturnErrorOnFailure(chip::Inet::IPAddress::GetIPAddressFromSockAddr(*address, ip));
if (address->sa_family == AF_INET6)
{
ip6_addr_t _ip6_adr;
struct sockaddr_in6 * addr_in6 = (struct sockaddr_in6 *) address;
inet6_addr_to_ip6addr(&_ip6_adr, &addr_in6->sin6_addr);
ip = chip::Inet::IPAddress(_ip6_adr);
}
else if (address->sa_family == AF_INET)
{
ip4_addr_t _ip4_adr;
struct sockaddr_in * addr_in = (struct sockaddr_in *) address;
inet_addr_to_ip4addr(&_ip4_adr, &addr_in->sin_addr);
ip = chip::Inet::IPAddress(_ip4_adr);
}
else
{
ChipLogDetail(Discovery, "Mdns: %s interface: %" PRIu32 " unknown sa_family(%d)", __func__, interfaceId,
address->sa_family);
// TODO: assign correct error code
err = (CHIP_ERROR) 1;
}
if (err == CHIP_NO_ERROR)
{
interfaces[interfaceId].addresses.push_back(ip);
}
#ifdef CHIP_DETAIL_LOGGING
char addrStr[INET6_ADDRSTRLEN];
ip.ToString(addrStr, sizeof(addrStr));
ChipLogDetail(Discovery, "Mdns: %s interface: %" PRIu32 " ip:%s", __func__, interfaceId, addrStr);
#endif // CHIP_DETAIL_LOGGING
return err;
}
CHIP_ERROR ResolveContext::OnNewLocalOnlyAddress()
{
struct sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
#define MDNS_TCP_SERVERADDR "127.0.0.1"
#define MDNS_TCP_SERVERPORT 5354
sockaddr.sin_addr.s_addr = inet_addr(MDNS_TCP_SERVERADDR);
sockaddr.sin_port = htons(MDNS_TCP_SERVERPORT);
sockaddr.sin_len = sizeof(sockaddr);
return OnNewAddress(kDNSServiceInterfaceIndexLocalOnly, reinterpret_cast<struct sockaddr *>(&sockaddr));
}
bool ResolveContext::HasAddress()
{
for (auto & interface : interfaces)
{
if (interface.second.addresses.size())
{
return true;
}
}
return false;
}
void ResolveContext::OnNewInterface(uint32_t interfaceId, const char * fullname, const char * hostnameWithDomain, uint16_t port,
uint16_t txtLen, const unsigned char * txtRecord)
{
#if CHIP_DETAIL_LOGGING
std::string txtString;
auto txtRecordIter = txtRecord;
size_t remainingLen = txtLen;
while (remainingLen > 0)
{
size_t len = *txtRecordIter;
++txtRecordIter;
--remainingLen;
len = min(len, remainingLen);
chip::Span<const unsigned char> bytes(txtRecordIter, len);
if (txtString.size() > 0)
{
txtString.push_back(',');
}
for (auto & byte : bytes)
{
if ((std::isalnum(byte) || std::ispunct(byte)) && byte != '\\' && byte != ',')
{
txtString.push_back(static_cast<char>(byte));
}
else
{
char hex[5];
snprintf(hex, sizeof(hex), "\\x%02x", byte);
txtString.append(hex);
}
}
txtRecordIter += len;
remainingLen -= len;
}
#endif // CHIP_DETAIL_LOGGING
ChipLogDetail(Discovery, "Mdns : %s hostname:%s fullname:%s interface: %" PRIu32 " port: %u TXT:\"%s\"", __func__,
hostnameWithDomain, fullname, interfaceId, ntohs(port), txtString.c_str());
InterfaceInfo interface;
interface.service.mPort = ntohs(port);
if (kDNSServiceInterfaceIndexLocalOnly == interfaceId)
{
// Set interface to ANY (0) - network stack can decide how to route this.
interface.service.mInterface = Inet::InterfaceId(0);
}
else
{
// FIX: get the netif from interfaceId
struct netif * sta_if = netif_default;
interface.service.mInterface = Inet::InterfaceId(sta_if);
}
// The hostname parameter contains the hostname followed by the domain. But the mHostName field is sized
// to contain either a 12 bytes mac address or an extended address of at most 16 bytes, not the domain name.
auto hostname = GetHostNameWithoutDomain(hostnameWithDomain);
Platform::CopyString(interface.service.mHostName, hostname.c_str());
Platform::CopyString(interface.service.mName, fullname);
GetTextEntries(interface.service, txtRecord, txtLen);
// If for some reason the hostname can not fit into the hostname field (e.g it is not a mac address) then
// DNSServiceGetAddrInfo will never return anything. So instead, copy the name as the FQDN and use it for
// resolving.
interface.fullyQualifiedDomainName = hostnameWithDomain;
interfaces.insert(std::make_pair(interfaceId, std::move(interface)));
}
bool ResolveContext::HasInterface()
{
return interfaces.size();
}
InterfaceInfo::InterfaceInfo()
{
service.mTextEntrySize = 0;
service.mTextEntries = nullptr;
}
InterfaceInfo::InterfaceInfo(InterfaceInfo && other) :
service(std::move(other.service)), addresses(std::move(other.addresses)),
fullyQualifiedDomainName(std::move(other.fullyQualifiedDomainName))
{
// Make sure we're not trying to free any state from the other DnssdService,
// since we took over ownership of its allocated bits.
other.service.mTextEntrySize = 0;
other.service.mTextEntries = nullptr;
}
InterfaceInfo::~InterfaceInfo()
{
if (service.mTextEntries == nullptr)
{
return;
}
const size_t count = service.mTextEntrySize;
for (size_t i = 0; i < count; i++)
{
const auto & textEntry = service.mTextEntries[i];
free(const_cast<char *>(textEntry.mKey));
free(const_cast<uint8_t *>(textEntry.mData));
}
Platform::MemoryFree(const_cast<TextEntry *>(service.mTextEntries));
}
} // namespace Dnssd
} // namespace chip