| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright 2025 Google LLC */ |
| |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| #include <sys/socket.h> |
| |
| #include "../../kselftest_harness.h" |
| |
| FIXTURE(so_peek_off) |
| { |
| int fd[2]; /* 0: sender, 1: receiver */ |
| }; |
| |
| FIXTURE_VARIANT(so_peek_off) |
| { |
| int type; |
| }; |
| |
| FIXTURE_VARIANT_ADD(so_peek_off, stream) |
| { |
| .type = SOCK_STREAM, |
| }; |
| |
| FIXTURE_VARIANT_ADD(so_peek_off, dgram) |
| { |
| .type = SOCK_DGRAM, |
| }; |
| |
| FIXTURE_VARIANT_ADD(so_peek_off, seqpacket) |
| { |
| .type = SOCK_SEQPACKET, |
| }; |
| |
| FIXTURE_SETUP(so_peek_off) |
| { |
| struct timeval timeout = { |
| .tv_sec = 5, |
| .tv_usec = 0, |
| }; |
| int ret; |
| |
| ret = socketpair(AF_UNIX, variant->type, 0, self->fd); |
| ASSERT_EQ(0, ret); |
| |
| ret = setsockopt(self->fd[1], SOL_SOCKET, SO_RCVTIMEO_NEW, |
| &timeout, sizeof(timeout)); |
| ASSERT_EQ(0, ret); |
| |
| ret = setsockopt(self->fd[1], SOL_SOCKET, SO_PEEK_OFF, |
| &(int){0}, sizeof(int)); |
| ASSERT_EQ(0, ret); |
| } |
| |
| FIXTURE_TEARDOWN(so_peek_off) |
| { |
| close_range(self->fd[0], self->fd[1], 0); |
| } |
| |
| #define sendeq(fd, str, flags) \ |
| do { \ |
| int bytes, len = strlen(str); \ |
| \ |
| bytes = send(fd, str, len, flags); \ |
| ASSERT_EQ(len, bytes); \ |
| } while (0) |
| |
| #define recveq(fd, str, buflen, flags) \ |
| do { \ |
| char buf[(buflen) + 1] = {}; \ |
| int bytes; \ |
| \ |
| bytes = recv(fd, buf, buflen, flags); \ |
| ASSERT_NE(-1, bytes); \ |
| ASSERT_STREQ(str, buf); \ |
| } while (0) |
| |
| #define peekoffeq(fd, expected) \ |
| do { \ |
| socklen_t optlen = sizeof(int); \ |
| int off = -1; \ |
| int ret; \ |
| \ |
| ret = getsockopt(fd, SOL_SOCKET, SO_PEEK_OFF, \ |
| &off, &optlen); \ |
| ASSERT_EQ(0, ret); \ |
| ASSERT_EQ((socklen_t)sizeof(off), optlen); \ |
| ASSERT_EQ(expected, off); \ |
| } while (0) |
| |
| #define async \ |
| for (pid_t pid = (pid = fork(), \ |
| pid < 0 ? \ |
| __TH_LOG("Failed to start async {}"), \ |
| _metadata->exit_code = KSFT_FAIL, \ |
| __bail(1, _metadata), \ |
| 0xdead : \ |
| pid); \ |
| !pid; exit(0)) |
| |
| TEST_F(so_peek_off, single_chunk) |
| { |
| sendeq(self->fd[0], "aaaabbbb", 0); |
| |
| recveq(self->fd[1], "aaaa", 4, MSG_PEEK); |
| peekoffeq(self->fd[1], 4); |
| recveq(self->fd[1], "bbbb", 100, MSG_PEEK); |
| peekoffeq(self->fd[1], 8); |
| |
| recveq(self->fd[1], "aaaabbbb", 8, 0); |
| peekoffeq(self->fd[1], 0); |
| } |
| |
| TEST_F(so_peek_off, two_chunks) |
| { |
| sendeq(self->fd[0], "aaaa", 0); |
| sendeq(self->fd[0], "bbbb", 0); |
| |
| recveq(self->fd[1], "aaaa", 4, MSG_PEEK); |
| peekoffeq(self->fd[1], 4); |
| recveq(self->fd[1], "bbbb", 100, MSG_PEEK); |
| peekoffeq(self->fd[1], 8); |
| |
| recveq(self->fd[1], "aaaa", 4, 0); |
| recveq(self->fd[1], "bbbb", 4, 0); |
| peekoffeq(self->fd[1], 0); |
| } |
| |
| TEST_F(so_peek_off, two_chunks_blocking) |
| { |
| async { |
| usleep(1000); |
| sendeq(self->fd[0], "aaaa", 0); |
| } |
| |
| recveq(self->fd[1], "aaaa", 4, MSG_PEEK); |
| peekoffeq(self->fd[1], 4); |
| |
| async { |
| usleep(1000); |
| sendeq(self->fd[0], "bbbb", 0); |
| } |
| |
| /* goto again; -> goto redo; in unix_stream_read_generic(). */ |
| recveq(self->fd[1], "bbbb", 100, MSG_PEEK); |
| peekoffeq(self->fd[1], 8); |
| |
| recveq(self->fd[1], "aaaa", 4, 0); |
| recveq(self->fd[1], "bbbb", 4, 0); |
| peekoffeq(self->fd[1], 0); |
| } |
| |
| TEST_F(so_peek_off, two_chunks_overlap) |
| { |
| sendeq(self->fd[0], "aaaa", 0); |
| recveq(self->fd[1], "aa", 2, MSG_PEEK); |
| peekoffeq(self->fd[1], 2); |
| |
| sendeq(self->fd[0], "bbbb", 0); |
| |
| if (variant->type == SOCK_STREAM) { |
| /* SOCK_STREAM tries to fill the buffer. */ |
| recveq(self->fd[1], "aabb", 4, MSG_PEEK); |
| peekoffeq(self->fd[1], 6); |
| recveq(self->fd[1], "bb", 100, MSG_PEEK); |
| peekoffeq(self->fd[1], 8); |
| } else { |
| /* SOCK_DGRAM and SOCK_SEQPACKET returns at the skb boundary. */ |
| recveq(self->fd[1], "aa", 100, MSG_PEEK); |
| peekoffeq(self->fd[1], 4); |
| recveq(self->fd[1], "bbbb", 100, MSG_PEEK); |
| peekoffeq(self->fd[1], 8); |
| } |
| |
| recveq(self->fd[1], "aaaa", 4, 0); |
| recveq(self->fd[1], "bbbb", 4, 0); |
| peekoffeq(self->fd[1], 0); |
| } |
| |
| TEST_F(so_peek_off, two_chunks_overlap_blocking) |
| { |
| async { |
| usleep(1000); |
| sendeq(self->fd[0], "aaaa", 0); |
| } |
| |
| recveq(self->fd[1], "aa", 2, MSG_PEEK); |
| peekoffeq(self->fd[1], 2); |
| |
| async { |
| usleep(1000); |
| sendeq(self->fd[0], "bbbb", 0); |
| } |
| |
| /* Even SOCK_STREAM does not wait if at least one byte is read. */ |
| recveq(self->fd[1], "aa", 100, MSG_PEEK); |
| peekoffeq(self->fd[1], 4); |
| |
| recveq(self->fd[1], "bbbb", 100, MSG_PEEK); |
| peekoffeq(self->fd[1], 8); |
| |
| recveq(self->fd[1], "aaaa", 4, 0); |
| recveq(self->fd[1], "bbbb", 4, 0); |
| peekoffeq(self->fd[1], 0); |
| } |
| |
| TEST_HARNESS_MAIN |