| /* |
| * teamd.c - Network team device daemon |
| * Copyright (C) 2011-2015 Jiri Pirko <jiri@resnulli.us> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| #include <getopt.h> |
| #include <errno.h> |
| #include <signal.h> |
| #include <stdbool.h> |
| #include <inttypes.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| #include <sys/select.h> |
| #include <linux/netdevice.h> |
| #include <sys/syslog.h> |
| #include <sys/timerfd.h> |
| #include <libdaemon/dfork.h> |
| #include <libdaemon/dsignal.h> |
| #include <libdaemon/dlog.h> |
| #include <libdaemon/dpid.h> |
| #include <private/list.h> |
| #include <private/misc.h> |
| #include <team.h> |
| |
| #include "config.h" |
| #include "teamd.h" |
| #include "teamd_workq.h" |
| #include "teamd_config.h" |
| #include "teamd_state.h" |
| #include "teamd_usock.h" |
| #include "teamd_dbus.h" |
| #include "teamd_zmq.h" |
| #include "teamd_phys_port_check.h" |
| |
| enum teamd_exit_code { |
| TEAMD_EXIT_SUCCESS, |
| TEAMD_EXIT_FAILURE, |
| TEAMD_EXIT_RUNTIME_FAILURE, |
| }; |
| |
| static const struct teamd_runner *teamd_runner_list[] = { |
| &teamd_runner_broadcast, |
| &teamd_runner_roundrobin, |
| &teamd_runner_random, |
| &teamd_runner_activebackup, |
| &teamd_runner_loadbalance, |
| &teamd_runner_lacp, |
| }; |
| |
| #define TEAMD_RUNNER_LIST_SIZE ARRAY_SIZE(teamd_runner_list) |
| |
| static const struct teamd_runner *teamd_find_runner(const char *runner_name) |
| { |
| int i; |
| |
| for (i = 0; i < TEAMD_RUNNER_LIST_SIZE; i++) { |
| if (strcmp(teamd_runner_list[i]->name, runner_name) == 0) |
| return teamd_runner_list[i]; |
| } |
| return NULL; |
| } |
| |
| #define TEAMD_DEFAULT_RUNNER_NAME "roundrobin" |
| #define TEAMD_DEFAULT_DEVNAME_PREFIX "team" |
| |
| static void libteam_log_daemon(struct team_handle *th, int priority, |
| const char *file, int line, const char *fn, |
| const char *format, va_list args) |
| { |
| daemon_logv(priority, format, args); |
| } |
| |
| static char **__g_pid_file; |
| |
| static void print_help(const struct teamd_context *ctx) { |
| int i; |
| |
| printf( |
| "%s [options]\n" |
| " -h --help Show this help\n" |
| " -d --daemonize Daemonize after startup\n" |
| " -k --kill Kill running daemon instance\n" |
| " -e --check Return 0 if a daemon is already running\n" |
| " -v --version Show version\n" |
| " -f --config-file=FILE Load the specified configuration file\n" |
| " -c --config=TEXT Use given config string (This causes configuration\n" |
| " file will be ignored)\n" |
| " -p --pid-file=FILE Use the specified PID file\n" |
| " -g --debug Increase verbosity\n" |
| " -l --log-output Force teamd log output to stdout, stderr or syslog\n" |
| " -r --force-recreate Force team device recreation in case it\n" |
| " already exists\n" |
| " -o --take-over Take over the device if it already exists\n" |
| " -N --no-quit-destroy Do not destroy the device on quit\n" |
| " -t --team-dev=DEVNAME Use the specified team device\n" |
| " -n --no-ports Start without ports\n" |
| " -D --dbus-enable Enable D-Bus interface\n" |
| " -Z --zmq-enable=ADDRESS Enable ZeroMQ interface\n" |
| " -U --usock-enable Enable UNIX domain socket interface\n" |
| " -u --usock-disable Disable UNIX domain socket interface\n", |
| ctx->argv0); |
| printf("Available runners: "); |
| for (i = 0; i < TEAMD_RUNNER_LIST_SIZE; i++) { |
| if (i != 0) |
| printf(", "); |
| printf("%s", teamd_runner_list[i]->name); |
| } |
| printf("\n"); |
| } |
| |
| static int parse_command_line(struct teamd_context *ctx, |
| int argc, char *argv[]) { |
| int opt; |
| static const struct option long_options[] = { |
| { "help", no_argument, NULL, 'h' }, |
| { "daemonize", no_argument, NULL, 'd' }, |
| { "kill", no_argument, NULL, 'k' }, |
| { "check", no_argument, NULL, 'e' }, |
| { "version", no_argument, NULL, 'v' }, |
| { "config-file", required_argument, NULL, 'f' }, |
| { "config", required_argument, NULL, 'c' }, |
| { "pid-file", required_argument, NULL, 'p' }, |
| { "debug", no_argument, NULL, 'g' }, |
| { "log-output", required_argument, NULL, 'l' }, |
| { "force-recreate", no_argument, NULL, 'r' }, |
| { "take-over", no_argument, NULL, 'o' }, |
| { "no-quit-destroy", no_argument, NULL, 'N' }, |
| { "team-dev", required_argument, NULL, 't' }, |
| { "no-ports", no_argument, NULL, 'n' }, |
| { "dbus-enable", no_argument, NULL, 'D' }, |
| { "zmq-enable", required_argument, NULL, 'Z' }, |
| { "usock-enable", no_argument, NULL, 'U' }, |
| { "usock-disable", no_argument, NULL, 'u' }, |
| { NULL, 0, NULL, 0 } |
| }; |
| |
| while ((opt = getopt_long(argc, argv, "hdkevf:c:p:gl:roNt:nDZ:Uu", |
| long_options, NULL)) >= 0) { |
| |
| switch(opt) { |
| case 'h': |
| ctx->cmd = DAEMON_CMD_HELP; |
| break; |
| case 'd': |
| ctx->daemonize = true; |
| break; |
| case 'k': |
| ctx->cmd = DAEMON_CMD_KILL; |
| break; |
| case 'e': |
| ctx->cmd = DAEMON_CMD_CHECK; |
| break; |
| case 'v': |
| ctx->cmd = DAEMON_CMD_VERSION; |
| break; |
| case 'f': |
| free(ctx->config_file); |
| ctx->config_file = realpath(optarg, NULL); |
| if (!ctx->config_file) { |
| fprintf(stderr, "Failed to get absolute path of \"%s\": %s\n", |
| optarg, strerror(errno)); |
| return -1; |
| } |
| break; |
| case 'c': |
| free(ctx->config_text); |
| ctx->config_text = strdup(optarg); |
| break; |
| case 'p': |
| free(ctx->pid_file); |
| ctx->pid_file = strdup(optarg); |
| break; |
| case 'g': |
| ctx->debug++; |
| break; |
| case 'l': |
| free(ctx->log_output); |
| ctx->log_output = strdup(optarg); |
| break; |
| case 'r': |
| ctx->force_recreate = true; |
| break; |
| case 'o': |
| ctx->take_over = true; |
| break; |
| case 'N': |
| ctx->no_quit_destroy = true; |
| break; |
| case 't': |
| free(ctx->team_devname); |
| ctx->team_devname = strdup(optarg); |
| break; |
| case 'n': |
| ctx->init_no_ports = true; |
| break; |
| case 'D': |
| #ifndef ENABLE_DBUS |
| fprintf(stderr, "D-Bus support is not compiled-in\n"); |
| return -1; |
| #else |
| ctx->dbus.enabled = true; |
| #endif |
| break; |
| case 'Z': |
| #ifndef ENABLE_ZMQ |
| fprintf(stderr, "ZeroMQ support is not compiled-in\n"); |
| return -1; |
| #else |
| ctx->zmq.enabled = true; |
| ctx->zmq.addr = optarg; |
| #endif |
| break; |
| case 'U': |
| ctx->usock.enabled = true; |
| break; |
| case 'u': |
| ctx->usock.enabled = false; |
| break; |
| default: |
| return -1; |
| } |
| } |
| |
| if (optind < argc) { |
| fprintf(stderr, "Too many arguments\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static const char *teamd_pid_file_proc(void) { |
| return *__g_pid_file; |
| } |
| |
| static int handle_period_fd(int fd) |
| { |
| ssize_t ret; |
| uint64_t exp; |
| |
| ret = read(fd, &exp, sizeof(uint64_t)); |
| if (ret == -1) { |
| if (errno == EINTR || errno == EAGAIN) |
| return 0; |
| teamd_log_err("read() failed."); |
| return -errno; |
| } |
| if (ret == 0) { |
| teamd_log_warn("read() for timer_fd returned 0."); |
| return 0; |
| } |
| if (ret != sizeof(uint64_t)) { |
| teamd_log_err("read() returned unexpected number of bytes."); |
| return -EINVAL; |
| } |
| if (exp > 1) |
| teamd_log_warn("some periodic function calls missed (%" PRIu64 ")", |
| exp - 1); |
| return 0; |
| } |
| |
| struct teamd_loop_callback { |
| struct list_item list; |
| char *name; |
| void *priv; |
| teamd_loop_callback_func_t func; |
| int fd; |
| int fd_event; |
| bool is_period; |
| bool enabled; |
| }; |
| |
| static void teamd_run_loop_set_fds(struct list_item *lcb_list, |
| fd_set *fds, int *fdmax) |
| { |
| struct teamd_loop_callback *lcb; |
| int i; |
| |
| list_for_each_node_entry(lcb, lcb_list, list) { |
| if (!lcb->enabled) |
| continue; |
| for (i = 0; i < 3; i++) { |
| if (lcb->fd_event & (1 << i)) { |
| FD_SET(lcb->fd, &fds[i]); |
| if (lcb->fd >= *fdmax) |
| *fdmax = lcb->fd + 1; |
| } |
| } |
| } |
| } |
| |
| static int teamd_check_ctrl(struct teamd_context *ctx) |
| { |
| int ctrl_fd = ctx->run_loop.ctrl_pipe_r; |
| struct timeval tv; |
| fd_set rfds; |
| |
| FD_ZERO(&rfds); |
| FD_SET(ctrl_fd, &rfds); |
| tv.tv_sec = tv.tv_usec = 0; |
| |
| return select(ctrl_fd + 1, &rfds, NULL, NULL, &tv); |
| } |
| |
| static int teamd_run_loop_do_callbacks(struct list_item *lcb_list, fd_set *fds, |
| struct teamd_context *ctx) |
| { |
| struct teamd_loop_callback *lcb; |
| int i; |
| int events; |
| int err; |
| |
| list_for_each_node_entry(lcb, lcb_list, list) { |
| for (i = 0; i < 3; i++) { |
| if (!(lcb->fd_event & (1 << i))) |
| continue; |
| events = 0; |
| if (FD_ISSET(lcb->fd, &fds[i])) |
| events |= (1 << i); |
| if (!events) |
| continue; |
| if (lcb->is_period) { |
| err = handle_period_fd(lcb->fd); |
| if (err) |
| return err; |
| } |
| err = lcb->func(ctx, events, lcb->priv); |
| if (err) { |
| teamd_log_warn("Loop callback failed with: %s", |
| strerror(-err)); |
| teamd_log_dbg(ctx, "Failed loop callback: %s, %p", |
| lcb->name, lcb->priv); |
| } |
| |
| /* |
| * If there's a control byte ready, it's possible that |
| * one or more entries have been removed from the |
| * callback list and restart has been requested. In any |
| * case, let the main loop deal with it first so that |
| * we know we're safe to proceed. |
| */ |
| if (teamd_check_ctrl(ctx)) |
| return 0; |
| } |
| } |
| return 0; |
| } |
| |
| static int teamd_flush_ports(struct teamd_context *ctx) |
| { |
| if (!ctx->no_quit_destroy) |
| return teamd_port_remove_all(ctx); |
| else |
| teamd_port_obj_remove_all(ctx); |
| return 0; |
| } |
| |
| static int teamd_run_loop_run(struct teamd_context *ctx) |
| { |
| int err; |
| int ctrl_fd = ctx->run_loop.ctrl_pipe_r; |
| fd_set fds[3]; |
| int fdmax; |
| char ctrl_byte; |
| int i; |
| bool quit_in_progress = false; |
| |
| /* |
| * To process all things correctly during cleanup, on quit command |
| * received via control pipe ('q') do flush all existing ports. |
| * After that wait until all ports are gone and return. |
| */ |
| |
| while (true) { |
| if (quit_in_progress && !teamd_has_ports(ctx)) |
| return ctx->run_loop.err; |
| |
| for (i = 0; i < 3; i++) |
| FD_ZERO(&fds[i]); |
| FD_SET(ctrl_fd, &fds[0]); |
| fdmax = ctrl_fd + 1; |
| |
| teamd_run_loop_set_fds(&ctx->run_loop.callback_list, |
| fds, &fdmax); |
| |
| while (select(fdmax, &fds[0], &fds[1], &fds[2], NULL) < 0) { |
| if (errno == EINTR) |
| continue; |
| |
| teamd_log_err("select() failed."); |
| return -errno; |
| } |
| |
| if (FD_ISSET(ctrl_fd, &fds[0])) { |
| err = read(ctrl_fd, &ctrl_byte, 1); |
| if (err != -1) { |
| switch(ctrl_byte) { |
| case 'q': |
| if (quit_in_progress) |
| return -EBUSY; |
| err = teamd_flush_ports(ctx); |
| if (err) |
| return err; |
| quit_in_progress = true; |
| continue; |
| case 'r': |
| continue; |
| } |
| } else if (errno == EINTR || errno == EAGAIN) { |
| continue; |
| } else { |
| teamd_log_err("read() failed."); |
| return -errno; |
| } |
| } |
| |
| err = teamd_run_loop_do_callbacks(&ctx->run_loop.callback_list, |
| fds, ctx); |
| if (err) |
| return err; |
| } |
| return 0; |
| } |
| |
| static void teamd_run_loop_sent_ctrl_byte(struct teamd_context *ctx, |
| const char ctrl_byte) |
| { |
| int err; |
| |
| retry: |
| err = write(ctx->run_loop.ctrl_pipe_w, &ctrl_byte, 1); |
| if (err == -1 && errno == EINTR) |
| goto retry; |
| } |
| |
| void teamd_run_loop_quit(struct teamd_context *ctx, int err) |
| { |
| ctx->run_loop.err = err; |
| teamd_run_loop_sent_ctrl_byte(ctx, 'q'); |
| } |
| |
| void teamd_run_loop_restart(struct teamd_context *ctx) |
| { |
| teamd_run_loop_sent_ctrl_byte(ctx, 'r'); |
| } |
| |
| static struct teamd_loop_callback *__get_lcb(struct teamd_context *ctx, |
| const char *cb_name, void *priv, |
| struct teamd_loop_callback *last) |
| { |
| struct teamd_loop_callback *lcb; |
| bool last_found; |
| |
| last_found = last == NULL ? true: false; |
| list_for_each_node_entry(lcb, &ctx->run_loop.callback_list, list) { |
| if (!last_found) { |
| if (lcb == last) |
| last_found = true; |
| continue; |
| } |
| if (cb_name && strcmp(lcb->name, cb_name)) |
| continue; |
| if (priv && lcb->priv != priv) |
| continue; |
| return lcb; |
| } |
| return NULL; |
| } |
| |
| static struct teamd_loop_callback *get_lcb(struct teamd_context *ctx, |
| const char *cb_name, void *priv) |
| { |
| return __get_lcb(ctx, cb_name, priv, NULL); |
| } |
| |
| static struct teamd_loop_callback *get_lcb_multi(struct teamd_context *ctx, |
| const char *cb_name, |
| void *priv, |
| struct teamd_loop_callback *last) |
| { |
| return __get_lcb(ctx, cb_name, priv, last); |
| } |
| |
| #define for_each_lcb_multi_match(lcb, ctx, cb_name, priv) \ |
| for (lcb = get_lcb_multi(ctx, cb_name, priv, NULL); lcb; \ |
| lcb = get_lcb_multi(ctx, cb_name, priv, lcb)) |
| |
| #define for_each_lcb_multi_match_safe(lcb, tmp, ctx, cb_name, priv) \ |
| for (lcb = get_lcb_multi(ctx, cb_name, priv, NULL), \ |
| tmp = get_lcb_multi(ctx, cb_name, priv, lcb); \ |
| lcb; \ |
| lcb = tmp, \ |
| tmp = get_lcb_multi(ctx, cb_name, priv, lcb)) |
| |
| static int __teamd_loop_callback_fd_add(struct teamd_context *ctx, |
| const char *cb_name, void *priv, |
| teamd_loop_callback_func_t func, |
| int fd, int fd_event, bool tail) |
| { |
| int err; |
| struct teamd_loop_callback *lcb; |
| |
| if (!cb_name || !priv) |
| return -EINVAL; |
| if (get_lcb(ctx, cb_name, priv)) { |
| teamd_log_err("Callback named \"%s\" is already registered.", |
| cb_name); |
| return -EEXIST; |
| } |
| lcb = myzalloc(sizeof(*lcb)); |
| if (!lcb) { |
| teamd_log_err("Failed alloc memory for callback."); |
| return -ENOMEM; |
| } |
| lcb->name = strdup(cb_name); |
| if (!lcb->name) { |
| err = -ENOMEM; |
| goto lcb_free; |
| } |
| lcb->priv = priv; |
| lcb->func = func; |
| lcb->fd = fd; |
| lcb->fd_event = fd_event & TEAMD_LOOP_FD_EVENT_MASK; |
| if (tail) |
| list_add_tail(&ctx->run_loop.callback_list, &lcb->list); |
| else |
| list_add(&ctx->run_loop.callback_list, &lcb->list); |
| teamd_log_dbg(ctx, "Added loop callback: %s, %p", lcb->name, lcb->priv); |
| return 0; |
| |
| lcb_free: |
| free(lcb); |
| return err; |
| } |
| |
| int teamd_loop_callback_fd_add(struct teamd_context *ctx, |
| const char *cb_name, void *priv, |
| teamd_loop_callback_func_t func, |
| int fd, int fd_event) |
| { |
| return __teamd_loop_callback_fd_add(ctx, cb_name, priv, func, |
| fd, fd_event, false); |
| } |
| |
| int teamd_loop_callback_fd_add_tail(struct teamd_context *ctx, |
| const char *cb_name, void *priv, |
| teamd_loop_callback_func_t func, |
| int fd, int fd_event) |
| { |
| return __teamd_loop_callback_fd_add(ctx, cb_name, priv, func, |
| fd, fd_event, true); |
| } |
| |
| static int __timerfd_reset(int fd, struct timespec *interval, |
| struct timespec *initial) |
| { |
| struct itimerspec its; |
| |
| memset(&its, 0, sizeof(its)); |
| if (interval) |
| its.it_interval = *interval; |
| if (initial) |
| its.it_value = *initial; |
| else |
| its.it_value.tv_nsec = 1; /* to enable that */ |
| if (timerfd_settime(fd, 0, &its, NULL) < 0) { |
| teamd_log_err("Failed to set timerfd."); |
| return -errno; |
| } |
| return 0; |
| } |
| |
| int teamd_loop_callback_timer_add_set(struct teamd_context *ctx, |
| const char *cb_name, void *priv, |
| teamd_loop_callback_func_t func, |
| struct timespec *interval, |
| struct timespec *initial) |
| { |
| int err; |
| int fd; |
| |
| fd = timerfd_create(CLOCK_MONOTONIC, 0); |
| if (fd < 0) { |
| teamd_log_err("Failed to create timerfd."); |
| return -errno; |
| } |
| if (interval || initial) { |
| err = __timerfd_reset(fd, interval, initial); |
| if (err) { |
| close(fd); |
| return err; |
| } |
| } |
| err = teamd_loop_callback_fd_add(ctx, cb_name, priv, func, fd, |
| TEAMD_LOOP_FD_EVENT_READ); |
| if (err) { |
| close(fd); |
| return err; |
| } |
| get_lcb(ctx, cb_name, priv)->is_period = true; |
| return 0; |
| } |
| |
| int teamd_loop_callback_timer_add(struct teamd_context *ctx, |
| const char *cb_name, void *priv, |
| teamd_loop_callback_func_t func) |
| { |
| return teamd_loop_callback_timer_add_set(ctx, cb_name, priv, func, |
| NULL, NULL); |
| } |
| |
| int teamd_loop_callback_timer_set(struct teamd_context *ctx, |
| const char *cb_name, |
| void *priv, |
| struct timespec *interval, |
| struct timespec *initial) |
| { |
| struct teamd_loop_callback *lcb; |
| |
| if (!cb_name || !priv) |
| return -EINVAL; |
| lcb = get_lcb(ctx, cb_name, priv); |
| if (!lcb) { |
| teamd_log_err("Callback named \"%s\" not found.", cb_name); |
| return -ENOENT; |
| } |
| if (!lcb->is_period) { |
| teamd_log_err("Can't reset non-periodic callback."); |
| return -EINVAL; |
| } |
| return __timerfd_reset(lcb->fd, interval, initial); |
| } |
| |
| void teamd_loop_callback_del(struct teamd_context *ctx, const char *cb_name, |
| void *priv) |
| { |
| struct teamd_loop_callback *lcb; |
| struct teamd_loop_callback *tmp; |
| bool found = false; |
| |
| for_each_lcb_multi_match_safe(lcb, tmp, ctx, cb_name, priv) { |
| list_del(&lcb->list); |
| if (lcb->is_period) |
| close(lcb->fd); |
| teamd_log_dbg(ctx, "Removed loop callback: %s, %p", |
| lcb->name, lcb->priv); |
| free(lcb->name); |
| free(lcb); |
| found = true; |
| } |
| if (found) |
| teamd_run_loop_restart(ctx); |
| else |
| teamd_log_dbg(ctx, "Callback named \"%s\" not found.", cb_name); |
| } |
| |
| int teamd_loop_callback_enable(struct teamd_context *ctx, const char *cb_name, |
| void *priv) |
| { |
| struct teamd_loop_callback *lcb; |
| bool found = false; |
| |
| for_each_lcb_multi_match(lcb, ctx, cb_name, priv) { |
| lcb->enabled = true; |
| found = true; |
| } |
| if (!found) |
| return -ENOENT; |
| teamd_run_loop_restart(ctx); |
| return 0; |
| } |
| |
| int teamd_loop_callback_disable(struct teamd_context *ctx, const char *cb_name, |
| void *priv) |
| { |
| struct teamd_loop_callback *lcb; |
| bool found = false; |
| |
| for_each_lcb_multi_match(lcb, ctx, cb_name, priv) { |
| lcb->enabled = false; |
| found = true; |
| } |
| if (!found) |
| return -ENOENT; |
| teamd_run_loop_restart(ctx); |
| return 0; |
| } |
| |
| static int callback_daemon_signal(struct teamd_context *ctx, int events, |
| void *priv) |
| { |
| int sig; |
| |
| /* Get signal */ |
| if ((sig = daemon_signal_next()) <= 0) { |
| teamd_log_err("daemon_signal_next() failed."); |
| return -EINVAL; |
| } |
| |
| /* Dispatch signal */ |
| switch (sig) { |
| case SIGINT: |
| case SIGQUIT: |
| case SIGTERM: |
| teamd_log_warn("Got SIGINT, SIGQUIT or SIGTERM."); |
| teamd_run_loop_quit(ctx, 0); |
| break; |
| } |
| return 0; |
| } |
| |
| static int callback_libteam_event(struct teamd_context *ctx, int events, |
| void *priv) |
| { |
| return team_handle_events(ctx->th); |
| } |
| |
| #define DAEMON_CB_NAME "daemon" |
| #define LIBTEAM_EVENTS_CB_NAME "libteam_events" |
| |
| static int teamd_run_loop_init(struct teamd_context *ctx) |
| { |
| int fds[2]; |
| int err; |
| |
| list_init(&ctx->run_loop.callback_list); |
| err = pipe(fds); |
| if (err) |
| return -errno; |
| ctx->run_loop.ctrl_pipe_r = fds[0]; |
| ctx->run_loop.ctrl_pipe_w = fds[1]; |
| |
| err = teamd_loop_callback_fd_add(ctx, DAEMON_CB_NAME, ctx, |
| callback_daemon_signal, |
| daemon_signal_fd(), |
| TEAMD_LOOP_FD_EVENT_READ); |
| if (err) { |
| teamd_log_err("Failed to add daemon loop callback"); |
| goto close_pipe; |
| } |
| |
| err = teamd_loop_callback_fd_add(ctx, LIBTEAM_EVENTS_CB_NAME, ctx, |
| callback_libteam_event, |
| team_get_event_fd(ctx->th), |
| TEAMD_LOOP_FD_EVENT_READ); |
| if (err) { |
| teamd_log_err("Failed to add libteam event loop callback"); |
| goto del_daemon_callback; |
| } |
| |
| teamd_loop_callback_enable(ctx, DAEMON_CB_NAME, ctx); |
| teamd_loop_callback_enable(ctx, LIBTEAM_EVENTS_CB_NAME, ctx); |
| |
| return 0; |
| |
| del_daemon_callback: |
| teamd_loop_callback_del(ctx, DAEMON_CB_NAME, ctx); |
| |
| close_pipe: |
| close(ctx->run_loop.ctrl_pipe_r); |
| close(ctx->run_loop.ctrl_pipe_w); |
| return err; |
| } |
| |
| static void teamd_run_loop_fini(struct teamd_context *ctx) |
| { |
| teamd_loop_callback_del(ctx, LIBTEAM_EVENTS_CB_NAME, NULL); |
| teamd_loop_callback_del(ctx, DAEMON_CB_NAME, ctx); |
| close(ctx->run_loop.ctrl_pipe_r); |
| close(ctx->run_loop.ctrl_pipe_w); |
| } |
| |
| static int parse_hwaddr(const char *hwaddr_str, char **phwaddr, |
| unsigned int *plen) |
| { |
| const char *pos = hwaddr_str; |
| unsigned int byte_count = 0; |
| unsigned int tmp; |
| int err; |
| char *hwaddr = NULL; |
| char *new_hwaddr; |
| char *endptr; |
| |
| while (true) { |
| errno = 0; |
| tmp = strtoul(pos, &endptr, 16); |
| if (errno != 0 || tmp > 0xFF) { |
| err = -EINVAL; |
| goto err_out; |
| } |
| byte_count++; |
| new_hwaddr = realloc(hwaddr, sizeof(char) * byte_count); |
| if (!new_hwaddr) { |
| err = -ENOMEM; |
| goto err_out; |
| } |
| hwaddr = new_hwaddr; |
| hwaddr[byte_count - 1] = (char) tmp; |
| while (isspace(endptr[0]) && endptr[0] != '\0') |
| endptr++; |
| if (endptr[0] == ':') { |
| pos = endptr + 1; |
| } else if (endptr[0] == '\0') { |
| break; |
| } else { |
| err = -EINVAL; |
| goto err_out; |
| } |
| } |
| *phwaddr = hwaddr; |
| *plen = byte_count; |
| return 0; |
| err_out: |
| free(hwaddr); |
| return err; |
| } |
| |
| static int teamd_set_hwaddr(struct teamd_context *ctx) |
| { |
| int err; |
| const char *hwaddr_str; |
| char *hwaddr; |
| unsigned int hwaddr_len; |
| |
| err = teamd_config_string_get(ctx, &hwaddr_str, "$.hwaddr"); |
| if (err) |
| return 0; /* addr is not defined in config, no change needed */ |
| |
| teamd_log_dbg(ctx, "Hwaddr string: \"%s\".", hwaddr_str); |
| err = parse_hwaddr(hwaddr_str, &hwaddr, &hwaddr_len); |
| if (err) { |
| teamd_log_err("Failed to parse hardware address."); |
| return err; |
| } |
| |
| if (hwaddr_len != ctx->hwaddr_len) { |
| teamd_log_err("Passed hardware address has different length (%d) than team device has (%d).", |
| hwaddr_len, ctx->hwaddr_len); |
| err = -EINVAL; |
| goto free_hwaddr; |
| } |
| |
| if (memcmp(hwaddr, ctx->hwaddr, hwaddr_len)) |
| err = team_hwaddr_set(ctx->th, ctx->ifindex, hwaddr, hwaddr_len); |
| else { |
| err = 0; |
| teamd_log_dbg(ctx, "Skip setting same hwaddr string: \"%s\".", hwaddr_str); |
| } |
| |
| if (!err) |
| ctx->hwaddr_explicit = true; |
| free_hwaddr: |
| free(hwaddr); |
| return err; |
| } |
| |
| static int teamd_add_ports(struct teamd_context *ctx) |
| { |
| int err; |
| const char *key; |
| |
| ctx->pre_add_ports = false; |
| if (ctx->init_no_ports) |
| return 0; |
| |
| teamd_config_for_each_key(key, ctx, "$.ports") { |
| err = teamd_port_add_ifname(ctx, key); |
| if (err == -ENODEV) { |
| teamd_log_warn("%s: Skipped adding a missing port.", key); |
| continue; |
| } else if (err) { |
| teamd_log_err("%s: Failed to add port (%s).", key, |
| strerror(-err)); |
| return err; |
| } |
| } |
| return 0; |
| } |
| |
| static int teamd_hwaddr_check_change(struct teamd_context *ctx, |
| struct teamd_port *tdport) |
| { |
| char *hwaddr; |
| unsigned char hwaddr_len; |
| int err; |
| |
| if (ctx->port_obj_list_count != 1 || ctx->hwaddr_explicit) |
| return 0; |
| hwaddr = team_get_ifinfo_orig_hwaddr(tdport->team_ifinfo); |
| hwaddr_len = team_get_ifinfo_orig_hwaddr_len(tdport->team_ifinfo); |
| if (hwaddr_len != ctx->hwaddr_len) { |
| teamd_log_err("%s: Port original hardware address has different length (%d) than team device has (%d).", |
| tdport->ifname, hwaddr_len, ctx->hwaddr_len); |
| return -EINVAL; |
| } |
| err = team_hwaddr_set(ctx->th, ctx->ifindex, hwaddr, hwaddr_len); |
| if (err) { |
| teamd_log_err("Failed to set team device hardware address."); |
| return err; |
| } |
| memcpy(ctx->hwaddr, hwaddr, hwaddr_len); |
| ctx->hwaddr_len = hwaddr_len; |
| return 0; |
| } |
| |
| static int teamd_event_watch_port_added(struct teamd_context *ctx, |
| struct teamd_port *tdport, void *priv) |
| { |
| int err; |
| int tmp; |
| |
| if (!ctx->pre_add_ports) { |
| err = teamd_hwaddr_check_change(ctx, tdport); |
| if (err) |
| return err; |
| } |
| |
| err = teamd_config_int_get(ctx, &tmp, "$.ports.%s.queue_id", |
| tdport->ifname); |
| if (!err) { |
| uint32_t queue_id; |
| |
| if (tmp < 0) { |
| teamd_log_err("%s: \"queue_id\" must not be negative number.", |
| tdport->ifname); |
| return -EINVAL; |
| } |
| queue_id = tmp; |
| err = team_set_port_queue_id(ctx->th, tdport->ifindex, |
| queue_id); |
| if (err) { |
| teamd_log_err("%s: Failed to set \"queue_id\".", |
| tdport->ifname); |
| return err; |
| } |
| } |
| err = teamd_config_int_get(ctx, &tmp, "$.ports.%s.prio", |
| tdport->ifname); |
| if (err) |
| tmp = 0; |
| err = team_set_port_priority(ctx->th, tdport->ifindex, tmp); |
| if (err) { |
| teamd_log_err("%s: Failed to set \"priority\".", |
| tdport->ifname); |
| return err; |
| } |
| return 0; |
| } |
| |
| static const struct teamd_event_watch_ops teamd_port_watch_ops = { |
| .port_added = teamd_event_watch_port_added, |
| }; |
| |
| static int teamd_port_watch_init(struct teamd_context *ctx) |
| { |
| return teamd_event_watch_register(ctx, &teamd_port_watch_ops, NULL); |
| } |
| |
| static void teamd_port_watch_fini(struct teamd_context *ctx) |
| { |
| teamd_event_watch_unregister(ctx, &teamd_port_watch_ops, NULL); |
| } |
| |
| static int teamd_runner_init(struct teamd_context *ctx) |
| { |
| int err; |
| const char *runner_name; |
| |
| err = teamd_config_string_get(ctx, &runner_name, "$.runner.name"); |
| if (err) { |
| teamd_log_dbg(ctx, "Failed to get team runner name from config."); |
| runner_name = TEAMD_DEFAULT_RUNNER_NAME; |
| err = teamd_config_string_set(ctx, runner_name, "$.runner.name"); |
| if (err) { |
| teamd_log_err("Failed to set default team runner name in config."); |
| return err; |
| } |
| teamd_log_dbg(ctx, "Using default team runner \"%s\".", runner_name); |
| } else { |
| teamd_log_dbg(ctx, "Using team runner \"%s\".", runner_name); |
| } |
| ctx->runner = teamd_find_runner(runner_name); |
| if (!ctx->runner) { |
| teamd_log_err("No runner named \"%s\" available.", runner_name); |
| return -EINVAL; |
| } |
| |
| if (ctx->runner->team_mode_name) { |
| char *cur_mode; |
| const char *new_mode = ctx->runner->team_mode_name; |
| |
| err = team_get_mode_name(ctx->th, &cur_mode); |
| if (err) { |
| teamd_log_err("Failed to det team mode."); |
| return err; |
| } |
| if (strcmp(cur_mode, new_mode)) { |
| err = team_set_mode_name(ctx->th, new_mode); |
| if (err) { |
| teamd_log_err("Failed to set team mode \"%s\".", |
| new_mode); |
| return err; |
| } |
| } |
| } else { |
| teamd_log_warn("Note \"%s\" runner does not select team mode resulting in no functionality!", |
| runner_name); |
| } |
| |
| if (ctx->runner->priv_size) { |
| ctx->runner_priv = myzalloc(ctx->runner->priv_size); |
| if (!ctx->runner_priv) |
| return -ENOMEM; |
| } |
| |
| if (ctx->runner->init) { |
| err = ctx->runner->init(ctx, ctx->runner_priv); |
| if (err) |
| goto free_runner_priv; |
| } |
| return 0; |
| |
| free_runner_priv: |
| free(ctx->runner_priv); |
| return err; |
| } |
| |
| static void teamd_runner_fini(struct teamd_context *ctx) |
| { |
| if (ctx->runner->fini) |
| ctx->runner->fini(ctx, ctx->runner_priv); |
| free(ctx->runner_priv); |
| ctx->runner = NULL; |
| } |
| |
| static int teamd_post_runner_init(struct teamd_context *ctx) |
| { |
| int err; |
| int tmp; |
| |
| err = teamd_config_int_get(ctx, &tmp, "$.notify_peers.count"); |
| if (!err) { |
| uint32_t count; |
| |
| if (tmp < 0) { |
| teamd_log_err("\"count\" must not be negative number."); |
| return -EINVAL; |
| } |
| count = tmp; |
| err = team_set_notify_peers_count(ctx->th, count); |
| if (err) { |
| if (err == -ENOENT) { |
| teamd_log_warn("Failed to set \"notify_peers_count\". Kernel probably does not support this option yet."); |
| } else { |
| teamd_log_err("Failed to set \"notify_peers_count\"."); |
| return err; |
| } |
| } |
| } |
| err = teamd_config_int_get(ctx, &tmp, "$.notify_peers.interval"); |
| if (!err) { |
| uint32_t interval; |
| |
| if (tmp < 0) { |
| teamd_log_err("\"interval\" must not be negative number."); |
| return -EINVAL; |
| } |
| interval = tmp; |
| err = team_set_notify_peers_interval(ctx->th, interval); |
| if (err) { |
| if (err == -ENOENT) { |
| teamd_log_warn("Failed to set \"notify_peers_interval\". Kernel probably does not support this option yet."); |
| } else { |
| teamd_log_err("Failed to set \"notify_peers_interval\"."); |
| return err; |
| } |
| } |
| } |
| err = teamd_config_int_get(ctx, &tmp, "$.mcast_rejoin.count"); |
| if (!err) { |
| uint32_t count; |
| |
| if (tmp < 0) { |
| teamd_log_err("\"count\" must not be negative number."); |
| return -EINVAL; |
| } |
| count = tmp; |
| err = team_set_mcast_rejoin_count(ctx->th, count); |
| if (err) { |
| if (err == -ENOENT) { |
| teamd_log_warn("Failed to set \"mcast_rejoin_count\". Kernel probably does not support this option yet."); |
| } else { |
| teamd_log_err("Failed to set \"mcast_rejoin_count\"."); |
| return err; |
| } |
| } |
| } |
| err = teamd_config_int_get(ctx, &tmp, "$.mcast_rejoin.interval"); |
| if (!err) { |
| uint32_t interval; |
| |
| if (tmp < 0) { |
| teamd_log_err("\"interval\" must not be negative number."); |
| return -EINVAL; |
| } |
| interval = tmp; |
| err = team_set_mcast_rejoin_interval(ctx->th, interval); |
| if (err) { |
| if (err == -ENOENT) { |
| teamd_log_warn("Failed to set \"mcast_rejoin_interval\". Kernel probably does not support this option yet."); |
| } else { |
| teamd_log_err("Failed to set \"mcast_rejoin_interval\"."); |
| return err; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static void debug_log_port_list(struct teamd_context *ctx) |
| { |
| struct team_port *port; |
| char buf[120]; |
| bool trunc; |
| |
| teamd_log_dbg(ctx, "<port_list>"); |
| team_for_each_port(port, ctx->th) { |
| trunc = team_port_str(port, buf, sizeof(buf)); |
| teamd_log_dbg(ctx, "%s %s", buf, trunc ? "<trunc>" : ""); |
| } |
| teamd_log_dbg(ctx, "</port_list>"); |
| } |
| |
| static void debug_log_option_list(struct teamd_context *ctx) |
| { |
| struct team_option *option; |
| char buf[120]; |
| bool trunc; |
| |
| teamd_log_dbgx(ctx, 2, "<changed_option_list>"); |
| team_for_each_option(option, ctx->th) { |
| if (!team_is_option_changed(option) || |
| team_is_option_changed_locally(option)) |
| continue; |
| trunc = team_option_str(ctx->th, option, buf, sizeof(buf)); |
| teamd_log_dbgx(ctx, 2, "%s %s", buf, trunc ? "<trunc>" : ""); |
| } |
| teamd_log_dbgx(ctx, 2, "</changed_option_list>"); |
| } |
| |
| static void debug_log_ifinfo_list(struct teamd_context *ctx) |
| { |
| struct team_ifinfo *ifinfo; |
| char buf[120]; |
| bool trunc; |
| |
| teamd_log_dbg(ctx, "<ifinfo_list>"); |
| team_for_each_ifinfo(ifinfo, ctx->th) { |
| trunc = team_ifinfo_str(ifinfo, buf, sizeof(buf)); |
| teamd_log_dbg(ctx, "%s %s", buf, trunc ? "<trunc>" : ""); |
| } |
| teamd_log_dbg(ctx, "</ifinfo_list>"); |
| } |
| |
| static int debug_change_handler_func(struct team_handle *th, void *priv, |
| team_change_type_mask_t type_mask) |
| { |
| struct teamd_context *ctx = priv; |
| |
| if (type_mask & TEAM_PORT_CHANGE) |
| debug_log_port_list(ctx); |
| if (type_mask & TEAM_OPTION_CHANGE) |
| debug_log_option_list(ctx); |
| if (type_mask & TEAM_IFINFO_CHANGE) |
| debug_log_ifinfo_list(ctx); |
| return 0; |
| } |
| |
| static const struct team_change_handler debug_change_handler = { |
| .func = debug_change_handler_func, |
| .type_mask = TEAM_PORT_CHANGE | TEAM_OPTION_CHANGE | TEAM_IFINFO_CHANGE, |
| }; |
| |
| static int teamd_register_debug_handler(struct teamd_context *ctx) |
| { |
| return team_change_handler_register_head(ctx->th, |
| &debug_change_handler, ctx); |
| } |
| |
| static int teamd_register_default_handlers(struct teamd_context *ctx) |
| { |
| if (!ctx->debug) |
| return 0; |
| return teamd_register_debug_handler(ctx); |
| } |
| |
| static void teamd_unregister_debug_handler(struct teamd_context *ctx) |
| { |
| team_change_handler_unregister(ctx->th, &debug_change_handler, ctx); |
| } |
| |
| static void teamd_unregister_default_handlers(struct teamd_context *ctx) |
| { |
| if (!ctx->debug) |
| return; |
| teamd_unregister_debug_handler(ctx); |
| } |
| |
| int teamd_change_debug_level(struct teamd_context *ctx, unsigned int new_debug) |
| { |
| int err = 0; |
| |
| if (!ctx->debug && new_debug) { |
| daemon_set_verbosity(LOG_DEBUG); |
| err = teamd_register_debug_handler(ctx); |
| } |
| if (ctx->debug && !new_debug) { |
| daemon_set_verbosity(LOG_WARNING); |
| teamd_unregister_debug_handler(ctx); |
| } |
| if (err) |
| return err; |
| ctx->debug = new_debug; |
| return 0; |
| } |
| |
| static int teamd_init(struct teamd_context *ctx) |
| { |
| int err; |
| |
| ctx->th = team_alloc(); |
| if (!ctx->th) { |
| teamd_log_err("Team alloc failed."); |
| return -ENOMEM; |
| } |
| if (ctx->debug) |
| team_set_log_priority(ctx->th, LOG_DEBUG); |
| |
| team_set_log_fn(ctx->th, libteam_log_daemon); |
| |
| ctx->ifindex = team_ifname2ifindex(ctx->th, ctx->team_devname); |
| if (ctx->ifindex && ctx->take_over) |
| goto skip_create; |
| |
| if (ctx->force_recreate) |
| err = team_recreate(ctx->th, ctx->team_devname); |
| else |
| err = team_create(ctx->th, ctx->team_devname); |
| if (err) { |
| teamd_log_err("Failed to create team device."); |
| goto team_free; |
| } |
| |
| ctx->ifindex = team_ifname2ifindex(ctx->th, ctx->team_devname); |
| if (!ctx->ifindex) { |
| teamd_log_err("Netdevice \"%s\" not found.", ctx->team_devname); |
| err = -ENODEV; |
| goto team_destroy; |
| } |
| skip_create: |
| |
| err = team_init(ctx->th, ctx->ifindex); |
| if (err) { |
| teamd_log_err("Team init failed."); |
| goto team_destroy; |
| } |
| |
| ctx->ifinfo = team_get_ifinfo(ctx->th); |
| ctx->hwaddr = team_get_ifinfo_hwaddr(ctx->ifinfo); |
| ctx->hwaddr_len = team_get_ifinfo_hwaddr_len(ctx->ifinfo); |
| |
| err = teamd_set_hwaddr(ctx); |
| if (err) { |
| teamd_log_err("Hardware address set failed."); |
| goto team_destroy; |
| } |
| |
| err = teamd_run_loop_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init run loop."); |
| goto team_destroy; |
| } |
| |
| err = teamd_workq_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init workq."); |
| goto run_loop_fini; |
| } |
| |
| err = teamd_register_default_handlers(ctx); |
| if (err) { |
| teamd_log_err("Failed to register debug event handlers."); |
| goto workq_fini; |
| } |
| |
| err = teamd_events_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init events infrastructure."); |
| goto team_unreg_debug_handlers; |
| } |
| |
| err = teamd_option_watch_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init option watches."); |
| goto events_fini; |
| } |
| |
| err = teamd_ifinfo_watch_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init ifinfo watches."); |
| goto option_watch_fini; |
| } |
| |
| err = teamd_port_watch_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init port watch."); |
| goto ifinfo_watch_fini; |
| } |
| |
| err = teamd_state_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init state json infrastructure."); |
| goto port_watch_fini; |
| } |
| |
| err = teamd_per_port_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init per-port."); |
| goto state_fini; |
| } |
| |
| err = teamd_link_watch_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init link watch."); |
| goto per_port_fini; |
| } |
| |
| err = teamd_runner_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init runner."); |
| goto link_watch_fini; |
| } |
| |
| err = teamd_post_runner_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to do post-runner initializations."); |
| goto runner_fini; |
| } |
| |
| err = teamd_state_basics_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init state json basics."); |
| goto runner_fini; |
| } |
| |
| err = teamd_phys_port_check_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init SR-IOV support."); |
| goto state_basics_fini; |
| } |
| |
| err = teamd_usock_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init unix domain socket."); |
| goto phys_port_check_fini; |
| } |
| |
| err = teamd_dbus_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init dbus."); |
| goto usock_fini; |
| } |
| |
| err = teamd_zmq_init(ctx); |
| if (err) { |
| teamd_log_err("Failed to init zmq."); |
| goto dbus_fini; |
| } |
| |
| ctx->pre_add_ports = true; |
| err = team_refresh(ctx->th); |
| if (err) { |
| teamd_log_err("Team refresh failed."); |
| goto zmq_fini; |
| } |
| |
| err = teamd_add_ports(ctx); |
| if (err) { |
| teamd_log_err("Failed to add ports."); |
| goto zmq_fini; |
| } |
| |
| /* |
| * Expose name as the last thing so watchers like systemd |
| * knows we are here and all ready. |
| */ |
| err = teamd_dbus_expose_name(ctx); |
| if (err) { |
| teamd_log_err("Failed to expose dbus name."); |
| goto zmq_fini; |
| } |
| |
| return 0; |
| zmq_fini: |
| teamd_zmq_fini(ctx); |
| dbus_fini: |
| teamd_dbus_fini(ctx); |
| usock_fini: |
| teamd_usock_fini(ctx); |
| phys_port_check_fini: |
| teamd_phys_port_check_fini(ctx); |
| state_basics_fini: |
| teamd_state_basics_fini(ctx); |
| runner_fini: |
| teamd_runner_fini(ctx); |
| link_watch_fini: |
| teamd_link_watch_fini(ctx); |
| per_port_fini: |
| teamd_per_port_fini(ctx); |
| state_fini: |
| teamd_state_fini(ctx); |
| port_watch_fini: |
| teamd_port_watch_fini(ctx); |
| ifinfo_watch_fini: |
| teamd_ifinfo_watch_fini(ctx); |
| option_watch_fini: |
| teamd_option_watch_fini(ctx); |
| events_fini: |
| teamd_events_fini(ctx); |
| team_unreg_debug_handlers: |
| teamd_unregister_default_handlers(ctx); |
| workq_fini: |
| teamd_workq_fini(ctx); |
| run_loop_fini: |
| teamd_run_loop_fini(ctx); |
| team_destroy: |
| if (!ctx->take_over) |
| team_destroy(ctx->th); |
| team_free: |
| team_free(ctx->th); |
| return err; |
| } |
| |
| static void teamd_fini(struct teamd_context *ctx) |
| { |
| teamd_zmq_fini(ctx); |
| teamd_dbus_fini(ctx); |
| teamd_usock_fini(ctx); |
| teamd_phys_port_check_fini(ctx); |
| teamd_state_basics_fini(ctx); |
| teamd_runner_fini(ctx); |
| teamd_link_watch_fini(ctx); |
| teamd_per_port_fini(ctx); |
| teamd_state_fini(ctx); |
| teamd_ifinfo_watch_fini(ctx); |
| teamd_option_watch_fini(ctx); |
| teamd_events_fini(ctx); |
| teamd_unregister_default_handlers(ctx); |
| teamd_workq_fini(ctx); |
| teamd_run_loop_fini(ctx); |
| if (!ctx->no_quit_destroy) |
| team_destroy(ctx->th); |
| team_free(ctx->th); |
| } |
| |
| static int teamd_start(struct teamd_context *ctx, enum teamd_exit_code *p_ret) |
| { |
| pid_t pid; |
| int err = 0; |
| |
| if (getuid() == 0) |
| teamd_log_warn("This program is not intended to be run as root."); |
| |
| if (daemon_reset_sigs(-1) < 0) { |
| teamd_log_err("Failed to reset all signal handlers."); |
| return -errno; |
| } |
| |
| if (daemon_unblock_sigs(SIGPIPE, -1) < 0) { |
| teamd_log_err("Failed to unblock all signals."); |
| return -errno; |
| } |
| |
| pid = daemon_pid_file_is_running(); |
| if (pid == 0) |
| daemon_pid_file_remove(); |
| if (pid > 0) { |
| teamd_log_err("Daemon already running on PID %u.", pid); |
| return -EEXIST; |
| } |
| |
| if (ctx->daemonize) { |
| daemon_retval_init(); |
| |
| pid = daemon_fork(); |
| if (pid < 0) { |
| teamd_log_err("Daemon fork failed."); |
| daemon_retval_done(); |
| return -errno; |
| } |
| else if (pid != 0) { |
| int ret; |
| |
| /* Parent */ |
| ret = daemon_retval_wait(20); |
| if (ret < 0) { |
| teamd_log_err("Could not receive return value from daemon process."); |
| return -errno; |
| } |
| if (ret > 0) |
| teamd_log_err("Daemon process failed."); |
| return -ret; |
| } |
| |
| /* Child */ |
| } |
| |
| ctx->log_output = ctx->log_output ? : getenv("TEAM_LOG_OUTPUT"); |
| if (ctx->log_output) { |
| if (strcmp(ctx->log_output, "stdout") == 0) |
| daemon_log_use = DAEMON_LOG_STDOUT; |
| else if (strcmp(ctx->log_output, "stderr") == 0) |
| daemon_log_use = DAEMON_LOG_STDERR; |
| else if (strcmp(ctx->log_output, "syslog") == 0) |
| daemon_log_use = DAEMON_LOG_SYSLOG; |
| } |
| |
| if (daemon_close_all(-1) < 0) { |
| teamd_log_err("Failed to close all file descriptors."); |
| daemon_retval_send(errno); |
| return -errno; |
| } |
| |
| if (daemon_pid_file_create() < 0) { |
| teamd_log_err("Could not create PID file."); |
| daemon_retval_send(errno); |
| return -errno; |
| } |
| |
| if (daemon_signal_init(SIGINT, SIGTERM, SIGQUIT, SIGHUP, 0) < 0) { |
| teamd_log_err("Could not register signal handlers."); |
| daemon_retval_send(errno); |
| err = -errno; |
| goto pid_file_remove; |
| } |
| |
| err = teamd_init(ctx); |
| if (err) { |
| teamd_log_err("teamd_init() failed."); |
| daemon_retval_send(-err); |
| goto signal_done; |
| } |
| *p_ret = TEAMD_EXIT_RUNTIME_FAILURE; |
| |
| daemon_retval_send(0); |
| |
| teamd_log_info(PACKAGE_VERSION" successfully started."); |
| |
| err = teamd_run_loop_run(ctx); |
| |
| teamd_log_info("Exiting..."); |
| |
| teamd_fini(ctx); |
| |
| signal_done: |
| daemon_signal_done(); |
| |
| pid_file_remove: |
| daemon_pid_file_remove(); |
| |
| return err; |
| } |
| |
| static int teamd_generate_devname(struct teamd_context *ctx) |
| { |
| char buf[IFNAMSIZ]; |
| int i = 0; |
| uint32_t ifindex = 0; /* gcc needs this initialized */ |
| int ret; |
| int err; |
| |
| do { |
| ret = snprintf(buf, sizeof(buf), |
| TEAMD_DEFAULT_DEVNAME_PREFIX "%d", i++); |
| if (ret >= sizeof(buf)) |
| return -EINVAL; |
| err = ifname2ifindex(&ifindex, buf); |
| if (err) |
| return err; |
| } while (ifindex); |
| teamd_log_dbg(ctx, "Generated team device name \"%s\".", buf); |
| |
| ctx->team_devname = strdup(buf); |
| if (!ctx->team_devname) |
| return -ENOMEM; |
| return 0; |
| } |
| |
| static int teamd_get_devname(struct teamd_context *ctx, bool generate_enabled) |
| { |
| int err; |
| |
| if (!ctx->team_devname) { |
| const char *team_name; |
| |
| err = teamd_config_string_get(ctx, &team_name, "$.device"); |
| if (!err) { |
| ctx->team_devname = strdup(team_name); |
| if (!ctx->team_devname) { |
| teamd_log_err("Failed allocate memory for device name."); |
| return -ENOMEM; |
| } |
| goto skip_set; |
| } else { |
| teamd_log_dbg(ctx, "Failed to get team device name from config."); |
| if (generate_enabled) { |
| err = teamd_generate_devname(ctx); |
| if (err) { |
| teamd_log_err("Failed to generate team device name."); |
| return err; |
| } |
| } else { |
| teamd_log_err("Team device name not specified."); |
| return -EINVAL; |
| } |
| } |
| } |
| err = teamd_config_string_set(ctx, ctx->team_devname, "$.device"); |
| if (err) { |
| teamd_log_err("Failed to set team device name in config."); |
| return err; |
| } |
| |
| skip_set: |
| teamd_log_dbg(ctx, "Using team device \"%s\".", ctx->team_devname); |
| |
| err = asprintf(&ctx->ident, "%s_%s", ctx->argv0, ctx->team_devname); |
| if (err == -1) { |
| teamd_log_err("Failed allocate memory for identification string."); |
| return -ENOMEM; |
| } |
| return 0; |
| } |
| |
| static int teamd_set_default_pid_file(struct teamd_context *ctx) |
| { |
| int err; |
| |
| /* Generate PID filename only if it was not set on command line */ |
| if (ctx->pid_file) |
| return 0; |
| |
| err = asprintf(&ctx->pid_file, TEAMD_RUN_DIR"%s.pid", ctx->team_devname); |
| if (err == -1) { |
| teamd_log_err("Failed allocate memory for PID file string."); |
| return -ENOMEM; |
| } |
| return 0; |
| } |
| |
| static void teamd_init_debug_level(struct teamd_context *ctx) |
| { |
| int err; |
| int tmp; |
| |
| err = teamd_config_int_get(ctx, &tmp, "$.debug_level"); |
| if (err || tmp <= ctx->debug) |
| return; |
| ctx->debug = tmp; |
| daemon_set_verbosity(LOG_DEBUG); |
| } |
| |
| static int teamd_context_init(struct teamd_context **pctx) |
| { |
| struct teamd_context *ctx; |
| |
| ctx = myzalloc(sizeof(*ctx)); |
| if (!ctx) |
| return -ENOMEM; |
| *pctx = ctx; |
| __g_pid_file = &ctx->pid_file; |
| |
| /* Enable usock by default */ |
| ctx->usock.enabled = true; |
| return 0; |
| } |
| |
| static void teamd_context_fini(struct teamd_context *ctx) |
| { |
| free(ctx->ident); |
| free(ctx->team_devname); |
| free(ctx->config_text); |
| free(ctx->config_file); |
| free(ctx->pid_file); |
| free(ctx); |
| } |
| |
| |
| #ifdef HAVE_LIBCAP |
| #include <sys/prctl.h> |
| #include <sys/capability.h> |
| #include <sys/types.h> |
| #include <pwd.h> |
| #include <grp.h> |
| |
| #ifndef TEAMD_USER |
| #define TEAMD_USER "root" |
| #endif |
| #ifndef TEAMD_GROUP |
| #define TEAMD_GROUP "root" |
| #endif |
| |
| static int teamd_drop_privileges() |
| { |
| cap_value_t cv[] = {CAP_NET_ADMIN, CAP_NET_BIND_SERVICE, CAP_NET_RAW}; |
| cap_t my_caps; |
| struct passwd *pw = NULL; |
| struct group *grpent = NULL; |
| |
| if ((pw = getpwnam(TEAMD_USER)) == NULL) { |
| fprintf(stderr, "Error reading user %s entry (%m)\n", TEAMD_USER); |
| goto error; |
| } |
| |
| if (pw->pw_uid == 0) |
| return 0; |
| |
| if ((grpent = getgrnam(TEAMD_GROUP)) == NULL) { |
| fprintf(stderr, "Error reading group %s entry (%m)\n", TEAMD_GROUP); |
| goto error; |
| } |
| |
| if (pw->pw_gid != grpent->gr_gid) { |
| fprintf(stderr, "%s GID (%u) does not match %s GID (%u)\n", |
| TEAMD_USER, pw->pw_gid, TEAMD_GROUP, grpent->gr_gid); |
| goto error; |
| } |
| |
| if (chown(TEAMD_RUN_DIR, pw->pw_uid, pw->pw_gid) < 0) { |
| fprintf(stderr, "Unable to change ownership of %s to %s/%s (%m)\n", |
| TEAMD_RUN_DIR, TEAMD_USER, TEAMD_GROUP); |
| goto error; |
| } |
| |
| if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) |
| goto error; |
| |
| if (setgid(pw->pw_gid) < 0) { |
| fprintf(stderr, "Unable to set process GID to %u (%m)\n", pw->pw_gid); |
| goto error; |
| } |
| |
| if (initgroups(TEAMD_USER, pw->pw_gid) < 0) { |
| fprintf(stderr, "Unable to initialize the group access list for %s user with GID %u (%m)\n", |
| TEAMD_USER, pw->pw_gid); |
| goto error; |
| } |
| if (setuid(pw->pw_uid) < 0) { |
| fprintf(stderr, "Unable to set UID to %u (%m)\n", pw->pw_uid); |
| goto error; |
| } |
| |
| if ((my_caps = cap_init()) == NULL) |
| goto error; |
| if (cap_set_flag(my_caps, CAP_EFFECTIVE, ARRAY_SIZE(cv), cv, CAP_SET) < 0) |
| goto error; |
| if (cap_set_flag(my_caps, CAP_PERMITTED, ARRAY_SIZE(cv), cv, CAP_SET) < 0) |
| goto error; |
| if (cap_set_proc(my_caps) < 0) |
| goto error; |
| cap_free(my_caps); |
| |
| return 0; |
| error: |
| fprintf(stderr, "Failed to drop privileges\n"); |
| return -EINVAL; |
| } |
| |
| #else |
| |
| static int teamd_drop_privileges() |
| { |
| return 0; |
| } |
| |
| #endif |
| |
| int main(int argc, char **argv) |
| { |
| enum teamd_exit_code ret = TEAMD_EXIT_FAILURE; |
| int err; |
| struct teamd_context *ctx; |
| |
| err = teamd_make_rundir(); |
| if (err) |
| return ret; |
| |
| err = teamd_drop_privileges(); |
| if (err) |
| return ret; |
| |
| err = teamd_context_init(&ctx); |
| if (err) { |
| fprintf(stderr, "Failed to init daemon context\n"); |
| return ret; |
| } |
| |
| err = parse_command_line(ctx, argc, argv); |
| if (err) |
| goto context_fini; |
| |
| ctx->argv0 = daemon_ident_from_argv0(argv[0]); |
| |
| switch (ctx->cmd) { |
| case DAEMON_CMD_HELP: |
| print_help(ctx); |
| ret = TEAMD_EXIT_SUCCESS; |
| goto context_fini; |
| case DAEMON_CMD_VERSION: |
| printf("%s "PACKAGE_VERSION"\n", ctx->argv0); |
| ret = TEAMD_EXIT_SUCCESS; |
| goto context_fini; |
| case DAEMON_CMD_KILL: |
| case DAEMON_CMD_CHECK: |
| case DAEMON_CMD_RUN: |
| break; |
| } |
| |
| if (ctx->debug) |
| daemon_set_verbosity(LOG_DEBUG); |
| |
| daemon_log_ident = ctx->argv0; |
| |
| err = teamd_config_load(ctx); |
| if (err) { |
| teamd_log_err("Failed to load config."); |
| goto context_fini; |
| } |
| |
| teamd_init_debug_level(ctx); |
| |
| err = teamd_get_devname(ctx, ctx->cmd == DAEMON_CMD_RUN); |
| if (err) |
| goto config_free; |
| |
| err = teamd_set_default_pid_file(ctx); |
| if (err) |
| goto config_free; |
| |
| daemon_log_ident = ctx->ident; |
| daemon_pid_file_proc = teamd_pid_file_proc; |
| |
| teamd_log_dbg(ctx, "Using PID file \"%s\"", daemon_pid_file_proc()); |
| if (ctx->config_file) |
| teamd_log_dbg(ctx, "Using config file \"%s\"", ctx->config_file); |
| |
| switch (ctx->cmd) { |
| case DAEMON_CMD_HELP: |
| case DAEMON_CMD_VERSION: |
| break; |
| case DAEMON_CMD_KILL: |
| if (daemon_pid_file_is_running() > 0) { |
| err = daemon_pid_file_kill_wait(SIGTERM, 30); |
| if (err) |
| teamd_log_warn("Failed to kill daemon: %s", |
| strerror(errno)); |
| else |
| ret = TEAMD_EXIT_SUCCESS; |
| } else { |
| teamd_log_warn("Daemon not running"); |
| } |
| break; |
| case DAEMON_CMD_CHECK: |
| ret = (daemon_pid_file_is_running() > 0) ? TEAMD_EXIT_SUCCESS : |
| TEAMD_EXIT_FAILURE; |
| break; |
| case DAEMON_CMD_RUN: |
| err = teamd_start(ctx, &ret); |
| if (err) |
| teamd_log_err("Failed: %s", strerror(-err)); |
| else |
| ret = TEAMD_EXIT_SUCCESS; |
| break; |
| } |
| |
| config_free: |
| teamd_config_free(ctx); |
| context_fini: |
| teamd_context_fini(ctx); |
| return ret; |
| } |