blob: 229d95d9206bc17ce5fd80afdd89a165bbfc5726 [file] [log] [blame]
/**
* 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);
mtr_weakify(self);
nw_connection_set_path_changed_handler(connection, ^(nw_path_t _Nonnull path) {
mtr_strongify(self);
if (self) {
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) {
mtr_strongify(self);
if (self) {
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