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.
Files changed (66) hide show
  1. package/README.md +1 -1
  2. package/binding.gyp +1 -0
  3. package/lib/index.d.ts +2 -1
  4. package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
  5. package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
  6. package/native-src/CMakeLists.txt +360 -0
  7. package/native-src/LICENSE +21 -0
  8. package/native-src/src/bencode.cpp +485 -0
  9. package/native-src/src/bencode.h +145 -0
  10. package/native-src/src/bittorrent.cpp +3682 -0
  11. package/native-src/src/bittorrent.h +731 -0
  12. package/native-src/src/dht.cpp +2460 -0
  13. package/native-src/src/dht.h +508 -0
  14. package/native-src/src/encrypted_socket.cpp +817 -0
  15. package/native-src/src/encrypted_socket.h +239 -0
  16. package/native-src/src/file_transfer.cpp +1808 -0
  17. package/native-src/src/file_transfer.h +567 -0
  18. package/native-src/src/fs.cpp +639 -0
  19. package/native-src/src/fs.h +108 -0
  20. package/native-src/src/gossipsub.cpp +1137 -0
  21. package/native-src/src/gossipsub.h +403 -0
  22. package/native-src/src/ice.cpp +1386 -0
  23. package/native-src/src/ice.h +328 -0
  24. package/native-src/src/json.hpp +25526 -0
  25. package/native-src/src/krpc.cpp +558 -0
  26. package/native-src/src/krpc.h +145 -0
  27. package/native-src/src/librats.cpp +2735 -0
  28. package/native-src/src/librats.h +1732 -0
  29. package/native-src/src/librats_bittorrent.cpp +167 -0
  30. package/native-src/src/librats_c.cpp +1333 -0
  31. package/native-src/src/librats_c.h +239 -0
  32. package/native-src/src/librats_encryption.cpp +123 -0
  33. package/native-src/src/librats_file_transfer.cpp +226 -0
  34. package/native-src/src/librats_gossipsub.cpp +293 -0
  35. package/native-src/src/librats_ice.cpp +515 -0
  36. package/native-src/src/librats_logging.cpp +158 -0
  37. package/native-src/src/librats_mdns.cpp +171 -0
  38. package/native-src/src/librats_nat.cpp +571 -0
  39. package/native-src/src/librats_persistence.cpp +815 -0
  40. package/native-src/src/logger.h +412 -0
  41. package/native-src/src/mdns.cpp +1178 -0
  42. package/native-src/src/mdns.h +253 -0
  43. package/native-src/src/network_utils.cpp +598 -0
  44. package/native-src/src/network_utils.h +162 -0
  45. package/native-src/src/noise.cpp +981 -0
  46. package/native-src/src/noise.h +227 -0
  47. package/native-src/src/os.cpp +371 -0
  48. package/native-src/src/os.h +40 -0
  49. package/native-src/src/rats_export.h +17 -0
  50. package/native-src/src/sha1.cpp +163 -0
  51. package/native-src/src/sha1.h +42 -0
  52. package/native-src/src/socket.cpp +1376 -0
  53. package/native-src/src/socket.h +309 -0
  54. package/native-src/src/stun.cpp +484 -0
  55. package/native-src/src/stun.h +349 -0
  56. package/native-src/src/threadmanager.cpp +105 -0
  57. package/native-src/src/threadmanager.h +53 -0
  58. package/native-src/src/tracker.cpp +1110 -0
  59. package/native-src/src/tracker.h +268 -0
  60. package/native-src/src/version.cpp +24 -0
  61. package/native-src/src/version.h.in +45 -0
  62. package/native-src/version.rc.in +31 -0
  63. package/package.json +2 -8
  64. package/scripts/build-librats.js +59 -12
  65. package/scripts/prepare-package.js +133 -37
  66. 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