mirror of
https://github.com/torvalds/linux.git
synced 2026-05-28 17:13:52 +02:00
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>
410 lines
9.6 KiB
C
410 lines
9.6 KiB
C
// 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
|