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