qosify: add support for keeping stats
[project/qosify.git] / interface.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
4 */
5 #define _GNU_SOURCE
6 #include <sys/types.h>
7 #include <sys/socket.h>
8 #include <sys/ioctl.h>
9 #include <net/if_arp.h>
10 #include <net/if.h>
11 #include <netinet/if_ether.h>
12
13 #include <unistd.h>
14 #include <errno.h>
15
16 #include <netlink/msg.h>
17 #include <netlink/attr.h>
18 #include <netlink/socket.h>
19
20 #include <linux/rtnetlink.h>
21 #include <linux/pkt_cls.h>
22
23 #include <libubox/vlist.h>
24 #include <libubox/avl-cmp.h>
25 #include <libubox/uloop.h>
26
27 #include "qosify.h"
28
29 static void interface_update_cb(struct vlist_tree *tree,
30 struct vlist_node *node_new,
31 struct vlist_node *node_old);
32
33 static VLIST_TREE(devices, avl_strcmp, interface_update_cb, true, false);
34 static VLIST_TREE(interfaces, avl_strcmp, interface_update_cb, true, false);
35 static int socket_fd;
36 static struct nl_sock *rtnl_sock;
37
38 #define APPEND(_buf, _ofs, _format, ...) _ofs += snprintf(_buf + _ofs, sizeof(_buf) - _ofs, _format, ##__VA_ARGS__)
39
40 struct qosify_iface_config {
41 struct blob_attr *data;
42
43 bool ingress;
44 bool egress;
45 bool nat;
46 bool host_isolate;
47 bool autorate_ingress;
48
49 const char *bandwidth_up;
50 const char *bandwidth_down;
51 const char *mode;
52 const char *common_opts;
53 const char *ingress_opts;
54 const char *egress_opts;
55 };
56
57
58 struct qosify_iface {
59 struct vlist_node node;
60
61 char ifname[IFNAMSIZ];
62 bool active;
63
64 bool device;
65 struct blob_attr *config_data;
66 struct qosify_iface_config config;
67 };
68
69 enum {
70 IFACE_ATTR_BW_UP,
71 IFACE_ATTR_BW_DOWN,
72 IFACE_ATTR_INGRESS,
73 IFACE_ATTR_EGRESS,
74 IFACE_ATTR_MODE,
75 IFACE_ATTR_NAT,
76 IFACE_ATTR_HOST_ISOLATE,
77 IFACE_ATTR_AUTORATE_IN,
78 IFACE_ATTR_INGRESS_OPTS,
79 IFACE_ATTR_EGRESS_OPTS,
80 IFACE_ATTR_OPTS,
81 __IFACE_ATTR_MAX
82 };
83
84 static inline const char *qosify_iface_name(struct qosify_iface *iface)
85 {
86 return iface->node.avl.key;
87 }
88
89 static void
90 iface_config_parse(struct blob_attr *attr, struct blob_attr **tb)
91 {
92 static const struct blobmsg_policy policy[__IFACE_ATTR_MAX] = {
93 [IFACE_ATTR_BW_UP] = { "bandwidth_up", BLOBMSG_TYPE_STRING },
94 [IFACE_ATTR_BW_DOWN] = { "bandwidth_down", BLOBMSG_TYPE_STRING },
95 [IFACE_ATTR_INGRESS] = { "ingress", BLOBMSG_TYPE_BOOL },
96 [IFACE_ATTR_EGRESS] = { "egress", BLOBMSG_TYPE_BOOL },
97 [IFACE_ATTR_MODE] = { "mode", BLOBMSG_TYPE_STRING },
98 [IFACE_ATTR_NAT] = { "nat", BLOBMSG_TYPE_BOOL },
99 [IFACE_ATTR_HOST_ISOLATE] = { "host_isolate", BLOBMSG_TYPE_BOOL },
100 [IFACE_ATTR_AUTORATE_IN] = { "autorate_ingress", BLOBMSG_TYPE_BOOL },
101 [IFACE_ATTR_INGRESS_OPTS] = { "ingress_options", BLOBMSG_TYPE_STRING },
102 [IFACE_ATTR_EGRESS_OPTS] = { "egress_options", BLOBMSG_TYPE_STRING },
103 [IFACE_ATTR_OPTS] = { "options", BLOBMSG_TYPE_STRING },
104 };
105
106 blobmsg_parse(policy, __IFACE_ATTR_MAX, tb, blobmsg_data(attr), blobmsg_len(attr));
107 }
108
109 static bool
110 iface_config_equal(struct qosify_iface *if1, struct qosify_iface *if2)
111 {
112 struct blob_attr *tb1[__IFACE_ATTR_MAX], *tb2[__IFACE_ATTR_MAX];
113 int i;
114
115 iface_config_parse(if1->config_data, tb1);
116 iface_config_parse(if2->config_data, tb2);
117
118 for (i = 0; i < __IFACE_ATTR_MAX; i++) {
119 if (!!tb1[i] != !!tb2[i])
120 return false;
121
122 if (!tb1[i])
123 continue;
124
125 if (blob_raw_len(tb1[i]) != blob_raw_len(tb2[i]))
126 return false;
127
128 if (memcmp(tb1[i], tb2[i], blob_raw_len(tb1[i])) != 0)
129 return false;
130 }
131
132 return true;
133 }
134
135 static const char *check_str(struct blob_attr *attr)
136 {
137 const char *str = blobmsg_get_string(attr);
138
139 if (strchr(str, '\''))
140 return NULL;
141
142 return str;
143 }
144
145 static void
146 iface_config_set(struct qosify_iface *iface, struct blob_attr *attr)
147 {
148 struct qosify_iface_config *cfg = &iface->config;
149 struct blob_attr *tb[__IFACE_ATTR_MAX];
150 struct blob_attr *cur;
151
152 iface_config_parse(attr, tb);
153
154 memset(cfg, 0, sizeof(*cfg));
155
156 /* defaults */
157 cfg->mode = "diffserv4";
158 cfg->ingress = true;
159 cfg->egress = true;
160 cfg->host_isolate = true;
161 cfg->autorate_ingress = false;
162 cfg->nat = !iface->device;
163
164 if ((cur = tb[IFACE_ATTR_BW_UP]) != NULL)
165 cfg->bandwidth_up = check_str(cur);
166 if ((cur = tb[IFACE_ATTR_BW_DOWN]) != NULL)
167 cfg->bandwidth_down = check_str(cur);
168 if ((cur = tb[IFACE_ATTR_MODE]) != NULL)
169 cfg->mode = check_str(cur);
170 if ((cur = tb[IFACE_ATTR_OPTS]) != NULL)
171 cfg->common_opts = check_str(cur);
172 if ((cur = tb[IFACE_ATTR_EGRESS_OPTS]) != NULL)
173 cfg->egress_opts = check_str(cur);
174 if ((cur = tb[IFACE_ATTR_INGRESS_OPTS]) != NULL)
175 cfg->ingress_opts = check_str(cur);
176 if ((cur = tb[IFACE_ATTR_INGRESS]) != NULL)
177 cfg->ingress = blobmsg_get_bool(cur);
178 if ((cur = tb[IFACE_ATTR_EGRESS]) != NULL)
179 cfg->egress = blobmsg_get_bool(cur);
180 if ((cur = tb[IFACE_ATTR_NAT]) != NULL)
181 cfg->nat = blobmsg_get_bool(cur);
182 if ((cur = tb[IFACE_ATTR_HOST_ISOLATE]) != NULL)
183 cfg->host_isolate = blobmsg_get_bool(cur);
184 if ((cur = tb[IFACE_ATTR_AUTORATE_IN]) != NULL)
185 cfg->autorate_ingress = blobmsg_get_bool(cur);
186 }
187
188 static const char *
189 interface_ifb_name(struct qosify_iface *iface)
190 {
191 static char ifname[IFNAMSIZ + 1] = "ifb-";
192 int len = strlen(iface->ifname);
193
194 if (len + 4 < IFNAMSIZ) {
195 snprintf(ifname + 4, IFNAMSIZ - 4, "%s", iface->ifname);
196
197 return ifname;
198 }
199
200 ifname[4] = iface->ifname[0];
201 ifname[5] = iface->ifname[1];
202 snprintf(ifname + 6, IFNAMSIZ - 6, "%s", iface->ifname + len - (IFNAMSIZ + 6) - 1);
203
204 return ifname;
205 }
206
207 static int
208 prepare_qdisc_cmd(char *buf, int len, const char *dev, bool add, const char *type)
209 {
210 return snprintf(buf, len, "tc qdisc %s dev '%s' %s",
211 add ? "add" : "del", dev, type);
212 }
213
214 static int
215 prepare_filter_cmd(char *buf, int len, const char *dev, int prio, bool add, bool egress)
216 {
217 return snprintf(buf, len, "tc filter %s dev '%s' %sgress prio %d",
218 add ? "add" : "del", dev, egress ? "e" : "in", prio);
219 }
220
221 static int
222 cmd_add_bpf_filter(const char *ifname, int prio, bool egress, bool eth)
223 {
224 struct tcmsg tcmsg = {
225 .tcm_family = AF_UNSPEC,
226 .tcm_ifindex = if_nametoindex(ifname),
227 };
228 struct nl_msg *msg;
229 struct nlattr *opts;
230 const char *suffix;
231 int prog_fd = -1;
232 char name[32];
233
234 suffix = qosify_get_program(!egress * QOSIFY_INGRESS + !eth * QOSIFY_IP_ONLY, &prog_fd);
235 if (!suffix)
236 return -1;
237
238 snprintf(name, sizeof(name), "qosify_%s", suffix);
239
240 if (egress)
241 tcmsg.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS);
242 else
243 tcmsg.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS);
244
245 tcmsg.tcm_info = TC_H_MAKE(prio << 16, htons(ETH_P_ALL));
246
247 msg = nlmsg_alloc_simple(RTM_NEWTFILTER, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL);
248 nlmsg_append(msg, &tcmsg, sizeof(tcmsg), NLMSG_ALIGNTO);
249 nla_put_string(msg, TCA_KIND, "bpf");
250
251 opts = nla_nest_start(msg, TCA_OPTIONS);
252 nla_put_u32(msg, TCA_BPF_FD, prog_fd);
253 nla_put_string(msg, TCA_BPF_NAME, name);
254 nla_put_u32(msg, TCA_BPF_FLAGS, TCA_BPF_FLAG_ACT_DIRECT);
255 nla_put_u32(msg, TCA_BPF_FLAGS_GEN, TCA_CLS_FLAGS_SKIP_HW);
256 nla_nest_end(msg, opts);
257
258 nl_send_auto_complete(rtnl_sock, msg);
259 nlmsg_free(msg);
260
261 return nl_wait_for_ack(rtnl_sock);
262 }
263
264 static int
265 cmd_add_qdisc(struct qosify_iface *iface, const char *ifname, bool egress, bool eth)
266 {
267 struct qosify_iface_config *cfg = &iface->config;
268 const char *bw = egress ? cfg->bandwidth_up : cfg->bandwidth_down;
269 const char *dir_opts = egress ? cfg->egress_opts : cfg->ingress_opts;
270 char buf[512];
271 int ofs;
272
273 ofs = prepare_qdisc_cmd(buf, sizeof(buf), ifname, true, "clsact");
274 qosify_run_cmd(buf, true);
275
276 ofs = prepare_qdisc_cmd(buf, sizeof(buf), ifname, true, "root cake");
277 if (bw)
278 APPEND(buf, ofs, " bandwidth %s", bw);
279
280 APPEND(buf, ofs, " %s %sgress", cfg->mode, egress ? "e" : "in");
281 if (!egress && cfg->autorate_ingress)
282 APPEND(buf, ofs, " autorate-ingress");
283
284 if (cfg->host_isolate)
285 APPEND(buf, ofs, " %snat dual-%shost",
286 cfg->nat ? "" : "no",
287 egress ? "src" : "dst");
288 else
289 APPEND(buf, ofs, " flows");
290
291 APPEND(buf, ofs, " %s %s",
292 cfg->common_opts ? cfg->common_opts : "",
293 dir_opts ? dir_opts : "");
294
295 return qosify_run_cmd(buf, false);
296 }
297
298 static int
299 cmd_add_ingress(struct qosify_iface *iface, bool eth)
300 {
301 const char *ifbdev = interface_ifb_name(iface);
302 char buf[256];
303 int prio = QOSIFY_PRIO_BASE;
304 int ofs;
305
306 cmd_add_bpf_filter(iface->ifname, prio++, false, eth);
307
308 ofs = prepare_filter_cmd(buf, sizeof(buf), iface->ifname, prio++, true, false);
309 APPEND(buf, ofs, " protocol ip u32 match ip sport 53 0xffff "
310 "flowid 1:1 action mirred egress redirect dev " QOSIFY_DNS_IFNAME);
311 qosify_run_cmd(buf, false);
312
313 ofs = prepare_filter_cmd(buf, sizeof(buf), iface->ifname, prio++, true, false);
314 APPEND(buf, ofs, " protocol 802.1Q u32 offset plus 4 match ip sport 53 0xffff "
315 "flowid 1:1 action mirred egress redirect dev " QOSIFY_DNS_IFNAME);
316 qosify_run_cmd(buf, false);
317
318 ofs = prepare_filter_cmd(buf, sizeof(buf), iface->ifname, prio++, true, false);
319 APPEND(buf, ofs, " protocol ipv6 u32 match ip6 sport 53 0xffff "
320 "flowid 1:1 action mirred egress redirect dev " QOSIFY_DNS_IFNAME);
321 qosify_run_cmd(buf, false);
322
323 ofs = prepare_filter_cmd(buf, sizeof(buf), iface->ifname, prio++, true, false);
324 APPEND(buf, ofs, " protocol ipv6 u32 offset plus 4 match ip6 sport 53 0xffff "
325 "flowid 1:1 action mirred egress redirect dev " QOSIFY_DNS_IFNAME);
326 qosify_run_cmd(buf, false);
327
328
329 if (!iface->config.ingress)
330 return 0;
331
332 snprintf(buf, sizeof(buf), "ip link add '%s' type ifb", ifbdev);
333 qosify_run_cmd(buf, false);
334
335 cmd_add_qdisc(iface, ifbdev, false, eth);
336
337 snprintf(buf, sizeof(buf), "ip link set dev '%s' up", ifbdev);
338 qosify_run_cmd(buf, false);
339
340 ofs = prepare_filter_cmd(buf, sizeof(buf), iface->ifname, prio++, true, false);
341 APPEND(buf, ofs, " protocol all u32 match u32 0 0 flowid 1:1"
342 " action mirred egress redirect dev '%s'", ifbdev);
343 return qosify_run_cmd(buf, false);
344 }
345
346 static int cmd_add_egress(struct qosify_iface *iface, bool eth)
347 {
348 if (!iface->config.egress)
349 return 0;
350
351 cmd_add_qdisc(iface, iface->ifname, true, eth);
352
353 return cmd_add_bpf_filter(iface->ifname, QOSIFY_PRIO_BASE, true, eth);
354 }
355
356 static void
357 interface_clear_qdisc(struct qosify_iface *iface)
358 {
359 char buf[64];
360 int i;
361
362 prepare_qdisc_cmd(buf, sizeof(buf), iface->ifname, false, "root");
363 qosify_run_cmd(buf, true);
364
365 for (i = 0; i < 6; i++) {
366 prepare_filter_cmd(buf, sizeof(buf), iface->ifname, QOSIFY_PRIO_BASE + i, false, false);
367 qosify_run_cmd(buf, true);
368 }
369
370 prepare_filter_cmd(buf, sizeof(buf), iface->ifname, QOSIFY_PRIO_BASE, false, true);
371 qosify_run_cmd(buf, true);
372
373 snprintf(buf, sizeof(buf), "ip link del '%s'", interface_ifb_name(iface));
374 qosify_run_cmd(buf, true);
375 }
376
377 static void
378 interface_start(struct qosify_iface *iface)
379 {
380 struct ifreq ifr = {};
381 bool eth;
382
383 if (!iface->ifname[0] || iface->active)
384 return;
385
386 ULOG_INFO("start interface %s\n", iface->ifname);
387
388 strncpy(ifr.ifr_name, iface->ifname, sizeof(ifr.ifr_name));
389 if (ioctl(socket_fd, SIOCGIFHWADDR, &ifr) < 0) {
390 ULOG_ERR("ioctl(SIOCGIFHWADDR, %s) failed: %s\n", iface->ifname, strerror(errno));
391 return;
392 }
393
394 eth = ifr.ifr_hwaddr.sa_family == ARPHRD_ETHER;
395
396 interface_clear_qdisc(iface);
397 cmd_add_egress(iface, eth);
398 cmd_add_ingress(iface, eth);
399
400 iface->active = true;
401 }
402
403 static void
404 interface_stop(struct qosify_iface *iface)
405 {
406 if (!iface->ifname[0] || !iface->active)
407 return;
408
409 ULOG_INFO("stop interface %s\n", iface->ifname);
410 iface->active = false;
411
412 interface_clear_qdisc(iface);
413 }
414
415 static void
416 interface_set_config(struct qosify_iface *iface, struct blob_attr *config)
417 {
418 iface->config_data = blob_memdup(config);
419 iface_config_set(iface, iface->config_data);
420 interface_start(iface);
421 }
422
423 static void
424 interface_update_cb(struct vlist_tree *tree,
425 struct vlist_node *node_new, struct vlist_node *node_old)
426 {
427 struct qosify_iface *if_new = NULL, *if_old = NULL;
428
429 if (node_new)
430 if_new = container_of(node_new, struct qosify_iface, node);
431 if (node_old)
432 if_old = container_of(node_old, struct qosify_iface, node);
433
434 if (if_new && if_old) {
435 if (!iface_config_equal(if_old, if_new)) {
436 interface_stop(if_old);
437 free(if_old->config_data);
438 interface_set_config(if_old, if_new->config_data);
439 }
440
441 free(if_new);
442 return;
443 }
444
445 if (if_old) {
446 interface_stop(if_old);
447 free(if_old->config_data);
448 free(if_old);
449 }
450
451 if (if_new)
452 interface_set_config(if_new, if_new->config_data);
453 }
454
455 static void
456 interface_create(struct blob_attr *attr, bool device)
457 {
458 struct qosify_iface *iface;
459 const char *name = blobmsg_name(attr);
460 int name_len = strlen(name);
461 char *name_buf;
462
463 if (strchr(name, '\''))
464 return;
465
466 if (name_len >= IFNAMSIZ)
467 return;
468
469 if (blobmsg_type(attr) != BLOBMSG_TYPE_TABLE)
470 return;
471
472 iface = calloc_a(sizeof(*iface), &name_buf, name_len + 1);
473 strcpy(name_buf, blobmsg_name(attr));
474 iface->config_data = attr;
475 iface->device = device;
476 vlist_add(device ? &devices : &interfaces, &iface->node, name_buf);
477 }
478
479 void qosify_iface_config_update(struct blob_attr *ifaces, struct blob_attr *devs)
480 {
481 struct blob_attr *cur;
482 int rem;
483
484 vlist_update(&devices);
485 blobmsg_for_each_attr(cur, devs, rem)
486 interface_create(cur, true);
487 vlist_flush(&devices);
488
489 vlist_update(&interfaces);
490 blobmsg_for_each_attr(cur, ifaces, rem)
491 interface_create(cur, false);
492 vlist_flush(&interfaces);
493 }
494
495 static void
496 qosify_iface_check_device(struct qosify_iface *iface)
497 {
498 const char *name = qosify_iface_name(iface);
499 int ifindex;
500
501 ifindex = if_nametoindex(name);
502 if (!ifindex) {
503 interface_stop(iface);
504 iface->ifname[0] = 0;
505 } else {
506 snprintf(iface->ifname, sizeof(iface->ifname), "%s", name);
507 interface_start(iface);
508 }
509 }
510
511 static void
512 qosify_iface_check_interface(struct qosify_iface *iface)
513 {
514 const char *name = qosify_iface_name(iface);
515 char ifname[IFNAMSIZ];
516
517 if (qosify_ubus_check_interface(name, ifname, sizeof(ifname)) == 0) {
518 snprintf(iface->ifname, sizeof(iface->ifname), "%s", ifname);
519 interface_start(iface);
520 } else {
521 interface_stop(iface);
522 iface->ifname[0] = 0;
523 }
524 }
525
526 static void qos_iface_check_cb(struct uloop_timeout *t)
527 {
528 struct qosify_iface *iface;
529
530 vlist_for_each_element(&devices, iface, node)
531 qosify_iface_check_device(iface);
532 vlist_for_each_element(&interfaces, iface, node)
533 qosify_iface_check_interface(iface);
534 qosify_ubus_update_bridger(false);
535 }
536
537 void qosify_iface_check(void)
538 {
539 static struct uloop_timeout timer = {
540 .cb = qos_iface_check_cb,
541 };
542
543 uloop_timeout_set(&timer, 10);
544 }
545
546 static void
547 __qosify_iface_status(struct blob_buf *b, struct qosify_iface *iface)
548 {
549 void *c;
550
551 c = blobmsg_open_table(b, qosify_iface_name(iface));
552 blobmsg_add_u8(b, "active", iface->active);
553 if (iface->ifname[0])
554 blobmsg_add_string(b, "ifname", iface->ifname);
555 blobmsg_add_u8(b, "egress", iface->config.egress);
556 blobmsg_add_u8(b, "ingress", iface->config.ingress);
557 blobmsg_close_table(b, c);
558
559 }
560
561 void qosify_iface_status(struct blob_buf *b)
562 {
563 struct qosify_iface *iface;
564 void *c;
565
566 c = blobmsg_open_table(b, "devices");
567 vlist_for_each_element(&devices, iface, node)
568 __qosify_iface_status(b, iface);
569 blobmsg_close_table(b, c);
570
571 c = blobmsg_open_table(b, "interfaces");
572 vlist_for_each_element(&interfaces, iface, node)
573 __qosify_iface_status(b, iface);
574 blobmsg_close_table(b, c);
575 }
576
577 static int
578 qosify_nl_error_cb(struct sockaddr_nl *nla, struct nlmsgerr *err,
579 void *arg)
580 {
581 struct nlmsghdr *nlh = (struct nlmsghdr *) err - 1;
582 struct nlattr *tb[NLMSGERR_ATTR_MAX + 1];
583 struct nlattr *attrs;
584 int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh);
585 int len = nlh->nlmsg_len;
586 const char *errstr = "(unknown)";
587
588 if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
589 return NL_STOP;
590
591 if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
592 ack_len += err->msg.nlmsg_len - sizeof(*nlh);
593
594 attrs = (void *) ((unsigned char *) nlh + ack_len);
595 len -= ack_len;
596
597 nla_parse(tb, NLMSGERR_ATTR_MAX, attrs, len, NULL);
598 if (tb[NLMSGERR_ATTR_MSG])
599 errstr = nla_data(tb[NLMSGERR_ATTR_MSG]);
600
601 ULOG_ERR("Netlink error(%d): %s\n", err->error, errstr);
602
603 return NL_STOP;
604 }
605
606 static void
607 __qosify_iface_get_device(struct blob_buf *b, struct qosify_iface *iface)
608 {
609 if (!iface->ifname[0] || !iface->active)
610 return;
611
612 blobmsg_add_string(b, NULL, iface->ifname);
613 }
614
615 void qosify_iface_get_devices(struct blob_buf *b)
616 {
617 struct qosify_iface *iface;
618
619 vlist_for_each_element(&devices, iface, node)
620 __qosify_iface_get_device(b, iface);
621 vlist_for_each_element(&interfaces, iface, node)
622 __qosify_iface_get_device(b, iface);
623 }
624
625 int qosify_iface_init(void)
626 {
627 int fd, opt;
628
629 socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
630 if (socket < 0)
631 return -1;
632
633 rtnl_sock = nl_socket_alloc();
634 if (!rtnl_sock)
635 return -1;
636
637 if (nl_connect(rtnl_sock, NETLINK_ROUTE))
638 return -1;
639
640 nl_cb_err(nl_socket_get_cb(rtnl_sock), NL_CB_CUSTOM,
641 qosify_nl_error_cb, NULL);
642
643 fd = nl_socket_get_fd(rtnl_sock);
644 opt = 1;
645 setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &opt, sizeof(opt));
646
647 opt = 1;
648 setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &opt, sizeof(opt));
649
650 return 0;
651 }
652
653 void qosify_iface_stop(void)
654 {
655 struct qosify_iface *iface;
656
657 vlist_for_each_element(&interfaces, iface, node)
658 interface_stop(iface);
659 vlist_for_each_element(&devices, iface, node)
660 interface_stop(iface);
661
662 nl_socket_free(rtnl_sock);
663 }
664