#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);
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;
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