mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-30 19:00:11 +01:00
When disconnecting a master device, propagate its NMDeviceStateReason to the slaves. That way, if the reason is USER_REQUESTED, then the slaves will be blocked from re-autoconnecting as well.
7315 lines
219 KiB
C
7315 lines
219 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
|
|
/* NetworkManager -- Network link manager
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Copyright (C) 2005 - 2013 Red Hat, Inc.
|
|
* Copyright (C) 2006 - 2008 Novell, Inc.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
#include <dbus/dbus.h>
|
|
#include <netinet/in.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <linux/sockios.h>
|
|
#include <linux/ethtool.h>
|
|
#include <sys/ioctl.h>
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <arpa/inet.h>
|
|
#include <fcntl.h>
|
|
#include <linux/if.h>
|
|
|
|
#include "libgsystem.h"
|
|
#include "nm-glib-compat.h"
|
|
#include "nm-device.h"
|
|
#include "nm-device-private.h"
|
|
#include "NetworkManagerUtils.h"
|
|
#include "nm-manager.h"
|
|
#include "nm-platform.h"
|
|
#include "nm-rdisc.h"
|
|
#include "nm-lndp-rdisc.h"
|
|
#include "nm-dhcp-manager.h"
|
|
#include "nm-dbus-manager.h"
|
|
#include "nm-utils.h"
|
|
#include "nm-logging.h"
|
|
#include "nm-setting-ip4-config.h"
|
|
#include "nm-setting-ip6-config.h"
|
|
#include "nm-setting-connection.h"
|
|
#include "nm-dnsmasq-manager.h"
|
|
#include "nm-dhcp4-config.h"
|
|
#include "nm-rfkill-manager.h"
|
|
#include "nm-firewall-manager.h"
|
|
#include "nm-properties-changed-signal.h"
|
|
#include "nm-enum-types.h"
|
|
#include "nm-settings-connection.h"
|
|
#include "nm-connection-provider.h"
|
|
#include "nm-posix-signals.h"
|
|
#include "nm-manager-auth.h"
|
|
#include "nm-dbus-glib-types.h"
|
|
#include "nm-dispatcher.h"
|
|
#include "nm-config-device.h"
|
|
#include "nm-config.h"
|
|
#include "nm-platform.h"
|
|
#include "nm-dns-manager.h"
|
|
|
|
#include "nm-device-bridge.h"
|
|
#include "nm-device-bond.h"
|
|
#include "nm-device-team.h"
|
|
|
|
static void impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context);
|
|
|
|
#include "nm-device-glue.h"
|
|
|
|
#define DBUS_G_TYPE_UINT_STRUCT (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INVALID))
|
|
|
|
/* default to installed helper, but can be modified for testing */
|
|
const char *nm_device_autoipd_helper_path = LIBEXECDIR "/nm-avahi-autoipd.action";
|
|
|
|
/***********************************************************/
|
|
#define NM_DEVICE_ERROR (nm_device_error_quark ())
|
|
|
|
static GQuark
|
|
nm_device_error_quark (void)
|
|
{
|
|
static GQuark quark = 0;
|
|
if (!quark)
|
|
quark = g_quark_from_static_string ("nm-device-error");
|
|
return quark;
|
|
}
|
|
|
|
/***********************************************************/
|
|
|
|
enum {
|
|
STATE_CHANGED,
|
|
AUTOCONNECT_ALLOWED,
|
|
AUTH_REQUEST,
|
|
IP4_CONFIG_CHANGED,
|
|
IP6_CONFIG_CHANGED,
|
|
LAST_SIGNAL,
|
|
};
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_PLATFORM_DEVICE,
|
|
PROP_UDI,
|
|
PROP_IFACE,
|
|
PROP_IP_IFACE,
|
|
PROP_DRIVER,
|
|
PROP_DRIVER_VERSION,
|
|
PROP_FIRMWARE_VERSION,
|
|
PROP_CAPABILITIES,
|
|
PROP_CARRIER,
|
|
PROP_MTU,
|
|
PROP_IP4_ADDRESS,
|
|
PROP_IP4_CONFIG,
|
|
PROP_DHCP4_CONFIG,
|
|
PROP_IP6_CONFIG,
|
|
PROP_DHCP6_CONFIG,
|
|
PROP_STATE,
|
|
PROP_STATE_REASON,
|
|
PROP_ACTIVE_CONNECTION,
|
|
PROP_DEVICE_TYPE,
|
|
PROP_MANAGED,
|
|
PROP_AUTOCONNECT,
|
|
PROP_FIRMWARE_MISSING,
|
|
PROP_TYPE_DESC,
|
|
PROP_RFKILL_TYPE,
|
|
PROP_IFINDEX,
|
|
PROP_AVAILABLE_CONNECTIONS,
|
|
PROP_PHYSICAL_PORT_ID,
|
|
PROP_IS_MASTER,
|
|
PROP_MASTER,
|
|
PROP_HW_ADDRESS,
|
|
PROP_HAS_PENDING_ACTION,
|
|
LAST_PROP
|
|
};
|
|
|
|
#define DEFAULT_AUTOCONNECT TRUE
|
|
|
|
/***********************************************************/
|
|
|
|
static void nm_device_config_device_interface_init (NMConfigDeviceInterface *iface);
|
|
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (NMDevice, nm_device, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (NM_TYPE_CONFIG_DEVICE, nm_device_config_device_interface_init))
|
|
|
|
#define NM_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE, NMDevicePrivate))
|
|
|
|
typedef enum {
|
|
IP_NONE = 0,
|
|
IP_WAIT,
|
|
IP_CONF,
|
|
IP_DONE,
|
|
IP_FAIL
|
|
} IpState;
|
|
|
|
typedef struct {
|
|
NMDeviceState state;
|
|
NMDeviceStateReason reason;
|
|
guint id;
|
|
} QueuedState;
|
|
|
|
typedef struct {
|
|
NMDevice *slave;
|
|
gboolean enslaved;
|
|
gboolean configure;
|
|
guint watch_id;
|
|
} SlaveInfo;
|
|
|
|
typedef struct {
|
|
guint log_domain;
|
|
guint timeout;
|
|
guint watch;
|
|
GPid pid;
|
|
} PingInfo;
|
|
|
|
typedef struct {
|
|
gboolean disposed;
|
|
gboolean initialized;
|
|
gboolean in_state_changed;
|
|
|
|
NMDeviceState state;
|
|
NMDeviceStateReason state_reason;
|
|
QueuedState queued_state;
|
|
guint queued_ip_config_id;
|
|
GSList *pending_actions;
|
|
|
|
char * udi;
|
|
char * path;
|
|
char * iface; /* may change, could be renamed by user */
|
|
int ifindex;
|
|
gboolean is_software;
|
|
char * ip_iface;
|
|
int ip_ifindex;
|
|
NMDeviceType type;
|
|
char * type_desc;
|
|
guint32 capabilities;
|
|
char * driver;
|
|
char * driver_version;
|
|
char * firmware_version;
|
|
RfKillType rfkill_type;
|
|
gboolean firmware_missing;
|
|
GHashTable * available_connections;
|
|
guint8 hw_addr[NM_UTILS_HWADDR_LEN_MAX];
|
|
guint hw_addr_len;
|
|
char * physical_port_id;
|
|
|
|
gboolean manager_managed; /* whether managed by NMManager or not */
|
|
gboolean default_unmanaged; /* whether unmanaged by default */
|
|
gboolean is_nm_owned; /* whether the device is a device owned and created by NM */
|
|
guint delete_on_deactivate_id; /* event id for scheduled link_delete action (g_idle_add) */
|
|
|
|
guint32 ip4_address;
|
|
|
|
NMActRequest * act_request;
|
|
guint act_source_id;
|
|
gpointer act_source_func;
|
|
guint act_source6_id;
|
|
gpointer act_source6_func;
|
|
|
|
/* Link stuff */
|
|
guint link_connected_id;
|
|
guint link_disconnected_id;
|
|
guint carrier_defer_id;
|
|
gboolean carrier;
|
|
guint carrier_wait_id;
|
|
gboolean ignore_carrier;
|
|
guint32 mtu;
|
|
|
|
/* Generic DHCP stuff */
|
|
NMDHCPManager * dhcp_manager;
|
|
guint32 dhcp_timeout;
|
|
GByteArray * dhcp_anycast_address;
|
|
|
|
/* IP4 configuration info */
|
|
NMIP4Config * ip4_config; /* Combined config from VPN, settings, and device */
|
|
IpState ip4_state;
|
|
NMIP4Config * dev_ip4_config; /* Config from DHCP, PPP, LLv4, etc */
|
|
NMIP4Config * ext_ip4_config; /* Stuff added outside NM */
|
|
|
|
/* DHCPv4 tracking */
|
|
NMDHCPClient * dhcp4_client;
|
|
gulong dhcp4_state_sigid;
|
|
gulong dhcp4_timeout_sigid;
|
|
NMDHCP4Config * dhcp4_config;
|
|
NMIP4Config * vpn4_config; /* routes added by a VPN which uses this device */
|
|
|
|
PingInfo gw_ping;
|
|
|
|
/* dnsmasq stuff for shared connections */
|
|
NMDnsMasqManager *dnsmasq_manager;
|
|
gulong dnsmasq_state_id;
|
|
|
|
/* Firewall Manager */
|
|
NMFirewallManager *fw_manager;
|
|
DBusGProxyCall *fw_call;
|
|
|
|
/* avahi-autoipd stuff */
|
|
GPid aipd_pid;
|
|
guint aipd_watch;
|
|
guint aipd_timeout;
|
|
|
|
/* IP6 configuration info */
|
|
NMIP6Config * ip6_config;
|
|
IpState ip6_state;
|
|
NMIP6Config * vpn6_config; /* routes added by a VPN which uses this device */
|
|
NMIP6Config * ext_ip6_config; /* Stuff added outside NM */
|
|
|
|
NMRDisc * rdisc;
|
|
gulong rdisc_config_changed_sigid;
|
|
/* IP6 config from autoconf */
|
|
NMIP6Config * ac_ip6_config;
|
|
|
|
guint linklocal6_timeout_id;
|
|
|
|
char * ip6_disable_ipv6_path;
|
|
gint32 ip6_disable_ipv6_save;
|
|
|
|
char * ip6_accept_ra_path;
|
|
gint32 ip6_accept_ra_save;
|
|
|
|
/* IPv6 privacy extensions (RFC4941) */
|
|
char * ip6_use_tempaddr_path;
|
|
gint32 ip6_use_tempaddr_save;
|
|
|
|
NMDHCPClient * dhcp6_client;
|
|
NMRDiscDHCPLevel dhcp6_mode;
|
|
gulong dhcp6_state_sigid;
|
|
gulong dhcp6_timeout_sigid;
|
|
NMDHCP6Config * dhcp6_config;
|
|
/* IP6 config from DHCP */
|
|
NMIP6Config * dhcp6_ip6_config;
|
|
|
|
/* allow autoconnect feature */
|
|
gboolean autoconnect;
|
|
|
|
/* master interface for bridge/bond/team slave */
|
|
NMDevice * master;
|
|
gboolean enslaved;
|
|
guint master_ready_id;
|
|
|
|
/* slave management */
|
|
gboolean is_master;
|
|
GSList * slaves; /* list of SlaveInfo */
|
|
|
|
NMConnectionProvider *con_provider;
|
|
|
|
/* connection provider signals for available connections property */
|
|
guint cp_added_id;
|
|
guint cp_loaded_id;
|
|
guint cp_removed_id;
|
|
guint cp_updated_id;
|
|
|
|
} NMDevicePrivate;
|
|
|
|
static gboolean nm_device_set_ip4_config (NMDevice *dev,
|
|
NMIP4Config *config,
|
|
gboolean commit,
|
|
NMDeviceStateReason *reason);
|
|
static gboolean ip4_config_merge_and_apply (NMDevice *self,
|
|
NMIP4Config *config,
|
|
gboolean commit,
|
|
NMDeviceStateReason *out_reason);
|
|
|
|
static gboolean nm_device_set_ip6_config (NMDevice *dev,
|
|
NMIP6Config *config,
|
|
gboolean commit,
|
|
NMDeviceStateReason *reason);
|
|
|
|
static gboolean nm_device_activate_ip6_config_commit (gpointer user_data);
|
|
|
|
static gboolean check_connection_available (NMDevice *device,
|
|
NMConnection *connection,
|
|
const char *specific_object);
|
|
|
|
static gboolean spec_match_list (NMDevice *device, const GSList *specs);
|
|
|
|
static void _clear_available_connections (NMDevice *device, gboolean do_signal);
|
|
|
|
static void dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release);
|
|
|
|
static const char *reason_to_string (NMDeviceStateReason reason);
|
|
|
|
static void ip_check_gw_ping_cleanup (NMDevice *self);
|
|
|
|
static void cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data);
|
|
static void cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data);
|
|
static void cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data);
|
|
|
|
static const char *state_to_string (NMDeviceState state);
|
|
|
|
static void link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *info, NMPlatformReason reason, NMDevice *device);
|
|
static void check_carrier (NMDevice *device);
|
|
|
|
static void nm_device_queued_ip_config_change_clear (NMDevice *self);
|
|
static void update_ip_config (NMDevice *self, gboolean initial);
|
|
static void device_ip_changed (NMPlatform *platform, int ifindex, gpointer platform_object, NMPlatformReason reason, gpointer user_data);
|
|
|
|
static void nm_device_slave_notify_enslave (NMDevice *dev, gboolean success);
|
|
static void nm_device_slave_notify_release (NMDevice *dev, NMDeviceStateReason reason);
|
|
|
|
static void addrconf6_start_with_link_ready (NMDevice *self);
|
|
|
|
static const char const *platform_ip_signals[] = {
|
|
NM_PLATFORM_IP4_ADDRESS_ADDED,
|
|
NM_PLATFORM_IP4_ADDRESS_CHANGED,
|
|
NM_PLATFORM_IP4_ADDRESS_REMOVED,
|
|
NM_PLATFORM_IP4_ROUTE_ADDED,
|
|
NM_PLATFORM_IP4_ROUTE_CHANGED,
|
|
NM_PLATFORM_IP4_ROUTE_REMOVED,
|
|
NM_PLATFORM_IP6_ADDRESS_ADDED,
|
|
NM_PLATFORM_IP6_ADDRESS_CHANGED,
|
|
NM_PLATFORM_IP6_ADDRESS_REMOVED,
|
|
NM_PLATFORM_IP6_ROUTE_ADDED,
|
|
NM_PLATFORM_IP6_ROUTE_CHANGED,
|
|
NM_PLATFORM_IP6_ROUTE_REMOVED,
|
|
};
|
|
static const int n_platform_ip_signals = G_N_ELEMENTS (platform_ip_signals);
|
|
|
|
static void
|
|
nm_device_init (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
priv->type = NM_DEVICE_TYPE_UNKNOWN;
|
|
priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED;
|
|
priv->state = NM_DEVICE_STATE_UNMANAGED;
|
|
priv->state_reason = NM_DEVICE_STATE_REASON_NONE;
|
|
priv->dhcp_timeout = 0;
|
|
priv->rfkill_type = RFKILL_TYPE_UNKNOWN;
|
|
priv->autoconnect = DEFAULT_AUTOCONNECT;
|
|
priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
|
|
}
|
|
|
|
static void
|
|
update_ip6_property_paths (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
const char *ip_iface;
|
|
char *new_path;
|
|
|
|
ip_iface = nm_device_get_ip_iface (self);
|
|
|
|
new_path = g_strdup_printf ("/proc/sys/net/ipv6/conf/%s/accept_ra", ip_iface);
|
|
if (priv->ip6_accept_ra_path) {
|
|
/* If ip_iface hasn't changed, then there's nothing to do */
|
|
if (!strcmp (new_path, priv->ip6_accept_ra_path)) {
|
|
g_free (new_path);
|
|
return;
|
|
}
|
|
|
|
/* If ip_iface did change, then any values we saved before are irrelevant. */
|
|
priv->ip6_accept_ra_save = -1;
|
|
priv->ip6_use_tempaddr_save = -1;
|
|
priv->ip6_disable_ipv6_save = -1;
|
|
|
|
g_free (priv->ip6_accept_ra_path);
|
|
g_free (priv->ip6_use_tempaddr_path);
|
|
g_free (priv->ip6_disable_ipv6_path);
|
|
}
|
|
|
|
priv->ip6_accept_ra_path = new_path;
|
|
|
|
new_path = g_strdup_printf ("/proc/sys/net/ipv6/conf/%s/use_tempaddr", ip_iface);
|
|
priv->ip6_use_tempaddr_path = new_path;
|
|
|
|
new_path = g_strdup_printf ("/proc/sys/net/ipv6/conf/%s/disable_ipv6", ip_iface);
|
|
priv->ip6_disable_ipv6_path = new_path;
|
|
}
|
|
|
|
static void
|
|
save_ip6_properties (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
priv->ip6_accept_ra_save = nm_platform_sysctl_get_int32 (priv->ip6_accept_ra_path, -1);
|
|
if (priv->ip6_accept_ra_save > 2 || priv->ip6_accept_ra_save < -1)
|
|
priv->ip6_accept_ra_save = -1;
|
|
|
|
priv->ip6_use_tempaddr_save = nm_platform_sysctl_get_int32 (priv->ip6_use_tempaddr_path, -1);
|
|
if (priv->ip6_use_tempaddr_save > 2 || priv->ip6_use_tempaddr_save < -1)
|
|
priv->ip6_use_tempaddr_save = -1;
|
|
|
|
priv->ip6_disable_ipv6_save = nm_platform_sysctl_get_int32 (priv->ip6_disable_ipv6_path, -1);
|
|
if (priv->ip6_disable_ipv6_save > 1 || priv->ip6_disable_ipv6_save < -1)
|
|
priv->ip6_disable_ipv6_save = -1;
|
|
}
|
|
|
|
static void
|
|
restore_ip6_properties (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
char tmp[16];
|
|
|
|
if ( priv->ip6_accept_ra_save != -1
|
|
&& g_file_test (priv->ip6_accept_ra_path, G_FILE_TEST_EXISTS)) {
|
|
snprintf (tmp, sizeof (tmp), "%d", priv->ip6_accept_ra_save);
|
|
nm_platform_sysctl_set (priv->ip6_accept_ra_path, tmp);
|
|
}
|
|
|
|
if ( priv->ip6_use_tempaddr_save != -1
|
|
&& g_file_test (priv->ip6_use_tempaddr_path, G_FILE_TEST_EXISTS)) {
|
|
snprintf (tmp, sizeof (tmp), "%d", priv->ip6_use_tempaddr_save);
|
|
nm_platform_sysctl_set (priv->ip6_use_tempaddr_path, tmp);
|
|
}
|
|
|
|
if ( priv->ip6_disable_ipv6_save != -1
|
|
&& g_file_test (priv->ip6_disable_ipv6_path, G_FILE_TEST_EXISTS)) {
|
|
nm_platform_sysctl_set (priv->ip6_disable_ipv6_path,
|
|
priv->ip6_disable_ipv6_save ? "1" : "0");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get driver info from SIOCETHTOOL ioctl() for 'iface'
|
|
* Returns driver and firmware versions to 'driver_version and' 'firmware_version'
|
|
*/
|
|
static gboolean
|
|
device_get_driver_info (const char *iface, char **driver_version, char **firmware_version)
|
|
{
|
|
struct ethtool_drvinfo drvinfo;
|
|
struct ifreq req;
|
|
int fd;
|
|
|
|
fd = socket (PF_INET, SOCK_DGRAM, 0);
|
|
if (fd < 0) {
|
|
nm_log_warn (LOGD_HW, "couldn't open control socket.");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Get driver and firmware version info */
|
|
memset (&drvinfo, 0, sizeof (drvinfo));
|
|
memset (&req, 0, sizeof (struct ifreq));
|
|
strncpy (req.ifr_name, iface, IFNAMSIZ);
|
|
drvinfo.cmd = ETHTOOL_GDRVINFO;
|
|
req.ifr_data = &drvinfo;
|
|
|
|
errno = 0;
|
|
if (ioctl (fd, SIOCETHTOOL, &req) < 0) {
|
|
nm_log_dbg (LOGD_HW, "SIOCETHTOOL ioctl() failed: cmd=ETHTOOL_GDRVINFO, iface=%s, errno=%d",
|
|
iface, errno);
|
|
close (fd);
|
|
return FALSE;
|
|
}
|
|
if (driver_version)
|
|
*driver_version = g_strdup (drvinfo.version);
|
|
if (firmware_version)
|
|
*firmware_version = g_strdup (drvinfo.fw_version);
|
|
|
|
close (fd);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
device_has_capability (NMDevice *device, NMDeviceCapabilities caps)
|
|
{
|
|
return !!(NM_DEVICE_GET_PRIVATE (device)->capabilities & caps);
|
|
}
|
|
|
|
static GObject*
|
|
constructor (GType type,
|
|
guint n_construct_params,
|
|
GObjectConstructParam *construct_params)
|
|
{
|
|
GObject *object;
|
|
NMDevice *dev;
|
|
NMDevicePrivate *priv;
|
|
NMPlatform *platform;
|
|
int i;
|
|
static guint32 id = 0;
|
|
|
|
object = G_OBJECT_CLASS (nm_device_parent_class)->constructor (type,
|
|
n_construct_params,
|
|
construct_params);
|
|
if (!object)
|
|
return NULL;
|
|
|
|
dev = NM_DEVICE (object);
|
|
priv = NM_DEVICE_GET_PRIVATE (dev);
|
|
|
|
if (!priv->iface) {
|
|
nm_log_err (LOGD_DEVICE, "No device interface provided, ignoring");
|
|
goto error;
|
|
}
|
|
|
|
if (!priv->udi) {
|
|
/* Use a placeholder UDI until we get a real one */
|
|
priv->udi = g_strdup_printf ("/virtual/device/placeholder/%d", id++);
|
|
}
|
|
|
|
if (NM_DEVICE_GET_CLASS (dev)->get_generic_capabilities)
|
|
priv->capabilities |= NM_DEVICE_GET_CLASS (dev)->get_generic_capabilities (dev);
|
|
|
|
priv->dhcp_manager = nm_dhcp_manager_get ();
|
|
|
|
priv->fw_manager = nm_firewall_manager_get ();
|
|
|
|
device_get_driver_info (priv->iface, &priv->driver_version, &priv->firmware_version);
|
|
|
|
update_ip6_property_paths (dev);
|
|
|
|
/* Watch for external IP config changes */
|
|
platform = nm_platform_get ();
|
|
for (i = 0; i < n_platform_ip_signals; i++) {
|
|
g_signal_connect (platform, platform_ip_signals[i],
|
|
G_CALLBACK (device_ip_changed), dev);
|
|
}
|
|
|
|
g_signal_connect (platform, NM_PLATFORM_LINK_CHANGED,
|
|
G_CALLBACK (link_changed_cb), dev);
|
|
|
|
priv->initialized = TRUE;
|
|
return object;
|
|
|
|
error:
|
|
g_object_unref (dev);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
constructed (GObject *object)
|
|
{
|
|
NMDevice *dev = NM_DEVICE (object);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
|
|
|
|
nm_device_update_hw_address (dev);
|
|
|
|
if (NM_DEVICE_GET_CLASS (dev)->update_permanent_hw_address)
|
|
NM_DEVICE_GET_CLASS (dev)->update_permanent_hw_address (dev);
|
|
|
|
if (NM_DEVICE_GET_CLASS (dev)->update_initial_hw_address)
|
|
NM_DEVICE_GET_CLASS (dev)->update_initial_hw_address (dev);
|
|
|
|
/* Have to call update_initial_hw_address() before calling get_ignore_carrier() */
|
|
if (device_has_capability (dev, NM_DEVICE_CAP_CARRIER_DETECT)) {
|
|
priv->ignore_carrier = nm_config_get_ignore_carrier (nm_config_get (), NM_CONFIG_DEVICE (dev));
|
|
|
|
check_carrier (dev);
|
|
nm_log_info (LOGD_HW,
|
|
"(%s): carrier is %s%s",
|
|
nm_device_get_iface (NM_DEVICE (dev)),
|
|
priv->carrier ? "ON" : "OFF",
|
|
priv->ignore_carrier ? " (but ignored)" : "");
|
|
} else {
|
|
/* Fake online link when carrier detection is not available. */
|
|
priv->carrier = TRUE;
|
|
}
|
|
|
|
if (priv->ifindex > 0) {
|
|
priv->is_software = nm_platform_link_is_software (priv->ifindex);
|
|
priv->physical_port_id = nm_platform_link_get_physical_port_id (priv->ifindex);
|
|
}
|
|
|
|
if (priv->ifindex > 0)
|
|
priv->mtu = nm_platform_link_get_mtu (priv->ifindex);
|
|
|
|
if (G_OBJECT_CLASS (nm_device_parent_class)->constructed)
|
|
G_OBJECT_CLASS (nm_device_parent_class)->constructed (object);
|
|
}
|
|
|
|
static gboolean
|
|
nm_device_is_up (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
|
|
|
|
if (NM_DEVICE_GET_CLASS (self)->is_up)
|
|
return NM_DEVICE_GET_CLASS (self)->is_up (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
is_up (NMDevice *device)
|
|
{
|
|
int ifindex = nm_device_get_ip_ifindex (device);
|
|
|
|
return ifindex ? nm_platform_link_is_up (ifindex) : TRUE;
|
|
}
|
|
|
|
void
|
|
nm_device_set_path (NMDevice *self, const char *path)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
g_return_if_fail (priv->path == NULL);
|
|
|
|
priv->path = g_strdup (path);
|
|
}
|
|
|
|
const char *
|
|
nm_device_get_path (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->path;
|
|
}
|
|
|
|
const char *
|
|
nm_device_get_udi (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->udi;
|
|
}
|
|
|
|
/*
|
|
* Get/set functions for iface
|
|
*/
|
|
const char *
|
|
nm_device_get_iface (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->iface;
|
|
}
|
|
|
|
int
|
|
nm_device_get_ifindex (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, 0);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->ifindex;
|
|
}
|
|
|
|
gboolean
|
|
nm_device_is_software (NMDevice *device)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
return priv->is_software;
|
|
}
|
|
|
|
const char *
|
|
nm_device_get_ip_iface (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
/* If it's not set, default to iface */
|
|
return priv->ip_iface ? priv->ip_iface : priv->iface;
|
|
}
|
|
|
|
int
|
|
nm_device_get_ip_ifindex (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_val_if_fail (self != NULL, 0);
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
/* If it's not set, default to iface */
|
|
return priv->ip_iface ? priv->ip_ifindex : priv->ifindex;
|
|
}
|
|
|
|
void
|
|
nm_device_set_ip_iface (NMDevice *self, const char *iface)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
char *old_ip_iface;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
old_ip_iface = priv->ip_iface;
|
|
priv->ip_ifindex = 0;
|
|
|
|
priv->ip_iface = g_strdup (iface);
|
|
if (priv->ip_iface) {
|
|
priv->ip_ifindex = nm_platform_link_get_ifindex (priv->ip_iface);
|
|
if (priv->ip_ifindex > 0) {
|
|
if (!nm_platform_link_is_up (priv->ip_ifindex))
|
|
nm_platform_link_set_up (priv->ip_ifindex);
|
|
} else {
|
|
/* Device IP interface must always be a kernel network interface */
|
|
nm_log_warn (LOGD_HW, "(%s): failed to look up interface index", iface);
|
|
}
|
|
}
|
|
|
|
update_ip6_property_paths (self);
|
|
|
|
/* Emit change notification */
|
|
if (g_strcmp0 (old_ip_iface, priv->ip_iface))
|
|
g_object_notify (G_OBJECT (self), NM_DEVICE_IP_IFACE);
|
|
g_free (old_ip_iface);
|
|
}
|
|
|
|
static guint
|
|
get_hw_address_length (NMDevice *dev, gboolean *out_permanent)
|
|
{
|
|
size_t len;
|
|
|
|
if (nm_platform_link_get_address (nm_device_get_ip_ifindex (dev), &len))
|
|
return len;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static guint
|
|
nm_device_get_hw_address_length (NMDevice *dev, gboolean *out_permanent)
|
|
{
|
|
return NM_DEVICE_GET_CLASS (dev)->get_hw_address_length (dev, out_permanent);
|
|
}
|
|
|
|
const guint8 *
|
|
nm_device_get_hw_address (NMDevice *dev, guint *out_len)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_val_if_fail (NM_IS_DEVICE (dev), NULL);
|
|
priv = NM_DEVICE_GET_PRIVATE (dev);
|
|
|
|
if (out_len)
|
|
*out_len = priv->hw_addr_len;
|
|
|
|
if (priv->hw_addr_len == 0)
|
|
return NULL;
|
|
else
|
|
return priv->hw_addr;
|
|
}
|
|
|
|
/*
|
|
* Get/set functions for driver
|
|
*/
|
|
const char *
|
|
nm_device_get_driver (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->driver;
|
|
}
|
|
|
|
const char *
|
|
nm_device_get_driver_version (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->driver_version;
|
|
}
|
|
|
|
const char *
|
|
nm_device_get_firmware_version (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->firmware_version;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get/set functions for type
|
|
*/
|
|
NMDeviceType
|
|
nm_device_get_device_type (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_TYPE_UNKNOWN);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->type;
|
|
}
|
|
|
|
|
|
/**
|
|
* nm_device_get_priority():
|
|
* @dev: the #NMDevice
|
|
*
|
|
* Returns: the device's routing priority. Lower numbers means a "better"
|
|
* device, eg higher priority.
|
|
*/
|
|
int
|
|
nm_device_get_priority (NMDevice *dev)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (dev), 100);
|
|
|
|
/* Device 'priority' is used for two things:
|
|
*
|
|
* a) two devices on the same IP subnet: the "better" (ie, lower number)
|
|
* device is the default outgoing device for that subnet
|
|
* b) default route: the "better" device gets the default route. This can
|
|
* always be modified by setting a connection to never-default=TRUE, in
|
|
* which case that device will never take the default route when
|
|
* it's using that connection.
|
|
*/
|
|
|
|
switch (nm_device_get_device_type (dev)) {
|
|
case NM_DEVICE_TYPE_ETHERNET:
|
|
return 1;
|
|
case NM_DEVICE_TYPE_INFINIBAND:
|
|
return 2;
|
|
case NM_DEVICE_TYPE_ADSL:
|
|
return 3;
|
|
case NM_DEVICE_TYPE_WIMAX:
|
|
return 4;
|
|
case NM_DEVICE_TYPE_BOND:
|
|
return 5;
|
|
case NM_DEVICE_TYPE_TEAM:
|
|
return 6;
|
|
case NM_DEVICE_TYPE_VLAN:
|
|
return 7;
|
|
case NM_DEVICE_TYPE_MODEM:
|
|
return 8;
|
|
case NM_DEVICE_TYPE_BT:
|
|
return 9;
|
|
case NM_DEVICE_TYPE_WIFI:
|
|
return 10;
|
|
case NM_DEVICE_TYPE_OLPC_MESH:
|
|
return 11;
|
|
default:
|
|
return 20;
|
|
}
|
|
}
|
|
|
|
const char *
|
|
nm_device_get_type_desc (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->type_desc;
|
|
}
|
|
|
|
void
|
|
nm_device_set_connection_provider (NMDevice *device,
|
|
NMConnectionProvider *provider)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_if_fail (device != NULL);
|
|
g_return_if_fail (NM_IS_CONNECTION_PROVIDER (provider));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (device);
|
|
g_return_if_fail (priv->con_provider == NULL);
|
|
|
|
priv->con_provider = provider;
|
|
priv->cp_added_id = g_signal_connect (priv->con_provider,
|
|
NM_CP_SIGNAL_CONNECTION_ADDED,
|
|
G_CALLBACK (cp_connection_added),
|
|
device);
|
|
|
|
priv->cp_removed_id = g_signal_connect (priv->con_provider,
|
|
NM_CP_SIGNAL_CONNECTION_REMOVED,
|
|
G_CALLBACK (cp_connection_removed),
|
|
device);
|
|
|
|
priv->cp_updated_id = g_signal_connect (priv->con_provider,
|
|
NM_CP_SIGNAL_CONNECTION_UPDATED,
|
|
G_CALLBACK (cp_connection_updated),
|
|
device);
|
|
}
|
|
|
|
NMConnectionProvider *
|
|
nm_device_get_connection_provider (NMDevice *device)
|
|
{
|
|
g_return_val_if_fail (device != NULL, NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (device)->con_provider;
|
|
}
|
|
|
|
static SlaveInfo *
|
|
find_slave_info (NMDevice *self, NMDevice *slave)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
SlaveInfo *info;
|
|
GSList *iter;
|
|
|
|
for (iter = priv->slaves; iter; iter = g_slist_next (iter)) {
|
|
info = iter->data;
|
|
if (info->slave == slave)
|
|
return info;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
free_slave_info (SlaveInfo *info)
|
|
{
|
|
g_signal_handler_disconnect (info->slave, info->watch_id);
|
|
g_clear_object (&info->slave);
|
|
memset (info, 0, sizeof (*info));
|
|
g_free (info);
|
|
}
|
|
|
|
/**
|
|
* nm_device_enslave_slave:
|
|
* @dev: the master device
|
|
* @slave: the slave device to enslave
|
|
* @connection: the slave device's connection
|
|
*
|
|
* If @dev is capable of enslaving other devices (ie it's a bridge, bond, team,
|
|
* etc) then this function enslaves @slave.
|
|
*
|
|
* Returns: %TRUE on success, %FALSE on failure or if this device cannot enslave
|
|
* other devices.
|
|
*/
|
|
static gboolean
|
|
nm_device_enslave_slave (NMDevice *dev, NMDevice *slave, NMConnection *connection)
|
|
{
|
|
SlaveInfo *info;
|
|
gboolean success = FALSE;
|
|
|
|
g_return_val_if_fail (dev != NULL, FALSE);
|
|
g_return_val_if_fail (slave != NULL, FALSE);
|
|
g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
|
|
g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->enslave_slave != NULL, FALSE);
|
|
|
|
info = find_slave_info (dev, slave);
|
|
if (!info)
|
|
return FALSE;
|
|
|
|
g_warn_if_fail (info->enslaved == FALSE);
|
|
success = NM_DEVICE_GET_CLASS (dev)->enslave_slave (dev, slave, connection, info->configure);
|
|
|
|
info->enslaved = success;
|
|
nm_device_slave_notify_enslave (info->slave, success);
|
|
|
|
/* Ensure the device's hardware address is up-to-date; it often changes
|
|
* when slaves change.
|
|
*/
|
|
nm_device_update_hw_address (dev);
|
|
|
|
/* Restart IP configuration if we're waiting for slaves. Do this
|
|
* after updating the hardware address as IP config may need the
|
|
* new address.
|
|
*/
|
|
if (success) {
|
|
if (NM_DEVICE_GET_PRIVATE (dev)->ip4_state == IP_WAIT)
|
|
nm_device_activate_stage3_ip4_start (dev);
|
|
|
|
if (NM_DEVICE_GET_PRIVATE (dev)->ip6_state == IP_WAIT)
|
|
nm_device_activate_stage3_ip6_start (dev);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* nm_device_release_one_slave:
|
|
* @dev: the master device
|
|
* @slave: the slave device to release
|
|
*
|
|
* If @dev is capable of enslaving other devices (ie it's a bridge, bond, team,
|
|
* etc) then this function releases the previously enslaved @slave.
|
|
*
|
|
* Returns: %TRUE on success, %FALSE on failure, if this device cannot enslave
|
|
* other devices, or if @slave was never enslaved.
|
|
*/
|
|
static gboolean
|
|
nm_device_release_one_slave (NMDevice *dev, NMDevice *slave)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
|
|
SlaveInfo *info;
|
|
gboolean success = FALSE;
|
|
NMDeviceStateReason reason;
|
|
|
|
g_return_val_if_fail (slave != NULL, FALSE);
|
|
g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->release_slave != NULL, FALSE);
|
|
|
|
info = find_slave_info (dev, slave);
|
|
if (!info)
|
|
return FALSE;
|
|
|
|
if (info->enslaved) {
|
|
success = NM_DEVICE_GET_CLASS (dev)->release_slave (dev, slave);
|
|
g_warn_if_fail (success);
|
|
}
|
|
|
|
if (priv->state == NM_DEVICE_STATE_FAILED)
|
|
reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
|
|
else
|
|
reason = priv->state_reason;
|
|
nm_device_slave_notify_release (info->slave, reason);
|
|
|
|
priv->slaves = g_slist_remove (priv->slaves, info);
|
|
free_slave_info (info);
|
|
|
|
/* Ensure the device's hardware address is up-to-date; it often changes
|
|
* when slaves change.
|
|
*/
|
|
nm_device_update_hw_address (dev);
|
|
|
|
return success;
|
|
}
|
|
|
|
static void
|
|
carrier_changed (NMDevice *device, gboolean carrier)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
if (!nm_device_get_managed (device))
|
|
return;
|
|
|
|
nm_device_recheck_available_connections (device);
|
|
|
|
if (priv->ignore_carrier) {
|
|
/* Ignore all carrier-off, and ignore carrier-on on connected devices */
|
|
if (!carrier || priv->state > NM_DEVICE_STATE_DISCONNECTED)
|
|
return;
|
|
}
|
|
|
|
if (nm_device_is_master (device)) {
|
|
/* Bridge/bond/team carrier does not affect its own activation,
|
|
* but when carrier comes on, if there are slaves waiting,
|
|
* it will restart them.
|
|
*/
|
|
if (!carrier)
|
|
return;
|
|
|
|
if (nm_device_activate_ip4_state_in_wait (device))
|
|
nm_device_activate_stage3_ip4_start (device);
|
|
if (nm_device_activate_ip6_state_in_wait (device))
|
|
nm_device_activate_stage3_ip6_start (device);
|
|
|
|
return;
|
|
} else if (nm_device_get_enslaved (device) && !carrier) {
|
|
/* Slaves don't deactivate when they lose carrier; for
|
|
* bonds/teams in particular that would be actively
|
|
* counterproductive.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (carrier) {
|
|
g_warn_if_fail (priv->state >= NM_DEVICE_STATE_UNAVAILABLE);
|
|
|
|
if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) {
|
|
nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED,
|
|
NM_DEVICE_STATE_REASON_CARRIER);
|
|
}
|
|
} else {
|
|
g_return_if_fail (priv->state >= NM_DEVICE_STATE_UNAVAILABLE);
|
|
|
|
if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) {
|
|
if (nm_device_queued_state_peek (device) >= NM_DEVICE_STATE_DISCONNECTED)
|
|
nm_device_queued_state_clear (device);
|
|
} else {
|
|
nm_device_queue_state (device, NM_DEVICE_STATE_UNAVAILABLE,
|
|
NM_DEVICE_STATE_REASON_CARRIER);
|
|
}
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
nm_device_has_carrier (NMDevice *device)
|
|
{
|
|
return NM_DEVICE_GET_PRIVATE (device)->carrier;
|
|
}
|
|
|
|
/* Returns %TRUE if @device is unavailable for connections because it
|
|
* needs carrier but does not have it.
|
|
*/
|
|
static gboolean
|
|
nm_device_is_unavailable_because_of_carrier (NMDevice *device)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
return !priv->carrier && !priv->ignore_carrier;
|
|
}
|
|
|
|
#define LINK_DISCONNECT_DELAY 4
|
|
|
|
static gboolean
|
|
link_disconnect_action_cb (gpointer user_data)
|
|
{
|
|
NMDevice *device = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
priv->carrier_defer_id = 0;
|
|
|
|
nm_log_info (LOGD_DEVICE, "(%s): link disconnected (calling deferred action)",
|
|
nm_device_get_iface (device));
|
|
|
|
NM_DEVICE_GET_CLASS (device)->carrier_changed (device, FALSE);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
nm_device_set_carrier (NMDevice *device, gboolean carrier)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device);
|
|
NMDeviceState state = nm_device_get_state (device);
|
|
const char *iface = nm_device_get_iface (device);
|
|
|
|
if (priv->carrier == carrier)
|
|
return;
|
|
|
|
priv->carrier = carrier;
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_CARRIER);
|
|
|
|
if (priv->carrier) {
|
|
nm_log_info (LOGD_DEVICE, "(%s): link connected", iface);
|
|
if (priv->carrier_defer_id) {
|
|
g_source_remove (priv->carrier_defer_id);
|
|
priv->carrier_defer_id = 0;
|
|
}
|
|
klass->carrier_changed (device, TRUE);
|
|
|
|
if (priv->carrier_wait_id) {
|
|
g_source_remove (priv->carrier_wait_id);
|
|
priv->carrier_wait_id = 0;
|
|
nm_device_remove_pending_action (device, "carrier wait");
|
|
}
|
|
} else if (state <= NM_DEVICE_STATE_DISCONNECTED) {
|
|
nm_log_info (LOGD_DEVICE, "(%s): link disconnected", iface);
|
|
klass->carrier_changed (device, FALSE);
|
|
} else {
|
|
nm_log_info (LOGD_DEVICE, "(%s): link disconnected (deferring action for %d seconds)",
|
|
iface, LINK_DISCONNECT_DELAY);
|
|
priv->carrier_defer_id = g_timeout_add_seconds (LINK_DISCONNECT_DELAY,
|
|
link_disconnect_action_cb, device);
|
|
}
|
|
}
|
|
|
|
static void
|
|
link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *info, NMPlatformReason reason, NMDevice *device)
|
|
{
|
|
NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
/* Ignore other devices. */
|
|
if (ifindex != nm_device_get_ifindex (device))
|
|
return;
|
|
|
|
/* We don't filter by 'reason' because we are interested in *all* link
|
|
* changes. For example a call to nm_platform_link_set_up() may result
|
|
* in an internal carrier change (i.e. we ask the kernel to set IFF_UP
|
|
* and it results in also setting IFF_LOWER_UP.
|
|
*/
|
|
|
|
if (info->udi && g_strcmp0 (info->udi, priv->udi)) {
|
|
/* Update UDI to what udev gives us */
|
|
g_free (priv->udi);
|
|
priv->udi = g_strdup (info->udi);
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_UDI);
|
|
}
|
|
|
|
/* Update MTU if it has changed. */
|
|
if (priv->mtu != info->mtu) {
|
|
priv->mtu = info->mtu;
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_MTU);
|
|
}
|
|
|
|
if (klass->link_changed)
|
|
klass->link_changed (device, info);
|
|
}
|
|
|
|
static void
|
|
link_changed (NMDevice *device, NMPlatformLink *info)
|
|
{
|
|
/* Update carrier from link event if applicable. */
|
|
if ( device_has_capability (device, NM_DEVICE_CAP_CARRIER_DETECT)
|
|
&& !device_has_capability (device, NM_DEVICE_CAP_NONSTANDARD_CARRIER))
|
|
nm_device_set_carrier (device, info->connected);
|
|
}
|
|
|
|
static void
|
|
check_carrier (NMDevice *device)
|
|
{
|
|
int ifindex = nm_device_get_ip_ifindex (device);
|
|
|
|
if (!device_has_capability (device, NM_DEVICE_CAP_NONSTANDARD_CARRIER))
|
|
nm_device_set_carrier (device, nm_platform_link_is_connected (ifindex));
|
|
}
|
|
|
|
static void
|
|
slave_state_changed (NMDevice *slave,
|
|
NMDeviceState slave_new_state,
|
|
NMDeviceState slave_old_state,
|
|
NMDeviceStateReason reason,
|
|
NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
gboolean release = FALSE;
|
|
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): slave %s state change %d (%s) -> %d (%s)",
|
|
nm_device_get_iface (self),
|
|
nm_device_get_iface (slave),
|
|
slave_old_state,
|
|
state_to_string (slave_old_state),
|
|
slave_new_state,
|
|
state_to_string (slave_new_state));
|
|
|
|
g_assert (priv->state > NM_DEVICE_STATE_DISCONNECTED);
|
|
|
|
/* Don't try to enslave slaves until the master is ready */
|
|
if (priv->state < NM_DEVICE_STATE_CONFIG)
|
|
return;
|
|
|
|
if (slave_new_state == NM_DEVICE_STATE_IP_CONFIG)
|
|
nm_device_enslave_slave (self, slave, nm_device_get_connection (slave));
|
|
else if (slave_new_state > NM_DEVICE_STATE_ACTIVATED)
|
|
release = TRUE;
|
|
else if ( slave_new_state <= NM_DEVICE_STATE_DISCONNECTED
|
|
&& slave_old_state > NM_DEVICE_STATE_DISCONNECTED) {
|
|
/* Catch failures due to unavailable or unmanaged */
|
|
release = TRUE;
|
|
}
|
|
|
|
if (release) {
|
|
nm_device_release_one_slave (self, slave);
|
|
/* Bridge/bond/team interfaces are left up until manually deactivated */
|
|
if (priv->slaves == NULL && priv->state == NM_DEVICE_STATE_ACTIVATED) {
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): last slave removed; remaining activated",
|
|
nm_device_get_iface (self));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* nm_device_master_add_slave:
|
|
* @dev: the master device
|
|
* @slave: the slave device to enslave
|
|
* @configure: pass %TRUE if the slave should be configured by the master, or
|
|
* %FALSE if it is already configured outside NetworkManager
|
|
*
|
|
* If @dev is capable of enslaving other devices (ie it's a bridge, bond, team,
|
|
* etc) then this function adds @slave to the slave list for later enslavement.
|
|
*
|
|
* Returns: %TRUE on success, %FALSE on failure
|
|
*/
|
|
gboolean
|
|
nm_device_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
|
|
SlaveInfo *info;
|
|
|
|
g_return_val_if_fail (dev != NULL, FALSE);
|
|
g_return_val_if_fail (slave != NULL, FALSE);
|
|
g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
|
|
g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->enslave_slave != NULL, FALSE);
|
|
|
|
if (!find_slave_info (dev, slave)) {
|
|
info = g_malloc0 (sizeof (SlaveInfo));
|
|
info->slave = g_object_ref (slave);
|
|
info->configure = configure;
|
|
info->watch_id = g_signal_connect (slave, "state-changed",
|
|
G_CALLBACK (slave_state_changed), dev);
|
|
priv->slaves = g_slist_prepend (priv->slaves, info);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* nm_device_master_get_slaves:
|
|
* @dev: the master device
|
|
*
|
|
* Returns: any slaves of which @device is the master. Caller owns returned list.
|
|
*/
|
|
GSList *
|
|
nm_device_master_get_slaves (NMDevice *dev)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
|
|
GSList *slaves = NULL, *iter;
|
|
|
|
for (iter = priv->slaves; iter; iter = g_slist_next (iter))
|
|
slaves = g_slist_prepend (slaves, ((SlaveInfo *) iter->data)->slave);
|
|
|
|
return slaves;
|
|
}
|
|
|
|
/**
|
|
* nm_device_master_get_slave_by_ifindex:
|
|
* @dev: the master device
|
|
* @ifindex: the slave's interface index
|
|
*
|
|
* Returns: the slave with the given @ifindex of which @device is the master,
|
|
* or %NULL if no device with @ifindex is a slave of @device.
|
|
*/
|
|
NMDevice *
|
|
nm_device_master_get_slave_by_ifindex (NMDevice *dev, int ifindex)
|
|
{
|
|
GSList *iter;
|
|
|
|
for (iter = NM_DEVICE_GET_PRIVATE (dev)->slaves; iter; iter = g_slist_next (iter)) {
|
|
SlaveInfo *info = iter->data;
|
|
|
|
if (nm_device_get_ip_ifindex (info->slave) == ifindex)
|
|
return info->slave;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* nm_device_master_check_slave_physical_port:
|
|
* @dev: the master device
|
|
* @slave: a slave device
|
|
* @log_domain: domain to log a warning in
|
|
*
|
|
* Checks if @dev already has a slave with the same #NMDevice:physical-port-id
|
|
* as @slave, and logs a warning if so.
|
|
*/
|
|
void
|
|
nm_device_master_check_slave_physical_port (NMDevice *dev, NMDevice *slave,
|
|
guint64 log_domain)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
|
|
const char *slave_physical_port_id, *existing_physical_port_id;
|
|
SlaveInfo *info;
|
|
GSList *iter;
|
|
|
|
slave_physical_port_id = nm_device_get_physical_port_id (slave);
|
|
if (!slave_physical_port_id)
|
|
return;
|
|
|
|
for (iter = priv->slaves; iter; iter = iter->next) {
|
|
info = iter->data;
|
|
if (info->slave == slave)
|
|
continue;
|
|
|
|
existing_physical_port_id = nm_device_get_physical_port_id (info->slave);
|
|
if (!g_strcmp0 (slave_physical_port_id, existing_physical_port_id)) {
|
|
nm_log_warn (log_domain, "(%s): slave %s shares a physical port with existing slave %s",
|
|
nm_device_get_ip_iface (dev),
|
|
nm_device_get_ip_iface (slave),
|
|
nm_device_get_ip_iface (info->slave));
|
|
/* Since this function will get called for every slave, we only have
|
|
* to warn about the first match we find; if there are other matches
|
|
* later in the list, we will have already warned about them matching
|
|
* @existing earlier.
|
|
*/
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* nm_device_is_master:
|
|
* @dev: the device
|
|
*
|
|
* Returns: whether @dev can enslave other devices (eg, bridge or bond or team)
|
|
*/
|
|
gboolean
|
|
nm_device_is_master (NMDevice *dev)
|
|
{
|
|
return NM_DEVICE_GET_PRIVATE (dev)->is_master;
|
|
}
|
|
|
|
/* release all slaves */
|
|
static void
|
|
nm_device_master_release_slaves (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
while (priv->slaves) {
|
|
SlaveInfo *info = priv->slaves->data;
|
|
|
|
nm_device_release_one_slave (self, info->slave);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* nm_device_get_master:
|
|
* @dev: the device
|
|
*
|
|
* If @dev has been enslaved by another device, this returns that
|
|
* device. Otherwise it returns %NULL. (In particular, note that if
|
|
* @dev is in the process of activating as a slave, but has not yet
|
|
* been enslaved by its master, this will return %NULL.)
|
|
*
|
|
* Returns: (transfer none): @dev's master, or %NULL
|
|
*/
|
|
NMDevice *
|
|
nm_device_get_master (NMDevice *dev)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
|
|
|
|
if (priv->enslaved)
|
|
return priv->master;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* nm_device_slave_notify_enslave:
|
|
* @dev: the slave device
|
|
* @success: whether the enslaving operation succeeded
|
|
*
|
|
* Notifies a slave that either it has been enslaved, or else its master tried
|
|
* to enslave it and failed.
|
|
*/
|
|
static void
|
|
nm_device_slave_notify_enslave (NMDevice *dev, gboolean success)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
|
|
NMConnection *connection = nm_device_get_connection (dev);
|
|
|
|
g_assert (priv->master);
|
|
g_warn_if_fail (priv->enslaved == FALSE);
|
|
g_warn_if_fail (priv->state == NM_DEVICE_STATE_IP_CONFIG);
|
|
|
|
if (success) {
|
|
nm_log_info (LOGD_DEVICE,
|
|
"Activation (%s) connection '%s' enslaved, continuing activation",
|
|
nm_device_get_iface (dev),
|
|
nm_connection_get_id (connection));
|
|
|
|
priv->enslaved = TRUE;
|
|
g_object_notify (G_OBJECT (dev), NM_DEVICE_MASTER);
|
|
} else {
|
|
nm_log_warn (LOGD_DEVICE,
|
|
"Activation (%s) connection '%s' could not be enslaved",
|
|
nm_device_get_iface (dev),
|
|
nm_connection_get_id (connection));
|
|
}
|
|
|
|
priv->ip4_state = IP_DONE;
|
|
priv->ip6_state = IP_DONE;
|
|
nm_device_queue_state (dev,
|
|
success ? NM_DEVICE_STATE_SECONDARIES : NM_DEVICE_STATE_FAILED,
|
|
NM_DEVICE_STATE_REASON_NONE);
|
|
}
|
|
|
|
/**
|
|
* nm_device_slave_notify_release:
|
|
* @dev: the slave device
|
|
* @reason: the reason associated with the state change
|
|
*
|
|
* Notifies a slave that it has been released, and why.
|
|
*/
|
|
static void
|
|
nm_device_slave_notify_release (NMDevice *dev, NMDeviceStateReason reason)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
|
|
NMConnection *connection = nm_device_get_connection (dev);
|
|
NMDeviceState new_state;
|
|
const char *master_status;
|
|
|
|
if ( priv->state > NM_DEVICE_STATE_DISCONNECTED
|
|
&& priv->state <= NM_DEVICE_STATE_ACTIVATED) {
|
|
if (reason == NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED) {
|
|
new_state = NM_DEVICE_STATE_FAILED;
|
|
master_status = "failed";
|
|
} else if (reason == NM_DEVICE_STATE_REASON_USER_REQUESTED) {
|
|
new_state = NM_DEVICE_STATE_DEACTIVATING;
|
|
master_status = "deactivated by user request";
|
|
} else {
|
|
new_state = NM_DEVICE_STATE_DISCONNECTED;
|
|
master_status = "deactivated";
|
|
}
|
|
|
|
nm_log_dbg (LOGD_DEVICE,
|
|
"Activation (%s) connection '%s' master %s",
|
|
nm_device_get_iface (dev),
|
|
nm_connection_get_id (connection),
|
|
master_status);
|
|
|
|
nm_device_queue_state (dev, new_state, reason);
|
|
}
|
|
|
|
if (priv->enslaved) {
|
|
priv->enslaved = FALSE;
|
|
g_object_notify (G_OBJECT (dev), NM_DEVICE_MASTER);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* nm_device_get_enslaved:
|
|
* @device: the #NMDevice
|
|
*
|
|
* Returns: %TRUE if the device is enslaved to a master device (eg bridge or
|
|
* bond or team), %FALSE if not
|
|
*/
|
|
gboolean
|
|
nm_device_get_enslaved (NMDevice *device)
|
|
{
|
|
return NM_DEVICE_GET_PRIVATE (device)->enslaved;
|
|
}
|
|
|
|
/*
|
|
* nm_device_get_act_request
|
|
*
|
|
* Return the devices activation request, if any.
|
|
*
|
|
*/
|
|
NMActRequest *
|
|
nm_device_get_act_request (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->act_request;
|
|
}
|
|
|
|
NMConnection *
|
|
nm_device_get_connection (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
return priv->act_request ? nm_act_request_get_connection (priv->act_request) : NULL;
|
|
}
|
|
|
|
static gboolean
|
|
is_available (NMDevice *device)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
return priv->carrier || priv->ignore_carrier;
|
|
}
|
|
|
|
/**
|
|
* nm_device_is_available:
|
|
* @self: the #NMDevice
|
|
*
|
|
* Checks if @self would currently be capable of activating a
|
|
* connection. In particular, it checks that the device is ready (eg,
|
|
* is not missing firmware), that it has carrier (if necessary), and
|
|
* that any necessary external software (eg, ModemManager,
|
|
* wpa_supplicant) is available.
|
|
*
|
|
* @self can only be in a state higher than
|
|
* %NM_DEVICE_STATE_UNAVAILABLE when nm_device_is_available() returns
|
|
* %TRUE. (But note that it can still be %NM_DEVICE_STATE_UNMANAGED
|
|
* when it is available.)
|
|
*
|
|
* Returns: %TRUE or %FALSE
|
|
*/
|
|
gboolean
|
|
nm_device_is_available (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (priv->firmware_missing)
|
|
return FALSE;
|
|
|
|
return NM_DEVICE_GET_CLASS (self)->is_available (self);
|
|
}
|
|
|
|
gboolean
|
|
nm_device_ignore_carrier (NMDevice *dev)
|
|
{
|
|
return NM_DEVICE_GET_PRIVATE (dev)->ignore_carrier;
|
|
}
|
|
|
|
gboolean
|
|
nm_device_get_enabled (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
|
|
|
|
if (NM_DEVICE_GET_CLASS (self)->get_enabled)
|
|
return NM_DEVICE_GET_CLASS (self)->get_enabled (self);
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
nm_device_set_enabled (NMDevice *self, gboolean enabled)
|
|
{
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
if (NM_DEVICE_GET_CLASS (self)->set_enabled)
|
|
NM_DEVICE_GET_CLASS (self)->set_enabled (self, enabled);
|
|
}
|
|
|
|
RfKillType
|
|
nm_device_get_rfkill_type (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->rfkill_type;
|
|
}
|
|
|
|
static gboolean
|
|
autoconnect_allowed_accumulator (GSignalInvocationHint *ihint,
|
|
GValue *return_accu,
|
|
const GValue *handler_return, gpointer data)
|
|
{
|
|
if (!g_value_get_boolean (handler_return))
|
|
g_value_set_boolean (return_accu, FALSE);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
nm_device_autoconnect_allowed (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
GValue instance = G_VALUE_INIT;
|
|
GValue retval = G_VALUE_INIT;
|
|
|
|
g_value_init (&instance, G_TYPE_OBJECT);
|
|
g_value_take_object (&instance, self);
|
|
|
|
g_value_init (&retval, G_TYPE_BOOLEAN);
|
|
if (priv->autoconnect)
|
|
g_value_set_boolean (&retval, TRUE);
|
|
else
|
|
g_value_set_boolean (&retval, FALSE);
|
|
|
|
/* Use g_signal_emitv() rather than g_signal_emit() to avoid the return
|
|
* value being changed if no handlers are connected */
|
|
g_signal_emitv (&instance, signals[AUTOCONNECT_ALLOWED], 0, &retval);
|
|
return g_value_get_boolean (&retval);
|
|
}
|
|
|
|
static gboolean
|
|
can_auto_connect (NMDevice *device,
|
|
NMConnection *connection,
|
|
char **specific_object)
|
|
{
|
|
NMSettingConnection *s_con;
|
|
|
|
s_con = nm_connection_get_setting_connection (connection);
|
|
if (!nm_setting_connection_get_autoconnect (s_con))
|
|
return FALSE;
|
|
|
|
return nm_device_connection_is_available (device, connection);
|
|
}
|
|
|
|
static gboolean
|
|
device_has_config (NMDevice *device)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
/* Check for IP configuration. */
|
|
if (priv->ip4_config && nm_ip4_config_get_num_addresses (priv->ip4_config))
|
|
return TRUE;
|
|
if (priv->ip6_config && nm_ip6_config_get_num_addresses (priv->ip6_config))
|
|
return TRUE;
|
|
|
|
/* The existence of a software device is good enough. */
|
|
if (nm_device_is_software (device))
|
|
return TRUE;
|
|
|
|
/* Slaves are also configured by definition */
|
|
if (nm_platform_link_get_master (priv->ifindex) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
NMConnection *
|
|
nm_device_generate_connection (NMDevice *device)
|
|
{
|
|
NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
const char *ifname = nm_device_get_iface (device);
|
|
int ifindex = nm_device_get_ifindex (device);
|
|
NMConnection *connection;
|
|
NMSetting *s_con;
|
|
NMSetting *s_ip4;
|
|
NMSetting *s_ip6;
|
|
gs_free char *uuid = NULL;
|
|
gs_free char *name = NULL;
|
|
int master_ifindex = 0;
|
|
const char *ip4_method, *ip6_method;
|
|
|
|
/* If update_connection() is not implemented, just fail. */
|
|
if (!klass->update_connection)
|
|
return NULL;
|
|
|
|
/* Return NULL if device is unconfigured. */
|
|
if (!device_has_config (device)) {
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): device has no existing configuration", ifname);
|
|
return NULL;
|
|
}
|
|
|
|
connection = nm_connection_new ();
|
|
s_con = nm_setting_connection_new ();
|
|
uuid = nm_utils_uuid_generate ();
|
|
name = g_strdup_printf ("%s", ifname);
|
|
|
|
g_object_set (s_con,
|
|
NM_SETTING_CONNECTION_UUID, uuid,
|
|
NM_SETTING_CONNECTION_ID, name,
|
|
NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
|
|
NM_SETTING_CONNECTION_TIMESTAMP, (guint64) time (NULL),
|
|
NULL);
|
|
if (klass->connection_type)
|
|
g_object_set (s_con, NM_SETTING_CONNECTION_TYPE, klass->connection_type, NULL);
|
|
nm_connection_add_setting (connection, s_con);
|
|
|
|
/* If the device is a slave, update various slave settings */
|
|
if (ifindex)
|
|
master_ifindex = nm_platform_link_get_master (ifindex);
|
|
if (master_ifindex > 0) {
|
|
const char *master_iface = nm_platform_link_get_name (master_ifindex);
|
|
const char *slave_type = NULL;
|
|
gboolean success = FALSE;
|
|
|
|
switch (nm_platform_link_get_type (master_ifindex)) {
|
|
case NM_LINK_TYPE_BRIDGE:
|
|
slave_type = NM_SETTING_BRIDGE_SETTING_NAME;
|
|
success = nm_bridge_update_slave_connection (device, connection);
|
|
break;
|
|
case NM_LINK_TYPE_BOND:
|
|
slave_type = NM_SETTING_BOND_SETTING_NAME;
|
|
success = TRUE;
|
|
break;
|
|
case NM_LINK_TYPE_TEAM:
|
|
slave_type = NM_SETTING_TEAM_SETTING_NAME;
|
|
success = nm_team_update_slave_connection (device, connection);
|
|
break;
|
|
default:
|
|
g_warn_if_reached ();
|
|
break;
|
|
}
|
|
|
|
if (!success)
|
|
nm_log_err (LOGD_DEVICE, "(%s): failed to read slave configuration", ifname);
|
|
|
|
g_object_set (s_con,
|
|
NM_SETTING_CONNECTION_MASTER, master_iface,
|
|
NM_SETTING_CONNECTION_SLAVE_TYPE, slave_type,
|
|
NULL);
|
|
} else {
|
|
/* Only regular and master devices get IP configuration; slaves do not */
|
|
s_ip4 = nm_setting_ip4_config_new ();
|
|
nm_connection_add_setting (connection, s_ip4);
|
|
if (priv->ip4_config)
|
|
nm_ip4_config_update_setting (priv->ip4_config, (NMSettingIP4Config *) s_ip4);
|
|
else
|
|
g_object_set (s_ip4, NM_SETTING_IP4_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL);
|
|
|
|
s_ip6 = nm_setting_ip6_config_new ();
|
|
nm_connection_add_setting (connection, s_ip6);
|
|
if (priv->ip6_config)
|
|
nm_ip6_config_update_setting (priv->ip6_config, (NMSettingIP6Config *) s_ip6);
|
|
else
|
|
g_object_set (s_ip6, NM_SETTING_IP6_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
|
|
}
|
|
|
|
klass->update_connection (device, connection);
|
|
|
|
/* Check the connection in case of update_connection() bug. */
|
|
g_return_val_if_fail (nm_connection_verify (connection, NULL), NULL);
|
|
|
|
/* Ignore the connection if it has no IP configuration,
|
|
* no slave configuration, and is not a master interface.
|
|
*/
|
|
ip4_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
|
|
ip6_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
|
|
if ( g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
|
|
&& g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0
|
|
&& !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))
|
|
&& !nm_platform_link_supports_slaves (priv->ifindex)) {
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): ignoring generated connection (no IP, not master, no slaves)", ifname);
|
|
g_object_unref (connection);
|
|
connection = NULL;
|
|
}
|
|
|
|
return connection;
|
|
}
|
|
|
|
/**
|
|
* nm_device_get_best_auto_connection:
|
|
* @dev: an #NMDevice
|
|
* @connections: (element-type #NMConnection): a list of connections
|
|
* @specific_object: (out) (transfer full): on output, the path of an
|
|
* object associated with the returned connection, to be passed to
|
|
* nm_manager_activate_connection(), or %NULL.
|
|
*
|
|
* Looks through @connections to see if there is a connection that can
|
|
* be auto-activated on @dev right now. This requires, at a minimum,
|
|
* that the connection be compatible with @dev, and that it have the
|
|
* #NMSettingConnection:autoconnect property set. Some devices impose
|
|
* additional requirements. (Eg, a Wi-Fi connection can only be
|
|
* activated if its SSID was seen in the last scan.)
|
|
*
|
|
* Returns: an auto-activatable #NMConnection, or %NULL if none are
|
|
* available.
|
|
*/
|
|
|
|
NMConnection *
|
|
nm_device_get_best_auto_connection (NMDevice *dev,
|
|
GSList *connections,
|
|
char **specific_object)
|
|
{
|
|
GSList *iter;
|
|
|
|
g_return_val_if_fail (NM_IS_DEVICE (dev), NULL);
|
|
g_return_val_if_fail (specific_object != NULL, NULL);
|
|
g_return_val_if_fail (*specific_object == NULL, NULL);
|
|
|
|
for (iter = connections; iter; iter = iter->next) {
|
|
NMConnection *connection = NM_CONNECTION (iter->data);
|
|
|
|
if (NM_DEVICE_GET_CLASS (dev)->can_auto_connect (dev, connection, specific_object))
|
|
return connection;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
nm_device_complete_connection (NMDevice *self,
|
|
NMConnection *connection,
|
|
const char *specific_object,
|
|
const GSList *existing_connections,
|
|
GError **error)
|
|
{
|
|
gboolean success = FALSE;
|
|
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
g_return_val_if_fail (connection != NULL, FALSE);
|
|
|
|
if (!NM_DEVICE_GET_CLASS (self)->complete_connection) {
|
|
g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CONNECTION_INVALID,
|
|
"Device class %s had no complete_connection method",
|
|
G_OBJECT_TYPE_NAME (self));
|
|
return FALSE;
|
|
}
|
|
|
|
success = NM_DEVICE_GET_CLASS (self)->complete_connection (self,
|
|
connection,
|
|
specific_object,
|
|
existing_connections,
|
|
error);
|
|
if (success)
|
|
success = nm_connection_verify (connection, error);
|
|
|
|
return success;
|
|
}
|
|
|
|
static gboolean
|
|
check_connection_compatible (NMDevice *device,
|
|
NMConnection *connection,
|
|
GError **error)
|
|
{
|
|
NMSettingConnection *s_con;
|
|
const char *config_iface, *device_iface;
|
|
|
|
s_con = nm_connection_get_setting_connection (connection);
|
|
g_assert (s_con);
|
|
|
|
config_iface = nm_setting_connection_get_interface_name (s_con);
|
|
device_iface = nm_device_get_iface (device);
|
|
if (config_iface && strcmp (config_iface, device_iface) != 0) {
|
|
g_set_error (error,
|
|
NM_DEVICE_ERROR, NM_DEVICE_ERROR_CONNECTION_INVALID,
|
|
"The connection is not valid for this interface.");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* nm_device_check_connection_compatible:
|
|
* @device: an #NMDevice
|
|
* @connection: an #NMConnection
|
|
* @error: return location for an error, or %NULL
|
|
*
|
|
* Checks if @connection could potentially be activated on @device.
|
|
* This means only that @device has the proper capabilities, and that
|
|
* @connection is not locked to some other device. It does not
|
|
* necessarily mean that @connection could be activated on @device
|
|
* right now. (Eg, it might refer to a Wi-Fi network that is not
|
|
* currently available.)
|
|
*
|
|
* Returns: #TRUE if @connection could potentially be activated on
|
|
* @device.
|
|
*/
|
|
gboolean
|
|
nm_device_check_connection_compatible (NMDevice *device,
|
|
NMConnection *connection,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
|
|
|
|
return NM_DEVICE_GET_CLASS (device)->check_connection_compatible (device, connection, error);
|
|
}
|
|
|
|
/**
|
|
* nm_device_can_assume_connections:
|
|
* @device: #NMDevice instance
|
|
*
|
|
* This is a convenience function to determine whether connection assumption
|
|
* is available for this device.
|
|
*
|
|
* Use this function when you need to determine whether full cleanup should
|
|
* be performed for this device or whether the device should be kept running
|
|
* between NetworkManager runs.
|
|
*
|
|
* Returns: %TRUE for assumable connections and %FALS for full-cleanup connections.
|
|
*
|
|
* FIXME: Consider turning this method into (a) a device capability or (b) a class
|
|
* method.
|
|
*/
|
|
gboolean
|
|
nm_device_can_assume_connections (NMDevice *device)
|
|
{
|
|
return !!NM_DEVICE_GET_CLASS (device)->update_connection;
|
|
}
|
|
|
|
static void
|
|
dnsmasq_state_changed_cb (NMDnsMasqManager *manager, guint32 status, gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
|
|
switch (status) {
|
|
case NM_DNSMASQ_STATUS_DEAD:
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
activation_source_clear (NMDevice *self, gboolean remove_source, int family)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
guint *act_source_id;
|
|
gpointer *act_source_func;
|
|
|
|
if (family == AF_INET6) {
|
|
act_source_id = &priv->act_source6_id;
|
|
act_source_func = &priv->act_source6_func;
|
|
} else {
|
|
act_source_id = &priv->act_source_id;
|
|
act_source_func = &priv->act_source_func;
|
|
}
|
|
|
|
if (*act_source_id) {
|
|
if (remove_source)
|
|
g_source_remove (*act_source_id);
|
|
*act_source_id = 0;
|
|
*act_source_func = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
activation_source_schedule (NMDevice *self, GSourceFunc func, int family)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
guint *act_source_id;
|
|
gpointer *act_source_func;
|
|
|
|
if (family == AF_INET6) {
|
|
act_source_id = &priv->act_source6_id;
|
|
act_source_func = &priv->act_source6_func;
|
|
} else {
|
|
act_source_id = &priv->act_source_id;
|
|
act_source_func = &priv->act_source_func;
|
|
}
|
|
|
|
if (*act_source_id) {
|
|
nm_log_err (LOGD_DEVICE, "activation stage already scheduled");
|
|
}
|
|
|
|
/* Don't bother rescheduling the same function that's about to
|
|
* run anyway. Fixes issues with crappy wireless drivers sending
|
|
* streams of associate events before NM has had a chance to process
|
|
* the first one.
|
|
*/
|
|
if (!*act_source_id || (*act_source_func != func)) {
|
|
activation_source_clear (self, TRUE, family);
|
|
*act_source_id = g_idle_add (func, self);
|
|
*act_source_func = func;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
nm_device_ip_config_should_fail (NMDevice *self, gboolean ip6)
|
|
{
|
|
NMConnection *connection;
|
|
NMSettingIP4Config *s_ip4;
|
|
NMSettingIP6Config *s_ip6;
|
|
|
|
g_return_val_if_fail (self != NULL, TRUE);
|
|
|
|
connection = nm_device_get_connection (self);
|
|
g_assert (connection);
|
|
|
|
/* Fail the connection if the failed IP method is required to complete */
|
|
if (ip6) {
|
|
s_ip6 = nm_connection_get_setting_ip6_config (connection);
|
|
if (!nm_setting_ip6_config_get_may_fail (s_ip6))
|
|
return TRUE;
|
|
} else {
|
|
s_ip4 = nm_connection_get_setting_ip4_config (connection);
|
|
if (!nm_setting_ip4_config_get_may_fail (s_ip4))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
master_ready_cb (NMActiveConnection *active,
|
|
GParamSpec *pspec,
|
|
NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMActiveConnection *master;
|
|
|
|
g_assert (priv->state == NM_DEVICE_STATE_PREPARE);
|
|
|
|
/* Notify a master device that it has a new slave */
|
|
g_assert (nm_active_connection_get_master_ready (active));
|
|
master = nm_active_connection_get_master (active);
|
|
|
|
priv->master = g_object_ref (nm_active_connection_get_device (master));
|
|
nm_device_master_add_slave (priv->master,
|
|
self,
|
|
nm_active_connection_get_assumed (active) ? FALSE : TRUE);
|
|
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): master connection ready; master device %s",
|
|
nm_device_get_iface (self),
|
|
nm_device_get_iface (priv->master));
|
|
|
|
if (priv->master_ready_id) {
|
|
g_signal_handler_disconnect (active, priv->master_ready_id);
|
|
priv->master_ready_id = 0;
|
|
}
|
|
|
|
nm_device_activate_schedule_stage2_device_config (self);
|
|
}
|
|
|
|
static NMActStageReturn
|
|
act_stage1_prepare (NMDevice *self, NMDeviceStateReason *reason)
|
|
{
|
|
return NM_ACT_STAGE_RETURN_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* nm_device_activate_stage1_device_prepare
|
|
*
|
|
* Prepare for device activation
|
|
*
|
|
*/
|
|
static gboolean
|
|
nm_device_activate_stage1_device_prepare (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
const char *iface;
|
|
NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
|
|
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
|
|
NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request);
|
|
|
|
/* Clear the activation source ID now that this stage has run */
|
|
activation_source_clear (self, FALSE, 0);
|
|
|
|
priv->ip4_state = priv->ip6_state = IP_NONE;
|
|
|
|
iface = nm_device_get_iface (self);
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) started...", iface);
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
|
|
|
|
/* Assumed connections were already set up outside NetworkManager */
|
|
if (!nm_active_connection_get_assumed (active)) {
|
|
ret = NM_DEVICE_GET_CLASS (self)->act_stage1_prepare (self, &reason);
|
|
if (ret == NM_ACT_STAGE_RETURN_POSTPONE) {
|
|
goto out;
|
|
} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
|
|
goto out;
|
|
}
|
|
g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
|
|
}
|
|
|
|
if (nm_active_connection_get_master (active)) {
|
|
/* If the master connection is ready for slaves, attach ourselves */
|
|
if (nm_active_connection_get_master_ready (active))
|
|
master_ready_cb (active, NULL, self);
|
|
else {
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): waiting for master connection to become ready",
|
|
nm_device_get_iface (self));
|
|
|
|
/* Attach a signal handler and wait for the master connection to begin activating */
|
|
g_assert (priv->master_ready_id == 0);
|
|
priv->master_ready_id = g_signal_connect (active,
|
|
"notify::" NM_ACTIVE_CONNECTION_INT_MASTER_READY,
|
|
(GCallback) master_ready_cb,
|
|
self);
|
|
/* Postpone */
|
|
}
|
|
} else
|
|
nm_device_activate_schedule_stage2_device_config (self);
|
|
|
|
out:
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) complete.", iface);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
* nm_device_activate_schedule_stage1_device_prepare
|
|
*
|
|
* Prepare a device for activation
|
|
*
|
|
*/
|
|
void
|
|
nm_device_activate_schedule_stage1_device_prepare (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
g_return_if_fail (priv->act_request);
|
|
|
|
activation_source_schedule (self, nm_device_activate_stage1_device_prepare, 0);
|
|
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) scheduled...",
|
|
nm_device_get_iface (self));
|
|
}
|
|
|
|
static NMActStageReturn
|
|
act_stage2_config (NMDevice *dev, NMDeviceStateReason *reason)
|
|
{
|
|
/* Nothing to do */
|
|
return NM_ACT_STAGE_RETURN_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* nm_device_activate_stage2_device_config
|
|
*
|
|
* Determine device parameters and set those on the device, ie
|
|
* for wireless devices, set SSID, keys, etc.
|
|
*
|
|
*/
|
|
static gboolean
|
|
nm_device_activate_stage2_device_config (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
const char *iface;
|
|
NMActStageReturn ret;
|
|
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
|
|
gboolean no_firmware = FALSE;
|
|
NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request);
|
|
GSList *iter;
|
|
|
|
/* Clear the activation source ID now that this stage has run */
|
|
activation_source_clear (self, FALSE, 0);
|
|
|
|
iface = nm_device_get_iface (self);
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) starting...", iface);
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
|
|
|
|
/* Assumed connections were already set up outside NetworkManager */
|
|
if (!nm_active_connection_get_assumed (active)) {
|
|
if (!nm_device_bring_up (self, FALSE, &no_firmware)) {
|
|
if (no_firmware)
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_FIRMWARE_MISSING);
|
|
else
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
|
|
goto out;
|
|
}
|
|
|
|
ret = NM_DEVICE_GET_CLASS (self)->act_stage2_config (self, &reason);
|
|
if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
|
|
goto out;
|
|
else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
|
|
goto out;
|
|
}
|
|
g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
|
|
}
|
|
|
|
/* If we have slaves that aren't yet enslaved, do that now */
|
|
for (iter = priv->slaves; iter; iter = g_slist_next (iter)) {
|
|
SlaveInfo *info = iter->data;
|
|
|
|
if (nm_device_get_state (info->slave) == NM_DEVICE_STATE_IP_CONFIG)
|
|
nm_device_enslave_slave (self, info->slave, nm_device_get_connection (info->slave));
|
|
}
|
|
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) successful.", iface);
|
|
|
|
nm_device_activate_schedule_stage3_ip_config_start (self);
|
|
|
|
out:
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) complete.", iface);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
* nm_device_activate_schedule_stage2_device_config
|
|
*
|
|
* Schedule setup of the hardware device
|
|
*
|
|
*/
|
|
void
|
|
nm_device_activate_schedule_stage2_device_config (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
g_return_if_fail (priv->act_request);
|
|
|
|
activation_source_schedule (self, nm_device_activate_stage2_device_config, 0);
|
|
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) scheduled...",
|
|
nm_device_get_iface (self));
|
|
}
|
|
|
|
/*********************************************/
|
|
/* avahi-autoipd stuff */
|
|
|
|
static void
|
|
aipd_timeout_remove (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (priv->aipd_timeout) {
|
|
g_source_remove (priv->aipd_timeout);
|
|
priv->aipd_timeout = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
aipd_cleanup (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (priv->aipd_watch) {
|
|
g_source_remove (priv->aipd_watch);
|
|
priv->aipd_watch = 0;
|
|
}
|
|
|
|
if (priv->aipd_pid > 0) {
|
|
kill (priv->aipd_pid, SIGKILL);
|
|
|
|
/* ensure the child is reaped */
|
|
nm_log_dbg (LOGD_AUTOIP4, "waiting for avahi-autoipd pid %d to exit", priv->aipd_pid);
|
|
waitpid (priv->aipd_pid, NULL, 0);
|
|
nm_log_dbg (LOGD_AUTOIP4, "avahi-autoip pid %d cleaned up", priv->aipd_pid);
|
|
|
|
priv->aipd_pid = -1;
|
|
}
|
|
|
|
aipd_timeout_remove (self);
|
|
}
|
|
|
|
static NMIP4Config *
|
|
aipd_get_ip4_config (NMDevice *self, guint32 lla)
|
|
{
|
|
NMIP4Config *config = NULL;
|
|
NMPlatformIP4Address address;
|
|
NMPlatformIP4Route route;
|
|
|
|
config = nm_ip4_config_new ();
|
|
g_assert (config);
|
|
|
|
memset (&address, 0, sizeof (address));
|
|
address.address = lla;
|
|
address.plen = 16;
|
|
nm_ip4_config_add_address (config, &address);
|
|
|
|
/* Add a multicast route for link-local connections: destination= 224.0.0.0, netmask=240.0.0.0 */
|
|
memset (&route, 0, sizeof (route));
|
|
route.network = htonl (0xE0000000L);
|
|
route.plen = 4;
|
|
nm_ip4_config_add_route (config, &route);
|
|
|
|
return config;
|
|
}
|
|
|
|
#define IPV4LL_NETWORK (htonl (0xA9FE0000L))
|
|
#define IPV4LL_NETMASK (htonl (0xFFFF0000L))
|
|
|
|
void
|
|
nm_device_handle_autoip4_event (NMDevice *self,
|
|
const char *event,
|
|
const char *address)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMConnection *connection = NULL;
|
|
const char *iface, *method;
|
|
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
|
|
|
|
g_return_if_fail (event != NULL);
|
|
|
|
if (priv->act_request == NULL)
|
|
return;
|
|
|
|
connection = nm_act_request_get_connection (priv->act_request);
|
|
g_assert (connection);
|
|
|
|
/* Ignore if the connection isn't an AutoIP connection */
|
|
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
|
|
if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) != 0)
|
|
return;
|
|
|
|
iface = nm_device_get_iface (self);
|
|
|
|
if (strcmp (event, "BIND") == 0) {
|
|
guint32 lla;
|
|
NMIP4Config *config;
|
|
|
|
if (inet_pton (AF_INET, address, &lla) <= 0) {
|
|
nm_log_err (LOGD_AUTOIP4, "(%s): invalid address %s received from avahi-autoipd.",
|
|
iface, address);
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_ERROR);
|
|
return;
|
|
}
|
|
|
|
if ((lla & IPV4LL_NETMASK) != IPV4LL_NETWORK) {
|
|
nm_log_err (LOGD_AUTOIP4, "(%s): invalid address %s received from avahi-autoipd (not link-local).",
|
|
iface, address);
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_ERROR);
|
|
return;
|
|
}
|
|
|
|
config = aipd_get_ip4_config (self, lla);
|
|
if (config == NULL) {
|
|
nm_log_err (LOGD_AUTOIP4, "failed to get autoip config");
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
|
|
return;
|
|
}
|
|
|
|
if (priv->ip4_state == IP_CONF) {
|
|
aipd_timeout_remove (self);
|
|
nm_device_activate_schedule_ip4_config_result (self, config);
|
|
} else if (priv->ip4_state == IP_DONE) {
|
|
if (!ip4_config_merge_and_apply (self, config, TRUE, &reason)) {
|
|
nm_log_err (LOGD_AUTOIP4, "(%s): failed to update IP4 config for autoip change.",
|
|
nm_device_get_iface (self));
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
|
|
}
|
|
} else
|
|
g_assert_not_reached ();
|
|
|
|
g_object_unref (config);
|
|
} else {
|
|
nm_log_warn (LOGD_AUTOIP4, "(%s): autoip address %s no longer valid because '%s'.",
|
|
iface, address, event);
|
|
|
|
/* The address is gone; terminate the connection or fail activation */
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
|
|
}
|
|
}
|
|
|
|
static void
|
|
aipd_watch_cb (GPid pid, gint status, gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMDeviceState state;
|
|
const char *iface;
|
|
|
|
if (!priv->aipd_watch)
|
|
return;
|
|
priv->aipd_watch = 0;
|
|
|
|
iface = nm_device_get_iface (self);
|
|
|
|
if (WIFEXITED (status)) {
|
|
nm_log_dbg (LOGD_AUTOIP4, "(%s): avahi-autoipd exited with error code %d",
|
|
iface, WEXITSTATUS (status));
|
|
} else if (WIFSTOPPED (status)) {
|
|
nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd stopped unexpectedly with signal %d",
|
|
iface, WSTOPSIG (status));
|
|
} else if (WIFSIGNALED (status)) {
|
|
nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd died with signal %d",
|
|
iface, WTERMSIG (status));
|
|
} else {
|
|
nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd died from an unknown cause", iface);
|
|
}
|
|
|
|
aipd_cleanup (self);
|
|
|
|
state = nm_device_get_state (self);
|
|
if (nm_device_is_activating (self) || (state == NM_DEVICE_STATE_ACTIVATED))
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
|
|
}
|
|
|
|
static gboolean
|
|
aipd_timeout_cb (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (priv->aipd_timeout) {
|
|
nm_log_info (LOGD_AUTOIP4, "(%s): avahi-autoipd timed out.", nm_device_get_iface (self));
|
|
priv->aipd_timeout = 0;
|
|
aipd_cleanup (self);
|
|
|
|
if (priv->ip4_state == IP_CONF)
|
|
nm_device_activate_schedule_ip4_config_timeout (self);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
aipd_child_setup (gpointer user_data G_GNUC_UNUSED)
|
|
{
|
|
/* We are in the child process at this point.
|
|
* Give child it's own program group for signal
|
|
* separation.
|
|
*/
|
|
pid_t pid = getpid ();
|
|
setpgid (pid, pid);
|
|
|
|
/*
|
|
* We blocked signals in main(). We need to restore original signal
|
|
* mask for avahi-autoipd here so that it can receive signals.
|
|
*/
|
|
nm_unblock_posix_signals (NULL);
|
|
}
|
|
|
|
static NMActStageReturn
|
|
aipd_start (NMDevice *self, NMDeviceStateReason *reason)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
const char *iface = nm_device_get_iface (self);
|
|
char *argv[6], *cmdline;
|
|
const char **aipd_binary = NULL;
|
|
static const char *aipd_paths[] = {
|
|
"/usr/sbin/avahi-autoipd",
|
|
"/usr/local/sbin/avahi-autoipd",
|
|
NULL
|
|
};
|
|
int i = 0;
|
|
GError *error = NULL;
|
|
|
|
aipd_cleanup (self);
|
|
|
|
/* Find avahi-autoipd */
|
|
aipd_binary = aipd_paths;
|
|
while (*aipd_binary != NULL) {
|
|
if (g_file_test (*aipd_binary, G_FILE_TEST_EXISTS))
|
|
break;
|
|
aipd_binary++;
|
|
}
|
|
|
|
if (!*aipd_binary) {
|
|
nm_log_warn (LOGD_DEVICE | LOGD_AUTOIP4,
|
|
"Activation (%s) Stage 3 of 5 (IP Configure Start) failed"
|
|
" to start avahi-autoipd: not found", iface);
|
|
*reason = NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED;
|
|
return NM_ACT_STAGE_RETURN_FAILURE;
|
|
}
|
|
|
|
argv[i++] = (char *) (*aipd_binary);
|
|
argv[i++] = "--script";
|
|
argv[i++] = (char *) nm_device_autoipd_helper_path;
|
|
|
|
if (nm_logging_enabled (LOGL_DEBUG, LOGD_AUTOIP4))
|
|
argv[i++] = "--debug";
|
|
argv[i++] = (char *) nm_device_get_ip_iface (self);
|
|
argv[i++] = NULL;
|
|
|
|
cmdline = g_strjoinv (" ", argv);
|
|
nm_log_dbg (LOGD_AUTOIP4, "running: %s", cmdline);
|
|
g_free (cmdline);
|
|
|
|
if (!g_spawn_async ("/", argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
|
|
&aipd_child_setup, NULL, &(priv->aipd_pid), &error)) {
|
|
nm_log_warn (LOGD_DEVICE | LOGD_AUTOIP4,
|
|
"Activation (%s) Stage 3 of 5 (IP Configure Start) failed"
|
|
" to start avahi-autoipd: %s",
|
|
iface,
|
|
error && error->message ? error->message : "(unknown)");
|
|
g_clear_error (&error);
|
|
aipd_cleanup (self);
|
|
return NM_ACT_STAGE_RETURN_FAILURE;
|
|
}
|
|
|
|
nm_log_info (LOGD_DEVICE | LOGD_AUTOIP4,
|
|
"Activation (%s) Stage 3 of 5 (IP Configure Start) started"
|
|
" avahi-autoipd...", iface);
|
|
|
|
/* Monitor the child process so we know when it dies */
|
|
priv->aipd_watch = g_child_watch_add (priv->aipd_pid, aipd_watch_cb, self);
|
|
|
|
/* Start a timeout to bound the address attempt */
|
|
priv->aipd_timeout = g_timeout_add_seconds (20, aipd_timeout_cb, self);
|
|
|
|
return NM_ACT_STAGE_RETURN_POSTPONE;
|
|
}
|
|
|
|
/*********************************************/
|
|
/* DHCPv4 stuff */
|
|
|
|
static void
|
|
dhcp4_add_option_cb (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
nm_dhcp4_config_add_option (NM_DHCP4_CONFIG (user_data),
|
|
(const char *) key,
|
|
(const char *) value);
|
|
}
|
|
|
|
static gboolean
|
|
ip4_config_merge_and_apply (NMDevice *self,
|
|
NMIP4Config *config,
|
|
gboolean commit,
|
|
NMDeviceStateReason *out_reason)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMConnection *connection;
|
|
gboolean success;
|
|
NMIP4Config *composite;
|
|
|
|
/* Merge all the configs into the composite config */
|
|
if (config) {
|
|
g_clear_object (&priv->dev_ip4_config);
|
|
priv->dev_ip4_config = g_object_ref (config);
|
|
}
|
|
|
|
composite = nm_ip4_config_new ();
|
|
if (priv->dev_ip4_config)
|
|
nm_ip4_config_merge (composite, priv->dev_ip4_config);
|
|
if (priv->vpn4_config)
|
|
nm_ip4_config_merge (composite, priv->vpn4_config);
|
|
if (priv->ext_ip4_config)
|
|
nm_ip4_config_merge (composite, priv->ext_ip4_config);
|
|
|
|
/* Merge user overrides into the composite config */
|
|
connection = nm_device_get_connection (self);
|
|
if (connection)
|
|
nm_ip4_config_merge_setting (composite, nm_connection_get_setting_ip4_config (connection));
|
|
|
|
/* Allow setting MTU etc */
|
|
if (commit) {
|
|
if (NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit)
|
|
NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit (self, composite);
|
|
}
|
|
|
|
success = nm_device_set_ip4_config (self, composite, commit, out_reason);
|
|
g_object_unref (composite);
|
|
return success;
|
|
}
|
|
|
|
static void
|
|
dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
|
|
{
|
|
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
|
|
|
|
g_return_if_fail (config != NULL);
|
|
|
|
if (!ip4_config_merge_and_apply (self, config, TRUE, &reason)) {
|
|
nm_log_warn (LOGD_DHCP4, "(%s): failed to update IPv4 config for DHCP change.",
|
|
nm_device_get_ip_iface (self));
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
|
|
} else {
|
|
/* Notify dispatcher scripts of new DHCP4 config */
|
|
nm_dispatcher_call (DISPATCHER_ACTION_DHCP4_CHANGE,
|
|
nm_device_get_connection (self),
|
|
self,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dhcp4_fail (NMDevice *device, gboolean timeout)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
nm_dhcp4_config_reset (priv->dhcp4_config);
|
|
|
|
if (timeout || (priv->ip4_state == IP_CONF))
|
|
nm_device_activate_schedule_ip4_config_timeout (device);
|
|
else if (priv->ip4_state == IP_FAIL)
|
|
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
|
|
}
|
|
|
|
static void
|
|
dhcp4_state_changed (NMDHCPClient *client,
|
|
NMDHCPState state,
|
|
gpointer user_data)
|
|
{
|
|
NMDevice *device = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
NMIP4Config *config;
|
|
|
|
g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == FALSE);
|
|
|
|
nm_log_dbg (LOGD_DHCP4, "(%s): new DHCPv4 client state %d",
|
|
nm_device_get_iface (device), state);
|
|
|
|
switch (state) {
|
|
case DHC_BOUND4: /* lease obtained */
|
|
case DHC_RENEW4: /* lease renewed */
|
|
case DHC_REBOOT: /* have valid lease, but now obtained a different one */
|
|
case DHC_REBIND4: /* new, different lease */
|
|
config = nm_dhcp_client_get_ip4_config (priv->dhcp4_client, FALSE);
|
|
if (!config) {
|
|
nm_log_warn (LOGD_DHCP4, "(%s): failed to get IPv4 config in response to DHCP event.",
|
|
nm_device_get_ip_iface (device));
|
|
nm_device_state_changed (device,
|
|
NM_DEVICE_STATE_FAILED,
|
|
NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
|
|
break;
|
|
}
|
|
|
|
/* Update the DHCP4 config object with new DHCP options */
|
|
nm_dhcp4_config_reset (priv->dhcp4_config);
|
|
nm_dhcp_client_foreach_option (priv->dhcp4_client,
|
|
dhcp4_add_option_cb,
|
|
priv->dhcp4_config);
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP4_CONFIG);
|
|
|
|
if (priv->ip4_state == IP_CONF)
|
|
nm_device_activate_schedule_ip4_config_result (device, config);
|
|
else if (priv->ip4_state == IP_DONE)
|
|
dhcp4_lease_change (device, config);
|
|
g_object_unref (config);
|
|
|
|
break;
|
|
case DHC_TIMEOUT: /* timed out contacting DHCP server */
|
|
dhcp4_fail (device, TRUE);
|
|
break;
|
|
case DHC_END: /* dhclient exited normally */
|
|
case DHC_FAIL: /* all attempts to contact server timed out, sleeping */
|
|
case DHC_ABEND: /* dhclient exited abnormally */
|
|
/* dhclient quit and can't get/renew a lease; so kill the connection */
|
|
dhcp4_fail (device, FALSE);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dhcp4_timeout (NMDHCPClient *client, gpointer user_data)
|
|
{
|
|
NMDevice *device = NM_DEVICE (user_data);
|
|
|
|
g_return_if_fail (nm_device_get_act_request (device) != NULL);
|
|
g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == FALSE);
|
|
|
|
nm_dhcp_client_stop (client, FALSE);
|
|
dhcp4_fail (device, TRUE);
|
|
}
|
|
|
|
static NMActStageReturn
|
|
dhcp4_start (NMDevice *self,
|
|
NMConnection *connection,
|
|
NMDeviceStateReason *reason)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMSettingIP4Config *s_ip4;
|
|
guint8 *anycast = NULL;
|
|
GByteArray *tmp = NULL;
|
|
|
|
s_ip4 = nm_connection_get_setting_ip4_config (connection);
|
|
|
|
if (priv->dhcp_anycast_address)
|
|
anycast = priv->dhcp_anycast_address->data;
|
|
|
|
/* Clear old exported DHCP options */
|
|
if (priv->dhcp4_config)
|
|
g_object_unref (priv->dhcp4_config);
|
|
priv->dhcp4_config = nm_dhcp4_config_new ();
|
|
|
|
if (priv->hw_addr_len) {
|
|
tmp = g_byte_array_sized_new (priv->hw_addr_len);
|
|
g_byte_array_append (tmp, priv->hw_addr, priv->hw_addr_len);
|
|
}
|
|
|
|
/* Begin DHCP on the interface */
|
|
g_warn_if_fail (priv->dhcp4_client == NULL);
|
|
priv->dhcp4_client = nm_dhcp_manager_start_ip4 (priv->dhcp_manager,
|
|
nm_device_get_ip_iface (self),
|
|
tmp,
|
|
nm_connection_get_uuid (connection),
|
|
s_ip4,
|
|
priv->dhcp_timeout,
|
|
anycast);
|
|
|
|
if (tmp)
|
|
g_byte_array_free (tmp, TRUE);
|
|
|
|
if (!priv->dhcp4_client) {
|
|
*reason = NM_DEVICE_STATE_REASON_DHCP_START_FAILED;
|
|
return NM_ACT_STAGE_RETURN_FAILURE;
|
|
}
|
|
|
|
priv->dhcp4_state_sigid = g_signal_connect (priv->dhcp4_client,
|
|
"state-changed",
|
|
G_CALLBACK (dhcp4_state_changed),
|
|
self);
|
|
priv->dhcp4_timeout_sigid = g_signal_connect (priv->dhcp4_client,
|
|
"timeout",
|
|
G_CALLBACK (dhcp4_timeout),
|
|
self);
|
|
|
|
/* DHCP devices will be notified by the DHCP manager when stuff happens */
|
|
return NM_ACT_STAGE_RETURN_POSTPONE;
|
|
}
|
|
|
|
gboolean
|
|
nm_device_dhcp4_renew (NMDevice *self, gboolean release)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMActStageReturn ret;
|
|
NMDeviceStateReason reason;
|
|
NMConnection *connection;
|
|
|
|
g_return_val_if_fail (priv->dhcp4_client != NULL, FALSE);
|
|
|
|
nm_log_info (LOGD_DHCP4, "(%s): DHCPv4 lease renewal requested",
|
|
nm_device_get_iface (self));
|
|
|
|
/* Terminate old DHCP instance and release the old lease */
|
|
dhcp4_cleanup (self, TRUE, release);
|
|
|
|
connection = nm_device_get_connection (self);
|
|
g_assert (connection);
|
|
|
|
/* Start DHCP again on the interface */
|
|
ret = dhcp4_start (self, connection, &reason);
|
|
|
|
return (ret != NM_ACT_STAGE_RETURN_FAILURE);
|
|
}
|
|
|
|
/*********************************************/
|
|
|
|
static GHashTable *shared_ips = NULL;
|
|
|
|
static void
|
|
release_shared_ip (gpointer data)
|
|
{
|
|
g_hash_table_remove (shared_ips, data);
|
|
}
|
|
|
|
static guint32
|
|
reserve_shared_ip (void)
|
|
{
|
|
guint32 start = (guint32) ntohl (0x0a2a0001); /* 10.42.0.1 */
|
|
guint32 count = 0;
|
|
|
|
while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) {
|
|
count += ntohl (0x100);
|
|
if (count > ntohl (0xFE00)) {
|
|
nm_log_err (LOGD_SHARING, "ran out of shared IP addresses!");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
g_hash_table_insert (shared_ips, GUINT_TO_POINTER (start + count), GUINT_TO_POINTER (TRUE));
|
|
return start + count;
|
|
}
|
|
|
|
static NMIP4Config *
|
|
shared4_new_config (NMDevice *self, NMDeviceStateReason *reason)
|
|
{
|
|
NMIP4Config *config = NULL;
|
|
NMPlatformIP4Address address;
|
|
guint32 tmp_addr;
|
|
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
if (G_UNLIKELY (shared_ips == NULL))
|
|
shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
|
|
tmp_addr = reserve_shared_ip ();
|
|
if (!tmp_addr) {
|
|
*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
|
|
return NULL;
|
|
}
|
|
|
|
config = nm_ip4_config_new ();
|
|
memset (&address, 0, sizeof (address));
|
|
address.address = tmp_addr;
|
|
address.plen = 24;
|
|
nm_ip4_config_add_address (config, &address);
|
|
|
|
/* Remove the address lock when the object gets disposed */
|
|
g_object_set_data_full (G_OBJECT (config), "shared-ip",
|
|
GUINT_TO_POINTER (tmp_addr), release_shared_ip);
|
|
|
|
return config;
|
|
}
|
|
|
|
/*********************************************/
|
|
|
|
static gboolean
|
|
have_any_ready_slaves (NMDevice *device, const GSList *slaves)
|
|
{
|
|
const GSList *iter;
|
|
|
|
/* Any enslaved slave is "ready" in the generic case as it's
|
|
* at least >= NM_DEVCIE_STATE_IP_CONFIG and has had Layer 2
|
|
* properties set up.
|
|
*/
|
|
for (iter = slaves; iter; iter = g_slist_next (iter)) {
|
|
if (nm_device_get_enslaved (iter->data))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
ip4_requires_slaves (NMConnection *connection)
|
|
{
|
|
const char *method;
|
|
|
|
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
|
|
return strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0;
|
|
}
|
|
|
|
static NMActStageReturn
|
|
act_stage3_ip4_config_start (NMDevice *self,
|
|
NMIP4Config **out_config,
|
|
NMDeviceStateReason *reason)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMConnection *connection;
|
|
NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
|
|
const char *method;
|
|
GSList *slaves;
|
|
gboolean ready_slaves;
|
|
|
|
g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
|
|
|
|
connection = nm_device_get_connection (self);
|
|
g_assert (connection);
|
|
|
|
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
|
|
if (priv->master)
|
|
g_assert_cmpstr (method, ==, NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
|
|
|
|
if ( strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) != 0
|
|
&& nm_device_is_master (self)
|
|
&& nm_device_is_unavailable_because_of_carrier (self)) {
|
|
nm_log_info (LOGD_IP4 | LOGD_DEVICE,
|
|
"(%s): IPv4 config waiting until carrier is on",
|
|
nm_device_get_ip_iface (self));
|
|
return NM_ACT_STAGE_RETURN_WAIT;
|
|
}
|
|
|
|
if (priv->is_master && ip4_requires_slaves (connection)) {
|
|
/* If the master has no ready slaves, and depends on slaves for
|
|
* a successful IPv4 attempt, then postpone IPv4 addressing.
|
|
*/
|
|
slaves = nm_device_master_get_slaves (self);
|
|
ready_slaves = NM_DEVICE_GET_CLASS (self)->have_any_ready_slaves (self, slaves);
|
|
g_slist_free (slaves);
|
|
|
|
if (ready_slaves == FALSE) {
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP4,
|
|
"(%s): IPv4 config waiting until slaves are ready",
|
|
nm_device_get_ip_iface (self));
|
|
return NM_ACT_STAGE_RETURN_WAIT;
|
|
}
|
|
}
|
|
|
|
/* Start IPv4 addressing based on the method requested */
|
|
if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0)
|
|
ret = dhcp4_start (self, connection, reason);
|
|
else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) == 0)
|
|
ret = aipd_start (self, reason);
|
|
else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) == 0) {
|
|
/* Use only IPv4 config from the connection data */
|
|
*out_config = nm_ip4_config_new ();
|
|
g_assert (*out_config);
|
|
ret = NM_ACT_STAGE_RETURN_SUCCESS;
|
|
} else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) {
|
|
*out_config = shared4_new_config (self, reason);
|
|
if (*out_config) {
|
|
priv->dnsmasq_manager = nm_dnsmasq_manager_new (nm_device_get_ip_iface (self));
|
|
ret = NM_ACT_STAGE_RETURN_SUCCESS;
|
|
} else
|
|
ret = NM_ACT_STAGE_RETURN_FAILURE;
|
|
} else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0) {
|
|
/* Nothing to do... */
|
|
ret = NM_ACT_STAGE_RETURN_STOP;
|
|
} else {
|
|
nm_log_warn (LOGD_IP4, "(%s): unhandled IPv4 config method '%s'; will fail",
|
|
nm_device_get_ip_iface (self), method);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*********************************************/
|
|
/* DHCPv6 stuff */
|
|
|
|
static void
|
|
dhcp6_add_option_cb (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
nm_dhcp6_config_add_option (NM_DHCP6_CONFIG (user_data),
|
|
(const char *) key,
|
|
(const char *) value);
|
|
}
|
|
|
|
static gboolean
|
|
ip6_config_merge_and_apply (NMDevice *self,
|
|
gboolean commit,
|
|
NMDeviceStateReason *out_reason)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMConnection *connection;
|
|
gboolean success;
|
|
NMIP6Config *composite;
|
|
|
|
/* If no config was passed in, create a new one */
|
|
composite = nm_ip6_config_new ();
|
|
g_assert (composite);
|
|
|
|
/* Merge all the IP configs into the composite config */
|
|
if (priv->ac_ip6_config)
|
|
nm_ip6_config_merge (composite, priv->ac_ip6_config);
|
|
if (priv->dhcp6_ip6_config)
|
|
nm_ip6_config_merge (composite, priv->dhcp6_ip6_config);
|
|
if (priv->vpn6_config)
|
|
nm_ip6_config_merge (composite, priv->vpn6_config);
|
|
if (priv->ext_ip6_config)
|
|
nm_ip6_config_merge (composite, priv->ext_ip6_config);
|
|
|
|
/* Merge user overrides into the composite config */
|
|
connection = nm_device_get_connection (self);
|
|
if (connection)
|
|
nm_ip6_config_merge_setting (composite, nm_connection_get_setting_ip6_config (connection));
|
|
|
|
success = nm_device_set_ip6_config (self, composite, commit, out_reason);
|
|
g_object_unref (composite);
|
|
return success;
|
|
}
|
|
|
|
static void
|
|
dhcp6_lease_change (NMDevice *device)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
NMConnection *connection;
|
|
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
|
|
|
|
if (priv->dhcp6_ip6_config == NULL) {
|
|
nm_log_warn (LOGD_DHCP6, "(%s): failed to get DHCPv6 config for rebind",
|
|
nm_device_get_ip_iface (device));
|
|
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
|
|
return;
|
|
}
|
|
|
|
g_assert (priv->dhcp6_client); /* sanity check */
|
|
|
|
connection = nm_device_get_connection (device);
|
|
g_assert (connection);
|
|
|
|
/* Apply the updated config */
|
|
if (ip6_config_merge_and_apply (device, TRUE, &reason) == FALSE) {
|
|
nm_log_warn (LOGD_DHCP6, "(%s): failed to update IPv6 config in response to DHCP event.",
|
|
nm_device_get_ip_iface (device));
|
|
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
|
|
} else {
|
|
/* Notify dispatcher scripts of new DHCPv6 config */
|
|
nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE, connection, device, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dhcp6_fail (NMDevice *device, gboolean timeout)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
nm_dhcp6_config_reset (priv->dhcp6_config);
|
|
|
|
if (timeout || (priv->ip6_state == IP_CONF))
|
|
nm_device_activate_schedule_ip6_config_timeout (device);
|
|
else if (priv->ip6_state == IP_FAIL)
|
|
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
|
|
}
|
|
|
|
static void
|
|
dhcp6_state_changed (NMDHCPClient *client,
|
|
NMDHCPState state,
|
|
gpointer user_data)
|
|
{
|
|
NMDevice *device = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE);
|
|
|
|
nm_log_dbg (LOGD_DHCP6, "(%s): new DHCPv6 client state %d",
|
|
nm_device_get_iface (device), state);
|
|
|
|
switch (state) {
|
|
case DHC_BOUND6:
|
|
case DHC_RENEW6: /* lease renewed */
|
|
case DHC_REBOOT: /* have valid lease, but now obtained a different one */
|
|
case DHC_REBIND6: /* new, different lease */
|
|
g_clear_object (&priv->dhcp6_ip6_config);
|
|
priv->dhcp6_ip6_config = nm_dhcp_client_get_ip6_config (priv->dhcp6_client, FALSE);
|
|
|
|
/* Update the DHCP6 config object with new DHCP options */
|
|
nm_dhcp6_config_reset (priv->dhcp6_config);
|
|
if (priv->dhcp6_ip6_config) {
|
|
nm_dhcp_client_foreach_option (priv->dhcp6_client,
|
|
dhcp6_add_option_cb,
|
|
priv->dhcp6_config);
|
|
}
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP6_CONFIG);
|
|
|
|
if (priv->ip6_state == IP_CONF) {
|
|
if (priv->dhcp6_ip6_config == NULL) {
|
|
/* FIXME: Initial DHCP failed; should we fail IPv6 entirely then? */
|
|
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DHCP_FAILED);
|
|
break;
|
|
}
|
|
nm_device_activate_schedule_ip6_config_result (device);
|
|
} else if (priv->ip6_state == IP_DONE)
|
|
dhcp6_lease_change (device);
|
|
break;
|
|
case DHC_TIMEOUT: /* timed out contacting DHCP server */
|
|
dhcp6_fail (device, TRUE);
|
|
break;
|
|
case DHC_END: /* dhclient exited normally */
|
|
/* In IPv6 info-only mode, the client doesn't handle leases so it
|
|
* may exit right after getting a response from the server. That's
|
|
* normal. In that case we just ignore the exit.
|
|
*/
|
|
if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_OTHERCONF)
|
|
break;
|
|
/* Otherwise, fall through */
|
|
case DHC_FAIL: /* all attempts to contact server timed out, sleeping */
|
|
case DHC_ABEND: /* dhclient exited abnormally */
|
|
/* dhclient quit and can't get/renew a lease; so kill the connection */
|
|
dhcp6_fail (device, FALSE);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dhcp6_timeout (NMDHCPClient *client, gpointer user_data)
|
|
{
|
|
NMDevice *device = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
g_return_if_fail (nm_device_get_act_request (device) != NULL);
|
|
g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE);
|
|
|
|
nm_dhcp_client_stop (client, FALSE);
|
|
if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED)
|
|
dhcp6_fail (device, TRUE);
|
|
else {
|
|
/* not a hard failure; just live with the RA info */
|
|
nm_dhcp6_config_reset (priv->dhcp6_config);
|
|
if (priv->dhcp6_ip6_config)
|
|
g_object_unref (priv->dhcp6_ip6_config);
|
|
priv->dhcp6_ip6_config = NULL;
|
|
|
|
if (priv->ip6_state == IP_CONF)
|
|
nm_device_activate_schedule_ip6_config_result (device);
|
|
}
|
|
}
|
|
|
|
static NMActStageReturn
|
|
dhcp6_start (NMDevice *self,
|
|
NMConnection *connection,
|
|
guint32 dhcp_opt,
|
|
NMDeviceStateReason *reason)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
|
|
guint8 *anycast = NULL;
|
|
GByteArray *tmp = NULL;
|
|
|
|
if (!connection) {
|
|
connection = nm_device_get_connection (self);
|
|
g_assert (connection);
|
|
}
|
|
|
|
/* Begin a DHCP transaction on the interface */
|
|
|
|
if (priv->dhcp_anycast_address)
|
|
anycast = priv->dhcp_anycast_address->data;
|
|
|
|
/* Clear old exported DHCP options */
|
|
if (priv->dhcp6_config)
|
|
g_object_unref (priv->dhcp6_config);
|
|
priv->dhcp6_config = nm_dhcp6_config_new ();
|
|
|
|
g_warn_if_fail (priv->dhcp6_ip6_config == NULL);
|
|
if (priv->dhcp6_ip6_config) {
|
|
g_object_unref (priv->dhcp6_ip6_config);
|
|
priv->dhcp6_ip6_config = NULL;
|
|
}
|
|
|
|
if (priv->hw_addr_len) {
|
|
tmp = g_byte_array_sized_new (priv->hw_addr_len);
|
|
g_byte_array_append (tmp, priv->hw_addr, priv->hw_addr_len);
|
|
}
|
|
|
|
priv->dhcp6_client = nm_dhcp_manager_start_ip6 (priv->dhcp_manager,
|
|
nm_device_get_ip_iface (self),
|
|
tmp,
|
|
nm_connection_get_uuid (connection),
|
|
nm_connection_get_setting_ip6_config (connection),
|
|
priv->dhcp_timeout,
|
|
anycast,
|
|
(dhcp_opt == NM_RDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE);
|
|
if (tmp)
|
|
g_byte_array_free (tmp, TRUE);
|
|
|
|
if (priv->dhcp6_client) {
|
|
priv->dhcp6_state_sigid = g_signal_connect (priv->dhcp6_client,
|
|
"state-changed",
|
|
G_CALLBACK (dhcp6_state_changed),
|
|
self);
|
|
priv->dhcp6_timeout_sigid = g_signal_connect (priv->dhcp6_client,
|
|
"timeout",
|
|
G_CALLBACK (dhcp6_timeout),
|
|
self);
|
|
|
|
/* DHCP devices will be notified by the DHCP manager when stuff happens */
|
|
ret = NM_ACT_STAGE_RETURN_POSTPONE;
|
|
} else {
|
|
*reason = NM_DEVICE_STATE_REASON_DHCP_START_FAILED;
|
|
ret = NM_ACT_STAGE_RETURN_FAILURE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/******************************************/
|
|
|
|
static gboolean
|
|
linklocal6_config_is_ready (const NMIP6Config *ip6_config)
|
|
{
|
|
int i;
|
|
|
|
if (!ip6_config)
|
|
return FALSE;
|
|
|
|
for (i = 0; i < nm_ip6_config_get_num_addresses (ip6_config); i++) {
|
|
const NMPlatformIP6Address *addr = nm_ip6_config_get_address (ip6_config, i);
|
|
|
|
if (IN6_IS_ADDR_LINKLOCAL (&addr->address) &&
|
|
!(addr->flags & IFA_F_TENTATIVE))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
linklocal6_cleanup (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (priv->linklocal6_timeout_id) {
|
|
g_source_remove (priv->linklocal6_timeout_id);
|
|
priv->linklocal6_timeout_id = 0;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
linklocal6_timeout_cb (gpointer user_data)
|
|
{
|
|
NMDevice *self = user_data;
|
|
|
|
linklocal6_cleanup (self);
|
|
|
|
nm_log_dbg (LOGD_DEVICE, "[%s] linklocal6: waiting for link-local addresses failed due to timeout",
|
|
nm_device_get_iface (self));
|
|
|
|
nm_device_activate_schedule_ip6_config_timeout (self);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
linklocal6_complete (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMConnection *connection;
|
|
const char *method;
|
|
|
|
g_assert (priv->linklocal6_timeout_id);
|
|
g_assert (linklocal6_config_is_ready (priv->ip6_config));
|
|
|
|
linklocal6_cleanup (self);
|
|
|
|
connection = nm_device_get_connection (self);
|
|
g_assert (connection);
|
|
|
|
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
|
|
|
|
nm_log_dbg (LOGD_DEVICE, "[%s] linklocal6: waiting for link-local addresses successful, continue with method %s",
|
|
nm_device_get_iface (self), method);
|
|
|
|
if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0)
|
|
addrconf6_start_with_link_ready (self);
|
|
else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0)
|
|
nm_device_activate_schedule_ip6_config_result (self);
|
|
else
|
|
g_return_if_fail (FALSE);
|
|
}
|
|
|
|
static NMActStageReturn
|
|
linklocal6_start (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMConnection *connection;
|
|
const char *method;
|
|
|
|
linklocal6_cleanup (self);
|
|
|
|
if (linklocal6_config_is_ready (priv->ip6_config))
|
|
return NM_ACT_STAGE_RETURN_SUCCESS;
|
|
|
|
connection = nm_device_get_connection (self);
|
|
g_assert (connection);
|
|
|
|
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
|
|
nm_log_dbg (LOGD_DEVICE, "[%s] linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses configured. Wait.",
|
|
nm_device_get_iface (self), method);
|
|
|
|
priv->linklocal6_timeout_id = g_timeout_add_seconds (5, linklocal6_timeout_cb, self);
|
|
|
|
return NM_ACT_STAGE_RETURN_POSTPONE;
|
|
}
|
|
|
|
/******************************************/
|
|
|
|
static void dhcp6_cleanup (NMDevice *self, gboolean stop, gboolean release);
|
|
|
|
static void
|
|
rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *device)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
NMConnection *connection;
|
|
int i;
|
|
NMDeviceStateReason reason;
|
|
|
|
g_return_if_fail (priv->act_request);
|
|
connection = nm_device_get_connection (device);
|
|
g_assert (connection);
|
|
|
|
if (!priv->ac_ip6_config)
|
|
priv->ac_ip6_config = nm_ip6_config_new ();
|
|
|
|
if (changed & NM_RDISC_CONFIG_GATEWAYS) {
|
|
/* Use the first gateway as ordered in router discovery cache. */
|
|
if (rdisc->gateways->len) {
|
|
NMRDiscGateway *gateway = &g_array_index (rdisc->gateways, NMRDiscGateway, 0);
|
|
|
|
nm_ip6_config_set_gateway (priv->ac_ip6_config, &gateway->address);
|
|
} else
|
|
nm_ip6_config_set_gateway (priv->ac_ip6_config, NULL);
|
|
}
|
|
|
|
if (changed & NM_RDISC_CONFIG_ADDRESSES) {
|
|
/* Rebuild address list from router discovery cache. */
|
|
nm_ip6_config_reset_addresses (priv->ac_ip6_config);
|
|
|
|
for (i = 0; i < rdisc->addresses->len; i++) {
|
|
NMRDiscAddress *discovered_address = &g_array_index (rdisc->addresses, NMRDiscAddress, i);
|
|
NMPlatformIP6Address address;
|
|
|
|
memset (&address, 0, sizeof (address));
|
|
address.address = discovered_address->address;
|
|
address.plen = 128;
|
|
address.timestamp = discovered_address->timestamp;
|
|
address.lifetime = discovered_address->lifetime;
|
|
address.preferred = discovered_address->preferred;
|
|
|
|
nm_ip6_config_add_address (priv->ac_ip6_config, &address);
|
|
}
|
|
}
|
|
|
|
if (changed & NM_RDISC_CONFIG_ROUTES) {
|
|
/* Rebuild route list from router discovery cache. */
|
|
nm_ip6_config_reset_routes (priv->ac_ip6_config);
|
|
|
|
for (i = 0; i < rdisc->routes->len; i++) {
|
|
NMRDiscRoute *discovered_route = &g_array_index (rdisc->routes, NMRDiscRoute, i);
|
|
NMPlatformIP6Route route;
|
|
|
|
/* Only accept non-default routes. The router has no idea what the
|
|
* local configuration or user preferences are, so sending routes
|
|
* with a prefix length of 0 is quite rude and thus ignored.
|
|
*/
|
|
if (discovered_route->plen > 0) {
|
|
memset (&route, 0, sizeof (route));
|
|
route.network = discovered_route->network;
|
|
route.plen = discovered_route->plen;
|
|
route.gateway = discovered_route->gateway;
|
|
|
|
nm_ip6_config_add_route (priv->ac_ip6_config, &route);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed & NM_RDISC_CONFIG_DNS_SERVERS) {
|
|
/* Rebuild DNS server list from router discovery cache. */
|
|
nm_ip6_config_reset_nameservers (priv->ac_ip6_config);
|
|
|
|
for (i = 0; i < rdisc->dns_servers->len; i++) {
|
|
NMRDiscDNSServer *discovered_server = &g_array_index (rdisc->dns_servers, NMRDiscDNSServer, i);
|
|
|
|
nm_ip6_config_add_nameserver (priv->ac_ip6_config, &discovered_server->address);
|
|
}
|
|
}
|
|
|
|
if (changed & NM_RDISC_CONFIG_DNS_DOMAINS) {
|
|
/* Rebuild domain list from router discovery cache. */
|
|
nm_ip6_config_reset_domains (priv->ac_ip6_config);
|
|
|
|
for (i = 0; i < rdisc->dns_domains->len; i++) {
|
|
NMRDiscDNSDomain *discovered_domain = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i);
|
|
|
|
nm_ip6_config_add_domain (priv->ac_ip6_config, discovered_domain->domain);
|
|
}
|
|
}
|
|
|
|
if (changed & NM_RDISC_CONFIG_DHCP_LEVEL) {
|
|
dhcp6_cleanup (device, TRUE, TRUE);
|
|
|
|
priv->dhcp6_mode = rdisc->dhcp_level;
|
|
|
|
switch (priv->dhcp6_mode) {
|
|
case NM_RDISC_DHCP_LEVEL_NONE:
|
|
break;
|
|
default:
|
|
nm_log_info (LOGD_DEVICE | LOGD_DHCP6,
|
|
"Activation (%s) Stage 3 of 5 (IP Configure Start) starting DHCPv6"
|
|
" as requested by IPv6 router...",
|
|
priv->iface);
|
|
switch (dhcp6_start (device, connection, priv->dhcp6_mode, &reason)) {
|
|
case NM_ACT_STAGE_RETURN_SUCCESS:
|
|
g_warn_if_reached ();
|
|
break;
|
|
case NM_ACT_STAGE_RETURN_POSTPONE:
|
|
return;
|
|
default:
|
|
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
nm_device_activate_schedule_ip6_config_result (device);
|
|
}
|
|
|
|
static gboolean
|
|
addrconf6_start (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMConnection *connection;
|
|
NMActStageReturn ret;
|
|
const char *ip_iface = nm_device_get_ip_iface (self);
|
|
|
|
connection = nm_device_get_connection (self);
|
|
g_assert (connection);
|
|
|
|
g_warn_if_fail (priv->ac_ip6_config == NULL);
|
|
if (priv->ac_ip6_config) {
|
|
g_object_unref (priv->ac_ip6_config);
|
|
priv->ac_ip6_config = NULL;
|
|
}
|
|
|
|
priv->rdisc = nm_lndp_rdisc_new (nm_device_get_ip_ifindex (self), ip_iface);
|
|
if (!priv->rdisc) {
|
|
nm_log_err (LOGD_IP6, "(%s): failed to start router discovery.", ip_iface);
|
|
return FALSE;
|
|
}
|
|
|
|
/* ensure link local is ready... */
|
|
ret = linklocal6_start (self);
|
|
if (ret == NM_ACT_STAGE_RETURN_SUCCESS)
|
|
addrconf6_start_with_link_ready (self);
|
|
else
|
|
g_return_val_if_fail (ret == NM_ACT_STAGE_RETURN_POSTPONE, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
addrconf6_start_with_link_ready (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
g_assert (priv->rdisc);
|
|
|
|
/* FIXME: what if interface has no lladdr, like PPP? */
|
|
if (priv->hw_addr_len)
|
|
nm_rdisc_set_lladdr (priv->rdisc, (const char *) priv->hw_addr, priv->hw_addr_len);
|
|
|
|
nm_platform_sysctl_set (priv->ip6_accept_ra_path, "0");
|
|
|
|
priv->rdisc_config_changed_sigid = g_signal_connect (priv->rdisc, NM_RDISC_CONFIG_CHANGED,
|
|
G_CALLBACK (rdisc_config_changed), self);
|
|
|
|
nm_rdisc_start (priv->rdisc);
|
|
}
|
|
|
|
static void
|
|
addrconf6_cleanup (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (priv->rdisc_config_changed_sigid) {
|
|
g_signal_handler_disconnect (priv->rdisc,
|
|
priv->rdisc_config_changed_sigid);
|
|
priv->rdisc_config_changed_sigid = 0;
|
|
}
|
|
|
|
g_clear_object (&priv->ac_ip6_config);
|
|
g_clear_object (&priv->rdisc);
|
|
}
|
|
|
|
/******************************************/
|
|
|
|
/* Get net.ipv6.conf.default.use_tempaddr value from /etc/sysctl.conf or
|
|
* /lib/sysctl.d/sysctl.conf
|
|
*/
|
|
static int
|
|
ip6_use_tempaddr (void)
|
|
{
|
|
char *contents = NULL;
|
|
const char *group_name = "[forged_group]\n";
|
|
char *sysctl_data = NULL;
|
|
GKeyFile *keyfile;
|
|
GError *error = NULL;
|
|
int tmp, ret = -1;
|
|
|
|
/* Read file contents to a string. */
|
|
if (!g_file_get_contents ("/etc/sysctl.conf", &contents, NULL, NULL))
|
|
if (!g_file_get_contents ("/lib/sysctl.d/sysctl.conf", &contents, NULL, NULL))
|
|
return -1;
|
|
|
|
/* Prepend a group so that we can use GKeyFile parser. */
|
|
sysctl_data = g_strdup_printf ("%s%s", group_name, contents);
|
|
|
|
keyfile = g_key_file_new ();
|
|
if (!g_key_file_load_from_data (keyfile, sysctl_data, -1, G_KEY_FILE_NONE, NULL))
|
|
goto done;
|
|
|
|
tmp = g_key_file_get_integer (keyfile, "forged_group", "net.ipv6.conf.default.use_tempaddr", &error);
|
|
if (error == NULL)
|
|
ret = tmp;
|
|
|
|
done:
|
|
g_free (contents);
|
|
g_free (sysctl_data);
|
|
g_clear_error (&error);
|
|
g_key_file_free (keyfile);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
ip6_requires_slaves (NMConnection *connection)
|
|
{
|
|
const char *method;
|
|
|
|
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
|
|
|
|
/* SLAAC, DHCP, and Link-Local depend on connectivity (and thus slaves)
|
|
* to complete addressing. SLAAC and DHCP obviously need a peer to
|
|
* provide a prefix, while Link-Local must perform DAD on the local link.
|
|
*/
|
|
return strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
|
|
|| strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0
|
|
|| strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0;
|
|
}
|
|
|
|
static NMActStageReturn
|
|
act_stage3_ip6_config_start (NMDevice *self,
|
|
NMIP6Config **out_config,
|
|
NMDeviceStateReason *reason)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
const char *ip_iface;
|
|
NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
|
|
NMConnection *connection;
|
|
const char *method;
|
|
int conf_use_tempaddr;
|
|
NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
|
|
const char *ip6_privacy_str = "0\n";
|
|
GSList *slaves;
|
|
gboolean ready_slaves;
|
|
|
|
g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
|
|
|
|
ip_iface = nm_device_get_ip_iface (self);
|
|
|
|
connection = nm_device_get_connection (self);
|
|
g_assert (connection);
|
|
|
|
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
|
|
if (priv->master)
|
|
g_assert_cmpstr (method, ==, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
|
|
|
|
if ( strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) != 0
|
|
&& nm_device_is_master (self)
|
|
&& nm_device_is_unavailable_because_of_carrier (self)) {
|
|
nm_log_info (LOGD_IP6 | LOGD_DEVICE,
|
|
"(%s): IPv6 config waiting until carrier is on", ip_iface);
|
|
return NM_ACT_STAGE_RETURN_WAIT;
|
|
}
|
|
|
|
if (priv->is_master && ip6_requires_slaves (connection)) {
|
|
/* If the master has no ready slaves, and depends on slaves for
|
|
* a successful IPv6 attempt, then postpone IPv6 addressing.
|
|
*/
|
|
slaves = nm_device_master_get_slaves (self);
|
|
ready_slaves = NM_DEVICE_GET_CLASS (self)->have_any_ready_slaves (self, slaves);
|
|
g_slist_free (slaves);
|
|
|
|
if (ready_slaves == FALSE) {
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP6,
|
|
"(%s): IPv6 config waiting until slaves are ready",
|
|
ip_iface);
|
|
return NM_ACT_STAGE_RETURN_WAIT;
|
|
}
|
|
}
|
|
|
|
priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE;
|
|
|
|
if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) {
|
|
if (!priv->master)
|
|
restore_ip6_properties (self);
|
|
return NM_ACT_STAGE_RETURN_STOP;
|
|
}
|
|
|
|
/* Re-enable IPv6 on the interface */
|
|
nm_platform_sysctl_set (priv->ip6_disable_ipv6_path, "0");
|
|
|
|
if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) {
|
|
if (!addrconf6_start (self)) {
|
|
/* IPv6 might be disabled; allow IPv4 to proceed */
|
|
ret = NM_ACT_STAGE_RETURN_STOP;
|
|
} else
|
|
ret = NM_ACT_STAGE_RETURN_POSTPONE;
|
|
} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) {
|
|
ret = linklocal6_start (self);
|
|
if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
|
|
/* New blank config; LL address is already in priv->ext_ip6_config */
|
|
*out_config = nm_ip6_config_new ();
|
|
g_assert (*out_config);
|
|
}
|
|
} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
|
|
priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_MANAGED;
|
|
ret = dhcp6_start (self, connection, priv->dhcp6_mode, reason);
|
|
} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) == 0) {
|
|
/* New blank config */
|
|
*out_config = nm_ip6_config_new ();
|
|
g_assert (*out_config);
|
|
|
|
ret = NM_ACT_STAGE_RETURN_SUCCESS;
|
|
} else {
|
|
nm_log_warn (LOGD_IP6, "(%s): unhandled IPv6 config method '%s'; will fail",
|
|
nm_device_get_ip_iface (self), method);
|
|
}
|
|
|
|
/* Other methods (shared) aren't implemented yet */
|
|
|
|
/* Enable/disable IPv6 Privacy Extensions.
|
|
* If a global value is configured by sysadmin (e.g. /etc/sysctl.conf),
|
|
* use that value instead of per-connection value.
|
|
*/
|
|
conf_use_tempaddr = ip6_use_tempaddr ();
|
|
if (conf_use_tempaddr >= 0)
|
|
ip6_privacy = conf_use_tempaddr;
|
|
else {
|
|
NMSettingIP6Config *s_ip6 = nm_connection_get_setting_ip6_config (connection);
|
|
|
|
if (s_ip6)
|
|
ip6_privacy = nm_setting_ip6_config_get_ip6_privacy (s_ip6);
|
|
}
|
|
ip6_privacy = CLAMP (ip6_privacy, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR);
|
|
|
|
switch (ip6_privacy) {
|
|
case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN:
|
|
case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
|
|
ip6_privacy_str = "0";
|
|
break;
|
|
case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
|
|
ip6_privacy_str = "1";
|
|
break;
|
|
case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
|
|
ip6_privacy_str = "2";
|
|
break;
|
|
}
|
|
nm_platform_sysctl_set (priv->ip6_use_tempaddr_path, ip6_privacy_str);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* nm_device_activate_stage3_ip4_start:
|
|
* @self: the device
|
|
*
|
|
* Try starting IPv4 configuration.
|
|
*/
|
|
gboolean
|
|
nm_device_activate_stage3_ip4_start (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMActStageReturn ret;
|
|
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
|
|
NMIP4Config *ip4_config = NULL;
|
|
|
|
g_assert (priv->ip4_state == IP_WAIT);
|
|
|
|
priv->ip4_state = IP_CONF;
|
|
ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip4_config_start (self, &ip4_config, &reason);
|
|
if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
|
|
g_assert (ip4_config);
|
|
nm_device_activate_schedule_ip4_config_result (self, ip4_config);
|
|
g_object_unref (ip4_config);
|
|
} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
|
|
return FALSE;
|
|
} else if (ret == NM_ACT_STAGE_RETURN_STOP) {
|
|
/* Early finish */
|
|
priv->ip4_state = IP_FAIL;
|
|
} else if (ret == NM_ACT_STAGE_RETURN_WAIT) {
|
|
/* Wait for something to try IP config again */
|
|
priv->ip4_state = IP_WAIT;
|
|
} else
|
|
g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* nm_device_activate_stage3_ip6_start:
|
|
* @self: the device
|
|
*
|
|
* Try starting IPv6 configuration.
|
|
*/
|
|
gboolean
|
|
nm_device_activate_stage3_ip6_start (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMActStageReturn ret;
|
|
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
|
|
NMIP6Config *ip6_config = NULL;
|
|
|
|
g_assert (priv->ip6_state == IP_WAIT);
|
|
|
|
priv->ip6_state = IP_CONF;
|
|
ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip6_config_start (self, &ip6_config, &reason);
|
|
if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
|
|
g_assert (ip6_config);
|
|
/* Here we get a static IPv6 config, like for Shared where it's
|
|
* autogenerated or from modems where it comes from ModemManager.
|
|
*/
|
|
g_warn_if_fail (priv->ac_ip6_config == NULL);
|
|
priv->ac_ip6_config = ip6_config;
|
|
nm_device_activate_schedule_ip6_config_result (self);
|
|
} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
|
|
return FALSE;
|
|
} else if (ret == NM_ACT_STAGE_RETURN_STOP) {
|
|
/* Early finish */
|
|
priv->ip6_state = IP_FAIL;
|
|
} else if (ret == NM_ACT_STAGE_RETURN_WAIT) {
|
|
/* Wait for something to try IP config again */
|
|
priv->ip6_state = IP_WAIT;
|
|
} else
|
|
g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* nm_device_activate_stage3_ip_config_start
|
|
*
|
|
* Begin automatic/manual IP configuration
|
|
*
|
|
*/
|
|
static gboolean
|
|
nm_device_activate_stage3_ip_config_start (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
const char *iface;
|
|
NMActiveConnection *master;
|
|
NMDevice *master_device;
|
|
|
|
/* Clear the activation source ID now that this stage has run */
|
|
activation_source_clear (self, FALSE, 0);
|
|
|
|
priv->ip4_state = priv->ip6_state = IP_WAIT;
|
|
|
|
iface = nm_device_get_iface (self);
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) started...", iface);
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
|
|
|
|
/* Device should be up before we can do anything with it */
|
|
if (!nm_platform_link_is_up (nm_device_get_ip_ifindex (self))) {
|
|
nm_log_warn (LOGD_DEVICE, "(%s): interface %s not up for IP configuration",
|
|
iface, nm_device_get_ip_iface (self));
|
|
}
|
|
|
|
/* If the device is a slave, then we don't do any IP configuration but we
|
|
* use the IP config stage to indicate to the master we're ready for
|
|
* enslavement. If the master is already activating, it will have tried to
|
|
* enslave us when we changed state to IP_CONFIG, causing us to queue a
|
|
* transition to SECONDARIES (or FAILED if the enslavement failed), with
|
|
* our IP states set to IP_DONE either way. If the master isn't yet
|
|
* activating, then they'll still be in IP_WAIT. Either way, we bail out
|
|
* of IP config here.
|
|
*/
|
|
master = nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request));
|
|
if (master) {
|
|
master_device = nm_active_connection_get_device (master);
|
|
if (priv->ip4_state == IP_WAIT && priv->ip6_state == IP_WAIT) {
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) connection '%s' waiting on master '%s'",
|
|
nm_device_get_iface (self),
|
|
nm_connection_get_id (nm_device_get_connection (self)),
|
|
nm_device_get_iface (master_device));
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
/* IPv4 */
|
|
if (!nm_device_activate_stage3_ip4_start (self))
|
|
goto out;
|
|
|
|
/* IPv6 */
|
|
if (!nm_device_activate_stage3_ip6_start (self))
|
|
goto out;
|
|
|
|
if (priv->ip4_state == IP_FAIL && priv->ip6_state == IP_FAIL) {
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED,
|
|
NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
|
|
}
|
|
|
|
out:
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) complete.", iface);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
fw_add_to_zone_cb (GError *error, gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
priv->fw_call = NULL;
|
|
|
|
if (error) {
|
|
/* FIXME: fail the device activation? */
|
|
}
|
|
|
|
activation_source_schedule (self, nm_device_activate_stage3_ip_config_start, 0);
|
|
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) scheduled.",
|
|
nm_device_get_iface (self));
|
|
}
|
|
|
|
/*
|
|
* nm_device_activate_schedule_stage3_ip_config_start
|
|
*
|
|
* Schedule IP configuration start
|
|
*/
|
|
void
|
|
nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
NMConnection *connection;
|
|
NMSettingConnection *s_con = NULL;
|
|
NMDeviceState state;
|
|
const char *zone;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
g_return_if_fail (priv->act_request);
|
|
|
|
state = nm_device_get_state (self);
|
|
|
|
/* Add the interface to the specified firewall zone */
|
|
connection = nm_device_get_connection (self);
|
|
g_assert (connection);
|
|
s_con = nm_connection_get_setting_connection (connection);
|
|
|
|
zone = nm_setting_connection_get_zone (s_con);
|
|
nm_log_dbg (LOGD_DEVICE, "Activation (%s) setting firewall zone '%s'",
|
|
nm_device_get_iface (self), zone ? zone : "default");
|
|
priv->fw_call = nm_firewall_manager_add_or_change_zone (priv->fw_manager,
|
|
nm_device_get_ip_iface (self),
|
|
zone,
|
|
TRUE,
|
|
fw_add_to_zone_cb,
|
|
self);
|
|
}
|
|
|
|
static NMActStageReturn
|
|
act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
|
|
{
|
|
if (nm_device_ip_config_should_fail (self, FALSE)) {
|
|
*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
|
|
return NM_ACT_STAGE_RETURN_FAILURE;
|
|
}
|
|
return NM_ACT_STAGE_RETURN_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* nm_device_activate_stage4_ip4_config_timeout
|
|
*
|
|
* Time out on retrieving the IPv4 config.
|
|
*
|
|
*/
|
|
static gboolean
|
|
nm_device_activate_ip4_config_timeout (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
const char *iface;
|
|
NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
|
|
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
|
|
|
|
/* Clear the activation source ID now that this stage has run */
|
|
activation_source_clear (self, FALSE, AF_INET);
|
|
|
|
iface = nm_device_get_iface (self);
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP4,
|
|
"Activation (%s) Stage 4 of 5 (IPv4 Configure Timeout) started...",
|
|
iface);
|
|
|
|
ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip4_config_timeout (self, &reason);
|
|
if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
|
|
goto out;
|
|
else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
|
|
goto out;
|
|
}
|
|
g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
|
|
|
|
priv->ip4_state = IP_FAIL;
|
|
|
|
/* If IPv4 failed and IPv6 failed, the activation fails */
|
|
if (priv->ip6_state == IP_FAIL)
|
|
nm_device_state_changed (self,
|
|
NM_DEVICE_STATE_FAILED,
|
|
NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
|
|
|
|
out:
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP4,
|
|
"Activation (%s) Stage 4 of 5 (IPv4 Configure Timeout) complete.",
|
|
iface);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
* nm_device_activate_schedule_ip4_config_timeout
|
|
*
|
|
* Deal with a timeout of the IPv4 configuration
|
|
*
|
|
*/
|
|
void
|
|
nm_device_activate_schedule_ip4_config_timeout (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
g_return_if_fail (priv->act_request);
|
|
|
|
activation_source_schedule (self, nm_device_activate_ip4_config_timeout, AF_INET);
|
|
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP4,
|
|
"Activation (%s) Stage 4 of 5 (IPv4 Configure Timeout) scheduled...",
|
|
nm_device_get_iface (self));
|
|
}
|
|
|
|
|
|
static NMActStageReturn
|
|
act_stage4_ip6_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
|
|
{
|
|
if (nm_device_ip_config_should_fail (self, TRUE)) {
|
|
*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
|
|
return NM_ACT_STAGE_RETURN_FAILURE;
|
|
}
|
|
|
|
return NM_ACT_STAGE_RETURN_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* nm_device_activate_ip6_config_timeout
|
|
*
|
|
* Time out on retrieving the IPv6 config.
|
|
*
|
|
*/
|
|
static gboolean
|
|
nm_device_activate_ip6_config_timeout (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
const char *iface;
|
|
NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
|
|
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
|
|
|
|
/* Clear the activation source ID now that this stage has run */
|
|
activation_source_clear (self, FALSE, AF_INET6);
|
|
|
|
iface = nm_device_get_iface (self);
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP6,
|
|
"Activation (%s) Stage 4 of 5 (IPv6 Configure Timeout) started...",
|
|
iface);
|
|
|
|
ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip6_config_timeout (self, &reason);
|
|
if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
|
|
goto out;
|
|
else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
|
|
goto out;
|
|
}
|
|
g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
|
|
|
|
priv->ip6_state = IP_FAIL;
|
|
|
|
/* If IPv6 failed and IPv4 failed, the activation fails */
|
|
if (priv->ip4_state == IP_FAIL)
|
|
nm_device_state_changed (self,
|
|
NM_DEVICE_STATE_FAILED,
|
|
NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
|
|
|
|
out:
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP6,
|
|
"Activation (%s) Stage 4 of 5 (IPv6 Configure Timeout) complete.",
|
|
iface);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
* nm_device_activate_schedule_ip6_config_timeout
|
|
*
|
|
* Deal with a timeout of the IPv6 configuration
|
|
*
|
|
*/
|
|
void
|
|
nm_device_activate_schedule_ip6_config_timeout (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
g_return_if_fail (priv->act_request);
|
|
|
|
activation_source_schedule (self, nm_device_activate_ip6_config_timeout, AF_INET6);
|
|
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP6,
|
|
"Activation (%s) Stage 4 of 5 (IPv6 Configure Timeout) scheduled...",
|
|
nm_device_get_iface (self));
|
|
}
|
|
|
|
static void
|
|
share_child_setup (gpointer user_data G_GNUC_UNUSED)
|
|
{
|
|
/* We are in the child process at this point */
|
|
pid_t pid = getpid ();
|
|
setpgid (pid, pid);
|
|
|
|
nm_unblock_posix_signals (NULL);
|
|
}
|
|
|
|
static gboolean
|
|
share_init (void)
|
|
{
|
|
int status;
|
|
char *modules[] = { "ip_tables", "iptable_nat", "nf_nat_ftp", "nf_nat_irc",
|
|
"nf_nat_sip", "nf_nat_tftp", "nf_nat_pptp", "nf_nat_h323",
|
|
NULL };
|
|
char **iter;
|
|
|
|
if (!nm_platform_sysctl_set ("/proc/sys/net/ipv4/ip_forward", "1")) {
|
|
nm_log_err (LOGD_SHARING, "Error starting IP forwarding: (%d) %s",
|
|
errno, strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!nm_platform_sysctl_set ("/proc/sys/net/ipv4/ip_dynaddr", "1")) {
|
|
nm_log_err (LOGD_SHARING, "error starting IP forwarding: (%d) %s",
|
|
errno, strerror (errno));
|
|
}
|
|
|
|
for (iter = modules; *iter; iter++) {
|
|
char *argv[3] = { "/sbin/modprobe", *iter, NULL };
|
|
char *envp[1] = { NULL };
|
|
GError *error = NULL;
|
|
|
|
if (!g_spawn_sync ("/", argv, envp, G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
|
|
share_child_setup, NULL, NULL, NULL, &status, &error)) {
|
|
nm_log_err (LOGD_SHARING, "error loading NAT module %s: (%d) %s",
|
|
*iter, error ? error->code : 0,
|
|
(error && error->message) ? error->message : "unknown");
|
|
if (error)
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
add_share_rule (NMActRequest *req, const char *table, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
char *cmd;
|
|
|
|
va_start (args, fmt);
|
|
cmd = g_strdup_vprintf (fmt, args);
|
|
va_end (args);
|
|
|
|
nm_act_request_add_share_rule (req, table, cmd);
|
|
g_free (cmd);
|
|
}
|
|
|
|
static gboolean
|
|
start_sharing (NMDevice *self, NMIP4Config *config)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMActRequest *req;
|
|
GError *error = NULL;
|
|
char str_addr[INET_ADDRSTRLEN + 1];
|
|
char str_mask[INET_ADDRSTRLEN + 1];
|
|
guint32 netmask, network;
|
|
const NMPlatformIP4Address *ip4_addr;
|
|
const char *ip_iface;
|
|
|
|
g_return_val_if_fail (config != NULL, FALSE);
|
|
|
|
ip_iface = nm_device_get_ip_iface (self);
|
|
|
|
ip4_addr = nm_ip4_config_get_address (config, 0);
|
|
if (!ip4_addr || !ip4_addr->address)
|
|
return FALSE;
|
|
|
|
netmask = nm_utils_ip4_prefix_to_netmask (ip4_addr->plen);
|
|
if (!inet_ntop (AF_INET, &netmask, str_mask, sizeof (str_mask)))
|
|
return FALSE;
|
|
|
|
network = ip4_addr->address & netmask;
|
|
if (!inet_ntop (AF_INET, &network, str_addr, sizeof (str_addr)))
|
|
return FALSE;
|
|
|
|
if (!share_init ())
|
|
return FALSE;
|
|
|
|
req = nm_device_get_act_request (self);
|
|
g_assert (req);
|
|
|
|
add_share_rule (req, "filter", "INPUT --in-interface %s --protocol tcp --destination-port 53 --jump ACCEPT", ip_iface);
|
|
add_share_rule (req, "filter", "INPUT --in-interface %s --protocol udp --destination-port 53 --jump ACCEPT", ip_iface);
|
|
add_share_rule (req, "filter", "INPUT --in-interface %s --protocol tcp --destination-port 67 --jump ACCEPT", ip_iface);
|
|
add_share_rule (req, "filter", "INPUT --in-interface %s --protocol udp --destination-port 67 --jump ACCEPT", ip_iface);
|
|
add_share_rule (req, "filter", "FORWARD --in-interface %s --jump REJECT", ip_iface);
|
|
add_share_rule (req, "filter", "FORWARD --out-interface %s --jump REJECT", ip_iface);
|
|
add_share_rule (req, "filter", "FORWARD --in-interface %s --out-interface %s --jump ACCEPT", ip_iface, ip_iface);
|
|
add_share_rule (req, "filter", "FORWARD --source %s/%s --in-interface %s --jump ACCEPT", str_addr, str_mask, ip_iface);
|
|
add_share_rule (req, "filter", "FORWARD --destination %s/%s --out-interface %s --match state --state ESTABLISHED,RELATED --jump ACCEPT", str_addr, str_mask, ip_iface);
|
|
add_share_rule (req, "nat", "POSTROUTING --source %s/%s ! --destination %s/%s --jump MASQUERADE", str_addr, str_mask, str_addr, str_mask);
|
|
|
|
nm_act_request_set_shared (req, TRUE);
|
|
|
|
if (!nm_dnsmasq_manager_start (priv->dnsmasq_manager, config, &error)) {
|
|
nm_log_err (LOGD_SHARING, "(%s/%s): failed to start dnsmasq: %s",
|
|
nm_device_get_iface (self), ip_iface,
|
|
(error && error->message) ? error->message : "(unknown)");
|
|
g_error_free (error);
|
|
nm_act_request_set_shared (req, FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
priv->dnsmasq_state_id = g_signal_connect (priv->dnsmasq_manager, "state-changed",
|
|
G_CALLBACK (dnsmasq_state_changed_cb),
|
|
self);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
nm_device_activate_ip4_config_commit (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMActRequest *req;
|
|
const char *iface, *method;
|
|
NMConnection *connection;
|
|
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
|
|
|
|
/* Clear the activation source ID now that this stage has run */
|
|
activation_source_clear (self, FALSE, AF_INET);
|
|
|
|
iface = nm_device_get_iface (self);
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IPv4 Commit) started...",
|
|
iface);
|
|
|
|
req = nm_device_get_act_request (self);
|
|
g_assert (req);
|
|
connection = nm_act_request_get_connection (req);
|
|
g_assert (connection);
|
|
|
|
/* Device should be up before we can do anything with it */
|
|
if (!nm_platform_link_is_up (nm_device_get_ip_ifindex (self))) {
|
|
nm_log_warn (LOGD_DEVICE, "(%s): interface %s not up for IP configuration",
|
|
iface, nm_device_get_ip_iface (self));
|
|
}
|
|
|
|
/* NULL to use the existing priv->dev_ip4_config */
|
|
if (!ip4_config_merge_and_apply (self, NULL, TRUE, &reason)) {
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP4,
|
|
"Activation (%s) Stage 5 of 5 (IPv4 Commit) failed",
|
|
iface);
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
|
|
goto out;
|
|
}
|
|
|
|
/* Start IPv4 sharing if we need it */
|
|
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
|
|
|
|
if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) {
|
|
if (!start_sharing (self, priv->ip4_config)) {
|
|
nm_log_warn (LOGD_SHARING, "Activation (%s) Stage 5 of 5 (IPv4 Commit) start sharing failed.", iface);
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Enter the IP_CHECK state if this is the first method to complete */
|
|
priv->ip4_state = IP_DONE;
|
|
if (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG)
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
|
|
|
|
out:
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IPv4 Commit) complete.",
|
|
iface);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *config)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
g_return_if_fail (NM_IS_IP4_CONFIG (config));
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
g_clear_object (&priv->dev_ip4_config);
|
|
priv->dev_ip4_config = g_object_ref (config);
|
|
|
|
activation_source_schedule (self, nm_device_activate_ip4_config_commit, AF_INET);
|
|
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP4,
|
|
"Activation (%s) Stage 5 of 5 (IPv4 Configure Commit) scheduled...",
|
|
nm_device_get_iface (self));
|
|
}
|
|
|
|
gboolean
|
|
nm_device_activate_ip4_state_in_conf (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_CONF;
|
|
}
|
|
|
|
gboolean
|
|
nm_device_activate_ip4_state_in_wait (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT;
|
|
}
|
|
|
|
static gboolean
|
|
nm_device_activate_ip6_config_commit (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMActRequest *req;
|
|
const char *iface;
|
|
NMConnection *connection;
|
|
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
|
|
|
|
/* Clear the activation source ID now that this stage has run */
|
|
activation_source_clear (self, FALSE, AF_INET6);
|
|
|
|
iface = nm_device_get_iface (self);
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IPv6 Commit) started...",
|
|
iface);
|
|
|
|
req = nm_device_get_act_request (self);
|
|
g_assert (req);
|
|
connection = nm_act_request_get_connection (req);
|
|
g_assert (connection);
|
|
|
|
/* Device should be up before we can do anything with it */
|
|
g_warn_if_fail (nm_platform_link_is_up (nm_device_get_ip_ifindex (self)));
|
|
|
|
/* Allow setting MTU etc */
|
|
if (NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit)
|
|
NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit (self);
|
|
|
|
if (ip6_config_merge_and_apply (self, TRUE, &reason)) {
|
|
/* Enter the IP_CHECK state if this is the first method to complete */
|
|
priv->ip6_state = IP_DONE;
|
|
if (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG)
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
|
|
} else {
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP6,
|
|
"Activation (%s) Stage 5 of 5 (IPv6 Commit) failed",
|
|
iface);
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
|
|
}
|
|
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IPv6 Commit) complete.",
|
|
iface);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
nm_device_activate_schedule_ip6_config_result (NMDevice *self)
|
|
{
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
activation_source_schedule (self, nm_device_activate_ip6_config_commit, AF_INET6);
|
|
|
|
nm_log_info (LOGD_DEVICE | LOGD_IP6,
|
|
"Activation (%s) Stage 5 of 5 (IPv6 Commit) scheduled...",
|
|
nm_device_get_iface (self));
|
|
}
|
|
|
|
gboolean
|
|
nm_device_activate_ip6_state_in_conf (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_CONF;
|
|
}
|
|
|
|
gboolean
|
|
nm_device_activate_ip6_state_in_wait (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT;
|
|
}
|
|
|
|
static void
|
|
clear_act_request (NMDevice *self)
|
|
{
|
|
NMDevicePrivate * priv;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (!priv->act_request)
|
|
return;
|
|
|
|
nm_active_connection_set_default (NM_ACTIVE_CONNECTION (priv->act_request), FALSE);
|
|
|
|
if (priv->master_ready_id) {
|
|
g_signal_handler_disconnect (priv->act_request, priv->master_ready_id);
|
|
priv->master_ready_id = 0;
|
|
}
|
|
|
|
g_object_unref (priv->act_request);
|
|
priv->act_request = NULL;
|
|
}
|
|
|
|
static void
|
|
dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (priv->dhcp4_config) {
|
|
g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG);
|
|
g_object_unref (priv->dhcp4_config);
|
|
priv->dhcp4_config = NULL;
|
|
}
|
|
|
|
if (priv->dhcp4_client) {
|
|
/* Stop any ongoing DHCP transaction on this device */
|
|
if (priv->dhcp4_state_sigid) {
|
|
g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_state_sigid);
|
|
priv->dhcp4_state_sigid = 0;
|
|
}
|
|
|
|
if (priv->dhcp4_timeout_sigid) {
|
|
g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_timeout_sigid);
|
|
priv->dhcp4_timeout_sigid = 0;
|
|
}
|
|
|
|
if (stop)
|
|
nm_dhcp_client_stop (priv->dhcp4_client, release);
|
|
|
|
g_object_unref (priv->dhcp4_client);
|
|
priv->dhcp4_client = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dhcp6_cleanup (NMDevice *self, gboolean stop, gboolean release)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE;
|
|
|
|
if (priv->dhcp6_ip6_config) {
|
|
g_object_unref (priv->dhcp6_ip6_config);
|
|
priv->dhcp6_ip6_config = NULL;
|
|
}
|
|
|
|
if (priv->dhcp6_config) {
|
|
g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG);
|
|
g_object_unref (priv->dhcp6_config);
|
|
priv->dhcp6_config = NULL;
|
|
}
|
|
|
|
if (priv->dhcp6_client) {
|
|
if (priv->dhcp6_state_sigid) {
|
|
g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_state_sigid);
|
|
priv->dhcp6_state_sigid = 0;
|
|
}
|
|
|
|
if (priv->dhcp6_timeout_sigid) {
|
|
g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_timeout_sigid);
|
|
priv->dhcp6_timeout_sigid = 0;
|
|
}
|
|
|
|
if (stop)
|
|
nm_dhcp_client_stop (priv->dhcp6_client, release);
|
|
|
|
g_object_unref (priv->dhcp6_client);
|
|
priv->dhcp6_client = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dnsmasq_cleanup (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (!priv->dnsmasq_manager)
|
|
return;
|
|
|
|
if (priv->dnsmasq_state_id) {
|
|
g_signal_handler_disconnect (priv->dnsmasq_manager, priv->dnsmasq_state_id);
|
|
priv->dnsmasq_state_id = 0;
|
|
}
|
|
|
|
nm_dnsmasq_manager_stop (priv->dnsmasq_manager);
|
|
g_object_unref (priv->dnsmasq_manager);
|
|
priv->dnsmasq_manager = NULL;
|
|
}
|
|
|
|
static void
|
|
_update_ip4_address (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
struct ifreq req;
|
|
guint32 new_address;
|
|
int fd;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
|
|
fd = socket (PF_INET, SOCK_DGRAM, 0);
|
|
if (fd < 0) {
|
|
nm_log_err (LOGD_IP4, "couldn't open control socket.");
|
|
return;
|
|
}
|
|
|
|
memset (&req, 0, sizeof (struct ifreq));
|
|
strncpy (req.ifr_name, nm_device_get_ip_iface (self), IFNAMSIZ);
|
|
if (ioctl (fd, SIOCGIFADDR, &req) == 0) {
|
|
new_address = ((struct sockaddr_in *)(&req.ifr_addr))->sin_addr.s_addr;
|
|
if (new_address != priv->ip4_address)
|
|
priv->ip4_address = new_address;
|
|
}
|
|
close (fd);
|
|
}
|
|
|
|
|
|
/*
|
|
* delete_on_deactivate_link_delete
|
|
*
|
|
* Function will be queued with g_idle_add to call
|
|
* nm_platform_link_delete for the underlying resources
|
|
* of the device.
|
|
*/
|
|
static gboolean
|
|
delete_on_deactivate_link_delete (gpointer user_data)
|
|
{
|
|
int ifindex = GPOINTER_TO_INT (user_data);
|
|
|
|
nm_log_dbg (LOGD_DEVICE, "device deactivated: cleanup and delete virtual link #%d", ifindex);
|
|
nm_platform_link_delete (ifindex);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
delete_on_deactivate_unschedule (NMDevicePrivate *priv)
|
|
{
|
|
if (priv->delete_on_deactivate_id) {
|
|
g_source_remove (priv->delete_on_deactivate_id);
|
|
priv->delete_on_deactivate_id = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
if (ifindex <= 0)
|
|
return;
|
|
if (!nm_device_get_is_nm_owned (self))
|
|
return;
|
|
if (!nm_device_is_software (self))
|
|
return;
|
|
if (nm_device_get_state (self) == NM_DEVICE_STATE_UNMANAGED)
|
|
return;
|
|
if (nm_device_get_state (self) == NM_DEVICE_STATE_UNAVAILABLE)
|
|
return;
|
|
nm_log_dbg (LOGD_DEVICE, "Schedule cleanup and delete virtual link #%d for [%s]",
|
|
ifindex, nm_device_get_iface (self));
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
delete_on_deactivate_unschedule (priv); /* always cancel and reschedule */
|
|
priv->delete_on_deactivate_id = g_idle_add (delete_on_deactivate_link_delete, GINT_TO_POINTER (ifindex));
|
|
}
|
|
|
|
gboolean
|
|
nm_device_get_is_nm_owned (NMDevice *device)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
|
|
return NM_DEVICE_GET_PRIVATE (device)->is_nm_owned;
|
|
}
|
|
|
|
gboolean
|
|
nm_device_set_is_nm_owned (NMDevice *device,
|
|
gboolean is_nm_owned)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
if (is_nm_owned == priv->is_nm_owned)
|
|
return TRUE;
|
|
if (!is_nm_owned)
|
|
return FALSE;
|
|
priv->is_nm_owned = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* nm_device_deactivate
|
|
*
|
|
* Remove a device's routing table entries and IP address.
|
|
*
|
|
*/
|
|
static void
|
|
nm_device_deactivate (NMDevice *self, NMDeviceStateReason reason)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE;
|
|
NMConnection *connection = NULL;
|
|
NMSettingConnection *s_con = NULL;
|
|
int ifindex;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
nm_log_info (LOGD_DEVICE, "(%s): deactivating device (reason '%s') [%d]",
|
|
nm_device_get_iface (self), reason_to_string (reason), reason);
|
|
|
|
/* Save whether or not we tried IPv6 for later */
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
/* Clean up when device was deactivated during call to firewall */
|
|
if (priv->fw_call) {
|
|
nm_firewall_manager_cancel_call (priv->fw_manager, priv->fw_call);
|
|
priv->fw_call = NULL;
|
|
}
|
|
|
|
if (priv->act_request)
|
|
connection = nm_act_request_get_connection (priv->act_request);
|
|
if (connection) {
|
|
s_con = nm_connection_get_setting_connection (connection);
|
|
nm_firewall_manager_remove_from_zone (priv->fw_manager,
|
|
nm_device_get_ip_iface (self),
|
|
nm_setting_connection_get_zone (s_con));
|
|
}
|
|
|
|
ip_check_gw_ping_cleanup (self);
|
|
|
|
/* Break the activation chain */
|
|
activation_source_clear (self, TRUE, AF_INET);
|
|
activation_source_clear (self, TRUE, AF_INET6);
|
|
|
|
/* Clear any queued transitions */
|
|
nm_device_queued_state_clear (self);
|
|
nm_device_queued_ip_config_change_clear (self);
|
|
|
|
priv->ip4_state = priv->ip6_state = IP_NONE;
|
|
|
|
dhcp4_cleanup (self, TRUE, FALSE);
|
|
dhcp6_cleanup (self, TRUE, FALSE);
|
|
linklocal6_cleanup (self);
|
|
addrconf6_cleanup (self);
|
|
dnsmasq_cleanup (self);
|
|
aipd_cleanup (self);
|
|
|
|
/* Turn off kernel IPv6 */
|
|
nm_platform_sysctl_set (priv->ip6_disable_ipv6_path, "1");
|
|
nm_platform_sysctl_set (priv->ip6_accept_ra_path, "0");
|
|
nm_platform_sysctl_set (priv->ip6_use_tempaddr_path, "0");
|
|
|
|
/* Call device type-specific deactivation */
|
|
if (NM_DEVICE_GET_CLASS (self)->deactivate)
|
|
NM_DEVICE_GET_CLASS (self)->deactivate (self);
|
|
|
|
/* master: release slaves */
|
|
nm_device_master_release_slaves (self);
|
|
|
|
/* slave: mark no longer enslaved */
|
|
g_clear_object (&priv->master);
|
|
priv->enslaved = FALSE;
|
|
g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER);
|
|
|
|
/* Tear down an existing activation request */
|
|
clear_act_request (self);
|
|
|
|
/* Take out any entries in the routing table and any IP address the device had. */
|
|
ifindex = nm_device_get_ip_ifindex (self);
|
|
if (ifindex > 0) {
|
|
nm_platform_route_flush (ifindex);
|
|
nm_platform_address_flush (ifindex);
|
|
}
|
|
|
|
/* Clean up nameservers and addresses */
|
|
nm_device_set_ip4_config (self, NULL, TRUE, &ignored);
|
|
nm_device_set_ip6_config (self, NULL, TRUE, &ignored);
|
|
g_clear_object (&priv->ext_ip4_config);
|
|
g_clear_object (&priv->vpn4_config);
|
|
g_clear_object (&priv->vpn6_config);
|
|
g_clear_object (&priv->ext_ip6_config);
|
|
|
|
/* Clear legacy IPv4 address property */
|
|
priv->ip4_address = 0;
|
|
g_object_notify (G_OBJECT (self), NM_DEVICE_IP4_ADDRESS);
|
|
|
|
/* Only clear ip_iface after flushing all routes and addreses, since
|
|
* those are identified by ip_iface, not by iface (which might be a tty
|
|
* or ATM device).
|
|
*/
|
|
nm_device_set_ip_iface (self, NULL);
|
|
|
|
/* Check if the device was deactivated, and if so, delete_link.
|
|
* Don't call delete_link synchronously because we are currently
|
|
* handling a state change -- which is not reentrant. */
|
|
delete_on_deactivate_check_and_schedule (self, ifindex);
|
|
}
|
|
|
|
static void
|
|
disconnect_cb (NMDevice *device,
|
|
DBusGMethodInvocation *context,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
GError *local = NULL;
|
|
|
|
if (error) {
|
|
dbus_g_method_return_error (context, error);
|
|
return;
|
|
}
|
|
|
|
/* Authorized */
|
|
if (priv->state <= NM_DEVICE_STATE_DISCONNECTED) {
|
|
local = g_error_new_literal (NM_DEVICE_ERROR,
|
|
NM_DEVICE_ERROR_NOT_ACTIVE,
|
|
"Device is not active");
|
|
dbus_g_method_return_error (context, local);
|
|
g_error_free (local);
|
|
} else {
|
|
priv->autoconnect = FALSE;
|
|
|
|
nm_device_state_changed (device,
|
|
NM_DEVICE_STATE_DEACTIVATING,
|
|
NM_DEVICE_STATE_REASON_USER_REQUESTED);
|
|
dbus_g_method_return (context);
|
|
}
|
|
}
|
|
|
|
static void
|
|
impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context)
|
|
{
|
|
NMConnection *connection;
|
|
GError *error = NULL;
|
|
|
|
if (NM_DEVICE_GET_PRIVATE (device)->act_request == NULL) {
|
|
error = g_error_new_literal (NM_DEVICE_ERROR,
|
|
NM_DEVICE_ERROR_NOT_ACTIVE,
|
|
"This device is not active");
|
|
dbus_g_method_return_error (context, error);
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
connection = nm_device_get_connection (device);
|
|
g_assert (connection);
|
|
|
|
/* Ask the manager to authenticate this request for us */
|
|
g_signal_emit (device, signals[AUTH_REQUEST], 0,
|
|
context,
|
|
connection,
|
|
NM_AUTH_PERMISSION_NETWORK_CONTROL,
|
|
TRUE,
|
|
disconnect_cb,
|
|
NULL);
|
|
}
|
|
|
|
void
|
|
nm_device_activate (NMDevice *self, NMActRequest *req)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
NMConnection *connection;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
g_return_if_fail (NM_IS_ACT_REQUEST (req));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
connection = nm_act_request_get_connection (req);
|
|
g_assert (connection);
|
|
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) starting connection '%s'",
|
|
nm_device_get_iface (self),
|
|
nm_connection_get_id (connection));
|
|
|
|
/* Move default unmanaged devices to DISCONNECTED state here */
|
|
if (priv->default_unmanaged && priv->state == NM_DEVICE_STATE_UNMANAGED) {
|
|
nm_device_state_changed (self,
|
|
NM_DEVICE_STATE_DISCONNECTED,
|
|
NM_DEVICE_STATE_REASON_NOW_MANAGED);
|
|
}
|
|
|
|
priv->act_request = g_object_ref (req);
|
|
g_object_notify (G_OBJECT (self), NM_DEVICE_ACTIVE_CONNECTION);
|
|
|
|
/* HACK: update the state a bit early to avoid a race between the
|
|
* scheduled stage1 handler and nm_policy_device_change_check() thinking
|
|
* that the activation request isn't deferred because the deferred bit
|
|
* gets cleared a bit too early, when the connection becomes valid.
|
|
*/
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
|
|
|
|
nm_device_activate_schedule_stage1_device_prepare (self);
|
|
}
|
|
|
|
/*
|
|
* nm_device_is_activating
|
|
*
|
|
* Return whether or not the device is currently activating itself.
|
|
*
|
|
*/
|
|
gboolean
|
|
nm_device_is_activating (NMDevice *device)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
NMDeviceState state;
|
|
|
|
g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
|
|
|
|
state = nm_device_get_state (device);
|
|
if (state >= NM_DEVICE_STATE_PREPARE && state <= NM_DEVICE_STATE_SECONDARIES)
|
|
return TRUE;
|
|
|
|
/* There's a small race between the time when stage 1 is scheduled
|
|
* and when the device actually sets STATE_PREPARE when the activation
|
|
* handler is actually run. If there's an activation handler scheduled
|
|
* we're activating anyway.
|
|
*/
|
|
return priv->act_source_id ? TRUE : FALSE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
can_interrupt_activation (NMDevice *device)
|
|
{
|
|
/* Devices that support carrier detect can interrupt activation
|
|
* if the link becomes inactive.
|
|
*/
|
|
return nm_device_is_unavailable_because_of_carrier (device);
|
|
}
|
|
|
|
gboolean
|
|
nm_device_can_interrupt_activation (NMDevice *self)
|
|
{
|
|
gboolean interrupt = FALSE;
|
|
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
|
|
if (NM_DEVICE_GET_CLASS (self)->can_interrupt_activation)
|
|
interrupt = NM_DEVICE_GET_CLASS (self)->can_interrupt_activation (self);
|
|
return interrupt;
|
|
}
|
|
|
|
/* IP Configuration stuff */
|
|
|
|
NMDHCP4Config *
|
|
nm_device_get_dhcp4_config (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->dhcp4_config;
|
|
}
|
|
|
|
NMIP4Config *
|
|
nm_device_get_ip4_config (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->ip4_config;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
nm_device_set_ip4_config (NMDevice *self,
|
|
NMIP4Config *new_config,
|
|
gboolean commit,
|
|
NMDeviceStateReason *reason)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
const char *ip_iface;
|
|
NMIP4Config *old_config = NULL;
|
|
gboolean has_changes = FALSE;
|
|
gboolean success = TRUE;
|
|
NMDeviceStateReason reason_local = NM_DEVICE_STATE_REASON_NONE;
|
|
int ip_ifindex;
|
|
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
ip_iface = nm_device_get_ip_iface (self);
|
|
ip_ifindex = nm_device_get_ip_ifindex (self);
|
|
|
|
old_config = priv->ip4_config;
|
|
|
|
/* Always commit to nm-platform to update lifetimes */
|
|
if (commit && new_config) {
|
|
success = nm_ip4_config_commit (new_config, ip_ifindex, nm_device_get_priority (self));
|
|
if (!success)
|
|
reason_local = NM_DEVICE_STATE_REASON_CONFIG_FAILED;
|
|
}
|
|
|
|
if (new_config) {
|
|
if (old_config) {
|
|
/* has_changes is set only on relevant changes, because when the configuration changes,
|
|
* this causes a re-read and reset. This should only happen for relevant changes */
|
|
nm_ip4_config_replace (old_config, new_config, &has_changes);
|
|
if (has_changes) {
|
|
nm_log_dbg (LOGD_IP4, "(%s): update IP4Config instance (%s)",
|
|
ip_iface, nm_ip4_config_get_dbus_path (old_config));
|
|
}
|
|
} else {
|
|
has_changes = TRUE;
|
|
priv->ip4_config = g_object_ref (new_config);
|
|
|
|
if (success && !nm_ip4_config_get_dbus_path (new_config)) {
|
|
/* Export over D-Bus */
|
|
nm_ip4_config_export (new_config);
|
|
}
|
|
}
|
|
} else if (old_config) {
|
|
has_changes = TRUE;
|
|
priv->ip4_config = NULL;
|
|
nm_log_dbg (LOGD_IP4, "(%s): clear IP4Config instance (%s)",
|
|
ip_iface, nm_ip4_config_get_dbus_path (old_config));
|
|
/* Device config is invalid if combined config is invalid */
|
|
g_clear_object (&priv->dev_ip4_config);
|
|
}
|
|
|
|
if (has_changes) {
|
|
_update_ip4_address (self);
|
|
|
|
if (old_config != priv->ip4_config)
|
|
g_object_notify (G_OBJECT (self), NM_DEVICE_IP4_CONFIG);
|
|
g_signal_emit (self, signals[IP4_CONFIG_CHANGED], 0, priv->ip4_config, old_config);
|
|
|
|
if (old_config != priv->ip4_config && old_config)
|
|
g_object_unref (old_config);
|
|
}
|
|
|
|
if (reason)
|
|
*reason = reason_local;
|
|
|
|
return success;
|
|
}
|
|
|
|
void
|
|
nm_device_set_vpn4_config (NMDevice *device, NMIP4Config *config)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
if (priv->vpn4_config == config)
|
|
return;
|
|
|
|
g_clear_object (&priv->vpn4_config);
|
|
if (config)
|
|
priv->vpn4_config = g_object_ref (config);
|
|
|
|
/* NULL to use existing configs */
|
|
if (!ip4_config_merge_and_apply (device, NULL, TRUE, NULL)) {
|
|
nm_log_warn (LOGD_IP4, "(%s): failed to set VPN routes for device",
|
|
nm_device_get_ip_iface (device));
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
nm_device_set_ip6_config (NMDevice *self,
|
|
NMIP6Config *new_config,
|
|
gboolean commit,
|
|
NMDeviceStateReason *reason)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
const char *ip_iface;
|
|
NMIP6Config *old_config = NULL;
|
|
gboolean has_changes = FALSE;
|
|
gboolean success = TRUE;
|
|
NMDeviceStateReason reason_local = NM_DEVICE_STATE_REASON_NONE;
|
|
int ip_ifindex;
|
|
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
ip_iface = nm_device_get_ip_iface (self);
|
|
ip_ifindex = nm_device_get_ip_ifindex (self);
|
|
|
|
old_config = priv->ip6_config;
|
|
|
|
/* Always commit to nm-platform to update lifetimes */
|
|
if (commit && new_config) {
|
|
success = nm_ip6_config_commit (new_config, ip_ifindex, nm_device_get_priority (self));
|
|
if (!success)
|
|
reason_local = NM_DEVICE_STATE_REASON_CONFIG_FAILED;
|
|
}
|
|
|
|
if (new_config) {
|
|
if (old_config) {
|
|
/* has_changes is set only on relevant changes, because when the configuration changes,
|
|
* this causes a re-read and reset. This should only happen for relevant changes */
|
|
nm_ip6_config_replace (old_config, new_config, &has_changes);
|
|
if (has_changes) {
|
|
nm_log_dbg (LOGD_IP6, "(%s): update IP6Config instance (%s)",
|
|
ip_iface, nm_ip6_config_get_dbus_path (old_config));
|
|
}
|
|
} else {
|
|
has_changes = TRUE;
|
|
priv->ip6_config = g_object_ref (new_config);
|
|
|
|
if (success && !nm_ip6_config_get_dbus_path (new_config)) {
|
|
/* Export over D-Bus */
|
|
nm_ip6_config_export (new_config);
|
|
}
|
|
}
|
|
} else if (old_config) {
|
|
has_changes = TRUE;
|
|
priv->ip6_config = NULL;
|
|
nm_log_dbg (LOGD_IP6, "(%s): clear IP6Config instance (%s)",
|
|
ip_iface, nm_ip6_config_get_dbus_path (old_config));
|
|
}
|
|
|
|
if (has_changes) {
|
|
if (old_config != priv->ip6_config)
|
|
g_object_notify (G_OBJECT (self), NM_DEVICE_IP6_CONFIG);
|
|
g_signal_emit (self, signals[IP6_CONFIG_CHANGED], 0, priv->ip6_config, old_config);
|
|
|
|
if (old_config != priv->ip6_config && old_config)
|
|
g_object_unref (old_config);
|
|
}
|
|
|
|
if (reason)
|
|
*reason = reason_local;
|
|
|
|
return success;
|
|
}
|
|
|
|
void
|
|
nm_device_set_vpn6_config (NMDevice *device, NMIP6Config *config)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
if (priv->vpn6_config == config)
|
|
return;
|
|
|
|
g_clear_object (&priv->vpn6_config);
|
|
if (config)
|
|
priv->vpn6_config = g_object_ref (config);
|
|
|
|
/* NULL to use existing configs */
|
|
if (!ip6_config_merge_and_apply (device, TRUE, NULL)) {
|
|
nm_log_warn (LOGD_IP6, "(%s): failed to set VPN routes for device",
|
|
nm_device_get_ip_iface (device));
|
|
}
|
|
}
|
|
|
|
NMDHCP6Config *
|
|
nm_device_get_dhcp6_config (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->dhcp6_config;
|
|
}
|
|
|
|
NMIP6Config *
|
|
nm_device_get_ip6_config (NMDevice *self)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (self)->ip6_config;
|
|
}
|
|
|
|
/****************************************************************/
|
|
|
|
static void
|
|
ip_check_gw_ping_cleanup (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (priv->gw_ping.watch) {
|
|
g_source_remove (priv->gw_ping.watch);
|
|
priv->gw_ping.watch = 0;
|
|
}
|
|
if (priv->gw_ping.timeout) {
|
|
g_source_remove (priv->gw_ping.timeout);
|
|
priv->gw_ping.timeout = 0;
|
|
}
|
|
|
|
if (priv->gw_ping.pid) {
|
|
guint count = 20;
|
|
int status;
|
|
|
|
kill (priv->gw_ping.pid, SIGKILL);
|
|
do {
|
|
if (waitpid (priv->gw_ping.pid, &status, WNOHANG) != 0)
|
|
break;
|
|
g_usleep (G_USEC_PER_SEC / 20);
|
|
} while (count--);
|
|
|
|
priv->gw_ping.pid = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
const char *iface;
|
|
guint log_domain = priv->gw_ping.log_domain;
|
|
|
|
if (!priv->gw_ping.watch)
|
|
return;
|
|
priv->gw_ping.watch = 0;
|
|
priv->gw_ping.pid = 0;
|
|
|
|
iface = nm_device_get_iface (self);
|
|
|
|
if (WIFEXITED (status)) {
|
|
if (WEXITSTATUS (status) == 0)
|
|
nm_log_dbg (log_domain, "(%s): gateway ping succeeded", iface);
|
|
else {
|
|
nm_log_warn (log_domain, "(%s): gateway ping failed with error code %d",
|
|
iface, WEXITSTATUS (status));
|
|
}
|
|
} else
|
|
nm_log_warn (log_domain, "(%s): ping stopped unexpectedly with status %d", iface, status);
|
|
|
|
/* We've got connectivity, proceed to secondaries */
|
|
ip_check_gw_ping_cleanup (self);
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE);
|
|
}
|
|
|
|
static gboolean
|
|
ip_check_ping_timeout_cb (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
priv->gw_ping.timeout = 0;
|
|
|
|
nm_log_warn (priv->gw_ping.log_domain, "(%s): gateway ping timed out",
|
|
nm_device_get_iface (self));
|
|
|
|
ip_check_gw_ping_cleanup (self);
|
|
nm_device_state_changed (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
spawn_ping (NMDevice *self,
|
|
guint log_domain,
|
|
const char *binary,
|
|
const char *address,
|
|
guint timeout)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
const char *args[] = { binary, "-I", nm_device_get_ip_iface (self), "-c", "1", "-w", NULL, address, NULL };
|
|
GError *error = NULL;
|
|
char *str_timeout, *cmd;
|
|
gboolean success;
|
|
|
|
g_return_val_if_fail (priv->gw_ping.watch == 0, FALSE);
|
|
g_return_val_if_fail (priv->gw_ping.timeout == 0, FALSE);
|
|
|
|
args[6] = str_timeout = g_strdup_printf ("%u", timeout);
|
|
|
|
if (nm_logging_enabled (LOGL_DEBUG, log_domain)) {
|
|
cmd = g_strjoinv (" ", (gchar **) args);
|
|
nm_log_dbg (log_domain, "(%s): running '%s'",
|
|
nm_device_get_iface (self),
|
|
cmd);
|
|
g_free (cmd);
|
|
}
|
|
|
|
success = g_spawn_async ("/",
|
|
(gchar **) args,
|
|
NULL,
|
|
G_SPAWN_DO_NOT_REAP_CHILD,
|
|
nm_unblock_posix_signals,
|
|
NULL,
|
|
&priv->gw_ping.pid,
|
|
&error);
|
|
if (success) {
|
|
priv->gw_ping.log_domain = log_domain;
|
|
priv->gw_ping.watch = g_child_watch_add (priv->gw_ping.pid, ip_check_ping_watch_cb, self);
|
|
priv->gw_ping.timeout = g_timeout_add_seconds (timeout + 1, ip_check_ping_timeout_cb, self);
|
|
} else {
|
|
nm_log_warn (log_domain, "could not spawn %s: %s", binary, error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
g_free (str_timeout);
|
|
return success;
|
|
}
|
|
|
|
static void
|
|
nm_device_start_ip_check (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMConnection *connection;
|
|
NMSettingConnection *s_con;
|
|
guint timeout = 0;
|
|
const char *ping_binary = NULL;
|
|
char buf[INET6_ADDRSTRLEN] = { 0 };
|
|
guint log_domain = LOGD_IP4;
|
|
|
|
/* Shouldn't be any active ping here, since IP_CHECK happens after the
|
|
* first IP method completes. Any subsequently completing IP method doesn't
|
|
* get checked.
|
|
*/
|
|
g_assert (!priv->gw_ping.watch);
|
|
g_assert (!priv->gw_ping.timeout);
|
|
g_assert (!priv->gw_ping.pid);
|
|
g_assert (priv->ip4_state == IP_DONE || priv->ip6_state == IP_DONE);
|
|
|
|
connection = nm_device_get_connection (self);
|
|
g_assert (connection);
|
|
|
|
s_con = nm_connection_get_setting_connection (connection);
|
|
g_assert (s_con);
|
|
timeout = nm_setting_connection_get_gateway_ping_timeout (s_con);
|
|
|
|
if (timeout) {
|
|
if (priv->ip4_state == IP_DONE) {
|
|
guint gw = 0;
|
|
|
|
ping_binary = "/usr/bin/ping";
|
|
log_domain = LOGD_IP4;
|
|
|
|
gw = nm_ip4_config_get_gateway (priv->ip4_config);
|
|
if (gw && !inet_ntop (AF_INET, &gw, buf, sizeof (buf)))
|
|
buf[0] = '\0';
|
|
} else if (priv->ip6_config && priv->ip6_state == IP_DONE) {
|
|
const struct in6_addr *gw = NULL;
|
|
|
|
ping_binary = "/usr/bin/ping6";
|
|
log_domain = LOGD_IP6;
|
|
|
|
gw = nm_ip6_config_get_gateway (priv->ip6_config);
|
|
if (gw && !inet_ntop (AF_INET6, gw, buf, sizeof (buf)))
|
|
buf[0] = '\0';
|
|
}
|
|
}
|
|
|
|
if (buf[0])
|
|
spawn_ping (self, log_domain, ping_binary, buf, timeout);
|
|
|
|
/* If no ping was started, just advance to SECONDARIES */
|
|
if (!priv->gw_ping.pid)
|
|
nm_device_queue_state (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE);
|
|
}
|
|
|
|
/****************************************************************/
|
|
|
|
static gboolean
|
|
carrier_wait_timeout (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
|
|
NM_DEVICE_GET_PRIVATE (self)->carrier_wait_id = 0;
|
|
nm_device_remove_pending_action (self, "carrier wait");
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
gboolean
|
|
nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
gboolean success;
|
|
guint32 tries = 0;
|
|
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
|
|
|
|
if (nm_device_is_up (self))
|
|
goto out;
|
|
|
|
nm_log_info (LOGD_HW, "(%s): bringing up device.", nm_device_get_iface (self));
|
|
|
|
if (NM_DEVICE_GET_CLASS (self)->bring_up) {
|
|
success = NM_DEVICE_GET_CLASS (self)->bring_up (self, no_firmware);
|
|
if (!success)
|
|
return FALSE;
|
|
}
|
|
|
|
/* Wait for the device to come up if requested */
|
|
while (block && !nm_device_is_up (self) && (tries++ < 50))
|
|
g_usleep (200);
|
|
|
|
if (!nm_device_is_up (self)) {
|
|
nm_log_warn (LOGD_HW, "(%s): device not up after timeout!", nm_device_get_iface (self));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Devices that support carrier detect must be IFF_UP to report carrier
|
|
* changes; so after setting the device IFF_UP we must suppress startup
|
|
* complete (via a pending action) until either the carrier turns on, or
|
|
* a timeout is reached.
|
|
*/
|
|
if (device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) {
|
|
if (priv->carrier_wait_id) {
|
|
g_source_remove (priv->carrier_wait_id);
|
|
nm_device_remove_pending_action (self, "carrier wait");
|
|
}
|
|
priv->carrier_wait_id = g_timeout_add_seconds (5, carrier_wait_timeout, self);
|
|
nm_device_add_pending_action (self, "carrier wait");
|
|
}
|
|
|
|
out:
|
|
/* Can only get HW address of some devices when they are up */
|
|
nm_device_update_hw_address (self);
|
|
|
|
_update_ip4_address (self);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
bring_up (NMDevice *device, gboolean *no_firmware)
|
|
{
|
|
int ifindex = nm_device_get_ip_ifindex (device);
|
|
gboolean result;
|
|
|
|
if (!ifindex)
|
|
return TRUE;
|
|
|
|
result = nm_platform_link_set_up (ifindex);
|
|
if (no_firmware)
|
|
*no_firmware = nm_platform_get_error () == NM_PLATFORM_ERROR_NO_FIRMWARE;
|
|
|
|
/* Store carrier immediately. */
|
|
if (result && device_has_capability (device, NM_DEVICE_CAP_CARRIER_DETECT))
|
|
check_carrier (device);
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
nm_device_take_down (NMDevice *self, gboolean block)
|
|
{
|
|
guint32 tries = 0;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
if (!nm_device_is_up (self))
|
|
return;
|
|
|
|
nm_log_info (LOGD_HW, "(%s): taking down device.", nm_device_get_iface (self));
|
|
|
|
if (NM_DEVICE_GET_CLASS (self)->take_down)
|
|
NM_DEVICE_GET_CLASS (self)->take_down (self);
|
|
|
|
/* Wait for the device to go down if requested */
|
|
while (block && nm_device_is_up (self) && (tries++ < 50))
|
|
g_usleep (200);
|
|
}
|
|
|
|
static void
|
|
take_down (NMDevice *device)
|
|
{
|
|
int ifindex = nm_device_get_ip_ifindex (device);
|
|
|
|
if (ifindex)
|
|
nm_platform_link_set_down (ifindex);
|
|
}
|
|
|
|
static void
|
|
dispose (GObject *object)
|
|
{
|
|
NMDevice *self = NM_DEVICE (object);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
gboolean deconfigure = TRUE;
|
|
NMPlatform *platform;
|
|
|
|
if (priv->disposed || !priv->initialized)
|
|
goto out;
|
|
|
|
priv->disposed = TRUE;
|
|
|
|
/* Don't down can-assume-connection capable devices that are activated with
|
|
* a connection that can be assumed.
|
|
*/
|
|
if (nm_device_can_assume_connections (self) && (priv->state == NM_DEVICE_STATE_ACTIVATED)) {
|
|
NMConnection *connection;
|
|
const char *method;
|
|
|
|
connection = nm_device_get_connection (self);
|
|
if (connection) {
|
|
/* Only static or DHCP IPv4 connections can be left up.
|
|
* All IPv6 connections can be left up, so we don't have
|
|
* to check that.
|
|
*/
|
|
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
|
|
if ( !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)
|
|
|| !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)
|
|
|| !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
|
|
deconfigure = FALSE;
|
|
}
|
|
}
|
|
|
|
ip_check_gw_ping_cleanup (self);
|
|
|
|
/* Clear any queued transitions */
|
|
nm_device_queued_state_clear (self);
|
|
nm_device_queued_ip_config_change_clear (self);
|
|
|
|
/* Clean up and stop DHCP */
|
|
dhcp4_cleanup (self, deconfigure, FALSE);
|
|
dhcp6_cleanup (self, deconfigure, FALSE);
|
|
linklocal6_cleanup (self);
|
|
addrconf6_cleanup (self);
|
|
dnsmasq_cleanup (self);
|
|
|
|
g_warn_if_fail (priv->slaves == NULL);
|
|
g_assert (priv->master_ready_id == 0);
|
|
|
|
/* Take the device itself down and clear its IP configuration */
|
|
if (nm_device_get_managed (self) && deconfigure) {
|
|
NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE;
|
|
|
|
if (nm_device_get_act_request (self))
|
|
nm_device_deactivate (self, NM_DEVICE_STATE_REASON_REMOVED);
|
|
nm_device_set_ip4_config (self, NULL, TRUE, &ignored);
|
|
nm_device_set_ip6_config (self, NULL, TRUE, &ignored);
|
|
|
|
nm_device_take_down (self, FALSE);
|
|
|
|
restore_ip6_properties (self);
|
|
|
|
/* do a final check whether we should delete_link */
|
|
delete_on_deactivate_check_and_schedule (self, nm_device_get_ip_ifindex (self));
|
|
}
|
|
g_clear_object (&priv->dev_ip4_config);
|
|
g_clear_object (&priv->ext_ip4_config);
|
|
g_clear_object (&priv->vpn4_config);
|
|
g_clear_object (&priv->ip4_config);
|
|
|
|
g_clear_object (&priv->ip6_config);
|
|
g_clear_object (&priv->ac_ip6_config);
|
|
g_clear_object (&priv->dhcp6_ip6_config);
|
|
g_clear_object (&priv->vpn6_config);
|
|
g_clear_object (&priv->ext_ip6_config);
|
|
|
|
g_free (priv->ip6_disable_ipv6_path);
|
|
g_free (priv->ip6_accept_ra_path);
|
|
g_free (priv->ip6_use_tempaddr_path);
|
|
|
|
if (priv->carrier_defer_id) {
|
|
g_source_remove (priv->carrier_defer_id);
|
|
priv->carrier_defer_id = 0;
|
|
}
|
|
|
|
if (priv->cp_added_id) {
|
|
g_signal_handler_disconnect (priv->con_provider, priv->cp_added_id);
|
|
priv->cp_added_id = 0;
|
|
}
|
|
|
|
if (priv->cp_loaded_id) {
|
|
g_signal_handler_disconnect (priv->con_provider, priv->cp_loaded_id);
|
|
priv->cp_loaded_id = 0;
|
|
}
|
|
|
|
if (priv->cp_removed_id) {
|
|
g_signal_handler_disconnect (priv->con_provider, priv->cp_removed_id);
|
|
priv->cp_removed_id = 0;
|
|
}
|
|
|
|
if (priv->cp_updated_id) {
|
|
g_signal_handler_disconnect (priv->con_provider, priv->cp_updated_id);
|
|
priv->cp_updated_id = 0;
|
|
}
|
|
|
|
if (priv->carrier_wait_id) {
|
|
g_source_remove (priv->carrier_wait_id);
|
|
priv->carrier_wait_id = 0;
|
|
}
|
|
|
|
g_hash_table_unref (priv->available_connections);
|
|
priv->available_connections = NULL;
|
|
|
|
g_clear_pointer (&priv->physical_port_id, g_free);
|
|
|
|
activation_source_clear (self, TRUE, AF_INET);
|
|
activation_source_clear (self, TRUE, AF_INET6);
|
|
|
|
clear_act_request (self);
|
|
|
|
platform = nm_platform_get ();
|
|
g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (device_ip_changed), self);
|
|
g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (link_changed_cb), self);
|
|
|
|
out:
|
|
G_OBJECT_CLASS (nm_device_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *object)
|
|
{
|
|
NMDevice *self = NM_DEVICE (object);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (priv->dhcp_manager)
|
|
g_object_unref (priv->dhcp_manager);
|
|
|
|
if (priv->fw_manager)
|
|
g_object_unref (priv->fw_manager);
|
|
|
|
g_slist_free_full (priv->pending_actions, g_free);
|
|
|
|
g_free (priv->udi);
|
|
g_free (priv->path);
|
|
g_free (priv->iface);
|
|
g_free (priv->ip_iface);
|
|
g_free (priv->driver);
|
|
g_free (priv->driver_version);
|
|
g_free (priv->firmware_version);
|
|
g_free (priv->type_desc);
|
|
if (priv->dhcp_anycast_address)
|
|
g_byte_array_free (priv->dhcp_anycast_address, TRUE);
|
|
|
|
G_OBJECT_CLASS (nm_device_parent_class)->finalize (object);
|
|
}
|
|
|
|
|
|
static void
|
|
set_property (GObject *object, guint prop_id,
|
|
const GValue *value, GParamSpec *pspec)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (object);
|
|
NMPlatformLink *platform_device;
|
|
const char *hw_addr;
|
|
|
|
switch (prop_id) {
|
|
case PROP_PLATFORM_DEVICE:
|
|
platform_device = g_value_get_pointer (value);
|
|
if (platform_device) {
|
|
g_free (priv->udi);
|
|
priv->udi = g_strdup (platform_device->udi);
|
|
g_free (priv->iface);
|
|
priv->iface = g_strdup (platform_device->name);
|
|
priv->ifindex = platform_device->ifindex;
|
|
g_free (priv->driver);
|
|
priv->driver = g_strdup (platform_device->driver);
|
|
}
|
|
break;
|
|
case PROP_UDI:
|
|
if (g_value_get_string (value)) {
|
|
g_free (priv->udi);
|
|
priv->udi = g_value_dup_string (value);
|
|
}
|
|
break;
|
|
case PROP_IFACE:
|
|
if (g_value_get_string (value)) {
|
|
g_free (priv->iface);
|
|
priv->ifindex = 0;
|
|
priv->iface = g_value_dup_string (value);
|
|
|
|
/* Only look up the ifindex if it appears to be an actual kernel
|
|
* interface name. eg Bluetooth devices won't have one until we know
|
|
* the IP interface.
|
|
*/
|
|
if (priv->iface && !strchr (priv->iface, ':')) {
|
|
priv->ifindex = nm_platform_link_get_ifindex (priv->iface);
|
|
if (priv->ifindex <= 0)
|
|
nm_log_warn (LOGD_HW, "(%s): failed to look up interface index", priv->iface);
|
|
}
|
|
}
|
|
break;
|
|
case PROP_DRIVER:
|
|
if (g_value_get_string (value)) {
|
|
g_free (priv->driver);
|
|
priv->driver = g_value_dup_string (value);
|
|
}
|
|
break;
|
|
case PROP_DRIVER_VERSION:
|
|
g_free (priv->driver_version);
|
|
priv->driver_version = g_strdup (g_value_get_string (value));
|
|
break;
|
|
case PROP_FIRMWARE_VERSION:
|
|
g_free (priv->firmware_version);
|
|
priv->firmware_version = g_strdup (g_value_get_string (value));
|
|
break;
|
|
case PROP_MTU:
|
|
priv->mtu = g_value_get_uint (value);
|
|
break;
|
|
case PROP_IP4_ADDRESS:
|
|
priv->ip4_address = g_value_get_uint (value);
|
|
break;
|
|
case PROP_AUTOCONNECT:
|
|
priv->autoconnect = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_FIRMWARE_MISSING:
|
|
priv->firmware_missing = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_DEVICE_TYPE:
|
|
g_return_if_fail (priv->type == NM_DEVICE_TYPE_UNKNOWN);
|
|
priv->type = g_value_get_uint (value);
|
|
break;
|
|
case PROP_TYPE_DESC:
|
|
g_free (priv->type_desc);
|
|
priv->type_desc = g_value_dup_string (value);
|
|
break;
|
|
case PROP_RFKILL_TYPE:
|
|
priv->rfkill_type = g_value_get_uint (value);
|
|
break;
|
|
case PROP_IS_MASTER:
|
|
priv->is_master = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_HW_ADDRESS:
|
|
priv->hw_addr_len = nm_device_get_hw_address_length (NM_DEVICE (object), NULL);
|
|
|
|
hw_addr = g_value_get_string (value);
|
|
if (!hw_addr)
|
|
break;
|
|
if (priv->hw_addr_len == 0) {
|
|
g_warn_if_fail (*hw_addr == '\0');
|
|
break;
|
|
}
|
|
|
|
if (!nm_utils_hwaddr_aton_len (hw_addr, priv->hw_addr, priv->hw_addr_len)) {
|
|
g_warning ("Could not parse hw-address '%s'", hw_addr);
|
|
memset (priv->hw_addr, 0, sizeof (priv->hw_addr));
|
|
}
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
ip_config_valid (NMDeviceState state)
|
|
{
|
|
return (state == NM_DEVICE_STATE_UNMANAGED) ||
|
|
(state >= NM_DEVICE_STATE_IP_CHECK &&
|
|
state <= NM_DEVICE_STATE_DEACTIVATING);
|
|
}
|
|
|
|
static void
|
|
get_property (GObject *object, guint prop_id,
|
|
GValue *value, GParamSpec *pspec)
|
|
{
|
|
NMDevice *self = NM_DEVICE (object);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
const char *ac_path = NULL;
|
|
GPtrArray *array;
|
|
GHashTableIter iter;
|
|
NMConnection *connection;
|
|
|
|
switch (prop_id) {
|
|
case PROP_UDI:
|
|
g_value_set_string (value, priv->udi);
|
|
break;
|
|
case PROP_IFACE:
|
|
g_value_set_string (value, priv->iface);
|
|
break;
|
|
case PROP_IP_IFACE:
|
|
if (ip_config_valid (priv->state))
|
|
g_value_set_string (value, nm_device_get_ip_iface (self));
|
|
else
|
|
g_value_set_string (value, NULL);
|
|
break;
|
|
case PROP_IFINDEX:
|
|
g_value_set_int (value, priv->ifindex);
|
|
break;
|
|
case PROP_DRIVER:
|
|
g_value_set_string (value, priv->driver);
|
|
break;
|
|
case PROP_DRIVER_VERSION:
|
|
g_value_set_string (value, priv->driver_version);
|
|
break;
|
|
case PROP_FIRMWARE_VERSION:
|
|
g_value_set_string (value, priv->firmware_version);
|
|
break;
|
|
case PROP_CAPABILITIES:
|
|
g_value_set_uint (value, (priv->capabilities & ~NM_DEVICE_CAP_INTERNAL_MASK));
|
|
break;
|
|
case PROP_IP4_ADDRESS:
|
|
g_value_set_uint (value, priv->ip4_address);
|
|
break;
|
|
case PROP_CARRIER:
|
|
g_value_set_boolean (value, priv->carrier);
|
|
break;
|
|
case PROP_MTU:
|
|
g_value_set_uint (value, priv->mtu);
|
|
break;
|
|
case PROP_IP4_CONFIG:
|
|
if (ip_config_valid (priv->state) && priv->ip4_config)
|
|
g_value_set_boxed (value, nm_ip4_config_get_dbus_path (priv->ip4_config));
|
|
else
|
|
g_value_set_boxed (value, "/");
|
|
break;
|
|
case PROP_DHCP4_CONFIG:
|
|
if (ip_config_valid (priv->state) && priv->dhcp4_client)
|
|
g_value_set_boxed (value, nm_dhcp4_config_get_dbus_path (priv->dhcp4_config));
|
|
else
|
|
g_value_set_boxed (value, "/");
|
|
break;
|
|
case PROP_IP6_CONFIG:
|
|
if (ip_config_valid (priv->state) && priv->ip6_config)
|
|
g_value_set_boxed (value, nm_ip6_config_get_dbus_path (priv->ip6_config));
|
|
else
|
|
g_value_set_boxed (value, "/");
|
|
break;
|
|
case PROP_DHCP6_CONFIG:
|
|
if (ip_config_valid (priv->state) && priv->dhcp6_client)
|
|
g_value_set_boxed (value, nm_dhcp6_config_get_dbus_path (priv->dhcp6_config));
|
|
else
|
|
g_value_set_boxed (value, "/");
|
|
break;
|
|
case PROP_STATE:
|
|
g_value_set_uint (value, priv->state);
|
|
break;
|
|
case PROP_STATE_REASON:
|
|
g_value_take_boxed (value, dbus_g_type_specialized_construct (DBUS_G_TYPE_UINT_STRUCT));
|
|
dbus_g_type_struct_set (value,
|
|
0, priv->state,
|
|
1, priv->state_reason,
|
|
G_MAXUINT);
|
|
break;
|
|
case PROP_ACTIVE_CONNECTION:
|
|
if (priv->act_request)
|
|
ac_path = nm_active_connection_get_path (NM_ACTIVE_CONNECTION (priv->act_request));
|
|
g_value_set_boxed (value, ac_path ? ac_path : "/");
|
|
break;
|
|
case PROP_DEVICE_TYPE:
|
|
g_value_set_uint (value, priv->type);
|
|
break;
|
|
case PROP_MANAGED:
|
|
g_value_set_boolean (value, nm_device_get_managed (self));
|
|
break;
|
|
case PROP_AUTOCONNECT:
|
|
g_value_set_boolean (value, priv->autoconnect);
|
|
break;
|
|
case PROP_FIRMWARE_MISSING:
|
|
g_value_set_boolean (value, priv->firmware_missing);
|
|
break;
|
|
case PROP_TYPE_DESC:
|
|
g_value_set_string (value, priv->type_desc);
|
|
break;
|
|
case PROP_RFKILL_TYPE:
|
|
g_value_set_uint (value, priv->rfkill_type);
|
|
break;
|
|
case PROP_AVAILABLE_CONNECTIONS:
|
|
array = g_ptr_array_sized_new (g_hash_table_size (priv->available_connections));
|
|
g_hash_table_iter_init (&iter, priv->available_connections);
|
|
while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL))
|
|
g_ptr_array_add (array, g_strdup (nm_connection_get_path (connection)));
|
|
g_value_take_boxed (value, array);
|
|
break;
|
|
case PROP_PHYSICAL_PORT_ID:
|
|
g_value_set_string (value, priv->physical_port_id);
|
|
break;
|
|
case PROP_IS_MASTER:
|
|
g_value_set_boolean (value, priv->is_master);
|
|
break;
|
|
case PROP_MASTER:
|
|
g_value_set_object (value, priv->master);
|
|
break;
|
|
case PROP_HW_ADDRESS:
|
|
if (priv->hw_addr_len)
|
|
g_value_take_string (value, nm_utils_hwaddr_ntoa_len (priv->hw_addr, priv->hw_addr_len));
|
|
else
|
|
g_value_set_string (value, NULL);
|
|
break;
|
|
case PROP_HAS_PENDING_ACTION:
|
|
g_value_set_boolean (value, nm_device_has_pending_action (self));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
nm_device_class_init (NMDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
g_type_class_add_private (object_class, sizeof (NMDevicePrivate));
|
|
|
|
/* Virtual methods */
|
|
object_class->dispose = dispose;
|
|
object_class->finalize = finalize;
|
|
object_class->set_property = set_property;
|
|
object_class->get_property = get_property;
|
|
object_class->constructor = constructor;
|
|
object_class->constructed = constructed;
|
|
|
|
klass->link_changed = link_changed;
|
|
|
|
klass->is_available = is_available;
|
|
klass->act_stage1_prepare = act_stage1_prepare;
|
|
klass->act_stage2_config = act_stage2_config;
|
|
klass->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
|
|
klass->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
|
|
klass->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
|
|
klass->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
|
|
klass->have_any_ready_slaves = have_any_ready_slaves;
|
|
|
|
klass->spec_match_list = spec_match_list;
|
|
klass->can_auto_connect = can_auto_connect;
|
|
klass->check_connection_compatible = check_connection_compatible;
|
|
klass->check_connection_available = check_connection_available;
|
|
klass->is_up = is_up;
|
|
klass->bring_up = bring_up;
|
|
klass->take_down = take_down;
|
|
klass->carrier_changed = carrier_changed;
|
|
klass->can_interrupt_activation = can_interrupt_activation;
|
|
klass->get_hw_address_length = get_hw_address_length;
|
|
|
|
/* Properties */
|
|
g_object_class_install_property
|
|
(object_class, PROP_PLATFORM_DEVICE,
|
|
g_param_spec_pointer (NM_DEVICE_PLATFORM_DEVICE,
|
|
"Platform Device",
|
|
"NMPlatform device object",
|
|
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_UDI,
|
|
g_param_spec_string (NM_DEVICE_UDI,
|
|
"UDI",
|
|
"Unique Device Identifier",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_IFACE,
|
|
g_param_spec_string (NM_DEVICE_IFACE,
|
|
"Interface",
|
|
"Interface",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_IP_IFACE,
|
|
g_param_spec_string (NM_DEVICE_IP_IFACE,
|
|
"IP Interface",
|
|
"IP Interface",
|
|
NULL,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_DRIVER,
|
|
g_param_spec_string (NM_DEVICE_DRIVER,
|
|
"Driver",
|
|
"Driver",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_DRIVER_VERSION,
|
|
g_param_spec_string (NM_DEVICE_DRIVER_VERSION,
|
|
"Driver Version",
|
|
"Driver Version",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_FIRMWARE_VERSION,
|
|
g_param_spec_string (NM_DEVICE_FIRMWARE_VERSION,
|
|
"Firmware Version",
|
|
"Firmware Version",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_CAPABILITIES,
|
|
g_param_spec_uint (NM_DEVICE_CAPABILITIES,
|
|
"Capabilities",
|
|
"Capabilities",
|
|
0, G_MAXUINT32, NM_DEVICE_CAP_NONE,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_CARRIER,
|
|
g_param_spec_boolean (NM_DEVICE_CARRIER,
|
|
"Carrier",
|
|
"Carrier",
|
|
FALSE,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_MTU,
|
|
g_param_spec_uint (NM_DEVICE_MTU,
|
|
"MTU",
|
|
"MTU",
|
|
0, G_MAXUINT32, 1500,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_IP4_ADDRESS,
|
|
g_param_spec_uint (NM_DEVICE_IP4_ADDRESS,
|
|
"IP4 address",
|
|
"IP4 address",
|
|
0, G_MAXUINT32, 0, /* FIXME */
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_IP4_CONFIG,
|
|
g_param_spec_boxed (NM_DEVICE_IP4_CONFIG,
|
|
"IP4 Config",
|
|
"IP4 Config",
|
|
DBUS_TYPE_G_OBJECT_PATH,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_DHCP4_CONFIG,
|
|
g_param_spec_boxed (NM_DEVICE_DHCP4_CONFIG,
|
|
"DHCP4 Config",
|
|
"DHCP4 Config",
|
|
DBUS_TYPE_G_OBJECT_PATH,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_IP6_CONFIG,
|
|
g_param_spec_boxed (NM_DEVICE_IP6_CONFIG,
|
|
"IP6 Config",
|
|
"IP6 Config",
|
|
DBUS_TYPE_G_OBJECT_PATH,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_DHCP6_CONFIG,
|
|
g_param_spec_boxed (NM_DEVICE_DHCP6_CONFIG,
|
|
"DHCP6 Config",
|
|
"DHCP6 Config",
|
|
DBUS_TYPE_G_OBJECT_PATH,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_STATE,
|
|
g_param_spec_uint (NM_DEVICE_STATE,
|
|
"State",
|
|
"State",
|
|
0, G_MAXUINT32, NM_DEVICE_STATE_UNKNOWN,
|
|
G_PARAM_READABLE));
|
|
g_object_class_install_property
|
|
(object_class, PROP_STATE_REASON,
|
|
g_param_spec_boxed (NM_DEVICE_STATE_REASON,
|
|
"StateReason",
|
|
"StateReason",
|
|
DBUS_G_TYPE_UINT_STRUCT,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_ACTIVE_CONNECTION,
|
|
g_param_spec_boxed (NM_DEVICE_ACTIVE_CONNECTION,
|
|
"ActiveConnection",
|
|
"ActiveConnection",
|
|
DBUS_TYPE_G_OBJECT_PATH,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_DEVICE_TYPE,
|
|
g_param_spec_uint (NM_DEVICE_DEVICE_TYPE,
|
|
"DeviceType",
|
|
"DeviceType",
|
|
0, G_MAXUINT32, NM_DEVICE_TYPE_UNKNOWN,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_MANAGED,
|
|
g_param_spec_boolean (NM_DEVICE_MANAGED,
|
|
"Managed",
|
|
"Managed",
|
|
FALSE,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_AUTOCONNECT,
|
|
g_param_spec_boolean (NM_DEVICE_AUTOCONNECT,
|
|
"Autoconnect",
|
|
"Autoconnect",
|
|
DEFAULT_AUTOCONNECT,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_FIRMWARE_MISSING,
|
|
g_param_spec_boolean (NM_DEVICE_FIRMWARE_MISSING,
|
|
"FirmwareMissing",
|
|
"Firmware missing",
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_TYPE_DESC,
|
|
g_param_spec_string (NM_DEVICE_TYPE_DESC,
|
|
"Type Description",
|
|
"Device type description",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_RFKILL_TYPE,
|
|
g_param_spec_uint (NM_DEVICE_RFKILL_TYPE,
|
|
"Rfkill Type",
|
|
"Type of rfkill switch (if any) supported by this device",
|
|
RFKILL_TYPE_WLAN,
|
|
RFKILL_TYPE_MAX,
|
|
RFKILL_TYPE_UNKNOWN,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_IFINDEX,
|
|
g_param_spec_int (NM_DEVICE_IFINDEX,
|
|
"Ifindex",
|
|
"Ifindex",
|
|
0, G_MAXINT, 0,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_AVAILABLE_CONNECTIONS,
|
|
g_param_spec_boxed (NM_DEVICE_AVAILABLE_CONNECTIONS,
|
|
"AvailableConnections",
|
|
"AvailableConnections",
|
|
DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_PHYSICAL_PORT_ID,
|
|
g_param_spec_string (NM_DEVICE_PHYSICAL_PORT_ID,
|
|
"PhysicalPortId",
|
|
"PhysicalPortId",
|
|
NULL,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_IS_MASTER,
|
|
g_param_spec_boolean (NM_DEVICE_IS_MASTER,
|
|
"IsMaster",
|
|
"IsMaster",
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_MASTER,
|
|
g_param_spec_object (NM_DEVICE_MASTER,
|
|
"Master",
|
|
"Master",
|
|
NM_TYPE_DEVICE,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_HW_ADDRESS,
|
|
g_param_spec_string (NM_DEVICE_HW_ADDRESS,
|
|
"Hardware Address",
|
|
"Hardware address",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property
|
|
(object_class, PROP_HAS_PENDING_ACTION,
|
|
g_param_spec_boolean (NM_DEVICE_HAS_PENDING_ACTION,
|
|
"Has pending action",
|
|
"Has pending action",
|
|
FALSE,
|
|
G_PARAM_READABLE));
|
|
|
|
/* Signals */
|
|
signals[STATE_CHANGED] =
|
|
g_signal_new ("state-changed",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (NMDeviceClass, state_changed),
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 3,
|
|
G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
|
|
|
|
signals[AUTOCONNECT_ALLOWED] =
|
|
g_signal_new ("autoconnect-allowed",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
autoconnect_allowed_accumulator, NULL, NULL,
|
|
G_TYPE_BOOLEAN, 0);
|
|
|
|
signals[AUTH_REQUEST] =
|
|
g_signal_new (NM_DEVICE_AUTH_REQUEST,
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
0, NULL, NULL, NULL,
|
|
/* dbus-glib context, connection, permission, allow_interaction, callback, user_data */
|
|
G_TYPE_NONE, 6, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER, G_TYPE_POINTER);
|
|
|
|
signals[IP4_CONFIG_CHANGED] =
|
|
g_signal_new (NM_DEVICE_IP4_CONFIG_CHANGED,
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_OBJECT);
|
|
|
|
signals[IP6_CONFIG_CHANGED] =
|
|
g_signal_new (NM_DEVICE_IP6_CONFIG_CHANGED,
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_OBJECT);
|
|
|
|
nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
|
|
G_TYPE_FROM_CLASS (klass),
|
|
&dbus_glib_nm_device_object_info);
|
|
|
|
dbus_g_error_domain_register (NM_DEVICE_ERROR, NULL, NM_TYPE_DEVICE_ERROR);
|
|
}
|
|
|
|
static void
|
|
nm_device_config_device_interface_init (NMConfigDeviceInterface *iface)
|
|
{
|
|
iface->spec_match_list = (gboolean (*) (NMConfigDevice *, const GSList *)) nm_device_spec_match_list;
|
|
iface->get_hw_address = (const guint8 * (*) (NMConfigDevice *, guint *)) nm_device_get_hw_address;
|
|
}
|
|
|
|
void
|
|
nm_device_set_firmware_missing (NMDevice *self, gboolean new_missing)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
if (priv->firmware_missing != new_missing) {
|
|
priv->firmware_missing = new_missing;
|
|
g_object_notify (G_OBJECT (self), NM_DEVICE_FIRMWARE_MISSING);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
nm_device_get_firmware_missing (NMDevice *self)
|
|
{
|
|
return NM_DEVICE_GET_PRIVATE (self)->firmware_missing;
|
|
}
|
|
|
|
#define QUEUED_PREFIX "queued state change to "
|
|
|
|
static const char *
|
|
queued_state_to_string (NMDeviceState state)
|
|
{
|
|
switch (state) {
|
|
case NM_DEVICE_STATE_UNMANAGED:
|
|
return QUEUED_PREFIX "unmanaged";
|
|
case NM_DEVICE_STATE_UNAVAILABLE:
|
|
return QUEUED_PREFIX "unavailable";
|
|
case NM_DEVICE_STATE_DISCONNECTED:
|
|
return QUEUED_PREFIX "disconnected";
|
|
case NM_DEVICE_STATE_PREPARE:
|
|
return QUEUED_PREFIX "prepare";
|
|
case NM_DEVICE_STATE_CONFIG:
|
|
return QUEUED_PREFIX "config";
|
|
case NM_DEVICE_STATE_NEED_AUTH:
|
|
return QUEUED_PREFIX "need-auth";
|
|
case NM_DEVICE_STATE_IP_CONFIG:
|
|
return QUEUED_PREFIX "ip-config";
|
|
case NM_DEVICE_STATE_IP_CHECK:
|
|
return QUEUED_PREFIX "ip-check";
|
|
case NM_DEVICE_STATE_SECONDARIES:
|
|
return QUEUED_PREFIX "secondaries";
|
|
case NM_DEVICE_STATE_ACTIVATED:
|
|
return QUEUED_PREFIX "activated";
|
|
case NM_DEVICE_STATE_DEACTIVATING:
|
|
return QUEUED_PREFIX "deactivating";
|
|
case NM_DEVICE_STATE_FAILED:
|
|
return QUEUED_PREFIX "failed";
|
|
default:
|
|
break;
|
|
}
|
|
return QUEUED_PREFIX "unknown";
|
|
}
|
|
|
|
static const char *
|
|
state_to_string (NMDeviceState state)
|
|
{
|
|
return queued_state_to_string (state) + strlen (QUEUED_PREFIX);
|
|
}
|
|
|
|
static const char *
|
|
reason_to_string (NMDeviceStateReason reason)
|
|
{
|
|
switch (reason) {
|
|
case NM_DEVICE_STATE_REASON_NONE:
|
|
return "none";
|
|
case NM_DEVICE_STATE_REASON_NOW_MANAGED:
|
|
return "managed";
|
|
case NM_DEVICE_STATE_REASON_NOW_UNMANAGED:
|
|
return "unmanaged";
|
|
case NM_DEVICE_STATE_REASON_CONFIG_FAILED:
|
|
return "config-failed";
|
|
case NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE:
|
|
return "ip-config-unavailable";
|
|
case NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED:
|
|
return "ip-config-expired";
|
|
case NM_DEVICE_STATE_REASON_NO_SECRETS:
|
|
return "no-secrets";
|
|
case NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT:
|
|
return "supplicant-disconnect";
|
|
case NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED:
|
|
return "supplicant-config-failed";
|
|
case NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED:
|
|
return "supplicant-failed";
|
|
case NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT:
|
|
return "supplicant-timeout";
|
|
case NM_DEVICE_STATE_REASON_PPP_START_FAILED:
|
|
return "ppp-start-failed";
|
|
case NM_DEVICE_STATE_REASON_PPP_DISCONNECT:
|
|
return "ppp-disconnect";
|
|
case NM_DEVICE_STATE_REASON_PPP_FAILED:
|
|
return "ppp-failed";
|
|
case NM_DEVICE_STATE_REASON_DHCP_START_FAILED:
|
|
return "dhcp-start-failed";
|
|
case NM_DEVICE_STATE_REASON_DHCP_ERROR:
|
|
return "dhcp-error";
|
|
case NM_DEVICE_STATE_REASON_DHCP_FAILED:
|
|
return "dhcp-failed";
|
|
case NM_DEVICE_STATE_REASON_SHARED_START_FAILED:
|
|
return "sharing-start-failed";
|
|
case NM_DEVICE_STATE_REASON_SHARED_FAILED:
|
|
return "sharing-failed";
|
|
case NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED:
|
|
return "autoip-start-failed";
|
|
case NM_DEVICE_STATE_REASON_AUTOIP_ERROR:
|
|
return "autoip-error";
|
|
case NM_DEVICE_STATE_REASON_AUTOIP_FAILED:
|
|
return "autoip-failed";
|
|
case NM_DEVICE_STATE_REASON_MODEM_BUSY:
|
|
return "modem-busy";
|
|
case NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE:
|
|
return "modem-no-dialtone";
|
|
case NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER:
|
|
return "modem-no-carrier";
|
|
case NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT:
|
|
return "modem-dial-timeout";
|
|
case NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED:
|
|
return "modem-dial-failed";
|
|
case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED:
|
|
return "modem-init-failed";
|
|
case NM_DEVICE_STATE_REASON_GSM_APN_FAILED:
|
|
return "gsm-apn-failed";
|
|
case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING:
|
|
return "gsm-registration-idle";
|
|
case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED:
|
|
return "gsm-registration-denied";
|
|
case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT:
|
|
return "gsm-registration-timeout";
|
|
case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED:
|
|
return "gsm-registration-failed";
|
|
case NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED:
|
|
return "gsm-pin-check-failed";
|
|
case NM_DEVICE_STATE_REASON_FIRMWARE_MISSING:
|
|
return "firmware-missing";
|
|
case NM_DEVICE_STATE_REASON_REMOVED:
|
|
return "removed";
|
|
case NM_DEVICE_STATE_REASON_SLEEPING:
|
|
return "sleeping";
|
|
case NM_DEVICE_STATE_REASON_CONNECTION_REMOVED:
|
|
return "connection-removed";
|
|
case NM_DEVICE_STATE_REASON_USER_REQUESTED:
|
|
return "user-requested";
|
|
case NM_DEVICE_STATE_REASON_CARRIER:
|
|
return "carrier-changed";
|
|
case NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED:
|
|
return "connection-assumed";
|
|
case NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE:
|
|
return "supplicant-available";
|
|
case NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND:
|
|
return "modem-not-found";
|
|
case NM_DEVICE_STATE_REASON_BT_FAILED:
|
|
return "bluetooth-failed";
|
|
case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED:
|
|
return "gsm-sim-not-inserted";
|
|
case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
|
|
return "gsm-sim-pin-required";
|
|
case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED:
|
|
return "gsm-sim-puk-required";
|
|
case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG:
|
|
return "gsm-sim-wrong";
|
|
case NM_DEVICE_STATE_REASON_INFINIBAND_MODE:
|
|
return "infiniband-mode";
|
|
case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
|
|
return "dependency-failed";
|
|
case NM_DEVICE_STATE_REASON_BR2684_FAILED:
|
|
return "br2684-bridge-failed";
|
|
case NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE:
|
|
return "modem-manager-unavailable";
|
|
case NM_DEVICE_STATE_REASON_SSID_NOT_FOUND:
|
|
return "SSID not found";
|
|
case NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED:
|
|
return "secondary-connection-failed";
|
|
case NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED:
|
|
return "DCB-FCoE-failed";
|
|
case NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED:
|
|
return "teamd-control-failed";
|
|
default:
|
|
break;
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
static void
|
|
notify_ip_properties (NMDevice *device)
|
|
{
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_IP_IFACE);
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_IP4_CONFIG);
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP4_CONFIG);
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_IP6_CONFIG);
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP6_CONFIG);
|
|
}
|
|
|
|
void
|
|
nm_device_state_changed (NMDevice *device,
|
|
NMDeviceState state,
|
|
NMDeviceStateReason reason)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
NMDeviceState old_state;
|
|
NMActRequest *req;
|
|
gboolean no_firmware = FALSE;
|
|
NMConnection *connection;
|
|
|
|
/* Track re-entry */
|
|
g_warn_if_fail (priv->in_state_changed == FALSE);
|
|
priv->in_state_changed = TRUE;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (device));
|
|
|
|
/* Do nothing if state isn't changing, but as a special case allow
|
|
* re-setting UNAVAILABLE if the device is missing firmware so that we
|
|
* can retry device initialization.
|
|
*/
|
|
if ( (priv->state == state)
|
|
&& !(state == NM_DEVICE_STATE_UNAVAILABLE && priv->firmware_missing)) {
|
|
priv->in_state_changed = FALSE;
|
|
return;
|
|
}
|
|
|
|
old_state = priv->state;
|
|
priv->state = state;
|
|
priv->state_reason = reason;
|
|
|
|
nm_log_info (LOGD_DEVICE, "(%s): device state change: %s -> %s (reason '%s') [%d %d %d]",
|
|
nm_device_get_iface (device),
|
|
state_to_string (old_state),
|
|
state_to_string (state),
|
|
reason_to_string (reason),
|
|
old_state,
|
|
state,
|
|
reason);
|
|
|
|
/* Clear any queued transitions */
|
|
nm_device_queued_state_clear (device);
|
|
|
|
/* Cache the activation request for the dispatcher */
|
|
req = priv->act_request ? g_object_ref (priv->act_request) : NULL;
|
|
|
|
if (state <= NM_DEVICE_STATE_UNAVAILABLE)
|
|
_clear_available_connections (device, TRUE);
|
|
|
|
/* Update the available connections list when a device first becomes available */
|
|
if ( state >= NM_DEVICE_STATE_DISCONNECTED
|
|
&& old_state < NM_DEVICE_STATE_DISCONNECTED)
|
|
nm_device_recheck_available_connections (device);
|
|
|
|
/* Handle the new state here; but anything that could trigger
|
|
* another state change should be done below.
|
|
*/
|
|
switch (state) {
|
|
case NM_DEVICE_STATE_UNMANAGED:
|
|
nm_device_set_firmware_missing (device, FALSE);
|
|
if (old_state > NM_DEVICE_STATE_UNMANAGED) {
|
|
/* Clean up if the device is now unmanaged but was activated */
|
|
if (nm_device_get_act_request (device))
|
|
nm_device_deactivate (device, reason);
|
|
nm_device_take_down (device, TRUE);
|
|
restore_ip6_properties (device);
|
|
}
|
|
break;
|
|
case NM_DEVICE_STATE_UNAVAILABLE:
|
|
if (old_state == NM_DEVICE_STATE_UNMANAGED) {
|
|
save_ip6_properties (device);
|
|
nm_platform_sysctl_set (priv->ip6_disable_ipv6_path, "1");
|
|
nm_platform_sysctl_set (priv->ip6_accept_ra_path, "0");
|
|
nm_platform_sysctl_set (priv->ip6_use_tempaddr_path, "0");
|
|
}
|
|
|
|
if (old_state == NM_DEVICE_STATE_UNMANAGED || priv->firmware_missing) {
|
|
if (!nm_device_bring_up (device, TRUE, &no_firmware) && no_firmware)
|
|
nm_log_warn (LOGD_HW, "(%s): firmware may be missing.", nm_device_get_iface (device));
|
|
nm_device_set_firmware_missing (device, no_firmware ? TRUE : FALSE);
|
|
}
|
|
/* Ensure the device gets deactivated in response to stuff like
|
|
* carrier changes or rfkill. But don't deactivate devices that are
|
|
* about to assume a connection since that defeats the purpose of
|
|
* assuming the device's existing connection.
|
|
*
|
|
* Note that we "deactivate" the device even when coming from
|
|
* UNMANAGED, to ensure that it's in a clean state.
|
|
*/
|
|
if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED)
|
|
nm_device_deactivate (device, reason);
|
|
break;
|
|
case NM_DEVICE_STATE_DISCONNECTED:
|
|
if (old_state > NM_DEVICE_STATE_UNAVAILABLE)
|
|
nm_device_deactivate (device, reason);
|
|
break;
|
|
default:
|
|
priv->autoconnect = TRUE;
|
|
break;
|
|
}
|
|
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_STATE);
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_STATE_REASON);
|
|
g_signal_emit_by_name (device, "state-changed", state, old_state, reason);
|
|
|
|
/* Post-process the event after internal notification */
|
|
|
|
switch (state) {
|
|
case NM_DEVICE_STATE_UNAVAILABLE:
|
|
/* If the device can activate now (ie, it's got a carrier, the supplicant
|
|
* is active, or whatever) schedule a delayed transition to DISCONNECTED
|
|
* to get things rolling. The device can't transition immediately because
|
|
* we can't change states again from the state handler for a variety of
|
|
* reasons.
|
|
*/
|
|
if (nm_device_is_available (device)) {
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): device is available, will transition to DISCONNECTED",
|
|
nm_device_get_iface (device));
|
|
nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE);
|
|
} else {
|
|
if (old_state == NM_DEVICE_STATE_UNMANAGED) {
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): device not yet available for transition to DISCONNECTED",
|
|
nm_device_get_iface (device));
|
|
} else if (old_state > NM_DEVICE_STATE_UNAVAILABLE && priv->default_unmanaged)
|
|
nm_device_queue_state (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE);
|
|
}
|
|
break;
|
|
case NM_DEVICE_STATE_DEACTIVATING:
|
|
nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, reason);
|
|
break;
|
|
case NM_DEVICE_STATE_DISCONNECTED:
|
|
if (old_state > NM_DEVICE_STATE_DISCONNECTED && priv->default_unmanaged)
|
|
nm_device_queue_state (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE);
|
|
break;
|
|
case NM_DEVICE_STATE_ACTIVATED:
|
|
nm_log_info (LOGD_DEVICE, "Activation (%s) successful, device activated.",
|
|
nm_device_get_iface (device));
|
|
nm_dispatcher_call (DISPATCHER_ACTION_UP, nm_act_request_get_connection (req), device, NULL, NULL);
|
|
break;
|
|
case NM_DEVICE_STATE_FAILED:
|
|
connection = nm_device_get_connection (device);
|
|
nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
|
|
"Activation (%s) failed for connection '%s'",
|
|
nm_device_get_iface (device),
|
|
connection ? nm_connection_get_id (connection) : "<unknown>");
|
|
|
|
/* Notify any slaves of the unexpected failure */
|
|
nm_device_master_release_slaves (device);
|
|
|
|
/* If the connection doesn't yet have a timestamp, set it to zero so that
|
|
* we can distinguish between connections we've tried to activate and have
|
|
* failed (zero timestamp), connections that succeeded (non-zero timestamp),
|
|
* and those we haven't tried yet (no timestamp).
|
|
*/
|
|
if (connection && !nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), NULL)) {
|
|
nm_settings_connection_update_timestamp (NM_SETTINGS_CONNECTION (connection),
|
|
(guint64) 0,
|
|
TRUE);
|
|
}
|
|
|
|
/* Schedule the transition to DISCONNECTED. The device can't transition
|
|
* immediately because we can't change states again from the state
|
|
* handler for a variety of reasons.
|
|
*/
|
|
nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE);
|
|
break;
|
|
case NM_DEVICE_STATE_IP_CHECK:
|
|
nm_device_start_ip_check (device);
|
|
|
|
/* IP-related properties are only valid when the device has IP configuration;
|
|
* now that it does, ensure their change notifications are emitted.
|
|
*/
|
|
notify_ip_properties (device);
|
|
break;
|
|
case NM_DEVICE_STATE_SECONDARIES:
|
|
ip_check_gw_ping_cleanup (device);
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): device entered SECONDARIES state",
|
|
nm_device_get_iface (device));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (state > NM_DEVICE_STATE_DISCONNECTED)
|
|
delete_on_deactivate_unschedule (priv);
|
|
|
|
if (old_state == NM_DEVICE_STATE_ACTIVATED)
|
|
nm_dispatcher_call (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), device, NULL, NULL);
|
|
|
|
/* IP-related properties are only valid when the device has IP configuration.
|
|
* If it no longer does, ensure their change notifications are emitted.
|
|
*/
|
|
if (ip_config_valid (old_state) && !ip_config_valid (state))
|
|
notify_ip_properties (device);
|
|
|
|
/* Dispose of the cached activation request */
|
|
if (req)
|
|
g_object_unref (req);
|
|
|
|
priv->in_state_changed = FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
queued_set_state (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
NMDeviceState new_state;
|
|
NMDeviceStateReason new_reason;
|
|
|
|
if (priv->queued_state.id) {
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): running queued state change to %s (id %d)",
|
|
nm_device_get_iface (self),
|
|
state_to_string (priv->queued_state.state),
|
|
priv->queued_state.id);
|
|
|
|
/* Clear queued state struct before triggering state change, since
|
|
* the state change may queue another state.
|
|
*/
|
|
priv->queued_state.id = 0;
|
|
new_state = priv->queued_state.state;
|
|
new_reason = priv->queued_state.reason;
|
|
nm_device_queued_state_clear (self);
|
|
|
|
nm_device_state_changed (self, new_state, new_reason);
|
|
nm_device_remove_pending_action (self, queued_state_to_string (new_state));
|
|
} else {
|
|
g_warn_if_fail (priv->queued_state.state == NM_DEVICE_STATE_UNKNOWN);
|
|
g_warn_if_fail (priv->queued_state.reason == NM_DEVICE_STATE_REASON_NONE);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
nm_device_queue_state (NMDevice *self,
|
|
NMDeviceState state,
|
|
NMDeviceStateReason reason)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (self));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
/* "lock" the pending actions so that if there was a previously
|
|
* queued action that's about to be cleared, that doesn't drop
|
|
* the pending actions to 0 before we add the new pending action.
|
|
*/
|
|
nm_device_add_pending_action (self, "queued state lock");
|
|
|
|
/* We should only ever have one delayed state transition at a time */
|
|
if (priv->queued_state.id) {
|
|
if (priv->queued_state.state == state)
|
|
return;
|
|
nm_log_warn (LOGD_DEVICE, "(%s): overwriting previously queued state change to %s (%s)",
|
|
nm_device_get_iface (self),
|
|
state_to_string (priv->queued_state.state),
|
|
reason_to_string (priv->queued_state.reason));
|
|
nm_device_queued_state_clear (self);
|
|
}
|
|
|
|
priv->queued_state.state = state;
|
|
priv->queued_state.reason = reason;
|
|
priv->queued_state.id = g_idle_add (queued_set_state, self);
|
|
nm_device_add_pending_action (self, queued_state_to_string (state));
|
|
|
|
nm_device_remove_pending_action (self, "queued state lock");
|
|
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): queued state change to %s due to %s (id %d)",
|
|
nm_device_get_iface (self), state_to_string (state), reason_to_string (reason),
|
|
priv->queued_state.id);
|
|
}
|
|
|
|
NMDeviceState
|
|
nm_device_queued_state_peek (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_STATE_UNKNOWN);
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
return priv->queued_state.id ? priv->queued_state.state : NM_DEVICE_STATE_UNKNOWN;
|
|
}
|
|
|
|
void
|
|
nm_device_queued_state_clear (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (priv->queued_state.id) {
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): clearing queued state transition (id %d)",
|
|
nm_device_get_iface (self), priv->queued_state.id);
|
|
g_source_remove (priv->queued_state.id);
|
|
nm_device_remove_pending_action (self, queued_state_to_string (priv->queued_state.state));
|
|
}
|
|
memset (&priv->queued_state, 0, sizeof (priv->queued_state));
|
|
}
|
|
|
|
NMDeviceState
|
|
nm_device_get_state (NMDevice *device)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (device), NM_DEVICE_STATE_UNKNOWN);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (device)->state;
|
|
}
|
|
|
|
static NMIP4Config *
|
|
find_ip4_lease_config (NMDevice *device,
|
|
NMConnection *connection,
|
|
NMIP4Config *ext_ip4_config)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
const char *ip_iface = nm_device_get_ip_iface (device);
|
|
GSList *leases, *liter;
|
|
NMIP4Config *found = NULL;
|
|
|
|
g_return_val_if_fail (NM_IS_IP4_CONFIG (ext_ip4_config), NULL);
|
|
g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
|
|
|
|
leases = nm_dhcp_manager_get_lease_ip_configs (priv->dhcp_manager,
|
|
ip_iface,
|
|
nm_connection_get_uuid (connection),
|
|
FALSE);
|
|
for (liter = leases; liter && !found; liter = liter->next) {
|
|
NMIP4Config *lease_config = liter->data;
|
|
const NMPlatformIP4Address *address = nm_ip4_config_get_address (lease_config, 0);
|
|
guint32 gateway = nm_ip4_config_get_gateway (lease_config);
|
|
|
|
g_assert (address);
|
|
if (!nm_ip4_config_address_exists (ext_ip4_config, address))
|
|
continue;
|
|
if (gateway != nm_ip4_config_get_gateway (ext_ip4_config))
|
|
continue;
|
|
found = g_object_ref (lease_config);
|
|
}
|
|
|
|
g_slist_free_full (leases, g_object_unref);
|
|
return found;
|
|
}
|
|
|
|
static void
|
|
capture_lease_config (NMDevice *device,
|
|
NMIP4Config *ext_ip4_config,
|
|
NMIP4Config **out_ip4_config,
|
|
NMIP6Config *ext_ip6_config,
|
|
NMIP6Config **out_ip6_config)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
const GSList *connections, *citer;
|
|
guint i;
|
|
gboolean dhcp_used = FALSE;
|
|
|
|
/* Ensure at least one address on the device has a non-infinite lifetime,
|
|
* otherwise DHCP cannot possibly be active on the device right now.
|
|
*/
|
|
if (ext_ip4_config && out_ip4_config) {
|
|
for (i = 0; i < nm_ip4_config_get_num_addresses (ext_ip4_config); i++) {
|
|
const NMPlatformIP4Address *addr = nm_ip4_config_get_address (ext_ip4_config, i);
|
|
|
|
if (addr->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) {
|
|
dhcp_used = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
} else if (ext_ip6_config && out_ip6_config) {
|
|
for (i = 0; i < nm_ip6_config_get_num_addresses (ext_ip6_config); i++) {
|
|
const NMPlatformIP6Address *addr = nm_ip6_config_get_address (ext_ip6_config, i);
|
|
|
|
if (addr->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) {
|
|
dhcp_used = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
g_return_if_fail ( (ext_ip6_config && out_ip6_config)
|
|
|| (ext_ip4_config && out_ip4_config));
|
|
}
|
|
|
|
if (!dhcp_used)
|
|
return;
|
|
|
|
connections = nm_connection_provider_get_connections (priv->con_provider);
|
|
for (citer = connections; citer; citer = citer->next) {
|
|
NMConnection *candidate = citer->data;
|
|
const char *method;
|
|
|
|
if (!nm_device_check_connection_compatible (device, candidate, NULL))
|
|
continue;
|
|
|
|
/* IPv4 leases */
|
|
method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP4_CONFIG);
|
|
if (out_ip4_config && strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
|
|
*out_ip4_config = find_ip4_lease_config (device, candidate, ext_ip4_config);
|
|
if (*out_ip4_config)
|
|
return;
|
|
}
|
|
|
|
/* IPv6 leases */
|
|
method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP6_CONFIG);
|
|
if (out_ip6_config && strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) {
|
|
/* FIXME: implement find_ip6_lease_config() */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_ip_config (NMDevice *self, gboolean initial)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
int ifindex;
|
|
gboolean linklocal6_just_completed = FALSE;
|
|
gboolean capture_resolv_conf;
|
|
NMDnsManagerResolvConfMode resolv_conf_mode;
|
|
|
|
ifindex = nm_device_get_ip_ifindex (self);
|
|
if (!ifindex)
|
|
return;
|
|
|
|
resolv_conf_mode = nm_dns_manager_get_resolv_conf_mode (nm_dns_manager_get ());
|
|
capture_resolv_conf = initial && (resolv_conf_mode == NM_DNS_MANAGER_RESOLV_CONF_EXPLICIT);
|
|
|
|
/* IPv4 */
|
|
g_clear_object (&priv->ext_ip4_config);
|
|
priv->ext_ip4_config = nm_ip4_config_capture (ifindex, capture_resolv_conf);
|
|
|
|
if (priv->ext_ip4_config) {
|
|
if (initial) {
|
|
g_clear_object (&priv->dev_ip4_config);
|
|
capture_lease_config (self, priv->ext_ip4_config, &priv->dev_ip4_config, NULL, NULL);
|
|
}
|
|
if (priv->dev_ip4_config)
|
|
nm_ip4_config_subtract (priv->ext_ip4_config, priv->dev_ip4_config);
|
|
if (priv->vpn4_config)
|
|
nm_ip4_config_subtract (priv->ext_ip4_config, priv->vpn4_config);
|
|
|
|
ip4_config_merge_and_apply (self, NULL, FALSE, NULL);
|
|
}
|
|
|
|
/* IPv6 */
|
|
g_clear_object (&priv->ext_ip6_config);
|
|
priv->ext_ip6_config = nm_ip6_config_capture (ifindex, capture_resolv_conf);
|
|
if (priv->ext_ip6_config) {
|
|
|
|
/* Check this before modifying ext_ip6_config */
|
|
linklocal6_just_completed = priv->linklocal6_timeout_id &&
|
|
linklocal6_config_is_ready (priv->ext_ip6_config);
|
|
|
|
if (priv->ac_ip6_config)
|
|
nm_ip6_config_subtract (priv->ext_ip6_config, priv->ac_ip6_config);
|
|
if (priv->dhcp6_ip6_config)
|
|
nm_ip6_config_subtract (priv->ext_ip6_config, priv->dhcp6_ip6_config);
|
|
if (priv->vpn6_config)
|
|
nm_ip6_config_subtract (priv->ext_ip6_config, priv->vpn6_config);
|
|
|
|
ip6_config_merge_and_apply (self, FALSE, NULL);
|
|
}
|
|
|
|
if (linklocal6_just_completed) {
|
|
/* linklocal6 is ready now, do the state transition... we are also
|
|
* invoked as g_idle_add, so no problems with reentrance doing it now.
|
|
*/
|
|
linklocal6_complete (self);
|
|
}
|
|
}
|
|
|
|
void
|
|
nm_device_capture_initial_config (NMDevice *dev)
|
|
{
|
|
update_ip_config (dev, TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
queued_ip_config_change (gpointer user_data)
|
|
{
|
|
NMDevice *self = NM_DEVICE (user_data);
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
/* Wait for any queued state changes */
|
|
if (priv->queued_state.id)
|
|
return TRUE;
|
|
|
|
priv->queued_ip_config_id = 0;
|
|
update_ip_config (self, FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
device_ip_changed (NMPlatform *platform, int ifindex, gpointer platform_object, NMPlatformReason reason, gpointer user_data)
|
|
{
|
|
NMDevice *self = user_data;
|
|
|
|
if (nm_device_get_ip_ifindex (self) == ifindex) {
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (!priv->queued_ip_config_id)
|
|
priv->queued_ip_config_id = g_idle_add (queued_ip_config_change, self);
|
|
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): queued IP config change",
|
|
nm_device_get_iface (self));
|
|
}
|
|
}
|
|
|
|
void
|
|
nm_device_queued_ip_config_change_clear (NMDevice *self)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
|
|
|
|
if (priv->queued_ip_config_id) {
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): clearing queued IP config change",
|
|
nm_device_get_iface (self));
|
|
g_source_remove (priv->queued_ip_config_id);
|
|
priv->queued_ip_config_id = 0;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
nm_device_get_managed (NMDevice *device)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
if (!priv->manager_managed)
|
|
return FALSE;
|
|
else if (priv->default_unmanaged)
|
|
return (priv->state != NM_DEVICE_STATE_UNMANAGED);
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
nm_device_set_managed_internal (NMDevice *device,
|
|
gboolean managed,
|
|
NMDeviceStateReason reason)
|
|
{
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): now %s",
|
|
nm_device_get_iface (device),
|
|
managed ? "managed" : "unmanaged");
|
|
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_MANAGED);
|
|
|
|
if (managed)
|
|
nm_device_state_changed (device, NM_DEVICE_STATE_UNAVAILABLE, reason);
|
|
else
|
|
nm_device_state_changed (device, NM_DEVICE_STATE_UNMANAGED, reason);
|
|
}
|
|
|
|
void
|
|
nm_device_set_manager_managed (NMDevice *device,
|
|
gboolean managed,
|
|
NMDeviceStateReason reason)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
gboolean was_managed, now_managed;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (device));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
was_managed = nm_device_get_managed (device);
|
|
priv->manager_managed = managed;
|
|
now_managed = nm_device_get_managed (device);
|
|
|
|
if (was_managed != now_managed)
|
|
nm_device_set_managed_internal (device, now_managed, reason);
|
|
}
|
|
|
|
void
|
|
nm_device_set_default_unmanaged (NMDevice *device,
|
|
gboolean default_unmanaged)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
gboolean was_managed, now_managed;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (device));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
was_managed = nm_device_get_managed (device);
|
|
priv->default_unmanaged = default_unmanaged;
|
|
now_managed = nm_device_get_managed (device);
|
|
|
|
if (was_managed != now_managed)
|
|
nm_device_set_managed_internal (device, now_managed,
|
|
default_unmanaged ? NM_DEVICE_STATE_REASON_NOW_UNMANAGED :
|
|
NM_DEVICE_STATE_REASON_NOW_MANAGED);
|
|
}
|
|
|
|
/**
|
|
* nm_device_spec_match_list:
|
|
* @device: an #NMDevice
|
|
* @specs: (element-type utf8): a list of device specs
|
|
*
|
|
* Checks if @device matches any of the specifications in @specs. The
|
|
* currently-supported spec types are:
|
|
*
|
|
* "mac:00:11:22:33:44:55" - matches a device with the given
|
|
* hardware address
|
|
*
|
|
* "interface-name:foo0" - matches a device with the given
|
|
* interface name
|
|
*
|
|
* "s390-subchannels:00.11.22" - matches a device with the given
|
|
* z/VM / s390 subchannels.
|
|
*
|
|
* "*" - matches any device
|
|
*
|
|
* Returns: #TRUE if @device matches one of the specs in @specs
|
|
*/
|
|
gboolean
|
|
nm_device_spec_match_list (NMDevice *device, const GSList *specs)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
|
|
|
|
if (!specs)
|
|
return FALSE;
|
|
|
|
return NM_DEVICE_GET_CLASS (device)->spec_match_list (device, specs);
|
|
}
|
|
|
|
static gboolean
|
|
spec_match_list (NMDevice *device, const GSList *specs)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
char *hwaddr_str;
|
|
gboolean matched = FALSE;
|
|
|
|
if (nm_match_spec_string (specs, "*"))
|
|
return TRUE;
|
|
|
|
if (priv->hw_addr_len) {
|
|
hwaddr_str = nm_utils_hwaddr_ntoa_len (priv->hw_addr, priv->hw_addr_len);
|
|
matched = nm_match_spec_hwaddr (specs, hwaddr_str);
|
|
g_free (hwaddr_str);
|
|
}
|
|
|
|
if (!matched)
|
|
matched = nm_match_spec_interface_name (specs, nm_device_get_iface (device));
|
|
|
|
return matched;
|
|
}
|
|
|
|
void
|
|
nm_device_set_dhcp_timeout (NMDevice *device, guint32 timeout)
|
|
{
|
|
g_return_if_fail (NM_IS_DEVICE (device));
|
|
|
|
NM_DEVICE_GET_PRIVATE (device)->dhcp_timeout = timeout;
|
|
}
|
|
|
|
void
|
|
nm_device_set_dhcp_anycast_address (NMDevice *device, guint8 *addr)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (device));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
if (priv->dhcp_anycast_address) {
|
|
g_byte_array_free (priv->dhcp_anycast_address, TRUE);
|
|
priv->dhcp_anycast_address = NULL;
|
|
}
|
|
|
|
if (addr) {
|
|
priv->dhcp_anycast_address = g_byte_array_sized_new (ETH_ALEN);
|
|
g_byte_array_append (priv->dhcp_anycast_address, addr, ETH_ALEN);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
nm_device_get_autoconnect (NMDevice *device)
|
|
{
|
|
g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
|
|
|
|
return NM_DEVICE_GET_PRIVATE (device)->autoconnect;
|
|
}
|
|
|
|
gboolean
|
|
nm_device_connection_is_available (NMDevice *device, NMConnection *connection)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
if (priv->default_unmanaged && (priv->state == NM_DEVICE_STATE_UNMANAGED)) {
|
|
/* default-unmanaged devices in UNMANAGED state have no available connections
|
|
* so we must manually check whether the connection is available here.
|
|
*/
|
|
if ( nm_device_check_connection_compatible (device, connection, NULL)
|
|
&& NM_DEVICE_GET_CLASS (device)->check_connection_available (device, connection, NULL))
|
|
return TRUE;
|
|
}
|
|
|
|
return !!g_hash_table_lookup (priv->available_connections, connection);
|
|
}
|
|
|
|
static void
|
|
_signal_available_connections_changed (NMDevice *device)
|
|
{
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_AVAILABLE_CONNECTIONS);
|
|
}
|
|
|
|
static void
|
|
_clear_available_connections (NMDevice *device, gboolean do_signal)
|
|
{
|
|
g_hash_table_remove_all (NM_DEVICE_GET_PRIVATE (device)->available_connections);
|
|
if (do_signal == TRUE)
|
|
_signal_available_connections_changed (device);
|
|
}
|
|
|
|
static gboolean
|
|
_try_add_available_connection (NMDevice *self, NMConnection *connection)
|
|
{
|
|
if (nm_device_get_state (self) < NM_DEVICE_STATE_DISCONNECTED)
|
|
return FALSE;
|
|
|
|
if (nm_device_check_connection_compatible (self, connection, NULL)) {
|
|
if (NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection, NULL)) {
|
|
g_hash_table_insert (NM_DEVICE_GET_PRIVATE (self)->available_connections,
|
|
g_object_ref (connection),
|
|
GUINT_TO_POINTER (1));
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
_del_available_connection (NMDevice *device, NMConnection *connection)
|
|
{
|
|
return g_hash_table_remove (NM_DEVICE_GET_PRIVATE (device)->available_connections, connection);
|
|
}
|
|
|
|
static gboolean
|
|
connection_requires_carrier (NMConnection *connection)
|
|
{
|
|
NMSettingIP4Config *s_ip4;
|
|
NMSettingIP6Config *s_ip6;
|
|
const char *method;
|
|
gboolean ip4_carrier_wanted = FALSE, ip6_carrier_wanted = FALSE;
|
|
gboolean ip4_used = FALSE, ip6_used = FALSE;
|
|
|
|
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
|
|
if ( strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) != 0
|
|
&& strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) != 0) {
|
|
ip4_carrier_wanted = TRUE;
|
|
|
|
/* If IPv4 wants a carrier and cannot fail, the whole connection
|
|
* requires a carrier regardless of the IPv6 method.
|
|
*/
|
|
s_ip4 = nm_connection_get_setting_ip4_config (connection);
|
|
if (s_ip4 && !nm_setting_ip4_config_get_may_fail (s_ip4))
|
|
return TRUE;
|
|
}
|
|
ip4_used = (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) != 0);
|
|
|
|
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
|
|
if ( strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) != 0
|
|
&& strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) != 0) {
|
|
ip6_carrier_wanted = TRUE;
|
|
|
|
/* If IPv6 wants a carrier and cannot fail, the whole connection
|
|
* requires a carrier regardless of the IPv4 method.
|
|
*/
|
|
s_ip6 = nm_connection_get_setting_ip6_config (connection);
|
|
if (s_ip6 && !nm_setting_ip6_config_get_may_fail (s_ip6))
|
|
return TRUE;
|
|
}
|
|
ip6_used = (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) != 0);
|
|
|
|
/* If an IP version wants a carrier and and the other IP version isn't
|
|
* used, the connection requires carrier since it will just fail without one.
|
|
*/
|
|
if (ip4_carrier_wanted && !ip6_used)
|
|
return TRUE;
|
|
if (ip6_carrier_wanted && !ip4_used)
|
|
return TRUE;
|
|
|
|
/* If both want a carrier, the whole connection wants a carrier */
|
|
return ip4_carrier_wanted && ip6_carrier_wanted;
|
|
}
|
|
|
|
static gboolean
|
|
check_connection_available (NMDevice *device,
|
|
NMConnection *connection,
|
|
const char *specific_object)
|
|
{
|
|
/* Connections which require a network connection are not available when
|
|
* the device has no carrier, even with ignore-carrer=TRUE.
|
|
*/
|
|
if (NM_DEVICE_GET_PRIVATE (device)->carrier == FALSE)
|
|
return connection_requires_carrier (connection) ? FALSE : TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
nm_device_recheck_available_connections (NMDevice *device)
|
|
{
|
|
NMDevicePrivate *priv;
|
|
const GSList *connections, *iter;
|
|
|
|
g_return_if_fail (NM_IS_DEVICE (device));
|
|
|
|
priv = NM_DEVICE_GET_PRIVATE(device);
|
|
|
|
if (priv->con_provider) {
|
|
_clear_available_connections (device, FALSE);
|
|
|
|
connections = nm_connection_provider_get_connections (priv->con_provider);
|
|
for (iter = connections; iter; iter = g_slist_next (iter))
|
|
_try_add_available_connection (device, NM_CONNECTION (iter->data));
|
|
|
|
_signal_available_connections_changed (device);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* nm_device_get_available_connections:
|
|
* @device: the #NMDevice
|
|
* @specific_object: a specific object path if any
|
|
*
|
|
* Returns a list of connections available to activate on the device, taking
|
|
* into account any device-specific details given by @specific_object (like
|
|
* WiFi access point path).
|
|
*
|
|
* Returns: caller-owned #GPtrArray of #NMConnections
|
|
*/
|
|
GPtrArray *
|
|
nm_device_get_available_connections (NMDevice *device, const char *specific_object)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
GHashTableIter iter;
|
|
guint num_available;
|
|
NMConnection *connection = NULL;
|
|
GPtrArray *array = NULL;
|
|
|
|
num_available = g_hash_table_size (priv->available_connections);
|
|
if (num_available > 0) {
|
|
array = g_ptr_array_sized_new (num_available);
|
|
g_hash_table_iter_init (&iter, priv->available_connections);
|
|
while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL)) {
|
|
/* If a specific object is given, only include connections that are
|
|
* compatible with it.
|
|
*/
|
|
if ( !specific_object
|
|
|| NM_DEVICE_GET_CLASS (device)->check_connection_available (device, connection, specific_object))
|
|
g_ptr_array_add (array, connection);
|
|
}
|
|
}
|
|
return array;
|
|
}
|
|
|
|
static void
|
|
cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
|
|
{
|
|
if (_try_add_available_connection (NM_DEVICE (user_data), connection))
|
|
_signal_available_connections_changed (NM_DEVICE (user_data));
|
|
}
|
|
|
|
static void
|
|
cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
|
|
{
|
|
if (_del_available_connection (NM_DEVICE (user_data), connection))
|
|
_signal_available_connections_changed (NM_DEVICE (user_data));
|
|
}
|
|
|
|
static void
|
|
cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
|
|
{
|
|
gboolean added, deleted;
|
|
|
|
/* FIXME: don't remove it from the hash if it's just going to get re-added */
|
|
deleted = _del_available_connection (NM_DEVICE (user_data), connection);
|
|
added = _try_add_available_connection (NM_DEVICE (user_data), connection);
|
|
|
|
/* Only signal if the connection was removed OR added, but not both */
|
|
if (added != deleted)
|
|
_signal_available_connections_changed (NM_DEVICE (user_data));
|
|
}
|
|
|
|
gboolean
|
|
nm_device_supports_vlans (NMDevice *device)
|
|
{
|
|
return nm_platform_link_supports_vlans (nm_device_get_ifindex (device));
|
|
}
|
|
|
|
gboolean
|
|
nm_device_update_hw_address (NMDevice *dev)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
|
|
gboolean changed = FALSE, permanent = FALSE;
|
|
|
|
priv->hw_addr_len = nm_device_get_hw_address_length (dev, &permanent);
|
|
|
|
/* If the address can't be changed, don't bother trying */
|
|
if (permanent)
|
|
return FALSE;
|
|
|
|
if (priv->hw_addr_len) {
|
|
int ifindex = nm_device_get_ip_ifindex (dev);
|
|
gsize addrlen;
|
|
const guint8 *binaddr;
|
|
|
|
g_return_val_if_fail (ifindex > 0, FALSE);
|
|
|
|
binaddr = nm_platform_link_get_address (ifindex, &addrlen);
|
|
|
|
if (addrlen != priv->hw_addr_len) {
|
|
nm_log_err (LOGD_HW | LOGD_DEVICE,
|
|
"(%s): hardware address is wrong length (got %zd, expected %d)",
|
|
nm_device_get_iface (dev), addrlen, priv->hw_addr_len);
|
|
} else {
|
|
changed = !!memcmp (priv->hw_addr, binaddr, addrlen);
|
|
if (changed) {
|
|
char *addrstr = nm_utils_hwaddr_ntoa_len (binaddr, priv->hw_addr_len);
|
|
|
|
memcpy (priv->hw_addr, binaddr, addrlen);
|
|
nm_log_dbg (LOGD_HW | LOGD_DEVICE,
|
|
"(%s): hardware address is %s",
|
|
nm_device_get_iface (dev), addrstr);
|
|
g_free (addrstr);
|
|
g_object_notify (G_OBJECT (dev), NM_DEVICE_HW_ADDRESS);
|
|
}
|
|
}
|
|
} else {
|
|
int i;
|
|
|
|
/* hw_addr_len is now 0; see if hw_addr was already empty */
|
|
for (i = 0; i < sizeof (priv->hw_addr) && !changed; i++) {
|
|
if (priv->hw_addr[i])
|
|
changed = TRUE;
|
|
}
|
|
if (changed) {
|
|
memset (priv->hw_addr, 0, sizeof (priv->hw_addr));
|
|
nm_log_dbg (LOGD_HW | LOGD_DEVICE,
|
|
"(%s): previous hardware address is no longer valid",
|
|
nm_device_get_iface (dev));
|
|
g_object_notify (G_OBJECT (dev), NM_DEVICE_HW_ADDRESS);
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
gboolean
|
|
nm_device_set_hw_addr (NMDevice *device, const guint8 *addr,
|
|
const char *detail, guint64 hw_log_domain)
|
|
{
|
|
const char *iface;
|
|
char *mac_str = NULL;
|
|
gboolean success = FALSE;
|
|
guint len;
|
|
const guint8 *cur_addr = nm_device_get_hw_address (device, &len);
|
|
|
|
g_return_val_if_fail (addr != NULL, FALSE);
|
|
|
|
iface = nm_device_get_iface (device);
|
|
|
|
/* Do nothing if current MAC is same */
|
|
if (cur_addr && !memcmp (cur_addr, addr, len)) {
|
|
nm_log_dbg (LOGD_DEVICE | hw_log_domain, "(%s): no MAC address change needed", iface);
|
|
return TRUE;
|
|
}
|
|
|
|
mac_str = nm_utils_hwaddr_ntoa_len (addr, len);
|
|
|
|
/* Can't change MAC address while device is up */
|
|
nm_device_take_down (device, FALSE);
|
|
|
|
success = nm_platform_link_set_address (nm_device_get_ip_ifindex (device), addr, len);
|
|
if (success) {
|
|
/* MAC address succesfully changed; update the current MAC to match */
|
|
nm_device_update_hw_address (device);
|
|
cur_addr = nm_device_get_hw_address (device, NULL);
|
|
if (memcmp (cur_addr, addr, len) == 0) {
|
|
nm_log_info (LOGD_DEVICE | hw_log_domain, "(%s): %s MAC address to %s",
|
|
iface, detail, mac_str);
|
|
} else {
|
|
nm_log_warn (LOGD_DEVICE | hw_log_domain, "(%s): new MAC address %s "
|
|
"not successfully set",
|
|
iface, mac_str);
|
|
success = FALSE;
|
|
}
|
|
} else {
|
|
nm_log_warn (LOGD_DEVICE | hw_log_domain, "(%s): failed to %s MAC address to %s",
|
|
iface, detail, mac_str);
|
|
}
|
|
nm_device_bring_up (device, TRUE, NULL);
|
|
g_free (mac_str);
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* nm_device_add_pending_action():
|
|
* @device: the #NMDevice to add the pending action to
|
|
* @action: a static string that identifies the action
|
|
*
|
|
* Adds a pending action to the device.
|
|
*/
|
|
void
|
|
nm_device_add_pending_action (NMDevice *device, const char *action)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
GSList *iter;
|
|
guint count;
|
|
|
|
g_return_if_fail (action);
|
|
|
|
/* Shouldn't ever add the same pending action twice */
|
|
for (iter = priv->pending_actions; iter; iter = iter->next) {
|
|
if (!strcmp (action, iter->data)) {
|
|
nm_log_warn (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s' already added",
|
|
nm_device_get_iface (device),
|
|
g_slist_length (priv->pending_actions),
|
|
action);
|
|
g_return_if_reached ();
|
|
}
|
|
}
|
|
|
|
priv->pending_actions = g_slist_append (priv->pending_actions, g_strdup (action));
|
|
count = g_slist_length (priv->pending_actions);
|
|
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s'",
|
|
nm_device_get_iface (device),
|
|
count,
|
|
action);
|
|
|
|
if (count == 1)
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_HAS_PENDING_ACTION);
|
|
}
|
|
|
|
/**
|
|
* nm_device_remove_pending_action():
|
|
* @device: the #NMDevice to remove the pending action from
|
|
* @action: a static string that identifies the action
|
|
*
|
|
* Removes a pending action previously added by nm_device_add_pending_action().
|
|
*/
|
|
void
|
|
nm_device_remove_pending_action (NMDevice *device, const char *action)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
GSList *iter;
|
|
|
|
g_return_if_fail (action);
|
|
|
|
/* Shouldn't ever add the same pending action twice */
|
|
for (iter = priv->pending_actions; iter; iter = iter->next) {
|
|
if (!strcmp (action, iter->data)) {
|
|
g_free (iter->data);
|
|
priv->pending_actions = g_slist_delete_link (priv->pending_actions, iter);
|
|
nm_log_dbg (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s'",
|
|
nm_device_get_iface (device),
|
|
g_slist_length (priv->pending_actions),
|
|
action);
|
|
|
|
if (priv->pending_actions == NULL)
|
|
g_object_notify (G_OBJECT (device), NM_DEVICE_HAS_PENDING_ACTION);
|
|
return;
|
|
}
|
|
}
|
|
|
|
nm_log_warn (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s' never added",
|
|
nm_device_get_iface (device),
|
|
g_slist_length (priv->pending_actions),
|
|
action);
|
|
g_return_if_reached ();
|
|
}
|
|
|
|
gboolean
|
|
nm_device_has_pending_action (NMDevice *device)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
return !!priv->pending_actions;
|
|
}
|
|
|
|
const char *
|
|
nm_device_get_physical_port_id (NMDevice *device)
|
|
{
|
|
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
|
|
|
|
return priv->physical_port_id;
|
|
}
|
|
|
|
/**
|
|
* nm_device_get_mtu:
|
|
* @device: the #NMDevice
|
|
*
|
|
* Returns: MTU of the #NMDevice
|
|
*/
|
|
guint32
|
|
nm_device_get_mtu (NMDevice *device)
|
|
{
|
|
return NM_DEVICE_GET_PRIVATE (device)->mtu;
|
|
}
|
|
|