#include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "triton.h" #include "log.h" #include "libnetlink.h" #include "ap_net.h" #include "memdebug.h" #ifndef HAVE_SETNS #ifdef SYS_setns int setns(int fd, int nstype) { return syscall(SYS_setns, fd, nstype); } #endif #endif struct kern_net { struct ap_net net; struct rtnl_handle *rth; int ns_fd; int sock; int sock6; }; static const char *conf_netns_run_dir; static LIST_HEAD(nets); static pthread_mutex_t nets_lock = PTHREAD_MUTEX_INITIALIZER; __export __thread struct ap_net *net; __export struct ap_net *def_net; static int def_ns_fd; __export int conf_nl_rcvbuf = 1024 * 1024; __export int conf_nl_sndbuf = -1; static int def_socket(int domain, int type, int proto) { return socket(domain, type, proto); } static int def_connect(int sock, const struct sockaddr *addr, socklen_t len) { return connect(sock, addr, len); } static int def_bind(int sock, const struct sockaddr *addr, socklen_t len) { return bind(sock, addr, len); } static int def_listen(int sock, int backlog) { return listen(sock, backlog); } static ssize_t def_read(int sock, void *buf, size_t len) { return read(sock, buf, len); } static ssize_t def_recvfrom(int sock, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) { return recvfrom(sock, buf, len, flags, src_addr, addrlen); } static ssize_t def_write(int sock, const void *buf, size_t len) { return write(sock, buf, len); } static ssize_t def_sendto(int sock, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { return sendto(sock, buf, len, flags, dest_addr, addrlen); } static int def_set_nonblocking(int sock, int f) { return fcntl(sock, F_SETFL, O_NONBLOCK); } static int def_setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen) { return setsockopt(sock, level, optname, optval, optlen); } static int def_ppp_open() { return open("/dev/ppp", O_RDWR); } static int def_ppp_ioctl(int fd, unsigned long request, void *arg) { return ioctl(fd, request, arg); } static int def_sock_ioctl(unsigned long request, void *arg) { struct kern_net *n = container_of(net, typeof(*n), net); return ioctl(n->sock, request, arg); } static int def_sock6_ioctl(unsigned long request, void *arg) { struct kern_net *n = container_of(net, typeof(*n), net); return ioctl(n->sock6, request, arg); } static void enter_ns() { #ifdef SYS_setns if (net != def_net) { struct kern_net *n = container_of(net, typeof(*n), net); setns(n->ns_fd, CLONE_NEWNET); } #endif } static void exit_ns() { #ifdef SYS_setns if (net != def_net) setns(def_ns_fd, CLONE_NEWNET); #endif } static struct rtnl_handle *def_rtnl_get() { struct kern_net *n = container_of(net, typeof(*n), net); struct rtnl_handle *rth = __sync_lock_test_and_set(&n->rth, NULL); int r; if (!rth) { rth = _malloc(sizeof(*rth)); enter_ns(); r = rtnl_open(rth, 0); exit_ns(); if (r) { _free(rth); return NULL; } } return rth; } static void def_rtnl_put(struct rtnl_handle *rth) { struct kern_net *n = container_of(net, typeof(*n), net); if (!__sync_bool_compare_and_swap(&n->rth, NULL, rth)) { rtnl_close(rth); _free(rth); } } static int def_rtnl_open(struct rtnl_handle *rth, int proto) { struct kern_net *n = container_of(net, typeof(*n), net); int r; enter_ns(); r = rtnl_open_byproto(rth, 0, proto); exit_ns(); return r; } static int def_move_link(struct ap_net *new_net, int ifindex) { #ifdef SYS_setns struct iplink_req { struct nlmsghdr n; struct ifinfomsg i; char buf[1024]; } req; struct rtnl_handle *rth = net->rtnl_get(); struct kern_net *n = container_of(new_net, typeof(*n), net); int r = 0; if (!rth) return -1; memset(&req, 0, sizeof(req) - 1024); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; req.n.nlmsg_type = RTM_SETLINK; req.i.ifi_family = AF_UNSPEC; req.i.ifi_index = ifindex; addattr_l(&req.n, 4096, IFLA_NET_NS_FD, &n->ns_fd, sizeof(n->ns_fd)); if (rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL, 0) < 0) r = -1; net->rtnl_put(rth); return r; #else return -1; #endif } static int def_get_ifindex(const char *ifname) { struct kern_net *n = container_of(net, typeof(*n), net); struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, ifname); if (ioctl(n->sock, SIOCGIFINDEX, &ifr)) { log_ppp_error("ioctl(SIOCGIFINDEX): %s\n", strerror(errno)); return -1; } return ifr.ifr_ifindex; } #ifdef HAVE_VRF static int def_set_vrf(int ifindex, int vrf_ifindex) { struct iplink_req { struct nlmsghdr n; struct ifinfomsg i; char buf[1024]; } req; struct rtnl_handle *rth = net->rtnl_get(); int r = 0; if (!rth) { log_ppp_error("def_set_vrf rtnl_get failed\n"); return -1; } memset(&req, 0, sizeof(req) - 1024); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.n.nlmsg_flags = NLM_F_REQUEST; req.n.nlmsg_type = RTM_NEWLINK; req.i.ifi_family = AF_UNSPEC; req.i.ifi_index = ifindex; addattr_l(&req.n, sizeof(req), IFLA_MASTER, &vrf_ifindex, sizeof(vrf_ifindex)); if (rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL, 0) < 0) { log_ppp_error("def_set_vrf rtnl_talk failed\n"); r = -1; } net->rtnl_put(rth); return r; } #endif static void def_release(struct ap_net *d) { struct kern_net *n = container_of(d, typeof(*n), net); if (d == def_net) return; pthread_mutex_lock(&nets_lock); if (--d->refs) { pthread_mutex_unlock(&nets_lock); return; } list_del(&d->entry); pthread_mutex_unlock(&nets_lock); net = def_net; log_debug("close ns %s\n", n->net.name); close(n->sock); close(n->sock6); close(n->ns_fd); if (n->rth) { rtnl_close(n->rth); _free(n->rth); } _free(n); } static struct ap_net *alloc_net(const char *name) { struct kern_net *n; struct ap_net *net; #ifdef SYS_setns int ns_fd; if (name) { char fname[PATH_MAX]; sprintf(fname, "%s/%s", conf_netns_run_dir, name); ns_fd = open(fname, O_RDONLY); if (ns_fd == -1) { log_ppp_error("open %s: %s\n", fname, strerror(errno)); return NULL; } if (setns(ns_fd, CLONE_NEWNET)) { log_ppp_error("setns %s: %s\n", fname, strerror(errno)); close(ns_fd); return NULL; } log_debug("open ns %s\n", name); } else def_ns_fd = ns_fd = open("/proc/self/ns/net", O_RDONLY); #endif n = _malloc(sizeof(*n)); net = &n->net; net->refs = 1; net->name = name ? _strdup(name) : "def"; net->socket = def_socket; net->connect = def_connect; net->bind = def_bind; net->listen = def_listen; net->read = def_read; net->recvfrom = def_recvfrom; net->write = def_write; net->sendto = def_sendto; net->set_nonblocking = def_set_nonblocking; net->setsockopt = def_setsockopt; net->ppp_open = def_ppp_open; net->ppp_ioctl = def_ppp_ioctl; net->sock_ioctl = def_sock_ioctl; net->sock6_ioctl = def_sock6_ioctl; net->enter_ns = enter_ns; net->exit_ns = exit_ns; net->rtnl_get = def_rtnl_get; net->rtnl_put = def_rtnl_put; net->rtnl_open = def_rtnl_open; net->move_link = def_move_link; net->get_ifindex = def_get_ifindex; net->release = def_release; #ifdef HAVE_VRF net->set_vrf = def_set_vrf; #endif n->sock = socket(AF_INET, SOCK_DGRAM, 0); n->sock6 = socket(AF_INET6, SOCK_DGRAM, 0); n->rth = _malloc(sizeof(*n->rth)); rtnl_open(n->rth, 0); #ifdef SYS_setns n->ns_fd = ns_fd; if (ns_fd != def_ns_fd) setns(def_ns_fd, CLONE_NEWNET); #endif list_add_tail(&net->entry, &nets); return net; }; int __export ap_net_register(struct ap_net *net) { pthread_mutex_lock(&nets_lock); list_add_tail(&net->entry, &nets); pthread_mutex_unlock(&nets_lock); return 0; } static struct ap_net *find_net(const char *name) { struct ap_net *n; list_for_each_entry(n, &nets, entry) { if (!strcmp(name, n->name)) { n->refs++; return n; } } return NULL; } __export struct ap_net *ap_net_find(const char *name) { struct ap_net *n; pthread_mutex_lock(&nets_lock); n = find_net(name); pthread_mutex_unlock(&nets_lock); return n; } __export struct ap_net *ap_net_open_ns(const char *name) { #ifdef SYS_setns struct ap_net *n; pthread_mutex_lock(&nets_lock); n = find_net(name); if (!n) n = alloc_net(name); pthread_mutex_unlock(&nets_lock); return n; #else log_ppp_error("netns is not suppotred\n"); return NULL; #endif } static void __init init() { const char *opt; opt = conf_get_opt("common", "nl-rcv-buffer"); if (opt) conf_nl_rcvbuf = atoi(opt); opt = conf_get_opt("common", "nl-snd-buffer"); if (opt) conf_nl_sndbuf = atoi(opt); opt = conf_get_opt("common", "netns-run-dir"); if (opt) conf_netns_run_dir = opt; else conf_netns_run_dir = "/var/run/netns"; def_net = net = alloc_net(NULL); } DEFINE_INIT(1, init);