// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2013 Jiri Pirko * Copyright (C) 2018 Red Hat, Inc. */ #include "nm-default.h" #include "nm-device-team.h" #include #include #include #include #include #include #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); }