OpenCPN Partial API docs
Loading...
Searching...
No Matches
zeroconf-detail.hpp
1#ifndef ZEROCONF_DETAIL_HPP
2#define ZEROCONF_DETAIL_HPP
3
5// zeroconf-detail.hpp
6
7// (C) Copyright 2016 Yuri Yakovlev <yvzmail@gmail.com>
8// Use, modification and distribution is subject to the GNU General Public
9// License
10
11#include <vector>
12#include <memory>
13#include <chrono>
14
15#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32)
16#define WIN32
17#endif
18
19#ifdef WIN32
20#define WIN32_LEAN_AND_MEAN
21#include <winsock2.h>
22#else
23#ifndef __WXOSX__
24#include <error.h>
25#else
26#include <mach/error.h>
27#endif
28
29#include <string.h>
30#include <unistd.h>
31#include <sys/socket.h>
32#include <netinet/in.h>
33#endif
34
35#include "zeroconf-util.hpp"
36
37namespace Zeroconf {
38namespace Detail {
39const size_t MdnsMessageMaxLength = 512;
40const size_t MdnsRecordHeaderLength = 12;
41
42const uint8_t MdnsOffsetToken = 0xC0;
43const uint16_t MdnsResponseFlag = 0x8400;
44
45const uint32_t SockTrue = 1;
46
47const uint8_t MdnsQueryHeader[] = {
48 0x00, 0x00, // ID
49 0x00, 0x00, // Flags
50 0x00, 0x01, // QDCOUNT
51 0x00, 0x00, // ANCOUNT
52 0x00, 0x00, // NSCOUNT
53 0x00, 0x00 // ARCOUNT
54};
55
56const uint8_t MdnsQueryFooter[] = {
57 0x00, 0x0c, // QTYPE
58 0x00, 0x01 // QCLASS
59};
60
62 sockaddr_storage peer;
63 std::vector<uint8_t> data;
64};
65
67 uint16_t type;
68 size_t pos;
69 size_t len;
70 std::string name;
71};
72
74 sockaddr_storage peer;
75 uint16_t qtype;
76 std::string qname;
77 std::vector<uint8_t> data;
78 std::vector<mdns_record> records;
79};
80
81inline int GetSocketError() {
82#ifdef WIN32
83 return WSAGetLastError();
84#else
85 return errno;
86#endif
87}
88
89inline void CloseSocket(int fd) {
90#ifdef WIN32
91 closesocket(fd);
92#else
93 close(fd);
94#endif
95}
96
97inline void WriteFqdn(const std::string& name, std::vector<uint8_t>* result) {
98 size_t len = 0;
99 size_t pos = result->size();
100 result->push_back(0);
101
102 for (size_t i = 0; i < name.size(); i++) {
103 if (name[i] != '.') {
104 result->push_back(name[i]);
105 len++;
106
107 if (len > UINT8_MAX) {
108 result->clear();
109 break;
110 }
111 }
112
113 if (name[i] == '.' || i == name.size() - 1) {
114 if (len == 0) continue;
115
116 result->at(pos) = len; // update component length
117
118 len = 0;
119 pos = result->size();
120 result->push_back(0); // length placeholder or trailing zero
121 }
122 }
123}
124
125inline size_t ReadFqdn(const std::vector<uint8_t>& data, size_t offset,
126 std::string* result) {
127 result->clear();
128
129 size_t pos = offset;
130 while (1) {
131 if (pos >= data.size()) return 0;
132
133 uint8_t len = data[pos++];
134
135 if (pos + len > data.size()) return 0;
136
137 if (len == 0) break;
138
139 if (!result->empty()) result->append(".");
140
141 result->append(reinterpret_cast<const char*>(&data[pos]), len);
142 pos += len;
143 }
144
145 return pos - offset;
146}
147
148inline bool CreateSocket(int* result) {
149 int fd = socket(AF_INET, SOCK_DGRAM, 0);
150 if (fd < 0) {
151 Log::Error("Failed to create socket with code " +
152 std::to_string(GetSocketError()));
153 return false;
154 }
155
156 int st =
157 setsockopt(fd, SOL_SOCKET, SO_BROADCAST,
158 reinterpret_cast<const char*>(&SockTrue), sizeof(SockTrue));
159 if (st < 0) {
160 CloseSocket(fd);
161 Log::Error("Failed to set socket option SO_BROADCAST with code " +
162 std::to_string(GetSocketError()));
163 return false;
164 }
165
166 *result = fd;
167 return true;
168}
169
170inline bool Send(int fd, const std::vector<uint8_t>& data) {
171 sockaddr_in broadcastAddr = {0};
172 broadcastAddr.sin_family = AF_INET;
173 broadcastAddr.sin_port = htons(5353);
174 broadcastAddr.sin_addr.s_addr = INADDR_BROADCAST;
175
176 auto st = sendto(fd, reinterpret_cast<const char*>(&data[0]), data.size(), 0,
177 reinterpret_cast<const sockaddr*>(&broadcastAddr),
178 sizeof(broadcastAddr));
179
180 // todo: st == data.size() ???
181 if (st < 0) {
182 Log::Error("Failed to send the query with code " +
183 std::to_string(GetSocketError()));
184 return false;
185 }
186
187 return true;
188}
189
190inline bool Receive(int fd, time_t scanTime,
191 std::vector<raw_responce>* result) {
192 auto start = std::chrono::system_clock::now();
193
194 while (1) {
195 auto now = std::chrono::system_clock::now();
196 if (now - start > std::chrono::seconds(scanTime)) break;
197
198 fd_set fds;
199 FD_ZERO(&fds);
200 FD_SET(fd, &fds);
201
202 timeval tv = {0};
203 tv.tv_sec = static_cast<long>(scanTime);
204
205 int st = select(fd + 1, &fds, nullptr, nullptr, &tv);
206
207 if (st < 0) {
208 Log::Error("Failed to wait on socket with code " +
209 std::to_string(GetSocketError()));
210 return false;
211 }
212
213 if (st > 0) {
214#ifdef WIN32
215 int salen = sizeof(sockaddr_storage);
216#else
217 unsigned int salen = sizeof(sockaddr_storage);
218#endif
219
220 raw_responce item;
221 item.data.resize(MdnsMessageMaxLength);
222
223#ifndef __OCPN__ANDROID__
224 auto cb =
225 recvfrom(fd, reinterpret_cast<char*>(&item.data[0]), item.data.size(),
226 0, reinterpret_cast<sockaddr*>(&item.peer), &salen);
227#else
228
229#ifdef ANDROID_ARM64
230 auto cb = recvfrom(
231 fd, reinterpret_cast<char*>(&item.data[0]), item.data.size(), 0,
232 reinterpret_cast<sockaddr*>(&item.peer), (unsigned int*)&salen);
233#else
234 auto cb =
235 recvfrom(fd, reinterpret_cast<char*>(&item.data[0]), item.data.size(),
236 0, reinterpret_cast<sockaddr*>(&item.peer), (int*)&salen);
237#endif
238
239#endif
240 if (cb < 0) {
241 Log::Error("Failed to receive with code " +
242 std::to_string(GetSocketError()));
243 return false;
244 }
245
246 item.data.resize((size_t)cb);
247 result->push_back(item);
248 }
249 }
250 return true;
251}
252
253inline bool Parse(const raw_responce& input, mdns_responce* result) {
254 // Structure:
255 // header (12b)
256 // qname fqdn
257 // qtype (2b)
258 // qclass (2b)
259 // 0xc0
260 // name offset (1b)
261 // DNS RR
262 //
263 // Note:
264 // GCC has bug in is.ignore(n)
265
266 if (input.data.empty()) return false;
267
268 result->qname.clear();
269 result->records.clear();
270
271 memcpy(&result->peer, &input.peer, sizeof(sockaddr_storage));
272 result->data = input.data;
273
274 stdext::membuf buf(&input.data[0], input.data.size());
275 std::istream is(&buf);
276
277 const auto Flags =
278 std::istream::failbit | std::istream::badbit | std::istream::eofbit;
279 is.exceptions(Flags);
280
281 try {
282 uint8_t u8;
283 uint16_t u16;
284
285 is.ignore(); // id
286 is.ignore();
287
288 is.read(reinterpret_cast<char*>(&u16), 2); // flags
289 if (ntohs(u16) != MdnsResponseFlag) {
290 Log::Warning("Found unexpected Flags value while parsing responce");
291 return false;
292 }
293
294 for (auto i = 0; i < 8; i++)
295 is.ignore(); // qdcount, ancount, nscount, arcount
296
297 size_t cb =
298 ReadFqdn(input.data, static_cast<size_t>(is.tellg()), &result->qname);
299 if (cb == 0) {
300 Log::Error("Failed to parse query name");
301 return false;
302 }
303
304 for (unsigned int i = 0; i < cb; i++) is.ignore(); // qname
305
306 is.read(reinterpret_cast<char*>(&u16), 2); // qtype
307 result->qtype = ntohs(u16);
308
309 is.ignore(); // qclass
310 is.ignore();
311
312 while (1) {
313 is.exceptions(std::istream::goodbit);
314 if (is.peek() == EOF) break;
315 is.exceptions(Flags);
316
317 mdns_record rr = {0};
318 rr.pos = static_cast<size_t>(is.tellg());
319
320 is.read(reinterpret_cast<char*>(&u8), 1); // offset token
321 if (u8 != MdnsOffsetToken) {
322 Log::Warning("Found incorrect offset token while parsing responce");
323 return false;
324 }
325
326 is.read(reinterpret_cast<char*>(&u8), 1); // offset value
327 if ((size_t)u8 >= input.data.size() ||
328 (size_t)u8 + input.data[u8] >= input.data.size()) {
329 Log::Warning("Failed to parse record name");
330 return false;
331 }
332
333 rr.name = std::string(reinterpret_cast<const char*>(&input.data[u8 + 1]),
334 input.data[u8]);
335
336 is.read(reinterpret_cast<char*>(&u16), 2); // type
337 rr.type = ntohs(u16);
338
339 for (auto i = 0; i < 6; i++) is.ignore(); // qclass, ttl
340
341 is.read(reinterpret_cast<char*>(&u16), 2); // length
342
343 for (auto i = 0; i < ntohs(u16); i++) is.ignore(); // data
344
345 rr.len = MdnsRecordHeaderLength + ntohs(u16);
346 result->records.push_back(rr);
347 }
348 } catch (const std::istream::failure& ex) {
349 Log::Warning(std::string("Stream error while parsing responce: ") +
350 ex.what());
351 return false;
352 }
353
354 return true;
355}
356
357inline bool Resolve(const std::string& serviceName, time_t scanTime,
358 std::vector<mdns_responce>* result) {
359 result->clear();
360
361 std::vector<uint8_t> query;
362 query.insert(query.end(), std::begin(MdnsQueryHeader),
363 std::end(MdnsQueryHeader));
364 WriteFqdn(serviceName, &query);
365 query.insert(query.end(), std::begin(MdnsQueryFooter),
366 std::end(MdnsQueryFooter));
367
368 int fd = 0;
369 if (!CreateSocket(&fd)) return false;
370
371 std::shared_ptr<void> guard(0, [fd](void*) { CloseSocket(fd); });
372
373 if (!Send(fd, query)) return false;
374
375 std::vector<raw_responce> responces;
376 if (!Receive(fd, scanTime, &responces)) return false;
377
378 for (auto& raw : responces) {
379 mdns_responce parsed = {{0}};
380 if (Parse(raw, &parsed)) result->push_back(parsed);
381 }
382
383 return true;
384}
385} // namespace Detail
386} // namespace Zeroconf
387
388#endif // ZEROCONF_DETAIL_HPP