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