/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gsystem-local-alloc.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-logging.h" #include "nm-activation-request.h" #include "nm-ip4-config.h" #include "nm-ip6-config.h" #include "nm-dnsmasq-manager.h" #include "nm-dhcp4-config.h" #include "nm-dhcp6-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-auth-utils.h" #include "nm-dbus-glib-types.h" #include "nm-dispatcher.h" #include "nm-config.h" #include "nm-dns-manager.h" #include "nm-core-internal.h" #include "nm-default-route-manager.h" #include "nm-device-logging.h" _LOG_DECLARE_SELF (NMDevice); static void impl_device_disconnect (NMDevice *self, DBusGMethodInvocation *context); static void impl_device_delete (NMDevice *self, DBusGMethodInvocation *context); #include "nm-device-glue.h" G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, G_TYPE_OBJECT) #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; char * hw_addr; 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; char * 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 */ gboolean ext_ip4_config_had_any_addresses; NMIP4Config * wwan_ip4_config; /* WWAN configuration */ struct { gboolean v4_has; gboolean v4_is_assumed; NMPlatformIP4Route v4; gboolean v6_has; gboolean v6_is_assumed; NMPlatformIP6Route v6; } default_route; /* DHCPv4 tracking */ NMDhcpClient * dhcp4_client; gulong dhcp4_state_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 */ NMFirewallPendingCall 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 * wwan_ip6_config; NMIP6Config * ext_ip6_config; /* Stuff added outside NM */ gboolean ext_ip6_config_had_any_addresses; gboolean nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */ NMRDisc * rdisc; gulong rdisc_changed_id; gulong rdisc_timeout_id; 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; 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 *self, NMIP4Config *config, guint32 default_route_metric, 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 *self, NMIP6Config *config, gboolean commit, NMDeviceStateReason *reason); static gboolean nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure); static void nm_device_slave_notify_enslave (NMDevice *self, gboolean success); static void nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason); static gboolean addrconf6_start_with_link_ready (NMDevice *self); static gboolean nm_device_get_default_unmanaged (NMDevice *self); static void _set_state_full (NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, gboolean quitting); static void nm_device_update_hw_address (NMDevice *self); /***********************************************************/ #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 ((gsize) 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 ((gsize) reason < G_N_ELEMENTS (reason_table)) return reason_table[reason]; return reason_table[NM_DEVICE_STATE_REASON_UNKNOWN]; } /***********************************************************/ 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 *self, NMDeviceCapabilities caps) { return !!(NM_DEVICE_GET_PRIVATE (self)->capabilities & caps); } /***********************************************************/ void nm_device_dbus_export (NMDevice *self) { static guint32 devcount = 0; NMDevicePrivate *priv; g_return_if_fail (NM_IS_DEVICE (self)); priv = NM_DEVICE_GET_PRIVATE (self); g_return_if_fail (priv->path == NULL); priv->path = g_strdup_printf ("/org/freedesktop/NetworkManager/Devices/%d", devcount++); _LOGI (LOGD_DEVICE, "exported as %s", priv->path); nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->path, self); } 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 (NM_IS_DEVICE (self), 0); 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 *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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_check_support_user_ipv6ll ()) nm_platform_link_set_user_ipv6ll_enabled (priv->ip_ifindex, TRUE); 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 */ _LOGW (LOGD_HW, "failed to look up interface index"); } } /* 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); } static gboolean get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *out_iid) { NMLinkType link_type; const guint8 *hwaddr = NULL; size_t hwaddr_len = 0; int ifindex; gboolean success; /* If we get here, we *must* have a kernel netdev, which implies an ifindex */ ifindex = nm_device_get_ip_ifindex (self); g_assert (ifindex); link_type = nm_platform_link_get_type (ifindex); g_return_val_if_fail (link_type > NM_LINK_TYPE_UNKNOWN, 0); hwaddr = nm_platform_link_get_address (ifindex, &hwaddr_len); if (!hwaddr_len) return FALSE; success = nm_utils_get_ipv6_interface_identifier (link_type, hwaddr, hwaddr_len, out_iid); if (!success) { _LOGW (LOGD_HW, "failed to generate interface identifier " "for link type %u hwaddr_len %zu", link_type, hwaddr_len); } return success; } static gboolean nm_device_get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *iid) { return NM_DEVICE_GET_CLASS (self)->get_ip_iface_identifier (self, iid); } 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(): * @self: the #NMDevice * * Returns: the device's routing priority. Lower numbers means a "better" * device, eg higher priority. */ int nm_device_get_priority (NMDevice *self) { g_return_val_if_fail (NM_IS_DEVICE (self), 1000); /* 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 (self)) { /* 10 is reserved for VPN (NM_VPN_ROUTE_METRIC_DEFAULT) */ case NM_DEVICE_TYPE_ETHERNET: return 20; case NM_DEVICE_TYPE_INFINIBAND: return 30; case NM_DEVICE_TYPE_ADSL: return 40; case NM_DEVICE_TYPE_WIMAX: return 50; case NM_DEVICE_TYPE_BOND: return 60; case NM_DEVICE_TYPE_TEAM: return 70; case NM_DEVICE_TYPE_VLAN: return 80; case NM_DEVICE_TYPE_MODEM: return 90; case NM_DEVICE_TYPE_BT: return 100; case NM_DEVICE_TYPE_WIFI: return 110; case NM_DEVICE_TYPE_OLPC_MESH: return 120; default: return 200; } } guint32 nm_device_get_ip4_route_metric (NMDevice *self) { NMConnection *connection; gint64 route_metric = -1; g_return_val_if_fail (NM_IS_DEVICE (self), G_MAXUINT32); connection = nm_device_get_connection (self); if (connection) route_metric = nm_setting_ip_config_get_route_metric (nm_connection_get_setting_ip4_config (connection)); return route_metric >= 0 ? route_metric : nm_device_get_priority (self); } guint32 nm_device_get_ip6_route_metric (NMDevice *self) { NMConnection *connection; gint64 route_metric = -1; g_return_val_if_fail (NM_IS_DEVICE (self), G_MAXUINT32); connection = nm_device_get_connection (self); if (connection) route_metric = nm_setting_ip_config_get_route_metric (nm_connection_get_setting_ip6_config (connection)); return route_metric >= 0 ? route_metric : nm_device_get_priority (self); } const NMPlatformIP4Route * nm_device_get_ip4_default_route (NMDevice *self, gboolean *out_is_assumed) { NMDevicePrivate *priv; g_return_val_if_fail (NM_IS_DEVICE (self), NULL); priv = NM_DEVICE_GET_PRIVATE (self); if (out_is_assumed) *out_is_assumed = priv->default_route.v4_has && priv->default_route.v4_is_assumed; return priv->default_route.v4_has ? &priv->default_route.v4 : NULL; } const NMPlatformIP6Route * nm_device_get_ip6_default_route (NMDevice *self, gboolean *out_is_assumed) { NMDevicePrivate *priv; g_return_val_if_fail (NM_IS_DEVICE (self), NULL); priv = NM_DEVICE_GET_PRIVATE (self); if (out_is_assumed) *out_is_assumed = priv->default_route.v6_has && priv->default_route.v6_is_assumed; return priv->default_route.v6_has ? &priv->default_route.v6 : NULL; } 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 *self) { return NM_DEVICE_GET_PRIVATE (self)->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 *self) { return NM_DEVICE_GET_PRIVATE (self)->physical_port_id; } /***********************************************************/ static gboolean nm_device_uses_generated_assumed_connection (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMConnection *connection; if ( priv->act_request && nm_active_connection_get_assumed (NM_ACTIVE_CONNECTION (priv->act_request))) { connection = nm_act_request_get_connection (priv->act_request); if ( connection && nm_settings_connection_get_nm_generated_assumed (NM_SETTINGS_CONNECTION (connection))) return TRUE; } return FALSE; } gboolean nm_device_uses_assumed_connection (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); if ( priv->act_request && nm_active_connection_get_assumed (NM_ACTIVE_CONNECTION (priv->act_request))) return TRUE; return FALSE; } 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: * @self: the master device * @slave: the slave device to enslave * @connection: (allow-none): the slave device's connection * * If @self 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 *self, NMDevice *slave, NMConnection *connection) { SlaveInfo *info; gboolean success = FALSE; gboolean configure; g_return_val_if_fail (self != NULL, FALSE); g_return_val_if_fail (slave != NULL, FALSE); g_return_val_if_fail (NM_DEVICE_GET_CLASS (self)->enslave_slave != NULL, FALSE); info = find_slave_info (self, 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 (self)->enslave_slave (self, 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 (self); /* 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 (self)->ip4_state == IP_WAIT) nm_device_activate_stage3_ip4_start (self); if (NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT) nm_device_activate_stage3_ip6_start (self); } return success; } /** * nm_device_release_one_slave: * @self: the master device * @slave: the slave device to release * @configure: whether @self needs to actually release @slave * @reason: the state change reason for the @slave * * If @self 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 @self 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 *self, NMDevice *slave, gboolean configure, NMDeviceStateReason reason) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); SlaveInfo *info; gboolean success = FALSE; g_return_val_if_fail (slave != NULL, FALSE); g_return_val_if_fail (NM_DEVICE_GET_CLASS (self)->release_slave != NULL, FALSE); info = find_slave_info (self, slave); if (!info) return FALSE; priv->slaves = g_slist_remove (priv->slaves, info); if (info->enslaved) { success = NM_DEVICE_GET_CLASS (self)->release_slave (self, 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 (self); return success; } static void carrier_changed (NMDevice *self, gboolean carrier) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); if (!nm_device_get_managed (self)) return; nm_device_recheck_available_connections (self); /* 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 (self)) nm_device_activate_stage3_ip4_start (self); if (nm_device_activate_ip6_state_in_wait (self)) nm_device_activate_stage3_ip6_start (self); return; } else if (nm_device_get_enslaved (self) && !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 (self, 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 (self); } } else { g_return_if_fail (priv->state >= NM_DEVICE_STATE_UNAVAILABLE); if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) { if (nm_device_queued_state_peek (self) >= NM_DEVICE_STATE_DISCONNECTED) nm_device_queued_state_clear (self); } else { nm_device_queue_state (self, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_CARRIER); } } } #define LINK_DISCONNECT_DELAY 4 static gboolean link_disconnect_action_cb (gpointer user_data) { NMDevice *self = NM_DEVICE (user_data); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); _LOGD (LOGD_DEVICE, "link disconnected (calling deferred action) (id=%u)", priv->carrier_defer_id); priv->carrier_defer_id = 0; _LOGI (LOGD_DEVICE, "link disconnected (calling deferred action)"); NM_DEVICE_GET_CLASS (self)->carrier_changed (self, 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); _LOGD (LOGD_DEVICE, "link disconnected (canceling deferred action) (id=%u)", priv->carrier_defer_id); priv->carrier_defer_id = 0; } } void nm_device_set_carrier (NMDevice *self, gboolean carrier) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self); NMDeviceState state = nm_device_get_state (self); if (priv->carrier == carrier) return; priv->carrier = carrier; g_object_notify (G_OBJECT (self), NM_DEVICE_CARRIER); if (priv->carrier) { _LOGI (LOGD_DEVICE, "link connected"); link_disconnect_action_cancel (self); klass->carrier_changed (self, TRUE); if (priv->carrier_wait_id) { g_source_remove (priv->carrier_wait_id); priv->carrier_wait_id = 0; nm_device_remove_pending_action (self, "carrier wait", TRUE); } } else if (state <= NM_DEVICE_STATE_DISCONNECTED) { _LOGI (LOGD_DEVICE, "link disconnected"); klass->carrier_changed (self, FALSE); } else { _LOGI (LOGD_DEVICE, "link disconnected (deferring action for %d seconds)", LINK_DISCONNECT_DELAY); priv->carrier_defer_id = g_timeout_add_seconds (LINK_DISCONNECT_DELAY, link_disconnect_action_cb, self); _LOGD (LOGD_DEVICE, "link disconnected (deferring action for %d seconds) (id=%u)", LINK_DISCONNECT_DELAY, priv->carrier_defer_id); } } static void update_for_ip_ifname_change (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); g_hash_table_remove_all (priv->ip6_saved_properties); if (priv->dhcp4_client) { if (!nm_device_dhcp4_renew (self, FALSE)) { nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DHCP_FAILED); return; } } if (priv->dhcp6_client) { if (!nm_device_dhcp6_renew (self, FALSE)) { nm_device_state_changed (self, 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 *self, NMPlatformLink *info) { NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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 (self), NM_DEVICE_UDI); } /* Update MTU if it has changed. */ if (priv->mtu != info->mtu) { priv->mtu = info->mtu; g_object_notify (G_OBJECT (self), NM_DEVICE_MTU); } if (info->name[0] && strcmp (priv->iface, info->name) != 0) { _LOGI (LOGD_DEVICE, "interface index %d renamed iface from '%s' to '%s'", 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 (self), NM_DEVICE_IFACE); if (ip_ifname_changed) g_object_notify (G_OBJECT (self), NM_DEVICE_IP_IFACE); /* Re-match available connections against the new interface name */ nm_device_recheck_available_connections (self); /* Let any connections that use the new interface name have a chance * to auto-activate on the device. */ nm_device_emit_recheck_auto_activate (self); } /* 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, self, FALSE); nm_device_enslave_slave (master, self, NULL); } else if (master) { _LOGI (LOGD_DEVICE, "enslaved to non-master-type device %s; ignoring", nm_device_get_iface (master)); } else { _LOGW (LOGD_DEVICE, "enslaved to unknown device %d %s", info->master, nm_platform_link_get_name (info->master)); } } else if (priv->enslaved && !info->master) nm_device_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_NONE); if (klass->link_changed) klass->link_changed (self, info); /* Update DHCP, etc, if needed */ if (ip_ifname_changed) update_for_ip_ifname_change (self); } static void device_ip_link_changed (NMDevice *self, NMPlatformLink *info) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); if (info->name[0] && g_strcmp0 (priv->ip_iface, info->name)) { _LOGI (LOGD_DEVICE, "interface index %d renamed ip_iface (%d) from '%s' to '%s'", priv->ifindex, nm_device_get_ip_ifindex (self), priv->ip_iface, info->name); g_free (priv->ip_iface); priv->ip_iface = g_strdup (info->name); g_object_notify (G_OBJECT (self), NM_DEVICE_IP_IFACE); update_for_ip_ifname_change (self); } } static void link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *info, NMPlatformSignalChangeType change_type, NMPlatformReason reason, NMDevice *self) { 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 (self)) device_link_changed (self, info); else if (ifindex == nm_device_get_ip_ifindex (self)) device_ip_link_changed (self, info); } static void link_changed (NMDevice *self, NMPlatformLink *info) { /* Update carrier from link event if applicable. */ if ( device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT) && !device_has_capability (self, NM_DEVICE_CAP_NONSTANDARD_CARRIER)) nm_device_set_carrier (self, info->connected); } /** * nm_device_notify_component_added(): * @self: 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 *self, GObject *component) { if (NM_DEVICE_GET_CLASS (self)->component_added) return NM_DEVICE_GET_CLASS (self)->component_added (self, component); return FALSE; } /** * nm_device_owns_iface(): * @self: 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 @self or it's components owns the interface name, * %FALSE if not */ gboolean nm_device_owns_iface (NMDevice *self, const char *iface) { if (NM_DEVICE_GET_CLASS (self)->owns_iface) return NM_DEVICE_GET_CLASS (self)->owns_iface (self, iface); return FALSE; } NMConnection * nm_device_new_default_connection (NMDevice *self) { if (NM_DEVICE_GET_CLASS (self)->new_default_connection) return NM_DEVICE_GET_CLASS (self)->new_default_connection (self); return NULL; } 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; _LOGD (LOGD_DEVICE, "slave %s state change %d (%s) -> %d (%s)", 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) _LOGD (LOGD_DEVICE, "last slave removed; remaining activated"); } } /** * nm_device_master_add_slave: * @self: 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 @self 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 *self, NMDevice *slave, gboolean configure) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); SlaveInfo *info; g_return_val_if_fail (self != NULL, FALSE); g_return_val_if_fail (slave != NULL, FALSE); g_return_val_if_fail (NM_DEVICE_GET_CLASS (self)->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 (self, 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), self); priv->slaves = g_slist_append (priv->slaves, info); } return TRUE; } /** * nm_device_master_get_slaves: * @self: the master device * * Returns: any slaves of which @self is the master. Caller owns returned list. */ GSList * nm_device_master_get_slaves (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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: * @self: the master device * @ifindex: the slave's interface index * * Returns: the slave with the given @ifindex of which @self is the master, * or %NULL if no device with @ifindex is a slave of @self. */ NMDevice * nm_device_master_get_slave_by_ifindex (NMDevice *self, int ifindex) { GSList *iter; for (iter = NM_DEVICE_GET_PRIVATE (self)->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: * @self: the master device * @slave: a slave device * @log_domain: domain to log a warning in * * Checks if @self 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 *self, NMDevice *slave, guint64 log_domain) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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)) { _LOGW (log_domain, "slave %s shares a physical port with existing slave %s", 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_assumed_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: * @self: the device * * If @self has been enslaved by another device, this returns that * device. Otherwise it returns %NULL. (In particular, note that if * @self 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): @self's master, or %NULL */ NMDevice * nm_device_get_master (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); if (priv->enslaved) return priv->master; else return NULL; } /** * nm_device_slave_notify_enslave: * @self: 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 *self, gboolean success) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMConnection *connection = nm_device_get_connection (self); gboolean activating = (priv->state == NM_DEVICE_STATE_IP_CONFIG); g_assert (priv->master); if (!priv->enslaved) { if (success) { if (activating) { _LOGI (LOGD_DEVICE, "Activation: connection '%s' enslaved, continuing activation", nm_connection_get_id (connection)); } else _LOGI (LOGD_DEVICE, "enslaved to %s", nm_device_get_iface (priv->master)); priv->enslaved = TRUE; g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER); } else if (activating) { _LOGW (LOGD_DEVICE, "Activation: connection '%s' could not be enslaved", nm_connection_get_id (connection)); } } if (activating) { priv->ip4_state = IP_DONE; priv->ip6_state = IP_DONE; nm_device_queue_state (self, success ? NM_DEVICE_STATE_SECONDARIES : NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NONE); } else nm_device_queue_recheck_assume (self); } /** * nm_device_slave_notify_release: * @self: 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 *self, NMDeviceStateReason reason) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMConnection *connection = nm_device_get_connection (self); 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"; } _LOGD (LOGD_DEVICE, "Activation: connection '%s' master %s", nm_connection_get_id (connection), master_status); nm_device_queue_state (self, new_state, reason); } else _LOGI (LOGD_DEVICE, "released from master %s", nm_device_get_iface (priv->master)); if (priv->enslaved) { priv->enslaved = FALSE; g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER); } } /** * nm_device_get_enslaved: * @self: 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 *self) { return NM_DEVICE_GET_PRIVATE (self)->enslaved; } static gboolean is_available (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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); } /** * nm_device_get_autoconnect: * @self: the #NMDevice * * Returns: %TRUE if the device allows autoconnect connections, or %FALSE if the * device is explicitly blocking all autoconnect connections. Does not take * into account transient conditions like companion devices that may wish to * block the device. */ gboolean nm_device_get_autoconnect (NMDevice *self) { g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); return NM_DEVICE_GET_PRIVATE (self)->autoconnect; } static void nm_device_set_autoconnect (NMDevice *self, gboolean autoconnect) { NMDevicePrivate *priv; g_return_if_fail (NM_IS_DEVICE (self)); priv = NM_DEVICE_GET_PRIVATE (self); if (priv->autoconnect == autoconnect) return; if (autoconnect) { /* Default-unmanaged devices never autoconnect */ if (!nm_device_get_default_unmanaged (self)) { priv->autoconnect = TRUE; g_object_notify (G_OBJECT (self), NM_DEVICE_AUTOCONNECT); } } else { priv->autoconnect = FALSE; g_object_notify (G_OBJECT (self), NM_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; } /** * nm_device_autoconnect_allowed: * @self: the #NMDevice * * Returns: %TRUE if the device can be auto-connected immediately, taking * transient conditions into account (like companion devices that may wish to * block autoconnect for a time). */ gboolean nm_device_autoconnect_allowed (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); GValue instance = G_VALUE_INIT; GValue retval = G_VALUE_INIT; if (priv->state < NM_DEVICE_STATE_DISCONNECTED || !priv->autoconnect) return FALSE; /* The 'autoconnect-allowed' signal is emitted on a device to allow * other listeners to block autoconnect on the device if they wish. * This is mainly used by the OLPC Mesh devices to block autoconnect * on their companion WiFi device as they share radio resources and * cannot be connected at the same time. */ 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 *self, 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 (self, connection, FALSE); } /** * nm_device_can_auto_connect: * @self: an #NMDevice * @connection: a #NMConnection * @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. * * Checks if @connection can be auto-activated on @self right now. * This requires, at a minimum, that the connection be compatible with * @self, and that it have the #NMSettingConnection:autoconnect property * set, and that the device allow auto connections. Some devices impose * additional requirements. (Eg, a Wi-Fi connection can only be activated * if its SSID was seen in the last scan.) * * Returns: %TRUE, if the @connection can be auto-activated. **/ gboolean nm_device_can_auto_connect (NMDevice *self, NMConnection *connection, char **specific_object) { g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); g_return_val_if_fail (specific_object && !*specific_object, FALSE); if (nm_device_autoconnect_allowed (self)) return NM_DEVICE_GET_CLASS (self)->can_auto_connect (self, connection, specific_object); return FALSE; } static gboolean device_has_config (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); /* 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 (self)) return TRUE; /* Slaves are also configured by definition */ if (nm_platform_link_get_master (priv->ifindex) > 0) return TRUE; return FALSE; } /** * nm_device_master_update_slave_connection: * @self: the master #NMDevice * @slave: the slave #NMDevice * @connection: the #NMConnection to update with the slave settings * @GError: (out): error description * * Reads the slave configuration for @slave and updates @connection with those * properties. This invokes a virtual function on the master device @self. * * Returns: %TRUE if the configuration was read and @connection updated, * %FALSE on failure. */ gboolean nm_device_master_update_slave_connection (NMDevice *self, NMDevice *slave, NMConnection *connection, GError **error) { NMDeviceClass *klass; gboolean success; g_return_val_if_fail (self, FALSE); g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); g_return_val_if_fail (slave, FALSE); g_return_val_if_fail (connection, FALSE); g_return_val_if_fail (!error || !*error, FALSE); g_return_val_if_fail (nm_connection_get_setting_connection (connection), FALSE); g_return_val_if_fail (nm_device_get_iface (self), FALSE); klass = NM_DEVICE_GET_CLASS (self); if (klass->master_update_slave_connection) { success = klass->master_update_slave_connection (self, slave, connection, error); g_return_val_if_fail (!error || (success && !*error) || *error, success); return success; } g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "master device '%s' cannot update a slave connection for slave device '%s' (master type not supported?)", nm_device_get_iface (self), nm_device_get_iface (slave)); return FALSE; } NMConnection * nm_device_generate_connection (NMDevice *self, NMDevice *master) { NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); const char *ifname = nm_device_get_iface (self); NMConnection *connection; NMSetting *s_con; NMSetting *s_ip4; NMSetting *s_ip6; gs_free char *uuid = NULL; 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 (self)) { _LOGD (LOGD_DEVICE, "device has no existing configuration"); return NULL; } connection = nm_simple_connection_new (); s_con = nm_setting_connection_new (); uuid = nm_utils_uuid_generate (); g_object_set (s_con, NM_SETTING_CONNECTION_UUID, uuid, NM_SETTING_CONNECTION_ID, ifname, 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) { if (!nm_device_master_update_slave_connection (master, self, connection, &error)) { _LOGE (LOGD_DEVICE, "master device '%s' failed to update slave connection: %s", nm_device_get_iface (master), error ? error->message : "(unknown error)"); g_error_free (error); g_object_unref (connection); return 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 (self, connection); /* Check the connection in case of update_connection() bug. */ if (!nm_connection_verify (connection, &error)) { _LOGE (LOGD_DEVICE, "Generated connection does not verify: %s", 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))) { _LOGD (LOGD_DEVICE, "ignoring generated connection (no IP and not slave)"); g_object_unref (connection); connection = NULL; } return connection; } 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_INVALID_CONNECTION, "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 *self, 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 (self); if (config_iface && strcmp (config_iface, device_iface) != 0) return FALSE; return TRUE; } /** * nm_device_check_connection_compatible: * @self: an #NMDevice * @connection: an #NMConnection * * Checks if @connection could potentially be activated on @self. * This means only that @self 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 @self * 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 * @self. */ gboolean nm_device_check_connection_compatible (NMDevice *self, NMConnection *connection) { g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); return NM_DEVICE_GET_CLASS (self)->check_connection_compatible (self, connection); } /** * nm_device_can_assume_connections: * @self: #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 *self) { return !!NM_DEVICE_GET_CLASS (self)->update_connection; } /** * nm_device_can_assume_active_connection: * @self: #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 *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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, NULL }; const char *assumable_ip4_methods[] = { NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL }; if (!nm_device_can_assume_connections (self)) return FALSE; connection = nm_device_get_connection (self); 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 (!_nm_utils_string_in_list (method, assumable_ip6_methods)) return FALSE; method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); if (!_nm_utils_string_in_list (method, 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)) { _LOGD (LOGD_DEVICE, "emit RECHECK_ASSUME signal"); 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) _LOGE (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; NMSettingIPConfig *s_ip4, *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_ip_config_get_may_fail (s_ip6)) return TRUE; } else { s_ip4 = nm_connection_get_setting_ip4_config (connection); if (!nm_setting_ip_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); _LOGD (LOGD_DEVICE, "master connection ready; master device %s", 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); 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); _LOGI (LOGD_DEVICE, "Activation: Stage 1 of 5 (Device Prepare) started..."); 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 { _LOGD (LOGD_DEVICE, "waiting for master connection to become ready"); /* 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: _LOGI (LOGD_DEVICE, "Activation: Stage 1 of 5 (Device Prepare) complete."); 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); _LOGI (LOGD_DEVICE, "Activation: Stage 1 of 5 (Device Prepare) scheduled..."); } static NMActStageReturn act_stage2_config (NMDevice *self, 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); 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); _LOGI (LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) starting..."); 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_assumed_connection (self) && slave_state <= NM_DEVICE_STATE_DISCONNECTED) nm_device_queue_recheck_assume (info->slave); } _LOGI (LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) successful."); nm_device_activate_schedule_stage3_ip_config_start (self); out: _LOGI (LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) complete."); 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); _LOGI (LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) scheduled..."); } /*********************************************/ /* 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) { nm_utils_kill_child_sync (priv->aipd_pid, SIGKILL, LOGD_AUTOIP4, "avahi-autoipd", NULL, 0, 0); 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_IP_CONFIG_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_IP_CONFIG_SOURCE_IP4LL; route.metric = nm_device_get_ip4_route_metric (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 *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; if (strcmp (event, "BIND") == 0) { guint32 lla; NMIP4Config *config; if (inet_pton (AF_INET, address, &lla) <= 0) { _LOGE (LOGD_AUTOIP4, "invalid address %s received from avahi-autoipd.", address); nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_ERROR); return; } if ((lla & IPV4LL_NETMASK) != IPV4LL_NETWORK) { _LOGE (LOGD_AUTOIP4, "invalid address %s received from avahi-autoipd (not link-local).", 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) { _LOGE (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)) { _LOGE (LOGD_AUTOIP4, "failed to update IP4 config for autoip change."); nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); } } else g_assert_not_reached (); g_object_unref (config); } else { _LOGW (LOGD_AUTOIP4, "autoip address %s no longer valid because '%s'.", 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; if (!priv->aipd_watch) return; priv->aipd_watch = 0; if (WIFEXITED (status)) _LOGD (LOGD_AUTOIP4, "avahi-autoipd exited with error code %d", WEXITSTATUS (status)); else if (WIFSTOPPED (status)) _LOGW (LOGD_AUTOIP4, "avahi-autoipd stopped unexpectedly with signal %d", WSTOPSIG (status)); else if (WIFSIGNALED (status)) _LOGW (LOGD_AUTOIP4, "avahi-autoipd died with signal %d", WTERMSIG (status)); else _LOGW (LOGD_AUTOIP4, "avahi-autoipd died from an unknown cause"); 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) { _LOGI (LOGD_AUTOIP4, "avahi-autoipd timed out."); 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 *argv[6]; char *cmdline; const char *aipd_binary; int i = 0; GError *error = NULL; aipd_cleanup (self); /* Find avahi-autoipd */ aipd_binary = nm_utils_find_helper ("avahi-autoipd", NULL, NULL); if (!aipd_binary) { _LOGW (LOGD_DEVICE | LOGD_AUTOIP4, "Activation: Stage 3 of 5 (IP Configure Start) failed" " to start avahi-autoipd: not found"); *reason = NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED; return NM_ACT_STAGE_RETURN_FAILURE; } argv[i++] = aipd_binary; argv[i++] = "--script"; argv[i++] = nm_device_autoipd_helper_path; if (nm_logging_enabled (LOGL_DEBUG, LOGD_AUTOIP4)) argv[i++] = "--debug"; argv[i++] = nm_device_get_ip_iface (self); argv[i++] = NULL; cmdline = g_strjoinv (" ", (char **) argv); _LOGD (LOGD_AUTOIP4, "running: %s", cmdline); g_free (cmdline); if (!g_spawn_async ("/", (char **) argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, &aipd_child_setup, NULL, &(priv->aipd_pid), &error)) { _LOGW (LOGD_DEVICE | LOGD_AUTOIP4, "Activation: Stage 3 of 5 (IP Configure Start) failed" " to start avahi-autoipd: %s", error && error->message ? error->message : "(unknown)"); g_clear_error (&error); aipd_cleanup (self); return NM_ACT_STAGE_RETURN_FAILURE; } _LOGI (LOGD_DEVICE | LOGD_AUTOIP4, "Activation: Stage 3 of 5 (IP Configure Start) started" " avahi-autoipd..."); /* 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; } /*********************************************/ static gboolean _device_get_default_route_from_platform (NMDevice *self, int addr_family, NMPlatformIPRoute *out_route) { gboolean success = FALSE; int ifindex = nm_device_get_ip_ifindex (self); GArray *routes; if (addr_family == AF_INET) routes = nm_platform_ip4_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT); else routes = nm_platform_ip6_route_get_all (ifindex, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT); if (routes) { guint route_metric = G_MAXUINT32, m; const NMPlatformIPRoute *route = NULL, *r; guint i; /* if there are several default routes, find the one with the best metric */ for (i = 0; i < routes->len; i++) { if (addr_family == AF_INET) { r = (const NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP4Route, i); m = r->metric; } else { r = (const NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP6Route, i); m = nm_utils_ip6_route_metric_normalize (r->metric); } if (!route || m < route_metric) { route = r; route_metric = m; } } if (route) { if (addr_family == AF_INET) *((NMPlatformIP4Route *) out_route) = *((NMPlatformIP4Route *) route); else *((NMPlatformIP6Route *) out_route) = *((NMPlatformIP6Route *) route); success = TRUE; } g_array_free (routes, TRUE); } return success; } /*********************************************/ /* 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; } 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 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; guint32 default_route_metric = nm_device_get_ip4_route_metric (self); /* 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 WWAN config *last* to ensure modem-given settings overwrite * any external stuff set by pppd or other scripts. */ if (priv->wwan_ip4_config) nm_ip4_config_merge (composite, priv->wwan_ip4_config); /* Merge user overrides into the composite config. Generated+assumed * connections come from the system not the user and merging them would * be redundant, so don't bother. */ connection = nm_device_get_connection (self); priv->default_route.v4_has = FALSE; if (connection) { gboolean assumed = nm_device_uses_assumed_connection (self); NMPlatformIP4Route *route = &priv->default_route.v4; if (!nm_settings_connection_get_nm_generated_assumed (NM_SETTINGS_CONNECTION (connection))) { nm_ip4_config_merge_setting (composite, nm_connection_get_setting_ip4_config (connection), default_route_metric); } /* Add the default route. * * We keep track of the default route of a device in a private field. * NMDevice needs to know the default route at this point, because the gateway * might require a direct route (see below). * * But also, we don't want to add the default route to priv->ip4_config, * because the default route from the setting might not be the same that * NMDefaultRouteManager eventually configures (because the it might * tweak the effective metric). */ if ( !assumed && nm_default_route_manager_ip4_connection_has_default_route (nm_default_route_manager_get (), connection)) { guint32 gateway = 0; if ( priv->ext_ip4_config_had_any_addresses || (commit && nm_ip4_config_get_num_addresses (composite))) { /* For managed interfaces, we can only configure a gateway, if either the external config indicates * that we already have addresses, or if we are about to commit any addresses. * Otherwise adding a default route will fail, because NMDefaultRouteManager does not add any * addresses for the route. */ gateway = nm_ip4_config_get_gateway (composite); if ( gateway || nm_device_get_device_type (self) == NM_DEVICE_TYPE_MODEM) { memset (route, 0, sizeof (*route)); route->source = NM_IP_CONFIG_SOURCE_USER; route->gateway = gateway; route->metric = default_route_metric; route->mss = nm_ip4_config_get_mss (composite); priv->default_route.v4_has = TRUE; priv->default_route.v4_is_assumed = FALSE; if ( gateway && !nm_ip4_config_get_subnet_for_host (composite, gateway) && !nm_ip4_config_get_direct_route_for_host (composite, gateway)) { /* add a direct route to the gateway */ NMPlatformIP4Route r = *route; r.network = gateway; r.plen = 32; r.gateway = 0; nm_ip4_config_add_route (composite, &r); } } } } else { /* For interfaces that are assumed and that have no default-route by configuration, we assume * the default connection and pick up whatever is configured. */ priv->default_route.v4_has = _device_get_default_route_from_platform (self, AF_INET, (NMPlatformIPRoute *) route); priv->default_route.v4_is_assumed = TRUE; } } /* 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, default_route_metric, 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)) { _LOGW (LOGD_DHCP4, "failed to update IPv4 config for DHCP change."); 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 *self, gboolean timeout) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); dhcp4_cleanup (self, TRUE, FALSE); if (timeout || (priv->ip4_state == IP_CONF)) nm_device_activate_schedule_ip4_config_timeout (self); else if (priv->ip4_state == IP_DONE) nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED); else g_warn_if_reached (); } static void dhcp4_update_config (NMDevice *self, NMDhcp4Config *config, GHashTable *options) { GHashTableIter iter; const char *key, *value; /* Update the DHCP4 config object with new DHCP options */ nm_dhcp4_config_reset (config); g_hash_table_iter_init (&iter, options); while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) nm_dhcp4_config_add_option (config, key, value); g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG); } static void dhcp4_state_changed (NMDhcpClient *client, NMDhcpState state, NMIP4Config *ip4_config, GHashTable *options, gpointer user_data) { NMDevice *self = NM_DEVICE (user_data); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == FALSE); g_return_if_fail (!ip4_config || NM_IS_IP4_CONFIG (ip4_config)); _LOGD (LOGD_DHCP4, "new DHCPv4 client state %d", state); switch (state) { case NM_DHCP_STATE_BOUND: if (!ip4_config) { _LOGW (LOGD_DHCP4, "failed to get IPv4 config in response to DHCP event."); nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); break; } dhcp4_update_config (self, priv->dhcp4_config, options); if (priv->ip4_state == IP_CONF) nm_device_activate_schedule_ip4_config_result (self, ip4_config); else if (priv->ip4_state == IP_DONE) dhcp4_lease_change (self, ip4_config); break; case NM_DHCP_STATE_TIMEOUT: dhcp4_fail (self, TRUE); break; case NM_DHCP_STATE_DONE: case NM_DHCP_STATE_FAIL: /* dhclient quit and can't get/renew a lease; so kill the connection */ dhcp4_fail (self, FALSE); break; default: break; } } static NMActStageReturn dhcp4_start (NMDevice *self, NMConnection *connection, NMDeviceStateReason *reason) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMSettingIPConfig *s_ip4; const guint8 *hw_addr; size_t hw_addr_len = 0; 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 (); hw_addr = nm_platform_link_get_address (nm_device_get_ip_ifindex (self), &hw_addr_len); if (hw_addr_len) { tmp = g_byte_array_sized_new (hw_addr_len); g_byte_array_append (tmp, hw_addr, 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), nm_device_get_ip_ifindex (self), tmp, nm_connection_get_uuid (connection), nm_device_get_ip4_route_metric (self), nm_setting_ip_config_get_dhcp_send_hostname (s_ip4), nm_setting_ip_config_get_dhcp_hostname (s_ip4), nm_setting_ip4_config_get_dhcp_client_id (NM_SETTING_IP4_CONFIG (s_ip4)), priv->dhcp_timeout, priv->dhcp_anycast_address, NULL); 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); 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); _LOGI (LOGD_DHCP4, "DHCPv4 lease renewal requested"); /* 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 (NMDevice *self, NMSettingIPConfig *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_ip_config_get_num_addresses (s_ip4)) { /* Use the first user-supplied address */ NMIPAddress *user = nm_setting_ip_config_get_address (s_ip4, 0); g_assert (user); nm_ip_address_get_address_binary (user, &address->address); address->plen = nm_ip_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)) { _LOGE (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 (self, 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_IP_CONFIG_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 connection_ip4_method_requires_carrier (NMConnection *connection, gboolean *out_ip4_enabled) { const char *method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); static const char *ip4_carrier_methods[] = { NM_SETTING_IP4_CONFIG_METHOD_AUTO, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL, NULL }; if (out_ip4_enabled) *out_ip4_enabled = !!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED); return _nm_utils_string_in_list (method, ip4_carrier_methods); } static gboolean connection_ip6_method_requires_carrier (NMConnection *connection, gboolean *out_ip6_enabled) { const char *method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); static const char *ip6_carrier_methods[] = { NM_SETTING_IP6_CONFIG_METHOD_AUTO, NM_SETTING_IP6_CONFIG_METHOD_DHCP, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, NULL }; if (out_ip6_enabled) *out_ip6_enabled = !!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE); return _nm_utils_string_in_list (method, ip6_carrier_methods); } static gboolean connection_requires_carrier (NMConnection *connection) { NMSettingIPConfig *s_ip4, *s_ip6; gboolean ip4_carrier_wanted, ip6_carrier_wanted; gboolean ip4_used = FALSE, ip6_used = FALSE; ip4_carrier_wanted = connection_ip4_method_requires_carrier (connection, &ip4_used); if (ip4_carrier_wanted) { /* 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_ip_config_get_may_fail (s_ip4)) return TRUE; } ip6_carrier_wanted = connection_ip6_method_requires_carrier (connection, &ip6_used); if (ip6_carrier_wanted) { /* 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_ip_config_get_may_fail (s_ip6)) return TRUE; } /* 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 have_any_ready_slaves (NMDevice *self, 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); if ( connection_ip4_method_requires_carrier (connection, NULL) && priv->is_master && !priv->carrier) { _LOGI (LOGD_IP4 | LOGD_DEVICE, "IPv4 config waiting until carrier is on"); 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) { _LOGI (LOGD_DEVICE | LOGD_IP4, "IPv4 config waiting until slaves are ready"); return NM_ACT_STAGE_RETURN_WAIT; } } method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); /* 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 _LOGW (LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", 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; } 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 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 WWAN config *last* to ensure modem-given settings overwrite * any external stuff set by pppd or other scripts. */ if (priv->wwan_ip6_config) nm_ip6_config_merge (composite, priv->wwan_ip6_config); /* Merge user overrides into the composite config. Generated+assumed * connections come from the system not the user and merging them would * be redundant, so don't bother. */ connection = nm_device_get_connection (self); priv->default_route.v6_has = FALSE; if (connection) { gboolean assumed = nm_device_uses_assumed_connection (self); NMPlatformIP6Route *route = &priv->default_route.v6; if (!nm_settings_connection_get_nm_generated_assumed (NM_SETTINGS_CONNECTION (connection))) { nm_ip6_config_merge_setting (composite, nm_connection_get_setting_ip6_config (connection), nm_device_get_ip6_route_metric (self)); } /* Add the default route. * * We keep track of the default route of a device in a private field. * NMDevice needs to know the default route at this point, because the gateway * might require a direct route (see below). * * But also, we don't want to add the default route to priv->ip4_config, * because the default route from the setting might not be the same that * NMDefaultRouteManager eventually configures (because the it might * tweak the effective metric). */ if ( !assumed && nm_default_route_manager_ip6_connection_has_default_route (nm_default_route_manager_get (), connection)) { const struct in6_addr *gateway = NULL; if ( priv->ext_ip6_config_had_any_addresses || (commit && nm_ip6_config_get_num_addresses (composite))) { /* For managed interfaces, we can only configure a gateway, if either the external config indicates * that we already have addresses, or if we are about to commit any addresses. * Otherwise adding a default route will fail, because NMDefaultRouteManager does not add any * addresses for the route. */ gateway = nm_ip6_config_get_gateway (composite); if (gateway) { memset (route, 0, sizeof (*route)); route->source = NM_IP_CONFIG_SOURCE_USER; route->gateway = *gateway; route->metric = nm_device_get_ip6_route_metric (self); route->mss = nm_ip6_config_get_mss (composite); priv->default_route.v6_has = TRUE; priv->default_route.v6_is_assumed = FALSE; if ( gateway && !nm_ip6_config_get_subnet_for_host (composite, gateway) && !nm_ip6_config_get_direct_route_for_host (composite, gateway)) { /* add a direct route to the gateway */ NMPlatformIP6Route r = *route; r.network = *gateway; r.plen = 128; r.gateway = in6addr_any; nm_ip6_config_add_route (composite, &r); } } } } else { /* For interfaces that are assumed and that have no default-route by configuration, we assume * the default connection and pick up whatever is configured. */ priv->default_route.v6_has = _device_get_default_route_from_platform (self, AF_INET6, (NMPlatformIPRoute *) route); priv->default_route.v6_is_assumed = TRUE; } } nm_ip6_config_addresses_sort (composite, priv->rdisc ? priv->rdisc_use_tempaddr : NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); /* Allow setting MTU etc */ if (commit) { if (NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit) NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit (self, composite); } success = nm_device_set_ip6_config (self, composite, commit, out_reason); g_object_unref (composite); return success; } static void dhcp6_lease_change (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMConnection *connection; NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; if (priv->dhcp6_ip6_config == NULL) { _LOGW (LOGD_DHCP6, "failed to get DHCPv6 config for rebind"); nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED); return; } g_assert (priv->dhcp6_client); /* sanity check */ connection = nm_device_get_connection (self); g_assert (connection); /* Apply the updated config */ if (ip6_config_merge_and_apply (self, TRUE, &reason) == FALSE) { _LOGW (LOGD_DHCP6, "failed to update IPv6 config in response to DHCP event."); nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); } else { /* Notify dispatcher scripts of new DHCPv6 config */ nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE, connection, self, NULL, NULL, NULL); } } static void dhcp6_fail (NMDevice *self, gboolean timeout) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); dhcp6_cleanup (self, TRUE, FALSE); if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED) { if (timeout || (priv->ip6_state == IP_CONF)) nm_device_activate_schedule_ip6_config_timeout (self); else if (priv->ip6_state == IP_DONE) nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED); else g_warn_if_reached (); } else { /* not a hard failure; just live with the RA info */ if (priv->ip6_state == IP_CONF) nm_device_activate_schedule_ip6_config_result (self); } } static void dhcp6_timeout (NMDevice *self, NMDhcpClient *client) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED) dhcp6_fail (self, TRUE); else { /* not a hard failure; just live with the RA info */ dhcp6_cleanup (self, TRUE, FALSE); if (priv->ip6_state == IP_CONF) nm_device_activate_schedule_ip6_config_result (self); } } static void dhcp6_update_config (NMDevice *self, NMDhcp6Config *config, GHashTable *options) { GHashTableIter iter; const char *key, *value; /* Update the DHCP6 config object with new DHCP options */ nm_dhcp6_config_reset (config); g_hash_table_iter_init (&iter, options); while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) nm_dhcp6_config_add_option (config, key, value); g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG); } static void dhcp6_state_changed (NMDhcpClient *client, NMDhcpState state, NMIP6Config *ip6_config, GHashTable *options, gpointer user_data) { NMDevice *self = NM_DEVICE (user_data); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE); g_return_if_fail (!ip6_config || NM_IS_IP6_CONFIG (ip6_config)); _LOGD (LOGD_DHCP6, "new DHCPv6 client state %d", state); switch (state) { case NM_DHCP_STATE_BOUND: g_clear_object (&priv->dhcp6_ip6_config); if (ip6_config) { priv->dhcp6_ip6_config = g_object_ref (ip6_config); dhcp6_update_config (self, priv->dhcp6_config, options); } 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 (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DHCP_FAILED); break; } nm_device_activate_schedule_ip6_config_result (self); } else if (priv->ip6_state == IP_DONE) dhcp6_lease_change (self); break; case NM_DHCP_STATE_TIMEOUT: dhcp6_timeout (self, client); break; case NM_DHCP_STATE_DONE: /* 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 NM_DHCP_STATE_FAIL: /* dhclient quit and can't get/renew a lease; so kill the connection */ dhcp6_fail (self, FALSE); break; default: break; } } static NMActStageReturn dhcp6_start (NMDevice *self, NMConnection *connection, guint32 dhcp_opt, NMDeviceStateReason *reason) { NMSettingIPConfig *s_ip6; NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; GByteArray *tmp = NULL; const guint8 *hw_addr; size_t hw_addr_len = 0; if (!connection) { connection = nm_device_get_connection (self); g_assert (connection); } s_ip6 = nm_connection_get_setting_ip6_config (connection); g_assert (s_ip6); /* 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; } hw_addr = nm_platform_link_get_address (nm_device_get_ip_ifindex (self), &hw_addr_len); if (hw_addr_len) { tmp = g_byte_array_sized_new (hw_addr_len); g_byte_array_append (tmp, hw_addr, hw_addr_len); } priv->dhcp6_client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (), nm_device_get_ip_iface (self), nm_device_get_ip_ifindex (self), tmp, nm_connection_get_uuid (connection), nm_device_get_ip6_route_metric (self), nm_setting_ip_config_get_dhcp_send_hostname (s_ip6), nm_setting_ip_config_get_dhcp_hostname (s_ip6), priv->dhcp_timeout, priv->dhcp_anycast_address, (dhcp_opt == NM_RDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE, nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (s_ip6))); 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); s_ip6 = nm_connection_get_setting_ip6_config (connection); if (!nm_setting_ip_config_get_may_fail (s_ip6) || !strcmp (nm_setting_ip_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); _LOGI (LOGD_DHCP6, "DHCPv6 lease renewal requested"); /* 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 have_ip6_address (const NMIP6Config *ip6_config, gboolean linklocal) { guint 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) == linklocal) && !(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); _LOGD (LOGD_DEVICE, "linklocal6: waiting for link-local addresses failed due to timeout"); 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 (have_ip6_address (priv->ip6_config, TRUE)); 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); _LOGD (LOGD_DEVICE, "linklocal6: waiting for link-local addresses successful, continue with method %s", method); if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) { if (!addrconf6_start_with_link_ready (self)) { /* Time out IPv6 instead of failing the entire activation */ nm_device_activate_schedule_ip6_config_timeout (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 void check_and_add_ipv6ll_addr (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); int ip_ifindex = nm_device_get_ip_ifindex (self); NMUtilsIPv6IfaceId iid; struct in6_addr lladdr; guint i, n; if (priv->nm_ipv6ll == FALSE) return; if (priv->ip6_config) { n = nm_ip6_config_get_num_addresses (priv->ip6_config); for (i = 0; i < n; i++) { const NMPlatformIP6Address *addr; addr = nm_ip6_config_get_address (priv->ip6_config, i); if (IN6_IS_ADDR_LINKLOCAL (&addr->address)) { /* Already have an LL address, nothing to do */ return; } } } if (!nm_device_get_ip_iface_identifier (self, &iid)) { _LOGW (LOGD_IP6, "failed to get interface identifier; IPv6 may be broken"); return; } memset (&lladdr, 0, sizeof (lladdr)); lladdr.s6_addr16[0] = htons (0xfe80); nm_utils_ipv6_addr_set_interface_identfier (&lladdr, iid); _LOGD (LOGD_IP6, "adding IPv6LL address %s", nm_utils_inet6_ntop (&lladdr, NULL)); if (!nm_platform_ip6_address_add (ip_ifindex, lladdr, in6addr_any, 64, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0)) { _LOGW (LOGD_IP6, "failed to add IPv6 link-local address %s", nm_utils_inet6_ntop (&lladdr, NULL)); } } static NMActStageReturn linklocal6_start (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMConnection *connection; const char *method; linklocal6_cleanup (self); if (have_ip6_address (priv->ip6_config, TRUE)) 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); _LOGD (LOGD_DEVICE, "linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses configured. Wait.", method); check_and_add_ipv6ll_addr (self); 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 *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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 (self); 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_IP_CONFIG_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_IP_CONFIG_SOURCE_RDISC; route.metric = nm_device_get_ip6_route_metric (self); 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 (self, TRUE, TRUE); priv->dhcp6_mode = rdisc->dhcp_level; switch (priv->dhcp6_mode) { case NM_RDISC_DHCP_LEVEL_NONE: break; default: _LOGI (LOGD_DEVICE | LOGD_DHCP6, "Activation: Stage 3 of 5 (IP Configure Start) starting DHCPv6" " as requested by IPv6 router..."); switch (dhcp6_start (self, 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 (self, NM_DEVICE_STATE_FAILED, reason); return; } } } /* hop_limit == 0 is a special value "unspecified", so do not touch * in this case */ if (changed & NM_RDISC_CONFIG_HOP_LIMIT && rdisc->hop_limit > 0) { char val[16]; g_snprintf (val, sizeof (val), "%d", rdisc->hop_limit); nm_device_ipv6_sysctl_set (self, "hop_limit", val); } if (changed & NM_RDISC_CONFIG_MTU) { char val[16]; g_snprintf (val, sizeof (val), "%d", rdisc->mtu); nm_device_ipv6_sysctl_set (self, "mtu", val); } nm_device_activate_schedule_ip6_config_result (self); } static void rdisc_ra_timeout (NMRDisc *rdisc, NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); /* We don't want to stop listening for router advertisements completely, * but instead let device activation continue activating. If an RA * shows up later, we'll use it as long as the device is not disconnected. */ _LOGD (LOGD_IP6, "timed out waiting for IPv6 router advertisement"); if (priv->ip6_state == IP_CONF) { /* If RA is our only source of addressing information and we don't * ever receive one, then time out IPv6. But if there is other * IPv6 configuration, like manual IPv6 addresses or external IPv6 * config, consider that sufficient for IPv6 success. */ if (have_ip6_address (priv->ip6_config, FALSE)) nm_device_activate_schedule_ip6_config_result (self); else nm_device_activate_schedule_ip6_config_timeout (self); } } static gboolean addrconf6_start_with_link_ready (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMUtilsIPv6IfaceId iid; g_assert (priv->rdisc); if (!nm_device_get_ip_iface_identifier (self, &iid)) { _LOGW (LOGD_IP6, "failed to get interface identifier; IPv6 cannot continue"); return FALSE; } nm_rdisc_set_iid (priv->rdisc, iid); /* Apply any manual configuration before starting RA */ if (!ip6_config_merge_and_apply (self, TRUE, NULL)) _LOGW (LOGD_IP6, "failed to apply manual IPv6 configuration"); 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_changed_id = g_signal_connect (priv->rdisc, NM_RDISC_CONFIG_CHANGED, G_CALLBACK (rdisc_config_changed), self); priv->rdisc_timeout_id = g_signal_connect (priv->rdisc, NM_RDISC_RA_TIMEOUT, G_CALLBACK (rdisc_ra_timeout), self); nm_rdisc_start (priv->rdisc); return TRUE; } static NMActStageReturn 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) { _LOGE (LOGD_IP6, "failed to start router discovery (%s)", ip_iface); return FALSE; } priv->rdisc_use_tempaddr = use_tempaddr; print_support_extended_ifa_flags (use_tempaddr); if (!nm_setting_ip_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_POSTPONE) { /* success; wait for the LL address to show up */ return TRUE; } /* success; already have the LL address; kick off router discovery */ g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS); return addrconf6_start_with_link_ready (self); } static void addrconf6_cleanup (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); if (priv->rdisc_changed_id) { g_signal_handler_disconnect (priv->rdisc, priv->rdisc_changed_id); priv->rdisc_changed_id = 0; } if (priv->rdisc_timeout_id) { g_signal_handler_disconnect (priv->rdisc, priv->rdisc_timeout_id); priv->rdisc_timeout_id = 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)) { /* Don't touch "disable_ipv6" if we're doing userland IPv6LL */ if (priv->nm_ipv6ll && strcmp (key, "disable_ipv6") == 0) continue; nm_device_ipv6_sysctl_set (self, key, value); } } static inline void set_disable_ipv6 (NMDevice *self, const char *value) { /* We only touch disable_ipv6 when NM is not managing the IPv6LL address */ if (NM_DEVICE_GET_PRIVATE (self)->nm_ipv6ll == FALSE) nm_device_ipv6_sysctl_set (self, "disable_ipv6", value); } static inline void set_nm_ipv6ll (NMDevice *self, gboolean enable) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); int ifindex = nm_device_get_ip_ifindex (self); const char *iface = nm_device_get_ip_iface (self); char *value; if (!nm_platform_check_support_user_ipv6ll ()) return; priv->nm_ipv6ll = enable; if (ifindex > 0) { const char *detail = enable ? "enable" : "disable"; _LOGD (LOGD_IP6, "will %s userland IPv6LL", detail); if ( !nm_platform_link_set_user_ipv6ll_enabled (ifindex, enable) && nm_platform_get_error () != NM_PLATFORM_ERROR_NOT_FOUND) _LOGW (LOGD_IP6, "failed to %s userspace IPv6LL address handling", detail); if (enable) { /* Bounce IPv6 to ensure the kernel stops IPv6LL address generation */ value = nm_platform_sysctl_get (nm_utils_ip6_property_path (iface, "disable_ipv6")); if (g_strcmp0 (value, "0") == 0) { nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1"); nm_device_ipv6_sysctl_set (self, "disable_ipv6", "0"); } g_free (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); if ( connection_ip4_method_requires_carrier (connection, NULL) && priv->is_master && !priv->carrier) { _LOGI (LOGD_IP6 | LOGD_DEVICE, "IPv6 config waiting until carrier is on"); 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) { _LOGI (LOGD_DEVICE | LOGD_IP6, "IPv6 config waiting until slaves are ready"); return NM_ACT_STAGE_RETURN_WAIT; } } priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE; method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) { if (!priv->master) { /* When activating an IPv6 'ignore' connection we need to revert back * to kernel IPv6LL, but the kernel won't actually assign an address * to the interface until disable_ipv6 is bounced. */ set_nm_ipv6ll (self, FALSE); nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1"); restore_ip6_properties (self); } return NM_ACT_STAGE_RETURN_STOP; } /* Any method past this point requires an IPv6LL address. Use NM-controlled * IPv6LL if this is not an assumed connection, since assumed connections * will already have IPv6 set up. */ if (!nm_device_uses_generated_assumed_connection (self)) set_nm_ipv6ll (self, TRUE); /* Re-enable IPv6 on the interface */ set_disable_ipv6 (self, "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) { NMSettingIPConfig *s_ip6 = nm_connection_get_setting_ip6_config (connection); if (s_ip6) ip6_privacy = nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (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 _LOGW (LOGD_IP6, "unhandled IPv6 config method '%s'; will fail", 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); 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; _LOGI (LOGD_DEVICE, "Activation: Stage 3 of 5 (IP Configure Start) started..."); 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))) _LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", 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) { _LOGI (LOGD_DEVICE, "Activation: connection '%s' waiting on master '%s'", 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: _LOGI (LOGD_DEVICE, "Activation: Stage 3 of 5 (IP Configure Start) complete."); return FALSE; } static void fw_change_zone_cb (GError *error, gpointer user_data) { NMDevice *self; NMDevicePrivate *priv; if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; self = NM_DEVICE (user_data); 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); _LOGI (LOGD_DEVICE, "Activation: Stage 3 of 5 (IP Configure Start) scheduled."); } /* * 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); g_return_if_fail (!priv->fw_call); /* 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); _LOGD (LOGD_DEVICE, "Activation: setting firewall zone '%s'", zone ? zone : "default"); priv->fw_call = nm_firewall_manager_add_or_change_zone (nm_firewall_manager_get (), nm_device_get_ip_iface (self), zone, FALSE, nm_device_uses_assumed_connection (self), 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); 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); _LOGI (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 4 of 5 (IPv4 Configure Timeout) started..."); 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: _LOGI (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 4 of 5 (IPv4 Configure Timeout) complete."); 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); _LOGI (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 4 of 5 (IPv4 Configure Timeout) scheduled..."); } 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); 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); _LOGI (LOGD_DEVICE | LOGD_IP6, "Activation: Stage 4 of 5 (IPv6 Configure Timeout) started..."); 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: _LOGI (LOGD_DEVICE | LOGD_IP6, "Activation: Stage 4 of 5 (IPv6 Configure Timeout) complete."); 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); _LOGI (LOGD_DEVICE | LOGD_IP6, "Activation: Stage 4 of 5 (IPv6 Configure Timeout) scheduled..."); } 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; int errsv; if (!nm_platform_sysctl_set ("/proc/sys/net/ipv4/ip_forward", "1")) { errsv = errno; nm_log_err (LOGD_SHARING, "share: error starting IP forwarding: (%d) %s", errsv, strerror (errsv)); return FALSE; } if (!nm_platform_sysctl_set ("/proc/sys/net/ipv4/ip_dynaddr", "1")) { errsv = errno; nm_log_err (LOGD_SHARING, "share: error starting IP forwarding: (%d) %s", errsv, strerror (errsv)); } 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, "share: 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)) { _LOGE (LOGD_SHARING, "share: (%s) failed to start dnsmasq: %s", 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[] = { NULL, mode_arg, "-q", "-I", nm_device_get_ip_iface (self), "-c", "1", NULL, NULL }; int ip_arg = G_N_ELEMENTS (argv) - 2; NMConnection *connection; NMSettingIPConfig *s_ip4; int i, num; NMIPAddress *addr; 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_ip_config_get_num_addresses (s_ip4); if (num == 0) return; argv[0] = nm_utils_find_helper ("arping", NULL, NULL); if (!argv[0]) { _LOGW (LOGD_DEVICE | LOGD_IP4, "arping could not be found; no ARPs will be sent"); return; } for (i = 0; i < num; i++) { gs_free char *tmp_str = NULL; addr = nm_setting_ip_config_get_address (s_ip4, i); argv[ip_arg] = nm_ip_address_get_address (addr); _LOGD (LOGD_DEVICE | LOGD_IP4, "arping: run %s", (tmp_str = g_strjoinv (" ", (char **) argv))); 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) { _LOGW (LOGD_DEVICE | LOGD_IP4, "arping: 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; NMSettingIPConfig *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_ip_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 *method; NMConnection *connection; NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; int ip_ifindex; /* Clear the activation source ID now that this stage has run */ activation_source_clear (self, FALSE, AF_INET); _LOGI (LOGD_DEVICE, "Activation: Stage 5 of 5 (IPv4 Commit) started..."); req = nm_device_get_act_request (self); g_assert (req); connection = nm_act_request_get_connection (req); g_assert (connection); /* Interface must be IFF_UP before IP config can be applied */ ip_ifindex = nm_device_get_ip_ifindex (self); if (!nm_platform_link_is_up (ip_ifindex)) { nm_platform_link_set_up (ip_ifindex); if (!nm_platform_link_is_up (ip_ifindex)) _LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", 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)) { _LOGI (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 5 of 5 (IPv4 Commit) failed"); 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)) { _LOGW (LOGD_SHARING, "Activation: Stage 5 of 5 (IPv4 Commit) start sharing failed."); 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: _LOGI (LOGD_DEVICE, "Activation: Stage 5 of 5 (IPv4 Commit) complete."); return FALSE; } void nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *config) { NMDevicePrivate *priv; g_return_if_fail (NM_IS_DEVICE (self)); priv = NM_DEVICE_GET_PRIVATE (self); g_clear_object (&priv->dev_ip4_config); if (config) priv->dev_ip4_config = g_object_ref (config); activation_source_schedule (self, nm_device_activate_ip4_config_commit, AF_INET); _LOGI (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 5 of 5 (IPv4 Configure Commit) scheduled..."); } 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; NMConnection *connection; NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; int ip_ifindex; /* Clear the activation source ID now that this stage has run */ activation_source_clear (self, FALSE, AF_INET6); _LOG (level, LOGD_DEVICE, "Activation: Stage 5 of 5 (IPv6 Commit) started..."); req = nm_device_get_act_request (self); g_assert (req); connection = nm_act_request_get_connection (req); g_assert (connection); /* Interface must be IFF_UP before IP config can be applied */ ip_ifindex = nm_device_get_ip_ifindex (self); if (!nm_platform_link_is_up (ip_ifindex)) { nm_platform_link_set_up (ip_ifindex); if (!nm_platform_link_is_up (ip_ifindex)) _LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (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 { _LOGW (LOGD_DEVICE | LOGD_IP6, "Activation: Stage 5 of 5 (IPv6 Commit) failed"); nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason); } _LOG (level, LOGD_DEVICE, "Activation: Stage 5 of 5 (IPv6 Commit) complete."); 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)); /* If IP had previously failed, move it back to IP_CONF since we * clearly now have configuration. */ if (priv->ip6_state == IP_FAIL) priv->ip6_state = IP_CONF; activation_source_schedule (self, nm_device_activate_ip6_config_commit, AF_INET6); _LOG (level, LOGD_DEVICE | LOGD_IP6, "Activation: Stage 5 of 5 (IPv6 Commit) scheduled..."); } 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) { _LOGE (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 *self) { return NM_DEVICE_GET_PRIVATE (self)->is_nm_owned; } void nm_device_set_nm_owned (NMDevice *self) { g_return_if_fail (NM_IS_DEVICE (self)); NM_DEVICE_GET_PRIVATE (self)->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; NMDevice *self = data->device; 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; } _LOGD (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); _LOGD (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 (priv->queued_act_request) 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; _LOGD (LOGD_DEVICE, "delete_on_deactivate: schedule cleanup and delete virtual link #%d (id=%u)", ifindex, data->idle_add_id); } static void disconnect_cb (NMDevice *self, DBusGMethodInvocation *context, GError *error, gpointer user_data) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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 { nm_device_set_autoconnect (self, FALSE); nm_device_state_changed (self, NM_DEVICE_STATE_DEACTIVATING, NM_DEVICE_STATE_REASON_USER_REQUESTED); dbus_g_method_return (context); } } static void impl_device_disconnect (NMDevice *self, DBusGMethodInvocation *context) { NMConnection *connection; GError *error = NULL; if (NM_DEVICE_GET_PRIVATE (self)->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 (self); g_assert (connection); /* Ask the manager to authenticate this request for us */ g_signal_emit (self, signals[AUTH_REQUEST], 0, context, connection, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE, disconnect_cb, NULL); } static void delete_cb (NMDevice *self, DBusGMethodInvocation *context, GError *error, gpointer user_data) { if (error) { dbus_g_method_return_error (context, error); return; } /* Authorized */ nm_platform_link_delete (nm_device_get_ifindex (self)); dbus_g_method_return (context); } static void impl_device_delete (NMDevice *self, DBusGMethodInvocation *context) { GError *error = NULL; if (!nm_device_is_software (self)) { error = g_error_new_literal (NM_DEVICE_ERROR, NM_DEVICE_ERROR_NOT_SOFTWARE, "This device is not a software device"); dbus_g_method_return_error (context, error); g_error_free (error); return; } /* Ask the manager to authenticate this request for us */ g_signal_emit (self, signals[AUTH_REQUEST], 0, context, NULL, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE, delete_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); _LOGI (LOGD_DEVICE, "Activation: starting connection '%s'", 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 */ _LOGI (LOGD_DEVICE, "disconnecting for new activation request."); 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 *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMDeviceState state; g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); state = nm_device_get_state (self); 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, guint32 default_route_metric, 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 && !nm_device_uses_assumed_connection (self)) { success = nm_ip4_config_commit (new_config, ip_ifindex, default_route_metric); 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) { _LOGD (LOGD_IP4, "update IP4Config instance (%s)", 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); } _LOGD (LOGD_IP4, "set IP4Config instance (%s)", nm_ip4_config_get_dbus_path (new_config)); } } else if (old_config) { has_changes = TRUE; priv->ip4_config = NULL; _LOGD (LOGD_IP4, "clear IP4Config instance (%s)", nm_ip4_config_get_dbus_path (old_config)); /* Device config is invalid if combined config is invalid */ g_clear_object (&priv->dev_ip4_config); } nm_default_route_manager_ip4_update_default_route (nm_default_route_manager_get (), self); 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_assumed_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 *self, NMIP4Config *config) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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 (self, NULL, TRUE, NULL)) _LOGW (LOGD_IP4, "failed to set VPN routes for device"); } void nm_device_set_wwan_ip4_config (NMDevice *self, NMIP4Config *config) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); if (priv->wwan_ip4_config == config) return; g_clear_object (&priv->wwan_ip4_config); if (config) priv->wwan_ip4_config = g_object_ref (config); /* NULL to use existing configs */ if (!ip4_config_merge_and_apply (self, NULL, TRUE, NULL)) _LOGW (LOGD_IP4, "failed to set WWAN IPv4 configuration"); } 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) { _LOGD (LOGD_IP6, "update IP6Config instance (%s)", 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); } _LOGD (LOGD_IP4, "set IP6Config instance (%s)", nm_ip6_config_get_dbus_path (new_config)); } } else if (old_config) { has_changes = TRUE; priv->ip6_config = NULL; _LOGD (LOGD_IP6, "clear IP6Config instance (%s)", nm_ip6_config_get_dbus_path (old_config)); } nm_default_route_manager_ip6_update_default_route (nm_default_route_manager_get (), self); 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_assumed_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 *self, NMIP6Config *config) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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 (self, TRUE, NULL)) _LOGW (LOGD_IP6, "failed to set VPN routes for device"); } void nm_device_set_wwan_ip6_config (NMDevice *self, NMIP6Config *config) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); if (priv->wwan_ip6_config == config) return; g_clear_object (&priv->wwan_ip6_config); if (config) priv->wwan_ip6_config = g_object_ref (config); /* NULL to use existing configs */ if (!ip6_config_merge_and_apply (self, TRUE, NULL)) _LOGW (LOGD_IP6, "failed to set WWAN IPv6 configuration"); } 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) { nm_utils_kill_child_async (priv->gw_ping.pid, SIGTERM, priv->gw_ping.log_domain, "ping", 1000, NULL, NULL); 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); guint log_domain = priv->gw_ping.log_domain; if (!priv->gw_ping.watch) return; priv->gw_ping.watch = 0; priv->gw_ping.pid = 0; if (WIFEXITED (status)) { if (WEXITSTATUS (status) == 0) _LOGD (log_domain, "ping: gateway ping succeeded"); else { _LOGW (log_domain, "ping: gateway ping failed with error code %d", WEXITSTATUS (status)); } } else _LOGW (log_domain, "ping: stopped unexpectedly with status %d", 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; _LOGW (priv->gw_ping.log_domain, "ping: gateway ping timed out"); 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; gs_free char *tmp_str = NULL; 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); _LOGD (log_domain, "ping: running '%s'", (tmp_str = g_strjoinv (" ", (gchar **) args))); 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 { _LOGW (log_domain, "ping: 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 *self) { int ifindex = nm_device_get_ip_ifindex (self); 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); _LOGD (LOGD_HW, "bringing up device."); 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) _LOGW (LOGD_HW, "device not up after timeout!"); else _LOGD (LOGD_HW, "device not up immediately"); 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 *self) { int ifindex = nm_device_get_ip_ifindex (self); if (!device_has_capability (self, NM_DEVICE_CAP_NONSTANDARD_CARRIER)) nm_device_set_carrier (self, nm_platform_link_is_connected (ifindex)); } static gboolean bring_up (NMDevice *self, gboolean *no_firmware) { int ifindex = nm_device_get_ip_ifindex (self); 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 (self, NM_DEVICE_CAP_CARRIER_DETECT)) check_carrier (self); return result; } void nm_device_take_down (NMDevice *self, gboolean block) { gboolean device_is_up; g_return_if_fail (NM_IS_DEVICE (self)); _LOGD (LOGD_HW, "taking down device."); 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) _LOGW (LOGD_HW, "device not down after timeout!"); else _LOGD (LOGD_HW, "device not down immediately"); } } static gboolean take_down (NMDevice *self) { int ifindex = nm_device_get_ip_ifindex (self); if (ifindex > 0) return nm_platform_link_set_down (ifindex); /* devices without ifindex are always up. */ _LOGD (LOGD_HW, "cannot take down device without ifindex"); 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 *self, NMConnection *connection, NMIP4Config *ext_ip4_config) { const char *ip_iface = nm_device_get_ip_iface (self); 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 *self, NMIP4Config *ext_ip4_config, NMIP4Config **out_ip4_config, NMIP6Config *ext_ip6_config, NMIP6Config **out_ip6_config) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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 (self, 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 (self, 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); priv->ext_ip4_config_had_any_addresses = priv->ext_ip4_config && nm_ip4_config_get_num_addresses (priv->ext_ip4_config) > 0; 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); if (priv->wwan_ip4_config) nm_ip4_config_subtract (priv->ext_ip4_config, priv->wwan_ip4_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); priv->ext_ip6_config_had_any_addresses = priv->ext_ip6_config && nm_ip6_config_get_num_addresses (priv->ext_ip6_config) > 0; if (priv->ext_ip6_config) { /* Check this before modifying ext_ip6_config */ linklocal6_just_completed = priv->linklocal6_timeout_id && have_ip6_address (priv->ext_ip6_config, TRUE); 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->wwan_ip6_config) nm_ip6_config_subtract (priv->ext_ip6_config, priv->wwan_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 *self) { update_ip_config (self, 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); /* If no IPv6 link-local address exists but other addresses do then we * must add the LL address to remain conformant with RFC 3513 chapter 2.1 * ("Addressing Model"): "All interfaces are required to have at least * one link-local unicast address". */ if (priv->ip6_config && nm_ip6_config_get_num_addresses (priv->ip6_config)) check_and_add_ipv6ll_addr (self); return FALSE; } static void device_ip_changed (NMPlatform *platform, int ifindex, gpointer platform_object, NMPlatformSignalChangeType change_type, NMPlatformReason reason, NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); if (nm_device_get_ip_ifindex (self) == ifindex) { if (!priv->queued_ip_config_id) priv->queued_ip_config_id = g_idle_add (queued_ip_config_change, self); _LOGD (LOGD_DEVICE, "queued IP config change"); } } static void nm_device_queued_ip_config_change_clear (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); if (priv->queued_ip_config_id) { _LOGD (LOGD_DEVICE, "clearing queued IP config change"); g_source_remove (priv->queued_ip_config_id); priv->queued_ip_config_id = 0; } } /** * nm_device_get_managed(): * @self: the #NMDevice * * Returns: %TRUE if the device is managed */ gboolean nm_device_get_managed (NMDevice *self) { NMDevicePrivate *priv; gboolean managed; g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); priv = NM_DEVICE_GET_PRIVATE (self); /* 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(): * @self: the #NMDevice * * Returns: %TRUE if the device is unmanaged for @flag. */ gboolean nm_device_get_unmanaged_flag (NMDevice *self, NMUnmanagedFlags flag) { return NM_DEVICE_GET_PRIVATE (self)->unmanaged_flags & flag; } /** * nm_device_get_default_unmanaged(): * @self: the #NMDevice * * Returns: %TRUE if the device is by default unmanaged */ static gboolean nm_device_get_default_unmanaged (NMDevice *self) { return nm_device_get_unmanaged_flag (self, NM_UNMANAGED_DEFAULT); } void nm_device_set_unmanaged (NMDevice *self, NMUnmanagedFlags flag, gboolean unmanaged, NMDeviceStateReason reason) { NMDevicePrivate *priv; gboolean was_managed, now_managed; g_return_if_fail (NM_IS_DEVICE (self)); g_return_if_fail (flag <= NM_UNMANAGED_LAST); priv = NM_DEVICE_GET_PRIVATE (self); was_managed = nm_device_get_managed (self); if (unmanaged) priv->unmanaged_flags |= flag; else priv->unmanaged_flags &= ~flag; now_managed = nm_device_get_managed (self); if (was_managed != now_managed) { _LOGD (LOGD_DEVICE, "now %s", unmanaged ? "unmanaged" : "managed"); g_object_notify (G_OBJECT (self), NM_DEVICE_MANAGED); if (unmanaged) nm_device_state_changed (self, NM_DEVICE_STATE_UNMANAGED, reason); else nm_device_state_changed (self, NM_DEVICE_STATE_UNAVAILABLE, reason); } } void nm_device_set_unmanaged_quitting (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); /* It's OK to block here because we're quitting */ if (nm_device_is_activating (self) || priv->state == NM_DEVICE_STATE_ACTIVATED) _set_state_full (self, NM_DEVICE_STATE_DEACTIVATING, NM_DEVICE_STATE_REASON_REMOVED, TRUE); nm_device_set_unmanaged (self, NM_UNMANAGED_INTERNAL, TRUE, NM_DEVICE_STATE_REASON_REMOVED); } /** * nm_device_set_initial_unmanaged_flag(): * @self: 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 *self, NMUnmanagedFlags flag, gboolean unmanaged) { NMDevicePrivate *priv; g_return_if_fail (NM_IS_DEVICE (self)); g_return_if_fail (flag <= NM_UNMANAGED_LAST); priv = NM_DEVICE_GET_PRIVATE (self); 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 *self, guint32 timeout) { g_return_if_fail (NM_IS_DEVICE (self)); NM_DEVICE_GET_PRIVATE (self)->dhcp_timeout = timeout; } void nm_device_set_dhcp_anycast_address (NMDevice *self, const char *addr) { NMDevicePrivate *priv; g_return_if_fail (NM_IS_DEVICE (self)); g_return_if_fail (!addr || nm_utils_hwaddr_valid (addr, ETH_ALEN)); priv = NM_DEVICE_GET_PRIVATE (self); g_free (priv->dhcp_anycast_address); priv->dhcp_anycast_address = g_strdup (addr); } /** * nm_device_connection_is_available(): * @self: 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 @self. Normally this * only checks if the connection is in @self'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 @self */ gboolean nm_device_connection_is_available (NMDevice *self, NMConnection *connection, gboolean allow_device_override) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); gboolean available = FALSE; if (nm_device_get_default_unmanaged (self) && (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 (self, connection) && NM_DEVICE_GET_CLASS (self)->check_connection_available (self, 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 (self, connection) && NM_DEVICE_GET_CLASS (self)->check_connection_available_wifi_hidden) available = NM_DEVICE_GET_CLASS (self)->check_connection_available_wifi_hidden (self, connection); } return available; } static void _signal_available_connections_changed (NMDevice *self) { g_object_notify (G_OBJECT (self), NM_DEVICE_AVAILABLE_CONNECTIONS); } static void _clear_available_connections (NMDevice *self, gboolean do_signal) { g_hash_table_remove_all (NM_DEVICE_GET_PRIVATE (self)->available_connections); if (do_signal == TRUE) _signal_available_connections_changed (self); } static gboolean _try_add_available_connection (NMDevice *self, NMConnection *connection) { if ( nm_device_get_state (self) < NM_DEVICE_STATE_DISCONNECTED && !nm_device_get_default_unmanaged (self)) 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 *self, NMConnection *connection) { return g_hash_table_remove (NM_DEVICE_GET_PRIVATE (self)->available_connections, connection); } static gboolean check_connection_available (NMDevice *self, 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 (self)->carrier == FALSE) return connection_requires_carrier (connection) ? FALSE : TRUE; return TRUE; } void nm_device_recheck_available_connections (NMDevice *self) { NMDevicePrivate *priv; const GSList *connections, *iter; g_return_if_fail (NM_IS_DEVICE (self)); priv = NM_DEVICE_GET_PRIVATE(self); if (priv->con_provider) { _clear_available_connections (self, FALSE); connections = nm_connection_provider_get_connections (priv->con_provider); for (iter = connections; iter; iter = g_slist_next (iter)) _try_add_available_connection (self, NM_CONNECTION (iter->data)); _signal_available_connections_changed (self); } } /** * nm_device_get_available_connections: * @self: 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 *self, const char *specific_object) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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 (self)->check_connection_available (self, 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 *self) { return nm_platform_link_supports_vlans (nm_device_get_ifindex (self)); } /** * nm_device_add_pending_action(): * @self: 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 *self, const char *action, gboolean assert_not_yet_pending) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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) { _LOGW (LOGD_DEVICE, "add_pending_action (%d): '%s' already pending", count + g_slist_length (iter), action); g_return_val_if_reached (FALSE); } else { _LOGD (LOGD_DEVICE, "add_pending_action (%d): '%s' already pending (expected)", count + g_slist_length (iter), action); } return FALSE; } count++; } priv->pending_actions = g_slist_append (priv->pending_actions, g_strdup (action)); count++; _LOGD (LOGD_DEVICE, "add_pending_action (%d): '%s'", count, action); if (count == 1) g_object_notify (G_OBJECT (self), NM_DEVICE_HAS_PENDING_ACTION); return TRUE; } /** * nm_device_remove_pending_action(): * @self: 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 *self, const char *action, gboolean assert_is_pending) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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)) { _LOGD (LOGD_DEVICE, "remove_pending_action (%d): '%s'", 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 (self), NM_DEVICE_HAS_PENDING_ACTION); return TRUE; } count++; } if (assert_is_pending) { _LOGW (LOGD_DEVICE, "remove_pending_action (%d): '%s' not pending", count, action); g_return_val_if_reached (FALSE); } else _LOGD (LOGD_DEVICE, "remove_pending_action (%d): '%s' not pending (expected)", count, action); return FALSE; } gboolean nm_device_has_pending_action (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); return !!priv->pending_actions; } /***********************************************************/ static void _cleanup_generic_pre (NMDevice *self, gboolean deconfigure) { NMConnection *connection; NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); /* Clean up when device was deactivated during call to firewall */ if (priv->fw_call) { nm_firewall_manager_cancel_call (nm_firewall_manager_get (), priv->fw_call); priv->fw_call = NULL; } connection = nm_device_get_connection (self); if (deconfigure && connection) { nm_firewall_manager_remove_from_zone (nm_firewall_manager_get (), nm_device_get_ip_iface (self), NULL, nm_device_uses_assumed_connection (self)); } 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; priv->default_route.v4_has = FALSE; priv->default_route.v6_has = FALSE; nm_default_route_manager_ip4_remove_default_route (nm_default_route_manager_get (), self); nm_default_route_manager_ip6_remove_default_route (nm_default_route_manager_get (), self); /* 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, 0, 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->wwan_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->wwan_ip6_config); g_clear_object (&priv->ip6_config); priv->ext_ip4_config_had_any_addresses = FALSE; priv->ext_ip6_config_had_any_addresses = FALSE; 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) _LOGI (LOGD_DEVICE, "preparing device"); else _LOGI (LOGD_DEVICE, "deactivating device (reason '%s') [%d]", 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 */ set_disable_ipv6 (self, "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 char * bin2hexstr (const char *bytes, gsize len) { GString *str; int i; g_return_val_if_fail (bytes != NULL, NULL); g_return_val_if_fail (len > 0, NULL); str = g_string_sized_new (len * 2 + 1); for (i = 0; i < len; i++) { if (str->len) g_string_append_c (str, ':'); g_string_append_printf (str, "%02x", (guint8) bytes[i]); } return g_string_free (str, FALSE); } static char * find_dhcp4_address (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); guint i, n; if (!priv->ip4_config) return NULL; n = nm_ip4_config_get_num_addresses (priv->ip4_config); for (i = 0; i < n; i++) { const NMPlatformIP4Address *a = nm_ip4_config_get_address (priv->ip4_config, i); if (a->source == NM_IP_CONFIG_SOURCE_DHCP) return g_strdup (nm_utils_inet4_ntop (a->address, NULL)); } return NULL; } void nm_device_spawn_iface_helper (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); gboolean priority_set = FALSE, configured = FALSE; NMConnection *connection; GError *error = NULL; const char *method; GPtrArray *argv; gs_free char *dhcp4_address = NULL; if (priv->state != NM_DEVICE_STATE_ACTIVATED) return; if (!nm_device_can_assume_connections (self)) return; connection = nm_device_get_connection (self); g_assert (connection); argv = g_ptr_array_sized_new (10); g_ptr_array_set_free_func (argv, g_free); g_ptr_array_add (argv, g_strdup (LIBEXECDIR "/nm-iface-helper")); g_ptr_array_add (argv, g_strdup ("--ifname")); g_ptr_array_add (argv, g_strdup (nm_device_get_ip_iface (self))); g_ptr_array_add (argv, g_strdup ("--uuid")); g_ptr_array_add (argv, g_strdup (nm_connection_get_uuid (connection))); dhcp4_address = find_dhcp4_address (self); method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); if ( priv->ip4_config && priv->ip4_state == IP_DONE && g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0 && priv->dhcp4_client && dhcp4_address) { NMSettingIPConfig *s_ip4; GBytes *client_id; char *hex_client_id; const char *hostname; s_ip4 = nm_connection_get_setting_ip4_config (connection); g_assert (s_ip4); g_ptr_array_add (argv, g_strdup ("--priority")); g_ptr_array_add (argv, g_strdup_printf ("%u", nm_dhcp_client_get_priority (priv->dhcp4_client))); priority_set = TRUE; g_ptr_array_add (argv, g_strdup ("--dhcp4")); g_ptr_array_add (argv, g_strdup (dhcp4_address)); if (nm_setting_ip_config_get_may_fail (s_ip4) == FALSE) g_ptr_array_add (argv, g_strdup ("--dhcp4-required")); client_id = nm_dhcp_client_get_client_id (priv->dhcp4_client); if (client_id) { g_ptr_array_add (argv, g_strdup ("--dhcp4-clientid")); hex_client_id = bin2hexstr (g_bytes_get_data (client_id, NULL), g_bytes_get_size (client_id)); g_ptr_array_add (argv, hex_client_id); } hostname = nm_dhcp_client_get_hostname (priv->dhcp4_client); if (client_id) { g_ptr_array_add (argv, g_strdup ("--dhcp4-hostname")); g_ptr_array_add (argv, g_strdup (hostname)); } configured = TRUE; } method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); if ( priv->ip6_config && priv->ip6_state == IP_DONE && g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0 && priv->rdisc && priv->ac_ip6_config) { NMSettingIPConfig *s_ip6; char *hex_iid; NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT; s_ip6 = nm_connection_get_setting_ip6_config (connection); g_assert (s_ip6); g_ptr_array_add (argv, g_strdup ("--slaac")); if (nm_setting_ip_config_get_may_fail (s_ip6) == FALSE) g_ptr_array_add (argv, g_strdup ("--slaac-required")); g_ptr_array_add (argv, g_strdup ("--slaac-tempaddr")); g_ptr_array_add (argv, g_strdup_printf ("%d", priv->rdisc_use_tempaddr)); if (nm_device_get_ip_iface_identifier (self, &iid)) { g_ptr_array_add (argv, g_strdup ("--iid")); hex_iid = bin2hexstr ((const char *) iid.id_u8, sizeof (NMUtilsIPv6IfaceId)); g_ptr_array_add (argv, hex_iid); } configured = TRUE; } if (configured) { GPid pid; if (!priority_set) { g_ptr_array_add (argv, g_strdup ("--priority")); g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_priority (self))); } g_ptr_array_add (argv, NULL); if (nm_logging_enabled (LOGL_DEBUG, LOGD_DEVICE)) { char *tmp; tmp = g_strjoinv (" ", (char **) argv->pdata); _LOGD (LOGD_DEVICE, "running '%s'", tmp); g_free (tmp); } if (g_spawn_async (NULL, (char **) argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, &error)) { _LOGI (LOGD_DEVICE, "spawned helper PID %u", (guint) pid); } else { _LOGW (LOGD_DEVICE, "failed to spawn helper: %s", error->message); g_error_free (error); } } g_ptr_array_unref (argv); } /***********************************************************/ 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 *self) { g_object_notify (G_OBJECT (self), NM_DEVICE_IP_IFACE); g_object_notify (G_OBJECT (self), NM_DEVICE_IP4_CONFIG); g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG); g_object_notify (G_OBJECT (self), NM_DEVICE_IP6_CONFIG); g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG); } static void _set_state_full (NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, gboolean quitting) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); 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 (self)); /* 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; _LOGI (LOGD_DEVICE, "device state change: %s -> %s (reason '%s') [%d %d %d]", 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 (self); dispatcher_cleanup (self); /* 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 (self, 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_get_default_unmanaged (self)) nm_device_recheck_available_connections (self); /* 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 (self, 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 (self)) nm_device_cleanup (self, reason); nm_device_take_down (self, TRUE); set_nm_ipv6ll (self, FALSE); restore_ip6_properties (self); } break; case NM_DEVICE_STATE_UNAVAILABLE: if (old_state == NM_DEVICE_STATE_UNMANAGED) { save_ip6_properties (self); if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) { set_nm_ipv6ll (self, TRUE); set_disable_ipv6 (self, "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"); nm_device_ipv6_sysctl_set (self, "use_tempaddr", "0"); } } if (old_state == NM_DEVICE_STATE_UNMANAGED || priv->firmware_missing) { if (!nm_device_bring_up (self, TRUE, &no_firmware) && no_firmware) _LOGW (LOGD_HW, "firmware may be missing."); nm_device_set_firmware_missing (self, 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 (self, reason); break; case NM_DEVICE_STATE_DISCONNECTED: if (old_state > NM_DEVICE_STATE_DISCONNECTED) { /* Ensure devices that previously assumed a connection now have * userspace IPv6LL enabled. */ set_nm_ipv6ll (self, TRUE); nm_device_cleanup (self, 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) nm_device_set_autoconnect (self, TRUE); g_object_notify (G_OBJECT (self), NM_DEVICE_STATE); g_object_notify (G_OBJECT (self), NM_DEVICE_STATE_REASON); g_signal_emit_by_name (self, "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 (self)) { _LOGD (LOGD_DEVICE, "device is available, will transition to DISCONNECTED"); nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); } else { if (old_state == NM_DEVICE_STATE_UNMANAGED) _LOGD (LOGD_DEVICE, "device not yet available for transition to DISCONNECTED"); else if ( old_state > NM_DEVICE_STATE_UNAVAILABLE && nm_device_get_default_unmanaged (self)) nm_device_queue_state (self, 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), self); } 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), self, dispatcher_complete_proceed_state, self, &priv->dispatcher.call_id)) { /* Just proceed on errors */ dispatcher_complete_proceed_state (0, self); } } 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 (self, queued_req); g_object_unref (queued_req); } else if ( old_state > NM_DEVICE_STATE_DISCONNECTED && nm_device_get_default_unmanaged (self)) nm_device_queue_state (self, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE); break; case NM_DEVICE_STATE_ACTIVATED: _LOGI (LOGD_DEVICE, "Activation: successful, device activated."); nm_dispatcher_call (DISPATCHER_ACTION_UP, nm_act_request_get_connection (req), self, NULL, NULL, NULL); break; case NM_DEVICE_STATE_FAILED: connection = nm_device_get_connection (self); _LOGW (LOGD_DEVICE | LOGD_WIFI, "Activation: failed for connection '%s'", connection ? nm_connection_get_id (connection) : ""); /* Notify any slaves of the unexpected failure */ nm_device_master_release_slaves (self); /* 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 (self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); break; case NM_DEVICE_STATE_IP_CHECK: nm_device_start_ip_check (self); /* 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 (self); break; case NM_DEVICE_STATE_SECONDARIES: ip_check_gw_ping_cleanup (self); _LOGD (LOGD_DEVICE, "device entered SECONDARIES state"); break; default: break; } if (state > NM_DEVICE_STATE_DISCONNECTED) delete_on_deactivate_unschedule (self); 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), self); else nm_dispatcher_call (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), self, 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 (self); /* Dispose of the cached activation request */ if (req) g_object_unref (req); priv->in_state_changed = FALSE; } void nm_device_state_changed (NMDevice *self, NMDeviceState state, NMDeviceStateReason reason) { _set_state_full (self, 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) { _LOGD (LOGD_DEVICE, "running queued state change to %s (id %d)", 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) { _LOGW (LOGD_DEVICE, "overwriting previously queued state change to %s (%s)", 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); _LOGD (LOGD_DEVICE, "queued state change to %s due to %s (id %d)", 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) { _LOGD (LOGD_DEVICE, "clearing queued state transition (id %d)", 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 *self) { g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_STATE_UNKNOWN); return NM_DEVICE_GET_PRIVATE (self)->state; } /***********************************************************/ /* NMConfigDevice interface related stuff */ const char * nm_device_get_hw_address (NMDevice *self) { NMDevicePrivate *priv; g_return_val_if_fail (NM_IS_DEVICE (self), NULL); priv = NM_DEVICE_GET_PRIVATE (self); return priv->hw_addr_len ? priv->hw_addr : NULL; } static void nm_device_update_hw_address (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); int ifindex = nm_device_get_ifindex (self); const guint8 *hwaddr; gsize hwaddrlen = 0; if (ifindex <= 0) return; hwaddr = nm_platform_link_get_address (ifindex, &hwaddrlen); if (hwaddrlen) { if (!priv->hw_addr || !nm_utils_hwaddr_matches (priv->hw_addr, -1, hwaddr, hwaddrlen)) { g_free (priv->hw_addr); priv->hw_addr = nm_utils_hwaddr_ntoa (hwaddr, hwaddrlen); _LOGD (LOGD_HW | LOGD_DEVICE, "hardware address now %s", priv->hw_addr); g_object_notify (G_OBJECT (self), NM_DEVICE_HW_ADDRESS); } } else { /* Invalid or no hardware address */ if (priv->hw_addr_len != 0) { g_clear_pointer (&priv->hw_addr, g_free); _LOGD (LOGD_HW | LOGD_DEVICE, "previous hardware address is no longer valid"); g_object_notify (G_OBJECT (self), NM_DEVICE_HW_ADDRESS); } } priv->hw_addr_len = hwaddrlen; } gboolean nm_device_set_hw_addr (NMDevice *self, const char *addr, const char *detail, guint64 hw_log_domain) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); gboolean success = FALSE; const char *cur_addr = nm_device_get_hw_address (self); guint8 addr_bytes[NM_UTILS_HWADDR_LEN_MAX]; g_return_val_if_fail (addr != NULL, FALSE); /* Do nothing if current MAC is same */ if (cur_addr && nm_utils_hwaddr_matches (cur_addr, -1, addr, -1)) { _LOGD (LOGD_DEVICE | hw_log_domain, "no MAC address change needed"); return TRUE; } if (!nm_utils_hwaddr_aton (addr, addr_bytes, priv->hw_addr_len)) { _LOGW (LOGD_DEVICE | hw_log_domain, "invalid MAC address %s", addr); return FALSE; } /* Can't change MAC address while device is up */ nm_device_take_down (self, FALSE); success = nm_platform_link_set_address (nm_device_get_ip_ifindex (self), addr_bytes, priv->hw_addr_len); if (success) { /* MAC address succesfully changed; update the current MAC to match */ nm_device_update_hw_address (self); cur_addr = nm_device_get_hw_address (self); if (cur_addr && nm_utils_hwaddr_matches (cur_addr, -1, addr, -1)) { _LOGI (LOGD_DEVICE | hw_log_domain, "%s MAC address to %s", detail, addr); } else { _LOGW (LOGD_DEVICE | hw_log_domain, "new MAC address %s not successfully set", addr); success = FALSE; } } else { _LOGW (LOGD_DEVICE | hw_log_domain, "failed to %s MAC address to %s", detail, addr); } nm_device_bring_up (self, TRUE, NULL); return success; } /** * nm_device_spec_match_list: * @self: an #NMDevice * @specs: (element-type utf8): a list of device specs * * Checks if @self 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 @self matches one of the specs in @specs */ gboolean nm_device_spec_match_list (NMDevice *self, const GSList *specs) { g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); if (!specs) return FALSE; return NM_DEVICE_GET_CLASS (self)->spec_match_list (self, specs); } static gboolean spec_match_list (NMDevice *self, const GSList *specs) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); gboolean matched = FALSE; if (nm_match_spec_string (specs, "*")) return TRUE; if (priv->hw_addr_len) matched = nm_match_spec_hwaddr (specs, priv->hw_addr); if (!matched) matched = nm_match_spec_interface_name (specs, nm_device_get_iface (self)); return matched; } /***********************************************************/ #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); } /* * 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 (NMDevice *self, 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) { _LOGW (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) { _LOGD (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 *self; 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; self = NM_DEVICE (object); priv = NM_DEVICE_GET_PRIVATE (self); _LOGD (LOGD_DEVICE, "constructor(): %s, kernel ifindex %d", G_OBJECT_TYPE_NAME (self), priv->ifindex); if (!priv->iface) { _LOGE (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 (self)->get_generic_capabilities) priv->capabilities |= NM_DEVICE_GET_CLASS (self)->get_generic_capabilities (self); device_get_driver_info (self, 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), self); g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), self); g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), self); g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), self); g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), self); if (nm_platform_check_support_user_ipv6ll ()) { int ip_ifindex = nm_device_get_ip_ifindex (self); if (ip_ifindex > 0) priv->nm_ipv6ll = nm_platform_link_get_user_ipv6ll_enabled (ip_ifindex); } return object; error: g_object_unref (self); return NULL; } static void constructed (GObject *object) { NMDevice *self = NM_DEVICE (object); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); nm_device_update_hw_address (self); if (NM_DEVICE_GET_CLASS (self)->update_permanent_hw_address) NM_DEVICE_GET_CLASS (self)->update_permanent_hw_address (self); if (NM_DEVICE_GET_CLASS (self)->update_initial_hw_address) NM_DEVICE_GET_CLASS (self)->update_initial_hw_address (self); /* Have to call update_initial_hw_address() before calling get_ignore_carrier() */ if (device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) { priv->ignore_carrier = nm_config_get_ignore_carrier (nm_config_get (), self); check_carrier (self); _LOGI (LOGD_HW, "carrier is %s%s", 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); priv->mtu = nm_platform_link_get_mtu (priv->ifindex); } /* Indicate software device in capabilities. */ if (priv->is_software) priv->capabilities |= NM_DEVICE_CAP_IS_SOFTWARE; 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), self); g_signal_connect (priv->con_provider, NM_CP_SIGNAL_CONNECTION_REMOVED, G_CALLBACK (cp_connection_removed), self); g_signal_connect (priv->con_provider, NM_CP_SIGNAL_CONNECTION_UPDATED, G_CALLBACK (cp_connection_updated), self); /* Update default-unmanaged device available connections immediately, * since they don't transition from UNMANAGED (and thus the state handler * doesn't run and update them) until something external happens. */ if (nm_device_get_default_unmanaged (self)) { nm_device_set_autoconnect (self, FALSE); nm_device_recheck_available_connections (self); } 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); /* Let the kernel manage IPv6LL again */ set_nm_ipv6ll (self, FALSE); _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_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); _LOGD (LOGD_DEVICE, "finalize(): %s", G_OBJECT_TYPE_NAME (self)); g_free (priv->hw_addr); 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); g_free (priv->dhcp_anycast_address); G_OBJECT_CLASS (nm_device_parent_class)->finalize (object); } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NMDevice *self = NM_DEVICE (object); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMPlatformLink *platform_device; const char *hw_addr, *p; guint count; 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) _LOGW (LOGD_HW, "failed to look up interface index"); } } 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: nm_device_set_autoconnect (self, 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: /* construct only */ p = hw_addr = g_value_get_string (value); /* Hardware address length is the number of ':' plus 1 */ count = 1; while (p && *p) { if (*p++ == ':') count++; } if (count < ETH_ALEN || count > NM_UTILS_HWADDR_LEN_MAX) { if (hw_addr && *hw_addr) { _LOGW (LOGD_DEVICE, "ignoring hardware address '%s' with unexpected length %d", hw_addr, count); } break; } priv->hw_addr_len = count; g_free (priv->hw_addr); if (nm_utils_hwaddr_valid (hw_addr, priv->hw_addr_len)) priv->hw_addr = g_strdup (hw_addr); else { _LOGW (LOGD_DEVICE, "could not parse hw-address '%s'", hw_addr); priv->hw_addr = NULL; } 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: g_value_set_string (value, priv->hw_addr); 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_ip_iface_identifier = get_ip_iface_identifier; /* Properties */ g_object_class_install_property (object_class, PROP_PLATFORM_DEVICE, g_param_spec_pointer (NM_DEVICE_PLATFORM_DEVICE, "", "", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_UDI, g_param_spec_string (NM_DEVICE_UDI, "", "", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_IFACE, g_param_spec_string (NM_DEVICE_IFACE, "", "", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_IP_IFACE, g_param_spec_string (NM_DEVICE_IP_IFACE, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_DRIVER, g_param_spec_string (NM_DEVICE_DRIVER, "", "", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_DRIVER_VERSION, g_param_spec_string (NM_DEVICE_DRIVER_VERSION, "", "", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_FIRMWARE_VERSION, g_param_spec_string (NM_DEVICE_FIRMWARE_VERSION, "", "", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_CAPABILITIES, g_param_spec_uint (NM_DEVICE_CAPABILITIES, "", "", 0, G_MAXUINT32, NM_DEVICE_CAP_NONE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_CARRIER, g_param_spec_boolean (NM_DEVICE_CARRIER, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_MTU, g_param_spec_uint (NM_DEVICE_MTU, "", "", 0, G_MAXUINT32, 1500, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_IP4_ADDRESS, g_param_spec_uint (NM_DEVICE_IP4_ADDRESS, "", "", 0, G_MAXUINT32, 0, /* FIXME */ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_IP4_CONFIG, g_param_spec_boxed (NM_DEVICE_IP4_CONFIG, "", "", DBUS_TYPE_G_OBJECT_PATH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_DHCP4_CONFIG, g_param_spec_boxed (NM_DEVICE_DHCP4_CONFIG, "", "", DBUS_TYPE_G_OBJECT_PATH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_IP6_CONFIG, g_param_spec_boxed (NM_DEVICE_IP6_CONFIG, "", "", DBUS_TYPE_G_OBJECT_PATH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_DHCP6_CONFIG, g_param_spec_boxed (NM_DEVICE_DHCP6_CONFIG, "", "", DBUS_TYPE_G_OBJECT_PATH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_STATE, g_param_spec_uint (NM_DEVICE_STATE, "", "", 0, G_MAXUINT32, NM_DEVICE_STATE_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_STATE_REASON, g_param_spec_boxed (NM_DEVICE_STATE_REASON, "", "", DBUS_TYPE_STATE_REASON_STRUCT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_ACTIVE_CONNECTION, g_param_spec_boxed (NM_DEVICE_ACTIVE_CONNECTION, "", "", DBUS_TYPE_G_OBJECT_PATH, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_DEVICE_TYPE, g_param_spec_uint (NM_DEVICE_DEVICE_TYPE, "", "", 0, G_MAXUINT32, NM_DEVICE_TYPE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_MANAGED, g_param_spec_boolean (NM_DEVICE_MANAGED, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_AUTOCONNECT, g_param_spec_boolean (NM_DEVICE_AUTOCONNECT, "", "", DEFAULT_AUTOCONNECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_FIRMWARE_MISSING, g_param_spec_boolean (NM_DEVICE_FIRMWARE_MISSING, "", "", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_TYPE_DESC, g_param_spec_string (NM_DEVICE_TYPE_DESC, "", "", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_RFKILL_TYPE, g_param_spec_uint (NM_DEVICE_RFKILL_TYPE, "", "", RFKILL_TYPE_WLAN, RFKILL_TYPE_MAX, RFKILL_TYPE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_IFINDEX, g_param_spec_int (NM_DEVICE_IFINDEX, "", "", 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_AVAILABLE_CONNECTIONS, g_param_spec_boxed (NM_DEVICE_AVAILABLE_CONNECTIONS, "", "", DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_PHYSICAL_PORT_ID, g_param_spec_string (NM_DEVICE_PHYSICAL_PORT_ID, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_IS_MASTER, g_param_spec_boolean (NM_DEVICE_IS_MASTER, "", "", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_MASTER, g_param_spec_object (NM_DEVICE_MASTER, "", "", NM_TYPE_DEVICE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_HW_ADDRESS, g_param_spec_string (NM_DEVICE_HW_ADDRESS, "", "", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_HAS_PENDING_ACTION, g_param_spec_boolean (NM_DEVICE_HAS_PENDING_ACTION, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /* 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); }