1 ex__network__accept_8c
gullradriel edited this page 2026-11-02 12:42:25 +01:00

examples/ex_network_accept.c

Structures

Name
struct CLIENT_JOB
a client thread's job

Functions

Name
void wait_stop(CLIENT_JOB * job)
void * tls_client(void * p)
void test_sockopt_timeo(void )
void test_peer_port(void )
void test_handshake_timeout(void )
void test_handshake_ok(int timeout_ms)
void test_handshake_dropped(const char * what, void ()(void *) client)
void * silent_client(void * p)
SOCKET raw_client(unsigned int port)
void process_args(int argc, char ** argv)
NETWORK * make_tls_listener(unsigned int * port, int timeout_ms)
NETWORK * make_listener(unsigned int * port)
int main(int argc, char ** argv)
unsigned int local_port(SOCKET s)
void * drip_client(void * p)

Attributes

Name
N_STR * leaf_key
N_STR * leaf_cert
int failures
N_STR * ca_key
N_STR * ca_cert

Defines

Name
HS_TIMEOUT_MS
the TLS deadline the timeout tests set, in msecs
HS_LATEST_MS
a dropped handshake must end before this, in msecs (deadline + margin)
CHECK(cond, ...)
record one check

Functions Documentation

function wait_stop

static void wait_stop(
    CLIENT_JOB * job
)

function tls_client

static void * tls_client(
    void * p
)

function test_sockopt_timeo

static void test_sockopt_timeo(
    void 
)

function test_peer_port

static void test_peer_port(
    void 
)

function test_handshake_timeout

static void test_handshake_timeout(
    void 
)

function test_handshake_ok

static void test_handshake_ok(
    int timeout_ms
)

function test_handshake_dropped

static void test_handshake_dropped(
    const char * what,
    void *(*)(void *) client
)

function silent_client

static void * silent_client(
    void * p
)

function raw_client

static SOCKET raw_client(
    unsigned int port
)

function process_args

void process_args(
    int argc,
    char ** argv
)

function make_tls_listener

static NETWORK * make_tls_listener(
    unsigned int * port,
    int timeout_ms
)

function make_listener

static NETWORK * make_listener(
    unsigned int * port
)

function main

int main(
    int argc,
    char ** argv
)

function local_port

static unsigned int local_port(
    SOCKET s
)

function drip_client

static void * drip_client(
    void * p
)

Attributes Documentation

variable leaf_key

static N_STR * leaf_key = NULL;

variable leaf_cert

static N_STR * leaf_cert = NULL;

variable failures

static int failures = 0;

variable ca_key

static N_STR * ca_key = NULL;

variable ca_cert

static N_STR * ca_cert = NULL;

Macros Documentation

define HS_TIMEOUT_MS

#define HS_TIMEOUT_MS 500

the TLS deadline the timeout tests set, in msecs

define HS_LATEST_MS

#define HS_LATEST_MS 1500

a dropped handshake must end before this, in msecs (deadline + margin)

define CHECK

#define CHECK(
    cond,
    ...
)
do {                                           \
    if (cond) {                                \
        n_log(LOG_NOTICE, "ok: " __VA_ARGS__); \
    } else {                                   \
        n_log(LOG_ERR, "FAIL: " __VA_ARGS__);  \
        failures++;                            \
    }                                          \
} while (0)

record one check

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include "nilorea/n_common.h"
#include "nilorea/n_log.h"
#include "nilorea/n_network.h"
#include "nilorea/n_str.h"
#include "nilorea/n_time.h"
#include "nilorea/n_x509.h"

#include <errno.h>
#include <getopt.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int failures = 0;

#define CHECK(cond, ...)                           \
    do {                                           \
        if (cond) {                                \
            n_log(LOG_NOTICE, "ok: " __VA_ARGS__); \
        } else {                                   \
            n_log(LOG_ERR, "FAIL: " __VA_ARGS__);  \
            failures++;                            \
        }                                          \
    } while (0)

/* Parse the -V LOG_LEVEL verbosity argument. */
void process_args(int argc, char** argv) {
    int opt = 0;
    while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
        switch (opt) {
            case 'V':
                if (!strncmp("LOG_NULL", optarg, 8))
                    set_log_level(LOG_NULL);
                else if (!strncmp("LOG_NOTICE", optarg, 10))
                    set_log_level(LOG_NOTICE);
                else if (!strncmp("LOG_INFO", optarg, 8))
                    set_log_level(LOG_INFO);
                else if (!strncmp("LOG_ERR", optarg, 7))
                    set_log_level(LOG_ERR);
                else if (!strncmp("LOG_DEBUG", optarg, 9))
                    set_log_level(LOG_DEBUG);
                break;
            case 'v':
                fprintf(stderr, "ex_network_accept\n");
                exit(1);
            case 'h':
            default:
                fprintf(stderr, "usage: %s [-V LOG_LEVEL]\n", argv[0]);
                break;
        }
    }
}

/* The port a socket is bound to, 0 on error. */
static unsigned int local_port(SOCKET s) {
    struct sockaddr_in a;
    socklen_t len = sizeof(a);
    memset(&a, 0, sizeof(a));
    if (getsockname(s, (struct sockaddr*)&a, &len) != 0)
        return 0;
    return ntohs(a.sin_port);
}

/* A plain TCP client socket bound to an ephemeral local port and connected to
 * 127.0.0.1:port, INVALID_SOCKET on error. */
static SOCKET raw_client(unsigned int port) {
    SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in a;
    if (s == INVALID_SOCKET)
        return INVALID_SOCKET;
    memset(&a, 0, sizeof(a));
    a.sin_family = AF_INET;
    a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    a.sin_port = 0;
    if (bind(s, (struct sockaddr*)&a, sizeof(a)) != 0) {
        closesocket(s);
        return INVALID_SOCKET;
    }
    a.sin_port = htons((unsigned short)port);
    if (connect(s, (struct sockaddr*)&a, sizeof(a)) != 0) {
        closesocket(s);
        return INVALID_SOCKET;
    }
    return s;
}

/* A listener on 127.0.0.1, on a port picked by the system. */
static NETWORK* make_listener(unsigned int* port) {
    NETWORK* l = NULL;
    if (netw_make_listening(&l, "127.0.0.1", "0", 8, NETWORK_IPV4) != TRUE || !l)
        return NULL;
    *port = local_port(l->link.sock);
    return l;
}

/* link.port of an accepted connection is the peer's port. */
static void test_peer_port(void) {
    unsigned int lport = 0;
    NETWORK* l = make_listener(&lport);
    CHECK(l && lport > 0, "plain listener on 127.0.0.1:%u", lport);
    if (!l)
        return;
    SOCKET c = raw_client(lport);
    unsigned int cport = (c != INVALID_SOCKET) ? local_port(c) : 0;
    int ret = 0;
    NETWORK* a = netw_accept_from_ex(l, 0, 0, 2000, &ret);
    CHECK(a != NULL, "accepted the client (retval %d)", ret);
    if (a) {
        char want[8];
        snprintf(want, sizeof(want), "%u", cport);
        CHECK(a->link.port && strcmp(a->link.port, want) == 0, "link.port '%s' is the peer's port %u, not the listener's %u", _str(a->link.port), cport, lport);
        CHECK(a->link.ip && strcmp(a->link.ip, "127.0.0.1") == 0, "link.ip '%s' is the peer's address", _str(a->link.ip));
        CHECK(l->link.port && strcmp(l->link.port, "0") == 0, "the listener keeps its own link.port '%s'", _str(l->link.port));
        netw_close(&a);
    }
    if (c != INVALID_SOCKET)
        closesocket(c);
    netw_close(&l);
}

/* SO_RCVTIMEO / SO_SNDTIMEO are msecs (NETW_SOCKOPT_TIMEO_MS). */
static void test_sockopt_timeo(void) {
    unsigned int lport = 0;
    NETWORK* l = make_listener(&lport);
    CHECK(l != NULL, "timeouts: listener on 127.0.0.1:%u", lport);
    if (!l)
        return;
    SOCKET c = raw_client(lport);
    int ret = 0;
    NETWORK* a = netw_accept_from_ex(l, 0, 0, 2000, &ret);
    CHECK(a != NULL, "timeouts: accepted the client");
    if (a) {
        CHECK(netw_setsockopt(a, SO_RCVTIMEO, 1500) == TRUE && netw_setsockopt(a, SO_SNDTIMEO, 1500) == TRUE, "timeouts: set to 1500");
#ifdef __windows__
        DWORD rcv = 0, snd = 0;
        int len = sizeof(rcv);
        getsockopt(a->link.sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&rcv, &len);
        len = sizeof(snd);
        getsockopt(a->link.sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&snd, &len);
        CHECK(rcv == 1500 && snd == 1500, "timeouts: 1500 reads back as %lu / %lu ms", (unsigned long)rcv, (unsigned long)snd);
#else
        struct timeval rcv, snd;
        socklen_t len = sizeof(rcv);
        memset(&rcv, 0, sizeof(rcv));
        memset(&snd, 0, sizeof(snd));
        getsockopt(a->link.sock, SOL_SOCKET, SO_RCVTIMEO, &rcv, &len);
        len = sizeof(snd);
        getsockopt(a->link.sock, SOL_SOCKET, SO_SNDTIMEO, &snd, &len);
        CHECK(rcv.tv_sec == 1 && rcv.tv_usec == 500000 && snd.tv_sec == 1 && snd.tv_usec == 500000,
              "timeouts: 1500 reads back as %ld.%06ld / %ld.%06ld s", (long)rcv.tv_sec, (long)rcv.tv_usec, (long)snd.tv_sec, (long)snd.tv_usec);
#endif
        /* the client sends nothing: the recv must give up after ~250 ms */
        char b;
        N_TIME t;
        netw_setsockopt(a, SO_RCVTIMEO, 250);
        start_HiTimer(&t);
        ssize_t r = recv(a->link.sock, &b, 1, 0);
        time_t ms = get_msec(&t);
        CHECK(r < 0 && ms >= 200 && ms < 2000, "timeouts: recv with 250 gave up after %ld ms", (long)ms);
        CHECK(netw_setsockopt(a, SO_RCVTIMEO, 0) == TRUE && a->so_rcvtimeo == 0, "timeouts: 0 clears it");
        netw_close(&a);
    }
    if (c != INVALID_SOCKET)
        closesocket(c);
    netw_close(&l);
}

#ifdef HAVE_OPENSSL
#define HS_TIMEOUT_MS 500
#define HS_LATEST_MS 1500

static N_STR *ca_cert = NULL, *ca_key = NULL, *leaf_cert = NULL, *leaf_key = NULL;

typedef struct CLIENT_JOB {
    unsigned int port; 
    volatile int stop; 
    int connected;     
} CLIENT_JOB;

/* A TLS listener on 127.0.0.1 with the test leaf, and @p timeout_ms set. */
static NETWORK* make_tls_listener(unsigned int* port, int timeout_ms) {
    NETWORK* l = make_listener(port);
    if (!l)
        return NULL;
    if (netw_set_crypto_pem(l, leaf_key->data, leaf_cert->data) != TRUE) {
        netw_close(&l);
        return NULL;
    }
    netw_setsockopt(l, NETWORK_HANDSHAKE_TIMEOUT, timeout_ms);
    return l;
}

/* Sleep until the main thread sets job->stop, at most 10 s. */
static void wait_stop(CLIENT_JOB* job) {
    int i;
    for (i = 0; i < 1000 && !job->stop; i++)
        u_sleep(10000);
}

/* Connect and send nothing. */
static void* silent_client(void* p) {
    CLIENT_JOB* job = (CLIENT_JOB*)p;
    SOCKET c = raw_client(job->port);
    wait_stop(job);
    if (c != INVALID_SOCKET)
        closesocket(c);
    return NULL;
}

/* Connect and drip the start of a ClientHello, one byte every 100 ms: each
 * byte would restart a per-read timeout, never the whole-handshake deadline. */
static void* drip_client(void* p) {
    static const unsigned char hello[] = {0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03,
                                          0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
                                          0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15};
    CLIENT_JOB* job = (CLIENT_JOB*)p;
    SOCKET c = raw_client(job->port);
    size_t i;
    for (i = 0; c != INVALID_SOCKET && i < sizeof(hello) && !job->stop; i++) {
        if (send(c, (const char*)hello + i, 1, 0) != 1)
            break;
        u_sleep(100000);
    }
    wait_stop(job);
    if (c != INVALID_SOCKET)
        closesocket(c);
    return NULL;
}

/* A real TLS client: handshake, read the server's one byte, close. */
static void* tls_client(void* p) {
    CLIENT_JOB* job = (CLIENT_JOB*)p;
    NETWORK* c = NULL;
    char port[8];
    snprintf(port, sizeof(port), "%u", job->port);
    /* netw_ssl_connect_client() only connects and sets up the context; the
     * handshake is netw_ssl_do_handshake(). Verification is off by default. */
    if (netw_ssl_connect_client(&c, "127.0.0.1", port, NETWORK_IPV4) == TRUE && c &&
        netw_ssl_do_handshake(c, "localhost") == TRUE) {
        char b = 0;
        job->connected = 1;
        SSL_read(c->ssl, &b, 1);
    }
    /* no wait_stop() here: the server's netw_close() waits for this close */
    if (c)
        netw_close(&c);
    return NULL;
}

/* A client that never completes the handshake is dropped at the deadline. */
static void test_handshake_dropped(const char* what, void* (*client)(void*)) {
    unsigned int lport = 0;
    NETWORK* l = make_tls_listener(&lport, HS_TIMEOUT_MS);
    CHECK(l != NULL, "%s: TLS listener with a %d ms handshake deadline", what, HS_TIMEOUT_MS);
    if (!l)
        return;
    CHECK(l->handshake_timeout == HS_TIMEOUT_MS, "%s: netw_setsockopt stored the deadline (%d)", what, l->handshake_timeout);
    CLIENT_JOB job = {lport, 0, 0};
    pthread_t th;
    pthread_create(&th, NULL, client, &job);
    int ret = 0;
    N_TIME t;
    start_HiTimer(&t);
    NETWORK* a = netw_accept_from_ex(l, 0, 0, 2000, &ret);
    time_t ms = get_msec(&t);
    CHECK(a == NULL, "%s: connection refused", what);
    CHECK(ret == ETIMEDOUT, "%s: retval is ETIMEDOUT (%d)", what, ret);
    CHECK(ms >= HS_TIMEOUT_MS - 50 && ms < HS_LATEST_MS, "%s: dropped after %ld ms (deadline %d ms)", what, (long)ms, HS_TIMEOUT_MS);
    if (a)
        netw_close(&a);
    job.stop = 1;
    pthread_join(th, NULL);
    netw_close(&l);
}

/* A real client completes the handshake, deadline set or not. */
static void test_handshake_ok(int timeout_ms) {
    unsigned int lport = 0;
    NETWORK* l = make_tls_listener(&lport, timeout_ms);
    CHECK(l != NULL, "real client, deadline %d ms: TLS listener", timeout_ms);
    if (!l)
        return;
    CLIENT_JOB job = {lport, 0, 0};
    pthread_t th;
    pthread_create(&th, NULL, tls_client, &job);
    int ret = 0;
    NETWORK* a = netw_accept_from_ex(l, 0, 0, 5000, &ret);
    CHECK(a != NULL, "real client, deadline %d ms: accepted (retval %d)", timeout_ms, ret);
    if (a) {
        CHECK(a->link.is_blocking == 1, "real client, deadline %d ms: socket blocking again", timeout_ms);
        CHECK(SSL_write(a->ssl, "x", 1) == 1, "real client, deadline %d ms: TLS write after the handshake", timeout_ms);
    }
    /* close our side before joining: the client's netw_close() waits for it */
    if (a)
        netw_close(&a);
    job.stop = 1;
    pthread_join(th, NULL);
    CHECK(job.connected == 1, "real client, deadline %d ms: client side completed", timeout_ms);
    netw_close(&l);
}

/* All the TLS handshake checks. */
static void test_handshake_timeout(void) {
    netw_init_openssl();
    if (n_x509_generate_ca("Nilorea Accept Test CA", 30, &ca_cert, &ca_key) != 0 ||
        n_x509_mint_host_cert("localhost", ca_cert, ca_key, 30, &leaf_cert, &leaf_key) != 0) {
        n_log(LOG_ERR, "FAIL: could not mint the test certificates");
        failures++;
    } else {
        test_handshake_dropped("silent client", silent_client);
        test_handshake_dropped("dripping client", drip_client);
        test_handshake_ok(2000);
        test_handshake_ok(0);
    }
    free_nstr(&ca_cert);
    free_nstr(&ca_key);
    free_nstr(&leaf_cert);
    free_nstr(&leaf_key);
    netw_unload_openssl();
}
#endif

int main(int argc, char** argv) {
    set_log_level(LOG_NOTICE);
    process_args(argc, argv);
    if (netw_init_wsa(1, 2, 2) == FALSE) {
        n_log(LOG_ERR, "WSA init failed");
        return 1;
    }

    test_peer_port();
    test_sockopt_timeo();
#ifdef HAVE_OPENSSL
    test_handshake_timeout();
#else
    n_log(LOG_NOTICE, "TLS handshake checks skipped: built without OpenSSL");
#endif

    netw_init_wsa(0, 2, 2);
    if (failures) {
        n_log(LOG_ERR, "%d check(s) failed", failures);
        return 1;
    }
    n_log(LOG_NOTICE, "all checks passed");
    return 0;
}