Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_network.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
27
28#include <errno.h>
29#include <limits.h>
30#include <stdarg.h>
31#include <pthread.h>
32#include <unistd.h>
33#include <string.h>
34#include <ctype.h>
35#include <sys/types.h>
36
37#include "nilorea/n_network.h"
39#include "nilorea/n_reactor.h"
40#include "nilorea/n_log.h"
41#include "nilorea/n_hash.h"
42#include "nilorea/n_base64.h"
43#include "nilorea/n_zlib.h"
44#include "nilorea/n_lz4.h"
45#include "nilorea/n_time.h"
46
47/* Opportunistic payload compression. Threshold and ratio knobs live
48 * in nilorea/n_network.h so the reactor send path shares them. */
49
50/* MinGW does not provide strndup */
51#ifdef __windows__
52static char* _n_strndup(const char* s, size_t n) {
53 size_t len = strlen(s);
54 if (n < len) len = n;
55 char* p = (char*)malloc(len + 1);
56 if (p) {
57 memcpy(p, s, len);
58 p[len] = '\0';
59 }
60 return p;
61}
62#define strndup _n_strndup
63#endif
64
65#ifdef HAVE_OPENSSL
66#include <openssl/sha.h>
67#include <openssl/rand.h>
68#include <openssl/x509v3.h>
69#endif
70
71/* error capture infrastructure */
72
74#ifndef _Thread_local
75#define _Thread_local __thread
76#endif
77static _Thread_local char s_connect_errors[8][512];
80
82static void _netw_capture_error(NETWORK* netw, const char* fmt, ...) N_PRINTF_FORMAT(2, 3);
83static void _netw_capture_error(NETWORK* netw, const char* fmt, ...) {
84 if (!netw) return;
85 va_list ap;
86 va_start(ap, fmt);
87 vsnprintf(netw->netw_errors[netw->netw_err_next], 512, fmt, ap);
88 va_end(ap);
89 netw->netw_err_next = (netw->netw_err_next + 1) % 8;
90 if (netw->netw_err_count < 8) netw->netw_err_count++;
91}
92
94static void _netw_capture_connect_error(const char* fmt, ...) N_PRINTF_FORMAT(1, 2);
95static void _netw_capture_connect_error(const char* fmt, ...) {
96 va_list ap;
97 va_start(ap, fmt);
98 vsnprintf(s_connect_errors[s_connect_err_next], 512, fmt, ap);
99 va_end(ap);
102}
103
104/* public error accessors */
105
107 return netw ? netw->netw_err_count : 0;
108}
109
110const char* n_netw_get_error(const NETWORK* netw, int index) {
111 if (!netw || index < 0 || index >= netw->netw_err_count) return NULL;
112 int oldest = (netw->netw_err_next - netw->netw_err_count + 8) % 8;
113 return netw->netw_errors[(oldest + index) % 8];
114}
115
117 if (!netw) return;
118 netw->netw_err_count = 0;
119 netw->netw_err_next = 0;
120}
121
125
126const char* n_netw_get_connect_error(int index) {
127 if (index < 0 || index >= s_connect_err_count) return NULL;
128 int oldest = (s_connect_err_next - s_connect_err_count + 8) % 8;
129 return s_connect_errors[(oldest + index) % 8];
130}
131
136
139
140
145size_t htonst(size_t value) {
146#if __BYTE_ORDER == __LITTLE_ENDIAN
147 if (sizeof(size_t) == 4) {
148 return (size_t)htonl((uint32_t)value);
149 } else if (sizeof(size_t) == 8) {
150 return ((size_t)htonl((uint32_t)(value >> 32)) |
151 ((size_t)htonl((uint32_t)value) << 32));
152 }
153#endif
154 return value; // No conversion needed for big-endian
155}
156
162size_t ntohst(size_t value) {
163#if __BYTE_ORDER == __LITTLE_ENDIAN
164 if (sizeof(size_t) == 4) {
165 return (size_t)ntohl((uint32_t)value);
166 } else if (sizeof(size_t) == 8) {
167 return ((size_t)ntohl((uint32_t)(value >> 32)) |
168 ((size_t)ntohl((uint32_t)value) << 32));
169 }
170#endif
171 return value; // No conversion needed for big-endian
172}
173
174#ifdef __windows__
175
181char* wchar_to_char(const wchar_t* pwchar) {
182 __n_assert(pwchar, return NULL);
183
184 // get the number of characters in the string.
185 int currentCharIndex = 0;
186 char currentChar = (char)pwchar[currentCharIndex];
187 char* filePathC = NULL;
188
189 while (currentChar != '\0') {
190 currentCharIndex++;
191 currentChar = (char)pwchar[currentCharIndex];
192 }
193
194 const int charCount = currentCharIndex + 1;
195
196 // allocate a new block of memory size char (1 byte) instead of wide char (2 bytes)
197 Malloc(filePathC, char, (size_t)charCount);
198 __n_assert(filePathC, return NULL);
199
200 for (int i = 0; i < charCount; i++) {
201 // convert to char (1 byte)
202 char character = (char)pwchar[i];
203
204 *filePathC = character;
205
206 filePathC += sizeof(char);
207 }
208 filePathC += '\0';
209
210 filePathC -= (sizeof(char) * (size_t)charCount);
211
212 return filePathC;
213}
214
216#define NETW_CALL_RETRY(__retvar, __expression, __max_tries) \
217 do { \
218 int __nb_retries = 0; \
219 do { \
220 __retvar = (__expression); \
221 __nb_retries++; \
222 } while (__retvar == -1 && (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEWOULDBLOCK) && __nb_retries < (__max_tries)); \
223 if (__retvar == -1 && __nb_retries >= (__max_tries)) __retvar = -2; \
224 } while (0)
225
227#define neterrno WSAGetLastError()
228
232static char* netstrerror(int code) {
233 wchar_t* ws = NULL;
234 FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
235 NULL, (DWORD)code,
236 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
237 (LPWSTR)&ws, 0, NULL);
238 char* netstr = wchar_to_char(ws);
239 LocalFree(ws);
240 return netstr;
241}
242
243#if __GNUC__ <= 6 && __GNUC_MINOR__ <= 3
244
245/*--------------------------------------------------------------------------------------
246 By Marco Ladino - mladinox.. jan/2016
247 MinGW 3.45 thru 4.5 versions, don't have the socket functions:
248 --> inet_ntop(..)
249 --> inet_pton(..)
250 But with this adapted code using the original functions from FreeBSD,
251 one can to use it in the C/C++ Applications, without problem..!
252 This implementation, include tests for IPV4 and IPV6 addresses,
253 and is full C/C++ compatible..
254 --------------------------------------------------------------------------------------*/
255/* OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp */
256/*-
257 * Copyright (c) 1998 Todd C. Miller <Todd.Miller at courtesan.com>
258 *
259 * Permission to use, copy, modify, and distribute this software for any
260 * purpose with or without fee is hereby granted, provided that the above
261 * copyright notice and this permission notice appear in all copies.
262 *
263 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
264 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
265 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
266 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
267 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
268 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
269 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
270 */
271
279size_t strlcpy(char* dst, const char* src, size_t siz) {
280 char* d = dst;
281 const char* s = src;
282 size_t n = siz;
283
284 /* Copy as many bytes as will fit */
285 if (n != 0) {
286 while (--n != 0) {
287 if ((*d++ = *s++) == '\0')
288 break;
289 }
290 }
291
292 /* Not enough room in dst, add NUL and traverse rest of src */
293 if (n == 0) {
294 if (siz != 0)
295 *d = '\0'; /* NUL-terminate dst */
296 while (*s++);
297 }
298
299 return (s - src - 1); /* count does not include NUL */
300}
301
302/*
303 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
304 * Copyright (c) 1996-1999 by Internet Software Consortium.
305 *
306 * Permission to use, copy, modify, and distribute this software for any
307 * purpose with or without fee is hereby granted, provided that the above
308 * copyright notice and this permission notice appear in all copies.
309 *
310 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
311 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
312 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
313 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
314 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
315 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
316 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
317 */
318
319/*
320 * WARNING: Don't even consider trying to compile this on a system where
321 * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX.
322 */
323
324static char* inet_ntop4(const unsigned char* src, char* dst, socklen_t size);
325static char* inet_ntop6(const unsigned char* src, char* dst, socklen_t size);
326
335char* inet_ntop(int af, const void* src, char* dst, socklen_t size) {
336 switch (af) {
337 case AF_INET:
338 return (inet_ntop4((const unsigned char*)src, dst, size));
339 case AF_INET6:
340 return (inet_ntop6((const unsigned char*)src, dst, size));
341 default:
342 return (NULL);
343 }
344 /* NOTREACHED */
345}
346
347/* const char *
348 * inet_ntop4(src, dst, size)
349 * format an IPv4 address
350 * return:
351 * `dst' (as a const)
352 * notes:
353 * (1) uses no statics
354 * (2) takes a u_char* not an in_addr as input
355 * author:
356 * Paul Vixie, 1996.
357 */
358static char* inet_ntop4(const unsigned char* src, char* dst, socklen_t size) {
359 static const char fmt[] = "%u.%u.%u.%u";
360 char tmp[sizeof "255.255.255.255"];
361 int l;
362
363 l = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]);
364 if (l <= 0 || (socklen_t)l >= size) {
365 return (NULL);
366 }
367 strlcpy(dst, tmp, size);
368 return (dst);
369}
370
371/* const char *
372 * inet_ntop6(src, dst, size)
373 * convert IPv6 binary address into presentation (printable) format
374 * author:
375 * Paul Vixie, 1996.
376 */
377static char* inet_ntop6(const unsigned char* src, char* dst, socklen_t size) {
378 /*
379 * Note that int32_t and int16_t need only be "at least" large enough
380 * to contain a value of the specified size. On some systems, like
381 * Crays, there is no such thing as an integer variable with 16 bits.
382 * Keep this in mind if you think this function should have been coded
383 * to use pointer overlays. All the world's not a VAX.
384 */
385 char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp;
386 struct
387 {
388 int base, len;
389 } best, cur;
390#define NS_IN6ADDRSZ 16
391#define NS_INT16SZ 2
392 u_int words[NS_IN6ADDRSZ / NS_INT16SZ];
393 int i;
394
395 /*
396 * Preprocess:
397 * Copy the input (bytewise) array into a wordwise array.
398 * Find the longest run of 0x00's in src[] for :: shorthanding.
399 */
400 memset(words, '\0', sizeof words);
401 for (i = 0; i < NS_IN6ADDRSZ; i++)
402 words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
403 best.base = -1;
404 best.len = 0;
405 cur.base = -1;
406 cur.len = 0;
407 for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
408 if (words[i] == 0) {
409 if (cur.base == -1)
410 cur.base = i, cur.len = 1;
411 else
412 cur.len++;
413 } else {
414 if (cur.base != -1) {
415 if (best.base == -1 || cur.len > best.len)
416 best = cur;
417 cur.base = -1;
418 }
419 }
420 }
421 if (cur.base != -1) {
422 if (best.base == -1 || cur.len > best.len)
423 best = cur;
424 }
425 if (best.base != -1 && best.len < 2)
426 best.base = -1;
427
428 /*
429 * Format the result.
430 */
431 tp = tmp;
432 for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
433 /* Are we inside the best run of 0x00's? */
434 if (best.base != -1 && i >= best.base &&
435 i < (best.base + best.len)) {
436 if (i == best.base)
437 *tp++ = ':';
438 continue;
439 }
440 /* Are we following an initial run of 0x00s or any real hex? */
441 if (i != 0)
442 *tp++ = ':';
443 /* Is this address an encapsulated IPv4? */
444 if (i == 6 && best.base == 0 && (best.len == 6 || (best.len == 7 && words[7] != 0x0001) || (best.len == 5 && words[5] == 0xffff))) {
445 if (!inet_ntop4(src + 12, tp, sizeof tmp - (tp - tmp)))
446 return (NULL);
447 tp += strlen(tp);
448 break;
449 }
450 tp += sprintf(tp, "%x", words[i]);
451 }
452 /* Was it a trailing run of 0x00's? */
453 if (best.base != -1 && (best.base + best.len) ==
454 (NS_IN6ADDRSZ / NS_INT16SZ))
455 *tp++ = ':';
456 *tp++ = '\0';
457
458 /*
459 * Check for overflow, copy, and we're done.
460 */
461 if ((socklen_t)(tp - tmp) > size) {
462 return (NULL);
463 }
464 strcpy(dst, tmp);
465 return (dst);
466}
467
468/*
469 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
470 * Copyright (c) 1996,1999 by Internet Software Consortium.
471 *
472 * Permission to use, copy, modify, and distribute this software for any
473 * purpose with or without fee is hereby granted, provided that the above
474 * copyright notice and this permission notice appear in all copies.
475 *
476 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
477 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
478 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
479 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
480 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
481 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
482 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
483 */
484
485/*
486 * WARNING: Don't even consider trying to compile this on a system where
487 * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX.
488 */
489
490static int inet_pton4(const char* src, u_char* dst);
491static int inet_pton6(const char* src, u_char* dst);
492
500int inet_pton(int af, const char* src, void* dst) {
501 switch (af) {
502 case AF_INET:
503 return (inet_pton4(src, (unsigned char*)dst));
504 case AF_INET6:
505 return (inet_pton6(src, (unsigned char*)dst));
506 default:
507 return (-1);
508 }
509 /* NOTREACHED */
510}
511
512/* int
513 * inet_pton4(src, dst)
514 * like inet_aton() but without all the hexadecimal and shorthand.
515 * return:
516 * 1 if `src' is a valid dotted quad, else 0.
517 * notice:
518 * does not touch `dst' unless it's returning 1.
519 * author:
520 * Paul Vixie, 1996.
521 */
522static int inet_pton4(const char* src, u_char* dst) {
523 static const char digits[] = "0123456789";
524 int saw_digit, octets, ch;
525#define NS_INADDRSZ 4
526 /* Zero-initialise: the octets are filled through the `tp` alias
527 * below, which cppcheck's data flow can't follow, so it cannot
528 * prove tmp is fully written before the trailing memcpy. The
529 * init is harmless and removes the false uninitvar diagnostic. */
530 u_char tmp[NS_INADDRSZ] = {0}, *tp;
531
532 saw_digit = 0;
533 octets = 0;
534 tp = tmp;
535 while ((ch = *src++) != '\0') {
536 const char* pch;
537
538 if ((pch = strchr(digits, ch)) != NULL) {
539 u_int uiNew = *tp * 10 + (pch - digits);
540
541 if (saw_digit && *tp == 0)
542 return (0);
543 if (uiNew > 255)
544 return (0);
545 *tp = uiNew;
546 if (!saw_digit) {
547 if (++octets > 4)
548 return (0);
549 saw_digit = 1;
550 }
551 } else if (ch == '.' && saw_digit) {
552 if (octets == 4)
553 return (0);
554 *++tp = 0;
555 saw_digit = 0;
556 } else
557 return (0);
558 }
559 if (octets < 4)
560 return (0);
561 memcpy(dst, tmp, NS_INADDRSZ);
562 return (1);
563}
564
565/* int
566 * inet_pton6(src, dst)
567 * convert presentation level address to network order binary form.
568 * return:
569 * 1 if `src' is a valid [RFC1884 2.2] address, else 0.
570 * notice:
571 * (1) does not touch `dst' unless it's returning 1.
572 * (2) :: in a full address is silently ignored.
573 * credit:
574 * inspired by Mark Andrews.
575 * author:
576 * Paul Vixie, 1996.
577 */
578static int inet_pton6(const char* src, u_char* dst) {
579 static const char xdigits_l[] = "0123456789abcdef",
580 xdigits_u[] = "0123456789ABCDEF";
581#define NS_IN6ADDRSZ 16
582#define NS_INT16SZ 2
583 u_char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp;
584 const char* curtok;
585 int ch, seen_xdigits;
586 u_int val;
587
588 memset((tp = tmp), '\0', NS_IN6ADDRSZ);
589 endp = tp + NS_IN6ADDRSZ;
590 colonp = NULL;
591 /* Leading :: requires some special handling. */
592 if (*src == ':')
593 if (*++src != ':')
594 return (0);
595 curtok = src;
596 seen_xdigits = 0;
597 val = 0;
598 while ((ch = *src++) != '\0') {
599 const char* xdigits : const char* pch;
600
601 if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL)
602 pch = strchr((xdigits = xdigits_u), ch);
603 if (pch != NULL) {
604 val <<= 4;
605 val |= (pch - xdigits);
606 if (++seen_xdigits > 4)
607 return (0);
608 continue;
609 }
610 if (ch == ':') {
611 curtok = src;
612 if (!seen_xdigits) {
613 if (colonp)
614 return (0);
615 colonp = tp;
616 continue;
617 } else if (*src == '\0') {
618 return (0);
619 }
620 if (tp + NS_INT16SZ > endp)
621 return (0);
622 *tp++ = (u_char)(val >> 8) & 0xff;
623 *tp++ = (u_char)val & 0xff;
624 seen_xdigits = 0;
625 val = 0;
626 continue;
627 }
628 if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
629 inet_pton4(curtok, tp) > 0) {
630 tp += NS_INADDRSZ;
631 seen_xdigits = 0;
632 break; /* '\\0' was seen by inet_pton4(). */
633 }
634 return (0);
635 }
636 if (seen_xdigits) {
637 if (tp + NS_INT16SZ > endp)
638 return (0);
639 *tp++ = (u_char)(val >> 8) & 0xff;
640 *tp++ = (u_char)val & 0xff;
641 }
642 if (colonp != NULL) {
643 /*
644 * Since some memmove()'s erroneously fail to handle
645 * overlapping regions, we'll do the shift by hand.
646 */
647 const int n = tp - colonp;
648 int i;
649
650 if (tp == endp)
651 return (0);
652 for (i = 1; i <= n; i++) {
653 endp[-i] = colonp[n - i];
654 colonp[n - i] = 0;
655 }
656 tp = endp;
657 }
658 if (tp != endp)
659 return (0);
660 memcpy(dst, tmp, NS_IN6ADDRSZ);
661 return (1);
662}
663
664#endif /* if GCC_VERSION <= 4.5 */
665
666#else /* not __windows__ */
667
668#include <sys/types.h>
669#include <sys/wait.h>
670
672#define NETW_CALL_RETRY(__retvar, __expression, __max_tries) \
673 do { \
674 int __nb_retries = 0; \
675 do { \
676 __retvar = (__expression); \
677 __nb_retries++; \
678 } while (__retvar == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) && __nb_retries < (__max_tries)); \
679 if (__retvar == -1 && __nb_retries >= (__max_tries)) __retvar = -2; \
680 } while (0)
681
683#define neterrno errno
684
686/* #define netstrerror( code )({ \
687 size_t errmsglen = 512 ; // strerrorlen_s( code ) + 1 ; \
688 char *errmsg = NULL ; \
689 Malloc( errmsg , char , errmsglen ); \
690 if( errmsg ) \
691 { \
692 strerror_s( errmsg , errmsglen , code ); \
693 } \
694 errmsg ; \
695 }) */
696
700static char* netstrerror(int code) {
701 /* strdup returns NULL and sets errno=ENOMEM on allocation failure;
702 * propagate that NULL to the caller, who treats it as "no message". */
703 return strdup(strerror(code));
704}
705
706#endif /* if not def windows */
707
714NETWORK* netw_new(size_t send_list_limit, size_t recv_list_limit) {
715 NETWORK* netw = NULL;
716
717 Malloc(netw, NETWORK, 1);
718 __n_assert(netw, return NULL);
719
720 /* netw itself */
721 netw->nb_pending = -1;
722 netw->mode = -1;
723 netw->user_id = -1;
724 netw->nb_running_threads = 0;
726 netw->threaded_engine_status = NETW_THR_ENGINE_STOPPED;
727 /* Default to zlib for back-compat with the first compression
728 * pass that shipped before LZ4 support landed. Callers can
729 * switch to NONE or LZ4 via netw_set_compression_mode(). */
730 netw->compress_mode = NETW_COMPRESS_ZLIB;
731
732 /* netw -> link */
733 netw->link.sock = INVALID_SOCKET;
734 netw->link.port =
735 netw->link.ip = NULL;
736 memset(&netw->link.hints, 0, sizeof(struct addrinfo));
737 memset(&netw->link.raddr, 0, sizeof(struct sockaddr_storage));
738
739 /*initiliaze mutexs*/
740 if (pthread_mutex_init(&netw->sendbolt, NULL) != 0) {
741 n_log(LOG_ERR, "Error initializing netw -> sendbolt");
742 Free(netw);
743 return NULL;
744 }
745 /*initiliaze mutexs*/
746 if (pthread_mutex_init(&netw->recvbolt, NULL) != 0) {
747 n_log(LOG_ERR, "Error initializing netw -> recvbolt");
748 pthread_mutex_destroy(&netw->sendbolt);
749 Free(netw);
750 return NULL;
751 }
752 /*initiliaze mutexs*/
753 if (pthread_mutex_init(&netw->eventbolt, NULL) != 0) {
754 n_log(LOG_ERR, "Error initializing netw -> eventbolt");
755 pthread_mutex_destroy(&netw->sendbolt);
756 pthread_mutex_destroy(&netw->recvbolt);
757 Free(netw);
758 return NULL;
759 }
760 /* initialize send sem bolt */
761 if (sem_init(&netw->send_blocker, 0, 0) != 0) {
762 n_log(LOG_ERR, "Error initializing netw -> eventbolt");
763 pthread_mutex_destroy(&netw->eventbolt);
764 pthread_mutex_destroy(&netw->sendbolt);
765 pthread_mutex_destroy(&netw->recvbolt);
766 Free(netw);
767 return NULL;
768 }
769 /*initialize queues */
770 netw->recv_buf = new_generic_list(recv_list_limit);
771 if (!netw->recv_buf) {
772 n_log(LOG_ERR, "Error when creating receive list with %zu item limit", recv_list_limit);
774 return NULL;
775 }
776 netw->send_buf = new_generic_list(send_list_limit);
777 if (!netw->send_buf) {
778 n_log(LOG_ERR, "Error when creating send list with %zu item limit", send_list_limit);
780 return NULL;
781 }
783 if (!netw->pools) {
784 n_log(LOG_ERR, "Error when creating pools list");
786 return NULL;
787 }
788 netw->addr_infos_loaded = 0;
789 netw->send_queue_consecutive_wait = 0;
790 netw->so_reuseaddr = -1;
791 // netw -> so_reuseport = -1 ;
792 netw->so_keepalive = -1;
793 netw->tcpnodelay = -1;
794 netw->so_sndbuf = -1;
795 netw->so_rcvbuf = -1;
796 netw->so_rcvtimeo = -1;
797 netw->so_sndtimeo = -1;
798 netw->so_linger = -1;
799 netw->deplete_socket_timeout = -1;
800 netw->deplete_queues_timeout = -1;
801 netw->wait_close_timeout = -1;
802 netw->transport_type = NETWORK_TCP;
803 netw->crypto_algo = NETW_ENCRYPT_NONE;
804
805 netw->send_data = &send_data;
806 netw->recv_data = &recv_data;
807 netw->send_data_once = &send_data_once;
808 netw->recv_data_once = &recv_data_once;
809
810#ifdef HAVE_OPENSSL
811 netw->method = NULL;
812 netw->ctx = NULL;
813 netw->ssl = NULL;
814 netw->key = NULL;
815 netw->certificate = NULL;
816#endif
817
818 netw->link.is_blocking = 1;
819 return netw;
820
821} /* netw_new() */
822
828char* get_in_addr(struct sockaddr* sa) {
829 return sa->sa_family == AF_INET
830 ? (char*)&(((struct sockaddr_in*)sa)->sin_addr)
831 : (char*)&(((struct sockaddr_in6*)sa)->sin6_addr);
832}
833
841int netw_init_wsa(int mode, int v1, int v2) {
842 int compiler_warning_suppressor = 0;
843#if !defined(__linux__) && !defined(__sun) && !defined(_AIX)
844 static WSADATA WSAdata; /*WSA world*/
845 static int WSA_IS_INITIALIZED = 0; /*status checking*/
846
847 switch (mode) {
848 default:
849 /*returning WSA status*/
850 case 2:
851 return WSA_IS_INITIALIZED;
852 break;
853 /*loading WSA dll*/
854 case 1:
855 if (WSA_IS_INITIALIZED == 1)
856 return TRUE; /*already loaded*/
857 if ((WSAStartup(MAKEWORD(v1, v2), &WSAdata)) != 0) {
858 WSA_IS_INITIALIZED = 0;
859 return FALSE;
860 } else {
861 WSA_IS_INITIALIZED = 1;
862 return TRUE;
863 }
864 break;
865 /*unloading (closing) WSA */
866 case 0:
867 if (WSA_IS_INITIALIZED == 0)
868 return TRUE; /*already CLEANED or not loaded */
869 if (WSACleanup() == 0) {
870 WSA_IS_INITIALIZED = 0;
871 return TRUE;
872 }
873 break;
874 } /*switch(...)*/
875#endif /* ifndef __linux__ __sun _AIX */
876 compiler_warning_suppressor = mode + v1 + v2;
877 (void)compiler_warning_suppressor;
878 compiler_warning_suppressor = TRUE;
879 return compiler_warning_suppressor;
880} /*netw_init_wsa(...)*/
881
894 __n_assert(netw, return FALSE);
895 if (mode != NETW_COMPRESS_NONE &&
898 n_log(LOG_ERR, "netw_set_compression_mode: unknown mode %d", mode);
899 return FALSE;
900 }
901 netw->compress_mode = mode;
902 return TRUE;
903} /* netw_set_compression_mode */
904
911int netw_set_blocking(NETWORK* netw, unsigned long int is_blocking) {
912 __n_assert(netw, return FALSE);
913
914 int error = 0;
915 (void)error;
916 char* errmsg = NULL;
917
918#if defined(__linux__) || defined(__sun)
919 int flags = 0;
920 flags = fcntl(netw->link.sock, F_GETFL, 0);
921 if (netw->link.is_blocking != 0 && !is_blocking) {
922 if (flags & O_NONBLOCK) {
923 n_log(LOG_DEBUG, "socket %d was already in non-blocking mode", netw->link.sock);
924 /* in case we missed it, let's update the link mode */
925 netw->link.is_blocking = 0;
926 return TRUE;
927 }
928 } else if (netw->link.is_blocking != 1 && is_blocking) {
929 if (!(flags & O_NONBLOCK)) {
930 n_log(LOG_DEBUG, "socket %d was already in blocking mode", netw->link.sock);
931 /* in case we missed it, let's update the link mode */
932 netw->link.is_blocking = 1;
933 return TRUE;
934 }
935 }
936 if (fcntl(netw->link.sock, F_SETFL, is_blocking ? flags & ~O_NONBLOCK : flags | O_NONBLOCK) == -1) {
937 error = neterrno;
938 errmsg = netstrerror(error);
939 _netw_capture_error(netw, "couldn't set blocking mode %lu on %d: %s", is_blocking, netw->link.sock, _str(errmsg));
940 n_log(LOG_ERR, "couldn't set blocking mode %lu on %d: %s", is_blocking, netw->link.sock, _str(errmsg));
941 FreeNoLog(errmsg);
942 return FALSE;
943 }
944#else
945 unsigned long int blocking = 1 - is_blocking;
946 int res = ioctlsocket(netw->link.sock, (long)FIONBIO, &blocking);
947 error = neterrno;
948 if (res != NO_ERROR) {
949 errmsg = netstrerror(error);
950 _netw_capture_error(netw, "ioctlsocket failed with error: %d , neterrno: %s", res, _str(errmsg));
951 n_log(LOG_ERR, "ioctlsocket failed with error: %d , neterrno: %s", res, _str(errmsg));
952 FreeNoLog(errmsg);
954 return FALSE;
955 }
956#endif
957 netw->link.is_blocking = is_blocking;
958 return TRUE;
959} /* netw_set_blocking */
960
969int netw_setsockopt(NETWORK* netw, int optname, int value) {
970 __n_assert(netw, return FALSE);
971
972 int error = 0;
973 char* errmsg = NULL;
974
975 switch (optname) {
976 case TCP_NODELAY:
977 if (value >= 0) {
978 /* disable naggle algorithm */
979 if (setsockopt(netw->link.sock, IPPROTO_TCP, TCP_NODELAY, (const char*)&value, sizeof(value)) == -1) {
980 error = neterrno;
981 errmsg = netstrerror(error);
982 _netw_capture_error(netw, "Error from setsockopt(TCP_NODELAY) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
983 n_log(LOG_ERR, "Error from setsockopt(TCP_NODELAY) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
984 FreeNoLog(errmsg);
985 return FALSE;
986 }
987 }
988 netw->tcpnodelay = value;
989 break;
990 case SO_SNDBUF:
991 /* socket sending buffer size */
992 if (value >= 0) {
993 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_SNDBUF, (const char*)&value, sizeof(value)) == -1) {
994 error = neterrno;
995 errmsg = netstrerror(error);
996 _netw_capture_error(netw, "Error from setsockopt(SO_SNDBUF) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
997 n_log(LOG_ERR, "Error from setsockopt(SO_SNDBUF) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
998 FreeNoLog(errmsg);
999 return FALSE;
1000 }
1001 }
1002 netw->so_sndbuf = value;
1003 break;
1004 case SO_RCVBUF:
1005 /* socket receiving buffer */
1006 if (value >= 0) {
1007 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_RCVBUF, (const char*)&value, sizeof(value)) == -1) {
1008 error = neterrno;
1009 errmsg = netstrerror(error);
1010 _netw_capture_error(netw, "Error from setsockopt(SO_RCVBUF) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1011 n_log(LOG_ERR, "Error from setsockopt(SO_RCVBUF) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1012 FreeNoLog(errmsg);
1013 return FALSE;
1014 }
1015 }
1016 netw->so_rcvbuf = value;
1017 break;
1018 case SO_REUSEADDR:
1019 /* lose the pesky "Address already in use" error message*/
1020 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof(value)) == -1) {
1021 error = neterrno;
1022 errmsg = netstrerror(error);
1023 _netw_capture_error(netw, "Error from setsockopt(SO_REUSEADDR) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1024 n_log(LOG_ERR, "Error from setsockopt(SO_REUSEADDR) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1025 FreeNoLog(errmsg);
1026 return FALSE;
1027 }
1028 netw->so_reuseaddr = value;
1029 break;
1030 /*case SO_REUSEPORT :
1031 // lose the pesky "port already in use" error message
1032 if ( setsockopt( netw -> link . sock, SOL_SOCKET, SO_REUSEPORT, (char *)&value, sizeof( value ) ) == -1 )
1033 {
1034 error=neterrno ;
1035 errmsg = netstrerror( error );
1036 n_log( LOG_ERR, "Error from setsockopt(SO_REUSEPORT) on socket %d. neterrno: %s", netw -> link . sock, _str( errmsg ) );
1037 FreeNoLog( errmsg );
1038 return FALSE ;
1039 }
1040 netw -> so_reuseport = value ;
1041 break ;*/
1042 case SO_LINGER: {
1043 struct linger ling;
1044 if (value < 0) {
1045 ling.l_onoff = 0;
1046 ling.l_linger = 0;
1047 } else if (value == 0) {
1048 ling.l_onoff = 1;
1049 ling.l_linger = 0;
1050 } else {
1051 ling.l_onoff = 1;
1052#ifdef __windows__
1053 ling.l_linger = (u_short)value;
1054#else
1055 ling.l_linger = value;
1056#endif
1057 }
1058#ifndef __windows__
1059 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)) == -1) {
1060 error = neterrno;
1061 errmsg = netstrerror(error);
1062 _netw_capture_error(netw, "Error from setsockopt(SO_LINGER) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1063 n_log(LOG_ERR, "Error from setsockopt(SO_LINGER) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1064 FreeNoLog(errmsg);
1065 return FALSE;
1066 }
1067#else
1068 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_LINGER, (const char*)&ling, sizeof(ling)) == -1) {
1069 error = neterrno;
1070 errmsg = netstrerror(error);
1071 _netw_capture_error(netw, "Error from setsockopt(SO_LINGER) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1072 n_log(LOG_ERR, "Error from setsockopt(SO_LINGER) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1073 FreeNoLog(errmsg);
1074 return FALSE;
1075 }
1076#endif // __windows__
1077 netw->so_linger = value;
1078 } break;
1079 case SO_RCVTIMEO:
1080 if (value >= 0) {
1081#ifndef __windows__
1082 {
1083 struct timeval tv;
1084 tv.tv_sec = value;
1085 tv.tv_usec = 0;
1086 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv) == -1) {
1087 error = neterrno;
1088 errmsg = netstrerror(error);
1089 _netw_capture_error(netw, "Error from setsockopt(SO_RCVTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1090 n_log(LOG_ERR, "Error from setsockopt(SO_RCVTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1091 FreeNoLog(errmsg);
1092 return FALSE;
1093 }
1094 }
1095#else
1096 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&value, sizeof value) == -1) {
1097 error = neterrno;
1098 errmsg = netstrerror(error);
1099 _netw_capture_error(netw, "Error from setsockopt(SO_RCVTIMEO) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1100 n_log(LOG_ERR, "Error from setsockopt(SO_RCVTIMEO) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1101 FreeNoLog(errmsg);
1102 return FALSE;
1103 }
1104#endif
1105 }
1106 netw->so_rcvtimeo = value;
1107 break;
1108 case SO_SNDTIMEO:
1109 if (value >= 0) {
1110#ifndef __windows__
1111 {
1112 struct timeval tv;
1113 tv.tv_sec = value;
1114 tv.tv_usec = 0;
1115
1116 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof tv) == -1) {
1117 error = neterrno;
1118 errmsg = netstrerror(error);
1119 _netw_capture_error(netw, "Error from setsockopt(SO_SNDTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1120 n_log(LOG_ERR, "Error from setsockopt(SO_SNDTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1121 FreeNoLog(errmsg);
1122 return FALSE;
1123 }
1124 }
1125#else
1126 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&value, sizeof value) == -1) {
1127 error = neterrno;
1128 errmsg = netstrerror(error);
1129 _netw_capture_error(netw, "Error from setsockopt(SO_SNDTIMEO) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1130 n_log(LOG_ERR, "Error from setsockopt(SO_SNDTIMEO) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1131 FreeNoLog(errmsg);
1132 return FALSE;
1133 }
1134#endif
1135 }
1136 netw->so_sndtimeo = value;
1137 break;
1139 netw->deplete_socket_timeout = value;
1140 break;
1142 netw->deplete_queues_timeout = value;
1143 break;
1145 netw->wait_close_timeout = value;
1146 break;
1148 netw->send_queue_consecutive_wait = value;
1149 break;
1150#ifdef __linux__
1151 case TCP_USER_TIMEOUT:
1152 if (value >= 0) {
1153 if (setsockopt(netw->link.sock, IPPROTO_TCP, TCP_USER_TIMEOUT, (const char*)&value, sizeof value) == -1) {
1154 error = neterrno;
1155 errmsg = netstrerror(error);
1156 _netw_capture_error(netw, "Error from setsockopt(TCP_USER_TIMEOUT) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1157 n_log(LOG_ERR, "Error from setsockopt(TCP_USER_TIMEOUT) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1158 FreeNoLog(errmsg);
1159 return FALSE;
1160 }
1161 }
1162 break;
1163 case TCP_QUICKACK:
1164 if (setsockopt(netw->link.sock, IPPROTO_TCP, TCP_QUICKACK, &value, sizeof(value)) < 0) {
1165 error = neterrno;
1166 errmsg = netstrerror(error);
1167 _netw_capture_error(netw, "Error setting setsockopt(TCP_QUICKACK) to %d on sock %d. neterrno: %s", value, netw->link.sock, _str(errmsg));
1168 n_log(LOG_ERR, "Error setting setsockopt(TCP_QUICKACK) to %d on sock %d. neterrno: %s", value, netw->link.sock, _str(errmsg));
1169 FreeNoLog(errmsg);
1170 return FALSE;
1171 }
1172 break;
1173#endif
1174 case SO_KEEPALIVE:
1175 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_KEEPALIVE, (const char*)&value, sizeof value) == -1) {
1176 error = neterrno;
1177 errmsg = netstrerror(error);
1178 _netw_capture_error(netw, "Error from setsockopt(SO_KEEPALIVE) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1179 n_log(LOG_ERR, "Error from setsockopt(SO_KEEPALIVE) on socket " SOCKET_SIZE_FORMAT ". neterrno: %s", netw->link.sock, _str(errmsg));
1180 FreeNoLog(errmsg);
1181 return FALSE;
1182 }
1183 netw->so_keepalive = value;
1184 break;
1185
1186 default:
1187 _netw_capture_error(netw, "%d is not a supported setsockopt", optname);
1188 n_log(LOG_ERR, "%d is not a supported setsockopt", optname);
1189 return FALSE;
1190 }
1191 return TRUE;
1192} /* netw_set_sock_opt */
1193
1194#ifdef HAVE_OPENSSL
1195
1201 BIO* bio = BIO_new(BIO_s_mem());
1202 if (!bio) {
1203 return NULL;
1204 }
1205
1206 ERR_print_errors(bio); // Write errors to the BIO
1207
1208 char* buf;
1209 size_t len = (size_t)BIO_get_mem_data(bio, &buf); // Get data from the BIO. Can return 0 if empty of failled
1210
1211 // Allocate memory for the error string and copy it
1212 char* error_str = malloc(len + 1);
1213 if (error_str) {
1214 memcpy(error_str, buf, len);
1215 error_str[len] = '\0'; // Null-terminate the string
1216 }
1217
1218 BIO_free(bio); // Free the BIO
1219
1220 return error_str;
1221}
1222
1232 unsigned long error = 0;
1233 while ((error = ERR_get_error())) {
1234 int level = LOG_ERR;
1235#ifdef SSL_R_UNEXPECTED_EOF_WHILE_READING
1236 /* A peer that closes the TCP connection without a TLS close_notify makes
1237 OpenSSL 3.x queue "unexpected eof while reading". Clients (web browsers
1238 especially, on speculative/preconnect sockets) do this routinely, so it
1239 is expected disconnect noise rather than an error: log it at DEBUG so it
1240 does not drown a real TLS failure, which keeps its own reason code and
1241 LOG_ERR level. */
1242 if (ERR_GET_REASON(error) == SSL_R_UNEXPECTED_EOF_WHILE_READING)
1243 level = LOG_DEBUG;
1244#endif
1245 n_log(level, "socket %d: %s", socket, ERR_reason_error_string(error));
1246 }
1247}
1248
1249/***************************************************************************
1250 * _ _ ____ _
1251 * Project ___| | | | _ \| |
1252 * / __| | | | |_) | |
1253 * | (__| |_| | _ <| |___
1254 * \___|\___/|_| \_\_____|
1255 *
1256 * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel at haxx.se>, et al.
1257 *
1258 * This software is licensed as described in the file COPYING, which
1259 * you should have received as part of this distribution. The terms
1260 * are also available at https://curl.haxx.se/docs/copyright.html.
1261 *
1262 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
1263 * copies of the Software, and permit persons to whom the Software is
1264 * furnished to do so, under the terms of the COPYING file.
1265 *
1266 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
1267 * KIND, either express or implied.
1268 *
1269 ***************************************************************************/
1270/* <DESC>
1271 * Show the required mutex callback setups for GnuTLS and OpenSSL when using
1272 * libcurl multi-threaded.
1273 * </DESC>
1274 */
1275/* A multi-threaded example that uses pthreads and fetches 4 remote files at
1276 * once over HTTPS. The lock callbacks and stuff assume OpenSSL <1.1 or GnuTLS
1277 * (libgcrypt) so far.
1278 *
1279 * OpenSSL docs for this:
1280 * https://www.openssl.org/docs/man1.0.2/man3/CRYPTO_num_locks.html
1281 * gcrypt docs for this:
1282 * https://gnupg.org/documentation/manuals/gcrypt/Multi_002dThreading.html
1283 */
1284
1285/* we have this global to let the callback get easy access to it */
1286static pthread_mutex_t* netw_ssl_lockarray;
1287
1288__attribute__((unused)) static void netw_ssl_lock_callback(int mode, int type, char* file, int line) {
1289 (void)file;
1290 (void)line;
1291 if (mode & CRYPTO_LOCK) {
1292 pthread_mutex_lock(&(netw_ssl_lockarray[type]));
1293 } else {
1294 pthread_mutex_unlock(&(netw_ssl_lockarray[type]));
1295 }
1296}
1297
1298__attribute__((unused)) static unsigned long thread_id(void) {
1299 unsigned long ret;
1300
1301 ret = (unsigned long)pthread_self();
1302 return ret;
1303}
1304
1305static void netw_init_locks(void) {
1306 int i;
1307
1308 size_t lock_count = (size_t)CRYPTO_num_locks();
1309 netw_ssl_lockarray = (pthread_mutex_t*)OPENSSL_malloc((size_t)(lock_count * sizeof(pthread_mutex_t)));
1310
1311 for (i = 0; i < CRYPTO_num_locks(); i++) {
1312 pthread_mutex_init(&(netw_ssl_lockarray[i]), NULL);
1313 }
1314
1315 CRYPTO_set_id_callback((unsigned long (*)())thread_id);
1316 CRYPTO_set_locking_callback((void (*)())netw_ssl_lock_callback);
1317}
1318
1319static void netw_kill_locks(void) {
1320 int i;
1321
1322 CRYPTO_set_locking_callback(NULL);
1323 for (i = 0; i < CRYPTO_num_locks(); i++)
1324 pthread_mutex_destroy(&(netw_ssl_lockarray[i]));
1325
1326 OPENSSL_free(netw_ssl_lockarray);
1327}
1328
1330
1336 if (OPENSSL_IS_INITIALIZED == 1)
1337 return TRUE; /*already loaded*/
1338
1339 SSL_library_init();
1340 SSL_load_error_strings();
1341 // Before OpenSSL 1.1.0 (< 0x10100000L): ERR_load_BIO_strings(); was required to load error messages for BIO functions
1342#if OPENSSL_VERSION_NUMBER < 0x10100000L
1343 ERR_load_BIO_strings();
1344#endif
1345 OpenSSL_add_all_algorithms();
1347
1348#ifndef __windows__
1349 /* The raw send paths pass MSG_NOSIGNAL (NETFLAGS),
1350 * but OpenSSL's internal write(2) does not: a TLS peer that
1351 * disconnects mid-SSL_write delivers SIGPIPE and kills the whole
1352 * process (server, client, or test alike). Ignoring it here turns
1353 * that into the EPIPE errno the SSL error paths already handle.
1354 * Standard practice for OpenSSL applications; scoped to TLS users
1355 * since this only runs from netw_init_openssl. */
1356 signal(SIGPIPE, SIG_IGN);
1357#endif
1358
1360
1361 return TRUE;
1362} /*netw_init_openssl(...)*/
1363
1369 if (OPENSSL_IS_INITIALIZED == 0)
1370 return TRUE; /*already unloaded*/
1371
1373 EVP_cleanup();
1374
1376
1377 return TRUE;
1378} /*netw_unload_openssl(...)*/
1379
1380/* Release the half-built SSL context left by a failed netw_set_crypto() or
1381 * netw_set_crypto_pem(). Those functions only set crypto_algo once the whole
1382 * setup succeeded, so netw_close() would not recognise the ctx as owned and it
1383 * would leak. Call this on every failure path taken after SSL_CTX_new(). */
1385 __n_assert(netw, return);
1386 if (netw->ctx) {
1387 SSL_CTX_free(netw->ctx);
1388 netw->ctx = NULL;
1389 }
1390}
1391
1399int netw_set_crypto(NETWORK* netw, char* key, char* certificate) {
1400 __n_assert(netw, return FALSE);
1401
1402 char* tmp_key = NULL;
1403 char* tmp_cert = NULL;
1404 if (key && strlen(key) > 0) {
1405 tmp_key = strdup(key);
1406 if (!tmp_key) {
1407 n_log(LOG_ERR, "strdup failed for key in netw_set_crypto");
1408 return FALSE;
1409 }
1410 }
1411 if (certificate && strlen(certificate) > 0) {
1412 tmp_cert = strdup(certificate);
1413 if (!tmp_cert) {
1414 n_log(LOG_ERR, "strdup failed for certificate in netw_set_crypto");
1415 FreeNoLog(tmp_key);
1416 return FALSE;
1417 }
1418 }
1419 if (tmp_key) {
1420 FreeNoLog(netw->key);
1421 netw->key = tmp_key;
1422 }
1423 if (tmp_cert) {
1424 FreeNoLog(netw->certificate);
1425 netw->certificate = tmp_cert;
1426 }
1427 if (key && certificate) {
1429#if OPENSSL_VERSION_NUMBER >= 0x10100000L // OpenSSL 1.1.0 or later
1430 netw->method = TLS_method(); // create new server-method instance
1431#else
1432 netw->method = TLSv1_2_method(); // create new server-method instance
1433#endif
1434 netw->ctx = SSL_CTX_new(netw->method); // create new context from method
1435 // SSL_CTX_set_verify(netw -> ctx, SSL_VERIFY_PEER, NULL); // Enable certificate verification
1436
1437 if (netw->ctx == NULL) {
1438 netw_ssl_print_errors(netw->link.sock);
1439 return FALSE;
1440 }
1441 /* PARTIAL_WRITE: a short SSL_write can be resumed from the
1442 * caller's buffer+offset (required by the reactor's
1443 * single-attempt send path); MOVING_WRITE_BUFFER: tolerate the
1444 * resume pointer not being the original one. No-op for the
1445 * blocking thread engine. */
1446 SSL_CTX_set_mode(netw->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
1447
1448#if OPENSSL_VERSION_NUMBER >= 0x10101000L
1449 /* 2026-07-18: send ZERO TLS 1.3 session tickets. The threaded
1450 * client engine runs SSL_read (recv thread) and SSL_write (send
1451 * thread) on the same SSL*; OpenSSL does not support concurrent
1452 * use, and the one window where the read path MUTATES shared
1453 * session state is post-handshake NewSessionTicket processing,
1454 * which lands at the exact moment the client fires its first
1455 * application write (HELLO). On low-RTT links (loopback / LAN)
1456 * the overlap corrupts the client's outbound record stream and
1457 * the server dies with "decryption failed or bad record mac" on
1458 * its FIRST read (torn records can also sit unreadable for
1459 * seconds until more bytes complete them). Nothing here uses TLS
1460 * session resumption, so tickets are pure race surface, drop
1461 * them at the source. Higher-RTT links (the 8 ms VPS) rarely hit
1462 * the window, which is why this only surfaced on local servers. */
1463 SSL_CTX_set_num_tickets(netw->ctx, 0);
1464#endif
1465
1466 // Load default system certs
1467 if (SSL_CTX_load_verify_locations(netw->ctx, NULL, "/etc/ssl/certs/") != 1) {
1468 netw_ssl_print_errors(netw->link.sock);
1470 return FALSE;
1471 }
1472
1473 if (SSL_CTX_use_certificate_file(netw->ctx, certificate, SSL_FILETYPE_PEM) <= 0) {
1474 netw_ssl_print_errors(netw->link.sock);
1476 return FALSE;
1477 }
1478 if (SSL_CTX_use_PrivateKey_file(netw->ctx, key, SSL_FILETYPE_PEM) <= 0) {
1479 netw_ssl_print_errors(netw->link.sock);
1481 return FALSE;
1482 }
1483
1484 netw->send_data = &send_ssl_data;
1485 netw->recv_data = &recv_ssl_data;
1486 netw->send_data_once = &send_ssl_data_once;
1487 netw->recv_data_once = &recv_ssl_data_once;
1488
1489 netw->crypto_algo = NETW_ENCRYPT_OPENSSL;
1490 }
1491
1492 return TRUE;
1493} /* netw_set_crypto */
1494
1502int netw_set_crypto_pem(NETWORK* netw, const char* key_pem, const char* cert_pem) {
1503 __n_assert(netw, return FALSE);
1504 __n_assert(key_pem, return FALSE);
1505 __n_assert(cert_pem, return FALSE);
1506
1508#if OPENSSL_VERSION_NUMBER >= 0x10100000L
1509 netw->method = TLS_method();
1510#else
1511 netw->method = TLSv1_2_method();
1512#endif
1513 netw->ctx = SSL_CTX_new(netw->method);
1514 if (netw->ctx == NULL) {
1515 netw_ssl_print_errors(netw->link.sock);
1516 return FALSE;
1517 }
1518 /* PARTIAL_WRITE: a short SSL_write can be resumed from the
1519 * caller's buffer+offset (required by the reactor's
1520 * single-attempt send path); MOVING_WRITE_BUFFER: tolerate the
1521 * resume pointer not being the original one. No-op for the
1522 * blocking thread engine. */
1523 SSL_CTX_set_mode(netw->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
1524
1525#if OPENSSL_VERSION_NUMBER >= 0x10101000L
1526 /* 2026-07-18: zero TLS 1.3 session tickets; see the twin comment in
1527 * netw_set_crypto: post-handshake NewSessionTicket processing in the
1528 * client's recv thread races the send thread's first SSL_write on the
1529 * shared SSL* ("bad record mac" at connect on low-RTT links). No
1530 * consumer uses session resumption. */
1531 SSL_CTX_set_num_tickets(netw->ctx, 0);
1532#endif
1533
1534 /* Load certificate from PEM string */
1535 BIO* cert_bio = BIO_new_mem_buf(cert_pem, -1);
1536 if (!cert_bio) {
1537 n_log(LOG_ERR, "Failed to create BIO for certificate PEM");
1539 return FALSE;
1540 }
1541 X509* cert = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL);
1542 BIO_free(cert_bio);
1543 if (!cert) {
1544 n_log(LOG_ERR, "Failed to parse certificate PEM");
1545 netw_ssl_print_errors(netw->link.sock);
1547 return FALSE;
1548 }
1549 if (SSL_CTX_use_certificate(netw->ctx, cert) <= 0) {
1550 X509_free(cert);
1551 netw_ssl_print_errors(netw->link.sock);
1553 return FALSE;
1554 }
1555 X509_free(cert);
1556
1557 /* Load private key from PEM string */
1558 BIO* key_bio = BIO_new_mem_buf(key_pem, -1);
1559 if (!key_bio) {
1560 n_log(LOG_ERR, "Failed to create BIO for key PEM");
1562 return FALSE;
1563 }
1564 EVP_PKEY* pkey = PEM_read_bio_PrivateKey(key_bio, NULL, NULL, NULL);
1565 BIO_free(key_bio);
1566 if (!pkey) {
1567 n_log(LOG_ERR, "Failed to parse private key PEM");
1568 netw_ssl_print_errors(netw->link.sock);
1570 return FALSE;
1571 }
1572 if (SSL_CTX_use_PrivateKey(netw->ctx, pkey) <= 0) {
1573 EVP_PKEY_free(pkey);
1574 netw_ssl_print_errors(netw->link.sock);
1576 return FALSE;
1577 }
1578 EVP_PKEY_free(pkey);
1579
1580 /* Verify that key matches certificate */
1581 if (!SSL_CTX_check_private_key(netw->ctx)) {
1582 n_log(LOG_ERR, "Private key does not match the certificate");
1584 return FALSE;
1585 }
1586
1587 netw->send_data = &send_ssl_data;
1588 netw->recv_data = &recv_ssl_data;
1589 netw->send_data_once = &send_ssl_data_once;
1590 netw->recv_data_once = &recv_ssl_data_once;
1591 netw->crypto_algo = NETW_ENCRYPT_OPENSSL;
1592
1593 return TRUE;
1594} /* netw_set_crypto_pem */
1595
1604int netw_set_crypto_chain(NETWORK* netw, char* key, char* certificate, char* ca_file) {
1605 __n_assert(netw, return FALSE);
1606 __n_assert(key, return FALSE);
1607 __n_assert(certificate, return FALSE);
1608 __n_assert(ca_file, return FALSE);
1609
1610 if (netw_set_crypto(netw, key, certificate) == FALSE) {
1611 return FALSE;
1612 }
1613
1614 /* Load the full certificate chain */
1615 if (SSL_CTX_use_certificate_chain_file(netw->ctx, certificate) <= 0) {
1616 n_log(LOG_ERR, "Failed to load certificate chain from %s", certificate);
1617 netw_ssl_print_errors(netw->link.sock);
1618 return FALSE;
1619 }
1620
1621 /* Load CA file for verification */
1622 if (SSL_CTX_load_verify_locations(netw->ctx, ca_file, NULL) != 1) {
1623 n_log(LOG_ERR, "Failed to load CA file %s", ca_file);
1624 netw_ssl_print_errors(netw->link.sock);
1625 return FALSE;
1626 }
1627
1628 return TRUE;
1629} /* netw_set_crypto_chain */
1630
1639int netw_set_crypto_chain_pem(NETWORK* netw, const char* key_pem, const char* cert_pem, const char* ca_pem) {
1640 __n_assert(netw, return FALSE);
1641 __n_assert(key_pem, return FALSE);
1642 __n_assert(cert_pem, return FALSE);
1643 __n_assert(ca_pem, return FALSE);
1644
1645 if (netw_set_crypto_pem(netw, key_pem, cert_pem) == FALSE) {
1646 return FALSE;
1647 }
1648
1649 /* Load CA certificate from PEM string into the trust store */
1650 BIO* ca_bio = BIO_new_mem_buf(ca_pem, -1);
1651 if (!ca_bio) {
1652 n_log(LOG_ERR, "Failed to create BIO for CA PEM");
1653 return FALSE;
1654 }
1655
1656 X509_STORE* store = SSL_CTX_get_cert_store(netw->ctx);
1657 if (!store) {
1658 BIO_free(ca_bio);
1659 n_log(LOG_ERR, "Failed to get certificate store from SSL context");
1660 return FALSE;
1661 }
1662
1663 X509* ca_cert = NULL;
1664 int ca_loaded = 0;
1665 while ((ca_cert = PEM_read_bio_X509(ca_bio, NULL, NULL, NULL)) != NULL) {
1666 if (X509_STORE_add_cert(store, ca_cert) != 1) {
1667 n_log(LOG_ERR, "Failed to add CA certificate to store");
1668 X509_free(ca_cert);
1669 BIO_free(ca_bio);
1670 return FALSE;
1671 }
1672 X509_free(ca_cert);
1673 ca_loaded++;
1674 }
1675 BIO_free(ca_bio);
1676
1677 if (ca_loaded == 0) {
1678 n_log(LOG_ERR, "No CA certificates were loaded from PEM string");
1679 return FALSE;
1680 }
1681
1682 /* Also load any extra chain certificates from the cert PEM */
1683 BIO* chain_bio = BIO_new_mem_buf(cert_pem, -1);
1684 if (chain_bio) {
1685 /* Skip the first certificate (already loaded as the leaf) */
1686 X509* skip_cert = PEM_read_bio_X509(chain_bio, NULL, NULL, NULL);
1687 if (skip_cert) {
1688 X509_free(skip_cert);
1689 }
1690 /* Load remaining certificates as chain intermediates */
1691 X509* chain_cert = NULL;
1692 while ((chain_cert = PEM_read_bio_X509(chain_bio, NULL, NULL, NULL)) != NULL) {
1693 if (SSL_CTX_add_extra_chain_cert(netw->ctx, chain_cert) != 1) {
1694 n_log(LOG_ERR, "Failed to add chain certificate");
1695 X509_free(chain_cert);
1696 }
1697 /* Note: SSL_CTX_add_extra_chain_cert takes ownership, so no X509_free on success */
1698 }
1699 BIO_free(chain_bio);
1700 }
1701
1702 return TRUE;
1703} /* netw_set_crypto_chain_pem */
1704
1705/* Install an in-memory certificate and key on a single SSL connection. Shared
1706 * by netw_set_crypto_pem_ctx and the SNI accept callback. Returns 0 on success,
1707 * -1 on error. */
1708static int _ssl_use_pem(SSL* ssl, const char* key_pem, const char* cert_pem) {
1709 BIO* cbio = BIO_new_mem_buf(cert_pem, -1);
1710 X509* cert = NULL;
1711 int rc = -1;
1712 if (!cbio)
1713 return -1;
1714 cert = PEM_read_bio_X509(cbio, NULL, NULL, NULL);
1715 BIO_free(cbio);
1716 if (!cert)
1717 return -1;
1718 if (SSL_use_certificate(ssl, cert) == 1) {
1719 BIO* kbio = BIO_new_mem_buf(key_pem, -1);
1720 if (kbio) {
1721 EVP_PKEY* pkey = PEM_read_bio_PrivateKey(kbio, NULL, NULL, NULL);
1722 BIO_free(kbio);
1723 if (pkey) {
1724 if (SSL_use_PrivateKey(ssl, pkey) == 1 && SSL_check_private_key(ssl) == 1)
1725 rc = 0;
1726 EVP_PKEY_free(pkey);
1727 }
1728 }
1729 }
1730 X509_free(cert);
1731 return rc;
1732}
1733
1740 __n_assert(netw, return NULL);
1741 if (!netw->ssl)
1742 return NULL;
1743 return SSL_get_servername(netw->ssl, TLSEXT_NAMETYPE_host_name);
1744}
1745
1753int netw_set_crypto_pem_ctx(NETWORK* netw, const N_STR* key_pem, const N_STR* cert_pem) {
1754 __n_assert(netw, return FALSE);
1755 __n_assert(netw->ssl, return FALSE);
1756 __n_assert(key_pem && key_pem->data, return FALSE);
1757 __n_assert(cert_pem && cert_pem->data, return FALSE);
1758 if (_ssl_use_pem(netw->ssl, key_pem->data, cert_pem->data) != 0) {
1759 netw_ssl_print_errors(netw->link.sock);
1760 return FALSE;
1761 }
1762 return TRUE;
1763}
1764
1765/* Arg handed (by stack pointer) to the servername and ALPN callbacks for the
1766 * duration of a single netw_accept_ssl_with_sni_cb call. */
1767typedef struct NETW_SNI_ARG {
1770 const char* const* alpn; /* protocols we accept, in server preference order */
1771 size_t alpn_count; /* how many, 0 when ALPN is not offered */
1772} NETW_SNI_ARG;
1773
1774/* OpenSSL ALPN select callback: fires during SSL_accept with the client's
1775 * protocol list (a series of length-prefixed names). Picks the first protocol
1776 * the server prefers that the client also offered. With no overlap it declines
1777 * (NOACK) rather than failing the handshake, so a client offering only protocols
1778 * we do not speak still connects and falls back to HTTP/1.1. */
1779static int _netw_alpn_select_cb(SSL* ssl, const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg) {
1780 NETW_SNI_ARG* a = (NETW_SNI_ARG*)arg;
1781 size_t i;
1782 (void)ssl;
1783 if (!a || !a->alpn || a->alpn_count == 0)
1784 return SSL_TLSEXT_ERR_NOACK;
1785 for (i = 0; i < a->alpn_count; i++) {
1786 size_t want = a->alpn[i] ? strlen(a->alpn[i]) : 0;
1787 unsigned int off = 0;
1788 if (want == 0 || want > 255)
1789 continue;
1790 while (off < inlen) {
1791 unsigned int l = in[off];
1792 if (l == 0 || off + 1 + l > inlen)
1793 break; /* malformed list: stop rather than read past it */
1794 if (l == want && memcmp(in + off + 1, a->alpn[i], want) == 0) {
1795 *out = in + off + 1;
1796 *outlen = (unsigned char)l;
1797 return SSL_TLSEXT_ERR_OK;
1798 }
1799 off += 1 + l;
1800 }
1801 }
1802 return SSL_TLSEXT_ERR_NOACK;
1803}
1804
1805/* Serialize a protocol list into the length-prefixed wire form ALPN uses.
1806 * Returns the bytes written, or 0 on a bad argument or an overflow. */
1807static size_t _netw_alpn_encode(const char* const* protos, size_t count, unsigned char* out, size_t out_cap) {
1808 size_t i, off = 0;
1809 if (!protos || count == 0 || !out)
1810 return 0;
1811 for (i = 0; i < count; i++) {
1812 size_t l = protos[i] ? strlen(protos[i]) : 0;
1813 if (l == 0 || l > 255 || off + 1 + l > out_cap)
1814 return 0;
1815 out[off++] = (unsigned char)l;
1816 memcpy(out + off, protos[i], l);
1817 off += l;
1818 }
1819 return off;
1820}
1821
1834int netw_ssl_set_alpn(NETWORK* netw, const char* const* protos, size_t count) {
1835 unsigned char wire[512];
1836 size_t len;
1837 __n_assert(netw, return FALSE);
1838 __n_assert(protos, return FALSE);
1839 __n_assert(netw->ctx, n_log(LOG_ERR, "SSL context not initialized, call netw_ssl_start_client first"); return FALSE);
1840 len = _netw_alpn_encode(protos, count, wire, sizeof(wire));
1841 if (len == 0) {
1842 n_log(LOG_ERR, "invalid ALPN protocol list");
1843 return FALSE;
1844 }
1845 if (SSL_CTX_set_alpn_protos(netw->ctx, wire, (unsigned int)len) != 0) {
1846 n_log(LOG_ERR, "OpenSSL refused the ALPN protocol list");
1847 return FALSE;
1848 }
1849 return TRUE;
1850}
1851
1864int netw_ssl_get_alpn(NETWORK* netw, char* out, size_t out_len) {
1865 const unsigned char* proto = NULL;
1866 unsigned int plen = 0;
1867 if (out && out_len > 0)
1868 out[0] = '\0';
1869 __n_assert(netw, return FALSE);
1870 __n_assert(out, return FALSE);
1871 if (!netw->ssl || out_len < 2)
1872 return FALSE;
1873 SSL_get0_alpn_selected(netw->ssl, &proto, &plen);
1874 if (!proto || plen == 0 || plen >= out_len)
1875 return FALSE;
1876 memcpy(out, proto, plen);
1877 out[plen] = '\0';
1878 return TRUE;
1879}
1880
1881/* OpenSSL servername callback: fires during SSL_accept once the ClientHello is
1882 * parsed. Picks a per-host certificate via the caller's pick() and installs it
1883 * on the current SSL so the handshake completes with a host-matching leaf. */
1884static int _netw_sni_servername_cb(SSL* ssl, int* al, void* arg) {
1885 NETW_SNI_ARG* a = (NETW_SNI_ARG*)arg;
1886 const char* sni;
1887 N_STR* cert_pem = NULL;
1888 N_STR* key_pem = NULL;
1889 int ok;
1890 (void)al;
1891 if (!a || !a->pick)
1892 return SSL_TLSEXT_ERR_OK;
1893 sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
1894 if (a->pick(sni, &cert_pem, &key_pem, a->user_data) != 0 || !cert_pem || !key_pem) {
1895 if (cert_pem)
1896 free_nstr(&cert_pem);
1897 if (key_pem)
1898 free_nstr(&key_pem);
1899 return SSL_TLSEXT_ERR_ALERT_FATAL;
1900 }
1901 ok = (_ssl_use_pem(ssl, key_pem->data, cert_pem->data) == 0);
1902 free_nstr(&cert_pem);
1903 free_nstr(&key_pem);
1904 return ok ? SSL_TLSEXT_ERR_OK : SSL_TLSEXT_ERR_ALERT_FATAL;
1905}
1906
1924 return netw_ssl_server_handshake_ex(netw, pick, user_data, NULL, 0);
1925}
1926
1947int netw_ssl_server_handshake_ex(NETWORK* netw, netw_sni_pick_cb pick, void* user_data, const char* const* alpn, size_t alpn_count) {
1948 NETW_SNI_ARG arg;
1949 __n_assert(netw, return FALSE);
1950 __n_assert(pick, return FALSE);
1951
1953#if OPENSSL_VERSION_NUMBER >= 0x10100000L
1954 netw->method = TLS_method();
1955#else
1956 netw->method = TLSv1_2_method();
1957#endif
1958 netw->ctx = SSL_CTX_new(netw->method);
1959 if (!netw->ctx) {
1960 netw_ssl_print_errors(netw->link.sock);
1961 return FALSE;
1962 }
1963 SSL_CTX_set_mode(netw->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
1964 SSL_CTX_set_tlsext_servername_callback(netw->ctx, _netw_sni_servername_cb);
1965
1966 /* The callback runs synchronously inside the blocking SSL_accept below, so a
1967 * stack-scoped arg is valid for its whole lifetime and needs no heap/free. */
1968 arg.pick = pick;
1969 arg.user_data = user_data;
1970 arg.alpn = alpn;
1971 arg.alpn_count = alpn ? alpn_count : 0;
1972 SSL_CTX_set_tlsext_servername_arg(netw->ctx, &arg);
1973 if (arg.alpn_count > 0)
1974 SSL_CTX_set_alpn_select_cb(netw->ctx, _netw_alpn_select_cb, &arg);
1975
1976 netw->ssl = SSL_new(netw->ctx);
1977 if (!netw->ssl) {
1978 netw_ssl_print_errors(netw->link.sock);
1979 return FALSE;
1980 }
1981 SSL_set_fd(netw->ssl, (int)netw->link.sock);
1982 netw->send_data = &send_ssl_data;
1983 netw->recv_data = &recv_ssl_data;
1984 netw->send_data_once = &send_ssl_data_once;
1985 netw->recv_data_once = &recv_ssl_data_once;
1986 netw->crypto_algo = NETW_ENCRYPT_OPENSSL;
1987
1988 if (SSL_accept(netw->ssl) <= 0) {
1989 netw_ssl_print_errors(netw->link.sock);
1990 return FALSE;
1991 }
1992 return TRUE;
1993}
1994
2008 NETWORK* client;
2009 __n_assert(listen, return NULL);
2010 __n_assert(pick, return NULL);
2011 client = netw_accept_from(listen);
2012 if (!client)
2013 return NULL;
2014 if (netw_ssl_server_handshake(client, pick, user_data) != TRUE) {
2015 netw_close(&client);
2016 return NULL;
2017 }
2018 return client;
2019}
2020
2028int netw_ssl_set_ca(NETWORK* netw, const char* ca_file, const char* ca_path) {
2029 __n_assert(netw, return FALSE);
2030 __n_assert(netw->ctx, n_log(LOG_ERR, "SSL context not initialized, call netw_set_crypto first"); return FALSE);
2031
2032 if (!ca_file && !ca_path) {
2033 n_log(LOG_ERR, "At least one of ca_file or ca_path must be specified");
2034 return FALSE;
2035 }
2036
2037 if (SSL_CTX_load_verify_locations(netw->ctx, ca_file, ca_path) != 1) {
2038 n_log(LOG_ERR, "Failed to load CA from file=%s path=%s", _str(ca_file), _str(ca_path));
2039 _netw_capture_error(netw, "Failed to load CA from file=%s path=%s", _str(ca_file), _str(ca_path));
2040 netw_ssl_print_errors(netw->link.sock);
2041 return FALSE;
2042 }
2043
2044 return TRUE;
2045} /* netw_ssl_set_ca */
2046
2054 __n_assert(netw, return FALSE);
2055 __n_assert(netw->ctx, n_log(LOG_ERR, "SSL context not initialized, call netw_set_crypto first"); return FALSE);
2056
2057 if (enable) {
2058 SSL_CTX_set_verify(netw->ctx, SSL_VERIFY_PEER, NULL);
2059 } else {
2060 SSL_CTX_set_verify(netw->ctx, SSL_VERIFY_NONE, NULL);
2061 }
2062
2063 return TRUE;
2064} /* netw_ssl_set_verify */
2065
2084// cppcheck-suppress constParameterPointer ; cppcheck does not parse the system OpenSSL headers, so OPENSSL_VERSION_NUMBER reads as 0 and only the 1.0.2 branch below is analysed, where netw is read-only. The >= 1.1.0 branch hands netw->ctx to SSL_CTX_set_min_proto_version(), which mutates it.
2085int netw_ssl_set_versions(NETWORK* netw, int min_version, int max_version) {
2086 __n_assert(netw, return FALSE);
2087 __n_assert(netw->ctx, n_log(LOG_ERR, "SSL context not initialized, call netw_set_crypto first"); return FALSE);
2088
2089#if N_OSSL_HAVE_PROTO_VERSION_SETTERS
2090 if (min_version != 0 && SSL_CTX_set_min_proto_version(netw->ctx, min_version) != 1) {
2091 n_log(LOG_ERR, "SSL_CTX_set_min_proto_version(%d) failed", min_version);
2092 return FALSE;
2093 }
2094 if (max_version != 0 && SSL_CTX_set_max_proto_version(netw->ctx, max_version) != 1) {
2095 n_log(LOG_ERR, "SSL_CTX_set_max_proto_version(%d) failed", max_version);
2096 return FALSE;
2097 }
2098#else
2099 /* SSL_CTX_set_min/max_proto_version() arrived in OpenSSL 1.1.0 (LibreSSL
2100 2.6). Without them the contexts this library builds come from
2101 TLSv1_2_method(), which is not version-flexible: TLS 1.2 is the only
2102 version they can ever speak.
2103 The only range that can be honoured is therefore one containing TLS 1.2.
2104 Refuse anything else rather than return TRUE and negotiate a version the
2105 caller did not ask for, which is the exact failure this call exists to
2106 prevent. */
2107 if (min_version != 0 && min_version > TLS1_2_VERSION) {
2108 n_log(LOG_ERR, "netw_ssl_set_versions: minimum %d is above TLS 1.2, the highest this OpenSSL supports", min_version);
2109 return FALSE;
2110 }
2111 if (max_version != 0 && max_version < TLS1_2_VERSION) {
2112 n_log(LOG_ERR, "netw_ssl_set_versions: maximum %d is below TLS 1.2, the only version this OpenSSL context speaks", max_version);
2113 return FALSE;
2114 }
2115#endif
2116 return TRUE;
2117} /* netw_ssl_set_versions */
2118
2135int netw_ssl_set_ciphers(NETWORK* netw, const char* cipher_list, const char* ciphersuites) {
2136 __n_assert(netw, return FALSE);
2137 __n_assert(netw->ctx, n_log(LOG_ERR, "SSL context not initialized, call netw_set_crypto first"); return FALSE);
2138
2139 if (cipher_list && cipher_list[0] && SSL_CTX_set_cipher_list(netw->ctx, cipher_list) != 1) {
2140 n_log(LOG_ERR, "SSL_CTX_set_cipher_list(%s) failed: no cipher matched", cipher_list);
2141 return FALSE;
2142 }
2143#if NETW_HAVE_TLS13
2144 if (ciphersuites && ciphersuites[0] && SSL_CTX_set_ciphersuites(netw->ctx, ciphersuites) != 1) {
2145 n_log(LOG_ERR, "SSL_CTX_set_ciphersuites(%s) failed", ciphersuites);
2146 return FALSE;
2147 }
2148#else
2149 /* SSL_CTX_set_ciphersuites() arrived with TLS 1.3 in OpenSSL 1.1.1 and only
2150 ever names TLS 1.3 suites. An OpenSSL that does not have the call cannot
2151 negotiate TLS 1.3 either, so there is no handshake for the string to
2152 restrict and nothing is silently left at a default: cipher_list above
2153 still governs every version this build can speak. Say so once and carry
2154 on, so a portable caller does not have to guard the argument. Callers
2155 that must know can test NETW_HAVE_TLS13. */
2156 if (ciphersuites && ciphersuites[0]) {
2157 n_log(LOG_DEBUG, "netw_ssl_set_ciphers: ignoring the TLS 1.3 ciphersuites '%s', this OpenSSL cannot negotiate TLS 1.3", ciphersuites);
2158 }
2159#endif
2160 return TRUE;
2161} /* netw_ssl_set_ciphers */
2162
2179int netw_ssl_get_verify_result(NETWORK* netw, const char* expected_host, char* errbuf, size_t errsz) {
2180 X509* cert = NULL;
2181 STACK_OF(X509)* chain = NULL;
2182 X509_STORE* store = NULL;
2183 X509_STORE_CTX* store_ctx = NULL;
2184 int chain_ok = 0;
2185 int host_ok = 1;
2186 int result = FALSE;
2187
2188 if (errbuf && errsz > 0)
2189 errbuf[0] = '\0';
2190 __n_assert(netw, return FALSE);
2191 __n_assert(netw->ssl, return FALSE);
2192
2194 if (!cert) {
2195 if (errbuf && errsz > 0)
2196 snprintf(errbuf, errsz, "no peer certificate");
2197 return FALSE;
2198 }
2199 chain = SSL_get_peer_cert_chain(netw->ssl);
2200
2201 store = X509_STORE_new();
2202 store_ctx = X509_STORE_CTX_new();
2203 if (!store || !store_ctx) {
2204 if (errbuf && errsz > 0)
2205 snprintf(errbuf, errsz, "out of memory");
2206 goto cleanup;
2207 }
2208 X509_STORE_set_default_paths(store);
2209 if (X509_STORE_CTX_init(store_ctx, store, cert, chain) != 1) {
2210 if (errbuf && errsz > 0)
2211 snprintf(errbuf, errsz, "verify init failed");
2212 goto cleanup;
2213 }
2214 if (X509_verify_cert(store_ctx) == 1) {
2215 chain_ok = 1;
2216 } else {
2217 int verr = X509_STORE_CTX_get_error(store_ctx);
2218 if (errbuf && errsz > 0)
2219 snprintf(errbuf, errsz, "%s", X509_verify_cert_error_string(verr));
2220 }
2221 if (expected_host && expected_host[0]) {
2222 if (X509_check_host(cert, expected_host, 0, 0, NULL) != 1) {
2223 host_ok = 0;
2224 if (errbuf && errsz > 0 && chain_ok)
2225 snprintf(errbuf, errsz, "hostname mismatch for %s", expected_host);
2226 }
2227 }
2228 result = (chain_ok && host_ok) ? TRUE : FALSE;
2229
2230cleanup:
2231 if (store_ctx)
2232 X509_STORE_CTX_free(store_ctx);
2233 if (store)
2234 X509_STORE_free(store);
2235 X509_free(cert);
2236 return result;
2237} /* netw_ssl_get_verify_result */
2238
2246int netw_ssl_set_client_cert(NETWORK* netw, const char* cert_file, const char* key_file) {
2247 __n_assert(netw, return FALSE);
2248 __n_assert(netw->ctx, n_log(LOG_ERR, "SSL context not initialized"); return FALSE);
2249 __n_assert(cert_file, return FALSE);
2250
2251 if (SSL_CTX_use_certificate_file(netw->ctx, cert_file, SSL_FILETYPE_PEM) != 1) {
2252 n_log(LOG_ERR, "Failed to load client certificate from %s", cert_file);
2253 _netw_capture_error(netw, "Failed to load client certificate from %s", cert_file);
2254 netw_ssl_print_errors(netw->link.sock);
2255 return FALSE;
2256 }
2257
2258 const char* kf = key_file ? key_file : cert_file;
2259 if (SSL_CTX_use_PrivateKey_file(netw->ctx, kf, SSL_FILETYPE_PEM) != 1) {
2260 n_log(LOG_ERR, "Failed to load client private key from %s", kf);
2261 _netw_capture_error(netw, "Failed to load client private key from %s", kf);
2262 netw_ssl_print_errors(netw->link.sock);
2263 return FALSE;
2264 }
2265
2266 if (SSL_CTX_check_private_key(netw->ctx) != 1) {
2267 n_log(LOG_ERR, "Client certificate and private key do not match");
2268 _netw_capture_error(netw, "Client certificate and private key do not match");
2269 netw_ssl_print_errors(netw->link.sock);
2270 return FALSE;
2271 }
2272
2273 return TRUE;
2274} /* netw_ssl_set_client_cert */
2275
2276#endif /* HAVE_OPENSSL */
2277
2280#define NETW_CONNECT_ABORT_POLL_MS 100
2281
2283static int (*_netw_connect_abort_cb)(void*) = NULL;
2285static void* _netw_connect_abort_ctx = NULL;
2286
2302static int _netw_timed_connect(NETWORK* netw, SOCKET sock, struct addrinfo* rp, int connect_timeout_ms) {
2303 if (connect_timeout_ms <= 0) {
2304 return connect(sock, rp->ai_addr, (socklen_t)rp->ai_addrlen);
2305 }
2306
2307 if (netw_set_blocking(netw, 0) == FALSE) {
2308 /* could not switch modes: fall back to a blocking connect */
2309 return connect(sock, rp->ai_addr, (socklen_t)rp->ai_addrlen);
2310 }
2311
2312 int rc = connect(sock, rp->ai_addr, (socklen_t)rp->ai_addrlen);
2313 if (rc == 0) {
2315 return 0;
2316 }
2317
2318 int in_progress = 0;
2319#ifdef __windows__
2320 if (neterrno == WSAEWOULDBLOCK || neterrno == WSAEINPROGRESS) in_progress = 1;
2321#else
2322 if (neterrno == EINPROGRESS) in_progress = 1;
2323#endif
2324 if (!in_progress) {
2326 return -1;
2327 }
2328
2329 /* select() blocks the thread in the kernel (no busy-wait). If a signal
2330 interrupts it (EINTR) we retry with the remaining time so the total
2331 wait still honours connect_timeout_ms. select() only rewrites the
2332 timeval argument on Linux, so the remaining time is tracked
2333 explicitly with N_TIME for portability (Windows/MinGW included).
2334 When a connect-abort callback is registered the per-select wait is
2335 capped to NETW_CONNECT_ABORT_POLL_MS so the loop wakes periodically to
2336 poll it, letting a caller interrupt a connect to an unresponsive host
2337 without waiting out the whole connect timeout. */
2338 N_TIME timer;
2339 start_HiTimer(&timer);
2340 time_t remaining_us = (time_t)connect_timeout_ms * 1000;
2341 time_t poll_us = _netw_connect_abort_cb ? (time_t)NETW_CONNECT_ABORT_POLL_MS * 1000 : 0;
2342 int connected = 0;
2343 int aborted = 0;
2344 for (;;) {
2346 aborted = 1;
2347 break;
2348 }
2349 /* Watch both the write set and the exception set: a completed
2350 connect signals writefds, but on Winsock a *failed* connect
2351 signals exceptfds (not writefds). Watching only writefds there
2352 would miss the failure and wait out the whole timeout. The
2353 SO_ERROR check after the loop is the final arbiter on both. */
2354 fd_set wset, eset;
2355 FD_ZERO(&wset);
2356 FD_SET(sock, &wset);
2357 FD_ZERO(&eset);
2358 FD_SET(sock, &eset);
2359 time_t wait_us = remaining_us;
2360 if (poll_us > 0 && wait_us > poll_us)
2361 wait_us = poll_us;
2362 struct timeval tv;
2363 tv.tv_sec = (long)(wait_us / 1000000);
2364 tv.tv_usec = (long)(wait_us % 1000000);
2365
2366 int sel = select((int)sock + 1, NULL, &wset, &eset, &tv);
2367 if (sel > 0) {
2368 connected = 1; /* socket signalled; SO_ERROR check decides below */
2369 break;
2370 }
2371 if (sel == 0) {
2372 /* the select() wait expired: subtract it from the remaining budget.
2373 If this was only a poll slice (an abort callback is registered) and
2374 time is left, loop to re-check the callback; otherwise the connect
2375 timeout has been reached. */
2376 remaining_us -= get_usec(&timer);
2377 if (remaining_us <= 0)
2378 break; /* connect timed out */
2379 continue;
2380 }
2381 /* sel < 0 */
2382#ifdef __windows__
2383 int interrupted = (neterrno == WSAEINTR);
2384#else
2385 int interrupted = (neterrno == EINTR);
2386#endif
2387 if (!interrupted) {
2388 break; /* genuine select() error */
2389 }
2390 /* interrupted by a signal: subtract the time already spent and
2391 retry only if some of the budget is left. */
2392 remaining_us -= get_usec(&timer);
2393 if (remaining_us <= 0) {
2394 break; /* budget exhausted while interrupted */
2395 }
2396 }
2397
2398 if (aborted || !connected) {
2400 return -1;
2401 }
2402
2403 int so_error = 0;
2404 socklen_t slen = sizeof(so_error);
2405 if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&so_error, &slen) == -1 || so_error != 0) {
2407 return -1;
2408 }
2409
2411 return 0;
2412} /* _netw_timed_connect */
2413
2430void netw_set_connect_abort_cb(int (*cb)(void* ctx), void* ctx) {
2433} /* netw_set_connect_abort_cb(...) */
2434
2448int netw_connect_ex_to(NETWORK** netw, char* host, char* port, size_t send_list_limit, size_t recv_list_limit, int ip_version, char* ssl_key_file, char* ssl_cert_file, int connect_timeout_ms) {
2449 // kill compilation warning when there is no openssl
2450 (void)ssl_key_file;
2451 (void)ssl_cert_file;
2452 int error = 0, net_status = 0;
2453 char* errmsg = NULL;
2454
2455 /*do not work over an already used netw*/
2456 if ((*netw)) {
2457 n_log(LOG_ERR, "Unable to allocate (*netw), already existing. You must use empty NETWORK *structs.");
2458 return FALSE;
2459 }
2460
2461 /*creating array*/
2462 (*netw) = netw_new(send_list_limit, recv_list_limit);
2463 __n_assert(netw && (*netw), return FALSE);
2464
2465 /*checking WSA when under windows*/
2466 if (netw_init_wsa(1, 2, 2) == FALSE) {
2467 n_log(LOG_ERR, "Unable to load WSA dll's");
2469 return FALSE;
2470 }
2471
2472 /* choose ip version */
2473 if (ip_version == NETWORK_IPV4) {
2474 (*netw)->link.hints.ai_family = AF_INET; /* Allow IPv4 */
2475 } else if (ip_version == NETWORK_IPV6) {
2476 (*netw)->link.hints.ai_family = AF_INET6; /* Allow IPv6 */
2477 } else {
2478 /* NETWORK_ALL or unknown value */
2479 (*netw)->link.hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
2480 }
2481
2482 (*netw)->link.hints.ai_socktype = SOCK_STREAM;
2483 (*netw)->link.hints.ai_protocol = IPPROTO_TCP;
2484 (*netw)->link.hints.ai_flags = AI_PASSIVE;
2485 (*netw)->link.hints.ai_canonname = NULL;
2486 (*netw)->link.hints.ai_next = NULL;
2487
2488 /* Time the resolve and connect phases so callers can present a
2489 connect-timing breakdown. get_usec returns the delta since the
2490 previous call, so the first reads the DNS phase and the second
2491 reads the TCP-connect phase. */
2492 N_TIME connect_timer;
2493 start_HiTimer(&connect_timer);
2494
2495 /* Note: on some system, i.e Solaris, it WILL show leak in getaddrinfo.
2496 * Testing it inside a 1,100 loop showed not effect on the amount of leaked
2497 * memory */
2498 error = getaddrinfo(host, port, &(*netw)->link.hints, &(*netw)->link.rhost);
2499 if (error != 0) {
2500 _netw_capture_error(*netw, "Error when resolving %s:%s getaddrinfo: %s", host, port, gai_strerror(error));
2501 n_log(LOG_ERR, "Error when resolving %s:%s getaddrinfo: %s", host, port, gai_strerror(error));
2502 _netw_capture_connect_error("DNS resolution failed for %s:%s: %s", host, port, gai_strerror(error));
2504 return FALSE;
2505 }
2506 (*netw)->addr_infos_loaded = 1;
2507 (*netw)->connect_dns_usec = (long long)get_usec(&connect_timer);
2508 Malloc((*netw)->link.ip, char, 64);
2509 __n_assert((*netw)->link.ip, netw_close(netw); return FALSE);
2510
2511 /* getaddrinfo() returns a list of address structures. Try each address until we successfully connect. If socket or connect fails, we close the socket and try the next address. */
2512 struct addrinfo* rp = NULL;
2513 for (rp = (*netw)->link.rhost; rp != NULL; rp = rp->ai_next) {
2514 SOCKET sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
2515 if (sock == INVALID_SOCKET) {
2516 error = neterrno;
2517 errmsg = netstrerror(error);
2518 n_log(LOG_ERR, "Error while trying to make a socket: %s", _str(errmsg));
2519 FreeNoLog(errmsg);
2520 continue;
2521 }
2522
2523 (*netw)->link.sock = sock;
2524
2525 net_status = _netw_timed_connect(*netw, sock, rp, connect_timeout_ms);
2526 if (net_status == -1) {
2527 error = neterrno;
2528 errmsg = netstrerror(error);
2529 n_log(LOG_INFO, "connecting to %s:%s : %s", host, port, _str(errmsg));
2530 FreeNoLog(errmsg);
2531 closesocket(sock);
2532 (*netw)->link.sock = INVALID_SOCKET;
2533 continue;
2534 } else {
2535 /*storing connected port and ip address*/
2536 if (!inet_ntop(rp->ai_family, get_in_addr(rp->ai_addr), (*netw)->link.ip, 64)) {
2537 error = neterrno;
2538 errmsg = netstrerror(error);
2539 n_log(LOG_ERR, "inet_ntop: %p , %s", rp, _str(errmsg));
2540 FreeNoLog(errmsg);
2541 }
2542 break; /* Success */
2543 }
2544 }
2545 if (rp == NULL) {
2546 /* No address succeeded */
2547 _netw_capture_error(*netw, "Couldn't connect to %s:%s : no address succeeded", host, port);
2548 n_log(LOG_ERR, "Couldn't connect to %s:%s : no address succeeded", host, port);
2549 _netw_capture_connect_error("TCP connect failed for %s:%s: no address succeeded", host, port);
2551 return FALSE;
2552 }
2553 (*netw)->connect_tcp_usec = (long long)get_usec(&connect_timer);
2554
2555 (*netw)->link.port = strdup(port);
2556 __n_assert((*netw)->link.port, netw_close(netw); return FALSE);
2557
2558 if (ssl_key_file && ssl_cert_file) {
2559#ifdef HAVE_OPENSSL
2560 if (netw_set_crypto((*netw), ssl_key_file, ssl_cert_file) == FALSE) {
2561 /* could not initialize SSL */
2562 n_log(LOG_ERR, "couldn't initialize SSL !");
2563 _netw_capture_error(*netw, "SSL initialization failed");
2565 return FALSE;
2566 }
2567
2568 (*netw)->ssl = SSL_new((*netw)->ctx);
2569 SSL_set_fd((*netw)->ssl, (int)(*netw)->link.sock);
2570
2571 // Perform SSL Handshake
2572 if (SSL_connect((*netw)->ssl) <= 0) {
2573 /* could not connect with SSL */
2574 n_log(LOG_ERR, "SSL Handshake error !");
2575 _netw_capture_error(*netw, "SSL handshake failed for %s:%s", (*netw)->link.ip, (*netw)->link.port);
2577 return FALSE;
2578 }
2579 n_log(LOG_DEBUG, "SSL-Connected to %s:%s", (*netw)->link.ip, (*netw)->link.port);
2580#else
2581 _netw_capture_error(*netw, "%s:%s trying to configure SSL but application was compiled without SSL support !", (*netw)->link.ip, (*netw)->link.port);
2582 n_log(LOG_ERR, "%s:%s trying to configure SSL but application was compiled without SSL support !", (*netw)->link.ip, (*netw)->link.port);
2583#endif
2584 } else {
2585 n_log(LOG_DEBUG, "Connected to %s:%s", (*netw)->link.ip, (*netw)->link.port);
2586 }
2587
2589
2590 return TRUE;
2591} /* netw_connect_ex_to(...)*/
2592
2604 while (netw->link.connect_rp) {
2605 struct addrinfo* rp = netw->link.connect_rp;
2606 SOCKET sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
2607 if (sock == INVALID_SOCKET) {
2608 netw->link.connect_rp = rp->ai_next;
2609 continue;
2610 }
2611 netw->link.sock = sock;
2612 if (netw_set_blocking(netw, 0) == FALSE) {
2613 closesocket(sock);
2614 netw->link.sock = INVALID_SOCKET;
2615 netw->link.connect_rp = rp->ai_next;
2616 continue;
2617 }
2618 if (connect(sock, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0)
2619 return TRUE; /* connected immediately (common on loopback) */
2620#ifdef __windows__
2621 if (neterrno == WSAEWOULDBLOCK || neterrno == WSAEINPROGRESS)
2622#else
2623 if (neterrno == EINPROGRESS)
2624#endif
2625 return TRUE; /* connect in flight: poll the socket for writability */
2626 /* hard failure on this address: close and try the next one */
2627 closesocket(sock);
2628 netw->link.sock = INVALID_SOCKET;
2629 netw->link.connect_rp = rp->ai_next;
2630 }
2631 return FALSE;
2632} /* _netw_start_nonblock_connect */
2633
2651int netw_connect_nonblock(NETWORK** netw, char* host, char* port, int ip_version) {
2652 int error = 0;
2653 __n_assert(netw, return FALSE);
2654 if (*netw) {
2655 n_log(LOG_ERR, "netw_connect_nonblock: pass an empty NETWORK* (got a non-NULL one)");
2656 return FALSE;
2657 }
2659 __n_assert(*netw, return FALSE);
2660 if (netw_init_wsa(1, 2, 2) == FALSE) {
2661 n_log(LOG_ERR, "netw_connect_nonblock: unable to load WSA");
2663 return FALSE;
2664 }
2665 if (ip_version == NETWORK_IPV4)
2666 (*netw)->link.hints.ai_family = AF_INET;
2667 else if (ip_version == NETWORK_IPV6)
2668 (*netw)->link.hints.ai_family = AF_INET6;
2669 else
2670 (*netw)->link.hints.ai_family = AF_UNSPEC;
2671 (*netw)->link.hints.ai_socktype = SOCK_STREAM;
2672 (*netw)->link.hints.ai_protocol = IPPROTO_TCP;
2673
2674 /* netw_new leaves link.ip NULL; netw_connect_check writes the peer address
2675 * into it via inet_ntop, so allocate the 64-byte buffer up front (mirrors
2676 * netw_connect_ex_to). */
2677 Malloc((*netw)->link.ip, char, 64);
2678 __n_assert((*netw)->link.ip, netw_close(netw); return FALSE);
2679
2680 error = getaddrinfo(host, port, &(*netw)->link.hints, &(*netw)->link.rhost);
2681 if (error != 0) {
2682 _netw_capture_error(*netw, "netw_connect_nonblock resolve %s:%s: %s", host, port, gai_strerror(error));
2683 n_log(LOG_ERR, "netw_connect_nonblock resolve %s:%s: %s", host, port, gai_strerror(error));
2685 return FALSE;
2686 }
2687 (*netw)->addr_infos_loaded = 1;
2688 (*netw)->link.connect_rp = (*netw)->link.rhost;
2689 (*netw)->link.port = strdup(port);
2690 __n_assert((*netw)->link.port, netw_close(netw); return FALSE);
2691
2692 if (_netw_start_nonblock_connect(*netw) == FALSE) {
2693 _netw_capture_error(*netw, "netw_connect_nonblock: could not initiate connect to %s:%s", host, port);
2694 n_log(LOG_ERR, "netw_connect_nonblock: could not initiate connect to %s:%s", host, port);
2696 return FALSE;
2697 }
2698 return TRUE;
2699} /* netw_connect_nonblock */
2700
2714 __n_assert(netw, return FALSE);
2715 if (netw->link.sock == INVALID_SOCKET)
2716 return FALSE;
2717
2718 fd_set wset, eset;
2719 FD_ZERO(&wset);
2720 FD_SET(netw->link.sock, &wset);
2721 FD_ZERO(&eset);
2722 FD_SET(netw->link.sock, &eset);
2723 struct timeval tv;
2724 tv.tv_sec = 0;
2725 tv.tv_usec = 0;
2726 int sel = select((int)netw->link.sock + 1, NULL, &wset, &eset, &tv);
2727 if (sel == 0)
2728 return NETW_IO_WANT_WRITE; /* not writable yet: still connecting */
2729 if (sel < 0) {
2730 if (neterrno == EINTR)
2731 return NETW_IO_WANT_WRITE;
2732 return FALSE;
2733 }
2734
2735 /* The socket signalled: SO_ERROR is the arbiter of success/failure. */
2736 int so_err = 0;
2737 socklen_t slen = sizeof(so_err);
2738 if (getsockopt(netw->link.sock, SOL_SOCKET, SO_ERROR, (char*)&so_err, &slen) != 0)
2739 so_err = neterrno;
2740
2741 if (so_err == 0) {
2742 /* Connected: record the peer address, mirror netw_connect_ex_to. */
2743 if (netw->link.connect_rp &&
2744 !inet_ntop(netw->link.connect_rp->ai_family, get_in_addr(netw->link.connect_rp->ai_addr), netw->link.ip, 64)) {
2745 n_log(LOG_ERR, "netw_connect_check: inet_ntop failed on socket " SOCKET_SIZE_FORMAT "", netw->link.sock);
2746 }
2748 return TRUE;
2749 }
2750
2751 /* This address failed: close it and try the next resolved address. */
2752 {
2753 char* errmsg = netstrerror(so_err);
2754 n_log(LOG_INFO, "netw_connect_check: connect failed on an address: %s", _str(errmsg));
2755 FreeNoLog(errmsg);
2756 }
2757 closesocket(netw->link.sock);
2758 netw->link.sock = INVALID_SOCKET;
2759 netw->link.connect_rp = netw->link.connect_rp ? netw->link.connect_rp->ai_next : NULL;
2761 return NETW_IO_WANT_WRITE; /* now trying the next address */
2762 return FALSE; /* every address failed */
2763} /* netw_connect_check */
2764
2777int netw_connect_ex(NETWORK** netw, char* host, char* port, size_t send_list_limit, size_t recv_list_limit, int ip_version, char* ssl_key_file, char* ssl_cert_file) {
2778 return netw_connect_ex_to(netw, host, port, send_list_limit, recv_list_limit, ip_version, ssl_key_file, ssl_cert_file, 0);
2779} /* netw_connect_ex(...)*/
2780
2789int netw_connect(NETWORK** netw, char* host, char* port, int ip_version) {
2790 n_log(LOG_INFO, "Trying to connect to %s : %s", _str(host), _str(port));
2791 return netw_connect_ex(netw, host, port, MAX_LIST_ITEMS, MAX_LIST_ITEMS, ip_version, NULL, NULL);
2792} /* netw_connect() */
2793
2803int netw_connect_to(NETWORK** netw, char* host, char* port, int ip_version, int connect_timeout_ms) {
2804 n_log(LOG_INFO, "Trying to connect to %s : %s (connect timeout %d ms)", _str(host), _str(port), connect_timeout_ms);
2805 return netw_connect_ex_to(netw, host, port, MAX_LIST_ITEMS, MAX_LIST_ITEMS, ip_version, NULL, NULL, connect_timeout_ms);
2806} /* netw_connect_to() */
2807
2808#ifdef HAVE_OPENSSL
2819int netw_ssl_connect(NETWORK** netw, char* host, char* port, int ip_version, char* ssl_key_file, char* ssl_cert_file) {
2820 n_log(LOG_INFO, "Trying to connect to %s : %s", _str(host), _str(port));
2821 return netw_connect_ex(netw, host, port, MAX_LIST_ITEMS, MAX_LIST_ITEMS, ip_version, ssl_key_file, ssl_cert_file);
2822} /* netw_connect() */
2823
2839int netw_ssl_connect_client_to(NETWORK** netw, char* host, char* port, int ip_version, int connect_timeout_ms) {
2840 __n_assert(netw, return FALSE);
2841 /* TCP connect first (no SSL params) */
2842 if (netw_connect_ex_to(netw, host, port, MAX_LIST_ITEMS, MAX_LIST_ITEMS, ip_version, NULL, NULL, connect_timeout_ms) == FALSE) {
2843 return FALSE;
2844 }
2845 if (netw_ssl_start_client(*netw) == FALSE) {
2847 return FALSE;
2848 }
2849 return TRUE;
2850} /* netw_ssl_connect_client_to */
2851
2869 __n_assert(netw, return FALSE);
2870 /* Create SSL context for client */
2872#if OPENSSL_VERSION_NUMBER >= 0x10100000L
2873 netw->method = TLS_client_method();
2874#else
2875 netw->method = TLSv1_2_client_method();
2876#endif
2877 netw->ctx = SSL_CTX_new(netw->method);
2878 if (!netw->ctx) {
2879 netw_ssl_print_errors(netw->link.sock);
2880 return FALSE;
2881 }
2882 /* PARTIAL_WRITE: a short SSL_write can be resumed from the
2883 * caller's buffer+offset (required by the reactor's
2884 * single-attempt send path); MOVING_WRITE_BUFFER: tolerate the
2885 * resume pointer not being the original one. No-op for the
2886 * blocking thread engine. */
2887 SSL_CTX_set_mode(netw->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
2888 /* Load default system CA paths */
2889 SSL_CTX_set_default_verify_paths(netw->ctx);
2890 /* Set send/recv to SSL variants */
2891 netw->send_data = &send_ssl_data;
2892 netw->recv_data = &recv_ssl_data;
2893 netw->send_data_once = &send_ssl_data_once;
2894 netw->recv_data_once = &recv_ssl_data_once;
2895 netw->crypto_algo = NETW_ENCRYPT_OPENSSL;
2896 return TRUE;
2897} /* netw_ssl_start_client */
2898
2911int netw_ssl_connect_client(NETWORK** netw, char* host, char* port, int ip_version) {
2913} /* netw_ssl_connect_client */
2914
2925static int netw_is_ip_literal(const char* host) {
2926 struct in_addr v4;
2927 struct in6_addr v6;
2928 if (!host || !host[0])
2929 return 0;
2930 if (inet_pton(AF_INET, host, &v4) == 1)
2931 return 1;
2932 if (inet_pton(AF_INET6, host, &v6) == 1)
2933 return 1;
2934 return 0;
2935}
2936
2948int netw_ssl_do_handshake(NETWORK* netw, const char* sni_hostname) {
2949 __n_assert(netw, return FALSE);
2950 __n_assert(netw->ctx, n_log(LOG_ERR, "netw_ssl_do_handshake: no SSL context"); return FALSE);
2951
2952 netw->ssl = SSL_new(netw->ctx);
2953 if (!netw->ssl) {
2954 _netw_capture_error(netw, "SSL_new failed");
2955 netw_ssl_print_errors(netw->link.sock);
2956 return FALSE;
2957 }
2958 SSL_set_fd(netw->ssl, (int)netw->link.sock);
2959 if (sni_hostname) {
2960 SSL_set_tlsext_host_name(netw->ssl, sni_hostname);
2961 /* SNI says which certificate to SEND; it does not say which names are
2962 acceptable ON it. Without the identity below, SSL_VERIFY_PEER validates the
2963 chain and nothing else, so any certificate that chains to a trusted root is
2964 accepted for any host: exactly the interception a TLS client exists to
2965 detect. An IP literal is matched against the certificate's IP entries, a
2966 name against its subjectAltName / CN. Partial wildcards (w*.example.com)
2967 are refused; a full one (*.example.com) still matches.
2968 With SSL_VERIFY_PEER set this makes the handshake fail on a mismatch; with
2969 verification off the handshake still completes and the mismatch is reported
2970 by netw_ssl_get_verify_result(), so a caller that deliberately does not
2971 verify is unaffected. */
2972 if (netw_is_ip_literal(sni_hostname)) {
2973 /* there is no SSL_set1_ip_asc; the IP form goes through the verify param */
2974 X509_VERIFY_PARAM* vp = SSL_get0_param(netw->ssl);
2975 if (!vp || X509_VERIFY_PARAM_set1_ip_asc(vp, sni_hostname) != 1)
2976 n_log(LOG_ERR, "netw_ssl_do_handshake: could not set the expected peer IP %s", sni_hostname);
2977 } else {
2978 SSL_set_hostflags(netw->ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
2979 if (SSL_set1_host(netw->ssl, sni_hostname) != 1)
2980 n_log(LOG_ERR, "netw_ssl_do_handshake: could not set the expected peer name %s", sni_hostname);
2981 }
2982 }
2983 if (SSL_connect(netw->ssl) <= 0) {
2984 n_log(LOG_ERR, "SSL handshake failed");
2985 unsigned long err = ERR_peek_error();
2986 _netw_capture_error(netw, "SSL handshake failed for %s:%s: %s",
2987 _str(netw->link.ip), _str(netw->link.port),
2988 err ? ERR_reason_error_string(err) : "unknown error");
2989 netw_ssl_print_errors(netw->link.sock);
2990 return FALSE;
2991 }
2992 n_log(LOG_DEBUG, "SSL handshake completed with %s:%s", _str(netw->link.ip), _str(netw->link.port));
2993 return TRUE;
2994} /* netw_ssl_do_handshake */
2995
3004 ERR_clear_error();
3005 errno = 0;
3006 int r = SSL_do_handshake(netw->ssl);
3007 int sys_errno = errno; /* before anything else can overwrite it */
3008 if (r == 1)
3009 return TRUE;
3010 int e = SSL_get_error(netw->ssl, r);
3011 if (e == SSL_ERROR_WANT_READ)
3012 return NETW_IO_WANT_READ;
3013 if (e == SSL_ERROR_WANT_WRITE)
3014 return NETW_IO_WANT_WRITE;
3015 {
3016 unsigned long err = ERR_peek_error();
3017 const char* why = err ? ERR_reason_error_string(err) : NULL;
3018 _netw_capture_error(netw, "TLS handshake failed for %s:%s: %s",
3019 _str(netw->link.ip), _str(netw->link.port),
3020 why ? why : "unknown error");
3021 /* Say WHY: "failed" alone left a burst of 1,375 failures in a server
3022 * log with nothing to tell a peer closing mid-handshake (SSL_ERROR_
3023 * SYSCALL / ZERO_RETURN, errno 0 or ECONNRESET) from a protocol or
3024 * certificate alert (SSL_ERROR_SSL and its reason string). */
3025 n_log(LOG_ERR, "socket %d: non-blocking TLS handshake failed (SSL_get_error %d, %s, errno %d)",
3026 netw->link.sock, e, why ? why : (e == SSL_ERROR_ZERO_RETURN ? "peer closed" : (e == SSL_ERROR_SYSCALL ? "syscall/EOF" : "no reason")), sys_errno);
3027 }
3028 /* A handshake mismatch/error queues entries; drain them so a stale reason
3029 * cannot mislead a later, unrelated OpenSSL call on this thread. */
3030 ERR_clear_error();
3031 return FALSE;
3032} /* _netw_ssl_handshake_step */
3033
3050int netw_ssl_handshake_once(NETWORK* netw, const char* sni_hostname) {
3051 __n_assert(netw, return FALSE);
3052 __n_assert(netw->ctx, n_log(LOG_ERR, "netw_ssl_handshake_once: no SSL context"); return FALSE);
3053 if (!netw->ssl) {
3054 netw->ssl = SSL_new(netw->ctx);
3055 if (!netw->ssl) {
3056 _netw_capture_error(netw, "SSL_new failed");
3057 netw_ssl_print_errors(netw->link.sock);
3058 return FALSE;
3059 }
3060 SSL_set_fd(netw->ssl, (int)netw->link.sock);
3061 SSL_set_connect_state(netw->ssl);
3062 if (sni_hostname)
3063 SSL_set_tlsext_host_name(netw->ssl, sni_hostname);
3064 if (netw_set_blocking(netw, 0) == FALSE)
3065 return FALSE;
3066 }
3068} /* netw_ssl_handshake_once */
3069
3094 __n_assert(netw, return FALSE);
3095 __n_assert(ctx, n_log(LOG_ERR, "netw_ssl_server_handshake_once: no SSL context"); return FALSE);
3096 if (!netw->ssl) {
3097 netw->ssl = SSL_new(ctx);
3098 if (!netw->ssl) {
3099 _netw_capture_error(netw, "SSL_new failed");
3100 netw_ssl_print_errors(netw->link.sock);
3101 return FALSE;
3102 }
3103 SSL_set_fd(netw->ssl, (int)netw->link.sock);
3104 SSL_set_accept_state(netw->ssl);
3105 netw->send_data = &send_ssl_data;
3106 netw->recv_data = &recv_ssl_data;
3107 netw->send_data_once = &send_ssl_data_once;
3108 netw->recv_data_once = &recv_ssl_data_once;
3109 netw->crypto_algo = NETW_ENCRYPT_OPENSSL;
3110 /* netw->ctx stays NULL: this connection borrows the caller's shared
3111 * ctx, so netw_close() frees the SSL but not the context (accepted
3112 * connections borrow ctx from the owner). */
3113 if (netw_set_blocking(netw, 0) == FALSE)
3114 return FALSE;
3115 }
3117} /* netw_ssl_server_handshake_once */
3118
3119#endif
3120
3128int netw_get_state(NETWORK* netw, uint32_t* state, int* thr_engine_status) {
3129 if (netw) {
3130 /* use eventbolt: netw_set() writes state/threaded_engine_status under eventbolt */
3131 pthread_mutex_lock(&netw->eventbolt);
3132 if (state)
3133 (*state) = netw_atomic_read_state(netw);
3134 if (thr_engine_status)
3135 (*thr_engine_status) = netw->threaded_engine_status;
3136 pthread_mutex_unlock(&netw->eventbolt);
3137 return TRUE;
3138 } else {
3139 n_log(LOG_ERR, "Can't get status of a NULL network");
3140 }
3141 return FALSE;
3142} /*netw_get_state() */
3143
3150int netw_set(NETWORK* netw, int flag) {
3151 __n_assert(netw, return FALSE);
3152 if (flag & NETW_EMPTY_SENDBUF) {
3153 pthread_mutex_lock(&netw->sendbolt);
3154 if (netw->send_buf)
3155 list_empty(netw->send_buf);
3156 pthread_mutex_unlock(&netw->sendbolt);
3157 };
3158 if (flag & NETW_EMPTY_RECVBUF) {
3159 pthread_mutex_lock(&netw->recvbolt);
3160 if (netw->recv_buf)
3161 list_empty(netw->recv_buf);
3162 pthread_mutex_unlock(&netw->recvbolt);
3163 }
3164 if (flag & NETW_DESTROY_SENDBUF) {
3165 pthread_mutex_lock(&netw->sendbolt);
3166 if (netw->send_buf)
3167 list_destroy(&netw->send_buf);
3168 pthread_mutex_unlock(&netw->sendbolt);
3169 };
3170 if (flag & NETW_DESTROY_RECVBUF) {
3171 pthread_mutex_lock(&netw->recvbolt);
3172 if (netw->recv_buf)
3173 list_destroy(&netw->recv_buf);
3174 pthread_mutex_unlock(&netw->recvbolt);
3175 }
3176 pthread_mutex_lock(&netw->eventbolt);
3177 if (flag & NETW_CLIENT) {
3178 netw->mode = NETW_CLIENT;
3179 }
3180 if (flag & NETW_SERVER) {
3181 netw->mode = NETW_SERVER;
3182 }
3183 if (flag & NETW_RUN) {
3185 }
3186 if (flag & NETW_EXITED) {
3188 }
3189 if (flag & NETW_ERROR) {
3191 }
3192 if (flag & NETW_EXIT_ASKED) {
3194 }
3195 if (flag & NETW_THR_ENGINE_STARTED) {
3196 netw->threaded_engine_status = NETW_THR_ENGINE_STARTED;
3197 }
3198 if (flag & NETW_THR_ENGINE_STOPPED) {
3199 netw->threaded_engine_status = NETW_THR_ENGINE_STOPPED;
3200 }
3201 pthread_mutex_unlock(&netw->eventbolt);
3202
3203 /* Only wake the send thread for state changes it actually cares
3204 * about. The inner loop in netw_send_func branches on NETW_ERROR,
3205 * NETW_EXITED, and NETW_EXIT_ASKED; every other flag (mode /
3206 * thread-engine-status / buffer maintenance) is invisible to it,
3207 * and posting unconditionally creates a spurious wakeup that,
3208 * paired with an empty send_buf, produces the classic
3209 * "offset-by-one semaphore" bug where every subsequent real
3210 * list_push leaves one extra post in the semaphore for the next
3211 * sem_wait to consume on a drained list. Avoiding the spurious
3212 * post here is the root cause fix and removes the need for a
3213 * defensive branch in the common path. */
3214 if (flag & (NETW_ERROR | NETW_EXITED | NETW_EXIT_ASKED)) {
3215 sem_post(&netw->send_blocker);
3216 }
3217
3218 return TRUE;
3219} /* netw_set(...) */
3220
3227int deplete_send_buffer(int fd, int timeout) {
3228#if defined(__linux__)
3229 int outstanding = 0;
3230 if (timeout <= 0) {
3231 return 0;
3232 }
3233 for (int it = 0; it < timeout; it += 100) {
3234 outstanding = 0;
3235 if (ioctl(fd, SIOCOUTQ, &outstanding) == -1) {
3236 int error = errno;
3237 n_log(LOG_ERR, "ioctl SIOCOUTQ returned -1: %s for socket %d", strerror(error), fd);
3238 return -1;
3239 }
3240 if (!outstanding) {
3241 break;
3242 }
3243 usleep(100000);
3244 }
3245 return outstanding;
3246#else
3247 (void)fd;
3248 (void)timeout;
3249 return 0;
3250#endif
3251}
3252
3259 __n_assert(netw && (*netw), return FALSE);
3260 uint32_t state = 0;
3261 int thr_engine_status = 0;
3262 int nb_running = 0;
3263
3264 /* Reactor close handshake. Synchronously wait for the reactor to
3265 * finish with this connection (drains pending sends best-effort,
3266 * shutdown(SHUT_WR) so the peer sees EOF, removes the fd from the
3267 * epoll set, publishes the close ack) before any of the existing
3268 * close logic runs. After this returns the reactor will never touch
3269 * the NETWORK again, so the rest of netw_close (and the final Free)
3270 * proceeds exactly like the thread-mode path.
3271 *
3272 * Keyed on reactor_registered, NOT the live reactor_mode: the reactor
3273 * clears reactor_mode partway through unregister (peer EOF / hard
3274 * error) and keeps touching the NETWORK for a few more lines before
3275 * acking. Gating on reactor_mode would let a clear that lands just
3276 * before this read send us straight to Free() while the reactor is
3277 * still mid-unregister (use-after-free). reactor_registered is set
3278 * once at register time and never cleared, so this is race-free.
3279 *
3280 * Compile-time gated on N_REACTOR_AVAILABLE so non-Linux/Android
3281 * builds (Windows MinGW, Solaris) don't pull in n_reactor.o just
3282 * to satisfy this symbol, the reactor is unavailable there anyway
3283 * and reactor_registered can never be set. */
3284#if N_REACTOR_AVAILABLE
3285 if (__atomic_load_n(&(*netw)->reactor_registered, __ATOMIC_ACQUIRE)) {
3287 }
3288#endif
3289
3290 if ((*netw)->deplete_queues_timeout > 0) {
3291 /* use iteration count to avoid integer overflow with large timeouts */
3292 int max_iterations = (*netw)->deplete_queues_timeout * 10; /* each iteration ~100ms */
3293 netw_get_state((*netw), &state, &thr_engine_status);
3294 if (thr_engine_status == NETW_THR_ENGINE_STARTED) {
3295 int it = 0;
3296 do {
3297 pthread_mutex_lock(&(*netw)->eventbolt);
3298 nb_running = (*netw)->nb_running_threads;
3299 pthread_mutex_unlock(&(*netw)->eventbolt);
3300 usleep(100000);
3301 it++;
3302 } while (nb_running > 0 && it < max_iterations);
3303 netw_get_state((*netw), &state, &thr_engine_status);
3304 if (it >= max_iterations && nb_running > 0) {
3305 n_log(LOG_ERR, "netw " SOCKET_SIZE_FORMAT ": %d threads are still running after %d seconds, netw is in state %s (%" PRIu32 ")", (*netw)->link.sock, nb_running, (*netw)->deplete_queues_timeout, N_ENUM_ENTRY(__netw_code_type, toString)(state), state);
3306 }
3307 }
3308 }
3309
3310 if ((*netw)->link.sock != INVALID_SOCKET) {
3311 int remaining = deplete_send_buffer((int)(*netw)->link.sock, (*netw)->deplete_socket_timeout);
3312 // cppcheck-suppress knownConditionTrueFalse ; remaining can be > 0 on Linux (SIOCOUTQ)
3313 if (remaining > 0) {
3314 n_log(LOG_ERR, "socket " SOCKET_SIZE_FORMAT " (%s:%s) %d octets still in send buffer before closing after a wait of %d msecs", (*netw)->link.sock, (*netw)->link.ip, (*netw)->link.port, remaining, (*netw)->deplete_socket_timeout);
3315 }
3316 }
3317
3318 list_foreach(node, (*netw)->pools) {
3319 NETWORK_POOL* pool = (NETWORK_POOL*)node->ptr;
3321 }
3322 list_destroy(&(*netw)->pools);
3323
3324 netw_get_state((*netw), &state, &thr_engine_status);
3325 if (thr_engine_status == NETW_THR_ENGINE_STARTED) {
3327 }
3328
3329 /* recompute nb_running after joining threads so the SSL_shutdown
3330 * decision reflects the actual post-join state */
3331 pthread_mutex_lock(&(*netw)->eventbolt);
3332 nb_running = (*netw)->nb_running_threads;
3333 pthread_mutex_unlock(&(*netw)->eventbolt);
3334
3335 if ((*netw)->link.sock != INVALID_SOCKET) {
3336#ifdef HAVE_OPENSSL
3337 if ((*netw)->crypto_algo == NETW_ENCRYPT_OPENSSL) {
3338 if ((*netw)->ssl) {
3339 /* only attempt SSL_shutdown if threads have fully stopped,
3340 * otherwise SSL operations may race with send/recv threads */
3341 if (nb_running == 0) {
3342 int shutdown_res = SSL_shutdown((*netw)->ssl);
3343 if (shutdown_res == 0) {
3344 /* try again to complete bidirectional shutdown;
3345 * peer may have already closed (common for HTTP),
3346 * so SYSCALL/SSL errors are non-fatal here */
3347 shutdown_res = SSL_shutdown((*netw)->ssl);
3348 }
3349 if (shutdown_res < 0) {
3350 int err = SSL_get_error((*netw)->ssl, shutdown_res);
3351 if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
3352 /* Non-blocking socket: the close_notify handshake
3353 * cannot complete synchronously. We tear the socket
3354 * down immediately afterwards anyway, so this is a
3355 * normal teardown, not an error. Drain the queue so
3356 * it cannot mislead a later OpenSSL call. */
3357 n_log(LOG_DEBUG, "SSL_shutdown() would block on non-blocking socket %d: %d", (*netw)->link.sock, err);
3358 ERR_clear_error();
3359 } else if (err != SSL_ERROR_SYSCALL && err != SSL_ERROR_SSL) {
3360 n_log(LOG_ERR, "SSL_shutdown() failed: %d", err);
3361 } else {
3362 n_log(LOG_DEBUG, "SSL_shutdown() peer already closed: %d", err);
3363 }
3364 }
3365 } else {
3366 n_log(LOG_WARNING, "netw %d: forcing bidirectional SSL_shutdown with %d threads still running", (*netw)->link.sock, nb_running);
3367 int shutdown_res = SSL_shutdown((*netw)->ssl);
3368 if (shutdown_res == 0) {
3369 /* send close_notify and wait for peer's close_notify */
3370 shutdown_res = SSL_shutdown((*netw)->ssl);
3371 }
3372 if (shutdown_res < 0) {
3373 int err = SSL_get_error((*netw)->ssl, shutdown_res);
3374 n_log(LOG_ERR, "netw %d: SSL_shutdown() failed with %d threads still running: %d", (*netw)->link.sock, nb_running, err);
3375 }
3376 }
3377 /* a shutdown that failed (mid-handshake, peer gone) leaves
3378 * entries on THIS thread's queue; on a single-threaded
3379 * server they would surface as the next connection's error */
3380 ERR_clear_error();
3381 SSL_free((*netw)->ssl);
3382 } else {
3383 if ((*netw)->mode != NETW_SERVER) {
3384 n_log(LOG_ERR, "SSL handle of socket %d was already NULL", (*netw)->link.sock);
3385 } else {
3386 n_log(LOG_DEBUG, "listening socket %d has no SSL handle (expected)", (*netw)->link.sock);
3387 }
3388 }
3389 }
3390#endif
3391
3392 /* inform peer that we have finished */
3393 shutdown((*netw)->link.sock, SHUT_WR);
3394
3395 if ((*netw)->wait_close_timeout > 0) {
3396 /* wait for fin ack using select() to avoid busy-waiting.
3397 * Use iteration count to avoid integer overflow. */
3398 char buffer[4096] = "";
3399 int max_iters = (*netw)->wait_close_timeout * 10; /* each iteration ~100ms */
3400 for (int it = 0; it < max_iters; it++) {
3401 fd_set rfds;
3402 struct timeval tv;
3403 FD_ZERO(&rfds);
3404#pragma GCC diagnostic push
3405#pragma GCC diagnostic ignored "-Wsign-conversion"
3406 FD_SET((*netw)->link.sock, &rfds);
3407#pragma GCC diagnostic pop
3408 tv.tv_sec = 0;
3409 tv.tv_usec = 100000; /* 100ms */
3410 int sel = select((int)(*netw)->link.sock + 1, &rfds, NULL, NULL, &tv);
3411 if (sel > 0) {
3412 ssize_t res = recv((*netw)->link.sock, buffer, 4096, NETFLAGS);
3413 if (!res)
3414 break;
3415 if (res < 0) {
3416 int error = neterrno;
3417 if (error != ENOTCONN && error != EINTR && error != ECONNRESET
3418#ifdef __windows__
3419 && error != WSAENOTCONN && error != WSAECONNRESET && error != WSAESHUTDOWN && error != WSAEWOULDBLOCK && error != WSAEINTR
3420#endif
3421 ) {
3422 char* errmsg = netstrerror(error);
3423 n_log(LOG_ERR, "read returned error %d when closing socket " SOCKET_SIZE_FORMAT " (%s:%s): %s", error, (*netw)->link.sock, _str((*netw)->link.ip), (*netw)->link.port, _str(errmsg));
3424 FreeNoLog(errmsg);
3425 } else {
3426 n_log(LOG_DEBUG, "wait close: connection gracefully closed on socket " SOCKET_SIZE_FORMAT " (%s:%s)", (*netw)->link.sock, _str((*netw)->link.ip), (*netw)->link.port);
3427 }
3428 break;
3429 }
3430 } else if (sel < 0) {
3431 /* select error, bail out */
3432 int error = neterrno;
3433 if (error != EINTR
3434#ifdef __windows__
3435 && error != WSAEINTR
3436#endif
3437 ) {
3438 char* errmsg = netstrerror(error);
3439 n_log(LOG_ERR, "select() error on socket " SOCKET_SIZE_FORMAT " during close: %s", (*netw)->link.sock, _str(errmsg));
3440 FreeNoLog(errmsg);
3441 break;
3442 }
3443 }
3444 /* sel == 0: timeout, continue waiting */
3445 }
3446 }
3447
3448 /* effectively close socket */
3449 closesocket((*netw)->link.sock);
3450
3451#ifdef HAVE_OPENSSL
3452 /* clean openssl state. Accepted server-side SSL connections borrow
3453 * ctx from the listener (their ctx is NULL by design), so a NULL
3454 * ctx here is not an error, only the owner frees it. */
3455 if ((*netw)->crypto_algo == NETW_ENCRYPT_OPENSSL && (*netw)->ctx) {
3456 SSL_CTX_free((*netw)->ctx);
3457 }
3458 n_log(LOG_DEBUG, "socket %d closed", (*netw)->link.sock);
3459 FreeNoLog((*netw)->key);
3460 FreeNoLog((*netw)->certificate);
3461#endif
3462 }
3463
3464 FreeNoLog((*netw)->link.ip);
3465 FreeNoLog((*netw)->link.port);
3466
3467 if ((*netw)->link.rhost) {
3468 freeaddrinfo((*netw)->link.rhost);
3469 }
3470
3471 /*list freeing*/
3473
3474 pthread_mutex_destroy(&(*netw)->recvbolt);
3475 pthread_mutex_destroy(&(*netw)->sendbolt);
3476 pthread_mutex_destroy(&(*netw)->eventbolt);
3477 sem_destroy(&(*netw)->send_blocker);
3478
3479 Free((*netw));
3480
3481 return TRUE;
3482} /* netw_close(...)*/
3483
3493int netw_make_listening(NETWORK** netw, char* addr, char* port, int nbpending, int ip_version) {
3494 __n_assert(port, return FALSE);
3495
3496 int error = 0;
3497 char* errmsg = NULL;
3498
3499 if (*netw) {
3500 n_log(LOG_ERR, "Cannot use an allocated network. Please pass a NULL network to modify");
3501 return FALSE;
3502 }
3503
3504 /*checking WSA when under windows*/
3505 if (netw_init_wsa(1, 2, 2) == FALSE) {
3506 n_log(LOG_ERR, "Unable to load WSA dll's");
3507 return FALSE;
3508 }
3509
3511 __n_assert((*netw), return FALSE);
3512 (*netw)->link.port = strdup(port);
3513 /*creating array*/
3514 if (ip_version == NETWORK_IPV4) {
3515 (*netw)->link.hints.ai_family = AF_INET; /* Allow IPv4 */
3516 } else if (ip_version == NETWORK_IPV6) {
3517 (*netw)->link.hints.ai_family = AF_INET6; /* Allow IPv6 */
3518 } else {
3519 /* NETWORK_ALL or unknown value */
3520 (*netw)->link.hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
3521 }
3522 if (!addr) {
3523 (*netw)->link.hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
3524 }
3525 (*netw)->link.hints.ai_socktype = SOCK_STREAM;
3526 (*netw)->link.hints.ai_protocol = IPPROTO_TCP;
3527 (*netw)->link.hints.ai_canonname = NULL;
3528 (*netw)->link.hints.ai_next = NULL;
3529
3530 error = getaddrinfo(addr, port, &(*netw)->link.hints, &(*netw)->link.rhost);
3531 if (error != 0) {
3532 _netw_capture_error(*netw, "Error when resolving %s:%s getaddrinfo: %s", _str(addr), port, gai_strerror(error));
3533 n_log(LOG_ERR, "Error when resolving %s:%s getaddrinfo: %s", _str(addr), port, gai_strerror(error));
3535 return FALSE;
3536 }
3537 (*netw)->addr_infos_loaded = 1;
3538
3539 /* getaddrinfo() returns a list of address structures.
3540 Try each address until we successfully connect(2).
3541 If socket(2) (or connect(2)) fails, we (close the socket
3542 and) try the next address. */
3543 struct addrinfo* rp = NULL;
3544 for (rp = (*netw)->link.rhost; rp != NULL; rp = rp->ai_next) {
3545 (*netw)->link.sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
3546 if ((*netw)->link.sock == INVALID_SOCKET) {
3547 error = neterrno;
3548 errmsg = netstrerror(error);
3549 n_log(LOG_ERR, "Error while trying to make a socket: %s", _str(errmsg));
3550 FreeNoLog(errmsg);
3551 continue;
3552 }
3553 /* SO_REUSEADDR means opposite things on the two platforms, so it is POSIX-only.
3554 * On POSIX it lets a restarted listener re-take the port it just released, which is
3555 * what it is here for. On Windows it lets an UNRELATED process bind over a socket
3556 * that is already holding the port: the second bind succeeds, and which socket gets
3557 * a given connection is not defined. A program that reports "listening" while
3558 * another process owns the port is worse than one that fails to start, and for a
3559 * security tool it is worse again, because the traffic it was asked to intercept can
3560 * go somewhere else entirely. Windows already refuses the second bind by default,
3561 * which is the behaviour wanted, so the option is simply not set there.
3562 * (SO_EXCLUSIVEADDRUSE would additionally stop others binding over US, and is worth
3563 * considering if port hijacking by a process that starts LATER ever matters here.) */
3564#ifndef __windows__
3565 netw_setsockopt((*netw), SO_REUSEADDR, 1);
3566#endif
3567 // netw_setsockopt( (*netw), SO_REUSEPORT, 1 );
3568 if (bind((*netw)->link.sock, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0) {
3569 char* ip = NULL;
3570 Malloc(ip, char, 64);
3571 if (!ip) {
3572 n_log(LOG_ERR, "Error allocating 64 bytes for ip");
3574 return FALSE;
3575 }
3576 if (!inet_ntop(rp->ai_family, get_in_addr(rp->ai_addr), ip, 64)) {
3577 error = neterrno;
3578 errmsg = netstrerror(error);
3579 n_log(LOG_ERR, "inet_ntop: %p , %s", (void*)&(*netw)->link.raddr, _str(errmsg));
3580 FreeNoLog(errmsg);
3581 }
3582 /*n_log( LOG_DEBUG, "Socket %d successfully binded to %s %s", (*netw) -> link . sock, _str( ip ) , _str( port ) );*/
3583 (*netw)->link.ip = ip;
3584 break; /* Success */
3585 }
3586 error = neterrno;
3587 errmsg = netstrerror(error);
3588 _netw_capture_error(*netw, "Error from bind() on port %s neterrno: %s", port, errmsg);
3589 n_log(LOG_ERR, "Error from bind() on port %s neterrno: %s", port, errmsg);
3590 FreeNoLog(errmsg);
3591 closesocket((*netw)->link.sock);
3592 }
3593 if (rp == NULL) {
3594 /* No address succeeded */
3595 _netw_capture_error(*netw, "Couldn't get a socket for listening on port %s", port);
3596 n_log(LOG_ERR, "Couldn't get a socket for listening on port %s", port);
3598 return FALSE;
3599 }
3600
3601 /* nb_pending connections*/
3602 (*netw)->nb_pending = nbpending;
3603 if (listen((*netw)->link.sock, (*netw)->nb_pending) != 0) {
3604 error = neterrno;
3605 errmsg = netstrerror(error);
3606 _netw_capture_error(*netw, "listen() failed on port %s: %s", port, _str(errmsg));
3607 n_log(LOG_ERR, "listen() failed on port %s: %s", port, _str(errmsg));
3608 FreeNoLog(errmsg);
3610 return FALSE;
3611 }
3612
3614
3615 return TRUE;
3616} /* netw_make_listening(...)*/
3617
3626int netw_bind_udp(NETWORK** netw, char* addr, char* port, int ip_version) {
3627 __n_assert(port, return FALSE);
3628
3629 int error = 0;
3630 char* errmsg = NULL;
3631
3632 if (*netw) {
3633 n_log(LOG_ERR, "Cannot use an allocated network. Please pass a NULL network to modify");
3634 return FALSE;
3635 }
3636
3637 /*checking WSA when under windows*/
3638 if (netw_init_wsa(1, 2, 2) == FALSE) {
3639 n_log(LOG_ERR, "Unable to load WSA dll's");
3640 return FALSE;
3641 }
3642
3644 __n_assert((*netw), return FALSE);
3645 (*netw)->link.port = strdup(port);
3646 (*netw)->transport_type = NETWORK_UDP;
3647
3648 /* choose ip version */
3649 if (ip_version == NETWORK_IPV4) {
3650 (*netw)->link.hints.ai_family = AF_INET;
3651 } else if (ip_version == NETWORK_IPV6) {
3652 (*netw)->link.hints.ai_family = AF_INET6;
3653 } else {
3654 (*netw)->link.hints.ai_family = AF_UNSPEC;
3655 }
3656 if (!addr) {
3657 (*netw)->link.hints.ai_flags = AI_PASSIVE;
3658 }
3659 (*netw)->link.hints.ai_socktype = SOCK_DGRAM;
3660 (*netw)->link.hints.ai_protocol = IPPROTO_UDP;
3661 (*netw)->link.hints.ai_canonname = NULL;
3662 (*netw)->link.hints.ai_next = NULL;
3663
3664 error = getaddrinfo(addr, port, &(*netw)->link.hints, &(*netw)->link.rhost);
3665 if (error != 0) {
3666 _netw_capture_error(*netw, "Error when resolving %s:%s getaddrinfo: %s", _str(addr), port, gai_strerror(error));
3667 n_log(LOG_ERR, "Error when resolving %s:%s getaddrinfo: %s", _str(addr), port, gai_strerror(error));
3669 return FALSE;
3670 }
3671 (*netw)->addr_infos_loaded = 1;
3672
3673 struct addrinfo* rp = NULL;
3674 for (rp = (*netw)->link.rhost; rp != NULL; rp = rp->ai_next) {
3675 (*netw)->link.sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
3676 if ((*netw)->link.sock == INVALID_SOCKET) {
3677 error = neterrno;
3678 errmsg = netstrerror(error);
3679 n_log(LOG_ERR, "Error while trying to make a UDP socket: %s", _str(errmsg));
3680 FreeNoLog(errmsg);
3681 continue;
3682 }
3683 /* SO_REUSEADDR means opposite things on the two platforms, so it is POSIX-only.
3684 * On POSIX it lets a restarted listener re-take the port it just released, which is
3685 * what it is here for. On Windows it lets an UNRELATED process bind over a socket
3686 * that is already holding the port: the second bind succeeds, and which socket gets
3687 * a given connection is not defined. A program that reports "listening" while
3688 * another process owns the port is worse than one that fails to start, and for a
3689 * security tool it is worse again, because the traffic it was asked to intercept can
3690 * go somewhere else entirely. Windows already refuses the second bind by default,
3691 * which is the behaviour wanted, so the option is simply not set there.
3692 * (SO_EXCLUSIVEADDRUSE would additionally stop others binding over US, and is worth
3693 * considering if port hijacking by a process that starts LATER ever matters here.) */
3694#ifndef __windows__
3695 netw_setsockopt((*netw), SO_REUSEADDR, 1);
3696#endif
3697 if (bind((*netw)->link.sock, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0) {
3698 char* ip = NULL;
3699 Malloc(ip, char, 64);
3700 if (!ip) {
3701 n_log(LOG_ERR, "Error allocating 64 bytes for ip");
3703 return FALSE;
3704 }
3705 if (!inet_ntop(rp->ai_family, get_in_addr(rp->ai_addr), ip, 64)) {
3706 error = neterrno;
3707 errmsg = netstrerror(error);
3708 n_log(LOG_ERR, "inet_ntop: %p , %s", (void*)&(*netw)->link.raddr, _str(errmsg));
3709 FreeNoLog(errmsg);
3710 }
3711 (*netw)->link.ip = ip;
3712 break; /* Success */
3713 }
3714 error = neterrno;
3715 errmsg = netstrerror(error);
3716 _netw_capture_error(*netw, "Error from bind() on UDP port %s neterrno: %s", port, errmsg);
3717 n_log(LOG_ERR, "Error from bind() on UDP port %s neterrno: %s", port, errmsg);
3718 FreeNoLog(errmsg);
3719 closesocket((*netw)->link.sock);
3720 }
3721 if (rp == NULL) {
3722 _netw_capture_error(*netw, "Couldn't get a socket for UDP binding on port %s", port);
3723 n_log(LOG_ERR, "Couldn't get a socket for UDP binding on port %s", port);
3725 return FALSE;
3726 }
3727
3728 /* set send/recv to UDP functions */
3729 (*netw)->send_data = &send_udp_data;
3730 (*netw)->recv_data = &recv_udp_data;
3731
3733
3734 n_log(LOG_DEBUG, "UDP socket bound to %s:%s", _str((*netw)->link.ip), port);
3735
3736 return TRUE;
3737} /* netw_bind_udp(...)*/
3738
3747int netw_connect_udp(NETWORK** netw, char* host, char* port, int ip_version) {
3748 int error = 0;
3749 char* errmsg = NULL;
3750
3751 if ((*netw)) {
3752 n_log(LOG_ERR, "Unable to allocate (*netw), already existing. You must use empty NETWORK *structs.");
3753 return FALSE;
3754 }
3755
3757 __n_assert(netw && (*netw), return FALSE);
3758 (*netw)->transport_type = NETWORK_UDP;
3759
3760 if (netw_init_wsa(1, 2, 2) == FALSE) {
3761 n_log(LOG_ERR, "Unable to load WSA dll's");
3763 return FALSE;
3764 }
3765
3766 /* choose ip version */
3767 if (ip_version == NETWORK_IPV4) {
3768 (*netw)->link.hints.ai_family = AF_INET;
3769 } else if (ip_version == NETWORK_IPV6) {
3770 (*netw)->link.hints.ai_family = AF_INET6;
3771 } else {
3772 (*netw)->link.hints.ai_family = AF_UNSPEC;
3773 }
3774
3775 (*netw)->link.hints.ai_socktype = SOCK_DGRAM;
3776 (*netw)->link.hints.ai_protocol = IPPROTO_UDP;
3777 (*netw)->link.hints.ai_flags = AI_PASSIVE;
3778 (*netw)->link.hints.ai_canonname = NULL;
3779 (*netw)->link.hints.ai_next = NULL;
3780
3781 error = getaddrinfo(host, port, &(*netw)->link.hints, &(*netw)->link.rhost);
3782 if (error != 0) {
3783 _netw_capture_error(*netw, "Error when resolving %s:%s getaddrinfo: %s", host, port, gai_strerror(error));
3784 n_log(LOG_ERR, "Error when resolving %s:%s getaddrinfo: %s", host, port, gai_strerror(error));
3786 return FALSE;
3787 }
3788 (*netw)->addr_infos_loaded = 1;
3789 Malloc((*netw)->link.ip, char, 64);
3790 __n_assert((*netw)->link.ip, netw_close(netw); return FALSE);
3791
3792 struct addrinfo* rp = NULL;
3793 for (rp = (*netw)->link.rhost; rp != NULL; rp = rp->ai_next) {
3794 SOCKET sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
3795 if (sock == INVALID_SOCKET) {
3796 error = neterrno;
3797 errmsg = netstrerror(error);
3798 n_log(LOG_ERR, "Error while trying to make a UDP socket: %s", _str(errmsg));
3799 FreeNoLog(errmsg);
3800 continue;
3801 }
3802
3803 (*netw)->link.sock = sock;
3804
3805 /* connect() on UDP sets the default destination for send/recv */
3806 if (connect(sock, rp->ai_addr, (socklen_t)rp->ai_addrlen) == -1) {
3807 error = neterrno;
3808 errmsg = netstrerror(error);
3809 n_log(LOG_INFO, "UDP connecting to %s:%s : %s", host, port, _str(errmsg));
3810 FreeNoLog(errmsg);
3811 closesocket(sock);
3812 (*netw)->link.sock = INVALID_SOCKET;
3813 continue;
3814 } else {
3815 if (!inet_ntop(rp->ai_family, get_in_addr(rp->ai_addr), (*netw)->link.ip, 64)) {
3816 error = neterrno;
3817 errmsg = netstrerror(error);
3818 n_log(LOG_ERR, "inet_ntop: %p , %s", rp, _str(errmsg));
3819 FreeNoLog(errmsg);
3820 }
3821 break; /* Success */
3822 }
3823 }
3824 if (rp == NULL) {
3825 _netw_capture_error(*netw, "Couldn't connect UDP to %s:%s : no address succeeded", host, port);
3826 n_log(LOG_ERR, "Couldn't connect UDP to %s:%s : no address succeeded", host, port);
3828 return FALSE;
3829 }
3830
3831 (*netw)->link.port = strdup(port);
3832 __n_assert((*netw)->link.port, netw_close(netw); return FALSE);
3833
3834 /* set send/recv to UDP functions */
3835 (*netw)->send_data = &send_udp_data;
3836 (*netw)->recv_data = &recv_udp_data;
3837
3839
3840 n_log(LOG_DEBUG, "UDP-Connected to %s:%s", (*netw)->link.ip, (*netw)->link.port);
3841
3842 return TRUE;
3843} /* netw_connect_udp(...)*/
3844
3852ssize_t send_udp_data(void* netw, char* buf, uint32_t n) {
3854 __n_assert(buf, return NETW_SOCKET_ERROR);
3855
3856 SOCKET s = ((NETWORK*)netw)->link.sock;
3857 int error = 0;
3858 char* errmsg = NULL;
3859
3860 if (n == 0) {
3861 _netw_capture_error((NETWORK*)netw, "Send of 0 is unsupported.");
3862 n_log(LOG_ERR, "Send of 0 is unsupported.");
3863 return NETW_SOCKET_ERROR;
3864 }
3865
3866 ssize_t bs = send(s, buf, NETW_BUFLEN_CAST(n), NETFLAGS);
3867 error = neterrno;
3868 if (bs < 0) {
3869 if (error == ECONNRESET || error == ENOTCONN
3870#ifdef __windows__
3871 || error == WSAECONNRESET || error == WSAENOTCONN
3872#endif
3873 ) {
3874 n_log(LOG_DEBUG, "UDP socket " SOCKET_SIZE_FORMAT " disconnected !", s);
3876 }
3877 errmsg = netstrerror(error);
3878 _netw_capture_error((NETWORK*)netw, "UDP Socket " SOCKET_SIZE_FORMAT " send Error: %zd , %s", s, bs, _str(errmsg));
3879 n_log(LOG_ERR, "UDP Socket " SOCKET_SIZE_FORMAT " send Error: %zd , %s", s, bs, _str(errmsg));
3880 FreeNoLog(errmsg);
3881 return NETW_SOCKET_ERROR;
3882 }
3883 return bs;
3884} /*send_udp_data(...)*/
3885
3893ssize_t recv_udp_data(void* netw, char* buf, uint32_t n) {
3895 __n_assert(buf, return NETW_SOCKET_ERROR);
3896
3897 SOCKET s = ((NETWORK*)netw)->link.sock;
3898
3899 if (n == 0) {
3900 _netw_capture_error((NETWORK*)netw, "Recv of 0 is unsupported.");
3901 n_log(LOG_ERR, "Recv of 0 is unsupported.");
3902 return NETW_SOCKET_ERROR;
3903 }
3904
3905 ssize_t br = recv(s, buf, NETW_BUFLEN_CAST(n), NETFLAGS);
3906 int error = neterrno;
3907 if (br < 0) {
3908 char* errmsg = netstrerror(error);
3909 _netw_capture_error((NETWORK*)netw, "UDP socket " SOCKET_SIZE_FORMAT " recv returned %zd, error: %s", s, br, _str(errmsg));
3910 n_log(LOG_ERR, "UDP socket " SOCKET_SIZE_FORMAT " recv returned %zd, error: %s", s, br, _str(errmsg));
3911 FreeNoLog(errmsg);
3912 return NETW_SOCKET_ERROR;
3913 }
3914 if (br == 0) {
3915 n_log(LOG_DEBUG, "UDP socket " SOCKET_SIZE_FORMAT ": zero-length recv", s);
3916 }
3917 return br;
3918} /*recv_udp_data(...)*/
3919
3929ssize_t netw_udp_sendto(NETWORK* netw, char* buf, uint32_t n, struct sockaddr* dest_addr, socklen_t dest_len) {
3931 __n_assert(buf, return NETW_SOCKET_ERROR);
3932 __n_assert(dest_addr, return NETW_SOCKET_ERROR);
3933
3934 if (n == 0) {
3935 _netw_capture_error(netw, "Sendto of 0 is unsupported.");
3936 n_log(LOG_ERR, "Sendto of 0 is unsupported.");
3937 return NETW_SOCKET_ERROR;
3938 }
3939
3940 ssize_t bs = sendto(netw->link.sock, buf, NETW_BUFLEN_CAST(n), NETFLAGS, dest_addr, dest_len);
3941 if (bs < 0) {
3942 int error = neterrno;
3943 char* errmsg = netstrerror(error);
3944 _netw_capture_error(netw, "UDP socket " SOCKET_SIZE_FORMAT " sendto Error: %zd , %s", netw->link.sock, bs, _str(errmsg));
3945 n_log(LOG_ERR, "UDP socket " SOCKET_SIZE_FORMAT " sendto Error: %zd , %s", netw->link.sock, bs, _str(errmsg));
3946 FreeNoLog(errmsg);
3947 return NETW_SOCKET_ERROR;
3948 }
3949 return bs;
3950} /*netw_udp_sendto(...)*/
3951
3961ssize_t netw_udp_recvfrom(NETWORK* netw, char* buf, uint32_t n, struct sockaddr* src_addr, socklen_t* src_len) {
3963 __n_assert(buf, return NETW_SOCKET_ERROR);
3964
3965 if (n == 0) {
3966 _netw_capture_error(netw, "Recvfrom of 0 is unsupported.");
3967 n_log(LOG_ERR, "Recvfrom of 0 is unsupported.");
3968 return NETW_SOCKET_ERROR;
3969 }
3970
3971 ssize_t br = recvfrom(netw->link.sock, buf, NETW_BUFLEN_CAST(n), NETFLAGS, src_addr, src_len);
3972 if (br < 0) {
3973 int error = neterrno;
3974 char* errmsg = netstrerror(error);
3975 _netw_capture_error(netw, "UDP socket " SOCKET_SIZE_FORMAT " recvfrom returned %zd, error: %s", netw->link.sock, br, _str(errmsg));
3976 n_log(LOG_ERR, "UDP socket " SOCKET_SIZE_FORMAT " recvfrom returned %zd, error: %s", netw->link.sock, br, _str(errmsg));
3977 FreeNoLog(errmsg);
3978 return NETW_SOCKET_ERROR;
3979 }
3980 return br;
3981} /*netw_udp_recvfrom(...)*/
3982
3992NETWORK* netw_accept_from_ex(NETWORK* from, size_t send_list_limit, size_t recv_list_limit, int blocking, int* retval) {
3993 SOCKET tmp = INVALID_SOCKET;
3994 int error;
3995 char* errmsg = NULL;
3996
3997#if defined(__linux__) || defined(__sun) || defined(_AIX)
3998 socklen_t sin_size = 0;
3999#else
4000 int sin_size = 0;
4001#endif
4002
4003 NETWORK* netw = NULL;
4004
4005 /*checking WSA when under windows*/
4006 if (netw_init_wsa(1, 2, 2) == FALSE) {
4007 n_log(LOG_ERR, "Unable to load WSA dll's");
4008 return NULL;
4009 }
4010
4011 __n_assert(from, return NULL);
4012
4013 netw = netw_new(send_list_limit, recv_list_limit);
4014
4015 sin_size = sizeof(netw->link.raddr);
4016
4017 if (blocking > 0) {
4018 int secs = blocking / 1000;
4019 int usecs = (blocking % 1000) * 1000;
4020 struct timeval select_timeout = {secs, usecs};
4021
4022 fd_set accept_set;
4023 FD_ZERO(&accept_set);
4024#pragma GCC diagnostic push
4025#pragma GCC diagnostic ignored "-Wsign-conversion"
4026 FD_SET(from->link.sock, &accept_set);
4027#pragma GCC diagnostic pop
4028
4029 int ret = select((int)(from->link.sock + 1), &accept_set, NULL, NULL, &select_timeout);
4030 if (ret == -1) {
4031 error = neterrno;
4032 errmsg = netstrerror(error);
4033 if (retval != NULL)
4034 (*retval) = error;
4035 n_log(LOG_DEBUG, "error on select with timeout %ds (%d.%ds), neterrno: %s", blocking, secs, usecs, _str(errmsg));
4036 FreeNoLog(errmsg);
4037 netw_close(&netw);
4038 return NULL;
4039 } else if (ret == 0) {
4040 /* that one produce waaaay too much logs under a lot of cases */
4041 n_log(LOG_DEBUG, "No connection waiting on " SOCKET_SIZE_FORMAT "", from->link.sock);
4042 netw_close(&netw);
4043 return NULL;
4044 }
4045#pragma GCC diagnostic push
4046#pragma GCC diagnostic ignored "-Wsign-conversion"
4047 if (FD_ISSET(from->link.sock, &accept_set)) {
4048#pragma GCC diagnostic pop
4049 // n_log( LOG_DEBUG, "select accept call on %d", from -> link . sock );
4050 tmp = accept(from->link.sock, (struct sockaddr*)&netw->link.raddr, &sin_size);
4051 if (tmp == INVALID_SOCKET) {
4052 error = neterrno;
4053 errmsg = netstrerror(error);
4054 if (retval != NULL)
4055 (*retval) = error;
4056 n_log(LOG_DEBUG, "error accepting on " SOCKET_SIZE_FORMAT ", %s", netw->link.sock, _str(errmsg));
4057 FreeNoLog(errmsg);
4058 netw_close(&netw);
4059 return NULL;
4060 }
4061 } else {
4062 _netw_capture_error(from, "FD_ISSET returned false on " SOCKET_SIZE_FORMAT "", from->link.sock);
4063 n_log(LOG_ERR, "FD_ISSET returned false on " SOCKET_SIZE_FORMAT "", from->link.sock);
4064 netw_close(&netw);
4065 return NULL;
4066 }
4067 netw->link.is_blocking = 0;
4068 } else if (blocking == -1) {
4069 if (from->link.is_blocking != 0) {
4070 netw_set_blocking(from, 0);
4071 }
4072 tmp = accept(from->link.sock, (struct sockaddr*)&netw->link.raddr, &sin_size);
4073 error = neterrno;
4074 if (retval != NULL)
4075 (*retval) = error;
4076 if (tmp == INVALID_SOCKET) {
4077 if (error != EINTR && error != EAGAIN
4078#ifdef __windows__
4079 && error != WSAEWOULDBLOCK
4080#endif
4081 ) {
4082 char* errmsg_nb = netstrerror(error);
4083 // cppcheck-suppress unknownMacro ; SOCKET_SIZE_FORMAT is a platform-specific printf format macro
4084 n_log(LOG_ERR, "accept returned an invalid socket (" SOCKET_SIZE_FORMAT "), neterrno: %s", tmp, _str(errmsg_nb));
4085 FreeNoLog(errmsg_nb);
4086 }
4087 netw_close(&netw);
4088 return NULL;
4089 }
4090 netw->link.is_blocking = 0;
4091 } else {
4092 if (from->link.is_blocking == 0) {
4093 netw_set_blocking(from, 1);
4094 n_log(LOG_DEBUG, "(default) blocking accept call on socket " SOCKET_SIZE_FORMAT "", from->link.sock);
4095 }
4096 tmp = accept(from->link.sock, (struct sockaddr*)&netw->link.raddr, &sin_size);
4097 if (tmp == INVALID_SOCKET) {
4098 error = neterrno;
4099 errmsg = netstrerror(error);
4100 if (retval != NULL)
4101 (*retval) = error;
4102 n_log(LOG_DEBUG, "error accepting on socket " SOCKET_SIZE_FORMAT ", %s", netw->link.sock, _str(errmsg));
4103 FreeNoLog(errmsg);
4104 netw_close(&netw);
4105 return NULL;
4106 }
4107 netw->link.is_blocking = 1;
4108 }
4109 netw->link.sock = tmp;
4110
4111 /* On Windows and Solaris, the accepted socket inherits the non-blocking
4112 * mode from the listening socket. The send/recv engine expects a
4113 * blocking socket, so force it to blocking here. On Linux this is a
4114 * harmless no-op since accept() always returns a blocking socket. */
4116 netw->link.is_blocking = 1;
4117
4118 netw->link.port = strdup(from->link.port);
4119 Malloc(netw->link.ip, char, 64);
4120 if (!netw->link.ip) {
4121 n_log(LOG_ERR, "Error allocating 64 bytes for ip");
4122 netw_close(&netw);
4123 return NULL;
4124 }
4125 if (!inet_ntop(netw->link.raddr.ss_family, get_in_addr(((struct sockaddr*)&netw->link.raddr)), netw->link.ip, 64)) {
4126 error = neterrno;
4127 errmsg = netstrerror(error);
4128 n_log(LOG_ERR, "inet_ntop: %p , %s", (void*)&netw->link.raddr, _str(errmsg));
4129 FreeNoLog(errmsg);
4130 netw_close(&netw);
4131 return NULL;
4132 }
4133
4134 netw_setsockopt(netw, SO_REUSEADDR, 1);
4135 // netw_setsockopt( netw, SO_REUSEPORT, 1 );
4136 netw_setsockopt(netw, SO_KEEPALIVE, 1);
4138 // netw_set_blocking(netw, 1);
4139
4140 n_log(LOG_DEBUG, "Connection accepted from %s:%s socket " SOCKET_SIZE_FORMAT "", netw->link.ip, netw->link.port, netw->link.sock);
4141
4142#ifdef HAVE_OPENSSL
4143 if (from->crypto_algo == NETW_ENCRYPT_OPENSSL) {
4144 netw->ssl = SSL_new(from->ctx);
4145 SSL_set_fd(netw->ssl, (int)netw->link.sock);
4146
4147 netw->send_data = &send_ssl_data;
4148 netw->recv_data = &recv_ssl_data;
4149 netw->send_data_once = &send_ssl_data_once;
4150 netw->recv_data_once = &recv_ssl_data_once;
4151 /* mark accepted netw as SSL so netw_close() runs SSL_free on its ssl;
4152 * ctx stays NULL, it is borrowed from the listener and freed there */
4153 netw->crypto_algo = NETW_ENCRYPT_OPENSSL;
4154
4155 if (SSL_accept(netw->ssl) <= 0) {
4156 error = errno;
4157 _netw_capture_error(netw, "SSL error on %d", netw->link.sock);
4158 n_log(LOG_ERR, "SSL error on %d", netw->link.sock);
4159 if (retval != NULL)
4160 (*retval) = error;
4161 netw_ssl_print_errors(netw->link.sock);
4162 netw_close(&netw);
4163 return NULL;
4164 } else {
4165 n_log(LOG_DEBUG, " socket %d: SSL connection established", netw->link.sock);
4166 }
4167 }
4168#endif
4169
4170 return netw;
4171} /* netw_accept_from_ex(...) */
4172
4179 return netw_accept_from_ex(from, MAX_LIST_ITEMS, MAX_LIST_ITEMS, 0, NULL);
4180} /* network_accept_from( ... ) */
4181
4189 return netw_accept_from_ex(from, MAX_LIST_ITEMS, MAX_LIST_ITEMS, blocking, NULL);
4190} /* network_accept_from( ... ) */
4191
4198/* Process-wide byte counters. See netw_bytes_stats_get in
4199 * n_network_msg.c. Every connection thread updates them, so they are only
4200 * touched with relaxed atomic operations: telemetry, no ordering implied. */
4201long long g_netw_bytes_sent = 0;
4202long long g_netw_bytes_recv = 0;
4203
4205 __n_assert(netw, return FALSE);
4206 __n_assert(msg, return FALSE);
4207 __n_assert(msg->data, return FALSE);
4208
4209 if (msg->length == 0) {
4210 _netw_capture_error(netw, "Empty messages are not supported. msg(%p)->length=%zu", msg, msg->length);
4211 n_log(LOG_ERR, "Empty messages are not supported. msg(%p)->length=%zu", msg, msg->length);
4212 return FALSE;
4213 }
4214
4215 /* Capture msg->written for the byte counter before the push:
4216 * list_push transfers ownership to send_buf with free_nstr_ptr
4217 * as the destructor, so once we release sendbolt the consumer
4218 * (send thread or reactor) can shift, send, and free `msg`,
4219 * making any post-unlock `msg->written` read a use-after-free.
4220 * The race is mostly latent in thread mode (send thread blocks
4221 * on the syscall before freeing) but the reactor consumes within
4222 * microseconds and TSan flags it consistently. */
4223 long long bytes_for_counter = (long long)msg->written;
4224
4225 pthread_mutex_lock(&netw->sendbolt);
4226
4227 if (list_push(netw->send_buf, msg, free_nstr_ptr) == FALSE) {
4228 pthread_mutex_unlock(&netw->sendbolt);
4229 return FALSE;
4230 }
4231
4232 pthread_mutex_unlock(&netw->sendbolt);
4233
4234 /* Reactor mode: wake the reactor's epoll_wait via its
4235 * wake-eventfd so it picks up the new send_buf entry. The
4236 * thread engine's sem_post path stays as the default; the wake
4237 * call is a single non-blocking eventfd write when reactor_mode
4238 * is set. Forward declared in n_reactor.h to avoid a circular
4239 * include with this header, the function pointer dispatch
4240 * happens through netw->reactor_handle.
4241 *
4242 * Compile-time gated on N_REACTOR_AVAILABLE: on Windows / Solaris
4243 * the reactor is unavailable, reactor_mode is always 0, and we
4244 * never want this TU to reference n_reactor_notify_send (which
4245 * would force linking n_reactor.o for a code path that can never
4246 * fire). The unconditional sem_post fallback matches the
4247 * non-reactor branch of the gated form. */
4248#if N_REACTOR_AVAILABLE
4250 extern void n_reactor_notify_send(NETWORK*);
4252 } else {
4253 sem_post(&netw->send_blocker);
4254 }
4255#else
4256 sem_post(&netw->send_blocker);
4257#endif
4258
4259 __atomic_fetch_add(&g_netw_bytes_sent, bytes_for_counter, __ATOMIC_RELAXED);
4260 return TRUE;
4261} /* netw_add_msg(...) */
4262
4270int netw_add_msg_ex(NETWORK* netw, char* str, unsigned int length) {
4271 __n_assert(netw, return FALSE);
4272 __n_assert(str, return FALSE);
4273 if (length == 0) {
4274 return FALSE;
4275 }
4276
4277 N_STR* nstr = NULL;
4278 Malloc(nstr, N_STR, 1);
4279 __n_assert(nstr, return FALSE);
4280
4281 nstr->data = str;
4282 nstr->written = nstr->length = length;
4283
4284 pthread_mutex_lock(&netw->sendbolt);
4285 if (list_push(netw->send_buf, nstr, free_nstr_ptr) == FALSE) {
4286 pthread_mutex_unlock(&netw->sendbolt);
4287 return FALSE;
4288 }
4289 pthread_mutex_unlock(&netw->sendbolt);
4290
4291 sem_post(&netw->send_blocker);
4292
4293 return TRUE;
4294} /* netw_add_msg_ex(...) */
4295
4302 N_STR* ptr = NULL;
4303
4304 __n_assert(netw, return NULL);
4305
4306 pthread_mutex_lock(&netw->recvbolt);
4307
4308 ptr = list_shift(netw->recv_buf, N_STR);
4309
4310 pthread_mutex_unlock(&netw->recvbolt);
4311
4312 if (ptr) __atomic_fetch_add(&g_netw_bytes_recv, (long long)ptr->written, __ATOMIC_RELAXED);
4313 return ptr;
4314} /* netw_get_msg(...)*/
4315
4323N_STR* netw_wait_msg(NETWORK* netw, unsigned int refresh, size_t timeout) {
4324 size_t timed = 0;
4325 unsigned int secs = 0;
4326 unsigned int usecs = 0;
4327
4328 __n_assert(netw, return NULL);
4329
4330 usecs = refresh;
4331 if (refresh > 999999) {
4332 secs = refresh / 1000000;
4333 usecs = refresh % 1000000;
4334 }
4335
4336 if (timeout > 0)
4337 timed = timeout;
4338
4339 n_log(LOG_DEBUG, "wait from socket " SOCKET_SIZE_FORMAT ", refresh: %u usec (%u secs, %u usecs), timeout %zu usec", netw->link.sock, refresh, secs, usecs, timeout);
4340 uint32_t state = NETW_RUN;
4341 int thr_state = 0;
4342 do {
4343 N_STR* nstrptr = netw_get_msg(netw);
4344 if (nstrptr)
4345 return nstrptr;
4346
4347 if (timeout > 0) {
4348 if (timed >= refresh)
4349 timed -= refresh;
4350 if (timed == 0 || timed < refresh) {
4351 _netw_capture_error(netw, "netw " SOCKET_SIZE_FORMAT ", status: %s (%" PRIu32 "), timeouted after waiting %zu msecs", netw->link.sock, N_ENUM_ENTRY(__netw_code_type, toString)(state), state, timeout);
4352 n_log(LOG_ERR, "netw " SOCKET_SIZE_FORMAT ", status: %s (%" PRIu32 "), timeouted after waiting %zu msecs", netw->link.sock, N_ENUM_ENTRY(__netw_code_type, toString)(state), state, timeout);
4353 return NULL;
4354 }
4355 }
4356 if (secs > 0)
4357 sleep(secs);
4358 if (usecs > 0)
4359 u_sleep(usecs);
4360
4361 netw_get_state(netw, &state, &thr_state);
4362 } while (state != NETW_EXITED && state != NETW_ERROR);
4363
4364 _netw_capture_error(netw, "got no answer and netw " SOCKET_SIZE_FORMAT " is no more running, state: %s (%" PRIu32 ")", netw->link.sock, N_ENUM_ENTRY(__netw_code_type, toString)(state), state);
4365 n_log(LOG_ERR, "got no answer and netw " SOCKET_SIZE_FORMAT " is no more running, state: %s (%" PRIu32 ")", netw->link.sock, N_ENUM_ENTRY(__netw_code_type, toString)(state), state);
4366
4367 return NULL;
4368} /* netw_wait_msg(...) */
4369
4376 __n_assert(netw, return FALSE);
4377
4378 pthread_mutex_lock(&netw->eventbolt);
4379 if (netw->threaded_engine_status == NETW_THR_ENGINE_STARTED) {
4380 _netw_capture_error(netw, "THR Engine already started for network %p (%s)", netw, _str(netw->link.ip));
4381 n_log(LOG_ERR, "THR Engine already started for network %p (%s)", netw, _str(netw->link.ip));
4382 pthread_mutex_unlock(&netw->eventbolt);
4383 return FALSE;
4384 }
4385
4386 if (pthread_create(&netw->recv_thr, NULL, netw_recv_func, (void*)netw) != 0) {
4387 _netw_capture_error(netw, "Unable to create recv_thread for network %p (%s)", netw, _str(netw->link.ip));
4388 n_log(LOG_ERR, "Unable to create recv_thread for network %p (%s)", netw, _str(netw->link.ip));
4389 pthread_mutex_unlock(&netw->eventbolt);
4390 return FALSE;
4391 }
4392 netw->nb_running_threads++;
4393 if (pthread_create(&netw->send_thr, NULL, netw_send_func, (void*)netw) != 0) {
4394 _netw_capture_error(netw, "Unable to create send_thread for network %p (%s)", netw, _str(netw->link.ip));
4395 n_log(LOG_ERR, "Unable to create send_thread for network %p (%s)", netw, _str(netw->link.ip));
4396 /* Android's Bionic libc has no pthread_cancel. Stop the just-started
4397 * recv thread the portable way netw_stop_thr_engine already uses: it
4398 * polls NETW_EXIT_ASKED and returns. Elsewhere pthread_cancel is the
4399 * forceful backup for a recv blocked with no read timeout. */
4400#if defined(__ANDROID__)
4402#else
4403 pthread_cancel(netw->recv_thr);
4404#endif
4405 pthread_join(netw->recv_thr, NULL);
4406 netw->nb_running_threads--;
4407 pthread_mutex_unlock(&netw->eventbolt);
4408 return FALSE;
4409 }
4410 netw->nb_running_threads++;
4411
4412 netw->threaded_engine_status = NETW_THR_ENGINE_STARTED;
4413
4414 pthread_mutex_unlock(&netw->eventbolt);
4415
4416 return TRUE;
4417} /* netw_create_recv_thread(....) */
4418
4424void* netw_send_func(void* NET) {
4425 int DONE = 0;
4426
4427 ssize_t net_status = 0;
4428
4429 uint32_t state;
4430 uint32_t nboctet;
4431
4432 char nboct[5] = "";
4433
4434 N_STR* ptr = NULL;
4435
4436 NETWORK* netw = (NETWORK*)NET;
4437 __n_assert(netw, return NULL);
4438
4439 do {
4440 /* do not consume cpu for nothing, reduce delay */
4441 sem_wait(&netw->send_blocker);
4442 int message_sent = 0;
4443 while (message_sent == 0 && !DONE) {
4445 if (state & NETW_ERROR) {
4447 } else if (state & NETW_EXITED) {
4449 } else if (state & NETW_EXIT_ASKED) {
4451 /* sending state */
4452 nboctet = htonl(NETW_EXIT_ASKED);
4453 memcpy(nboct, &nboctet, sizeof(uint32_t));
4454 n_log(LOG_DEBUG, "" SOCKET_SIZE_FORMAT " Sending Quit !", netw->link.sock);
4455 net_status = netw->send_data(netw, nboct, sizeof(int32_t));
4456 /* Peer disconnect during QUIT send is the expected
4457 * end of the handshake; only a real socket error
4458 * deserves the DONE=4 error path. */
4459 if (net_status < 0 && net_status != NETW_SOCKET_DISCONNECTED)
4460 DONE = 4;
4461 n_log(LOG_DEBUG, "" SOCKET_SIZE_FORMAT " Quit sent!", netw->link.sock);
4462 } else {
4463 pthread_mutex_lock(&netw->sendbolt);
4464 ptr = list_shift(netw->send_buf, N_STR);
4465 pthread_mutex_unlock(&netw->sendbolt);
4466 if (ptr && ptr->length > 0 && ptr->data) {
4467 /* Headroom for the 8-byte state+length header so
4468 * frame_len below cannot overflow the uint32 the
4469 * send_data API takes. */
4470 if (ptr->written <= UINT_MAX - 2 * sizeof(uint32_t)) {
4471 n_log(LOG_DEBUG, "Sending ptr size %zu written %zu...", ptr->length, ptr->written);
4472
4473 /* Opportunistic compression. Gated on threshold
4474 * + ratio so small packets and incompressible
4475 * binary snapshots don't pay the codec cost.
4476 * Algorithm picked per-connection via
4477 * netw_set_compression_mode(); NONE skips the
4478 * entire attempt. */
4479 uint32_t pkt_state = state;
4480 if (netw->compress_mode != NETW_COMPRESS_NONE &&
4482 N_STR* zipped = NULL;
4483 uint32_t flag_bit = 0;
4484 if (netw->compress_mode == NETW_COMPRESS_LZ4) {
4485 zipped = zip4_nstr(ptr);
4486 flag_bit = NETW_COMPRESSED_LZ4;
4487 } else {
4488 zipped = zip_nstr(ptr);
4489 flag_bit = NETW_COMPRESSED_ZLIB;
4490 }
4491 if (zipped && zipped->written > 0 &&
4492 zipped->written * 100u <=
4493 ptr->written * (100u - NETW_COMPRESS_MIN_RATIO)) {
4494 /* Good enough shrink, swap. */
4495 free_nstr(&ptr);
4496 ptr = zipped;
4497 pkt_state |= flag_bit;
4498 } else if (zipped) {
4499 free_nstr(&zipped);
4500 }
4501 }
4502
4503 /* Single-write framing. The frame
4504 * used to go out as three separate send_data
4505 * calls (4B state, 4B length, payload): three
4506 * syscalls, and three TLS records on crypto
4507 * sockets, per message, with the two 4-byte
4508 * writes interacting badly with Nagle +
4509 * delayed-ACK on small-message streams (game
4510 * inputs, snapshots). Build the contiguous
4511 * frame and hand the kernel one write.
4512 * Peer disconnect (NETW_SOCKET_DISCONNECTED)
4513 * during the send is a normal end-of-life: the
4514 * connection is over, nothing to log. Only a
4515 * real socket error trips the DONE=1 error
4516 * path so the tail logger emits LOG_ERR. */
4517 size_t frame_len = 2 * sizeof(uint32_t) + ptr->written;
4518 char* frame = NULL;
4519 Malloc(frame, char, frame_len);
4520 if (frame) {
4521 nboctet = htonl(pkt_state);
4522 memcpy(frame, &nboctet, sizeof(uint32_t));
4523 nboctet = htonl((uint32_t)ptr->written);
4524 memcpy(frame + sizeof(uint32_t), &nboctet, sizeof(uint32_t));
4525 memcpy(frame + 2 * sizeof(uint32_t), ptr->data, ptr->written);
4526 net_status = netw->send_data(netw, frame, (uint32_t)frame_len);
4527 if (net_status < 0)
4528 DONE = (net_status == NETW_SOCKET_DISCONNECTED) ? NETW_THR_EXIT_OK : 1;
4529 Free(frame);
4530 } else {
4531 /* OOM building the frame: drop the message
4532 * (stream alignment is preserved, nothing
4533 * was written) and keep the connection up,
4534 * mirroring the oversize-discard branch. */
4535 _netw_capture_error(netw, "could not allocate %zu byte send frame, packet dropped", frame_len);
4536 n_log(LOG_ERR, "could not allocate %zu byte send frame, packet dropped", frame_len);
4537 }
4538 if (netw->send_queue_consecutive_wait >= 0) {
4539 u_sleep((unsigned int)netw->send_queue_consecutive_wait);
4540 }
4541 message_sent = 1;
4542 free_nstr(&ptr);
4543 } else {
4544 _netw_capture_error(netw, "discarded packet of size %zu which is greater than %" PRIu32, ptr->written, UINT_MAX);
4545 n_log(LOG_ERR, "discarded packet of size %zu which is greater than %" PRIu32, ptr->written, UINT_MAX);
4546 message_sent = 1;
4547 free_nstr(&ptr);
4548 }
4549 } else {
4550 /* Empty send_buf or malformed head entry: the
4551 * producer-consumer contract means sem_post was
4552 * called without a usable list_push, or a state
4553 * transition posted without a real message. Break
4554 * out of the inner loop and block on sem_wait
4555 * rather than spinning, spinning grabs sendbolt
4556 * at MHz rates and creates main-thread contention
4557 * in netw_add_msg. */
4558 if (ptr) free_nstr(&ptr);
4559 message_sent = 1;
4560 }
4561 }
4562 }
4563 } while (!DONE);
4564
4565 if (DONE == 1) {
4566 _netw_capture_error(netw, "Error when sending state %" PRIu32 " on socket " SOCKET_SIZE_FORMAT " (%s), network: %s", netw_atomic_read_state(netw), netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4567 n_log(LOG_ERR, "Error when sending state %" PRIu32 " on socket " SOCKET_SIZE_FORMAT " (%s), network: %s", netw_atomic_read_state(netw), netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4568 } else if (DONE == 2) {
4569 _netw_capture_error(netw, "Error when sending number of octet to socket " SOCKET_SIZE_FORMAT " (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4570 n_log(LOG_ERR, "Error when sending number of octet to socket " SOCKET_SIZE_FORMAT " (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4571 } else if (DONE == 3)
4572 n_log(LOG_DEBUG, "Error when sending data on socket " SOCKET_SIZE_FORMAT " (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4573 else if (DONE == 4) {
4574 _netw_capture_error(netw, "Error when sending state QUIT on socket " SOCKET_SIZE_FORMAT " (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4575 n_log(LOG_ERR, "Error when sending state QUIT on socket " SOCKET_SIZE_FORMAT " (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4576 } else if (DONE == 5) {
4577 _netw_capture_error(netw, "Error when sending state QUIT number of octet (0) on socket " SOCKET_SIZE_FORMAT " (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4578 n_log(LOG_ERR, "Error when sending state QUIT number of octet (0) on socket " SOCKET_SIZE_FORMAT " (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4579 }
4580
4581 if (DONE == NETW_THR_EXIT_OK) {
4582 n_log(LOG_DEBUG, "Socket " SOCKET_SIZE_FORMAT ": Sending thread exiting correctly", netw->link.sock);
4584 } else {
4585 _netw_capture_error(netw, "Socket " SOCKET_SIZE_FORMAT " (%s): Sending thread exiting with error %d !", netw->link.sock, _str(netw->link.ip), DONE);
4586 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " (%s): Sending thread exiting with error %d !", netw->link.sock, _str(netw->link.ip), DONE);
4588 }
4589
4590 pthread_mutex_lock(&netw->eventbolt);
4591 netw->nb_running_threads--;
4592 pthread_mutex_unlock(&netw->eventbolt);
4593
4594 pthread_exit(0);
4595
4596 /* suppress compiler warning */
4597#if !defined(__linux__)
4598 return NULL;
4599#endif
4600} /* netw_send_func(...) */
4601
4607void* netw_recv_func(void* NET) {
4608 int DONE = 0;
4609 ssize_t net_status = 0;
4610
4611 uint32_t nboctet;
4612 uint32_t tmpstate;
4613 uint32_t state;
4614
4615 char nboct[5] = "";
4616
4617 N_STR* recvdmsg = NULL;
4618
4619 NETWORK* netw = (NETWORK*)NET;
4620
4621 __n_assert(netw, return NULL);
4622
4623 do {
4625 if (state & NETW_EXIT_ASKED || state & NETW_EXITED) {
4627 }
4628 if (state & NETW_ERROR) {
4630 }
4631 if (!DONE) {
4632 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " : waiting to receive status", netw->link.sock);
4633 /* receiving state */
4634 net_status = netw->recv_data(netw, nboct, sizeof(uint32_t));
4635 if (net_status < 0) {
4636 /* Peer disconnect (NETW_SOCKET_DISCONNECTED) is the
4637 * normal end of a connection, no log noise, exit
4638 * via the clean NETW_THR_EXIT_OK path so the
4639 * application observes NETW_EXIT_ASKED, not
4640 * NETW_ERROR. Only NETW_SOCKET_ERROR routes through
4641 * the DONE=N error tail logger. */
4642 DONE = (net_status == NETW_SOCKET_DISCONNECTED) ? NETW_THR_EXIT_OK : 1;
4643 } else {
4644 memcpy(&nboctet, nboct, sizeof(uint32_t));
4645 tmpstate = ntohl(nboctet);
4646 nboctet = tmpstate;
4647 /* Freeze the per-packet state word before the next
4648 * recv_data call reuses tmpstate for the payload
4649 * length, the NETW_COMPRESSED_* flag check below
4650 * reads pkt_state, not tmpstate. */
4651 uint32_t pkt_state = tmpstate;
4652 if (tmpstate == NETW_EXIT_ASKED) {
4653 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " : receiving order to QUIT !", netw->link.sock);
4655 } else {
4656 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " : waiting to receive next message nboctets", netw->link.sock);
4657 /* receiving nboctet */
4658 net_status = netw->recv_data(netw, nboct, sizeof(uint32_t));
4659 if (net_status < 0) {
4660 DONE = (net_status == NETW_SOCKET_DISCONNECTED) ? NETW_THR_EXIT_OK : 2;
4661 } else {
4662 memcpy(&nboctet, nboct, sizeof(uint32_t));
4663 tmpstate = ntohl(nboctet);
4664 nboctet = tmpstate;
4665
4666 Malloc(recvdmsg, N_STR, 1);
4667 if (!recvdmsg) {
4668 DONE = 3;
4669 } else if (nboctet > NETW_MSG_MAX_FRAME_SIZE) {
4670 /* Hostile / corrupt length prefix. Reject before
4671 * allocating the payload: an unbounded value is a
4672 * memory-DoS, and 0xFFFFFFFF would wrap the
4673 * `nboctet + 1` below to 0, yielding a zero-byte
4674 * buffer that the payload recv then overflows. */
4675 free_nstr(&recvdmsg);
4676 DONE = 8;
4677 } else {
4678 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " : %" PRIu32 " octets to receive...", netw->link.sock, nboctet);
4679 Malloc(recvdmsg->data, char, nboctet + 1);
4680 if (!recvdmsg->data) {
4681 free_nstr(&recvdmsg);
4682 DONE = 4;
4683 } else {
4684 recvdmsg->length = nboctet + 1;
4685 recvdmsg->written = nboctet;
4686
4687 /* receiving the data itself */
4688 net_status = netw->recv_data(netw, recvdmsg->data, nboctet);
4689 if (net_status < 0) {
4690 free_nstr(&recvdmsg);
4691 DONE = (net_status == NETW_SOCKET_DISCONNECTED) ? NETW_THR_EXIT_OK : 5;
4692 } else {
4693 /* Inverse of the compression step on
4694 * the send side. Each NETW_COMPRESSED_*
4695 * bit identifies the codec the sender
4696 * chose; we decode regardless of our
4697 * own compress_mode so two peers
4698 * running different algorithms still
4699 * interop. */
4700 int want_zlib = (pkt_state & NETW_COMPRESSED_ZLIB) != 0;
4701 int want_lz4 = (pkt_state & NETW_COMPRESSED_LZ4) != 0;
4702 if (want_zlib || want_lz4) {
4703 recvdmsg->data[nboctet] = '\0';
4704 N_STR* plain = want_lz4
4705 ? unzip4_nstr(recvdmsg)
4706 : unzip_nstr(recvdmsg);
4707 if (plain) {
4708 free_nstr(&recvdmsg);
4709 recvdmsg = plain;
4710 } else {
4711 n_log(LOG_ERR,
4712 "socket " SOCKET_SIZE_FORMAT " : failed to decompress payload (%" PRIu32 " bytes, codec=%s); dropping",
4713 netw->link.sock, nboctet,
4714 want_lz4 ? "lz4" : "zlib");
4715 free_nstr(&recvdmsg);
4716 DONE = 7;
4717 }
4718 }
4719 if (!DONE) {
4720 pthread_mutex_lock(&netw->recvbolt);
4721 if (list_push(netw->recv_buf, recvdmsg, free_nstr_ptr) == FALSE)
4722 DONE = 6;
4723 pthread_mutex_unlock(&netw->recvbolt);
4724 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " : %" PRIu32 " octets received !", netw->link.sock, nboctet);
4725 }
4726 } /* recv data */
4727 } /* recv data allocation */
4728 } /* recv struct allocation */
4729 } /* recv nb octet*/
4730 } /* exit asked */
4731 } /* recv state */
4732 } /* if( !done) */
4733 } while (!DONE);
4734
4735 if (DONE == 1) {
4736 _netw_capture_error(netw, "Error when receiving state from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4737 n_log(LOG_ERR, "Error when receiving state from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4738 } else if (DONE == 2) {
4739 _netw_capture_error(netw, "Error when receiving nboctet from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4740 n_log(LOG_ERR, "Error when receiving nboctet from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4741 } else if (DONE == 3) {
4742 _netw_capture_error(netw, "Error when receiving data from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4743 n_log(LOG_ERR, "Error when receiving data from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4744 } else if (DONE == 4) {
4745 _netw_capture_error(netw, "Error allocating received message struct from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4746 n_log(LOG_ERR, "Error allocating received message struct from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4747 } else if (DONE == 5) {
4748 _netw_capture_error(netw, "Error allocating received messages data array from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4749 n_log(LOG_ERR, "Error allocating received messages data array from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4750 } else if (DONE == 6) {
4751 _netw_capture_error(netw, "Error adding receved message from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4752 n_log(LOG_ERR, "Error adding receved message from socket " SOCKET_SIZE_FORMAT " (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4753 } else if (DONE == 8) {
4754 _netw_capture_error(netw, "Rejected oversized frame from socket " SOCKET_SIZE_FORMAT " (%s): length exceeds NETW_MSG_MAX_FRAME_SIZE (%lu bytes)", netw->link.sock, _str(netw->link.ip), (unsigned long)NETW_MSG_MAX_FRAME_SIZE);
4755 n_log(LOG_ERR, "Rejected oversized frame from socket " SOCKET_SIZE_FORMAT " (%s): length exceeds NETW_MSG_MAX_FRAME_SIZE (%lu bytes)", netw->link.sock, _str(netw->link.ip), (unsigned long)NETW_MSG_MAX_FRAME_SIZE);
4756 }
4757
4758 if (DONE == NETW_THR_EXIT_OK) {
4759 n_log(LOG_DEBUG, "Socket " SOCKET_SIZE_FORMAT " (%s): Receive thread exiting correctly", netw->link.sock, _str(netw->link.ip));
4761 } else {
4762 _netw_capture_error(netw, "Socket " SOCKET_SIZE_FORMAT " (%s): Receive thread exiting with code %d !", netw->link.sock, _str(netw->link.ip), DONE);
4763 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " (%s): Receive thread exiting with code %d !", netw->link.sock, _str(netw->link.ip), DONE);
4765 }
4766
4767 pthread_mutex_lock(&netw->eventbolt);
4768 netw->nb_running_threads--;
4769 pthread_mutex_unlock(&netw->eventbolt);
4770
4771 pthread_exit(0);
4772
4773 /* suppress compiler warning */
4774#if !defined(__linux__)
4775 return NULL;
4776#endif
4777} /* netw_recv_func(...) */
4778
4785 __n_assert(netw, return FALSE);
4786 uint32_t state = 0;
4787 int thr_engine_status = 0;
4788
4789 n_log(LOG_DEBUG, "Network " SOCKET_SIZE_FORMAT " stop threads event", netw->link.sock);
4790
4791 netw_get_state(netw, &state, &thr_engine_status);
4792
4793 if (thr_engine_status != NETW_THR_ENGINE_STARTED) {
4794 _netw_capture_error(netw, "Thread engine status already stopped for network %p", netw);
4795 n_log(LOG_ERR, "Thread engine status already stopped for network %p", netw);
4796 return FALSE;
4797 }
4798
4800
4801 n_log(LOG_DEBUG, "Network " SOCKET_SIZE_FORMAT " waits for send threads to stop", netw->link.sock);
4802 for (int it = 0; it < 10; it++) {
4803 sem_post(&netw->send_blocker);
4804 usleep(1000);
4805 }
4806 pthread_join(netw->send_thr, NULL);
4807 n_log(LOG_DEBUG, "Network " SOCKET_SIZE_FORMAT " waits for recv threads to stop", netw->link.sock);
4808 pthread_join(netw->recv_thr, NULL);
4809
4811
4812 n_log(LOG_DEBUG, "Network " SOCKET_SIZE_FORMAT " threads Stopped !", netw->link.sock);
4813
4814 return TRUE;
4815} /* Stop_Network( ... ) */
4816
4824ssize_t send_data(void* netw, char* buf, uint32_t n) {
4826 __n_assert(buf, return NETW_SOCKET_ERROR);
4827
4828 SOCKET s = ((NETWORK*)netw)->link.sock;
4829 ssize_t bcount = 0; /* counts bytes sent */
4830 int error;
4831 char* errmsg = NULL;
4832
4833 if (n == 0) {
4834 _netw_capture_error((NETWORK*)netw, "Send of 0 is unsupported.");
4835 n_log(LOG_ERR, "Send of 0 is unsupported.");
4836 return NETW_SOCKET_ERROR;
4837 }
4838
4839 char* tmp_buf = buf;
4840 while (bcount < n) /* loop until all sent */
4841 {
4842 ssize_t bs = 0; /* bytes sent this pass */
4843
4844 NETW_CALL_RETRY(bs, send(s, tmp_buf, NETW_BUFLEN_CAST(n - bcount), NETFLAGS), NETW_MAX_RETRIES);
4845 error = neterrno;
4846
4847 if (bs > 0) {
4848 bcount += bs; /* increment byte counter */
4849 tmp_buf += bs; /* move buffer ptr for next send */
4850 } else if (error == ECONNRESET || error == ENOTCONN || error == EPIPE
4851#ifdef __windows__
4852 || error == WSAECONNRESET || error == WSAENOTCONN || error == WSAESHUTDOWN
4853#endif
4854 ) {
4855 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " disconnected !", s);
4857 } else if (bs == -1) {
4858 /* real network error */
4859 errmsg = netstrerror(error);
4860 _netw_capture_error((NETWORK*)netw, "Socket " SOCKET_SIZE_FORMAT " send error: %zd, %s", s, bs, _str(errmsg));
4861 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " send error: %zd, %s", s, bs, _str(errmsg));
4862 FreeNoLog(errmsg);
4863 return NETW_SOCKET_ERROR;
4864 } else if (bs == -2) {
4865 /* EINTR/WOULDBLOCK retries exhausted */
4866 _netw_capture_error((NETWORK*)netw, "Socket " SOCKET_SIZE_FORMAT " : retry storm on send (%d retries)", s, NETW_MAX_RETRIES);
4867 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " : retry storm on send (%d retries)", s, NETW_MAX_RETRIES);
4868 return NETW_SOCKET_ERROR;
4869 } else if (bs == 0) {
4870 /* should never happen on send() */
4871 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " : send returned 0", s);
4873 }
4874 }
4875 return bcount;
4876} /*send_data(...)*/
4877
4885ssize_t recv_data(void* netw, char* buf, uint32_t n) {
4887 __n_assert(buf, return NETW_SOCKET_ERROR);
4888
4889 SOCKET s = ((NETWORK*)netw)->link.sock;
4890 ssize_t bcount = 0; /* counts bytes read */
4891 int error;
4892 char* errmsg = NULL;
4893
4894#if defined(NETWORK_DISABLE_ZERO_LENGTH_RECV) && (NETWORK_DISABLE_ZERO_LENGTH_RECV == TRUE)
4895 if (n == 0) {
4896 _netw_capture_error((NETWORK*)netw, "Recv of 0 is unsupported.");
4897 n_log(LOG_ERR, "Recv of 0 is unsupported.");
4898 return NETW_SOCKET_ERROR;
4899 }
4900#endif
4901
4902 char* tmp_buf = buf;
4903 while (bcount < n) /* loop until all received */
4904 {
4905 ssize_t br = 0; /* bytes received this pass */
4906
4907 NETW_CALL_RETRY(br, recv(s, tmp_buf, NETW_BUFLEN_CAST(n - bcount), NETFLAGS), NETW_MAX_RETRIES);
4908 error = neterrno;
4909
4910 if (br > 0) {
4911 bcount += br; /* increment byte counter */
4912 tmp_buf += br; /* move buffer ptr for next recv */
4913 } else if (br == 0) {
4914 /* clean shutdown from peer */
4915 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " : peer closed connection", s);
4917 } else if (error == ECONNRESET || error == ENOTCONN
4918#ifdef __windows__
4919 || error == WSAECONNRESET || error == WSAENOTCONN || error == WSAESHUTDOWN
4920#endif
4921 ) {
4922 /* connection reset or not connected */
4923 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " disconnected !", s);
4925 } else if (br == -1) {
4926 /* real network error */
4927 errmsg = netstrerror(error);
4928 _netw_capture_error((NETWORK*)netw, "Socket " SOCKET_SIZE_FORMAT " recv error: %zd, %s", s, br, _str(errmsg));
4929 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " recv error: %zd, %s", s, br, _str(errmsg));
4930 FreeNoLog(errmsg);
4931 return NETW_SOCKET_ERROR;
4932 } else if (br == -2) {
4933 /* EINTR/WOULDBLOCK retries exhausted */
4934 _netw_capture_error((NETWORK*)netw, "Socket " SOCKET_SIZE_FORMAT " : retry storm on recv (%d retries)", s, NETW_MAX_RETRIES);
4935 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " : retry storm on recv (%d retries)", s, NETW_MAX_RETRIES);
4936 return NETW_SOCKET_ERROR;
4937 }
4938 }
4939 return bcount;
4940} /*recv_data(...)*/
4941
4952ssize_t send_data_once(void* netw, char* buf, uint32_t n) {
4954 __n_assert(buf, return NETW_SOCKET_ERROR);
4955
4956 SOCKET s = ((NETWORK*)netw)->link.sock;
4957 if (n == 0) {
4958 _netw_capture_error((NETWORK*)netw, "Send of 0 is unsupported.");
4959 n_log(LOG_ERR, "Send of 0 is unsupported.");
4960 return NETW_SOCKET_ERROR;
4961 }
4962 for (;;) {
4963 ssize_t bs = send(s, buf, NETW_BUFLEN_CAST(n), NETFLAGS);
4964 int error = neterrno;
4965 if (bs > 0) return bs;
4966 if (bs == 0) {
4967 /* should never happen on send() */
4968 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " : send returned 0", s);
4970 }
4971 if (error == EINTR
4972#ifdef __windows__
4973 || error == WSAEINTR
4974#endif
4975 )
4976 continue;
4977 if (error == EAGAIN || error == EWOULDBLOCK
4978#ifdef __windows__
4979 || error == WSAEWOULDBLOCK
4980#endif
4981 )
4982 return NETW_IO_WANT_WRITE;
4983 if (error == ECONNRESET || error == ENOTCONN || error == EPIPE
4984#ifdef __windows__
4985 || error == WSAECONNRESET || error == WSAENOTCONN || error == WSAESHUTDOWN
4986#endif
4987 ) {
4988 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " disconnected !", s);
4990 }
4991 char* errmsg = netstrerror(error);
4992 _netw_capture_error((NETWORK*)netw, "Socket " SOCKET_SIZE_FORMAT " send_once error: %s", s, _str(errmsg));
4993 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " send_once error: %s", s, _str(errmsg));
4994 FreeNoLog(errmsg);
4995 return NETW_SOCKET_ERROR;
4996 }
4997} /*send_data_once(...)*/
4998
5008ssize_t recv_data_once(void* netw, char* buf, uint32_t n) {
5010 __n_assert(buf, return NETW_SOCKET_ERROR);
5011
5012 SOCKET s = ((NETWORK*)netw)->link.sock;
5013 if (n == 0) {
5014 _netw_capture_error((NETWORK*)netw, "Recv of 0 is unsupported.");
5015 n_log(LOG_ERR, "Recv of 0 is unsupported.");
5016 return NETW_SOCKET_ERROR;
5017 }
5018 for (;;) {
5019 ssize_t br = recv(s, buf, NETW_BUFLEN_CAST(n), NETFLAGS);
5020 int error = neterrno;
5021 if (br > 0) return br;
5022 if (br == 0) {
5023 /* orderly shutdown by peer */
5024 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " disconnected !", s);
5026 }
5027 if (error == EINTR
5028#ifdef __windows__
5029 || error == WSAEINTR
5030#endif
5031 )
5032 continue;
5033 if (error == EAGAIN || error == EWOULDBLOCK
5034#ifdef __windows__
5035 || error == WSAEWOULDBLOCK
5036#endif
5037 )
5038 return NETW_IO_WANT_READ;
5039 if (error == ECONNRESET || error == ENOTCONN
5040#ifdef __windows__
5041 || error == WSAECONNRESET || error == WSAENOTCONN
5042#endif
5043 ) {
5044 n_log(LOG_DEBUG, "socket " SOCKET_SIZE_FORMAT " disconnected !", s);
5046 }
5047 char* errmsg = netstrerror(error);
5048 _netw_capture_error((NETWORK*)netw, "Socket " SOCKET_SIZE_FORMAT " recv_once error: %s", s, _str(errmsg));
5049 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " recv_once error: %s", s, _str(errmsg));
5050 FreeNoLog(errmsg);
5051 return NETW_SOCKET_ERROR;
5052 }
5053} /*recv_data_once(...)*/
5054
5055#ifdef HAVE_OPENSSL
5063ssize_t send_ssl_data(void* netw, char* buf, uint32_t n) {
5064 __n_assert(netw, return -1);
5065 __n_assert(buf, return -1);
5066
5067 SSL* ssl = ((NETWORK*)netw)->ssl;
5068 __n_assert(ssl, return -1);
5069
5070 SOCKET s = ((NETWORK*)netw)->link.sock;
5071
5072 ssize_t bcount = 0; // counts bytes sent
5073 int error;
5074 char* errmsg = NULL;
5075
5076 if (n == 0) {
5077 _netw_capture_error((NETWORK*)netw, "Send of 0 is unsupported.");
5078 n_log(LOG_ERR, "Send of 0 is unsupported.");
5079 return -1;
5080 }
5081
5082 while (bcount < n) // loop until full buffer
5083 {
5084 size_t bs = 0; // bytes sent this pass
5085 /* the thread's error queue must be empty before the call, or
5086 * SSL_get_error() reports another connection's stale error */
5087 ERR_clear_error();
5088#if OPENSSL_VERSION_NUMBER < 0x1010107fL
5089 int status = SSL_write(ssl, buf, (int)(n - bcount)); // OpenSSL < 1.1.1
5090 bs = (status > 0) ? (size_t)status : 0;
5091#else
5092 int status = SSL_write_ex(ssl, buf, (size_t)(n - bcount), &bs); // OpenSSL >= 1.1.1
5093#endif
5094 error = neterrno;
5095 if (status > 0) {
5096 bcount += (ssize_t)bs; // increment byte counter
5097 buf += bs; // move buffer ptr for next read
5098 } else {
5099 int ssl_error = SSL_get_error(ssl, status);
5100 if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
5101 /* Rare on the blocking sockets the thread engine uses
5102 * (renegotiation / partial record). usleep(0) was a
5103 * pure busy-yield, burn a bounded 1 ms instead so a
5104 * stuck WANT state can't peg a core. */
5105 u_sleep(1000);
5106 continue;
5107 }
5108 /* Peer-closed connection is a normal end-of-life, not a
5109 * protocol error. SSL_ERROR_ZERO_RETURN is a clean TLS
5110 * close_notify; SSL_ERROR_SYSCALL with errno EPIPE /
5111 * ECONNRESET / ENOTCONN (or errno 0, OpenSSL's "unexpected
5112 * EOF" signal) means the socket went away under us. Mirror
5113 * the plaintext send_data() path: log at DEBUG and return
5114 * NETW_SOCKET_DISCONNECTED so the send thread's QUIT / normal
5115 * branches treat it as a graceful exit instead of an error.
5116 * Without this, a TLS client quitting after the server has
5117 * already dropped the link logs three spurious LOG_ERR lines
5118 * (SSL_write syscall error / state QUIT / thread exit err 4). */
5119 if (ssl_error == SSL_ERROR_ZERO_RETURN ||
5120 (ssl_error == SSL_ERROR_SYSCALL &&
5121 (error == 0 || error == EPIPE || error == ECONNRESET || error == ENOTCONN
5122#ifdef __windows__
5123 || error == WSAECONNRESET || error == WSAENOTCONN || error == WSAESHUTDOWN
5124#endif
5125 ))) {
5126 n_log(LOG_DEBUG, "socket %d disconnected during SSL_write !", s);
5128 }
5129 errmsg = netstrerror(error);
5130 switch (ssl_error) {
5131 case SSL_ERROR_SYSCALL:
5132 // Real, unexpected syscall failure (errno set to something other than a peer close)
5133 _netw_capture_error((NETWORK*)netw, "socket %d SSL_write syscall error: %s", s, _str(errmsg));
5134 n_log(LOG_ERR, "socket %d SSL_write syscall error: %s", s, _str(errmsg));
5135 break;
5136 case SSL_ERROR_SSL:
5137 // Handle SSL protocol failure
5138 _netw_capture_error((NETWORK*)netw, "socket %d SSL_write returned %zu, error: %s", s, bs, ERR_reason_error_string(ERR_get_error()));
5139 n_log(LOG_ERR, "socket %d SSL_write returned %zu, error: %s", s, bs, ERR_reason_error_string(ERR_get_error()));
5140 break;
5141 default:
5142 // Other errors
5143 _netw_capture_error((NETWORK*)netw, "socket %d SSL_write returned %zu, errno: %s", s, bs, _str(errmsg));
5144 n_log(LOG_ERR, "socket %d SSL_write returned %zu, errno: %s", s, bs, _str(errmsg));
5145 break;
5146 }
5147 FreeNoLog(errmsg);
5148 return NETW_SOCKET_ERROR;
5149 }
5150 }
5151 return bcount;
5152} /*send_ssl_data(...)*/
5153
5161ssize_t recv_ssl_data(void* netw, char* buf, uint32_t n) {
5162 __n_assert(netw, return -1);
5163 __n_assert(buf, return -1);
5164
5165 SSL* ssl = ((NETWORK*)netw)->ssl;
5166 __n_assert(ssl, return -1);
5167
5168 SOCKET s = ((NETWORK*)netw)->link.sock;
5169 ssize_t bcount = 0; // counts bytes read
5170 int error;
5171 char* errmsg = NULL;
5172
5173 if (n == 0) {
5174 _netw_capture_error((NETWORK*)netw, "Recv of 0 is unsupported.");
5175 n_log(LOG_ERR, "Recv of 0 is unsupported.");
5176 return -1;
5177 }
5178
5179 while (bcount < n) {
5180 size_t br = 0; // bytes read this pass
5181 // loop until full buffer
5182 ERR_clear_error(); /* see send_ssl_data: a stale entry misreports */
5183#if OPENSSL_VERSION_NUMBER < 0x10101000L
5184 int status = SSL_read(ssl, buf, (int)(n - bcount)); // OpenSSL < 1.1.1
5185 br = (status > 0) ? (size_t)status : 0;
5186#else
5187 int status = SSL_read_ex(ssl, buf, (size_t)(n - bcount), &br); // OpenSSL >= 1.1.1
5188#endif
5189 error = neterrno;
5190 if (status > 0) {
5191 bcount += (ssize_t)br; // increment byte counter
5192 buf += br; // move buffer ptr for next read
5193 } else {
5194 int ssl_error = SSL_get_error(ssl, status);
5195 if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
5196 /* Same bounded back-off as send_ssl_data, usleep(0)
5197 * was a busy-yield that could peg a core on a stuck
5198 * WANT state. */
5199 u_sleep(1000);
5200 continue;
5201 }
5202 /* Connection teardown is the normal end of a session, not an
5203 * error. Mirror the plaintext recv_data() and the
5204 * non-blocking recv_ssl_data_once() paths: return
5205 * NETW_SOCKET_DISCONNECTED so netw_recv_func() exits via the
5206 * clean NETW_THR_EXIT_OK path with no LOG_ERR noise -- and so
5207 * the recv thread does not flip the NETWORK into NETW_ERROR,
5208 * which is what was tripping the send thread into the
5209 * "exiting with error 666" (NETW_THR_EXIT_ERROR) tail on a
5210 * normal client exit. */
5211 if (ssl_error == SSL_ERROR_ZERO_RETURN) {
5212 /* peer sent a clean close_notify */
5213 n_log(LOG_DEBUG, "socket %d : TLS session closed by peer (close_notify)", s);
5215 }
5216 if (ssl_error == SSL_ERROR_SYSCALL && (error == 0 || error == ECONNRESET || error == ENOTCONN
5217#ifdef __windows__
5218 || error == WSAECONNRESET || error == WSAENOTCONN || error == WSAESHUTDOWN
5219#endif
5220 )) {
5221 /* peer vanished without close_notify (TCP FIN/RST) */
5222 n_log(LOG_DEBUG, "socket %d : peer closed connection (TLS, no close_notify)", s);
5224 }
5225 /* Pop the queued reason exactly ONCE and reuse it below;
5226 * calling ERR_get_error() twice (capture + log) cleared the
5227 * queue between calls, so the second read always printed
5228 * "(null)" instead of the real reason. */
5229 unsigned long ssl_reason = ERR_get_error();
5230 ERR_clear_error(); /* the rest would fail the next connection */
5231#ifdef SSL_R_UNEXPECTED_EOF_WHILE_READING
5232 if (ssl_error == SSL_ERROR_SSL && ERR_GET_REASON(ssl_reason) == SSL_R_UNEXPECTED_EOF_WHILE_READING) {
5233 /* OpenSSL >= 3.0 reports an unclean TCP close as an
5234 * SSL_ERROR_SSL/UNEXPECTED_EOF rather than the
5235 * SSL_ERROR_SYSCALL/errno-0 of 1.1.1; still a disconnect. */
5236 n_log(LOG_DEBUG, "socket %d : peer closed connection (TLS unexpected EOF)", s);
5238 }
5239#endif
5240 errmsg = netstrerror(error);
5241 switch (ssl_error) {
5242 case SSL_ERROR_SYSCALL:
5243 // Genuine syscall-level failure (not a peer close)
5244 _netw_capture_error((NETWORK*)netw, "socket %d SSL_read syscall error: %s", s, _str(errmsg));
5245 n_log(LOG_ERR, "socket %d SSL_read syscall error: %s", s, _str(errmsg));
5246 break;
5247 case SSL_ERROR_SSL:
5248 // Genuine TLS protocol failure
5249 _netw_capture_error((NETWORK*)netw, "socket %d SSL_read protocol error: %s", s, _str((char*)ERR_reason_error_string(ssl_reason)));
5250 n_log(LOG_ERR, "socket %d SSL_read protocol error: %s", s, _str((char*)ERR_reason_error_string(ssl_reason)));
5251 break;
5252 default:
5253 // Other errors
5254 _netw_capture_error((NETWORK*)netw, "socket %d SSL_read returned %zu, errno: %s", s, br, _str(errmsg));
5255 n_log(LOG_ERR, "socket %d SSL_read returned %zu, errno: %s", s, br, _str(errmsg));
5256 break;
5257 }
5258 FreeNoLog(errmsg);
5259 return NETW_SOCKET_ERROR;
5260 }
5261 }
5262 return bcount;
5263} /*recv_ssl_data(...)*/
5264
5279ssize_t send_ssl_data_once(void* netw, char* buf, uint32_t n) {
5281 __n_assert(buf, return NETW_SOCKET_ERROR);
5282
5283 SSL* ssl = ((NETWORK*)netw)->ssl;
5284 __n_assert(ssl, return NETW_SOCKET_ERROR);
5285 SOCKET s = ((NETWORK*)netw)->link.sock;
5286
5287 if (n == 0) {
5288 _netw_capture_error((NETWORK*)netw, "Send of 0 is unsupported.");
5289 n_log(LOG_ERR, "Send of 0 is unsupported.");
5290 return NETW_SOCKET_ERROR;
5291 }
5292
5293 size_t bs = 0;
5294 /* OpenSSL requires an EMPTY error queue before SSL_write/SSL_read:
5295 * SSL_get_error() returns SSL_ERROR_SSL whenever the queue holds
5296 * anything, so a stale entry left by ANOTHER connection (a shutdown
5297 * mid-handshake, say) turns this one's ordinary WANT_WRITE into a
5298 * fatal error, and a single-threaded server closes an innocent
5299 * connection with the other one's reason. Measured in DarkChan
5300 * (2026-10-08): a client's first frames were lost that way. */
5301 ERR_clear_error();
5302#if OPENSSL_VERSION_NUMBER < 0x1010107fL
5303 int status = SSL_write(ssl, buf, (int)n);
5304 bs = (status > 0) ? (size_t)status : 0;
5305#else
5306 int status = SSL_write_ex(ssl, buf, (size_t)n, &bs);
5307#endif
5308 int error = neterrno;
5309 if (status > 0) return (ssize_t)bs;
5310
5311 int ssl_error = SSL_get_error(ssl, status);
5312 switch (ssl_error) {
5313 case SSL_ERROR_WANT_READ:
5314 return NETW_IO_WANT_READ;
5315 case SSL_ERROR_WANT_WRITE:
5316 return NETW_IO_WANT_WRITE;
5317 case SSL_ERROR_ZERO_RETURN:
5318 n_log(LOG_DEBUG, "socket %d TLS session closed by peer", s);
5320 case SSL_ERROR_SYSCALL:
5321 if (error == 0 || error == ECONNRESET || error == EPIPE || error == ENOTCONN
5322#ifdef __windows__
5323 || error == WSAECONNRESET || error == WSAENOTCONN
5324#endif
5325 ) {
5326 n_log(LOG_DEBUG, "socket %d SSL_write syscall: connection closed by peer", s);
5328 }
5329 if (error == EINTR || error == EAGAIN || error == EWOULDBLOCK
5330#ifdef __windows__
5331 || error == WSAEINTR || error == WSAEWOULDBLOCK
5332#endif
5333 )
5334 return NETW_IO_WANT_WRITE;
5335 {
5336 char* errmsg = netstrerror(error);
5337 _netw_capture_error((NETWORK*)netw, "socket %d SSL_write_once syscall error: %s", s, _str(errmsg));
5338 n_log(LOG_ERR, "socket %d SSL_write_once syscall error: %s", s, _str(errmsg));
5339 FreeNoLog(errmsg);
5340 }
5341 return NETW_SOCKET_ERROR;
5342 default: {
5343 /* pop the reason ONCE (two ERR_get_error() calls logged the
5344 * second entry, or "(null)"), then drop the rest so it cannot
5345 * fail the next connection's I/O */
5346 unsigned long ssl_reason = ERR_get_error();
5347 ERR_clear_error();
5348 _netw_capture_error((NETWORK*)netw, "socket %d SSL_write_once error: %s", s, _str((char*)ERR_reason_error_string(ssl_reason)));
5349 n_log(LOG_ERR, "socket %d SSL_write_once error: %s", s, _str((char*)ERR_reason_error_string(ssl_reason)));
5350 return NETW_SOCKET_ERROR;
5351 }
5352 }
5353} /*send_ssl_data_once(...)*/
5354
5367ssize_t recv_ssl_data_once(void* netw, char* buf, uint32_t n) {
5369 __n_assert(buf, return NETW_SOCKET_ERROR);
5370
5371 SSL* ssl = ((NETWORK*)netw)->ssl;
5372 __n_assert(ssl, return NETW_SOCKET_ERROR);
5373 SOCKET s = ((NETWORK*)netw)->link.sock;
5374
5375 if (n == 0) {
5376 _netw_capture_error((NETWORK*)netw, "Recv of 0 is unsupported.");
5377 n_log(LOG_ERR, "Recv of 0 is unsupported.");
5378 return NETW_SOCKET_ERROR;
5379 }
5380
5381 size_t br = 0;
5382 ERR_clear_error(); /* empty before the call: see send_ssl_data_once */
5383#if OPENSSL_VERSION_NUMBER < 0x10101000L
5384 int status = SSL_read(ssl, buf, (int)n);
5385 br = (status > 0) ? (size_t)status : 0;
5386#else
5387 int status = SSL_read_ex(ssl, buf, (size_t)n, &br);
5388#endif
5389 int error = neterrno;
5390 if (status > 0) return (ssize_t)br;
5391
5392 int ssl_error = SSL_get_error(ssl, status);
5393 switch (ssl_error) {
5394 case SSL_ERROR_WANT_READ:
5395 return NETW_IO_WANT_READ;
5396 case SSL_ERROR_WANT_WRITE:
5397 return NETW_IO_WANT_WRITE;
5398 case SSL_ERROR_ZERO_RETURN:
5399 n_log(LOG_DEBUG, "socket %d TLS session closed by peer", s);
5401 case SSL_ERROR_SYSCALL:
5402 if (error == 0 || error == ECONNRESET || error == ENOTCONN
5403#ifdef __windows__
5404 || error == WSAECONNRESET || error == WSAENOTCONN
5405#endif
5406 ) {
5407 n_log(LOG_DEBUG, "socket %d SSL_read syscall: connection closed by peer", s);
5409 }
5410 if (error == EINTR || error == EAGAIN || error == EWOULDBLOCK
5411#ifdef __windows__
5412 || error == WSAEINTR || error == WSAEWOULDBLOCK
5413#endif
5414 )
5415 return NETW_IO_WANT_READ;
5416 {
5417 char* errmsg = netstrerror(error);
5418 _netw_capture_error((NETWORK*)netw, "socket %d SSL_read_once syscall error: %s", s, _str(errmsg));
5419 n_log(LOG_ERR, "socket %d SSL_read_once syscall error: %s", s, _str(errmsg));
5420 FreeNoLog(errmsg);
5421 }
5422 return NETW_SOCKET_ERROR;
5423 default: {
5424 /* Pop the reason once (a second ERR_get_error() would clear
5425 * the queue and print "(null)"). OpenSSL >= 3.0 reports an
5426 * unclean TCP close here as SSL_ERROR_SSL/UNEXPECTED_EOF --
5427 * a disconnect, not a protocol error. */
5428 unsigned long ssl_reason = ERR_get_error();
5429 ERR_clear_error(); /* the rest would fail the next connection */
5430#ifdef SSL_R_UNEXPECTED_EOF_WHILE_READING
5431 if (ssl_error == SSL_ERROR_SSL && ERR_GET_REASON(ssl_reason) == SSL_R_UNEXPECTED_EOF_WHILE_READING) {
5432 n_log(LOG_DEBUG, "socket %d : peer closed connection (TLS unexpected EOF)", s);
5434 }
5435#endif
5436 _netw_capture_error((NETWORK*)netw, "socket %d SSL_read_once error: %s", s, _str((char*)ERR_reason_error_string(ssl_reason)));
5437 n_log(LOG_ERR, "socket %d SSL_read_once error: %s", s, _str((char*)ERR_reason_error_string(ssl_reason)));
5438 return NETW_SOCKET_ERROR;
5439 }
5440 }
5441} /*recv_ssl_data_once(...)*/
5442#endif
5443
5452ssize_t send_php(SOCKET s, int _code, char* buf, int n) {
5453 ssize_t bcount = 0; /* counts bytes read */
5454 ssize_t bs; /* bytes read this pass */
5455
5456 int error;
5457 char* errmsg = NULL;
5458
5459 char* ptr = NULL; /* temp char ptr ;-) */
5460 char head[HEAD_SIZE + 1] = "";
5461 char code[HEAD_CODE + 1] = "";
5462
5463 // Format and assign to head
5464 snprintf(head, HEAD_SIZE + 1, "%0*d", HEAD_SIZE, n);
5465 // Format and assign to code
5466 snprintf(code, HEAD_CODE + 1, "%0*d", HEAD_CODE, _code);
5467
5468 /* sending head */
5469 bcount = 0;
5470 ptr = head;
5471 while (bcount < HEAD_SIZE) /* loop until full buffer */
5472 {
5473 bs = send(s, ptr, NETW_BUFLEN_CAST(HEAD_SIZE - bcount), NETFLAGS);
5474 error = neterrno;
5475 if (bs > 0) {
5476 bcount += bs; /* increment byte counter */
5477 ptr += bs; /* move buffer ptr for next read */
5478 } else {
5479 /* signal an error to the caller */
5480 errmsg = netstrerror(error);
5481 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " sending Error %zd when sending head size, neterrno: %s", s, bs, _str(errmsg));
5482 FreeNoLog(errmsg);
5483 return -1;
5484 }
5485 }
5486
5487 /* sending code */
5488 bcount = 0;
5489 ptr = code;
5490 while (bcount < HEAD_CODE) /* loop until full buffer */
5491 {
5492 bs = send(s, ptr, NETW_BUFLEN_CAST(HEAD_CODE - bcount), NETFLAGS);
5493 error = neterrno;
5494 if (bs > 0) {
5495 bcount += bs; /* increment byte counter */
5496 ptr += bs; /* move buffer ptr for next read */
5497 } else {
5498 errmsg = netstrerror(error);
5499 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " sending Error %zd when sending head code, neterrno: %s", s, bs, _str(errmsg));
5500 FreeNoLog(errmsg);
5501 return -1;
5502 }
5503 }
5504
5505 /* sending buf */
5506 bcount = 0;
5507 while (bcount < n) /* loop until full buffer */
5508 {
5509 bs = send(s, buf, NETW_BUFLEN_CAST(n - bcount), NETFLAGS);
5510 error = neterrno;
5511 if (bs > 0) {
5512 bcount += bs; /* increment byte counter */
5513 buf += bs; /* move buffer ptr for next read */
5514 } else {
5515 /* signal an error to the caller */
5516 errmsg = netstrerror(error);
5517 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " sending Error %zd when sending message of size %d, neterrno: %s", s, bs, n, _str(errmsg));
5518 FreeNoLog(errmsg);
5519 return -1;
5520 }
5521 }
5522
5523 return bcount;
5524
5525} /*send_php(...)*/
5526
5534ssize_t recv_php(SOCKET s, int* _code, char** buf) {
5535 ssize_t bcount = 0; /* counts bytes read */
5536 ssize_t br; /* bytes read this pass */
5537 long int tmpnb = 0, size = 0; /* size of message to receive */
5538 char* ptr = NULL;
5539
5540 int error;
5541 char* errmsg = NULL;
5542
5543 char head[HEAD_SIZE + 1] = "";
5544 char code[HEAD_CODE + 1] = "";
5545
5546 /* Receiving total message size */
5547 bcount = 0;
5548 ptr = head;
5549 while (bcount < HEAD_SIZE) {
5550 /* loop until full buffer */
5551 br = recv(s, ptr, NETW_BUFLEN_CAST(HEAD_SIZE - bcount), NETFLAGS);
5552 error = neterrno;
5553 if (br > 0) {
5554 bcount += br; /* increment byte counter */
5555 ptr += br; /* move buffer ptr for next read */
5556 } else {
5557 /* signal an error to the caller */
5558 errmsg = netstrerror(error);
5559 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " receive %zd Error %s", s, br, _str(errmsg));
5560 FreeNoLog(errmsg);
5561 return FALSE;
5562 }
5563 }
5564
5565 tmpnb = strtol(head, NULL, 10);
5566 if (tmpnb == LONG_MIN || tmpnb == LONG_MAX) {
5567 n_log(LOG_ERR, "Size received ( %ld ) can not be determined on socket " SOCKET_SIZE_FORMAT "", tmpnb, s);
5568 return FALSE;
5569 }
5570
5571 size = tmpnb;
5572
5573 /* receiving request code */
5574 bcount = 0;
5575 ptr = code;
5576 while (bcount < HEAD_CODE) {
5577 /* loop until full buffer */
5578 br = recv(s, ptr, NETW_BUFLEN_CAST(HEAD_CODE - bcount), NETFLAGS);
5579 error = neterrno;
5580 if (br > 0) {
5581 bcount += br; /* increment byte counter */
5582 ptr += br; /* move buffer ptr for next read */
5583 } else {
5584 /* signal an error to the caller */
5585 errmsg = netstrerror(error);
5586 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " receive %zd Error , neterrno: %s", s, br, _str(errmsg));
5587 FreeNoLog(errmsg);
5588 return FALSE;
5589 }
5590 }
5591
5592 tmpnb = strtol(code, NULL, 10);
5593 if (tmpnb <= INT_MIN || tmpnb >= INT_MAX) {
5594 n_log(LOG_ERR, "Code received ( %ld ) too big or too little to be valid code on socket " SOCKET_SIZE_FORMAT "", tmpnb, s);
5595 return FALSE;
5596 }
5597
5598 (*_code) = (int)tmpnb;
5599
5600 /* receving message */
5601 if ((*buf)) {
5602 // cppcheck-suppress identicalInnerCondition ; Free() macro rechecks pointer internally
5603 Free((*buf));
5604 }
5605 Malloc((*buf), char, (size_t)(size + 1));
5606 if (!(*buf)) {
5607 n_log(LOG_ERR, "Could not allocate PHP receive buf");
5608 return FALSE;
5609 }
5610
5611 bcount = 0;
5612 ptr = (*buf);
5613 while (bcount < size) {
5614 /* loop until full buffer */
5615 br = recv(s, ptr, NETW_BUFLEN_CAST(size - bcount), NETFLAGS);
5616 error = neterrno;
5617 if (br > 0) {
5618 bcount += br; /* increment byte counter */
5619 ptr += br; /* move buffer ptr for next read */
5620 } else {
5621 /* signal an error to the caller */
5622 errmsg = netstrerror(error);
5623 n_log(LOG_ERR, "Socket " SOCKET_SIZE_FORMAT " receive %zd Error neterrno: %s", s, br, _str(errmsg));
5624 FreeNoLog(errmsg);
5625 return FALSE;
5626 }
5627 }
5628 return size;
5629} /*recv_php(...)*/
5630
5639int netw_get_queue_status(NETWORK* netw, size_t* nb_to_send, size_t* nb_to_read) {
5640 __n_assert(netw, return FALSE);
5641
5642 pthread_mutex_lock(&netw->sendbolt);
5643 (*nb_to_send) = netw->send_buf->nb_items;
5644 pthread_mutex_unlock(&netw->sendbolt);
5645
5646 pthread_mutex_lock(&netw->recvbolt);
5647 (*nb_to_read) = netw->recv_buf->nb_items;
5648 pthread_mutex_unlock(&netw->recvbolt);
5649
5650 return TRUE;
5651} /* get queue states */
5652
5658NETWORK_POOL* netw_new_pool(size_t nb_min_element) {
5659 NETWORK_POOL* netw_pool = NULL;
5660
5661 Malloc(netw_pool, NETWORK_POOL, 1);
5662 __n_assert(netw_pool, return NULL);
5663
5664 netw_pool->pool = new_ht(nb_min_element);
5665 __n_assert(netw_pool->pool, Free(netw_pool); return NULL);
5666
5667 init_lock(netw_pool->rwlock);
5668
5669 return netw_pool;
5670} /* netw_new_pool() */
5671
5678 __n_assert(netw_pool && (*netw_pool), return FALSE);
5679
5680 write_lock((*netw_pool)->rwlock);
5681 if ((*netw_pool)->pool)
5682 destroy_ht(&(*netw_pool)->pool);
5683 unlock((*netw_pool)->rwlock);
5684
5685 rw_lock_destroy((*netw_pool)->rwlock);
5686
5687 Free((*netw_pool));
5688
5689 return TRUE;
5690} /* netw_destroy_pool() */
5691
5696void netw_pool_netw_close(void* netw_ptr) {
5697 NETWORK* netw = (NETWORK*)netw_ptr;
5698 __n_assert(netw, return);
5699 n_log(LOG_DEBUG, "Network pool %p: network id " SOCKET_SIZE_FORMAT " still active !!", netw, netw->link.sock);
5700 return;
5701} /* netw_pool_netw_close() */
5702
5710 __n_assert(netw_pool, return FALSE);
5711 __n_assert(netw, return FALSE);
5712
5713 n_log(LOG_DEBUG, "Trying to add %lld to %p", (unsigned long long)netw->link.sock, netw_pool->pool);
5714
5715 /* write lock the pool */
5716 write_lock(netw_pool->rwlock);
5717 /* test if not already added */
5718 N_STR* key = NULL;
5719 nstrprintf(key, "%lld", (unsigned long long)netw->link.sock);
5720 HASH_NODE* node = NULL;
5721 if (ht_get_ptr(netw_pool->pool, _nstr(key), (void*)&node) == TRUE) {
5722 _netw_capture_error(netw, "Network id " SOCKET_SIZE_FORMAT " already added !", netw->link.sock);
5723 n_log(LOG_ERR, "Network id " SOCKET_SIZE_FORMAT " already added !", netw->link.sock);
5724 free_nstr(&key);
5725 unlock(netw_pool->rwlock);
5726 return FALSE;
5727 }
5728 int retval;
5729 /* add it */
5730 if ((retval = ht_put_ptr(netw_pool->pool, _nstr(key), netw, &netw_pool_netw_close, NULL)) == TRUE) {
5731 if ((retval = list_push(netw->pools, netw_pool, NULL)) == TRUE) {
5732 n_log(LOG_DEBUG, "added netw " SOCKET_SIZE_FORMAT " to pool %p", netw->link.sock, netw_pool);
5733 } else {
5734 _netw_capture_error(netw, "could not add netw " SOCKET_SIZE_FORMAT " to pool %p", netw->link.sock, netw_pool);
5735 n_log(LOG_ERR, "could not add netw " SOCKET_SIZE_FORMAT " to pool %p", netw->link.sock, netw_pool);
5736 }
5737 } else {
5738 _netw_capture_error(netw, "could not add netw " SOCKET_SIZE_FORMAT " to pool %p", netw->link.sock, netw_pool);
5739 n_log(LOG_ERR, "could not add netw " SOCKET_SIZE_FORMAT " to pool %p", netw->link.sock, netw_pool);
5740 }
5741 free_nstr(&key);
5742
5743 /* unlock the pool */
5744 unlock(netw_pool->rwlock);
5745
5746 return retval;
5747} /* netw_pool_add() */
5748
5756 __n_assert(netw_pool, return FALSE);
5757 __n_assert(netw, return FALSE);
5758
5759 /* write lock the pool */
5760 write_lock(netw_pool->rwlock);
5761 /* test if present */
5762 N_STR* key = NULL;
5763 nstrprintf(key, "%lld", (unsigned long long int)netw->link.sock);
5764 if (ht_remove(netw_pool->pool, _nstr(key)) == TRUE) {
5765 LIST_NODE* node = list_search(netw->pools, netw_pool);
5766 if (node) {
5767 if (!remove_list_node(netw->pools, node, NETWORK_POOL)) {
5768 _netw_capture_error(netw, "Network id " SOCKET_SIZE_FORMAT " could not be removed !", netw->link.sock);
5769 n_log(LOG_ERR, "Network id " SOCKET_SIZE_FORMAT " could not be removed !", netw->link.sock);
5770 }
5771 }
5772 unlock(netw_pool->rwlock);
5773 n_log(LOG_DEBUG, "Network id " SOCKET_SIZE_FORMAT " removed !", netw->link.sock);
5774 free_nstr(&key);
5775 return TRUE;
5776 }
5777 free_nstr(&key);
5778 _netw_capture_error(netw, "Network id " SOCKET_SIZE_FORMAT " already removed !", netw->link.sock);
5779 n_log(LOG_ERR, "Network id " SOCKET_SIZE_FORMAT " already removed !", netw->link.sock);
5780 /* unlock the pool */
5781 unlock(netw_pool->rwlock);
5782 return FALSE;
5783} /* netw_pool_remove */
5784
5792int netw_pool_broadcast(NETWORK_POOL* netw_pool, const NETWORK* from, N_STR* net_msg) {
5793 __n_assert(netw_pool, return FALSE);
5794 __n_assert(net_msg, return FALSE);
5795
5796 /* write lock the pool */
5797 read_lock(netw_pool->rwlock);
5798 ht_foreach(node, netw_pool->pool) {
5799 NETWORK* netw = hash_val(node, NETWORK);
5800 if (from) {
5801 if (netw->link.sock != from->link.sock)
5802 netw_add_msg(netw, nstrdup(net_msg));
5803 } else {
5804 netw_add_msg(netw, nstrdup(net_msg));
5805 }
5806 }
5807 unlock(netw_pool->rwlock);
5808 return TRUE;
5809} /* netw_pool_broadcast */
5810
5817 __n_assert(netw_pool, return 0);
5818
5819 size_t nb = 0;
5820 read_lock(netw_pool->rwlock);
5821 nb = netw_pool->pool->nb_keys;
5822 unlock(netw_pool->rwlock);
5823
5824 return nb;
5825} /* netw_pool_nbclients() */
5826
5834 __n_assert(netw, return FALSE);
5835 netw->user_id = id;
5836 return TRUE;
5837} /* netw_set_user_id() */
5838
5848int netw_send_ping(NETWORK* netw, int type, int id_from, int id_to, int time) {
5849 N_STR* tmpstr = NULL;
5850 __n_assert(netw, return FALSE);
5851
5852 tmpstr = netmsg_make_ping(type, id_from, id_to, time);
5853 __n_assert(tmpstr, return FALSE);
5854
5855 return netw_add_msg(netw, tmpstr);
5856} /* netw_send_ping( ... ) */
5857
5867int netw_send_ident(NETWORK* netw, int type, int id, N_STR* name, N_STR* passwd) {
5868 N_STR* tmpstr = NULL;
5869
5870 __n_assert(netw, return FALSE);
5871
5872 tmpstr = netmsg_make_ident(type, id, name, passwd);
5873 __n_assert(tmpstr, return FALSE);
5874
5875 return netw_add_msg(netw, tmpstr);
5876} /* netw_send_ident( ... ) */
5877
5891int netw_send_position(NETWORK* netw, int id, double X, double Y, double vx, double vy, double acc_x, double acc_y, int time_stamp) {
5892 N_STR* tmpstr = NULL;
5893
5894 __n_assert(netw, return FALSE);
5895
5896 tmpstr = netmsg_make_position_msg(id, X, Y, vx, vy, acc_x, acc_y, time_stamp);
5897
5898 __n_assert(tmpstr, return FALSE);
5899
5900 return netw_add_msg(netw, tmpstr);
5901} /* netw_send_position( ... ) */
5902
5913int netw_send_string_to(NETWORK* netw, int id_to, N_STR* name, N_STR* chan, N_STR* txt, int color) {
5914 N_STR* tmpstr = NULL;
5915
5916 __n_assert(netw, return FALSE);
5917
5918 tmpstr = netmsg_make_string_msg(netw->user_id, id_to, name, chan, txt, color);
5919 __n_assert(tmpstr, return FALSE);
5920
5921 return netw_add_msg(netw, tmpstr);
5922} /* netw_send_string_to( ... ) */
5923
5933int netw_send_string_to_all(NETWORK* netw, N_STR* name, N_STR* chan, N_STR* txt, int color) {
5934 N_STR* tmpstr = NULL;
5935
5936 __n_assert(netw, return FALSE);
5937
5938 tmpstr = netmsg_make_string_msg(netw->user_id, -1, name, chan, txt, color);
5939 __n_assert(tmpstr, return FALSE);
5940
5941 return netw_add_msg(netw, tmpstr);
5942} /* netw_send_string_to_all( ... ) */
5943
5950 __n_assert(netw, return FALSE);
5951
5952 N_STR* tmpstr = NULL;
5953
5954 tmpstr = netmsg_make_quit_msg();
5955 __n_assert(tmpstr, return FALSE);
5956
5957 return netw_add_msg(netw, tmpstr);
5958} /* netw_send_quit( ... ) */
5959
5966size_t netw_calculate_urlencoded_size(const char* str, size_t len) {
5967 __n_assert(str, return 0);
5968
5969 size_t encoded_size = 0;
5970
5971 for (size_t i = 0; i < len; i++) {
5972 unsigned char c = (unsigned char)str[i];
5973 if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
5974 encoded_size += 1; // Safe character, no encoding needed
5975 } else {
5976 encoded_size += 3; // Unsafe character, will be encoded as %XX
5977 }
5978 }
5979
5980 return encoded_size;
5981}
5982
5989char* netw_urlencode(const char* str, size_t len) {
5990 __n_assert(str, return NULL);
5991
5992 static const char* hex = "0123456789ABCDEF";
5993 size_t encoded_size = netw_calculate_urlencoded_size(str, len);
5994 char* encoded = (char*)malloc(encoded_size + 1); // Allocate memory for the encoded string (+1 for the null terminator)
5995
5996 if (!encoded) {
5997 return NULL; // Return NULL if memory allocation fails
5998 }
5999
6000 char* pbuf = encoded;
6001
6002 for (size_t i = 0; i < len; i++) {
6003 unsigned char c = (unsigned char)str[i];
6004 if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
6005 *pbuf++ = (char)c; // Copy safe characters directly
6006 } else {
6007 *pbuf++ = '%'; // Encode unsafe characters as %XX
6008 *pbuf++ = hex[c >> 4];
6009 *pbuf++ = hex[c & 0xF];
6010 }
6011 }
6012
6013 *pbuf = '\0'; // Null-terminate the encoded string
6014 return encoded;
6015}
6016
6022char* netw_extract_http_request_type(const char* request) {
6023 __n_assert(request, return NULL);
6024 // Find the first space in the request string
6025 const char* space = strchr(request, ' ');
6026
6027 if (space == NULL) {
6028 // No space found, invalid request format
6029 return NULL;
6030 }
6031
6032 // Calculate the length of the request type
6033 size_t method_length = (size_t)(space - request);
6034
6035 // Allocate memory for the method string (+1 for the null terminator)
6036 char* method = (char*)malloc(method_length + 1);
6037
6038 if (method == NULL) {
6039 // Memory allocation failed
6040 return NULL;
6041 }
6042
6043 // Copy the request method to the allocated memory
6044 strncpy(method, request, method_length);
6045 method[method_length] = '\0'; // Null-terminate the string
6046
6047 return method;
6048}
6049
6056 NETWORK_HTTP_INFO info = {.content_length = 0, .body = NULL, .type = NULL, .content_type = {0}};
6057
6058 __n_assert(request, return info);
6059
6060 // Hold the request-type allocation in a local until just before return.
6061 // The clang static analyzer's unix.Malloc checker mis-tracks ownership
6062 // when a malloc'd pointer is parked in a returned-by-value struct member
6063 // early in the function; keeping it local until the final assignment
6064 // makes the escape via the returned struct unambiguous to the analyzer.
6065 char* request_type = netw_extract_http_request_type(request);
6066
6067 // Find Content-Type header (Optional)
6068 const char* content_type_header = strstr(request, "Content-Type:");
6069 if (content_type_header) {
6070 const char* start = content_type_header + strlen("Content-Type: ");
6071 const char* end = strstr(start, "\r\n");
6072 if (end) {
6073 size_t length = (size_t)(end - start);
6074 if (length > 255) length = 255;
6075 strncpy(info.content_type, start, length);
6076 info.content_type[length] = '\0';
6077 }
6078 } else {
6079 // If no Content-Type header found, set default
6080 strncpy(info.content_type, "text/plain", sizeof(info.content_type) - 1);
6081 }
6082
6083 // Find Content-Length header (Optional)
6084 const char* content_length_header = strstr(request, "Content-Length:");
6085 if (content_length_header) {
6086 const char* start = content_length_header + strlen("Content-Length: ");
6087 errno = 0;
6088 unsigned long tmp_cl = strtoul(start, NULL, 10); /* parse once */
6089 int error = errno;
6090 // Handle out-of-range error (e.g., set a safe default or return an error)
6091 // Only when ULONG_MAX is larger than SIZE_MAX, to prevent a constant false comparison warning
6092#if ULONG_MAX > SIZE_MAX
6093 if (error == ERANGE || tmp_cl > SIZE_MAX) {
6094#else
6095 if (error == ERANGE) {
6096#endif
6097 n_log(LOG_ERR, "could not get content_length for request %p, returned %s", request, strerror(error));
6098 info.content_length = SIZE_MAX; /* clamp to the maximum supported value */
6099 } else {
6100 info.content_length = (size_t)tmp_cl;
6101 }
6102 }
6103
6104 // Find the start of the body (after \r\n\r\n)
6105 const char* body_start = strstr(request, "\r\n\r\n");
6106 if (body_start) {
6107 body_start += 4; // Skip the \r\n\r\n
6108
6109 // If there is a Content-Length, and it's greater than 0, copy the body
6110 if (info.content_length > 0 && info.content_length < SIZE_MAX) {
6111 info.body = malloc(info.content_length + 1); // Allocate memory for body
6112 if (info.body) {
6113 strncpy(info.body, body_start, info.content_length);
6114 info.body[info.content_length] = '\0'; // Null-terminate the body
6115 }
6116 }
6117 }
6118
6119 info.type = request_type;
6120 return info;
6121}
6122
6129 FreeNoLog(http_request.body);
6130 FreeNoLog(http_request.type);
6131 return TRUE;
6132}
6133
6141int netw_get_url_from_http_request(const char* request, char* url, size_t size) {
6142 __n_assert(request && strlen(request) > 0, return FALSE);
6143 __n_assert(url && size > 1, return FALSE);
6144
6145 /* Default output */
6146 strncpy(url, "/", size - 1);
6147 url[size - 1] = '\0';
6148
6149 /* Example request line: "GET /path/to/resource HTTP/1.1" */
6150 const char* first_space = strchr(request, ' ');
6151 if (!first_space) {
6152 /* Malformed request, return default '/' */
6153 return FALSE;
6154 }
6155
6156 const char* second_space = strchr(first_space + 1, ' ');
6157 if (!second_space) {
6158 /* Malformed request, return default '/' */
6159 return FALSE;
6160 }
6161
6162 size_t len = (size_t)(second_space - first_space - 1);
6163 if (len >= size) {
6164 len = size - 1;
6165 }
6166
6167 strncpy(url, first_space + 1, len);
6168 url[len] = '\0'; /* Null-terminate the URL */
6169
6170 return TRUE;
6171}
6172
6178char* netw_urldecode(const char* str) {
6179 __n_assert(str, return NULL);
6180 char* decoded = malloc(strlen(str) + 1);
6181 __n_assert(decoded, return NULL);
6182
6183 char* p = decoded;
6184 while (*str) {
6185 if (*str == '%') {
6186 if (isxdigit((unsigned char)str[1]) && isxdigit((unsigned char)str[2])) {
6187 unsigned int value;
6188 if (sscanf(str + 1, "%2x", &value) >= 1) {
6189 *p++ = (char)value;
6190 str += 3;
6191 } else {
6192 n_log(LOG_ERR, "sscanf could not parse char *str (%p) for a %%2x", str);
6193 Free(decoded);
6194 return NULL;
6195 }
6196 } else {
6197 *p++ = *str++;
6198 }
6199 } else if (*str == '+') {
6200 *p++ = ' ';
6201 str++;
6202 } else {
6203 *p++ = *str++;
6204 }
6205 }
6206 *p = '\0';
6207 return decoded;
6208}
6209
6215HASH_TABLE* netw_parse_post_data(const char* post_data) {
6216 __n_assert(post_data, return NULL);
6217
6218 // Create a copy of the post_data string because strtok modifies the string
6219 char* data = strdup(post_data);
6220 __n_assert(data, return NULL);
6221
6222 char* pair = data;
6223
6224 HASH_TABLE* post_data_table = new_ht(32);
6225
6226 while (pair != NULL) {
6227 // Find the next key-value pair separated by '&'
6228 char* ampersand_pos = strchr(pair, '&');
6229
6230 // If found, replace it with '\0' to isolate the current pair
6231 if (ampersand_pos != NULL) {
6232 *ampersand_pos = '\0';
6233 }
6234
6235 // Now split each pair by '=' to get the key and value
6236 char* equal_pos = strchr(pair, '=');
6237 if (equal_pos != NULL) {
6238 *equal_pos = '\0'; // Terminate the key string
6239 const char* key = pair;
6240 const char* value = equal_pos + 1;
6241
6242 // Decode the value since POST data is URL-encoded
6243 char* decoded_value = netw_urldecode(value);
6244 ht_put_string(post_data_table, key, decoded_value);
6245 // printf("Key: %s, Value: %s\n", key, decoded_value);
6246 free(decoded_value);
6247 }
6248 // Move to the next key-value pair (if any)
6249 pair = (ampersand_pos != NULL) ? (ampersand_pos + 1) : NULL;
6250 }
6251 // Free the duplicated string
6252 free(data);
6253 return post_data_table;
6254}
6255
6261const char* netw_guess_http_content_type(const char* url) {
6262 __n_assert(url, return NULL);
6263
6264 // Create a copy of the URL to work on (to avoid modifying the original string)
6265 char url_copy[1024];
6266 strncpy(url_copy, url, sizeof(url_copy) - 1);
6267 url_copy[sizeof(url_copy) - 1] = '\0';
6268
6269 // Find if there is a '?' (indicating GET parameters) and terminate the string there
6270 char* query_start = strchr(url_copy, '?');
6271 if (query_start) {
6272 *query_start = '\0'; // Terminate the string before the query parameters
6273 }
6274
6275 // Find the last occurrence of a dot in the URL (file extension)
6276 const char* ext = strrchr(url_copy, '.');
6277
6278 // If no extension is found, return "unknown"
6279 if (!ext) {
6280 return "unknown";
6281 }
6282
6283 // Compare the extension to known content types
6284 if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) {
6285 return "text/html";
6286 } else if (strcmp(ext, ".txt") == 0) {
6287 return "text/plain";
6288 } else if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) {
6289 return "image/jpeg";
6290 } else if (strcmp(ext, ".png") == 0) {
6291 return "image/png";
6292 } else if (strcmp(ext, ".gif") == 0) {
6293 return "image/gif";
6294 } else if (strcmp(ext, ".css") == 0) {
6295 return "text/css";
6296 } else if (strcmp(ext, ".js") == 0) {
6297 return "application/javascript";
6298 } else if (strcmp(ext, ".json") == 0) {
6299 return "application/json";
6300 } else if (strcmp(ext, ".xml") == 0) {
6301 return "application/xml";
6302 } else if (strcmp(ext, ".pdf") == 0) {
6303 return "application/pdf";
6304 } else if (strcmp(ext, ".zip") == 0) {
6305 return "application/zip";
6306 } else if (strcmp(ext, ".mp4") == 0) {
6307 return "video/mp4";
6308 } else if (strcmp(ext, ".mp3") == 0) {
6309 return "audio/mpeg";
6310 } else if (strcmp(ext, ".wav") == 0) {
6311 return "audio/wav";
6312 } else if (strcmp(ext, ".ogg") == 0) {
6313 return "audio/ogg";
6314 }
6315
6316 // Return unknown for other types
6317 return "unknown";
6318}
6319
6325const char* netw_get_http_status_message(int status_code) {
6326 switch (status_code) {
6327 case 200:
6328 return "OK";
6329 case 204:
6330 return "No Content";
6331 case 304:
6332 return "Not Modified";
6333 case 404:
6334 return "Not Found";
6335 case 500:
6336 return "Internal Server Error";
6337 // Add more status codes as needed
6338 default:
6339 return "Unknown";
6340 }
6341}
6342
6343int n_http_status_class(int status_code) {
6344 if (status_code < 100 || status_code > 599)
6345 return 0;
6346 return status_code / 100;
6347}
6348
6350 return n_http_status_class(status_code) == 1;
6351}
6352
6353int n_http_status_is_success(int status_code) {
6354 return n_http_status_class(status_code) == 2;
6355}
6356
6357int n_http_status_is_redirect(int status_code) {
6358 return n_http_status_class(status_code) == 3;
6359}
6360
6361int n_http_status_is_client_error(int status_code) {
6362 return n_http_status_class(status_code) == 4;
6363}
6364
6365int n_http_status_is_server_error(int status_code) {
6366 return n_http_status_class(status_code) == 5;
6367}
6368
6369/* Inflate src/len with the given zlib window bits into a fresh N_STR.
6370 * window_bits: 15+16 for gzip framing, -15 for raw deflate, 15 for zlib-wrapped.
6371 * Returns 0 on success (*out set), -1 on error. */
6372static int n_http_inflate(const unsigned char* src, size_t len, int window_bits, N_STR** out) {
6373 z_stream strm;
6374 size_t alloc, total = 0;
6375 char* buf;
6376 int ret;
6377 *out = NULL;
6378 memset(&strm, 0, sizeof(strm));
6379 if (inflateInit2(&strm, window_bits) != Z_OK)
6380 return -1;
6381 alloc = len * 4;
6382 if (alloc < 4096)
6383 alloc = 4096;
6384 buf = malloc(alloc);
6385 if (!buf) {
6386 inflateEnd(&strm);
6387 return -1;
6388 }
6389 strm.next_in = (Bytef*)src;
6390 strm.avail_in = (uInt)len;
6391 do {
6392 if (total >= alloc - 1) {
6393 char* tmp;
6394 alloc *= 2;
6395 tmp = realloc(buf, alloc);
6396 if (!tmp) {
6397 free(buf);
6398 inflateEnd(&strm);
6399 return -1;
6400 }
6401 buf = tmp;
6402 }
6403 strm.next_out = (Bytef*)(buf + total);
6404 strm.avail_out = (uInt)(alloc - total - 1);
6405 ret = inflate(&strm, Z_NO_FLUSH);
6406 if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR || ret == Z_NEED_DICT) {
6407 free(buf);
6408 inflateEnd(&strm);
6409 return -1;
6410 }
6411 total = alloc - 1 - strm.avail_out;
6412 } while (ret != Z_STREAM_END);
6413 inflateEnd(&strm);
6414 buf[total] = '\0';
6415 {
6416 N_STR* r = NULL;
6417 char_to_nstr_ex(buf, (NSTRBYTE)total, &r);
6418 free(buf);
6419 if (!r)
6420 return -1;
6421 *out = r;
6422 }
6423 return 0;
6424}
6425
6426int n_http_decompress_body(const N_STR* body, const char* encoding, N_STR** out) {
6427 char enc[32];
6428 int is_gzip = 0, is_deflate = 0;
6429 if (!body || !out)
6430 return -1;
6431 *out = NULL;
6432
6433 enc[0] = '\0';
6434 if (encoding) {
6435 size_t i;
6436 for (i = 0; i < sizeof(enc) - 1 && encoding[i]; i++)
6437 enc[i] = (char)tolower((unsigned char)encoding[i]);
6438 enc[i] = '\0';
6439 }
6440 if (strstr(enc, "gzip"))
6441 is_gzip = 1;
6442 else if (strstr(enc, "deflate"))
6443 is_deflate = 1;
6444
6445 /* identity / unknown / empty body: pass a copy through unchanged */
6446 if ((!is_gzip && !is_deflate) || !body->data || body->written == 0) {
6447 N_STR* copy = nstrdup((N_STR*)body);
6448 if (!copy)
6449 return -1;
6450 *out = copy;
6451 return 0;
6452 }
6453
6454 if (is_gzip)
6455 return n_http_inflate((const unsigned char*)body->data, body->written, 15 + 16, out);
6456 /* "deflate" is sent both raw (-15) and zlib-wrapped (15); try raw, then wrapped */
6457 if (n_http_inflate((const unsigned char*)body->data, body->written, -15, out) == 0)
6458 return 0;
6459 return n_http_inflate((const unsigned char*)body->data, body->written, 15, out);
6460}
6461
6462/* Binary substring search: first offset of needle in hay, or -1. */
6463static long mp_find(const unsigned char* hay, size_t hlen, const char* needle, size_t nlen) {
6464 size_t i;
6465 if (nlen == 0 || nlen > hlen)
6466 return -1;
6467 for (i = 0; i + nlen <= hlen; i++) {
6468 if (hay[i] == (unsigned char)needle[0] && memcmp(hay + i, needle, nlen) == 0)
6469 return (long)i;
6470 }
6471 return -1;
6472}
6473
6474/* Duplicate len bytes of s into a fresh NUL-terminated C string. */
6475static char* mp_strndup(const char* s, size_t len) {
6476 char* r = NULL;
6477 Malloc(r, char, len + 1);
6478 if (!r)
6479 return NULL;
6480 if (len)
6481 memcpy(r, s, len);
6482 r[len] = '\0';
6483 return r;
6484}
6485
6486int n_http_multipart_boundary(const char* content_type, char* out, size_t outsz) {
6487 const char* p;
6488 size_t i;
6489 if (!content_type || !out || outsz == 0)
6490 return -1;
6491 /* case-insensitive search for "boundary=" */
6492 for (p = content_type; *p; p++) {
6493 if (strncasecmp(p, "boundary=", 9) == 0)
6494 break;
6495 }
6496 if (!*p)
6497 return -1;
6498 p += 9;
6499 i = 0;
6500 if (*p == '"') {
6501 p++;
6502 while (*p && *p != '"' && i + 1 < outsz)
6503 out[i++] = *p++;
6504 } else {
6505 while (*p && *p != ';' && *p != ' ' && *p != '\r' && *p != '\n' && i + 1 < outsz)
6506 out[i++] = *p++;
6507 }
6508 out[i] = '\0';
6509 return i > 0 ? 0 : -1;
6510}
6511
6512/* Free an N_HTTP_MULTIPART_PART stored in a LIST. */
6513static void mp_part_free(void* ptr) {
6515 if (!mp)
6516 return;
6517 FreeNoLog(mp->name);
6518 FreeNoLog(mp->filename);
6521 if (mp->body)
6522 free_nstr(&mp->body);
6523 Free(mp);
6524}
6525
6527 if (parts && *parts)
6528 list_destroy(parts);
6529}
6530
6531/* Within a header block, find the value of the named header (case-insensitive),
6532 * returning a pointer to the value start and its length (trailing CRLF trimmed). */
6533static const char* mp_header_value(const char* block, size_t blen, const char* name, size_t* vlen) {
6534 size_t nlen = strlen(name);
6535 size_t i = 0;
6536 *vlen = 0;
6537 while (i < blen) {
6538 size_t line_end = i;
6539 while (line_end < blen && block[line_end] != '\n')
6540 line_end++;
6541 /* line spans [i, line_end) (may include a trailing '\r') */
6542 if (line_end - i > nlen + 1 && strncasecmp(block + i, name, nlen) == 0 && block[i + nlen] == ':') {
6543 const char* v = block + i + nlen + 1;
6544 size_t vl = line_end - (i + nlen + 1);
6545 while (vl > 0 && (*v == ' ' || *v == '\t')) {
6546 v++;
6547 vl--;
6548 }
6549 while (vl > 0 && (v[vl - 1] == '\r' || v[vl - 1] == ' ' || v[vl - 1] == '\t'))
6550 vl--;
6551 *vlen = vl;
6552 return v;
6553 }
6554 i = line_end + 1;
6555 }
6556 return NULL;
6557}
6558
6559/* Extract a quoted (or token) parameter value from a Content-Disposition value,
6560 * e.g. param "name" from `form-data; name="x"; filename="y"`. */
6561static char* mp_disp_param(const char* disp, size_t dlen, const char* param) {
6562 char key[32];
6563 size_t klen, i;
6564 snprintf(key, sizeof(key), "%s=", param);
6565 klen = strlen(key);
6566 for (i = 0; i + klen <= dlen; i++) {
6567 if (strncasecmp(disp + i, key, klen) == 0) {
6568 const char* v = disp + i + klen;
6569 size_t avail = dlen - (i + klen);
6570 size_t j = 0;
6571 if (avail > 0 && *v == '"') {
6572 v++;
6573 avail--;
6574 while (j < avail && v[j] != '"')
6575 j++;
6576 } else {
6577 while (j < avail && v[j] != ';' && v[j] != ' ')
6578 j++;
6579 }
6580 return mp_strndup(v, j);
6581 }
6582 }
6583 return NULL;
6584}
6585
6586LIST* n_http_parse_multipart(const N_STR* body, const char* boundary) {
6587 char dash[256];
6588 char crlf_dash[260];
6589 const unsigned char* data;
6590 size_t len, dlen, cdlen, cursor;
6591 long first;
6592 LIST* parts;
6593
6594 if (!body || !body->data || !boundary || !boundary[0])
6595 return NULL;
6596 if (strlen(boundary) + 2 >= sizeof(dash))
6597 return NULL;
6598 snprintf(dash, sizeof(dash), "--%s", boundary);
6599 snprintf(crlf_dash, sizeof(crlf_dash), "\r\n--%s", boundary);
6600 dlen = strlen(dash);
6601 cdlen = strlen(crlf_dash);
6602
6603 parts = new_generic_list(0);
6604 if (!parts)
6605 return NULL;
6606
6607 data = (const unsigned char*)body->data;
6608 len = body->written;
6609 first = mp_find(data, len, dash, dlen);
6610 if (first < 0)
6611 return parts; /* no boundary present: empty result */
6612 cursor = (size_t)first + dlen;
6613
6614 for (;;) {
6615 long rel;
6616 size_t part_start, part_end;
6617 size_t hvl;
6619 /* closing delimiter "--boundary--" */
6620 if (cursor + 2 <= len && data[cursor] == '-' && data[cursor + 1] == '-')
6621 break;
6622 /* skip the CRLF that follows the delimiter */
6623 if (cursor + 2 <= len && data[cursor] == '\r' && data[cursor + 1] == '\n')
6624 cursor += 2;
6625 else if (cursor < len && data[cursor] == '\n')
6626 cursor += 1;
6627 part_start = cursor;
6628 rel = mp_find(data + part_start, len - part_start, crlf_dash, cdlen);
6629 if (rel < 0)
6630 break; /* malformed: no closing delimiter */
6631 part_end = part_start + (size_t)rel;
6632
6634 if (!mp)
6635 break;
6636 mp->name = NULL;
6637 mp->filename = NULL;
6638 mp->content_type = NULL;
6639 mp->body = NULL;
6640 /* split the part into its header block and body at the blank line */
6641 {
6642 long s = mp_find(data + part_start, part_end - part_start, "\r\n\r\n", 4);
6643 if (s >= 0) {
6644 size_t hlen = (size_t)s;
6645 size_t bbeg = part_start + (size_t)s + 4;
6646 const char* hblock = (const char*)(data + part_start);
6647 const char* hv = mp_header_value(hblock, hlen, "Content-Disposition", &hvl);
6648 if (hv) {
6649 mp->name = mp_disp_param(hv, hvl, "name");
6650 mp->filename = mp_disp_param(hv, hvl, "filename");
6651 }
6652 hv = mp_header_value(hblock, hlen, "Content-Type", &hvl);
6653 if (hv)
6654 mp->content_type = mp_strndup(hv, hvl);
6655 {
6656 size_t blen = part_end - bbeg;
6657 mp->body = new_nstr(blen + 1);
6658 if (mp->body) {
6659 if (blen)
6660 memcpy(mp->body->data, data + bbeg, blen);
6661 mp->body->data[blen] = '\0';
6662 mp->body->written = blen;
6663 }
6664 }
6665 }
6666 }
6667 if (!mp->name)
6668 mp->name = mp_strndup("", 0);
6669 if (!mp->filename)
6670 mp->filename = mp_strndup("", 0);
6671 if (!mp->content_type)
6672 mp->content_type = mp_strndup("", 0);
6673 if (!mp->body)
6674 mp->body = new_nstr(1);
6675 list_push(parts, mp, mp_part_free);
6676
6677 cursor = part_end + cdlen; /* skip "\r\n--boundary" to the next part/closing */
6678 }
6679 return parts;
6680}
6681
6682/* Append n bytes to buf at *pos (sizing pass when buf == NULL). */
6683static void mp_put(char* buf, size_t* pos, const void* src, size_t n) {
6684 if (buf && n)
6685 memcpy(buf + *pos, src, n);
6686 *pos += n;
6687}
6688
6689/* Append a NUL-terminated string (length via strlen). */
6690static void mp_puts(char* buf, size_t* pos, const char* s) {
6691 if (s)
6692 mp_put(buf, pos, s, strlen(s));
6693}
6694
6695/* Emit the full multipart body into buf (or size it when buf == NULL). */
6696static void mp_emit(char* buf, size_t* pos, LIST* parts, const char* dash) {
6697 list_foreach(node, parts) {
6698 const N_HTTP_MULTIPART_PART* mp = (const N_HTTP_MULTIPART_PART*)node->ptr;
6699 if (!mp)
6700 continue;
6701 mp_puts(buf, pos, dash);
6702 mp_puts(buf, pos, "\r\nContent-Disposition: form-data; name=\"");
6703 mp_puts(buf, pos, mp->name ? mp->name : "");
6704 mp_puts(buf, pos, "\"");
6705 if (mp->filename && mp->filename[0]) {
6706 mp_puts(buf, pos, "; filename=\"");
6707 mp_puts(buf, pos, mp->filename);
6708 mp_puts(buf, pos, "\"");
6709 }
6710 if (mp->disposition_extra && mp->disposition_extra[0])
6711 mp_puts(buf, pos, mp->disposition_extra); /* verbatim, separator included */
6712 mp_puts(buf, pos, "\r\n");
6713 if (mp->content_type && mp->content_type[0]) {
6714 mp_puts(buf, pos, "Content-Type: ");
6715 mp_puts(buf, pos, mp->content_type);
6716 mp_puts(buf, pos, "\r\n");
6717 }
6718 mp_puts(buf, pos, "\r\n");
6719 if (mp->body && mp->body->written)
6720 mp_put(buf, pos, mp->body->data, mp->body->written);
6721 mp_puts(buf, pos, "\r\n");
6722 }
6723 mp_puts(buf, pos, dash);
6724 mp_puts(buf, pos, "--\r\n");
6725}
6726
6727N_STR* n_http_build_multipart(LIST* parts, const char* boundary) {
6728 char dash[256];
6729 size_t total = 0;
6730 char* buf;
6731 N_STR* out = NULL;
6732 if (!parts || !boundary || !boundary[0])
6733 return NULL;
6734 if (strlen(boundary) + 2 >= sizeof(dash))
6735 return NULL;
6736 snprintf(dash, sizeof(dash), "--%s", boundary);
6737
6738 mp_emit(NULL, &total, parts, dash); /* sizing pass */
6739 Malloc(buf, char, total + 1);
6740 if (!buf)
6741 return NULL;
6742 total = 0;
6743 mp_emit(buf, &total, parts, dash); /* fill pass */
6744 buf[total] = '\0';
6745 char_to_nstr_ex(buf, (NSTRBYTE)total, &out);
6746 free(buf);
6747 return out;
6748}
6749
6756int netw_get_http_date(char* buffer, size_t buffer_size) {
6757 __n_assert(buffer, return FALSE);
6758 const time_t now = time(NULL);
6759 struct tm gmt;
6760#ifdef _WIN32
6761 if (gmtime_s(&gmt, &now) != 0) {
6762 n_log(LOG_ERR, "gmtime_s failed");
6763 return FALSE;
6764 }
6765#else
6766 if (!gmtime_r(&now, &gmt)) {
6767 n_log(LOG_ERR, "gmtime_r returned NULL");
6768 return FALSE;
6769 }
6770#endif
6771 if (strftime(buffer, buffer_size, "%a, %d %b %Y %H:%M:%S GMT", &gmt) == 0) {
6772 n_log(LOG_ERR, "strftime failed: buffer too small");
6773 return FALSE;
6774 }
6775 return TRUE;
6776}
6777
6788int netw_build_http_response(N_STR** http_response, int status_code, const char* server_name, const char* content_type, char* additional_headers, N_STR* body) {
6789 __n_assert(server_name, return FALSE);
6790 __n_assert(content_type, return FALSE);
6791 __n_assert(additional_headers, return FALSE);
6792
6793 const char* status_message = netw_get_http_status_message(status_code);
6794 const char* connection_type = "close";
6795
6796 // Buffer for the date header
6797 char date_buffer[128] = "";
6798 netw_get_http_date(date_buffer, sizeof(date_buffer));
6799
6800 if ((*http_response)) {
6801 (*http_response)->written = 0;
6802 }
6803
6804 if (!body || body->written == 0) {
6805 // Handle the case where there is no body
6806 nstrprintf((*http_response),
6807 "HTTP/1.1 %d %s\r\n"
6808 "Date: %s\r\n"
6809 "Server: %s\r\n"
6810 "Content-Length: 0\r\n"
6811 "%s"
6812 "Connection: %s\r\n\r\n",
6813 status_code, status_message, date_buffer, server_name, additional_headers, connection_type);
6814 n_log(LOG_DEBUG, "empty response");
6815 } else {
6816 // Build the response with body
6817 nstrprintf((*http_response),
6818 "HTTP/1.1 %d %s\r\n"
6819 "Date: %s\r\n"
6820 "Server: %s\r\n"
6821 "Content-Type: %s\r\n"
6822 "Content-Length: %zu\r\n"
6823 "%s"
6824 "Connection: %s\r\n\r\n",
6825 status_code, status_message, date_buffer, server_name, content_type, body->written, additional_headers, connection_type);
6826 nstrcat((*http_response), body);
6827 n_log(LOG_DEBUG, "body response");
6828 }
6829 return TRUE;
6830}
6831
6832#ifdef HAVE_OPENSSL
6833
6839static ssize_t _ws_write(N_WS_CONN* conn, const void* buf, size_t len) {
6840 __n_assert(conn, return -1);
6841 __n_assert(conn->netw, return -1);
6842 if (conn->netw->crypto_algo == NETW_ENCRYPT_OPENSSL && conn->netw->ssl) {
6843 int ret = SSL_write(conn->netw->ssl, buf, (int)len);
6844 return (ret > 0) ? (ssize_t)ret : -1;
6845 }
6846 ssize_t ret = send(conn->netw->link.sock, buf, NETW_BUFLEN_CAST(len), NETFLAGS);
6847 return ret;
6848}
6849
6855static ssize_t _ws_read(N_WS_CONN* conn, void* buf, size_t len) {
6856 __n_assert(conn, return -1);
6857 __n_assert(conn->netw, return -1);
6858 size_t total = 0;
6859 char* p = (char*)buf;
6860 while (total < len) {
6861 ssize_t ret = 0;
6862 if (conn->netw->crypto_algo == NETW_ENCRYPT_OPENSSL && conn->netw->ssl) {
6863 ret = SSL_read(conn->netw->ssl, p + total, (int)(len - total));
6864 } else {
6865 ret = recv(conn->netw->link.sock, p + total, NETW_BUFLEN_CAST(len - total), 0);
6866 }
6867 if (ret <= 0) {
6868 return -1;
6869 }
6870 total += (size_t)ret;
6871 }
6872 return (ssize_t)total;
6873}
6874
6886N_WS_CONN* n_ws_connect(const char* host, const char* port, const char* path, int use_ssl) {
6887 __n_assert(host, return NULL);
6888 __n_assert(port, return NULL);
6889 __n_assert(path, return NULL);
6890
6891 N_WS_CONN* conn = NULL;
6892 Malloc(conn, N_WS_CONN, 1);
6893 __n_assert(conn, return NULL);
6894
6895 conn->netw = NULL;
6896 conn->connected = 0;
6897 conn->host = strdup(host);
6898 conn->path = strdup(path);
6899 if (!conn->host || !conn->path) {
6900 n_log(LOG_ERR, "n_ws_connect: strdup failed");
6901 goto ws_connect_fail;
6902 }
6903
6904 /* establish TCP + optional SSL */
6905 if (use_ssl) {
6906 if (netw_ssl_connect_client(&conn->netw, (char*)host, (char*)port, NETWORK_IPALL) == FALSE) {
6907 n_log(LOG_ERR, "n_ws_connect: TCP+SSL context setup failed for %s:%s", host, port);
6908 goto ws_connect_fail;
6909 }
6910 if (netw_ssl_do_handshake(conn->netw, host) == FALSE) {
6911 _netw_capture_error(conn->netw, "n_ws_connect: SSL handshake failed for %s:%s", host, port);
6912 n_log(LOG_ERR, "n_ws_connect: SSL handshake failed for %s:%s", host, port);
6913 goto ws_connect_fail;
6914 }
6915 } else {
6916 if (netw_connect(&conn->netw, (char*)host, (char*)port, NETWORK_IPALL) == FALSE) {
6917 n_log(LOG_ERR, "n_ws_connect: TCP connect failed for %s:%s", host, port);
6918 goto ws_connect_fail;
6919 }
6920 }
6921
6922 /* generate Sec-WebSocket-Key: base64 of 16 random bytes */
6923 unsigned char rand_bytes[16];
6924 if (RAND_bytes(rand_bytes, 16) != 1) {
6925 _netw_capture_error(conn->netw, "n_ws_connect: RAND_bytes failed");
6926 n_log(LOG_ERR, "n_ws_connect: RAND_bytes failed");
6927 goto ws_connect_fail;
6928 }
6929
6930 N_STR* rand_nstr = new_nstr(16);
6931 __n_assert(rand_nstr, goto ws_connect_fail);
6932 memcpy(rand_nstr->data, rand_bytes, 16);
6933 rand_nstr->written = 16;
6934
6935 N_STR* ws_key_nstr = n_base64_encode(rand_nstr);
6936 free_nstr(&rand_nstr);
6937 __n_assert(ws_key_nstr, goto ws_connect_fail);
6938 /* strip any trailing whitespace from base64 output */
6939 while (ws_key_nstr->written > 0 &&
6940 (ws_key_nstr->data[ws_key_nstr->written - 1] == '\n' ||
6941 ws_key_nstr->data[ws_key_nstr->written - 1] == '\r' ||
6942 ws_key_nstr->data[ws_key_nstr->written - 1] == ' ')) {
6943 ws_key_nstr->data[--ws_key_nstr->written] = '\0';
6944 }
6945
6946 /* build HTTP upgrade request */
6947 char upgrade_req[2048];
6948 int req_len = snprintf(upgrade_req, sizeof(upgrade_req),
6949 "GET %s HTTP/1.1\r\n"
6950 "Host: %s\r\n"
6951 "Upgrade: websocket\r\n"
6952 "Connection: Upgrade\r\n"
6953 "Sec-WebSocket-Key: %s\r\n"
6954 "Sec-WebSocket-Version: 13\r\n"
6955 "\r\n",
6956 path, host, ws_key_nstr->data);
6957
6958 if (req_len < 0 || (size_t)req_len >= sizeof(upgrade_req)) {
6959 _netw_capture_error(conn->netw, "n_ws_connect: upgrade request too large");
6960 n_log(LOG_ERR, "n_ws_connect: upgrade request too large");
6961 free_nstr(&ws_key_nstr);
6962 goto ws_connect_fail;
6963 }
6964
6965 /* send upgrade request */
6966 if (_ws_write(conn, upgrade_req, (size_t)req_len) < 0) {
6967 _netw_capture_error(conn->netw, "n_ws_connect: failed to send upgrade request");
6968 n_log(LOG_ERR, "n_ws_connect: failed to send upgrade request");
6969 free_nstr(&ws_key_nstr);
6970 goto ws_connect_fail;
6971 }
6972
6973 /* read response (up to 4096 bytes, look for \r\n\r\n) */
6974 char resp_buf[4096];
6975 memset(resp_buf, 0, sizeof(resp_buf));
6976 size_t resp_len = 0;
6977 while (resp_len < sizeof(resp_buf) - 1) {
6978 ssize_t r = 0;
6979 if (conn->netw->crypto_algo == NETW_ENCRYPT_OPENSSL && conn->netw->ssl) {
6980 r = SSL_read(conn->netw->ssl, resp_buf + resp_len, 1);
6981 } else {
6982 r = recv(conn->netw->link.sock, resp_buf + resp_len, 1, 0);
6983 }
6984 if (r <= 0) {
6985 _netw_capture_error(conn->netw, "n_ws_connect: failed reading handshake response");
6986 n_log(LOG_ERR, "n_ws_connect: failed reading handshake response");
6987 free_nstr(&ws_key_nstr);
6988 goto ws_connect_fail;
6989 }
6990 resp_len += (size_t)r;
6991 if (resp_len >= 4 &&
6992 resp_buf[resp_len - 4] == '\r' && resp_buf[resp_len - 3] == '\n' &&
6993 resp_buf[resp_len - 2] == '\r' && resp_buf[resp_len - 1] == '\n') {
6994 break;
6995 }
6996 }
6997 resp_buf[resp_len] = '\0';
6998
6999 /* verify 101 Switching Protocols */
7000 if (strstr(resp_buf, "101") == NULL) {
7001 _netw_capture_error(conn->netw, "n_ws_connect: server did not return 101: %.128s", resp_buf);
7002 n_log(LOG_ERR, "n_ws_connect: server did not return 101: %.128s", resp_buf);
7003 free_nstr(&ws_key_nstr);
7004 goto ws_connect_fail;
7005 }
7006
7007 /* verify Sec-WebSocket-Accept */
7008 static const char ws_magic[] = "258EAFA5-E914-47DA-95CA-5AB5FE44F513";
7009 char concat_key[256];
7010 snprintf(concat_key, sizeof(concat_key), "%s%s", ws_key_nstr->data, ws_magic);
7011 n_log(LOG_DEBUG, "n_ws_connect: key sent: [%s] len:%zu", ws_key_nstr->data, ws_key_nstr->written);
7012 n_log(LOG_DEBUG, "n_ws_connect: concat_key: [%s]", concat_key);
7013 free_nstr(&ws_key_nstr);
7014
7015 unsigned char sha1_hash[SHA_DIGEST_LENGTH];
7016 SHA1((const unsigned char*)concat_key, strlen(concat_key), sha1_hash);
7017
7018 N_STR* sha1_nstr = new_nstr(SHA_DIGEST_LENGTH);
7019 __n_assert(sha1_nstr, goto ws_connect_fail);
7020 memcpy(sha1_nstr->data, sha1_hash, SHA_DIGEST_LENGTH);
7021 sha1_nstr->written = SHA_DIGEST_LENGTH;
7022
7023 N_STR* expected_accept = n_base64_encode(sha1_nstr);
7024 free_nstr(&sha1_nstr);
7025 __n_assert(expected_accept, goto ws_connect_fail);
7026 /* strip trailing whitespace from base64 output */
7027 while (expected_accept->written > 0 &&
7028 (expected_accept->data[expected_accept->written - 1] == '\n' ||
7029 expected_accept->data[expected_accept->written - 1] == '\r' ||
7030 expected_accept->data[expected_accept->written - 1] == ' ')) {
7031 expected_accept->data[--expected_accept->written] = '\0';
7032 }
7033
7034 /* find Sec-WebSocket-Accept in response (case-insensitive search) */
7035 const char* accept_hdr = NULL;
7036 char* search_pos = resp_buf;
7037 while (*search_pos) {
7038 if (strncasecmp(search_pos, "Sec-WebSocket-Accept:", 21) == 0) {
7039 accept_hdr = search_pos + 21;
7040 break;
7041 }
7042 search_pos++;
7043 }
7044 if (!accept_hdr) {
7045 _netw_capture_error(conn->netw, "n_ws_connect: no Sec-WebSocket-Accept header in response");
7046 n_log(LOG_ERR, "n_ws_connect: no Sec-WebSocket-Accept header in response");
7047 free_nstr(&expected_accept);
7048 goto ws_connect_fail;
7049 }
7050 /* skip whitespace */
7051 while (*accept_hdr == ' ') accept_hdr++;
7052 /* compare up to expected length; trim trailing \r\n from accept_hdr */
7053 char accept_val[128];
7054 {
7055 int ai = 0;
7056 while (ai < (int)sizeof(accept_val) - 1 && accept_hdr[ai] && accept_hdr[ai] != '\r' && accept_hdr[ai] != '\n') {
7057 accept_val[ai] = accept_hdr[ai];
7058 ai++;
7059 }
7060 accept_val[ai] = '\0';
7061 }
7062 if (strcmp(accept_val, expected_accept->data) != 0) {
7063 /* Many proxies (Fly.io, Cloudflare, etc.) re-key the handshake,
7064 * so the accept may not match. Log as debug, not error. */
7065 n_log(LOG_DEBUG, "n_ws_connect: Sec-WebSocket-Accept mismatch (proxy?): got [%s] expected [%s]",
7066 accept_val, expected_accept->data);
7067 }
7068 free_nstr(&expected_accept);
7069
7070 conn->connected = 1;
7071 n_log(LOG_INFO, "n_ws_connect: WebSocket handshake completed with %s:%s%s", host, port, path);
7072 return conn;
7073
7074ws_connect_fail:
7075 n_ws_conn_free(&conn);
7076 return NULL;
7077}
7078
7090int n_ws_send(N_WS_CONN* conn, const char* payload, size_t len, int opcode) {
7091 __n_assert(conn, return -1);
7092 __n_assert(conn->netw, return -1);
7093
7094 /* max frame overhead: 2 (header) + 8 (ext len) + 4 (mask) = 14 */
7095 size_t frame_max = 14 + len;
7096 unsigned char* frame = NULL;
7097 Malloc(frame, unsigned char, frame_max);
7098 __n_assert(frame, return -1);
7099
7100 size_t pos = 0;
7101
7102 /* byte 0: FIN + opcode */
7103 frame[pos++] = (unsigned char)(0x80 | (opcode & 0x0F));
7104
7105 /* byte 1+: mask bit set + payload length */
7106 if (len <= 125) {
7107 frame[pos++] = (unsigned char)(0x80 | len);
7108 } else if (len <= 65535) {
7109 frame[pos++] = (unsigned char)(0x80 | 126);
7110 frame[pos++] = (unsigned char)((len >> 8) & 0xFF);
7111 frame[pos++] = (unsigned char)(len & 0xFF);
7112 } else {
7113 frame[pos++] = (unsigned char)(0x80 | 127);
7114 for (int i = 7; i >= 0; i--) {
7115 frame[pos++] = (unsigned char)((len >> (8 * i)) & 0xFF);
7116 }
7117 }
7118
7119 /* masking key: 4 random bytes */
7120 unsigned char mask_key[4];
7121 if (RAND_bytes(mask_key, 4) != 1) {
7122 _netw_capture_error(conn->netw, "n_ws_send: RAND_bytes failed for mask key");
7123 n_log(LOG_ERR, "n_ws_send: RAND_bytes failed for mask key");
7124 FreeNoLog(frame);
7125 return -1;
7126 }
7127 memcpy(frame + pos, mask_key, 4);
7128 pos += 4;
7129
7130 /* masked payload */
7131 if (payload && len > 0) {
7132 for (size_t i = 0; i < len; i++) {
7133 frame[pos + i] = (unsigned char)((unsigned char)payload[i] ^ mask_key[i % 4]);
7134 }
7135 pos += len;
7136 }
7137
7138 ssize_t written = _ws_write(conn, frame, pos);
7139 FreeNoLog(frame);
7140 if (written < 0 || (size_t)written != pos) {
7141 _netw_capture_error(conn->netw, "n_ws_send: write failed (wrote %zd of %zu)", written, pos);
7142 n_log(LOG_ERR, "n_ws_send: write failed (wrote %zd of %zu)", written, pos);
7143 return -1;
7144 }
7145 return 0;
7146}
7147
7157int n_ws_recv(N_WS_CONN* conn, N_WS_MESSAGE* msg_out) {
7158 __n_assert(conn, return -1);
7159 __n_assert(conn->netw, return -1);
7160 __n_assert(msg_out, return -1);
7161
7162 memset(msg_out, 0, sizeof(*msg_out));
7163
7164 /* read 2-byte header */
7165 unsigned char hdr[2];
7166 if (_ws_read(conn, hdr, 2) < 0) {
7167 return -1;
7168 }
7169
7170 /* int fin = (hdr[0] >> 7) & 1; */
7171 int opcode = hdr[0] & 0x0F;
7172 int mask_bit = (hdr[1] >> 7) & 1;
7173 uint64_t payload_len = hdr[1] & 0x7F;
7174
7175 /* extended payload length */
7176 if (payload_len == 126) {
7177 unsigned char ext[2];
7178 if (_ws_read(conn, ext, 2) < 0) return -1;
7179 payload_len = ((uint64_t)ext[0] << 8) | (uint64_t)ext[1];
7180 } else if (payload_len == 127) {
7181 unsigned char ext[8];
7182 if (_ws_read(conn, ext, 8) < 0) return -1;
7183 payload_len = 0;
7184 for (int i = 0; i < 8; i++) {
7185 payload_len = (payload_len << 8) | (uint64_t)ext[i];
7186 }
7187 }
7188
7189 /* the length above comes straight off the wire: bound it BEFORE it is used to
7190 size an allocation. Without this a hostile peer declares a 64-bit length whose
7191 terminator arithmetic wraps to a tiny allocation and the payload read below then
7192 writes past it, or simply asks for an allocation large enough to exhaust memory */
7193 if (payload_len > (uint64_t)N_WS_MAX_PAYLOAD) {
7194 _netw_capture_error(conn->netw, "n_ws_recv: payload length %llu exceeds N_WS_MAX_PAYLOAD (%zu)", (unsigned long long)payload_len, (size_t)N_WS_MAX_PAYLOAD);
7195 n_log(LOG_ERR, "n_ws_recv: payload length %llu exceeds N_WS_MAX_PAYLOAD (%zu)", (unsigned long long)payload_len, (size_t)N_WS_MAX_PAYLOAD);
7196 return -1;
7197 }
7198
7199 /* read masking key if present */
7200 unsigned char mask_key[4] = {0};
7201 if (mask_bit) {
7202 if (_ws_read(conn, mask_key, 4) < 0) return -1;
7203 }
7204
7205 /* read payload */
7206 N_STR* payload = new_nstr((size_t)payload_len + 1);
7207 __n_assert(payload, return -1);
7208
7209 if (payload_len > 0) {
7210 if (_ws_read(conn, payload->data, (size_t)payload_len) < 0) {
7211 free_nstr(&payload);
7212 return -1;
7213 }
7214 /* unmask if needed */
7215 if (mask_bit) {
7216 for (uint64_t i = 0; i < payload_len; i++) {
7217 payload->data[i] = (char)((unsigned char)payload->data[i] ^ mask_key[i % 4]);
7218 }
7219 }
7220 }
7221 payload->written = (size_t)payload_len;
7222 payload->data[payload_len] = '\0';
7223
7224 msg_out->opcode = opcode;
7225 msg_out->payload = payload;
7226 msg_out->masked = mask_bit;
7227
7228 return 0;
7229}
7230
7240 __n_assert(conn, return);
7241
7242 if (conn->connected && conn->netw) {
7243 /* send close frame with empty payload */
7244 n_ws_send(conn, NULL, 0, N_WS_OP_CLOSE);
7245
7246 /* try to read close response with a short timeout */
7247 /* set non-blocking or just try one read */
7248 N_WS_MESSAGE msg;
7249 memset(&msg, 0, sizeof(msg));
7250 /* best effort: read one frame, ignore errors */
7251 if (n_ws_recv(conn, &msg) == 0) {
7252 free_nstr(&msg.payload);
7253 }
7254 conn->connected = 0;
7255 }
7256
7257 if (conn->netw) {
7258 netw_close(&conn->netw);
7259 }
7260}
7261
7270 __n_assert(conn, return);
7271 __n_assert(*conn, return);
7272
7273 N_WS_CONN* c = *conn;
7274
7275 if (c->connected) {
7276 n_ws_close(c);
7277 }
7278
7279 if (c->netw) {
7280 netw_close(&c->netw);
7281 }
7282
7283 FreeNoLog(c->host);
7284 FreeNoLog(c->path);
7285 FreeNoLog(c);
7286 *conn = NULL;
7287}
7288
7294 if (!event) return;
7295 if (event->event) free_nstr(&event->event);
7296 if (event->data) free_nstr(&event->data);
7297 if (event->id) free_nstr(&event->id);
7298 event->retry = 0;
7299}
7300
7306 __n_assert(conn, return);
7307 __atomic_store_n(&conn->stop_flag, 1, __ATOMIC_RELEASE);
7308}
7309
7315 __n_assert(conn, return);
7316 __n_assert(*conn, return);
7317
7318 N_SSE_CONN* c = *conn;
7319 if (c->netw) {
7320 netw_close(&c->netw);
7321 }
7322 FreeNoLog(c);
7323 *conn = NULL;
7324}
7325
7332static ssize_t _sse_read_byte(NETWORK* netw, char* ch, volatile int* stop_flag) {
7333 __n_assert(netw, return -1);
7334
7335 /* poll with 500ms timeout so we can check stop_flag periodically */
7336 for (;;) {
7337 if (stop_flag && __atomic_load_n(stop_flag, __ATOMIC_ACQUIRE)) {
7338 return -1;
7339 }
7340 struct timeval tv;
7341 tv.tv_sec = 0;
7342 tv.tv_usec = 500000; /* 500ms */
7343 fd_set fds;
7344 FD_ZERO(&fds);
7345 FD_SET(netw->link.sock, &fds);
7346
7347 /* For SSL: check if there's already buffered data */
7348 int ready = 0;
7349 if (netw->crypto_algo == NETW_ENCRYPT_OPENSSL && netw->ssl) {
7350 if (SSL_pending(netw->ssl) > 0) {
7351 ready = 1;
7352 }
7353 }
7354 if (!ready) {
7355 int sel = select((int)netw->link.sock + 1, &fds, NULL, NULL, &tv);
7356 if (sel < 0) return -1; /* error */
7357 if (sel == 0) continue; /* timeout, loop back to check stop_flag */
7358 }
7359
7360 ssize_t ret = 0;
7361 if (netw->crypto_algo == NETW_ENCRYPT_OPENSSL && netw->ssl) {
7362 ret = SSL_read(netw->ssl, ch, 1);
7363 } else {
7364 ret = recv(netw->link.sock, ch, 1, 0);
7365 }
7366 return (ret == 1) ? 1 : -1;
7367 }
7368}
7369
7375static ssize_t _sse_write(NETWORK* netw, const void* buf, size_t len) {
7376 __n_assert(netw, return -1);
7377 if (netw->crypto_algo == NETW_ENCRYPT_OPENSSL && netw->ssl) {
7378 int ret = SSL_write(netw->ssl, buf, (int)len);
7379 return (ret > 0) ? (ssize_t)ret : -1;
7380 }
7381 ssize_t ret = send(netw->link.sock, buf, NETW_BUFLEN_CAST(len), NETFLAGS);
7382 return ret;
7383}
7384
7399N_SSE_CONN* n_sse_connect(const char* host, const char* port, const char* path, int use_ssl, const char* user_agent, void (*on_event)(N_SSE_EVENT*, N_SSE_CONN*, void*), void* user_data) {
7400 __n_assert(host, return NULL);
7401 __n_assert(port, return NULL);
7402 __n_assert(path, return NULL);
7403 __n_assert(on_event, return NULL);
7404
7405 N_SSE_CONN* conn = NULL;
7406 Malloc(conn, N_SSE_CONN, 1);
7407 __n_assert(conn, return NULL);
7408
7409 conn->netw = NULL;
7410 conn->stop_flag = 0;
7411 conn->on_event = on_event;
7412 conn->user_data = user_data;
7413
7414 /* establish TCP + optional SSL */
7415 if (use_ssl) {
7416 if (netw_ssl_connect_client(&conn->netw, (char*)host, (char*)port, NETWORK_IPALL) == FALSE) {
7417 n_log(LOG_ERR, "n_sse_connect: TCP+SSL context setup failed for %s:%s", host, port);
7418 goto sse_connect_fail;
7419 }
7420 if (netw_ssl_do_handshake(conn->netw, host) == FALSE) {
7421 _netw_capture_error(conn->netw, "n_sse_connect: SSL handshake failed for %s:%s", host, port);
7422 n_log(LOG_ERR, "n_sse_connect: SSL handshake failed for %s:%s", host, port);
7423 goto sse_connect_fail;
7424 }
7425 } else {
7426 if (netw_connect(&conn->netw, (char*)host, (char*)port, NETWORK_IPALL) == FALSE) {
7427 n_log(LOG_ERR, "n_sse_connect: TCP connect failed for %s:%s", host, port);
7428 goto sse_connect_fail;
7429 }
7430 }
7431
7432 /* send HTTP GET with SSE headers */
7433 char request[4096];
7434 int req_len;
7435 if (user_agent && user_agent[0]) {
7436 req_len = snprintf(request, sizeof(request),
7437 "GET %s HTTP/1.1\r\n"
7438 "Host: %s\r\n"
7439 "Accept: text/event-stream\r\n"
7440 "Cache-Control: no-cache\r\n"
7441 "Connection: keep-alive\r\n"
7442 "User-Agent: %s\r\n"
7443 "\r\n",
7444 path, host, user_agent);
7445 } else {
7446 req_len = snprintf(request, sizeof(request),
7447 "GET %s HTTP/1.1\r\n"
7448 "Host: %s\r\n"
7449 "Accept: text/event-stream\r\n"
7450 "Cache-Control: no-cache\r\n"
7451 "Connection: keep-alive\r\n"
7452 "\r\n",
7453 path, host);
7454 }
7455
7456 if (req_len < 0 || (size_t)req_len >= sizeof(request)) {
7457 _netw_capture_error(conn->netw, "n_sse_connect: request too large");
7458 n_log(LOG_ERR, "n_sse_connect: request too large");
7459 goto sse_connect_fail;
7460 }
7461
7462 if (_sse_write(conn->netw, request, (size_t)req_len) < 0) {
7463 _netw_capture_error(conn->netw, "n_sse_connect: failed to send HTTP request");
7464 n_log(LOG_ERR, "n_sse_connect: failed to send HTTP request");
7465 goto sse_connect_fail;
7466 }
7467
7468 /* read HTTP response headers (byte by byte until \r\n\r\n) */
7469 char resp_buf[8192];
7470 size_t resp_len = 0;
7471 while (resp_len < sizeof(resp_buf) - 1) {
7472 char ch = 0;
7473 if (_sse_read_byte(conn->netw, &ch, NULL) < 0) {
7474 _netw_capture_error(conn->netw, "n_sse_connect: failed reading HTTP response");
7475 n_log(LOG_ERR, "n_sse_connect: failed reading HTTP response");
7476 goto sse_connect_fail;
7477 }
7478 resp_buf[resp_len++] = ch;
7479 if (resp_len >= 4 &&
7480 resp_buf[resp_len - 4] == '\r' && resp_buf[resp_len - 3] == '\n' &&
7481 resp_buf[resp_len - 2] == '\r' && resp_buf[resp_len - 1] == '\n') {
7482 break;
7483 }
7484 }
7485 resp_buf[resp_len] = '\0';
7486
7487 /* verify HTTP 200 status */
7488 if (strstr(resp_buf, "200") == NULL) {
7489 _netw_capture_error(conn->netw, "n_sse_connect: server did not return 200: %.128s", resp_buf);
7490 n_log(LOG_ERR, "n_sse_connect: server did not return 200: %.128s", resp_buf);
7491 goto sse_connect_fail;
7492 }
7493
7494 n_log(LOG_INFO, "n_sse_connect: SSE connected to %s:%s%s", host, port, path);
7495
7496 /* enter SSE read loop */
7497 {
7498 N_SSE_EVENT current;
7499 memset(&current, 0, sizeof(current));
7500
7501 /* line buffer for SSE parsing */
7502 char line[8192];
7503 size_t line_len = 0;
7504
7505 while (!__atomic_load_n(&conn->stop_flag, __ATOMIC_ACQUIRE)) {
7506 char ch = 0;
7507 if (_sse_read_byte(conn->netw, &ch, &conn->stop_flag) < 0) {
7508 n_log(LOG_DEBUG, "n_sse_connect: connection closed or read error");
7509 break;
7510 }
7511
7512 if (ch == '\n') {
7513 /* terminate line (strip trailing \r) */
7514 if (line_len > 0 && line[line_len - 1] == '\r') {
7515 line_len--;
7516 }
7517 line[line_len] = '\0';
7518
7519 if (line_len == 0) {
7520 /* empty line = dispatch event if we have data */
7521 if (current.data) {
7522 conn->on_event(&current, conn, conn->user_data);
7523 n_sse_event_clean(&current);
7524 memset(&current, 0, sizeof(current));
7525 }
7526 } else if (line[0] == ':') {
7527 /* comment line, ignore */
7528 n_log(LOG_DEBUG, "n_sse_connect: comment: %s", line + 1);
7529 } else {
7530 /* parse field:value */
7531 char* colon = strchr(line, ':');
7532 const char* field = line;
7533 const char* value = "";
7534 if (colon) {
7535 *colon = '\0';
7536 value = colon + 1;
7537 /* skip single leading space after colon */
7538 if (*value == ' ') value++;
7539 }
7540
7541 if (strcmp(field, "data") == 0) {
7542 if (current.data) {
7543 /* append newline + value to existing data */
7544 nstrprintf_cat(current.data, "\n%s", value);
7545 } else {
7546 current.data = char_to_nstr((char*)value);
7547 }
7548 } else if (strcmp(field, "event") == 0) {
7549 if (current.event) free_nstr(&current.event);
7550 current.event = char_to_nstr((char*)value);
7551 } else if (strcmp(field, "id") == 0) {
7552 if (current.id) free_nstr(&current.id);
7553 current.id = char_to_nstr((char*)value);
7554 } else if (strcmp(field, "retry") == 0) {
7555 current.retry = atoi(value);
7556 }
7557 }
7558
7559 line_len = 0;
7560 } else {
7561 if (line_len < sizeof(line) - 1) {
7562 line[line_len++] = ch;
7563 }
7564 }
7565 }
7566
7567 /* clean up any partial event */
7568 n_sse_event_clean(&current);
7569 }
7570
7571 return conn;
7572
7573sse_connect_fail:
7574 n_sse_conn_free(&conn);
7575 return NULL;
7576}
7577
7578#endif /* HAVE_OPENSSL */
7579
7582#define N_WS_FRAME_MAX_PAYLOAD (16U * 1024U * 1024U)
7583
7593int n_ws_frame_parse(const unsigned char* buf, size_t len, N_WS_FRAME* frame, size_t* consumed) {
7594 size_t hdr = 2;
7595 uint64_t plen;
7596 int len7;
7597 if (!buf || !frame || !consumed)
7598 return -1;
7599 if (len < 2)
7600 return 0;
7601 frame->fin = (buf[0] & 0x80) ? 1 : 0;
7602 frame->rsv = (buf[0] >> 4) & 0x07;
7603 frame->opcode = buf[0] & 0x0F;
7604 frame->masked = (buf[1] & 0x80) ? 1 : 0;
7605 len7 = buf[1] & 0x7F;
7606 if (len7 < 126) {
7607 plen = (uint64_t)len7;
7608 } else if (len7 == 126) {
7609 if (len < 4)
7610 return 0;
7611 plen = ((uint64_t)buf[2] << 8) | (uint64_t)buf[3];
7612 hdr = 4;
7613 } else {
7614 int i;
7615 if (len < 10)
7616 return 0;
7617 plen = 0;
7618 for (i = 0; i < 8; i++)
7619 plen = (plen << 8) | (uint64_t)buf[2 + i];
7620 hdr = 10;
7621 }
7622 if (plen > N_WS_FRAME_MAX_PAYLOAD)
7623 return -1;
7624 if (frame->masked) {
7625 if (len < hdr + 4)
7626 return 0;
7627 memcpy(frame->mask, buf + hdr, 4);
7628 hdr += 4;
7629 } else {
7630 memset(frame->mask, 0, 4);
7631 }
7632 if (len < hdr + plen)
7633 return 0;
7634 frame->payload_len = plen;
7635 frame->payload = buf + hdr;
7636 *consumed = hdr + (size_t)plen;
7637 return 1;
7638}
7639
7640void n_ws_unmask(unsigned char* dst, const unsigned char* src, size_t len, const unsigned char mask[4]) {
7641 size_t i;
7642 if (!dst || !src || !mask)
7643 return;
7644 for (i = 0; i < len; i++)
7645 dst[i] = (unsigned char)(src[i] ^ mask[i & 3]);
7646}
7647
7648size_t n_ws_frame_build(int fin, int opcode, int do_mask, const unsigned char* payload, size_t len, const unsigned char mask_key[4], unsigned char* out, size_t out_cap) {
7649 size_t pos = 0;
7650 unsigned char b1 = (unsigned char)(do_mask ? 0x80 : 0);
7651 if (!out || out_cap < 2)
7652 return 0;
7653 out[pos++] = (unsigned char)((fin ? 0x80 : 0) | (opcode & 0x0F));
7654 if (len <= 125) {
7655 out[pos++] = (unsigned char)(b1 | (unsigned char)len);
7656 } else if (len <= 65535) {
7657 if (out_cap < pos + 3)
7658 return 0;
7659 out[pos++] = (unsigned char)(b1 | 126);
7660 out[pos++] = (unsigned char)((len >> 8) & 0xFF);
7661 out[pos++] = (unsigned char)(len & 0xFF);
7662 } else {
7663 int i;
7664 if (out_cap < pos + 9)
7665 return 0;
7666 out[pos++] = (unsigned char)(b1 | 127);
7667 for (i = 7; i >= 0; i--)
7668 out[pos++] = (unsigned char)((len >> (8 * i)) & 0xFF);
7669 }
7670 if (do_mask) {
7671 if (!mask_key || out_cap < pos + 4)
7672 return 0;
7673 memcpy(out + pos, mask_key, 4);
7674 pos += 4;
7675 }
7676 if (out_cap < pos + len)
7677 return 0;
7678 if (do_mask && mask_key) {
7679 size_t i;
7680 for (i = 0; i < len; i++)
7681 out[pos + i] = (unsigned char)((payload ? payload[i] : 0) ^ mask_key[i & 3]);
7682 } else if (payload && len > 0) {
7683 memcpy(out + pos, payload, len);
7684 }
7685 pos += len;
7686 return pos;
7687}
7688
7694static void _n_mock_parse_request(const char* buf, N_HTTP_REQUEST* req) {
7695 if (!buf || !req) return;
7696
7697 /* parse request line: METHOD PATH?QUERY HTTP/1.x */
7698 const char* line_end = strstr(buf, "\r\n");
7699 if (!line_end) line_end = strchr(buf, '\n');
7700 if (!line_end) return;
7701
7702 size_t line_len = (size_t)(line_end - buf);
7703 char line[4096];
7704 if (line_len >= sizeof(line)) line_len = sizeof(line) - 1;
7705 memcpy(line, buf, line_len);
7706 line[line_len] = '\0';
7707
7708 /* method */
7709 char* sp1 = strchr(line, ' ');
7710 if (!sp1) return;
7711 size_t mlen = (size_t)(sp1 - line);
7712 if (mlen >= sizeof(req->method)) mlen = sizeof(req->method) - 1;
7713 memcpy(req->method, line, mlen);
7714 req->method[mlen] = '\0';
7715
7716 /* path and query */
7717 sp1++;
7718 char* sp2 = strchr(sp1, ' ');
7719 if (sp2) *sp2 = '\0';
7720 char* qmark = strchr(sp1, '?');
7721 if (qmark) {
7722 *qmark = '\0';
7723 strncpy(req->query, qmark + 1, sizeof(req->query) - 1);
7724 req->query[sizeof(req->query) - 1] = '\0';
7725 }
7726 strncpy(req->path, sp1, sizeof(req->path) - 1);
7727 req->path[sizeof(req->path) - 1] = '\0';
7728
7729 /* headers: skip past request line */
7730 const char* hdr_start = line_end;
7731 if (*hdr_start == '\r') hdr_start++;
7732 if (*hdr_start == '\n') hdr_start++;
7733
7734 req->headers = new_generic_list(0);
7735
7736 const char* body_start = NULL;
7737 while (hdr_start && *hdr_start) {
7738 const char* next = strstr(hdr_start, "\r\n");
7739 if (!next) next = strchr(hdr_start, '\n');
7740 if (!next) break;
7741
7742 size_t hlen = (size_t)(next - hdr_start);
7743 if (hlen == 0) {
7744 /* blank line = end of headers */
7745 body_start = next;
7746 if (*body_start == '\r') body_start++;
7747 if (*body_start == '\n') body_start++;
7748 break;
7749 }
7750
7751 char* header_line = strndup(hdr_start, hlen);
7752 if (header_line) {
7753 list_push(req->headers, header_line, free);
7754 }
7755
7756 hdr_start = next;
7757 if (*hdr_start == '\r') hdr_start++;
7758 if (*hdr_start == '\n') hdr_start++;
7759 }
7760
7761 /* body */
7762 if (body_start && *body_start) {
7763 req->body = char_to_nstr(body_start);
7764 }
7765}
7766
7772 if (!req) return;
7773 if (req->headers) list_destroy(&req->headers);
7774 if (req->body) free_nstr(&req->body);
7775}
7776
7785 void (*on_request)(N_HTTP_REQUEST*, N_HTTP_RESPONSE*, void*),
7786 void* user_data) {
7787 __n_assert(on_request, return NULL);
7788
7789 N_MOCK_SERVER* server = NULL;
7791 __n_assert(server, return NULL);
7792 memset(server, 0, sizeof(*server));
7793
7794 server->on_request = on_request;
7795 server->user_data = user_data;
7796 server->port = port;
7797 server->stop_flag = 0;
7798
7799 char port_str[16];
7800 snprintf(port_str, sizeof(port_str), "%d", port);
7801
7802 NETWORK* listener = NULL;
7803 if (netw_make_listening(&listener, NULL, port_str, 5, NETWORK_IPALL) != TRUE) {
7804 n_log(LOG_ERR, "n_mock_server_start: failed to listen on port %d", port);
7806 return NULL;
7807 }
7808 server->listener = listener;
7809
7810 n_log(LOG_INFO, "mock server listening on port %d", port);
7811 return server;
7812}
7813
7819 __n_assert(server, return);
7820 __n_assert(server->listener, return);
7821
7822 while (!__atomic_load_n(&server->stop_flag, __ATOMIC_ACQUIRE)) {
7823 /* Use select with timeout to avoid blocking forever */
7824 fd_set readfds;
7825 FD_ZERO(&readfds);
7826 FD_SET(server->listener->link.sock, &readfds);
7827 struct timeval tv;
7828 tv.tv_sec = 0;
7829 tv.tv_usec = 200000; /* 200ms */
7830 int sel = select((int)(server->listener->link.sock + 1), &readfds, NULL, NULL, &tv);
7831 if (sel <= 0) continue;
7832
7833 /* Accept connection using netw_accept_from_ex with 1000ms timeout */
7834 int ret = 0;
7835 NETWORK* client = netw_accept_from_ex(server->listener, 0, 0, 1000, &ret);
7836 if (!client) continue;
7837
7838 /* Read HTTP request */
7839 char buf[8192];
7840 ssize_t n = recv(client->link.sock, buf, sizeof(buf) - 1, 0);
7841 if (n <= 0) {
7842 netw_close(&client);
7843 continue;
7844 }
7845 buf[n] = '\0';
7846
7847 /* Parse request */
7848 N_HTTP_REQUEST req;
7849 memset(&req, 0, sizeof(req));
7850 _n_mock_parse_request(buf, &req);
7851
7852 /* Prepare default response */
7853 N_HTTP_RESPONSE resp;
7854 memset(&resp, 0, sizeof(resp));
7855 resp.status_code = 404;
7856 strncpy(resp.content_type, "text/plain", sizeof(resp.content_type) - 1);
7857
7858 /* Call handler */
7859 server->on_request(&req, &resp, server->user_data);
7860
7861 /* Build HTTP response */
7862 const char* status_msg = netw_get_http_status_message(resp.status_code);
7863 if (!status_msg) status_msg = "Unknown";
7864
7865 size_t body_len = 0;
7866 const char* body_data = "";
7867 if (resp.body && resp.body->data) {
7868 body_data = resp.body->data;
7869 body_len = resp.body->written;
7870 }
7871
7872 char header_buf[1024];
7873 int hlen = snprintf(header_buf, sizeof(header_buf),
7874 "HTTP/1.1 %d %s\r\n"
7875 "Content-Type: %s\r\n"
7876 "Content-Length: %zu\r\n"
7877 "Connection: close\r\n"
7878 "\r\n",
7879 resp.status_code, status_msg,
7880 resp.content_type,
7881 body_len);
7882
7883 if (hlen > 0) {
7884 send(client->link.sock, header_buf, NETW_BUFLEN_CAST(hlen), NETFLAGS);
7885 }
7886 if (body_len > 0) {
7887 send(client->link.sock, body_data, NETW_BUFLEN_CAST(body_len), NETFLAGS);
7888 }
7889
7890 /* Cleanup */
7891 if (resp.body) free_nstr(&resp.body);
7893 netw_close(&client);
7894 }
7895
7896 n_log(LOG_INFO, "mock server stopped");
7897}
7898
7904 __n_assert(server, return);
7905 __atomic_store_n(&server->stop_flag, 1, __ATOMIC_RELEASE);
7906}
7907
7913 __n_assert(server && *server, return);
7914 if ((*server)->listener) {
7915 netw_close(&(*server)->listener);
7916 }
7917 FreeNoLog(*server);
7918 *server = NULL;
7919}
7920
7921static void _n_parse_query_params(N_URL* u, const char* qs) {
7922 if (!u || !qs || !qs[0]) return;
7923 char* buf = strdup(qs);
7924 if (!buf) return;
7925 char* saveptr = NULL;
7926 /* NOT const: this walks the mutable copy in buf, and the loop writes a NUL into it
7927 to split key from value */
7928 // cppcheck-suppress constVariablePointer ; see above, strchr() on a const char* still yields a char* in C and the loop writes through it
7929 char* tok = strtok_r(buf, "&", &saveptr);
7930 while (tok && u->nb_params < N_URL_MAX_PARAMS) {
7931 char* eq = strchr(tok, '=');
7932 if (eq) {
7933 *eq = '\0';
7934 u->params[u->nb_params].key = strdup(tok);
7935 u->params[u->nb_params].value = strdup(eq + 1);
7936 } else {
7937 u->params[u->nb_params].key = strdup(tok);
7938 u->params[u->nb_params].value = strdup("");
7939 }
7940 u->nb_params++;
7941 tok = strtok_r(NULL, "&", &saveptr);
7942 }
7943 free(buf);
7944}
7945
7946N_URL* n_url_parse(const char* url) {
7947 if (!url) return NULL;
7948 N_URL* u = NULL;
7949 Malloc(u, N_URL, 1);
7950 if (!u) return NULL;
7951 memset(u, 0, sizeof(*u));
7952 const char* p = url;
7953 const char* scheme_end = strstr(p, "://");
7954 if (scheme_end) {
7955 u->scheme = strndup(p, (size_t)(scheme_end - p));
7956 p = scheme_end + 3;
7957 } else {
7958 u->scheme = strdup("http");
7959 }
7960 const char* host_end = p;
7961 while (*host_end && *host_end != '/' && *host_end != '?' && *host_end != ':') host_end++;
7962 u->host = strndup(p, (size_t)(host_end - p));
7963 p = host_end;
7964 if (*p == ':') {
7965 p++;
7966 u->port = atoi(p);
7967 while (*p && *p != '/' && *p != '?') p++;
7968 }
7969 if (*p == '/') {
7970 const char* pe = p;
7971 while (*pe && *pe != '?') pe++;
7972 u->path = strndup(p, (size_t)(pe - p));
7973 p = pe;
7974 } else {
7975 u->path = strdup("/");
7976 }
7977 if (*p == '?') {
7978 p++;
7979 u->query = strdup(p);
7981 }
7982 return u;
7983}
7984
7986 if (!u) return NULL;
7987 N_STR* result = new_nstr(512);
7988 if (!result) return NULL;
7989 nstrprintf(result, "%s://%s", u->scheme ? u->scheme : "http", u->host ? u->host : "localhost");
7990 if (u->port > 0) {
7991 int dp = (u->scheme && strcmp(u->scheme, "https") == 0) ? 443 : 80;
7992 if (u->port != dp) nstrprintf_cat(result, ":%d", u->port);
7993 }
7994 nstrprintf_cat(result, "%s", u->path ? u->path : "/");
7995 if (u->query && u->query[0]) nstrprintf_cat(result, "?%s", u->query);
7996 return result;
7997}
7998
8005static void _n_url_normalize_path(const char* path, char* out, size_t outsz) {
8006 const char* seg_starts[256];
8007 size_t seg_lens[256];
8008 int nb = 0;
8009 const char* p = (path && path[0]) ? path : "/";
8010 size_t pos = 0;
8011 int i;
8012 /* split on '/', applying the dot-segment rules */
8013 while (*p) {
8014 const char* slash;
8015 size_t slen;
8016 if (*p == '/') {
8017 p++;
8018 continue;
8019 }
8020 slash = strchr(p, '/');
8021 slen = slash ? (size_t)(slash - p) : strlen(p);
8022 if (slen == 1 && p[0] == '.') {
8023 /* skip "." */
8024 } else if (slen == 2 && p[0] == '.' && p[1] == '.') {
8025 if (nb > 0) nb--; /* pop one segment */
8026 } else if (nb < 256) {
8027 seg_starts[nb] = p;
8028 seg_lens[nb] = slen;
8029 nb++;
8030 }
8031 p += slen;
8032 }
8033 /* rebuild with a leading slash */
8034 if (outsz == 0) return;
8035 out[pos++] = '/';
8036 for (i = 0; i < nb && pos < outsz - 1; i++) {
8037 size_t k;
8038 if (i > 0 && pos < outsz - 1) out[pos++] = '/';
8039 for (k = 0; k < seg_lens[i] && pos < outsz - 1; k++)
8040 out[pos++] = seg_starts[i][k];
8041 }
8042 /* preserve a trailing slash from the original (it can be significant) */
8043 if (path && path[0] && path[strlen(path) - 1] == '/' && pos < outsz - 1 && (nb > 0))
8044 out[pos++] = '/';
8045 out[pos] = '\0';
8046}
8047
8058 N_STR* result = NULL;
8059 char scheme[16];
8060 char host[256];
8061 char path[2048];
8062 size_t i;
8063 if (!u) return NULL;
8064 snprintf(scheme, sizeof(scheme), "%s", u->scheme ? u->scheme : "http");
8065 snprintf(host, sizeof(host), "%s", u->host ? u->host : "");
8066 for (i = 0; scheme[i]; i++) scheme[i] = (char)tolower((unsigned char)scheme[i]);
8067 for (i = 0; host[i]; i++) host[i] = (char)tolower((unsigned char)host[i]);
8068 _n_url_normalize_path(u->path, path, sizeof(path));
8069 result = new_nstr(2560);
8070 if (!result) return NULL;
8071 nstrprintf(result, "%s://%s", scheme, host);
8072 if (u->port > 0) {
8073 int dp = (strcmp(scheme, "https") == 0) ? 443 : 80;
8074 if (u->port != dp) nstrprintf_cat(result, ":%d", u->port);
8075 }
8076 nstrprintf_cat(result, "%s", path);
8077 if (u->query && u->query[0]) nstrprintf_cat(result, "?%s", u->query);
8078 return result;
8079}
8080
8087 N_URL* u = n_url_parse(url);
8088 N_STR* out;
8089 if (!u) return NULL;
8090 out = n_url_canonicalize(u);
8091 n_url_free(&u);
8092 return out;
8093}
8094
8100static int _n_url_has_scheme(const char* s) {
8101 if (!s || !isalpha((unsigned char)s[0])) return 0;
8102 size_t i = 1;
8103 while (s[i] && (isalnum((unsigned char)s[i]) || s[i] == '+' || s[i] == '-' || s[i] == '.')) i++;
8104 return s[i] == ':';
8105}
8106
8124N_STR* n_url_resolve(const char* base, const char* ref) {
8125 if (!base) return NULL;
8126
8127 /* fragment-stripped working copy of the reference */
8128 char* refbuf = strdup(ref ? ref : "");
8129 if (!refbuf) return NULL;
8130 char* frag = strchr(refbuf, '#');
8131 if (frag) *frag = '\0';
8132
8133 /* absolute reference: it carries its own scheme, return it verbatim */
8134 if (_n_url_has_scheme(refbuf)) {
8135 N_STR* out = new_nstr(strlen(refbuf) + 1);
8136 if (out) nstrprintf(out, "%s", refbuf);
8137 free(refbuf);
8138 return out;
8139 }
8140
8141 N_URL* b = n_url_parse(base);
8142 if (!b) {
8143 free(refbuf);
8144 return NULL;
8145 }
8146
8147 /* split the reference into its path and query parts at the first '?' */
8148 char* refq = strchr(refbuf, '?');
8149 const char* ref_query = NULL;
8150 if (refq) {
8151 *refq = '\0';
8152 ref_query = refq + 1;
8153 }
8154
8155 /* base authority = host plus a non-elided port */
8156 char authority[300];
8157 if (b->port > 0)
8158 snprintf(authority, sizeof(authority), "%s:%d", b->host ? b->host : "", b->port);
8159 else
8160 snprintf(authority, sizeof(authority), "%s", b->host ? b->host : "");
8161
8162 char rawpath[4096];
8163 const char* t_scheme = b->scheme ? b->scheme : "http";
8164 const char* t_query = NULL;
8165
8166 if (refbuf[0] == '\0') {
8167 /* empty path: same resource; keep the base query unless one was given */
8168 snprintf(rawpath, sizeof(rawpath), "%s", b->path ? b->path : "/");
8169 t_query = refq ? ref_query : b->query;
8170 } else if (refbuf[0] == '/' && refbuf[1] == '/') {
8171 /* scheme-relative: base scheme, new authority + path from the reference */
8172 const char* a = refbuf + 2;
8173 const char* slash = strchr(a, '/');
8174 if (slash) {
8175 snprintf(authority, sizeof(authority), "%.*s", (int)(slash - a), a);
8176 snprintf(rawpath, sizeof(rawpath), "%s", slash);
8177 } else {
8178 snprintf(authority, sizeof(authority), "%s", a);
8179 snprintf(rawpath, sizeof(rawpath), "/");
8180 }
8181 t_query = ref_query;
8182 } else if (refbuf[0] == '/') {
8183 /* absolute-path reference */
8184 snprintf(rawpath, sizeof(rawpath), "%s", refbuf);
8185 t_query = ref_query;
8186 } else {
8187 /* relative-path reference: merge against the base path */
8188 const char* bp = (b->path && b->path[0]) ? b->path : "/";
8189 const char* lastslash = strrchr(bp, '/');
8190 size_t prefixlen = lastslash ? (size_t)(lastslash - bp) + 1 : 0;
8191 snprintf(rawpath, sizeof(rawpath), "%.*s%s", (int)prefixlen, bp, refbuf);
8192 t_query = ref_query;
8193 }
8194
8195 char normpath[4096];
8196 _n_url_normalize_path(rawpath, normpath, sizeof(normpath));
8197
8198 N_STR* out = new_nstr(strlen(authority) + sizeof(normpath) + 32);
8199 if (out) {
8200 nstrprintf(out, "%s://%s%s", t_scheme, authority, normpath);
8201 if (t_query && t_query[0]) nstrprintf_cat(out, "?%s", t_query);
8202 }
8203 n_url_free(&b);
8204 free(refbuf);
8205 return out;
8206}
8207
8208N_STR* n_url_encode(const char* str) {
8209 return n_str_url_encode(str);
8210}
8211
8212N_STR* n_url_decode(const char* str) {
8213 if (!str) return NULL;
8214 size_t len = strlen(str);
8215 N_STR* result = new_nstr(len + 1);
8216 if (!result) return NULL;
8217 for (size_t i = 0; i < len; i++) {
8218 if (str[i] == '%' && i + 2 < len && isxdigit((unsigned char)str[i + 1]) && isxdigit((unsigned char)str[i + 2])) {
8219 const char hex[3] = {str[i + 1], str[i + 2], '\0'};
8220 unsigned int val = 0;
8221 sscanf(hex, "%x", &val);
8222 nstrprintf_cat(result, "%c", (char)val);
8223 i += 2;
8224 } else if (str[i] == '+') {
8225 nstrprintf_cat(result, " ");
8226 } else {
8227 nstrprintf_cat(result, "%c", str[i]);
8228 }
8229 }
8230 return result;
8231}
8232
8234 if (!u || !*u) return;
8235 N_URL* p = *u;
8236 FreeNoLog(p->scheme);
8237 FreeNoLog(p->host);
8238 FreeNoLog(p->path);
8239 FreeNoLog(p->query);
8240 for (int i = 0; i < p->nb_params; i++) {
8241 FreeNoLog(p->params[i].key);
8242 FreeNoLog(p->params[i].value);
8243 }
8244 FreeNoLog(p);
8245 *u = NULL;
8246}
8247
8254static SOCKET _proxy_tcp_connect(const char* host, int port) {
8255 char port_str[16];
8256 snprintf(port_str, sizeof(port_str), "%d", port);
8257
8258 struct addrinfo hints;
8259 memset(&hints, 0, sizeof(hints));
8260 hints.ai_family = AF_UNSPEC;
8261 hints.ai_socktype = SOCK_STREAM;
8262
8263 struct addrinfo* res = NULL;
8264 int gai_rc = getaddrinfo(host, port_str, &hints, &res);
8265 if (gai_rc != 0 || !res) {
8266 n_log(LOG_ERR, "n_proxy: DNS lookup failed for %s:%d: %s",
8267 host, port, gai_strerror(gai_rc));
8268 return INVALID_SOCKET;
8269 }
8270
8271 SOCKET fd = INVALID_SOCKET;
8272 struct addrinfo* rp = NULL;
8273 for (rp = res; rp; rp = rp->ai_next) {
8274 fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
8275 if (fd == INVALID_SOCKET) continue;
8276 if (connect(fd, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0) break;
8277 closesocket(fd);
8278 fd = INVALID_SOCKET;
8279 }
8280 freeaddrinfo(res);
8281
8282 if (fd == INVALID_SOCKET) {
8283 n_log(LOG_ERR, "n_proxy: TCP connect to %s:%d failed", host, port);
8284 }
8285 return fd;
8286}
8287
8289 if (!url) return NULL;
8290
8291 /* Expect scheme://... */
8292 const char* sep = strstr(url, "://");
8293 if (!sep) {
8294 n_log(LOG_ERR, "n_proxy_cfg_parse: no scheme in '%s'", url);
8295 return NULL;
8296 }
8297
8298 size_t scheme_len = (size_t)(sep - url);
8299 if (scheme_len == 0 || scheme_len > 16) return NULL;
8300
8301 char scheme[17];
8302 memcpy(scheme, url, scheme_len);
8303 scheme[scheme_len] = '\0';
8304
8305 /* Only http, https, and socks5 */
8306 if (strcmp(scheme, "http") != 0 && strcmp(scheme, "https") != 0 && strcmp(scheme, "socks5") != 0) {
8307 n_log(LOG_ERR, "n_proxy_cfg_parse: unsupported scheme '%s'", scheme);
8308 return NULL;
8309 }
8310
8311 const char* after = sep + 3; /* after :// */
8312 if (!*after) return NULL;
8313
8314 /* Check for user:pass@host:port */
8315 const char* at = strchr(after, '@');
8316 const char* host_start = NULL;
8317 char* username = NULL;
8318 char* password = NULL;
8319
8320 if (at) {
8321 /* Parse user:pass */
8322 const char* colon = strchr(after, ':');
8323 if (colon && colon < at) {
8324 username = strndup(after, (size_t)(colon - after));
8325 password = strndup(colon + 1, (size_t)(at - colon - 1));
8326 } else {
8327 username = strndup(after, (size_t)(at - after));
8328 }
8329 host_start = at + 1;
8330 } else {
8331 host_start = after;
8332 }
8333
8334 /* Parse host:port */
8335 /* Handle IPv6 [host]:port */
8336 char* hostname = NULL;
8337 int port = 0;
8338
8339 if (host_start[0] == '[') {
8340 const char* bracket = strchr(host_start, ']');
8341 if (!bracket) goto fail;
8342 hostname = strndup(host_start + 1, (size_t)(bracket - host_start - 1));
8343 if (bracket[1] == ':') {
8344 port = atoi(bracket + 2);
8345 }
8346 } else {
8347 const char* colon = strchr(host_start, ':');
8348 if (colon) {
8349 hostname = strndup(host_start, (size_t)(colon - host_start));
8350 port = atoi(colon + 1);
8351 } else {
8352 hostname = strdup(host_start);
8353 }
8354 }
8355
8356 if (!hostname || !hostname[0]) goto fail;
8357 if (port <= 0 || port > 65535) {
8358 /* Default ports */
8359 if (strcmp(scheme, "http") == 0 || strcmp(scheme, "https") == 0)
8360 port = 3128;
8361 else if (strcmp(scheme, "socks5") == 0)
8362 port = 1080;
8363 }
8364
8365 N_PROXY_CFG* cfg = NULL;
8366 Malloc(cfg, N_PROXY_CFG, 1);
8367 if (!cfg) goto fail;
8368 memset(cfg, 0, sizeof(*cfg));
8369 cfg->scheme = strdup(scheme);
8370 cfg->host = hostname;
8371 cfg->port = port;
8372 cfg->username = username;
8373 cfg->password = password;
8374 return cfg;
8375
8376fail:
8377 FreeNoLog(hostname);
8378 FreeNoLog(username);
8379 FreeNoLog(password);
8380 return NULL;
8381}
8382
8384 if (!cfg || !*cfg) return;
8385 N_PROXY_CFG* p = *cfg;
8386 FreeNoLog(p->scheme);
8387 FreeNoLog(p->host);
8388 FreeNoLog(p->username);
8389 FreeNoLog(p->password);
8390 FreeNoLog(p);
8391 *cfg = NULL;
8392}
8393
8395 const char* target_host,
8396 int target_port) {
8397 if (!proxy || !target_host) return -1;
8398
8399 SOCKET fd = _proxy_tcp_connect(proxy->host, proxy->port);
8400 if (fd == INVALID_SOCKET) return -1;
8401
8402 /* Build CONNECT request */
8403 char connect_req[2048];
8404 int len = 0;
8405
8406 len = snprintf(connect_req, sizeof(connect_req),
8407 "CONNECT %s:%d HTTP/1.1\r\n"
8408 "Host: %s:%d\r\n",
8409 target_host, target_port,
8410 target_host, target_port);
8411
8412 /* Proxy-Authorization if credentials present */
8413 if (proxy->username && proxy->username[0]) {
8414 char cred[512];
8415 snprintf(cred, sizeof(cred), "%s:%s",
8416 proxy->username, proxy->password ? proxy->password : "");
8417 N_STR* cred_nstr = char_to_nstr(cred);
8418 if (cred_nstr) {
8419 N_STR* b64 = n_base64_encode(cred_nstr);
8420 if (b64 && b64->data) {
8421 len += snprintf(connect_req + len,
8422 sizeof(connect_req) - (size_t)len,
8423 "Proxy-Authorization: Basic %s\r\n",
8424 b64->data);
8425 }
8426 if (b64) free_nstr(&b64);
8427 free_nstr(&cred_nstr);
8428 }
8429 }
8430
8431 len += snprintf(connect_req + len, sizeof(connect_req) - (size_t)len,
8432 "\r\n");
8433
8434 /* Send CONNECT */
8435 ssize_t sent = send(fd, connect_req, NETW_BUFLEN_CAST(len), 0);
8436 if (sent != (ssize_t)len) {
8437 n_log(LOG_ERR, "n_proxy_connect_tunnel: send CONNECT failed");
8438 closesocket(fd);
8439 return -1;
8440 }
8441
8442 /* Read response */
8443 char resp_buf[4096];
8444 ssize_t nr = recv(fd, resp_buf, NETW_BUFLEN_CAST(sizeof(resp_buf) - 1), 0);
8445 if (nr <= 0) {
8446 n_log(LOG_ERR, "n_proxy_connect_tunnel: no response from proxy");
8447 closesocket(fd);
8448 return -1;
8449 }
8450 resp_buf[nr] = '\0';
8451
8452 /* Check for "HTTP/1.x 200" */
8453 if (strstr(resp_buf, " 200 ") == NULL) {
8454 n_log(LOG_ERR, "n_proxy_connect_tunnel: proxy rejected CONNECT: %.*s",
8455 (int)(strchr(resp_buf, '\r') ? strchr(resp_buf, '\r') - resp_buf : nr),
8456 resp_buf);
8457 closesocket(fd);
8458 return -1;
8459 }
8460
8461 n_log(LOG_DEBUG, "n_proxy_connect_tunnel: tunnel established to %s:%d via %s:%d",
8462 target_host, target_port, proxy->host, proxy->port);
8463 return (int)fd;
8464}
8465
8466#ifdef HAVE_OPENSSL
8468 const char* target_host,
8469 int target_port) {
8470 if (!proxy || !target_host) return -1;
8471
8472 SOCKET fd = _proxy_tcp_connect(proxy->host, proxy->port);
8473 if (fd == INVALID_SOCKET) return -1;
8474
8475 /* TLS handshake to the proxy itself */
8477
8478#if OPENSSL_VERSION_NUMBER >= 0x10100000L
8479 const SSL_METHOD* method = TLS_client_method();
8480#else
8481 const SSL_METHOD* method = TLSv1_2_client_method();
8482#endif
8483 SSL_CTX* ctx = SSL_CTX_new(method);
8484 if (!ctx) {
8485 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: SSL_CTX_new failed");
8486 closesocket(fd);
8487 return -1;
8488 }
8489 SSL_CTX_set_default_verify_paths(ctx);
8490
8491 SSL* ssl = SSL_new(ctx);
8492 if (!ssl) {
8493 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: SSL_new failed");
8494 SSL_CTX_free(ctx);
8495 closesocket(fd);
8496 return -1;
8497 }
8498 SSL_set_fd(ssl, (int)fd);
8499 SSL_set_tlsext_host_name(ssl, proxy->host);
8500
8501 if (SSL_connect(ssl) <= 0) {
8502 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: TLS handshake to proxy %s:%d failed",
8503 proxy->host, proxy->port);
8504 SSL_free(ssl);
8505 SSL_CTX_free(ctx);
8506 closesocket(fd);
8507 return -1;
8508 }
8509
8510 n_log(LOG_DEBUG, "n_proxy_connect_tunnel_ssl: TLS established to proxy %s:%d",
8511 proxy->host, proxy->port);
8512
8513 /* Build CONNECT request */
8514 char connect_req[2048];
8515 int len = 0;
8516
8517 len = snprintf(connect_req, sizeof(connect_req),
8518 "CONNECT %s:%d HTTP/1.1\r\n"
8519 "Host: %s:%d\r\n",
8520 target_host, target_port,
8521 target_host, target_port);
8522
8523 /* Proxy-Authorization if credentials present */
8524 if (proxy->username && proxy->username[0]) {
8525 char cred[512];
8526 snprintf(cred, sizeof(cred), "%s:%s",
8527 proxy->username, proxy->password ? proxy->password : "");
8528 N_STR* cred_nstr = char_to_nstr(cred);
8529 if (cred_nstr) {
8530 N_STR* b64 = n_base64_encode(cred_nstr);
8531 if (b64 && b64->data) {
8532 len += snprintf(connect_req + len,
8533 sizeof(connect_req) - (size_t)len,
8534 "Proxy-Authorization: Basic %s\r\n",
8535 b64->data);
8536 }
8537 if (b64) free_nstr(&b64);
8538 free_nstr(&cred_nstr);
8539 }
8540 }
8541
8542 len += snprintf(connect_req + len, sizeof(connect_req) - (size_t)len,
8543 "\r\n");
8544
8545 /* Send CONNECT over TLS */
8546 if (SSL_write(ssl, connect_req, len) != len) {
8547 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: send CONNECT failed");
8548 SSL_free(ssl);
8549 SSL_CTX_free(ctx);
8550 closesocket(fd);
8551 return -1;
8552 }
8553
8554 /* Read response over TLS */
8555 char resp_buf[4096];
8556 int nr = SSL_read(ssl, resp_buf, (int)(sizeof(resp_buf) - 1));
8557 if (nr <= 0) {
8558 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: no response from proxy");
8559 SSL_free(ssl);
8560 SSL_CTX_free(ctx);
8561 closesocket(fd);
8562 return -1;
8563 }
8564 resp_buf[nr] = '\0';
8565
8566 /* Check for "HTTP/1.x 200" */
8567 if (strstr(resp_buf, " 200 ") == NULL) {
8568 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: proxy rejected CONNECT: %.*s",
8569 (int)(strchr(resp_buf, '\r') ? strchr(resp_buf, '\r') - resp_buf : nr),
8570 resp_buf);
8571 SSL_free(ssl);
8572 SSL_CTX_free(ctx);
8573 closesocket(fd);
8574 return -1;
8575 }
8576
8577 /* Tunnel established, tear down the proxy TLS session but keep the fd.
8578 * SSL_shutdown sends close_notify; the proxy knows the CONNECT handshake
8579 * is done and the raw tunnel begins. We call SSL_set_quiet_shutdown to
8580 * avoid waiting for the peer's close_notify (the proxy won't send one
8581 * mid-tunnel). */
8582 SSL_set_quiet_shutdown(ssl, 1);
8583 SSL_shutdown(ssl);
8584 SSL_free(ssl);
8585 SSL_CTX_free(ctx);
8586
8587 n_log(LOG_DEBUG, "n_proxy_connect_tunnel_ssl: tunnel established to %s:%d via https://%s:%d",
8588 target_host, target_port, proxy->host, proxy->port);
8589 return (int)fd;
8590}
8591#endif /* HAVE_OPENSSL */
8592
8594 const char* target_host,
8595 int target_port) {
8596 if (!proxy || !target_host) return -1;
8597
8598 SOCKET fd = _proxy_tcp_connect(proxy->host, proxy->port);
8599 if (fd == INVALID_SOCKET) return -1;
8600
8601 int use_auth = (proxy->username && proxy->username[0]) ? 1 : 0;
8602
8603 /* SOCKS5 greeting */
8604 char greeting[4];
8605 if (use_auth) {
8606 greeting[0] = 0x05; /* version */
8607 greeting[1] = 0x02; /* 2 methods */
8608 greeting[2] = 0x00; /* no auth */
8609 greeting[3] = 0x02; /* user/pass */
8610 if (send(fd, greeting, 4, 0) != 4) goto fail;
8611 } else {
8612 greeting[0] = 0x05;
8613 greeting[1] = 0x01;
8614 greeting[2] = 0x00;
8615 if (send(fd, greeting, 3, 0) != 3) goto fail;
8616 }
8617
8618 /* Read server method selection */
8619 char method_resp[2];
8620 if (recv(fd, method_resp, 2, 0) != 2) goto fail;
8621 if (method_resp[0] != 0x05) goto fail;
8622
8623 if (method_resp[1] == 0x02 && use_auth) {
8624 /* User/pass auth sub-negotiation (RFC 1929) */
8625 size_t ulen = strlen(proxy->username);
8626 size_t plen = proxy->password ? strlen(proxy->password) : 0;
8627 if (ulen > 255 || plen > 255) goto fail;
8628
8629 char auth_req[515]; /* 1+1+255+1+255 */
8630 size_t pos = 0;
8631 auth_req[pos++] = 0x01; /* version */
8632 auth_req[pos++] = (char)ulen;
8633 memcpy(auth_req + pos, proxy->username, ulen);
8634 pos += ulen;
8635 auth_req[pos++] = (char)plen;
8636 if (plen > 0) {
8637 memcpy(auth_req + pos, proxy->password, plen);
8638 pos += plen;
8639 }
8640 /* SOCKS5 username/password auth (RFC 1929) transmits the credentials
8641 * in cleartext by design; the protocol has no in-band encryption. A
8642 * caller needing confidentiality must tunnel this over an encrypted
8643 * link (e.g. an HTTPS CONNECT proxy). Not a fixable defect here. */
8644 if (send(fd, auth_req, NETW_BUFLEN_CAST(pos), 0) != (ssize_t)pos) goto fail;
8645
8646 char auth_resp[2];
8647 if (recv(fd, auth_resp, 2, 0) != 2) goto fail;
8648 if (auth_resp[1] != 0x00) {
8649 n_log(LOG_ERR, "n_proxy_connect_socks5: auth rejected");
8650 goto fail;
8651 }
8652 } else if (method_resp[1] != 0x00) {
8653 n_log(LOG_ERR, "n_proxy_connect_socks5: no acceptable method");
8654 goto fail;
8655 }
8656
8657 /* Send connect request (domain name addressing) */
8658 {
8659 size_t hlen = strlen(target_host);
8660 if (hlen > 255) goto fail;
8661
8662 char conn_req[263]; /* 4 + 1 + 255 + 2 */
8663 size_t pos = 0;
8664 conn_req[pos++] = 0x05; /* version */
8665 conn_req[pos++] = 0x01; /* connect */
8666 conn_req[pos++] = 0x00; /* reserved */
8667 conn_req[pos++] = 0x03; /* domain name */
8668 conn_req[pos++] = (char)hlen;
8669 memcpy(conn_req + pos, target_host, hlen);
8670 pos += hlen;
8671 conn_req[pos++] = (char)((target_port >> 8) & 0xFF);
8672 conn_req[pos++] = (char)(target_port & 0xFF);
8673
8674 if (send(fd, conn_req, NETW_BUFLEN_CAST(pos), 0) != (ssize_t)pos) goto fail;
8675 }
8676
8677 /* Read connect response */
8678 {
8679 char conn_resp[10];
8680 /* Minimum response: 4 bytes header + address (varies) */
8681 ssize_t nr = recv(fd, conn_resp, NETW_BUFLEN_CAST(sizeof(conn_resp)), 0);
8682 if (nr < 4) goto fail;
8683 if (conn_resp[0] != 0x05 || conn_resp[1] != 0x00) {
8684 n_log(LOG_ERR, "n_proxy_connect_socks5: connect failed, reply=%02x",
8685 conn_resp[1]);
8686 goto fail;
8687 }
8688
8689 /* If address type is domain (0x03), we may need to read more */
8690 if (conn_resp[3] == 0x03 && nr < 5) {
8691 /* Read remaining bytes */
8692 char extra[256];
8693 recv(fd, extra, NETW_BUFLEN_CAST(sizeof(extra)), 0);
8694 } else if (conn_resp[3] == 0x04 && nr < 10) {
8695 /* IPv6: 16 + 2 bytes remaining */
8696 char extra[18];
8697 size_t need = 22 - (size_t)nr; /* total IPv6 response = 4+16+2=22 */
8698 if (need <= sizeof(extra)) {
8699 recv(fd, extra, NETW_BUFLEN_CAST(need), 0);
8700 }
8701 }
8702 }
8703
8704 n_log(LOG_DEBUG, "n_proxy_connect_socks5: tunnel established to %s:%d via %s:%d",
8705 target_host, target_port, proxy->host, proxy->port);
8706 return (int)fd;
8707
8708fail:
8709 n_log(LOG_ERR, "n_proxy_connect_socks5: handshake failed");
8710 closesocket(fd);
8711 return -1;
8712}
8713
8731int netw_adopt_client_fd(NETWORK** netw, SOCKET fd, const char* host, const char* port) {
8732 __n_assert(netw, return FALSE);
8733 if ((*netw)) {
8734 n_log(LOG_ERR, "netw_adopt_client_fd: target NETWORK must be empty");
8735 return FALSE;
8736 }
8737 if (fd == INVALID_SOCKET) {
8738 n_log(LOG_ERR, "netw_adopt_client_fd: invalid socket");
8739 return FALSE;
8740 }
8741 if (netw_init_wsa(1, 2, 2) == FALSE) {
8742 n_log(LOG_ERR, "netw_adopt_client_fd: unable to load WSA dll's");
8743 return FALSE;
8744 }
8746 __n_assert(netw && (*netw), return FALSE);
8747
8748 (*netw)->link.sock = fd;
8749 (*netw)->link.ip = strdup(host ? host : "");
8750 if (!(*netw)->link.ip) {
8752 return FALSE;
8753 }
8754 (*netw)->link.port = strdup(port ? port : "");
8755 if (!(*netw)->link.port) {
8757 return FALSE;
8758 }
8759
8761 n_log(LOG_DEBUG, "netw_adopt_client_fd: adopted socket %d for %s:%s", (int)fd, (*netw)->link.ip, (*netw)->link.port);
8762 return TRUE;
8763} /* netw_adopt_client_fd */
int DONE
Definition ex_fluid.c:58
static int mode
static char * port_str
static NETWORK_POOL * pool
static void type(N_GUI_CTX *gui, const char *s)
Type a whole string into whatever has focus.
static int on_request(void *ud, N_H2_SERVER *srv, const N_H2_REQUEST *req)
NETWORK * netw
Network for server mode, accepting incomming.
Definition ex_network.c:39
char * ca_file
NETWORK * server
int ip_version
char * addr
char * cert
char * port
static int eq(double a, double b)
Definition ex_wrap.c:93
void line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color)
draw a line
volatile char key[227]
TRUE while the key is held.
#define init_lock(__rwlock_mutex)
Macro for initializing a rwlock.
Definition n_common.h:350
#define FreeNoLog(__ptr)
Free Handler without log.
Definition n_common.h:272
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
Definition n_common.h:204
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:279
#define _str(__PTR)
define true
Definition n_common.h:193
#define rw_lock_destroy(__rwlock_mutex)
Macro to destroy rwlock mutex.
Definition n_common.h:409
#define unlock(__rwlock_mutex)
Macro for releasing read/write lock a rwlock mutex.
Definition n_common.h:396
#define endif
close a ifwhatever block
Definition n_common.h:325
#define write_lock(__rwlock_mutex)
Macro for acquiring a write lock on a rwlock mutex.
Definition n_common.h:382
#define Free(__ptr)
Free Handler to get errors.
Definition n_common.h:263
#define read_lock(__rwlock_mutex)
Macro for acquiring a read lock on a rwlock mutex.
Definition n_common.h:368
#define _nstr(__PTR)
N_STR or "NULL" string for logging purposes.
Definition n_common.h:199
N_STR * n_base64_encode(N_STR *input)
encode a N_STR *string
Definition n_base64.c:270
#define N_ENUM_DEFINE(MACRO_DEFINITION, enum_name)
Macro to define an N_ENUM.
Definition n_enum.h:169
#define N_ENUM_ENTRY(class, method)
helper to build an N_ENUM
Definition n_enum.h:46
size_t nb_keys
total number of used keys in the table
Definition n_hash.h:142
int ht_get_ptr(HASH_TABLE *table, const char *key, void **val)
get pointer at 'key' from 'table'
Definition n_hash.c:2147
#define ht_foreach(__ITEM_, __HASH_)
ForEach macro helper (classic / old).
Definition n_hash.h:192
int destroy_ht(HASH_TABLE **table)
empty a table and destroy it
Definition n_hash.c:2281
int ht_remove(HASH_TABLE *table, const char *key)
remove and delete node at key in table
Definition n_hash.c:2239
HASH_TABLE * new_ht(size_t size)
Create a hash table with the given size.
Definition n_hash.c:2048
int ht_put_ptr(HASH_TABLE *table, const char *key, void *ptr, void(*destructor)(void *ptr), void *(*duplicator)(void *ptr))
put an arbitrary pointer value with given key in the targeted hash table
Definition n_hash.c:2201
int ht_put_string(HASH_TABLE *table, const char *key, char *string)
put a string value (copy/dup) with given key in the targeted hash table
Definition n_hash.c:2214
#define hash_val(node, type)
Cast a HASH_NODE element.
Definition n_hash.h:188
structure of a hash table node
Definition n_hash.h:112
structure of a hash table
Definition n_hash.h:138
#define list_shift(__LIST_, __TYPE_)
Shift macro helper for void pointer casting.
Definition n_list.h:96
int list_empty(LIST *list)
Empty a LIST list of pointers.
Definition n_list.c:501
LIST_NODE * list_search(LIST *list, const void *ptr)
search ptr in list
Definition n_list.c:469
int list_push(LIST *list, void *ptr, void(*destructor)(void *ptr))
Add a pointer to the end of the list.
Definition n_list.c:228
#define list_foreach(__ITEM_, __LIST_)
ForEach macro helper, safe for node removal during iteration.
Definition n_list.h:89
#define remove_list_node(__LIST_, __NODE_, __TYPE_)
Remove macro helper for void pointer casting.
Definition n_list.h:98
int list_destroy(LIST **list)
Empty and Free a list container.
Definition n_list.c:548
LIST * new_generic_list(size_t max_items)
Initialiaze a generic list container to max_items pointers.
Definition n_list.c:37
#define MAX_LIST_ITEMS
flag to pass to new_generic_list for the maximum possible number of item in a list
Definition n_list.h:75
Structure of a generic LIST container.
Definition n_list.h:59
Structure of a generic list node.
Definition n_list.h:44
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
#define LOG_ERR
error conditions
Definition n_log.h:100
#define N_PRINTF_FORMAT(fmt, args)
declare a function printf-like; nothing on a compiler without attributes
Definition n_log.h:63
#define LOG_WARNING
warning conditions
Definition n_log.h:102
#define LOG_INFO
informational
Definition n_log.h:106
N_STR * zip4_nstr(N_STR *src)
Compress src with LZ4 block format.
Definition n_lz4.c:55
N_STR * unzip4_nstr(N_STR *src)
Decompress an N_STR produced by zip4_nstr.
Definition n_lz4.c:107
size_t written
number of meaningful bytes in data, excluding the null terminator; the size including the null termin...
Definition n_str.h:68
char * data
the string
Definition n_str.h:63
size_t length
total allocation (in bytes) of the data buffer, padding included
Definition n_str.h:65
void free_nstr_ptr(void *ptr)
Free a N_STR pointer structure.
Definition n_str.c:70
N_STR * n_str_url_encode(const char *src)
Percent-encode a C string per RFC 3986 (unreserved set kept as-is).
Definition n_str.c:1734
size_t NSTRBYTE
N_STR base unit.
Definition n_str.h:58
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
#define nstrcat(__nstr_dst, __nstr_src)
Macro to quickly concatenate two N_STR.
Definition n_str.h:125
N_STR * nstrdup(N_STR *str)
Duplicate a N_STR.
Definition n_str.c:723
#define nstrprintf_cat(__nstr_var, __format,...)
Macro to quickly allocate and sprintf and cat to a N_STR.
Definition n_str.h:121
N_STR * char_to_nstr(const char *src)
Convert a char into a N_STR, short version.
Definition n_str.c:262
N_STR * new_nstr(NSTRBYTE size)
create a new N_STR string
Definition n_str.c:209
#define nstrprintf(__nstr_var, __format,...)
Macro to quickly allocate and sprintf to N_STR.
Definition n_str.h:117
int char_to_nstr_ex(const char *from, NSTRBYTE nboct, N_STR **to)
Convert a char into a N_STR, extended version.
Definition n_str.c:239
A box including a string and his lenght.
Definition n_str.h:61
void u_sleep(unsigned int usec)
wrapper around usleep for API consistency
Definition n_time.c:54
int start_HiTimer(N_TIME *timer)
Initialize or restart from zero any N_TIME HiTimer.
Definition n_time.c:85
time_t get_usec(N_TIME *timer)
Poll any N_TIME HiTimer, returning usec, and moving currentTime to startTime.
Definition n_time.c:107
Timing Structure.
Definition n_time.h:49
N_STR * netmsg_make_position_msg(int id, double X, double Y, double vx, double vy, double acc_x, double acc_y, int time_stamp)
make a network NETMSG_POSITION message with given parameters
N_STR * netmsg_make_ident(int type, int id, N_STR *name, N_STR *passwd)
Add a formatted NETWMSG_IDENT message to the specified network.
N_STR * netmsg_make_quit_msg(void)
make a generic network NETMSG_QUIT message
N_STR * netmsg_make_ping(int type, int id_from, int id_to, int time)
Make a ping message to send to a network.
N_STR * netmsg_make_string_msg(int id_from, int id_to, N_STR *name, N_STR *chan, N_STR *txt, int color)
make a network NETMSG_STRING message with given parameters
volatile int stop_flag
atomic stop flag
Definition n_network.h:1133
char query[2048]
query string (or empty)
Definition n_network.h:1159
N_SOCKET link
networking socket
Definition n_network.h:454
size_t content_length
Store content length.
Definition n_network.h:639
char * type
Type of request.
Definition n_network.h:643
char * filename
Content-Disposition filename, or "".
Definition n_network.h:896
int port
port number (0 if not specified)
Definition n_network.h:1027
int masked
MASK bit (client-to-server frames are masked).
Definition n_network.h:1085
char * host
proxy hostname
Definition n_network.h:928
pthread_rwlock_t rwlock
thread safety
Definition n_network.h:630
NETWORK * netw
underlying network connection
Definition n_network.h:1132
char * host
hostname
Definition n_network.h:1026
char * path
path starting with "/", or "/" if empty
Definition n_network.h:1028
char * content_type
the part's Content-Type, or ""
Definition n_network.h:897
N_STR * body
response body
Definition n_network.h:1168
char * scheme
"http" or "https"
Definition n_network.h:1025
int opcode
opcode (N_WS_OP_*)
Definition n_network.h:1084
NETWORK * netw
underlying network connection
Definition n_network.h:1073
void(* on_event)(N_SSE_EVENT *event, struct N_SSE_CONN *conn, void *user_data)
callback
Definition n_network.h:1134
N_STR * body
the part's raw body bytes (binary-safe), or NULL
Definition n_network.h:898
int fin
FIN bit (1 = final fragment).
Definition n_network.h:1082
N_STR * event
event type (or NULL for default)
Definition n_network.h:1122
int connected
1 if handshake completed
Definition n_network.h:1074
N_STR * data
event data
Definition n_network.h:1123
N_STR * body
request body (or NULL)
Definition n_network.h:1161
char * key
parameter name
Definition n_network.h:1019
uint64_t payload_len
payload length in bytes
Definition n_network.h:1087
char * password
NULL if no auth.
Definition n_network.h:931
char * query
raw query string without leading '?
Definition n_network.h:1029
int port
proxy port
Definition n_network.h:929
N_URL_PARAM params[64]
parsed key=value pairs
Definition n_network.h:1030
char * body
Pointer to the body data.
Definition n_network.h:641
LIST * headers
list of char* "Name: Value" strings
Definition n_network.h:1160
char * value
parameter value
Definition n_network.h:1020
char * host
remote hostname
Definition n_network.h:1075
SOCKET sock
a normal socket
Definition n_network.h:356
char path[2048]
request path
Definition n_network.h:1158
char * scheme
"http", "https", or "socks5"
Definition n_network.h:927
char * port
port of socket
Definition n_network.h:354
int nb_params
number of parsed parameters
Definition n_network.h:1031
char * name
Content-Disposition form-field name, or "".
Definition n_network.h:895
SSL_CTX * ctx
SSL context holder.
Definition n_network.h:444
const unsigned char * payload
pointer into the input buffer (still masked)
Definition n_network.h:1088
int retry
retry interval in ms (0 if not set)
Definition n_network.h:1125
char content_type[256]
Store content type.
Definition n_network.h:637
N_STR * id
last event ID (or NULL)
Definition n_network.h:1124
char * path
resource path
Definition n_network.h:1076
int rsv
the three reserved bits, rsv1<<2 | rsv2<<1 | rsv3
Definition n_network.h:1083
N_STR * payload
message payload
Definition n_network.h:1067
char method[16]
HTTP method.
Definition n_network.h:1157
int opcode
frame opcode
Definition n_network.h:1066
unsigned char mask[4]
masking key when masked, else zeroed
Definition n_network.h:1086
int masked
1 if masked
Definition n_network.h:1068
char * disposition_extra
extra Content-Disposition parameters, appended verbatim after name (and filename) as written,...
Definition n_network.h:899
char * username
NULL if no auth.
Definition n_network.h:930
int status_code
HTTP status code.
Definition n_network.h:1166
SSL * ssl
SSL handle.
Definition n_network.h:446
unsigned long int is_blocking
flag to quickly check socket mode
Definition n_network.h:361
int crypto_algo
if encryption is on, which one (flags NETW_ENCRYPT_*)
Definition n_network.h:407
char content_type[128]
Content-Type header value.
Definition n_network.h:1167
HASH_TABLE * pool
table of clients
Definition n_network.h:627
void * user_data
user data for callback
Definition n_network.h:1135
#define NETW_SOCKET_ERROR
code for a socket error
Definition n_network.h:70
int netw_send_string_to_all(NETWORK *netw, N_STR *name, N_STR *chan, N_STR *txt, int color)
Add a string to the network, aiming all server-side users.
Definition n_network.c:5933
ssize_t send_ssl_data_once(void *netw, char *buf, uint32_t n)
single-attempt TLS send for non-blocking sockets (reactor use).
Definition n_network.c:5279
#define N_URL_MAX_PARAMS
maximum number of parsed query parameters
Definition n_network.h:1015
#define NETW_IO_WANT_READ
single-attempt I/O (send_data_once / recv_data_once): the operation cannot progress until the socket ...
Definition n_network.h:77
#define NETW_COMPRESS_THRESHOLD
Opportunistic compression policy.
Definition n_network.h:342
N_STR * netw_get_msg(NETWORK *netw)
Get a message from aimed NETWORK.
Definition n_network.c:4301
N_STR * n_http_build_multipart(LIST *parts, const char *boundary)
build a multipart/form-data body from a LIST of N_HTTP_MULTIPART_PART* using boundary (without "--");...
Definition n_network.c:6727
int netw_add_msg(NETWORK *netw, N_STR *msg)
Add a message to send in aimed NETWORK.
Definition n_network.c:4204
int netw_ssl_get_verify_result(NETWORK *netw, const char *expected_host, char *errbuf, size_t errsz)
evaluate the peer certificate of a completed TLS client connection
Definition n_network.c:2179
const char * n_netw_get_connect_error(int index)
Get pre-connection error message by index.
Definition n_network.c:126
ssize_t send_ssl_data(void *netw, char *buf, uint32_t n)
send data onto the socket
Definition n_network.c:5063
char * netw_extract_http_request_type(const char *request)
function to extract the request method from an http request
Definition n_network.c:6022
int netw_set_crypto_pem_ctx(NETWORK *netw, const N_STR *key_pem, const N_STR *cert_pem)
install an in-memory cert/key on a single accepted connection's SSL
Definition n_network.c:1753
#define N_WS_MAX_PAYLOAD
Largest WebSocket payload n_ws_recv() will accept from a peer, in bytes.
Definition n_network.h:1062
int netw_get_queue_status(NETWORK *netw, size_t *nb_to_send, size_t *nb_to_read)
retrieve network send queue status
Definition n_network.c:5639
int netw_bind_udp(NETWORK **netw, char *addr, char *port, int ip_version)
Create a UDP bound socket for receiving datagrams.
Definition n_network.c:3626
int netw_set_crypto_pem(NETWORK *netw, const char *key_pem, const char *cert_pem)
activate SSL encryption using PEM-formatted key and certificate strings loaded from memory
Definition n_network.c:1502
ssize_t recv_ssl_data_once(void *netw, char *buf, uint32_t n)
single-attempt TLS recv for non-blocking sockets (reactor use).
Definition n_network.c:5367
#define NETW_THR_EXIT_ERROR
Internal DONE value indicating a send/recv thread observed NETW_ERROR without an in-flight local shut...
Definition n_network.h:98
#define NETW_COMPRESS_MIN_RATIO
Definition n_network.h:343
int netw_init_wsa(int mode, int v1, int v2)
Do not directly use, internal api.
Definition n_network.c:841
int netw_ssl_set_verify(NETWORK *netw, int enable)
enable or disable SSL peer certificate verification
Definition n_network.c:2053
ssize_t send_php(SOCKET s, int _code, char *buf, int n)
send data onto the socket
Definition n_network.c:5452
#define NETW_MSG_MAX_FRAME_SIZE
Maximum accepted size, in bytes, of a single received message frame payload.
Definition n_network.h:93
int netw_stop_thr_engine(NETWORK *netw)
Stop a NETWORK connection sending and receing thread.
Definition n_network.c:4784
void n_ws_close(N_WS_CONN *conn)
Send close frame and close the connection.
Definition n_network.c:7239
char * netw_urlencode(const char *str, size_t len)
function to perform URL encoding
Definition n_network.c:5989
N_STR * n_url_encode(const char *str)
percent-encode a string for use in URLs (delegates to n_str_url_encode())
Definition n_network.c:8208
int n_http_status_class(int status_code)
leading digit of an HTTP status code (1..5), or 0 when outside 100..599
Definition n_network.c:6343
#define SSL_get1_peer_certificate(ssl)
Definition n_network.h:247
void * netw_send_func(void *NET)
Thread send function.
Definition n_network.c:4424
NETWORK * netw_accept_nonblock_from(NETWORK *from, int blocking)
make a normal blocking 'accept' .
Definition n_network.c:4188
int netw_get_url_from_http_request(const char *request, char *url, size_t size)
Helper function to extract the URL from the HTTP request line.
Definition n_network.c:6141
N_PROXY_CFG * n_proxy_cfg_parse(const char *url)
Parse a proxy URL string into an N_PROXY_CFG struct.
Definition n_network.c:8288
#define N_ENUM_netw_code_type(_)
Network codes definition.
Definition n_network.h:296
int netw_set_crypto(NETWORK *netw, char *key, char *certificate)
activate SSL encryption on selected network, using key and certificate
Definition n_network.c:1399
int netw_ssl_server_handshake_ex(NETWORK *netw, netw_sni_pick_cb pick, void *user_data, const char *const *alpn, size_t alpn_count)
upgrade an already-accepted plaintext connection to a TLS server, selecting the certificate per clien...
Definition n_network.c:1947
int netw_set_crypto_chain_pem(NETWORK *netw, const char *key_pem, const char *cert_pem, const char *ca_pem)
activate SSL encryption using PEM strings for key, certificate, and CA
Definition n_network.c:1639
int netw_adopt_client_fd(NETWORK **netw, SOCKET fd, const char *host, const char *port)
Adopt an already-connected socket fd into a fresh client NETWORK.
Definition n_network.c:8731
int netw_ssl_set_ca(NETWORK *netw, const char *ca_file, const char *ca_path)
set custom CA verify location for SSL context
Definition n_network.c:2028
void n_sse_stop(N_SSE_CONN *conn)
Signal the SSE connection to stop reading.
Definition n_network.c:7305
ssize_t recv_data_once(void *netw, char *buf, uint32_t n)
single-attempt recv for non-blocking sockets (reactor use).
Definition n_network.c:5008
#define NETWORK_IPV6
Flag to force IPV6.
Definition n_network.h:52
void n_http_multipart_free(LIST **parts)
free a LIST returned by n_http_parse_multipart and NULL the pointer
Definition n_network.c:6526
void n_ws_unmask(unsigned char *dst, const unsigned char *src, size_t len, const unsigned char mask[4])
XOR-unmask len bytes of src into dst using a 4-byte WebSocket mask key.
Definition n_network.c:7640
int netw_connect_ex_to(NETWORK **netw, char *host, char *port, size_t send_list_limit, size_t recv_list_limit, int ip_version, char *ssl_key_file, char *ssl_cert_file, int connect_timeout_ms)
Use this to connect a NETWORK to any listening one.
Definition n_network.c:2448
#define NETWORK_UDP
Flag for UDP transport.
Definition n_network.h:56
N_STR * n_url_canonicalize_string(const char *url)
parse and canonicalize a URL string in one call
Definition n_network.c:8086
N_STR * n_url_resolve(const char *base, const char *ref)
resolve a (possibly relative) URL reference against an absolute base
Definition n_network.c:8124
ssize_t send_data_once(void *netw, char *buf, uint32_t n)
single-attempt send for non-blocking sockets (reactor use).
Definition n_network.c:4952
int netw_ssl_server_handshake_once(NETWORK *netw, SSL_CTX *ctx)
non-blocking, resumable TLS server handshake against a shared ctx
Definition n_network.c:3093
int netw_get_http_date(char *buffer, size_t buffer_size)
helper function to generate the current date in HTTP format
Definition n_network.c:6756
int netw_connect_nonblock(NETWORK **netw, char *host, char *port, int ip_version)
begin a non-blocking TCP connect without waiting for it to complete
Definition n_network.c:2651
NETWORK_POOL * netw_new_pool(size_t nb_min_element)
return a new network pool of nb_min_element
Definition n_network.c:5658
int netw_set_user_id(NETWORK *netw, int id)
associate an id and a network
Definition n_network.c:5833
void n_mock_server_free(N_MOCK_SERVER **server)
Free a mock server and close the listening socket.
Definition n_network.c:7912
ssize_t recv_data(void *netw, char *buf, uint32_t n)
recv data from the socket
Definition n_network.c:4885
int netw_ssl_handshake_once(NETWORK *netw, const char *sni_hostname)
non-blocking, resumable TLS client handshake
Definition n_network.c:3050
int netw_init_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1335
void n_netw_clear_errors(NETWORK *netw)
Clear captured errors on a NETWORK handle.
Definition n_network.c:116
ssize_t recv_ssl_data(void *netw, char *buf, uint32_t n)
recv data from the socket
Definition n_network.c:5161
int netw_make_listening(NETWORK **netw, char *addr, char *port, int nbpending, int ip_version)
Make a NETWORK be a Listening network.
Definition n_network.c:3493
int netw_ssl_do_handshake(NETWORK *netw, const char *sni_hostname)
Complete the SSL handshake on an already-connected NETWORK.
Definition n_network.c:2948
#define HEAD_SIZE
Size of a HEAD message.
Definition n_network.h:66
ssize_t send_udp_data(void *netw, char *buf, uint32_t n)
send data via UDP on a connected socket
Definition n_network.c:3852
#define netw_atomic_write_state(netw, val)
Lock-free atomic write of the network state field.
Definition n_network.h:607
int netw_set_compression_mode(NETWORK *netw, int mode)
Pick send-side payload compression algorithm.
Definition n_network.c:893
int netw_start_thr_engine(NETWORK *netw)
Start the NETWORK netw Threaded Engine.
Definition n_network.c:4375
int netw_destroy_pool(NETWORK_POOL **netw_pool)
free a NETWORK_POOL *pool
Definition n_network.c:5677
void n_url_free(N_URL **u)
free a N_URL and all its members
Definition n_network.c:8233
#define NETWORK_IPV4
Flag to force IPV4.
Definition n_network.h:50
int netw_build_http_response(N_STR **http_response, int status_code, const char *server_name, const char *content_type, char *additional_headers, N_STR *body)
function to dynamically generate an HTTP response
Definition n_network.c:6788
void * netw_recv_func(void *NET)
To Thread Receiving function.
Definition n_network.c:4607
void n_sse_conn_free(N_SSE_CONN **conn)
Free an SSE connection structure.
Definition n_network.c:7314
int n_proxy_connect_tunnel_ssl(const N_PROXY_CFG *proxy, const char *target_host, int target_port)
Open a TCP connection through an HTTPS proxy using CONNECT tunneling.
Definition n_network.c:8467
N_STR * n_url_decode(const char *str)
decode a percent-encoded string (returns N_STR)
Definition n_network.c:8212
int n_netw_get_error_count(const NETWORK *netw)
Get number of captured errors on a NETWORK handle.
Definition n_network.c:106
int n_ws_frame_parse(const unsigned char *buf, size_t len, N_WS_FRAME *frame, size_t *consumed)
Parse one WebSocket frame from a byte buffer (stateless, no allocation).
Definition n_network.c:7593
N_WS_CONN * n_ws_connect(const char *host, const char *port, const char *path, int use_ssl)
Connect to a WebSocket server (ws:// or wss://).
Definition n_network.c:6886
int n_http_status_is_server_error(int status_code)
1 when status_code is a server error (5xx), else 0
Definition n_network.c:6365
#define SOCKET_SIZE_FORMAT
socket associated printf style
Definition n_network.h:113
__netw_code_type size_t htonst(size_t value)
host to network size_t
Definition n_network.c:145
int netw_unload_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1368
const char * netw_ssl_get_sni(NETWORK *netw)
get the SNI server_name the peer requested in its ClientHello
Definition n_network.c:1739
int n_http_status_is_client_error(int status_code)
1 when status_code is a client error (4xx), else 0
Definition n_network.c:6361
#define HEAD_CODE
Code of a HEAD message.
Definition n_network.h:68
int n_proxy_connect_socks5(const N_PROXY_CFG *proxy, const char *target_host, int target_port)
Open a TCP connection through a SOCKS5 proxy.
Definition n_network.c:8593
size_t ntohst(size_t value)
network to host size_t
Definition n_network.c:162
ssize_t netw_udp_sendto(NETWORK *netw, char *buf, uint32_t n, struct sockaddr *dest_addr, socklen_t dest_len)
send data via UDP to a specific destination address
Definition n_network.c:3929
#define NETWORK_CONSECUTIVE_SEND_WAIT
Flag to set consecutive send waiting timeout.
Definition n_network.h:62
#define netw_atomic_read_reactor_mode(netw)
Lock-free atomic read of the reactor_mode flag.
Definition n_network.h:616
size_t netw_pool_nbclients(NETWORK_POOL *netw_pool)
return the number of networks in netw_pool
Definition n_network.c:5816
int(* netw_sni_pick_cb)(const char *sni, N_STR **cert_pem, N_STR **key_pem, void *user_data)
SNI cert-pick callback for netw_accept_ssl_with_sni_cb: given the client SNI host (may be NULL),...
Definition n_network.h:677
int n_proxy_connect_tunnel(const N_PROXY_CFG *proxy, const char *target_host, int target_port)
Open a TCP connection through an HTTP proxy using CONNECT tunneling.
Definition n_network.c:8394
NETWORK * netw_accept_from_ex(NETWORK *from, size_t send_list_limit, size_t recv_list_limit, int blocking, int *retval)
make a normal 'accept' .
Definition n_network.c:3992
void n_netw_clear_connect_errors(void)
Clear pre-connection errors on this thread.
Definition n_network.c:132
void n_mock_server_stop(N_MOCK_SERVER *server)
Signal the mock server to stop accepting connections.
Definition n_network.c:7903
int netw_connect_ex(NETWORK **netw, char *host, char *port, size_t send_list_limit, size_t recv_list_limit, int ip_version, char *ssl_key_file, char *ssl_cert_file)
Use this to connect a NETWORK to any listening one.
Definition n_network.c:2777
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
Definition n_network.h:48
int SOCKET
default socket declaration
Definition n_network.h:111
#define NETW_SOCKET_DISCONNECTED
Code for a disconnected recv.
Definition n_network.h:72
int netw_pool_broadcast(NETWORK_POOL *netw_pool, const NETWORK *from, N_STR *net_msg)
add net_msg to all network in netork pool
Definition n_network.c:5792
int n_http_decompress_body(const N_STR *body, const char *encoding, N_STR **out)
decompress an HTTP body per its Content-Encoding ("gzip"/"deflate"; identity/NULL/unknown copies thro...
Definition n_network.c:6426
int netw_ssl_connect_client_to(NETWORK **netw, char *host, char *port, int ip_version, int connect_timeout_ms)
Connect as an SSL client without providing a client certificate.
Definition n_network.c:2839
const char * n_netw_get_error(const NETWORK *netw, int index)
Get captured error message by index (0 = oldest).
Definition n_network.c:110
void n_sse_event_clean(N_SSE_EVENT *event)
Free the contents of an SSE event (does not free the struct itself).
Definition n_network.c:7293
#define NETWORK_WAIT_CLOSE_TIMEOUT
Flag to set network closing wait timeout.
Definition n_network.h:64
int netw_setsockopt(NETWORK *netw, int optname, int value)
Modify common socket options on the given netw.
Definition n_network.c:969
int n_http_multipart_boundary(const char *content_type, char *out, size_t outsz)
copy the boundary token from a Content-Type value into out (without the leading "--").
Definition n_network.c:6486
int netw_ssl_set_alpn(NETWORK *netw, const char *const *protos, size_t count)
offer an ALPN protocol list on a TLS client connection, before its handshake
Definition n_network.c:1834
int netw_set(NETWORK *netw, int flag)
Restart or reset the specified network ability.
Definition n_network.c:3150
ssize_t send_data(void *netw, char *buf, uint32_t n)
send data onto the socket
Definition n_network.c:4824
ssize_t recv_udp_data(void *netw, char *buf, uint32_t n)
recv data via UDP from a connected socket
Definition n_network.c:3893
NETWORK * netw_accept_ssl_with_sni_cb(NETWORK *listen, netw_sni_pick_cb pick, void *user_data)
accept a TLS connection, selecting the server certificate per client SNI
Definition n_network.c:2007
int netw_get_state(NETWORK *netw, uint32_t *state, int *thr_engine_status)
Get the state of a network.
Definition n_network.c:3128
N_SSE_CONN * n_sse_connect(const char *host, const char *port, const char *path, int use_ssl, const char *user_agent, void(*on_event)(N_SSE_EVENT *, N_SSE_CONN *, void *), void *user_data)
Connect to an SSE endpoint and start reading events.
Definition n_network.c:7399
N_URL * n_url_parse(const char *url)
parse a URL string into components
Definition n_network.c:7946
int n_http_status_is_informational(int status_code)
1 when status_code is informational (1xx), else 0
Definition n_network.c:6349
void netw_pool_netw_close(void *netw_ptr)
close a network from a network pool
Definition n_network.c:5696
int netw_connect_check(NETWORK *netw)
advance a non-blocking connect started by netw_connect_nonblock()
Definition n_network.c:2713
int n_netw_get_connect_error_count(void)
Get number of pre-connection errors captured on this thread.
Definition n_network.c:122
size_t netw_calculate_urlencoded_size(const char *str, size_t len)
function to calculate the required size for the URL-encoded string
Definition n_network.c:5966
#define netw_atomic_read_reactor_handle(netw)
Same contract for the back-pointer to the reactor.
Definition n_network.h:621
int deplete_send_buffer(int fd, int timeout)
wait until the socket is empty or timeout, checking each 100 msec.
Definition n_network.c:3227
NETWORK * netw_accept_from(NETWORK *from)
make a normal blocking 'accept' .
Definition n_network.c:4178
static void SSL_set_hostflags(SSL *ssl, unsigned int flags)
1.0.2 stand-in for SSL_set_hostflags(): set the X509_CHECK_FLAG_* host-check flags of ssl
Definition n_network.h:279
N_STR * n_url_canonicalize(const N_URL *u)
produce a canonical URL string for deduplication
Definition n_network.c:8057
#define NETW_IO_WANT_WRITE
single-attempt I/O: the operation cannot progress until the socket is WRITABLE.
Definition n_network.h:81
#define NETW_MAX_RETRIES
Send or recv max number of retries.
Definition n_network.h:83
void n_proxy_cfg_free(N_PROXY_CFG **cfg)
Free an N_PROXY_CFG created by n_proxy_cfg_parse().
Definition n_network.c:8383
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:3258
int n_http_status_is_success(int status_code)
1 when status_code is success (2xx), else 0
Definition n_network.c:6353
N_STR * n_url_build(const N_URL *u)
build a URL string from parsed components
Definition n_network.c:7985
void n_mock_server_run(N_MOCK_SERVER *server)
Run the mock server accept loop.
Definition n_network.c:7818
int netw_send_quit(NETWORK *netw)
Add a formatted NETMSG_QUIT message to the specified network.
Definition n_network.c:5949
#define NETW_THR_EXIT_OK
Internal DONE value indicating a send/recv thread reached end-of-life cleanly (peer QUIT or local NET...
Definition n_network.h:96
int netw_ssl_set_ciphers(NETWORK *netw, const char *cipher_list, const char *ciphersuites)
set the cipher list (TLS 1.2 and below) and/or the ciphersuites (TLS 1.3)
Definition n_network.c:2135
int netw_ssl_connect_client(NETWORK **netw, char *host, char *port, int ip_version)
Connect as an SSL client without providing a client certificate.
Definition n_network.c:2911
int netw_ssl_start_client(NETWORK *netw)
Set up a client SSL_CTX on an already-connected NETWORK.
Definition n_network.c:2868
const char * netw_get_http_status_message(int status_code)
helper function to convert status code to a human-readable message
Definition n_network.c:6325
int netw_set_crypto_chain(NETWORK *netw, char *key, char *certificate, char *ca_file)
activate SSL encryption using key/certificate files and a CA file for chain verification
Definition n_network.c:1604
int n_ws_recv(N_WS_CONN *conn, N_WS_MESSAGE *msg_out)
Receive one WebSocket frame.
Definition n_network.c:7157
int netw_set_blocking(NETWORK *netw, unsigned long int is_blocking)
Modify blocking socket mode.
Definition n_network.c:911
N_STR * netw_wait_msg(NETWORK *netw, unsigned int refresh, size_t timeout)
Wait a message from aimed NETWORK.
Definition n_network.c:4323
int n_http_status_is_redirect(int status_code)
1 when status_code is a redirect (3xx), else 0
Definition n_network.c:6357
LIST * n_http_parse_multipart(const N_STR *body, const char *boundary)
parse a multipart body (boundary given without "--") into a LIST of N_HTTP_MULTIPART_PART*; free with...
Definition n_network.c:6586
int netw_send_string_to(NETWORK *netw, int id_to, N_STR *name, N_STR *chan, N_STR *txt, int color)
Add a string to the network, aiming a specific user.
Definition n_network.c:5913
int netw_send_ping(NETWORK *netw, int type, int id_from, int id_to, int time)
Add a ping reply to the network.
Definition n_network.c:5848
int netw_ssl_connect(NETWORK **netw, char *host, char *port, int ip_version, char *ssl_key_file, char *ssl_cert_file)
Use this to connect a NETWORK to any listening one, unrestricted send/recv lists.
Definition n_network.c:2819
NETWORK_HTTP_INFO netw_extract_http_info(char *request)
extract a lot of informations, mostly as pointers, and populate a NETWORK_HTTP_INFO structure
Definition n_network.c:6055
size_t n_ws_frame_build(int fin, int opcode, int do_mask, const unsigned char *payload, size_t len, const unsigned char mask_key[4], unsigned char *out, size_t out_cap)
build a WebSocket frame into out (optionally masking the payload).
Definition n_network.c:7648
int netw_ssl_server_handshake(NETWORK *netw, netw_sni_pick_cb pick, void *user_data)
upgrade an already-accepted plaintext connection to a TLS server, selecting the certificate per clien...
Definition n_network.c:1923
#define netw_atomic_read_state(netw)
Lock-free atomic read of the network state field.
Definition n_network.h:602
int netw_connect(NETWORK **netw, char *host, char *port, int ip_version)
Use this to connect a NETWORK to any listening one, unrestricted send/recv lists.
Definition n_network.c:2789
N_MOCK_SERVER * n_mock_server_start(int port, void(*on_request)(N_HTTP_REQUEST *, N_HTTP_RESPONSE *, void *), void *user_data)
Start a mock HTTP server: set up listener and return immediately.
Definition n_network.c:7784
__netw_code_type
Network codes declaration.
Definition n_network.h:346
#define NETWORK_DEPLETE_SOCKET_TIMEOUT
Flag to set send buffer depletion timeout.
Definition n_network.h:58
int netw_connect_to(NETWORK **netw, char *host, char *port, int ip_version, int connect_timeout_ms)
Connect a NETWORK with a bounded connection-establishment time.
Definition n_network.c:2803
int netw_send_ident(NETWORK *netw, int type, int id, N_STR *name, N_STR *passwd)
Add a formatted NETWMSG_IDENT message to the specified network.
Definition n_network.c:5867
char * netw_urldecode(const char *str)
Function to decode URL-encoded data.
Definition n_network.c:6178
int netw_ssl_set_client_cert(NETWORK *netw, const char *cert_file, const char *key_file)
load a client certificate and private key for mTLS
Definition n_network.c:2246
void n_ws_conn_free(N_WS_CONN **conn)
Free a WebSocket connection structure.
Definition n_network.c:7269
#define NETWORK_DEPLETE_QUEUES_TIMEOUT
Flag to set network queues depletion timeout.
Definition n_network.h:60
int netw_pool_add(NETWORK_POOL *netw_pool, NETWORK *netw)
add a NETWORK *netw to a NETWORK_POOL *pool
Definition n_network.c:5709
int netw_connect_udp(NETWORK **netw, char *host, char *port, int ip_version)
Connect a UDP socket to a remote host.
Definition n_network.c:3747
int netw_info_destroy(NETWORK_HTTP_INFO http_request)
destroy a NETWORK_HTTP_INFO loaded informations
Definition n_network.c:6128
int netw_ssl_get_alpn(NETWORK *netw, char *out, size_t out_len)
get the ALPN protocol the completed handshake selected
Definition n_network.c:1864
int netw_ssl_set_versions(NETWORK *netw, int min_version, int max_version)
set the minimum (and optionally maximum) TLS protocol version on a connection's SSL context
Definition n_network.c:2085
HASH_TABLE * netw_parse_post_data(const char *post_data)
Function to parse POST data.
Definition n_network.c:6215
const char * netw_guess_http_content_type(const char *url)
function to guess the content type based on URL extension
Definition n_network.c:6261
int n_ws_send(N_WS_CONN *conn, const char *payload, size_t len, int opcode)
Send a WebSocket frame (client always masks).
Definition n_network.c:7090
ssize_t recv_php(SOCKET s, int *_code, char **buf)
recv data from the socket
Definition n_network.c:5534
static int SSL_set1_host(SSL *ssl, const char *host)
1.0.2 stand-in for SSL_set1_host(): set the expected peer name of ssl, returning 1 on success and 0 o...
Definition n_network.h:286
int netw_send_position(NETWORK *netw, int id, double X, double Y, double vx, double vy, double acc_x, double acc_y, int time_stamp)
Add a formatted NETWMSG_IDENT message to the specified network.
Definition n_network.c:5891
int netw_pool_remove(NETWORK_POOL *netw_pool, NETWORK *netw)
remove a NETWORK *netw to a NETWORK_POOL *pool
Definition n_network.c:5755
void netw_set_connect_abort_cb(int(*cb)(void *ctx), void *ctx)
Register a process-wide callback polled while a connect is in progress.
Definition n_network.c:2430
int netw_add_msg_ex(NETWORK *netw, char *str, unsigned int length)
Add a message to send in aimed NETWORK.
Definition n_network.c:4270
#define NETWORK_TCP
Flag for TCP transport (default).
Definition n_network.h:54
ssize_t netw_udp_recvfrom(NETWORK *netw, char *buf, uint32_t n, struct sockaddr *src_addr, socklen_t *src_len)
recv data via UDP and capture the source address
Definition n_network.c:3961
#define N_WS_OP_CLOSE
Definition n_network.h:1054
@ NETW_COMPRESS_NONE
no automatic compression on send, still decompresses inbound
Definition n_network.h:328
@ NETW_COMPRESS_ZLIB
compress on send with zlib, decompress either on recv
Definition n_network.h:330
@ NETW_COMPRESS_LZ4
compress on send with LZ4, decompress either on recv
Definition n_network.h:332
@ NETW_COMPRESSED_LZ4
Definition n_network.h:346
@ NETW_DESTROY_RECVBUF
Definition n_network.h:346
@ NETW_ENCRYPT_OPENSSL
Definition n_network.h:346
@ NETW_THR_ENGINE_STARTED
Definition n_network.h:346
@ NETW_COMPRESSED_ZLIB
Definition n_network.h:346
@ NETW_SERVER
Definition n_network.h:346
@ NETW_EMPTY_SENDBUF
Definition n_network.h:346
@ NETW_DESTROY_SENDBUF
Definition n_network.h:346
@ NETW_THR_ENGINE_STOPPED
Definition n_network.h:346
@ NETW_RUN
Definition n_network.h:346
@ NETW_CLIENT
Definition n_network.h:346
@ NETW_EMPTY_RECVBUF
Definition n_network.h:346
@ NETW_EXITED
Definition n_network.h:346
@ NETW_ERROR
Definition n_network.h:346
@ NETW_EXIT_ASKED
Definition n_network.h:346
@ NETW_ENCRYPT_NONE
Definition n_network.h:346
a single part of a parsed multipart/form-data body
Definition n_network.h:894
parsed HTTP request for mock server callback
Definition n_network.h:1156
HTTP response to send from mock server callback.
Definition n_network.h:1165
mock HTTP server handle
Definition n_network.h:1172
Parsed proxy URL components.
Definition n_network.h:926
SSE connection handle.
Definition n_network.h:1131
SSE event received from server.
Definition n_network.h:1121
parsed URL components
Definition n_network.h:1024
WebSocket connection.
Definition n_network.h:1072
A single parsed WebSocket frame (RFC 6455).
Definition n_network.h:1081
WebSocket message.
Definition n_network.h:1065
Structure of a NETWORK.
Definition n_network.h:375
structure for splitting HTTP requests
Definition n_network.h:635
structure of a network pool
Definition n_network.h:625
N_STR * unzip_nstr(N_STR *src)
return an uncompressed version of src
Definition n_zlib.c:217
N_STR * zip_nstr(N_STR *src)
return a compressed version of src
Definition n_zlib.c:166
Base64 encoding and decoding functions using N_STR.
Hash functions and table.
Generic log system.
LZ4 block-compression handler.
static char * netstrerror(int code)
BSD style errno string NO WORKING ON REDHAT.
Definition n_network.c:700
#define NETW_CALL_RETRY(__retvar, __expression, __max_tries)
network-aware retry macro: retries on EINTR and EAGAIN/EWOULDBLOCK
Definition n_network.c:672
netw_sni_pick_cb pick
Definition n_network.c:1768
static size_t _netw_alpn_encode(const char *const *protos, size_t count, unsigned char *out, size_t out_cap)
Definition n_network.c:1807
static void _n_url_normalize_path(const char *path, char *out, size_t outsz)
normalize a path by removing "." and ".." segments (RFC 3986 style)
Definition n_network.c:8005
static int _ssl_use_pem(SSL *ssl, const char *key_pem, const char *cert_pem)
Definition n_network.c:1708
static char * mp_strndup(const char *s, size_t len)
Definition n_network.c:6475
static __thread int s_connect_err_next
Definition n_network.c:79
static int _netw_sni_servername_cb(SSL *ssl, int *al, void *arg)
Definition n_network.c:1884
#define N_WS_FRAME_MAX_PAYLOAD
upper bound on a single parsed WebSocket frame payload, to bound a proxy's reassembly buffer against ...
Definition n_network.c:7582
static ssize_t _ws_read(N_WS_CONN *conn, void *buf, size_t len)
read exactly len bytes from a WebSocket connection
Definition n_network.c:6855
static void mp_put(char *buf, size_t *pos, const void *src, size_t n)
Definition n_network.c:6683
__attribute__((unused))
Definition n_network.c:1288
static void netw_init_locks(void)
Definition n_network.c:1305
void netw_ssl_print_errors(SOCKET socket)
print the queued OpenSSL errors for a given socket
Definition n_network.c:1231
static ssize_t _sse_write(NETWORK *netw, const void *buf, size_t len)
write bytes to an SSE connection (SSL or plain)
Definition n_network.c:7375
#define _Thread_local
thread-local pre-connection error buffer (DNS, socket creation)
Definition n_network.c:75
static void _n_parse_query_params(N_URL *u, const char *qs)
Definition n_network.c:7921
static int(* _netw_connect_abort_cb)(void *)
Optional connect-abort callback, or NULL.
Definition n_network.c:2283
static void _n_mock_parse_request(const char *buf, N_HTTP_REQUEST *req)
Parse a raw HTTP request buffer into an N_HTTP_REQUEST.
Definition n_network.c:7694
static ssize_t _ws_write(N_WS_CONN *conn, const void *buf, size_t len)
write bytes to a WebSocket connection (SSL or plain)
Definition n_network.c:6839
static int _netw_ssl_handshake_step(NETWORK *netw)
advance an in-flight TLS handshake by a single non-blocking step
Definition n_network.c:3003
NETWORK * netw_new(size_t send_list_limit, size_t recv_list_limit)
Return an empty allocated network ready to be netw_closed.
Definition n_network.c:714
static ssize_t _sse_read_byte(NETWORK *netw, char *ch, volatile int *stop_flag)
read one byte from an SSE connection (SSL or plain).
Definition n_network.c:7332
static int _netw_start_nonblock_connect(NETWORK *netw)
create a non-blocking socket for netw->link.connect_rp and start connect()
Definition n_network.c:2603
static SOCKET _proxy_tcp_connect(const char *host, int port)
Helper: connect a plain TCP socket to host:port.
Definition n_network.c:8254
long long g_netw_bytes_sent
Add a message to send in aimed NETWORK.
Definition n_network.c:4201
static void * _netw_connect_abort_ctx
Opaque context handed to _netw_connect_abort_cb on each poll.
Definition n_network.c:2285
static void mp_emit(char *buf, size_t *pos, LIST *parts, const char *dash)
Definition n_network.c:6696
static int n_http_inflate(const unsigned char *src, size_t len, int window_bits, N_STR **out)
Definition n_network.c:6372
static int OPENSSL_IS_INITIALIZED
Definition n_network.c:1329
static __thread int s_connect_err_count
Definition n_network.c:78
#define neterrno
get last socket error code, linux version
Definition n_network.c:683
static pthread_mutex_t * netw_ssl_lockarray
Definition n_network.c:1286
static void mp_part_free(void *ptr)
Definition n_network.c:6513
static void _netw_capture_connect_error(const char *fmt,...)
capture a pre-connection error (thread-local)
Definition n_network.c:95
#define NETW_CONNECT_ABORT_POLL_MS
Poll interval (milliseconds) at which an in-progress connect wakes to check the registered connect-ab...
Definition n_network.c:2280
static long mp_find(const unsigned char *hay, size_t hlen, const char *needle, size_t nlen)
Definition n_network.c:6463
char * get_in_addr(struct sockaddr *sa)
get sockaddr, IPv4 or IPv6
Definition n_network.c:828
static int _netw_timed_connect(NETWORK *netw, SOCKET sock, struct addrinfo *rp, int connect_timeout_ms)
Connect a socket, optionally bounding the attempt by a timeout.
Definition n_network.c:2302
static const char * mp_header_value(const char *block, size_t blen, const char *name, size_t *vlen)
Definition n_network.c:6533
void * user_data
Definition n_network.c:1769
static void _n_mock_request_clean(N_HTTP_REQUEST *req)
Free the contents of an N_HTTP_REQUEST (does not free the struct itself).
Definition n_network.c:7771
const char *const * alpn
Definition n_network.c:1770
static int _n_url_has_scheme(const char *s)
test whether a reference string begins with its own URI scheme
Definition n_network.c:8100
long long g_netw_bytes_recv
Definition n_network.c:4202
static char * mp_disp_param(const char *disp, size_t dlen, const char *param)
Definition n_network.c:6561
char * netw_get_openssl_error_string()
get the OpenSSL error string
Definition n_network.c:1200
static __thread char s_connect_errors[8][512]
Definition n_network.c:77
static int _netw_alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)
Definition n_network.c:1779
static void _netw_capture_error(NETWORK *netw, const char *fmt,...)
capture an error into a NETWORK handle's ring buffer
Definition n_network.c:83
static void mp_puts(char *buf, size_t *pos, const char *s)
Definition n_network.c:6690
static void netw_kill_locks(void)
Definition n_network.c:1319
static int netw_is_ip_literal(const char *host)
is host a numeric IP literal rather than a DNS name?
Definition n_network.c:2925
static void _netw_crypto_setup_failed(NETWORK *netw)
Definition n_network.c:1384
size_t alpn_count
Definition n_network.c:1771
Network Engine.
Network messages , serialization tools.
void n_reactor_notify_send(NETWORK *netw)
Producer-side wake-up after a netw_add_msg.
Definition n_reactor.c:1279
void n_reactor_close_netw_sync(NETWORK *netw)
Synchronously close a reactor-registered NETWORK from the game thread.
Definition n_reactor.c:1283
Single-threaded epoll reactor for n_network connections.
Timing utilities.
ZLIB compression handler.