diff --git a/src/platform/Darwin/BUILD.gn b/src/platform/Darwin/BUILD.gn index dc58f04faa75e9..29fddf18d99e1d 100644 --- a/src/platform/Darwin/BUILD.gn +++ b/src/platform/Darwin/BUILD.gn @@ -55,6 +55,7 @@ static_library("Darwin") { "DiagnosticDataProviderImpl.cpp", "DiagnosticDataProviderImpl.h", "DnssdContexts.cpp", + "DnssdHostNameRegistrar.cpp", "DnssdImpl.cpp", "DnssdImpl.h", "InetPlatformConfig.h", diff --git a/src/platform/Darwin/DnssdContexts.cpp b/src/platform/Darwin/DnssdContexts.cpp index c195b268fbe747..b3771bb980808b 100644 --- a/src/platform/Darwin/DnssdContexts.cpp +++ b/src/platform/Darwin/DnssdContexts.cpp @@ -130,7 +130,7 @@ CHIP_ERROR GenericContext::Finalize(DNSServiceErrorType err) chip::Platform::Delete(this); } - return (kDNSServiceErr_NoError == err) ? CHIP_NO_ERROR : CHIP_ERROR_INTERNAL; + return Error::ToChipError(err); } MdnsContexts::~MdnsContexts() @@ -157,7 +157,7 @@ CHIP_ERROR MdnsContexts::Add(GenericContext * context, DNSServiceRef sdRef) if (kDNSServiceErr_NoError != err) { chip::Platform::Delete(context); - return CHIP_ERROR_INTERNAL; + return Error::ToChipError(err); } context->serviceRef = sdRef; @@ -264,7 +264,7 @@ RegisterContext::RegisterContext(const char * sType, const char * instanceName, void RegisterContext::DispatchFailure(DNSServiceErrorType err) { ChipLogError(Discovery, "Mdns: Register failure (%s)", Error::ToString(err)); - callback(context, nullptr, nullptr, CHIP_ERROR_INTERNAL); + callback(context, nullptr, nullptr, Error::ToChipError(err)); MdnsContexts::GetInstance().Remove(this); } @@ -274,6 +274,23 @@ void RegisterContext::DispatchSuccess() callback(context, typeWithoutSubTypes.c_str(), mInstanceName.c_str(), CHIP_NO_ERROR); } +RegisterRecordContext::RegisterRecordContext(RegisterContext * context) +{ + type = ContextType::RegisterRecord; + mRegisterContext = context; +} + +void RegisterRecordContext::DispatchFailure(DNSServiceErrorType err) +{ + mRegisterContext->Finalize(err); + MdnsContexts::GetInstance().Remove(this); +} + +void RegisterRecordContext::DispatchSuccess() +{ + mRegisterContext->Finalize(); +} + BrowseContext::BrowseContext(void * cbContext, DnssdBrowseCallback cb, DnssdServiceProtocol cbContextProtocol) { type = ContextType::Browse; @@ -285,7 +302,7 @@ BrowseContext::BrowseContext(void * cbContext, DnssdBrowseCallback cb, DnssdServ void BrowseContext::DispatchFailure(DNSServiceErrorType err) { ChipLogError(Discovery, "Mdns: Browse failure (%s)", Error::ToString(err)); - callback(context, nullptr, 0, true, CHIP_ERROR_INTERNAL); + callback(context, nullptr, 0, true, Error::ToChipError(err)); MdnsContexts::GetInstance().Remove(this); } @@ -308,7 +325,7 @@ ResolveContext::~ResolveContext() {} void ResolveContext::DispatchFailure(DNSServiceErrorType err) { ChipLogError(Discovery, "Mdns: Resolve failure (%s)", Error::ToString(err)); - callback(context, nullptr, Span(), CHIP_ERROR_INTERNAL); + callback(context, nullptr, Span(), Error::ToChipError(err)); MdnsContexts::GetInstance().Remove(this); } diff --git a/src/platform/Darwin/DnssdHostNameRegistrar.cpp b/src/platform/Darwin/DnssdHostNameRegistrar.cpp new file mode 100644 index 00000000000000..d4d25fd61ab200 --- /dev/null +++ b/src/platform/Darwin/DnssdHostNameRegistrar.cpp @@ -0,0 +1,203 @@ +/* + * + * Copyright (c) 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 "DnssdHostNameRegistrar.h" +#include "DnssdImpl.h" + +#include +#include +#include +#include +#include + +constexpr DNSServiceFlags kRegisterRecordFlags = kDNSServiceFlagsShared; + +namespace chip { +namespace Dnssd { + +namespace { + +static void OnRegisterRecord(DNSServiceRef sdRef, DNSRecordRef recordRef, DNSServiceFlags flags, DNSServiceErrorType err, + void * context) +{ + ChipLogDetail(Discovery, "Mdns: %s flags: %d", __func__, flags); + + auto sdCtx = reinterpret_cast(context); + + auto & registrar = sdCtx->mRegisterContext->mHostNameRegistrar; + if (!registrar.IncrementRegistrationCount(err)) + { + sdCtx->Finalize(registrar.HasRegisteredRegistrant() ? kDNSServiceErr_NoError : kDNSServiceErr_Unknown); + } +} + +} // namespace + +bool InterfaceRegistrant::Init(const struct ifaddrs * ifa, Inet::IPAddressType addressType, uint32_t interfaceId) +{ + VerifyOrReturnValue(ifa != nullptr, false); + VerifyOrReturnValue(ifa->ifa_addr != nullptr, false); + VerifyOrReturnValue(HasValidFlags(ifa->ifa_flags), false); + VerifyOrReturnValue(IsValidInterfaceId(interfaceId, if_nametoindex(ifa->ifa_name)), false); + VerifyOrReturnValue(HasValidType(addressType, ifa->ifa_addr->sa_family), false); + + // The incoming interface id can be kDNSServiceInterfaceIndexAny, but here what needs to be done is to + // associate a given ip with a specific interface id, so the incoming interface id is used as a hint + // to validate if the current interface is of interest or not, but it is not what is stored internally. + mInterfaceId = if_nametoindex(ifa->ifa_name); + mInterfaceAddressType = ifa->ifa_addr->sa_family; + + if (IsIPv4()) + { + mInterfaceAddress.ipv4 = reinterpret_cast(ifa->ifa_addr)->sin_addr; + } + else + { + mInterfaceAddress.ipv6 = reinterpret_cast(ifa->ifa_addr)->sin6_addr; + } + + return true; +} + +DNSServiceErrorType InterfaceRegistrant::Register(DNSServiceRef sdRef, RegisterRecordContext * sdCtx, const char * hostname) const +{ + LogDetail(); + + uint16_t rrtype = IsIPv4() ? kDNSServiceType_A : kDNSServiceType_AAAA; + uint16_t rdlen = IsIPv4() ? sizeof(in_addr) : sizeof(in6_addr); + const void * rdata = IsIPv4() ? static_cast(GetIPv4()) : static_cast(GetIPv6()); + + DNSRecordRef dnsRecordRef; + return DNSServiceRegisterRecord(sdRef, &dnsRecordRef, kRegisterRecordFlags, mInterfaceId, hostname, rrtype, kDNSServiceClass_IN, + rdlen, rdata, 0, OnRegisterRecord, sdCtx); +} + +bool InterfaceRegistrant::HasValidFlags(unsigned int flags) +{ + return !(flags & IFF_POINTOPOINT) && (flags & IFF_RUNNING) && (flags & IFF_MULTICAST); +} + +bool InterfaceRegistrant::HasValidType(Inet::IPAddressType addressType, const sa_family_t addressFamily) +{ + bool useIPv4 = addressType == Inet::IPAddressType::kIPv4 || addressType == Inet::IPAddressType::kAny; + bool useIPv6 = addressType == Inet::IPAddressType::kIPv6 || addressType == Inet::IPAddressType::kAny; + + return (useIPv6 && addressFamily == AF_INET6) || (useIPv4 && addressFamily == AF_INET); +} + +bool InterfaceRegistrant::IsValidInterfaceId(uint32_t targetInterfaceId, unsigned int currentInterfaceId) +{ + return targetInterfaceId == kDNSServiceInterfaceIndexAny || targetInterfaceId == currentInterfaceId; +} + +void InterfaceRegistrant::LogDetail() const +{ + char interfaceName[IFNAMSIZ] = {}; + VerifyOrReturn(if_indextoname(mInterfaceId, interfaceName) != nullptr); + + if (IsIPv4()) + { + char addr[INET_ADDRSTRLEN] = {}; + inet_ntop(AF_INET, GetIPv4(), addr, sizeof(addr)); + ChipLogDetail(Discovery, "\tInterface: %s (%u) ipv4: %s", interfaceName, mInterfaceId, addr); + } + else + { + char addr[INET6_ADDRSTRLEN] = {}; + inet_ntop(AF_INET6, GetIPv6(), addr, sizeof(addr)); + ChipLogDetail(Discovery, "\tInterface: %s (%u) ipv6: %s", interfaceName, mInterfaceId, addr); + } +} + +bool HostNameRegistrar::Init(const char * hostname, Inet::IPAddressType addressType, uint32_t interfaceId) +{ + mHostName = hostname; + + // When the target interface is kDNSServiceInterfaceIndexLocalOnly, there is no need to map the hostname to + // an IP address. + VerifyOrReturnValue(interfaceId != kDNSServiceInterfaceIndexLocalOnly, true); + + struct ifaddrs * ifap; + VerifyOrReturnValue(getifaddrs(&ifap) >= 0, false); + + for (struct ifaddrs * ifa = ifap; ifa; ifa = ifa->ifa_next) + { + InterfaceRegistrant registrant; + if (registrant.Init(ifa, addressType, interfaceId)) + { + mRegistry.push_back(registrant); + } + } + + freeifaddrs(ifap); + + return mRegistry.size() != 0; +} + +CHIP_ERROR HostNameRegistrar::Register(RegisterContext * registerCtx) +{ + // If the target interface is kDNSServiceInterfaceIndexLocalOnly, there are no interfaces to register against + // the dns daemon. + if (mRegistry.size() == 0) + { + return registerCtx->Finalize(); + } + + DNSServiceRef sdRef; + auto err = DNSServiceCreateConnection(&sdRef); + VerifyOrReturnError(kDNSServiceErr_NoError == err, CHIP_ERROR_INTERNAL); + + RegisterRecordContext * sdCtx = Platform::New(registerCtx); + VerifyOrReturnError(nullptr != sdCtx, CHIP_ERROR_NO_MEMORY); + + ChipLogDetail(Discovery, "Mdns: Mapping %s to:", mHostName.c_str()); + + for (auto & registrant : mRegistry) + { + err = registrant.Register(sdRef, sdCtx, mHostName.c_str()); + + // An error to register a particular registrant is not fatal unless all registrants have failed. + // Otherwise, if there is an errror, it indicates that this registrant will not trigger a callback + // with its registration status. So we take the registration failure into account here. + if (kDNSServiceErr_NoError != err) + { + VerifyOrReturnError(IncrementRegistrationCount(err), sdCtx->Finalize(err)); + } + } + + return MdnsContexts::GetInstance().Add(sdCtx, sdRef); +} + +bool HostNameRegistrar::IncrementRegistrationCount(DNSServiceErrorType err) +{ + mRegistrationCount++; + VerifyOrDie(mRegistrationCount <= mRegistry.size()); + + // A single registration success is enough to consider the whole process a success. + // This is very permissive in the sense that the interface that has succeeded may not be + // enough to do anything useful, but on the other hand, the failure of a single interface + // to successfuly registered does not makes it obvious that it won't work. + if (kDNSServiceErr_NoError == err) + { + mRegistrationSuccess = true; + } + + return mRegistrationCount != mRegistry.size(); +} + +} // namespace Dnssd +} // namespace chip diff --git a/src/platform/Darwin/DnssdHostNameRegistrar.h b/src/platform/Darwin/DnssdHostNameRegistrar.h new file mode 100644 index 00000000000000..2b23c3cdb81593 --- /dev/null +++ b/src/platform/Darwin/DnssdHostNameRegistrar.h @@ -0,0 +1,102 @@ +/* + * + * Copyright (c) 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. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace chip { +namespace Dnssd { + +struct RegisterContext; +struct RegisterRecordContext; + +class InterfaceRegistrant +{ +public: + /** + * Initialize an interface if it match filtering arguments. + * + * @param[in] ifa The interface to initialize from. + * @param[in] addressType An allowed address type. + * Passing in Inet::IPAddressType::kAny effectively allow all address types. + * @param[in] interfaceId An allowed interface id. + * Passing in kDNSServiceInterfaceIndexAny effectively allow all interface ids. + * + * @return A boolean indicating if the initialization from the data of the ifaddrs was allowed by the filtering parameters. + */ + bool Init(const struct ifaddrs * ifa, Inet::IPAddressType addressType, uint32_t interfaceId); + + DNSServiceErrorType Register(DNSServiceRef sdRef, RegisterRecordContext * sdCtx, const char * hostname) const; + +private: + static bool HasValidFlags(unsigned int flags); + static bool HasValidType(Inet::IPAddressType addressType, const sa_family_t addressFamily); + static bool IsValidInterfaceId(uint32_t targetInterfaceId, unsigned int currentInterfaceId); + + const struct in_addr * GetIPv4() const { return &mInterfaceAddress.ipv4; } + const struct in6_addr * GetIPv6() const { return &mInterfaceAddress.ipv6; } + + bool IsIPv4() const { return mInterfaceAddressType == AF_INET; }; + bool IsIPv6() const { return mInterfaceAddressType == AF_INET6; }; + + void LogDetail() const; + + union InterfaceAddress + { + struct in_addr ipv4; + struct in6_addr ipv6; + }; + + uint32_t mInterfaceId; + InterfaceAddress mInterfaceAddress; + sa_family_t mInterfaceAddressType; +}; + +class HostNameRegistrar +{ +public: + bool Init(const char * hostname, Inet::IPAddressType addressType, uint32_t interfaceId); + + CHIP_ERROR Register(RegisterContext * registerCtx); + + /** + * IncrementRegistrationCount is used to indicate a registrant status. + * When all registrants status has been recovered, it returns false to indicate + * that the registration process is finished. + * + * @param[in] err The registration status + * + * @return A boolean indicating if the registration process needs to continue. + */ + bool IncrementRegistrationCount(DNSServiceErrorType err); + + bool HasRegisteredRegistrant() { return mRegistrationSuccess; } + +private: + std::string mHostName; + std::vector mRegistry; + uint8_t mRegistrationCount = 0; + bool mRegistrationSuccess = false; +}; + +} // namespace Dnssd +} // namespace chip diff --git a/src/platform/Darwin/DnssdImpl.cpp b/src/platform/Darwin/DnssdImpl.cpp index 9a989490dc812c..ea370fcdf7c96d 100644 --- a/src/platform/Darwin/DnssdImpl.cpp +++ b/src/platform/Darwin/DnssdImpl.cpp @@ -96,6 +96,11 @@ std::string GetFullTypeWithSubTypes(const DnssdService * service) return GetFullTypeWithSubTypes(service->mType, service->mProtocol, service->mSubTypes, service->mSubTypeSize); } +std::string GetHostNameWithDomain(const char * hostname) +{ + return std::string(hostname) + '.' + kLocalDot; +} + void LogOnFailure(const char * name, DNSServiceErrorType err) { if (kDNSServiceErr_NoError != err) @@ -168,14 +173,18 @@ static void OnRegister(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErr ChipLogDetail(Discovery, "Mdns: %s name: %s, type: %s, domain: %s, flags: %d", __func__, name, type, domain, flags); auto sdCtx = reinterpret_cast(context); - sdCtx->Finalize(err); + VerifyOrReturn(kDNSServiceErr_NoError == err, sdCtx->Finalize(err)); + + // Once a service has been properly published it is normally unreachable because the hostname has not been yet + // registered against the dns daemon. Register the records mapping the hostname to our IP. + sdCtx->mHostNameRegistrar.Register(sdCtx); }; CHIP_ERROR Register(void * context, DnssdPublishCallback callback, uint32_t interfaceId, const char * type, const char * name, - uint16_t port, ScopedTXTRecord & record) + uint16_t port, ScopedTXTRecord & record, Inet::IPAddressType addressType, const char * hostname) { - ChipLogProgress(Discovery, "Publishing service %s on port %u with type: %s on interface id: %" PRIu32, name, port, type, - interfaceId); + ChipLogDetail(Discovery, "Registering service %s on host %s with port %u and type: %s on interface id: %" PRIu32, name, + hostname, port, type, interfaceId); RegisterContext * sdCtx = nullptr; if (CHIP_NO_ERROR == MdnsContexts::GetInstance().GetRegisterContextOfType(type, &sdCtx)) @@ -188,8 +197,11 @@ CHIP_ERROR Register(void * context, DnssdPublishCallback callback, uint32_t inte sdCtx = chip::Platform::New(type, name, callback, context); VerifyOrReturnError(nullptr != sdCtx, CHIP_ERROR_NO_MEMORY); + VerifyOrReturnError(sdCtx->mHostNameRegistrar.Init(hostname, addressType, interfaceId), + sdCtx->Finalize(kDNSServiceErr_BadInterfaceIndex)); + DNSServiceRef sdRef; - auto err = DNSServiceRegister(&sdRef, kRegisterFlags, interfaceId, name, type, kLocalDot, nullptr, ntohs(port), record.size(), + auto err = DNSServiceRegister(&sdRef, kRegisterFlags, interfaceId, name, type, kLocalDot, hostname, ntohs(port), record.size(), record.data(), OnRegister, sdCtx); VerifyOrReturnError(kDNSServiceErr_NoError == err, sdCtx->Finalize(err)); @@ -362,13 +374,17 @@ CHIP_ERROR ChipDnssdPublishService(const DnssdService * service, DnssdPublishCal VerifyOrReturnError(service != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(IsSupportedProtocol(service->mProtocol), CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(callback != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(strcmp(service->mHostName, "") != 0, CHIP_ERROR_INVALID_ARGUMENT); ScopedTXTRecord record; ReturnErrorOnFailure(record.Init(service->mTextEntries, service->mTextEntrySize)); auto regtype = GetFullTypeWithSubTypes(service); auto interfaceId = GetInterfaceId(service->mInterface); - return Register(context, callback, interfaceId, regtype.c_str(), service->mName, service->mPort, record); + auto hostname = GetHostNameWithDomain(service->mHostName); + + return Register(context, callback, interfaceId, regtype.c_str(), service->mName, service->mPort, record, service->mAddressType, + hostname.c_str()); } CHIP_ERROR ChipDnssdRemoveServices() @@ -376,8 +392,16 @@ CHIP_ERROR ChipDnssdRemoveServices() auto err = MdnsContexts::GetInstance().RemoveAllOfType(ContextType::Register); if (CHIP_ERROR_KEY_NOT_FOUND == err) { - return CHIP_NO_ERROR; + err = CHIP_NO_ERROR; + } + ReturnErrorOnFailure(err); + + err = MdnsContexts::GetInstance().RemoveAllOfType(ContextType::RegisterRecord); + if (CHIP_ERROR_KEY_NOT_FOUND == err) + { + err = CHIP_NO_ERROR; } + ReturnErrorOnFailure(err); return err; } diff --git a/src/platform/Darwin/DnssdImpl.h b/src/platform/Darwin/DnssdImpl.h index 67ee638f98c720..d6bb8246703d9a 100644 --- a/src/platform/Darwin/DnssdImpl.h +++ b/src/platform/Darwin/DnssdImpl.h @@ -20,6 +20,8 @@ #include #include +#include "DnssdHostNameRegistrar.h" + #include #include #include @@ -30,6 +32,7 @@ namespace Dnssd { enum class ContextType { Register, + RegisterRecord, Browse, Resolve, }; @@ -93,6 +96,7 @@ struct RegisterContext : public GenericContext DnssdPublishCallback callback; std::string mType; std::string mInstanceName; + HostNameRegistrar mHostNameRegistrar; RegisterContext(const char * sType, const char * instanceName, DnssdPublishCallback cb, void * cbContext); virtual ~RegisterContext() {} @@ -103,6 +107,17 @@ struct RegisterContext : public GenericContext bool matches(const char * sType) { return mType.compare(sType) == 0; } }; +struct RegisterRecordContext : public GenericContext +{ + RegisterContext * mRegisterContext; + + RegisterRecordContext(RegisterContext * context); + virtual ~RegisterRecordContext(){}; + + void DispatchFailure(DNSServiceErrorType err) override; + void DispatchSuccess() override; +}; + struct BrowseContext : public GenericContext { DnssdBrowseCallback callback; diff --git a/src/platform/Darwin/MdnsError.cpp b/src/platform/Darwin/MdnsError.cpp index c4cc4f7794b8fd..9ad52cb9840fee 100644 --- a/src/platform/Darwin/MdnsError.cpp +++ b/src/platform/Darwin/MdnsError.cpp @@ -94,6 +94,19 @@ const char * ToString(DNSServiceErrorType errorCode) } } +CHIP_ERROR ToChipError(DNSServiceErrorType errorCode) +{ + switch (errorCode) + { + case kDNSServiceErr_NoError: + return CHIP_NO_ERROR; + case kDNSServiceErr_NameConflict: + return CHIP_ERROR_MDNS_COLLISION; + default: + return CHIP_ERROR_INTERNAL; + } +} + } // namespace Error } // namespace Dnssd } // namespace chip diff --git a/src/platform/Darwin/MdnsError.h b/src/platform/Darwin/MdnsError.h index 7ae3e76a1d12fd..da1a9773208b83 100644 --- a/src/platform/Darwin/MdnsError.h +++ b/src/platform/Darwin/MdnsError.h @@ -25,6 +25,8 @@ namespace Error { const char * ToString(DNSServiceErrorType errorCode); +CHIP_ERROR ToChipError(DNSServiceErrorType errorCode); + } // namespace Error } // namespace Dnssd } // namespace chip diff --git a/src/platform/tests/TestDnssd.cpp b/src/platform/tests/TestDnssd.cpp index 9d14d5ec63a815..75fc48537b8231 100644 --- a/src/platform/tests/TestDnssd.cpp +++ b/src/platform/tests/TestDnssd.cpp @@ -82,6 +82,7 @@ static void InitCallback(void * context, CHIP_ERROR error) service.mInterface = chip::Inet::InterfaceId::Null(); service.mPort = 80; + strcpy(service.mHostName, "MatterTest"); strcpy(service.mName, "test"); strcpy(service.mType, "_mock"); service.mAddressType = chip::Inet::IPAddressType::kAny;