summaryrefslogtreecommitdiff
path: root/tools/testing
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing')
-rw-r--r--tools/testing/selftests/bpf/prog_tests/sockmap_ktls.c420
-rw-r--r--tools/testing/selftests/bpf/prog_tests/test_xsk.c193
-rw-r--r--tools/testing/selftests/bpf/prog_tests/test_xsk.h3
-rw-r--r--tools/testing/selftests/bpf/progs/test_sockmap_kern.h56
-rw-r--r--tools/testing/selftests/bpf/progs/test_sockmap_ktls.c61
-rw-r--r--tools/testing/selftests/bpf/test_sockmap.c250
-rw-r--r--tools/testing/selftests/drivers/net/.gitignore1
-rw-r--r--tools/testing/selftests/drivers/net/Makefile2
-rw-r--r--tools/testing/selftests/drivers/net/bonding/Makefile1
-rwxr-xr-xtools/testing/selftests/drivers/net/bonding/bond_lacp_strict.sh347
-rw-r--r--tools/testing/selftests/drivers/net/config5
-rwxr-xr-xtools/testing/selftests/drivers/net/gro.py29
-rw-r--r--tools/testing/selftests/drivers/net/hw/Makefile3
-rw-r--r--tools/testing/selftests/drivers/net/hw/config5
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/devmem.py77
-rw-r--r--tools/testing/selftests/drivers/net/hw/devmem_lib.py222
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/gro_hw.py10
-rw-r--r--tools/testing/selftests/drivers/net/hw/iou-zcrx.c6
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/iou-zcrx.py38
-rw-r--r--tools/testing/selftests/drivers/net/hw/lib/py/__init__.py4
-rw-r--r--tools/testing/selftests/drivers/net/hw/ncdevmem.c58
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/nk_devmem.py46
-rw-r--r--tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c39
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/nk_qlease.py306
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/ntuple.py5
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/rss_ctx.py98
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/tso.py3
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/userns_devmem.py49
-rw-r--r--tools/testing/selftests/drivers/net/lib/py/__init__.py9
-rw-r--r--tools/testing/selftests/drivers/net/lib/py/env.py160
-rwxr-xr-xtools/testing/selftests/drivers/net/psp.py423
-rw-r--r--tools/testing/selftests/drivers/net/so_txtime.c (renamed from tools/testing/selftests/net/so_txtime.c)33
-rwxr-xr-xtools/testing/selftests/drivers/net/so_txtime.py118
-rw-r--r--tools/testing/selftests/net/.gitignore2
-rw-r--r--tools/testing/selftests/net/Makefile5
-rw-r--r--tools/testing/selftests/net/bind_bhash.c9
-rw-r--r--tools/testing/selftests/net/config3
-rwxr-xr-xtools/testing/selftests/net/ecmp_rehash.sh1109
-rwxr-xr-xtools/testing/selftests/net/fib_tests.sh308
-rwxr-xr-xtools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh140
-rw-r--r--tools/testing/selftests/net/forwarding/ipmr.c189
-rw-r--r--tools/testing/selftests/net/getsockopt_iter.c300
-rw-r--r--tools/testing/selftests/net/lib/gro.c116
-rw-r--r--tools/testing/selftests/net/lib/py/__init__.py8
-rw-r--r--tools/testing/selftests/net/lib/py/netns.py75
-rw-r--r--tools/testing/selftests/net/lib/py/utils.py58
-rwxr-xr-xtools/testing/selftests/net/mptcp/mptcp_join.sh116
-rwxr-xr-xtools/testing/selftests/net/mptcp/mptcp_sockopt.sh2
-rwxr-xr-xtools/testing/selftests/net/mptcp/pm_netlink.sh56
-rw-r--r--tools/testing/selftests/net/mptcp/pm_nl_ctl.c8
-rwxr-xr-xtools/testing/selftests/net/mptcp/simult_flows.sh34
-rw-r--r--tools/testing/selftests/net/netfilter/Makefile1
-rw-r--r--tools/testing/selftests/net/netfilter/config6
-rwxr-xr-xtools/testing/selftests/net/netfilter/nft_offload.sh132
-rwxr-xr-xtools/testing/selftests/net/nl_netdev.py119
-rwxr-xr-xtools/testing/selftests/net/openvswitch/openvswitch.sh292
-rw-r--r--tools/testing/selftests/net/openvswitch/ovs-dpctl.py403
-rw-r--r--tools/testing/selftests/net/packetdrill/tcp_syncookies_ip4_9k.pkt37
-rw-r--r--tools/testing/selftests/net/packetdrill/tcp_syncookies_ip6_9k.pkt36
-rw-r--r--tools/testing/selftests/net/ppp/Makefile1
-rw-r--r--tools/testing/selftests/net/ppp/config2
-rw-r--r--tools/testing/selftests/net/ppp/pppoe-server-options1
-rwxr-xr-xtools/testing/selftests/net/ppp/pppol2tp.sh110
-rwxr-xr-xtools/testing/selftests/net/protodown.sh182
-rw-r--r--tools/testing/selftests/net/rds/.gitignore1
-rw-r--r--tools/testing/selftests/net/rds/Makefile6
-rw-r--r--tools/testing/selftests/net/rds/README.txt59
-rw-r--r--tools/testing/selftests/net/rds/config1
-rwxr-xr-xtools/testing/selftests/net/rds/config.sh18
-rw-r--r--tools/testing/selftests/net/rds/getsockopt.c208
-rwxr-xr-xtools/testing/selftests/net/rds/rds_run.sh319
-rwxr-xr-xtools/testing/selftests/net/rds/run.sh227
-rw-r--r--tools/testing/selftests/net/rds/settings2
-rwxr-xr-xtools/testing/selftests/net/rds/test.py677
-rwxr-xr-xtools/testing/selftests/net/rtnetlink.py41
-rwxr-xr-xtools/testing/selftests/net/so_txtime.sh110
-rw-r--r--tools/testing/selftests/net/tcp_ao/config4
-rw-r--r--tools/testing/selftests/net/tcp_ao/key-management.c41
-rwxr-xr-xtools/testing/selftests/net/test_bridge_neigh_suppress.sh468
-rw-r--r--tools/testing/selftests/net/tls.c4
-rw-r--r--tools/testing/selftests/tc-testing/config1
-rw-r--r--tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py7
-rw-r--r--tools/testing/selftests/tc-testing/tc-tests/actions/ife.json55
-rw-r--r--tools/testing/selftests/tc-testing/tc-tests/actions/pedit.json49
-rw-r--r--tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json344
-rwxr-xr-xtools/testing/selftests/tc-testing/tdc.py3
-rw-r--r--tools/testing/selftests/tc-testing/tdc_config.py1
-rwxr-xr-xtools/testing/selftests/vsock/vmtest.sh47
88 files changed, 7692 insertions, 1941 deletions
diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_ktls.c b/tools/testing/selftests/bpf/prog_tests/sockmap_ktls.c
index 6ed8e149e3d5..34737e8df6ea 100644
--- a/tools/testing/selftests/bpf/prog_tests/sockmap_ktls.c
+++ b/tools/testing/selftests/bpf/prog_tests/sockmap_ktls.c
@@ -9,7 +9,6 @@
#include "test_progs.h"
#include "sockmap_helpers.h"
#include "test_skmsg_load_helpers.skel.h"
-#include "test_sockmap_ktls.skel.h"
#define MAX_TEST_NAME 80
#define TCP_ULP 31
@@ -117,6 +116,68 @@ close:
close(s);
}
+static void test_sockmap_ktls_enable_fails_when_in_sockmap(int family, int map)
+{
+ struct tls12_crypto_info_aes_gcm_128 crypto = {
+ .info = {
+ .version = TLS_1_2_VERSION,
+ .cipher_type = TLS_CIPHER_AES_GCM_128,
+ },
+ };
+ struct sockaddr_storage addr = {};
+ socklen_t len = sizeof(addr);
+ struct sockaddr_in6 *v6;
+ struct sockaddr_in *v4;
+ int err, s, zero = 0;
+
+ switch (family) {
+ case AF_INET:
+ v4 = (struct sockaddr_in *)&addr;
+ v4->sin_family = AF_INET;
+ break;
+ case AF_INET6:
+ v6 = (struct sockaddr_in6 *)&addr;
+ v6->sin6_family = AF_INET6;
+ break;
+ default:
+ PRINT_FAIL("unsupported socket family %d", family);
+ return;
+ }
+
+ s = socket(family, SOCK_STREAM, 0);
+ if (!ASSERT_GE(s, 0, "socket"))
+ return;
+
+ err = bind(s, (struct sockaddr *)&addr, len);
+ if (!ASSERT_OK(err, "bind"))
+ goto close;
+
+ err = getsockname(s, (struct sockaddr *)&addr, &len);
+ if (!ASSERT_OK(err, "getsockname"))
+ goto close;
+
+ err = connect(s, (struct sockaddr *)&addr, len);
+ if (!ASSERT_OK(err, "connect"))
+ goto close;
+
+ /* Add the socket to the sockmap, attaching a psock. */
+ err = bpf_map_update_elem(map, &zero, &s, BPF_ANY);
+ if (!ASSERT_OK(err, "sockmap update elem"))
+ goto close;
+
+ /* Installing the TLS ULP is allowed, it does not touch the datapath. */
+ err = setsockopt(s, IPPROTO_TCP, TCP_ULP, "tls", strlen("tls"));
+ if (!ASSERT_OK(err, "setsockopt(TCP_ULP)"))
+ goto close;
+
+ /* Enabling the TLS crypto datapath must be rejected. */
+ err = setsockopt(s, SOL_TLS, TLS_TX, &crypto, sizeof(crypto));
+ ASSERT_ERR(err, "setsockopt(TLS_TX)");
+
+close:
+ close(s);
+}
+
static const char *fmt_test_name(const char *subtest_name, int family,
enum bpf_map_type map_type)
{
@@ -160,249 +221,6 @@ out:
close(p);
}
-static void test_sockmap_ktls_tx_cork(int family, int sotype, bool push)
-{
- int err, off;
- int i, j;
- int start_push = 0, push_len = 0;
- int c = 0, p = 0, one = 1, sent, recvd;
- int prog_fd, map_fd;
- char msg[12] = "hello world\0";
- char rcv[20] = {0};
- struct test_sockmap_ktls *skel;
-
- skel = test_sockmap_ktls__open_and_load();
- if (!ASSERT_TRUE(skel, "open ktls skel"))
- return;
-
- err = create_pair(family, sotype, &c, &p);
- if (!ASSERT_OK(err, "create_pair()"))
- goto out;
-
- prog_fd = bpf_program__fd(skel->progs.prog_sk_policy);
- map_fd = bpf_map__fd(skel->maps.sock_map);
-
- err = bpf_prog_attach(prog_fd, map_fd, BPF_SK_MSG_VERDICT, 0);
- if (!ASSERT_OK(err, "bpf_prog_attach sk msg"))
- goto out;
-
- err = bpf_map_update_elem(map_fd, &one, &c, BPF_NOEXIST);
- if (!ASSERT_OK(err, "bpf_map_update_elem(c)"))
- goto out;
-
- err = init_ktls_pairs(c, p);
- if (!ASSERT_OK(err, "init_ktls_pairs(c, p)"))
- goto out;
-
- skel->bss->cork_byte = sizeof(msg);
- if (push) {
- start_push = 1;
- push_len = 2;
- }
- skel->bss->push_start = start_push;
- skel->bss->push_end = push_len;
-
- off = sizeof(msg) / 2;
- sent = send(c, msg, off, 0);
- if (!ASSERT_EQ(sent, off, "send(msg)"))
- goto out;
-
- recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, 1);
- if (!ASSERT_EQ(-1, recvd, "expected no data"))
- goto out;
-
- /* send remaining msg */
- sent = send(c, msg + off, sizeof(msg) - off, 0);
- if (!ASSERT_EQ(sent, sizeof(msg) - off, "send remaining data"))
- goto out;
-
- recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, 1);
- if (!ASSERT_OK(err, "recv(msg)") ||
- !ASSERT_EQ(recvd, sizeof(msg) + push_len, "check length mismatch"))
- goto out;
-
- for (i = 0, j = 0; i < recvd;) {
- /* skip checking the data that has been pushed in */
- if (i >= start_push && i <= start_push + push_len - 1) {
- i++;
- continue;
- }
- if (!ASSERT_EQ(rcv[i], msg[j], "data mismatch"))
- goto out;
- i++;
- j++;
- }
-out:
- if (c)
- close(c);
- if (p)
- close(p);
- test_sockmap_ktls__destroy(skel);
-}
-
-static void test_sockmap_ktls_tx_no_buf(int family, int sotype, bool push)
-{
- int c = -1, p = -1, one = 1, two = 2;
- struct test_sockmap_ktls *skel;
- unsigned char *data = NULL;
- struct msghdr msg = {0};
- struct iovec iov[2];
- int prog_fd, map_fd;
- int txrx_buf = 1024;
- int iov_length = 8192;
- int err;
-
- skel = test_sockmap_ktls__open_and_load();
- if (!ASSERT_TRUE(skel, "open ktls skel"))
- return;
-
- err = create_pair(family, sotype, &c, &p);
- if (!ASSERT_OK(err, "create_pair()"))
- goto out;
-
- err = setsockopt(c, SOL_SOCKET, SO_RCVBUFFORCE, &txrx_buf, sizeof(int));
- err |= setsockopt(p, SOL_SOCKET, SO_SNDBUFFORCE, &txrx_buf, sizeof(int));
- if (!ASSERT_OK(err, "set buf limit"))
- goto out;
-
- prog_fd = bpf_program__fd(skel->progs.prog_sk_policy_redir);
- map_fd = bpf_map__fd(skel->maps.sock_map);
-
- err = bpf_prog_attach(prog_fd, map_fd, BPF_SK_MSG_VERDICT, 0);
- if (!ASSERT_OK(err, "bpf_prog_attach sk msg"))
- goto out;
-
- err = bpf_map_update_elem(map_fd, &one, &c, BPF_NOEXIST);
- if (!ASSERT_OK(err, "bpf_map_update_elem(c)"))
- goto out;
-
- err = bpf_map_update_elem(map_fd, &two, &p, BPF_NOEXIST);
- if (!ASSERT_OK(err, "bpf_map_update_elem(p)"))
- goto out;
-
- skel->bss->apply_bytes = 1024;
-
- err = init_ktls_pairs(c, p);
- if (!ASSERT_OK(err, "init_ktls_pairs(c, p)"))
- goto out;
-
- data = calloc(iov_length, sizeof(char));
- if (!data)
- goto out;
-
- iov[0].iov_base = data;
- iov[0].iov_len = iov_length;
- iov[1].iov_base = data;
- iov[1].iov_len = iov_length;
- msg.msg_iov = iov;
- msg.msg_iovlen = 2;
-
- for (;;) {
- err = sendmsg(c, &msg, MSG_DONTWAIT);
- if (err <= 0)
- break;
- }
-
-out:
- if (data)
- free(data);
- if (c != -1)
- close(c);
- if (p != -1)
- close(p);
-
- test_sockmap_ktls__destroy(skel);
-}
-
-static void test_sockmap_ktls_tx_pop(int family, int sotype)
-{
- char msg[37] = "0123456789abcdefghijklmnopqrstuvwxyz\0";
- int c = 0, p = 0, one = 1, sent, recvd;
- struct test_sockmap_ktls *skel;
- int prog_fd, map_fd;
- char rcv[50] = {0};
- int err;
- int i, m, r;
-
- skel = test_sockmap_ktls__open_and_load();
- if (!ASSERT_TRUE(skel, "open ktls skel"))
- return;
-
- err = create_pair(family, sotype, &c, &p);
- if (!ASSERT_OK(err, "create_pair()"))
- goto out;
-
- prog_fd = bpf_program__fd(skel->progs.prog_sk_policy);
- map_fd = bpf_map__fd(skel->maps.sock_map);
-
- err = bpf_prog_attach(prog_fd, map_fd, BPF_SK_MSG_VERDICT, 0);
- if (!ASSERT_OK(err, "bpf_prog_attach sk msg"))
- goto out;
-
- err = bpf_map_update_elem(map_fd, &one, &c, BPF_NOEXIST);
- if (!ASSERT_OK(err, "bpf_map_update_elem(c)"))
- goto out;
-
- err = init_ktls_pairs(c, p);
- if (!ASSERT_OK(err, "init_ktls_pairs(c, p)"))
- goto out;
-
- struct {
- int pop_start;
- int pop_len;
- } pop_policy[] = {
- /* trim the start */
- {0, 2},
- {0, 10},
- {1, 2},
- {1, 10},
- /* trim the end */
- {35, 2},
- /* New entries should be added before this line */
- {-1, -1},
- };
-
- i = 0;
- while (pop_policy[i].pop_start >= 0) {
- skel->bss->pop_start = pop_policy[i].pop_start;
- skel->bss->pop_end = pop_policy[i].pop_len;
-
- sent = send(c, msg, sizeof(msg), 0);
- if (!ASSERT_EQ(sent, sizeof(msg), "send(msg)"))
- goto out;
-
- recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, 1);
- if (!ASSERT_EQ(recvd, sizeof(msg) - pop_policy[i].pop_len, "pop len mismatch"))
- goto out;
-
- /* verify the data
- * msg: 0123456789a bcdefghij klmnopqrstuvwxyz
- * | |
- * popped data
- */
- for (m = 0, r = 0; m < sizeof(msg);) {
- /* skip checking the data that has been popped */
- if (m >= pop_policy[i].pop_start &&
- m <= pop_policy[i].pop_start + pop_policy[i].pop_len - 1) {
- m++;
- continue;
- }
-
- if (!ASSERT_EQ(msg[m], rcv[r], "data mismatch"))
- goto out;
- m++;
- r++;
- }
- i++;
- }
-out:
- if (c)
- close(c);
- if (p)
- close(p);
- test_sockmap_ktls__destroy(skel);
-}
-
static void run_tests(int family, enum bpf_map_type map_type)
{
int map;
@@ -414,124 +232,16 @@ static void run_tests(int family, enum bpf_map_type map_type)
if (test__start_subtest(fmt_test_name("update_fails_when_sock_has_ulp", family, map_type)))
test_sockmap_ktls_update_fails_when_sock_has_ulp(family, map);
- close(map);
-}
+ if (test__start_subtest(fmt_test_name("enable_fails_when_in_sockmap", family, map_type)))
+ test_sockmap_ktls_enable_fails_when_in_sockmap(family, map);
-/*
- * Regression test for the KTLS + sockmap (verdict) reverse-order UAF.
- *
- * Vulnerable sequence:
- * 1. Insert receiver socket into sockmap with BPF_SK_SKB_VERDICT program.
- * sk->sk_data_ready becomes sk_psock_verdict_data_ready.
- * 2. Configure TLS RX: tls_sw_strparser_arm() saves
- * sk_psock_verdict_data_ready as rx_ctx->saved_data_ready.
- *
- * When data arrives, tls_rx_msg_ready() calls saved_data_ready() =
- * sk_psock_verdict_data_ready(), which calls tcp_read_skb() and drains
- * sk_receive_queue via __skb_unlink() without advancing copied_seq.
- * tls_strp_msg_load() then finds the queue empty while tcp_inq() is still
- * non-zero, hits WARN_ON_ONCE(!first), and leaves a dangling frag_list
- * pointer that tls_decrypt_sg() walks — a use-after-free.
- *
- * The fix adds a tls_sw_has_ctx_rx() check to sk_psock_verdict_data_ready(),
- * mirroring what sk_psock_strp_data_ready() already does: when a TLS RX
- * context is present, defer to psock->saved_data_ready (sock_def_readable)
- * instead of calling tcp_read_skb(), so TLS retains sole ownership of the
- * receive queue. Data is then decrypted and returned correctly by
- * tls_sw_recvmsg().
- */
-static void test_sockmap_ktls_verdict_with_tls_rx(int family, int sotype)
-{
- struct tls12_crypto_info_aes_gcm_128 crypto_info = {};
- char send_buf[] = "hello ktls sockmap reverse order";
- char recv_buf[sizeof(send_buf)] = {};
- struct test_sockmap_ktls *skel;
- int c = -1, p = -1, zero = 0;
- int prog_fd, map_fd;
- ssize_t n;
- int err;
-
- skel = test_sockmap_ktls__open_and_load();
- if (!ASSERT_TRUE(skel, "open_and_load"))
- return;
-
- err = create_pair(family, sotype, &c, &p);
- if (!ASSERT_OK(err, "create_pair"))
- goto out;
-
- prog_fd = bpf_program__fd(skel->progs.prog_skb_verdict_pass);
- map_fd = bpf_map__fd(skel->maps.sock_map_verdict);
-
- err = bpf_prog_attach(prog_fd, map_fd, BPF_SK_SKB_VERDICT, 0);
- if (!ASSERT_OK(err, "bpf_prog_attach sk_skb verdict"))
- goto out;
-
- /* Step 1: configure TLS TX on sender (no sockmap involvement) */
- err = setsockopt(c, IPPROTO_TCP, TCP_ULP, "tls", strlen("tls"));
- if (!ASSERT_OK(err, "setsockopt(TCP_ULP) client"))
- goto out;
-
- crypto_info.info.version = TLS_1_2_VERSION;
- crypto_info.info.cipher_type = TLS_CIPHER_AES_GCM_128;
- memset(crypto_info.key, 0x01, sizeof(crypto_info.key));
- memset(crypto_info.salt, 0x02, sizeof(crypto_info.salt));
-
- err = setsockopt(c, SOL_TLS, TLS_TX, &crypto_info, sizeof(crypto_info));
- if (!ASSERT_OK(err, "setsockopt(TLS_TX)"))
- goto out;
-
- /* Step 2: insert receiver into sockmap BEFORE TLS RX */
- err = bpf_map_update_elem(map_fd, &zero, &p, BPF_NOEXIST);
- if (!ASSERT_OK(err, "bpf_map_update_elem"))
- goto out;
-
- /* Step 3: configure TLS RX AFTER sockmap insertion */
- err = setsockopt(p, IPPROTO_TCP, TCP_ULP, "tls", strlen("tls"));
- if (!ASSERT_OK(err, "setsockopt(TCP_ULP) server"))
- goto out;
-
- err = setsockopt(p, SOL_TLS, TLS_RX, &crypto_info, sizeof(crypto_info));
- if (!ASSERT_OK(err, "setsockopt(TLS_RX)"))
- goto out;
-
- /*
- * A buggy kernel hits WARN_ON_ONCE in tls_strp_load_anchor_with_queue
- * and may UAF in tls_decrypt_sg here. With the fix,
- * sk_psock_verdict_data_ready defers to sock_def_readable and TLS
- * decrypts the record normally.
- */
- n = send(c, send_buf, sizeof(send_buf), 0);
- if (!ASSERT_EQ(n, (ssize_t)sizeof(send_buf), "send"))
- goto out;
-
- n = recv_timeout(p, recv_buf, sizeof(recv_buf), 0, 5);
- if (!ASSERT_EQ(n, (ssize_t)sizeof(send_buf), "recv"))
- goto out;
-
- ASSERT_OK(memcmp(send_buf, recv_buf, sizeof(send_buf)), "data integrity");
-
-out:
- if (c != -1)
- close(c);
- if (p != -1)
- close(p);
- test_sockmap_ktls__destroy(skel);
+ close(map);
}
static void run_ktls_test(int family, int sotype)
{
if (test__start_subtest("tls simple offload"))
test_sockmap_ktls_offload(family, sotype);
- if (test__start_subtest("tls tx cork"))
- test_sockmap_ktls_tx_cork(family, sotype, false);
- if (test__start_subtest("tls tx cork with push"))
- test_sockmap_ktls_tx_cork(family, sotype, true);
- if (test__start_subtest("tls tx egress with no buf"))
- test_sockmap_ktls_tx_no_buf(family, sotype, true);
- if (test__start_subtest("tls tx with pop"))
- test_sockmap_ktls_tx_pop(family, sotype);
- if (test__start_subtest("tls verdict with tls rx"))
- test_sockmap_ktls_verdict_with_tls_rx(family, sotype);
}
void test_sockmap_ktls(void)
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
index 7950c504ed28..72875071d4f1 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
@@ -65,9 +65,9 @@ static void gen_eth_hdr(struct xsk_socket_info *xsk, struct ethhdr *eth_hdr)
eth_hdr->h_proto = htons(ETH_P_LOOPBACK);
}
-static bool is_umem_valid(struct ifobject *ifobj)
+static bool is_umem_valid(struct xsk_socket_info *xsk)
{
- return !!ifobj->umem->umem;
+ return !!xsk->umem->umem;
}
static u32 mode_to_xdp_flags(enum test_mode mode)
@@ -213,6 +213,7 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
for (i = 0; i < MAX_INTERFACES; i++) {
struct ifobject *ifobj = i ? ifobj_rx : ifobj_tx;
+ struct xsk_umem_info *umem_real;
ifobj->xsk = &ifobj->xsk_arr[0];
ifobj->use_poll = false;
@@ -229,24 +230,30 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx,
ifobj->tx_on = false;
}
- memset(ifobj->umem, 0, sizeof(*ifobj->umem));
- ifobj->umem->num_frames = DEFAULT_UMEM_BUFFERS;
- ifobj->umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE;
-
+ umem_real = ifobj->xsk_arr[0].umem_real;
+ memset(umem_real, 0, sizeof(*umem_real));
for (j = 0; j < MAX_SOCKETS; j++) {
- memset(&ifobj->xsk_arr[j], 0, sizeof(ifobj->xsk_arr[j]));
- ifobj->xsk_arr[j].rxqsize = XSK_RING_CONS__DEFAULT_NUM_DESCS;
- ifobj->xsk_arr[j].batch_size = DEFAULT_BATCH_SIZE;
+ struct xsk_socket_info *xsk = &ifobj->xsk_arr[j];
+
+ memset(xsk, 0, sizeof(*xsk));
+ xsk->rxqsize = XSK_RING_CONS__DEFAULT_NUM_DESCS;
+ if (j == 0)
+ xsk->umem_real = umem_real;
+ xsk->umem = umem_real;
+ xsk->batch_size = DEFAULT_BATCH_SIZE;
if (i == 0)
- ifobj->xsk_arr[j].pkt_stream = test->tx_pkt_stream_default;
+ xsk->pkt_stream = test->tx_pkt_stream_default;
else
- ifobj->xsk_arr[j].pkt_stream = test->rx_pkt_stream_default;
+ xsk->pkt_stream = test->rx_pkt_stream_default;
- memcpy(ifobj->xsk_arr[j].src_mac, g_mac, ETH_ALEN);
- memcpy(ifobj->xsk_arr[j].dst_mac, g_mac, ETH_ALEN);
- ifobj->xsk_arr[j].src_mac[5] += ((j * 2) + 0);
- ifobj->xsk_arr[j].dst_mac[5] += ((j * 2) + 1);
+ memcpy(xsk->src_mac, g_mac, ETH_ALEN);
+ memcpy(xsk->dst_mac, g_mac, ETH_ALEN);
+ xsk->src_mac[5] += ((j * 2) + 0);
+ xsk->dst_mac[5] += ((j * 2) + 1);
}
+
+ ifobj->xsk->umem->num_frames = DEFAULT_UMEM_BUFFERS;
+ ifobj->xsk->umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE;
}
if (ifobj_tx->hw_ring_size_supp)
@@ -303,6 +310,18 @@ static void test_spec_reset(struct test_spec *test)
__test_spec_init(test, test->ifobj_tx, test->ifobj_rx);
}
+static void test_spec_set_unaligned(struct test_spec *test)
+{
+ test->ifobj_tx->xsk->umem->unaligned_mode = true;
+ test->ifobj_rx->xsk->umem->unaligned_mode = true;
+}
+
+static void test_spec_set_frame_size(struct test_spec *test, u32 size)
+{
+ test->ifobj_tx->xsk->umem->frame_size = size;
+ test->ifobj_rx->xsk->umem->frame_size = size;
+}
+
static void test_spec_set_xdp_prog(struct test_spec *test, struct bpf_program *xdp_prog_rx,
struct bpf_program *xdp_prog_tx, struct bpf_map *xskmap_rx,
struct bpf_map *xskmap_tx)
@@ -810,11 +829,11 @@ static bool is_frag_valid(struct xsk_umem_info *umem, u64 addr, u32 len, u32 exp
{
u32 seqnum, pkt_nb, *pkt_data, words_to_end, expected_seqnum;
void *data = xsk_umem__get_data(umem->buffer, addr);
+ u64 umem_sz = umem_size(umem);
addr -= umem->base_addr;
- if (addr >= umem->num_frames * umem->frame_size ||
- addr + len > umem->num_frames * umem->frame_size) {
+ if (addr >= umem_sz || addr + len > umem_sz) {
ksft_print_msg("Frag invalid addr: %llx len: %u\n",
(unsigned long long)addr, len);
return false;
@@ -991,7 +1010,7 @@ static int __receive_pkts(struct test_spec *test, struct xsk_socket_info *xsk)
return TEST_FAILURE;
if (!ret) {
- if (!is_umem_valid(test->ifobj_tx))
+ if (!is_umem_valid(test->ifobj_tx->xsk))
return TEST_PASS;
ksft_print_msg("ERROR: [%s] Poll timed out\n", __func__);
@@ -1151,7 +1170,7 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk, b
{
u32 i, idx = 0, valid_pkts = 0, valid_frags = 0, buffer_len;
struct pkt_stream *pkt_stream = xsk->pkt_stream;
- struct xsk_umem_info *umem = ifobject->umem;
+ struct xsk_umem_info *umem = xsk->umem;
bool use_poll = ifobject->use_poll;
struct pollfd fds = { };
int ret;
@@ -1210,7 +1229,7 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk, b
while (nb_frags_left--) {
struct xdp_desc *tx_desc = xsk_ring_prod__tx_desc(&xsk->tx, idx + i);
- tx_desc->addr = pkt_get_addr(pkt, ifobject->umem);
+ tx_desc->addr = pkt_get_addr(pkt, umem);
if (pkt_stream->verbatim) {
tx_desc->len = pkt->len;
tx_desc->options = pkt->options;
@@ -1303,7 +1322,7 @@ bool all_packets_sent(struct test_spec *test, unsigned long *bitmap)
static int send_pkts(struct test_spec *test, struct ifobject *ifobject)
{
- bool timeout = !is_umem_valid(test->ifobj_rx);
+ bool timeout = !is_umem_valid(test->ifobj_rx->xsk);
DECLARE_BITMAP(bitmap, test->nb_sockets);
u32 i, ret;
@@ -1488,14 +1507,25 @@ static int xsk_configure(struct test_spec *test, struct ifobject *ifobject,
static int thread_common_ops_tx(struct test_spec *test, struct ifobject *ifobject)
{
- int ret = xsk_configure(test, ifobject, test->ifobj_rx->umem, true);
+ struct xsk_umem_info *umem_rx, *umem_tx;
+ int ret;
+ if (!test->ifobj_rx || !test->ifobj_rx->xsk_arr[0].umem->umem) {
+ ksft_print_msg("Error: RX UMEM is not initialized before shared-UMEM TX setup\n");
+ return -EINVAL;
+ }
+
+ umem_rx = test->ifobj_rx->xsk_arr[0].umem;
+ umem_tx = ifobject->xsk_arr[0].umem_real;
+ memcpy(umem_tx, umem_rx, sizeof(*umem_tx));
+ umem_tx->base_addr = 0;
+ umem_tx->next_buffer = 0;
+
+ ret = xsk_configure(test, ifobject, umem_tx, true);
if (ret)
return ret;
ifobject->xsk = &ifobject->xsk_arr[0];
ifobject->xskmap = test->ifobj_rx->xskmap;
- memcpy(ifobject->umem, test->ifobj_rx->umem, sizeof(struct xsk_umem_info));
- ifobject->umem->base_addr = 0;
return 0;
}
@@ -1548,31 +1578,37 @@ static int xsk_populate_fill_ring(struct xsk_umem_info *umem, struct pkt_stream
static int thread_common_ops(struct test_spec *test, struct ifobject *ifobject)
{
+ struct xsk_umem_info *umem = ifobject->xsk->umem;
LIBBPF_OPTS(bpf_xdp_query_opts, opts);
int mmap_flags;
- u64 umem_sz;
+ u64 umem_sz, mmap_sz;
void *bufs;
int ret;
u32 i;
- umem_sz = ifobject->umem->num_frames * ifobject->umem->frame_size;
+ umem_sz = umem_size(umem);
mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE;
- if (ifobject->umem->unaligned_mode)
+ if (umem->unaligned_mode)
mmap_flags |= MAP_HUGETLB | MAP_HUGE_2MB;
if (ifobject->shared_umem)
umem_sz *= 2;
- bufs = mmap(NULL, umem_sz, PROT_READ | PROT_WRITE, mmap_flags, -1, 0);
+ mmap_sz = umem->unaligned_mode ?
+ ceil_u64(umem_sz, HUGEPAGE_SIZE) * HUGEPAGE_SIZE : umem_sz;
+
+ bufs = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE, mmap_flags, -1, 0);
if (bufs == MAP_FAILED)
return -errno;
- ret = xsk_configure_umem(ifobject, ifobject->umem, bufs, umem_sz);
+ umem->mmap_size = mmap_sz;
+
+ ret = xsk_configure_umem(ifobject, umem, bufs, umem_sz);
if (ret)
return ret;
- ret = xsk_configure(test, ifobject, ifobject->umem, false);
+ ret = xsk_configure(test, ifobject, umem, false);
if (ret)
return ret;
@@ -1581,14 +1617,13 @@ static int thread_common_ops(struct test_spec *test, struct ifobject *ifobject)
if (!ifobject->rx_on)
return 0;
- ret = xsk_populate_fill_ring(ifobject->umem, ifobject->xsk->pkt_stream,
+ ret = xsk_populate_fill_ring(umem, ifobject->xsk->pkt_stream,
ifobject->use_fill_ring);
if (ret)
return ret;
for (i = 0; i < test->nb_sockets; i++) {
- ifobject->xsk = &ifobject->xsk_arr[i];
- ret = xsk_update_xskmap(ifobject->xskmap, ifobject->xsk->xsk, i);
+ ret = xsk_update_xskmap(ifobject->xskmap, ifobject->xsk_arr[i].xsk, i);
if (ret)
return ret;
}
@@ -1675,14 +1710,10 @@ void *worker_testapp_validate_rx(void *arg)
static void testapp_clean_xsk_umem(struct ifobject *ifobj)
{
- u64 umem_sz = ifobj->umem->num_frames * ifobj->umem->frame_size;
-
- if (ifobj->shared_umem)
- umem_sz *= 2;
+ struct xsk_umem_info *umem = ifobj->xsk->umem;
- umem_sz = ceil_u64(umem_sz, HUGEPAGE_SIZE) * HUGEPAGE_SIZE;
- xsk_umem__delete(ifobj->umem->umem);
- munmap(ifobj->umem->buffer, umem_sz);
+ xsk_umem__delete(umem->umem);
+ munmap(umem->buffer, umem->mmap_size);
}
static void handler(int signum)
@@ -1825,8 +1856,7 @@ static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *i
if (!ifobj2)
pthread_kill(t0, SIGUSR1);
- else
- pthread_join(t0, NULL);
+ pthread_join(t0, NULL);
if (test->total_steps == test->current_step || test->fail) {
clean_sockets(test, ifobj1);
@@ -1845,8 +1875,8 @@ static int testapp_validate_traffic(struct test_spec *test)
struct ifobject *ifobj_rx = test->ifobj_rx;
struct ifobject *ifobj_tx = test->ifobj_tx;
- if ((ifobj_rx->umem->unaligned_mode && !ifobj_rx->unaligned_supp) ||
- (ifobj_tx->umem->unaligned_mode && !ifobj_tx->unaligned_supp)) {
+ if ((ifobj_rx->xsk->umem->unaligned_mode && !ifobj_rx->unaligned_supp) ||
+ (ifobj_tx->xsk->umem->unaligned_mode && !ifobj_tx->unaligned_supp)) {
ksft_print_msg("No huge pages present.\n");
return TEST_SKIP;
}
@@ -1953,12 +1983,13 @@ int testapp_xdp_prog_cleanup(struct test_spec *test)
int testapp_headroom(struct test_spec *test)
{
- test->ifobj_rx->umem->frame_headroom = UMEM_HEADROOM_TEST_SIZE;
+ test->ifobj_rx->xsk->umem->frame_headroom = UMEM_HEADROOM_TEST_SIZE;
return testapp_validate_traffic(test);
}
int testapp_stats_rx_dropped(struct test_spec *test)
{
+ struct xsk_umem_info *umem = test->ifobj_rx->xsk->umem;
u32 umem_tr = test->ifobj_tx->umem_tailroom;
if (test->mode == TEST_MODE_ZC) {
@@ -1968,7 +1999,7 @@ int testapp_stats_rx_dropped(struct test_spec *test)
if (pkt_stream_replace_half(test, (MIN_PKT_SIZE * 3) + umem_tr, 0))
return TEST_FAILURE;
- test->ifobj_rx->umem->frame_headroom = test->ifobj_rx->umem->frame_size -
+ umem->frame_headroom = umem->frame_size -
XDP_PACKET_HEADROOM - (MIN_PKT_SIZE * 2) - umem_tr;
if (pkt_stream_receive_half(test))
return TEST_FAILURE;
@@ -2025,8 +2056,7 @@ int testapp_stats_fill_empty(struct test_spec *test)
int testapp_send_receive_unaligned(struct test_spec *test)
{
- test->ifobj_tx->umem->unaligned_mode = true;
- test->ifobj_rx->umem->unaligned_mode = true;
+ test_spec_set_unaligned(test);
/* Let half of the packets straddle a 4K buffer boundary */
if (pkt_stream_replace_half(test, MIN_PKT_SIZE, -MIN_PKT_SIZE / 2))
return TEST_FAILURE;
@@ -2037,8 +2067,7 @@ int testapp_send_receive_unaligned(struct test_spec *test)
int testapp_send_receive_unaligned_mb(struct test_spec *test)
{
test->mtu = MAX_ETH_JUMBO_SIZE;
- test->ifobj_tx->umem->unaligned_mode = true;
- test->ifobj_rx->umem->unaligned_mode = true;
+ test_spec_set_unaligned(test);
if (pkt_stream_replace(test, DEFAULT_PKT_CNT, MAX_ETH_JUMBO_SIZE))
return TEST_FAILURE;
return testapp_validate_traffic(test);
@@ -2064,8 +2093,8 @@ int testapp_send_receive_mb(struct test_spec *test)
int testapp_invalid_desc_mb(struct test_spec *test)
{
- struct xsk_umem_info *umem = test->ifobj_tx->umem;
- u64 umem_size = umem->num_frames * umem->frame_size;
+ struct xsk_umem_info *umem = test->ifobj_tx->xsk->umem;
+ u64 umem_sz = umem_size(umem);
struct pkt pkts[] = {
/* Valid packet for synch to start with */
{0, MIN_PKT_SIZE, 0, true, 0},
@@ -2075,7 +2104,7 @@ int testapp_invalid_desc_mb(struct test_spec *test)
{0, 0, 0, false, 0},
/* Invalid address in the second frame */
{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
- {umem_size, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
+ {umem_sz, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
/* Invalid len in the middle */
{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
{0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
@@ -2105,8 +2134,8 @@ int testapp_invalid_desc_mb(struct test_spec *test)
int testapp_invalid_desc(struct test_spec *test)
{
- struct xsk_umem_info *umem = test->ifobj_tx->umem;
- u64 umem_size = umem->num_frames * umem->frame_size;
+ struct xsk_umem_info *umem = test->ifobj_tx->xsk->umem;
+ u64 umem_sz = umem_size(umem);
struct pkt pkts[] = {
/* Zero packet address allowed */
{0, MIN_PKT_SIZE, 0, true},
@@ -2117,11 +2146,11 @@ int testapp_invalid_desc(struct test_spec *test)
/* Packet too large */
{0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false},
/* Up to end of umem allowed */
- {umem_size - MIN_PKT_SIZE - 2 * umem->frame_size, MIN_PKT_SIZE, 0, true},
+ {umem_sz - MIN_PKT_SIZE - 2 * umem->frame_size, MIN_PKT_SIZE, 0, true},
/* After umem ends */
- {umem_size, MIN_PKT_SIZE, 0, false},
+ {umem_sz, MIN_PKT_SIZE, 0, false},
/* Straddle the end of umem */
- {umem_size - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, false},
+ {umem_sz - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, false},
/* Straddle a 4K boundary */
{0x1000 - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, false},
/* Straddle a 2K boundary */
@@ -2139,9 +2168,9 @@ int testapp_invalid_desc(struct test_spec *test)
}
if (test->ifobj_tx->shared_umem) {
- pkts[4].offset += umem_size;
- pkts[5].offset += umem_size;
- pkts[6].offset += umem_size;
+ pkts[4].offset += umem_sz;
+ pkts[5].offset += umem_sz;
+ pkts[6].offset += umem_sz;
}
if (pkt_stream_generate_custom(test, pkts, ARRAY_SIZE(pkts)))
@@ -2204,7 +2233,7 @@ int testapp_poll_txq_tmout(struct test_spec *test)
{
test->ifobj_tx->use_poll = true;
/* create invalid frame by set umem frame_size and pkt length equal to 2048 */
- test->ifobj_tx->umem->frame_size = 2048;
+ test->ifobj_tx->xsk->umem->frame_size = 2048;
if (pkt_stream_replace(test, 2 * DEFAULT_PKT_CNT, 2048))
return TEST_FAILURE;
return testapp_validate_traffic_single_thread(test, test->ifobj_tx);
@@ -2337,8 +2366,7 @@ int testapp_send_receive(struct test_spec *test)
int testapp_send_receive_2k_frame(struct test_spec *test)
{
- test->ifobj_tx->umem->frame_size = 2048;
- test->ifobj_rx->umem->frame_size = 2048;
+ test_spec_set_frame_size(test, 2048);
if (pkt_stream_replace(test, DEFAULT_PKT_CNT, MIN_PKT_SIZE))
return TEST_FAILURE;
return testapp_validate_traffic(test);
@@ -2363,34 +2391,30 @@ int testapp_aligned_inv_desc(struct test_spec *test)
int testapp_aligned_inv_desc_2k_frame(struct test_spec *test)
{
- test->ifobj_tx->umem->frame_size = 2048;
- test->ifobj_rx->umem->frame_size = 2048;
+ test_spec_set_frame_size(test, 2048);
return testapp_invalid_desc(test);
}
int testapp_unaligned_inv_desc(struct test_spec *test)
{
- test->ifobj_tx->umem->unaligned_mode = true;
- test->ifobj_rx->umem->unaligned_mode = true;
+ test_spec_set_unaligned(test);
return testapp_invalid_desc(test);
}
int testapp_unaligned_inv_desc_4001_frame(struct test_spec *test)
{
- u64 page_size, umem_size;
+ u64 page_size, umem_sz;
/* Odd frame size so the UMEM doesn't end near a page boundary. */
- test->ifobj_tx->umem->frame_size = 4001;
- test->ifobj_rx->umem->frame_size = 4001;
- test->ifobj_tx->umem->unaligned_mode = true;
- test->ifobj_rx->umem->unaligned_mode = true;
+ test_spec_set_frame_size(test, 4001);
+ test_spec_set_unaligned(test);
/* This test exists to test descriptors that staddle the end of
* the UMEM but not a page.
*/
page_size = sysconf(_SC_PAGESIZE);
- umem_size = test->ifobj_tx->umem->num_frames * test->ifobj_tx->umem->frame_size;
- assert(umem_size % page_size > MIN_PKT_SIZE);
- assert(umem_size % page_size < page_size - MIN_PKT_SIZE);
+ umem_sz = umem_size(test->ifobj_tx->xsk->umem);
+ assert(umem_sz % page_size > MIN_PKT_SIZE);
+ assert(umem_sz % page_size < page_size - MIN_PKT_SIZE);
return testapp_invalid_desc(test);
}
@@ -2402,8 +2426,7 @@ int testapp_aligned_inv_desc_mb(struct test_spec *test)
int testapp_unaligned_inv_desc_mb(struct test_spec *test)
{
- test->ifobj_tx->umem->unaligned_mode = true;
- test->ifobj_rx->umem->unaligned_mode = true;
+ test_spec_set_unaligned(test);
return testapp_invalid_desc_mb(test);
}
@@ -2447,9 +2470,9 @@ int testapp_hw_sw_max_ring_size(struct test_spec *test)
test->total_steps = 2;
test->ifobj_tx->ring.tx_pending = test->ifobj_tx->ring.tx_max_pending;
test->ifobj_tx->ring.rx_pending = test->ifobj_tx->ring.rx_max_pending;
- test->ifobj_rx->umem->num_frames = max_descs;
- test->ifobj_rx->umem->fill_size = max_descs;
- test->ifobj_rx->umem->comp_size = max_descs;
+ test->ifobj_rx->xsk->umem->num_frames = max_descs;
+ test->ifobj_rx->xsk->umem->fill_size = max_descs;
+ test->ifobj_rx->xsk->umem->comp_size = max_descs;
test->ifobj_tx->xsk->batch_size = XSK_RING_PROD__DEFAULT_NUM_DESCS;
test->ifobj_rx->xsk->batch_size = XSK_RING_PROD__DEFAULT_NUM_DESCS;
@@ -2590,8 +2613,8 @@ struct ifobject *ifobject_create(void)
if (!ifobj->xsk_arr)
goto out_xsk_arr;
- ifobj->umem = calloc(1, sizeof(*ifobj->umem));
- if (!ifobj->umem)
+ ifobj->xsk_arr[0].umem_real = calloc(1, sizeof(struct xsk_umem_info));
+ if (!ifobj->xsk_arr[0].umem_real)
goto out_umem;
return ifobj;
@@ -2605,7 +2628,9 @@ out_xsk_arr:
void ifobject_delete(struct ifobject *ifobj)
{
- free(ifobj->umem);
+ if (ifobj->xsk_arr)
+ free(ifobj->xsk_arr[0].umem_real);
+
free(ifobj->xsk_arr);
free(ifobj);
}
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.h b/tools/testing/selftests/bpf/prog_tests/test_xsk.h
index 1ab8aee4ce56..4313d0d87235 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.h
+++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.h
@@ -83,6 +83,7 @@ typedef int (*test_func_t)(struct test_spec *test);
struct xsk_socket_info {
struct xsk_ring_cons rx;
struct xsk_ring_prod tx;
+ struct xsk_umem_info *umem_real;
struct xsk_umem_info *umem;
struct xsk_socket *xsk;
struct pkt_stream *pkt_stream;
@@ -102,6 +103,7 @@ struct xsk_umem_info {
struct xsk_ring_cons cq;
struct xsk_umem *umem;
u64 next_buffer;
+ u64 mmap_size;
u32 num_frames;
u32 frame_headroom;
void *buffer;
@@ -123,7 +125,6 @@ struct ifobject {
char ifname[MAX_INTERFACE_NAME_CHARS];
struct xsk_socket_info *xsk;
struct xsk_socket_info *xsk_arr;
- struct xsk_umem_info *umem;
thread_func_t func_ptr;
validation_func_t validation_func;
struct xsk_xdp_progs *xdp_progs;
diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_kern.h b/tools/testing/selftests/bpf/progs/test_sockmap_kern.h
index f48f85f1bd70..284a2f2e50cf 100644
--- a/tools/testing/selftests/bpf/progs/test_sockmap_kern.h
+++ b/tools/testing/selftests/bpf/progs/test_sockmap_kern.h
@@ -85,13 +85,6 @@ struct {
__type(value, int);
} sock_skb_opts SEC(".maps");
-struct {
- __uint(type, TEST_MAP_TYPE);
- __uint(max_entries, 20);
- __uint(key_size, sizeof(int));
- __uint(value_size, sizeof(int));
-} tls_sock_map SEC(".maps");
-
SEC("sk_skb/stream_parser")
int bpf_prog1(struct __sk_buff *skb)
{
@@ -135,55 +128,6 @@ int bpf_prog2(struct __sk_buff *skb)
}
-static inline void bpf_write_pass(struct __sk_buff *skb, int offset)
-{
- int err = bpf_skb_pull_data(skb, 6 + offset);
- void *data_end;
- char *c;
-
- if (err)
- return;
-
- c = (char *)(long)skb->data;
- data_end = (void *)(long)skb->data_end;
-
- if (c + 5 + offset < data_end)
- memcpy(c + offset, "PASS", 4);
-}
-
-SEC("sk_skb/stream_verdict")
-int bpf_prog3(struct __sk_buff *skb)
-{
- int err, *f, ret = SK_PASS;
- const int one = 1;
-
- f = bpf_map_lookup_elem(&sock_skb_opts, &one);
- if (f && *f) {
- __u64 flags = 0;
-
- ret = 0;
- flags = *f;
-
- err = bpf_skb_adjust_room(skb, -13, 0, 0);
- if (err)
- return SK_DROP;
- err = bpf_skb_adjust_room(skb, 4, 0, 0);
- if (err)
- return SK_DROP;
- bpf_write_pass(skb, 0);
-#ifdef SOCKMAP
- return bpf_sk_redirect_map(skb, &tls_sock_map, ret, flags);
-#else
- return bpf_sk_redirect_hash(skb, &tls_sock_map, &ret, flags);
-#endif
- }
- err = bpf_skb_adjust_room(skb, 4, 0, 0);
- if (err)
- return SK_DROP;
- bpf_write_pass(skb, 13);
- return ret;
-}
-
SEC("sockops")
int bpf_sockmap(struct bpf_sock_ops *skops)
{
diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_ktls.c b/tools/testing/selftests/bpf/progs/test_sockmap_ktls.c
deleted file mode 100644
index facafeaf4620..000000000000
--- a/tools/testing/selftests/bpf/progs/test_sockmap_ktls.c
+++ /dev/null
@@ -1,61 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-#include <linux/bpf.h>
-#include <bpf/bpf_helpers.h>
-#include <bpf/bpf_endian.h>
-
-int cork_byte;
-int push_start;
-int push_end;
-int apply_bytes;
-int pop_start;
-int pop_end;
-
-struct {
- __uint(type, BPF_MAP_TYPE_SOCKMAP);
- __uint(max_entries, 20);
- __type(key, int);
- __type(value, int);
-} sock_map SEC(".maps");
-
-struct {
- __uint(type, BPF_MAP_TYPE_SOCKMAP);
- __uint(max_entries, 2);
- __type(key, int);
- __type(value, int);
-} sock_map_verdict SEC(".maps");
-
-SEC("sk_msg")
-int prog_sk_policy(struct sk_msg_md *msg)
-{
- if (cork_byte > 0)
- bpf_msg_cork_bytes(msg, cork_byte);
- if (push_start > 0 && push_end > 0)
- bpf_msg_push_data(msg, push_start, push_end, 0);
- if (pop_start >= 0 && pop_end > 0)
- bpf_msg_pop_data(msg, pop_start, pop_end, 0);
-
- return SK_PASS;
-}
-
-SEC("sk_msg")
-int prog_sk_policy_redir(struct sk_msg_md *msg)
-{
- int two = 2;
-
- bpf_msg_apply_bytes(msg, apply_bytes);
- return bpf_msg_redirect_map(msg, &sock_map, two, 0);
-}
-
-/*
- * Verdict program for the reverse-order TLS/sockmap regression test.
- * Returns SK_PASS so tcp_read_skb() drains the receive queue via
- * sk_psock_verdict_recv() without calling tcp_eat_skb(), which is
- * the precondition for the KTLS strparser frag_list UAF.
- */
-SEC("sk_skb/verdict")
-int prog_skb_verdict_pass(struct __sk_buff *skb)
-{
- return SK_PASS;
-}
-
-char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_sockmap.c b/tools/testing/selftests/bpf/test_sockmap.c
index 76568db7a664..ac814eb63edb 100644
--- a/tools/testing/selftests/bpf/test_sockmap.c
+++ b/tools/testing/selftests/bpf/test_sockmap.c
@@ -26,7 +26,6 @@
#include <linux/sock_diag.h>
#include <linux/bpf.h>
#include <linux/if_link.h>
-#include <linux/tls.h>
#include <assert.h>
#include <libgen.h>
@@ -41,13 +40,6 @@
int running;
static void running_handler(int a);
-#ifndef TCP_ULP
-# define TCP_ULP 31
-#endif
-#ifndef SOL_TLS
-# define SOL_TLS 282
-#endif
-
/* randomly selected ports for testing on lo */
#define S1_PORT 10000
#define S2_PORT 10001
@@ -63,10 +55,10 @@ int s1, s2, c1, c2, p1, p2;
int test_cnt;
int passed;
int failed;
-int map_fd[9];
-struct bpf_map *maps[9];
-struct bpf_program *progs[9];
-struct bpf_link *links[9];
+int map_fd[8];
+struct bpf_map *maps[8];
+struct bpf_program *progs[8];
+struct bpf_link *links[8];
int txmsg_pass;
int txmsg_redir;
@@ -81,10 +73,6 @@ int txmsg_start_pop;
int txmsg_pop;
int txmsg_ingress;
int txmsg_redir_skb;
-int txmsg_ktls_skb;
-int txmsg_ktls_skb_drop;
-int txmsg_ktls_skb_redir;
-int ktls;
int peek_flag;
int skb_use_parser;
int txmsg_omit_skb_parser;
@@ -115,7 +103,6 @@ static const struct option long_options[] = {
{"txmsg_pop", required_argument, NULL, 'x'},
{"txmsg_ingress", no_argument, &txmsg_ingress, 1 },
{"txmsg_redir_skb", no_argument, &txmsg_redir_skb, 1 },
- {"ktls", no_argument, &ktls, 1 },
{"peek", no_argument, &peek_flag, 1 },
{"txmsg_omit_skb_parser", no_argument, &txmsg_omit_skb_parser, 1},
{"whitelist", required_argument, NULL, 'n' },
@@ -183,7 +170,6 @@ static void test_reset(void)
txmsg_pass = txmsg_drop = txmsg_redir = 0;
txmsg_apply = txmsg_cork = 0;
txmsg_ingress = txmsg_redir_skb = 0;
- txmsg_ktls_skb = txmsg_ktls_skb_drop = txmsg_ktls_skb_redir = 0;
txmsg_omit_skb_parser = 0;
skb_use_parser = 0;
}
@@ -238,71 +224,6 @@ static void usage(char *argv[])
printf("\n");
}
-char *sock_to_string(int s)
-{
- if (s == c1)
- return "client1";
- else if (s == c2)
- return "client2";
- else if (s == s1)
- return "server1";
- else if (s == s2)
- return "server2";
- else if (s == p1)
- return "peer1";
- else if (s == p2)
- return "peer2";
- else
- return "unknown";
-}
-
-static int sockmap_init_ktls(int verbose, int s)
-{
- struct tls12_crypto_info_aes_gcm_128 tls_tx = {
- .info = {
- .version = TLS_1_2_VERSION,
- .cipher_type = TLS_CIPHER_AES_GCM_128,
- },
- };
- struct tls12_crypto_info_aes_gcm_128 tls_rx = {
- .info = {
- .version = TLS_1_2_VERSION,
- .cipher_type = TLS_CIPHER_AES_GCM_128,
- },
- };
- int so_buf = 6553500;
- int err;
-
- err = setsockopt(s, 6, TCP_ULP, "tls", sizeof("tls"));
- if (err) {
- fprintf(stderr, "setsockopt: TCP_ULP(%s) failed with error %i\n", sock_to_string(s), err);
- return -EINVAL;
- }
- err = setsockopt(s, SOL_TLS, TLS_TX, (void *)&tls_tx, sizeof(tls_tx));
- if (err) {
- fprintf(stderr, "setsockopt: TLS_TX(%s) failed with error %i\n", sock_to_string(s), err);
- return -EINVAL;
- }
- err = setsockopt(s, SOL_TLS, TLS_RX, (void *)&tls_rx, sizeof(tls_rx));
- if (err) {
- fprintf(stderr, "setsockopt: TLS_RX(%s) failed with error %i\n", sock_to_string(s), err);
- return -EINVAL;
- }
- err = setsockopt(s, SOL_SOCKET, SO_SNDBUF, &so_buf, sizeof(so_buf));
- if (err) {
- fprintf(stderr, "setsockopt: (%s) failed sndbuf with error %i\n", sock_to_string(s), err);
- return -EINVAL;
- }
- err = setsockopt(s, SOL_SOCKET, SO_RCVBUF, &so_buf, sizeof(so_buf));
- if (err) {
- fprintf(stderr, "setsockopt: (%s) failed rcvbuf with error %i\n", sock_to_string(s), err);
- return -EINVAL;
- }
-
- if (verbose)
- fprintf(stdout, "socket(%s) kTLS enabled\n", sock_to_string(s));
- return 0;
-}
static int sockmap_init_sockets(int verbose)
{
int i, err, one = 1;
@@ -557,19 +478,6 @@ static int msg_verify_data(struct msghdr *msg, int size, int chunk_sz,
for (i = 0, j = 0; i < msg->msg_iovlen && size; i++, j = 0) {
unsigned char *d = msg->msg_iov[i].iov_base;
- /* Special case test for skb ingress + ktls */
- if (i == 0 && txmsg_ktls_skb) {
- if (msg->msg_iov[i].iov_len < 4)
- return -EDATAINTEGRITY;
- if (memcmp(d, "PASS", 4) != 0) {
- fprintf(stderr,
- "detected skb data error with skb ingress update @iov[%i]:%i \"%02x %02x %02x %02x\" != \"PASS\"\n",
- i, 0, d[0], d[1], d[2], d[3]);
- return -EDATAINTEGRITY;
- }
- j = 4; /* advance index past PASS header */
- }
-
for (; j < msg->msg_iov[i].iov_len && size; j++) {
if (push > 0 &&
check_cnt == verify_push_start + verify_push_len - push) {
@@ -849,21 +757,6 @@ static int sendmsg_test(struct sockmap_options *opt)
else
rx_fd = p2;
- if (ktls) {
- /* Redirecting into non-TLS socket which sends into a TLS
- * socket is not a valid test. So in this case lets not
- * enable kTLS but still run the test.
- */
- if (!txmsg_redir || txmsg_ingress) {
- err = sockmap_init_ktls(opt->verbose, rx_fd);
- if (err)
- return err;
- }
- err = sockmap_init_ktls(opt->verbose, c1);
- if (err)
- return err;
- }
-
if (opt->tx_wait_mem) {
struct timeval timeout;
int rxtx_buf_len = 1024;
@@ -882,7 +775,7 @@ static int sendmsg_test(struct sockmap_options *opt)
rxpid = fork();
if (rxpid == 0) {
- if (opt->drop_expected || txmsg_ktls_skb_drop)
+ if (opt->drop_expected)
_exit(0);
if (!iov_buf) /* zero bytes sent case */
@@ -1073,28 +966,8 @@ static int run_options(struct sockmap_options *options, int cg_fd, int test)
return -1;
}
- /* Attach programs to TLS sockmap */
- if (txmsg_ktls_skb) {
- if (!txmsg_omit_skb_parser) {
- links[2] = bpf_program__attach_sockmap(progs[0], map_fd[8]);
- if (!links[2]) {
- fprintf(stderr,
- "ERROR: bpf_program__attach_sockmap (TLS sockmap %i->%i): (%s)\n",
- bpf_program__fd(progs[0]), map_fd[8], strerror(errno));
- return -1;
- }
- }
-
- links[3] = bpf_program__attach_sockmap(progs[2], map_fd[8]);
- if (!links[3]) {
- fprintf(stderr, "ERROR: bpf_program__attach_sockmap (TLS sockmap): (%s)\n",
- strerror(errno));
- return -1;
- }
- }
-
/* Attach to cgroups */
- err = bpf_prog_attach(bpf_program__fd(progs[3]), cg_fd, BPF_CGROUP_SOCK_OPS, 0);
+ err = bpf_prog_attach(bpf_program__fd(progs[2]), cg_fd, BPF_CGROUP_SOCK_OPS, 0);
if (err) {
fprintf(stderr, "ERROR: bpf_prog_attach (groups): %d (%s)\n",
err, strerror(errno));
@@ -1110,15 +983,15 @@ run:
/* Attach txmsg program to sockmap */
if (txmsg_pass)
- tx_prog = progs[4];
+ tx_prog = progs[3];
else if (txmsg_redir)
- tx_prog = progs[5];
+ tx_prog = progs[4];
else if (txmsg_apply)
- tx_prog = progs[6];
+ tx_prog = progs[5];
else if (txmsg_cork)
- tx_prog = progs[7];
+ tx_prog = progs[6];
else if (txmsg_drop)
- tx_prog = progs[8];
+ tx_prog = progs[7];
else
tx_prog = NULL;
@@ -1291,34 +1164,6 @@ run:
}
}
- if (txmsg_ktls_skb) {
- int ingress = BPF_F_INGRESS;
-
- i = 0;
- err = bpf_map_update_elem(map_fd[8], &i, &p2, BPF_ANY);
- if (err) {
- fprintf(stderr,
- "ERROR: bpf_map_update_elem (c1 sockmap): %d (%s)\n",
- err, strerror(errno));
- }
-
- if (txmsg_ktls_skb_redir) {
- i = 1;
- err = bpf_map_update_elem(map_fd[7],
- &i, &ingress, BPF_ANY);
- if (err) {
- fprintf(stderr,
- "ERROR: bpf_map_update_elem (txmsg_ingress): %d (%s)\n",
- err, strerror(errno));
- }
- }
-
- if (txmsg_ktls_skb_drop) {
- i = 1;
- err = bpf_map_update_elem(map_fd[7], &i, &i, BPF_ANY);
- }
- }
-
if (txmsg_redir_skb) {
int skb_fd = (test == SENDMSG || test == SENDPAGE) ?
p2 : p1;
@@ -1373,7 +1218,7 @@ run:
fprintf(stderr, "unknown test\n");
out:
/* Detach and zero all the maps */
- bpf_prog_detach2(bpf_program__fd(progs[3]), cg_fd, BPF_CGROUP_SOCK_OPS);
+ bpf_prog_detach2(bpf_program__fd(progs[2]), cg_fd, BPF_CGROUP_SOCK_OPS);
for (i = 0; i < ARRAY_SIZE(links); i++) {
if (links[i])
@@ -1457,10 +1302,6 @@ static void test_options(char *options)
append_str(options, "ingress,", OPTSTRING);
if (txmsg_redir_skb)
append_str(options, "redir_skb,", OPTSTRING);
- if (txmsg_ktls_skb)
- append_str(options, "ktls_skb,", OPTSTRING);
- if (ktls)
- append_str(options, "ktls,", OPTSTRING);
if (peek_flag)
append_str(options, "peek,", OPTSTRING);
}
@@ -1602,57 +1443,6 @@ static void test_txmsg_ingress_redir(int cgrp, struct sockmap_options *opt)
test_send(opt, cgrp);
}
-static void test_txmsg_skb(int cgrp, struct sockmap_options *opt)
-{
- bool data = opt->data_test;
- int k = ktls;
-
- opt->data_test = true;
- ktls = 1;
-
- txmsg_pass = txmsg_drop = 0;
- txmsg_ingress = txmsg_redir = 0;
- txmsg_ktls_skb = 1;
- txmsg_pass = 1;
-
- /* Using data verification so ensure iov layout is
- * expected from test receiver side. e.g. has enough
- * bytes to write test code.
- */
- opt->iov_length = 100;
- opt->iov_count = 1;
- opt->rate = 1;
- test_exec(cgrp, opt);
-
- txmsg_ktls_skb_drop = 1;
- test_exec(cgrp, opt);
-
- txmsg_ktls_skb_drop = 0;
- txmsg_ktls_skb_redir = 1;
- test_exec(cgrp, opt);
- txmsg_ktls_skb_redir = 0;
-
- /* Tests that omit skb_parser */
- txmsg_omit_skb_parser = 1;
- ktls = 0;
- txmsg_ktls_skb = 0;
- test_exec(cgrp, opt);
-
- txmsg_ktls_skb_drop = 1;
- test_exec(cgrp, opt);
- txmsg_ktls_skb_drop = 0;
-
- txmsg_ktls_skb_redir = 1;
- test_exec(cgrp, opt);
-
- ktls = 1;
- test_exec(cgrp, opt);
- txmsg_omit_skb_parser = 0;
-
- opt->data_test = data;
- ktls = k;
-}
-
/* Test cork with hung data. This tests poor usage patterns where
* cork can leave data on the ring if user program is buggy and
* doesn't flush them somehow. They do take some time however
@@ -1908,8 +1698,6 @@ static void test_txmsg_ingress_parser(int cgrp, struct sockmap_options *opt)
{
txmsg_pass = 1;
skb_use_parser = 512;
- if (ktls == 1)
- skb_use_parser = 570;
opt->iov_length = 256;
opt->iov_count = 1;
opt->rate = 2;
@@ -1918,8 +1706,6 @@ static void test_txmsg_ingress_parser(int cgrp, struct sockmap_options *opt)
static void test_txmsg_ingress_parser2(int cgrp, struct sockmap_options *opt)
{
- if (ktls == 1)
- return;
skb_use_parser = 10;
opt->iov_length = 20;
opt->iov_count = 1;
@@ -1938,7 +1724,6 @@ char *map_names[] = {
"sock_bytes",
"sock_redir_flags",
"sock_skb_opts",
- "tls_sock_map",
};
static int populate_progs(char *bpf_file)
@@ -1988,7 +1773,6 @@ struct _test test[] = {
{"txmsg test redirect wait send mem", test_txmsg_redir_wait_sndmem},
{"txmsg test drop", test_txmsg_drop},
{"txmsg test ingress redirect", test_txmsg_ingress_redir},
- {"txmsg test skb", test_txmsg_skb},
{"txmsg test apply", test_txmsg_apply},
{"txmsg test cork", test_txmsg_cork},
{"txmsg test hanging corks", test_txmsg_cork_hangs},
@@ -2085,20 +1869,10 @@ static void test_selftests_sockhash(int cg_fd, struct sockmap_options *opt)
__test_selftests(cg_fd, opt);
}
-static void test_selftests_ktls(int cg_fd, struct sockmap_options *opt)
-{
- opt->map = BPF_SOCKHASH_FILENAME;
- opt->prepend = "ktls";
- ktls = 1;
- __test_selftests(cg_fd, opt);
- ktls = 0;
-}
-
static int test_selftest(int cg_fd, struct sockmap_options *opt)
{
test_selftests_sockmap(cg_fd, opt);
test_selftests_sockhash(cg_fd, opt);
- test_selftests_ktls(cg_fd, opt);
test_print_results();
return 0;
}
diff --git a/tools/testing/selftests/drivers/net/.gitignore b/tools/testing/selftests/drivers/net/.gitignore
index 585ecb4d5dc4..e5314ce4bb2d 100644
--- a/tools/testing/selftests/drivers/net/.gitignore
+++ b/tools/testing/selftests/drivers/net/.gitignore
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
napi_id_helper
psp_responder
+so_txtime
diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile
index b72080c6d06b..d5bf4cb638a8 100644
--- a/tools/testing/selftests/drivers/net/Makefile
+++ b/tools/testing/selftests/drivers/net/Makefile
@@ -7,6 +7,7 @@ TEST_INCLUDES := $(wildcard lib/py/*.py) \
TEST_GEN_FILES := \
napi_id_helper \
+ so_txtime \
# end of TEST_GEN_FILES
TEST_PROGS := \
@@ -21,6 +22,7 @@ TEST_PROGS := \
queues.py \
ring_reconfig.py \
shaper.py \
+ so_txtime.py \
stats.py \
xdp.py \
# end of TEST_PROGS
diff --git a/tools/testing/selftests/drivers/net/bonding/Makefile b/tools/testing/selftests/drivers/net/bonding/Makefile
index 9af5f84edd37..be130bf585a4 100644
--- a/tools/testing/selftests/drivers/net/bonding/Makefile
+++ b/tools/testing/selftests/drivers/net/bonding/Makefile
@@ -8,6 +8,7 @@ TEST_PROGS := \
bond-lladdr-target.sh \
bond_ipsec_offload.sh \
bond_lacp_prio.sh \
+ bond_lacp_strict.sh \
bond_macvlan_ipvlan.sh \
bond_options.sh \
bond_passive_lacp.sh \
diff --git a/tools/testing/selftests/drivers/net/bonding/bond_lacp_strict.sh b/tools/testing/selftests/drivers/net/bonding/bond_lacp_strict.sh
new file mode 100755
index 000000000000..f1a93a1d952f
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/bonding/bond_lacp_strict.sh
@@ -0,0 +1,347 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Testing if bond lacp_strict works
+#
+# Partner (p_ns)
+# +--------------------------+
+# | bond0 |
+# | + |
+# | eth0 | eth1 |
+# | +---+---+ |
+# | | | |
+# +--------------------------+
+# | | eth0 | eth1 |
+# | | | |
+# |(br_ns) | br0 | br1 |
+# | | | |
+# | | eth2 | eth3 |
+# +--------------------------+
+# | | | |
+# | +---+---+ |
+# | eth0 | eth1 |
+# | + |
+# | bond0 |
+# +--------------------------+
+# Dut (d_ns)
+
+lib_dir=$(dirname "$0")
+# shellcheck disable=SC1090
+source "$lib_dir"/../../../net/lib.sh
+
+COLLECTING_DISTRIBUTING_MASK=48
+COLLECTING_DISTRIBUTING=48
+FAILED=0
+
+setup_links()
+{
+ # shellcheck disable=SC2154
+ ip -n "${p_ns}" link add eth0 type veth peer name eth0 netns "${br_ns}"
+ ip -n "${p_ns}" link add eth1 type veth peer name eth1 netns "${br_ns}"
+ ip -n "${d_ns}" link add eth0 type veth peer name eth2 netns "${br_ns}"
+ ip -n "${d_ns}" link add eth1 type veth peer name eth3 netns "${br_ns}"
+
+ ip -n "${br_ns}" link add br0 type bridge
+ ip -n "${br_ns}" link add br1 type bridge
+
+ ip -n "${br_ns}" link set dev br0 type bridge stp_state 0
+ ip -n "${br_ns}" link set dev br1 type bridge stp_state 0
+
+ ip -n "${br_ns}" link set eth0 master br0
+ ip -n "${br_ns}" link set eth2 master br0
+ ip -n "${br_ns}" link set eth1 master br1
+ ip -n "${br_ns}" link set eth3 master br1
+
+ # Allow LACP trames forwarding on bridge ports
+ ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth0/group_fwd_mask"
+ ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth1/group_fwd_mask"
+ ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth2/group_fwd_mask"
+ ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth3/group_fwd_mask"
+
+ ip -n "${br_ns}" link set eth0 up
+ ip -n "${br_ns}" link set eth2 up
+ ip -n "${br_ns}" link set eth1 up
+ ip -n "${br_ns}" link set eth3 up
+
+ ip -n "${br_ns}" link set br0 up
+ ip -n "${br_ns}" link set br1 up
+
+ ip -n "${d_ns}" link add bond0 type bond mode 802.3ad miimon 100 \
+ lacp_rate fast min_links 1
+ ip -n "${p_ns}" link add bond0 type bond mode 802.3ad miimon 100 \
+ lacp_rate fast min_links 1
+
+ ip -n "${d_ns}" link set eth0 master bond0
+ ip -n "${d_ns}" link set eth1 master bond0
+ ip -n "${p_ns}" link set eth0 master bond0
+ ip -n "${p_ns}" link set eth1 master bond0
+
+ ip -n "${d_ns}" link set bond0 up
+ ip -n "${p_ns}" link set bond0 up
+}
+
+test_master_carrier() {
+ local expected=$1
+ local mode_name=$2
+ local carrier
+
+ carrier=$(ip netns exec "${d_ns}" cat /sys/class/net/bond0/carrier)
+ [ "$carrier" == "1" ] && carrier="up" || carrier="down"
+
+ [ "$carrier" == "$expected" ] && return
+
+ echo "FAIL: Expected carrier $expected in lacp_strict $mode_name mode, got $carrier"
+
+ RET=1
+
+}
+
+compare_state() {
+ local actual_state=$1
+ local expected_state=$2
+ local iface=$3
+ local last_attempt=$4
+
+ [ $((actual_state & COLLECTING_DISTRIBUTING_MASK)) -eq "$expected_state" ] \
+ && return 0
+
+ [ "$last_attempt" -ne 1 ] && return 1
+
+ printf "FAIL: Expected LACP %s actor state to " "$iface"
+ if [ "$expected_state" -eq $COLLECTING_DISTRIBUTING ]; then
+ echo "be in Collecting/Distributing state"
+ else
+ echo "have neither Collecting nor Distributing set."
+ fi
+
+ return 1
+}
+
+_test_lacp_port_state() {
+ local interface=$1
+ local expected=$2
+ local last_attempt=$3
+ local eth0_actor_state eth1_actor_state
+ local ret=0
+
+ # shellcheck disable=SC2016
+ while IFS='=' read -r k v; do
+ printf -v "$k" '%s' "$v"
+ done < <(
+ ip netns exec "${d_ns}" awk '
+ /^Slave Interface: / { iface=$3 }
+ /details actor lacp pdu:/ { ctx="actor" }
+ /details partner lacp pdu:/ { ctx="partner" }
+ /^[[:space:]]+port state: / {
+ if (ctx == "actor") {
+ gsub(":", "", iface)
+ printf "%s_%s_state=%s\n", iface, ctx, $3
+ }
+ }
+ ' /proc/net/bonding/bond0
+ )
+
+ if [ "$interface" == "eth0" ] || [ "$interface" == "both" ]; then
+ compare_state "$eth0_actor_state" "$expected" eth0 "$last_attempt" || ret=1
+ fi
+
+ if [ "$interface" == "eth1" ] || [ "$interface" == "both" ]; then
+ compare_state "$eth1_actor_state" "$expected" eth1 "$last_attempt" || ret=1
+ fi
+
+ return $ret
+}
+
+test_lacp_port_state() {
+ local interface=$1
+ local expected=$2
+ local retry=$3
+ local last_attempt=0
+ local attempt=1
+ local ret=1
+
+ while [ $attempt -le $((retry + 1)) ]; do
+ [ $attempt -eq $((retry + 1)) ] && last_attempt=1
+ _test_lacp_port_state "$interface" "$expected" "$last_attempt" && return
+ ((attempt++))
+ sleep 1
+ done
+
+ RET=1
+}
+
+
+trap cleanup_all_ns EXIT
+setup_ns d_ns p_ns br_ns
+setup_links
+
+# Initial state
+RET=0
+mode=off
+test_lacp_port_state both $COLLECTING_DISTRIBUTING 3
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 up"
+
+# partner eth0 down, eth1 up
+# (replicate eth0 state to dut eth0 by shutting a bridge port)
+RET=0
+ip -n "${p_ns}" link set eth0 down
+ip -n "${br_ns}" link set eth2 down
+test_lacp_port_state eth0 $FAILED 5
+test_lacp_port_state eth1 $COLLECTING_DISTRIBUTING 1
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 down"
+
+# partner eth0 and eth1 down
+RET=0
+ip -n "${p_ns}" link set eth1 down
+ip -n "${br_ns}" link set eth3 down
+test_lacp_port_state both $FAILED 5
+test_master_carrier down $mode # down because of min_links
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 down"
+
+# partner eth0 up, eth1 down
+RET=0
+ip -n "${p_ns}" link set eth0 up
+ip -n "${br_ns}" link set eth2 up
+test_lacp_port_state eth0 $COLLECTING_DISTRIBUTING 60
+test_lacp_port_state eth1 $FAILED 1
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 up, eth1 down"
+
+# partner eth0 and eth1 up
+RET=0
+ip -n "${p_ns}" link set eth1 up
+ip -n "${br_ns}" link set eth3 up
+test_lacp_port_state both $COLLECTING_DISTRIBUTING 60
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 up"
+
+# partner eth0 stops LACP and eth1 up
+RET=0
+ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br0/brif/eth0/group_fwd_mask"
+ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br0/brif/eth2/group_fwd_mask"
+test_lacp_port_state eth0 $FAILED 5
+test_lacp_port_state eth1 $COLLECTING_DISTRIBUTING 1
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 stopped sending LACP"
+
+# partner eth0 and eth1 stop LACP
+RET=0
+ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br1/brif/eth1/group_fwd_mask"
+ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br1/brif/eth3/group_fwd_mask"
+test_lacp_port_state both $FAILED 5
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 stopped sending LACP"
+
+# switch to lacp_strict on
+RET=0
+mode=on
+ip -n "${d_ns}" link set dev bond0 type bond lacp_strict $mode
+test_lacp_port_state both $FAILED 1
+test_master_carrier down $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 stopped sending LACP"
+
+# switch back to lacp_strict off mode
+RET=0
+mode=off
+ip -n "${d_ns}" link set dev bond0 type bond lacp_strict $mode
+test_lacp_port_state both $FAILED 1
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 stopped sending LACP"
+
+# eth0 recovers LACP
+RET=0
+ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth0/group_fwd_mask"
+ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth2/group_fwd_mask"
+test_lacp_port_state eth0 $COLLECTING_DISTRIBUTING 60
+test_lacp_port_state eth1 $FAILED 1
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 recovered and eth1 stopped sending LACP"
+
+# eth1 recovers LACP
+RET=0
+ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth1/group_fwd_mask"
+ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth3/group_fwd_mask"
+test_lacp_port_state both $COLLECTING_DISTRIBUTING 60
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 recovered LACP"
+
+# switch to lacp_strict on
+RET=0
+mode=on
+ip -n "${d_ns}" link set dev bond0 type bond lacp_strict $mode
+test_lacp_port_state both $COLLECTING_DISTRIBUTING 1
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 up"
+
+# partner eth0 down, eth1 up
+RET=0
+ip -n "${p_ns}" link set eth0 down
+ip -n "${br_ns}" link set eth2 down
+test_lacp_port_state eth0 $FAILED 5
+test_lacp_port_state eth1 $COLLECTING_DISTRIBUTING 1
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 down"
+
+# partner eth0 and eth1 down
+RET=0
+ip -n "${p_ns}" link set eth1 down
+ip -n "${br_ns}" link set eth3 down
+test_lacp_port_state both $FAILED 5
+test_master_carrier down $mode # down because of min_links
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 down"
+
+# partner eth0 up, eth1 down
+RET=0
+ip -n "${p_ns}" link set eth0 up
+ip -n "${br_ns}" link set eth2 up
+test_lacp_port_state eth0 $COLLECTING_DISTRIBUTING 60
+test_lacp_port_state eth1 $FAILED 1
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 up, eth1 down"
+
+# partner eth0 and eth1 up
+RET=0
+ip -n "${p_ns}" link set eth1 up
+ip -n "${br_ns}" link set eth3 up
+test_lacp_port_state both $COLLECTING_DISTRIBUTING 60
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 up"
+
+# partner eth0 stops LACP and eth1 up
+RET=0
+ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br0/brif/eth0/group_fwd_mask"
+ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br0/brif/eth2/group_fwd_mask"
+test_lacp_port_state eth0 $FAILED 5
+test_lacp_port_state eth1 $COLLECTING_DISTRIBUTING 1
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 stopped sending LACP"
+
+# partner eth0 and eth1 stop LACP
+RET=0
+ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br1/brif/eth1/group_fwd_mask"
+ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br1/brif/eth3/group_fwd_mask"
+test_lacp_port_state both $FAILED 5
+test_master_carrier down $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 stopped sending LACP"
+
+# eth0 recovers LACP
+RET=0
+ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth0/group_fwd_mask"
+ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth2/group_fwd_mask"
+test_lacp_port_state eth0 $COLLECTING_DISTRIBUTING 60
+test_lacp_port_state eth1 $FAILED 1
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 recovered and eth1 stopped sending LACP"
+
+# eth1 recovers LACP
+# shellcheck disable=SC2034
+RET=0
+ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth1/group_fwd_mask"
+ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth3/group_fwd_mask"
+test_lacp_port_state both $COLLECTING_DISTRIBUTING 60
+test_master_carrier up $mode
+log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 recovered LACP"
+
+exit "${EXIT_STATUS}"
diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config
index fd16994366f4..91d4fd410914 100644
--- a/tools/testing/selftests/drivers/net/config
+++ b/tools/testing/selftests/drivers/net/config
@@ -8,5 +8,10 @@ CONFIG_NETCONSOLE=m
CONFIG_NETCONSOLE_DYNAMIC=y
CONFIG_NETCONSOLE_EXTENDED_LOG=y
CONFIG_NETDEVSIM=m
+CONFIG_NETKIT=y
+CONFIG_NET_SCH_ETF=m
+CONFIG_NET_SCH_FQ=m
+CONFIG_PPP=y
+CONFIG_PPPOE=y
CONFIG_VLAN_8021Q=m
CONFIG_XDP_SOCKETS=y
diff --git a/tools/testing/selftests/drivers/net/gro.py b/tools/testing/selftests/drivers/net/gro.py
index 221f27e57147..6ab8c97880d1 100755
--- a/tools/testing/selftests/drivers/net/gro.py
+++ b/tools/testing/selftests/drivers/net/gro.py
@@ -40,7 +40,7 @@ import glob
import os
import re
from lib.py import ksft_run, ksft_exit, ksft_pr
-from lib.py import NetDrvEpEnv, KsftXfailEx
+from lib.py import NetDrvEpEnv, KsftFailEx, KsftXfailEx
from lib.py import NetdevFamily, EthtoolFamily
from lib.py import bkg, cmd, defer, ethtool, ip
from lib.py import ksft_variants, KsftNamedVariant
@@ -132,11 +132,21 @@ def _get_queue_stats(cfg, queue_id):
return {}
+def _require_ntuple(cfg):
+ features = ethtool(f"-k {cfg.ifname}", json=True)[0]
+ if not features["ntuple-filters"]["active"]:
+ if features["ntuple-filters"]["fixed"]:
+ raise KsftXfailEx("Device does not support ntuple-filters")
+ ethtool(f"-K {cfg.ifname} ntuple-filters on")
+ defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
+
+
def _setup_isolated_queue(cfg):
"""Set up an isolated queue for testing using ntuple filter.
Remove queue 1 from the default RSS context and steer test traffic to it.
"""
+ _require_ntuple(cfg)
test_queue = 1
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
@@ -313,6 +323,12 @@ def _gro_variants():
"ip_frag6", "ip_v6ext_same", "ip_v6ext_diff",
]
+ # Tests specific to PPPoE
+ pppoe_tests = [
+ "data_same", "data_lrg_sml", "data_sml_lrg", "data_lrg_1byte",
+ "data_burst", "pppoe_sid",
+ ]
+
for mode in ["sw", "hw", "lro"]:
for protocol in ["ipv4", "ipv6", "ipip", "ip6ip6"]:
for test_name in common_tests:
@@ -325,6 +341,11 @@ def _gro_variants():
for test_name in ipv6_tests:
yield mode, protocol, test_name
+ for mode in ["sw"]:
+ for protocol in ["pppoev4", "pppoev6"]:
+ for test_name in pppoe_tests:
+ yield mode, protocol, test_name
+
@ksft_variants(_gro_variants())
def test(cfg, mode, protocol, test_name):
@@ -349,6 +370,12 @@ def test(cfg, mode, protocol, test_name):
ksft_pr(rx_proc)
+ # ret==42 means the receiver detected over-coalescing.
+ # This is unambiguous proof of a bug, retries can only cause
+ # false negatives.
+ if rx_proc.ret == 42:
+ raise KsftFailEx(f"GRO over-coalesced in {protocol}/{test_name}")
+
if test_name.startswith("large_") and os.environ.get("KSFT_MACHINE_SLOW"):
ksft_pr(f"Ignoring {protocol}/{test_name} failure due to slow environment")
return
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index 82809d5b2478..fd0535a96d84 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -35,6 +35,7 @@ TEST_PROGS = \
irq.py \
loopback.sh \
nic_timestamp.py \
+ nk_devmem.py \
nk_netns.py \
nk_qlease.py \
ntuple.py \
@@ -46,12 +47,14 @@ TEST_PROGS = \
rss_input_xfrm.py \
toeplitz.py \
tso.py \
+ userns_devmem.py \
uso.py \
xdp_metadata.py \
xsk_reconfig.py \
#
TEST_FILES := \
+ devmem_lib.py \
ethtool_lib.sh \
#
diff --git a/tools/testing/selftests/drivers/net/hw/config b/tools/testing/selftests/drivers/net/hw/config
index 8c132ace2b8d..ed8642b68094 100644
--- a/tools/testing/selftests/drivers/net/hw/config
+++ b/tools/testing/selftests/drivers/net/hw/config
@@ -3,6 +3,7 @@ CONFIG_FAIL_FUNCTION=y
CONFIG_FAULT_INJECTION=y
CONFIG_FAULT_INJECTION_DEBUG_FS=y
CONFIG_FUNCTION_ERROR_INJECTION=y
+CONFIG_HUGETLBFS=y
CONFIG_INET6_ESP=y
CONFIG_INET6_ESP_OFFLOAD=y
CONFIG_INET_ESP=y
@@ -10,12 +11,16 @@ CONFIG_INET_ESP_OFFLOAD=y
CONFIG_IO_URING=y
CONFIG_IPV6=y
CONFIG_IPV6_GRE=y
+CONFIG_IPV6_SIT=y
+CONFIG_IPV6_TUNNEL=y
CONFIG_NET_CLS_ACT=y
CONFIG_NET_CLS_BPF=y
CONFIG_NET_IPGRE=y
CONFIG_NET_IPGRE_DEMUX=y
+CONFIG_NET_IPIP=y
CONFIG_NETKIT=y
CONFIG_NET_SCH_INGRESS=y
CONFIG_UDMABUF=y
+CONFIG_USER_NS=y
CONFIG_VXLAN=y
CONFIG_XFRM_USER=y
diff --git a/tools/testing/selftests/drivers/net/hw/devmem.py b/tools/testing/selftests/drivers/net/hw/devmem.py
index ee863e90d1e0..031cf9905f65 100755
--- a/tools/testing/selftests/drivers/net/hw/devmem.py
+++ b/tools/testing/selftests/drivers/net/hw/devmem.py
@@ -2,91 +2,40 @@
# SPDX-License-Identifier: GPL-2.0
from os import path
-from lib.py import ksft_run, ksft_exit
-from lib.py import ksft_eq, KsftSkipEx
+from devmem_lib import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds
+from lib.py import ksft_run, ksft_exit, ksft_disruptive
from lib.py import NetDrvEpEnv
-from lib.py import bkg, cmd, rand_port, wait_port_listen
-from lib.py import ksft_disruptive
-
-
-def require_devmem(cfg):
- if not hasattr(cfg, "_devmem_probed"):
- probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
- cfg._devmem_supported = cmd(probe_command, fail=False, shell=True).ret == 0
- cfg._devmem_probed = True
-
- if not cfg._devmem_supported:
- raise KsftSkipEx("Test requires devmem support")
@ksft_disruptive
def check_rx(cfg) -> None:
- require_devmem(cfg)
-
- port = rand_port()
- socat = f"socat -u - TCP{cfg.addr_ipver}:{cfg.baddr}:{port},bind={cfg.remote_baddr}:{port}"
- listen_cmd = f"{cfg.bin_local} -l -f {cfg.ifname} -s {cfg.addr} -p {port} -c {cfg.remote_addr} -v 7"
-
- with bkg(listen_cmd, exit_wait=True) as ncdevmem:
- wait_port_listen(port)
- cmd(f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | \
- head -c 1K | {socat}", host=cfg.remote, shell=True)
-
- ksft_eq(ncdevmem.ret, 0)
+ """Run the devmem RX test."""
+ run_rx(cfg)
@ksft_disruptive
def check_tx(cfg) -> None:
- require_devmem(cfg)
-
- port = rand_port()
- listen_cmd = f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
-
- with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
- wait_port_listen(port, host=cfg.remote)
- cmd(f"echo -e \"hello\\nworld\"| {cfg.bin_local} -f {cfg.ifname} -s {cfg.remote_addr} -p {port}", shell=True)
-
- ksft_eq(socat.stdout.strip(), "hello\nworld")
+ """Run the devmem TX test."""
+ run_tx(cfg)
@ksft_disruptive
def check_tx_chunks(cfg) -> None:
- require_devmem(cfg)
-
- port = rand_port()
- listen_cmd = f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
-
- with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
- wait_port_listen(port, host=cfg.remote)
- cmd(f"echo -e \"hello\\nworld\"| {cfg.bin_local} -f {cfg.ifname} -s {cfg.remote_addr} -p {port} -z 3", shell=True)
-
- ksft_eq(socat.stdout.strip(), "hello\nworld")
+ """Run the devmem TX chunking test."""
+ run_tx_chunks(cfg)
def check_rx_hds(cfg) -> None:
- """Test HDS splitting across payload sizes."""
- require_devmem(cfg)
-
- for size in [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]:
- port = rand_port()
- listen_cmd = f"{cfg.bin_local} -L -l -f {cfg.ifname} -s {cfg.addr} -p {port}"
-
- with bkg(listen_cmd, exit_wait=True) as ncdevmem:
- wait_port_listen(port)
- cmd(f"dd if=/dev/zero bs={size} count=1 2>/dev/null | " +
- f"socat -b {size} -u - TCP{cfg.addr_ipver}:{cfg.baddr}:{port},nodelay",
- host=cfg.remote, shell=True)
-
- ksft_eq(ncdevmem.ret, 0, f"HDS failed for payload size {size}")
+ """Run the HDS test."""
+ run_rx_hds(cfg)
def main() -> None:
+ """Run the devmem test cases."""
with NetDrvEpEnv(__file__) as cfg:
- cfg.bin_local = path.abspath(path.dirname(__file__) + "/ncdevmem")
- cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
-
+ setup_test(cfg, path.abspath(path.dirname(__file__) + "/ncdevmem"))
ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds],
- args=(cfg, ))
+ args=(cfg,))
ksft_exit()
diff --git a/tools/testing/selftests/drivers/net/hw/devmem_lib.py b/tools/testing/selftests/drivers/net/hw/devmem_lib.py
new file mode 100644
index 000000000000..0921ff03eb81
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/devmem_lib.py
@@ -0,0 +1,222 @@
+# SPDX-License-Identifier: GPL-2.0
+"""Shared helpers for devmem TCP selftests."""
+
+import re
+
+from lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen,
+ ksft_eq, KsftSkipEx, NetNSEnter, EthtoolFamily,
+ NetdevFamily)
+
+
+def require_devmem(cfg):
+ """Probe ncdevmem on cfg.ifname and SKIP the test if devmem isn't supported."""
+ if not hasattr(cfg, "devmem_probed"):
+ probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
+ cfg.devmem_supported = cmd(probe_command, fail=False, shell=True).ret == 0
+ cfg.devmem_probed = True
+
+ if not cfg.devmem_supported:
+ raise KsftSkipEx("Test requires devmem support")
+
+
+def configure_nic(cfg):
+ """Channels, rings, RSS, queue lease for netkit devmem."""
+ if not hasattr(cfg, 'netns'):
+ return
+
+ cfg.require_ipver('6')
+ ethnl = EthtoolFamily()
+
+ channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
+ channels = channels['combined-count']
+ if channels < 2:
+ raise KsftSkipEx(
+ 'Test requires NETIF with at least 2 combined channels'
+ )
+
+ rings = ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
+ orig_rx_rings = rings['rx']
+ orig_hds_thresh = rings.get('hds-thresh', 0)
+ orig_data_split = rings.get('tcp-data-split', 'unknown')
+
+ ethnl.rings_set({'header': {'dev-index': cfg.ifindex},
+ 'tcp-data-split': 'enabled',
+ 'hds-thresh': 0,
+ 'rx': min(64, orig_rx_rings)})
+ defer(ethnl.rings_set, {'header': {'dev-index': cfg.ifindex},
+ 'tcp-data-split': orig_data_split,
+ 'hds-thresh': orig_hds_thresh,
+ 'rx': orig_rx_rings})
+
+ cfg.src_queue = channels - 1
+ ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
+ defer(ethtool, f"-X {cfg.ifname} default")
+
+ if not hasattr(cfg, 'nk_queue'):
+ with NetNSEnter(str(cfg.netns)):
+ netdevnl = NetdevFamily()
+ lease_result = netdevnl.queue_create({
+ "ifindex": cfg.nk_guest_ifindex,
+ "type": "rx",
+ "lease": {
+ "ifindex": cfg.ifindex,
+ "queue": {"id": cfg.src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ })
+ cfg.nk_queue = lease_result['id']
+
+
+def set_flow_rule(cfg, port):
+ """Install a flow rule steering to src_queue and return the flow rule ID."""
+ output = ethtool(
+ f"-N {cfg.ifname} flow-type tcp6 dst-port {port}"
+ f" action {cfg.src_queue}"
+ ).stdout
+ return int(re.search(r'ID (\d+)', output).group(1))
+
+
+def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False):
+ """Build the ncdevmem RX listener command."""
+ if hasattr(cfg, 'netns'):
+ flow_rule_id = set_flow_rule(cfg, port)
+ defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
+
+ ifname = cfg.nk_guest_ifname
+ addr = cfg.nk_guest_ipv6
+ extras = [f"-t {cfg.nk_queue}", "-q 1", "-n"]
+ else:
+ ifname = cfg.ifname
+ addr = cfg.addr
+ extras = []
+ if flow_steer:
+ extras.append(f"-c {cfg.remote_addr}")
+
+ if verify:
+ extras.append("-v 7")
+ if fail_on_linear:
+ extras.append("-L")
+
+ parts = [cfg.bin_local, "-l", f"-f {ifname}", f"-s {addr}",
+ f"-p {port}", *extras]
+ return " ".join(parts)
+
+
+def ncdevmem_tx(cfg, port, chunk_size=0):
+ """Build the ncdevmem TX send command."""
+ if hasattr(cfg, 'netns'):
+ ifname = cfg.nk_guest_ifname
+ addr = cfg.remote_addr_v['6']
+ extras = ["-t 0", "-q 1", "-n"]
+ else:
+ ifname = cfg.ifname
+ addr = cfg.remote_addr
+ extras = []
+
+ if chunk_size:
+ extras.append(f"-z {chunk_size}")
+
+ parts = [cfg.bin_local, f"-f {ifname}", f"-s {addr}",
+ f"-p {port}", *extras]
+ return " ".join(parts)
+
+
+def socat_send(cfg, port, buf_size=0):
+ """Socat command for sending to the devmem listener.
+
+ When buf_size > 0, force one TCP segment per write of exactly that size by
+ setting socat's buffer (-b) and disabling Nagle (TCP_NODELAY).
+ """
+ proto = f"TCP{cfg.addr_ipver}"
+
+ if hasattr(cfg, 'netns'):
+ addr = f"[{cfg.nk_guest_ipv6}]"
+ else:
+ addr = cfg.baddr
+
+ suffix = f",bind={cfg.remote_baddr}:{port}"
+
+ buf = ""
+ if buf_size:
+ buf = f"-b {buf_size}"
+ suffix += ",nodelay"
+
+ return f"socat {buf} -u - {proto}:{addr}:{port}{suffix}"
+
+
+def socat_listen(cfg, port):
+ """Socat listen command for TX tests."""
+ return f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
+
+
+def setup_test(cfg, bin_local):
+ """Stash the local ncdevmem path on cfg and deploy it to the remote."""
+ cfg.bin_local = bin_local
+ cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
+
+
+def run_rx(cfg):
+ """Run the devmem RX test."""
+ require_devmem(cfg)
+ configure_nic(cfg)
+ port = rand_port()
+ socat = socat_send(cfg, port)
+ data_pipe = (f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | head -c 1K"
+ f" | {socat}")
+ netns = getattr(cfg, "netns", None)
+
+ listen_cmd = ncdevmem_rx(cfg, port, flow_steer=not hasattr(cfg, 'netns'))
+ with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem:
+ wait_port_listen(port, proto="tcp", ns=netns)
+ cmd(data_pipe, host=cfg.remote, shell=True)
+ ksft_eq(ncdevmem.ret, 0)
+
+
+def run_tx(cfg):
+ """Run the devmem TX test."""
+ require_devmem(cfg)
+ configure_nic(cfg)
+ netns = getattr(cfg, "netns", None)
+ port = rand_port()
+ tx_cmd = ncdevmem_tx(cfg, port)
+ listen_cmd = socat_listen(cfg, port)
+
+ with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
+ wait_port_listen(port, host=cfg.remote)
+ cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx_cmd}'", ns=netns, shell=True)
+ ksft_eq(socat.stdout.strip(), "hello\nworld")
+
+
+def run_tx_chunks(cfg):
+ """Run the devmem TX chunking test."""
+ require_devmem(cfg)
+ configure_nic(cfg)
+ netns = getattr(cfg, "netns", None)
+ port = rand_port()
+ tx_cmd = ncdevmem_tx(cfg, port, chunk_size=3)
+ listen_cmd = socat_listen(cfg, port)
+
+ with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
+ wait_port_listen(port, host=cfg.remote)
+ cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx_cmd}'", ns=netns, shell=True)
+ ksft_eq(socat.stdout.strip(), "hello\nworld")
+
+
+def run_rx_hds(cfg):
+ """Run the HDS test by running devmem RX across a segment size sweep."""
+ require_devmem(cfg)
+ configure_nic(cfg)
+ netns = getattr(cfg, "netns", None)
+
+ for size in [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]:
+ port = rand_port()
+
+ listen_cmd = ncdevmem_rx(cfg, port, verify=False,
+ fail_on_linear=True)
+ socat = socat_send(cfg, port, buf_size=size)
+
+ with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem:
+ wait_port_listen(port, proto="tcp", ns=netns)
+ cmd(f"dd if=/dev/zero bs={size} count=1 2>/dev/null | "
+ f"{socat}", host=cfg.remote, shell=True)
+ ksft_eq(ncdevmem.ret, 0, f"HDS failed for payload size {size}")
diff --git a/tools/testing/selftests/drivers/net/hw/gro_hw.py b/tools/testing/selftests/drivers/net/hw/gro_hw.py
index 10e08b22ee0e..70e76e3888bd 100755
--- a/tools/testing/selftests/drivers/net/hw/gro_hw.py
+++ b/tools/testing/selftests/drivers/net/hw/gro_hw.py
@@ -51,11 +51,21 @@ def _resolve_dmac(cfg, ipver):
return getattr(cfg, attr)
+def _require_ntuple(cfg):
+ features = ethtool(f"-k {cfg.ifname}", json=True)[0]
+ if not features["ntuple-filters"]["active"]:
+ if features["ntuple-filters"]["fixed"]:
+ raise KsftSkipEx("Device does not support ntuple-filters")
+ ethtool(f"-K {cfg.ifname} ntuple-filters on")
+ defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
+
+
def _setup_isolated_queue(cfg):
"""Set up an isolated queue for testing using ntuple filter.
Remove queue 1 from the default RSS context and steer test traffic to it.
"""
+ _require_ntuple(cfg)
test_queue = 1
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
diff --git a/tools/testing/selftests/drivers/net/hw/iou-zcrx.c b/tools/testing/selftests/drivers/net/hw/iou-zcrx.c
index 240d13dbc54e..f6a8fc5fac24 100644
--- a/tools/testing/selftests/drivers/net/hw/iou-zcrx.c
+++ b/tools/testing/selftests/drivers/net/hw/iou-zcrx.c
@@ -351,9 +351,6 @@ static void run_server(void)
if (ret < 0)
error(1, 0, "bind()");
- if (listen(fd, 1024) < 0)
- error(1, 0, "listen()");
-
flags |= IORING_SETUP_COOP_TASKRUN;
flags |= IORING_SETUP_SINGLE_ISSUER;
flags |= IORING_SETUP_DEFER_TASKRUN;
@@ -366,6 +363,9 @@ static void run_server(void)
if (cfg_dry_run)
return;
+ if (listen(fd, 1024) < 0)
+ error(1, 0, "listen()");
+
add_accept(&ring, fd);
tstop = gettimeofday_ms() + 5000;
diff --git a/tools/testing/selftests/drivers/net/hw/iou-zcrx.py b/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
index e81724cb5542..b7a225fe4bea 100755
--- a/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
+++ b/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-2.0
import re
+import resource
import time
from os import path
from lib.py import ksft_run, ksft_exit, KsftSkipEx, ksft_variants, KsftNamedVariant
@@ -41,6 +42,23 @@ def set_flow_rule_rss(cfg, rss_ctx_id):
return int(values)
+def check_iou_rx_buf_len(cfg, expected_rx_buf_len):
+ """Check the io-uring memory provider exposes the expected rx_buf_len."""
+ q = cfg.netnl.queue_get({'ifindex': cfg.ifindex, 'type': 'rx', 'id': cfg.target})
+ napi_id = q['napi-id']
+ pools = cfg.netnl.page_pool_get({}, dump=True)
+ pools = [p for p in pools if p.get('napi-id') == napi_id
+ and 'io-uring' in p]
+ if len(pools) != 1:
+ raise Exception(f"Expected 1 io-uring page pool, found {len(pools)}")
+ rx_buf_len = pools[0]['io-uring'].get('rx-buf-len')
+ if rx_buf_len is None:
+ raise KsftSkipEx("io-uring 'rx-buf-len' attribute not supported")
+ if rx_buf_len != expected_rx_buf_len:
+ raise Exception(f'Expected io-uring rx-buf-len {expected_rx_buf_len}, '
+ f'got {rx_buf_len}')
+
+
def single(cfg):
channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
channels = channels['combined-count']
@@ -100,12 +118,22 @@ def rss(cfg):
defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
+def _require_ntuple(cfg):
+ features = ethtool(f"-k {cfg.ifname}", json=True)[0]
+ if not features["ntuple-filters"]["active"]:
+ if features["ntuple-filters"]["fixed"]:
+ raise KsftSkipEx("Device does not support ntuple-filters")
+ ethtool(f"-K {cfg.ifname} ntuple-filters on")
+ defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
+
+
@ksft_variants([
KsftNamedVariant("single", single),
KsftNamedVariant("rss", rss),
])
def test_zcrx(cfg, setup) -> None:
cfg.require_ipver('6')
+ _require_ntuple(cfg)
setup(cfg)
rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.target}"
@@ -121,6 +149,7 @@ def test_zcrx(cfg, setup) -> None:
])
def test_zcrx_oneshot(cfg, setup) -> None:
cfg.require_ipver('6')
+ _require_ntuple(cfg)
setup(cfg)
rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.target} -o 4"
@@ -134,6 +163,7 @@ def test_zcrx_large_chunks(cfg) -> None:
"""Test zcrx with large buffer chunks."""
cfg.require_ipver('6')
+ _require_ntuple(cfg)
hp_file = "/proc/sys/vm/nr_hugepages"
with open(hp_file, 'r+', encoding='utf-8') as f:
@@ -144,7 +174,10 @@ def test_zcrx_large_chunks(cfg) -> None:
defer(lambda: open(hp_file, 'w', encoding='utf-8').write(str(nr_hugepages)))
single(cfg)
- rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.target} -x 2"
+ page_size = resource.getpagesize()
+ nr_pages = 2
+ rx_buf_len = nr_pages * page_size
+ rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.target} -x {nr_pages}"
tx_cmd = f"{cfg.bin_remote} -c -h {cfg.addr_v['6']} -p {cfg.port} -l 12840"
probe = cmd(rx_cmd + " -d", fail=False)
@@ -154,6 +187,9 @@ def test_zcrx_large_chunks(cfg) -> None:
mp_clear_wait(cfg)
with bkg(rx_cmd, exit_wait=True):
wait_port_listen(cfg.port, proto="tcp")
+
+ check_iou_rx_buf_len(cfg, rx_buf_len)
+
cmd(tx_cmd, host=cfg.remote)
diff --git a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
index 84a4dab6c649..8a58cb17cc06 100644
--- a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
@@ -18,7 +18,7 @@ try:
sys.path.append(KSFT_DIR.as_posix())
# Import one by one to avoid pylint false positives
- from net.lib.py import NetNS, NetNSEnter, NetdevSimDev
+ from net.lib.py import NetNS, NetNSEnter, NetdevSimDev, UserNetNS
from net.lib.py import EthtoolFamily, NetdevFamily, NetshaperFamily, \
NlError, RtnlFamily, DevlinkFamily, PSPFamily, Netlink
from net.lib.py import CmdExitFailure
@@ -34,7 +34,7 @@ try:
from drivers.net.lib.py import GenerateTraffic, Remote, Iperf3Runner
from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv
- __all__ = ["NetNS", "NetNSEnter", "NetdevSimDev",
+ __all__ = ["NetNS", "NetNSEnter", "NetdevSimDev", "UserNetNS",
"EthtoolFamily", "NetdevFamily", "NetshaperFamily",
"NlError", "RtnlFamily", "DevlinkFamily", "PSPFamily", "Netlink",
"CmdExitFailure",
diff --git a/tools/testing/selftests/drivers/net/hw/ncdevmem.c b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
index e098d6534c3c..d96e8a3b5a65 100644
--- a/tools/testing/selftests/drivers/net/hw/ncdevmem.c
+++ b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
@@ -93,6 +93,7 @@ static char *port;
static size_t do_validation;
static int start_queue = -1;
static int num_queues = -1;
+static int skip_config;
static char *ifname;
static unsigned int ifindex;
static unsigned int dmabuf_id;
@@ -828,7 +829,7 @@ static struct netdev_queue_id *create_queues(void)
static int do_server(struct memory_buffer *mem)
{
- struct ethtool_rings_get_rsp *ring_config;
+ struct ethtool_rings_get_rsp *ring_config = NULL;
char ctrl_data[sizeof(int) * 20000];
size_t non_page_aligned_frags = 0;
struct sockaddr_in6 client_addr;
@@ -851,27 +852,29 @@ static int do_server(struct memory_buffer *mem)
return -1;
}
- ring_config = get_ring_config();
- if (!ring_config) {
- pr_err("Failed to get current ring configuration");
- return -1;
- }
+ if (!skip_config) {
+ ring_config = get_ring_config();
+ if (!ring_config) {
+ pr_err("Failed to get current ring configuration");
+ return -1;
+ }
- if (configure_headersplit(ring_config, 1)) {
- pr_err("Failed to enable TCP header split");
- goto err_free_ring_config;
- }
+ if (configure_headersplit(ring_config, 1)) {
+ pr_err("Failed to enable TCP header split");
+ goto err_free_ring_config;
+ }
- /* Configure RSS to divert all traffic from our devmem queues */
- if (configure_rss()) {
- pr_err("Failed to configure rss");
- goto err_reset_headersplit;
- }
+ /* Configure RSS to divert all traffic from our devmem queues */
+ if (configure_rss()) {
+ pr_err("Failed to configure rss");
+ goto err_reset_headersplit;
+ }
- /* Flow steer our devmem flows to start_queue */
- if (configure_flow_steering(&server_sin)) {
- pr_err("Failed to configure flow steering");
- goto err_reset_rss;
+ /* Flow steer our devmem flows to start_queue */
+ if (configure_flow_steering(&server_sin)) {
+ pr_err("Failed to configure flow steering");
+ goto err_reset_rss;
+ }
}
if (bind_rx_queue(ifindex, mem->fd, create_queues(), num_queues, &ys)) {
@@ -1052,13 +1055,17 @@ err_free_tmp:
err_unbind:
ynl_sock_destroy(ys);
err_reset_flow_steering:
- reset_flow_steering();
+ if (!skip_config)
+ reset_flow_steering();
err_reset_rss:
- reset_rss();
+ if (!skip_config)
+ reset_rss();
err_reset_headersplit:
- restore_ring_config(ring_config);
+ if (!skip_config)
+ restore_ring_config(ring_config);
err_free_ring_config:
- ethtool_rings_get_rsp_free(ring_config);
+ if (!skip_config)
+ ethtool_rings_get_rsp_free(ring_config);
return err;
}
@@ -1404,7 +1411,7 @@ int main(int argc, char *argv[])
int is_server = 0, opt;
int ret, err = 1;
- while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:")) != -1) {
+ while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:n")) != -1) {
switch (opt) {
case 'L':
fail_on_linear = true;
@@ -1436,6 +1443,9 @@ int main(int argc, char *argv[])
case 'z':
max_chunk = atoi(optarg);
break;
+ case 'n':
+ skip_config = 1;
+ break;
case '?':
fprintf(stderr, "unknown option: %c\n", optopt);
break;
diff --git a/tools/testing/selftests/drivers/net/hw/nk_devmem.py b/tools/testing/selftests/drivers/net/hw/nk_devmem.py
new file mode 100755
index 000000000000..300ed2a70ab4
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_devmem.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""Test devmem TCP with netkit."""
+
+import os
+from devmem_lib import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds
+from lib.py import ksft_run, ksft_exit, ksft_disruptive
+from lib.py import NetDrvContEnv
+
+
+@ksft_disruptive
+def check_nk_rx(cfg) -> None:
+ """Run the devmem RX test through netkit."""
+ run_rx(cfg)
+
+
+@ksft_disruptive
+def check_nk_tx(cfg) -> None:
+ """Run the devmem TX test through netkit."""
+ run_tx(cfg)
+
+
+@ksft_disruptive
+def check_nk_tx_chunks(cfg) -> None:
+ """Run the devmem TX chunking test through netkit."""
+ run_tx_chunks(cfg)
+
+
+def check_nk_rx_hds(cfg) -> None:
+ """Run the HDS test through netkit."""
+ run_rx_hds(cfg)
+
+
+def main() -> None:
+ """Run the netkit devmem test cases."""
+ with NetDrvContEnv(__file__, rxqueues=2, primary_rx_redirect=True) as cfg:
+ setup_test(cfg,
+ os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "ncdevmem"))
+ ksft_run([check_nk_rx, check_nk_tx, check_nk_tx_chunks,
+ check_nk_rx_hds], args=(cfg,))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c b/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c
new file mode 100644
index 000000000000..46ff494b23de
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <linux/pkt_cls.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/ipv6.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#define ctx_ptr(field) ((void *)(long)(field))
+
+volatile __u32 phys_ifindex;
+
+SEC("tc/ingress")
+int nk_primary_rx_redirect(struct __sk_buff *skb)
+{
+ void *data_end = ctx_ptr(skb->data_end);
+ void *data = ctx_ptr(skb->data);
+ struct ethhdr *eth;
+ struct ipv6hdr *ip6h;
+
+ eth = data;
+ if ((void *)(eth + 1) > data_end)
+ return TC_ACT_OK;
+
+ if (eth->h_proto != bpf_htons(ETH_P_IPV6))
+ return TC_ACT_OK;
+
+ ip6h = data + sizeof(struct ethhdr);
+ if ((void *)(ip6h + 1) > data_end)
+ return TC_ACT_OK;
+
+ if (ip6h->nexthdr == IPPROTO_ICMPV6)
+ return TC_ACT_OK;
+
+ return bpf_redirect_neigh(phys_ifindex, NULL, 0, 0);
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/drivers/net/hw/nk_qlease.py b/tools/testing/selftests/drivers/net/hw/nk_qlease.py
index aa83dc321328..4f53034c9a50 100755
--- a/tools/testing/selftests/drivers/net/hw/nk_qlease.py
+++ b/tools/testing/selftests/drivers/net/hw/nk_qlease.py
@@ -18,8 +18,10 @@ from lib.py import (
NetNSEnter,
EthtoolFamily,
NetdevFamily,
+ RtnlFamily,
)
from lib.py import (
+ Netlink,
bkg,
cmd,
defer,
@@ -30,10 +32,138 @@ from lib.py import (
)
from lib.py import KsftSkipEx, CmdExitFailure
+# iou-zcrx exits with 42 from setup_zcrx() when the NIC does not advertise
+# QCFG_RX_PAGE_SIZE (or otherwise rejects the requested rx_buf_len).
+SKIP_CODE = 42
-def set_flow_rule(cfg):
+
+def _restore_hugepages(count):
+ with open("/proc/sys/vm/nr_hugepages", "w", encoding="utf-8") as f:
+ f.write(str(count))
+
+
+def _mp_clear_wait(cfg, src_queue):
+ """Wait for the io_uring memory provider to clear from the leased
+ physical queue; io_uring tears it down asynchronously after the
+ process holding the ifq exits."""
+ netdevnl = NetdevFamily()
+ deadline = time.time() + 5
+ while time.time() < deadline:
+ queue_info = netdevnl.queue_get(
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
+ )
+ if "io-uring" not in queue_info:
+ return
+ time.sleep(0.1)
+ raise TimeoutError("Timed out waiting for memory provider to clear")
+
+
+def _create_netkit_pair(cfg, rxqueues=2):
+ if cfg.nk_host_ifname:
+ cmd(f"ip link del dev {cfg.nk_host_ifname}", fail=False)
+ cfg.nk_host_ifname = None
+ cfg.nk_guest_ifname = None
+ cfg.detach_bpf()
+
+ all_links = ip("-d link show", json=True)
+ old_idxs = {
+ link["ifindex"]
+ for link in all_links
+ if link.get("linkinfo", {}).get("info_kind") == "netkit"
+ }
+
+ rtnl = RtnlFamily()
+ rtnl.newlink(
+ {
+ "linkinfo": {
+ "kind": "netkit",
+ "data": {
+ "mode": "l2",
+ "policy": "forward",
+ "peer-policy": "forward",
+ },
+ },
+ "num-rx-queues": rxqueues,
+ },
+ flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL],
+ )
+
+ all_links = ip("-d link show", json=True)
+ nk_links = [
+ link
+ for link in all_links
+ if link.get("linkinfo", {}).get("info_kind") == "netkit"
+ and link["ifindex"] not in old_idxs
+ ]
+ if len(nk_links) != 2:
+ raise KsftSkipEx("Failed to create netkit pair")
+
+ nk_links.sort(key=lambda x: x["ifindex"])
+ cfg.nk_host_ifname = nk_links[1]["ifname"]
+ cfg.nk_guest_ifname = nk_links[0]["ifname"]
+ cfg.nk_host_ifindex = nk_links[1]["ifindex"]
+ cfg.nk_guest_ifindex = nk_links[0]["ifindex"]
+
+ ip(f"link set dev {cfg.nk_guest_ifname} netns {cfg.netns.name}")
+ ip(f"link set dev {cfg.nk_host_ifname} up")
+ ip(f"-6 addr add fe80::1/64 dev {cfg.nk_host_ifname} nodad")
+ ip(
+ f"-6 route add {cfg.nk_guest_ipv6}/128 via fe80::2 "
+ f"dev {cfg.nk_host_ifname}"
+ )
+ ip(f"link set dev {cfg.nk_guest_ifname} up", ns=cfg.netns)
+ ip(f"-6 addr add fe80::2/64 dev {cfg.nk_guest_ifname}", ns=cfg.netns)
+ ip(
+ f"-6 addr add {cfg.nk_guest_ipv6}/64 dev {cfg.nk_guest_ifname} nodad",
+ ns=cfg.netns,
+ )
+ ip(
+ f"-6 route add default via fe80::1 dev {cfg.nk_guest_ifname}",
+ ns=cfg.netns,
+ )
+
+ cfg.attach_bpf()
+
+
+def _setup_lease(cfg, rxqueues=2):
+ _create_netkit_pair(cfg, rxqueues=rxqueues)
+
+ ethnl = EthtoolFamily()
+ channels = ethnl.channels_get({"header": {"dev-index": cfg.ifindex}})[
+ "combined-count"
+ ]
+ if channels < 2:
+ raise KsftSkipEx(
+ "Test requires NETIF with at least 2 combined channels"
+ )
+ src_queue = channels - 1
+
+ with NetNSEnter(str(cfg.netns)):
+ netdevnl = NetdevFamily()
+ bind_result = netdevnl.queue_create(
+ {
+ "ifindex": cfg.nk_guest_ifindex,
+ "type": "rx",
+ "lease": {
+ "ifindex": cfg.ifindex,
+ "queue": {"id": src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ return src_queue, bind_result["id"]
+
+
+def _teardown_netkit(cfg):
+ if cfg.nk_host_ifname:
+ cmd(f"ip link del dev {cfg.nk_host_ifname}", fail=False)
+ cfg.nk_host_ifname = None
+ cfg.nk_guest_ifname = None
+
+
+def set_flow_rule(cfg, src_queue):
output = ethtool(
- f"-N {cfg.ifname} flow-type tcp6 dst-port {cfg.port} action {cfg.src_queue}"
+ f"-N {cfg.ifname} flow-type tcp6 dst-port {cfg.port} action {src_queue}"
).stdout
values = re.search(r"ID (\d+)", output).group(1)
return int(values)
@@ -41,6 +171,8 @@ def set_flow_rule(cfg):
def test_iou_zcrx(cfg) -> None:
cfg.require_ipver("6")
+ src_queue, nk_queue = _setup_lease(cfg)
+ defer(_teardown_netkit, cfg)
ethnl = EthtoolFamily()
rings = ethnl.rings_get({"header": {"dev-index": cfg.ifindex}})
@@ -65,40 +197,121 @@ def test_iou_zcrx(cfg) -> None:
},
)
- ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
+ ethtool(f"-X {cfg.ifname} equal {src_queue}")
defer(ethtool, f"-X {cfg.ifname} default")
- flow_rule_id = set_flow_rule(cfg)
+ flow_rule_id = set_flow_rule(cfg, src_queue)
defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
- rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}"
+ rx_cmd = (
+ f"{cfg.bin_local} -s -p {cfg.port} "
+ f"-i {cfg.nk_guest_ifname} -q {nk_queue}"
+ )
tx_cmd = f"{cfg.bin_remote} -c -h {cfg.nk_guest_ipv6} -p {cfg.port} -l 12840"
- with bkg(rx_cmd, exit_wait=True):
+ with bkg(rx_cmd, exit_wait=True, ns=cfg.netns):
+ wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns)
+ cmd(tx_cmd, host=cfg.remote)
+
+
+def test_iou_zcrx_large_buf(cfg) -> None:
+ """iou-zcrx with rx_buf_len > page size, going through a netkit-leased
+ queue. Exercises the queue rx-buf-len path via netif_mp_open_rxq()'s
+ lease redirect: the netkit ifindex is opaque to io_uring, but
+ rx_page_size is honoured by the *physical* qops because the lease
+ pointer rewrites the request from netkit onto the leased physical
+ rxq before supported_params/validate_qcfg are consulted.
+ """
+ cfg.require_ipver("6")
+ src_queue, nk_queue = _setup_lease(cfg)
+ defer(_teardown_netkit, cfg)
+ ethnl = EthtoolFamily()
+
+ with open("/proc/sys/vm/nr_hugepages", "r+", encoding="utf-8") as f:
+ nr_hugepages = int(f.read().strip())
+ if nr_hugepages < 64:
+ f.seek(0)
+ f.write("64")
+ defer(_restore_hugepages, nr_hugepages)
+
+ rings = ethnl.rings_get({"header": {"dev-index": cfg.ifindex}})
+ rx_rings = rings["rx"]
+ hds_thresh = rings.get("hds-thresh", 0)
+
+ ethnl.rings_set(
+ {
+ "header": {"dev-index": cfg.ifindex},
+ "tcp-data-split": "enabled",
+ "hds-thresh": 0,
+ "rx": 64,
+ }
+ )
+ defer(
+ ethnl.rings_set,
+ {
+ "header": {"dev-index": cfg.ifindex},
+ "tcp-data-split": "unknown",
+ "hds-thresh": hds_thresh,
+ "rx": rx_rings,
+ },
+ )
+
+ ethtool(f"-X {cfg.ifname} equal {src_queue}")
+ defer(ethtool, f"-X {cfg.ifname} default")
+
+ flow_rule_id = set_flow_rule(cfg, src_queue)
+ defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
+
+ # -x 2 asks iou-zcrx for rx_buf_len = 2 * page_size (8 KiB on x86_64),
+ # backed by a 2 MiB hugepage area so the chunks are physically
+ # contiguous, which is what zcrx requires for non-default rx_buf_len.
+ rx_cmd = (
+ f"{cfg.bin_local} -s -p {cfg.port} "
+ f"-i {cfg.nk_guest_ifname} -q {nk_queue} -x 2"
+ )
+ tx_cmd = f"{cfg.bin_remote} -c -h {cfg.nk_guest_ipv6} -p {cfg.port} -l 12840"
+
+ # Probe via -d (dry run): exits with SKIP_CODE if the leased physical
+ # qops doesn't advertise QCFG_RX_PAGE_SIZE (e.g. older bnxt FW/HW).
+ probe = cmd(rx_cmd + " -d", fail=False, ns=cfg.netns)
+ if probe.ret == SKIP_CODE:
+ msg = probe.stdout.strip() or "rx_buf_len not supported by leased NIC"
+ raise KsftSkipEx(msg)
+
+ # A successful dry run still registered the zcrx ifq on the leased
+ # physical queue; wait for its async teardown before the real server
+ # binds the same queue.
+ _mp_clear_wait(cfg, src_queue)
+
+ with bkg(rx_cmd, exit_wait=True, ns=cfg.netns):
wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns)
cmd(tx_cmd, host=cfg.remote)
def test_attrs(cfg) -> None:
cfg.require_ipver("6")
+ src_queue, nk_queue = _setup_lease(cfg)
+ defer(_teardown_netkit, cfg)
netdevnl = NetdevFamily()
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
- ksft_eq(queue_info["id"], cfg.src_queue)
+ ksft_eq(queue_info["id"], src_queue)
ksft_eq(queue_info["type"], "rx")
ksft_eq(queue_info["ifindex"], cfg.ifindex)
ksft_in("lease", queue_info)
lease = queue_info["lease"]
ksft_eq(lease["ifindex"], cfg.nk_guest_ifindex)
- ksft_eq(lease["queue"]["id"], cfg.nk_queue)
+ ksft_eq(lease["queue"]["id"], nk_queue)
ksft_eq(lease["queue"]["type"], "rx")
ksft_in("netns-id", lease)
def test_attach_xdp_with_mp(cfg) -> None:
cfg.require_ipver("6")
+ src_queue, nk_queue = _setup_lease(cfg)
+ defer(_teardown_netkit, cfg)
ethnl = EthtoolFamily()
rings = ethnl.rings_get({"header": {"dev-index": cfg.ifindex}})
@@ -123,18 +336,21 @@ def test_attach_xdp_with_mp(cfg) -> None:
},
)
- ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
+ ethtool(f"-X {cfg.ifname} equal {src_queue}")
defer(ethtool, f"-X {cfg.ifname} default")
netdevnl = NetdevFamily()
- rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}"
- with bkg(rx_cmd):
+ rx_cmd = (
+ f"{cfg.bin_local} -s -p {cfg.port} "
+ f"-i {cfg.nk_guest_ifname} -q {nk_queue}"
+ )
+ with bkg(rx_cmd, ns=cfg.netns):
wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns)
time.sleep(0.1)
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
ksft_in("io-uring", queue_info)
@@ -144,13 +360,15 @@ def test_attach_xdp_with_mp(cfg) -> None:
time.sleep(0.1)
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
ksft_not_in("io-uring", queue_info)
def test_destroy(cfg) -> None:
cfg.require_ipver("6")
+ src_queue, nk_queue = _setup_lease(cfg)
+ defer(_teardown_netkit, cfg)
ethnl = EthtoolFamily()
rings = ethnl.rings_get({"header": {"dev-index": cfg.ifindex}})
@@ -175,16 +393,19 @@ def test_destroy(cfg) -> None:
},
)
- ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
+ ethtool(f"-X {cfg.ifname} equal {src_queue}")
defer(ethtool, f"-X {cfg.ifname} default")
- rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}"
- rx_proc = cmd(rx_cmd, background=True)
+ rx_cmd = (
+ f"{cfg.bin_local} -s -p {cfg.port} "
+ f"-i {cfg.nk_guest_ifname} -q {nk_queue}"
+ )
+ rx_proc = cmd(rx_cmd, background=True, ns=cfg.netns)
wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns)
netdevnl = NetdevFamily()
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
ksft_in("io-uring", queue_info)
@@ -193,23 +414,20 @@ def test_destroy(cfg) -> None:
kill_timer = threading.Timer(1, rx_proc.proc.terminate)
kill_timer.start()
- ip(f"link del dev {cfg._nk_host_ifname}")
+ ip(f"link del dev {cfg.nk_host_ifname}")
kill_timer.join()
- cfg._nk_host_ifname = None
- cfg._nk_guest_ifname = None
+ cfg.nk_host_ifname = None
+ cfg.nk_guest_ifname = None
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
ksft_not_in("io-uring", queue_info)
- cmd(f"tc filter del dev {cfg.ifname} ingress pref {cfg._bpf_prog_pref}")
- cfg._tc_attached = False
-
- flow_rule_id = set_flow_rule(cfg)
+ flow_rule_id = set_flow_rule(cfg, src_queue)
defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
- rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.src_queue}"
+ rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {src_queue}"
tx_cmd = f"{cfg.bin_remote} -c -h {cfg.addr_v['6']} -p {cfg.port} -l 12840"
with bkg(rx_cmd, exit_wait=True):
wait_port_listen(cfg.port, proto="tcp")
@@ -217,7 +435,7 @@ def test_destroy(cfg) -> None:
# Short delay since iou cleanup is async and takes a bit of time.
time.sleep(0.1)
queue_info = netdevnl.queue_get(
- {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"}
+ {"ifindex": cfg.ifindex, "id": src_queue, "type": "rx"}
)
ksft_not_in("io-uring", queue_info)
@@ -230,32 +448,14 @@ def main() -> None:
cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
cfg.port = rand_port()
- ethnl = EthtoolFamily()
- channels = ethnl.channels_get({"header": {"dev-index": cfg.ifindex}})
- channels = channels["combined-count"]
- if channels < 2:
- raise KsftSkipEx("Test requires NETIF with at least 2 combined channels")
-
- cfg.src_queue = channels - 1
-
- with NetNSEnter(str(cfg.netns)):
- netdevnl = NetdevFamily()
- bind_result = netdevnl.queue_create(
- {
- "ifindex": cfg.nk_guest_ifindex,
- "type": "rx",
- "lease": {
- "ifindex": cfg.ifindex,
- "queue": {"id": cfg.src_queue, "type": "rx"},
- "netns-id": 0,
- },
- }
- )
- cfg.nk_queue = bind_result["id"]
-
- # test_destroy must be last because it destroys the netkit devices
ksft_run(
- [test_iou_zcrx, test_attrs, test_attach_xdp_with_mp, test_destroy],
+ [
+ test_iou_zcrx,
+ test_iou_zcrx_large_buf,
+ test_attrs,
+ test_attach_xdp_with_mp,
+ test_destroy,
+ ],
args=(cfg,),
)
ksft_exit()
diff --git a/tools/testing/selftests/drivers/net/hw/ntuple.py b/tools/testing/selftests/drivers/net/hw/ntuple.py
index 232733142c02..ef4604bfa8ef 100755
--- a/tools/testing/selftests/drivers/net/hw/ntuple.py
+++ b/tools/testing/selftests/drivers/net/hw/ntuple.py
@@ -22,7 +22,10 @@ class NtupleField(Enum):
def _require_ntuple(cfg):
features = ethtool(f"-k {cfg.ifname}", json=True)[0]
if not features["ntuple-filters"]["active"]:
- raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))
+ if features["ntuple-filters"]["fixed"]:
+ raise KsftSkipEx("Device does not support ntuple-filters")
+ ethtool(f"-K {cfg.ifname} ntuple-filters on")
+ defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
def _get_rx_cnts(cfg, prev=None):
diff --git a/tools/testing/selftests/drivers/net/hw/rss_ctx.py b/tools/testing/selftests/drivers/net/hw/rss_ctx.py
index 51f4e7bc3e5d..f36f76d6ca59 100755
--- a/tools/testing/selftests/drivers/net/hw/rss_ctx.py
+++ b/tools/testing/selftests/drivers/net/hw/rss_ctx.py
@@ -9,7 +9,7 @@ from lib.py import ksft_disruptive
from lib.py import ksft_run, ksft_pr, ksft_exit
from lib.py import ksft_eq, ksft_ne, ksft_ge, ksft_in, ksft_lt, ksft_true, ksft_raises
from lib.py import NetDrvEpEnv
-from lib.py import EthtoolFamily, NetdevFamily
+from lib.py import EthtoolFamily, NetdevFamily, NlError
from lib.py import KsftSkipEx, KsftFailEx
from lib.py import rand_port, rand_ports
from lib.py import cmd, ethtool, ip, defer, CmdExitFailure, wait_file
@@ -57,9 +57,10 @@ def ethtool_create(cfg, act, opts):
def require_ntuple(cfg):
features = ethtool(f"-k {cfg.ifname}", json=True)[0]
if not features["ntuple-filters"]["active"]:
- # ntuple is more of a capability than a config knob, don't bother
- # trying to enable it (until some driver actually needs it).
- raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))
+ if features["ntuple-filters"]["fixed"]:
+ raise KsftSkipEx("Device does not support ntuple-filters")
+ ethtool(f"-K {cfg.ifname} ntuple-filters on")
+ defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
def require_context_cnt(cfg, need_cnt):
@@ -828,6 +829,94 @@ def test_rss_default_context_rule(cfg):
'noise' : (0, 1) })
+def _set_flow_hash(cfg, fl_type, fields, context=0):
+ req = {"header": {"dev-index": cfg.ifindex},
+ "flow-hash": {fl_type: fields}}
+ if context:
+ req["context"] = context
+ cfg.ethnl.rss_set(req)
+
+
+def _get_flow_hash(cfg, fl_type, context=0):
+ req = {"header": {"dev-index": cfg.ifindex}}
+ if context:
+ req["context"] = context
+ rss = cfg.ethnl.rss_get(req)
+ return rss.get("flow-hash", {}).get(fl_type, set())
+
+
+def test_rss_context_flow_hash(cfg):
+ """
+ Validate, with traffic, that an additional RSS context honors the
+ flow-hash field selection. If the driver lacks per-context field
+ configuration ("ops->rxfh_per_ctx_fields") fall back to setting the
+ fields on the main context, which the kernel applies device-wide.
+ """
+
+ require_ntuple(cfg)
+
+ queue_cnt = len(_get_rx_cnts(cfg))
+ if queue_cnt < 6:
+ try:
+ ksft_pr(f"Increasing queue count {queue_cnt} -> 6")
+ ethtool(f"-L {cfg.ifname} combined 6")
+ defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
+ except CmdExitFailure as exc:
+ raise KsftSkipEx("Not enough queues for the test") from exc
+
+ fl_type = f"tcp{cfg.addr_ipver}"
+ if not _get_flow_hash(cfg, fl_type):
+ raise KsftSkipEx(f"Device does not report flow-hash for {fl_type}")
+
+ # Reserve queues 0/1 for main, build a new context spanning 2..5
+ ethtool(f"-X {cfg.ifname} equal 2")
+ defer(ethtool, f"-X {cfg.ifname} default")
+ ctx_id = ethtool_create(cfg, "-X", "context new start 2 equal 4")
+ defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
+
+ port = rand_port()
+ flow = f"flow-type {fl_type} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
+ ntuple = ethtool_create(cfg, "-N", flow)
+ defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")
+
+ ip_only = {"ip-src", "ip-dst"}
+ ip_l4 = ip_only | {"l4-b-0-1", "l4-b-2-3"}
+
+ # Try per-context flow-hash; fall back to main context if unsupported.
+ cfg_ctx = ctx_id
+ try:
+ orig = _get_flow_hash(cfg, fl_type, context=ctx_id)
+ _set_flow_hash(cfg, fl_type, ip_only, context=ctx_id)
+ except NlError:
+ ksft_pr("Per-context flow-hash not supported, using device-wide")
+ cfg_ctx = 0
+ orig = _get_flow_hash(cfg, fl_type)
+ _set_flow_hash(cfg, fl_type, ip_only)
+ defer(_set_flow_hash, cfg, fl_type, orig, context=cfg_ctx)
+
+ def measure():
+ cnts = _get_rx_cnts(cfg)
+ GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
+ cnts = _get_rx_cnts(cfg, prev=cnts)
+ ctx_cnts = cnts[2:6]
+ directed = sum(ctx_cnts)
+ used = sum(1 for c in ctx_cnts if c > directed / 200)
+ return cnts, directed, used
+
+ # IP-only hash: iperf3 streams share src/dst IP, all should land on the
+ # same queue inside the context's range.
+ cnts, directed, used = measure()
+ ksft_ge(directed, 20000, f"traffic on context {ctx_id} (IP-only): {cnts}")
+ ksft_eq(used, 1, f"IP-only hash should use one queue in context {ctx_id}, got: {cnts}")
+
+ # IP+L4 hash: streams have distinct src ports, traffic should spread.
+ _set_flow_hash(cfg, fl_type, ip_l4, context=cfg_ctx)
+
+ cnts, directed, used = measure()
+ ksft_ge(directed, 20000, f"traffic on context {ctx_id} (IP+L4): {cnts}")
+ ksft_ge(used, 2, f"IP+L4 hash should spread across context {ctx_id} queues, got: {cnts}")
+
+
@ksft_disruptive
def test_rss_context_persist_ifupdown(cfg, pre_down=False):
"""
@@ -935,6 +1024,7 @@ def main() -> None:
test_flow_add_context_missing,
test_delete_rss_context_busy, test_rss_ntuple_addition,
test_rss_default_context_rule,
+ test_rss_context_flow_hash,
test_rss_context_persist_create_and_ifdown,
test_rss_context_persist_ifdown_and_create],
args=(cfg, ))
diff --git a/tools/testing/selftests/drivers/net/hw/tso.py b/tools/testing/selftests/drivers/net/hw/tso.py
index bb675e3dac88..1b789fea8929 100755
--- a/tools/testing/selftests/drivers/net/hw/tso.py
+++ b/tools/testing/selftests/drivers/net/hw/tso.py
@@ -239,6 +239,9 @@ def main() -> None:
("vxlan_csum", "", "tx-udp_tnl-csum-segmentation", ("vxlan", "id 100 dstport 4789 udpcsum", ("4", "6"))),
("gre", "4", "tx-gre-segmentation", ("gre", "", ("4", "6"))),
("gre", "6", "tx-gre-segmentation", ("ip6gre","", ("4", "6"))),
+ ("ip", "6", "tx-ipxip6-segmentation", ("ip6tnl","mode any", ("4", "6"))),
+ ("ip", "4", "tx-ipxip4-segmentation", ("sit","", ("6", ))),
+ ("ip", "4", "tx-ipxip4-segmentation", ("ipip","", ("4", ))),
)
cases = []
diff --git a/tools/testing/selftests/drivers/net/hw/userns_devmem.py b/tools/testing/selftests/drivers/net/hw/userns_devmem.py
new file mode 100755
index 000000000000..2aaf6ea81715
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/userns_devmem.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Devmem tests for non-init userns.
+"""
+
+import os
+
+from devmem_lib import run_rx, run_rx_hds, run_tx, run_tx_chunks, setup_test
+from lib.py import NetDrvContEnv, ksft_disruptive, ksft_exit, ksft_run
+
+
+@ksft_disruptive
+def check_userns_rx(cfg) -> None:
+ """Run the devmem RX test through non-init userns netkit."""
+ run_rx(cfg)
+
+
+@ksft_disruptive
+def check_userns_tx(cfg) -> None:
+ """Run the devmem TX test through non-init userns netkit."""
+ run_tx(cfg)
+
+
+@ksft_disruptive
+def check_userns_tx_chunks(cfg) -> None:
+ """Run the devmem TX chunking test through non-init userns netkit."""
+ run_tx_chunks(cfg)
+
+
+def check_userns_rx_hds(cfg) -> None:
+ """Run the HDS test through non-init userns netkit."""
+ run_rx_hds(cfg)
+
+
+def main() -> None:
+ """Run userns devmem RX selftests against the test environment."""
+ with NetDrvContEnv(__file__, userns=True, rxqueues=2,
+ primary_rx_redirect=True) as cfg:
+ setup_test(cfg,
+ os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "ncdevmem"))
+ ksft_run([check_userns_rx, check_userns_tx, check_userns_tx_chunks,
+ check_userns_rx_hds], args=(cfg,))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py
index 2b5ec0505672..ee903bcf3207 100644
--- a/tools/testing/selftests/drivers/net/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py
@@ -18,12 +18,13 @@ try:
sys.path.append(KSFT_DIR.as_posix())
# Import one by one to avoid pylint false positives
- from net.lib.py import NetNS, NetNSEnter, NetdevSimDev
+ from net.lib.py import NetNS, NetNSEnter, NetdevSimDev, UserNetNS
from net.lib.py import EthtoolFamily, NetdevFamily, NetshaperFamily, \
NlError, RtnlFamily, DevlinkFamily, PSPFamily, Netlink
from net.lib.py import CmdExitFailure
from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \
- fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, wait_file
+ fd_read_timeout, ip, rand_port, rand_ports, tc, wait_port_listen, \
+ wait_file
from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
@@ -31,12 +32,12 @@ try:
from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \
ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none
- __all__ = ["NetNS", "NetNSEnter", "NetdevSimDev",
+ __all__ = ["NetNS", "NetNSEnter", "NetdevSimDev", "UserNetNS",
"EthtoolFamily", "NetdevFamily", "NetshaperFamily",
"NlError", "RtnlFamily", "DevlinkFamily", "PSPFamily", "Netlink",
"CmdExitFailure",
"bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool",
- "fd_read_timeout", "ip", "rand_port", "rand_ports",
+ "fd_read_timeout", "ip", "rand_port", "rand_ports", "tc",
"wait_port_listen", "wait_file",
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
"KsftSkipEx", "KsftFailEx", "KsftXfailEx",
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
index 24ce122abd9c..e4ab99b905b1 100644
--- a/tools/testing/selftests/drivers/net/lib/py/env.py
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -2,13 +2,14 @@
import ipaddress
import os
+import sys
import time
import json
from pathlib import Path
from lib.py import KsftSkipEx, KsftXfailEx
from lib.py import ksft_setup, wait_file
from lib.py import cmd, ethtool, ip, CmdExitFailure
-from lib.py import NetNS, NetdevSimDev
+from lib.py import NetNS, NetdevSimDev, UserNetNS
from .remote import Remote
from . import bpftool, RtnlFamily, Netlink
@@ -336,15 +337,20 @@ class NetDrvContEnv(NetDrvEpEnv):
+---------------+
"""
- def __init__(self, src_path, rxqueues=1, **kwargs):
+ def __init__(self, src_path, rxqueues=1, primary_rx_redirect=False,
+ userns=False, **kwargs):
self.netns = None
- self._nk_host_ifname = None
- self._nk_guest_ifname = None
+ self._userns = userns
+ self.nk_host_ifname = None
+ self.nk_guest_ifname = None
self._tc_clsact_added = False
self._tc_attached = False
+ self._primary_rx_redirect_attached = False
+ self._primary_rx_redirect_clsact_added = False
self._bpf_prog_pref = None
self._bpf_prog_id = None
self._init_ns_attached = False
+ self._remote_route_added = False
self._old_fwd = None
self._old_accept_ra = None
@@ -389,15 +395,25 @@ class NetDrvContEnv(NetDrvEpEnv):
raise KsftSkipEx("Failed to create netkit pair")
netkit_links.sort(key=lambda x: x['ifindex'])
- self._nk_host_ifname = netkit_links[1]['ifname']
- self._nk_guest_ifname = netkit_links[0]['ifname']
+ self.nk_host_ifname = netkit_links[1]['ifname']
+ self.nk_guest_ifname = netkit_links[0]['ifname']
self.nk_host_ifindex = netkit_links[1]['ifindex']
self.nk_guest_ifindex = netkit_links[0]['ifindex']
self._setup_ns()
- self._attach_bpf()
+ self.attach_bpf()
+ if primary_rx_redirect:
+ self._attach_primary_rx_redirect_bpf()
def __del__(self):
+ if self._primary_rx_redirect_attached:
+ cmd(f"tc filter del dev {self.nk_host_ifname} ingress", fail=False)
+ self._primary_rx_redirect_attached = False
+
+ if self._primary_rx_redirect_clsact_added:
+ cmd(f"tc qdisc del dev {self.nk_host_ifname} clsact", fail=False)
+ self._primary_rx_redirect_clsact_added = False
+
if self._tc_attached:
cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}")
self._tc_attached = False
@@ -406,10 +422,15 @@ class NetDrvContEnv(NetDrvEpEnv):
cmd(f"tc qdisc del dev {self.ifname} clsact")
self._tc_clsact_added = False
- if self._nk_host_ifname:
- cmd(f"ip link del dev {self._nk_host_ifname}")
- self._nk_host_ifname = None
- self._nk_guest_ifname = None
+ if self._remote_route_added:
+ cmd(f"ip -6 route del {self.nk_guest_ipv6}/128",
+ host=self.remote, fail=False)
+ self._remote_route_added = False
+
+ if self.nk_host_ifname:
+ cmd(f"ip link del dev {self.nk_host_ifname}")
+ self.nk_host_ifname = None
+ self.nk_guest_ifname = None
if self._init_ns_attached:
cmd("ip netns del init", fail=False)
@@ -444,28 +465,37 @@ class NetDrvContEnv(NetDrvEpEnv):
with open(ra_path, "w", encoding="utf-8") as f:
f.write("2")
- self.netns = NetNS()
+ self.netns = UserNetNS() if self._userns else NetNS()
cmd("ip netns attach init 1")
self._init_ns_attached = True
ip("netns set init 0", ns=self.netns)
- ip(f"link set dev {self._nk_guest_ifname} netns {self.netns.name}")
- ip(f"link set dev {self._nk_host_ifname} up")
- ip(f"-6 addr add fe80::1/64 dev {self._nk_host_ifname} nodad")
- ip(f"-6 route add {self.nk_guest_ipv6}/128 via fe80::2 dev {self._nk_host_ifname}")
+ ip(f"link set dev {self.nk_guest_ifname} netns {self.netns.name}")
+ nk_guest_dev = ip(f"link show dev {self.nk_guest_ifname}",
+ json=True, ns=self.netns)[0]
+ self.nk_guest_ifindex = nk_guest_dev['ifindex']
+ ip(f"link set dev {self.nk_host_ifname} up")
+ ip(f"-6 addr add fe80::1/64 dev {self.nk_host_ifname} nodad")
+ ip(f"-6 route add {self.nk_guest_ipv6}/128 via fe80::2 dev {self.nk_host_ifname}")
ip("link set lo up", ns=self.netns)
- ip(f"link set dev {self._nk_guest_ifname} up", ns=self.netns)
- ip(f"-6 addr add fe80::2/64 dev {self._nk_guest_ifname}", ns=self.netns)
- ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self._nk_guest_ifname} nodad", ns=self.netns)
- ip(f"-6 route add default via fe80::1 dev {self._nk_guest_ifname}", ns=self.netns)
+ ip(f"link set dev {self.nk_guest_ifname} up", ns=self.netns)
+ ip(f"-6 addr add fe80::2/64 dev {self.nk_guest_ifname}", ns=self.netns)
+ ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self.nk_guest_ifname} nodad", ns=self.netns)
+ ip(f"-6 route add default via fe80::1 dev {self.nk_guest_ifname}", ns=self.netns)
- def _tc_ensure_clsact(self):
- qdisc = json.loads(cmd(f"tc -j qdisc show dev {self.ifname}").stdout)
+ def _tc_ensure_clsact(self, ifname=None):
+ """Ensure a clsact qdisc exists on @ifname.
+
+ Returns True if this call added the qdisc, otherwise returns False.
+ """
+ if ifname is None:
+ ifname = self.ifname
+ qdisc = json.loads(cmd(f"tc -j qdisc show dev {ifname}").stdout)
for q in qdisc:
if q['kind'] == 'clsact':
- return
- cmd(f"tc qdisc add dev {self.ifname} clsact")
- self._tc_clsact_added = True
+ return False
+ cmd(f"tc qdisc add dev {ifname} clsact")
+ return True
def _get_bpf_prog_ids(self):
filters = json.loads(cmd(f"tc -j filter show dev {self.ifname} ingress").stdout)
@@ -476,28 +506,43 @@ class NetDrvContEnv(NetDrvEpEnv):
return (bpf['pref'], bpf['options']['prog']['id'])
raise Exception("Failed to get BPF prog ID")
- def _attach_bpf(self):
- bpf_obj = self.test_dir / "nk_forward.bpf.o"
- if not bpf_obj.exists():
- raise KsftSkipEx("BPF prog not found")
+ def _find_bss_map_id(self, prog_id):
+ """Find the .bss map ID for a loaded BPF program."""
+ prog_info = bpftool(f"prog show id {prog_id}", json=True)
+ for map_id in prog_info.get("map_ids", []):
+ map_info = bpftool(f"map show id {map_id}", json=True)
+ if map_info.get("name", "").endswith("bss"):
+ return map_id
+ raise Exception(f"Failed to find .bss map for prog {prog_id}")
+
+ def _find_bpf_obj(self, name):
+ bpf_obj = self.test_dir / name
+ if bpf_obj.exists():
+ return bpf_obj
+ bpf_obj = self.test_dir / "hw" / name
+ if bpf_obj.exists():
+ return bpf_obj
+ return None
+
+ def detach_bpf(self):
+ if self._tc_attached:
+ cmd(f"tc filter del dev {self.ifname} ingress pref "
+ f"{self._bpf_prog_pref}", fail=False)
+ self._tc_attached = False
+
+ def attach_bpf(self):
+ bpf_obj = self._find_bpf_obj("nk_forward.bpf.o")
+ if not bpf_obj:
+ raise KsftSkipEx("BPF prog nk_forward.bpf.o not found")
- self._tc_ensure_clsact()
+ if self._tc_ensure_clsact():
+ self._tc_clsact_added = True
cmd(f"tc filter add dev {self.ifname} ingress bpf obj {bpf_obj}"
" sec tc/ingress direct-action")
self._tc_attached = True
(self._bpf_prog_pref, self._bpf_prog_id) = self._get_bpf_prog_ids()
- prog_info = bpftool(f"prog show id {self._bpf_prog_id}", json=True)
- map_ids = prog_info.get("map_ids", [])
-
- bss_map_id = None
- for map_id in map_ids:
- map_info = bpftool(f"map show id {map_id}", json=True)
- if map_info.get("name").endswith("bss"):
- bss_map_id = map_id
-
- if bss_map_id is None:
- raise Exception("Failed to find .bss map")
+ bss_map_id = self._find_bss_map_id(self._bpf_prog_id)
ipv6_addr = ipaddress.IPv6Address(self.ipv6_prefix)
ipv6_bytes = ipv6_addr.packed
@@ -505,3 +550,36 @@ class NetDrvContEnv(NetDrvEpEnv):
value = ipv6_bytes + ifindex_bytes
value_hex = ' '.join(f'{b:02x}' for b in value)
bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
+
+ def _attach_primary_rx_redirect_bpf(self):
+ """Attach BPF redirect program on the primary netkit ingress."""
+ bpf_obj = self._find_bpf_obj("nk_primary_rx_redirect.bpf.o")
+ if not bpf_obj:
+ raise KsftSkipEx("nk_primary_rx_redirect.bpf.o not found")
+
+ if self._tc_ensure_clsact(self.nk_host_ifname):
+ self._primary_rx_redirect_clsact_added = True
+ cmd(f"tc filter add dev {self.nk_host_ifname} ingress"
+ f" bpf obj {bpf_obj} sec tc/ingress direct-action")
+ self._primary_rx_redirect_attached = True
+
+ ip(f"-6 route add {self.nk_guest_ipv6}/128 via {self.addr_v['6']}",
+ host=self.remote)
+ self._remote_route_added = True
+
+ filters = json.loads(
+ cmd(f"tc -j filter show dev {self.nk_host_ifname} ingress").stdout)
+ redirect_prog_id = None
+ for bpf in filters:
+ if 'options' not in bpf:
+ continue
+ if bpf['options']['bpf_name'].startswith('nk_primary_rx_redirect'):
+ redirect_prog_id = bpf['options']['prog']['id']
+ break
+ if redirect_prog_id is None:
+ raise Exception("Failed to get primary RX redirect BPF prog ID")
+
+ bss_map_id = self._find_bss_map_id(redirect_prog_id)
+ phys_ifindex_bytes = self.ifindex.to_bytes(4, byteorder=sys.byteorder)
+ value_hex = ' '.join(f'{b:02x}' for b in phys_ifindex_bytes)
+ bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py
index 864d9fce1094..315648a770d0 100755
--- a/tools/testing/selftests/drivers/net/psp.py
+++ b/tools/testing/selftests/drivers/net/psp.py
@@ -5,6 +5,7 @@
import errno
import fcntl
+import os
import socket
import struct
import termios
@@ -14,9 +15,13 @@ from lib.py import defer
from lib.py import ksft_run, ksft_exit, ksft_pr
from lib.py import ksft_true, ksft_eq, ksft_ne, ksft_gt, ksft_raises
from lib.py import ksft_not_none
-from lib.py import KsftSkipEx
-from lib.py import NetDrvEpEnv, PSPFamily, NlError
+from lib.py import ksft_variants, KsftNamedVariant
+from lib.py import KsftSkipEx, KsftFailEx
+from lib.py import NetDrvEpEnv, NetDrvContEnv
+from lib.py import Netlink, NlError, PSPFamily, RtnlFamily
+from lib.py import NetNSEnter
from lib.py import bkg, rand_port, wait_port_listen
+from lib.py import ip
def _get_outq(s):
@@ -117,11 +122,13 @@ def _get_stat(cfg, key):
# Test case boiler plate
#
-def _init_psp_dev(cfg):
+def _init_psp_dev(cfg, use_psp_ifindex=False):
if not hasattr(cfg, 'psp_dev_id'):
# Figure out which local device we are testing against
+ # For NetDrvContEnv: use psp_ifindex instead of ifindex
+ target_ifindex = cfg.psp_ifindex if use_psp_ifindex else cfg.ifindex
for dev in cfg.pspnl.dev_get({}, dump=True):
- if dev['ifindex'] == cfg.ifindex:
+ if dev['ifindex'] == target_ifindex:
cfg.psp_info = dev
cfg.psp_dev_id = cfg.psp_info['id']
break
@@ -571,33 +578,388 @@ def removal_device_bi(cfg):
_close_conn(cfg, s)
-def psp_ip_ver_test_builder(name, test_func, psp_ver, ipver):
- """Build test cases for each combo of PSP version and IP version"""
- def test_case(cfg):
- cfg.require_ipver(ipver)
- test_func(cfg, psp_ver, ipver)
+def _get_psp_ver_ip_variants():
+ for ver in range(4):
+ for ipv in ("4", "6"):
+ yield KsftNamedVariant(f"v{ver}_ip{ipv}", ver, ipv)
- test_case.__name__ = f"{name}_v{psp_ver}_ip{ipver}"
- return test_case
+def _get_ip_variants():
+ for ipv in ("4", "6"):
+ yield KsftNamedVariant(f"ip{ipv}", ipv)
-def ipver_test_builder(name, test_func, ipver):
- """Build test cases for each IP version"""
- def test_case(cfg):
- cfg.require_ipver(ipver)
- test_func(cfg, ipver)
- test_case.__name__ = f"{name}_ip{ipver}"
- return test_case
+@ksft_variants(_get_psp_ver_ip_variants())
+def data_basic_send(cfg, version, ipver):
+ """Test basic PSP data send."""
+ cfg.require_ipver(ipver)
+ _data_basic_send(cfg, version, ipver)
+
+
+@ksft_variants(_get_ip_variants())
+def data_mss_adjust(cfg, ipver):
+ """Test MSS adjustment with PSP."""
+ cfg.require_ipver(ipver)
+ _data_mss_adjust(cfg, ipver)
+
+
+def _check_assoc_list(cfg, psp_dev_id, ifindex, nsid=None):
+ """Verify assoc-list contains device with given ifindex, no duplicates."""
+ dev_info = cfg.pspnl.dev_get({'id': psp_dev_id})
+
+ ksft_true('assoc-list' in dev_info,
+ "No assoc-list in dev_get() response after association")
+ found = False
+ for assoc in dev_info['assoc-list']:
+ if assoc['ifindex'] != ifindex:
+ continue
+ if nsid is not None and assoc['nsid'] != nsid:
+ continue
+ ksft_eq(found, False, "Duplicate assoc entry found")
+ found = True
+ ksft_eq(found, True,
+ "Associated device not found in dev_get() response")
+
+
+def _data_basic_send_netkit_psp_assoc(cfg, version, ipver):
+ """
+ Test basic data send with netkit interface associated with PSP dev.
+ """
+ _assoc_nk_guest(cfg)
+
+ # Enter guest namespace (netns) to run PSP test
+ with NetNSEnter(cfg.netns.name):
+ cfg.pspnl = PSPFamily()
+
+ sock = _make_psp_conn(cfg, version, ipver)
+
+ rx_assoc = cfg.pspnl.rx_assoc({"version": version,
+ "dev-id": cfg.psp_dev_id,
+ "sock-fd": sock.fileno()})
+ rx_key = rx_assoc['rx-key']
+ tx_key = _spi_xchg(sock, rx_key)
+
+ cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
+ "version": version,
+ "tx-key": tx_key,
+ "sock-fd": sock.fileno()})
+
+ data_len = _send_careful(cfg, sock, 100)
+ _check_data_rx(cfg, data_len)
+ _close_psp_conn(cfg, sock)
+
+
+def _assoc_check_list(cfg):
+ """Test that assoc-list is correctly populated after dev-assoc."""
+ _assoc_nk_guest(cfg)
+ _check_assoc_list(cfg, cfg.psp_dev_id, cfg.nk_guest_ifindex,
+ cfg.psp_dev_peer_nsid)
+
+
+def _get_psp_ver_ip6_variants():
+ for ver in range(4):
+ yield KsftNamedVariant(f"v{ver}_ip6", ver, "6")
+
+
+@ksft_variants(_get_psp_ver_ip6_variants())
+def data_basic_send_netkit_psp_assoc(cfg, version, ipver):
+ """Test PSP data send via netkit with dev-assoc."""
+ cfg.require_ipver(ipver)
+ _data_basic_send_netkit_psp_assoc(cfg, version, ipver)
+
+
+def _key_rotation_notify_multi_ns_netkit(cfg):
+ """ Test key rotation notifications across multiple namespaces using netkit """
+ _assoc_nk_guest(cfg)
+
+ # Create listener in guest namespace; socket stays bound to that ns
+ with NetNSEnter(cfg.netns.name):
+ peer_pspnl = PSPFamily()
+ peer_pspnl.ntf_subscribe('use')
+
+ # Create listener in main namespace
+ main_pspnl = PSPFamily()
+ main_pspnl.ntf_subscribe('use')
+
+ # Trigger key rotation on the PSP device
+ cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
+
+ # Poll both sockets from main thread
+ for pspnl, label in [(main_pspnl, "main"), (peer_pspnl, "guest")]:
+ for ntf in pspnl.poll_ntf(duration=10):
+ if ntf['msg'].get('id') == cfg.psp_dev_id:
+ break
+ else:
+ raise KsftFailEx(
+ f"No key rotation notification received"
+ f" in {label} namespace")
+
+
+def _dev_change_notify_multi_ns_netkit(cfg):
+ """ Test dev_change notifications across multiple namespaces using netkit """
+ _assoc_nk_guest(cfg)
+
+ # Create listener in guest namespace; socket stays bound to that ns
+ with NetNSEnter(cfg.netns.name):
+ peer_pspnl = PSPFamily()
+ peer_pspnl.ntf_subscribe('mgmt')
+
+ # Create listener in main namespace
+ main_pspnl = PSPFamily()
+ main_pspnl.ntf_subscribe('mgmt')
+
+ # Trigger dev_change by calling dev_set (notification is always sent)
+ cfg.pspnl.dev_set({'id': cfg.psp_dev_id,
+ 'psp-versions-ena': cfg.psp_info['psp-versions-cap']})
+
+ # Poll both sockets from main thread
+ for pspnl, label in [(main_pspnl, "main"), (peer_pspnl, "guest")]:
+ for ntf in pspnl.poll_ntf(duration=10):
+ if ntf['msg'].get('id') == cfg.psp_dev_id:
+ break
+ else:
+ raise KsftFailEx(
+ f"No dev_change notification received"
+ f" in {label} namespace")
+
+
+def _psp_dev_get_check_netkit_psp_assoc(cfg):
+ """ Check psp dev-get output with netkit interface associated with PSP dev """
+ _assoc_nk_guest(cfg)
+
+ # Check 1: In default netns, verify dev-get has correct ifindex and assoc-list
+ dev_info = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
+ ksft_eq(dev_info['ifindex'], cfg.psp_ifindex)
+ _check_assoc_list(cfg, cfg.psp_dev_id, cfg.nk_guest_ifindex,
+ cfg.psp_dev_peer_nsid)
+
+ # Check 2: In guest netns, verify dev-get has assoc-list with nk_guest device
+ with NetNSEnter(cfg.netns.name):
+ peer_pspnl = PSPFamily()
+
+ # Dump all devices in the guest namespace
+ peer_devices = peer_pspnl.dev_get({}, dump=True)
+
+ # Find the device with by-association flag
+ peer_dev = None
+ for dev in peer_devices:
+ if dev.get('by-association'):
+ peer_dev = dev
+ break
+
+ ksft_not_none(peer_dev, "No PSP device found with by-association flag in guest netns")
+
+ # Verify assoc-list contains the nk_guest device
+ ksft_true('assoc-list' in peer_dev and len(peer_dev['assoc-list']) > 0,
+ "Guest device should have assoc-list with local devices")
+
+ # Verify the assoc-list contains nk_guest ifindex with nsid=-1 (same namespace)
+ found = False
+ for assoc in peer_dev['assoc-list']:
+ if assoc['ifindex'] == cfg.nk_guest_ifindex:
+ ksft_eq(assoc['nsid'], -1,
+ "nsid should be -1 (NETNSA_NSID_NOT_ASSIGNED) for same-namespace device")
+ found = True
+ break
+ ksft_true(found, "nk_guest ifindex not found in assoc-list")
+
+
+def _dev_assoc_no_nsid(cfg):
+ """ Test dev-assoc and dev-disassoc without nsid attribute """
+ _init_psp_dev(cfg, True)
+
+ # Associate without nsid - should look up ifindex in caller's netns
+ cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id,
+ 'ifindex': cfg.nk_host_ifindex})
+ defer(_try_disassoc, cfg,
+ cfg.psp_dev_id, cfg.nk_host_ifindex)
+ defer(delattr, cfg, 'psp_dev_id')
+ defer(delattr, cfg, 'psp_info')
+
+ # Verify assoc-list contains the device (match by ifindex only)
+ _check_assoc_list(cfg, cfg.psp_dev_id, cfg.nk_host_ifindex)
+
+ # Disassociate without nsid - should also use caller's netns
+ cfg.pspnl.dev_disassoc({'id': cfg.psp_dev_id,
+ 'ifindex': cfg.nk_host_ifindex})
+
+ # Verify assoc-list no longer contains the device
+ dev_info = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
+ found = False
+ if 'assoc-list' in dev_info:
+ for assoc in dev_info['assoc-list']:
+ if assoc['ifindex'] == cfg.nk_host_ifindex:
+ found = True
+ break
+ ksft_true(not found, "Device should not be in assoc-list after disassociation")
+
+
+def _psp_dev_assoc_cleanup_on_netkit_del(cfg):
+ """Test that assoc-list is cleared when associated netkit is deleted.
+
+ Creates a disposable netkit pair for this test to avoid destroying
+ the shared environment.
+ """
+ _init_psp_dev(cfg, True)
+ defer(delattr, cfg, 'psp_dev_id')
+ defer(delattr, cfg, 'psp_info')
+
+ existing = {cfg.nk_host_ifindex, cfg.nk_guest_ifindex}
+
+ # Create a temporary netkit pair
+ tmp_host_name = "tmp_nk_host"
+ tmp_guest_name = "tmp_nk_guest"
+ rtnl = RtnlFamily()
+ rtnl.newlink(
+ {
+ "ifname": tmp_host_name,
+ "linkinfo": {
+ "kind": "netkit",
+ "data": {
+ "mode": "l2",
+ "policy": "forward",
+ "peer-policy": "forward",
+ },
+ },
+ },
+ flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL],
+ )
+ cleanup_netkit = defer(ip, f"link del {tmp_host_name}")
+
+ # Find the peer by diffing against existing netkit ifindexes
+ all_links = ip("-d link show", json=True)
+ tmp_peer = [link for link in all_links
+ if link.get('linkinfo', {}).get('info_kind') == 'netkit'
+ and link['ifindex'] not in existing
+ and link['ifname'] != tmp_host_name]
+ ksft_eq(len(tmp_peer), 1,
+ "Failed to find temporary netkit peer")
+ guest_name = tmp_peer[0]['ifname']
+
+ # Rename and move guest end into the test namespace
+ ip(f"link set dev {guest_name} name {tmp_guest_name}")
+ ip(f"link set dev {tmp_guest_name} netns {cfg.netns.name}")
+ tmp_guest_dev = ip(f"link show dev {tmp_guest_name}",
+ json=True, ns=cfg.netns)[0]
+ tmp_guest_ifindex = tmp_guest_dev['ifindex']
+ ip(f"link set dev {tmp_guest_name} up", ns=cfg.netns)
+
+ # Associate PSP device with the temporary guest interface
+ cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id,
+ 'ifindex': tmp_guest_ifindex,
+ 'nsid': cfg.psp_dev_peer_nsid})
+
+ # Verify assoc-list contains the temporary device
+ _check_assoc_list(cfg, cfg.psp_dev_id, tmp_guest_ifindex,
+ cfg.psp_dev_peer_nsid)
+
+ # Delete the temporary netkit pair (deleting one end removes both)
+ ip(f"link del {tmp_host_name}")
+ cleanup_netkit.cancel()
+
+ # Verify assoc-list is cleared after netkit deletion
+ dev_info = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
+ ksft_true('assoc-list' not in dev_info
+ or len(dev_info['assoc-list']) == 0,
+ "assoc-list should be empty after netkit deletion")
+
+
+def _try_disassoc(cfg, psp_dev_id, ifindex, nsid=None):
+ """Best-effort disassociate, ignoring errors if already removed."""
+ try:
+ params = {'id': psp_dev_id, 'ifindex': ifindex}
+ if nsid is not None:
+ params['nsid'] = nsid
+ cfg.pspnl.dev_disassoc(params)
+ except NlError:
+ pass
+
+
+def _assoc_nk_guest(cfg):
+ """Associate nk_guest with PSP device and register cleanup via defer()."""
+ _init_psp_dev(cfg, True)
+
+ cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id,
+ 'ifindex': cfg.nk_guest_ifindex,
+ 'nsid': cfg.psp_dev_peer_nsid})
+ defer(_disassoc_nk_guest, cfg,
+ cfg.psp_dev_id, cfg.nk_guest_ifindex)
+
+
+def _disassoc_nk_guest(cfg, psp_dev_id, nk_guest_ifindex):
+ """Disassociate nk_guest and reset cfg PSP state."""
+ pspnl = PSPFamily()
+ pspnl.dev_disassoc({'id': psp_dev_id, 'ifindex': nk_guest_ifindex,
+ 'nsid': cfg.psp_dev_peer_nsid})
+ cfg.pspnl = pspnl
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
+def _get_nsid(ns_name):
+ """Get the nsid for a namespace."""
+ for entry in ip("netns list-id", json=True):
+ if entry.get("name") == str(ns_name):
+ return entry["nsid"]
+ raise KsftSkipEx(f"nsid not found for namespace {ns_name}")
+
+
+def _setup_psp_attributes(cfg):
+ # pylint: disable=protected-access
+ """
+ Set up PSP-specific attributes on the environment.
+
+ This sets attributes needed for PSP tests based on whether we're using
+ netdevsim or a real NIC.
+ """
+ if cfg._ns is not None:
+ # netdevsim case: PSP device is the local dev (in host namespace)
+ cfg.psp_dev = cfg._ns.nsims[0].dev
+ cfg.psp_ifname = cfg.psp_dev['ifname']
+ cfg.psp_ifindex = cfg.psp_dev['ifindex']
+
+ # PSP peer device is the remote dev (in _netns, where psp_responder runs)
+ cfg.psp_dev_peer = cfg._ns_peer.nsims[0].dev
+ cfg.psp_dev_peer_ifname = cfg.psp_dev_peer['ifname']
+ cfg.psp_dev_peer_ifindex = cfg.psp_dev_peer['ifindex']
+ else:
+ # Real NIC case: PSP device is the local interface
+ cfg.psp_dev = cfg.dev
+ cfg.psp_ifname = cfg.ifname
+ cfg.psp_ifindex = cfg.ifindex
+
+ # PSP peer device is the remote interface
+ cfg.psp_dev_peer = cfg.remote_dev
+ cfg.psp_dev_peer_ifname = cfg.remote_ifname
+ cfg.psp_dev_peer_ifindex = cfg.remote_ifindex
+
+ # Get nsid for the guest namespace (netns) where nk_guest is
+ cfg.psp_dev_peer_nsid = _get_nsid(cfg.netns.name)
+
def main() -> None:
""" Ksft boiler plate main """
- with NetDrvEpEnv(__file__) as cfg:
+ # Make sure LOCAL_PREFIX_V6 is set
+ if "LOCAL_PREFIX_V6" not in os.environ:
+ os.environ["LOCAL_PREFIX_V6"] = "2001:db8:2::"
+
+ try:
+ env = NetDrvContEnv(__file__, primary_rx_redirect=True)
+ has_cont = True
+ except KsftSkipEx:
+ env = NetDrvEpEnv(__file__)
+ has_cont = False
+
+ with env as cfg:
cfg.pspnl = PSPFamily()
+ if has_cont:
+ _setup_psp_attributes(cfg)
+
# Set up responder and communication sock
+ # psp_responder runs in _netns (remote namespace with psp_dev_peer)
responder = cfg.remote.deploy("psp_responder")
cfg.comm_port = rand_port()
@@ -611,17 +973,18 @@ def main() -> None:
cfg.comm_port),
timeout=1)
- cases = [
- psp_ip_ver_test_builder(
- "data_basic_send", _data_basic_send, version, ipver
- )
- for version in range(0, 4)
- for ipver in ("4", "6")
- ]
- cases += [
- ipver_test_builder("data_mss_adjust", _data_mss_adjust, ipver)
- for ipver in ("4", "6")
- ]
+ cases = [data_basic_send, data_mss_adjust]
+
+ if has_cont:
+ cases += [
+ _assoc_check_list,
+ data_basic_send_netkit_psp_assoc,
+ _key_rotation_notify_multi_ns_netkit,
+ _dev_change_notify_multi_ns_netkit,
+ _psp_dev_get_check_netkit_psp_assoc,
+ _dev_assoc_no_nsid,
+ _psp_dev_assoc_cleanup_on_netkit_del,
+ ]
ksft_run(cases=cases, globs=globals(),
case_pfx={"dev_", "data_", "assoc_", "removal_"},
diff --git a/tools/testing/selftests/net/so_txtime.c b/tools/testing/selftests/drivers/net/so_txtime.c
index b76df1efc2ef..75f3beef13d9 100644
--- a/tools/testing/selftests/net/so_txtime.c
+++ b/tools/testing/selftests/drivers/net/so_txtime.c
@@ -33,9 +33,12 @@
#include <unistd.h>
#include <poll.h>
+#include "kselftest.h"
+
static int cfg_clockid = CLOCK_TAI;
static uint16_t cfg_port = 8000;
static int cfg_variance_us = 4000;
+static bool cfg_machine_slow;
static uint64_t cfg_start_time_ns;
static int cfg_mark;
static bool cfg_rx;
@@ -43,6 +46,8 @@ static bool cfg_rx;
static uint64_t glob_tstart;
static uint64_t tdeliver_max;
+static int errors;
+
/* encode one timed transmission (of a 1B payload) */
struct timed_send {
char data;
@@ -131,13 +136,15 @@ static void do_recv_one(int fdr, struct timed_send *ts)
fprintf(stderr, "payload:%c delay:%lld expected:%lld (us)\n",
rbuf[0], (long long)tstop, (long long)texpect);
- if (rbuf[0] != ts->data)
- error(1, 0, "payload mismatch. expected %c", ts->data);
+ if (rbuf[0] != ts->data) {
+ fprintf(stderr, "payload mismatch. expected %c\n", ts->data);
+ errors++;
+ }
if (llabs(tstop - texpect) > cfg_variance_us) {
fprintf(stderr, "exceeds variance (%d us)\n", cfg_variance_us);
- if (!getenv("KSFT_MACHINE_SLOW"))
- exit(1);
+ if (!cfg_machine_slow)
+ errors++;
}
}
@@ -255,8 +262,12 @@ static void start_time_wait(void)
return;
now = gettime_ns(CLOCK_REALTIME);
- if (cfg_start_time_ns < now)
+ if (cfg_start_time_ns < now) {
+ fprintf(stderr, "FAIL: start time already passed\n");
+ if (!cfg_machine_slow)
+ errors++;
return;
+ }
err = usleep((cfg_start_time_ns - now) / 1000);
if (err)
@@ -316,6 +327,9 @@ static int setup_rx(struct sockaddr *addr, socklen_t alen)
if (bind(fd, addr, alen))
error(1, errno, "bind");
+ if (cfg_machine_slow)
+ tv.tv_sec = 2;
+
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)))
error(1, errno, "setsockopt rcv timeout");
@@ -502,6 +516,8 @@ static void parse_opts(int argc, char **argv)
setup_sockaddr(domain, saddr, &cfg_src_addr);
cfg_num_pkt = parse_io(argv[optind], cfg_buf);
+
+ cfg_machine_slow = getenv("KSFT_MACHINE_SLOW");
}
int main(int argc, char **argv)
@@ -513,5 +529,10 @@ int main(int argc, char **argv)
else
do_test_tx((void *)&cfg_src_addr, cfg_alen);
- return 0;
+ if (errors) {
+ fprintf(stderr, "FAIL: %d errors\n", errors);
+ return KSFT_FAIL;
+ }
+
+ return KSFT_PASS;
}
diff --git a/tools/testing/selftests/drivers/net/so_txtime.py b/tools/testing/selftests/drivers/net/so_txtime.py
new file mode 100755
index 000000000000..adf6c848d6d8
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/so_txtime.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""Regression tests for the SO_TXTIME interface.
+
+Test delivery time in FQ and ETF qdiscs.
+"""
+
+import os
+import time
+
+from lib.py import ksft_exit, ksft_run, ksft_variants
+from lib.py import KsftNamedVariant, KsftSkipEx
+from lib.py import NetDrvEpEnv, bkg, cmd, defer, tc
+
+
+def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success):
+ """Main function. Run so_txtime as sender and receiver."""
+ slow_machine = os.environ.get('KSFT_MACHINE_SLOW')
+
+ if not hasattr(cfg, "bin_remote"):
+ cfg.bin_local = cfg.test_dir / "so_txtime"
+ cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
+
+ tstart = time.time_ns() + (2000_000_000 if slow_machine else 200_000_000)
+
+ cmd_addr = f"-S {cfg.addr_v[ipver]} -D {cfg.remote_addr_v[ipver]}"
+ cmd_args = f"-{ipver} -c {clockid} -t {tstart} {cmd_addr}"
+ cmd_rx = f"{cfg.bin_remote} {cmd_args} {args_rx} -r"
+ cmd_tx = f"{cfg.bin_local} {cmd_args} {args_tx}"
+
+ expect_fail = not expect_success
+ if slow_machine:
+ expect_success = False
+
+ with bkg(cmd_rx, host=cfg.remote, fail=expect_success,
+ expect_fail=expect_fail, exit_wait=True):
+ cmd(cmd_tx)
+
+
+def _qdisc_setup(ifname, qdisc, optargs=""):
+ """Replace root qdisc. Restore the original after the test.
+
+ If the original is mq, children will be of type default_qdisc.
+ """
+ orig = tc(f"qdisc show dev {ifname} root", json=True)[0].get("kind", None)
+ defer(tc, f"qdisc replace dev {ifname} root {orig}")
+ tc(f"qdisc replace dev {ifname} root {qdisc} {optargs}")
+
+
+def _test_variants_fq():
+ for ipver in ["4", "6"]:
+ for testcase in [
+ ["no_delay", "a,-1", "a,-1"],
+ ["zero_delay", "a,0", "a,0"],
+ ["one_pkt", "a,10", "a,10"],
+ ["in_order", "a,10,b,20", "a,10,b,20"],
+ ["reverse_order", "a,20,b,10", "b,10,a,20"],
+ ]:
+ name = f"v{ipver}_{testcase[0]}"
+ yield KsftNamedVariant(name, ipver, testcase[1], testcase[2])
+
+
+@ksft_variants(_test_variants_fq())
+def test_so_txtime_fq_mono(cfg, ipver, args_tx, args_rx):
+ """Run all variants of monotonic (fq) tests."""
+ cfg.require_ipver(ipver)
+ _qdisc_setup(cfg.ifname, "fq")
+ test_so_txtime(cfg, "mono", ipver, args_tx, args_rx, True)
+
+
+@ksft_variants(_test_variants_fq())
+def test_so_txtime_fq_tai(cfg, ipver, args_tx, args_rx):
+ """Run all variants of fq tests, but pass CLOCK_TAI to test conversion."""
+ cfg.require_ipver(ipver)
+ _qdisc_setup(cfg.ifname, "fq")
+ test_so_txtime(cfg, "tai", ipver, args_tx, args_rx, True)
+
+
+def _test_variants_etf():
+ for ipver in ["4", "6"]:
+ for testcase in [
+ ["no_delay", "a,-1", "a,-1", False],
+ ["zero_delay", "a,0", "a,0", False],
+ ["one_pkt", "a,10", "a,10", True],
+ ["in_order", "a,10,b,20", "a,10,b,20", True],
+ ["reverse_order", "a,20,b,10", "b,10,a,20", True],
+ ]:
+ name = f"v{ipver}_{testcase[0]}"
+ yield KsftNamedVariant(
+ name, ipver, testcase[1], testcase[2], testcase[3]
+ )
+
+
+@ksft_variants(_test_variants_etf())
+def test_so_txtime_etf(cfg, ipver, args_tx, args_rx, expect_fail):
+ """Run all variants of etf tests."""
+ cfg.require_ipver(ipver)
+ try:
+ _qdisc_setup(cfg.ifname, "etf", "clockid CLOCK_TAI delta 400000")
+ except Exception as e:
+ raise KsftSkipEx("tc does not support qdisc etf. skipping") from e
+
+ test_so_txtime(cfg, "tai", ipver, args_tx, args_rx, expect_fail)
+
+
+def main() -> None:
+ """Boilerplate ksft main."""
+ with NetDrvEpEnv(__file__) as cfg:
+ ksft_run(
+ [test_so_txtime_fq_mono, test_so_txtime_fq_tai, test_so_txtime_etf],
+ args=(cfg,),
+ )
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
index 97ad4d551d44..c9f46031ac73 100644
--- a/tools/testing/selftests/net/.gitignore
+++ b/tools/testing/selftests/net/.gitignore
@@ -6,6 +6,7 @@ busy_poller
cmsg_sender
epoll_busy_poll
fin_ack_lat
+getsockopt_iter
hwtstamp_config
icmp_rfc4884
io_uring_zerocopy_tx
@@ -40,7 +41,6 @@ skf_net_off
socket
so_incoming_cpu
so_netns_cookie
-so_txtime
so_rcv_listener
stress_reuseport_listen
tap
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 2ed7d803eb54..708d960ae07d 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -26,6 +26,7 @@ TEST_PROGS := \
cmsg_time.sh \
double_udp_encap.sh \
drop_monitor_tests.sh \
+ ecmp_rehash.sh \
fcnal-ipv4.sh \
fcnal-ipv6.sh \
fcnal-other.sh \
@@ -69,6 +70,7 @@ TEST_PROGS := \
nl_netdev.py \
nl_nlctrl.py \
pmtu.sh \
+ protodown.sh \
psock_snd.sh \
reuseaddr_ports_exhausted.sh \
reuseport_addr_any.sh \
@@ -83,7 +85,6 @@ TEST_PROGS := \
rxtimestamp.sh \
sctp_vrf.sh \
skf_net_off.sh \
- so_txtime.sh \
srv6_end_dt46_l3vpn_test.sh \
srv6_end_dt4_l3vpn_test.sh \
srv6_end_dt6_l3vpn_test.sh \
@@ -159,7 +160,6 @@ TEST_GEN_FILES := \
skf_net_off \
so_netns_cookie \
so_rcv_listener \
- so_txtime \
socket \
stress_reuseport_listen \
tcp_fastopen_backup_key \
@@ -178,6 +178,7 @@ TEST_GEN_PROGS := \
bind_timewait \
bind_wildcard \
epoll_busy_poll \
+ getsockopt_iter \
icmp_rfc4884 \
ipv6_fragmentation \
proc_net_pktgen \
diff --git a/tools/testing/selftests/net/bind_bhash.c b/tools/testing/selftests/net/bind_bhash.c
index da04b0b19b73..2bd100777448 100644
--- a/tools/testing/selftests/net/bind_bhash.c
+++ b/tools/testing/selftests/net/bind_bhash.c
@@ -52,18 +52,19 @@ static int bind_socket(int opt, const char *addr)
err = setsockopt(sock_fd, SOL_SOCKET, opt, &reuse, sizeof(reuse));
if (err) {
perror("setsockopt failed");
- goto cleanup;
+ goto err_free_info;
}
}
err = bind(sock_fd, res->ai_addr, res->ai_addrlen);
if (err) {
perror("failed to bind to port");
- goto cleanup;
+ goto err_free_info;
}
-
+ freeaddrinfo(res);
return sock_fd;
-
+err_free_info:
+ freeaddrinfo(res);
cleanup:
close(sock_fd);
return err;
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index 94d722770420..e1ce35c2abbe 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -117,11 +117,14 @@ CONFIG_OPENVSWITCH=m
CONFIG_OPENVSWITCH_GENEVE=m
CONFIG_OPENVSWITCH_GRE=m
CONFIG_OPENVSWITCH_VXLAN=m
+CONFIG_PAGE_POOL_STATS=y
CONFIG_PROC_SYSCTL=y
CONFIG_PSAMPLE=m
CONFIG_RPS=y
+CONFIG_SYN_COOKIES=y
CONFIG_SYSFS=y
CONFIG_TAP=m
+CONFIG_TCP_CONG_DCTCP=y
CONFIG_TCP_MD5SIG=y
CONFIG_TEST_BLACKHOLE_DEV=m
CONFIG_TEST_BPF=m
diff --git a/tools/testing/selftests/net/ecmp_rehash.sh b/tools/testing/selftests/net/ecmp_rehash.sh
new file mode 100755
index 000000000000..f05a6c8edd2a
--- /dev/null
+++ b/tools/testing/selftests/net/ecmp_rehash.sh
@@ -0,0 +1,1109 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test local ECMP path re-selection on TCP retransmission timeout and PLB.
+#
+# Two namespaces connected by two parallel veth pairs with a 2-way ECMP
+# route. When a TCP path is blocked (via tc drop) or congested (via
+# netem ECN marking), the kernel rehashes the connection via
+# sk_rethink_txhash() + __sk_dst_reset(), causing the next route lookup
+# to select the other ECMP path.
+#
+# Expected runtime: ~60 seconds. Most time is spent waiting for TCP
+# retransmission timeouts (1-7s per test) and running multi-round
+# consistency checks (10 rounds each). The large slowwait/connect-timeout
+# values (30-120s) are worst-case bounds for CI; a correctly functioning
+# kernel reaches each check well before the timeout expires.
+
+source lib.sh
+
+SUBNETS=(a b)
+PORT=9900
+: "${ECMP_REBUILD_ROUNDS:=10}"
+
+# alloc_ports NAME [COUNT]: set NAME to the next free port and reserve
+# COUNT ports (default 1) from a shared counter. Each test allocates its
+# own port(s) where it runs, so a retry or a newly added test never
+# collides; the per-round tests reserve ECMP_REBUILD_ROUNDS each.
+NEXT_PORT=$PORT
+alloc_ports()
+{
+ printf -v "$1" '%d' "$NEXT_PORT"
+ NEXT_PORT=$((NEXT_PORT + ${2:-1}))
+}
+
+ALL_TESTS="
+ test_ecmp_syn_rehash
+ test_ecmp_synack_rehash
+ test_ecmp_midstream_rehash
+ test_ecmp_midstream_ack_rehash
+ test_ecmp_plb_rehash
+ test_ecmp_hash_policy1_no_rehash
+ test_ecmp_no_flowlabel_leak
+ test_ecmp_dst_rebuild_consistency
+ test_ecmp_syncookie_path_consistency
+"
+
+link_tx_packets_get()
+{
+ local ns=$1; shift
+ local dev=$1; shift
+
+ ip netns exec "$ns" cat "/sys/class/net/$dev/statistics/tx_packets"
+}
+
+# Return the number of packets matched by the tc filter action on a device.
+# When tc drops packets via "action drop", the device's tx_packets is not
+# incremented (packet never reaches veth_xmit), but the tc action maintains
+# its own counter.
+tc_filter_pkt_count()
+{
+ local ns=$1; shift
+ local dev=$1; shift
+
+ ip netns exec "$ns" tc -s filter show dev "$dev" parent 1: 2>/dev/null |
+ awk '/Sent .* pkt/ {
+ for (i=1; i<=NF; i++)
+ if ($i == "pkt") { print $(i-1); exit }
+ }'
+}
+
+# Read a TcpExt counter from /proc/net/netstat in a namespace.
+# Returns 0 if the counter is not found.
+get_netstat_counter()
+{
+ local ns=$1; shift
+ local field=$1; shift
+ local val
+
+ # shellcheck disable=SC2016
+ val=$(ip netns exec "$ns" awk -v key="$field" '
+ /^TcpExt:/ {
+ if (!h) { split($0, n); h=1 }
+ else {
+ split($0, v)
+ for (i in n)
+ if (n[i] == key) print v[i]
+ }
+ }
+ ' /proc/net/netstat)
+ echo "${val:-0}"
+}
+
+# Apply netem ECN marking: CE-mark all ECT packets instead of dropping them.
+mark_ecn()
+{
+ local ns=$1; shift
+ local dev=$1; shift
+
+ ip netns exec "$ns" tc qdisc add dev "$dev" root netem loss 100% ecn
+}
+
+# Block TCP (IPv6 next-header = 6) egress, allowing ICMPv6 through.
+block_tcp()
+{
+ local ns=$1; shift
+ local dev=$1; shift
+
+ ip netns exec "$ns" tc qdisc add dev "$dev" root handle 1: prio
+ ip netns exec "$ns" tc filter add dev "$dev" parent 1: \
+ protocol ipv6 prio 1 u32 match u8 0x06 0xff at 6 action drop
+}
+
+unblock_tcp()
+{
+ local ns=$1; shift
+ local dev=$1; shift
+
+ ip netns exec "$ns" tc qdisc del dev "$dev" root 2>/dev/null
+}
+
+# Return success when a device's TX counter exceeds a baseline value.
+dev_tx_packets_above()
+{
+ local ns=$1; shift
+ local dev=$1; shift
+ local baseline=$1; shift
+
+ local cur
+ cur=$(link_tx_packets_get "$ns" "$dev")
+ [ "$cur" -gt "$baseline" ]
+}
+
+# Return success when both devices have dropped at least one TCP packet.
+both_devs_attempted()
+{
+ local ns=$1; shift
+ local dev0=$1; shift
+ local dev1=$1; shift
+
+ local c0 c1
+ c0=$(tc_filter_pkt_count "$ns" "$dev0")
+ c1=$(tc_filter_pkt_count "$ns" "$dev1")
+ [ "${c0:-0}" -ge 1 ] && [ "${c1:-0}" -ge 1 ]
+}
+
+link_tx_packets_total()
+{
+ local ns=$1; shift
+ local dev0=${1:-veth0a}; shift 2>/dev/null
+ local dev1=${1:-veth1a}
+
+ echo $(( $(link_tx_packets_get "$ns" "$dev0") +
+ $(link_tx_packets_get "$ns" "$dev1") ))
+}
+
+# (Re)install the ECMP multipath routes between NS1 and NS2. $1 is the
+# ip route operation ("add" to create, "change" to replace). If $2 is
+# given it names a congestion control to pin on both routes via "congctl";
+# because dctcp carries TCP_CONG_NEEDS_ECN, this also tags the route with
+# DST_FEATURE_ECN_CA, which makes the server negotiate ECN without the
+# listener itself having to run dctcp. The nexthop topology lives here
+# only, so a test can re-pin the routes and restore them with one call.
+install_ecmp_routes()
+{
+ local op=$1 cc=$2
+ local -a cc_attr=()
+
+ [ -n "$cc" ] && cc_attr=(congctl "$cc")
+
+ ip -n "$NS1" -6 route "$op" fd00:ff::2/128 "${cc_attr[@]}" \
+ nexthop via fd00:a::2 dev veth0a \
+ nexthop via fd00:b::2 dev veth1a
+
+ ip -n "$NS2" -6 route "$op" fd00:ff::1/128 "${cc_attr[@]}" \
+ nexthop via fd00:a::1 dev veth0b \
+ nexthop via fd00:b::1 dev veth1b
+}
+
+setup()
+{
+ setup_ns NS1 NS2
+
+ local ns
+ for ns in "$NS1" "$NS2"; do
+ ip netns exec "$ns" sysctl -qw net.ipv6.conf.all.accept_dad=0
+ ip netns exec "$ns" sysctl -qw net.ipv6.conf.default.accept_dad=0
+ ip netns exec "$ns" sysctl -qw net.ipv6.conf.all.forwarding=1
+ ip netns exec "$ns" sysctl -qw net.core.txrehash=1
+ done
+
+ local i sub
+ for i in 0 1; do
+ sub=${SUBNETS[$i]}
+ ip link add "veth${i}a" type veth peer name "veth${i}b"
+ ip link set "veth${i}a" netns "$NS1"
+ ip link set "veth${i}b" netns "$NS2"
+ ip -n "$NS1" addr add "fd00:${sub}::1/64" dev "veth${i}a"
+ ip -n "$NS2" addr add "fd00:${sub}::2/64" dev "veth${i}b"
+ ip -n "$NS1" link set "veth${i}a" up
+ ip -n "$NS2" link set "veth${i}b" up
+ done
+
+ ip -n "$NS1" addr add fd00:ff::1/128 dev lo
+ ip -n "$NS2" addr add fd00:ff::2/128 dev lo
+
+ # Allow many SYN retries at 1-second intervals (linear, no
+ # exponential backoff) so the rehash test has enough attempts
+ # to exercise both ECMP paths.
+ if ! ip netns exec "$NS1" sysctl -qw \
+ net.ipv4.tcp_syn_linear_timeouts=25; then
+ echo "SKIP: tcp_syn_linear_timeouts not supported"
+ return "$ksft_skip"
+ fi
+ ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_syn_retries=25
+
+ # Keep the server's request socket alive during the blocking
+ # period so SYN/ACK retransmits continue.
+ ip netns exec "$NS2" sysctl -qw net.ipv4.tcp_synack_retries=25
+
+ install_ecmp_routes add
+
+ for i in 0 1; do
+ sub=${SUBNETS[$i]}
+ ip netns exec "$NS1" \
+ ping -6 -c1 -W5 "fd00:${sub}::2" &>/dev/null
+ ip netns exec "$NS2" \
+ ping -6 -c1 -W5 "fd00:${sub}::1" &>/dev/null
+ done
+
+ if ! ip netns exec "$NS1" ping -6 -c1 -W5 fd00:ff::2 &>/dev/null; then
+ echo "Basic connectivity check failed"
+ return "$ksft_skip"
+ fi
+}
+
+# Block ALL paths, start a connection, wait until SYNs have been dropped
+# on both interfaces (proving rehash steered the SYN to a new path), then
+# unblock so the connection completes.
+test_ecmp_syn_rehash()
+{
+ RET=0
+ local port
+ alloc_ports port
+
+ block_tcp "$NS1" veth0a
+ defer unblock_tcp "$NS1" veth0a
+ block_tcp "$NS1" veth1a
+ defer unblock_tcp "$NS1" veth1a
+
+ ip netns exec "$NS2" socat \
+ "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr,fork" \
+ EXEC:"echo ESTABLISH_OK" &
+ defer kill_process $!
+
+ wait_local_port_listen "$NS2" "$port" tcp
+
+ local rehash_before
+ rehash_before=$(get_netstat_counter "$NS1" TcpTimeoutRehash)
+
+ # Start the connection in the background; it will retry SYNs at
+ # 1-second intervals until an unblocked path is found.
+ # Use -u (unidirectional) to only receive from the server;
+ # sending data back would risk SIGPIPE if the server's EXEC
+ # child has already exited.
+ local tmpfile
+ tmpfile=$(mktemp)
+ defer rm -f "$tmpfile"
+
+ ip netns exec "$NS1" socat -u \
+ "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],connect-timeout=60" \
+ STDOUT >"$tmpfile" 2>&1 &
+ local client_pid=$!
+ defer kill_process "$client_pid"
+
+ # Wait until both paths have seen at least one dropped SYN.
+ # This proves sk_rethink_txhash() rehashed the connection from
+ # one ECMP path to the other.
+ slowwait 30 both_devs_attempted "$NS1" veth0a veth1a > /dev/null
+ check_err $? "SYNs did not appear on both paths (rehash not working)"
+ if [ "$RET" -ne 0 ]; then
+ log_test "Local ECMP SYN rehash: establish with blocked paths"
+ return
+ fi
+
+ # Unblock both paths and let the next SYN retransmit succeed.
+ unblock_tcp "$NS1" veth0a
+ unblock_tcp "$NS1" veth1a
+
+ local rc=0
+ wait "$client_pid" || rc=$?
+
+ local result
+ result=$(cat "$tmpfile" 2>/dev/null)
+
+ if [[ "$result" != *"ESTABLISH_OK"* ]]; then
+ check_err 1 "connection failed after unblocking (rc=$rc): $result"
+ fi
+
+ local rehash_after
+ rehash_after=$(get_netstat_counter "$NS1" TcpTimeoutRehash)
+ if [ "$rehash_after" -le "$rehash_before" ]; then
+ check_err 1 "TcpTimeoutRehash counter did not increment"
+ fi
+
+ log_test "Local ECMP SYN rehash: establish with blocked paths"
+}
+
+# Block the server's return paths so SYN/ACKs are dropped. The client
+# retransmits SYNs at 1-second intervals; each duplicate SYN arriving at
+# the server triggers tcp_rtx_synack() which re-rolls txhash, so the
+# retransmitted SYN/ACK selects a different ECMP return path.
+test_ecmp_synack_rehash()
+{
+ RET=0
+ local port
+ alloc_ports port
+
+ block_tcp "$NS2" veth0b
+ defer unblock_tcp "$NS2" veth0b
+ block_tcp "$NS2" veth1b
+ defer unblock_tcp "$NS2" veth1b
+
+ ip netns exec "$NS2" socat \
+ "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr,fork" \
+ EXEC:"echo SYNACK_OK" &
+ defer kill_process $!
+
+ wait_local_port_listen "$NS2" "$port" tcp
+
+ # Start the connection; SYNs reach the server (client egress is
+ # open) but SYN/ACKs are dropped on the server's return path.
+ local tmpfile
+ tmpfile=$(mktemp)
+ defer rm -f "$tmpfile"
+
+ ip netns exec "$NS1" socat -u \
+ "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],connect-timeout=60" \
+ STDOUT >"$tmpfile" 2>&1 &
+ local client_pid=$!
+ defer kill_process "$client_pid"
+
+ # Wait until both server-side interfaces have dropped at least
+ # one SYN/ACK, proving the server rehashed its return path.
+ slowwait 30 both_devs_attempted "$NS2" veth0b veth1b > /dev/null
+ check_err $? "SYN/ACKs did not appear on both return paths"
+ if [ "$RET" -ne 0 ]; then
+ log_test "Local ECMP SYN/ACK rehash: blocked return path"
+ return
+ fi
+
+ # Unblock and let the connection complete.
+ unblock_tcp "$NS2" veth0b
+ unblock_tcp "$NS2" veth1b
+
+ local rc=0
+ wait "$client_pid" || rc=$?
+
+ local result
+ result=$(cat "$tmpfile" 2>/dev/null)
+
+ if [[ "$result" != *"SYNACK_OK"* ]]; then
+ check_err 1 "connection failed after unblocking (rc=$rc): $result"
+ fi
+
+ log_test "Local ECMP SYN/ACK rehash: blocked return path"
+}
+
+# Establish a data transfer with both paths open, then block the
+# active path. Verify that data appears on the previously inactive
+# path (proving RTO triggered a rehash) and that TcpTimeoutRehash
+# incremented.
+#
+# With 2-way ECMP each rehash may pick the same path, so a single
+# attempt can occasionally fail. Retry once for robustness.
+
+# Single attempt at the midstream rehash check. Returns 0 on success.
+ecmp_midstream_rehash_attempt()
+{
+ local port=$1; shift
+ local reason=""
+
+ ip netns exec "$NS2" socat -u \
+ "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" - >/dev/null &
+ local server_pid=$!
+
+ wait_local_port_listen "$NS2" "$port" tcp
+
+ local base_tx0 base_tx1
+ base_tx0=$(link_tx_packets_get "$NS1" veth0a)
+ base_tx1=$(link_tx_packets_get "$NS1" veth1a)
+
+ # Continuous data source; timeout caps overall test duration and
+ # must exceed the slowwait below so data keeps flowing.
+ ip netns exec "$NS1" timeout 90 socat -u \
+ OPEN:/dev/zero \
+ "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1]" &>/dev/null &
+ local client_pid=$!
+
+ # Wait for enough packets to identify the active path.
+ if ! busywait "$BUSYWAIT_TIMEOUT" until_counter_is \
+ ">= $((base_tx0 + base_tx1 + 10))" \
+ link_tx_packets_total "$NS1" > /dev/null; then
+ kill "$client_pid" "$server_pid" 2>/dev/null
+ wait "$client_pid" "$server_pid" 2>/dev/null
+ echo "no TX activity"
+ return 1
+ fi
+
+ # Find the active path and block it.
+ local current_tx0 current_tx1 active_idx inactive_idx
+ current_tx0=$(link_tx_packets_get "$NS1" veth0a)
+ current_tx1=$(link_tx_packets_get "$NS1" veth1a)
+ if [ $((current_tx0 - base_tx0)) -ge $((current_tx1 - base_tx1)) ]; then
+ active_idx=0; inactive_idx=1
+ else
+ active_idx=1; inactive_idx=0
+ fi
+
+ local rehash_before
+ rehash_before=$(get_netstat_counter "$NS1" TcpTimeoutRehash)
+ # Suppress __dst_negative_advice() in tcp_write_timeout() so
+ # that __sk_dst_reset() is the only dst-invalidation mechanism
+ # on the RTO path.
+ local saved_retries1
+ saved_retries1=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_retries1)
+ ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_retries1=255
+
+ block_tcp "$NS1" "veth${active_idx}a"
+
+ # Capture baseline after block_tcp returns. block_tcp adds a
+ # prio qdisc then a tc filter; between those two steps the
+ # qdisc's CAN_BYPASS fast-path lets packets through unfiltered.
+ local inactive_before
+ inactive_before=$(link_tx_packets_get "$NS1" "veth${inactive_idx}a")
+
+ # Wait for meaningful data on the previously inactive path,
+ # proving RTO triggered a rehash and data actually moved.
+ if ! slowwait 60 dev_tx_packets_above \
+ "$NS1" "veth${inactive_idx}a" "$((inactive_before + 100))" \
+ > /dev/null; then
+ reason="no data on alternate path"
+ fi
+
+ local rehash_after
+ rehash_after=$(get_netstat_counter "$NS1" TcpTimeoutRehash)
+ if [ "$rehash_after" -le "$rehash_before" ]; then
+ reason="${reason:+$reason; }TcpTimeoutRehash did not increment"
+ fi
+
+ unblock_tcp "$NS1" "veth${active_idx}a"
+ ip netns exec "$NS1" sysctl -qw \
+ net.ipv4.tcp_retries1="$saved_retries1"
+ kill "$client_pid" "$server_pid" 2>/dev/null
+ wait "$client_pid" "$server_pid" 2>/dev/null
+ if [ -n "$reason" ]; then
+ echo "$reason"
+ return 1
+ fi
+ return 0
+}
+
+test_ecmp_midstream_rehash()
+{
+ RET=0
+ local port retry_port
+ alloc_ports port
+ alloc_ports retry_port
+
+ local fail_reason
+ if ! ecmp_midstream_rehash_attempt "$port" >/dev/null; then
+ fail_reason=$(ecmp_midstream_rehash_attempt "$retry_port")
+ check_err $? "$fail_reason"
+ fi
+
+ log_test "Local ECMP midstream rehash: block active path"
+}
+
+# Single attempt at the ACK rehash check. Returns 0 on success.
+ecmp_ack_rehash_attempt()
+{
+ local port=$1; shift
+ local reason=""
+
+ ip netns exec "$NS2" socat -u \
+ "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" - >/dev/null &
+ local server_pid=$!
+
+ wait_local_port_listen "$NS2" "$port" tcp
+
+ local base_tx0 base_tx1
+ base_tx0=$(link_tx_packets_get "$NS2" veth0b)
+ base_tx1=$(link_tx_packets_get "$NS2" veth1b)
+
+ # Continuous data source from NS1 to NS2. Cap the send buffer
+ # so in-flight data stays below the receiver's advertised window.
+ # Without this, the sender can exhaust the receiver's window and
+ # enter persist mode (zero-window probing) instead of RTO when
+ # ACKs are blocked, and persist probes do not trigger flowlabel
+ # rehash.
+ ip netns exec "$NS1" timeout 120 socat -u \
+ OPEN:/dev/zero \
+ "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],sndbuf=16384" \
+ &>/dev/null &
+ local client_pid=$!
+
+ # Wait for enough server TX (ACKs) to identify the active return path.
+ if ! busywait "$BUSYWAIT_TIMEOUT" until_counter_is \
+ ">= $((base_tx0 + base_tx1 + 10))" \
+ link_tx_packets_total "$NS2" veth0b veth1b > /dev/null; then
+ kill "$client_pid" "$server_pid" 2>/dev/null
+ wait "$client_pid" "$server_pid" 2>/dev/null
+ echo "no server TX activity"
+ return 1
+ fi
+
+ local cur_tx0 cur_tx1 active_dev inactive_dev
+ cur_tx0=$(link_tx_packets_get "$NS2" veth0b)
+ cur_tx1=$(link_tx_packets_get "$NS2" veth1b)
+ if [ $((cur_tx0 - base_tx0)) -ge $((cur_tx1 - base_tx1)) ]; then
+ active_dev=veth0b; inactive_dev=veth1b
+ else
+ active_dev=veth1b; inactive_dev=veth0b
+ fi
+
+ local rehash_before
+ rehash_before=$(get_netstat_counter "$NS2" TcpDuplicateDataRehash)
+
+ # Block the inactive return path first (no effect on current
+ # ACK flow), then block the active path. This avoids counting
+ # normal ACK drops as rehash evidence.
+ block_tcp "$NS2" "$inactive_dev"
+ local inactive_before
+ inactive_before=$(tc_filter_pkt_count "$NS2" "$inactive_dev")
+ block_tcp "$NS2" "$active_dev"
+
+ # NS1 will RTO (no ACKs), retransmit with new flowlabel.
+ # NS2 detects the flowlabel change via tcp_rcv_spurious_retrans(),
+ # rehashes, and NS2's ACKs try the previously inactive return
+ # path. One successful rehash is sufficient.
+ if ! slowwait 60 until_counter_is \
+ ">= $((${inactive_before:-0} + 1))" \
+ tc_filter_pkt_count "$NS2" "$inactive_dev" > /dev/null; then
+ reason="no ACKs on alternate return path after blocking"
+ fi
+
+ local rehash_after
+ rehash_after=$(get_netstat_counter "$NS2" TcpDuplicateDataRehash)
+ if [ "$rehash_after" -le "$rehash_before" ]; then
+ reason="${reason:+$reason; }TcpDuplicateDataRehash did not increment"
+ fi
+
+ unblock_tcp "$NS2" "$active_dev"
+ unblock_tcp "$NS2" "$inactive_dev"
+ kill "$client_pid" "$server_pid" 2>/dev/null
+ wait "$client_pid" "$server_pid" 2>/dev/null
+ if [ -n "$reason" ]; then
+ echo "$reason"
+ return 1
+ fi
+ return 0
+}
+
+# Block the receiver's (NS2) ACK return paths while data flows from
+# NS1 to NS2. The sender (NS1) times out and retransmits with a new
+# flowlabel; the receiver detects the changed flowlabel via
+# tcp_rcv_spurious_retrans() and rehashes its own txhash so that its
+# ACKs try a different ECMP return path.
+#
+# With 2-way ECMP each rehash may pick the same path, so a single
+# attempt can occasionally fail. Retry once for robustness.
+test_ecmp_midstream_ack_rehash()
+{
+ RET=0
+ local port retry_port
+ alloc_ports port
+ alloc_ports retry_port
+
+ local fail_reason
+ if ! ecmp_ack_rehash_attempt "$port" >/dev/null; then
+ fail_reason=$(ecmp_ack_rehash_attempt "$retry_port")
+ check_err $? "$fail_reason"
+ fi
+
+ log_test "Local ECMP midstream ACK rehash: blocked return path"
+}
+
+# Establish a DCTCP data transfer with PLB enabled, then ECN-mark both
+# paths. Sustained CE marking triggers PLB to call sk_rethink_txhash()
+# + __sk_dst_reset(), bouncing the connection between ECMP paths.
+# Verify data appears on both paths and that TCPPLBRehash incremented.
+test_ecmp_plb_rehash()
+{
+ RET=0
+ local port
+ alloc_ports port
+
+ # PLB needs DCTCP, a restricted congestion control. Adding it to
+ # the host-global tcp_allowed_congestion_control would relax the
+ # restricted-CC policy for the whole host (there is no per-netns
+ # allowed set). Instead pin dctcp on the test routes with
+ # "congctl": the route's RTAX_CC_ALGO is honoured on both the
+ # connect and accept paths without the restricted-CC check, and a
+ # dctcp route also carries DST_FEATURE_ECN_CA so the server
+ # negotiates ECN -- all confined to the test namespaces.
+ local available
+ available=$(ip netns exec "$NS1" sysctl -n \
+ net.ipv4.tcp_available_congestion_control)
+ if ! echo "$available" | grep -qw dctcp; then
+ log_test_skip "Local ECMP PLB rehash: DCTCP not available"
+ return "$ksft_skip"
+ fi
+ install_ecmp_routes change dctcp
+ defer install_ecmp_routes change
+
+ # Save NS1 sysctls before modifying them.
+ local saved_ecn1 saved_plb_enabled saved_plb_rounds
+ local saved_plb_thresh saved_plb_suspend
+ saved_ecn1=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_ecn)
+ saved_plb_enabled=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_plb_enabled)
+ saved_plb_rounds=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_plb_rehash_rounds)
+ saved_plb_thresh=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_plb_cong_thresh)
+ saved_plb_suspend=$(ip netns exec "$NS1" sysctl -n net.ipv4.tcp_plb_suspend_rto_sec)
+
+ # Enable ECN and PLB on the sender; dctcp comes from the route.
+ ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_ecn=1
+ ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_enabled=1
+ ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_rehash_rounds=3
+ ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_cong_thresh=1
+ ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_suspend_rto_sec=0
+ defer ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_ecn="$saved_ecn1"
+ defer ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_enabled="$saved_plb_enabled"
+ defer ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_rehash_rounds="$saved_plb_rounds"
+ defer ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_cong_thresh="$saved_plb_thresh"
+ defer ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_plb_suspend_rto_sec="$saved_plb_suspend"
+
+ ip netns exec "$NS2" socat -u \
+ "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" - >/dev/null &
+ defer kill_process $!
+
+ wait_local_port_listen "$NS2" "$port" tcp
+
+ local base_tx0 base_tx1
+ base_tx0=$(link_tx_packets_get "$NS1" veth0a)
+ base_tx1=$(link_tx_packets_get "$NS1" veth1a)
+
+ ip netns exec "$NS1" timeout 90 socat -u \
+ OPEN:/dev/zero \
+ "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1]" &>/dev/null &
+ local client_pid=$!
+ defer kill_process "$client_pid"
+
+ # Wait for data to start flowing before applying ECN marking.
+ busywait "$BUSYWAIT_TIMEOUT" until_counter_is \
+ ">= $((base_tx0 + base_tx1 + 10))" \
+ link_tx_packets_total "$NS1" > /dev/null
+ check_err $? "no TX activity detected"
+ if [ "$RET" -ne 0 ]; then
+ log_test "Local ECMP PLB rehash: ECN-marked path"
+ return
+ fi
+
+ # Snapshot TX counters and rehash stats before ECN marking.
+ local pre_ecn_tx0 pre_ecn_tx1
+ pre_ecn_tx0=$(link_tx_packets_get "$NS1" veth0a)
+ pre_ecn_tx1=$(link_tx_packets_get "$NS1" veth1a)
+
+ local plb_before rto_before
+ plb_before=$(get_netstat_counter "$NS1" TCPPLBRehash)
+ rto_before=$(get_netstat_counter "$NS1" TcpTimeoutRehash)
+
+ # CE-mark all data on both paths. PLB detects sustained
+ # congestion and rehashes, bouncing traffic between paths.
+ mark_ecn "$NS1" veth0a
+ defer unblock_tcp "$NS1" veth0a # removes the marking rule
+ mark_ecn "$NS1" veth1a
+ defer unblock_tcp "$NS1" veth1a # removes the marking rule
+
+ # Wait for meaningful data on both paths, proving PLB rehashed
+ # the connection and traffic actually moved. Require at least
+ # 100 packets beyond the baseline to rule out stray control
+ # packets (ND, etc.) satisfying the check.
+ slowwait 60 dev_tx_packets_above \
+ "$NS1" veth0a "$((pre_ecn_tx0 + 100))" > /dev/null
+ check_err $? "no data on veth0a after ECN marking"
+
+ slowwait 60 dev_tx_packets_above \
+ "$NS1" veth1a "$((pre_ecn_tx1 + 100))" > /dev/null
+ check_err $? "no data on veth1a after ECN marking"
+
+ local plb_after rto_after
+ plb_after=$(get_netstat_counter "$NS1" TCPPLBRehash)
+ rto_after=$(get_netstat_counter "$NS1" TcpTimeoutRehash)
+ if [ "$plb_after" -le "$plb_before" ]; then
+ check_err 1 "TCPPLBRehash counter did not increment"
+ fi
+ if [ "$rto_after" -gt "$rto_before" ]; then
+ check_err 1 "TcpTimeoutRehash incremented; rehash was RTO-driven, not PLB"
+ fi
+
+ log_test "Local ECMP PLB rehash: ECN-marked path"
+}
+
+# Verify that hash policy 1 (L3+L4 symmetric) preserves the ECMP path
+# across rehash. Policy 1 computes a deterministic hash from the
+# 5-tuple, so mp_hash stays 0 and rt6_multipath_hash() always selects
+# the same path regardless of txhash changes.
+test_ecmp_hash_policy1_no_rehash()
+{
+ RET=0
+ local port
+ alloc_ports port
+
+ local saved_policy
+ saved_policy=$(ip netns exec "$NS1" sysctl -n \
+ net.ipv6.fib_multipath_hash_policy)
+ ip netns exec "$NS1" sysctl -qw net.ipv6.fib_multipath_hash_policy=1
+ defer ip netns exec "$NS1" sysctl -qw \
+ net.ipv6.fib_multipath_hash_policy="$saved_policy"
+
+ block_tcp "$NS1" veth0a
+ defer unblock_tcp "$NS1" veth0a
+ block_tcp "$NS1" veth1a
+ defer unblock_tcp "$NS1" veth1a
+
+ ip netns exec "$NS2" socat \
+ "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr,fork" \
+ EXEC:"echo POLICY1_OK" &
+ defer kill_process $!
+
+ wait_local_port_listen "$NS2" "$port" tcp
+
+ local rehash_before
+ rehash_before=$(get_netstat_counter "$NS1" TcpTimeoutRehash)
+
+ ip netns exec "$NS1" timeout 10 socat -u \
+ "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],connect-timeout=8" \
+ STDOUT >/dev/null 2>&1 &
+ local client_pid=$!
+ defer kill_process "$client_pid"
+
+ # With policy 1, the deterministic 5-tuple hash always selects
+ # the same path. Wait for multiple SYN retransmits (proving
+ # rehash was attempted), then verify all SYNs landed on the
+ # same interface.
+ local rehash_after
+ slowwait 8 until_counter_is ">= $((rehash_before + 3))" \
+ get_netstat_counter "$NS1" TcpTimeoutRehash > /dev/null
+ rehash_after=$(get_netstat_counter "$NS1" TcpTimeoutRehash)
+ if [ "$rehash_after" -le "$rehash_before" ]; then
+ check_err 1 "TcpTimeoutRehash counter did not increment"
+ fi
+
+ local c0 c1
+ c0=$(tc_filter_pkt_count "$NS1" veth0a)
+ c1=$(tc_filter_pkt_count "$NS1" veth1a)
+ if [ "${c0:-0}" -ge 1 ] && [ "${c1:-0}" -ge 1 ]; then
+ check_err 1 "SYNs appeared on both paths despite policy 1"
+ fi
+ if [ "${c0:-0}" -eq 0 ] && [ "${c1:-0}" -eq 0 ]; then
+ check_err 1 "no SYNs observed on either path"
+ fi
+
+ log_test "Local ECMP policy 1: no path change on rehash"
+}
+
+# Verify that mp_hash does not leak into the on-wire flowlabel.
+# With auto_flowlabels=0, the wire flowlabel must be 0. Install tc
+# filters that pass TCP with flowlabel=0 but drop TCP with nonzero
+# flowlabel, then establish a connection and transfer data. If
+# mp_hash leaked into fl6->flowlabel, the SYN or data packets would
+# be dropped and the connection would fail.
+test_ecmp_no_flowlabel_leak()
+{
+ RET=0
+ local port
+ alloc_ports port
+
+ local saved_afl
+ saved_afl=$(ip netns exec "$NS1" sysctl -n \
+ net.ipv6.auto_flowlabels)
+ ip netns exec "$NS1" sysctl -qw net.ipv6.auto_flowlabels=0
+ defer ip netns exec "$NS1" sysctl -qw \
+ net.ipv6.auto_flowlabels="$saved_afl"
+
+ # On both egress interfaces: pass TCP with flowlabel=0 (prio 1),
+ # drop any remaining TCP (nonzero flowlabel, prio 2). ICMPv6
+ # matches neither filter and passes through normally.
+ local dev
+ for dev in veth0a veth1a; do
+ ip netns exec "$NS1" tc qdisc add dev "$dev" \
+ root handle 1: prio
+ ip netns exec "$NS1" tc filter add dev "$dev" parent 1: \
+ protocol ipv6 prio 1 u32 \
+ match u32 0x00000000 0x000FFFFF at 0 \
+ match u8 0x06 0xff at 6 \
+ action ok
+ ip netns exec "$NS1" tc filter add dev "$dev" parent 1: \
+ protocol ipv6 prio 2 u32 \
+ match u8 0x06 0xff at 6 \
+ action drop
+ defer unblock_tcp "$NS1" "$dev"
+ done
+
+ ip netns exec "$NS2" socat \
+ "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" \
+ EXEC:"echo FLOWLABEL_OK" &
+ defer kill_process $!
+
+ wait_local_port_listen "$NS2" "$port" tcp
+
+ local tmpfile
+ tmpfile=$(mktemp)
+ defer rm -f "$tmpfile"
+
+ ip netns exec "$NS1" socat -u \
+ "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],connect-timeout=10" \
+ STDOUT >"$tmpfile" 2>&1
+
+ local result
+ result=$(cat "$tmpfile" 2>/dev/null)
+ if [[ "$result" != *"FLOWLABEL_OK"* ]]; then
+ check_err 1 "connection failed: mp_hash may have leaked into wire flowlabel"
+ fi
+
+ log_test "No flowlabel leak with auto_flowlabels=0"
+}
+
+# Helper: stream data, invalidate the cached dst by adding and
+# removing a dummy route (bumps fib6_node sernum), then check that
+# traffic stays on the same ECMP path. Used by both the normal
+# tcp_v6_connect and syncookie variants.
+ecmp_dst_rebuild_check()
+{
+ local ns_client=$1; shift
+ local port=$1; shift
+ local rc=0
+
+ # Suppress __dst_negative_advice() during the test so that a
+ # real TCP timeout cannot trigger an additional dst
+ # invalidation via a different code path.
+ local saved_retries1
+ saved_retries1=$(ip netns exec "$ns_client" sysctl -n \
+ net.ipv4.tcp_retries1)
+ ip netns exec "$ns_client" sysctl -qw net.ipv4.tcp_retries1=255
+
+ local base0 base1
+ base0=$(link_tx_packets_get "$ns_client" veth0a)
+ base1=$(link_tx_packets_get "$ns_client" veth1a)
+
+ ip netns exec "$ns_client" timeout 15 socat -u \
+ OPEN:/dev/zero \
+ "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1]" \
+ &>/dev/null &
+ local client_pid=$!
+
+ # Wait for enough packets to identify the active path.
+ # Return 2 for setup failure (distinct from 1 = path changed).
+ if ! busywait "$BUSYWAIT_TIMEOUT" until_counter_is \
+ ">= $((base0 + base1 + 50))" \
+ link_tx_packets_total "$ns_client" > /dev/null; then
+ ip netns exec "$ns_client" sysctl -qw \
+ net.ipv4.tcp_retries1="$saved_retries1"
+ kill "$client_pid" 2>/dev/null
+ wait "$client_pid" 2>/dev/null
+ return 2
+ fi
+
+ local mid0 mid1 active_dev inactive_dev
+ mid0=$(link_tx_packets_get "$ns_client" veth0a)
+ mid1=$(link_tx_packets_get "$ns_client" veth1a)
+ if [ $((mid0 - base0)) -ge $((mid1 - base1)) ]; then
+ active_dev=veth0a; inactive_dev=veth1a
+ else
+ active_dev=veth1a; inactive_dev=veth0a
+ fi
+
+ local active_before inactive_before
+ active_before=$(link_tx_packets_get "$ns_client" "$active_dev")
+ inactive_before=$(link_tx_packets_get "$ns_client" "$inactive_dev")
+
+ # Invalidate the cached dst by bumping the fib6_node sernum.
+ # Adding and removing a high-metric dummy route achieves this
+ # without touching the ECMP nexthops, avoiding a transient
+ # single-nexthop state during multipath route replace.
+ ip -n "$ns_client" -6 route add fd00:ff::2/128 dev lo metric 9999
+ ip -n "$ns_client" -6 route del fd00:ff::2/128 dev lo metric 9999
+
+ # Wait for enough post-rebuild traffic to detect a path change.
+ if ! busywait "$BUSYWAIT_TIMEOUT" until_counter_is \
+ ">= $((active_before + inactive_before + 50))" \
+ link_tx_packets_total "$ns_client" > /dev/null; then
+ ip netns exec "$ns_client" sysctl -qw \
+ net.ipv4.tcp_retries1="$saved_retries1"
+ kill "$client_pid" 2>/dev/null
+ wait "$client_pid" 2>/dev/null
+ return 2
+ fi
+
+ local active_after inactive_after
+ active_after=$(link_tx_packets_get "$ns_client" "$active_dev")
+ inactive_after=$(link_tx_packets_get "$ns_client" "$inactive_dev")
+
+ local active_delta=$((active_after - active_before))
+ local inactive_delta=$((inactive_after - inactive_before))
+
+ if [ "$inactive_delta" -gt "$active_delta" ]; then
+ rc=1
+ fi
+
+ ip netns exec "$ns_client" sysctl -qw \
+ net.ipv4.tcp_retries1="$saved_retries1"
+ kill "$client_pid" 2>/dev/null
+ wait "$client_pid" 2>/dev/null
+ return "$rc"
+}
+
+# Run ecmp_dst_rebuild_check for ECMP_REBUILD_ROUNDS rounds, each with
+# a fresh server and connection. With a correct kernel the path is
+# deterministic (same txhash always selects the same ECMP nexthop),
+# so any path change is a bug. Multiple rounds catch a buggy kernel
+# that picks a random path: each round has 50% chance of accidentally
+# matching, so 10 rounds gives < 0.1% false-pass probability.
+ecmp_dst_rebuild_loop()
+{
+ local base_port=$1; shift
+ local label=$1; shift
+ local path_changes=0
+ local r
+
+ for r in $(seq 1 "$ECMP_REBUILD_ROUNDS"); do
+ local port=$((base_port + r - 1))
+
+ ip netns exec "$NS2" socat -u \
+ "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" \
+ - >/dev/null &
+ local server_pid=$!
+
+ wait_local_port_listen "$NS2" "$port" tcp
+
+ local check_rc=0
+ ecmp_dst_rebuild_check "$NS1" "$port" || check_rc=$?
+
+ kill "$server_pid" 2>/dev/null
+ wait "$server_pid" 2>/dev/null
+
+ busywait "$BUSYWAIT_TIMEOUT" \
+ port_has_no_active_tcp "$NS1" "$port" > /dev/null
+ busywait "$BUSYWAIT_TIMEOUT" \
+ port_has_no_active_tcp "$NS2" "$port" > /dev/null
+
+ if [ "$check_rc" -eq 2 ]; then
+ check_err 1 "no TX activity in round $r"
+ break
+ elif [ "$check_rc" -eq 1 ]; then
+ path_changes=$((path_changes + 1))
+ fi
+ done
+
+ if [ "$path_changes" -gt 0 ]; then
+ check_err 1 "$path_changes/$ECMP_REBUILD_ROUNDS changed path"
+ fi
+
+ log_test "$label"
+}
+
+# Verify that a dst invalidation does not cause the connection to
+# switch ECMP paths. With the fix, both the initial route lookup
+# (tcp_v6_connect) and subsequent rebuilds (inet6_csk_route_socket)
+# use sk_txhash >> 1, so the path is stable.
+test_ecmp_dst_rebuild_consistency()
+{
+ RET=0
+ local base_port
+ alloc_ports base_port "$ECMP_REBUILD_ROUNDS"
+
+ ecmp_dst_rebuild_loop "$base_port" \
+ "ECMP path stable after dst invalidation"
+}
+
+# Return 0 (true) when no active TCP sockets remain on a port.
+# TIME_WAIT is excluded because it does not generate outgoing traffic.
+port_has_no_active_tcp()
+{
+ local ns=$1; shift
+ local port=$1; shift
+
+ ! ip netns exec "$ns" ss -tnH \
+ state established \
+ state fin-wait-1 \
+ state fin-wait-2 \
+ state close-wait \
+ state last-ack \
+ state closing \
+ state syn-sent \
+ state syn-recv \
+ "sport = :$port or dport = :$port" | grep -q .
+}
+
+# Count TCP packets on server egress without blocking them.
+# Uses tc filters with "action ok" so packets are counted and passed.
+count_tcp()
+{
+ local ns=$1; shift
+ local dev=$1; shift
+
+ ip netns exec "$ns" tc qdisc add dev "$dev" root handle 1: prio
+ ip netns exec "$ns" tc filter add dev "$dev" parent 1: \
+ protocol ipv6 prio 1 u32 match u8 0x06 0xff at 6 action ok
+}
+
+# Verify that the server's SYN-ACK (sent from the request socket) and
+# subsequent ACKs (sent from the full socket created in cookie_v6_check)
+# use the same ECMP path. With syncookies the request socket is freed
+# after the SYN-ACK and a new one is created during cookie validation;
+# this test catches the case where the two request sockets pick
+# different ECMP paths due to independent txhash values.
+test_ecmp_syncookie_path_consistency()
+{
+ RET=0
+
+ local saved_syncookies
+ saved_syncookies=$(ip netns exec "$NS2" sysctl -n \
+ net.ipv4.tcp_syncookies 2>/dev/null)
+ if [ -z "$saved_syncookies" ]; then
+ log_test_skip "Syncookie server ECMP path consistent"
+ return "$ksft_skip"
+ fi
+ ip netns exec "$NS2" sysctl -qw net.ipv4.tcp_syncookies=2
+ defer ip netns exec "$NS2" sysctl -qw \
+ net.ipv4.tcp_syncookies="$saved_syncookies"
+
+ count_tcp "$NS2" veth0b
+ defer unblock_tcp "$NS2" veth0b
+ count_tcp "$NS2" veth1b
+ defer unblock_tcp "$NS2" veth1b
+
+ local path_splits=0
+ local r base_port
+ alloc_ports base_port "$ECMP_REBUILD_ROUNDS"
+
+ for r in $(seq 1 "$ECMP_REBUILD_ROUNDS"); do
+ local port=$((base_port + r - 1))
+
+ ip netns exec "$NS2" socat -u \
+ "TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" \
+ - >/dev/null &
+ local server_pid=$!
+
+ wait_local_port_listen "$NS2" "$port" tcp
+
+ local srv_base0 srv_base1
+ srv_base0=$(tc_filter_pkt_count "$NS2" veth0b)
+ srv_base1=$(tc_filter_pkt_count "$NS2" veth1b)
+
+ ip netns exec "$NS1" timeout 5 socat -u \
+ OPEN:/dev/zero \
+ "TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1]" \
+ &>/dev/null &
+ local client_pid=$!
+
+ local cli_base
+ cli_base=$(link_tx_packets_total "$NS1")
+ if ! busywait "$BUSYWAIT_TIMEOUT" until_counter_is \
+ ">= $((cli_base + 200))" \
+ link_tx_packets_total "$NS1" > /dev/null; then
+ check_err 1 "no TX activity in round $r"
+ kill "$client_pid" 2>/dev/null
+ wait "$client_pid" 2>/dev/null
+ kill "$server_pid" 2>/dev/null
+ wait "$server_pid" 2>/dev/null
+ break
+ fi
+
+ local srv_tcp0 srv_tcp1
+ srv_tcp0=$(tc_filter_pkt_count "$NS2" veth0b)
+ srv_tcp1=$(tc_filter_pkt_count "$NS2" veth1b)
+ local srv_delta0=$(( ${srv_tcp0:-0} - ${srv_base0:-0} ))
+ local srv_delta1=$(( ${srv_tcp1:-0} - ${srv_base1:-0} ))
+
+ if [ "$srv_delta0" -gt 0 ] && [ "$srv_delta1" -gt 0 ]; then
+ path_splits=$((path_splits + 1))
+ fi
+
+ kill "$client_pid" 2>/dev/null
+ wait "$client_pid" 2>/dev/null
+ kill "$server_pid" 2>/dev/null
+ wait "$server_pid" 2>/dev/null
+
+ # Wait for TCP teardown packets (FIN/RST) to finish so
+ # they do not pollute the next round's tc filter counters.
+ busywait "$BUSYWAIT_TIMEOUT" \
+ port_has_no_active_tcp "$NS1" "$port" > /dev/null
+ busywait "$BUSYWAIT_TIMEOUT" \
+ port_has_no_active_tcp "$NS2" "$port" > /dev/null
+ done
+
+ if [ "$path_splits" -gt 0 ]; then
+ check_err 1 "$path_splits/$ECMP_REBUILD_ROUNDS had split server path"
+ fi
+
+ log_test "Syncookie server ECMP path consistent"
+}
+
+require_command socat
+
+trap 'defer_scopes_cleanup; cleanup_all_ns' EXIT
+setup || exit $?
+tests_run
+exit "$EXIT_STATUS"
diff --git a/tools/testing/selftests/net/fib_tests.sh b/tools/testing/selftests/net/fib_tests.sh
index af64f93bb2e1..b338bfb196a2 100755
--- a/tools/testing/selftests/net/fib_tests.sh
+++ b/tools/testing/selftests/net/fib_tests.sh
@@ -12,7 +12,9 @@ TESTS="unregister down carrier nexthop suppress ipv6_notify ipv4_notify \
ipv4_route_metrics ipv4_route_v6_gw rp_filter ipv4_del_addr \
ipv6_del_addr ipv4_mangle ipv6_mangle ipv4_bcast_neigh fib6_gc_test \
ipv4_mpath_list ipv6_mpath_list ipv4_mpath_balance ipv6_mpath_balance \
- ipv4_mpath_balance_preferred fib6_ra_to_static"
+ ipv4_mpath_balance_preferred ipv4_mpath_oif ipv4_mpath_oif_nh \
+ ipv4_mpath_oif_vrf ipv6_mpath_oif ipv6_mpath_oif_nh ipv6_mpath_oif_vrf \
+ fib6_ra_to_static fib6_temp_addr_renewal"
VERBOSE=0
PAUSE_ON_FAIL=no
@@ -1611,6 +1613,62 @@ fib6_ra_to_static()
cleanup &> /dev/null
}
+fib6_temp_addr_renewal() {
+ setup
+
+ echo
+ echo "Fib6 temporary address renewal test"
+ set -e
+
+ # ra6 is required for the test. (ipv6toolkit)
+ if [ ! -x "$(command -v ra6)" ]; then
+ echo "SKIP: ra6 not found."
+ set +e
+ cleanup &> /dev/null
+ return
+ fi
+
+ # Create a pair of veth devices to send a RA message from one
+ # device to another.
+ $IP link add veth1 type veth peer name veth2
+ $IP link set dev veth1 up
+ $IP link set dev veth2 up
+
+ # Make veth1 ready to receive RA messages.
+ $NS_EXEC sysctl -wq net.ipv6.conf.veth1.accept_ra=2
+ $NS_EXEC sysctl -wq net.ipv6.conf.veth1.use_tempaddr=2
+ $NS_EXEC sysctl -wq net.ipv6.conf.veth1.temp_prefered_lft=15
+ $NS_EXEC sysctl -wq net.ipv6.conf.veth1.max_desync_factor=0
+
+ # Send a RA message with a prefix from veth2.
+ $NS_EXEC ra6 -i veth2 -s fe80::1 -d ff02::1 -P 2001:12::/64\#LA\#3600\#3600 -e
+ sleep 3
+
+ # Deprecate it
+ $NS_EXEC ra6 -i veth2 -s fe80::1 -d ff02::1 -P 2001:12::/64\#LA\#3600\#0 -e
+ sleep 3
+
+ # Restore it
+ $NS_EXEC ra6 -i veth2 -s fe80::1 -d ff02::1 -P 2001:12::/64\#LA\#3600\#3600 -e
+
+ ret=1
+ for i in $(seq 1 25); do
+ sleep 1
+ num_dep="$($IP -6 addr | grep -c "temporary deprecated" || true)"
+ num_tot="$($IP -6 addr | grep -c "temporary" || true)"
+
+ if [ "$num_dep" -eq 1 ] && [ "$num_tot" -ge 2 ]; then
+ ret=0
+ break
+ fi
+ done
+ log_test "$ret" 0 "IPv6 temporary address cleanly deprecated and regenerated"
+
+ set +e
+
+ cleanup &> /dev/null
+}
+
# add route for a prefix, flushing any existing routes first
# expected to be the first step of a test
add_route()
@@ -2915,6 +2973,247 @@ ipv6_mpath_balance_test()
forwarding_cleanup
}
+ipv4_mpath_oif_test_common()
+{
+ local get_param=$1; shift
+ local expected_oif=$1; shift
+ local test_name=$1; shift
+ local tmp_file
+
+ tmp_file=$(mktemp)
+
+ for i in {1..100}; do
+ $IP route get 203.0.113.${i} $get_param >> "$tmp_file"
+ done
+
+ [[ $(grep "$expected_oif" "$tmp_file" | wc -l) -eq 100 ]]
+ log_test $? 0 "$test_name"
+
+ rm "$tmp_file"
+}
+
+ipv4_mpath_oif_test()
+{
+ echo
+ echo "IPv4 multipath oif test"
+
+ setup
+
+ set -e
+ $IP link add dummy1 up type dummy
+ $IP address add 192.0.2.1/28 dev dummy1
+ $IP address add 192.0.2.17/32 dev lo
+
+ $IP route add 203.0.113.0/24 \
+ nexthop via 198.51.100.2 dev dummy0 \
+ nexthop via 192.0.2.2 dev dummy1
+ set +e
+
+ ipv4_mpath_oif_test_common "oif dummy0" "dummy0" \
+ "IPv4 multipath via first nexthop"
+
+ ipv4_mpath_oif_test_common "oif dummy1" "dummy1" \
+ "IPv4 multipath via second nexthop"
+
+ ipv4_mpath_oif_test_common "oif dummy0 from 192.0.2.17" "dummy0" \
+ "IPv4 multipath via first nexthop with source address"
+
+ ipv4_mpath_oif_test_common "oif dummy1 from 192.0.2.17" "dummy1" \
+ "IPv4 multipath via second nexthop with source address"
+
+ cleanup
+}
+
+ipv4_mpath_oif_nh_test()
+{
+ echo
+ echo "IPv4 multipath oif with nexthop object test"
+
+ setup
+
+ set -e
+ $IP link add dummy1 up type dummy
+ $IP address add 192.0.2.1/28 dev dummy1
+ $IP address add 192.0.2.17/32 dev lo
+
+ $IP nexthop add id 1 via 198.51.100.2 dev dummy0
+ $IP nexthop add id 2 via 192.0.2.2 dev dummy1
+ $IP nexthop add id 3 group 1/2
+ $IP route add 203.0.113.0/24 nhid 3
+ set +e
+
+ ipv4_mpath_oif_test_common "oif dummy0" "dummy0" \
+ "IPv4 multipath via first nexthop"
+
+ ipv4_mpath_oif_test_common "oif dummy1" "dummy1" \
+ "IPv4 multipath via second nexthop"
+
+ ipv4_mpath_oif_test_common "oif dummy0 from 192.0.2.17" "dummy0" \
+ "IPv4 multipath via first nexthop with source address"
+
+ ipv4_mpath_oif_test_common "oif dummy1 from 192.0.2.17" "dummy1" \
+ "IPv4 multipath via second nexthop with source address"
+
+ cleanup
+}
+
+ipv4_mpath_oif_vrf_test()
+{
+ echo
+ echo "IPv4 multipath oif with VRF test"
+
+ setup
+
+ set -e
+ $IP -4 rule add pref 32765 table local
+ $IP -4 rule del pref 0
+ $IP link add name vrf-123 up type vrf table 123
+ $IP link set dev dummy0 master vrf-123
+ $IP link add dummy1 up master vrf-123 type dummy
+ $IP address add 192.0.2.1/28 dev dummy1
+ $IP address add 192.0.2.17/32 dev vrf-123
+
+ $IP route add 203.0.113.0/24 vrf vrf-123 \
+ nexthop via 198.51.100.2 dev dummy0 \
+ nexthop via 192.0.2.2 dev dummy1
+ set +e
+
+ ipv4_mpath_oif_test_common "oif dummy0" "dummy0" \
+ "IPv4 multipath via first nexthop"
+
+ ipv4_mpath_oif_test_common "oif dummy1" "dummy1" \
+ "IPv4 multipath via second nexthop"
+
+ ipv4_mpath_oif_test_common "oif dummy0 from 192.0.2.17" "dummy0" \
+ "IPv4 multipath via first nexthop with source address"
+
+ ipv4_mpath_oif_test_common "oif dummy1 from 192.0.2.17" "dummy1" \
+ "IPv4 multipath via second nexthop with source address"
+
+ cleanup
+}
+
+ipv6_mpath_oif_test_common()
+{
+ local get_param=$1; shift
+ local expected_oif=$1; shift
+ local test_name=$1; shift
+ local tmp_file
+
+ tmp_file=$(mktemp)
+
+ for i in {1..100}; do
+ $IP route get 2001:db8:10::${i} $get_param >> "$tmp_file"
+ done
+
+ [[ $(grep "$expected_oif" "$tmp_file" | wc -l) -eq 100 ]]
+ log_test $? 0 "$test_name"
+
+ rm "$tmp_file"
+}
+
+ipv6_mpath_oif_test()
+{
+ echo
+ echo "IPv6 multipath oif test"
+
+ setup
+
+ set -e
+ $IP link add dummy1 up type dummy
+ $IP address add 2001:db8:2::1/64 dev dummy1
+ $IP address add 2001:db8:100::1/128 dev lo
+
+ $IP route add 2001:db8:10::/64 \
+ nexthop via 2001:db8:1::2 dev dummy0 \
+ nexthop via 2001:db8:2::2 dev dummy1
+ set +e
+
+ ipv6_mpath_oif_test_common "oif dummy0" "dummy0" \
+ "IPv6 multipath via first nexthop"
+
+ ipv6_mpath_oif_test_common "oif dummy1" "dummy1" \
+ "IPv6 multipath via second nexthop"
+
+ ipv6_mpath_oif_test_common "oif dummy0 from 2001:db8:100::1" "dummy0" \
+ "IPv6 multipath via first nexthop with source address"
+
+ ipv6_mpath_oif_test_common "oif dummy1 from 2001:db8:100::1" "dummy1" \
+ "IPv6 multipath via second nexthop with source address"
+
+ cleanup
+}
+
+ipv6_mpath_oif_nh_test()
+{
+ echo
+ echo "IPv6 multipath oif with nexthop object test"
+
+ setup
+
+ set -e
+ $IP link add dummy1 up type dummy
+ $IP address add 2001:db8:2::1/64 dev dummy1
+ $IP address add 2001:db8:100::1/128 dev lo
+
+ $IP nexthop add id 1 via 2001:db8:1::2 dev dummy0
+ $IP nexthop add id 2 via 2001:db8:2::2 dev dummy1
+ $IP nexthop add id 3 group 1/2
+ $IP route add 2001:db8:10::/64 nhid 3
+ set +e
+
+ ipv6_mpath_oif_test_common "oif dummy0" "dummy0" \
+ "IPv6 multipath via first nexthop"
+
+ ipv6_mpath_oif_test_common "oif dummy1" "dummy1" \
+ "IPv6 multipath via second nexthop"
+
+ ipv6_mpath_oif_test_common "oif dummy0 from 2001:db8:100::1" "dummy0" \
+ "IPv6 multipath via first nexthop with source address"
+
+ ipv6_mpath_oif_test_common "oif dummy1 from 2001:db8:100::1" "dummy1" \
+ "IPv6 multipath via second nexthop with source address"
+
+ cleanup
+}
+
+ipv6_mpath_oif_vrf_test()
+{
+ echo
+ echo "IPv6 multipath oif with VRF test"
+
+ setup
+
+ set -e
+ $NS_EXEC sysctl -qw net.ipv6.conf.all.keep_addr_on_down=1
+ $IP -6 rule add pref 32765 table local
+ $IP -6 rule del pref 0
+ $IP link add name vrf-123 up type vrf table 123
+ $IP link set dev dummy0 master vrf-123
+ $IP link add dummy1 up master vrf-123 type dummy
+ $IP address add 2001:db8:2::1/64 dev dummy1
+ $IP address add 2001:db8:100::1/128 dev vrf-123
+
+ $IP route add 2001:db8:10::/64 vrf vrf-123 \
+ nexthop via 2001:db8:1::2 dev dummy0 \
+ nexthop via 2001:db8:2::2 dev dummy1
+ set +e
+
+ ipv6_mpath_oif_test_common "oif dummy0" "dummy0" \
+ "IPv6 multipath via first nexthop"
+
+ ipv6_mpath_oif_test_common "oif dummy1" "dummy1" \
+ "IPv6 multipath via second nexthop"
+
+ ipv6_mpath_oif_test_common "oif dummy0 from 2001:db8:100::1" "dummy0" \
+ "IPv6 multipath via first nexthop with source address"
+
+ ipv6_mpath_oif_test_common "oif dummy1 from 2001:db8:100::1" "dummy1" \
+ "IPv6 multipath via second nexthop with source address"
+
+ cleanup
+}
+
################################################################################
# usage
@@ -3001,7 +3300,14 @@ do
ipv4_mpath_balance) ipv4_mpath_balance_test;;
ipv6_mpath_balance) ipv6_mpath_balance_test;;
ipv4_mpath_balance_preferred) ipv4_mpath_balance_preferred_test;;
+ ipv4_mpath_oif) ipv4_mpath_oif_test;;
+ ipv4_mpath_oif_nh) ipv4_mpath_oif_nh_test;;
+ ipv4_mpath_oif_vrf) ipv4_mpath_oif_vrf_test;;
+ ipv6_mpath_oif) ipv6_mpath_oif_test;;
+ ipv6_mpath_oif_nh) ipv6_mpath_oif_nh_test;;
+ ipv6_mpath_oif_vrf) ipv6_mpath_oif_vrf_test;;
fib6_ra_to_static) fib6_ra_to_static;;
+ fib6_temp_addr_renewal) fib6_temp_addr_renewal;;
help) echo "Test names: $TESTS"; exit 0;;
esac
diff --git a/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh b/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh
index ebdb4c790a5d..c4cd2078a8db 100755
--- a/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh
+++ b/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh
@@ -162,14 +162,27 @@ vlmc_query_cnt_setup()
{
local type=$1
local dev=$2
+ local match=($3)
if [[ $type == "igmp" ]]; then
- tc filter add dev $dev egress pref 10 prot 802.1Q \
+ # This matches: IP Protocol 2 (IGMP)
+ tc filter add dev "$dev" egress pref 10 prot 802.1Q \
flower vlan_id 10 vlan_ethtype ipv4 dst_ip 224.0.0.1 ip_proto 2 \
+ action goto chain 1
+ # AND Type 0x11 (Query) at offset 0 of IGMP header
+ # 20 bytes IPv4 header + 4 bytes Router Alert option + IGMP[offset 0]
+ tc filter add dev "$dev" egress pref 20 chain 1 prot 802.1Q u32 \
+ match u8 0x11 0xff at 24 "${match[@]}" \
action pass
else
- tc filter add dev $dev egress pref 10 prot 802.1Q \
+ # This matches: ICMPv6
+ tc filter add dev "$dev" egress pref 10 prot 802.1Q \
flower vlan_id 10 vlan_ethtype ipv6 dst_ip ff02::1 ip_proto icmpv6 \
+ action goto chain 1
+ # AND Type 0x82 (Query) at offset 0 of MLD header
+ # 40 bytes IPv6 header + 8 bytes Hop-by-hop option + MLD[offset 0]
+ tc filter add dev "$dev" egress pref 20 chain 1 prot 802.1Q u32 \
+ match u8 0x82 0xff at 48 "${match[@]}" \
action pass
fi
@@ -181,7 +194,39 @@ vlmc_query_cnt_cleanup()
local dev=$1
ip link set dev br0 type bridge mcast_stats_enabled 0
- tc filter del dev $dev egress pref 10
+ tc filter del dev "$dev" egress pref 20 chain 1
+ tc filter del dev "$dev" egress pref 10
+}
+
+vlmc_query_get_intvl_match()
+{
+ local type=$1
+ local version=$2
+ local test=$3
+ local enc_val=$4
+
+ if [ "$test" = "qqic" ]; then
+ # QQIC is 8-bit floating point encoding for IGMPv3 and MLDv2
+ if [ "${type}v${version}" = "igmpv3" ]; then
+ # QQIC is at offset 9 of IGMP header
+ # 20 bytes IPv4 header + 4 bytes Router Alert option + IGMP[offset 9]
+ echo "match u8 $enc_val 0xff at 33"
+ elif [ "${type}v${version}" = "mldv2" ]; then
+ # QQIC is at offset 25 of MLD header
+ # 40 bytes IPv6 header + 8 bytes Hop-by-hop option + MLD[offset 25]
+ echo "match u8 $enc_val 0xff at 73"
+ fi
+ elif [ "$test" = "mrc" ]; then
+ if [ "${type}v${version}" = "igmpv3" ]; then
+ # MRC is 8-bit floating point encoding at offset 1 of IGMP header
+ # 20 bytes IPv4 header + 4 bytes Router Alert option + IGMP[offset 1]
+ echo "match u8 $enc_val 0xff at 25"
+ elif [ "${type}v${version}" = "mldv2" ]; then
+ # MRC is 16-bit floating point encoding at offset 4 of MLD header
+ # 40 bytes IPv6 header + 8 bytes Hop-by-hop option + MLD[offset 4]
+ echo "match u16 $enc_val 0xffff at 52"
+ fi
+ fi
}
vlmc_check_query()
@@ -191,9 +236,13 @@ vlmc_check_query()
local dev=$3
local expect=$4
local time=$5
+ local test=$6
+ local enc_val=$7
+ local intvl_match=""
local ret=0
- vlmc_query_cnt_setup $type $dev
+ intvl_match="$(vlmc_query_get_intvl_match "$type" "$version" "$test" "$enc_val")"
+ vlmc_query_cnt_setup "$type" "$dev" "$intvl_match"
local pre_tx_xstats=$(vlmc_query_cnt_xstats $type $version $dev)
bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_querier 1
@@ -201,7 +250,7 @@ vlmc_check_query()
if [[ $ret -eq 0 ]]; then
sleep $time
- local tcstats=$(tc_rule_stats_get $dev 10 egress)
+ local tcstats=$(tc_rule_stats_get "$dev" 20 egress)
local post_tx_xstats=$(vlmc_query_cnt_xstats $type $version $dev)
if [[ $tcstats != $expect || \
@@ -448,8 +497,46 @@ vlmc_query_intvl_test()
# 1 is sent immediately, then 2 more in the next 5 seconds
vlmc_check_query igmp 2 $swp1 3 5
check_err $? "Wrong number of tagged IGMPv2 general queries sent"
- log_test "Vlan 10 mcast_query_interval option changed to 200"
+ log_test "Number of tagged IGMPv2 general query"
+
+ RET=0
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 3
+ check_err $? "Could not set mcast_igmp_version in vlan 10"
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 2
+ check_err $? "Could not set mcast_mld_version in vlan 10"
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_interval 6000
+ check_err $? "Could not set mcast_query_interval in vlan 10"
+ # 1 is sent immediately, IGMPv3 QQIC should match with linear value 60 (0x3c)
+ # which is 8-bit encoded value of 60 [units of seconds]
+ vlmc_check_query igmp 3 $swp1 1 1 qqic 0x3c
+ check_err $? "Wrong QQIC in generated IGMPv3 general queries"
+ log_test "IGMPv3 QQIC linear value 60(s)"
+
+ RET=0
+ # 1 is sent immediately, MLDv2 QQIC should match with linear value 60 (0x3c)
+ # which is 8-bit encoded value of 60 [units of seconds]
+ vlmc_check_query mld 2 $swp1 1 1 qqic 0x3c
+ check_err $? "Wrong QQIC in generated MLDv2 general queries"
+ log_test "MLDv2 QQIC linear value 60(s)"
+
+ RET=0
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_interval 16000
+ check_err $? "Could not set mcast_query_interval in vlan 10"
+ # 1 is sent immediately, IGMPv3 QQIC should match with non linear value 132 (0x84)
+ # which is 8-bit encoded value of 160 [units of seconds]
+ vlmc_check_query igmp 3 $swp1 1 1 qqic 0x84
+ check_err $? "Wrong QQIC in generated IGMPv3 general queries"
+ log_test "IGMPv3 QQIC non linear value 160(s)"
+ RET=0
+ # 1 is sent immediately, MLDv2 QQIC should match with non linear value 132 (0x84)
+ # which is 8-bit encoded value of 160 [units of seconds]
+ vlmc_check_query mld 2 $swp1 1 1 qqic 0x84
+ check_err $? "Wrong QQIC in generated MLDv2 general queries"
+ log_test "MLDv2 QQIC non linear value 160(s)"
+
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 2
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 1
bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_startup_query_count 2
bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_interval 12500
}
@@ -469,10 +556,47 @@ vlmc_query_response_intvl_test()
log_test "Vlan mcast_query_response_interval global option default value"
RET=0
- bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 200
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_startup_query_count 0
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 3
+ check_err $? "Could not set mcast_igmp_version in vlan 10"
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 2
+ check_err $? "Could not set mcast_mld_version in vlan 10"
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 600
+ check_err $? "Could not set mcast_query_response_interval in vlan 10"
+ # 1 is sent immediately, IGMPv3 MRC should match with linear value 60 (0x3c)
+ # which is 8-bit encoded value of 60 [units of 0.1s = 6 seconds]
+ vlmc_check_query igmp 3 $swp1 1 1 mrc 0x3c
+ check_err $? "Wrong MRC in generated IGMPv3 general queries"
+ log_test "IGMPv3 MRC linear value of 60(x0.1s)"
+
+ RET=0
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 2400
+ check_err $? "Could not set mcast_query_response_interval in vlan 10"
+ # 1 is sent immediately, MLDv2 MRC should match with linear value 0x5dc0 (24000)
+ # which is 16-bit encoded value of 24000 [units of ms / 24 seconds]
+ vlmc_check_query mld 2 $swp1 1 1 mrc 0x5dc0
+ check_err $? "Wrong MRC in generated MLDv2 general queries"
+ log_test "MLDv2 MRC linear value of 24000(ms)"
+
+ RET=0
+ # 1 is sent immediately, IGMPv3 MRC should match with non linear value 142 (0x8e)
+ # which is 8-bit encoded value of 240 [units of 0.1s = 24 seconds]
+ vlmc_check_query igmp 3 $swp1 1 1 mrc 0x8e
+ check_err $? "Wrong MRC in generated IGMPv3 general queries"
+ log_test "IGMPv3 MRC non linear value of 240(x0.1s)"
+
+ RET=0
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 4800
check_err $? "Could not set mcast_query_response_interval in vlan 10"
- log_test "Vlan 10 mcast_query_response_interval option changed to 200"
+ # 1 is sent immediately, MLDv2 MRC should match with non linear value 0x8770 (34672)
+ # which is 16-bit encoded value of 48000 [units of ms / 48 seconds]
+ vlmc_check_query mld 2 $swp1 1 1 mrc 0x8770
+ check_err $? "Wrong MRC in generated MLDv2 general queries"
+ log_test "MLDv2 MRC non linear value of 48000(ms)"
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 2
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 1
+ bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_startup_query_count 2
bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 1000
}
diff --git a/tools/testing/selftests/net/forwarding/ipmr.c b/tools/testing/selftests/net/forwarding/ipmr.c
index df870aad9ead..9cd9f70de132 100644
--- a/tools/testing/selftests/net/forwarding/ipmr.c
+++ b/tools/testing/selftests/net/forwarding/ipmr.c
@@ -2,7 +2,9 @@
/* Copyright 2026 Google LLC */
#include <linux/if.h>
+#include <linux/in6.h>
#include <linux/mroute.h>
+#include <linux/mroute6.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/socket.h>
@@ -17,6 +19,14 @@ FIXTURE(ipmr)
int netlink_sk;
int raw_sk;
int veth_ifindex;
+ union {
+ struct vifctl vif;
+ struct mif6ctl vif6;
+ };
+ union {
+ struct mfcctl mfc;
+ struct mf6cctl mfc6;
+ };
};
FIXTURE_VARIANT(ipmr)
@@ -24,7 +34,14 @@ FIXTURE_VARIANT(ipmr)
int family;
int protocol;
int level;
+ int rtm_family;
int opts[MRT_MAX - MRT_BASE + 1];
+ int flush_flags;
+ int vif_size;
+ char vif_check_cmd_pimreg[64];
+ char vif_check_cmd_veth[64];
+ int mfc_size;
+ char mfc_check_cmd[1024];
};
FIXTURE_VARIANT_ADD(ipmr, ipv4)
@@ -32,6 +49,7 @@ FIXTURE_VARIANT_ADD(ipmr, ipv4)
.family = AF_INET,
.protocol = IPPROTO_IGMP,
.level = IPPROTO_IP,
+ .rtm_family = RTNL_FAMILY_IPMR,
.opts = {
MRT_INIT,
MRT_DONE,
@@ -47,6 +65,44 @@ FIXTURE_VARIANT_ADD(ipmr, ipv4)
MRT_DEL_MFC_PROXY,
MRT_FLUSH,
},
+ .flush_flags = MRT_FLUSH_MFC | MRT_FLUSH_MFC_STATIC |
+ MRT_FLUSH_VIFS | MRT_FLUSH_VIFS_STATIC,
+ .vif_size = sizeof(struct vifctl),
+ .vif_check_cmd_pimreg = "cat /proc/net/ip_mr_vif | grep -q pimreg",
+ .vif_check_cmd_veth = "cat /proc/net/ip_mr_vif | grep -q veth",
+ .mfc_size = sizeof(struct mfcctl),
+ .mfc_check_cmd = "cat /proc/net/ip_mr_cache | grep -q '00000000 00000000'",
+};
+
+FIXTURE_VARIANT_ADD(ipmr, ipv6)
+{
+ .family = AF_INET6,
+ .protocol = IPPROTO_ICMPV6,
+ .level = IPPROTO_IPV6,
+ .rtm_family = RTNL_FAMILY_IP6MR,
+ .opts = {
+ MRT6_INIT,
+ MRT6_DONE,
+ MRT6_ADD_MIF,
+ MRT6_DEL_MIF,
+ MRT6_ADD_MFC,
+ MRT6_DEL_MFC,
+ MRT6_VERSION,
+ MRT6_ASSERT,
+ MRT6_PIM,
+ MRT6_TABLE,
+ MRT6_ADD_MFC_PROXY,
+ MRT6_DEL_MFC_PROXY,
+ MRT6_FLUSH,
+ },
+ .flush_flags = MRT6_FLUSH_MFC | MRT6_FLUSH_MFC_STATIC |
+ MRT6_FLUSH_MIFS | MRT6_FLUSH_MIFS_STATIC,
+ .vif_size = sizeof(struct mif6ctl),
+ .vif_check_cmd_pimreg = "cat /proc/net/ip6_mr_vif | grep -q pim6reg",
+ .vif_check_cmd_veth = "cat /proc/net/ip6_mr_vif | grep -q veth",
+ .mfc_size = sizeof(struct mf6cctl),
+ .mfc_check_cmd = "cat /proc/net/ip6_mr_cache | "
+ "grep -q '0000:0000:0000:0000:0000:0000:0000:0000 0000:0000:0000:0000:0000:0000:0000:0000'",
};
struct mfc_attr {
@@ -71,7 +127,9 @@ static struct rtattr *nl_add_rtattr(struct nlmsghdr *nlmsg, struct rtattr *rta,
return RTA_NEXT(rta, unused);
}
-static int nl_sendmsg_mfc(struct __test_metadata *_metadata, FIXTURE_DATA(ipmr) *self,
+static int nl_sendmsg_mfc(struct __test_metadata *_metadata,
+ FIXTURE_DATA(ipmr) *self,
+ const FIXTURE_VARIANT(ipmr) *variant,
__u16 nlmsg_type, struct mfc_attr *mfc_attr)
{
struct {
@@ -87,7 +145,7 @@ static int nl_sendmsg_mfc(struct __test_metadata *_metadata, FIXTURE_DATA(ipmr)
},
.rtm = {
/* hard requirements in rtm_to_ipmr_mfcc() */
- .rtm_family = RTNL_FAMILY_IPMR,
+ .rtm_family = variant->rtm_family,
.rtm_dst_len = 32,
.rtm_type = RTN_MULTICAST,
.rtm_scope = RT_SCOPE_UNIVERSE,
@@ -144,6 +202,18 @@ FIXTURE_SETUP(ipmr)
ASSERT_EQ(0, err);
self->veth_ifindex = ifr.ifr_ifindex;
+
+ if (variant->family == AF_INET) {
+ self->vif = (struct vifctl){
+ .vifc_flags = VIFF_USE_IFINDEX,
+ .vifc_lcl_ifindex = self->veth_ifindex,
+ };
+ } else {
+ self->vif6 = (struct mif6ctl){
+ .mif6c_flags = 0,
+ .mif6c_pifi = self->veth_ifindex,
+ };
+ }
}
FIXTURE_TEARDOWN(ipmr)
@@ -169,41 +239,39 @@ TEST_F(ipmr, mrt_init)
TEST_F(ipmr, mrt_add_vif_register)
{
- struct vifctl vif = {
- .vifc_vifi = 0,
- .vifc_flags = VIFF_REGISTER,
- };
int err;
+ memset(&self->vif, 0, variant->vif_size);
+
+ if (variant->family == AF_INET)
+ self->vif.vifc_flags = VIFF_REGISTER;
+ else
+ self->vif6.mif6c_flags = MIFF_REGISTER;
+
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
- &vif, sizeof(vif));
+ &self->vif, variant->vif_size);
ASSERT_EQ(0, err);
- err = system("cat /proc/net/ip_mr_vif | grep -q pimreg");
+ err = system(variant->vif_check_cmd_pimreg);
ASSERT_EQ(0, err);
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_DEL_VIF - MRT_BASE],
- &vif, sizeof(vif));
+ &self->vif, variant->vif_size);
ASSERT_EQ(0, err);
}
TEST_F(ipmr, mrt_del_vif_unreg)
{
- struct vifctl vif = {
- .vifc_vifi = 0,
- .vifc_flags = VIFF_USE_IFINDEX,
- .vifc_lcl_ifindex = self->veth_ifindex,
- };
int err;
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
- &vif, sizeof(vif));
+ &self->vif, variant->vif_size);
ASSERT_EQ(0, err);
- err = system("cat /proc/net/ip_mr_vif | grep -q veth0");
+ err = system(variant->vif_check_cmd_veth);
ASSERT_EQ(0, err);
/* VIF is removed along with its device. */
@@ -213,23 +281,18 @@ TEST_F(ipmr, mrt_del_vif_unreg)
/* mrt->vif_table[veth_ifindex]->dev is NULL. */
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_DEL_VIF - MRT_BASE],
- &vif, sizeof(vif));
+ &self->vif, variant->vif_size);
ASSERT_EQ(-1, err);
ASSERT_EQ(EADDRNOTAVAIL, errno);
}
TEST_F(ipmr, mrt_del_vif_netns_dismantle)
{
- struct vifctl vif = {
- .vifc_vifi = 0,
- .vifc_flags = VIFF_USE_IFINDEX,
- .vifc_lcl_ifindex = self->veth_ifindex,
- };
int err;
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
- &vif, sizeof(vif));
+ &self->vif, variant->vif_size);
ASSERT_EQ(0, err);
/* Let cleanup_net() remove veth0 and VIF. */
@@ -237,49 +300,42 @@ TEST_F(ipmr, mrt_del_vif_netns_dismantle)
TEST_F(ipmr, mrt_add_mfc)
{
- struct mfcctl mfc = {};
int err;
/* MRT_ADD_MFC / MRT_ADD_MFC_PROXY does not need vif to exist (unlike netlink). */
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_MFC - MRT_BASE],
- &mfc, sizeof(mfc));
+ &self->mfc, variant->mfc_size);
ASSERT_EQ(0, err);
/* (0.0.0.0 -> 0.0.0.0) */
- err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
+ err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_DEL_MFC - MRT_BASE],
- &mfc, sizeof(mfc));
+ &self->mfc, variant->mfc_size);
}
TEST_F(ipmr, mrt_add_mfc_proxy)
{
- struct mfcctl mfc = {};
int err;
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_MFC_PROXY - MRT_BASE],
- &mfc, sizeof(mfc));
+ &self->mfc, variant->mfc_size);
ASSERT_EQ(0, err);
- err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
+ err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_DEL_MFC_PROXY - MRT_BASE],
- &mfc, sizeof(mfc));
+ &self->mfc, variant->mfc_size);
}
TEST_F(ipmr, mrt_add_mfc_netlink)
{
- struct vifctl vif = {
- .vifc_vifi = 0,
- .vifc_flags = VIFF_USE_IFINDEX,
- .vifc_lcl_ifindex = self->veth_ifindex,
- };
struct mfc_attr mfc_attr = {
.table = RT_TABLE_DEFAULT,
.origin = 0,
@@ -291,26 +347,21 @@ TEST_F(ipmr, mrt_add_mfc_netlink)
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
- &vif, sizeof(vif));
+ &self->vif, variant->vif_size);
ASSERT_EQ(0, err);
- err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
+ err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
ASSERT_EQ(0, err);
- err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
+ err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
- err = nl_sendmsg_mfc(_metadata, self, RTM_DELROUTE, &mfc_attr);
+ err = nl_sendmsg_mfc(_metadata, self, variant, RTM_DELROUTE, &mfc_attr);
ASSERT_EQ(0, err);
}
TEST_F(ipmr, mrt_add_mfc_netlink_proxy)
{
- struct vifctl vif = {
- .vifc_vifi = 0,
- .vifc_flags = VIFF_USE_IFINDEX,
- .vifc_lcl_ifindex = self->veth_ifindex,
- };
struct mfc_attr mfc_attr = {
.table = RT_TABLE_DEFAULT,
.origin = 0,
@@ -322,16 +373,16 @@ TEST_F(ipmr, mrt_add_mfc_netlink_proxy)
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
- &vif, sizeof(vif));
+ &self->vif, variant->vif_size);
ASSERT_EQ(0, err);
- err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
+ err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
ASSERT_EQ(0, err);
- err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
+ err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
- err = nl_sendmsg_mfc(_metadata, self, RTM_DELROUTE, &mfc_attr);
+ err = nl_sendmsg_mfc(_metadata, self, variant, RTM_DELROUTE, &mfc_attr);
ASSERT_EQ(0, err);
}
@@ -347,12 +398,12 @@ TEST_F(ipmr, mrt_add_mfc_netlink_no_vif)
/* netlink always requires RTA_IIF of an existing vif. */
mfc_attr.ifindex = 0;
- err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
+ err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
ASSERT_EQ(-ENFILE, err);
/* netlink always requires RTA_IIF of an existing vif. */
mfc_attr.ifindex = self->veth_ifindex;
- err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
+ err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
ASSERT_EQ(-ENFILE, err);
}
@@ -387,10 +438,10 @@ TEST_F(ipmr, mrt_del_mfc_netlink_netns_dismantle)
}
/* Create a MFC for mrt->vif_table[0]. */
- err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
+ err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
ASSERT_EQ(0, err);
- err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
+ err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
/* Remove mrt->vif_table[0]. */
@@ -398,13 +449,13 @@ TEST_F(ipmr, mrt_del_mfc_netlink_netns_dismantle)
ASSERT_EQ(0, err);
/* MFC entry is NOT removed even if the tied VIF is removed... */
- err = system("cat /proc/net/ip_mr_cache | grep -q '00000000 00000000' ");
+ err = system(variant->mfc_check_cmd);
ASSERT_EQ(0, err);
/* ... and netlink is not capable of removing such an entry
* because netlink always requires a valid RTA_IIF ... :/
*/
- err = nl_sendmsg_mfc(_metadata, self, RTM_DELROUTE, &mfc_attr);
+ err = nl_sendmsg_mfc(_metadata, self, variant, RTM_DELROUTE, &mfc_attr);
ASSERT_EQ(-ENODEV, err);
/* It can be removed by setsockopt(), but let cleanup_net() remove this time. */
@@ -412,11 +463,6 @@ TEST_F(ipmr, mrt_del_mfc_netlink_netns_dismantle)
TEST_F(ipmr, mrt_table_flush)
{
- struct vifctl vif = {
- .vifc_vifi = 0,
- .vifc_flags = VIFF_USE_IFINDEX,
- .vifc_lcl_ifindex = self->veth_ifindex,
- };
struct mfc_attr mfc_attr = {
.origin = 0,
.group = 0,
@@ -424,7 +470,7 @@ TEST_F(ipmr, mrt_table_flush)
.proxy = false,
};
int table_id = 92;
- int err, flags;
+ int err;
/* Set a random table id rather than RT_TABLE_DEFAULT.
* Note that /proc/net/ip_mr_{vif,cache} only supports RT_TABLE_DEFAULT.
@@ -436,20 +482,29 @@ TEST_F(ipmr, mrt_table_flush)
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_ADD_VIF - MRT_BASE],
- &vif, sizeof(vif));
+ &self->vif, variant->vif_size);
ASSERT_EQ(0, err);
- mfc_attr.table = table_id;
- err = nl_sendmsg_mfc(_metadata, self, RTM_NEWROUTE, &mfc_attr);
+ if (variant->family == AF_INET) {
+ mfc_attr.table = table_id;
+ err = nl_sendmsg_mfc(_metadata, self, variant, RTM_NEWROUTE, &mfc_attr);
+ } else {
+ err = setsockopt(self->raw_sk,
+ variant->level, variant->opts[MRT_ADD_MFC - MRT_BASE],
+ &self->mfc, variant->mfc_size);
+ }
ASSERT_EQ(0, err);
/* Flush mrt->vif_table[] and all caches. */
- flags = MRT_FLUSH_VIFS | MRT_FLUSH_VIFS_STATIC |
- MRT_FLUSH_MFC | MRT_FLUSH_MFC_STATIC;
err = setsockopt(self->raw_sk,
variant->level, variant->opts[MRT_FLUSH - MRT_BASE],
- &flags, sizeof(flags));
+ &variant->flush_flags, sizeof(variant->flush_flags));
ASSERT_EQ(0, err);
}
+XFAIL_ADD(ipmr, ipv6, mrt_add_mfc_netlink);
+XFAIL_ADD(ipmr, ipv6, mrt_add_mfc_netlink_proxy);
+XFAIL_ADD(ipmr, ipv6, mrt_add_mfc_netlink_no_vif);
+XFAIL_ADD(ipmr, ipv6, mrt_del_mfc_netlink_netns_dismantle);
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/net/getsockopt_iter.c b/tools/testing/selftests/net/getsockopt_iter.c
new file mode 100644
index 000000000000..209569354d0e
--- /dev/null
+++ b/tools/testing/selftests/net/getsockopt_iter.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Quick test for getsockopt{_iter} tests.
+ *
+ * Each fixture targets one converted protocol and pins down the
+ * returned-length / errno semantics across buffer-size variations,
+ * an unknown optname and a bogus level.
+ *
+ * - netlink: NETLINK_PKTINFO covers the flag-style int path; the
+ * NETLINK_LIST_MEMBERSHIPS cases cover the size-discovery path
+ * that always reports the required buffer length back via optlen,
+ * even when the user buffer is too small to receive any group bits.
+ * - vsock: SO_VM_SOCKETS_BUFFER_SIZE covers the u64 path.
+ *
+ * Author: Breno Leitao <leitao@debian.org>
+ */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/time_types.h>
+#include <linux/vm_sockets.h>
+#include <sys/socket.h>
+#include "kselftest_harness.h"
+
+#ifndef AF_VSOCK
+#define AF_VSOCK 40
+#endif
+
+/* ---------- netlink ---------- */
+
+FIXTURE(netlink)
+{
+ int fd;
+};
+
+FIXTURE_SETUP(netlink)
+{
+ int group = RTNLGRP_LINK;
+
+ self->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (self->fd < 0)
+ SKIP(return, "AF_NETLINK socket: %s", strerror(errno));
+
+ /* Joining a multicast group grows nlk->ngroups so the
+ * NETLINK_LIST_MEMBERSHIPS path has a non-zero size to report.
+ */
+ if (setsockopt(self->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
+ &group, sizeof(group)) < 0)
+ SKIP(return, "NETLINK_ADD_MEMBERSHIP: %s", strerror(errno));
+}
+
+FIXTURE_TEARDOWN(netlink)
+{
+ if (self->fd >= 0)
+ close(self->fd);
+}
+
+TEST_F(netlink, pktinfo_exact)
+{
+ socklen_t optlen;
+ int val = -1;
+
+ optlen = sizeof(val);
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_NETLINK, NETLINK_PKTINFO,
+ &val, &optlen));
+ ASSERT_EQ(sizeof(int), optlen);
+ ASSERT_TRUE(val == 0 || val == 1);
+}
+
+TEST_F(netlink, pktinfo_oversize_clamped)
+{
+ char buf[16] = {};
+ socklen_t optlen;
+
+ optlen = sizeof(buf);
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_NETLINK, NETLINK_PKTINFO,
+ buf, &optlen));
+ ASSERT_EQ(sizeof(int), optlen);
+}
+
+TEST_F(netlink, pktinfo_undersize)
+{
+ char buf[2] = {};
+ socklen_t optlen;
+
+ optlen = sizeof(buf);
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_NETLINK, NETLINK_PKTINFO,
+ buf, &optlen));
+ ASSERT_EQ(EINVAL, errno);
+ ASSERT_EQ(sizeof(buf), optlen);
+}
+
+TEST_F(netlink, list_memberships_size_discovery)
+{
+ socklen_t optlen = 0;
+ char dummy;
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_NETLINK,
+ NETLINK_LIST_MEMBERSHIPS,
+ &dummy, &optlen));
+ ASSERT_GT(optlen, 0);
+ ASSERT_EQ(0, optlen % sizeof(__u32));
+}
+
+TEST_F(netlink, list_memberships_full_read)
+{
+ __u32 buf[64] = {};
+ socklen_t optlen;
+
+ optlen = sizeof(buf);
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_NETLINK,
+ NETLINK_LIST_MEMBERSHIPS,
+ buf, &optlen));
+ ASSERT_GT(optlen, 0);
+ ASSERT_LE(optlen, sizeof(buf));
+ ASSERT_EQ(0, optlen % sizeof(__u32));
+}
+
+TEST_F(netlink, bad_level)
+{
+ socklen_t optlen;
+ int val;
+
+ optlen = sizeof(val);
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_SOCKET + 1, NETLINK_PKTINFO,
+ &val, &optlen));
+ ASSERT_EQ(ENOPROTOOPT, errno);
+ ASSERT_EQ(sizeof(val), optlen);
+}
+
+TEST_F(netlink, bad_optname)
+{
+ socklen_t optlen;
+ int val;
+
+ optlen = sizeof(val);
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_NETLINK, 0x7fff,
+ &val, &optlen));
+ ASSERT_EQ(ENOPROTOOPT, errno);
+ ASSERT_EQ(sizeof(val), optlen);
+}
+
+/* ---------- vsock ---------- */
+
+FIXTURE(vsock)
+{
+ int fd;
+};
+
+FIXTURE_SETUP(vsock)
+{
+ self->fd = socket(AF_VSOCK, SOCK_STREAM, 0);
+ if (self->fd < 0)
+ SKIP(return, "AF_VSOCK socket: %s", strerror(errno));
+}
+
+FIXTURE_TEARDOWN(vsock)
+{
+ if (self->fd >= 0)
+ close(self->fd);
+}
+
+TEST_F(vsock, buffer_size_exact)
+{
+ socklen_t optlen;
+ uint64_t val = 0;
+
+ optlen = sizeof(val);
+
+ ASSERT_EQ(0, getsockopt(self->fd, AF_VSOCK,
+ SO_VM_SOCKETS_BUFFER_SIZE,
+ &val, &optlen));
+ ASSERT_EQ(sizeof(uint64_t), optlen);
+ ASSERT_GT(val, 0);
+}
+
+TEST_F(vsock, buffer_size_oversize_clamped)
+{
+ char buf[16] = {};
+ socklen_t optlen;
+
+ optlen = sizeof(buf);
+
+ ASSERT_EQ(0, getsockopt(self->fd, AF_VSOCK,
+ SO_VM_SOCKETS_BUFFER_SIZE,
+ buf, &optlen));
+ ASSERT_EQ(sizeof(uint64_t), optlen);
+}
+
+TEST_F(vsock, buffer_size_undersize)
+{
+ char buf[4] = {};
+ socklen_t optlen;
+
+ optlen = sizeof(buf);
+
+ ASSERT_EQ(-1, getsockopt(self->fd, AF_VSOCK,
+ SO_VM_SOCKETS_BUFFER_SIZE,
+ buf, &optlen));
+ ASSERT_EQ(EINVAL, errno);
+ ASSERT_EQ(sizeof(buf), optlen);
+}
+
+TEST_F(vsock, bad_level)
+{
+ socklen_t optlen;
+ uint64_t val;
+
+ optlen = sizeof(val);
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_SOCKET + 1,
+ SO_VM_SOCKETS_BUFFER_SIZE,
+ &val, &optlen));
+ ASSERT_EQ(ENOPROTOOPT, errno);
+ ASSERT_EQ(sizeof(val), optlen);
+}
+
+TEST_F(vsock, bad_optname)
+{
+ socklen_t optlen;
+ uint64_t val;
+
+ optlen = sizeof(val);
+
+ ASSERT_EQ(-1, getsockopt(self->fd, AF_VSOCK, 0x7fff,
+ &val, &optlen));
+ ASSERT_EQ(ENOPROTOOPT, errno);
+ ASSERT_EQ(sizeof(val), optlen);
+}
+
+/* SO_VM_SOCKETS_CONNECT_TIMEOUT_{NEW,OLD} return a sock_timeval-shaped
+ * payload, which is wider than u64 on 64-bit. They exercise the path
+ * where the protocol's reported lv (16 bytes) is larger than the
+ * common 8-byte u64 case covered above.
+ */
+TEST_F(vsock, connect_timeout_new_exact)
+{
+ struct __kernel_sock_timeval tv = {};
+ socklen_t optlen;
+
+ optlen = sizeof(tv);
+
+ ASSERT_EQ(0, getsockopt(self->fd, AF_VSOCK,
+ SO_VM_SOCKETS_CONNECT_TIMEOUT_NEW,
+ &tv, &optlen));
+ ASSERT_EQ(sizeof(tv), optlen);
+}
+
+TEST_F(vsock, connect_timeout_new_oversize_clamped)
+{
+ char buf[sizeof(struct __kernel_sock_timeval) * 2] = {};
+ socklen_t optlen;
+
+ optlen = sizeof(buf);
+
+ ASSERT_EQ(0, getsockopt(self->fd, AF_VSOCK,
+ SO_VM_SOCKETS_CONNECT_TIMEOUT_NEW,
+ buf, &optlen));
+ ASSERT_EQ(sizeof(struct __kernel_sock_timeval), optlen);
+}
+
+TEST_F(vsock, connect_timeout_new_undersize)
+{
+ socklen_t optlen;
+ uint64_t val;
+
+ optlen = sizeof(val);
+
+ ASSERT_EQ(-1, getsockopt(self->fd, AF_VSOCK,
+ SO_VM_SOCKETS_CONNECT_TIMEOUT_NEW,
+ &val, &optlen));
+ ASSERT_EQ(EINVAL, errno);
+ ASSERT_EQ(sizeof(val), optlen);
+}
+
+TEST_F(vsock, connect_timeout_old_exact)
+{
+ struct __kernel_old_timeval tv = {};
+ socklen_t optlen;
+
+ optlen = sizeof(tv);
+
+ ASSERT_EQ(0, getsockopt(self->fd, AF_VSOCK,
+ SO_VM_SOCKETS_CONNECT_TIMEOUT_OLD,
+ &tv, &optlen));
+ ASSERT_EQ(sizeof(tv), optlen);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c
index 11b16ae5f0e8..7a333155de1a 100644
--- a/tools/testing/selftests/net/lib/gro.c
+++ b/tools/testing/selftests/net/lib/gro.c
@@ -67,12 +67,14 @@
#include <errno.h>
#include <error.h>
#include <getopt.h>
+#include <net/ethernet.h>
+#include <net/if.h>
#include <linux/filter.h>
#include <linux/if_packet.h>
+#include <linux/if_pppox.h>
#include <linux/ipv6.h>
#include <linux/net_tstamp.h>
-#include <net/ethernet.h>
-#include <net/if.h>
+#include <linux/ppp_defs.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
@@ -102,9 +104,12 @@
#define MAX_LARGE_PKT_CNT ((IP_MAXPACKET - (MAX_HDR_LEN - ETH_HLEN)) / \
(ASSUMED_MTU - (MAX_HDR_LEN - ETH_HLEN)))
#define MIN_EXTHDR_SIZE 8
+#define L2_HLEN_MAX (ETH_HLEN + PPPOE_SES_HLEN)
#define EXT_PAYLOAD_1 "\x00\x00\x00\x00\x00\x00"
#define EXT_PAYLOAD_2 "\x11\x11\x11\x11\x11\x11"
+#define EXIT_OVER_COALESCE 42
+
#define ipv6_optlen(p) (((p)->hdrlen+1) << 3) /* calculate IPv6 extension header len */
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
@@ -134,6 +139,7 @@ static int total_hdr_len = -1;
static int ethhdr_proto = -1;
static bool ipip;
static bool ip6ip6;
+static bool pppoe;
static uint64_t txtime_ns;
static int num_flows = 4;
static bool order_check;
@@ -171,6 +177,22 @@ static void vlog(const char *fmt, ...)
}
}
+static void fill_pppoelayer(void *buf, int payload_len, uint16_t sid)
+{
+ struct pppoe_ppp_hdr {
+ struct pppoe_hdr eh;
+ __be16 proto;
+ } *ph = buf;
+
+ payload_len += sizeof(struct tcphdr);
+ ph->eh.type = 1;
+ ph->eh.ver = 1;
+ ph->eh.code = 0;
+ ph->eh.sid = htons(sid);
+ ph->eh.length = htons(payload_len + sizeof(ph->proto));
+ ph->proto = htons(proto == PF_INET ? PPP_IP : PPP_IPV6);
+}
+
static void setup_sock_filter(int fd)
{
const int dport_off = tcp_offset + offsetof(struct tcphdr, dest);
@@ -412,11 +434,15 @@ static void create_packet(void *buf, int seq_offset, int ack_offset,
fill_networklayer(buf + inner_ip_off, payload_len, IPPROTO_TCP);
if (inner_ip_off > ETH_HLEN) {
- int encap_proto = (proto == PF_INET) ?
- IPPROTO_IPIP : IPPROTO_IPV6;
+ if (pppoe) {
+ fill_pppoelayer(buf + ETH_HLEN, payload_len + ip_hdr_len, 0x1234);
+ } else {
+ int encap_proto = (proto == PF_INET) ?
+ IPPROTO_IPIP : IPPROTO_IPV6;
- fill_networklayer(buf + ETH_HLEN,
- payload_len + ip_hdr_len, encap_proto);
+ fill_networklayer(buf + ETH_HLEN,
+ payload_len + ip_hdr_len, encap_proto);
+ }
}
fill_datalinklayer(buf);
@@ -526,7 +552,7 @@ static void send_flags(int fd, struct sockaddr_ll *daddr, int psh, int syn,
static void send_data_pkts(int fd, struct sockaddr_ll *daddr,
int payload_len1, int payload_len2)
{
- static char buf[ETH_HLEN + IP_MAXPACKET];
+ static char buf[L2_HLEN_MAX + IP_MAXPACKET];
create_packet(buf, 0, 0, payload_len1, 0);
write_packet(fd, buf, total_hdr_len + payload_len1, daddr);
@@ -1071,6 +1097,20 @@ static void send_fragment6(int fd, struct sockaddr_ll *daddr)
write_packet(fd, buf, bufpkt_len, daddr);
}
+static void send_changed_pppoe_sid(int fd, struct sockaddr_ll *daddr)
+{
+ static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
+ int pkt_size = total_hdr_len + PAYLOAD_LEN;
+ struct pppoe_hdr *hdr = (struct pppoe_hdr *)(buf + ETH_HLEN);
+
+ create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
+ write_packet(fd, buf, pkt_size, daddr);
+
+ create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
+ hdr->sid = htons(0x4321);
+ write_packet(fd, buf, pkt_size, daddr);
+}
+
static void bind_packetsocket(int fd)
{
struct sockaddr_ll daddr = {};
@@ -1121,11 +1161,14 @@ static void recv_error(int fd, int rcv_errno)
static void check_recv_pkts(int fd, int *correct_payload,
int correct_num_pkts)
{
- static char buffer[IP_MAXPACKET + ETH_HLEN + 1];
- struct iphdr *iph = (struct iphdr *)(buffer + ETH_HLEN);
- struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + ETH_HLEN);
+ static char buffer[IP_MAXPACKET + L2_HLEN_MAX + 1];
+ int nhoff = ETH_HLEN + (pppoe ? PPPOE_SES_HLEN : 0);
+ struct iphdr *iph = (struct iphdr *)(buffer + nhoff);
+ struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + nhoff);
struct tcphdr *tcph;
bool bad_packet = false;
+ int bytes_expected = 0;
+ int bytes_received = 0;
int tcp_ext_len = 0;
int ip_ext_len = 0;
int pkt_size = -1;
@@ -1134,13 +1177,15 @@ static void check_recv_pkts(int fd, int *correct_payload,
int i;
vlog("Expected {");
- for (i = 0; i < correct_num_pkts; i++)
+ for (i = 0; i < correct_num_pkts; i++) {
vlog("%d ", correct_payload[i]);
+ bytes_expected += correct_payload[i];
+ }
vlog("}, Total %d packets\nReceived {", correct_num_pkts);
while (1) {
ip_ext_len = 0;
- pkt_size = recv(fd, buffer, IP_MAXPACKET + ETH_HLEN + 1, 0);
+ pkt_size = recv(fd, buffer, sizeof(buffer), 0);
if (pkt_size < 0)
recv_error(fd, errno);
@@ -1170,9 +1215,17 @@ static void check_recv_pkts(int fd, int *correct_payload,
vlog("[!=%d]", correct_payload[num_pkt]);
bad_packet = true;
}
+ bytes_received += data_len;
num_pkt++;
}
vlog("}, Total %d packets.\n", num_pkt);
+ /* Signal over-coalescing explicitly, it's a hard failure, unlike
+ * under-coalescing which could be timing- or loss-related.
+ */
+ if (num_pkt < correct_num_pkts && bytes_received == bytes_expected)
+ error(EXIT_OVER_COALESCE, 0,
+ "over-coalesced: got %d pkts vs expected %d (%d B)",
+ num_pkt, correct_num_pkts, bytes_received);
if (num_pkt != correct_num_pkts)
error(1, 0, "incorrect number of packets");
if (bad_packet)
@@ -1183,9 +1236,10 @@ static void check_recv_pkts(int fd, int *correct_payload,
static void check_capacity_pkts(int fd)
{
- static char buffer[IP_MAXPACKET + ETH_HLEN + 1];
- struct iphdr *iph = (struct iphdr *)(buffer + ETH_HLEN);
- struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + ETH_HLEN);
+ static char buffer[IP_MAXPACKET + L2_HLEN_MAX + 1];
+ int nhoff = ETH_HLEN + (pppoe ? PPPOE_SES_HLEN : 0);
+ struct iphdr *iph = (struct iphdr *)(buffer + nhoff);
+ struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + nhoff);
int num_pkt = 0, num_coal = 0, pkt_idx;
const char *fail_reason = NULL;
int flow_order[num_flows * 2];
@@ -1203,7 +1257,7 @@ static void check_capacity_pkts(int fd)
while (1) {
ip_ext_len = 0;
- pkt_size = recv(fd, buffer, IP_MAXPACKET + ETH_HLEN + 1, 0);
+ pkt_size = recv(fd, buffer, sizeof(buffer), 0);
if (pkt_size < 0)
recv_error(fd, errno);
@@ -1499,6 +1553,12 @@ static void gro_sender(void)
usleep(fin_delay_us);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
+ /* PPPoE sub-tests */
+ } else if (strcmp(testname, "pppoe_sid") == 0) {
+ send_changed_pppoe_sid(txfd, &daddr);
+ usleep(fin_delay_us);
+ write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
+
} else {
error(1, 0, "Unknown testcase: %s", testname);
}
@@ -1716,6 +1776,12 @@ static void gro_receiver(void)
} else if (strcmp(testname, "capacity") == 0) {
check_capacity_pkts(rxfd);
+ } else if (strcmp(testname, "pppoe_sid") == 0) {
+ correct_payload[0] = PAYLOAD_LEN;
+ correct_payload[1] = PAYLOAD_LEN;
+ printf("different PPPoE session ID doesn't coalesce: ");
+ check_recv_pkts(rxfd, correct_payload, 2);
+
} else {
error(1, 0, "Test case error: unknown testname %s", testname);
}
@@ -1734,6 +1800,8 @@ static void parse_args(int argc, char **argv)
{ "ipv6", no_argument, NULL, '6' },
{ "ipip", no_argument, NULL, 'e' },
{ "ip6ip6", no_argument, NULL, 'E' },
+ { "pppoev4", no_argument, NULL, 'p' },
+ { "pppoev6", no_argument, NULL, 'P' },
{ "num-flows", required_argument, NULL, 'n' },
{ "rx", no_argument, NULL, 'r' },
{ "saddr", required_argument, NULL, 's' },
@@ -1745,7 +1813,7 @@ static void parse_args(int argc, char **argv)
};
int c;
- while ((c = getopt_long(argc, argv, "46d:D:eEi:n:rs:S:t:ov", opts, NULL)) != -1) {
+ while ((c = getopt_long(argc, argv, "46d:D:eEi:n:pPrs:S:t:ov", opts, NULL)) != -1) {
switch (c) {
case '4':
proto = PF_INET;
@@ -1765,6 +1833,16 @@ static void parse_args(int argc, char **argv)
proto = PF_INET6;
ethhdr_proto = htons(ETH_P_IPV6);
break;
+ case 'p':
+ pppoe = true;
+ proto = PF_INET;
+ ethhdr_proto = htons(ETH_P_PPP_SES);
+ break;
+ case 'P':
+ pppoe = true;
+ proto = PF_INET6;
+ ethhdr_proto = htons(ETH_P_PPP_SES);
+ break;
case 'd':
addr4_dst = addr6_dst = optarg;
break;
@@ -1812,6 +1890,10 @@ int main(int argc, char **argv)
} else if (ip6ip6) {
tcp_offset = ETH_HLEN + sizeof(struct ipv6hdr) * 2;
total_hdr_len = tcp_offset + sizeof(struct tcphdr);
+ } else if (pppoe) {
+ tcp_offset = ETH_HLEN + PPPOE_SES_HLEN +
+ (proto == PF_INET ? sizeof(struct iphdr) : sizeof(struct ipv6hdr));
+ total_hdr_len = tcp_offset + sizeof(struct tcphdr);
} else if (proto == PF_INET) {
tcp_offset = ETH_HLEN + sizeof(struct iphdr);
total_hdr_len = tcp_offset + sizeof(struct tcphdr);
diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
index 7c81d86a7e97..e58bdbdc58ee 100644
--- a/tools/testing/selftests/net/lib/py/__init__.py
+++ b/tools/testing/selftests/net/lib/py/__init__.py
@@ -10,11 +10,11 @@ from .ksft import KsftFailEx, KsftSkipEx, KsftXfailEx, ksft_pr, ksft_eq, \
ksft_ge, ksft_gt, ksft_lt, ksft_raises, ksft_busy_wait, \
ktap_result, ksft_disruptive, ksft_setup, ksft_run, ksft_exit, \
ksft_variants, KsftNamedVariant
-from .netns import NetNS, NetNSEnter
+from .netns import NetNS, NetNSEnter, UserNetNS
from .nsim import NetdevSim, NetdevSimDev
from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
bpftool, ip, ethtool, bpftrace, rand_port, rand_ports, wait_port_listen, \
- wait_file, tool
+ wait_file, tool, tc
from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
from .ynl import NlError, NlctrlFamily, YnlFamily, \
EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily
@@ -26,10 +26,10 @@ __all__ = ["KSRC",
"ksft_is", "ksft_ge", "ksft_gt", "ksft_lt", "ksft_raises",
"ksft_busy_wait", "ktap_result", "ksft_disruptive", "ksft_setup",
"ksft_run", "ksft_exit", "ksft_variants", "KsftNamedVariant",
- "NetNS", "NetNSEnter",
+ "NetNS", "NetNSEnter", "UserNetNS",
"CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer",
"bpftool", "ip", "ethtool", "bpftrace", "rand_port", "rand_ports",
- "wait_port_listen", "wait_file", "tool",
+ "wait_port_listen", "wait_file", "tool", "tc",
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
"NetdevSim", "NetdevSimDev",
"NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
diff --git a/tools/testing/selftests/net/lib/py/netns.py b/tools/testing/selftests/net/lib/py/netns.py
index 8e9317044eef..526f6aa80077 100644
--- a/tools/testing/selftests/net/lib/py/netns.py
+++ b/tools/testing/selftests/net/lib/py/netns.py
@@ -1,9 +1,14 @@
# SPDX-License-Identifier: GPL-2.0
-from .utils import ip
import ctypes
+import os
import random
import string
+import subprocess
+import time
+from pathlib import Path
+
+from .utils import ip
libc = ctypes.cdll.LoadLibrary('libc.so.6')
@@ -34,6 +39,74 @@ class NetNS:
return f"NetNS({self.name})"
+class UserNetNS:
+ """Network namespace owned by a non-init user namespace."""
+
+ def __init__(self):
+ self.name = ''.join(
+ random.choice(string.ascii_lowercase) for _ in range(8))
+ self.user_ns_path = f"/run/userns/{self.name}"
+ self.net_ns_path = f"/run/netns/{self.name}"
+ self._user_mounted = False
+ self._net_mounted = False
+
+ os.makedirs("/run/userns", exist_ok=True)
+ os.makedirs("/run/netns", exist_ok=True)
+
+ Path(self.user_ns_path).touch()
+ Path(self.net_ns_path).touch()
+
+ with subprocess.Popen(
+ ["unshare", "--user", "--net", "--map-root-user",
+ "sleep", "infinity"]) as proc:
+ try:
+ pid = proc.pid
+ init_user = os.readlink("/proc/self/ns/user")
+ for _ in range(200):
+ try:
+ if os.readlink(f"/proc/{pid}/ns/user") != init_user:
+ break
+ except OSError:
+ pass
+ time.sleep(0.01)
+ else:
+ raise RuntimeError("unshare child did not create userns")
+
+ subprocess.run(["mount", "--bind", f"/proc/{pid}/ns/user",
+ self.user_ns_path], check=True)
+ self._user_mounted = True
+ subprocess.run(["mount", "--bind", f"/proc/{pid}/ns/net",
+ self.net_ns_path], check=True)
+ self._net_mounted = True
+ finally:
+ proc.kill()
+
+ def __del__(self):
+ if self._net_mounted:
+ subprocess.run(["umount", self.net_ns_path], check=False)
+ self._net_mounted = False
+ if self._user_mounted:
+ subprocess.run(["umount", self.user_ns_path], check=False)
+ self._user_mounted = False
+ for path in (self.net_ns_path, self.user_ns_path):
+ try:
+ os.unlink(path)
+ except OSError:
+ pass
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, ex_type, ex_value, ex_tb):
+ self.__del__()
+
+ def __str__(self):
+ return self.name
+
+ def __repr__(self):
+ return f"UserNetNS({self.name})"
+
+
class NetNSEnter:
def __init__(self, ns_name):
self.ns_path = f"/run/netns/{ns_name}"
diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py
index 6c44a3d2bbf7..87eae79d01c1 100644
--- a/tools/testing/selftests/net/lib/py/utils.py
+++ b/tools/testing/selftests/net/lib/py/utils.py
@@ -23,6 +23,10 @@ class CmdExitFailure(Exception):
self.cmd = cmd_obj
+class CmdExitZeroFailure(CmdExitFailure):
+ """ Command succeeded (returned zero exit code), but expected failure. """
+
+
def fd_read_timeout(fd, timeout):
rlist, _, _ = select.select([fd], [], [], timeout)
if rlist:
@@ -39,10 +43,16 @@ class cmd:
Use bkg() instead to run a command in the background.
"""
- def __init__(self, comm, shell=None, fail=True, ns=None, background=False,
- host=None, timeout=5, ksft_ready=None, ksft_wait=None):
+ def __init__(self, comm, shell=None, fail=True, expect_fail=False, ns=None,
+ background=False, host=None, timeout=5, ksft_ready=None,
+ ksft_wait=None):
if ns:
- comm = f'ip netns exec {ns} ' + comm
+ if hasattr(ns, 'user_ns_path'):
+ comm = (f'nsenter --user={ns.user_ns_path} '
+ f'--net={ns.net_ns_path} --setuid=0 --setgid=0 -- '
+ + comm)
+ else:
+ comm = f'ip netns exec {ns} ' + comm
self.stdout = None
self.stderr = None
@@ -88,7 +98,8 @@ class cmd:
self._process_terminate(terminate=terminate, timeout=1)
raise CmdInitFailure("Did not receive ready message", self)
if not background:
- self.process(terminate=False, fail=fail, timeout=timeout)
+ self.process(terminate=False, fail=fail, expect_fail=expect_fail,
+ timeout=timeout)
def _process_terminate(self, terminate, timeout):
if terminate:
@@ -102,7 +113,7 @@ class cmd:
return stdout, stderr
- def process(self, terminate=True, fail=None, timeout=5):
+ def process(self, terminate=True, fail=None, expect_fail=False, timeout=5):
if fail is None:
fail = not terminate
@@ -111,10 +122,19 @@ class cmd:
stdout, stderr = self._process_terminate(terminate=terminate,
timeout=timeout)
- if self.proc.returncode != 0 and fail:
+
+ # Fail on unexpected test failure if fail.
+ # Fail on unexpected test success if expect_fail.
+ # Fail on negative returncode if either:
+ # Set by subprocess on crash or signal, this is never expected failure.
+ if (self.proc.returncode != 0 and fail or
+ (self.proc.returncode < 0 and expect_fail)):
if len(stderr) > 0 and stderr[-1] == "\n":
stderr = stderr[:-1]
raise CmdExitFailure("Command failed", self)
+ elif self.proc.returncode == 0 and expect_fail:
+ raise CmdExitZeroFailure("Command succeeded (expected fail)", self)
+
def __repr__(self):
def str_fmt(name, s):
@@ -157,14 +177,17 @@ class bkg(cmd):
with bkg("my_binary", ksft_wait=5):
"""
- def __init__(self, comm, shell=None, fail=None, ns=None, host=None,
- exit_wait=False, ksft_ready=None, ksft_wait=None):
+ def __init__(self, comm, shell=None, fail=None, expect_fail=None,
+ ns=None, host=None, exit_wait=False, ksft_ready=None,
+ ksft_wait=None):
super().__init__(comm, background=True,
- shell=shell, fail=fail, ns=ns, host=host,
- ksft_ready=ksft_ready, ksft_wait=ksft_wait)
+ shell=shell, fail=fail, expect_fail=expect_fail,
+ ns=ns, host=host, ksft_ready=ksft_ready,
+ ksft_wait=ksft_wait)
self.terminate = not exit_wait and not ksft_wait
self._exit_wait = exit_wait
self.check_fail = fail
+ self.expect_fail = expect_fail
if shell and self.terminate:
print("# Warning: combining shell and terminate is risky!")
@@ -179,7 +202,8 @@ class bkg(cmd):
# since forcing termination silences failures with fail=None
if self.proc.poll() is None:
terminate = terminate or (self._exit_wait and ex_type is not None)
- return self.process(terminate=terminate, fail=self.check_fail)
+ return self.process(terminate=terminate, fail=self.check_fail,
+ expect_fail=self.expect_fail)
GLOBAL_DEFER_QUEUE = []
@@ -220,7 +244,10 @@ class defer:
def tool(name, args, json=None, ns=None, host=None):
cmd_str = name + ' '
if json:
- cmd_str += '--json '
+ if name == 'tc':
+ cmd_str += '-json '
+ else:
+ cmd_str += '--json '
cmd_str += args
cmd_obj = cmd(cmd_str, ns=ns, host=host)
if json:
@@ -238,6 +265,13 @@ def ip(args, json=None, ns=None, host=None):
return tool('ip', args, json=json, host=host)
+def tc(args, json=None, ns=None, host=None):
+ """ Helper to call tc with standard set of optional args. """
+ if ns:
+ args = f'-netns {ns} ' + args
+ return tool('tc', args, json=json, host=host)
+
+
def ethtool(args, json=None, ns=None, host=None):
return tool('ethtool', args, json=json, ns=ns, host=host)
diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh
index 4b3f71e66609..c0aeffd5cb71 100755
--- a/tools/testing/selftests/net/mptcp/mptcp_join.sh
+++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh
@@ -63,6 +63,7 @@ unset fastclose
unset fullmesh
unset speed
unset bind_addr
+unset ifaces_nr
unset join_syn_rej
unset join_csum_ns1
unset join_csum_ns2
@@ -86,6 +87,10 @@ unset fb_mpc_data
unset fb_md5_sig
unset fb_dss
+unset add_addr_tx_nr
+unset add_addr_echo_tx_nr
+unset add_addr_drop_tx_nr
+
# generated using "nfbpf_compile '(ip && (ip[54] & 0xf0) == 0x30) ||
# (ip6 && (ip6[74] & 0xf0) == 0x30)'"
CBPF_MPTCP_SUBOPTION_ADD_ADDR="14,
@@ -146,7 +151,7 @@ init_partial()
# ns1eth4 ns2eth4
local i
- for i in $(seq 1 4); do
+ for i in $(seq 1 "${ifaces_nr:-4}"); do
ip link add ns1eth$i netns "$ns1" type veth peer name ns2eth$i netns "$ns2"
ip -net "$ns1" addr add 10.0.$i.1/24 dev ns1eth$i
ip -net "$ns1" addr add dead:beef:$i::1/64 dev ns1eth$i nodad
@@ -165,7 +170,7 @@ init_partial()
init_shapers()
{
local i
- for i in $(seq 1 4); do
+ for i in $(seq 1 "${ifaces_nr:-4}"); do
tc -n $ns1 qdisc add dev ns1eth$i root netem rate 20mbit delay 1ms
tc -n $ns2 qdisc add dev ns2eth$i root netem rate 20mbit delay 1ms
done
@@ -512,6 +517,19 @@ reset_with_tcp_filter()
fi
}
+# For kernel supporting limits above 8
+# $1: title ; $2,4: addrs limit ns1,2 ; $3,5: subflows limit ns1,2
+reset_with_high_limits()
+{
+ reset "${1}" || return 1
+
+ if ! pm_nl_set_limits "${ns1}" "${2}" "${3}" 2>/dev/null ||
+ ! pm_nl_set_limits "${ns2}" "${4}" "${5}" 2>/dev/null; then
+ mark_as_skipped "unable to set the limits to ${*:2}"
+ return 1
+ fi
+}
+
# $1: err msg
fail_test()
{
@@ -1696,6 +1714,9 @@ chk_add_nr()
local ack_nr=$port_nr
local mis_syn_nr=0
local mis_ack_nr=0
+ local add_tx_nr=${add_addr_tx_nr:-${add_nr}}
+ local echo_tx_nr=${add_addr_echo_tx_nr:-${echo_nr}}
+ local drop_tx_nr=${add_addr_drop_tx_nr:-0}
local ns_tx=$ns1
local ns_rx=$ns2
local tx=""
@@ -1797,50 +1818,25 @@ chk_add_nr()
print_ok
fi
fi
-}
-
-chk_add_tx_nr()
-{
- local add_tx_nr=$1
- local echo_tx_nr=$2
- local count
- print_check "add addr tx"
- count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtAddAddrTx")
- if [ -z "$count" ]; then
- print_skip
+ count=$(mptcp_lib_get_counter ${ns_tx} "MPTcpExtAddAddrTx")
# Tolerate more ADD_ADDR then expected (if any), due to retransmissions
- elif [ "$count" != "$add_tx_nr" ] &&
- { [ "$add_tx_nr" -eq 0 ] || [ "$count" -lt "$add_tx_nr" ]; }; then
+ if [ -n "$count" ] && [ "$count" != "$add_tx_nr" ] &&
+ { [ "$add_tx_nr" -eq 0 ] || [ "$count" -lt "$add_tx_nr" ]; }; then
+ print_check "add addr tx"
fail_test "got $count ADD_ADDR[s] TX, expected $add_tx_nr"
- else
- print_ok
fi
- print_check "add addr echo tx"
- count=$(mptcp_lib_get_counter ${ns2} "MPTcpExtEchoAddTx")
- if [ -z "$count" ]; then
- print_skip
- elif [ "$count" != "$echo_tx_nr" ]; then
+ count=$(mptcp_lib_get_counter ${ns_rx} "MPTcpExtEchoAddTx")
+ if [ -n "$count" ] && [ "$count" != "$echo_tx_nr" ]; then
+ print_check "add addr echo tx"
fail_test "got $count ADD_ADDR echo[s] TX, expected $echo_tx_nr"
- else
- print_ok
fi
-}
-
-chk_add_drop_tx_nr()
-{
- local drop_tx_nr=$1
- local count
- print_check "add addr tx drop"
- count=$(mptcp_lib_get_counter ${ns1} "MPTcpExtAddAddrTxDrop")
- if [ -z "$count" ]; then
- print_skip
- elif [ "$count" != "$drop_tx_nr" ]; then
+ count=$(mptcp_lib_get_counter ${ns_tx} "MPTcpExtAddAddrTxDrop")
+ if [ -n "$count" ] && [ "$count" != "$drop_tx_nr" ]; then
+ print_check "add addr tx drop"
fail_test "got $count ADD_ADDR drop[s] TX, expected $drop_tx_nr"
- else
- print_ok
fi
}
@@ -2253,7 +2249,6 @@ signal_address_tests()
pm_nl_add_endpoint $ns1 10.0.2.1 flags signal
run_tests $ns1 $ns2 10.0.1.1
chk_join_nr 0 0 0
- chk_add_tx_nr 1 1
chk_add_nr 1 1
fi
@@ -2531,8 +2526,8 @@ add_addr_timeout_tests()
speed=slow \
run_tests $ns1 $ns2 10.0.1.1
chk_join_nr 1 1 1
- chk_add_tx_nr 4 4
- chk_add_nr 4 0
+ add_addr_echo_tx_nr=4 \
+ chk_add_nr 4 0
fi
# add_addr timeout IPv6
@@ -2543,7 +2538,8 @@ add_addr_timeout_tests()
speed=slow \
run_tests $ns1 $ns2 dead:beef:1::1
chk_join_nr 1 1 1
- chk_add_nr 4 0
+ add_addr_echo_tx_nr=4 \
+ chk_add_nr 4 0
fi
# signal addresses timeout
@@ -2555,7 +2551,8 @@ add_addr_timeout_tests()
speed=10 \
run_tests $ns1 $ns2 10.0.1.1
chk_join_nr 2 2 2
- chk_add_nr 8 0
+ add_addr_echo_tx_nr=8 \
+ chk_add_nr 8 0
fi
# signal invalid addresses timeout
@@ -2568,7 +2565,8 @@ add_addr_timeout_tests()
run_tests $ns1 $ns2 10.0.1.1
join_syn_tx=2 \
chk_join_nr 1 1 1
- chk_add_nr 8 0
+ add_addr_echo_tx_nr=7 \
+ chk_add_nr 8 0
fi
}
@@ -3200,6 +3198,17 @@ add_addr_ports_tests()
chk_add_nr 1 1 1
fi
+ # signal address v6 with port
+ if reset "signal address v6 with port" &&
+ continue_if mptcp_lib_has_file '/proc/sys/net/mptcp/add_addr_v6_port_drop_ts'; then
+ pm_nl_set_limits $ns1 0 1
+ pm_nl_set_limits $ns2 1 1
+ pm_nl_add_endpoint $ns1 dead:beef:2::1 flags signal port 10100
+ run_tests $ns1 $ns2 dead:beef:1::1
+ chk_join_nr 1 1 1
+ chk_add_nr 1 1 1
+ fi
+
# subflow and signal with port
if reset "subflow and signal with port"; then
pm_nl_add_endpoint $ns1 10.0.2.1 flags signal port 10100
@@ -3299,15 +3308,15 @@ add_addr_ports_tests()
if reset "signal addr list progresses after tx drop"; then
pm_nl_set_limits $ns1 0 2
pm_nl_set_limits $ns2 1 0
+ ip netns exec $ns1 sysctl -q net.mptcp.add_addr_v6_port_drop_ts=0 2>/dev/null || true
ip netns exec $ns1 sysctl -q net.ipv4.tcp_timestamps=1
ip netns exec $ns2 sysctl -q net.ipv4.tcp_timestamps=1
pm_nl_add_endpoint $ns1 dead:beef:2::1 flags signal port 10100
pm_nl_add_endpoint $ns1 dead:beef:3::1 flags signal
run_tests $ns1 $ns2 dead:beef:1::1
- chk_add_drop_tx_nr 1
- chk_add_tx_nr 1 1
- chk_add_nr 1 1 0
+ add_addr_drop_tx_nr=1 \
+ chk_add_nr 1 1 0
fi
}
@@ -3700,6 +3709,21 @@ fullmesh_tests()
chk_prio_nr 0 1 1 0
chk_rm_nr 0 1
fi
+
+ # fullmesh in 8x8 to create 63 additional subflows
+ if ifaces_nr=8 reset_with_high_limits "fullmesh 8x8" 64 64 64 64; then
+ # higher chance to lose ADD_ADDR: allow retransmissions
+ ip netns exec $ns1 sysctl -q net.mptcp.add_addr_timeout=1
+ local i
+ for i in $(seq 1 8); do
+ pm_nl_add_endpoint $ns2 10.0.$i.2 flags subflow,fullmesh
+ pm_nl_add_endpoint $ns1 10.0.$i.1 flags signal
+ done
+ speed=slow \
+ run_tests $ns1 $ns2 10.0.1.1
+ chk_join_nr 63 63 63
+ fi
+
}
fastclose_tests()
diff --git a/tools/testing/selftests/net/mptcp/mptcp_sockopt.sh b/tools/testing/selftests/net/mptcp/mptcp_sockopt.sh
index ab8bce06b262..e850a87429b6 100755
--- a/tools/testing/selftests/net/mptcp/mptcp_sockopt.sh
+++ b/tools/testing/selftests/net/mptcp/mptcp_sockopt.sh
@@ -355,10 +355,10 @@ sin=$(mktemp)
sout=$(mktemp)
cin=$(mktemp)
cout=$(mktemp)
+trap cleanup EXIT
init
make_file "$cin" "client" 1
make_file "$sin" "server" 1
-trap cleanup EXIT
mptcp_lib_subtests_last_ts_reset
run_tests $ns1 $ns2 10.0.1.1
diff --git a/tools/testing/selftests/net/mptcp/pm_netlink.sh b/tools/testing/selftests/net/mptcp/pm_netlink.sh
index 04594dfc22b1..21bfe1311f11 100755
--- a/tools/testing/selftests/net/mptcp/pm_netlink.sh
+++ b/tools/testing/selftests/net/mptcp/pm_netlink.sh
@@ -66,6 +66,15 @@ get_limits() {
fi
}
+get_limits_nb() {
+ if mptcp_lib_is_ip_mptcp; then
+ ip -n "${ns1}" mptcp limits | awk '{ print $2" "$4 }'
+ else
+ ip netns exec "${ns1}" ./pm_nl_ctl limits | \
+ awk '{ printf "%s ", $2 }'
+ fi
+}
+
format_endpoints() {
mptcp_lib_pm_nl_format_endpoints "${@}"
}
@@ -164,6 +173,7 @@ check "get_endpoint 2" "" "simple del addr" 1
check "show_endpoints" \
"$(format_endpoints "1,10.0.1.1" \
"3,10.0.1.3,signal backup")" "dump addrs after del"
+add_endpoint 10.0.1.2 id 2
add_endpoint 10.0.1.3 2>/dev/null
check "get_endpoint 4" "" "duplicate addr" 1
@@ -171,25 +181,29 @@ check "get_endpoint 4" "" "duplicate addr" 1
add_endpoint 10.0.1.4 flags signal
check "get_endpoint 4" "$(format_endpoints "4,10.0.1.4,signal")" "id addr increment"
-for i in $(seq 5 9); do
- add_endpoint "10.0.1.${i}" flags signal >/dev/null 2>&1
-done
-check "get_endpoint 9" "$(format_endpoints "9,10.0.1.9,signal")" "hard addr limit"
-check "get_endpoint 10" "" "above hard addr limit" 1
+read -r -a default_limits_nb <<< "$(get_limits_nb)"
+# limits have been increased: from 8 to 64 for subflows/add_addr & 255 for endp
+if mptcp_lib_expect_all_features || set_limits 9 9 2>/dev/null; then
+ max_endp=255
+ max_limits=64
+else
+ max_endp=8
+ max_limits=8
+fi
+set_limits "${default_limits_nb[@]}"
-del_endpoint 9
-for i in $(seq 10 255); do
- add_endpoint 10.0.0.9 id "${i}"
- del_endpoint "${i}"
+for i in $(seq 5 ${max_endp}); do
+ add_endpoint "10.0.0.${i}" id "${i}"
done
-check "show_endpoints" \
- "$(format_endpoints "1,10.0.1.1" \
- "3,10.0.1.3,signal backup" \
- "4,10.0.1.4,signal" \
- "5,10.0.1.5,signal" \
- "6,10.0.1.6,signal" \
- "7,10.0.1.7,signal" \
- "8,10.0.1.8,signal")" "id limit"
+check "get_endpoint ${max_endp}" \
+ "$(format_endpoints "${max_endp},10.0.0.${max_endp}")" "id limit"
+
+if add_endpoint '10.0.0.1' &>/dev/null; then
+ hardlimit="no error"
+else
+ hardlimit="error"
+fi
+check "echo ${hardlimit}" "error" "above hard addr limit"
flush_endpoint
check "show_endpoints" "" "flush addrs"
@@ -202,15 +216,15 @@ if ! mptcp_lib_is_ip_mptcp; then
flush_endpoint
fi
-set_limits 9 1 2>/dev/null
+set_limits $((max_limits + 1)) 1 2>/dev/null
check "get_limits" "${default_limits}" "rcv addrs above hard limit"
-set_limits 1 9 2>/dev/null
+set_limits 1 $((max_limits + 1)) 2>/dev/null
check "get_limits" "${default_limits}" "subflows above hard limit"
-set_limits 8 8
+set_limits ${max_limits} ${max_limits}
flush_endpoint ## to make sure it doesn't affect the limits
-check "get_limits" "$(format_limits 8 8)" "set limits"
+check "get_limits" "$(format_limits ${max_limits} ${max_limits})" "set limits"
flush_endpoint
add_endpoint 10.0.1.1
diff --git a/tools/testing/selftests/net/mptcp/pm_nl_ctl.c b/tools/testing/selftests/net/mptcp/pm_nl_ctl.c
index 99eecccbf0c8..78180da1efcc 100644
--- a/tools/testing/selftests/net/mptcp/pm_nl_ctl.c
+++ b/tools/testing/selftests/net/mptcp/pm_nl_ctl.c
@@ -217,8 +217,6 @@ static int capture_events(int fd, int event_group)
/* do a netlink command and, if max > 0, fetch the reply ; nh's size >1024B */
static int do_nl_req(int fd, struct nlmsghdr *nh, int len, int max)
{
- struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
- socklen_t addr_len;
void *data = nh;
int rem, ret;
int err = 0;
@@ -230,15 +228,15 @@ static int do_nl_req(int fd, struct nlmsghdr *nh, int len, int max)
}
nh->nlmsg_len = len;
- ret = sendto(fd, data, len, 0, (void *)&nladdr, sizeof(nladdr));
+ ret = send(fd, data, len, 0);
if (ret != len)
error(1, errno, "send netlink: %uB != %uB\n", ret, len);
- addr_len = sizeof(nladdr);
- rem = ret = recvfrom(fd, data, max, 0, (void *)&nladdr, &addr_len);
+ ret = recv(fd, data, max, 0);
if (ret < 0)
error(1, errno, "recv netlink: %uB\n", ret);
+ rem = ret;
/* Beware: the NLMSG_NEXT macro updates the 'rem' argument */
for (; NLMSG_OK(nh, rem); nh = NLMSG_NEXT(nh, rem)) {
if (nh->nlmsg_type == NLMSG_DONE)
diff --git a/tools/testing/selftests/net/mptcp/simult_flows.sh b/tools/testing/selftests/net/mptcp/simult_flows.sh
index d11a8b949aab..7b9aabe10170 100755
--- a/tools/testing/selftests/net/mptcp/simult_flows.sh
+++ b/tools/testing/selftests/net/mptcp/simult_flows.sh
@@ -76,13 +76,13 @@ setup()
ip -net "$ns1" addr add 10.0.1.1/24 dev ns1eth1
ip -net "$ns1" addr add dead:beef:1::1/64 dev ns1eth1 nodad
- ip -net "$ns1" link set ns1eth1 up mtu 1500
+ ip -net "$ns1" link set ns1eth1 up mtu 1500 gso_max_segs 0
ip -net "$ns1" route add default via 10.0.1.2
ip -net "$ns1" route add default via dead:beef:1::2
ip -net "$ns1" addr add 10.0.2.1/24 dev ns1eth2
ip -net "$ns1" addr add dead:beef:2::1/64 dev ns1eth2 nodad
- ip -net "$ns1" link set ns1eth2 up mtu 1500
+ ip -net "$ns1" link set ns1eth2 up mtu 1500 gso_max_segs 0
ip -net "$ns1" route add default via 10.0.2.2 metric 101
ip -net "$ns1" route add default via dead:beef:2::2 metric 101
@@ -91,21 +91,21 @@ setup()
ip -net "$ns2" addr add 10.0.1.2/24 dev ns2eth1
ip -net "$ns2" addr add dead:beef:1::2/64 dev ns2eth1 nodad
- ip -net "$ns2" link set ns2eth1 up mtu 1500
+ ip -net "$ns2" link set ns2eth1 up mtu 1500 gso_max_segs 0
ip -net "$ns2" addr add 10.0.2.2/24 dev ns2eth2
ip -net "$ns2" addr add dead:beef:2::2/64 dev ns2eth2 nodad
- ip -net "$ns2" link set ns2eth2 up mtu 1500
+ ip -net "$ns2" link set ns2eth2 up mtu 1500 gso_max_segs 0
ip -net "$ns2" addr add 10.0.3.2/24 dev ns2eth3
ip -net "$ns2" addr add dead:beef:3::2/64 dev ns2eth3 nodad
- ip -net "$ns2" link set ns2eth3 up mtu 1500
+ ip -net "$ns2" link set ns2eth3 up mtu 1500 gso_max_segs 0
ip netns exec "$ns2" sysctl -q net.ipv4.ip_forward=1
ip netns exec "$ns2" sysctl -q net.ipv6.conf.all.forwarding=1
ip -net "$ns3" addr add 10.0.3.3/24 dev ns3eth1
ip -net "$ns3" addr add dead:beef:3::3/64 dev ns3eth1 nodad
- ip -net "$ns3" link set ns3eth1 up mtu 1500
+ ip -net "$ns3" link set ns3eth1 up mtu 1500 gso_max_segs 0
ip -net "$ns3" route add default via 10.0.3.2
ip -net "$ns3" route add default via dead:beef:3::2
@@ -223,9 +223,11 @@ run_test()
local rate2=$2
local delay1=$3
local delay2=$4
+ local limit1=$5
+ local limit2=$6
local lret
local dev
- shift 4
+ shift 6
local msg=$*
[ $delay1 -gt 0 ] && delay1="delay ${delay1}ms" || delay1=""
@@ -240,10 +242,10 @@ run_test()
# keep the queued pkts number low, or the RTT estimator will see
# increasing latency over time.
- tc -n $ns1 qdisc add dev ns1eth1 root netem rate ${rate1}mbit $delay1 limit 50
- tc -n $ns1 qdisc add dev ns1eth2 root netem rate ${rate2}mbit $delay2 limit 50
- tc -n $ns2 qdisc add dev ns2eth1 root netem rate ${rate1}mbit $delay1 limit 50
- tc -n $ns2 qdisc add dev ns2eth2 root netem rate ${rate2}mbit $delay2 limit 50
+ tc -n $ns1 qdisc add dev ns1eth1 root netem rate ${rate1}mbit $delay1 limit ${limit1}
+ tc -n $ns1 qdisc add dev ns1eth2 root netem rate ${rate2}mbit $delay2 limit ${limit2}
+ tc -n $ns2 qdisc add dev ns2eth1 root netem rate ${rate1}mbit $delay1 limit ${limit1}
+ tc -n $ns2 qdisc add dev ns2eth2 root netem rate ${rate2}mbit $delay2 limit ${limit2}
# time is measured in ms, account for transfer size, aggregated link speed
# and header overhead (10%)
@@ -301,13 +303,13 @@ done
setup
mptcp_lib_subtests_last_ts_reset
-run_test 10 10 0 0 "balanced bwidth"
-run_test 10 10 1 25 "balanced bwidth with unbalanced delay"
+run_test 10 10 0 0 20 20 "balanced bwidth"
+run_test 10 10 1 25 20 50 "balanced bwidth with unbalanced delay"
# we still need some additional infrastructure to pass the following test-cases
-MPTCP_LIB_SUBTEST_FLAKY=1 run_test 10 3 0 0 "unbalanced bwidth"
-run_test 10 3 1 25 "unbalanced bwidth with unbalanced delay"
-run_test 10 3 25 1 "unbalanced bwidth with opposed, unbalanced delay"
+MPTCP_LIB_SUBTEST_FLAKY=1 run_test 10 3 0 0 30 20 "unbalanced bwidth"
+run_test 10 3 1 25 40 30 "unbalanced bwidth with unbalanced delay"
+run_test 10 3 25 1 50 30 "unbalanced bwidth with opposed, unbalanced delay"
mptcp_lib_result_print_all_tap
exit $ret
diff --git a/tools/testing/selftests/net/netfilter/Makefile b/tools/testing/selftests/net/netfilter/Makefile
index d953ee218c0f..f88dd4ef8d26 100644
--- a/tools/testing/selftests/net/netfilter/Makefile
+++ b/tools/testing/selftests/net/netfilter/Makefile
@@ -32,6 +32,7 @@ TEST_PROGS := \
nft_meta.sh \
nft_nat.sh \
nft_nat_zones.sh \
+ nft_offload.sh \
nft_queue.sh \
nft_synproxy.sh \
nft_tproxy_tcp.sh \
diff --git a/tools/testing/selftests/net/netfilter/config b/tools/testing/selftests/net/netfilter/config
index 979cff56e1f5..c3c121b6f300 100644
--- a/tools/testing/selftests/net/netfilter/config
+++ b/tools/testing/selftests/net/netfilter/config
@@ -11,7 +11,12 @@ CONFIG_BRIDGE_NF_EBTABLES_LEGACY=m
CONFIG_BRIDGE_VLAN_FILTERING=y
CONFIG_CGROUP_BPF=y
CONFIG_CRYPTO_SHA1=m
+CONFIG_DEBUG_FS=y
CONFIG_DUMMY=m
+CONFIG_FAIL_FUNCTION=y
+CONFIG_FAULT_INJECTION=y
+CONFIG_FAULT_INJECTION_DEBUG_FS=y
+CONFIG_FUNCTION_ERROR_INJECTION=y
CONFIG_INET_DIAG=m
CONFIG_INET_ESP=m
CONFIG_INET_SCTP_DIAG=m
@@ -36,6 +41,7 @@ CONFIG_IP_VS_RR=m
CONFIG_MACVLAN=m
CONFIG_NAMESPACES=y
CONFIG_NET_CLS_U32=m
+CONFIG_NETDEVSIM=m
CONFIG_NETFILTER=y
CONFIG_NETFILTER_ADVANCED=y
CONFIG_NETFILTER_NETLINK=m
diff --git a/tools/testing/selftests/net/netfilter/nft_offload.sh b/tools/testing/selftests/net/netfilter/nft_offload.sh
new file mode 100755
index 000000000000..859bdedf1a51
--- /dev/null
+++ b/tools/testing/selftests/net/netfilter/nft_offload.sh
@@ -0,0 +1,132 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+source lib.sh
+
+checktool "nft --version" "run test without nft tool"
+modprobe -q netdevsim
+
+sysfs="/sys/kernel/debug/fail_function"
+failname="/proc/self/make-it-fail"
+duration=30
+fault=0
+ret=0
+file_ft=""
+file_rs=""
+id=$((RANDOM%65536))
+
+read -r t < /proc/sys/kernel/tainted
+if [ "$t" -ne 0 ];then
+ echo SKIP: kernel is tainted
+ exit $ksft_skip
+fi
+
+cleanup() {
+ cleanup_netdevsim "$id" "$NS"
+ cleanup_ns "$NS"
+ [ "$fault" -eq 1 ] && echo '!nsim_setup_tc' > "$sysfs/inject"
+ rm -f "$file_ft" "$file_rs"
+}
+trap cleanup EXIT
+
+skip() {
+ echo "SKIP: $*"
+ [ $ret -eq 0 ] && exit 4
+
+ exit $ret
+}
+
+set -e
+setup_ns NS
+
+create_netdevsim "$id" "$NS" >/dev/null
+nsim_port=$(create_netdevsim_port "$id" "$NS" 2)
+
+file_ft=$(mktemp)
+cat > "$file_ft" <<EOF
+flush ruleset
+table inet t {
+ flowtable f {
+ flags offload
+ hook ingress priority filter + 10
+ devices = { "$nsim_port", "dummyf1" }
+ }
+
+ chain cf {
+ type filter hook forward priority 0; policy accept;
+ ct state new meta l4proto tcp flow add @f
+ }
+}
+EOF
+
+if ip netns exec "$NS" nft -f "$file_ft"; then
+ echo "PASS: flowtable offload"
+else
+ echo "FAIL: flowtable offload"
+ ret=1
+fi
+
+file_rs=$(mktemp)
+cat > "$file_rs" <<EOF
+table netdev t {
+ chain c {
+ type filter hook ingress device $nsim_port priority 1
+ flags offload
+ ip saddr 10.2.1.1 ip daddr 10.2.1.2 ip protocol icmp accept
+ ip saddr 10.2.1.1 ip daddr 10.2.1.3 ip protocol icmp drop
+ ip saddr 10.2.1.0/24 ip daddr 10.2.1.0/24 ip protocol icmp accept
+ ip6 saddr dead:beef::1 ip6 daddr dead:beef::2 meta l4proto ipv6-icmp accept
+ ip6 saddr dead:beef::1 ip6 daddr dead:beef::3 meta l4proto ipv6-icmp drop
+ ip6 saddr dead:beef::/64 ip6 daddr dead:beef::/64 meta l4proto ipv6-icmp accept
+ }
+}
+EOF
+if ip netns exec "$NS" nft -f "$file_rs"; then
+ echo "PASS: ruleset offload"
+else
+ echo "FAIL: ruleset offload"
+ ret=1
+fi
+
+test -d "$sysfs" || skip "$sysfs not present"
+grep -q nsim_setup_tc "$sysfs/injectable" || skip "nsim_setup_tc fault injection not available"
+
+echo Y > "$sysfs/task-filter"
+echo 0 > "$sysfs/verbose"
+echo "nsim_setup_tc" > "$sysfs/inject"
+fault=1
+
+p=$(((RANDOM%90) + 10))
+echo $p > "$sysfs/probability"
+echo -1 > "$sysfs/times"
+
+count=0
+ok=0
+
+now=$(date +%s)
+stop=$((now+duration))
+
+# fault-injection enabled rule loads are expected to fail.
+set +e
+while [ "$now" -le "$stop" ]; do
+ for f in "$file_ft" "$file_rs"; do
+ if ip netns exec "$NS" bash -c "echo 1 > $failname ; ip netns exec \"$NS\" nft -f $f" 2> /dev/null;then
+ ok=$((ok+1))
+ fi
+ count=$((count+1))
+ done
+ now=$(date +%s)
+done
+
+sleep 5
+
+read -r t < /proc/sys/kernel/tainted
+if [ "$t" -eq 0 ];then
+ echo "PASS: Not tainted. $count rounds, $ok successful ruleset loads with P $p."
+else
+ echo "ERROR: Tainted. $count rounds, $ok successful ruleset loads with P $p."
+ dmesg
+ ret=1
+fi
+
+exit $ret
diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py
index eff55c64a012..ceb44c8e1fec 100755
--- a/tools/testing/selftests/net/nl_netdev.py
+++ b/tools/testing/selftests/net/nl_netdev.py
@@ -9,7 +9,7 @@ import errno
from os import system
from lib.py import ksft_run, ksft_exit
from lib.py import ksft_eq, ksft_ge, ksft_ne, ksft_raises, ksft_busy_wait
-from lib.py import NetdevFamily, NetdevSimDev, NlError, ip
+from lib.py import NetdevFamily, NetdevSimDev, NlError, defer, ip
def empty_check(nf) -> None:
@@ -255,6 +255,117 @@ def page_pool_check(nf) -> None:
nsim.dfs_write("pp_hold", "y")
+def page_pool_dump_ifindex(nf) -> None:
+ """Test page pool dump filtering by ifindex."""
+ nsimdev1 = NetdevSimDev(queue_count=3)
+ rm_nsim1 = defer(nsimdev1.remove)
+ nsimdev2 = NetdevSimDev(queue_count=5)
+ defer(nsimdev2.remove)
+
+ nsim1 = nsimdev1.nsims[0]
+ nsim2 = nsimdev2.nsims[0]
+
+ ip(f"link set dev {nsim1.ifname} up")
+ ip(f"link set dev {nsim2.ifname} up")
+
+ # Unfiltered dump should have pools from both devices
+ all_pp = nf.page_pool_get({}, dump=True)
+ pp1_all = [pp for pp in all_pp
+ if pp.get("ifindex") == nsim1.ifindex]
+ pp2_all = [pp for pp in all_pp
+ if pp.get("ifindex") == nsim2.ifindex]
+ ksft_ge(len(pp1_all), 1)
+ ksft_ge(len(pp2_all), 1)
+
+ # Filtered dump should only return pools for that device
+ pp1_flt = nf.page_pool_get({'ifindex': nsim1.ifindex}, dump=True)
+ ksft_eq(pp1_flt, pp1_all)
+
+ pp2_flt = nf.page_pool_get({'ifindex': nsim2.ifindex}, dump=True)
+ ksft_eq(pp2_flt, pp2_all)
+
+ # Non-existent ifindex should return empty dump
+ pp_none = nf.page_pool_get({'ifindex': 12345678}, dump=True)
+ ksft_eq(len(pp_none), 0)
+
+ # Device down - no pools for that ifindex
+ ip(f"link set dev {nsim1.ifname} down")
+ pp1_down = nf.page_pool_get({'ifindex': nsim1.ifindex}, dump=True)
+ ksft_eq(len(pp1_down), 0)
+
+ # Remove device, dump by its old ifindex should return empty
+ old_ifindex = nsim1.ifindex
+ rm_nsim1.exec()
+ pp1_gone = nf.page_pool_get({'ifindex': old_ifindex}, dump=True)
+ ksft_eq(len(pp1_gone), 0)
+
+
+def page_pool_ifindex_leak_check(nf) -> None:
+ """Test that zombie page pools don't show up under the original ifindex."""
+ nsimdev = NetdevSimDev()
+ rm_nsim = defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+
+ ip(f"link set dev {nsim.ifname} up")
+ nsim.dfs_write("pp_hold", "y")
+
+ pp_up = nf.page_pool_get({'ifindex': nsim.ifindex}, dump=True)
+ ksft_ge(len(pp_up), 1)
+
+ # Remove device with leaked page - pool becomes zombie (orphaned to lo)
+ old_ifindex = nsim.ifindex
+ rm_nsim.exec()
+
+ # Zombie pool should NOT appear under the original device
+ pp_down = nf.page_pool_get({'ifindex': old_ifindex}, dump=True)
+ ksft_eq(len(pp_down), 0)
+
+ # But it should appear in an unfiltered dump (under loopback)
+ pp_all = nf.page_pool_get({}, dump=True)
+ orphans = [pp for pp in pp_all
+ if "detach-time" in pp and "ifindex" not in pp]
+ ksft_ge(len(orphans), 1)
+
+
+def page_pool_stats_ifindex_check(nf) -> None:
+ """Test page pool stats dump filtering by ifindex."""
+ nsimdev1 = NetdevSimDev(queue_count=3)
+ defer(nsimdev1.remove)
+ nsimdev2 = NetdevSimDev(queue_count=5)
+ defer(nsimdev2.remove)
+
+ nsim1 = nsimdev1.nsims[0]
+ nsim2 = nsimdev2.nsims[0]
+
+ ip(f"link set dev {nsim1.ifname} up")
+ ip(f"link set dev {nsim2.ifname} up")
+
+ # Unfiltered stats dump
+ all_stats = nf.page_pool_stats_get({}, dump=True)
+ s1_all = [s for s in all_stats
+ if s.get("info", {}).get("ifindex") == nsim1.ifindex]
+ s2_all = [s for s in all_stats
+ if s.get("info", {}).get("ifindex") == nsim2.ifindex]
+ ksft_ge(len(s1_all), 1)
+ ksft_ge(len(s2_all), 1)
+
+ # Filtered stats dump
+ s1_flt = nf.page_pool_stats_get({'info': {'ifindex': nsim1.ifindex}},
+ dump=True)
+ ksft_eq(s1_flt, s1_all)
+
+ # Non-existent ifindex should return empty
+ s_none = nf.page_pool_stats_get({'info': {'ifindex': 12345678}}, dump=True)
+ ksft_eq(len(s_none), 0)
+
+ # info.id should be rejected for stats dump
+ with ksft_raises(NlError) as cm:
+ nf.page_pool_stats_get({'info': {'id': s1_all[0]['info']['id']}},
+ dump=True)
+ ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL)
+ ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.info.id')
+
+
def main() -> None:
""" Ksft boiler plate main """
nf = NetdevFamily()
@@ -265,7 +376,11 @@ def main() -> None:
napi_set_threaded,
dev_set_threaded,
nsim_rxq_reset_down,
- page_pool_check],
+ page_pool_check,
+ page_pool_dump_ifindex,
+ page_pool_ifindex_leak_check,
+ page_pool_stats_ifindex_check
+ ],
args=(nf, ))
ksft_exit()
diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh
index 3cdd953f6813..2954245129a2 100755
--- a/tools/testing/selftests/net/openvswitch/openvswitch.sh
+++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh
@@ -28,6 +28,10 @@ tests="
tunnel_metadata ovs: test extraction of tunnel metadata
tunnel_refcount ovs: test tunnel vport reference cleanup
drop_reason drop: test drop reasons are emitted
+ pop_vlan vlan: POP_VLAN action strips tag
+ dec_ttl ttl: dec_ttl decrements IP TTL
+ flow_set flow-set: Flow modify
+ action_set set: SET action rewrites fields
psample psample: Sampling packets with psample"
info() {
@@ -191,6 +195,23 @@ ovs_add_flow () {
return 0
}
+ovs_mod_flow () {
+ if [ -n "$4" ]; then
+ info "Modifying flow: sbx:$1 br:$2 flow:$3 act:$4"
+ ovs_sbx "$1" python3 $ovs_base/ovs-dpctl.py \
+ mod-flow "$2" "$3" "$4"
+ else
+ info "Modifying flow (no actions): sbx:$1 br:$2 flow:$3"
+ ovs_sbx "$1" python3 $ovs_base/ovs-dpctl.py \
+ mod-flow "$2" "$3"
+ fi
+ if [ $? -ne 0 ]; then
+ info "Flow modify [ $3 ] failed"
+ return 1
+ fi
+ return 0
+}
+
ovs_del_flows () {
info "Deleting all flows from DP: sbx:$1 br:$2"
ovs_sbx "$1" python3 $ovs_base/ovs-dpctl.py del-flows "$2"
@@ -244,6 +265,184 @@ usage() {
}
+test_dec_ttl() {
+ sbx_add "test_dec_ttl" || return $?
+ ovs_add_dp "test_dec_ttl" decttl || return 1
+
+ info "create namespaces"
+ for ns in client server; do
+ ovs_add_netns_and_veths "test_dec_ttl" "decttl" "$ns" \
+ "${ns:0:1}0" "${ns:0:1}1" || return 1
+ done
+
+ ip netns exec client ip addr add 10.0.0.1/24 dev c1
+ ip netns exec client ip link set c1 up
+ ip netns exec server ip addr add 10.0.0.2/24 dev s1
+ ip netns exec server ip link set s1 up
+
+ # Probe: check if kernel supports dec_ttl action.
+ ovs_add_flow "test_dec_ttl" decttl \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' \
+ 'dec_ttl(le_1())' &>/dev/null
+ if [ $? -ne 0 ]; then
+ info "no support for dec_ttl - skipping"
+ ovs_exit_sig
+ return $ksft_skip
+ fi
+
+ ovs_del_flows "test_dec_ttl" decttl
+
+ # ARP flows (bidirectional)
+ ovs_add_flow "test_dec_ttl" decttl \
+ 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+ ovs_add_flow "test_dec_ttl" decttl \
+ 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+
+ # IP flows with dec_ttl action
+ ovs_add_flow "test_dec_ttl" decttl \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' \
+ 'dec_ttl(le_1()),2' || return 1
+ ovs_add_flow "test_dec_ttl" decttl \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' \
+ 'dec_ttl(le_1()),1' || return 1
+
+ info "verify connectivity with dec_ttl"
+ ovs_sbx "test_dec_ttl" ip netns exec client ping -c 1 -W 2 \
+ 10.0.0.2 || return 1
+
+ info "verify TTL=1 is dropped by dec_ttl"
+ ovs_sbx "test_dec_ttl" ip netns exec client ping -c 1 -W 2 \
+ -t 1 10.0.0.2 >/dev/null 2>&1 \
+ && { info "FAIL: ping should fail with TTL=1 and dec_ttl"
+ return 1; }
+
+ return 0
+}
+
+test_flow_set() {
+ sbx_add "test_flow_set" || return $?
+ ovs_add_dp "test_flow_set" flowset || return 1
+
+ info "create namespaces"
+ for ns in client server; do
+ ovs_add_netns_and_veths "test_flow_set" "flowset" "$ns" \
+ "${ns:0:1}0" "${ns:0:1}1" || return 1
+ done
+
+ ip netns exec client ip addr add 10.0.0.1/24 dev c1
+ ip netns exec client ip link set c1 up
+ ip netns exec server ip addr add 10.0.0.2/24 dev s1
+ ip netns exec server ip link set s1 up
+
+ ovs_add_flow "test_flow_set" flowset \
+ 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+ ovs_add_flow "test_flow_set" flowset \
+ 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+
+ local fwd_flow="ufid:00000001-0002-0003-0004-000500060007"
+ fwd_flow="$fwd_flow,in_port(1),eth(),eth_type(0x0800),ipv4()"
+
+ ovs_add_flow "test_flow_set" flowset "$fwd_flow" '2' \
+ || return 1
+ ovs_add_flow "test_flow_set" flowset \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' '1' || return 1
+
+ info "verify initial forwarding"
+ ovs_sbx "test_flow_set" ip netns exec client ping -c 1 -W 2 \
+ 10.0.0.2 || return 1
+
+ info "mod-flow with new actions (change to drop)"
+ ovs_mod_flow "test_flow_set" flowset "$fwd_flow" 'drop' \
+ || return 1
+
+ info "verify traffic is now dropped"
+ ovs_sbx "test_flow_set" ip netns exec client ping -c 1 -W 2 \
+ 10.0.0.2 >/dev/null 2>&1 \
+ && { info "FAIL: ping should fail after mod-flow to drop"
+ return 1; }
+
+ info "mod-flow without actions"
+ ovs_mod_flow "test_flow_set" flowset "$fwd_flow" || return 1
+
+ info "verify flow retained drop action via dump"
+ python3 "$ovs_base/ovs-dpctl.py" dump-flows flowset \
+ | grep -q "actions:drop" || \
+ { info "FAIL: flow not showing drop action"; return 1; }
+
+ info "verify drop actions unchanged"
+ ovs_sbx "test_flow_set" ip netns exec client ping -c 1 -W 2 \
+ 10.0.0.2 >/dev/null 2>&1 \
+ && { info "FAIL: ping should still fail after no-actions set"
+ return 1; }
+
+ return 0
+}
+
+test_action_set() {
+ sbx_add "test_action_set" || return $?
+ ovs_add_dp "test_action_set" settest || return 1
+
+ info "create namespaces"
+ for ns in client server; do
+ ovs_add_netns_and_veths "test_action_set" "settest" "$ns" \
+ "${ns:0:1}0" "${ns:0:1}1" || return 1
+ done
+
+ ip netns exec client ip addr add 10.0.0.1/24 dev c1
+ ip netns exec client ip link set c1 up
+ ip netns exec server ip addr add 10.0.0.2/24 dev s1
+ ip netns exec server ip link set s1 up
+
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' '2' || return 1
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' '1' || return 1
+
+ info "verify connectivity without SET"
+ ovs_sbx "test_action_set" ip netns exec client ping -c 1 -W 2 \
+ 10.0.0.2 || return 1
+
+ ovs_del_flows "test_action_set" settest
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+
+ info "set ipv4 dst to unreachable address"
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' \
+ 'set(ipv4(dst=10.0.0.99)),2' || return 1
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' '1' || return 1
+
+ info "verify ping fails with rewritten dst"
+ ovs_sbx "test_action_set" ip netns exec client ping -c 1 -W 2 \
+ 10.0.0.2 >/dev/null 2>&1 \
+ && { info "FAIL: ping should fail with dst rewritten"
+ return 1; }
+
+ ovs_del_flows "test_action_set" settest
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' '2' || return 1
+ ovs_add_flow "test_action_set" settest \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' '1' || return 1
+
+ info "verify connectivity restored without SET"
+ ovs_sbx "test_action_set" ip netns exec client ping -c 1 -W 2 \
+ 10.0.0.2 || return 1
+
+ return 0
+}
+
# psample test
# - use psample to observe packets
test_psample() {
@@ -304,6 +503,8 @@ test_psample() {
# sFlow / IPFIX.
nlpid=$(grep -E "listening on upcall packet handler" \
$ovs_dir/s0.out | cut -d ":" -f 2 | tr -d ' ')
+ [ -z "$nlpid" ] && \
+ { info "failed to get upcall PID"; return 1; }
ovs_add_flow "test_psample" psample \
"in_port(2),eth(),eth_type(0x0800),ipv4()" \
@@ -337,6 +538,10 @@ test_drop_reason() {
ovs_drop_subsys=$(pahole -C skb_drop_reason_subsys |
awk '/OPENVSWITCH/ { print $3; }' |
tr -d ,)
+ if [ -z "$ovs_drop_subsys" ]; then
+ info "failed to get OVS drop subsys ID"
+ return $ksft_skip
+ fi
sbx_add "test_drop_reason" || return $?
@@ -435,13 +640,19 @@ test_arp_ping () {
# Setup client namespace
ip netns exec client ip addr add 172.31.110.10/24 dev c1
ip netns exec client ip link set c1 up
- HW_CLIENT=`ip netns exec client ip link show dev c1 | grep -E 'link/ether [0-9a-f:]+' | awk '{print $2;}'`
+ HW_CLIENT=$(ip netns exec client ip link show dev c1 \
+ | awk '/link\/ether/ {print $2}')
+ [ -z "$HW_CLIENT" ] && \
+ { info "failed to get client hwaddr"; return 1; }
info "Client hwaddr: $HW_CLIENT"
# Setup server namespace
ip netns exec server ip addr add 172.31.110.20/24 dev s1
ip netns exec server ip link set s1 up
- HW_SERVER=`ip netns exec server ip link show dev s1 | grep -E 'link/ether [0-9a-f:]+' | awk '{print $2;}'`
+ HW_SERVER=$(ip netns exec server ip link show dev s1 \
+ | awk '/link\/ether/ {print $2}')
+ [ -z "$HW_SERVER" ] && \
+ { info "failed to get server hwaddr"; return 1; }
info "Server hwaddr: $HW_SERVER"
ovs_add_flow "test_arp_ping" arpping \
@@ -864,6 +1075,83 @@ test_tunnel_refcount() {
ovs_wait dev_removed dp-${tun_type} || return 1
ovs_wait dev_removed ovs-${tun_type}0 || return 1
done
+
+ return 0
+}
+
+test_pop_vlan() {
+ local sbx="test_pop_vlan"
+ sbx_add "$sbx" || return $?
+ ovs_add_dp "$sbx" vlandp || return 1
+
+ ovs_add_netns_and_veths "$sbx" vlandp \
+ ns1 veth1 ns1veth 192.0.2.1/24 || return 1
+ ovs_add_netns_and_veths "$sbx" vlandp \
+ ns2 veth2 ns2veth 192.0.2.2/24 || return 1
+
+ # Baseline: untagged bidirectional forwarding
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' '2' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' '1' || return 1
+ ovs_sbx "$sbx" ip netns exec ns1 ping -c 3 -W 2 \
+ 192.0.2.2 || return 1
+
+ # VLAN topology: ns1 uses VLAN sub-interface, ns2 is plain
+ ip -n ns1 link add link ns1veth name ns1veth.10 \
+ type vlan id 10 || return 1
+ on_exit "ip -n ns1 link del ns1veth.10 2>/dev/null"
+ ip -n ns1 addr add 198.51.100.1/24 dev ns1veth.10 || return 1
+ ip -n ns1 link set ns1veth.10 up || return 1
+ ip -n ns2 addr add 198.51.100.2/24 dev ns2veth || return 1
+
+ ovs_del_flows "$sbx" vlandp
+
+ # Static ARP: avoids VLAN-tagged ARP complexity
+ local ns1veth10mac ns2mac
+ ns1veth10mac=$(ip -n ns1 link show ns1veth.10 \
+ | awk '/link\/ether/ {print $2}')
+ [ -z "$ns1veth10mac" ] && \
+ { info "failed to get ns1veth10mac"; return 1; }
+ ns2mac=$(ip -n ns2 link show ns2veth \
+ | awk '/link\/ether/ {print $2}')
+ [ -z "$ns2mac" ] && \
+ { info "failed to get ns2mac"; return 1; }
+ ip -n ns1 neigh replace 198.51.100.2 lladdr "$ns2mac" \
+ dev ns1veth.10 nud permanent || return 1
+ ip -n ns2 neigh replace 198.51.100.1 \
+ lladdr "$ns1veth10mac" \
+ dev ns2veth nud permanent || return 1
+
+ local vlan_match='in_port(1),eth(),eth_type(0x8100),'
+ vlan_match+='vlan(vid=10),'
+ vlan_match+='encap(eth_type(0x0800),'
+ vlan_match+='ipv4(src=198.51.100.1,proto=1),icmp())'
+
+ # Negative: forward without pop_vlan -- tagged frame
+ # is invisible to ns2 (no VLAN sub-interface), ping fails
+ ovs_add_flow "$sbx" vlandp "$vlan_match" '2' || return 1
+ ovs_sbx "$sbx" ip netns exec ns1 ping -I ns1veth.10 \
+ -c 3 -W 1 198.51.100.2 >/dev/null 2>&1 \
+ && { info "FAIL: ping should fail without pop_vlan"
+ return 1; }
+
+ ovs_del_flows "$sbx" vlandp
+
+ # Positive: pop_vlan strips tag on forward path,
+ # push_vlan restores tag on return path -- ping succeeds
+ ovs_add_flow "$sbx" vlandp \
+ "$vlan_match" 'pop_vlan,2' || return 1
+ ovs_add_flow "$sbx" vlandp \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' \
+ 'push_vlan(vid=10,pcp=0,tpid=0x8100),1' || return 1
+ ovs_sbx "$sbx" ip netns exec ns1 ping -I ns1veth.10 \
+ -c 3 -W 2 198.51.100.2 || return 1
+
return 0
}
diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index bbe35e2718d2..e1ecfad2c03e 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -369,7 +369,7 @@ class ovsactions(nla):
("OVS_ACTION_ATTR_OUTPUT", "uint32"),
("OVS_ACTION_ATTR_USERSPACE", "userspace"),
("OVS_ACTION_ATTR_SET", "ovskey"),
- ("OVS_ACTION_ATTR_PUSH_VLAN", "none"),
+ ("OVS_ACTION_ATTR_PUSH_VLAN", "push_vlan"),
("OVS_ACTION_ATTR_POP_VLAN", "flag"),
("OVS_ACTION_ATTR_SAMPLE", "sample"),
("OVS_ACTION_ATTR_RECIRC", "uint32"),
@@ -388,11 +388,21 @@ class ovsactions(nla):
("OVS_ACTION_ATTR_CLONE", "recursive"),
("OVS_ACTION_ATTR_CHECK_PKT_LEN", "none"),
("OVS_ACTION_ATTR_ADD_MPLS", "none"),
- ("OVS_ACTION_ATTR_DEC_TTL", "none"),
+ ("OVS_ACTION_ATTR_DEC_TTL", "dec_ttl"),
("OVS_ACTION_ATTR_DROP", "uint32"),
("OVS_ACTION_ATTR_PSAMPLE", "psample"),
)
+ class dec_ttl(nla): # pylint: disable=invalid-name
+ """Nested OVS_DEC_TTL_ATTR_* sub-attributes."""
+
+ nla_flags = NLA_F_NESTED
+
+ nla_map = (
+ ("OVS_DEC_TTL_ATTR_UNSPEC", "none"),
+ ("OVS_DEC_TTL_ATTR_ACTION", "actions"),
+ )
+
class psample(nla):
nla_flags = NLA_F_NESTED
@@ -426,6 +436,9 @@ class ovsactions(nla):
return actstr
+ class push_vlan(nla):
+ fields = (("vlan_tpid", "!H"), ("vlan_tci", "!H"))
+
class sample(nla):
nla_flags = NLA_F_NESTED
@@ -632,6 +645,21 @@ class ovsactions(nla):
print_str += "ct_clear"
elif field[0] == "OVS_ACTION_ATTR_POP_VLAN":
print_str += "pop_vlan"
+ elif field[0] == "OVS_ACTION_ATTR_DEC_TTL":
+ datum = self.get_attr(field[0])
+ print_str += "dec_ttl(le_1("
+ subacts = datum.get_attr("OVS_DEC_TTL_ATTR_ACTION")
+ if subacts and subacts.get("attrs"):
+ print_str += subacts.dpstr(more)
+ print_str += "))"
+ elif field[0] == "OVS_ACTION_ATTR_PUSH_VLAN":
+ datum = self.get_attr(field[0])
+ tpid = datum["vlan_tpid"]
+ tci = datum["vlan_tci"]
+ vid = tci & 0x0FFF
+ pcp = (tci >> 13) & 0x7
+ print_str += "push_vlan(vid=%d,pcp=%d" \
+ ",tpid=0x%04x)" % (vid, pcp, tpid)
elif field[0] == "OVS_ACTION_ATTR_POP_ETH":
print_str += "pop_eth"
elif field[0] == "OVS_ACTION_ATTR_POP_NSH":
@@ -725,7 +753,71 @@ class ovsactions(nla):
actstr = actstr[strspn(actstr, ", ") :]
parsed = True
- if parse_starts_block(actstr, "clone(", False):
+ if parse_starts_block(actstr, "push_vlan(", False):
+ actstr = actstr[len("push_vlan("):]
+ vid = 0
+ pcp = 0
+ tpid = 0x8100
+ if ")" not in actstr:
+ raise ValueError(
+ "push_vlan(): missing ')'")
+ paren = actstr.index(")")
+ if not actstr[:paren].strip():
+ raise ValueError("push_vlan(): no fields")
+ for kv in actstr[:paren].split(","):
+ if "=" not in kv:
+ raise ValueError(
+ "push_vlan(): bad field '%s'"
+ % kv.strip())
+ k = kv[:kv.index("=")].strip()
+ v = kv[kv.index("=") + 1:].strip()
+ if k == "vid":
+ vid = int(v, 0)
+ if vid < 0 or vid > 0xFFF:
+ raise ValueError(
+ "push_vlan(): vid=%d out of "
+ "range (0-4095)" % vid)
+ elif k == "pcp":
+ pcp = int(v, 0)
+ if pcp < 0 or pcp > 7:
+ raise ValueError(
+ "push_vlan(): pcp=%d out of "
+ "range (0-7)" % pcp)
+ elif k == "tpid":
+ tpid = int(v, 0)
+ if tpid < 0 or tpid > 0xFFFF:
+ raise ValueError(
+ "push_vlan(): tpid=0x%x out "
+ "of range (0-0xffff)" % tpid)
+ else:
+ raise ValueError(
+ "push_vlan(): unknown key '%s'"
+ % k)
+ tci = (vid & 0x0FFF) | ((pcp & 0x7) << 13) \
+ | 0x1000
+ pvact = self.push_vlan()
+ pvact["vlan_tpid"] = tpid
+ pvact["vlan_tci"] = tci
+ self["attrs"].append(
+ ["OVS_ACTION_ATTR_PUSH_VLAN", pvact])
+ actstr = actstr[paren + 1:]
+ parsed = True
+
+ elif parse_starts_block(actstr, "dec_ttl(le_1(", False):
+ parencount += 2
+ subacts = ovsactions()
+ actstr = actstr[len("dec_ttl(le_1("):]
+ parsed_len = subacts.parse(actstr)
+ decttl = ovsactions.dec_ttl()
+ decttl["attrs"].append(
+ ("OVS_DEC_TTL_ATTR_ACTION", subacts)
+ )
+ self["attrs"].append(
+ ("OVS_ACTION_ATTR_DEC_TTL", decttl)
+ )
+ actstr = actstr[parsed_len:]
+ parsed = True
+ elif parse_starts_block(actstr, "clone(", False):
parencount += 1
subacts = ovsactions()
actstr = actstr[len("clone("):]
@@ -896,15 +988,21 @@ class ovsactions(nla):
return (totallen - len(actstr))
+# pyroute2 resolves nla_map types via getattr(self, name).
+# dec_ttl needs "actions" to resolve to ovsactions, but
+# ovsactions is not defined when dec_ttl class body runs.
+ovsactions.dec_ttl.actions = ovsactions
+
+
class ovskey(nla):
nla_flags = NLA_F_NESTED
nla_map = (
("OVS_KEY_ATTR_UNSPEC", "none"),
- ("OVS_KEY_ATTR_ENCAP", "none"),
+ ("OVS_KEY_ATTR_ENCAP", "encap_ovskey"),
("OVS_KEY_ATTR_PRIORITY", "uint32"),
("OVS_KEY_ATTR_IN_PORT", "uint32"),
("OVS_KEY_ATTR_ETHERNET", "ethaddr"),
- ("OVS_KEY_ATTR_VLAN", "uint16"),
+ ("OVS_KEY_ATTR_VLAN", "be16"),
("OVS_KEY_ATTR_ETHERTYPE", "be16"),
("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"),
("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"),
@@ -1635,6 +1733,194 @@ class ovskey(nla):
class ovs_key_mpls(nla):
fields = (("lse", ">I"),)
+ # 802.1Q CFI (Canonical Format Indicator) bit, always set for Ethernet
+ _VLAN_CFI_MASK = 0x1000
+
+ @staticmethod
+ def _vlan_dpstr(tci):
+ """Format VLAN TCI as vid=X,pcp=Y,cfi=Z or tci=0xNNNN.
+
+ When cfi=1 (standard Ethernet VLAN), outputs decomposed
+ vid/pcp/cfi fields. When cfi=0 (truncated VLAN header),
+ falls back to raw tci=0x%04x to ensure round-trip
+ correctness: the parser auto-adds cfi=1 for vid/pcp
+ format, so cfi=0 would be lost on re-parse."""
+ vid = tci & 0x0FFF
+ pcp = (tci >> 13) & 0x7
+ cfi = (tci >> 12) & 0x1
+ if cfi:
+ return "vid=%d,pcp=%d,cfi=%d" % (vid, pcp, cfi)
+ return "tci=0x%04x" % tci
+
+ @staticmethod
+ def _parse_vlan_from_flowstr(flowstr):
+ """Parse vlan(tci=X) or vlan(vid=X[,pcp=Y,cfi=Z]) from flowstr.
+
+ Returns (remaining_flowstr, key_tci, mask_tci).
+ TCI values use standard bit layout (VID bits 0-11,
+ CFI bit 12, PCP bits 13-15); byte order conversion to
+ big-endian happens in pyroute2 be16 NLA serialization.
+ The mask covers only the fields the caller specified:
+ vid -> 0x0FFF, pcp -> 0xE000, cfi -> 0x1000, tci -> 0xFFFF.
+
+ The tci= key sets the raw TCI bitfield (no CFI validation) to allow
+ non-Ethernet use cases. Use cfi=1 for standard Ethernet VLAN matching.
+ """
+ tci = 0
+ mask = 0
+ has_tci = False
+ has_vid = has_pcp = has_cfi = False
+ _tci_mix_err = "vlan(): 'tci' cannot be mixed " \
+ "with 'vid'/'pcp'/'cfi'"
+ first = True
+ while True:
+ flowstr = flowstr.lstrip()
+ if not flowstr:
+ raise ValueError("vlan(): missing ')'")
+ if flowstr[0] == ')':
+ break
+ if not first:
+ flowstr = flowstr[1:] # skip ','
+ if not flowstr:
+ raise ValueError("vlan(): missing ')' after trailing comma")
+ flowstr = flowstr.lstrip()
+ if flowstr and flowstr[0] == ')':
+ break
+ if flowstr and flowstr[0] == ',':
+ raise ValueError(
+ "vlan(): empty or extra comma in field list")
+ first = False
+
+ eq = flowstr.find('=')
+ if eq == -1:
+ raise ValueError(
+ "vlan(): expected key=value, got '%s'" % flowstr)
+ key = flowstr[:eq].strip()
+ flowstr = flowstr[eq + 1:]
+
+ end = flowstr.find(',')
+ end2 = flowstr.find(')')
+ if end == -1 and end2 == -1:
+ raise ValueError("vlan(): missing ')'")
+ if end == -1 or (end2 != -1 and end2 < end):
+ end = end2
+ val = flowstr[:end].strip()
+ flowstr = flowstr[end:]
+
+ if not val:
+ raise ValueError("vlan(): empty value for key '%s'" % key)
+ try:
+ v = int(val, 0)
+ except ValueError as exc:
+ raise ValueError(
+ "vlan(): invalid value '%s' for key '%s'"
+ % (val, key)) from exc
+
+ if key == 'tci':
+ if has_tci:
+ raise ValueError("vlan(): duplicate 'tci'")
+ if has_vid or has_pcp or has_cfi:
+ raise ValueError(_tci_mix_err)
+ if v > 0xFFFF or v < 0:
+ raise ValueError("vlan(): tci=0x%x out of range" % v)
+ tci = v
+ mask = 0xFFFF
+ has_tci = True
+ elif key == 'vid':
+ if has_tci:
+ raise ValueError(_tci_mix_err)
+ if has_vid:
+ raise ValueError("vlan(): duplicate 'vid'")
+ if v < 0 or v > 0xFFF:
+ raise ValueError("vlan(): vid=%d out of range (0-4095)" % v)
+ tci |= v
+ mask |= 0x0FFF
+ has_vid = True
+ elif key == 'pcp':
+ if has_tci:
+ raise ValueError(_tci_mix_err)
+ if has_pcp:
+ raise ValueError("vlan(): duplicate 'pcp'")
+ if v < 0 or v > 7:
+ raise ValueError("vlan(): pcp=%d out of range (0-7)" % v)
+ tci |= (v & 0x7) << 13
+ mask |= 0xE000
+ has_pcp = True
+ elif key == 'cfi':
+ if has_tci:
+ raise ValueError(_tci_mix_err)
+ if has_cfi:
+ raise ValueError("vlan(): duplicate 'cfi'")
+ if v != 1:
+ raise ValueError("vlan(): cfi must be 1 for Ethernet")
+ tci |= ovskey._VLAN_CFI_MASK
+ mask |= ovskey._VLAN_CFI_MASK
+ has_cfi = True
+ else:
+ raise ValueError("vlan(): unknown key '%s'" % key)
+
+ flowstr = flowstr[1:] # skip ')'
+ # Catch immediate '))' (user error). A ')' after ',' is consumed
+ # by parse()'s strspn(flowstr, "), ") inter-field separator stripping.
+ if flowstr.lstrip().startswith(')'):
+ raise ValueError("vlan(): unmatched ')'")
+ # parse() strips trailing ',', ')', ' ' as inter-field separators,
+ # so we do not need to call strspn here.
+
+ if mask == 0:
+ raise ValueError("vlan(): no fields specified, "
+ "use vlan(vid=X[,pcp=Y,cfi=Z]) or vlan(tci=X)")
+ if not has_tci:
+ tci |= ovskey._VLAN_CFI_MASK
+ mask |= ovskey._VLAN_CFI_MASK
+ return flowstr, tci, mask
+
+ @staticmethod
+ def _parse_encap_from_flowstr(flowstr):
+ """Parse encap(inner_flow) from flowstr.
+
+ Returns (remaining_flowstr, inner_key_dict, inner_mask_dict)
+ where each dict has an 'attrs' key for recursive NLA encoding.
+ Parenthesis-depth tracking handles nested encap() calls but not
+ quoted strings containing literal parentheses.
+ """
+ depth = 1
+ end = -1
+ for i, c in enumerate(flowstr):
+ if c == '(':
+ depth += 1
+ elif c == ')':
+ depth -= 1
+ if depth < 0:
+ raise ValueError(
+ "encap(): unmatched ')' at position %d" % i)
+ if depth == 0:
+ end = i
+ break
+
+ if end == -1:
+ if depth > 1:
+ raise ValueError("encap(): missing ')' in nested encap")
+ raise ValueError("encap(): missing ')'")
+
+ inner_str = flowstr[:end].strip()
+ if not inner_str:
+ raise ValueError("encap(): empty inner flow")
+
+ flowstr = flowstr[end + 1:]
+ if flowstr.lstrip().startswith(')'):
+ raise ValueError("encap(): unmatched ')' after encap()")
+
+ inner_key = encap_ovskey()
+ inner_mask = encap_ovskey()
+ remaining = inner_key.parse(inner_str, inner_mask)
+ if remaining and re.search(r'[^\s,)]', remaining):
+ raise ValueError(
+ "encap(): unrecognized trailing "
+ "content '%s'" % remaining.strip())
+
+ return flowstr, inner_key, inner_mask
+
def parse(self, flowstr, mask=None):
for field in (
("OVS_KEY_ATTR_PRIORITY", "skb_priority", intparse),
@@ -1657,6 +1943,16 @@ class ovskey(nla):
lambda x: intparse(x, "0xffff"),
),
(
+ "OVS_KEY_ATTR_VLAN",
+ "vlan",
+ ovskey._parse_vlan_from_flowstr,
+ ),
+ (
+ "OVS_KEY_ATTR_ENCAP",
+ "encap",
+ ovskey._parse_encap_from_flowstr,
+ ),
+ (
"OVS_KEY_ATTR_IPV4",
"ipv4",
ovskey.ovs_key_ipv4,
@@ -1793,6 +2089,9 @@ class ovskey(nla):
True,
),
("OVS_KEY_ATTR_ETHERNET", None, None, False, False),
+ ("OVS_KEY_ATTR_VLAN", "vlan", ovskey._vlan_dpstr,
+ lambda x: False, True),
+ ("OVS_KEY_ATTR_ENCAP", None, None, False, False),
(
"OVS_KEY_ATTR_ETHERTYPE",
"eth_type",
@@ -1820,22 +2119,63 @@ class ovskey(nla):
v = self.get_attr(field[0])
if v is not None:
m = None if mask is None else mask.get_attr(field[0])
+ fmt = field[2] # str format or callable
if field[4] is False:
print_str += v.dpstr(m, more)
print_str += ","
else:
if m is None or field[3](m):
- print_str += field[1] + "("
- print_str += field[2] % v
- print_str += "),"
+ val = fmt(v) if callable(fmt) else fmt % v
+ print_str += field[1] + "(" + val + "),"
elif more or m != 0:
- print_str += field[1] + "("
- print_str += (field[2] % v) + "/" + (field[2] % m)
- print_str += "),"
+ if field[0] == "OVS_KEY_ATTR_VLAN":
+ val = "tci=0x%04x/0x%04x" % (v, m)
+ elif callable(fmt):
+ val = fmt(v) + "/" + fmt(m)
+ else:
+ val = (fmt % v) + "/" + (fmt % m)
+ print_str += field[1] + "(" + val + "),"
return print_str
+class encap_ovskey(ovskey):
+ """Inner flow key attributes valid inside 802.1Q ENCAP.
+
+ Only L2-L4 key attributes (slots 0-21) appear inside ENCAP.
+ Metadata-only attributes (SKB_MARK, DP_HASH, RECIRC_ID, etc.)
+ are set to "none" -- they never appear inside ENCAP per
+ ovs_nla_put_vlan() in net/openvswitch/flow_netlink.c.
+
+ nla_map indexes must match OVS_KEY_ATTR_* enum values in
+ include/uapi/linux/openvswitch.h.
+ """
+ nla_map = (
+ ("OVS_KEY_ATTR_UNSPEC", "none"),
+ ("OVS_KEY_ATTR_ENCAP", "none"), # placeholder, parsed by ovskey
+ ("OVS_KEY_ATTR_PRIORITY", "none"), # skb metadata, not in ENCAP
+ ("OVS_KEY_ATTR_IN_PORT", "none"), # skb metadata, not in ENCAP
+ ("OVS_KEY_ATTR_ETHERNET", "ethaddr"),
+ ("OVS_KEY_ATTR_VLAN", "be16"),
+ ("OVS_KEY_ATTR_ETHERTYPE", "be16"),
+ ("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"),
+ ("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"),
+ ("OVS_KEY_ATTR_TCP", "ovs_key_tcp"),
+ ("OVS_KEY_ATTR_UDP", "ovs_key_udp"),
+ ("OVS_KEY_ATTR_ICMP", "ovs_key_icmp"),
+ ("OVS_KEY_ATTR_ICMPV6", "ovs_key_icmpv6"),
+ ("OVS_KEY_ATTR_ARP", "ovs_key_arp"),
+ ("OVS_KEY_ATTR_ND", "ovs_key_nd"),
+ ("OVS_KEY_ATTR_SKB_MARK", "none"), # metadata, not in ENCAP
+ ("OVS_KEY_ATTR_TUNNEL", "none"), # tunnel metadata, not in ENCAP
+ ("OVS_KEY_ATTR_SCTP", "ovs_key_sctp"),
+ ("OVS_KEY_ATTR_TCP_FLAGS", "be16"),
+ ("OVS_KEY_ATTR_DP_HASH", "none"), # metadata, not in ENCAP
+ ("OVS_KEY_ATTR_RECIRC_ID", "none"), # metadata, not in ENCAP
+ ("OVS_KEY_ATTR_MPLS", "array(ovs_key_mpls)"),
+ )
+
+
class OvsPacket(GenericNetlinkSocket):
OVS_PACKET_CMD_MISS = 1 # Flow table miss
OVS_PACKET_CMD_ACTION = 2 # USERSPACE action
@@ -2362,9 +2702,10 @@ class OvsFlow(GenericNetlinkSocket):
self["attrs"].append(["OVS_FLOW_ATTR_KEY", k])
self["attrs"].append(["OVS_FLOW_ATTR_MASK", m])
- a = ovsactions()
- a.parse(actstr)
- self["attrs"].append(["OVS_FLOW_ATTR_ACTIONS", a])
+ if actstr is not None:
+ a = ovsactions()
+ a.parse(actstr)
+ self["attrs"].append(["OVS_FLOW_ATTR_ACTIONS", a])
def __init__(self):
GenericNetlinkSocket.__init__(self)
@@ -2398,6 +2739,25 @@ class OvsFlow(GenericNetlinkSocket):
raise ne
return reply
+ def mod_flow(self, dpifindex, flowmsg):
+ """Modify an existing flow in the kernel."""
+ flowmsg["cmd"] = OVS_FLOW_CMD_SET
+ flowmsg["version"] = OVS_DATAPATH_VERSION
+ flowmsg["reserved"] = 0
+ flowmsg["dpifindex"] = dpifindex
+
+ try:
+ reply = self.nlm_request(
+ flowmsg,
+ msg_type=self.prid,
+ msg_flags=NLM_F_REQUEST | NLM_F_ACK,
+ )
+ reply = reply[0]
+ except NetlinkError as ne:
+ print(flowmsg)
+ raise ne
+ return reply
+
def del_flows(self, dpifindex):
"""
Send a del message to the kernel that will drop all flows.
@@ -2583,6 +2943,7 @@ def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()):
def main(argv):
+ nlmsg_atoms.encap_ovskey = encap_ovskey
nlmsg_atoms.ovskey = ovskey
nlmsg_atoms.ovsactions = ovsactions
@@ -2672,6 +3033,12 @@ def main(argv):
addflcmd.add_argument("flow", help="Flow specification")
addflcmd.add_argument("acts", help="Flow actions")
+ modflcmd = subparsers.add_parser("mod-flow")
+ modflcmd.add_argument("modbr", help="Datapath name")
+ modflcmd.add_argument("modflow", help="Flow specification")
+ modflcmd.add_argument("modacts", help="Flow actions",
+ nargs="?", default=None)
+
delfscmd = subparsers.add_parser("del-flows")
delfscmd.add_argument("flsbr", help="Datapath name")
@@ -2769,6 +3136,14 @@ def main(argv):
flow = OvsFlow.ovs_flow_msg()
flow.parse(args.flow, args.acts, rep["dpifindex"])
ovsflow.add_flow(rep["dpifindex"], flow)
+ elif hasattr(args, "modbr"):
+ rep = ovsdp.info(args.modbr, 0)
+ if rep is None:
+ print(f"DP '{args.modbr}' not found.")
+ return 1
+ flow = OvsFlow.ovs_flow_msg()
+ flow.parse(args.modflow, args.modacts, rep["dpifindex"])
+ ovsflow.mod_flow(rep["dpifindex"], flow)
elif hasattr(args, "flsbr"):
rep = ovsdp.info(args.flsbr, 0)
if rep is None:
diff --git a/tools/testing/selftests/net/packetdrill/tcp_syncookies_ip4_9k.pkt b/tools/testing/selftests/net/packetdrill/tcp_syncookies_ip4_9k.pkt
new file mode 100644
index 000000000000..60910069b3d7
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_syncookies_ip4_9k.pkt
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Check syncookies.
+//
+// Check we are able to rebuild client sack, wscale, ecn and mss options.
+// IPv4 msstab[4] = { 536, 1300, 1440, 1460 }
+
+--ip_version=ipv4
+
+`./defaults.sh
+sysctl -q net.ipv4.tcp_syncookies=2
+ip link set dev tun0 mtu 9000
+`
+
+ 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+ +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+ +0 bind(3, ..., ...) = 0
+ +0 listen(3, 10) = 0
+
+ +0 < S 0:0(0) win 32792 <mss 8960,sackOK,TS val 100 ecr 0,nop,wscale 10>
+ +0 > S. 0:0(0) ack 1 <mss 8960,sackOK,TS val 4000 ecr 100,nop,wscale 8>
+ +.01 < . 1:1(0) ack 1 win 1024 <nop,nop,TS val 110 ecr 4000>
+
+ +0 accept(3, ..., ...) = 4
+
+// Check we properly infer from the final packet the other peer wanted mss >= 1460, wscale 10, sackOK and no ECN.
+// Note that mss is limited to 1460 - 12 because of IPv4 msstab[]
+// This is only possible because TCP TS option was used.
+// Linux uses the SYNACK TS.val 6 low order bits to encode the options.
+
+ +0 %{ assert tcpi_snd_mss == 1460 - 12, tcpi_snd_mss; \
+ assert tcpi_snd_wscale == 10, tcpi_snd_wscale; \
+ assert (tcpi_options & TCPI_OPT_SACK) != 0, tcpi_options; \
+ assert (tcpi_options & TCPI_OPT_TIMESTAMPS) != 0, tcpi_options; \
+ assert (tcpi_options & TCPI_OPT_WSCALE) != 0, tcpi_options; \
+ assert (tcpi_options & TCPI_OPT_ECN) == 0, tcpi_options
+}%
diff --git a/tools/testing/selftests/net/packetdrill/tcp_syncookies_ip6_9k.pkt b/tools/testing/selftests/net/packetdrill/tcp_syncookies_ip6_9k.pkt
new file mode 100644
index 000000000000..f333c61044bc
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_syncookies_ip6_9k.pkt
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Check syncookies.
+//
+// Check we are able to rebuild client sack, wscale, ecn and mss options.
+// IPv6 msstab[4] = { 1280 - 60, 1480 - 60, 1500 - 60, 9000 - 60 }
+
+--ip_version=ipv6
+
+`./defaults.sh
+sysctl -q net.ipv4.tcp_syncookies=2
+ip link set dev tun0 mtu 9000
+`
+
+ 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+ +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+ +0 bind(3, ..., ...) = 0
+ +0 listen(3, 10) = 0
+
+ +0 < S 0:0(0) win 32792 <mss 8940,sackOK,TS val 100 ecr 0,nop,wscale 10>
+ +0 > S. 0:0(0) ack 1 <mss 8940,sackOK,TS val 4000 ecr 100,nop,wscale 8>
+ +.01 < . 1:1(0) ack 1 win 1024 <nop,nop,TS val 110 ecr 4000>
+
+ +0 accept(3, ..., ...) = 4
+
+// Check we properly infer from the final packet the other peer wanted mss >= 8940, wscale 10, sackOK and no ECN.
+// This is only possible because TCP TS option was used.
+// Linux uses the SYNACK TS.val 6 low order bits to encode the options.
+
+ +0 %{ assert tcpi_snd_mss == 8940 - 12, tcpi_snd_mss; \
+ assert tcpi_snd_wscale == 10, tcpi_snd_wscale; \
+ assert (tcpi_options & TCPI_OPT_SACK) != 0, tcpi_options; \
+ assert (tcpi_options & TCPI_OPT_TIMESTAMPS) != 0, tcpi_options; \
+ assert (tcpi_options & TCPI_OPT_WSCALE) != 0, tcpi_options; \
+ assert (tcpi_options & TCPI_OPT_ECN) == 0, tcpi_options
+}%
diff --git a/tools/testing/selftests/net/ppp/Makefile b/tools/testing/selftests/net/ppp/Makefile
index b39b0abadde6..6036fa134351 100644
--- a/tools/testing/selftests/net/ppp/Makefile
+++ b/tools/testing/selftests/net/ppp/Makefile
@@ -5,6 +5,7 @@ top_srcdir = ../../../../..
TEST_PROGS := \
ppp_async.sh \
pppoe.sh \
+ pppol2tp.sh \
# end of TEST_PROGS
TEST_FILES := \
diff --git a/tools/testing/selftests/net/ppp/config b/tools/testing/selftests/net/ppp/config
index b45d25c5b970..843545df8f03 100644
--- a/tools/testing/selftests/net/ppp/config
+++ b/tools/testing/selftests/net/ppp/config
@@ -1,4 +1,5 @@
CONFIG_IPV6=y
+CONFIG_L2TP=m
CONFIG_PACKET=y
CONFIG_PPP=m
CONFIG_PPP_ASYNC=m
@@ -6,4 +7,5 @@ CONFIG_PPP_BSDCOMP=m
CONFIG_PPP_DEFLATE=m
CONFIG_PPPOE=m
CONFIG_PPPOE_HASH_BITS_4=y
+CONFIG_PPPOL2TP=m
CONFIG_VETH=y
diff --git a/tools/testing/selftests/net/ppp/pppoe-server-options b/tools/testing/selftests/net/ppp/pppoe-server-options
index 66c8c9d319e9..cd586be7061b 100644
--- a/tools/testing/selftests/net/ppp/pppoe-server-options
+++ b/tools/testing/selftests/net/ppp/pppoe-server-options
@@ -1,2 +1,3 @@
noauth
noipdefault
+nomagic
diff --git a/tools/testing/selftests/net/ppp/pppol2tp.sh b/tools/testing/selftests/net/ppp/pppol2tp.sh
new file mode 100755
index 000000000000..37c4d56c5c6e
--- /dev/null
+++ b/tools/testing/selftests/net/ppp/pppol2tp.sh
@@ -0,0 +1,110 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+source ppp_common.sh
+
+VETH_SERVER="veth-server"
+VETH_CLIENT="veth-client"
+OUTER_IP_SERVER="172.16.1.1"
+OUTER_IP_CLIENT="172.16.1.2"
+
+PPPOL2TP_DIR=$(mktemp -d /tmp/pppol2tp.XXXXXX)
+PPPOL2TP_LOG="$PPPOL2TP_DIR/l2tp.log"
+
+# shellcheck disable=SC2329
+cleanup() {
+ cleanup_all_ns
+ [ -n "$SOCAT_PID" ] && kill_process "$SOCAT_PID"
+ rm -rf "$PPPOL2TP_DIR"
+}
+
+trap cleanup EXIT
+
+require_command xl2tpd
+ppp_common_init
+modprobe -q l2tp_ppp
+
+# Create the veth pair
+ip link add "$VETH_SERVER" type veth peer name "$VETH_CLIENT"
+ip link set "$VETH_SERVER" netns "$NS_SERVER"
+ip link set "$VETH_CLIENT" netns "$NS_CLIENT"
+ip -netns "$NS_SERVER" link set "$VETH_SERVER" up
+ip -netns "$NS_CLIENT" link set "$VETH_CLIENT" up
+ip -netns "$NS_SERVER" address add dev "$VETH_SERVER" "$OUTER_IP_SERVER" peer "$OUTER_IP_CLIENT"
+ip -netns "$NS_CLIENT" address add dev "$VETH_CLIENT" "$OUTER_IP_CLIENT" peer "$OUTER_IP_SERVER"
+
+# Start socat as syslog listener
+socat -v -u UNIX-RECV:/dev/log OPEN:/dev/null > "$PPPOL2TP_LOG" 2>&1 &
+SOCAT_PID=$!
+
+# Generate configuration files
+cat > "$PPPOL2TP_DIR/l2tp-server.conf" <<EOF
+[global]
+listen-addr = $OUTER_IP_SERVER
+access control = no
+
+[lns default]
+ip range = $IP_CLIENT
+local ip = $IP_SERVER
+require authentication = no
+require chap = no
+require pap = no
+ppp debug = yes
+pppoptfile = $(pwd)/pppoe-server-options
+EOF
+
+cat > "$PPPOL2TP_DIR/l2tp-client.conf" <<EOF
+[global]
+listen-addr = $OUTER_IP_CLIENT
+access control = no
+
+[lac server]
+lns = $OUTER_IP_SERVER
+require authentication = no
+require chap = no
+require pap = no
+ppp debug = yes
+pppoptfile = $(pwd)/pppoe-server-options
+EOF
+
+# Start the L2TP Server
+ip netns exec "$NS_SERVER" xl2tpd -D -c "$PPPOL2TP_DIR/l2tp-server.conf" \
+ -p "$PPPOL2TP_DIR/l2tp-server.pid" -C "$PPPOL2TP_DIR/l2tp-server.control" &
+
+# Start the L2TP Client
+ip netns exec "$NS_CLIENT" xl2tpd -D -c "$PPPOL2TP_DIR/l2tp-client.conf" \
+ -p "$PPPOL2TP_DIR/l2tp-client.pid" -C "$PPPOL2TP_DIR/l2tp-client.control" &
+
+# Wait for xl2tpd to start and open their control pipes
+slowwait 2 [ -p "$PPPOL2TP_DIR/l2tp-server.control" ]
+slowwait 2 [ -p "$PPPOL2TP_DIR/l2tp-client.control" ]
+
+# Connect LAC to LNS
+echo "c server" > "$PPPOL2TP_DIR/l2tp-client.control"
+
+ppp_test_connectivity
+
+log_test "PPPoL2TP"
+
+# Recursion test
+RET=0
+# Delete route to LNS IP
+ip -netns "$NS_CLIENT" route del "$OUTER_IP_SERVER"
+# Add default route through ppp0
+ip -netns "$NS_CLIENT" route add default dev ppp0
+# ping (we expect the ping to fail but not deadlock the system)
+ip netns exec "$NS_CLIENT" ping -c 1 "$IP_SERVER" -w 1
+check_fail $?
+
+log_test "PPPoL2TP Recursion"
+
+# Dump syslog messages if the test failed
+if [ "$EXIT_STATUS" -ne 0 ]; then
+ while read -r _sign _date _time len _from _to
+ do len=${len##*=}
+ read -n "$len" -r LINE
+ echo "$LINE"
+ done < "$PPPOL2TP_LOG"
+fi
+
+exit "$EXIT_STATUS"
diff --git a/tools/testing/selftests/net/protodown.sh b/tools/testing/selftests/net/protodown.sh
new file mode 100755
index 000000000000..0a7b78c63c37
--- /dev/null
+++ b/tools/testing/selftests/net/protodown.sh
@@ -0,0 +1,182 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test the protodown mechanism. Verify basic protodown toggling, protodown
+# reasons, operational state when the lower device carrier changes, and correct
+# operational state when the lower device has no carrier.
+
+# shellcheck disable=SC1091,SC2034,SC2154,SC2317
+source lib.sh
+
+require_command jq
+
+ALL_TESTS="
+ protodown_basic_macvlan
+ protodown_basic_vxlan
+ protodown_reasons
+ protodown_lower_toggle
+ protodown_lower_down
+"
+
+operstate_get()
+{
+ local ns=$1; shift
+ local dev=$1; shift
+
+ ip -n "$ns" -j link show dev "$dev" | jq -r '.[].operstate'
+}
+
+operstate_check()
+{
+ local ns=$1; shift
+ local dev=$1; shift
+ local expected=$1; shift
+
+ local current
+ current=$(operstate_get "$ns" "$dev")
+
+ [ "$current" = "$expected" ]
+}
+
+setup_prepare()
+{
+ setup_ns NS
+ defer cleanup_all_ns
+
+ ip -n "$NS" link add name dummy0 up type dummy
+
+ ip -n "$NS" link add name macvlan0 link dummy0 up type macvlan mode bridge
+
+ ip -n "$NS" link add name vxlan0 up type vxlan id 10010 dstport 4789
+}
+
+protodown_basic()
+{
+ local dev=$1; shift
+
+ ip -n "$NS" link set dev "$dev" protodown on
+ check_err $? "Failed to set protodown on"
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" "$dev" DOWN
+ check_err $? "Operational state is not DOWN after setting protodown"
+
+ ip -n "$NS" link set dev "$dev" protodown off
+ check_err $? "Failed to set protodown off"
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" "$dev" UP
+ check_err $? "Operational state is not UP after clearing protodown"
+}
+
+protodown_basic_macvlan()
+{
+ RET=0
+
+ protodown_basic macvlan0
+
+ log_test "Basic protodown on/off with macvlan"
+}
+
+protodown_basic_vxlan()
+{
+ RET=0
+
+ protodown_basic vxlan0
+
+ log_test "Basic protodown on/off with vxlan"
+}
+
+protodown_reasons()
+{
+ RET=0
+
+ ip -n "$NS" link set dev macvlan0 protodown on
+
+ ip -n "$NS" link set dev macvlan0 protodown_reason 0 on
+ check_err $? "Failed to set protodown reason bit 0"
+
+ # Cannot clear protodown while reasons are active.
+ ip -n "$NS" link set dev macvlan0 protodown off 2>/dev/null
+ check_fail $? "Clearing protodown succeeded with active reasons"
+
+ ip -n "$NS" link set dev macvlan0 protodown_reason 0 off
+ check_err $? "Failed to clear protodown reason bit 0"
+
+ # Can clear protodown when no reasons are active.
+ ip -n "$NS" link set dev macvlan0 protodown off
+ check_err $? "Failed to clear protodown with no active reasons"
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 UP
+ check_err $? "Operational state is not UP after clearing protodown"
+
+ log_test "Protodown reasons"
+}
+
+protodown_lower_toggle()
+{
+ RET=0
+
+ ip -n "$NS" link set dev macvlan0 protodown on
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 DOWN
+ check_err $? "Operational state is not DOWN after setting protodown"
+
+ # Toggle carrier on the lower device. The macvlan should stay DOWN
+ # because protodown is on.
+ ip -n "$NS" link set dev dummy0 carrier off
+ ip -n "$NS" link set dev dummy0 carrier on
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" dummy0 UP
+ check_err $? "Lower device is not UP after carrier on"
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 DOWN
+ check_err $? "Macvlan operational state is not DOWN despite protodown"
+
+ # Clear protodown and verify the macvlan comes back up.
+ ip -n "$NS" link set dev macvlan0 protodown off
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 UP
+ check_err $? "Operational state is not UP after clearing protodown"
+
+ log_test "Protodown with lower device toggled"
+}
+
+protodown_lower_down()
+{
+ RET=0
+
+ # Bring the lower device carrier down first.
+ ip -n "$NS" link set dev dummy0 carrier off
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 LOWERLAYERDOWN
+ check_err $? "Macvlan is not LOWERLAYERDOWN with lower carrier off"
+
+ # Toggle protodown on and off while lower has no carrier. The macvlan
+ # should not transition to UP.
+ ip -n "$NS" link set dev macvlan0 protodown on
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 LOWERLAYERDOWN
+ check_err $? "Macvlan is not LOWERLAYERDOWN after setting protodown"
+
+ ip -n "$NS" link set dev macvlan0 protodown off
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 LOWERLAYERDOWN
+ check_err $? "Macvlan is not LOWERLAYERDOWN after clearing protodown"
+
+ # Bring the lower device carrier up. The macvlan should transition to
+ # UP.
+ ip -n "$NS" link set dev dummy0 carrier on
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" dummy0 UP
+ check_err $? "Lower device is not UP after carrier on"
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 UP
+ check_err $? "Macvlan is not UP after lower device is UP"
+
+ log_test "Protodown with lower device down"
+}
+
+trap defer_scopes_cleanup EXIT
+setup_prepare
+tests_run
+
+exit "$EXIT_STATUS"
diff --git a/tools/testing/selftests/net/rds/.gitignore b/tools/testing/selftests/net/rds/.gitignore
index 1c6f04e2aa11..7ca4b1440f51 100644
--- a/tools/testing/selftests/net/rds/.gitignore
+++ b/tools/testing/selftests/net/rds/.gitignore
@@ -1 +1,2 @@
include.sh
+getsockopt
diff --git a/tools/testing/selftests/net/rds/Makefile b/tools/testing/selftests/net/rds/Makefile
index fe363be8e358..ab9e92399a6d 100644
--- a/tools/testing/selftests/net/rds/Makefile
+++ b/tools/testing/selftests/net/rds/Makefile
@@ -3,7 +3,9 @@
all:
@echo mk_build_dir="$(shell pwd)" > include.sh
-TEST_PROGS := run.sh
+TEST_PROGS := rds_run.sh
+
+TEST_GEN_PROGS := getsockopt
TEST_FILES := \
include.sh \
@@ -16,4 +18,6 @@ EXTRA_CLEAN := \
/tmp/rds_logs \
# end of EXTRA_CLEAN
+CFLAGS += $(KHDR_INCLUDES)
+
include ../../lib.mk
diff --git a/tools/testing/selftests/net/rds/README.txt b/tools/testing/selftests/net/rds/README.txt
index c6fe003d503b..8aa41148b1b5 100644
--- a/tools/testing/selftests/net/rds/README.txt
+++ b/tools/testing/selftests/net/rds/README.txt
@@ -1,21 +1,27 @@
RDS self-tests
==============
-These scripts provide a coverage test for RDS-TCP by creating two
-network namespaces and running rds packets between them. A loopback
-network is provisioned with optional probability of packet loss or
-corruption. A workload of 50000 hashes, each 64 characters in size,
-are passed over an RDS socket on this test network. A passing test means
-the RDS-TCP stack was able to recover properly. The provided config.sh
-can be used to compile the kernel with the necessary gcov options. The
-kernel may optionally be configured to omit the coverage report as well.
+These scripts provide a coverage test for RDS-TCP and RDS-RDMA (over
+RoCE/RXE) by setting up two endpoints and running RDS packets between
+them. The TCP path creates two network namespaces; the RDMA path uses
+an RXE (soft RoCE) device backed by a veth pair. A workload of 50000
+hashes, each 64 characters in size, is passed over an RDS socket on
+this test network with an optional probability of packet loss or
+corruption. A passing test means the RDS stack was able to recover
+properly. The provided config.sh can be used to compile the kernel
+with the necessary gcov options; pass -r to also enable the kernel
+configs required for the RDMA transport. The kernel may optionally be
+configured to omit the coverage report as well.
USAGE:
- run.sh [-d logdir] [-l packet_loss] [-c packet_corruption]
- [-u packet_duplcate]
+ rds_run.sh [-d logdir] [-l packet_loss] [-c packet_corruption]
+ [-u packet_duplicate] [-t timeout]
+ [-T tcp|rdma|tcp,rdma]
OPTIONS:
- -d Log directory. Defaults to tools/testing/selftests/net/rds/rds_logs
+ -d Log directory. If set, logs will be stored in the
+ given dir, or skipped if unset. Log dir can also be
+ set through the RDS_LOG_DIR env variable
-l Simulates a percentage of packet loss
@@ -23,11 +29,36 @@ OPTIONS:
-u Simulates a percentage of packet duplication.
+ -t Test timeout. Defaults to tools/testing/selftests/net/rds/settings
+
+ -T Comma-separated list of transports to test. Accepts
+ "tcp", "rdma", or "tcp,rdma". Defaults to "tcp". Use
+ config.sh -r to enable required RDMA configs
+
+ENV VARIABLES:
+ RDS_LOG_DIR Log directory. If set, logs will be stored in
+ the given dir, or skipped if unset. Log dir
+ can also be set with the -d flag.
+
+ Use with --rwdir on the CI path to retain logs after
+ test compleation. Log dir end point must be within
+ the specified --rwdir path for logs to persist on
+ the host.
+
+ SUDO_USER The user name that should be used for tcpdump
+ --relinquish-privileges. Set this to a user
+ belonging to the sudoers group to avoid drop
+ privilege errors with the vng 9p filesystem
+ which may result in empty pcaps
+
EXAMPLE:
# Create a suitable gcov enabled .config
tools/testing/selftests/net/rds/config.sh -g
+ # Optionally add RDMA configs (CONFIG_RDS_RDMA, CONFIG_RDMA_RXE)
+ tools/testing/selftests/net/rds/config.sh -r
+
# Alternatly create a gcov disabled .config
tools/testing/selftests/net/rds/config.sh
@@ -39,6 +70,8 @@ EXAMPLE:
# launch the tests in a VM
vng -v --rwdir ./ --run . --user root --cpus 4 -- \
- "export PYTHONPATH=tools/testing/selftests/net/; tools/testing/selftests/net/rds/run.sh"
+ "export PYTHONPATH=tools/testing/selftests/net/; \
+ export SUDO_USER=example_user; \
+ export RDS_LOG_DIR=tools/testing/selftests/net/rds/rds_logs; \
+ tools/testing/selftests/net/rds/rds_run.sh -T tcp,rdma"
-An HTML coverage report will be output in tools/testing/selftests/net/rds/rds_logs/coverage/.
diff --git a/tools/testing/selftests/net/rds/config b/tools/testing/selftests/net/rds/config
index 3d62d0c750a8..97db7ecb892a 100644
--- a/tools/testing/selftests/net/rds/config
+++ b/tools/testing/selftests/net/rds/config
@@ -1,4 +1,3 @@
-CONFIG_MODULES=n
CONFIG_NET_NS=y
CONFIG_NET_SCH_NETEM=y
CONFIG_RDS=y
diff --git a/tools/testing/selftests/net/rds/config.sh b/tools/testing/selftests/net/rds/config.sh
index 29a79314dd60..2df2226310ef 100755
--- a/tools/testing/selftests/net/rds/config.sh
+++ b/tools/testing/selftests/net/rds/config.sh
@@ -10,7 +10,8 @@ CONF_FILE=""
FLAGS=()
GENERATE_GCOV_REPORT=0
-while getopts "gc:" opt; do
+ENABLE_RDMA=0
+while getopts "gc:r" opt; do
case ${opt} in
g)
GENERATE_GCOV_REPORT=1
@@ -18,8 +19,11 @@ while getopts "gc:" opt; do
c)
CONF_FILE=$OPTARG
;;
+ r)
+ ENABLE_RDMA=1
+ ;;
:)
- echo "USAGE: config.sh [-g] [-c config]"
+ echo "USAGE: config.sh [-g] [-c config] [-r]"
exit 1
;;
?)
@@ -33,9 +37,6 @@ if [[ "$CONF_FILE" != "" ]]; then
FLAGS=(--file "$CONF_FILE")
fi
-# no modules
-scripts/config "${FLAGS[@]}" --disable CONFIG_MODULES
-
# enable RDS
scripts/config "${FLAGS[@]}" --enable CONFIG_RDS
scripts/config "${FLAGS[@]}" --enable CONFIG_RDS_TCP
@@ -58,3 +59,10 @@ scripts/config "${FLAGS[@]}" --enable CONFIG_VETH
# simulate packet loss
scripts/config "${FLAGS[@]}" --enable CONFIG_NET_SCH_NETEM
+if [ "$ENABLE_RDMA" -eq 1 ]; then
+ # enable RDS over InfiniBand / RDMA (rds_rdma test)
+ scripts/config "${FLAGS[@]}" --enable CONFIG_INFINIBAND
+ scripts/config "${FLAGS[@]}" --enable CONFIG_INFINIBAND_ADDR_TRANS
+ scripts/config "${FLAGS[@]}" --enable CONFIG_RDMA_RXE
+ scripts/config "${FLAGS[@]}" --enable CONFIG_RDS_RDMA
+fi
diff --git a/tools/testing/selftests/net/rds/getsockopt.c b/tools/testing/selftests/net/rds/getsockopt.c
new file mode 100644
index 000000000000..93ff252c69b8
--- /dev/null
+++ b/tools/testing/selftests/net/rds/getsockopt.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Exercise the RDS getsockopt() paths that were converted to the
+ * getsockopt_iter() / sockopt_t callback.
+ *
+ * Three distinct paths are covered:
+ *
+ * - RDS_RECVERR and SO_RDS_TRANSPORT, which now return their int value
+ * through copy_to_iter() and report the written length in opt->optlen.
+ *
+ * - RDS_INFO_*, which pins the userspace buffer with
+ * iov_iter_extract_pages() (including a non-zero starting page offset)
+ * and lets the info producers memcpy the snapshot in under a spinlock.
+ *
+ * The kvec (in-kernel buffer) -> -EOPNOTSUPP path of rds_info_getsockopt()
+ * is not reachable from a userspace getsockopt() and so is not tested here.
+ */
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <linux/rds.h>
+
+#include "../../kselftest_harness.h"
+
+#ifndef AF_RDS
+#define AF_RDS 21
+#endif
+
+FIXTURE(rds) {
+ int fd;
+};
+
+FIXTURE_SETUP(rds)
+{
+ self->fd = socket(AF_RDS, SOCK_SEQPACKET, 0);
+ if (self->fd < 0)
+ SKIP(return, "AF_RDS unavailable (errno %d) - load the rds module",
+ errno);
+}
+
+FIXTURE_TEARDOWN(rds)
+{
+ if (self->fd >= 0)
+ close(self->fd);
+}
+
+/* RDS_RECVERR defaults to 0 and is reported back as a 4-byte int. */
+TEST_F(rds, recverr_default)
+{
+ socklen_t len = sizeof(int);
+ int val = 0xdeadbeef;
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, &len));
+ EXPECT_EQ(sizeof(int), len);
+ EXPECT_EQ(0, val);
+}
+
+/* A value set via setsockopt() must be readable back unchanged. */
+TEST_F(rds, recverr_set_get)
+{
+ socklen_t len = sizeof(int);
+ int val = 1;
+
+ ASSERT_EQ(0, setsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, len));
+
+ val = 0;
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, &len));
+ EXPECT_EQ(sizeof(int), len);
+ EXPECT_EQ(1, val);
+}
+
+/* A buffer smaller than an int is rejected with EINVAL, not silently. */
+TEST_F(rds, recverr_short_buffer)
+{
+ socklen_t len = sizeof(int) - 1;
+ char buf[sizeof(int)];
+
+ EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, buf, &len));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+/* An unbound socket reports RDS_TRANS_NONE for SO_RDS_TRANSPORT. */
+TEST_F(rds, transport_unbound)
+{
+ socklen_t len = sizeof(int);
+ int val = 0;
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, SO_RDS_TRANSPORT, &val,
+ &len));
+ EXPECT_EQ(sizeof(int), len);
+ EXPECT_EQ(RDS_TRANS_NONE, (unsigned int)val);
+}
+
+TEST_F(rds, transport_short_buffer)
+{
+ socklen_t len = sizeof(int) - 1;
+ char buf[sizeof(int)];
+
+ EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, SO_RDS_TRANSPORT, buf,
+ &len));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+/*
+ * RDS_INFO_COUNTERS with a zero-length buffer is the "probe" call: it must
+ * fail with ENOSPC and report the required snapshot size in optlen.
+ */
+TEST_F(rds, info_counters_probe)
+{
+ socklen_t len = 0;
+
+ EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL,
+ &len));
+ EXPECT_EQ(ENOSPC, errno);
+ EXPECT_GT(len, 0);
+ /* The snapshot is an array of fixed-size counter records. */
+ EXPECT_EQ(0, len % (socklen_t)sizeof(struct rds_info_counter));
+}
+
+/*
+ * A real snapshot into an unaligned userspace buffer exercises the
+ * iov_iter_extract_pages() path, including the non-zero offset0 handling
+ * that the patch reworked. Place the buffer at a non-page-aligned address
+ * spanning into the next page to make sure multi-page pinning works too.
+ */
+TEST_F(rds, info_counters_snapshot)
+{
+ struct rds_info_counter *ctr;
+ socklen_t need = 0, len;
+ long pagesz = sysconf(_SC_PAGESIZE);
+ size_t offset, map_len;
+ unsigned int i, n;
+ char *region, *buf;
+ int ret;
+
+ /* Probe for the required size. */
+ getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL, &need);
+ ASSERT_GT(need, 0);
+
+ /*
+ * Place the buffer at a non-page-aligned offset that runs past the
+ * first page boundary, and size the mapping from the probed length so
+ * the test keeps working if the counter set grows.
+ */
+ offset = pagesz - 64;
+ map_len = ((offset + need + pagesz - 1) / pagesz) * pagesz;
+
+ region = mmap(NULL, map_len, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ ASSERT_NE(MAP_FAILED, region);
+
+ buf = region + offset;
+
+ /*
+ * On success the RDS_INFO path returns the positive per-element size
+ * (lens.each) rather than 0, and writes the full snapshot length back
+ * into optlen.
+ */
+ len = need;
+ ret = getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, buf, &len);
+ ASSERT_GE(ret, 0) {
+ TH_LOG("getsockopt snapshot failed: errno %d", errno);
+ }
+ EXPECT_EQ(sizeof(struct rds_info_counter), ret);
+ EXPECT_EQ(need, len);
+
+ /* The counter names must be NUL-terminated, non-empty strings. */
+ ctr = (struct rds_info_counter *)buf;
+ n = len / sizeof(*ctr);
+ ASSERT_GT(n, 0);
+ for (i = 0; i < n; i++) {
+ size_t namelen = strnlen((char *)ctr[i].name,
+ sizeof(ctr[i].name));
+
+ EXPECT_GT(namelen, 0);
+ EXPECT_LT(namelen, sizeof(ctr[i].name));
+ }
+
+ munmap(region, map_len);
+}
+
+/*
+ * A non-zero but too-small buffer must report ENOSPC and the full required
+ * length, without corrupting memory past the buffer.
+ */
+TEST_F(rds, info_counters_short_buffer)
+{
+ socklen_t need = 0, len;
+ char small[sizeof(struct rds_info_counter)];
+
+ getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL, &need);
+ ASSERT_GT(need, 0);
+
+ /* Ask with a buffer guaranteed smaller than the full snapshot. */
+ if (need <= (socklen_t)sizeof(small))
+ SKIP(return, "snapshot fits in one record; nothing to test");
+
+ len = 1; /* < sizeof(struct rds_info_counter) */
+ EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, small,
+ &len));
+ EXPECT_EQ(ENOSPC, errno);
+ EXPECT_EQ(need, len);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/net/rds/rds_run.sh b/tools/testing/selftests/net/rds/rds_run.sh
new file mode 100755
index 000000000000..cdf487ec97dc
--- /dev/null
+++ b/tools/testing/selftests/net/rds/rds_run.sh
@@ -0,0 +1,319 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+set -u
+
+unset KBUILD_OUTPUT
+
+current_dir="$(realpath "$(dirname "$0")")"
+build_dir="$current_dir"
+
+build_include="$current_dir/include.sh"
+if test -f "$build_include"; then
+ # this include will define "$mk_build_dir" as the location the test was
+ # built. We will need this if the tests are installed in a location
+ # other than the kernel source
+
+ source "$build_include"
+ build_dir="$mk_build_dir"
+fi
+
+# Source settings for timeout value (also used by ksft runner)
+source "$current_dir"/settings
+
+# This test requires kernel source and the *.gcda data therein
+# Locate the top level of the kernel source, and the net/rds
+# subfolder with the appropriate *.gcno object files
+ksrc_dir="$(realpath "$build_dir"/../../../../../)"
+kconfig="$ksrc_dir/.config"
+obj_dir="$ksrc_dir/net/rds"
+
+GCOV_CMD=gcov
+
+#check to see if the host has the required packages to generate a gcov report
+check_gcov_env()
+{
+ if ! which "$GCOV_CMD" > /dev/null 2>&1; then
+ echo "# Warning: Could not find gcov. "
+ GENERATE_GCOV_REPORT=0
+ return
+ fi
+
+ # the gcov version must match the gcc version
+ GCC_VER=$(gcc -dumpfullversion)
+ GCOV_VER=$($GCOV_CMD -v | grep gcov | awk '{print $3}'| awk 'BEGIN {FS="-"}{print $1}')
+ if [ "$GCOV_VER" != "$GCC_VER" ]; then
+ #attempt to find a matching gcov version
+ GCOV_CMD=gcov-$(gcc -dumpversion)
+
+ if ! which "$GCOV_CMD" > /dev/null 2>&1; then
+ echo "# Warning: Could not find an appropriate gcov installation. \
+ gcov version must match gcc version"
+ GENERATE_GCOV_REPORT=0
+ return
+ fi
+
+ #recheck version number of found gcov executable
+ GCOV_VER=$($GCOV_CMD -v | grep gcov | awk '{print $3}'| \
+ awk 'BEGIN {FS="-"}{print $1}')
+ if [ "$GCOV_VER" != "$GCC_VER" ]; then
+ echo "# Warning: Could not find an appropriate gcov installation. \
+ gcov version must match gcc version"
+ GENERATE_GCOV_REPORT=0
+ else
+ echo "# Warning: Mismatched gcc and gcov detected. Using $GCOV_CMD"
+ fi
+ fi
+}
+
+# Check to see if the kconfig has the required configs to generate a coverage report
+check_gcov_conf()
+{
+ if ! grep -x "CONFIG_GCOV_PROFILE_RDS=y" "$kconfig" > /dev/null 2>&1; then
+ echo "# INFO: CONFIG_GCOV_PROFILE_RDS should be enabled for coverage reports"
+ GENERATE_GCOV_REPORT=0
+ fi
+ if ! grep -x "CONFIG_GCOV_KERNEL=y" "$kconfig" > /dev/null 2>&1; then
+ echo "# INFO: CONFIG_GCOV_KERNEL should be enabled for coverage reports"
+ GENERATE_GCOV_REPORT=0
+ fi
+ if grep -x "CONFIG_GCOV_PROFILE_ALL=y" "$kconfig" > /dev/null 2>&1; then
+ echo "# INFO: CONFIG_GCOV_PROFILE_ALL should be disabled for coverage reports"
+ GENERATE_GCOV_REPORT=0
+ fi
+
+ if [ "$GENERATE_GCOV_REPORT" -eq 0 ]; then
+ echo "# To enable gcov reports, please run "\
+ "\"tools/testing/selftests/net/rds/config.sh -g\" and rebuild the kernel"
+ else
+ # if we have the required kernel configs, proceed to check the environment to
+ # ensure we have the required gcov packages
+ check_gcov_env
+ fi
+}
+
+# Checks if a kconfig is enabled (set to =y or =m)
+# $1: kconfig symbol to check
+# $2: (optional) module name backing $1
+# Ex: check_conf_enabled CONFIG_RDS_TCP rds_tcp
+# Modules for configs set to =m will be probed
+# If omitted, only a built-in (=y) config is accepted.
+# Returns on success. exits 4 on failure
+# Kselftest framework requirement - SKIP code is 4.
+check_conf_enabled() {
+ if grep -x "$1=y" "$kconfig" > /dev/null 2>&1; then
+ return
+ fi
+ if [ -n "${2:-}" ] && grep -x "$1=m" "$kconfig" > /dev/null 2>&1; then
+ probe_module "$2"
+ return
+ fi
+ echo "selftests: [SKIP] This test requires $1 enabled"
+ echo "Please run" \
+ "tools/testing/selftests/net/rds/config.sh and rebuild the kernel"
+ exit 4
+}
+
+check_rdma_conf_enabled() {
+ if grep -x "$1=y" "$kconfig" > /dev/null 2>&1; then
+ return
+ fi
+ if [ -n "${2:-}" ] && grep -x "$1=m" "$kconfig" > /dev/null 2>&1; then
+ probe_module "$2"
+ return
+ fi
+ echo "selftests: [XFAIL] rdma transport requires $1 enabled"
+ echo "To enable, run" \
+ "tools/testing/selftests/net/rds/config.sh -r and rebuild"
+ exit 2
+}
+
+# Load the module backing a config that is built as a loadable module
+# (=m). Built-in (=y) configs are already available and don't reach
+# here. Exits with the SKIP code if a required module cannot be loaded.
+probe_module() {
+ if ! modprobe -q "$1"; then
+ echo "selftests: [SKIP] could not load required module $1"
+ exit 4
+ fi
+}
+
+check_conf() {
+ check_conf_enabled CONFIG_NET_SCH_NETEM sch_netem
+ check_conf_enabled CONFIG_VETH veth
+ check_conf_enabled CONFIG_NET_NS
+ check_conf_enabled CONFIG_RDS_TCP rds_tcp
+ check_conf_enabled CONFIG_RDS rds
+}
+
+# Check kernel config and host environment for RDS-RDMA support.
+# Exits with XFAIL (2) if the user requested rdma but prerequisites
+# are not met.
+check_rdma_conf()
+{
+ case "$TRANSPORT" in
+ *rdma*) ;;
+ *) return ;;
+ esac
+
+ # Kconfig will enforce CONFIG_INFINIBAND_* as dependencies
+ # of CONFIG_RDMA_RXE
+ check_rdma_conf_enabled CONFIG_RDMA_RXE rdma_rxe
+ check_rdma_conf_enabled CONFIG_RDS_RDMA rds_rdma
+
+ if ! which rdma > /dev/null 2>&1; then
+ echo "selftests: [XFAIL] rdma transport requires the 'rdma'" \
+ "tool (iproute2)"
+ exit 2
+ fi
+}
+
+check_env()
+{
+ if ! test -d "$obj_dir"; then
+ echo "selftests: [SKIP] This test requires a kernel source tree"
+ exit 4
+ fi
+ if ! test -e "$kconfig"; then
+ echo "selftests: [SKIP] This test requires a configured kernel source tree"
+ exit 4
+ fi
+ if ! which strace > /dev/null 2>&1; then
+ echo "selftests: [SKIP] Could not run test without strace"
+ exit 4
+ fi
+ if ! which tcpdump > /dev/null 2>&1; then
+ echo "selftests: [SKIP] Could not run test without tcpdump"
+ exit 4
+ fi
+
+ if ! which python3 > /dev/null 2>&1; then
+ echo "selftests: [SKIP] Could not run test without python3"
+ exit 4
+ fi
+
+ python_major=$(python3 -c "import sys; print(sys.version_info[0])")
+ python_minor=$(python3 -c "import sys; print(sys.version_info[1])")
+ if [[ python_major -lt 3 || ( python_major -eq 3 && python_minor -lt 9 ) ]] ; then
+ echo "selftests: [SKIP] Could not run test without at least python3.9"
+ python3 -V
+ exit 4
+ fi
+}
+
+LOG_DIR="${RDS_LOG_DIR:-}"
+TIMEOUT=$timeout
+GENERATE_GCOV_REPORT=1
+TRANSPORT=tcp
+FLAGS=()
+
+while getopts "d:l:c:u:t:T:" opt; do
+ case ${opt} in
+ d)
+ LOG_DIR=${OPTARG}
+ ;;
+ l)
+ FLAGS+=("-l" "${OPTARG}")
+ ;;
+ c)
+ FLAGS+=("-c" "${OPTARG}")
+ ;;
+ t)
+ TIMEOUT=${OPTARG}
+ ;;
+ u)
+ FLAGS+=("-u" "${OPTARG}")
+ ;;
+ T)
+ TRANSPORT=${OPTARG}
+ ;;
+ :)
+ echo "USAGE: rds_run.sh [-d logdir] [-l packet_loss]" \
+ "[-c packet_corruption] [-u packet_duplicate] [-t timeout]" \
+ "[-T tcp|rdma|tcp,rdma]"
+ exit 1
+ ;;
+ ?)
+ echo "Invalid option: -${OPTARG}."
+ exit 1
+ ;;
+ esac
+done
+
+# Validate transport tokens
+IFS=',' read -ra transports <<< "$TRANSPORT"
+for t in "${transports[@]}"; do
+ if [ "$t" != "tcp" ] && [ "$t" != "rdma" ]; then
+ echo "rds_run.sh: unknown transport '$t' (expected tcp or rdma)"
+ exit 1
+ fi
+done
+
+FLAGS+=("--transport" "${TRANSPORT}")
+
+check_env
+check_conf
+check_gcov_conf
+check_rdma_conf
+
+TRACE_CMD=()
+if [[ -n "$LOG_DIR" ]]; then
+ FLAGS+=("-d" "$LOG_DIR")
+
+ TRACE_FILE="${LOG_DIR}/rds-strace.txt"
+ COVR_DIR="${LOG_DIR}/coverage/"
+ DMESG_FILE="${LOG_DIR}/rds-dmesg.out"
+
+ mkdir -p "$LOG_DIR"
+ mkdir -p "$COVR_DIR"
+
+ rm -f "$TRACE_FILE"
+ rm -f "$DMESG_FILE"
+ rm -f "$LOG_DIR"/rds-*.pcap
+ rm -f "$COVR_DIR"/gcovr*
+
+ echo "# Traces will be logged to ${TRACE_FILE}"
+ TRACE_CMD=(strace -T -tt -o "${TRACE_FILE}")
+fi
+
+set +e
+echo "# running RDS tests..."
+"${TRACE_CMD[@]}" python3 "$(dirname "$0")/test.py" "${FLAGS[@]}" -t "$TIMEOUT"
+
+test_rc=$?
+
+if [[ -n "$LOG_DIR" ]]; then
+ dmesg > "${DMESG_FILE}"
+fi
+
+if [[ -n "$LOG_DIR" ]] && [ "$GENERATE_GCOV_REPORT" -eq 1 ]; then
+ echo "# saving coverage data..."
+
+ # Ensure debugfs is mounted before reading gcov data.
+ if ! mountpoint -q /sys/kernel/debug 2>/dev/null; then
+ mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null || true
+ fi
+
+ (set +x; cd /sys/kernel/debug/gcov; find ./* -name '*.gcda' | \
+ while read -r f
+ do
+ cat < "/sys/kernel/debug/gcov/$f" > "/$f"
+ done)
+
+ echo "# running gcovr..."
+ gcovr -s --html-details --gcov-executable "$GCOV_CMD" --gcov-ignore-parse-errors \
+ --root "${ksrc_dir}" -o "${COVR_DIR}/gcovr" "${ksrc_dir}/net/rds/" \
+ > "${LOG_DIR}/gcovr.log" 2>&1
+ echo "# gcovr log: ${LOG_DIR}/gcovr.log"
+else
+ echo "# Coverage report will be skipped"
+fi
+
+if [ "$test_rc" -eq 0 ]; then
+ echo "# PASS: Test completed successfully"
+else
+ echo "# FAIL: Test failed"
+fi
+
+exit "$test_rc"
diff --git a/tools/testing/selftests/net/rds/run.sh b/tools/testing/selftests/net/rds/run.sh
deleted file mode 100755
index 897d17d1b8db..000000000000
--- a/tools/testing/selftests/net/rds/run.sh
+++ /dev/null
@@ -1,227 +0,0 @@
-#! /bin/bash
-# SPDX-License-Identifier: GPL-2.0
-
-set -e
-set -u
-
-unset KBUILD_OUTPUT
-
-current_dir="$(realpath "$(dirname "$0")")"
-build_dir="$current_dir"
-
-build_include="$current_dir/include.sh"
-if test -f "$build_include"; then
- # this include will define "$mk_build_dir" as the location the test was
- # built. We will need this if the tests are installed in a location
- # other than the kernel source
-
- source "$build_include"
- build_dir="$mk_build_dir"
-fi
-
-# Source settings for timeout value (also used by ksft runner)
-source "$current_dir"/settings
-
-# This test requires kernel source and the *.gcda data therein
-# Locate the top level of the kernel source, and the net/rds
-# subfolder with the appropriate *.gcno object files
-ksrc_dir="$(realpath "$build_dir"/../../../../../)"
-kconfig="$ksrc_dir/.config"
-obj_dir="$ksrc_dir/net/rds"
-
-GCOV_CMD=gcov
-
-#check to see if the host has the required packages to generate a gcov report
-check_gcov_env()
-{
- if ! which "$GCOV_CMD" > /dev/null 2>&1; then
- echo "Warning: Could not find gcov. "
- GENERATE_GCOV_REPORT=0
- return
- fi
-
- # the gcov version must match the gcc version
- GCC_VER=$(gcc -dumpfullversion)
- GCOV_VER=$($GCOV_CMD -v | grep gcov | awk '{print $3}'| awk 'BEGIN {FS="-"}{print $1}')
- if [ "$GCOV_VER" != "$GCC_VER" ]; then
- #attempt to find a matching gcov version
- GCOV_CMD=gcov-$(gcc -dumpversion)
-
- if ! which "$GCOV_CMD" > /dev/null 2>&1; then
- echo "Warning: Could not find an appropriate gcov installation. \
- gcov version must match gcc version"
- GENERATE_GCOV_REPORT=0
- return
- fi
-
- #recheck version number of found gcov executable
- GCOV_VER=$($GCOV_CMD -v | grep gcov | awk '{print $3}'| \
- awk 'BEGIN {FS="-"}{print $1}')
- if [ "$GCOV_VER" != "$GCC_VER" ]; then
- echo "Warning: Could not find an appropriate gcov installation. \
- gcov version must match gcc version"
- GENERATE_GCOV_REPORT=0
- else
- echo "Warning: Mismatched gcc and gcov detected. Using $GCOV_CMD"
- fi
- fi
-}
-
-# Check to see if the kconfig has the required configs to generate a coverage report
-check_gcov_conf()
-{
- if ! grep -x "CONFIG_GCOV_PROFILE_RDS=y" "$kconfig" > /dev/null 2>&1; then
- echo "INFO: CONFIG_GCOV_PROFILE_RDS should be enabled for coverage reports"
- GENERATE_GCOV_REPORT=0
- fi
- if ! grep -x "CONFIG_GCOV_KERNEL=y" "$kconfig" > /dev/null 2>&1; then
- echo "INFO: CONFIG_GCOV_KERNEL should be enabled for coverage reports"
- GENERATE_GCOV_REPORT=0
- fi
- if grep -x "CONFIG_GCOV_PROFILE_ALL=y" "$kconfig" > /dev/null 2>&1; then
- echo "INFO: CONFIG_GCOV_PROFILE_ALL should be disabled for coverage reports"
- GENERATE_GCOV_REPORT=0
- fi
-
- if [ "$GENERATE_GCOV_REPORT" -eq 0 ]; then
- echo "To enable gcov reports, please run "\
- "\"tools/testing/selftests/net/rds/config.sh -g\" and rebuild the kernel"
- else
- # if we have the required kernel configs, proceed to check the environment to
- # ensure we have the required gcov packages
- check_gcov_env
- fi
-}
-
-# Kselftest framework requirement - SKIP code is 4.
-check_conf_enabled() {
- if ! grep -x "$1=y" "$kconfig" > /dev/null 2>&1; then
- echo "selftests: [SKIP] This test requires $1 enabled"
- echo "Please run tools/testing/selftests/net/rds/config.sh and rebuild the kernel"
- exit 4
- fi
-}
-check_conf_disabled() {
- if grep -x "$1=y" "$kconfig" > /dev/null 2>&1; then
- echo "selftests: [SKIP] This test requires $1 disabled"
- echo "Please run tools/testing/selftests/net/rds/config.sh and rebuild the kernel"
- exit 4
- fi
-}
-check_conf() {
- check_conf_enabled CONFIG_NET_SCH_NETEM
- check_conf_enabled CONFIG_VETH
- check_conf_enabled CONFIG_NET_NS
- check_conf_enabled CONFIG_RDS_TCP
- check_conf_enabled CONFIG_RDS
- check_conf_disabled CONFIG_MODULES
-}
-
-check_env()
-{
- if ! test -d "$obj_dir"; then
- echo "selftests: [SKIP] This test requires a kernel source tree"
- exit 4
- fi
- if ! test -e "$kconfig"; then
- echo "selftests: [SKIP] This test requires a configured kernel source tree"
- exit 4
- fi
- if ! which strace > /dev/null 2>&1; then
- echo "selftests: [SKIP] Could not run test without strace"
- exit 4
- fi
- if ! which tcpdump > /dev/null 2>&1; then
- echo "selftests: [SKIP] Could not run test without tcpdump"
- exit 4
- fi
-
- if ! which python3 > /dev/null 2>&1; then
- echo "selftests: [SKIP] Could not run test without python3"
- exit 4
- fi
-
- python_major=$(python3 -c "import sys; print(sys.version_info[0])")
- python_minor=$(python3 -c "import sys; print(sys.version_info[1])")
- if [[ python_major -lt 3 || ( python_major -eq 3 && python_minor -lt 9 ) ]] ; then
- echo "selftests: [SKIP] Could not run test without at least python3.9"
- python3 -V
- exit 4
- fi
-}
-
-LOG_DIR="$current_dir"/rds_logs
-PLOSS=0
-PCORRUPT=0
-PDUP=0
-GENERATE_GCOV_REPORT=1
-while getopts "d:l:c:u:" opt; do
- case ${opt} in
- d)
- LOG_DIR=${OPTARG}
- ;;
- l)
- PLOSS=${OPTARG}
- ;;
- c)
- PCORRUPT=${OPTARG}
- ;;
- u)
- PDUP=${OPTARG}
- ;;
- :)
- echo "USAGE: run.sh [-d logdir] [-l packet_loss] [-c packet_corruption]" \
- "[-u packet_duplcate] [-g]"
- exit 1
- ;;
- ?)
- echo "Invalid option: -${OPTARG}."
- exit 1
- ;;
- esac
-done
-
-
-check_env
-check_conf
-check_gcov_conf
-
-
-rm -fr "$LOG_DIR"
-TRACE_FILE="${LOG_DIR}/rds-strace.txt"
-COVR_DIR="${LOG_DIR}/coverage/"
-mkdir -p "$LOG_DIR"
-mkdir -p "$COVR_DIR"
-
-set +e
-echo running RDS tests...
-echo Traces will be logged to "$TRACE_FILE"
-rm -f "$TRACE_FILE"
-strace -T -tt -o "$TRACE_FILE" python3 "$(dirname "$0")/test.py" \
- --timeout "$timeout" -d "$LOG_DIR" -l "$PLOSS" -c "$PCORRUPT" -u "$PDUP"
-
-test_rc=$?
-dmesg > "${LOG_DIR}/dmesg.out"
-
-if [ "$GENERATE_GCOV_REPORT" -eq 1 ]; then
- echo saving coverage data...
- (set +x; cd /sys/kernel/debug/gcov; find ./* -name '*.gcda' | \
- while read -r f
- do
- cat < "/sys/kernel/debug/gcov/$f" > "/$f"
- done)
-
- echo running gcovr...
- gcovr -s --html-details --gcov-executable "$GCOV_CMD" --gcov-ignore-parse-errors \
- -o "${COVR_DIR}/gcovr" "${ksrc_dir}/net/rds/"
-else
- echo "Coverage report will be skipped"
-fi
-
-if [ "$test_rc" -eq 0 ]; then
- echo "PASS: Test completed successfully"
-else
- echo "FAIL: Test failed"
-fi
-
-exit "$test_rc"
diff --git a/tools/testing/selftests/net/rds/settings b/tools/testing/selftests/net/rds/settings
index d2009a64589c..8cb41e6a83cc 100644
--- a/tools/testing/selftests/net/rds/settings
+++ b/tools/testing/selftests/net/rds/settings
@@ -1 +1 @@
-timeout=400
+timeout=800
diff --git a/tools/testing/selftests/net/rds/test.py b/tools/testing/selftests/net/rds/test.py
index 93e23e8b256c..9e4df01cb0d4 100755
--- a/tools/testing/selftests/net/rds/test.py
+++ b/tools/testing/selftests/net/rds/test.py
@@ -1,23 +1,30 @@
#! /usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
+"""
+This module provides functional testing for the net/rds component.
+"""
import argparse
+import atexit
import ctypes
import errno
import hashlib
import os
import select
+import re
import signal
import socket
import subprocess
import sys
-import tempfile
-import shutil
+import time
# Allow utils module to be imported from different directory
this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(this_dir, "../"))
-from lib.py.utils import ip
+# pylint: disable-next=wrong-import-position,import-error,no-name-in-module
+from lib.py.utils import ip, cmd # noqa: E402
+# pylint: disable-next=wrong-import-position,import-error,no-name-in-module
+from lib.py.ksft import ksft_pr # noqa: E402
libc = ctypes.cdll.LoadLibrary('libc.so.6')
setns = libc.setns
@@ -28,6 +35,44 @@ NET1 = 'net1'
VETH0 = 'veth0'
VETH1 = 'veth1'
+tcpdump_procs = []
+tcp_addrs = [
+ # we technically don't need different port numbers, but this will
+ # help identify traffic in the network analyzer
+ ('10.0.0.1', 10000),
+ ('10.0.0.2', 20000),
+]
+
+# RDMA network configs
+RXE_DEV0 = 'rxe0'
+RXE_DEV1 = 'rxe1'
+
+VETH_RDMA0 = 'veth_rdma0'
+VETH_RDMA1 = 'veth_rdma1'
+
+rdma_addrs = [
+ ('10.0.0.3', 30000),
+ ('10.0.0.4', 30000),
+]
+
+# send_packets flag space
+OP_FLAG_TCP = 0x1
+OP_FLAG_RDMA = 0x2
+
+# from include/uapi/linux/rds.h: SO_RDS_TRANSPORT pins a socket to a
+# specific RDS transport so connection setup cannot silently fall back
+# to another (e.g. loopback) transport.
+SOL_RDS = 276
+SO_RDS_TRANSPORT = 8
+RDS_TRANS_TCP = 2
+RDS_TRANS_IB = 0
+
+signal_handler_label = ""
+
+tap_idx = 0
+nr_pass = 0
+nr_fail = 0
+
# Helper function for creating a socket inside a network namespace.
# We need this because otherwise RDS will detect that the two TCP
# sockets are on the same interface and use the loop transport instead
@@ -43,233 +88,469 @@ def netns_socket(netns, *sock_args):
child = os.fork()
if child == 0:
- # change network namespace
- with open(f'/var/run/netns/{netns}', encoding='utf-8') as f:
- try:
+ try:
+ # change network namespace
+ with open(f'/var/run/netns/{netns}', encoding='utf-8') as f:
setns(f.fileno(), 0)
- except IOError as e:
- print(e.errno)
- print(e)
-
- # create socket in target namespace
- sock = socket.socket(*sock_args)
+ # create socket in target namespace
+ sock = socket.socket(*sock_args)
- # send resulting socket to parent
- socket.send_fds(u0, [], [sock.fileno()])
+ # send resulting socket to parent
+ socket.send_fds(u0, [], [sock.fileno()])
- sys.exit(0)
+ os._exit(0)
+ except BaseException:
+ os._exit(1)
# receive socket from child
_, fds, _, _ = socket.recv_fds(u1, 0, 1)
- os.waitpid(child, 0)
+ _, status = os.waitpid(child, 0)
u0.close()
u1.close()
+ if not os.WIFEXITED(status) or os.WEXITSTATUS(status) != 0:
+ raise RuntimeError(
+ f"netns_socket child failed in netns {netns} (status={status})")
return socket.fromfd(fds[0], *sock_args)
-def signal_handler(_sig, _frame):
+def send_burst(socks, ip_addrs, snd_hashes, nr_sent, nr_total):
+ """Send until blocked or nr_total reached. Return updated nr_sent."""
+
+ while nr_sent < nr_total:
+ data = hashlib.sha256(
+ f'packet {nr_sent}'.encode('utf-8')).hexdigest().encode('utf-8')
+ # pseudo-random send/receive pattern
+ snd_idx = nr_sent % 2
+ rcv_idx = 1 - (nr_sent % 3) % 2
+
+ snd = socks[snd_idx]
+ rcv = socks[rcv_idx]
+ try:
+ snd.sendto(data, ip_addrs[rcv_idx])
+ except BlockingIOError:
+ return nr_sent
+ except OSError as e:
+ if e.errno in (errno.ENOBUFS, errno.ECONNRESET, errno.EPIPE):
+ return nr_sent
+ raise
+ snd_hashes.setdefault((snd.fileno(), rcv.fileno()),
+ hashlib.sha256()).update(f'<{data}>'.encode('utf-8'))
+ nr_sent += 1
+ return nr_sent
+
+def recv_burst(epoll, socks, ip_addrs, rcv_hashes, nr_rcv):
+ """Drain whatever's readable from epoll. Return updated nr_recv."""
+ for filen, evntmask in epoll.poll():
+ if not evntmask & select.EPOLLRDNORM:
+ continue
+ rcv = next(s for s in socks if s.fileno() == filen)
+ while True:
+ try:
+ data, adr = rcv.recvfrom(1024)
+ except BlockingIOError:
+ break
+ snd_idx = ip_addrs.index(adr)
+ snd = socks[snd_idx]
+ rcv_hashes.setdefault((snd.fileno(), rcv.fileno()),
+ hashlib.sha256()).update(f'<{data}>'.encode('utf-8'))
+ nr_rcv += 1
+ return nr_rcv
+
+def check_info(socks):
"""
- Test timed out signal handler
+ Check all rds info pages for errors
+
+ :param socks: list of sockets to check
"""
- print('Test timed out')
- sys.exit(1)
-#Parse out command line arguments. We take an optional
-# timeout parameter and an optional log output folder
-parser = argparse.ArgumentParser(description="init script args",
- formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-parser.add_argument("-d", "--logdir", action="store",
- help="directory to store logs", default="/tmp")
-parser.add_argument('--timeout', help="timeout to terminate hung test",
- type=int, default=0)
-parser.add_argument('-l', '--loss', help="Simulate tcp packet loss",
- type=int, default=0)
-parser.add_argument('-c', '--corruption', help="Simulate tcp packet corruption",
- type=int, default=0)
-parser.add_argument('-u', '--duplicate', help="Simulate tcp packet duplication",
- type=int, default=0)
-args = parser.parse_args()
-logdir=args.logdir
-PACKET_LOSS=str(args.loss)+'%'
-PACKET_CORRUPTION=str(args.corruption)+'%'
-PACKET_DUPLICATE=str(args.duplicate)+'%'
+ # the Python socket module doesn't know these
+ rds_info_first = 10000
+ rds_info_last = 10017
-ip(f"netns add {NET0}")
-ip(f"netns add {NET1}")
-ip("link add type veth")
+ nr_success = 0
+ nr_error = 0
-addrs = [
- # we technically don't need different port numbers, but this will
- # help identify traffic in the network analyzer
- ('10.0.0.1', 10000),
- ('10.0.0.2', 20000),
-]
+ for sock in socks:
+ for optname in range(rds_info_first, rds_info_last + 1):
+ # Sigh, the Python socket module doesn't allow us to pass
+ # buffer lengths greater than 1024 for some reason. RDS
+ # wants multiple pages.
+ try:
+ sock.getsockopt(socket.SOL_RDS, optname, 1024)
+ nr_success = nr_success + 1
+ except OSError as e:
+ nr_error = nr_error + 1
+ if e.errno == errno.ENOSPC:
+ # ignore
+ pass
+
+ ksft_pr(f"getsockopt(): {nr_success}/{nr_error}")
+
+def verify_hashes(snd_hashes, rcv_hashes):
+ """Compare send/recv hashes per (sender, receiver) pair."""
+ for key, snd_hash in snd_hashes.items():
+ rcv_hash = rcv_hashes.get(key)
+ if rcv_hash is None:
+ ksft_pr("FAIL: No data received")
+ return 1
+ if snd_hash.hexdigest() != rcv_hash.hexdigest():
+ ksft_pr("FAIL: Send/recv mismatch")
+ ksft_pr("hash expected:", snd_hash.hexdigest())
+ ksft_pr("hash received:", rcv_hash.hexdigest())
+ return 1
+ ksft_pr(f"{key[0]}/{key[1]}: ok")
+ return 0
+
+def snd_rcv_packets(env):
+ """
+ Send packets on the given network interfaces
-# move interfaces to separate namespaces so they can no longer be
-# bound directly; this prevents rds from switching over from the tcp
-# transport to the loop transport.
-ip(f"link set {VETH0} netns {NET0} up")
-ip(f"link set {VETH1} netns {NET1} up")
+ :param env: transport-environment dict for setup_tcp() / setup_rdma().
+ "addrs": list of (ip, port) tuples matching the sockets
+ "netns": list of netns names for TCP or None for RDMA
+ "flags": OP_FLAG_TCP or OP_FLAG_RDMA, selects sockets
+ """
+ addrs = env["addrs"]
+ netns_list = env["netns"]
+ flags = env.get("flags", 0)
+ if (flags & OP_FLAG_TCP) and (flags & OP_FLAG_RDMA):
+ raise RuntimeError(f"Invalid transport flag sets multiple transports: {flags}")
-# add addresses
-ip(f"-n {NET0} addr add {addrs[0][0]}/32 dev {VETH0}")
-ip(f"-n {NET1} addr add {addrs[1][0]}/32 dev {VETH1}")
+ if flags & OP_FLAG_TCP:
+ sockets = [
+ netns_socket(netns_list[0], socket.AF_RDS, socket.SOCK_SEQPACKET),
+ netns_socket(netns_list[1], socket.AF_RDS, socket.SOCK_SEQPACKET),
+ ]
-# add routes
-ip(f"-n {NET0} route add {addrs[1][0]}/32 dev {VETH0}")
-ip(f"-n {NET1} route add {addrs[0][0]}/32 dev {VETH1}")
+ # Pin the sockets to the TCP transport so it doesn't fail over to a
+ # different transport during this test
+ for s in sockets:
+ s.setsockopt(SOL_RDS, SO_RDS_TRANSPORT, RDS_TRANS_TCP)
+ elif flags & OP_FLAG_RDMA:
+ sockets = [
+ socket.socket(socket.AF_RDS, socket.SOCK_SEQPACKET),
+ socket.socket(socket.AF_RDS, socket.SOCK_SEQPACKET),
+ ]
-# sanity check that our two interfaces/addresses are correctly set up
-# and communicating by doing a single ping
-ip(f"netns exec {NET0} ping -c 1 {addrs[1][0]}")
+ # Pin the sockets to the RDMA transport so it doesn't fail over to a
+ # different transport during this test
+ for s in sockets:
+ s.setsockopt(SOL_RDS, SO_RDS_TRANSPORT, RDS_TRANS_IB)
+ else:
+ raise RuntimeError(f"Invalid transport flag sets no transports: {flags}")
-# Start a packet capture on each network
-tcpdump_procs = []
-for net in [NET0, NET1]:
- pcap = logdir+'/'+net+'.pcap'
- fd, pcap_tmp = tempfile.mkstemp(suffix=".pcap", prefix=f"{net}-", dir="/tmp")
- p = subprocess.Popen(
- ['ip', 'netns', 'exec', net,
- '/usr/sbin/tcpdump', '-i', 'any', '-w', pcap_tmp])
- tcpdump_procs.append((p, pcap_tmp, pcap, fd))
-
-# simulate packet loss, duplication and corruption
-for net, iface in [(NET0, VETH0), (NET1, VETH1)]:
- ip(f"netns exec {net} /usr/sbin/tc qdisc add dev {iface} root netem \
- corrupt {PACKET_CORRUPTION} loss {PACKET_LOSS} duplicate \
- {PACKET_DUPLICATE}")
-
-# add a timeout
-if args.timeout > 0:
- signal.alarm(args.timeout)
- signal.signal(signal.SIGALRM, signal_handler)
-
-sockets = [
- netns_socket(NET0, socket.AF_RDS, socket.SOCK_SEQPACKET),
- netns_socket(NET1, socket.AF_RDS, socket.SOCK_SEQPACKET),
-]
+ for s, addr in zip(sockets, addrs):
+ s.bind(addr)
+ s.setblocking(0)
-for s, addr in zip(sockets, addrs):
- s.bind(addr)
- s.setblocking(0)
+ send_hashes = {}
+ recv_hashes = {}
-fileno_to_socket = {
- s.fileno(): s for s in sockets
-}
+ ep = select.epoll()
-addr_to_socket = dict(zip(addrs, sockets))
+ for s in sockets:
+ ep.register(s, select.EPOLLRDNORM)
-socket_to_addr = {
- s: addr for addr, s in zip(addrs, sockets)
-}
+ num_packets = 50000
+ nr_send = 0
+ nr_recv = 0
-send_hashes = {}
-recv_hashes = {}
+ while nr_send < num_packets:
-ep = select.epoll()
+ # Send as much as we can without blocking
+ ksft_pr("sending...", nr_send, nr_recv)
+ nr_send = send_burst(sockets, addrs, send_hashes, nr_send, num_packets)
-for s in sockets:
- ep.register(s, select.EPOLLRDNORM)
+ # Receive as much as we can without blocking
+ ksft_pr("receiving...", nr_send, nr_recv)
+ while nr_recv < nr_send:
+ nr_recv = recv_burst(ep, sockets, addrs, recv_hashes, nr_recv)
-NUM_PACKETS = 50000
-nr_send = 0
-nr_recv = 0
+ # exercise net/rds/tcp.c:rds_tcp_sysctl_reset()
+ if netns_list:
+ for net in netns_list:
+ ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_rcvbuf=10000")
+ ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_sndbuf=10000")
-while nr_send < NUM_PACKETS:
- # Send as much as we can without blocking
- print("sending...", nr_send, nr_recv)
- while nr_send < NUM_PACKETS:
- send_data = hashlib.sha256(
- f'packet {nr_send}'.encode('utf-8')).hexdigest().encode('utf-8')
+ ksft_pr("done", nr_send, nr_recv)
- # pseudo-random send/receive pattern
- sender = sockets[nr_send % 2]
- receiver = sockets[1 - (nr_send % 3) % 2]
+ check_info(sockets)
- try:
- sender.sendto(send_data, socket_to_addr[receiver])
- send_hashes.setdefault((sender.fileno(), receiver.fileno()),
- hashlib.sha256()).update(f'<{send_data}>'.encode('utf-8'))
- nr_send = nr_send + 1
- except BlockingIOError as e:
- break
- except OSError as e:
- if e.errno in [errno.ENOBUFS, errno.ECONNRESET, errno.EPIPE]:
- break
- raise
+ # We're done sending and receiving stuff, now let's check if what
+ # we received is what we sent.
+ rc = verify_hashes(send_hashes, recv_hashes)
+
+ ep.close()
+ for s in sockets:
+ s.close()
+
+ return rc
+
+def stop_pcaps():
+ """Stop tcpdump processes.
+
+ We use pop() here to drain the list in the event that the test
+ completes after the signal handler is fired. List will be empty
+ if logdir is not set
+ """
+
+ if not tcpdump_procs:
+ return
- # Receive as much as we can without blocking
- print("receiving...", nr_send, nr_recv)
- while nr_recv < nr_send:
- for fileno, eventmask in ep.poll():
- receiver = fileno_to_socket[fileno]
-
- if eventmask & select.EPOLLRDNORM:
- while True:
- try:
- recv_data, address = receiver.recvfrom(1024)
- sender = addr_to_socket[address]
- recv_hashes.setdefault((sender.fileno(),
- receiver.fileno()), hashlib.sha256()).update(
- f'<{recv_data}>'.encode('utf-8'))
- nr_recv = nr_recv + 1
- except BlockingIOError as e:
- break
-
- # exercise net/rds/tcp.c:rds_tcp_sysctl_reset()
- for net in [NET0, NET1]:
- ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_rcvbuf=10000")
- ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_sndbuf=10000")
-
-print("done", nr_send, nr_recv)
-
-# the Python socket module doesn't know these
-RDS_INFO_FIRST = 10000
-RDS_INFO_LAST = 10017
-
-nr_success = 0
-nr_error = 0
-
-for s in sockets:
- for optname in range(RDS_INFO_FIRST, RDS_INFO_LAST + 1):
- # Sigh, the Python socket module doesn't allow us to pass
- # buffer lengths greater than 1024 for some reason. RDS
- # wants multiple pages.
+ ksft_pr("Stopping network packet captures")
+ while tcpdump_procs:
+ proc = tcpdump_procs.pop()
+ proc.terminate()
try:
- s.getsockopt(socket.SOL_RDS, optname, 1024)
- nr_success = nr_success + 1
- except OSError as e:
- nr_error = nr_error + 1
- if e.errno == errno.ENOSPC:
- # ignore
- pass
-
-print(f"getsockopt(): {nr_success}/{nr_error}")
-
-print("Stopping network packet captures")
-for p, pcap_tmp, pcap, fd in tcpdump_procs:
- p.terminate()
- p.wait()
- os.close(fd)
- shutil.move(pcap_tmp, pcap)
-
-# We're done sending and receiving stuff, now let's check if what
-# we received is what we sent.
-for (sender, receiver), send_hash in send_hashes.items():
- recv_hash = recv_hashes.get((sender, receiver))
-
- if recv_hash is None:
- print("FAIL: No data received")
- sys.exit(1)
-
- if send_hash.hexdigest() != recv_hash.hexdigest():
- print("FAIL: Send/recv mismatch")
- print("hash expected:", send_hash.hexdigest())
- print("hash received:", recv_hash.hexdigest())
- sys.exit(1)
-
- print(f"{sender}/{receiver}: ok")
-
-print("Success")
-sys.exit(0)
+ proc.wait(timeout=5)
+ except subprocess.TimeoutExpired:
+ proc.kill()
+ proc.wait()
+
+def signal_handler(_sig, _frame):
+ """
+ Test timed out signal handler
+ """
+ ksft_pr(f"Test timed out: {signal_handler_label}")
+ print(f"not ok {tap_idx} rds selftest {signal_handler_label}")
+ sys.exit(1)
+
+def setup_tcp():
+ """
+ Configure tcp network
+ """
+
+ # clean up any leftovers from a previously interrupted run
+ teardown_tcp()
+
+ ip(f"netns add {NET0}")
+ ip(f"netns add {NET1}")
+ ip("link add type veth")
+
+ # Move TCP interfaces into separate namespaces so they can no longer be
+ # bound directly; this prevents rds from switching over from the tcp
+ # transport to the loop transport.
+ ip(f"link set {VETH0} netns {NET0} up")
+ ip(f"link set {VETH1} netns {NET1} up")
+
+ # add addresses
+ ip(f"-n {NET0} addr add {tcp_addrs[0][0]}/32 dev {VETH0}")
+ ip(f"-n {NET1} addr add {tcp_addrs[1][0]}/32 dev {VETH1}")
+
+ # add routes
+ ip(f"-n {NET0} route add {tcp_addrs[1][0]}/32 dev {VETH0}")
+ ip(f"-n {NET1} route add {tcp_addrs[0][0]}/32 dev {VETH1}")
+
+ # sanity check that our two interfaces/addresses are correctly set up
+ # and communicating by doing a single ping
+ ip(f"netns exec {NET0} ping -c 1 {tcp_addrs[1][0]}")
+
+ # Start a packet capture on each network
+ if logdir is not None:
+ for netn in [NET0, NET1]:
+ pcap = logdir+'/rds-'+netn+'.pcap'
+
+ tcpdump_cmd = ['ip', 'netns', 'exec', netn, '/usr/sbin/tcpdump']
+ sudo_user = os.environ.get('SUDO_USER')
+ if sudo_user:
+ tcpdump_cmd.extend(['-Z', sudo_user])
+ tcpdump_cmd.extend(['-i', 'any', '-w', pcap])
+
+ # pylint: disable-next=consider-using-with
+ p = subprocess.Popen(tcpdump_cmd,
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ tcpdump_procs.append(p)
+
+ # simulate packet loss, duplication and corruption
+ for netn, iface in [(NET0, VETH0), (NET1, VETH1)]:
+ ip(f"netns exec {netn} /usr/sbin/tc qdisc add dev {iface} root netem \
+ corrupt {PACKET_CORRUPTION} loss {PACKET_LOSS} duplicate \
+ {PACKET_DUPLICATE}")
+
+def teardown_tcp():
+ """
+ Tear down the tcp network configured by setup_tcp().
+
+ Removing the namespaces also removes the veth pair, addresses,
+ routes, and netem qdisc that live inside them. fail=False so
+ this is safe to call in error paths after a partial or complete setup.
+ """
+ cmd(f"ip netns del {NET0}", fail=False)
+ cmd(f"ip netns del {NET1}", fail=False)
+
+def get_iface_mac(iface):
+ """Return the MAC address of a local network interface."""
+ out = subprocess.check_output(['ip', 'link', 'show', iface], text=True)
+ mac = re.search(r'link/ether\s+([0-9a-f:]+)', out)
+ if not mac:
+ raise RuntimeError(f"Cannot determine MAC address of {iface}")
+ return mac.group(1)
+
+def setup_rdma():
+ """
+ Configure rdma network
+ """
+
+ # remove links left over by previously interrupted run.
+ teardown_rdma()
+
+ # use call here since modprobe may fail if the rdma_rxe
+ # module is built-in
+ subprocess.call(['modprobe', 'rdma_rxe'],
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+
+ ip(f"link add {VETH_RDMA0} type veth peer name {VETH_RDMA1}")
+
+ ip(f"link set {VETH_RDMA0} up")
+ ip(f"link set {VETH_RDMA1} up")
+
+ # Since both addresses are in the same namespace, the source address
+ # is always local, so enable accept_local
+ cmd(f"/usr/sbin/sysctl -q net.ipv4.conf.{VETH_RDMA0}.accept_local=1")
+ cmd(f"/usr/sbin/sysctl -q net.ipv4.conf.{VETH_RDMA1}.accept_local=1")
+
+ # Reverse path filters must be disabled so that the local routes don't
+ # cause RPF failures.
+ cmd(f"/usr/sbin/sysctl -q net.ipv4.conf.{VETH_RDMA0}.rp_filter=0")
+ cmd(f"/usr/sbin/sysctl -q net.ipv4.conf.{VETH_RDMA1}.rp_filter=0")
+
+ # add addresses
+ ip(f"addr add {rdma_addrs[0][0]}/32 dev {VETH_RDMA0}")
+ ip(f"addr add {rdma_addrs[1][0]}/32 dev {VETH_RDMA1}")
+
+ # add routes
+ ip(f"route add {rdma_addrs[1][0]}/32 dev {VETH_RDMA0}")
+ ip(f"route add {rdma_addrs[0][0]}/32 dev {VETH_RDMA1}")
+
+ # ARP will not resolve neighbor IPs on /32 routes without a subnet.
+ # Avoid this by adding neighbors directly so RDMA CM can populate path
+ # records with correct mac addrs without waiting for the ARP.
+ mac0 = get_iface_mac(VETH_RDMA0)
+ mac1 = get_iface_mac(VETH_RDMA1)
+ ip(f"neigh add {rdma_addrs[1][0]} lladdr {mac1} dev {VETH_RDMA0} nud permanent")
+ ip(f"neigh add {rdma_addrs[0][0]} lladdr {mac0} dev {VETH_RDMA1} nud permanent")
+
+ cmd(f'rdma link add {RXE_DEV0} type rxe netdev {VETH_RDMA0}')
+ cmd(f'rdma link add {RXE_DEV1} type rxe netdev {VETH_RDMA1}')
+
+ time.sleep(1) # allow RXE devices to initialise
+
+ # Start a packet capture on each network
+ if logdir is not None:
+ for iface in [VETH_RDMA0, VETH_RDMA1]:
+ pcap = logdir+'/rds-roce-'+iface+'.pcap'
+
+ tcpdump_cmd = ['/usr/sbin/tcpdump']
+ sudo_user = os.environ.get('SUDO_USER')
+ if sudo_user:
+ tcpdump_cmd.extend(['-Z', sudo_user])
+ tcpdump_cmd.extend(['-i', iface, '-w', pcap])
+
+ # pylint: disable-next=consider-using-with
+ p = subprocess.Popen(tcpdump_cmd,
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ tcpdump_procs.append(p)
+
+ # simulate packet loss, duplication and corruption
+ for iface in [VETH_RDMA0, VETH_RDMA1]:
+ cmd(f"/usr/sbin/tc qdisc add dev {iface} root netem \
+ corrupt {PACKET_CORRUPTION} loss {PACKET_LOSS} duplicate \
+ {PACKET_DUPLICATE}")
+
+def teardown_rdma():
+ """
+ Tear down the rdma network configured by setup_rdma().
+ """
+
+ # remove links left over by previously interrupted run.
+ cmd(f'rdma link del {RXE_DEV0}', fail=False)
+ cmd(f'rdma link del {RXE_DEV1}', fail=False)
+ cmd(f'ip link del {VETH_RDMA0}', fail=False)
+
+
+#Parse out command line arguments. We take an optional
+# timeout parameter and an optional log output folder
+parser = argparse.ArgumentParser(description="init script args",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+parser.add_argument("-d", "--logdir", action="store",
+ help="directory to store logs", default=None)
+parser.add_argument("-T", "--transport", default="tcp",
+ help="Comma-separated list of transports to test: "
+ "tcp, rdma, or tcp,rdma. Each matching test "
+ "is run once per transport. "
+ "'rdma' requires CONFIG_RDS_RDMA and rdma_rxe.")
+parser.add_argument('-t', '--timeout', help="timeout to terminate hung test",
+ type=int, default=0)
+parser.add_argument('-l', '--loss', help="Simulate tcp packet loss",
+ type=int, default=0)
+parser.add_argument('-c', '--corruption', help="Simulate tcp packet corruption",
+ type=int, default=0)
+parser.add_argument('-u', '--duplicate', help="Simulate tcp packet duplication",
+ type=int, default=0)
+args = parser.parse_args()
+logdir=args.logdir
+PACKET_LOSS=str(args.loss)+'%'
+PACKET_CORRUPTION=str(args.corruption)+'%'
+PACKET_DUPLICATE=str(args.duplicate)+'%'
+
+# check transport is either tcp or rdma
+transports = [t.strip() for t in args.transport.split(',')]
+for t in transports:
+ if t not in ('tcp', 'rdma'):
+ raise SystemExit(f"test.py: unknown transport: {t!r}")
+
+# Register stop_pcaps before any network setups so that any partially setup
+# tcpdumps are still cleaned up on error
+atexit.register(stop_pcaps)
+
+# Set up all requested transports upfront so network plumbing is
+# ready before any test runs.
+transport_envs = {}
+FLAGS = 0
+if 'tcp' in transports:
+ # Register cleanups before setups to handle partial setups that error'd out
+ atexit.register(teardown_tcp)
+ setup_tcp()
+ transport_envs['tcp'] = {
+ 'addrs': tcp_addrs,
+ 'netns': [NET0, NET1],
+ 'flags': FLAGS | OP_FLAG_TCP,
+ }
+
+if 'rdma' in transports:
+ atexit.register(teardown_rdma)
+ setup_rdma()
+ transport_envs['rdma'] = {
+ 'addrs': rdma_addrs,
+ 'netns': None,
+ 'flags': FLAGS | OP_FLAG_RDMA,
+ }
+
+print("TAP version 13")
+print(f"1..{len(transport_envs)}")
+
+for transport, tenv in transport_envs.items():
+ tap_idx += 1
+
+ # add a timeout
+ if args.timeout > 0:
+ signal_handler_label = transport
+ signal.alarm(args.timeout)
+ signal.signal(signal.SIGALRM, signal_handler)
+
+ ret = snd_rcv_packets(tenv)
+
+ # cancel timeout
+ signal.alarm(0)
+
+ if ret == 0:
+ ksft_pr("Success")
+ print(f"ok {tap_idx} rds selftest {transport}")
+ nr_pass += 1
+ else:
+ print(f"not ok {tap_idx} rds selftest {transport}")
+ nr_fail += 1
+
+ksft_pr(f"Totals: pass:{nr_pass} fail:{nr_fail} skip:0")
+sys.exit(1 if nr_fail else 0)
diff --git a/tools/testing/selftests/net/rtnetlink.py b/tools/testing/selftests/net/rtnetlink.py
index e9ad5e88da97..3622413d793d 100755
--- a/tools/testing/selftests/net/rtnetlink.py
+++ b/tools/testing/selftests/net/rtnetlink.py
@@ -1,17 +1,20 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
-from lib.py import ksft_exit, ksft_run, ksft_ge, RtnlAddrFamily
import socket
+import time
+from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_ge, ksft_true, KsftSkipEx
+from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily
IPV4_ALL_HOSTS_MULTICAST = b'\xe0\x00\x00\x01'
-def dump_mcaddr_check(rtnl: RtnlAddrFamily) -> None:
+def dump_mcaddr_check() -> None:
"""
Verify that at least one interface has the IPv4 all-hosts multicast address.
At least the loopback interface should have this address.
"""
+ rtnl = RtnlAddrFamily()
addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True)
all_host_multicasts = [
@@ -21,9 +24,39 @@ def dump_mcaddr_check(rtnl: RtnlAddrFamily) -> None:
ksft_ge(len(all_host_multicasts), 1,
"No interface found with the IPv4 all-hosts multicast address")
+def ipv4_devconf_notify() -> None:
+ """
+ Configure an interface and set ipv4-devconf values through netlink
+ to verify that the appropriate netlink notifications are being sent.
+ """
+
+ with NetNS() as ns:
+ with NetNSEnter(str(ns)):
+ ifname = "dummy1"
+ ip(f"link add name {ifname} type dummy", ns=str(ns))
+
+ with bkg("ip monitor", ns=str(ns)) as cmd_obj:
+ time.sleep(1)
+ try:
+ ip(f"link set dev {ifname} inet forwarding on")
+ ip(f"link set dev {ifname} inet proxy_arp on")
+ ip(f"link set dev {ifname} inet rp_filter 1")
+ ip(f"link set dev {ifname} inet ignore_routes_with_linkdown on")
+ except CmdExitFailure:
+ raise KsftSkipEx("iproute2 does not support IPv4 devconf attributes")
+ time.sleep(1)
+
+ ksft_true(f"inet {ifname} ignore_routes_with_linkdown on" in cmd_obj.stdout,
+ f"No 'ignore_routes_with_linkdown on' notificiation found for interface {ifname}")
+ ksft_true(f"inet {ifname} rp_filter strict" in cmd_obj.stdout,
+ f"No 'rp_filter strict' notificiation found for interface {ifname}")
+ ksft_true(f"inet {ifname} proxy_neigh on" in cmd_obj.stdout,
+ f"No 'proxy_neigh on' notificiation found for interface {ifname}")
+ ksft_true(f"inet {ifname} forwarding on" in cmd_obj.stdout,
+ f"No 'forwarding on' notificiation found for interface {ifname}")
+
def main() -> None:
- rtnl = RtnlAddrFamily()
- ksft_run([dump_mcaddr_check], args=(rtnl, ))
+ ksft_run([dump_mcaddr_check, ipv4_devconf_notify])
ksft_exit()
if __name__ == "__main__":
diff --git a/tools/testing/selftests/net/so_txtime.sh b/tools/testing/selftests/net/so_txtime.sh
deleted file mode 100755
index 5e861ad32a42..000000000000
--- a/tools/testing/selftests/net/so_txtime.sh
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/bin/bash
-# SPDX-License-Identifier: GPL-2.0
-#
-# Regression tests for the SO_TXTIME interface
-
-set -e
-
-readonly ksft_skip=4
-readonly DEV="veth0"
-readonly BIN="./so_txtime"
-
-readonly RAND="$(mktemp -u XXXXXX)"
-readonly NSPREFIX="ns-${RAND}"
-readonly NS1="${NSPREFIX}1"
-readonly NS2="${NSPREFIX}2"
-
-readonly SADDR4='192.168.1.1'
-readonly DADDR4='192.168.1.2'
-readonly SADDR6='fd::1'
-readonly DADDR6='fd::2'
-
-cleanup() {
- ip netns del "${NS2}"
- ip netns del "${NS1}"
-}
-
-trap cleanup EXIT
-
-# Create virtual ethernet pair between network namespaces
-ip netns add "${NS1}"
-ip netns add "${NS2}"
-
-ip link add "${DEV}" netns "${NS1}" type veth \
- peer name "${DEV}" netns "${NS2}"
-
-# Bring the devices up
-ip -netns "${NS1}" link set "${DEV}" up
-ip -netns "${NS2}" link set "${DEV}" up
-
-# Set fixed MAC addresses on the devices
-ip -netns "${NS1}" link set dev "${DEV}" address 02:02:02:02:02:02
-ip -netns "${NS2}" link set dev "${DEV}" address 06:06:06:06:06:06
-
-# Add fixed IP addresses to the devices
-ip -netns "${NS1}" addr add 192.168.1.1/24 dev "${DEV}"
-ip -netns "${NS2}" addr add 192.168.1.2/24 dev "${DEV}"
-ip -netns "${NS1}" addr add fd::1/64 dev "${DEV}" nodad
-ip -netns "${NS2}" addr add fd::2/64 dev "${DEV}" nodad
-
-run_test() {
- local readonly IP="$1"
- local readonly CLOCK="$2"
- local readonly TXARGS="$3"
- local readonly RXARGS="$4"
-
- if [[ "${IP}" == "4" ]]; then
- local readonly SADDR="${SADDR4}"
- local readonly DADDR="${DADDR4}"
- elif [[ "${IP}" == "6" ]]; then
- local readonly SADDR="${SADDR6}"
- local readonly DADDR="${DADDR6}"
- else
- echo "Invalid IP version ${IP}"
- exit 1
- fi
-
- local readonly START="$(date +%s%N --date="+ 0.1 seconds")"
-
- ip netns exec "${NS2}" "${BIN}" -"${IP}" -c "${CLOCK}" -t "${START}" -S "${SADDR}" -D "${DADDR}" "${RXARGS}" -r &
- ip netns exec "${NS1}" "${BIN}" -"${IP}" -c "${CLOCK}" -t "${START}" -S "${SADDR}" -D "${DADDR}" "${TXARGS}"
- wait "$!"
-}
-
-do_test() {
- run_test $@
- [ $? -ne 0 ] && ret=1
-}
-
-do_fail_test() {
- run_test $@
- [ $? -eq 0 ] && ret=1
-}
-
-ip netns exec "${NS1}" tc qdisc add dev "${DEV}" root fq
-set +e
-ret=0
-do_test 4 mono a,-1 a,-1
-do_test 6 mono a,0 a,0
-do_test 6 mono a,10 a,10
-do_test 4 mono a,10,b,20 a,10,b,20
-do_test 6 mono a,20,b,10 b,20,a,20
-
-if ip netns exec "${NS1}" tc qdisc replace dev "${DEV}" root etf clockid CLOCK_TAI delta 400000; then
- do_fail_test 4 tai a,-1 a,-1
- do_fail_test 6 tai a,0 a,0
- do_test 6 tai a,10 a,10
- do_test 4 tai a,10,b,20 a,10,b,20
- do_test 6 tai a,20,b,10 b,10,a,20
-else
- echo "tc ($(tc -V)) does not support qdisc etf. skipping"
- [ $ret -eq 0 ] && ret=$ksft_skip
-fi
-
-if [ $ret -eq 0 ]; then
- echo OK. All tests passed
-elif [[ $ret -ne $ksft_skip && -n "$KSFT_MACHINE_SLOW" ]]; then
- echo "Ignoring errors due to slow environment" 1>&2
- ret=0
-fi
-exit $ret
diff --git a/tools/testing/selftests/net/tcp_ao/config b/tools/testing/selftests/net/tcp_ao/config
index f22148512365..1b120bfd89c4 100644
--- a/tools/testing/selftests/net/tcp_ao/config
+++ b/tools/testing/selftests/net/tcp_ao/config
@@ -1,7 +1,3 @@
-CONFIG_CRYPTO_CMAC=y
-CONFIG_CRYPTO_HMAC=y
-CONFIG_CRYPTO_RMD160=y
-CONFIG_CRYPTO_SHA1=y
CONFIG_IPV6=y
CONFIG_IPV6_MULTIPLE_TABLES=y
CONFIG_NET_L3_MASTER_DEV=y
diff --git a/tools/testing/selftests/net/tcp_ao/key-management.c b/tools/testing/selftests/net/tcp_ao/key-management.c
index 69d9a7a05d5c..d86bb380b79f 100644
--- a/tools/testing/selftests/net/tcp_ao/key-management.c
+++ b/tools/testing/selftests/net/tcp_ao/key-management.c
@@ -380,31 +380,6 @@ static void check_listen_socket(void)
close(sk);
}
-static const char *fips_fpath = "/proc/sys/crypto/fips_enabled";
-static bool is_fips_enabled(void)
-{
- static int fips_checked = -1;
- FILE *fenabled;
- int enabled;
-
- if (fips_checked >= 0)
- return !!fips_checked;
- if (access(fips_fpath, R_OK)) {
- if (errno != ENOENT)
- test_error("Can't open %s", fips_fpath);
- fips_checked = 0;
- return false;
- }
- fenabled = fopen(fips_fpath, "r");
- if (!fenabled)
- test_error("Can't open %s", fips_fpath);
- if (fscanf(fenabled, "%d", &enabled) != 1)
- test_error("Can't read from %s", fips_fpath);
- fclose(fenabled);
- fips_checked = !!enabled;
- return !!fips_checked;
-}
-
struct test_key {
char password[TCP_AO_MAXKEYLEN];
const char *alg;
@@ -430,14 +405,7 @@ struct key_collection {
static struct key_collection collection;
#define TEST_MAX_MACLEN 16
-const char *test_algos[] = {
- "cmac(aes128)",
- "hmac(sha1)", "hmac(sha512)", "hmac(sha384)", "hmac(sha256)",
- "hmac(sha224)", "hmac(sha3-512)",
- /* only if !CONFIG_FIPS */
-#define TEST_NON_FIPS_ALGOS 2
- "hmac(rmd160)", "hmac(md5)"
-};
+const char *test_algos[] = { "cmac(aes128)", "hmac(sha1)", "hmac(sha256)" };
const unsigned int test_maclens[] = { 1, 4, 12, 16 };
#define MACLEN_SHIFT 2
#define ALGOS_SHIFT 4
@@ -452,7 +420,7 @@ static unsigned int make_mask(unsigned int shift, unsigned int prev_shift)
static void init_key_in_collection(unsigned int index, bool randomized)
{
struct test_key *key = &collection.keys[index];
- unsigned int algos_nr, algos_index;
+ unsigned int algos_index;
/* Same for randomized and non-randomized test flows */
key->client_keyid = index;
@@ -474,10 +442,7 @@ static void init_key_in_collection(unsigned int index, bool randomized)
key->maclen = test_maclens[index & make_mask(shift, 0)];
algos_index = index & make_mask(ALGOS_SHIFT, shift);
}
- algos_nr = ARRAY_SIZE(test_algos);
- if (is_fips_enabled())
- algos_nr -= TEST_NON_FIPS_ALGOS;
- key->alg = test_algos[algos_index % algos_nr];
+ key->alg = test_algos[algos_index % ARRAY_SIZE(test_algos)];
}
static int init_default_key_collection(unsigned int nr_keys, bool randomized)
diff --git a/tools/testing/selftests/net/test_bridge_neigh_suppress.sh b/tools/testing/selftests/net/test_bridge_neigh_suppress.sh
index 9067197c9055..e9ed0d750996 100755
--- a/tools/testing/selftests/net/test_bridge_neigh_suppress.sh
+++ b/tools/testing/selftests/net/test_bridge_neigh_suppress.sh
@@ -56,6 +56,12 @@ TESTS="
neigh_suppress_uc_ns
neigh_vlan_suppress_arp
neigh_vlan_suppress_ns
+ neigh_suppress_arp_probe
+ neigh_suppress_dad_ns
+ neigh_forward_grat_arp
+ neigh_forward_grat_na
+ neigh_vlan_forward_grat_arp
+ neigh_vlan_forward_grat_na
"
VERBOSE=0
PAUSE_ON_FAIL=no
@@ -74,7 +80,8 @@ log_test()
printf "TEST: %-60s [ OK ]\n" "${msg}"
nsuccess=$((nsuccess+1))
else
- ret=1
+ # shellcheck disable=SC2154
+ ret=$(ksft_exit_status_merge "$ret" "$ksft_fail")
nfail=$((nfail+1))
printf "TEST: %-60s [FAIL]\n" "${msg}"
if [ "$VERBOSE" = "1" ]; then
@@ -97,6 +104,7 @@ log_test()
fi
[ "$VERBOSE" = "1" ] && echo
+ return 0
}
run_cmd()
@@ -134,6 +142,15 @@ tc_check_packets()
[[ $pkts == $count ]]
}
+neigh_forward_grat_check()
+{
+ if ! bridge link help 2>&1 | grep -q "neigh_forward_grat"; then
+ echo "SKIP: iproute2 bridge too old, missing gratuitous ARP/unsolicited NA forwarding control support"
+ # shellcheck disable=SC2154
+ return "$ksft_skip"
+ fi
+}
+
################################################################################
# Setup
@@ -561,6 +578,17 @@ icmpv6_header_get()
echo $p
}
+icmpv6_na_header_get()
+{
+ local csum=$1; shift
+ local tip=$1; shift
+
+ # Type 136 (Neighbor Advertisement), hex format, Override flag set,
+ # Solicited flag clear (unsolicited NA).
+ # ICMPv6.type : ICMPv6.code : ICMPv6.checksum : Flags : Target Address
+ echo "88:00:$csum:20:00:00:00:$tip:"
+}
+
neigh_suppress_uc_ns_common()
{
local vid=$1; shift
@@ -875,6 +903,439 @@ neigh_vlan_suppress_ns()
log_test $? 0 "NS suppression (VLAN $vid2)"
}
+neigh_suppress_arp_probe()
+{
+ local vid=10
+ local tip=192.0.2.2
+ local h2_mac
+
+ echo
+ echo "Per-port ARP probe suppression"
+ echo "------------------------------"
+
+ run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto 0x0806 flower indev swp1 arp_tip $tip arp_sip 0.0.0.0 arp_op request action pass"
+
+ # Initial state - check that ARP probes are not suppressed.
+ run_cmd "ip netns exec $h1 arping -D -q -c 1 -w 5 -I eth0.$vid $tip"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 1
+ log_test $? 0 "ARP probe suppression"
+
+ # Enable neighbor suppression and check that nothing changes.
+ run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on"
+ run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
+ log_test $? 0 "\"neigh_suppress\" is on"
+
+ run_cmd "ip netns exec $h1 arping -D -q -c 1 -w 5 -I eth0.$vid $tip"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 2
+ log_test $? 0 "ARP probe suppression"
+
+ # Install FDB and a neighbor and check that ARP probes are suppressed.
+ h2_mac=$(ip -n "$h2" -j -p link show eth0."$vid" | jq -r '.[]["address"]')
+ run_cmd "bridge -n $sw1 fdb replace $h2_mac dev vx0 master static vlan $vid"
+ run_cmd "ip -n $sw1 neigh replace $tip lladdr $h2_mac nud permanent dev br0.$vid"
+ log_test $? 0 "FDB and neighbor entry installation"
+
+ run_cmd "ip netns exec $h1 arping -D -q -c 1 -w 5 -I eth0.$vid $tip"
+ log_test $? 1 "arping"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 2
+ log_test $? 0 "ARP probe suppression"
+
+ # Remove the neighbor entry and check that ARP probes are not suppressed.
+ run_cmd "ip -n $sw1 neigh del $tip dev br0.$vid"
+ log_test $? 0 "neighbor removal"
+
+ run_cmd "ip netns exec $h1 arping -D -q -c 1 -w 5 -I eth0.$vid $tip"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 3
+ log_test $? 0 "ARP probe suppression"
+
+ # Disable neighbor suppression.
+ run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress off"
+ run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress off\""
+ log_test $? 0 "\"neigh_suppress\" is off"
+
+ run_cmd "ip netns exec $h1 arping -D -q -c 1 -w 5 -I eth0.$vid $tip"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 4
+ log_test $? 0 "ARP probe suppression"
+}
+
+neigh_suppress_dad_ns()
+{
+ local vid=10
+ local tip=2001:db8:1::99
+ local mcast=ff02::1:ff00:99
+ local dmac=33:33:ff:00:00:99
+ local full_tip=20:01:0d:b8:00:01:00:00:00:00:00:00:00:00:00:99
+ local csum="4b:bc"
+ local smac
+ local tmac
+
+ echo
+ echo "Per-port DAD NS suppression"
+ echo "---------------------------"
+
+ smac=$(ip -n "$h1" -j -p link show eth0."$vid" | jq -r '.[]["address"]')
+
+ run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto ipv6 flower indev swp1 ip_proto icmpv6 dst_ip $mcast src_ip :: type 135 code 0 action pass"
+
+ # Initial state - check that DAD NS are not suppressed.
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A :: -B $mcast -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum" "$full_tip") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 1
+ log_test $? 0 "DAD NS suppression"
+
+ # Enable neighbor suppression and check that nothing changes.
+ run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on"
+ run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
+ log_test $? 0 "\"neigh_suppress\" is on"
+
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A :: -B $mcast -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum" "$full_tip") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 2
+ log_test $? 0 "DAD NS suppression"
+
+ # Install FDB and a neighbor and check that DAD NS are suppressed
+ # and that a proxy NA is sent back to h1.
+ tmac=$(ip -n "$h2" -j -p link show eth0."$vid" | jq -r '.[]["address"]')
+ run_cmd "bridge -n $sw1 fdb replace $tmac dev vx0 master static vlan $vid"
+ run_cmd "ip -n $sw1 -6 neigh replace $tip lladdr $tmac nud permanent dev br0.$vid"
+ log_test $? 0 "FDB and neighbor entry installation"
+
+ run_cmd "tc -n $h1 qdisc replace dev eth0.$vid clsact"
+ run_cmd "tc -n $h1 filter replace dev eth0.$vid ingress pref 1 handle 101 proto ipv6 flower ip_proto icmpv6 dst_ip ff02::1 src_ip $tip type 136 code 0 action pass"
+
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A :: -B $mcast -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum" "$full_tip") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 2
+ log_test $? 0 "DAD NS suppression"
+ tc_check_packets "$h1" "dev eth0.$vid ingress" 101 1
+ log_test $? 0 "DAD NS proxy NA reply"
+
+ # Remove the neighbor entry and check that DAD NS are not suppressed.
+ run_cmd "ip -n $sw1 -6 neigh del $tip dev br0.$vid"
+ log_test $? 0 "neighbor removal"
+
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A :: -B $mcast -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum" "$full_tip") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 3
+ log_test $? 0 "DAD NS suppression"
+
+ # Disable neighbor suppression.
+ run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress off"
+ run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress off\""
+ log_test $? 0 "\"neigh_suppress\" is off"
+
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A :: -B $mcast -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum" "$full_tip") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 4
+ log_test $? 0 "DAD NS suppression"
+}
+
+neigh_forward_grat_arp()
+{
+ local vid=10
+ local sip=192.0.2.1
+ local tip=$sip
+ local h2_ip=192.0.2.2
+ local h2_mac
+
+ neigh_forward_grat_check || return $?
+
+ echo
+ echo "Gratuitous ARP forwarding"
+ echo "-------------------------"
+
+ run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto 0x0806 flower indev swp1 arp_tip $tip arp_sip $sip arp_op request action pass"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 102 proto 0x0806 flower indev swp1 arp_tip $h2_ip arp_sip $sip arp_op request action pass"
+
+ h2_mac=$(ip -n "$h2" -j -p link show eth0."$vid" | jq -r '.[]["address"]')
+ run_cmd "bridge -n $sw1 fdb replace $h2_mac dev vx0 master static vlan $vid"
+ run_cmd "ip -n $sw1 neigh replace $tip lladdr $h2_mac nud permanent dev br0.$vid"
+ run_cmd "ip -n $sw1 neigh replace $h2_ip lladdr $h2_mac nud permanent dev br0.$vid"
+
+ # Enable neighbor suppression. Gratuitous ARP should be suppressed by
+ # default (neigh_forward_grat defaults to off).
+ run_cmd "ip -n $sw1 link set dev vx0 type bridge_slave neigh_suppress on"
+ run_cmd "ip -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
+ log_test $? 0 "\"neigh_suppress\" is on"
+
+ # Send gratuitous ARP (sip == tip) and check it's suppressed.
+ run_cmd "ip netns exec $h1 arping -U -c 1 -w 5 -I eth0.$vid $tip"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 0
+ log_test $? 0 "Gratuitous ARP suppression"
+
+ # Explicitly enable neigh_forward_grat and verify gratuitous ARP is
+ # now forwarded.
+ run_cmd "ip -n $sw1 link set dev vx0 type bridge_slave neigh_forward_grat on"
+ run_cmd "ip -n $sw1 -d link show dev vx0 | grep \"neigh_forward_grat on\""
+ log_test $? 0 "\"neigh_forward_grat\" is on"
+
+ run_cmd "ip netns exec $h1 arping -U -c 1 -w 5 -I eth0.$vid $tip"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 1
+ log_test $? 0 "Gratuitous ARP forwarding"
+
+ # Verify that regular (non-gratuitous) ARP requests are still
+ # suppressed when neigh_forward_grat is enabled.
+ run_cmd "ip netns exec $h1 arping -c 1 -w 5 -I eth0.$vid $h2_ip"
+ tc_check_packets "$sw1" "dev vx0 egress" 102 0
+ log_test $? 0 "Regular ARP suppression with \"neigh_forward_grat\" on"
+
+ # Disable neigh_forward_grat and verify suppression resumes.
+ run_cmd "ip -n $sw1 link set dev vx0 type bridge_slave neigh_forward_grat off"
+ run_cmd "ip -n $sw1 -d link show dev vx0 | grep \"neigh_forward_grat off\""
+ log_test $? 0 "\"neigh_forward_grat\" is off"
+
+ run_cmd "ip netns exec $h1 arping -U -c 1 -w 5 -I eth0.$vid $tip"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 1
+ log_test $? 0 "Gratuitous ARP suppression"
+}
+
+# neigh_forward_grat_arp() uses 'ip link' interface, and neigh_forward_grat_na()
+# uses 'bridge link' interface to exercise both paths.
+neigh_forward_grat_na()
+{
+ local vid=10
+ local saddr=2001:db8:1::1
+ local daddr=ff02::1
+ local h2_addr=2001:db8:1::2
+ local h2_maddr=ff02::1:ff00:2
+ local full_addr=20:01:0d:b8:00:01:00:00:00:00:00:00:00:00:00:01
+ local h2_full_addr=20:01:0d:b8:00:01:00:00:00:00:00:00:00:00:00:02
+ local csum="fd:32"
+ local csum_ns="1f:2f"
+ local dmac=33:33:00:00:00:01
+ local h2_dmac=33:33:ff:00:00:02
+ local h2_mac
+ local smac
+
+ neigh_forward_grat_check || return $?
+
+ echo
+ echo "Unsolicited NA forwarding"
+ echo "-------------------------"
+
+ smac=$(ip -n "$h1" -j -p link show eth0."$vid" | jq -r '.[]["address"]')
+
+ run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto ipv6 flower indev swp1 ip_proto icmpv6 dst_ip $daddr src_ip $saddr type 136 code 0 action pass"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 102 proto ipv6 flower indev swp1 ip_proto icmpv6 dst_ip $h2_maddr src_ip $saddr type 135 code 0 action pass"
+
+ h2_mac=$(ip -n "$h2" -j -p link show eth0."$vid" | jq -r '.[]["address"]')
+ run_cmd "bridge -n $sw1 fdb replace $h2_mac dev vx0 master static vlan $vid"
+ run_cmd "ip -n $sw1 neigh replace $saddr lladdr $h2_mac nud permanent dev br0.$vid"
+ run_cmd "ip -n $sw1 neigh replace $h2_addr lladdr $h2_mac nud permanent dev br0.$vid"
+
+ # Enable neighbor suppression. Unsolicited NA should be suppressed by
+ # default (neigh_forward_grat defaults to off).
+ run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on"
+ run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
+ log_test $? 0 "\"neigh_suppress\" is on"
+
+ # Send unsolicited NA and check it's suppressed.
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A $saddr -B $daddr -t ip hop=255,next=58,payload=$(icmpv6_na_header_get "$csum" "$full_addr") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 0
+ log_test $? 0 "Unsolicited NA suppression"
+
+ # Explicitly enable neigh_forward_grat and verify unsolicited NA is
+ # now forwarded.
+ run_cmd "bridge -n $sw1 link set dev vx0 neigh_forward_grat on"
+ run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_forward_grat on\""
+ log_test $? 0 "\"neigh_forward_grat\" is on"
+
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A $saddr -B $daddr -t ip hop=255,next=58,payload=$(icmpv6_na_header_get "$csum" "$full_addr") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 1
+ log_test $? 0 "Unsolicited NA forwarding"
+
+ # Verify that solicited NS messages are still suppressed when
+ # neigh_forward_grat is enabled.
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $h2_dmac -A $saddr -B $h2_maddr -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum_ns" "$h2_full_addr") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 102 0
+ log_test $? 0 "Solicited NS suppression with \"neigh_forward_grat\" on"
+
+ # Disable neigh_forward_grat and verify suppression resumes.
+ run_cmd "bridge -n $sw1 link set dev vx0 neigh_forward_grat off"
+ run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_forward_grat off\""
+ log_test $? 0 "\"neigh_forward_grat\" is off"
+
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A $saddr -B $daddr -t ip hop=255,next=58,payload=$(icmpv6_na_header_get "$csum" "$full_addr") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 1
+ log_test $? 0 "Unsolicited NA suppression"
+}
+
+neigh_vlan_forward_grat_arp()
+{
+ local vid1=10
+ local vid2=20
+ local sip1=192.0.2.1
+ local sip2=192.0.2.17
+ local h2_ip1=192.0.2.2
+ local h2_mac1
+ local h2_mac2
+
+ neigh_forward_grat_check || return $?
+
+ echo
+ echo "Per-VLAN gratuitous ARP forwarding"
+ echo "----------------------------------"
+
+ run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto 0x0806 flower indev swp1 arp_tip $sip1 arp_sip $sip1 arp_op request action pass"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 102 proto 0x0806 flower indev swp1 arp_tip $sip2 arp_sip $sip2 arp_op request action pass"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 103 proto 0x0806 flower indev swp1 arp_tip $h2_ip1 arp_sip $sip1 arp_op request action pass"
+
+ h2_mac1=$(ip -n "$h2" -j -p link show eth0."$vid1" | jq -r '.[]["address"]')
+ h2_mac2=$(ip -n "$h2" -j -p link show eth0."$vid2" | jq -r '.[]["address"]')
+ run_cmd "bridge -n $sw1 fdb replace $h2_mac1 dev vx0 master static vlan $vid1"
+ run_cmd "bridge -n $sw1 fdb replace $h2_mac2 dev vx0 master static vlan $vid2"
+ run_cmd "ip -n $sw1 neigh replace $sip1 lladdr $h2_mac1 nud permanent dev br0.$vid1"
+ run_cmd "ip -n $sw1 neigh replace $sip2 lladdr $h2_mac2 nud permanent dev br0.$vid2"
+ run_cmd "ip -n $sw1 neigh replace $h2_ip1 lladdr $h2_mac1 nud permanent dev br0.$vid1"
+
+ # Enable per-{Port, VLAN} neighbor suppression.
+ run_cmd "bridge -n $sw1 link set dev vx0 neigh_vlan_suppress on"
+ run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_vlan_suppress on\""
+ log_test $? 0 "\"neigh_vlan_suppress\" is on"
+
+ # Enable neighbor suppression on VLAN 10. Gratuitous ARP should be
+ # suppressed by default on VLAN 10 (neigh_forward_grat defaults to off)
+ # but not on VLAN 20.
+ run_cmd "bridge -n $sw1 vlan set vid $vid1 dev vx0 neigh_suppress on"
+ run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid1 | grep \"neigh_suppress on\""
+ log_test $? 0 "\"neigh_suppress\" is on (VLAN $vid1)"
+
+ run_cmd "ip netns exec $h1 arping -U -c 1 -w 5 -I eth0.$vid1 $sip1"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 0
+ log_test $? 0 "Gratuitous ARP suppression (VLAN $vid1)"
+
+ run_cmd "ip netns exec $h1 arping -U -c 1 -w 5 -I eth0.$vid2 $sip2"
+ tc_check_packets "$sw1" "dev vx0 egress" 102 1
+ log_test $? 0 "Gratuitous ARP forwarding (VLAN $vid2)"
+
+ # Enable neigh_forward_grat on VLAN 10 and verify gratuitous ARP is
+ # now forwarded.
+ run_cmd "bridge -n $sw1 vlan set vid $vid1 dev vx0 neigh_forward_grat on"
+ run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid1 | grep \"neigh_forward_grat on\""
+ log_test $? 0 "\"neigh_forward_grat\" is on (VLAN $vid1)"
+
+ run_cmd "ip netns exec $h1 arping -U -c 1 -w 5 -I eth0.$vid1 $sip1"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 1
+ log_test $? 0 "Gratuitous ARP forwarding (VLAN $vid1)"
+
+ # Verify that regular (non-gratuitous) ARP requests on VLAN $vid1 are
+ # still suppressed when neigh_forward_grat is enabled.
+ run_cmd "ip netns exec $h1 arping -c 1 -w 5 -I eth0.$vid1 $h2_ip1"
+ tc_check_packets "$sw1" "dev vx0 egress" 103 0
+ log_test $? 0 "Regular ARP suppression with \"neigh_forward_grat\" on (VLAN $vid1)"
+
+ # Enable neighbor suppression on VLAN 20 (neigh_forward_grat defaults to
+ # off), and verify gratuitous ARP is suppressed on VLAN 20.
+ run_cmd "bridge -n $sw1 vlan set vid $vid2 dev vx0 neigh_suppress on"
+ run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid2 | grep \"neigh_suppress on\""
+ log_test $? 0 "\"neigh_suppress\" is on (VLAN $vid2)"
+
+ # VLAN 10 should still forward (neigh_forward_grat is on).
+ run_cmd "ip netns exec $h1 arping -U -c 1 -w 5 -I eth0.$vid1 $sip1"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 2
+ log_test $? 0 "Gratuitous ARP forwarding (VLAN $vid1)"
+
+ # VLAN 20 should suppress (neigh_forward_grat defaults to off).
+ run_cmd "ip netns exec $h1 arping -U -c 1 -w 5 -I eth0.$vid2 $sip2"
+ tc_check_packets "$sw1" "dev vx0 egress" 102 1
+ log_test $? 0 "Gratuitous ARP suppression (VLAN $vid2)"
+}
+
+neigh_vlan_forward_grat_na()
+{
+ local vid1=10
+ local vid2=20
+ local saddr1=2001:db8:1::1
+ local daddr=ff02::1
+ local h2_addr1=2001:db8:1::2
+ local h2_maddr1=ff02::1:ff00:2
+ local full_addr1=20:01:0d:b8:00:01:00:00:00:00:00:00:00:00:00:01
+ local h2_full_addr1=20:01:0d:b8:00:01:00:00:00:00:00:00:00:00:00:02
+ local csum1="fd:32"
+ local csum_ns1="1f:2f"
+ local saddr2=2001:db8:2::1
+ local full_addr2=20:01:0d:b8:00:02:00:00:00:00:00:00:00:00:00:01
+ local csum2="fd:30"
+ local dmac=33:33:00:00:00:01
+ local h2_dmac1=33:33:ff:00:00:02
+ local h2_mac1
+ local h2_mac2
+ local smac
+
+ neigh_forward_grat_check || return $?
+
+ echo
+ echo "Per-VLAN unsolicited NA forwarding"
+ echo "----------------------------------"
+
+ smac=$(ip -n "$h1" -j -p link show eth0."$vid1" | jq -r '.[]["address"]')
+
+ run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto ipv6 flower indev swp1 ip_proto icmpv6 dst_ip $daddr src_ip $saddr1 type 136 code 0 action pass"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 102 proto ipv6 flower indev swp1 ip_proto icmpv6 dst_ip $daddr src_ip $saddr2 type 136 code 0 action pass"
+ run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 103 proto ipv6 flower indev swp1 ip_proto icmpv6 dst_ip $h2_maddr1 src_ip $saddr1 type 135 code 0 action pass"
+
+ h2_mac1=$(ip -n "$h2" -j -p link show eth0."$vid1" | jq -r '.[]["address"]')
+ h2_mac2=$(ip -n "$h2" -j -p link show eth0."$vid2" | jq -r '.[]["address"]')
+ run_cmd "bridge -n $sw1 fdb replace $h2_mac1 dev vx0 master static vlan $vid1"
+ run_cmd "bridge -n $sw1 fdb replace $h2_mac2 dev vx0 master static vlan $vid2"
+ run_cmd "ip -n $sw1 neigh replace $saddr1 lladdr $h2_mac1 nud permanent dev br0.$vid1"
+ run_cmd "ip -n $sw1 neigh replace $saddr2 lladdr $h2_mac2 nud permanent dev br0.$vid2"
+ run_cmd "ip -n $sw1 neigh replace $h2_addr1 lladdr $h2_mac1 nud permanent dev br0.$vid1"
+
+ # Enable per-{Port, VLAN} neighbor suppression.
+ run_cmd "bridge -n $sw1 link set dev vx0 neigh_vlan_suppress on"
+ run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_vlan_suppress on\""
+ log_test $? 0 "\"neigh_vlan_suppress\" is on"
+
+ # Enable neighbor suppression on VLAN 10. Unsolicited NA should be
+ # suppressed by default on VLAN 10 (neigh_forward_grat defaults to off)
+ # but not on VLAN 20.
+ run_cmd "bridge -n $sw1 vlan set vid $vid1 dev vx0 neigh_suppress on"
+ run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid1 | grep \"neigh_suppress on\""
+ log_test $? 0 "\"neigh_suppress\" is on (VLAN $vid1)"
+
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid1 -c 1 -a $smac -b $dmac -A $saddr1 -B $daddr -t ip hop=255,next=58,payload=$(icmpv6_na_header_get "$csum1" "$full_addr1") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 0
+ log_test $? 0 "Unsolicited NA suppression (VLAN $vid1)"
+
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid2 -c 1 -a $smac -b $dmac -A $saddr2 -B $daddr -t ip hop=255,next=58,payload=$(icmpv6_na_header_get "$csum2" "$full_addr2") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 102 1
+ log_test $? 0 "Unsolicited NA forwarding (VLAN $vid2)"
+
+ # Enable neigh_forward_grat on VLAN 10 and verify unsolicited NA is
+ # now forwarded.
+ run_cmd "bridge -n $sw1 vlan set vid $vid1 dev vx0 neigh_forward_grat on"
+ run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid1 | grep \"neigh_forward_grat on\""
+ log_test $? 0 "\"neigh_forward_grat\" is on (VLAN $vid1)"
+
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid1 -c 1 -a $smac -b $dmac -A $saddr1 -B $daddr -t ip hop=255,next=58,payload=$(icmpv6_na_header_get "$csum1" "$full_addr1") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 1
+ log_test $? 0 "Unsolicited NA forwarding (VLAN $vid1)"
+
+ # Verify that solicited NS messages on VLAN $vid1 are still suppressed
+ # when neigh_forward_grat is enabled.
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid1 -c 1 -a $smac -b $h2_dmac1 -A $saddr1 -B $h2_maddr1 -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum_ns1" "$h2_full_addr1") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 103 0
+ log_test $? 0 "Solicited NS suppression with \"neigh_forward_grat\" on (VLAN $vid1)"
+
+ # Enable neighbor suppression on VLAN 20 (neigh_forward_grat defaults to
+ # off), and verify unsolicited NA is suppressed on VLAN 20.
+ run_cmd "bridge -n $sw1 vlan set vid $vid2 dev vx0 neigh_suppress on"
+ run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid2 | grep \"neigh_suppress on\""
+ log_test $? 0 "\"neigh_suppress\" is on (VLAN $vid2)"
+
+ # VLAN 10 should still forward (neigh_forward_grat is on).
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid1 -c 1 -a $smac -b $dmac -A $saddr1 -B $daddr -t ip hop=255,next=58,payload=$(icmpv6_na_header_get "$csum1" "$full_addr1") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 101 2
+ log_test $? 0 "Unsolicited NA forwarding (VLAN $vid1)"
+
+ # VLAN 20 should suppress (neigh_forward_grat defaults to off).
+ run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid2 -c 1 -a $smac -b $dmac -A $saddr2 -B $daddr -t ip hop=255,next=58,payload=$(icmpv6_na_header_get "$csum2" "$full_addr2") -q"
+ tc_check_packets "$sw1" "dev vx0 egress" 102 1
+ log_test $? 0 "Unsolicited NA suppression (VLAN $vid2)"
+}
+
################################################################################
# Usage
@@ -961,7 +1422,10 @@ cleanup
for t in $TESTS
do
- setup; $t; cleanup;
+ setup
+ $t
+ ret=$(ksft_exit_status_merge "$ret" $?)
+ cleanup
done
if [ "$TESTS" != "none" ]; then
diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c
index 30a236b8e9f7..9b9a3cb2700d 100644
--- a/tools/testing/selftests/net/tls.c
+++ b/tools/testing/selftests/net/tls.c
@@ -1549,7 +1549,7 @@ test_mutliproc(struct __test_metadata *_metadata, struct _test_data_tls *self,
res = recv(self->cfd, rb,
left > sizeof(rb) ? sizeof(rb) : left, 0);
- EXPECT_GE(res, 0);
+ ASSERT_GE(res, 0);
left -= res;
}
} else {
@@ -1566,7 +1566,7 @@ test_mutliproc(struct __test_metadata *_metadata, struct _test_data_tls *self,
res = send(self->fd, buf,
left > file_sz ? file_sz : left, 0);
- EXPECT_GE(res, 0);
+ ASSERT_GE(res, 0);
left -= res;
}
}
diff --git a/tools/testing/selftests/tc-testing/config b/tools/testing/selftests/tc-testing/config
index c20aa16b1d63..0e5618be0335 100644
--- a/tools/testing/selftests/tc-testing/config
+++ b/tools/testing/selftests/tc-testing/config
@@ -4,6 +4,7 @@
CONFIG_DUMMY=y
CONFIG_VETH=y
+CONFIG_IFB=y
#
# Core Netfilter Configuration
diff --git a/tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py b/tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py
index bb19b8b76d3b..0bece7c74f07 100644
--- a/tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py
+++ b/tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py
@@ -120,6 +120,7 @@ class SubPlugin(TdcPlugin):
dev0 = self.args.NAMES["DEV0"];
dev1 = self.args.NAMES["DEV1"];
dummy = self.args.NAMES["DUMMY"];
+ ifb = self.args.NAMES['IFB']
if self.args.verbose:
print('{}._nl_ns_create'.format(self.sub_class))
@@ -129,6 +130,7 @@ class SubPlugin(TdcPlugin):
with IPRoute() as ip:
ip.link('add', ifname=dev1, kind='veth', peer={'ifname': dev0, 'net_ns_fd':'/proc/1/ns/net'})
ip.link('add', ifname=dummy, kind='dummy')
+ ip.link('add', ifname=ifb, kind='ifb')
ticks = 20
while True:
if ticks == 0:
@@ -136,8 +138,10 @@ class SubPlugin(TdcPlugin):
try:
dev1_idx = ip.link_lookup(ifname=dev1)[0]
dummy_idx = ip.link_lookup(ifname=dummy)[0]
+ ifb_idx = ip.link_lookup(ifname=ifb)[0]
ip.link('set', index=dev1_idx, state='up')
ip.link('set', index=dummy_idx, state='up')
+ ip.link('set', index=ifb_idx, state='up')
break
except:
time.sleep(0.1)
@@ -169,8 +173,11 @@ class SubPlugin(TdcPlugin):
cmds.append(self._replace_keywords('link set $DEV1 netns {}'.format(ns)))
cmds.append(self._replace_keywords('link add $DUMMY type dummy'.format(ns)))
cmds.append(self._replace_keywords('link set $DUMMY netns {}'.format(ns)))
+ cmds.append(self._replace_keywords('link add $IFB type ifb'))
+ cmds.append(self._replace_keywords('link set $IFB netns {}'.format(ns)))
cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV1 up'.format(ns)))
cmds.append(self._replace_keywords('netns exec {} $IP link set $DUMMY up'.format(ns)))
+ cmds.append(self._replace_keywords('netns exec {} $IP link set $IFB up'.format(ns)))
cmds.append(self._replace_keywords('link set $DEV0 up'.format(ns)))
if self.args.device:
diff --git a/tools/testing/selftests/tc-testing/tc-tests/actions/ife.json b/tools/testing/selftests/tc-testing/tc-tests/actions/ife.json
index 808aef4afe22..ece7ec41bf99 100644
--- a/tools/testing/selftests/tc-testing/tc-tests/actions/ife.json
+++ b/tools/testing/selftests/tc-testing/tc-tests/actions/ife.json
@@ -1378,5 +1378,60 @@
"teardown": [
"$TC actions flush action ife"
]
+ },
+ {
+ "id": "4e6b",
+ "name": "Decode IFE packet with truncated inner Ethernet header",
+ "category": [
+ "actions",
+ "ife"
+ ],
+ "plugins": {
+ "requires": [
+ "nsPlugin",
+ "scapyPlugin"
+ ]
+ },
+ "setup": [
+ [
+ "$TC actions flush action ife",
+ 0,
+ 1,
+ 255
+ ],
+ "$TC qdisc add dev $DEV1 clsact"
+ ],
+ "scapy": [
+ {
+ "iface": "$DEV0",
+ "count": 1,
+ "packet": "Ether(type=0xED3E) / Raw(b'\\x00\\x02\\xaa')"
+ }
+ ],
+ "cmdUnderTest": "$TC filter add dev $DEV1 ingress protocol all matchall action ife decode pipe index 10",
+ "expExitCode": "0",
+ "verifyCmd": "$TC -s -j actions get action ife index 10",
+ "matchJSON": [
+ {
+ "total acts": 0
+ },
+ {
+ "actions": [
+ {
+ "kind": "ife",
+ "mode": "decode",
+ "index": 10,
+ "stats": {
+ "bytes": 3,
+ "packets": 1,
+ "drops": 1
+ }
+ }
+ ]
+ }
+ ],
+ "teardown": [
+ "$TC qdisc del dev $DEV1 clsact"
+ ]
}
]
diff --git a/tools/testing/selftests/tc-testing/tc-tests/actions/pedit.json b/tools/testing/selftests/tc-testing/tc-tests/actions/pedit.json
index 37c410332174..d8b685cfc62d 100644
--- a/tools/testing/selftests/tc-testing/tc-tests/actions/pedit.json
+++ b/tools/testing/selftests/tc-testing/tc-tests/actions/pedit.json
@@ -1920,5 +1920,54 @@
"teardown": [
"$TC actions flush action pedit"
]
+ },
+ {
+ "id": "1a4f",
+ "name": "Pedit udp dport should not mangle TCP packet dport",
+ "category": [
+ "actions",
+ "pedit"
+ ],
+ "plugins": {
+ "requires": [
+ "nsPlugin",
+ "scapyPlugin"
+ ]
+ },
+ "setup": [
+ "$TC qdisc add dev $DEV1 clsact",
+ "$TC filter add dev $DEV1 ingress protocol ip pref 1 matchall action pedit ex munge udp dport set 18053 continue"
+ ],
+ "cmdUnderTest": "$TC filter add dev $DEV1 ingress protocol ip pref 2 flower ip_proto tcp dst_port 2222 action drop index 1",
+ "scapy": {
+ "iface": "$DEV0",
+ "count": 1,
+ "packet": "Ether()/IP(dst='10.10.10.1')/TCP(dport=2222)"
+ },
+ "expExitCode": "0",
+ "verifyCmd": "$TC -j -s actions get action gact index 1",
+ "matchJSON": [
+ {
+ "total acts": 0
+ },
+ {
+ "actions": [
+ {
+ "order": 1,
+ "kind": "gact",
+ "control_action": {
+ "type": "drop"
+ },
+ "index": 1,
+ "stats": {
+ "packets": 1
+ }
+ }
+ ]
+ }
+ ],
+ "teardown": [
+ "$TC qdisc del dev $DEV1 clsact"
+ ]
}
]
diff --git a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
index 82c38a13dfbf..a1f97a4b606e 100644
--- a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
+++ b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
@@ -392,26 +392,32 @@
"htb"
],
"plugins": {
- "requires": "nsPlugin"
+ "requires": [
+ "nsPlugin",
+ "scapyPlugin"
+ ]
},
"setup": [
- "$IP link set dev $DUMMY up || true",
- "$IP addr add 10.10.10.10/24 dev $DUMMY || true",
- "$TC qdisc add dev $DUMMY handle 1: root htb default 10",
- "$TC class add dev $DUMMY parent 1: classid 1:10 htb rate 1kbit",
- "$TC qdisc add dev $DUMMY parent 1:10 handle 10: fq_codel memory_limit 1 flows 1 target 0.1ms interval 1ms",
- "$TC filter add dev $DUMMY parent 1: protocol ip prio 1 u32 match ip protocol 1 0xff flowid 1:10",
- "ping -c 5 -f -I $DUMMY 10.10.10.1 > /dev/null || true",
- "sleep 0.1"
+ "$TC qdisc add dev $IFB handle 1: root htb default 10",
+ "$TC class add dev $IFB parent 1: classid 1:10 htb rate 1kbit",
+ "$TC qdisc add dev $IFB parent 1:10 handle 10: fq_codel memory_limit 1 flows 1 target 0.1ms interval 1ms",
+ "$TC filter add dev $IFB parent 1: protocol ip prio 1 u32 match ip protocol 1 0xff flowid 1:10",
+ "$TC qdisc add dev $DEV1 ingress",
+ "$TC filter add dev $DEV1 ingress protocol ip prio 1 u32 match ip protocol 1 0xff action mirred egress mirror dev $IFB"
],
- "cmdUnderTest": "$TC -s qdisc show dev $DUMMY",
+ "scapy": {
+ "iface": "$DEV0",
+ "count": 5,
+ "packet": "Ether()/IP(dst='10.10.10.1', src='10.10.10.10')/ICMP()"
+ },
+ "cmdUnderTest": "$TC -s qdisc show dev $IFB",
"expExitCode": "0",
- "verifyCmd": "$TC -s qdisc show dev $DUMMY | grep -A 5 'qdisc fq_codel'",
+ "verifyCmd": "$TC -s qdisc show dev $IFB | grep -A 5 'qdisc fq_codel'",
"matchPattern": "dropped [1-9][0-9]*",
"matchCount": "1",
"teardown": [
- "$TC qdisc del dev $DUMMY handle 1: root",
- "$IP addr del 10.10.10.10/24 dev $DUMMY || true"
+ "$TC qdisc del dev $IFB root",
+ "$TC qdisc del dev $DEV1 ingress"
]
},
{
@@ -423,26 +429,32 @@
"qfq"
],
"plugins": {
- "requires": "nsPlugin"
+ "requires": [
+ "nsPlugin",
+ "scapyPlugin"
+ ]
},
"setup": [
- "$IP link set dev $DUMMY up || true",
- "$IP addr add 10.10.10.10/24 dev $DUMMY || true",
- "$TC qdisc add dev $DUMMY handle 1: root qfq",
- "$TC class add dev $DUMMY parent 1: classid 1:10 qfq weight 1 maxpkt 1000",
- "$TC qdisc add dev $DUMMY parent 1:10 handle 10: fq_codel memory_limit 1 flows 1 target 0.1ms interval 1ms",
- "$TC filter add dev $DUMMY parent 1: protocol ip prio 1 u32 match ip protocol 1 0xff flowid 1:10",
- "ping -c 10 -s 1000 -f -I $DUMMY 10.10.10.1 > /dev/null || true",
- "sleep 0.1"
+ "$TC qdisc add dev $IFB handle 1: root qfq",
+ "$TC class add dev $IFB parent 1: classid 1:10 qfq weight 1 maxpkt 1000",
+ "$TC qdisc add dev $IFB parent 1:10 handle 10: fq_codel memory_limit 1 flows 1 target 0.1ms interval 1ms",
+ "$TC filter add dev $IFB parent 1: protocol ip prio 1 u32 match ip protocol 1 0xff flowid 1:10",
+ "$TC qdisc add dev $DEV1 ingress",
+ "$TC filter add dev $DEV1 ingress protocol ip prio 1 u32 match ip protocol 1 0xff action mirred egress mirror dev $IFB"
],
- "cmdUnderTest": "$TC -s qdisc show dev $DUMMY",
+ "scapy": {
+ "iface": "$DEV0",
+ "count": 10,
+ "packet": "Ether()/IP(dst='10.10.10.1', src='10.10.10.10')/ICMP()"
+ },
+ "cmdUnderTest": "$TC -s qdisc show dev $IFB",
"expExitCode": "0",
- "verifyCmd": "$TC -s qdisc show dev $DUMMY | grep -A 5 'qdisc fq_codel'",
+ "verifyCmd": "$TC -s qdisc show dev $IFB | grep -A 5 'qdisc fq_codel'",
"matchPattern": "dropped [1-9][0-9]*",
"matchCount": "1",
"teardown": [
- "$TC qdisc del dev $DUMMY handle 1: root",
- "$IP addr del 10.10.10.10/24 dev $DUMMY || true"
+ "$TC qdisc del dev $IFB root",
+ "$TC qdisc del dev $DEV1 ingress"
]
},
{
@@ -454,26 +466,32 @@
"hfsc"
],
"plugins": {
- "requires": "nsPlugin"
+ "requires": [
+ "nsPlugin",
+ "scapyPlugin"
+ ]
},
"setup": [
- "$IP link set dev $DUMMY up || true",
- "$IP addr add 10.10.10.10/24 dev $DUMMY || true",
- "$TC qdisc add dev $DUMMY handle 1: root hfsc default 10",
- "$TC class add dev $DUMMY parent 1: classid 1:10 hfsc sc rate 1kbit ul rate 1kbit",
- "$TC qdisc add dev $DUMMY parent 1:10 handle 10: fq_codel memory_limit 1 flows 1 target 0.1ms interval 1ms",
- "$TC filter add dev $DUMMY parent 1: protocol ip prio 1 u32 match ip protocol 1 0xff flowid 1:10",
- "ping -c 5 -f -I $DUMMY 10.10.10.1 > /dev/null || true",
- "sleep 0.1"
+ "$TC qdisc add dev $IFB handle 1: root hfsc default 10",
+ "$TC class add dev $IFB parent 1: classid 1:10 hfsc sc rate 1kbit ul rate 1kbit",
+ "$TC qdisc add dev $IFB parent 1:10 handle 10: fq_codel memory_limit 1 flows 1 target 0.1ms interval 1ms",
+ "$TC filter add dev $IFB parent 1: protocol ip prio 1 u32 match ip protocol 1 0xff flowid 1:10",
+ "$TC qdisc add dev $DEV1 ingress",
+ "$TC filter add dev $DEV1 ingress protocol ip prio 1 u32 match ip protocol 1 0xff action mirred egress mirror dev $IFB"
],
- "cmdUnderTest": "$TC -s qdisc show dev $DUMMY",
+ "scapy": {
+ "iface": "$DEV0",
+ "count": 5,
+ "packet": "Ether()/IP(dst='10.10.10.1', src='10.10.10.10')/ICMP()"
+ },
+ "cmdUnderTest": "$TC -s qdisc show dev $IFB",
"expExitCode": "0",
- "verifyCmd": "$TC -s qdisc show dev $DUMMY | grep -A 5 'qdisc fq_codel'",
+ "verifyCmd": "$TC -s qdisc show dev $IFB | grep -A 5 'qdisc fq_codel'",
"matchPattern": "dropped [1-9][0-9]*",
"matchCount": "1",
"teardown": [
- "$TC qdisc del dev $DUMMY handle 1: root",
- "$IP addr del 10.10.10.10/24 dev $DUMMY || true"
+ "$TC qdisc del dev $IFB root",
+ "$TC qdisc del dev $DEV1 ingress"
]
},
{
@@ -485,26 +503,32 @@
"drr"
],
"plugins": {
- "requires": "nsPlugin"
+ "requires": [
+ "nsPlugin",
+ "scapyPlugin"
+ ]
},
"setup": [
- "$IP link set dev $DUMMY up || true",
- "$IP addr add 10.10.10.10/24 dev $DUMMY || true",
- "$TC qdisc add dev $DUMMY handle 1: root drr",
- "$TC class add dev $DUMMY parent 1: classid 1:10 drr quantum 1500",
- "$TC qdisc add dev $DUMMY parent 1:10 handle 10: fq_codel memory_limit 1 flows 1 target 0.1ms interval 1ms",
- "$TC filter add dev $DUMMY parent 1: protocol ip prio 1 u32 match ip protocol 1 0xff flowid 1:10",
- "ping -c 5 -f -I $DUMMY 10.10.10.1 > /dev/null || true",
- "sleep 0.1"
+ "$TC qdisc add dev $IFB handle 1: root drr",
+ "$TC class add dev $IFB parent 1: classid 1:10 drr quantum 1500",
+ "$TC qdisc add dev $IFB parent 1:10 handle 10: fq_codel memory_limit 1 flows 1 target 0.1ms interval 1ms",
+ "$TC filter add dev $IFB parent 1: protocol ip prio 1 u32 match ip protocol 1 0xff flowid 1:10",
+ "$TC qdisc add dev $DEV1 ingress",
+ "$TC filter add dev $DEV1 ingress protocol ip prio 1 u32 match ip protocol 1 0xff action mirred egress mirror dev $IFB"
],
- "cmdUnderTest": "$TC -s qdisc show dev $DUMMY",
+ "scapy": {
+ "iface": "$DEV0",
+ "count": 5,
+ "packet": "Ether()/IP(dst='10.10.10.1', src='10.10.10.10')/ICMP()"
+ },
+ "cmdUnderTest": "$TC -s qdisc show dev $IFB",
"expExitCode": "0",
- "verifyCmd": "$TC -s qdisc show dev $DUMMY | grep -A 5 'qdisc fq_codel'",
+ "verifyCmd": "$TC -s qdisc show dev $IFB | grep -A 5 'qdisc fq_codel'",
"matchPattern": "dropped [1-9][0-9]*",
"matchCount": "1",
"teardown": [
- "$TC qdisc del dev $DUMMY handle 1: root",
- "$IP addr del 10.10.10.10/24 dev $DUMMY || true"
+ "$TC qdisc del dev $IFB root",
+ "$TC qdisc del dev $DEV1 ingress"
]
},
{
@@ -516,26 +540,32 @@
"ets"
],
"plugins": {
- "requires": "nsPlugin"
+ "requires": [
+ "nsPlugin",
+ "scapyPlugin"
+ ]
},
"setup": [
- "$IP link set dev $DUMMY up || true",
- "$IP addr add 10.10.10.10/24 dev $DUMMY || true",
- "$TC qdisc add dev $DUMMY handle 1: root ets bands 2 strict 1",
- "$TC class change dev $DUMMY parent 1: classid 1:1 ets",
- "$TC qdisc add dev $DUMMY parent 1:1 handle 10: fq_codel memory_limit 1 flows 1 target 0.1ms interval 1ms",
- "$TC filter add dev $DUMMY parent 1: protocol ip prio 1 u32 match ip protocol 1 0xff flowid 1:1",
- "ping -c 5 -f -I $DUMMY 10.10.10.1 > /dev/null || true",
- "sleep 0.1"
+ "$TC qdisc add dev $IFB handle 1: root ets bands 2 strict 1",
+ "$TC class change dev $IFB parent 1: classid 1:1 ets",
+ "$TC qdisc add dev $IFB parent 1:1 handle 10: fq_codel memory_limit 1 flows 1 target 0.1ms interval 1ms",
+ "$TC filter add dev $IFB parent 1: protocol ip prio 1 u32 match ip protocol 1 0xff flowid 1:1",
+ "$TC qdisc add dev $DEV1 ingress",
+ "$TC filter add dev $DEV1 ingress protocol ip prio 1 u32 match ip protocol 1 0xff action mirred egress mirror dev $IFB"
],
- "cmdUnderTest": "$TC -s qdisc show dev $DUMMY",
+ "scapy": {
+ "iface": "$DEV0",
+ "count": 5,
+ "packet": "Ether()/IP(dst='10.10.10.1', src='10.10.10.10')/ICMP()"
+ },
+ "cmdUnderTest": "$TC -s qdisc show dev $IFB",
"expExitCode": "0",
- "verifyCmd": "$TC -s qdisc show dev $DUMMY | grep -A 5 'qdisc fq_codel'",
+ "verifyCmd": "$TC -s qdisc show dev $IFB | grep -A 5 'qdisc fq_codel'",
"matchPattern": "dropped [1-9][0-9]*",
"matchCount": "1",
"teardown": [
- "$TC qdisc del dev $DUMMY handle 1: root",
- "$IP addr del 10.10.10.10/24 dev $DUMMY || true"
+ "$TC qdisc del dev $IFB root",
+ "$TC qdisc del dev $DEV1 ingress"
]
},
{
@@ -1326,5 +1356,189 @@
"teardown": [
"$TC qdisc del dev $DUMMY handle 1: root"
]
+ },
+ {
+ "id": "c797",
+ "name": "Verify fq_codel won't mistakenly deactivate QFQ parent class during peek",
+ "category": [
+ "qdisc",
+ "qfq",
+ "fq_codel"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "$IP link set dev $DUMMY up || true",
+ "$IP addr add 10.10.10.10/24 dev $DUMMY || true",
+ "$TC qdisc add dev $DUMMY root handle 1: qfq",
+ "$TC class add dev $DUMMY parent 1: classid 1:1 qfq weight 1 maxpkt 1000",
+ "$TC class add dev $DUMMY parent 1: classid 1:2 qfq weight 1 maxpkt 1000",
+ "$TC qdisc add dev $DUMMY parent 1:1 handle 2:0 plug limit 1024",
+ "$IP l set dev $DUMMY mtu 1500",
+ "$TC qdisc add dev $DUMMY parent 1:2 handle 10: fq_codel target 1 interval 1 flows 1",
+ "$TC filter add dev $DUMMY parent 1: protocol ip prio 1 u32 match ip dst 10.10.10.1/32 flowid 1:1",
+ "$TC filter add dev $DUMMY parent 1: protocol ip prio 2 u32 match ip dst 10.10.10.2/32 flowid 1:2",
+ "$IP l set dev $DUMMY mtu 65336",
+ "ping -c 1 -I $DUMMY 10.10.10.1 -W0.01 > /dev/null || true",
+ "ping -c 3 -s 2000 -I $DUMMY 10.10.10.2 -W0.01 > /dev/null || true",
+ "sleep 0.1"
+ ],
+ "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 2:0 plug release_indefinite",
+ "expExitCode": "0",
+ "verifyCmd": "$TC -s -j qdisc show dev $DUMMY",
+ "matchJSON": [
+ {
+ "kind": "qfq",
+ "handle": "1:",
+ "packets": 3,
+ "drops": 1,
+ "backlog": 0,
+ "qlen": 0
+ },
+ {
+ "kind": "plug",
+ "handle": "2:",
+ "packets": 1,
+ "drops": 0,
+ "backlog": 0,
+ "qlen": 0
+ },
+ {
+ "kind": "fq_codel",
+ "handle": "10:",
+ "packets": 2,
+ "drops": 1,
+ "backlog": 0,
+ "qlen": 0
+ }
+ ],
+ "teardown": [
+ "$TC qdisc del dev $DUMMY root",
+ "$IP addr del 10.10.10.10/24 dev $DUMMY || true"
+ ]
+ },
+ {
+ "id": "82d9",
+ "name": "Verify codel won't mistakenly deactivate QFQ parent class during peek",
+ "category": [
+ "qdisc",
+ "qfq",
+ "codel"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "$IP link set dev $DUMMY up || true",
+ "$IP addr add 10.10.10.10/24 dev $DUMMY || true",
+ "$TC qdisc add dev $DUMMY root handle 1: qfq",
+ "$TC class add dev $DUMMY parent 1: classid 1:1 qfq weight 1 maxpkt 1000",
+ "$TC class add dev $DUMMY parent 1: classid 1:2 qfq weight 1 maxpkt 1000",
+ "$TC qdisc add dev $DUMMY parent 1:1 handle 2:0 plug limit 1024",
+ "$IP l set dev $DUMMY mtu 1500",
+ "$TC qdisc add dev $DUMMY parent 1:2 handle 10: codel target 1ms interval 1ms",
+ "$TC filter add dev $DUMMY parent 1: protocol ip prio 1 u32 match ip dst 10.10.10.1/32 flowid 1:1",
+ "$TC filter add dev $DUMMY parent 1: protocol ip prio 2 u32 match ip dst 10.10.10.2/32 flowid 1:2",
+ "$IP l set dev $DUMMY mtu 65336",
+ "ping -c 1 -I $DUMMY 10.10.10.1 -W0.01 > /dev/null || true",
+ "ping -c 3 -s 2000 -I $DUMMY 10.10.10.2 -W0.01 > /dev/null || true",
+ "sleep 0.1"
+ ],
+ "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 2:0 plug release_indefinite",
+ "expExitCode": "0",
+ "verifyCmd": "$TC -s -j qdisc show dev $DUMMY",
+ "matchJSON": [
+ {
+ "kind": "qfq",
+ "handle": "1:",
+ "packets": 3,
+ "drops": 1,
+ "backlog": 0,
+ "qlen": 0
+ },
+ {
+ "kind": "plug",
+ "handle": "2:",
+ "packets": 1,
+ "drops": 0,
+ "backlog": 0,
+ "qlen": 0
+ },
+ {
+ "kind": "codel",
+ "handle": "10:",
+ "packets": 2,
+ "drops": 1,
+ "backlog": 0,
+ "qlen": 0
+ }
+ ],
+ "teardown": [
+ "$TC qdisc del dev $DUMMY root",
+ "$IP addr del 10.10.10.10/24 dev $DUMMY || true"
+ ]
+ },
+ {
+ "id": "d3da",
+ "name": "Verify dualpi2 won't mistakenly deactivate QFQ parent class during peek",
+ "category": [
+ "qdisc",
+ "qfq",
+ "dualpi2"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "$IP link set dev $DUMMY up || true",
+ "$IP addr add 10.10.10.10/24 dev $DUMMY || true" ,
+ "$TC qdisc add dev $DUMMY root handle 1: qfq",
+ "$TC class add dev $DUMMY parent 1: classid 1:1 qfq weight 1 maxpkt 1000",
+ "$TC class add dev $DUMMY parent 1: classid 1:2 qfq weight 1 maxpkt 1000",
+ "$TC qdisc add dev $DUMMY parent 1:1 handle 2:0 plug limit 1024",
+ "$TC qdisc add dev $DUMMY parent 1:2 handle 10: dualpi2 step_thresh 500ms",
+ "$TC filter add dev $DUMMY parent 10: protocol ip prio 1 matchall classid 10:1 action ok",
+ "$TC filter add dev $DUMMY parent 1: protocol ip prio 1 u32 match ip dst 10.10.10.1/32 flowid 1:1",
+ "$TC filter add dev $DUMMY parent 1: protocol ip prio 2 u32 match ip dst 10.10.10.2/32 flowid 1:2",
+ "ping -c 1 -I $DUMMY 10.10.10.1 -W0.01 || true",
+ "ping -c 3 -i 0.1 -I $DUMMY 10.10.10.2 -W0.01 || true",
+ "sleep 0.7",
+ "ping -c 1 -I $DUMMY 10.10.10.2 -W0.01 || true",
+ "$TC qdisc change dev $DUMMY handle 2:0 plug release_indefinite"
+ ],
+ "cmdUnderTest": "ping -c 1 -I $DUMMY 10.10.10.1 -W0.01",
+ "expExitCode": "1",
+ "verifyCmd": "$TC -s -j qdisc show dev $DUMMY",
+ "matchJSON": [
+ {
+ "kind": "qfq",
+ "handle": "1:",
+ "packets": 4,
+ "drops": 2,
+ "backlog": 0,
+ "qlen": 0
+ },
+ {
+ "kind": "plug",
+ "handle": "2:",
+ "packets": 2,
+ "drops": 0,
+ "backlog": 0,
+ "qlen": 0
+ },
+ {
+ "kind": "dualpi2",
+ "handle": "10:",
+ "packets": 2,
+ "drops": 2,
+ "backlog": 0,
+ "qlen": 0
+ }
+ ],
+ "teardown": [
+ "$TC qdisc del dev $DUMMY root",
+ "$IP addr del 10.10.10.10/24 dev $DUMMY || true"
+ ]
}
]
diff --git a/tools/testing/selftests/tc-testing/tdc.py b/tools/testing/selftests/tc-testing/tdc.py
index 81b4ac3f050c..511d66c36a2a 100755
--- a/tools/testing/selftests/tc-testing/tdc.py
+++ b/tools/testing/selftests/tc-testing/tdc.py
@@ -378,6 +378,7 @@ def run_one_test(pm, args, index, tidx):
dev0 = NAMES['DEV0']
dev1 = NAMES['DEV1']
dummy = NAMES['DUMMY']
+ ifb = NAMES['IFB']
result = True
tresult = ""
tap = ""
@@ -414,6 +415,7 @@ def run_one_test(pm, args, index, tidx):
NAMES['DEV0'] = '{}id{}'.format(NAMES['DEV0'], tidx['id'])
NAMES['DEV1'] = '{}id{}'.format(NAMES['DEV1'], tidx['id'])
NAMES['DUMMY'] = '{}id{}'.format(NAMES['DUMMY'], tidx['id'])
+ NAMES['IFB'] = '{}id{}'.format(NAMES['IFB'], tidx['id'])
pm.call_pre_case(tidx)
prepare_env(tidx, args, pm, 'setup', "-----> prepare stage", tidx["setup"])
@@ -474,6 +476,7 @@ def run_one_test(pm, args, index, tidx):
NAMES['DEV0'] = dev0
NAMES['DEV1'] = dev1
NAMES['DUMMY'] = dummy
+ NAMES['IFB'] = ifb
return res
diff --git a/tools/testing/selftests/tc-testing/tdc_config.py b/tools/testing/selftests/tc-testing/tdc_config.py
index 9488b03cbc2c..cd0bd42f05a5 100644
--- a/tools/testing/selftests/tc-testing/tdc_config.py
+++ b/tools/testing/selftests/tc-testing/tdc_config.py
@@ -17,6 +17,7 @@ NAMES = {
'DEV1': 'v0p1',
'DEV2': '',
'DUMMY': 'dummy1',
+ 'IFB': 'ifbtdc0',
'ETHTOOL': '/usr/sbin/ethtool',
'ETH': 'eth0',
'BATCH_FILE': './batch.txt',
diff --git a/tools/testing/selftests/vsock/vmtest.sh b/tools/testing/selftests/vsock/vmtest.sh
index d97913a6bdc7..310dfc2a39ad 100755
--- a/tools/testing/selftests/vsock/vmtest.sh
+++ b/tools/testing/selftests/vsock/vmtest.sh
@@ -330,27 +330,34 @@ check_netns() {
return 0
}
+# Compare MAJOR.MINOR versions numerically. Returns 0 (true) if $1 < $2.
+version_lt() {
+ local -a a=(${1//./ })
+ local -a b=(${2//./ })
+
+ if [[ "${a[0]}" -lt "${b[0]}" ]]; then
+ return 0
+ elif [[ "${a[0]}" -gt "${b[0]}" ]]; then
+ return 1
+ elif [[ "${a[1]}" -lt "${b[1]}" ]]; then
+ return 0
+ fi
+
+ return 1
+}
+
check_vng() {
- local tested_versions
local version
- local ok
- tested_versions=("1.33" "1.36" "1.37")
- version="$(vng --version)"
+ version="$(vng --version | awk '{print $2}')"
- ok=0
- for tv in "${tested_versions[@]}"; do
- if [[ "${version}" == *"${tv}"* ]]; then
- ok=1
- break
- fi
- done
-
- if [[ ! "${ok}" -eq 1 ]]; then
- printf "warning: vng version '%s' has not been tested and may " "${version}" >&2
- printf "not function properly.\n\tThe following versions have been tested: " >&2
- echo "${tested_versions[@]}" >&2
+ # Supported: 1.33, or any version >= 1.36. 1.34 and 1.35 are untested.
+ if [[ "${version}" == "1.33" ]] || ! version_lt "${version}" "1.36"; then
+ return
fi
+
+ printf "warning: vng version '%s' has not been tested and may " "${version}" >&2
+ printf "not function properly.\n\tSupported: 1.33 or >= 1.36\n" >&2
}
check_socat() {
@@ -438,8 +445,14 @@ vng_dry_run() {
# stopped with SIGTTOU and hangs until kselftest's timer expires.
# setsid works around this by launching vng in a new session that has
# no controlling terminal, so tcsetattr() succeeds.
+ #
+ # Fixed in 1.41 (https://github.com/arighi/virtme-ng/pull/453).
- setsid -w vng --run "$@" --dry-run &>/dev/null
+ if version_lt "$(vng --version | awk '{print $2}')" "1.41"; then
+ setsid -w vng --run "$@" --dry-run &>/dev/null
+ else
+ vng --run "$@" --dry-run &>/dev/null
+ fi
}
vm_start() {