summaryrefslogtreecommitdiffstats
path: root/watchfrr
diff options
context:
space:
mode:
authorDavid Lamparter <equinox@diac24.net>2019-08-08 19:25:39 +0200
committerDavid Lamparter <equinox@diac24.net>2020-07-22 12:56:04 +0200
commit33606a154753f6f50e711d185ee2728d3a32660e (patch)
tree1426f20ac38fc3b21e82aaaf07892c9177338dcf /watchfrr
parentMerge pull request #6729 from volta-networks/fix_stale_pw_status (diff)
downloadfrr-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.c181
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);