selftest: af_unix: Add test for SO_PEEK_OFF.

The test covers various cases to verify SO_PEEK_OFF behaviour
for all AF_UNIX socket types.

two_chunks_blocking and two_chunks_overlap_blocking reproduce
the issue mentioned in the previous patch.

Without the patch, the two tests fail:

  #  RUN           so_peek_off.stream.two_chunks_blocking ...
  # so_peek_off.c:121:two_chunks_blocking:Expected 'bbbb' == 'aaaabbbb'.
  # two_chunks_blocking: Test terminated by assertion
  #          FAIL  so_peek_off.stream.two_chunks_blocking
  not ok 3 so_peek_off.stream.two_chunks_blocking

  #  RUN           so_peek_off.stream.two_chunks_overlap_blocking ...
  # so_peek_off.c:159:two_chunks_overlap_blocking:Expected 'bbbb' == 'aaaabbbb'.
  # two_chunks_overlap_blocking: Test terminated by assertion
  #          FAIL  so_peek_off.stream.two_chunks_overlap_blocking
  not ok 5 so_peek_off.stream.two_chunks_overlap_blocking

With the patch, all tests pass:

  # PASSED: 15 / 15 tests passed.
  # Totals: pass:15 fail:0 xfail:0 xpass:0 skip:0 error:0

Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
Link: https://patch.msgid.link/20251117174740.3684604-3-kuniyu@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Kuniyuki Iwashima 2025-11-17 17:47:11 +00:00 committed by Jakub Kicinski
parent 7bf3a476ce
commit e1bb28bf13
3 changed files with 164 additions and 0 deletions

View File

@ -45,6 +45,7 @@ skf_net_off
socket socket
so_incoming_cpu so_incoming_cpu
so_netns_cookie so_netns_cookie
so_peek_off
so_txtime so_txtime
so_rcv_listener so_rcv_listener
stress_reuseport_listen stress_reuseport_listen

View File

@ -6,6 +6,7 @@ TEST_GEN_PROGS := \
scm_inq \ scm_inq \
scm_pidfd \ scm_pidfd \
scm_rights \ scm_rights \
so_peek_off \
unix_connect \ unix_connect \
# end of TEST_GEN_PROGS # end of TEST_GEN_PROGS

View File

@ -0,0 +1,162 @@
// 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 = 0,
.tv_usec = 3000,
};
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 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);
recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
}
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);
recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
}
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);
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);
}
TEST_F(so_peek_off, two_chunks_overlap)
{
sendeq(self->fd[0], "aaaa", 0);
recveq(self->fd[1], "aa", 2, MSG_PEEK);
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);
recveq(self->fd[1], "bb", 100, MSG_PEEK);
} else {
/* SOCK_DGRAM and SOCK_SEQPACKET returns at the skb boundary. */
recveq(self->fd[1], "aa", 100, MSG_PEEK);
recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
}
}
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);
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);
recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
}
TEST_HARNESS_MAIN