Path: blob/main/crypto/openssl/demos/http3/ossl-nghttp3-demo-server.c
109455 views
/*1* Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.2*3* Licensed under the Apache License 2.0 (the "License"). You may not use4* this file except in compliance with the License. You can obtain a copy5* in the file LICENSE in the source distribution or at6* https://www.openssl.org/source/license.html7*/8#include <assert.h>9#include <netinet/in.h>10#include <nghttp3/nghttp3.h>11#include <openssl/err.h>12#include <openssl/quic.h>13#include <openssl/ssl.h>14#include <unistd.h>15#include <sys/stat.h>16#include <fcntl.h>17#include <sys/socket.h>1819#ifndef PATH_MAX20#define PATH_MAX 25521#endif2223#define nghttp3_arraylen(A) (sizeof(A) / sizeof(*(A)))2425/* The crappy test wants 20 bytes */26#define NULL_PAYLOAD "12345678901234567890"27static uint8_t *nulldata = (uint8_t *)NULL_PAYLOAD;28static size_t nulldata_sz = sizeof(NULL_PAYLOAD) - 1;2930/* The nghttp3 variable we need in the main part and read_from_ssl_ids */31static nghttp3_settings settings;32static const nghttp3_mem *mem;33static nghttp3_callbacks callbacks = { 0 };3435/* 3 streams created by the server and 4 by the client (one is bidi) */36struct ssl_id {37SSL *s; /* the stream openssl uses in SSL_read(), SSL_write etc */38uint64_t id; /* the stream identifier the nghttp3 uses */39int status; /* 0 or one the below status and origin */40};41/* status and origin of the streams the possible values are: */42#define CLIENTUNIOPEN 0x01 /* unidirectional open by the client (2, 6 and 10) */43#define CLIENTCLOSED 0x02 /* closed by the client */44#define CLIENTBIDIOPEN 0x04 /* bidirectional open by the client (something like 0, 4, 8 ...) */45#define SERVERUNIOPEN 0x08 /* unidirectional open by the server (3, 7 and 11) */46#define SERVERCLOSED 0x10 /* closed by the server (us) */47#define TOBEREMOVED 0x20 /* marked for removing in read_from_ssl_ids, */48/* it will be removed after processing all events */49#define ISLISTENER 0x40 /* the stream is a listener from SSL_new_listener() */50#define ISCONNECTION 0x80 /* the stream is a connection from SSL_accept_connection() */5152#define MAXSSL_IDS 2053#define MAXURL 2555455struct h3ssl {56struct ssl_id ssl_ids[MAXSSL_IDS];57int end_headers_received; /* h3 header received call back called */58int datadone; /* h3 has given openssl all the data of the response */59int has_uni; /* we have the 3 uni directional stream needed */60int close_done; /* connection begins terminating EVENT_EC */61int close_wait; /* we are waiting for a close or a new request */62int done; /* connection terminated EVENT_ECD, after EVENT_EC */63int new_conn; /* a new connection has been received */64int received_from_two; /* workaround for -607 on nghttp3_conn_read_stream on stream 2 */65int restart; /* new request/response cycle started */66uint64_t id_bidi; /* the id of the stream used to read request and send response */67char *fileprefix; /* prefix of the directory to fetch files from */68char url[MAXURL]; /* url to serve the request */69uint8_t *ptr_data; /* pointer to the data to send */70size_t ldata; /* amount of bytes to send */71int offset_data; /* offset to next data to send */72};7374static void make_nv(nghttp3_nv *nv, const char *name, const char *value)75{76nv->name = (uint8_t *)name;77nv->value = (uint8_t *)value;78nv->namelen = strlen(name);79nv->valuelen = strlen(value);80nv->flags = NGHTTP3_NV_FLAG_NONE;81}8283static void init_ids(struct h3ssl *h3ssl)84{85struct ssl_id *ssl_ids;86int i;87char *prior_fileprefix = h3ssl->fileprefix;8889if (h3ssl->ptr_data != NULL && h3ssl->ptr_data != nulldata)90free(h3ssl->ptr_data);9192memset(h3ssl, 0, sizeof(struct h3ssl));9394ssl_ids = h3ssl->ssl_ids;95for (i = 0; i < MAXSSL_IDS; i++)96ssl_ids[i].id = UINT64_MAX;97h3ssl->id_bidi = UINT64_MAX;9899/* restore the fileprefix */100h3ssl->fileprefix = prior_fileprefix;101}102103static void reuse_h3ssl(struct h3ssl *h3ssl)104{105h3ssl->end_headers_received = 0;106h3ssl->datadone = 0;107h3ssl->close_done = 0;108h3ssl->close_wait = 0;109h3ssl->done = 0;110memset(h3ssl->url, '\0', sizeof(h3ssl->url));111if (h3ssl->ptr_data != NULL && h3ssl->ptr_data != nulldata)112free(h3ssl->ptr_data);113h3ssl->ptr_data = NULL;114h3ssl->offset_data = 0;115h3ssl->ldata = 0;116}117118static void add_id_status(uint64_t id, SSL *ssl, struct h3ssl *h3ssl, int status)119{120struct ssl_id *ssl_ids;121int i;122123ssl_ids = h3ssl->ssl_ids;124for (i = 0; i < MAXSSL_IDS; i++) {125if (ssl_ids[i].s == NULL) {126ssl_ids[i].s = ssl;127ssl_ids[i].id = id;128ssl_ids[i].status = status;129return;130}131}132printf("Oops too many streams to add!!!\n");133exit(1);134}135static void add_id(uint64_t id, SSL *ssl, struct h3ssl *h3ssl)136{137add_id_status(id, ssl, h3ssl, 0);138}139140/* Add listener and connection */141static void add_ids_listener(SSL *ssl, struct h3ssl *h3ssl)142{143add_id_status(UINT64_MAX, ssl, h3ssl, ISLISTENER);144}145static void add_ids_connection(struct h3ssl *h3ssl, SSL *ssl)146{147add_id_status(UINT64_MAX, ssl, h3ssl, ISCONNECTION);148}149static SSL *get_ids_connection(struct h3ssl *h3ssl)150{151struct ssl_id *ssl_ids;152int i;153154ssl_ids = h3ssl->ssl_ids;155for (i = 0; i < MAXSSL_IDS; i++) {156if (ssl_ids[i].status & ISCONNECTION) {157printf("get_ids_connection\n");158return ssl_ids[i].s;159}160}161return NULL;162}163static void replace_ids_connection(struct h3ssl *h3ssl, SSL *oldstream, SSL *newstream)164{165struct ssl_id *ssl_ids;166int i;167168ssl_ids = h3ssl->ssl_ids;169for (i = 0; i < MAXSSL_IDS; i++) {170if (ssl_ids[i].status & ISCONNECTION && ssl_ids[i].s == oldstream) {171printf("replace_ids_connection\n");172ssl_ids[i].s = newstream;173}174}175}176177/* remove the ids marked for removal */178static void remove_marked_ids(struct h3ssl *h3ssl)179{180struct ssl_id *ssl_ids;181int i;182183ssl_ids = h3ssl->ssl_ids;184for (i = 0; i < MAXSSL_IDS; i++) {185if (ssl_ids[i].status & TOBEREMOVED) {186printf("remove_id %llu\n", (unsigned long long)ssl_ids[i].id);187SSL_free(ssl_ids[i].s);188ssl_ids[i].s = NULL;189ssl_ids[i].id = UINT64_MAX;190ssl_ids[i].status = 0;191return;192}193}194}195196/* add the status bytes to the status */197static void set_id_status(uint64_t id, int status, struct h3ssl *h3ssl)198{199struct ssl_id *ssl_ids;200int i;201202ssl_ids = h3ssl->ssl_ids;203for (i = 0; i < MAXSSL_IDS; i++) {204if (ssl_ids[i].id == id) {205printf("set_id_status: %llu to %d\n", (unsigned long long)ssl_ids[i].id, status);206ssl_ids[i].status = ssl_ids[i].status | status;207return;208}209}210printf("Oops can't set status, can't find stream!!!\n");211assert(0);212}213static int get_id_status(uint64_t id, struct h3ssl *h3ssl)214{215struct ssl_id *ssl_ids;216int i;217218ssl_ids = h3ssl->ssl_ids;219for (i = 0; i < MAXSSL_IDS; i++) {220if (ssl_ids[i].id == id) {221printf("get_id_status: %llu to %d\n",222(unsigned long long)ssl_ids[i].id, ssl_ids[i].status);223return ssl_ids[i].status;224}225}226printf("Oops can't get status, can't find stream!!!\n");227assert(0);228return -1;229}230231/* check that all streams opened by the client are closed */232static int are_all_clientid_closed(struct h3ssl *h3ssl)233{234struct ssl_id *ssl_ids;235int i;236237ssl_ids = h3ssl->ssl_ids;238for (i = 0; i < MAXSSL_IDS; i++) {239if (ssl_ids[i].id == UINT64_MAX)240continue;241printf("are_all_clientid_closed: %llu status %d : %d\n",242(unsigned long long)ssl_ids[i].id, ssl_ids[i].status, CLIENTUNIOPEN | CLIENTCLOSED);243if (ssl_ids[i].status & CLIENTUNIOPEN) {244if (ssl_ids[i].status & CLIENTCLOSED) {245printf("are_all_clientid_closed: %llu closed\n",246(unsigned long long)ssl_ids[i].id);247SSL_free(ssl_ids[i].s);248ssl_ids[i].s = NULL;249ssl_ids[i].id = UINT64_MAX;250continue;251}252printf("are_all_clientid_closed: %llu open\n", (unsigned long long)ssl_ids[i].id);253return 0;254}255}256return 1;257}258259/* free all the ids except listener and connection */260static void close_all_ids(struct h3ssl *h3ssl)261{262struct ssl_id *ssl_ids;263int i;264265ssl_ids = h3ssl->ssl_ids;266for (i = 0; i < MAXSSL_IDS; i++) {267if (ssl_ids[i].id == UINT64_MAX)268continue;269SSL_free(ssl_ids[i].s);270ssl_ids[i].s = NULL;271ssl_ids[i].id = UINT64_MAX;272}273}274275static int on_recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token,276nghttp3_rcbuf *name, nghttp3_rcbuf *value,277uint8_t flags, void *user_data,278void *stream_user_data)279{280nghttp3_vec vname, vvalue;281struct h3ssl *h3ssl = (struct h3ssl *)user_data;282283/* Received a single HTTP header. */284vname = nghttp3_rcbuf_get_buf(name);285vvalue = nghttp3_rcbuf_get_buf(value);286287fwrite(vname.base, vname.len, 1, stdout);288fprintf(stdout, ": ");289fwrite(vvalue.base, vvalue.len, 1, stdout);290fprintf(stdout, "\n");291292if (token == NGHTTP3_QPACK_TOKEN__PATH) {293int len = (((vvalue.len) < (MAXURL)) ? (vvalue.len) : (MAXURL));294295memset(h3ssl->url, 0, sizeof(h3ssl->url));296if (vvalue.base[0] == '/') {297if (vvalue.base[1] == '\0') {298strncpy(h3ssl->url, "index.html", MAXURL);299} else {300memcpy(h3ssl->url, vvalue.base + 1, len - 1);301h3ssl->url[len - 1] = '\0';302}303} else {304memcpy(h3ssl->url, vvalue.base, len);305}306}307308return 0;309}310311static int on_end_headers(nghttp3_conn *conn, int64_t stream_id, int fin,312void *user_data, void *stream_user_data)313{314struct h3ssl *h3ssl = (struct h3ssl *)user_data;315316fprintf(stderr, "on_end_headers!\n");317h3ssl->end_headers_received = 1;318return 0;319}320321static int on_recv_data(nghttp3_conn *conn, int64_t stream_id,322const uint8_t *data, size_t datalen,323void *conn_user_data, void *stream_user_data)324{325fprintf(stderr, "on_recv_data! %ld\n", (unsigned long)datalen);326fprintf(stderr, "on_recv_data! %.*s\n", (int)datalen, data);327return 0;328}329330static int on_end_stream(nghttp3_conn *h3conn, int64_t stream_id,331void *conn_user_data, void *stream_user_data)332{333struct h3ssl *h3ssl = (struct h3ssl *)conn_user_data;334335printf("on_end_stream!\n");336h3ssl->done = 1;337return 0;338}339340/* Read from the stream and push to the h3conn */341static int quic_server_read(nghttp3_conn *h3conn, SSL *stream, uint64_t id, struct h3ssl *h3ssl)342{343int ret, r;344uint8_t msg2[16000];345size_t l = sizeof(msg2);346347if (!SSL_has_pending(stream))348return 0; /* Nothing to read */349350ret = SSL_read(stream, msg2, l);351if (ret <= 0) {352fprintf(stderr, "SSL_read %d on %llu failed\n",353SSL_get_error(stream, ret),354(unsigned long long)id);355switch (SSL_get_error(stream, ret)) {356case SSL_ERROR_WANT_READ:357return 0;358case SSL_ERROR_ZERO_RETURN:359return 1;360default:361ERR_print_errors_fp(stderr);362return -1;363}364return -1;365}366367/* XXX: work around nghttp3_conn_read_stream returning -607 on stream 2 */368if (!h3ssl->received_from_two && id != 2) {369r = nghttp3_conn_read_stream(h3conn, id, msg2, ret, 0);370} else {371r = ret; /* ignore it for the moment ... */372}373374printf("nghttp3_conn_read_stream used %d of %d on %llu\n", r,375ret, (unsigned long long)id);376if (r != ret) {377/* chrome returns -607 on stream 2 */378if (!nghttp3_err_is_fatal(r)) {379printf("nghttp3_conn_read_stream used %d of %d (not fatal) on %llu\n", r,380ret, (unsigned long long)id);381if (id == 2)382h3ssl->received_from_two = 1;383return 1;384}385return -1;386}387return 1;388}389390/*391* creates the control stream, the encoding and decoding streams.392* nghttp3_conn_bind_control_stream() is for the control stream.393*/394static int quic_server_h3streams(nghttp3_conn *h3conn, struct h3ssl *h3ssl)395{396SSL *rstream = NULL;397SSL *pstream = NULL;398SSL *cstream = NULL;399SSL *conn;400uint64_t r_streamid, p_streamid, c_streamid;401402conn = get_ids_connection(h3ssl);403if (conn == NULL) {404fprintf(stderr, "quic_server_h3streams no connection\n");405fflush(stderr);406return -1;407}408rstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI);409if (rstream != NULL) {410printf("=> Opened on %llu\n",411(unsigned long long)SSL_get_stream_id(rstream));412} else {413fprintf(stderr, "=> Stream == NULL!\n");414goto err;415}416pstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI);417if (pstream != NULL) {418printf("=> Opened on %llu\n",419(unsigned long long)SSL_get_stream_id(pstream));420} else {421fprintf(stderr, "=> Stream == NULL!\n");422goto err;423}424cstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI);425if (cstream != NULL) {426fprintf(stderr, "=> Opened on %llu\n",427(unsigned long long)SSL_get_stream_id(cstream));428fflush(stderr);429} else {430fprintf(stderr, "=> Stream == NULL!\n");431goto err;432}433r_streamid = SSL_get_stream_id(rstream);434p_streamid = SSL_get_stream_id(pstream);435c_streamid = SSL_get_stream_id(cstream);436if (nghttp3_conn_bind_qpack_streams(h3conn, p_streamid, r_streamid)) {437fprintf(stderr, "nghttp3_conn_bind_qpack_streams failed!\n");438goto err;439}440if (nghttp3_conn_bind_control_stream(h3conn, c_streamid)) {441fprintf(stderr, "nghttp3_conn_bind_qpack_streams failed!\n");442goto err;443}444printf("control: %llu enc %llu dec %llu\n",445(unsigned long long)c_streamid,446(unsigned long long)p_streamid,447(unsigned long long)r_streamid);448add_id(SSL_get_stream_id(rstream), rstream, h3ssl);449add_id(SSL_get_stream_id(pstream), pstream, h3ssl);450add_id(SSL_get_stream_id(cstream), cstream, h3ssl);451452return 0;453err:454fflush(stderr);455SSL_free(rstream);456SSL_free(pstream);457SSL_free(cstream);458return -1;459}460461/* Try to read from the streams we have */462static int read_from_ssl_ids(nghttp3_conn **curh3conn, struct h3ssl *h3ssl)463{464int hassomething = 0, i;465struct ssl_id *ssl_ids = h3ssl->ssl_ids;466SSL_POLL_ITEM items[MAXSSL_IDS] = { 0 }, *item = items;467static const struct timeval nz_timeout = { 0, 0 };468size_t result_count = SIZE_MAX;469int numitem = 0, ret;470uint64_t processed_event = 0;471int has_ids_to_remove = 0;472nghttp3_conn *h3conn = *curh3conn;473474/*475* Process all the streams476* the first one is the connection if we get something here is a new stream477*/478for (i = 0; i < MAXSSL_IDS; i++) {479if (ssl_ids[i].s != NULL) {480item->desc = SSL_as_poll_descriptor(ssl_ids[i].s);481item->events = UINT64_MAX; /* TODO adjust to the event we need process */482item->revents = UINT64_MAX; /* TODO adjust to the event we need process */483numitem++;484item++;485}486}487488/*489* SSL_POLL_FLAG_NO_HANDLE_EVENTS would require to use:490* SSL_get_event_timeout on the connection stream491* select/wait using the timeout value (which could be no wait time)492* SSL_handle_events493* SSL_poll494* for the moment we let SSL_poll to performs ticking internally495* on an automatic basis.496*/497ret = SSL_poll(items, numitem, sizeof(SSL_POLL_ITEM), &nz_timeout,498SSL_POLL_FLAG_NO_HANDLE_EVENTS, &result_count);499if (!ret) {500fprintf(stderr, "SSL_poll failed\n");501printf("SSL_poll failed\n");502return -1; /* something is wrong */503}504printf("read_from_ssl_ids %ld events\n", (unsigned long)result_count);505if (result_count == 0) {506/* Timeout may be something somewhere */507return 0;508}509510/* reset the states */511h3ssl->new_conn = 0;512h3ssl->restart = 0;513h3ssl->done = 0;514515/* Process all the item we have polled */516for (i = 0, item = items; i < numitem; i++, item++) {517SSL *s;518519if (item->revents == SSL_POLL_EVENT_NONE)520continue;521processed_event = 0;522/* get the stream */523s = item->desc.value.ssl;524525/* New connection */526if (item->revents & SSL_POLL_EVENT_IC) {527SSL *conn = SSL_accept_connection(item->desc.value.ssl, 0);528SSL *oldconn;529530printf("SSL_accept_connection\n");531if (conn == NULL) {532fprintf(stderr, "error while accepting connection\n");533ret = -1;534goto err;535}536537/* the previous might be still there */538oldconn = get_ids_connection(h3ssl);539if (oldconn != NULL) {540/* XXX we support only one connection for the moment */541printf("SSL_accept_connection closing previous\n");542SSL_free(oldconn);543replace_ids_connection(h3ssl, oldconn, conn);544reuse_h3ssl(h3ssl);545close_all_ids(h3ssl);546h3ssl->id_bidi = UINT64_MAX;547h3ssl->has_uni = 0;548} else {549printf("SSL_accept_connection first connection\n");550add_ids_connection(h3ssl, conn);551}552h3ssl->new_conn = 1;553/* create the new h3conn */554nghttp3_conn_del(*curh3conn);555nghttp3_settings_default(&settings);556if (nghttp3_conn_server_new(curh3conn, &callbacks, &settings, mem,557h3ssl)) {558fprintf(stderr, "nghttp3_conn_client_new failed!\n");559exit(1);560}561h3conn = *curh3conn;562hassomething++;563564if (!SSL_set_incoming_stream_policy(conn,565SSL_INCOMING_STREAM_POLICY_ACCEPT, 0)) {566fprintf(stderr, "error while setting inccoming stream policy\n");567ret = -1;568goto err;569}570571printf("SSL_accept_connection\n");572processed_event = processed_event | SSL_POLL_EVENT_IC;573}574/* SSL_accept_stream if SSL_POLL_EVENT_ISB or SSL_POLL_EVENT_ISU */575if ((item->revents & SSL_POLL_EVENT_ISB) || (item->revents & SSL_POLL_EVENT_ISU)) {576SSL *stream = SSL_accept_stream(item->desc.value.ssl, 0);577uint64_t new_id;578int r;579580if (stream == NULL) {581ret = -1;582goto err;583}584new_id = SSL_get_stream_id(stream);585printf("=> Received connection on %lld %d\n", (unsigned long long)new_id,586SSL_get_stream_type(stream));587add_id(new_id, stream, h3ssl);588if (h3ssl->close_wait) {589printf("in close_wait so we will have a new request\n");590reuse_h3ssl(h3ssl);591h3ssl->restart = 1; /* Checked in wait_close loop */592}593if (SSL_get_stream_type(stream) == SSL_STREAM_TYPE_BIDI) {594/* bidi that is the id where we have to send the response */595if (h3ssl->id_bidi != UINT64_MAX) {596set_id_status(h3ssl->id_bidi, TOBEREMOVED, h3ssl);597has_ids_to_remove++;598}599h3ssl->id_bidi = new_id;600reuse_h3ssl(h3ssl);601h3ssl->restart = 1;602} else {603set_id_status(new_id, CLIENTUNIOPEN, h3ssl);604}605606r = quic_server_read(h3conn, stream, new_id, h3ssl);607if (r == -1) {608ret = -1;609goto err;610}611if (r == 1)612hassomething++;613614if (item->revents & SSL_POLL_EVENT_ISB)615processed_event = processed_event | SSL_POLL_EVENT_ISB;616if (item->revents & SSL_POLL_EVENT_ISU)617processed_event = processed_event | SSL_POLL_EVENT_ISU;618}619if (item->revents & SSL_POLL_EVENT_OSB) {620/* Create new streams when allowed */621/* at least one bidi */622processed_event = processed_event | SSL_POLL_EVENT_OSB;623printf("Create bidi?\n");624}625if (item->revents & SSL_POLL_EVENT_OSU) {626/* at least one uni */627/* we have 4 streams from the client 2, 6 , 10 and 0 */628/* need 3 streams to the client */629printf("Create uni?\n");630processed_event = processed_event | SSL_POLL_EVENT_OSU;631if (!h3ssl->has_uni) {632printf("Create uni\n");633ret = quic_server_h3streams(h3conn, h3ssl);634if (ret == -1) {635fprintf(stderr, "quic_server_h3streams failed!\n");636goto err;637}638h3ssl->has_uni = 1;639hassomething++;640}641}642if (item->revents & SSL_POLL_EVENT_EC) {643/* the connection begins terminating */644printf("Connection terminating\n");645printf("Connection terminating restart %d\n", h3ssl->restart);646if (!h3ssl->close_done) {647h3ssl->close_done = 1;648} else {649h3ssl->done = 1;650}651hassomething++;652processed_event = processed_event | SSL_POLL_EVENT_EC;653}654if (item->revents & SSL_POLL_EVENT_ECD) {655/* the connection is terminated */656printf("Connection terminated\n");657h3ssl->done = 1;658hassomething++;659processed_event = processed_event | SSL_POLL_EVENT_ECD;660}661662if (item->revents & SSL_POLL_EVENT_R) {663/* try to read */664uint64_t id = UINT64_MAX;665int r;666667/* get the id, well the connection has no id... */668id = SSL_get_stream_id(item->desc.value.ssl);669printf("revent READ on %llu\n", (unsigned long long)id);670r = quic_server_read(h3conn, s, id, h3ssl);671if (r == 0) {672uint8_t msg[1];673size_t l = sizeof(msg);674675/* check that the other side is closed */676r = SSL_read(s, msg, l);677printf("SSL_read tells %d\n", r);678if (r > 0) {679ret = -1;680goto err;681}682r = SSL_get_error(s, r);683if (r != SSL_ERROR_ZERO_RETURN) {684ret = -1;685goto err;686}687set_id_status(id, TOBEREMOVED, h3ssl);688has_ids_to_remove++;689continue;690}691if (r == -1) {692ret = -1;693goto err;694}695hassomething++;696processed_event = processed_event | SSL_POLL_EVENT_R;697}698if (item->revents & SSL_POLL_EVENT_ER) {699/* mark it closed */700uint64_t id = UINT64_MAX;701int status;702703id = SSL_get_stream_id(item->desc.value.ssl);704status = get_id_status(id, h3ssl);705706printf("revent exception READ on %llu\n", (unsigned long long)id);707if (status & CLIENTUNIOPEN) {708set_id_status(id, CLIENTCLOSED, h3ssl);709hassomething++;710}711processed_event = processed_event | SSL_POLL_EVENT_ER;712}713if (item->revents & SSL_POLL_EVENT_W) {714/* we ignore those for the moment */715processed_event = processed_event | SSL_POLL_EVENT_W;716}717if (item->revents & SSL_POLL_EVENT_EW) {718/* write part received a STOP_SENDING */719uint64_t id = UINT64_MAX;720int status;721722id = SSL_get_stream_id(item->desc.value.ssl);723status = get_id_status(id, h3ssl);724725if (status & SERVERCLOSED) {726printf("both sides closed on %llu\n", (unsigned long long)id);727set_id_status(id, TOBEREMOVED, h3ssl);728has_ids_to_remove++;729hassomething++;730}731processed_event = processed_event | SSL_POLL_EVENT_EW;732}733if (item->revents != processed_event) {734/* Figure out ??? */735uint64_t id = UINT64_MAX;736737id = SSL_get_stream_id(item->desc.value.ssl);738printf("revent %llu (%d) on %llu NOT PROCESSED!\n",739(unsigned long long)item->revents, SSL_POLL_EVENT_W,740(unsigned long long)id);741}742}743ret = hassomething;744err:745if (has_ids_to_remove)746remove_marked_ids(h3ssl);747return ret;748}749750static void handle_events_from_ids(struct h3ssl *h3ssl)751{752struct ssl_id *ssl_ids = h3ssl->ssl_ids;753int i;754755ssl_ids = h3ssl->ssl_ids;756for (i = 0; i < MAXSSL_IDS; i++) {757if (ssl_ids[i].s != NULL && (ssl_ids[i].status & ISCONNECTION || ssl_ids[i].status & ISLISTENER)) {758if (SSL_handle_events(ssl_ids[i].s))759ERR_print_errors_fp(stderr);760}761}762}763764static size_t get_file_length(struct h3ssl *h3ssl)765{766char filename[PATH_MAX];767struct stat st;768769memset(filename, 0, PATH_MAX);770if (h3ssl->fileprefix != NULL)771strcat(filename, h3ssl->fileprefix);772strcat(filename, h3ssl->url);773774if (strcmp(h3ssl->url, "big") == 0) {775printf("big!!!\n");776return (size_t)INT_MAX;777}778if (stat(filename, &st) == 0) {779/* Only process regular files */780if (S_ISREG(st.st_mode)) {781printf("get_file_length %s %lld\n", filename, (unsigned long long)st.st_size);782return (size_t)st.st_size;783}784}785printf("Can't get_file_length %s\n", filename);786return 0;787}788789static char *get_file_data(struct h3ssl *h3ssl)790{791char filename[PATH_MAX];792size_t size = get_file_length(h3ssl);793char *res;794int fd;795796if (size == 0)797return NULL;798799memset(filename, 0, PATH_MAX);800if (h3ssl->fileprefix != NULL)801strcat(filename, h3ssl->fileprefix);802strcat(filename, h3ssl->url);803804res = malloc(size + 1);805res[size] = '\0';806fd = open(filename, O_RDONLY);807if (read(fd, res, size) == -1) {808close(fd);809free(res);810return NULL;811}812close(fd);813printf("read from %s : %zu\n", filename, size);814return res;815}816817static nghttp3_ssize step_read_data(nghttp3_conn *conn, int64_t stream_id,818nghttp3_vec *vec, size_t veccnt,819uint32_t *pflags, void *user_data,820void *stream_user_data)821{822struct h3ssl *h3ssl = (struct h3ssl *)user_data;823824if (h3ssl->datadone) {825*pflags = NGHTTP3_DATA_FLAG_EOF;826return 0;827}828/* send the data */829printf("step_read_data for %s %zu\n", h3ssl->url, h3ssl->ldata);830if (h3ssl->ldata <= 4096) {831vec[0].base = &(h3ssl->ptr_data[h3ssl->offset_data]);832vec[0].len = h3ssl->ldata;833h3ssl->datadone++;834*pflags = NGHTTP3_DATA_FLAG_EOF;835} else {836vec[0].base = &(h3ssl->ptr_data[h3ssl->offset_data]);837vec[0].len = 4096;838if (h3ssl->ldata == INT_MAX) {839printf("big = endless!\n");840} else {841h3ssl->offset_data = h3ssl->offset_data + 4096;842h3ssl->ldata = h3ssl->ldata - 4096;843}844}845846return 1;847}848849static int quic_server_write(struct h3ssl *h3ssl, uint64_t streamid,850uint8_t *buff, size_t len, uint64_t flags,851size_t *written)852{853struct ssl_id *ssl_ids;854int i;855856ssl_ids = h3ssl->ssl_ids;857for (i = 0; i < MAXSSL_IDS; i++) {858if (ssl_ids[i].id == streamid) {859if (!SSL_write_ex2(ssl_ids[i].s, buff, len, flags, written) || *written != len) {860fprintf(stderr, "couldn't write on connection\n");861ERR_print_errors_fp(stderr);862return 0;863}864printf("written %lld on %lld flags %lld\n", (unsigned long long)len,865(unsigned long long)streamid, (unsigned long long)flags);866return 1;867}868}869printf("quic_server_write %lld on %lld (NOT FOUND!)\n", (unsigned long long)len,870(unsigned long long)streamid);871return 0;872}873874#define OSSL_NELEM(x) (sizeof(x) / sizeof((x)[0]))875876/*877* This is a basic demo of QUIC server functionality in which one connection at878* a time is accepted in a blocking loop.879*/880881/* ALPN string for TLS handshake. We pretent h3-29 and h3 */882static const unsigned char alpn_ossltest[] = { 5, 'h', '3', '-', '2',883'9', 2, 'h', '3' };884885/*886* This callback validates and negotiates the desired ALPN on the server side.887*/888static int select_alpn(SSL *ssl, const unsigned char **out,889unsigned char *out_len, const unsigned char *in,890unsigned int in_len, void *arg)891{892if (SSL_select_next_proto((unsigned char **)out, out_len, alpn_ossltest,893sizeof(alpn_ossltest), in,894in_len)895!= OPENSSL_NPN_NEGOTIATED)896return SSL_TLSEXT_ERR_ALERT_FATAL;897898return SSL_TLSEXT_ERR_OK;899}900901/* Create SSL_CTX. */902static SSL_CTX *create_ctx(const char *cert_path, const char *key_path)903{904SSL_CTX *ctx;905906ctx = SSL_CTX_new(OSSL_QUIC_server_method());907if (ctx == NULL)908goto err;909910/* Load certificate and corresponding private key. */911if (SSL_CTX_use_certificate_chain_file(ctx, cert_path) <= 0) {912fprintf(stderr, "couldn't load certificate file: %s\n", cert_path);913goto err;914}915916if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0) {917fprintf(stderr, "couldn't load key file: %s\n", key_path);918goto err;919}920921if (!SSL_CTX_check_private_key(ctx)) {922fprintf(stderr, "private key check failed\n");923goto err;924}925926/* Setup ALPN negotiation callback. */927SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL);928return ctx;929930err:931SSL_CTX_free(ctx);932return NULL;933}934935/* Create UDP socket using given port. */936static int create_socket(uint16_t port)937{938int fd = -1;939struct sockaddr_in sa = { 0 };940941if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {942fprintf(stderr, "cannot create socket");943goto err;944}945946sa.sin_family = AF_INET;947sa.sin_port = htons(port);948949if (bind(fd, (const struct sockaddr *)&sa, sizeof(sa)) < 0) {950fprintf(stderr, "cannot bind to %u\n", port);951goto err;952}953954return fd;955956err:957if (fd >= 0)958BIO_closesocket(fd);959960return -1;961}962963/* Copied from demos/guide/quic-server-non-block.c */964/**965* @brief Waits for activity on the SSL socket, either for reading or writing.966*967* This function monitors the underlying file descriptor of the given SSL968* connection to determine when it is ready for reading or writing, or both.969* It uses the select function to wait until the socket is either readable970* or writable, depending on what the SSL connection requires.971*972* @param ssl A pointer to the SSL object representing the connection.973*974* @note This function blocks until there is activity on the socket. In a real975* application, you might want to perform other tasks while waiting, such as976* updating a GUI or handling other connections.977*978* @note This function uses select for simplicity and portability. Depending979* on your application's requirements, you might consider using other980* mechanisms like poll or epoll for handling multiple file descriptors.981*/982static int wait_for_activity(SSL *ssl)983{984int sock, isinfinite;985fd_set read_fd, write_fd;986struct timeval tv;987struct timeval *tvp = NULL;988989/* Get hold of the underlying file descriptor for the socket */990if ((sock = SSL_get_fd(ssl)) == -1) {991fprintf(stderr, "Unable to get file descriptor");992return -1;993}994995/* Initialize the fd_set structure */996FD_ZERO(&read_fd);997FD_ZERO(&write_fd);998999/*1000* Determine if we would like to write to the socket, read from it, or both.1001*/1002if (SSL_net_write_desired(ssl))1003FD_SET(sock, &write_fd);1004if (SSL_net_read_desired(ssl))1005FD_SET(sock, &read_fd);10061007/* Add the socket file descriptor to the fd_set */1008FD_SET(sock, &read_fd);10091010/*1011* Find out when OpenSSL would next like to be called, regardless of1012* whether the state of the underlying socket has changed or not.1013*/1014if (SSL_get_event_timeout(ssl, &tv, &isinfinite) && !isinfinite)1015tvp = &tv;10161017/*1018* Wait until the socket is writeable or readable. We use select here1019* for the sake of simplicity and portability, but you could equally use1020* poll/epoll or similar functions1021*1022* NOTE: For the purposes of this demonstration code this effectively1023* makes this demo block until it has something more useful to do. In a1024* real application you probably want to go and do other work here (e.g.1025* update a GUI, or service other connections).1026*1027* Let's say for example that you want to update the progress counter on1028* a GUI every 100ms. One way to do that would be to use the timeout in1029* the last parameter to "select" below. If the tvp value is greater1030* than 100ms then use 100ms instead. Then, when select returns, you1031* check if it did so because of activity on the file descriptors or1032* because of the timeout. If the 100ms GUI timeout has expired but the1033* tvp timeout has not then go and update the GUI and then restart the1034* "select" (with updated timeouts).1035*/10361037return (select(sock + 1, &read_fd, &write_fd, NULL, tvp));1038}10391040/* Main loop for server to accept QUIC connections. */1041static int run_quic_server(SSL_CTX *ctx, int fd)1042{1043int ok = 0;1044int hassomething = 0;1045SSL *listener = NULL;1046nghttp3_conn *h3conn = NULL;1047struct h3ssl h3ssl;1048SSL *ssl;1049char *fileprefix = getenv("FILEPREFIX");10501051/* Create a new QUIC listener. */1052if ((listener = SSL_new_listener(ctx, 0)) == NULL)1053goto err;10541055/* Provide the listener with our UDP socket. */1056if (!SSL_set_fd(listener, fd))1057goto err;10581059/* Begin listening. */1060if (!SSL_listen(listener))1061goto err;10621063/*1064* Listeners, and other QUIC objects, default to operating in blocking mode.1065* The configured behaviour is inherited by child objects.1066* Make sure we won't block as we use select().1067*/1068if (!SSL_set_blocking_mode(listener, 0))1069goto err;10701071/* Setup callbacks. */1072callbacks.recv_header = on_recv_header;1073callbacks.end_headers = on_end_headers;1074callbacks.recv_data = on_recv_data;1075callbacks.end_stream = on_end_stream;10761077/* mem default */1078mem = nghttp3_mem_default();10791080for (;;) {1081nghttp3_nv resp[10];1082size_t num_nv;1083nghttp3_data_reader dr;1084int ret;1085int numtimeout;1086char slength[22];1087int hasnothing;10881089init_ids(&h3ssl);1090h3ssl.fileprefix = fileprefix;1091printf("listener: %p\n", (void *)listener);1092add_ids_listener(listener, &h3ssl);10931094if (!hassomething) {1095printf("waiting on socket\n");1096fflush(stdout);1097ret = wait_for_activity(listener);1098if (ret == -1) {1099fprintf(stderr, "wait_for_activity failed!\n");1100goto err;1101}1102}1103/*1104* Service the connection. In a real application this would be done1105* concurrently. In this demonstration program a single connection is1106* accepted and serviced at a time.1107*/1108newconn:11091110printf("process_server starting...\n");1111fflush(stdout);11121113/* wait until we have received the headers */1114restart:1115numtimeout = 0;1116num_nv = 0;1117while (!h3ssl.end_headers_received) {1118if (!hassomething) {1119if (wait_for_activity(listener) == 0) {1120printf("waiting for end_headers_received timeout %d\n", numtimeout);1121numtimeout++;1122if (numtimeout == 25)1123goto err;1124}1125handle_events_from_ids(&h3ssl);1126}1127hassomething = read_from_ssl_ids(&h3conn, &h3ssl);1128if (hassomething == -1) {1129fprintf(stderr, "read_from_ssl_ids hassomething failed\n");1130goto err;1131} else if (hassomething == 0) {1132printf("read_from_ssl_ids hassomething nothing...\n");1133} else {1134numtimeout = 0;1135printf("read_from_ssl_ids hassomething %d...\n", hassomething);1136if (h3ssl.close_done) {1137/* Other side has closed */1138break;1139}1140h3ssl.restart = 0;1141}1142}1143if (h3ssl.close_done) {1144printf("Other side close without request\n");1145goto wait_close;1146}1147printf("end_headers_received!!!\n");1148if (!h3ssl.has_uni) {1149/* time to create those otherwise we can't push anything to the client */1150printf("Create uni\n");1151if (quic_server_h3streams(h3conn, &h3ssl) == -1) {1152fprintf(stderr, "quic_server_h3streams failed!\n");1153goto err;1154}1155h3ssl.has_uni = 1;1156}11571158/* we have receive the request build the response and send it */1159/* XXX add MAKE_NV("connection", "close"), to resp[] and recheck */1160make_nv(&resp[num_nv++], ":status", "200");1161h3ssl.ldata = get_file_length(&h3ssl);1162if (h3ssl.ldata == 0) {1163/* We don't find the file: use default test string */1164h3ssl.ptr_data = nulldata;1165h3ssl.ldata = nulldata_sz;1166sprintf(slength, "%zu", h3ssl.ldata);1167/* content-type: text/html */1168make_nv(&resp[num_nv++], "content-type", "text/html");1169} else if (h3ssl.ldata == INT_MAX) {1170/* endless file for tests */1171sprintf(slength, "%zu", h3ssl.ldata);1172h3ssl.ptr_data = (uint8_t *)malloc(4096);1173memset(h3ssl.ptr_data, 'A', 4096);1174} else {1175/* normal file we have opened */1176sprintf(slength, "%zu", h3ssl.ldata);1177h3ssl.ptr_data = (uint8_t *)get_file_data(&h3ssl);1178if (h3ssl.ptr_data == NULL)1179abort();1180printf("before nghttp3_conn_submit_response on %llu for %s ...\n",1181(unsigned long long)h3ssl.id_bidi, h3ssl.url);1182if (strstr(h3ssl.url, ".png"))1183make_nv(&resp[num_nv++], "content-type", "image/png");1184else if (strstr(h3ssl.url, ".ico"))1185make_nv(&resp[num_nv++], "content-type", "image/vnd.microsoft.icon");1186else if (strstr(h3ssl.url, ".htm"))1187make_nv(&resp[num_nv++], "content-type", "text/html");1188else1189make_nv(&resp[num_nv++], "content-type", "application/octet-stream");1190make_nv(&resp[num_nv++], "content-length", slength);1191}11921193dr.read_data = step_read_data;1194if (nghttp3_conn_submit_response(h3conn, h3ssl.id_bidi, resp, num_nv, &dr)) {1195fprintf(stderr, "nghttp3_conn_submit_response failed!\n");1196goto err;1197}1198printf("nghttp3_conn_submit_response on %llu...\n", (unsigned long long)h3ssl.id_bidi);1199for (;;) {1200nghttp3_vec vec[256];1201nghttp3_ssize sveccnt;1202int fin, i;1203int64_t streamid;12041205sveccnt = nghttp3_conn_writev_stream(h3conn, &streamid, &fin, vec,1206nghttp3_arraylen(vec));1207if (sveccnt <= 0) {1208printf("nghttp3_conn_writev_stream done: %ld stream: %llu fin %d\n",1209(long int)sveccnt,1210(unsigned long long)streamid,1211fin);1212if (streamid != -1 && fin) {1213printf("Sending end data on %llu fin %d\n",1214(unsigned long long)streamid, fin);1215nghttp3_conn_add_write_offset(h3conn, streamid, 0);1216continue;1217}1218if (!h3ssl.datadone)1219goto err;1220else1221break; /* Done */1222}1223printf("nghttp3_conn_writev_stream: %ld fin: %d\n", (long int)sveccnt, fin);1224for (i = 0; i < sveccnt; i++) {1225size_t numbytes = vec[i].len;1226int flagwrite = 0;12271228printf("quic_server_write on %llu for %ld\n",1229(unsigned long long)streamid, (unsigned long)vec[i].len);1230if (fin && i == sveccnt - 1)1231flagwrite = SSL_WRITE_FLAG_CONCLUDE;1232if (!quic_server_write(&h3ssl, streamid, vec[i].base,1233vec[i].len, flagwrite, &numbytes)) {1234fprintf(stderr, "quic_server_write failed!\n");1235goto err;1236}1237}1238if (nghttp3_conn_add_write_offset(1239h3conn, streamid,1240(size_t)nghttp3_vec_len(vec, (size_t)sveccnt))) {1241fprintf(stderr, "nghttp3_conn_add_write_offset failed!\n");1242goto err;1243}1244}1245printf("nghttp3_conn_submit_response DONE!!!\n");12461247if (h3ssl.datadone) {1248/*1249* All the data was sent.1250* close stream zero1251*/1252if (!h3ssl.close_done) {1253set_id_status(h3ssl.id_bidi, SERVERCLOSED, &h3ssl);1254h3ssl.close_wait = 1;1255}1256} else {1257printf("nghttp3_conn_submit_response still not finished\n");1258}12591260/* wait until closed */1261wait_close:1262hasnothing = 0;1263for (;;) {12641265if (!hasnothing) {1266SSL *newssl = get_ids_connection(&h3ssl);12671268printf("hasnothing nothing WAIT %d!!!\n", h3ssl.close_done);1269if (newssl == NULL)1270newssl = listener;1271ret = wait_for_activity(newssl);1272if (ret == -1)1273goto err;1274if (ret == 0)1275printf("hasnothing timeout\n");1276/* we have something or a timeout */1277handle_events_from_ids(&h3ssl);1278}1279hasnothing = read_from_ssl_ids(&h3conn, &h3ssl);1280if (hasnothing == -1) {1281printf("hasnothing failed\n");1282break;1283/* goto err; well in fact not */1284} else if (hasnothing == 0) {1285printf("hasnothing nothing\n");1286continue;1287} else {1288printf("hasnothing something\n");1289if (h3ssl.done) {1290printf("hasnothing something... DONE\n");1291/* we might already have the next connection to accept */1292hassomething = 1;1293break;1294}1295if (h3ssl.new_conn) {1296printf("hasnothing something... NEW CONN\n");1297h3ssl.new_conn = 0;1298goto newconn;1299}1300if (h3ssl.restart) {1301printf("hasnothing something... RESTART\n");1302h3ssl.restart = 0;1303goto restart;1304}1305if (are_all_clientid_closed(&h3ssl)) {1306printf("hasnothing something... DONE other side closed\n");1307/* there might 2 or 3 message we will ignore */1308hassomething = 0;1309break;1310}1311}1312}13131314/*1315* Free the streams, then loop again, accepting another connection.1316*/1317close_all_ids(&h3ssl);1318ssl = get_ids_connection(&h3ssl);1319if (ssl != NULL) {1320SSL_free(ssl);1321replace_ids_connection(&h3ssl, ssl, NULL);1322}1323hassomething = 0;1324}13251326ok = 1;1327err:1328if (!ok)1329ERR_print_errors_fp(stderr);13301331SSL_free(listener);1332return ok;1333}13341335/*1336* demo server... just return a 20 bytes ascii string as response for any1337* request single h3 connection and single threaded.1338*/1339int main(int argc, char **argv)1340{1341int rc = 1;1342SSL_CTX *ctx = NULL;1343int fd = -1;1344unsigned long port;13451346if (argc < 4) {1347fprintf(stderr, "usage: %s <port> <server.crt> <server.key>\n",1348argv[0]);1349goto err;1350}13511352/* Create SSL_CTX. */1353if ((ctx = create_ctx(argv[2], argv[3])) == NULL)1354goto err;13551356/* Parse port number from command line arguments. */1357port = strtoul(argv[1], NULL, 0);1358if (port == 0 || port > UINT16_MAX) {1359fprintf(stderr, "invalid port: %lu\n", port);1360goto err;1361}13621363/* Create UDP socket. */1364if ((fd = create_socket((uint16_t)port)) < 0)1365goto err;13661367/* Enter QUIC server connection acceptance loop. */1368if (!run_quic_server(ctx, fd))1369goto err;13701371rc = 0;1372err:1373if (rc != 0)1374ERR_print_errors_fp(stderr);13751376SSL_CTX_free(ctx);13771378if (fd != -1)1379BIO_closesocket(fd);13801381return rc;1382}138313841385