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