From: Alexandr Nedvedicky Date: Thu, 9 Oct 2025 14:05:59 +0000 (+0200) Subject: Make eyeballs happy again for ossl-nghttp3-demo X-Git-Tag: 4.0-PRE-CLANG-FORMAT-WEBKIT~388 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1a56dae03b3464cb870e77ab438b324d82f92803;p=thirdparty%2Fopenssl.git Make eyeballs happy again for ossl-nghttp3-demo Adjust the http3 demo client so it works better on dual stack hosts. This fixes the case when DNS returns both IPv4 and IPv6 addresses for host we try to reach. The current code just uses the first address found in DNS answer. If things are unfortunate and the service (port number) demo client tries to reach does not listen on the address then demo gives up and exits. Demo can do better. The RFC 6555 suggests application should try to reach the service on the next address returned by DNS, when the first attempt fails for the first address returned by DNS. This change helps with situation when DNS prefers, let' say, IPv6 address, but the service is reachable via IPv4 only. In that case application sees the failure on the first attempt to connect to remote server over IPv6, but the second attempt that uses IPv4 is going to succeed. This extra handling is required for QUIC which uses UDP protocol. For TLS client which uses TCP all this happens inside BIO layer which tries to establish TCP connection. There is no TCP-handshake on UDP protocol so BIO can not see the service is not reachable on requested address. Fixes: #28331 Reviewed-by: Neil Horman Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/28802) --- diff --git a/demos/http3/ossl-nghttp3-demo-server.c b/demos/http3/ossl-nghttp3-demo-server.c index 2b44fb8b142..1b06300cf4d 100644 --- a/demos/http3/ossl-nghttp3-demo-server.c +++ b/demos/http3/ossl-nghttp3-demo-server.c @@ -1079,7 +1079,7 @@ static int run_quic_server(SSL_CTX *ctx, int fd) /* mem default */ mem = nghttp3_mem_default(); - + memset(&h3ssl, 0, sizeof(h3ssl)); for (;;) { nghttp3_nv resp[10]; size_t num_nv; diff --git a/demos/http3/ossl-nghttp3-demo.c b/demos/http3/ossl-nghttp3-demo.c index 2225b0cd584..41cc7a0ed8b 100644 --- a/demos/http3/ossl-nghttp3-demo.c +++ b/demos/http3/ossl-nghttp3-demo.c @@ -9,6 +9,9 @@ #include "ossl-nghttp3.h" #include +#include +#include + static int done; static void make_nv(nghttp3_nv *nv, const char *name, const char *value) @@ -76,15 +79,47 @@ static int on_end_stream(nghttp3_conn *h3conn, int64_t stream_id, return 0; } +static int try_conn(OSSL_DEMO_H3_CONN *conn, const char *bare_hostname) +{ + nghttp3_nv nva[16]; + size_t num_nv = 0; + + /* Build HTTP headers. */ + make_nv(&nva[num_nv++], ":method", "GET"); + make_nv(&nva[num_nv++], ":scheme", "https"); + make_nv(&nva[num_nv++], ":authority", bare_hostname); + make_nv(&nva[num_nv++], ":path", "/"); + make_nv(&nva[num_nv++], "user-agent", "OpenSSL-Demo/nghttp3"); + + /* Submit request. */ + if (!OSSL_DEMO_H3_CONN_submit_request(conn, nva, num_nv, NULL, NULL)) { + ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL, + "cannot submit HTTP/3 request"); + return 0; + } + + /* Wait for request to complete. */ + done = 0; + while (!done) + if (!OSSL_DEMO_H3_CONN_handle_events(conn)) { + ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL, + "cannot handle events"); + return 0; + } + return 1; +} + int main(int argc, char **argv) { int ret = 1; + int ok; SSL_CTX *ctx = NULL; OSSL_DEMO_H3_CONN *conn = NULL; - nghttp3_nv nva[16]; nghttp3_callbacks callbacks = {0}; - size_t num_nv = 0; const char *addr; + char *hostname, *service; + BIO_ADDRINFO *bai = NULL; + const BIO_ADDRINFO *bai_walk; /* Check arguments. */ if (argc < 2) { @@ -94,6 +129,29 @@ int main(int argc, char **argv) addr = argv[1]; + hostname = NULL; + service = NULL; + if (BIO_parse_hostserv(addr, &hostname, &service, 0) != 1) { + fprintf(stderr, "usage: %s \n", argv[0]); + goto err; + } + + if (hostname == NULL || service == NULL) { + fprintf(stderr, "usage: %s \n", argv[0]); + goto err; + } + + /* + * Remember DNS may return more IP addresses (and it typically does these + * dual-stack days). + */ + ok = BIO_lookup_ex(hostname, service, BIO_LOOKUP_CLIENT, + 0, SOCK_DGRAM, IPPROTO_UDP, &bai); + if (ok == 0) { + fprintf(stderr, "host %s not found\n", hostname); + goto err; + } + /* Setup SSL_CTX. */ if ((ctx = SSL_CTX_new(OSSL_QUIC_client_method())) == NULL) goto err; @@ -109,42 +167,44 @@ int main(int argc, char **argv) callbacks.recv_data = on_recv_data; callbacks.end_stream = on_end_stream; - /* Create connection. */ - if ((conn = OSSL_DEMO_H3_CONN_new_for_addr(ctx, addr, &callbacks, - NULL, NULL)) == NULL) { - ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL, - "cannot create HTTP/3 connection"); - goto err; - } - - /* Build HTTP headers. */ - make_nv(&nva[num_nv++], ":method", "GET"); - make_nv(&nva[num_nv++], ":scheme", "https"); - make_nv(&nva[num_nv++], ":authority", addr); - make_nv(&nva[num_nv++], ":path", "/"); - make_nv(&nva[num_nv++], "user-agent", "OpenSSL-Demo/nghttp3"); - - /* Submit request. */ - if (!OSSL_DEMO_H3_CONN_submit_request(conn, nva, num_nv, NULL, NULL)) { - ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL, - "cannot submit HTTP/3 request"); - goto err; - } - - /* Wait for request to complete. */ - while (!done) - if (!OSSL_DEMO_H3_CONN_handle_events(conn)) { - ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL, - "cannot handle events"); - goto err; + /* + * Unlike TCP there is no handshake on UDP protocol. + * The BIO subsystem uses connect(2) to establish + * connection. However connect(2) for UDP does not + * perform handshake, so BIO just assumes the remote + * service is reachable on the first address returned + * by DNS. It's the SSL-handshake itself when QUIC stack + * realizes the service is not reachable, so the application + * needs to initiate a QUIC connection on the next address + * returned by DNS. + */ + for (bai_walk = bai; bai_walk != NULL; + bai_walk = BIO_ADDRINFO_next(bai_walk)) { + conn = OSSL_DEMO_H3_CONN_new_for_addr(ctx, bai_walk, hostname, + &callbacks, NULL, NULL); + if (conn != NULL) { + if (try_conn(conn, addr) == 0) { + /* + * Failure, try the next address. + */ + OSSL_DEMO_H3_CONN_free(conn); + conn = NULL; + ret = 1; + } else { + /* + * Success, done. + */ + ret = 0; + break; + } } - - ret = 0; + } err: if (ret != 0) ERR_print_errors_fp(stderr); OSSL_DEMO_H3_CONN_free(conn); SSL_CTX_free(ctx); + BIO_ADDRINFO_free(bai); return ret; } diff --git a/demos/http3/ossl-nghttp3.c b/demos/http3/ossl-nghttp3.c index be781f71982..fc58659a9de 100644 --- a/demos/http3/ossl-nghttp3.c +++ b/demos/http3/ossl-nghttp3.c @@ -10,6 +10,9 @@ #include #include +#include +#include + #define ARRAY_LEN(x) (sizeof(x)/sizeof((x)[0])) enum { @@ -228,7 +231,7 @@ static int h3_conn_deferred_consume(nghttp3_conn *h3conn, int64_t stream_id, return ret; } -OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(BIO *qconn_bio, +OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(SSL *qconn, const nghttp3_callbacks *callbacks, const nghttp3_settings *settings, void *user_data) @@ -242,7 +245,7 @@ OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(BIO *qconn_bio, nghttp3_callbacks intl_callbacks = {0}; static const unsigned char alpn[] = {2, 'h', '3'}; - if (qconn_bio == NULL) { + if (qconn == NULL) { ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER, "QUIC connection BIO must be provided"); return NULL; @@ -251,38 +254,19 @@ OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(BIO *qconn_bio, if ((conn = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_CONN))) == NULL) return NULL; - conn->qconn_bio = qconn_bio; + conn->qconn = qconn; conn->user_data = user_data; - if (BIO_get_ssl(qconn_bio, &conn->qconn) == 0) { - ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_INVALID_ARGUMENT, - "BIO must be an SSL BIO"); - goto err; - } - /* Create the map of stream IDs to OSSL_DEMO_H3_STREAM structures. */ if ((conn->streams = lh_OSSL_DEMO_H3_STREAM_new(h3_stream_hash, h3_stream_eq)) == NULL) goto err; - /* - * If the application has not started connecting yet, helpfully - * auto-configure ALPN. If the application wants to initiate the connection - * itself, it must take care of this itself. - */ - if (SSL_in_before(conn->qconn)) - if (SSL_set_alpn_protos(conn->qconn, alpn, sizeof(alpn))) { - /* SSL_set_alpn_protos returns 1 on failure */ - ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, - "failed to configure ALPN"); - goto err; - } - - /* - * We use the QUIC stack in non-blocking mode so that we can react to - * incoming data on different streams, and e.g. incoming streams initiated - * by a server, as and when events occur. - */ - BIO_set_nbio(conn->qconn_bio, 1); + if (SSL_set_alpn_protos(conn->qconn, alpn, sizeof(alpn))) { + /* SSL_set_alpn_protos returns 1 on failure */ + ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, + "failed to configure ALPN"); + goto err; + } /* * Disable default stream mode and create all streams explicitly. Each QUIC @@ -388,7 +372,39 @@ err: return NULL; } -OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const char *addr, +/* + * Create BIO with UDP connected socket for one of the address we got from + * resolver. + */ +static BIO * +create_socket_bio(const BIO_ADDRINFO *bai) +{ + int sock; + BIO *bio; + + sock = BIO_socket(BIO_ADDRINFO_family(bai), SOCK_DGRAM, 0, 0); + if (sock == -1) + return NULL; + + if (!BIO_connect(sock, BIO_ADDRINFO_address(bai), 0)) + goto err; + + if (!BIO_socket_nbio(sock, 1)) + goto err; + + if ((bio = BIO_new(BIO_s_datagram())) == NULL) + goto err; + BIO_set_fd(bio, sock, BIO_CLOSE); + + return bio; +err: + BIO_closesocket(sock); + + return NULL; +} + +OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const BIO_ADDRINFO *bai, + const char *bare_hostname, const nghttp3_callbacks *callbacks, const nghttp3_settings *settings, void *user_data) @@ -396,26 +412,16 @@ OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const char *addr BIO *qconn_bio = NULL; SSL *qconn = NULL; OSSL_DEMO_H3_CONN *conn = NULL; - const char *bare_hostname; - /* QUIC connection setup */ - if ((qconn_bio = BIO_new_ssl_connect(ctx)) == NULL) - goto err; - - /* Pass the 'hostname:port' string into the ssl_connect BIO. */ - if (BIO_set_conn_hostname(qconn_bio, addr) == 0) - goto err; + if ((qconn_bio = create_socket_bio(bai)) == NULL) + return NULL; - /* - * Get the 'bare' hostname out of the ssl_connect BIO. This is the hostname - * without the port. - */ - bare_hostname = BIO_get_conn_hostname(qconn_bio); - if (bare_hostname == NULL) + qconn = SSL_new(ctx); + if (qconn == NULL) goto err; - if (BIO_get_ssl(qconn_bio, &qconn) == 0) - goto err; + SSL_set_bio(qconn, qconn_bio, qconn_bio); + qconn_bio = NULL; /* Set the hostname we will validate the X.509 certificate against. */ if (SSL_set1_host(qconn, bare_hostname) <= 0) @@ -425,7 +431,7 @@ OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const char *addr if (!SSL_set_tlsext_host_name(qconn, bare_hostname)) goto err; - conn = OSSL_DEMO_H3_CONN_new_for_conn(qconn_bio, callbacks, + conn = OSSL_DEMO_H3_CONN_new_for_conn(qconn, callbacks, settings, user_data); if (conn == NULL) goto err; @@ -433,15 +439,11 @@ OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const char *addr return conn; err: + SSL_free(qconn); BIO_free_all(qconn_bio); return NULL; } -int OSSL_DEMO_H3_CONN_connect(OSSL_DEMO_H3_CONN *conn) -{ - return SSL_connect(OSSL_DEMO_H3_CONN_get0_connection(conn)); -} - void *OSSL_DEMO_H3_CONN_get_user_data(const OSSL_DEMO_H3_CONN *conn) { return conn->user_data; diff --git a/demos/http3/ossl-nghttp3.h b/demos/http3/ossl-nghttp3.h index ebf239657f0..a71197aa1dd 100644 --- a/demos/http3/ossl-nghttp3.h +++ b/demos/http3/ossl-nghttp3.h @@ -45,7 +45,7 @@ typedef struct ossl_demo_h3_stream_st OSSL_DEMO_H3_STREAM; * * Returns NULL on failure. */ -OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(BIO *qconn_bio, +OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(SSL *qconn, const nghttp3_callbacks *callbacks, const nghttp3_settings *settings, void *user_data); @@ -61,7 +61,8 @@ OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(BIO *qconn_bio, * SSL_METHOD. */ OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, - const char *addr, + const BIO_ADDRINFO *addr, + const char *bare_hostname, const nghttp3_callbacks *callbacks, const nghttp3_settings *settings, void *user_data);