diff options
Diffstat (limited to 'src/devices/nm-device.c')
| -rw-r--r-- | src/devices/nm-device.c | 7966 |
1 files changed, 7966 insertions, 0 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c new file mode 100644 index 00000000..4788a604 --- /dev/null +++ b/src/devices/nm-device.c @@ -0,0 +1,7966 @@ +/* -*- 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 <netlink/route/addr.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-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" + +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)) + +enum { + STATE_CHANGED, + AUTOCONNECT_ALLOWED, + AUTH_REQUEST, + IP4_CONFIG_CHANGED, + IP6_CONFIG_CHANGED, + REMOVED, + RECHECK_AUTO_ACTIVATE, + RECHECK_ASSUME, + 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 PENDING_ACTION_DHCP4 "dhcp4" +#define PENDING_ACTION_DHCP6 "dhcp6" +#define PENDING_ACTION_AUTOCONF6 "autoconf6" + +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 { + NMDevice *device; + guint idle_add_id; + int ifindex; +} DeleteOnDeactivateData; + +typedef struct { + 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; + + NMUnmanagedFlags unmanaged_flags; + gboolean is_nm_owned; /* whether the device is a device owned and created by NM */ + DeleteOnDeactivateData *delete_on_deactivate_data; /* data for scheduled cleanup when deleting link (g_idle_add) */ + + guint32 ip4_address; + + NMActRequest * queued_act_request; + NMActRequest * act_request; + guint act_source_id; + gpointer act_source_func; + guint act_source6_id; + gpointer act_source6_func; + guint recheck_assume_id; + struct { + guint call_id; + NMDeviceState post_state; + NMDeviceStateReason post_state_reason; + } dispatcher; + + /* 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 */ + 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 */ + + guint arp_round2_id; + 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; + NMSettingIP6ConfigPrivacy rdisc_use_tempaddr; + /* IP6 config from autoconf */ + NMIP6Config * ac_ip6_config; + + guint linklocal6_timeout_id; + + GHashTable * ip6_saved_properties; + + 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; +} 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_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure); +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 gboolean nm_device_get_default_unmanaged (NMDevice *device); + +static void _set_state_full (NMDevice *device, + NMDeviceState state, + NMDeviceStateReason reason, + gboolean quitting); + +/***********************************************************/ + +static GQuark +nm_device_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-device-error"); + return quark; +} + +#define NM_DEVICE_ERROR (nm_device_error_quark ()) + +/***********************************************************/ + +#define QUEUED_PREFIX "queued state change to " + +static const char *state_table[] = { + [NM_DEVICE_STATE_UNKNOWN] = QUEUED_PREFIX "unknown", + [NM_DEVICE_STATE_UNMANAGED] = QUEUED_PREFIX "unmanaged", + [NM_DEVICE_STATE_UNAVAILABLE] = QUEUED_PREFIX "unavailable", + [NM_DEVICE_STATE_DISCONNECTED] = QUEUED_PREFIX "disconnected", + [NM_DEVICE_STATE_PREPARE] = QUEUED_PREFIX "prepare", + [NM_DEVICE_STATE_CONFIG] = QUEUED_PREFIX "config", + [NM_DEVICE_STATE_NEED_AUTH] = QUEUED_PREFIX "need-auth", + [NM_DEVICE_STATE_IP_CONFIG] = QUEUED_PREFIX "ip-config", + [NM_DEVICE_STATE_IP_CHECK] = QUEUED_PREFIX "ip-check", + [NM_DEVICE_STATE_SECONDARIES] = QUEUED_PREFIX "secondaries", + [NM_DEVICE_STATE_ACTIVATED] = QUEUED_PREFIX "activated", + [NM_DEVICE_STATE_DEACTIVATING] = QUEUED_PREFIX "deactivating", + [NM_DEVICE_STATE_FAILED] = QUEUED_PREFIX "failed", +}; + +static const char * +queued_state_to_string (NMDeviceState state) +{ + if (state >= 0 && state < G_N_ELEMENTS (state_table)) + return state_table[state]; + return state_table[NM_DEVICE_STATE_UNKNOWN]; +} + +static const char * +state_to_string (NMDeviceState state) +{ + return queued_state_to_string (state) + strlen (QUEUED_PREFIX); +} + +static const char *reason_table[] = { + [NM_DEVICE_STATE_REASON_NONE] = "none", + [NM_DEVICE_STATE_REASON_NOW_MANAGED] = "managed", + [NM_DEVICE_STATE_REASON_NOW_UNMANAGED] = "unmanaged", + [NM_DEVICE_STATE_REASON_CONFIG_FAILED] = "config-failed", + [NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE] = "ip-config-unavailable", + [NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED] = "ip-config-expired", + [NM_DEVICE_STATE_REASON_NO_SECRETS] = "no-secrets", + [NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT] = "supplicant-disconnect", + [NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED] = "supplicant-config-failed", + [NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED] = "supplicant-failed", + [NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT] = "supplicant-timeout", + [NM_DEVICE_STATE_REASON_PPP_START_FAILED] = "ppp-start-failed", + [NM_DEVICE_STATE_REASON_PPP_DISCONNECT] = "ppp-disconnect", + [NM_DEVICE_STATE_REASON_PPP_FAILED] = "ppp-failed", + [NM_DEVICE_STATE_REASON_DHCP_START_FAILED] = "dhcp-start-failed", + [NM_DEVICE_STATE_REASON_DHCP_ERROR] = "dhcp-error", + [NM_DEVICE_STATE_REASON_DHCP_FAILED] = "dhcp-failed", + [NM_DEVICE_STATE_REASON_SHARED_START_FAILED] = "sharing-start-failed", + [NM_DEVICE_STATE_REASON_SHARED_FAILED] = "sharing-failed", + [NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED] = "autoip-start-failed", + [NM_DEVICE_STATE_REASON_AUTOIP_ERROR] = "autoip-error", + [NM_DEVICE_STATE_REASON_AUTOIP_FAILED] = "autoip-failed", + [NM_DEVICE_STATE_REASON_MODEM_BUSY] = "modem-busy", + [NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE] = "modem-no-dialtone", + [NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER] = "modem-no-carrier", + [NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT] = "modem-dial-timeout", + [NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED] = "modem-dial-failed", + [NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED] = "modem-init-failed", + [NM_DEVICE_STATE_REASON_GSM_APN_FAILED] = "gsm-apn-failed", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING] = "gsm-registration-idle", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED] = "gsm-registration-denied", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT] = "gsm-registration-timeout", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED] = "gsm-registration-failed", + [NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED] = "gsm-pin-check-failed", + [NM_DEVICE_STATE_REASON_FIRMWARE_MISSING] = "firmware-missing", + [NM_DEVICE_STATE_REASON_REMOVED] = "removed", + [NM_DEVICE_STATE_REASON_SLEEPING] = "sleeping", + [NM_DEVICE_STATE_REASON_CONNECTION_REMOVED] = "connection-removed", + [NM_DEVICE_STATE_REASON_USER_REQUESTED] = "user-requested", + [NM_DEVICE_STATE_REASON_CARRIER] = "carrier-changed", + [NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED] = "connection-assumed", + [NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE] = "supplicant-available", + [NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND] = "modem-not-found", + [NM_DEVICE_STATE_REASON_BT_FAILED] = "bluetooth-failed", + [NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED] = "gsm-sim-not-inserted", + [NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED] = "gsm-sim-pin-required", + [NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED] = "gsm-sim-puk-required", + [NM_DEVICE_STATE_REASON_GSM_SIM_WRONG] = "gsm-sim-wrong", + [NM_DEVICE_STATE_REASON_INFINIBAND_MODE] = "infiniband-mode", + [NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED] = "dependency-failed", + [NM_DEVICE_STATE_REASON_BR2684_FAILED] = "br2684-bridge-failed", + [NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE] = "modem-manager-unavailable", + [NM_DEVICE_STATE_REASON_SSID_NOT_FOUND] = "ssid-not-found", + [NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED] = "secondary-connection-failed", + [NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED] = "dcb-fcoe-failed", + [NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED] = "teamd-control-failed", + [NM_DEVICE_STATE_REASON_MODEM_FAILED] = "modem-failed", + [NM_DEVICE_STATE_REASON_MODEM_AVAILABLE] = "modem-available", + [NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT] = "sim-pin-incorrect", +}; + +static const char * +reason_to_string (NMDeviceStateReason reason) +{ + if (reason >= 0 && reason < G_N_ELEMENTS (reason_table)) + return reason_table[reason]; + return reason_table[NM_DEVICE_STATE_REASON_UNKNOWN]; +} + +/***********************************************************/ + +static inline gboolean +nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value) +{ + return nm_platform_sysctl_set (nm_utils_ip6_property_path (nm_device_get_ip_iface (self), property), value); +} + +static gboolean +device_has_capability (NMDevice *device, NMDeviceCapabilities caps) +{ + return !!(NM_DEVICE_GET_PRIVATE (device)->capabilities & caps); +} + +/***********************************************************/ + +void +nm_device_dbus_export (NMDevice *device) +{ + static guint32 devcount = 0; + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (device)); + + priv = NM_DEVICE_GET_PRIVATE (device); + g_return_if_fail (priv->path == NULL); + + priv->path = g_strdup_printf ("/org/freedesktop/NetworkManager/Devices/%d", devcount++); + nm_log_info (LOGD_DEVICE, "(%s): exported as %s", priv->iface, priv->path); + nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->path, device); +} + +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; +} + +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); + if (!g_strcmp0 (iface, priv->ip_iface)) + return; + + 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); + } + } + + /* We don't care about any saved values from the old iface */ + g_hash_table_remove_all (priv->ip6_saved_properties); + + /* 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); +} + +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; +} + +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; +} + +gboolean +nm_device_has_carrier (NMDevice *device) +{ + return NM_DEVICE_GET_PRIVATE (device)->carrier; +} + +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; +} + +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 const char * +nm_device_get_physical_port_id (NMDevice *device) +{ + return NM_DEVICE_GET_PRIVATE (device)->physical_port_id; +} + +/***********************************************************/ + +static gboolean +nm_device_uses_generated_connection (NMDevice *self) +{ + NMConnection *connection; + + connection = nm_device_get_connection (self); + if (!connection) + return FALSE; + return nm_settings_connection_get_nm_generated (NM_SETTINGS_CONNECTION (connection)); +} + +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: (allow-none): 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; + gboolean configure; + + g_return_val_if_fail (dev != NULL, FALSE); + g_return_val_if_fail (slave != NULL, 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; + + if (info->enslaved) + success = TRUE; + else { + configure = (info->configure && connection != NULL); + if (configure) + g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE); + + success = NM_DEVICE_GET_CLASS (dev)->enslave_slave (dev, slave, connection, 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 + * @configure: whether @dev needs to actually release @slave + * @reason: the state change reason for the @slave + * + * If @dev is capable of enslaving other devices (ie it's a bridge, bond, team, + * etc) then this function releases the previously enslaved @slave and/or + * updates the state of @dev and @slave to reflect its release. + * + * 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, gboolean configure, NMDeviceStateReason reason) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + SlaveInfo *info; + gboolean success = FALSE; + + 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; + priv->slaves = g_slist_remove (priv->slaves, info); + + if (info->enslaved) { + success = NM_DEVICE_GET_CLASS (dev)->release_slave (dev, slave, configure); + /* The release_slave() implementation logs success/failure (in the + * correct device-specific log domain), so we don't have to do anything. + */ + } + + if (!configure) { + g_warn_if_fail (reason == NM_DEVICE_STATE_REASON_NONE); + reason = NM_DEVICE_STATE_REASON_NONE; + } else if (reason == NM_DEVICE_STATE_REASON_NONE) { + g_warn_if_reached (); + reason = NM_DEVICE_STATE_REASON_UNKNOWN; + } + nm_device_slave_notify_release (info->slave, reason); + + 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); + + /* ignore-carrier devices ignore all carrier-down events */ + if (priv->ignore_carrier && !carrier) + return; + + if (priv->is_master) { + /* 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 if (priv->state == NM_DEVICE_STATE_DISCONNECTED) { + /* If the device is already in DISCONNECTED state without a carrier + * (probably because it is tagged for carrier ignore) ensure that + * when the carrier appears, auto connections are rechecked for + * the device. + */ + nm_device_emit_recheck_auto_activate (device); + } + } else { + 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 if (priv->state >= NM_DEVICE_STATE_DISCONNECTED) { + nm_device_queue_state (device, NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_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); + + nm_log_dbg (LOGD_DEVICE, "(%s): link disconnected (calling deferred action) (id=%u)", + nm_device_get_iface (device), priv->carrier_defer_id); + + 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; +} + +static void +link_disconnect_action_cancel (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->carrier_defer_id) { + g_source_remove (priv->carrier_defer_id); + nm_log_dbg (LOGD_DEVICE, "(%s): link disconnected (canceling deferred action) (id=%u)", + nm_device_get_iface (self), priv->carrier_defer_id); + priv->carrier_defer_id = 0; + } +} + +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); + link_disconnect_action_cancel (device); + 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", TRUE); + } + } 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); + nm_log_dbg (LOGD_DEVICE, "(%s): link disconnected (deferring action for %d seconds) (id=%u)", + iface, LINK_DISCONNECT_DELAY, priv->carrier_defer_id); + } +} + +static void +update_for_ip_ifname_change (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + g_hash_table_remove_all (priv->ip6_saved_properties); + + if (priv->dhcp4_client) { + if (!nm_device_dhcp4_renew (device, FALSE)) { + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DHCP_FAILED); + return; + } + } + if (priv->dhcp6_client) { + if (!nm_device_dhcp6_renew (device, FALSE)) { + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_DHCP_FAILED); + return; + } + } + if (priv->rdisc) { + /* FIXME: todo */ + } + if (priv->dnsmasq_manager) { + /* FIXME: todo */ + } +} + +static void +device_link_changed (NMDevice *device, NMPlatformLink *info) +{ + NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + gboolean ip_ifname_changed = FALSE; + + 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 (info->name[0] && strcmp (priv->iface, info->name) != 0) { + nm_log_info (LOGD_DEVICE, "(%s): interface index %d renamed iface from '%s' to '%s'", + priv->iface, priv->ifindex, priv->iface, info->name); + g_free (priv->iface); + priv->iface = g_strdup (info->name); + + /* If the device has no explicit ip_iface, then changing iface changes ip_iface too. */ + ip_ifname_changed = !priv->ip_iface; + + g_object_notify (G_OBJECT (device), NM_DEVICE_IFACE); + if (ip_ifname_changed) + g_object_notify (G_OBJECT (device), NM_DEVICE_IP_IFACE); + + /* Re-match available connections against the new interface name */ + nm_device_recheck_available_connections (device); + + /* Let any connections that use the new interface name have a chance + * to auto-activate on the device. + */ + nm_device_emit_recheck_auto_activate (device); + } + + /* Update slave status for external changes */ + if (info->master && !priv->enslaved) { + NMDevice *master; + + master = nm_manager_get_device_by_ifindex (nm_manager_get (), info->master); + if (master && NM_DEVICE_GET_CLASS (master)->enslave_slave) { + g_clear_object (&priv->master); + priv->master = g_object_ref (master); + nm_device_master_add_slave (master, device, FALSE); + nm_device_enslave_slave (master, device, NULL); + } else if (master) { + nm_log_info (LOGD_DEVICE, "(%s): enslaved to non-master-type device %s; ignoring", + nm_device_get_iface (device), + nm_device_get_iface (master)); + } else { + nm_log_warn (LOGD_DEVICE, "(%s): enslaved to unknown device %d %s", + nm_device_get_iface (device), + info->master, + nm_platform_link_get_name (info->master)); + } + } else if (priv->enslaved && !info->master) + nm_device_release_one_slave (priv->master, device, FALSE, NM_DEVICE_STATE_REASON_NONE); + + if (klass->link_changed) + klass->link_changed (device, info); + + + /* Update DHCP, etc, if needed */ + if (ip_ifname_changed) + update_for_ip_ifname_change (device); +} + +static void +device_ip_link_changed (NMDevice *device, NMPlatformLink *info) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + if (info->name[0] && g_strcmp0 (priv->ip_iface, info->name)) { + nm_log_info (LOGD_DEVICE, "(%s): interface index %d renamed ip_iface (%d) from '%s' to '%s'", + priv->iface, priv->ifindex, nm_device_get_ip_ifindex (device), + priv->ip_iface, info->name); + g_free (priv->ip_iface); + priv->ip_iface = g_strdup (info->name); + + g_object_notify (G_OBJECT (device), NM_DEVICE_IP_IFACE); + update_for_ip_ifname_change (device); + } +} + +static void +link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *info, NMPlatformSignalChangeType change_type, NMPlatformReason reason, NMDevice *device) +{ + if (change_type != NM_PLATFORM_SIGNAL_CHANGED) + 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 (ifindex == nm_device_get_ifindex (device)) + device_link_changed (device, info); + else if (ifindex == nm_device_get_ip_ifindex (device)) + device_ip_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); +} + +/** + * nm_device_notify_component_added(): + * @device: the #NMDevice + * @component: the component being added by a plugin + * + * Called by the manager to notify the device that a new component has + * been found. The device implementation should return %TRUE if it + * wishes to claim the component, or %FALSE if it cannot. + * + * Returns: %TRUE to claim the component, %FALSE if the component cannot be + * claimed. + */ +gboolean +nm_device_notify_component_added (NMDevice *device, GObject *component) +{ + if (NM_DEVICE_GET_CLASS (device)->component_added) + return NM_DEVICE_GET_CLASS (device)->component_added (device, component); + return FALSE; +} + +/** + * nm_device_owns_iface(): + * @device: the #NMDevice + * @iface: an interface name + * + * Called by the manager to ask if the device or any of its components owns + * @iface. For example, a WWAN implementation would return %TRUE for an + * ethernet interface name that was owned by the WWAN device's modem component, + * because that ethernet interface is controlled by the WWAN device and cannot + * be used independently of the WWAN device. + * + * Returns: %TRUE if @device or it's components owns the interface name, + * %FALSE if not + */ +gboolean +nm_device_owns_iface (NMDevice *device, const char *iface) +{ + if (NM_DEVICE_GET_CLASS (device)->owns_iface) + return NM_DEVICE_GET_CLASS (device)->owns_iface (device, iface); + return FALSE; +} + +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)); + + /* 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, TRUE, reason); + /* 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 + */ +static 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_CLASS (dev)->enslave_slave != NULL, FALSE); + + if (configure) + g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, 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_append (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; + } + } +} + +/* release all slaves */ +static void +nm_device_master_release_slaves (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMDeviceStateReason reason; + + /* Don't release the slaves if this connection doesn't belong to NM. */ + if (nm_device_uses_generated_connection (self)) + return; + + reason = priv->state_reason; + if (priv->state == NM_DEVICE_STATE_FAILED) + reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED; + + while (priv->slaves) { + SlaveInfo *info = priv->slaves->data; + + nm_device_release_one_slave (self, info->slave, TRUE, reason); + } +} + +/** + * 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); + gboolean activating = (priv->state == NM_DEVICE_STATE_IP_CONFIG); + + g_assert (priv->master); + + if (!priv->enslaved) { + if (success) { + if (activating) { + nm_log_info (LOGD_DEVICE, + "Activation (%s) connection '%s' enslaved, continuing activation", + nm_device_get_iface (dev), + nm_connection_get_id (connection)); + } else { + nm_log_info (LOGD_DEVICE, + "(%s): enslaved to %s", + nm_device_get_iface (dev), + nm_device_get_iface (priv->master)); + } + + priv->enslaved = TRUE; + g_object_notify (G_OBJECT (dev), NM_DEVICE_MASTER); + } else if (activating) { + nm_log_warn (LOGD_DEVICE, + "Activation (%s) connection '%s' could not be enslaved", + nm_device_get_iface (dev), + nm_connection_get_id (connection)); + } + } + + if (activating) { + 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); + } else + nm_device_queue_recheck_assume (dev); +} + +/** + * 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 ( reason != NM_DEVICE_STATE_REASON_NONE + && 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); + } else { + nm_log_info (LOGD_DEVICE, + "(%s): released from master %s", + nm_device_get_iface (dev), + nm_device_get_iface (priv->master)); + } + + 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; +} + +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_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); +} + +gboolean +nm_device_get_autoconnect (NMDevice *device) +{ + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); + + return NM_DEVICE_GET_PRIVATE (device)->autoconnect; +} + +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_set_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); + g_value_unset (&instance); + 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, FALSE); +} + +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; + GError *error = NULL; + + /* 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; + } + + if (ifindex) + master_ifindex = nm_platform_link_get_master (ifindex); + if (master_ifindex) { + NMDevice *master; + + master = nm_manager_get_device_by_ifindex (nm_manager_get (), master_ifindex); + if (!master || !nm_device_get_act_request (master)) { + nm_log_dbg (LOGD_DEVICE, "(%s): cannot generate connection for slave before its master (%s)", + ifname, nm_platform_link_get_name (master_ifindex)); + 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_INTERFACE_NAME, ifname, + 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 (master_ifindex) { + 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_ip4_config_create_setting (priv->ip4_config); + nm_connection_add_setting (connection, s_ip4); + + s_ip6 = nm_ip6_config_create_setting (priv->ip6_config); + nm_connection_add_setting (connection, s_ip6); + } + + klass->update_connection (device, connection); + + /* Check the connection in case of update_connection() bug. */ + if (!nm_connection_verify (connection, &error)) { + nm_log_err (LOGD_DEVICE, "(%s): Generated connection does not verify: %s", + nm_device_get_iface (device), error->message); + g_clear_error (&error); + g_object_unref (connection); + return 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_log_dbg (LOGD_DEVICE, "(%s): ignoring generated connection (no IP and not slave)", 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) +{ + 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) + return FALSE; + + return TRUE; +} + +/** + * nm_device_check_connection_compatible: + * @device: an #NMDevice + * @connection: an #NMConnection + * + * 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) +{ + 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); +} + +static gboolean +string_in_list (const char *str, const char **array, gsize array_len) +{ + gsize i; + + for (i = 0; i < array_len; i++) { + if (strcmp (str, array[i]) == 0) + return TRUE; + } + return FALSE; +} + +/** + * nm_device_can_assume_connections: + * @device: #NMDevice instance + * + * This is a convenience function to determine whether connection assumption + * is available for this device. + * + * Returns: %TRUE if the device is capable of assuming connections, %FALSE if not + */ +static gboolean +nm_device_can_assume_connections (NMDevice *device) +{ + return !!NM_DEVICE_GET_CLASS (device)->update_connection; +} + +/** + * nm_device_can_assume_active_connection: + * @device: #NMDevice instance + * + * This is a convenience function to determine whether the device's active + * connection can be assumed if NetworkManager restarts. This method returns + * %TRUE if and only if the device can assume connections, and the device has + * an active connection, and that active connection can be assumed. + * + * Returns: %TRUE if the device's active connection can be assumed, or %FALSE + * if there is no active connection or the active connection cannot be + * assumed. + */ +gboolean +nm_device_can_assume_active_connection (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMConnection *connection; + const char *method; + const char *assumable_ip6_methods[] = { + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_DHCP, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + }; + const char *assumable_ip4_methods[] = { + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + }; + + if (!nm_device_can_assume_connections (device)) + return FALSE; + + connection = nm_device_get_connection (device); + if (!connection) + return FALSE; + + /* Can't assume connections that aren't yet configured + * FIXME: what about bridges/bonds waiting for slaves? + */ + if (priv->state < NM_DEVICE_STATE_IP_CONFIG) + return FALSE; + if (priv->ip4_state != IP_DONE && priv->ip6_state != IP_DONE) + return FALSE; + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); + if (!string_in_list (method, assumable_ip6_methods, G_N_ELEMENTS (assumable_ip6_methods))) + return FALSE; + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (!string_in_list (method, assumable_ip4_methods, G_N_ELEMENTS (assumable_ip4_methods))) + return FALSE; + + return TRUE; +} + +static gboolean +nm_device_emit_recheck_assume (gpointer self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->recheck_assume_id = 0; + if (!nm_device_get_act_request (self) && (priv->ip4_config || priv->ip6_config)) + g_signal_emit (self, signals[RECHECK_ASSUME], 0); + return G_SOURCE_REMOVE; +} + +void +nm_device_queue_recheck_assume (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (nm_device_can_assume_connections (self) && !priv->recheck_assume_id) + priv->recheck_assume_id = g_idle_add (nm_device_emit_recheck_assume, self); +} + +void +nm_device_emit_recheck_auto_activate (NMDevice *self) +{ + g_signal_emit (self, signals[RECHECK_AUTO_ACTIVATE], 0); +} + +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; + + /* Notify the new ActiveConnection along with the state change */ + g_object_notify (G_OBJECT (self), NM_DEVICE_ACTIVE_CONNECTION); + + 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; + NMDeviceState slave_state = nm_device_get_state (info->slave); + + if (slave_state == NM_DEVICE_STATE_IP_CONFIG) + nm_device_enslave_slave (self, info->slave, nm_device_get_connection (info->slave)); + else if ( nm_device_uses_generated_connection (self) + && slave_state <= NM_DEVICE_STATE_DISCONNECTED) + nm_device_queue_recheck_assume (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; + address.source = NM_PLATFORM_SOURCE_IP4LL; + 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; + route.source = NM_PLATFORM_SOURCE_IP4LL; + route.metric = nm_device_get_priority (self); + 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); +} + +/* default to installed helper, but can be modified for testing */ +const char *nm_device_autoipd_helper_path = LIBEXECDIR "/nm-avahi-autoipd.action"; + +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_cleanup (NMDevice *self, gboolean stop, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + 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; + } + + nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE); + + if (stop) + nm_dhcp_client_stop (priv->dhcp4_client, release); + + g_clear_object (&priv->dhcp4_client); + } + + if (priv->dhcp4_config) { + g_clear_object (&priv->dhcp4_config); + g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG); + } +} + +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), + nm_device_get_priority (self)); + } + + /* 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, + 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; + GByteArray *tmp = NULL; + + s_ip4 = nm_connection_get_setting_ip4_config (connection); + + /* 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 (nm_dhcp_manager_get (), + nm_device_get_ip_iface (self), + tmp, + nm_connection_get_uuid (connection), + nm_device_get_priority (self), + s_ip4, + priv->dhcp_timeout, + priv->dhcp_anycast_address); + + 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, + NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, + G_CALLBACK (dhcp4_state_changed), + self); + priv->dhcp4_timeout_sigid = g_signal_connect (priv->dhcp4_client, + NM_DHCP_CLIENT_SIGNAL_TIMEOUT, + G_CALLBACK (dhcp4_timeout), + self); + + nm_device_add_pending_action (self, PENDING_ACTION_DHCP4, TRUE); + + /* 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 gboolean +reserve_shared_ip (NMSettingIP4Config *s_ip4, NMPlatformIP4Address *address) +{ + if (G_UNLIKELY (shared_ips == NULL)) + shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal); + + memset (address, 0, sizeof (*address)); + + if (s_ip4 && nm_setting_ip4_config_get_num_addresses (s_ip4)) { + /* Use the first user-supplied address */ + NMIP4Address *user = nm_setting_ip4_config_get_address (s_ip4, 0); + + g_assert (user); + address->address = nm_ip4_address_get_address (user); + address->plen = nm_ip4_address_get_prefix (user); + } else { + /* Find an unused address in the 10.42.x.x range */ + 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 FALSE; + } + } + address->address = start + count; + address->plen = 24; + + g_hash_table_insert (shared_ips, + GUINT_TO_POINTER (address->address), + GUINT_TO_POINTER (TRUE)); + } + + return TRUE; +} + +static NMIP4Config * +shared4_new_config (NMDevice *self, NMConnection *connection, NMDeviceStateReason *reason) +{ + NMIP4Config *config = NULL; + NMPlatformIP4Address address; + + g_return_val_if_fail (self != NULL, NULL); + + if (!reserve_shared_ip (nm_connection_get_setting_ip4_config (connection), &address)) { + *reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE; + return NULL; + } + + config = nm_ip4_config_new (); + address.source = NM_PLATFORM_SOURCE_SHARED; + 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 (address.address), + 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 + && priv->is_master + && !priv->carrier) { + 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, connection, 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_cleanup (NMDevice *self, gboolean stop, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE; + g_clear_object (&priv->dhcp6_ip6_config); + + 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; + } + + nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE); + + if (stop) + nm_dhcp_client_stop (priv->dhcp6_client, release); + + g_clear_object (&priv->dhcp6_client); + } + + if (priv->dhcp6_config) { + g_clear_object (&priv->dhcp6_config); + g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG); + } +} + +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), + nm_device_get_priority (self)); + } + + nm_ip6_config_addresses_sort (composite, + priv->rdisc ? priv->rdisc_use_tempaddr : NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); + + 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, 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) +{ + NMSettingIP6Config *s_ip6; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + GByteArray *tmp = NULL; + + if (!connection) { + connection = nm_device_get_connection (self); + g_assert (connection); + } + + /* Begin a DHCP transaction on the interface */ + + /* 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 (nm_dhcp_manager_get (), + nm_device_get_ip_iface (self), + tmp, + nm_connection_get_uuid (connection), + nm_device_get_priority (self), + nm_connection_get_setting_ip6_config (connection), + priv->dhcp_timeout, + priv->dhcp_anycast_address, + (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, + NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, + G_CALLBACK (dhcp6_state_changed), + self); + priv->dhcp6_timeout_sigid = g_signal_connect (priv->dhcp6_client, + NM_DHCP_CLIENT_SIGNAL_TIMEOUT, + G_CALLBACK (dhcp6_timeout), + self); + + s_ip6 = nm_connection_get_setting_ip6_config (connection); + if (!nm_setting_ip6_config_get_may_fail (s_ip6) || + !strcmp (nm_setting_ip6_config_get_method (s_ip6), NM_SETTING_IP6_CONFIG_METHOD_DHCP)) + nm_device_add_pending_action (self, PENDING_ACTION_DHCP6, TRUE); + + /* 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; +} + +gboolean +nm_device_dhcp6_renew (NMDevice *self, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMActStageReturn ret; + NMDeviceStateReason reason; + NMConnection *connection; + + g_return_val_if_fail (priv->dhcp6_client != NULL, FALSE); + + nm_log_info (LOGD_DHCP6, "(%s): DHCPv6 lease renewal requested", + nm_device_get_iface (self)); + + /* Terminate old DHCP instance and release the old lease */ + dhcp6_cleanup (self, TRUE, release); + + connection = nm_device_get_connection (self); + g_assert (connection); + + /* Start DHCP again on the interface */ + ret = dhcp6_start (self, connection, priv->dhcp6_mode, &reason); + + return (ret != NM_ACT_STAGE_RETURN_FAILURE); +} + +/******************************************/ + +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 +print_support_extended_ifa_flags (NMSettingIP6ConfigPrivacy use_tempaddr) +{ + static gint8 warn = 0; + static gint8 s_libnl = -1, s_kernel; + + if (warn >= 2) + return; + + if (s_libnl == -1) { + s_libnl = !!nm_platform_check_support_libnl_extended_ifa_flags (); + s_kernel = !!nm_platform_check_support_kernel_extended_ifa_flags (); + + if (s_libnl && s_kernel) { + nm_log_dbg (LOGD_IP6, "kernel and libnl support extended IFA_FLAGS (needed by NM for IPv6 private addresses)"); + warn = 2; + return; + } + } + + if ( use_tempaddr != NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR + && use_tempaddr != NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR) { + if (warn == 0) { + nm_log_dbg (LOGD_IP6, "%s%s%s %s not support extended IFA_FLAGS (needed by NM for IPv6 private addresses)", + !s_kernel ? "kernel" : "", + !s_kernel && !s_libnl ? " and " : "", + !s_libnl ? "libnl" : "", + !s_kernel && !s_libnl ? "do" : "does"); + warn = 1; + } + return; + } + + if (!s_libnl && !s_kernel) { + nm_log_warn (LOGD_IP6, "libnl and the kernel do not support extended IFA_FLAGS needed by NM for " + "IPv6 private addresses. This feature is not available"); + } else if (!s_libnl) { + nm_log_warn (LOGD_IP6, "libnl does not support extended IFA_FLAGS needed by NM for " + "IPv6 private addresses. This feature is not available"); + } else if (!s_kernel) { + nm_log_warn (LOGD_IP6, "The kernel does not support extended IFA_FLAGS needed by NM for " + "IPv6 private addresses. This feature is not available"); + } + + warn = 2; +} + +static void +rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMConnection *connection; + int i; + NMDeviceStateReason reason; + static int system_support = -1; + guint ifa_flags = 0x00; + + if (system_support == -1) { + /* + * Check, if both libnl and the kernel are recent enough, + * to help user space handling RA. If it's not supported, + * we have no ipv6-privacy and must add autoconf addresses + * as /128. The reason for the /128 is to prevent the kernel + * from adding a prefix route for this address. + **/ + system_support = nm_platform_check_support_libnl_extended_ifa_flags () && + nm_platform_check_support_kernel_extended_ifa_flags (); + } + + if (system_support) + ifa_flags = IFA_F_NOPREFIXROUTE; + if (priv->rdisc_use_tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR + || priv->rdisc_use_tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR) + { + /* without system_support, this flag will be ignored. Still set it, doesn't seem to do any harm. */ + ifa_flags |= IFA_F_MANAGETEMPADDR; + } + + 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); + + /* rdisc->addresses contains at most max_addresses entries. + * This is different from what the kernel does, which + * also counts static and temporary addresses when checking + * max_addresses. + **/ + 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 = system_support ? 64 : 128; + address.timestamp = discovered_address->timestamp; + address.lifetime = discovered_address->lifetime; + address.preferred = discovered_address->preferred; + if (address.preferred > address.lifetime) + address.preferred = address.lifetime; + address.source = NM_PLATFORM_SOURCE_RDISC; + address.flags = ifa_flags; + + 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; + route.source = NM_PLATFORM_SOURCE_RDISC; + route.metric = nm_device_get_priority (device); + + 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; + } + } + } + + if (changed & NM_RDISC_CONFIG_HOP_LIMIT) { + char val[16]; + + g_snprintf (val, sizeof (val), "%d", rdisc->hop_limit); + nm_device_ipv6_sysctl_set (device, "hop_limit", val); + } + + nm_device_activate_schedule_ip6_config_result (device); +} + +static gboolean +addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr) +{ + 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; + } + + priv->rdisc_use_tempaddr = use_tempaddr; + print_support_extended_ifa_flags (use_tempaddr); + + if (!nm_setting_ip6_config_get_may_fail (nm_connection_get_setting_ip6_config (connection))) + nm_device_add_pending_action (self, PENDING_ACTION_AUTOCONF6, TRUE); + + /* 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_device_ipv6_sysctl_set (self, "accept_ra", "1"); + nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0"); + nm_device_ipv6_sysctl_set (self, "accept_ra_pinfo", "0"); + nm_device_ipv6_sysctl_set (self, "accept_ra_rtr_pref", "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; + } + + nm_device_remove_pending_action (self, PENDING_ACTION_AUTOCONF6, FALSE); + + g_clear_object (&priv->ac_ip6_config); + g_clear_object (&priv->rdisc); +} + +/******************************************/ + +static const char *ip6_properties_to_save[] = { + "accept_ra", + "accept_ra_defrtr", + "accept_ra_pinfo", + "accept_ra_rtr_pref", + "disable_ipv6", + "hop_limit", + "use_tempaddr", +}; + +static void +save_ip6_properties (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *ifname = nm_device_get_ip_iface (self); + char *value; + int i; + + g_hash_table_remove_all (priv->ip6_saved_properties); + + for (i = 0; i < G_N_ELEMENTS (ip6_properties_to_save); i++) { + value = nm_platform_sysctl_get (nm_utils_ip6_property_path (ifname, ip6_properties_to_save[i])); + if (value) { + g_hash_table_insert (priv->ip6_saved_properties, + (char *) ip6_properties_to_save[i], + value); + } + } +} + +static void +restore_ip6_properties (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, priv->ip6_saved_properties); + while (g_hash_table_iter_next (&iter, &key, &value)) + nm_device_ipv6_sysctl_set (self, key, value); +} + +static NMSettingIP6ConfigPrivacy +use_tempaddr_clamp (NMSettingIP6ConfigPrivacy use_tempaddr) +{ + switch (use_tempaddr) { + case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED: + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR: + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR: + return use_tempaddr; + default: + return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + } +} + +/* Get net.ipv6.conf.default.use_tempaddr value from /etc/sysctl.conf or + * /lib/sysctl.d/sysctl.conf + */ +static NMSettingIP6ConfigPrivacy +ip6_use_tempaddr (void) +{ + char *contents = NULL; + const char *group_name = "[forged_group]\n"; + char *sysctl_data = NULL; + GKeyFile *keyfile; + GError *error = NULL; + gint tmp; + NMSettingIP6ConfigPrivacy ret = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + + /* 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 NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + + /* 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 = use_tempaddr_clamp (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; + 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 + && priv->is_master + && !priv->carrier) { + 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_device_ipv6_sysctl_set (self, "disable_ipv6", "0"); + + /* 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. + */ + ip6_privacy = ip6_use_tempaddr (); + if (ip6_privacy == NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN) { + 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 = use_tempaddr_clamp (ip6_privacy); + + if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) { + if (!addrconf6_start (self, ip6_privacy)) { + /* 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 */ + + 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_device_ipv6_sysctl_set (self, "use_tempaddr", 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)), + master_device ? nm_device_get_iface (master_device) : "(unknown)"); + } + 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_change_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; + const char *zone; + + g_return_if_fail (NM_IS_DEVICE (self)); + + priv = NM_DEVICE_GET_PRIVATE (self); + g_return_if_fail (priv->act_request); + + /* 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, + FALSE, + fw_change_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 void +send_arps (NMDevice *self, const char *mode_arg) +{ + const char *argv[] = { "/sbin/arping", mode_arg, "-q", "-I", nm_device_get_ip_iface (self), "-c", "1", NULL, NULL }; + int ip_arg = G_N_ELEMENTS (argv) - 2; + NMConnection *connection; + NMSettingIP4Config *s_ip4; + int i, num; + NMIP4Address *addr; + guint32 ipaddr; + GError *error = NULL; + + connection = nm_device_get_connection (self); + if (!connection) + return; + s_ip4 = nm_connection_get_setting_ip4_config (connection); + if (!s_ip4) + return; + num = nm_setting_ip4_config_get_num_addresses (s_ip4); + + for (i = 0; i < num; i++) { + addr = nm_setting_ip4_config_get_address (s_ip4, i); + ipaddr = nm_ip4_address_get_address (addr); + argv[ip_arg] = (char *) nm_utils_inet4_ntop (ipaddr, NULL); + + nm_log_dbg (LOGD_DEVICE | LOGD_IP4, + "Running arping %s -I %s %s", + mode_arg, nm_device_get_iface (self), argv[ip_arg]); + g_spawn_async (NULL, (char **) argv, NULL, + G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, + nm_unblock_posix_signals, + NULL, NULL, &error); + if (error) { + nm_log_warn (LOGD_DEVICE | LOGD_IP4, + "Could not send ARP for local address %s: %s", + argv[ip_arg], error->message); + g_clear_error (&error); + } + } +} + +static gboolean +arp_announce_round2 (gpointer self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->arp_round2_id = 0; + + if ( priv->state >= NM_DEVICE_STATE_IP_CONFIG + && priv->state <= NM_DEVICE_STATE_ACTIVATED) + send_arps (self, "-U"); + + return G_SOURCE_REMOVE; +} + +static void +arp_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->arp_round2_id) { + g_source_remove (priv->arp_round2_id); + priv->arp_round2_id = 0; + } +} + +static void +arp_announce (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMConnection *connection; + NMSettingIP4Config *s_ip4; + int num; + + arp_cleanup (self); + + /* We only care about manually-configured addresses; DHCP- and autoip-configured + * ones should already have been seen on the network at this point. + */ + connection = nm_device_get_connection (self); + if (!connection) + return; + s_ip4 = nm_connection_get_setting_ip4_config (connection); + if (!s_ip4) + return; + num = nm_setting_ip4_config_get_num_addresses (s_ip4); + if (num == 0) + return; + + send_arps (self, "-A"); + priv->arp_round2_id = g_timeout_add_seconds (2, arp_announce_round2, self); +} + +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; + } + } + + /* If IPv4 wasn't the first to complete, and DHCP was used, then ensure + * dispatcher scripts get the DHCP lease information. + */ + if ( priv->dhcp4_client + && nm_device_activate_ip4_state_in_conf (self) + && (nm_device_get_state (self) > NM_DEVICE_STATE_IP_CONFIG)) { + /* Notify dispatcher scripts of new DHCP4 config */ + nm_dispatcher_call (DISPATCHER_ACTION_DHCP4_CHANGE, + nm_device_get_connection (self), + self, + NULL, + NULL, + NULL); + } + + arp_announce (self); + + /* Enter the IP_CHECK state if this is the first method to complete */ + priv->ip4_state = IP_DONE; + + nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE); + + 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); + guint level = (priv->ip6_state == IP_DONE) ? LOGL_DEBUG : LOGL_INFO; + 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 (LOGD_DEVICE, level, "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)) { + /* If IPv6 wasn't the first IP to complete, and DHCP was used, + * then ensure dispatcher scripts get the DHCP lease information. + */ + if ( priv->dhcp6_client + && nm_device_activate_ip6_state_in_conf (self) + && (nm_device_get_state (self) > NM_DEVICE_STATE_IP_CONFIG)) { + /* Notify dispatcher scripts of new DHCP6 config */ + nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE, + nm_device_get_connection (self), + self, + NULL, + NULL, + NULL); + } + + /* Enter the IP_CHECK state if this is the first method to complete */ + priv->ip6_state = IP_DONE; + + nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE); + nm_device_remove_pending_action (self, PENDING_ACTION_AUTOCONF6, FALSE); + + 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_warn (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 (LOGD_DEVICE, level, "Activation (%s) Stage 5 of 5 (IPv6 Commit) complete.", iface); + + return FALSE; +} + +void +nm_device_activate_schedule_ip6_config_result (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + guint level = (priv->ip6_state == IP_DONE) ? LOGL_DEBUG : LOGL_INFO; + + g_return_if_fail (NM_IS_DEVICE (self)); + + activation_source_schedule (self, nm_device_activate_ip6_config_commit, AF_INET6); + + nm_log (LOGD_DEVICE | LOGD_IP6, level, + "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 = 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_clear_object (&priv->act_request); + g_object_notify (G_OBJECT (self), NM_DEVICE_ACTIVE_CONNECTION); +} + +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); +} + +gboolean +nm_device_get_is_nm_owned (NMDevice *device) +{ + return NM_DEVICE_GET_PRIVATE (device)->is_nm_owned; +} + +void +nm_device_set_nm_owned (NMDevice *device) +{ + g_return_if_fail (NM_IS_DEVICE (device)); + + NM_DEVICE_GET_PRIVATE (device)->is_nm_owned = TRUE; +} + +/* + * 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) +{ + DeleteOnDeactivateData *data = user_data; + + if (data->device) { + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (data->device); + + g_object_remove_weak_pointer (G_OBJECT (data->device), (void **) &data->device); + priv->delete_on_deactivate_data = NULL; + } + + nm_log_dbg (LOGD_DEVICE, "delete_on_deactivate: cleanup and delete virtual link #%d (id=%u)", + data->ifindex, data->idle_add_id); + nm_platform_link_delete (data->ifindex); + g_free (data); + return FALSE; +} + +static void +delete_on_deactivate_unschedule (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->delete_on_deactivate_data) { + DeleteOnDeactivateData *data = priv->delete_on_deactivate_data; + + priv->delete_on_deactivate_data = NULL; + + g_source_remove (data->idle_add_id); + g_object_remove_weak_pointer (G_OBJECT (self), (void **) &data->device); + nm_log_dbg (LOGD_DEVICE, "delete_on_deactivate: cancel cleanup and delete virtual link #%d (id=%u)", + data->ifindex, data->idle_add_id); + g_free (data); + } +} + +static void +delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + DeleteOnDeactivateData *data; + + if (ifindex <= 0) + return; + if (!priv->is_nm_owned) + 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; + delete_on_deactivate_unschedule (self); /* always cancel and reschedule */ + + data = g_new (DeleteOnDeactivateData, 1); + g_object_add_weak_pointer (G_OBJECT (self), (void **) &data->device); + data->device = self; + data->ifindex = ifindex; + data->idle_add_id = g_idle_add (delete_on_deactivate_link_delete, data); + priv->delete_on_deactivate_data = data; + + nm_log_dbg (LOGD_DEVICE, "delete_on_deactivate: schedule cleanup and delete virtual link #%d for [%s] (id=%u)", + ifindex, nm_device_get_iface (self), data->idle_add_id); +} + +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); +} + +static void +_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)); + + delete_on_deactivate_unschedule (self); + + /* Move default unmanaged devices to DISCONNECTED state here */ + if (nm_device_get_default_unmanaged (self) && priv->state == NM_DEVICE_STATE_UNMANAGED) { + nm_device_state_changed (self, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_NOW_MANAGED); + } + + /* note: don't notify D-Bus of the new AC here, but do it later when + * changing state to PREPARE so that the two properties change together. + */ + priv->act_request = g_object_ref (req); + + nm_device_activate_schedule_stage1_device_prepare (self); +} + +void +nm_device_queue_activation (NMDevice *self, NMActRequest *req) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (!priv->act_request) { + /* Just activate immediately */ + _device_activate (self, req); + return; + } + + /* supercede any already-queued request */ + g_clear_object (&priv->queued_act_request); + priv->queued_act_request = g_object_ref (req); + + /* Deactivate existing activation request first */ + nm_log_info (LOGD_DEVICE, "(%s): disconnecting for new activation request.", + nm_device_get_iface (self)); + nm_device_state_changed (self, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_NONE); +} + +/* + * 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; +} + +/* 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); + 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); + } + + nm_log_dbg (LOGD_IP4, "(%s): set IP4Config instance (%s)", + ip_iface, nm_ip4_config_get_dbus_path (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 (nm_device_uses_generated_connection (self)) { + NMConnection *connection = nm_device_get_connection (self); + NMSetting *s_ip4; + + g_object_freeze_notify (G_OBJECT (connection)); + nm_connection_remove_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); + s_ip4 = nm_ip4_config_create_setting (priv->ip4_config); + nm_connection_add_setting (connection, s_ip4); + g_object_thaw_notify (G_OBJECT (connection)); + } + + nm_device_queue_recheck_assume (self); + } + + 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); + 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); + } + + nm_log_dbg (LOGD_IP4, "(%s): set IP6Config instance (%s)", + ip_iface, nm_ip6_config_get_dbus_path (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 (nm_device_uses_generated_connection (self)) { + NMConnection *connection = nm_device_get_connection (self); + NMSetting *s_ip6; + + g_object_freeze_notify (G_OBJECT (connection)); + nm_connection_remove_setting (connection, NM_TYPE_SETTING_IP6_CONFIG); + s_ip6 = nm_ip6_config_create_setting (priv->ip6_config); + nm_connection_add_setting (connection, s_ip6); + g_object_thaw_notify (G_OBJECT (connection)); + } + + nm_device_queue_recheck_assume (self); + } + + 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 +dispatcher_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->dispatcher.call_id) { + nm_dispatcher_call_cancel (priv->dispatcher.call_id); + priv->dispatcher.call_id = 0; + priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; + } +} + +static void +dispatcher_complete_proceed_state (guint call_id, gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + g_return_if_fail (call_id == priv->dispatcher.call_id); + + priv->dispatcher.call_id = 0; + nm_device_queue_state (self, priv->dispatcher.post_state, + priv->dispatcher.post_state_reason); + priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; +} + +/****************************************************************/ + +static void +ip_check_pre_up (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->dispatcher.call_id != 0) { + g_warn_if_reached (); + dispatcher_cleanup (self); + } + + priv->dispatcher.post_state = NM_DEVICE_STATE_SECONDARIES; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; + if (!nm_dispatcher_call (DISPATCHER_ACTION_PRE_UP, + nm_device_get_connection (self), + self, + dispatcher_complete_proceed_state, + self, + &priv->dispatcher.call_id)) { + /* Just proceed on errors */ + dispatcher_complete_proceed_state (0, self); + } +} + +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 pre_up */ + ip_check_gw_ping_cleanup (self); + ip_check_pre_up (self); +} + +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); + ip_check_pre_up (self); + 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 pre_up */ + if (!priv->gw_ping.pid) + ip_check_pre_up (self); +} + +/****************************************************************/ + +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", TRUE); + return G_SOURCE_REMOVE; +} + +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 > 0 ? nm_platform_link_is_up (ifindex) : TRUE; +} + +gboolean +nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + gboolean device_is_up = FALSE; + + g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); + + nm_log_dbg (LOGD_HW, "(%s): bringing up device.", nm_device_get_iface (self)); + + if (NM_DEVICE_GET_CLASS (self)->bring_up) { + if (!NM_DEVICE_GET_CLASS (self)->bring_up (self, no_firmware)) + return FALSE; + } + + device_is_up = nm_device_is_up (self); + if (block && !device_is_up) { + int ifindex = nm_device_get_ip_ifindex (self); + gint64 wait_until = nm_utils_get_monotonic_timestamp_us () + 10000 /* microseconds */; + + do { + g_usleep (200); + if (!nm_platform_link_refresh (ifindex)) + return FALSE; + device_is_up = nm_device_is_up (self); + } while (!device_is_up && nm_utils_get_monotonic_timestamp_us () < wait_until); + } + + if (!device_is_up) { + if (block) + nm_log_warn (LOGD_HW, "(%s): device not up after timeout!", nm_device_get_iface (self)); + else + nm_log_dbg (LOGD_HW, "(%s): device not up immediately", 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", TRUE); + } + priv->carrier_wait_id = g_timeout_add_seconds (5, carrier_wait_timeout, self); + nm_device_add_pending_action (self, "carrier wait", TRUE); + } + + /* 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 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 gboolean +bring_up (NMDevice *device, gboolean *no_firmware) +{ + int ifindex = nm_device_get_ip_ifindex (device); + gboolean result; + + if (ifindex <= 0) { + if (no_firmware) + *no_firmware = FALSE; + 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) +{ + gboolean device_is_up; + + g_return_if_fail (NM_IS_DEVICE (self)); + + nm_log_dbg (LOGD_HW, "(%s): taking down device.", nm_device_get_iface (self)); + + if (NM_DEVICE_GET_CLASS (self)->take_down) { + if (!NM_DEVICE_GET_CLASS (self)->take_down (self)) + return; + } + + device_is_up = nm_device_is_up (self); + if (block && device_is_up) { + int ifindex = nm_device_get_ip_ifindex (self); + gint64 wait_until = nm_utils_get_monotonic_timestamp_us () + 10000 /* microseconds */; + + do { + g_usleep (200); + if (!nm_platform_link_refresh (ifindex)) + return; + device_is_up = nm_device_is_up (self); + } while (device_is_up && nm_utils_get_monotonic_timestamp_us () < wait_until); + } + + if (device_is_up) { + if (block) + nm_log_warn (LOGD_HW, "(%s): device not down after timeout!", nm_device_get_iface (self)); + else + nm_log_dbg (LOGD_HW, "(%s): device not down immediately", nm_device_get_iface (self)); + } +} + +static gboolean +take_down (NMDevice *device) +{ + int ifindex = nm_device_get_ip_ifindex (device); + + if (ifindex > 0) + return nm_platform_link_set_down (ifindex); + + /* devices without ifindex are always up. */ + nm_log_dbg (LOGD_HW, "(%s): cannot take down device without ifindex", nm_device_get_iface (device)); + return FALSE; +} + +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; +} + +static NMIP4Config * +find_ip4_lease_config (NMDevice *device, + NMConnection *connection, + NMIP4Config *ext_ip4_config) +{ + 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 (nm_dhcp_manager_get (), + 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)) + 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, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); + 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, NMPlatformSignalChangeType change_type, 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)); + } +} + +static 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; + } +} + +/** + * nm_device_get_managed(): + * @device: the #NMDevice + * + * Returns: %TRUE if the device is managed + */ +gboolean +nm_device_get_managed (NMDevice *device) +{ + NMDevicePrivate *priv; + gboolean managed; + + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); + + priv = NM_DEVICE_GET_PRIVATE (device); + + /* Return the composite of all managed flags. However, if the device + * is a default-unmanaged device, and would be managed except for the + * default-unmanaged flag (eg, only NM_UNMANAGED_DEFAULT is set) then + * the device is managed whenever it's not in the UNMANAGED state. + */ + managed = !(priv->unmanaged_flags & ~NM_UNMANAGED_DEFAULT); + if (managed && (priv->unmanaged_flags & NM_UNMANAGED_DEFAULT)) + managed = (priv->state > NM_DEVICE_STATE_UNMANAGED); + + return managed; +} + +/** + * nm_device_get_unmanaged_flag(): + * @device: the #NMDevice + * + * Returns: %TRUE if the device is unmanaged for @flag. + */ +gboolean +nm_device_get_unmanaged_flag (NMDevice *device, NMUnmanagedFlags flag) +{ + return NM_DEVICE_GET_PRIVATE (device)->unmanaged_flags & flag; +} + +/** + * nm_device_get_default_unmanaged(): + * @device: the #NMDevice + * + * Returns: %TRUE if the device is by default unmanaged + */ +static gboolean +nm_device_get_default_unmanaged (NMDevice *device) +{ + return nm_device_get_unmanaged_flag (device, NM_UNMANAGED_DEFAULT); +} + +void +nm_device_set_unmanaged (NMDevice *device, + NMUnmanagedFlags flag, + gboolean unmanaged, + NMDeviceStateReason reason) +{ + NMDevicePrivate *priv; + gboolean was_managed, now_managed; + + g_return_if_fail (NM_IS_DEVICE (device)); + g_return_if_fail (flag <= NM_UNMANAGED_LAST); + + priv = NM_DEVICE_GET_PRIVATE (device); + + was_managed = nm_device_get_managed (device); + if (unmanaged) + priv->unmanaged_flags |= flag; + else + priv->unmanaged_flags &= ~flag; + now_managed = nm_device_get_managed (device); + + if (was_managed != now_managed) { + nm_log_dbg (LOGD_DEVICE, "(%s): now %s", + nm_device_get_iface (device), + unmanaged ? "unmanaged" : "managed"); + + g_object_notify (G_OBJECT (device), NM_DEVICE_MANAGED); + + if (unmanaged) + nm_device_state_changed (device, NM_DEVICE_STATE_UNMANAGED, reason); + else + nm_device_state_changed (device, NM_DEVICE_STATE_UNAVAILABLE, reason); + } +} + +void +nm_device_set_unmanaged_quitting (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + /* It's OK to block here because we're quitting */ + if (nm_device_is_activating (device) || priv->state == NM_DEVICE_STATE_ACTIVATED) + _set_state_full (device, NM_DEVICE_STATE_DEACTIVATING, NM_DEVICE_STATE_REASON_REMOVED, TRUE); + + nm_device_set_unmanaged (device, + NM_UNMANAGED_INTERNAL, + TRUE, + NM_DEVICE_STATE_REASON_REMOVED); +} + +/** + * nm_device_set_initial_unmanaged_flag(): + * @device: the #NMDevice + * @flag: an #NMUnmanagedFlag + * @unmanaged: %TRUE or %FALSE to set or clear @flag + * + * Like nm_device_set_unmanaged() but must be set before the device is exported + * and does not trigger state changes. Should only be used when initializing + * a device. + */ +void +nm_device_set_initial_unmanaged_flag (NMDevice *device, + NMUnmanagedFlags flag, + gboolean unmanaged) +{ + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (device)); + g_return_if_fail (flag <= NM_UNMANAGED_LAST); + + priv = NM_DEVICE_GET_PRIVATE (device); + g_return_if_fail (priv->path == NULL); + + if (unmanaged) + priv->unmanaged_flags |= flag; + else + priv->unmanaged_flags &= ~flag; +} + +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); + } +} + +/** + * nm_device_connection_is_available(): + * @device: the #NMDevice + * @connection: the #NMConnection to check for availability + * @allow_device_override: set to %TRUE to let the device do specific checks + * + * Check if @connection is available to be activated on @device. Normally this + * only checks if the connection is in @device's AvailableConnections property. + * If @allow_device_override is %TRUE then the device is asked to do specific + * checks that may bypass the AvailableConnections property. + * + * Returns: %TRUE if @connection can be activated on @device + */ +gboolean +nm_device_connection_is_available (NMDevice *device, + NMConnection *connection, + gboolean allow_device_override) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + gboolean available = FALSE; + + if (nm_device_get_default_unmanaged (device) && (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) + && NM_DEVICE_GET_CLASS (device)->check_connection_available (device, connection, NULL)) + return TRUE; + } + + available = !!g_hash_table_lookup (priv->available_connections, connection); + if (!available && allow_device_override) { + /* FIXME: hack for hidden WiFi becuase clients didn't consistently + * set the 'hidden' property to indicate hidden SSID networks. If + * activating but the network isn't available let the device recheck + * availability. + */ + if ( nm_device_check_connection_compatible (device, connection) + && NM_DEVICE_GET_CLASS (device)->check_connection_available_wifi_hidden) + available = NM_DEVICE_GET_CLASS (device)->check_connection_available_wifi_hidden (device, connection); + } + + return available; +} + +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)) { + 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)); +} + +/** + * nm_device_add_pending_action(): + * @device: the #NMDevice to add the pending action to + * @action: a static string that identifies the action + * @assert_not_yet_pending: if %TRUE, assert that the @action is currently not yet pending. + * Otherwise, ignore duplicate scheduling of the same action silently. + * + * Adds a pending action to the device. + * + * Returns: %TRUE if the action was added (and not already added before). %FALSE + * if the same action is already scheduled. In the latter case, the action was not scheduled + * a second time. + */ +gboolean +nm_device_add_pending_action (NMDevice *device, const char *action, gboolean assert_not_yet_pending) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + GSList *iter; + guint count = 0; + + g_return_val_if_fail (action, FALSE); + + /* Check if the action is already pending. Cannot add duplicate actions */ + for (iter = priv->pending_actions; iter; iter = iter->next) { + if (!strcmp (action, iter->data)) { + if (assert_not_yet_pending) { + nm_log_warn (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s' already pending", + nm_device_get_iface (device), + count + g_slist_length (iter), + action); + g_return_val_if_reached (FALSE); + } else { + nm_log_dbg (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s' already pending (expected)", + nm_device_get_iface (device), + count + g_slist_length (iter), + action); + } + return FALSE; + } + count++; + } + + priv->pending_actions = g_slist_append (priv->pending_actions, g_strdup (action)); + count++; + + 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); + + return TRUE; +} + +/** + * nm_device_remove_pending_action(): + * @device: the #NMDevice to remove the pending action from + * @action: a static string that identifies the action + * @assert_is_pending: if %TRUE, assert that the @action is pending. + * If %FALSE, don't do anything if the current action is not pending and + * return %FALSE. + * + * Removes a pending action previously added by nm_device_add_pending_action(). + * + * Returns: whether the @action was pending and is now removed. + */ +gboolean +nm_device_remove_pending_action (NMDevice *device, const char *action, gboolean assert_is_pending) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + GSList *iter; + guint count = 0; + + g_return_val_if_fail (action, FALSE); + + for (iter = priv->pending_actions; iter; iter = iter->next) { + if (!strcmp (action, iter->data)) { + nm_log_dbg (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s'", + nm_device_get_iface (device), + count + g_slist_length (iter->next), /* length excluding 'iter' */ + action); + g_free (iter->data); + priv->pending_actions = g_slist_delete_link (priv->pending_actions, iter); + if (priv->pending_actions == NULL) + g_object_notify (G_OBJECT (device), NM_DEVICE_HAS_PENDING_ACTION); + return TRUE; + } + count++; + } + + if (assert_is_pending) { + nm_log_warn (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s' not pending", + nm_device_get_iface (device), + count, + action); + g_return_val_if_reached (FALSE); + } else { + nm_log_dbg (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s' not pending (expected)", + nm_device_get_iface (device), + count, + action); + } + return FALSE; +} + +gboolean +nm_device_has_pending_action (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + return !!priv->pending_actions; +} + +/***********************************************************/ + +static void +_cleanup_generic_pre (NMDevice *self, gboolean deconfigure) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + /* Clean up when device was deactivated during call to firewall */ + if (priv->fw_manager) { + NMConnection *connection; + + if (priv->fw_call) { + nm_firewall_manager_cancel_call (priv->fw_manager, priv->fw_call); + priv->fw_call = NULL; + } + + connection = nm_device_get_connection (self); + if (deconfigure && connection) { + nm_firewall_manager_remove_from_zone (priv->fw_manager, + nm_device_get_ip_iface (self), + NULL); + } + } + + 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, deconfigure, FALSE); + arp_cleanup (self); + dhcp6_cleanup (self, deconfigure, FALSE); + linklocal6_cleanup (self); + addrconf6_cleanup (self); + dnsmasq_cleanup (self); + aipd_cleanup (self); +} + +static void +_cleanup_generic_post (NMDevice *self, gboolean deconfigure) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE; + + /* Clean up IP configs; this does not actually deconfigure the + * interface; the caller must flush routes and addresses explicitly. + */ + nm_device_set_ip4_config (self, NULL, TRUE, &ignored); + nm_device_set_ip6_config (self, NULL, TRUE, &ignored); + 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->ac_ip6_config); + g_clear_object (&priv->ext_ip6_config); + g_clear_object (&priv->vpn6_config); + g_clear_object (&priv->ip6_config); + + clear_act_request (self); + + /* Clear legacy IPv4 address property */ + if (priv->ip4_address) { + priv->ip4_address = 0; + g_object_notify (G_OBJECT (self), NM_DEVICE_IP4_ADDRESS); + } + + if (deconfigure) { + /* 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, nm_device_get_ip_ifindex (self)); + } + + /* ip_iface should be cleared 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); +} + +/* + * nm_device_cleanup + * + * Remove a device's routing table entries and IP addresses. + * + */ +static void +nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason) +{ + NMDevicePrivate *priv; + int ifindex; + + g_return_if_fail (NM_IS_DEVICE (self)); + + if (reason == NM_DEVICE_STATE_REASON_NOW_MANAGED) { + nm_log_info (LOGD_DEVICE, "(%s): preparing device", + nm_device_get_iface (self)); + } else { + 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); + + _cleanup_generic_pre (self, TRUE); + + /* Turn off kernel IPv6 */ + nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1"); + nm_device_ipv6_sysctl_set (self, "accept_ra", "0"); + nm_device_ipv6_sysctl_set (self, "use_tempaddr", "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); + + /* 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); + } + + _cleanup_generic_post (self, TRUE); +} + +/***********************************************************/ + +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 +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); +} + +static void +_set_state_full (NMDevice *device, + NMDeviceState state, + NMDeviceStateReason reason, + gboolean quitting) +{ + 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); + + dispatcher_cleanup (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); + g_clear_object (&priv->queued_act_request); + } + + /* 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_cleanup (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); + if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) { + nm_device_ipv6_sysctl_set (device, "disable_ipv6", "1"); + nm_device_ipv6_sysctl_set (device, "accept_ra_defrtr", "0"); + nm_device_ipv6_sysctl_set (device, "accept_ra_pinfo", "0"); + nm_device_ipv6_sysctl_set (device, "accept_ra_rtr_pref", "0"); + nm_device_ipv6_sysctl_set (device, "use_tempaddr", "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_cleanup (device, reason); + break; + case NM_DEVICE_STATE_DISCONNECTED: + if (old_state > NM_DEVICE_STATE_UNAVAILABLE) + nm_device_cleanup (device, reason); + break; + default: + break; + } + + /* Reset autoconnect flag when the device is activating or connected. */ + if ( state >= NM_DEVICE_STATE_PREPARE + && state <= NM_DEVICE_STATE_ACTIVATED) + priv->autoconnect = TRUE; + + 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 + && nm_device_get_default_unmanaged (device)) + nm_device_queue_state (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE); + } + break; + case NM_DEVICE_STATE_DEACTIVATING: + if (quitting) { + nm_dispatcher_call_sync (DISPATCHER_ACTION_PRE_DOWN, + nm_act_request_get_connection (req), + device); + } else { + priv->dispatcher.post_state = NM_DEVICE_STATE_DISCONNECTED; + priv->dispatcher.post_state_reason = reason; + if (!nm_dispatcher_call (DISPATCHER_ACTION_PRE_DOWN, + nm_act_request_get_connection (req), + device, + dispatcher_complete_proceed_state, + device, + &priv->dispatcher.call_id)) { + /* Just proceed on errors */ + dispatcher_complete_proceed_state (0, device); + } + } + break; + case NM_DEVICE_STATE_DISCONNECTED: + if (priv->queued_act_request) { + NMActRequest *queued_req; + + queued_req = priv->queued_act_request; + priv->queued_act_request = NULL; + _device_activate (device, queued_req); + g_object_unref (queued_req); + } else if ( old_state > NM_DEVICE_STATE_DISCONNECTED + && nm_device_get_default_unmanaged (device)) + 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, 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 (device); + + if ( (old_state == NM_DEVICE_STATE_ACTIVATED || old_state == NM_DEVICE_STATE_DEACTIVATING) + && (state != NM_DEVICE_STATE_DEACTIVATING)) { + if (quitting) + nm_dispatcher_call_sync (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), device); + else + nm_dispatcher_call (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), device, NULL, 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; +} + +void +nm_device_state_changed (NMDevice *device, + NMDeviceState state, + NMDeviceStateReason reason) +{ + _set_state_full (device, state, reason, 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), TRUE); + } 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); + + if (priv->queued_state.id && priv->queued_state.state == state) + return; + + /* Add pending action for the new state before clearing the queued states, so + * that we don't accidently pop all pending states and reach 'startup complete' */ + nm_device_add_pending_action (self, queued_state_to_string (state), TRUE); + + /* We should only ever have one delayed state transition at a time */ + if (priv->queued_state.id) { + 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_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), TRUE); + } + 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; +} + +/***********************************************************/ +/* NMConfigDevice interface related stuff */ + +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; +} + +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_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; +} + +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; +} + +/***********************************************************/ + +#define DEFAULT_AUTOCONNECT TRUE + +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->unmanaged_flags = NM_UNMANAGED_INTERNAL; + priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); + priv->ip6_saved_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); +} + +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; +} + +/* + * 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 GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + NMDevice *dev; + NMDevicePrivate *priv; + NMPlatform *platform; + 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->fw_manager = nm_firewall_manager_get (); + + device_get_driver_info (priv->iface, &priv->driver_version, &priv->firmware_version); + + /* Watch for external IP config changes */ + platform = nm_platform_get (); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), dev); + + 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); + + priv->con_provider = nm_connection_provider_get (); + g_assert (priv->con_provider); + g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_ADDED, + G_CALLBACK (cp_connection_added), + dev); + + g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_REMOVED, + G_CALLBACK (cp_connection_removed), + dev); + + g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_UPDATED, + G_CALLBACK (cp_connection_updated), + dev); + + G_OBJECT_CLASS (nm_device_parent_class)->constructed (object); +} + +static void +dispose (GObject *object) +{ + NMDevice *self = NM_DEVICE (object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMPlatform *platform; + + dispatcher_cleanup (self); + + _cleanup_generic_pre (self, FALSE); + + g_warn_if_fail (priv->slaves == NULL); + g_assert (priv->master_ready_id == 0); + + _cleanup_generic_post (self, FALSE); + + g_clear_pointer (&priv->ip6_saved_properties, g_hash_table_unref); + + if (priv->recheck_assume_id) { + g_source_remove (priv->recheck_assume_id); + priv->recheck_assume_id = 0; + } + + link_disconnect_action_cancel (self); + + if (priv->con_provider) { + g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_added, self); + g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_removed, self); + g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_updated, self); + priv->con_provider = NULL; + } + + g_hash_table_unref (priv->available_connections); + priv->available_connections = NULL; + + if (priv->carrier_wait_id) { + g_source_remove (priv->carrier_wait_id); + priv->carrier_wait_id = 0; + } + + g_clear_object (&priv->queued_act_request); + + 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); + + g_clear_object (&priv->fw_manager); + + 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); + + g_slist_free_full (priv->pending_actions, g_free); + g_clear_pointer (&priv->physical_port_id, 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; + } +} + +#define DBUS_TYPE_STATE_REASON_STRUCT (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INVALID)) + +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_config) + 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_config) + 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_TYPE_STATE_REASON_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->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_TYPE_STATE_REASON_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); + + signals[REMOVED] = + g_signal_new (NM_DEVICE_REMOVED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[RECHECK_AUTO_ACTIVATE] = + g_signal_new (NM_DEVICE_RECHECK_AUTO_ACTIVATE, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[RECHECK_ASSUME] = + g_signal_new (NM_DEVICE_RECHECK_ASSUME, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + 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); +} + |