diff options
| author | Jakub Kicinski <kuba@kernel.org> | 2026-03-10 03:02:30 +0300 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-03-10 03:02:30 +0300 |
| commit | 86020d7db03abd337d89b02586ce0fb0d65667e0 (patch) | |
| tree | 8f2584bc705f58bee10d6dbdeefd28711090fbdd | |
| parent | 0bcac7b11262557c990da1ac564d45777eb6b005 (diff) | |
| parent | aa234faa5a4d0f213697c4b9012c75b80b9d2c20 (diff) | |
| download | linux-86020d7db03abd337d89b02586ce0fb0d65667e0.tar.xz | |
Merge branch 'tools-ynl-convert-samples-into-selftests'
Jakub Kicinski says:
====================
tools: ynl: convert samples into selftests
The "samples" were always poor man's tests, used to manually
confirm that C YNL works as expected. Since a proper tests/
directory now exists move the samples and use the kselftest
harness to turn them into selftests outputting KTAP.
====================
Link: https://patch.msgid.link/20260307033630.1396085-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
27 files changed, 1501 insertions, 1119 deletions
diff --git a/tools/net/ynl/Makefile b/tools/net/ynl/Makefile index 9b692f368be7..d514a48dae27 100644 --- a/tools/net/ynl/Makefile +++ b/tools/net/ynl/Makefile @@ -14,12 +14,12 @@ includedir ?= $(prefix)/include SPECDIR=../../../Documentation/netlink/specs -SUBDIRS = lib generated samples ynltool tests +SUBDIRS = lib generated ynltool tests all: $(SUBDIRS) libynl.a +tests: | lib generated libynl.a ynltool: | lib generated libynl.a -samples: | lib generated libynl.a: | lib generated @echo -e "\tAR $@" @ar rcs $@ lib/ynl.o generated/*-user.o diff --git a/tools/net/ynl/samples/Makefile b/tools/net/ynl/samples/Makefile deleted file mode 100644 index d76cbd41cbb1..000000000000 --- a/tools/net/ynl/samples/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 - -include ../Makefile.deps - -CC=gcc -CFLAGS += -std=gnu11 -O2 -W -Wall -Wextra -Wno-unused-parameter -Wshadow \ - -I../lib/ -I../generated/ -idirafter $(UAPI_PATH) -ifeq ("$(DEBUG)","1") - CFLAGS += -g -fsanitize=address -fsanitize=leak -static-libasan -endif - -LDLIBS=../lib/ynl.a ../generated/protos.a - -SRCS=$(wildcard *.c) -BINS=$(patsubst %.c,%,${SRCS}) - -include $(wildcard *.d) - -all: $(BINS) - -CFLAGS_page-pool=$(CFLAGS_netdev) -CFLAGS_tc-filter-add:=$(CFLAGS_tc) - -$(BINS): ../lib/ynl.a ../generated/protos.a $(SRCS) - @echo -e '\tCC sample $@' - @$(COMPILE.c) $(CFLAGS_$@) $@.c -o $@.o - @$(LINK.c) $@.o -o $@ $(LDLIBS) - -clean: - rm -f *.o *.d *~ - -distclean: clean - rm -f $(BINS) - -.PHONY: all clean distclean -.DEFAULT_GOAL=all diff --git a/tools/net/ynl/samples/devlink.c b/tools/net/ynl/samples/devlink.c deleted file mode 100644 index ac9dfb01f280..000000000000 --- a/tools/net/ynl/samples/devlink.c +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <stdio.h> -#include <string.h> - -#include <ynl.h> - -#include "devlink-user.h" - -int main(int argc, char **argv) -{ - struct devlink_get_list *devs; - struct ynl_sock *ys; - - ys = ynl_sock_create(&ynl_devlink_family, NULL); - if (!ys) - return 1; - - devs = devlink_get_dump(ys); - if (!devs) - goto err_close; - - ynl_dump_foreach(devs, d) { - struct devlink_info_get_req *info_req; - struct devlink_info_get_rsp *info_rsp; - unsigned i; - - printf("%s/%s:\n", d->bus_name, d->dev_name); - - info_req = devlink_info_get_req_alloc(); - devlink_info_get_req_set_bus_name(info_req, d->bus_name); - devlink_info_get_req_set_dev_name(info_req, d->dev_name); - - info_rsp = devlink_info_get(ys, info_req); - devlink_info_get_req_free(info_req); - if (!info_rsp) - goto err_free_devs; - - if (info_rsp->_len.info_driver_name) - printf(" driver: %s\n", info_rsp->info_driver_name); - if (info_rsp->_count.info_version_running) - printf(" running fw:\n"); - for (i = 0; i < info_rsp->_count.info_version_running; i++) - printf(" %s: %s\n", - info_rsp->info_version_running[i].info_version_name, - info_rsp->info_version_running[i].info_version_value); - printf(" ...\n"); - devlink_info_get_rsp_free(info_rsp); - } - devlink_get_list_free(devs); - - ynl_sock_destroy(ys); - - return 0; - -err_free_devs: - devlink_get_list_free(devs); -err_close: - fprintf(stderr, "YNL: %s\n", ys->err.msg); - ynl_sock_destroy(ys); - return 2; -} diff --git a/tools/net/ynl/samples/ethtool.c b/tools/net/ynl/samples/ethtool.c deleted file mode 100644 index a7ebbd1b98db..000000000000 --- a/tools/net/ynl/samples/ethtool.c +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <stdio.h> -#include <string.h> - -#include <ynl.h> - -#include <net/if.h> - -#include "ethtool-user.h" - -int main(int argc, char **argv) -{ - struct ethtool_channels_get_req_dump creq = {}; - struct ethtool_rings_get_req_dump rreq = {}; - struct ethtool_channels_get_list *channels; - struct ethtool_rings_get_list *rings; - struct ynl_sock *ys; - - ys = ynl_sock_create(&ynl_ethtool_family, NULL); - if (!ys) - return 1; - - creq._present.header = 1; /* ethtool needs an empty nest, sigh */ - channels = ethtool_channels_get_dump(ys, &creq); - if (!channels) - goto err_close; - - printf("Channels:\n"); - ynl_dump_foreach(channels, dev) { - printf(" %8s: ", dev->header.dev_name); - if (dev->_present.rx_count) - printf("rx %d ", dev->rx_count); - if (dev->_present.tx_count) - printf("tx %d ", dev->tx_count); - if (dev->_present.combined_count) - printf("combined %d ", dev->combined_count); - printf("\n"); - } - ethtool_channels_get_list_free(channels); - - rreq._present.header = 1; /* ethtool needs an empty nest.. */ - rings = ethtool_rings_get_dump(ys, &rreq); - if (!rings) - goto err_close; - - printf("Rings:\n"); - ynl_dump_foreach(rings, dev) { - printf(" %8s: ", dev->header.dev_name); - if (dev->_present.rx) - printf("rx %d ", dev->rx); - if (dev->_present.tx) - printf("tx %d ", dev->tx); - printf("\n"); - } - ethtool_rings_get_list_free(rings); - - ynl_sock_destroy(ys); - - return 0; - -err_close: - fprintf(stderr, "YNL (%d): %s\n", ys->err.code, ys->err.msg); - ynl_sock_destroy(ys); - return 2; -} diff --git a/tools/net/ynl/samples/netdev.c b/tools/net/ynl/samples/netdev.c deleted file mode 100644 index 22609d44c89a..000000000000 --- a/tools/net/ynl/samples/netdev.c +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <stdio.h> -#include <string.h> - -#include <ynl.h> - -#include <net/if.h> - -#include "netdev-user.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. - */ - -static void netdev_print_device(struct netdev_dev_get_rsp *d, unsigned int op) -{ - char ifname[IF_NAMESIZE]; - const char *name; - - if (!d->_present.ifindex) - return; - - name = if_indextoname(d->ifindex, ifname); - if (name) - printf("%8s", name); - printf("[%d]\t", d->ifindex); - - if (!d->_present.xdp_features) - return; - - printf("xdp-features (%llx):", d->xdp_features); - for (int i = 0; d->xdp_features >= 1U << i; i++) { - if (d->xdp_features & (1U << i)) - printf(" %s", netdev_xdp_act_str(1 << i)); - } - - 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(" xsk-features (%llx):", d->xsk_features); - for (int i = 0; d->xsk_features >= 1U << i; i++) { - if (d->xsk_features & (1U << i)) - printf(" %s", netdev_xsk_flags_str(1 << i)); - } - - printf(" xdp-zc-max-segs=%u", d->xdp_zc_max_segs); - - name = netdev_op_str(op); - if (name) - printf(" (ntf: %s)", name); - printf("\n"); -} - -int main(int argc, char **argv) -{ - struct netdev_dev_get_list *devs; - struct ynl_ntf_base_type *ntf; - struct ynl_error yerr; - struct ynl_sock *ys; - int ifindex = 0; - - if (argc > 1) - ifindex = strtol(argv[1], NULL, 0); - - ys = ynl_sock_create(&ynl_netdev_family, &yerr); - if (!ys) { - fprintf(stderr, "YNL: %s\n", yerr.msg); - return 1; - } - - if (ynl_subscribe(ys, "mgmt")) - goto err_close; - - 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; - } - - 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); - netdev_dev_get_list_free(devs); - } else if (ifindex == -2) { - ynl_ntf_check(ys); - } - while ((ntf = ynl_ntf_dequeue(ys))) { - netdev_print_device((struct netdev_dev_get_rsp *)&ntf->data, - ntf->cmd); - ynl_ntf_free(ntf); - } - - ynl_sock_destroy(ys); - return 0; - -err_close: - fprintf(stderr, "YNL: %s\n", ys->err.msg); -err_destroy: - ynl_sock_destroy(ys); - return 2; -} diff --git a/tools/net/ynl/samples/ovs.c b/tools/net/ynl/samples/ovs.c deleted file mode 100644 index 3e975c003d77..000000000000 --- a/tools/net/ynl/samples/ovs.c +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <stdio.h> -#include <string.h> - -#include <ynl.h> - -#include "ovs_datapath-user.h" - -int main(int argc, char **argv) -{ - struct ynl_sock *ys; - int err; - - ys = ynl_sock_create(&ynl_ovs_datapath_family, NULL); - if (!ys) - return 1; - - if (argc > 1) { - struct ovs_datapath_new_req *req; - - req = ovs_datapath_new_req_alloc(); - if (!req) - goto err_close; - - ovs_datapath_new_req_set_upcall_pid(req, 1); - ovs_datapath_new_req_set_name(req, argv[1]); - - err = ovs_datapath_new(ys, req); - ovs_datapath_new_req_free(req); - if (err) - goto err_close; - } else { - struct ovs_datapath_get_req_dump *req; - struct ovs_datapath_get_list *dps; - - printf("Dump:\n"); - req = ovs_datapath_get_req_dump_alloc(); - - dps = ovs_datapath_get_dump(ys, req); - ovs_datapath_get_req_dump_free(req); - if (!dps) - goto err_close; - - ynl_dump_foreach(dps, dp) { - printf(" %s(%d): pid:%u cache:%u\n", - dp->name, dp->_hdr.dp_ifindex, - dp->upcall_pid, dp->masks_cache_size); - } - ovs_datapath_get_list_free(dps); - } - - ynl_sock_destroy(ys); - - return 0; - -err_close: - fprintf(stderr, "YNL (%d): %s\n", ys->err.code, ys->err.msg); - ynl_sock_destroy(ys); - return 2; -} diff --git a/tools/net/ynl/samples/rt-addr.c b/tools/net/ynl/samples/rt-addr.c deleted file mode 100644 index 2edde5c36b18..000000000000 --- a/tools/net/ynl/samples/rt-addr.c +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <stdio.h> -#include <string.h> - -#include <ynl.h> - -#include <arpa/inet.h> -#include <net/if.h> - -#include "rt-addr-user.h" - -static void rt_addr_print(struct rt_addr_getaddr_rsp *a) -{ - char ifname[IF_NAMESIZE]; - char addr_str[64]; - const char *addr; - const char *name; - - name = if_indextoname(a->_hdr.ifa_index, ifname); - if (name) - printf("%16s: ", name); - - switch (a->_len.address) { - case 4: - addr = inet_ntop(AF_INET, a->address, - addr_str, sizeof(addr_str)); - break; - case 16: - addr = inet_ntop(AF_INET6, a->address, - addr_str, sizeof(addr_str)); - break; - default: - addr = NULL; - break; - } - if (addr) - printf("%s", addr); - else - printf("[%d]", a->_len.address); - - printf("\n"); -} - -int main(int argc, char **argv) -{ - struct rt_addr_getaddr_list *rsp; - struct rt_addr_getaddr_req *req; - struct ynl_error yerr; - struct ynl_sock *ys; - - ys = ynl_sock_create(&ynl_rt_addr_family, &yerr); - if (!ys) { - fprintf(stderr, "YNL: %s\n", yerr.msg); - return 1; - } - - req = rt_addr_getaddr_req_alloc(); - if (!req) - goto err_destroy; - - rsp = rt_addr_getaddr_dump(ys, req); - rt_addr_getaddr_req_free(req); - if (!rsp) - goto err_close; - - if (ynl_dump_empty(rsp)) - fprintf(stderr, "Error: no addresses reported\n"); - ynl_dump_foreach(rsp, addr) - rt_addr_print(addr); - rt_addr_getaddr_list_free(rsp); - - ynl_sock_destroy(ys); - return 0; - -err_close: - fprintf(stderr, "YNL: %s\n", ys->err.msg); -err_destroy: - ynl_sock_destroy(ys); - return 2; -} diff --git a/tools/net/ynl/samples/rt-link.c b/tools/net/ynl/samples/rt-link.c deleted file mode 100644 index acdd4b4a0f74..000000000000 --- a/tools/net/ynl/samples/rt-link.c +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <stdio.h> -#include <string.h> - -#include <ynl.h> - -#include <arpa/inet.h> -#include <net/if.h> - -#include "rt-link-user.h" - -static void rt_link_print(struct rt_link_getlink_rsp *r) -{ - unsigned int i; - - printf("%3d: ", r->_hdr.ifi_index); - - if (r->_len.ifname) - printf("%16s: ", r->ifname); - - if (r->_present.mtu) - printf("mtu %5d ", r->mtu); - - if (r->linkinfo._len.kind) - printf("kind %-8s ", r->linkinfo.kind); - else - printf(" %8s ", ""); - - if (r->prop_list._count.alt_ifname) { - printf("altname "); - for (i = 0; i < r->prop_list._count.alt_ifname; i++) - printf("%s ", r->prop_list.alt_ifname[i]->str); - printf(" "); - } - - if (r->linkinfo._present.data && r->linkinfo.data._present.netkit) { - struct rt_link_linkinfo_netkit_attrs *netkit; - const char *name; - - netkit = &r->linkinfo.data.netkit; - printf("primary %d ", netkit->primary); - - name = NULL; - if (netkit->_present.policy) - name = rt_link_netkit_policy_str(netkit->policy); - if (name) - printf("policy %s ", name); - } - - printf("\n"); -} - -static int rt_link_create_netkit(struct ynl_sock *ys) -{ - struct rt_link_getlink_ntf *ntf_gl; - struct rt_link_newlink_req *req; - struct ynl_ntf_base_type *ntf; - int ret; - - req = rt_link_newlink_req_alloc(); - if (!req) { - fprintf(stderr, "Can't alloc req\n"); - return -1; - } - - /* rtnetlink doesn't provide info about the created object. - * It expects us to set the ECHO flag and the dig the info out - * of the notifications... - */ - rt_link_newlink_req_set_nlflags(req, NLM_F_CREATE | NLM_F_ECHO); - - rt_link_newlink_req_set_linkinfo_kind(req, "netkit"); - - /* Test error messages */ - rt_link_newlink_req_set_linkinfo_data_netkit_policy(req, 10); - ret = rt_link_newlink(ys, req); - if (ret) { - printf("Testing error message for policy being bad:\n\t%s\n", ys->err.msg); - } else { - fprintf(stderr, "Warning: unexpected success creating netkit with bad attrs\n"); - goto created; - } - - rt_link_newlink_req_set_linkinfo_data_netkit_policy(req, NETKIT_DROP); - - ret = rt_link_newlink(ys, req); -created: - rt_link_newlink_req_free(req); - if (ret) { - fprintf(stderr, "YNL: %s\n", ys->err.msg); - return -1; - } - - if (!ynl_has_ntf(ys)) { - fprintf(stderr, - "Warning: interface created but received no notification, won't delete the interface\n"); - return 0; - } - - ntf = ynl_ntf_dequeue(ys); - if (ntf->cmd != RTM_NEWLINK) { - fprintf(stderr, - "Warning: unexpected notification type, won't delete the interface\n"); - return 0; - } - ntf_gl = (void *)ntf; - ret = ntf_gl->obj._hdr.ifi_index; - ynl_ntf_free(ntf); - - return ret; -} - -static void rt_link_del(struct ynl_sock *ys, int ifindex) -{ - struct rt_link_dellink_req *req; - - req = rt_link_dellink_req_alloc(); - if (!req) { - fprintf(stderr, "Can't alloc req\n"); - return; - } - - req->_hdr.ifi_index = ifindex; - if (rt_link_dellink(ys, req)) - fprintf(stderr, "YNL: %s\n", ys->err.msg); - else - fprintf(stderr, - "Trying to delete a Netkit interface (ifindex %d)\n", - ifindex); - - rt_link_dellink_req_free(req); -} - -int main(int argc, char **argv) -{ - struct rt_link_getlink_req_dump *req; - struct rt_link_getlink_list *rsp; - struct ynl_error yerr; - struct ynl_sock *ys; - int created = 0; - - ys = ynl_sock_create(&ynl_rt_link_family, &yerr); - if (!ys) { - fprintf(stderr, "YNL: %s\n", yerr.msg); - return 1; - } - - if (argc > 1) { - fprintf(stderr, "Trying to create a Netkit interface\n"); - created = rt_link_create_netkit(ys); - if (created < 0) - goto err_destroy; - } - - req = rt_link_getlink_req_dump_alloc(); - if (!req) - goto err_del_ifc; - - rsp = rt_link_getlink_dump(ys, req); - rt_link_getlink_req_dump_free(req); - if (!rsp) - goto err_close; - - if (ynl_dump_empty(rsp)) - fprintf(stderr, "Error: no links reported\n"); - ynl_dump_foreach(rsp, link) - rt_link_print(link); - rt_link_getlink_list_free(rsp); - - if (created) - rt_link_del(ys, created); - - ynl_sock_destroy(ys); - return 0; - -err_close: - fprintf(stderr, "YNL: %s\n", ys->err.msg); -err_del_ifc: - if (created) - rt_link_del(ys, created); -err_destroy: - ynl_sock_destroy(ys); - return 2; -} diff --git a/tools/net/ynl/samples/rt-route.c b/tools/net/ynl/samples/rt-route.c deleted file mode 100644 index 7427104a96df..000000000000 --- a/tools/net/ynl/samples/rt-route.c +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <stdio.h> -#include <string.h> - -#include <ynl.h> - -#include <arpa/inet.h> -#include <net/if.h> - -#include "rt-route-user.h" - -static void rt_route_print(struct rt_route_getroute_rsp *r) -{ - char ifname[IF_NAMESIZE]; - char route_str[64]; - const char *route; - const char *name; - - /* Ignore local */ - if (r->_hdr.rtm_table == RT_TABLE_LOCAL) - return; - - if (r->_present.oif) { - name = if_indextoname(r->oif, ifname); - if (name) - printf("oif: %-16s ", name); - } - - if (r->_len.dst) { - route = inet_ntop(r->_hdr.rtm_family, r->dst, - route_str, sizeof(route_str)); - printf("dst: %s/%d", route, r->_hdr.rtm_dst_len); - } - - if (r->_len.gateway) { - route = inet_ntop(r->_hdr.rtm_family, r->gateway, - route_str, sizeof(route_str)); - printf("gateway: %s ", route); - } - - printf("\n"); -} - -int main(int argc, char **argv) -{ - struct rt_route_getroute_req_dump *req; - struct rt_route_getroute_list *rsp; - struct ynl_error yerr; - struct ynl_sock *ys; - - ys = ynl_sock_create(&ynl_rt_route_family, &yerr); - if (!ys) { - fprintf(stderr, "YNL: %s\n", yerr.msg); - return 1; - } - - req = rt_route_getroute_req_dump_alloc(); - if (!req) - goto err_destroy; - - rsp = rt_route_getroute_dump(ys, req); - rt_route_getroute_req_dump_free(req); - if (!rsp) - goto err_close; - - if (ynl_dump_empty(rsp)) - fprintf(stderr, "Error: no routeesses reported\n"); - ynl_dump_foreach(rsp, route) - rt_route_print(route); - rt_route_getroute_list_free(rsp); - - ynl_sock_destroy(ys); - return 0; - -err_close: - fprintf(stderr, "YNL: %s\n", ys->err.msg); -err_destroy: - ynl_sock_destroy(ys); - return 2; -} diff --git a/tools/net/ynl/samples/tc-filter-add.c b/tools/net/ynl/samples/tc-filter-add.c deleted file mode 100644 index 97871e9e9edc..000000000000 --- a/tools/net/ynl/samples/tc-filter-add.c +++ /dev/null @@ -1,335 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <stdio.h> -#include <string.h> -#include <stdlib.h> -#include <arpa/inet.h> -#include <linux/pkt_sched.h> -#include <linux/tc_act/tc_vlan.h> -#include <linux/tc_act/tc_gact.h> -#include <linux/if_ether.h> -#include <net/if.h> - -#include <ynl.h> - -#include "tc-user.h" - -#define TC_HANDLE (0xFFFF << 16) - -const char *vlan_act_name(struct tc_vlan *p) -{ - switch (p->v_action) { - case TCA_VLAN_ACT_POP: - return "pop"; - case TCA_VLAN_ACT_PUSH: - return "push"; - case TCA_VLAN_ACT_MODIFY: - return "modify"; - default: - break; - } - - return "not supported"; -} - -const char *gact_act_name(struct tc_gact *p) -{ - switch (p->action) { - case TC_ACT_SHOT: - return "drop"; - case TC_ACT_OK: - return "ok"; - case TC_ACT_PIPE: - return "pipe"; - default: - break; - } - - return "not supported"; -} - -static void print_vlan(struct tc_act_vlan_attrs *vlan) -{ - printf("%s ", vlan_act_name(vlan->parms)); - if (vlan->_present.push_vlan_id) - printf("id %u ", vlan->push_vlan_id); - if (vlan->_present.push_vlan_protocol) - printf("protocol %#x ", ntohs(vlan->push_vlan_protocol)); - if (vlan->_present.push_vlan_priority) - printf("priority %u ", vlan->push_vlan_priority); -} - -static void print_gact(struct tc_act_gact_attrs *gact) -{ - struct tc_gact *p = gact->parms; - - printf("%s ", gact_act_name(p)); -} - -static void flower_print(struct tc_flower_attrs *flower, const char *kind) -{ - struct tc_act_attrs *a; - unsigned int i; - - printf("%s:\n", kind); - - if (flower->_present.key_vlan_id) - printf(" vlan_id: %u\n", flower->key_vlan_id); - if (flower->_present.key_vlan_prio) - printf(" vlan_prio: %u\n", flower->key_vlan_prio); - if (flower->_present.key_num_of_vlans) - printf(" num_of_vlans: %u\n", flower->key_num_of_vlans); - - for (i = 0; i < flower->_count.act; i++) { - a = &flower->act[i]; - printf("action order: %i %s ", i + 1, a->kind); - if (a->options._present.vlan) - print_vlan(&a->options.vlan); - else if (a->options._present.gact) - print_gact(&a->options.gact); - printf("\n"); - } - printf("\n"); -} - -static void tc_filter_print(struct tc_gettfilter_rsp *f) -{ - struct tc_options_msg *opt = &f->options; - - if (opt->_present.flower) - flower_print(&opt->flower, f->kind); - else if (f->_len.kind) - printf("%s pref %u proto: %#x\n", f->kind, - (f->_hdr.tcm_info >> 16), - ntohs(TC_H_MIN(f->_hdr.tcm_info))); -} - -static int tc_filter_add(struct ynl_sock *ys, int ifi) -{ - struct tc_newtfilter_req *req; - struct tc_act_attrs *acts; - struct tc_vlan p = { - .action = TC_ACT_PIPE, - .v_action = TCA_VLAN_ACT_PUSH - }; - __u16 flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE; - int ret; - - req = tc_newtfilter_req_alloc(); - if (!req) { - fprintf(stderr, "tc_newtfilter_req_alloc failed\n"); - return -1; - } - memset(req, 0, sizeof(*req)); - - acts = tc_act_attrs_alloc(3); - if (!acts) { - fprintf(stderr, "tc_act_attrs_alloc\n"); - tc_newtfilter_req_free(req); - return -1; - } - memset(acts, 0, sizeof(*acts) * 3); - - req->_hdr.tcm_ifindex = ifi; - req->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS); - req->_hdr.tcm_info = TC_H_MAKE(1 << 16, htons(ETH_P_8021Q)); - req->chain = 0; - - tc_newtfilter_req_set_nlflags(req, flags); - tc_newtfilter_req_set_kind(req, "flower"); - tc_newtfilter_req_set_options_flower_key_vlan_id(req, 100); - tc_newtfilter_req_set_options_flower_key_vlan_prio(req, 5); - tc_newtfilter_req_set_options_flower_key_num_of_vlans(req, 3); - - __tc_newtfilter_req_set_options_flower_act(req, acts, 3); - - /* Skip action at index 0 because in TC, the action array - * index starts at 1, with each index defining the action's - * order. In contrast, in YNL indexed arrays start at index 0. - */ - tc_act_attrs_set_kind(&acts[1], "vlan"); - tc_act_attrs_set_options_vlan_parms(&acts[1], &p, sizeof(p)); - tc_act_attrs_set_options_vlan_push_vlan_id(&acts[1], 200); - tc_act_attrs_set_kind(&acts[2], "vlan"); - tc_act_attrs_set_options_vlan_parms(&acts[2], &p, sizeof(p)); - tc_act_attrs_set_options_vlan_push_vlan_id(&acts[2], 300); - - tc_newtfilter_req_set_options_flower_flags(req, 0); - tc_newtfilter_req_set_options_flower_key_eth_type(req, htons(0x8100)); - - ret = tc_newtfilter(ys, req); - if (ret) - fprintf(stderr, "tc_newtfilter: %s\n", ys->err.msg); - - tc_newtfilter_req_free(req); - - return ret; -} - -static int tc_filter_show(struct ynl_sock *ys, int ifi) -{ - struct tc_gettfilter_req_dump *req; - struct tc_gettfilter_list *rsp; - - req = tc_gettfilter_req_dump_alloc(); - if (!req) { - fprintf(stderr, "tc_gettfilter_req_dump_alloc failed\n"); - return -1; - } - memset(req, 0, sizeof(*req)); - - req->_hdr.tcm_ifindex = ifi; - req->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS); - req->_present.chain = 1; - req->chain = 0; - - rsp = tc_gettfilter_dump(ys, req); - tc_gettfilter_req_dump_free(req); - if (!rsp) { - fprintf(stderr, "YNL: %s\n", ys->err.msg); - return -1; - } - - if (ynl_dump_empty(rsp)) - fprintf(stderr, "Error: no filters reported\n"); - else - ynl_dump_foreach(rsp, flt) tc_filter_print(flt); - - tc_gettfilter_list_free(rsp); - - return 0; -} - -static int tc_filter_del(struct ynl_sock *ys, int ifi) -{ - struct tc_deltfilter_req *req; - __u16 flags = NLM_F_REQUEST; - int ret; - - req = tc_deltfilter_req_alloc(); - if (!req) { - fprintf(stderr, "tc_deltfilter_req_alloc failed\n"); - return -1; - } - memset(req, 0, sizeof(*req)); - - req->_hdr.tcm_ifindex = ifi; - req->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS); - req->_hdr.tcm_info = TC_H_MAKE(1 << 16, htons(ETH_P_8021Q)); - tc_deltfilter_req_set_nlflags(req, flags); - - ret = tc_deltfilter(ys, req); - if (ret) - fprintf(stderr, "tc_deltfilter failed: %s\n", ys->err.msg); - - tc_deltfilter_req_free(req); - - return ret; -} - -static int tc_clsact_add(struct ynl_sock *ys, int ifi) -{ - struct tc_newqdisc_req *req; - __u16 flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE; - int ret; - - req = tc_newqdisc_req_alloc(); - if (!req) { - fprintf(stderr, "tc_newqdisc_req_alloc failed\n"); - return -1; - } - memset(req, 0, sizeof(*req)); - - req->_hdr.tcm_ifindex = ifi; - req->_hdr.tcm_parent = TC_H_CLSACT; - req->_hdr.tcm_handle = TC_HANDLE; - tc_newqdisc_req_set_nlflags(req, flags); - tc_newqdisc_req_set_kind(req, "clsact"); - - ret = tc_newqdisc(ys, req); - if (ret) - fprintf(stderr, "tc_newqdisc failed: %s\n", ys->err.msg); - - tc_newqdisc_req_free(req); - - return ret; -} - -static int tc_clsact_del(struct ynl_sock *ys, int ifi) -{ - struct tc_delqdisc_req *req; - __u16 flags = NLM_F_REQUEST; - int ret; - - req = tc_delqdisc_req_alloc(); - if (!req) { - fprintf(stderr, "tc_delqdisc_req_alloc failed\n"); - return -1; - } - memset(req, 0, sizeof(*req)); - - req->_hdr.tcm_ifindex = ifi; - req->_hdr.tcm_parent = TC_H_CLSACT; - req->_hdr.tcm_handle = TC_HANDLE; - tc_delqdisc_req_set_nlflags(req, flags); - - ret = tc_delqdisc(ys, req); - if (ret) - fprintf(stderr, "tc_delqdisc failed: %s\n", ys->err.msg); - - tc_delqdisc_req_free(req); - - return ret; -} - -static int tc_filter_config(struct ynl_sock *ys, int ifi) -{ - int ret = 0; - - if (tc_filter_add(ys, ifi)) - return -1; - - ret = tc_filter_show(ys, ifi); - - if (tc_filter_del(ys, ifi)) - return -1; - - return ret; -} - -int main(int argc, char **argv) -{ - struct ynl_error yerr; - struct ynl_sock *ys; - int ifi, ret = 0; - - if (argc < 2) { - fprintf(stderr, "Usage: %s <interface_name>\n", argv[0]); - return 1; - } - ifi = if_nametoindex(argv[1]); - if (!ifi) { - perror("if_nametoindex"); - return 1; - } - - ys = ynl_sock_create(&ynl_tc_family, &yerr); - if (!ys) { - fprintf(stderr, "YNL: %s\n", yerr.msg); - return 1; - } - - if (tc_clsact_add(ys, ifi)) { - ret = 2; - goto err_destroy; - } - - if (tc_filter_config(ys, ifi)) - ret = 3; - - if (tc_clsact_del(ys, ifi)) - ret = 4; - -err_destroy: - ynl_sock_destroy(ys); - return ret; -} diff --git a/tools/net/ynl/samples/tc.c b/tools/net/ynl/samples/tc.c deleted file mode 100644 index 0bfff0fdd792..000000000000 --- a/tools/net/ynl/samples/tc.c +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <stdio.h> -#include <string.h> - -#include <ynl.h> - -#include <net/if.h> - -#include "tc-user.h" - -static void tc_qdisc_print(struct tc_getqdisc_rsp *q) -{ - char ifname[IF_NAMESIZE]; - const char *name; - - name = if_indextoname(q->_hdr.tcm_ifindex, ifname); - if (name) - printf("%16s: ", name); - - if (q->_len.kind) { - printf("%s ", q->kind); - - if (q->options._present.fq_codel) { - struct tc_fq_codel_attrs *fq_codel; - struct tc_fq_codel_xstats *stats; - - fq_codel = &q->options.fq_codel; - stats = q->stats2.app.fq_codel; - - if (fq_codel->_present.limit) - printf("limit: %dp ", fq_codel->limit); - if (fq_codel->_present.target) - printf("target: %dms ", - (fq_codel->target + 500) / 1000); - if (q->stats2.app._len.fq_codel) - printf("new_flow_cnt: %d ", - stats->qdisc_stats.new_flow_count); - } - } - - printf("\n"); -} - -int main(int argc, char **argv) -{ - struct tc_getqdisc_req_dump *req; - struct tc_getqdisc_list *rsp; - struct ynl_error yerr; - struct ynl_sock *ys; - - ys = ynl_sock_create(&ynl_tc_family, &yerr); - if (!ys) { - fprintf(stderr, "YNL: %s\n", yerr.msg); - return 1; - } - - req = tc_getqdisc_req_dump_alloc(); - if (!req) - goto err_destroy; - - rsp = tc_getqdisc_dump(ys, req); - tc_getqdisc_req_dump_free(req); - if (!rsp) - goto err_close; - - if (ynl_dump_empty(rsp)) - fprintf(stderr, "Error: no addresses reported\n"); - ynl_dump_foreach(rsp, qdisc) - tc_qdisc_print(qdisc); - tc_getqdisc_list_free(rsp); - - ynl_sock_destroy(ys); - return 0; - -err_close: - fprintf(stderr, "YNL: %s\n", ys->err.msg); -err_destroy: - ynl_sock_destroy(ys); - return 2; -} diff --git a/tools/net/ynl/samples/.gitignore b/tools/net/ynl/tests/.gitignore index 05087ee323ba..045385df42a4 100644 --- a/tools/net/ynl/samples/.gitignore +++ b/tools/net/ynl/tests/.gitignore @@ -1,8 +1,7 @@ -ethtool devlink +ethtool netdev ovs -page-pool rt-addr rt-link rt-route diff --git a/tools/net/ynl/tests/Makefile b/tools/net/ynl/tests/Makefile index eb166c9550db..2a02958c7039 100644 --- a/tools/net/ynl/tests/Makefile +++ b/tools/net/ynl/tests/Makefile @@ -1,21 +1,69 @@ # SPDX-License-Identifier: GPL-2.0 # Makefile for YNL tests +include ../Makefile.deps + +CC=gcc +CFLAGS += -std=gnu11 -O2 -W -Wall -Wextra -Wno-unused-parameter -Wshadow \ + -I../lib/ -I../generated/ -I../../../testing/selftests/ \ + -idirafter $(UAPI_PATH) +ifneq ("$(NDEBUG)","1") + CFLAGS += -g -fsanitize=address -fsanitize=leak -static-libasan +endif + +LDLIBS=../lib/ynl.a ../generated/protos.a + TEST_PROGS := \ + devlink.sh \ + ethtool.sh \ + rt-addr.sh \ + rt-route.sh \ test_ynl_cli.sh \ test_ynl_ethtool.sh \ # end of TEST_PROGS +TEST_GEN_PROGS := \ + netdev \ + ovs \ + rt-link \ + tc \ +# end of TEST_GEN_PROGS + +TEST_GEN_FILES := \ + devlink \ + ethtool \ + rt-addr \ + rt-route \ +# end of TEST_GEN_FILES + +TEST_FILES := ynl_nsim_lib.sh + +CFLAGS_netdev:=$(CFLAGS_netdev) $(CFLAGS_rt-link) +CFLAGS_ovs:=$(CFLAGS_ovs_datapath) + +include $(wildcard *.d) + INSTALL_PATH ?= $(DESTDIR)/usr/share/kselftest -all: $(TEST_PROGS) +all: $(TEST_GEN_PROGS) $(TEST_GEN_FILES) + +../lib/ynl.a: + @$(MAKE) -C ../lib + + ../generated/protos.a: + @$(MAKE) -C ../generated + +$(TEST_GEN_PROGS) $(TEST_GEN_FILES): %: %.c ../lib/ynl.a ../generated/protos.a + @echo -e '\tCC test $@' + @$(COMPILE.c) $(CFLAGS_$@) $@.c -o $@.o + @$(LINK.c) $@.o -o $@ $(LDLIBS) run_tests: @for test in $(TEST_PROGS); do \ ./$$test; \ done -install: $(TEST_PROGS) +install: $(TEST_GEN_PROGS) $(TEST_GEN_FILES) @mkdir -p $(INSTALL_PATH)/ynl @cp ../../../testing/selftests/kselftest/ktap_helpers.sh $(INSTALL_PATH)/ @for test in $(TEST_PROGS); do \ @@ -26,11 +74,21 @@ install: $(TEST_PROGS) $$test > $(INSTALL_PATH)/ynl/$$name; \ chmod +x $(INSTALL_PATH)/ynl/$$name; \ done - @for test in $(TEST_PROGS); do \ + @for file in $(TEST_FILES); do \ + cp $$file $(INSTALL_PATH)/ynl/$$file; \ + done + @for bin in $(TEST_GEN_PROGS) $(TEST_GEN_FILES); do \ + cp $$bin $(INSTALL_PATH)/ynl/$$bin; \ + done + @for test in $(TEST_PROGS) $(TEST_GEN_PROGS); do \ echo "ynl:$$test"; \ done > $(INSTALL_PATH)/kselftest-list.txt -clean distclean: - @# Nothing to clean +clean: + rm -f *.o *.d *~ + +distclean: clean + rm -f $(TEST_GEN_PROGS) $(TEST_GEN_FILES) -.PHONY: all install clean run_tests +.PHONY: all install clean distclean run_tests +.DEFAULT_GOAL=all diff --git a/tools/net/ynl/tests/config b/tools/net/ynl/tests/config index 339f1309c03f..75c0fe72391f 100644 --- a/tools/net/ynl/tests/config +++ b/tools/net/ynl/tests/config @@ -1,6 +1,14 @@ CONFIG_DUMMY=m CONFIG_INET_DIAG=y CONFIG_IPV6=y +CONFIG_NET_ACT_VLAN=m +CONFIG_NET_CLS_ACT=y +CONFIG_NET_CLS_FLOWER=m +CONFIG_NET_SCH_FQ_CODEL=m +CONFIG_NET_SCH_INGRESS=m CONFIG_NET_NS=y +CONFIG_NET_SCHED=y CONFIG_NETDEVSIM=m +CONFIG_NETKIT=y +CONFIG_OPENVSWITCH=m CONFIG_VETH=m diff --git a/tools/net/ynl/tests/devlink.c b/tools/net/ynl/tests/devlink.c new file mode 100644 index 000000000000..2e668bb15af1 --- /dev/null +++ b/tools/net/ynl/tests/devlink.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> + +#include <ynl.h> + +#include <kselftest_harness.h> + +#include "devlink-user.h" + +FIXTURE(devlink) +{ + struct ynl_sock *ys; +}; + +FIXTURE_SETUP(devlink) +{ + self->ys = ynl_sock_create(&ynl_devlink_family, NULL); + ASSERT_NE(NULL, self->ys) + TH_LOG("failed to create devlink socket"); +} + +FIXTURE_TEARDOWN(devlink) +{ + ynl_sock_destroy(self->ys); +} + +TEST_F(devlink, dump) +{ + struct devlink_get_list *devs; + + devs = devlink_get_dump(self->ys); + ASSERT_NE(NULL, devs) { + TH_LOG("dump failed: %s", self->ys->err.msg); + } + + if (ynl_dump_empty(devs)) { + devlink_get_list_free(devs); + SKIP(return, "no entries in dump"); + } + + ynl_dump_foreach(devs, d) { + EXPECT_TRUE((bool)d->_len.bus_name); + EXPECT_TRUE((bool)d->_len.dev_name); + ksft_print_msg("%s/%s\n", d->bus_name, d->dev_name); + } + + devlink_get_list_free(devs); +} + +TEST_F(devlink, info) +{ + struct devlink_get_list *devs; + + devs = devlink_get_dump(self->ys); + ASSERT_NE(NULL, devs) { + TH_LOG("dump failed: %s", self->ys->err.msg); + } + + if (ynl_dump_empty(devs)) { + devlink_get_list_free(devs); + SKIP(return, "no devices to query"); + } + + ynl_dump_foreach(devs, d) { + struct devlink_info_get_req *info_req; + struct devlink_info_get_rsp *info_rsp; + unsigned int i; + + EXPECT_TRUE((bool)d->_len.bus_name); + EXPECT_TRUE((bool)d->_len.dev_name); + ksft_print_msg("%s/%s:\n", d->bus_name, d->dev_name); + + info_req = devlink_info_get_req_alloc(); + ASSERT_NE(NULL, info_req); + devlink_info_get_req_set_bus_name(info_req, d->bus_name); + devlink_info_get_req_set_dev_name(info_req, d->dev_name); + + info_rsp = devlink_info_get(self->ys, info_req); + devlink_info_get_req_free(info_req); + ASSERT_NE(NULL, info_rsp) { + devlink_get_list_free(devs); + TH_LOG("info_get failed: %s", self->ys->err.msg); + } + + EXPECT_TRUE((bool)info_rsp->_len.info_driver_name); + if (info_rsp->_len.info_driver_name) + ksft_print_msg(" driver: %s\n", + info_rsp->info_driver_name); + if (info_rsp->_count.info_version_running) + ksft_print_msg(" running fw:\n"); + for (i = 0; i < info_rsp->_count.info_version_running; i++) + ksft_print_msg(" %s: %s\n", + info_rsp->info_version_running[i].info_version_name, + info_rsp->info_version_running[i].info_version_value); + devlink_info_get_rsp_free(info_rsp); + } + devlink_get_list_free(devs); +} + +TEST_HARNESS_MAIN diff --git a/tools/net/ynl/tests/devlink.sh b/tools/net/ynl/tests/devlink.sh new file mode 100755 index 000000000000..a684c749aa5e --- /dev/null +++ b/tools/net/ynl/tests/devlink.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +source "$(dirname "$(realpath "$0")")/ynl_nsim_lib.sh" +nsim_setup +"$(dirname "$(realpath "$0")")/devlink" diff --git a/tools/net/ynl/tests/ethtool.c b/tools/net/ynl/tests/ethtool.c new file mode 100644 index 000000000000..926a75d23c9b --- /dev/null +++ b/tools/net/ynl/tests/ethtool.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> + +#include <ynl.h> + +#include <net/if.h> + +#include <kselftest_harness.h> + +#include "ethtool-user.h" + +FIXTURE(ethtool) +{ + struct ynl_sock *ys; +}; + +FIXTURE_SETUP(ethtool) +{ + self->ys = ynl_sock_create(&ynl_ethtool_family, NULL); + ASSERT_NE(NULL, self->ys) + TH_LOG("failed to create ethtool socket"); +} + +FIXTURE_TEARDOWN(ethtool) +{ + ynl_sock_destroy(self->ys); +} + +TEST_F(ethtool, channels) +{ + struct ethtool_channels_get_req_dump creq = {}; + struct ethtool_channels_get_list *channels; + + creq._present.header = 1; /* ethtool needs an empty nest */ + channels = ethtool_channels_get_dump(self->ys, &creq); + ASSERT_NE(NULL, channels) { + TH_LOG("channels dump failed: %s", self->ys->err.msg); + } + + if (ynl_dump_empty(channels)) { + ethtool_channels_get_list_free(channels); + SKIP(return, "no entries in channels dump"); + } + + ynl_dump_foreach(channels, dev) { + EXPECT_TRUE((bool)dev->header._len.dev_name); + ksft_print_msg("%8s: ", dev->header.dev_name); + EXPECT_TRUE(dev->_present.rx_count || + dev->_present.tx_count || + dev->_present.combined_count); + if (dev->_present.rx_count) + printf("rx %d ", dev->rx_count); + if (dev->_present.tx_count) + printf("tx %d ", dev->tx_count); + if (dev->_present.combined_count) + printf("combined %d ", dev->combined_count); + printf("\n"); + } + ethtool_channels_get_list_free(channels); +} + +TEST_F(ethtool, rings) +{ + struct ethtool_rings_get_req_dump rreq = {}; + struct ethtool_rings_get_list *rings; + + rreq._present.header = 1; /* ethtool needs an empty nest */ + rings = ethtool_rings_get_dump(self->ys, &rreq); + ASSERT_NE(NULL, rings) { + TH_LOG("rings dump failed: %s", self->ys->err.msg); + } + + if (ynl_dump_empty(rings)) { + ethtool_rings_get_list_free(rings); + SKIP(return, "no entries in rings dump"); + } + + ynl_dump_foreach(rings, dev) { + EXPECT_TRUE((bool)dev->header._len.dev_name); + ksft_print_msg("%8s: ", dev->header.dev_name); + EXPECT_TRUE(dev->_present.rx || dev->_present.tx); + if (dev->_present.rx) + printf("rx %d ", dev->rx); + if (dev->_present.tx) + printf("tx %d ", dev->tx); + printf("\n"); + } + ethtool_rings_get_list_free(rings); +} + +TEST_HARNESS_MAIN diff --git a/tools/net/ynl/tests/ethtool.sh b/tools/net/ynl/tests/ethtool.sh new file mode 100755 index 000000000000..0859ddd697e8 --- /dev/null +++ b/tools/net/ynl/tests/ethtool.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +source "$(dirname "$(realpath "$0")")/ynl_nsim_lib.sh" +nsim_setup +"$(dirname "$(realpath "$0")")/ethtool" diff --git a/tools/net/ynl/tests/netdev.c b/tools/net/ynl/tests/netdev.c new file mode 100644 index 000000000000..f849e3d7f4b3 --- /dev/null +++ b/tools/net/ynl/tests/netdev.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> + +#include <ynl.h> + +#include <net/if.h> + +#include <kselftest_harness.h> + +#include "netdev-user.h" +#include "rt-link-user.h" + +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) + 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; + + printf("xdp-features (%llx):", d->xdp_features); + for (int i = 0; d->xdp_features >= 1U << i; i++) { + if (d->xdp_features & (1U << i)) + printf(" %s", netdev_xdp_act_str(1 << i)); + } + + 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(" xsk-features (%llx):", d->xsk_features); + for (int i = 0; d->xsk_features >= 1U << i; i++) { + if (d->xsk_features & (1U << i)) + printf(" %s", netdev_xsk_flags_str(1 << i)); + } + + printf(" xdp-zc-max-segs=%u", d->xdp_zc_max_segs); + + name = netdev_op_str(op); + if (name) + printf(" (ntf: %s)", name); + printf("\n"); +} + +static int veth_create(struct ynl_sock *ys_link) +{ + struct rt_link_getlink_ntf *ntf_gl; + struct rt_link_newlink_req *req; + struct ynl_ntf_base_type *ntf; + 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; + struct ynl_sock *ys_link; +}; + +FIXTURE_SETUP(netdev) +{ + struct ynl_error yerr; + + 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); + } +} + +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; + + devs = netdev_dev_get_dump(self->ys); + ASSERT_NE(NULL, devs) { + TH_LOG("dump failed: %s", self->ys->err.msg); + } + + if (ynl_dump_empty(devs)) { + netdev_dev_get_list_free(devs); + SKIP(return, "no entries in dump"); + } + + 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); + } + + /* Drain any remaining notifications */ + while ((ntf = ynl_ntf_dequeue(self->ys))) + ynl_ntf_free(ntf); + + veth_delete(_metadata, self->ys_link, veth_ifindex); + + ASSERT_TRUE(received) + TH_LOG("no notification received"); +} + +TEST_HARNESS_MAIN diff --git a/tools/net/ynl/tests/ovs.c b/tools/net/ynl/tests/ovs.c new file mode 100644 index 000000000000..d49f5a8e647e --- /dev/null +++ b/tools/net/ynl/tests/ovs.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> + +#include <ynl.h> + +#include <kselftest_harness.h> + +#include "ovs_datapath-user.h" + +static void ovs_print_datapath(struct __test_metadata *_metadata, + struct ovs_datapath_get_rsp *dp) +{ + EXPECT_TRUE((bool)dp->_len.name); + if (!dp->_len.name) + return; + + EXPECT_TRUE((bool)dp->_hdr.dp_ifindex); + ksft_print_msg("%s(%d): pid:%u cache:%u\n", + dp->name, dp->_hdr.dp_ifindex, + dp->upcall_pid, dp->masks_cache_size); +} + +FIXTURE(ovs) +{ + struct ynl_sock *ys; + char *dp_name; +}; + +FIXTURE_SETUP(ovs) +{ + self->ys = ynl_sock_create(&ynl_ovs_datapath_family, NULL); + ASSERT_NE(NULL, self->ys) + TH_LOG("failed to create OVS datapath socket"); +} + +FIXTURE_TEARDOWN(ovs) +{ + if (self->dp_name) { + struct ovs_datapath_del_req *req; + + req = ovs_datapath_del_req_alloc(); + if (req) { + ovs_datapath_del_req_set_name(req, self->dp_name); + ovs_datapath_del(self->ys, req); + ovs_datapath_del_req_free(req); + } + } + ynl_sock_destroy(self->ys); +} + +TEST_F(ovs, crud) +{ + struct ovs_datapath_get_req_dump *dreq; + struct ovs_datapath_new_req *new_req; + struct ovs_datapath_get_list *dps; + struct ovs_datapath_get_rsp *dp; + struct ovs_datapath_get_req *req; + bool found = false; + int err; + + new_req = ovs_datapath_new_req_alloc(); + ASSERT_NE(NULL, new_req); + ovs_datapath_new_req_set_upcall_pid(new_req, 1); + ovs_datapath_new_req_set_name(new_req, "ynl-test"); + + err = ovs_datapath_new(self->ys, new_req); + ovs_datapath_new_req_free(new_req); + ASSERT_EQ(0, err) { + TH_LOG("new failed: %s", self->ys->err.msg); + } + self->dp_name = "ynl-test"; + + ksft_print_msg("get:\n"); + req = ovs_datapath_get_req_alloc(); + ASSERT_NE(NULL, req); + ovs_datapath_get_req_set_name(req, "ynl-test"); + + dp = ovs_datapath_get(self->ys, req); + ovs_datapath_get_req_free(req); + ASSERT_NE(NULL, dp) { + TH_LOG("get failed: %s", self->ys->err.msg); + } + + ovs_print_datapath(_metadata, dp); + EXPECT_STREQ("ynl-test", dp->name); + ovs_datapath_get_rsp_free(dp); + + ksft_print_msg("dump:\n"); + dreq = ovs_datapath_get_req_dump_alloc(); + ASSERT_NE(NULL, dreq); + + dps = ovs_datapath_get_dump(self->ys, dreq); + ovs_datapath_get_req_dump_free(dreq); + ASSERT_NE(NULL, dps) { + TH_LOG("dump failed: %s", self->ys->err.msg); + } + + ynl_dump_foreach(dps, d) { + ovs_print_datapath(_metadata, d); + if (d->name && !strcmp(d->name, "ynl-test")) + found = true; + } + ovs_datapath_get_list_free(dps); + EXPECT_TRUE(found); +} + +TEST_HARNESS_MAIN diff --git a/tools/net/ynl/tests/rt-addr.c b/tools/net/ynl/tests/rt-addr.c new file mode 100644 index 000000000000..f6c3715b2f20 --- /dev/null +++ b/tools/net/ynl/tests/rt-addr.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> + +#include <ynl.h> + +#include <arpa/inet.h> +#include <net/if.h> + +#include <kselftest_harness.h> + +#include "rt-addr-user.h" + +static void rt_addr_print(struct __test_metadata *_metadata, + struct rt_addr_getaddr_rsp *a) +{ + char ifname[IF_NAMESIZE]; + char addr_str[64]; + const char *addr; + const char *name; + + name = if_indextoname(a->_hdr.ifa_index, ifname); + EXPECT_NE(NULL, name); + if (name) + ksft_print_msg("%16s: ", name); + + EXPECT_TRUE(a->_len.address == 4 || a->_len.address == 16); + switch (a->_len.address) { + case 4: + addr = inet_ntop(AF_INET, a->address, + addr_str, sizeof(addr_str)); + break; + case 16: + addr = inet_ntop(AF_INET6, a->address, + addr_str, sizeof(addr_str)); + break; + default: + addr = NULL; + break; + } + if (addr) + printf("%s", addr); + else + printf("[%d]", a->_len.address); + + printf("\n"); +} + +FIXTURE(rt_addr) +{ + struct ynl_sock *ys; +}; + +FIXTURE_SETUP(rt_addr) +{ + struct ynl_error yerr; + + self->ys = ynl_sock_create(&ynl_rt_addr_family, &yerr); + ASSERT_NE(NULL, self->ys) + TH_LOG("failed to create rt-addr socket: %s", yerr.msg); +} + +FIXTURE_TEARDOWN(rt_addr) +{ + ynl_sock_destroy(self->ys); +} + +TEST_F(rt_addr, dump) +{ + struct rt_addr_getaddr_list *rsp; + struct rt_addr_getaddr_req *req; + struct in6_addr v6_expected; + struct in_addr v4_expected; + bool found_v4 = false; + bool found_v6 = false; + + /* The bash wrapper for this test adds these addresses on nsim0, + * make sure we can find them in the dump. + */ + inet_pton(AF_INET, "192.168.1.1", &v4_expected); + inet_pton(AF_INET6, "2001:db8::1", &v6_expected); + + req = rt_addr_getaddr_req_alloc(); + ASSERT_NE(NULL, req); + + rsp = rt_addr_getaddr_dump(self->ys, req); + rt_addr_getaddr_req_free(req); + ASSERT_NE(NULL, rsp) { + TH_LOG("dump failed: %s", self->ys->err.msg); + } + + ASSERT_FALSE(ynl_dump_empty(rsp)) { + rt_addr_getaddr_list_free(rsp); + TH_LOG("no addresses reported"); + } + + ynl_dump_foreach(rsp, addr) { + rt_addr_print(_metadata, addr); + + found_v4 |= addr->_len.address == 4 && + !memcmp(addr->address, &v4_expected, 4); + found_v6 |= addr->_len.address == 16 && + !memcmp(addr->address, &v6_expected, 16); + } + rt_addr_getaddr_list_free(rsp); + + EXPECT_TRUE(found_v4); + EXPECT_TRUE(found_v6); +} + +TEST_HARNESS_MAIN diff --git a/tools/net/ynl/tests/rt-addr.sh b/tools/net/ynl/tests/rt-addr.sh new file mode 100755 index 000000000000..87661236d126 --- /dev/null +++ b/tools/net/ynl/tests/rt-addr.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +source "$(dirname "$(realpath "$0")")/ynl_nsim_lib.sh" +nsim_setup +"$(dirname "$(realpath "$0")")/rt-addr" diff --git a/tools/net/ynl/tests/rt-link.c b/tools/net/ynl/tests/rt-link.c new file mode 100644 index 000000000000..ef619ce6143f --- /dev/null +++ b/tools/net/ynl/tests/rt-link.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> + +#include <ynl.h> + +#include <arpa/inet.h> +#include <net/if.h> + +#include <kselftest_harness.h> + +#include "rt-link-user.h" + +static void rt_link_print(struct __test_metadata *_metadata, + struct rt_link_getlink_rsp *r) +{ + unsigned int i; + + EXPECT_TRUE((bool)r->_hdr.ifi_index); + ksft_print_msg("%3d: ", r->_hdr.ifi_index); + + EXPECT_TRUE((bool)r->_len.ifname); + if (r->_len.ifname) + printf("%6s: ", r->ifname); + + if (r->_present.mtu) + printf("mtu %5d ", r->mtu); + + if (r->linkinfo._len.kind) + printf("kind %-8s ", r->linkinfo.kind); + else + printf(" %8s ", ""); + + if (r->prop_list._count.alt_ifname) { + printf("altname "); + for (i = 0; i < r->prop_list._count.alt_ifname; i++) + printf("%s ", r->prop_list.alt_ifname[i]->str); + printf(" "); + } + + if (r->linkinfo._present.data && r->linkinfo.data._present.netkit) { + struct rt_link_linkinfo_netkit_attrs *netkit; + const char *name; + + netkit = &r->linkinfo.data.netkit; + printf("primary %d ", netkit->primary); + + name = NULL; + if (netkit->_present.policy) + name = rt_link_netkit_policy_str(netkit->policy); + if (name) + printf("policy %s ", name); + } + + printf("\n"); +} + +static int netkit_create(struct ynl_sock *ys) +{ + struct rt_link_getlink_ntf *ntf_gl; + struct rt_link_newlink_req *req; + struct ynl_ntf_base_type *ntf; + 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, "netkit"); + rt_link_newlink_req_set_linkinfo_data_netkit_policy(req, NETKIT_DROP); + + ret = rt_link_newlink(ys, req); + rt_link_newlink_req_free(req); + if (ret) + return -1; + + if (!ynl_has_ntf(ys)) + return 0; + + ntf = ynl_ntf_dequeue(ys); + 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 netkit_delete(struct __test_metadata *_metadata, + struct ynl_sock *ys, 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, req)); + rt_link_dellink_req_free(req); +} + +FIXTURE(rt_link) +{ + struct ynl_sock *ys; +}; + +FIXTURE_SETUP(rt_link) +{ + struct ynl_error yerr; + + self->ys = ynl_sock_create(&ynl_rt_link_family, &yerr); + ASSERT_NE(NULL, self->ys) { + TH_LOG("failed to create rt-link socket: %s", yerr.msg); + } +} + +FIXTURE_TEARDOWN(rt_link) +{ + ynl_sock_destroy(self->ys); +} + +TEST_F(rt_link, dump) +{ + struct rt_link_getlink_req_dump *req; + struct rt_link_getlink_list *rsp; + + req = rt_link_getlink_req_dump_alloc(); + ASSERT_NE(NULL, req); + rsp = rt_link_getlink_dump(self->ys, req); + rt_link_getlink_req_dump_free(req); + ASSERT_NE(NULL, rsp) { + TH_LOG("dump failed: %s", self->ys->err.msg); + } + ASSERT_FALSE(ynl_dump_empty(rsp)); + + ynl_dump_foreach(rsp, link) + rt_link_print(_metadata, link); + + rt_link_getlink_list_free(rsp); +} + +TEST_F(rt_link, netkit) +{ + struct rt_link_getlink_req_dump *dreq; + struct rt_link_getlink_list *rsp; + bool found = false; + int netkit_ifindex; + + /* Create netkit with valid policy */ + netkit_ifindex = netkit_create(self->ys); + ASSERT_GT(netkit_ifindex, 0) + TH_LOG("failed to create netkit: %s", self->ys->err.msg); + + /* Verify it appears in a dump */ + dreq = rt_link_getlink_req_dump_alloc(); + ASSERT_NE(NULL, dreq); + rsp = rt_link_getlink_dump(self->ys, dreq); + rt_link_getlink_req_dump_free(dreq); + ASSERT_NE(NULL, rsp) { + TH_LOG("dump failed: %s", self->ys->err.msg); + } + + ynl_dump_foreach(rsp, link) { + if (link->_hdr.ifi_index == netkit_ifindex) { + rt_link_print(_metadata, link); + found = true; + } + } + rt_link_getlink_list_free(rsp); + EXPECT_TRUE(found); + + netkit_delete(_metadata, self->ys, netkit_ifindex); +} + +TEST_F(rt_link, netkit_err_msg) +{ + struct rt_link_newlink_req *req; + int ret; + + /* Test creating netkit with bad policy - should fail */ + req = rt_link_newlink_req_alloc(); + ASSERT_NE(NULL, req); + rt_link_newlink_req_set_nlflags(req, NLM_F_CREATE); + rt_link_newlink_req_set_linkinfo_kind(req, "netkit"); + rt_link_newlink_req_set_linkinfo_data_netkit_policy(req, 10); + + ret = rt_link_newlink(self->ys, req); + rt_link_newlink_req_free(req); + EXPECT_NE(0, ret) { + TH_LOG("creating netkit with bad policy should fail"); + } + + /* Expect: + * Kernel error: 'Provided default xmit policy not supported' (bad attribute: .linkinfo.data(netkit).policy) + */ + EXPECT_NE(NULL, strstr(self->ys->err.msg, "bad attribute: .linkinfo.data(netkit).policy")) { + TH_LOG("expected extack msg not found: %s", + self->ys->err.msg); + } +} + +TEST_HARNESS_MAIN diff --git a/tools/net/ynl/tests/rt-route.c b/tools/net/ynl/tests/rt-route.c new file mode 100644 index 000000000000..c9fd2bc48144 --- /dev/null +++ b/tools/net/ynl/tests/rt-route.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> + +#include <ynl.h> + +#include <arpa/inet.h> +#include <net/if.h> + +#include <kselftest_harness.h> + +#include "rt-route-user.h" + +static void rt_route_print(struct __test_metadata *_metadata, + struct rt_route_getroute_rsp *r) +{ + char ifname[IF_NAMESIZE]; + char route_str[64]; + const char *route; + const char *name; + + /* Ignore local */ + if (r->_hdr.rtm_table == RT_TABLE_LOCAL) + return; + + if (r->_present.oif) { + name = if_indextoname(r->oif, ifname); + EXPECT_NE(NULL, name); + if (name) + ksft_print_msg("oif: %-16s ", name); + } + + if (r->_len.dst) { + route = inet_ntop(r->_hdr.rtm_family, r->dst, + route_str, sizeof(route_str)); + printf("dst: %s/%d", route, r->_hdr.rtm_dst_len); + } + + if (r->_len.gateway) { + route = inet_ntop(r->_hdr.rtm_family, r->gateway, + route_str, sizeof(route_str)); + printf("gateway: %s ", route); + } + + printf("\n"); +} + +FIXTURE(rt_route) +{ + struct ynl_sock *ys; +}; + +FIXTURE_SETUP(rt_route) +{ + struct ynl_error yerr; + + self->ys = ynl_sock_create(&ynl_rt_route_family, &yerr); + ASSERT_NE(NULL, self->ys) + TH_LOG("failed to create rt-route socket: %s", yerr.msg); +} + +FIXTURE_TEARDOWN(rt_route) +{ + ynl_sock_destroy(self->ys); +} + +TEST_F(rt_route, dump) +{ + struct rt_route_getroute_req_dump *req; + struct rt_route_getroute_list *rsp; + struct in6_addr v6_expected; + struct in_addr v4_expected; + bool found_v4 = false; + bool found_v6 = false; + + /* The bash wrapper configures 192.168.1.1/24 and 2001:db8::1/64, + * make sure we can find the connected routes in the dump. + */ + inet_pton(AF_INET, "192.168.1.0", &v4_expected); + inet_pton(AF_INET6, "2001:db8::", &v6_expected); + + req = rt_route_getroute_req_dump_alloc(); + ASSERT_NE(NULL, req); + + rsp = rt_route_getroute_dump(self->ys, req); + rt_route_getroute_req_dump_free(req); + ASSERT_NE(NULL, rsp) { + TH_LOG("dump failed: %s", self->ys->err.msg); + } + + ASSERT_FALSE(ynl_dump_empty(rsp)) { + rt_route_getroute_list_free(rsp); + TH_LOG("no routes reported"); + } + + ynl_dump_foreach(rsp, route) { + rt_route_print(_metadata, route); + + if (route->_hdr.rtm_table == RT_TABLE_LOCAL) + continue; + + if (route->_len.dst == 4 && route->_hdr.rtm_dst_len == 24) + found_v4 |= !memcmp(route->dst, &v4_expected, 4); + if (route->_len.dst == 16 && route->_hdr.rtm_dst_len == 64) + found_v6 |= !memcmp(route->dst, &v6_expected, 16); + } + rt_route_getroute_list_free(rsp); + + EXPECT_TRUE(found_v4); + EXPECT_TRUE(found_v6); +} + +TEST_HARNESS_MAIN diff --git a/tools/net/ynl/tests/rt-route.sh b/tools/net/ynl/tests/rt-route.sh new file mode 100755 index 000000000000..020338f0a238 --- /dev/null +++ b/tools/net/ynl/tests/rt-route.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +source "$(dirname "$(realpath "$0")")/ynl_nsim_lib.sh" +nsim_setup +"$(dirname "$(realpath "$0")")/rt-route" diff --git a/tools/net/ynl/tests/tc.c b/tools/net/ynl/tests/tc.c new file mode 100644 index 000000000000..6ff13876578d --- /dev/null +++ b/tools/net/ynl/tests/tc.c @@ -0,0 +1,409 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <sched.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <linux/pkt_sched.h> +#include <linux/tc_act/tc_vlan.h> +#include <linux/tc_act/tc_gact.h> +#include <linux/if_ether.h> +#include <net/if.h> + +#include <ynl.h> + +#include <kselftest_harness.h> + +#include "tc-user.h" + +#define TC_HANDLE (0xFFFF << 16) + +static bool tc_qdisc_print(struct __test_metadata *_metadata, + struct tc_getqdisc_rsp *q) +{ + bool was_fq_codel = false; + char ifname[IF_NAMESIZE]; + const char *name; + + name = if_indextoname(q->_hdr.tcm_ifindex, ifname); + EXPECT_TRUE((bool)name); + ksft_print_msg("%16s: ", name ?: "no-name"); + + if (q->_len.kind) { + printf("%s ", q->kind); + + if (q->options._present.fq_codel) { + struct tc_fq_codel_attrs *fq_codel; + struct tc_fq_codel_xstats *stats; + + fq_codel = &q->options.fq_codel; + stats = q->stats2.app.fq_codel; + + EXPECT_EQ(true, + fq_codel->_present.limit && + fq_codel->_present.target && + q->stats2.app._len.fq_codel); + + if (fq_codel->_present.limit) + printf("limit: %dp ", fq_codel->limit); + if (fq_codel->_present.target) + printf("target: %dms ", + (fq_codel->target + 500) / 1000); + if (q->stats2.app._len.fq_codel) + printf("new_flow_cnt: %d ", + stats->qdisc_stats.new_flow_count); + was_fq_codel = true; + } + } + printf("\n"); + + return was_fq_codel; +} + +static const char *vlan_act_name(struct tc_vlan *p) +{ + switch (p->v_action) { + case TCA_VLAN_ACT_POP: + return "pop"; + case TCA_VLAN_ACT_PUSH: + return "push"; + case TCA_VLAN_ACT_MODIFY: + return "modify"; + default: + break; + } + + return "not supported"; +} + +static const char *gact_act_name(struct tc_gact *p) +{ + switch (p->action) { + case TC_ACT_SHOT: + return "drop"; + case TC_ACT_OK: + return "ok"; + case TC_ACT_PIPE: + return "pipe"; + default: + break; + } + + return "not supported"; +} + +static void print_vlan(struct tc_act_vlan_attrs *vlan) +{ + printf("%s ", vlan_act_name(vlan->parms)); + if (vlan->_present.push_vlan_id) + printf("id %u ", vlan->push_vlan_id); + if (vlan->_present.push_vlan_protocol) + printf("protocol %#x ", ntohs(vlan->push_vlan_protocol)); + if (vlan->_present.push_vlan_priority) + printf("priority %u ", vlan->push_vlan_priority); +} + +static void print_gact(struct tc_act_gact_attrs *gact) +{ + struct tc_gact *p = gact->parms; + + printf("%s ", gact_act_name(p)); +} + +static void flower_print(struct tc_flower_attrs *flower, const char *kind) +{ + struct tc_act_attrs *a; + unsigned int i; + + ksft_print_msg("%s:\n", kind); + + if (flower->_present.key_vlan_id) + ksft_print_msg(" vlan_id: %u\n", flower->key_vlan_id); + if (flower->_present.key_vlan_prio) + ksft_print_msg(" vlan_prio: %u\n", flower->key_vlan_prio); + if (flower->_present.key_num_of_vlans) + ksft_print_msg(" num_of_vlans: %u\n", + flower->key_num_of_vlans); + + for (i = 0; i < flower->_count.act; i++) { + a = &flower->act[i]; + ksft_print_msg("action order: %i %s ", i + 1, a->kind); + if (a->options._present.vlan) + print_vlan(&a->options.vlan); + else if (a->options._present.gact) + print_gact(&a->options.gact); + printf("\n"); + } +} + +static void tc_filter_print(struct __test_metadata *_metadata, + struct tc_gettfilter_rsp *f) +{ + struct tc_options_msg *opt = &f->options; + + if (opt->_present.flower) { + EXPECT_TRUE((bool)f->_len.kind); + flower_print(&opt->flower, f->kind); + } else if (f->_len.kind) { + ksft_print_msg("%s pref %u proto: %#x\n", f->kind, + (f->_hdr.tcm_info >> 16), + ntohs(TC_H_MIN(f->_hdr.tcm_info))); + } +} + +static int tc_clsact_add(struct ynl_sock *ys, int ifi) +{ + struct tc_newqdisc_req *req; + int ret; + + req = tc_newqdisc_req_alloc(); + if (!req) + return -1; + memset(req, 0, sizeof(*req)); + + req->_hdr.tcm_ifindex = ifi; + req->_hdr.tcm_parent = TC_H_CLSACT; + req->_hdr.tcm_handle = TC_HANDLE; + tc_newqdisc_req_set_nlflags(req, + NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE); + tc_newqdisc_req_set_kind(req, "clsact"); + + ret = tc_newqdisc(ys, req); + tc_newqdisc_req_free(req); + + return ret; +} + +static int tc_clsact_del(struct ynl_sock *ys, int ifi) +{ + struct tc_delqdisc_req *req; + int ret; + + req = tc_delqdisc_req_alloc(); + if (!req) + return -1; + memset(req, 0, sizeof(*req)); + + req->_hdr.tcm_ifindex = ifi; + req->_hdr.tcm_parent = TC_H_CLSACT; + req->_hdr.tcm_handle = TC_HANDLE; + tc_delqdisc_req_set_nlflags(req, NLM_F_REQUEST); + + ret = tc_delqdisc(ys, req); + tc_delqdisc_req_free(req); + + return ret; +} + +static int tc_filter_add(struct ynl_sock *ys, int ifi) +{ + struct tc_newtfilter_req *req; + struct tc_act_attrs *acts; + struct tc_vlan p = { + .action = TC_ACT_PIPE, + .v_action = TCA_VLAN_ACT_PUSH + }; + int ret; + + req = tc_newtfilter_req_alloc(); + if (!req) + return -1; + memset(req, 0, sizeof(*req)); + + acts = tc_act_attrs_alloc(3); + if (!acts) { + tc_newtfilter_req_free(req); + return -1; + } + memset(acts, 0, sizeof(*acts) * 3); + + req->_hdr.tcm_ifindex = ifi; + req->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS); + req->_hdr.tcm_info = TC_H_MAKE(1 << 16, htons(ETH_P_8021Q)); + req->chain = 0; + + tc_newtfilter_req_set_nlflags(req, NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE); + tc_newtfilter_req_set_kind(req, "flower"); + tc_newtfilter_req_set_options_flower_key_vlan_id(req, 100); + tc_newtfilter_req_set_options_flower_key_vlan_prio(req, 5); + tc_newtfilter_req_set_options_flower_key_num_of_vlans(req, 3); + + __tc_newtfilter_req_set_options_flower_act(req, acts, 3); + + /* Skip action at index 0 because in TC, the action array + * index starts at 1, with each index defining the action's + * order. In contrast, in YNL indexed arrays start at index 0. + */ + tc_act_attrs_set_kind(&acts[1], "vlan"); + tc_act_attrs_set_options_vlan_parms(&acts[1], &p, sizeof(p)); + tc_act_attrs_set_options_vlan_push_vlan_id(&acts[1], 200); + tc_act_attrs_set_kind(&acts[2], "vlan"); + tc_act_attrs_set_options_vlan_parms(&acts[2], &p, sizeof(p)); + tc_act_attrs_set_options_vlan_push_vlan_id(&acts[2], 300); + + tc_newtfilter_req_set_options_flower_flags(req, 0); + tc_newtfilter_req_set_options_flower_key_eth_type(req, htons(0x8100)); + + ret = tc_newtfilter(ys, req); + tc_newtfilter_req_free(req); + + return ret; +} + +static int tc_filter_del(struct ynl_sock *ys, int ifi) +{ + struct tc_deltfilter_req *req; + int ret; + + req = tc_deltfilter_req_alloc(); + if (!req) + return -1; + memset(req, 0, sizeof(*req)); + + req->_hdr.tcm_ifindex = ifi; + req->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS); + req->_hdr.tcm_info = TC_H_MAKE(1 << 16, htons(ETH_P_8021Q)); + tc_deltfilter_req_set_nlflags(req, NLM_F_REQUEST); + + ret = tc_deltfilter(ys, req); + tc_deltfilter_req_free(req); + + return ret; +} + +FIXTURE(tc) +{ + struct ynl_sock *ys; + int ifindex; +}; + +FIXTURE_SETUP(tc) +{ + struct ynl_error yerr; + int ret; + + ret = unshare(CLONE_NEWNET); + ASSERT_EQ(0, ret); + + self->ifindex = 1; /* loopback */ + + self->ys = ynl_sock_create(&ynl_tc_family, &yerr); + ASSERT_NE(NULL, self->ys) { + TH_LOG("failed to create tc socket: %s", yerr.msg); + } +} + +FIXTURE_TEARDOWN(tc) +{ + ynl_sock_destroy(self->ys); +} + +TEST_F(tc, qdisc) +{ + struct tc_getqdisc_req_dump *dreq; + struct tc_newqdisc_req *add_req; + struct tc_delqdisc_req *del_req; + struct tc_getqdisc_list *rsp; + bool found = false; + int ret; + + add_req = tc_newqdisc_req_alloc(); + ASSERT_NE(NULL, add_req); + memset(add_req, 0, sizeof(*add_req)); + + add_req->_hdr.tcm_ifindex = self->ifindex; + add_req->_hdr.tcm_parent = TC_H_ROOT; + tc_newqdisc_req_set_nlflags(add_req, + NLM_F_REQUEST | NLM_F_CREATE); + tc_newqdisc_req_set_kind(add_req, "fq_codel"); + + ret = tc_newqdisc(self->ys, add_req); + tc_newqdisc_req_free(add_req); + ASSERT_EQ(0, ret) { + TH_LOG("qdisc add failed: %s", self->ys->err.msg); + } + + dreq = tc_getqdisc_req_dump_alloc(); + ASSERT_NE(NULL, dreq); + rsp = tc_getqdisc_dump(self->ys, dreq); + tc_getqdisc_req_dump_free(dreq); + ASSERT_NE(NULL, rsp) { + TH_LOG("dump failed: %s", self->ys->err.msg); + } + ASSERT_FALSE(ynl_dump_empty(rsp)); + + ynl_dump_foreach(rsp, qdisc) { + found |= tc_qdisc_print(_metadata, qdisc); + } + tc_getqdisc_list_free(rsp); + EXPECT_TRUE(found); + + del_req = tc_delqdisc_req_alloc(); + ASSERT_NE(NULL, del_req); + memset(del_req, 0, sizeof(*del_req)); + + del_req->_hdr.tcm_ifindex = self->ifindex; + del_req->_hdr.tcm_parent = TC_H_ROOT; + tc_delqdisc_req_set_nlflags(del_req, NLM_F_REQUEST); + + ret = tc_delqdisc(self->ys, del_req); + tc_delqdisc_req_free(del_req); + EXPECT_EQ(0, ret) { + TH_LOG("qdisc del failed: %s", self->ys->err.msg); + } +} + +TEST_F(tc, flower) +{ + struct tc_gettfilter_req_dump *dreq; + struct tc_gettfilter_list *rsp; + bool found = false; + int ret; + + ret = tc_clsact_add(self->ys, self->ifindex); + if (ret) + SKIP(return, "clsact not supported: %s", self->ys->err.msg); + + ret = tc_filter_add(self->ys, self->ifindex); + ASSERT_EQ(0, ret) { + TH_LOG("filter add failed: %s", self->ys->err.msg); + } + + dreq = tc_gettfilter_req_dump_alloc(); + ASSERT_NE(NULL, dreq); + memset(dreq, 0, sizeof(*dreq)); + dreq->_hdr.tcm_ifindex = self->ifindex; + dreq->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS); + dreq->_present.chain = 1; + dreq->chain = 0; + + rsp = tc_gettfilter_dump(self->ys, dreq); + tc_gettfilter_req_dump_free(dreq); + ASSERT_NE(NULL, rsp) { + TH_LOG("filter dump failed: %s", self->ys->err.msg); + } + + ynl_dump_foreach(rsp, flt) { + tc_filter_print(_metadata, flt); + if (flt->options._present.flower) { + EXPECT_EQ(100, flt->options.flower.key_vlan_id); + EXPECT_EQ(5, flt->options.flower.key_vlan_prio); + found = true; + } + } + tc_gettfilter_list_free(rsp); + EXPECT_TRUE(found); + + ret = tc_filter_del(self->ys, self->ifindex); + EXPECT_EQ(0, ret) { + TH_LOG("filter del failed: %s", self->ys->err.msg); + } + + ret = tc_clsact_del(self->ys, self->ifindex); + EXPECT_EQ(0, ret) { + TH_LOG("clsact del failed: %s", self->ys->err.msg); + } +} + +TEST_HARNESS_MAIN diff --git a/tools/net/ynl/tests/ynl_nsim_lib.sh b/tools/net/ynl/tests/ynl_nsim_lib.sh new file mode 100644 index 000000000000..98cdce44a69c --- /dev/null +++ b/tools/net/ynl/tests/ynl_nsim_lib.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Shared netdevsim setup/cleanup for YNL C test wrappers + +NSIM_ID="1337" +NSIM_DEV="" +KSFT_SKIP=4 + +nsim_cleanup() { + echo "$NSIM_ID" > /sys/bus/netdevsim/del_device 2>/dev/null || true +} + +nsim_setup() { + modprobe netdevsim 2>/dev/null + if ! [ -f /sys/bus/netdevsim/new_device ]; then + echo "netdevsim module not available, skipping" >&2 + exit "$KSFT_SKIP" + fi + + trap nsim_cleanup EXIT + + echo "$NSIM_ID 1" > /sys/bus/netdevsim/new_device + udevadm settle + + NSIM_DEV=$(ls /sys/bus/netdevsim/devices/netdevsim${NSIM_ID}/net 2>/dev/null | head -1) + if [ -z "$NSIM_DEV" ]; then + echo "failed to find netdevsim device" >&2 + exit 1 + fi + + ip link set dev "$NSIM_DEV" name nsim0 + ip link set dev nsim0 up + ip addr add 192.168.1.1/24 dev nsim0 + ip addr add 2001:db8::1/64 dev nsim0 nodad +} |
