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
@@ -1,14 +1,34 @@
1
- #include "socket.h"
2
- #include "network_utils.h"
3
- #include "logger.h"
1
+ #include "librats/core/socket.h"
2
+ #include "librats/util/network_utils.h"
3
+ #include "librats/util/logger.h"
4
4
  #include <iostream>
5
5
  #include <cstring>
6
6
  #include <mutex>
7
7
  #include <thread>
8
8
  #include <chrono>
9
9
  #ifndef _WIN32
10
- #include <fcntl.h> // for O_NONBLOCK
11
- #include <errno.h> // for errno
10
+ #include <fcntl.h> // for O_NONBLOCK
11
+ #include <errno.h> // for errno
12
+ #include <sys/uio.h> // for iovec (send_vectored)
13
+ #include <limits.h> // for IOV_MAX (send_vectored)
14
+ #include <netinet/in.h> // for IPPROTO_TCP (set_tcp_nodelay)
15
+ #include <netinet/tcp.h> // for TCP_NODELAY
16
+ // macOS/BSD have no MSG_NOSIGNAL; they suppress SIGPIPE with the SO_NOSIGPIPE
17
+ // socket option instead, which suppress_sigpipe() sets on every TCP socket we
18
+ // send on — so sending with no flag is the right fallback there.
19
+ #ifndef MSG_NOSIGNAL
20
+ #define MSG_NOSIGNAL 0
21
+ #endif
22
+ #endif
23
+
24
+ // RATS_HAVE_MMSG (recvmmsg/sendmmsg availability) is decided in socket.h, next to
25
+ // the kUdpBatchIsOneSyscall it publishes — callers outside this file need to know
26
+ // whether a batch is genuinely one syscall.
27
+
28
+ // On Windows, SIO_UDP_CONNRESET lives in <mstcpip.h>, which mingw doesn't always pull
29
+ // in via <winsock2.h>. Define it from its well-known control code as a fallback.
30
+ #if defined(_WIN32) && !defined(SIO_UDP_CONNRESET)
31
+ #define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)
12
32
  #endif
13
33
 
14
34
  // Socket module logging macros
@@ -45,33 +65,71 @@ static std::string socket_error_string(int error) {
45
65
  #endif
46
66
  }
47
67
 
48
- // Extract sender peer info from sockaddr_storage (shared by UDP receive)
49
- static void extract_sender_peer(const sockaddr_storage& sender_addr, Peer& peer) {
50
- if (sender_addr.ss_family == AF_INET) {
51
- char ip_str[INET_ADDRSTRLEN];
52
- const auto* addr_in = reinterpret_cast<const sockaddr_in*>(&sender_addr);
53
- inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, INET_ADDRSTRLEN);
54
- peer.ip = ip_str;
55
- peer.port = ntohs(addr_in->sin_port);
56
- } else if (sender_addr.ss_family == AF_INET6) {
57
- const auto* addr_in6 = reinterpret_cast<const sockaddr_in6*>(&sender_addr);
58
-
59
- // Check if this is an IPv4-mapped IPv6 address (::ffff:x.x.x.x)
60
- if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {
61
- char ip_str[INET_ADDRSTRLEN];
62
- struct in_addr ipv4_addr;
63
- memcpy(&ipv4_addr, &addr_in6->sin6_addr.s6_addr[12], 4);
64
- inet_ntop(AF_INET, &ipv4_addr, ip_str, INET_ADDRSTRLEN);
65
- peer.ip = ip_str;
66
- } else {
67
- char ip_str[INET6_ADDRSTRLEN];
68
- inet_ntop(AF_INET6, &addr_in6->sin6_addr, ip_str, INET6_ADDRSTRLEN);
69
- peer.ip = ip_str;
70
- }
71
- peer.port = ntohs(addr_in6->sin6_port);
68
+ static void set_last_socket_error(int error) {
69
+ #ifdef _WIN32
70
+ WSASetLastError(error);
71
+ #else
72
+ errno = error;
73
+ #endif
74
+ }
75
+
76
+ /*
77
+ * Put a failing socket back in the caller's hands with the error that actually
78
+ * caused it. close_socket() is a syscall like any other and overwrites errno on
79
+ * its way out, so a factory that cleans up before returning would otherwise hand
80
+ * the caller the close()'s error — or a stale success.
81
+ */
82
+ static socket_t fail_socket(socket_t socket, int error) {
83
+ close_socket(socket);
84
+ set_last_socket_error(error);
85
+ return RATS_INVALID_SOCKET;
86
+ }
87
+
88
+ bool last_error_was_port_unavailable() {
89
+ #ifdef _WIN32
90
+ /*
91
+ * WSAEACCES from a bind is not "denied" in the POSIX sense: it is how
92
+ * Windows reports a port inside a reserved range — the blocks Hyper-V,
93
+ * WinNAT and WSL2 carve out of the ephemeral range, or a port another
94
+ * process holds with SO_EXCLUSIVEADDRUSE. Like a clash, it is answered by
95
+ * moving to another port, and it has to be, because the TCP and UDP
96
+ * exclusion ranges are separate: a stream socket lands on a port the
97
+ * matching datagram bind is then refused.
98
+ */
99
+ const int error = get_last_socket_error();
100
+ return error == WSAEADDRINUSE || error == WSAEACCES;
101
+ #else
102
+ // No POSIX counterpart worth adding: EACCES there means a privileged port,
103
+ // which the next few ports up would be refused for just the same.
104
+ return get_last_socket_error() == EADDRINUSE;
105
+ #endif
106
+ }
107
+
108
+ void suppress_sigpipe(socket_t socket) {
109
+ #ifdef SO_NOSIGPIPE
110
+ int on = 1;
111
+ if (setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE,
112
+ reinterpret_cast<const char*>(&on), sizeof(on)) != 0) {
113
+ LOG_SOCKET_WARN("Failed to set SO_NOSIGPIPE on socket " << socket << ": "
114
+ << socket_error_string(get_last_socket_error()));
115
+ }
116
+ #else
117
+ (void)socket;
118
+ #endif
119
+ }
120
+
121
+ // Extract sender peer info from sockaddr_storage (shared by UDP receive).
122
+ // IpAddress::from_sockaddr copies the raw address bytes (and unwraps IPv4-mapped
123
+ // IPv6) with no textual round-trip; an unknown family yields an unspecified peer.
124
+ static void extract_sender_peer(const sockaddr_storage& sender_addr, Address& peer) {
125
+ const auto* sa = reinterpret_cast<const sockaddr*>(&sender_addr);
126
+ if (auto ip = IpAddress::from_sockaddr(sa)) {
127
+ peer.ip = *ip;
128
+ peer.port = (sender_addr.ss_family == AF_INET)
129
+ ? ntohs(reinterpret_cast<const sockaddr_in*>(&sender_addr)->sin_port)
130
+ : ntohs(reinterpret_cast<const sockaddr_in6*>(&sender_addr)->sin6_port);
72
131
  } else {
73
- peer.ip = "unknown";
74
- peer.port = 0;
132
+ peer = Address{};
75
133
  }
76
134
  }
77
135
 
@@ -81,10 +139,11 @@ static socket_t create_tcp_client_v4(const std::string& host, int port, int time
81
139
  LOG_SOCKET_DEBUG("Creating TCP client socket (IPv4) for " << host << ":" << port);
82
140
 
83
141
  socket_t client_socket = socket(AF_INET, SOCK_STREAM, 0);
84
- if (client_socket == INVALID_SOCKET_VALUE) {
142
+ if (client_socket == RATS_INVALID_SOCKET) {
85
143
  LOG_SOCKET_ERROR("Failed to create IPv4 client socket");
86
- return INVALID_SOCKET_VALUE;
144
+ return RATS_INVALID_SOCKET;
87
145
  }
146
+ suppress_sigpipe(client_socket);
88
147
 
89
148
  sockaddr_in server_addr;
90
149
  memset(&server_addr, 0, sizeof(server_addr));
@@ -95,13 +154,13 @@ static socket_t create_tcp_client_v4(const std::string& host, int port, int time
95
154
  if (resolved_ip.empty()) {
96
155
  LOG_SOCKET_ERROR("Failed to resolve hostname: " << host);
97
156
  close_socket(client_socket);
98
- return INVALID_SOCKET_VALUE;
157
+ return RATS_INVALID_SOCKET;
99
158
  }
100
159
 
101
160
  if (inet_pton(AF_INET, resolved_ip.c_str(), &server_addr.sin_addr) <= 0) {
102
161
  LOG_SOCKET_ERROR("Invalid address: " << resolved_ip);
103
162
  close_socket(client_socket);
104
- return INVALID_SOCKET_VALUE;
163
+ return RATS_INVALID_SOCKET;
105
164
  }
106
165
 
107
166
  LOG_SOCKET_DEBUG("Connecting to " << resolved_ip << ":" << port);
@@ -111,13 +170,13 @@ static socket_t create_tcp_client_v4(const std::string& host, int port, int time
111
170
  sizeof(server_addr), timeout_ms);
112
171
  } else {
113
172
  ok = (connect(client_socket, reinterpret_cast<sockaddr*>(&server_addr),
114
- sizeof(server_addr)) != SOCKET_ERROR_VALUE);
173
+ sizeof(server_addr)) != RATS_SOCKET_ERROR);
115
174
  }
116
175
 
117
176
  if (!ok) {
118
177
  LOG_SOCKET_DEBUG("Connection to " << resolved_ip << ":" << port << " failed");
119
178
  close_socket(client_socket);
120
- return INVALID_SOCKET_VALUE;
179
+ return RATS_INVALID_SOCKET;
121
180
  }
122
181
 
123
182
  LOG_SOCKET_INFO("Successfully connected to " << resolved_ip << ":" << port);
@@ -128,10 +187,11 @@ static socket_t create_tcp_client_v6(const std::string& host, int port, int time
128
187
  LOG_SOCKET_DEBUG("Creating TCP client socket (IPv6) for " << host << ":" << port);
129
188
 
130
189
  socket_t client_socket = socket(AF_INET6, SOCK_STREAM, 0);
131
- if (client_socket == INVALID_SOCKET_VALUE) {
190
+ if (client_socket == RATS_INVALID_SOCKET) {
132
191
  LOG_SOCKET_ERROR("Failed to create IPv6 client socket");
133
- return INVALID_SOCKET_VALUE;
192
+ return RATS_INVALID_SOCKET;
134
193
  }
194
+ suppress_sigpipe(client_socket);
135
195
 
136
196
  sockaddr_in6 server_addr;
137
197
  memset(&server_addr, 0, sizeof(server_addr));
@@ -142,13 +202,13 @@ static socket_t create_tcp_client_v6(const std::string& host, int port, int time
142
202
  if (resolved_ip.empty()) {
143
203
  LOG_SOCKET_DEBUG("Failed to resolve hostname to IPv6: " << host);
144
204
  close_socket(client_socket);
145
- return INVALID_SOCKET_VALUE;
205
+ return RATS_INVALID_SOCKET;
146
206
  }
147
207
 
148
208
  if (inet_pton(AF_INET6, resolved_ip.c_str(), &server_addr.sin6_addr) <= 0) {
149
209
  LOG_SOCKET_ERROR("Invalid IPv6 address: " << resolved_ip);
150
210
  close_socket(client_socket);
151
- return INVALID_SOCKET_VALUE;
211
+ return RATS_INVALID_SOCKET;
152
212
  }
153
213
 
154
214
  LOG_SOCKET_DEBUG("Connecting to IPv6 " << resolved_ip << ":" << port);
@@ -158,13 +218,13 @@ static socket_t create_tcp_client_v6(const std::string& host, int port, int time
158
218
  sizeof(server_addr), timeout_ms);
159
219
  } else {
160
220
  ok = (connect(client_socket, reinterpret_cast<sockaddr*>(&server_addr),
161
- sizeof(server_addr)) != SOCKET_ERROR_VALUE);
221
+ sizeof(server_addr)) != RATS_SOCKET_ERROR);
162
222
  }
163
223
 
164
224
  if (!ok) {
165
225
  LOG_SOCKET_DEBUG("Connection to IPv6 " << resolved_ip << ":" << port << " failed");
166
226
  close_socket(client_socket);
167
- return INVALID_SOCKET_VALUE;
227
+ return RATS_INVALID_SOCKET;
168
228
  }
169
229
 
170
230
  LOG_SOCKET_INFO("Successfully connected to IPv6 " << resolved_ip << ":" << port);
@@ -293,16 +353,102 @@ bool connect_with_timeout(socket_t socket, struct sockaddr* addr, socklen_t addr
293
353
  return false;
294
354
  }
295
355
 
356
+ // ── Non-blocking connect (reactor-driven) ───────────────────────────────────
357
+
358
+ static socket_t tcp_connect_start_family(int family, const std::string& resolved_ip, int port) {
359
+ socket_t s = socket(family, SOCK_STREAM, 0);
360
+ if (s == RATS_INVALID_SOCKET) return RATS_INVALID_SOCKET;
361
+ suppress_sigpipe(s);
362
+
363
+ if (!set_socket_nonblocking(s)) {
364
+ close_socket(s);
365
+ return RATS_INVALID_SOCKET;
366
+ }
367
+
368
+ sockaddr_storage ss;
369
+ memset(&ss, 0, sizeof(ss));
370
+ socklen_t addr_len = 0;
371
+
372
+ if (family == AF_INET) {
373
+ auto* a = reinterpret_cast<sockaddr_in*>(&ss);
374
+ a->sin_family = AF_INET;
375
+ a->sin_port = htons(static_cast<uint16_t>(port));
376
+ if (inet_pton(AF_INET, resolved_ip.c_str(), &a->sin_addr) <= 0) {
377
+ close_socket(s);
378
+ return RATS_INVALID_SOCKET;
379
+ }
380
+ addr_len = sizeof(sockaddr_in);
381
+ } else {
382
+ auto* a = reinterpret_cast<sockaddr_in6*>(&ss);
383
+ a->sin6_family = AF_INET6;
384
+ a->sin6_port = htons(static_cast<uint16_t>(port));
385
+ if (inet_pton(AF_INET6, resolved_ip.c_str(), &a->sin6_addr) <= 0) {
386
+ close_socket(s);
387
+ return RATS_INVALID_SOCKET;
388
+ }
389
+ addr_len = sizeof(sockaddr_in6);
390
+ }
391
+
392
+ int r = connect(s, reinterpret_cast<sockaddr*>(&ss), addr_len);
393
+ if (r == 0) {
394
+ return s; // Connected synchronously (common on loopback).
395
+ }
396
+ #ifdef _WIN32
397
+ int e = WSAGetLastError();
398
+ if (e == WSAEWOULDBLOCK || e == WSAEINPROGRESS) return s; // In progress.
399
+ #else
400
+ if (errno == EINPROGRESS) return s; // In progress.
401
+ #endif
402
+ LOG_SOCKET_DEBUG("Non-blocking connect to " << resolved_ip << ":" << port << " failed to start");
403
+ close_socket(s);
404
+ return RATS_INVALID_SOCKET;
405
+ }
406
+
407
+ socket_t tcp_connect_start(const std::string& host, int port) {
408
+ if (!validate_port(port)) return RATS_INVALID_SOCKET;
409
+
410
+ // Prefer IPv6, fall back to IPv4 — same precedence as create_tcp_client().
411
+ // Note: fallback happens at *resolution* time; a v6 address that resolves
412
+ // but fails asynchronously surfaces later as ConnectFailed (happy-eyeballs
413
+ // sequencing belongs in the higher-level dialer).
414
+ std::string ip6 = network_utils::resolve_hostname_v6(host);
415
+ if (!ip6.empty()) {
416
+ socket_t s = tcp_connect_start_family(AF_INET6, ip6, port);
417
+ if (is_valid_socket(s)) return s;
418
+ }
419
+
420
+ std::string ip4 = network_utils::resolve_hostname(host);
421
+ if (!ip4.empty()) {
422
+ socket_t s = tcp_connect_start_family(AF_INET, ip4, port);
423
+ if (is_valid_socket(s)) return s;
424
+ }
425
+
426
+ return RATS_INVALID_SOCKET;
427
+ }
428
+
429
+ int tcp_connect_result(socket_t socket) {
430
+ int err = 0;
431
+ socklen_t len = sizeof(err);
432
+ if (getsockopt(socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&err), &len) != 0) {
433
+ #ifdef _WIN32
434
+ return WSAGetLastError();
435
+ #else
436
+ return errno;
437
+ #endif
438
+ }
439
+ return err; // 0 == connected
440
+ }
441
+
296
442
  // ── TCP Socket Functions ────────────────────────────────────────────────────
297
443
 
298
444
  socket_t create_tcp_client(const std::string& host, int port, int timeout_ms) {
299
- if (!validate_port(port)) return INVALID_SOCKET_VALUE;
445
+ if (!validate_port(port)) return RATS_INVALID_SOCKET;
300
446
 
301
447
  LOG_SOCKET_DEBUG("Creating TCP client socket (dual stack) for " << host << ":" << port);
302
448
 
303
449
  // Try IPv6 first
304
450
  socket_t client_socket = create_tcp_client_v6(host, port, timeout_ms);
305
- if (client_socket != INVALID_SOCKET_VALUE) {
451
+ if (client_socket != RATS_INVALID_SOCKET) {
306
452
  LOG_SOCKET_INFO("Successfully connected using IPv6");
307
453
  return client_socket;
308
454
  }
@@ -310,17 +456,17 @@ socket_t create_tcp_client(const std::string& host, int port, int timeout_ms) {
310
456
  // Fall back to IPv4
311
457
  LOG_SOCKET_DEBUG("IPv6 connection failed, trying IPv4");
312
458
  client_socket = create_tcp_client_v4(host, port, timeout_ms);
313
- if (client_socket != INVALID_SOCKET_VALUE) {
459
+ if (client_socket != RATS_INVALID_SOCKET) {
314
460
  LOG_SOCKET_INFO("Successfully connected using IPv4");
315
461
  return client_socket;
316
462
  }
317
463
 
318
464
  LOG_SOCKET_DEBUG("Failed to connect using both IPv6 and IPv4");
319
- return INVALID_SOCKET_VALUE;
465
+ return RATS_INVALID_SOCKET;
320
466
  }
321
467
 
322
468
  socket_t create_tcp_server(int port, int backlog, const std::string& bind_address, AddressFamily af) {
323
- if (!validate_port(port)) return INVALID_SOCKET_VALUE;
469
+ if (!validate_port(port)) return RATS_INVALID_SOCKET;
324
470
 
325
471
  const char* af_label = (af == AddressFamily::IPv4) ? "IPv4" :
326
472
  (af == AddressFamily::IPv6) ? "IPv6" : "dual stack";
@@ -330,27 +476,30 @@ socket_t create_tcp_server(int port, int backlog, const std::string& bind_addres
330
476
  int family = (af == AddressFamily::IPv4) ? AF_INET : AF_INET6;
331
477
 
332
478
  socket_t server_socket = socket(family, SOCK_STREAM, 0);
333
- if (server_socket == INVALID_SOCKET_VALUE) {
334
- LOG_SOCKET_ERROR("Failed to create " << af_label << " server socket");
335
- return INVALID_SOCKET_VALUE;
479
+ if (server_socket == RATS_INVALID_SOCKET) {
480
+ LOG_SOCKET_ERROR("Failed to create " << af_label << " server socket (error: "
481
+ << socket_error_string(get_last_socket_error()) << ")");
482
+ return RATS_INVALID_SOCKET;
336
483
  }
337
484
 
338
485
  // Set socket option to reuse address
339
486
  int opt = 1;
340
487
  if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR,
341
- (char*)&opt, sizeof(opt)) == SOCKET_ERROR_VALUE) {
342
- LOG_SOCKET_ERROR("Failed to set " << af_label << " socket options");
488
+ (char*)&opt, sizeof(opt)) == RATS_SOCKET_ERROR) {
489
+ LOG_SOCKET_ERROR("Failed to set " << af_label << " socket options (error: "
490
+ << socket_error_string(get_last_socket_error()) << ")");
343
491
  close_socket(server_socket);
344
- return INVALID_SOCKET_VALUE;
492
+ return RATS_INVALID_SOCKET;
345
493
  }
346
494
 
347
495
  // For IPv6/DualStack sockets, configure IPV6_V6ONLY
348
496
  if (family == AF_INET6) {
349
497
  int ipv6_only = (af == AddressFamily::IPv6) ? 1 : 0;
350
498
  if (setsockopt(server_socket, IPPROTO_IPV6, IPV6_V6ONLY,
351
- (char*)&ipv6_only, sizeof(ipv6_only)) == SOCKET_ERROR_VALUE) {
499
+ (char*)&ipv6_only, sizeof(ipv6_only)) == RATS_SOCKET_ERROR) {
352
500
  if (af == AddressFamily::DualStack) {
353
- LOG_SOCKET_WARN("Failed to disable IPv6-only mode, will be IPv6 only");
501
+ LOG_SOCKET_WARN("Failed to disable IPv6-only mode, will be IPv6 only (error: "
502
+ << socket_error_string(get_last_socket_error()) << ")");
354
503
  }
355
504
  }
356
505
  }
@@ -368,15 +517,16 @@ socket_t create_tcp_server(int port, int backlog, const std::string& bind_addres
368
517
  if (inet_pton(AF_INET, bind_address.c_str(), &server_addr.sin_addr) != 1) {
369
518
  LOG_SOCKET_ERROR("Invalid IPv4 bind address: " << bind_address);
370
519
  close_socket(server_socket);
371
- return INVALID_SOCKET_VALUE;
520
+ return RATS_INVALID_SOCKET;
372
521
  }
373
522
  }
374
523
 
375
524
  if (bind(server_socket, reinterpret_cast<sockaddr*>(&server_addr),
376
- sizeof(server_addr)) == SOCKET_ERROR_VALUE) {
377
- LOG_SOCKET_ERROR("Failed to bind " << af_label << " server socket to port " << port);
378
- close_socket(server_socket);
379
- return INVALID_SOCKET_VALUE;
525
+ sizeof(server_addr)) == RATS_SOCKET_ERROR) {
526
+ const int error = get_last_socket_error();
527
+ LOG_SOCKET_ERROR("Failed to bind " << af_label << " server socket to port " << port
528
+ << " (error: " << socket_error_string(error) << ")");
529
+ return fail_socket(server_socket, error);
380
530
  }
381
531
  } else {
382
532
  sockaddr_in6 server_addr;
@@ -390,22 +540,24 @@ socket_t create_tcp_server(int port, int backlog, const std::string& bind_addres
390
540
  if (inet_pton(AF_INET6, bind_address.c_str(), &server_addr.sin6_addr) != 1) {
391
541
  LOG_SOCKET_ERROR("Invalid IPv6 bind address: " << bind_address);
392
542
  close_socket(server_socket);
393
- return INVALID_SOCKET_VALUE;
543
+ return RATS_INVALID_SOCKET;
394
544
  }
395
545
  }
396
546
 
397
547
  if (bind(server_socket, reinterpret_cast<sockaddr*>(&server_addr),
398
- sizeof(server_addr)) == SOCKET_ERROR_VALUE) {
399
- LOG_SOCKET_ERROR("Failed to bind " << af_label << " server socket to port " << port);
400
- close_socket(server_socket);
401
- return INVALID_SOCKET_VALUE;
548
+ sizeof(server_addr)) == RATS_SOCKET_ERROR) {
549
+ const int error = get_last_socket_error();
550
+ LOG_SOCKET_ERROR("Failed to bind " << af_label << " server socket to port " << port
551
+ << " (error: " << socket_error_string(error) << ")");
552
+ return fail_socket(server_socket, error);
402
553
  }
403
554
  }
404
555
 
405
- if (listen(server_socket, backlog) == SOCKET_ERROR_VALUE) {
406
- LOG_SOCKET_ERROR("Failed to listen on " << af_label << " server socket");
556
+ if (listen(server_socket, backlog) == RATS_SOCKET_ERROR) {
557
+ LOG_SOCKET_ERROR("Failed to listen on " << af_label << " server socket (error: "
558
+ << socket_error_string(get_last_socket_error()) << ")");
407
559
  close_socket(server_socket);
408
- return INVALID_SOCKET_VALUE;
560
+ return RATS_INVALID_SOCKET;
409
561
  }
410
562
 
411
563
  LOG_SOCKET_INFO(af_label << " server listening on port " << port << " (backlog: " << backlog << ")");
@@ -417,10 +569,11 @@ socket_t accept_client(socket_t server_socket) {
417
569
  socklen_t client_addr_len = sizeof(client_addr);
418
570
 
419
571
  socket_t client_socket = accept(server_socket, reinterpret_cast<sockaddr*>(&client_addr), &client_addr_len);
420
- if (client_socket == INVALID_SOCKET_VALUE) {
572
+ if (client_socket == RATS_INVALID_SOCKET) {
421
573
  LOG_SOCKET_ERROR("Failed to accept client connection");
422
- return INVALID_SOCKET_VALUE;
574
+ return RATS_INVALID_SOCKET;
423
575
  }
576
+ suppress_sigpipe(client_socket);
424
577
 
425
578
  if (client_addr.ss_family == AF_INET) {
426
579
  char client_ip[INET_ADDRSTRLEN];
@@ -443,7 +596,7 @@ std::string get_peer_address(socket_t socket) {
443
596
  sockaddr_storage peer_addr;
444
597
  socklen_t peer_addr_len = sizeof(peer_addr);
445
598
 
446
- if (getpeername(socket, reinterpret_cast<sockaddr*>(&peer_addr), &peer_addr_len) == SOCKET_ERROR_VALUE) {
599
+ if (getpeername(socket, reinterpret_cast<sockaddr*>(&peer_addr), &peer_addr_len) == RATS_SOCKET_ERROR) {
447
600
  LOG_SOCKET_ERROR("Failed to get peer address for socket " << socket);
448
601
  return "";
449
602
  }
@@ -471,6 +624,58 @@ std::string get_peer_address(socket_t socket) {
471
624
  return peer_ip + ":" + std::to_string(peer_port);
472
625
  }
473
626
 
627
+ std::optional<Address> get_peer_endpoint(socket_t socket) {
628
+ sockaddr_storage ss;
629
+ socklen_t len = sizeof(ss);
630
+ if (getpeername(socket, reinterpret_cast<sockaddr*>(&ss), &len) == RATS_SOCKET_ERROR)
631
+ return std::nullopt;
632
+ auto ip = IpAddress::from_sockaddr(reinterpret_cast<sockaddr*>(&ss));
633
+ if (!ip) return std::nullopt;
634
+ const uint16_t port = (ss.ss_family == AF_INET)
635
+ ? ntohs(reinterpret_cast<sockaddr_in*>(&ss)->sin_port)
636
+ : ntohs(reinterpret_cast<sockaddr_in6*>(&ss)->sin6_port);
637
+ return Address{*ip, port};
638
+ }
639
+
640
+ std::ptrdiff_t send_vectored(socket_t socket, const ByteView* slices, size_t count) {
641
+ if (count == 0) return 0;
642
+ if (count > kMaxSendSlices) count = kMaxSendSlices;
643
+ #if !defined(_WIN32) && defined(IOV_MAX)
644
+ // sendmsg() rejects an iovec longer than IOV_MAX with EINVAL — which the callers
645
+ // read as "the peer is gone" and would close a perfectly healthy connection over.
646
+ // POSIX only guarantees 16 (Linux and macOS allow 1024), so clamp rather than
647
+ // trust kMaxSendSlices to be under every platform's limit. The slices that don't
648
+ // fit are not lost: they go out on the next round of the caller's flush loop.
649
+ if (count > static_cast<size_t>(IOV_MAX)) count = static_cast<size_t>(IOV_MAX);
650
+ #endif
651
+
652
+ #ifdef _WIN32
653
+ WSABUF bufs[kMaxSendSlices];
654
+ for (size_t i = 0; i < count; ++i) {
655
+ bufs[i].buf = reinterpret_cast<CHAR*>(const_cast<uint8_t*>(slices[i].data()));
656
+ bufs[i].len = static_cast<ULONG>(slices[i].size());
657
+ }
658
+ DWORD sent = 0;
659
+ if (WSASend(socket, bufs, static_cast<DWORD>(count), &sent, 0, nullptr, nullptr) == SOCKET_ERROR) {
660
+ return -1;
661
+ }
662
+ return static_cast<std::ptrdiff_t>(sent);
663
+ #else
664
+ struct iovec iov[kMaxSendSlices];
665
+ for (size_t i = 0; i < count; ++i) {
666
+ iov[i].iov_base = const_cast<uint8_t*>(slices[i].data());
667
+ iov[i].iov_len = slices[i].size();
668
+ }
669
+ struct msghdr msg = {};
670
+ msg.msg_iov = iov;
671
+ msg.msg_iovlen = static_cast<decltype(msg.msg_iovlen)>(count);
672
+ // MSG_NOSIGNAL keeps a peer that hung up from killing the process with SIGPIPE.
673
+ // It doesn't exist on macOS/BSD (they use SO_NOSIGPIPE on the socket instead),
674
+ // where the fallback below makes this a plain sendmsg().
675
+ return ::sendmsg(socket, &msg, MSG_NOSIGNAL);
676
+ #endif
677
+ }
678
+
474
679
  int send_tcp_data(socket_t socket, const std::vector<uint8_t>& data) {
475
680
  LOG_SOCKET_DEBUG("Sending " << data.size() << " bytes to TCP socket " << socket);
476
681
 
@@ -484,7 +689,7 @@ int send_tcp_data(socket_t socket, const std::vector<uint8_t>& data) {
484
689
  #else
485
690
  int bytes_sent = send(socket, buffer + total_sent, remaining, MSG_NOSIGNAL);
486
691
  #endif
487
- if (bytes_sent == SOCKET_ERROR_VALUE) {
692
+ if (bytes_sent == RATS_SOCKET_ERROR) {
488
693
  int error = get_last_socket_error();
489
694
  #ifdef _WIN32
490
695
  if (error == WSAEWOULDBLOCK) { continue; }
@@ -523,7 +728,7 @@ std::vector<uint8_t> receive_tcp_data(socket_t socket, size_t buffer_size) {
523
728
  std::vector<uint8_t> buffer(buffer_size);
524
729
 
525
730
  int bytes_received = recv(socket, reinterpret_cast<char*>(buffer.data()), buffer_size, 0);
526
- if (bytes_received == SOCKET_ERROR_VALUE) {
731
+ if (bytes_received == RATS_SOCKET_ERROR) {
527
732
  int error = get_last_socket_error();
528
733
  #ifdef _WIN32
529
734
  if (error == WSAEWOULDBLOCK) { return {}; }
@@ -579,7 +784,7 @@ static std::vector<uint8_t> receive_exact_bytes(socket_t socket, size_t num_byte
579
784
  std::vector<uint8_t> buffer(num_bytes - total_received);
580
785
  int bytes_received = recv(socket, reinterpret_cast<char*>(buffer.data()), buffer.size(), 0);
581
786
 
582
- if (bytes_received == SOCKET_ERROR_VALUE) {
787
+ if (bytes_received == RATS_SOCKET_ERROR) {
583
788
  int error = get_last_socket_error();
584
789
  #ifdef _WIN32
585
790
  if (error == WSAEWOULDBLOCK) {
@@ -656,7 +861,7 @@ int send_tcp_string(socket_t socket, const std::string& data) {
656
861
  // ── UDP Socket Functions ────────────────────────────────────────────────────
657
862
 
658
863
  socket_t create_udp_socket(int port, const std::string& bind_address, AddressFamily af) {
659
- if (!validate_port(port)) return INVALID_SOCKET_VALUE;
864
+ if (!validate_port(port)) return RATS_INVALID_SOCKET;
660
865
 
661
866
  const char* af_label = (af == AddressFamily::IPv4) ? "IPv4" :
662
867
  (af == AddressFamily::IPv6) ? "IPv6" : "dual stack";
@@ -666,28 +871,55 @@ socket_t create_udp_socket(int port, const std::string& bind_address, AddressFam
666
871
  int family = (af == AddressFamily::IPv4) ? AF_INET : AF_INET6;
667
872
 
668
873
  socket_t udp_socket = socket(family, SOCK_DGRAM, 0);
669
- if (udp_socket == INVALID_SOCKET_VALUE) {
874
+ if (udp_socket == RATS_INVALID_SOCKET) {
670
875
  LOG_SOCKET_ERROR("Failed to create " << af_label << " UDP socket (error: "
671
876
  << socket_error_string(get_last_socket_error()) << ")");
672
- return INVALID_SOCKET_VALUE;
877
+ return RATS_INVALID_SOCKET;
673
878
  }
674
879
 
675
- // Set socket option to reuse address
676
- int opt = 1;
677
- if (setsockopt(udp_socket, SOL_SOCKET, SO_REUSEADDR,
678
- (char*)&opt, sizeof(opt)) == SOCKET_ERROR_VALUE) {
679
- LOG_SOCKET_ERROR("Failed to set " << af_label << " UDP socket options");
680
- close_socket(udp_socket);
681
- return INVALID_SOCKET_VALUE;
880
+ #ifdef _WIN32
881
+ // Disable the Windows-only "UDP connection reset" behaviour. By default, when a
882
+ // datagram we sent provokes an ICMP "port unreachable", Windows fails the *next*
883
+ // recvfrom() on this socket with WSAECONNRESET — nonsensical for a connectionless
884
+ // protocol. A DHT constantly sends to dead/unreachable nodes, so without this every
885
+ // such ICMP would cost us a receive cycle. FALSE turns it off.
886
+ {
887
+ BOOL connreset = FALSE;
888
+ DWORD bytes_returned = 0;
889
+ WSAIoctl(udp_socket, SIO_UDP_CONNRESET, &connreset, sizeof(connreset),
890
+ nullptr, 0, &bytes_returned, nullptr, nullptr);
891
+ }
892
+ #endif
893
+
894
+ // Reuse the address — but only for a port we asked for by number, where the
895
+ // point is to rebind a port a previous run may still be lingering on.
896
+ //
897
+ // Never when the kernel is choosing the port. On a datagram socket the option
898
+ // does not merely relax rebinding, it takes the port out of the set an auto-bind
899
+ // must avoid: asked for port 0, the kernel may return a port another socket
900
+ // already holds, provided that one set the option too — and every UDP socket
901
+ // here did. The two then share the port and the kernel picks one of them per
902
+ // datagram, so a dial-only socket can land on the port a listener beside it is
903
+ // serving and start eating its traffic. Rare, and invisible from either end.
904
+ if (port != 0) {
905
+ int opt = 1;
906
+ if (setsockopt(udp_socket, SOL_SOCKET, SO_REUSEADDR,
907
+ (char*)&opt, sizeof(opt)) == RATS_SOCKET_ERROR) {
908
+ LOG_SOCKET_ERROR("Failed to set " << af_label << " UDP socket options (error: "
909
+ << socket_error_string(get_last_socket_error()) << ")");
910
+ close_socket(udp_socket);
911
+ return RATS_INVALID_SOCKET;
912
+ }
682
913
  }
683
914
 
684
915
  // For IPv6/DualStack sockets, configure IPV6_V6ONLY
685
916
  if (family == AF_INET6) {
686
917
  int ipv6_only = (af == AddressFamily::IPv6) ? 1 : 0;
687
918
  if (setsockopt(udp_socket, IPPROTO_IPV6, IPV6_V6ONLY,
688
- (char*)&ipv6_only, sizeof(ipv6_only)) == SOCKET_ERROR_VALUE) {
919
+ (char*)&ipv6_only, sizeof(ipv6_only)) == RATS_SOCKET_ERROR) {
689
920
  if (af == AddressFamily::DualStack) {
690
- LOG_SOCKET_WARN("Failed to disable IPv6-only mode, will be IPv6 only");
921
+ LOG_SOCKET_WARN("Failed to disable IPv6-only mode, will be IPv6 only (error: "
922
+ << socket_error_string(get_last_socket_error()) << ")");
691
923
  }
692
924
  }
693
925
  }
@@ -705,15 +937,15 @@ socket_t create_udp_socket(int port, const std::string& bind_address, AddressFam
705
937
  if (inet_pton(AF_INET, bind_address.c_str(), &addr.sin_addr) != 1) {
706
938
  LOG_SOCKET_ERROR("Invalid IPv4 bind address: " << bind_address);
707
939
  close_socket(udp_socket);
708
- return INVALID_SOCKET_VALUE;
940
+ return RATS_INVALID_SOCKET;
709
941
  }
710
942
  }
711
943
 
712
- if (bind(udp_socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR_VALUE) {
944
+ if (bind(udp_socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == RATS_SOCKET_ERROR) {
945
+ const int error = get_last_socket_error();
713
946
  LOG_SOCKET_ERROR("Failed to bind " << af_label << " UDP socket to port " << port
714
- << " (error: " << socket_error_string(get_last_socket_error()) << ")");
715
- close_socket(udp_socket);
716
- return INVALID_SOCKET_VALUE;
947
+ << " (error: " << socket_error_string(error) << ")");
948
+ return fail_socket(udp_socket, error);
717
949
  }
718
950
  } else {
719
951
  sockaddr_in6 addr;
@@ -727,15 +959,15 @@ socket_t create_udp_socket(int port, const std::string& bind_address, AddressFam
727
959
  if (inet_pton(AF_INET6, bind_address.c_str(), &addr.sin6_addr) != 1) {
728
960
  LOG_SOCKET_ERROR("Invalid IPv6 bind address: " << bind_address);
729
961
  close_socket(udp_socket);
730
- return INVALID_SOCKET_VALUE;
962
+ return RATS_INVALID_SOCKET;
731
963
  }
732
964
  }
733
965
 
734
- if (bind(udp_socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR_VALUE) {
966
+ if (bind(udp_socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == RATS_SOCKET_ERROR) {
967
+ const int error = get_last_socket_error();
735
968
  LOG_SOCKET_ERROR("Failed to bind " << af_label << " UDP socket to port " << port
736
- << " (error: " << socket_error_string(get_last_socket_error()) << ")");
737
- close_socket(udp_socket);
738
- return INVALID_SOCKET_VALUE;
969
+ << " (error: " << socket_error_string(error) << ")");
970
+ return fail_socket(udp_socket, error);
739
971
  }
740
972
  }
741
973
 
@@ -833,7 +1065,7 @@ int send_udp_data(socket_t socket, const std::vector<uint8_t>& data,
833
1065
 
834
1066
  int bytes_sent = sendto(socket, (char*)data.data(), data.size(), 0,
835
1067
  reinterpret_cast<sockaddr*>(&dest_addr), addr_len);
836
- if (bytes_sent == SOCKET_ERROR_VALUE) {
1068
+ if (bytes_sent == RATS_SOCKET_ERROR) {
837
1069
  LOG_SOCKET_ERROR("Failed to send UDP data to " << host << ":" << port
838
1070
  << " (error: " << socket_error_string(get_last_socket_error()) << ")");
839
1071
  return -1;
@@ -843,8 +1075,247 @@ int send_udp_data(socket_t socket, const std::vector<uint8_t>& data,
843
1075
  return bytes_sent;
844
1076
  }
845
1077
 
846
- std::vector<uint8_t> receive_udp_data(socket_t socket, size_t buffer_size, Peer& sender_peer,
847
- int timeout_ms, socket_t interrupt_fd) {
1078
+ // Build a UDP destination sockaddr straight from a numeric Address — no hostname
1079
+ // resolution and no inet_pton, just a memcpy of the raw address bytes. On a
1080
+ // DualStack/IPv6 socket an IPv4 address is written as an IPv4-mapped IPv6 address.
1081
+ static bool build_udp_dest_addr(const IpAddress& ip, int port, AddressFamily af,
1082
+ sockaddr_storage& addr, socklen_t& addr_len) {
1083
+ memset(&addr, 0, sizeof(addr));
1084
+ if (ip.is_v6()) {
1085
+ auto* a6 = reinterpret_cast<sockaddr_in6*>(&addr);
1086
+ a6->sin6_family = AF_INET6;
1087
+ a6->sin6_port = htons(static_cast<uint16_t>(port));
1088
+ memcpy(&a6->sin6_addr, ip.bytes().data(), 16);
1089
+ addr_len = sizeof(sockaddr_in6);
1090
+ return true;
1091
+ }
1092
+ if (ip.is_v4()) {
1093
+ if (af == AddressFamily::IPv4) {
1094
+ auto* a4 = reinterpret_cast<sockaddr_in*>(&addr);
1095
+ a4->sin_family = AF_INET;
1096
+ a4->sin_port = htons(static_cast<uint16_t>(port));
1097
+ memcpy(&a4->sin_addr, ip.bytes().data(), 4);
1098
+ addr_len = sizeof(sockaddr_in);
1099
+ } else {
1100
+ auto* a6 = reinterpret_cast<sockaddr_in6*>(&addr);
1101
+ a6->sin6_family = AF_INET6;
1102
+ a6->sin6_port = htons(static_cast<uint16_t>(port));
1103
+ a6->sin6_addr.s6_addr[10] = 0xff;
1104
+ a6->sin6_addr.s6_addr[11] = 0xff;
1105
+ memcpy(&a6->sin6_addr.s6_addr[12], ip.bytes().data(), 4);
1106
+ addr_len = sizeof(sockaddr_in6);
1107
+ }
1108
+ return true;
1109
+ }
1110
+ return false; // unspecified — nothing to send to
1111
+ }
1112
+
1113
+ int send_udp_data(socket_t socket, const std::vector<uint8_t>& data,
1114
+ const Address& dest, AddressFamily af) {
1115
+ sockaddr_storage dest_addr;
1116
+ socklen_t addr_len;
1117
+ if (!build_udp_dest_addr(dest.ip, dest.port, af, dest_addr, addr_len)) return -1;
1118
+
1119
+ int bytes_sent = sendto(socket, (char*)data.data(), data.size(), 0,
1120
+ reinterpret_cast<sockaddr*>(&dest_addr), addr_len);
1121
+ if (bytes_sent == RATS_SOCKET_ERROR) {
1122
+ LOG_SOCKET_ERROR("Failed to send UDP data to " << dest.to_string()
1123
+ << " (error: " << socket_error_string(get_last_socket_error()) << ")");
1124
+ return -1;
1125
+ }
1126
+ return bytes_sent;
1127
+ }
1128
+
1129
+ bool set_socket_buffer_sizes(socket_t socket, int recv_bytes, int send_bytes) {
1130
+ bool ok = true;
1131
+ if (recv_bytes > 0 &&
1132
+ setsockopt(socket, SOL_SOCKET, SO_RCVBUF,
1133
+ reinterpret_cast<const char*>(&recv_bytes), sizeof(recv_bytes)) != 0) {
1134
+ LOG_SOCKET_DEBUG("Could not set SO_RCVBUF to " << recv_bytes);
1135
+ ok = false;
1136
+ }
1137
+ if (send_bytes > 0 &&
1138
+ setsockopt(socket, SOL_SOCKET, SO_SNDBUF,
1139
+ reinterpret_cast<const char*>(&send_bytes), sizeof(send_bytes)) != 0) {
1140
+ LOG_SOCKET_DEBUG("Could not set SO_SNDBUF to " << send_bytes);
1141
+ ok = false;
1142
+ }
1143
+ return ok;
1144
+ }
1145
+
1146
+ bool set_tcp_nodelay(socket_t socket) {
1147
+ const int on = 1;
1148
+ if (setsockopt(socket, IPPROTO_TCP, TCP_NODELAY,
1149
+ reinterpret_cast<const char*>(&on), sizeof(on)) != 0) {
1150
+ LOG_SOCKET_DEBUG("Could not disable Nagle on socket " << socket);
1151
+ return false;
1152
+ }
1153
+ return true;
1154
+ }
1155
+
1156
+ std::ptrdiff_t send_udp_to(socket_t socket, const void* data, size_t len,
1157
+ const Address& dest, AddressFamily af) {
1158
+ sockaddr_storage dest_addr;
1159
+ socklen_t addr_len;
1160
+ if (!build_udp_dest_addr(dest.ip, dest.port, af, dest_addr, addr_len)) return -1;
1161
+
1162
+ const int sent = sendto(socket, static_cast<const char*>(data), static_cast<int>(len), 0,
1163
+ reinterpret_cast<sockaddr*>(&dest_addr), addr_len);
1164
+ if (sent != RATS_SOCKET_ERROR) return sent;
1165
+
1166
+ const int error = get_last_socket_error();
1167
+ #ifdef _WIN32
1168
+ if (error == WSAEWOULDBLOCK) return 0;
1169
+ #else
1170
+ if (error == EAGAIN || error == EWOULDBLOCK || error == ENOBUFS) return 0;
1171
+ #endif
1172
+ // A datagram socket reports per-destination problems (ICMP unreachable on a
1173
+ // connected socket, a route that just went away) asynchronously on a later call.
1174
+ // They say nothing about the socket itself, so they are the caller's business,
1175
+ // not a fatal condition — hence a debug log rather than an error.
1176
+ LOG_SOCKET_DEBUG("Failed to send datagram to " << dest.to_string()
1177
+ << " (error: " << socket_error_string(error) << ")");
1178
+ return -1;
1179
+ }
1180
+
1181
+ std::ptrdiff_t recv_udp_from(socket_t socket, void* buffer, size_t len, Address& from) {
1182
+ sockaddr_storage sender_addr;
1183
+ socklen_t sender_addr_len = sizeof(sender_addr);
1184
+
1185
+ const int received = recvfrom(socket, static_cast<char*>(buffer), static_cast<int>(len), 0,
1186
+ reinterpret_cast<sockaddr*>(&sender_addr), &sender_addr_len);
1187
+ if (received != RATS_SOCKET_ERROR) {
1188
+ extract_sender_peer(sender_addr, from);
1189
+ return received;
1190
+ }
1191
+
1192
+ const int error = get_last_socket_error();
1193
+ #ifdef _WIN32
1194
+ if (error == WSAEWOULDBLOCK) return kUdpRecvWouldBlock;
1195
+ // Windows reports an ICMP port-unreachable for a *previous* datagram as an error
1196
+ // on the next recvfrom(). It concerns one destination, not the socket, so keep
1197
+ // draining rather than treating the whole mux as broken.
1198
+ if (error == WSAECONNRESET || error == WSAENETRESET) return kUdpRecvError;
1199
+ #else
1200
+ if (error == EAGAIN || error == EWOULDBLOCK) return kUdpRecvWouldBlock;
1201
+ if (error == EINTR || error == ECONNREFUSED) return kUdpRecvError;
1202
+ #endif
1203
+ LOG_SOCKET_DEBUG("Failed to receive datagram: " << socket_error_string(error));
1204
+ return kUdpRecvError;
1205
+ }
1206
+
1207
+ // ── Batched datagram I/O ────────────────────────────────────────────────────
1208
+
1209
+ std::ptrdiff_t recv_udp_batch(socket_t socket, UdpBatchSlot* slots, size_t count) {
1210
+ if (count == 0) return kUdpRecvWouldBlock;
1211
+ if (count > kUdpBatchMax) count = kUdpBatchMax;
1212
+
1213
+ #ifdef RATS_HAVE_MMSG
1214
+ mmsghdr msgs[kUdpBatchMax];
1215
+ iovec iov[kUdpBatchMax];
1216
+ sockaddr_storage addrs[kUdpBatchMax];
1217
+
1218
+ memset(msgs, 0, sizeof(mmsghdr) * count);
1219
+ for (size_t i = 0; i < count; ++i) {
1220
+ iov[i].iov_base = slots[i].data;
1221
+ iov[i].iov_len = slots[i].len;
1222
+ msgs[i].msg_hdr.msg_name = &addrs[i];
1223
+ msgs[i].msg_hdr.msg_namelen = sizeof(sockaddr_storage);
1224
+ msgs[i].msg_hdr.msg_iov = &iov[i];
1225
+ msgs[i].msg_hdr.msg_iovlen = 1;
1226
+ }
1227
+
1228
+ // MSG_DONTWAIT matters even on a socket that is already non-blocking: without
1229
+ // it recvmmsg() waits for the *whole* array to fill when no timeout is given.
1230
+ const int n = recvmmsg(socket, msgs, static_cast<unsigned int>(count), MSG_DONTWAIT, nullptr);
1231
+ if (n > 0) {
1232
+ for (int i = 0; i < n; ++i) {
1233
+ slots[i].len = msgs[i].msg_len;
1234
+ extract_sender_peer(addrs[i], slots[i].endpoint);
1235
+ }
1236
+ return n;
1237
+ }
1238
+ if (n == 0) return kUdpRecvWouldBlock;
1239
+
1240
+ const int error = get_last_socket_error();
1241
+ if (error == EAGAIN || error == EWOULDBLOCK) return kUdpRecvWouldBlock;
1242
+ // Only an error on the *first* datagram is reported here; one further in simply
1243
+ // truncates the batch, which is why a short result is not proof of an empty
1244
+ // queue. Either way this concerns one destination, not the socket.
1245
+ if (error == EINTR || error == ECONNREFUSED) return kUdpRecvError;
1246
+ LOG_SOCKET_DEBUG("Failed to receive a datagram batch: " << socket_error_string(error));
1247
+ return kUdpRecvError;
1248
+ #else
1249
+ size_t got = 0;
1250
+ for (size_t i = 0; i < count; ++i) {
1251
+ Address from;
1252
+ const std::ptrdiff_t n = recv_udp_from(socket, slots[i].data, slots[i].len, from);
1253
+ if (n == kUdpRecvWouldBlock) break;
1254
+ // Report what has already arrived and let the caller come back for the rest;
1255
+ // only an error on the very first datagram has nothing to report alongside it.
1256
+ if (n == kUdpRecvError) return got > 0 ? static_cast<std::ptrdiff_t>(got) : kUdpRecvError;
1257
+ slots[i].len = static_cast<size_t>(n);
1258
+ slots[i].endpoint = from;
1259
+ ++got;
1260
+ }
1261
+ return got > 0 ? static_cast<std::ptrdiff_t>(got) : kUdpRecvWouldBlock;
1262
+ #endif
1263
+ }
1264
+
1265
+ size_t send_udp_batch(socket_t socket, const UdpBatchSlot* slots, size_t count, AddressFamily af) {
1266
+ if (count == 0) return 0;
1267
+ if (count > kUdpBatchMax) count = kUdpBatchMax;
1268
+
1269
+ #ifdef RATS_HAVE_MMSG
1270
+ mmsghdr msgs[kUdpBatchMax];
1271
+ iovec iov[kUdpBatchMax];
1272
+ sockaddr_storage addrs[kUdpBatchMax];
1273
+ socklen_t addr_lens[kUdpBatchMax];
1274
+
1275
+ memset(msgs, 0, sizeof(mmsghdr) * count);
1276
+ size_t staged = 0;
1277
+ for (size_t i = 0; i < count; ++i) {
1278
+ if (slots[i].len == 0) continue;
1279
+ // An unspecified destination has nowhere to go; skip it rather than letting
1280
+ // one bad slot fail the whole batch.
1281
+ if (!build_udp_dest_addr(slots[i].endpoint.ip, slots[i].endpoint.port, af,
1282
+ addrs[staged], addr_lens[staged]))
1283
+ continue;
1284
+
1285
+ iov[staged].iov_base = const_cast<uint8_t*>(slots[i].data);
1286
+ iov[staged].iov_len = slots[i].len;
1287
+ msgs[staged].msg_hdr.msg_name = &addrs[staged];
1288
+ msgs[staged].msg_hdr.msg_namelen = addr_lens[staged];
1289
+ msgs[staged].msg_hdr.msg_iov = &iov[staged];
1290
+ msgs[staged].msg_hdr.msg_iovlen = 1;
1291
+ ++staged;
1292
+ }
1293
+ if (staged == 0) return 0;
1294
+
1295
+ const int n = sendmmsg(socket, msgs, static_cast<unsigned int>(staged),
1296
+ MSG_DONTWAIT | MSG_NOSIGNAL);
1297
+ if (n >= 0) return static_cast<size_t>(n);
1298
+
1299
+ const int error = get_last_socket_error();
1300
+ // A full send buffer drops the batch, exactly as a congested link would drop it.
1301
+ if (error == EAGAIN || error == EWOULDBLOCK || error == ENOBUFS) return 0;
1302
+ LOG_SOCKET_DEBUG("Failed to send a datagram batch (error: "
1303
+ << socket_error_string(error) << ")");
1304
+ return 0;
1305
+ #else
1306
+ size_t sent = 0;
1307
+ for (size_t i = 0; i < count; ++i) {
1308
+ if (slots[i].len == 0) continue;
1309
+ if (send_udp_to(socket, slots[i].data, slots[i].len, slots[i].endpoint, af) > 0) ++sent;
1310
+ }
1311
+ return sent;
1312
+ #endif
1313
+ }
1314
+
1315
+ std::vector<uint8_t> receive_udp_data(socket_t socket, size_t buffer_size, Address& sender_peer,
1316
+ int timeout_ms, socket_t interrupt_fd, bool* error_out) {
1317
+ if (error_out) *error_out = false;
1318
+
848
1319
  // Handle timeout (and optional interrupt socket) using select. When no interrupt
849
1320
  // fd is supplied this path is identical to the plain timeout behavior.
850
1321
  const bool have_interrupt = is_valid_socket(interrupt_fd);
@@ -868,19 +1339,29 @@ std::vector<uint8_t> receive_udp_data(socket_t socket, size_t buffer_size, Peer&
868
1339
 
869
1340
  int result = select(static_cast<int>(maxfd) + 1, &read_fds, nullptr, nullptr, ptimeout);
870
1341
  if (result == 0) {
871
- LOG_SOCKET_DEBUG("UDP receive timeout (" << timeout_ms << "ms)");
1342
+ // Timeout with no data normal control flow for a polling/idle loop, so it's
1343
+ // deliberately not logged (it would spam every idle cycle, e.g. the DHT runner).
872
1344
  return {};
873
1345
  } else if (result < 0) {
874
- LOG_SOCKET_ERROR("Select error while waiting for UDP data");
875
- return {};
876
- }
877
- // Woken by the interrupt socket (e.g. stop requested): leave the data socket
878
- // untouched and report no data so the caller can re-check its stop flag.
879
- if (have_interrupt && FD_ISSET(interrupt_fd, &read_fds)) {
1346
+ const int error = get_last_socket_error();
1347
+ #ifndef _WIN32
1348
+ // A signal cut the wait short — nothing is wrong with the socket. select()
1349
+ // is never restarted by SA_RESTART, so a host process with a periodic timer
1350
+ // signal (a sampling profiler, say) can hit this on every poll; reporting it
1351
+ // as an error would let a caller that counts consecutive failures give up on
1352
+ // a perfectly healthy socket. Report "no data" and let it poll again.
1353
+ if (error == EINTR) { return {}; }
1354
+ #endif
1355
+ LOG_SOCKET_ERROR("Select error while waiting for UDP data: " << socket_error_string(error));
1356
+ if (error_out) *error_out = true;
880
1357
  return {};
881
1358
  }
882
- // Guard against calling recvfrom on a data socket that isn't actually ready.
1359
+ // Prefer real data: if a datagram arrived alongside a wakeup, read it now rather
1360
+ // than deferring it a loop iteration. The wakeup byte stays buffered and is drained
1361
+ // by the caller right after, so no wakeup is lost by checking data first.
883
1362
  if (!FD_ISSET(socket, &read_fds)) {
1363
+ // No data — must have been the interrupt socket (e.g. stop/posted work).
1364
+ // Report no data so the caller can re-check its stop flag / run posted tasks.
884
1365
  return {};
885
1366
  }
886
1367
  }
@@ -892,14 +1373,30 @@ std::vector<uint8_t> receive_udp_data(socket_t socket, size_t buffer_size, Peer&
892
1373
  int bytes_received = recvfrom(socket, (char*)buffer.data(), buffer_size, 0,
893
1374
  reinterpret_cast<sockaddr*>(&sender_addr), &sender_addr_len);
894
1375
 
895
- if (bytes_received == SOCKET_ERROR_VALUE) {
1376
+ if (bytes_received == RATS_SOCKET_ERROR) {
896
1377
  int error = get_last_socket_error();
897
1378
  #ifdef _WIN32
898
1379
  if (error == WSAEWOULDBLOCK) { return {}; }
1380
+ // Windows reports an ICMP port-unreachable for a *previous* datagram as an error
1381
+ // on the next recvfrom(). It concerns one destination, not the socket, so it is
1382
+ // not a reason to declare the receive path broken. Same classification as
1383
+ // recv_udp_from().
1384
+ if (error == WSAECONNRESET || error == WSAENETRESET) {
1385
+ LOG_SOCKET_DEBUG("Transient UDP receive error: " << socket_error_string(error));
1386
+ return {};
1387
+ }
899
1388
  #else
900
1389
  if (error == EAGAIN || error == EWOULDBLOCK) { return {}; }
1390
+ // EINTR is a signal, ECONNREFUSED an asynchronous ICMP unreachable for one
1391
+ // destination — neither says anything about the socket itself. Same
1392
+ // classification as recv_udp_from().
1393
+ if (error == EINTR || error == ECONNREFUSED) {
1394
+ LOG_SOCKET_DEBUG("Transient UDP receive error: " << socket_error_string(error));
1395
+ return {};
1396
+ }
901
1397
  #endif
902
1398
  LOG_SOCKET_DEBUG("Failed to receive UDP data: " << socket_error_string(error));
1399
+ if (error_out) *error_out = true;
903
1400
  return {};
904
1401
  }
905
1402
 
@@ -910,7 +1407,7 @@ std::vector<uint8_t> receive_udp_data(socket_t socket, size_t buffer_size, Peer&
910
1407
 
911
1408
  extract_sender_peer(sender_addr, sender_peer);
912
1409
 
913
- LOG_SOCKET_DEBUG("Received " << bytes_received << " bytes from " << sender_peer.ip << ":" << sender_peer.port);
1410
+ LOG_SOCKET_DEBUG("Received " << bytes_received << " bytes from " << sender_peer.to_string());
914
1411
 
915
1412
  buffer.resize(bytes_received);
916
1413
  return buffer;
@@ -937,30 +1434,37 @@ void close_socket(socket_t socket, bool force) {
937
1434
  #endif
938
1435
  }
939
1436
 
940
- closesocket(socket);
1437
+ #ifdef _WIN32
1438
+ ::closesocket(socket);
1439
+ #else
1440
+ ::close(socket);
1441
+ #endif
941
1442
  }
942
1443
  }
943
1444
 
944
1445
  bool is_valid_socket(socket_t socket) {
945
- return socket != INVALID_SOCKET_VALUE;
1446
+ return socket != RATS_INVALID_SOCKET;
946
1447
  }
947
1448
 
948
1449
  bool set_socket_nonblocking(socket_t socket) {
949
1450
  #ifdef _WIN32
950
1451
  unsigned long mode = 1;
951
1452
  if (ioctlsocket(socket, FIONBIO, &mode) != 0) {
952
- LOG_SOCKET_ERROR("Failed to set socket to non-blocking mode");
1453
+ LOG_SOCKET_ERROR("Failed to set socket to non-blocking mode (error: "
1454
+ << socket_error_string(get_last_socket_error()) << ")");
953
1455
  return false;
954
1456
  }
955
1457
  #else
956
1458
  int flags = fcntl(socket, F_GETFL, 0);
957
1459
  if (flags == -1) {
958
- LOG_SOCKET_ERROR("Failed to get socket flags");
1460
+ LOG_SOCKET_ERROR("Failed to get socket flags (error: "
1461
+ << socket_error_string(get_last_socket_error()) << ")");
959
1462
  return false;
960
1463
  }
961
1464
 
962
1465
  if (fcntl(socket, F_SETFL, flags | O_NONBLOCK) == -1) {
963
- LOG_SOCKET_ERROR("Failed to set socket to non-blocking mode");
1466
+ LOG_SOCKET_ERROR("Failed to set socket to non-blocking mode (error: "
1467
+ << socket_error_string(get_last_socket_error()) << ")");
964
1468
  return false;
965
1469
  }
966
1470
  #endif
@@ -973,18 +1477,21 @@ bool set_socket_blocking(socket_t socket) {
973
1477
  #ifdef _WIN32
974
1478
  unsigned long mode = 0;
975
1479
  if (ioctlsocket(socket, FIONBIO, &mode) != 0) {
976
- LOG_SOCKET_ERROR("Failed to set socket to blocking mode");
1480
+ LOG_SOCKET_ERROR("Failed to set socket to blocking mode (error: "
1481
+ << socket_error_string(get_last_socket_error()) << ")");
977
1482
  return false;
978
1483
  }
979
1484
  #else
980
1485
  int flags = fcntl(socket, F_GETFL, 0);
981
1486
  if (flags == -1) {
982
- LOG_SOCKET_ERROR("Failed to get socket flags");
1487
+ LOG_SOCKET_ERROR("Failed to get socket flags (error: "
1488
+ << socket_error_string(get_last_socket_error()) << ")");
983
1489
  return false;
984
1490
  }
985
1491
 
986
1492
  if (fcntl(socket, F_SETFL, flags & ~O_NONBLOCK) == -1) {
987
- LOG_SOCKET_ERROR("Failed to set socket to blocking mode");
1493
+ LOG_SOCKET_ERROR("Failed to set socket to blocking mode (error: "
1494
+ << socket_error_string(get_last_socket_error()) << ")");
988
1495
  return false;
989
1496
  }
990
1497
  #endif