xref: /openbmc/linux/tools/lib/bpf/netlink.c (revision f7af616c)
1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2 /* Copyright (c) 2018 Facebook */
3 
4 #include <stdlib.h>
5 #include <memory.h>
6 #include <unistd.h>
7 #include <arpa/inet.h>
8 #include <linux/bpf.h>
9 #include <linux/if_ether.h>
10 #include <linux/pkt_cls.h>
11 #include <linux/rtnetlink.h>
12 #include <sys/socket.h>
13 #include <errno.h>
14 #include <time.h>
15 
16 #include "bpf.h"
17 #include "libbpf.h"
18 #include "libbpf_internal.h"
19 #include "nlattr.h"
20 
21 #ifndef SOL_NETLINK
22 #define SOL_NETLINK 270
23 #endif
24 
25 typedef int (*libbpf_dump_nlmsg_t)(void *cookie, void *msg, struct nlattr **tb);
26 
27 typedef int (*__dump_nlmsg_t)(struct nlmsghdr *nlmsg, libbpf_dump_nlmsg_t,
28 			      void *cookie);
29 
30 struct xdp_id_md {
31 	int ifindex;
32 	__u32 flags;
33 	struct xdp_link_info info;
34 };
35 
36 static int libbpf_netlink_open(__u32 *nl_pid)
37 {
38 	struct sockaddr_nl sa;
39 	socklen_t addrlen;
40 	int one = 1, ret;
41 	int sock;
42 
43 	memset(&sa, 0, sizeof(sa));
44 	sa.nl_family = AF_NETLINK;
45 
46 	sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
47 	if (sock < 0)
48 		return -errno;
49 
50 	if (setsockopt(sock, SOL_NETLINK, NETLINK_EXT_ACK,
51 		       &one, sizeof(one)) < 0) {
52 		pr_warn("Netlink error reporting not supported\n");
53 	}
54 
55 	if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
56 		ret = -errno;
57 		goto cleanup;
58 	}
59 
60 	addrlen = sizeof(sa);
61 	if (getsockname(sock, (struct sockaddr *)&sa, &addrlen) < 0) {
62 		ret = -errno;
63 		goto cleanup;
64 	}
65 
66 	if (addrlen != sizeof(sa)) {
67 		ret = -LIBBPF_ERRNO__INTERNAL;
68 		goto cleanup;
69 	}
70 
71 	*nl_pid = sa.nl_pid;
72 	return sock;
73 
74 cleanup:
75 	close(sock);
76 	return ret;
77 }
78 
79 static void libbpf_netlink_close(int sock)
80 {
81 	close(sock);
82 }
83 
84 enum {
85 	NL_CONT,
86 	NL_NEXT,
87 	NL_DONE,
88 };
89 
90 static int libbpf_netlink_recv(int sock, __u32 nl_pid, int seq,
91 			       __dump_nlmsg_t _fn, libbpf_dump_nlmsg_t fn,
92 			       void *cookie)
93 {
94 	bool multipart = true;
95 	struct nlmsgerr *err;
96 	struct nlmsghdr *nh;
97 	char buf[4096];
98 	int len, ret;
99 
100 	while (multipart) {
101 start:
102 		multipart = false;
103 		len = recv(sock, buf, sizeof(buf), 0);
104 		if (len < 0) {
105 			ret = -errno;
106 			goto done;
107 		}
108 
109 		if (len == 0)
110 			break;
111 
112 		for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len);
113 		     nh = NLMSG_NEXT(nh, len)) {
114 			if (nh->nlmsg_pid != nl_pid) {
115 				ret = -LIBBPF_ERRNO__WRNGPID;
116 				goto done;
117 			}
118 			if (nh->nlmsg_seq != seq) {
119 				ret = -LIBBPF_ERRNO__INVSEQ;
120 				goto done;
121 			}
122 			if (nh->nlmsg_flags & NLM_F_MULTI)
123 				multipart = true;
124 			switch (nh->nlmsg_type) {
125 			case NLMSG_ERROR:
126 				err = (struct nlmsgerr *)NLMSG_DATA(nh);
127 				if (!err->error)
128 					continue;
129 				ret = err->error;
130 				libbpf_nla_dump_errormsg(nh);
131 				goto done;
132 			case NLMSG_DONE:
133 				return 0;
134 			default:
135 				break;
136 			}
137 			if (_fn) {
138 				ret = _fn(nh, fn, cookie);
139 				switch (ret) {
140 				case NL_CONT:
141 					break;
142 				case NL_NEXT:
143 					goto start;
144 				case NL_DONE:
145 					return 0;
146 				default:
147 					return ret;
148 				}
149 			}
150 		}
151 	}
152 	ret = 0;
153 done:
154 	return ret;
155 }
156 
157 static int libbpf_netlink_send_recv(struct nlmsghdr *nh,
158 				    __dump_nlmsg_t parse_msg,
159 				    libbpf_dump_nlmsg_t parse_attr,
160 				    void *cookie)
161 {
162 	__u32 nl_pid = 0;
163 	int sock, ret;
164 
165 	sock = libbpf_netlink_open(&nl_pid);
166 	if (sock < 0)
167 		return sock;
168 
169 	nh->nlmsg_pid = 0;
170 	nh->nlmsg_seq = time(NULL);
171 
172 	if (send(sock, nh, nh->nlmsg_len, 0) < 0) {
173 		ret = -errno;
174 		goto out;
175 	}
176 
177 	ret = libbpf_netlink_recv(sock, nl_pid, nh->nlmsg_seq,
178 				  parse_msg, parse_attr, cookie);
179 out:
180 	libbpf_netlink_close(sock);
181 	return ret;
182 }
183 
184 static int __bpf_set_link_xdp_fd_replace(int ifindex, int fd, int old_fd,
185 					 __u32 flags)
186 {
187 	struct nlattr *nla;
188 	int ret;
189 	struct {
190 		struct nlmsghdr  nh;
191 		struct ifinfomsg ifinfo;
192 		char             attrbuf[64];
193 	} req;
194 
195 	memset(&req, 0, sizeof(req));
196 	req.nh.nlmsg_len      = NLMSG_LENGTH(sizeof(struct ifinfomsg));
197 	req.nh.nlmsg_flags    = NLM_F_REQUEST | NLM_F_ACK;
198 	req.nh.nlmsg_type     = RTM_SETLINK;
199 	req.ifinfo.ifi_family = AF_UNSPEC;
200 	req.ifinfo.ifi_index  = ifindex;
201 
202 	nla = nlattr_begin_nested(&req.nh, sizeof(req), IFLA_XDP);
203 	if (!nla)
204 		return -EMSGSIZE;
205 	ret = nlattr_add(&req.nh, sizeof(req), IFLA_XDP_FD, &fd, sizeof(fd));
206 	if (ret < 0)
207 		return ret;
208 	if (flags) {
209 		ret = nlattr_add(&req.nh, sizeof(req), IFLA_XDP_FLAGS, &flags,
210 				 sizeof(flags));
211 		if (ret < 0)
212 			return ret;
213 	}
214 	if (flags & XDP_FLAGS_REPLACE) {
215 		ret = nlattr_add(&req.nh, sizeof(req), IFLA_XDP_EXPECTED_FD,
216 				 &old_fd, sizeof(old_fd));
217 		if (ret < 0)
218 			return ret;
219 	}
220 	nlattr_end_nested(&req.nh, nla);
221 
222 	return libbpf_netlink_send_recv(&req.nh, NULL, NULL, NULL);
223 }
224 
225 int bpf_set_link_xdp_fd_opts(int ifindex, int fd, __u32 flags,
226 			     const struct bpf_xdp_set_link_opts *opts)
227 {
228 	int old_fd = -1;
229 
230 	if (!OPTS_VALID(opts, bpf_xdp_set_link_opts))
231 		return -EINVAL;
232 
233 	if (OPTS_HAS(opts, old_fd)) {
234 		old_fd = OPTS_GET(opts, old_fd, -1);
235 		flags |= XDP_FLAGS_REPLACE;
236 	}
237 
238 	return __bpf_set_link_xdp_fd_replace(ifindex, fd, old_fd, flags);
239 }
240 
241 int bpf_set_link_xdp_fd(int ifindex, int fd, __u32 flags)
242 {
243 	return __bpf_set_link_xdp_fd_replace(ifindex, fd, 0, flags);
244 }
245 
246 static int __dump_link_nlmsg(struct nlmsghdr *nlh,
247 			     libbpf_dump_nlmsg_t dump_link_nlmsg, void *cookie)
248 {
249 	struct nlattr *tb[IFLA_MAX + 1], *attr;
250 	struct ifinfomsg *ifi = NLMSG_DATA(nlh);
251 	int len;
252 
253 	len = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
254 	attr = (struct nlattr *) ((void *) ifi + NLMSG_ALIGN(sizeof(*ifi)));
255 
256 	if (libbpf_nla_parse(tb, IFLA_MAX, attr, len, NULL) != 0)
257 		return -LIBBPF_ERRNO__NLPARSE;
258 
259 	return dump_link_nlmsg(cookie, ifi, tb);
260 }
261 
262 static int get_xdp_info(void *cookie, void *msg, struct nlattr **tb)
263 {
264 	struct nlattr *xdp_tb[IFLA_XDP_MAX + 1];
265 	struct xdp_id_md *xdp_id = cookie;
266 	struct ifinfomsg *ifinfo = msg;
267 	int ret;
268 
269 	if (xdp_id->ifindex && xdp_id->ifindex != ifinfo->ifi_index)
270 		return 0;
271 
272 	if (!tb[IFLA_XDP])
273 		return 0;
274 
275 	ret = libbpf_nla_parse_nested(xdp_tb, IFLA_XDP_MAX, tb[IFLA_XDP], NULL);
276 	if (ret)
277 		return ret;
278 
279 	if (!xdp_tb[IFLA_XDP_ATTACHED])
280 		return 0;
281 
282 	xdp_id->info.attach_mode = libbpf_nla_getattr_u8(
283 		xdp_tb[IFLA_XDP_ATTACHED]);
284 
285 	if (xdp_id->info.attach_mode == XDP_ATTACHED_NONE)
286 		return 0;
287 
288 	if (xdp_tb[IFLA_XDP_PROG_ID])
289 		xdp_id->info.prog_id = libbpf_nla_getattr_u32(
290 			xdp_tb[IFLA_XDP_PROG_ID]);
291 
292 	if (xdp_tb[IFLA_XDP_SKB_PROG_ID])
293 		xdp_id->info.skb_prog_id = libbpf_nla_getattr_u32(
294 			xdp_tb[IFLA_XDP_SKB_PROG_ID]);
295 
296 	if (xdp_tb[IFLA_XDP_DRV_PROG_ID])
297 		xdp_id->info.drv_prog_id = libbpf_nla_getattr_u32(
298 			xdp_tb[IFLA_XDP_DRV_PROG_ID]);
299 
300 	if (xdp_tb[IFLA_XDP_HW_PROG_ID])
301 		xdp_id->info.hw_prog_id = libbpf_nla_getattr_u32(
302 			xdp_tb[IFLA_XDP_HW_PROG_ID]);
303 
304 	return 0;
305 }
306 
307 int bpf_get_link_xdp_info(int ifindex, struct xdp_link_info *info,
308 			  size_t info_size, __u32 flags)
309 {
310 	struct xdp_id_md xdp_id = {};
311 	__u32 mask;
312 	int ret;
313 	struct {
314 		struct nlmsghdr  nh;
315 		struct ifinfomsg ifm;
316 	} req = {
317 		.nh.nlmsg_len   = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
318 		.nh.nlmsg_type  = RTM_GETLINK,
319 		.nh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
320 		.ifm.ifi_family = AF_PACKET,
321 	};
322 
323 	if (flags & ~XDP_FLAGS_MASK || !info_size)
324 		return -EINVAL;
325 
326 	/* Check whether the single {HW,DRV,SKB} mode is set */
327 	flags &= (XDP_FLAGS_SKB_MODE | XDP_FLAGS_DRV_MODE | XDP_FLAGS_HW_MODE);
328 	mask = flags - 1;
329 	if (flags && flags & mask)
330 		return -EINVAL;
331 
332 	xdp_id.ifindex = ifindex;
333 	xdp_id.flags = flags;
334 
335 	ret = libbpf_netlink_send_recv(&req.nh, __dump_link_nlmsg,
336 				       get_xdp_info, &xdp_id);
337 	if (!ret) {
338 		size_t sz = min(info_size, sizeof(xdp_id.info));
339 
340 		memcpy(info, &xdp_id.info, sz);
341 		memset((void *) info + sz, 0, info_size - sz);
342 	}
343 
344 	return ret;
345 }
346 
347 static __u32 get_xdp_id(struct xdp_link_info *info, __u32 flags)
348 {
349 	flags &= XDP_FLAGS_MODES;
350 
351 	if (info->attach_mode != XDP_ATTACHED_MULTI && !flags)
352 		return info->prog_id;
353 	if (flags & XDP_FLAGS_DRV_MODE)
354 		return info->drv_prog_id;
355 	if (flags & XDP_FLAGS_HW_MODE)
356 		return info->hw_prog_id;
357 	if (flags & XDP_FLAGS_SKB_MODE)
358 		return info->skb_prog_id;
359 
360 	return 0;
361 }
362 
363 int bpf_get_link_xdp_id(int ifindex, __u32 *prog_id, __u32 flags)
364 {
365 	struct xdp_link_info info;
366 	int ret;
367 
368 	ret = bpf_get_link_xdp_info(ifindex, &info, sizeof(info), flags);
369 	if (!ret)
370 		*prog_id = get_xdp_id(&info, flags);
371 
372 	return ret;
373 }
374 
375 typedef int (*qdisc_config_t)(struct nlmsghdr *nh, struct tcmsg *t,
376 			      size_t maxsz);
377 
378 static int clsact_config(struct nlmsghdr *nh, struct tcmsg *t, size_t maxsz)
379 {
380 	t->tcm_parent = TC_H_CLSACT;
381 	t->tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0);
382 
383 	return nlattr_add(nh, maxsz, TCA_KIND, "clsact", sizeof("clsact"));
384 }
385 
386 static int attach_point_to_config(struct bpf_tc_hook *hook,
387 				  qdisc_config_t *config)
388 {
389 	switch (OPTS_GET(hook, attach_point, 0)) {
390 	case BPF_TC_INGRESS:
391 	case BPF_TC_EGRESS:
392 	case BPF_TC_INGRESS | BPF_TC_EGRESS:
393 		if (OPTS_GET(hook, parent, 0))
394 			return -EINVAL;
395 		*config = &clsact_config;
396 		return 0;
397 	case BPF_TC_CUSTOM:
398 		return -EOPNOTSUPP;
399 	default:
400 		return -EINVAL;
401 	}
402 }
403 
404 static int tc_get_tcm_parent(enum bpf_tc_attach_point attach_point,
405 			     __u32 *parent)
406 {
407 	switch (attach_point) {
408 	case BPF_TC_INGRESS:
409 	case BPF_TC_EGRESS:
410 		if (*parent)
411 			return -EINVAL;
412 		*parent = TC_H_MAKE(TC_H_CLSACT,
413 				    attach_point == BPF_TC_INGRESS ?
414 				    TC_H_MIN_INGRESS : TC_H_MIN_EGRESS);
415 		break;
416 	case BPF_TC_CUSTOM:
417 		if (!*parent)
418 			return -EINVAL;
419 		break;
420 	default:
421 		return -EINVAL;
422 	}
423 	return 0;
424 }
425 
426 static int tc_qdisc_modify(struct bpf_tc_hook *hook, int cmd, int flags)
427 {
428 	qdisc_config_t config;
429 	int ret;
430 	struct {
431 		struct nlmsghdr nh;
432 		struct tcmsg tc;
433 		char buf[256];
434 	} req;
435 
436 	ret = attach_point_to_config(hook, &config);
437 	if (ret < 0)
438 		return ret;
439 
440 	memset(&req, 0, sizeof(req));
441 	req.nh.nlmsg_len   = NLMSG_LENGTH(sizeof(struct tcmsg));
442 	req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags;
443 	req.nh.nlmsg_type  = cmd;
444 	req.tc.tcm_family  = AF_UNSPEC;
445 	req.tc.tcm_ifindex = OPTS_GET(hook, ifindex, 0);
446 
447 	ret = config(&req.nh, &req.tc, sizeof(req));
448 	if (ret < 0)
449 		return ret;
450 
451 	return libbpf_netlink_send_recv(&req.nh, NULL, NULL, NULL);
452 }
453 
454 static int tc_qdisc_create_excl(struct bpf_tc_hook *hook)
455 {
456 	return tc_qdisc_modify(hook, RTM_NEWQDISC, NLM_F_CREATE);
457 }
458 
459 static int tc_qdisc_delete(struct bpf_tc_hook *hook)
460 {
461 	return tc_qdisc_modify(hook, RTM_DELQDISC, 0);
462 }
463 
464 int bpf_tc_hook_create(struct bpf_tc_hook *hook)
465 {
466 	if (!hook || !OPTS_VALID(hook, bpf_tc_hook) ||
467 	    OPTS_GET(hook, ifindex, 0) <= 0)
468 		return -EINVAL;
469 
470 	return tc_qdisc_create_excl(hook);
471 }
472 
473 static int __bpf_tc_detach(const struct bpf_tc_hook *hook,
474 			   const struct bpf_tc_opts *opts,
475 			   const bool flush);
476 
477 int bpf_tc_hook_destroy(struct bpf_tc_hook *hook)
478 {
479 	if (!hook || !OPTS_VALID(hook, bpf_tc_hook) ||
480 	    OPTS_GET(hook, ifindex, 0) <= 0)
481 		return -EINVAL;
482 
483 	switch (OPTS_GET(hook, attach_point, 0)) {
484 	case BPF_TC_INGRESS:
485 	case BPF_TC_EGRESS:
486 		return __bpf_tc_detach(hook, NULL, true);
487 	case BPF_TC_INGRESS | BPF_TC_EGRESS:
488 		return tc_qdisc_delete(hook);
489 	case BPF_TC_CUSTOM:
490 		return -EOPNOTSUPP;
491 	default:
492 		return -EINVAL;
493 	}
494 }
495 
496 struct bpf_cb_ctx {
497 	struct bpf_tc_opts *opts;
498 	bool processed;
499 };
500 
501 static int __get_tc_info(void *cookie, struct tcmsg *tc, struct nlattr **tb,
502 			 bool unicast)
503 {
504 	struct nlattr *tbb[TCA_BPF_MAX + 1];
505 	struct bpf_cb_ctx *info = cookie;
506 
507 	if (!info || !info->opts)
508 		return -EINVAL;
509 	if (unicast && info->processed)
510 		return -EINVAL;
511 	if (!tb[TCA_OPTIONS])
512 		return NL_CONT;
513 
514 	libbpf_nla_parse_nested(tbb, TCA_BPF_MAX, tb[TCA_OPTIONS], NULL);
515 	if (!tbb[TCA_BPF_ID])
516 		return -EINVAL;
517 
518 	OPTS_SET(info->opts, prog_id, libbpf_nla_getattr_u32(tbb[TCA_BPF_ID]));
519 	OPTS_SET(info->opts, handle, tc->tcm_handle);
520 	OPTS_SET(info->opts, priority, TC_H_MAJ(tc->tcm_info) >> 16);
521 
522 	info->processed = true;
523 	return unicast ? NL_NEXT : NL_DONE;
524 }
525 
526 static int get_tc_info(struct nlmsghdr *nh, libbpf_dump_nlmsg_t fn,
527 		       void *cookie)
528 {
529 	struct tcmsg *tc = NLMSG_DATA(nh);
530 	struct nlattr *tb[TCA_MAX + 1];
531 
532 	libbpf_nla_parse(tb, TCA_MAX,
533 			 (struct nlattr *)((char *)tc + NLMSG_ALIGN(sizeof(*tc))),
534 			 NLMSG_PAYLOAD(nh, sizeof(*tc)), NULL);
535 	if (!tb[TCA_KIND])
536 		return NL_CONT;
537 	return __get_tc_info(cookie, tc, tb, nh->nlmsg_flags & NLM_F_ECHO);
538 }
539 
540 static int tc_add_fd_and_name(struct nlmsghdr *nh, size_t maxsz, int fd)
541 {
542 	struct bpf_prog_info info = {};
543 	__u32 info_len = sizeof(info);
544 	char name[256];
545 	int len, ret;
546 
547 	ret = bpf_obj_get_info_by_fd(fd, &info, &info_len);
548 	if (ret < 0)
549 		return ret;
550 
551 	ret = nlattr_add(nh, maxsz, TCA_BPF_FD, &fd, sizeof(fd));
552 	if (ret < 0)
553 		return ret;
554 	len = snprintf(name, sizeof(name), "%s:[%u]", info.name, info.id);
555 	if (len < 0)
556 		return -errno;
557 	if (len >= sizeof(name))
558 		return -ENAMETOOLONG;
559 	return nlattr_add(nh, maxsz, TCA_BPF_NAME, name, len + 1);
560 }
561 
562 int bpf_tc_attach(const struct bpf_tc_hook *hook, struct bpf_tc_opts *opts)
563 {
564 	__u32 protocol, bpf_flags, handle, priority, parent, prog_id, flags;
565 	int ret, ifindex, attach_point, prog_fd;
566 	struct bpf_cb_ctx info = {};
567 	struct nlattr *nla;
568 	struct {
569 		struct nlmsghdr nh;
570 		struct tcmsg tc;
571 		char buf[256];
572 	} req;
573 
574 	if (!hook || !opts ||
575 	    !OPTS_VALID(hook, bpf_tc_hook) ||
576 	    !OPTS_VALID(opts, bpf_tc_opts))
577 		return -EINVAL;
578 
579 	ifindex      = OPTS_GET(hook, ifindex, 0);
580 	parent       = OPTS_GET(hook, parent, 0);
581 	attach_point = OPTS_GET(hook, attach_point, 0);
582 
583 	handle       = OPTS_GET(opts, handle, 0);
584 	priority     = OPTS_GET(opts, priority, 0);
585 	prog_fd      = OPTS_GET(opts, prog_fd, 0);
586 	prog_id      = OPTS_GET(opts, prog_id, 0);
587 	flags        = OPTS_GET(opts, flags, 0);
588 
589 	if (ifindex <= 0 || !prog_fd || prog_id)
590 		return -EINVAL;
591 	if (priority > UINT16_MAX)
592 		return -EINVAL;
593 	if (flags & ~BPF_TC_F_REPLACE)
594 		return -EINVAL;
595 
596 	flags = (flags & BPF_TC_F_REPLACE) ? NLM_F_REPLACE : NLM_F_EXCL;
597 	protocol = ETH_P_ALL;
598 
599 	memset(&req, 0, sizeof(req));
600 	req.nh.nlmsg_len   = NLMSG_LENGTH(sizeof(struct tcmsg));
601 	req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE |
602 			     NLM_F_ECHO | flags;
603 	req.nh.nlmsg_type  = RTM_NEWTFILTER;
604 	req.tc.tcm_family  = AF_UNSPEC;
605 	req.tc.tcm_ifindex = ifindex;
606 	req.tc.tcm_handle  = handle;
607 	req.tc.tcm_info    = TC_H_MAKE(priority << 16, htons(protocol));
608 
609 	ret = tc_get_tcm_parent(attach_point, &parent);
610 	if (ret < 0)
611 		return ret;
612 	req.tc.tcm_parent = parent;
613 
614 	ret = nlattr_add(&req.nh, sizeof(req), TCA_KIND, "bpf", sizeof("bpf"));
615 	if (ret < 0)
616 		return ret;
617 	nla = nlattr_begin_nested(&req.nh, sizeof(req), TCA_OPTIONS);
618 	if (!nla)
619 		return -EMSGSIZE;
620 	ret = tc_add_fd_and_name(&req.nh, sizeof(req), prog_fd);
621 	if (ret < 0)
622 		return ret;
623 	bpf_flags = TCA_BPF_FLAG_ACT_DIRECT;
624 	ret = nlattr_add(&req.nh, sizeof(req), TCA_BPF_FLAGS, &bpf_flags,
625 			 sizeof(bpf_flags));
626 	if (ret < 0)
627 		return ret;
628 	nlattr_end_nested(&req.nh, nla);
629 
630 	info.opts = opts;
631 
632 	ret = libbpf_netlink_send_recv(&req.nh, get_tc_info, NULL, &info);
633 	if (ret < 0)
634 		return ret;
635 	if (!info.processed)
636 		return -ENOENT;
637 	return ret;
638 }
639 
640 static int __bpf_tc_detach(const struct bpf_tc_hook *hook,
641 			   const struct bpf_tc_opts *opts,
642 			   const bool flush)
643 {
644 	__u32 protocol = 0, handle, priority, parent, prog_id, flags;
645 	int ret, ifindex, attach_point, prog_fd;
646 	struct {
647 		struct nlmsghdr nh;
648 		struct tcmsg tc;
649 		char buf[256];
650 	} req;
651 
652 	if (!hook ||
653 	    !OPTS_VALID(hook, bpf_tc_hook) ||
654 	    !OPTS_VALID(opts, bpf_tc_opts))
655 		return -EINVAL;
656 
657 	ifindex      = OPTS_GET(hook, ifindex, 0);
658 	parent       = OPTS_GET(hook, parent, 0);
659 	attach_point = OPTS_GET(hook, attach_point, 0);
660 
661 	handle       = OPTS_GET(opts, handle, 0);
662 	priority     = OPTS_GET(opts, priority, 0);
663 	prog_fd      = OPTS_GET(opts, prog_fd, 0);
664 	prog_id      = OPTS_GET(opts, prog_id, 0);
665 	flags        = OPTS_GET(opts, flags, 0);
666 
667 	if (ifindex <= 0 || flags || prog_fd || prog_id)
668 		return -EINVAL;
669 	if (priority > UINT16_MAX)
670 		return -EINVAL;
671 	if (flags & ~BPF_TC_F_REPLACE)
672 		return -EINVAL;
673 	if (!flush) {
674 		if (!handle || !priority)
675 			return -EINVAL;
676 		protocol = ETH_P_ALL;
677 	} else {
678 		if (handle || priority)
679 			return -EINVAL;
680 	}
681 
682 	memset(&req, 0, sizeof(req));
683 	req.nh.nlmsg_len   = NLMSG_LENGTH(sizeof(struct tcmsg));
684 	req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
685 	req.nh.nlmsg_type  = RTM_DELTFILTER;
686 	req.tc.tcm_family  = AF_UNSPEC;
687 	req.tc.tcm_ifindex = ifindex;
688 	if (!flush) {
689 		req.tc.tcm_handle = handle;
690 		req.tc.tcm_info   = TC_H_MAKE(priority << 16, htons(protocol));
691 	}
692 
693 	ret = tc_get_tcm_parent(attach_point, &parent);
694 	if (ret < 0)
695 		return ret;
696 	req.tc.tcm_parent = parent;
697 
698 	if (!flush) {
699 		ret = nlattr_add(&req.nh, sizeof(req), TCA_KIND,
700 				 "bpf", sizeof("bpf"));
701 		if (ret < 0)
702 			return ret;
703 	}
704 
705 	return libbpf_netlink_send_recv(&req.nh, NULL, NULL, NULL);
706 }
707 
708 int bpf_tc_detach(const struct bpf_tc_hook *hook,
709 		  const struct bpf_tc_opts *opts)
710 {
711 	return !opts ? -EINVAL : __bpf_tc_detach(hook, opts, false);
712 }
713 
714 int bpf_tc_query(const struct bpf_tc_hook *hook, struct bpf_tc_opts *opts)
715 {
716 	__u32 protocol, handle, priority, parent, prog_id, flags;
717 	int ret, ifindex, attach_point, prog_fd;
718 	struct bpf_cb_ctx info = {};
719 	struct {
720 		struct nlmsghdr nh;
721 		struct tcmsg tc;
722 		char buf[256];
723 	} req;
724 
725 	if (!hook || !opts ||
726 	    !OPTS_VALID(hook, bpf_tc_hook) ||
727 	    !OPTS_VALID(opts, bpf_tc_opts))
728 		return -EINVAL;
729 
730 	ifindex      = OPTS_GET(hook, ifindex, 0);
731 	parent       = OPTS_GET(hook, parent, 0);
732 	attach_point = OPTS_GET(hook, attach_point, 0);
733 
734 	handle       = OPTS_GET(opts, handle, 0);
735 	priority     = OPTS_GET(opts, priority, 0);
736 	prog_fd      = OPTS_GET(opts, prog_fd, 0);
737 	prog_id      = OPTS_GET(opts, prog_id, 0);
738 	flags        = OPTS_GET(opts, flags, 0);
739 
740 	if (ifindex <= 0 || flags || prog_fd || prog_id ||
741 	    !handle || !priority)
742 		return -EINVAL;
743 	if (priority > UINT16_MAX)
744 		return -EINVAL;
745 
746 	protocol = ETH_P_ALL;
747 
748 	memset(&req, 0, sizeof(req));
749 	req.nh.nlmsg_len   = NLMSG_LENGTH(sizeof(struct tcmsg));
750 	req.nh.nlmsg_flags = NLM_F_REQUEST;
751 	req.nh.nlmsg_type  = RTM_GETTFILTER;
752 	req.tc.tcm_family  = AF_UNSPEC;
753 	req.tc.tcm_ifindex = ifindex;
754 	req.tc.tcm_handle  = handle;
755 	req.tc.tcm_info    = TC_H_MAKE(priority << 16, htons(protocol));
756 
757 	ret = tc_get_tcm_parent(attach_point, &parent);
758 	if (ret < 0)
759 		return ret;
760 	req.tc.tcm_parent = parent;
761 
762 	ret = nlattr_add(&req.nh, sizeof(req), TCA_KIND, "bpf", sizeof("bpf"));
763 	if (ret < 0)
764 		return ret;
765 
766 	info.opts = opts;
767 
768 	ret = libbpf_netlink_send_recv(&req.nh, get_tc_info, NULL, &info);
769 	if (ret < 0)
770 		return ret;
771 	if (!info.processed)
772 		return -ENOENT;
773 	return ret;
774 }
775