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