Skip to content

Commit f08b6d7

Browse files
committed
[Darwin] MTRDevice should trigger resubscription on connectivity changes
1 parent 8a4dffc commit f08b6d7

6 files changed

+342
-0
lines changed

src/darwin/Framework/CHIP/MTRDevice.mm

+49
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#import "MTRCommandTimedCheck.h"
2929
#import "MTRConversion.h"
3030
#import "MTRDefines_Internal.h"
31+
#import "MTRDeviceConnectivityMonitor.h"
3132
#import "MTRDeviceControllerOverXPC.h"
3233
#import "MTRDeviceController_Internal.h"
3334
#import "MTRDevice_Internal.h"
@@ -46,6 +47,7 @@
4647
#include <app/BufferedReadCallback.h>
4748
#include <app/ClusterStateCache.h>
4849
#include <app/InteractionModelEngine.h>
50+
#include <lib/dnssd/ServiceNaming.h>
4951
#include <platform/LockTracker.h>
5052
#include <platform/PlatformManager.h>
5153

@@ -355,6 +357,7 @@ @implementation MTRDevice {
355357
// _setupSubscription or via the auto-resubscribe behavior of the
356358
// ReadClient). Nil if we have had no such failures.
357359
NSDate * _Nullable _lastSubscriptionFailureTime;
360+
MTRDeviceConnectivityMonitor * _connectivityMonitor;
358361
}
359362

360363
- (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller
@@ -669,6 +672,9 @@ - (void)invalidate
669672
// subscription. In that case, _internalDeviceState will update when the
670673
// subscription is actually terminated.
671674

675+
[_connectivityMonitor stopMonitoring];
676+
_connectivityMonitor = nil;
677+
672678
os_unfair_lock_unlock(&self->_lock);
673679
}
674680

@@ -861,6 +867,10 @@ - (void)_handleSubscriptionEstablished
861867

862868
[self _changeState:MTRDeviceStateReachable];
863869

870+
// No need to monitor connectivity after subscription establishment
871+
[_connectivityMonitor stopMonitoring];
872+
_connectivityMonitor = nil;
873+
864874
os_unfair_lock_unlock(&self->_lock);
865875

866876
os_unfair_lock_lock(&self->_timeSyncLock);
@@ -894,6 +904,9 @@ - (void)_handleResubscriptionNeeded
894904
// former case we recently had a subscription and do not want to be forcing
895905
// retries immediately.
896906
_lastSubscriptionFailureTime = [NSDate now];
907+
908+
// Set up connectivity monitoring in case network routability changes for the positive, to accellerate resubscription
909+
[self _setupConnectivityMonitoring];
897910
}
898911

899912
- (void)_handleSubscriptionReset:(NSNumber * _Nullable)retryDelay
@@ -1241,6 +1254,39 @@ - (void)_createDataVersionFilterListFromDictionary:(NSDictionary<MTRClusterPath
12411254
*count = maxDataVersionFilterSize;
12421255
}
12431256

1257+
- (void)_setupConnectivityMonitoring
1258+
{
1259+
std::lock_guard lock(_lock);
1260+
1261+
if (_connectivityMonitor) {
1262+
// already monitoring
1263+
return;
1264+
}
1265+
1266+
// Get the required info before setting up the connectivity monitor
1267+
NSNumber * compressedFabricID = [_deviceController syncGetCompressedFabricID];
1268+
if (!compressedFabricID) {
1269+
MTR_LOG_INFO("%@ could not get compressed fabricID", self);
1270+
return;
1271+
}
1272+
1273+
char instanceName[chip::Dnssd::kMaxOperationalServiceNameSize];
1274+
chip::PeerId peerId(static_cast<chip::CompressedFabricId>(compressedFabricID.unsignedLongLongValue), static_cast<chip::NodeId>(_nodeID.unsignedLongLongValue));
1275+
CHIP_ERROR err = chip::Dnssd::MakeInstanceName(instanceName, sizeof(instanceName), peerId);
1276+
if (err != CHIP_NO_ERROR) {
1277+
MTR_LOG_ERROR("%@ could not make instance name", self);
1278+
return;
1279+
}
1280+
1281+
_connectivityMonitor = [[MTRDeviceConnectivityMonitor alloc] initWithInstanceName:[NSString stringWithUTF8String:instanceName]];
1282+
[_connectivityMonitor startMonitoringWithHandler:^{
1283+
[self->_deviceController asyncDispatchToMatterQueue:^{
1284+
[self _triggerResubscribeWithReason:"read-through skipped while not subscribed" nodeLikelyReachable:YES];
1285+
}
1286+
errorHandler:nil];
1287+
} queue:_queue];
1288+
}
1289+
12441290
// assume lock is held
12451291
- (void)_setupSubscription
12461292
{
@@ -1462,6 +1508,9 @@ - (void)_setupSubscription
14621508
callback->AdoptClusterStateCache(std::move(clusterStateCache));
14631509
callback.release();
14641510
}];
1511+
1512+
// Set up connectivity monitoring in case network becomes routable after any part of the subscription process goes into backoff retries.
1513+
[self _setupConnectivityMonitoring];
14651514
}
14661515

14671516
#ifdef DEBUG
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Copyright (c) 2023 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
19+
NS_ASSUME_NONNULL_BEGIN
20+
21+
typedef void (^MTRDeviceConnectivityMonitorHandler)(void);
22+
23+
@interface MTRDeviceConnectivityMonitor : NSObject
24+
- (instancetype)initWithInstanceName:(NSString *)instanceName;
25+
- (void)startMonitoringWithHandler:(MTRDeviceConnectivityMonitorHandler)handler queue:(dispatch_queue_t)queue;
26+
- (void)stopMonitoring;
27+
@end
28+
29+
NS_ASSUME_NONNULL_END
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/**
2+
* Copyright (c) 2023 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "MTRDeviceConnectivityMonitor.h"
18+
#import "MTRLogging_Internal.h"
19+
#import "MTRUnfairLock.h"
20+
21+
#import <Network/Network.h>
22+
#import <dns_sd.h>
23+
#import <os/lock.h>
24+
25+
@interface MTRDeviceConnectivityMonitor ()
26+
- (void)handleResolvedHostname:(const char *)hostName port:(uint16_t)port error:(DNSServiceErrorType)error;
27+
@end
28+
29+
@implementation MTRDeviceConnectivityMonitor {
30+
NSString * _instanceName;
31+
DNSServiceRef _resolver;
32+
NSMutableDictionary<NSString *, nw_connection_t> * _connections;
33+
34+
MTRDeviceConnectivityMonitorHandler _monitorHandler;
35+
dispatch_queue_t _handlerQueue;
36+
}
37+
38+
namespace {
39+
constexpr char kLocalDot[] = "local.";
40+
constexpr char kOperationalType[] = "_matter._tcp";
41+
}
42+
43+
static dispatch_once_t sConnecitivityMonitorOnceToken;
44+
static os_unfair_lock sConnectivityMonitorLock;
45+
static NSUInteger sConnectivityMonitorCount;
46+
static DNSServiceRef sSharedResolverConnection;
47+
static dispatch_queue_t sSharedResolverQueue;
48+
49+
- (instancetype)initWithInstanceName:(NSString *)instanceName
50+
{
51+
if (self = [super init]) {
52+
dispatch_once(&sConnecitivityMonitorOnceToken, ^{
53+
sConnectivityMonitorLock = OS_UNFAIR_LOCK_INIT;
54+
});
55+
_instanceName = [instanceName copy];
56+
_connections = [NSMutableDictionary dictionary];
57+
}
58+
return self;
59+
}
60+
61+
- (void)dealloc
62+
{
63+
if (_resolver) {
64+
DNSServiceRefDeallocate(_resolver);
65+
}
66+
}
67+
68+
- (NSString *)description
69+
{
70+
return [NSString stringWithFormat:@"<MTRDeviceConnectivityMonitor: %@>", _instanceName];
71+
}
72+
73+
+ (DNSServiceRef)_sharedResolverConnection
74+
{
75+
os_unfair_lock_assert_owner(&sConnectivityMonitorLock);
76+
77+
if (!sSharedResolverConnection) {
78+
DNSServiceErrorType dnsError = DNSServiceCreateConnection(&sSharedResolverConnection);
79+
if (dnsError) {
80+
MTR_LOG_ERROR("MTRDeviceConnectivityMonitor: DNSServiceCreateConnection failed %d", dnsError);
81+
return NULL;
82+
}
83+
sSharedResolverQueue = dispatch_queue_create("MTRDeviceConnectivityMonitor", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
84+
dnsError = DNSServiceSetDispatchQueue(sSharedResolverConnection, sSharedResolverQueue);
85+
if (dnsError != kDNSServiceErr_NoError) {
86+
MTR_LOG_ERROR("%@ cannot set dispatch queue on resolve", self);
87+
DNSServiceRefDeallocate(sSharedResolverConnection);
88+
sSharedResolverConnection = NULL;
89+
sSharedResolverQueue = nil;
90+
return NULL;
91+
}
92+
}
93+
94+
return sSharedResolverConnection;
95+
}
96+
97+
- (void)_callHandler
98+
{
99+
os_unfair_lock_assert_owner(&sConnectivityMonitorLock);
100+
MTRDeviceConnectivityMonitorHandler handlerToCall = self->_monitorHandler;
101+
if (handlerToCall) {
102+
dispatch_async(self->_handlerQueue, ^{ handlerToCall(); });
103+
}
104+
}
105+
106+
- (void)handleResolvedHostname:(const char *)hostName port:(uint16_t)port error:(DNSServiceErrorType)error
107+
{
108+
std::lock_guard lock(sConnectivityMonitorLock);
109+
110+
// dns_sd.h: must check and call deallocate if error is kDNSServiceErr_ServiceNotRunning
111+
if (error == kDNSServiceErr_ServiceNotRunning) {
112+
MTR_LOG_ERROR("%@ disconnected from dns-sd subsystem", self);
113+
[self _stopMonitoring];
114+
return;
115+
}
116+
117+
// Create a nw_connection to monitor connectivity if the host name is not being monitored yet
118+
NSString * hostNameString = [NSString stringWithUTF8String:hostName];
119+
if (!_connections[hostNameString]) {
120+
char portString[6];
121+
snprintf(portString, sizeof(portString), "%d", ntohs(port));
122+
nw_endpoint_t endpoint = nw_endpoint_create_host(hostName, portString);
123+
if (!endpoint) {
124+
MTR_LOG_ERROR("%@ failed to create endpoint for %s:%s", self, hostName, portString);
125+
return;
126+
}
127+
nw_parameters_t params = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION);
128+
if (!params) {
129+
MTR_LOG_ERROR("%@ failed to create udp parameters", self);
130+
return;
131+
}
132+
nw_connection_t connection = nw_connection_create(endpoint, params);
133+
if (!connection) {
134+
MTR_LOG_ERROR("%@ failed to create connection for %s:%s", self, hostName, portString);
135+
return;
136+
}
137+
nw_connection_set_queue(connection, sSharedResolverQueue);
138+
nw_connection_set_path_changed_handler(connection, ^(nw_path_t _Nonnull path) {
139+
nw_path_status_t status = nw_path_get_status(path);
140+
if (status == nw_path_status_satisfied) {
141+
MTR_LOG_INFO("%@ path is satisfied", self);
142+
std::lock_guard lock(sConnectivityMonitorLock);
143+
[self _callHandler];
144+
}
145+
});
146+
nw_connection_set_viability_changed_handler(connection, ^(bool viable) {
147+
if (viable) {
148+
std::lock_guard lock(sConnectivityMonitorLock);
149+
MTR_LOG_INFO("%@ connectivity now viable", self);
150+
[self _callHandler];
151+
}
152+
});
153+
nw_connection_start(connection);
154+
}
155+
}
156+
157+
static void _resolveReplyCallback(
158+
DNSServiceRef sdRef,
159+
DNSServiceFlags flags,
160+
uint32_t interfaceIndex,
161+
DNSServiceErrorType errorCode,
162+
const char * fullname,
163+
const char * hosttarget,
164+
uint16_t port, /* In network byte order */
165+
uint16_t txtLen,
166+
const unsigned char * txtRecord,
167+
void * context)
168+
{
169+
auto * connectivityMonitor = (__bridge MTRDeviceConnectivityMonitor *) context;
170+
[connectivityMonitor handleResolvedHostname:hosttarget port:port error:errorCode];
171+
}
172+
173+
- (void)startMonitoringWithHandler:(MTRDeviceConnectivityMonitorHandler)handler queue:(dispatch_queue_t)queue
174+
{
175+
std::lock_guard lock(sConnectivityMonitorLock);
176+
177+
MTRDeviceConnectivityMonitorHandler handlerCopy = [handler copy];
178+
_monitorHandler = handlerCopy;
179+
_handlerQueue = queue;
180+
181+
// If there's already a resolver running, just return
182+
if (_resolver) {
183+
MTR_LOG_INFO("%@ connectivity monitor updated handler", self);
184+
return;
185+
}
186+
187+
MTR_LOG_INFO("%@ start connectivity monitoring for %@ (%lu monitoring objects)", self, _instanceName, static_cast<unsigned long>(sConnectivityMonitorCount));
188+
189+
_resolver = [MTRDeviceConnectivityMonitor _sharedResolverConnection];
190+
if (!_resolver) {
191+
MTR_LOG_ERROR("%@ failed to get shared resolver connection", self);
192+
return;
193+
}
194+
DNSServiceErrorType dnsError = DNSServiceResolve(&_resolver,
195+
kDNSServiceFlagsShareConnection,
196+
kDNSServiceInterfaceIndexAny,
197+
_instanceName.UTF8String,
198+
kOperationalType,
199+
kLocalDot,
200+
_resolveReplyCallback,
201+
(__bridge void *) self);
202+
if (dnsError != kDNSServiceErr_NoError) {
203+
MTR_LOG_ERROR("%@ failed to create resolver", self);
204+
return;
205+
}
206+
207+
sConnectivityMonitorCount++;
208+
}
209+
210+
#define MTRDEVICECONNECTIVITYMONITOR_SHARED_CONNECTION_LINGER_INTERVAL (10)
211+
212+
- (void)_stopMonitoring
213+
{
214+
os_unfair_lock_assert_owner(&sConnectivityMonitorLock);
215+
for (NSString * hostName in _connections) {
216+
nw_connection_cancel(_connections[hostName]);
217+
}
218+
[_connections removeAllObjects];
219+
220+
if (_resolver) {
221+
DNSServiceRefDeallocate(_resolver);
222+
_resolver = NULL;
223+
224+
// If no monitor objects exist, schedule to deallocate shared connection and queue
225+
sConnectivityMonitorCount--;
226+
if (!sConnectivityMonitorCount) {
227+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (MTRDEVICECONNECTIVITYMONITOR_SHARED_CONNECTION_LINGER_INTERVAL * NSEC_PER_SEC)), sSharedResolverQueue, ^{
228+
std::lock_guard lock(sConnectivityMonitorLock);
229+
230+
if (!sConnectivityMonitorCount) {
231+
MTR_LOG_INFO("%@ Closing shared resolver connection", self);
232+
DNSServiceRefDeallocate(sSharedResolverConnection);
233+
sSharedResolverConnection = NULL;
234+
sSharedResolverQueue = nil;
235+
}
236+
});
237+
}
238+
}
239+
}
240+
241+
- (void)stopMonitoring
242+
{
243+
MTR_LOG_INFO("%@ stop connectivity monitoring for %@", self, _instanceName);
244+
std::lock_guard lock(sConnectivityMonitorLock);
245+
[self _stopMonitoring];
246+
}
247+
@end

src/darwin/Framework/CHIP/MTRDeviceController.mm

+7
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,13 @@ - (nullable NSNumber *)compressedFabricID
13681368
return @(_cppCommissioner->GetCompressedFabricId());
13691369
}
13701370

1371+
- (NSNumber * _Nullable)syncGetCompressedFabricID
1372+
{
1373+
return [self syncRunOnWorkQueueWithReturnValue:^NSNumber * {
1374+
return [self compressedFabricID];
1375+
} error:nil];
1376+
}
1377+
13711378
- (CHIP_ERROR)isRunningOnFabric:(chip::FabricTable *)fabricTable
13721379
fabricIndex:(chip::FabricIndex)fabricIndex
13731380
isRunning:(BOOL *)isRunning

0 commit comments

Comments
 (0)