49#include <openssl/ssl.h>
52#define ALPN_HOST "alpn.host.example"
56#define H2_REPLY "served-over-h2"
79 while ((opt = getopt(argc, argv,
"hvV:")) != EOF) {
82 if (!strncmp(
"LOG_NULL", optarg, 8))
84 else if (!strncmp(
"LOG_NOTICE", optarg, 10))
86 else if (!strncmp(
"LOG_INFO", optarg, 8))
88 else if (!strncmp(
"LOG_ERR", optarg, 7))
90 else if (!strncmp(
"LOG_DEBUG", optarg, 9))
94 fprintf(stderr,
"ex_network_alpn\n");
98 fprintf(stderr,
"usage: %s [-V LOG_LEVEL]\n", argv[0]);
107 const char* host = (sni && sni[0]) ? sni :
"localhost";
115static ssize_t
tls_read(
void* io,
void* buf,
size_t len) {
117 int r = SSL_read(n->
ssl, buf, (
int)len);
118 return (r < 0) ? -1 : (ssize_t)r;
121static ssize_t
tls_write(
void* io,
const void* buf,
size_t len) {
125 int w = SSL_write(n->
ssl, (
const char*)buf + off, (
int)(len - off));
143 h[0].
name = (
char*)
":status";
144 h[0].
value = (
char*)
"200";
145 h[1].
name = (
char*)
"content-type";
146 h[1].
value = (
char*)
"text/plain";
154 static const char*
const protos[] = {
"h2",
"http/1.1"};
157 for (round = 0; round <
ROUNDS; round++) {
162 n_log(
LOG_ERR,
"server TLS handshake failed on round %d", round);
169 if (strcmp(s->
selected[round],
"h2") == 0) {
179 if (SSL_read(c->
ssl, buf, 4) > 0)
195 unsigned char block[512], frame[1024], rb[8192];
198 size_t w, rlen = 0, blen = 0;
199 int done = 0, rc = -1;
203 req[0].
name = (
char*)
":method";
204 req[0].
value = (
char*)
"GET";
205 req[1].
name = (
char*)
":scheme";
206 req[1].
value = (
char*)
"https";
207 req[2].
name = (
char*)
":authority";
209 req[3].
name = (
char*)
":path";
210 req[3].
value = (
char*)path;
235 if (rlen ==
sizeof(rb))
237 n =
tls_read(c, rb + rlen,
sizeof(rb) - rlen);
248 for (i = 0; i < count; i++)
249 if (hdrs[i].name && strcmp(hdrs[i].name,
":status") == 0)
250 *status = atoi(hdrs[i].value ? hdrs[i].value :
"0");
253 if (blen + f.
length < body_cap) {
261 memmove(rb, rb + consumed, rlen - consumed);
297 for (p = 18400; p < 18500; p++) {
300 snprintf(ps,
sizeof(ps),
"%d", p);
302 snprintf(port_out, port_out_len,
"%s", ps);
311int main(
int argc,
char** argv) {
312 static const char*
const both[] = {
"h2",
"http/1.1"};
313 static const char*
const only11[] = {
"http/1.1"};
324 memset(&srv, 0,
sizeof(srv));
340 pthread_create(&th, NULL,
server_fn, &srv);
350 if (strcmp(got,
"h2") != 0) {
351 n_log(
LOG_ERR,
"round 1 negotiated '%s', expected h2", got);
354 if (
h2_client_get(cli,
"/alpn", &status, body,
sizeof(body)) != 0) {
358 if (status != 200 || strcmp(body,
H2_REPLY) != 0) {
359 n_log(
LOG_ERR,
"round 1 got status %d body '%s'", status, body);
372 if (strcmp(got,
"http/1.1") != 0) {
373 n_log(
LOG_ERR,
"round 2 negotiated '%s', expected http/1.1", got);
377 if (SSL_read(cli->
ssl, buf, 4) <= 0) {
393 if (got[0] !=
'\0') {
394 n_log(
LOG_ERR,
"round 3 negotiated '%s', expected nothing", got);
398 if (SSL_read(cli->
ssl, buf, 4) <= 0) {
405 pthread_join(th, NULL);
void process_args(int argc, char **argv)
static int h2_client_get(NETWORK *c, const char *path, int *status, char *body, size_t body_cap)
static void * server_fn(void *p)
#define ROUNDS
how many connections the server accepts before it stops
#define ALPN_HOST
the host name the client requests via SNI
static int h2_on_request(void *ud, N_H2_SERVER *srv, const N_H2_REQUEST *req)
static NETWORK * bind_free(char *port_out, size_t port_out_len)
#define H2_REPLY
the body the HTTP/2 handler answers with
static int pick_cb(const char *sni, N_STR **cert_pem, N_STR **key_pem, void *user_data)
static NETWORK * client_connect(const char *port, const char *const *protos, size_t count, char *got, size_t got_len)
static ssize_t tls_read(void *io, void *buf, size_t len)
static ssize_t tls_write(void *io, const void *buf, size_t len)
N_STR * key
the CA private key PEM
N_STR * cert
the CA certificate PEM
CA used by the server to mint per-host leaves.
volatile sig_atomic_t done
NETWORK * listen
the listening socket
char h2_path[128]
the :path of the last HTTP/2 request
int rounds_done
how many connections it served
int h2_requests
requests served over HTTP/2
char selected[3][32]
what ALPN the server answered per round
CA_CTX * ca
the CA to mint leaves with
#define _str(__PTR)
define true
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_DEBUG
debug-level messages
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
#define LOG_NOTICE
normal but significant condition
#define LOG_NULL
no log output
#define LOG_INFO
informational
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
A box including a string and his lenght.
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.
int n_x509_mint_host_cert(const char *host, const N_STR *ca_cert_pem, const N_STR *ca_key_pem, int days, N_STR **leaf_cert_pem, N_STR **leaf_key_pem)
Mint a per-host leaf certificate signed by the given CA.
int netw_ssl_set_verify(NETWORK *netw, int enable)
enable or disable SSL peer certificate verification
int netw_ssl_server_handshake_ex(NETWORK *netw, netw_sni_pick_cb pick, void *user_data, const char *const *alpn, size_t alpn_count)
upgrade an already-accepted plaintext connection to a TLS server, selecting the certificate per clien...
int netw_init_openssl(void)
Do not directly use, internal api.
int netw_make_listening(NETWORK **netw, char *addr, char *port, int nbpending, int ip_version)
Make a NETWORK be a Listening network.
int netw_ssl_do_handshake(NETWORK *netw, const char *sni_hostname)
Complete the SSL handshake on an already-connected NETWORK.
int netw_unload_openssl(void)
Do not directly use, internal api.
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
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.
int netw_ssl_set_alpn(NETWORK *netw, const char *const *protos, size_t count)
offer an ALPN protocol list on a TLS client connection, before its handshake
NETWORK * netw_accept_from(NETWORK *from)
make a normal blocking 'accept' .
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
int netw_ssl_get_alpn(NETWORK *netw, char *out, size_t out_len)
get the ALPN protocol the completed handshake selected
Common headers and low-level functions & define.
int n_http2_hpack_decode(N_H2_HPACK *ctx, const unsigned char *block, size_t len, N_H2_HEADER *out, size_t max_out, size_t *count)
decode an HPACK header block into out[] (owned name/value strings).
size_t n_http2_hpack_encode(const N_H2_HEADER *headers, size_t n, unsigned char *out, size_t out_cap)
encode a header list into a block using literal representations without indexing and without Huffman ...
N_H2_HPACK * n_http2_hpack_new(size_t max_table_size)
create an HPACK codec context with the given dynamic table size limit (0 uses N_H2_HPACK_DEFAULT_TABL...
void n_http2_hpack_free(N_H2_HPACK **pctx)
free an HPACK codec context and NULL the caller's pointer
void n_http2_hpack_headers_free(N_H2_HEADER *headers, size_t n)
free the owned name/value strings of a decoded header array (does not free the array itself)
int n_http2_frame_parse(const unsigned char *buf, size_t len, N_H2_FRAME *frame, size_t *consumed)
parse one HTTP/2 frame from a byte buffer (stateless).
size_t n_http2_frame_build_header(unsigned char *out, size_t out_cap, uint32_t length, int type, int flags, uint32_t stream_id)
write a 9-byte HTTP/2 frame header into out.
HPACK codec context: the dynamic table (index 0 = most recently added).
char * value
header field value (owned after decode)
#define N_H2_PREFACE
the client connection preface sent before any HTTP/2 frame (RFC 7540 3.5)
#define N_H2_FLAG_END_HEADERS
HEADERS/PUSH_PROMISE/CONTINUATION: header block complete.
#define N_H2_FRAME_HEADER_LEN
length of the fixed frame header in bytes
char * name
header field name (owned after decode)
uint32_t length
payload length (24-bit)
const unsigned char * payload
pointer into the input buffer (may be NULL when length is 0)
int type
frame type (N_H2_*)
#define N_H2_SETTINGS
SETTINGS.
uint32_t stream_id
stream identifier (31-bit; the reserved bit is cleared)
#define N_H2_HEADERS
HEADERS.
#define N_H2_FLAG_END_STREAM
DATA/HEADERS: last frame of the stream.
a parsed HTTP/2 frame; payload aliases the input buffer
one HPACK header field; after a decode both strings are owned (heap)
int n_http2_server_respond(N_H2_SERVER *s, uint32_t stream_id, const N_H2_HEADER *headers, size_t nheaders, const unsigned char *body, size_t body_len)
write a complete response on a stream (HEADERS then DATA, END_STREAM)
int n_http2_server_run(N_H2_SERVER *s, n_h2_srv_request_cb cb, void *user_data)
serve the connection until it ends
N_H2_SERVER * n_http2_server_new(void *io, n_h2_srv_read_fn rd, n_h2_srv_write_fn wr)
create a server connection over the given I/O callbacks
void n_http2_server_free(N_H2_SERVER **srv)
free a server connection and NULL the caller's pointer
const char * n_http2_server_error(const N_H2_SERVER *s)
the reason the connection failed, or NULL when none was recorded
int n_http2_server_handshake(N_H2_SERVER *s)
read and validate the client connection preface, then send ours
HTTP/2 (RFC 7540) server-side connection and stream state machine.
uint32_t stream_id
the stream the request arrived on
const char * path
the :path pseudo-header, or NULL when absent
one complete request received on a stream; every pointer is owned by the server and valid only for th...
N_STR and string function declaration.
X.509 helpers: self-signed CA generation and per-host leaf minting.