Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_network_ssl_nonblock.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
29#include "nilorea/n_common.h"
30#include "nilorea/n_log.h"
31#include "nilorea/n_network.h"
32#include "nilorea/n_str.h"
33#include "nilorea/n_x509.h"
34
35#ifndef __windows__
36#include <poll.h>
37#endif
38#include <pthread.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <unistd.h>
43
44#include <openssl/bio.h>
45#include <openssl/pem.h>
46#include <openssl/ssl.h>
47#include <openssl/x509.h>
48
50#define MSG "nonblock-client->server"
52#define REPLY "nonblock-server->client"
54#define MSG_LEN 24
55
60static void leave_stale_openssl_error(void) {
61 static const char garbage[] = "not a certificate";
62 BIO* b = BIO_new_mem_buf(garbage, (int)(sizeof(garbage) - 1));
63 if (b) {
64 X509* x = PEM_read_bio_X509(b, NULL, NULL, NULL); /* fails: queues */
65 if (x) X509_free(x);
66 BIO_free(b);
67 }
68}
69
71typedef struct SRV {
72 NETWORK* listen;
73 SSL_CTX* ctx;
74 int ok;
75} SRV;
76
77/* Parse the -V LOG_LEVEL verbosity argument. */
78static void process_args(int argc, char** argv) {
79 int opt = 0;
80 while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
81 switch (opt) {
82 case 'V':
83 if (!strncmp("LOG_NULL", optarg, 8))
85 else if (!strncmp("LOG_NOTICE", optarg, 10))
87 else if (!strncmp("LOG_INFO", optarg, 8))
89 else if (!strncmp("LOG_ERR", optarg, 7))
91 else if (!strncmp("LOG_DEBUG", optarg, 9))
93 else {
94 fprintf(stderr, "Unknown log level %s\n", optarg);
95 exit(1);
96 }
97 break;
98 case 'v':
99 fprintf(stderr, "ex_network_ssl_nonblock\n");
100 exit(1);
101 case 'h':
102 default:
103 fprintf(stderr, "usage: %s [-V LOG_LEVEL]\n", argv[0]);
104 break;
105 }
106 }
107}
108
109/* Build a server ctx from an in-memory PEM cert + key, pinned to the newest TLS
110 * version the linked TLS library can negotiate. That is TLS 1.3 from OpenSSL
111 * 1.1.1 (LibreSSL 3.5) on; on OpenSSL 1.0.2 neither the version-flexible
112 * TLS_server_method() nor the min/max_proto_version setters exist, and
113 * TLSv1_2_server_method() already pins the context to the one version that
114 * build speaks. What this example
115 * exercises is the non-blocking handshake and I/O drive loop, which is the
116 * same either way. */
117static SSL_CTX* make_server_ctx(const N_STR* cert_pem, const N_STR* key_pem) {
118#if N_OSSL_HAVE_PROTO_VERSION_SETTERS
119 SSL_CTX* ctx = SSL_CTX_new(TLS_server_method());
120 if (!ctx)
121 return NULL;
122 SSL_CTX_set_min_proto_version(ctx, NETW_TLS_MAX_VERSION);
123 SSL_CTX_set_max_proto_version(ctx, NETW_TLS_MAX_VERSION);
124#else
125 SSL_CTX* ctx = SSL_CTX_new(TLSv1_2_server_method());
126 if (!ctx)
127 return NULL;
128#endif
129 SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
130
131 BIO* cb = BIO_new_mem_buf(cert_pem->data, (int)cert_pem->written);
132 BIO* kb = BIO_new_mem_buf(key_pem->data, (int)key_pem->written);
133 X509* x = cb ? PEM_read_bio_X509(cb, NULL, NULL, NULL) : NULL;
134 EVP_PKEY* pk = kb ? PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL) : NULL;
135 int ok = x && pk && SSL_CTX_use_certificate(ctx, x) == 1 &&
136 SSL_CTX_use_PrivateKey(ctx, pk) == 1;
137 if (x)
138 X509_free(x);
139 if (pk)
140 EVP_PKEY_free(pk);
141 if (cb)
142 BIO_free(cb);
143 if (kb)
144 BIO_free(kb);
145 if (!ok) {
146 SSL_CTX_free(ctx);
147 return NULL;
148 }
149 return ctx;
150}
151
152/* Wait for a socket to become readable (want_write==0) or writable.
153 * poll() is POSIX only. Winsock has no <poll.h>, and its WSAPoll() equivalent
154 * needs _WIN32_WINNT >= 0x0600 headers while n_windows.h targets 0x0501, so the
155 * Windows branch waits with select(), which Winsock always provides. A failed
156 * non-blocking connect is signalled through exceptfds there (not writefds), so
157 * that set is watched too: the caller learns the real outcome from its next
158 * netw_connect_check() / SSL attempt. */
159static int poll_fd(SOCKET fd, int want_write, int timeout_ms) {
160#ifdef __windows__
161 fd_set rset, wset, eset;
162 struct timeval tv;
163
164 FD_ZERO(&rset);
165 FD_ZERO(&wset);
166 FD_ZERO(&eset);
167 if (want_write) {
168 FD_SET(fd, &wset);
169 FD_SET(fd, &eset);
170 } else {
171 FD_SET(fd, &rset);
172 }
173 tv.tv_sec = timeout_ms / 1000;
174 tv.tv_usec = (timeout_ms % 1000) * 1000;
175
176 return select((int)fd + 1, want_write ? NULL : &rset,
177 want_write ? &wset : NULL, want_write ? &eset : NULL, &tv);
178#else
179 struct pollfd p;
180 p.fd = fd;
181 p.events = (short)(want_write ? POLLOUT : POLLIN);
182 p.revents = 0;
183 return poll(&p, 1, timeout_ms);
184#endif
185}
186
187/* Drive a resumable handshake to completion from a poll() loop. */
188static int drive_handshake(NETWORK* netw, int is_server, SSL_CTX* sctx) {
189 int guard;
190 for (guard = 0; guard < 10000; guard++) {
191 int r = is_server ? netw_ssl_server_handshake_once(netw, sctx)
193 if (r == TRUE)
194 return TRUE;
195 if (r == NETW_IO_WANT_READ) {
196 if (poll_fd(netw->link.sock, 0, 2000) <= 0)
197 return FALSE;
198 continue;
199 }
200 if (r == NETW_IO_WANT_WRITE) {
201 if (poll_fd(netw->link.sock, 1, 2000) <= 0)
202 return FALSE;
203 continue;
204 }
205 return FALSE; /* fatal */
206 }
207 return FALSE;
208}
209
210/* Drive a non-blocking connect to completion from a poll() loop. */
212 int guard;
213 for (guard = 0; guard < 10000; guard++) {
214 int r = netw_connect_check(netw);
215 if (r == TRUE)
216 return TRUE;
217 if (r == NETW_IO_WANT_WRITE) {
218 if (poll_fd(netw->link.sock, 1, 2000) <= 0)
219 return FALSE;
220 continue;
221 }
222 return FALSE; /* all addresses failed */
223 }
224 return FALSE;
225}
226
227/* Move exactly n bytes with the single-attempt movers, polling on WANT. */
228static int io_full(NETWORK* netw, char* buf, uint32_t n, int is_send) {
229 uint32_t done = 0;
230 int guard;
231 for (guard = 0; guard < 100000 && done < n; guard++) {
232 ssize_t r = is_send ? send_ssl_data_once(netw, buf + done, n - done)
233 : recv_ssl_data_once(netw, buf + done, n - done);
234 if (r > 0) {
235 done += (uint32_t)r;
236 continue;
237 }
238 if (r == NETW_IO_WANT_READ) {
239 if (poll_fd(netw->link.sock, 0, 2000) <= 0)
240 return FALSE;
241 continue;
242 }
243 if (r == NETW_IO_WANT_WRITE) {
244 if (poll_fd(netw->link.sock, 1, 2000) <= 0)
245 return FALSE;
246 continue;
247 }
248 return FALSE; /* disconnect or error */
249 }
250 return done == n ? TRUE : FALSE;
251}
252
253/* Server thread: accept one connection (non-blocking), complete the resumable
254 * handshake, read the probe, echo the reply. */
255static void* server_fn(void* p) {
256 SRV* s = (SRV*)p;
257 NETWORK* c = NULL;
258 int guard;
259 for (guard = 0; guard < 10000 && !c; guard++) {
260 if (poll_fd(s->listen->link.sock, 0, 2000) <= 0)
261 break;
263 }
264 if (!c)
265 return NULL;
266 if (drive_handshake(c, 1, s->ctx) == TRUE) {
267 char buf[MSG_LEN] = "";
268 char reply[MSG_LEN];
269 memcpy(reply, REPLY, MSG_LEN);
270 if (io_full(c, buf, MSG_LEN, 0) == TRUE && memcmp(buf, MSG, MSG_LEN) == 0) {
271 n_log(LOG_NOTICE, "server received: %s", buf);
272 if (io_full(c, reply, MSG_LEN, 1) == TRUE)
273 s->ok = 1;
274 }
275 }
276 netw_close(&c);
277 return NULL;
278}
279
280/* Bind a loopback listening socket on the first free port in a range. */
281static NETWORK* bind_free(char* port_out, size_t port_out_len) {
282 int p;
283 for (p = 18500; p < 18600; p++) {
284 NETWORK* l = NULL;
285 char ps[16];
286 snprintf(ps, sizeof(ps), "%d", p);
287 if (netw_make_listening(&l, "127.0.0.1", ps, 5, NETWORK_IPALL) == TRUE && l) {
288 snprintf(port_out, port_out_len, "%s", ps);
289 return l;
290 }
291 if (l)
292 netw_close(&l);
293 }
294 return NULL;
295}
296
297int main(int argc, char** argv) {
298 N_STR* cert = NULL;
299 N_STR* key = NULL;
300 SSL_CTX* sctx = NULL;
301 NETWORK* listen = NULL;
302 NETWORK* cli = NULL;
303 SRV srv;
304 pthread_t th;
305 char port[16] = "";
306 int failures = 0;
307
309 process_args(argc, argv);
310
311 /* OpenSSL is initialised once in the main thread: the server and client
312 * threads both use TLS and the lazy init guard is not synchronized. */
314
315 /* A private self-signed CA cert doubles as the server leaf here; the client
316 * turns verification off (it is not in any trust store). */
317 if (n_x509_generate_ca("Nilorea NonBlock TLS Test", 3650, &cert, &key) != 0 || !cert || !key) {
318 n_log(LOG_ERR, "certificate generation failed");
320 return 1;
321 }
322 sctx = make_server_ctx(cert, key);
323 if (!sctx) {
324 n_log(LOG_ERR, "server ctx setup failed");
325 free_nstr(&cert);
326 free_nstr(&key);
328 return 1;
329 }
330
331 listen = bind_free(port, sizeof(port));
332 if (!listen) {
333 n_log(LOG_ERR, "could not bind a loopback port");
334 SSL_CTX_free(sctx);
335 free_nstr(&cert);
336 free_nstr(&key);
338 return 1;
339 }
340
341 srv.listen = listen;
342 srv.ctx = sctx;
343 srv.ok = 0;
344 pthread_create(&th, NULL, server_fn, &srv);
345
346 /* The whole client path is non-blocking + poll-driven: start the TCP
347 * connect, drive it to completion, set up the client TLS ctx, then run the
348 * TLS handshake -- none of it blocks a shared thread. */
349 if (netw_connect_nonblock(&cli, "127.0.0.1", port, NETWORK_IPALL) != TRUE || !cli) {
350 n_log(LOG_ERR, "client connect start failed");
351 failures++;
352 } else if (drive_connect(cli) != TRUE) {
353 n_log(LOG_ERR, "client non-blocking connect failed");
354 failures++;
355 } else if (netw_ssl_start_client(cli) != TRUE) {
356 n_log(LOG_ERR, "client TLS ctx setup failed");
357 failures++;
358 } else {
359 netw_ssl_set_verify(cli, 0);
361 n_log(LOG_ERR, "client TLS version pin failed");
362 failures++;
363 }
364 if (drive_handshake(cli, 0, NULL) != TRUE) {
365 n_log(LOG_ERR, "client non-blocking handshake failed");
366 failures++;
367 } else {
368 char msg[MSG_LEN];
369 char buf[MSG_LEN] = "";
370 /* A stale entry on the thread's error queue -- left by some
371 * OTHER connection, e.g. one shut down mid-handshake -- must not
372 * fail this healthy one: OpenSSL's SSL_get_error() reports
373 * SSL_ERROR_SSL whenever the queue is non-empty, so the *_once
374 * movers must clear it first. The server is waiting for our
375 * message, so the idle read can only be WANT_READ. (A
376 * single-threaded DarkChan server closed an innocent client this
377 * way and lost its first frames, 2026-10-08.) */
379 ssize_t idle = recv_ssl_data_once(cli, buf, MSG_LEN);
380 if (idle != NETW_IO_WANT_READ) {
381 n_log(LOG_ERR, "stale error queue: idle recv returned %zd, not WANT_READ", idle);
382 failures++;
383 }
384 leave_stale_openssl_error(); /* and the send that follows */
385 memcpy(msg, MSG, MSG_LEN);
386 if (io_full(cli, msg, MSG_LEN, 1) != TRUE) {
387 n_log(LOG_ERR, "client send failed");
388 failures++;
389 } else if (io_full(cli, buf, MSG_LEN, 0) != TRUE) {
390 n_log(LOG_ERR, "client recv failed");
391 failures++;
392 } else if (memcmp(buf, REPLY, MSG_LEN) != 0) {
393 n_log(LOG_ERR, "client got an unexpected reply");
394 failures++;
395 } else {
396 n_log(LOG_NOTICE, "client received: %s", buf);
397 }
398 }
399 }
400
401 netw_close(&cli);
402 pthread_join(th, NULL);
403 if (!srv.ok) {
404 n_log(LOG_ERR, "server did not complete the exchange");
405 failures++;
406 }
407
408 netw_close(&listen);
409 SSL_CTX_free(sctx);
410 free_nstr(&cert);
411 free_nstr(&key);
413
414 if (failures) {
415 n_log(LOG_ERR, "ex_network_ssl_nonblock: %d failure(s)", failures);
416 return 1;
417 }
418 n_log(LOG_NOTICE, "ex_network_ssl_nonblock: all checks passed");
419 return 0;
420}
void process_args(int argc, char **argv)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
NETWORK * netw
Network for server mode, accepting incomming.
Definition ex_network.c:39
static void * server_fn(void *p)
static NETWORK * bind_free(char *port_out, size_t port_out_len)
#define MSG_LEN
fixed exchange message length (both messages are 17 chars + NUL)
volatile sig_atomic_t done
char * cert
char * port
#define MSG
client->server probe (both messages share this fixed length)
SSL_CTX * ctx
shared server ctx (owned by main)
static SSL_CTX * make_server_ctx(const N_STR *cert_pem, const N_STR *key_pem)
static int io_full(NETWORK *netw, char *buf, uint32_t n, int is_send)
static int poll_fd(SOCKET fd, int want_write, int timeout_ms)
static int drive_handshake(NETWORK *netw, int is_server, SSL_CTX *sctx)
NETWORK * listen
the listening socket
#define REPLY
server->client probe
static int drive_connect(NETWORK *netw)
int ok
set to 1 once the server completed the exchange
static void leave_stale_openssl_error(void)
leave a genuine error on this thread's OpenSSL queue (portable: a failed PEM parse,...
server worker context
volatile char key[227]
TRUE while the key is held.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:104
#define LOG_NULL
no log output
Definition n_log.h:70
#define LOG_INFO
informational
Definition n_log.h:106
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
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
A box including a string and his lenght.
Definition n_str.h:61
int n_x509_generate_ca(const char *cn, int days, N_STR **ca_cert_pem, N_STR **ca_key_pem)
Generate a self-signed certificate authority (CA) keypair and cert.
Definition n_x509.c:226
N_SOCKET link
networking socket
Definition n_network.h:454
SOCKET sock
a normal socket
Definition n_network.h:356
ssize_t send_ssl_data_once(void *netw, char *buf, uint32_t n)
single-attempt TLS send for non-blocking sockets (reactor use).
Definition n_network.c:5279
#define 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
ssize_t recv_ssl_data_once(void *netw, char *buf, uint32_t n)
single-attempt TLS recv for non-blocking sockets (reactor use).
Definition n_network.c:5367
int netw_ssl_set_verify(NETWORK *netw, int enable)
enable or disable SSL peer certificate verification
Definition n_network.c:2053
NETWORK * netw_accept_nonblock_from(NETWORK *from, int blocking)
make a normal blocking 'accept' .
Definition n_network.c:4188
int netw_ssl_server_handshake_once(NETWORK *netw, SSL_CTX *ctx)
non-blocking, resumable TLS server handshake against a shared ctx
Definition n_network.c:3093
int netw_connect_nonblock(NETWORK **netw, char *host, char *port, int ip_version)
begin a non-blocking TCP connect without waiting for it to complete
Definition n_network.c:2651
int netw_ssl_handshake_once(NETWORK *netw, const char *sni_hostname)
non-blocking, resumable TLS client handshake
Definition n_network.c:3050
int netw_init_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1335
#define NETW_TLS_MAX_VERSION
highest TLS version the linked TLS library can negotiate, as a TLS*_VERSION constant.
Definition n_network.h:266
int netw_make_listening(NETWORK **netw, char *addr, char *port, int nbpending, int ip_version)
Make a NETWORK be a Listening network.
Definition n_network.c:3493
int netw_unload_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1368
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
Definition n_network.h:48
int SOCKET
default socket declaration
Definition n_network.h:111
int netw_connect_check(NETWORK *netw)
advance a non-blocking connect started by netw_connect_nonblock()
Definition n_network.c:2713
#define NETW_IO_WANT_WRITE
single-attempt I/O: the operation cannot progress until the socket is WRITABLE.
Definition n_network.h:81
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:3258
int netw_ssl_start_client(NETWORK *netw)
Set up a client SSL_CTX on an already-connected NETWORK.
Definition n_network.c:2868
int netw_ssl_set_versions(NETWORK *netw, int min_version, int max_version)
set the minimum (and optionally maximum) TLS protocol version on a connection's SSL context
Definition n_network.c:2085
Structure of a NETWORK.
Definition n_network.h:375
Common headers and low-level functions & define.
Generic log system.
Network Engine.
N_STR and string function declaration.
X.509 helpers: self-signed CA generation and per-host leaf minting.