| /* |
| * |
| * 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 "MdnsError.h" |
| |
| #include <arpa/inet.h> |
| #include <ifaddrs.h> |
| #include <net/ethernet.h> |
| #include <net/if_dl.h> |
| #include <netdb.h> |
| |
| #include <set> |
| |
| #include <platform/CHIPDeviceLayer.h> |
| |
| constexpr DNSServiceFlags kRegisterRecordFlags = kDNSServiceFlagsShared; |
| |
| namespace chip { |
| namespace Dnssd { |
| |
| namespace { |
| |
| #if CHIP_PROGRESS_LOGGING |
| constexpr const char * kPathStatusInvalid = "Invalid"; |
| constexpr const char * kPathStatusUnsatisfied = "Unsatisfied"; |
| constexpr const char * kPathStatusSatisfied = "Satisfied"; |
| constexpr const char * kPathStatusSatisfiable = "Satisfiable"; |
| constexpr const char * kPathStatusUnknown = "Unknown"; |
| |
| constexpr const char * kInterfaceTypeCellular = "Cellular"; |
| constexpr const char * kInterfaceTypeWiFi = "WiFi"; |
| constexpr const char * kInterfaceTypeWired = "Wired"; |
| constexpr const char * kInterfaceTypeLoopback = "Loopback"; |
| constexpr const char * kInterfaceTypeOther = "Other"; |
| constexpr const char * kInterfaceTypeUnknown = "Unknown"; |
| |
| const char * GetPathStatusString(nw_path_status_t status) |
| { |
| const char * str = nullptr; |
| |
| if (status == nw_path_status_invalid) |
| { |
| str = kPathStatusInvalid; |
| } |
| else if (status == nw_path_status_unsatisfied) |
| { |
| str = kPathStatusUnsatisfied; |
| } |
| else if (status == nw_path_status_satisfied) |
| { |
| str = kPathStatusSatisfied; |
| } |
| else if (status == nw_path_status_satisfiable) |
| { |
| str = kPathStatusSatisfiable; |
| } |
| else |
| { |
| str = kPathStatusUnknown; |
| } |
| |
| return str; |
| } |
| |
| const char * GetInterfaceTypeString(nw_interface_type_t type) |
| { |
| const char * str = nullptr; |
| |
| if (type == nw_interface_type_cellular) |
| { |
| str = kInterfaceTypeCellular; |
| } |
| else if (type == nw_interface_type_wifi) |
| { |
| str = kInterfaceTypeWiFi; |
| } |
| else if (type == nw_interface_type_wired) |
| { |
| str = kInterfaceTypeWired; |
| } |
| else if (type == nw_interface_type_loopback) |
| { |
| str = kInterfaceTypeLoopback; |
| } |
| else if (type == nw_interface_type_other) |
| { |
| str = kInterfaceTypeOther; |
| } |
| else |
| { |
| str = kInterfaceTypeUnknown; |
| } |
| |
| return str; |
| } |
| |
| void LogDetails(uint32_t interfaceId, InetInterfacesVector inetInterfaces, Inet6InterfacesVector inet6Interfaces) |
| { |
| for (auto & inetInterface : inetInterfaces) |
| { |
| if (interfaceId == inetInterface.first) |
| { |
| char addr[INET_ADDRSTRLEN] = {}; |
| inet_ntop(AF_INET, &inetInterface.second, addr, sizeof(addr)); |
| ChipLogProgress(Discovery, "\t\t* ipv4: %s", addr); |
| } |
| } |
| |
| for (auto & inet6Interface : inet6Interfaces) |
| { |
| if (interfaceId == inet6Interface.first) |
| { |
| char addr[INET6_ADDRSTRLEN] = {}; |
| inet_ntop(AF_INET6, &inet6Interface.second, addr, sizeof(addr)); |
| ChipLogProgress(Discovery, "\t\t* ipv6: %s", addr); |
| } |
| } |
| } |
| |
| void LogDetails(nw_path_t path) |
| { |
| auto status = nw_path_get_status(path); |
| ChipLogProgress(Discovery, "Status: %s", GetPathStatusString(status)); |
| } |
| |
| void LogDetails(InetInterfacesVector inetInterfaces, Inet6InterfacesVector inet6Interfaces) |
| { |
| std::set<uint32_t> interfaceIds; |
| for (auto & inetInterface : inetInterfaces) |
| { |
| interfaceIds.insert(inetInterface.first); |
| } |
| |
| for (auto & inet6Interface : inet6Interfaces) |
| { |
| interfaceIds.insert(inet6Interface.first); |
| } |
| |
| for (auto interfaceId : interfaceIds) |
| { |
| char interfaceName[IFNAMSIZ] = {}; |
| if_indextoname(interfaceId, interfaceName); |
| ChipLogProgress(Discovery, "\t%s (%u)", interfaceName, interfaceId); |
| LogDetails(interfaceId, inetInterfaces, inet6Interfaces); |
| } |
| } |
| |
| void LogDetails(nw_interface_t interface, InetInterfacesVector inetInterfaces, Inet6InterfacesVector inet6Interfaces) |
| { |
| auto interfaceId = nw_interface_get_index(interface); |
| auto interfaceName = nw_interface_get_name(interface); |
| auto interfaceType = nw_interface_get_type(interface); |
| ChipLogProgress(Discovery, "\t%s (%u / %s)", interfaceName, interfaceId, GetInterfaceTypeString(interfaceType)); |
| LogDetails(interfaceId, inetInterfaces, inet6Interfaces); |
| } |
| #endif // CHIP_PROGRESS_LOGGING |
| |
| bool HasValidFlags(unsigned int flags, bool allowLoopbackOnly) |
| { |
| VerifyOrReturnValue(!allowLoopbackOnly || (flags & IFF_LOOPBACK), false); |
| VerifyOrReturnValue((flags & IFF_RUNNING), false); |
| VerifyOrReturnValue((flags & IFF_MULTICAST), false); |
| return true; |
| } |
| |
| bool HasValidNetworkType(nw_interface_t interface) |
| { |
| auto interfaceType = nw_interface_get_type(interface); |
| return interfaceType == nw_interface_type_wifi || interfaceType == nw_interface_type_wired || |
| interfaceType == nw_interface_type_other; |
| } |
| |
| bool IsValidInterfaceId(uint32_t targetInterfaceId, nw_interface_t interface) |
| { |
| auto currentInterfaceId = nw_interface_get_index(interface); |
| return targetInterfaceId == kDNSServiceInterfaceIndexAny || targetInterfaceId == currentInterfaceId; |
| } |
| |
| void ShouldUseVersion(chip::Inet::IPAddressType addressType, bool & shouldUseIPv4, bool & shouldUseIPv6) |
| { |
| #if INET_CONFIG_ENABLE_IPV4 |
| shouldUseIPv4 = addressType == Inet::IPAddressType::kIPv4 || addressType == Inet::IPAddressType::kAny; |
| #else |
| shouldUseIPv4 = false; |
| #endif // INET_CONFIG_ENABLE_IPV4 |
| shouldUseIPv6 = addressType == Inet::IPAddressType::kIPv6 || addressType == Inet::IPAddressType::kAny; |
| } |
| |
| static void OnRegisterRecord(DNSServiceRef sdRef, DNSRecordRef recordRef, DNSServiceFlags flags, DNSServiceErrorType err, |
| void * context) |
| { |
| ChipLogProgress(Discovery, "Mdns: %s flags: %d", __func__, flags); |
| if (kDNSServiceErr_NoError != err) |
| { |
| ChipLogError(Discovery, "%s (%s)", __func__, Error::ToString(err)); |
| } |
| } |
| |
| void GetInterfaceAddresses(uint32_t interfaceId, chip::Inet::IPAddressType addressType, InetInterfacesVector & inetInterfaces, |
| Inet6InterfacesVector & inet6Interfaces, bool searchLoopbackOnly = false) |
| { |
| bool shouldUseIPv4, shouldUseIPv6; |
| ShouldUseVersion(addressType, shouldUseIPv4, shouldUseIPv6); |
| |
| ifaddrs * ifap; |
| VerifyOrReturn(getifaddrs(&ifap) >= 0); |
| |
| for (struct ifaddrs * ifa = ifap; ifa != nullptr; ifa = ifa->ifa_next) |
| { |
| auto interfaceAddress = ifa->ifa_addr; |
| if (interfaceAddress == nullptr) |
| { |
| continue; |
| } |
| |
| if (!HasValidFlags(ifa->ifa_flags, searchLoopbackOnly)) |
| { |
| continue; |
| } |
| |
| auto currentInterfaceId = if_nametoindex(ifa->ifa_name); |
| if (interfaceId != kDNSServiceInterfaceIndexAny && interfaceId != currentInterfaceId) |
| { |
| continue; |
| } |
| |
| if (shouldUseIPv4 && (AF_INET == interfaceAddress->sa_family)) |
| { |
| auto inetAddress = reinterpret_cast<struct sockaddr_in *>(interfaceAddress)->sin_addr; |
| inetInterfaces.push_back(InetInterface(currentInterfaceId, inetAddress)); |
| } |
| else if (shouldUseIPv6 && (AF_INET6 == interfaceAddress->sa_family)) |
| { |
| auto inet6Address = reinterpret_cast<struct sockaddr_in6 *>(interfaceAddress)->sin6_addr; |
| inet6Interfaces.push_back(Inet6Interface(currentInterfaceId, inet6Address)); |
| } |
| } |
| |
| freeifaddrs(ifap); |
| } |
| } // namespace |
| |
| HostNameRegistrar::~HostNameRegistrar() |
| { |
| Unregister(); |
| if (mLivenessTracker != nullptr) |
| { |
| *mLivenessTracker = false; |
| } |
| } |
| |
| DNSServiceErrorType HostNameRegistrar::Init(const char * hostname, Inet::IPAddressType addressType, uint32_t interfaceId) |
| { |
| mHostname = hostname; |
| mInterfaceId = interfaceId; |
| mAddressType = addressType; |
| mServiceRef = nullptr; |
| mInterfaceMonitor = nullptr; |
| |
| mLivenessTracker = std::make_shared<bool>(true); |
| if (mLivenessTracker == nullptr) |
| { |
| return kDNSServiceErr_NoMemory; |
| } |
| |
| return kDNSServiceErr_NoError; |
| } |
| |
| CHIP_ERROR HostNameRegistrar::Register() |
| { |
| // If the target interface is kDNSServiceInterfaceIndexLocalOnly, just |
| // register the loopback addresses. |
| if (IsLocalOnly()) |
| { |
| ReturnErrorOnFailure(ResetSharedConnection()); |
| |
| InetInterfacesVector inetInterfaces; |
| Inet6InterfacesVector inet6Interfaces; |
| // Instead of mInterfaceId (which will not match any actual interface), |
| // use kDNSServiceInterfaceIndexAny and restrict to loopback interfaces. |
| GetInterfaceAddresses(kDNSServiceInterfaceIndexAny, mAddressType, inetInterfaces, inet6Interfaces, |
| true /* searchLoopbackOnly */); |
| |
| // But we register the IPs with mInterfaceId, not the actual interface |
| // IDs, so that resolution code that is grouping addresses by interface |
| // ends up doing the right thing, since we registered our SRV record on |
| // mInterfaceId. |
| // |
| // And only register the IPv6 ones, for simplicity. |
| for (auto & interface : inet6Interfaces) |
| { |
| ReturnErrorOnFailure(RegisterInterface(mInterfaceId, interface.second, kDNSServiceType_AAAA)); |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| return StartMonitorInterfaces(^(InetInterfacesVector inetInterfaces, Inet6InterfacesVector inet6Interfaces) { |
| ReturnOnFailure(ResetSharedConnection()); |
| RegisterInterfaces(inetInterfaces, kDNSServiceType_A); |
| RegisterInterfaces(inet6Interfaces, kDNSServiceType_AAAA); |
| }); |
| } |
| |
| CHIP_ERROR HostNameRegistrar::RegisterInterface(uint32_t interfaceId, uint16_t rtype, const void * rdata, uint16_t rdlen) |
| { |
| DNSRecordRef dnsRecordRef; |
| auto err = DNSServiceRegisterRecord(mServiceRef, &dnsRecordRef, kRegisterRecordFlags, interfaceId, mHostname.c_str(), rtype, |
| kDNSServiceClass_IN, rdlen, rdata, 0, OnRegisterRecord, nullptr); |
| return Error::ToChipError(err); |
| } |
| |
| void HostNameRegistrar::Unregister() |
| { |
| if (!IsLocalOnly()) |
| { |
| StopMonitorInterfaces(); |
| } |
| StopSharedConnection(); |
| } |
| |
| CHIP_ERROR HostNameRegistrar::StartMonitorInterfaces(OnInterfaceChanges interfaceChangesBlock) |
| { |
| mInterfaceMonitor = nw_path_monitor_create(); |
| VerifyOrReturnError(mInterfaceMonitor != nullptr, CHIP_ERROR_NO_MEMORY); |
| |
| nw_path_monitor_set_queue(mInterfaceMonitor, chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue()); |
| |
| // Our update handler closes over "this", but can't keep us alive (because we |
| // are not refcounted). Make sure it closes over a shared ref to our |
| // liveness tracker, which it _can_ keep alive, so it can bail out if we |
| // have been destroyed between when the task was queued and when it ran. |
| std::shared_ptr<bool> livenessTracker = mLivenessTracker; |
| nw_path_monitor_set_update_handler(mInterfaceMonitor, ^(nw_path_t path) { |
| if (!*livenessTracker) |
| { |
| // The HostNameRegistrar has been destroyed; just bail out. |
| return; |
| } |
| |
| #if CHIP_PROGRESS_LOGGING |
| LogDetails(path); |
| #endif // CHIP_PROGRESS_LOGGING |
| |
| __block InetInterfacesVector inet; |
| __block Inet6InterfacesVector inet6; |
| |
| // The loopback interfaces needs to be manually added. While lo0 is usually 1, this is not guaranteed. So search for a |
| // loopback interface with the specified interface id. If the specified interface id is kDNSServiceInterfaceIndexAny, it |
| // will look for all available loopback interfaces. |
| GetInterfaceAddresses(mInterfaceId, mAddressType, inet, inet6, true /* searchLoopbackOnly */); |
| #if CHIP_PROGRESS_LOGGING |
| LogDetails(inet, inet6); |
| #endif // CHIP_PROGRESS_LOGGING |
| |
| auto status = nw_path_get_status(path); |
| if (status == nw_path_status_satisfied) |
| { |
| nw_path_enumerate_interfaces(path, ^(nw_interface_t interface) { |
| VerifyOrReturnValue(HasValidNetworkType(interface), true); |
| VerifyOrReturnValue(IsValidInterfaceId(mInterfaceId, interface), true); |
| |
| auto targetInterfaceId = nw_interface_get_index(interface); |
| GetInterfaceAddresses(targetInterfaceId, mAddressType, inet, inet6); |
| #if CHIP_PROGRESS_LOGGING |
| LogDetails(interface, inet, inet6); |
| #endif // CHIP_PROGRESS_LOGGING |
| return true; |
| }); |
| } |
| |
| interfaceChangesBlock(inet, inet6); |
| }); |
| |
| nw_path_monitor_start(mInterfaceMonitor); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void HostNameRegistrar::StopMonitorInterfaces() |
| { |
| if (mInterfaceMonitor != nullptr) |
| { |
| nw_path_monitor_cancel(mInterfaceMonitor); |
| nw_release(mInterfaceMonitor); |
| mInterfaceMonitor = nullptr; |
| } |
| } |
| |
| CHIP_ERROR HostNameRegistrar::StartSharedConnection() |
| { |
| VerifyOrReturnError(mServiceRef == nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| auto err = DNSServiceCreateConnection(&mServiceRef); |
| VerifyOrReturnValue(kDNSServiceErr_NoError == err, Error::ToChipError(err)); |
| |
| err = DNSServiceSetDispatchQueue(mServiceRef, chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue()); |
| if (kDNSServiceErr_NoError != err) |
| { |
| StopSharedConnection(); |
| } |
| |
| return Error::ToChipError(err); |
| } |
| |
| void HostNameRegistrar::StopSharedConnection() |
| { |
| if (mServiceRef != nullptr) |
| { |
| // All the DNSRecordRefs registered to the shared DNSServiceRef will be deallocated. |
| DNSServiceRefDeallocate(mServiceRef); |
| mServiceRef = nullptr; |
| } |
| } |
| |
| CHIP_ERROR HostNameRegistrar::ResetSharedConnection() |
| { |
| StopSharedConnection(); |
| ReturnLogErrorOnFailure(StartSharedConnection()); |
| return CHIP_NO_ERROR; |
| } |
| |
| } // namespace Dnssd |
| } // namespace chip |