]> git.proxmox.com Git - mirror_frr.git/blob - pimd/mtracebis_netlink.c
Merge pull request #12798 from donaldsharp/rib_match_multicast
[mirror_frr.git] / pimd / mtracebis_netlink.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * libnetlink.c RTnetlink service routines.
4 *
5 * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
6 *
7 */
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12
13 #ifdef __linux__
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <syslog.h>
19 #include <fcntl.h>
20 #include <net/if_arp.h>
21 #include <sys/socket.h>
22 #include <netinet/in.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <time.h>
26 #include <sys/uio.h>
27 #include <assert.h>
28
29 #include "mtracebis_netlink.h"
30
31 int rcvbuf = 1024 * 1024;
32
33 void rtnl_close(struct rtnl_handle *rth)
34 {
35 if (rth->fd >= 0) {
36 close(rth->fd);
37 rth->fd = -1;
38 }
39 }
40
41 int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions,
42 int protocol)
43 {
44 socklen_t addr_len;
45 int sndbuf = 32768;
46
47 memset(rth, 0, sizeof(*rth));
48
49 rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol);
50 if (rth->fd < 0) {
51 perror("Cannot open netlink socket");
52 return -1;
53 }
54
55 if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))
56 < 0) {
57 perror("SO_SNDBUF");
58 return -1;
59 }
60
61 if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf))
62 < 0) {
63 perror("SO_RCVBUF");
64 return -1;
65 }
66
67 memset(&rth->local, 0, sizeof(rth->local));
68 rth->local.nl_family = AF_NETLINK;
69 rth->local.nl_groups = subscriptions;
70
71 if (bind(rth->fd, (struct sockaddr *)&rth->local, sizeof(rth->local))
72 < 0) {
73 perror("Cannot bind netlink socket");
74 return -1;
75 }
76 addr_len = sizeof(rth->local);
77 if (getsockname(rth->fd, (struct sockaddr *)&rth->local, &addr_len)
78 < 0) {
79 perror("Cannot getsockname");
80 return -1;
81 }
82 if (addr_len != sizeof(rth->local)) {
83 fprintf(stderr, "Wrong address length %d\n", addr_len);
84 return -1;
85 }
86 if (rth->local.nl_family != AF_NETLINK) {
87 fprintf(stderr, "Wrong address family %d\n",
88 rth->local.nl_family);
89 return -1;
90 }
91 rth->seq = getpid();
92 return 0;
93 }
94
95 int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions)
96 {
97 return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
98 }
99
100 int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type)
101 {
102 struct {
103 struct nlmsghdr nlh;
104 struct rtgenmsg g;
105 } req;
106
107 memset(&req, 0, sizeof(req));
108 req.nlh.nlmsg_len = sizeof(req);
109 req.nlh.nlmsg_type = type;
110 req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
111 req.nlh.nlmsg_pid = 0;
112 req.nlh.nlmsg_seq = rth->dump = ++rth->seq;
113 req.g.rtgen_family = family;
114
115 return send(rth->fd, (void *)&req, sizeof(req), 0);
116 }
117
118 int rtnl_send(struct rtnl_handle *rth, const char *buf, int len)
119 {
120 return send(rth->fd, buf, len, 0);
121 }
122
123 int rtnl_send_check(struct rtnl_handle *rth, const char *buf, int len)
124 {
125 struct nlmsghdr *h;
126 int status;
127 char resp[1024];
128
129 status = send(rth->fd, buf, len, 0);
130 if (status < 0)
131 return status;
132
133 /* Check for immediate errors */
134 status = recv(rth->fd, resp, sizeof(resp), MSG_DONTWAIT | MSG_PEEK);
135 if (status < 0) {
136 if (errno == EAGAIN)
137 return 0;
138 return -1;
139 }
140
141 for (h = (struct nlmsghdr *)resp; NLMSG_OK(h, (uint32_t)status);
142 h = NLMSG_NEXT(h, status)) {
143 if (h->nlmsg_type == NLMSG_ERROR) {
144 struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h);
145 if (h->nlmsg_len
146 < NLMSG_LENGTH(sizeof(struct nlmsgerr)))
147 fprintf(stderr, "ERROR truncated\n");
148 else
149 errno = -err->error;
150 return -1;
151 }
152 }
153
154 return 0;
155 }
156
157 int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len)
158 {
159 struct nlmsghdr nlh;
160 struct sockaddr_nl nladdr;
161 struct iovec iov[2] = {{.iov_base = &nlh, .iov_len = sizeof(nlh)},
162 {.iov_base = req, .iov_len = len}};
163 struct msghdr msg = {
164 .msg_name = &nladdr,
165 .msg_namelen = sizeof(nladdr),
166 .msg_iov = iov,
167 .msg_iovlen = 2,
168 };
169
170 memset(&nladdr, 0, sizeof(nladdr));
171 nladdr.nl_family = AF_NETLINK;
172
173 nlh.nlmsg_len = NLMSG_LENGTH(len);
174 nlh.nlmsg_type = type;
175 nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
176 nlh.nlmsg_pid = 0;
177 nlh.nlmsg_seq = rth->dump = ++rth->seq;
178
179 return sendmsg(rth->fd, &msg, 0);
180 }
181
182 int rtnl_dump_filter_l(struct rtnl_handle *rth,
183 const struct rtnl_dump_filter_arg *arg)
184 {
185 struct sockaddr_nl nladdr;
186 char buf[16384];
187 struct iovec iov = {
188 .iov_base = buf,
189 .iov_len = sizeof(buf),
190 };
191 struct msghdr msg = {
192 .msg_name = &nladdr,
193 .msg_namelen = sizeof(nladdr),
194 .msg_iov = &iov,
195 .msg_iovlen = 1,
196 };
197
198 while (1) {
199 int status;
200 const struct rtnl_dump_filter_arg *a;
201 int found_done = 0;
202 int msglen = 0;
203
204 iov.iov_len = sizeof(buf);
205 status = recvmsg(rth->fd, &msg, 0);
206
207 if (status < 0) {
208 if (errno == EINTR || errno == EAGAIN)
209 continue;
210 fprintf(stderr, "netlink receive error %s (%d)\n",
211 strerror(errno), errno);
212 return -1;
213 }
214
215 if (status == 0) {
216 fprintf(stderr, "EOF on netlink\n");
217 return -1;
218 }
219
220 for (a = arg; a->filter; a++) {
221 struct nlmsghdr *h = (struct nlmsghdr *)iov.iov_base;
222 msglen = status;
223
224 while (NLMSG_OK(h, (uint32_t)msglen)) {
225 int err;
226
227 if (nladdr.nl_pid != 0
228 || h->nlmsg_pid != rth->local.nl_pid
229 || h->nlmsg_seq != rth->dump) {
230 if (a->junk) {
231 err = a->junk(&nladdr, h,
232 a->arg2);
233 if (err < 0)
234 return err;
235 }
236 goto skip_it;
237 }
238
239 if (h->nlmsg_type == NLMSG_DONE) {
240 found_done = 1;
241 break; /* process next filter */
242 }
243 if (h->nlmsg_type == NLMSG_ERROR) {
244 struct nlmsgerr *merr =
245 (struct nlmsgerr *)NLMSG_DATA(
246 h);
247 if (h->nlmsg_len
248 < NLMSG_LENGTH(sizeof(
249 struct nlmsgerr))) {
250 fprintf(stderr,
251 "ERROR truncated\n");
252 } else {
253 errno = -merr->error;
254 perror("RTNETLINK answers");
255 }
256 return -1;
257 }
258 err = a->filter(&nladdr, h, a->arg1);
259 if (err < 0)
260 return err;
261
262 skip_it:
263 h = NLMSG_NEXT(h, msglen);
264 }
265 }
266
267 if (found_done)
268 return 0;
269
270 if (msg.msg_flags & MSG_TRUNC) {
271 fprintf(stderr, "Message truncated\n");
272 continue;
273 }
274 if (msglen) {
275 fprintf(stderr, "!!!Remnant of size %d\n", msglen);
276 exit(1);
277 }
278 }
279 }
280
281 int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, void *arg1,
282 rtnl_filter_t junk, void *arg2)
283 {
284 const struct rtnl_dump_filter_arg a[2] = {
285 {.filter = filter, .arg1 = arg1, .junk = junk, .arg2 = arg2},
286 {.filter = NULL, .arg1 = NULL, .junk = NULL, .arg2 = NULL}};
287
288 return rtnl_dump_filter_l(rth, a);
289 }
290
291 int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer,
292 unsigned groups, struct nlmsghdr *answer, rtnl_filter_t junk,
293 void *jarg)
294 {
295 int status;
296 unsigned seq;
297 struct nlmsghdr *h;
298 struct sockaddr_nl nladdr;
299 struct iovec iov = {.iov_base = (void *)n, .iov_len = n->nlmsg_len};
300 struct msghdr msg = {
301 .msg_name = &nladdr,
302 .msg_namelen = sizeof(nladdr),
303 .msg_iov = &iov,
304 .msg_iovlen = 1,
305 };
306 char buf[16384];
307
308 memset(&nladdr, 0, sizeof(nladdr));
309 nladdr.nl_family = AF_NETLINK;
310 nladdr.nl_pid = peer;
311 nladdr.nl_groups = groups;
312
313 n->nlmsg_seq = seq = ++rtnl->seq;
314
315 if (answer == NULL)
316 n->nlmsg_flags |= NLM_F_ACK;
317
318 status = sendmsg(rtnl->fd, &msg, 0);
319
320 if (status < 0) {
321 perror("Cannot talk to rtnetlink");
322 return -1;
323 }
324
325 memset(buf, 0, sizeof(buf));
326
327 iov.iov_base = buf;
328
329 while (1) {
330 iov.iov_len = sizeof(buf);
331 status = recvmsg(rtnl->fd, &msg, 0);
332
333 if (status < 0) {
334 if (errno == EINTR || errno == EAGAIN)
335 continue;
336 fprintf(stderr, "netlink receive error %s (%d)\n",
337 strerror(errno), errno);
338 return -1;
339 }
340 if (status == 0) {
341 fprintf(stderr, "EOF on netlink\n");
342 return -1;
343 }
344 if (msg.msg_namelen != sizeof(nladdr)) {
345 fprintf(stderr, "sender address length == %d\n",
346 msg.msg_namelen);
347 exit(1);
348 }
349 for (h = (struct nlmsghdr *)iov.iov_base;
350 status >= (int)sizeof(*h);) {
351 int err;
352 int len = h->nlmsg_len;
353 int l = len - sizeof(*h);
354
355 if (l < 0 || len > status) {
356 if (msg.msg_flags & MSG_TRUNC) {
357 fprintf(stderr, "Truncated message\n");
358 return -1;
359 }
360 fprintf(stderr,
361 "!!!malformed message: len=%d\n", len);
362 exit(1);
363 }
364
365 if ((int)nladdr.nl_pid != peer
366 || h->nlmsg_pid != rtnl->local.nl_pid
367 || h->nlmsg_seq != seq) {
368 if (junk) {
369 err = junk(&nladdr, h, jarg);
370 if (err < 0)
371 return err;
372 }
373 /* Don't forget to skip that message. */
374 status -= NLMSG_ALIGN(len);
375 h = (struct nlmsghdr *)((char *)h
376 + NLMSG_ALIGN(len));
377 continue;
378 }
379
380 if (h->nlmsg_type == NLMSG_ERROR) {
381 struct nlmsgerr *merr =
382 (struct nlmsgerr *)NLMSG_DATA(h);
383 if (l < (int)sizeof(struct nlmsgerr)) {
384 fprintf(stderr, "ERROR truncated\n");
385 } else {
386 errno = -merr->error;
387 if (errno == 0) {
388 if (answer)
389 memcpy(answer, h,
390 h->nlmsg_len);
391 return 0;
392 }
393 perror("RTNETLINK answers");
394 }
395 return -1;
396 }
397 if (answer) {
398 memcpy(answer, h, h->nlmsg_len);
399 return 0;
400 }
401
402 fprintf(stderr, "Unexpected reply!!!\n");
403
404 status -= NLMSG_ALIGN(len);
405 h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
406 }
407 if (msg.msg_flags & MSG_TRUNC) {
408 fprintf(stderr, "Message truncated\n");
409 continue;
410 }
411 if (status) {
412 fprintf(stderr, "!!!Remnant of size %d\n", status);
413 exit(1);
414 }
415 }
416 }
417
418 int rtnl_listen(struct rtnl_handle *rtnl, rtnl_filter_t handler, void *jarg)
419 {
420 int status;
421 struct nlmsghdr *h;
422 struct sockaddr_nl nladdr;
423 char buf[8192];
424 struct iovec iov = {
425 .iov_base = buf,
426 .iov_len = sizeof(buf),
427 };
428 struct msghdr msg = {
429 .msg_name = &nladdr,
430 .msg_namelen = sizeof(nladdr),
431 .msg_iov = &iov,
432 .msg_iovlen = 1,
433 };
434
435 memset(&nladdr, 0, sizeof(nladdr));
436 nladdr.nl_family = AF_NETLINK;
437 nladdr.nl_pid = 0;
438 nladdr.nl_groups = 0;
439
440 while (1) {
441 iov.iov_len = sizeof(buf);
442 status = recvmsg(rtnl->fd, &msg, 0);
443
444 if (status < 0) {
445 if (errno == EINTR || errno == EAGAIN)
446 continue;
447 fprintf(stderr, "netlink receive error %s (%d)\n",
448 strerror(errno), errno);
449 if (errno == ENOBUFS)
450 continue;
451 return -1;
452 }
453 if (status == 0) {
454 fprintf(stderr, "EOF on netlink\n");
455 return -1;
456 }
457 if (msg.msg_namelen != sizeof(nladdr)) {
458 fprintf(stderr, "Sender address length == %d\n",
459 msg.msg_namelen);
460 exit(1);
461 }
462 for (h = (struct nlmsghdr *)buf; status >= (int)sizeof(*h);) {
463 int err;
464 int len = h->nlmsg_len;
465 int l = len - sizeof(*h);
466
467 if (l < 0 || len > status) {
468 if (msg.msg_flags & MSG_TRUNC) {
469 fprintf(stderr, "Truncated message\n");
470 return -1;
471 }
472 fprintf(stderr,
473 "!!!malformed message: len=%d\n", len);
474 exit(1);
475 }
476
477 err = handler(&nladdr, h, jarg);
478 if (err < 0)
479 return err;
480
481 status -= NLMSG_ALIGN(len);
482 h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
483 }
484 if (msg.msg_flags & MSG_TRUNC) {
485 fprintf(stderr, "Message truncated\n");
486 continue;
487 }
488 if (status) {
489 fprintf(stderr, "!!!Remnant of size %d\n", status);
490 exit(1);
491 }
492 }
493 }
494
495 int rtnl_from_file(FILE *rtnl, rtnl_filter_t handler, void *jarg)
496 {
497 struct sockaddr_nl nladdr;
498 char buf[8192];
499 struct nlmsghdr *h = (void *)buf;
500
501 memset(&nladdr, 0, sizeof(nladdr));
502 nladdr.nl_family = AF_NETLINK;
503 nladdr.nl_pid = 0;
504 nladdr.nl_groups = 0;
505
506 while (1) {
507 int err;
508 size_t l, rl, arl;
509
510 rl = sizeof(*h);
511 arl = fread(&buf, 1, rl, rtnl);
512
513 if (arl != rl) {
514 if (arl == 0)
515 return 0;
516
517 if (ferror(rtnl))
518 fprintf(stderr, "%s: header read failed\n",
519 __func__);
520 else
521 fprintf(stderr, "%s: truncated header\n",
522 __func__);
523 return -1;
524 }
525
526 l = h->nlmsg_len > rl ? h->nlmsg_len - rl : 0;
527
528 if (l == 0 || (l + (size_t)NLMSG_HDRLEN) > sizeof(buf)) {
529 fprintf(stderr, "%s: malformed message: len=%zu @%lu\n",
530 __func__, (size_t)h->nlmsg_len, ftell(rtnl));
531 return -1;
532 }
533
534 rl = NLMSG_ALIGN(l);
535 arl = fread(NLMSG_DATA(h), 1, rl, rtnl);
536
537 if (arl != rl) {
538 if (ferror(rtnl))
539 fprintf(stderr, "%s: msg read failed\n",
540 __func__);
541 else
542 fprintf(stderr, "%s: truncated message\n",
543 __func__);
544 return -1;
545 }
546
547 err = handler(&nladdr, h, jarg);
548 if (err < 0)
549 return err;
550 }
551 }
552
553 int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data)
554 {
555 int len = RTA_LENGTH(4);
556 struct rtattr *rta;
557 if ((int)(NLMSG_ALIGN(n->nlmsg_len) + len) > maxlen) {
558 fprintf(stderr,
559 "addattr32: Error! max allowed bound %d exceeded\n",
560 maxlen);
561 return -1;
562 }
563 rta = NLMSG_TAIL(n);
564 rta->rta_type = type;
565 rta->rta_len = len;
566 memcpy(RTA_DATA(rta), &data, 4);
567 n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
568 return 0;
569 }
570
571 int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data,
572 int alen)
573 {
574 int len = RTA_LENGTH(alen);
575 struct rtattr *rta;
576
577 if ((int)(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen) {
578 fprintf(stderr,
579 "addattr_l ERROR: message exceeded bound of %d\n",
580 maxlen);
581 return -1;
582 }
583 rta = NLMSG_TAIL(n);
584 rta->rta_type = type;
585 rta->rta_len = len;
586
587 if (data)
588 memcpy(RTA_DATA(rta), data, alen);
589 else
590 assert(alen == 0);
591
592 n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
593 return 0;
594 }
595
596 int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len)
597 {
598 if ((int)(NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len)) > maxlen) {
599 fprintf(stderr,
600 "addraw_l ERROR: message exceeded bound of %d\n",
601 maxlen);
602 return -1;
603 }
604
605 memcpy(NLMSG_TAIL(n), data, len);
606 memset((uint8_t *)NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len);
607 n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len);
608 return 0;
609 }
610
611 struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type)
612 {
613 struct rtattr *nest = NLMSG_TAIL(n);
614
615 addattr_l(n, maxlen, type, NULL, 0);
616 return nest;
617 }
618
619 int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest)
620 {
621 nest->rta_len = (uint8_t *)NLMSG_TAIL(n) - (uint8_t *)nest;
622 return n->nlmsg_len;
623 }
624
625 struct rtattr *addattr_nest_compat(struct nlmsghdr *n, int maxlen, int type,
626 const void *data, int len)
627 {
628 struct rtattr *start = NLMSG_TAIL(n);
629
630 addattr_l(n, maxlen, type, data, len);
631 addattr_nest(n, maxlen, type);
632 return start;
633 }
634
635 int addattr_nest_compat_end(struct nlmsghdr *n, struct rtattr *start)
636 {
637 struct rtattr *nest = start + NLMSG_ALIGN(start->rta_len);
638
639 start->rta_len = (uint8_t *)NLMSG_TAIL(n) - (uint8_t *)start;
640 addattr_nest_end(n, nest);
641 return n->nlmsg_len;
642 }
643
644 int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data)
645 {
646 int len = RTA_LENGTH(4);
647 struct rtattr *subrta;
648
649 if ((int)(RTA_ALIGN(rta->rta_len) + len) > maxlen) {
650 fprintf(stderr,
651 "rta_addattr32: Error! max allowed bound %d exceeded\n",
652 maxlen);
653 return -1;
654 }
655 subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len));
656 subrta->rta_type = type;
657 subrta->rta_len = len;
658 memcpy(RTA_DATA(subrta), &data, 4);
659 rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len;
660 return 0;
661 }
662
663 int rta_addattr_l(struct rtattr *rta, int maxlen, int type, const void *data,
664 int alen)
665 {
666 struct rtattr *subrta;
667 int len = RTA_LENGTH(alen);
668
669 if ((int)(RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len)) > maxlen) {
670 fprintf(stderr,
671 "rta_addattr_l: Error! max allowed bound %d exceeded\n",
672 maxlen);
673 return -1;
674 }
675 subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len));
676 subrta->rta_type = type;
677 subrta->rta_len = len;
678 memcpy(RTA_DATA(subrta), data, alen);
679 rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len);
680 return 0;
681 }
682
683 int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
684 {
685 memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
686 while (RTA_OK(rta, len)) {
687 if ((rta->rta_type <= max) && (!tb[rta->rta_type]))
688 tb[rta->rta_type] = rta;
689 rta = RTA_NEXT(rta, len);
690 }
691 if (len)
692 fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len,
693 rta->rta_len);
694 return 0;
695 }
696
697 int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta,
698 int len)
699 {
700 int i = 0;
701
702 memset(tb, 0, sizeof(struct rtattr *) * max);
703 while (RTA_OK(rta, len)) {
704 if (rta->rta_type <= max && i < max)
705 tb[i++] = rta;
706 rta = RTA_NEXT(rta, len);
707 }
708 if (len)
709 fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len,
710 rta->rta_len);
711 return i;
712 }
713
714 int __parse_rtattr_nested_compat(struct rtattr *tb[], int max,
715 struct rtattr *rta, int len)
716 {
717 if ((int)RTA_PAYLOAD(rta) < len)
718 return -1;
719 if (RTA_PAYLOAD(rta) >= RTA_ALIGN(len) + sizeof(struct rtattr)) {
720 rta = (struct rtattr *)(uint8_t *)RTA_DATA(rta)
721 + RTA_ALIGN(len);
722 return parse_rtattr_nested(tb, max, rta);
723 }
724 memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
725 return 0;
726 }
727
728 #endif /* __linux__ */