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