This implements the functionality of getsockname(2) and getpeername(2) under a single operation. Signed-off-by: Gabriel Krisman Bertazi --- src/include/liburing.h | 13 +++++++++++++ src/include/liburing/io_uring.h | 1 + 2 files changed, 14 insertions(+) diff --git a/src/include/liburing.h b/src/include/liburing.h index 83819eb7..1626f3bb 100644 --- a/src/include/liburing.h +++ b/src/include/liburing.h @@ -1572,6 +1572,19 @@ 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_uring_cmd(sqe, SOCKET_URING_OP_GETSOCKNAME, fd); + + sqe->addr = (uintptr_t) sockaddr; + 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 a54e5b42..8e8b8e6a 100644 --- a/src/include/liburing/io_uring.h +++ b/src/include/liburing/io_uring.h @@ -966,6 +966,7 @@ enum io_uring_socket_op { SOCKET_URING_OP_GETSOCKOPT, SOCKET_URING_OP_SETSOCKOPT, SOCKET_URING_OP_TX_TIMESTAMP, + SOCKET_URING_OP_GETSOCKNAME, }; /* -- 2.52.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. To avoid regressing old systems, bite the bullet and do the syscall version for older kernels, fixing the test there as well. Signed-off-by: Gabriel Krisman Bertazi --- since v2: - don't fail on older kernels --- test/bind-listen.c | 89 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 10 deletions(-) diff --git a/test/bind-listen.c b/test/bind-listen.c index 6f80f177..a468aa94 100644 --- a/test/bind-listen.c +++ b/test/bind-listen.c @@ -22,7 +22,7 @@ static void msec_to_ts(struct __kernel_timespec *ts, unsigned int msec) } static const char *magic = "Hello World!"; -static int use_port = 8000; +static bool no_getsockname = false; enum { SRV_INDEX = 0, @@ -74,18 +74,82 @@ 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) +/* + * getsockname was added to the kernel a few releases after bind/listen. + * In order to provide a backward-compatible test, fallback to + * non-io-uring if we are on an older kernel, allowing the test to + * continue. + */ +static int do_getsockname(struct io_uring *ring, int direct_socket, + int peer, struct sockaddr *saddr, + socklen_t *saddr_len) +{ + struct io_uring_sqe *sqe; + struct io_uring_cqe *cqe; + int res, fd; + + if (!no_getsockname) { + /* attempt io_uring. Commmand might not exist */ + sqe = io_uring_get_sqe(ring); + io_uring_prep_cmd_getsockname(sqe, direct_socket, + saddr, saddr_len, peer); + sqe->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; + io_uring_submit(ring); + io_uring_wait_cqe(ring, &cqe); + res = cqe->res; + io_uring_cqe_seen(ring, cqe); + } + + if (no_getsockname || res == -ENOTSUP) { + /* + * Older kernel. install the fd and use the getsockname + * syscall. + */ + no_getsockname = true; + + sqe = io_uring_get_sqe(ring); + io_uring_prep_fixed_fd_install(sqe, direct_socket, 0); + io_uring_submit(ring); + io_uring_wait_cqe(ring, &cqe); + fd = cqe->res; + io_uring_cqe_seen(ring, cqe); + + if (fd < 0) { + fprintf(stderr, "installing direct fd failed. %d\n", + cqe->res); + return T_EXIT_FAIL; + } + if (peer) + res = getpeername(fd, saddr, saddr_len); + else + res = getsockname(fd, saddr, saddr_len); + + if (res) { + fprintf(stderr, "get%sname syscall failed. %d\n", + peer? "peer":"sock", errno); + return T_EXIT_FAIL; + } + close(fd); + } else if (res < 0) { + fprintf(stderr, "getsockname server failed. %d\n", cqe->res); + return T_EXIT_FAIL; + } + return 0; +} + +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 +162,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 +196,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 +220,17 @@ 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) { + if (do_getsockname(&ring, SRV_INDEX, 0, (struct sockaddr*) &saddr, + &saddr_len)) + return T_EXIT_FAIL; + + if (connect_client(&ring, saddr.sin_port) != T_SETUP_OK) { fprintf(stderr, "cli startup failed.\n"); return T_SETUP_SKIP; } -- 2.52.0 Signed-off-by: Gabriel Krisman Bertazi --- test/bind-listen.c | 99 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/test/bind-listen.c b/test/bind-listen.c index a468aa94..38200879 100644 --- a/test/bind-listen.c +++ b/test/bind-listen.c @@ -202,7 +202,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]; @@ -235,7 +235,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; @@ -248,6 +248,22 @@ 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. + */ + port = saddr.sin_port; + saddr.sin_port = 0; + if (do_getsockname(&ring, CLI_INDEX, 1, + (struct sockaddr*)&saddr, &saddr_len)) + return T_EXIT_FAIL; + + 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; + } + sqe = io_uring_get_sqe(&ring); io_uring_prep_recv(sqe, CONN_INDEX, buf, sizeof(buf), 0); sqe->flags |= IOSQE_FIXED_FILE; @@ -424,6 +440,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; @@ -472,6 +559,12 @@ int main(int argc, char *argv[]) fprintf(stderr, "bad listen failed\n"); return T_EXIT_FAIL; } - + if (!no_getsockname) { + ret = test_bad_sockname(); + if (ret) { + fprintf(stderr, "bad sockname failed\n"); + return T_EXIT_FAIL; + } + } return T_EXIT_PASS; } -- 2.52.0 Signed-off-by: Gabriel Krisman Bertazi --- since v2: - Fix Jens comments --- man/io_uring_prep_getsockname.3 | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 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..de78cb40 --- /dev/null +++ b/man/io_uring_prep_getsockname.3 @@ -0,0 +1,78 @@ +.\" Copyright (C) 2024 SUSE LLC. +.\" +.\" SPDX-License-Identifier: LGPL-2.0-or-later +.\" +.TH io_uring_prep_getsockname 3 "Dec 3, 2025" "liburing-2.13" "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 +.B 0 +or the peer address if +.IR peer +is set to +.BR 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.52.0