]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Add SSL_get_peer_addr() function to query peer address for QUIC
authorGustaf Neumann <neumann@wu-wien.ac.at>
Sun, 28 Sep 2025 13:03:32 +0000 (15:03 +0200)
committerTomas Mraz <tomas@openssl.org>
Sat, 4 Oct 2025 08:21:38 +0000 (10:21 +0200)
This change introduces a new public API symbol: SSL_get_peer_addr().
The change is QUIC-only, there are no changes for TLS connections

- API: add peer address query for QUIC connections
  * Internal: declare/implement ossl_quic_get_peer_addr(SSL*, BIO_ADDR*)
  * Public: declare/implement SSL_get_peer_addr(SSL*, BIO_ADDR*)

Rationale:
- Allow applications to retrieve the remote UDP tuple for QUIC sessions
  (e.g., logging, access control, diagnostics)

Provided documentation and test cases for SSL_get_peer_addr().

Set peer via channel API on new-conn.

- In ch_on_new_conn_common(), BIO_ADDR_copy(&ch->cur_peer_addr, peer)
  was replaced with ossl_quic_channel_set_peer_addr(ch, peer) so
  addressed_mode is enabled at connection bring-up.

Dropped redundant peer detection in create_qc_from_incoming_conn()

The peer address is now propagated in ch_on_new_conn_common() via
ossl_quic_channel_set_peer_addr(), so the channel is already in
"addressed" mode. This also avoids querying the (unconnected) server
UDP BIO, reduces duplication, and simplifies the accept path. All
regression tests pass.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Saša Nedvědický <sashan@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/28690)

doc/build.info
doc/man3/SSL_get_peer_addr.pod [new file with mode: 0644]
doc/man7/openssl-quic.pod
include/internal/quic_ssl.h
include/openssl/ssl.h.in
ssl/quic/quic_channel.c
ssl/quic/quic_impl.c
ssl/quic/quic_port.c
ssl/ssl_lib.c
test/quicapitest.c
util/libssl.num

index a91a5c5798309f3362f4f919c17ef348bcdf73a9..091c8e44ce70af73597e619697d737856f7b0a73 100644 (file)
@@ -2659,6 +2659,10 @@ DEPEND[html/man3/SSL_get_handshake_rtt.html]=man3/SSL_get_handshake_rtt.pod
 GENERATE[html/man3/SSL_get_handshake_rtt.html]=man3/SSL_get_handshake_rtt.pod
 DEPEND[man/man3/SSL_get_handshake_rtt.3]=man3/SSL_get_handshake_rtt.pod
 GENERATE[man/man3/SSL_get_handshake_rtt.3]=man3/SSL_get_handshake_rtt.pod
+DEPEND[html/man3/SSL_get_peer_addr.html]=man3/SSL_get_peer_addr.pod
+GENERATE[html/man3/SSL_get_peer_addr.html]=man3/SSL_get_peer_addr.pod
+DEPEND[man/man3/SSL_get_peer_addr.3]=man3/SSL_get_peer_addr.pod
+GENERATE[man/man3/SSL_get_peer_addr.3]=man3/SSL_get_peer_addr.pod
 DEPEND[html/man3/SSL_get_peer_cert_chain.html]=man3/SSL_get_peer_cert_chain.pod
 GENERATE[html/man3/SSL_get_peer_cert_chain.html]=man3/SSL_get_peer_cert_chain.pod
 DEPEND[man/man3/SSL_get_peer_cert_chain.3]=man3/SSL_get_peer_cert_chain.pod
@@ -3716,6 +3720,7 @@ html/man3/SSL_get_event_timeout.html \
 html/man3/SSL_get_extms_support.html \
 html/man3/SSL_get_fd.html \
 html/man3/SSL_get_handshake_rtt.html \
+html/man3/SSL_get_peer_addr.html \
 html/man3/SSL_get_peer_cert_chain.html \
 html/man3/SSL_get_peer_certificate.html \
 html/man3/SSL_get_peer_signature_nid.html \
@@ -4388,6 +4393,7 @@ man/man3/SSL_get_event_timeout.3 \
 man/man3/SSL_get_extms_support.3 \
 man/man3/SSL_get_fd.3 \
 man/man3/SSL_get_handshake_rtt.3 \
+man/man3/SSL_get_peer_addr.3 \
 man/man3/SSL_get_peer_cert_chain.3 \
 man/man3/SSL_get_peer_certificate.3 \
 man/man3/SSL_get_peer_signature_nid.3 \
diff --git a/doc/man3/SSL_get_peer_addr.pod b/doc/man3/SSL_get_peer_addr.pod
new file mode 100644 (file)
index 0000000..97362e2
--- /dev/null
@@ -0,0 +1,57 @@
+=pod
+
+=head1 NAME
+
+SSL_get_peer_addr - obtain the peer address of a QUIC connection
+
+=head1 SYNOPSIS
+
+ #include <openssl/ssl.h>
+
+ int SSL_get_peer_addr(SSL *ssl, BIO_ADDR *peer_addr);
+
+=head1 DESCRIPTION
+
+SSL_get_peer_addr() retrieves the peer address of a QUIC connection and stores
+it into the B<BIO_ADDR> structure pointed to by I<peer_addr>. The caller must
+supply a valid B<BIO_ADDR> object which will be filled in on success.
+
+This function is only meaningful when called on an SSL object which represents
+a QUIC connection or stream. It is not valid for non-QUIC SSL objects.
+
+=head1 NOTES
+
+The peer address identifies the remote endpoint of a QUIC connection. For UDP
+sockets used by QUIC, the kernel does not maintain a connected peer in the same
+sense as for TCP. Instead, the peer address is tracked in the QUIC connection
+state. SSL_get_peer_addr() provides a way to obtain this address information
+from OpenSSL.
+
+The B<BIO_ADDR> type is an OpenSSL abstraction which can represent
+both IPv4 and IPv6 socket addresses. Applications can get the address
+family via the L<BIO_ADDR(3)> API.
+
+=head1 RETURN VALUES
+
+SSL_get_peer_addr() returns 1 on success. On failure, or if called on an SSL
+object which is not a QUIC SSL object, 0 is returned and I<peer_addr> is left
+unchanged.
+
+=head1 SEE ALSO
+
+L<SSL_accept_connection(3)>, L<SSL_accept_stream(3)>, L<BIO_ADDR(3)>
+
+=head1 HISTORY
+
+This function was added in OpenSSL 4.0.
+
+=head1 COPYRIGHT
+
+Copyright 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
+L<https://www.openssl.org/source/license.html>.
+
+=cut
index 35661e15b7c0f3b2ab61f1d4e90650de4cae72c5..75ca85d7ed9b87adc7336d110f58aae4ac5fcd0a 100644 (file)
@@ -710,6 +710,10 @@ or allow them to be handled using L<SSL_accept_stream(3)>.
 Used to configure or disable default stream mode; see the MODES OF OPERATION
 section for details.
 
+=item L<SSL_get_peer_addr(3)>
+
+Used to obtain the peer address from a QUIC connection or stream.
+
 =back
 
 The following BIO APIs are not specific to QUIC but have been added to
index e7fd260ab600061f682fd38741da514dba434a8d..0df73c22003e3ff2c79805efd8344590adddcbb5 100644 (file)
@@ -120,6 +120,7 @@ __owur int ossl_quic_set_value_uint(SSL *s, uint32_t class_, uint32_t id,
 __owur SSL *ossl_quic_accept_connection(SSL *ssl, uint64_t flags);
 __owur size_t ossl_quic_get_accept_connection_queue_len(SSL *ssl);
 __owur int ossl_quic_listen(SSL *ssl);
+__owur int ossl_quic_get_peer_addr(SSL *ssl, BIO_ADDR *peer_addr);
 
 __owur int ossl_quic_stream_reset(SSL *ssl,
                                   const SSL_STREAM_RESET_ARGS *args,
index 1cc77189023c5acdd9c9039649f9e8261dd7935e..cb30dda92d2c8e196cfd33734d4c278919539583 100644 (file)
@@ -2302,6 +2302,7 @@ size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx);
 /* QUIC support */
 int SSL_handle_events(SSL *s);
 __owur int SSL_get_event_timeout(SSL *s, struct timeval *tv, int *is_infinite);
+__owur int SSL_get_peer_addr(SSL *ssl, BIO_ADDR *peer_addr);
 __owur int SSL_get_rpoll_descriptor(SSL *s, BIO_POLL_DESCRIPTOR *desc);
 __owur int SSL_get_wpoll_descriptor(SSL *s, BIO_POLL_DESCRIPTOR *desc);
 __owur int SSL_net_read_desired(SSL *s);
index 61e54570b1c9067338a2ed16dbdced40ed186b7f..7679ec8790e13724d2706b1f206c11ae2d88a2ed 100644 (file)
@@ -3718,7 +3718,7 @@ static int ch_on_new_conn_common(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
                                  const QUIC_CONN_ID *peer_odcid)
 {
     /* Note our newly learnt peer address and CIDs. */
-    if (!BIO_ADDR_copy(&ch->cur_peer_addr, peer))
+    if (!ossl_quic_channel_set_peer_addr(ch, peer))
         return 0;
 
     ch->init_dcid       = *peer_dcid;
index e5f5eea7b45a6180bd44cc9c39c013a20ecb07d5..60e8081e1046802e7f6b483a422ddd8cd4e89558 100644 (file)
@@ -4729,7 +4729,6 @@ static QUIC_CONNECTION *create_qc_from_incoming_conn(QUIC_LISTENER *ql, QUIC_CHA
         goto err;
     }
 
-    ossl_quic_channel_get_peer_addr(ch, &qc->init_peer_addr); /* best effort */
     qc->pending                 = 1;
     qc->engine                  = ql->engine;
     qc->port                    = ql->port;
@@ -5000,6 +4999,22 @@ size_t ossl_quic_get_accept_connection_queue_len(SSL *ssl)
     return ret;
 }
 
+QUIC_TAKES_LOCK
+int ossl_quic_get_peer_addr(SSL *ssl, BIO_ADDR *peer_addr)
+{
+    QCTX ctx;
+    int ret;
+
+    if (!expect_quic_cs(ssl, &ctx))
+        return 0;
+
+    qctx_lock(&ctx);
+    ret = ossl_quic_channel_get_peer_addr(ctx.qc->ch, peer_addr);
+    qctx_unlock(&ctx);
+
+    return ret;
+}
+
 /*
  * QUIC Front-End I/O API: Domains
  * ===============================
index 7cad535c5c73669eb73d7468ed1f06061b22ff97..cc646f51fef4812d70008062d7762ab1cd70adcc 100644 (file)
@@ -519,7 +519,7 @@ static QUIC_CHANNEL *port_make_channel(QUIC_PORT *port, SSL *tls, OSSL_QRX *qrx,
     args.is_tserver_ch = is_tserver;
 
     /*
-     * Creating a new channel is made a bit tricky here as there is a
+     * Creating a new channel is made a bit tricky here as there is a
      * bit of a circular dependency.  Initializing a channel requires that
      * the ch->tls and optionally the qlog_title be configured prior to
      * initialization, but we need the channel at least partially configured
@@ -740,6 +740,9 @@ static void port_bind_channel(QUIC_PORT *port, const BIO_ADDR *peer,
     if (port->tserver_ch != NULL) {
         ch = port->tserver_ch;
         port->tserver_ch = NULL;
+        if (peer != NULL && BIO_ADDR_family(peer) != AF_UNSPEC)
+            ossl_quic_channel_set_peer_addr(ch, peer);
+
         ossl_quic_channel_bind_qrx(ch, qrx);
         ossl_qrx_set_msg_callback(ch->qrx, ch->msg_callback,
                                   ch->msg_callback_ssl);
index 8660824f1d0988770ee8217a947b9a59f898769b..d0aff1942d1f01fcc88a740b1d7dda976fc47ba1 100644 (file)
@@ -8105,6 +8105,18 @@ size_t SSL_get_accept_connection_queue_len(SSL *ssl)
 #endif
 }
 
+int SSL_get_peer_addr(SSL *ssl, BIO_ADDR *peer_addr)
+{
+#ifndef OPENSSL_NO_QUIC
+    if (!IS_QUIC(ssl))
+        return 0;
+
+    return ossl_quic_get_peer_addr(ssl, peer_addr);
+#else
+    return 0;
+#endif
+}
+
 int SSL_listen(SSL *ssl)
 {
 #ifndef OPENSSL_NO_QUIC
index fa0185c6b86254bce4284a3b70cc362b5123b835..c0fcb4411e3a07a4e1c7d92f2cc8d37d04086bc9 100644 (file)
@@ -2998,6 +2998,187 @@ err:
 #endif
 }
 
+/* family = AF_INET (alen=4) or AF_INET6 (alen=16) */
+static int create_quic_ssl_objects_seed_peer(SSL_CTX *sctx, SSL_CTX *cctx,
+                                             SSL **lssl, SSL **cssl,
+                                             int family,
+                                             const unsigned char *srv_ip, uint16_t srv_port,
+                                             const unsigned char *cli_ip, uint16_t cli_port)
+{
+    BIO *cbio = NULL, *sbio = NULL;
+    BIO_ADDR *srv_local = NULL, *cli_local = NULL;
+    BIO_ADDR *srv_peer  = NULL, *srv_peer2 = NULL;
+    size_t alen = (family == AF_INET) ? 4 : 16;
+    int ret = 0;
+
+    *cssl = *lssl = NULL;
+
+    if (!TEST_true(BIO_new_bio_dgram_pair(&cbio, 0, &sbio, 0)))
+        goto err;
+
+    /* server local bind (in-memory dgram pair metadata) */
+    if (!TEST_ptr(srv_local = BIO_ADDR_new())
+        || !TEST_true(BIO_ADDR_rawmake(srv_local, family, srv_ip, alen, htons(srv_port)))
+        || !TEST_true(bio_addr_bind(sbio, srv_local)))
+        goto err;
+    srv_local = NULL; /* set0 consumed */
+
+    /* seed peer on the BIO we give the listener (so port's net BIO sees it) */
+    if (!TEST_ptr(srv_peer = BIO_ADDR_new())
+        || !TEST_true(BIO_ADDR_rawmake(srv_peer, family, cli_ip, alen, htons(cli_port))))
+        goto err;
+    (void)BIO_ctrl(sbio, BIO_CTRL_DGRAM_SET_PEER, 0, srv_peer);
+    BIO_ADDR_free(srv_peer);
+    srv_peer = NULL;
+
+    /* create listener */
+    if (!TEST_ptr(*lssl = ql_create(sctx, sbio)))
+        goto err;
+    sbio = NULL;
+
+    /* also seed on the listener's current wbio (covers wrapping) */
+    if (!TEST_ptr(srv_peer2 = BIO_ADDR_new())
+        || !TEST_true(BIO_ADDR_rawmake(srv_peer2, family, cli_ip, alen, htons(cli_port))))
+        goto err;
+    (void)BIO_ctrl(SSL_get_wbio(*lssl), BIO_CTRL_DGRAM_SET_PEER, 0, srv_peer2);
+    BIO_ADDR_free(srv_peer2);
+    srv_peer2 = NULL;
+
+    /* client object + local bind */
+    if (!TEST_ptr(*cssl = SSL_new(cctx)))
+        goto err;
+
+    if (!TEST_ptr(cli_local = BIO_ADDR_new())
+        || !TEST_true(BIO_ADDR_rawmake(cli_local, family, cli_ip, alen, htons(cli_port)))
+        || !TEST_true(bio_addr_bind(cbio, cli_local)))
+        goto err;
+    cli_local = NULL; /* consumed */
+
+    /* qc_init needs server addr (fresh copy) */
+    if (!TEST_ptr(srv_peer = BIO_ADDR_new())
+        || !TEST_true(BIO_ADDR_rawmake(srv_peer, family, srv_ip, alen, htons(srv_port)))
+        || !TEST_true(qc_init(*cssl, srv_peer)))
+        goto err;
+    BIO_ADDR_free(srv_peer);
+    srv_peer = NULL;
+
+    SSL_set_bio(*cssl, cbio, cbio);
+    cbio = NULL;
+
+    ret = 1;
+
+err:
+    if (!ret) {
+        SSL_free(*cssl);
+        SSL_free(*lssl);
+        *cssl = *lssl = NULL;
+    }
+    BIO_free(cbio);
+    BIO_free(sbio);
+    BIO_ADDR_free(srv_local);
+    BIO_ADDR_free(cli_local);
+    BIO_ADDR_free(srv_peer);
+    BIO_ADDR_free(srv_peer2);
+
+    return ret;
+}
+
+/* Parameterized test: family=AF_INET/AF_INET6, e.g. "127.0.0.1" / "::1" */
+static int test_quic_peer_addr_common(int family,
+                                      const char *srv_str, uint16_t srv_port,
+                                      const char *cli_str, uint16_t cli_port)
+{
+    SSL_CTX *sctx = NULL, *cctx = NULL;
+    SSL *qlistener = NULL, *clientssl = NULL, *serverssl = NULL;
+    BIO_ADDR *got = NULL;
+    unsigned char srv_ip[16], cli_ip[16], raw[16];
+    size_t alen = (family == AF_INET) ? 4 : 16, rawlen = 0;
+    int ret, ok = 0;
+
+#if defined(_WIN32)
+    /* OpenSSL's tests usually call BIO_sock_init() elsewhere; safe to call here too */
+    BIO_sock_init();
+#endif
+
+    if (!TEST_int_eq(inet_pton(family, srv_str, srv_ip), 1)
+        || !TEST_int_eq(inet_pton(family, cli_str, cli_ip), 1))
+        goto err;
+
+    if (!TEST_ptr(sctx = create_server_ctx())
+        || !TEST_ptr(cctx = create_client_ctx()))
+        goto err;
+
+    if (!TEST_true(create_quic_ssl_objects_seed_peer(sctx, cctx,
+                                                     &qlistener, &clientssl,
+                                                     family,
+                                                     srv_ip, srv_port,
+                                                     cli_ip, cli_port)))
+        goto err;
+
+    /* Minimal QUIC progress */
+    ret = SSL_connect(clientssl);
+    if (!TEST_true(ret <= 0))
+        goto err;
+    SSL_handle_events(qlistener);
+
+    ret = SSL_connect(clientssl);
+    if (!TEST_true(ret <= 0))
+        goto err;
+    SSL_handle_events(qlistener);
+
+    /* Accept connection (pre-handshake) */
+    if (!TEST_ptr(serverssl = SSL_accept_connection(qlistener, 0)))
+        goto err;
+
+    /* Server sees client */
+    if (!TEST_ptr(got = BIO_ADDR_new()))
+        goto err;
+    BIO_ADDR_clear(got);
+
+    if (!TEST_int_eq(SSL_get_peer_addr(serverssl, got), 1)
+        || !TEST_int_eq(BIO_ADDR_family(got), family)
+        || !TEST_true(BIO_ADDR_rawaddress(got, raw, &rawlen))
+        || !TEST_size_t_eq(rawlen, alen)
+        || !TEST_mem_eq(raw, rawlen, cli_ip, alen)
+        || !TEST_int_eq((int)ntohs(BIO_ADDR_rawport(got)), (int)cli_port))
+        goto err;
+
+    /* Client sees server */
+    BIO_ADDR_clear(got);
+    if (!TEST_int_eq(SSL_get_peer_addr(clientssl, got), 1)
+        || !TEST_int_eq(BIO_ADDR_family(got), family)
+        || !TEST_true(BIO_ADDR_rawaddress(got, raw, &rawlen))
+        || !TEST_size_t_eq(rawlen, alen)
+        || !TEST_mem_eq(raw, rawlen, srv_ip, alen)
+        || !TEST_int_eq((int)ntohs(BIO_ADDR_rawport(got)), (int)srv_port))
+        goto err;
+
+    ok = 1;
+
+err:
+    BIO_ADDR_free(got);
+    SSL_free(serverssl);
+    SSL_free(clientssl);
+    SSL_free(qlistener);
+    SSL_CTX_free(cctx);
+    SSL_CTX_free(sctx);
+    return ok;
+}
+
+static int test_quic_peer_addr_v4(void)
+{
+    return test_quic_peer_addr_common(AF_INET,
+                                      "127.0.0.1", 4433,
+                                      "127.0.0.2", 4434);
+}
+
+static int test_quic_peer_addr_v6(void)
+{
+    return test_quic_peer_addr_common(AF_INET6,
+                                      "::1", 4433,
+                                      "::2", 4434);
+}
+
 /***********************************************************************************/
 OPT_TEST_DECLARE_USAGE("provider config certsdir datadir\n")
 
@@ -3101,6 +3282,9 @@ int setup_tests(void)
     ADD_TEST(test_ssl_set_verify);
     ADD_TEST(test_accept_stream);
     ADD_TEST(test_client_hello_retry);
+    ADD_TEST(test_quic_peer_addr_v6);
+    ADD_TEST(test_quic_peer_addr_v4);
+
     return 1;
  err:
     cleanup_tests();
index 54c465160aed8b8af3a5b8a656879301f63d93cf..91a7a53c47aed4b63d9f07043a05b8f2d38899fc 100644 (file)
@@ -606,3 +606,4 @@ SSL_CTX_set_domain_flags                ?   4_0_0   EXIST::FUNCTION:
 SSL_CTX_get_domain_flags                ?      4_0_0   EXIST::FUNCTION:
 SSL_get_domain_flags                    ?      4_0_0   EXIST::FUNCTION:
 SSL_CTX_set_new_pending_conn_cb         ?      4_0_0   EXIST::FUNCTION:
+SSL_get_peer_addr                       ?      4_0_0   EXIST::FUNCTION: