| Flutter iOS Embedder |

FlutterDartVMServicePublisher.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #define FML_USED_ON_EMBEDDER
6
7 #import "FlutterDartVMServicePublisher.h"
8
9 #if FLUTTER_RELEASE
10
11 @implementation FlutterDartVMServicePublisher
12 - (instancetype)initWithEnableVMServicePublication:(BOOL)enableVMServicePublication {
13 return [super init];
14 }
15 @end
16
17 #else // FLUTTER_RELEASE
18
19 #import <TargetConditionals.h>
20 // NSNetService works fine on physical devices before iOS 13.2.
21 // However, it doesn't expose the services to regular mDNS
22 // queries on the Simulator or on iOS 13.2+ devices.
23 //
24 // When debugging issues with this implementation, the following is helpful:
25 //
26 // 1) Running `dns-sd -Z _dartVmService`. This is a built-in macOS tool that
27 // can find advertized observatories using this method. If dns-sd can't find
28 // it, then the VM service is not getting advertised over any network
29 // interface that the host machine has access to.
30 // 2) The Python zeroconf package. The dns-sd tool can sometimes see things
31 // that aren't advertizing over a network interface - for example, simulators
32 // using NSNetService has been observed using dns-sd, but doesn't show up in
33 // the Python package (which is a high quality socket based implementation).
34 // If that happens, this code should be tweaked such that it shows up in both
35 // dns-sd's output and Python zeroconf's detection.
36 // 3) The Dart multicast_dns package, which is what Flutter uses to find the
37 // port and auth code. If the advertizement shows up in dns-sd and Python
38 // zeroconf but not multicast_dns, then it is a bug in multicast_dns.
39 #include <dns_sd.h>
40 #include <net/if.h>
41
42 #include "flutter/fml/logging.h"
43 #include "flutter/fml/memory/weak_ptr.h"
44 #include "flutter/fml/message_loop.h"
45 #include "flutter/fml/platform/darwin/scoped_nsobject.h"
46 #include "flutter/runtime/dart_service_isolate.h"
47
48 @protocol FlutterDartVMServicePublisherDelegate
49 - (void)publishServiceProtocolPort:(NSURL*)uri;
50 - (void)stopService;
51 @end
52
53 @interface FlutterDartVMServicePublisher ()
54 + (NSData*)createTxtData:(NSURL*)url;
55
56 @property(readonly, class) NSString* serviceName;
57 @property(readonly) fml::scoped_nsobject<NSObject<FlutterDartVMServicePublisherDelegate>> delegate;
58 @property(nonatomic, readwrite) NSURL* url;
59 @property(readonly) BOOL enableVMServicePublication;
60
61 @end
62
63 @interface DartVMServiceDNSServiceDelegate : NSObject <FlutterDartVMServicePublisherDelegate>
64 @end
65
66 @implementation DartVMServiceDNSServiceDelegate {
67 DNSServiceRef _dnsServiceRef;
68 DNSServiceRef _legacyDnsServiceRef;
69 }
70
71 - (void)stopService {
72 if (_dnsServiceRef) {
73 DNSServiceRefDeallocate(_dnsServiceRef);
74 _dnsServiceRef = NULL;
75 }
76 if (_legacyDnsServiceRef) {
77 DNSServiceRefDeallocate(_legacyDnsServiceRef);
78 _legacyDnsServiceRef = NULL;
79 }
80 }
81
82 - (void)publishServiceProtocolPort:(NSURL*)url {
83 // TODO(vashworth): Remove once done debugging https://github.com/flutter/flutter/issues/129836
84 FML_LOG(INFO) << "Publish Service Protocol Port";
85 DNSServiceFlags flags = kDNSServiceFlagsDefault;
86 #if TARGET_IPHONE_SIMULATOR
87 // Simulator needs to use local loopback explicitly to work.
88 uint32_t interfaceIndex = if_nametoindex("lo0");
89 #else // TARGET_IPHONE_SIMULATOR
90 // Physical devices need to request all interfaces.
91 uint32_t interfaceIndex = 0;
92 #endif // TARGET_IPHONE_SIMULATOR
93 const char* registrationType = "_dartVmService._tcp";
94 const char* legacyRegistrationType = "_dartobservatory._tcp";
95
96 const char* domain = "local."; // default domain
97 uint16_t port = [[url port] unsignedShortValue];
98
99 NSData* txtData = [FlutterDartVMServicePublisher createTxtData:url];
100 int err = DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex,
101 FlutterDartVMServicePublisher.serviceName.UTF8String,
102 registrationType, domain, NULL, htons(port), txtData.length,
103 txtData.bytes, RegistrationCallback, NULL);
104
105 if (err == 0) {
106 DNSServiceSetDispatchQueue(_dnsServiceRef, dispatch_get_main_queue());
107 return;
108 }
109
110 // TODO(bkonyi): remove once flutter_tools no longer looks for the legacy registration type.
111 // See https://github.com/dart-lang/sdk/issues/50233
112 //
113 // Try to fallback on the legacy registration type.
114 err = DNSServiceRegister(&_legacyDnsServiceRef, flags, interfaceIndex,
115 FlutterDartVMServicePublisher.serviceName.UTF8String,
116 legacyRegistrationType, domain, NULL, htons(port), txtData.length,
117 txtData.bytes, RegistrationCallback, NULL);
118
119 if (err == 0) {
120 DNSServiceSetDispatchQueue(_legacyDnsServiceRef, dispatch_get_main_queue());
121 return;
122 }
123
124 FML_LOG(ERROR) << "Failed to register Dart VM Service port with mDNS with error " << err << ".";
125 if (@available(iOS 14.0, *)) {
126 FML_LOG(ERROR) << "On iOS 14+, local network broadcast in apps need to be declared in "
127 << "the app's Info.plist. Debug and profile Flutter apps and modules host "
128 << "VM services on the local network to support debugging features such "
129 << "as hot reload and DevTools. To make your Flutter app or module "
130 << "attachable and debuggable, add a '" << registrationType << "' value "
131 << "to the 'NSBonjourServices' key in your Info.plist for the Debug/"
132 << "Profile configurations. "
133 << "For more information, see "
134 << "https://flutter.dev/docs/development/add-to-app/ios/"
135 "project-setup#local-network-privacy-permissions";
136 }
137 }
138
139 static void DNSSD_API RegistrationCallback(DNSServiceRef sdRef,
140 DNSServiceFlags flags,
141 DNSServiceErrorType errorCode,
142 const char* name,
143 const char* regType,
144 const char* domain,
145 void* context) {
146 if (errorCode == kDNSServiceErr_NoError) {
147 FML_DLOG(INFO) << "FlutterDartVMServicePublisher is ready!";
148 } else if (errorCode == kDNSServiceErr_PolicyDenied) {
149 FML_LOG(ERROR)
150 << "Could not register as server for FlutterDartVMServicePublisher, permission "
151 << "denied. Check your 'Local Network' permissions for this app in the Privacy section of "
152 << "the system Settings.";
153 } else {
154 FML_LOG(ERROR) << "Could not register as server for FlutterDartVMServicePublisher. Check your "
155 "network settings and relaunch the application.";
156 }
157 }
158
159 @end
160
161 @implementation FlutterDartVMServicePublisher {
162 flutter::DartServiceIsolate::CallbackHandle _callbackHandle;
163 std::unique_ptr<fml::WeakPtrFactory<FlutterDartVMServicePublisher>> _weakFactory;
164 }
165
166 - (instancetype)initWithEnableVMServicePublication:(BOOL)enableVMServicePublication {
167 self = [super init];
168 NSAssert(self, @"Super must not return null on init.");
169
170 _delegate.reset([[DartVMServiceDNSServiceDelegate alloc] init]);
171 _enableVMServicePublication = enableVMServicePublication;
172 _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterDartVMServicePublisher>>(self);
173
174 fml::MessageLoop::EnsureInitializedForCurrentThread();
175
176 _callbackHandle = flutter::DartServiceIsolate::AddServerStatusCallback(
177 [weak = _weakFactory->GetWeakPtr(),
178 runner = fml::MessageLoop::GetCurrent().GetTaskRunner()](const std::string& uri) {
179 if (!uri.empty()) {
180 runner->PostTask([weak, uri]() {
181 // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port
182 // number.
183 if (weak) {
184 NSURL* url = [[[NSURL alloc]
185 initWithString:[NSString stringWithUTF8String:uri.c_str()]] autorelease];
186 weak.get().url = url;
187 if (weak.get().enableVMServicePublication) {
188 [[weak.get() delegate] publishServiceProtocolPort:url];
189 }
190 }
191 });
192 }
193 });
194
195 return self;
196 }
197
198 + (NSString*)serviceName {
199 return NSBundle.mainBundle.bundleIdentifier;
200 }
201
202 + (NSData*)createTxtData:(NSURL*)url {
203 // Check to see if there's an authentication code. If there is, we'll provide
204 // it as a txt record so flutter tools can establish a connection.
205 NSString* path = [[url path] substringFromIndex:MIN(1, [[url path] length])];
206 NSData* pathData = [path dataUsingEncoding:NSUTF8StringEncoding];
207 NSDictionary<NSString*, NSData*>* txtDict = @{
208 @"authCode" : pathData,
209 };
210 return [NSNetService dataFromTXTRecordDictionary:txtDict];
211 }
212
213 - (void)dealloc {
214 // It will be destroyed and invalidate its weak pointers
215 // before any other members are destroyed.
216 _weakFactory.reset();
217
218 [_delegate stopService];
219 [_url release];
220
221 flutter::DartServiceIsolate::RemoveServerStatusCallback(_callbackHandle);
222 [super dealloc];
223 }
224 @end
225
226 #endif // FLUTTER_RELEASE
FlutterDartVMServicePublisher::url
NSURL * url
Definition: FlutterDartVMServicePublisher.h:17
FlutterDartVMServicePublisher.h
FlutterDartVMServicePublisherDelegate-p
Definition: FlutterDartVMServicePublisher.mm:48
-[FlutterDartVMServicePublisherDelegate-p stopService]
void stopService()
_weakFactory
std::unique_ptr< fml::WeakPtrFactory< FlutterDartVMServicePublisher > > _weakFactory
Definition: FlutterDartVMServicePublisher.mm:161
_legacyDnsServiceRef
DNSServiceRef _legacyDnsServiceRef
Definition: FlutterDartVMServicePublisher.mm:66
DartVMServiceDNSServiceDelegate
Definition: FlutterDartVMServicePublisher.mm:63
FlutterDartVMServicePublisher
Definition: FlutterDartVMServicePublisher.h:10