// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2013 Jiri Pirko <jiri@resnulli.us>
* Copyright (C) 2018 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-device-team.h"
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <teamdctl.h>
#include <stdlib.h>
#include "nm-glib-aux/nm-jansson.h"
#include "NetworkManagerUtils.h"
#include "devices/nm-device-private.h"
#include "platform/nm-platform.h"
#include "nm-config.h"
#include "nm-core-internal.h"
#include "nm-dbus-manager.h"
#include "nm-ip4-config.h"
#include "nm-std-aux/nm-dbus-compat.h"
#include "devices/nm-device-logging.h"
_LOG_DECLARE_SELF(NMDeviceTeam);
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceTeam,
PROP_CONFIG,
);
typedef struct {
struct teamdctl *tdc;
char *config;
GPid teamd_pid;
guint teamd_process_watch;
guint teamd_timeout;
guint teamd_read_timeout;
guint teamd_dbus_watch;
bool kill_in_progress:1;
GFileMonitor *usock_monitor;
NMDeviceStageState stage1_state:3;
} NMDeviceTeamPrivate;
struct _NMDeviceTeam {
NMDevice parent;
NMDeviceTeamPrivate _priv;
};
struct _NMDeviceTeamClass {
NMDeviceClass parent;
};
G_DEFINE_TYPE (NMDeviceTeam, nm_device_team, NM_TYPE_DEVICE)
#define NM_DEVICE_TEAM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceTeam, NM_IS_DEVICE_TEAM, NMDevice)
/*****************************************************************************/
static gboolean teamd_start (NMDeviceTeam *self);
/*****************************************************************************/
static NMDeviceCapabilities
get_generic_capabilities (NMDevice *device)
{
return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE;
}
static gboolean
complete_connection (NMDevice *device,
NMConnection *connection,
const char *specific_object,
NMConnection *const*existing_connections,
GError **error)
{
NMSettingTeam *s_team;
nm_utils_complete_generic (nm_device_get_platform (device),
connection,
NM_SETTING_TEAM_SETTING_NAME,
existing_connections,
NULL,
_("Team connection"),
"team",
NULL,
TRUE);
s_team = nm_connection_get_setting_team (connection);
if (!s_team) {
s_team = (NMSettingTeam *) nm_setting_team_new ();
nm_connection_add_setting (connection, NM_SETTING (s_team));
}
return TRUE;
}
static gboolean
ensure_teamd_connection (NMDevice *device)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (device);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
int err;
if (priv->tdc)
return TRUE;
priv->tdc = teamdctl_alloc ();
g_assert (priv->tdc);
err = teamdctl_connect (priv->tdc, nm_device_get_iface (device), NULL, NULL);
if (err != 0) {
_LOGE (LOGD_TEAM, "failed to connect to teamd (err=%d)", err);
teamdctl_free (priv->tdc);
priv->tdc = NULL;
}
return !!priv->tdc;
}
static const char *
_get_config (NMDeviceTeam *self)
{
return nm_str_not_empty (NM_DEVICE_TEAM_GET_PRIVATE (self)->config);
}
static gboolean
teamd_read_config (NMDeviceTeam *self)
{
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
const char *config = NULL;
int err;
if (priv->tdc) {
err = teamdctl_config_actual_get_raw_direct (priv->tdc, (char **) &config);
if (err)
return FALSE;
if (!config) {
/* set "" to distinguish an empty result from no config at all. */
config = "";
}
}
if (!nm_streq0 (config, priv->config)) {
g_free (priv->config);
priv->config = g_strdup (config);
_notify (self, PROP_CONFIG);
}
return TRUE;
}
static gboolean
teamd_read_timeout_cb (gpointer user_data)
{
NMDeviceTeam *self = user_data;
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
priv->teamd_read_timeout = 0;
teamd_read_config (self);
return G_SOURCE_REMOVE;
}
static void
update_connection (NMDevice *device, NMConnection *connection)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (device);
NMSettingTeam *s_team = nm_connection_get_setting_team (connection);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
struct teamdctl *tdc = priv->tdc;
if (!s_team) {
s_team = (NMSettingTeam *) nm_setting_team_new ();
nm_connection_add_setting (connection, (NMSetting *) s_team);
}
/* Read the configuration only if not already set */
if ( !priv->config
&& ensure_teamd_connection (device))
teamd_read_config (self);
/* Restore previous tdc state */
if (priv->tdc && !tdc) {
teamdctl_disconnect (priv->tdc);
teamdctl_free (priv->tdc);
priv->tdc = NULL;
}
g_object_set (G_OBJECT (s_team), NM_SETTING_TEAM_CONFIG, _get_config (self), NULL);
}
/*****************************************************************************/
static gboolean
master_update_slave_connection (NMDevice *self,
NMDevice *slave,
NMConnection *connection,
GError **error)
{
NMSettingTeamPort *s_port;
char *port_config = NULL;
int err = 0;
struct teamdctl *tdc;
const char *team_port_config = NULL;
const char *iface = nm_device_get_iface (self);
const char *iface_slave = nm_device_get_iface (slave);
tdc = teamdctl_alloc ();
if (!tdc) {
g_set_error (error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"update slave connection for slave '%s' failed to connect to teamd for master %s (out of memory?)",
iface_slave, iface);
g_return_val_if_reached (FALSE);
}
err = teamdctl_connect (tdc, iface, NULL, NULL);
if (err) {
teamdctl_free (tdc);
g_set_error (error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"update slave connection for slave '%s' failed to connect to teamd for master %s (err=%d)",
iface_slave, iface, err);
return FALSE;
}
err = teamdctl_port_config_get_raw_direct (tdc, iface_slave, (char **)&team_port_config);
port_config = g_strdup (team_port_config);
teamdctl_disconnect (tdc);
teamdctl_free (tdc);
if (err) {
g_set_error (error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"update slave connection for slave '%s' failed to get configuration from teamd master %s (err=%d)",
iface_slave, iface, err);
g_free (port_config);
return FALSE;
}
s_port = nm_connection_get_setting_team_port (connection);
if (!s_port) {
s_port = (NMSettingTeamPort *) nm_setting_team_port_new ();
nm_connection_add_setting (connection, NM_SETTING (s_port));
}
g_object_set (G_OBJECT (s_port), NM_SETTING_TEAM_PORT_CONFIG, port_config, NULL);
g_free (port_config);
g_object_set (nm_connection_get_setting_connection (connection),
NM_SETTING_CONNECTION_MASTER, iface,
NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_TEAM_SETTING_NAME,
NULL);
return TRUE;
}
/*****************************************************************************/
static void
teamd_kill_cb (pid_t pid, gboolean success, int child_status, void *user_data)
{
gs_unref_object NMDeviceTeam *self = user_data;
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
priv->kill_in_progress = FALSE;
if (nm_device_get_state (NM_DEVICE (self)) != NM_DEVICE_STATE_PREPARE) {
_LOGT (LOGD_TEAM, "kill terminated");
return;
}
_LOGT (LOGD_TEAM, "kill terminated, starting teamd...");
if (!teamd_start (self)) {
nm_device_state_changed (NM_DEVICE (self),
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
}
}
static void
teamd_cleanup (NMDeviceTeam *self, gboolean free_tdc)
{
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
nm_clear_g_source (&priv->teamd_process_watch);
nm_clear_g_source (&priv->teamd_timeout);
nm_clear_g_source (&priv->teamd_read_timeout);
if (priv->teamd_pid > 0) {
priv->kill_in_progress = TRUE;
nm_utils_kill_child_async (priv->teamd_pid,
SIGTERM,
LOGD_TEAM,
"teamd",
2000,
teamd_kill_cb,
g_object_ref (self));
priv->teamd_pid = 0;
}
if ( priv->tdc
&& free_tdc) {
teamdctl_disconnect (priv->tdc);
teamdctl_free (priv->tdc);
priv->tdc = NULL;
}
}
static gboolean
teamd_timeout_cb (gpointer user_data)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (user_data);
NMDevice *device = NM_DEVICE (self);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
g_return_val_if_fail (priv->teamd_timeout, FALSE);
priv->teamd_timeout = 0;
if (priv->teamd_pid && !priv->tdc) {
/* Timed out launching our own teamd process */
_LOGW (LOGD_TEAM, "teamd timed out");
teamd_cleanup (self, TRUE);
g_warn_if_fail (nm_device_is_activating (device));
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
} else {
/* Read again the configuration after the timeout since it might
* have changed.
*/
if (!teamd_read_config (self)) {
_LOGW (LOGD_TEAM, "failed to read teamd configuration");
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
}
}
return G_SOURCE_REMOVE;
}
static void
teamd_ready (NMDeviceTeam *self)
{
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
NMDevice *device = NM_DEVICE (self);
gboolean success;
if (priv->kill_in_progress) {
/* If we are currently killing teamd, we are not
* interested in knowing when it becomes ready. */
return;
}
nm_device_queue_recheck_assume (device);
/* Grab a teamd control handle even if we aren't going to use it
* immediately. But if we are, and grabbing it failed, fail the
* device activation.
*/
success = ensure_teamd_connection (device);
if ( nm_device_get_state (device) != NM_DEVICE_STATE_PREPARE
|| priv->stage1_state != NM_DEVICE_STAGE_STATE_PENDING)
return;
if (success)
success = teamd_read_config (self);
if (!success) {
teamd_cleanup (self, TRUE);
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
return;
}
priv->stage1_state = NM_DEVICE_STAGE_STATE_COMPLETED;
nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
}
static void
teamd_gone (NMDeviceTeam *self)
{
NMDevice *device = NM_DEVICE (self);
NMDeviceState state;
teamd_cleanup (self, TRUE);
state = nm_device_get_state (device);
/* Attempt to respawn teamd */
if ( state >= NM_DEVICE_STATE_PREPARE
&& state <= NM_DEVICE_STATE_ACTIVATED) {
if (!teamd_start (self)) {
nm_device_state_changed (device,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
}
}
}
static void
teamd_dbus_appeared (GDBusConnection *connection,
const char *name,
const char *name_owner,
gpointer user_data)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (user_data);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
g_return_if_fail (priv->teamd_dbus_watch);
_LOGI (LOGD_TEAM, "teamd appeared on D-Bus");
/* If another teamd grabbed the bus name while our teamd was starting,
* just ignore the death of our teamd and run with the existing one.
*/
if (priv->teamd_process_watch) {
gs_unref_variant GVariant *ret = NULL;
guint32 pid;
ret = g_dbus_connection_call_sync (connection,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
"GetConnectionUnixProcessID",
g_variant_new ("(s)", name_owner),
NULL,
G_DBUS_CALL_FLAGS_NO_AUTO_START,
2000,
NULL,
NULL);
if (ret) {
g_variant_get (ret, "(u)", &pid);
if (pid != priv->teamd_pid)
teamd_cleanup (self, FALSE);
} else {
/* The process that registered on the bus died. If it's
* the teamd instance we just started, ignore the event
* as we already detect the failure through the process
* watch. If it's a previous instance that got killed,
* also ignore that as our new instance will register
* again. */
_LOGD (LOGD_TEAM, "failed to determine D-Bus name owner, ignoring");
return;
}
}
teamd_ready (self);
}
static void
teamd_dbus_vanished (GDBusConnection *dbus_connection,
const char *name,
gpointer user_data)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (user_data);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
g_return_if_fail (priv->teamd_dbus_watch);
if (!priv->tdc) {
/* g_bus_watch_name will always raise an initial signal, to indicate whether the
* name exists/not exists initially. Do not take this as a failure if it hadn't
* previously appeared.
*/
_LOGD (LOGD_TEAM, "teamd not on D-Bus (ignored)");
return;
}
_LOGI (LOGD_TEAM, "teamd vanished from D-Bus");
teamd_gone (self);
}
static void
monitor_changed_cb (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type,
gpointer user_data)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (user_data);
switch (event_type) {
case G_FILE_MONITOR_EVENT_CREATED:
_LOGI (LOGD_TEAM, "file %s was created", g_file_get_path (file));
teamd_ready (self);
break;
case G_FILE_MONITOR_EVENT_DELETED:
_LOGI (LOGD_TEAM, "file %s was deleted", g_file_get_path (file));
teamd_gone (self);
break;
default:
;
}
}
static void
teamd_process_watch_cb (GPid pid, int status, gpointer user_data)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (user_data);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
NMDevice *device = NM_DEVICE (self);
NMDeviceState state = nm_device_get_state (device);
g_return_if_fail (priv->teamd_process_watch);
_LOGD (LOGD_TEAM, "teamd %lld died with status %d", (long long) pid, status);
priv->teamd_pid = 0;
priv->teamd_process_watch = 0;
/* If teamd quit within 5 seconds of starting, it's probably hosed
* and will just die again, so fail the activation.
*/
if (priv->teamd_timeout &&
(state >= NM_DEVICE_STATE_PREPARE) &&
(state <= NM_DEVICE_STATE_ACTIVATED)) {
_LOGW (LOGD_TEAM, "teamd process %lld quit unexpectedly; failing activation", (long long) pid);
teamd_cleanup (self, TRUE);
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
}
}
static void
teamd_child_setup (gpointer user_data)
{
nm_utils_setpgid (NULL);
signal (SIGPIPE, SIG_IGN);
}
static const char **
teamd_env (void)
{
const char **env = g_new0 (const char *, 2);
if (nm_config_get_is_debug (nm_config_get ()))
env[0] = "TEAM_LOG_OUTPUT=stderr";
else
env[0] = "TEAM_LOG_OUTPUT=syslog";
return env;
}
static gboolean
teamd_kill (NMDeviceTeam *self, const char *teamd_binary, GError **error)
{
gs_unref_ptrarray GPtrArray *argv = NULL;
gs_free char *tmp_str = NULL;
gs_free const char **envp = NULL;
if (!teamd_binary) {
teamd_binary = nm_utils_find_helper ("teamd", NULL, error);
if (!teamd_binary) {
_LOGW (LOGD_TEAM, "Activation: (team) failed to start teamd: teamd binary not found");
return FALSE;
}
}
argv = g_ptr_array_new ();
g_ptr_array_add (argv, (gpointer) teamd_binary);
g_ptr_array_add (argv, (gpointer) "-k");
g_ptr_array_add (argv, (gpointer) "-t");
g_ptr_array_add (argv, (gpointer) nm_device_get_iface (NM_DEVICE (self)));
g_ptr_array_add (argv, NULL);
envp = teamd_env ();
_LOGD (LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv (" ", (char **) argv->pdata)));
return g_spawn_sync ("/", (char **) argv->pdata, (char **) envp, 0,
teamd_child_setup, NULL, NULL, NULL, NULL, error);
}
static gboolean
teamd_start (NMDeviceTeam *self)
{
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
const char *iface = nm_device_get_ip_iface (NM_DEVICE (self));
NMConnection *connection;
gs_unref_ptrarray GPtrArray *argv = NULL;
gs_free_error GError *error = NULL;
gs_free char *tmp_str = NULL;
const char *teamd_binary;
const char *config;
nm_auto_free const char *config_free = NULL;
NMSettingTeam *s_team;
gs_free char *cloned_mac = NULL;
gs_free const char **envp = NULL;
connection = nm_device_get_applied_connection (NM_DEVICE (self));
s_team = nm_connection_get_setting_team (connection);
if (!s_team)
g_return_val_if_reached (FALSE);
nm_assert (iface);
teamd_binary = nm_utils_find_helper ("teamd", NULL, NULL);
if (!teamd_binary) {
_LOGW (LOGD_TEAM, "Activation: (team) failed to start teamd: teamd binary not found");
return FALSE;
}
if (priv->teamd_process_watch || priv->teamd_pid > 0 || priv->tdc) {
g_warn_if_reached ();
if (!priv->teamd_pid)
teamd_kill (self, teamd_binary, NULL);
teamd_cleanup (self, TRUE);
}
/* Start teamd now */
argv = g_ptr_array_new ();
g_ptr_array_add (argv, (gpointer) teamd_binary);
g_ptr_array_add (argv, (gpointer) "-o");
g_ptr_array_add (argv, (gpointer) "-n");
g_ptr_array_add (argv, (gpointer) "-U");
if (priv->teamd_dbus_watch)
g_ptr_array_add (argv, (gpointer) "-D");
g_ptr_array_add (argv, (gpointer) "-N");
g_ptr_array_add (argv, (gpointer) "-t");
g_ptr_array_add (argv, (gpointer) iface);
config = nm_setting_team_get_config (s_team);
if (!nm_device_hw_addr_get_cloned (NM_DEVICE (self), connection, FALSE, &cloned_mac, NULL, &error)) {
_LOGW (LOGD_DEVICE, "set-hw-addr: %s", error->message);
return FALSE;
}
if (cloned_mac) {
json_t *json, *hwaddr;
json_error_t jerror;
/* Inject the hwaddr property into the JSON configuration.
* While doing so, detect potential conflicts */
json = json_loads (config ?: "{}", JSON_REJECT_DUPLICATES, &jerror);
g_return_val_if_fail (json, FALSE);
hwaddr = json_object_get (json, "hwaddr");
if (hwaddr) {
if ( !json_is_string (hwaddr)
|| !nm_streq0 (json_string_value (hwaddr), cloned_mac))
_LOGW (LOGD_TEAM, "set-hw-addr: can't set team cloned-mac-address as the JSON configuration already contains \"hwaddr\"");
} else {
hwaddr = json_string (cloned_mac);
json_object_set (json, "hwaddr", hwaddr);
config = config_free = json_dumps (json, JSON_INDENT(0) |
JSON_ENSURE_ASCII |
JSON_SORT_KEYS);
_LOGD (LOGD_TEAM, "set-hw-addr: injected \"hwaddr\" \"%s\" into team configuration", cloned_mac);
json_decref (hwaddr);
}
json_decref (json);
}
if (config) {
g_ptr_array_add (argv, (gpointer) "-c");
g_ptr_array_add (argv, (gpointer) config);
}
if (nm_logging_enabled (LOGL_DEBUG, LOGD_TEAM))
g_ptr_array_add (argv, (gpointer) "-gg");
g_ptr_array_add (argv, NULL);
envp = teamd_env ();
_LOGD (LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv (" ", (char **) argv->pdata)));
if (!g_spawn_async ("/", (char **) argv->pdata, (char **) envp, G_SPAWN_DO_NOT_REAP_CHILD,
teamd_child_setup, NULL, &priv->teamd_pid, &error)) {
_LOGW (LOGD_TEAM, "Activation: (team) failed to start teamd: %s", error->message);
teamd_cleanup (self, TRUE);
return FALSE;
}
/* Start a timeout for teamd to appear at D-Bus */
if (!priv->teamd_timeout)
priv->teamd_timeout = g_timeout_add_seconds (5, teamd_timeout_cb, self);
/* Monitor the child process so we know when it dies */
priv->teamd_process_watch = g_child_watch_add (priv->teamd_pid,
teamd_process_watch_cb,
self);
_LOGI (LOGD_TEAM, "Activation: (team) started teamd [pid %u]...", (guint) priv->teamd_pid);
return TRUE;
}
static NMActStageReturn
act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (device);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
gs_free_error GError *error = NULL;
NMSettingTeam *s_team;
const char *cfg;
if (nm_device_sys_iface_state_is_external (device))
return NM_ACT_STAGE_RETURN_SUCCESS;
if (nm_device_sys_iface_state_is_external_or_assume (device)) {
if (ensure_teamd_connection (device))
return NM_ACT_STAGE_RETURN_SUCCESS;
}
s_team = nm_device_get_applied_setting (device, NM_TYPE_SETTING_TEAM);
if (!s_team)
g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
if (priv->stage1_state == NM_DEVICE_STAGE_STATE_PENDING)
return NM_ACT_STAGE_RETURN_POSTPONE;
if (priv->stage1_state == NM_DEVICE_STAGE_STATE_COMPLETED)
return NM_ACT_STAGE_RETURN_SUCCESS;
priv->stage1_state = NM_DEVICE_STAGE_STATE_PENDING;
if (priv->tdc) {
/* If the existing teamd config is the same as we're about to use,
* then we can proceed. If it's not the same, and we have a PID,
* kill it so we can respawn it with the right config. If we don't
* have a PID, then we must fail.
*/
cfg = teamdctl_config_get_raw (priv->tdc);
if ( cfg
&& nm_streq0 (cfg, nm_setting_team_get_config (s_team))) {
_LOGD (LOGD_TEAM, "using existing matching teamd config");
return NM_ACT_STAGE_RETURN_SUCCESS;
}
if (!priv->teamd_pid) {
_LOGD (LOGD_TEAM, "existing teamd config mismatch; killing existing via teamdctl");
if (!teamd_kill (self, NULL, &error)) {
_LOGW (LOGD_TEAM, "existing teamd config mismatch; failed to kill existing teamd: %s", error->message);
NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
return NM_ACT_STAGE_RETURN_FAILURE;
}
}
_LOGD (LOGD_TEAM, "existing teamd config mismatch; respawning...");
teamd_cleanup (self, TRUE);
}
if (priv->kill_in_progress) {
_LOGT (LOGD_TEAM, "kill in progress, wait before starting teamd");
return NM_ACT_STAGE_RETURN_POSTPONE;
}
if (!teamd_start (self))
return NM_ACT_STAGE_RETURN_FAILURE;
return NM_ACT_STAGE_RETURN_POSTPONE;
}
static void
deactivate (NMDevice *device)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (device);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT;
if (nm_device_sys_iface_state_is_external (device))
return;
if ( priv->teamd_pid
|| priv->tdc)
_LOGI (LOGD_TEAM, "deactivation: stopping teamd...");
if (!priv->teamd_pid)
teamd_kill (self, NULL, NULL);
teamd_cleanup (self, TRUE);
}
static gboolean
enslave_slave (NMDevice *device,
NMDevice *slave,
NMConnection *connection,
gboolean configure)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (device);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
gboolean success = TRUE;
const char *slave_iface = nm_device_get_ip_iface (slave);
NMSettingTeamPort *s_team_port;
nm_device_master_check_slave_physical_port (device, slave, LOGD_TEAM);
if (configure) {
nm_device_take_down (slave, TRUE);
s_team_port = nm_connection_get_setting_team_port (connection);
if (s_team_port) {
const char *config = nm_setting_team_port_get_config (s_team_port);
if (config) {
if (!priv->tdc) {
_LOGW (LOGD_TEAM, "enslaved team port %s config not changed, not connected to teamd",
slave_iface);
} else {
int err;
char *sanitized_config;
sanitized_config = g_strdelimit (g_strdup (config), "\r\n", ' ');
err = teamdctl_port_config_update_raw (priv->tdc, slave_iface, sanitized_config);
g_free (sanitized_config);
if (err != 0) {
_LOGE (LOGD_TEAM, "failed to update config for port %s (err=%d)",
slave_iface, err);
return FALSE;
}
}
}
}
success = nm_platform_link_enslave (nm_device_get_platform (device),
nm_device_get_ip_ifindex (device),
nm_device_get_ip_ifindex (slave));
nm_device_bring_up (slave, TRUE, NULL);
if (!success)
return FALSE;
nm_clear_g_source (&priv->teamd_read_timeout);
priv->teamd_read_timeout = g_timeout_add_seconds (5,
teamd_read_timeout_cb,
self);
_LOGI (LOGD_TEAM, "enslaved team port %s", slave_iface);
} else
_LOGI (LOGD_TEAM, "team port %s was enslaved", slave_iface);
return TRUE;
}
static void
release_slave (NMDevice *device,
NMDevice *slave,
gboolean configure)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (device);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
gboolean do_release, success;
NMSettingTeamPort *s_port;
int ifindex_slave;
int ifindex;
do_release = configure;
if (do_release) {
ifindex = nm_device_get_ifindex (device);
if ( ifindex <= 0
|| !nm_platform_link_get (nm_device_get_platform (device), ifindex))
do_release = FALSE;
}
ifindex_slave = nm_device_get_ip_ifindex (slave);
if (ifindex_slave <= 0) {
_LOGD (LOGD_TEAM, "team port %s is already released", nm_device_get_ip_iface (slave));
} else if (do_release) {
success = nm_platform_link_release (nm_device_get_platform (device),
nm_device_get_ip_ifindex (device),
ifindex_slave);
if (success)
_LOGI (LOGD_TEAM, "released team port %s", nm_device_get_ip_iface (slave));
else
_LOGW (LOGD_TEAM, "failed to release team port %s", nm_device_get_ip_iface (slave));
/* Kernel team code "closes" the port when releasing it, (which clears
* IFF_UP), so we must bring it back up here to ensure carrier changes and
* other state is noticed by the now-released port.
*/
if (!nm_device_bring_up (slave, TRUE, NULL)) {
_LOGW (LOGD_TEAM, "released team port %s could not be brought up",
nm_device_get_ip_iface (slave));
}
nm_clear_g_source (&priv->teamd_read_timeout);
priv->teamd_read_timeout = g_timeout_add_seconds (5,
teamd_read_timeout_cb,
self);
} else
_LOGI (LOGD_TEAM, "team port %s was released", nm_device_get_ip_iface (slave));
/* Delete any port configuration we previously set */
if ( configure
&& priv->tdc
&& (s_port = nm_device_get_applied_setting (slave, NM_TYPE_SETTING_TEAM_PORT))
&& (nm_setting_team_port_get_config (s_port)))
teamdctl_port_config_update_raw (priv->tdc, nm_device_get_ip_iface (slave), "{}");
}
static gboolean
create_and_realize (NMDevice *device,
NMConnection *connection,
NMDevice *parent,
const NMPlatformLink **out_plink,
GError **error)
{
const char *iface = nm_device_get_iface (device);
int r;
r = nm_platform_link_team_add (nm_device_get_platform (device), iface, out_plink);
if (r < 0) {
g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
"Failed to create team master interface '%s' for '%s': %s",
iface,
nm_connection_get_id (connection),
nm_strerror (r));
return FALSE;
}
return TRUE;
}
/*****************************************************************************/
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (object);
switch (prop_id) {
case PROP_CONFIG:
g_value_set_string (value, _get_config (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/*****************************************************************************/
static void
nm_device_team_init (NMDeviceTeam * self)
{
nm_assert (nm_device_is_master (NM_DEVICE (self)));
}
static void
constructed (GObject *object)
{
NMDevice *device = NM_DEVICE (object);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (device);
gs_free char *tmp_str = NULL;
gs_unref_object GFile *file = NULL;
GError *error;
G_OBJECT_CLASS (nm_device_team_parent_class)->constructed (object);
if (nm_dbus_manager_get_dbus_connection (nm_dbus_manager_get ())) {
/* Register D-Bus name watcher */
tmp_str = g_strdup_printf ("org.libteam.teamd.%s", nm_device_get_ip_iface (device));
priv->teamd_dbus_watch = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
tmp_str,
G_BUS_NAME_WATCHER_FLAGS_NONE,
teamd_dbus_appeared,
teamd_dbus_vanished,
NM_DEVICE (device),
NULL);
return;
}
/* No D-Bus, watch unix socket */
tmp_str = g_strdup_printf ("/run/teamd/%s.sock",
nm_device_get_ip_iface (device));
file = g_file_new_for_path (tmp_str);
priv->usock_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error);
if (!priv->usock_monitor) {
nm_log_warn (LOGD_TEAM, "error monitoring %s: %s", tmp_str, error->message);
} else {
g_signal_connect (priv->usock_monitor,
"changed",
G_CALLBACK (monitor_changed_cb),
object);
}
}
NMDevice *
nm_device_team_new (const char *iface)
{
return (NMDevice *) g_object_new (NM_TYPE_DEVICE_TEAM,
NM_DEVICE_IFACE, iface,
NM_DEVICE_DRIVER, "team",
NM_DEVICE_TYPE_DESC, "Team",
NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_TEAM,
NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_TEAM,
NULL);
}
static void
dispose (GObject *object)
{
NMDeviceTeam *self = NM_DEVICE_TEAM (object);
NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
if (priv->teamd_dbus_watch) {
g_bus_unwatch_name (priv->teamd_dbus_watch);
priv->teamd_dbus_watch = 0;
}
if (priv->usock_monitor) {
g_signal_handlers_disconnect_by_data (priv->usock_monitor, object);
g_clear_object (&priv->usock_monitor);
}
teamd_cleanup (self, TRUE);
nm_clear_g_free (&priv->config);
G_OBJECT_CLASS (nm_device_team_parent_class)->dispose (object);
}
static const NMDBusInterfaceInfoExtended interface_info_device_team = {
.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
NM_DBUS_INTERFACE_DEVICE_TEAM,
.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
&nm_signal_info_property_changed_legacy,
),
.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Carrier", "b", NM_DEVICE_CARRIER),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Slaves", "ao", NM_DEVICE_SLAVES),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Config", "s", NM_DEVICE_TEAM_CONFIG),
),
),
.legacy_property_changed = TRUE,
};
static void
nm_device_team_class_init (NMDeviceTeamClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
object_class->constructed = constructed;
object_class->dispose = dispose;
object_class->get_property = get_property;
dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_team);
device_class->connection_type_supported = NM_SETTING_TEAM_SETTING_NAME;
device_class->connection_type_check_compatible = NM_SETTING_TEAM_SETTING_NAME;
device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_TEAM);
device_class->is_master = TRUE;
device_class->create_and_realize = create_and_realize;
device_class->get_generic_capabilities = get_generic_capabilities;
device_class->complete_connection = complete_connection;
device_class->update_connection = update_connection;
device_class->master_update_slave_connection = master_update_slave_connection;
device_class->act_stage1_prepare_also_for_external_or_assume = TRUE;
device_class->act_stage1_prepare = act_stage1_prepare;
device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
device_class->deactivate = deactivate;
device_class->enslave_slave = enslave_slave;
device_class->release_slave = release_slave;
obj_properties[PROP_CONFIG] =
g_param_spec_string (NM_DEVICE_TEAM_CONFIG, "", "",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
}