1 /*
2 * libiio - Library for interfacing industrial I/O (IIO) devices
3 *
4 * Copyright (C) 2020 Matej Kenda.
5 * Author: Matej Kenda <matejken<at>gmail.com>
6 * Robin Getz <robin.getz@analog.com>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * */
19
20 #include <CFNetwork/CFNetwork.h>
21
22 #include "iio-lock.h"
23 #include "iio-private.h"
24 #include "network.h"
25 #include "debug.h"
26
27 /*
28 Implementation for DNS SD discovery for macOS using CFNetServices.
29 */
30
new_discovery_data(struct dns_sd_discovery_data ** data)31 static int new_discovery_data(struct dns_sd_discovery_data **data)
32 {
33 struct dns_sd_discovery_data *d;
34
35 d = zalloc(sizeof(struct dns_sd_discovery_data));
36 if (!d)
37 return -ENOMEM;
38
39 *data = d;
40 return 0;
41 }
42
dnssd_free_discovery_data(struct dns_sd_discovery_data * d)43 void dnssd_free_discovery_data(struct dns_sd_discovery_data *d)
44 {
45 free(d->hostname);
46 free(d);
47 }
48
__cfnet_browser_cb(CFNetServiceBrowserRef browser,CFOptionFlags flags,CFTypeRef domainOrService,CFStreamError * error,void * info)49 static void __cfnet_browser_cb (
50 CFNetServiceBrowserRef browser,
51 CFOptionFlags flags,
52 CFTypeRef domainOrService,
53 CFStreamError* error,
54 void* info)
55 {
56 CFStreamError anError;
57
58 if ((flags & kCFNetServiceFlagIsDomain) != 0) {
59 IIO_ERROR("DNS SD: FATAL! Callback called for domain, not service.\n");
60 goto stop_browsing;
61 }
62
63 struct dns_sd_discovery_data *dd = (struct dns_sd_discovery_data *)info;
64 if (dd == NULL) {
65 IIO_ERROR("DNS SD: Missing info structure. Stop browsing.\n");
66 goto stop_browsing;
67 }
68
69 if ((flags & kCFNetServiceFlagRemove) != 0) {
70 IIO_DEBUG("DNS SD: Callback to remove service. Ignore.\n");
71 return;
72 }
73
74 iio_mutex_lock(dd->lock);
75
76 const CFNetServiceRef netService = (CFNetServiceRef)domainOrService;
77 if (netService == NULL) {
78 IIO_DEBUG("DNS SD: Net service is null.\n");
79 goto verify_flags;
80 }
81
82 if (!CFNetServiceResolveWithTimeout(netService, 10.0, &anError)) {
83 IIO_DEBUG("DNS SD: Resolve error: %ld.%d\n", anError.domain, anError.error);
84 goto exit;
85 }
86
87 CFStringRef targetHost = CFNetServiceGetTargetHost(netService);
88 if (targetHost == NULL) {
89 IIO_DEBUG("DNS SD: No valid target host for service.\n");
90 goto exit;
91 }
92
93 char hostname[MAXHOSTNAMELEN];
94 if (!CFStringGetCString(targetHost, hostname, sizeof(hostname), kCFStringEncodingASCII)) {
95 IIO_ERROR("DNS SD: Could not translate hostname\n");
96 goto exit;
97 }
98
99 CFStringRef svcName = CFNetServiceGetName(netService);
100 char name[MAXHOSTNAMELEN];
101 if (!CFStringGetCString(svcName, name, sizeof(name), kCFStringEncodingASCII)) {
102 IIO_ERROR("DNS SD: Could not translate service name\n");
103 goto exit;
104 }
105
106 SInt32 port = CFNetServiceGetPortNumber(netService);
107
108 CFArrayRef addrArr = CFNetServiceGetAddressing(netService);
109 if (addrArr == NULL) {
110 IIO_WARNING("DNS SD: No valid addresses for service %s.\n", name);
111 goto exit;
112 }
113
114 bool have_v4 = FALSE;
115 bool have_v6 = FALSE;
116 char address_v4[DNS_SD_ADDRESS_STR_MAX+1] = "";
117 char address_v6[DNS_SD_ADDRESS_STR_MAX+1] = "";
118 for (CFIndex i = 0; i < CFArrayGetCount(addrArr); i++) {
119 struct sockaddr_in *sa = (struct sockaddr_in *)
120 CFDataGetBytePtr(CFArrayGetValueAtIndex(addrArr, i));
121 switch(sa->sin_family) {
122 case AF_INET:
123 if (inet_ntop(sa->sin_family, &sa->sin_addr,
124 address_v4, sizeof(address_v4))) {
125 have_v4 = TRUE;
126 }
127 case AF_INET6:
128 if (inet_ntop(sa->sin_family, &sa->sin_addr,
129 address_v6, sizeof(address_v6))) {
130 have_v6 = TRUE;
131 }
132 }
133 }
134
135 if (!have_v4 && !have_v6) {
136 IIO_WARNING("DNS SD: Can't resolve valid address for service %s.\n", name);
137 goto exit;
138 }
139
140 /* Set properties on the last element on the list. */
141 while (dd->next)
142 dd = dd->next;
143
144 dd->port = port;
145 dd->hostname = strdup(hostname);
146 if (have_v4) {
147 iio_strlcpy(dd->addr_str, address_v4, sizeof(dd->addr_str));
148 } else if(have_v6) {
149 iio_strlcpy(dd->addr_str, address_v6, sizeof(dd->addr_str));
150 }
151
152 IIO_DEBUG("DNS SD: added %s (%s:%d)\n", hostname, dd->addr_str, port);
153
154 if (have_v4 || have_v6) {
155 // A list entry was filled, prepare new item on the list.
156 if (!new_discovery_data(&dd->next)) {
157 /* duplicate lock */
158 dd->next->lock = dd->lock;
159 } else {
160 IIO_ERROR("DNS SD Bonjour Resolver : memory failure\n");
161 }
162 }
163
164 verify_flags:
165 if ((flags & kCFNetServiceFlagMoreComing) == 0) {
166 IIO_DEBUG("DNS SD: No more entries coming.\n");
167 CFNetServiceBrowserStopSearch(browser, &anError);
168 }
169
170 exit:
171 iio_mutex_unlock(dd->lock);
172 return;
173
174 stop_browsing:
175 CFNetServiceBrowserStopSearch(browser, &anError);
176 }
177
dnssd_find_hosts(struct dns_sd_discovery_data ** ddata)178 int dnssd_find_hosts(struct dns_sd_discovery_data ** ddata)
179 {
180 int ret = 0;
181 struct dns_sd_discovery_data *d;
182
183 IIO_DEBUG("DNS SD: Start service discovery.\n");
184
185 if (new_discovery_data(&d) < 0) {
186 return -ENOMEM;
187 }
188
189 d->lock = iio_mutex_create();
190 if (!d->lock) {
191 dnssd_free_all_discovery_data(d);
192 return -ENOMEM;
193 }
194
195 CFNetServiceClientContext clientContext = { 0, d, NULL, NULL, NULL };
196 CFNetServiceBrowserRef serviceBrowser = CFNetServiceBrowserCreate(
197 kCFAllocatorDefault, __cfnet_browser_cb, &clientContext);
198
199 if (serviceBrowser == NULL) {
200 IIO_ERROR("DNS SD: Failed to create service browser.\n");
201 dnssd_free_all_discovery_data(d);
202 ret = -ENOMEM;
203 goto exit;
204 }
205
206 CFRunLoopRef runLoop = CFRunLoopGetCurrent();
207 CFNetServiceBrowserScheduleWithRunLoop(serviceBrowser, runLoop, kCFRunLoopDefaultMode);
208
209 CFStringRef type = CFSTR("_iio._tcp.");
210 CFStringRef domain = CFSTR("");
211 CFStreamError error;
212 Boolean result = CFNetServiceBrowserSearchForServices(serviceBrowser, domain, type, &error);
213
214 if (result == false) {
215 IIO_ERROR("DNS SD: CFNetServiceBrowserSearchForServices failed (domain = %ld, error = %d)\n",
216 (long)error.domain, error.error);
217
218 ret = -ENXIO;
219 } else {
220 CFRunLoopRunResult runRes = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2, TRUE);
221
222 if (runRes != kCFRunLoopRunHandledSource && runRes != kCFRunLoopRunTimedOut) {
223 if (runRes == kCFRunLoopRunFinished)
224 IIO_ERROR("DSN SD: CFRunLoopRunInMode completed kCFRunLoopRunFinished (%d)\n", runRes);
225 else if (runRes == kCFRunLoopRunStopped)
226 IIO_ERROR("DSN SD: CFRunLoopRunInMode completed kCFRunLoopRunStopped (%d)\n", runRes);
227 else
228 IIO_ERROR("DSN SD: CFRunLoopRunInMode completed for unknown reason (%d)\n", runRes);
229 } else {
230 if (runRes == kCFRunLoopRunHandledSource)
231 IIO_DEBUG("DSN SD: CFRunLoopRunInMode completed kCFRunLoopRunHandledSource (%d)\n", runRes);
232 else
233 IIO_DEBUG("DSN SD: CFRunLoopRunInMode completed kCFRunLoopRunTimedOut (%d)\n", runRes);
234 }
235
236 port_knock_discovery_data(&d);
237 remove_dup_discovery_data(&d);
238 *ddata = d;
239 }
240
241 CFNetServiceBrowserUnscheduleFromRunLoop(serviceBrowser, runLoop, kCFRunLoopDefaultMode);
242 CFRelease(serviceBrowser);
243 serviceBrowser = NULL;
244
245 IIO_DEBUG("DNS SD: Completed service discovery, return code : %d\n", ret);
246
247 exit:
248 iio_mutex_destroy(d->lock);
249 return ret;
250 }
251