[LTP] [PATCH v2 21/31] testcases: sysfs: Add sys_net02

Cyril Hrubis <[email protected]>
Newsgroups it.linux.lists.ltp
Message-ID <[email protected]>
A test for /sys/class/net/*/* files.

This test, unlike sys_net01, creates a virtual ethernet interface so
that it can change various attributes and verify that they are mirrored
into the sysfs correctly.

Signed-off-by: Cyril Hrubis <[email protected]>
---
 runtest/sysfs                                 |   1 +
 testcases/kernel/sysfs/class/net/.gitignore   |   1 +
 testcases/kernel/sysfs/class/net/sys_net02.c  | 179 ++++++++++++++++++
 .../kernel/sysfs/class/net/sys_net_common.h   |  80 ++++++++
 4 files changed, 261 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net02.c
 create mode 100644 testcases/kernel/sysfs/class/net/sys_net_common.h

diff --git a/runtest/sysfs b/runtest/sysfs
index 90e4d2b8e..fee8a0b2b 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -16,3 +16,4 @@ sys_wakeup01 sys_wakeup01
 sys_rtc01 sys_rtc01
 sys_thermal01 sys_thermal01
 sys_net01 sys_net01
+sys_net02 sys_net02
diff --git a/testcases/kernel/sysfs/class/net/.gitignore b/testcases/kernel/sysfs/class/net/.gitignore
index f5ef57b75..b70d02158 100644
--- a/testcases/kernel/sysfs/class/net/.gitignore
+++ b/testcases/kernel/sysfs/class/net/.gitignore
@@ -1 +1,2 @@
 /sys_net01
+/sys_net02
diff --git a/testcases/kernel/sysfs/class/net/sys_net02.c b/testcases/kernel/sysfs/class/net/sys_net02.c
new file mode 100644
index 000000000..f8f564c1f
--- /dev/null
+++ b/testcases/kernel/sysfs/class/net/sys_net02.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <[email protected]>
+ */
+
+/*\
+ * Verifies that the link state attributes exported under
+ * /sys/class/net/<iface>/ correctly mirror the administrative state of a
+ * veth peer.
+ *
+ * A veth device's carrier is expected to be up only while its peer is up:
+ * veth_open()/veth_close() call netif_carrier_on()/netif_carrier_off() based
+ * on whether the peer is running. The test creates a veth pair and checks
+ * that bringing the peer down and back up is reflected in:
+ *
+ * - carrier - 1 while the peer is up, 0 while it is down
+ * - operstate - "up" while the peer is up, "lowerlayerdown" while it is down
+ * - carrier_up_count / carrier_down_count - incremented by exactly one on
+ *   each corresponding transition
+ * - carrier_changes - always equal to carrier_up_count + carrier_down_count
+ *
+ * Note this only checks the values synchronously after each netlink state
+ * change (which is itself synchronous), it does not use poll(2)/select(2):
+ * unlike some other sysfs attributes, /sys/class/net/<iface>/carrier does not
+ * support poll() based notification on Linux, link state changes are
+ * reported to userspace via rtnetlink instead.
+ *
+ * The test also checks attributes that are not tied to the peer's state:
+ *
+ * - mtu - directly writable via sysfs; a few valid values are written and
+ *   read back, and one syntactically invalid value is rejected (with the mtu
+ *   left unchanged)
+ * - address - read-only in sysfs, changed via rtnetlink instead (the
+ *   interface is brought down first since most drivers require this); while
+ *   doing so, the IFF_UP bit in "flags" is also cross-checked against the
+ *   interface's own administrative state
+ * - netdev_group and ifalias - directly writable via sysfs, a value is
+ *   written and read back
+ *
+ * This needs root to create the veth pair.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <net/if.h>
+
+#include "tst_test.h"
+#include "tst_netdevice.h"
+#include "tst_sysfs_assert.h"
+
+#define IFNAME1 "ltp_vethp1"
+#define IFNAME2 "ltp_vethp2"
+#define IFACE_PATH "/sys/class/net/" IFNAME1
+
+static int veth_created;
+
+static long read_mtu(void)
+{
+	return TST_SYSFS_READ_LI(IFACE_PATH "/mtu");
+}
+
+#include "sys_net_common.h"
+
+static void setup(void)
+{
+	CREATE_VETH_PAIR(IFNAME1, IFNAME2);
+	veth_created = 1;
+
+	NETDEV_SET_STATE(IFNAME1, 1);
+	NETDEV_SET_STATE(IFNAME2, 1);
+}
+
+static unsigned long read_flags(void)
+{
+	return TST_SYSFS_READ_LX(IFACE_PATH "/flags");
+}
+
+static void check_address(void)
+{
+	static const unsigned char new_addr[6] = {0x02, 0, 0, 0, 0, 0x01};
+	char actual[32];
+	long iff_up;
+
+	tst_res(TINFO, "IFNAME1 is up before the address change");
+	iff_up = read_flags() & IFF_UP;
+	TST_EXP_EQ_LI(iff_up, IFF_UP);
+
+	NETDEV_SET_STATE(IFNAME1, 0);
+	tst_res(TINFO, "IFNAME1 is down for the address change");
+	iff_up = read_flags() & IFF_UP;
+	TST_EXP_EQ_LI(iff_up, 0);
+
+	NETDEV_SET_HWADDR(IFNAME1, new_addr, sizeof(new_addr));
+	NETDEV_SET_STATE(IFNAME1, 1);
+	tst_res(TINFO, "IFNAME1 is up again after the address change");
+	iff_up = read_flags() & IFF_UP;
+	TST_EXP_EQ_LI(iff_up, IFF_UP);
+
+	TST_SYSFS_READ_STR(actual, sizeof(actual), IFACE_PATH "/address");
+
+	TST_EXP_EQ_STR(actual, "02:00:00:00:00:01");
+}
+
+static void check_netdev_group(void)
+{
+	long group;
+
+	if (FILE_PRINTF(IFACE_PATH "/netdev_group", "%d", 7)) {
+		tst_res(TFAIL, "Failed to set netdev_group");
+		return;
+	}
+
+	group = TST_SYSFS_READ_LI(IFACE_PATH "/netdev_group");
+
+	TST_EXP_EQ_LI(group, 7);
+}
+
+static void check_ifalias(void)
+{
+	char alias[64];
+
+	if (FILE_PRINTF(IFACE_PATH "/ifalias", "%s", "ltp-test-alias")) {
+		tst_res(TFAIL, "Failed to set ifalias");
+		return;
+	}
+
+	TST_SYSFS_READ_STR(alias, sizeof(alias), IFACE_PATH "/ifalias");
+	TST_EXP_EQ_STR(alias, "ltp-test-alias");
+}
+
+static void run(void)
+{
+	struct netdev_state s0, s1, s2;
+
+	read_state(&s0);
+	check_state(&s0, 1, "up", "both ends up");
+
+	NETDEV_SET_STATE(IFNAME2, 0);
+	read_state(&s1);
+	check_state(&s1, 0, "lowerlayerdown", "peer down");
+	check_state_delta(&s0, &s1, 0, 1, "peer down transition");
+
+	NETDEV_SET_STATE(IFNAME2, 1);
+	read_state(&s2);
+	check_state(&s2, 1, "up", "peer back up");
+	check_state_delta(&s1, &s2, 1, 0, "peer up transition");
+
+	check_mtu_valid(68);
+	check_mtu_valid(1500);
+	check_mtu_valid(9000);
+	check_mtu_valid(65535);
+	check_mtu_invalid("-1");
+	check_mtu_invalid("0");
+	check_mtu_invalid("67");
+	check_mtu_invalid("70000");
+
+	check_address();
+
+	check_netdev_group();
+	check_ifalias();
+}
+
+static void cleanup(void)
+{
+	if (veth_created)
+		NETDEV_REMOVE_DEVICE(IFNAME1);
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_root = 1,
+	.needs_kconfigs = (const char *const[]){
+		"CONFIG_VETH",
+		NULL
+	},
+};
diff --git a/testcases/kernel/sysfs/class/net/sys_net_common.h b/testcases/kernel/sysfs/class/net/sys_net_common.h
new file mode 100644
index 000000000..7b0346c9c
--- /dev/null
+++ b/testcases/kernel/sysfs/class/net/sys_net_common.h
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <[email protected]>
+ */
+
+#ifndef SYS_NET_COMMON
+#define SYS_NET_COMMON
+
+struct netdev_state {
+	long carrier;
+	long carrier_changes;
+	long carrier_up_count;
+	long carrier_down_count;
+	char operstate[32];
+};
+
+static void read_state(struct netdev_state *st)
+{
+	st->carrier = TST_SYSFS_READ_LI(IFACE_PATH "/carrier");
+	st->carrier_changes = TST_SYSFS_READ_LI(IFACE_PATH "/carrier_changes");
+	st->carrier_up_count = TST_SYSFS_READ_LI(IFACE_PATH "/carrier_up_count");
+	st->carrier_down_count = TST_SYSFS_READ_LI(IFACE_PATH "/carrier_down_count");
+	TST_SYSFS_READ_STR(st->operstate, sizeof(st->operstate), IFACE_PATH "/operstate");
+}
+
+static void check_state(const struct netdev_state *st, long carrier,
+			const char *operstate, const char *desc)
+{
+	tst_res(TINFO, "%s", desc);
+
+	TST_EXP_EQ_LI(st->carrier, carrier);
+	TST_EXP_EQ_STR(st->operstate, operstate);
+	TST_EXP_EQ_LI(st->carrier_changes,
+		      st->carrier_up_count + st->carrier_down_count);
+}
+
+static void check_state_delta(const struct netdev_state *prev,
+			      const struct netdev_state *cur, long up_delta,
+			      long down_delta, const char *desc)
+{
+	long got_up = cur->carrier_up_count - prev->carrier_up_count;
+	long got_down = cur->carrier_down_count - prev->carrier_down_count;
+	long got_changes = cur->carrier_changes - prev->carrier_changes;
+
+	tst_res(TINFO, "%s", desc);
+
+	TST_EXP_EQ_LI(got_up, up_delta);
+	TST_EXP_EQ_LI(got_down, down_delta);
+	TST_EXP_EQ_LI(got_changes, up_delta + down_delta);
+}
+
+static void check_mtu_valid(long mtu)
+{
+	char mtu_str[16];
+
+	snprintf(mtu_str, sizeof(mtu_str), "%ld", mtu);
+
+	if (FILE_PRINTF(IFACE_PATH "/mtu", "%s", mtu_str)) {
+		tst_res(TFAIL, "Failed to set mtu to %ld", mtu);
+		return;
+	}
+
+	TST_EXP_EQ_LI(read_mtu(), mtu);
+}
+
+static void check_mtu_invalid(const char *mtu_str)
+{
+	long before = read_mtu();
+
+	if (!FILE_PRINTF(IFACE_PATH "/mtu", "%s", mtu_str)) {
+		tst_res(TFAIL, "Writing mtu '%s' unexpectedly succeeded",
+			mtu_str);
+		return;
+	}
+
+	tst_res(TINFO, "Writing mtu '%s' was rejected as expected", mtu_str);
+	TST_EXP_EQ_LI(read_mtu(), before);
+}
+
+#endif /* SYS_NET_COMMON */
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.