]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
tools: ynl: convert netdev sample to selftest
authorJakub Kicinski <kuba@kernel.org>
Sat, 7 Mar 2026 03:36:22 +0000 (19:36 -0800)
committerJakub Kicinski <kuba@kernel.org>
Tue, 10 Mar 2026 00:02:26 +0000 (17:02 -0700)
Convert netdev.c to produce KTAP output with 3 tests:
- dev_dump: dump all netdev devices, skip if empty
- dev_get: query first device from dump by ifindex
- ntf_check: subscribe to "mgmt", create a veth via rt-link,
  verify netdev notification is received, then delete the veth

Remove stdin/scanf-based UI. Add rt-link dependency for the veth
notification test.

  TAP version 13
  1..3
  # Starting 3 tests from 1 test cases.
  #  RUN           netdev.dump ...
  #       lo[1] xdp-features (0): xdp-rx-metadata-features (0): xsk-fea...
  #     sit0[2] xdp-features (0): xdp-rx-metadata-features (0): xsk-fea...
  #            OK  netdev.dump
  ok 1 netdev.dump
  #  RUN           netdev.get ...
  #       lo[1] xdp-features (0): xdp-rx-metadata-features (0): xsk-fea...
  #            OK  netdev.get
  ok 2 netdev.get
  #  RUN           netdev.ntf_check ...
  #    veth0[7] xdp-features (0): xdp-rx-metadata-features (7): timesta...
  #            OK  netdev.ntf_check
  ok 3 netdev.ntf_check
  # PASSED: 3 / 3 tests passed.
  # Totals: pass:3 fail:0 xfail:0 xpass:0 skip:0 error:0

Reviewed-by: Donald Hunter <donald.hunter@gmail.com>
Tested-by: Donald Hunter <donald.hunter@gmail.com>
Link: https://patch.msgid.link/20260307033630.1396085-3-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
tools/net/ynl/tests/Makefile
tools/net/ynl/tests/netdev.c

index 5fa36c877235dbe120b6f2e1bb133411a1e30007..1d77d3662f46fd082483150177888eb24f6e9bae 100644 (file)
@@ -18,16 +18,29 @@ TEST_PROGS := \
        test_ynl_ethtool.sh \
 # end of TEST_PROGS
 
-SRCS=$(wildcard *.c)
-BINS=$(patsubst %.c,%,${SRCS})
-
+TEST_GEN_PROGS := \
+       netdev \
+# end of TEST_GEN_PROGS
+
+BINS := \
+       devlink \
+       ethtool \
+       ovs \
+       rt-addr \
+       rt-link \
+       rt-route \
+       tc \
+       tc-filter-add \
+# end of BINS
+
+CFLAGS_netdev:=$(CFLAGS_netdev) $(CFLAGS_rt-link)
 CFLAGS_tc-filter-add:=$(CFLAGS_tc)
 
 include $(wildcard *.d)
 
 INSTALL_PATH ?= $(DESTDIR)/usr/share/kselftest
 
-all: $(BINS) $(TEST_PROGS)
+all: $(TEST_GEN_PROGS) $(BINS)
 
 ../lib/ynl.a:
        @$(MAKE) -C ../lib
@@ -35,7 +48,7 @@ all: $(BINS) $(TEST_PROGS)
  ../generated/protos.a:
        @$(MAKE) -C ../generated
 
-$(BINS): ../lib/ynl.a ../generated/protos.a
+$(TEST_GEN_PROGS) $(BINS): %: %.c ../lib/ynl.a ../generated/protos.a
        @echo -e '\tCC test $@'
        @$(COMPILE.c) $(CFLAGS_$@) $@.c -o $@.o
        @$(LINK.c) $@.o -o $@  $(LDLIBS)
@@ -45,7 +58,7 @@ run_tests:
                ./$$test; \
        done
 
-install: $(TEST_PROGS) $(BINS)
+install: $(TEST_GEN_PROGS) $(BINS)
        @mkdir -p $(INSTALL_PATH)/ynl
        @cp ../../../testing/selftests/kselftest/ktap_helpers.sh $(INSTALL_PATH)/
        @for test in $(TEST_PROGS); do \
@@ -56,10 +69,10 @@ install: $(TEST_PROGS) $(BINS)
                    $$test > $(INSTALL_PATH)/ynl/$$name; \
                chmod +x $(INSTALL_PATH)/ynl/$$name; \
        done
-       @for bin in $(BINS); do \
+       @for bin in $(TEST_GEN_PROGS) $(BINS); do \
                cp $$bin $(INSTALL_PATH)/ynl/$$bin; \
        done
-       @for test in $(TEST_PROGS); do \
+       @for test in $(TEST_PROGS) $(TEST_GEN_PROGS); do \
                echo "ynl:$$test"; \
        done > $(INSTALL_PATH)/kselftest-list.txt
 
@@ -67,7 +80,7 @@ clean:
        rm -f *.o *.d *~
 
 distclean: clean
-       rm -f $(BINS)
+       rm -f $(TEST_GEN_PROGS) $(BINS)
 
 .PHONY: all install clean distclean run_tests
 .DEFAULT_GOAL=all
index 22609d44c89ab05e48b4e840490c55e17ae82c3a..f849e3d7f4b3f213f9a685539c625169bb64c162 100644 (file)
@@ -6,29 +6,29 @@
 
 #include <net/if.h>
 
-#include "netdev-user.h"
+#include <kselftest_harness.h>
 
-/* netdev genetlink family code sample
- * This sample shows off basics of the netdev family but also notification
- * handling, hence the somewhat odd UI. We subscribe to notifications first
- * then wait for ifc selection, so the socket may already accumulate
- * notifications as we wait. This allows us to test that YNL can handle
- * requests and notifications getting interleaved.
- */
+#include "netdev-user.h"
+#include "rt-link-user.h"
 
-static void netdev_print_device(struct netdev_dev_get_rsp *d, unsigned int op)
+static void netdev_print_device(struct __test_metadata *_metadata,
+                               struct netdev_dev_get_rsp *d, unsigned int op)
 {
        char ifname[IF_NAMESIZE];
        const char *name;
 
+       EXPECT_TRUE((bool)d->_present.ifindex);
        if (!d->_present.ifindex)
                return;
 
        name = if_indextoname(d->ifindex, ifname);
+       EXPECT_TRUE((bool)name);
        if (name)
-               printf("%8s", name);
-       printf("[%d]\t", d->ifindex);
+               ksft_print_msg("%8s[%d]\t", name, d->ifindex);
+       else
+               ksft_print_msg("[%d]\t", d->ifindex);
 
+       EXPECT_TRUE((bool)d->_present.xdp_features);
        if (!d->_present.xdp_features)
                return;
 
@@ -38,10 +38,12 @@ static void netdev_print_device(struct netdev_dev_get_rsp *d, unsigned int op)
                        printf(" %s", netdev_xdp_act_str(1 << i));
        }
 
-       printf(" xdp-rx-metadata-features (%llx):", d->xdp_rx_metadata_features);
+       printf(" xdp-rx-metadata-features (%llx):",
+              d->xdp_rx_metadata_features);
        for (int i = 0; d->xdp_rx_metadata_features >= 1U << i; i++) {
                if (d->xdp_rx_metadata_features & (1U << i))
-                       printf(" %s", netdev_xdp_rx_metadata_str(1 << i));
+                       printf(" %s",
+                              netdev_xdp_rx_metadata_str(1 << i));
        }
 
        printf(" xsk-features (%llx):", d->xsk_features);
@@ -58,71 +60,172 @@ static void netdev_print_device(struct netdev_dev_get_rsp *d, unsigned int op)
        printf("\n");
 }
 
-int main(int argc, char **argv)
+static int veth_create(struct ynl_sock *ys_link)
 {
-       struct netdev_dev_get_list *devs;
+       struct rt_link_getlink_ntf *ntf_gl;
+       struct rt_link_newlink_req *req;
        struct ynl_ntf_base_type *ntf;
-       struct ynl_error yerr;
+       int ret;
+
+       req = rt_link_newlink_req_alloc();
+       if (!req)
+               return -1;
+
+       rt_link_newlink_req_set_nlflags(req, NLM_F_CREATE | NLM_F_ECHO);
+       rt_link_newlink_req_set_linkinfo_kind(req, "veth");
+
+       ret = rt_link_newlink(ys_link, req);
+       rt_link_newlink_req_free(req);
+       if (ret)
+               return -1;
+
+       if (!ynl_has_ntf(ys_link))
+               return 0;
+
+       ntf = ynl_ntf_dequeue(ys_link);
+       if (!ntf || ntf->cmd != RTM_NEWLINK) {
+               ynl_ntf_free(ntf);
+               return 0;
+       }
+       ntf_gl = (void *)ntf;
+       ret = ntf_gl->obj._hdr.ifi_index;
+       ynl_ntf_free(ntf);
+
+       return ret;
+}
+
+static void veth_delete(struct __test_metadata *_metadata,
+                       struct ynl_sock *ys_link, int ifindex)
+{
+       struct rt_link_dellink_req *req;
+
+       req = rt_link_dellink_req_alloc();
+       ASSERT_NE(NULL, req);
+
+       req->_hdr.ifi_index = ifindex;
+       EXPECT_EQ(0, rt_link_dellink(ys_link, req));
+       rt_link_dellink_req_free(req);
+}
+
+FIXTURE(netdev)
+{
        struct ynl_sock *ys;
-       int ifindex = 0;
+       struct ynl_sock *ys_link;
+};
 
-       if (argc > 1)
-               ifindex = strtol(argv[1], NULL, 0);
+FIXTURE_SETUP(netdev)
+{
+       struct ynl_error yerr;
 
-       ys = ynl_sock_create(&ynl_netdev_family, &yerr);
-       if (!ys) {
-               fprintf(stderr, "YNL: %s\n", yerr.msg);
-               return 1;
+       self->ys = ynl_sock_create(&ynl_netdev_family, &yerr);
+       ASSERT_NE(NULL, self->ys) {
+               TH_LOG("Failed to create YNL netdev socket: %s", yerr.msg);
        }
+}
 
-       if (ynl_subscribe(ys, "mgmt"))
-               goto err_close;
+FIXTURE_TEARDOWN(netdev)
+{
+       if (self->ys_link)
+               ynl_sock_destroy(self->ys_link);
+       ynl_sock_destroy(self->ys);
+}
+
+TEST_F(netdev, dump)
+{
+       struct netdev_dev_get_list *devs;
 
-       printf("Select ifc ($ifindex; or 0 = dump; or -2 ntf check): ");
-       if (scanf("%d", &ifindex) != 1) {
-               fprintf(stderr, "Error: unable to parse input\n");
-               goto err_destroy;
+       devs = netdev_dev_get_dump(self->ys);
+       ASSERT_NE(NULL, devs) {
+               TH_LOG("dump failed: %s", self->ys->err.msg);
        }
 
-       if (ifindex > 0) {
-               struct netdev_dev_get_req *req;
-               struct netdev_dev_get_rsp *d;
-
-               req = netdev_dev_get_req_alloc();
-               netdev_dev_get_req_set_ifindex(req, ifindex);
-
-               d = netdev_dev_get(ys, req);
-               netdev_dev_get_req_free(req);
-               if (!d)
-                       goto err_close;
-
-               netdev_print_device(d, 0);
-               netdev_dev_get_rsp_free(d);
-       } else if (!ifindex) {
-               devs = netdev_dev_get_dump(ys);
-               if (!devs)
-                       goto err_close;
-
-               if (ynl_dump_empty(devs))
-                       fprintf(stderr, "Error: no devices reported\n");
-               ynl_dump_foreach(devs, d)
-                       netdev_print_device(d, 0);
+       if (ynl_dump_empty(devs)) {
                netdev_dev_get_list_free(devs);
-       } else if (ifindex == -2) {
-               ynl_ntf_check(ys);
+               SKIP(return, "no entries in dump");
        }
-       while ((ntf = ynl_ntf_dequeue(ys))) {
-               netdev_print_device((struct netdev_dev_get_rsp *)&ntf->data,
+
+       ynl_dump_foreach(devs, d)
+               netdev_print_device(_metadata, d, 0);
+
+       netdev_dev_get_list_free(devs);
+}
+
+TEST_F(netdev, get)
+{
+       struct netdev_dev_get_list *devs;
+       struct netdev_dev_get_req *req;
+       struct netdev_dev_get_rsp *dev;
+       int ifindex = 0;
+
+       devs = netdev_dev_get_dump(self->ys);
+       ASSERT_NE(NULL, devs) {
+               TH_LOG("dump failed: %s", self->ys->err.msg);
+       }
+
+       ynl_dump_foreach(devs, d) {
+               if (d->_present.ifindex) {
+                       ifindex = d->ifindex;
+                       break;
+               }
+       }
+       netdev_dev_get_list_free(devs);
+
+       if (!ifindex)
+               SKIP(return, "no device to query");
+
+       req = netdev_dev_get_req_alloc();
+       ASSERT_NE(NULL, req);
+       netdev_dev_get_req_set_ifindex(req, ifindex);
+
+       dev = netdev_dev_get(self->ys, req);
+       netdev_dev_get_req_free(req);
+       ASSERT_NE(NULL, dev) {
+               TH_LOG("dev_get failed: %s", self->ys->err.msg);
+       }
+
+       netdev_print_device(_metadata, dev, 0);
+       netdev_dev_get_rsp_free(dev);
+}
+
+TEST_F(netdev, ntf_check)
+{
+       struct ynl_ntf_base_type *ntf;
+       int veth_ifindex;
+       bool received;
+       int ret;
+
+       ret = ynl_subscribe(self->ys, "mgmt");
+       ASSERT_EQ(0, ret) {
+               TH_LOG("subscribe failed: %s", self->ys->err.msg);
+       }
+
+       self->ys_link = ynl_sock_create(&ynl_rt_link_family, NULL);
+       ASSERT_NE(NULL, self->ys_link)
+               TH_LOG("failed to create rt-link socket");
+
+       veth_ifindex = veth_create(self->ys_link);
+       ASSERT_GT(veth_ifindex, 0)
+               TH_LOG("failed to create veth");
+
+       ynl_ntf_check(self->ys);
+
+       ntf = ynl_ntf_dequeue(self->ys);
+       received = ntf;
+       if (ntf) {
+               netdev_print_device(_metadata,
+                                   (struct netdev_dev_get_rsp *)&ntf->data,
                                    ntf->cmd);
                ynl_ntf_free(ntf);
        }
 
-       ynl_sock_destroy(ys);
-       return 0;
+       /* Drain any remaining notifications */
+       while ((ntf = ynl_ntf_dequeue(self->ys)))
+               ynl_ntf_free(ntf);
+
+       veth_delete(_metadata, self->ys_link, veth_ifindex);
 
-err_close:
-       fprintf(stderr, "YNL: %s\n", ys->err.msg);
-err_destroy:
-       ynl_sock_destroy(ys);
-       return 2;
+       ASSERT_TRUE(received)
+               TH_LOG("no notification received");
 }
+
+TEST_HARNESS_MAIN