OpenCPN Partial API docs
Loading...
Searching...
No Matches
mdns_query.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 <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
62std::vector<ocpn_DNS_record_t> g_sk_servers;
63
64static char addrbuffer[64];
65static char entrybuffer[256];
66static char namebuffer[256];
67static char sendbuffer[1024];
68static mdns_record_txt_t txtbuffer[128];
69
70static struct sockaddr_in service_address_ipv4;
71static struct sockaddr_in6 service_address_ipv6;
72
73static int has_ipv4;
74static int has_ipv6;
75
76static 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
86static 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
135static 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); }),
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
202int 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
285mdns_query_t s_query;
286
287void 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
297void 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
307std::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.
Global variables reflecting command line options and arguments.
MdnsCache mDNS host lookups cache.
mDNS lookup wrappers.