| /* |
| * 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_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; |
| unsigned int table; |
| } 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 table, ifindex = 0; |
| void *gw = NULL; |
| |
| /* filter-out non-zero dst prefixes */ |
| if (res->default_only && r->rtm_dst_len != 0) |
| { |
| return 1; |
| } |
| |
| /* route table, ignored with RTA_TABLE */ |
| table = r->rtm_table; |
| |
| 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: |
| gw = RTA_DATA(rta); |
| break; |
| |
| /* route table */ |
| case RTA_TABLE: |
| table = *(unsigned int *)RTA_DATA(rta); |
| break; |
| } |
| |
| rta = RTA_NEXT(rta, len); |
| } |
| |
| /* filter out any route not coming from the selected table */ |
| if (res->table && res->table != table) |
| { |
| return 1; |
| } |
| |
| if (!if_indextoname(ifindex, res->iface)) |
| { |
| msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifname for index %d", |
| __func__, ifindex); |
| return -1; |
| } |
| |
| if (gw) |
| { |
| memcpy(&res->gw, gw, res->addr_size); |
| } |
| |
| 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; |
| res.table = RT_TABLE_MAIN; |
| } |
| 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 */ |