1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
2 | // Copyright (C) 2018 Facebook |
3 | |
4 | #ifndef _GNU_SOURCE |
5 | #define _GNU_SOURCE |
6 | #endif |
7 | #include <errno.h> |
8 | #include <fcntl.h> |
9 | #include <stdlib.h> |
10 | #include <string.h> |
11 | #include <time.h> |
12 | #include <unistd.h> |
13 | #include <bpf/bpf.h> |
14 | #include <bpf/libbpf.h> |
15 | #include <net/if.h> |
16 | #include <linux/rtnetlink.h> |
17 | #include <linux/socket.h> |
18 | #include <linux/tc_act/tc_bpf.h> |
19 | #include <sys/socket.h> |
20 | #include <sys/stat.h> |
21 | #include <sys/types.h> |
22 | |
23 | #include "bpf/nlattr.h" |
24 | #include "main.h" |
25 | #include "netlink_dumper.h" |
26 | |
27 | #ifndef SOL_NETLINK |
28 | #define SOL_NETLINK 270 |
29 | #endif |
30 | |
31 | struct ip_devname_ifindex { |
32 | char devname[64]; |
33 | int ifindex; |
34 | }; |
35 | |
36 | struct bpf_netdev_t { |
37 | struct ip_devname_ifindex *devices; |
38 | int used_len; |
39 | int array_len; |
40 | int filter_idx; |
41 | }; |
42 | |
43 | struct tc_kind_handle { |
44 | char kind[64]; |
45 | int handle; |
46 | }; |
47 | |
48 | struct bpf_tcinfo_t { |
49 | struct tc_kind_handle *handle_array; |
50 | int used_len; |
51 | int array_len; |
52 | bool is_qdisc; |
53 | }; |
54 | |
55 | struct bpf_filter_t { |
56 | const char *kind; |
57 | const char *devname; |
58 | int ifindex; |
59 | }; |
60 | |
61 | struct bpf_attach_info { |
62 | __u32 flow_dissector_id; |
63 | }; |
64 | |
65 | enum net_attach_type { |
66 | NET_ATTACH_TYPE_XDP, |
67 | NET_ATTACH_TYPE_XDP_GENERIC, |
68 | NET_ATTACH_TYPE_XDP_DRIVER, |
69 | NET_ATTACH_TYPE_XDP_OFFLOAD, |
70 | }; |
71 | |
72 | static const char * const attach_type_strings[] = { |
73 | [NET_ATTACH_TYPE_XDP] = "xdp" , |
74 | [NET_ATTACH_TYPE_XDP_GENERIC] = "xdpgeneric" , |
75 | [NET_ATTACH_TYPE_XDP_DRIVER] = "xdpdrv" , |
76 | [NET_ATTACH_TYPE_XDP_OFFLOAD] = "xdpoffload" , |
77 | }; |
78 | |
79 | static const char * const attach_loc_strings[] = { |
80 | [BPF_TCX_INGRESS] = "tcx/ingress" , |
81 | [BPF_TCX_EGRESS] = "tcx/egress" , |
82 | [BPF_NETKIT_PRIMARY] = "netkit/primary" , |
83 | [BPF_NETKIT_PEER] = "netkit/peer" , |
84 | }; |
85 | |
86 | const size_t net_attach_type_size = ARRAY_SIZE(attach_type_strings); |
87 | |
88 | static enum net_attach_type parse_attach_type(const char *str) |
89 | { |
90 | enum net_attach_type type; |
91 | |
92 | for (type = 0; type < net_attach_type_size; type++) { |
93 | if (attach_type_strings[type] && |
94 | is_prefix(pfx: str, str: attach_type_strings[type])) |
95 | return type; |
96 | } |
97 | |
98 | return net_attach_type_size; |
99 | } |
100 | |
101 | typedef int (*dump_nlmsg_t)(void *cookie, void *msg, struct nlattr **tb); |
102 | |
103 | typedef int (*__dump_nlmsg_t)(struct nlmsghdr *nlmsg, dump_nlmsg_t, void *cookie); |
104 | |
105 | static int netlink_open(__u32 *nl_pid) |
106 | { |
107 | struct sockaddr_nl sa; |
108 | socklen_t addrlen; |
109 | int one = 1, ret; |
110 | int sock; |
111 | |
112 | memset(&sa, 0, sizeof(sa)); |
113 | sa.nl_family = AF_NETLINK; |
114 | |
115 | sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); |
116 | if (sock < 0) |
117 | return -errno; |
118 | |
119 | if (setsockopt(sock, SOL_NETLINK, NETLINK_EXT_ACK, |
120 | &one, sizeof(one)) < 0) { |
121 | p_err(fmt: "Netlink error reporting not supported" ); |
122 | } |
123 | |
124 | if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { |
125 | ret = -errno; |
126 | goto cleanup; |
127 | } |
128 | |
129 | addrlen = sizeof(sa); |
130 | if (getsockname(sock, (struct sockaddr *)&sa, &addrlen) < 0) { |
131 | ret = -errno; |
132 | goto cleanup; |
133 | } |
134 | |
135 | if (addrlen != sizeof(sa)) { |
136 | ret = -LIBBPF_ERRNO__INTERNAL; |
137 | goto cleanup; |
138 | } |
139 | |
140 | *nl_pid = sa.nl_pid; |
141 | return sock; |
142 | |
143 | cleanup: |
144 | close(sock); |
145 | return ret; |
146 | } |
147 | |
148 | static int netlink_recv(int sock, __u32 nl_pid, __u32 seq, |
149 | __dump_nlmsg_t _fn, dump_nlmsg_t fn, |
150 | void *cookie) |
151 | { |
152 | bool multipart = true; |
153 | struct nlmsgerr *err; |
154 | struct nlmsghdr *nh; |
155 | char buf[4096]; |
156 | int len, ret; |
157 | |
158 | while (multipart) { |
159 | multipart = false; |
160 | len = recv(sock, buf, sizeof(buf), 0); |
161 | if (len < 0) { |
162 | ret = -errno; |
163 | goto done; |
164 | } |
165 | |
166 | if (len == 0) |
167 | break; |
168 | |
169 | for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, (unsigned int)len); |
170 | nh = NLMSG_NEXT(nh, len)) { |
171 | if (nh->nlmsg_pid != nl_pid) { |
172 | ret = -LIBBPF_ERRNO__WRNGPID; |
173 | goto done; |
174 | } |
175 | if (nh->nlmsg_seq != seq) { |
176 | ret = -LIBBPF_ERRNO__INVSEQ; |
177 | goto done; |
178 | } |
179 | if (nh->nlmsg_flags & NLM_F_MULTI) |
180 | multipart = true; |
181 | switch (nh->nlmsg_type) { |
182 | case NLMSG_ERROR: |
183 | err = (struct nlmsgerr *)NLMSG_DATA(nh); |
184 | if (!err->error) |
185 | continue; |
186 | ret = err->error; |
187 | libbpf_nla_dump_errormsg(nh); |
188 | goto done; |
189 | case NLMSG_DONE: |
190 | return 0; |
191 | default: |
192 | break; |
193 | } |
194 | if (_fn) { |
195 | ret = _fn(nh, fn, cookie); |
196 | if (ret) |
197 | return ret; |
198 | } |
199 | } |
200 | } |
201 | ret = 0; |
202 | done: |
203 | return ret; |
204 | } |
205 | |
206 | static int __dump_class_nlmsg(struct nlmsghdr *nlh, |
207 | dump_nlmsg_t dump_class_nlmsg, |
208 | void *cookie) |
209 | { |
210 | struct nlattr *tb[TCA_MAX + 1], *attr; |
211 | struct tcmsg *t = NLMSG_DATA(nlh); |
212 | int len; |
213 | |
214 | len = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*t)); |
215 | attr = (struct nlattr *) ((void *) t + NLMSG_ALIGN(sizeof(*t))); |
216 | if (libbpf_nla_parse(tb, TCA_MAX, attr, len, NULL) != 0) |
217 | return -LIBBPF_ERRNO__NLPARSE; |
218 | |
219 | return dump_class_nlmsg(cookie, t, tb); |
220 | } |
221 | |
222 | static int netlink_get_class(int sock, unsigned int nl_pid, int ifindex, |
223 | dump_nlmsg_t dump_class_nlmsg, void *cookie) |
224 | { |
225 | struct { |
226 | struct nlmsghdr nlh; |
227 | struct tcmsg t; |
228 | } req = { |
229 | .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)), |
230 | .nlh.nlmsg_type = RTM_GETTCLASS, |
231 | .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, |
232 | .t.tcm_family = AF_UNSPEC, |
233 | .t.tcm_ifindex = ifindex, |
234 | }; |
235 | int seq = time(NULL); |
236 | |
237 | req.nlh.nlmsg_seq = seq; |
238 | if (send(sock, &req, req.nlh.nlmsg_len, 0) < 0) |
239 | return -errno; |
240 | |
241 | return netlink_recv(sock, nl_pid, seq, fn: __dump_class_nlmsg, |
242 | fn: dump_class_nlmsg, cookie); |
243 | } |
244 | |
245 | static int __dump_qdisc_nlmsg(struct nlmsghdr *nlh, |
246 | dump_nlmsg_t dump_qdisc_nlmsg, |
247 | void *cookie) |
248 | { |
249 | struct nlattr *tb[TCA_MAX + 1], *attr; |
250 | struct tcmsg *t = NLMSG_DATA(nlh); |
251 | int len; |
252 | |
253 | len = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*t)); |
254 | attr = (struct nlattr *) ((void *) t + NLMSG_ALIGN(sizeof(*t))); |
255 | if (libbpf_nla_parse(tb, TCA_MAX, attr, len, NULL) != 0) |
256 | return -LIBBPF_ERRNO__NLPARSE; |
257 | |
258 | return dump_qdisc_nlmsg(cookie, t, tb); |
259 | } |
260 | |
261 | static int netlink_get_qdisc(int sock, unsigned int nl_pid, int ifindex, |
262 | dump_nlmsg_t dump_qdisc_nlmsg, void *cookie) |
263 | { |
264 | struct { |
265 | struct nlmsghdr nlh; |
266 | struct tcmsg t; |
267 | } req = { |
268 | .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)), |
269 | .nlh.nlmsg_type = RTM_GETQDISC, |
270 | .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, |
271 | .t.tcm_family = AF_UNSPEC, |
272 | .t.tcm_ifindex = ifindex, |
273 | }; |
274 | int seq = time(NULL); |
275 | |
276 | req.nlh.nlmsg_seq = seq; |
277 | if (send(sock, &req, req.nlh.nlmsg_len, 0) < 0) |
278 | return -errno; |
279 | |
280 | return netlink_recv(sock, nl_pid, seq, fn: __dump_qdisc_nlmsg, |
281 | fn: dump_qdisc_nlmsg, cookie); |
282 | } |
283 | |
284 | static int __dump_filter_nlmsg(struct nlmsghdr *nlh, |
285 | dump_nlmsg_t dump_filter_nlmsg, |
286 | void *cookie) |
287 | { |
288 | struct nlattr *tb[TCA_MAX + 1], *attr; |
289 | struct tcmsg *t = NLMSG_DATA(nlh); |
290 | int len; |
291 | |
292 | len = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*t)); |
293 | attr = (struct nlattr *) ((void *) t + NLMSG_ALIGN(sizeof(*t))); |
294 | if (libbpf_nla_parse(tb, TCA_MAX, attr, len, NULL) != 0) |
295 | return -LIBBPF_ERRNO__NLPARSE; |
296 | |
297 | return dump_filter_nlmsg(cookie, t, tb); |
298 | } |
299 | |
300 | static int netlink_get_filter(int sock, unsigned int nl_pid, int ifindex, int handle, |
301 | dump_nlmsg_t dump_filter_nlmsg, void *cookie) |
302 | { |
303 | struct { |
304 | struct nlmsghdr nlh; |
305 | struct tcmsg t; |
306 | } req = { |
307 | .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)), |
308 | .nlh.nlmsg_type = RTM_GETTFILTER, |
309 | .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, |
310 | .t.tcm_family = AF_UNSPEC, |
311 | .t.tcm_ifindex = ifindex, |
312 | .t.tcm_parent = handle, |
313 | }; |
314 | int seq = time(NULL); |
315 | |
316 | req.nlh.nlmsg_seq = seq; |
317 | if (send(sock, &req, req.nlh.nlmsg_len, 0) < 0) |
318 | return -errno; |
319 | |
320 | return netlink_recv(sock, nl_pid, seq, fn: __dump_filter_nlmsg, |
321 | fn: dump_filter_nlmsg, cookie); |
322 | } |
323 | |
324 | static int __dump_link_nlmsg(struct nlmsghdr *nlh, |
325 | dump_nlmsg_t dump_link_nlmsg, void *cookie) |
326 | { |
327 | struct nlattr *tb[IFLA_MAX + 1], *attr; |
328 | struct ifinfomsg *ifi = NLMSG_DATA(nlh); |
329 | int len; |
330 | |
331 | len = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi)); |
332 | attr = (struct nlattr *) ((void *) ifi + NLMSG_ALIGN(sizeof(*ifi))); |
333 | if (libbpf_nla_parse(tb, IFLA_MAX, attr, len, NULL) != 0) |
334 | return -LIBBPF_ERRNO__NLPARSE; |
335 | |
336 | return dump_link_nlmsg(cookie, ifi, tb); |
337 | } |
338 | |
339 | static int netlink_get_link(int sock, unsigned int nl_pid, |
340 | dump_nlmsg_t dump_link_nlmsg, void *cookie) |
341 | { |
342 | struct { |
343 | struct nlmsghdr nlh; |
344 | struct ifinfomsg ifm; |
345 | } req = { |
346 | .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), |
347 | .nlh.nlmsg_type = RTM_GETLINK, |
348 | .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, |
349 | .ifm.ifi_family = AF_PACKET, |
350 | }; |
351 | int seq = time(NULL); |
352 | |
353 | req.nlh.nlmsg_seq = seq; |
354 | if (send(sock, &req, req.nlh.nlmsg_len, 0) < 0) |
355 | return -errno; |
356 | |
357 | return netlink_recv(sock, nl_pid, seq, fn: __dump_link_nlmsg, |
358 | fn: dump_link_nlmsg, cookie); |
359 | } |
360 | |
361 | static int dump_link_nlmsg(void *cookie, void *msg, struct nlattr **tb) |
362 | { |
363 | struct bpf_netdev_t *netinfo = cookie; |
364 | struct ifinfomsg *ifinfo = msg; |
365 | |
366 | if (netinfo->filter_idx > 0 && netinfo->filter_idx != ifinfo->ifi_index) |
367 | return 0; |
368 | |
369 | if (netinfo->used_len == netinfo->array_len) { |
370 | netinfo->devices = realloc(netinfo->devices, |
371 | (netinfo->array_len + 16) * |
372 | sizeof(struct ip_devname_ifindex)); |
373 | if (!netinfo->devices) |
374 | return -ENOMEM; |
375 | |
376 | netinfo->array_len += 16; |
377 | } |
378 | netinfo->devices[netinfo->used_len].ifindex = ifinfo->ifi_index; |
379 | snprintf(buf: netinfo->devices[netinfo->used_len].devname, |
380 | size: sizeof(netinfo->devices[netinfo->used_len].devname), |
381 | fmt: "%s" , |
382 | tb[IFLA_IFNAME] |
383 | ? libbpf_nla_getattr_str(tb[IFLA_IFNAME]) |
384 | : "" ); |
385 | netinfo->used_len++; |
386 | |
387 | return do_xdp_dump(ifinfo, tb); |
388 | } |
389 | |
390 | static int dump_class_qdisc_nlmsg(void *cookie, void *msg, struct nlattr **tb) |
391 | { |
392 | struct bpf_tcinfo_t *tcinfo = cookie; |
393 | struct tcmsg *info = msg; |
394 | |
395 | if (tcinfo->is_qdisc) { |
396 | /* skip clsact qdisc */ |
397 | if (tb[TCA_KIND] && |
398 | strcmp(libbpf_nla_data(tb[TCA_KIND]), "clsact" ) == 0) |
399 | return 0; |
400 | if (info->tcm_handle == 0) |
401 | return 0; |
402 | } |
403 | |
404 | if (tcinfo->used_len == tcinfo->array_len) { |
405 | tcinfo->handle_array = realloc(tcinfo->handle_array, |
406 | (tcinfo->array_len + 16) * sizeof(struct tc_kind_handle)); |
407 | if (!tcinfo->handle_array) |
408 | return -ENOMEM; |
409 | |
410 | tcinfo->array_len += 16; |
411 | } |
412 | tcinfo->handle_array[tcinfo->used_len].handle = info->tcm_handle; |
413 | snprintf(buf: tcinfo->handle_array[tcinfo->used_len].kind, |
414 | size: sizeof(tcinfo->handle_array[tcinfo->used_len].kind), |
415 | fmt: "%s" , |
416 | tb[TCA_KIND] |
417 | ? libbpf_nla_getattr_str(tb[TCA_KIND]) |
418 | : "unknown" ); |
419 | tcinfo->used_len++; |
420 | |
421 | return 0; |
422 | } |
423 | |
424 | static int dump_filter_nlmsg(void *cookie, void *msg, struct nlattr **tb) |
425 | { |
426 | const struct bpf_filter_t *filter_info = cookie; |
427 | |
428 | return do_filter_dump(ifinfo: (struct tcmsg *)msg, tb, kind: filter_info->kind, |
429 | devname: filter_info->devname, ifindex: filter_info->ifindex); |
430 | } |
431 | |
432 | static int __show_dev_tc_bpf_name(__u32 id, char *name, size_t len) |
433 | { |
434 | struct bpf_prog_info info = {}; |
435 | __u32 ilen = sizeof(info); |
436 | int fd, ret; |
437 | |
438 | fd = bpf_prog_get_fd_by_id(id); |
439 | if (fd < 0) |
440 | return fd; |
441 | ret = bpf_obj_get_info_by_fd(fd, &info, &ilen); |
442 | if (ret < 0) |
443 | goto out; |
444 | ret = -ENOENT; |
445 | if (info.name[0]) { |
446 | get_prog_full_name(prog_info: &info, prog_fd: fd, name_buff: name, buff_len: len); |
447 | ret = 0; |
448 | } |
449 | out: |
450 | close(fd); |
451 | return ret; |
452 | } |
453 | |
454 | static void __show_dev_tc_bpf(const struct ip_devname_ifindex *dev, |
455 | const enum bpf_attach_type loc) |
456 | { |
457 | __u32 prog_flags[64] = {}, link_flags[64] = {}, i, j; |
458 | __u32 prog_ids[64] = {}, link_ids[64] = {}; |
459 | LIBBPF_OPTS(bpf_prog_query_opts, optq); |
460 | char prog_name[MAX_PROG_FULL_NAME]; |
461 | int ret; |
462 | |
463 | optq.prog_ids = prog_ids; |
464 | optq.prog_attach_flags = prog_flags; |
465 | optq.link_ids = link_ids; |
466 | optq.link_attach_flags = link_flags; |
467 | optq.count = ARRAY_SIZE(prog_ids); |
468 | |
469 | ret = bpf_prog_query_opts(dev->ifindex, loc, &optq); |
470 | if (ret) |
471 | return; |
472 | for (i = 0; i < optq.count; i++) { |
473 | NET_START_OBJECT; |
474 | NET_DUMP_STR("devname" , "%s" , dev->devname); |
475 | NET_DUMP_UINT("ifindex" , "(%u)" , dev->ifindex); |
476 | NET_DUMP_STR("kind" , " %s" , attach_loc_strings[loc]); |
477 | ret = __show_dev_tc_bpf_name(id: prog_ids[i], name: prog_name, |
478 | len: sizeof(prog_name)); |
479 | if (!ret) |
480 | NET_DUMP_STR("name" , " %s" , prog_name); |
481 | NET_DUMP_UINT("prog_id" , " prog_id %u " , prog_ids[i]); |
482 | if (prog_flags[i] || json_output) { |
483 | NET_START_ARRAY("prog_flags" , "%s " ); |
484 | for (j = 0; prog_flags[i] && j < 32; j++) { |
485 | if (!(prog_flags[i] & (1 << j))) |
486 | continue; |
487 | NET_DUMP_UINT_ONLY(1 << j); |
488 | } |
489 | NET_END_ARRAY("" ); |
490 | } |
491 | if (link_ids[i] || json_output) { |
492 | NET_DUMP_UINT("link_id" , "link_id %u " , link_ids[i]); |
493 | if (link_flags[i] || json_output) { |
494 | NET_START_ARRAY("link_flags" , "%s " ); |
495 | for (j = 0; link_flags[i] && j < 32; j++) { |
496 | if (!(link_flags[i] & (1 << j))) |
497 | continue; |
498 | NET_DUMP_UINT_ONLY(1 << j); |
499 | } |
500 | NET_END_ARRAY("" ); |
501 | } |
502 | } |
503 | NET_END_OBJECT_FINAL; |
504 | } |
505 | } |
506 | |
507 | static void show_dev_tc_bpf(struct ip_devname_ifindex *dev) |
508 | { |
509 | __show_dev_tc_bpf(dev, loc: BPF_TCX_INGRESS); |
510 | __show_dev_tc_bpf(dev, loc: BPF_TCX_EGRESS); |
511 | |
512 | __show_dev_tc_bpf(dev, loc: BPF_NETKIT_PRIMARY); |
513 | __show_dev_tc_bpf(dev, loc: BPF_NETKIT_PEER); |
514 | } |
515 | |
516 | static int show_dev_tc_bpf_classic(int sock, unsigned int nl_pid, |
517 | struct ip_devname_ifindex *dev) |
518 | { |
519 | struct bpf_filter_t filter_info; |
520 | struct bpf_tcinfo_t tcinfo; |
521 | int i, handle, ret = 0; |
522 | |
523 | tcinfo.handle_array = NULL; |
524 | tcinfo.used_len = 0; |
525 | tcinfo.array_len = 0; |
526 | |
527 | tcinfo.is_qdisc = false; |
528 | ret = netlink_get_class(sock, nl_pid, ifindex: dev->ifindex, |
529 | dump_class_nlmsg: dump_class_qdisc_nlmsg, cookie: &tcinfo); |
530 | if (ret) |
531 | goto out; |
532 | |
533 | tcinfo.is_qdisc = true; |
534 | ret = netlink_get_qdisc(sock, nl_pid, ifindex: dev->ifindex, |
535 | dump_qdisc_nlmsg: dump_class_qdisc_nlmsg, cookie: &tcinfo); |
536 | if (ret) |
537 | goto out; |
538 | |
539 | filter_info.devname = dev->devname; |
540 | filter_info.ifindex = dev->ifindex; |
541 | for (i = 0; i < tcinfo.used_len; i++) { |
542 | filter_info.kind = tcinfo.handle_array[i].kind; |
543 | ret = netlink_get_filter(sock, nl_pid, ifindex: dev->ifindex, |
544 | handle: tcinfo.handle_array[i].handle, |
545 | dump_filter_nlmsg, cookie: &filter_info); |
546 | if (ret) |
547 | goto out; |
548 | } |
549 | |
550 | /* root, ingress and egress handle */ |
551 | handle = TC_H_ROOT; |
552 | filter_info.kind = "root" ; |
553 | ret = netlink_get_filter(sock, nl_pid, ifindex: dev->ifindex, handle, |
554 | dump_filter_nlmsg, cookie: &filter_info); |
555 | if (ret) |
556 | goto out; |
557 | |
558 | handle = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS); |
559 | filter_info.kind = "clsact/ingress" ; |
560 | ret = netlink_get_filter(sock, nl_pid, ifindex: dev->ifindex, handle, |
561 | dump_filter_nlmsg, cookie: &filter_info); |
562 | if (ret) |
563 | goto out; |
564 | |
565 | handle = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS); |
566 | filter_info.kind = "clsact/egress" ; |
567 | ret = netlink_get_filter(sock, nl_pid, ifindex: dev->ifindex, handle, |
568 | dump_filter_nlmsg, cookie: &filter_info); |
569 | if (ret) |
570 | goto out; |
571 | |
572 | out: |
573 | free(tcinfo.handle_array); |
574 | return 0; |
575 | } |
576 | |
577 | static int query_flow_dissector(struct bpf_attach_info *attach_info) |
578 | { |
579 | __u32 attach_flags; |
580 | __u32 prog_ids[1]; |
581 | __u32 prog_cnt; |
582 | int err; |
583 | int fd; |
584 | |
585 | fd = open("/proc/self/ns/net" , O_RDONLY); |
586 | if (fd < 0) { |
587 | p_err("can't open /proc/self/ns/net: %s" , |
588 | strerror(errno)); |
589 | return -1; |
590 | } |
591 | prog_cnt = ARRAY_SIZE(prog_ids); |
592 | err = bpf_prog_query(fd, BPF_FLOW_DISSECTOR, 0, |
593 | &attach_flags, prog_ids, &prog_cnt); |
594 | close(fd); |
595 | if (err) { |
596 | if (errno == EINVAL) { |
597 | /* Older kernel's don't support querying |
598 | * flow dissector programs. |
599 | */ |
600 | errno = 0; |
601 | return 0; |
602 | } |
603 | p_err("can't query prog: %s" , strerror(errno)); |
604 | return -1; |
605 | } |
606 | |
607 | if (prog_cnt == 1) |
608 | attach_info->flow_dissector_id = prog_ids[0]; |
609 | |
610 | return 0; |
611 | } |
612 | |
613 | static int net_parse_dev(int *argc, char ***argv) |
614 | { |
615 | int ifindex; |
616 | |
617 | if (is_prefix(pfx: **argv, str: "dev" )) { |
618 | NEXT_ARGP(); |
619 | |
620 | ifindex = if_nametoindex(**argv); |
621 | if (!ifindex) |
622 | p_err(fmt: "invalid devname %s" , **argv); |
623 | |
624 | NEXT_ARGP(); |
625 | } else { |
626 | p_err(fmt: "expected 'dev', got: '%s'?" , **argv); |
627 | return -1; |
628 | } |
629 | |
630 | return ifindex; |
631 | } |
632 | |
633 | static int do_attach_detach_xdp(int progfd, enum net_attach_type attach_type, |
634 | int ifindex, bool overwrite) |
635 | { |
636 | __u32 flags = 0; |
637 | |
638 | if (!overwrite) |
639 | flags = XDP_FLAGS_UPDATE_IF_NOEXIST; |
640 | if (attach_type == NET_ATTACH_TYPE_XDP_GENERIC) |
641 | flags |= XDP_FLAGS_SKB_MODE; |
642 | if (attach_type == NET_ATTACH_TYPE_XDP_DRIVER) |
643 | flags |= XDP_FLAGS_DRV_MODE; |
644 | if (attach_type == NET_ATTACH_TYPE_XDP_OFFLOAD) |
645 | flags |= XDP_FLAGS_HW_MODE; |
646 | |
647 | return bpf_xdp_attach(ifindex, progfd, flags, NULL); |
648 | } |
649 | |
650 | static int do_attach(int argc, char **argv) |
651 | { |
652 | enum net_attach_type attach_type; |
653 | int progfd, ifindex, err = 0; |
654 | bool overwrite = false; |
655 | |
656 | /* parse attach args */ |
657 | if (!REQ_ARGS(5)) |
658 | return -EINVAL; |
659 | |
660 | attach_type = parse_attach_type(str: *argv); |
661 | if (attach_type == net_attach_type_size) { |
662 | p_err(fmt: "invalid net attach/detach type: %s" , *argv); |
663 | return -EINVAL; |
664 | } |
665 | NEXT_ARG(); |
666 | |
667 | progfd = prog_parse_fd(argc: &argc, argv: &argv); |
668 | if (progfd < 0) |
669 | return -EINVAL; |
670 | |
671 | ifindex = net_parse_dev(argc: &argc, argv: &argv); |
672 | if (ifindex < 1) { |
673 | err = -EINVAL; |
674 | goto cleanup; |
675 | } |
676 | |
677 | if (argc) { |
678 | if (is_prefix(pfx: *argv, str: "overwrite" )) { |
679 | overwrite = true; |
680 | } else { |
681 | p_err(fmt: "expected 'overwrite', got: '%s'?" , *argv); |
682 | err = -EINVAL; |
683 | goto cleanup; |
684 | } |
685 | } |
686 | |
687 | /* attach xdp prog */ |
688 | if (is_prefix(pfx: "xdp" , str: attach_type_strings[attach_type])) |
689 | err = do_attach_detach_xdp(progfd, attach_type, ifindex, |
690 | overwrite); |
691 | if (err) { |
692 | p_err(fmt: "interface %s attach failed: %s" , |
693 | attach_type_strings[attach_type], strerror(-err)); |
694 | goto cleanup; |
695 | } |
696 | |
697 | if (json_output) |
698 | jsonw_null(self: json_wtr); |
699 | cleanup: |
700 | close(progfd); |
701 | return err; |
702 | } |
703 | |
704 | static int do_detach(int argc, char **argv) |
705 | { |
706 | enum net_attach_type attach_type; |
707 | int progfd, ifindex, err = 0; |
708 | |
709 | /* parse detach args */ |
710 | if (!REQ_ARGS(3)) |
711 | return -EINVAL; |
712 | |
713 | attach_type = parse_attach_type(str: *argv); |
714 | if (attach_type == net_attach_type_size) { |
715 | p_err(fmt: "invalid net attach/detach type: %s" , *argv); |
716 | return -EINVAL; |
717 | } |
718 | NEXT_ARG(); |
719 | |
720 | ifindex = net_parse_dev(argc: &argc, argv: &argv); |
721 | if (ifindex < 1) |
722 | return -EINVAL; |
723 | |
724 | /* detach xdp prog */ |
725 | progfd = -1; |
726 | if (is_prefix(pfx: "xdp" , str: attach_type_strings[attach_type])) |
727 | err = do_attach_detach_xdp(progfd, attach_type, ifindex, NULL); |
728 | |
729 | if (err < 0) { |
730 | p_err(fmt: "interface %s detach failed: %s" , |
731 | attach_type_strings[attach_type], strerror(-err)); |
732 | return err; |
733 | } |
734 | |
735 | if (json_output) |
736 | jsonw_null(self: json_wtr); |
737 | |
738 | return 0; |
739 | } |
740 | |
741 | static int netfilter_link_compar(const void *a, const void *b) |
742 | { |
743 | const struct bpf_link_info *nfa = a; |
744 | const struct bpf_link_info *nfb = b; |
745 | int delta; |
746 | |
747 | delta = nfa->netfilter.pf - nfb->netfilter.pf; |
748 | if (delta) |
749 | return delta; |
750 | |
751 | delta = nfa->netfilter.hooknum - nfb->netfilter.hooknum; |
752 | if (delta) |
753 | return delta; |
754 | |
755 | if (nfa->netfilter.priority < nfb->netfilter.priority) |
756 | return -1; |
757 | if (nfa->netfilter.priority > nfb->netfilter.priority) |
758 | return 1; |
759 | |
760 | return nfa->netfilter.flags - nfb->netfilter.flags; |
761 | } |
762 | |
763 | static void show_link_netfilter(void) |
764 | { |
765 | unsigned int nf_link_len = 0, nf_link_count = 0; |
766 | struct bpf_link_info *nf_link_info = NULL; |
767 | __u32 id = 0; |
768 | |
769 | while (true) { |
770 | struct bpf_link_info info; |
771 | int fd, err; |
772 | __u32 len; |
773 | |
774 | err = bpf_link_get_next_id(id, &id); |
775 | if (err) { |
776 | if (errno == ENOENT) |
777 | break; |
778 | p_err("can't get next link: %s (id %d)" , strerror(errno), id); |
779 | break; |
780 | } |
781 | |
782 | fd = bpf_link_get_fd_by_id(id); |
783 | if (fd < 0) { |
784 | p_err("can't get link by id (%u): %s" , id, strerror(errno)); |
785 | continue; |
786 | } |
787 | |
788 | memset(&info, 0, sizeof(info)); |
789 | len = sizeof(info); |
790 | |
791 | err = bpf_link_get_info_by_fd(fd, &info, &len); |
792 | |
793 | close(fd); |
794 | |
795 | if (err) { |
796 | p_err("can't get link info for fd %d: %s" , fd, strerror(errno)); |
797 | continue; |
798 | } |
799 | |
800 | if (info.type != BPF_LINK_TYPE_NETFILTER) |
801 | continue; |
802 | |
803 | if (nf_link_count >= nf_link_len) { |
804 | static const unsigned int max_link_count = INT_MAX / sizeof(info); |
805 | struct bpf_link_info *expand; |
806 | |
807 | if (nf_link_count > max_link_count) { |
808 | p_err(fmt: "cannot handle more than %u links\n" , max_link_count); |
809 | break; |
810 | } |
811 | |
812 | nf_link_len += 16; |
813 | |
814 | expand = realloc(nf_link_info, nf_link_len * sizeof(info)); |
815 | if (!expand) { |
816 | p_err("realloc: %s" , strerror(errno)); |
817 | break; |
818 | } |
819 | |
820 | nf_link_info = expand; |
821 | } |
822 | |
823 | nf_link_info[nf_link_count] = info; |
824 | nf_link_count++; |
825 | } |
826 | |
827 | qsort(nf_link_info, nf_link_count, sizeof(*nf_link_info), netfilter_link_compar); |
828 | |
829 | for (id = 0; id < nf_link_count; id++) { |
830 | NET_START_OBJECT; |
831 | if (json_output) |
832 | netfilter_dump_json(info: &nf_link_info[id], wtr: json_wtr); |
833 | else |
834 | netfilter_dump_plain(info: &nf_link_info[id]); |
835 | |
836 | NET_DUMP_UINT("id" , " prog_id %u" , nf_link_info[id].prog_id); |
837 | NET_END_OBJECT; |
838 | } |
839 | |
840 | free(nf_link_info); |
841 | } |
842 | |
843 | static int do_show(int argc, char **argv) |
844 | { |
845 | struct bpf_attach_info attach_info = {}; |
846 | int i, sock, ret, filter_idx = -1; |
847 | struct bpf_netdev_t dev_array; |
848 | unsigned int nl_pid = 0; |
849 | char err_buf[256]; |
850 | |
851 | if (argc == 2) { |
852 | filter_idx = net_parse_dev(argc: &argc, argv: &argv); |
853 | if (filter_idx < 1) |
854 | return -1; |
855 | } else if (argc != 0) { |
856 | usage(); |
857 | } |
858 | |
859 | ret = query_flow_dissector(attach_info: &attach_info); |
860 | if (ret) |
861 | return -1; |
862 | |
863 | sock = netlink_open(nl_pid: &nl_pid); |
864 | if (sock < 0) { |
865 | fprintf(stderr, "failed to open netlink sock\n" ); |
866 | return -1; |
867 | } |
868 | |
869 | dev_array.devices = NULL; |
870 | dev_array.used_len = 0; |
871 | dev_array.array_len = 0; |
872 | dev_array.filter_idx = filter_idx; |
873 | |
874 | if (json_output) |
875 | jsonw_start_array(self: json_wtr); |
876 | NET_START_OBJECT; |
877 | NET_START_ARRAY("xdp" , "%s:\n" ); |
878 | ret = netlink_get_link(sock, nl_pid, dump_link_nlmsg, cookie: &dev_array); |
879 | NET_END_ARRAY("\n" ); |
880 | |
881 | if (!ret) { |
882 | NET_START_ARRAY("tc" , "%s:\n" ); |
883 | for (i = 0; i < dev_array.used_len; i++) { |
884 | show_dev_tc_bpf(dev: &dev_array.devices[i]); |
885 | ret = show_dev_tc_bpf_classic(sock, nl_pid, |
886 | dev: &dev_array.devices[i]); |
887 | if (ret) |
888 | break; |
889 | } |
890 | NET_END_ARRAY("\n" ); |
891 | } |
892 | |
893 | NET_START_ARRAY("flow_dissector" , "%s:\n" ); |
894 | if (attach_info.flow_dissector_id > 0) |
895 | NET_DUMP_UINT("id" , "id %u" , attach_info.flow_dissector_id); |
896 | NET_END_ARRAY("\n" ); |
897 | |
898 | NET_START_ARRAY("netfilter" , "%s:\n" ); |
899 | show_link_netfilter(); |
900 | NET_END_ARRAY("\n" ); |
901 | |
902 | NET_END_OBJECT; |
903 | if (json_output) |
904 | jsonw_end_array(self: json_wtr); |
905 | |
906 | if (ret) { |
907 | if (json_output) |
908 | jsonw_null(self: json_wtr); |
909 | libbpf_strerror(ret, err_buf, sizeof(err_buf)); |
910 | fprintf(stderr, "Error: %s\n" , err_buf); |
911 | } |
912 | free(dev_array.devices); |
913 | close(sock); |
914 | return ret; |
915 | } |
916 | |
917 | static int do_help(int argc, char **argv) |
918 | { |
919 | if (json_output) { |
920 | jsonw_null(self: json_wtr); |
921 | return 0; |
922 | } |
923 | |
924 | fprintf(stderr, |
925 | "Usage: %1$s %2$s { show | list } [dev <devname>]\n" |
926 | " %1$s %2$s attach ATTACH_TYPE PROG dev <devname> [ overwrite ]\n" |
927 | " %1$s %2$s detach ATTACH_TYPE dev <devname>\n" |
928 | " %1$s %2$s help\n" |
929 | "\n" |
930 | " " HELP_SPEC_PROGRAM "\n" |
931 | " ATTACH_TYPE := { xdp | xdpgeneric | xdpdrv | xdpoffload }\n" |
932 | " " HELP_SPEC_OPTIONS " }\n" |
933 | "\n" |
934 | "Note: Only xdp, tcx, tc, netkit, flow_dissector and netfilter attachments\n" |
935 | " are currently supported.\n" |
936 | " For progs attached to cgroups, use \"bpftool cgroup\"\n" |
937 | " to dump program attachments. For program types\n" |
938 | " sk_{filter,skb,msg,reuseport} and lwt/seg6, please\n" |
939 | " consult iproute2.\n" |
940 | "" , |
941 | bin_name, argv[-2]); |
942 | |
943 | return 0; |
944 | } |
945 | |
946 | static const struct cmd cmds[] = { |
947 | { "show" , do_show }, |
948 | { "list" , do_show }, |
949 | { "attach" , do_attach }, |
950 | { "detach" , do_detach }, |
951 | { "help" , do_help }, |
952 | { 0 } |
953 | }; |
954 | |
955 | int do_net(int argc, char **argv) |
956 | { |
957 | return cmd_select(cmds, argc, argv, help: do_help); |
958 | } |
959 | |