/* * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy * in the file LICENSE in the source distribution or at * https://www.openssl.org/source/license.html */ #include #include #include #include #include #include #include #include #include #include #ifndef PATH_MAX # define PATH_MAX 255 #endif #define nghttp3_arraylen(A) (sizeof(A) / sizeof(*(A))) /* The crappy test wants 20 bytes */ #define NULL_PAYLOAD "12345678901234567890" static uint8_t *nulldata = (uint8_t *) NULL_PAYLOAD; static size_t nulldata_sz = sizeof(NULL_PAYLOAD) - 1; /* The nghttp3 variable we need in the main part and read_from_ssl_ids */ static nghttp3_settings settings; static const nghttp3_mem *mem; static nghttp3_callbacks callbacks = {0}; /* 3 streams created by the server and 4 by the client (one is bidi) */ struct ssl_id { SSL *s; /* the stream openssl uses in SSL_read(), SSL_write etc */ uint64_t id; /* the stream identifier the nghttp3 uses */ int status; /* 0 or one the below status and origin */ }; /* status and origin of the streams the possible values are: */ #define CLIENTUNIOPEN 0x01 /* unidirectional open by the client (2, 6 and 10) */ #define CLIENTCLOSED 0x02 /* closed by the client */ #define CLIENTBIDIOPEN 0x04 /* bidirectional open by the client (something like 0, 4, 8 ...) */ #define SERVERUNIOPEN 0x08 /* unidirectional open by the server (3, 7 and 11) */ #define SERVERCLOSED 0x10 /* closed by the server (us) */ #define TOBEREMOVED 0x20 /* marked for removing in read_from_ssl_ids, */ /* it will be removed after processing all events */ #define ISLISTENER 0x40 /* the stream is a listener from SSL_new_listener() */ #define ISCONNECTION 0x80 /* the stream is a connection from SSL_accept_connection() */ #define MAXSSL_IDS 20 #define MAXURL 255 struct h3ssl { struct ssl_id ssl_ids[MAXSSL_IDS]; int end_headers_received; /* h3 header received call back called */ int datadone; /* h3 has given openssl all the data of the response */ int has_uni; /* we have the 3 uni directional stream needed */ int close_done; /* connection begins terminating EVENT_EC */ int close_wait; /* we are waiting for a close or a new request */ int done; /* connection terminated EVENT_ECD, after EVENT_EC */ int new_conn; /* a new connection has been received */ int received_from_two; /* workaround for -607 on nghttp3_conn_read_stream on stream 2 */ int restart; /* new request/response cycle started */ uint64_t id_bidi; /* the id of the stream used to read request and send response */ char *fileprefix; /* prefix of the directory to fetch files from */ char url[MAXURL]; /* url to serve the request */ uint8_t *ptr_data; /* pointer to the data to send */ size_t ldata; /* amount of bytes to send */ int offset_data; /* offset to next data to send */ }; static void make_nv(nghttp3_nv *nv, const char *name, const char *value) { nv->name = (uint8_t *)name; nv->value = (uint8_t *)value; nv->namelen = strlen(name); nv->valuelen = strlen(value); nv->flags = NGHTTP3_NV_FLAG_NONE; } static void init_ids(struct h3ssl *h3ssl) { struct ssl_id *ssl_ids; int i; char *prior_fileprefix = h3ssl->fileprefix; if (h3ssl->ptr_data != NULL && h3ssl->ptr_data != nulldata) free(h3ssl->ptr_data); memset(h3ssl, 0, sizeof(struct h3ssl)); ssl_ids = h3ssl->ssl_ids; for (i = 0; i < MAXSSL_IDS; i++) ssl_ids[i].id = UINT64_MAX; h3ssl->id_bidi = UINT64_MAX; /* restore the fileprefix */ h3ssl->fileprefix = prior_fileprefix; } static void reuse_h3ssl(struct h3ssl *h3ssl) { h3ssl->end_headers_received = 0; h3ssl->datadone = 0; h3ssl->close_done = 0; h3ssl->close_wait = 0; h3ssl->done = 0; memset(h3ssl->url, '\0', sizeof(h3ssl->url)); if (h3ssl->ptr_data != NULL && h3ssl->ptr_data != nulldata) free(h3ssl->ptr_data); h3ssl->ptr_data = NULL; h3ssl->offset_data = 0; h3ssl->ldata = 0; } static void add_id_status(uint64_t id, SSL *ssl, struct h3ssl *h3ssl, int status) { struct ssl_id *ssl_ids; int i; ssl_ids = h3ssl->ssl_ids; for (i = 0; i < MAXSSL_IDS; i++) { if (ssl_ids[i].s == NULL) { ssl_ids[i].s = ssl; ssl_ids[i].id = id; ssl_ids[i].status = status; return; } } printf("Oops too many streams to add!!!\n"); exit(1); } static void add_id(uint64_t id, SSL *ssl, struct h3ssl *h3ssl) { add_id_status(id, ssl, h3ssl, 0); } /* Add listener and connection */ static void add_ids_listener(SSL *ssl, struct h3ssl *h3ssl) { add_id_status(UINT64_MAX, ssl, h3ssl, ISLISTENER); } static void add_ids_connection(struct h3ssl *h3ssl, SSL *ssl) { add_id_status(UINT64_MAX, ssl, h3ssl, ISCONNECTION); } static SSL *get_ids_connection(struct h3ssl *h3ssl) { struct ssl_id *ssl_ids; int i; ssl_ids = h3ssl->ssl_ids; for (i = 0; i < MAXSSL_IDS; i++) { if (ssl_ids[i].status & ISCONNECTION) { printf("get_ids_connection\n"); return ssl_ids[i].s; } } return NULL; } static void replace_ids_connection(struct h3ssl *h3ssl, SSL *oldstream, SSL *newstream) { struct ssl_id *ssl_ids; int i; ssl_ids = h3ssl->ssl_ids; for (i = 0; i < MAXSSL_IDS; i++) { if (ssl_ids[i].status & ISCONNECTION && ssl_ids[i].s == oldstream) { printf("replace_ids_connection\n"); ssl_ids[i].s = newstream; } } } /* remove the ids marked for removal */ static void remove_marked_ids(struct h3ssl *h3ssl) { struct ssl_id *ssl_ids; int i; ssl_ids = h3ssl->ssl_ids; for (i = 0; i < MAXSSL_IDS; i++) { if (ssl_ids[i].status & TOBEREMOVED) { printf("remove_id %llu\n", (unsigned long long) ssl_ids[i].id); SSL_free(ssl_ids[i].s); ssl_ids[i].s = NULL; ssl_ids[i].id = UINT64_MAX; ssl_ids[i].status = 0; return; } } } /* add the status bytes to the status */ static void set_id_status(uint64_t id, int status, struct h3ssl *h3ssl) { struct ssl_id *ssl_ids; int i; ssl_ids = h3ssl->ssl_ids; for (i = 0; i < MAXSSL_IDS; i++) { if (ssl_ids[i].id == id) { printf("set_id_status: %llu to %d\n", (unsigned long long) ssl_ids[i].id, status); ssl_ids[i].status = ssl_ids[i].status | status; return; } } printf("Oops can't set status, can't find stream!!!\n"); assert(0); } static int get_id_status(uint64_t id, struct h3ssl *h3ssl) { struct ssl_id *ssl_ids; int i; ssl_ids = h3ssl->ssl_ids; for (i = 0; i < MAXSSL_IDS; i++) { if (ssl_ids[i].id == id) { printf("get_id_status: %llu to %d\n", (unsigned long long) ssl_ids[i].id, ssl_ids[i].status); return ssl_ids[i].status; } } printf("Oops can't get status, can't find stream!!!\n"); assert(0); return -1; } /* check that all streams opened by the client are closed */ static int are_all_clientid_closed(struct h3ssl *h3ssl) { struct ssl_id *ssl_ids; int i; ssl_ids = h3ssl->ssl_ids; for (i = 0; i < MAXSSL_IDS; i++) { if (ssl_ids[i].id == UINT64_MAX) continue; printf("are_all_clientid_closed: %llu status %d : %d\n", (unsigned long long) ssl_ids[i].id, ssl_ids[i].status, CLIENTUNIOPEN | CLIENTCLOSED); if (ssl_ids[i].status & CLIENTUNIOPEN) { if (ssl_ids[i].status & CLIENTCLOSED) { printf("are_all_clientid_closed: %llu closed\n", (unsigned long long) ssl_ids[i].id); SSL_free(ssl_ids[i].s); ssl_ids[i].s = NULL; ssl_ids[i].id = UINT64_MAX; continue; } printf("are_all_clientid_closed: %llu open\n", (unsigned long long) ssl_ids[i].id); return 0; } } return 1; } /* free all the ids except listener and connection */ static void close_all_ids(struct h3ssl *h3ssl) { struct ssl_id *ssl_ids; int i; ssl_ids = h3ssl->ssl_ids; for (i = 0; i < MAXSSL_IDS; i++) { if (ssl_ids[i].id == UINT64_MAX) continue; SSL_free(ssl_ids[i].s); ssl_ids[i].s = NULL; ssl_ids[i].id = UINT64_MAX; } } static int on_recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token, nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, void *user_data, void *stream_user_data) { nghttp3_vec vname, vvalue; struct h3ssl *h3ssl = (struct h3ssl *)user_data; /* Received a single HTTP header. */ vname = nghttp3_rcbuf_get_buf(name); vvalue = nghttp3_rcbuf_get_buf(value); fwrite(vname.base, vname.len, 1, stdout); fprintf(stdout, ": "); fwrite(vvalue.base, vvalue.len, 1, stdout); fprintf(stdout, "\n"); if (token == NGHTTP3_QPACK_TOKEN__PATH) { int len = (((vvalue.len) < (MAXURL)) ? (vvalue.len) : (MAXURL)); memset(h3ssl->url, 0, sizeof(h3ssl->url)); if (vvalue.base[0] == '/') { if (vvalue.base[1] == '\0') { strncpy(h3ssl->url, "index.html", MAXURL); } else { memcpy(h3ssl->url, vvalue.base + 1, len - 1); h3ssl->url[len - 1] = '\0'; } } else { memcpy(h3ssl->url, vvalue.base, len); } } return 0; } static int on_end_headers(nghttp3_conn *conn, int64_t stream_id, int fin, void *user_data, void *stream_user_data) { struct h3ssl *h3ssl = (struct h3ssl *)user_data; fprintf(stderr, "on_end_headers!\n"); h3ssl->end_headers_received = 1; return 0; } static int on_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, size_t datalen, void *conn_user_data, void *stream_user_data) { fprintf(stderr, "on_recv_data! %ld\n", (unsigned long)datalen); fprintf(stderr, "on_recv_data! %.*s\n", (int)datalen, data); return 0; } static int on_end_stream(nghttp3_conn *h3conn, int64_t stream_id, void *conn_user_data, void *stream_user_data) { struct h3ssl *h3ssl = (struct h3ssl *)conn_user_data; printf("on_end_stream!\n"); h3ssl->done = 1; return 0; } /* Read from the stream and push to the h3conn */ static int quic_server_read(nghttp3_conn *h3conn, SSL *stream, uint64_t id, struct h3ssl *h3ssl) { int ret, r; uint8_t msg2[16000]; size_t l = sizeof(msg2); if (!SSL_has_pending(stream)) return 0; /* Nothing to read */ ret = SSL_read(stream, msg2, l); if (ret <= 0) { fprintf(stderr, "SSL_read %d on %llu failed\n", SSL_get_error(stream, ret), (unsigned long long) id); switch (SSL_get_error(stream, ret)) { case SSL_ERROR_WANT_READ: return 0; case SSL_ERROR_ZERO_RETURN: return 1; default: ERR_print_errors_fp(stderr); return -1; } return -1; } /* XXX: work around nghttp3_conn_read_stream returning -607 on stream 2 */ if (!h3ssl->received_from_two && id != 2) { r = nghttp3_conn_read_stream(h3conn, id, msg2, ret, 0); } else { r = ret; /* ignore it for the moment ... */ } printf("nghttp3_conn_read_stream used %d of %d on %llu\n", r, ret, (unsigned long long) id); if (r != ret) { /* chrome returns -607 on stream 2 */ if (!nghttp3_err_is_fatal(r)) { printf("nghttp3_conn_read_stream used %d of %d (not fatal) on %llu\n", r, ret, (unsigned long long) id); if (id == 2) h3ssl->received_from_two = 1; return 1; } return -1; } return 1; } /* * creates the control stream, the encoding and decoding streams. * nghttp3_conn_bind_control_stream() is for the control stream. */ static int quic_server_h3streams(nghttp3_conn *h3conn, struct h3ssl *h3ssl) { SSL *rstream = NULL; SSL *pstream = NULL; SSL *cstream = NULL; SSL *conn; uint64_t r_streamid, p_streamid, c_streamid; conn = get_ids_connection(h3ssl); if (conn == NULL) { fprintf(stderr, "quic_server_h3streams no connection\n"); fflush(stderr); return -1; } rstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI); if (rstream != NULL) { printf("=> Opened on %llu\n", (unsigned long long)SSL_get_stream_id(rstream)); } else { fprintf(stderr, "=> Stream == NULL!\n"); goto err; } pstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI); if (pstream != NULL) { printf("=> Opened on %llu\n", (unsigned long long)SSL_get_stream_id(pstream)); } else { fprintf(stderr, "=> Stream == NULL!\n"); goto err; } cstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI); if (cstream != NULL) { fprintf(stderr, "=> Opened on %llu\n", (unsigned long long)SSL_get_stream_id(cstream)); fflush(stderr); } else { fprintf(stderr, "=> Stream == NULL!\n"); goto err; } r_streamid = SSL_get_stream_id(rstream); p_streamid = SSL_get_stream_id(pstream); c_streamid = SSL_get_stream_id(cstream); if (nghttp3_conn_bind_qpack_streams(h3conn, p_streamid, r_streamid)) { fprintf(stderr, "nghttp3_conn_bind_qpack_streams failed!\n"); goto err; } if (nghttp3_conn_bind_control_stream(h3conn, c_streamid)) { fprintf(stderr, "nghttp3_conn_bind_qpack_streams failed!\n"); goto err; } printf("control: %llu enc %llu dec %llu\n", (unsigned long long)c_streamid, (unsigned long long)p_streamid, (unsigned long long)r_streamid); add_id(SSL_get_stream_id(rstream), rstream, h3ssl); add_id(SSL_get_stream_id(pstream), pstream, h3ssl); add_id(SSL_get_stream_id(cstream), cstream, h3ssl); return 0; err: fflush(stderr); SSL_free(rstream); SSL_free(pstream); SSL_free(cstream); return -1; } /* Try to read from the streams we have */ static int read_from_ssl_ids(nghttp3_conn **curh3conn, struct h3ssl *h3ssl) { int hassomething = 0, i; struct ssl_id *ssl_ids = h3ssl->ssl_ids; SSL_POLL_ITEM items[MAXSSL_IDS] = {0}, *item = items; static const struct timeval nz_timeout = {0, 0}; size_t result_count = SIZE_MAX; int numitem = 0, ret; uint64_t processed_event = 0; int has_ids_to_remove = 0; nghttp3_conn *h3conn = *curh3conn; /* * Process all the streams * the first one is the connection if we get something here is a new stream */ for (i = 0; i < MAXSSL_IDS; i++) { if (ssl_ids[i].s != NULL) { item->desc = SSL_as_poll_descriptor(ssl_ids[i].s); item->events = UINT64_MAX; /* TODO adjust to the event we need process */ item->revents = UINT64_MAX; /* TODO adjust to the event we need process */ numitem++; item++; } } /* * SSL_POLL_FLAG_NO_HANDLE_EVENTS would require to use: * SSL_get_event_timeout on the connection stream * select/wait using the timeout value (which could be no wait time) * SSL_handle_events * SSL_poll * for the moment we let SSL_poll to performs ticking internally * on an automatic basis. */ ret = SSL_poll(items, numitem, sizeof(SSL_POLL_ITEM), &nz_timeout, SSL_POLL_FLAG_NO_HANDLE_EVENTS, &result_count); if (!ret) { fprintf(stderr, "SSL_poll failed\n"); printf("SSL_poll failed\n"); return -1; /* something is wrong */ } printf("read_from_ssl_ids %ld events\n", (unsigned long)result_count); if (result_count == 0) { /* Timeout may be something somewhere */ return 0; } /* reset the states */ h3ssl->new_conn = 0; h3ssl->restart = 0; h3ssl->done = 0; /* Process all the item we have polled */ for (i = 0, item = items; i < numitem; i++, item++) { SSL *s; if (item->revents == SSL_POLL_EVENT_NONE) continue; processed_event = 0; /* get the stream */ s = item->desc.value.ssl; /* New connection */ if (item->revents & SSL_POLL_EVENT_IC) { SSL *conn = SSL_accept_connection(item->desc.value.ssl, 0); SSL *oldconn; printf("SSL_accept_connection\n"); if (conn == NULL) { fprintf(stderr, "error while accepting connection\n"); ret = -1; goto err; } /* the previous might be still there */ oldconn = get_ids_connection(h3ssl); if (oldconn != NULL) { /* XXX we support only one connection for the moment */ printf("SSL_accept_connection closing previous\n"); SSL_free(oldconn); replace_ids_connection(h3ssl, oldconn, conn); reuse_h3ssl(h3ssl); close_all_ids(h3ssl); h3ssl->id_bidi = UINT64_MAX; h3ssl->has_uni = 0; } else { printf("SSL_accept_connection first connection\n"); add_ids_connection(h3ssl, conn); } h3ssl->new_conn = 1; /* create the new h3conn */ nghttp3_conn_del(*curh3conn); nghttp3_settings_default(&settings); if (nghttp3_conn_server_new(curh3conn, &callbacks, &settings, mem, h3ssl)) { fprintf(stderr, "nghttp3_conn_client_new failed!\n"); exit(1); } h3conn = *curh3conn; hassomething++; if (!SSL_set_incoming_stream_policy(conn, SSL_INCOMING_STREAM_POLICY_ACCEPT, 0)) { fprintf(stderr, "error while setting inccoming stream policy\n"); ret = -1; goto err; } printf("SSL_accept_connection\n"); processed_event = processed_event | SSL_POLL_EVENT_IC; } /* SSL_accept_stream if SSL_POLL_EVENT_ISB or SSL_POLL_EVENT_ISU */ if ((item->revents & SSL_POLL_EVENT_ISB) || (item->revents & SSL_POLL_EVENT_ISU)) { SSL *stream = SSL_accept_stream(item->desc.value.ssl, 0); uint64_t new_id; int r; if (stream == NULL) { ret = -1; goto err; } new_id = SSL_get_stream_id(stream); printf("=> Received connection on %lld %d\n", (unsigned long long) new_id, SSL_get_stream_type(stream)); add_id(new_id, stream, h3ssl); if (h3ssl->close_wait) { printf("in close_wait so we will have a new request\n"); reuse_h3ssl(h3ssl); h3ssl->restart = 1; /* Checked in wait_close loop */ } if (SSL_get_stream_type(stream) == SSL_STREAM_TYPE_BIDI) { /* bidi that is the id where we have to send the response */ if (h3ssl->id_bidi != UINT64_MAX) { set_id_status(h3ssl->id_bidi, TOBEREMOVED, h3ssl); has_ids_to_remove++; } h3ssl->id_bidi = new_id; reuse_h3ssl(h3ssl); h3ssl->restart = 1; } else { set_id_status(new_id, CLIENTUNIOPEN, h3ssl); } r = quic_server_read(h3conn, stream, new_id, h3ssl); if (r == -1) { ret = -1; goto err; } if (r == 1) hassomething++; if (item->revents & SSL_POLL_EVENT_ISB) processed_event = processed_event | SSL_POLL_EVENT_ISB; if (item->revents & SSL_POLL_EVENT_ISU) processed_event = processed_event | SSL_POLL_EVENT_ISU; } if (item->revents & SSL_POLL_EVENT_OSB) { /* Create new streams when allowed */ /* at least one bidi */ processed_event = processed_event | SSL_POLL_EVENT_OSB; printf("Create bidi?\n"); } if (item->revents & SSL_POLL_EVENT_OSU) { /* at least one uni */ /* we have 4 streams from the client 2, 6 , 10 and 0 */ /* need 3 streams to the client */ printf("Create uni?\n"); processed_event = processed_event | SSL_POLL_EVENT_OSU; if (!h3ssl->has_uni) { printf("Create uni\n"); ret = quic_server_h3streams(h3conn, h3ssl); if (ret == -1) { fprintf(stderr, "quic_server_h3streams failed!\n"); goto err; } h3ssl->has_uni = 1; hassomething++; } } if (item->revents & SSL_POLL_EVENT_EC) { /* the connection begins terminating */ printf("Connection terminating\n"); printf("Connection terminating restart %d\n", h3ssl->restart); if (!h3ssl->close_done) { h3ssl->close_done = 1; } else { h3ssl->done = 1; } hassomething++; processed_event = processed_event | SSL_POLL_EVENT_EC; } if (item->revents & SSL_POLL_EVENT_ECD) { /* the connection is terminated */ printf("Connection terminated\n"); h3ssl->done = 1; hassomething++; processed_event = processed_event | SSL_POLL_EVENT_ECD; } if (item->revents & SSL_POLL_EVENT_R) { /* try to read */ uint64_t id = UINT64_MAX; int r; /* get the id, well the connection has no id... */ id = SSL_get_stream_id(item->desc.value.ssl); printf("revent READ on %llu\n", (unsigned long long)id); r = quic_server_read(h3conn, s, id, h3ssl); if (r == 0) { uint8_t msg[1]; size_t l = sizeof(msg); /* check that the other side is closed */ r = SSL_read(s, msg, l); printf("SSL_read tells %d\n", r); if (r > 0) { ret = -1; goto err; } r = SSL_get_error(s, r); if (r != SSL_ERROR_ZERO_RETURN) { ret = -1; goto err; } set_id_status(id, TOBEREMOVED, h3ssl); has_ids_to_remove++; continue; } if (r == -1) { ret = -1; goto err; } hassomething++; processed_event = processed_event | SSL_POLL_EVENT_R; } if (item->revents & SSL_POLL_EVENT_ER) { /* mark it closed */ uint64_t id = UINT64_MAX; int status; id = SSL_get_stream_id(item->desc.value.ssl); status = get_id_status(id, h3ssl); printf("revent exception READ on %llu\n", (unsigned long long)id); if (status & CLIENTUNIOPEN) { set_id_status(id, CLIENTCLOSED, h3ssl); hassomething++; } processed_event = processed_event | SSL_POLL_EVENT_ER; } if (item->revents & SSL_POLL_EVENT_W) { /* we ignore those for the moment */ processed_event = processed_event | SSL_POLL_EVENT_W; } if (item->revents & SSL_POLL_EVENT_EW) { /* write part received a STOP_SENDING */ uint64_t id = UINT64_MAX; int status; id = SSL_get_stream_id(item->desc.value.ssl); status = get_id_status(id, h3ssl); if (status & SERVERCLOSED) { printf("both sides closed on %llu\n", (unsigned long long)id); set_id_status(id, TOBEREMOVED, h3ssl); has_ids_to_remove++; hassomething++; } processed_event = processed_event | SSL_POLL_EVENT_EW; } if (item->revents != processed_event) { /* Figure out ??? */ uint64_t id = UINT64_MAX; id = SSL_get_stream_id(item->desc.value.ssl); printf("revent %llu (%d) on %llu NOT PROCESSED!\n", (unsigned long long)item->revents, SSL_POLL_EVENT_W, (unsigned long long)id); } } ret = hassomething; err: if (has_ids_to_remove) remove_marked_ids(h3ssl); return ret; } static void handle_events_from_ids(struct h3ssl *h3ssl) { struct ssl_id *ssl_ids = h3ssl->ssl_ids; int i; ssl_ids = h3ssl->ssl_ids; for (i = 0; i < MAXSSL_IDS; i++) { if (ssl_ids[i].s != NULL && (ssl_ids[i].status & ISCONNECTION || ssl_ids[i].status & ISLISTENER)) { if (SSL_handle_events(ssl_ids[i].s)) ERR_print_errors_fp(stderr); } } } static size_t get_file_length(struct h3ssl *h3ssl) { char filename[PATH_MAX]; struct stat st; memset(filename, 0, PATH_MAX); if (h3ssl->fileprefix != NULL) strcat(filename, h3ssl->fileprefix); strcat(filename, h3ssl->url); if (strcmp(h3ssl->url, "big") == 0) { printf("big!!!\n"); return (size_t)INT_MAX; } if (stat(filename, &st) == 0) { /* Only process regular files */ if (S_ISREG(st.st_mode)) { printf("get_file_length %s %lld\n", filename, (unsigned long long) st.st_size); return (size_t)st.st_size; } } printf("Can't get_file_length %s\n", filename); return 0; } static char *get_file_data(struct h3ssl *h3ssl) { char filename[PATH_MAX]; size_t size = get_file_length(h3ssl); char *res; int fd; if (size == 0) return NULL; memset(filename, 0, PATH_MAX); if (h3ssl->fileprefix != NULL) strcat(filename, h3ssl->fileprefix); strcat(filename, h3ssl->url); res = malloc(size+1); res[size] = '\0'; fd = open(filename, O_RDONLY); if (read(fd, res, size) == -1) { close(fd); free(res); return NULL; } close(fd); printf("read from %s : %zu\n", filename, size); return res; } static nghttp3_ssize step_read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec, size_t veccnt, uint32_t *pflags, void *user_data, void *stream_user_data) { struct h3ssl *h3ssl = (struct h3ssl *)user_data; if (h3ssl->datadone) { *pflags = NGHTTP3_DATA_FLAG_EOF; return 0; } /* send the data */ printf("step_read_data for %s %zu\n", h3ssl->url, h3ssl->ldata); if (h3ssl->ldata <= 4096) { vec[0].base = &(h3ssl->ptr_data[h3ssl->offset_data]); vec[0].len = h3ssl->ldata; h3ssl->datadone++; *pflags = NGHTTP3_DATA_FLAG_EOF; } else { vec[0].base = &(h3ssl->ptr_data[h3ssl->offset_data]); vec[0].len = 4096; if (h3ssl->ldata == INT_MAX) { printf("big = endless!\n"); } else { h3ssl->offset_data = h3ssl->offset_data + 4096; h3ssl->ldata = h3ssl->ldata - 4096; } } return 1; } static int quic_server_write(struct h3ssl *h3ssl, uint64_t streamid, uint8_t *buff, size_t len, uint64_t flags, size_t *written) { struct ssl_id *ssl_ids; int i; ssl_ids = h3ssl->ssl_ids; for (i = 0; i < MAXSSL_IDS; i++) { if (ssl_ids[i].id == streamid) { if (!SSL_write_ex2(ssl_ids[i].s, buff, len, flags, written) || *written != len) { fprintf(stderr, "couldn't write on connection\n"); ERR_print_errors_fp(stderr); return 0; } printf("written %lld on %lld flags %lld\n", (unsigned long long)len, (unsigned long long)streamid, (unsigned long long)flags); return 1; } } printf("quic_server_write %lld on %lld (NOT FOUND!)\n", (unsigned long long)len, (unsigned long long)streamid); return 0; } #define OSSL_NELEM(x) (sizeof(x) / sizeof((x)[0])) /* * This is a basic demo of QUIC server functionality in which one connection at * a time is accepted in a blocking loop. */ /* ALPN string for TLS handshake. We pretent h3-29 and h3 */ static const unsigned char alpn_ossltest[] = { 5, 'h', '3', '-', '2', '9', 2, 'h', '3' }; /* * This callback validates and negotiates the desired ALPN on the server side. */ static int select_alpn(SSL *ssl, const unsigned char **out, unsigned char *out_len, const unsigned char *in, unsigned int in_len, void *arg) { if (SSL_select_next_proto((unsigned char **)out, out_len, alpn_ossltest, sizeof(alpn_ossltest), in, in_len) != OPENSSL_NPN_NEGOTIATED) return SSL_TLSEXT_ERR_ALERT_FATAL; return SSL_TLSEXT_ERR_OK; } /* Create SSL_CTX. */ static SSL_CTX *create_ctx(const char *cert_path, const char *key_path) { SSL_CTX *ctx; ctx = SSL_CTX_new(OSSL_QUIC_server_method()); if (ctx == NULL) goto err; /* Load certificate and corresponding private key. */ if (SSL_CTX_use_certificate_chain_file(ctx, cert_path) <= 0) { fprintf(stderr, "couldn't load certificate file: %s\n", cert_path); goto err; } if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0) { fprintf(stderr, "couldn't load key file: %s\n", key_path); goto err; } if (!SSL_CTX_check_private_key(ctx)) { fprintf(stderr, "private key check failed\n"); goto err; } /* Setup ALPN negotiation callback. */ SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL); return ctx; err: SSL_CTX_free(ctx); return NULL; } /* Create UDP socket using given port. */ static int create_socket(uint16_t port) { int fd = -1; struct sockaddr_in sa = {0}; if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { fprintf(stderr, "cannot create socket"); goto err; } sa.sin_family = AF_INET; sa.sin_port = htons(port); if (bind(fd, (const struct sockaddr *)&sa, sizeof(sa)) < 0) { fprintf(stderr, "cannot bind to %u\n", port); goto err; } return fd; err: if (fd >= 0) BIO_closesocket(fd); return -1; } /* Copied from demos/guide/quic-server-non-block.c */ /** * @brief Waits for activity on the SSL socket, either for reading or writing. * * This function monitors the underlying file descriptor of the given SSL * connection to determine when it is ready for reading or writing, or both. * It uses the select function to wait until the socket is either readable * or writable, depending on what the SSL connection requires. * * @param ssl A pointer to the SSL object representing the connection. * * @note This function blocks until there is activity on the socket. In a real * application, you might want to perform other tasks while waiting, such as * updating a GUI or handling other connections. * * @note This function uses select for simplicity and portability. Depending * on your application's requirements, you might consider using other * mechanisms like poll or epoll for handling multiple file descriptors. */ static int wait_for_activity(SSL *ssl) { int sock, isinfinite; fd_set read_fd, write_fd; struct timeval tv; struct timeval *tvp = NULL; /* Get hold of the underlying file descriptor for the socket */ if ((sock = SSL_get_fd(ssl)) == -1) { fprintf(stderr, "Unable to get file descriptor"); return -1; } /* Initialize the fd_set structure */ FD_ZERO(&read_fd); FD_ZERO(&write_fd); /* * Determine if we would like to write to the socket, read from it, or both. */ if (SSL_net_write_desired(ssl)) FD_SET(sock, &write_fd); if (SSL_net_read_desired(ssl)) FD_SET(sock, &read_fd); /* Add the socket file descriptor to the fd_set */ FD_SET(sock, &read_fd); /* * Find out when OpenSSL would next like to be called, regardless of * whether the state of the underlying socket has changed or not. */ if (SSL_get_event_timeout(ssl, &tv, &isinfinite) && !isinfinite) tvp = &tv; /* * Wait until the socket is writeable or readable. We use select here * for the sake of simplicity and portability, but you could equally use * poll/epoll or similar functions * * NOTE: For the purposes of this demonstration code this effectively * makes this demo block until it has something more useful to do. In a * real application you probably want to go and do other work here (e.g. * update a GUI, or service other connections). * * Let's say for example that you want to update the progress counter on * a GUI every 100ms. One way to do that would be to use the timeout in * the last parameter to "select" below. If the tvp value is greater * than 100ms then use 100ms instead. Then, when select returns, you * check if it did so because of activity on the file descriptors or * because of the timeout. If the 100ms GUI timeout has expired but the * tvp timeout has not then go and update the GUI and then restart the * "select" (with updated timeouts). */ return (select(sock + 1, &read_fd, &write_fd, NULL, tvp)); } /* Main loop for server to accept QUIC connections. */ static int run_quic_server(SSL_CTX *ctx, int fd) { int ok = 0; int hassomething = 0; SSL *listener = NULL; nghttp3_conn *h3conn = NULL; struct h3ssl h3ssl; SSL *ssl; char *fileprefix = getenv("FILEPREFIX"); /* Create a new QUIC listener. */ if ((listener = SSL_new_listener(ctx, 0)) == NULL) goto err; /* Provide the listener with our UDP socket. */ if (!SSL_set_fd(listener, fd)) goto err; /* Begin listening. */ if (!SSL_listen(listener)) goto err; /* * Listeners, and other QUIC objects, default to operating in blocking mode. * The configured behaviour is inherited by child objects. * Make sure we won't block as we use select(). */ if (!SSL_set_blocking_mode(listener, 0)) goto err; /* Setup callbacks. */ callbacks.recv_header = on_recv_header; callbacks.end_headers = on_end_headers; callbacks.recv_data = on_recv_data; callbacks.end_stream = on_end_stream; /* mem default */ mem = nghttp3_mem_default(); for (;;) { nghttp3_nv resp[10]; size_t num_nv; nghttp3_data_reader dr; int ret; int numtimeout; char slength[22]; int hasnothing; init_ids(&h3ssl); h3ssl.fileprefix = fileprefix; printf("listener: %p\n", (void *)listener); add_ids_listener(listener, &h3ssl); if (!hassomething) { printf("waiting on socket\n"); fflush(stdout); ret = wait_for_activity(listener); if (ret == -1) { fprintf(stderr, "wait_for_activity failed!\n"); goto err; } } /* * Service the connection. In a real application this would be done * concurrently. In this demonstration program a single connection is * accepted and serviced at a time. */ newconn: printf("process_server starting...\n"); fflush(stdout); /* wait until we have received the headers */ restart: numtimeout = 0; num_nv = 0; while (!h3ssl.end_headers_received) { if (!hassomething) { if (wait_for_activity(listener) == 0) { printf("waiting for end_headers_received timeout %d\n", numtimeout); numtimeout++; if (numtimeout == 25) goto err; } handle_events_from_ids(&h3ssl); } hassomething = read_from_ssl_ids(&h3conn, &h3ssl); if (hassomething == -1) { fprintf(stderr, "read_from_ssl_ids hassomething failed\n"); goto err; } else if (hassomething == 0) { printf("read_from_ssl_ids hassomething nothing...\n"); } else { numtimeout = 0; printf("read_from_ssl_ids hassomething %d...\n", hassomething); if (h3ssl.close_done) { /* Other side has closed */ break; } h3ssl.restart = 0; } } if (h3ssl.close_done) { printf("Other side close without request\n"); goto wait_close; } printf("end_headers_received!!!\n"); if (!h3ssl.has_uni) { /* time to create those otherwise we can't push anything to the client */ printf("Create uni\n"); if (quic_server_h3streams(h3conn, &h3ssl) == -1) { fprintf(stderr, "quic_server_h3streams failed!\n"); goto err; } h3ssl.has_uni = 1; } /* we have receive the request build the response and send it */ /* XXX add MAKE_NV("connection", "close"), to resp[] and recheck */ make_nv(&resp[num_nv++], ":status", "200"); h3ssl.ldata = get_file_length(&h3ssl); if (h3ssl.ldata == 0) { /* We don't find the file: use default test string */ h3ssl.ptr_data = nulldata; h3ssl.ldata = nulldata_sz; sprintf(slength, "%zu", h3ssl.ldata); /* content-type: text/html */ make_nv(&resp[num_nv++], "content-type", "text/html"); } else if (h3ssl.ldata == INT_MAX) { /* endless file for tests */ sprintf(slength, "%zu", h3ssl.ldata); h3ssl.ptr_data = (uint8_t *) malloc(4096); memset(h3ssl.ptr_data, 'A', 4096); } else { /* normal file we have opened */ sprintf(slength, "%zu", h3ssl.ldata); h3ssl.ptr_data = (uint8_t *) get_file_data(&h3ssl); if (h3ssl.ptr_data == NULL) abort(); printf("before nghttp3_conn_submit_response on %llu for %s ...\n", (unsigned long long) h3ssl.id_bidi, h3ssl.url); if (strstr(h3ssl.url, ".png")) make_nv(&resp[num_nv++], "content-type", "image/png"); else if (strstr(h3ssl.url, ".ico")) make_nv(&resp[num_nv++], "content-type", "image/vnd.microsoft.icon"); else if (strstr(h3ssl.url, ".htm")) make_nv(&resp[num_nv++], "content-type", "text/html"); else make_nv(&resp[num_nv++], "content-type", "application/octet-stream"); make_nv(&resp[num_nv++], "content-length", slength); } dr.read_data = step_read_data; if (nghttp3_conn_submit_response(h3conn, h3ssl.id_bidi, resp, num_nv, &dr)) { fprintf(stderr, "nghttp3_conn_submit_response failed!\n"); goto err; } printf("nghttp3_conn_submit_response on %llu...\n", (unsigned long long) h3ssl.id_bidi); for (;;) { nghttp3_vec vec[256]; nghttp3_ssize sveccnt; int fin, i; int64_t streamid; sveccnt = nghttp3_conn_writev_stream(h3conn, &streamid, &fin, vec, nghttp3_arraylen(vec)); if (sveccnt <= 0) { printf("nghttp3_conn_writev_stream done: %ld stream: %llu fin %d\n", (long int)sveccnt, (unsigned long long)streamid, fin); if (streamid != -1 && fin) { printf("Sending end data on %llu fin %d\n", (unsigned long long) streamid, fin); nghttp3_conn_add_write_offset(h3conn, streamid, 0); continue; } if (!h3ssl.datadone) goto err; else break; /* Done */ } printf("nghttp3_conn_writev_stream: %ld fin: %d\n", (long int)sveccnt, fin); for (i = 0; i < sveccnt; i++) { size_t numbytes = vec[i].len; int flagwrite = 0; printf("quic_server_write on %llu for %ld\n", (unsigned long long)streamid, (unsigned long)vec[i].len); if (fin && i == sveccnt - 1) flagwrite = SSL_WRITE_FLAG_CONCLUDE; if (!quic_server_write(&h3ssl, streamid, vec[i].base, vec[i].len, flagwrite, &numbytes)) { fprintf(stderr, "quic_server_write failed!\n"); goto err; } } if (nghttp3_conn_add_write_offset( h3conn, streamid, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt))) { fprintf(stderr, "nghttp3_conn_add_write_offset failed!\n"); goto err; } } printf("nghttp3_conn_submit_response DONE!!!\n"); if (h3ssl.datadone) { /* * All the data was sent. * close stream zero */ if (!h3ssl.close_done) { set_id_status(h3ssl.id_bidi, SERVERCLOSED, &h3ssl); h3ssl.close_wait = 1; } } else { printf("nghttp3_conn_submit_response still not finished\n"); } /* wait until closed */ wait_close: hasnothing = 0; for (;;) { if (!hasnothing) { SSL *newssl = get_ids_connection(&h3ssl); printf("hasnothing nothing WAIT %d!!!\n", h3ssl.close_done); if (newssl == NULL) newssl = listener; ret = wait_for_activity(newssl); if (ret == -1) goto err; if (ret == 0) printf("hasnothing timeout\n"); /* we have something or a timeout */ handle_events_from_ids(&h3ssl); } hasnothing = read_from_ssl_ids(&h3conn, &h3ssl); if (hasnothing == -1) { printf("hasnothing failed\n"); break; /* goto err; well in fact not */ } else if (hasnothing == 0) { printf("hasnothing nothing\n"); continue; } else { printf("hasnothing something\n"); if (h3ssl.done) { printf("hasnothing something... DONE\n"); /* we might already have the next connection to accept */ hassomething = 1; break; } if (h3ssl.new_conn) { printf("hasnothing something... NEW CONN\n"); h3ssl.new_conn = 0; goto newconn; } if (h3ssl.restart) { printf("hasnothing something... RESTART\n"); h3ssl.restart = 0; goto restart; } if (are_all_clientid_closed(&h3ssl)) { printf("hasnothing something... DONE other side closed\n"); /* there might 2 or 3 message we will ignore */ hassomething = 0; break; } } } /* * Free the streams, then loop again, accepting another connection. */ close_all_ids(&h3ssl); ssl = get_ids_connection(&h3ssl); if (ssl != NULL) { SSL_free(ssl); replace_ids_connection(&h3ssl, ssl, NULL); } hassomething = 0; } ok = 1; err: if (!ok) ERR_print_errors_fp(stderr); SSL_free(listener); return ok; } /* * demo server... just return a 20 bytes ascii string as response for any * request single h3 connection and single threaded. */ int main(int argc, char **argv) { int rc = 1; SSL_CTX *ctx = NULL; int fd = -1; unsigned long port; if (argc < 4) { fprintf(stderr, "usage: %s \n", argv[0]); goto err; } /* Create SSL_CTX. */ if ((ctx = create_ctx(argv[2], argv[3])) == NULL) goto err; /* Parse port number from command line arguments. */ port = strtoul(argv[1], NULL, 0); if (port == 0 || port > UINT16_MAX) { fprintf(stderr, "invalid port: %lu\n", port); goto err; } /* Create UDP socket. */ if ((fd = create_socket((uint16_t)port)) < 0) goto err; /* Enter QUIC server connection acceptance loop. */ if (!run_quic_server(ctx, fd)) goto err; rc = 0; err: if (rc != 0) ERR_print_errors_fp(stderr); SSL_CTX_free(ctx); if (fd != -1) BIO_closesocket(fd); return rc; }