Add SSL_get_peer_addr() function to query peer address for QUIC

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)
This commit is contained in:
Gustaf Neumann 2025-09-28 15:03:32 +02:00 committed by Tomas Mraz
parent 2b97f4d300
commit beec4e146a
11 changed files with 287 additions and 3 deletions

View 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 \

View File

@ -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

View 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

View 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,

View 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);

View 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;

View 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
* ===============================

View 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 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);

View 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

View 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();

View 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: