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>
This commit is contained in:
David Lamparter 2019-08-08 19:25:39 +02:00
parent f1ed7d22d6
commit 33606a1547
5 changed files with 205 additions and 18 deletions

View 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");

View 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"

View File

@ -16,10 +16,14 @@
#
# 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." \

View 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 $?
;;

View 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);