/* SPDX-License-Identifier: MIT */ /* * Check that CMD operations on sockets are consistent. */ #include #include #include #include #include #include #include #include #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(); }