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
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, ...) {
83 if (!netw) return;
84 va_list ap;
85 va_start(ap, fmt);
86 vsnprintf(netw->netw_errors[netw->netw_err_next], 512, fmt, ap);
87 va_end(ap);
90}
91
93static void _netw_capture_connect_error(const char* fmt, ...) {
94 va_list ap;
95 va_start(ap, fmt);
96 vsnprintf(s_connect_errors[s_connect_err_next], 512, fmt, ap);
97 va_end(ap);
100}
101
102/* public error accessors */
103
105 return netw ? netw->netw_err_count : 0;
106}
107
108const char* n_netw_get_error(const NETWORK* netw, int index) {
109 if (!netw || index < 0 || index >= netw->netw_err_count) return NULL;
110 int oldest = (netw->netw_err_next - netw->netw_err_count + 8) % 8;
111 return netw->netw_errors[(oldest + index) % 8];
112}
113
115 if (!netw) return;
116 netw->netw_err_count = 0;
117 netw->netw_err_next = 0;
118}
119
123
124const char* n_netw_get_connect_error(int index) {
125 if (index < 0 || index >= s_connect_err_count) return NULL;
126 int oldest = (s_connect_err_next - s_connect_err_count + 8) % 8;
127 return s_connect_errors[(oldest + index) % 8];
128}
129
134
137
138
143size_t htonst(size_t value) {
144#if __BYTE_ORDER == __LITTLE_ENDIAN
145 if (sizeof(size_t) == 4) {
146 return (size_t)htonl((uint32_t)value);
147 } else if (sizeof(size_t) == 8) {
148 return ((size_t)htonl((uint32_t)(value >> 32)) |
149 ((size_t)htonl((uint32_t)value) << 32));
150 }
151#endif
152 return value; // No conversion needed for big-endian
153}
154
160size_t ntohst(size_t value) {
161#if __BYTE_ORDER == __LITTLE_ENDIAN
162 if (sizeof(size_t) == 4) {
163 return (size_t)ntohl((uint32_t)value);
164 } else if (sizeof(size_t) == 8) {
165 return ((size_t)ntohl((uint32_t)(value >> 32)) |
166 ((size_t)ntohl((uint32_t)value) << 32));
167 }
168#endif
169 return value; // No conversion needed for big-endian
170}
171
172#ifdef __windows__
173
179char* wchar_to_char(const wchar_t* pwchar) {
180 __n_assert(pwchar, return NULL);
181
182 // get the number of characters in the string.
183 int currentCharIndex = 0;
184 char currentChar = (char)pwchar[currentCharIndex];
185 char* filePathC = NULL;
186
187 while (currentChar != '\0') {
188 currentCharIndex++;
189 currentChar = (char)pwchar[currentCharIndex];
190 }
191
192 const int charCount = currentCharIndex + 1;
193
194 // allocate a new block of memory size char (1 byte) instead of wide char (2 bytes)
195 Malloc(filePathC, char, (size_t)charCount);
196 __n_assert(filePathC, return NULL);
197
198 for (int i = 0; i < charCount; i++) {
199 // convert to char (1 byte)
200 char character = (char)pwchar[i];
201
202 *filePathC = character;
203
204 filePathC += sizeof(char);
205 }
206 filePathC += '\0';
207
208 filePathC -= (sizeof(char) * (size_t)charCount);
209
210 return filePathC;
211}
212
214#define NETW_CALL_RETRY(__retvar, __expression, __max_tries) \
215 do { \
216 int __nb_retries = 0; \
217 do { \
218 __retvar = (__expression); \
219 __nb_retries++; \
220 } while (__retvar == -1 && (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEWOULDBLOCK) && __nb_retries < (__max_tries)); \
221 if (__retvar == -1 && __nb_retries >= (__max_tries)) __retvar = -2; \
222 } while (0)
223
225#define neterrno WSAGetLastError()
226
230static char* netstrerror(int code) {
231 wchar_t* ws = NULL;
232 FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
233 NULL, (DWORD)code,
234 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
235 (LPWSTR)&ws, 0, NULL);
236 char* netstr = wchar_to_char(ws);
237 LocalFree(ws);
238 return netstr;
239}
240
241#if __GNUC__ <= 6 && __GNUC_MINOR__ <= 3
242
243/*--------------------------------------------------------------------------------------
244 By Marco Ladino - mladinox.. jan/2016
245 MinGW 3.45 thru 4.5 versions, don't have the socket functions:
246 --> inet_ntop(..)
247 --> inet_pton(..)
248 But with this adapted code using the original functions from FreeBSD,
249 one can to use it in the C/C++ Applications, without problem..!
250 This implementation, include tests for IPV4 and IPV6 addresses,
251 and is full C/C++ compatible..
252 --------------------------------------------------------------------------------------*/
253/* OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp */
254/*-
255 * Copyright (c) 1998 Todd C. Miller <Todd.Miller at courtesan.com>
256 *
257 * Permission to use, copy, modify, and distribute this software for any
258 * purpose with or without fee is hereby granted, provided that the above
259 * copyright notice and this permission notice appear in all copies.
260 *
261 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
262 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
263 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
264 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
265 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
266 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
267 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
268 */
269
277size_t strlcpy(char* dst, const char* src, size_t siz) {
278 char* d = dst;
279 const char* s = src;
280 size_t n = siz;
281
282 /* Copy as many bytes as will fit */
283 if (n != 0) {
284 while (--n != 0) {
285 if ((*d++ = *s++) == '\0')
286 break;
287 }
288 }
289
290 /* Not enough room in dst, add NUL and traverse rest of src */
291 if (n == 0) {
292 if (siz != 0)
293 *d = '\0'; /* NUL-terminate dst */
294 while (*s++);
295 }
296
297 return (s - src - 1); /* count does not include NUL */
298}
299
300/*
301 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
302 * Copyright (c) 1996-1999 by Internet Software Consortium.
303 *
304 * Permission to use, copy, modify, and distribute this software for any
305 * purpose with or without fee is hereby granted, provided that the above
306 * copyright notice and this permission notice appear in all copies.
307 *
308 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
309 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
310 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
311 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
312 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
313 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
314 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
315 */
316
317/*
318 * WARNING: Don't even consider trying to compile this on a system where
319 * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX.
320 */
321
322static char* inet_ntop4(const unsigned char* src, char* dst, socklen_t size);
323static char* inet_ntop6(const unsigned char* src, char* dst, socklen_t size);
324
333char* inet_ntop(int af, const void* src, char* dst, socklen_t size) {
334 switch (af) {
335 case AF_INET:
336 return (inet_ntop4((const unsigned char*)src, dst, size));
337 case AF_INET6:
338 return (inet_ntop6((const unsigned char*)src, dst, size));
339 default:
340 return (NULL);
341 }
342 /* NOTREACHED */
343}
344
345/* const char *
346 * inet_ntop4(src, dst, size)
347 * format an IPv4 address
348 * return:
349 * `dst' (as a const)
350 * notes:
351 * (1) uses no statics
352 * (2) takes a u_char* not an in_addr as input
353 * author:
354 * Paul Vixie, 1996.
355 */
356static char* inet_ntop4(const unsigned char* src, char* dst, socklen_t size) {
357 static const char fmt[] = "%u.%u.%u.%u";
358 char tmp[sizeof "255.255.255.255"];
359 int l;
360
361 l = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]);
362 if (l <= 0 || (socklen_t)l >= size) {
363 return (NULL);
364 }
365 strlcpy(dst, tmp, size);
366 return (dst);
367}
368
369/* const char *
370 * inet_ntop6(src, dst, size)
371 * convert IPv6 binary address into presentation (printable) format
372 * author:
373 * Paul Vixie, 1996.
374 */
375static char* inet_ntop6(const unsigned char* src, char* dst, socklen_t size) {
376 /*
377 * Note that int32_t and int16_t need only be "at least" large enough
378 * to contain a value of the specified size. On some systems, like
379 * Crays, there is no such thing as an integer variable with 16 bits.
380 * Keep this in mind if you think this function should have been coded
381 * to use pointer overlays. All the world's not a VAX.
382 */
383 char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp;
384 struct
385 {
386 int base, len;
387 } best, cur;
388#define NS_IN6ADDRSZ 16
389#define NS_INT16SZ 2
390 u_int words[NS_IN6ADDRSZ / NS_INT16SZ];
391 int i;
392
393 /*
394 * Preprocess:
395 * Copy the input (bytewise) array into a wordwise array.
396 * Find the longest run of 0x00's in src[] for :: shorthanding.
397 */
398 memset(words, '\0', sizeof words);
399 for (i = 0; i < NS_IN6ADDRSZ; i++)
400 words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
401 best.base = -1;
402 best.len = 0;
403 cur.base = -1;
404 cur.len = 0;
405 for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
406 if (words[i] == 0) {
407 if (cur.base == -1)
408 cur.base = i, cur.len = 1;
409 else
410 cur.len++;
411 } else {
412 if (cur.base != -1) {
413 if (best.base == -1 || cur.len > best.len)
414 best = cur;
415 cur.base = -1;
416 }
417 }
418 }
419 if (cur.base != -1) {
420 if (best.base == -1 || cur.len > best.len)
421 best = cur;
422 }
423 if (best.base != -1 && best.len < 2)
424 best.base = -1;
425
426 /*
427 * Format the result.
428 */
429 tp = tmp;
430 for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
431 /* Are we inside the best run of 0x00's? */
432 if (best.base != -1 && i >= best.base &&
433 i < (best.base + best.len)) {
434 if (i == best.base)
435 *tp++ = ':';
436 continue;
437 }
438 /* Are we following an initial run of 0x00s or any real hex? */
439 if (i != 0)
440 *tp++ = ':';
441 /* Is this address an encapsulated IPv4? */
442 if (i == 6 && best.base == 0 && (best.len == 6 || (best.len == 7 && words[7] != 0x0001) || (best.len == 5 && words[5] == 0xffff))) {
443 if (!inet_ntop4(src + 12, tp, sizeof tmp - (tp - tmp)))
444 return (NULL);
445 tp += strlen(tp);
446 break;
447 }
448 tp += sprintf(tp, "%x", words[i]);
449 }
450 /* Was it a trailing run of 0x00's? */
451 if (best.base != -1 && (best.base + best.len) ==
452 (NS_IN6ADDRSZ / NS_INT16SZ))
453 *tp++ = ':';
454 *tp++ = '\0';
455
456 /*
457 * Check for overflow, copy, and we're done.
458 */
459 if ((socklen_t)(tp - tmp) > size) {
460 return (NULL);
461 }
462 strcpy(dst, tmp);
463 return (dst);
464}
465
466/*
467 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
468 * Copyright (c) 1996,1999 by Internet Software Consortium.
469 *
470 * Permission to use, copy, modify, and distribute this software for any
471 * purpose with or without fee is hereby granted, provided that the above
472 * copyright notice and this permission notice appear in all copies.
473 *
474 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
475 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
476 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
477 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
478 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
479 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
480 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
481 */
482
483/*
484 * WARNING: Don't even consider trying to compile this on a system where
485 * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX.
486 */
487
488static int inet_pton4(const char* src, u_char* dst);
489static int inet_pton6(const char* src, u_char* dst);
490
498int inet_pton(int af, const char* src, void* dst) {
499 switch (af) {
500 case AF_INET:
501 return (inet_pton4(src, (unsigned char*)dst));
502 case AF_INET6:
503 return (inet_pton6(src, (unsigned char*)dst));
504 default:
505 return (-1);
506 }
507 /* NOTREACHED */
508}
509
510/* int
511 * inet_pton4(src, dst)
512 * like inet_aton() but without all the hexadecimal and shorthand.
513 * return:
514 * 1 if `src' is a valid dotted quad, else 0.
515 * notice:
516 * does not touch `dst' unless it's returning 1.
517 * author:
518 * Paul Vixie, 1996.
519 */
520static int inet_pton4(const char* src, u_char* dst) {
521 static const char digits[] = "0123456789";
522 int saw_digit, octets, ch;
523#define NS_INADDRSZ 4
524 /* Zero-initialise: the octets are filled through the `tp` alias
525 * below, which cppcheck's data flow can't follow, so it cannot
526 * prove tmp is fully written before the trailing memcpy. The
527 * init is harmless and removes the false uninitvar diagnostic. */
528 u_char tmp[NS_INADDRSZ] = {0}, *tp;
529
530 saw_digit = 0;
531 octets = 0;
532 tp = tmp;
533 while ((ch = *src++) != '\0') {
534 const char* pch;
535
536 if ((pch = strchr(digits, ch)) != NULL) {
537 u_int uiNew = *tp * 10 + (pch - digits);
538
539 if (saw_digit && *tp == 0)
540 return (0);
541 if (uiNew > 255)
542 return (0);
543 *tp = uiNew;
544 if (!saw_digit) {
545 if (++octets > 4)
546 return (0);
547 saw_digit = 1;
548 }
549 } else if (ch == '.' && saw_digit) {
550 if (octets == 4)
551 return (0);
552 *++tp = 0;
553 saw_digit = 0;
554 } else
555 return (0);
556 }
557 if (octets < 4)
558 return (0);
559 memcpy(dst, tmp, NS_INADDRSZ);
560 return (1);
561}
562
563/* int
564 * inet_pton6(src, dst)
565 * convert presentation level address to network order binary form.
566 * return:
567 * 1 if `src' is a valid [RFC1884 2.2] address, else 0.
568 * notice:
569 * (1) does not touch `dst' unless it's returning 1.
570 * (2) :: in a full address is silently ignored.
571 * credit:
572 * inspired by Mark Andrews.
573 * author:
574 * Paul Vixie, 1996.
575 */
576static int inet_pton6(const char* src, u_char* dst) {
577 static const char xdigits_l[] = "0123456789abcdef",
578 xdigits_u[] = "0123456789ABCDEF";
579#define NS_IN6ADDRSZ 16
580#define NS_INT16SZ 2
581 u_char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp;
582 const char* curtok;
583 int ch, seen_xdigits;
584 u_int val;
585
586 memset((tp = tmp), '\0', NS_IN6ADDRSZ);
587 endp = tp + NS_IN6ADDRSZ;
588 colonp = NULL;
589 /* Leading :: requires some special handling. */
590 if (*src == ':')
591 if (*++src != ':')
592 return (0);
593 curtok = src;
594 seen_xdigits = 0;
595 val = 0;
596 while ((ch = *src++) != '\0') {
597 const char* xdigits : const char* pch;
598
599 if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL)
600 pch = strchr((xdigits = xdigits_u), ch);
601 if (pch != NULL) {
602 val <<= 4;
603 val |= (pch - xdigits);
604 if (++seen_xdigits > 4)
605 return (0);
606 continue;
607 }
608 if (ch == ':') {
609 curtok = src;
610 if (!seen_xdigits) {
611 if (colonp)
612 return (0);
613 colonp = tp;
614 continue;
615 } else if (*src == '\0') {
616 return (0);
617 }
618 if (tp + NS_INT16SZ > endp)
619 return (0);
620 *tp++ = (u_char)(val >> 8) & 0xff;
621 *tp++ = (u_char)val & 0xff;
622 seen_xdigits = 0;
623 val = 0;
624 continue;
625 }
626 if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
627 inet_pton4(curtok, tp) > 0) {
628 tp += NS_INADDRSZ;
629 seen_xdigits = 0;
630 break; /* '\\0' was seen by inet_pton4(). */
631 }
632 return (0);
633 }
634 if (seen_xdigits) {
635 if (tp + NS_INT16SZ > endp)
636 return (0);
637 *tp++ = (u_char)(val >> 8) & 0xff;
638 *tp++ = (u_char)val & 0xff;
639 }
640 if (colonp != NULL) {
641 /*
642 * Since some memmove()'s erroneously fail to handle
643 * overlapping regions, we'll do the shift by hand.
644 */
645 const int n = tp - colonp;
646 int i;
647
648 if (tp == endp)
649 return (0);
650 for (i = 1; i <= n; i++) {
651 endp[-i] = colonp[n - i];
652 colonp[n - i] = 0;
653 }
654 tp = endp;
655 }
656 if (tp != endp)
657 return (0);
658 memcpy(dst, tmp, NS_IN6ADDRSZ);
659 return (1);
660}
661
662#endif /* if GCC_VERSION <= 4.5 */
663
664#else /* not __windows__ */
665
666#include <sys/types.h>
667#include <sys/wait.h>
668
670#define NETW_CALL_RETRY(__retvar, __expression, __max_tries) \
671 do { \
672 int __nb_retries = 0; \
673 do { \
674 __retvar = (__expression); \
675 __nb_retries++; \
676 } while (__retvar == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) && __nb_retries < (__max_tries)); \
677 if (__retvar == -1 && __nb_retries >= (__max_tries)) __retvar = -2; \
678 } while (0)
679
681#define neterrno errno
682
684/* #define netstrerror( code )({ \
685 size_t errmsglen = 512 ; // strerrorlen_s( code ) + 1 ; \
686 char *errmsg = NULL ; \
687 Malloc( errmsg , char , errmsglen ); \
688 if( errmsg ) \
689 { \
690 strerror_s( errmsg , errmsglen , code ); \
691 } \
692 errmsg ; \
693 }) */
694
698static char* netstrerror(int code) {
699 /* strdup returns NULL and sets errno=ENOMEM on allocation failure;
700 * propagate that NULL to the caller, who treats it as "no message". */
701 return strdup(strerror(code));
702}
703
704#endif /* if not def windows */
705
712NETWORK* netw_new(size_t send_list_limit, size_t recv_list_limit) {
713 NETWORK* netw = NULL;
714
715 Malloc(netw, NETWORK, 1);
716 __n_assert(netw, return NULL);
717
718 /* netw itself */
719 netw->nb_pending = -1;
720 netw->mode = -1;
721 netw->user_id = -1;
725 /* Default to zlib for back-compat with the first compression
726 * pass that shipped before LZ4 support landed. Callers can
727 * switch to NONE or LZ4 via netw_set_compression_mode(). */
729
730 /* netw -> link */
731 netw->link.sock = INVALID_SOCKET;
732 netw->link.port =
733 netw->link.ip = NULL;
734 memset(&netw->link.hints, 0, sizeof(struct addrinfo));
735 memset(&netw->link.raddr, 0, sizeof(struct sockaddr_storage));
736
737 /*initiliaze mutexs*/
738 if (pthread_mutex_init(&netw->sendbolt, NULL) != 0) {
739 n_log(LOG_ERR, "Error initializing netw -> sendbolt");
740 Free(netw);
741 return NULL;
742 }
743 /*initiliaze mutexs*/
744 if (pthread_mutex_init(&netw->recvbolt, NULL) != 0) {
745 n_log(LOG_ERR, "Error initializing netw -> recvbolt");
746 pthread_mutex_destroy(&netw->sendbolt);
747 Free(netw);
748 return NULL;
749 }
750 /*initiliaze mutexs*/
751 if (pthread_mutex_init(&netw->eventbolt, NULL) != 0) {
752 n_log(LOG_ERR, "Error initializing netw -> eventbolt");
753 pthread_mutex_destroy(&netw->sendbolt);
754 pthread_mutex_destroy(&netw->recvbolt);
755 Free(netw);
756 return NULL;
757 }
758 /* initialize send sem bolt */
759 if (sem_init(&netw->send_blocker, 0, 0) != 0) {
760 n_log(LOG_ERR, "Error initializing netw -> eventbolt");
761 pthread_mutex_destroy(&netw->eventbolt);
762 pthread_mutex_destroy(&netw->sendbolt);
763 pthread_mutex_destroy(&netw->recvbolt);
764 Free(netw);
765 return NULL;
766 }
767 /*initialize queues */
768 netw->recv_buf = new_generic_list(recv_list_limit);
769 if (!netw->recv_buf) {
770 n_log(LOG_ERR, "Error when creating receive list with %d item limit", recv_list_limit);
772 return NULL;
773 }
774 netw->send_buf = new_generic_list(send_list_limit);
775 if (!netw->send_buf) {
776 n_log(LOG_ERR, "Error when creating send list with %d item limit", send_list_limit);
778 return NULL;
779 }
781 if (!netw->pools) {
782 n_log(LOG_ERR, "Error when creating pools list");
784 return NULL;
785 }
788 netw->so_reuseaddr = -1;
789 // netw -> so_reuseport = -1 ;
790 netw->so_keepalive = -1;
791 netw->tcpnodelay = -1;
792 netw->so_sndbuf = -1;
793 netw->so_rcvbuf = -1;
794 netw->so_rcvtimeo = -1;
795 netw->so_sndtimeo = -1;
796 netw->so_linger = -1;
802
807
808#ifdef HAVE_OPENSSL
809 netw->method = NULL;
810 netw->ctx = NULL;
811 netw->ssl = NULL;
812 netw->key = NULL;
813 netw->certificate = NULL;
814#endif
815
816 netw->link.is_blocking = 1;
817 return netw;
818
819} /* netw_new() */
820
826char* get_in_addr(struct sockaddr* sa) {
827 return sa->sa_family == AF_INET
828 ? (char*)&(((struct sockaddr_in*)sa)->sin_addr)
829 : (char*)&(((struct sockaddr_in6*)sa)->sin6_addr);
830}
831
839int netw_init_wsa(int mode, int v1, int v2) {
840 int compiler_warning_suppressor = 0;
841#if !defined(__linux__) && !defined(__sun) && !defined(_AIX)
842 static WSADATA WSAdata; /*WSA world*/
843 static int WSA_IS_INITIALIZED = 0; /*status checking*/
844
845 switch (mode) {
846 default:
847 /*returning WSA status*/
848 case 2:
849 return WSA_IS_INITIALIZED;
850 break;
851 /*loading WSA dll*/
852 case 1:
853 if (WSA_IS_INITIALIZED == 1)
854 return TRUE; /*already loaded*/
855 if ((WSAStartup(MAKEWORD(v1, v2), &WSAdata)) != 0) {
856 WSA_IS_INITIALIZED = 0;
857 return FALSE;
858 } else {
859 WSA_IS_INITIALIZED = 1;
860 return TRUE;
861 }
862 break;
863 /*unloading (closing) WSA */
864 case 0:
865 if (WSA_IS_INITIALIZED == 0)
866 return TRUE; /*already CLEANED or not loaded */
867 if (WSACleanup() == 0) {
868 WSA_IS_INITIALIZED = 0;
869 return TRUE;
870 }
871 break;
872 } /*switch(...)*/
873#endif /* ifndef __linux__ __sun _AIX */
874 compiler_warning_suppressor = mode + v1 + v2;
875 (void)compiler_warning_suppressor;
876 compiler_warning_suppressor = TRUE;
877 return compiler_warning_suppressor;
878} /*netw_init_wsa(...)*/
879
892 __n_assert(netw, return FALSE);
893 if (mode != NETW_COMPRESS_NONE &&
896 n_log(LOG_ERR, "netw_set_compression_mode: unknown mode %d", mode);
897 return FALSE;
898 }
900 return TRUE;
901} /* netw_set_compression_mode */
902
909int netw_set_blocking(NETWORK* netw, unsigned long int is_blocking) {
910 __n_assert(netw, return FALSE);
911
912 int error = 0;
913 (void)error;
914 char* errmsg = NULL;
915
916#if defined(__linux__) || defined(__sun)
917 int flags = 0;
918 flags = fcntl(netw->link.sock, F_GETFL, 0);
919 if (netw->link.is_blocking != 0 && !is_blocking) {
920 if (flags & O_NONBLOCK) {
921 n_log(LOG_DEBUG, "socket %d was already in non-blocking mode", netw->link.sock);
922 /* in case we missed it, let's update the link mode */
923 netw->link.is_blocking = 0;
924 return TRUE;
925 }
926 } else if (netw->link.is_blocking != 1 && is_blocking) {
927 if (!(flags & O_NONBLOCK)) {
928 n_log(LOG_DEBUG, "socket %d was already in blocking mode", netw->link.sock);
929 /* in case we missed it, let's update the link mode */
930 netw->link.is_blocking = 1;
931 return TRUE;
932 }
933 }
934 if (fcntl(netw->link.sock, F_SETFL, is_blocking ? flags & ~O_NONBLOCK : flags | O_NONBLOCK) == -1) {
935 error = neterrno;
936 errmsg = netstrerror(error);
937 _netw_capture_error(netw, "couldn't set blocking mode %lu on %d: %s", is_blocking, netw->link.sock, _str(errmsg));
938 n_log(LOG_ERR, "couldn't set blocking mode %lu on %d: %s", is_blocking, netw->link.sock, _str(errmsg));
939 FreeNoLog(errmsg);
940 return FALSE;
941 }
942#else
943 unsigned long int blocking = 1 - is_blocking;
944 int res = ioctlsocket(netw->link.sock, (long)FIONBIO, &blocking);
945 error = neterrno;
946 if (res != NO_ERROR) {
947 errmsg = netstrerror(error);
948 _netw_capture_error(netw, "ioctlsocket failed with error: %ld , neterrno: %s", res, _str(errmsg));
949 n_log(LOG_ERR, "ioctlsocket failed with error: %ld , neterrno: %s", res, _str(errmsg));
950 FreeNoLog(errmsg);
952 return FALSE;
953 }
954#endif
955 netw->link.is_blocking = is_blocking;
956 return TRUE;
957} /* netw_set_blocking */
958
967int netw_setsockopt(NETWORK* netw, int optname, int value) {
968 __n_assert(netw, return FALSE);
969
970 int error = 0;
971 char* errmsg = NULL;
972
973 switch (optname) {
974 case TCP_NODELAY:
975 if (value >= 0) {
976 /* disable naggle algorithm */
977 if (setsockopt(netw->link.sock, IPPROTO_TCP, TCP_NODELAY, (const char*)&value, sizeof(value)) == -1) {
978 error = neterrno;
979 errmsg = netstrerror(error);
980 _netw_capture_error(netw, "Error from setsockopt(TCP_NODELAY) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
981 n_log(LOG_ERR, "Error from setsockopt(TCP_NODELAY) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
982 FreeNoLog(errmsg);
983 return FALSE;
984 }
985 }
986 netw->tcpnodelay = value;
987 break;
988 case SO_SNDBUF:
989 /* socket sending buffer size */
990 if (value >= 0) {
991 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_SNDBUF, (const char*)&value, sizeof(value)) == -1) {
992 error = neterrno;
993 errmsg = netstrerror(error);
994 _netw_capture_error(netw, "Error from setsockopt(SO_SNDBUF) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
995 n_log(LOG_ERR, "Error from setsockopt(SO_SNDBUF) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
996 FreeNoLog(errmsg);
997 return FALSE;
998 }
999 }
1000 netw->so_sndbuf = value;
1001 break;
1002 case SO_RCVBUF:
1003 /* socket receiving buffer */
1004 if (value >= 0) {
1005 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_RCVBUF, (const char*)&value, sizeof(value)) == -1) {
1006 error = neterrno;
1007 errmsg = netstrerror(error);
1008 _netw_capture_error(netw, "Error from setsockopt(SO_RCVBUF) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1009 n_log(LOG_ERR, "Error from setsockopt(SO_RCVBUF) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1010 FreeNoLog(errmsg);
1011 return FALSE;
1012 }
1013 }
1014 netw->so_rcvbuf = value;
1015 break;
1016 case SO_REUSEADDR:
1017 /* lose the pesky "Address already in use" error message*/
1018 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof(value)) == -1) {
1019 error = neterrno;
1020 errmsg = netstrerror(error);
1021 _netw_capture_error(netw, "Error from setsockopt(SO_REUSEADDR) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1022 n_log(LOG_ERR, "Error from setsockopt(SO_REUSEADDR) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1023 FreeNoLog(errmsg);
1024 return FALSE;
1025 }
1026 netw->so_reuseaddr = value;
1027 break;
1028 /*case SO_REUSEPORT :
1029 // lose the pesky "port already in use" error message
1030 if ( setsockopt( netw -> link . sock, SOL_SOCKET, SO_REUSEPORT, (char *)&value, sizeof( value ) ) == -1 )
1031 {
1032 error=neterrno ;
1033 errmsg = netstrerror( error );
1034 n_log( LOG_ERR, "Error from setsockopt(SO_REUSEPORT) on socket %d. neterrno: %s", netw -> link . sock, _str( errmsg ) );
1035 FreeNoLog( errmsg );
1036 return FALSE ;
1037 }
1038 netw -> so_reuseport = value ;
1039 break ;*/
1040 case SO_LINGER: {
1041 struct linger ling;
1042 if (value < 0) {
1043 ling.l_onoff = 0;
1044 ling.l_linger = 0;
1045 } else if (value == 0) {
1046 ling.l_onoff = 1;
1047 ling.l_linger = 0;
1048 } else {
1049 ling.l_onoff = 1;
1050#ifdef __windows__
1051 ling.l_linger = (u_short)value;
1052#else
1053 ling.l_linger = value;
1054#endif
1055 }
1056#ifndef __windows__
1057 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)) == -1) {
1058 error = neterrno;
1059 errmsg = netstrerror(error);
1060 _netw_capture_error(netw, "Error from setsockopt(SO_LINGER) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1061 n_log(LOG_ERR, "Error from setsockopt(SO_LINGER) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1062 FreeNoLog(errmsg);
1063 return FALSE;
1064 }
1065#else
1066 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_LINGER, (const char*)&ling, sizeof(ling)) == -1) {
1067 error = neterrno;
1068 errmsg = netstrerror(error);
1069 _netw_capture_error(netw, "Error from setsockopt(SO_LINGER) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1070 n_log(LOG_ERR, "Error from setsockopt(SO_LINGER) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1071 FreeNoLog(errmsg);
1072 return FALSE;
1073 }
1074#endif // __windows__
1075 netw->so_linger = value;
1076 } break;
1077 case SO_RCVTIMEO:
1078 if (value >= 0) {
1079#ifndef __windows__
1080 {
1081 struct timeval tv;
1082 tv.tv_sec = value;
1083 tv.tv_usec = 0;
1084 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv) == -1) {
1085 error = neterrno;
1086 errmsg = netstrerror(error);
1087 _netw_capture_error(netw, "Error from setsockopt(SO_RCVTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1088 n_log(LOG_ERR, "Error from setsockopt(SO_RCVTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1089 FreeNoLog(errmsg);
1090 return FALSE;
1091 }
1092 }
1093#else
1094 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&value, sizeof value) == -1) {
1095 error = neterrno;
1096 errmsg = netstrerror(error);
1097 _netw_capture_error(netw, "Error from setsockopt(SO_RCVTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1098 n_log(LOG_ERR, "Error from setsockopt(SO_RCVTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1099 FreeNoLog(errmsg);
1100 return FALSE;
1101 }
1102#endif
1103 }
1104 netw->so_rcvtimeo = value;
1105 break;
1106 case SO_SNDTIMEO:
1107 if (value >= 0) {
1108#ifndef __windows__
1109 {
1110 struct timeval tv;
1111 tv.tv_sec = value;
1112 tv.tv_usec = 0;
1113
1114 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof tv) == -1) {
1115 error = neterrno;
1116 errmsg = netstrerror(error);
1117 _netw_capture_error(netw, "Error from setsockopt(SO_SNDTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1118 n_log(LOG_ERR, "Error from setsockopt(SO_SNDTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1119 FreeNoLog(errmsg);
1120 return FALSE;
1121 }
1122 }
1123#else
1124 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&value, sizeof value) == -1) {
1125 error = neterrno;
1126 errmsg = netstrerror(error);
1127 _netw_capture_error(netw, "Error from setsockopt(SO_SNDTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1128 n_log(LOG_ERR, "Error from setsockopt(SO_SNDTIMEO) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1129 FreeNoLog(errmsg);
1130 return FALSE;
1131 }
1132#endif
1133 }
1134 netw->so_sndtimeo = value;
1135 break;
1138 break;
1141 break;
1143 netw->wait_close_timeout = value;
1144 break;
1147 break;
1148#ifdef __linux__
1149 case TCP_USER_TIMEOUT:
1150 if (value >= 0) {
1151 if (setsockopt(netw->link.sock, IPPROTO_TCP, TCP_USER_TIMEOUT, (const char*)&value, sizeof value) == -1) {
1152 error = neterrno;
1153 errmsg = netstrerror(error);
1154 _netw_capture_error(netw, "Error from setsockopt(TCP_USER_TIMEOUT) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1155 n_log(LOG_ERR, "Error from setsockopt(TCP_USER_TIMEOUT) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1156 FreeNoLog(errmsg);
1157 return FALSE;
1158 }
1159 }
1160 break;
1161 case TCP_QUICKACK:
1162 if (setsockopt(netw->link.sock, IPPROTO_TCP, TCP_QUICKACK, &value, sizeof(value)) < 0) {
1163 error = neterrno;
1164 errmsg = netstrerror(error);
1165 _netw_capture_error(netw, "Error setting setsockopt(TCP_QUICKACK) to %d on sock %d. neterrno: %s", value, netw->link.sock, _str(errmsg));
1166 n_log(LOG_ERR, "Error setting setsockopt(TCP_QUICKACK) to %d on sock %d. neterrno: %s", value, netw->link.sock, _str(errmsg));
1167 FreeNoLog(errmsg);
1168 return FALSE;
1169 }
1170 break;
1171#endif
1172 case SO_KEEPALIVE:
1173 if (setsockopt(netw->link.sock, SOL_SOCKET, SO_KEEPALIVE, (const char*)&value, sizeof value) == -1) {
1174 error = neterrno;
1175 errmsg = netstrerror(error);
1176 _netw_capture_error(netw, "Error from setsockopt(SO_KEEPALIVE) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1177 n_log(LOG_ERR, "Error from setsockopt(SO_KEEPALIVE) on socket %d. neterrno: %s", netw->link.sock, _str(errmsg));
1178 FreeNoLog(errmsg);
1179 return FALSE;
1180 }
1181 netw->so_keepalive = value;
1182 break;
1183
1184 default:
1185 _netw_capture_error(netw, "%d is not a supported setsockopt", optname);
1186 n_log(LOG_ERR, "%d is not a supported setsockopt", optname);
1187 return FALSE;
1188 }
1189 return TRUE;
1190} /* netw_set_sock_opt */
1191
1192#ifdef HAVE_OPENSSL
1193
1199 BIO* bio = BIO_new(BIO_s_mem());
1200 if (!bio) {
1201 return NULL;
1202 }
1203
1204 ERR_print_errors(bio); // Write errors to the BIO
1205
1206 char* buf;
1207 size_t len = (size_t)BIO_get_mem_data(bio, &buf); // Get data from the BIO. Can return 0 if empty of failled
1208
1209 // Allocate memory for the error string and copy it
1210 char* error_str = malloc(len + 1);
1211 if (error_str) {
1212 memcpy(error_str, buf, len);
1213 error_str[len] = '\0'; // Null-terminate the string
1214 }
1215
1216 BIO_free(bio); // Free the BIO
1217
1218 return error_str;
1219}
1220
1230 unsigned long error = 0;
1231 while ((error = ERR_get_error())) {
1232 int level = LOG_ERR;
1233#ifdef SSL_R_UNEXPECTED_EOF_WHILE_READING
1234 /* A peer that closes the TCP connection without a TLS close_notify makes
1235 OpenSSL 3.x queue "unexpected eof while reading". Clients (web browsers
1236 especially, on speculative/preconnect sockets) do this routinely, so it
1237 is expected disconnect noise rather than an error: log it at DEBUG so it
1238 does not drown a real TLS failure, which keeps its own reason code and
1239 LOG_ERR level. */
1240 if (ERR_GET_REASON(error) == SSL_R_UNEXPECTED_EOF_WHILE_READING)
1241 level = LOG_DEBUG;
1242#endif
1243 n_log(level, "socket %d: %s", socket, ERR_reason_error_string(error));
1244 }
1245}
1246
1247/***************************************************************************
1248 * _ _ ____ _
1249 * Project ___| | | | _ \| |
1250 * / __| | | | |_) | |
1251 * | (__| |_| | _ <| |___
1252 * \___|\___/|_| \_\_____|
1253 *
1254 * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel at haxx.se>, et al.
1255 *
1256 * This software is licensed as described in the file COPYING, which
1257 * you should have received as part of this distribution. The terms
1258 * are also available at https://curl.haxx.se/docs/copyright.html.
1259 *
1260 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
1261 * copies of the Software, and permit persons to whom the Software is
1262 * furnished to do so, under the terms of the COPYING file.
1263 *
1264 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
1265 * KIND, either express or implied.
1266 *
1267 ***************************************************************************/
1268/* <DESC>
1269 * Show the required mutex callback setups for GnuTLS and OpenSSL when using
1270 * libcurl multi-threaded.
1271 * </DESC>
1272 */
1273/* A multi-threaded example that uses pthreads and fetches 4 remote files at
1274 * once over HTTPS. The lock callbacks and stuff assume OpenSSL <1.1 or GnuTLS
1275 * (libgcrypt) so far.
1276 *
1277 * OpenSSL docs for this:
1278 * https://www.openssl.org/docs/man1.0.2/man3/CRYPTO_num_locks.html
1279 * gcrypt docs for this:
1280 * https://gnupg.org/documentation/manuals/gcrypt/Multi_002dThreading.html
1281 */
1282
1283/* we have this global to let the callback get easy access to it */
1284static pthread_mutex_t* netw_ssl_lockarray;
1285
1286__attribute__((unused)) static void netw_ssl_lock_callback(int mode, int type, char* file, int line) {
1287 (void)file;
1288 (void)line;
1289 if (mode & CRYPTO_LOCK) {
1290 pthread_mutex_lock(&(netw_ssl_lockarray[type]));
1291 } else {
1292 pthread_mutex_unlock(&(netw_ssl_lockarray[type]));
1293 }
1294}
1295
1296__attribute__((unused)) static unsigned long thread_id(void) {
1297 unsigned long ret;
1298
1299 ret = (unsigned long)pthread_self();
1300 return ret;
1301}
1302
1303static void netw_init_locks(void) {
1304 int i;
1305
1306 size_t lock_count = (size_t)CRYPTO_num_locks();
1307 netw_ssl_lockarray = (pthread_mutex_t*)OPENSSL_malloc((size_t)(lock_count * sizeof(pthread_mutex_t)));
1308
1309 for (i = 0; i < CRYPTO_num_locks(); i++) {
1310 pthread_mutex_init(&(netw_ssl_lockarray[i]), NULL);
1311 }
1312
1313 CRYPTO_set_id_callback((unsigned long (*)())thread_id);
1314 CRYPTO_set_locking_callback((void (*)())netw_ssl_lock_callback);
1315}
1316
1317static void netw_kill_locks(void) {
1318 int i;
1319
1320 CRYPTO_set_locking_callback(NULL);
1321 for (i = 0; i < CRYPTO_num_locks(); i++)
1322 pthread_mutex_destroy(&(netw_ssl_lockarray[i]));
1323
1324 OPENSSL_free(netw_ssl_lockarray);
1325}
1326
1328
1334 if (OPENSSL_IS_INITIALIZED == 1)
1335 return TRUE; /*already loaded*/
1336
1337 SSL_library_init();
1338 SSL_load_error_strings();
1339 // Before OpenSSL 1.1.0 (< 0x10100000L): ERR_load_BIO_strings(); was required to load error messages for BIO functions
1340#if OPENSSL_VERSION_NUMBER < 0x10100000L
1341 ERR_load_BIO_strings();
1342#endif
1343 OpenSSL_add_all_algorithms();
1345
1346#ifndef __windows__
1347 /* The raw send paths pass MSG_NOSIGNAL (NETFLAGS),
1348 * but OpenSSL's internal write(2) does not: a TLS peer that
1349 * disconnects mid-SSL_write delivers SIGPIPE and kills the whole
1350 * process (server, client, or test alike). Ignoring it here turns
1351 * that into the EPIPE errno the SSL error paths already handle.
1352 * Standard practice for OpenSSL applications; scoped to TLS users
1353 * since this only runs from netw_init_openssl. */
1354 signal(SIGPIPE, SIG_IGN);
1355#endif
1356
1358
1359 return TRUE;
1360} /*netw_init_openssl(...)*/
1361
1367 if (OPENSSL_IS_INITIALIZED == 0)
1368 return TRUE; /*already unloaded*/
1369
1371 EVP_cleanup();
1372
1374
1375 return TRUE;
1376} /*netw_unload_openssl(...)*/
1377
1385int netw_set_crypto(NETWORK* netw, char* key, char* certificate) {
1386 __n_assert(netw, return FALSE);
1387
1388 char* tmp_key = NULL;
1389 char* tmp_cert = NULL;
1390 if (key && strlen(key) > 0) {
1391 tmp_key = strdup(key);
1392 if (!tmp_key) {
1393 n_log(LOG_ERR, "strdup failed for key in netw_set_crypto");
1394 return FALSE;
1395 }
1396 }
1397 if (certificate && strlen(certificate) > 0) {
1398 tmp_cert = strdup(certificate);
1399 if (!tmp_cert) {
1400 n_log(LOG_ERR, "strdup failed for certificate in netw_set_crypto");
1401 FreeNoLog(tmp_key);
1402 return FALSE;
1403 }
1404 }
1405 if (tmp_key) {
1406 FreeNoLog(netw->key);
1407 netw->key = tmp_key;
1408 }
1409 if (tmp_cert) {
1411 netw->certificate = tmp_cert;
1412 }
1413 if (key && certificate) {
1415#if OPENSSL_VERSION_NUMBER >= 0x10100000L // OpenSSL 1.1.0 or later
1416 netw->method = TLS_method(); // create new server-method instance
1417#else
1418 netw->method = TLSv1_2_method(); // create new server-method instance
1419#endif
1420 netw->ctx = SSL_CTX_new(netw->method); // create new context from method
1421 // SSL_CTX_set_verify(netw -> ctx, SSL_VERIFY_PEER, NULL); // Enable certificate verification
1422
1423 if (netw->ctx == NULL) {
1425 return FALSE;
1426 }
1427 /* PARTIAL_WRITE: a short SSL_write can be resumed from the
1428 * caller's buffer+offset (required by the reactor's
1429 * single-attempt send path); MOVING_WRITE_BUFFER: tolerate the
1430 * resume pointer not being the original one. No-op for the
1431 * blocking thread engine. */
1432 SSL_CTX_set_mode(netw->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
1433
1434#if OPENSSL_VERSION_NUMBER >= 0x10101000L
1435 /* 2026-07-18 — send ZERO TLS 1.3 session tickets. The threaded
1436 * client engine runs SSL_read (recv thread) and SSL_write (send
1437 * thread) on the same SSL*; OpenSSL does not support concurrent
1438 * use, and the one window where the read path MUTATES shared
1439 * session state is post-handshake NewSessionTicket processing —
1440 * which lands at the exact moment the client fires its first
1441 * application write (HELLO). On low-RTT links (loopback / LAN)
1442 * the overlap corrupts the client's outbound record stream and
1443 * the server dies with "decryption failed or bad record mac" on
1444 * its FIRST read (torn records can also sit unreadable for
1445 * seconds until more bytes complete them). Nothing here uses TLS
1446 * session resumption, so tickets are pure race surface — drop
1447 * them at the source. Higher-RTT links (the 8 ms VPS) rarely hit
1448 * the window, which is why this only surfaced on local servers. */
1449 SSL_CTX_set_num_tickets(netw->ctx, 0);
1450#endif
1451
1452 // Load default system certs
1453 if (SSL_CTX_load_verify_locations(netw->ctx, NULL, "/etc/ssl/certs/") != 1) {
1455 return FALSE;
1456 }
1457
1458 if (SSL_CTX_use_certificate_file(netw->ctx, certificate, SSL_FILETYPE_PEM) <= 0) {
1460 return FALSE;
1461 }
1462 if (SSL_CTX_use_PrivateKey_file(netw->ctx, key, SSL_FILETYPE_PEM) <= 0) {
1464 return FALSE;
1465 }
1466
1471
1473 }
1474
1475 return TRUE;
1476} /* netw_set_crypto */
1477
1485int netw_set_crypto_pem(NETWORK* netw, const char* key_pem, const char* cert_pem) {
1486 __n_assert(netw, return FALSE);
1487 __n_assert(key_pem, return FALSE);
1488 __n_assert(cert_pem, return FALSE);
1489
1491#if OPENSSL_VERSION_NUMBER >= 0x10100000L
1492 netw->method = TLS_method();
1493#else
1494 netw->method = TLSv1_2_method();
1495#endif
1496 netw->ctx = SSL_CTX_new(netw->method);
1497 if (netw->ctx == NULL) {
1499 return FALSE;
1500 }
1501 /* PARTIAL_WRITE: a short SSL_write can be resumed from the
1502 * caller's buffer+offset (required by the reactor's
1503 * single-attempt send path); MOVING_WRITE_BUFFER: tolerate the
1504 * resume pointer not being the original one. No-op for the
1505 * blocking thread engine. */
1506 SSL_CTX_set_mode(netw->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
1507
1508#if OPENSSL_VERSION_NUMBER >= 0x10101000L
1509 /* 2026-07-18 — zero TLS 1.3 session tickets; see the twin comment in
1510 * netw_set_crypto: post-handshake NewSessionTicket processing in the
1511 * client's recv thread races the send thread's first SSL_write on the
1512 * shared SSL* ("bad record mac" at connect on low-RTT links). No
1513 * consumer uses session resumption. */
1514 SSL_CTX_set_num_tickets(netw->ctx, 0);
1515#endif
1516
1517 /* Load certificate from PEM string */
1518 BIO* cert_bio = BIO_new_mem_buf(cert_pem, -1);
1519 if (!cert_bio) {
1520 n_log(LOG_ERR, "Failed to create BIO for certificate PEM");
1521 return FALSE;
1522 }
1523 X509* cert = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL);
1524 BIO_free(cert_bio);
1525 if (!cert) {
1526 n_log(LOG_ERR, "Failed to parse certificate PEM");
1528 return FALSE;
1529 }
1530 if (SSL_CTX_use_certificate(netw->ctx, cert) <= 0) {
1531 X509_free(cert);
1533 return FALSE;
1534 }
1535 X509_free(cert);
1536
1537 /* Load private key from PEM string */
1538 BIO* key_bio = BIO_new_mem_buf(key_pem, -1);
1539 if (!key_bio) {
1540 n_log(LOG_ERR, "Failed to create BIO for key PEM");
1541 return FALSE;
1542 }
1543 EVP_PKEY* pkey = PEM_read_bio_PrivateKey(key_bio, NULL, NULL, NULL);
1544 BIO_free(key_bio);
1545 if (!pkey) {
1546 n_log(LOG_ERR, "Failed to parse private key PEM");
1548 return FALSE;
1549 }
1550 if (SSL_CTX_use_PrivateKey(netw->ctx, pkey) <= 0) {
1551 EVP_PKEY_free(pkey);
1553 return FALSE;
1554 }
1555 EVP_PKEY_free(pkey);
1556
1557 /* Verify that key matches certificate */
1558 if (!SSL_CTX_check_private_key(netw->ctx)) {
1559 n_log(LOG_ERR, "Private key does not match the certificate");
1560 return FALSE;
1561 }
1562
1568
1569 return TRUE;
1570} /* netw_set_crypto_pem */
1571
1580int netw_set_crypto_chain(NETWORK* netw, char* key, char* certificate, char* ca_file) {
1581 __n_assert(netw, return FALSE);
1582 __n_assert(key, return FALSE);
1583 __n_assert(certificate, return FALSE);
1584 __n_assert(ca_file, return FALSE);
1585
1586 if (netw_set_crypto(netw, key, certificate) == FALSE) {
1587 return FALSE;
1588 }
1589
1590 /* Load the full certificate chain */
1591 if (SSL_CTX_use_certificate_chain_file(netw->ctx, certificate) <= 0) {
1592 n_log(LOG_ERR, "Failed to load certificate chain from %s", certificate);
1594 return FALSE;
1595 }
1596
1597 /* Load CA file for verification */
1598 if (SSL_CTX_load_verify_locations(netw->ctx, ca_file, NULL) != 1) {
1599 n_log(LOG_ERR, "Failed to load CA file %s", ca_file);
1601 return FALSE;
1602 }
1603
1604 return TRUE;
1605} /* netw_set_crypto_chain */
1606
1615int netw_set_crypto_chain_pem(NETWORK* netw, const char* key_pem, const char* cert_pem, const char* ca_pem) {
1616 __n_assert(netw, return FALSE);
1617 __n_assert(key_pem, return FALSE);
1618 __n_assert(cert_pem, return FALSE);
1619 __n_assert(ca_pem, return FALSE);
1620
1621 if (netw_set_crypto_pem(netw, key_pem, cert_pem) == FALSE) {
1622 return FALSE;
1623 }
1624
1625 /* Load CA certificate from PEM string into the trust store */
1626 BIO* ca_bio = BIO_new_mem_buf(ca_pem, -1);
1627 if (!ca_bio) {
1628 n_log(LOG_ERR, "Failed to create BIO for CA PEM");
1629 return FALSE;
1630 }
1631
1632 X509_STORE* store = SSL_CTX_get_cert_store(netw->ctx);
1633 if (!store) {
1634 BIO_free(ca_bio);
1635 n_log(LOG_ERR, "Failed to get certificate store from SSL context");
1636 return FALSE;
1637 }
1638
1639 X509* ca_cert = NULL;
1640 int ca_loaded = 0;
1641 while ((ca_cert = PEM_read_bio_X509(ca_bio, NULL, NULL, NULL)) != NULL) {
1642 if (X509_STORE_add_cert(store, ca_cert) != 1) {
1643 n_log(LOG_ERR, "Failed to add CA certificate to store");
1644 X509_free(ca_cert);
1645 BIO_free(ca_bio);
1646 return FALSE;
1647 }
1648 X509_free(ca_cert);
1649 ca_loaded++;
1650 }
1651 BIO_free(ca_bio);
1652
1653 if (ca_loaded == 0) {
1654 n_log(LOG_ERR, "No CA certificates were loaded from PEM string");
1655 return FALSE;
1656 }
1657
1658 /* Also load any extra chain certificates from the cert PEM */
1659 BIO* chain_bio = BIO_new_mem_buf(cert_pem, -1);
1660 if (chain_bio) {
1661 /* Skip the first certificate (already loaded as the leaf) */
1662 X509* skip_cert = PEM_read_bio_X509(chain_bio, NULL, NULL, NULL);
1663 if (skip_cert) {
1664 X509_free(skip_cert);
1665 }
1666 /* Load remaining certificates as chain intermediates */
1667 X509* chain_cert = NULL;
1668 while ((chain_cert = PEM_read_bio_X509(chain_bio, NULL, NULL, NULL)) != NULL) {
1669 if (SSL_CTX_add_extra_chain_cert(netw->ctx, chain_cert) != 1) {
1670 n_log(LOG_ERR, "Failed to add chain certificate");
1671 X509_free(chain_cert);
1672 }
1673 /* Note: SSL_CTX_add_extra_chain_cert takes ownership, so no X509_free on success */
1674 }
1675 BIO_free(chain_bio);
1676 }
1677
1678 return TRUE;
1679} /* netw_set_crypto_chain_pem */
1680
1681/* Install an in-memory certificate and key on a single SSL connection. Shared
1682 * by netw_set_crypto_pem_ctx and the SNI accept callback. Returns 0 on success,
1683 * -1 on error. */
1684static int _ssl_use_pem(SSL* ssl, const char* key_pem, const char* cert_pem) {
1685 BIO* cbio = BIO_new_mem_buf(cert_pem, -1);
1686 X509* cert = NULL;
1687 int rc = -1;
1688 if (!cbio)
1689 return -1;
1690 cert = PEM_read_bio_X509(cbio, NULL, NULL, NULL);
1691 BIO_free(cbio);
1692 if (!cert)
1693 return -1;
1694 if (SSL_use_certificate(ssl, cert) == 1) {
1695 BIO* kbio = BIO_new_mem_buf(key_pem, -1);
1696 if (kbio) {
1697 EVP_PKEY* pkey = PEM_read_bio_PrivateKey(kbio, NULL, NULL, NULL);
1698 BIO_free(kbio);
1699 if (pkey) {
1700 if (SSL_use_PrivateKey(ssl, pkey) == 1 && SSL_check_private_key(ssl) == 1)
1701 rc = 0;
1702 EVP_PKEY_free(pkey);
1703 }
1704 }
1705 }
1706 X509_free(cert);
1707 return rc;
1708}
1709
1716 __n_assert(netw, return NULL);
1717 if (!netw->ssl)
1718 return NULL;
1719 return SSL_get_servername(netw->ssl, TLSEXT_NAMETYPE_host_name);
1720}
1721
1729int netw_set_crypto_pem_ctx(NETWORK* netw, const N_STR* key_pem, const N_STR* cert_pem) {
1730 __n_assert(netw, return FALSE);
1731 __n_assert(netw->ssl, return FALSE);
1732 __n_assert(key_pem && key_pem->data, return FALSE);
1733 __n_assert(cert_pem && cert_pem->data, return FALSE);
1734 if (_ssl_use_pem(netw->ssl, key_pem->data, cert_pem->data) != 0) {
1736 return FALSE;
1737 }
1738 return TRUE;
1739}
1740
1741/* Arg handed (by stack pointer) to the servername callback for the duration of
1742 * a single netw_accept_ssl_with_sni_cb call. */
1747
1748/* OpenSSL servername callback: fires during SSL_accept once the ClientHello is
1749 * parsed. Picks a per-host certificate via the caller's pick() and installs it
1750 * on the current SSL so the handshake completes with a host-matching leaf. */
1751static int _netw_sni_servername_cb(SSL* ssl, int* al, void* arg) {
1752 NETW_SNI_ARG* a = (NETW_SNI_ARG*)arg;
1753 const char* sni;
1754 N_STR* cert_pem = NULL;
1755 N_STR* key_pem = NULL;
1756 int ok;
1757 (void)al;
1758 if (!a || !a->pick)
1759 return SSL_TLSEXT_ERR_OK;
1760 sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
1761 if (a->pick(sni, &cert_pem, &key_pem, a->user_data) != 0 || !cert_pem || !key_pem) {
1762 if (cert_pem)
1763 free_nstr(&cert_pem);
1764 if (key_pem)
1765 free_nstr(&key_pem);
1766 return SSL_TLSEXT_ERR_ALERT_FATAL;
1767 }
1768 ok = (_ssl_use_pem(ssl, key_pem->data, cert_pem->data) == 0);
1769 free_nstr(&cert_pem);
1770 free_nstr(&key_pem);
1771 return ok ? SSL_TLSEXT_ERR_OK : SSL_TLSEXT_ERR_ALERT_FATAL;
1772}
1773
1791 NETW_SNI_ARG arg;
1792 __n_assert(netw, return FALSE);
1793 __n_assert(pick, return FALSE);
1794
1796#if OPENSSL_VERSION_NUMBER >= 0x10100000L
1797 netw->method = TLS_method();
1798#else
1799 netw->method = TLSv1_2_method();
1800#endif
1801 netw->ctx = SSL_CTX_new(netw->method);
1802 if (!netw->ctx) {
1804 return FALSE;
1805 }
1806 SSL_CTX_set_mode(netw->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
1807 SSL_CTX_set_tlsext_servername_callback(netw->ctx, _netw_sni_servername_cb);
1808
1809 /* The callback runs synchronously inside the blocking SSL_accept below, so a
1810 * stack-scoped arg is valid for its whole lifetime and needs no heap/free. */
1811 arg.pick = pick;
1812 arg.user_data = user_data;
1813 SSL_CTX_set_tlsext_servername_arg(netw->ctx, &arg);
1814
1815 netw->ssl = SSL_new(netw->ctx);
1816 if (!netw->ssl) {
1818 return FALSE;
1819 }
1820 SSL_set_fd(netw->ssl, (int)netw->link.sock);
1826
1827 if (SSL_accept(netw->ssl) <= 0) {
1829 return FALSE;
1830 }
1831 return TRUE;
1832}
1833
1847 NETWORK* client;
1848 __n_assert(listen, return NULL);
1849 __n_assert(pick, return NULL);
1850 client = netw_accept_from(listen);
1851 if (!client)
1852 return NULL;
1853 if (netw_ssl_server_handshake(client, pick, user_data) != TRUE) {
1854 netw_close(&client);
1855 return NULL;
1856 }
1857 return client;
1858}
1859
1867int netw_ssl_set_ca(NETWORK* netw, const char* ca_file, const char* ca_path) {
1868 __n_assert(netw, return FALSE);
1869 __n_assert(netw->ctx, n_log(LOG_ERR, "SSL context not initialized, call netw_set_crypto first"); return FALSE);
1870
1871 if (!ca_file && !ca_path) {
1872 n_log(LOG_ERR, "At least one of ca_file or ca_path must be specified");
1873 return FALSE;
1874 }
1875
1876 if (SSL_CTX_load_verify_locations(netw->ctx, ca_file, ca_path) != 1) {
1877 n_log(LOG_ERR, "Failed to load CA from file=%s path=%s", _str(ca_file), _str(ca_path));
1878 _netw_capture_error(netw, "Failed to load CA from file=%s path=%s", _str(ca_file), _str(ca_path));
1880 return FALSE;
1881 }
1882
1883 return TRUE;
1884} /* netw_ssl_set_ca */
1885
1893 __n_assert(netw, return FALSE);
1894 __n_assert(netw->ctx, n_log(LOG_ERR, "SSL context not initialized, call netw_set_crypto first"); return FALSE);
1895
1896 if (enable) {
1897 SSL_CTX_set_verify(netw->ctx, SSL_VERIFY_PEER, NULL);
1898 } else {
1899 SSL_CTX_set_verify(netw->ctx, SSL_VERIFY_NONE, NULL);
1900 }
1901
1902 return TRUE;
1903} /* netw_ssl_set_verify */
1904
1921int netw_ssl_get_verify_result(NETWORK* netw, const char* expected_host, char* errbuf, size_t errsz) {
1922 X509* cert = NULL;
1923 STACK_OF(X509)* chain = NULL;
1924 X509_STORE* store = NULL;
1925 X509_STORE_CTX* store_ctx = NULL;
1926 int chain_ok = 0;
1927 int host_ok = 1;
1928 int result = FALSE;
1929
1930 if (errbuf && errsz > 0)
1931 errbuf[0] = '\0';
1932 __n_assert(netw, return FALSE);
1933 __n_assert(netw->ssl, return FALSE);
1934
1935 cert = SSL_get_peer_certificate(netw->ssl);
1936 if (!cert) {
1937 if (errbuf && errsz > 0)
1938 snprintf(errbuf, errsz, "no peer certificate");
1939 return FALSE;
1940 }
1941 chain = SSL_get_peer_cert_chain(netw->ssl);
1942
1943 store = X509_STORE_new();
1944 store_ctx = X509_STORE_CTX_new();
1945 if (!store || !store_ctx) {
1946 if (errbuf && errsz > 0)
1947 snprintf(errbuf, errsz, "out of memory");
1948 goto cleanup;
1949 }
1950 X509_STORE_set_default_paths(store);
1951 if (X509_STORE_CTX_init(store_ctx, store, cert, chain) != 1) {
1952 if (errbuf && errsz > 0)
1953 snprintf(errbuf, errsz, "verify init failed");
1954 goto cleanup;
1955 }
1956 if (X509_verify_cert(store_ctx) == 1) {
1957 chain_ok = 1;
1958 } else {
1959 int verr = X509_STORE_CTX_get_error(store_ctx);
1960 if (errbuf && errsz > 0)
1961 snprintf(errbuf, errsz, "%s", X509_verify_cert_error_string(verr));
1962 }
1963 if (expected_host && expected_host[0]) {
1964 if (X509_check_host(cert, expected_host, 0, 0, NULL) != 1) {
1965 host_ok = 0;
1966 if (errbuf && errsz > 0 && chain_ok)
1967 snprintf(errbuf, errsz, "hostname mismatch for %s", expected_host);
1968 }
1969 }
1970 result = (chain_ok && host_ok) ? TRUE : FALSE;
1971
1972cleanup:
1973 if (store_ctx)
1974 X509_STORE_CTX_free(store_ctx);
1975 if (store)
1976 X509_STORE_free(store);
1977 X509_free(cert);
1978 return result;
1979} /* netw_ssl_get_verify_result */
1980
1988int netw_ssl_set_client_cert(NETWORK* netw, const char* cert_file, const char* key_file) {
1989 __n_assert(netw, return FALSE);
1990 __n_assert(netw->ctx, n_log(LOG_ERR, "SSL context not initialized"); return FALSE);
1991 __n_assert(cert_file, return FALSE);
1992
1993 if (SSL_CTX_use_certificate_file(netw->ctx, cert_file, SSL_FILETYPE_PEM) != 1) {
1994 n_log(LOG_ERR, "Failed to load client certificate from %s", cert_file);
1995 _netw_capture_error(netw, "Failed to load client certificate from %s", cert_file);
1997 return FALSE;
1998 }
1999
2000 const char* kf = key_file ? key_file : cert_file;
2001 if (SSL_CTX_use_PrivateKey_file(netw->ctx, kf, SSL_FILETYPE_PEM) != 1) {
2002 n_log(LOG_ERR, "Failed to load client private key from %s", kf);
2003 _netw_capture_error(netw, "Failed to load client private key from %s", kf);
2005 return FALSE;
2006 }
2007
2008 if (SSL_CTX_check_private_key(netw->ctx) != 1) {
2009 n_log(LOG_ERR, "Client certificate and private key do not match");
2010 _netw_capture_error(netw, "Client certificate and private key do not match");
2012 return FALSE;
2013 }
2014
2015 return TRUE;
2016} /* netw_ssl_set_client_cert */
2017
2018#endif /* HAVE_OPENSSL */
2019
2022#define NETW_CONNECT_ABORT_POLL_MS 100
2023
2025static int (*_netw_connect_abort_cb)(void*) = NULL;
2027static void* _netw_connect_abort_ctx = NULL;
2028
2044static int _netw_timed_connect(NETWORK* netw, SOCKET sock, struct addrinfo* rp, int connect_timeout_ms) {
2045 if (connect_timeout_ms <= 0) {
2046 return connect(sock, rp->ai_addr, (socklen_t)rp->ai_addrlen);
2047 }
2048
2049 if (netw_set_blocking(netw, 0) == FALSE) {
2050 /* could not switch modes: fall back to a blocking connect */
2051 return connect(sock, rp->ai_addr, (socklen_t)rp->ai_addrlen);
2052 }
2053
2054 int rc = connect(sock, rp->ai_addr, (socklen_t)rp->ai_addrlen);
2055 if (rc == 0) {
2057 return 0;
2058 }
2059
2060 int in_progress = 0;
2061#ifdef __windows__
2062 if (neterrno == WSAEWOULDBLOCK || neterrno == WSAEINPROGRESS) in_progress = 1;
2063#else
2064 if (neterrno == EINPROGRESS) in_progress = 1;
2065#endif
2066 if (!in_progress) {
2068 return -1;
2069 }
2070
2071 /* select() blocks the thread in the kernel (no busy-wait). If a signal
2072 interrupts it (EINTR) we retry with the remaining time so the total
2073 wait still honours connect_timeout_ms. select() only rewrites the
2074 timeval argument on Linux, so the remaining time is tracked
2075 explicitly with N_TIME for portability (Windows/MinGW included).
2076 When a connect-abort callback is registered the per-select wait is
2077 capped to NETW_CONNECT_ABORT_POLL_MS so the loop wakes periodically to
2078 poll it, letting a caller interrupt a connect to an unresponsive host
2079 without waiting out the whole connect timeout. */
2080 N_TIME timer;
2081 start_HiTimer(&timer);
2082 time_t remaining_us = (time_t)connect_timeout_ms * 1000;
2083 time_t poll_us = _netw_connect_abort_cb ? (time_t)NETW_CONNECT_ABORT_POLL_MS * 1000 : 0;
2084 int connected = 0;
2085 int aborted = 0;
2086 for (;;) {
2088 aborted = 1;
2089 break;
2090 }
2091 /* Watch both the write set and the exception set: a completed
2092 connect signals writefds, but on Winsock a *failed* connect
2093 signals exceptfds (not writefds). Watching only writefds there
2094 would miss the failure and wait out the whole timeout. The
2095 SO_ERROR check after the loop is the final arbiter on both. */
2096 fd_set wset, eset;
2097 FD_ZERO(&wset);
2098 FD_SET(sock, &wset);
2099 FD_ZERO(&eset);
2100 FD_SET(sock, &eset);
2101 time_t wait_us = remaining_us;
2102 if (poll_us > 0 && wait_us > poll_us)
2103 wait_us = poll_us;
2104 struct timeval tv;
2105 tv.tv_sec = (long)(wait_us / 1000000);
2106 tv.tv_usec = (long)(wait_us % 1000000);
2107
2108 int sel = select((int)sock + 1, NULL, &wset, &eset, &tv);
2109 if (sel > 0) {
2110 connected = 1; /* socket signalled; SO_ERROR check decides below */
2111 break;
2112 }
2113 if (sel == 0) {
2114 /* the select() wait expired: subtract it from the remaining budget.
2115 If this was only a poll slice (an abort callback is registered) and
2116 time is left, loop to re-check the callback; otherwise the connect
2117 timeout has been reached. */
2118 remaining_us -= get_usec(&timer);
2119 if (remaining_us <= 0)
2120 break; /* connect timed out */
2121 continue;
2122 }
2123 /* sel < 0 */
2124#ifdef __windows__
2125 int interrupted = (neterrno == WSAEINTR);
2126#else
2127 int interrupted = (neterrno == EINTR);
2128#endif
2129 if (!interrupted) {
2130 break; /* genuine select() error */
2131 }
2132 /* interrupted by a signal: subtract the time already spent and
2133 retry only if some of the budget is left. */
2134 remaining_us -= get_usec(&timer);
2135 if (remaining_us <= 0) {
2136 break; /* budget exhausted while interrupted */
2137 }
2138 }
2139
2140 if (aborted || !connected) {
2142 return -1;
2143 }
2144
2145 int so_error = 0;
2146 socklen_t slen = sizeof(so_error);
2147 if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&so_error, &slen) == -1 || so_error != 0) {
2149 return -1;
2150 }
2151
2153 return 0;
2154} /* _netw_timed_connect */
2155
2172void netw_set_connect_abort_cb(int (*cb)(void* ctx), void* ctx) {
2175} /* netw_set_connect_abort_cb(...) */
2176
2190int 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) {
2191 // kill compilation warning when there is no openssl
2192 (void)ssl_key_file;
2193 (void)ssl_cert_file;
2194 int error = 0, net_status = 0;
2195 char* errmsg = NULL;
2196
2197 /*do not work over an already used netw*/
2198 if ((*netw)) {
2199 n_log(LOG_ERR, "Unable to allocate (*netw), already existing. You must use empty NETWORK *structs.");
2200 return FALSE;
2201 }
2202
2203 /*creating array*/
2204 (*netw) = netw_new(send_list_limit, recv_list_limit);
2205 __n_assert(netw && (*netw), return FALSE);
2206
2207 /*checking WSA when under windows*/
2208 if (netw_init_wsa(1, 2, 2) == FALSE) {
2209 n_log(LOG_ERR, "Unable to load WSA dll's");
2211 return FALSE;
2212 }
2213
2214 /* choose ip version */
2215 if (ip_version == NETWORK_IPV4) {
2216 (*netw)->link.hints.ai_family = AF_INET; /* Allow IPv4 */
2217 } else if (ip_version == NETWORK_IPV6) {
2218 (*netw)->link.hints.ai_family = AF_INET6; /* Allow IPv6 */
2219 } else {
2220 /* NETWORK_ALL or unknown value */
2221 (*netw)->link.hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
2222 }
2223
2224 (*netw)->link.hints.ai_socktype = SOCK_STREAM;
2225 (*netw)->link.hints.ai_protocol = IPPROTO_TCP;
2226 (*netw)->link.hints.ai_flags = AI_PASSIVE;
2227 (*netw)->link.hints.ai_canonname = NULL;
2228 (*netw)->link.hints.ai_next = NULL;
2229
2230 /* Time the resolve and connect phases so callers can present a
2231 connect-timing breakdown. get_usec returns the delta since the
2232 previous call, so the first reads the DNS phase and the second
2233 reads the TCP-connect phase. */
2234 N_TIME connect_timer;
2235 start_HiTimer(&connect_timer);
2236
2237 /* Note: on some system, i.e Solaris, it WILL show leak in getaddrinfo.
2238 * Testing it inside a 1,100 loop showed not effect on the amount of leaked
2239 * memory */
2240 error = getaddrinfo(host, port, &(*netw)->link.hints, &(*netw)->link.rhost);
2241 if (error != 0) {
2242 _netw_capture_error(*netw, "Error when resolving %s:%s getaddrinfo: %s", host, port, gai_strerror(error));
2243 n_log(LOG_ERR, "Error when resolving %s:%s getaddrinfo: %s", host, port, gai_strerror(error));
2244 _netw_capture_connect_error("DNS resolution failed for %s:%s: %s", host, port, gai_strerror(error));
2246 return FALSE;
2247 }
2248 (*netw)->addr_infos_loaded = 1;
2249 (*netw)->connect_dns_usec = (long long)get_usec(&connect_timer);
2250 Malloc((*netw)->link.ip, char, 64);
2251 __n_assert((*netw)->link.ip, netw_close(netw); return FALSE);
2252
2253 /* 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. */
2254 struct addrinfo* rp = NULL;
2255 for (rp = (*netw)->link.rhost; rp != NULL; rp = rp->ai_next) {
2256 SOCKET sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
2257 if (sock == INVALID_SOCKET) {
2258 error = neterrno;
2259 errmsg = netstrerror(error);
2260 n_log(LOG_ERR, "Error while trying to make a socket: %s", _str(errmsg));
2261 FreeNoLog(errmsg);
2262 continue;
2263 }
2264
2265 (*netw)->link.sock = sock;
2266
2267 net_status = _netw_timed_connect(*netw, sock, rp, connect_timeout_ms);
2268 if (net_status == -1) {
2269 error = neterrno;
2270 errmsg = netstrerror(error);
2271 n_log(LOG_INFO, "connecting to %s:%s : %s", host, port, _str(errmsg));
2272 FreeNoLog(errmsg);
2273 closesocket(sock);
2274 (*netw)->link.sock = INVALID_SOCKET;
2275 continue;
2276 } else {
2277 /*storing connected port and ip address*/
2278 if (!inet_ntop(rp->ai_family, get_in_addr(rp->ai_addr), (*netw)->link.ip, 64)) {
2279 error = neterrno;
2280 errmsg = netstrerror(error);
2281 n_log(LOG_ERR, "inet_ntop: %p , %s", rp, _str(errmsg));
2282 FreeNoLog(errmsg);
2283 }
2284 break; /* Success */
2285 }
2286 }
2287 if (rp == NULL) {
2288 /* No address succeeded */
2289 _netw_capture_error(*netw, "Couldn't connect to %s:%s : no address succeeded", host, port);
2290 n_log(LOG_ERR, "Couldn't connect to %s:%s : no address succeeded", host, port);
2291 _netw_capture_connect_error("TCP connect failed for %s:%s: no address succeeded", host, port);
2293 return FALSE;
2294 }
2295 (*netw)->connect_tcp_usec = (long long)get_usec(&connect_timer);
2296
2297 (*netw)->link.port = strdup(port);
2298 __n_assert((*netw)->link.port, netw_close(netw); return FALSE);
2299
2300 if (ssl_key_file && ssl_cert_file) {
2301#ifdef HAVE_OPENSSL
2302 if (netw_set_crypto((*netw), ssl_key_file, ssl_cert_file) == FALSE) {
2303 /* could not initialize SSL */
2304 n_log(LOG_ERR, "couldn't initialize SSL !");
2305 _netw_capture_error(*netw, "SSL initialization failed");
2307 return FALSE;
2308 }
2309
2310 (*netw)->ssl = SSL_new((*netw)->ctx);
2311 SSL_set_fd((*netw)->ssl, (int)(*netw)->link.sock);
2312
2313 // Perform SSL Handshake
2314 if (SSL_connect((*netw)->ssl) <= 0) {
2315 /* could not connect with SSL */
2316 n_log(LOG_ERR, "SSL Handshake error !");
2317 _netw_capture_error(*netw, "SSL handshake failed for %s:%s", (*netw)->link.ip, (*netw)->link.port);
2319 return FALSE;
2320 }
2321 n_log(LOG_DEBUG, "SSL-Connected to %s:%s", (*netw)->link.ip, (*netw)->link.port);
2322#else
2323 _netw_capture_error(*netw, "%s:%s trying to configure SSL but application was compiled without SSL support !", (*netw)->link.ip, (*netw)->link.port);
2324 n_log(LOG_ERR, "%s:%s trying to configure SSL but application was compiled without SSL support !", (*netw)->link.ip, (*netw)->link.port);
2325#endif
2326 } else {
2327 n_log(LOG_DEBUG, "Connected to %s:%s", (*netw)->link.ip, (*netw)->link.port);
2328 }
2329
2331
2332 return TRUE;
2333} /* netw_connect_ex_to(...)*/
2334
2347int 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) {
2348 return netw_connect_ex_to(netw, host, port, send_list_limit, recv_list_limit, ip_version, ssl_key_file, ssl_cert_file, 0);
2349} /* netw_connect_ex(...)*/
2350
2359int netw_connect(NETWORK** netw, char* host, char* port, int ip_version) {
2360 n_log(LOG_INFO, "Trying to connect to %s : %s", _str(host), _str(port));
2361 return netw_connect_ex(netw, host, port, MAX_LIST_ITEMS, MAX_LIST_ITEMS, ip_version, NULL, NULL);
2362} /* netw_connect() */
2363
2373int netw_connect_to(NETWORK** netw, char* host, char* port, int ip_version, int connect_timeout_ms) {
2374 n_log(LOG_INFO, "Trying to connect to %s : %s (connect timeout %d ms)", _str(host), _str(port), connect_timeout_ms);
2375 return netw_connect_ex_to(netw, host, port, MAX_LIST_ITEMS, MAX_LIST_ITEMS, ip_version, NULL, NULL, connect_timeout_ms);
2376} /* netw_connect_to() */
2377
2378#ifdef HAVE_OPENSSL
2389int netw_ssl_connect(NETWORK** netw, char* host, char* port, int ip_version, char* ssl_key_file, char* ssl_cert_file) {
2390 n_log(LOG_INFO, "Trying to connect to %s : %s", _str(host), _str(port));
2391 return netw_connect_ex(netw, host, port, MAX_LIST_ITEMS, MAX_LIST_ITEMS, ip_version, ssl_key_file, ssl_cert_file);
2392} /* netw_connect() */
2393
2409int netw_ssl_connect_client_to(NETWORK** netw, char* host, char* port, int ip_version, int connect_timeout_ms) {
2410 __n_assert(netw, return FALSE);
2411 /* TCP connect first (no SSL params) */
2412 if (netw_connect_ex_to(netw, host, port, MAX_LIST_ITEMS, MAX_LIST_ITEMS, ip_version, NULL, NULL, connect_timeout_ms) == FALSE) {
2413 return FALSE;
2414 }
2415 if (netw_ssl_start_client(*netw) == FALSE) {
2417 return FALSE;
2418 }
2419 return TRUE;
2420} /* netw_ssl_connect_client_to */
2421
2439 __n_assert(netw, return FALSE);
2440 /* Create SSL context for client */
2442#if OPENSSL_VERSION_NUMBER >= 0x10100000L
2443 netw->method = TLS_client_method();
2444#else
2445 netw->method = TLSv1_2_client_method();
2446#endif
2447 netw->ctx = SSL_CTX_new(netw->method);
2448 if (!netw->ctx) {
2450 return FALSE;
2451 }
2452 /* PARTIAL_WRITE: a short SSL_write can be resumed from the
2453 * caller's buffer+offset (required by the reactor's
2454 * single-attempt send path); MOVING_WRITE_BUFFER: tolerate the
2455 * resume pointer not being the original one. No-op for the
2456 * blocking thread engine. */
2457 SSL_CTX_set_mode(netw->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
2458 /* Load default system CA paths */
2459 SSL_CTX_set_default_verify_paths(netw->ctx);
2460 /* Set send/recv to SSL variants */
2466 return TRUE;
2467} /* netw_ssl_start_client */
2468
2481int netw_ssl_connect_client(NETWORK** netw, char* host, char* port, int ip_version) {
2483} /* netw_ssl_connect_client */
2484
2496int netw_ssl_do_handshake(NETWORK* netw, const char* sni_hostname) {
2497 __n_assert(netw, return FALSE);
2498 __n_assert(netw->ctx, n_log(LOG_ERR, "netw_ssl_do_handshake: no SSL context"); return FALSE);
2499
2500 netw->ssl = SSL_new(netw->ctx);
2501 if (!netw->ssl) {
2502 _netw_capture_error(netw, "SSL_new failed");
2504 return FALSE;
2505 }
2506 SSL_set_fd(netw->ssl, (int)netw->link.sock);
2507 if (sni_hostname) {
2508 SSL_set_tlsext_host_name(netw->ssl, sni_hostname);
2509 }
2510 if (SSL_connect(netw->ssl) <= 0) {
2511 n_log(LOG_ERR, "SSL handshake failed");
2512 unsigned long err = ERR_peek_error();
2513 _netw_capture_error(netw, "SSL handshake failed for %s:%s: %s",
2515 err ? ERR_reason_error_string(err) : "unknown error");
2517 return FALSE;
2518 }
2519 n_log(LOG_DEBUG, "SSL handshake completed with %s:%s", _str(netw->link.ip), _str(netw->link.port));
2520 return TRUE;
2521} /* netw_ssl_do_handshake */
2522
2523#endif
2524
2532int netw_get_state(NETWORK* netw, uint32_t* state, int* thr_engine_status) {
2533 if (netw) {
2534 /* use eventbolt: netw_set() writes state/threaded_engine_status under eventbolt */
2535 pthread_mutex_lock(&netw->eventbolt);
2536 if (state)
2537 (*state) = netw_atomic_read_state(netw);
2538 if (thr_engine_status)
2539 (*thr_engine_status) = netw->threaded_engine_status;
2540 pthread_mutex_unlock(&netw->eventbolt);
2541 return TRUE;
2542 } else {
2543 n_log(LOG_ERR, "Can't get status of a NULL network");
2544 }
2545 return FALSE;
2546} /*netw_get_state() */
2547
2554int netw_set(NETWORK* netw, int flag) {
2555 __n_assert(netw, return FALSE);
2556 if (flag & NETW_EMPTY_SENDBUF) {
2557 pthread_mutex_lock(&netw->sendbolt);
2558 if (netw->send_buf)
2560 pthread_mutex_unlock(&netw->sendbolt);
2561 };
2562 if (flag & NETW_EMPTY_RECVBUF) {
2563 pthread_mutex_lock(&netw->recvbolt);
2564 if (netw->recv_buf)
2566 pthread_mutex_unlock(&netw->recvbolt);
2567 }
2568 if (flag & NETW_DESTROY_SENDBUF) {
2569 pthread_mutex_lock(&netw->sendbolt);
2570 if (netw->send_buf)
2572 pthread_mutex_unlock(&netw->sendbolt);
2573 };
2574 if (flag & NETW_DESTROY_RECVBUF) {
2575 pthread_mutex_lock(&netw->recvbolt);
2576 if (netw->recv_buf)
2578 pthread_mutex_unlock(&netw->recvbolt);
2579 }
2580 pthread_mutex_lock(&netw->eventbolt);
2581 if (flag & NETW_CLIENT) {
2583 }
2584 if (flag & NETW_SERVER) {
2586 }
2587 if (flag & NETW_RUN) {
2589 }
2590 if (flag & NETW_EXITED) {
2592 }
2593 if (flag & NETW_ERROR) {
2595 }
2596 if (flag & NETW_EXIT_ASKED) {
2598 }
2599 if (flag & NETW_THR_ENGINE_STARTED) {
2601 }
2602 if (flag & NETW_THR_ENGINE_STOPPED) {
2604 }
2605 pthread_mutex_unlock(&netw->eventbolt);
2606
2607 /* Only wake the send thread for state changes it actually cares
2608 * about. The inner loop in netw_send_func branches on NETW_ERROR,
2609 * NETW_EXITED, and NETW_EXIT_ASKED; every other flag (mode /
2610 * thread-engine-status / buffer maintenance) is invisible to it,
2611 * and posting unconditionally creates a spurious wakeup that,
2612 * paired with an empty send_buf, produces the classic
2613 * "offset-by-one semaphore" bug where every subsequent real
2614 * list_push leaves one extra post in the semaphore for the next
2615 * sem_wait to consume on a drained list. Avoiding the spurious
2616 * post here is the root cause fix and removes the need for a
2617 * defensive branch in the common path. */
2618 if (flag & (NETW_ERROR | NETW_EXITED | NETW_EXIT_ASKED)) {
2619 sem_post(&netw->send_blocker);
2620 }
2621
2622 return TRUE;
2623} /* netw_set(...) */
2624
2631int deplete_send_buffer(int fd, int timeout) {
2632#if defined(__linux__)
2633 int outstanding = 0;
2634 if (timeout <= 0) {
2635 return 0;
2636 }
2637 for (int it = 0; it < timeout; it += 100) {
2638 outstanding = 0;
2639 if (ioctl(fd, SIOCOUTQ, &outstanding) == -1) {
2640 int error = errno;
2641 n_log(LOG_ERR, "ioctl SIOCOUTQ returned -1: %s for socket %d", strerror(error), fd);
2642 return -1;
2643 }
2644 if (!outstanding) {
2645 break;
2646 }
2647 usleep(100000);
2648 }
2649 return outstanding;
2650#else
2651 (void)fd;
2652 (void)timeout;
2653 return 0;
2654#endif
2655}
2656
2663 __n_assert(netw && (*netw), return FALSE);
2664 uint32_t state = 0;
2665 int thr_engine_status = 0;
2666 int nb_running = 0;
2667
2668 /* Reactor close handshake. Synchronously wait for the reactor to
2669 * finish with this connection (drains pending sends best-effort,
2670 * shutdown(SHUT_WR) so the peer sees EOF, removes the fd from the
2671 * epoll set, publishes the close ack) before any of the existing
2672 * close logic runs. After this returns the reactor will never touch
2673 * the NETWORK again, so the rest of netw_close (and the final Free)
2674 * proceeds exactly like the thread-mode path.
2675 *
2676 * Keyed on reactor_registered, NOT the live reactor_mode: the reactor
2677 * clears reactor_mode partway through unregister (peer EOF / hard
2678 * error) and keeps touching the NETWORK for a few more lines before
2679 * acking. Gating on reactor_mode would let a clear that lands just
2680 * before this read send us straight to Free() while the reactor is
2681 * still mid-unregister (use-after-free). reactor_registered is set
2682 * once at register time and never cleared, so this is race-free.
2683 *
2684 * Compile-time gated on N_REACTOR_AVAILABLE so non-Linux/Android
2685 * builds (Windows MinGW, Solaris) don't pull in n_reactor.o just
2686 * to satisfy this symbol, the reactor is unavailable there anyway
2687 * and reactor_registered can never be set. */
2688#if N_REACTOR_AVAILABLE
2689 if (__atomic_load_n(&(*netw)->reactor_registered, __ATOMIC_ACQUIRE)) {
2691 }
2692#endif
2693
2694 if ((*netw)->deplete_queues_timeout > 0) {
2695 /* use iteration count to avoid integer overflow with large timeouts */
2696 int max_iterations = (*netw)->deplete_queues_timeout * 10; /* each iteration ~100ms */
2697 netw_get_state((*netw), &state, &thr_engine_status);
2698 if (thr_engine_status == NETW_THR_ENGINE_STARTED) {
2699 int it = 0;
2700 do {
2701 pthread_mutex_lock(&(*netw)->eventbolt);
2702 nb_running = (*netw)->nb_running_threads;
2703 pthread_mutex_unlock(&(*netw)->eventbolt);
2704 usleep(100000);
2705 it++;
2706 } while (nb_running > 0 && it < max_iterations);
2707 netw_get_state((*netw), &state, &thr_engine_status);
2708 if (it >= max_iterations && nb_running > 0) {
2709 n_log(LOG_ERR, "netw %d: %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);
2710 }
2711 }
2712 }
2713
2714 if ((*netw)->link.sock != INVALID_SOCKET) {
2715 int remaining = deplete_send_buffer((int)(*netw)->link.sock, (*netw)->deplete_socket_timeout);
2716 // cppcheck-suppress knownConditionTrueFalse ; remaining can be > 0 on Linux (SIOCOUTQ)
2717 if (remaining > 0) {
2718 n_log(LOG_ERR, "socket %d (%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);
2719 }
2720 }
2721
2722 list_foreach(node, (*netw)->pools) {
2723 NETWORK_POOL* pool = (NETWORK_POOL*)node->ptr;
2725 }
2726 list_destroy(&(*netw)->pools);
2727
2728 netw_get_state((*netw), &state, &thr_engine_status);
2729 if (thr_engine_status == NETW_THR_ENGINE_STARTED) {
2731 }
2732
2733 /* recompute nb_running after joining threads so the SSL_shutdown
2734 * decision reflects the actual post-join state */
2735 pthread_mutex_lock(&(*netw)->eventbolt);
2736 nb_running = (*netw)->nb_running_threads;
2737 pthread_mutex_unlock(&(*netw)->eventbolt);
2738
2739 if ((*netw)->link.sock != INVALID_SOCKET) {
2740#ifdef HAVE_OPENSSL
2741 if ((*netw)->crypto_algo == NETW_ENCRYPT_OPENSSL) {
2742 if ((*netw)->ssl) {
2743 /* only attempt SSL_shutdown if threads have fully stopped,
2744 * otherwise SSL operations may race with send/recv threads */
2745 if (nb_running == 0) {
2746 int shutdown_res = SSL_shutdown((*netw)->ssl);
2747 if (shutdown_res == 0) {
2748 /* try again to complete bidirectional shutdown;
2749 * peer may have already closed (common for HTTP),
2750 * so SYSCALL/SSL errors are non-fatal here */
2751 shutdown_res = SSL_shutdown((*netw)->ssl);
2752 }
2753 if (shutdown_res < 0) {
2754 int err = SSL_get_error((*netw)->ssl, shutdown_res);
2755 if (err != SSL_ERROR_SYSCALL && err != SSL_ERROR_SSL) {
2756 n_log(LOG_ERR, "SSL_shutdown() failed: %d", err);
2757 } else {
2758 n_log(LOG_DEBUG, "SSL_shutdown() peer already closed: %d", err);
2759 }
2760 }
2761 } else {
2762 n_log(LOG_WARNING, "netw %d: forcing bidirectional SSL_shutdown with %d threads still running", (*netw)->link.sock, nb_running);
2763 int shutdown_res = SSL_shutdown((*netw)->ssl);
2764 if (shutdown_res == 0) {
2765 /* send close_notify and wait for peer's close_notify */
2766 shutdown_res = SSL_shutdown((*netw)->ssl);
2767 }
2768 if (shutdown_res < 0) {
2769 int err = SSL_get_error((*netw)->ssl, shutdown_res);
2770 n_log(LOG_ERR, "netw %d: SSL_shutdown() failed with %d threads still running: %d", (*netw)->link.sock, nb_running, err);
2771 }
2772 }
2773 SSL_free((*netw)->ssl);
2774 } else {
2775 if ((*netw)->mode != NETW_SERVER) {
2776 n_log(LOG_ERR, "SSL handle of socket %d was already NULL", (*netw)->link.sock);
2777 } else {
2778 n_log(LOG_DEBUG, "listening socket %d has no SSL handle (expected)", (*netw)->link.sock);
2779 }
2780 }
2781 }
2782#endif
2783
2784 /* inform peer that we have finished */
2785 shutdown((*netw)->link.sock, SHUT_WR);
2786
2787 if ((*netw)->wait_close_timeout > 0) {
2788 /* wait for fin ack using select() to avoid busy-waiting.
2789 * Use iteration count to avoid integer overflow. */
2790 char buffer[4096] = "";
2791 int max_iters = (*netw)->wait_close_timeout * 10; /* each iteration ~100ms */
2792 for (int it = 0; it < max_iters; it++) {
2793 fd_set rfds;
2794 struct timeval tv;
2795 FD_ZERO(&rfds);
2796#pragma GCC diagnostic push
2797#pragma GCC diagnostic ignored "-Wsign-conversion"
2798 FD_SET((*netw)->link.sock, &rfds);
2799#pragma GCC diagnostic pop
2800 tv.tv_sec = 0;
2801 tv.tv_usec = 100000; /* 100ms */
2802 int sel = select((int)(*netw)->link.sock + 1, &rfds, NULL, NULL, &tv);
2803 if (sel > 0) {
2804 ssize_t res = recv((*netw)->link.sock, buffer, 4096, NETFLAGS);
2805 if (!res)
2806 break;
2807 if (res < 0) {
2808 int error = neterrno;
2809 if (error != ENOTCONN && error != EINTR && error != ECONNRESET
2810#ifdef __windows__
2811 && error != WSAENOTCONN && error != WSAECONNRESET && error != WSAESHUTDOWN && error != WSAEWOULDBLOCK && error != WSAEINTR
2812#endif
2813 ) {
2814 char* errmsg = netstrerror(error);
2815 n_log(LOG_ERR, "read returned error %d when closing socket %d (%s:%s): %s", error, (*netw)->link.sock, _str((*netw)->link.ip), (*netw)->link.port, _str(errmsg));
2816 FreeNoLog(errmsg);
2817 } else {
2818 n_log(LOG_DEBUG, "wait close: connection gracefully closed on socket %d (%s:%s)", (*netw)->link.sock, _str((*netw)->link.ip), (*netw)->link.port);
2819 }
2820 break;
2821 }
2822 } else if (sel < 0) {
2823 /* select error, bail out */
2824 int error = neterrno;
2825 if (error != EINTR
2826#ifdef __windows__
2827 && error != WSAEINTR
2828#endif
2829 ) {
2830 char* errmsg = netstrerror(error);
2831 n_log(LOG_ERR, "select() error on socket %d during close: %s", (*netw)->link.sock, _str(errmsg));
2832 FreeNoLog(errmsg);
2833 break;
2834 }
2835 }
2836 /* sel == 0: timeout, continue waiting */
2837 }
2838 }
2839
2840 /* effectively close socket */
2841 closesocket((*netw)->link.sock);
2842
2843#ifdef HAVE_OPENSSL
2844 /* clean openssl state. Accepted server-side SSL connections borrow
2845 * ctx from the listener (their ctx is NULL by design), so a NULL
2846 * ctx here is not an error, only the owner frees it. */
2847 if ((*netw)->crypto_algo == NETW_ENCRYPT_OPENSSL && (*netw)->ctx) {
2848 SSL_CTX_free((*netw)->ctx);
2849 }
2850 n_log(LOG_DEBUG, "socket %d closed", (*netw)->link.sock);
2851 FreeNoLog((*netw)->key);
2852 FreeNoLog((*netw)->certificate);
2853#endif
2854 }
2855
2856 FreeNoLog((*netw)->link.ip);
2857 FreeNoLog((*netw)->link.port);
2858
2859 if ((*netw)->link.rhost) {
2860 freeaddrinfo((*netw)->link.rhost);
2861 }
2862
2863 /*list freeing*/
2865
2866 pthread_mutex_destroy(&(*netw)->recvbolt);
2867 pthread_mutex_destroy(&(*netw)->sendbolt);
2868 pthread_mutex_destroy(&(*netw)->eventbolt);
2869 sem_destroy(&(*netw)->send_blocker);
2870
2871 Free((*netw));
2872
2873 return TRUE;
2874} /* netw_close(...)*/
2875
2885int netw_make_listening(NETWORK** netw, char* addr, char* port, int nbpending, int ip_version) {
2886 __n_assert(port, return FALSE);
2887
2888 int error = 0;
2889 char* errmsg = NULL;
2890
2891 if (*netw) {
2892 n_log(LOG_ERR, "Cannot use an allocated network. Please pass a NULL network to modify");
2893 return FALSE;
2894 }
2895
2896 /*checking WSA when under windows*/
2897 if (netw_init_wsa(1, 2, 2) == FALSE) {
2898 n_log(LOG_ERR, "Unable to load WSA dll's");
2899 return FALSE;
2900 }
2901
2903 __n_assert((*netw), return FALSE);
2904 (*netw)->link.port = strdup(port);
2905 /*creating array*/
2906 if (ip_version == NETWORK_IPV4) {
2907 (*netw)->link.hints.ai_family = AF_INET; /* Allow IPv4 */
2908 } else if (ip_version == NETWORK_IPV6) {
2909 (*netw)->link.hints.ai_family = AF_INET6; /* Allow IPv6 */
2910 } else {
2911 /* NETWORK_ALL or unknown value */
2912 (*netw)->link.hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
2913 }
2914 if (!addr) {
2915 (*netw)->link.hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
2916 }
2917 (*netw)->link.hints.ai_socktype = SOCK_STREAM;
2918 (*netw)->link.hints.ai_protocol = IPPROTO_TCP;
2919 (*netw)->link.hints.ai_canonname = NULL;
2920 (*netw)->link.hints.ai_next = NULL;
2921
2922 error = getaddrinfo(addr, port, &(*netw)->link.hints, &(*netw)->link.rhost);
2923 if (error != 0) {
2924 _netw_capture_error(*netw, "Error when resolving %s:%s getaddrinfo: %s", _str(addr), port, gai_strerror(error));
2925 n_log(LOG_ERR, "Error when resolving %s:%s getaddrinfo: %s", _str(addr), port, gai_strerror(error));
2927 return FALSE;
2928 }
2929 (*netw)->addr_infos_loaded = 1;
2930
2931 /* getaddrinfo() returns a list of address structures.
2932 Try each address until we successfully connect(2).
2933 If socket(2) (or connect(2)) fails, we (close the socket
2934 and) try the next address. */
2935 struct addrinfo* rp = NULL;
2936 for (rp = (*netw)->link.rhost; rp != NULL; rp = rp->ai_next) {
2937 (*netw)->link.sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
2938 if ((*netw)->link.sock == INVALID_SOCKET) {
2939 error = neterrno;
2940 errmsg = netstrerror(error);
2941 n_log(LOG_ERR, "Error while trying to make a socket: %s", _str(errmsg));
2942 FreeNoLog(errmsg);
2943 continue;
2944 }
2945 netw_setsockopt((*netw), SO_REUSEADDR, 1);
2946 // netw_setsockopt( (*netw), SO_REUSEPORT, 1 );
2947 if (bind((*netw)->link.sock, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0) {
2948 char* ip = NULL;
2949 Malloc(ip, char, 64);
2950 if (!ip) {
2951 n_log(LOG_ERR, "Error allocating 64 bytes for ip");
2953 return FALSE;
2954 }
2955 if (!inet_ntop(rp->ai_family, get_in_addr(rp->ai_addr), ip, 64)) {
2956 error = neterrno;
2957 errmsg = netstrerror(error);
2958 n_log(LOG_ERR, "inet_ntop: %p , %s", (*netw)->link.raddr, _str(errmsg));
2959 FreeNoLog(errmsg);
2960 }
2961 /*n_log( LOG_DEBUG, "Socket %d successfully binded to %s %s", (*netw) -> link . sock, _str( ip ) , _str( port ) );*/
2962 (*netw)->link.ip = ip;
2963 break; /* Success */
2964 }
2965 error = neterrno;
2966 errmsg = netstrerror(error);
2967 _netw_capture_error(*netw, "Error from bind() on port %s neterrno: %s", port, errmsg);
2968 n_log(LOG_ERR, "Error from bind() on port %s neterrno: %s", port, errmsg);
2969 FreeNoLog(errmsg);
2970 closesocket((*netw)->link.sock);
2971 }
2972 if (rp == NULL) {
2973 /* No address succeeded */
2974 _netw_capture_error(*netw, "Couldn't get a socket for listening on port %s", port);
2975 n_log(LOG_ERR, "Couldn't get a socket for listening on port %s", port);
2977 return FALSE;
2978 }
2979
2980 /* nb_pending connections*/
2981 (*netw)->nb_pending = nbpending;
2982 if (listen((*netw)->link.sock, (*netw)->nb_pending) != 0) {
2983 error = neterrno;
2984 errmsg = netstrerror(error);
2985 _netw_capture_error(*netw, "listen() failed on port %s: %s", port, _str(errmsg));
2986 n_log(LOG_ERR, "listen() failed on port %s: %s", port, _str(errmsg));
2987 FreeNoLog(errmsg);
2989 return FALSE;
2990 }
2991
2993
2994 return TRUE;
2995} /* netw_make_listening(...)*/
2996
3005int netw_bind_udp(NETWORK** netw, char* addr, char* port, int ip_version) {
3006 __n_assert(port, return FALSE);
3007
3008 int error = 0;
3009 char* errmsg = NULL;
3010
3011 if (*netw) {
3012 n_log(LOG_ERR, "Cannot use an allocated network. Please pass a NULL network to modify");
3013 return FALSE;
3014 }
3015
3016 /*checking WSA when under windows*/
3017 if (netw_init_wsa(1, 2, 2) == FALSE) {
3018 n_log(LOG_ERR, "Unable to load WSA dll's");
3019 return FALSE;
3020 }
3021
3023 __n_assert((*netw), return FALSE);
3024 (*netw)->link.port = strdup(port);
3025 (*netw)->transport_type = NETWORK_UDP;
3026
3027 /* choose ip version */
3028 if (ip_version == NETWORK_IPV4) {
3029 (*netw)->link.hints.ai_family = AF_INET;
3030 } else if (ip_version == NETWORK_IPV6) {
3031 (*netw)->link.hints.ai_family = AF_INET6;
3032 } else {
3033 (*netw)->link.hints.ai_family = AF_UNSPEC;
3034 }
3035 if (!addr) {
3036 (*netw)->link.hints.ai_flags = AI_PASSIVE;
3037 }
3038 (*netw)->link.hints.ai_socktype = SOCK_DGRAM;
3039 (*netw)->link.hints.ai_protocol = IPPROTO_UDP;
3040 (*netw)->link.hints.ai_canonname = NULL;
3041 (*netw)->link.hints.ai_next = NULL;
3042
3043 error = getaddrinfo(addr, port, &(*netw)->link.hints, &(*netw)->link.rhost);
3044 if (error != 0) {
3045 _netw_capture_error(*netw, "Error when resolving %s:%s getaddrinfo: %s", _str(addr), port, gai_strerror(error));
3046 n_log(LOG_ERR, "Error when resolving %s:%s getaddrinfo: %s", _str(addr), port, gai_strerror(error));
3048 return FALSE;
3049 }
3050 (*netw)->addr_infos_loaded = 1;
3051
3052 struct addrinfo* rp = NULL;
3053 for (rp = (*netw)->link.rhost; rp != NULL; rp = rp->ai_next) {
3054 (*netw)->link.sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
3055 if ((*netw)->link.sock == INVALID_SOCKET) {
3056 error = neterrno;
3057 errmsg = netstrerror(error);
3058 n_log(LOG_ERR, "Error while trying to make a UDP socket: %s", _str(errmsg));
3059 FreeNoLog(errmsg);
3060 continue;
3061 }
3062 netw_setsockopt((*netw), SO_REUSEADDR, 1);
3063 if (bind((*netw)->link.sock, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0) {
3064 char* ip = NULL;
3065 Malloc(ip, char, 64);
3066 if (!ip) {
3067 n_log(LOG_ERR, "Error allocating 64 bytes for ip");
3069 return FALSE;
3070 }
3071 if (!inet_ntop(rp->ai_family, get_in_addr(rp->ai_addr), ip, 64)) {
3072 error = neterrno;
3073 errmsg = netstrerror(error);
3074 n_log(LOG_ERR, "inet_ntop: %p , %s", (*netw)->link.raddr, _str(errmsg));
3075 FreeNoLog(errmsg);
3076 }
3077 (*netw)->link.ip = ip;
3078 break; /* Success */
3079 }
3080 error = neterrno;
3081 errmsg = netstrerror(error);
3082 _netw_capture_error(*netw, "Error from bind() on UDP port %s neterrno: %s", port, errmsg);
3083 n_log(LOG_ERR, "Error from bind() on UDP port %s neterrno: %s", port, errmsg);
3084 FreeNoLog(errmsg);
3085 closesocket((*netw)->link.sock);
3086 }
3087 if (rp == NULL) {
3088 _netw_capture_error(*netw, "Couldn't get a socket for UDP binding on port %s", port);
3089 n_log(LOG_ERR, "Couldn't get a socket for UDP binding on port %s", port);
3091 return FALSE;
3092 }
3093
3094 /* set send/recv to UDP functions */
3095 (*netw)->send_data = &send_udp_data;
3096 (*netw)->recv_data = &recv_udp_data;
3097
3099
3100 n_log(LOG_DEBUG, "UDP socket bound to %s:%s", _str((*netw)->link.ip), port);
3101
3102 return TRUE;
3103} /* netw_bind_udp(...)*/
3104
3113int netw_connect_udp(NETWORK** netw, char* host, char* port, int ip_version) {
3114 int error = 0;
3115 char* errmsg = NULL;
3116
3117 if ((*netw)) {
3118 n_log(LOG_ERR, "Unable to allocate (*netw), already existing. You must use empty NETWORK *structs.");
3119 return FALSE;
3120 }
3121
3123 __n_assert(netw && (*netw), return FALSE);
3124 (*netw)->transport_type = NETWORK_UDP;
3125
3126 if (netw_init_wsa(1, 2, 2) == FALSE) {
3127 n_log(LOG_ERR, "Unable to load WSA dll's");
3129 return FALSE;
3130 }
3131
3132 /* choose ip version */
3133 if (ip_version == NETWORK_IPV4) {
3134 (*netw)->link.hints.ai_family = AF_INET;
3135 } else if (ip_version == NETWORK_IPV6) {
3136 (*netw)->link.hints.ai_family = AF_INET6;
3137 } else {
3138 (*netw)->link.hints.ai_family = AF_UNSPEC;
3139 }
3140
3141 (*netw)->link.hints.ai_socktype = SOCK_DGRAM;
3142 (*netw)->link.hints.ai_protocol = IPPROTO_UDP;
3143 (*netw)->link.hints.ai_flags = AI_PASSIVE;
3144 (*netw)->link.hints.ai_canonname = NULL;
3145 (*netw)->link.hints.ai_next = NULL;
3146
3147 error = getaddrinfo(host, port, &(*netw)->link.hints, &(*netw)->link.rhost);
3148 if (error != 0) {
3149 _netw_capture_error(*netw, "Error when resolving %s:%s getaddrinfo: %s", host, port, gai_strerror(error));
3150 n_log(LOG_ERR, "Error when resolving %s:%s getaddrinfo: %s", host, port, gai_strerror(error));
3152 return FALSE;
3153 }
3154 (*netw)->addr_infos_loaded = 1;
3155 Malloc((*netw)->link.ip, char, 64);
3156 __n_assert((*netw)->link.ip, netw_close(netw); return FALSE);
3157
3158 struct addrinfo* rp = NULL;
3159 for (rp = (*netw)->link.rhost; rp != NULL; rp = rp->ai_next) {
3160 SOCKET sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
3161 if (sock == INVALID_SOCKET) {
3162 error = neterrno;
3163 errmsg = netstrerror(error);
3164 n_log(LOG_ERR, "Error while trying to make a UDP socket: %s", _str(errmsg));
3165 FreeNoLog(errmsg);
3166 continue;
3167 }
3168
3169 (*netw)->link.sock = sock;
3170
3171 /* connect() on UDP sets the default destination for send/recv */
3172 if (connect(sock, rp->ai_addr, (socklen_t)rp->ai_addrlen) == -1) {
3173 error = neterrno;
3174 errmsg = netstrerror(error);
3175 n_log(LOG_INFO, "UDP connecting to %s:%s : %s", host, port, _str(errmsg));
3176 FreeNoLog(errmsg);
3177 closesocket(sock);
3178 (*netw)->link.sock = INVALID_SOCKET;
3179 continue;
3180 } else {
3181 if (!inet_ntop(rp->ai_family, get_in_addr(rp->ai_addr), (*netw)->link.ip, 64)) {
3182 error = neterrno;
3183 errmsg = netstrerror(error);
3184 n_log(LOG_ERR, "inet_ntop: %p , %s", rp, _str(errmsg));
3185 FreeNoLog(errmsg);
3186 }
3187 break; /* Success */
3188 }
3189 }
3190 if (rp == NULL) {
3191 _netw_capture_error(*netw, "Couldn't connect UDP to %s:%s : no address succeeded", host, port);
3192 n_log(LOG_ERR, "Couldn't connect UDP to %s:%s : no address succeeded", host, port);
3194 return FALSE;
3195 }
3196
3197 (*netw)->link.port = strdup(port);
3198 __n_assert((*netw)->link.port, netw_close(netw); return FALSE);
3199
3200 /* set send/recv to UDP functions */
3201 (*netw)->send_data = &send_udp_data;
3202 (*netw)->recv_data = &recv_udp_data;
3203
3205
3206 n_log(LOG_DEBUG, "UDP-Connected to %s:%s", (*netw)->link.ip, (*netw)->link.port);
3207
3208 return TRUE;
3209} /* netw_connect_udp(...)*/
3210
3218ssize_t send_udp_data(void* netw, char* buf, uint32_t n) {
3220 __n_assert(buf, return NETW_SOCKET_ERROR);
3221
3222 SOCKET s = ((NETWORK*)netw)->link.sock;
3223 int error = 0;
3224 char* errmsg = NULL;
3225
3226 if (n == 0) {
3227 _netw_capture_error((NETWORK*)netw, "Send of 0 is unsupported.");
3228 n_log(LOG_ERR, "Send of 0 is unsupported.");
3229 return NETW_SOCKET_ERROR;
3230 }
3231
3232 ssize_t bs = send(s, buf, NETW_BUFLEN_CAST(n), NETFLAGS);
3233 error = neterrno;
3234 if (bs < 0) {
3235 if (error == ECONNRESET || error == ENOTCONN
3236#ifdef __windows__
3237 || error == WSAECONNRESET || error == WSAENOTCONN
3238#endif
3239 ) {
3240 n_log(LOG_DEBUG, "UDP socket %d disconnected !", s);
3242 }
3243 errmsg = netstrerror(error);
3244 _netw_capture_error((NETWORK*)netw, "UDP Socket %d send Error: %zd , %s", s, bs, _str(errmsg));
3245 n_log(LOG_ERR, "UDP Socket %d send Error: %zd , %s", s, bs, _str(errmsg));
3246 FreeNoLog(errmsg);
3247 return NETW_SOCKET_ERROR;
3248 }
3249 return bs;
3250} /*send_udp_data(...)*/
3251
3259ssize_t recv_udp_data(void* netw, char* buf, uint32_t n) {
3261 __n_assert(buf, return NETW_SOCKET_ERROR);
3262
3263 SOCKET s = ((NETWORK*)netw)->link.sock;
3264
3265 if (n == 0) {
3266 _netw_capture_error((NETWORK*)netw, "Recv of 0 is unsupported.");
3267 n_log(LOG_ERR, "Recv of 0 is unsupported.");
3268 return NETW_SOCKET_ERROR;
3269 }
3270
3271 ssize_t br = recv(s, buf, NETW_BUFLEN_CAST(n), NETFLAGS);
3272 int error = neterrno;
3273 if (br < 0) {
3274 char* errmsg = netstrerror(error);
3275 _netw_capture_error((NETWORK*)netw, "UDP socket %d recv returned %zd, error: %s", s, br, _str(errmsg));
3276 n_log(LOG_ERR, "UDP socket %d recv returned %zd, error: %s", s, br, _str(errmsg));
3277 FreeNoLog(errmsg);
3278 return NETW_SOCKET_ERROR;
3279 }
3280 if (br == 0) {
3281 n_log(LOG_DEBUG, "UDP socket %d: zero-length recv", s);
3282 }
3283 return br;
3284} /*recv_udp_data(...)*/
3285
3295ssize_t netw_udp_sendto(NETWORK* netw, char* buf, uint32_t n, struct sockaddr* dest_addr, socklen_t dest_len) {
3297 __n_assert(buf, return NETW_SOCKET_ERROR);
3298 __n_assert(dest_addr, return NETW_SOCKET_ERROR);
3299
3300 if (n == 0) {
3301 _netw_capture_error(netw, "Sendto of 0 is unsupported.");
3302 n_log(LOG_ERR, "Sendto of 0 is unsupported.");
3303 return NETW_SOCKET_ERROR;
3304 }
3305
3306 ssize_t bs = sendto(netw->link.sock, buf, NETW_BUFLEN_CAST(n), NETFLAGS, dest_addr, dest_len);
3307 if (bs < 0) {
3308 int error = neterrno;
3309 char* errmsg = netstrerror(error);
3310 _netw_capture_error(netw, "UDP socket %d sendto Error: %zd , %s", netw->link.sock, bs, _str(errmsg));
3311 n_log(LOG_ERR, "UDP socket %d sendto Error: %zd , %s", netw->link.sock, bs, _str(errmsg));
3312 FreeNoLog(errmsg);
3313 return NETW_SOCKET_ERROR;
3314 }
3315 return bs;
3316} /*netw_udp_sendto(...)*/
3317
3327ssize_t netw_udp_recvfrom(NETWORK* netw, char* buf, uint32_t n, struct sockaddr* src_addr, socklen_t* src_len) {
3329 __n_assert(buf, return NETW_SOCKET_ERROR);
3330
3331 if (n == 0) {
3332 _netw_capture_error(netw, "Recvfrom of 0 is unsupported.");
3333 n_log(LOG_ERR, "Recvfrom of 0 is unsupported.");
3334 return NETW_SOCKET_ERROR;
3335 }
3336
3337 ssize_t br = recvfrom(netw->link.sock, buf, NETW_BUFLEN_CAST(n), NETFLAGS, src_addr, src_len);
3338 if (br < 0) {
3339 int error = neterrno;
3340 char* errmsg = netstrerror(error);
3341 _netw_capture_error(netw, "UDP socket %d recvfrom returned %zd, error: %s", netw->link.sock, br, _str(errmsg));
3342 n_log(LOG_ERR, "UDP socket %d recvfrom returned %zd, error: %s", netw->link.sock, br, _str(errmsg));
3343 FreeNoLog(errmsg);
3344 return NETW_SOCKET_ERROR;
3345 }
3346 return br;
3347} /*netw_udp_recvfrom(...)*/
3348
3358NETWORK* netw_accept_from_ex(NETWORK* from, size_t send_list_limit, size_t recv_list_limit, int blocking, int* retval) {
3359 SOCKET tmp = INVALID_SOCKET;
3360 int error;
3361 char* errmsg = NULL;
3362
3363#if defined(__linux__) || defined(__sun) || defined(_AIX)
3364 socklen_t sin_size = 0;
3365#else
3366 int sin_size = 0;
3367#endif
3368
3369 NETWORK* netw = NULL;
3370
3371 /*checking WSA when under windows*/
3372 if (netw_init_wsa(1, 2, 2) == FALSE) {
3373 n_log(LOG_ERR, "Unable to load WSA dll's");
3374 return NULL;
3375 }
3376
3377 __n_assert(from, return NULL);
3378
3379 netw = netw_new(send_list_limit, recv_list_limit);
3380
3381 sin_size = sizeof(netw->link.raddr);
3382
3383 if (blocking > 0) {
3384 int secs = blocking / 1000;
3385 int usecs = (blocking % 1000) * 1000;
3386 struct timeval select_timeout = {secs, usecs};
3387
3388 fd_set accept_set;
3389 FD_ZERO(&accept_set);
3390#pragma GCC diagnostic push
3391#pragma GCC diagnostic ignored "-Wsign-conversion"
3392 FD_SET(from->link.sock, &accept_set);
3393#pragma GCC diagnostic pop
3394
3395 int ret = select((int)(from->link.sock + 1), &accept_set, NULL, NULL, &select_timeout);
3396 if (ret == -1) {
3397 error = neterrno;
3398 errmsg = netstrerror(error);
3399 if (retval != NULL)
3400 (*retval) = error;
3401 n_log(LOG_DEBUG, "error on select with timeout %ds (%d.%ds), neterrno: %s", blocking, secs, usecs, _str(errmsg));
3402 FreeNoLog(errmsg);
3403 netw_close(&netw);
3404 return NULL;
3405 } else if (ret == 0) {
3406 /* that one produce waaaay too much logs under a lot of cases */
3407 n_log(LOG_DEBUG, "No connection waiting on %d", from->link.sock);
3408 netw_close(&netw);
3409 return NULL;
3410 }
3411#pragma GCC diagnostic push
3412#pragma GCC diagnostic ignored "-Wsign-conversion"
3413 if (FD_ISSET(from->link.sock, &accept_set)) {
3414#pragma GCC diagnostic pop
3415 // n_log( LOG_DEBUG, "select accept call on %d", from -> link . sock );
3416 tmp = accept(from->link.sock, (struct sockaddr*)&netw->link.raddr, &sin_size);
3417 if (tmp == INVALID_SOCKET) {
3418 error = neterrno;
3419 errmsg = netstrerror(error);
3420 if (retval != NULL)
3421 (*retval) = error;
3422 n_log(LOG_DEBUG, "error accepting on %d, %s", netw->link.sock, _str(errmsg));
3423 FreeNoLog(errmsg);
3424 netw_close(&netw);
3425 return NULL;
3426 }
3427 } else {
3428 _netw_capture_error(from, "FD_ISSET returned false on %d", from->link.sock);
3429 n_log(LOG_ERR, "FD_ISSET returned false on %d", from->link.sock);
3430 netw_close(&netw);
3431 return NULL;
3432 }
3433 netw->link.is_blocking = 0;
3434 } else if (blocking == -1) {
3435 if (from->link.is_blocking != 0) {
3436 netw_set_blocking(from, 0);
3437 }
3438 tmp = accept(from->link.sock, (struct sockaddr*)&netw->link.raddr, &sin_size);
3439 error = neterrno;
3440 if (retval != NULL)
3441 (*retval) = error;
3442 if (tmp == INVALID_SOCKET) {
3443 if (error != EINTR && error != EAGAIN
3444#ifdef __windows__
3445 && error != WSAEWOULDBLOCK
3446#endif
3447 ) {
3448 char* errmsg_nb = netstrerror(error);
3449 // cppcheck-suppress unknownMacro ; SOCKET_SIZE_FORMAT is a platform-specific printf format macro
3450 n_log(LOG_ERR, "accept returned an invalid socket (" SOCKET_SIZE_FORMAT "), neterrno: %s", tmp, _str(errmsg_nb));
3451 FreeNoLog(errmsg_nb);
3452 }
3453 netw_close(&netw);
3454 return NULL;
3455 }
3456 netw->link.is_blocking = 0;
3457 } else {
3458 if (from->link.is_blocking == 0) {
3459 netw_set_blocking(from, 1);
3460 n_log(LOG_DEBUG, "(default) blocking accept call on socket %d", from->link.sock);
3461 }
3462 tmp = accept(from->link.sock, (struct sockaddr*)&netw->link.raddr, &sin_size);
3463 if (tmp == INVALID_SOCKET) {
3464 error = neterrno;
3465 errmsg = netstrerror(error);
3466 if (retval != NULL)
3467 (*retval) = error;
3468 n_log(LOG_DEBUG, "error accepting on socket %d, %s", netw->link.sock, _str(errmsg));
3469 FreeNoLog(errmsg);
3470 netw_close(&netw);
3471 return NULL;
3472 }
3473 netw->link.is_blocking = 1;
3474 }
3475 netw->link.sock = tmp;
3476
3477 /* On Windows and Solaris, the accepted socket inherits the non-blocking
3478 * mode from the listening socket. The send/recv engine expects a
3479 * blocking socket, so force it to blocking here. On Linux this is a
3480 * harmless no-op since accept() always returns a blocking socket. */
3482 netw->link.is_blocking = 1;
3483
3484 netw->link.port = strdup(from->link.port);
3485 Malloc(netw->link.ip, char, 64);
3486 if (!netw->link.ip) {
3487 n_log(LOG_ERR, "Error allocating 64 bytes for ip");
3488 netw_close(&netw);
3489 return NULL;
3490 }
3491 if (!inet_ntop(netw->link.raddr.ss_family, get_in_addr(((struct sockaddr*)&netw->link.raddr)), netw->link.ip, 64)) {
3492 error = neterrno;
3493 errmsg = netstrerror(error);
3494 n_log(LOG_ERR, "inet_ntop: %p , %s", netw->link.raddr, _str(errmsg));
3495 FreeNoLog(errmsg);
3496 netw_close(&netw);
3497 return NULL;
3498 }
3499
3500 netw_setsockopt(netw, SO_REUSEADDR, 1);
3501 // netw_setsockopt( netw, SO_REUSEPORT, 1 );
3502 netw_setsockopt(netw, SO_KEEPALIVE, 1);
3504 // netw_set_blocking(netw, 1);
3505
3506 n_log(LOG_DEBUG, "Connection accepted from %s:%s socket %d", netw->link.ip, netw->link.port, netw->link.sock);
3507
3508#ifdef HAVE_OPENSSL
3509 if (from->crypto_algo == NETW_ENCRYPT_OPENSSL) {
3510 netw->ssl = SSL_new(from->ctx);
3511 SSL_set_fd(netw->ssl, (int)netw->link.sock);
3512
3517 /* mark accepted netw as SSL so netw_close() runs SSL_free on its ssl;
3518 * ctx stays NULL, it is borrowed from the listener and freed there */
3520
3521 if (SSL_accept(netw->ssl) <= 0) {
3522 error = errno;
3523 _netw_capture_error(netw, "SSL error on %d", netw->link.sock);
3524 n_log(LOG_ERR, "SSL error on %d", netw->link.sock);
3525 if (retval != NULL)
3526 (*retval) = error;
3528 netw_close(&netw);
3529 return NULL;
3530 } else {
3531 n_log(LOG_DEBUG, " socket %d: SSL connection established", netw->link.sock);
3532 }
3533 }
3534#endif
3535
3536 return netw;
3537} /* netw_accept_from_ex(...) */
3538
3545 return netw_accept_from_ex(from, MAX_LIST_ITEMS, MAX_LIST_ITEMS, 0, NULL);
3546} /* network_accept_from( ... ) */
3547
3555 return netw_accept_from_ex(from, MAX_LIST_ITEMS, MAX_LIST_ITEMS, blocking, NULL);
3556} /* network_accept_from( ... ) */
3557
3564/* Process-wide byte counters. See netw_bytes_stats_get in
3565 * n_network_msg.c. Advisory telemetry; no atomic guarantee. */
3566long long g_netw_bytes_sent = 0;
3567long long g_netw_bytes_recv = 0;
3568
3570 __n_assert(netw, return FALSE);
3571 __n_assert(msg, return FALSE);
3572 __n_assert(msg->data, return FALSE);
3573
3574 if (msg->length == 0) {
3575 _netw_capture_error(netw, "Empty messages are not supported. msg(%p)->length=%zu", msg, msg->length);
3576 n_log(LOG_ERR, "Empty messages are not supported. msg(%p)->length=%zu", msg, msg->length);
3577 return FALSE;
3578 }
3579
3580 /* Capture msg->written for the byte counter before the push:
3581 * list_push transfers ownership to send_buf with free_nstr_ptr
3582 * as the destructor, so once we release sendbolt the consumer
3583 * (send thread or reactor) can shift, send, and free `msg`,
3584 * making any post-unlock `msg->written` read a use-after-free.
3585 * The race is mostly latent in thread mode (send thread blocks
3586 * on the syscall before freeing) but the reactor consumes within
3587 * microseconds and TSan flags it consistently. */
3588 long long bytes_for_counter = (long long)msg->written;
3589
3590 pthread_mutex_lock(&netw->sendbolt);
3591
3592 if (list_push(netw->send_buf, msg, free_nstr_ptr) == FALSE) {
3593 pthread_mutex_unlock(&netw->sendbolt);
3594 return FALSE;
3595 }
3596
3597 pthread_mutex_unlock(&netw->sendbolt);
3598
3599 /* Reactor mode: wake the reactor's epoll_wait via its
3600 * wake-eventfd so it picks up the new send_buf entry. The
3601 * thread engine's sem_post path stays as the default; the wake
3602 * call is a single non-blocking eventfd write when reactor_mode
3603 * is set. Forward declared in n_reactor.h to avoid a circular
3604 * include with this header, the function pointer dispatch
3605 * happens through netw->reactor_handle.
3606 *
3607 * Compile-time gated on N_REACTOR_AVAILABLE: on Windows / Solaris
3608 * the reactor is unavailable, reactor_mode is always 0, and we
3609 * never want this TU to reference n_reactor_notify_send (which
3610 * would force linking n_reactor.o for a code path that can never
3611 * fire). The unconditional sem_post fallback matches the
3612 * non-reactor branch of the gated form. */
3613#if N_REACTOR_AVAILABLE
3615 extern void n_reactor_notify_send(NETWORK*);
3617 } else {
3618 sem_post(&netw->send_blocker);
3619 }
3620#else
3621 sem_post(&netw->send_blocker);
3622#endif
3623
3624 g_netw_bytes_sent += bytes_for_counter;
3625 return TRUE;
3626} /* netw_add_msg(...) */
3627
3635int netw_add_msg_ex(NETWORK* netw, char* str, unsigned int length) {
3636 __n_assert(netw, return FALSE);
3637 __n_assert(str, return FALSE);
3638 if (length == 0) {
3639 return FALSE;
3640 }
3641
3642 N_STR* nstr = NULL;
3643 Malloc(nstr, N_STR, 1);
3644 __n_assert(nstr, return FALSE);
3645
3646 nstr->data = str;
3647 nstr->written = nstr->length = length;
3648
3649 pthread_mutex_lock(&netw->sendbolt);
3650 if (list_push(netw->send_buf, nstr, free_nstr_ptr) == FALSE) {
3651 pthread_mutex_unlock(&netw->sendbolt);
3652 return FALSE;
3653 }
3654 pthread_mutex_unlock(&netw->sendbolt);
3655
3656 sem_post(&netw->send_blocker);
3657
3658 return TRUE;
3659} /* netw_add_msg_ex(...) */
3660
3667 N_STR* ptr = NULL;
3668
3669 __n_assert(netw, return NULL);
3670
3671 pthread_mutex_lock(&netw->recvbolt);
3672
3673 ptr = list_shift(netw->recv_buf, N_STR);
3674
3675 pthread_mutex_unlock(&netw->recvbolt);
3676
3677 if (ptr) g_netw_bytes_recv += (long long)ptr->written;
3678 return ptr;
3679} /* netw_get_msg(...)*/
3680
3688N_STR* netw_wait_msg(NETWORK* netw, unsigned int refresh, size_t timeout) {
3689 size_t timed = 0;
3690 unsigned int secs = 0;
3691 unsigned int usecs = 0;
3692
3693 __n_assert(netw, return NULL);
3694
3695 usecs = refresh;
3696 if (refresh > 999999) {
3697 secs = refresh / 1000000;
3698 usecs = refresh % 1000000;
3699 }
3700
3701 if (timeout > 0)
3702 timed = timeout;
3703
3704 n_log(LOG_DEBUG, "wait from socket %d, refresh: %zu usec (%zu secs, %zu usecs), timeout %zu usec", netw->link.sock, refresh, secs, usecs, timeout);
3705 uint32_t state = NETW_RUN;
3706 int thr_state = 0;
3707 do {
3708 N_STR* nstrptr = netw_get_msg(netw);
3709 if (nstrptr)
3710 return nstrptr;
3711
3712 if (timeout > 0) {
3713 if (timed >= refresh)
3714 timed -= refresh;
3715 if (timed == 0 || timed < refresh) {
3716 _netw_capture_error(netw, "netw %d, status: %s (%" PRIu32 "), timeouted after waiting %zu msecs", netw->link.sock, N_ENUM_ENTRY(__netw_code_type, toString)(state), state, timeout);
3717 n_log(LOG_ERR, "netw %d, status: %s (%" PRIu32 "), timeouted after waiting %zu msecs", netw->link.sock, N_ENUM_ENTRY(__netw_code_type, toString)(state), state, timeout);
3718 return NULL;
3719 }
3720 }
3721 if (secs > 0)
3722 sleep(secs);
3723 if (usecs > 0)
3724 u_sleep(usecs);
3725
3726 netw_get_state(netw, &state, &thr_state);
3727 } while (state != NETW_EXITED && state != NETW_ERROR);
3728
3729 _netw_capture_error(netw, "got no answer and netw %d is no more running, state: %s (%" PRIu32 ")", netw->link.sock, N_ENUM_ENTRY(__netw_code_type, toString)(state), state);
3730 n_log(LOG_ERR, "got no answer and netw %d is no more running, state: %s (%" PRIu32 ")", netw->link.sock, N_ENUM_ENTRY(__netw_code_type, toString)(state), state);
3731
3732 return NULL;
3733} /* netw_wait_msg(...) */
3734
3741 __n_assert(netw, return FALSE);
3742
3743 pthread_mutex_lock(&netw->eventbolt);
3745 _netw_capture_error(netw, "THR Engine already started for network %p (%s)", netw, _str(netw->link.ip));
3746 n_log(LOG_ERR, "THR Engine already started for network %p (%s)", netw, _str(netw->link.ip));
3747 pthread_mutex_unlock(&netw->eventbolt);
3748 return FALSE;
3749 }
3750
3751 if (pthread_create(&netw->recv_thr, NULL, netw_recv_func, (void*)netw) != 0) {
3752 _netw_capture_error(netw, "Unable to create recv_thread for network %p (%s)", netw, _str(netw->link.ip));
3753 n_log(LOG_ERR, "Unable to create recv_thread for network %p (%s)", netw, _str(netw->link.ip));
3754 pthread_mutex_unlock(&netw->eventbolt);
3755 return FALSE;
3756 }
3758 if (pthread_create(&netw->send_thr, NULL, netw_send_func, (void*)netw) != 0) {
3759 _netw_capture_error(netw, "Unable to create send_thread for network %p (%s)", netw, _str(netw->link.ip));
3760 n_log(LOG_ERR, "Unable to create send_thread for network %p (%s)", netw, _str(netw->link.ip));
3761 pthread_cancel(netw->recv_thr);
3762 pthread_join(netw->recv_thr, NULL);
3764 pthread_mutex_unlock(&netw->eventbolt);
3765 return FALSE;
3766 }
3768
3770
3771 pthread_mutex_unlock(&netw->eventbolt);
3772
3773 return TRUE;
3774} /* netw_create_recv_thread(....) */
3775
3781void* netw_send_func(void* NET) {
3782 int DONE = 0;
3783
3784 ssize_t net_status = 0;
3785
3786 uint32_t state;
3787 uint32_t nboctet;
3788
3789 char nboct[5] = "";
3790
3791 N_STR* ptr = NULL;
3792
3793 NETWORK* netw = (NETWORK*)NET;
3794 __n_assert(netw, return NULL);
3795
3796 do {
3797 /* do not consume cpu for nothing, reduce delay */
3798 sem_wait(&netw->send_blocker);
3799 int message_sent = 0;
3800 while (message_sent == 0 && !DONE) {
3802 if (state & NETW_ERROR) {
3804 } else if (state & NETW_EXITED) {
3806 } else if (state & NETW_EXIT_ASKED) {
3808 /* sending state */
3809 nboctet = htonl(NETW_EXIT_ASKED);
3810 memcpy(nboct, &nboctet, sizeof(uint32_t));
3811 n_log(LOG_DEBUG, "%d Sending Quit !", netw->link.sock);
3812 net_status = netw->send_data(netw, nboct, sizeof(int32_t));
3813 /* Peer disconnect during QUIT send is the expected
3814 * end of the handshake; only a real socket error
3815 * deserves the DONE=4 error path. */
3816 if (net_status < 0 && net_status != NETW_SOCKET_DISCONNECTED)
3817 DONE = 4;
3818 n_log(LOG_DEBUG, "%d Quit sent!", netw->link.sock);
3819 } else {
3820 pthread_mutex_lock(&netw->sendbolt);
3821 ptr = list_shift(netw->send_buf, N_STR);
3822 pthread_mutex_unlock(&netw->sendbolt);
3823 if (ptr && ptr->length > 0 && ptr->data) {
3824 /* Headroom for the 8-byte state+length header so
3825 * frame_len below cannot overflow the uint32 the
3826 * send_data API takes. */
3827 if (ptr->written <= UINT_MAX - 2 * sizeof(uint32_t)) {
3828 n_log(LOG_DEBUG, "Sending ptr size %zu written %zu...", ptr->length, ptr->written);
3829
3830 /* Opportunistic compression. Gated on threshold
3831 * + ratio so small packets and incompressible
3832 * binary snapshots don't pay the codec cost.
3833 * Algorithm picked per-connection via
3834 * netw_set_compression_mode(); NONE skips the
3835 * entire attempt. */
3836 uint32_t pkt_state = state;
3839 N_STR* zipped = NULL;
3840 uint32_t flag_bit = 0;
3842 zipped = zip4_nstr(ptr);
3843 flag_bit = NETW_COMPRESSED_LZ4;
3844 } else {
3845 zipped = zip_nstr(ptr);
3846 flag_bit = NETW_COMPRESSED_ZLIB;
3847 }
3848 if (zipped && zipped->written > 0 &&
3849 zipped->written * 100u <=
3850 ptr->written * (100u - NETW_COMPRESS_MIN_RATIO)) {
3851 /* Good enough shrink, swap. */
3852 free_nstr(&ptr);
3853 ptr = zipped;
3854 pkt_state |= flag_bit;
3855 } else if (zipped) {
3856 free_nstr(&zipped);
3857 }
3858 }
3859
3860 /* Single-write framing. The frame
3861 * used to go out as three separate send_data
3862 * calls (4B state, 4B length, payload): three
3863 * syscalls, and three TLS records on crypto
3864 * sockets, per message, with the two 4-byte
3865 * writes interacting badly with Nagle +
3866 * delayed-ACK on small-message streams (game
3867 * inputs, snapshots). Build the contiguous
3868 * frame and hand the kernel one write.
3869 * Peer disconnect (NETW_SOCKET_DISCONNECTED)
3870 * during the send is a normal end-of-life: the
3871 * connection is over, nothing to log. Only a
3872 * real socket error trips the DONE=1 error
3873 * path so the tail logger emits LOG_ERR. */
3874 size_t frame_len = 2 * sizeof(uint32_t) + ptr->written;
3875 char* frame = NULL;
3876 Malloc(frame, char, frame_len);
3877 if (frame) {
3878 nboctet = htonl(pkt_state);
3879 memcpy(frame, &nboctet, sizeof(uint32_t));
3880 nboctet = htonl((uint32_t)ptr->written);
3881 memcpy(frame + sizeof(uint32_t), &nboctet, sizeof(uint32_t));
3882 memcpy(frame + 2 * sizeof(uint32_t), ptr->data, ptr->written);
3883 net_status = netw->send_data(netw, frame, (uint32_t)frame_len);
3884 if (net_status < 0)
3885 DONE = (net_status == NETW_SOCKET_DISCONNECTED) ? NETW_THR_EXIT_OK : 1;
3886 Free(frame);
3887 } else {
3888 /* OOM building the frame: drop the message
3889 * (stream alignment is preserved, nothing
3890 * was written) and keep the connection up,
3891 * mirroring the oversize-discard branch. */
3892 _netw_capture_error(netw, "could not allocate %zu byte send frame, packet dropped", frame_len);
3893 n_log(LOG_ERR, "could not allocate %zu byte send frame, packet dropped", frame_len);
3894 }
3897 }
3898 message_sent = 1;
3899 free_nstr(&ptr);
3900 } else {
3901 _netw_capture_error(netw, "discarded packet of size %zu which is greater than %" PRIu32, ptr->written, UINT_MAX);
3902 n_log(LOG_ERR, "discarded packet of size %zu which is greater than %" PRIu32, ptr->written, UINT_MAX);
3903 message_sent = 1;
3904 free_nstr(&ptr);
3905 }
3906 } else {
3907 /* Empty send_buf or malformed head entry: the
3908 * producer-consumer contract means sem_post was
3909 * called without a usable list_push, or a state
3910 * transition posted without a real message. Break
3911 * out of the inner loop and block on sem_wait
3912 * rather than spinning, spinning grabs sendbolt
3913 * at MHz rates and creates main-thread contention
3914 * in netw_add_msg. */
3915 if (ptr) free_nstr(&ptr);
3916 message_sent = 1;
3917 }
3918 }
3919 }
3920 } while (!DONE);
3921
3922 if (DONE == 1) {
3923 _netw_capture_error(netw, "Error when sending state %" PRIu32 " on socket %d (%s), network: %s", netw_atomic_read_state(netw), netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
3924 n_log(LOG_ERR, "Error when sending state %" PRIu32 " on socket %d (%s), network: %s", netw_atomic_read_state(netw), netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
3925 } else if (DONE == 2) {
3926 _netw_capture_error(netw, "Error when sending number of octet to socket %d (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
3927 n_log(LOG_ERR, "Error when sending number of octet to socket %d (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
3928 } else if (DONE == 3)
3929 n_log(LOG_DEBUG, "Error when sending data on socket %d (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
3930 else if (DONE == 4) {
3931 _netw_capture_error(netw, "Error when sending state QUIT on socket %d (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
3932 n_log(LOG_ERR, "Error when sending state QUIT on socket %d (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
3933 } else if (DONE == 5) {
3934 _netw_capture_error(netw, "Error when sending state QUIT number of octet (0) on socket %d (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
3935 n_log(LOG_ERR, "Error when sending state QUIT number of octet (0) on socket %d (%s), network: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
3936 }
3937
3938 if (DONE == NETW_THR_EXIT_OK) {
3939 n_log(LOG_DEBUG, "Socket %d: Sending thread exiting correctly", netw->link.sock);
3941 } else {
3942 _netw_capture_error(netw, "Socket %d (%s): Sending thread exiting with error %d !", netw->link.sock, _str(netw->link.ip), DONE);
3943 n_log(LOG_ERR, "Socket %d (%s): Sending thread exiting with error %d !", netw->link.sock, _str(netw->link.ip), DONE);
3945 }
3946
3947 pthread_mutex_lock(&netw->eventbolt);
3949 pthread_mutex_unlock(&netw->eventbolt);
3950
3951 pthread_exit(0);
3952
3953 /* suppress compiler warning */
3954#if !defined(__linux__)
3955 return NULL;
3956#endif
3957} /* netw_send_func(...) */
3958
3964void* netw_recv_func(void* NET) {
3965 int DONE = 0;
3966 ssize_t net_status = 0;
3967
3968 uint32_t nboctet;
3969 uint32_t tmpstate;
3970 uint32_t state;
3971
3972 char nboct[5] = "";
3973
3974 N_STR* recvdmsg = NULL;
3975
3976 NETWORK* netw = (NETWORK*)NET;
3977
3978 __n_assert(netw, return NULL);
3979
3980 do {
3982 if (state & NETW_EXIT_ASKED || state & NETW_EXITED) {
3984 }
3985 if (state & NETW_ERROR) {
3987 }
3988 if (!DONE) {
3989 n_log(LOG_DEBUG, "socket %d : waiting to receive status", netw->link.sock);
3990 /* receiving state */
3991 net_status = netw->recv_data(netw, nboct, sizeof(uint32_t));
3992 if (net_status < 0) {
3993 /* Peer disconnect (NETW_SOCKET_DISCONNECTED) is the
3994 * normal end of a connection, no log noise, exit
3995 * via the clean NETW_THR_EXIT_OK path so the
3996 * application observes NETW_EXIT_ASKED, not
3997 * NETW_ERROR. Only NETW_SOCKET_ERROR routes through
3998 * the DONE=N error tail logger. */
3999 DONE = (net_status == NETW_SOCKET_DISCONNECTED) ? NETW_THR_EXIT_OK : 1;
4000 } else {
4001 memcpy(&nboctet, nboct, sizeof(uint32_t));
4002 tmpstate = ntohl(nboctet);
4003 nboctet = tmpstate;
4004 /* Freeze the per-packet state word before the next
4005 * recv_data call reuses tmpstate for the payload
4006 * length, the NETW_COMPRESSED_* flag check below
4007 * reads pkt_state, not tmpstate. */
4008 uint32_t pkt_state = tmpstate;
4009 if (tmpstate == NETW_EXIT_ASKED) {
4010 n_log(LOG_DEBUG, "socket %d : receiving order to QUIT !", netw->link.sock);
4012 } else {
4013 n_log(LOG_DEBUG, "socket %d : waiting to receive next message nboctets", netw->link.sock);
4014 /* receiving nboctet */
4015 net_status = netw->recv_data(netw, nboct, sizeof(uint32_t));
4016 if (net_status < 0) {
4017 DONE = (net_status == NETW_SOCKET_DISCONNECTED) ? NETW_THR_EXIT_OK : 2;
4018 } else {
4019 memcpy(&nboctet, nboct, sizeof(uint32_t));
4020 tmpstate = ntohl(nboctet);
4021 nboctet = tmpstate;
4022
4023 Malloc(recvdmsg, N_STR, 1);
4024 if (!recvdmsg) {
4025 DONE = 3;
4026 } else {
4027 n_log(LOG_DEBUG, "socket %d : %" PRIu32 " octets to receive...", netw->link.sock, nboctet);
4028 Malloc(recvdmsg->data, char, nboctet + 1);
4029 if (!recvdmsg->data) {
4030 free_nstr(&recvdmsg);
4031 DONE = 4;
4032 } else {
4033 recvdmsg->length = nboctet + 1;
4034 recvdmsg->written = nboctet;
4035
4036 /* receiving the data itself */
4037 net_status = netw->recv_data(netw, recvdmsg->data, nboctet);
4038 if (net_status < 0) {
4039 free_nstr(&recvdmsg);
4040 DONE = (net_status == NETW_SOCKET_DISCONNECTED) ? NETW_THR_EXIT_OK : 5;
4041 } else {
4042 /* Inverse of the compression step on
4043 * the send side. Each NETW_COMPRESSED_*
4044 * bit identifies the codec the sender
4045 * chose; we decode regardless of our
4046 * own compress_mode so two peers
4047 * running different algorithms still
4048 * interop. */
4049 int want_zlib = (pkt_state & NETW_COMPRESSED_ZLIB) != 0;
4050 int want_lz4 = (pkt_state & NETW_COMPRESSED_LZ4) != 0;
4051 if (want_zlib || want_lz4) {
4052 recvdmsg->data[nboctet] = '\0';
4053 N_STR* plain = want_lz4
4054 ? unzip4_nstr(recvdmsg)
4055 : unzip_nstr(recvdmsg);
4056 if (plain) {
4057 free_nstr(&recvdmsg);
4058 recvdmsg = plain;
4059 } else {
4060 n_log(LOG_ERR,
4061 "socket %d : failed to decompress payload (%" PRIu32 " bytes, codec=%s); dropping",
4062 netw->link.sock, nboctet,
4063 want_lz4 ? "lz4" : "zlib");
4064 free_nstr(&recvdmsg);
4065 DONE = 7;
4066 }
4067 }
4068 if (!DONE) {
4069 pthread_mutex_lock(&netw->recvbolt);
4070 if (list_push(netw->recv_buf, recvdmsg, free_nstr_ptr) == FALSE)
4071 DONE = 6;
4072 pthread_mutex_unlock(&netw->recvbolt);
4073 n_log(LOG_DEBUG, "socket %d : %" PRIu32 " octets received !", netw->link.sock, nboctet);
4074 }
4075 } /* recv data */
4076 } /* recv data allocation */
4077 } /* recv struct allocation */
4078 } /* recv nb octet*/
4079 } /* exit asked */
4080 } /* recv state */
4081 } /* if( !done) */
4082 } while (!DONE);
4083
4084 if (DONE == 1) {
4085 _netw_capture_error(netw, "Error when receiving state from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4086 n_log(LOG_ERR, "Error when receiving state from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4087 } else if (DONE == 2) {
4088 _netw_capture_error(netw, "Error when receiving nboctet from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4089 n_log(LOG_ERR, "Error when receiving nboctet from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4090 } else if (DONE == 3) {
4091 _netw_capture_error(netw, "Error when receiving data from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4092 n_log(LOG_ERR, "Error when receiving data from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4093 } else if (DONE == 4) {
4094 _netw_capture_error(netw, "Error allocating received message struct from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4095 n_log(LOG_ERR, "Error allocating received message struct from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4096 } else if (DONE == 5) {
4097 _netw_capture_error(netw, "Error allocating received messages data array from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4098 n_log(LOG_ERR, "Error allocating received messages data array from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4099 } else if (DONE == 6) {
4100 _netw_capture_error(netw, "Error adding receved message from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4101 n_log(LOG_ERR, "Error adding receved message from socket %d (%s), net_status: %s", netw->link.sock, _str(netw->link.ip), (net_status == NETW_SOCKET_DISCONNECTED) ? "disconnected" : "socket error");
4102 }
4103
4104 if (DONE == NETW_THR_EXIT_OK) {
4105 n_log(LOG_DEBUG, "Socket %d (%s): Receive thread exiting correctly", netw->link.sock, _str(netw->link.ip));
4107 } else {
4108 _netw_capture_error(netw, "Socket %d (%s): Receive thread exiting with code %d !", netw->link.sock, _str(netw->link.ip), DONE);
4109 n_log(LOG_ERR, "Socket %d (%s): Receive thread exiting with code %d !", netw->link.sock, _str(netw->link.ip), DONE);
4111 }
4112
4113 pthread_mutex_lock(&netw->eventbolt);
4115 pthread_mutex_unlock(&netw->eventbolt);
4116
4117 pthread_exit(0);
4118
4119 /* suppress compiler warning */
4120#if !defined(__linux__)
4121 return NULL;
4122#endif
4123} /* netw_recv_func(...) */
4124
4131 __n_assert(netw, return FALSE);
4132 uint32_t state = 0;
4133 int thr_engine_status = 0;
4134
4135 n_log(LOG_DEBUG, "Network %d stop threads event", netw->link.sock);
4136
4137 netw_get_state(netw, &state, &thr_engine_status);
4138
4139 if (thr_engine_status != NETW_THR_ENGINE_STARTED) {
4140 _netw_capture_error(netw, "Thread engine status already stopped for network %p", netw);
4141 n_log(LOG_ERR, "Thread engine status already stopped for network %p", netw);
4142 return FALSE;
4143 }
4144
4146
4147 n_log(LOG_DEBUG, "Network %d waits for send threads to stop", netw->link.sock);
4148 for (int it = 0; it < 10; it++) {
4149 sem_post(&netw->send_blocker);
4150 usleep(1000);
4151 }
4152 pthread_join(netw->send_thr, NULL);
4153 n_log(LOG_DEBUG, "Network %d waits for recv threads to stop", netw->link.sock);
4154 pthread_join(netw->recv_thr, NULL);
4155
4157
4158 n_log(LOG_DEBUG, "Network %d threads Stopped !", netw->link.sock);
4159
4160 return TRUE;
4161} /* Stop_Network( ... ) */
4162
4170ssize_t send_data(void* netw, char* buf, uint32_t n) {
4172 __n_assert(buf, return NETW_SOCKET_ERROR);
4173
4174 SOCKET s = ((NETWORK*)netw)->link.sock;
4175 ssize_t bcount = 0; /* counts bytes sent */
4176 int error;
4177 char* errmsg = NULL;
4178
4179 if (n == 0) {
4180 _netw_capture_error((NETWORK*)netw, "Send of 0 is unsupported.");
4181 n_log(LOG_ERR, "Send of 0 is unsupported.");
4182 return NETW_SOCKET_ERROR;
4183 }
4184
4185 char* tmp_buf = buf;
4186 while (bcount < n) /* loop until all sent */
4187 {
4188 ssize_t bs = 0; /* bytes sent this pass */
4189
4190 NETW_CALL_RETRY(bs, send(s, tmp_buf, NETW_BUFLEN_CAST(n - bcount), NETFLAGS), NETW_MAX_RETRIES);
4191 error = neterrno;
4192
4193 if (bs > 0) {
4194 bcount += bs; /* increment byte counter */
4195 tmp_buf += bs; /* move buffer ptr for next send */
4196 } else if (error == ECONNRESET || error == ENOTCONN || error == EPIPE
4197#ifdef __windows__
4198 || error == WSAECONNRESET || error == WSAENOTCONN || error == WSAESHUTDOWN
4199#endif
4200 ) {
4201 n_log(LOG_DEBUG, "socket %d disconnected !", s);
4203 } else if (bs == -1) {
4204 /* real network error */
4205 errmsg = netstrerror(error);
4206 _netw_capture_error((NETWORK*)netw, "Socket %d send error: %zd, %s", s, bs, _str(errmsg));
4207 n_log(LOG_ERR, "Socket %d send error: %zd, %s", s, bs, _str(errmsg));
4208 FreeNoLog(errmsg);
4209 return NETW_SOCKET_ERROR;
4210 } else if (bs == -2) {
4211 /* EINTR/WOULDBLOCK retries exhausted */
4212 _netw_capture_error((NETWORK*)netw, "Socket %d : retry storm on send (%d retries)", s, NETW_MAX_RETRIES);
4213 n_log(LOG_ERR, "Socket %d : retry storm on send (%d retries)", s, NETW_MAX_RETRIES);
4214 return NETW_SOCKET_ERROR;
4215 } else if (bs == 0) {
4216 /* should never happen on send() */
4217 n_log(LOG_DEBUG, "socket %d : send returned 0", s);
4219 }
4220 }
4221 return bcount;
4222} /*send_data(...)*/
4223
4231ssize_t recv_data(void* netw, char* buf, uint32_t n) {
4233 __n_assert(buf, return NETW_SOCKET_ERROR);
4234
4235 SOCKET s = ((NETWORK*)netw)->link.sock;
4236 ssize_t bcount = 0; /* counts bytes read */
4237 int error;
4238 char* errmsg = NULL;
4239
4240#if defined(NETWORK_DISABLE_ZERO_LENGTH_RECV) && (NETWORK_DISABLE_ZERO_LENGTH_RECV == TRUE)
4241 if (n == 0) {
4242 _netw_capture_error((NETWORK*)netw, "Recv of 0 is unsupported.");
4243 n_log(LOG_ERR, "Recv of 0 is unsupported.");
4244 return NETW_SOCKET_ERROR;
4245 }
4246#endif
4247
4248 char* tmp_buf = buf;
4249 while (bcount < n) /* loop until all received */
4250 {
4251 ssize_t br = 0; /* bytes received this pass */
4252
4253 NETW_CALL_RETRY(br, recv(s, tmp_buf, NETW_BUFLEN_CAST(n - bcount), NETFLAGS), NETW_MAX_RETRIES);
4254 error = neterrno;
4255
4256 if (br > 0) {
4257 bcount += br; /* increment byte counter */
4258 tmp_buf += br; /* move buffer ptr for next recv */
4259 } else if (br == 0) {
4260 /* clean shutdown from peer */
4261 n_log(LOG_DEBUG, "socket %d : peer closed connection", s);
4263 } else if (error == ECONNRESET || error == ENOTCONN
4264#ifdef __windows__
4265 || error == WSAECONNRESET || error == WSAENOTCONN || error == WSAESHUTDOWN
4266#endif
4267 ) {
4268 /* connection reset or not connected */
4269 n_log(LOG_DEBUG, "socket %d disconnected !", s);
4271 } else if (br == -1) {
4272 /* real network error */
4273 errmsg = netstrerror(error);
4274 _netw_capture_error((NETWORK*)netw, "Socket %d recv error: %zd, %s", s, br, _str(errmsg));
4275 n_log(LOG_ERR, "Socket %d recv error: %zd, %s", s, br, _str(errmsg));
4276 FreeNoLog(errmsg);
4277 return NETW_SOCKET_ERROR;
4278 } else if (br == -2) {
4279 /* EINTR/WOULDBLOCK retries exhausted */
4280 _netw_capture_error((NETWORK*)netw, "Socket %d : retry storm on recv (%d retries)", s, NETW_MAX_RETRIES);
4281 n_log(LOG_ERR, "Socket %d : retry storm on recv (%d retries)", s, NETW_MAX_RETRIES);
4282 return NETW_SOCKET_ERROR;
4283 }
4284 }
4285 return bcount;
4286} /*recv_data(...)*/
4287
4298ssize_t send_data_once(void* netw, char* buf, uint32_t n) {
4300 __n_assert(buf, return NETW_SOCKET_ERROR);
4301
4302 SOCKET s = ((NETWORK*)netw)->link.sock;
4303 if (n == 0) {
4304 _netw_capture_error((NETWORK*)netw, "Send of 0 is unsupported.");
4305 n_log(LOG_ERR, "Send of 0 is unsupported.");
4306 return NETW_SOCKET_ERROR;
4307 }
4308 for (;;) {
4309 ssize_t bs = send(s, buf, NETW_BUFLEN_CAST(n), NETFLAGS);
4310 int error = neterrno;
4311 if (bs > 0) return bs;
4312 if (bs == 0) {
4313 /* should never happen on send() */
4314 n_log(LOG_DEBUG, "socket %d : send returned 0", s);
4316 }
4317 if (error == EINTR
4318#ifdef __windows__
4319 || error == WSAEINTR
4320#endif
4321 )
4322 continue;
4323 if (error == EAGAIN || error == EWOULDBLOCK
4324#ifdef __windows__
4325 || error == WSAEWOULDBLOCK
4326#endif
4327 )
4328 return NETW_IO_WANT_WRITE;
4329 if (error == ECONNRESET || error == ENOTCONN || error == EPIPE
4330#ifdef __windows__
4331 || error == WSAECONNRESET || error == WSAENOTCONN || error == WSAESHUTDOWN
4332#endif
4333 ) {
4334 n_log(LOG_DEBUG, "socket %d disconnected !", s);
4336 }
4337 char* errmsg = netstrerror(error);
4338 _netw_capture_error((NETWORK*)netw, "Socket %d send_once error: %s", s, _str(errmsg));
4339 n_log(LOG_ERR, "Socket %d send_once error: %s", s, _str(errmsg));
4340 FreeNoLog(errmsg);
4341 return NETW_SOCKET_ERROR;
4342 }
4343} /*send_data_once(...)*/
4344
4354ssize_t recv_data_once(void* netw, char* buf, uint32_t n) {
4356 __n_assert(buf, return NETW_SOCKET_ERROR);
4357
4358 SOCKET s = ((NETWORK*)netw)->link.sock;
4359 if (n == 0) {
4360 _netw_capture_error((NETWORK*)netw, "Recv of 0 is unsupported.");
4361 n_log(LOG_ERR, "Recv of 0 is unsupported.");
4362 return NETW_SOCKET_ERROR;
4363 }
4364 for (;;) {
4365 ssize_t br = recv(s, buf, NETW_BUFLEN_CAST(n), NETFLAGS);
4366 int error = neterrno;
4367 if (br > 0) return br;
4368 if (br == 0) {
4369 /* orderly shutdown by peer */
4370 n_log(LOG_DEBUG, "socket %d disconnected !", s);
4372 }
4373 if (error == EINTR
4374#ifdef __windows__
4375 || error == WSAEINTR
4376#endif
4377 )
4378 continue;
4379 if (error == EAGAIN || error == EWOULDBLOCK
4380#ifdef __windows__
4381 || error == WSAEWOULDBLOCK
4382#endif
4383 )
4384 return NETW_IO_WANT_READ;
4385 if (error == ECONNRESET || error == ENOTCONN
4386#ifdef __windows__
4387 || error == WSAECONNRESET || error == WSAENOTCONN
4388#endif
4389 ) {
4390 n_log(LOG_DEBUG, "socket %d disconnected !", s);
4392 }
4393 char* errmsg = netstrerror(error);
4394 _netw_capture_error((NETWORK*)netw, "Socket %d recv_once error: %s", s, _str(errmsg));
4395 n_log(LOG_ERR, "Socket %d recv_once error: %s", s, _str(errmsg));
4396 FreeNoLog(errmsg);
4397 return NETW_SOCKET_ERROR;
4398 }
4399} /*recv_data_once(...)*/
4400
4401#ifdef HAVE_OPENSSL
4409ssize_t send_ssl_data(void* netw, char* buf, uint32_t n) {
4410 __n_assert(netw, return -1);
4411 __n_assert(buf, return -1);
4412
4413 SSL* ssl = ((NETWORK*)netw)->ssl;
4414 __n_assert(ssl, return -1);
4415
4416 SOCKET s = ((NETWORK*)netw)->link.sock;
4417
4418 ssize_t bcount = 0; // counts bytes sent
4419 int error;
4420 char* errmsg = NULL;
4421
4422 if (n == 0) {
4423 _netw_capture_error((NETWORK*)netw, "Send of 0 is unsupported.");
4424 n_log(LOG_ERR, "Send of 0 is unsupported.");
4425 return -1;
4426 }
4427
4428 while (bcount < n) // loop until full buffer
4429 {
4430 size_t bs = 0; // bytes sent this pass
4431#if OPENSSL_VERSION_NUMBER < 0x1010107fL
4432 int status = SSL_write(ssl, buf, (int)(n - bcount)); // OpenSSL < 1.1.1
4433 bs = (status > 0) ? (size_t)status : 0;
4434#else
4435 int status = SSL_write_ex(ssl, buf, (size_t)(n - bcount), &bs); // OpenSSL >= 1.1.1
4436#endif
4437 error = neterrno;
4438 if (status > 0) {
4439 bcount += (ssize_t)bs; // increment byte counter
4440 buf += bs; // move buffer ptr for next read
4441 } else {
4442 int ssl_error = SSL_get_error(ssl, status);
4443 if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
4444 /* Rare on the blocking sockets the thread engine uses
4445 * (renegotiation / partial record). usleep(0) was a
4446 * pure busy-yield, burn a bounded 1 ms instead so a
4447 * stuck WANT state can't peg a core. */
4448 u_sleep(1000);
4449 continue;
4450 }
4451 /* Peer-closed connection is a normal end-of-life, not a
4452 * protocol error. SSL_ERROR_ZERO_RETURN is a clean TLS
4453 * close_notify; SSL_ERROR_SYSCALL with errno EPIPE /
4454 * ECONNRESET / ENOTCONN (or errno 0, OpenSSL's "unexpected
4455 * EOF" signal) means the socket went away under us. Mirror
4456 * the plaintext send_data() path: log at DEBUG and return
4457 * NETW_SOCKET_DISCONNECTED so the send thread's QUIT / normal
4458 * branches treat it as a graceful exit instead of an error.
4459 * Without this, a TLS client quitting after the server has
4460 * already dropped the link logs three spurious LOG_ERR lines
4461 * (SSL_write syscall error / state QUIT / thread exit err 4). */
4462 if (ssl_error == SSL_ERROR_ZERO_RETURN ||
4463 (ssl_error == SSL_ERROR_SYSCALL &&
4464 (error == 0 || error == EPIPE || error == ECONNRESET || error == ENOTCONN
4465#ifdef __windows__
4466 || error == WSAECONNRESET || error == WSAENOTCONN || error == WSAESHUTDOWN
4467#endif
4468 ))) {
4469 n_log(LOG_DEBUG, "socket %d disconnected during SSL_write !", s);
4471 }
4472 errmsg = netstrerror(error);
4473 switch (ssl_error) {
4474 case SSL_ERROR_SYSCALL:
4475 // Real, unexpected syscall failure (errno set to something other than a peer close)
4476 _netw_capture_error((NETWORK*)netw, "socket %d SSL_write syscall error: %s", s, _str(errmsg));
4477 n_log(LOG_ERR, "socket %d SSL_write syscall error: %s", s, _str(errmsg));
4478 break;
4479 case SSL_ERROR_SSL:
4480 // Handle SSL protocol failure
4481 _netw_capture_error((NETWORK*)netw, "socket %d SSL_write returned %zu, error: %s", s, bs, ERR_reason_error_string(ERR_get_error()));
4482 n_log(LOG_ERR, "socket %d SSL_write returned %zu, error: %s", s, bs, ERR_reason_error_string(ERR_get_error()));
4483 break;
4484 default:
4485 // Other errors
4486 _netw_capture_error((NETWORK*)netw, "socket %d SSL_write returned %zu, errno: %s", s, bs, _str(errmsg));
4487 n_log(LOG_ERR, "socket %d SSL_write returned %zu, errno: %s", s, bs, _str(errmsg));
4488 break;
4489 }
4490 FreeNoLog(errmsg);
4491 return NETW_SOCKET_ERROR;
4492 }
4493 }
4494 return bcount;
4495} /*send_ssl_data(...)*/
4496
4504ssize_t recv_ssl_data(void* netw, char* buf, uint32_t n) {
4505 __n_assert(netw, return -1);
4506 __n_assert(buf, return -1);
4507
4508 SSL* ssl = ((NETWORK*)netw)->ssl;
4509 __n_assert(ssl, return -1);
4510
4511 SOCKET s = ((NETWORK*)netw)->link.sock;
4512 ssize_t bcount = 0; // counts bytes read
4513 int error;
4514 char* errmsg = NULL;
4515
4516 if (n == 0) {
4517 _netw_capture_error((NETWORK*)netw, "Recv of 0 is unsupported.");
4518 n_log(LOG_ERR, "Recv of 0 is unsupported.");
4519 return -1;
4520 }
4521
4522 while (bcount < n) {
4523 size_t br = 0; // bytes read this pass
4524 // loop until full buffer
4525#if OPENSSL_VERSION_NUMBER < 0x10101000L
4526 int status = SSL_read(ssl, buf, (int)(n - bcount)); // OpenSSL < 1.1.1
4527 br = (status > 0) ? (size_t)status : 0;
4528#else
4529 int status = SSL_read_ex(ssl, buf, (size_t)(n - bcount), &br); // OpenSSL >= 1.1.1
4530#endif
4531 error = neterrno;
4532 if (status > 0) {
4533 bcount += (ssize_t)br; // increment byte counter
4534 buf += br; // move buffer ptr for next read
4535 } else {
4536 int ssl_error = SSL_get_error(ssl, status);
4537 if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
4538 /* Same bounded back-off as send_ssl_data, usleep(0)
4539 * was a busy-yield that could peg a core on a stuck
4540 * WANT state. */
4541 u_sleep(1000);
4542 continue;
4543 }
4544 /* Connection teardown is the normal end of a session, not an
4545 * error. Mirror the plaintext recv_data() and the
4546 * non-blocking recv_ssl_data_once() paths: return
4547 * NETW_SOCKET_DISCONNECTED so netw_recv_func() exits via the
4548 * clean NETW_THR_EXIT_OK path with no LOG_ERR noise -- and so
4549 * the recv thread does not flip the NETWORK into NETW_ERROR,
4550 * which is what was tripping the send thread into the
4551 * "exiting with error 666" (NETW_THR_EXIT_ERROR) tail on a
4552 * normal client exit. */
4553 if (ssl_error == SSL_ERROR_ZERO_RETURN) {
4554 /* peer sent a clean close_notify */
4555 n_log(LOG_DEBUG, "socket %d : TLS session closed by peer (close_notify)", s);
4557 }
4558 if (ssl_error == SSL_ERROR_SYSCALL && (error == 0 || error == ECONNRESET || error == ENOTCONN
4559#ifdef __windows__
4560 || error == WSAECONNRESET || error == WSAENOTCONN || error == WSAESHUTDOWN
4561#endif
4562 )) {
4563 /* peer vanished without close_notify (TCP FIN/RST) */
4564 n_log(LOG_DEBUG, "socket %d : peer closed connection (TLS, no close_notify)", s);
4566 }
4567 /* Pop the queued reason exactly ONCE and reuse it below;
4568 * calling ERR_get_error() twice (capture + log) cleared the
4569 * queue between calls, so the second read always printed
4570 * "(null)" instead of the real reason. */
4571 unsigned long ssl_reason = ERR_get_error();
4572#ifdef SSL_R_UNEXPECTED_EOF_WHILE_READING
4573 if (ssl_error == SSL_ERROR_SSL && ERR_GET_REASON(ssl_reason) == SSL_R_UNEXPECTED_EOF_WHILE_READING) {
4574 /* OpenSSL >= 3.0 reports an unclean TCP close as an
4575 * SSL_ERROR_SSL/UNEXPECTED_EOF rather than the
4576 * SSL_ERROR_SYSCALL/errno-0 of 1.1.1; still a disconnect. */
4577 n_log(LOG_DEBUG, "socket %d : peer closed connection (TLS unexpected EOF)", s);
4579 }
4580#endif
4581 errmsg = netstrerror(error);
4582 switch (ssl_error) {
4583 case SSL_ERROR_SYSCALL:
4584 // Genuine syscall-level failure (not a peer close)
4585 _netw_capture_error((NETWORK*)netw, "socket %d SSL_read syscall error: %s", s, _str(errmsg));
4586 n_log(LOG_ERR, "socket %d SSL_read syscall error: %s", s, _str(errmsg));
4587 break;
4588 case SSL_ERROR_SSL:
4589 // Genuine TLS protocol failure
4590 _netw_capture_error((NETWORK*)netw, "socket %d SSL_read protocol error: %s", s, _str((char*)ERR_reason_error_string(ssl_reason)));
4591 n_log(LOG_ERR, "socket %d SSL_read protocol error: %s", s, _str((char*)ERR_reason_error_string(ssl_reason)));
4592 break;
4593 default:
4594 // Other errors
4595 _netw_capture_error((NETWORK*)netw, "socket %d SSL_read returned %zu, errno: %s", s, br, _str(errmsg));
4596 n_log(LOG_ERR, "socket %d SSL_read returned %zu, errno: %s", s, br, _str(errmsg));
4597 break;
4598 }
4599 FreeNoLog(errmsg);
4600 return NETW_SOCKET_ERROR;
4601 }
4602 }
4603 return bcount;
4604} /*recv_ssl_data(...)*/
4605
4620ssize_t send_ssl_data_once(void* netw, char* buf, uint32_t n) {
4622 __n_assert(buf, return NETW_SOCKET_ERROR);
4623
4624 SSL* ssl = ((NETWORK*)netw)->ssl;
4625 __n_assert(ssl, return NETW_SOCKET_ERROR);
4626 SOCKET s = ((NETWORK*)netw)->link.sock;
4627
4628 if (n == 0) {
4629 _netw_capture_error((NETWORK*)netw, "Send of 0 is unsupported.");
4630 n_log(LOG_ERR, "Send of 0 is unsupported.");
4631 return NETW_SOCKET_ERROR;
4632 }
4633
4634 size_t bs = 0;
4635#if OPENSSL_VERSION_NUMBER < 0x1010107fL
4636 int status = SSL_write(ssl, buf, (int)n);
4637 bs = (status > 0) ? (size_t)status : 0;
4638#else
4639 int status = SSL_write_ex(ssl, buf, (size_t)n, &bs);
4640#endif
4641 int error = neterrno;
4642 if (status > 0) return (ssize_t)bs;
4643
4644 int ssl_error = SSL_get_error(ssl, status);
4645 switch (ssl_error) {
4646 case SSL_ERROR_WANT_READ:
4647 return NETW_IO_WANT_READ;
4648 case SSL_ERROR_WANT_WRITE:
4649 return NETW_IO_WANT_WRITE;
4650 case SSL_ERROR_ZERO_RETURN:
4651 n_log(LOG_DEBUG, "socket %d TLS session closed by peer", s);
4653 case SSL_ERROR_SYSCALL:
4654 if (error == 0 || error == ECONNRESET || error == EPIPE || error == ENOTCONN
4655#ifdef __windows__
4656 || error == WSAECONNRESET || error == WSAENOTCONN
4657#endif
4658 ) {
4659 n_log(LOG_DEBUG, "socket %d SSL_write syscall: connection closed by peer", s);
4661 }
4662 if (error == EINTR || error == EAGAIN || error == EWOULDBLOCK
4663#ifdef __windows__
4664 || error == WSAEINTR || error == WSAEWOULDBLOCK
4665#endif
4666 )
4667 return NETW_IO_WANT_WRITE;
4668 {
4669 char* errmsg = netstrerror(error);
4670 _netw_capture_error((NETWORK*)netw, "socket %d SSL_write_once syscall error: %s", s, _str(errmsg));
4671 n_log(LOG_ERR, "socket %d SSL_write_once syscall error: %s", s, _str(errmsg));
4672 FreeNoLog(errmsg);
4673 }
4674 return NETW_SOCKET_ERROR;
4675 default:
4676 _netw_capture_error((NETWORK*)netw, "socket %d SSL_write_once error: %s", s, ERR_reason_error_string(ERR_get_error()));
4677 n_log(LOG_ERR, "socket %d SSL_write_once error: %s", s, ERR_reason_error_string(ERR_get_error()));
4678 return NETW_SOCKET_ERROR;
4679 }
4680} /*send_ssl_data_once(...)*/
4681
4694ssize_t recv_ssl_data_once(void* netw, char* buf, uint32_t n) {
4696 __n_assert(buf, return NETW_SOCKET_ERROR);
4697
4698 SSL* ssl = ((NETWORK*)netw)->ssl;
4699 __n_assert(ssl, return NETW_SOCKET_ERROR);
4700 SOCKET s = ((NETWORK*)netw)->link.sock;
4701
4702 if (n == 0) {
4703 _netw_capture_error((NETWORK*)netw, "Recv of 0 is unsupported.");
4704 n_log(LOG_ERR, "Recv of 0 is unsupported.");
4705 return NETW_SOCKET_ERROR;
4706 }
4707
4708 size_t br = 0;
4709#if OPENSSL_VERSION_NUMBER < 0x10101000L
4710 int status = SSL_read(ssl, buf, (int)n);
4711 br = (status > 0) ? (size_t)status : 0;
4712#else
4713 int status = SSL_read_ex(ssl, buf, (size_t)n, &br);
4714#endif
4715 int error = neterrno;
4716 if (status > 0) return (ssize_t)br;
4717
4718 int ssl_error = SSL_get_error(ssl, status);
4719 switch (ssl_error) {
4720 case SSL_ERROR_WANT_READ:
4721 return NETW_IO_WANT_READ;
4722 case SSL_ERROR_WANT_WRITE:
4723 return NETW_IO_WANT_WRITE;
4724 case SSL_ERROR_ZERO_RETURN:
4725 n_log(LOG_DEBUG, "socket %d TLS session closed by peer", s);
4727 case SSL_ERROR_SYSCALL:
4728 if (error == 0 || error == ECONNRESET || error == ENOTCONN
4729#ifdef __windows__
4730 || error == WSAECONNRESET || error == WSAENOTCONN
4731#endif
4732 ) {
4733 n_log(LOG_DEBUG, "socket %d SSL_read syscall: connection closed by peer", s);
4735 }
4736 if (error == EINTR || error == EAGAIN || error == EWOULDBLOCK
4737#ifdef __windows__
4738 || error == WSAEINTR || error == WSAEWOULDBLOCK
4739#endif
4740 )
4741 return NETW_IO_WANT_READ;
4742 {
4743 char* errmsg = netstrerror(error);
4744 _netw_capture_error((NETWORK*)netw, "socket %d SSL_read_once syscall error: %s", s, _str(errmsg));
4745 n_log(LOG_ERR, "socket %d SSL_read_once syscall error: %s", s, _str(errmsg));
4746 FreeNoLog(errmsg);
4747 }
4748 return NETW_SOCKET_ERROR;
4749 default: {
4750 /* Pop the reason once (a second ERR_get_error() would clear
4751 * the queue and print "(null)"). OpenSSL >= 3.0 reports an
4752 * unclean TCP close here as SSL_ERROR_SSL/UNEXPECTED_EOF --
4753 * a disconnect, not a protocol error. */
4754 unsigned long ssl_reason = ERR_get_error();
4755#ifdef SSL_R_UNEXPECTED_EOF_WHILE_READING
4756 if (ssl_error == SSL_ERROR_SSL && ERR_GET_REASON(ssl_reason) == SSL_R_UNEXPECTED_EOF_WHILE_READING) {
4757 n_log(LOG_DEBUG, "socket %d : peer closed connection (TLS unexpected EOF)", s);
4759 }
4760#endif
4761 _netw_capture_error((NETWORK*)netw, "socket %d SSL_read_once error: %s", s, _str((char*)ERR_reason_error_string(ssl_reason)));
4762 n_log(LOG_ERR, "socket %d SSL_read_once error: %s", s, _str((char*)ERR_reason_error_string(ssl_reason)));
4763 return NETW_SOCKET_ERROR;
4764 }
4765 }
4766} /*recv_ssl_data_once(...)*/
4767#endif
4768
4777ssize_t send_php(SOCKET s, int _code, char* buf, int n) {
4778 ssize_t bcount = 0; /* counts bytes read */
4779 ssize_t bs; /* bytes read this pass */
4780
4781 int error;
4782 char* errmsg = NULL;
4783
4784 char* ptr = NULL; /* temp char ptr ;-) */
4785 char head[HEAD_SIZE + 1] = "";
4786 char code[HEAD_CODE + 1] = "";
4787
4788 // Format and assign to head
4789 snprintf(head, HEAD_SIZE + 1, "%0*d", HEAD_SIZE, n);
4790 // Format and assign to code
4791 snprintf(code, HEAD_CODE + 1, "%0*d", HEAD_CODE, _code);
4792
4793 /* sending head */
4794 bcount = 0;
4795 ptr = head;
4796 while (bcount < HEAD_SIZE) /* loop until full buffer */
4797 {
4798 bs = send(s, ptr, NETW_BUFLEN_CAST(HEAD_SIZE - bcount), NETFLAGS);
4799 error = neterrno;
4800 if (bs > 0) {
4801 bcount += bs; /* increment byte counter */
4802 ptr += bs; /* move buffer ptr for next read */
4803 } else {
4804 /* signal an error to the caller */
4805 errmsg = netstrerror(error);
4806 n_log(LOG_ERR, "Socket %d sending Error %d when sending head size, neterrno: %s", s, bs, _str(errmsg));
4807 FreeNoLog(errmsg);
4808 return -1;
4809 }
4810 }
4811
4812 /* sending code */
4813 bcount = 0;
4814 ptr = code;
4815 while (bcount < HEAD_CODE) /* loop until full buffer */
4816 {
4817 bs = send(s, ptr, NETW_BUFLEN_CAST(HEAD_CODE - bcount), NETFLAGS);
4818 error = neterrno;
4819 if (bs > 0) {
4820 bcount += bs; /* increment byte counter */
4821 ptr += bs; /* move buffer ptr for next read */
4822 } else {
4823 errmsg = netstrerror(error);
4824 n_log(LOG_ERR, "Socket %d sending Error %d when sending head code, neterrno: %s", s, bs, _str(errmsg));
4825 FreeNoLog(errmsg);
4826 return -1;
4827 }
4828 }
4829
4830 /* sending buf */
4831 bcount = 0;
4832 while (bcount < n) /* loop until full buffer */
4833 {
4834 bs = send(s, buf, NETW_BUFLEN_CAST(n - bcount), NETFLAGS);
4835 error = neterrno;
4836 if (bs > 0) {
4837 bcount += bs; /* increment byte counter */
4838 buf += bs; /* move buffer ptr for next read */
4839 } else {
4840 /* signal an error to the caller */
4841 errmsg = netstrerror(error);
4842 n_log(LOG_ERR, "Socket %d sending Error %d when sending message of size %d, neterrno: %s", s, bs, n, _str(errmsg));
4843 FreeNoLog(errmsg);
4844 return -1;
4845 }
4846 }
4847
4848 return bcount;
4849
4850} /*send_php(...)*/
4851
4859ssize_t recv_php(SOCKET s, int* _code, char** buf) {
4860 ssize_t bcount = 0; /* counts bytes read */
4861 ssize_t br; /* bytes read this pass */
4862 long int tmpnb = 0, size = 0; /* size of message to receive */
4863 char* ptr = NULL;
4864
4865 int error;
4866 char* errmsg = NULL;
4867
4868 char head[HEAD_SIZE + 1] = "";
4869 char code[HEAD_CODE + 1] = "";
4870
4871 /* Receiving total message size */
4872 bcount = 0;
4873 ptr = head;
4874 while (bcount < HEAD_SIZE) {
4875 /* loop until full buffer */
4876 br = recv(s, ptr, NETW_BUFLEN_CAST(HEAD_SIZE - bcount), NETFLAGS);
4877 error = neterrno;
4878 if (br > 0) {
4879 bcount += br; /* increment byte counter */
4880 ptr += br; /* move buffer ptr for next read */
4881 } else {
4882 /* signal an error to the caller */
4883 errmsg = netstrerror(error);
4884 n_log(LOG_ERR, "Socket %d receive %d Error %s", s, br, _str(errmsg));
4885 FreeNoLog(errmsg);
4886 return FALSE;
4887 }
4888 }
4889
4890 tmpnb = strtol(head, NULL, 10);
4891 if (tmpnb == LONG_MIN || tmpnb == LONG_MAX) {
4892 n_log(LOG_ERR, "Size received ( %ld ) can not be determined on socket %d", tmpnb, s);
4893 return FALSE;
4894 }
4895
4896 size = tmpnb;
4897
4898 /* receiving request code */
4899 bcount = 0;
4900 ptr = code;
4901 while (bcount < HEAD_CODE) {
4902 /* loop until full buffer */
4903 br = recv(s, ptr, NETW_BUFLEN_CAST(HEAD_CODE - bcount), NETFLAGS);
4904 error = neterrno;
4905 if (br > 0) {
4906 bcount += br; /* increment byte counter */
4907 ptr += br; /* move buffer ptr for next read */
4908 } else {
4909 /* signal an error to the caller */
4910 errmsg = netstrerror(error);
4911 n_log(LOG_ERR, "Socket %d receive %d Error , neterrno: %s", s, br, _str(errmsg));
4912 FreeNoLog(errmsg);
4913 return FALSE;
4914 }
4915 }
4916
4917 tmpnb = strtol(code, NULL, 10);
4918 if (tmpnb <= INT_MIN || tmpnb >= INT_MAX) {
4919 n_log(LOG_ERR, "Code received ( %ld ) too big or too little to be valid code on socket %d", tmpnb, s);
4920 return FALSE;
4921 }
4922
4923 (*_code) = (int)tmpnb;
4924
4925 /* receving message */
4926 if ((*buf)) {
4927 // cppcheck-suppress identicalInnerCondition ; Free() macro rechecks pointer internally
4928 Free((*buf));
4929 }
4930 Malloc((*buf), char, (size_t)(size + 1));
4931 if (!(*buf)) {
4932 n_log(LOG_ERR, "Could not allocate PHP receive buf");
4933 return FALSE;
4934 }
4935
4936 bcount = 0;
4937 ptr = (*buf);
4938 while (bcount < size) {
4939 /* loop until full buffer */
4940 br = recv(s, ptr, NETW_BUFLEN_CAST(size - bcount), NETFLAGS);
4941 error = neterrno;
4942 if (br > 0) {
4943 bcount += br; /* increment byte counter */
4944 ptr += br; /* move buffer ptr for next read */
4945 } else {
4946 /* signal an error to the caller */
4947 errmsg = netstrerror(error);
4948 n_log(LOG_ERR, "Socket %d receive %d Error neterrno: %s", s, br, _str(errmsg));
4949 FreeNoLog(errmsg);
4950 return FALSE;
4951 }
4952 }
4953 return size;
4954} /*recv_php(...)*/
4955
4964int netw_get_queue_status(NETWORK* netw, size_t* nb_to_send, size_t* nb_to_read) {
4965 __n_assert(netw, return FALSE);
4966
4967 pthread_mutex_lock(&netw->sendbolt);
4968 (*nb_to_send) = netw->send_buf->nb_items;
4969 pthread_mutex_unlock(&netw->sendbolt);
4970
4971 pthread_mutex_lock(&netw->recvbolt);
4972 (*nb_to_read) = netw->recv_buf->nb_items;
4973 pthread_mutex_unlock(&netw->recvbolt);
4974
4975 return TRUE;
4976} /* get queue states */
4977
4983NETWORK_POOL* netw_new_pool(size_t nb_min_element) {
4984 NETWORK_POOL* netw_pool = NULL;
4985
4986 Malloc(netw_pool, NETWORK_POOL, 1);
4987 __n_assert(netw_pool, return NULL);
4988
4989 netw_pool->pool = new_ht(nb_min_element);
4990 __n_assert(netw_pool->pool, Free(netw_pool); return NULL);
4991
4992 init_lock(netw_pool->rwlock);
4993
4994 return netw_pool;
4995} /* netw_new_pool() */
4996
5003 __n_assert(netw_pool && (*netw_pool), return FALSE);
5004
5005 write_lock((*netw_pool)->rwlock);
5006 if ((*netw_pool)->pool)
5007 destroy_ht(&(*netw_pool)->pool);
5008 unlock((*netw_pool)->rwlock);
5009
5010 rw_lock_destroy((*netw_pool)->rwlock);
5011
5012 Free((*netw_pool));
5013
5014 return TRUE;
5015} /* netw_destroy_pool() */
5016
5021void netw_pool_netw_close(void* netw_ptr) {
5022 NETWORK* netw = (NETWORK*)netw_ptr;
5023 __n_assert(netw, return);
5024 n_log(LOG_DEBUG, "Network pool %p: network id %d still active !!", netw, netw->link.sock);
5025 return;
5026} /* netw_pool_netw_close() */
5027
5035 __n_assert(netw_pool, return FALSE);
5036 __n_assert(netw, return FALSE);
5037
5038 n_log(LOG_DEBUG, "Trying to add %lld to %p", (unsigned long long)netw->link.sock, netw_pool->pool);
5039
5040 /* write lock the pool */
5041 write_lock(netw_pool->rwlock);
5042 /* test if not already added */
5043 N_STR* key = NULL;
5044 nstrprintf(key, "%lld", (unsigned long long)netw->link.sock);
5045 HASH_NODE* node = NULL;
5046 if (ht_get_ptr(netw_pool->pool, _nstr(key), (void*)&node) == TRUE) {
5047 _netw_capture_error(netw, "Network id %d already added !", netw->link.sock);
5048 n_log(LOG_ERR, "Network id %d already added !", netw->link.sock);
5049 free_nstr(&key);
5050 unlock(netw_pool->rwlock);
5051 return FALSE;
5052 }
5053 int retval;
5054 /* add it */
5055 if ((retval = ht_put_ptr(netw_pool->pool, _nstr(key), netw, &netw_pool_netw_close, NULL)) == TRUE) {
5056 if ((retval = list_push(netw->pools, netw_pool, NULL)) == TRUE) {
5057 n_log(LOG_DEBUG, "added netw %d to pool %p", netw->link.sock, netw_pool);
5058 } else {
5059 _netw_capture_error(netw, "could not add netw %d to pool %p", netw->link.sock, netw_pool);
5060 n_log(LOG_ERR, "could not add netw %d to pool %p", netw->link.sock, netw_pool);
5061 }
5062 } else {
5063 _netw_capture_error(netw, "could not add netw %d to pool %p", netw->link.sock, netw_pool);
5064 n_log(LOG_ERR, "could not add netw %d to pool %p", netw->link.sock, netw_pool);
5065 }
5066 free_nstr(&key);
5067
5068 /* unlock the pool */
5069 unlock(netw_pool->rwlock);
5070
5071 return retval;
5072} /* netw_pool_add() */
5073
5081 __n_assert(netw_pool, return FALSE);
5082 __n_assert(netw, return FALSE);
5083
5084 /* write lock the pool */
5085 write_lock(netw_pool->rwlock);
5086 /* test if present */
5087 N_STR* key = NULL;
5088 nstrprintf(key, "%lld", (unsigned long long int)netw->link.sock);
5089 if (ht_remove(netw_pool->pool, _nstr(key)) == TRUE) {
5090 LIST_NODE* node = list_search(netw->pools, netw_pool);
5091 if (node) {
5092 if (!remove_list_node(netw->pools, node, NETWORK_POOL)) {
5093 _netw_capture_error(netw, "Network id %d could not be removed !", netw->link.sock);
5094 n_log(LOG_ERR, "Network id %d could not be removed !", netw->link.sock);
5095 }
5096 }
5097 unlock(netw_pool->rwlock);
5098 n_log(LOG_DEBUG, "Network id %d removed !", netw->link.sock);
5099 free_nstr(&key);
5100 return TRUE;
5101 }
5102 free_nstr(&key);
5103 _netw_capture_error(netw, "Network id %d already removed !", netw->link.sock);
5104 n_log(LOG_ERR, "Network id %d already removed !", netw->link.sock);
5105 /* unlock the pool */
5106 unlock(netw_pool->rwlock);
5107 return FALSE;
5108} /* netw_pool_remove */
5109
5117int netw_pool_broadcast(NETWORK_POOL* netw_pool, const NETWORK* from, N_STR* net_msg) {
5118 __n_assert(netw_pool, return FALSE);
5119 __n_assert(net_msg, return FALSE);
5120
5121 /* write lock the pool */
5122 read_lock(netw_pool->rwlock);
5123 ht_foreach(node, netw_pool->pool) {
5124 NETWORK* netw = hash_val(node, NETWORK);
5125 if (from) {
5126 if (netw->link.sock != from->link.sock)
5127 netw_add_msg(netw, nstrdup(net_msg));
5128 } else {
5129 netw_add_msg(netw, nstrdup(net_msg));
5130 }
5131 }
5132 unlock(netw_pool->rwlock);
5133 return TRUE;
5134} /* netw_pool_broadcast */
5135
5142 __n_assert(netw_pool, return 0);
5143
5144 size_t nb = 0;
5145 read_lock(netw_pool->rwlock);
5146 nb = netw_pool->pool->nb_keys;
5147 unlock(netw_pool->rwlock);
5148
5149 return nb;
5150} /* netw_pool_nbclients() */
5151
5159 __n_assert(netw, return FALSE);
5160 netw->user_id = id;
5161 return TRUE;
5162} /* netw_set_user_id() */
5163
5173int netw_send_ping(NETWORK* netw, int type, int id_from, int id_to, int time) {
5174 N_STR* tmpstr = NULL;
5175 __n_assert(netw, return FALSE);
5176
5177 tmpstr = netmsg_make_ping(type, id_from, id_to, time);
5178 __n_assert(tmpstr, return FALSE);
5179
5180 return netw_add_msg(netw, tmpstr);
5181} /* netw_send_ping( ... ) */
5182
5192int netw_send_ident(NETWORK* netw, int type, int id, N_STR* name, N_STR* passwd) {
5193 N_STR* tmpstr = NULL;
5194
5195 __n_assert(netw, return FALSE);
5196
5197 tmpstr = netmsg_make_ident(type, id, name, passwd);
5198 __n_assert(tmpstr, return FALSE);
5199
5200 return netw_add_msg(netw, tmpstr);
5201} /* netw_send_ident( ... ) */
5202
5216int netw_send_position(NETWORK* netw, int id, double X, double Y, double vx, double vy, double acc_x, double acc_y, int time_stamp) {
5217 N_STR* tmpstr = NULL;
5218
5219 __n_assert(netw, return FALSE);
5220
5221 tmpstr = netmsg_make_position_msg(id, X, Y, vx, vy, acc_x, acc_y, time_stamp);
5222
5223 __n_assert(tmpstr, return FALSE);
5224
5225 return netw_add_msg(netw, tmpstr);
5226} /* netw_send_position( ... ) */
5227
5238int netw_send_string_to(NETWORK* netw, int id_to, N_STR* name, N_STR* chan, N_STR* txt, int color) {
5239 N_STR* tmpstr = NULL;
5240
5241 __n_assert(netw, return FALSE);
5242
5243 tmpstr = netmsg_make_string_msg(netw->user_id, id_to, name, chan, txt, color);
5244 __n_assert(tmpstr, return FALSE);
5245
5246 return netw_add_msg(netw, tmpstr);
5247} /* netw_send_string_to( ... ) */
5248
5258int netw_send_string_to_all(NETWORK* netw, N_STR* name, N_STR* chan, N_STR* txt, int color) {
5259 N_STR* tmpstr = NULL;
5260
5261 __n_assert(netw, return FALSE);
5262
5263 tmpstr = netmsg_make_string_msg(netw->user_id, -1, name, chan, txt, color);
5264 __n_assert(tmpstr, return FALSE);
5265
5266 return netw_add_msg(netw, tmpstr);
5267} /* netw_send_string_to_all( ... ) */
5268
5275 __n_assert(netw, return FALSE);
5276
5277 N_STR* tmpstr = NULL;
5278
5279 tmpstr = netmsg_make_quit_msg();
5280 __n_assert(tmpstr, return FALSE);
5281
5282 return netw_add_msg(netw, tmpstr);
5283} /* netw_send_quit( ... ) */
5284
5291size_t netw_calculate_urlencoded_size(const char* str, size_t len) {
5292 __n_assert(str, return 0);
5293
5294 size_t encoded_size = 0;
5295
5296 for (size_t i = 0; i < len; i++) {
5297 unsigned char c = (unsigned char)str[i];
5298 if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
5299 encoded_size += 1; // Safe character, no encoding needed
5300 } else {
5301 encoded_size += 3; // Unsafe character, will be encoded as %XX
5302 }
5303 }
5304
5305 return encoded_size;
5306}
5307
5314char* netw_urlencode(const char* str, size_t len) {
5315 __n_assert(str, return NULL);
5316
5317 static const char* hex = "0123456789ABCDEF";
5318 size_t encoded_size = netw_calculate_urlencoded_size(str, len);
5319 char* encoded = (char*)malloc(encoded_size + 1); // Allocate memory for the encoded string (+1 for the null terminator)
5320
5321 if (!encoded) {
5322 return NULL; // Return NULL if memory allocation fails
5323 }
5324
5325 char* pbuf = encoded;
5326
5327 for (size_t i = 0; i < len; i++) {
5328 unsigned char c = (unsigned char)str[i];
5329 if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
5330 *pbuf++ = (char)c; // Copy safe characters directly
5331 } else {
5332 *pbuf++ = '%'; // Encode unsafe characters as %XX
5333 *pbuf++ = hex[c >> 4];
5334 *pbuf++ = hex[c & 0xF];
5335 }
5336 }
5337
5338 *pbuf = '\0'; // Null-terminate the encoded string
5339 return encoded;
5340}
5341
5347char* netw_extract_http_request_type(const char* request) {
5348 __n_assert(request, return NULL);
5349 // Find the first space in the request string
5350 const char* space = strchr(request, ' ');
5351
5352 if (space == NULL) {
5353 // No space found, invalid request format
5354 return NULL;
5355 }
5356
5357 // Calculate the length of the request type
5358 size_t method_length = (size_t)(space - request);
5359
5360 // Allocate memory for the method string (+1 for the null terminator)
5361 char* method = (char*)malloc(method_length + 1);
5362
5363 if (method == NULL) {
5364 // Memory allocation failed
5365 return NULL;
5366 }
5367
5368 // Copy the request method to the allocated memory
5369 strncpy(method, request, method_length);
5370 method[method_length] = '\0'; // Null-terminate the string
5371
5372 return method;
5373}
5374
5381 NETWORK_HTTP_INFO info = {.content_length = 0, .body = NULL, .type = NULL, .content_type = {0}};
5382
5383 __n_assert(request, return info);
5384
5385 // Hold the request-type allocation in a local until just before return.
5386 // The clang static analyzer's unix.Malloc checker mis-tracks ownership
5387 // when a malloc'd pointer is parked in a returned-by-value struct member
5388 // early in the function; keeping it local until the final assignment
5389 // makes the escape via the returned struct unambiguous to the analyzer.
5390 char* request_type = netw_extract_http_request_type(request);
5391
5392 // Find Content-Type header (Optional)
5393 const char* content_type_header = strstr(request, "Content-Type:");
5394 if (content_type_header) {
5395 const char* start = content_type_header + strlen("Content-Type: ");
5396 const char* end = strstr(start, "\r\n");
5397 if (end) {
5398 size_t length = (size_t)(end - start);
5399 if (length > 255) length = 255;
5400 strncpy(info.content_type, start, length);
5401 info.content_type[length] = '\0';
5402 }
5403 } else {
5404 // If no Content-Type header found, set default
5405 strncpy(info.content_type, "text/plain", sizeof(info.content_type) - 1);
5406 }
5407
5408 // Find Content-Length header (Optional)
5409 const char* content_length_header = strstr(request, "Content-Length:");
5410 if (content_length_header) {
5411 const char* start = content_length_header + strlen("Content-Length: ");
5412 errno = 0;
5413 unsigned long tmp_cl = strtoul(start, NULL, 10); /* parse once */
5414 int error = errno;
5415 // Handle out-of-range error (e.g., set a safe default or return an error)
5416 // Only when ULONG_MAX is larger than SIZE_MAX, to prevent a constant false comparison warning
5417#if ULONG_MAX > SIZE_MAX
5418 if (error == ERANGE || tmp_cl > SIZE_MAX) {
5419#else
5420 if (error == ERANGE) {
5421#endif
5422 n_log(LOG_ERR, "could not get content_length for request %p, returned %s", request, strerror(error));
5423 info.content_length = SIZE_MAX; /* clamp to the maximum supported value */
5424 } else {
5425 info.content_length = (size_t)tmp_cl;
5426 }
5427 }
5428
5429 // Find the start of the body (after \r\n\r\n)
5430 const char* body_start = strstr(request, "\r\n\r\n");
5431 if (body_start) {
5432 body_start += 4; // Skip the \r\n\r\n
5433
5434 // If there is a Content-Length, and it's greater than 0, copy the body
5435 if (info.content_length > 0 && info.content_length < SIZE_MAX) {
5436 info.body = malloc(info.content_length + 1); // Allocate memory for body
5437 if (info.body) {
5438 strncpy(info.body, body_start, info.content_length);
5439 info.body[info.content_length] = '\0'; // Null-terminate the body
5440 }
5441 }
5442 }
5443
5444 info.type = request_type;
5445 return info;
5446}
5447
5454 FreeNoLog(http_request.body);
5455 FreeNoLog(http_request.type);
5456 return TRUE;
5457}
5458
5466int netw_get_url_from_http_request(const char* request, char* url, size_t size) {
5467 __n_assert(request && strlen(request) > 0, return FALSE);
5468 __n_assert(url && size > 1, return FALSE);
5469
5470 /* Default output */
5471 strncpy(url, "/", size - 1);
5472 url[size - 1] = '\0';
5473
5474 /* Example request line: "GET /path/to/resource HTTP/1.1" */
5475 const char* first_space = strchr(request, ' ');
5476 if (!first_space) {
5477 /* Malformed request, return default '/' */
5478 return FALSE;
5479 }
5480
5481 const char* second_space = strchr(first_space + 1, ' ');
5482 if (!second_space) {
5483 /* Malformed request, return default '/' */
5484 return FALSE;
5485 }
5486
5487 size_t len = (size_t)(second_space - first_space - 1);
5488 if (len >= size) {
5489 len = size - 1;
5490 }
5491
5492 strncpy(url, first_space + 1, len);
5493 url[len] = '\0'; /* Null-terminate the URL */
5494
5495 return TRUE;
5496}
5497
5503char* netw_urldecode(const char* str) {
5504 __n_assert(str, return NULL);
5505 char* decoded = malloc(strlen(str) + 1);
5506 __n_assert(decoded, return NULL);
5507
5508 char* p = decoded;
5509 while (*str) {
5510 if (*str == '%') {
5511 if (isxdigit((unsigned char)str[1]) && isxdigit((unsigned char)str[2])) {
5512 unsigned int value;
5513 if (sscanf(str + 1, "%2x", &value) >= 1) {
5514 *p++ = (char)value;
5515 str += 3;
5516 } else {
5517 n_log(LOG_ERR, "sscanf could not parse char *str (%p) for a %%2x", str);
5518 Free(decoded);
5519 return NULL;
5520 }
5521 } else {
5522 *p++ = *str++;
5523 }
5524 } else if (*str == '+') {
5525 *p++ = ' ';
5526 str++;
5527 } else {
5528 *p++ = *str++;
5529 }
5530 }
5531 *p = '\0';
5532 return decoded;
5533}
5534
5540HASH_TABLE* netw_parse_post_data(const char* post_data) {
5541 __n_assert(post_data, return NULL);
5542
5543 // Create a copy of the post_data string because strtok modifies the string
5544 char* data = strdup(post_data);
5545 __n_assert(data, return NULL);
5546
5547 char* pair = data;
5548
5549 HASH_TABLE* post_data_table = new_ht(32);
5550
5551 while (pair != NULL) {
5552 // Find the next key-value pair separated by '&'
5553 char* ampersand_pos = strchr(pair, '&');
5554
5555 // If found, replace it with '\0' to isolate the current pair
5556 if (ampersand_pos != NULL) {
5557 *ampersand_pos = '\0';
5558 }
5559
5560 // Now split each pair by '=' to get the key and value
5561 char* equal_pos = strchr(pair, '=');
5562 if (equal_pos != NULL) {
5563 *equal_pos = '\0'; // Terminate the key string
5564 const char* key = pair;
5565 const char* value = equal_pos + 1;
5566
5567 // Decode the value since POST data is URL-encoded
5568 char* decoded_value = netw_urldecode(value);
5569 ht_put_string(post_data_table, key, decoded_value);
5570 // printf("Key: %s, Value: %s\n", key, decoded_value);
5571 free(decoded_value);
5572 }
5573 // Move to the next key-value pair (if any)
5574 pair = (ampersand_pos != NULL) ? (ampersand_pos + 1) : NULL;
5575 }
5576 // Free the duplicated string
5577 free(data);
5578 return post_data_table;
5579}
5580
5586const char* netw_guess_http_content_type(const char* url) {
5587 __n_assert(url, return NULL);
5588
5589 // Create a copy of the URL to work on (to avoid modifying the original string)
5590 char url_copy[1024];
5591 strncpy(url_copy, url, sizeof(url_copy) - 1);
5592 url_copy[sizeof(url_copy) - 1] = '\0';
5593
5594 // Find if there is a '?' (indicating GET parameters) and terminate the string there
5595 char* query_start = strchr(url_copy, '?');
5596 if (query_start) {
5597 *query_start = '\0'; // Terminate the string before the query parameters
5598 }
5599
5600 // Find the last occurrence of a dot in the URL (file extension)
5601 const char* ext = strrchr(url_copy, '.');
5602
5603 // If no extension is found, return "unknown"
5604 if (!ext) {
5605 return "unknown";
5606 }
5607
5608 // Compare the extension to known content types
5609 if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) {
5610 return "text/html";
5611 } else if (strcmp(ext, ".txt") == 0) {
5612 return "text/plain";
5613 } else if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) {
5614 return "image/jpeg";
5615 } else if (strcmp(ext, ".png") == 0) {
5616 return "image/png";
5617 } else if (strcmp(ext, ".gif") == 0) {
5618 return "image/gif";
5619 } else if (strcmp(ext, ".css") == 0) {
5620 return "text/css";
5621 } else if (strcmp(ext, ".js") == 0) {
5622 return "application/javascript";
5623 } else if (strcmp(ext, ".json") == 0) {
5624 return "application/json";
5625 } else if (strcmp(ext, ".xml") == 0) {
5626 return "application/xml";
5627 } else if (strcmp(ext, ".pdf") == 0) {
5628 return "application/pdf";
5629 } else if (strcmp(ext, ".zip") == 0) {
5630 return "application/zip";
5631 } else if (strcmp(ext, ".mp4") == 0) {
5632 return "video/mp4";
5633 } else if (strcmp(ext, ".mp3") == 0) {
5634 return "audio/mpeg";
5635 } else if (strcmp(ext, ".wav") == 0) {
5636 return "audio/wav";
5637 } else if (strcmp(ext, ".ogg") == 0) {
5638 return "audio/ogg";
5639 }
5640
5641 // Return unknown for other types
5642 return "unknown";
5643}
5644
5650const char* netw_get_http_status_message(int status_code) {
5651 switch (status_code) {
5652 case 200:
5653 return "OK";
5654 case 204:
5655 return "No Content";
5656 case 304:
5657 return "Not Modified";
5658 case 404:
5659 return "Not Found";
5660 case 500:
5661 return "Internal Server Error";
5662 // Add more status codes as needed
5663 default:
5664 return "Unknown";
5665 }
5666}
5667
5668int n_http_status_class(int status_code) {
5669 if (status_code < 100 || status_code > 599)
5670 return 0;
5671 return status_code / 100;
5672}
5673
5675 return n_http_status_class(status_code) == 1;
5676}
5677
5678int n_http_status_is_success(int status_code) {
5679 return n_http_status_class(status_code) == 2;
5680}
5681
5682int n_http_status_is_redirect(int status_code) {
5683 return n_http_status_class(status_code) == 3;
5684}
5685
5686int n_http_status_is_client_error(int status_code) {
5687 return n_http_status_class(status_code) == 4;
5688}
5689
5690int n_http_status_is_server_error(int status_code) {
5691 return n_http_status_class(status_code) == 5;
5692}
5693
5694/* Inflate src/len with the given zlib window bits into a fresh N_STR.
5695 * window_bits: 15+16 for gzip framing, -15 for raw deflate, 15 for zlib-wrapped.
5696 * Returns 0 on success (*out set), -1 on error. */
5697static int n_http_inflate(const unsigned char* src, size_t len, int window_bits, N_STR** out) {
5698 z_stream strm;
5699 size_t alloc, total = 0;
5700 char* buf;
5701 int ret;
5702 *out = NULL;
5703 memset(&strm, 0, sizeof(strm));
5704 if (inflateInit2(&strm, window_bits) != Z_OK)
5705 return -1;
5706 alloc = len * 4;
5707 if (alloc < 4096)
5708 alloc = 4096;
5709 buf = malloc(alloc);
5710 if (!buf) {
5711 inflateEnd(&strm);
5712 return -1;
5713 }
5714 strm.next_in = (Bytef*)src;
5715 strm.avail_in = (uInt)len;
5716 do {
5717 if (total >= alloc - 1) {
5718 char* tmp;
5719 alloc *= 2;
5720 tmp = realloc(buf, alloc);
5721 if (!tmp) {
5722 free(buf);
5723 inflateEnd(&strm);
5724 return -1;
5725 }
5726 buf = tmp;
5727 }
5728 strm.next_out = (Bytef*)(buf + total);
5729 strm.avail_out = (uInt)(alloc - total - 1);
5730 ret = inflate(&strm, Z_NO_FLUSH);
5731 if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR || ret == Z_NEED_DICT) {
5732 free(buf);
5733 inflateEnd(&strm);
5734 return -1;
5735 }
5736 total = alloc - 1 - strm.avail_out;
5737 } while (ret != Z_STREAM_END);
5738 inflateEnd(&strm);
5739 buf[total] = '\0';
5740 {
5741 N_STR* r = NULL;
5742 char_to_nstr_ex(buf, (NSTRBYTE)total, &r);
5743 free(buf);
5744 if (!r)
5745 return -1;
5746 *out = r;
5747 }
5748 return 0;
5749}
5750
5751int n_http_decompress_body(const N_STR* body, const char* encoding, N_STR** out) {
5752 char enc[32];
5753 int is_gzip = 0, is_deflate = 0;
5754 if (!body || !out)
5755 return -1;
5756 *out = NULL;
5757
5758 enc[0] = '\0';
5759 if (encoding) {
5760 size_t i;
5761 for (i = 0; i < sizeof(enc) - 1 && encoding[i]; i++)
5762 enc[i] = (char)tolower((unsigned char)encoding[i]);
5763 enc[i] = '\0';
5764 }
5765 if (strstr(enc, "gzip"))
5766 is_gzip = 1;
5767 else if (strstr(enc, "deflate"))
5768 is_deflate = 1;
5769
5770 /* identity / unknown / empty body: pass a copy through unchanged */
5771 if ((!is_gzip && !is_deflate) || !body->data || body->written == 0) {
5772 N_STR* copy = nstrdup((N_STR*)body);
5773 if (!copy)
5774 return -1;
5775 *out = copy;
5776 return 0;
5777 }
5778
5779 if (is_gzip)
5780 return n_http_inflate((const unsigned char*)body->data, body->written, 15 + 16, out);
5781 /* "deflate" is sent both raw (-15) and zlib-wrapped (15); try raw, then wrapped */
5782 if (n_http_inflate((const unsigned char*)body->data, body->written, -15, out) == 0)
5783 return 0;
5784 return n_http_inflate((const unsigned char*)body->data, body->written, 15, out);
5785}
5786
5787/* Binary substring search: first offset of needle in hay, or -1. */
5788static long mp_find(const unsigned char* hay, size_t hlen, const char* needle, size_t nlen) {
5789 size_t i;
5790 if (nlen == 0 || nlen > hlen)
5791 return -1;
5792 for (i = 0; i + nlen <= hlen; i++) {
5793 if (hay[i] == (unsigned char)needle[0] && memcmp(hay + i, needle, nlen) == 0)
5794 return (long)i;
5795 }
5796 return -1;
5797}
5798
5799/* Duplicate len bytes of s into a fresh NUL-terminated C string. */
5800static char* mp_strndup(const char* s, size_t len) {
5801 char* r = NULL;
5802 Malloc(r, char, len + 1);
5803 if (!r)
5804 return NULL;
5805 if (len)
5806 memcpy(r, s, len);
5807 r[len] = '\0';
5808 return r;
5809}
5810
5811int n_http_multipart_boundary(const char* content_type, char* out, size_t outsz) {
5812 const char* p;
5813 size_t i;
5814 if (!content_type || !out || outsz == 0)
5815 return -1;
5816 /* case-insensitive search for "boundary=" */
5817 for (p = content_type; *p; p++) {
5818 if (strncasecmp(p, "boundary=", 9) == 0)
5819 break;
5820 }
5821 if (!*p)
5822 return -1;
5823 p += 9;
5824 i = 0;
5825 if (*p == '"') {
5826 p++;
5827 while (*p && *p != '"' && i + 1 < outsz)
5828 out[i++] = *p++;
5829 } else {
5830 while (*p && *p != ';' && *p != ' ' && *p != '\r' && *p != '\n' && i + 1 < outsz)
5831 out[i++] = *p++;
5832 }
5833 out[i] = '\0';
5834 return i > 0 ? 0 : -1;
5835}
5836
5837/* Free an N_HTTP_MULTIPART_PART stored in a LIST. */
5838static void mp_part_free(void* ptr) {
5840 if (!mp)
5841 return;
5842 FreeNoLog(mp->name);
5843 FreeNoLog(mp->filename);
5845 if (mp->body)
5846 free_nstr(&mp->body);
5847 Free(mp);
5848}
5849
5851 if (parts && *parts)
5852 list_destroy(parts);
5853}
5854
5855/* Within a header block, find the value of the named header (case-insensitive),
5856 * returning a pointer to the value start and its length (trailing CRLF trimmed). */
5857static const char* mp_header_value(const char* block, size_t blen, const char* name, size_t* vlen) {
5858 size_t nlen = strlen(name);
5859 size_t i = 0;
5860 *vlen = 0;
5861 while (i < blen) {
5862 size_t line_end = i;
5863 while (line_end < blen && block[line_end] != '\n')
5864 line_end++;
5865 /* line spans [i, line_end) (may include a trailing '\r') */
5866 if (line_end - i > nlen + 1 && strncasecmp(block + i, name, nlen) == 0 && block[i + nlen] == ':') {
5867 const char* v = block + i + nlen + 1;
5868 size_t vl = line_end - (i + nlen + 1);
5869 while (vl > 0 && (*v == ' ' || *v == '\t')) {
5870 v++;
5871 vl--;
5872 }
5873 while (vl > 0 && (v[vl - 1] == '\r' || v[vl - 1] == ' ' || v[vl - 1] == '\t'))
5874 vl--;
5875 *vlen = vl;
5876 return v;
5877 }
5878 i = line_end + 1;
5879 }
5880 return NULL;
5881}
5882
5883/* Extract a quoted (or token) parameter value from a Content-Disposition value,
5884 * e.g. param "name" from `form-data; name="x"; filename="y"`. */
5885static char* mp_disp_param(const char* disp, size_t dlen, const char* param) {
5886 char key[32];
5887 size_t klen, i;
5888 snprintf(key, sizeof(key), "%s=", param);
5889 klen = strlen(key);
5890 for (i = 0; i + klen <= dlen; i++) {
5891 if (strncasecmp(disp + i, key, klen) == 0) {
5892 const char* v = disp + i + klen;
5893 size_t avail = dlen - (i + klen);
5894 size_t j = 0;
5895 if (avail > 0 && *v == '"') {
5896 v++;
5897 avail--;
5898 while (j < avail && v[j] != '"')
5899 j++;
5900 } else {
5901 while (j < avail && v[j] != ';' && v[j] != ' ')
5902 j++;
5903 }
5904 return mp_strndup(v, j);
5905 }
5906 }
5907 return NULL;
5908}
5909
5910LIST* n_http_parse_multipart(const N_STR* body, const char* boundary) {
5911 char dash[256];
5912 char crlf_dash[260];
5913 const unsigned char* data;
5914 size_t len, dlen, cdlen, cursor;
5915 long first;
5916 LIST* parts;
5917
5918 if (!body || !body->data || !boundary || !boundary[0])
5919 return NULL;
5920 if (strlen(boundary) + 2 >= sizeof(dash))
5921 return NULL;
5922 snprintf(dash, sizeof(dash), "--%s", boundary);
5923 snprintf(crlf_dash, sizeof(crlf_dash), "\r\n--%s", boundary);
5924 dlen = strlen(dash);
5925 cdlen = strlen(crlf_dash);
5926
5927 parts = new_generic_list(0);
5928 if (!parts)
5929 return NULL;
5930
5931 data = (const unsigned char*)body->data;
5932 len = body->written;
5933 first = mp_find(data, len, dash, dlen);
5934 if (first < 0)
5935 return parts; /* no boundary present: empty result */
5936 cursor = (size_t)first + dlen;
5937
5938 for (;;) {
5939 long rel;
5940 size_t part_start, part_end;
5941 size_t hvl;
5943 /* closing delimiter "--boundary--" */
5944 if (cursor + 2 <= len && data[cursor] == '-' && data[cursor + 1] == '-')
5945 break;
5946 /* skip the CRLF that follows the delimiter */
5947 if (cursor + 2 <= len && data[cursor] == '\r' && data[cursor + 1] == '\n')
5948 cursor += 2;
5949 else if (cursor < len && data[cursor] == '\n')
5950 cursor += 1;
5951 part_start = cursor;
5952 rel = mp_find(data + part_start, len - part_start, crlf_dash, cdlen);
5953 if (rel < 0)
5954 break; /* malformed: no closing delimiter */
5955 part_end = part_start + (size_t)rel;
5956
5958 if (!mp)
5959 break;
5960 mp->name = NULL;
5961 mp->filename = NULL;
5962 mp->content_type = NULL;
5963 mp->body = NULL;
5964 /* split the part into its header block and body at the blank line */
5965 {
5966 long s = mp_find(data + part_start, part_end - part_start, "\r\n\r\n", 4);
5967 if (s >= 0) {
5968 size_t hlen = (size_t)s;
5969 size_t bbeg = part_start + (size_t)s + 4;
5970 const char* hblock = (const char*)(data + part_start);
5971 const char* hv = mp_header_value(hblock, hlen, "Content-Disposition", &hvl);
5972 if (hv) {
5973 mp->name = mp_disp_param(hv, hvl, "name");
5974 mp->filename = mp_disp_param(hv, hvl, "filename");
5975 }
5976 hv = mp_header_value(hblock, hlen, "Content-Type", &hvl);
5977 if (hv)
5978 mp->content_type = mp_strndup(hv, hvl);
5979 {
5980 size_t blen = part_end - bbeg;
5981 mp->body = new_nstr(blen + 1);
5982 if (mp->body) {
5983 if (blen)
5984 memcpy(mp->body->data, data + bbeg, blen);
5985 mp->body->data[blen] = '\0';
5986 mp->body->written = blen;
5987 }
5988 }
5989 }
5990 }
5991 if (!mp->name)
5992 mp->name = mp_strndup("", 0);
5993 if (!mp->filename)
5994 mp->filename = mp_strndup("", 0);
5995 if (!mp->content_type)
5996 mp->content_type = mp_strndup("", 0);
5997 if (!mp->body)
5998 mp->body = new_nstr(1);
5999 list_push(parts, mp, mp_part_free);
6000
6001 cursor = part_end + cdlen; /* skip "\r\n--boundary" to the next part/closing */
6002 }
6003 return parts;
6004}
6005
6006/* Append n bytes to buf at *pos (sizing pass when buf == NULL). */
6007static void mp_put(char* buf, size_t* pos, const void* src, size_t n) {
6008 if (buf && n)
6009 memcpy(buf + *pos, src, n);
6010 *pos += n;
6011}
6012
6013/* Append a NUL-terminated string (length via strlen). */
6014static void mp_puts(char* buf, size_t* pos, const char* s) {
6015 if (s)
6016 mp_put(buf, pos, s, strlen(s));
6017}
6018
6019/* Emit the full multipart body into buf (or size it when buf == NULL). */
6020static void mp_emit(char* buf, size_t* pos, LIST* parts, const char* dash) {
6021 list_foreach(node, parts) {
6022 const N_HTTP_MULTIPART_PART* mp = (const N_HTTP_MULTIPART_PART*)node->ptr;
6023 if (!mp)
6024 continue;
6025 mp_puts(buf, pos, dash);
6026 mp_puts(buf, pos, "\r\nContent-Disposition: form-data; name=\"");
6027 mp_puts(buf, pos, mp->name ? mp->name : "");
6028 mp_puts(buf, pos, "\"");
6029 if (mp->filename && mp->filename[0]) {
6030 mp_puts(buf, pos, "; filename=\"");
6031 mp_puts(buf, pos, mp->filename);
6032 mp_puts(buf, pos, "\"");
6033 }
6034 mp_puts(buf, pos, "\r\n");
6035 if (mp->content_type && mp->content_type[0]) {
6036 mp_puts(buf, pos, "Content-Type: ");
6037 mp_puts(buf, pos, mp->content_type);
6038 mp_puts(buf, pos, "\r\n");
6039 }
6040 mp_puts(buf, pos, "\r\n");
6041 if (mp->body && mp->body->written)
6042 mp_put(buf, pos, mp->body->data, mp->body->written);
6043 mp_puts(buf, pos, "\r\n");
6044 }
6045 mp_puts(buf, pos, dash);
6046 mp_puts(buf, pos, "--\r\n");
6047}
6048
6049N_STR* n_http_build_multipart(LIST* parts, const char* boundary) {
6050 char dash[256];
6051 size_t total = 0;
6052 char* buf;
6053 N_STR* out = NULL;
6054 if (!parts || !boundary || !boundary[0])
6055 return NULL;
6056 if (strlen(boundary) + 2 >= sizeof(dash))
6057 return NULL;
6058 snprintf(dash, sizeof(dash), "--%s", boundary);
6059
6060 mp_emit(NULL, &total, parts, dash); /* sizing pass */
6061 Malloc(buf, char, total + 1);
6062 if (!buf)
6063 return NULL;
6064 total = 0;
6065 mp_emit(buf, &total, parts, dash); /* fill pass */
6066 buf[total] = '\0';
6067 char_to_nstr_ex(buf, (NSTRBYTE)total, &out);
6068 free(buf);
6069 return out;
6070}
6071
6078int netw_get_http_date(char* buffer, size_t buffer_size) {
6079 __n_assert(buffer, return FALSE);
6080 const time_t now = time(NULL);
6081 struct tm gmt;
6082#ifdef _WIN32
6083 if (gmtime_s(&gmt, &now) != 0) {
6084 n_log(LOG_ERR, "gmtime_s failed");
6085 return FALSE;
6086 }
6087#else
6088 if (!gmtime_r(&now, &gmt)) {
6089 n_log(LOG_ERR, "gmtime_r returned NULL");
6090 return FALSE;
6091 }
6092#endif
6093 if (strftime(buffer, buffer_size, "%a, %d %b %Y %H:%M:%S GMT", &gmt) == 0) {
6094 n_log(LOG_ERR, "strftime failed: buffer too small");
6095 return FALSE;
6096 }
6097 return TRUE;
6098}
6099
6110int 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) {
6111 __n_assert(server_name, return FALSE);
6112 __n_assert(content_type, return FALSE);
6113 __n_assert(additional_headers, return FALSE);
6114
6115 const char* status_message = netw_get_http_status_message(status_code);
6116 const char* connection_type = "close";
6117
6118 // Buffer for the date header
6119 char date_buffer[128] = "";
6120 netw_get_http_date(date_buffer, sizeof(date_buffer));
6121
6122 if ((*http_response)) {
6123 (*http_response)->written = 0;
6124 }
6125
6126 if (!body || body->written == 0) {
6127 // Handle the case where there is no body
6128 nstrprintf((*http_response),
6129 "HTTP/1.1 %d %s\r\n"
6130 "Date: %s\r\n"
6131 "Server: %s\r\n"
6132 "Content-Length: 0\r\n"
6133 "%s"
6134 "Connection: %s\r\n\r\n",
6135 status_code, status_message, date_buffer, server_name, additional_headers, connection_type);
6136 n_log(LOG_DEBUG, "empty response");
6137 } else {
6138 // Build the response with body
6139 nstrprintf((*http_response),
6140 "HTTP/1.1 %d %s\r\n"
6141 "Date: %s\r\n"
6142 "Server: %s\r\n"
6143 "Content-Type: %s\r\n"
6144 "Content-Length: %zu\r\n"
6145 "%s"
6146 "Connection: %s\r\n\r\n",
6147 status_code, status_message, date_buffer, server_name, content_type, body->written, additional_headers, connection_type);
6148 nstrcat((*http_response), body);
6149 n_log(LOG_DEBUG, "body response");
6150 }
6151 return TRUE;
6152}
6153
6154#ifdef HAVE_OPENSSL
6155
6161static ssize_t _ws_write(N_WS_CONN* conn, const void* buf, size_t len) {
6162 __n_assert(conn, return -1);
6163 __n_assert(conn->netw, return -1);
6164 if (conn->netw->crypto_algo == NETW_ENCRYPT_OPENSSL && conn->netw->ssl) {
6165 int ret = SSL_write(conn->netw->ssl, buf, (int)len);
6166 return (ret > 0) ? (ssize_t)ret : -1;
6167 }
6168 ssize_t ret = send(conn->netw->link.sock, buf, NETW_BUFLEN_CAST(len), NETFLAGS);
6169 return ret;
6170}
6171
6177static ssize_t _ws_read(N_WS_CONN* conn, void* buf, size_t len) {
6178 __n_assert(conn, return -1);
6179 __n_assert(conn->netw, return -1);
6180 size_t total = 0;
6181 char* p = (char*)buf;
6182 while (total < len) {
6183 ssize_t ret = 0;
6184 if (conn->netw->crypto_algo == NETW_ENCRYPT_OPENSSL && conn->netw->ssl) {
6185 ret = SSL_read(conn->netw->ssl, p + total, (int)(len - total));
6186 } else {
6187 ret = recv(conn->netw->link.sock, p + total, NETW_BUFLEN_CAST(len - total), 0);
6188 }
6189 if (ret <= 0) {
6190 return -1;
6191 }
6192 total += (size_t)ret;
6193 }
6194 return (ssize_t)total;
6195}
6196
6208N_WS_CONN* n_ws_connect(const char* host, const char* port, const char* path, int use_ssl) {
6209 __n_assert(host, return NULL);
6210 __n_assert(port, return NULL);
6211 __n_assert(path, return NULL);
6212
6213 N_WS_CONN* conn = NULL;
6214 Malloc(conn, N_WS_CONN, 1);
6215 __n_assert(conn, return NULL);
6216
6217 conn->netw = NULL;
6218 conn->connected = 0;
6219 conn->host = strdup(host);
6220 conn->path = strdup(path);
6221 if (!conn->host || !conn->path) {
6222 n_log(LOG_ERR, "n_ws_connect: strdup failed");
6223 goto ws_connect_fail;
6224 }
6225
6226 /* establish TCP + optional SSL */
6227 if (use_ssl) {
6228 if (netw_ssl_connect_client(&conn->netw, (char*)host, (char*)port, NETWORK_IPALL) == FALSE) {
6229 n_log(LOG_ERR, "n_ws_connect: TCP+SSL context setup failed for %s:%s", host, port);
6230 goto ws_connect_fail;
6231 }
6232 if (netw_ssl_do_handshake(conn->netw, host) == FALSE) {
6233 _netw_capture_error(conn->netw, "n_ws_connect: SSL handshake failed for %s:%s", host, port);
6234 n_log(LOG_ERR, "n_ws_connect: SSL handshake failed for %s:%s", host, port);
6235 goto ws_connect_fail;
6236 }
6237 } else {
6238 if (netw_connect(&conn->netw, (char*)host, (char*)port, NETWORK_IPALL) == FALSE) {
6239 n_log(LOG_ERR, "n_ws_connect: TCP connect failed for %s:%s", host, port);
6240 goto ws_connect_fail;
6241 }
6242 }
6243
6244 /* generate Sec-WebSocket-Key: base64 of 16 random bytes */
6245 unsigned char rand_bytes[16];
6246 if (RAND_bytes(rand_bytes, 16) != 1) {
6247 _netw_capture_error(conn->netw, "n_ws_connect: RAND_bytes failed");
6248 n_log(LOG_ERR, "n_ws_connect: RAND_bytes failed");
6249 goto ws_connect_fail;
6250 }
6251
6252 N_STR* rand_nstr = new_nstr(16);
6253 __n_assert(rand_nstr, goto ws_connect_fail);
6254 memcpy(rand_nstr->data, rand_bytes, 16);
6255 rand_nstr->written = 16;
6256
6257 N_STR* ws_key_nstr = n_base64_encode(rand_nstr);
6258 free_nstr(&rand_nstr);
6259 __n_assert(ws_key_nstr, goto ws_connect_fail);
6260 /* strip any trailing whitespace from base64 output */
6261 while (ws_key_nstr->written > 0 &&
6262 (ws_key_nstr->data[ws_key_nstr->written - 1] == '\n' ||
6263 ws_key_nstr->data[ws_key_nstr->written - 1] == '\r' ||
6264 ws_key_nstr->data[ws_key_nstr->written - 1] == ' ')) {
6265 ws_key_nstr->data[--ws_key_nstr->written] = '\0';
6266 }
6267
6268 /* build HTTP upgrade request */
6269 char upgrade_req[2048];
6270 int req_len = snprintf(upgrade_req, sizeof(upgrade_req),
6271 "GET %s HTTP/1.1\r\n"
6272 "Host: %s\r\n"
6273 "Upgrade: websocket\r\n"
6274 "Connection: Upgrade\r\n"
6275 "Sec-WebSocket-Key: %s\r\n"
6276 "Sec-WebSocket-Version: 13\r\n"
6277 "\r\n",
6278 path, host, ws_key_nstr->data);
6279
6280 if (req_len < 0 || (size_t)req_len >= sizeof(upgrade_req)) {
6281 _netw_capture_error(conn->netw, "n_ws_connect: upgrade request too large");
6282 n_log(LOG_ERR, "n_ws_connect: upgrade request too large");
6283 free_nstr(&ws_key_nstr);
6284 goto ws_connect_fail;
6285 }
6286
6287 /* send upgrade request */
6288 if (_ws_write(conn, upgrade_req, (size_t)req_len) < 0) {
6289 _netw_capture_error(conn->netw, "n_ws_connect: failed to send upgrade request");
6290 n_log(LOG_ERR, "n_ws_connect: failed to send upgrade request");
6291 free_nstr(&ws_key_nstr);
6292 goto ws_connect_fail;
6293 }
6294
6295 /* read response (up to 4096 bytes, look for \r\n\r\n) */
6296 char resp_buf[4096];
6297 memset(resp_buf, 0, sizeof(resp_buf));
6298 size_t resp_len = 0;
6299 while (resp_len < sizeof(resp_buf) - 1) {
6300 ssize_t r = 0;
6301 if (conn->netw->crypto_algo == NETW_ENCRYPT_OPENSSL && conn->netw->ssl) {
6302 r = SSL_read(conn->netw->ssl, resp_buf + resp_len, 1);
6303 } else {
6304 r = recv(conn->netw->link.sock, resp_buf + resp_len, 1, 0);
6305 }
6306 if (r <= 0) {
6307 _netw_capture_error(conn->netw, "n_ws_connect: failed reading handshake response");
6308 n_log(LOG_ERR, "n_ws_connect: failed reading handshake response");
6309 free_nstr(&ws_key_nstr);
6310 goto ws_connect_fail;
6311 }
6312 resp_len += (size_t)r;
6313 if (resp_len >= 4 &&
6314 resp_buf[resp_len - 4] == '\r' && resp_buf[resp_len - 3] == '\n' &&
6315 resp_buf[resp_len - 2] == '\r' && resp_buf[resp_len - 1] == '\n') {
6316 break;
6317 }
6318 }
6319 resp_buf[resp_len] = '\0';
6320
6321 /* verify 101 Switching Protocols */
6322 if (strstr(resp_buf, "101") == NULL) {
6323 _netw_capture_error(conn->netw, "n_ws_connect: server did not return 101: %.128s", resp_buf);
6324 n_log(LOG_ERR, "n_ws_connect: server did not return 101: %.128s", resp_buf);
6325 free_nstr(&ws_key_nstr);
6326 goto ws_connect_fail;
6327 }
6328
6329 /* verify Sec-WebSocket-Accept */
6330 static const char ws_magic[] = "258EAFA5-E914-47DA-95CA-5AB5FE44F513";
6331 char concat_key[256];
6332 snprintf(concat_key, sizeof(concat_key), "%s%s", ws_key_nstr->data, ws_magic);
6333 n_log(LOG_DEBUG, "n_ws_connect: key sent: [%s] len:%zu", ws_key_nstr->data, ws_key_nstr->written);
6334 n_log(LOG_DEBUG, "n_ws_connect: concat_key: [%s]", concat_key);
6335 free_nstr(&ws_key_nstr);
6336
6337 unsigned char sha1_hash[SHA_DIGEST_LENGTH];
6338 SHA1((const unsigned char*)concat_key, strlen(concat_key), sha1_hash);
6339
6340 N_STR* sha1_nstr = new_nstr(SHA_DIGEST_LENGTH);
6341 __n_assert(sha1_nstr, goto ws_connect_fail);
6342 memcpy(sha1_nstr->data, sha1_hash, SHA_DIGEST_LENGTH);
6343 sha1_nstr->written = SHA_DIGEST_LENGTH;
6344
6345 N_STR* expected_accept = n_base64_encode(sha1_nstr);
6346 free_nstr(&sha1_nstr);
6347 __n_assert(expected_accept, goto ws_connect_fail);
6348 /* strip trailing whitespace from base64 output */
6349 while (expected_accept->written > 0 &&
6350 (expected_accept->data[expected_accept->written - 1] == '\n' ||
6351 expected_accept->data[expected_accept->written - 1] == '\r' ||
6352 expected_accept->data[expected_accept->written - 1] == ' ')) {
6353 expected_accept->data[--expected_accept->written] = '\0';
6354 }
6355
6356 /* find Sec-WebSocket-Accept in response (case-insensitive search) */
6357 const char* accept_hdr = NULL;
6358 char* search_pos = resp_buf;
6359 while (*search_pos) {
6360 if (strncasecmp(search_pos, "Sec-WebSocket-Accept:", 21) == 0) {
6361 accept_hdr = search_pos + 21;
6362 break;
6363 }
6364 search_pos++;
6365 }
6366 if (!accept_hdr) {
6367 _netw_capture_error(conn->netw, "n_ws_connect: no Sec-WebSocket-Accept header in response");
6368 n_log(LOG_ERR, "n_ws_connect: no Sec-WebSocket-Accept header in response");
6369 free_nstr(&expected_accept);
6370 goto ws_connect_fail;
6371 }
6372 /* skip whitespace */
6373 while (*accept_hdr == ' ') accept_hdr++;
6374 /* compare up to expected length; trim trailing \r\n from accept_hdr */
6375 char accept_val[128];
6376 {
6377 int ai = 0;
6378 while (ai < (int)sizeof(accept_val) - 1 && accept_hdr[ai] && accept_hdr[ai] != '\r' && accept_hdr[ai] != '\n') {
6379 accept_val[ai] = accept_hdr[ai];
6380 ai++;
6381 }
6382 accept_val[ai] = '\0';
6383 }
6384 if (strcmp(accept_val, expected_accept->data) != 0) {
6385 /* Many proxies (Fly.io, Cloudflare, etc.) re-key the handshake,
6386 * so the accept may not match. Log as debug, not error. */
6387 n_log(LOG_DEBUG, "n_ws_connect: Sec-WebSocket-Accept mismatch (proxy?): got [%s] expected [%s]",
6388 accept_val, expected_accept->data);
6389 }
6390 free_nstr(&expected_accept);
6391
6392 conn->connected = 1;
6393 n_log(LOG_INFO, "n_ws_connect: WebSocket handshake completed with %s:%s%s", host, port, path);
6394 return conn;
6395
6396ws_connect_fail:
6397 n_ws_conn_free(&conn);
6398 return NULL;
6399}
6400
6412int n_ws_send(N_WS_CONN* conn, const char* payload, size_t len, int opcode) {
6413 __n_assert(conn, return -1);
6414 __n_assert(conn->netw, return -1);
6415
6416 /* max frame overhead: 2 (header) + 8 (ext len) + 4 (mask) = 14 */
6417 size_t frame_max = 14 + len;
6418 unsigned char* frame = NULL;
6419 Malloc(frame, unsigned char, frame_max);
6420 __n_assert(frame, return -1);
6421
6422 size_t pos = 0;
6423
6424 /* byte 0: FIN + opcode */
6425 frame[pos++] = (unsigned char)(0x80 | (opcode & 0x0F));
6426
6427 /* byte 1+: mask bit set + payload length */
6428 if (len <= 125) {
6429 frame[pos++] = (unsigned char)(0x80 | len);
6430 } else if (len <= 65535) {
6431 frame[pos++] = (unsigned char)(0x80 | 126);
6432 frame[pos++] = (unsigned char)((len >> 8) & 0xFF);
6433 frame[pos++] = (unsigned char)(len & 0xFF);
6434 } else {
6435 frame[pos++] = (unsigned char)(0x80 | 127);
6436 for (int i = 7; i >= 0; i--) {
6437 frame[pos++] = (unsigned char)((len >> (8 * i)) & 0xFF);
6438 }
6439 }
6440
6441 /* masking key: 4 random bytes */
6442 unsigned char mask_key[4];
6443 if (RAND_bytes(mask_key, 4) != 1) {
6444 _netw_capture_error(conn->netw, "n_ws_send: RAND_bytes failed for mask key");
6445 n_log(LOG_ERR, "n_ws_send: RAND_bytes failed for mask key");
6446 FreeNoLog(frame);
6447 return -1;
6448 }
6449 memcpy(frame + pos, mask_key, 4);
6450 pos += 4;
6451
6452 /* masked payload */
6453 if (payload && len > 0) {
6454 for (size_t i = 0; i < len; i++) {
6455 frame[pos + i] = (unsigned char)((unsigned char)payload[i] ^ mask_key[i % 4]);
6456 }
6457 pos += len;
6458 }
6459
6460 ssize_t written = _ws_write(conn, frame, pos);
6461 FreeNoLog(frame);
6462 if (written < 0 || (size_t)written != pos) {
6463 _netw_capture_error(conn->netw, "n_ws_send: write failed (wrote %zd of %zu)", written, pos);
6464 n_log(LOG_ERR, "n_ws_send: write failed (wrote %zd of %zu)", written, pos);
6465 return -1;
6466 }
6467 return 0;
6468}
6469
6479int n_ws_recv(N_WS_CONN* conn, N_WS_MESSAGE* msg_out) {
6480 __n_assert(conn, return -1);
6481 __n_assert(conn->netw, return -1);
6482 __n_assert(msg_out, return -1);
6483
6484 memset(msg_out, 0, sizeof(*msg_out));
6485
6486 /* read 2-byte header */
6487 unsigned char hdr[2];
6488 if (_ws_read(conn, hdr, 2) < 0) {
6489 return -1;
6490 }
6491
6492 /* int fin = (hdr[0] >> 7) & 1; */
6493 int opcode = hdr[0] & 0x0F;
6494 int mask_bit = (hdr[1] >> 7) & 1;
6495 uint64_t payload_len = hdr[1] & 0x7F;
6496
6497 /* extended payload length */
6498 if (payload_len == 126) {
6499 unsigned char ext[2];
6500 if (_ws_read(conn, ext, 2) < 0) return -1;
6501 payload_len = ((uint64_t)ext[0] << 8) | (uint64_t)ext[1];
6502 } else if (payload_len == 127) {
6503 unsigned char ext[8];
6504 if (_ws_read(conn, ext, 8) < 0) return -1;
6505 payload_len = 0;
6506 for (int i = 0; i < 8; i++) {
6507 payload_len = (payload_len << 8) | (uint64_t)ext[i];
6508 }
6509 }
6510
6511 /* read masking key if present */
6512 unsigned char mask_key[4] = {0};
6513 if (mask_bit) {
6514 if (_ws_read(conn, mask_key, 4) < 0) return -1;
6515 }
6516
6517 /* read payload */
6518 N_STR* payload = new_nstr((size_t)payload_len + 1);
6519 __n_assert(payload, return -1);
6520
6521 if (payload_len > 0) {
6522 if (_ws_read(conn, payload->data, (size_t)payload_len) < 0) {
6523 free_nstr(&payload);
6524 return -1;
6525 }
6526 /* unmask if needed */
6527 if (mask_bit) {
6528 for (uint64_t i = 0; i < payload_len; i++) {
6529 payload->data[i] = (char)((unsigned char)payload->data[i] ^ mask_key[i % 4]);
6530 }
6531 }
6532 }
6533 payload->written = (size_t)payload_len;
6534 payload->data[payload_len] = '\0';
6535
6536 msg_out->opcode = opcode;
6537 msg_out->payload = payload;
6538 msg_out->masked = mask_bit;
6539
6540 return 0;
6541}
6542
6552 __n_assert(conn, return);
6553
6554 if (conn->connected && conn->netw) {
6555 /* send close frame with empty payload */
6556 n_ws_send(conn, NULL, 0, N_WS_OP_CLOSE);
6557
6558 /* try to read close response with a short timeout */
6559 /* set non-blocking or just try one read */
6560 N_WS_MESSAGE msg;
6561 memset(&msg, 0, sizeof(msg));
6562 /* best effort: read one frame, ignore errors */
6563 if (n_ws_recv(conn, &msg) == 0) {
6564 free_nstr(&msg.payload);
6565 }
6566 conn->connected = 0;
6567 }
6568
6569 if (conn->netw) {
6570 netw_close(&conn->netw);
6571 }
6572}
6573
6582 __n_assert(conn, return);
6583 __n_assert(*conn, return);
6584
6585 N_WS_CONN* c = *conn;
6586
6587 if (c->connected) {
6588 n_ws_close(c);
6589 }
6590
6591 if (c->netw) {
6592 netw_close(&c->netw);
6593 }
6594
6595 FreeNoLog(c->host);
6596 FreeNoLog(c->path);
6597 FreeNoLog(c);
6598 *conn = NULL;
6599}
6600
6606 if (!event) return;
6607 if (event->event) free_nstr(&event->event);
6608 if (event->data) free_nstr(&event->data);
6609 if (event->id) free_nstr(&event->id);
6610 event->retry = 0;
6611}
6612
6618 __n_assert(conn, return);
6619 __atomic_store_n(&conn->stop_flag, 1, __ATOMIC_RELEASE);
6620}
6621
6627 __n_assert(conn, return);
6628 __n_assert(*conn, return);
6629
6630 N_SSE_CONN* c = *conn;
6631 if (c->netw) {
6632 netw_close(&c->netw);
6633 }
6634 FreeNoLog(c);
6635 *conn = NULL;
6636}
6637
6644static ssize_t _sse_read_byte(NETWORK* netw, char* ch, volatile int* stop_flag) {
6645 __n_assert(netw, return -1);
6646
6647 /* poll with 500ms timeout so we can check stop_flag periodically */
6648 for (;;) {
6649 if (stop_flag && __atomic_load_n(stop_flag, __ATOMIC_ACQUIRE)) {
6650 return -1;
6651 }
6652 struct timeval tv;
6653 tv.tv_sec = 0;
6654 tv.tv_usec = 500000; /* 500ms */
6655 fd_set fds;
6656 FD_ZERO(&fds);
6657 FD_SET(netw->link.sock, &fds);
6658
6659 /* For SSL: check if there's already buffered data */
6660 int ready = 0;
6662 if (SSL_pending(netw->ssl) > 0) {
6663 ready = 1;
6664 }
6665 }
6666 if (!ready) {
6667 int sel = select((int)netw->link.sock + 1, &fds, NULL, NULL, &tv);
6668 if (sel < 0) return -1; /* error */
6669 if (sel == 0) continue; /* timeout, loop back to check stop_flag */
6670 }
6671
6672 ssize_t ret = 0;
6674 ret = SSL_read(netw->ssl, ch, 1);
6675 } else {
6676 ret = recv(netw->link.sock, ch, 1, 0);
6677 }
6678 return (ret == 1) ? 1 : -1;
6679 }
6680}
6681
6687static ssize_t _sse_write(NETWORK* netw, const void* buf, size_t len) {
6688 __n_assert(netw, return -1);
6690 int ret = SSL_write(netw->ssl, buf, (int)len);
6691 return (ret > 0) ? (ssize_t)ret : -1;
6692 }
6693 ssize_t ret = send(netw->link.sock, buf, NETW_BUFLEN_CAST(len), NETFLAGS);
6694 return ret;
6695}
6696
6711N_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) {
6712 __n_assert(host, return NULL);
6713 __n_assert(port, return NULL);
6714 __n_assert(path, return NULL);
6715 __n_assert(on_event, return NULL);
6716
6717 N_SSE_CONN* conn = NULL;
6718 Malloc(conn, N_SSE_CONN, 1);
6719 __n_assert(conn, return NULL);
6720
6721 conn->netw = NULL;
6722 conn->stop_flag = 0;
6723 conn->on_event = on_event;
6724 conn->user_data = user_data;
6725
6726 /* establish TCP + optional SSL */
6727 if (use_ssl) {
6728 if (netw_ssl_connect_client(&conn->netw, (char*)host, (char*)port, NETWORK_IPALL) == FALSE) {
6729 n_log(LOG_ERR, "n_sse_connect: TCP+SSL context setup failed for %s:%s", host, port);
6730 goto sse_connect_fail;
6731 }
6732 if (netw_ssl_do_handshake(conn->netw, host) == FALSE) {
6733 _netw_capture_error(conn->netw, "n_sse_connect: SSL handshake failed for %s:%s", host, port);
6734 n_log(LOG_ERR, "n_sse_connect: SSL handshake failed for %s:%s", host, port);
6735 goto sse_connect_fail;
6736 }
6737 } else {
6738 if (netw_connect(&conn->netw, (char*)host, (char*)port, NETWORK_IPALL) == FALSE) {
6739 n_log(LOG_ERR, "n_sse_connect: TCP connect failed for %s:%s", host, port);
6740 goto sse_connect_fail;
6741 }
6742 }
6743
6744 /* send HTTP GET with SSE headers */
6745 char request[4096];
6746 int req_len;
6747 if (user_agent && user_agent[0]) {
6748 req_len = snprintf(request, sizeof(request),
6749 "GET %s HTTP/1.1\r\n"
6750 "Host: %s\r\n"
6751 "Accept: text/event-stream\r\n"
6752 "Cache-Control: no-cache\r\n"
6753 "Connection: keep-alive\r\n"
6754 "User-Agent: %s\r\n"
6755 "\r\n",
6756 path, host, user_agent);
6757 } else {
6758 req_len = snprintf(request, sizeof(request),
6759 "GET %s HTTP/1.1\r\n"
6760 "Host: %s\r\n"
6761 "Accept: text/event-stream\r\n"
6762 "Cache-Control: no-cache\r\n"
6763 "Connection: keep-alive\r\n"
6764 "\r\n",
6765 path, host);
6766 }
6767
6768 if (req_len < 0 || (size_t)req_len >= sizeof(request)) {
6769 _netw_capture_error(conn->netw, "n_sse_connect: request too large");
6770 n_log(LOG_ERR, "n_sse_connect: request too large");
6771 goto sse_connect_fail;
6772 }
6773
6774 if (_sse_write(conn->netw, request, (size_t)req_len) < 0) {
6775 _netw_capture_error(conn->netw, "n_sse_connect: failed to send HTTP request");
6776 n_log(LOG_ERR, "n_sse_connect: failed to send HTTP request");
6777 goto sse_connect_fail;
6778 }
6779
6780 /* read HTTP response headers (byte by byte until \r\n\r\n) */
6781 char resp_buf[8192];
6782 size_t resp_len = 0;
6783 while (resp_len < sizeof(resp_buf) - 1) {
6784 char ch = 0;
6785 if (_sse_read_byte(conn->netw, &ch, NULL) < 0) {
6786 _netw_capture_error(conn->netw, "n_sse_connect: failed reading HTTP response");
6787 n_log(LOG_ERR, "n_sse_connect: failed reading HTTP response");
6788 goto sse_connect_fail;
6789 }
6790 resp_buf[resp_len++] = ch;
6791 if (resp_len >= 4 &&
6792 resp_buf[resp_len - 4] == '\r' && resp_buf[resp_len - 3] == '\n' &&
6793 resp_buf[resp_len - 2] == '\r' && resp_buf[resp_len - 1] == '\n') {
6794 break;
6795 }
6796 }
6797 resp_buf[resp_len] = '\0';
6798
6799 /* verify HTTP 200 status */
6800 if (strstr(resp_buf, "200") == NULL) {
6801 _netw_capture_error(conn->netw, "n_sse_connect: server did not return 200: %.128s", resp_buf);
6802 n_log(LOG_ERR, "n_sse_connect: server did not return 200: %.128s", resp_buf);
6803 goto sse_connect_fail;
6804 }
6805
6806 n_log(LOG_INFO, "n_sse_connect: SSE connected to %s:%s%s", host, port, path);
6807
6808 /* enter SSE read loop */
6809 {
6810 N_SSE_EVENT current;
6811 memset(&current, 0, sizeof(current));
6812
6813 /* line buffer for SSE parsing */
6814 char line[8192];
6815 size_t line_len = 0;
6816
6817 while (!__atomic_load_n(&conn->stop_flag, __ATOMIC_ACQUIRE)) {
6818 char ch = 0;
6819 if (_sse_read_byte(conn->netw, &ch, &conn->stop_flag) < 0) {
6820 n_log(LOG_DEBUG, "n_sse_connect: connection closed or read error");
6821 break;
6822 }
6823
6824 if (ch == '\n') {
6825 /* terminate line (strip trailing \r) */
6826 if (line_len > 0 && line[line_len - 1] == '\r') {
6827 line_len--;
6828 }
6829 line[line_len] = '\0';
6830
6831 if (line_len == 0) {
6832 /* empty line = dispatch event if we have data */
6833 if (current.data) {
6834 conn->on_event(&current, conn, conn->user_data);
6835 n_sse_event_clean(&current);
6836 memset(&current, 0, sizeof(current));
6837 }
6838 } else if (line[0] == ':') {
6839 /* comment line, ignore */
6840 n_log(LOG_DEBUG, "n_sse_connect: comment: %s", line + 1);
6841 } else {
6842 /* parse field:value */
6843 char* colon = strchr(line, ':');
6844 const char* field = line;
6845 const char* value = "";
6846 if (colon) {
6847 *colon = '\0';
6848 value = colon + 1;
6849 /* skip single leading space after colon */
6850 if (*value == ' ') value++;
6851 }
6852
6853 if (strcmp(field, "data") == 0) {
6854 if (current.data) {
6855 /* append newline + value to existing data */
6856 nstrprintf_cat(current.data, "\n%s", value);
6857 } else {
6858 current.data = char_to_nstr((char*)value);
6859 }
6860 } else if (strcmp(field, "event") == 0) {
6861 if (current.event) free_nstr(&current.event);
6862 current.event = char_to_nstr((char*)value);
6863 } else if (strcmp(field, "id") == 0) {
6864 if (current.id) free_nstr(&current.id);
6865 current.id = char_to_nstr((char*)value);
6866 } else if (strcmp(field, "retry") == 0) {
6867 current.retry = atoi(value);
6868 }
6869 }
6870
6871 line_len = 0;
6872 } else {
6873 if (line_len < sizeof(line) - 1) {
6874 line[line_len++] = ch;
6875 }
6876 }
6877 }
6878
6879 /* clean up any partial event */
6880 n_sse_event_clean(&current);
6881 }
6882
6883 return conn;
6884
6885sse_connect_fail:
6886 n_sse_conn_free(&conn);
6887 return NULL;
6888}
6889
6890#endif /* HAVE_OPENSSL */
6891
6894#define N_WS_FRAME_MAX_PAYLOAD (16U * 1024U * 1024U)
6895
6905int n_ws_frame_parse(const unsigned char* buf, size_t len, N_WS_FRAME* frame, size_t* consumed) {
6906 size_t hdr = 2;
6907 uint64_t plen;
6908 int len7;
6909 if (!buf || !frame || !consumed)
6910 return -1;
6911 if (len < 2)
6912 return 0;
6913 frame->fin = (buf[0] & 0x80) ? 1 : 0;
6914 frame->rsv = (buf[0] >> 4) & 0x07;
6915 frame->opcode = buf[0] & 0x0F;
6916 frame->masked = (buf[1] & 0x80) ? 1 : 0;
6917 len7 = buf[1] & 0x7F;
6918 if (len7 < 126) {
6919 plen = (uint64_t)len7;
6920 } else if (len7 == 126) {
6921 if (len < 4)
6922 return 0;
6923 plen = ((uint64_t)buf[2] << 8) | (uint64_t)buf[3];
6924 hdr = 4;
6925 } else {
6926 int i;
6927 if (len < 10)
6928 return 0;
6929 plen = 0;
6930 for (i = 0; i < 8; i++)
6931 plen = (plen << 8) | (uint64_t)buf[2 + i];
6932 hdr = 10;
6933 }
6934 if (plen > N_WS_FRAME_MAX_PAYLOAD)
6935 return -1;
6936 if (frame->masked) {
6937 if (len < hdr + 4)
6938 return 0;
6939 memcpy(frame->mask, buf + hdr, 4);
6940 hdr += 4;
6941 } else {
6942 memset(frame->mask, 0, 4);
6943 }
6944 if (len < hdr + plen)
6945 return 0;
6946 frame->payload_len = plen;
6947 frame->payload = buf + hdr;
6948 *consumed = hdr + (size_t)plen;
6949 return 1;
6950}
6951
6952void n_ws_unmask(unsigned char* dst, const unsigned char* src, size_t len, const unsigned char mask[4]) {
6953 size_t i;
6954 if (!dst || !src || !mask)
6955 return;
6956 for (i = 0; i < len; i++)
6957 dst[i] = (unsigned char)(src[i] ^ mask[i & 3]);
6958}
6959
6960size_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) {
6961 size_t pos = 0;
6962 unsigned char b1 = (unsigned char)(do_mask ? 0x80 : 0);
6963 if (!out || out_cap < 2)
6964 return 0;
6965 out[pos++] = (unsigned char)((fin ? 0x80 : 0) | (opcode & 0x0F));
6966 if (len <= 125) {
6967 out[pos++] = (unsigned char)(b1 | (unsigned char)len);
6968 } else if (len <= 65535) {
6969 if (out_cap < pos + 3)
6970 return 0;
6971 out[pos++] = (unsigned char)(b1 | 126);
6972 out[pos++] = (unsigned char)((len >> 8) & 0xFF);
6973 out[pos++] = (unsigned char)(len & 0xFF);
6974 } else {
6975 int i;
6976 if (out_cap < pos + 9)
6977 return 0;
6978 out[pos++] = (unsigned char)(b1 | 127);
6979 for (i = 7; i >= 0; i--)
6980 out[pos++] = (unsigned char)((len >> (8 * i)) & 0xFF);
6981 }
6982 if (do_mask) {
6983 if (!mask_key || out_cap < pos + 4)
6984 return 0;
6985 memcpy(out + pos, mask_key, 4);
6986 pos += 4;
6987 }
6988 if (out_cap < pos + len)
6989 return 0;
6990 if (do_mask && mask_key) {
6991 size_t i;
6992 for (i = 0; i < len; i++)
6993 out[pos + i] = (unsigned char)((payload ? payload[i] : 0) ^ mask_key[i & 3]);
6994 } else if (payload && len > 0) {
6995 memcpy(out + pos, payload, len);
6996 }
6997 pos += len;
6998 return pos;
6999}
7000
7006static void _n_mock_parse_request(const char* buf, N_HTTP_REQUEST* req) {
7007 if (!buf || !req) return;
7008
7009 /* parse request line: METHOD PATH?QUERY HTTP/1.x */
7010 const char* line_end = strstr(buf, "\r\n");
7011 if (!line_end) line_end = strchr(buf, '\n');
7012 if (!line_end) return;
7013
7014 size_t line_len = (size_t)(line_end - buf);
7015 char line[4096];
7016 if (line_len >= sizeof(line)) line_len = sizeof(line) - 1;
7017 memcpy(line, buf, line_len);
7018 line[line_len] = '\0';
7019
7020 /* method */
7021 char* sp1 = strchr(line, ' ');
7022 if (!sp1) return;
7023 size_t mlen = (size_t)(sp1 - line);
7024 if (mlen >= sizeof(req->method)) mlen = sizeof(req->method) - 1;
7025 memcpy(req->method, line, mlen);
7026 req->method[mlen] = '\0';
7027
7028 /* path and query */
7029 sp1++;
7030 char* sp2 = strchr(sp1, ' ');
7031 if (sp2) *sp2 = '\0';
7032 char* qmark = strchr(sp1, '?');
7033 if (qmark) {
7034 *qmark = '\0';
7035 strncpy(req->query, qmark + 1, sizeof(req->query) - 1);
7036 req->query[sizeof(req->query) - 1] = '\0';
7037 }
7038 strncpy(req->path, sp1, sizeof(req->path) - 1);
7039 req->path[sizeof(req->path) - 1] = '\0';
7040
7041 /* headers: skip past request line */
7042 const char* hdr_start = line_end;
7043 if (*hdr_start == '\r') hdr_start++;
7044 if (*hdr_start == '\n') hdr_start++;
7045
7046 req->headers = new_generic_list(0);
7047
7048 const char* body_start = NULL;
7049 while (hdr_start && *hdr_start) {
7050 const char* next = strstr(hdr_start, "\r\n");
7051 if (!next) next = strchr(hdr_start, '\n');
7052 if (!next) break;
7053
7054 size_t hlen = (size_t)(next - hdr_start);
7055 if (hlen == 0) {
7056 /* blank line = end of headers */
7057 body_start = next;
7058 if (*body_start == '\r') body_start++;
7059 if (*body_start == '\n') body_start++;
7060 break;
7061 }
7062
7063 char* header_line = strndup(hdr_start, hlen);
7064 if (header_line) {
7065 list_push(req->headers, header_line, free);
7066 }
7067
7068 hdr_start = next;
7069 if (*hdr_start == '\r') hdr_start++;
7070 if (*hdr_start == '\n') hdr_start++;
7071 }
7072
7073 /* body */
7074 if (body_start && *body_start) {
7075 req->body = char_to_nstr(body_start);
7076 }
7077}
7078
7084 if (!req) return;
7085 if (req->headers) list_destroy(&req->headers);
7086 if (req->body) free_nstr(&req->body);
7087}
7088
7097 void (*on_request)(N_HTTP_REQUEST*, N_HTTP_RESPONSE*, void*),
7098 void* user_data) {
7099 __n_assert(on_request, return NULL);
7100
7101 N_MOCK_SERVER* server = NULL;
7103 __n_assert(server, return NULL);
7104 memset(server, 0, sizeof(*server));
7105
7106 server->on_request = on_request;
7107 server->user_data = user_data;
7108 server->port = port;
7109 server->stop_flag = 0;
7110
7111 char port_str[16];
7112 snprintf(port_str, sizeof(port_str), "%d", port);
7113
7114 NETWORK* listener = NULL;
7115 if (netw_make_listening(&listener, NULL, port_str, 5, NETWORK_IPALL) != TRUE) {
7116 n_log(LOG_ERR, "n_mock_server_start: failed to listen on port %d", port);
7118 return NULL;
7119 }
7120 server->listener = listener;
7121
7122 n_log(LOG_INFO, "mock server listening on port %d", port);
7123 return server;
7124}
7125
7131 __n_assert(server, return);
7132 __n_assert(server->listener, return);
7133
7134 while (!__atomic_load_n(&server->stop_flag, __ATOMIC_ACQUIRE)) {
7135 /* Use select with timeout to avoid blocking forever */
7136 fd_set readfds;
7137 FD_ZERO(&readfds);
7138 FD_SET(server->listener->link.sock, &readfds);
7139 struct timeval tv;
7140 tv.tv_sec = 0;
7141 tv.tv_usec = 200000; /* 200ms */
7142 int sel = select((int)(server->listener->link.sock + 1), &readfds, NULL, NULL, &tv);
7143 if (sel <= 0) continue;
7144
7145 /* Accept connection using netw_accept_from_ex with 1000ms timeout */
7146 int ret = 0;
7147 NETWORK* client = netw_accept_from_ex(server->listener, 0, 0, 1000, &ret);
7148 if (!client) continue;
7149
7150 /* Read HTTP request */
7151 char buf[8192];
7152 ssize_t n = recv(client->link.sock, buf, sizeof(buf) - 1, 0);
7153 if (n <= 0) {
7154 netw_close(&client);
7155 continue;
7156 }
7157 buf[n] = '\0';
7158
7159 /* Parse request */
7160 N_HTTP_REQUEST req;
7161 memset(&req, 0, sizeof(req));
7162 _n_mock_parse_request(buf, &req);
7163
7164 /* Prepare default response */
7165 N_HTTP_RESPONSE resp;
7166 memset(&resp, 0, sizeof(resp));
7167 resp.status_code = 404;
7168 strncpy(resp.content_type, "text/plain", sizeof(resp.content_type) - 1);
7169
7170 /* Call handler */
7171 server->on_request(&req, &resp, server->user_data);
7172
7173 /* Build HTTP response */
7174 const char* status_msg = netw_get_http_status_message(resp.status_code);
7175 if (!status_msg) status_msg = "Unknown";
7176
7177 size_t body_len = 0;
7178 const char* body_data = "";
7179 if (resp.body && resp.body->data) {
7180 body_data = resp.body->data;
7181 body_len = resp.body->written;
7182 }
7183
7184 char header_buf[1024];
7185 int hlen = snprintf(header_buf, sizeof(header_buf),
7186 "HTTP/1.1 %d %s\r\n"
7187 "Content-Type: %s\r\n"
7188 "Content-Length: %zu\r\n"
7189 "Connection: close\r\n"
7190 "\r\n",
7191 resp.status_code, status_msg,
7192 resp.content_type,
7193 body_len);
7194
7195 if (hlen > 0) {
7196 send(client->link.sock, header_buf, NETW_BUFLEN_CAST(hlen), NETFLAGS);
7197 }
7198 if (body_len > 0) {
7199 send(client->link.sock, body_data, NETW_BUFLEN_CAST(body_len), NETFLAGS);
7200 }
7201
7202 /* Cleanup */
7203 if (resp.body) free_nstr(&resp.body);
7205 netw_close(&client);
7206 }
7207
7208 n_log(LOG_INFO, "mock server stopped");
7209}
7210
7216 __n_assert(server, return);
7217 __atomic_store_n(&server->stop_flag, 1, __ATOMIC_RELEASE);
7218}
7219
7225 __n_assert(server && *server, return);
7226 if ((*server)->listener) {
7227 netw_close(&(*server)->listener);
7228 }
7229 FreeNoLog(*server);
7230 *server = NULL;
7231}
7232
7233static void _n_parse_query_params(N_URL* u, const char* qs) {
7234 if (!u || !qs || !qs[0]) return;
7235 char* buf = strdup(qs);
7236 if (!buf) return;
7237 char* saveptr = NULL;
7238 const char* tok = strtok_r(buf, "&", &saveptr);
7239 while (tok && u->nb_params < N_URL_MAX_PARAMS) {
7240 char* eq = strchr(tok, '=');
7241 if (eq) {
7242 *eq = '\0';
7243 u->params[u->nb_params].key = strdup(tok);
7244 u->params[u->nb_params].value = strdup(eq + 1);
7245 } else {
7246 u->params[u->nb_params].key = strdup(tok);
7247 u->params[u->nb_params].value = strdup("");
7248 }
7249 u->nb_params++;
7250 tok = strtok_r(NULL, "&", &saveptr);
7251 }
7252 free(buf);
7253}
7254
7255N_URL* n_url_parse(const char* url) {
7256 if (!url) return NULL;
7257 N_URL* u = NULL;
7258 Malloc(u, N_URL, 1);
7259 if (!u) return NULL;
7260 memset(u, 0, sizeof(*u));
7261 const char* p = url;
7262 const char* scheme_end = strstr(p, "://");
7263 if (scheme_end) {
7264 u->scheme = strndup(p, (size_t)(scheme_end - p));
7265 p = scheme_end + 3;
7266 } else {
7267 u->scheme = strdup("http");
7268 }
7269 const char* host_end = p;
7270 while (*host_end && *host_end != '/' && *host_end != '?' && *host_end != ':') host_end++;
7271 u->host = strndup(p, (size_t)(host_end - p));
7272 p = host_end;
7273 if (*p == ':') {
7274 p++;
7275 u->port = atoi(p);
7276 while (*p && *p != '/' && *p != '?') p++;
7277 }
7278 if (*p == '/') {
7279 const char* pe = p;
7280 while (*pe && *pe != '?') pe++;
7281 u->path = strndup(p, (size_t)(pe - p));
7282 p = pe;
7283 } else {
7284 u->path = strdup("/");
7285 }
7286 if (*p == '?') {
7287 p++;
7288 u->query = strdup(p);
7290 }
7291 return u;
7292}
7293
7295 if (!u) return NULL;
7296 N_STR* result = new_nstr(512);
7297 if (!result) return NULL;
7298 nstrprintf(result, "%s://%s", u->scheme ? u->scheme : "http", u->host ? u->host : "localhost");
7299 if (u->port > 0) {
7300 int dp = (u->scheme && strcmp(u->scheme, "https") == 0) ? 443 : 80;
7301 if (u->port != dp) nstrprintf_cat(result, ":%d", u->port);
7302 }
7303 nstrprintf_cat(result, "%s", u->path ? u->path : "/");
7304 if (u->query && u->query[0]) nstrprintf_cat(result, "?%s", u->query);
7305 return result;
7306}
7307
7314static void _n_url_normalize_path(const char* path, char* out, size_t outsz) {
7315 const char* seg_starts[256];
7316 size_t seg_lens[256];
7317 int nb = 0;
7318 const char* p = (path && path[0]) ? path : "/";
7319 size_t pos = 0;
7320 int i;
7321 /* split on '/', applying the dot-segment rules */
7322 while (*p) {
7323 const char* slash;
7324 size_t slen;
7325 if (*p == '/') {
7326 p++;
7327 continue;
7328 }
7329 slash = strchr(p, '/');
7330 slen = slash ? (size_t)(slash - p) : strlen(p);
7331 if (slen == 1 && p[0] == '.') {
7332 /* skip "." */
7333 } else if (slen == 2 && p[0] == '.' && p[1] == '.') {
7334 if (nb > 0) nb--; /* pop one segment */
7335 } else if (nb < 256) {
7336 seg_starts[nb] = p;
7337 seg_lens[nb] = slen;
7338 nb++;
7339 }
7340 p += slen;
7341 }
7342 /* rebuild with a leading slash */
7343 if (outsz == 0) return;
7344 out[pos++] = '/';
7345 for (i = 0; i < nb && pos < outsz - 1; i++) {
7346 size_t k;
7347 if (i > 0 && pos < outsz - 1) out[pos++] = '/';
7348 for (k = 0; k < seg_lens[i] && pos < outsz - 1; k++)
7349 out[pos++] = seg_starts[i][k];
7350 }
7351 /* preserve a trailing slash from the original (it can be significant) */
7352 if (path && path[0] && path[strlen(path) - 1] == '/' && pos < outsz - 1 && (nb > 0))
7353 out[pos++] = '/';
7354 out[pos] = '\0';
7355}
7356
7367 N_STR* result = NULL;
7368 char scheme[16];
7369 char host[256];
7370 char path[2048];
7371 size_t i;
7372 if (!u) return NULL;
7373 snprintf(scheme, sizeof(scheme), "%s", u->scheme ? u->scheme : "http");
7374 snprintf(host, sizeof(host), "%s", u->host ? u->host : "");
7375 for (i = 0; scheme[i]; i++) scheme[i] = (char)tolower((unsigned char)scheme[i]);
7376 for (i = 0; host[i]; i++) host[i] = (char)tolower((unsigned char)host[i]);
7377 _n_url_normalize_path(u->path, path, sizeof(path));
7378 result = new_nstr(2560);
7379 if (!result) return NULL;
7380 nstrprintf(result, "%s://%s", scheme, host);
7381 if (u->port > 0) {
7382 int dp = (strcmp(scheme, "https") == 0) ? 443 : 80;
7383 if (u->port != dp) nstrprintf_cat(result, ":%d", u->port);
7384 }
7385 nstrprintf_cat(result, "%s", path);
7386 if (u->query && u->query[0]) nstrprintf_cat(result, "?%s", u->query);
7387 return result;
7388}
7389
7396 N_URL* u = n_url_parse(url);
7397 N_STR* out;
7398 if (!u) return NULL;
7399 out = n_url_canonicalize(u);
7400 n_url_free(&u);
7401 return out;
7402}
7403
7409static int _n_url_has_scheme(const char* s) {
7410 if (!s || !isalpha((unsigned char)s[0])) return 0;
7411 size_t i = 1;
7412 while (s[i] && (isalnum((unsigned char)s[i]) || s[i] == '+' || s[i] == '-' || s[i] == '.')) i++;
7413 return s[i] == ':';
7414}
7415
7433N_STR* n_url_resolve(const char* base, const char* ref) {
7434 if (!base) return NULL;
7435
7436 /* fragment-stripped working copy of the reference */
7437 char* refbuf = strdup(ref ? ref : "");
7438 if (!refbuf) return NULL;
7439 char* frag = strchr(refbuf, '#');
7440 if (frag) *frag = '\0';
7441
7442 /* absolute reference: it carries its own scheme, return it verbatim */
7443 if (_n_url_has_scheme(refbuf)) {
7444 N_STR* out = new_nstr(strlen(refbuf) + 1);
7445 if (out) nstrprintf(out, "%s", refbuf);
7446 free(refbuf);
7447 return out;
7448 }
7449
7450 N_URL* b = n_url_parse(base);
7451 if (!b) {
7452 free(refbuf);
7453 return NULL;
7454 }
7455
7456 /* split the reference into its path and query parts at the first '?' */
7457 char* refq = strchr(refbuf, '?');
7458 const char* ref_query = NULL;
7459 if (refq) {
7460 *refq = '\0';
7461 ref_query = refq + 1;
7462 }
7463
7464 /* base authority = host plus a non-elided port */
7465 char authority[300];
7466 if (b->port > 0)
7467 snprintf(authority, sizeof(authority), "%s:%d", b->host ? b->host : "", b->port);
7468 else
7469 snprintf(authority, sizeof(authority), "%s", b->host ? b->host : "");
7470
7471 char rawpath[4096];
7472 const char* t_scheme = b->scheme ? b->scheme : "http";
7473 const char* t_query = NULL;
7474
7475 if (refbuf[0] == '\0') {
7476 /* empty path: same resource; keep the base query unless one was given */
7477 snprintf(rawpath, sizeof(rawpath), "%s", b->path ? b->path : "/");
7478 t_query = refq ? ref_query : b->query;
7479 } else if (refbuf[0] == '/' && refbuf[1] == '/') {
7480 /* scheme-relative: base scheme, new authority + path from the reference */
7481 const char* a = refbuf + 2;
7482 const char* slash = strchr(a, '/');
7483 if (slash) {
7484 snprintf(authority, sizeof(authority), "%.*s", (int)(slash - a), a);
7485 snprintf(rawpath, sizeof(rawpath), "%s", slash);
7486 } else {
7487 snprintf(authority, sizeof(authority), "%s", a);
7488 snprintf(rawpath, sizeof(rawpath), "/");
7489 }
7490 t_query = ref_query;
7491 } else if (refbuf[0] == '/') {
7492 /* absolute-path reference */
7493 snprintf(rawpath, sizeof(rawpath), "%s", refbuf);
7494 t_query = ref_query;
7495 } else {
7496 /* relative-path reference: merge against the base path */
7497 const char* bp = (b->path && b->path[0]) ? b->path : "/";
7498 const char* lastslash = strrchr(bp, '/');
7499 size_t prefixlen = lastslash ? (size_t)(lastslash - bp) + 1 : 0;
7500 snprintf(rawpath, sizeof(rawpath), "%.*s%s", (int)prefixlen, bp, refbuf);
7501 t_query = ref_query;
7502 }
7503
7504 char normpath[4096];
7505 _n_url_normalize_path(rawpath, normpath, sizeof(normpath));
7506
7507 N_STR* out = new_nstr(strlen(authority) + sizeof(normpath) + 32);
7508 if (out) {
7509 nstrprintf(out, "%s://%s%s", t_scheme, authority, normpath);
7510 if (t_query && t_query[0]) nstrprintf_cat(out, "?%s", t_query);
7511 }
7512 n_url_free(&b);
7513 free(refbuf);
7514 return out;
7515}
7516
7517N_STR* n_url_encode(const char* str) {
7518 return n_str_url_encode(str);
7519}
7520
7521N_STR* n_url_decode(const char* str) {
7522 if (!str) return NULL;
7523 size_t len = strlen(str);
7524 N_STR* result = new_nstr(len + 1);
7525 if (!result) return NULL;
7526 for (size_t i = 0; i < len; i++) {
7527 if (str[i] == '%' && i + 2 < len && isxdigit((unsigned char)str[i + 1]) && isxdigit((unsigned char)str[i + 2])) {
7528 const char hex[3] = {str[i + 1], str[i + 2], '\0'};
7529 unsigned int val = 0;
7530 sscanf(hex, "%x", &val);
7531 nstrprintf_cat(result, "%c", (char)val);
7532 i += 2;
7533 } else if (str[i] == '+') {
7534 nstrprintf_cat(result, " ");
7535 } else {
7536 nstrprintf_cat(result, "%c", str[i]);
7537 }
7538 }
7539 return result;
7540}
7541
7543 if (!u || !*u) return;
7544 N_URL* p = *u;
7545 FreeNoLog(p->scheme);
7546 FreeNoLog(p->host);
7547 FreeNoLog(p->path);
7548 FreeNoLog(p->query);
7549 for (int i = 0; i < p->nb_params; i++) {
7550 FreeNoLog(p->params[i].key);
7551 FreeNoLog(p->params[i].value);
7552 }
7553 FreeNoLog(p);
7554 *u = NULL;
7555}
7556
7563static SOCKET _proxy_tcp_connect(const char* host, int port) {
7564 char port_str[16];
7565 snprintf(port_str, sizeof(port_str), "%d", port);
7566
7567 struct addrinfo hints;
7568 memset(&hints, 0, sizeof(hints));
7569 hints.ai_family = AF_UNSPEC;
7570 hints.ai_socktype = SOCK_STREAM;
7571
7572 struct addrinfo* res = NULL;
7573 int gai_rc = getaddrinfo(host, port_str, &hints, &res);
7574 if (gai_rc != 0 || !res) {
7575 n_log(LOG_ERR, "n_proxy: DNS lookup failed for %s:%d: %s",
7576 host, port, gai_strerror(gai_rc));
7577 return INVALID_SOCKET;
7578 }
7579
7580 SOCKET fd = INVALID_SOCKET;
7581 struct addrinfo* rp = NULL;
7582 for (rp = res; rp; rp = rp->ai_next) {
7583 fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
7584 if (fd == INVALID_SOCKET) continue;
7585 if (connect(fd, rp->ai_addr, (socklen_t)rp->ai_addrlen) == 0) break;
7586 closesocket(fd);
7587 fd = INVALID_SOCKET;
7588 }
7589 freeaddrinfo(res);
7590
7591 if (fd == INVALID_SOCKET) {
7592 n_log(LOG_ERR, "n_proxy: TCP connect to %s:%d failed", host, port);
7593 }
7594 return fd;
7595}
7596
7598 if (!url) return NULL;
7599
7600 /* Expect scheme://... */
7601 const char* sep = strstr(url, "://");
7602 if (!sep) {
7603 n_log(LOG_ERR, "n_proxy_cfg_parse: no scheme in '%s'", url);
7604 return NULL;
7605 }
7606
7607 size_t scheme_len = (size_t)(sep - url);
7608 if (scheme_len == 0 || scheme_len > 16) return NULL;
7609
7610 char scheme[17];
7611 memcpy(scheme, url, scheme_len);
7612 scheme[scheme_len] = '\0';
7613
7614 /* Only http, https, and socks5 */
7615 if (strcmp(scheme, "http") != 0 && strcmp(scheme, "https") != 0 && strcmp(scheme, "socks5") != 0) {
7616 n_log(LOG_ERR, "n_proxy_cfg_parse: unsupported scheme '%s'", scheme);
7617 return NULL;
7618 }
7619
7620 const char* after = sep + 3; /* after :// */
7621 if (!*after) return NULL;
7622
7623 /* Check for user:pass@host:port */
7624 const char* at = strchr(after, '@');
7625 const char* host_start = NULL;
7626 char* username = NULL;
7627 char* password = NULL;
7628
7629 if (at) {
7630 /* Parse user:pass */
7631 const char* colon = strchr(after, ':');
7632 if (colon && colon < at) {
7633 username = strndup(after, (size_t)(colon - after));
7634 password = strndup(colon + 1, (size_t)(at - colon - 1));
7635 } else {
7636 username = strndup(after, (size_t)(at - after));
7637 }
7638 host_start = at + 1;
7639 } else {
7640 host_start = after;
7641 }
7642
7643 /* Parse host:port */
7644 /* Handle IPv6 [host]:port */
7645 char* hostname = NULL;
7646 int port = 0;
7647
7648 if (host_start[0] == '[') {
7649 const char* bracket = strchr(host_start, ']');
7650 if (!bracket) goto fail;
7651 hostname = strndup(host_start + 1, (size_t)(bracket - host_start - 1));
7652 if (bracket[1] == ':') {
7653 port = atoi(bracket + 2);
7654 }
7655 } else {
7656 const char* colon = strchr(host_start, ':');
7657 if (colon) {
7658 hostname = strndup(host_start, (size_t)(colon - host_start));
7659 port = atoi(colon + 1);
7660 } else {
7661 hostname = strdup(host_start);
7662 }
7663 }
7664
7665 if (!hostname || !hostname[0]) goto fail;
7666 if (port <= 0 || port > 65535) {
7667 /* Default ports */
7668 if (strcmp(scheme, "http") == 0 || strcmp(scheme, "https") == 0)
7669 port = 3128;
7670 else if (strcmp(scheme, "socks5") == 0)
7671 port = 1080;
7672 }
7673
7674 N_PROXY_CFG* cfg = NULL;
7675 Malloc(cfg, N_PROXY_CFG, 1);
7676 if (!cfg) goto fail;
7677 memset(cfg, 0, sizeof(*cfg));
7678 cfg->scheme = strdup(scheme);
7679 cfg->host = hostname;
7680 cfg->port = port;
7681 cfg->username = username;
7682 cfg->password = password;
7683 return cfg;
7684
7685fail:
7686 FreeNoLog(hostname);
7687 FreeNoLog(username);
7688 FreeNoLog(password);
7689 return NULL;
7690}
7691
7693 if (!cfg || !*cfg) return;
7694 N_PROXY_CFG* p = *cfg;
7695 FreeNoLog(p->scheme);
7696 FreeNoLog(p->host);
7697 FreeNoLog(p->username);
7698 FreeNoLog(p->password);
7699 FreeNoLog(p);
7700 *cfg = NULL;
7701}
7702
7704 const char* target_host,
7705 int target_port) {
7706 if (!proxy || !target_host) return -1;
7707
7708 SOCKET fd = _proxy_tcp_connect(proxy->host, proxy->port);
7709 if (fd == INVALID_SOCKET) return -1;
7710
7711 /* Build CONNECT request */
7712 char connect_req[2048];
7713 int len = 0;
7714
7715 len = snprintf(connect_req, sizeof(connect_req),
7716 "CONNECT %s:%d HTTP/1.1\r\n"
7717 "Host: %s:%d\r\n",
7718 target_host, target_port,
7719 target_host, target_port);
7720
7721 /* Proxy-Authorization if credentials present */
7722 if (proxy->username && proxy->username[0]) {
7723 char cred[512];
7724 snprintf(cred, sizeof(cred), "%s:%s",
7725 proxy->username, proxy->password ? proxy->password : "");
7726 N_STR* cred_nstr = char_to_nstr(cred);
7727 if (cred_nstr) {
7728 N_STR* b64 = n_base64_encode(cred_nstr);
7729 if (b64 && b64->data) {
7730 len += snprintf(connect_req + len,
7731 sizeof(connect_req) - (size_t)len,
7732 "Proxy-Authorization: Basic %s\r\n",
7733 b64->data);
7734 }
7735 if (b64) free_nstr(&b64);
7736 free_nstr(&cred_nstr);
7737 }
7738 }
7739
7740 len += snprintf(connect_req + len, sizeof(connect_req) - (size_t)len,
7741 "\r\n");
7742
7743 /* Send CONNECT */
7744 ssize_t sent = send(fd, connect_req, NETW_BUFLEN_CAST(len), 0);
7745 if (sent != (ssize_t)len) {
7746 n_log(LOG_ERR, "n_proxy_connect_tunnel: send CONNECT failed");
7747 closesocket(fd);
7748 return -1;
7749 }
7750
7751 /* Read response */
7752 char resp_buf[4096];
7753 ssize_t nr = recv(fd, resp_buf, NETW_BUFLEN_CAST(sizeof(resp_buf) - 1), 0);
7754 if (nr <= 0) {
7755 n_log(LOG_ERR, "n_proxy_connect_tunnel: no response from proxy");
7756 closesocket(fd);
7757 return -1;
7758 }
7759 resp_buf[nr] = '\0';
7760
7761 /* Check for "HTTP/1.x 200" */
7762 if (strstr(resp_buf, " 200 ") == NULL) {
7763 n_log(LOG_ERR, "n_proxy_connect_tunnel: proxy rejected CONNECT: %.*s",
7764 (int)(strchr(resp_buf, '\r') ? strchr(resp_buf, '\r') - resp_buf : nr),
7765 resp_buf);
7766 closesocket(fd);
7767 return -1;
7768 }
7769
7770 n_log(LOG_DEBUG, "n_proxy_connect_tunnel: tunnel established to %s:%d via %s:%d",
7771 target_host, target_port, proxy->host, proxy->port);
7772 return (int)fd;
7773}
7774
7775#ifdef HAVE_OPENSSL
7777 const char* target_host,
7778 int target_port) {
7779 if (!proxy || !target_host) return -1;
7780
7781 SOCKET fd = _proxy_tcp_connect(proxy->host, proxy->port);
7782 if (fd == INVALID_SOCKET) return -1;
7783
7784 /* TLS handshake to the proxy itself */
7786
7787#if OPENSSL_VERSION_NUMBER >= 0x10100000L
7788 const SSL_METHOD* method = TLS_client_method();
7789#else
7790 const SSL_METHOD* method = TLSv1_2_client_method();
7791#endif
7792 SSL_CTX* ctx = SSL_CTX_new(method);
7793 if (!ctx) {
7794 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: SSL_CTX_new failed");
7795 closesocket(fd);
7796 return -1;
7797 }
7798 SSL_CTX_set_default_verify_paths(ctx);
7799
7800 SSL* ssl = SSL_new(ctx);
7801 if (!ssl) {
7802 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: SSL_new failed");
7803 SSL_CTX_free(ctx);
7804 closesocket(fd);
7805 return -1;
7806 }
7807 SSL_set_fd(ssl, (int)fd);
7808 SSL_set_tlsext_host_name(ssl, proxy->host);
7809
7810 if (SSL_connect(ssl) <= 0) {
7811 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: TLS handshake to proxy %s:%d failed",
7812 proxy->host, proxy->port);
7813 SSL_free(ssl);
7814 SSL_CTX_free(ctx);
7815 closesocket(fd);
7816 return -1;
7817 }
7818
7819 n_log(LOG_DEBUG, "n_proxy_connect_tunnel_ssl: TLS established to proxy %s:%d",
7820 proxy->host, proxy->port);
7821
7822 /* Build CONNECT request */
7823 char connect_req[2048];
7824 int len = 0;
7825
7826 len = snprintf(connect_req, sizeof(connect_req),
7827 "CONNECT %s:%d HTTP/1.1\r\n"
7828 "Host: %s:%d\r\n",
7829 target_host, target_port,
7830 target_host, target_port);
7831
7832 /* Proxy-Authorization if credentials present */
7833 if (proxy->username && proxy->username[0]) {
7834 char cred[512];
7835 snprintf(cred, sizeof(cred), "%s:%s",
7836 proxy->username, proxy->password ? proxy->password : "");
7837 N_STR* cred_nstr = char_to_nstr(cred);
7838 if (cred_nstr) {
7839 N_STR* b64 = n_base64_encode(cred_nstr);
7840 if (b64 && b64->data) {
7841 len += snprintf(connect_req + len,
7842 sizeof(connect_req) - (size_t)len,
7843 "Proxy-Authorization: Basic %s\r\n",
7844 b64->data);
7845 }
7846 if (b64) free_nstr(&b64);
7847 free_nstr(&cred_nstr);
7848 }
7849 }
7850
7851 len += snprintf(connect_req + len, sizeof(connect_req) - (size_t)len,
7852 "\r\n");
7853
7854 /* Send CONNECT over TLS */
7855 if (SSL_write(ssl, connect_req, len) != len) {
7856 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: send CONNECT failed");
7857 SSL_free(ssl);
7858 SSL_CTX_free(ctx);
7859 closesocket(fd);
7860 return -1;
7861 }
7862
7863 /* Read response over TLS */
7864 char resp_buf[4096];
7865 int nr = SSL_read(ssl, resp_buf, (int)(sizeof(resp_buf) - 1));
7866 if (nr <= 0) {
7867 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: no response from proxy");
7868 SSL_free(ssl);
7869 SSL_CTX_free(ctx);
7870 closesocket(fd);
7871 return -1;
7872 }
7873 resp_buf[nr] = '\0';
7874
7875 /* Check for "HTTP/1.x 200" */
7876 if (strstr(resp_buf, " 200 ") == NULL) {
7877 n_log(LOG_ERR, "n_proxy_connect_tunnel_ssl: proxy rejected CONNECT: %.*s",
7878 (int)(strchr(resp_buf, '\r') ? strchr(resp_buf, '\r') - resp_buf : nr),
7879 resp_buf);
7880 SSL_free(ssl);
7881 SSL_CTX_free(ctx);
7882 closesocket(fd);
7883 return -1;
7884 }
7885
7886 /* Tunnel established, tear down the proxy TLS session but keep the fd.
7887 * SSL_shutdown sends close_notify; the proxy knows the CONNECT handshake
7888 * is done and the raw tunnel begins. We call SSL_set_quiet_shutdown to
7889 * avoid waiting for the peer's close_notify (the proxy won't send one
7890 * mid-tunnel). */
7891 SSL_set_quiet_shutdown(ssl, 1);
7892 SSL_shutdown(ssl);
7893 SSL_free(ssl);
7894 SSL_CTX_free(ctx);
7895
7896 n_log(LOG_DEBUG, "n_proxy_connect_tunnel_ssl: tunnel established to %s:%d via https://%s:%d",
7897 target_host, target_port, proxy->host, proxy->port);
7898 return (int)fd;
7899}
7900#endif /* HAVE_OPENSSL */
7901
7903 const char* target_host,
7904 int target_port) {
7905 if (!proxy || !target_host) return -1;
7906
7907 SOCKET fd = _proxy_tcp_connect(proxy->host, proxy->port);
7908 if (fd == INVALID_SOCKET) return -1;
7909
7910 int use_auth = (proxy->username && proxy->username[0]) ? 1 : 0;
7911
7912 /* SOCKS5 greeting */
7913 char greeting[4];
7914 if (use_auth) {
7915 greeting[0] = 0x05; /* version */
7916 greeting[1] = 0x02; /* 2 methods */
7917 greeting[2] = 0x00; /* no auth */
7918 greeting[3] = 0x02; /* user/pass */
7919 if (send(fd, greeting, 4, 0) != 4) goto fail;
7920 } else {
7921 greeting[0] = 0x05;
7922 greeting[1] = 0x01;
7923 greeting[2] = 0x00;
7924 if (send(fd, greeting, 3, 0) != 3) goto fail;
7925 }
7926
7927 /* Read server method selection */
7928 char method_resp[2];
7929 if (recv(fd, method_resp, 2, 0) != 2) goto fail;
7930 if (method_resp[0] != 0x05) goto fail;
7931
7932 if (method_resp[1] == 0x02 && use_auth) {
7933 /* User/pass auth sub-negotiation (RFC 1929) */
7934 size_t ulen = strlen(proxy->username);
7935 size_t plen = proxy->password ? strlen(proxy->password) : 0;
7936 if (ulen > 255 || plen > 255) goto fail;
7937
7938 char auth_req[515]; /* 1+1+255+1+255 */
7939 size_t pos = 0;
7940 auth_req[pos++] = 0x01; /* version */
7941 auth_req[pos++] = (char)ulen;
7942 memcpy(auth_req + pos, proxy->username, ulen);
7943 pos += ulen;
7944 auth_req[pos++] = (char)plen;
7945 if (plen > 0) {
7946 memcpy(auth_req + pos, proxy->password, plen);
7947 pos += plen;
7948 }
7949 /* SOCKS5 username/password auth (RFC 1929) transmits the credentials
7950 * in cleartext by design; the protocol has no in-band encryption. A
7951 * caller needing confidentiality must tunnel this over an encrypted
7952 * link (e.g. an HTTPS CONNECT proxy). Not a fixable defect here. */
7953 if (send(fd, auth_req, NETW_BUFLEN_CAST(pos), 0) != (ssize_t)pos) goto fail;
7954
7955 char auth_resp[2];
7956 if (recv(fd, auth_resp, 2, 0) != 2) goto fail;
7957 if (auth_resp[1] != 0x00) {
7958 n_log(LOG_ERR, "n_proxy_connect_socks5: auth rejected");
7959 goto fail;
7960 }
7961 } else if (method_resp[1] != 0x00) {
7962 n_log(LOG_ERR, "n_proxy_connect_socks5: no acceptable method");
7963 goto fail;
7964 }
7965
7966 /* Send connect request (domain name addressing) */
7967 {
7968 size_t hlen = strlen(target_host);
7969 if (hlen > 255) goto fail;
7970
7971 char conn_req[263]; /* 4 + 1 + 255 + 2 */
7972 size_t pos = 0;
7973 conn_req[pos++] = 0x05; /* version */
7974 conn_req[pos++] = 0x01; /* connect */
7975 conn_req[pos++] = 0x00; /* reserved */
7976 conn_req[pos++] = 0x03; /* domain name */
7977 conn_req[pos++] = (char)hlen;
7978 memcpy(conn_req + pos, target_host, hlen);
7979 pos += hlen;
7980 conn_req[pos++] = (char)((target_port >> 8) & 0xFF);
7981 conn_req[pos++] = (char)(target_port & 0xFF);
7982
7983 if (send(fd, conn_req, NETW_BUFLEN_CAST(pos), 0) != (ssize_t)pos) goto fail;
7984 }
7985
7986 /* Read connect response */
7987 {
7988 char conn_resp[10];
7989 /* Minimum response: 4 bytes header + address (varies) */
7990 ssize_t nr = recv(fd, conn_resp, NETW_BUFLEN_CAST(sizeof(conn_resp)), 0);
7991 if (nr < 4) goto fail;
7992 if (conn_resp[0] != 0x05 || conn_resp[1] != 0x00) {
7993 n_log(LOG_ERR, "n_proxy_connect_socks5: connect failed, reply=%02x",
7994 conn_resp[1]);
7995 goto fail;
7996 }
7997
7998 /* If address type is domain (0x03), we may need to read more */
7999 if (conn_resp[3] == 0x03 && nr < 5) {
8000 /* Read remaining bytes */
8001 char extra[256];
8002 recv(fd, extra, NETW_BUFLEN_CAST(sizeof(extra)), 0);
8003 } else if (conn_resp[3] == 0x04 && nr < 10) {
8004 /* IPv6: 16 + 2 bytes remaining */
8005 char extra[18];
8006 size_t need = 22 - (size_t)nr; /* total IPv6 response = 4+16+2=22 */
8007 if (need <= sizeof(extra)) {
8008 recv(fd, extra, NETW_BUFLEN_CAST(need), 0);
8009 }
8010 }
8011 }
8012
8013 n_log(LOG_DEBUG, "n_proxy_connect_socks5: tunnel established to %s:%d via %s:%d",
8014 target_host, target_port, proxy->host, proxy->port);
8015 return (int)fd;
8016
8017fail:
8018 n_log(LOG_ERR, "n_proxy_connect_socks5: handshake failed");
8019 closesocket(fd);
8020 return -1;
8021}
8022
8040int netw_adopt_client_fd(NETWORK** netw, SOCKET fd, const char* host, const char* port) {
8041 __n_assert(netw, return FALSE);
8042 if ((*netw)) {
8043 n_log(LOG_ERR, "netw_adopt_client_fd: target NETWORK must be empty");
8044 return FALSE;
8045 }
8046 if (fd == INVALID_SOCKET) {
8047 n_log(LOG_ERR, "netw_adopt_client_fd: invalid socket");
8048 return FALSE;
8049 }
8050 if (netw_init_wsa(1, 2, 2) == FALSE) {
8051 n_log(LOG_ERR, "netw_adopt_client_fd: unable to load WSA dll's");
8052 return FALSE;
8053 }
8055 __n_assert(netw && (*netw), return FALSE);
8056
8057 (*netw)->link.sock = fd;
8058 (*netw)->link.ip = strdup(host ? host : "");
8059 if (!(*netw)->link.ip) {
8061 return FALSE;
8062 }
8063 (*netw)->link.port = strdup(port ? port : "");
8064 if (!(*netw)->link.port) {
8066 return FALSE;
8067 }
8068
8070 n_log(LOG_DEBUG, "netw_adopt_client_fd: adopted socket %d for %s:%s", (int)fd, (*netw)->link.ip, (*netw)->link.port);
8071 return TRUE;
8072} /* netw_adopt_client_fd */
int DONE
Definition ex_fluid.c:58
static int mode
static char * port_str
static NETWORK_POOL * pool
NETWORK * netw
Network for server mode, accepting incomming.
Definition ex_network.c:39
static void on_request(N_HTTP_REQUEST *req, N_HTTP_RESPONSE *resp, void *user_data)
Request handler: returns JSON for GET /api/test, 404 otherwise.
char * ca_file
NETWORK * server
char * key
int ip_version
char * addr
char * cert
char * port
#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:166
#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:2110
#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:2244
int ht_remove(HASH_TABLE *table, const char *key)
remove and delete node at key in table
Definition n_hash.c:2202
HASH_TABLE * new_ht(size_t size)
Create a hash table with the given size.
Definition n_hash.c:2011
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:2164
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:2177
#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
size_t nb_items
number of item currently in the list
Definition n_list.h:61
#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:89
#define LOG_DEBUG
debug-level messages
Definition n_log.h:84
#define LOG_ERR
error conditions
Definition n_log.h:76
#define LOG_WARNING
warning conditions
Definition n_log.h:78
#define LOG_INFO
informational
Definition n_log.h:82
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:1725
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:716
#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:255
N_STR * new_nstr(NSTRBYTE size)
create a new N_STR string
Definition n_str.c:207
#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:232
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:1014
char query[2048]
query string (or empty)
Definition n_network.h:1040
char * ip
ip of the connected socket
Definition n_network.h:295
N_SOCKET link
networking socket
Definition n_network.h:388
char * certificate
openssl certificate file
Definition n_network.h:382
char netw_errors[8][512]
per-connection error capture ring buffer (max 8 entries, 512 chars each)
Definition n_network.h:413
int threaded_engine_status
Threaded network engine state for this network.
Definition n_network.h:315
size_t content_length
Store content length.
Definition n_network.h:573
char * type
Type of request.
Definition n_network.h:577
pthread_t send_thr
sending thread
Definition n_network.h:396
int compress_mode
Per-packet compression mode, see NETW_COMPRESS_MODE.
Definition n_network.h:426
int nb_pending
Nb pending connection,if listening.
Definition n_network.h:311
char * filename
Content-Disposition filename, or "".
Definition n_network.h:792
int so_reuseaddr
so reuseaddr state
Definition n_network.h:321
pthread_t recv_thr
receiving thread
Definition n_network.h:398
int port
port number (0 if not specified)
Definition n_network.h:914
int masked
MASK bit (client-to-server frames are masked)
Definition n_network.h:966
char * host
proxy hostname
Definition n_network.h:815
pthread_rwlock_t rwlock
thread safety
Definition n_network.h:564
NETWORK * netw
underlying network connection
Definition n_network.h:1013
struct sockaddr_storage raddr
connected remote addr
Definition n_network.h:305
char * host
hostname
Definition n_network.h:913
char * path
path starting with "/", or "/" if empty
Definition n_network.h:915
pthread_mutex_t eventbolt
mutex for threaded access of state event
Definition n_network.h:405
char * content_type
the part's Content-Type, or ""
Definition n_network.h:793
const SSL_METHOD * method
SSL method container.
Definition n_network.h:376
N_STR * body
response body
Definition n_network.h:1049
int netw_err_next
next write slot in ring buffer
Definition n_network.h:417
int deplete_socket_timeout
deplete socket send buffer timeout ( 0 disabled, > 0 wait for timeout and check unset/unack datas)
Definition n_network.h:349
char * scheme
"http" or "https"
Definition n_network.h:912
int deplete_queues_timeout
deplete network queues timeout ( 0 disabled, > 0 wait for timeout and check unset/unack datas)
Definition n_network.h:347
int nb_running_threads
nb running threads, if > 0 thread engine is still running
Definition n_network.h:345
int opcode
opcode (N_WS_OP_*)
Definition n_network.h:965
pthread_mutex_t recvbolt
mutex for threaded access of recv buf
Definition n_network.h:403
NETWORK * netw
underlying network connection
Definition n_network.h:954
void(* on_event)(N_SSE_EVENT *event, struct N_SSE_CONN *conn, void *user_data)
callback
Definition n_network.h:1015
pthread_mutex_t sendbolt
mutex for threaded access of send_buf
Definition n_network.h:401
N_STR * body
the part's raw body bytes (binary-safe), or NULL
Definition n_network.h:794
int send_queue_consecutive_wait
send queue consecutive pool interval, used when there are still items to send, in usec
Definition n_network.h:319
int fin
FIN bit (1 = final fragment)
Definition n_network.h:963
N_STR * event
event type (or NULL for default)
Definition n_network.h:1003
int connected
1 if handshake completed
Definition n_network.h:955
N_STR * data
event data
Definition n_network.h:1004
N_STR * body
request body (or NULL)
Definition n_network.h:1042
char * key
parameter name
Definition n_network.h:906
int so_rcvtimeo
send timeout value
Definition n_network.h:335
uint64_t payload_len
payload length in bytes
Definition n_network.h:968
char * password
NULL if no auth.
Definition n_network.h:818
char * query
raw query string without leading '?', or NULL
Definition n_network.h:916
int port
proxy port
Definition n_network.h:816
N_URL_PARAM params[64]
parsed key=value pairs
Definition n_network.h:917
int tcpnodelay
state of naggle algorythm, 0 untouched, 1 forcibly disabled
Definition n_network.h:327
char * body
Pointer to the body data.
Definition n_network.h:575
LIST * headers
list of char* "Name: Value" strings
Definition n_network.h:1041
char * value
parameter value
Definition n_network.h:907
char * host
remote hostname
Definition n_network.h:956
int netw_err_count
number of captured errors
Definition n_network.h:415
SOCKET sock
a normal socket
Definition n_network.h:293
char path[2048]
request path
Definition n_network.h:1039
char * scheme
"http", "https", or "socks5"
Definition n_network.h:814
char * port
port of socket
Definition n_network.h:291
netw_func recv_data_once
single-attempt recv, same contract as send_data_once.
Definition n_network.h:372
LIST * recv_buf
reveicing buffer (for incomming usage)
Definition n_network.h:393
int transport_type
transport type: NETWORK_TCP (0) or NETWORK_UDP (1)
Definition n_network.h:353
int nb_params
number of parsed parameters
Definition n_network.h:918
char * name
Content-Disposition form-field name, or "".
Definition n_network.h:791
int user_id
if part of a user property, id of the user
Definition n_network.h:343
sem_t send_blocker
block sending func
Definition n_network.h:407
SSL_CTX * ctx
SSL context holder.
Definition n_network.h:378
int so_sndbuf
size of the socket send buffer, 0 untouched, else size in bytes
Definition n_network.h:329
int so_sndtimeo
send timeout value
Definition n_network.h:333
const unsigned char * payload
pointer into the input buffer (still masked)
Definition n_network.h:969
int retry
retry interval in ms (0 if not set)
Definition n_network.h:1006
char content_type[256]
Store content type.
Definition n_network.h:571
N_STR * id
last event ID (or NULL)
Definition n_network.h:1005
struct addrinfo hints
address of local machine
Definition n_network.h:301
int so_keepalive
so keepalive state
Definition n_network.h:325
netw_func send_data
send func ptr
Definition n_network.h:359
int addr_infos_loaded
Internal flag to know if we have to free addr infos.
Definition n_network.h:317
char * path
resource path
Definition n_network.h:957
int rsv
the three reserved bits, rsv1<<2 | rsv2<<1 | rsv3
Definition n_network.h:964
N_STR * payload
message payload
Definition n_network.h:948
char method[16]
HTTP method.
Definition n_network.h:1038
int opcode
frame opcode
Definition n_network.h:947
unsigned char mask[4]
masking key when masked, else zeroed
Definition n_network.h:967
int masked
1 if masked
Definition n_network.h:949
LIST * pools
pointers to network pools if members of any
Definition n_network.h:410
char * username
NULL if no auth.
Definition n_network.h:817
netw_func send_data_once
single-attempt send (non-blocking / reactor use).
Definition n_network.h:369
int status_code
HTTP status code.
Definition n_network.h:1047
char * key
openssl key file
Definition n_network.h:384
SSL * ssl
SSL handle.
Definition n_network.h:380
int so_linger
close lingering value (-1 disabled, 0 force close, >0 linger )
Definition n_network.h:337
unsigned long int is_blocking
flag to quickly check socket mode
Definition n_network.h:298
int crypto_algo
if encryption is on, which one (flags NETW_ENCRYPT_*)
Definition n_network.h:341
char content_type[128]
Content-Type header value.
Definition n_network.h:1048
HASH_TABLE * pool
table of clients
Definition n_network.h:561
LIST * send_buf
sending buffer (for outgoing queuing )
Definition n_network.h:391
int mode
NETWORK mode , 1 listening, 0 connecting.
Definition n_network.h:313
int so_rcvbuf
size of the socket recv buffer, 0 untouched, else size in bytes
Definition n_network.h:331
void * user_data
user data for callback
Definition n_network.h:1016
netw_func recv_data
receive func ptr
Definition n_network.h:361
int wait_close_timeout
network wait close timeout value ( < 1 disabled, >= 1 timeout sec )
Definition n_network.h:351
#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:5258
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:4620
#define N_URL_MAX_PARAMS
maximum number of parsed query parameters
Definition n_network.h:902
#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:279
N_STR * netw_get_msg(NETWORK *netw)
Get a message from aimed NETWORK.
Definition n_network.c:3666
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:6049
int netw_add_msg(NETWORK *netw, N_STR *msg)
Add a message to send in aimed NETWORK.
Definition n_network.c:3569
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:1921
const char * n_netw_get_connect_error(int index)
Get pre-connection error message by index.
Definition n_network.c:124
ssize_t send_ssl_data(void *netw, char *buf, uint32_t n)
send data onto the socket
Definition n_network.c:4409
char * netw_extract_http_request_type(const char *request)
function to extract the request method from an http request
Definition n_network.c:5347
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:1729
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:4964
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:3005
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:1485
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:4694
#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:89
#define NETW_COMPRESS_MIN_RATIO
Definition n_network.h:280
int netw_init_wsa(int mode, int v1, int v2)
Do not directly use, internal api.
Definition n_network.c:839
int netw_ssl_set_verify(NETWORK *netw, int enable)
enable or disable SSL peer certificate verification
Definition n_network.c:1892
ssize_t send_php(SOCKET s, int _code, char *buf, int n)
send data onto the socket
Definition n_network.c:4777
int netw_stop_thr_engine(NETWORK *netw)
Stop a NETWORK connection sending and receing thread.
Definition n_network.c:4130
void n_ws_close(N_WS_CONN *conn)
Send close frame and close the connection.
Definition n_network.c:6551
char * netw_urlencode(const char *str, size_t len)
function to perform URL encoding
Definition n_network.c:5314
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:7517
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:5668
void * netw_send_func(void *NET)
Thread send function.
Definition n_network.c:3781
NETWORK * netw_accept_nonblock_from(NETWORK *from, int blocking)
make a normal blocking 'accept' .
Definition n_network.c:3554
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:5466
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:7597
int netw_set_crypto(NETWORK *netw, char *key, char *certificate)
activate SSL encryption on selected network, using key and certificate
Definition n_network.c:1385
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:1615
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:8040
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:1867
void n_sse_stop(N_SSE_CONN *conn)
Signal the SSE connection to stop reading.
Definition n_network.c:6617
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:4354
#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:5850
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:6952
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:2190
#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:7395
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:7433
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:4298
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:6078
NETWORK_POOL * netw_new_pool(size_t nb_min_element)
return a new network pool of nb_min_element
Definition n_network.c:4983
int netw_set_user_id(NETWORK *netw, int id)
associate an id and a network
Definition n_network.c:5158
void n_mock_server_free(N_MOCK_SERVER **server)
Free a mock server and close the listening socket.
Definition n_network.c:7224
ssize_t recv_data(void *netw, char *buf, uint32_t n)
recv data from the socket
Definition n_network.c:4231
int netw_init_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1333
void n_netw_clear_errors(NETWORK *netw)
Clear captured errors on a NETWORK handle.
Definition n_network.c:114
ssize_t recv_ssl_data(void *netw, char *buf, uint32_t n)
recv data from the socket
Definition n_network.c:4504
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:2885
int netw_ssl_do_handshake(NETWORK *netw, const char *sni_hostname)
Complete the SSL handshake on an already-connected NETWORK.
Definition n_network.c:2496
#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:3218
#define netw_atomic_write_state(netw, val)
Lock-free atomic write of the network state field.
Definition n_network.h:541
int netw_set_compression_mode(NETWORK *netw, int mode)
Pick send-side payload compression algorithm.
Definition n_network.c:891
int netw_start_thr_engine(NETWORK *netw)
Start the NETWORK netw Threaded Engine.
Definition n_network.c:3740
int netw_destroy_pool(NETWORK_POOL **netw_pool)
free a NETWORK_POOL *pool
Definition n_network.c:5002
void n_url_free(N_URL **u)
free a N_URL and all its members
Definition n_network.c:7542
#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:6110
void * netw_recv_func(void *NET)
To Thread Receiving function.
Definition n_network.c:3964
void n_sse_conn_free(N_SSE_CONN **conn)
Free an SSE connection structure.
Definition n_network.c:6626
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:7776
N_STR * n_url_decode(const char *str)
decode a percent-encoded string (returns N_STR)
Definition n_network.c:7521
int n_netw_get_error_count(const NETWORK *netw)
Get number of captured errors on a NETWORK handle.
Definition n_network.c:104
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:6905
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:6208
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:5690
#define SOCKET_SIZE_FORMAT
socket associated printf style
Definition n_network.h:104
__netw_code_type size_t htonst(size_t value)
host to network size_t
Definition n_network.c:143
int netw_unload_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1366
const char * netw_ssl_get_sni(NETWORK *netw)
get the SNI server_name the peer requested in its ClientHello
Definition n_network.c:1715
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:5686
#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:7902
size_t ntohst(size_t value)
network to host size_t
Definition n_network.c:160
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:3295
#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:550
size_t netw_pool_nbclients(NETWORK_POOL *netw_pool)
return the number of networks in netw_pool
Definition n_network.c:5141
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:607
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:7703
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:3358
void n_netw_clear_connect_errors(void)
Clear pre-connection errors on this thread.
Definition n_network.c:130
void n_mock_server_stop(N_MOCK_SERVER *server)
Signal the mock server to stop accepting connections.
Definition n_network.c:7215
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:2347
#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:102
#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:5117
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:5751
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:2409
const char * n_netw_get_error(const NETWORK *netw, int index)
Get captured error message by index (0 = oldest).
Definition n_network.c:108
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:6605
#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:967
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:5811
int netw_set(NETWORK *netw, int flag)
Restart or reset the specified network ability.
Definition n_network.c:2554
ssize_t send_data(void *netw, char *buf, uint32_t n)
send data onto the socket
Definition n_network.c:4170
ssize_t recv_udp_data(void *netw, char *buf, uint32_t n)
recv data via UDP from a connected socket
Definition n_network.c:3259
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:1846
int netw_get_state(NETWORK *netw, uint32_t *state, int *thr_engine_status)
Get the state of a network.
Definition n_network.c:2532
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:6711
N_URL * n_url_parse(const char *url)
parse a URL string into components
Definition n_network.c:7255
int n_http_status_is_informational(int status_code)
1 when status_code is informational (1xx), else 0
Definition n_network.c:5674
void netw_pool_netw_close(void *netw_ptr)
close a network from a network pool
Definition n_network.c:5021
int n_netw_get_connect_error_count(void)
Get number of pre-connection errors captured on this thread.
Definition n_network.c:120
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:5291
#define netw_atomic_read_reactor_handle(netw)
Same contract for the back-pointer to the reactor.
Definition n_network.h:555
int deplete_send_buffer(int fd, int timeout)
wait until the socket is empty or timeout, checking each 100 msec.
Definition n_network.c:2631
NETWORK * netw_accept_from(NETWORK *from)
make a normal blocking 'accept' .
Definition n_network.c:3544
N_STR * n_url_canonicalize(const N_URL *u)
produce a canonical URL string for deduplication
Definition n_network.c:7366
#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:7692
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:2662
int n_http_status_is_success(int status_code)
1 when status_code is success (2xx), else 0
Definition n_network.c:5678
N_STR * n_url_build(const N_URL *u)
build a URL string from parsed components
Definition n_network.c:7294
void n_mock_server_run(N_MOCK_SERVER *server)
Run the mock server accept loop.
Definition n_network.c:7130
int netw_send_quit(NETWORK *netw)
Add a formatted NETMSG_QUIT message to the specified network.
Definition n_network.c:5274
#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:87
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:2481
int netw_ssl_start_client(NETWORK *netw)
Set up a client SSL_CTX on an already-connected NETWORK.
Definition n_network.c:2438
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:5650
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:1580
int n_ws_recv(N_WS_CONN *conn, N_WS_MESSAGE *msg_out)
Receive one WebSocket frame.
Definition n_network.c:6479
int netw_set_blocking(NETWORK *netw, unsigned long int is_blocking)
Modify blocking socket mode.
Definition n_network.c:909
N_STR * netw_wait_msg(NETWORK *netw, unsigned int refresh, size_t timeout)
Wait a message from aimed NETWORK.
Definition n_network.c:3688
int n_http_status_is_redirect(int status_code)
1 when status_code is a redirect (3xx), else 0
Definition n_network.c:5682
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:5910
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:5238
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:5173
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:2389
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:5380
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:6960
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:1790
#define netw_atomic_read_state(netw)
Lock-free atomic read of the network state field.
Definition n_network.h:536
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:2359
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:7096
__netw_code_type
Network codes declaration.
Definition n_network.h:283
#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:2373
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:5192
char * netw_urldecode(const char *str)
Function to decode URL-encoded data.
Definition n_network.c:5503
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:1988
void n_ws_conn_free(N_WS_CONN **conn)
Free a WebSocket connection structure.
Definition n_network.c:6581
#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:5034
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:3113
int netw_info_destroy(NETWORK_HTTP_INFO http_request)
destroy a NETWORK_HTTP_INFO loaded informations
Definition n_network.c:5453
HASH_TABLE * netw_parse_post_data(const char *post_data)
Function to parse POST data.
Definition n_network.c:5540
const char * netw_guess_http_content_type(const char *url)
function to guess the content type based on URL extension
Definition n_network.c:5586
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:6412
ssize_t recv_php(SOCKET s, int *_code, char **buf)
recv data from the socket
Definition n_network.c:4859
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:5216
int netw_pool_remove(NETWORK_POOL *netw_pool, NETWORK *netw)
remove a NETWORK *netw to a NETWORK_POOL *pool
Definition n_network.c:5080
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:2172
int netw_add_msg_ex(NETWORK *netw, char *str, unsigned int length)
Add a message to send in aimed NETWORK.
Definition n_network.c:3635
#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:3327
#define N_WS_OP_CLOSE
Definition n_network.h:941
@ NETW_COMPRESS_NONE
no automatic compression on send, still decompresses inbound
Definition n_network.h:265
@ NETW_COMPRESS_ZLIB
compress on send with zlib, decompress either on recv
Definition n_network.h:267
@ NETW_COMPRESS_LZ4
compress on send with LZ4, decompress either on recv
Definition n_network.h:269
@ NETW_COMPRESSED_LZ4
Definition n_network.h:283
@ NETW_DESTROY_RECVBUF
Definition n_network.h:283
@ NETW_ENCRYPT_OPENSSL
Definition n_network.h:283
@ NETW_THR_ENGINE_STARTED
Definition n_network.h:283
@ NETW_COMPRESSED_ZLIB
Definition n_network.h:283
@ NETW_SERVER
Definition n_network.h:283
@ NETW_EMPTY_SENDBUF
Definition n_network.h:283
@ NETW_DESTROY_SENDBUF
Definition n_network.h:283
@ NETW_THR_ENGINE_STOPPED
Definition n_network.h:283
@ NETW_RUN
Definition n_network.h:283
@ NETW_CLIENT
Definition n_network.h:283
@ NETW_EMPTY_RECVBUF
Definition n_network.h:283
@ NETW_EXITED
Definition n_network.h:283
@ NETW_ERROR
Definition n_network.h:283
@ NETW_EXIT_ASKED
Definition n_network.h:283
@ NETW_ENCRYPT_NONE
Definition n_network.h:283
a single part of a parsed multipart/form-data body
Definition n_network.h:790
parsed HTTP request for mock server callback
Definition n_network.h:1037
HTTP response to send from mock server callback.
Definition n_network.h:1046
mock HTTP server handle
Definition n_network.h:1053
Parsed proxy URL components.
Definition n_network.h:813
SSE connection handle.
Definition n_network.h:1012
SSE event received from server.
Definition n_network.h:1002
parsed URL components
Definition n_network.h:911
WebSocket connection.
Definition n_network.h:953
A single parsed WebSocket frame (RFC 6455).
Definition n_network.h:962
WebSocket message.
Definition n_network.h:946
Structure of a NETWORK.
Definition n_network.h:309
structure for splitting HTTP requests
Definition n_network.h:569
structure of a network pool
Definition n_network.h:559
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:698
#define NETW_CALL_RETRY(__retvar, __expression, __max_tries)
network-aware retry macro: retries on EINTR and EAGAIN/EWOULDBLOCK
Definition n_network.c:670
netw_sni_pick_cb pick
Definition n_network.c:1744
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:7314
static int _ssl_use_pem(SSL *ssl, const char *key_pem, const char *cert_pem)
Definition n_network.c:1684
static char * mp_strndup(const char *s, size_t len)
Definition n_network.c:5800
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:1751
#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:6894
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:6177
static void mp_put(char *buf, size_t *pos, const void *src, size_t n)
Definition n_network.c:6007
__attribute__((unused))
Definition n_network.c:1286
static void netw_init_locks(void)
Definition n_network.c:1303
void netw_ssl_print_errors(SOCKET socket)
print the queued OpenSSL errors for a given socket
Definition n_network.c:1229
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:6687
#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:7233
static int(* _netw_connect_abort_cb)(void *)
Optional connect-abort callback, or NULL.
Definition n_network.c:2025
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:7006
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:6161
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:712
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:6644
static SOCKET _proxy_tcp_connect(const char *host, int port)
Helper: connect a plain TCP socket to host:port.
Definition n_network.c:7563
long long g_netw_bytes_sent
Add a message to send in aimed NETWORK.
Definition n_network.c:3566
static void * _netw_connect_abort_ctx
Opaque context handed to _netw_connect_abort_cb on each poll.
Definition n_network.c:2027
N_ENUM_netw_code_type
network error code
Definition n_network.c:136
static void mp_emit(char *buf, size_t *pos, LIST *parts, const char *dash)
Definition n_network.c:6020
static int n_http_inflate(const unsigned char *src, size_t len, int window_bits, N_STR **out)
Definition n_network.c:5697
static int OPENSSL_IS_INITIALIZED
Definition n_network.c:1327
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:681
static pthread_mutex_t * netw_ssl_lockarray
Definition n_network.c:1284
static void mp_part_free(void *ptr)
Definition n_network.c:5838
static void _netw_capture_connect_error(const char *fmt,...)
capture a pre-connection error (thread-local)
Definition n_network.c:93
#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:2022
static long mp_find(const unsigned char *hay, size_t hlen, const char *needle, size_t nlen)
Definition n_network.c:5788
char * get_in_addr(struct sockaddr *sa)
get sockaddr, IPv4 or IPv6
Definition n_network.c:826
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:2044
static const char * mp_header_value(const char *block, size_t blen, const char *name, size_t *vlen)
Definition n_network.c:5857
void * user_data
Definition n_network.c:1745
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:7083
static int _n_url_has_scheme(const char *s)
test whether a reference string begins with its own URI scheme
Definition n_network.c:7409
long long g_netw_bytes_recv
Definition n_network.c:3567
static char * mp_disp_param(const char *disp, size_t dlen, const char *param)
Definition n_network.c:5885
char * netw_get_openssl_error_string()
get the OpenSSL error string
Definition n_network.c:1198
static __thread char s_connect_errors[8][512]
Definition n_network.c:77
static void _netw_capture_error(NETWORK *netw, const char *fmt,...)
capture an error into a NETWORK handle's ring buffer
Definition n_network.c:82
static void mp_puts(char *buf, size_t *pos, const char *s)
Definition n_network.c:6014
static void netw_kill_locks(void)
Definition n_network.c:1317
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:1248
void n_reactor_close_netw_sync(NETWORK *netw)
Synchronously close a reactor-registered NETWORK from the game thread.
Definition n_reactor.c:1252
Single-threaded epoll reactor for n_network connections.
Timing utilities.
ZLIB compression handler.