]>
Commit | Line | Data |
---|---|---|
acddc0ed | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
449a30ed SY |
2 | /* |
3 | * Zebra Traffic Control (TC) interaction with the kernel using netlink. | |
4 | * | |
5 | * Copyright (C) 2022 Shichu Yang | |
449a30ed SY |
6 | */ |
7 | ||
8 | #include <zebra.h> | |
9 | ||
10 | #ifdef HAVE_NETLINK | |
11 | ||
c317d3f2 SY |
12 | #include <linux/pkt_cls.h> |
13 | #include <linux/pkt_sched.h> | |
83f496bd | 14 | #include <netinet/if_ether.h> |
449a30ed SY |
15 | #include <sys/socket.h> |
16 | ||
17 | #include "if.h" | |
18 | #include "prefix.h" | |
19 | #include "vrf.h" | |
20 | ||
449a30ed SY |
21 | #include "zebra/zserv.h" |
22 | #include "zebra/zebra_ns.h" | |
449a30ed SY |
23 | #include "zebra/rt.h" |
24 | #include "zebra/interface.h" | |
25 | #include "zebra/debug.h" | |
449a30ed SY |
26 | #include "zebra/kernel_netlink.h" |
27 | #include "zebra/tc_netlink.h" | |
28 | #include "zebra/zebra_errors.h" | |
29 | #include "zebra/zebra_dplane.h" | |
c317d3f2 | 30 | #include "zebra/zebra_tc.h" |
449a30ed SY |
31 | #include "zebra/zebra_trace.h" |
32 | ||
449a30ed SY |
33 | #define TC_FREQ_DEFAULT (100) |
34 | ||
c317d3f2 SY |
35 | /* some magic number */ |
36 | #define TC_QDISC_MAJOR_ZEBRA (0xbeef0000u) | |
449a30ed SY |
37 | #define TC_MINOR_NOCLASS (0xffffu) |
38 | ||
449a30ed SY |
39 | #define TIME_UNITS_PER_SEC (1000000) |
40 | #define xmittime(r, s) (TIME_UNITS_PER_SEC * ((double)(s) / (double)(r))) | |
41 | ||
42 | static uint32_t tc_get_freq(void) | |
43 | { | |
44 | int freq = 0; | |
45 | FILE *fp = fopen("/proc/net/psched", "r"); | |
46 | ||
47 | if (fp) { | |
48 | uint32_t nom, denom; | |
49 | ||
50 | if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2) { | |
51 | if (nom == 1000000) | |
52 | freq = denom; | |
53 | } | |
54 | fclose(fp); | |
55 | } | |
56 | ||
57 | return freq == 0 ? TC_FREQ_DEFAULT : freq; | |
58 | } | |
59 | ||
449a30ed SY |
60 | static void tc_calc_rate_table(struct tc_ratespec *ratespec, uint32_t *table, |
61 | uint32_t mtu) | |
62 | { | |
63 | if (mtu == 0) | |
64 | mtu = 2047; | |
65 | ||
66 | int cell_log = -1; | |
67 | ||
68 | if (cell_log < 0) { | |
69 | cell_log = 0; | |
70 | while ((mtu >> cell_log) > 255) | |
71 | cell_log++; | |
72 | } | |
73 | ||
74 | for (int i = 0; i < 256; i++) | |
75 | table[i] = xmittime(ratespec->rate, (i + 1) << cell_log); | |
76 | ||
77 | ratespec->cell_align = -1; | |
78 | ratespec->cell_log = cell_log; | |
79 | ratespec->linklayer = TC_LINKLAYER_ETHERNET; | |
80 | } | |
81 | ||
82 | static int tc_flower_get_inet_prefix(const struct prefix *prefix, | |
83 | struct inet_prefix *addr) | |
84 | { | |
85 | addr->family = prefix->family; | |
86 | ||
87 | if (addr->family == AF_INET) { | |
88 | addr->bytelen = 4; | |
89 | addr->bitlen = prefix->prefixlen; | |
90 | addr->flags = 0; | |
91 | addr->flags |= PREFIXLEN_SPECIFIED; | |
92 | addr->flags |= ADDRTYPE_INET; | |
93 | memcpy(addr->data, prefix->u.val32, sizeof(prefix->u.val32)); | |
94 | } else if (addr->family == AF_INET6) { | |
95 | addr->bytelen = 16; | |
96 | addr->bitlen = prefix->prefixlen; | |
97 | addr->flags = 0; | |
98 | addr->flags |= PREFIXLEN_SPECIFIED; | |
99 | addr->flags |= ADDRTYPE_INET; | |
100 | memcpy(addr->data, prefix->u.val, sizeof(prefix->u.val)); | |
101 | } else { | |
102 | return -1; | |
103 | } | |
104 | ||
105 | return 0; | |
106 | } | |
107 | ||
108 | static int tc_flower_get_inet_mask(const struct prefix *prefix, | |
109 | struct inet_prefix *addr) | |
110 | { | |
111 | addr->family = prefix->family; | |
112 | ||
113 | if (addr->family == AF_INET) { | |
114 | addr->bytelen = 4; | |
115 | addr->bitlen = prefix->prefixlen; | |
116 | addr->flags = 0; | |
117 | addr->flags |= PREFIXLEN_SPECIFIED; | |
118 | addr->flags |= ADDRTYPE_INET; | |
119 | } else if (addr->family == AF_INET6) { | |
120 | addr->bytelen = 16; | |
121 | addr->bitlen = prefix->prefixlen; | |
122 | addr->flags = 0; | |
123 | addr->flags |= PREFIXLEN_SPECIFIED; | |
124 | addr->flags |= ADDRTYPE_INET; | |
125 | } else { | |
126 | return -1; | |
127 | } | |
128 | ||
129 | memset(addr->data, 0xff, addr->bytelen); | |
130 | ||
131 | int rest = prefix->prefixlen; | |
132 | ||
133 | for (int i = 0; i < addr->bytelen / 4; i++) { | |
134 | if (!rest) { | |
135 | addr->data[i] = 0; | |
136 | } else if (rest / 32 >= 1) { | |
137 | rest -= 32; | |
138 | } else { | |
139 | addr->data[i] <<= 32 - rest; | |
140 | addr->data[i] = htonl(addr->data[i]); | |
141 | rest = 0; | |
142 | } | |
143 | } | |
144 | ||
145 | return 0; | |
146 | } | |
147 | ||
148 | /* | |
149 | * Traffic control queue discipline encoding (only "htb" supported) | |
150 | */ | |
151 | static ssize_t netlink_qdisc_msg_encode(int cmd, struct zebra_dplane_ctx *ctx, | |
152 | void *data, size_t datalen) | |
153 | { | |
154 | struct nlsock *nl; | |
c317d3f2 | 155 | const char *kind_str = NULL; |
449a30ed SY |
156 | |
157 | struct rtattr *nest; | |
158 | ||
159 | struct { | |
160 | struct nlmsghdr n; | |
161 | struct tcmsg t; | |
162 | char buf[0]; | |
163 | } *req = (void *)data; | |
164 | ||
165 | if (datalen < sizeof(*req)) | |
166 | return 0; | |
167 | ||
168 | nl = kernel_netlink_nlsock_lookup(dplane_ctx_get_ns_sock(ctx)); | |
169 | ||
170 | memset(req, 0, sizeof(*req)); | |
171 | ||
172 | req->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); | |
173 | req->n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST; | |
174 | ||
175 | req->n.nlmsg_flags |= NLM_F_REPLACE; | |
176 | ||
177 | req->n.nlmsg_type = cmd; | |
178 | ||
179 | req->n.nlmsg_pid = nl->snl.nl_pid; | |
180 | ||
181 | req->t.tcm_family = AF_UNSPEC; | |
182 | req->t.tcm_ifindex = dplane_ctx_get_ifindex(ctx); | |
c317d3f2 SY |
183 | req->t.tcm_info = 0; |
184 | req->t.tcm_handle = 0; | |
449a30ed SY |
185 | req->t.tcm_parent = TC_H_ROOT; |
186 | ||
c317d3f2 SY |
187 | if (cmd == RTM_NEWQDISC) { |
188 | req->t.tcm_handle = TC_H_MAKE(TC_QDISC_MAJOR_ZEBRA, 0); | |
189 | ||
190 | kind_str = dplane_ctx_tc_qdisc_get_kind_str(ctx); | |
449a30ed | 191 | |
c317d3f2 SY |
192 | nl_attr_put(&req->n, datalen, TCA_KIND, kind_str, |
193 | strlen(kind_str) + 1); | |
449a30ed | 194 | |
c317d3f2 SY |
195 | nest = nl_attr_nest(&req->n, datalen, TCA_OPTIONS); |
196 | ||
197 | switch (dplane_ctx_tc_qdisc_get_kind(ctx)) { | |
198 | case TC_QDISC_HTB: { | |
199 | struct tc_htb_glob htb_glob = { | |
200 | .rate2quantum = 10, | |
201 | .version = 3, | |
202 | .defcls = TC_MINOR_NOCLASS}; | |
203 | nl_attr_put(&req->n, datalen, TCA_HTB_INIT, &htb_glob, | |
204 | sizeof(htb_glob)); | |
205 | break; | |
206 | } | |
207 | case TC_QDISC_NOQUEUE: | |
208 | break; | |
209 | default: | |
210 | break; | |
211 | /* not implemented */ | |
212 | } | |
213 | ||
214 | nl_attr_nest_end(&req->n, nest); | |
215 | } else { | |
216 | /* ifindex are enough for del/get qdisc */ | |
217 | } | |
449a30ed SY |
218 | |
219 | return NLMSG_ALIGN(req->n.nlmsg_len); | |
220 | } | |
221 | ||
222 | /* | |
223 | * Traffic control class encoding | |
224 | */ | |
225 | static ssize_t netlink_tclass_msg_encode(int cmd, struct zebra_dplane_ctx *ctx, | |
226 | void *data, size_t datalen) | |
227 | { | |
c317d3f2 | 228 | enum dplane_op_e op = dplane_ctx_get_op(ctx); |
449a30ed | 229 | |
c317d3f2 SY |
230 | struct nlsock *nl; |
231 | const char *kind_str = NULL; | |
449a30ed SY |
232 | |
233 | struct rtattr *nest; | |
234 | ||
235 | struct { | |
236 | struct nlmsghdr n; | |
237 | struct tcmsg t; | |
238 | char buf[0]; | |
239 | } *req = (void *)data; | |
240 | ||
241 | if (datalen < sizeof(*req)) | |
242 | return 0; | |
243 | ||
244 | nl = kernel_netlink_nlsock_lookup(dplane_ctx_get_ns_sock(ctx)); | |
245 | ||
246 | memset(req, 0, sizeof(*req)); | |
247 | ||
248 | req->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); | |
249 | req->n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST; | |
250 | ||
c317d3f2 SY |
251 | if (op == DPLANE_OP_TC_CLASS_UPDATE) |
252 | req->n.nlmsg_flags |= NLM_F_REPLACE; | |
253 | ||
449a30ed SY |
254 | req->n.nlmsg_type = cmd; |
255 | ||
256 | req->n.nlmsg_pid = nl->snl.nl_pid; | |
257 | ||
258 | req->t.tcm_family = AF_UNSPEC; | |
259 | req->t.tcm_ifindex = dplane_ctx_get_ifindex(ctx); | |
449a30ed | 260 | |
c317d3f2 SY |
261 | req->t.tcm_handle = TC_H_MAKE(TC_QDISC_MAJOR_ZEBRA, |
262 | dplane_ctx_tc_class_get_handle(ctx)); | |
263 | req->t.tcm_parent = TC_H_MAKE(TC_QDISC_MAJOR_ZEBRA, 0); | |
264 | req->t.tcm_info = 0; | |
265 | ||
266 | kind_str = dplane_ctx_tc_class_get_kind_str(ctx); | |
267 | ||
268 | if (op == DPLANE_OP_TC_CLASS_ADD || op == DPLANE_OP_TC_CLASS_UPDATE) { | |
269 | zlog_debug("netlink tclass encoder: op: %s kind: %s handle: %u", | |
270 | op == DPLANE_OP_TC_CLASS_UPDATE ? "update" : "add", | |
271 | kind_str, dplane_ctx_tc_class_get_handle(ctx)); | |
272 | ||
273 | nl_attr_put(&req->n, datalen, TCA_KIND, kind_str, | |
274 | strlen(kind_str) + 1); | |
275 | ||
276 | nest = nl_attr_nest(&req->n, datalen, TCA_OPTIONS); | |
449a30ed | 277 | |
c317d3f2 SY |
278 | switch (dplane_ctx_tc_class_get_kind(ctx)) { |
279 | case TC_QDISC_HTB: { | |
280 | struct tc_htb_opt htb_opt = {}; | |
449a30ed | 281 | |
c317d3f2 SY |
282 | uint64_t rate = dplane_ctx_tc_class_get_rate(ctx), |
283 | ceil = dplane_ctx_tc_class_get_ceil(ctx); | |
449a30ed | 284 | |
c317d3f2 | 285 | uint64_t buffer, cbuffer; |
449a30ed | 286 | |
c317d3f2 SY |
287 | /* TODO: fetch mtu from interface */ |
288 | uint32_t mtu = 1500; | |
449a30ed | 289 | |
c317d3f2 SY |
290 | uint32_t rtab[256]; |
291 | uint32_t ctab[256]; | |
449a30ed | 292 | |
c317d3f2 | 293 | ceil = MAX(rate, ceil); |
449a30ed | 294 | |
c317d3f2 SY |
295 | htb_opt.rate.rate = (rate >> 32 != 0) ? ~0U : rate; |
296 | htb_opt.ceil.rate = (ceil >> 32 != 0) ? ~0U : ceil; | |
449a30ed | 297 | |
c317d3f2 SY |
298 | buffer = rate / tc_get_freq() + mtu; |
299 | cbuffer = ceil / tc_get_freq() + mtu; | |
300 | ||
301 | htb_opt.buffer = buffer; | |
302 | htb_opt.cbuffer = cbuffer; | |
303 | ||
304 | tc_calc_rate_table(&htb_opt.rate, rtab, mtu); | |
305 | tc_calc_rate_table(&htb_opt.ceil, ctab, mtu); | |
306 | ||
307 | htb_opt.ceil.mpu = htb_opt.rate.mpu = 0; | |
308 | htb_opt.ceil.overhead = htb_opt.rate.overhead = 0; | |
309 | ||
310 | if (rate >> 32 != 0) { | |
311 | nl_attr_put(&req->n, datalen, TCA_HTB_RATE64, | |
312 | &rate, sizeof(rate)); | |
313 | } | |
314 | ||
315 | if (ceil >> 32 != 0) { | |
316 | nl_attr_put(&req->n, datalen, TCA_HTB_CEIL64, | |
317 | &ceil, sizeof(ceil)); | |
318 | } | |
319 | ||
320 | nl_attr_put(&req->n, datalen, TCA_HTB_PARMS, &htb_opt, | |
321 | sizeof(htb_opt)); | |
322 | ||
323 | nl_attr_put(&req->n, datalen, TCA_HTB_RTAB, rtab, | |
324 | sizeof(rtab)); | |
325 | nl_attr_put(&req->n, datalen, TCA_HTB_CTAB, ctab, | |
326 | sizeof(ctab)); | |
327 | break; | |
328 | } | |
329 | default: | |
330 | break; | |
331 | } | |
332 | ||
333 | nl_attr_nest_end(&req->n, nest); | |
449a30ed SY |
334 | } |
335 | ||
c317d3f2 SY |
336 | return NLMSG_ALIGN(req->n.nlmsg_len); |
337 | } | |
338 | ||
339 | static int netlink_tfilter_flower_port_type(uint8_t ip_proto, bool src) | |
340 | { | |
341 | if (ip_proto == IPPROTO_TCP) | |
342 | return src ? TCA_FLOWER_KEY_TCP_SRC : TCA_FLOWER_KEY_TCP_DST; | |
343 | else if (ip_proto == IPPROTO_UDP) | |
344 | return src ? TCA_FLOWER_KEY_UDP_SRC : TCA_FLOWER_KEY_UDP_DST; | |
345 | else if (ip_proto == IPPROTO_SCTP) | |
346 | return src ? TCA_FLOWER_KEY_SCTP_SRC : TCA_FLOWER_KEY_SCTP_DST; | |
347 | else | |
348 | return -1; | |
349 | } | |
350 | ||
351 | static void netlink_tfilter_flower_put_options(struct nlmsghdr *n, | |
352 | size_t datalen, | |
353 | struct zebra_dplane_ctx *ctx) | |
354 | { | |
355 | struct inet_prefix addr; | |
356 | uint32_t flags = 0, classid; | |
357 | uint8_t protocol = htons(dplane_ctx_tc_filter_get_eth_proto(ctx)); | |
358 | uint32_t filter_bm = dplane_ctx_tc_filter_get_filter_bm(ctx); | |
359 | ||
360 | if (filter_bm & TC_FLOWER_SRC_IP) { | |
361 | const struct prefix *src_p = | |
362 | dplane_ctx_tc_filter_get_src_ip(ctx); | |
363 | ||
364 | if (tc_flower_get_inet_prefix(src_p, &addr) != 0) | |
365 | return; | |
366 | ||
367 | nl_attr_put(n, datalen, | |
368 | (addr.family == AF_INET) ? TCA_FLOWER_KEY_IPV4_SRC | |
369 | : TCA_FLOWER_KEY_IPV6_SRC, | |
370 | addr.data, addr.bytelen); | |
371 | ||
372 | if (tc_flower_get_inet_mask(src_p, &addr) != 0) | |
373 | return; | |
374 | ||
375 | nl_attr_put(n, datalen, | |
376 | (addr.family == AF_INET) | |
377 | ? TCA_FLOWER_KEY_IPV4_SRC_MASK | |
378 | : TCA_FLOWER_KEY_IPV6_SRC_MASK, | |
379 | addr.data, addr.bytelen); | |
449a30ed SY |
380 | } |
381 | ||
c317d3f2 SY |
382 | if (filter_bm & TC_FLOWER_DST_IP) { |
383 | const struct prefix *dst_p = | |
384 | dplane_ctx_tc_filter_get_dst_ip(ctx); | |
449a30ed | 385 | |
c317d3f2 SY |
386 | if (tc_flower_get_inet_prefix(dst_p, &addr) != 0) |
387 | return; | |
449a30ed | 388 | |
c317d3f2 SY |
389 | nl_attr_put(n, datalen, |
390 | (addr.family == AF_INET) ? TCA_FLOWER_KEY_IPV4_DST | |
391 | : TCA_FLOWER_KEY_IPV6_DST, | |
392 | addr.data, addr.bytelen); | |
393 | ||
394 | if (tc_flower_get_inet_mask(dst_p, &addr) != 0) | |
395 | return; | |
396 | ||
397 | nl_attr_put(n, datalen, | |
398 | (addr.family == AF_INET) | |
399 | ? TCA_FLOWER_KEY_IPV4_DST_MASK | |
400 | : TCA_FLOWER_KEY_IPV6_DST_MASK, | |
401 | addr.data, addr.bytelen); | |
402 | } | |
403 | ||
404 | if (filter_bm & TC_FLOWER_IP_PROTOCOL) { | |
405 | nl_attr_put8(n, datalen, TCA_FLOWER_KEY_IP_PROTO, | |
406 | dplane_ctx_tc_filter_get_ip_proto(ctx)); | |
407 | } | |
408 | ||
409 | if (filter_bm & TC_FLOWER_SRC_PORT) { | |
410 | uint16_t min, max; | |
411 | ||
412 | min = dplane_ctx_tc_filter_get_src_port_min(ctx); | |
413 | max = dplane_ctx_tc_filter_get_src_port_max(ctx); | |
414 | ||
415 | if (max > min) { | |
416 | nl_attr_put16(n, datalen, TCA_FLOWER_KEY_PORT_SRC_MIN, | |
417 | htons(min)); | |
418 | ||
419 | nl_attr_put16(n, datalen, TCA_FLOWER_KEY_PORT_SRC_MAX, | |
420 | htons(max)); | |
421 | } else { | |
422 | int type = netlink_tfilter_flower_port_type( | |
423 | dplane_ctx_tc_filter_get_ip_proto(ctx), true); | |
424 | ||
425 | if (type < 0) | |
426 | return; | |
427 | ||
428 | nl_attr_put16(n, datalen, type, htons(min)); | |
429 | } | |
430 | } | |
431 | ||
432 | if (filter_bm & TC_FLOWER_DST_PORT) { | |
433 | uint16_t min = dplane_ctx_tc_filter_get_dst_port_min(ctx), | |
434 | max = dplane_ctx_tc_filter_get_dst_port_max(ctx); | |
435 | ||
436 | if (max > min) { | |
437 | nl_attr_put16(n, datalen, TCA_FLOWER_KEY_PORT_DST_MIN, | |
438 | htons(min)); | |
439 | ||
440 | nl_attr_put16(n, datalen, TCA_FLOWER_KEY_PORT_DST_MAX, | |
441 | htons(max)); | |
442 | } else { | |
443 | int type = netlink_tfilter_flower_port_type( | |
444 | dplane_ctx_tc_filter_get_ip_proto(ctx), false); | |
445 | ||
446 | if (type < 0) | |
447 | return; | |
448 | ||
449 | nl_attr_put16(n, datalen, type, htons(min)); | |
450 | } | |
451 | } | |
452 | ||
453 | if (filter_bm & TC_FLOWER_DSFIELD) { | |
454 | nl_attr_put8(n, datalen, TCA_FLOWER_KEY_IP_TOS, | |
455 | dplane_ctx_tc_filter_get_dsfield(ctx)); | |
456 | nl_attr_put8(n, datalen, TCA_FLOWER_KEY_IP_TOS_MASK, | |
457 | dplane_ctx_tc_filter_get_dsfield_mask(ctx)); | |
458 | } | |
459 | ||
460 | classid = TC_H_MAKE(TC_QDISC_MAJOR_ZEBRA, | |
461 | dplane_ctx_tc_filter_get_classid(ctx)); | |
462 | nl_attr_put32(n, datalen, TCA_FLOWER_CLASSID, classid); | |
463 | ||
464 | nl_attr_put32(n, datalen, TCA_FLOWER_FLAGS, flags); | |
465 | ||
466 | nl_attr_put16(n, datalen, TCA_FLOWER_KEY_ETH_TYPE, protocol); | |
449a30ed SY |
467 | } |
468 | ||
469 | /* | |
c317d3f2 | 470 | * Traffic control filter encoding |
449a30ed SY |
471 | */ |
472 | static ssize_t netlink_tfilter_msg_encode(int cmd, struct zebra_dplane_ctx *ctx, | |
473 | void *data, size_t datalen) | |
474 | { | |
c317d3f2 SY |
475 | enum dplane_op_e op = dplane_ctx_get_op(ctx); |
476 | ||
449a30ed | 477 | struct nlsock *nl; |
c317d3f2 | 478 | const char *kind_str = NULL; |
449a30ed | 479 | |
c317d3f2 | 480 | struct rtattr *nest; |
449a30ed SY |
481 | |
482 | uint16_t priority; | |
483 | uint16_t protocol; | |
449a30ed SY |
484 | |
485 | struct { | |
486 | struct nlmsghdr n; | |
487 | struct tcmsg t; | |
488 | char buf[0]; | |
489 | } *req = (void *)data; | |
490 | ||
491 | if (datalen < sizeof(*req)) | |
492 | return 0; | |
493 | ||
494 | nl = kernel_netlink_nlsock_lookup(dplane_ctx_get_ns_sock(ctx)); | |
495 | ||
496 | memset(req, 0, sizeof(*req)); | |
497 | ||
498 | req->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); | |
499 | req->n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST; | |
500 | ||
c317d3f2 SY |
501 | if (op == DPLANE_OP_TC_FILTER_UPDATE) |
502 | req->n.nlmsg_flags |= NLM_F_REPLACE; | |
449a30ed SY |
503 | |
504 | req->n.nlmsg_type = cmd; | |
505 | ||
506 | req->n.nlmsg_pid = nl->snl.nl_pid; | |
507 | ||
508 | req->t.tcm_family = AF_UNSPEC; | |
509 | req->t.tcm_ifindex = dplane_ctx_get_ifindex(ctx); | |
510 | ||
c317d3f2 SY |
511 | priority = dplane_ctx_tc_filter_get_priority(ctx); |
512 | protocol = htons(dplane_ctx_tc_filter_get_eth_proto(ctx)); | |
513 | ||
514 | req->t.tcm_info = TC_H_MAKE(priority << 16, protocol); | |
515 | req->t.tcm_handle = dplane_ctx_tc_filter_get_handle(ctx); | |
516 | req->t.tcm_parent = TC_H_MAKE(TC_QDISC_MAJOR_ZEBRA, 0); | |
517 | ||
518 | kind_str = dplane_ctx_tc_filter_get_kind_str(ctx); | |
519 | ||
520 | if (op == DPLANE_OP_TC_FILTER_ADD || op == DPLANE_OP_TC_FILTER_UPDATE) { | |
521 | nl_attr_put(&req->n, datalen, TCA_KIND, kind_str, | |
522 | strlen(kind_str) + 1); | |
523 | ||
524 | zlog_debug( | |
525 | "netlink tfilter encoder: op: %s priority: %u protocol: %u kind: %s handle: %u filter_bm: %u ip_proto: %u", | |
526 | op == DPLANE_OP_TC_FILTER_UPDATE ? "update" : "add", | |
527 | priority, protocol, kind_str, | |
528 | dplane_ctx_tc_filter_get_handle(ctx), | |
529 | dplane_ctx_tc_filter_get_filter_bm(ctx), | |
530 | dplane_ctx_tc_filter_get_ip_proto(ctx)); | |
531 | ||
532 | nest = nl_attr_nest(&req->n, datalen, TCA_OPTIONS); | |
533 | switch (dplane_ctx_tc_filter_get_kind(ctx)) { | |
534 | case TC_FILTER_FLOWER: { | |
535 | netlink_tfilter_flower_put_options(&req->n, datalen, | |
536 | ctx); | |
537 | break; | |
538 | } | |
539 | default: | |
540 | break; | |
541 | } | |
542 | nl_attr_nest_end(&req->n, nest); | |
543 | } | |
544 | ||
545 | return NLMSG_ALIGN(req->n.nlmsg_len); | |
546 | } | |
547 | ||
548 | static ssize_t netlink_newqdisc_msg_encoder(struct zebra_dplane_ctx *ctx, | |
549 | void *buf, size_t buflen) | |
550 | { | |
551 | return netlink_qdisc_msg_encode(RTM_NEWQDISC, ctx, buf, buflen); | |
552 | } | |
553 | ||
554 | static ssize_t netlink_delqdisc_msg_encoder(struct zebra_dplane_ctx *ctx, | |
555 | void *buf, size_t buflen) | |
556 | { | |
557 | return netlink_qdisc_msg_encode(RTM_DELQDISC, ctx, buf, buflen); | |
558 | } | |
559 | ||
560 | static ssize_t netlink_newtclass_msg_encoder(struct zebra_dplane_ctx *ctx, | |
561 | void *buf, size_t buflen) | |
562 | { | |
563 | return netlink_tclass_msg_encode(RTM_NEWTCLASS, ctx, buf, buflen); | |
564 | } | |
449a30ed | 565 | |
c317d3f2 SY |
566 | static ssize_t netlink_deltclass_msg_encoder(struct zebra_dplane_ctx *ctx, |
567 | void *buf, size_t buflen) | |
568 | { | |
569 | return netlink_tclass_msg_encode(RTM_DELTCLASS, ctx, buf, buflen); | |
570 | } | |
449a30ed | 571 | |
c317d3f2 SY |
572 | static ssize_t netlink_newtfilter_msg_encoder(struct zebra_dplane_ctx *ctx, |
573 | void *buf, size_t buflen) | |
574 | { | |
575 | return netlink_tfilter_msg_encode(RTM_NEWTFILTER, ctx, buf, buflen); | |
576 | } | |
449a30ed | 577 | |
c317d3f2 SY |
578 | static ssize_t netlink_deltfilter_msg_encoder(struct zebra_dplane_ctx *ctx, |
579 | void *buf, size_t buflen) | |
580 | { | |
581 | return netlink_tfilter_msg_encode(RTM_DELTFILTER, ctx, buf, buflen); | |
582 | } | |
449a30ed | 583 | |
c317d3f2 SY |
584 | enum netlink_msg_status |
585 | netlink_put_tc_qdisc_update_msg(struct nl_batch *bth, | |
586 | struct zebra_dplane_ctx *ctx) | |
587 | { | |
588 | enum dplane_op_e op; | |
589 | enum netlink_msg_status ret; | |
449a30ed | 590 | |
c317d3f2 | 591 | op = dplane_ctx_get_op(ctx); |
449a30ed | 592 | |
c317d3f2 SY |
593 | if (op == DPLANE_OP_TC_QDISC_INSTALL) { |
594 | ret = netlink_batch_add_msg( | |
595 | bth, ctx, netlink_newqdisc_msg_encoder, false); | |
596 | } else if (op == DPLANE_OP_TC_QDISC_UNINSTALL) { | |
597 | ret = netlink_batch_add_msg( | |
598 | bth, ctx, netlink_delqdisc_msg_encoder, false); | |
599 | } else { | |
600 | return FRR_NETLINK_ERROR; | |
601 | } | |
449a30ed | 602 | |
c317d3f2 SY |
603 | return ret; |
604 | } | |
449a30ed | 605 | |
c317d3f2 SY |
606 | enum netlink_msg_status |
607 | netlink_put_tc_class_update_msg(struct nl_batch *bth, | |
608 | struct zebra_dplane_ctx *ctx) | |
609 | { | |
610 | enum dplane_op_e op; | |
611 | enum netlink_msg_status ret; | |
449a30ed | 612 | |
c317d3f2 SY |
613 | op = dplane_ctx_get_op(ctx); |
614 | ||
615 | if (op == DPLANE_OP_TC_CLASS_ADD || op == DPLANE_OP_TC_CLASS_UPDATE) { | |
616 | ret = netlink_batch_add_msg( | |
617 | bth, ctx, netlink_newtclass_msg_encoder, false); | |
618 | } else if (op == DPLANE_OP_TC_CLASS_DELETE) { | |
619 | ret = netlink_batch_add_msg( | |
620 | bth, ctx, netlink_deltclass_msg_encoder, false); | |
621 | } else { | |
622 | return FRR_NETLINK_ERROR; | |
449a30ed SY |
623 | } |
624 | ||
c317d3f2 SY |
625 | return ret; |
626 | } | |
449a30ed | 627 | |
c317d3f2 SY |
628 | enum netlink_msg_status |
629 | netlink_put_tc_filter_update_msg(struct nl_batch *bth, | |
630 | struct zebra_dplane_ctx *ctx) | |
631 | { | |
632 | enum dplane_op_e op; | |
633 | enum netlink_msg_status ret; | |
634 | ||
635 | op = dplane_ctx_get_op(ctx); | |
636 | ||
637 | if (op == DPLANE_OP_TC_FILTER_ADD) { | |
638 | ret = netlink_batch_add_msg( | |
639 | bth, ctx, netlink_newtfilter_msg_encoder, false); | |
640 | } else if (op == DPLANE_OP_TC_FILTER_UPDATE) { | |
641 | /* | |
642 | * Replace will fail if either filter type or the number of | |
643 | * filter options is changed, so DEL then NEW | |
644 | * | |
645 | * TFILTER may have refs to TCLASS. | |
646 | */ | |
647 | ||
648 | (void)netlink_batch_add_msg( | |
649 | bth, ctx, netlink_deltfilter_msg_encoder, false); | |
650 | ret = netlink_batch_add_msg( | |
651 | bth, ctx, netlink_newtfilter_msg_encoder, false); | |
652 | } else if (op == DPLANE_OP_TC_FILTER_DELETE) { | |
653 | ret = netlink_batch_add_msg( | |
654 | bth, ctx, netlink_deltfilter_msg_encoder, false); | |
655 | } else { | |
656 | return FRR_NETLINK_ERROR; | |
657 | } | |
449a30ed | 658 | |
c317d3f2 SY |
659 | return ret; |
660 | } | |
449a30ed | 661 | |
c317d3f2 SY |
662 | /* |
663 | * Request filters from the kernel | |
664 | */ | |
665 | static int netlink_request_filters(struct zebra_ns *zns, int family, int type, | |
666 | ifindex_t ifindex) | |
667 | { | |
668 | struct { | |
669 | struct nlmsghdr n; | |
670 | struct tcmsg tc; | |
671 | } req; | |
449a30ed | 672 | |
c317d3f2 SY |
673 | memset(&req, 0, sizeof(req)); |
674 | req.n.nlmsg_type = type; | |
675 | req.n.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; | |
676 | req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); | |
677 | req.tc.tcm_family = family; | |
678 | req.tc.tcm_ifindex = ifindex; | |
679 | ||
680 | return netlink_request(&zns->netlink_cmd, &req); | |
681 | } | |
682 | ||
683 | /* | |
684 | * Request queue discipline from the kernel | |
685 | */ | |
686 | static int netlink_request_qdiscs(struct zebra_ns *zns, int family, int type) | |
687 | { | |
688 | struct { | |
689 | struct nlmsghdr n; | |
690 | struct tcmsg tc; | |
691 | } req; | |
692 | ||
693 | memset(&req, 0, sizeof(req)); | |
694 | req.n.nlmsg_type = type; | |
695 | req.n.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; | |
696 | req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); | |
697 | req.tc.tcm_family = family; | |
698 | ||
699 | return netlink_request(&zns->netlink_cmd, &req); | |
700 | } | |
701 | ||
702 | int netlink_qdisc_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) | |
703 | { | |
704 | struct tcmsg *tcm; | |
705 | struct zebra_tc_qdisc qdisc = {}; | |
706 | ||
707 | int len; | |
708 | struct rtattr *tb[TCA_MAX + 1]; | |
709 | ||
710 | frrtrace(3, frr_zebra, netlink_tc_qdisc_change, h, ns_id, startup); | |
711 | ||
712 | len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct tcmsg)); | |
713 | ||
714 | if (len < 0) { | |
715 | zlog_err( | |
716 | "%s: Message received from netlink is of a broken size %d %zu", | |
717 | __func__, h->nlmsg_len, | |
718 | (size_t)NLMSG_LENGTH(sizeof(struct tcmsg))); | |
719 | return -1; | |
720 | } | |
721 | ||
722 | tcm = NLMSG_DATA(h); | |
723 | netlink_parse_rtattr(tb, TCA_MAX, TCA_RTA(tcm), len); | |
724 | ||
725 | const char *kind_str = (const char *)RTA_DATA(tb[TCA_KIND]); | |
726 | ||
727 | enum tc_qdisc_kind kind = tc_qdisc_str2kind(kind_str); | |
728 | ||
729 | qdisc.qdisc.ifindex = tcm->tcm_ifindex; | |
730 | ||
731 | switch (kind) { | |
732 | case TC_QDISC_NOQUEUE: | |
733 | /* "noqueue" is the default qdisc */ | |
734 | break; | |
a98701f0 DS |
735 | case TC_QDISC_HTB: |
736 | case TC_QDISC_UNSPEC: | |
c317d3f2 | 737 | break; |
449a30ed SY |
738 | } |
739 | ||
c317d3f2 SY |
740 | if (tb[TCA_OPTIONS] != NULL) { |
741 | struct rtattr *options[TCA_HTB_MAX + 1]; | |
742 | ||
743 | netlink_parse_rtattr_nested(options, TCA_HTB_MAX, | |
744 | tb[TCA_OPTIONS]); | |
745 | ||
746 | /* TODO: more details */ | |
747 | /* struct tc_htb_glob *glob = RTA_DATA(options[TCA_HTB_INIT]); | |
748 | */ | |
449a30ed SY |
749 | } |
750 | ||
c317d3f2 SY |
751 | if (h->nlmsg_type == RTM_NEWQDISC) { |
752 | if (startup && | |
753 | TC_H_MAJ(tcm->tcm_handle) == TC_QDISC_MAJOR_ZEBRA) { | |
754 | enum zebra_dplane_result ret; | |
449a30ed | 755 | |
c317d3f2 | 756 | ret = dplane_tc_qdisc_uninstall(&qdisc); |
449a30ed | 757 | |
c317d3f2 SY |
758 | zlog_debug("%s: %s leftover qdisc: ifindex %d kind %s", |
759 | __func__, | |
760 | ((ret == ZEBRA_DPLANE_REQUEST_FAILURE) | |
761 | ? "Failed to remove" | |
762 | : "Removed"), | |
763 | qdisc.qdisc.ifindex, kind_str); | |
764 | } | |
765 | } | |
766 | ||
767 | return 0; | |
449a30ed SY |
768 | } |
769 | ||
c317d3f2 | 770 | int netlink_tclass_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) |
449a30ed | 771 | { |
c317d3f2 SY |
772 | struct tcmsg *tcm; |
773 | ||
774 | int len; | |
775 | struct rtattr *tb[TCA_MAX + 1]; | |
776 | ||
777 | frrtrace(3, frr_zebra, netlink_tc_class_change, h, ns_id, startup); | |
778 | ||
779 | len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct tcmsg)); | |
780 | ||
781 | if (len < 0) { | |
782 | zlog_err( | |
783 | "%s: Message received from netlink is of a broken size %d %zu", | |
784 | __func__, h->nlmsg_len, | |
785 | (size_t)NLMSG_LENGTH(sizeof(struct tcmsg))); | |
786 | return -1; | |
787 | } | |
788 | ||
789 | tcm = NLMSG_DATA(h); | |
790 | netlink_parse_rtattr(tb, TCA_MAX, TCA_RTA(tcm), len); | |
791 | ||
792 | ||
793 | if (tb[TCA_OPTIONS] != NULL) { | |
794 | struct rtattr *options[TCA_HTB_MAX + 1]; | |
795 | ||
796 | netlink_parse_rtattr_nested(options, TCA_HTB_MAX, | |
797 | tb[TCA_OPTIONS]); | |
798 | ||
799 | /* TODO: more details */ | |
800 | /* struct tc_htb_opt *opt = RTA_DATA(options[TCA_HTB_PARMS]); */ | |
801 | } | |
802 | ||
803 | return 0; | |
449a30ed SY |
804 | } |
805 | ||
c317d3f2 | 806 | int netlink_tfilter_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) |
449a30ed | 807 | { |
c317d3f2 SY |
808 | struct tcmsg *tcm; |
809 | ||
810 | int len; | |
811 | struct rtattr *tb[TCA_MAX + 1]; | |
812 | ||
813 | frrtrace(3, frr_zebra, netlink_tc_filter_change, h, ns_id, startup); | |
814 | ||
815 | len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct tcmsg)); | |
816 | ||
817 | if (len < 0) { | |
818 | zlog_err( | |
819 | "%s: Message received from netlink is of a broken size %d %zu", | |
820 | __func__, h->nlmsg_len, | |
821 | (size_t)NLMSG_LENGTH(sizeof(struct tcmsg))); | |
822 | return -1; | |
823 | } | |
824 | ||
825 | tcm = NLMSG_DATA(h); | |
826 | netlink_parse_rtattr(tb, TCA_MAX, TCA_RTA(tcm), len); | |
827 | ||
828 | return 0; | |
449a30ed SY |
829 | } |
830 | ||
c317d3f2 | 831 | int netlink_qdisc_read(struct zebra_ns *zns) |
449a30ed | 832 | { |
c317d3f2 SY |
833 | int ret; |
834 | struct zebra_dplane_info dp_info; | |
835 | ||
836 | zebra_dplane_info_from_zns(&dp_info, zns, true); | |
837 | ||
838 | ret = netlink_request_qdiscs(zns, AF_UNSPEC, RTM_GETQDISC); | |
839 | if (ret < 0) | |
840 | return ret; | |
841 | ||
842 | ret = netlink_parse_info(netlink_qdisc_change, &zns->netlink_cmd, | |
843 | &dp_info, 0, true); | |
844 | if (ret < 0) | |
845 | return ret; | |
846 | ||
847 | return 0; | |
449a30ed SY |
848 | } |
849 | ||
c317d3f2 | 850 | int netlink_tfilter_read_for_interface(struct zebra_ns *zns, ifindex_t ifindex) |
449a30ed | 851 | { |
c317d3f2 SY |
852 | int ret; |
853 | struct zebra_dplane_info dp_info; | |
854 | ||
855 | zebra_dplane_info_from_zns(&dp_info, zns, true); | |
449a30ed | 856 | |
c317d3f2 SY |
857 | ret = netlink_request_filters(zns, AF_UNSPEC, RTM_GETTFILTER, ifindex); |
858 | if (ret < 0) | |
859 | return ret; | |
860 | ||
861 | ret = netlink_parse_info(netlink_tfilter_change, &zns->netlink_cmd, | |
862 | &dp_info, 0, true); | |
863 | if (ret < 0) | |
864 | return ret; | |
865 | ||
866 | return 0; | |
449a30ed SY |
867 | } |
868 | ||
869 | #endif /* HAVE_NETLINK */ |