| /* SPDX-License-Identifier: MIT */ |
| /* |
| * Check that CMD operations on sockets are consistent. |
| */ |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <assert.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <linux/sockios.h> |
| #include <sys/ioctl.h> |
| |
| #include "liburing.h" |
| #include "helpers.h" |
| |
| #define USERDATA 0x1234 |
| #define MSG "foobarbaz" |
| |
| static int no_io_cmd; |
| |
| struct fds { |
| int tx; |
| int rx; |
| }; |
| |
| /* Create 2 sockets (tx, rx) given the socket type */ |
| static struct fds create_sockets(bool stream) |
| { |
| struct fds retval; |
| int fd[2]; |
| |
| t_create_socket_pair(fd, stream); |
| |
| retval.tx = fd[0]; |
| retval.rx = fd[1]; |
| |
| return retval; |
| } |
| |
| static int create_sqe_and_submit(struct io_uring *ring, int32_t fd, int op) |
| { |
| struct io_uring_sqe *sqe; |
| int ret; |
| |
| assert(fd > 0); |
| sqe = io_uring_get_sqe(ring); |
| assert(sqe != NULL); |
| |
| io_uring_prep_cmd_sock(sqe, op, fd, 0, 0, NULL, 0); |
| sqe->user_data = USERDATA; |
| |
| /* Submitting SQE */ |
| ret = io_uring_submit_and_wait(ring, 1); |
| if (ret <= 0) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int receive_cqe(struct io_uring *ring) |
| { |
| struct io_uring_cqe *cqe; |
| int err; |
| |
| err = io_uring_wait_cqe(ring, &cqe); |
| assert(err == 0); |
| assert(cqe->user_data == USERDATA); |
| err = cqe->res; |
| io_uring_cqe_seen(ring, cqe); |
| |
| /* Return the result of the operation */ |
| return err; |
| } |
| |
| static ssize_t send_data(struct fds *s, char *str) |
| { |
| size_t written_bytes; |
| |
| written_bytes = write(s->tx, str, strlen(str)); |
| assert(written_bytes == strlen(MSG)); |
| |
| return written_bytes; |
| } |
| |
| static int run_test(bool stream) |
| { |
| struct fds sockfds; |
| ssize_t bytes_in, bytes_out; |
| struct io_uring ring; |
| size_t written_bytes; |
| int error; |
| |
| /* Create three sockets */ |
| sockfds = create_sockets(stream); |
| assert(sockfds.tx > 0); |
| assert(sockfds.rx > 0); |
| /* Send data sing the sockfds->send */ |
| written_bytes = send_data(&sockfds, MSG); |
| |
| /* Simply io_uring ring creation */ |
| error = t_create_ring(1, &ring, 0); |
| if (error == T_SETUP_SKIP) |
| return error; |
| else if (error != T_SETUP_OK) |
| return T_EXIT_FAIL; |
| |
| error = create_sqe_and_submit(&ring, sockfds.rx, |
| SOCKET_URING_OP_SIOCINQ); |
| if (error) |
| return T_EXIT_FAIL; |
| bytes_in = receive_cqe(&ring); |
| if (bytes_in < 0) { |
| if (bytes_in == -EINVAL || bytes_in == -EOPNOTSUPP) { |
| no_io_cmd = 1; |
| return T_EXIT_SKIP; |
| } |
| fprintf(stderr, "Bad return value %ld\n", (long) bytes_in); |
| return T_EXIT_FAIL; |
| } |
| |
| error = create_sqe_and_submit(&ring, sockfds.tx, |
| SOCKET_URING_OP_SIOCOUTQ); |
| if (error) |
| return T_EXIT_FAIL; |
| |
| bytes_out = receive_cqe(&ring); |
| if (bytes_in == -ENOTSUP || bytes_out == -ENOTSUP) { |
| fprintf(stderr, "Skipping tests. -ENOTSUP returned\n"); |
| return T_EXIT_SKIP; |
| } |
| |
| /* |
| * Assert the number of written bytes are either in the socket buffer |
| * or on the receive side |
| */ |
| if (bytes_in + bytes_out != written_bytes) { |
| fprintf(stderr, "values does not match: %zu+%zu != %zu\n", |
| bytes_in, bytes_out, written_bytes); |
| return T_EXIT_FAIL; |
| } |
| |
| io_uring_queue_exit(&ring); |
| |
| return T_EXIT_PASS; |
| } |
| |
| /* |
| * Make sure that siocoutq and siocinq returns the same value |
| * using ioctl(2) and uring commands for raw sockets |
| */ |
| static int run_test_raw(void) |
| { |
| int ioctl_siocoutq, ioctl_siocinq; |
| int uring_siocoutq, uring_siocinq; |
| struct io_uring ring; |
| int retry = 0, sock, error; |
| |
| sock = socket(PF_INET, SOCK_RAW, IPPROTO_TCP); |
| if (sock == -1) { |
| /* You need root to create raw socket */ |
| perror("Not able to create a raw socket"); |
| return T_EXIT_SKIP; |
| } |
| |
| /* Get the same operation using uring cmd */ |
| error = t_create_ring(1, &ring, 0); |
| if (error == T_SETUP_SKIP) |
| return error; |
| else if (error != T_SETUP_OK) |
| return T_EXIT_FAIL; |
| |
| again: |
| /* Simple SIOCOUTQ using ioctl */ |
| error = ioctl(sock, SIOCOUTQ, &ioctl_siocoutq); |
| if (error < 0) { |
| fprintf(stderr, "Failed to run ioctl(SIOCOUTQ): %d\n", error); |
| return T_EXIT_FAIL; |
| } |
| |
| error = ioctl(sock, SIOCINQ, &ioctl_siocinq); |
| if (error < 0) { |
| fprintf(stderr, "Failed to run ioctl(SIOCINQ): %d\n", error); |
| return T_EXIT_FAIL; |
| } |
| |
| create_sqe_and_submit(&ring, sock, SOCKET_URING_OP_SIOCOUTQ); |
| uring_siocoutq = receive_cqe(&ring); |
| |
| create_sqe_and_submit(&ring, sock, SOCKET_URING_OP_SIOCINQ); |
| uring_siocinq = receive_cqe(&ring); |
| |
| /* Compare that both values (ioctl and uring CMD) should be similar */ |
| if (uring_siocoutq != ioctl_siocoutq) { |
| if (!retry) { |
| retry = 1; |
| goto again; |
| } |
| fprintf(stderr, "values does not match: %d != %d\n", |
| uring_siocoutq, ioctl_siocoutq); |
| return T_EXIT_FAIL; |
| } |
| if (uring_siocinq != ioctl_siocinq) { |
| if (!retry) { |
| retry = 1; |
| goto again; |
| } |
| fprintf(stderr, "values does not match: %d != %d\n", |
| uring_siocinq, ioctl_siocinq); |
| return T_EXIT_FAIL; |
| } |
| |
| return T_EXIT_PASS; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int err; |
| |
| if (argc > 1) |
| return 0; |
| |
| /* Test SOCK_STREAM */ |
| err = run_test(true); |
| if (err) |
| return err; |
| if (no_io_cmd) |
| return T_EXIT_SKIP; |
| |
| /* Test SOCK_DGRAM */ |
| err = run_test(false); |
| if (err) |
| return err; |
| |
| /* Test raw sockets */ |
| return run_test_raw(); |
| } |