]> git.puffer.fish Git - matthieu/frr.git/commitdiff
watchfrr: add (network) namespace support
authorDavid Lamparter <equinox@diac24.net>
Thu, 8 Aug 2019 17:25:39 +0000 (19:25 +0200)
committerDavid Lamparter <equinox@diac24.net>
Wed, 22 Jul 2020 10:56:04 +0000 (12:56 +0200)
This adds -N and --netns options to watchfrr, allowing it to start
daemons with -N and switching network namespaces respectively.

Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
lib/libfrr.c
tools/etc/frr/daemons
tools/frrcommon.sh.in
tools/frrinit.sh.in
watchfrr/watchfrr.c

index b3df7de6d3dc2d54ac462e89e85a5dc299c10736..2597eb61e2407ef190ebdd99ac9b98a90c7653f8 100644 (file)
@@ -105,6 +105,7 @@ static const struct option lo_always[] = {
        {"daemon", no_argument, NULL, 'd'},
        {"module", no_argument, NULL, 'M'},
        {"profile", required_argument, NULL, 'F'},
+       {"pathspace", required_argument, NULL, 'N'},
        {"vty_socket", required_argument, NULL, OPTION_VTYSOCK},
        {"moduledir", required_argument, NULL, OPTION_MODULEDIR},
        {"log", required_argument, NULL, OPTION_LOG},
@@ -113,12 +114,13 @@ static const struct option lo_always[] = {
        {"command-log-always", no_argument, NULL, OPTION_LOGGING},
        {NULL}};
 static const struct optspec os_always = {
-       "hvdM:F:",
+       "hvdM:F:N:",
        "  -h, --help         Display this help and exit\n"
        "  -v, --version      Print program version\n"
        "  -d, --daemon       Runs in daemon mode\n"
        "  -M, --module       Load specified module\n"
        "  -F, --profile      Use specified configuration profile\n"
+       "  -N, --pathspace    Insert prefix into config & socket paths\n"
        "      --vty_socket   Override vty socket path\n"
        "      --moduledir    Override modules directory\n"
        "      --log          Set Logging to stdout, syslog, or file:<name>\n"
@@ -133,18 +135,16 @@ static const struct option lo_cfg_pid_dry[] = {
 #ifdef HAVE_SQLITE3
        {"db_file", required_argument, NULL, OPTION_DB_FILE},
 #endif
-       {"pathspace", required_argument, NULL, 'N'},
        {"dryrun", no_argument, NULL, 'C'},
        {"terminal", no_argument, NULL, 't'},
        {NULL}};
 static const struct optspec os_cfg_pid_dry = {
-       "f:i:CtN:",
+       "f:i:Ct",
        "  -f, --config_file  Set configuration file name\n"
        "  -i, --pid_file     Set process identifier file name\n"
 #ifdef HAVE_SQLITE3
        "      --db_file      Set database file name\n"
 #endif
-       "  -N, --pathspace    Insert prefix into config & socket paths\n"
        "  -C, --dryrun       Check configuration for validity and exit\n"
        "  -t, --terminal     Open terminal session on stdio\n"
        "  -d -t              Daemonize after terminal session ends\n",
@@ -428,8 +428,6 @@ static int frr_opt(int opt)
                di->config_file = optarg;
                break;
        case 'N':
-               if (di->flags & FRR_NO_CFG_PID_DRY)
-                       return 1;
                if (di->pathspace) {
                        fprintf(stderr,
                                "-N/--pathspace option specified more than once!\n");
index 8bec3c5bb68d6a3da38ff300a6a9e4b20319ba61..0221b0c19ec8b01e7cb29640194f5fd762b48e45 100644 (file)
@@ -72,6 +72,11 @@ vrrpd_options="  -A 127.0.0.1"
 # The list of daemons to watch is automatically generated by the init script.
 #watchfrr_options=""
 
+# To make watchfrr create/join the specified netns, use the following option:
+#watchfrr_options="--netns"
+# This only has an effect in /etc/frr/<somename>/daemons, and you need to
+# start FRR with "/usr/lib/frr/frrinit.sh start <somename>".
+
 # for debugging purposes, you can specify a "wrap" command to start instead
 # of starting the daemon directly, e.g. to use valgrind on ospfd:
 #   ospfd_wrap="/usr/bin/valgrind"
index 2955f74ce34c5c5cec6167ddec48a45bc5b4595b..1203a2fe7afc2119657851605e7eff0dc23a023e 100644 (file)
 #
 # This script should be installed in  @CFG_SBIN@/frrcommon.sh
 
+# FRR_PATHSPACE is passed in from watchfrr
+suffix="${FRR_PATHSPACE:+/${FRR_PATHSPACE}}"
+nsopt="${FRR_PATHSPACE:+-N ${FRR_PATHSPACE}}"
+
 PATH=/bin:/usr/bin:/sbin:/usr/sbin
 D_PATH="@CFG_SBIN@" # /usr/lib/frr
-C_PATH="@CFG_SYSCONF@" # /etc/frr
-V_PATH="@CFG_STATE@" # /var/run/frr
+C_PATH="@CFG_SYSCONF@${suffix}" # /etc/frr
+V_PATH="@CFG_STATE@${suffix}" # /var/run/frr
 VTYSH="@vtysh_bin@" # /usr/bin/vtysh
 FRR_USER="@enable_user@" # frr
 FRR_GROUP="@enable_group@" # frr
@@ -61,9 +65,9 @@ vtysh_b () {
        [ "$1" = "watchfrr" ] && return 0
        [ -r "$C_PATH/frr.conf" ] || return 0
        if [ -n "$1" ]; then
-               "$VTYSH" -b -n -d "$1"
+               "$VTYSH" `echo $nsopt` -b -n -d "$1"
        else
-               "$VTYSH" -b -n
+               "$VTYSH" `echo $nsopt` -b -n
        fi
 }
 
@@ -156,7 +160,7 @@ daemon_start() {
        instopt="${inst:+-n $inst}"
        eval args="\$${daemon}_options"
 
-       if eval "$all_wrap $wrap $bin -d $frr_global_options $instopt $args"; then
+       if eval "$all_wrap $wrap $bin $nsopt -d $frr_global_options $instopt $args"; then
                log_success_msg "Started $dmninst"
                vtysh_b "$daemon"
        else
@@ -292,9 +296,11 @@ load_old_config() {
 }
 . "$C_PATH/daemons"
 
-load_old_config "$C_PATH/daemons.conf"
-load_old_config "/etc/default/frr"
-load_old_config "/etc/sysconfig/frr"
+if [ -z "$FRR_PATHSPACE" ]; then
+       load_old_config "$C_PATH/daemons.conf"
+       load_old_config "/etc/default/frr"
+       load_old_config "/etc/sysconfig/frr"
+fi
 
 if { declare -p watchfrr_options 2>/dev/null || true; } | grep -q '^declare \-a'; then
        log_warning_msg "watchfrr_options contains a bash array value." \
index 423d6b9b1de6a09706b52f551a5519c1188857c8..539ab7d81677408f037853483322d2cdbb4571d9 100644 (file)
@@ -30,6 +30,9 @@ else
        }
 fi
 
+# "/usr/lib/frr/frrinit.sh start somenamespace"
+FRR_PATHSPACE="$2"
+
 self="`dirname $0`"
 if [ -r "$self/frrcommon.sh" ]; then
        . "$self/frrcommon.sh"
@@ -105,7 +108,7 @@ reload)
 
        NEW_CONFIG_FILE="${2:-$C_PATH/frr.conf}"
        [ ! -r $NEW_CONFIG_FILE ] && log_failure_msg "Unable to read new configuration file $NEW_CONFIG_FILE" && exit 1
-       "$RELOAD_SCRIPT" --reload "$NEW_CONFIG_FILE"
+       "$RELOAD_SCRIPT" --reload "$NEW_CONFIG_FILE" `echo $nsopt`
        exit $?
        ;;
 
index db327a0fa2e7fdcae3257935d0d6272aa331b5d1..d1003ad5fac38785672e54710b57a66daa27b380 100644 (file)
@@ -29,6 +29,7 @@
 #include "lib_errors.h"
 #include "zlog_targets.h"
 #include "network.h"
+#include "printfrr.h"
 
 #include <getopt.h>
 #include <sys/un.h>
@@ -174,6 +175,7 @@ struct daemon {
 #define OPTION_MINRESTART 2000
 #define OPTION_MAXRESTART 2001
 #define OPTION_DRY        2002
+#define OPTION_NETNS      2003
 
 static const struct option longopts[] = {
        {"daemon", no_argument, NULL, 'd'},
@@ -190,6 +192,9 @@ static const struct option longopts[] = {
        {"max-restart-interval", required_argument, NULL, OPTION_MAXRESTART},
        {"pid-file", required_argument, NULL, 'p'},
        {"blank-string", required_argument, NULL, 'b'},
+#ifdef GNU_LINUX
+       {"netns", optional_argument, NULL, OPTION_NETNS},
+#endif
        {"help", no_argument, NULL, 'h'},
        {"version", no_argument, NULL, 'v'},
        {NULL, 0, NULL, 0}};
@@ -244,7 +249,12 @@ Otherwise, the interval is doubled (but capped at the -M value).\n\n",
 -d, --daemon   Run in daemon mode.  In this mode, error messages are sent\n\
                to syslog instead of stdout.\n\
 -S, --statedir Set the vty socket directory (default is %s)\n\
--l, --loglevel Set the logging level (default is %d).\n\
+-N, --pathspace        Insert prefix into config & socket paths\n"
+#ifdef GNU_LINUX
+"    --netns   Create and/or use Linux network namespace.  If no name is\n"
+"              given, uses the value from `-N`.\n"
+#endif
+"-l, --loglevel        Set the logging level (default is %d).\n\
                The value should range from %d (LOG_EMERG) to %d (LOG_DEBUG),\n\
                but it can be set higher than %d if extra-verbose debugging\n\
                messages are desired.\n\
@@ -704,7 +714,7 @@ static void daemon_send_ready(int exitcode)
 
        frr_detach();
 
-       snprintf(started, sizeof(started), "%s%s", frr_vtydir,
+       snprintf(started, sizeof(started), "%s/%s", frr_vtydir,
                 "watchfrr.started");
        fp = fopen(started, "w");
        if (fp)
@@ -1102,6 +1112,148 @@ static int startup_timeout(struct thread *t_wakeup)
        return 0;
 }
 
+#ifdef GNU_LINUX
+
+#include <sys/mount.h>
+#include <sched.h>
+
+#define NETNS_RUN_DIR "/var/run/netns"
+
+static void netns_create(int dirfd, const char *nsname)
+{
+       /* make /var/run/netns shared between mount namespaces
+        * just like iproute2 sets it up
+        */
+       if (mount("", NETNS_RUN_DIR, "none", MS_SHARED | MS_REC, NULL)) {
+               if (errno != EINVAL) {
+                       perror("mount");
+                       exit(1);
+               }
+
+               if (mount(NETNS_RUN_DIR, NETNS_RUN_DIR, "none",
+                         MS_BIND | MS_REC, NULL)) {
+                       perror("mount");
+                       exit(1);
+               }
+
+               if (mount("", NETNS_RUN_DIR, "none", MS_SHARED | MS_REC,
+                         NULL)) {
+                       perror("mount");
+                       exit(1);
+               }
+       }
+
+       /* need an empty file to mount on top of */
+       int nsfd = openat(dirfd, nsname, O_CREAT | O_RDONLY | O_EXCL, 0);
+
+       if (nsfd < 0) {
+               fprintf(stderr, "failed to create \"%s/%s\": %s\n",
+                       NETNS_RUN_DIR, nsname, strerror(errno));
+               exit(1);
+       }
+       close(nsfd);
+
+       if (unshare(CLONE_NEWNET)) {
+               perror("unshare");
+               unlinkat(dirfd, nsname, 0);
+               exit(1);
+       }
+
+       char *dstpath = asprintfrr(MTYPE_TMP, "%s/%s", NETNS_RUN_DIR, nsname);
+
+       /* bind-mount so the namespace has a name and is persistent */
+       if (mount("/proc/self/ns/net", dstpath, "none", MS_BIND, NULL) < 0) {
+               fprintf(stderr, "failed to bind-mount netns to \"%s\": %s\n",
+                       dstpath, strerror(errno));
+               unlinkat(dirfd, nsname, 0);
+               exit(1);
+       }
+
+       XFREE(MTYPE_TMP, dstpath);
+}
+
+static void netns_setup(const char *nsname)
+{
+       int dirfd, nsfd;
+
+       dirfd = open(NETNS_RUN_DIR, O_DIRECTORY | O_RDONLY);
+       if (dirfd < 0) {
+               if (errno == ENOTDIR) {
+                       fprintf(stderr, "error: \"%s\" is not a directory!\n",
+                               NETNS_RUN_DIR);
+                       exit(1);
+               } else if (errno == ENOENT) {
+                       if (mkdir(NETNS_RUN_DIR, 0755)) {
+                               fprintf(stderr, "error: \"%s\": mkdir: %s\n",
+                                       NETNS_RUN_DIR, strerror(errno));
+                               exit(1);
+                       }
+                       dirfd = open(NETNS_RUN_DIR, O_DIRECTORY | O_RDONLY);
+                       if (dirfd < 0) {
+                               fprintf(stderr, "error: \"%s\": opendir: %s\n",
+                                       NETNS_RUN_DIR, strerror(errno));
+                               exit(1);
+                       }
+               } else {
+                       fprintf(stderr, "error: \"%s\": %s\n",
+                               NETNS_RUN_DIR, strerror(errno));
+                       exit(1);
+               }
+       }
+
+       nsfd = openat(dirfd, nsname, O_RDONLY);
+       if (nsfd < 0 && errno != ENOENT) {
+               fprintf(stderr, "error: \"%s/%s\": %s\n",
+                       NETNS_RUN_DIR, nsname, strerror(errno));
+               exit(1);
+       }
+       if (nsfd < 0)
+               netns_create(dirfd, nsname);
+       else {
+               if (setns(nsfd, CLONE_NEWNET)) {
+                       perror("setns");
+                       exit(1);
+               }
+               close(nsfd);
+       }
+       close(dirfd);
+
+       /* make sure loopback is up... weird things happen otherwise.
+        * ioctl is perfectly fine for this, don't need netlink...
+        */
+       int sockfd;
+       struct ifreq ifr = { };
+
+       strlcpy(ifr.ifr_name, "lo", sizeof(ifr.ifr_name));
+
+       sockfd = socket(AF_INET, SOCK_DGRAM, 0);
+       if (sockfd < 0) {
+               perror("socket");
+               exit(1);
+       }
+       if (ioctl(sockfd, SIOCGIFFLAGS, &ifr)) {
+               perror("ioctl(SIOCGIFFLAGS, \"lo\")");
+               exit(1);
+       }
+       if (!(ifr.ifr_flags & IFF_UP)) {
+               ifr.ifr_flags |= IFF_UP;
+               if (ioctl(sockfd, SIOCSIFFLAGS, &ifr)) {
+                       perror("ioctl(SIOCSIFFLAGS, \"lo\")");
+                       exit(1);
+               }
+       }
+       close(sockfd);
+}
+
+#else /* !GNU_LINUX */
+
+static void netns_setup(const char *nsname)
+{
+       fprintf(stderr, "network namespaces are only available on Linux\n");
+       exit(1);
+}
+#endif
+
 static void watchfrr_init(int argc, char **argv)
 {
        const char *special = "zebra";
@@ -1191,11 +1343,13 @@ int main(int argc, char **argv)
 {
        int opt;
        const char *blankstr = NULL;
+       const char *netns = NULL;
+       bool netns_en = false;
 
        frr_preinit(&watchfrr_di, argc, argv);
        progname = watchfrr_di.progname;
 
-       frr_opt_add("b:dk:l:i:p:r:S:s:t:T:" DEPRECATED_OPTIONS, longopts, "");
+       frr_opt_add("b:di:k:l:N:p:r:S:s:t:T:" DEPRECATED_OPTIONS, longopts, "");
 
        gs.restart.name = "all";
        while ((opt = frr_getopt(argc, argv, NULL)) != EOF) {
@@ -1260,6 +1414,16 @@ int main(int argc, char **argv)
                                frr_help_exit(1);
                        }
                } break;
+               case OPTION_NETNS:
+                       netns_en = true;
+                       if (strchr(optarg, '/')) {
+                               fprintf(stderr,
+                                       "invalid network namespace name \"%s\" (may not contain slashes)\n",
+                                       optarg);
+                               frr_help_exit(1);
+                       }
+                       netns = optarg;
+                       break;
                case 'i': {
                        char garbage[3];
                        int period;
@@ -1351,6 +1515,17 @@ int main(int argc, char **argv)
 
        gs.restart.interval = gs.min_restart_interval;
 
+       /* env variable for the processes that we start */
+       if (watchfrr_di.pathspace)
+               setenv("FRR_PATHSPACE", watchfrr_di.pathspace, 1);
+       else
+               unsetenv("FRR_PATHSPACE");
+
+       if (netns_en && !netns)
+               netns = watchfrr_di.pathspace;
+       if (netns_en && netns && netns[0])
+               netns_setup(netns);
+
        master = frr_init();
        watchfrr_error_init();
        watchfrr_init(argc, argv);