OpenCPN Partial API docs
Loading...
Searching...
No Matches
mdns_service.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2022 by David Register *
3 * Copyright (C) 2022 Alec Leamas *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 **************************************************************************/
20
26#include <string>
27#include <mutex>
28#include <vector>
29#include <thread>
30
31#ifdef _WIN32
32#ifndef _CRT_SECURE_NO_WARNINGS
33#define _CRT_SECURE_NO_WARNINGS 1
34#endif
35#endif
36
37#include <stdio.h>
38
39#include <errno.h>
40#include <signal.h>
41
42#ifdef _WIN32
43#include <winsock2.h>
44#include <iphlpapi.h>
45#define sleep(x) Sleep(x * 1000)
46#else
47#include <netdb.h>
48#include <ifaddrs.h>
49#include <net/if.h>
50#endif
51
52#include "config.h"
53#include "mdns_util.h"
54
55#ifndef OCPN_ENABLE_MDNS_DEBUG
56#define printf(...)
57#endif
58
59static char addrbuffer[64];
60static char namebuffer[256];
61static char sendbuffer[1024];
62
63static struct sockaddr_in service_address_ipv4;
64static struct sockaddr_in6 service_address_ipv6;
65
66volatile sig_atomic_t running_server = 1;
67
68// Data for our service including the mDNS records
69typedef struct {
70 mdns_string_t service;
71 mdns_string_t hostname;
72 mdns_string_t service_instance;
73 mdns_string_t hostname_qualified;
74 struct sockaddr_in address_ipv4;
75 struct sockaddr_in6 address_ipv6;
76 int port;
77 mdns_record_t record_ptr;
78 mdns_record_t record_srv;
79 mdns_record_t record_a;
80 mdns_record_t record_aaaa;
81 mdns_record_t txt_record[2];
82} service_t;
83
84// Callback handling questions incoming on service sockets
85int ocpn_service_callback(int sock, const struct sockaddr* from, size_t addrlen,
86 mdns_entry_type_t entry, uint16_t query_id,
87 uint16_t rtype, uint16_t rclass, uint32_t ttl,
88 const void* data, size_t size, size_t name_offset,
89 size_t name_length, size_t record_offset,
90 size_t record_length, void* user_data) {
91 (void)sizeof(ttl);
92 if (entry != MDNS_ENTRYTYPE_QUESTION) return 0;
93
94 const char dns_sd[] = "_services._dns-sd._udp.local.";
95 const service_t* service = (const service_t*)user_data;
96
97 mdns_string_t fromaddrstr =
98 ip_address_to_string(addrbuffer, sizeof(addrbuffer), from, addrlen);
99
100 size_t offset = name_offset;
101 mdns_string_t name =
102 mdns_string_extract(data, size, &offset, namebuffer, sizeof(namebuffer));
103
104 const char* record_name = 0;
105 if (rtype == MDNS_RECORDTYPE_PTR)
106 record_name = "PTR";
107 else if (rtype == MDNS_RECORDTYPE_SRV)
108 record_name = "SRV";
109 else if (rtype == MDNS_RECORDTYPE_A)
110 record_name = "A";
111 else if (rtype == MDNS_RECORDTYPE_AAAA)
112 record_name = "AAAA";
113 else if (rtype == MDNS_RECORDTYPE_TXT)
114 record_name = "TXT";
115 else if (rtype == MDNS_RECORDTYPE_ANY)
116 record_name = "ANY";
117 else
118 return 0;
119 printf("Query %s %.*s\n", record_name, MDNS_STRING_FORMAT(name));
120
121 if ((name.length == (sizeof(dns_sd) - 1)) &&
122 (strncmp(name.str, dns_sd, sizeof(dns_sd) - 1) == 0)) {
123 if ((rtype == MDNS_RECORDTYPE_PTR) || (rtype == MDNS_RECORDTYPE_ANY)) {
124 // The PTR query was for the DNS-SD domain, send answer with a PTR record
125 // for the service name we advertise, typically on the
126 // "<_service-name>._tcp.local." format
127
128 // Answer PTR record reverse mapping "<_service-name>._tcp.local." to
129 // "<hostname>.<_service-name>._tcp.local."
130 mdns_record_t answer;
131 answer.name = name;
132 answer.type = MDNS_RECORDTYPE_PTR;
133 answer.data.ptr.name = service->service;
134
135 // Send the answer, unicast or multicast depending on flag in query
136 uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
137 printf(" --> answer %.*s (%s)\n",
138 MDNS_STRING_FORMAT(answer.data.ptr.name),
139 (unicast ? "unicast" : "multicast"));
140
141 if (unicast) {
142 mdns_query_answer_unicast(sock, from, addrlen, sendbuffer,
143 sizeof(sendbuffer), query_id,
144 (mdns_record_type_t)rtype, name.str,
145 name.length, answer, 0, 0, 0, 0);
146 } else {
147 mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer),
148 answer, 0, 0, 0, 0);
149 }
150 }
151 } else if ((name.length == service->service.length) &&
152 (strncmp(name.str, service->service.str, name.length) == 0)) {
153 if ((rtype == MDNS_RECORDTYPE_PTR) || (rtype == MDNS_RECORDTYPE_ANY)) {
154 // The PTR query was for our service (usually
155 // "<_service-name._tcp.local"), answer a PTR record reverse mapping the
156 // queried service name to our service instance name (typically on the
157 // "<hostname>.<_service-name>._tcp.local." format), and add additional
158 // records containing the SRV record mapping the service instance name to
159 // our qualified hostname (typically "<hostname>.local.") and port, as
160 // well as any IPv4/IPv6 address for the hostname as A/AAAA records, and
161 // two test TXT records
162
163 // Answer PTR record reverse mapping "<_service-name>._tcp.local." to
164 // "<hostname>.<_service-name>._tcp.local."
165 mdns_record_t answer = service->record_ptr;
166
167 mdns_record_t additional[5]{{}};
168 size_t additional_count = 0;
169
170 // SRV record mapping "<hostname>.<_service-name>._tcp.local." to
171 // "<hostname>.local." with port. Set weight & priority to 0.
172 additional[additional_count++] = service->record_srv;
173
174 // A/AAAA records mapping "<hostname>.local." to IPv4/IPv6 addresses
175 if (service->address_ipv4.sin_family == AF_INET)
176 additional[additional_count++] = service->record_a;
177 if (service->address_ipv6.sin6_family == AF_INET6)
178 additional[additional_count++] = service->record_aaaa;
179
180 // Add two test TXT records for our service instance name, will be
181 // coalesced into one record with both key-value pair strings by the
182 // library
183 // additional[additional_count++] = service->txt_record[0];
184 // additional[additional_count++] = service->txt_record[1];
185
186 // Send the answer, unicast or multicast depending on flag in query
187 uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
188 printf(" --> answer %.*s (%s)\n",
189 MDNS_STRING_FORMAT(service->record_ptr.data.ptr.name),
190 (unicast ? "unicast" : "multicast"));
191
192 if (unicast) {
193 mdns_query_answer_unicast(
194 sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
195 (mdns_record_type)rtype, name.str, name.length, answer, 0, 0,
196 additional, additional_count);
197 } else {
198 mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer),
199 answer, 0, 0, additional, additional_count);
200 }
201 }
202 } else if ((name.length == service->service_instance.length) &&
203 (strncmp(name.str, service->service_instance.str, name.length) ==
204 0)) {
205 if ((rtype == MDNS_RECORDTYPE_SRV) || (rtype == MDNS_RECORDTYPE_ANY)) {
206 // The SRV query was for our service instance (usually
207 // "<hostname>.<_service-name._tcp.local"), answer a SRV record mapping
208 // the service instance name to our qualified hostname (typically
209 // "<hostname>.local.") and port, as well as any IPv4/IPv6 address for the
210 // hostname as A/AAAA records, and two test TXT records
211
212 // Answer PTR record reverse mapping "<_service-name>._tcp.local." to
213 // "<hostname>.<_service-name>._tcp.local."
214 mdns_record_t answer = service->record_srv;
215
216 mdns_record_t additional[5]{{}};
217 size_t additional_count = 0;
218
219 // A/AAAA records mapping "<hostname>.local." to IPv4/IPv6 addresses
220 if (service->address_ipv4.sin_family == AF_INET)
221 additional[additional_count++] = service->record_a;
222 if (service->address_ipv6.sin6_family == AF_INET6)
223 additional[additional_count++] = service->record_aaaa;
224
225 // Add two test TXT records for our service instance name, will be
226 // coalesced into one record with both key-value pair strings by the
227 // library
228 additional[additional_count++] = service->txt_record[0];
229 additional[additional_count++] = service->txt_record[1];
230
231 // Send the answer, unicast or multicast depending on flag in query
232 uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
233 printf(" --> answer %.*s port %d (%s)\n",
234 MDNS_STRING_FORMAT(service->record_srv.data.srv.name),
235 service->port, (unicast ? "unicast" : "multicast"));
236
237 if (unicast) {
238 mdns_query_answer_unicast(
239 sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
240 (mdns_record_type)rtype, name.str, name.length, answer, 0, 0,
241 additional, additional_count);
242 } else {
243 mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer),
244 answer, 0, 0, additional, additional_count);
245 }
246 }
247 } else if ((name.length == service->hostname_qualified.length) &&
248 (strncmp(name.str, service->hostname_qualified.str, name.length) ==
249 0)) {
250 if (((rtype == MDNS_RECORDTYPE_A) || (rtype == MDNS_RECORDTYPE_ANY)) &&
251 (service->address_ipv4.sin_family == AF_INET)) {
252 // The A query was for our qualified hostname (typically
253 // "<hostname>.local.") and we have an IPv4 address, answer with an A
254 // record mappiing the hostname to an IPv4 address, as well as any IPv6
255 // address for the hostname, and two test TXT records
256
257 // Answer A records mapping "<hostname>.local." to IPv4 address
258 mdns_record_t answer = service->record_a;
259
260 mdns_record_t additional[5]{{}};
261 size_t additional_count = 0;
262
263 // AAAA record mapping "<hostname>.local." to IPv6 addresses
264 if (service->address_ipv6.sin6_family == AF_INET6)
265 additional[additional_count++] = service->record_aaaa;
266
267 // Add two test TXT records for our service instance name, will be
268 // coalesced into one record with both key-value pair strings by the
269 // library
270 additional[additional_count++] = service->txt_record[0];
271 additional[additional_count++] = service->txt_record[1];
272
273 // Send the answer, unicast or multicast depending on flag in query
274 uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
275 mdns_string_t addrstr =
276 ip_address_to_string(addrbuffer, sizeof(addrbuffer),
277 (struct sockaddr*)&service->record_a.data.a.addr,
278 sizeof(service->record_a.data.a.addr));
279 printf(" --> answer %.*s IPv4 %.*s (%s)\n",
280 MDNS_STRING_FORMAT(service->record_a.name),
281 MDNS_STRING_FORMAT(addrstr), (unicast ? "unicast" : "multicast"));
282
283 if (unicast) {
284 mdns_query_answer_unicast(
285 sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
286 (mdns_record_type)rtype, name.str, name.length, answer, 0, 0,
287 additional, additional_count);
288 } else {
289 mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer),
290 answer, 0, 0, additional, additional_count);
291 }
292 } else if (((rtype == MDNS_RECORDTYPE_AAAA) ||
293 (rtype == MDNS_RECORDTYPE_ANY)) &&
294 (service->address_ipv6.sin6_family == AF_INET6)) {
295 // The AAAA query was for our qualified hostname (typically
296 // "<hostname>.local.") and we have an IPv6 address, answer with an AAAA
297 // record mappiing the hostname to an IPv6 address, as well as any IPv4
298 // address for the hostname, and two test TXT records
299
300 // Answer AAAA records mapping "<hostname>.local." to IPv6 address
301 mdns_record_t answer = service->record_aaaa;
302
303 mdns_record_t additional[5]{{}};
304 size_t additional_count = 0;
305
306 // A record mapping "<hostname>.local." to IPv4 addresses
307 if (service->address_ipv4.sin_family == AF_INET)
308 additional[additional_count++] = service->record_a;
309
310 // Add two test TXT records for our service instance name, will be
311 // coalesced into one record with both key-value pair strings by the
312 // library
313 additional[additional_count++] = service->txt_record[0];
314 additional[additional_count++] = service->txt_record[1];
315
316 // Send the answer, unicast or multicast depending on flag in query
317 uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
318 mdns_string_t addrstr = ip_address_to_string(
319 addrbuffer, sizeof(addrbuffer),
320 (struct sockaddr*)&service->record_aaaa.data.aaaa.addr,
321 sizeof(service->record_aaaa.data.aaaa.addr));
322 printf(" --> answer %.*s IPv6 %.*s (%s)\n",
323 MDNS_STRING_FORMAT(service->record_aaaa.name),
324 MDNS_STRING_FORMAT(addrstr), (unicast ? "unicast" : "multicast"));
325
326 if (unicast) {
327 mdns_query_answer_unicast(
328 sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
329 (mdns_record_type)rtype, name.str, name.length, answer, 0, 0,
330 additional, additional_count);
331 } else {
332 mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer),
333 answer, 0, 0, additional, additional_count);
334 }
335 }
336 }
337 return 0;
338}
339
340// Provide a mDNS service, answering incoming DNS-SD and mDNS queries
341void service_mdns(const char* hostname, const char* service_name,
342 int service_port) {
343 int sockets[32];
344 int num_sockets =
345 open_service_sockets(sockets, sizeof(sockets) / sizeof(sockets[0]));
346 if (num_sockets <= 0) {
347 printf("Failed to open any client sockets\n");
348 return;
349 }
350 printf("Opened %d socket%s for mDNS service\n", num_sockets,
351 num_sockets ? "s" : "");
352
353 size_t service_name_length = strlen(service_name);
354 if (!service_name_length) {
355 printf("Invalid service name\n");
356 return;
357 }
358
359 char* service_name_buffer = (char*)malloc(service_name_length + 2);
360 memcpy(service_name_buffer, service_name, service_name_length);
361 if (service_name_buffer[service_name_length - 1] != '.')
362 service_name_buffer[service_name_length++] = '.';
363 service_name_buffer[service_name_length] = 0;
364 service_name = service_name_buffer;
365
366 printf("Service mDNS: %s:%d\n", service_name, service_port);
367 printf("Hostname: %s\n", hostname);
368
369 size_t capacity = 2048;
370 void* buffer = malloc(capacity);
371
372 mdns_string_t service_string;
373 service_string.str = service_name;
374 service_string.length = strlen(service_name);
375 mdns_string_t hostname_string;
376 hostname_string.str = hostname;
377 hostname_string.length = strlen(hostname);
378
379 // Build the service instance "<hostname>.<_service-name>._tcp.local." string
380 char service_instance_buffer[256] = {0};
381 snprintf(service_instance_buffer, sizeof(service_instance_buffer) - 1,
382 "%.*s.%.*s", MDNS_STRING_FORMAT(hostname_string),
383 MDNS_STRING_FORMAT(service_string));
384
385 mdns_string_t service_instance_string;
386 service_instance_string.str = service_instance_buffer;
387 service_instance_string.length = strlen(service_instance_buffer);
388
389 // Build the "<hostname>.local." string
390 char qualified_hostname_buffer[256] = {0};
391 snprintf(qualified_hostname_buffer, sizeof(qualified_hostname_buffer) - 1,
392 "%.*s.local.", MDNS_STRING_FORMAT(hostname_string));
393 mdns_string_t hostname_qualified_string;
394 hostname_qualified_string.str = qualified_hostname_buffer;
395 hostname_qualified_string.length = strlen(qualified_hostname_buffer);
396
397 service_t service{};
398 service.service = service_string;
399 service.hostname = hostname_string;
400 service.service_instance = service_instance_string;
401 service.hostname_qualified = hostname_qualified_string;
402 service.address_ipv4 = service_address_ipv4;
403 service.address_ipv6 = service_address_ipv6;
404 service.port = service_port;
405
406 // Setup our mDNS records
407
408 // PTR record reverse mapping "<_service-name>._tcp.local." to
409 // "<hostname>.<_service-name>._tcp.local."
410 service.record_ptr.name = service.service;
411 service.record_ptr.type = MDNS_RECORDTYPE_PTR;
412 service.record_ptr.rclass = 0;
413 service.record_ptr.ttl = 0;
414 service.record_ptr.data.ptr.name = service.service_instance;
415
416 // SRV record mapping "<hostname>.<_service-name>._tcp.local." to
417 // "<hostname>.local." with port. Set weight & priority to 0.
418 service.record_srv.name = service.service_instance;
419 service.record_srv.type = MDNS_RECORDTYPE_SRV;
420 service.record_srv.data.srv.name = service.hostname_qualified;
421 service.record_srv.data.srv.port = service.port;
422 service.record_srv.data.srv.priority = 0;
423 service.record_srv.data.srv.weight = 0;
424 service.record_srv.rclass = 0;
425 service.record_srv.ttl = 0;
426
427 // A/AAAA records mapping "<hostname>.local." to IPv4/IPv6 addresses
428
429 service.record_a.name = service.hostname_qualified;
430 service.record_a.type = MDNS_RECORDTYPE_A;
431 service.record_a.data.a.addr = service.address_ipv4;
432 service.record_a.rclass = 0;
433 service.record_a.ttl = 0;
434
435 service.record_aaaa.name = service.hostname_qualified;
436 service.record_aaaa.type = MDNS_RECORDTYPE_AAAA;
437 service.record_aaaa.data.aaaa.addr = service.address_ipv6;
438 service.record_aaaa.rclass = 0;
439 service.record_aaaa.ttl = 0;
440
441 // Add two test TXT records for our service instance name, will be coalesced
442 // into one record with both key-value pair strings by the library
443#if 0
444 service.txt_record[0].name = service.service_instance;
445 service.txt_record[0].type = MDNS_RECORDTYPE_TXT;
446 service.txt_record[0].data.txt.key = {MDNS_STRING_CONST("test")};
447 service.txt_record[0].data.txt.value = {MDNS_STRING_CONST("1")};
448 service.txt_record[0].rclass = 0;
449 service.txt_record[0].ttl = 0;
450
451 service.txt_record[1].name = service.service_instance;
452 service.txt_record[1].type = MDNS_RECORDTYPE_TXT;
453 service.txt_record[1].data.txt.key = {MDNS_STRING_CONST("other")};
454 service.txt_record[1].data.txt.value = {MDNS_STRING_CONST("value")};
455 service.txt_record[1].rclass = 0;
456 service.txt_record[1].ttl = 0;
457#endif
458
459 // Send an announcement on startup of service
460 {
461 printf("Sending announce\n");
462 mdns_record_t additional[5]{{}};
463 size_t additional_count = 0;
464 additional[additional_count++] = service.record_srv;
465 if (service.address_ipv4.sin_family == AF_INET)
466 additional[additional_count++] = service.record_a;
467 if (service.address_ipv6.sin6_family == AF_INET6)
468 additional[additional_count++] = service.record_aaaa;
469 // additional[additional_count++] = service.txt_record[0];
470 // additional[additional_count++] = service.txt_record[1];
471
472 for (int isock = 0; isock < num_sockets; ++isock)
473 mdns_announce_multicast(sockets[isock], buffer, capacity,
474 service.record_ptr, 0, 0, additional,
475 additional_count);
476 }
477
478 // This is a crude implementation that checks for incoming queries
479 while (running_server) {
480 int nfds = 0;
481 fd_set readfs;
482 FD_ZERO(&readfs);
483 for (int isock = 0; isock < num_sockets; ++isock) {
484 if (sockets[isock] >= nfds) nfds = sockets[isock] + 1;
485 FD_SET(sockets[isock], &readfs);
486 }
487
488 struct timeval timeout;
489 timeout.tv_sec = 0;
490 timeout.tv_usec = 100000;
491
492 if (select(nfds, &readfs, 0, 0, &timeout) >= 0) {
493 for (int isock = 0; isock < num_sockets; ++isock) {
494 if (FD_ISSET(sockets[isock], &readfs)) {
495 mdns_socket_listen(sockets[isock], buffer, capacity,
496 ocpn_service_callback, &service);
497 }
498 FD_SET(sockets[isock], &readfs);
499 }
500 } else {
501 break;
502 }
503 }
504
505 // Send a goodbye on end of service
506 {
507 printf("Sending goodbye\n");
508 mdns_record_t additional[5]{{}};
509 size_t additional_count = 0;
510 additional[additional_count++] = service.record_srv;
511 if (service.address_ipv4.sin_family == AF_INET)
512 additional[additional_count++] = service.record_a;
513 if (service.address_ipv6.sin6_family == AF_INET6)
514 additional[additional_count++] = service.record_aaaa;
515 additional[additional_count++] = service.txt_record[0];
516 additional[additional_count++] = service.txt_record[1];
517
518 for (int isock = 0; isock < num_sockets; ++isock)
519 mdns_goodbye_multicast(sockets[isock], buffer, capacity,
520 service.record_ptr, 0, 0, additional,
521 additional_count);
522 }
523
524 free(buffer);
525 free(service_name_buffer);
526
527 for (int isock = 0; isock < num_sockets; ++isock)
528 mdns_socket_close(sockets[isock]);
529 printf("Closed socket%s\n", num_sockets ? "s" : "");
530
531 return;
532}
533
534std::string host;
535std::string service;
536
537int StartMDNSService(std::string hostname, std::string service_name,
538 int service_port) {
539 host = hostname;
540 service = service_name;
541
542 std::thread mdns_service_thread(service_mdns, host.c_str(), service.c_str(),
543 service_port);
544 mdns_service_thread.detach();
545
546 return 0;
547}
548
549bool StopMDNSService() { return true; }