| /** |
| * Copyright (c) 2023 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. |
| */ |
| |
| #import "MTRDeviceConnectivityMonitor.h" |
| #import "MTRLogging_Internal.h" |
| #import "MTRUnfairLock.h" |
| |
| #import <Network/Network.h> |
| #import <dns_sd.h> |
| #import <os/lock.h> |
| |
| #include <lib/dnssd/ServiceNaming.h> |
| #include <lib/support/CodeUtils.h> |
| #include <vector> |
| |
| @implementation MTRDeviceConnectivityMonitor { |
| NSString * _instanceName; |
| std::vector<DNSServiceRef> _resolvers; |
| NSMutableDictionary<NSString *, nw_connection_t> * _connections; |
| |
| MTRDeviceConnectivityMonitorHandler _monitorHandler; |
| dispatch_queue_t _handlerQueue; |
| } |
| |
| namespace { |
| constexpr const char * kResolveDomains[] = { |
| "default.service.arpa.", // SRP |
| "local.", |
| }; |
| constexpr char kOperationalType[] = "_matter._tcp"; |
| constexpr int64_t kSharedConnectionLingerIntervalSeconds = (10); |
| } |
| |
| static os_unfair_lock sConnectivityMonitorLock = OS_UNFAIR_LOCK_INIT; |
| static NSUInteger sConnectivityMonitorCount; |
| static DNSServiceRef sSharedResolverConnection; |
| static dispatch_queue_t sSharedResolverQueue; |
| |
| - (instancetype)initWithInstanceName:(NSString *)instanceName |
| { |
| if (self = [super init]) { |
| _instanceName = [instanceName copy]; |
| _connections = [NSMutableDictionary dictionary]; |
| } |
| return self; |
| } |
| |
| - (instancetype)initWithCompressedFabricID:(NSNumber *)compressedFabricID nodeID:(NSNumber *)nodeID |
| { |
| char instanceName[chip::Dnssd::kMaxOperationalServiceNameSize]; |
| chip::PeerId peerId(static_cast<chip::CompressedFabricId>(compressedFabricID.unsignedLongLongValue), static_cast<chip::NodeId>(nodeID.unsignedLongLongValue)); |
| CHIP_ERROR err = chip::Dnssd::MakeInstanceName(instanceName, sizeof(instanceName), peerId); |
| if (err != CHIP_NO_ERROR) { |
| MTR_LOG_ERROR("%@ could not make instance name", self); |
| return nil; |
| } |
| |
| return [self initWithInstanceName:[NSString stringWithUTF8String:instanceName]]; |
| } |
| |
| - (void)dealloc |
| { |
| for (auto & resolver : _resolvers) { |
| DNSServiceRefDeallocate(resolver); |
| } |
| } |
| |
| - (NSString *)description |
| { |
| return [NSString stringWithFormat:@"<MTRDeviceConnectivityMonitor: %@>", _instanceName]; |
| } |
| |
| + (DNSServiceRef)_sharedResolverConnection |
| { |
| os_unfair_lock_assert_owner(&sConnectivityMonitorLock); |
| |
| if (!sSharedResolverConnection) { |
| DNSServiceErrorType dnsError = DNSServiceCreateConnection(&sSharedResolverConnection); |
| if (dnsError != kDNSServiceErr_NoError) { |
| MTR_LOG_ERROR("MTRDeviceConnectivityMonitor: DNSServiceCreateConnection failed %d", dnsError); |
| return NULL; |
| } |
| sSharedResolverQueue = dispatch_queue_create("MTRDeviceConnectivityMonitor", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); |
| dnsError = DNSServiceSetDispatchQueue(sSharedResolverConnection, sSharedResolverQueue); |
| if (dnsError != kDNSServiceErr_NoError) { |
| MTR_LOG_ERROR("%@ cannot set dispatch queue on resolve", self); |
| DNSServiceRefDeallocate(sSharedResolverConnection); |
| sSharedResolverConnection = NULL; |
| sSharedResolverQueue = nil; |
| return NULL; |
| } |
| } |
| |
| return sSharedResolverConnection; |
| } |
| |
| - (void)_callHandler |
| { |
| os_unfair_lock_assert_owner(&sConnectivityMonitorLock); |
| MTRDeviceConnectivityMonitorHandler handlerToCall = self->_monitorHandler; |
| if (handlerToCall) { |
| dispatch_async(self->_handlerQueue, handlerToCall); |
| } |
| } |
| |
| - (void)handleResolvedHostname:(const char *)hostName port:(uint16_t)port error:(DNSServiceErrorType)error |
| { |
| std::lock_guard lock(sConnectivityMonitorLock); |
| |
| // dns_sd.h: must check and call deallocate if error is kDNSServiceErr_ServiceNotRunning |
| if (error == kDNSServiceErr_ServiceNotRunning) { |
| MTR_LOG_ERROR("%@ disconnected from dns-sd subsystem", self); |
| [self _stopMonitoring]; |
| return; |
| } |
| |
| // Create a nw_connection to monitor connectivity if the host name is not being monitored yet |
| NSString * hostNameString = [NSString stringWithUTF8String:hostName]; |
| if (!_connections[hostNameString]) { |
| char portString[6]; |
| snprintf(portString, sizeof(portString), "%d", ntohs(port)); |
| nw_endpoint_t endpoint = nw_endpoint_create_host(hostName, portString); |
| if (!endpoint) { |
| MTR_LOG_ERROR("%@ failed to create endpoint for %s:%s", self, hostName, portString); |
| return; |
| } |
| nw_parameters_t params = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION); |
| if (!params) { |
| MTR_LOG_ERROR("%@ failed to create udp parameters", self); |
| return; |
| } |
| nw_connection_t connection = nw_connection_create(endpoint, params); |
| if (!connection) { |
| MTR_LOG_ERROR("%@ failed to create connection for %s:%s", self, hostName, portString); |
| return; |
| } |
| nw_connection_set_queue(connection, sSharedResolverQueue); |
| nw_connection_set_path_changed_handler(connection, ^(nw_path_t _Nonnull path) { |
| nw_path_status_t status = nw_path_get_status(path); |
| if (status == nw_path_status_satisfied) { |
| MTR_LOG_INFO("%@ path is satisfied", self); |
| std::lock_guard lock(sConnectivityMonitorLock); |
| [self _callHandler]; |
| } |
| }); |
| nw_connection_set_viability_changed_handler(connection, ^(bool viable) { |
| if (viable) { |
| std::lock_guard lock(sConnectivityMonitorLock); |
| MTR_LOG_INFO("%@ connectivity now viable", self); |
| [self _callHandler]; |
| } |
| }); |
| nw_connection_start(connection); |
| |
| _connections[hostNameString] = connection; |
| } |
| } |
| |
| static void ResolveCallback( |
| DNSServiceRef sdRef, |
| DNSServiceFlags flags, |
| uint32_t interfaceIndex, |
| DNSServiceErrorType errorCode, |
| const char * fullName, |
| const char * hostName, |
| uint16_t port, /* In network byte order */ |
| uint16_t txtLen, |
| const unsigned char * txtRecord, |
| void * context) |
| { |
| auto * connectivityMonitor = (__bridge MTRDeviceConnectivityMonitor *) context; |
| [connectivityMonitor handleResolvedHostname:hostName port:port error:errorCode]; |
| } |
| |
| - (void)startMonitoringWithHandler:(MTRDeviceConnectivityMonitorHandler)handler queue:(dispatch_queue_t)queue |
| { |
| std::lock_guard lock(sConnectivityMonitorLock); |
| |
| _monitorHandler = handler; |
| _handlerQueue = queue; |
| |
| // If there's already a resolver running, just return |
| if (_resolvers.size() != 0) { |
| MTR_LOG_INFO("%@ connectivity monitor already running", self); |
| return; |
| } |
| |
| MTR_LOG_INFO("%@ start connectivity monitoring for %@ (%lu monitoring objects)", self, _instanceName, static_cast<unsigned long>(sConnectivityMonitorCount)); |
| |
| auto sharedConnection = [MTRDeviceConnectivityMonitor _sharedResolverConnection]; |
| if (!sharedConnection) { |
| MTR_LOG_ERROR("%@ failed to get shared resolver connection", self); |
| return; |
| } |
| |
| for (auto domain : kResolveDomains) { |
| DNSServiceRef resolver = sharedConnection; |
| DNSServiceErrorType dnsError = DNSServiceResolve(&resolver, |
| kDNSServiceFlagsShareConnection, |
| kDNSServiceInterfaceIndexAny, |
| _instanceName.UTF8String, |
| kOperationalType, |
| domain, |
| ResolveCallback, |
| (__bridge void *) self); |
| if (dnsError == kDNSServiceErr_NoError) { |
| _resolvers.emplace_back(std::move(resolver)); |
| } else { |
| MTR_LOG_ERROR("%@ failed to create resolver for \"%s\" domain: %" PRId32, self, StringOrNullMarker(domain), dnsError); |
| } |
| } |
| |
| if (_resolvers.size() != 0) { |
| sConnectivityMonitorCount++; |
| } |
| } |
| |
| - (void)_stopMonitoring |
| { |
| os_unfair_lock_assert_owner(&sConnectivityMonitorLock); |
| for (NSString * hostName in _connections) { |
| nw_connection_cancel(_connections[hostName]); |
| } |
| [_connections removeAllObjects]; |
| |
| _monitorHandler = nil; |
| _handlerQueue = nil; |
| |
| if (_resolvers.size() != 0) { |
| for (auto & resolver : _resolvers) { |
| DNSServiceRefDeallocate(resolver); |
| } |
| _resolvers.clear(); |
| |
| // If no monitor objects exist, schedule to deallocate shared connection and queue |
| sConnectivityMonitorCount--; |
| if (!sConnectivityMonitorCount) { |
| dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kSharedConnectionLingerIntervalSeconds * NSEC_PER_SEC), sSharedResolverQueue, ^{ |
| std::lock_guard lock(sConnectivityMonitorLock); |
| |
| if (!sConnectivityMonitorCount) { |
| MTR_LOG_INFO("MTRDeviceConnectivityMonitor: Closing shared resolver connection"); |
| DNSServiceRefDeallocate(sSharedResolverConnection); |
| sSharedResolverConnection = NULL; |
| sSharedResolverQueue = nil; |
| } |
| }); |
| } |
| } |
| } |
| |
| - (void)stopMonitoring |
| { |
| MTR_LOG_INFO("%@ stop connectivity monitoring for %@", self, _instanceName); |
| std::lock_guard lock(sConnectivityMonitorLock); |
| [self _stopMonitoring]; |
| } |
| @end |