Add a selftest for J1939 NAME-based addressing and address claiming: binding and reading back a NAME, the source address seen on the wire, NAME-addressed transfers (with a forged Address Claimed), the Address Claimed a socket emits, and address-claim conflicts where the contender with the higher NAME loses and the one with the lower NAME keeps the address. It also runs several concurrent transport sessions as a stress check.
The NAME is serialized onto the wire byte-wise (put_name) so the tests behave the same on big- and little-endian hosts. Assisted-by: LLM Signed-off-by: Oleksij Rempel <[email protected]> --- tools/testing/selftests/net/can/.gitignore | 1 + tools/testing/selftests/net/can/Makefile | 2 + .../selftests/net/can/test_j1939_addr.c | 336 ++++++++++++++++++ .../selftests/net/can/test_j1939_addr.sh | 5 + 4 files changed, 344 insertions(+) create mode 100644 tools/testing/selftests/net/can/test_j1939_addr.c create mode 100755 tools/testing/selftests/net/can/test_j1939_addr.sh diff --git a/tools/testing/selftests/net/can/.gitignore b/tools/testing/selftests/net/can/.gitignore index 427eed83e49e..7ca63a19472c 100644 --- a/tools/testing/selftests/net/can/.gitignore +++ b/tools/testing/selftests/net/can/.gitignore @@ -2,3 +2,4 @@ test_raw_filter test_j1939_base test_j1939_opts +test_j1939_addr diff --git a/tools/testing/selftests/net/can/Makefile b/tools/testing/selftests/net/can/Makefile index 24a57edb912c..6e4176a1b985 100644 --- a/tools/testing/selftests/net/can/Makefile +++ b/tools/testing/selftests/net/can/Makefile @@ -10,6 +10,7 @@ TEST_PROGS := \ test_raw_filter.sh \ test_j1939_base.sh \ test_j1939_opts.sh \ + test_j1939_addr.sh \ TEST_FILES := j1939_lib.sh @@ -17,5 +18,6 @@ TEST_GEN_FILES := \ test_raw_filter \ test_j1939_base \ test_j1939_opts \ + test_j1939_addr \ include ../../lib.mk diff --git a/tools/testing/selftests/net/can/test_j1939_addr.c b/tools/testing/selftests/net/can/test_j1939_addr.c new file mode 100644 index 000000000000..8b1b093f0839 --- /dev/null +++ b/tools/testing/selftests/net/can/test_j1939_addr.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +/* + * J1939 addressing and concurrency selftests. + * + * Cover NAME-based addressing and address claiming: binding a NAME and reading + * it back, the bound source address appearing in the CAN id, a sender reaching a + * peer by NAME, address-claim contention where the lower NAME wins, and a stress + * run of many concurrent transport sessions. + * + * The vcan interface is set up by the caller (see test_j1939_addr.sh); its name + * is passed via the CANIF environment variable. + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/wait.h> + +#include "kselftest_harness.h" +#include "j1939_tests.h" + +#define SA_TX 0x80 +#define SA_RX 0x90 +#define NAME_TX 0x00000000c0ffee01ULL +#define NAME_RX 0x00000000c0ffee02ULL +#define PGN_TEST 0x0ab00 +#define STRESS_N 8 /* arbitrary test tuning */ +#define STRESS_SIZE 128 /* arbitrary test tuning */ +#define ACLAIM_SETTLE_US 300000 /* settle: empirical, not spec */ + +/* Open a J1939 socket bound to (name, sa, pgn). */ +static int j1939_open_name(int ifindex, name_t name, uint8_t sa, pgn_t pgn) +{ + return j1939_open_flags(ifindex, name, sa, pgn, 0); +} + +/* + * Inject an Address Claimed message (PGN 0xee00, global destination) for the + * given source address and NAME via a raw socket, so the kernel learns the + * NAME <-> address mapping. The kernel does not claim addresses itself. + */ +static int forge_address_claim(int raw, uint8_t sa, name_t name) +{ + uint8_t nb[8]; + + put_name(nb, name); + if (send_raw8(raw, J1939_PRIO_DEFAULT, J1939_PGN_ADDRESS_CLAIMED, + J1939_NO_ADDR, sa, nb)) + return -1; + /* give the kernel RX path a moment to process the claim */ + usleep(ACLAIM_SETTLE_US); + return 0; +} + +/* One independent transport-protocol round-trip; returns 0 on success. */ +static int stress_child(int ifindex, uint8_t sa_tx, uint8_t sa_rx) +{ + uint8_t tx_buf[STRESS_SIZE]; + int tx, rx, rc = 1; + + fill_pattern(tx_buf, sizeof(tx_buf)); + + rx = j1939_open_name(ifindex, J1939_NO_NAME, sa_rx, PGN_TEST); + tx = j1939_open_name(ifindex, J1939_NO_NAME, sa_tx, J1939_NO_PGN); + if (rx < 0 || tx < 0) + goto out; + if (j1939_connect_name(tx, ifindex, J1939_NO_NAME, sa_rx, PGN_TEST)) + goto out; + + if (send_recv_verify(tx, rx, tx_buf, sizeof(tx_buf), + J1939_RECV_TIMEOUT_MS) == 0) + rc = 0; +out: + if (rx >= 0) + close(rx); + if (tx >= 0) + close(tx); + return rc; +} + +FIXTURE(j1939_addr) +{ + int ifindex; + int tx; + int rx; + int raw; +}; + +FIXTURE_SETUP(j1939_addr) +{ + self->tx = -1; + self->rx = -1; + self->raw = -1; + + J1939_SETUP_IFINDEX(self); +} + +FIXTURE_TEARDOWN(j1939_addr) +{ + if (self->tx >= 0) + close(self->tx); + if (self->rx >= 0) + close(self->rx); + if (self->raw >= 0) + close(self->raw); +} + +/* bind with a NAME and read it back with getsockname(2). */ +TEST_F(j1939_addr, name_bind_getsockname) +{ + struct sockaddr_can addr = {}; + socklen_t len = sizeof(addr); + + self->tx = j1939_open_name(self->ifindex, NAME_TX, SA_TX, J1939_NO_PGN); + ASSERT_GE(self->tx, 0) + TH_LOG("bind with NAME failed: %d", errno); + + ASSERT_EQ(getsockname(self->tx, (struct sockaddr *)&addr, &len), 0); + EXPECT_EQ(addr.can_addr.j1939.name, NAME_TX); + EXPECT_EQ(addr.can_addr.j1939.addr, SA_TX); +} + +/* the bound source address is used as source address on the wire. */ +TEST_F(j1939_addr, source_sa_on_wire) +{ + uint8_t buf[8] = {}; + struct can_frame frame; + int ret; + + self->raw = open_raw(self->ifindex); + ASSERT_GE(self->raw, 0); + + /* static source address: no address claim needed to transmit */ + self->tx = j1939_open_name(self->ifindex, J1939_NO_NAME, SA_TX, + J1939_NO_PGN); + ASSERT_GE(self->tx, 0); + ASSERT_EQ(j1939_connect_name(self->tx, self->ifindex, J1939_NO_NAME, + SA_RX, PGN_TEST), 0); + + ASSERT_EQ(send(self->tx, buf, sizeof(buf), 0), (int)sizeof(buf)); + + ret = recv_timeout(self->raw, &frame, sizeof(frame), J1939_RECV_TIMEOUT_MS); + ASSERT_GT(ret, 0); + EXPECT_EQ((int)J1939_ID_SA(frame.can_id), SA_TX) + TH_LOG("source address on wire is 0x%02x, expected 0x%02x", + (unsigned int)J1939_ID_SA(frame.can_id), SA_TX); +} + +/* + * NAME based addressing reaches the socket bound to that NAME. + * + * The peer must have claimed its address before it is reachable by NAME, so + * inject an Address Claimed message for (SA_RX, NAME_RX) first. The sender + * uses a static source address and resolves the destination by NAME. + */ +TEST_F(j1939_addr, name_addressing) +{ + uint8_t tx_buf[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + uint8_t rx_buf[8]; + + /* static receiver on SA_RX; the forged claim maps NAME_RX -> SA_RX */ + self->rx = j1939_open_name(self->ifindex, J1939_NO_NAME, SA_RX, PGN_TEST); + ASSERT_GE(self->rx, 0); + + self->raw = open_raw(self->ifindex); + ASSERT_GE(self->raw, 0); + ASSERT_EQ(forge_address_claim(self->raw, SA_RX, NAME_RX), 0) + TH_LOG("failed to inject address claim: %d", errno); + + self->tx = j1939_open_name(self->ifindex, J1939_NO_NAME, SA_TX, + J1939_NO_PGN); + ASSERT_GE(self->tx, 0); + ASSERT_EQ(j1939_connect_name(self->tx, self->ifindex, NAME_RX, + J1939_NO_ADDR, PGN_TEST), 0) + TH_LOG("connect to peer NAME failed: %d", errno); + + ASSERT_EQ(send(self->tx, tx_buf, sizeof(tx_buf), 0), + (int)sizeof(tx_buf)) + TH_LOG("send to peer NAME failed: %d", errno); + 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); +} + +/* many concurrent transport-protocol sessions run without corruption. */ +TEST_F(j1939_addr, stress_concurrent_sessions) +{ + pid_t pids[STRESS_N]; + int i, failed = 0; + + for (i = 0; i < STRESS_N; i++) { + pids[i] = fork(); + ASSERT_GE(pids[i], 0); + if (pids[i] == 0) + _exit(stress_child(self->ifindex, 0x40 + i, 0x20 + i)); + } + + for (i = 0; i < STRESS_N; i++) { + int status; + + if (waitpid(pids[i], &status, 0) < 0 || + !WIFEXITED(status) || WEXITSTATUS(status) != 0) + failed++; + } + + EXPECT_EQ(failed, 0) + TH_LOG("%d of %d concurrent sessions failed", failed, STRESS_N); +} + +#define SA_CLAIM 0x50 +#define SA_DST 0x60 +#define NAME_CLAIM 0x00000000c0ffee10ULL +#define NAME_LOWER 0x00000000c0ffee00ULL /* higher priority */ +#define NAME_HIGHER 0x00000000c0ffee20ULL /* lower priority */ + +/* + * Claim an address the way j1939acd would: bind a NAME to an address and send + * an Address Claimed message, then wait for the claim to settle. Returns the + * (still open) socket on success. + */ +static int claim_addr(int ifindex, name_t name, uint8_t sa) +{ + uint8_t nb[8]; + int s; + + s = j1939_open_flags(ifindex, name, sa, J1939_NO_PGN, J1939_OPEN_BCAST); + if (s < 0) + return -1; + + put_name(nb, name); + if (j1939_sendto(s, nb, 8, ifindex, J1939_NO_NAME, J1939_NO_ADDR, + J1939_PGN_ADDRESS_CLAIMED) != 8) { + close(s); + return -1; + } + + usleep(ACLAIM_SETTLE_US); /* settling time */ + return s; +} + +/* Send a single data frame from a socket to (SA_DST, PGN_TEST). */ +static int send_data(int s, int ifindex) +{ + uint8_t data[8] = {}; + + return j1939_sendto(s, data, sizeof(data), ifindex, J1939_NO_NAME, + SA_DST, PGN_TEST); +} + +static bool match_addr_claimed(canid_t id, const void *ctx) +{ + (void)ctx; + return J1939_ID_PF(id) == J1939_PF(J1939_PGN_ADDRESS_CLAIMED) && + J1939_ID_SA(id) == SA_CLAIM; +} + +/* sending an Address Claimed message puts it on the wire and claims. */ +TEST_F(j1939_addr, address_claim_send) +{ + struct can_frame frame; + + self->raw = open_raw(self->ifindex); + ASSERT_GE(self->raw, 0); + + self->tx = claim_addr(self->ifindex, NAME_CLAIM, SA_CLAIM); + ASSERT_GE(self->tx, 0) + TH_LOG("address claim failed: %d", errno); + + /* the Address Claimed frame must have appeared on the wire */ + EXPECT_EQ(recv_match(self->raw, ACLAIM_SETTLE_US / 1000, &frame, + match_addr_claimed, NULL), 0) + TH_LOG("no Address Claimed frame seen on the wire"); + + /* with the address claimed, the socket can transmit */ + EXPECT_EQ(send_data(self->tx, self->ifindex), 8) + TH_LOG("transmit from claimed address failed: %d", errno); +} + +/* + * Shared address-claim arbitration setup for the win/lose conflict tests. + * Claim SA_CLAIM with NAME_CLAIM, confirm we own it, then let a contender with + * @contender claim the same SA. On success stores the result of transmitting + * after the contention in *tx_ret (errno is left as that send set it) and + * returns 0; returns -1 if any setup step failed. Each caller asserts its own + * win/lose outcome on *tx_ret. + */ +static int ac_conflict_probe(FIXTURE_DATA(j1939_addr) *self, name_t contender, + int *tx_ret) +{ + self->raw = open_raw(self->ifindex); + if (self->raw < 0) + return -1; + self->tx = claim_addr(self->ifindex, NAME_CLAIM, SA_CLAIM); + if (self->tx < 0) + return -1; + + /* baseline: we own the address and can transmit */ + if (send_data(self->tx, self->ifindex) != 8) + return -1; + + /* let the contender claim our SA */ + if (forge_address_claim(self->raw, SA_CLAIM, contender)) + return -1; + + *tx_ret = send_data(self->tx, self->ifindex); + return 0; +} + +/* a higher-priority competing claim takes the address away from us. */ +TEST_F(j1939_addr, ac_conflict_lose) +{ + int tx_ret; + + /* a contender with a lower NAME (higher priority) claims our SA */ + ASSERT_EQ(ac_conflict_probe(self, NAME_LOWER, &tx_ret), 0); + + /* we must have lost the address */ + EXPECT_LT(tx_ret, 0) + TH_LOG("transmit still succeeded after losing the address"); + EXPECT_EQ(errno, EADDRNOTAVAIL); +} + +/* a lower-priority competing claim does not take our address. */ +TEST_F(j1939_addr, ac_conflict_defend) +{ + int tx_ret; + + /* a contender with a higher NAME (lower priority) must not win */ + ASSERT_EQ(ac_conflict_probe(self, NAME_HIGHER, &tx_ret), 0); + + EXPECT_EQ(tx_ret, 8) + TH_LOG("lost the address to a lower-priority claim"); +} + +J1939_TEST_MAIN(); diff --git a/tools/testing/selftests/net/can/test_j1939_addr.sh b/tools/testing/selftests/net/can/test_j1939_addr.sh new file mode 100755 index 000000000000..e611fe211c6d --- /dev/null +++ b/tools/testing/selftests/net/can/test_j1939_addr.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +J1939_TEST_PROG=test_j1939_addr +source "$(dirname "$0")/j1939_lib.sh" -- 2.47.3

