From beec4e146a9ec755c83d6521a428ea103bdc8607 Mon Sep 17 00:00:00 2001 From: Gustaf Neumann Date: Sun, 28 Sep 2025 15:03:32 +0200 Subject: [PATCH] Add SSL_get_peer_addr() function to query peer address for QUIC MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Saša Nedvědický Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/28690) --- doc/build.info | 6 ++ doc/man3/SSL_get_peer_addr.pod | 57 ++++++++++ doc/man7/openssl-quic.pod | 4 + include/internal/quic_ssl.h | 1 + include/openssl/ssl.h.in | 1 + ssl/quic/quic_channel.c | 2 +- ssl/quic/quic_impl.c | 17 ++- ssl/quic/quic_port.c | 5 +- ssl/ssl_lib.c | 12 +++ test/quicapitest.c | 184 +++++++++++++++++++++++++++++++++ util/libssl.num | 1 + 11 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 doc/man3/SSL_get_peer_addr.pod diff --git a/doc/build.info b/doc/build.info index a91a5c57983..091c8e44ce7 100644 --- a/doc/build.info +++ b/doc/build.info @@ -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 index 00000000000..97362e2d863 --- /dev/null +++ b/doc/man3/SSL_get_peer_addr.pod @@ -0,0 +1,57 @@ +=pod + +=head1 NAME + +SSL_get_peer_addr - obtain the peer address of a QUIC connection + +=head1 SYNOPSIS + + #include + + 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 structure pointed to by I. The caller must +supply a valid B 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 type is an OpenSSL abstraction which can represent +both IPv4 and IPv6 socket addresses. Applications can get the address +family via the L 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 is left +unchanged. + +=head1 SEE ALSO + +L, L, L + +=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. + +=cut diff --git a/doc/man7/openssl-quic.pod b/doc/man7/openssl-quic.pod index 35661e15b7c..75ca85d7ed9 100644 --- a/doc/man7/openssl-quic.pod +++ b/doc/man7/openssl-quic.pod @@ -710,6 +710,10 @@ or allow them to be handled using L. Used to configure or disable default stream mode; see the MODES OF OPERATION section for details. +=item L + +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 diff --git a/include/internal/quic_ssl.h b/include/internal/quic_ssl.h index e7fd260ab60..0df73c22003 100644 --- a/include/internal/quic_ssl.h +++ b/include/internal/quic_ssl.h @@ -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, diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in index 1cc77189023..cb30dda92d2 100644 --- a/include/openssl/ssl.h.in +++ b/include/openssl/ssl.h.in @@ -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); diff --git a/ssl/quic/quic_channel.c b/ssl/quic/quic_channel.c index 61e54570b1c..7679ec8790e 100644 --- a/ssl/quic/quic_channel.c +++ b/ssl/quic/quic_channel.c @@ -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; diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c index e5f5eea7b45..60e8081e104 100644 --- a/ssl/quic/quic_impl.c +++ b/ssl/quic/quic_impl.c @@ -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 * =============================== diff --git a/ssl/quic/quic_port.c b/ssl/quic/quic_port.c index 7cad535c5c7..cc646f51fef 100644 --- a/ssl/quic/quic_port.c +++ b/ssl/quic/quic_port.c @@ -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 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); diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 8660824f1d0..d0aff1942d1 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -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 diff --git a/test/quicapitest.c b/test/quicapitest.c index fa0185c6b86..c0fcb4411e3 100644 --- a/test/quicapitest.c +++ b/test/quicapitest.c @@ -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(); diff --git a/util/libssl.num b/util/libssl.num index 54c465160ae..91a7a53c47a 100644 --- a/util/libssl.num +++ b/util/libssl.num @@ -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: -- 2.47.3