mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-08-04 20:18:54 +00:00
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:
parent
f1ed7d22d6
commit
33606a1547
10
lib/libfrr.c
10
lib/libfrr.c
@ -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");
|
||||
|
@ -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"
|
||||
|
@ -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." \
|
||||
|
@ -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 $?
|
||||
;;
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user