This implements the functionality of getsockname(2) and getpeername(2) under a single operation. Signed-off-by: Gabriel Krisman Bertazi --- src/include/liburing.h | 12 ++++++++++++ src/include/liburing/io_uring.h | 1 + 2 files changed, 13 insertions(+) diff --git a/src/include/liburing.h b/src/include/liburing.h index 83819eb7..77b0a135 100644 --- a/src/include/liburing.h +++ b/src/include/liburing.h @@ -1572,6 +1572,18 @@ IOURINGINLINE void io_uring_prep_cmd_sock(struct io_uring_sqe *sqe, sqe->level = level; } +IOURINGINLINE void io_uring_prep_cmd_getsockname(struct io_uring_sqe *sqe, + int fd, struct sockaddr *sockaddr, + socklen_t *sockaddr_len, + int peer) + LIBURING_NOEXCEPT +{ + io_uring_prep_rw(IORING_OP_URING_CMD, sqe, fd, sockaddr, 0, 0); + sqe->cmd_op = SOCKET_URING_OP_GETSOCKNAME; + sqe->addr3 = (unsigned long) (uintptr_t) sockaddr_len; + sqe->optlen = peer; +} + IOURINGINLINE void io_uring_prep_waitid(struct io_uring_sqe *sqe, idtype_t idtype, id_t id, diff --git a/src/include/liburing/io_uring.h b/src/include/liburing/io_uring.h index 44ce8229..365f0584 100644 --- a/src/include/liburing/io_uring.h +++ b/src/include/liburing/io_uring.h @@ -950,6 +950,7 @@ enum io_uring_socket_op { SOCKET_URING_OP_GETSOCKOPT, SOCKET_URING_OP_SETSOCKOPT, SOCKET_URING_OP_TX_TIMESTAMP, + SOCKET_URING_OP_GETSOCKNAME, }; /* -- 2.51.0 This test fails if port 8000 is already in use by something else. Now that we have getsockname with direct file descriptors, use an ephemeral port instead. Signed-off-by: Gabriel Krisman Bertazi --- test/bind-listen.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/test/bind-listen.c b/test/bind-listen.c index 6f80f177..7c229a17 100644 --- a/test/bind-listen.c +++ b/test/bind-listen.c @@ -22,7 +22,6 @@ static void msec_to_ts(struct __kernel_timespec *ts, unsigned int msec) } static const char *magic = "Hello World!"; -static int use_port = 8000; enum { SRV_INDEX = 0, @@ -74,18 +73,19 @@ static int connect_client(struct io_uring *ring, unsigned short peer_port) return T_SETUP_OK; } -static int setup_srv(struct io_uring *ring, struct sockaddr_in *server_addr) +static int setup_srv(struct io_uring *ring) { + struct sockaddr_in server_addr; struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; struct __kernel_timespec ts; int ret, val, submitted; unsigned head; - memset(server_addr, 0, sizeof(struct sockaddr_in)); - server_addr->sin_family = AF_INET; - server_addr->sin_port = htons(use_port++); - server_addr->sin_addr.s_addr = htons(INADDR_ANY); + memset(&server_addr, 0, sizeof(struct sockaddr_in)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(0); + server_addr.sin_addr.s_addr = htons(INADDR_ANY); sqe = io_uring_get_sqe(ring); io_uring_prep_socket_direct(sqe, AF_INET, SOCK_STREAM, 0, SRV_INDEX, 0); @@ -98,7 +98,7 @@ static int setup_srv(struct io_uring *ring, struct sockaddr_in *server_addr) sqe->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; sqe = io_uring_get_sqe(ring); - io_uring_prep_bind(sqe, SRV_INDEX, (struct sockaddr *) server_addr, + io_uring_prep_bind(sqe, SRV_INDEX, (struct sockaddr *) &server_addr, sizeof(struct sockaddr_in)); sqe->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; @@ -132,7 +132,8 @@ static int setup_srv(struct io_uring *ring, struct sockaddr_in *server_addr) static int test_good_server(unsigned int ring_flags) { - struct sockaddr_in server_addr; + struct sockaddr_in saddr = {}; + socklen_t saddr_len = sizeof(saddr); struct __kernel_timespec ts; struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; @@ -155,13 +156,25 @@ static int test_good_server(unsigned int ring_flags) return T_SETUP_SKIP; } - ret = setup_srv(&ring, &server_addr); + ret = setup_srv(&ring); if (ret != T_SETUP_OK) { fprintf(stderr, "srv startup failed.\n"); return T_EXIT_FAIL; } - if (connect_client(&ring, server_addr.sin_port) != T_SETUP_OK) { + sqe = io_uring_get_sqe(&ring); + io_uring_prep_cmd_getsockname(sqe, SRV_INDEX, (struct sockaddr*)&saddr, + &saddr_len, 0); + sqe->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; + io_uring_submit(&ring); + io_uring_wait_cqe(&ring, &cqe); + if (cqe->res < 0) { + fprintf(stderr, "getsockname server failed. %d\n", cqe->res); + return T_EXIT_FAIL; + } + io_uring_cqe_seen(&ring, cqe); + + if (connect_client(&ring, saddr.sin_port) != T_SETUP_OK) { fprintf(stderr, "cli startup failed.\n"); return T_SETUP_SKIP; } -- 2.51.0 Signed-off-by: Gabriel Krisman Bertazi --- test/bind-listen.c | 104 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 2 deletions(-) diff --git a/test/bind-listen.c b/test/bind-listen.c index 7c229a17..22dd9a32 100644 --- a/test/bind-listen.c +++ b/test/bind-listen.c @@ -138,7 +138,7 @@ static int test_good_server(unsigned int ring_flags) struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; struct io_uring ring; - int ret; + int ret, port; int fds[3]; char buf[1024]; @@ -179,7 +179,7 @@ static int test_good_server(unsigned int ring_flags) return T_SETUP_SKIP; } - /* Wait for a request */ + /* Wait for a connection */ sqe = io_uring_get_sqe(&ring); io_uring_prep_accept_direct(sqe, SRV_INDEX, NULL, NULL, 0, CONN_INDEX); sqe->flags |= IOSQE_FIXED_FILE; @@ -192,6 +192,30 @@ static int test_good_server(unsigned int ring_flags) } io_uring_cqe_seen(&ring, cqe); + /* Test that getsockname on the peer (getpeername) yields a + * sane result. + */ + sqe = io_uring_get_sqe(&ring); + saddr_len = sizeof(saddr); + port = saddr.sin_port; + io_uring_prep_cmd_getsockname(sqe, CONN_INDEX, (struct sockaddr*)&saddr, + &saddr_len, 1); + sqe->flags |= IOSQE_FIXED_FILE; + io_uring_submit(&ring); + io_uring_wait_cqe(&ring, &cqe); + if (cqe->res < 0) { + fprintf(stderr, "getsockname client failed. %d\n", cqe->res); + return T_EXIT_FAIL; + } else { + if (saddr.sin_addr.s_addr != htonl(INADDR_LOOPBACK) || + saddr.sin_port != port) { + fprintf(stderr, "getsockname peer got wrong address: %s:%d\n", + inet_ntoa(saddr.sin_addr), saddr.sin_port); + return T_EXIT_FAIL; + } + } + io_uring_cqe_seen(&ring, cqe); + sqe = io_uring_get_sqe(&ring); io_uring_prep_recv(sqe, CONN_INDEX, buf, sizeof(buf), 0); sqe->flags |= IOSQE_FIXED_FILE; @@ -368,6 +392,77 @@ fail: return ret; } +static int test_bad_sockname(void) +{ + struct sockaddr_in saddr; + socklen_t saddr_len; + struct io_uring_sqe *sqe; + struct io_uring_cqe *cqe; + struct io_uring ring; + int sock = -1, err; + int ret = T_EXIT_FAIL; + + memset(&saddr, 0, sizeof(struct sockaddr_in)); + saddr.sin_family = AF_INET; + saddr.sin_port = htons(8001); + saddr.sin_addr.s_addr = htons(INADDR_ANY); + + err = t_create_ring(1, &ring, 0); + if (err < 0) { + fprintf(stderr, "queue_init: %d\n", err); + return T_SETUP_SKIP; + } + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket"); + goto fail; + } + + err = t_bind_ephemeral_port(sock, &saddr); + if (err) { + fprintf(stderr, "bind: %s\n", strerror(-err)); + goto fail; + } + + /* getsockname on a !socket fd. with getsockname(2), this would + * return -ENOTSOCK, but we can't do it in an io_uring_cmd. + */ + sqe = io_uring_get_sqe(&ring); + saddr_len = sizeof(saddr); + io_uring_prep_cmd_getsockname(sqe, 1, (struct sockaddr*)&saddr, &saddr_len, 0); + err = io_uring_submit(&ring); + if (err < 0) + goto fail; + err = io_uring_wait_cqe(&ring, &cqe); + if (err) + goto fail; + if (cqe->res != -ENOTSUP) + goto fail; + io_uring_cqe_seen(&ring, cqe); + + /* getsockname with weird parameters */ + sqe = io_uring_get_sqe(&ring); + io_uring_prep_cmd_getsockname(sqe, sock, (struct sockaddr*)&saddr, + &saddr_len, 3); + err = io_uring_submit(&ring); + if (err < 0) + goto fail; + err = io_uring_wait_cqe(&ring, &cqe); + if (err) + goto fail; + if (cqe->res != -EINVAL) + goto fail; + io_uring_cqe_seen(&ring, cqe); + + ret = T_EXIT_PASS; +fail: + io_uring_queue_exit(&ring); + if (sock != -1) + close(sock); + return ret; +} + int main(int argc, char *argv[]) { struct io_uring_probe *probe; @@ -417,5 +512,10 @@ int main(int argc, char *argv[]) return T_EXIT_FAIL; } + ret = test_bad_sockname(); + if (ret) { + fprintf(stderr, "bad sockname failed\n"); + return T_EXIT_FAIL; + } return T_EXIT_PASS; } -- 2.51.0 Signed-off-by: Gabriel Krisman Bertazi --- man/io_uring_prep_getsockname.3 | 76 +++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 man/io_uring_prep_getsockname.3 diff --git a/man/io_uring_prep_getsockname.3 b/man/io_uring_prep_getsockname.3 new file mode 100644 index 00000000..71e65f1d --- /dev/null +++ b/man/io_uring_prep_getsockname.3 @@ -0,0 +1,76 @@ +.\" Copyright (C) 2024 SUSE LLC. +.\" +.\" SPDX-License-Identifier: LGPL-2.0-or-later +.\" +.TH io_uring_prep_getsockname 3 "Oct 23, 2025" "liburing-2.11" "liburing Manual" +.SH NAME +io_uring_prep_getsockname \- prepare a getsockname or getpeername request +.SH SYNOPSIS +.nf +.B #include +.B #include +.PP +.BI "void io_uring_prep_getsockname(struct io_uring_sqe *" sqe "," +.BI " int " sockfd "," +.BI " struct sockaddr *" sockaddr "," +.BI " socklen_t *" sockaddr_len "," +.BI " int " peer ");" +.fi +.SH DESCRIPTION +The +.BR io_uring_prep_getsockname (3) +function prepares a getsockname/getpeername request. +The submission queue entry +.I sqe +is setup to fetch the locally bound address or peer address of the socket +file descriptor pointed by +.IR sockfd. +The parameter +.IR sockaddr +points to a region of size +.IR sockaddr_len +where the output is written. +.IR sockaddr_len +is modified by the kernel to indicate how many bytes were written. +The output address is the locally bound address if +.IR peer +is set to 0 +or the peer address if +.IR peer +is set to 1. + +This function prepares an async +.BR getsockname (2) +or +.BR getpeername (2) +request. See those man pages for details. + +.SH RETURN VALUE +None +.SH ERRORS +The CQE +.I res +field will contain the result of the operation. See the related man page for +details on possible values. Note that where synchronous system calls will return +.B -1 +on failure and set +.I errno +to the actual error value, io_uring never uses +.IR errno . +Instead it returns the negated +.I errno +directly in the CQE +.I res +field. +.BR +Differently from the equivalent system calls, if the user attempts to +use this operation on a non-socket file descriptor, the CQE error result +is +.IR ENOTSUP +instead of +.IR ENOSOCK. +.SH SEE ALSO +.BR io_uring_get_sqe (3), +.BR io_uring_submit (3), +.BR io_uring_prep_getsockname (2) +.BR io_uring_prep_getpeername (2) -- 2.51.0