OpenCPN Partial API docs
mDNS_query.cpp
1 /***************************************************************************
2  *
3  * Project: OpenCPN
4  * Purpose: Implement mDNS Query, and friends.
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 <algorithm>
27 #include <memory>
28 #include <thread>
29 
30 #ifdef _WIN32
31 #define _CRT_SECURE_NO_WARNINGS 1
32 #endif
33 
34 #include <stdio.h>
35 
36 #include <errno.h>
37 #include <signal.h>
38 
39 #ifdef _WIN32
40 #include <winsock2.h>
41 #include <iphlpapi.h>
42 #define sleep(x) Sleep(x * 1000)
43 #else
44 #include <netdb.h>
45 #include <ifaddrs.h>
46 #include <net/if.h>
47 #endif
48 
49 #include <wx/datetime.h>
50 #include <wx/log.h>
51 
52 #ifdef __ANDROID__
53 #include "androidUTIL.h"
54 #endif
55 
56 #include "model/cmdline.h"
57 #include "mdns_util.h"
58 #include "model/mdns_cache.h"
59 #include "model/mDNS_query.h"
60 
61 // Static data structs
62 std::vector<ocpn_DNS_record_t> g_sk_servers;
63 
64 static char addrbuffer[64];
65 static char entrybuffer[256];
66 static char namebuffer[256];
67 static char sendbuffer[1024];
68 static mdns_record_txt_t txtbuffer[128];
69 
70 static struct sockaddr_in service_address_ipv4;
71 static struct sockaddr_in6 service_address_ipv6;
72 
73 static int has_ipv4;
74 static int has_ipv6;
75 
76 static void log_printf(const char* fmt, ...) {
77  if (getenv("OCPN_MDNS_DEBUG") ||
78  wxLog::GetActiveTarget()->GetLogLevel() >= wxLOG_Debug) {
79  va_list ap;
80  va_start(ap, fmt);
81  vprintf(fmt, ap);
82  va_end(ap);
83  }
84 }
85 
86 static int ocpn_query_callback(int sock, const struct sockaddr* from,
87  size_t addrlen, mdns_entry_type_t entry,
88  uint16_t query_id, uint16_t rtype,
89  uint16_t rclass, uint32_t ttl, const void* data,
90  size_t size, size_t name_offset,
91  size_t name_length, size_t record_offset,
92  size_t record_length, void* user_data) {
93  (void)sizeof(sock);
94  (void)sizeof(query_id);
95  (void)sizeof(name_length);
96  (void)sizeof(user_data);
97  mdns_string_t fromaddrstr =
98  ip_address_to_string(addrbuffer, sizeof(addrbuffer), from, addrlen);
99  const char* entrytype =
100  (entry == MDNS_ENTRYTYPE_ANSWER)
101  ? "answer"
102  : ((entry == MDNS_ENTRYTYPE_AUTHORITY) ? "authority" : "additional");
103  mdns_string_t entrystr = mdns_string_extract(
104  data, size, &name_offset, entrybuffer, sizeof(entrybuffer));
105  bool is_ipv4 =
106  from->sa_family == AF_INET; // Only ipv4 responses are to be used.
107 
108  if ((rtype == MDNS_RECORDTYPE_PTR) && is_ipv4) {
109  mdns_string_t namestr =
110  mdns_record_parse_ptr(data, size, record_offset, record_length,
111  namebuffer, sizeof(namebuffer));
112  log_printf("%.*s : %s %.*s PTR %.*s rclass 0x%x ttl %u length %d\n",
113  MDNS_STRING_FORMAT(fromaddrstr), entrytype,
114  MDNS_STRING_FORMAT(entrystr), MDNS_STRING_FORMAT(namestr),
115  rclass, ttl, (int)record_length);
116 
117  std::string srv(namestr.str, namestr.length);
118  size_t rh = srv.find("opencpn-object");
119  if (rh > 1) rh--;
120  std::string hostname = srv.substr(0, rh);
121 
122  std::string from(fromaddrstr.str, fromaddrstr.length);
123  size_t r = from.find(':');
124  std::string ip = from.substr(0, r);
125 
126  // Is the destination a portable? Detect by string inspection.
127  std::string port =
128  hostname.find("Portable") == std::string::npos ? "8000" : "8001";
129  MdnsCache::GetInstance().Add(srv, hostname, ip, port);
130  }
131 
132  return 0;
133 }
134 
135 static int sk_query_callback(int sock, const struct sockaddr* from,
136  size_t addrlen, mdns_entry_type_t entry,
137  uint16_t query_id, uint16_t rtype, uint16_t rclass,
138  uint32_t ttl, const void* data, size_t size,
139  size_t name_offset, size_t name_length,
140  size_t record_offset, size_t record_length,
141  void* user_data) {
142  (void)sizeof(sock);
143  (void)sizeof(query_id);
144  (void)sizeof(name_length);
145  (void)sizeof(user_data);
146  mdns_string_t fromaddrstr =
147  ip_address_to_string(addrbuffer, sizeof(addrbuffer), from, addrlen);
148  const char* entrytype =
149  (entry == MDNS_ENTRYTYPE_ANSWER)
150  ? "answer"
151  : ((entry == MDNS_ENTRYTYPE_AUTHORITY) ? "authority" : "additional");
152  mdns_string_t entrystr = mdns_string_extract(
153  data, size, &name_offset, entrybuffer, sizeof(entrybuffer));
154  bool is_ipv4 =
155  from->sa_family == AF_INET; // Only ipv4 responses are to be used.
156 
157  if ((rtype == MDNS_RECORDTYPE_PTR) && is_ipv4) {
158  mdns_string_t namestr =
159  mdns_record_parse_ptr(data, size, record_offset, record_length,
160  namebuffer, sizeof(namebuffer));
161  std::string srv(namestr.str, namestr.length);
162  size_t rh = srv.find("_signalk-ws");
163  if (rh > 1) {
164  rh--;
165  }
166  std::string hostname = srv.substr(0, rh);
167  // Remove non-printable characters as seen in names returned by macOS
168  hostname.erase(remove_if(hostname.begin(), hostname.end(),
169  [](char c) { return !(c >= 0 && c < 128); }),
170  hostname.end());
171  bool found = false;
172  for (const auto& sks : g_sk_servers) {
173  if (sks.hostname == hostname) {
174  found = true;
175  break;
176  }
177  }
178  if (!found) {
179  ocpn_DNS_record_t sk_server;
180  sk_server.service_instance = srv;
181  sk_server.hostname = hostname;
182  g_sk_servers.push_back(sk_server);
183  }
184  } else if ((rtype == MDNS_RECORDTYPE_SRV) && is_ipv4) {
185  mdns_record_srv_t srv =
186  mdns_record_parse_srv(data, size, record_offset, record_length,
187  namebuffer, sizeof(namebuffer));
188  g_sk_servers.back().port = std::to_string(srv.port);
189  } else if ((rtype == MDNS_RECORDTYPE_A) && is_ipv4) {
190  sockaddr_in addr;
191  mdns_record_parse_a(data, size, record_offset, record_length, &addr);
192  mdns_string_t addrstr = ipv4_address_to_string(
193  namebuffer, sizeof(namebuffer), &addr, sizeof(addr));
194  g_sk_servers.back().ip = addrstr.str;
195  } else {
196  // log_printf("SOMETING ELSE\n");
197  }
198  return 0;
199 }
200 
201 // Send a mDNS query
202 int send_mdns_query(mdns_query_t* query, size_t count, size_t timeout_secs,
203  mdns_record_callback_fn callback_function) {
204  int sockets[32];
205  int query_id[32];
206  int num_sockets =
207  open_client_sockets(sockets, sizeof(sockets) / sizeof(sockets[0]), 0);
208  if (num_sockets <= 0) {
209  log_printf("Failed to open any client sockets\n");
210  return -1;
211  }
212  log_printf("Opened %d socket%s for mDNS query\n", num_sockets,
213  num_sockets ? "s" : "");
214 
215  size_t capacity = 2048;
216  void* buffer = malloc(capacity);
217  void* user_data = 0;
218 
219  log_printf("Sending mDNS query");
220  for (size_t iq = 0; iq < count; ++iq) {
221  const char* record_name = "PTR";
222  if (query[iq].type == MDNS_RECORDTYPE_SRV)
223  record_name = "SRV";
224  else if (query[iq].type == MDNS_RECORDTYPE_A)
225  record_name = "A";
226  else if (query[iq].type == MDNS_RECORDTYPE_AAAA)
227  record_name = "AAAA";
228  else
229  query[iq].type = MDNS_RECORDTYPE_PTR;
230  log_printf(" : %s %s", query[iq].name, record_name);
231  }
232  log_printf("\n");
233  for (int isock = 0; isock < num_sockets; ++isock) {
234  query_id[isock] =
235  mdns_multiquery_send(sockets[isock], query, count, buffer, capacity, 0);
236  if (query_id[isock] < 0)
237  log_printf("Failed to send mDNS query: %s\n", strerror(errno));
238  }
239 
240  // This is a simple implementation that loops for timeout_secs or as long as
241  // we get replies
242  int res;
243  log_printf("Reading mDNS query replies\n");
244  int records = 0;
245  do {
246  struct timeval timeout;
247  timeout.tv_sec = timeout_secs;
248  timeout.tv_usec = 0;
249 
250  int nfds = 0;
251  fd_set readfs;
252  FD_ZERO(&readfs);
253  for (int isock = 0; isock < num_sockets; ++isock) {
254  if (sockets[isock] >= nfds) nfds = sockets[isock] + 1;
255  FD_SET(sockets[isock], &readfs);
256  }
257 
258  res = select(nfds, &readfs, 0, 0, &timeout);
259  if (res > 0) {
260  for (int isock = 0; isock < num_sockets; ++isock) {
261  if (FD_ISSET(sockets[isock], &readfs)) {
262  int rec =
263  mdns_query_recv(sockets[isock], buffer, capacity,
264  callback_function, user_data, query_id[isock]);
265  if (rec > 0) records += rec;
266  }
267  FD_SET(sockets[isock], &readfs);
268  }
269  }
270  } while (res > 0);
271 
272  log_printf("Read %d records\n", records);
273 
274  free(buffer);
275 
276  for (int isock = 0; isock < num_sockets; ++isock)
277  mdns_socket_close(sockets[isock]);
278  log_printf("Closed socket%s\n", num_sockets ? "s" : "");
279 
280  return 0;
281 }
282 
283 // Static query definition,
284 // be careful with thread sync if multiple querries used simultaneously
285 mdns_query_t s_query;
286 
287 void FindAllOCPNServers(size_t timeout_secs) {
288  s_query.name = "opencpn-object-control-service";
289  s_query.type = MDNS_RECORDTYPE_PTR;
290  s_query.length = strlen(s_query.name);
291 
292  std::thread{send_mdns_query, &s_query, 1, timeout_secs, ocpn_query_callback}
293  .detach();
294  // send_mdns_query(&query, 1, timeout_secs);
295 }
296 
297 void FindAllSignalKServers(size_t timeout_secs) {
298  g_sk_servers.clear();
299  s_query.name = "_signalk-ws._tcp.local.";
300  s_query.type = MDNS_RECORDTYPE_PTR;
301  s_query.length = strlen(s_query.name);
302 
303  std::thread{send_mdns_query, &s_query, 1, timeout_secs, sk_query_callback}
304  .detach();
305 }
306 
307 std::vector<std::string> get_local_ipv4_addresses() {
308  std::vector<std::string> ret_vec;
309 
310 #ifdef __ANDROID__
311  wxString ipa = androidGetIpV4Address();
312  ret_vec.push_back(ipa.ToStdString());
313 #endif
314 
315  // When sending, each socket can only send to one network interface
316  // Thus we need to open one socket for each interface and address family
317  int num_sockets = 0;
318 
319 #ifdef _WIN32
320 
321  IP_ADAPTER_ADDRESSES* adapter_address = 0;
322  ULONG address_size = 8000;
323  unsigned int ret;
324  unsigned int num_retries = 4;
325  do {
326  adapter_address = (IP_ADAPTER_ADDRESSES*)malloc(address_size);
327  ret = GetAdaptersAddresses(AF_UNSPEC,
328  GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST,
329  0, adapter_address, &address_size);
330  if (ret == ERROR_BUFFER_OVERFLOW) {
331  free(adapter_address);
332  adapter_address = 0;
333  address_size *= 2;
334  } else {
335  break;
336  }
337  } while (num_retries-- > 0);
338 
339  if (!adapter_address || (ret != NO_ERROR)) {
340  free(adapter_address);
341  log_printf("Failed to get network adapter addresses\n");
342  return ret_vec;
343  }
344 
345  int first_ipv4 = 1;
346  int first_ipv6 = 1;
347  for (PIP_ADAPTER_ADDRESSES adapter = adapter_address; adapter;
348  adapter = adapter->Next) {
349  if (adapter->TunnelType == TUNNEL_TYPE_TEREDO) continue;
350  if (adapter->OperStatus != IfOperStatusUp) continue;
351 
352  for (IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress;
353  unicast; unicast = unicast->Next) {
354  if (unicast->Address.lpSockaddr->sa_family == AF_INET) {
355  struct sockaddr_in* saddr =
356  (struct sockaddr_in*)unicast->Address.lpSockaddr;
357  if ((saddr->sin_addr.S_un.S_un_b.s_b1 != 127) ||
358  (saddr->sin_addr.S_un.S_un_b.s_b2 != 0) ||
359  (saddr->sin_addr.S_un.S_un_b.s_b3 != 0) ||
360  (saddr->sin_addr.S_un.S_un_b.s_b4 != 1)) {
361  int log_addr = 0;
362  if (first_ipv4) {
363  service_address_ipv4 = *saddr;
364  first_ipv4 = 0;
365  log_addr = 1;
366  }
367  has_ipv4 = 1;
368 
369  char buffer[128];
370  mdns_string_t addr = ipv4_address_to_string(
371  buffer, sizeof(buffer), saddr, sizeof(struct sockaddr_in));
372  std::string addr_string(addr.str, addr.length);
373  ret_vec.push_back(addr_string);
374  }
375  }
376  }
377  }
378 #if 0
379  else if (unicast->Address.lpSockaddr->sa_family == AF_INET6) {
380  struct sockaddr_in6* saddr = (struct sockaddr_in6*)unicast->Address.lpSockaddr;
381  // Ignore link-local addresses
382  if (saddr->sin6_scope_id)
383  continue;
384  static const unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0,
385  0, 0, 0, 0, 0, 0, 0, 1};
386  static const unsigned char localhost_mapped[] = {0, 0, 0, 0, 0, 0, 0, 0,
387  0, 0, 0xff, 0xff, 0x7f, 0, 0, 1};
388  if ((unicast->DadState == NldsPreferred) &&
389  memcmp(saddr->sin6_addr.s6_addr, localhost, 16) &&
390  memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16)) {
391  int log_addr = 0;
392  if (first_ipv6) {
393  service_address_ipv6 = *saddr;
394  first_ipv6 = 0;
395  log_addr = 1;
396  }
397  has_ipv6 = 1;
398  if (num_sockets < max_sockets) {
399  saddr->sin6_port = htons((unsigned short)port);
400  int sock = mdns_socket_open_ipv6(saddr);
401  if (sock >= 0) {
402  sockets[num_sockets++] = sock;
403  log_addr = 1;
404  } else {
405  log_addr = 0;
406  }
407  }
408  if (log_addr) {
409  char buffer[128];
410  mdns_string_t addr = ipv6_address_to_string(buffer, sizeof(buffer), saddr,
411  sizeof(struct sockaddr_in6));
412  log_printf("Local IPv6 address: %.*s\n", MDNS_STRING_FORMAT(addr));
413  }
414  }
415  }
416  }
417  }
418 
419 #endif
420  free(adapter_address);
421 
422 #endif
423 
424 #if !defined(_WIN32) && !defined(__ANDROID__)
425 
426  struct ifaddrs* ifaddr = 0;
427  struct ifaddrs* ifa = 0;
428 
429  if (getifaddrs(&ifaddr) < 0)
430  log_printf("Unable to get interface addresses\n");
431 
432  int first_ipv4 = 1;
433  int first_ipv6 = 1;
434  for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
435  if (!ifa->ifa_addr) continue;
436  if (!(ifa->ifa_flags & IFF_UP) || !(ifa->ifa_flags & IFF_MULTICAST))
437  continue;
438  if ((ifa->ifa_flags & IFF_LOOPBACK) || (ifa->ifa_flags & IFF_POINTOPOINT))
439  continue;
440 
441  if (ifa->ifa_addr->sa_family == AF_INET) {
442  struct sockaddr_in* saddr = (struct sockaddr_in*)ifa->ifa_addr;
443  if (saddr->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
444  int log_addr = 0;
445  if (first_ipv4) {
446  service_address_ipv4 = *saddr;
447  first_ipv4 = 0;
448  log_addr = 1;
449  }
450  has_ipv4 = 1;
451 
452  char buffer[128];
453  mdns_string_t addr = ipv4_address_to_string(
454  buffer, sizeof(buffer), saddr, sizeof(struct sockaddr_in));
455  std::string addr_string(addr.str, addr.length);
456  ret_vec.push_back(addr_string);
457 #if 0
458  if (num_sockets < max_sockets) {
459  saddr->sin_port = htons(port);
460  int sock = mdns_socket_open_ipv4(saddr);
461  if (sock >= 0) {
462  sockets[num_sockets++] = sock;
463  log_addr = 1;
464  } else {
465  log_addr = 0;
466  }
467  }
468  if (log_addr) {
469  char buffer[128];
470  mdns_string_t addr = ipv4_address_to_string(buffer, sizeof(buffer), saddr,
471  sizeof(struct sockaddr_in));
472  log_printf("Local IPv4 address: %.*s\n", MDNS_STRING_FORMAT(addr));
473  }
474 #endif
475  }
476  }
477 #if 0
478  else if (ifa->ifa_addr->sa_family == AF_INET6) {
479  struct sockaddr_in6* saddr = (struct sockaddr_in6*)ifa->ifa_addr;
480  // Ignore link-local addresses
481  if (saddr->sin6_scope_id)
482  continue;
483  static const unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0,
484  0, 0, 0, 0, 0, 0, 0, 1};
485  static const unsigned char localhost_mapped[] = {0, 0, 0, 0, 0, 0, 0, 0,
486  0, 0, 0xff, 0xff, 0x7f, 0, 0, 1};
487  if (memcmp(saddr->sin6_addr.s6_addr, localhost, 16) &&
488  memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16)) {
489  int log_addr = 0;
490  if (first_ipv6) {
491  service_address_ipv6 = *saddr;
492  first_ipv6 = 0;
493  log_addr = 1;
494  }
495  has_ipv6 = 1;
496  if (num_sockets < max_sockets) {
497  saddr->sin6_port = htons(port);
498  int sock = mdns_socket_open_ipv6(saddr);
499  if (sock >= 0) {
500  sockets[num_sockets++] = sock;
501  log_addr = 1;
502  } else {
503  log_addr = 0;
504  }
505  }
506  if (log_addr) {
507  char buffer[128];
508  mdns_string_t addr = ipv6_address_to_string(buffer, sizeof(buffer), saddr,
509  sizeof(struct sockaddr_in6));
510  log_printf("Local IPv6 address: %.*s\n", MDNS_STRING_FORMAT(addr));
511  }
512  }
513  }
514 #endif
515  }
516 
517  freeifaddrs(ifaddr);
518 
519 #endif
520 
521  return ret_vec;
522 }
bool Add(const Entry &entry)
Add new entry to the cache.
Definition: mdns_cache.cpp:51
Global variables reflecting command line options and arguments.