Skip to content

Commit

Permalink
Register the DNS service for the accessory with a valid hostname (pro…
Browse files Browse the repository at this point in the history
…ject-chip#22010)

- Call DNSServiceRegisterRecord to register a record for the hostname for all interfaces
  before calling DNSServiceRegister.
  • Loading branch information
nivi-apple authored Sep 12, 2022
1 parent c6eeece commit 70b8bb1
Show file tree
Hide file tree
Showing 9 changed files with 390 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/platform/Darwin/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ static_library("Darwin") {
"DiagnosticDataProviderImpl.cpp",
"DiagnosticDataProviderImpl.h",
"DnssdContexts.cpp",
"DnssdHostNameRegistrar.cpp",
"DnssdImpl.cpp",
"DnssdImpl.h",
"InetPlatformConfig.h",
Expand Down
27 changes: 22 additions & 5 deletions src/platform/Darwin/DnssdContexts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

Expand All @@ -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;
Expand All @@ -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);
}

Expand All @@ -308,7 +325,7 @@ ResolveContext::~ResolveContext() {}
void ResolveContext::DispatchFailure(DNSServiceErrorType err)
{
ChipLogError(Discovery, "Mdns: Resolve failure (%s)", Error::ToString(err));
callback(context, nullptr, Span<Inet::IPAddress>(), CHIP_ERROR_INTERNAL);
callback(context, nullptr, Span<Inet::IPAddress>(), Error::ToChipError(err));
MdnsContexts::GetInstance().Remove(this);
}

Expand Down
203 changes: 203 additions & 0 deletions src/platform/Darwin/DnssdHostNameRegistrar.cpp
Original file line number Diff line number Diff line change
@@ -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 <arpa/inet.h>
#include <ifaddrs.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <netdb.h>

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<RegisterRecordContext *>(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<struct sockaddr_in *>(ifa->ifa_addr)->sin_addr;
}
else
{
mInterfaceAddress.ipv6 = reinterpret_cast<struct sockaddr_in6 *>(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<const void *>(GetIPv4()) : static_cast<const void *>(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<RegisterRecordContext>(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
102 changes: 102 additions & 0 deletions src/platform/Darwin/DnssdHostNameRegistrar.h
Original file line number Diff line number Diff line change
@@ -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 <dns_sd.h>
#include <lib/dnssd/platform/Dnssd.h>

#include <string>
#include <vector>

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<InterfaceRegistrant> mRegistry;
uint8_t mRegistrationCount = 0;
bool mRegistrationSuccess = false;
};

} // namespace Dnssd
} // namespace chip
Loading

0 comments on commit 70b8bb1

Please sign in to comment.