]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
tools: ynl: convert tc and tc-filter-add samples to selftest
authorJakub Kicinski <kuba@kernel.org>
Sat, 7 Mar 2026 03:36:25 +0000 (19:36 -0800)
committerJakub Kicinski <kuba@kernel.org>
Tue, 10 Mar 2026 00:02:26 +0000 (17:02 -0700)
Convert tc.c and tc-filter-add.c to produce KTAP output with
kselftest_harness. Merge the two tests together. They both
test TC one is testing qdisc and the other classifiers but
they can easily live in a single selftest.

Make the test spawn a new netns, and run the operations on
lo to avoid onerous setup and cleanup.

  TAP version 13
  1..2
  # Starting 2 tests from 1 test cases.
  #  RUN           tc.qdisc ...
  #               lo: fq_codel  limit: 10240p target: 5ms new_flow_cnt: 0
  #            OK  tc.qdisc
  ok 1 tc.qdisc
  #  RUN           tc.flower ...
  # flower pref 1 proto: 0x8100
  # flower:
  #   vlan_id: 100
  #   vlan_prio: 5
  #   num_of_vlans: 3
  # action order: 1 vlan push id 200 protocol 0x8100 priority 0
  # action order: 2 vlan push id 300 protocol 0x8100 priority 0
  #            OK  tc.flower
  ok 2 tc.flower
  # PASSED: 2 / 2 tests passed.
  # Totals: pass:2 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-6-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
tools/net/ynl/tests/Makefile
tools/net/ynl/tests/config
tools/net/ynl/tests/tc-filter-add.c [deleted file]
tools/net/ynl/tests/tc.c

index 08d1146d91ce356898e11191fdef2bc7746ba334..524092a8de7ee277f720b62eb63cf1fe2b58b1fb 100644 (file)
@@ -22,6 +22,7 @@ TEST_GEN_PROGS := \
        netdev \
        ovs \
        rt-link \
+       tc \
 # end of TEST_GEN_PROGS
 
 BINS := \
@@ -29,13 +30,10 @@ BINS := \
        ethtool \
        rt-addr \
        rt-route \
-       tc \
-       tc-filter-add \
 # end of BINS
 
 CFLAGS_netdev:=$(CFLAGS_netdev) $(CFLAGS_rt-link)
 CFLAGS_ovs:=$(CFLAGS_ovs_datapath)
-CFLAGS_tc-filter-add:=$(CFLAGS_tc)
 
 include $(wildcard *.d)
 
index b4c58d86a6c26d8f8c02415bd1d4fe1971ff1eef..75c0fe72391fed4bd13944244619cb04a1f3fbbc 100644 (file)
@@ -1,7 +1,13 @@
 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
diff --git a/tools/net/ynl/tests/tc-filter-add.c b/tools/net/ynl/tests/tc-filter-add.c
deleted file mode 100644 (file)
index 97871e9..0000000
+++ /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;
-}
index 0bfff0fdd792e59e6e5b7bac28512214aed8747e..6ff13876578d4b156d3bc92d3dd64602651663e9 100644 (file)
@@ -1,21 +1,34 @@
 // 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 <net/if.h>
+#include <kselftest_harness.h>
 
 #include "tc-user.h"
 
-static void tc_qdisc_print(struct tc_getqdisc_rsp *q)
+#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);
-       if (name)
-               printf("%16s: ", name);
+       EXPECT_TRUE((bool)name);
+       ksft_print_msg("%16s: ", name ?: "no-name");
 
        if (q->_len.kind) {
                printf("%s  ", q->kind);
@@ -27,6 +40,11 @@ static void tc_qdisc_print(struct tc_getqdisc_rsp *q)
                        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)
@@ -35,46 +53,357 @@ static void tc_qdisc_print(struct tc_getqdisc_rsp *q)
                        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;
 }
 
-int main(int argc, char **argv)
+static const char *vlan_act_name(struct tc_vlan *p)
 {
-       struct tc_getqdisc_req_dump *req;
-       struct tc_getqdisc_list *rsp;
-       struct ynl_error yerr;
-       struct ynl_sock *ys;
+       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));
 
-       ys = ynl_sock_create(&ynl_tc_family, &yerr);
-       if (!ys) {
-               fprintf(stderr, "YNL: %s\n", yerr.msg);
-               return 1;
+       acts = tc_act_attrs_alloc(3);
+       if (!acts) {
+               tc_newtfilter_req_free(req);
+               return -1;
        }
+       memset(acts, 0, sizeof(*acts) * 3);
 
-       req = tc_getqdisc_req_dump_alloc();
+       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)
-               goto err_destroy;
+               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);
+}
 
-       rsp = tc_getqdisc_dump(ys, req);
-       tc_getqdisc_req_dump_free(req);
-       if (!rsp)
-               goto err_close;
+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));
 
-       if (ynl_dump_empty(rsp))
-               fprintf(stderr, "Error: no addresses reported\n");
-       ynl_dump_foreach(rsp, qdisc)
-               tc_qdisc_print(qdisc);
+       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);
+       }
+}
 
-       ynl_sock_destroy(ys);
-       return 0;
+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);
+       }
 
-err_close:
-       fprintf(stderr, "YNL: %s\n", ys->err.msg);
-err_destroy:
-       ynl_sock_destroy(ys);
-       return 2;
+       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