summaryrefslogtreecommitdiff
path: root/src/openvpn/networking_sitnl.c
diff options
context:
space:
mode:
authorBernhard Schmidt <berni@debian.org>2020-09-01 16:52:17 +0200
committerBernhard Schmidt <berni@debian.org>2020-09-01 16:52:17 +0200
commit9fc3b98112217f2d92a67977dbde0987cc7a1803 (patch)
tree29fcc8654ee65d9dd89ade797bea2f3d9dfd9cfd /src/openvpn/networking_sitnl.c
parenta8758c0e03eed188dcb9da0e4fd781a67c25bf1e (diff)
parent69b02b1f7fd609d84ace13ab04697158de2418a9 (diff)
Merge branch 'debian/experimental-2.5'
Diffstat (limited to 'src/openvpn/networking_sitnl.c')
-rw-r--r--src/openvpn/networking_sitnl.c1246
1 files changed, 1246 insertions, 0 deletions
diff --git a/src/openvpn/networking_sitnl.c b/src/openvpn/networking_sitnl.c
new file mode 100644
index 0000000..713a213
--- /dev/null
+++ b/src/openvpn/networking_sitnl.c
@@ -0,0 +1,1246 @@
+/*
+ * Simplified Interface To NetLink
+ *
+ * Copyright (C) 2016-2018 Antonio Quartulli <a@unstable.cc>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#ifdef TARGET_LINUX
+
+#include "syshead.h"
+
+#include "errlevel.h"
+#include "buffer.h"
+#include "networking.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#define SNDBUF_SIZE (1024 * 2)
+#define RCVBUF_SIZE (1024 * 4)
+
+#define SITNL_ADDATTR(_msg, _max_size, _attr, _data, _size) \
+ { \
+ if (sitnl_addattr(_msg, _max_size, _attr, _data, _size) < 0) \
+ { \
+ goto err; \
+ } \
+ }
+
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *)(((uint8_t *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+/**
+ * Generic address data structure used to pass addresses and prefixes as
+ * argument to AF family agnostic functions
+ */
+typedef union {
+ in_addr_t ipv4;
+ struct in6_addr ipv6;
+} inet_address_t;
+
+/**
+ * Link state request message
+ */
+struct sitnl_link_req {
+ struct nlmsghdr n;
+ struct ifinfomsg i;
+ char buf[256];
+};
+
+/**
+ * Address request message
+ */
+struct sitnl_addr_req {
+ struct nlmsghdr n;
+ struct ifaddrmsg i;
+ char buf[256];
+};
+
+/**
+ * Route request message
+ */
+struct sitnl_route_req {
+ struct nlmsghdr n;
+ struct rtmsg r;
+ char buf[256];
+};
+
+typedef int (*sitnl_parse_reply_cb)(struct nlmsghdr *msg, void *arg);
+
+/**
+ * Object returned by route request operation
+ */
+struct sitnl_route_data_cb {
+ unsigned int iface;
+ inet_address_t gw;
+};
+
+/**
+ * Helper function used to easily add attributes to a rtnl message
+ */
+static int
+sitnl_addattr(struct nlmsghdr *n, int maxlen, int type, const void *data,
+ int alen)
+{
+ int len = RTA_LENGTH(alen);
+ struct rtattr *rta;
+
+ if ((int)(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen)
+ {
+ msg(M_WARN, "%s: rtnl: message exceeded bound of %d", __func__,
+ maxlen);
+ return -EMSGSIZE;
+ }
+
+ rta = NLMSG_TAIL(n);
+ rta->rta_type = type;
+ rta->rta_len = len;
+
+ if (!data)
+ {
+ memset(RTA_DATA(rta), 0, alen);
+ }
+ else
+ {
+ memcpy(RTA_DATA(rta), data, alen);
+ }
+
+ n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
+
+ return 0;
+}
+
+/**
+ * Open RTNL socket
+ */
+static int
+sitnl_socket(void)
+{
+ int sndbuf = SNDBUF_SIZE;
+ int rcvbuf = RCVBUF_SIZE;
+ int fd;
+
+ fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (fd < 0)
+ {
+ msg(M_WARN, "%s: cannot open netlink socket", __func__);
+ return fd;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: SO_SNDBUF", __func__);
+ close(fd);
+ return -1;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: SO_RCVBUF", __func__);
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+/**
+ * Bind socket to Netlink subsystem
+ */
+static int
+sitnl_bind(int fd, uint32_t groups)
+{
+ socklen_t addr_len;
+ struct sockaddr_nl local;
+
+ CLEAR(local);
+
+ local.nl_family = AF_NETLINK;
+ local.nl_groups = groups;
+
+ if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: cannot bind netlink socket", __func__);
+ return -errno;
+ }
+
+ addr_len = sizeof(local);
+ if (getsockname(fd, (struct sockaddr *)&local, &addr_len) < 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: cannot getsockname", __func__);
+ return -errno;
+ }
+
+ if (addr_len != sizeof(local))
+ {
+ msg(M_WARN, "%s: wrong address length %d", __func__, addr_len);
+ return -EINVAL;
+ }
+
+ if (local.nl_family != AF_NETLINK)
+ {
+ msg(M_WARN, "%s: wrong address family %d", __func__, local.nl_family);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * Send Netlink message and run callback on reply (if specified)
+ */
+static int
+sitnl_send(struct nlmsghdr *payload, pid_t peer, unsigned int groups,
+ sitnl_parse_reply_cb cb, void *arg_cb)
+{
+ int len, rem_len, fd, ret, rcv_len;
+ struct sockaddr_nl nladdr;
+ struct nlmsgerr *err;
+ struct nlmsghdr *h;
+ unsigned int seq;
+ char buf[1024 * 16];
+ struct iovec iov =
+ {
+ .iov_base = payload,
+ .iov_len = payload->nlmsg_len,
+ };
+ struct msghdr nlmsg =
+ {
+ .msg_name = &nladdr,
+ .msg_namelen = sizeof(nladdr),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+
+ CLEAR(nladdr);
+
+ nladdr.nl_family = AF_NETLINK;
+ nladdr.nl_pid = peer;
+ nladdr.nl_groups = groups;
+
+ payload->nlmsg_seq = seq = time(NULL);
+
+ /* no need to send reply */
+ if (!cb)
+ {
+ payload->nlmsg_flags |= NLM_F_ACK;
+ }
+
+ fd = sitnl_socket();
+ if (fd < 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: can't open rtnl socket", __func__);
+ return -errno;
+ }
+
+ ret = sitnl_bind(fd, 0);
+ if (ret < 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: can't bind rtnl socket", __func__);
+ ret = -errno;
+ goto out;
+ }
+
+ ret = sendmsg(fd, &nlmsg, 0);
+ if (ret < 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: rtnl: error on sendmsg()", __func__);
+ ret = -errno;
+ goto out;
+ }
+
+ /* prepare buffer to store RTNL replies */
+ memset(buf, 0, sizeof(buf));
+ iov.iov_base = buf;
+
+ while (1)
+ {
+ /*
+ * iov_len is modified by recvmsg(), therefore has to be initialized before
+ * using it again
+ */
+ msg(D_RTNL, "%s: checking for received messages", __func__);
+ iov.iov_len = sizeof(buf);
+ rcv_len = recvmsg(fd, &nlmsg, 0);
+ msg(D_RTNL, "%s: rtnl: received %d bytes", __func__, rcv_len);
+ if (rcv_len < 0)
+ {
+ if ((errno == EINTR) || (errno == EAGAIN))
+ {
+ msg(D_RTNL, "%s: interrupted call", __func__);
+ continue;
+ }
+ msg(M_WARN | M_ERRNO, "%s: rtnl: error on recvmsg()", __func__);
+ ret = -errno;
+ goto out;
+ }
+
+ if (rcv_len == 0)
+ {
+ msg(M_WARN, "%s: rtnl: socket reached unexpected EOF", __func__);
+ ret = -EIO;
+ goto out;
+ }
+
+ if (nlmsg.msg_namelen != sizeof(nladdr))
+ {
+ msg(M_WARN, "%s: sender address length: %u (expected %zu)",
+ __func__, nlmsg.msg_namelen, sizeof(nladdr));
+ ret = -EIO;
+ goto out;
+ }
+
+ h = (struct nlmsghdr *)buf;
+ while (rcv_len >= (int)sizeof(*h))
+ {
+ len = h->nlmsg_len;
+ rem_len = len - sizeof(*h);
+
+ if ((rem_len < 0) || (len > rcv_len))
+ {
+ if (nlmsg.msg_flags & MSG_TRUNC)
+ {
+ msg(M_WARN, "%s: truncated message", __func__);
+ ret = -EIO;
+ goto out;
+ }
+ msg(M_WARN, "%s: malformed message: len=%d", __func__, len);
+ ret = -EIO;
+ goto out;
+ }
+
+/* if (((int)nladdr.nl_pid != peer) || (h->nlmsg_pid != nladdr.nl_pid)
+ * || (h->nlmsg_seq != seq))
+ * {
+ * rcv_len -= NLMSG_ALIGN(len);
+ * h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
+ * msg(M_DEBUG, "%s: skipping unrelated message. nl_pid:%d (peer:%d) nl_msg_pid:%d nl_seq:%d seq:%d",
+ * __func__, (int)nladdr.nl_pid, peer, h->nlmsg_pid,
+ * h->nlmsg_seq, seq);
+ * continue;
+ * }
+ */
+ if (h->nlmsg_type == NLMSG_ERROR)
+ {
+ err = (struct nlmsgerr *)NLMSG_DATA(h);
+ if (rem_len < (int)sizeof(struct nlmsgerr))
+ {
+ msg(M_WARN, "%s: ERROR truncated", __func__);
+ ret = -EIO;
+ }
+ else
+ {
+ if (!err->error)
+ {
+ ret = 0;
+ if (cb)
+ {
+ ret = cb(h, arg_cb);
+ }
+ }
+ else
+ {
+ msg(M_WARN, "%s: rtnl: generic error (%d): %s",
+ __func__, err->error, strerror(-err->error));
+ ret = err->error;
+ }
+ }
+ goto out;
+ }
+
+ if (cb)
+ {
+ ret = cb(h, arg_cb);
+ goto out;
+ }
+ else
+ {
+ msg(M_WARN, "%s: RTNL: unexpected reply", __func__);
+ }
+
+ rcv_len -= NLMSG_ALIGN(len);
+ h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
+ }
+
+ if (nlmsg.msg_flags & MSG_TRUNC)
+ {
+ msg(M_WARN, "%s: message truncated", __func__);
+ continue;
+ }
+
+ if (rcv_len)
+ {
+ msg(M_WARN, "%s: rtnl: %d not parsed bytes", __func__, rcv_len);
+ ret = -1;
+ goto out;
+ }
+ }
+out:
+ close(fd);
+
+ return ret;
+}
+
+typedef struct {
+ int addr_size;
+ inet_address_t gw;
+ char iface[IFNAMSIZ];
+} route_res_t;
+
+static int
+sitnl_route_save(struct nlmsghdr *n, void *arg)
+{
+ route_res_t *res = arg;
+ struct rtmsg *r = NLMSG_DATA(n);
+ struct rtattr *rta = RTM_RTA(r);
+ int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
+ unsigned int ifindex = 0;
+
+ while (RTA_OK(rta, len))
+ {
+ switch (rta->rta_type)
+ {
+ /* route interface */
+ case RTA_OIF:
+ ifindex = *(unsigned int *)RTA_DATA(rta);
+ break;
+
+ /* route prefix */
+ case RTA_DST:
+ break;
+
+ /* GW for the route */
+ case RTA_GATEWAY:
+ memcpy(&res->gw, RTA_DATA(rta), res->addr_size);
+ break;
+ }
+
+ rta = RTA_NEXT(rta, len);
+ }
+
+ if (!if_indextoname(ifindex, res->iface))
+ {
+ msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifname for index %d",
+ __func__, ifindex);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+sitnl_route_best_gw(sa_family_t af_family, const inet_address_t *dst,
+ void *best_gw, char *best_iface)
+{
+ struct sitnl_route_req req;
+ route_res_t res;
+ int ret = -EINVAL;
+
+ ASSERT(best_gw);
+ ASSERT(best_iface);
+
+ CLEAR(req);
+ CLEAR(res);
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.r));
+ req.n.nlmsg_type = RTM_GETROUTE;
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+
+ req.r.rtm_family = af_family;
+
+ switch (af_family)
+ {
+ case AF_INET:
+ res.addr_size = sizeof(in_addr_t);
+ req.n.nlmsg_flags |= NLM_F_DUMP;
+ break;
+
+ case AF_INET6:
+ res.addr_size = sizeof(struct in6_addr);
+ break;
+
+ default:
+ /* unsupported */
+ return -EINVAL;
+ }
+
+ SITNL_ADDATTR(&req.n, sizeof(req), RTA_DST, dst, res.addr_size);
+
+ ret = sitnl_send(&req.n, 0, 0, sitnl_route_save, &res);
+ if (ret < 0)
+ {
+ goto err;
+ }
+
+ /* save result in output variables */
+ memcpy(best_gw, &res.gw, res.addr_size);
+ strncpy(best_iface, res.iface, IFNAMSIZ);
+err:
+ return ret;
+
+}
+
+/* used by iproute2 implementation too */
+int
+net_route_v6_best_gw(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+ struct in6_addr *best_gw, char *best_iface)
+{
+ inet_address_t dst_v6 = {0};
+ char buf[INET6_ADDRSTRLEN];
+ int ret;
+
+ if (dst)
+ {
+ dst_v6.ipv6 = *dst;
+ }
+
+ msg(D_ROUTE, "%s query: dst %s", __func__,
+ inet_ntop(AF_INET6, &dst_v6.ipv6, buf, sizeof(buf)));
+
+ ret = sitnl_route_best_gw(AF_INET6, &dst_v6, best_gw, best_iface);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ msg(D_ROUTE, "%s result: via %s dev %s", __func__,
+ inet_ntop(AF_INET6, best_gw, buf, sizeof(buf)), best_iface);
+
+ return ret;
+
+}
+
+#ifdef ENABLE_SITNL
+
+int
+net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)
+{
+ (void)c;
+ (void)ctx;
+
+ return 0;
+}
+
+void
+net_ctx_reset(openvpn_net_ctx_t *ctx)
+{
+ (void)ctx;
+}
+
+void
+net_ctx_free(openvpn_net_ctx_t *ctx)
+{
+ (void)ctx;
+}
+
+int
+net_route_v4_best_gw(openvpn_net_ctx_t *ctx, const in_addr_t *dst,
+ in_addr_t *best_gw, char *best_iface)
+{
+ inet_address_t dst_v4 = {0};
+ char buf[INET_ADDRSTRLEN];
+ int ret;
+
+ if (dst)
+ {
+ dst_v4.ipv4 = htonl(*dst);
+ }
+
+ msg(D_ROUTE, "%s query: dst %s", __func__,
+ inet_ntop(AF_INET, &dst_v4.ipv4, buf, sizeof(buf)));
+
+ ret = sitnl_route_best_gw(AF_INET, &dst_v4, best_gw, best_iface);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ msg(D_ROUTE, "%s result: via %s dev %s", __func__,
+ inet_ntop(AF_INET, best_gw, buf, sizeof(buf)), best_iface);
+
+ /* result is expected in Host Order */
+ *best_gw = ntohl(*best_gw);
+
+ return ret;
+}
+
+int
+net_iface_up(openvpn_net_ctx_t *ctx, const char *iface, bool up)
+{
+ struct sitnl_link_req req;
+ int ifindex;
+
+ CLEAR(req);
+
+ if (!iface)
+ {
+ msg(M_WARN, "%s: passed NULL interface", __func__);
+ return -EINVAL;
+ }
+
+ ifindex = if_nametoindex(iface);
+ if (ifindex == 0)
+ {
+ msg(M_WARN, "%s: rtnl: cannot get ifindex for %s: %s", __func__, iface,
+ strerror(errno));
+ return -ENOENT;
+ }
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.n.nlmsg_type = RTM_NEWLINK;
+
+ req.i.ifi_family = AF_PACKET;
+ req.i.ifi_index = ifindex;
+ req.i.ifi_change |= IFF_UP;
+ if (up)
+ {
+ req.i.ifi_flags |= IFF_UP;
+ }
+ else
+ {
+ req.i.ifi_flags &= ~IFF_UP;
+ }
+
+ msg(M_INFO, "%s: set %s %s", __func__, iface, up ? "up" : "down");
+
+ return sitnl_send(&req.n, 0, 0, NULL, NULL);
+}
+
+int
+net_iface_mtu_set(openvpn_net_ctx_t *ctx, const char *iface,
+ uint32_t mtu)
+{
+ struct sitnl_link_req req;
+ int ifindex, ret = -1;
+
+ CLEAR(req);
+
+ ifindex = if_nametoindex(iface);
+ if (ifindex == 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__,
+ iface);
+ return -1;
+ }
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.n.nlmsg_type = RTM_NEWLINK;
+
+ req.i.ifi_family = AF_PACKET;
+ req.i.ifi_index = ifindex;
+
+ SITNL_ADDATTR(&req.n, sizeof(req), IFLA_MTU, &mtu, 4);
+
+ msg(M_INFO, "%s: mtu %u for %s", __func__, mtu, iface);
+
+ ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+err:
+ return ret;
+}
+
+static int
+sitnl_addr_set(int cmd, uint32_t flags, int ifindex, sa_family_t af_family,
+ const inet_address_t *local, const inet_address_t *remote,
+ int prefixlen)
+{
+ struct sitnl_addr_req req;
+ uint32_t size;
+ int ret = -EINVAL;
+
+ CLEAR(req);
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
+ req.n.nlmsg_type = cmd;
+ req.n.nlmsg_flags = NLM_F_REQUEST | flags;
+
+ req.i.ifa_index = ifindex;
+ req.i.ifa_family = af_family;
+
+ switch (af_family)
+ {
+ case AF_INET:
+ size = sizeof(struct in_addr);
+ break;
+
+ case AF_INET6:
+ size = sizeof(struct in6_addr);
+ break;
+
+ default:
+ msg(M_WARN, "%s: rtnl: unknown address family %d", __func__,
+ af_family);
+ return -EINVAL;
+ }
+
+ /* if no prefixlen has been specified, assume host address */
+ if (prefixlen == 0)
+ {
+ prefixlen = size * 8;
+ }
+ req.i.ifa_prefixlen = prefixlen;
+
+ if (remote)
+ {
+ SITNL_ADDATTR(&req.n, sizeof(req), IFA_ADDRESS, remote, size);
+ }
+
+ if (local)
+ {
+ SITNL_ADDATTR(&req.n, sizeof(req), IFA_LOCAL, local, size);
+ }
+
+ ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+ if (ret == -EEXIST)
+ {
+ ret = 0;
+ }
+err:
+ return ret;
+}
+
+static int
+sitnl_addr_ptp_add(sa_family_t af_family, const char *iface,
+ const inet_address_t *local,
+ const inet_address_t *remote)
+{
+ int ifindex;
+
+ switch (af_family)
+ {
+ case AF_INET:
+ case AF_INET6:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (!iface)
+ {
+ msg(M_WARN, "%s: passed NULL interface", __func__);
+ return -EINVAL;
+ }
+
+ ifindex = if_nametoindex(iface);
+ if (ifindex == 0)
+ {
+ msg(M_WARN, "%s: cannot get ifindex for %s: %s", __func__, np(iface),
+ strerror(errno));
+ return -ENOENT;
+ }
+
+ return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, ifindex,
+ af_family, local, remote, 0);
+}
+
+static int
+sitnl_addr_ptp_del(sa_family_t af_family, const char *iface,
+ const inet_address_t *local)
+{
+ int ifindex;
+
+ switch (af_family)
+ {
+ case AF_INET:
+ case AF_INET6:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (!iface)
+ {
+ msg(M_WARN, "%s: passed NULL interface", __func__);
+ return -EINVAL;
+ }
+
+ ifindex = if_nametoindex(iface);
+ if (ifindex == 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: cannot get ifindex for %s", __func__, iface);
+ return -ENOENT;
+ }
+
+ return sitnl_addr_set(RTM_DELADDR, 0, ifindex, af_family, local, NULL, 0);
+}
+
+static int
+sitnl_route_set(int cmd, uint32_t flags, int ifindex, sa_family_t af_family,
+ const void *dst, int prefixlen,
+ const void *gw, enum rt_class_t table, int metric,
+ enum rt_scope_t scope, int protocol, int type)
+{
+ struct sitnl_route_req req;
+ int ret = -1, size;
+
+ CLEAR(req);
+
+ switch (af_family)
+ {
+ case AF_INET:
+ size = sizeof(in_addr_t);
+ break;
+
+ case AF_INET6:
+ size = sizeof(struct in6_addr);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.r));
+ req.n.nlmsg_type = cmd;
+ req.n.nlmsg_flags = NLM_F_REQUEST | flags;
+
+ req.r.rtm_family = af_family;
+ req.r.rtm_scope = scope;
+ req.r.rtm_protocol = protocol;
+ req.r.rtm_type = type;
+ req.r.rtm_dst_len = prefixlen;
+
+ if (table < 256)
+ {
+ req.r.rtm_table = table;
+ }
+ else
+ {
+ req.r.rtm_table = RT_TABLE_UNSPEC;
+ SITNL_ADDATTR(&req.n, sizeof(req), RTA_TABLE, &table, 4);
+ }
+
+ if (dst)
+ {
+ SITNL_ADDATTR(&req.n, sizeof(req), RTA_DST, dst, size);
+ }
+
+ if (gw)
+ {
+ SITNL_ADDATTR(&req.n, sizeof(req), RTA_GATEWAY, gw, size);
+ }
+
+ if (ifindex > 0)
+ {
+ SITNL_ADDATTR(&req.n, sizeof(req), RTA_OIF, &ifindex, 4);
+ }
+
+ if (metric > 0)
+ {
+ SITNL_ADDATTR(&req.n, sizeof(req), RTA_PRIORITY, &metric, 4);
+ }
+
+ ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+ if (ret == -EEXIST)
+ {
+ ret = 0;
+ }
+err:
+ return ret;
+}
+
+static int
+sitnl_addr_add(sa_family_t af_family, const char *iface,
+ const inet_address_t *addr, int prefixlen)
+{
+ int ifindex;
+
+ switch (af_family)
+ {
+ case AF_INET:
+ case AF_INET6:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (!iface)
+ {
+ msg(M_WARN, "%s: passed NULL interface", __func__);
+ return -EINVAL;
+ }
+
+ ifindex = if_nametoindex(iface);
+ if (ifindex == 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__,
+ iface);
+ return -ENOENT;
+ }
+
+ return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, ifindex,
+ af_family, addr, NULL, prefixlen);
+}
+
+static int
+sitnl_addr_del(sa_family_t af_family, const char *iface, inet_address_t *addr,
+ int prefixlen)
+{
+ int ifindex;
+
+ switch (af_family)
+ {
+ case AF_INET:
+ case AF_INET6:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (!iface)
+ {
+ msg(M_WARN, "%s: passed NULL interface", __func__);
+ return -EINVAL;
+ }
+
+ ifindex = if_nametoindex(iface);
+ if (ifindex == 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__,
+ iface);
+ return -ENOENT;
+ }
+
+ return sitnl_addr_set(RTM_DELADDR, 0, ifindex, af_family, addr, NULL,
+ prefixlen);
+}
+
+int
+net_addr_v4_add(openvpn_net_ctx_t *ctx, const char *iface,
+ const in_addr_t *addr, int prefixlen)
+{
+ inet_address_t addr_v4 = { 0 };
+ char buf[INET_ADDRSTRLEN];
+
+ if (!addr)
+ {
+ return -EINVAL;
+ }
+
+ addr_v4.ipv4 = htonl(*addr);
+
+ msg(M_INFO, "%s: %s/%d dev %s", __func__,
+ inet_ntop(AF_INET, &addr_v4.ipv4, buf, sizeof(buf)), prefixlen,iface);
+
+ return sitnl_addr_add(AF_INET, iface, &addr_v4, prefixlen);
+}
+
+int
+net_addr_v6_add(openvpn_net_ctx_t *ctx, const char *iface,
+ const struct in6_addr *addr, int prefixlen)
+{
+ inet_address_t addr_v6 = { 0 };
+ char buf[INET6_ADDRSTRLEN];
+
+ if (!addr)
+ {
+ return -EINVAL;
+ }
+
+ addr_v6.ipv6 = *addr;
+
+ msg(M_INFO, "%s: %s/%d dev %s", __func__,
+ inet_ntop(AF_INET6, &addr_v6.ipv6, buf, sizeof(buf)), prefixlen, iface);
+
+ return sitnl_addr_add(AF_INET6, iface, &addr_v6, prefixlen);
+}
+
+int
+net_addr_v4_del(openvpn_net_ctx_t *ctx, const char *iface,
+ const in_addr_t *addr, int prefixlen)
+{
+ inet_address_t addr_v4 = { 0 };
+ char buf[INET_ADDRSTRLEN];
+
+ if (!addr)
+ {
+ return -EINVAL;
+ }
+
+ addr_v4.ipv4 = htonl(*addr);
+
+ msg(M_INFO, "%s: %s dev %s", __func__,
+ inet_ntop(AF_INET, &addr_v4.ipv4, buf, sizeof(buf)), iface);
+
+ return sitnl_addr_del(AF_INET, iface, &addr_v4, prefixlen);
+}
+
+int
+net_addr_v6_del(openvpn_net_ctx_t *ctx, const char *iface,
+ const struct in6_addr *addr, int prefixlen)
+{
+ inet_address_t addr_v6 = { 0 };
+ char buf[INET6_ADDRSTRLEN];
+
+ if (!addr)
+ {
+ return -EINVAL;
+ }
+
+ addr_v6.ipv6 = *addr;
+
+ msg(M_INFO, "%s: %s/%d dev %s", __func__,
+ inet_ntop(AF_INET6, &addr_v6.ipv6, buf, sizeof(buf)), prefixlen, iface);
+
+ return sitnl_addr_del(AF_INET6, iface, &addr_v6, prefixlen);
+}
+
+int
+net_addr_ptp_v4_add(openvpn_net_ctx_t *ctx, const char *iface,
+ const in_addr_t *local, const in_addr_t *remote)
+{
+ inet_address_t local_v4 = { 0 };
+ inet_address_t remote_v4 = { 0 };
+ char buf1[INET_ADDRSTRLEN];
+ char buf2[INET_ADDRSTRLEN];
+
+ if (!local)
+ {
+ return -EINVAL;
+ }
+
+ local_v4.ipv4 = htonl(*local);
+
+ if (remote)
+ {
+ remote_v4.ipv4 = htonl(*remote);
+ }
+
+ msg(M_INFO, "%s: %s peer %s dev %s", __func__,
+ inet_ntop(AF_INET, &local_v4.ipv4, buf1, sizeof(buf1)),
+ inet_ntop(AF_INET, &remote_v4.ipv4, buf2, sizeof(buf2)), iface);
+
+ return sitnl_addr_ptp_add(AF_INET, iface, &local_v4, &remote_v4);
+}
+
+int
+net_addr_ptp_v4_del(openvpn_net_ctx_t *ctx, const char *iface,
+ const in_addr_t *local, const in_addr_t *remote)
+{
+ inet_address_t local_v4 = { 0 };
+ char buf[INET6_ADDRSTRLEN];
+
+
+ if (!local)
+ {
+ return -EINVAL;
+ }
+
+ local_v4.ipv4 = htonl(*local);
+
+ msg(M_INFO, "%s: %s dev %s", __func__,
+ inet_ntop(AF_INET, &local_v4.ipv4, buf, sizeof(buf)), iface);
+
+ return sitnl_addr_ptp_del(AF_INET, iface, &local_v4);
+}
+
+static int
+sitnl_route_add(const char *iface, sa_family_t af_family, const void *dst,
+ int prefixlen, const void *gw, uint32_t table, int metric)
+{
+ enum rt_scope_t scope = RT_SCOPE_UNIVERSE;
+ int ifindex = 0;
+
+ if (iface)
+ {
+ ifindex = if_nametoindex(iface);
+ if (ifindex == 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifindex for %s",
+ __func__, iface);
+ return -ENOENT;
+ }
+ }
+
+ if (table == 0)
+ {
+ table = RT_TABLE_MAIN;
+ }
+
+ if (!gw && iface)
+ {
+ scope = RT_SCOPE_LINK;
+ }
+
+ return sitnl_route_set(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_REPLACE, ifindex,
+ af_family, dst, prefixlen, gw, table, metric, scope,
+ RTPROT_BOOT, RTN_UNICAST);
+}
+
+int
+net_route_v4_add(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen,
+ const in_addr_t *gw, const char *iface,
+ uint32_t table, int metric)
+{
+ in_addr_t *dst_ptr = NULL, *gw_ptr = NULL;
+ in_addr_t dst_be = 0, gw_be = 0;
+ char dst_str[INET_ADDRSTRLEN];
+ char gw_str[INET_ADDRSTRLEN];
+
+ if (dst)
+ {
+ dst_be = htonl(*dst);
+ dst_ptr = &dst_be;
+ }
+
+ if (gw)
+ {
+ gw_be = htonl(*gw);
+ gw_ptr = &gw_be;
+ }
+
+ msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
+ inet_ntop(AF_INET, &dst_be, dst_str, sizeof(dst_str)),
+ prefixlen, inet_ntop(AF_INET, &gw_be, gw_str, sizeof(gw_str)),
+ np(iface), table, metric);
+
+ return sitnl_route_add(iface, AF_INET, dst_ptr, prefixlen, gw_ptr, table,
+ metric);
+}
+
+int
+net_route_v6_add(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+ int prefixlen, const struct in6_addr *gw,
+ const char *iface, uint32_t table, int metric)
+{
+ inet_address_t dst_v6 = { 0 };
+ inet_address_t gw_v6 = { 0 };
+ char dst_str[INET6_ADDRSTRLEN];
+ char gw_str[INET6_ADDRSTRLEN];
+
+ if (dst)
+ {
+ dst_v6.ipv6 = *dst;
+ }
+
+ if (gw)
+ {
+ gw_v6.ipv6 = *gw;
+ }
+
+ msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
+ inet_ntop(AF_INET6, &dst_v6.ipv6, dst_str, sizeof(dst_str)),
+ prefixlen, inet_ntop(AF_INET6, &gw_v6.ipv6, gw_str, sizeof(gw_str)),
+ np(iface), table, metric);
+
+ return sitnl_route_add(iface, AF_INET6, dst, prefixlen, gw, table,
+ metric);
+}
+
+static int
+sitnl_route_del(const char *iface, sa_family_t af_family, inet_address_t *dst,
+ int prefixlen, inet_address_t *gw, uint32_t table,
+ int metric)
+{
+ int ifindex = 0;
+
+ if (iface)
+ {
+ ifindex = if_nametoindex(iface);
+ if (ifindex == 0)
+ {
+ msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifindex for %s",
+ __func__, iface);
+ return -ENOENT;
+ }
+ }
+
+ if (table == 0)
+ {
+ table = RT_TABLE_MAIN;
+ }
+
+ return sitnl_route_set(RTM_DELROUTE, 0, ifindex, af_family, dst, prefixlen,
+ gw, table, metric, RT_SCOPE_NOWHERE, 0, 0);
+}
+
+int
+net_route_v4_del(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen,
+ const in_addr_t *gw, const char *iface, uint32_t table,
+ int metric)
+{
+ inet_address_t dst_v4 = { 0 };
+ inet_address_t gw_v4 = { 0 };
+ char dst_str[INET_ADDRSTRLEN];
+ char gw_str[INET_ADDRSTRLEN];
+
+ if (dst)
+ {
+ dst_v4.ipv4 = htonl(*dst);
+ }
+
+ if (gw)
+ {
+ gw_v4.ipv4 = htonl(*gw);
+ }
+
+ msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
+ inet_ntop(AF_INET, &dst_v4.ipv4, dst_str, sizeof(dst_str)),
+ prefixlen, inet_ntop(AF_INET, &gw_v4.ipv4, gw_str, sizeof(gw_str)),
+ np(iface), table, metric);
+
+ return sitnl_route_del(iface, AF_INET, &dst_v4, prefixlen, &gw_v4, table,
+ metric);
+}
+
+int
+net_route_v6_del(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+ int prefixlen, const struct in6_addr *gw,
+ const char *iface, uint32_t table, int metric)
+{
+ inet_address_t dst_v6 = { 0 };
+ inet_address_t gw_v6 = { 0 };
+ char dst_str[INET6_ADDRSTRLEN];
+ char gw_str[INET6_ADDRSTRLEN];
+
+ if (dst)
+ {
+ dst_v6.ipv6 = *dst;
+ }
+
+ if (gw)
+ {
+ gw_v6.ipv6 = *gw;
+ }
+
+ msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
+ inet_ntop(AF_INET6, &dst_v6.ipv6, dst_str, sizeof(dst_str)),
+ prefixlen, inet_ntop(AF_INET6, &gw_v6.ipv6, gw_str, sizeof(gw_str)),
+ np(iface), table, metric);
+
+ return sitnl_route_del(iface, AF_INET6, &dst_v6, prefixlen, &gw_v6,
+ table, metric);
+}
+
+#endif /* !ENABLE_SITNL */
+
+#endif /* TARGET_LINUX */