--- /dev/null
+=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
#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")
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();