]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Make eyeballs happy again for ossl-nghttp3-demo
authorAlexandr Nedvedicky <sashan@openssl.org>
Thu, 9 Oct 2025 14:05:59 +0000 (16:05 +0200)
committerNeil Horman <nhorman@openssl.org>
Tue, 14 Oct 2025 11:53:16 +0000 (07:53 -0400)
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 <nhorman@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/28802)

demos/http3/ossl-nghttp3-demo-server.c
demos/http3/ossl-nghttp3-demo.c
demos/http3/ossl-nghttp3.c
demos/http3/ossl-nghttp3.h

index 2b44fb8b14211234bdb53ec514018d45cdbb25c0..1b06300cf4da94fc0a506403ddb3f34b4293dd1f 100644 (file)
@@ -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;
index 2225b0cd584c4066875f77f8354ab8263f5a2623..41cc7a0ed8ba08746f806f1903312c3ab5432a32 100644 (file)
@@ -9,6 +9,9 @@
 #include "ossl-nghttp3.h"
 #include <openssl/err.h>
 
+#include <sys/socket.h>
+#include <netinet/in.h>
+
 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 <host:port>\n", argv[0]);
+        goto err;
+    }
+
+    if (hostname == NULL || service == NULL) {
+        fprintf(stderr, "usage: %s <host:port>\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;
 }
index be781f71982a4cc33a486b9f95b9e830ccb5e653..fc58659a9de1bb9458bd20380a0de75285d108d7 100644 (file)
@@ -10,6 +10,9 @@
 #include <openssl/err.h>
 #include <assert.h>
 
+#include <sys/socket.h>
+#include <netinet/in.h>
+
 #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;
index ebf239657f08ceb15b3389d1c7c41d99c014a9d1..a71197aa1dda2b831d3b1a695c25cf2722f48356 100644 (file)
@@ -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);