xref: /linux/tools/net/ynl/tests/tc.c (revision 91a4855d6c03e770e42f17c798a36a3c46e63de2)
1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 #include <sched.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include <stdlib.h>
7 #include <arpa/inet.h>
8 #include <linux/pkt_sched.h>
9 #include <linux/tc_act/tc_vlan.h>
10 #include <linux/tc_act/tc_gact.h>
11 #include <linux/if_ether.h>
12 #include <net/if.h>
13 
14 #include <ynl.h>
15 
16 #include <kselftest_harness.h>
17 
18 #include "tc-user.h"
19 
20 #define TC_HANDLE (0xFFFF << 16)
21 
22 static bool tc_qdisc_print(struct __test_metadata *_metadata,
23 			   struct tc_getqdisc_rsp *q)
24 {
25 	bool was_fq_codel = false;
26 	char ifname[IF_NAMESIZE];
27 	const char *name;
28 
29 	name = if_indextoname(q->_hdr.tcm_ifindex, ifname);
30 	EXPECT_TRUE((bool)name);
31 	ksft_print_msg("%16s: ", name ?: "no-name");
32 
33 	if (q->_len.kind) {
34 		printf("%s  ", q->kind);
35 
36 		if (q->options._present.fq_codel) {
37 			struct tc_fq_codel_attrs *fq_codel;
38 			struct tc_fq_codel_xstats *stats;
39 
40 			fq_codel = &q->options.fq_codel;
41 			stats = q->stats2.app.fq_codel;
42 
43 			EXPECT_EQ(true,
44 				  fq_codel->_present.limit &&
45 				  fq_codel->_present.target &&
46 				  q->stats2.app._len.fq_codel);
47 
48 			if (fq_codel->_present.limit)
49 				printf("limit: %dp ", fq_codel->limit);
50 			if (fq_codel->_present.target)
51 				printf("target: %dms ",
52 				       (fq_codel->target + 500) / 1000);
53 			if (q->stats2.app._len.fq_codel)
54 				printf("new_flow_cnt: %d ",
55 				       stats->qdisc_stats.new_flow_count);
56 			was_fq_codel = true;
57 		}
58 	}
59 	printf("\n");
60 
61 	return was_fq_codel;
62 }
63 
64 static const char *vlan_act_name(struct tc_vlan *p)
65 {
66 	switch (p->v_action) {
67 	case TCA_VLAN_ACT_POP:
68 		return "pop";
69 	case TCA_VLAN_ACT_PUSH:
70 		return "push";
71 	case TCA_VLAN_ACT_MODIFY:
72 		return "modify";
73 	default:
74 		break;
75 	}
76 
77 	return "not supported";
78 }
79 
80 static const char *gact_act_name(struct tc_gact *p)
81 {
82 	switch (p->action) {
83 	case TC_ACT_SHOT:
84 		return "drop";
85 	case TC_ACT_OK:
86 		return "ok";
87 	case TC_ACT_PIPE:
88 		return "pipe";
89 	default:
90 		break;
91 	}
92 
93 	return "not supported";
94 }
95 
96 static void print_vlan(struct tc_act_vlan_attrs *vlan)
97 {
98 	printf("%s ", vlan_act_name(vlan->parms));
99 	if (vlan->_present.push_vlan_id)
100 		printf("id %u ", vlan->push_vlan_id);
101 	if (vlan->_present.push_vlan_protocol)
102 		printf("protocol %#x ", ntohs(vlan->push_vlan_protocol));
103 	if (vlan->_present.push_vlan_priority)
104 		printf("priority %u ", vlan->push_vlan_priority);
105 }
106 
107 static void print_gact(struct tc_act_gact_attrs *gact)
108 {
109 	struct tc_gact *p = gact->parms;
110 
111 	printf("%s ", gact_act_name(p));
112 }
113 
114 static void flower_print(struct tc_flower_attrs *flower, const char *kind)
115 {
116 	struct tc_act_attrs *a;
117 	unsigned int i;
118 
119 	ksft_print_msg("%s:\n", kind);
120 
121 	if (flower->_present.key_vlan_id)
122 		ksft_print_msg("  vlan_id: %u\n", flower->key_vlan_id);
123 	if (flower->_present.key_vlan_prio)
124 		ksft_print_msg("  vlan_prio: %u\n", flower->key_vlan_prio);
125 	if (flower->_present.key_num_of_vlans)
126 		ksft_print_msg("  num_of_vlans: %u\n",
127 			       flower->key_num_of_vlans);
128 
129 	for (i = 0; i < flower->_count.act; i++) {
130 		a = &flower->act[i];
131 		ksft_print_msg("action order: %i %s ", i + 1, a->kind);
132 		if (a->options._present.vlan)
133 			print_vlan(&a->options.vlan);
134 		else if (a->options._present.gact)
135 			print_gact(&a->options.gact);
136 		printf("\n");
137 	}
138 }
139 
140 static void tc_filter_print(struct __test_metadata *_metadata,
141 			     struct tc_gettfilter_rsp *f)
142 {
143 	struct tc_options_msg *opt = &f->options;
144 
145 	if (opt->_present.flower) {
146 		EXPECT_TRUE((bool)f->_len.kind);
147 		flower_print(&opt->flower, f->kind);
148 	} else if (f->_len.kind) {
149 		ksft_print_msg("%s pref %u proto: %#x\n", f->kind,
150 			       (f->_hdr.tcm_info >> 16),
151 			       ntohs(TC_H_MIN(f->_hdr.tcm_info)));
152 	}
153 }
154 
155 static int tc_clsact_add(struct ynl_sock *ys, int ifi)
156 {
157 	struct tc_newqdisc_req *req;
158 	int ret;
159 
160 	req = tc_newqdisc_req_alloc();
161 	if (!req)
162 		return -1;
163 	memset(req, 0, sizeof(*req));
164 
165 	req->_hdr.tcm_ifindex = ifi;
166 	req->_hdr.tcm_parent = TC_H_CLSACT;
167 	req->_hdr.tcm_handle = TC_HANDLE;
168 	tc_newqdisc_req_set_nlflags(req,
169 				    NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE);
170 	tc_newqdisc_req_set_kind(req, "clsact");
171 
172 	ret = tc_newqdisc(ys, req);
173 	tc_newqdisc_req_free(req);
174 
175 	return ret;
176 }
177 
178 static int tc_clsact_del(struct ynl_sock *ys, int ifi)
179 {
180 	struct tc_delqdisc_req *req;
181 	int ret;
182 
183 	req = tc_delqdisc_req_alloc();
184 	if (!req)
185 		return -1;
186 	memset(req, 0, sizeof(*req));
187 
188 	req->_hdr.tcm_ifindex = ifi;
189 	req->_hdr.tcm_parent = TC_H_CLSACT;
190 	req->_hdr.tcm_handle = TC_HANDLE;
191 	tc_delqdisc_req_set_nlflags(req, NLM_F_REQUEST);
192 
193 	ret = tc_delqdisc(ys, req);
194 	tc_delqdisc_req_free(req);
195 
196 	return ret;
197 }
198 
199 static int tc_filter_add(struct ynl_sock *ys, int ifi)
200 {
201 	struct tc_newtfilter_req *req;
202 	struct tc_act_attrs *acts;
203 	struct tc_vlan p = {
204 		.action = TC_ACT_PIPE,
205 		.v_action = TCA_VLAN_ACT_PUSH
206 	};
207 	int ret;
208 
209 	req = tc_newtfilter_req_alloc();
210 	if (!req)
211 		return -1;
212 	memset(req, 0, sizeof(*req));
213 
214 	acts = tc_act_attrs_alloc(3);
215 	if (!acts) {
216 		tc_newtfilter_req_free(req);
217 		return -1;
218 	}
219 	memset(acts, 0, sizeof(*acts) * 3);
220 
221 	req->_hdr.tcm_ifindex = ifi;
222 	req->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS);
223 	req->_hdr.tcm_info = TC_H_MAKE(1 << 16, htons(ETH_P_8021Q));
224 	req->chain = 0;
225 
226 	tc_newtfilter_req_set_nlflags(req, NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE);
227 	tc_newtfilter_req_set_kind(req, "flower");
228 	tc_newtfilter_req_set_options_flower_key_vlan_id(req, 100);
229 	tc_newtfilter_req_set_options_flower_key_vlan_prio(req, 5);
230 	tc_newtfilter_req_set_options_flower_key_num_of_vlans(req, 3);
231 
232 	__tc_newtfilter_req_set_options_flower_act(req, acts, 3);
233 
234 	/* Skip action at index 0 because in TC, the action array
235 	 * index starts at 1, with each index defining the action's
236 	 * order. In contrast, in YNL indexed arrays start at index 0.
237 	 */
238 	tc_act_attrs_set_kind(&acts[1], "vlan");
239 	tc_act_attrs_set_options_vlan_parms(&acts[1], &p, sizeof(p));
240 	tc_act_attrs_set_options_vlan_push_vlan_id(&acts[1], 200);
241 	tc_act_attrs_set_kind(&acts[2], "vlan");
242 	tc_act_attrs_set_options_vlan_parms(&acts[2], &p, sizeof(p));
243 	tc_act_attrs_set_options_vlan_push_vlan_id(&acts[2], 300);
244 
245 	tc_newtfilter_req_set_options_flower_flags(req, 0);
246 	tc_newtfilter_req_set_options_flower_key_eth_type(req, htons(0x8100));
247 
248 	ret = tc_newtfilter(ys, req);
249 	tc_newtfilter_req_free(req);
250 
251 	return ret;
252 }
253 
254 static int tc_filter_del(struct ynl_sock *ys, int ifi)
255 {
256 	struct tc_deltfilter_req *req;
257 	int ret;
258 
259 	req = tc_deltfilter_req_alloc();
260 	if (!req)
261 		return -1;
262 	memset(req, 0, sizeof(*req));
263 
264 	req->_hdr.tcm_ifindex = ifi;
265 	req->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS);
266 	req->_hdr.tcm_info = TC_H_MAKE(1 << 16, htons(ETH_P_8021Q));
267 	tc_deltfilter_req_set_nlflags(req, NLM_F_REQUEST);
268 
269 	ret = tc_deltfilter(ys, req);
270 	tc_deltfilter_req_free(req);
271 
272 	return ret;
273 }
274 
275 FIXTURE(tc)
276 {
277 	struct ynl_sock *ys;
278 	int ifindex;
279 };
280 
281 FIXTURE_SETUP(tc)
282 {
283 	struct ynl_error yerr;
284 	int ret;
285 
286 	ret = unshare(CLONE_NEWNET);
287 	ASSERT_EQ(0, ret);
288 
289 	self->ifindex = 1; /* loopback */
290 
291 	self->ys = ynl_sock_create(&ynl_tc_family, &yerr);
292 	ASSERT_NE(NULL, self->ys) {
293 		TH_LOG("failed to create tc socket: %s", yerr.msg);
294 	}
295 }
296 
297 FIXTURE_TEARDOWN(tc)
298 {
299 	ynl_sock_destroy(self->ys);
300 }
301 
302 TEST_F(tc, qdisc)
303 {
304 	struct tc_getqdisc_req_dump *dreq;
305 	struct tc_newqdisc_req *add_req;
306 	struct tc_delqdisc_req *del_req;
307 	struct tc_getqdisc_list *rsp;
308 	bool found = false;
309 	int ret;
310 
311 	add_req = tc_newqdisc_req_alloc();
312 	ASSERT_NE(NULL, add_req);
313 	memset(add_req, 0, sizeof(*add_req));
314 
315 	add_req->_hdr.tcm_ifindex = self->ifindex;
316 	add_req->_hdr.tcm_parent = TC_H_ROOT;
317 	tc_newqdisc_req_set_nlflags(add_req,
318 				    NLM_F_REQUEST | NLM_F_CREATE);
319 	tc_newqdisc_req_set_kind(add_req, "fq_codel");
320 
321 	ret = tc_newqdisc(self->ys, add_req);
322 	tc_newqdisc_req_free(add_req);
323 	ASSERT_EQ(0, ret) {
324 		TH_LOG("qdisc add failed: %s", self->ys->err.msg);
325 	}
326 
327 	dreq = tc_getqdisc_req_dump_alloc();
328 	ASSERT_NE(NULL, dreq);
329 	rsp = tc_getqdisc_dump(self->ys, dreq);
330 	tc_getqdisc_req_dump_free(dreq);
331 	ASSERT_NE(NULL, rsp) {
332 		TH_LOG("dump failed: %s", self->ys->err.msg);
333 	}
334 	ASSERT_FALSE(ynl_dump_empty(rsp));
335 
336 	ynl_dump_foreach(rsp, qdisc) {
337 		found |= tc_qdisc_print(_metadata, qdisc);
338 	}
339 	tc_getqdisc_list_free(rsp);
340 	EXPECT_TRUE(found);
341 
342 	del_req = tc_delqdisc_req_alloc();
343 	ASSERT_NE(NULL, del_req);
344 	memset(del_req, 0, sizeof(*del_req));
345 
346 	del_req->_hdr.tcm_ifindex = self->ifindex;
347 	del_req->_hdr.tcm_parent = TC_H_ROOT;
348 	tc_delqdisc_req_set_nlflags(del_req, NLM_F_REQUEST);
349 
350 	ret = tc_delqdisc(self->ys, del_req);
351 	tc_delqdisc_req_free(del_req);
352 	EXPECT_EQ(0, ret) {
353 		TH_LOG("qdisc del failed: %s", self->ys->err.msg);
354 	}
355 }
356 
357 TEST_F(tc, flower)
358 {
359 	struct tc_gettfilter_req_dump *dreq;
360 	struct tc_gettfilter_list *rsp;
361 	bool found = false;
362 	int ret;
363 
364 	ret = tc_clsact_add(self->ys, self->ifindex);
365 	if (ret)
366 		SKIP(return, "clsact not supported: %s", self->ys->err.msg);
367 
368 	ret = tc_filter_add(self->ys, self->ifindex);
369 	ASSERT_EQ(0, ret) {
370 		TH_LOG("filter add failed: %s", self->ys->err.msg);
371 	}
372 
373 	dreq = tc_gettfilter_req_dump_alloc();
374 	ASSERT_NE(NULL, dreq);
375 	memset(dreq, 0, sizeof(*dreq));
376 	dreq->_hdr.tcm_ifindex = self->ifindex;
377 	dreq->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS);
378 	dreq->_present.chain = 1;
379 	dreq->chain = 0;
380 
381 	rsp = tc_gettfilter_dump(self->ys, dreq);
382 	tc_gettfilter_req_dump_free(dreq);
383 	ASSERT_NE(NULL, rsp) {
384 		TH_LOG("filter dump failed: %s", self->ys->err.msg);
385 	}
386 
387 	ynl_dump_foreach(rsp, flt) {
388 		tc_filter_print(_metadata, flt);
389 		if (flt->options._present.flower) {
390 			EXPECT_EQ(100, flt->options.flower.key_vlan_id);
391 			EXPECT_EQ(5, flt->options.flower.key_vlan_prio);
392 			found = true;
393 		}
394 	}
395 	tc_gettfilter_list_free(rsp);
396 	EXPECT_TRUE(found);
397 
398 	ret = tc_filter_del(self->ys, self->ifindex);
399 	EXPECT_EQ(0, ret) {
400 		TH_LOG("filter del failed: %s", self->ys->err.msg);
401 	}
402 
403 	ret = tc_clsact_del(self->ys, self->ifindex);
404 	EXPECT_EQ(0, ret) {
405 		TH_LOG("clsact del failed: %s", self->ys->err.msg);
406 	}
407 }
408 
409 TEST_HARNESS_MAIN
410