diff options
Diffstat (limited to 'zebra/zebra_netns_notify.c')
-rw-r--r-- | zebra/zebra_netns_notify.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/zebra/zebra_netns_notify.c b/zebra/zebra_netns_notify.c new file mode 100644 index 0000000..cc4138e --- /dev/null +++ b/zebra/zebra_netns_notify.c @@ -0,0 +1,446 @@ +/* + * Zebra NS collector and notifier for Network NameSpaces + * Copyright (C) 2017 6WIND + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <zebra.h> + +#ifdef HAVE_NETLINK +#ifdef HAVE_NETNS +#undef _GNU_SOURCE +#define _GNU_SOURCE + +#include <sched.h> +#endif +#include <dirent.h> +#include <sys/inotify.h> +#include <sys/stat.h> + +#include "thread.h" +#include "ns.h" +#include "command.h" +#include "memory.h" +#include "lib_errors.h" + +#include "zebra_router.h" +#endif /* defined(HAVE_NETLINK) */ + +#include "zebra_netns_notify.h" +#include "zebra_netns_id.h" +#include "zebra_errors.h" +#include "interface.h" + +#ifdef HAVE_NETLINK + +/* upon creation of folder under /var/run/netns, + * wait that netns context is bound to + * that folder 10 seconds + */ +#define ZEBRA_NS_POLLING_INTERVAL_MSEC 1000 +#define ZEBRA_NS_POLLING_MAX_RETRIES 200 + +DEFINE_MTYPE_STATIC(ZEBRA, NETNS_MISC, "ZebraNetNSInfo"); +static struct thread *zebra_netns_notify_current; + +struct zebra_netns_info { + const char *netnspath; + unsigned int retries; +}; + +static void zebra_ns_ready_read(struct thread *t); +static void zebra_ns_notify_create_context_from_entry_name(const char *name); +static int zebra_ns_continue_read(struct zebra_netns_info *zns_info, + int stop_retry); +static void zebra_ns_notify_read(struct thread *t); + +static struct vrf *vrf_handler_create(struct vty *vty, const char *vrfname) +{ + if (strlen(vrfname) > VRF_NAMSIZ) { + flog_warn(EC_LIB_VRF_LENGTH, + "%% VRF name %s invalid: length exceeds %d bytes", + vrfname, VRF_NAMSIZ); + return NULL; + } + + return vrf_get(VRF_UNKNOWN, vrfname); +} + +static void zebra_ns_notify_create_context_from_entry_name(const char *name) +{ + char *netnspath = ns_netns_pathname(NULL, name); + struct vrf *vrf; + int ret; + ns_id_t ns_id, ns_id_external, ns_id_relative = NS_UNKNOWN; + struct ns *default_ns; + + if (netnspath == NULL) + return; + + frr_with_privs(&zserv_privs) { + ns_id = zebra_ns_id_get(netnspath, -1); + } + if (ns_id == NS_UNKNOWN) + return; + ns_id_external = ns_map_nsid_with_external(ns_id, true); + /* if VRF with NS ID already present */ + vrf = vrf_lookup_by_id((vrf_id_t)ns_id_external); + if (vrf) { + zlog_debug( + "NS notify : same NSID used by VRF %s. Ignore NS %s creation", + vrf->name, netnspath); + return; + } + vrf = vrf_handler_create(NULL, name); + if (!vrf) { + flog_warn(EC_ZEBRA_NS_VRF_CREATION_FAILED, + "NS notify : failed to create VRF %s", name); + ns_map_nsid_with_external(ns_id, false); + return; + } + + default_ns = ns_get_default(); + + /* force kernel ns_id creation in that new vrf */ + frr_with_privs(&zserv_privs) { + ns_switch_to_netns(netnspath); + ns_id_relative = zebra_ns_id_get(NULL, default_ns->fd); + ns_switchback_to_initial(); + } + + frr_with_privs(&zserv_privs) { + ret = zebra_vrf_netns_handler_create(NULL, vrf, netnspath, + ns_id_external, ns_id, + ns_id_relative); + } + if (ret != CMD_SUCCESS) { + flog_warn(EC_ZEBRA_NS_VRF_CREATION_FAILED, + "NS notify : failed to create NS %s", netnspath); + ns_map_nsid_with_external(ns_id, false); + vrf_delete(vrf); + return; + } + zlog_info("NS notify : created VRF %s NS %s", name, netnspath); +} + +static int zebra_ns_continue_read(struct zebra_netns_info *zns_info, + int stop_retry) +{ + void *ns_path_ptr = (void *)zns_info->netnspath; + + if (stop_retry) { + XFREE(MTYPE_NETNS_MISC, ns_path_ptr); + XFREE(MTYPE_NETNS_MISC, zns_info); + return 0; + } + thread_add_timer_msec(zrouter.master, zebra_ns_ready_read, + (void *)zns_info, ZEBRA_NS_POLLING_INTERVAL_MSEC, + NULL); + return 0; +} + +static int zebra_ns_delete(char *name) +{ + struct vrf *vrf = vrf_lookup_by_name(name); + struct interface *ifp, *tmp; + struct ns *ns; + + if (!vrf) { + flog_warn(EC_ZEBRA_NS_DELETION_FAILED_NO_VRF, + "NS notify : no VRF found using NS %s", name); + return 0; + } + + /* + * We don't receive interface down/delete notifications from kernel + * when a netns is deleted. Therefore we have to manually replicate + * the necessary actions here. + */ + RB_FOREACH_SAFE (ifp, if_name_head, &vrf->ifaces_by_name, tmp) { + if (!CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) + continue; + + if (if_is_no_ptm_operative(ifp)) { + UNSET_FLAG(ifp->flags, IFF_RUNNING); + if_down(ifp); + } + + if (IS_ZEBRA_IF_BOND(ifp)) + zebra_l2if_update_bond(ifp, false); + if (IS_ZEBRA_IF_BOND_SLAVE(ifp)) + zebra_l2if_update_bond_slave(ifp, IFINDEX_INTERNAL, + false); + /* Special handling for bridge or VxLAN interfaces. */ + if (IS_ZEBRA_IF_BRIDGE(ifp)) + zebra_l2_bridge_del(ifp); + else if (IS_ZEBRA_IF_VXLAN(ifp)) + zebra_l2_vxlanif_del(ifp); + + UNSET_FLAG(ifp->flags, IFF_UP); + if_delete_update(&ifp); + } + + ns = (struct ns *)vrf->ns_ctxt; + /* the deletion order is the same + * as the one used when siging signal is received + */ + vrf->ns_ctxt = NULL; + vrf_delete(vrf); + if (ns) + ns_delete(ns); + + zlog_info("NS notify : deleted VRF %s", name); + return 0; +} + +static int zebra_ns_notify_self_identify(struct stat *netst) +{ + char net_path[PATH_MAX]; + int netns; + + snprintf(net_path, sizeof(net_path), "/proc/self/ns/net"); + netns = open(net_path, O_RDONLY); + if (netns < 0) + return -1; + if (fstat(netns, netst) < 0) { + close(netns); + return -1; + } + close(netns); + return 0; +} + +static bool zebra_ns_notify_is_default_netns(const char *name) +{ + struct stat default_netns_stat; + struct stat st; + char netnspath[PATH_MAX]; + + if (zebra_ns_notify_self_identify(&default_netns_stat)) + return false; + + memset(&st, 0, sizeof(st)); + snprintf(netnspath, sizeof(netnspath), "%s/%s", NS_RUN_DIR, name); + /* compare with local stat */ + if (stat(netnspath, &st) == 0 && + (st.st_dev == default_netns_stat.st_dev) && + (st.st_ino == default_netns_stat.st_ino)) + return true; + return false; +} + +static void zebra_ns_ready_read(struct thread *t) +{ + struct zebra_netns_info *zns_info = THREAD_ARG(t); + const char *netnspath; + int err, stop_retry = 0; + + if (!zns_info) + return; + if (!zns_info->netnspath) { + XFREE(MTYPE_NETNS_MISC, zns_info); + return; + } + netnspath = zns_info->netnspath; + if (--zns_info->retries == 0) + stop_retry = 1; + frr_with_privs(&zserv_privs) { + err = ns_switch_to_netns(netnspath); + } + if (err < 0) { + zebra_ns_continue_read(zns_info, stop_retry); + return; + } + + /* go back to default ns */ + frr_with_privs(&zserv_privs) { + err = ns_switchback_to_initial(); + } + if (err < 0) { + zebra_ns_continue_read(zns_info, stop_retry); + return; + } + + /* check default name is not already set */ + if (strmatch(VRF_DEFAULT_NAME, basename(netnspath))) { + zlog_warn("NS notify : NS %s is already default VRF.Cancel VRF Creation", basename(netnspath)); + zebra_ns_continue_read(zns_info, 1); + return; + } + if (zebra_ns_notify_is_default_netns(basename(netnspath))) { + zlog_warn( + "NS notify : NS %s is default VRF. Ignore VRF creation", + basename(netnspath)); + zebra_ns_continue_read(zns_info, 1); + return; + } + + /* success : close fd and create zns context */ + zebra_ns_notify_create_context_from_entry_name(basename(netnspath)); + zebra_ns_continue_read(zns_info, 1); +} + +static void zebra_ns_notify_read(struct thread *t) +{ + int fd_monitor = THREAD_FD(t); + struct inotify_event *event; + char buf[BUFSIZ]; + ssize_t len; + + thread_add_read(zrouter.master, zebra_ns_notify_read, NULL, fd_monitor, + &zebra_netns_notify_current); + len = read(fd_monitor, buf, sizeof(buf)); + if (len < 0) { + flog_err_sys(EC_ZEBRA_NS_NOTIFY_READ, + "NS notify read: failed to read (%s)", + safe_strerror(errno)); + return; + } + for (event = (struct inotify_event *)buf; (char *)event < &buf[len]; + event = (struct inotify_event *)((char *)event + sizeof(*event) + + event->len)) { + char *netnspath; + struct zebra_netns_info *netnsinfo; + + if (!(event->mask & (IN_CREATE | IN_DELETE))) + continue; + + if (offsetof(struct inotify_event, name) + event->len + >= sizeof(buf)) { + flog_err(EC_ZEBRA_NS_NOTIFY_READ, + "NS notify read: buffer underflow"); + break; + } + + if (strnlen(event->name, event->len) == event->len) { + flog_err(EC_ZEBRA_NS_NOTIFY_READ, + "NS notify error: bad event name"); + break; + } + + if (event->mask & IN_DELETE) { + zebra_ns_delete(event->name); + continue; + } + netnspath = ns_netns_pathname(NULL, event->name); + if (!netnspath) + continue; + netnspath = XSTRDUP(MTYPE_NETNS_MISC, netnspath); + netnsinfo = XCALLOC(MTYPE_NETNS_MISC, + sizeof(struct zebra_netns_info)); + netnsinfo->retries = ZEBRA_NS_POLLING_MAX_RETRIES; + netnsinfo->netnspath = netnspath; + thread_add_timer_msec(zrouter.master, zebra_ns_ready_read, + (void *)netnsinfo, 0, NULL); + } +} + +void zebra_ns_notify_parse(void) +{ + struct dirent *dent; + DIR *srcdir = opendir(NS_RUN_DIR); + + if (srcdir == NULL) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "NS parsing init: failed to parse %s", NS_RUN_DIR); + return; + } + while ((dent = readdir(srcdir)) != NULL) { + struct stat st; + + if (strcmp(dent->d_name, ".") == 0 + || strcmp(dent->d_name, "..") == 0) + continue; + if (fstatat(dirfd(srcdir), dent->d_name, &st, 0) < 0) { + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "NS parsing init: failed to parse entry %s", + dent->d_name); + continue; + } + if (S_ISDIR(st.st_mode)) { + zlog_debug("NS parsing init: %s is not a NS", + dent->d_name); + continue; + } + /* check default name is not already set */ + if (strmatch(VRF_DEFAULT_NAME, basename(dent->d_name))) { + zlog_warn("NS notify : NS %s is already default VRF.Cancel VRF Creation", dent->d_name); + continue; + } + if (zebra_ns_notify_is_default_netns(dent->d_name)) { + zlog_warn( + "NS notify : NS %s is default VRF. Ignore VRF creation", + dent->d_name); + continue; + } + zebra_ns_notify_create_context_from_entry_name(dent->d_name); + } + closedir(srcdir); +} + +void zebra_ns_notify_init(void) +{ + int fd_monitor; + + fd_monitor = inotify_init(); + if (fd_monitor < 0) { + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "NS notify init: failed to initialize inotify (%s)", + safe_strerror(errno)); + } + if (inotify_add_watch(fd_monitor, NS_RUN_DIR, + IN_CREATE | IN_DELETE) < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "NS notify watch: failed to add watch (%s)", + safe_strerror(errno)); + } + thread_add_read(zrouter.master, zebra_ns_notify_read, NULL, fd_monitor, + &zebra_netns_notify_current); +} + +void zebra_ns_notify_close(void) +{ + if (zebra_netns_notify_current == NULL) + return; + + int fd = 0; + + if (zebra_netns_notify_current->u.fd > 0) + fd = zebra_netns_notify_current->u.fd; + + if (zebra_netns_notify_current->master != NULL) + THREAD_OFF(zebra_netns_notify_current); + + /* auto-removal of notify items */ + if (fd > 0) + close(fd); +} + +#else +void zebra_ns_notify_parse(void) +{ +} + +void zebra_ns_notify_init(void) +{ +} + +void zebra_ns_notify_close(void) +{ +} +#endif /* !HAVE_NETLINK */ |