Commit e1bb28bf authored by Kuniyuki Iwashima's avatar Kuniyuki Iwashima Committed by Jakub Kicinski
Browse files

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: default avatarKuniyuki Iwashima <kuniyu@google.com>
Link: https://patch.msgid.link/20251117174740.3684604-3-kuniyu@google.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 7bf3a476
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ skf_net_off
socket
so_incoming_cpu
so_netns_cookie
so_peek_off
so_txtime
so_rcv_listener
stress_reuseport_listen
+1 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ TEST_GEN_PROGS := \
	scm_inq \
	scm_pidfd \
	scm_rights \
	so_peek_off \
	unix_connect \
# end of TEST_GEN_PROGS

+162 −0
Original line number Diff line number Diff line
// 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