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