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