librats 1.0.2 → 2.2.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.
Files changed (319) hide show
  1. package/README.md +145 -331
  2. package/binding.gyp +16 -3
  3. package/lib/index.d.ts +288 -696
  4. package/lib/index.js +407 -44
  5. package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
  6. package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
  7. package/native-src/CMakeLists.txt +404 -179
  8. package/native-src/LICENSE +1 -1
  9. package/native-src/src/librats/bindings/rats.cpp +762 -0
  10. package/native-src/src/librats/bindings/rats.h +380 -0
  11. package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
  12. package/native-src/src/librats/bittorrent/bencode.h +176 -0
  13. package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
  14. package/native-src/src/librats/bittorrent/bitfield.h +76 -0
  15. package/native-src/src/librats/bittorrent/byte_io.h +58 -0
  16. package/native-src/src/librats/bittorrent/choker.cpp +25 -0
  17. package/native-src/src/librats/bittorrent/choker.h +46 -0
  18. package/native-src/src/librats/bittorrent/client.cpp +413 -0
  19. package/native-src/src/librats/bittorrent/client.h +227 -0
  20. package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
  21. package/native-src/src/librats/bittorrent/disk_io.h +150 -0
  22. package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
  23. package/native-src/src/librats/bittorrent/extensions.h +94 -0
  24. package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
  25. package/native-src/src/librats/bittorrent/file_storage.h +79 -0
  26. package/native-src/src/librats/bittorrent/log.h +42 -0
  27. package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
  28. package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
  29. package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
  30. package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
  31. package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
  32. package/native-src/src/librats/bittorrent/peer_list.h +75 -0
  33. package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
  34. package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
  35. package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
  36. package/native-src/src/librats/bittorrent/reactor.h +89 -0
  37. package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
  38. package/native-src/src/librats/bittorrent/resume_data.h +41 -0
  39. package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
  40. package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
  41. package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
  42. package/native-src/src/librats/bittorrent/torrent.h +260 -0
  43. package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
  44. package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
  45. package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
  46. package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
  47. package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
  48. package/native-src/src/librats/bittorrent/tracker.h +108 -0
  49. package/native-src/src/librats/bittorrent/types.cpp +206 -0
  50. package/native-src/src/librats/bittorrent/types.h +86 -0
  51. package/native-src/src/librats/core/address.cpp +35 -0
  52. package/native-src/src/librats/core/address.h +78 -0
  53. package/native-src/src/librats/core/bytes.h +69 -0
  54. package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
  55. package/native-src/src/librats/core/chained_send_buffer.h +183 -0
  56. package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
  57. package/native-src/src/librats/core/endpoint_parse.h +31 -0
  58. package/native-src/src/librats/core/event_bus.h +70 -0
  59. package/native-src/src/librats/core/host_endpoint.h +56 -0
  60. package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
  61. package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
  62. package/native-src/src/librats/core/ip_address.cpp +120 -0
  63. package/native-src/src/librats/core/ip_address.h +109 -0
  64. package/native-src/src/librats/core/mpsc_queue.h +47 -0
  65. package/native-src/src/librats/core/notifier.h +74 -0
  66. package/native-src/src/librats/core/receive_buffer.cpp +219 -0
  67. package/native-src/src/librats/core/receive_buffer.h +171 -0
  68. package/native-src/src/librats/core/service_registry.h +58 -0
  69. package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
  70. package/native-src/src/librats/core/socket.h +496 -0
  71. package/native-src/src/librats/core/timer_queue.h +105 -0
  72. package/native-src/src/librats/core/types.cpp +43 -0
  73. package/native-src/src/librats/core/types.h +103 -0
  74. package/native-src/src/librats/core/wakeup_pipe.h +83 -0
  75. package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
  76. package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
  77. package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
  78. package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
  79. package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
  80. package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
  81. package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
  82. package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
  83. package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
  84. package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
  85. package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
  86. package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
  87. package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
  88. package/native-src/src/librats/crypto/hkdf.c +266 -0
  89. package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
  90. package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
  91. package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
  92. package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
  93. package/native-src/src/librats/crypto/poly1305.h +37 -0
  94. package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
  95. package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
  96. package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
  97. package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
  98. package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
  99. package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
  100. package/native-src/src/librats/dht/announce.cpp +37 -0
  101. package/native-src/src/librats/dht/announce.h +41 -0
  102. package/native-src/src/librats/dht/bep42.cpp +109 -0
  103. package/native-src/src/librats/dht/bep42.h +48 -0
  104. package/native-src/src/librats/dht/dht.cpp +501 -0
  105. package/native-src/src/librats/dht/dht.h +119 -0
  106. package/native-src/src/librats/dht/dht_runner.cpp +103 -0
  107. package/native-src/src/librats/dht/dht_runner.h +71 -0
  108. package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
  109. package/native-src/src/librats/dht/dos_blocker.h +47 -0
  110. package/native-src/src/librats/dht/find_peers.cpp +52 -0
  111. package/native-src/src/librats/dht/find_peers.h +73 -0
  112. package/native-src/src/librats/dht/id.h +167 -0
  113. package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
  114. package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
  115. package/native-src/src/librats/dht/log.h +38 -0
  116. package/native-src/src/librats/dht/node.cpp +473 -0
  117. package/native-src/src/librats/dht/node.h +164 -0
  118. package/native-src/src/librats/dht/node_entry.h +81 -0
  119. package/native-src/src/librats/dht/observer.h +72 -0
  120. package/native-src/src/librats/dht/persistence.cpp +90 -0
  121. package/native-src/src/librats/dht/persistence.h +32 -0
  122. package/native-src/src/librats/dht/routing_table.cpp +559 -0
  123. package/native-src/src/librats/dht/routing_table.h +185 -0
  124. package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
  125. package/native-src/src/librats/dht/rpc_manager.h +77 -0
  126. package/native-src/src/librats/dht/storage.cpp +92 -0
  127. package/native-src/src/librats/dht/storage.h +74 -0
  128. package/native-src/src/librats/dht/transport.h +27 -0
  129. package/native-src/src/librats/dht/traversal.cpp +326 -0
  130. package/native-src/src/librats/dht/traversal.h +120 -0
  131. package/native-src/src/librats/dht/udp_transport.cpp +49 -0
  132. package/native-src/src/librats/dht/udp_transport.h +51 -0
  133. package/native-src/src/librats/mdns/log.h +22 -0
  134. package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
  135. package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
  136. package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
  137. package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
  138. package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
  139. package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
  140. package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
  141. package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
  142. package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
  143. package/native-src/src/librats/node/circuit_service.h +84 -0
  144. package/native-src/src/librats/node/config.h +110 -0
  145. package/native-src/src/librats/node/dial_service.h +54 -0
  146. package/native-src/src/librats/node/dialer.cpp +264 -0
  147. package/native-src/src/librats/node/dialer.h +188 -0
  148. package/native-src/src/librats/node/host_events.h +26 -0
  149. package/native-src/src/librats/node/identify.cpp +130 -0
  150. package/native-src/src/librats/node/identify.h +71 -0
  151. package/native-src/src/librats/node/nat_status.cpp +103 -0
  152. package/native-src/src/librats/node/nat_status.h +118 -0
  153. package/native-src/src/librats/node/node.cpp +865 -0
  154. package/native-src/src/librats/node/node.h +344 -0
  155. package/native-src/src/librats/node/node_context.h +33 -0
  156. package/native-src/src/librats/node/peer_network.h +91 -0
  157. package/native-src/src/librats/peer/peer.h +49 -0
  158. package/native-src/src/librats/peer/peer_book.cpp +181 -0
  159. package/native-src/src/librats/peer/peer_book.h +88 -0
  160. package/native-src/src/librats/peer/peer_id.cpp +72 -0
  161. package/native-src/src/librats/peer/peer_id.h +62 -0
  162. package/native-src/src/librats/peer/peer_info.h +37 -0
  163. package/native-src/src/librats/peer/peer_table.cpp +170 -0
  164. package/native-src/src/librats/peer/peer_table.h +148 -0
  165. package/native-src/src/librats/security/handshaker.h +66 -0
  166. package/native-src/src/librats/security/identity.h +43 -0
  167. package/native-src/src/librats/security/noise_security.cpp +122 -0
  168. package/native-src/src/librats/security/noise_security.h +37 -0
  169. package/native-src/src/librats/security/plaintext_security.h +106 -0
  170. package/native-src/src/librats/security/session.h +37 -0
  171. package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
  172. package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
  173. package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
  174. package/native-src/src/librats/subsystems/bittorrent.h +136 -0
  175. package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
  176. package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
  177. package/native-src/src/librats/subsystems/dht_service.h +36 -0
  178. package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
  179. package/native-src/src/librats/subsystems/file_transfer.h +367 -0
  180. package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
  181. package/native-src/src/librats/subsystems/hole_punch.h +290 -0
  182. package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
  183. package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
  184. package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
  185. package/native-src/src/librats/subsystems/message_json.cpp +112 -0
  186. package/native-src/src/librats/subsystems/message_json.h +88 -0
  187. package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
  188. package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
  189. package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
  190. package/native-src/src/librats/subsystems/ping_service.h +66 -0
  191. package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
  192. package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
  193. package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
  194. package/native-src/src/librats/subsystems/pubsub.h +175 -0
  195. package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
  196. package/native-src/src/librats/subsystems/reconnection.h +126 -0
  197. package/native-src/src/librats/subsystems/relay.cpp +1142 -0
  198. package/native-src/src/librats/subsystems/relay.h +211 -0
  199. package/native-src/src/librats/subsystems/relay_service.h +46 -0
  200. package/native-src/src/librats/transport/connection.cpp +343 -0
  201. package/native-src/src/librats/transport/connection.h +283 -0
  202. package/native-src/src/librats/transport/link.h +96 -0
  203. package/native-src/src/librats/transport/reactor.cpp +588 -0
  204. package/native-src/src/librats/transport/reactor.h +262 -0
  205. package/native-src/src/librats/transport/reactor_pool.h +81 -0
  206. package/native-src/src/librats/transport/relay_link.cpp +208 -0
  207. package/native-src/src/librats/transport/relay_link.h +303 -0
  208. package/native-src/src/librats/transport/tcp_link.cpp +49 -0
  209. package/native-src/src/librats/transport/tcp_link.h +43 -0
  210. package/native-src/src/librats/transport/udp_mux.cpp +617 -0
  211. package/native-src/src/librats/transport/udp_mux.h +363 -0
  212. package/native-src/src/librats/transport/udp_packet.cpp +121 -0
  213. package/native-src/src/librats/transport/udp_packet.h +190 -0
  214. package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
  215. package/native-src/src/librats/transport/udp_stream.h +614 -0
  216. package/native-src/src/librats/util/features.h.in +51 -0
  217. package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
  218. package/native-src/src/librats/util/fs.h +136 -0
  219. package/native-src/src/librats/util/json.cpp +1002 -0
  220. package/native-src/src/librats/util/json.h +444 -0
  221. package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
  222. package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
  223. package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
  224. package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
  225. package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
  226. package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
  227. package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
  228. package/native-src/src/librats/util/rats_export.h +69 -0
  229. package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
  230. package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
  231. package/native-src/src/librats/wire/frame.cpp +76 -0
  232. package/native-src/src/librats/wire/frame.h +111 -0
  233. package/native-src/src/librats/wire/message_router.cpp +45 -0
  234. package/native-src/src/librats/wire/message_router.h +47 -0
  235. package/package.json +5 -4
  236. package/scripts/build-librats.js +1 -0
  237. package/scripts/postinstall.js +3 -3
  238. package/scripts/prepare-package.js +4 -4
  239. package/scripts/verify-installation.js +63 -105
  240. package/src/librats_node.cpp +1067 -1323
  241. package/native-src/src/bencode.cpp +0 -485
  242. package/native-src/src/bencode.h +0 -145
  243. package/native-src/src/bittorrent.cpp +0 -14
  244. package/native-src/src/bittorrent.h +0 -74
  245. package/native-src/src/bt_bitfield.cpp +0 -372
  246. package/native-src/src/bt_bitfield.h +0 -316
  247. package/native-src/src/bt_choker.cpp +0 -228
  248. package/native-src/src/bt_choker.h +0 -147
  249. package/native-src/src/bt_client.cpp +0 -1047
  250. package/native-src/src/bt_client.h +0 -445
  251. package/native-src/src/bt_create_torrent.cpp +0 -677
  252. package/native-src/src/bt_create_torrent.h +0 -473
  253. package/native-src/src/bt_extension.cpp +0 -469
  254. package/native-src/src/bt_extension.h +0 -309
  255. package/native-src/src/bt_file_storage.cpp +0 -261
  256. package/native-src/src/bt_file_storage.h +0 -298
  257. package/native-src/src/bt_handshake.cpp +0 -134
  258. package/native-src/src/bt_handshake.h +0 -157
  259. package/native-src/src/bt_messages.cpp +0 -364
  260. package/native-src/src/bt_messages.h +0 -324
  261. package/native-src/src/bt_network.cpp +0 -1007
  262. package/native-src/src/bt_network.h +0 -417
  263. package/native-src/src/bt_peer_connection.cpp +0 -742
  264. package/native-src/src/bt_peer_connection.h +0 -592
  265. package/native-src/src/bt_piece_picker.cpp +0 -786
  266. package/native-src/src/bt_piece_picker.h +0 -473
  267. package/native-src/src/bt_resume_data.cpp +0 -410
  268. package/native-src/src/bt_resume_data.h +0 -249
  269. package/native-src/src/bt_torrent.cpp +0 -2120
  270. package/native-src/src/bt_torrent.h +0 -641
  271. package/native-src/src/bt_torrent_info.cpp +0 -659
  272. package/native-src/src/bt_torrent_info.h +0 -418
  273. package/native-src/src/bt_types.h +0 -621
  274. package/native-src/src/chained_send_buffer.cpp +0 -75
  275. package/native-src/src/chained_send_buffer.h +0 -137
  276. package/native-src/src/crypto/hkdf.c +0 -266
  277. package/native-src/src/crypto/poly1305.h +0 -36
  278. package/native-src/src/dht.cpp +0 -3311
  279. package/native-src/src/dht.h +0 -717
  280. package/native-src/src/disk_io.cpp +0 -632
  281. package/native-src/src/disk_io.h +0 -315
  282. package/native-src/src/file_transfer.cpp +0 -1415
  283. package/native-src/src/file_transfer.h +0 -286
  284. package/native-src/src/fs.h +0 -108
  285. package/native-src/src/gossipsub.cpp +0 -1139
  286. package/native-src/src/gossipsub.h +0 -403
  287. package/native-src/src/ice.cpp +0 -893
  288. package/native-src/src/ice.h +0 -559
  289. package/native-src/src/json.hpp +0 -25526
  290. package/native-src/src/librats.cpp +0 -2378
  291. package/native-src/src/librats.h +0 -2324
  292. package/native-src/src/librats_bittorrent.cpp +0 -601
  293. package/native-src/src/librats_c.cpp +0 -1557
  294. package/native-src/src/librats_c.h +0 -323
  295. package/native-src/src/librats_discovery.cpp +0 -402
  296. package/native-src/src/librats_encryption.cpp +0 -275
  297. package/native-src/src/librats_file_transfer.cpp +0 -144
  298. package/native-src/src/librats_gossipsub.cpp +0 -289
  299. package/native-src/src/librats_ice.cpp +0 -213
  300. package/native-src/src/librats_log_macros.h +0 -36
  301. package/native-src/src/librats_logging.cpp +0 -173
  302. package/native-src/src/librats_mdns.cpp +0 -166
  303. package/native-src/src/librats_persistence.cpp +0 -796
  304. package/native-src/src/librats_portmap.cpp +0 -419
  305. package/native-src/src/librats_reconnection.cpp +0 -218
  306. package/native-src/src/librats_statistic.cpp +0 -105
  307. package/native-src/src/librats_storage.cpp +0 -189
  308. package/native-src/src/rats_export.h +0 -17
  309. package/native-src/src/receive_buffer.cpp +0 -82
  310. package/native-src/src/receive_buffer.h +0 -127
  311. package/native-src/src/socket.h +0 -228
  312. package/native-src/src/threadmanager.cpp +0 -105
  313. package/native-src/src/threadmanager.h +0 -53
  314. package/native-src/src/tracker.cpp +0 -1264
  315. package/native-src/src/tracker.h +0 -319
  316. package/native-src/src/turn.cpp +0 -762
  317. package/native-src/src/turn.h +0 -460
  318. package/native-src/src/wakeup_pipe.h +0 -60
  319. /package/native-src/src/{os.h → librats/util/os.h} +0 -0
@@ -0,0 +1,865 @@
1
+ #include "librats/node/node.h"
2
+ #include "librats/node/host_events.h"
3
+ #include "librats/node/identify.h"
4
+ #include "librats/subsystems/message_json.h"
5
+ #include "librats/security/noise_security.h"
6
+ #include "librats/security/plaintext_security.h"
7
+ #include "librats/util/fs.h"
8
+ #include "librats/util/logger.h"
9
+ #include "librats/util/network_monitor.h"
10
+ #include "librats/util/network_utils.h"
11
+
12
+ #include <algorithm>
13
+ #include <cstring>
14
+ #include <memory>
15
+ #include <utility>
16
+
17
+ namespace librats {
18
+
19
+ namespace {
20
+
21
+ // Pick the listen socket's address family from the bind address:
22
+ // - empty or "::" → DualStack wildcard: one IPv6 socket that also accepts IPv4
23
+ // (IPv4-mapped), so the node is reachable over both families.
24
+ // - other IPv6 literal (contains ':') → IPv6-only, bound to that interface.
25
+ // - IPv4 literal / hostname → IPv4.
26
+ // A specific (non-wildcard) address pins a single interface, so dual-stack only
27
+ // applies to the wildcard cases where binding both families is meaningful.
28
+ AddressFamily family_for_bind(const std::string& bind_address) {
29
+ if (bind_address.empty() || bind_address == "::") return AddressFamily::DualStack;
30
+ if (bind_address.find(':') != std::string::npos) return AddressFamily::IPv6;
31
+ return AddressFamily::IPv4;
32
+ }
33
+
34
+ // A wildcard/empty IP is never a dialable endpoint; filter it out of identify.
35
+ bool is_unspecified_ip(const std::string& ip) {
36
+ return ip.empty() || ip == "0.0.0.0" || ip == "::";
37
+ }
38
+
39
+ std::unique_ptr<SecurityProvider> make_security(const NodeConfig& cfg, const Identity& id) {
40
+ if (cfg.security == NodeConfig::Security::Noise)
41
+ return std::make_unique<NoiseSecurity>(id, cfg.protocol);
42
+ return std::make_unique<PlaintextSecurity>(id, cfg.protocol);
43
+ }
44
+
45
+ // Load a persisted identity from <data_dir>/identity.key, or generate one and
46
+ // save it. An empty data_dir means ephemeral (fresh identity each run).
47
+ Identity load_or_create_identity(const std::string& data_dir) {
48
+ if (data_dir.empty()) return Identity::generate();
49
+
50
+ const std::string key_path = combine_paths(data_dir, "identity.key");
51
+
52
+ size_t size = 0;
53
+ void* data = read_file_binary(key_path.c_str(), &size);
54
+ if (data) {
55
+ if (size == librats::NOISE_DH_SIZE) {
56
+ uint8_t priv[librats::NOISE_DH_SIZE];
57
+ std::memcpy(priv, data, librats::NOISE_DH_SIZE);
58
+ free_file_buffer(data);
59
+ return Identity::from_private_key(priv);
60
+ }
61
+ free_file_buffer(data); // malformed key file → regenerate
62
+ LOG_WARN("node", "Ignoring malformed identity key at " << key_path);
63
+ }
64
+
65
+ Identity identity = Identity::generate();
66
+ create_directories(data_dir.c_str());
67
+ if (!create_file_binary(key_path.c_str(), identity.static_keypair.private_key, librats::NOISE_DH_SIZE))
68
+ LOG_WARN("node", "Failed to persist identity key to " << key_path);
69
+ return identity;
70
+ }
71
+
72
+ } // namespace
73
+
74
+ Node::Node(NodeConfig config)
75
+ : config_(std::move(config)),
76
+ identity_(load_or_create_identity(config_.data_dir)),
77
+ security_(make_security(config_, identity_)),
78
+ reactors_(std::make_unique<ReactorPool>(config_.reactor_threads, *this, *security_)) {
79
+ max_peers_.store(config_.max_peers, std::memory_order_relaxed);
80
+
81
+ DialPolicy policy;
82
+ policy.preferred = config_.preferred_transport;
83
+ policy.allow_tcp = config_.enable_tcp;
84
+ policy.allow_udp = config_.enable_udp;
85
+ policy.fallback_delay = std::chrono::milliseconds(config_.transport_fallback_ms);
86
+ dialer_ = std::make_unique<Dialer>(*reactors_, policy,
87
+ [this](const std::string& host, uint16_t port) {
88
+ report_dial_failed(host, port);
89
+ });
90
+
91
+ // Published here rather than in start(): a subsystem resolves what it needs
92
+ // during attach(), which runs before start(), and both of these are node state
93
+ // that exists from construction. Neither extends Node's public surface — they
94
+ // are narrow capabilities a module asks for by interface (see
95
+ // node/dial_service.h, node/nat_status.h).
96
+ services_.provide<DialService>(this);
97
+ services_.provide<CircuitService>(this);
98
+ services_.provide<ExternalAddressService>(&nat_status_);
99
+ }
100
+
101
+ Node::~Node() {
102
+ stop();
103
+ }
104
+
105
+ // ── Lifecycle ───────────────────────────────────────────────────────────────
106
+
107
+ MessageJson* Node::json() noexcept { return subsystem<MessageJson>(); }
108
+
109
+ bool Node::open_listeners() {
110
+ // Attempts at finding a port that is free for BOTH transports. A clash
111
+ // between the two is unlikely (TCP and UDP have separate port spaces) but it
112
+ // is not impossible, and silently landing on mismatched ports would make
113
+ // every address this node advertises half-wrong. The same attempts cover a
114
+ // configured port that some other process already holds: attempt N tries
115
+ // listen_port + N, so one squatter on the default port does not leave the
116
+ // host without a node.
117
+ constexpr int kBindAttempts = 5;
118
+
119
+ const AddressFamily family = family_for_bind(config_.bind_address);
120
+ const bool listen_tcp = config_.enable_listen && config_.enable_tcp;
121
+ const bool listen_udp = config_.enable_listen && config_.enable_udp;
122
+
123
+ if (config_.enable_listen && !listen_tcp && !listen_udp) {
124
+ LOG_ERROR("node", "enable_listen is set but neither transport is enabled");
125
+ return false;
126
+ }
127
+
128
+ if (!config_.enable_listen) {
129
+ // Dial-only. TCP needs no socket up front, but the datagram transport
130
+ // still wants its one shared socket — that single socket is what gives
131
+ // every peer one NAT mapping instead of one each. It just does not need a
132
+ // predictable port, since nobody dials us.
133
+ if (!config_.enable_udp) return true;
134
+ udp_socket_ = create_udp_socket(0, config_.bind_address, family);
135
+ if (!is_valid_socket(udp_socket_))
136
+ LOG_WARN("node", "Could not open a UDP socket; dials will fall back to TCP");
137
+ else
138
+ reactors_->listen_udp(udp_socket_, family);
139
+ return true;
140
+ }
141
+
142
+ for (int attempt = 0; attempt < kBindAttempts; ++attempt) {
143
+ socket_t udp = RATS_INVALID_SOCKET;
144
+ socket_t tcp = RATS_INVALID_SOCKET;
145
+
146
+ // Walk upward from the configured port; an ephemeral one is re-requested
147
+ // as 0 each time, since the kernel picks a different port on its own.
148
+ const int candidate = config_.listen_port == 0 ? 0 : config_.listen_port + attempt;
149
+ if (candidate > 65535) break;
150
+ uint16_t port = static_cast<uint16_t>(candidate);
151
+
152
+ // Which socket names the port, and which one has to accept the name.
153
+ //
154
+ // Whichever binds second is the one that can be refused: the kernel only
155
+ // ever hands out a port that is free for the protocol it is allocating
156
+ // for, and the two protocols have separate port spaces. So the order is a
157
+ // choice of which refusal to face, and the sides are not symmetric.
158
+ //
159
+ // The stream socket names it first, because the scarcity is on its side.
160
+ // Both draw from the same ephemeral range, but every outbound TCP
161
+ // connection on the host holds a source port in it, and goes on holding
162
+ // it through TIME_WAIT after it closes; a busy machine keeps tens of
163
+ // thousands there, and nearly nothing competes for the UDP half of the
164
+ // same range. Letting UDP name the port and then demanding TCP match it
165
+ // is a bet against that crowd, and under load it loses often enough to
166
+ // fail a node's start outright.
167
+ //
168
+ // That reasoning is about a port being *taken*, though, and on Windows
169
+ // the usual refusal is a port being *reserved* — the blocks Hyper-V,
170
+ // WinNAT and WSL2 carve out of the ephemeral range, which are not the
171
+ // same blocks for the two protocols. Against a reservation, retrying the
172
+ // same way barely moves: Windows hands out ephemeral ports in ascending
173
+ // order while a reserved block is contiguous and can be hundreds wide, so
174
+ // each retry lands one port further into the same hole. A retry therefore
175
+ // swaps the roles rather than repeating them — an ephemeral bind on the
176
+ // refused side steps clear of that side's reservations in one move, since
177
+ // the kernel never allocates out of them. Only worth doing when the port
178
+ // is ours to choose, and never on the last attempt, so running out of
179
+ // attempts still ends on the conservative order.
180
+ const bool udp_names_port = listen_tcp && listen_udp && port == 0
181
+ && attempt % 2 == 1 && attempt + 1 < kBindAttempts;
182
+ if (udp_names_port) {
183
+ udp = create_udp_socket(0, config_.bind_address, family);
184
+ // Failing on a port the kernel picked itself is not a failure another
185
+ // port would fix, so there is nothing to walk to: leave the datagram
186
+ // socket to the usual path below and let TCP name the port after all.
187
+ if (is_valid_socket(udp))
188
+ port = static_cast<uint16_t>(get_bound_port(udp));
189
+ }
190
+
191
+ if (listen_tcp) {
192
+ tcp = create_tcp_server(port, 128, config_.bind_address, family);
193
+ if (!is_valid_socket(tcp)) {
194
+ // A port that is taken, or that Windows keeps reserved, is the
195
+ // bind failure another port can answer. Every other one — no
196
+ // permission for a privileged port, a bind address that is not on
197
+ // this host, IPv6 switched off — fails identically on all 65535 of
198
+ // them, and walking the range would only bury the real error under
199
+ // four more copies of it.
200
+ const bool retry = last_error_was_port_unavailable()
201
+ && attempt + 1 < kBindAttempts;
202
+ if (is_valid_socket(udp)) close_socket(udp);
203
+ if (retry) {
204
+ LOG_WARN("node", "TCP port " << port << " is unavailable, trying another");
205
+ continue;
206
+ }
207
+ LOG_ERROR("node", "Failed to listen on "
208
+ << config_.bind_address << ":" << port);
209
+ return false;
210
+ }
211
+ if (port == 0) port = static_cast<uint16_t>(get_bound_port(tcp));
212
+ }
213
+
214
+ // Skipped when the datagram socket already has the port: it named it.
215
+ if (listen_udp && !is_valid_socket(udp)) {
216
+ udp = create_udp_socket(port, config_.bind_address, family);
217
+ if (is_valid_socket(udp)) {
218
+ if (port == 0) port = static_cast<uint16_t>(get_bound_port(udp));
219
+ } else if (listen_tcp) {
220
+ // Worth another attempt only for a port another port would fix,
221
+ // and only while the next attempt would actually move: both the
222
+ // ephemeral case and the configured one land on a different port
223
+ // next time round. Windows makes this path the common one — its
224
+ // reserved ranges differ between TCP and UDP, so a port the stream
225
+ // socket was handed can be refused to the datagram one.
226
+ if (last_error_was_port_unavailable() && attempt + 1 < kBindAttempts) {
227
+ close_socket(tcp);
228
+ continue;
229
+ }
230
+ // Out of attempts, or a failure no other port would fix: the node
231
+ // still has a working transport, so it runs on that one rather
232
+ // than refusing to start.
233
+ LOG_WARN("node", "UDP port " << port << " unavailable; this node runs TCP-only");
234
+ } else {
235
+ // UDP-only node: nothing to fall back to, so an unavailable port
236
+ // is walked past exactly as it is for TCP above.
237
+ if (last_error_was_port_unavailable() && attempt + 1 < kBindAttempts) {
238
+ LOG_WARN("node", "UDP port " << port << " is unavailable, trying another");
239
+ continue;
240
+ }
241
+ LOG_ERROR("node", "Failed to listen on UDP "
242
+ << config_.bind_address << ":" << port);
243
+ return false;
244
+ }
245
+ }
246
+
247
+ if (config_.listen_port != 0 && port != config_.listen_port) {
248
+ LOG_WARN("node", "Configured port " << config_.listen_port
249
+ << " was unavailable; listening on " << port << " instead");
250
+ }
251
+
252
+ listen_socket_ = tcp;
253
+ udp_socket_ = udp;
254
+ listen_port_ = port;
255
+ if (is_valid_socket(tcp)) {
256
+ reactors_->listen(tcp);
257
+ transports_ |= PeerTransportTcp;
258
+ }
259
+ if (is_valid_socket(udp)) {
260
+ reactors_->listen_udp(udp, family);
261
+ transports_ |= PeerTransportUdp;
262
+ }
263
+ return true;
264
+ }
265
+
266
+ // Reached only by running the port range off its end; the last attempt does
267
+ // not retry, it settles for the transport it has.
268
+ LOG_ERROR("node", "No free port found from " << config_.listen_port
269
+ << " upward for " << config_.bind_address);
270
+ return false;
271
+ }
272
+
273
+ bool Node::start() {
274
+ if (running_.exchange(true)) return false;
275
+ init_socket_library();
276
+
277
+ dialer_->start(); // a node may be restarted; clear any state from the last run
278
+
279
+ if (!open_listeners()) {
280
+ running_.store(false);
281
+ return false;
282
+ }
283
+
284
+ // Compute our advertised addresses once; the network monitor refreshes them
285
+ // on change. Done here (not per-connection) so the send path stays syscall-free.
286
+ rebuild_advertised_addresses(network_utils::get_local_interface_addresses());
287
+
288
+ // Attach subsystems (registers their message handlers, event subscriptions
289
+ // and service providers) BEFORE any reactor thread runs, so the router and the
290
+ // event/service tables are fully built with no concurrent writes.
291
+ NodeContext ctx{*this, events_, services_};
292
+ for (auto& s : subsystems_) s->attach(ctx);
293
+
294
+ reactors_->start();
295
+
296
+ for (auto& s : subsystems_) s->start();
297
+
298
+ start_network_monitor();
299
+
300
+ LOG_INFO("node", "Node " << identity_.id.short_hex() << " started on port " << listen_port_
301
+ << " (" << ((transports_ & PeerTransportTcp) ? "tcp" : "")
302
+ << ((transports_ == (PeerTransportTcp | PeerTransportUdp)) ? "+" : "")
303
+ << ((transports_ & PeerTransportUdp) ? "udp" : "")
304
+ << ", " << reactors_->size() << " reactor(s), "
305
+ << subsystems_.size() << " subsystem(s))");
306
+ return true;
307
+ }
308
+
309
+ void Node::stop() {
310
+ if (!running_.exchange(false)) return;
311
+ dialer_->stop(); // no more fallback attempts get started
312
+ stop_network_monitor(); // no more NetworkChanged after this
313
+ // Stop subsystems in reverse attach order, so a subsystem that depends on an
314
+ // earlier one (e.g. Bittorrent borrowing DhtDiscovery's DhtClient) tears down
315
+ // before the dependency it borrowed is itself stopped and destroyed.
316
+ for (auto it = subsystems_.rbegin(); it != subsystems_.rend(); ++it) (*it)->stop();
317
+ reactors_->stop(); // then join reactors; close connections
318
+ // The listen sockets are owned by us, not the reactors — close them here,
319
+ // after the reactor threads have joined (so nothing is still polling them), to
320
+ // release the bound port immediately on stop() rather than leaking it until
321
+ // destruction.
322
+ if (is_valid_socket(listen_socket_)) {
323
+ close_socket(listen_socket_);
324
+ listen_socket_ = RATS_INVALID_SOCKET;
325
+ }
326
+ if (is_valid_socket(udp_socket_)) {
327
+ close_socket(udp_socket_);
328
+ udp_socket_ = RATS_INVALID_SOCKET;
329
+ }
330
+ transports_ = 0;
331
+ LOG_INFO("node", "Node " << identity_.id.short_hex() << " stopped");
332
+ }
333
+
334
+ // ── Host network-change watch ────────────────────────────────────────────────
335
+ //
336
+ // One monitor, many reactors: the NetworkMonitor detects a change on its own
337
+ // thread and hands the new address set to the maintenance thread, which performs
338
+ // the (possibly slow) EventBus emit. Subscribers — PortMappingService, DhtDiscovery,
339
+ // … — react independently without the monitor knowing they exist.
340
+
341
+ void Node::start_network_monitor() {
342
+ if (!config_.enable_network_monitor) return;
343
+
344
+ maintenance_stop_ = false;
345
+ maintenance_thread_ = std::thread([this] { maintenance_loop(); });
346
+
347
+ monitor_ = std::make_unique<NetworkMonitor>();
348
+ monitor_->start([this](const std::vector<std::string>& addresses) {
349
+ // Runs on the monitor thread; just hand off and return promptly. Coalesces:
350
+ // a burst collapses to one emit carrying the latest address set.
351
+ {
352
+ std::lock_guard<std::mutex> lock(maintenance_mutex_);
353
+ pending_addresses_ = addresses;
354
+ maintenance_pending_ = true;
355
+ }
356
+ maintenance_cv_.notify_one();
357
+ });
358
+ }
359
+
360
+ void Node::stop_network_monitor() {
361
+ monitor_.reset(); // joins the monitor thread → no further hand-offs
362
+
363
+ {
364
+ std::lock_guard<std::mutex> lock(maintenance_mutex_);
365
+ maintenance_stop_ = true;
366
+ }
367
+ maintenance_cv_.notify_one();
368
+ if (maintenance_thread_.joinable()) maintenance_thread_.join();
369
+ }
370
+
371
+ void Node::maintenance_loop() {
372
+ for (;;) {
373
+ std::vector<std::string> addresses;
374
+ {
375
+ std::unique_lock<std::mutex> lock(maintenance_mutex_);
376
+ maintenance_cv_.wait(lock, [this] { return maintenance_pending_ || maintenance_stop_; });
377
+ if (maintenance_stop_) return;
378
+ maintenance_pending_ = false;
379
+ addresses = std::move(pending_addresses_);
380
+ }
381
+ LOG_INFO("node", "Network change: " << addresses.size()
382
+ << " local address(es); notifying subsystems");
383
+ rebuild_advertised_addresses(addresses); // keep identify's advertised set fresh
384
+ // Every external endpoint we learned was a mapping made by the path that
385
+ // just changed underneath us. Holding on to it would aim a punch at a port
386
+ // that no longer exists; peers re-report as their links come back.
387
+ nat_status_.reset();
388
+ events_.emit(NetworkChanged{std::move(addresses)});
389
+ }
390
+ }
391
+
392
+ // ── Connections ─────────────────────────────────────────────────────────────
393
+
394
+ void Node::connect(const Address& address) { connect(address.ip.to_string(), address.port); }
395
+
396
+ void Node::connect(const std::string& host, uint16_t port) {
397
+ // The dialer owns the transport choice: preferred first, the other raced in
398
+ // after the fallback delay, loser dropped. See node/dialer.h.
399
+ dialer_->dial(host, port);
400
+ }
401
+
402
+ bool Node::dial_direct(const Address& addr, TransportKind kind, const DialProfile& profile) {
403
+ if (!addr.is_valid()) return false;
404
+ if (kind == TransportKind::Udp && !reactors_->has_udp()) return false;
405
+ if (kind == TransportKind::Tcp && !config_.enable_tcp) return false;
406
+ // A relayed circuit is not dialed: it is negotiated with a third node and then
407
+ // adopted (see node/circuit_service.h). There is no address to aim at here.
408
+ if (kind == TransportKind::Relay) return false;
409
+
410
+ // Straight to the reactor, deliberately around the Dialer: this dial is not a
411
+ // race and must not become one (see node/dial_service.h). The Dialer is left
412
+ // unaware of it, which is exactly right — its bookkeeping is keyed by
413
+ // (reactor, conn) and simply does not match, so the connection's later
414
+ // established/closed reports are no-ops there rather than corrections.
415
+ const ConnId id = reactors_->pick(kind).connect(addr.ip.to_string(), addr.port, kind, profile);
416
+ return id != kInvalidConnId;
417
+ }
418
+
419
+ // ── CircuitService: a relayed byte stream becomes a peer connection ─────────
420
+
421
+ std::optional<PeerRoute> Node::adopt_circuit(const PeerId& carrier, std::unique_ptr<Link> link,
422
+ ConnRole role, bool connected) {
423
+ if (!link) return std::nullopt;
424
+
425
+ // The carrier has to still be a peer: it is the only thing this circuit's bytes
426
+ // can travel through, and a connection whose carrier is already gone would
427
+ // simply sit there until the establish deadline reaped it.
428
+ const auto carrier_route = peers_.route(carrier);
429
+ if (!carrier_route) return std::nullopt;
430
+
431
+ // Inbound circuits go through the same admission gate as an accepted socket.
432
+ // A relayed connection is cheaper for whoever opens it than a direct one — no
433
+ // NAT to get through, someone else's bandwidth — so if anything it deserves the
434
+ // gate more, not less.
435
+ if (role == ConnRole::Inbound && !admit_inbound()) return std::nullopt;
436
+
437
+ Reactor& reactor = reactors_->by_index(carrier_route->reactor);
438
+ // Same reactor as the carrier, which is the whole point: every byte of this
439
+ // circuit arrives on the carrier's connection and leaves through it, so the two
440
+ // share a thread and the relayed path needs no locks and no hand-offs.
441
+ const ConnId id = reactor.reserve_conn_id();
442
+ reactor.adopt_link(id, std::move(link), role, connected);
443
+ return PeerRoute{carrier_route->reactor, id};
444
+ }
445
+
446
+ void Node::wake_circuit(PeerRoute route, uint32_t events) {
447
+ reactors_->by_index(route.reactor).wake(route.conn, events);
448
+ }
449
+
450
+ void Node::close_circuit(PeerRoute route, CloseReason reason) {
451
+ reactors_->by_index(route.reactor).close(route.conn, reason);
452
+ }
453
+
454
+ void Node::report_dial_failed(const std::string& host, uint16_t port) {
455
+ // Only a numeric target can be handed on as an Address; a dial by hostname has
456
+ // no dialable Address to report (peers cannot use our resolver), and the
457
+ // subscribers of this event — a reconnection policy, say — deal in addresses.
458
+ const auto ip = IpAddress::parse(host);
459
+ if (!ip) return;
460
+ const Address addr{*ip, port};
461
+ for (auto& cb : dial_failed_) cb(addr);
462
+ }
463
+
464
+ std::optional<Peer> Node::peer(const PeerId& id) {
465
+ auto route = peers_.route(id);
466
+ if (!route) return std::nullopt;
467
+ return make_peer(id, *route);
468
+ }
469
+
470
+ // ── Application messaging ────────────────────────────────────────────────────
471
+
472
+ bool Node::send(const PeerId& to, std::string_view channel, ByteView payload) {
473
+ // Route and writability in one lookup. The answer is the queue's state as of
474
+ // now rather than after this message lands — the send itself is handed to the
475
+ // owning reactor and completes there — which is all a backpressure hint can
476
+ // ever be, and is enough: it is the *previous* messages that filled the queue.
477
+ auto dest = peers_.destination(to);
478
+ if (!dest) return false;
479
+ // Charged before the hand-off and discharged by the reactor task, so a caller
480
+ // in a tight loop is answered on what it has actually offered rather than on
481
+ // what the reactor has managed to look at so far.
482
+ const size_t in_transit =
483
+ dest->owed->fetch_add(payload.size(), std::memory_order_relaxed) + payload.size();
484
+ route_send(dest->route, FrameHeader{MessageType::App, 0, MessageRouter::channel_id(channel)},
485
+ payload.to_bytes(), dest->owed);
486
+ return dest->writable && in_transit <= send_low_water();
487
+ }
488
+
489
+ bool Node::peer_writable(const PeerId& id) const {
490
+ const auto dest = peers_.destination(id);
491
+ return dest && dest->writable;
492
+ }
493
+
494
+ bool Node::broadcast(std::string_view channel, ByteView payload) {
495
+ auto data = std::make_shared<const Bytes>(payload.to_bytes());
496
+ FrameHeader header{MessageType::App, 0, MessageRouter::channel_id(channel)};
497
+ reactors_->for_each([&](Reactor& r) { r.broadcast(header, data); });
498
+ return peers_.all_writable();
499
+ }
500
+
501
+ // ── PeerNetwork (subsystems) ─────────────────────────────────────────────────
502
+
503
+ bool Node::send(const PeerId& to, MessageType type, ByteView payload) {
504
+ auto dest = peers_.destination(to);
505
+ if (!dest) return false;
506
+ const size_t in_transit =
507
+ dest->owed->fetch_add(payload.size(), std::memory_order_relaxed) + payload.size();
508
+ route_send(dest->route, FrameHeader{type, 0, 0}, payload.to_bytes(), dest->owed);
509
+ return dest->writable && in_transit <= send_low_water();
510
+ }
511
+
512
+ bool Node::broadcast(MessageType type, ByteView payload) {
513
+ auto data = std::make_shared<const Bytes>(payload.to_bytes());
514
+ FrameHeader header{type, 0, 0};
515
+ reactors_->for_each([&](Reactor& r) { r.broadcast(header, data); });
516
+ return peers_.all_writable();
517
+ }
518
+
519
+ std::vector<PeerId> Node::connected_peers() const {
520
+ std::vector<PeerId> ids;
521
+ for (const auto& info : peers_.snapshot()) ids.push_back(info.id);
522
+ return ids;
523
+ }
524
+
525
+ // ── Routed send/close (used by Node and Peer) ───────────────────────────────
526
+
527
+ void Node::route_send(PeerRoute route, FrameHeader header, Bytes payload,
528
+ std::shared_ptr<std::atomic<size_t>> owed) {
529
+ Reactor& reactor = reactors_->by_index(route.reactor);
530
+ ConnId conn = route.conn;
531
+ auto data = std::make_shared<Bytes>(std::move(payload));
532
+ const size_t weight = data->size();
533
+ reactor.execute([&reactor, conn, header, data, owed, weight] {
534
+ // Discharged whether or not the connection is still there, and *before*
535
+ // the frame is queued rather than after: from here on these bytes are
536
+ // counted by the send queue instead, and the connection weighs both
537
+ // together (Connection::backlog). Discharging afterwards would leave a
538
+ // window where they were counted twice — and, worse, one where they were
539
+ // counted by neither, which is what an opening must never be reported on.
540
+ if (owed) owed->fetch_sub(weight, std::memory_order_relaxed);
541
+ if (auto* c = reactor.find(conn)) c->send(header, ByteView(*data));
542
+ });
543
+ }
544
+
545
+ size_t Node::send_low_water() const noexcept {
546
+ const size_t limit = config_.send_queue_limit != 0 ? config_.send_queue_limit
547
+ : Connection::kDefaultSendHighWater;
548
+ return limit / 4;
549
+ }
550
+
551
+ void Node::route_close(PeerRoute route) {
552
+ reactors_->by_index(route.reactor).close(route.conn, CloseReason::LocalClose);
553
+ }
554
+
555
+ // ── ConnectionDelegate (reactor thread) ─────────────────────────────────────
556
+
557
+ bool Node::admit_inbound() {
558
+ // Coarse gate at accept time: refuse new inbound when at capacity, before any
559
+ // handshake cost. The exact per-peer cap (which can tell a reconnect of a
560
+ // known peer from a brand-new one) is enforced in on_established below.
561
+ const size_t cap = max_peers_.load(std::memory_order_relaxed);
562
+ return cap == 0 || peers_.size() < cap;
563
+ }
564
+
565
+ void Node::on_established(Connection& conn) {
566
+ if (config_.send_queue_limit != 0) conn.set_send_high_water(config_.send_queue_limit);
567
+
568
+ // Settle the dial race first: this attempt won its target, so any sibling
569
+ // attempt over the other transport is redundant and gets cancelled — unless it
570
+ // has established too, in which case it arrives here as an ordinary duplicate
571
+ // and the peer table decides between them (see dialer.h). Done before
572
+ // the checks below so a self-connection or a rejected duplicate still counts
573
+ // as "this dial resolved" rather than leaving the race running.
574
+ if (conn.role() == ConnRole::Outbound)
575
+ dialer_->on_established(PeerRoute{conn.reactor_index(), conn.id()});
576
+
577
+ // Reject self-connections: a self-certifying handshake against our own
578
+ // listener yields our own id. (Common once DHT/discovery starts dialing.)
579
+ if (conn.remote_id() == identity_.id) {
580
+ reactors_->by_index(conn.reactor_index()).close(conn.id(), CloseReason::LocalClose);
581
+ return;
582
+ }
583
+
584
+ // Peer-limit backstop. Inbound handshakes that raced past admit_inbound()
585
+ // before the cap was hit are rejected here, now that we know the remote id.
586
+ // A reconnect/duplicate of an already-known peer does not grow the count, so
587
+ // it is allowed through (the peer-table tie-break supersedes the old link).
588
+ // Our own outbound dials are intentional and never rejected.
589
+ const size_t cap = max_peers_.load(std::memory_order_relaxed);
590
+ if (cap != 0 && conn.role() == ConnRole::Inbound &&
591
+ peers_.size() >= cap && !peers_.contains(conn.remote_id())) {
592
+ LOG_DEBUG("node", "Rejecting inbound peer " << conn.remote_id().short_hex()
593
+ << "; peer limit (" << cap << ") reached");
594
+ reactors_->by_index(conn.reactor_index()).close(conn.id(), CloseReason::PeerLimit);
595
+ return;
596
+ }
597
+
598
+ PeerInfo info;
599
+ info.id = conn.remote_id();
600
+ info.direction = conn.role();
601
+ info.transport = conn.transport();
602
+ // Outbound: remember the address we dialed — but only if it was a numeric IP.
603
+ // A dial-by-hostname target isn't a dialable Address (peers can't use our name),
604
+ // and the resolved IP is re-learned via identify anyway.
605
+ if (conn.has_dial_address())
606
+ if (auto ip = IpAddress::parse(conn.dial_host()))
607
+ info.addresses.push_back(Address{*ip, conn.dial_port()});
608
+
609
+ const PeerRoute route{conn.reactor_index(), conn.id()};
610
+ // Symmetric tie-break for a simultaneous cross-connect: both peers keep the
611
+ // link initiated by the smaller id, so they converge on the same connection.
612
+ const bool prefer_outbound = identity_.id < conn.remote_id();
613
+ const auto outcome = peers_.add(info, route, prefer_outbound);
614
+
615
+ // Tear down the loser of a duplicate/cross-connect race so it can't linger
616
+ // holding an fd. Its on_closed is a no-op: the peer table no longer maps its
617
+ // route, so it neither evicts the survivor nor fires a spurious disconnect.
618
+ if (outcome.close)
619
+ reactors_->by_index(outcome.close->reactor).close(outcome.close->conn, CloseReason::DuplicateConn);
620
+
621
+ // Hand the peer's in-transit counter to the connection that now carries its
622
+ // traffic, so the send queue's marks weigh what callers have offered as well
623
+ // as what is already queued — the two halves of one backlog, and the reason a
624
+ // caller told "no room" is always the one told when the room comes back. The
625
+ // route check keeps the loser of a race off the survivor's counter.
626
+ if (const auto dest = peers_.destination(conn.remote_id()); dest && dest->route == route)
627
+ conn.set_in_transit(dest->owed);
628
+
629
+ // Fire "connected" only on the 0→1 transition. A reconnect/duplicate that
630
+ // merely swapped the live route keeps the peer connected from the app's view.
631
+ if (outcome.result == PeerTable::AddResult::NewPeer) {
632
+ LOG_INFO("node", "Peer " << conn.remote_id().short_hex() << " connected ("
633
+ << (conn.role() == ConnRole::Inbound ? "inbound" : "outbound")
634
+ << " over " << to_string(conn.transport()) << ")");
635
+ Peer handle = make_peer(conn.remote_id(), route);
636
+ for (auto& cb : peer_connected_) cb(handle);
637
+ }
638
+
639
+ // Tell the peer how to reach us, and what address we see it at. Skipped for a
640
+ // rejected duplicate (its connection was just closed above); sent on the
641
+ // surviving link otherwise — including the winner of a cross-connect race.
642
+ if (outcome.result != PeerTable::AddResult::Rejected)
643
+ send_identify(conn);
644
+ }
645
+
646
+ void Node::on_frame(Connection& conn, const Frame& frame) {
647
+ // Control is the node's own plane (identify); it never reaches the app router.
648
+ if (frame.header.type == MessageType::Control) {
649
+ handle_identify(conn, frame);
650
+ return;
651
+ }
652
+ Peer peer = make_peer(conn.remote_id(), PeerRoute{conn.reactor_index(), conn.id()});
653
+ if (!router_.dispatch(peer, frame)) {
654
+ LOG_DEBUG("node", "No handler for frame from " << conn.remote_id().short_hex()
655
+ << " (type " << static_cast<int>(frame.header.type)
656
+ << ", channel " << frame.header.channel << ")");
657
+ }
658
+ }
659
+
660
+ void Node::on_closed(Connection& conn, CloseReason reason) {
661
+ // Tell the dial race this attempt is out. If it was the last one and none
662
+ // succeeded, the dialer reports the target as failed — a redial policy
663
+ // (ReconnectionService) needs to know its in-flight dial resolved, or it would
664
+ // wait out a timeout before retrying. The dialer coalesces that into exactly
665
+ // one report per target, however many transports were raced.
666
+ if (conn.role() == ConnRole::Outbound)
667
+ dialer_->on_closed(PeerRoute{conn.reactor_index(), conn.id()});
668
+
669
+ // Only peers that actually established were registered.
670
+ if (conn.remote_id().is_zero()) return;
671
+
672
+ const PeerId id = conn.remote_id();
673
+ const PeerRoute route{conn.reactor_index(), conn.id()};
674
+
675
+ // Disconnect fires only for the connection currently registered for this peer.
676
+ // A loser of a duplicate race (or a rejected self-connection) is not mapped
677
+ // under its route, so removing it is a no-op and must NOT surface a disconnect
678
+ // for a peer that is still connected over the surviving link.
679
+ if (!peers_.remove(id, route)) return;
680
+ // What this peer told us about our external endpoint went with it: the mapping
681
+ // it observed may well have been made for its link alone, and keeping it would
682
+ // let a long-gone peer keep voting on how our NAT behaves.
683
+ nat_status_.forget(id);
684
+ LOG_INFO("node", "Peer " << id.short_hex() << " disconnected (" << to_string(reason) << ")");
685
+ for (auto& cb : peer_disconnected_) cb(id);
686
+ }
687
+
688
+ void Node::on_writable_changed(Connection& conn, bool writable) {
689
+ // Only an established peer has a table entry to record this against; a
690
+ // connection still handshaking cannot be sent to anyway.
691
+ if (conn.remote_id().is_zero()) return;
692
+ const PeerRoute route{conn.reactor_index(), conn.id()};
693
+ peers_.set_writable(conn.remote_id(), route, writable);
694
+
695
+ // Only the opening is worth waking anyone for. "You have filled up" is
696
+ // already answered by send() returning false at the moment it happens, so an
697
+ // event for it would tell the caller what it has just been told.
698
+ if (!writable) return;
699
+ Peer handle = make_peer(conn.remote_id(), route);
700
+ for (auto& cb : peer_writable_) cb(handle);
701
+ }
702
+
703
+ void Node::on_dial_aborted(uint8_t reactor_index, ConnId id,
704
+ const std::string& /*host*/, uint16_t /*port*/) {
705
+ // The attempt died before there was ever a Connection (unresolvable host, no
706
+ // socket). It resolves exactly like a connection that closed without
707
+ // establishing, so the dialer can move on to the next transport at once.
708
+ dialer_->on_closed(PeerRoute{reactor_index, id});
709
+ }
710
+
711
+ // ── Identify: dialable-address discovery (reactor thread) ────────────────────
712
+ //
713
+ // A TCP socket only exposes a peer's ephemeral source port, so an inbound peer's
714
+ // dialable address is unknowable from the connection alone. Right after the
715
+ // handshake each side sends a Control/identify frame; the receiver pairs the
716
+ // peer's advertised listen port with the IP it sees to recover the dialable
717
+ // address, and learns its own public address from the peer's observation.
718
+
719
+ void Node::send_identify(Connection& conn) {
720
+ IdentifyMessage msg;
721
+ msg.listen_port = listen_port_;
722
+ msg.addresses = advertised_addresses();
723
+ // Both transports share listen_port_, so which of them a peer may dial there
724
+ // is a property of the node, not of the address — say it once, here.
725
+ msg.transports = transports_;
726
+ const IpAddress seen_ip = conn.remote_ip();
727
+ if (!seen_ip.is_any()) {
728
+ // The port we saw the peer at is worth reporting on exactly one of the two
729
+ // wires. On TCP it is an ephemeral port its OS picked for this one outbound
730
+ // socket and tells the peer nothing it can use. On a datagram link every
731
+ // peer shares ONE socket, so that port is what the peer's NAT mapped that
732
+ // socket to — the port a third party has to aim at to reach it, and the
733
+ // only thing a hole punch can be pointed at. So: report it there, and only
734
+ // there. A zero port keeps the old meaning ("IP only"), which is what an
735
+ // older peer already expects and what TCP still sends.
736
+ uint16_t seen_port = 0;
737
+ if (conn.transport() == TransportKind::Udp)
738
+ if (const auto endpoint = conn.remote_endpoint()) seen_port = endpoint->port;
739
+ msg.observed = Address{seen_ip, seen_port};
740
+ }
741
+
742
+ const Bytes payload = msg.encode();
743
+ conn.send(FrameHeader{MessageType::Control, 0, 0}, ByteView(payload));
744
+ }
745
+
746
+ void Node::handle_identify(Connection& conn, const Frame& frame) {
747
+ const auto msg = IdentifyMessage::decode(frame.payload);
748
+ if (!msg) {
749
+ LOG_DEBUG("node", "Ignoring malformed identify from " << conn.remote_id().short_hex());
750
+ return;
751
+ }
752
+
753
+ // The peer's dialable addresses: the address we see it at paired with its
754
+ // advertised listen port (the linchpin for inbound peers), plus any extra
755
+ // addresses it self-advertised. PeerTable de-duplicates and caps the set.
756
+ const PeerRoute identify_route{conn.reactor_index(), conn.id()};
757
+ const IpAddress seen_ip = conn.remote_ip();
758
+ if (msg->transports != 0) {
759
+ peers_.set_supported_transports(conn.remote_id(), identify_route, msg->transports);
760
+ // ...and tell the dialer, which is the half that actually saves anything:
761
+ // without it every later dial to this address pays the fallback delay again
762
+ // to rediscover what we are being told right here. Only addresses this node
763
+ // can vouch for are recorded — the one we dialed ourselves, and the IP we
764
+ // observe the peer at paired with the listen port it claims. The addresses
765
+ // in msg->addresses are the peer's unverified word about where *anyone*
766
+ // lives, so letting them carry a transports mask would let one peer set the
767
+ // dial order for somebody else's address.
768
+ if (conn.has_dial_address())
769
+ dialer_->remember(conn.dial_host(), conn.dial_port(), msg->transports);
770
+ if (msg->listen_port != 0 && !seen_ip.is_any())
771
+ dialer_->remember(seen_ip.to_string(), msg->listen_port, msg->transports);
772
+ }
773
+
774
+ std::vector<Address> candidates;
775
+ if (msg->listen_port != 0 && !seen_ip.is_any())
776
+ candidates.push_back(Address{seen_ip, msg->listen_port});
777
+ for (const Address& a : msg->addresses)
778
+ if (a.port != 0 && !a.ip.is_any())
779
+ candidates.push_back(a);
780
+
781
+ if (!candidates.empty()) {
782
+ const auto added = peers_.add_addresses(conn.remote_id(), identify_route, candidates);
783
+ if (!added.empty())
784
+ LOG_DEBUG("node", "Learned " << added.size() << " address(es) for peer "
785
+ << conn.remote_id().short_hex() << " (e.g. " << added.front().to_string() << ")");
786
+ }
787
+
788
+ // Learn our own public address: pair the IP the peer saw us at with OUR listen
789
+ // port, since on TCP its observed port is our ephemeral source port and is not
790
+ // dialable.
791
+ if (msg->observed && !msg->observed->ip.is_any()) {
792
+ if (listen_port_ != 0)
793
+ record_observed_address(Address{msg->observed->ip, listen_port_});
794
+
795
+ // A datagram peer reports the port as well, and there it is not ephemeral:
796
+ // it is what a NAT mapped our one shared socket to. That endpoint — not the
797
+ // listen port, which a NAT is under no obligation to preserve — is what a
798
+ // third peer would have to aim at, so it is kept apart from the address set
799
+ // above and classified across peers (see node/nat_status.h).
800
+ if (conn.transport() == TransportKind::Udp && msg->observed->port != 0)
801
+ nat_status_.record_udp_observation(conn.remote_id(), *msg->observed,
802
+ is_own_endpoint(*msg->observed));
803
+ }
804
+ }
805
+
806
+ bool Node::is_own_endpoint(const Address& addr) const {
807
+ std::lock_guard<std::mutex> lock(advertised_mutex_);
808
+ // The advertised set is every local interface IP paired with our listen port —
809
+ // so a match means the peer reached us without anything rewriting the endpoint
810
+ // on the way, i.e. there is no NAT between us.
811
+ return std::find(advertised_addresses_.begin(), advertised_addresses_.end(), addr) !=
812
+ advertised_addresses_.end();
813
+ }
814
+
815
+ std::vector<Address> Node::advertised_addresses() const {
816
+ std::lock_guard<std::mutex> lock(advertised_mutex_);
817
+ return advertised_addresses_; // hot path: just hand back the stored snapshot
818
+ }
819
+
820
+ void Node::rebuild_advertised_addresses(const std::vector<std::string>& local_ips) {
821
+ // Pair each local interface IP with our listen port. A non-listening node has
822
+ // nothing dialable to advertise. Called off the hot path: once at start() and
823
+ // again whenever the NetworkMonitor reports the interface set changed.
824
+ std::vector<Address> fresh;
825
+ if (listen_port_ != 0) {
826
+ for (const std::string& ip : local_ips) {
827
+ if (is_unspecified_ip(ip)) continue;
828
+ fresh.push_back(Address{ip, listen_port_});
829
+ if (fresh.size() >= IdentifyMessage::kMaxAddresses) break;
830
+ }
831
+ }
832
+ std::lock_guard<std::mutex> lock(advertised_mutex_);
833
+ advertised_addresses_ = std::move(fresh);
834
+ }
835
+
836
+ void Node::record_observed_address(const Address& addr) {
837
+ std::lock_guard<std::mutex> lock(observed_mutex_);
838
+ if (std::find(observed_addresses_.begin(), observed_addresses_.end(), addr) != observed_addresses_.end())
839
+ return;
840
+ if (observed_addresses_.size() >= 16) return; // bound (NAT remap churn / hostile peers)
841
+ observed_addresses_.push_back(addr);
842
+ LOG_DEBUG("node", "Observed own address " << addr.to_string() << " (reported by a peer)");
843
+ }
844
+
845
+ std::vector<Address> Node::observed_addresses() const {
846
+ std::lock_guard<std::mutex> lock(observed_mutex_);
847
+ return observed_addresses_;
848
+ }
849
+
850
+ // ── Peer handle methods (defined here for the full Node type) ────────────────
851
+
852
+ void Peer::send(std::string_view channel, ByteView payload) const {
853
+ node_->route_send(route_, FrameHeader{MessageType::App, 0, MessageRouter::channel_id(channel)},
854
+ payload.to_bytes());
855
+ }
856
+
857
+ void Peer::disconnect() const {
858
+ node_->route_close(route_);
859
+ }
860
+
861
+ std::optional<PeerInfo> Peer::info() const {
862
+ return node_->peers_.info(id_);
863
+ }
864
+
865
+ } // namespace librats