diff options
author | David Lamparter <equinox@diac24.net> | 2019-08-08 19:25:39 +0200 |
---|---|---|
committer | David Lamparter <equinox@diac24.net> | 2020-07-22 12:56:04 +0200 |
commit | 33606a154753f6f50e711d185ee2728d3a32660e (patch) | |
tree | 1426f20ac38fc3b21e82aaaf07892c9177338dcf /watchfrr | |
parent | Merge pull request #6729 from volta-networks/fix_stale_pw_status (diff) | |
download | frr-33606a154753f6f50e711d185ee2728d3a32660e.tar.xz frr-33606a154753f6f50e711d185ee2728d3a32660e.zip |
watchfrr: add (network) namespace support
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>
Diffstat (limited to 'watchfrr')
-rw-r--r-- | watchfrr/watchfrr.c | 181 |
1 files changed, 178 insertions, 3 deletions
diff --git a/watchfrr/watchfrr.c b/watchfrr/watchfrr.c index db327a0fa..d1003ad5f 100644 --- a/watchfrr/watchfrr.c +++ b/watchfrr/watchfrr.c @@ -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); |