librats 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/binding.gyp +1 -0
- package/lib/index.d.ts +2 -1
- package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
- package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
- package/native-src/CMakeLists.txt +360 -0
- package/native-src/LICENSE +21 -0
- package/native-src/src/bencode.cpp +485 -0
- package/native-src/src/bencode.h +145 -0
- package/native-src/src/bittorrent.cpp +3682 -0
- package/native-src/src/bittorrent.h +731 -0
- package/native-src/src/dht.cpp +2460 -0
- package/native-src/src/dht.h +508 -0
- package/native-src/src/encrypted_socket.cpp +817 -0
- package/native-src/src/encrypted_socket.h +239 -0
- package/native-src/src/file_transfer.cpp +1808 -0
- package/native-src/src/file_transfer.h +567 -0
- package/native-src/src/fs.cpp +639 -0
- package/native-src/src/fs.h +108 -0
- package/native-src/src/gossipsub.cpp +1137 -0
- package/native-src/src/gossipsub.h +403 -0
- package/native-src/src/ice.cpp +1386 -0
- package/native-src/src/ice.h +328 -0
- package/native-src/src/json.hpp +25526 -0
- package/native-src/src/krpc.cpp +558 -0
- package/native-src/src/krpc.h +145 -0
- package/native-src/src/librats.cpp +2735 -0
- package/native-src/src/librats.h +1732 -0
- package/native-src/src/librats_bittorrent.cpp +167 -0
- package/native-src/src/librats_c.cpp +1333 -0
- package/native-src/src/librats_c.h +239 -0
- package/native-src/src/librats_encryption.cpp +123 -0
- package/native-src/src/librats_file_transfer.cpp +226 -0
- package/native-src/src/librats_gossipsub.cpp +293 -0
- package/native-src/src/librats_ice.cpp +515 -0
- package/native-src/src/librats_logging.cpp +158 -0
- package/native-src/src/librats_mdns.cpp +171 -0
- package/native-src/src/librats_nat.cpp +571 -0
- package/native-src/src/librats_persistence.cpp +815 -0
- package/native-src/src/logger.h +412 -0
- package/native-src/src/mdns.cpp +1178 -0
- package/native-src/src/mdns.h +253 -0
- package/native-src/src/network_utils.cpp +598 -0
- package/native-src/src/network_utils.h +162 -0
- package/native-src/src/noise.cpp +981 -0
- package/native-src/src/noise.h +227 -0
- package/native-src/src/os.cpp +371 -0
- package/native-src/src/os.h +40 -0
- package/native-src/src/rats_export.h +17 -0
- package/native-src/src/sha1.cpp +163 -0
- package/native-src/src/sha1.h +42 -0
- package/native-src/src/socket.cpp +1376 -0
- package/native-src/src/socket.h +309 -0
- package/native-src/src/stun.cpp +484 -0
- package/native-src/src/stun.h +349 -0
- package/native-src/src/threadmanager.cpp +105 -0
- package/native-src/src/threadmanager.h +53 -0
- package/native-src/src/tracker.cpp +1110 -0
- package/native-src/src/tracker.h +268 -0
- package/native-src/src/version.cpp +24 -0
- package/native-src/src/version.h.in +45 -0
- package/native-src/version.rc.in +31 -0
- package/package.json +2 -8
- package/scripts/build-librats.js +59 -12
- package/scripts/prepare-package.js +133 -37
- package/src/librats_node.cpp +46 -1
|
@@ -0,0 +1,1178 @@
|
|
|
1
|
+
#include "mdns.h"
|
|
2
|
+
#include "network_utils.h"
|
|
3
|
+
#include "os.h"
|
|
4
|
+
#include "socket.h"
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <random>
|
|
7
|
+
#include <sstream>
|
|
8
|
+
#include <iomanip>
|
|
9
|
+
#include <cstring>
|
|
10
|
+
|
|
11
|
+
#ifdef _WIN32
|
|
12
|
+
#include <ws2tcpip.h>
|
|
13
|
+
#include <iphlpapi.h>
|
|
14
|
+
#else
|
|
15
|
+
#include <sys/socket.h>
|
|
16
|
+
#include <netinet/in.h>
|
|
17
|
+
#include <arpa/inet.h>
|
|
18
|
+
#include <net/if.h>
|
|
19
|
+
#include <ifaddrs.h>
|
|
20
|
+
#include <unistd.h>
|
|
21
|
+
#endif
|
|
22
|
+
|
|
23
|
+
namespace librats {
|
|
24
|
+
|
|
25
|
+
MdnsClient::MdnsClient(const std::string& service_instance_name, uint16_t service_port)
|
|
26
|
+
: service_instance_name_(service_instance_name),
|
|
27
|
+
service_port_(service_port),
|
|
28
|
+
multicast_socket_(INVALID_SOCKET_VALUE),
|
|
29
|
+
running_(false),
|
|
30
|
+
announcing_(false),
|
|
31
|
+
discovering_(false),
|
|
32
|
+
announcement_interval_(std::chrono::seconds(60)),
|
|
33
|
+
query_interval_(std::chrono::seconds(30)) {
|
|
34
|
+
|
|
35
|
+
// Get local network information
|
|
36
|
+
local_hostname_ = get_local_hostname();
|
|
37
|
+
local_ip_address_ = get_local_ip_address();
|
|
38
|
+
|
|
39
|
+
LOG_MDNS_INFO("Created mDNS client with hostname: " << local_hostname_ << ", IP: " << local_ip_address_);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
MdnsClient::~MdnsClient() {
|
|
43
|
+
stop();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
bool MdnsClient::start() {
|
|
47
|
+
if (running_.load()) {
|
|
48
|
+
LOG_MDNS_WARN("mDNS client is already running");
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
LOG_MDNS_INFO("Starting mDNS client");
|
|
53
|
+
|
|
54
|
+
// Initialize socket library (safe to call multiple times)
|
|
55
|
+
if (!init_socket_library()) {
|
|
56
|
+
LOG_MDNS_ERROR("Failed to initialize socket library");
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Create and configure multicast socket
|
|
61
|
+
if (!create_multicast_socket()) {
|
|
62
|
+
LOG_MDNS_ERROR("Failed to create multicast socket");
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Join multicast group
|
|
67
|
+
if (!join_multicast_group()) {
|
|
68
|
+
LOG_MDNS_ERROR("Failed to join multicast group");
|
|
69
|
+
close_multicast_socket();
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
running_.store(true);
|
|
74
|
+
|
|
75
|
+
// Start receiver thread
|
|
76
|
+
receiver_thread_ = std::thread(&MdnsClient::receiver_loop, this);
|
|
77
|
+
|
|
78
|
+
LOG_MDNS_INFO("mDNS client started successfully");
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
void MdnsClient::stop() {
|
|
83
|
+
if (!running_.load()) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
LOG_MDNS_INFO("Stopping mDNS client");
|
|
88
|
+
|
|
89
|
+
// Trigger immediate shutdown of all background threads
|
|
90
|
+
shutdown_immediate();
|
|
91
|
+
|
|
92
|
+
// Close socket to break receiver loop
|
|
93
|
+
close_multicast_socket();
|
|
94
|
+
|
|
95
|
+
// Wait for threads to finish
|
|
96
|
+
if (receiver_thread_.joinable()) {
|
|
97
|
+
receiver_thread_.join();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (announcer_thread_.joinable()) {
|
|
101
|
+
announcer_thread_.join();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (querier_thread_.joinable()) {
|
|
105
|
+
querier_thread_.join();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Clear discovered services
|
|
109
|
+
{
|
|
110
|
+
std::lock_guard<std::mutex> lock(services_mutex_);
|
|
111
|
+
discovered_services_.clear();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
LOG_MDNS_INFO("mDNS client stopped");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
void MdnsClient::shutdown_immediate() {
|
|
118
|
+
LOG_MDNS_INFO("Triggering immediate shutdown of mDNS background threads");
|
|
119
|
+
|
|
120
|
+
// Stop all operations
|
|
121
|
+
announcing_.store(false);
|
|
122
|
+
discovering_.store(false);
|
|
123
|
+
running_.store(false);
|
|
124
|
+
|
|
125
|
+
// Notify all waiting threads to wake up immediately
|
|
126
|
+
shutdown_cv_.notify_all();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
bool MdnsClient::is_running() const {
|
|
130
|
+
return running_.load();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
bool MdnsClient::announce_service(const std::string& instance_name, uint16_t port,
|
|
134
|
+
const std::map<std::string, std::string>& txt_records) {
|
|
135
|
+
if (!running_.load()) {
|
|
136
|
+
LOG_MDNS_ERROR("mDNS client is not running");
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Update service information
|
|
141
|
+
service_instance_name_ = instance_name;
|
|
142
|
+
service_port_ = port;
|
|
143
|
+
txt_records_ = txt_records;
|
|
144
|
+
|
|
145
|
+
if (announcing_.load()) {
|
|
146
|
+
LOG_MDNS_INFO("Already announcing service, updating information");
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
LOG_MDNS_INFO("Starting service announcement for: " << instance_name << " on port " << port);
|
|
151
|
+
|
|
152
|
+
announcing_.store(true);
|
|
153
|
+
announcer_thread_ = std::thread(&MdnsClient::announcer_loop, this);
|
|
154
|
+
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
void MdnsClient::stop_announcing() {
|
|
159
|
+
if (!announcing_.load()) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
LOG_MDNS_INFO("Stopping service announcement");
|
|
164
|
+
announcing_.store(false);
|
|
165
|
+
|
|
166
|
+
if (announcer_thread_.joinable()) {
|
|
167
|
+
announcer_thread_.join();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
bool MdnsClient::is_announcing() const {
|
|
172
|
+
return announcing_.load();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
void MdnsClient::set_service_callback(MdnsServiceCallback callback) {
|
|
176
|
+
service_callback_ = callback;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
bool MdnsClient::start_discovery() {
|
|
180
|
+
if (!running_.load()) {
|
|
181
|
+
LOG_MDNS_ERROR("mDNS client is not running");
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (discovering_.load()) {
|
|
186
|
+
LOG_MDNS_INFO("Already discovering services");
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
LOG_MDNS_INFO("Starting service discovery");
|
|
191
|
+
|
|
192
|
+
discovering_.store(true);
|
|
193
|
+
querier_thread_ = std::thread(&MdnsClient::querier_loop, this);
|
|
194
|
+
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
void MdnsClient::stop_discovery() {
|
|
199
|
+
if (!discovering_.load()) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
LOG_MDNS_INFO("Stopping service discovery");
|
|
204
|
+
discovering_.store(false);
|
|
205
|
+
|
|
206
|
+
if (querier_thread_.joinable()) {
|
|
207
|
+
querier_thread_.join();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
bool MdnsClient::is_discovering() const {
|
|
212
|
+
return discovering_.load();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
bool MdnsClient::query_services() {
|
|
216
|
+
if (!running_.load()) {
|
|
217
|
+
LOG_MDNS_ERROR("mDNS client is not running");
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
LOG_MDNS_INFO("Sending mDNS query for librats services");
|
|
222
|
+
|
|
223
|
+
DnsMessage query = create_query_message();
|
|
224
|
+
std::vector<uint8_t> packet = serialize_dns_message(query);
|
|
225
|
+
|
|
226
|
+
return send_multicast_packet(packet);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
std::vector<MdnsService> MdnsClient::get_discovered_services() const {
|
|
230
|
+
std::lock_guard<std::mutex> lock(services_mutex_);
|
|
231
|
+
std::vector<MdnsService> services;
|
|
232
|
+
|
|
233
|
+
for (const auto& pair : discovered_services_) {
|
|
234
|
+
services.push_back(pair.second);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return services;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
std::vector<MdnsService> MdnsClient::get_recent_services(std::chrono::seconds max_age) const {
|
|
241
|
+
std::lock_guard<std::mutex> lock(services_mutex_);
|
|
242
|
+
std::vector<MdnsService> recent_services;
|
|
243
|
+
auto now = std::chrono::steady_clock::now();
|
|
244
|
+
|
|
245
|
+
for (const auto& pair : discovered_services_) {
|
|
246
|
+
const MdnsService& service = pair.second;
|
|
247
|
+
auto age = std::chrono::duration_cast<std::chrono::seconds>(now - service.last_seen);
|
|
248
|
+
|
|
249
|
+
if (age <= max_age) {
|
|
250
|
+
recent_services.push_back(service);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return recent_services;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
void MdnsClient::clear_old_services(std::chrono::seconds max_age) {
|
|
258
|
+
std::lock_guard<std::mutex> lock(services_mutex_);
|
|
259
|
+
auto now = std::chrono::steady_clock::now();
|
|
260
|
+
|
|
261
|
+
auto it = discovered_services_.begin();
|
|
262
|
+
while (it != discovered_services_.end()) {
|
|
263
|
+
auto age = std::chrono::duration_cast<std::chrono::seconds>(now - it->second.last_seen);
|
|
264
|
+
|
|
265
|
+
if (age > max_age) {
|
|
266
|
+
LOG_MDNS_DEBUG("Removing old service: " << it->second.service_name);
|
|
267
|
+
it = discovered_services_.erase(it);
|
|
268
|
+
} else {
|
|
269
|
+
++it;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
void MdnsClient::set_announcement_interval(std::chrono::seconds interval) {
|
|
275
|
+
announcement_interval_ = interval;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
void MdnsClient::set_query_interval(std::chrono::seconds interval) {
|
|
279
|
+
query_interval_ = interval;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
bool MdnsClient::create_multicast_socket() {
|
|
283
|
+
multicast_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
284
|
+
if (!librats::is_valid_socket(multicast_socket_)) {
|
|
285
|
+
#ifdef _WIN32
|
|
286
|
+
LOG_MDNS_ERROR("Failed to create UDP socket (error: " << WSAGetLastError() << ")");
|
|
287
|
+
#else
|
|
288
|
+
LOG_MDNS_ERROR("Failed to create UDP socket (error: " << strerror(errno) << ")");
|
|
289
|
+
#endif
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Set socket options for multicast
|
|
294
|
+
int reuse = 1;
|
|
295
|
+
if (setsockopt(multicast_socket_, SOL_SOCKET, SO_REUSEADDR,
|
|
296
|
+
reinterpret_cast<const char*>(&reuse), sizeof(reuse)) < 0) {
|
|
297
|
+
LOG_MDNS_WARN("Failed to set SO_REUSEADDR on multicast socket");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#ifdef SO_REUSEPORT
|
|
301
|
+
if (setsockopt(multicast_socket_, SOL_SOCKET, SO_REUSEPORT,
|
|
302
|
+
reinterpret_cast<const char*>(&reuse), sizeof(reuse)) < 0) {
|
|
303
|
+
LOG_MDNS_WARN("Failed to set SO_REUSEPORT on multicast socket");
|
|
304
|
+
}
|
|
305
|
+
#endif
|
|
306
|
+
|
|
307
|
+
// Bind to mDNS port
|
|
308
|
+
sockaddr_in bind_addr{};
|
|
309
|
+
bind_addr.sin_family = AF_INET;
|
|
310
|
+
bind_addr.sin_addr.s_addr = INADDR_ANY;
|
|
311
|
+
bind_addr.sin_port = htons(MDNS_PORT);
|
|
312
|
+
|
|
313
|
+
if (bind(multicast_socket_, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)) < 0) {
|
|
314
|
+
#ifdef _WIN32
|
|
315
|
+
LOG_MDNS_ERROR("Failed to bind to mDNS port " << MDNS_PORT << " (error: " << WSAGetLastError() << ")");
|
|
316
|
+
#else
|
|
317
|
+
LOG_MDNS_ERROR("Failed to bind to mDNS port " << MDNS_PORT << " (error: " << strerror(errno) << ")");
|
|
318
|
+
#endif
|
|
319
|
+
close_socket(multicast_socket_);
|
|
320
|
+
multicast_socket_ = INVALID_SOCKET_VALUE;
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
LOG_MDNS_DEBUG("Created and bound multicast socket to port " << MDNS_PORT);
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
bool MdnsClient::join_multicast_group() {
|
|
329
|
+
if (!is_valid_socket(multicast_socket_)) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Join IPv4 multicast group
|
|
334
|
+
ip_mreq mreq{};
|
|
335
|
+
inet_pton(AF_INET, MDNS_MULTICAST_IPv4.c_str(), &mreq.imr_multiaddr);
|
|
336
|
+
mreq.imr_interface.s_addr = INADDR_ANY;
|
|
337
|
+
|
|
338
|
+
if (setsockopt(multicast_socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP,
|
|
339
|
+
reinterpret_cast<const char*>(&mreq), sizeof(mreq)) < 0) {
|
|
340
|
+
#ifdef _WIN32
|
|
341
|
+
LOG_MDNS_ERROR("Failed to join IPv4 multicast group (error: " << WSAGetLastError() << ")");
|
|
342
|
+
#else
|
|
343
|
+
LOG_MDNS_ERROR("Failed to join IPv4 multicast group (error: " << strerror(errno) << ")");
|
|
344
|
+
#endif
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Set multicast TTL
|
|
349
|
+
int ttl = 255;
|
|
350
|
+
if (setsockopt(multicast_socket_, IPPROTO_IP, IP_MULTICAST_TTL,
|
|
351
|
+
reinterpret_cast<const char*>(&ttl), sizeof(ttl)) < 0) {
|
|
352
|
+
LOG_MDNS_WARN("Failed to set multicast TTL");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Disable loopback
|
|
356
|
+
int loopback = 0;
|
|
357
|
+
if (setsockopt(multicast_socket_, IPPROTO_IP, IP_MULTICAST_LOOP,
|
|
358
|
+
reinterpret_cast<const char*>(&loopback), sizeof(loopback)) < 0) {
|
|
359
|
+
LOG_MDNS_WARN("Failed to disable multicast loopback");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
LOG_MDNS_DEBUG("Joined IPv4 multicast group: " << MDNS_MULTICAST_IPv4);
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
bool MdnsClient::leave_multicast_group() {
|
|
367
|
+
if (!is_valid_socket(multicast_socket_)) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
ip_mreq mreq{};
|
|
372
|
+
inet_pton(AF_INET, MDNS_MULTICAST_IPv4.c_str(), &mreq.imr_multiaddr);
|
|
373
|
+
mreq.imr_interface.s_addr = INADDR_ANY;
|
|
374
|
+
|
|
375
|
+
if (setsockopt(multicast_socket_, IPPROTO_IP, IP_DROP_MEMBERSHIP,
|
|
376
|
+
reinterpret_cast<const char*>(&mreq), sizeof(mreq)) < 0) {
|
|
377
|
+
LOG_MDNS_WARN("Failed to leave IPv4 multicast group");
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
LOG_MDNS_DEBUG("Left IPv4 multicast group");
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
void MdnsClient::close_multicast_socket() {
|
|
386
|
+
if (librats::is_valid_socket(multicast_socket_)) {
|
|
387
|
+
leave_multicast_group();
|
|
388
|
+
librats::close_socket(multicast_socket_, true);
|
|
389
|
+
multicast_socket_ = INVALID_SOCKET_VALUE;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
void MdnsClient::receiver_loop() {
|
|
394
|
+
LOG_MDNS_DEBUG("mDNS receiver loop started");
|
|
395
|
+
|
|
396
|
+
std::vector<uint8_t> buffer(4096);
|
|
397
|
+
sockaddr_in sender_addr{};
|
|
398
|
+
socklen_t addr_len = sizeof(sender_addr);
|
|
399
|
+
|
|
400
|
+
while (running_.load()) {
|
|
401
|
+
if (!librats::is_valid_socket(multicast_socket_)) {
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
int received = recvfrom(multicast_socket_, reinterpret_cast<char*>(buffer.data()),
|
|
406
|
+
static_cast<int>(buffer.size()), 0, reinterpret_cast<sockaddr*>(&sender_addr), &addr_len);
|
|
407
|
+
|
|
408
|
+
if (received <= 0) {
|
|
409
|
+
if (running_.load()) {
|
|
410
|
+
LOG_MDNS_ERROR("Failed to receive multicast packet");
|
|
411
|
+
}
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Get sender IP address
|
|
416
|
+
char sender_ip[INET_ADDRSTRLEN];
|
|
417
|
+
inet_ntop(AF_INET, &sender_addr.sin_addr, sender_ip, INET_ADDRSTRLEN);
|
|
418
|
+
|
|
419
|
+
// Ignore packets from ourselves
|
|
420
|
+
if (std::string(sender_ip) == local_ip_address_) {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Process the received packet
|
|
425
|
+
std::vector<uint8_t> packet(buffer.begin(), buffer.begin() + received);
|
|
426
|
+
handle_received_packet(packet, std::string(sender_ip));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
LOG_MDNS_DEBUG("mDNS receiver loop ended");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
void MdnsClient::announcer_loop() {
|
|
433
|
+
LOG_MDNS_DEBUG("mDNS announcer loop started");
|
|
434
|
+
|
|
435
|
+
// Send initial announcement immediately
|
|
436
|
+
DnsMessage announcement = create_announcement_message();
|
|
437
|
+
std::vector<uint8_t> packet = serialize_dns_message(announcement);
|
|
438
|
+
send_multicast_packet(packet);
|
|
439
|
+
|
|
440
|
+
auto last_announcement = std::chrono::steady_clock::now();
|
|
441
|
+
|
|
442
|
+
while (announcing_.load() && running_.load()) {
|
|
443
|
+
auto now = std::chrono::steady_clock::now();
|
|
444
|
+
|
|
445
|
+
if (now - last_announcement >= announcement_interval_) {
|
|
446
|
+
// Send periodic announcement
|
|
447
|
+
announcement = create_announcement_message();
|
|
448
|
+
packet = serialize_dns_message(announcement);
|
|
449
|
+
|
|
450
|
+
if (send_multicast_packet(packet)) {
|
|
451
|
+
LOG_MDNS_DEBUG("Sent service announcement");
|
|
452
|
+
} else {
|
|
453
|
+
LOG_MDNS_WARN("Failed to send service announcement");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
last_announcement = now;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Use conditional variable for responsive shutdown
|
|
460
|
+
{
|
|
461
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
462
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(500), [this] { return !announcing_.load() || !running_.load(); })) {
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
LOG_MDNS_DEBUG("mDNS announcer loop ended");
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
void MdnsClient::querier_loop() {
|
|
472
|
+
LOG_MDNS_DEBUG("mDNS querier loop started");
|
|
473
|
+
|
|
474
|
+
// Send initial query immediately
|
|
475
|
+
query_services();
|
|
476
|
+
|
|
477
|
+
auto last_query = std::chrono::steady_clock::now();
|
|
478
|
+
|
|
479
|
+
while (discovering_.load() && running_.load()) {
|
|
480
|
+
auto now = std::chrono::steady_clock::now();
|
|
481
|
+
|
|
482
|
+
if (now - last_query >= query_interval_) {
|
|
483
|
+
// Send periodic query
|
|
484
|
+
if (query_services()) {
|
|
485
|
+
LOG_MDNS_DEBUG("Sent service query");
|
|
486
|
+
} else {
|
|
487
|
+
LOG_MDNS_WARN("Failed to send service query");
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
last_query = now;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Clean up old services
|
|
494
|
+
clear_old_services(std::chrono::seconds(600));
|
|
495
|
+
|
|
496
|
+
// Use conditional variable for responsive shutdown
|
|
497
|
+
{
|
|
498
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
499
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(1000), [this] { return !discovering_.load() || !running_.load(); })) {
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
LOG_MDNS_DEBUG("mDNS querier loop ended");
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
void MdnsClient::handle_received_packet(const std::vector<uint8_t>& packet, const std::string& sender_ip) {
|
|
509
|
+
LOG_MDNS_DEBUG("Received mDNS packet from " << sender_ip << " (" << packet.size() << " bytes)");
|
|
510
|
+
|
|
511
|
+
DnsMessage message;
|
|
512
|
+
if (!deserialize_dns_message(packet, message)) {
|
|
513
|
+
LOG_MDNS_WARN("Failed to deserialize mDNS packet from " << sender_ip);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
process_mdns_message(message, sender_ip);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
void MdnsClient::process_mdns_message(const DnsMessage& message, const std::string& sender_ip) {
|
|
521
|
+
// Check if this is a query or response
|
|
522
|
+
bool is_response = (message.header.flags & static_cast<uint16_t>(MdnsFlags::RESPONSE)) != 0;
|
|
523
|
+
|
|
524
|
+
if (is_response) {
|
|
525
|
+
process_response(message, sender_ip);
|
|
526
|
+
} else {
|
|
527
|
+
process_query(message, sender_ip);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
void MdnsClient::process_query(const DnsMessage& query, const std::string& sender_ip) {
|
|
532
|
+
LOG_MDNS_DEBUG("Processing mDNS query from " << sender_ip);
|
|
533
|
+
|
|
534
|
+
// Check if any questions match our announced service
|
|
535
|
+
if (!announcing_.load()) {
|
|
536
|
+
return; // Not announcing anything
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
for (const auto& question : query.questions) {
|
|
540
|
+
bool should_respond = false;
|
|
541
|
+
|
|
542
|
+
// Check if the question is asking for our service type
|
|
543
|
+
if (question.name == LIBRATS_SERVICE_TYPE && question.type == DnsRecordType::PTR) {
|
|
544
|
+
should_respond = true;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Check if the question is asking for our specific service instance
|
|
548
|
+
std::string our_service_name = create_service_instance_name(service_instance_name_);
|
|
549
|
+
if (question.name == our_service_name) {
|
|
550
|
+
should_respond = true;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (should_respond) {
|
|
554
|
+
LOG_MDNS_DEBUG("Responding to mDNS query for: " << question.name);
|
|
555
|
+
|
|
556
|
+
DnsMessage response = create_response_message(question);
|
|
557
|
+
std::vector<uint8_t> packet = serialize_dns_message(response);
|
|
558
|
+
|
|
559
|
+
// Add a small random delay to avoid response collisions
|
|
560
|
+
std::random_device rd;
|
|
561
|
+
std::mt19937 gen(rd());
|
|
562
|
+
std::uniform_int_distribution<> dis(20, 120);
|
|
563
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(dis(gen)));
|
|
564
|
+
|
|
565
|
+
send_multicast_packet(packet);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
void MdnsClient::process_response(const DnsMessage& response, const std::string& sender_ip) {
|
|
571
|
+
LOG_MDNS_DEBUG("Processing mDNS response from " << sender_ip);
|
|
572
|
+
|
|
573
|
+
extract_service_from_response(response, sender_ip);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
void MdnsClient::extract_service_from_response(const DnsMessage& response, const std::string& sender_ip) {
|
|
577
|
+
MdnsService service;
|
|
578
|
+
service.ip_address = sender_ip;
|
|
579
|
+
service.last_seen = std::chrono::steady_clock::now();
|
|
580
|
+
|
|
581
|
+
bool has_ptr = false;
|
|
582
|
+
bool has_srv = false;
|
|
583
|
+
bool has_txt = false;
|
|
584
|
+
|
|
585
|
+
// Process all answer records
|
|
586
|
+
for (const auto& record : response.answers) {
|
|
587
|
+
if (record.type == DnsRecordType::PTR && is_librats_service(record.name)) {
|
|
588
|
+
// Extract service instance name from PTR record data
|
|
589
|
+
size_t offset = 0;
|
|
590
|
+
service.service_name = read_dns_name(record.data, offset);
|
|
591
|
+
has_ptr = true;
|
|
592
|
+
LOG_MDNS_DEBUG("Found PTR record: " << service.service_name);
|
|
593
|
+
}
|
|
594
|
+
else if (record.type == DnsRecordType::SRV) {
|
|
595
|
+
// Extract SRV record data
|
|
596
|
+
uint16_t priority, weight;
|
|
597
|
+
if (decode_srv_record(record.data, priority, weight, service.port, service.host_name)) {
|
|
598
|
+
has_srv = true;
|
|
599
|
+
LOG_MDNS_DEBUG("Found SRV record: " << service.host_name << ":" << service.port);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
else if (record.type == DnsRecordType::TXT) {
|
|
603
|
+
// Extract TXT record data
|
|
604
|
+
service.txt_records = decode_txt_record(record.data);
|
|
605
|
+
has_txt = true;
|
|
606
|
+
LOG_MDNS_DEBUG("Found TXT record with " << service.txt_records.size() << " entries");
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Process additional records for A records
|
|
611
|
+
for (const auto& record : response.additionals) {
|
|
612
|
+
if (record.type == DnsRecordType::A && record.name == service.host_name) {
|
|
613
|
+
// Override IP address from A record if available
|
|
614
|
+
if (record.data.size() == 4) {
|
|
615
|
+
char ip_str[INET_ADDRSTRLEN];
|
|
616
|
+
inet_ntop(AF_INET, record.data.data(), ip_str, INET_ADDRSTRLEN);
|
|
617
|
+
service.ip_address = std::string(ip_str);
|
|
618
|
+
LOG_MDNS_DEBUG("Found A record: " << service.ip_address);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Only add service if we have the essential information
|
|
624
|
+
if (has_ptr && has_srv && !service.service_name.empty() && service.port > 0) {
|
|
625
|
+
add_or_update_service(service);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
bool MdnsClient::is_librats_service(const std::string& service_name) const {
|
|
630
|
+
return service_name == LIBRATS_SERVICE_TYPE;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
void MdnsClient::add_or_update_service(const MdnsService& service) {
|
|
634
|
+
bool is_new = false;
|
|
635
|
+
|
|
636
|
+
{
|
|
637
|
+
std::lock_guard<std::mutex> lock(services_mutex_);
|
|
638
|
+
auto it = discovered_services_.find(service.service_name);
|
|
639
|
+
|
|
640
|
+
if (it == discovered_services_.end()) {
|
|
641
|
+
// New service
|
|
642
|
+
discovered_services_[service.service_name] = service;
|
|
643
|
+
is_new = true;
|
|
644
|
+
LOG_MDNS_INFO("Discovered new librats service: " << service.service_name
|
|
645
|
+
<< " at " << service.ip_address << ":" << service.port);
|
|
646
|
+
} else {
|
|
647
|
+
// Update existing service
|
|
648
|
+
it->second = service;
|
|
649
|
+
LOG_MDNS_DEBUG("Updated existing librats service: " << service.service_name);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Call callback if registered
|
|
654
|
+
if (service_callback_) {
|
|
655
|
+
try {
|
|
656
|
+
service_callback_(service, is_new);
|
|
657
|
+
} catch (const std::exception& e) {
|
|
658
|
+
LOG_MDNS_ERROR("Exception in service callback: " << e.what());
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
DnsMessage MdnsClient::create_query_message() {
|
|
664
|
+
DnsMessage query;
|
|
665
|
+
|
|
666
|
+
// Set header
|
|
667
|
+
query.header.transaction_id = 0;
|
|
668
|
+
query.header.flags = static_cast<uint16_t>(MdnsFlags::QUERY);
|
|
669
|
+
query.header.question_count = 1;
|
|
670
|
+
|
|
671
|
+
// Add question for librats services
|
|
672
|
+
DnsQuestion question(LIBRATS_SERVICE_TYPE, DnsRecordType::PTR, DnsRecordClass::CLASS_IN);
|
|
673
|
+
query.questions.push_back(question);
|
|
674
|
+
|
|
675
|
+
return query;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
DnsMessage MdnsClient::create_announcement_message() {
|
|
679
|
+
DnsMessage announcement;
|
|
680
|
+
|
|
681
|
+
// Set header
|
|
682
|
+
announcement.header.transaction_id = 0;
|
|
683
|
+
announcement.header.flags = static_cast<uint16_t>(MdnsFlags::AUTHORITATIVE);
|
|
684
|
+
|
|
685
|
+
std::string our_service_name = create_service_instance_name(service_instance_name_);
|
|
686
|
+
std::string our_hostname = local_hostname_;
|
|
687
|
+
if (our_hostname.find(".local.") == std::string::npos) {
|
|
688
|
+
our_hostname += ".local.";
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Create PTR record
|
|
692
|
+
DnsResourceRecord ptr_record = create_ptr_record(LIBRATS_SERVICE_TYPE, our_service_name);
|
|
693
|
+
announcement.answers.push_back(ptr_record);
|
|
694
|
+
|
|
695
|
+
// Create SRV record
|
|
696
|
+
DnsResourceRecord srv_record = create_srv_record(our_service_name, our_hostname, service_port_);
|
|
697
|
+
announcement.answers.push_back(srv_record);
|
|
698
|
+
|
|
699
|
+
// Create TXT record
|
|
700
|
+
DnsResourceRecord txt_record = create_txt_record(our_service_name, txt_records_);
|
|
701
|
+
announcement.answers.push_back(txt_record);
|
|
702
|
+
|
|
703
|
+
// Create A record
|
|
704
|
+
DnsResourceRecord a_record = create_a_record(our_hostname, local_ip_address_);
|
|
705
|
+
announcement.additionals.push_back(a_record);
|
|
706
|
+
|
|
707
|
+
// Update header counts
|
|
708
|
+
announcement.header.answer_count = static_cast<uint16_t>(announcement.answers.size());
|
|
709
|
+
announcement.header.additional_count = static_cast<uint16_t>(announcement.additionals.size());
|
|
710
|
+
|
|
711
|
+
return announcement;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
DnsMessage MdnsClient::create_response_message(const DnsQuestion& question) {
|
|
715
|
+
return create_announcement_message(); // Same as announcement for simplicity
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
DnsResourceRecord MdnsClient::create_ptr_record(const std::string& service_type, const std::string& instance_name, uint32_t ttl) {
|
|
719
|
+
DnsResourceRecord record(service_type, DnsRecordType::PTR, DnsRecordClass::CLASS_IN_FLUSH, ttl);
|
|
720
|
+
|
|
721
|
+
// PTR record data is the instance name
|
|
722
|
+
std::vector<uint8_t> data;
|
|
723
|
+
write_dns_name(data, instance_name);
|
|
724
|
+
record.data = data;
|
|
725
|
+
|
|
726
|
+
return record;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
DnsResourceRecord MdnsClient::create_srv_record(const std::string& instance_name, const std::string& hostname, uint16_t port, uint32_t ttl) {
|
|
730
|
+
DnsResourceRecord record(instance_name, DnsRecordType::SRV, DnsRecordClass::CLASS_IN_FLUSH, ttl);
|
|
731
|
+
|
|
732
|
+
// SRV record format: priority (2), weight (2), port (2), target (variable)
|
|
733
|
+
record.data = encode_srv_record(0, 0, port, hostname);
|
|
734
|
+
|
|
735
|
+
return record;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
DnsResourceRecord MdnsClient::create_txt_record(const std::string& instance_name, const std::map<std::string, std::string>& txt_data, uint32_t ttl) {
|
|
739
|
+
DnsResourceRecord record(instance_name, DnsRecordType::TXT, DnsRecordClass::CLASS_IN_FLUSH, ttl);
|
|
740
|
+
|
|
741
|
+
record.data = encode_txt_record(txt_data);
|
|
742
|
+
|
|
743
|
+
return record;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
DnsResourceRecord MdnsClient::create_a_record(const std::string& hostname, const std::string& ip_address, uint32_t ttl) {
|
|
747
|
+
DnsResourceRecord record(hostname, DnsRecordType::A, DnsRecordClass::CLASS_IN_FLUSH, ttl);
|
|
748
|
+
|
|
749
|
+
// A record data is 4 bytes of IPv4 address
|
|
750
|
+
sockaddr_in addr{};
|
|
751
|
+
inet_pton(AF_INET, ip_address.c_str(), &addr.sin_addr);
|
|
752
|
+
|
|
753
|
+
record.data.resize(4);
|
|
754
|
+
std::memcpy(record.data.data(), &addr.sin_addr, 4);
|
|
755
|
+
|
|
756
|
+
return record;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
std::vector<uint8_t> MdnsClient::serialize_dns_message(const DnsMessage& message) {
|
|
760
|
+
std::vector<uint8_t> buffer;
|
|
761
|
+
|
|
762
|
+
// Write header
|
|
763
|
+
write_uint16(buffer, message.header.transaction_id);
|
|
764
|
+
write_uint16(buffer, message.header.flags);
|
|
765
|
+
write_uint16(buffer, message.header.question_count);
|
|
766
|
+
write_uint16(buffer, message.header.answer_count);
|
|
767
|
+
write_uint16(buffer, message.header.authority_count);
|
|
768
|
+
write_uint16(buffer, message.header.additional_count);
|
|
769
|
+
|
|
770
|
+
// Write questions
|
|
771
|
+
for (const auto& question : message.questions) {
|
|
772
|
+
write_dns_name(buffer, question.name);
|
|
773
|
+
write_uint16(buffer, static_cast<uint16_t>(question.type));
|
|
774
|
+
write_uint16(buffer, static_cast<uint16_t>(question.record_class));
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Write answers
|
|
778
|
+
for (const auto& record : message.answers) {
|
|
779
|
+
write_dns_name(buffer, record.name);
|
|
780
|
+
write_uint16(buffer, static_cast<uint16_t>(record.type));
|
|
781
|
+
write_uint16(buffer, static_cast<uint16_t>(record.record_class));
|
|
782
|
+
write_uint32(buffer, record.ttl);
|
|
783
|
+
write_uint16(buffer, static_cast<uint16_t>(record.data.size()));
|
|
784
|
+
buffer.insert(buffer.end(), record.data.begin(), record.data.end());
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Write authorities (same format as answers)
|
|
788
|
+
for (const auto& record : message.authorities) {
|
|
789
|
+
write_dns_name(buffer, record.name);
|
|
790
|
+
write_uint16(buffer, static_cast<uint16_t>(record.type));
|
|
791
|
+
write_uint16(buffer, static_cast<uint16_t>(record.record_class));
|
|
792
|
+
write_uint32(buffer, record.ttl);
|
|
793
|
+
write_uint16(buffer, static_cast<uint16_t>(record.data.size()));
|
|
794
|
+
buffer.insert(buffer.end(), record.data.begin(), record.data.end());
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Write additionals (same format as answers)
|
|
798
|
+
for (const auto& record : message.additionals) {
|
|
799
|
+
write_dns_name(buffer, record.name);
|
|
800
|
+
write_uint16(buffer, static_cast<uint16_t>(record.type));
|
|
801
|
+
write_uint16(buffer, static_cast<uint16_t>(record.record_class));
|
|
802
|
+
write_uint32(buffer, record.ttl);
|
|
803
|
+
write_uint16(buffer, static_cast<uint16_t>(record.data.size()));
|
|
804
|
+
buffer.insert(buffer.end(), record.data.begin(), record.data.end());
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return buffer;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
bool MdnsClient::deserialize_dns_message(const std::vector<uint8_t>& data, DnsMessage& message) {
|
|
811
|
+
if (data.size() < 12) { // Minimum DNS header size
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
size_t offset = 0;
|
|
816
|
+
|
|
817
|
+
try {
|
|
818
|
+
// Read header
|
|
819
|
+
message.header.transaction_id = read_uint16(data, offset);
|
|
820
|
+
message.header.flags = read_uint16(data, offset);
|
|
821
|
+
message.header.question_count = read_uint16(data, offset);
|
|
822
|
+
message.header.answer_count = read_uint16(data, offset);
|
|
823
|
+
message.header.authority_count = read_uint16(data, offset);
|
|
824
|
+
message.header.additional_count = read_uint16(data, offset);
|
|
825
|
+
|
|
826
|
+
// Read questions
|
|
827
|
+
message.questions.clear();
|
|
828
|
+
for (uint16_t i = 0; i < message.header.question_count; ++i) {
|
|
829
|
+
DnsQuestion question;
|
|
830
|
+
question.name = read_dns_name(data, offset);
|
|
831
|
+
question.type = static_cast<DnsRecordType>(read_uint16(data, offset));
|
|
832
|
+
question.record_class = static_cast<DnsRecordClass>(read_uint16(data, offset));
|
|
833
|
+
message.questions.push_back(question);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Read answers
|
|
837
|
+
message.answers.clear();
|
|
838
|
+
for (uint16_t i = 0; i < message.header.answer_count; ++i) {
|
|
839
|
+
DnsResourceRecord record;
|
|
840
|
+
record.name = read_dns_name(data, offset);
|
|
841
|
+
record.type = static_cast<DnsRecordType>(read_uint16(data, offset));
|
|
842
|
+
record.record_class = static_cast<DnsRecordClass>(read_uint16(data, offset));
|
|
843
|
+
record.ttl = read_uint32(data, offset);
|
|
844
|
+
uint16_t data_length = read_uint16(data, offset);
|
|
845
|
+
|
|
846
|
+
if (offset + data_length > data.size()) {
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
record.data.assign(data.begin() + offset, data.begin() + offset + data_length);
|
|
851
|
+
offset += data_length;
|
|
852
|
+
|
|
853
|
+
message.answers.push_back(record);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Read authorities (skip for simplicity)
|
|
857
|
+
for (uint16_t i = 0; i < message.header.authority_count; ++i) {
|
|
858
|
+
read_dns_name(data, offset); // name
|
|
859
|
+
read_uint16(data, offset); // type
|
|
860
|
+
read_uint16(data, offset); // class
|
|
861
|
+
read_uint32(data, offset); // ttl
|
|
862
|
+
uint16_t data_length = read_uint16(data, offset);
|
|
863
|
+
offset += data_length;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Read additionals
|
|
867
|
+
message.additionals.clear();
|
|
868
|
+
for (uint16_t i = 0; i < message.header.additional_count; ++i) {
|
|
869
|
+
DnsResourceRecord record;
|
|
870
|
+
record.name = read_dns_name(data, offset);
|
|
871
|
+
record.type = static_cast<DnsRecordType>(read_uint16(data, offset));
|
|
872
|
+
record.record_class = static_cast<DnsRecordClass>(read_uint16(data, offset));
|
|
873
|
+
record.ttl = read_uint32(data, offset);
|
|
874
|
+
uint16_t data_length = read_uint16(data, offset);
|
|
875
|
+
|
|
876
|
+
if (offset + data_length > data.size()) {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
record.data.assign(data.begin() + offset, data.begin() + offset + data_length);
|
|
881
|
+
offset += data_length;
|
|
882
|
+
|
|
883
|
+
message.additionals.push_back(record);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
return true;
|
|
887
|
+
|
|
888
|
+
} catch (const std::exception& e) {
|
|
889
|
+
LOG_MDNS_ERROR("Exception while deserializing DNS message: " << e.what());
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
void MdnsClient::write_dns_name(std::vector<uint8_t>& buffer, const std::string& name) {
|
|
895
|
+
std::string normalized = normalize_dns_name(name);
|
|
896
|
+
|
|
897
|
+
std::istringstream iss(normalized);
|
|
898
|
+
std::string label;
|
|
899
|
+
|
|
900
|
+
while (std::getline(iss, label, '.')) {
|
|
901
|
+
if (label.empty()) continue;
|
|
902
|
+
|
|
903
|
+
if (label.length() > 63) {
|
|
904
|
+
LOG_MDNS_WARN("DNS label too long, truncating: " << label);
|
|
905
|
+
label = label.substr(0, 63);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
buffer.push_back(static_cast<uint8_t>(label.length()));
|
|
909
|
+
buffer.insert(buffer.end(), label.begin(), label.end());
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
buffer.push_back(0); // Root label
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
std::string MdnsClient::read_dns_name(const std::vector<uint8_t>& buffer, size_t& offset) {
|
|
916
|
+
std::string name;
|
|
917
|
+
bool jumped = false;
|
|
918
|
+
size_t original_offset = offset;
|
|
919
|
+
int jumps = 0;
|
|
920
|
+
|
|
921
|
+
while (offset < buffer.size() && jumps < 10) {
|
|
922
|
+
uint8_t length = buffer[offset++];
|
|
923
|
+
|
|
924
|
+
if (length == 0) {
|
|
925
|
+
break; // End of name
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if ((length & 0xC0) == 0xC0) {
|
|
929
|
+
// Compression pointer
|
|
930
|
+
if (!jumped) {
|
|
931
|
+
original_offset = offset + 1;
|
|
932
|
+
jumped = true;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
uint16_t pointer = ((length & 0x3F) << 8) | buffer[offset++];
|
|
936
|
+
offset = pointer;
|
|
937
|
+
jumps++;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (offset + length > buffer.size()) {
|
|
942
|
+
break; // Invalid length
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (!name.empty()) {
|
|
946
|
+
name += ".";
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
name.append(reinterpret_cast<const char*>(&buffer[offset]), length);
|
|
950
|
+
offset += length;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
if (jumped) {
|
|
954
|
+
offset = original_offset;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return name;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
void MdnsClient::write_uint16(std::vector<uint8_t>& buffer, uint16_t value) {
|
|
961
|
+
buffer.push_back(static_cast<uint8_t>(value >> 8));
|
|
962
|
+
buffer.push_back(static_cast<uint8_t>(value & 0xFF));
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
void MdnsClient::write_uint32(std::vector<uint8_t>& buffer, uint32_t value) {
|
|
966
|
+
buffer.push_back(static_cast<uint8_t>(value >> 24));
|
|
967
|
+
buffer.push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
|
|
968
|
+
buffer.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
|
|
969
|
+
buffer.push_back(static_cast<uint8_t>(value & 0xFF));
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
uint16_t MdnsClient::read_uint16(const std::vector<uint8_t>& buffer, size_t& offset) {
|
|
973
|
+
if (offset + 2 > buffer.size()) {
|
|
974
|
+
throw std::out_of_range("Buffer too small for uint16");
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
uint16_t value = (static_cast<uint16_t>(buffer[offset]) << 8) | buffer[offset + 1];
|
|
978
|
+
offset += 2;
|
|
979
|
+
return value;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
uint32_t MdnsClient::read_uint32(const std::vector<uint8_t>& buffer, size_t& offset) {
|
|
983
|
+
if (offset + 4 > buffer.size()) {
|
|
984
|
+
throw std::out_of_range("Buffer too small for uint32");
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
uint32_t value = (static_cast<uint32_t>(buffer[offset]) << 24) |
|
|
988
|
+
(static_cast<uint32_t>(buffer[offset + 1]) << 16) |
|
|
989
|
+
(static_cast<uint32_t>(buffer[offset + 2]) << 8) |
|
|
990
|
+
buffer[offset + 3];
|
|
991
|
+
offset += 4;
|
|
992
|
+
return value;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
std::vector<uint8_t> MdnsClient::encode_txt_record(const std::map<std::string, std::string>& txt_data) {
|
|
996
|
+
std::vector<uint8_t> data;
|
|
997
|
+
|
|
998
|
+
if (txt_data.empty()) {
|
|
999
|
+
// Empty TXT record
|
|
1000
|
+
data.push_back(0);
|
|
1001
|
+
return data;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
for (const auto& pair : txt_data) {
|
|
1005
|
+
std::string entry = pair.first;
|
|
1006
|
+
if (!pair.second.empty()) {
|
|
1007
|
+
entry += "=" + pair.second;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
if (entry.length() > 255) {
|
|
1011
|
+
LOG_MDNS_WARN("TXT record entry too long, truncating: " << entry);
|
|
1012
|
+
entry = entry.substr(0, 255);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
data.push_back(static_cast<uint8_t>(entry.length()));
|
|
1016
|
+
data.insert(data.end(), entry.begin(), entry.end());
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
return data;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
std::map<std::string, std::string> MdnsClient::decode_txt_record(const std::vector<uint8_t>& txt_data) {
|
|
1023
|
+
std::map<std::string, std::string> result;
|
|
1024
|
+
|
|
1025
|
+
size_t offset = 0;
|
|
1026
|
+
while (offset < txt_data.size()) {
|
|
1027
|
+
uint8_t length = txt_data[offset++];
|
|
1028
|
+
|
|
1029
|
+
if (length == 0 || offset + length > txt_data.size()) {
|
|
1030
|
+
break;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
std::string entry(reinterpret_cast<const char*>(&txt_data[offset]), length);
|
|
1034
|
+
offset += length;
|
|
1035
|
+
|
|
1036
|
+
// Parse key=value format
|
|
1037
|
+
size_t eq_pos = entry.find('=');
|
|
1038
|
+
if (eq_pos != std::string::npos) {
|
|
1039
|
+
std::string key = entry.substr(0, eq_pos);
|
|
1040
|
+
std::string value = entry.substr(eq_pos + 1);
|
|
1041
|
+
result[key] = value;
|
|
1042
|
+
} else {
|
|
1043
|
+
result[entry] = "";
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return result;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
std::vector<uint8_t> MdnsClient::encode_srv_record(uint16_t priority, uint16_t weight, uint16_t port, const std::string& target) {
|
|
1051
|
+
std::vector<uint8_t> data;
|
|
1052
|
+
|
|
1053
|
+
write_uint16(data, priority);
|
|
1054
|
+
write_uint16(data, weight);
|
|
1055
|
+
write_uint16(data, port);
|
|
1056
|
+
write_dns_name(data, target);
|
|
1057
|
+
|
|
1058
|
+
return data;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
bool MdnsClient::decode_srv_record(const std::vector<uint8_t>& srv_data, uint16_t& priority, uint16_t& weight, uint16_t& port, std::string& target) {
|
|
1062
|
+
if (srv_data.size() < 6) {
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
size_t offset = 0;
|
|
1067
|
+
|
|
1068
|
+
try {
|
|
1069
|
+
priority = read_uint16(srv_data, offset);
|
|
1070
|
+
weight = read_uint16(srv_data, offset);
|
|
1071
|
+
port = read_uint16(srv_data, offset);
|
|
1072
|
+
target = read_dns_name(srv_data, offset);
|
|
1073
|
+
return true;
|
|
1074
|
+
} catch (const std::exception&) {
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
std::string MdnsClient::get_local_hostname() {
|
|
1080
|
+
librats::SystemInfo sys_info = librats::get_system_info();
|
|
1081
|
+
std::string hostname = sys_info.hostname;
|
|
1082
|
+
|
|
1083
|
+
if (hostname.empty()) {
|
|
1084
|
+
hostname = "unknown-host";
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// Replace spaces and special characters
|
|
1088
|
+
std::replace_if(hostname.begin(), hostname.end(),
|
|
1089
|
+
[](char c) { return !std::isalnum(c) && c != '-'; }, '-');
|
|
1090
|
+
|
|
1091
|
+
return hostname;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
std::string MdnsClient::get_local_ip_address() {
|
|
1095
|
+
auto addresses = librats::network_utils::get_local_interface_addresses();
|
|
1096
|
+
|
|
1097
|
+
// Prefer non-loopback IPv4 addresses
|
|
1098
|
+
for (const auto& addr : addresses) {
|
|
1099
|
+
if (addr != "127.0.0.1" && addr != "::1" && addr.find(':') == std::string::npos) {
|
|
1100
|
+
return addr;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Fall back to first available address
|
|
1105
|
+
if (!addresses.empty()) {
|
|
1106
|
+
return addresses[0];
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return "127.0.0.1";
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
std::string MdnsClient::create_service_instance_name(const std::string& instance_name) {
|
|
1113
|
+
std::string clean_name = instance_name;
|
|
1114
|
+
|
|
1115
|
+
// Replace spaces and special characters
|
|
1116
|
+
std::replace_if(clean_name.begin(), clean_name.end(),
|
|
1117
|
+
[](char c) { return !std::isalnum(c) && c != '-'; }, '-');
|
|
1118
|
+
|
|
1119
|
+
if (clean_name.empty()) {
|
|
1120
|
+
clean_name = "librats-node";
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return clean_name + "." + LIBRATS_SERVICE_TYPE;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
std::string MdnsClient::extract_instance_name_from_service(const std::string& service_name) {
|
|
1127
|
+
size_t pos = service_name.find("." + LIBRATS_SERVICE_TYPE);
|
|
1128
|
+
if (pos != std::string::npos) {
|
|
1129
|
+
return service_name.substr(0, pos);
|
|
1130
|
+
}
|
|
1131
|
+
return service_name;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
bool MdnsClient::send_multicast_packet(const std::vector<uint8_t>& packet) {
|
|
1135
|
+
if (!librats::is_valid_socket(multicast_socket_)) {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
sockaddr_in dest_addr{};
|
|
1140
|
+
dest_addr.sin_family = AF_INET;
|
|
1141
|
+
inet_pton(AF_INET, MDNS_MULTICAST_IPv4.c_str(), &dest_addr.sin_addr);
|
|
1142
|
+
dest_addr.sin_port = htons(MDNS_PORT);
|
|
1143
|
+
|
|
1144
|
+
int sent = sendto(multicast_socket_, reinterpret_cast<const char*>(packet.data()),
|
|
1145
|
+
static_cast<int>(packet.size()), 0, reinterpret_cast<sockaddr*>(&dest_addr), sizeof(dest_addr));
|
|
1146
|
+
|
|
1147
|
+
if (sent < 0 || static_cast<size_t>(sent) != packet.size()) {
|
|
1148
|
+
#ifdef _WIN32
|
|
1149
|
+
LOG_MDNS_ERROR("Failed to send multicast packet (error: " << WSAGetLastError() << ")");
|
|
1150
|
+
#else
|
|
1151
|
+
LOG_MDNS_ERROR("Failed to send multicast packet (error: " << strerror(errno) << ")");
|
|
1152
|
+
#endif
|
|
1153
|
+
return false;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
return true;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
bool MdnsClient::is_valid_dns_name(const std::string& name) const {
|
|
1160
|
+
if (name.empty() || name.length() > 255) {
|
|
1161
|
+
return false;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
return true; // Simplified validation
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
std::string MdnsClient::normalize_dns_name(const std::string& name) const {
|
|
1168
|
+
std::string normalized = name;
|
|
1169
|
+
|
|
1170
|
+
// Ensure name ends with dot if not empty
|
|
1171
|
+
if (!normalized.empty() && normalized.back() != '.') {
|
|
1172
|
+
normalized += '.';
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
return normalized;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
} // namespace librats
|