mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Oleksij Rempel <o.rempel@pengutronix.de>
To: Robin van der Gracht <robin@protonic.nl>,
	Oliver Hartkopp <socketcan@hartkopp.net>,
	Marc Kleine-Budde <mkl@pengutronix.de>,
	Shuah Khan <shuah@kernel.org>
Cc: "Oleksij Rempel" <o.rempel@pengutronix.de>,
	kernel@pengutronix.de, linux-can@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
	"Alexander Hölzl" <alexander.hoelzl@gmx.net>,
	Quchaosheng <quchaosheng000406@163.com>,
	"Tetsuo Handa" <penguin-kernel@I-love.SAKURA.ne.jp>
Subject: [PATCH can-next v1 3/8] selftests: can: add J1939 socket option and metadata tests
Date: Wed, 14 Oct 2026 17:44:29 +0200	[thread overview]
Message-ID: <20261014154435.3577983-4-o.rempel@pengutronix.de> (raw)
In-Reply-To: <20261014154435.3577983-1-o.rempel@pengutronix.de>

Add a selftest covering the J1939 socket options and send metadata: the
PGN filter (SO_J1939_FILTER), the send priority (SO_J1939_SEND_PRIO,
observed on the wire via a CAN_RAW peer), the promisc flag, destination
addressing via sendto(), a wrong-family bind, and the TX-completion
error-queue notification (SO_J1939_ERRQUEUE / SO_TIMESTAMPING), including
that it is labelled SCM_TSTAMP_ACK. setsockopt values are also read back
with getsockopt.

This is the first J1939 test to open a CAN_RAW socket, so enable
CONFIG_CAN_RAW in the test config.

Assisted-by: LLM
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
 tools/testing/selftests/net/can/.gitignore    |   1 +
 tools/testing/selftests/net/can/Makefile      |   2 +
 tools/testing/selftests/net/can/config        |   1 +
 .../selftests/net/can/test_j1939_opts.c       | 305 ++++++++++++++++++
 .../selftests/net/can/test_j1939_opts.sh      |   5 +
 5 files changed, 314 insertions(+)
 create mode 100644 tools/testing/selftests/net/can/test_j1939_opts.c
 create mode 100755 tools/testing/selftests/net/can/test_j1939_opts.sh

diff --git a/tools/testing/selftests/net/can/.gitignore b/tools/testing/selftests/net/can/.gitignore
index 0ab563fe9ddf..427eed83e49e 100644
--- a/tools/testing/selftests/net/can/.gitignore
+++ b/tools/testing/selftests/net/can/.gitignore
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 test_raw_filter
 test_j1939_base
+test_j1939_opts
diff --git a/tools/testing/selftests/net/can/Makefile b/tools/testing/selftests/net/can/Makefile
index cf4bd2461a6c..24a57edb912c 100644
--- a/tools/testing/selftests/net/can/Makefile
+++ b/tools/testing/selftests/net/can/Makefile
@@ -9,11 +9,13 @@ LOCAL_HDRS := j1939_tests.h
 TEST_PROGS := \
 	test_raw_filter.sh \
 	test_j1939_base.sh \
+	test_j1939_opts.sh \
 
 TEST_FILES := j1939_lib.sh
 
 TEST_GEN_FILES := \
 	test_raw_filter \
 	test_j1939_base \
+	test_j1939_opts \
 
 include ../../lib.mk
diff --git a/tools/testing/selftests/net/can/config b/tools/testing/selftests/net/can/config
index 52a186bd1055..908342600101 100644
--- a/tools/testing/selftests/net/can/config
+++ b/tools/testing/selftests/net/can/config
@@ -2,3 +2,4 @@ CONFIG_CAN=m
 CONFIG_CAN_DEV=m
 CONFIG_CAN_VCAN=m
 CONFIG_CAN_J1939=m
+CONFIG_CAN_RAW=m
diff --git a/tools/testing/selftests/net/can/test_j1939_opts.c b/tools/testing/selftests/net/can/test_j1939_opts.c
new file mode 100644
index 000000000000..f761a42db213
--- /dev/null
+++ b/tools/testing/selftests/net/can/test_j1939_opts.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+/*
+ * J1939 socket option and metadata selftests.
+ *
+ * On top of the basic transport, cover the user-visible J1939 socket options and
+ * addressing: the SO_J1939_FILTER PGN whitelist, the SO_J1939_SEND_PRIO send
+ * priority seen in the CAN id (via a CAN_RAW peer, including the default of 6),
+ * unconnected sendto(2), a wrong-family bind rejection, and SO_J1939_ERRQUEUE
+ * TX-completion notifications.
+ *
+ * The vcan interface is set up by the caller (see test_j1939_opts.sh); its name
+ * is passed via the CANIF environment variable.
+ */
+#include <errno.h>
+#include <time.h>
+
+#include "kselftest_harness.h"
+#include "j1939_tests.h"
+
+#include <linux/net_tstamp.h>
+#include <linux/errqueue.h>
+
+#define SA_TX			0x80
+#define SA_RX			0x90
+#define PGN_A			0x0ab00
+#define PGN_B			0x0cd00
+#define PRIO_SET		3
+/*
+ * Worst-case error-queue drain budget: each iteration can block up to
+ * J1939_RECV_TIMEOUT_MS, so this bounds the drain time. A generous cap; only one
+ * ACK is expected.
+ */
+#define ERRQUEUE_POLL_RETRIES	8
+
+/* Read the next CAN frame from a raw socket and return its J1939 priority. */
+static int raw_prio(int raw, int timeout_ms)
+{
+	struct can_frame frame;
+
+	if (recv_timeout(raw, &frame, sizeof(frame), timeout_ms) < 0)
+		return -1;
+	return (int)J1939_ID_PRIO(frame.can_id & CAN_EFF_MASK);
+}
+
+/* Set an int J1939 sockopt, read it back, and return the value (or -1). */
+static int sockopt_roundtrip(int s, int optname, int val)
+{
+	int got = -1;
+	socklen_t len = sizeof(got);
+
+	if (setsockopt(s, SOL_CAN_J1939, optname, &val, sizeof(val)) < 0)
+		return -1;
+	if (getsockopt(s, SOL_CAN_J1939, optname, &got, &len) < 0)
+		return -1;
+	return got;
+}
+
+FIXTURE(j1939_opts)
+{
+	int ifindex;
+	int tx;
+	int rx;
+	int raw;
+};
+
+FIXTURE_SETUP(j1939_opts)
+{
+	self->tx = -1;
+	self->rx = -1;
+	self->raw = -1;
+
+	J1939_SETUP_IFINDEX(self);
+
+	J1939_SETUP_RAW(self, 0);
+}
+
+FIXTURE_TEARDOWN(j1939_opts)
+{
+	if (self->tx >= 0)
+		close(self->tx);
+	if (self->rx >= 0)
+		close(self->rx);
+	if (self->raw >= 0)
+		close(self->raw);
+}
+
+/* SO_J1939_FILTER whitelists PGN_A; PGN_A is received, PGN_B is dropped. */
+TEST_F(j1939_opts, filter_pgn)
+{
+	struct j1939_filter filt = {
+		.pgn = PGN_A,
+		.pgn_mask = J1939_PGN_MAX,
+	};
+	uint8_t tx_buf[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+	uint8_t rx_buf[8];
+	int ret;
+
+	self->rx = j1939_open(self->ifindex, SA_RX, J1939_NO_PGN);
+	ASSERT_GE(self->rx, 0);
+	ASSERT_EQ(setsockopt(self->rx, SOL_CAN_J1939, SO_J1939_FILTER,
+			     &filt, sizeof(filt)), 0)
+		TH_LOG("SO_J1939_FILTER failed: %d", errno);
+
+	self->tx = j1939_open(self->ifindex, SA_TX, J1939_NO_PGN);
+	ASSERT_GE(self->tx, 0);
+
+	/* PGN_A must pass the filter */
+	ret = j1939_sendto(self->tx, tx_buf, sizeof(tx_buf), self->ifindex,
+			   J1939_NO_NAME, SA_RX, PGN_A);
+	ASSERT_EQ(ret, (int)sizeof(tx_buf))
+		TH_LOG("sendto PGN_A failed: %d", errno);
+	EXPECT_EQ(recv_timeout(self->rx, rx_buf, sizeof(rx_buf),
+			       J1939_RECV_TIMEOUT_MS), (int)sizeof(tx_buf))
+		TH_LOG("PGN_A was not received despite matching filter");
+
+	/* PGN_B must be dropped by the filter */
+	ret = j1939_sendto(self->tx, tx_buf, sizeof(tx_buf), self->ifindex,
+			   J1939_NO_NAME, SA_RX, PGN_B);
+	ASSERT_EQ(ret, (int)sizeof(tx_buf))
+		TH_LOG("sendto PGN_B failed: %d", errno);
+	EXPECT_EQ(recv_timeout(self->rx, rx_buf, sizeof(rx_buf),
+			       J1939_NEG_RECV_TIMEOUT_MS), -1)
+		TH_LOG("PGN_B was received but should have been filtered out");
+}
+
+/* without SO_J1939_SEND_PRIO the J1939 default priority (6) is used. */
+TEST_F(j1939_opts, send_prio_default)
+{
+	uint8_t buf[8] = {};
+
+	self->tx = j1939_open(self->ifindex, SA_TX, J1939_NO_PGN);
+	ASSERT_GE(self->tx, 0);
+
+	ASSERT_EQ(j1939_sendto(self->tx, buf, sizeof(buf), self->ifindex,
+			       J1939_NO_NAME, SA_RX, PGN_A),
+		  (int)sizeof(buf));
+
+	EXPECT_EQ(raw_prio(self->raw, J1939_RECV_TIMEOUT_MS), J1939_PRIO_DEFAULT);
+}
+
+/* SO_J1939_SEND_PRIO changes the priority carried in the CAN id. */
+TEST_F(j1939_opts, send_prio_set)
+{
+	int prio = PRIO_SET;
+	uint8_t buf[8] = {};
+
+	self->tx = j1939_open(self->ifindex, SA_TX, J1939_NO_PGN);
+	ASSERT_GE(self->tx, 0);
+	ASSERT_EQ(setsockopt(self->tx, SOL_CAN_J1939, SO_J1939_SEND_PRIO,
+			     &prio, sizeof(prio)), 0)
+		TH_LOG("SO_J1939_SEND_PRIO failed: %d", errno);
+
+	ASSERT_EQ(j1939_sendto(self->tx, buf, sizeof(buf), self->ifindex,
+			       J1939_NO_NAME, SA_RX, PGN_A),
+		  (int)sizeof(buf));
+
+	EXPECT_EQ(raw_prio(self->raw, J1939_RECV_TIMEOUT_MS), PRIO_SET);
+}
+
+/* an unconnected socket can address a peer via sendto(2). */
+TEST_F(j1939_opts, sendto_unconnected)
+{
+	uint8_t tx_buf[8] = { 9, 8, 7, 6, 5, 4, 3, 2 };
+	uint8_t rx_buf[8];
+
+	self->rx = j1939_open(self->ifindex, SA_RX, PGN_A);
+	ASSERT_GE(self->rx, 0);
+	self->tx = j1939_open(self->ifindex, SA_TX, J1939_NO_PGN);
+	ASSERT_GE(self->tx, 0);
+
+	ASSERT_EQ(j1939_sendto(self->tx, tx_buf, sizeof(tx_buf), self->ifindex,
+			       J1939_NO_NAME, SA_RX, PGN_A),
+		  (int)sizeof(tx_buf));
+
+	ASSERT_EQ(recv_timeout(self->rx, rx_buf, sizeof(rx_buf),
+			       J1939_RECV_TIMEOUT_MS), (int)sizeof(tx_buf));
+	EXPECT_EQ(memcmp(tx_buf, rx_buf, sizeof(tx_buf)), 0);
+}
+
+/* bind(2) with a wrong address family is rejected. */
+TEST_F(j1939_opts, bind_wrong_family)
+{
+	struct sockaddr_can addr = {
+		.can_family = AF_INET,
+		.can_ifindex = self->ifindex,
+	};
+	int s;
+
+	s = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);
+	ASSERT_GE(s, 0);
+	EXPECT_LT(bind(s, (struct sockaddr *)&addr, sizeof(addr)), 0)
+		TH_LOG("bind with AF_INET unexpectedly succeeded");
+	close(s);
+}
+
+/* SO_J1939_ERRQUEUE delivers TX completion notifications for a TP send. */
+TEST_F(j1939_opts, errqueue_tx_completion)
+{
+	int enable = 1;
+	int ts = SOF_TIMESTAMPING_TX_ACK | SOF_TIMESTAMPING_TX_SCHED |
+		 SOF_TIMESTAMPING_OPT_ID;
+	uint8_t tx_buf[128], rx_buf[128];
+	int got_ack = 0, i;
+
+	memset(tx_buf, 0x5a, sizeof(tx_buf));
+
+	self->tx = j1939_open(self->ifindex, SA_TX, J1939_NO_PGN);
+	ASSERT_GE(self->tx, 0);
+	/*
+	 * setsockopt(SO_J1939_ERRQUEUE) returns the flag value (1) on success
+	 * rather than 0, so only check for a non-negative result.
+	 */
+	ASSERT_GE(setsockopt(self->tx, SOL_CAN_J1939, SO_J1939_ERRQUEUE,
+			     &enable, sizeof(enable)), 0)
+		TH_LOG("SO_J1939_ERRQUEUE failed: %d", errno);
+	ASSERT_EQ(setsockopt(self->tx, SOL_SOCKET, SO_TIMESTAMPING,
+			     &ts, sizeof(ts)), 0)
+		TH_LOG("SO_TIMESTAMPING failed: %d", errno);
+
+	/* A receiver lets the TP session complete and be acknowledged. */
+	self->rx = j1939_open(self->ifindex, SA_RX, PGN_A);
+	ASSERT_GE(self->rx, 0);
+
+	ASSERT_EQ(j1939_sendto(self->tx, tx_buf, sizeof(tx_buf), self->ifindex,
+			       J1939_NO_NAME, SA_RX, PGN_A),
+		  (int)sizeof(tx_buf));
+
+	ASSERT_EQ(recv_timeout(self->rx, rx_buf, sizeof(rx_buf),
+			       J1939_RECV_TIMEOUT_MS), (int)sizeof(tx_buf));
+
+	/*
+	 * Collect the TX-completion (ACK) from the error queue. Allow a few extra
+	 * iterations because other control messages may arrive before the ACK.
+	 */
+	for (i = 0; i < ERRQUEUE_POLL_RETRIES && !got_ack; i++) {
+		struct pollfd pfd = { .fd = self->tx, .events = POLLERR };
+		union {
+			char buf[CMSG_SPACE(sizeof(struct sock_extended_err)) +
+				 CMSG_SPACE(sizeof(struct scm_timestamping))];
+			struct cmsghdr align;	/* portable cmsg alignment */
+		} ctrl;
+		char data[128];
+		struct iovec iov = { .iov_base = data, .iov_len = sizeof(data) };
+		struct msghdr msg = {
+			.msg_iov = &iov,
+			.msg_iovlen = 1,
+			.msg_control = ctrl.buf,
+			.msg_controllen = sizeof(ctrl.buf),
+		};
+		struct cmsghdr *cmsg;
+
+		if (poll(&pfd, 1, J1939_RECV_TIMEOUT_MS) <= 0)
+			break;
+		if (recvmsg(self->tx, &msg, MSG_ERRQUEUE) < 0)
+			break;
+
+		for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
+		     cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+			struct sock_extended_err *serr;
+
+			if (cmsg->cmsg_level != SOL_CAN_J1939 ||
+			    cmsg->cmsg_type != SCM_J1939_ERRQUEUE)
+				continue;
+			serr = (struct sock_extended_err *)CMSG_DATA(cmsg);
+			if (serr->ee_origin != SO_EE_ORIGIN_TIMESTAMPING)
+				continue;
+			/* The TX-completion notification must be SCM_TSTAMP_ACK. */
+			if (serr->ee_info == SCM_TSTAMP_ACK)
+				got_ack = 1;
+		}
+	}
+
+	EXPECT_EQ(got_ack, 1)
+		TH_LOG("no SCM_TSTAMP_ACK TX completion on the error queue");
+}
+
+/* getsockopt reads back SO_J1939_SEND_PRIO. */
+TEST_F(j1939_opts, getsockopt_send_prio)
+{
+	self->tx = j1939_open(self->ifindex, SA_TX, J1939_NO_PGN);
+	ASSERT_GE(self->tx, 0);
+	EXPECT_EQ(sockopt_roundtrip(self->tx, SO_J1939_SEND_PRIO, PRIO_SET),
+		  PRIO_SET)
+		TH_LOG("SO_J1939_SEND_PRIO round-trip failed: %d", errno);
+}
+
+/* getsockopt reads back the SO_J1939_PROMISC flag. */
+TEST_F(j1939_opts, getsockopt_promisc)
+{
+	self->tx = j1939_open(self->ifindex, SA_TX, J1939_NO_PGN);
+	ASSERT_GE(self->tx, 0);
+	EXPECT_EQ(sockopt_roundtrip(self->tx, SO_J1939_PROMISC, 1), 1)
+		TH_LOG("SO_J1939_PROMISC round-trip failed: %d", errno);
+}
+
+/* getsockopt reads back the SO_J1939_ERRQUEUE flag. */
+TEST_F(j1939_opts, getsockopt_errqueue)
+{
+	self->tx = j1939_open(self->ifindex, SA_TX, J1939_NO_PGN);
+	ASSERT_GE(self->tx, 0);
+	EXPECT_EQ(sockopt_roundtrip(self->tx, SO_J1939_ERRQUEUE, 1), 1)
+		TH_LOG("SO_J1939_ERRQUEUE round-trip failed: %d", errno);
+}
+
+J1939_TEST_MAIN();
diff --git a/tools/testing/selftests/net/can/test_j1939_opts.sh b/tools/testing/selftests/net/can/test_j1939_opts.sh
new file mode 100755
index 000000000000..5d76c56f24a4
--- /dev/null
+++ b/tools/testing/selftests/net/can/test_j1939_opts.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+J1939_TEST_PROG=test_j1939_opts
+source "$(dirname "$0")/j1939_lib.sh"
-- 
2.47.3


  parent reply	other threads:[~2026-10-14 15:44 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-14 15:44 [PATCH can-next v1 0/8] selftests: can: add J1939 socket and transport tests Oleksij Rempel
2026-10-14 15:44 ` [PATCH can-next v1 1/8] selftests: can: add J1939 test helper header and library Oleksij Rempel
2026-10-16  6:59   ` netdev-bot+sashiko
2026-10-14 15:44 ` [PATCH can-next v1 2/8] selftests: can: add J1939 base functional tests Oleksij Rempel
2026-10-16  7:00   ` netdev-bot+sashiko
2026-10-14 15:44 ` Oleksij Rempel [this message]
2026-10-16  7:00   ` [PATCH can-next v1 3/8] selftests: can: add J1939 socket option and metadata tests netdev-bot+sashiko
2026-10-14 15:44 ` [PATCH can-next v1 4/8] selftests: can: add J1939 addressing and concurrency tests Oleksij Rempel
2026-10-16  7:00   ` netdev-bot+sashiko
2026-10-14 15:44 ` [PATCH can-next v1 5/8] selftests: can: add J1939 wire-format tests against CAN_RAW Oleksij Rempel
2026-10-16  7:00   ` netdev-bot+sashiko
2026-10-14 15:44 ` [PATCH can-next v1 6/8] selftests: can: add J1939 TP/ETP multi-frame " Oleksij Rempel
2026-10-16  7:00   ` netdev-bot+sashiko
2026-10-14 15:44 ` [PATCH can-next v1 7/8] selftests: can: cover J1939 transport abort and timeout paths Oleksij Rempel
2026-10-16  7:00   ` netdev-bot+sashiko
2026-10-14 15:44 ` [PATCH can-next v1 8/8] selftests: can: check SO_J1939_PROMISC receive effect Oleksij Rempel
2026-10-14 22:19 ` [PATCH can-next v1 0/8] selftests: can: add J1939 socket and transport tests Marc Kleine-Budde

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261014154435.3577983-4-o.rempel@pengutronix.de \
    --to=o.rempel@pengutronix.de \
    --cc=alexander.hoelzl@gmx.net \
    --cc=kernel@pengutronix.de \
    --cc=linux-can@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=mkl@pengutronix.de \
    --cc=penguin-kernel@I-love.SAKURA.ne.jp \
    --cc=quchaosheng000406@163.com \
    --cc=robin@protonic.nl \
    --cc=shuah@kernel.org \
    --cc=socketcan@hartkopp.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®