Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register the DNS service for the accessory with a valid hostname #22010

Merged
merged 1 commit into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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