librats 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,18 +19,170 @@
19
19
 
20
20
  namespace librats {
21
21
 
22
- // Static flag to track socket library initialization
22
+ // ── Internal helpers ────────────────────────────────────────────────────────
23
+
24
+ static bool validate_port(int port) {
25
+ if (port < 0 || port > 65535) {
26
+ LOG_SOCKET_ERROR("Invalid port number: " << port << " (must be 0-65535)");
27
+ return false;
28
+ }
29
+ return true;
30
+ }
31
+
32
+ static int get_last_socket_error() {
33
+ #ifdef _WIN32
34
+ return WSAGetLastError();
35
+ #else
36
+ return errno;
37
+ #endif
38
+ }
39
+
40
+ static std::string socket_error_string(int error) {
41
+ #ifdef _WIN32
42
+ return std::to_string(error);
43
+ #else
44
+ return strerror(error);
45
+ #endif
46
+ }
47
+
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);
72
+ } else {
73
+ peer.ip = "unknown";
74
+ peer.port = 0;
75
+ }
76
+ }
77
+
78
+ // ── Static TCP client helpers (IPv4 / IPv6) ─────────────────────────────────
79
+
80
+ static socket_t create_tcp_client_v4(const std::string& host, int port, int timeout_ms) {
81
+ LOG_SOCKET_DEBUG("Creating TCP client socket (IPv4) for " << host << ":" << port);
82
+
83
+ socket_t client_socket = socket(AF_INET, SOCK_STREAM, 0);
84
+ if (client_socket == INVALID_SOCKET_VALUE) {
85
+ LOG_SOCKET_ERROR("Failed to create IPv4 client socket");
86
+ return INVALID_SOCKET_VALUE;
87
+ }
88
+
89
+ sockaddr_in server_addr;
90
+ memset(&server_addr, 0, sizeof(server_addr));
91
+ server_addr.sin_family = AF_INET;
92
+ server_addr.sin_port = htons(port);
93
+
94
+ std::string resolved_ip = network_utils::resolve_hostname(host);
95
+ if (resolved_ip.empty()) {
96
+ LOG_SOCKET_ERROR("Failed to resolve hostname: " << host);
97
+ close_socket(client_socket);
98
+ return INVALID_SOCKET_VALUE;
99
+ }
100
+
101
+ if (inet_pton(AF_INET, resolved_ip.c_str(), &server_addr.sin_addr) <= 0) {
102
+ LOG_SOCKET_ERROR("Invalid address: " << resolved_ip);
103
+ close_socket(client_socket);
104
+ return INVALID_SOCKET_VALUE;
105
+ }
106
+
107
+ LOG_SOCKET_DEBUG("Connecting to " << resolved_ip << ":" << port);
108
+ bool ok;
109
+ if (timeout_ms > 0) {
110
+ ok = connect_with_timeout(client_socket, reinterpret_cast<sockaddr*>(&server_addr),
111
+ sizeof(server_addr), timeout_ms);
112
+ } else {
113
+ ok = (connect(client_socket, reinterpret_cast<sockaddr*>(&server_addr),
114
+ sizeof(server_addr)) != SOCKET_ERROR_VALUE);
115
+ }
116
+
117
+ if (!ok) {
118
+ LOG_SOCKET_DEBUG("Connection to " << resolved_ip << ":" << port << " failed");
119
+ close_socket(client_socket);
120
+ return INVALID_SOCKET_VALUE;
121
+ }
122
+
123
+ LOG_SOCKET_INFO("Successfully connected to " << resolved_ip << ":" << port);
124
+ return client_socket;
125
+ }
126
+
127
+ static socket_t create_tcp_client_v6(const std::string& host, int port, int timeout_ms) {
128
+ LOG_SOCKET_DEBUG("Creating TCP client socket (IPv6) for " << host << ":" << port);
129
+
130
+ socket_t client_socket = socket(AF_INET6, SOCK_STREAM, 0);
131
+ if (client_socket == INVALID_SOCKET_VALUE) {
132
+ LOG_SOCKET_ERROR("Failed to create IPv6 client socket");
133
+ return INVALID_SOCKET_VALUE;
134
+ }
135
+
136
+ sockaddr_in6 server_addr;
137
+ memset(&server_addr, 0, sizeof(server_addr));
138
+ server_addr.sin6_family = AF_INET6;
139
+ server_addr.sin6_port = htons(port);
140
+
141
+ std::string resolved_ip = network_utils::resolve_hostname_v6(host);
142
+ if (resolved_ip.empty()) {
143
+ LOG_SOCKET_DEBUG("Failed to resolve hostname to IPv6: " << host);
144
+ close_socket(client_socket);
145
+ return INVALID_SOCKET_VALUE;
146
+ }
147
+
148
+ if (inet_pton(AF_INET6, resolved_ip.c_str(), &server_addr.sin6_addr) <= 0) {
149
+ LOG_SOCKET_ERROR("Invalid IPv6 address: " << resolved_ip);
150
+ close_socket(client_socket);
151
+ return INVALID_SOCKET_VALUE;
152
+ }
153
+
154
+ LOG_SOCKET_DEBUG("Connecting to IPv6 " << resolved_ip << ":" << port);
155
+ bool ok;
156
+ if (timeout_ms > 0) {
157
+ ok = connect_with_timeout(client_socket, reinterpret_cast<sockaddr*>(&server_addr),
158
+ sizeof(server_addr), timeout_ms);
159
+ } else {
160
+ ok = (connect(client_socket, reinterpret_cast<sockaddr*>(&server_addr),
161
+ sizeof(server_addr)) != SOCKET_ERROR_VALUE);
162
+ }
163
+
164
+ if (!ok) {
165
+ LOG_SOCKET_DEBUG("Connection to IPv6 " << resolved_ip << ":" << port << " failed");
166
+ close_socket(client_socket);
167
+ return INVALID_SOCKET_VALUE;
168
+ }
169
+
170
+ LOG_SOCKET_INFO("Successfully connected to IPv6 " << resolved_ip << ":" << port);
171
+ return client_socket;
172
+ }
173
+
174
+ // ── Socket Library Initialization ───────────────────────────────────────────
175
+
23
176
  static bool socket_library_initialized = false;
24
177
  static std::mutex socket_init_mutex;
25
178
 
26
- // Socket Library Initialization
27
179
  bool init_socket_library() {
28
180
  std::lock_guard<std::mutex> lock(socket_init_mutex);
29
-
181
+
30
182
  if (socket_library_initialized) {
31
- return true; // Already initialized
183
+ return true;
32
184
  }
33
-
185
+
34
186
  #ifdef _WIN32
35
187
  WSADATA wsaData;
36
188
  int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
@@ -40,7 +192,7 @@ bool init_socket_library() {
40
192
  }
41
193
  LOG_SOCKET_INFO("Windows Socket API initialized");
42
194
  #endif
43
-
195
+
44
196
  socket_library_initialized = true;
45
197
  LOG_SOCKET_INFO("Socket library initialized");
46
198
  return true;
@@ -48,38 +200,34 @@ bool init_socket_library() {
48
200
 
49
201
  void cleanup_socket_library() {
50
202
  std::lock_guard<std::mutex> lock(socket_init_mutex);
51
-
203
+
52
204
  if (!socket_library_initialized) {
53
- return; // Not initialized or already cleaned up
205
+ return;
54
206
  }
55
-
207
+
56
208
  #ifdef _WIN32
57
209
  WSACleanup();
58
210
  LOG_SOCKET_INFO("Windows Socket API cleaned up");
59
211
  #endif
60
-
212
+
61
213
  socket_library_initialized = false;
62
214
  LOG_SOCKET_INFO("Socket library cleaned up");
63
215
  }
64
216
 
65
- // Helper function for connection with timeout
217
+ // ── connect_with_timeout ────────────────────────────────────────────────────
218
+
66
219
  bool connect_with_timeout(socket_t socket, struct sockaddr* addr, socklen_t addr_len, int timeout_ms) {
67
- // Set socket to non-blocking mode
68
220
  if (!set_socket_nonblocking(socket)) {
69
221
  LOG_SOCKET_ERROR("Failed to set socket to non-blocking mode for timeout connection");
70
222
  return false;
71
223
  }
72
-
73
- // Attempt to connect
224
+
74
225
  int result = connect(socket, addr, addr_len);
75
-
76
226
  if (result == 0) {
77
- // Connection succeeded immediately - this is rare but possible
78
227
  LOG_SOCKET_DEBUG("Connection succeeded immediately");
79
228
  return true;
80
229
  }
81
-
82
- // Check for expected non-blocking connect error
230
+
83
231
  #ifdef _WIN32
84
232
  int error = WSAGetLastError();
85
233
  if (error != WSAEWOULDBLOCK) {
@@ -93,90 +241,72 @@ bool connect_with_timeout(socket_t socket, struct sockaddr* addr, socklen_t addr
93
241
  return false;
94
242
  }
95
243
  #endif
96
-
97
- // Use select to wait for connection with timeout
244
+
98
245
  fd_set write_fds, error_fds;
99
246
  FD_ZERO(&write_fds);
100
247
  FD_ZERO(&error_fds);
101
248
  FD_SET(socket, &write_fds);
102
249
  FD_SET(socket, &error_fds);
103
-
250
+
104
251
  struct timeval timeout;
105
252
  timeout.tv_sec = timeout_ms / 1000;
106
253
  timeout.tv_usec = (timeout_ms % 1000) * 1000;
107
-
254
+
108
255
  LOG_SOCKET_DEBUG("Waiting for connection with timeout " << timeout_ms << "ms");
109
256
  int select_result = select(socket + 1, nullptr, &write_fds, &error_fds, &timeout);
110
-
257
+
111
258
  if (select_result == 0) {
112
- // Timeout occurred - expected for unavailable peers
113
259
  LOG_SOCKET_DEBUG("Connection timeout after " << timeout_ms << "ms");
114
260
  return false;
115
261
  } else if (select_result < 0) {
116
- // Select error
117
- #ifdef _WIN32
118
- LOG_SOCKET_ERROR("Select error during connect: " << WSAGetLastError());
119
- #else
120
- LOG_SOCKET_ERROR("Select error during connect: " << strerror(errno));
121
- #endif
262
+ LOG_SOCKET_ERROR("Select error during connect: " << socket_error_string(get_last_socket_error()));
122
263
  return false;
123
264
  }
124
-
125
- // Check if connection completed successfully or failed
265
+
126
266
  if (FD_ISSET(socket, &error_fds)) {
127
- // Connection failed - this is expected for unavailable peers in P2P networks
128
267
  LOG_SOCKET_DEBUG("Connection failed (error detected)");
129
268
  return false;
130
269
  }
131
-
270
+
132
271
  if (FD_ISSET(socket, &write_fds)) {
133
- // Check for actual connection success using getsockopt
134
272
  int sock_error;
135
273
  socklen_t len = sizeof(sock_error);
136
274
  if (getsockopt(socket, SOL_SOCKET, SO_ERROR, (char*)&sock_error, &len) < 0) {
137
275
  LOG_SOCKET_ERROR("Failed to get socket error status");
138
276
  return false;
139
277
  }
140
-
278
+
141
279
  if (sock_error != 0) {
142
- #ifdef _WIN32
143
- LOG_SOCKET_ERROR("Connection failed with error: " << sock_error);
144
- #else
145
- LOG_SOCKET_ERROR("Connection failed with error: " << strerror(sock_error));
146
- #endif
280
+ LOG_SOCKET_ERROR("Connection failed with error: " << socket_error_string(sock_error));
147
281
  return false;
148
282
  }
149
-
150
- // Connection succeeded - restore socket to blocking mode
283
+
151
284
  if (!set_socket_blocking(socket)) {
152
285
  LOG_SOCKET_WARN("Failed to restore socket to blocking mode after connection");
153
- // Continue anyway - some operations may still work
154
286
  }
155
-
287
+
156
288
  LOG_SOCKET_DEBUG("Connection succeeded within timeout");
157
289
  return true;
158
290
  }
159
-
160
- // This shouldn't happen
291
+
161
292
  LOG_SOCKET_ERROR("Unexpected select result state");
162
293
  return false;
163
294
  }
164
295
 
165
- // TCP Socket Functions
296
+ // ── TCP Socket Functions ────────────────────────────────────────────────────
297
+
166
298
  socket_t create_tcp_client(const std::string& host, int port, int timeout_ms) {
167
- if (timeout_ms > 0) {
168
- LOG_SOCKET_DEBUG("Creating TCP client socket (dual stack) for " << host << ":" << port << " with timeout " << timeout_ms << "ms");
169
- } else {
170
- LOG_SOCKET_DEBUG("Creating TCP client socket (dual stack) for " << host << ":" << port);
171
- }
172
-
299
+ if (!validate_port(port)) return INVALID_SOCKET_VALUE;
300
+
301
+ LOG_SOCKET_DEBUG("Creating TCP client socket (dual stack) for " << host << ":" << port);
302
+
173
303
  // Try IPv6 first
174
304
  socket_t client_socket = create_tcp_client_v6(host, port, timeout_ms);
175
305
  if (client_socket != INVALID_SOCKET_VALUE) {
176
306
  LOG_SOCKET_INFO("Successfully connected using IPv6");
177
307
  return client_socket;
178
308
  }
179
-
309
+
180
310
  // Fall back to IPv4
181
311
  LOG_SOCKET_DEBUG("IPv6 connection failed, trying IPv4");
182
312
  client_socket = create_tcp_client_v4(host, port, timeout_ms);
@@ -184,377 +314,152 @@ socket_t create_tcp_client(const std::string& host, int port, int timeout_ms) {
184
314
  LOG_SOCKET_INFO("Successfully connected using IPv4");
185
315
  return client_socket;
186
316
  }
187
-
317
+
188
318
  LOG_SOCKET_DEBUG("Failed to connect using both IPv6 and IPv4");
189
319
  return INVALID_SOCKET_VALUE;
190
320
  }
191
321
 
192
- socket_t create_tcp_client_v4(const std::string& host, int port, int timeout_ms) {
193
- if (timeout_ms > 0) {
194
- LOG_SOCKET_DEBUG("Creating TCP client socket for " << host << ":" << port << " with timeout " << timeout_ms << "ms");
195
- } else {
196
- LOG_SOCKET_DEBUG("Creating TCP client socket for " << host << ":" << port);
197
- }
198
-
199
- // Validate port number
200
- if (port < 0 || port > 65535) {
201
- LOG_SOCKET_ERROR("Invalid port number: " << port << " (must be 0-65535)");
202
- return INVALID_SOCKET_VALUE;
203
- }
204
-
205
- socket_t client_socket = socket(AF_INET, SOCK_STREAM, 0);
206
- if (client_socket == INVALID_SOCKET_VALUE) {
207
- LOG_SOCKET_ERROR("Failed to create client socket");
208
- return INVALID_SOCKET_VALUE;
209
- }
322
+ 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;
210
324
 
211
- sockaddr_in server_addr;
212
- memset(&server_addr, 0, sizeof(server_addr));
213
- server_addr.sin_family = AF_INET;
214
- server_addr.sin_port = htons(port);
215
-
216
- // Resolve hostname to IP address
217
- std::string resolved_ip = network_utils::resolve_hostname(host);
218
- if (resolved_ip.empty()) {
219
- LOG_SOCKET_ERROR("Failed to resolve hostname: " << host);
220
- close_socket(client_socket);
221
- return INVALID_SOCKET_VALUE;
222
- }
223
-
224
- // Convert IP address from string to binary form
225
- if (inet_pton(AF_INET, resolved_ip.c_str(), &server_addr.sin_addr) <= 0) {
226
- LOG_SOCKET_ERROR("Invalid address: " << resolved_ip);
227
- close_socket(client_socket);
228
- return INVALID_SOCKET_VALUE;
229
- }
230
-
231
- // Connect to server
232
- LOG_SOCKET_DEBUG("Connecting to " << resolved_ip << ":" << port);
233
- bool connection_success;
234
-
235
- if (timeout_ms > 0) {
236
- // Use timeout connection
237
- connection_success = connect_with_timeout(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr), timeout_ms);
238
- } else {
239
- // Use blocking connection
240
- connection_success = (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) != SOCKET_ERROR_VALUE);
241
- }
242
-
243
- if (!connection_success) {
244
- if (timeout_ms > 0) {
245
- LOG_SOCKET_DEBUG("Connection to " << resolved_ip << ":" << port << " failed or timed out after " << timeout_ms << "ms");
246
- } else {
247
- LOG_SOCKET_DEBUG("Connection to " << resolved_ip << ":" << port << " failed");
248
- }
249
- close_socket(client_socket);
250
- return INVALID_SOCKET_VALUE;
251
- }
252
-
253
- LOG_SOCKET_INFO("Successfully connected to " << resolved_ip << ":" << port);
254
- return client_socket;
255
- }
256
-
257
- socket_t create_tcp_client_v6(const std::string& host, int port, int timeout_ms) {
258
- if (timeout_ms > 0) {
259
- LOG_SOCKET_DEBUG("Creating TCP client socket for IPv6 " << host << ":" << port << " with timeout " << timeout_ms << "ms");
260
- } else {
261
- LOG_SOCKET_DEBUG("Creating TCP client socket for IPv6 " << host << ":" << port);
262
- }
263
-
264
- // Validate port number
265
- if (port < 0 || port > 65535) {
266
- LOG_SOCKET_ERROR("Invalid port number: " << port << " (must be 0-65535)");
267
- return INVALID_SOCKET_VALUE;
268
- }
269
-
270
- socket_t client_socket = socket(AF_INET6, SOCK_STREAM, 0);
271
- if (client_socket == INVALID_SOCKET_VALUE) {
272
- LOG_SOCKET_ERROR("Failed to create IPv6 client socket");
273
- return INVALID_SOCKET_VALUE;
274
- }
275
-
276
- sockaddr_in6 server_addr;
277
- memset(&server_addr, 0, sizeof(server_addr));
278
- server_addr.sin6_family = AF_INET6;
279
- server_addr.sin6_port = htons(port);
280
-
281
- // Resolve hostname to IPv6 address
282
- std::string resolved_ip = network_utils::resolve_hostname_v6(host);
283
- if (resolved_ip.empty()) {
284
- LOG_SOCKET_DEBUG("Failed to resolve hostname to IPv6: " << host);
285
- close_socket(client_socket);
286
- return INVALID_SOCKET_VALUE;
287
- }
288
-
289
- // Convert IPv6 address from string to binary form
290
- if (inet_pton(AF_INET6, resolved_ip.c_str(), &server_addr.sin6_addr) <= 0) {
291
- LOG_SOCKET_ERROR("Invalid IPv6 address: " << resolved_ip);
292
- close_socket(client_socket);
293
- return INVALID_SOCKET_VALUE;
294
- }
295
-
296
- // Connect to server
297
- LOG_SOCKET_DEBUG("Connecting to IPv6 " << resolved_ip << ":" << port);
298
- bool connection_success;
299
-
300
- if (timeout_ms > 0) {
301
- // Use timeout connection
302
- connection_success = connect_with_timeout(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr), timeout_ms);
303
- } else {
304
- // Use blocking connection
305
- connection_success = (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) != SOCKET_ERROR_VALUE);
306
- }
307
-
308
- if (!connection_success) {
309
- if (timeout_ms > 0) {
310
- LOG_SOCKET_DEBUG("Connection to IPv6 " << resolved_ip << ":" << port << " failed or timed out after " << timeout_ms << "ms");
311
- } else {
312
- LOG_SOCKET_DEBUG("Connection to IPv6 " << resolved_ip << ":" << port << " failed");
313
- }
314
- close_socket(client_socket);
315
- return INVALID_SOCKET_VALUE;
316
- }
325
+ const char* af_label = (af == AddressFamily::IPv4) ? "IPv4" :
326
+ (af == AddressFamily::IPv6) ? "IPv6" : "dual stack";
327
+ LOG_SOCKET_DEBUG("Creating TCP server socket (" << af_label << ") on port " << port <<
328
+ (bind_address.empty() ? "" : " bound to " + bind_address));
317
329
 
318
- LOG_SOCKET_INFO("Successfully connected to IPv6 " << resolved_ip << ":" << port);
319
- return client_socket;
320
- }
330
+ int family = (af == AddressFamily::IPv4) ? AF_INET : AF_INET6;
321
331
 
322
- socket_t create_tcp_server(int port, int backlog, const std::string& bind_address) {
323
- LOG_SOCKET_DEBUG("Creating TCP server socket (dual stack) on port " << port <<
324
- (bind_address.empty() ? "" : " bound to " + bind_address));
325
-
326
- // Validate port number
327
- if (port < 0 || port > 65535) {
328
- LOG_SOCKET_ERROR("Invalid port number: " << port << " (must be 0-65535)");
329
- return INVALID_SOCKET_VALUE;
330
- }
331
-
332
- socket_t server_socket = socket(AF_INET6, SOCK_STREAM, 0);
332
+ socket_t server_socket = socket(family, SOCK_STREAM, 0);
333
333
  if (server_socket == INVALID_SOCKET_VALUE) {
334
- LOG_SOCKET_ERROR("Failed to create dual stack server socket");
334
+ LOG_SOCKET_ERROR("Failed to create " << af_label << " server socket");
335
335
  return INVALID_SOCKET_VALUE;
336
336
  }
337
337
 
338
338
  // Set socket option to reuse address
339
339
  int opt = 1;
340
- if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR,
340
+ if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR,
341
341
  (char*)&opt, sizeof(opt)) == SOCKET_ERROR_VALUE) {
342
- LOG_SOCKET_ERROR("Failed to set dual stack socket options");
342
+ LOG_SOCKET_ERROR("Failed to set " << af_label << " socket options");
343
343
  close_socket(server_socket);
344
344
  return INVALID_SOCKET_VALUE;
345
345
  }
346
346
 
347
- // Disable IPv6-only mode to allow IPv4 connections
348
- int ipv6_only = 0;
349
- if (setsockopt(server_socket, IPPROTO_IPV6, IPV6_V6ONLY,
350
- (char*)&ipv6_only, sizeof(ipv6_only)) == SOCKET_ERROR_VALUE) {
351
- LOG_SOCKET_WARN("Failed to disable IPv6-only mode, will be IPv6 only");
352
- }
353
-
354
- sockaddr_in6 server_addr;
355
- memset(&server_addr, 0, sizeof(server_addr));
356
- server_addr.sin6_family = AF_INET6;
357
- server_addr.sin6_port = htons(port);
358
-
359
- // Set bind address (default to all interfaces if empty)
360
- if (bind_address.empty()) {
361
- server_addr.sin6_addr = in6addr_any;
362
- } else {
363
- if (inet_pton(AF_INET6, bind_address.c_str(), &server_addr.sin6_addr) != 1) {
364
- LOG_SOCKET_ERROR("Invalid IPv6 bind address: " << bind_address);
365
- close_socket(server_socket);
366
- return INVALID_SOCKET_VALUE;
347
+ // For IPv6/DualStack sockets, configure IPV6_V6ONLY
348
+ if (family == AF_INET6) {
349
+ int ipv6_only = (af == AddressFamily::IPv6) ? 1 : 0;
350
+ if (setsockopt(server_socket, IPPROTO_IPV6, IPV6_V6ONLY,
351
+ (char*)&ipv6_only, sizeof(ipv6_only)) == SOCKET_ERROR_VALUE) {
352
+ if (af == AddressFamily::DualStack) {
353
+ LOG_SOCKET_WARN("Failed to disable IPv6-only mode, will be IPv6 only");
354
+ }
367
355
  }
368
356
  }
369
357
 
370
- // Bind socket to address
371
- LOG_SOCKET_DEBUG("Binding dual stack server socket to port " << port);
372
- if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR_VALUE) {
373
- LOG_SOCKET_ERROR("Failed to bind dual stack server socket to port " << port);
374
- close_socket(server_socket);
375
- return INVALID_SOCKET_VALUE;
376
- }
377
-
378
- // Listen for connections
379
- if (listen(server_socket, backlog) == SOCKET_ERROR_VALUE) {
380
- LOG_SOCKET_ERROR("Failed to listen on dual stack server socket");
381
- close_socket(server_socket);
382
- return INVALID_SOCKET_VALUE;
383
- }
384
-
385
- LOG_SOCKET_INFO("Dual stack server listening on port " << port << " (backlog: " << backlog << ")");
386
- return server_socket;
387
- }
358
+ // Bind
359
+ if (family == AF_INET) {
360
+ sockaddr_in server_addr;
361
+ memset(&server_addr, 0, sizeof(server_addr));
362
+ server_addr.sin_family = AF_INET;
363
+ server_addr.sin_port = htons(port);
388
364
 
389
- socket_t create_tcp_server_v4(int port, int backlog, const std::string& bind_address) {
390
- LOG_SOCKET_DEBUG("Creating TCP server socket on port " << port <<
391
- (bind_address.empty() ? "" : " bound to " + bind_address));
392
-
393
- // Validate port number
394
- if (port < 0 || port > 65535) {
395
- LOG_SOCKET_ERROR("Invalid port number: " << port << " (must be 0-65535)");
396
- return INVALID_SOCKET_VALUE;
397
- }
398
-
399
- socket_t server_socket = socket(AF_INET, SOCK_STREAM, 0);
400
- if (server_socket == INVALID_SOCKET_VALUE) {
401
- LOG_SOCKET_ERROR("Failed to create server socket");
402
- return INVALID_SOCKET_VALUE;
403
- }
404
-
405
- // Set socket option to reuse address
406
- int opt = 1;
407
- if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR,
408
- (char*)&opt, sizeof(opt)) == SOCKET_ERROR_VALUE) {
409
- LOG_SOCKET_ERROR("Failed to set socket options");
410
- close_socket(server_socket);
411
- return INVALID_SOCKET_VALUE;
412
- }
365
+ if (bind_address.empty()) {
366
+ server_addr.sin_addr.s_addr = INADDR_ANY;
367
+ } else {
368
+ if (inet_pton(AF_INET, bind_address.c_str(), &server_addr.sin_addr) != 1) {
369
+ LOG_SOCKET_ERROR("Invalid IPv4 bind address: " << bind_address);
370
+ close_socket(server_socket);
371
+ return INVALID_SOCKET_VALUE;
372
+ }
373
+ }
413
374
 
414
- sockaddr_in server_addr;
415
- memset(&server_addr, 0, sizeof(server_addr));
416
- server_addr.sin_family = AF_INET;
417
- server_addr.sin_port = htons(port);
418
-
419
- // Set bind address (default to all interfaces if empty)
420
- if (bind_address.empty()) {
421
- server_addr.sin_addr.s_addr = INADDR_ANY;
422
- } else {
423
- if (inet_pton(AF_INET, bind_address.c_str(), &server_addr.sin_addr) != 1) {
424
- LOG_SOCKET_ERROR("Invalid IPv4 bind address: " << bind_address);
375
+ 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);
425
378
  close_socket(server_socket);
426
379
  return INVALID_SOCKET_VALUE;
427
380
  }
428
- }
429
-
430
- // Bind socket to address
431
- LOG_SOCKET_DEBUG("Binding server socket to port " << port);
432
- if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR_VALUE) {
433
- LOG_SOCKET_ERROR("Failed to bind server socket to port " << port);
434
- close_socket(server_socket);
435
- return INVALID_SOCKET_VALUE;
436
- }
437
-
438
- // Listen for connections
439
- if (listen(server_socket, backlog) == SOCKET_ERROR_VALUE) {
440
- LOG_SOCKET_ERROR("Failed to listen on server socket");
441
- close_socket(server_socket);
442
- return INVALID_SOCKET_VALUE;
443
- }
444
-
445
- LOG_SOCKET_INFO("Server listening on port " << port << " (backlog: " << backlog << ")");
446
- return server_socket;
447
- }
448
-
449
- socket_t create_tcp_server_v6(int port, int backlog, const std::string& bind_address) {
450
- LOG_SOCKET_DEBUG("Creating TCP server socket on IPv6 port " << port <<
451
- (bind_address.empty() ? "" : " bound to " + bind_address));
452
-
453
- // Validate port number
454
- if (port < 0 || port > 65535) {
455
- LOG_SOCKET_ERROR("Invalid port number: " << port << " (must be 0-65535)");
456
- return INVALID_SOCKET_VALUE;
457
- }
458
-
459
- socket_t server_socket = socket(AF_INET6, SOCK_STREAM, 0);
460
- if (server_socket == INVALID_SOCKET_VALUE) {
461
- LOG_SOCKET_ERROR("Failed to create IPv6 server socket");
462
- return INVALID_SOCKET_VALUE;
463
- }
381
+ } else {
382
+ sockaddr_in6 server_addr;
383
+ memset(&server_addr, 0, sizeof(server_addr));
384
+ server_addr.sin6_family = AF_INET6;
385
+ server_addr.sin6_port = htons(port);
464
386
 
465
- // Set socket option to reuse address
466
- int opt = 1;
467
- if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR,
468
- (char*)&opt, sizeof(opt)) == SOCKET_ERROR_VALUE) {
469
- LOG_SOCKET_ERROR("Failed to set IPv6 socket options");
470
- close_socket(server_socket);
471
- return INVALID_SOCKET_VALUE;
472
- }
387
+ if (bind_address.empty()) {
388
+ server_addr.sin6_addr = in6addr_any;
389
+ } else {
390
+ if (inet_pton(AF_INET6, bind_address.c_str(), &server_addr.sin6_addr) != 1) {
391
+ LOG_SOCKET_ERROR("Invalid IPv6 bind address: " << bind_address);
392
+ close_socket(server_socket);
393
+ return INVALID_SOCKET_VALUE;
394
+ }
395
+ }
473
396
 
474
- sockaddr_in6 server_addr;
475
- memset(&server_addr, 0, sizeof(server_addr));
476
- server_addr.sin6_family = AF_INET6;
477
- server_addr.sin6_port = htons(port);
478
-
479
- // Set bind address (default to all interfaces if empty)
480
- if (bind_address.empty()) {
481
- server_addr.sin6_addr = in6addr_any;
482
- } else {
483
- if (inet_pton(AF_INET6, bind_address.c_str(), &server_addr.sin6_addr) != 1) {
484
- LOG_SOCKET_ERROR("Invalid IPv6 bind address: " << bind_address);
397
+ 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);
485
400
  close_socket(server_socket);
486
401
  return INVALID_SOCKET_VALUE;
487
402
  }
488
403
  }
489
404
 
490
- // Bind socket to address
491
- LOG_SOCKET_DEBUG("Binding IPv6 server socket to port " << port);
492
- if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR_VALUE) {
493
- LOG_SOCKET_ERROR("Failed to bind IPv6 server socket to port " << port);
494
- close_socket(server_socket);
495
- return INVALID_SOCKET_VALUE;
496
- }
497
-
498
- // Listen for connections
499
405
  if (listen(server_socket, backlog) == SOCKET_ERROR_VALUE) {
500
- LOG_SOCKET_ERROR("Failed to listen on IPv6 server socket");
406
+ LOG_SOCKET_ERROR("Failed to listen on " << af_label << " server socket");
501
407
  close_socket(server_socket);
502
408
  return INVALID_SOCKET_VALUE;
503
409
  }
504
410
 
505
- LOG_SOCKET_INFO("IPv6 server listening on port " << port << " (backlog: " << backlog << ")");
411
+ LOG_SOCKET_INFO(af_label << " server listening on port " << port << " (backlog: " << backlog << ")");
506
412
  return server_socket;
507
413
  }
508
414
 
509
415
  socket_t accept_client(socket_t server_socket) {
510
416
  sockaddr_storage client_addr;
511
417
  socklen_t client_addr_len = sizeof(client_addr);
512
-
513
- socket_t client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
418
+
419
+ socket_t client_socket = accept(server_socket, reinterpret_cast<sockaddr*>(&client_addr), &client_addr_len);
514
420
  if (client_socket == INVALID_SOCKET_VALUE) {
515
421
  LOG_SOCKET_ERROR("Failed to accept client connection");
516
422
  return INVALID_SOCKET_VALUE;
517
423
  }
518
424
 
519
- // Log client information based on address family
520
425
  if (client_addr.ss_family == AF_INET) {
521
426
  char client_ip[INET_ADDRSTRLEN];
522
- struct sockaddr_in* addr_in = (struct sockaddr_in*)&client_addr;
427
+ auto* addr_in = reinterpret_cast<sockaddr_in*>(&client_addr);
523
428
  inet_ntop(AF_INET, &addr_in->sin_addr, client_ip, INET_ADDRSTRLEN);
524
429
  LOG_SOCKET_INFO("Client connected from " << client_ip << ":" << ntohs(addr_in->sin_port));
525
430
  } else if (client_addr.ss_family == AF_INET6) {
526
431
  char client_ip[INET6_ADDRSTRLEN];
527
- struct sockaddr_in6* addr_in6 = (struct sockaddr_in6*)&client_addr;
432
+ auto* addr_in6 = reinterpret_cast<sockaddr_in6*>(&client_addr);
528
433
  inet_ntop(AF_INET6, &addr_in6->sin6_addr, client_ip, INET6_ADDRSTRLEN);
529
434
  LOG_SOCKET_INFO("Client connected from IPv6 [" << client_ip << "]:" << ntohs(addr_in6->sin6_port));
530
435
  } else {
531
436
  LOG_SOCKET_INFO("Client connected from unknown address family");
532
437
  }
533
-
438
+
534
439
  return client_socket;
535
440
  }
536
441
 
537
442
  std::string get_peer_address(socket_t socket) {
538
443
  sockaddr_storage peer_addr;
539
444
  socklen_t peer_addr_len = sizeof(peer_addr);
540
-
541
- if (getpeername(socket, (struct sockaddr*)&peer_addr, &peer_addr_len) == SOCKET_ERROR_VALUE) {
445
+
446
+ if (getpeername(socket, reinterpret_cast<sockaddr*>(&peer_addr), &peer_addr_len) == SOCKET_ERROR_VALUE) {
542
447
  LOG_SOCKET_ERROR("Failed to get peer address for socket " << socket);
543
448
  return "";
544
449
  }
545
-
450
+
546
451
  std::string peer_ip;
547
452
  uint16_t peer_port = 0;
548
-
453
+
549
454
  if (peer_addr.ss_family == AF_INET) {
550
455
  char ip_str[INET_ADDRSTRLEN];
551
- struct sockaddr_in* addr_in = (struct sockaddr_in*)&peer_addr;
456
+ auto* addr_in = reinterpret_cast<sockaddr_in*>(&peer_addr);
552
457
  inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, INET_ADDRSTRLEN);
553
458
  peer_ip = ip_str;
554
459
  peer_port = ntohs(addr_in->sin_port);
555
460
  } else if (peer_addr.ss_family == AF_INET6) {
556
461
  char ip_str[INET6_ADDRSTRLEN];
557
- struct sockaddr_in6* addr_in6 = (struct sockaddr_in6*)&peer_addr;
462
+ auto* addr_in6 = reinterpret_cast<sockaddr_in6*>(&peer_addr);
558
463
  inet_ntop(AF_INET6, &addr_in6->sin6_addr, ip_str, INET6_ADDRSTRLEN);
559
464
  peer_ip = ip_str;
560
465
  peer_port = ntohs(addr_in6->sin6_port);
@@ -562,745 +467,436 @@ std::string get_peer_address(socket_t socket) {
562
467
  LOG_SOCKET_ERROR("Unknown address family for socket " << socket);
563
468
  return "";
564
469
  }
565
-
470
+
566
471
  return peer_ip + ":" + std::to_string(peer_port);
567
472
  }
568
473
 
569
474
  int send_tcp_data(socket_t socket, const std::vector<uint8_t>& data) {
570
475
  LOG_SOCKET_DEBUG("Sending " << data.size() << " bytes to TCP socket " << socket);
571
-
476
+
572
477
  size_t total_sent = 0;
573
478
  const char* buffer = reinterpret_cast<const char*>(data.data());
574
479
  size_t remaining = data.size();
575
-
480
+
576
481
  while (remaining > 0) {
577
482
  #ifdef _WIN32
578
483
  int bytes_sent = send(socket, buffer + total_sent, remaining, 0);
579
484
  #else
580
- // Use MSG_NOSIGNAL to prevent SIGPIPE on broken connections
581
485
  int bytes_sent = send(socket, buffer + total_sent, remaining, MSG_NOSIGNAL);
582
486
  #endif
583
487
  if (bytes_sent == SOCKET_ERROR_VALUE) {
488
+ int error = get_last_socket_error();
584
489
  #ifdef _WIN32
585
- int error = WSAGetLastError();
586
- if (error == WSAEWOULDBLOCK) {
587
- // Non-blocking socket would block, try again
588
- continue;
589
- }
490
+ if (error == WSAEWOULDBLOCK) { continue; }
590
491
  #else
591
- int error = errno;
592
- if (error == EAGAIN || error == EWOULDBLOCK) {
593
- // Non-blocking socket would block, try again
594
- continue;
595
- }
492
+ if (error == EAGAIN || error == EWOULDBLOCK) { continue; }
596
493
  if (error == EPIPE || error == ECONNRESET || error == ENOTCONN) {
597
- // Connection closed by peer - this is expected during shutdown
598
- LOG_SOCKET_DEBUG("Connection closed during send to socket " << socket << " (error: " << strerror(error) << ")");
494
+ LOG_SOCKET_DEBUG("Connection closed during send to socket " << socket
495
+ << " (error: " << strerror(error) << ")");
599
496
  return -1;
600
497
  }
601
498
  #endif
602
- LOG_SOCKET_ERROR("Failed to send TCP data to socket " << socket << " (error: " << error << ")");
499
+ LOG_SOCKET_ERROR("Failed to send TCP data to socket " << socket
500
+ << " (error: " << socket_error_string(error) << ")");
603
501
  return -1;
604
502
  }
605
-
503
+
606
504
  if (bytes_sent == 0) {
607
505
  LOG_SOCKET_ERROR("Connection closed by peer during send on socket " << socket);
608
506
  return -1;
609
507
  }
610
-
508
+
611
509
  total_sent += bytes_sent;
612
510
  remaining -= bytes_sent;
613
511
  LOG_SOCKET_DEBUG("Sent " << bytes_sent << " bytes, " << remaining << " remaining");
614
512
  }
615
-
513
+
616
514
  LOG_SOCKET_DEBUG("Successfully sent all " << total_sent << " bytes to TCP socket " << socket);
617
515
  return static_cast<int>(total_sent);
618
516
  }
619
517
 
620
518
  std::vector<uint8_t> receive_tcp_data(socket_t socket, size_t buffer_size) {
621
519
  if (buffer_size == 0) {
622
- buffer_size = 1024; // Default buffer size
520
+ buffer_size = 1024;
623
521
  }
624
-
522
+
625
523
  std::vector<uint8_t> buffer(buffer_size);
626
-
524
+
627
525
  int bytes_received = recv(socket, reinterpret_cast<char*>(buffer.data()), buffer_size, 0);
628
526
  if (bytes_received == SOCKET_ERROR_VALUE) {
527
+ int error = get_last_socket_error();
629
528
  #ifdef _WIN32
630
- int error = WSAGetLastError();
631
- if (error == WSAEWOULDBLOCK) {
632
- // No data available on non-blocking socket - this is normal
633
- return std::vector<uint8_t>();
634
- }
635
- LOG_SOCKET_ERROR("Failed to receive TCP data from socket " << socket << " (error: " << error << ")");
529
+ if (error == WSAEWOULDBLOCK) { return {}; }
636
530
  #else
637
- int error = errno;
638
- if (error == EAGAIN || error == EWOULDBLOCK) {
639
- // No data available on non-blocking socket - this is normal
640
- return std::vector<uint8_t>();
641
- }
642
- LOG_SOCKET_ERROR("Failed to receive TCP data from socket " << socket << " (error: " << strerror(error) << ")");
531
+ if (error == EAGAIN || error == EWOULDBLOCK) { return {}; }
643
532
  #endif
644
- return std::vector<uint8_t>();
533
+ LOG_SOCKET_ERROR("Failed to receive TCP data from socket " << socket
534
+ << " (error: " << socket_error_string(error) << ")");
535
+ return {};
645
536
  }
646
-
537
+
647
538
  if (bytes_received == 0) {
648
539
  LOG_SOCKET_INFO("Connection closed by peer on socket " << socket);
649
- return std::vector<uint8_t>();
540
+ return {};
650
541
  }
651
-
542
+
652
543
  LOG_SOCKET_DEBUG("Received " << bytes_received << " bytes from TCP socket " << socket);
653
-
654
- // Resize to actual received size
655
544
  buffer.resize(bytes_received);
656
545
  return buffer;
657
546
  }
658
547
 
659
- // Large message handling with length-prefixed framing
660
- int send_tcp_message_framed(socket_t socket, const std::vector<uint8_t>& message) {
548
+ // ── Framed message protocol ────────────────────────────────────────────────
549
+
550
+ int send_tcp_message(socket_t socket, const std::vector<uint8_t>& message) {
661
551
  // Create length prefix (4 bytes, network byte order)
662
552
  uint32_t message_length = static_cast<uint32_t>(message.size());
663
553
  uint32_t length_prefix = htonl(message_length);
664
-
665
- // Send length prefix first
666
- std::vector<uint8_t> prefix_data(reinterpret_cast<const uint8_t*>(&length_prefix),
554
+
555
+ std::vector<uint8_t> prefix_data(reinterpret_cast<const uint8_t*>(&length_prefix),
667
556
  reinterpret_cast<const uint8_t*>(&length_prefix) + 4);
668
557
  int prefix_sent = send_tcp_data(socket, prefix_data);
669
558
  if (prefix_sent != 4) {
670
559
  LOG_SOCKET_ERROR("Failed to send message length prefix to socket " << socket);
671
560
  return -1;
672
561
  }
673
-
674
- // Send the actual message
562
+
675
563
  int message_sent = send_tcp_data(socket, message);
676
564
  if (message_sent != static_cast<int>(message.size())) {
677
565
  LOG_SOCKET_ERROR("Failed to send complete message to socket " << socket);
678
566
  return -1;
679
567
  }
680
-
568
+
681
569
  LOG_SOCKET_DEBUG("Successfully sent framed message (" << message.size() << " bytes) to socket " << socket);
682
570
  return prefix_sent + message_sent;
683
571
  }
684
572
 
685
- std::vector<uint8_t> receive_exact_bytes(socket_t socket, size_t num_bytes) {
573
+ static std::vector<uint8_t> receive_exact_bytes(socket_t socket, size_t num_bytes) {
686
574
  std::vector<uint8_t> result;
687
575
  result.reserve(num_bytes);
688
-
576
+
689
577
  size_t total_received = 0;
690
578
  while (total_received < num_bytes) {
691
579
  std::vector<uint8_t> buffer(num_bytes - total_received);
692
580
  int bytes_received = recv(socket, reinterpret_cast<char*>(buffer.data()), buffer.size(), 0);
693
-
581
+
694
582
  if (bytes_received == SOCKET_ERROR_VALUE) {
583
+ int error = get_last_socket_error();
695
584
  #ifdef _WIN32
696
- int error = WSAGetLastError();
697
585
  if (error == WSAEWOULDBLOCK) {
698
- // No data available on non-blocking socket - try again
699
586
  std::this_thread::sleep_for(std::chrono::milliseconds(1));
700
587
  continue;
701
588
  }
702
- LOG_SOCKET_ERROR("Failed to receive exact bytes from socket " << socket << " (error: " << error << ")");
703
589
  #else
704
- int error = errno;
705
590
  if (error == EAGAIN || error == EWOULDBLOCK) {
706
- // No data available on non-blocking socket - try again
707
591
  std::this_thread::sleep_for(std::chrono::milliseconds(1));
708
592
  continue;
709
593
  }
710
- LOG_SOCKET_ERROR("Failed to receive exact bytes from socket " << socket << " (error: " << strerror(error) << ")");
711
594
  #endif
712
- return std::vector<uint8_t>();
595
+ LOG_SOCKET_ERROR("Failed to receive exact bytes from socket " << socket
596
+ << " (error: " << socket_error_string(error) << ")");
597
+ return {};
713
598
  }
714
-
599
+
715
600
  if (bytes_received == 0) {
716
601
  LOG_SOCKET_INFO("Connection closed by peer while receiving exact bytes on socket " << socket);
717
- return std::vector<uint8_t>();
602
+ return {};
718
603
  }
719
-
604
+
720
605
  result.insert(result.end(), buffer.begin(), buffer.begin() + bytes_received);
721
606
  total_received += bytes_received;
722
607
  }
723
-
608
+
724
609
  LOG_SOCKET_DEBUG("Successfully received " << total_received << " exact bytes from socket " << socket);
725
610
  return result;
726
611
  }
727
612
 
728
- std::vector<uint8_t> receive_tcp_message_framed(socket_t socket) {
729
- // First, receive the 4-byte length prefix
613
+ std::vector<uint8_t> receive_tcp_message(socket_t socket) {
614
+ // Receive the 4-byte length prefix
730
615
  std::vector<uint8_t> length_data = receive_exact_bytes(socket, 4);
731
616
  if (length_data.size() != 4) {
732
617
  if (!length_data.empty()) {
733
- LOG_SOCKET_ERROR("Failed to receive complete length prefix from socket " << socket << " (got " << length_data.size() << " bytes)");
618
+ LOG_SOCKET_ERROR("Failed to receive complete length prefix from socket " << socket
619
+ << " (got " << length_data.size() << " bytes)");
734
620
  }
735
- return std::vector<uint8_t>();
621
+ return {};
736
622
  }
737
-
738
- // Extract message length (convert from network byte order)
623
+
739
624
  uint32_t length_prefix;
740
625
  memcpy(&length_prefix, length_data.data(), 4);
741
626
  uint32_t message_length = ntohl(length_prefix);
742
-
743
- // Validate message length (prevent excessive memory allocation)
627
+
744
628
  if (message_length == 0) {
745
629
  LOG_SOCKET_DEBUG("Received keep-alive message (length 0) from socket " << socket);
746
- return std::vector<uint8_t>();
630
+ return {};
747
631
  }
748
-
632
+
749
633
  if (message_length > 100 * 1024 * 1024) { // 100MB limit
750
634
  LOG_SOCKET_ERROR("Message length too large: " << message_length << " bytes from socket " << socket);
751
- return std::vector<uint8_t>();
635
+ return {};
752
636
  }
753
-
754
- // Receive the actual message
637
+
755
638
  std::vector<uint8_t> message = receive_exact_bytes(socket, message_length);
756
639
  if (message.size() != message_length) {
757
- LOG_SOCKET_ERROR("Failed to receive complete message from socket " << socket << " (expected " << message_length << " bytes, got " << message.size() << ")");
758
- return std::vector<uint8_t>();
640
+ LOG_SOCKET_ERROR("Failed to receive complete message from socket " << socket
641
+ << " (expected " << message_length << " bytes, got " << message.size() << ")");
642
+ return {};
759
643
  }
760
-
644
+
761
645
  LOG_SOCKET_DEBUG("Successfully received framed message (" << message_length << " bytes) from socket " << socket);
762
646
  return message;
763
647
  }
764
648
 
765
- // Convenience functions for string compatibility
649
+ // ── String convenience ──────────────────────────────────────────────────────
650
+
766
651
  int send_tcp_string(socket_t socket, const std::string& data) {
767
652
  std::vector<uint8_t> binary_data(data.begin(), data.end());
768
653
  return send_tcp_data(socket, binary_data);
769
654
  }
770
655
 
771
- std::string receive_tcp_string(socket_t socket, size_t buffer_size) {
772
- std::vector<uint8_t> binary_data = receive_tcp_data(socket, buffer_size);
773
- if (binary_data.empty()) {
774
- return "";
775
- }
776
- return std::string(binary_data.begin(), binary_data.end());
777
- }
778
-
779
- int send_tcp_string_framed(socket_t socket, const std::string& message) {
780
- std::vector<uint8_t> binary_message(message.begin(), message.end());
781
- return send_tcp_message_framed(socket, binary_message);
782
- }
656
+ // ── UDP Socket Functions ────────────────────────────────────────────────────
783
657
 
784
- std::string receive_tcp_string_framed(socket_t socket) {
785
- std::vector<uint8_t> binary_message = receive_tcp_message_framed(socket);
786
- if (binary_message.empty()) {
787
- return "";
788
- }
789
- return std::string(binary_message.begin(), binary_message.end());
790
- }
658
+ socket_t create_udp_socket(int port, const std::string& bind_address, AddressFamily af) {
659
+ if (!validate_port(port)) return INVALID_SOCKET_VALUE;
791
660
 
792
- // UDP Socket Functions
793
- socket_t create_udp_socket(int port, const std::string& bind_address) {
794
- LOG_SOCKET_DEBUG("Creating dual stack UDP socket on port " << port <<
661
+ const char* af_label = (af == AddressFamily::IPv4) ? "IPv4" :
662
+ (af == AddressFamily::IPv6) ? "IPv6" : "dual stack";
663
+ LOG_SOCKET_DEBUG("Creating " << af_label << " UDP socket on port " << port <<
795
664
  (bind_address.empty() ? "" : " bound to " + bind_address));
796
-
797
- // Validate port number
798
- if (port < 0 || port > 65535) {
799
- LOG_SOCKET_ERROR("Invalid port number: " << port << " (must be 0-65535)");
800
- return INVALID_SOCKET_VALUE;
801
- }
802
-
803
- socket_t udp_socket = socket(AF_INET6, SOCK_DGRAM, 0);
665
+
666
+ int family = (af == AddressFamily::IPv4) ? AF_INET : AF_INET6;
667
+
668
+ socket_t udp_socket = socket(family, SOCK_DGRAM, 0);
804
669
  if (udp_socket == INVALID_SOCKET_VALUE) {
805
- #ifdef _WIN32
806
- LOG_SOCKET_ERROR("Failed to create dual stack UDP socket (error: " << WSAGetLastError() << ")");
807
- #else
808
- LOG_SOCKET_ERROR("Failed to create dual stack UDP socket (error: " << strerror(errno) << ")");
809
- #endif
670
+ LOG_SOCKET_ERROR("Failed to create " << af_label << " UDP socket (error: "
671
+ << socket_error_string(get_last_socket_error()) << ")");
810
672
  return INVALID_SOCKET_VALUE;
811
673
  }
812
674
 
813
675
  // Set socket option to reuse address
814
676
  int opt = 1;
815
- if (setsockopt(udp_socket, SOL_SOCKET, SO_REUSEADDR,
677
+ if (setsockopt(udp_socket, SOL_SOCKET, SO_REUSEADDR,
816
678
  (char*)&opt, sizeof(opt)) == SOCKET_ERROR_VALUE) {
817
- LOG_SOCKET_ERROR("Failed to set dual stack UDP socket options");
679
+ LOG_SOCKET_ERROR("Failed to set " << af_label << " UDP socket options");
818
680
  close_socket(udp_socket);
819
681
  return INVALID_SOCKET_VALUE;
820
682
  }
821
683
 
822
- // Disable IPv6-only mode to allow IPv4 connections
823
- int ipv6_only = 0;
824
- if (setsockopt(udp_socket, IPPROTO_IPV6, IPV6_V6ONLY,
825
- (char*)&ipv6_only, sizeof(ipv6_only)) == SOCKET_ERROR_VALUE) {
826
- LOG_SOCKET_WARN("Failed to disable IPv6-only mode, will be IPv6 only");
827
- }
828
-
829
- // Always bind the socket - this is required for receiving data
830
- sockaddr_in6 addr;
831
- memset(&addr, 0, sizeof(addr));
832
- addr.sin6_family = AF_INET6;
833
- addr.sin6_port = htons(port); // port=0 will get an ephemeral port
834
-
835
- // Set bind address (default to all interfaces if empty)
836
- if (bind_address.empty()) {
837
- addr.sin6_addr = in6addr_any;
838
- } else {
839
- if (inet_pton(AF_INET6, bind_address.c_str(), &addr.sin6_addr) != 1) {
840
- LOG_SOCKET_ERROR("Invalid IPv6 bind address: " << bind_address);
841
- close_socket(udp_socket);
842
- return INVALID_SOCKET_VALUE;
684
+ // For IPv6/DualStack sockets, configure IPV6_V6ONLY
685
+ if (family == AF_INET6) {
686
+ int ipv6_only = (af == AddressFamily::IPv6) ? 1 : 0;
687
+ if (setsockopt(udp_socket, IPPROTO_IPV6, IPV6_V6ONLY,
688
+ (char*)&ipv6_only, sizeof(ipv6_only)) == SOCKET_ERROR_VALUE) {
689
+ if (af == AddressFamily::DualStack) {
690
+ LOG_SOCKET_WARN("Failed to disable IPv6-only mode, will be IPv6 only");
691
+ }
843
692
  }
844
693
  }
845
694
 
846
- if (bind(udp_socket, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR_VALUE) {
847
- #ifdef _WIN32
848
- LOG_SOCKET_ERROR("Failed to bind dual stack UDP socket to port " << port << " (error: " << WSAGetLastError() << ")");
849
- #else
850
- LOG_SOCKET_ERROR("Failed to bind dual stack UDP socket to port " << port << " (error: " << strerror(errno) << ")");
851
- #endif
852
- close_socket(udp_socket);
853
- return INVALID_SOCKET_VALUE;
854
- }
855
-
856
- // Get the actual bound port if ephemeral port was requested
857
- if (port == 0) {
858
- sockaddr_in6 bound_addr;
859
- socklen_t addr_len = sizeof(bound_addr);
860
- if (getsockname(udp_socket, (struct sockaddr*)&bound_addr, &addr_len) == 0) {
861
- uint16_t actual_port = ntohs(bound_addr.sin6_port);
862
- LOG_SOCKET_INFO("Dual stack UDP socket bound to ephemeral port " << actual_port);
695
+ // Bind
696
+ if (family == AF_INET) {
697
+ sockaddr_in addr;
698
+ memset(&addr, 0, sizeof(addr));
699
+ addr.sin_family = AF_INET;
700
+ addr.sin_port = htons(port);
701
+
702
+ if (bind_address.empty()) {
703
+ addr.sin_addr.s_addr = INADDR_ANY;
863
704
  } else {
864
- LOG_SOCKET_INFO("Dual stack UDP socket bound to ephemeral port (unknown)");
705
+ if (inet_pton(AF_INET, bind_address.c_str(), &addr.sin_addr) != 1) {
706
+ LOG_SOCKET_ERROR("Invalid IPv4 bind address: " << bind_address);
707
+ close_socket(udp_socket);
708
+ return INVALID_SOCKET_VALUE;
709
+ }
865
710
  }
866
- } else {
867
- LOG_SOCKET_INFO("Dual stack UDP socket bound to port " << port);
868
- }
869
-
870
- return udp_socket;
871
- }
872
-
873
- socket_t create_udp_socket_v4(int port, const std::string& bind_address) {
874
- LOG_SOCKET_DEBUG("Creating UDP socket on port " << port <<
875
- (bind_address.empty() ? "" : " bound to " + bind_address));
876
-
877
- // Validate port number
878
- if (port < 0 || port > 65535) {
879
- LOG_SOCKET_ERROR("Invalid port number: " << port << " (must be 0-65535)");
880
- return INVALID_SOCKET_VALUE;
881
- }
882
-
883
- socket_t udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
884
- if (udp_socket == INVALID_SOCKET_VALUE) {
885
- #ifdef _WIN32
886
- LOG_SOCKET_ERROR("Failed to create UDP socket (error: " << WSAGetLastError() << ")");
887
- #else
888
- LOG_SOCKET_ERROR("Failed to create UDP socket (error: " << strerror(errno) << ")");
889
- #endif
890
- return INVALID_SOCKET_VALUE;
891
- }
892
-
893
- // Set socket option to reuse address
894
- int opt = 1;
895
- if (setsockopt(udp_socket, SOL_SOCKET, SO_REUSEADDR,
896
- (char*)&opt, sizeof(opt)) == SOCKET_ERROR_VALUE) {
897
- LOG_SOCKET_ERROR("Failed to set UDP socket options");
898
- close_socket(udp_socket);
899
- return INVALID_SOCKET_VALUE;
900
- }
901
711
 
902
- // Always bind the socket - this is required for receiving data
903
- sockaddr_in addr;
904
- memset(&addr, 0, sizeof(addr));
905
- addr.sin_family = AF_INET;
906
- addr.sin_port = htons(port); // port=0 will get an ephemeral port
907
-
908
- // Set bind address (default to all interfaces if empty)
909
- if (bind_address.empty()) {
910
- addr.sin_addr.s_addr = INADDR_ANY;
911
- } else {
912
- if (inet_pton(AF_INET, bind_address.c_str(), &addr.sin_addr) != 1) {
913
- LOG_SOCKET_ERROR("Invalid IPv4 bind address: " << bind_address);
712
+ if (bind(udp_socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR_VALUE) {
713
+ LOG_SOCKET_ERROR("Failed to bind " << af_label << " UDP socket to port " << port
714
+ << " (error: " << socket_error_string(get_last_socket_error()) << ")");
914
715
  close_socket(udp_socket);
915
716
  return INVALID_SOCKET_VALUE;
916
717
  }
917
- }
718
+ } else {
719
+ sockaddr_in6 addr;
720
+ memset(&addr, 0, sizeof(addr));
721
+ addr.sin6_family = AF_INET6;
722
+ addr.sin6_port = htons(port);
918
723
 
919
- if (bind(udp_socket, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR_VALUE) {
920
- #ifdef _WIN32
921
- LOG_SOCKET_ERROR("Failed to bind UDP socket to port " << port << " (error: " << WSAGetLastError() << ")");
922
- #else
923
- LOG_SOCKET_ERROR("Failed to bind UDP socket to port " << port << " (error: " << strerror(errno) << ")");
924
- #endif
925
- close_socket(udp_socket);
926
- return INVALID_SOCKET_VALUE;
927
- }
928
-
929
- // Get the actual bound port if ephemeral port was requested
930
- if (port == 0) {
931
- sockaddr_in bound_addr;
932
- socklen_t addr_len = sizeof(bound_addr);
933
- if (getsockname(udp_socket, (struct sockaddr*)&bound_addr, &addr_len) == 0) {
934
- uint16_t actual_port = ntohs(bound_addr.sin_port);
935
- LOG_SOCKET_INFO("UDP socket bound to ephemeral port " << actual_port);
724
+ if (bind_address.empty()) {
725
+ addr.sin6_addr = in6addr_any;
936
726
  } else {
937
- LOG_SOCKET_INFO("UDP socket bound to ephemeral port (unknown)");
727
+ if (inet_pton(AF_INET6, bind_address.c_str(), &addr.sin6_addr) != 1) {
728
+ LOG_SOCKET_ERROR("Invalid IPv6 bind address: " << bind_address);
729
+ close_socket(udp_socket);
730
+ return INVALID_SOCKET_VALUE;
731
+ }
938
732
  }
939
- } else {
940
- LOG_SOCKET_INFO("UDP socket bound to port " << port);
941
- }
942
-
943
- return udp_socket;
944
- }
945
-
946
- socket_t create_udp_socket_v6(int port, const std::string& bind_address) {
947
- LOG_SOCKET_DEBUG("Creating UDP socket on IPv6 port " << port <<
948
- (bind_address.empty() ? "" : " bound to " + bind_address));
949
-
950
- // Validate port number
951
- if (port < 0 || port > 65535) {
952
- LOG_SOCKET_ERROR("Invalid port number: " << port << " (must be 0-65535)");
953
- return INVALID_SOCKET_VALUE;
954
- }
955
-
956
- socket_t udp_socket = socket(AF_INET6, SOCK_DGRAM, 0);
957
- if (udp_socket == INVALID_SOCKET_VALUE) {
958
- #ifdef _WIN32
959
- LOG_SOCKET_ERROR("Failed to create IPv6 UDP socket (error: " << WSAGetLastError() << ")");
960
- #else
961
- LOG_SOCKET_ERROR("Failed to create IPv6 UDP socket (error: " << strerror(errno) << ")");
962
- #endif
963
- return INVALID_SOCKET_VALUE;
964
- }
965
-
966
- // Set socket option to reuse address
967
- int opt = 1;
968
- if (setsockopt(udp_socket, SOL_SOCKET, SO_REUSEADDR,
969
- (char*)&opt, sizeof(opt)) == SOCKET_ERROR_VALUE) {
970
- LOG_SOCKET_ERROR("Failed to set IPv6 UDP socket options");
971
- close_socket(udp_socket);
972
- return INVALID_SOCKET_VALUE;
973
- }
974
733
 
975
- // Always bind the socket - this is required for receiving data
976
- sockaddr_in6 addr;
977
- memset(&addr, 0, sizeof(addr));
978
- addr.sin6_family = AF_INET6;
979
- addr.sin6_port = htons(port); // port=0 will get an ephemeral port
980
-
981
- // Set bind address (default to all interfaces if empty)
982
- if (bind_address.empty()) {
983
- addr.sin6_addr = in6addr_any;
984
- } else {
985
- if (inet_pton(AF_INET6, bind_address.c_str(), &addr.sin6_addr) != 1) {
986
- LOG_SOCKET_ERROR("Invalid IPv6 bind address: " << bind_address);
734
+ if (bind(udp_socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR_VALUE) {
735
+ LOG_SOCKET_ERROR("Failed to bind " << af_label << " UDP socket to port " << port
736
+ << " (error: " << socket_error_string(get_last_socket_error()) << ")");
987
737
  close_socket(udp_socket);
988
738
  return INVALID_SOCKET_VALUE;
989
739
  }
990
740
  }
991
741
 
992
- if (bind(udp_socket, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR_VALUE) {
993
- #ifdef _WIN32
994
- LOG_SOCKET_ERROR("Failed to bind IPv6 UDP socket to port " << port << " (error: " << WSAGetLastError() << ")");
995
- #else
996
- LOG_SOCKET_ERROR("Failed to bind IPv6 UDP socket to port " << port << " (error: " << strerror(errno) << ")");
997
- #endif
998
- close_socket(udp_socket);
999
- return INVALID_SOCKET_VALUE;
1000
- }
1001
-
1002
- // Get the actual bound port if ephemeral port was requested
742
+ // Log the actual bound port
1003
743
  if (port == 0) {
1004
- sockaddr_in6 bound_addr;
1005
- socklen_t addr_len = sizeof(bound_addr);
1006
- if (getsockname(udp_socket, (struct sockaddr*)&bound_addr, &addr_len) == 0) {
1007
- uint16_t actual_port = ntohs(bound_addr.sin6_port);
1008
- LOG_SOCKET_INFO("IPv6 UDP socket bound to ephemeral port " << actual_port);
744
+ int actual_port = get_bound_port(udp_socket);
745
+ if (actual_port > 0) {
746
+ LOG_SOCKET_INFO(af_label << " UDP socket bound to ephemeral port " << actual_port);
1009
747
  } else {
1010
- LOG_SOCKET_INFO("IPv6 UDP socket bound to ephemeral port (unknown)");
748
+ LOG_SOCKET_INFO(af_label << " UDP socket bound to ephemeral port (unknown)");
1011
749
  }
1012
750
  } else {
1013
- LOG_SOCKET_INFO("IPv6 UDP socket bound to port " << port);
751
+ LOG_SOCKET_INFO(af_label << " UDP socket bound to port " << port);
1014
752
  }
1015
753
 
1016
754
  return udp_socket;
1017
755
  }
1018
756
 
757
+ // Build a sockaddr for sending UDP data to the given host:port.
758
+ // For DualStack/IPv6 sockets, IPv4 addresses are mapped to ::ffff:x.x.x.x
759
+ static bool build_udp_dest_addr(const std::string& host, int port, AddressFamily af,
760
+ sockaddr_storage& addr, socklen_t& addr_len) {
761
+ memset(&addr, 0, sizeof(addr));
1019
762
 
1020
-
1021
- int send_udp_data(socket_t socket, const std::vector<uint8_t>& data, const Peer& peer) {
1022
- LOG_SOCKET_DEBUG("Sending " << data.size() << " bytes to " << peer.ip << ":" << peer.port);
1023
-
1024
- // Check if it's an IPv6 address
1025
- if (network_utils::is_valid_ipv6(peer.ip)) {
1026
- // Handle IPv6 address
1027
- sockaddr_in6 addr;
1028
- memset(&addr, 0, sizeof(addr));
1029
- addr.sin6_family = AF_INET6;
1030
- addr.sin6_port = htons(peer.port);
1031
-
1032
- if (inet_pton(AF_INET6, peer.ip.c_str(), &addr.sin6_addr) <= 0) {
1033
- LOG_SOCKET_ERROR("Invalid IPv6 address: " << peer.ip);
1034
- return -1;
763
+ // Native IPv6 address
764
+ if (network_utils::is_valid_ipv6(host)) {
765
+ auto* a6 = reinterpret_cast<sockaddr_in6*>(&addr);
766
+ a6->sin6_family = AF_INET6;
767
+ a6->sin6_port = htons(port);
768
+ if (inet_pton(AF_INET6, host.c_str(), &a6->sin6_addr) <= 0) {
769
+ LOG_SOCKET_ERROR("Invalid IPv6 address: " << host);
770
+ return false;
1035
771
  }
772
+ addr_len = sizeof(sockaddr_in6);
773
+ return true;
774
+ }
1036
775
 
1037
- int bytes_sent = sendto(socket, (char*)data.data(), data.size(), 0,
1038
- (struct sockaddr*)&addr, sizeof(addr));
1039
- if (bytes_sent == SOCKET_ERROR_VALUE) {
1040
- LOG_SOCKET_ERROR("Failed to send UDP data to IPv6 " << peer.ip << ":" << peer.port);
1041
- return -1;
776
+ // Resolve IPv4 / hostname
777
+ std::string resolved_ip = network_utils::resolve_hostname(host);
778
+ if (resolved_ip.empty()) {
779
+ LOG_SOCKET_ERROR("Failed to resolve hostname: " << host);
780
+ return false;
781
+ }
782
+
783
+ if (af == AddressFamily::IPv4) {
784
+ // Pure IPv4 socket
785
+ auto* a4 = reinterpret_cast<sockaddr_in*>(&addr);
786
+ a4->sin_family = AF_INET;
787
+ a4->sin_port = htons(port);
788
+ if (inet_pton(AF_INET, resolved_ip.c_str(), &a4->sin_addr) <= 0) {
789
+ LOG_SOCKET_ERROR("Invalid IPv4 address: " << resolved_ip);
790
+ return false;
1042
791
  }
1043
-
1044
- LOG_SOCKET_DEBUG("Successfully sent " << bytes_sent << " bytes to IPv6 " << peer.ip << ":" << peer.port);
1045
- return bytes_sent;
792
+ addr_len = sizeof(sockaddr_in);
1046
793
  } else {
1047
- // Handle IPv4 address or hostname
1048
- std::string resolved_ip = network_utils::resolve_hostname(peer.ip);
1049
- if (resolved_ip.empty()) {
1050
- LOG_SOCKET_ERROR("Failed to resolve hostname: " << peer.ip);
1051
- return -1;
1052
- }
1053
-
1054
- // For dual-stack sockets, we need to use IPv6 address structure
1055
- // and convert IPv4 to IPv4-mapped IPv6 address (::ffff:x.x.x.x)
1056
- sockaddr_in6 addr;
1057
- memset(&addr, 0, sizeof(addr));
1058
- addr.sin6_family = AF_INET6;
1059
- addr.sin6_port = htons(peer.port);
1060
-
1061
- // Convert IPv4 to IPv4-mapped IPv6 address
794
+ // DualStack / IPv6 → IPv4-mapped IPv6 address (::ffff:x.x.x.x)
795
+ auto* a6 = reinterpret_cast<sockaddr_in6*>(&addr);
796
+ a6->sin6_family = AF_INET6;
797
+ a6->sin6_port = htons(port);
798
+
1062
799
  struct in_addr ipv4_addr;
1063
800
  if (inet_pton(AF_INET, resolved_ip.c_str(), &ipv4_addr) <= 0) {
1064
801
  LOG_SOCKET_ERROR("Invalid IPv4 address: " << resolved_ip);
1065
- return -1;
1066
- }
1067
-
1068
- // Create IPv4-mapped IPv6 address: ::ffff:x.x.x.x
1069
- addr.sin6_addr.s6_addr[10] = 0xff;
1070
- addr.sin6_addr.s6_addr[11] = 0xff;
1071
- memcpy(&addr.sin6_addr.s6_addr[12], &ipv4_addr.s_addr, 4);
1072
-
1073
- LOG_SOCKET_DEBUG("Converting IPv4 " << resolved_ip << " to IPv4-mapped IPv6 for dual-stack socket");
1074
-
1075
- int bytes_sent = sendto(socket, (char*)data.data(), data.size(), 0,
1076
- (struct sockaddr*)&addr, sizeof(addr));
1077
- if (bytes_sent == SOCKET_ERROR_VALUE) {
1078
- #ifdef _WIN32
1079
- LOG_SOCKET_ERROR("Failed to send UDP data to " << resolved_ip << ":" << peer.port << " (error: " << WSAGetLastError() << ")");
1080
- #else
1081
- LOG_SOCKET_ERROR("Failed to send UDP data to " << resolved_ip << ":" << peer.port << " (error: " << strerror(errno) << ")");
1082
- #endif
1083
- return -1;
802
+ return false;
1084
803
  }
1085
-
1086
- LOG_SOCKET_DEBUG("Successfully sent " << bytes_sent << " bytes to " << resolved_ip << ":" << peer.port << " via IPv4-mapped IPv6");
1087
- return bytes_sent;
804
+ a6->sin6_addr.s6_addr[10] = 0xff;
805
+ a6->sin6_addr.s6_addr[11] = 0xff;
806
+ memcpy(&a6->sin6_addr.s6_addr[12], &ipv4_addr.s_addr, 4);
807
+ addr_len = sizeof(sockaddr_in6);
1088
808
  }
809
+ return true;
1089
810
  }
1090
811
 
1091
- int send_udp_data_to(socket_t socket, const std::vector<uint8_t>& data, const std::string& hostname, int port) {
1092
- // Delegate to send_udp_data with Peer struct for consistent dual-stack handling
1093
- Peer peer(hostname, static_cast<uint16_t>(port));
1094
- return send_udp_data(socket, data, peer);
1095
- }
812
+ int send_udp_data(socket_t socket, const std::vector<uint8_t>& data,
813
+ const std::string& host, int port, AddressFamily af) {
814
+ LOG_SOCKET_DEBUG("Sending " << data.size() << " bytes to " << host << ":" << port);
1096
815
 
1097
- std::vector<uint8_t> receive_udp_data(socket_t socket, size_t buffer_size, Peer& sender_peer) {
1098
- std::vector<uint8_t> buffer(buffer_size);
1099
- sockaddr_storage sender_addr;
1100
- socklen_t sender_addr_len = sizeof(sender_addr);
1101
-
1102
- int bytes_received = recvfrom(socket, (char*)buffer.data(), buffer_size, 0,
1103
- (struct sockaddr*)&sender_addr, &sender_addr_len);
1104
-
1105
- if (bytes_received == SOCKET_ERROR_VALUE) {
1106
- // Check if this is just a non-blocking socket with no data available
1107
- #ifdef _WIN32
1108
- int error = WSAGetLastError();
1109
- if (error == WSAEWOULDBLOCK) {
1110
- // No data available on non-blocking socket - this is normal
1111
- return std::vector<uint8_t>();
1112
- } else {
1113
- LOG_SOCKET_DEBUG("Failed to receive UDP data: " << error);
1114
- return std::vector<uint8_t>();
1115
- }
1116
- #else
1117
- int error = errno;
1118
- if (error == EAGAIN || error == EWOULDBLOCK) {
1119
- // No data available on non-blocking socket - this is normal
1120
- return std::vector<uint8_t>();
1121
- } else {
1122
- LOG_SOCKET_DEBUG("Failed to receive UDP data: " << strerror(error));
1123
- return std::vector<uint8_t>();
1124
- }
1125
- #endif
1126
- }
1127
-
1128
- if (bytes_received == 0) {
1129
- LOG_SOCKET_DEBUG("Received empty UDP packet");
1130
- return std::vector<uint8_t>();
816
+ sockaddr_storage dest_addr;
817
+ socklen_t addr_len;
818
+ if (!build_udp_dest_addr(host, port, af, dest_addr, addr_len)) {
819
+ return -1;
1131
820
  }
1132
-
1133
- // Extract sender information based on address family
1134
- if (sender_addr.ss_family == AF_INET) {
1135
- char sender_ip[INET_ADDRSTRLEN];
1136
- struct sockaddr_in* addr_in = (struct sockaddr_in*)&sender_addr;
1137
- inet_ntop(AF_INET, &addr_in->sin_addr, sender_ip, INET_ADDRSTRLEN);
1138
- sender_peer.ip = sender_ip;
1139
- sender_peer.port = ntohs(addr_in->sin_port);
1140
-
1141
- LOG_SOCKET_DEBUG("Received " << bytes_received << " bytes from " << sender_peer.ip << ":" << sender_peer.port);
1142
- } else if (sender_addr.ss_family == AF_INET6) {
1143
- struct sockaddr_in6* addr_in6 = (struct sockaddr_in6*)&sender_addr;
1144
-
1145
- // Check if this is an IPv4-mapped IPv6 address (::ffff:x.x.x.x)
1146
- if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {
1147
- // Extract the IPv4 address from the last 4 bytes
1148
- char ip_str[INET_ADDRSTRLEN];
1149
- struct in_addr ipv4_addr;
1150
- memcpy(&ipv4_addr, &addr_in6->sin6_addr.s6_addr[12], 4);
1151
- inet_ntop(AF_INET, &ipv4_addr, ip_str, INET_ADDRSTRLEN);
1152
- sender_peer.ip = ip_str;
1153
- sender_peer.port = ntohs(addr_in6->sin6_port);
1154
-
1155
- LOG_SOCKET_DEBUG("Received " << bytes_received << " bytes from IPv4-mapped " << sender_peer.ip << ":" << sender_peer.port);
1156
- } else {
1157
- char ip_str[INET6_ADDRSTRLEN];
1158
- inet_ntop(AF_INET6, &addr_in6->sin6_addr, ip_str, INET6_ADDRSTRLEN);
1159
- sender_peer.ip = ip_str;
1160
- sender_peer.port = ntohs(addr_in6->sin6_port);
1161
-
1162
- LOG_SOCKET_DEBUG("Received " << bytes_received << " bytes from IPv6 [" << sender_peer.ip << "]:" << sender_peer.port);
1163
- }
1164
- } else {
1165
- LOG_SOCKET_WARN("Received UDP data from unknown address family");
1166
- sender_peer.ip = "unknown";
1167
- sender_peer.port = 0;
821
+
822
+ int bytes_sent = sendto(socket, (char*)data.data(), data.size(), 0,
823
+ reinterpret_cast<sockaddr*>(&dest_addr), addr_len);
824
+ if (bytes_sent == SOCKET_ERROR_VALUE) {
825
+ LOG_SOCKET_ERROR("Failed to send UDP data to " << host << ":" << port
826
+ << " (error: " << socket_error_string(get_last_socket_error()) << ")");
827
+ return -1;
1168
828
  }
1169
-
1170
- buffer.resize(bytes_received);
1171
- return buffer;
829
+
830
+ LOG_SOCKET_DEBUG("Successfully sent " << bytes_sent << " bytes to " << host << ":" << port);
831
+ return bytes_sent;
1172
832
  }
1173
833
 
1174
- std::vector<uint8_t> receive_udp_data_with_timeout(socket_t socket, size_t buffer_size, int timeout_ms,
1175
- std::string* sender_ip, int* sender_port) {
1176
- LOG_SOCKET_DEBUG("Receiving UDP data with timeout " << timeout_ms << "ms");
1177
-
1178
- std::vector<uint8_t> buffer(buffer_size);
1179
- sockaddr_storage sender_addr;
1180
- socklen_t sender_addr_len = sizeof(sender_addr);
1181
-
1182
- // Handle timeout if specified
1183
- if (timeout_ms > 0) {
1184
- // Set up timeout using select
834
+ std::vector<uint8_t> receive_udp_data(socket_t socket, size_t buffer_size, Peer& sender_peer,
835
+ int timeout_ms) {
836
+ // Handle timeout using select
837
+ if (timeout_ms >= 0) {
1185
838
  fd_set read_fds;
1186
839
  FD_ZERO(&read_fds);
1187
840
  FD_SET(socket, &read_fds);
1188
-
841
+
1189
842
  struct timeval timeout;
1190
843
  timeout.tv_sec = timeout_ms / 1000;
1191
844
  timeout.tv_usec = (timeout_ms % 1000) * 1000;
1192
-
1193
- // Wait for data with timeout
845
+
1194
846
  int result = select(socket + 1, &read_fds, nullptr, nullptr, &timeout);
1195
847
  if (result == 0) {
1196
848
  LOG_SOCKET_DEBUG("UDP receive timeout (" << timeout_ms << "ms)");
1197
- return std::vector<uint8_t>();
849
+ return {};
1198
850
  } else if (result < 0) {
1199
851
  LOG_SOCKET_ERROR("Select error while waiting for UDP data");
1200
- return std::vector<uint8_t>();
852
+ return {};
1201
853
  }
1202
854
  }
1203
-
855
+
856
+ std::vector<uint8_t> buffer(buffer_size);
857
+ sockaddr_storage sender_addr;
858
+ socklen_t sender_addr_len = sizeof(sender_addr);
859
+
1204
860
  int bytes_received = recvfrom(socket, (char*)buffer.data(), buffer_size, 0,
1205
- (struct sockaddr*)&sender_addr, &sender_addr_len);
1206
-
861
+ reinterpret_cast<sockaddr*>(&sender_addr), &sender_addr_len);
862
+
1207
863
  if (bytes_received == SOCKET_ERROR_VALUE) {
1208
- // Check if this is just a non-blocking socket with no data available
864
+ int error = get_last_socket_error();
1209
865
  #ifdef _WIN32
1210
- int error = WSAGetLastError();
1211
- if (error == WSAEWOULDBLOCK) {
1212
- // No data available on non-blocking socket - this is normal
1213
- LOG_SOCKET_DEBUG("No UDP data available (non-blocking)");
1214
- return std::vector<uint8_t>();
1215
- } else {
1216
- LOG_SOCKET_DEBUG("Failed to receive UDP data: " << error);
1217
- return std::vector<uint8_t>();
1218
- }
866
+ if (error == WSAEWOULDBLOCK) { return {}; }
1219
867
  #else
1220
- int error = errno;
1221
- if (error == EAGAIN || error == EWOULDBLOCK) {
1222
- // No data available on non-blocking socket - this is normal
1223
- LOG_SOCKET_DEBUG("No UDP data available (non-blocking)");
1224
- return std::vector<uint8_t>();
1225
- } else {
1226
- LOG_SOCKET_DEBUG("Failed to receive UDP data: " << strerror(error));
1227
- return std::vector<uint8_t>();
1228
- }
868
+ if (error == EAGAIN || error == EWOULDBLOCK) { return {}; }
1229
869
  #endif
870
+ LOG_SOCKET_DEBUG("Failed to receive UDP data: " << socket_error_string(error));
871
+ return {};
1230
872
  }
1231
-
873
+
1232
874
  if (bytes_received == 0) {
1233
875
  LOG_SOCKET_DEBUG("Received empty UDP packet");
1234
- return std::vector<uint8_t>();
1235
- }
1236
-
1237
- // Extract sender information based on address family
1238
- if (sender_addr.ss_family == AF_INET) {
1239
- char ip_str[INET_ADDRSTRLEN];
1240
- struct sockaddr_in* addr_in = (struct sockaddr_in*)&sender_addr;
1241
- inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, INET_ADDRSTRLEN);
1242
-
1243
- if (sender_ip) *sender_ip = ip_str;
1244
- if (sender_port) *sender_port = ntohs(addr_in->sin_port);
1245
-
1246
- LOG_SOCKET_DEBUG("Received " << bytes_received << " bytes from " << ip_str << ":" << ntohs(addr_in->sin_port));
1247
- } else if (sender_addr.ss_family == AF_INET6) {
1248
- struct sockaddr_in6* addr_in6 = (struct sockaddr_in6*)&sender_addr;
1249
-
1250
- // Check if this is an IPv4-mapped IPv6 address (::ffff:x.x.x.x)
1251
- if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {
1252
- // Extract the IPv4 address from the last 4 bytes
1253
- char ip_str[INET_ADDRSTRLEN];
1254
- struct in_addr ipv4_addr;
1255
- memcpy(&ipv4_addr, &addr_in6->sin6_addr.s6_addr[12], 4);
1256
- inet_ntop(AF_INET, &ipv4_addr, ip_str, INET_ADDRSTRLEN);
1257
-
1258
- if (sender_ip) *sender_ip = ip_str;
1259
- if (sender_port) *sender_port = ntohs(addr_in6->sin6_port);
1260
-
1261
- LOG_SOCKET_DEBUG("Received " << bytes_received << " bytes from IPv4-mapped " << ip_str << ":" << ntohs(addr_in6->sin6_port));
1262
- } else {
1263
- char ip_str[INET6_ADDRSTRLEN];
1264
- inet_ntop(AF_INET6, &addr_in6->sin6_addr, ip_str, INET6_ADDRSTRLEN);
1265
-
1266
- if (sender_ip) *sender_ip = ip_str;
1267
- if (sender_port) *sender_port = ntohs(addr_in6->sin6_port);
1268
-
1269
- LOG_SOCKET_DEBUG("Received " << bytes_received << " bytes from IPv6 [" << ip_str << "]:" << ntohs(addr_in6->sin6_port));
1270
- }
1271
- } else {
1272
- LOG_SOCKET_WARN("Received UDP data from unknown address family");
1273
- if (sender_ip) *sender_ip = "unknown";
1274
- if (sender_port) *sender_port = 0;
876
+ return {};
1275
877
  }
1276
-
878
+
879
+ extract_sender_peer(sender_addr, sender_peer);
880
+
881
+ LOG_SOCKET_DEBUG("Received " << bytes_received << " bytes from " << sender_peer.ip << ":" << sender_peer.port);
882
+
1277
883
  buffer.resize(bytes_received);
1278
884
  return buffer;
1279
885
  }
1280
886
 
1281
- // Helper function to determine if a socket is TCP
1282
- bool is_tcp_socket(socket_t socket) {
1283
- if (!is_valid_socket(socket)) {
1284
- return false;
1285
- }
1286
-
1287
- int sock_type;
1288
- socklen_t opt_len = sizeof(sock_type);
1289
-
1290
- if (getsockopt(socket, SOL_SOCKET, SO_TYPE, (char*)&sock_type, &opt_len) == 0) {
1291
- return sock_type == SOCK_STREAM;
1292
- }
1293
-
1294
- return false; // Assume not TCP if we can't determine
1295
- }
887
+ // ── Common Socket Functions ─────────────────────────────────────────────────
1296
888
 
1297
- // Common Socket Functions
1298
889
  void close_socket(socket_t socket, bool force) {
1299
890
  if (is_valid_socket(socket)) {
1300
891
  LOG_SOCKET_DEBUG("Closing socket " << socket);
1301
-
1302
- // Peform force shutdown if needed
892
+
1303
893
  if (force) {
894
+ struct linger lin;
895
+ lin.l_onoff = 1;
896
+ lin.l_linger = 0;
897
+ setsockopt(socket, SOL_SOCKET, SO_LINGER,
898
+ (const char*)&lin, sizeof(lin));
899
+
1304
900
  LOG_SOCKET_DEBUG("Performing shutdown for TCP socket " << socket);
1305
901
  #ifdef _WIN32
1306
902
  shutdown(socket, SD_BOTH);
@@ -1308,7 +904,7 @@ void close_socket(socket_t socket, bool force) {
1308
904
  shutdown(socket, SHUT_RDWR);
1309
905
  #endif
1310
906
  }
1311
-
907
+
1312
908
  closesocket(socket);
1313
909
  }
1314
910
  }
@@ -1330,13 +926,13 @@ bool set_socket_nonblocking(socket_t socket) {
1330
926
  LOG_SOCKET_ERROR("Failed to get socket flags");
1331
927
  return false;
1332
928
  }
1333
-
929
+
1334
930
  if (fcntl(socket, F_SETFL, flags | O_NONBLOCK) == -1) {
1335
931
  LOG_SOCKET_ERROR("Failed to set socket to non-blocking mode");
1336
932
  return false;
1337
933
  }
1338
934
  #endif
1339
-
935
+
1340
936
  LOG_SOCKET_DEBUG("Socket set to non-blocking mode");
1341
937
  return true;
1342
938
  }
@@ -1354,25 +950,31 @@ bool set_socket_blocking(socket_t socket) {
1354
950
  LOG_SOCKET_ERROR("Failed to get socket flags");
1355
951
  return false;
1356
952
  }
1357
-
953
+
1358
954
  if (fcntl(socket, F_SETFL, flags & ~O_NONBLOCK) == -1) {
1359
955
  LOG_SOCKET_ERROR("Failed to set socket to blocking mode");
1360
956
  return false;
1361
957
  }
1362
958
  #endif
1363
-
959
+
1364
960
  LOG_SOCKET_DEBUG("Socket set to blocking mode");
1365
961
  return true;
1366
962
  }
1367
963
 
1368
- int get_ephemeral_port(socket_t socket)
1369
- {
1370
- sockaddr_in6 bound_addr;
964
+ int get_bound_port(socket_t socket) {
965
+ sockaddr_storage bound_addr;
1371
966
  socklen_t addr_len = sizeof(bound_addr);
1372
- if (getsockname(socket, (struct sockaddr*)&bound_addr, &addr_len) == 0) {
1373
- return ntohs(bound_addr.sin6_port);
967
+ if (getsockname(socket, reinterpret_cast<sockaddr*>(&bound_addr), &addr_len) != 0) {
968
+ return 0;
969
+ }
970
+
971
+ if (bound_addr.ss_family == AF_INET) {
972
+ return ntohs(reinterpret_cast<sockaddr_in*>(&bound_addr)->sin_port);
973
+ } else if (bound_addr.ss_family == AF_INET6) {
974
+ return ntohs(reinterpret_cast<sockaddr_in6*>(&bound_addr)->sin6_port);
1374
975
  }
976
+
1375
977
  return 0;
1376
978
  }
1377
979
 
1378
- } // namespace librats
980
+ } // namespace librats