OpenVPN
networking_sitnl.c
Go to the documentation of this file.
1/*
2 * Simplified Interface To NetLink
3 *
4 * Copyright (C) 2016-2026 Antonio Quartulli <a@unstable.cc>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2
8 * as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program (see the file COPYING included with this
17 * distribution); if not, see <https://www.gnu.org/licenses/>.
18 */
19
20#ifdef HAVE_CONFIG_H
21#include "config.h"
22#endif
23
24#ifdef TARGET_LINUX
25
26#include "syshead.h"
27
28#include "dco.h"
29#include "errlevel.h"
30#include "fdmisc.h"
31#include "buffer.h"
32#include "misc.h"
33#include "networking.h"
34#include "proto.h"
35#include "route.h"
36
37#include <errno.h>
38#include <string.h>
39#include <unistd.h>
40#include <sys/types.h>
41#include <sys/socket.h>
42#include <linux/netlink.h>
43#include <linux/rtnetlink.h>
44
45#define SNDBUF_SIZE (1024 * 2)
46#define RCVBUF_SIZE (1024 * 4)
47
48#define SITNL_ADDATTR(_msg, _max_size, _attr, _data, _size) \
49 { \
50 if (sitnl_addattr(_msg, _max_size, _attr, _data, _size) < 0) \
51 { \
52 goto err; \
53 } \
54 }
55
56#define SITNL_NEST(_msg, _max_size, _attr) \
57 ({ \
58 struct rtattr *_nest = sitnl_nlmsg_tail(_msg); \
59 SITNL_ADDATTR(_msg, _max_size, _attr, NULL, 0); \
60 _nest; \
61 })
62
63#define SITNL_NEST_END(_msg, _nest) \
64 { \
65 _nest->rta_len = (unsigned short)((void *)sitnl_nlmsg_tail(_msg) - (void *)_nest); \
66 }
67
68/* This function was originally implemented as a macro, but compiling with
69 * gcc and -O3 was getting confused about the math and thus raising
70 * security warnings on subsequent memcpy() calls.
71 *
72 * Converting the macro to a function was not enough, because gcc was still
73 * inlining it and falling in the same math trap.
74 *
75 * The only way out to avoid any warning/error is to force the function to
76 * not be inline'd.
77 */
78static __attribute__((noinline)) void *
79sitnl_nlmsg_tail(const struct nlmsghdr *nlh)
80{
81 return (unsigned char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len);
82}
83
88typedef union
89{
90 in_addr_t ipv4;
91 struct in6_addr ipv6;
93
97struct sitnl_link_req
98{
99 struct nlmsghdr n;
100 struct ifinfomsg i;
101 char buf[256];
102};
103
107struct sitnl_addr_req
108{
109 struct nlmsghdr n;
110 struct ifaddrmsg i;
111 char buf[256];
112};
113
117struct sitnl_route_req
118{
119 struct nlmsghdr n;
120 struct rtmsg r;
121 char buf[256];
122};
123
124typedef int (*sitnl_parse_reply_cb)(struct nlmsghdr *msg, void *arg);
125
129static int
130sitnl_addattr(struct nlmsghdr *n, size_t maxlen, unsigned short type, const void *data, size_t alen)
131{
132 size_t len = RTA_LENGTH(alen);
133
134 if ((NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen)
135 {
136 msg(M_WARN, "%s: rtnl: message exceeded bound of %zu", __func__, maxlen);
137 return -EMSGSIZE;
138 }
139
140 struct rtattr *rta = sitnl_nlmsg_tail(n);
141 rta->rta_type = type;
142 ASSERT(len <= USHRT_MAX);
143 rta->rta_len = (unsigned short)len;
144
145 if (!data)
146 {
147 memset(RTA_DATA(rta), 0, alen);
148 }
149 else
150 {
151 memcpy(RTA_DATA(rta), data, alen);
152 }
153
154 n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
155
156 return 0;
157}
158
162static int
163sitnl_socket(void)
164{
165 int sndbuf = SNDBUF_SIZE;
166 int rcvbuf = RCVBUF_SIZE;
167 int fd;
168
169 fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
170 if (fd < 0)
171 {
172 msg(M_WARN, "%s: cannot open netlink socket", __func__);
173 return fd;
174 }
175
176 /* set close on exec to avoid child processes access the socket */
177 set_cloexec(fd);
178
179 if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0)
180 {
181 msg(M_WARN | M_ERRNO, "%s: SO_SNDBUF", __func__);
182 close(fd);
183 return -1;
184 }
185
186 if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0)
187 {
188 msg(M_WARN | M_ERRNO, "%s: SO_RCVBUF", __func__);
189 close(fd);
190 return -1;
191 }
192
193 return fd;
194}
195
199static int
200sitnl_bind(int fd, uint32_t groups)
201{
202 socklen_t addr_len;
203 struct sockaddr_nl local;
204
205 CLEAR(local);
206
207 local.nl_family = AF_NETLINK;
208 local.nl_groups = groups;
209
210 if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0)
211 {
212 msg(M_WARN | M_ERRNO, "%s: cannot bind netlink socket", __func__);
213 return -errno;
214 }
215
216 addr_len = sizeof(local);
217 if (getsockname(fd, (struct sockaddr *)&local, &addr_len) < 0)
218 {
219 msg(M_WARN | M_ERRNO, "%s: cannot getsockname", __func__);
220 return -errno;
221 }
222
223 if (addr_len != sizeof(local))
224 {
225 msg(M_WARN, "%s: wrong address length %d", __func__, addr_len);
226 return -EINVAL;
227 }
228
229 if (local.nl_family != AF_NETLINK)
230 {
231 msg(M_WARN, "%s: wrong address family %d", __func__, local.nl_family);
232 return -EINVAL;
233 }
234
235 return 0;
236}
237
241static int
242sitnl_send(struct nlmsghdr *payload, pid_t peer, unsigned int groups, sitnl_parse_reply_cb cb,
243 void *arg_cb)
244{
245 int fd, ret;
246 struct sockaddr_nl nladdr;
247 struct nlmsgerr *err;
248 struct nlmsghdr *h;
249 char buf[1024 * 16];
250 struct iovec iov = {
251 .iov_base = payload,
252 .iov_len = payload->nlmsg_len,
253 };
254 struct msghdr nlmsg = {
255 .msg_name = &nladdr,
256 .msg_namelen = sizeof(nladdr),
257 .msg_iov = &iov,
258 .msg_iovlen = 1,
259 };
260
261 CLEAR(nladdr);
262
263 nladdr.nl_family = AF_NETLINK;
264 nladdr.nl_pid = peer;
265 nladdr.nl_groups = groups;
266
267 /* NB: We currently do not verify seq and pid on answers.
268 * If we ever want to start with that we probably need to come up
269 * with something better than "seconds since epoch"...
270 */
271 payload->nlmsg_seq = (uint32_t)time(NULL);
272
273 /* no need to send reply */
274 if (!cb)
275 {
276 payload->nlmsg_flags |= NLM_F_ACK;
277 }
278
279 fd = sitnl_socket();
280 if (fd < 0)
281 {
282 msg(M_WARN | M_ERRNO, "%s: can't open rtnl socket", __func__);
283 return -errno;
284 }
285
286 if (sitnl_bind(fd, 0) < 0)
287 {
288 msg(M_WARN | M_ERRNO, "%s: can't bind rtnl socket", __func__);
289 ret = -errno;
290 goto out;
291 }
292
293 if (sendmsg(fd, &nlmsg, 0) < 0)
294 {
295 msg(M_WARN | M_ERRNO, "%s: rtnl: error on sendmsg()", __func__);
296 ret = -errno;
297 goto out;
298 }
299
300 /* prepare buffer to store RTNL replies */
301 memset(buf, 0, sizeof(buf));
302 iov.iov_base = buf;
303
304 while (1)
305 {
306 /*
307 * iov_len is modified by recvmsg(), therefore has to be initialized before
308 * using it again
309 */
310 msg(D_RTNL, "%s: checking for received messages", __func__);
311 iov.iov_len = sizeof(buf);
312 ssize_t rcv_len = recvmsg(fd, &nlmsg, 0);
313 msg(D_RTNL, "%s: rtnl: received %zd bytes", __func__, rcv_len);
314 if (rcv_len < 0)
315 {
316 if ((errno == EINTR) || (errno == EAGAIN))
317 {
318 msg(D_RTNL, "%s: interrupted call", __func__);
319 continue;
320 }
321 msg(M_WARN | M_ERRNO, "%s: rtnl: error on recvmsg()", __func__);
322 ret = -errno;
323 goto out;
324 }
325
326 if (rcv_len == 0)
327 {
328 msg(M_WARN, "%s: rtnl: socket reached unexpected EOF", __func__);
329 ret = -EIO;
330 goto out;
331 }
332
333 if (nlmsg.msg_namelen != sizeof(nladdr))
334 {
335 msg(M_WARN, "%s: sender address length: %u (expected %zu)", __func__, nlmsg.msg_namelen,
336 sizeof(nladdr));
337 ret = -EIO;
338 goto out;
339 }
340
341 h = (struct nlmsghdr *)buf;
342 while (rcv_len >= (int)sizeof(*h))
343 {
344 uint32_t len = h->nlmsg_len;
345 ssize_t rem_len = len - sizeof(*h);
346
347 if ((rem_len < 0) || (len > rcv_len))
348 {
349 if (nlmsg.msg_flags & MSG_TRUNC)
350 {
351 msg(M_WARN, "%s: truncated message", __func__);
352 ret = -EIO;
353 goto out;
354 }
355 msg(M_WARN, "%s: malformed message: len=%u", __func__, len);
356 ret = -EIO;
357 goto out;
358 }
359
360 /* if (((int)nladdr.nl_pid != peer) || (h->nlmsg_pid != nladdr.nl_pid)
361 * || (h->nlmsg_seq != seq))
362 * {
363 * rcv_len -= NLMSG_ALIGN(len);
364 * h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
365 * msg(M_DEBUG, "%s: skipping unrelated message. nl_pid:%d (peer:%d)
366 * nl_msg_pid:%d nl_seq:%d seq:%d",
367 * __func__, (int)nladdr.nl_pid, peer, h->nlmsg_pid,
368 * h->nlmsg_seq, seq);
369 * continue;
370 * }
371 */
372
373 if (h->nlmsg_type == NLMSG_DONE)
374 {
375 ret = 0;
376 goto out;
377 }
378
379 if (h->nlmsg_type == NLMSG_ERROR)
380 {
381 err = (struct nlmsgerr *)NLMSG_DATA(h);
382 if (rem_len < (int)sizeof(struct nlmsgerr))
383 {
384 msg(M_WARN, "%s: ERROR truncated", __func__);
385 ret = -EIO;
386 }
387 else
388 {
389 if (!err->error)
390 {
391 ret = 0;
392 if (cb)
393 {
394 int r = cb(h, arg_cb);
395 if (r <= 0)
396 {
397 ret = r;
398 }
399 }
400 }
401 else
402 {
403 msg(M_WARN, "%s: rtnl: generic error (%d): %s", __func__, err->error,
404 strerror(-err->error));
405 ret = err->error;
406 }
407 }
408 goto out;
409 }
410
411 if (cb)
412 {
413 int r = cb(h, arg_cb);
414 if (r <= 0)
415 {
416 ret = r;
417 goto out;
418 }
419 }
420 else
421 {
422 msg(M_WARN, "%s: RTNL: unexpected reply", __func__);
423 }
424
425 rcv_len -= NLMSG_ALIGN(len);
426 h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
427 }
428
429 if (nlmsg.msg_flags & MSG_TRUNC)
430 {
431 msg(M_WARN, "%s: message truncated", __func__);
432 continue;
433 }
434
435 if (rcv_len)
436 {
437 msg(M_WARN, "%s: rtnl: %zd not parsed bytes", __func__, rcv_len);
438 ret = -1;
439 goto out;
440 }
441 }
442out:
443 close(fd);
444
445 return ret;
446}
447
448typedef struct
449{
450 size_t addr_size;
452 char iface[IFNAMSIZ];
453 bool default_only;
454 unsigned int table;
455} route_res_t;
456
457static int
458sitnl_route_save(struct nlmsghdr *n, void *arg)
459{
460 route_res_t *res = arg;
461 struct rtmsg *r = NLMSG_DATA(n);
462 struct rtattr *rta = RTM_RTA(r);
463 size_t len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
464 unsigned int table, ifindex = 0;
465 void *gw = NULL;
466
467 /* filter-out non-zero dst prefixes */
468 if (res->default_only && r->rtm_dst_len != 0)
469 {
470 return 1;
471 }
472
473 /* route table, ignored with RTA_TABLE */
474 table = r->rtm_table;
475
476 while (RTA_OK(rta, len))
477 {
478 switch (rta->rta_type)
479 {
480 /* route interface */
481 case RTA_OIF:
482 ifindex = *(unsigned int *)RTA_DATA(rta);
483 break;
484
485 /* route prefix */
486 case RTA_DST:
487 break;
488
489 /* GW for the route */
490 case RTA_GATEWAY:
491 gw = RTA_DATA(rta);
492 break;
493
494 /* route table */
495 case RTA_TABLE:
496 table = *(unsigned int *)RTA_DATA(rta);
497 break;
498 }
499
500 rta = RTA_NEXT(rta, len);
501 }
502
503 /* filter out any route not coming from the selected table */
504 if (res->table && res->table != table)
505 {
506 return 1;
507 }
508
509 if (!if_indextoname(ifindex, res->iface))
510 {
511 msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifname for index %d", __func__, ifindex);
512 return -1;
513 }
514
515 if (gw)
516 {
517 memcpy(&res->gw, gw, res->addr_size);
518 }
519
520 return 0;
521}
522
523static int
524sitnl_route_best_gw(sa_family_t af_family, const inet_address_t *dst, void *best_gw,
525 char *best_iface)
526{
527 struct sitnl_route_req req;
528 route_res_t res;
529 int ret = -EINVAL;
530
531 ASSERT(best_gw);
532 ASSERT(best_iface);
533
534 CLEAR(req);
535 CLEAR(res);
536
537 req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.r));
538 req.n.nlmsg_type = RTM_GETROUTE;
539 req.n.nlmsg_flags = NLM_F_REQUEST;
540
541 ASSERT(af_family <= UCHAR_MAX);
542 req.r.rtm_family = (unsigned char)af_family;
543
544 switch (af_family)
545 {
546 case AF_INET:
547 res.addr_size = sizeof(in_addr_t);
548 /*
549 * kernel can't return 0.0.0.0/8 host route, dump all
550 * the routes and filter for 0.0.0.0/0 in cb()
551 */
552 if (!dst || !dst->ipv4)
553 {
554 req.n.nlmsg_flags |= NLM_F_DUMP;
555 res.default_only = true;
556 res.table = RT_TABLE_MAIN;
557 }
558 else
559 {
560 req.r.rtm_dst_len = 32;
561 }
562 break;
563
564 case AF_INET6:
565 res.addr_size = sizeof(struct in6_addr);
566 /* kernel can return ::/128 host route */
567 req.r.rtm_dst_len = 128;
568 break;
569
570 default:
571 /* unsupported */
572 return -EINVAL;
573 }
574
575 SITNL_ADDATTR(&req.n, sizeof(req), RTA_DST, dst, res.addr_size);
576
577 ret = sitnl_send(&req.n, 0, 0, sitnl_route_save, &res);
578 if (ret < 0)
579 {
580 goto err;
581 }
582
583 /* save result in output variables */
584 memcpy(best_gw, &res.gw, res.addr_size);
585 strncpy(best_iface, res.iface, IFNAMSIZ);
586err:
587 return ret;
588}
589
590/* used by iproute2 implementation too */
591int
592net_route_v6_best_gw(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, struct in6_addr *best_gw,
593 char *best_iface)
594{
595 inet_address_t dst_v6 = { 0 };
596 char buf[INET6_ADDRSTRLEN];
597 int ret;
598
599 if (dst)
600 {
601 dst_v6.ipv6 = *dst;
602 }
603
604 msg(D_ROUTE, "%s query: dst %s", __func__, inet_ntop(AF_INET6, &dst_v6.ipv6, buf, sizeof(buf)));
605
606 ret = sitnl_route_best_gw(AF_INET6, &dst_v6, best_gw, best_iface);
607 if (ret < 0)
608 {
609 return ret;
610 }
611
612 msg(D_ROUTE, "%s result: via %s dev %s", __func__,
613 inet_ntop(AF_INET6, best_gw, buf, sizeof(buf)), best_iface);
614
615 return ret;
616}
617
618/* used by iproute2 implementation too */
619int
620net_route_v4_best_gw(openvpn_net_ctx_t *ctx, const in_addr_t *dst, in_addr_t *best_gw,
621 char *best_iface)
622{
623 inet_address_t dst_v4 = { 0 };
624 char buf[INET_ADDRSTRLEN];
625 int ret;
626
627 if (dst)
628 {
629 dst_v4.ipv4 = htonl(*dst);
630 }
631
632 msg(D_ROUTE, "%s query: dst %s", __func__, inet_ntop(AF_INET, &dst_v4.ipv4, buf, sizeof(buf)));
633
634 ret = sitnl_route_best_gw(AF_INET, &dst_v4, best_gw, best_iface);
635 if (ret < 0)
636 {
637 return ret;
638 }
639
640 msg(D_ROUTE, "%s result: via %s dev %s", __func__,
641 inet_ntop(AF_INET, best_gw, buf, sizeof(buf)), best_iface);
642
643 /* result is expected in Host Order */
644 *best_gw = ntohl(*best_gw);
645
646 return ret;
647}
648
649#ifdef ENABLE_SITNL
650
651int
652net_iface_up(openvpn_net_ctx_t *ctx, const char *iface, bool up)
653{
654 struct sitnl_link_req req;
655 int ifindex;
656
657 CLEAR(req);
658
659 if (!iface)
660 {
661 msg(M_WARN, "%s: passed NULL interface", __func__);
662 return -EINVAL;
663 }
664
665 ifindex = if_nametoindex(iface);
666 if (ifindex == 0)
667 {
668 msg(M_WARN, "%s: rtnl: cannot get ifindex for %s: %s", __func__, iface, strerror(errno));
669 return -ENOENT;
670 }
671
672 req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
673 req.n.nlmsg_flags = NLM_F_REQUEST;
674 req.n.nlmsg_type = RTM_NEWLINK;
675
676 req.i.ifi_family = AF_PACKET;
677 req.i.ifi_index = ifindex;
678 req.i.ifi_change |= IFF_UP;
679 if (up)
680 {
681 req.i.ifi_flags |= IFF_UP;
682 }
683 else
684 {
685 req.i.ifi_flags &= ~IFF_UP;
686 }
687
688 msg(M_INFO, "%s: set %s %s", __func__, iface, up ? "up" : "down");
689
690 return sitnl_send(&req.n, 0, 0, NULL, NULL);
691}
692
693int
694net_iface_mtu_set(openvpn_net_ctx_t *ctx, const char *iface, uint32_t mtu)
695{
696 struct sitnl_link_req req;
697 int ifindex, ret = -1;
698
699 CLEAR(req);
700
701 ifindex = if_nametoindex(iface);
702 if (ifindex == 0)
703 {
704 msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__, iface);
705 return -1;
706 }
707
708 req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
709 req.n.nlmsg_flags = NLM_F_REQUEST;
710 req.n.nlmsg_type = RTM_NEWLINK;
711
712 req.i.ifi_family = AF_PACKET;
713 req.i.ifi_index = ifindex;
714
715 SITNL_ADDATTR(&req.n, sizeof(req), IFLA_MTU, &mtu, 4);
716
717 msg(M_INFO, "%s: mtu %u for %s", __func__, mtu, iface);
718
719 ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
720err:
721 return ret;
722}
723
724int
725net_addr_ll_set(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface, uint8_t *addr)
726{
727 struct sitnl_link_req req;
728 int ifindex, ret = -1;
729
730 CLEAR(req);
731
732 ifindex = if_nametoindex(iface);
733 if (ifindex == 0)
734 {
735 msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__, iface);
736 return -1;
737 }
738
739 req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
740 req.n.nlmsg_flags = NLM_F_REQUEST;
741 req.n.nlmsg_type = RTM_NEWLINK;
742
743 req.i.ifi_family = AF_PACKET;
744 req.i.ifi_index = ifindex;
745
746 SITNL_ADDATTR(&req.n, sizeof(req), IFLA_ADDRESS, addr, OPENVPN_ETH_ALEN);
747
748 msg(M_INFO, "%s: lladdr " MAC_FMT " for %s", __func__, MAC_PRINT_ARG(addr), iface);
749
750 ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
751err:
752 return ret;
753}
754
755static int
756sitnl_addr_set(uint16_t cmd, uint16_t flags, int ifindex, sa_family_t af_family,
757 const inet_address_t *local, const inet_address_t *remote, int prefixlen)
758{
759 struct sitnl_addr_req req;
760 uint32_t size;
761 int ret = -EINVAL;
762
763 CLEAR(req);
764
765 req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
766 req.n.nlmsg_type = cmd;
767 req.n.nlmsg_flags = NLM_F_REQUEST | flags;
768
769 req.i.ifa_index = ifindex;
770 ASSERT(af_family <= UINT8_MAX);
771 req.i.ifa_family = (uint8_t)af_family;
772
773 switch (af_family)
774 {
775 case AF_INET:
776 size = sizeof(struct in_addr);
777 break;
778
779 case AF_INET6:
780 size = sizeof(struct in6_addr);
781 break;
782
783 default:
784 msg(M_WARN, "%s: rtnl: unknown address family %d", __func__, af_family);
785 return -EINVAL;
786 }
787
788 /* if no prefixlen has been specified, assume host address */
789 if (prefixlen == 0)
790 {
791 prefixlen = size * 8;
792 }
793 ASSERT(prefixlen <= UINT8_MAX);
794 req.i.ifa_prefixlen = (uint8_t)prefixlen;
795
796 if (remote)
797 {
798 SITNL_ADDATTR(&req.n, sizeof(req), IFA_ADDRESS, remote, size);
799 }
800
801 if (local)
802 {
803 SITNL_ADDATTR(&req.n, sizeof(req), IFA_LOCAL, local, size);
804 }
805
806 if (af_family == AF_INET && local && !remote && prefixlen <= 30)
807 {
808 inet_address_t broadcast = *local;
809 broadcast.ipv4 |= htonl(~netbits_to_netmask(prefixlen));
810 SITNL_ADDATTR(&req.n, sizeof(req), IFA_BROADCAST, &broadcast, size);
811 }
812
813 ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
814 if (ret == -EEXIST)
815 {
816 ret = 0;
817 }
818err:
819 return ret;
820}
821
822static int
823sitnl_addr_ptp_add(sa_family_t af_family, const char *iface, const inet_address_t *local,
824 const inet_address_t *remote)
825{
826 int ifindex;
827
828 switch (af_family)
829 {
830 case AF_INET:
831 case AF_INET6:
832 break;
833
834 default:
835 return -EINVAL;
836 }
837
838 if (!iface)
839 {
840 msg(M_WARN, "%s: passed NULL interface", __func__);
841 return -EINVAL;
842 }
843
844 ifindex = if_nametoindex(iface);
845 if (ifindex == 0)
846 {
847 msg(M_WARN, "%s: cannot get ifindex for %s: %s", __func__, np(iface), strerror(errno));
848 return -ENOENT;
849 }
850
851 return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, ifindex, af_family, local,
852 remote, 0);
853}
854
855static int
856sitnl_addr_ptp_del(sa_family_t af_family, const char *iface, const inet_address_t *local)
857{
858 int ifindex;
859
860 switch (af_family)
861 {
862 case AF_INET:
863 case AF_INET6:
864 break;
865
866 default:
867 return -EINVAL;
868 }
869
870 if (!iface)
871 {
872 msg(M_WARN, "%s: passed NULL interface", __func__);
873 return -EINVAL;
874 }
875
876 ifindex = if_nametoindex(iface);
877 if (ifindex == 0)
878 {
879 msg(M_WARN | M_ERRNO, "%s: cannot get ifindex for %s", __func__, iface);
880 return -ENOENT;
881 }
882
883 return sitnl_addr_set(RTM_DELADDR, 0, ifindex, af_family, local, NULL, 0);
884}
885
886static int
887sitnl_route_set(uint16_t cmd, uint16_t flags, int ifindex, sa_family_t af_family, const void *dst,
888 int prefixlen, const void *gw, enum rt_class_t table, int metric,
889 enum rt_scope_t scope, unsigned char protocol, unsigned char type)
890{
891 struct sitnl_route_req req;
892 int ret = -1, size;
893
894 CLEAR(req);
895
896 switch (af_family)
897 {
898 case AF_INET:
899 size = sizeof(in_addr_t);
900 break;
901
902 case AF_INET6:
903 size = sizeof(struct in6_addr);
904 break;
905
906 default:
907 return -EINVAL;
908 }
909
910 req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.r));
911 req.n.nlmsg_type = cmd;
912 req.n.nlmsg_flags = NLM_F_REQUEST | flags;
913
914 ASSERT(af_family <= UCHAR_MAX);
915 req.r.rtm_family = (unsigned char)af_family;
916 req.r.rtm_scope = (unsigned char)scope;
917 req.r.rtm_protocol = protocol;
918 req.r.rtm_type = type;
919 ASSERT(prefixlen >= 0 && prefixlen <= UCHAR_MAX);
920 req.r.rtm_dst_len = (unsigned char)prefixlen;
921
922 if (table <= UCHAR_MAX)
923 {
924 req.r.rtm_table = (unsigned char)table;
925 }
926 else
927 {
928 req.r.rtm_table = RT_TABLE_UNSPEC;
929 SITNL_ADDATTR(&req.n, sizeof(req), RTA_TABLE, &table, 4);
930 }
931
932 if (dst)
933 {
934 SITNL_ADDATTR(&req.n, sizeof(req), RTA_DST, dst, size);
935 }
936
937 if (gw)
938 {
939 SITNL_ADDATTR(&req.n, sizeof(req), RTA_GATEWAY, gw, size);
940 }
941
942 if (ifindex > 0)
943 {
944 SITNL_ADDATTR(&req.n, sizeof(req), RTA_OIF, &ifindex, 4);
945 }
946
947 if (metric > 0)
948 {
949 SITNL_ADDATTR(&req.n, sizeof(req), RTA_PRIORITY, &metric, 4);
950 }
951
952 ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
953err:
954 return ret;
955}
956
957static int
958sitnl_addr_add(sa_family_t af_family, const char *iface, const inet_address_t *addr, int prefixlen)
959{
960 int ifindex;
961
962 switch (af_family)
963 {
964 case AF_INET:
965 case AF_INET6:
966 break;
967
968 default:
969 return -EINVAL;
970 }
971
972 if (!iface)
973 {
974 msg(M_WARN, "%s: passed NULL interface", __func__);
975 return -EINVAL;
976 }
977
978 ifindex = if_nametoindex(iface);
979 if (ifindex == 0)
980 {
981 msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__, iface);
982 return -ENOENT;
983 }
984
985 return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, ifindex, af_family, addr, NULL,
986 prefixlen);
987}
988
989static int
990sitnl_addr_del(sa_family_t af_family, const char *iface, inet_address_t *addr, int prefixlen)
991{
992 int ifindex;
993
994 switch (af_family)
995 {
996 case AF_INET:
997 case AF_INET6:
998 break;
999
1000 default:
1001 return -EINVAL;
1002 }
1003
1004 if (!iface)
1005 {
1006 msg(M_WARN, "%s: passed NULL interface", __func__);
1007 return -EINVAL;
1008 }
1009
1010 ifindex = if_nametoindex(iface);
1011 if (ifindex == 0)
1012 {
1013 msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__, iface);
1014 return -ENOENT;
1015 }
1016
1017 return sitnl_addr_set(RTM_DELADDR, 0, ifindex, af_family, addr, NULL, prefixlen);
1018}
1019
1020int
1021net_addr_v4_add(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *addr, int prefixlen)
1022{
1023 inet_address_t addr_v4 = { 0 };
1024 char buf[INET_ADDRSTRLEN];
1025
1026 if (!addr)
1027 {
1028 return -EINVAL;
1029 }
1030
1031 addr_v4.ipv4 = htonl(*addr);
1032
1033 msg(M_INFO, "%s: %s/%d dev %s", __func__, inet_ntop(AF_INET, &addr_v4.ipv4, buf, sizeof(buf)),
1034 prefixlen, iface);
1035
1036 return sitnl_addr_add(AF_INET, iface, &addr_v4, prefixlen);
1037}
1038
1039int
1040net_addr_v6_add(openvpn_net_ctx_t *ctx, const char *iface, const struct in6_addr *addr,
1041 int prefixlen)
1042{
1043 inet_address_t addr_v6 = { 0 };
1044 char buf[INET6_ADDRSTRLEN];
1045
1046 if (!addr)
1047 {
1048 return -EINVAL;
1049 }
1050
1051 addr_v6.ipv6 = *addr;
1052
1053 msg(M_INFO, "%s: %s/%d dev %s", __func__, inet_ntop(AF_INET6, &addr_v6.ipv6, buf, sizeof(buf)),
1054 prefixlen, iface);
1055
1056 return sitnl_addr_add(AF_INET6, iface, &addr_v6, prefixlen);
1057}
1058
1059int
1060net_addr_v4_del(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *addr, int prefixlen)
1061{
1062 inet_address_t addr_v4 = { 0 };
1063 char buf[INET_ADDRSTRLEN];
1064
1065 if (!addr)
1066 {
1067 return -EINVAL;
1068 }
1069
1070 addr_v4.ipv4 = htonl(*addr);
1071
1072 msg(M_INFO, "%s: %s dev %s", __func__, inet_ntop(AF_INET, &addr_v4.ipv4, buf, sizeof(buf)),
1073 iface);
1074
1075 return sitnl_addr_del(AF_INET, iface, &addr_v4, prefixlen);
1076}
1077
1078int
1079net_addr_v6_del(openvpn_net_ctx_t *ctx, const char *iface, const struct in6_addr *addr,
1080 int prefixlen)
1081{
1082 inet_address_t addr_v6 = { 0 };
1083 char buf[INET6_ADDRSTRLEN];
1084
1085 if (!addr)
1086 {
1087 return -EINVAL;
1088 }
1089
1090 addr_v6.ipv6 = *addr;
1091
1092 msg(M_INFO, "%s: %s/%d dev %s", __func__, inet_ntop(AF_INET6, &addr_v6.ipv6, buf, sizeof(buf)),
1093 prefixlen, iface);
1094
1095 return sitnl_addr_del(AF_INET6, iface, &addr_v6, prefixlen);
1096}
1097
1098int
1099net_addr_ptp_v4_add(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *local,
1100 const in_addr_t *remote)
1101{
1102 inet_address_t local_v4 = { 0 };
1103 inet_address_t remote_v4 = { 0 };
1104 char buf1[INET_ADDRSTRLEN];
1105 char buf2[INET_ADDRSTRLEN];
1106
1107 if (!local)
1108 {
1109 return -EINVAL;
1110 }
1111
1112 local_v4.ipv4 = htonl(*local);
1113
1114 if (remote)
1115 {
1116 remote_v4.ipv4 = htonl(*remote);
1117 }
1118
1119 msg(M_INFO, "%s: %s peer %s dev %s", __func__,
1120 inet_ntop(AF_INET, &local_v4.ipv4, buf1, sizeof(buf1)),
1121 inet_ntop(AF_INET, &remote_v4.ipv4, buf2, sizeof(buf2)), iface);
1122
1123 return sitnl_addr_ptp_add(AF_INET, iface, &local_v4, &remote_v4);
1124}
1125
1126int
1127net_addr_ptp_v4_del(openvpn_net_ctx_t *ctx, const char *iface, const in_addr_t *local,
1128 const in_addr_t *remote)
1129{
1130 inet_address_t local_v4 = { 0 };
1131 char buf[INET6_ADDRSTRLEN];
1132
1133
1134 if (!local)
1135 {
1136 return -EINVAL;
1137 }
1138
1139 local_v4.ipv4 = htonl(*local);
1140
1141 msg(M_INFO, "%s: %s dev %s", __func__, inet_ntop(AF_INET, &local_v4.ipv4, buf, sizeof(buf)),
1142 iface);
1143
1144 return sitnl_addr_ptp_del(AF_INET, iface, &local_v4);
1145}
1146
1147static int
1148sitnl_route_add(const char *iface, sa_family_t af_family, const void *dst, int prefixlen,
1149 const void *gw, uint32_t table, int metric)
1150{
1151 enum rt_scope_t scope = RT_SCOPE_UNIVERSE;
1152 int ifindex = 0;
1153
1154 if (iface)
1155 {
1156 ifindex = if_nametoindex(iface);
1157 if (ifindex == 0)
1158 {
1159 msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifindex for %s", __func__, iface);
1160 return -ENOENT;
1161 }
1162 }
1163
1164 if (table == 0)
1165 {
1166 table = RT_TABLE_MAIN;
1167 }
1168
1169 if (!gw && iface)
1170 {
1171 scope = RT_SCOPE_LINK;
1172 }
1173
1174 return sitnl_route_set(RTM_NEWROUTE, NLM_F_CREATE, ifindex, af_family, dst, prefixlen, gw,
1175 table, metric, scope, RTPROT_BOOT, RTN_UNICAST);
1176}
1177
1178int
1179net_route_v4_add(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen, const in_addr_t *gw,
1180 const char *iface, uint32_t table, int metric)
1181{
1182 in_addr_t *dst_ptr = NULL, *gw_ptr = NULL;
1183 in_addr_t dst_be = 0, gw_be = 0;
1184 char dst_str[INET_ADDRSTRLEN];
1185 char gw_str[INET_ADDRSTRLEN];
1186
1187 if (dst)
1188 {
1189 dst_be = htonl(*dst);
1190 dst_ptr = &dst_be;
1191 }
1192
1193 if (gw)
1194 {
1195 gw_be = htonl(*gw);
1196 gw_ptr = &gw_be;
1197 }
1198
1199 msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
1200 inet_ntop(AF_INET, &dst_be, dst_str, sizeof(dst_str)), prefixlen,
1201 inet_ntop(AF_INET, &gw_be, gw_str, sizeof(gw_str)), np(iface), table, metric);
1202
1203 return sitnl_route_add(iface, AF_INET, dst_ptr, prefixlen, gw_ptr, table, metric);
1204}
1205
1206int
1207net_route_v6_add(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, int prefixlen,
1208 const struct in6_addr *gw, const char *iface, uint32_t table, int metric)
1209{
1210 inet_address_t dst_v6 = { 0 };
1211 inet_address_t gw_v6 = { 0 };
1212 char dst_str[INET6_ADDRSTRLEN];
1213 char gw_str[INET6_ADDRSTRLEN];
1214
1215 if (dst)
1216 {
1217 dst_v6.ipv6 = *dst;
1218 }
1219
1220 if (gw)
1221 {
1222 gw_v6.ipv6 = *gw;
1223 }
1224
1225 msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
1226 inet_ntop(AF_INET6, &dst_v6.ipv6, dst_str, sizeof(dst_str)), prefixlen,
1227 inet_ntop(AF_INET6, &gw_v6.ipv6, gw_str, sizeof(gw_str)), np(iface), table, metric);
1228
1229 return sitnl_route_add(iface, AF_INET6, dst, prefixlen, gw, table, metric);
1230}
1231
1232static int
1233sitnl_route_del(const char *iface, sa_family_t af_family, inet_address_t *dst, int prefixlen,
1234 inet_address_t *gw, uint32_t table, int metric)
1235{
1236 int ifindex = 0;
1237
1238 if (iface)
1239 {
1240 ifindex = if_nametoindex(iface);
1241 if (ifindex == 0)
1242 {
1243 msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifindex for %s", __func__, iface);
1244 return -ENOENT;
1245 }
1246 }
1247
1248 if (table == 0)
1249 {
1250 table = RT_TABLE_MAIN;
1251 }
1252
1253 return sitnl_route_set(RTM_DELROUTE, 0, ifindex, af_family, dst, prefixlen, gw, table, metric,
1254 RT_SCOPE_NOWHERE, 0, 0);
1255}
1256
1257int
1258net_route_v4_del(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen, const in_addr_t *gw,
1259 const char *iface, uint32_t table, int metric)
1260{
1261 inet_address_t dst_v4 = { 0 };
1262 inet_address_t gw_v4 = { 0 };
1263 char dst_str[INET_ADDRSTRLEN];
1264 char gw_str[INET_ADDRSTRLEN];
1265
1266 if (dst)
1267 {
1268 dst_v4.ipv4 = htonl(*dst);
1269 }
1270
1271 if (gw)
1272 {
1273 gw_v4.ipv4 = htonl(*gw);
1274 }
1275
1276 msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
1277 inet_ntop(AF_INET, &dst_v4.ipv4, dst_str, sizeof(dst_str)), prefixlen,
1278 inet_ntop(AF_INET, &gw_v4.ipv4, gw_str, sizeof(gw_str)), np(iface), table, metric);
1279
1280 return sitnl_route_del(iface, AF_INET, &dst_v4, prefixlen, &gw_v4, table, metric);
1281}
1282
1283int
1284net_route_v6_del(openvpn_net_ctx_t *ctx, const struct in6_addr *dst, int prefixlen,
1285 const struct in6_addr *gw, const char *iface, uint32_t table, int metric)
1286{
1287 inet_address_t dst_v6 = { 0 };
1288 inet_address_t gw_v6 = { 0 };
1289 char dst_str[INET6_ADDRSTRLEN];
1290 char gw_str[INET6_ADDRSTRLEN];
1291
1292 if (dst)
1293 {
1294 dst_v6.ipv6 = *dst;
1295 }
1296
1297 if (gw)
1298 {
1299 gw_v6.ipv6 = *gw;
1300 }
1301
1302 msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
1303 inet_ntop(AF_INET6, &dst_v6.ipv6, dst_str, sizeof(dst_str)), prefixlen,
1304 inet_ntop(AF_INET6, &gw_v6.ipv6, gw_str, sizeof(gw_str)), np(iface), table, metric);
1305
1306 return sitnl_route_del(iface, AF_INET6, &dst_v6, prefixlen, &gw_v6, table, metric);
1307}
1308
1309
1310int
1311net_iface_new(openvpn_net_ctx_t *ctx, const char *iface, const char *type, void *arg)
1312{
1313 struct sitnl_link_req req = {};
1314 int ret = -1;
1315
1316 ASSERT(iface);
1317
1318 req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
1319 req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
1320 req.n.nlmsg_type = RTM_NEWLINK;
1321
1322 SITNL_ADDATTR(&req.n, sizeof(req), IFLA_IFNAME, iface, strlen(iface) + 1);
1323
1324 struct rtattr *linkinfo = SITNL_NEST(&req.n, sizeof(req), IFLA_LINKINFO);
1325 SITNL_ADDATTR(&req.n, sizeof(req), IFLA_INFO_KIND, type, strlen(type) + 1);
1326#if defined(ENABLE_DCO)
1327 if (arg && (strcmp(type, OVPN_FAMILY_NAME) == 0))
1328 {
1329 dco_context_t *dco = arg;
1330 struct rtattr *data = SITNL_NEST(&req.n, sizeof(req), IFLA_INFO_DATA);
1331
1332 /* the netlink format is uint8_t for this and using something
1333 * other than uint8_t here (enum underlying type is undefined but
1334 * commonly int) causes the values to be 0 when passed
1335 * on big endian arch as we only take the (biggest endian) byte
1336 * directly at the address
1337 */
1338 uint8_t ifmode = (uint8_t)dco->ifmode;
1339 SITNL_ADDATTR(&req.n, sizeof(req), IFLA_OVPN_MODE, &ifmode, sizeof(uint8_t));
1340 SITNL_NEST_END(&req.n, data);
1341 }
1342#endif
1343 SITNL_NEST_END(&req.n, linkinfo);
1344
1345 req.i.ifi_family = AF_PACKET;
1346
1347 msg(D_ROUTE, "%s: add %s type %s", __func__, iface, type);
1348
1349 ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
1350err:
1351 return ret;
1352}
1353
1354static void
1355sitnl_parse_rtattr_flags(struct rtattr *tb[], size_t max, struct rtattr *rta, size_t len,
1356 unsigned short flags)
1357{
1358 unsigned short type;
1359
1360 memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
1361
1362 while (RTA_OK(rta, len))
1363 {
1364 type = rta->rta_type & ~flags;
1365
1366 if ((type <= max) && (!tb[type]))
1367 {
1368 tb[type] = rta;
1369 }
1370
1371 rta = RTA_NEXT(rta, len);
1372 }
1373
1374 if (len)
1375 {
1376 msg(D_ROUTE, "%s: %zu bytes not parsed! (rta_len=%u)", __func__, len, rta->rta_len);
1377 }
1378}
1379
1380static void
1381sitnl_parse_rtattr(struct rtattr *tb[], size_t max, struct rtattr *rta, size_t len)
1382{
1383 sitnl_parse_rtattr_flags(tb, max, rta, len, 0);
1384}
1385
1386#define sitnl_parse_rtattr_nested(tb, max, rta) \
1387 (sitnl_parse_rtattr_flags(tb, max, RTA_DATA(rta), RTA_PAYLOAD(rta), NLA_F_NESTED))
1388
1389static int
1390sitnl_type_save(struct nlmsghdr *n, void *arg)
1391{
1392 char *type = arg;
1393 struct ifinfomsg *ifi = NLMSG_DATA(n);
1394 struct rtattr *tb[IFLA_MAX + 1];
1395
1396 sitnl_parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n));
1397
1398 if (tb[IFLA_LINKINFO])
1399 {
1400 struct rtattr *tb_link[IFLA_INFO_MAX + 1];
1401
1402 sitnl_parse_rtattr_nested(tb_link, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
1403
1404 if (!tb_link[IFLA_INFO_KIND])
1405 {
1406 return -ENOENT;
1407 }
1408
1409 strncpynt(type, RTA_DATA(tb_link[IFLA_INFO_KIND]), IFACE_TYPE_LEN_MAX);
1410 }
1411
1412 return 0;
1413}
1414
1415int
1416net_iface_type(openvpn_net_ctx_t *ctx, const char *iface, char type[IFACE_TYPE_LEN_MAX])
1417{
1418 struct sitnl_link_req req = {};
1419 int ifindex = if_nametoindex(iface);
1420
1421 if (!ifindex)
1422 {
1423 return -errno;
1424 }
1425
1426 req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
1427 req.n.nlmsg_flags = NLM_F_REQUEST;
1428 req.n.nlmsg_type = RTM_GETLINK;
1429
1430 req.i.ifi_family = AF_PACKET;
1431 req.i.ifi_index = ifindex;
1432
1433 memset(type, 0, IFACE_TYPE_LEN_MAX);
1434
1435 int ret = sitnl_send(&req.n, 0, 0, sitnl_type_save, type);
1436 if (ret < 0)
1437 {
1438 msg(D_ROUTE, "%s: cannot retrieve iface %s: %s (%d)", __func__, iface, strerror(-ret), ret);
1439 return ret;
1440 }
1441
1442 msg(D_ROUTE, "%s: type of %s: %s", __func__, iface, type);
1443
1444 return 0;
1445}
1446
1447int
1448net_iface_del(openvpn_net_ctx_t *ctx, const char *iface)
1449{
1450 struct sitnl_link_req req = {};
1451 int ifindex = if_nametoindex(iface);
1452
1453 if (!ifindex)
1454 {
1455 return -errno;
1456 }
1457
1458 req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
1459 req.n.nlmsg_flags = NLM_F_REQUEST;
1460 req.n.nlmsg_type = RTM_DELLINK;
1461
1462 req.i.ifi_family = AF_PACKET;
1463 req.i.ifi_index = ifindex;
1464
1465 msg(D_ROUTE, "%s: delete %s", __func__, iface);
1466
1467 return sitnl_send(&req.n, 0, 0, NULL, NULL);
1468}
1469
1470#endif /* !ENABLE_SITNL */
1471
1472#endif /* TARGET_LINUX */
static void strncpynt(char *dest, const char *src, size_t maxlen)
Definition buffer.h:362
void * dco_context_t
Definition dco.h:259
#define D_RTNL
Definition errlevel.h:111
#define M_INFO
Definition errlevel.h:54
#define D_ROUTE
Definition errlevel.h:79
void set_cloexec(socket_descriptor_t fd)
Definition fdmisc.c:78
#define MAC_PRINT_ARG(_mac)
Definition misc.h:227
#define MAC_FMT
Definition misc.h:225
static const char * np(const char *str)
Definition multi-auth.c:146
void * openvpn_net_iface_t
Definition networking.h:39
#define IFACE_TYPE_LEN_MAX
Definition networking.h:25
void * openvpn_net_ctx_t
Definition networking.h:38
#define CLEAR(x)
Definition basic.h:32
#define msg(flags,...)
Definition error.h:152
#define ASSERT(x)
Definition error.h:219
#define M_WARN
Definition error.h:92
#define M_ERRNO
Definition error.h:95
#define OVPN_FAMILY_NAME
#define OPENVPN_ETH_ALEN
Definition proto.h:52
static in_addr_t netbits_to_netmask(const int netbits)
Definition route.h:401
unsigned short sa_family_t
Definition syshead.h:409
uint32_t in_addr_t
Definition syshead.h:52
__attribute__((unused))
Definition test.c:42
static char * iface
struct in6_addr ipv6
Definition openvpn-msg.h:64
struct in_addr ipv4
Definition openvpn-msg.h:63