Line data Source code
1 : // Webthing-CPP
2 : // SPDX-FileCopyrightText: 2023-present Benno Waldhauer
3 : // SPDX-License-Identifier: MIT
4 :
5 : // MdnsService follows mdns.c example of Mattia Janssons mdns lib.
6 : // also cf. https://github.com/mjansson/mdns
7 :
8 : #pragma once
9 :
10 : // Link with Iphlpapi.lib
11 : #pragma comment(lib, "IPHLPAPI.lib")
12 :
13 : #include <mdns.h>
14 : #include <bw/webthing/utils.hpp>
15 :
16 : #ifdef _WIN32
17 : #include <iphlpapi.h>
18 : #else
19 : #include <ifaddrs.h>
20 : #include <net/if.h>
21 : #include <netdb.h>
22 : #endif
23 :
24 : namespace bw::webthing
25 : {
26 :
27 105 : static void mdns_log(std::string msg)
28 : {
29 105 : logger::trace("MdnsService - " + msg);
30 105 : }
31 :
32 : const static int MAX_HOST_SIZE = 1025;
33 :
34 : static char addrbuffer[64];
35 : static char entrybuffer[256];
36 : static char namebuffer[256];
37 : static char sendbuffer[1024];
38 : static mdns_record_txt_t txtbuffer[128];
39 :
40 : static struct sockaddr_in service_address_ipv4;
41 : static struct sockaddr_in6 service_address_ipv6;
42 :
43 : static int has_ipv4;
44 : static int has_ipv6;
45 :
46 : static int is_tls_server;
47 :
48 :
49 45 : static mdns_string_t ipv4_address_to_string(char* buffer, size_t capacity, const struct sockaddr_in* addr,
50 : size_t addrlen) {
51 45 : char host[MAX_HOST_SIZE] = {0};
52 45 : char service[NI_MAXSERV] = {0};
53 45 : int ret = getnameinfo((const struct sockaddr*)addr, (socklen_t)addrlen, host, MAX_HOST_SIZE,
54 : service, NI_MAXSERV, NI_NUMERICSERV | NI_NUMERICHOST);
55 45 : int len = 0;
56 45 : if (ret == 0) {
57 45 : if (addr->sin_port != 0)
58 0 : len = snprintf(buffer, capacity, "%s:%s", host, service);
59 : else
60 45 : len = snprintf(buffer, capacity, "%s", host);
61 : }
62 45 : if (len >= (int)capacity)
63 0 : len = (int)capacity - 1;
64 : mdns_string_t str;
65 45 : str.str = buffer;
66 45 : str.length = len;
67 45 : return str;
68 : }
69 :
70 0 : static mdns_string_t ipv6_address_to_string(char* buffer, size_t capacity, const struct sockaddr_in6* addr,
71 : size_t addrlen) {
72 0 : char host[NI_MAXHOST] = {0};
73 0 : char service[NI_MAXSERV] = {0};
74 0 : int ret = getnameinfo((const struct sockaddr*)addr, (socklen_t)addrlen, host, NI_MAXHOST,
75 : service, NI_MAXSERV, NI_NUMERICSERV | NI_NUMERICHOST);
76 0 : int len = 0;
77 0 : if (ret == 0) {
78 0 : if (addr->sin6_port != 0)
79 0 : len = snprintf(buffer, capacity, "[%s]:%s", host, service);
80 : else
81 0 : len = snprintf(buffer, capacity, "%s", host);
82 : }
83 0 : if (len >= (int)capacity)
84 0 : len = (int)capacity - 1;
85 : mdns_string_t str;
86 0 : str.str = buffer;
87 0 : str.length = len;
88 0 : return str;
89 : }
90 :
91 30 : static mdns_string_t ip_address_to_string(char* buffer, size_t capacity, const struct sockaddr* addr, size_t addrlen) {
92 30 : if (addr->sa_family == AF_INET6)
93 0 : return ipv6_address_to_string(buffer, capacity, (const struct sockaddr_in6*)addr, addrlen);
94 30 : return ipv4_address_to_string(buffer, capacity, (const struct sockaddr_in*)addr, addrlen);
95 : }
96 :
97 15 : static std::vector<std::string> get_addresses()
98 : {
99 15 : std::vector<std::string> ips;
100 :
101 : #ifdef _WIN32
102 :
103 : PIP_ADAPTER_ADDRESSES ifaddresses = NULL;
104 : ULONG size = 0;
105 : DWORD ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST, NULL, ifaddresses, &size);
106 : if (ret == ERROR_BUFFER_OVERFLOW)
107 : {
108 : ifaddresses = (IP_ADAPTER_ADDRESSES *)malloc(size);
109 : ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST, NULL, ifaddresses, &size);
110 : }
111 :
112 : for (PIP_ADAPTER_ADDRESSES adapter = ifaddresses; adapter != NULL; adapter = adapter->Next)
113 : {
114 : for (PIP_ADAPTER_UNICAST_ADDRESS unicast = adapter->FirstUnicastAddress; unicast != NULL; unicast = unicast->Next)
115 : {
116 : sockaddr *sa = unicast->Address.lpSockaddr;
117 : char ip[INET6_ADDRSTRLEN];
118 :
119 : if (sa->sa_family == AF_INET)
120 : {
121 : struct sockaddr_in *saddr = (struct sockaddr_in *)sa;
122 : if (saddr->sin_addr.s_addr == htonl(INADDR_LOOPBACK))
123 : continue;
124 :
125 : inet_ntop(AF_INET, &(saddr)->sin_addr, ip, INET_ADDRSTRLEN);
126 : ips.push_back(ip);
127 : }
128 : else if (sa->sa_family == AF_INET6)
129 : {
130 : struct sockaddr_in6 *saddr = (struct sockaddr_in6 *)sa;
131 : // Ignore link-local addresses
132 : if (saddr->sin6_scope_id)
133 : continue;
134 :
135 : static const unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
136 : static const unsigned char localhost_mapped[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0x7f, 0, 0, 1};
137 :
138 : if (!memcmp(saddr->sin6_addr.s6_addr, localhost, 16) ||
139 : !memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16))
140 : {
141 : continue;
142 : }
143 :
144 : inet_ntop(AF_INET6, &((sockaddr_in6 *)sa)->sin6_addr, ip, INET6_ADDRSTRLEN);
145 : ips.push_back(ip);
146 : }
147 : }
148 : }
149 : free(ifaddresses);
150 :
151 : #else
152 15 : struct ifaddrs* ifaddr = 0;
153 15 : struct ifaddrs* ifa = 0;
154 :
155 15 : if (getifaddrs(&ifaddr) >= 0)
156 : {
157 : char addr_buffer[64];
158 135 : for (ifa = ifaddr; ifa; ifa = ifa->ifa_next)
159 : {
160 120 : if(!ifa->ifa_addr)
161 0 : continue;
162 :
163 120 : if(ifa->ifa_addr->sa_family == AF_INET)
164 : {
165 45 : struct sockaddr_in* saddr = (struct sockaddr_in*)ifa->ifa_addr;
166 45 : if (saddr->sin_addr.s_addr == htonl(INADDR_LOOPBACK))
167 15 : continue;
168 30 : mdns_string_t ip = ip_address_to_string(addr_buffer, sizeof(addr_buffer), ifa->ifa_addr, sizeof(sockaddr_in));
169 60 : ips.push_back(ip.str);
170 :
171 : }
172 75 : else if(ifa->ifa_addr->sa_family == AF_INET6)
173 : {
174 30 : struct sockaddr_in6* saddr = (struct sockaddr_in6*)ifa->ifa_addr;
175 : // Ignore link-local addresses
176 30 : if (saddr->sin6_scope_id)
177 30 : continue;
178 :
179 : static const unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
180 : static const unsigned char localhost_mapped[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0x7f, 0, 0, 1};
181 :
182 15 : if(!memcmp(saddr->sin6_addr.s6_addr, localhost, 16) ||
183 0 : !memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16))
184 : {
185 15 : continue;
186 : }
187 :
188 0 : mdns_string_t ip = ip_address_to_string(addr_buffer, sizeof(addr_buffer), ifa->ifa_addr, sizeof(sockaddr_in6));
189 0 : ips.push_back(ip.str);
190 : }
191 : }
192 : }
193 15 : freeifaddrs(ifaddr);
194 : #endif
195 :
196 30 : return ips;
197 0 : }
198 :
199 :
200 :
201 : struct MdnsService
202 : {
203 : volatile bool run_requested = false;
204 : volatile bool running = false;
205 :
206 : // Data for our service including the mDNS records
207 : typedef struct {
208 : mdns_string_t service;
209 : mdns_string_t hostname;
210 : mdns_string_t service_instance;
211 : mdns_string_t hostname_qualified;
212 : struct sockaddr_in address_ipv4;
213 : struct sockaddr_in6 address_ipv6;
214 : int port;
215 : mdns_record_t record_ptr;
216 : mdns_record_t record_srv;
217 : mdns_record_t record_a;
218 : mdns_record_t record_aaaa;
219 : mdns_record_t txt_record[2];
220 : } service_t;
221 :
222 :
223 : // Callback handling questions incoming on service sockets
224 96 : static int service_callback(int sock, const struct sockaddr* from, size_t addrlen, mdns_entry_type_t entry,
225 : uint16_t query_id, uint16_t ui16_rtype, uint16_t rclass, uint32_t ttl, const void* data,
226 : size_t size, size_t name_offset, size_t name_length, size_t record_offset,
227 : size_t record_length, void* user_data) {
228 : (void)sizeof(ttl);
229 96 : if (entry != MDNS_ENTRYTYPE_QUESTION)
230 96 : return 0;
231 :
232 0 : mdns_record_type rtype = static_cast<mdns_record_type>(ui16_rtype);
233 :
234 0 : const char dns_sd[] = "_services._dns-sd._udp.local.";
235 0 : const service_t* service = (const service_t*)user_data;
236 :
237 0 : mdns_string_t fromaddrstr = ip_address_to_string(addrbuffer, sizeof(addrbuffer), from, addrlen);
238 :
239 0 : size_t offset = name_offset;
240 0 : mdns_string_t name = mdns_string_extract(data, size, &offset, namebuffer, sizeof(namebuffer));
241 :
242 0 : const char* record_name = 0;
243 0 : if (rtype == MDNS_RECORDTYPE_PTR)
244 0 : record_name = "PTR";
245 0 : else if (rtype == MDNS_RECORDTYPE_SRV)
246 0 : record_name = "SRV";
247 0 : else if (rtype == MDNS_RECORDTYPE_A)
248 0 : record_name = "A";
249 0 : else if (rtype == MDNS_RECORDTYPE_AAAA)
250 0 : record_name = "AAAA";
251 0 : else if (rtype == MDNS_RECORDTYPE_TXT)
252 0 : record_name = "TXT";
253 0 : else if (rtype == MDNS_RECORDTYPE_ANY)
254 0 : record_name = "ANY";
255 : else
256 0 : return 0;
257 0 : mdns_log("Query " + std::string(record_name) + " " + std::string(name.str));
258 :
259 0 : if ((name.length == (sizeof(dns_sd) - 1)) &&
260 0 : (strncmp(name.str, dns_sd, sizeof(dns_sd) - 1) == 0)) {
261 0 : if ((rtype == MDNS_RECORDTYPE_PTR) || (rtype == MDNS_RECORDTYPE_ANY)) {
262 : // The PTR query was for the DNS-SD domain, send answer with a PTR record for the
263 : // service name we advertise, typically on the "<_service-name>._tcp.local." format
264 :
265 : // Answer PTR record reverse mapping "<_service-name>._tcp.local." to
266 : // "<hostname>.<_service-name>._tcp.local."
267 0 : mdns_record_t answer = {
268 0 : name, MDNS_RECORDTYPE_PTR, {service->service}};
269 :
270 : // Send the answer, unicast or multicast depending on flag in query
271 0 : uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
272 0 : mdns_log(" --> answer " + std::string(answer.data.ptr.name.str) + " " +
273 0 : std::string(unicast ? "unicast" : "multicast"));
274 :
275 0 : if (unicast) {
276 0 : mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer),
277 : query_id, rtype, name.str, name.length, answer, 0, 0, 0,
278 : 0);
279 : } else {
280 0 : mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0, 0,
281 : 0);
282 : }
283 : }
284 0 : } else if ((name.length == service->service.length) &&
285 0 : (strncmp(name.str, service->service.str, name.length) == 0)) {
286 0 : if ((rtype == MDNS_RECORDTYPE_PTR) || (rtype == MDNS_RECORDTYPE_ANY)) {
287 : // The PTR query was for our service (usually "<_service-name._tcp.local"), answer a PTR
288 : // record reverse mapping the queried service name to our service instance name
289 : // (typically on the "<hostname>.<_service-name>._tcp.local." format), and add
290 : // additional records containing the SRV record mapping the service instance name to our
291 : // qualified hostname (typically "<hostname>.local.") and port, as well as any IPv4/IPv6
292 : // address for the hostname as A/AAAA records, and two test TXT records
293 :
294 : // Answer PTR record reverse mapping "<_service-name>._tcp.local." to
295 : // "<hostname>.<_service-name>._tcp.local."
296 0 : mdns_record_t answer = service->record_ptr;
297 :
298 0 : mdns_record_t additional[5] = {0};
299 0 : size_t additional_count = 0;
300 :
301 : // SRV record mapping "<hostname>.<_service-name>._tcp.local." to
302 : // "<hostname>.local." with port. Set weight & priority to 0.
303 0 : additional[additional_count++] = service->record_srv;
304 :
305 : // A/AAAA records mapping "<hostname>.local." to IPv4/IPv6 addresses
306 0 : if (service->address_ipv4.sin_family == AF_INET)
307 0 : additional[additional_count++] = service->record_a;
308 0 : if (service->address_ipv6.sin6_family == AF_INET6)
309 0 : additional[additional_count++] = service->record_aaaa;
310 :
311 : // Add two test TXT records for our service instance name, will be coalesced into
312 : // one record with both key-value pair strings by the library
313 0 : additional[additional_count++] = service->txt_record[0];
314 0 : if(is_tls_server)
315 0 : additional[additional_count++] = service->txt_record[1];
316 :
317 : // Send the answer, unicast or multicast depending on flag in query
318 0 : uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
319 0 : mdns_log(" --> answer " + std::string(service->record_ptr.data.ptr.name.str) +
320 0 : " (" + std::string(unicast ? "unicast" : "multicast") + ")");
321 :
322 0 : if (unicast) {
323 0 : mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer),
324 : query_id, rtype, name.str, name.length, answer, 0, 0,
325 : additional, additional_count);
326 : } else {
327 0 : mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0,
328 : additional, additional_count);
329 : }
330 : }
331 0 : } else if ((name.length == service->service_instance.length) &&
332 0 : (strncmp(name.str, service->service_instance.str, name.length) == 0)) {
333 0 : if ((rtype == MDNS_RECORDTYPE_SRV) || (rtype == MDNS_RECORDTYPE_ANY)) {
334 : // The SRV query was for our service instance (usually
335 : // "<hostname>.<_service-name._tcp.local"), answer a SRV record mapping the service
336 : // instance name to our qualified hostname (typically "<hostname>.local.") and port, as
337 : // well as any IPv4/IPv6 address for the hostname as A/AAAA records, and two test TXT
338 : // records
339 :
340 : // Answer PTR record reverse mapping "<_service-name>._tcp.local." to
341 : // "<hostname>.<_service-name>._tcp.local."
342 0 : mdns_record_t answer = service->record_srv;
343 :
344 0 : mdns_record_t additional[5] = {0};
345 0 : size_t additional_count = 0;
346 :
347 : // A/AAAA records mapping "<hostname>.local." to IPv4/IPv6 addresses
348 0 : if (service->address_ipv4.sin_family == AF_INET)
349 0 : additional[additional_count++] = service->record_a;
350 0 : if (service->address_ipv6.sin6_family == AF_INET6)
351 0 : additional[additional_count++] = service->record_aaaa;
352 :
353 : // Add two test TXT records for our service instance name, will be coalesced into
354 : // one record with both key-value pair strings by the library
355 0 : additional[additional_count++] = service->txt_record[0];
356 0 : if(is_tls_server)
357 0 : additional[additional_count++] = service->txt_record[1];
358 :
359 : // Send the answer, unicast or multicast depending on flag in query
360 0 : uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
361 0 : mdns_log(" --> answer " + std::string(service->record_srv.data.srv.name.str) +
362 0 : " port " + std::to_string(service->port) + " (" + (unicast ? "unicast" : "multicast") + ")");
363 :
364 0 : if (unicast) {
365 0 : mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer),
366 : query_id, rtype, name.str, name.length, answer, 0, 0,
367 : additional, additional_count);
368 : } else {
369 0 : mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0,
370 : additional, additional_count);
371 : }
372 : }
373 0 : } else if ((name.length == service->hostname_qualified.length) &&
374 0 : (strncmp(name.str, service->hostname_qualified.str, name.length) == 0)) {
375 0 : if (((rtype == MDNS_RECORDTYPE_A) || (rtype == MDNS_RECORDTYPE_ANY)) &&
376 0 : (service->address_ipv4.sin_family == AF_INET)) {
377 : // The A query was for our qualified hostname (typically "<hostname>.local.") and we
378 : // have an IPv4 address, answer with an A record mappiing the hostname to an IPv4
379 : // address, as well as any IPv6 address for the hostname, and two test TXT records
380 :
381 : // Answer A records mapping "<hostname>.local." to IPv4 address
382 0 : mdns_record_t answer = service->record_a;
383 :
384 0 : mdns_record_t additional[5] = {0};
385 0 : size_t additional_count = 0;
386 :
387 : // AAAA record mapping "<hostname>.local." to IPv6 addresses
388 0 : if (service->address_ipv6.sin6_family == AF_INET6)
389 0 : additional[additional_count++] = service->record_aaaa;
390 :
391 : // Add two test TXT records for our service instance name, will be coalesced into
392 : // one record with both key-value pair strings by the library
393 0 : additional[additional_count++] = service->txt_record[0];
394 0 : if(is_tls_server)
395 0 : additional[additional_count++] = service->txt_record[1];
396 :
397 : // Send the answer, unicast or multicast depending on flag in query
398 0 : uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
399 0 : mdns_string_t addrstr = ip_address_to_string(
400 0 : addrbuffer, sizeof(addrbuffer), (struct sockaddr*)&service->record_a.data.a.addr,
401 : sizeof(service->record_a.data.a.addr));
402 0 : mdns_log(" --> answer " + std::string(service->record_a.name.str) +
403 0 : " IPv4 " + std::string(addrstr.str) + " (" + std::string(unicast ? "unicast" : "multicast") + ")");
404 :
405 0 : if (unicast) {
406 0 : mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer),
407 : query_id, rtype, name.str, name.length, answer, 0, 0,
408 : additional, additional_count);
409 : } else {
410 0 : mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0,
411 : additional, additional_count);
412 : }
413 0 : } else if (((rtype == MDNS_RECORDTYPE_AAAA) || (rtype == MDNS_RECORDTYPE_ANY)) &&
414 0 : (service->address_ipv6.sin6_family == AF_INET6)) {
415 : // The AAAA query was for our qualified hostname (typically "<hostname>.local.") and we
416 : // have an IPv6 address, answer with an AAAA record mappiing the hostname to an IPv6
417 : // address, as well as any IPv4 address for the hostname, and two test TXT records
418 :
419 : // Answer AAAA records mapping "<hostname>.local." to IPv6 address
420 0 : mdns_record_t answer = service->record_aaaa;
421 :
422 0 : mdns_record_t additional[5] = {0};
423 0 : size_t additional_count = 0;
424 :
425 : // A record mapping "<hostname>.local." to IPv4 addresses
426 0 : if (service->address_ipv4.sin_family == AF_INET)
427 0 : additional[additional_count++] = service->record_a;
428 :
429 : // Add two test TXT records for our service instance name, will be coalesced into
430 : // one record with both key-value pair strings by the library
431 0 : additional[additional_count++] = service->txt_record[0];
432 0 : if(is_tls_server)
433 0 : additional[additional_count++] = service->txt_record[1];
434 :
435 : // Send the answer, unicast or multicast depending on flag in query
436 0 : uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
437 : mdns_string_t addrstr =
438 0 : ip_address_to_string(addrbuffer, sizeof(addrbuffer),
439 0 : (struct sockaddr*)&service->record_aaaa.data.aaaa.addr,
440 : sizeof(service->record_aaaa.data.aaaa.addr));
441 0 : mdns_log(" --> answer " + std::string(service->record_aaaa.name.str) +
442 0 : " IPv6 " + std::string(addrstr.str) + " (" + std::string(unicast ? "unicast" : "multicast") + ")");
443 :
444 0 : if (unicast) {
445 0 : mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer),
446 : query_id, rtype, name.str, name.length, answer, 0, 0,
447 : additional, additional_count);
448 : } else {
449 0 : mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0,
450 : additional, additional_count);
451 : }
452 : }
453 : }
454 0 : return 0;
455 : }
456 :
457 : // Open sockets for sending one-shot multicast queries from an ephemeral port
458 15 : static int open_client_sockets(int* sockets, int max_sockets, int port) {
459 : // When sending, each socket can only send to one network interface
460 : // Thus we need to open one socket for each interface and address family
461 15 : int num_sockets = 0;
462 :
463 : #ifdef _WIN32
464 :
465 : IP_ADAPTER_ADDRESSES* adapter_address = 0;
466 : ULONG address_size = 8000;
467 : unsigned int ret;
468 : unsigned int num_retries = 4;
469 : do {
470 : adapter_address = (IP_ADAPTER_ADDRESSES*)malloc(address_size);
471 : ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST, 0,
472 : adapter_address, &address_size);
473 : if (ret == ERROR_BUFFER_OVERFLOW) {
474 : free(adapter_address);
475 : adapter_address = 0;
476 : address_size *= 2;
477 : } else {
478 : break;
479 : }
480 : } while (num_retries-- > 0);
481 :
482 : if (!adapter_address || (ret != NO_ERROR)) {
483 : free(adapter_address);
484 : mdns_log("Failed to get network adapter addresses");
485 : return num_sockets;
486 : }
487 :
488 : int first_ipv4 = 1;
489 : int first_ipv6 = 1;
490 : for (PIP_ADAPTER_ADDRESSES adapter = adapter_address; adapter; adapter = adapter->Next) {
491 : if (adapter->TunnelType == TUNNEL_TYPE_TEREDO)
492 : continue;
493 : if (adapter->OperStatus != IfOperStatusUp)
494 : continue;
495 :
496 : for (IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; unicast;
497 : unicast = unicast->Next) {
498 : if (unicast->Address.lpSockaddr->sa_family == AF_INET) {
499 : struct sockaddr_in* saddr = (struct sockaddr_in*)unicast->Address.lpSockaddr;
500 : if ((saddr->sin_addr.S_un.S_un_b.s_b1 != 127) ||
501 : (saddr->sin_addr.S_un.S_un_b.s_b2 != 0) ||
502 : (saddr->sin_addr.S_un.S_un_b.s_b3 != 0) ||
503 : (saddr->sin_addr.S_un.S_un_b.s_b4 != 1)) {
504 : int log_addr = 0;
505 : if (first_ipv4) {
506 : service_address_ipv4 = *saddr;
507 : first_ipv4 = 0;
508 : log_addr = 1;
509 : }
510 : has_ipv4 = 1;
511 : if (num_sockets < max_sockets) {
512 : saddr->sin_port = htons((unsigned short)port);
513 : int sock = mdns_socket_open_ipv4(saddr);
514 : if (sock >= 0) {
515 : sockets[num_sockets++] = sock;
516 : log_addr = 1;
517 : } else {
518 : log_addr = 0;
519 : }
520 : }
521 : if (log_addr) {
522 : char buffer[128];
523 : mdns_string_t addr = ipv4_address_to_string(buffer, sizeof(buffer), saddr,
524 : sizeof(struct sockaddr_in));
525 : mdns_log("Local IPv4 address: " + std::string(addr.str));
526 : }
527 : }
528 : } else if (unicast->Address.lpSockaddr->sa_family == AF_INET6) {
529 : struct sockaddr_in6* saddr = (struct sockaddr_in6*)unicast->Address.lpSockaddr;
530 : // Ignore link-local addresses
531 : if (saddr->sin6_scope_id)
532 : continue;
533 : static const unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0,
534 : 0, 0, 0, 0, 0, 0, 0, 1};
535 : static const unsigned char localhost_mapped[] = {0, 0, 0, 0, 0, 0, 0, 0,
536 : 0, 0, 0xff, 0xff, 0x7f, 0, 0, 1};
537 : if ((unicast->DadState == NldsPreferred) &&
538 : memcmp(saddr->sin6_addr.s6_addr, localhost, 16) &&
539 : memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16)) {
540 : int log_addr = 0;
541 : if (first_ipv6) {
542 : service_address_ipv6 = *saddr;
543 : first_ipv6 = 0;
544 : log_addr = 1;
545 : }
546 : has_ipv6 = 1;
547 : if (num_sockets < max_sockets) {
548 : saddr->sin6_port = htons((unsigned short)port);
549 : int sock = mdns_socket_open_ipv6(saddr);
550 : if (sock >= 0) {
551 : sockets[num_sockets++] = sock;
552 : log_addr = 1;
553 : } else {
554 : log_addr = 0;
555 : }
556 : }
557 : if (log_addr) {
558 : char buffer[128];
559 : mdns_string_t addr = ipv6_address_to_string(buffer, sizeof(buffer), saddr,
560 : sizeof(struct sockaddr_in6));
561 : mdns_log("Local IPv6 address: " + std::string(addr.str));
562 : }
563 : }
564 : }
565 : }
566 : }
567 :
568 : free(adapter_address);
569 :
570 : #else
571 :
572 15 : struct ifaddrs* ifaddr = 0;
573 15 : struct ifaddrs* ifa = 0;
574 :
575 15 : if (getifaddrs(&ifaddr) < 0)
576 0 : mdns_log("Unable to get interface addresses");
577 :
578 15 : int first_ipv4 = 1;
579 15 : int first_ipv6 = 1;
580 135 : for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
581 120 : if (!ifa->ifa_addr)
582 0 : continue;
583 120 : if (!(ifa->ifa_flags & IFF_UP) || !(ifa->ifa_flags & IFF_MULTICAST))
584 45 : continue;
585 75 : if ((ifa->ifa_flags & IFF_LOOPBACK) || (ifa->ifa_flags & IFF_POINTOPOINT))
586 0 : continue;
587 :
588 75 : if (ifa->ifa_addr->sa_family == AF_INET) {
589 30 : struct sockaddr_in* saddr = (struct sockaddr_in*)ifa->ifa_addr;
590 30 : if (saddr->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
591 30 : int log_addr = 0;
592 30 : if (first_ipv4) {
593 15 : service_address_ipv4 = *saddr;
594 15 : first_ipv4 = 0;
595 15 : log_addr = 1;
596 : }
597 30 : has_ipv4 = 1;
598 30 : if (num_sockets < max_sockets) {
599 0 : saddr->sin_port = htons(port);
600 0 : int sock = mdns_socket_open_ipv4(saddr);
601 0 : if (sock >= 0) {
602 0 : sockets[num_sockets++] = sock;
603 0 : log_addr = 1;
604 : } else {
605 0 : log_addr = 0;
606 : }
607 : }
608 30 : if (log_addr) {
609 : char buffer[128];
610 15 : mdns_string_t addr = ipv4_address_to_string(buffer, sizeof(buffer), saddr,
611 : sizeof(struct sockaddr_in));
612 30 : mdns_log("Local IPv4 address: " + std::string(addr.str));
613 : }
614 : }
615 45 : } else if (ifa->ifa_addr->sa_family == AF_INET6) {
616 15 : struct sockaddr_in6* saddr = (struct sockaddr_in6*)ifa->ifa_addr;
617 : // Ignore link-local addresses
618 15 : if (saddr->sin6_scope_id)
619 15 : continue;
620 : static const unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0,
621 : 0, 0, 0, 0, 0, 0, 0, 1};
622 : static const unsigned char localhost_mapped[] = {0, 0, 0, 0, 0, 0, 0, 0,
623 : 0, 0, 0xff, 0xff, 0x7f, 0, 0, 1};
624 0 : if (memcmp(saddr->sin6_addr.s6_addr, localhost, 16) &&
625 0 : memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16)) {
626 0 : int log_addr = 0;
627 0 : if (first_ipv6) {
628 0 : service_address_ipv6 = *saddr;
629 0 : first_ipv6 = 0;
630 0 : log_addr = 1;
631 : }
632 0 : has_ipv6 = 1;
633 0 : if (num_sockets < max_sockets) {
634 0 : saddr->sin6_port = htons(port);
635 0 : int sock = mdns_socket_open_ipv6(saddr);
636 0 : if (sock >= 0) {
637 0 : sockets[num_sockets++] = sock;
638 0 : log_addr = 1;
639 : } else {
640 0 : log_addr = 0;
641 : }
642 : }
643 0 : if (log_addr) {
644 : char buffer[128];
645 0 : mdns_string_t addr = ipv6_address_to_string(buffer, sizeof(buffer), saddr,
646 : sizeof(struct sockaddr_in6));
647 0 : mdns_log("Local IPv6 address: " + std::string(addr.str));
648 : }
649 : }
650 : }
651 : }
652 :
653 15 : freeifaddrs(ifaddr);
654 :
655 : #endif
656 :
657 15 : return num_sockets;
658 : }
659 :
660 : // Open sockets to listen to incoming mDNS queries on port 5353
661 15 : static int open_service_sockets(int* sockets, int max_sockets) {
662 : // When recieving, each socket can recieve data from all network interfaces
663 : // Thus we only need to open one socket for each address family
664 15 : int num_sockets = 0;
665 :
666 : // Call the client socket function to enumerate and get local addresses,
667 : // but not open the actual sockets
668 15 : open_client_sockets(0, 0, 0);
669 :
670 15 : if (num_sockets < max_sockets) {
671 : struct sockaddr_in sock_addr;
672 15 : memset(&sock_addr, 0, sizeof(struct sockaddr_in));
673 15 : sock_addr.sin_family = AF_INET;
674 : #ifdef _WIN32
675 : sock_addr.sin_addr = in4addr_any;
676 : #else
677 15 : sock_addr.sin_addr.s_addr = INADDR_ANY;
678 : #endif
679 15 : sock_addr.sin_port = htons(MDNS_PORT);
680 : #ifdef __APPLE__
681 : sock_addr.sin_len = sizeof(struct sockaddr_in);
682 : #endif
683 15 : int sock = mdns_socket_open_ipv4(&sock_addr);
684 15 : if (sock >= 0)
685 15 : sockets[num_sockets++] = sock;
686 : }
687 :
688 15 : if (num_sockets < max_sockets) {
689 : struct sockaddr_in6 sock_addr;
690 15 : memset(&sock_addr, 0, sizeof(struct sockaddr_in6));
691 15 : sock_addr.sin6_family = AF_INET6;
692 15 : sock_addr.sin6_addr = in6addr_any;
693 15 : sock_addr.sin6_port = htons(MDNS_PORT);
694 : #ifdef __APPLE__
695 : sock_addr.sin6_len = sizeof(struct sockaddr_in6);
696 : #endif
697 15 : int sock = mdns_socket_open_ipv6(&sock_addr);
698 15 : if (sock >= 0)
699 15 : sockets[num_sockets++] = sock;
700 : }
701 :
702 15 : return num_sockets;
703 : }
704 :
705 :
706 :
707 : // Provide a mDNS service, answering incoming DNS-SD and mDNS queries
708 15 : int service_mdns(const char* hostname, const char* service_name, int service_port, const char* path, bool tls)
709 : {
710 15 : running = true;
711 :
712 : int sockets[32];
713 15 : int num_sockets = open_service_sockets(sockets, sizeof(sockets) / sizeof(sockets[0]));
714 15 : if (num_sockets <= 0) {
715 0 : mdns_log("Failed to open any client sockets");
716 0 : return -1;
717 : }
718 15 : mdns_log("Opened " + std::to_string(num_sockets) + " socket" + std::string(num_sockets ? "s" : "") + " for mDNS service");
719 :
720 15 : size_t service_name_length = strlen(service_name);
721 15 : if (!service_name_length) {
722 0 : mdns_log("Invalid service name");
723 0 : return -1;
724 : }
725 :
726 15 : char* service_name_buffer = (char*)malloc(service_name_length + 2);
727 15 : memcpy(service_name_buffer, service_name, service_name_length);
728 15 : if (service_name_buffer[service_name_length - 1] != '.')
729 0 : service_name_buffer[service_name_length++] = '.';
730 15 : service_name_buffer[service_name_length] = 0;
731 15 : service_name = service_name_buffer;
732 :
733 45 : mdns_log("Service mDNS: " + std::string(service_name) + ":" + std::to_string(service_port));
734 15 : mdns_log("Hostname: " + std::string(hostname));
735 :
736 15 : size_t capacity = 2048;
737 15 : void* buffer = malloc(capacity);
738 :
739 15 : mdns_string_t service_string = mdns_string_t{service_name, strlen(service_name)};
740 15 : mdns_string_t hostname_string = mdns_string_t{hostname, strlen(hostname)};
741 :
742 : // Build the service instance "<hostname>.<_service-name>._tcp.local." string
743 15 : char service_instance_buffer[256] = {0};
744 15 : snprintf(service_instance_buffer, sizeof(service_instance_buffer) - 1, "%.*s.%.*s",
745 15 : MDNS_STRING_FORMAT(hostname_string), MDNS_STRING_FORMAT(service_string));
746 : mdns_string_t service_instance_string =
747 15 : mdns_string_t{service_instance_buffer, strlen(service_instance_buffer)};
748 :
749 : // Build the "<hostname>.local." string
750 15 : char qualified_hostname_buffer[256] = {0};
751 15 : snprintf(qualified_hostname_buffer, sizeof(qualified_hostname_buffer) - 1, "%.*s.local.",
752 15 : MDNS_STRING_FORMAT(hostname_string));
753 : mdns_string_t hostname_qualified_string =
754 15 : mdns_string_t{qualified_hostname_buffer, strlen(qualified_hostname_buffer)};
755 :
756 15 : service_t service = {0};
757 15 : service.service = service_string;
758 15 : service.hostname = hostname_string;
759 15 : service.service_instance = service_instance_string;
760 15 : service.hostname_qualified = hostname_qualified_string;
761 15 : service.address_ipv4 = service_address_ipv4;
762 15 : service.address_ipv6 = service_address_ipv6;
763 15 : service.port = service_port;
764 :
765 : // Setup our mDNS records
766 :
767 : // PTR record reverse mapping "<_service-name>._tcp.local." to
768 : // "<hostname>.<_service-name>._tcp.local."
769 : mdns_record_t::mdns_record_data rd_ptr;
770 15 : rd_ptr.ptr = { service.service_instance };
771 :
772 : mdns_record_t ptr_rec;
773 15 : ptr_rec.name = service.service;
774 15 : ptr_rec.type = MDNS_RECORDTYPE_PTR;
775 15 : ptr_rec.data = rd_ptr;
776 15 : ptr_rec.rclass = 0;
777 15 : ptr_rec.ttl = 0;
778 :
779 15 : service.record_ptr = ptr_rec;
780 :
781 : // SRV record mapping "<hostname>.<_service-name>._tcp.local." to
782 : // "<hostname>.local." with port. Set weight & priority to 0.
783 15 : uint16_t p = service.port;
784 : mdns_record_t::mdns_record_data rd_srv;
785 15 : rd_srv.srv = { 0, 0, p, service.hostname_qualified};
786 :
787 : mdns_record_t srv_rec;
788 15 : srv_rec.name = service.service_instance;
789 15 : srv_rec.type = MDNS_RECORDTYPE_SRV;
790 15 : srv_rec.data = rd_srv;
791 15 : srv_rec.rclass = 0;
792 15 : srv_rec.ttl = 0;
793 :
794 15 : service.record_srv = srv_rec;
795 :
796 : // A/AAAA records mapping "<hostname>.local." to IPv4/IPv6 addresses
797 : mdns_record_t::mdns_record_data rd_a;
798 15 : rd_a.a = {service.address_ipv4};
799 :
800 : mdns_record_t a_rec;
801 15 : a_rec.name = service.hostname_qualified;
802 15 : a_rec.type = MDNS_RECORDTYPE_A;
803 15 : a_rec.data = rd_a;
804 15 : a_rec.rclass = 0;
805 15 : a_rec.ttl = 0;
806 :
807 15 : service.record_a = a_rec;
808 :
809 :
810 : mdns_record_t::mdns_record_data rd_aaaa;
811 15 : rd_aaaa.aaaa = {service.address_ipv6};
812 :
813 : mdns_record_t aaaa_rec;
814 15 : aaaa_rec.name = service.hostname_qualified;
815 15 : aaaa_rec.type = MDNS_RECORDTYPE_AAAA;
816 15 : aaaa_rec.data = rd_aaaa;
817 15 : aaaa_rec.rclass = 0;
818 15 : aaaa_rec.ttl = 0;
819 :
820 15 : service.record_aaaa = aaaa_rec;
821 :
822 : // Add two test TXT records for our service instance name, will be coalesced into
823 : // one record with both key-value pair strings by the library
824 : mdns_record_t::mdns_record_data rd_path;
825 15 : rd_path.txt = {{MDNS_STRING_CONST("path")},{path, std::strlen(path)}};
826 :
827 : mdns_record_t path_rec;
828 15 : path_rec.name = service.service_instance;
829 15 : path_rec.type = MDNS_RECORDTYPE_TXT;
830 15 : path_rec.data = rd_path;
831 15 : path_rec.rclass = 0;
832 15 : path_rec.ttl = 0;
833 :
834 15 : service.txt_record[0] = path_rec;
835 :
836 : mdns_record_t::mdns_record_data rd_tls;
837 15 : rd_tls.txt = {{MDNS_STRING_CONST("tls")},{MDNS_STRING_CONST(tls ? "1" : "0")}};
838 :
839 : mdns_record_t tls_rec;
840 15 : tls_rec.name = service.service_instance;
841 15 : tls_rec.type = MDNS_RECORDTYPE_TXT;
842 15 : tls_rec.data = rd_tls;
843 15 : tls_rec.rclass = 0;
844 15 : tls_rec.ttl = 0;
845 :
846 15 : service.txt_record[1] = tls_rec;
847 :
848 : // Send an announcement on startup of service
849 : {
850 15 : mdns_log("Sending announce");
851 15 : mdns_record_t additional[5] = {0};
852 15 : size_t additional_count = 0;
853 15 : additional[additional_count++] = service.record_srv;
854 15 : if (service.address_ipv4.sin_family == AF_INET)
855 15 : additional[additional_count++] = service.record_a;
856 15 : if (service.address_ipv6.sin6_family == AF_INET6)
857 0 : additional[additional_count++] = service.record_aaaa;
858 15 : additional[additional_count++] = service.txt_record[0];
859 15 : if(is_tls_server)
860 0 : additional[additional_count++] = service.txt_record[1];
861 :
862 45 : for (int isock = 0; isock < num_sockets; ++isock)
863 30 : mdns_announce_multicast(sockets[isock], buffer, capacity, service.record_ptr, 0, 0,
864 : additional, additional_count);
865 : }
866 :
867 : // This is a crude implementation that checks for incoming queries
868 45 : while (run_requested) {
869 30 : int nfds = 0;
870 : fd_set readfs;
871 510 : FD_ZERO(&readfs);
872 90 : for (int isock = 0; isock < num_sockets; ++isock) {
873 60 : if (sockets[isock] >= nfds)
874 60 : nfds = sockets[isock] + 1;
875 60 : FD_SET(sockets[isock], &readfs);
876 : }
877 :
878 : struct timeval timeout;
879 30 : timeout.tv_sec = 0;
880 30 : timeout.tv_usec = 100000;
881 :
882 30 : if (select(nfds, &readfs, 0, 0, &timeout) >= 0) {
883 90 : for (int isock = 0; isock < num_sockets; ++isock) {
884 60 : if (FD_ISSET(sockets[isock], &readfs)) {
885 24 : mdns_socket_listen(sockets[isock], buffer, capacity, service_callback,
886 : &service);
887 : }
888 60 : FD_SET(sockets[isock], &readfs);
889 : }
890 : } else {
891 0 : break;
892 : }
893 : }
894 :
895 : // Send a goodbye on end of service
896 : {
897 15 : mdns_log("Sending goodbye");
898 15 : mdns_record_t additional[5] = {0};
899 15 : size_t additional_count = 0;
900 15 : additional[additional_count++] = service.record_srv;
901 15 : if (service.address_ipv4.sin_family == AF_INET)
902 15 : additional[additional_count++] = service.record_a;
903 15 : if (service.address_ipv6.sin6_family == AF_INET6)
904 0 : additional[additional_count++] = service.record_aaaa;
905 15 : additional[additional_count++] = service.txt_record[0];
906 15 : if(is_tls_server)
907 0 : additional[additional_count++] = service.txt_record[1];
908 :
909 45 : for (int isock = 0; isock < num_sockets; ++isock)
910 30 : mdns_goodbye_multicast(sockets[isock], buffer, capacity, service.record_ptr, 0, 0,
911 : additional, additional_count);
912 : }
913 :
914 15 : free(buffer);
915 15 : free(service_name_buffer);
916 :
917 45 : for (int isock = 0; isock < num_sockets; ++isock)
918 30 : mdns_socket_close(sockets[isock]);
919 15 : mdns_log("Closed socket" + std::string(num_sockets ? "s" : ""));
920 :
921 15 : return 0;
922 : }
923 :
924 :
925 15 : void start_service(std::string hostname, std::string service, int port, std::string path, bool tls)
926 : {
927 15 : is_tls_server = tls;
928 15 : run_requested = true;
929 15 : service_mdns(hostname.c_str(), service.c_str(), port, path.c_str(), tls);
930 15 : running = false;
931 15 : }
932 :
933 15 : void stop_service()
934 : {
935 15 : run_requested = false;
936 15 : }
937 :
938 884 : bool is_running()
939 : {
940 884 : return running;
941 : }
942 : };
943 :
944 : } // bw::webthing
|