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