librats 0.8.0 → 0.9.0

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.
@@ -98,6 +98,13 @@ public:
98
98
  ev.events = to_epoll_events(events);
99
99
 
100
100
  if (epoll_ctl(epfd_, EPOLL_CTL_MOD, fd, &ev) < 0) {
101
+ // ENOENT is expected during disconnect races —
102
+ // another thread removed the fd between lookup and modify
103
+ if (errno == ENOENT) {
104
+ LOG_POLLER_DEBUG("epoll_ctl MOD: fd " + std::to_string(fd) +
105
+ " already removed (race with disconnect)");
106
+ return false;
107
+ }
101
108
  LOG_POLLER_ERROR("epoll_ctl MOD failed for fd " + std::to_string(fd) +
102
109
  ": " + std::string(strerror(errno)));
103
110
  return false;
@@ -183,6 +183,15 @@ BencodeValue KrpcProtocol::encode_query(const KrpcMessage& message) {
183
183
  break;
184
184
  }
185
185
 
186
+ // BEP 32: advertise which node families we want back
187
+ if (!message.want.empty()) {
188
+ BencodeValue want_list = BencodeValue::create_list();
189
+ for (const auto& w : message.want) {
190
+ want_list.push_back(BencodeValue(w));
191
+ }
192
+ args["want"] = want_list;
193
+ }
194
+
186
195
  root["a"] = args;
187
196
  return root;
188
197
  }
@@ -198,13 +207,24 @@ BencodeValue KrpcProtocol::encode_response(const KrpcMessage& message) {
198
207
  BencodeValue response = BencodeValue::create_dict();
199
208
  response["id"] = BencodeValue(node_id_to_string(message.response_id));
200
209
 
201
- // Add nodes if present
210
+ // Add nodes if present. BEP 32: IPv4 nodes go in "nodes" (26 bytes each),
211
+ // IPv6 nodes go in "nodes6" (38 bytes each).
202
212
  if (!message.nodes.empty()) {
203
- std::string compact_nodes;
213
+ std::string compact_nodes_v4;
214
+ std::string compact_nodes_v6;
204
215
  for (const auto& node : message.nodes) {
205
- compact_nodes += compact_node_info(node);
216
+ if (network_utils::is_valid_ipv6(node.ip)) {
217
+ compact_nodes_v6 += compact_node_info(node);
218
+ } else {
219
+ compact_nodes_v4 += compact_node_info(node);
220
+ }
221
+ }
222
+ if (!compact_nodes_v4.empty()) {
223
+ response["nodes"] = BencodeValue(compact_nodes_v4);
224
+ }
225
+ if (!compact_nodes_v6.empty()) {
226
+ response["nodes6"] = BencodeValue(compact_nodes_v6);
206
227
  }
207
- response["nodes"] = BencodeValue(compact_nodes);
208
228
  }
209
229
 
210
230
  // Add peers if present
@@ -220,8 +240,18 @@ BencodeValue KrpcProtocol::encode_response(const KrpcMessage& message) {
220
240
  if (!message.token.empty()) {
221
241
  response["token"] = BencodeValue(message.token);
222
242
  }
223
-
243
+
224
244
  root["r"] = response;
245
+
246
+ // BEP 42: echo the requester's external address as a top-level "ip" field
247
+ // (compact 6-byte IPv4 / 18-byte IPv6 ip+port).
248
+ if (!message.external_ip.empty()) {
249
+ std::string compact = compact_peer_info(Peer(message.external_ip, message.external_port));
250
+ if (!compact.empty()) {
251
+ root["ip"] = BencodeValue(compact);
252
+ }
253
+ }
254
+
225
255
  return root;
226
256
  }
227
257
 
@@ -295,7 +325,17 @@ std::unique_ptr<KrpcMessage> KrpcProtocol::decode_query(const BencodeValue& data
295
325
  }
296
326
 
297
327
  message->sender_id = string_to_node_id(args["id"].as_string());
298
-
328
+
329
+ // BEP 32: optional "want" list specifying desired node families
330
+ if (args.has_key("want")) {
331
+ const BencodeValue& want_list = args["want"];
332
+ if (want_list.is_list()) {
333
+ for (size_t i = 0; i < want_list.size(); ++i) {
334
+ message->want.push_back(want_list[i].as_string());
335
+ }
336
+ }
337
+ }
338
+
299
339
  switch (message->query_type) {
300
340
  case KrpcQueryType::Ping:
301
341
  // No additional arguments
@@ -347,10 +387,15 @@ std::unique_ptr<KrpcMessage> KrpcProtocol::decode_response(const BencodeValue& d
347
387
 
348
388
  message->response_id = string_to_node_id(response["id"].as_string());
349
389
 
350
- // Parse nodes if present
390
+ // Parse nodes if present (BEP 32: "nodes" = IPv4, "nodes6" = IPv6)
351
391
  if (response.has_key("nodes")) {
352
392
  std::string compact_nodes = response["nodes"].as_string();
353
- message->nodes = parse_compact_node_info(compact_nodes);
393
+ message->nodes = parse_compact_node_info(compact_nodes, /*ipv6=*/false);
394
+ }
395
+ if (response.has_key("nodes6")) {
396
+ std::string compact_nodes6 = response["nodes6"].as_string();
397
+ auto nodes6 = parse_compact_node_info(compact_nodes6, /*ipv6=*/true);
398
+ message->nodes.insert(message->nodes.end(), nodes6.begin(), nodes6.end());
354
399
  }
355
400
 
356
401
  // Parse peers if present
@@ -369,7 +414,16 @@ std::unique_ptr<KrpcMessage> KrpcProtocol::decode_response(const BencodeValue& d
369
414
  if (response.has_key("token")) {
370
415
  message->token = response["token"].as_string();
371
416
  }
372
-
417
+
418
+ // BEP 42: top-level "ip" field tells us our external address as the responder sees it.
419
+ if (data.has_key("ip")) {
420
+ auto endpoints = parse_compact_peer_info(data["ip"].as_string());
421
+ if (!endpoints.empty()) {
422
+ message->external_ip = endpoints[0].ip;
423
+ message->external_port = endpoints[0].port;
424
+ }
425
+ }
426
+
373
427
  return message;
374
428
  }
375
429
 
@@ -415,132 +469,143 @@ NodeId KrpcProtocol::string_to_node_id(const std::string& str) {
415
469
  return id;
416
470
  }
417
471
 
418
- std::string KrpcProtocol::compact_peer_info(const Peer& peer) {
419
- std::string result;
420
- result.reserve(6);
421
-
422
- // Convert IP address to 4 bytes
423
- if (network_utils::is_valid_ipv4(peer.ip)) {
424
- struct in_addr addr;
425
- if (inet_pton(AF_INET, peer.ip.c_str(), &addr) == 1) {
426
- uint32_t ip = ntohl(addr.s_addr);
427
- result += static_cast<char>((ip >> 24) & 0xFF);
428
- result += static_cast<char>((ip >> 16) & 0xFF);
429
- result += static_cast<char>((ip >> 8) & 0xFF);
430
- result += static_cast<char>(ip & 0xFF);
431
- } else {
432
- // Invalid IP, use 0.0.0.0
433
- result += "\x00\x00\x00\x00";
472
+ // Append a compact IP address (4 bytes for IPv4, 16 bytes for IPv6) to `out`.
473
+ // Returns the number of address bytes written, or 0 on failure.
474
+ static size_t append_compact_address(const std::string& ip, std::string& out) {
475
+ if (network_utils::is_valid_ipv6(ip)) {
476
+ struct in6_addr addr6;
477
+ if (inet_pton(AF_INET6, ip.c_str(), &addr6) == 1) {
478
+ out.append(reinterpret_cast<const char*>(addr6.s6_addr), 16);
479
+ return 16;
434
480
  }
435
- } else {
436
- // Not IPv4, use 0.0.0.0
437
- result += "\x00\x00\x00\x00";
481
+ return 0;
438
482
  }
439
-
483
+
484
+ struct in_addr addr;
485
+ if (inet_pton(AF_INET, ip.c_str(), &addr) == 1) {
486
+ uint32_t v = ntohl(addr.s_addr);
487
+ out += static_cast<char>((v >> 24) & 0xFF);
488
+ out += static_cast<char>((v >> 16) & 0xFF);
489
+ out += static_cast<char>((v >> 8) & 0xFF);
490
+ out += static_cast<char>(v & 0xFF);
491
+ return 4;
492
+ }
493
+ // Invalid IPv4, use 0.0.0.0
494
+ out.append(4, '\x00');
495
+ return 4;
496
+ }
497
+
498
+ // Read a compact IP address of `addr_len` bytes (4=IPv4, 16=IPv6) at compact_info[offset]
499
+ // into a printable string.
500
+ static std::string read_compact_address(const std::string& compact_info, size_t offset, size_t addr_len) {
501
+ if (addr_len == 16) {
502
+ struct in6_addr addr6;
503
+ memcpy(addr6.s6_addr, compact_info.data() + offset, 16);
504
+ char ip_str[INET6_ADDRSTRLEN];
505
+ inet_ntop(AF_INET6, &addr6, ip_str, INET6_ADDRSTRLEN);
506
+ return std::string(ip_str);
507
+ }
508
+
509
+ struct in_addr addr;
510
+ uint32_t ip = 0;
511
+ ip |= (static_cast<uint8_t>(compact_info[offset]) << 24);
512
+ ip |= (static_cast<uint8_t>(compact_info[offset + 1]) << 16);
513
+ ip |= (static_cast<uint8_t>(compact_info[offset + 2]) << 8);
514
+ ip |= static_cast<uint8_t>(compact_info[offset + 3]);
515
+ addr.s_addr = htonl(ip);
516
+ char ip_str[INET_ADDRSTRLEN];
517
+ inet_ntop(AF_INET, &addr, ip_str, INET_ADDRSTRLEN);
518
+ return std::string(ip_str);
519
+ }
520
+
521
+ std::string KrpcProtocol::compact_peer_info(const Peer& peer) {
522
+ std::string result;
523
+ result.reserve(18);
524
+
525
+ // IP address (4 bytes IPv4 / 16 bytes IPv6)
526
+ append_compact_address(peer.ip, result);
527
+
440
528
  // Convert port to 2 bytes (network byte order)
441
529
  result += static_cast<char>((peer.port >> 8) & 0xFF);
442
530
  result += static_cast<char>(peer.port & 0xFF);
443
-
531
+
444
532
  return result;
445
533
  }
446
534
 
447
535
  std::string KrpcProtocol::compact_node_info(const KrpcNode& node) {
448
536
  std::string result;
449
- result.reserve(26);
450
-
537
+ result.reserve(38);
538
+
451
539
  // Node ID (20 bytes)
452
540
  result += node_id_to_string(node.id);
453
-
454
- // IP address (4 bytes)
455
- if (network_utils::is_valid_ipv4(node.ip)) {
456
- struct in_addr addr;
457
- if (inet_pton(AF_INET, node.ip.c_str(), &addr) == 1) {
458
- uint32_t ip = ntohl(addr.s_addr);
459
- result += static_cast<char>((ip >> 24) & 0xFF);
460
- result += static_cast<char>((ip >> 16) & 0xFF);
461
- result += static_cast<char>((ip >> 8) & 0xFF);
462
- result += static_cast<char>(ip & 0xFF);
463
- } else {
464
- // Invalid IP, use 0.0.0.0
465
- result += "\x00\x00\x00\x00";
466
- }
467
- } else {
468
- // Not IPv4, use 0.0.0.0
469
- result += "\x00\x00\x00\x00";
470
- }
471
-
541
+
542
+ // IP address (4 bytes IPv4 / 16 bytes IPv6)
543
+ append_compact_address(node.ip, result);
544
+
472
545
  // Port (2 bytes, network byte order)
473
546
  result += static_cast<char>((node.port >> 8) & 0xFF);
474
547
  result += static_cast<char>(node.port & 0xFF);
475
-
548
+
476
549
  return result;
477
550
  }
478
551
 
479
552
  std::vector<Peer> KrpcProtocol::parse_compact_peer_info(const std::string& compact_info) {
480
553
  std::vector<Peer> peers;
481
-
482
- if (compact_info.size() % 6 != 0) {
554
+
555
+ // BEP 5/BEP 7: each "values" entry is a single compact peer: 6 bytes (IPv4) or 18 bytes (IPv6).
556
+ // Detect the family from the entry length. An exact length of 18 is treated as one IPv6 peer;
557
+ // otherwise we chunk by 6 (some implementations concatenate multiple IPv4 peers).
558
+ size_t record_size = 6;
559
+ size_t addr_len = 4;
560
+ if (compact_info.size() == 18) {
561
+ record_size = 18;
562
+ addr_len = 16;
563
+ } else if (compact_info.size() % 6 != 0) {
483
564
  LOG_KRPC_WARN("Invalid compact peer info size: " << compact_info.size());
484
565
  return peers;
485
566
  }
486
-
487
- for (size_t i = 0; i < compact_info.size(); i += 6) {
488
- // Extract IP address (4 bytes)
489
- uint32_t ip = 0;
490
- ip |= (static_cast<uint8_t>(compact_info[i]) << 24);
491
- ip |= (static_cast<uint8_t>(compact_info[i + 1]) << 16);
492
- ip |= (static_cast<uint8_t>(compact_info[i + 2]) << 8);
493
- ip |= static_cast<uint8_t>(compact_info[i + 3]);
494
-
495
- struct in_addr addr;
496
- addr.s_addr = htonl(ip);
497
- char ip_str[INET_ADDRSTRLEN];
498
- inet_ntop(AF_INET, &addr, ip_str, INET_ADDRSTRLEN);
499
-
500
- // Extract port (2 bytes)
567
+
568
+ for (size_t i = 0; i + record_size <= compact_info.size(); i += record_size) {
569
+ std::string ip_str = read_compact_address(compact_info, i, addr_len);
570
+
501
571
  uint16_t port = 0;
502
- port |= (static_cast<uint8_t>(compact_info[i + 4]) << 8);
503
- port |= static_cast<uint8_t>(compact_info[i + 5]);
504
-
572
+ port |= (static_cast<uint8_t>(compact_info[i + addr_len]) << 8);
573
+ port |= static_cast<uint8_t>(compact_info[i + addr_len + 1]);
574
+
505
575
  peers.emplace_back(ip_str, port);
506
576
  }
507
-
577
+
508
578
  return peers;
509
579
  }
510
580
 
511
- std::vector<KrpcNode> KrpcProtocol::parse_compact_node_info(const std::string& compact_info) {
581
+ std::vector<KrpcNode> KrpcProtocol::parse_compact_node_info(const std::string& compact_info, bool ipv6) {
512
582
  std::vector<KrpcNode> nodes;
513
-
514
- if (compact_info.size() % 26 != 0) {
515
- LOG_KRPC_WARN("Invalid compact node info size: " << compact_info.size());
583
+
584
+ const size_t addr_len = ipv6 ? 16 : 4;
585
+ const size_t record_size = 20 + addr_len + 2; // 26 (IPv4) or 38 (IPv6)
586
+
587
+ if (compact_info.size() % record_size != 0) {
588
+ LOG_KRPC_WARN("Invalid compact node info size: " << compact_info.size()
589
+ << " (expected multiple of " << record_size << ")");
516
590
  return nodes;
517
591
  }
518
-
519
- for (size_t i = 0; i < compact_info.size(); i += 26) {
592
+
593
+ for (size_t i = 0; i < compact_info.size(); i += record_size) {
520
594
  // Extract node ID (20 bytes)
521
595
  NodeId node_id;
522
596
  std::copy_n(compact_info.begin() + i, 20, node_id.begin());
523
-
524
- // Extract IP address (4 bytes)
525
- uint32_t ip = 0;
526
- ip |= (static_cast<uint8_t>(compact_info[i + 20]) << 24);
527
- ip |= (static_cast<uint8_t>(compact_info[i + 21]) << 16);
528
- ip |= (static_cast<uint8_t>(compact_info[i + 22]) << 8);
529
- ip |= static_cast<uint8_t>(compact_info[i + 23]);
530
-
531
- struct in_addr addr;
532
- addr.s_addr = htonl(ip);
533
- char ip_str[INET_ADDRSTRLEN];
534
- inet_ntop(AF_INET, &addr, ip_str, INET_ADDRSTRLEN);
535
-
597
+
598
+ // Extract IP address (4 or 16 bytes)
599
+ std::string ip_str = read_compact_address(compact_info, i + 20, addr_len);
600
+
536
601
  // Extract port (2 bytes)
537
602
  uint16_t port = 0;
538
- port |= (static_cast<uint8_t>(compact_info[i + 24]) << 8);
539
- port |= static_cast<uint8_t>(compact_info[i + 25]);
540
-
603
+ port |= (static_cast<uint8_t>(compact_info[i + 20 + addr_len]) << 8);
604
+ port |= static_cast<uint8_t>(compact_info[i + 20 + addr_len + 1]);
605
+
541
606
  nodes.emplace_back(node_id, ip_str, port);
542
607
  }
543
-
608
+
544
609
  return nodes;
545
610
  }
546
611
 
@@ -71,15 +71,26 @@ struct KrpcMessage {
71
71
  bool implied_port; // BEP 5: if true, use UDP source port instead of 'port' field
72
72
  std::string token;
73
73
 
74
+ // BEP 32: requested node families for queries ("n4" and/or "n6").
75
+ // Empty means "no want specified" (responder defaults to the family the request arrived on).
76
+ std::vector<std::string> want;
77
+
74
78
  // For responses
75
79
  NodeId response_id;
76
- std::vector<KrpcNode> nodes;
80
+ std::vector<KrpcNode> nodes; // may mix IPv4/IPv6 nodes; encoder splits into nodes/nodes6
77
81
  std::vector<Peer> peers;
78
-
82
+
83
+ // BEP 42: top-level "ip" field (compact ip+port).
84
+ // On responses we SEND: the requester's external address, so they can learn how the
85
+ // network sees them and derive a compliant node ID.
86
+ // On responses we RECEIVE: our own external address as observed by the responder.
87
+ std::string external_ip;
88
+ uint16_t external_port = 0;
89
+
79
90
  // For errors
80
91
  KrpcErrorCode error_code;
81
92
  std::string error_message;
82
-
93
+
83
94
  KrpcMessage() : type(KrpcMessageType::Query), query_type(KrpcQueryType::Ping), sender_id(), target_id(), info_hash(), port(0), implied_port(false), response_id(), error_code(KrpcErrorCode::GenericError) {}
84
95
  };
85
96
 
@@ -123,10 +134,13 @@ public:
123
134
  */
124
135
  static std::string node_id_to_string(const NodeId& id);
125
136
  static NodeId string_to_node_id(const std::string& str);
137
+ // Compact encodings auto-select IPv4 (6/26 bytes) or IPv6 (18/38 bytes) based on the address.
126
138
  static std::string compact_peer_info(const Peer& peer);
127
139
  static std::string compact_node_info(const KrpcNode& node);
140
+ // parse_compact_peer_info detects the family from the string length (one peer per string).
128
141
  static std::vector<Peer> parse_compact_peer_info(const std::string& compact_info);
129
- static std::vector<KrpcNode> parse_compact_node_info(const std::string& compact_info);
142
+ // parse_compact_node_info reads fixed-size records; pass ipv6=true for 38-byte ("nodes6") records.
143
+ static std::vector<KrpcNode> parse_compact_node_info(const std::string& compact_info, bool ipv6 = false);
130
144
 
131
145
  private:
132
146
  static BencodeValue encode_query(const KrpcMessage& message);
@@ -652,6 +652,9 @@ void RatsClient::handle_disconnect(socket_t socket) {
652
652
  if (gossipsub_) {
653
653
  gossipsub_->handle_peer_disconnected(peer_id);
654
654
  }
655
+ if (file_transfer_manager_) {
656
+ file_transfer_manager_->on_peer_disconnected(peer_id);
657
+ }
655
658
  if (should_schedule_reconnect && running_.load()) {
656
659
  schedule_reconnect(peer_copy_for_reconnect);
657
660
  }