/* * Simplified Interface To NetLink * * Copyright (C) 2016-2018 Antonio Quartulli * * 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 #include #include #include #include #include #include #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_DONE) { ret = 0; goto out; } 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) { int r = cb(h, arg_cb); if (r <= 0) { ret = r; } } } else { msg(M_WARN, "%s: rtnl: generic error (%d): %s", __func__, err->error, strerror(-err->error)); ret = err->error; } } goto out; } if (cb) { int r = cb(h, arg_cb); if (r <= 0) { ret = r; 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]; bool default_only; } 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; /* filter-out non-zero dst prefixes */ if (res->default_only && r->rtm_dst_len != 0) { return 1; } 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); /* * kernel can't return 0.0.0.0/8 host route, dump all * the routes and filter for 0.0.0.0/0 in cb() */ if (!dst || !dst->ipv4) { req.n.nlmsg_flags |= NLM_F_DUMP; res.default_only = true; } else { req.r.rtm_dst_len = 32; } break; case AF_INET6: res.addr_size = sizeof(struct in6_addr); /* kernel can return ::/128 host route */ req.r.rtm_dst_len = 128; 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 */