diff options
| author | Michael Biebl <biebl@debian.org> | 2020-05-08 21:40:53 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2020-05-08 21:40:53 +0200 |
| commit | d460892bbfece74fb6d3cd846bf6ef548290be41 (patch) | |
| tree | 0474e5be0b5e5fac0d2f3a1e554382e8d1aa6397 /src | |
| parent | 1e5977b62f896e844b548c3007ace9e1dfa7f9ed (diff) | |
New upstream version 1.24.0 upstream/1.24.0
Diffstat (limited to 'src')
38 files changed, 1500 insertions, 694 deletions
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c index 6ff96c32..0bbac4c4 100644 --- a/src/devices/bluetooth/nm-bluez-manager.c +++ b/src/devices/bluetooth/nm-bluez-manager.c @@ -1251,9 +1251,9 @@ _network_server_unregister_bridge (NMBluezManager *self, if (r_req_data) { nm_clear_g_cancellable (&r_req_data->int_cancellable); - nm_utils_invoke_on_idle (_network_server_unregister_bridge_complete_on_idle_cb, - nm_utils_user_data_pack (r_req_data, g_strdup (reason)), - r_req_data->ext_cancellable); + nm_utils_invoke_on_idle (r_req_data->ext_cancellable, + _network_server_unregister_bridge_complete_on_idle_cb, + nm_utils_user_data_pack (r_req_data, g_strdup (reason))); } _nm_device_bridge_notify_unregister_bt_nap (device, reason); diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c index ab18897f..a02ce34f 100644 --- a/src/devices/bluetooth/nm-device-bt.c +++ b/src/devices/bluetooth/nm-device-bt.c @@ -1282,6 +1282,7 @@ dispose (GObject *object) if (priv->modem_manager) { g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_name_owner_changed_cb), self); + g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_modem_added_cb), self); nm_modem_manager_name_owner_unref (priv->modem_manager); g_clear_object (&priv->modem_manager); } diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c index 735dd25b..1c6c42a0 100644 --- a/src/devices/nm-acd-manager.c +++ b/src/devices/nm-acd-manager.c @@ -81,7 +81,20 @@ _acd_event_to_string (unsigned int event) return NULL; } -#define acd_event_to_string_a(event) NM_UTILS_LOOKUP_STR_A (_acd_event_to_string, event) +#define ACD_EVENT_TO_STRING_BUF_SIZE 50 + +static const char * +_acd_event_to_string_buf (unsigned event, char buffer[static ACD_EVENT_TO_STRING_BUF_SIZE]) +{ + const char *s; + + s = _acd_event_to_string (event); + if (s) + return s; + + g_snprintf (buffer, ACD_EVENT_TO_STRING_BUF_SIZE, "(%u)", event); + return buffer; +} static const char * acd_error_to_string (int error) @@ -172,6 +185,7 @@ acd_event (int fd, while ( !n_acd_pop_event (self->acd, &event) && event) { + char to_string_buffer[ACD_EVENT_TO_STRING_BUF_SIZE]; gs_free char *hwaddr_str = NULL; gboolean check_probing_done = FALSE; @@ -215,7 +229,7 @@ acd_event (int fd, nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex)); break; default: - _LOGD ("unhandled event '%s'", acd_event_to_string_a (event->event)); + _LOGD ("unhandled event '%s'", _acd_event_to_string_buf (event->event, to_string_buffer)); break; } diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c index c6c54344..c93c6a54 100644 --- a/src/devices/nm-device-bridge.c +++ b/src/devices/nm-device-bridge.c @@ -328,6 +328,7 @@ commit_option (NMDevice *device, NMSetting *setting, const Option *option, gbool nm_auto_unset_gvalue GValue val = G_VALUE_INIT; GParamSpec *pspec; const char *value; + char value_buf[100]; if (slave) nm_assert (NM_IS_SETTING_BRIDGE_PORT (setting)); @@ -350,7 +351,6 @@ commit_option (NMDevice *device, NMSetting *setting, const Option *option, gbool value = g_value_get_boolean (&val) ? "1" : "0"; break; case G_TYPE_UINT: { - char value_buf[100]; guint uval; uval = g_value_get_uint (&val); diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h index 8d539026..4e260da2 100644 --- a/src/devices/nm-device-private.h +++ b/src/devices/nm-device-private.h @@ -46,6 +46,8 @@ void nm_device_arp_announce (NMDevice *self); NMSettings *nm_device_get_settings (NMDevice *self); +NMManager *nm_device_get_manager (NMDevice *self); + gboolean nm_device_set_ip_ifindex (NMDevice *self, int ifindex); gboolean nm_device_set_ip_iface (NMDevice *self, const char *iface); @@ -203,4 +205,15 @@ gboolean nm_device_match_parent_hwaddr (NMDevice *device, NMConnection *connection, gboolean fail_if_no_hwaddr); +/*****************************************************************************/ + +void nm_device_auth_request (NMDevice *self, + GDBusMethodInvocation *context, + NMConnection *connection, + const char *permission, + gboolean allow_interaction, + GCancellable *cancellable, + NMManagerDeviceAuthRequestFunc callback, + gpointer user_data); + #endif /* NM_DEVICE_PRIVATE_H */ diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c index bb7a5950..9f01a763 100644 --- a/src/devices/nm-device-wireguard.c +++ b/src/devices/nm-device-wireguard.c @@ -1631,8 +1631,20 @@ _get_dev2_ip_config (NMDeviceWireGuard *self, if (prefix < 0) prefix = (addr_family == AF_INET) ? 32 : 128; - if (!ip_config) + if (prefix == 0) { + NMSettingIPConfig *s_ip; + + s_ip = nm_connection_get_setting_ip_config (connection, addr_family); + if (nm_setting_ip_config_get_never_default (s_ip)) + continue; + } + + if (!ip_config) { ip_config = nm_device_ip_config_new (NM_DEVICE (self), addr_family); + nm_ip_config_set_config_flags (ip_config, + NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES, + 0); + } nm_utils_ipx_address_clear_host_address (addr_family, &addrbin, NULL, prefix); diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index c3483e4d..8a22cba8 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -191,7 +191,6 @@ typedef struct { enum { STATE_CHANGED, AUTOCONNECT_ALLOWED, - AUTH_REQUEST, IP4_CONFIG_CHANGED, IP6_CONFIG_CHANGED, IP6_PREFIX_DELEGATED, @@ -577,6 +576,7 @@ typedef struct _NMDevicePrivate { NMMetered metered; NMSettings *settings; + NMManager *manager; NMNetns *netns; @@ -912,6 +912,12 @@ nm_device_get_settings (NMDevice *self) return NM_DEVICE_GET_PRIVATE (self)->settings; } +NMManager * +nm_device_get_manager (NMDevice *self) +{ + return NM_DEVICE_GET_PRIVATE (self)->manager; +} + NMNetns * nm_device_get_netns (NMDevice *self) { @@ -6257,6 +6263,27 @@ dnsmasq_state_changed_cb (NMDnsMasqManager *manager, guint32 status, gpointer us } } +void +nm_device_auth_request (NMDevice *self, + GDBusMethodInvocation *context, + NMConnection *connection, + const char *permission, + gboolean allow_interaction, + GCancellable *cancellable, + NMManagerDeviceAuthRequestFunc callback, + gpointer user_data) +{ + nm_manager_device_auth_request (nm_device_get_manager (self), + self, + context, + connection, + permission, + allow_interaction, + cancellable, + callback, + user_data); +} + /*****************************************************************************/ static void @@ -10557,6 +10584,8 @@ act_stage3_ip_config_start (NMDevice *self, } else { NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; const char *ip6_privacy_str = "0"; + NMPlatform *platform; + int ifindex; if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) { nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1"); @@ -10564,18 +10593,35 @@ act_stage3_ip_config_start (NMDevice *self, } if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) { - if ( !priv->master - && !nm_device_sys_iface_state_is_external (self)) { - gboolean ipv6ll_handle_old = priv->ipv6ll_handle; - - /* 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); - if (ipv6ll_handle_old) - nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1"); - restore_ip6_properties (self); + if (!nm_device_sys_iface_state_is_external (self)) { + if (priv->master) { + /* If a device only has an IPv6 link-local address, + * we don't generate an assumed connection. Therefore, + * when a new slave connection (without IP configuration) + * is activated on the device, the link-local address + * remains configured. The IP configuration of an activated + * slave should not depend on the previous state. Flush + * addresses and routes on activation. + */ + ifindex = nm_device_get_ip_ifindex (self); + platform = nm_device_get_platform (self); + + if (ifindex > 0) { + nm_platform_ip_route_flush (platform, AF_INET6, ifindex); + nm_platform_ip_address_flush (platform, AF_INET6, ifindex); + } + } else { + gboolean ipv6ll_handle_old = priv->ipv6ll_handle; + + /* 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); + if (ipv6ll_handle_old) + nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1"); + restore_ip6_properties (self); + } } return NM_ACT_STAGE_RETURN_IP_DONE; } @@ -12234,13 +12280,14 @@ impl_device_reapply (NMDBusObject *obj, } else reapply_data = NULL; - g_signal_emit (self, signals[AUTH_REQUEST], 0, - invocation, - nm_device_get_applied_connection (self), - NM_AUTH_PERMISSION_NETWORK_CONTROL, - TRUE, - reapply_cb, - reapply_data); + nm_device_auth_request (self, + invocation, + nm_device_get_applied_connection (self), + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + NULL, + reapply_cb, + reapply_data); } /*****************************************************************************/ @@ -12277,13 +12324,14 @@ get_applied_connection_cb (NMDevice *self, if (applied_connection != user_data) { /* The applied connection changed due to a race. Reauthenticate. */ - g_signal_emit (self, signals[AUTH_REQUEST], 0, - context, - applied_connection, - NM_AUTH_PERMISSION_NETWORK_CONTROL, - TRUE, - get_applied_connection_cb, - applied_connection /* no need take a ref. We will not dereference this pointer. */); + nm_device_auth_request (self, + context, + applied_connection, + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + NULL, + get_applied_connection_cb, + applied_connection /* no need take a ref. We will not dereference this pointer. */); return; } @@ -12330,13 +12378,14 @@ impl_device_get_applied_connection (NMDBusObject *obj, return; } - g_signal_emit (self, signals[AUTH_REQUEST], 0, - invocation, - applied_connection, - NM_AUTH_PERMISSION_NETWORK_CONTROL, - TRUE, - get_applied_connection_cb, - applied_connection /* no need take a ref. We will not dereference this pointer. */); + nm_device_auth_request (self, + invocation, + applied_connection, + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + NULL, + get_applied_connection_cb, + applied_connection /* no need take a ref. We will not dereference this pointer. */); } /*****************************************************************************/ @@ -12504,13 +12553,14 @@ impl_device_disconnect (NMDBusObject *obj, connection = nm_device_get_applied_connection (self); nm_assert (connection); - g_signal_emit (self, signals[AUTH_REQUEST], 0, - invocation, - connection, - NM_AUTH_PERMISSION_NETWORK_CONTROL, - TRUE, - disconnect_cb, - NULL); + nm_device_auth_request (self, + invocation, + connection, + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + NULL, + disconnect_cb, + NULL); } static void @@ -12556,13 +12606,14 @@ impl_device_delete (NMDBusObject *obj, return; } - g_signal_emit (self, signals[AUTH_REQUEST], 0, - invocation, - NULL, - NM_AUTH_PERMISSION_NETWORK_CONTROL, - TRUE, - delete_cb, - NULL); + nm_device_auth_request (self, + invocation, + NULL, + NM_AUTH_PERMISSION_NETWORK_CONTROL, + TRUE, + NULL, + delete_cb, + NULL); } static void @@ -17390,8 +17441,8 @@ constructed (GObject *object) g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ipx_changed), self); g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), self); + priv->manager = g_object_ref (NM_MANAGER_GET); priv->settings = g_object_ref (NM_SETTINGS_GET); - g_assert (priv->settings); g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED, @@ -17548,6 +17599,7 @@ finalize (GObject *object) /* for testing, NMDeviceTest does not invoke NMDevice::constructed, * and thus @settings might be unset. */ nm_g_object_unref (priv->settings); + nm_g_object_unref (priv->manager); nm_g_object_unref (priv->concheck_mgr); @@ -17944,14 +17996,6 @@ nm_device_class_init (NMDeviceClass *klass) 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, - /* context, connection, permission, allow_interaction, callback, user_data */ - G_TYPE_NONE, 6, G_TYPE_DBUS_METHOD_INVOCATION, NM_TYPE_CONNECTION, 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), diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h index c18301b2..8d51c707 100644 --- a/src/devices/nm-device.h +++ b/src/devices/nm-device.h @@ -115,7 +115,6 @@ nm_device_state_reason_check (NMDeviceStateReason reason) #define NM_DEVICE_HAS_PENDING_ACTION "has-pending-action" /* Internal only */ /* Internal signals */ -#define NM_DEVICE_AUTH_REQUEST "auth-request" #define NM_DEVICE_IP4_CONFIG_CHANGED "ip4-config-changed" #define NM_DEVICE_IP6_CONFIG_CHANGED "ip6-config-changed" #define NM_DEVICE_IP6_PREFIX_DELEGATED "ip6-prefix-delegated" @@ -455,12 +454,6 @@ typedef struct _NMDeviceClass { } NMDeviceClass; -typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device, - GDBusMethodInvocation *context, - NMAuthSubject *subject, - GError *error, - gpointer user_data); - GType nm_device_get_type (void); struct _NMDedupMultiIndex *nm_device_get_multi_index (NMDevice *self); diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c index 951b5788..10f9fa94 100644 --- a/src/devices/ovs/nm-device-ovs-interface.c +++ b/src/devices/ovs/nm-device-ovs-interface.c @@ -310,7 +310,7 @@ deactivate_async (NMDevice *device, nm_device_get_iface (device))) { _LOGT (LOGD_CORE, "deactivate: link not present, proceeding"); nm_device_update_from_platform_link (NM_DEVICE (self), NULL); - nm_utils_invoke_on_idle (deactivate_cb_on_idle, data, cancellable); + nm_utils_invoke_on_idle (cancellable, deactivate_cb_on_idle, data); return; } diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c index 3c1d5b22..4b57fa38 100644 --- a/src/devices/wifi/nm-device-iwd.c +++ b/src/devices/wifi/nm-device-iwd.c @@ -507,7 +507,7 @@ deactivate_async (NMDevice *device, user_data = nm_utils_user_data_pack (g_object_ref (self), callback, callback_user_data); if (!priv->dbus_obj) { - nm_utils_invoke_on_idle (disconnect_cb_on_idle, user_data, cancellable); + nm_utils_invoke_on_idle (cancellable, disconnect_cb_on_idle, user_data); return; } @@ -615,13 +615,6 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError return FALSE; } - /* Hidden SSIDs not supported in any mode (client or AP) */ - if (nm_setting_wireless_get_hidden (s_wireless)) { - nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, - "hidden networks not supported by the IWD backend"); - return FALSE; - } - security = nm_wifi_connection_get_iwd_security (connection, &mapped); if (!mapped) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, @@ -630,6 +623,15 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError } mode = nm_setting_wireless_get_mode (s_wireless); + + /* Hidden SSIDs only supported in client mode */ + if ( nm_setting_wireless_get_hidden (s_wireless) + && !NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) { + nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "non-infrastructure hidden networks not supported by the IWD backend"); + return FALSE; + } + if (NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) { /* 8021x networks can only be used if they've been provisioned on the IWD side and * thus are Known Networks. @@ -757,6 +759,7 @@ complete_connection (NMDevice *device, NMWifiAP *ap; GBytes *ssid; GBytes *setting_ssid = NULL; + gboolean hidden = FALSE; const char *mode; s_wifi = nm_connection_get_setting_wireless (connection); @@ -789,16 +792,14 @@ complete_connection (NMDevice *device, /* Find a compatible AP in the scan list */ ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection); if (!ap) { - if (!nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) { - g_set_error_literal (error, - NM_DEVICE_ERROR, - NM_DEVICE_ERROR_INVALID_CONNECTION, - "No compatible AP in the scan list and hidden SSIDs not supported."); - return FALSE; - } - + /* If we still don't have an AP, then the WiFI settings needs to be + * fully specified by the client. Might not be able to find an AP + * if the network isn't broadcasting the SSID for example. + */ if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error)) return FALSE; + + hidden = TRUE; } } else { ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object); @@ -849,6 +850,9 @@ complete_connection (NMDevice *device, nm_setting_wireless_get_mac_address (s_wifi) ? NULL : nm_device_get_iface (device), TRUE); + if (hidden) + g_object_set (s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL); + return TRUE; } @@ -1061,14 +1065,14 @@ _nm_device_iwd_request_scan (NMDeviceIwd *self, return; } - g_signal_emit_by_name (device, - NM_DEVICE_AUTH_REQUEST, - invocation, - NULL, - NM_AUTH_PERMISSION_WIFI_SCAN, - TRUE, - dbus_request_scan_cb, - options ? g_variant_ref (options) : NULL); + nm_device_auth_request (device, + invocation, + NULL, + NM_AUTH_PERMISSION_WIFI_SCAN, + TRUE, + NULL, + dbus_request_scan_cb, + nm_g_variant_ref (options)); } static gboolean @@ -1786,11 +1790,34 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason) goto out_fail; } + if ( !is_connection_known_network (connection) + && nm_setting_wireless_get_hidden (s_wireless)) { + gs_free char *ssid_str = NULL; + + /* Use Station.ConnectHiddenNetwork method instead of Network proxy. */ + ssid_str = _nm_utils_ssid_to_utf8 (nm_setting_wireless_get_ssid (s_wireless)); + g_dbus_proxy_call (priv->dbus_station_proxy, + "ConnectHiddenNetwork", + g_variant_new ("(s)", ssid_str), + G_DBUS_CALL_FLAGS_NONE, G_MAXINT, + priv->cancellable, + network_connect_cb, + self); + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + if (!nm_wifi_ap_get_supplicant_path (ap)) { + _LOGW (LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) network is provisioned but dbus supplicant path for AP unknown"); + NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + goto out_fail; + } + network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (), nm_ref_string_get_str (nm_wifi_ap_get_supplicant_path (ap)), NM_IWD_NETWORK_INTERFACE); if (!network_proxy) { - _LOGE (LOGD_DEVICE | LOGD_WIFI, + _LOGW (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) could not get Network interface proxy for %s", nm_ref_string_get_str (nm_wifi_ap_get_supplicant_path (ap))); NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c index 266a8425..f5b62989 100644 --- a/src/devices/wifi/nm-device-wifi.c +++ b/src/devices/wifi/nm-device-wifi.c @@ -48,7 +48,14 @@ _LOG_DECLARE_SELF(NMDeviceWifi); #define SCAN_INTERVAL_SEC_STEP 20 #define SCAN_INTERVAL_SEC_MAX 120 -#define SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN 5 +#define SCAN_EXTRA_DELAY_MSEC 500 + +#define SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC (5*60) + +#define SCAN_REQUEST_SSIDS_MAX_NUM 32u +#define SCAN_REQUEST_SSIDS_MAX_AGE_MSEC (3 * 60 * NM_UTILS_MSEC_PER_SEC) + +#define _LOGT_scan(...) _LOGT (LOGD_WIFI_SCAN, "wifi-scan: " __VA_ARGS__) /*****************************************************************************/ @@ -76,44 +83,58 @@ typedef struct { CList scanning_prohibited_lst_head; + GCancellable *scan_request_cancellable; + + GSource *scan_request_delay_source; + NMWifiAP * current_ap; - guint32 rate; - bool enabled:1; /* rfkilled or not */ - bool requested_scan:1; - bool ssid_found:1; - bool is_scanning:1; - bool hidden_probe_scan_warn:1; - gint64 last_scan_msec; + GHashTable *scan_request_ssids_hash; + CList scan_request_ssids_lst_head; - gint32 scheduled_scan_time; /* seconds */ - guint8 scan_interval_sec; - guint pending_scan_id; - guint ap_dump_id; + NMActRequestGetSecretsCallId *wifi_secrets_id; - NMSupplicantManager *sup_mgr; + NMSupplicantManager *sup_mgr; NMSupplMgrCreateIfaceHandle *sup_create_handle; NMSupplicantInterface *sup_iface; - guint sup_timeout_id; /* supplicant association timeout */ - NM80211Mode mode; + gint64 scan_last_complete_msec; + gint64 scan_periodic_next_msec; - NMActRequestGetSecretsCallId *wifi_secrets_id; + gint64 scan_last_request_started_at_msec; + + guint scan_kickoff_timeout_id; + + guint ap_dump_id; guint periodic_update_id; + guint link_timeout_id; - guint32 failed_iface_count; guint reacquire_iface_id; + guint wps_timeout_id; + guint sup_timeout_id; /* supplicant association timeout */ NMDeviceWifiCapabilities capabilities; + NMSettingWirelessWakeOnWLan wowlan_restore; - gint32 hw_addr_scan_expire; + NMDeviceWifiP2P *p2p_device; + NM80211Mode mode; - guint wps_timeout_id; + guint32 failed_iface_count; + gint32 hw_addr_scan_expire; - NMSettingWirelessWakeOnWLan wowlan_restore; + guint32 rate; + + guint8 scan_periodic_interval_sec; + + bool enabled:1; /* rfkilled or not */ + bool scan_is_scanning:1; + bool scan_periodic_allowed:1; + bool scan_explicit_allowed:1; + bool scan_explicit_requested:1; + bool ssid_found:1; + bool hidden_probe_scan_warn:1; - NMDeviceWifiP2P *p2p_device; } NMDeviceWifiPrivate; struct _NMDeviceWifi @@ -135,12 +156,8 @@ G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE) /*****************************************************************************/ -static gboolean check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic); - static void supplicant_iface_state_down (NMDeviceWifi *self); -static void schedule_scan (NMDeviceWifi *self, gboolean backoff); - static void cleanup_association_attempt (NMDeviceWifi * self, gboolean disconnect); @@ -173,15 +190,8 @@ static void supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface, GParamSpec *pspec, NMDeviceWifi *self); -static void _requested_scan_set (NMDeviceWifi *self, gboolean value); - static void periodic_update (NMDeviceWifi *self); -static void request_wireless_scan (NMDeviceWifi *self, - gboolean periodic, - gboolean force_if_scanning, - const GPtrArray *ssids); - static void ap_add_remove (NMDeviceWifi *self, gboolean is_adding, NMWifiAP *ap, @@ -191,6 +201,158 @@ static void _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset); static void recheck_p2p_availability (NMDeviceWifi *self); +static void _scan_kickoff (NMDeviceWifi *self); + +static gboolean _scan_notify_allowed (NMDeviceWifi *self, NMTernary do_kickoff); + +/*****************************************************************************/ + +typedef struct { + GBytes *ssid; + CList lst; + gint64 timestamp_msec; +} ScanRequestSsidData; + +static void +_scan_request_ssids_remove (ScanRequestSsidData *srs_data) +{ + c_list_unlink_stale (&srs_data->lst); + g_bytes_unref (srs_data->ssid); + nm_g_slice_free (srs_data); +} + +static void +_scan_request_ssids_remove_with_hash (NMDeviceWifiPrivate *priv, + ScanRequestSsidData *srs_data) +{ + nm_assert (srs_data); + nm_assert (nm_g_hash_table_lookup (priv->scan_request_ssids_hash, srs_data) == srs_data); + if (!g_hash_table_remove (priv->scan_request_ssids_hash, srs_data)) + nm_assert_not_reached (); + _scan_request_ssids_remove (srs_data); +} + +static void +_scan_request_ssids_remove_all (NMDeviceWifiPrivate *priv, + gint64 cutoff_with_now_msec, + guint cutoff_at_len) +{ + ScanRequestSsidData *srs_data; + + nm_assert ((!priv->scan_request_ssids_hash) == c_list_is_empty (&priv->scan_request_ssids_lst_head)); + if (!priv->scan_request_ssids_hash) + return; + + if (cutoff_at_len == 0) { + nm_clear_pointer (&priv->scan_request_ssids_hash, g_hash_table_destroy); + while ((srs_data = c_list_first_entry (&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) + _scan_request_ssids_remove (srs_data); + return; + } + + if (cutoff_with_now_msec != 0) { + gint64 cutoff_time_msec; + + /* remove all entries that are older than a max-age. */ + nm_assert (cutoff_with_now_msec > 0); + cutoff_time_msec = cutoff_with_now_msec - SCAN_REQUEST_SSIDS_MAX_AGE_MSEC; + while ((srs_data = c_list_last_entry (&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) { + if (srs_data->timestamp_msec > cutoff_time_msec) + break; + _scan_request_ssids_remove_with_hash (priv, srs_data); + } + } + + if (cutoff_at_len != G_MAXUINT) { + guint i; + + /* trim the list to cutoff_at_len elements. */ + i = nm_g_hash_table_size (priv->scan_request_ssids_hash); + for (; i > cutoff_at_len; i--) { + ScanRequestSsidData *d; + + d = c_list_last_entry (&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst); + _scan_request_ssids_remove_with_hash (priv, d); + } + } + + nm_assert (nm_g_hash_table_size (priv->scan_request_ssids_hash) <= SCAN_REQUEST_SSIDS_MAX_NUM); + nm_assert (nm_g_hash_table_size (priv->scan_request_ssids_hash) == c_list_length (&priv->scan_request_ssids_lst_head)); + if (c_list_is_empty (&priv->scan_request_ssids_lst_head)) + nm_clear_pointer (&priv->scan_request_ssids_hash, g_hash_table_destroy); +} + +static GPtrArray * +_scan_request_ssids_fetch (NMDeviceWifiPrivate *priv, gint64 now_msec) +{ + ScanRequestSsidData *srs_data; + GPtrArray *ssids; + guint len; + + _scan_request_ssids_remove_all (priv, now_msec, G_MAXUINT); + + len = nm_g_hash_table_size (priv->scan_request_ssids_hash); + if (len == 0) + return NULL; + + ssids = g_ptr_array_new_full (len, (GDestroyNotify) g_bytes_unref); + nm_clear_pointer (&priv->scan_request_ssids_hash, g_hash_table_destroy); + while ((srs_data = c_list_first_entry (&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) { + g_ptr_array_add (ssids, g_steal_pointer (&srs_data->ssid)); + _scan_request_ssids_remove (srs_data); + } + return ssids; +} + +static void +_scan_request_ssids_track (NMDeviceWifiPrivate *priv, + const GPtrArray *ssids) +{ + CList old_lst_head; + gint64 now_msec; + guint i; + + if ( !ssids + || ssids->len == 0) + return; + + now_msec = nm_utils_get_monotonic_timestamp_msec (); + + if (!priv->scan_request_ssids_hash) + priv->scan_request_ssids_hash = g_hash_table_new (nm_pgbytes_hash, nm_pgbytes_equal); + + /* Do a little dance. New elements shall keep their order as in @ssids, but all + * new elements should be sorted in the list preexisting elements of the list. + * First move the old elements away, and splice them back afterwards. */ + c_list_init (&old_lst_head); + c_list_splice (&old_lst_head, &priv->scan_request_ssids_lst_head); + + for (i = 0; i < ssids->len; i++) { + GBytes *ssid = ssids->pdata[i]; + ScanRequestSsidData *d; + + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (ScanRequestSsidData, ssid) == 0); + d = g_hash_table_lookup (priv->scan_request_ssids_hash, &ssid); + if (!d) { + d = g_slice_new (ScanRequestSsidData); + *d = (ScanRequestSsidData) { + .lst = C_LIST_INIT (d->lst), + .timestamp_msec = now_msec, + .ssid = g_bytes_ref (ssid), + }; + g_hash_table_add (priv->scan_request_ssids_hash, d); + } else + d->timestamp_msec = now_msec; + c_list_link_tail (&priv->scan_request_ssids_lst_head, &d->lst); + } + + c_list_splice (&priv->scan_request_ssids_lst_head, &old_lst_head); + + /* Trim the excess. After our splice with old_lst_head, the list contains the new + * elements (from @ssids) at the front (in there original order), followed by older elements. */ + _scan_request_ssids_remove_all (priv, now_msec, SCAN_REQUEST_SSIDS_MAX_NUM); +} + /*****************************************************************************/ void @@ -218,16 +380,15 @@ nm_device_wifi_scanning_prohibited_track (NMDeviceWifi *self, if (!temporarily_prohibited) { if (!elem) return; - nm_c_list_elem_free (elem); - return; + } else { + if (elem) + return; + c_list_link_tail (&priv->scanning_prohibited_lst_head, + &nm_c_list_elem_new_stale (tag)->lst); } - if (elem) - return; - - c_list_link_tail (&priv->scanning_prohibited_lst_head, - &nm_c_list_elem_new_stale (tag)->lst); + _scan_notify_allowed (self, NM_TERNARY_DEFAULT); } /*****************************************************************************/ @@ -252,28 +413,36 @@ nm_device_wifi_get_scanning (NMDeviceWifi *self) { g_return_val_if_fail (NM_IS_DEVICE_WIFI (self), FALSE); - return NM_DEVICE_WIFI_GET_PRIVATE (self)->is_scanning; + return NM_DEVICE_WIFI_GET_PRIVATE (self)->scan_is_scanning; } -static void -_notify_scanning (NMDeviceWifi *self) +static gboolean +_scan_is_scanning_eval (NMDeviceWifiPrivate *priv) +{ + return priv->scan_request_cancellable + || priv->scan_request_delay_source + || ( priv->sup_iface + && nm_supplicant_interface_get_scanning (priv->sup_iface)); +} + +static gboolean +_scan_notify_is_scanning (NMDeviceWifi *self) { NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); - gboolean scanning; gboolean last_scan_changed = FALSE; + NMDeviceState state; + gboolean scanning; - scanning = priv->sup_iface - && nm_supplicant_interface_get_scanning (priv->sup_iface); - - if (scanning == priv->is_scanning) - return; + scanning = _scan_is_scanning_eval (priv); + if (scanning == priv->scan_is_scanning) + return FALSE; - priv->is_scanning = scanning; + priv->scan_is_scanning = scanning; if ( !scanning - || priv->last_scan_msec == 0) { + || priv->scan_last_complete_msec == 0) { last_scan_changed = TRUE; - priv->last_scan_msec = nm_utils_get_monotonic_timestamp_msec (); + priv->scan_last_complete_msec = nm_utils_get_monotonic_timestamp_msec (); } _LOGD (LOGD_WIFI, @@ -281,7 +450,16 @@ _notify_scanning (NMDeviceWifi *self) scanning ? "scanning" : "idle", last_scan_changed ? " (notify last-scan)" : ""); - schedule_scan (self, TRUE); + state = nm_device_get_state (NM_DEVICE (self)); + + if (scanning) { + /* while the device is activating/activated, we don't need the pending + * action. The pending action exists to delay startup complete, while + * activating that is already achieved via other means. */ + if ( state <= NM_DEVICE_STATE_DISCONNECTED + || state > NM_DEVICE_STATE_ACTIVATED) + nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WIFI_SCAN, FALSE); + } nm_gobject_notify_together (self, PROP_SCANNING, @@ -289,13 +467,73 @@ _notify_scanning (NMDeviceWifi *self) ? PROP_LAST_SCAN : PROP_0); - if (!priv->is_scanning) { - _requested_scan_set (self, FALSE); - if (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED) { - /* Run a quick update of current AP when coming out of a scan */ - periodic_update (self); - } + _scan_kickoff (self); + + if (!_scan_is_scanning_eval (priv)) { + if ( state <= NM_DEVICE_STATE_DISCONNECTED + || state > NM_DEVICE_STATE_ACTIVATED) + nm_device_emit_recheck_auto_activate (NM_DEVICE (self)); + nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WIFI_SCAN, FALSE); } + + return TRUE; +} + +static gboolean +_scan_notify_allowed (NMDeviceWifi *self, NMTernary do_kickoff) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + gboolean explicit_allowed; + gboolean periodic_allowed; + NMDeviceState state; + gboolean changed = FALSE; + + state = nm_device_get_state (NM_DEVICE (self)); + + explicit_allowed = FALSE; + periodic_allowed = FALSE; + + if (!c_list_is_empty (&priv->scanning_prohibited_lst_head)) { + /* something prohibits scanning. */ + } else if (NM_IN_SET (priv->mode, NM_802_11_MODE_ADHOC, + NM_802_11_MODE_AP)) { + /* Don't scan when a an AP or Ad-Hoc connection is active as it will + * disrupt connected clients or peers. */ + } else if (NM_IN_SET (state, NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_FAILED)) { + /* Can always scan when disconnected */ + explicit_allowed = TRUE; + periodic_allowed = TRUE; + } else if (NM_IN_SET (state, NM_DEVICE_STATE_ACTIVATED)) { + /* Prohibit periodic scans when connected; we ask the supplicant to + * background scan for us, unless the connection is locked to a specific + * BSSID (in which case scanning is effectively disabled). */ + periodic_allowed = FALSE; + + /* Prohibit scans if the supplicant is busy */ + explicit_allowed = !NM_IN_SET (nm_supplicant_interface_get_state (priv->sup_iface), + NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING, + NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED, + NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE, + NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE); + } + + if ( explicit_allowed != priv->scan_explicit_allowed + || periodic_allowed != priv->scan_periodic_allowed) { + priv->scan_periodic_allowed = periodic_allowed; + priv->scan_explicit_allowed = explicit_allowed; + _LOGT_scan ("scan-periodic-allowed=%d, scan-explicit-allowed=%d", + periodic_allowed, + explicit_allowed); + changed = TRUE; + } + + if ( do_kickoff == NM_TERNARY_TRUE + || ( do_kickoff == NM_TERNARY_DEFAULT + && changed)) + _scan_kickoff (self); + + return changed; } static void @@ -303,7 +541,7 @@ supplicant_iface_notify_scanning_cb (NMSupplicantInterface *iface, GParamSpec *pspec, NMDeviceWifi *self) { - _notify_scanning (self); + _scan_notify_is_scanning (self); } static gboolean @@ -370,7 +608,7 @@ supplicant_interface_acquire_cb (NMSupplicantManager *supplicant_manager, G_CALLBACK (supplicant_iface_notify_p2p_available), self); - _notify_scanning (self); + _scan_notify_is_scanning (self); if (nm_supplicant_interface_get_state (priv->sup_iface) != NM_SUPPLICANT_INTERFACE_STATE_STARTING) { /* fake an initial state change. */ @@ -399,26 +637,6 @@ supplicant_interface_acquire (NMDeviceWifi *self) } static void -_requested_scan_set (NMDeviceWifi *self, gboolean value) -{ - NMDeviceWifiPrivate *priv; - - value = !!value; - - priv = NM_DEVICE_WIFI_GET_PRIVATE (self); - if (priv->requested_scan == value) - return; - - priv->requested_scan = value; - if (value) - nm_device_add_pending_action ((NMDevice *) self, NM_PENDING_ACTION_WIFI_SCAN, TRUE); - else { - nm_device_emit_recheck_auto_activate (NM_DEVICE (self)); - nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WIFI_SCAN, TRUE); - } -} - -static void supplicant_interface_release (NMDeviceWifi *self) { NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); @@ -426,14 +644,14 @@ supplicant_interface_release (NMDeviceWifi *self) if (nm_clear_pointer (&priv->sup_create_handle, nm_supplicant_manager_create_interface_cancel)) nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE); - _requested_scan_set (self, FALSE); + nm_clear_g_source (&priv->scan_kickoff_timeout_id); + nm_clear_g_source_inst (&priv->scan_request_delay_source); + nm_clear_g_cancellable (&priv->scan_request_cancellable); - nm_clear_g_source (&priv->pending_scan_id); + _scan_request_ssids_remove_all (priv, 0, 0); - /* Reset the scan interval to be pretty frequent when disconnected */ - priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN + SCAN_INTERVAL_SEC_STEP; - _LOGD (LOGD_WIFI, "wifi-scan: reset interval to %u seconds", - (unsigned) priv->scan_interval_sec); + priv->scan_periodic_interval_sec = 0; + priv->scan_periodic_next_msec = 0; nm_clear_g_source (&priv->ap_dump_id); @@ -452,7 +670,7 @@ supplicant_interface_release (NMDeviceWifi *self) nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, NULL); } - _notify_scanning (self); + _scan_notify_is_scanning (self); } static void @@ -684,7 +902,7 @@ deactivate_async (NMDevice *device, user_data = nm_utils_user_data_pack (g_object_ref (self), callback, callback_user_data); if (!priv->sup_iface) { - nm_utils_invoke_on_idle (disconnect_cb_on_idle, user_data, cancellable); + nm_utils_invoke_on_idle (cancellable, disconnect_cb_on_idle, user_data); return; } @@ -702,7 +920,6 @@ deactivate (NMDevice *device) NMDeviceWifi *self = NM_DEVICE_WIFI (device); NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); int ifindex = nm_device_get_ifindex (device); - NM80211Mode old_mode = priv->mode; nm_clear_g_source (&priv->periodic_update_id); @@ -732,9 +949,7 @@ deactivate (NMDevice *device) _notify (self, PROP_MODE); } - /* Ensure we trigger a scan after deactivating a Hotspot */ - if (old_mode == NM_802_11_MODE_AP) - request_wireless_scan (self, FALSE, FALSE, NULL); + _scan_notify_allowed (self, NM_TERNARY_TRUE); } static void @@ -1057,10 +1272,7 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags) static gboolean get_autoconnect_allowed (NMDevice *device) { - NMDeviceWifiPrivate *priv; - - priv = NM_DEVICE_WIFI_GET_PRIVATE (NM_DEVICE_WIFI (device)); - return !priv->requested_scan; + return !NM_DEVICE_WIFI_GET_PRIVATE (NM_DEVICE_WIFI (device))->scan_is_scanning; } static gboolean @@ -1153,8 +1365,12 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset) /* expire the temporary MAC address used during scanning */ priv->hw_addr_scan_expire = 0; - if (do_reset) + if (do_reset) { + priv->scan_last_request_started_at_msec = G_MININT64; + priv->scan_periodic_next_msec = 0; + priv->scan_periodic_interval_sec = 0; nm_device_hw_addr_reset (device, "scanning"); + } return; } @@ -1169,13 +1385,16 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset) * We don't bother with to update the MAC address exactly when * it expires, instead on the next scan request, we will generate * a new one.*/ - priv->hw_addr_scan_expire = now + (SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN * 60); + priv->hw_addr_scan_expire = now + SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC; generate_mac_address_mask = nm_config_data_get_device_config (NM_CONFIG_GET_DATA, "wifi.scan-generate-mac-address-mask", device, NULL); + priv->scan_last_request_started_at_msec = G_MININT64; + priv->scan_periodic_next_msec = 0; + priv->scan_periodic_interval_sec = 0; hw_addr_scan = nm_utils_hw_addr_gen_random_eth (nm_device_get_initial_hw_address (device), generate_mac_address_mask); nm_device_hw_addr_set (device, hw_addr_scan, "scanning", TRUE); @@ -1238,6 +1457,7 @@ dbus_request_scan_cb (NMDevice *device, gpointer user_data) { NMDeviceWifi *self = NM_DEVICE_WIFI (device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); gs_unref_ptrarray GPtrArray *ssids = user_data; if (error) { @@ -1245,15 +1465,9 @@ dbus_request_scan_cb (NMDevice *device, return; } - if (check_scanning_prohibited (self, FALSE)) { - g_dbus_method_invocation_return_error_literal (context, - NM_DEVICE_ERROR, - NM_DEVICE_ERROR_NOT_ALLOWED, - "Scanning not allowed at this time"); - return; - } - - request_wireless_scan (self, FALSE, FALSE, ssids); + _scan_request_ssids_track (priv, ssids); + priv->scan_explicit_requested = TRUE; + _scan_kickoff (self); g_dbus_method_invocation_return_value (context, NULL); } @@ -1264,7 +1478,6 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self, { NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); NMDevice *device = NM_DEVICE (self); - gint64 last_scan; gs_unref_ptrarray GPtrArray *ssids = NULL; if (options) { @@ -1291,104 +1504,22 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self, if ( !priv->enabled || !priv->sup_iface - || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED - || nm_device_is_activating (device)) { + || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED) { g_dbus_method_invocation_return_error_literal (invocation, NM_DEVICE_ERROR, NM_DEVICE_ERROR_NOT_ALLOWED, - "Scanning not allowed while unavailable or activating"); + "Scanning not allowed while unavailable"); return; } - if (nm_supplicant_interface_get_scanning (priv->sup_iface)) { - g_dbus_method_invocation_return_error_literal (invocation, - NM_DEVICE_ERROR, - NM_DEVICE_ERROR_NOT_ALLOWED, - "Scanning not allowed while already scanning"); - return; - } - - last_scan = nm_supplicant_interface_get_last_scan (priv->sup_iface); - if ( last_scan > 0 - && nm_utils_get_monotonic_timestamp_msec () < last_scan + (10 * NM_UTILS_MSEC_PER_SEC)) { - /* FIXME: we really should not outright reject a scan request in this case. We should - * ensure to start a scan request soon, possibly with rate limiting. And there is no - * need to tell the caller that we aren't going to scan... - * - * Same above, if we are currently scanning... */ - g_dbus_method_invocation_return_error_literal (invocation, - NM_DEVICE_ERROR, - NM_DEVICE_ERROR_NOT_ALLOWED, - "Scanning not allowed immediately following previous scan"); - return; - } - - g_signal_emit_by_name (device, - NM_DEVICE_AUTH_REQUEST, - invocation, - NULL, - NM_AUTH_PERMISSION_WIFI_SCAN, - TRUE, - dbus_request_scan_cb, - g_steal_pointer (&ssids)); -} - -static gboolean -check_scanning_prohibited (NMDeviceWifi *self, - gboolean periodic) -{ - NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); - - nm_assert (NM_IS_SUPPLICANT_INTERFACE (priv->sup_iface)); - - if (!c_list_is_empty (&priv->scanning_prohibited_lst_head)) - return TRUE; - - /* Don't scan when a an AP or Ad-Hoc connection is active as it will - * disrupt connected clients or peers. - */ - if (NM_IN_SET (priv->mode, NM_802_11_MODE_ADHOC, - NM_802_11_MODE_AP)) - return TRUE; - - switch (nm_device_get_state (NM_DEVICE (self))) { - case NM_DEVICE_STATE_UNKNOWN: - case NM_DEVICE_STATE_UNMANAGED: - case NM_DEVICE_STATE_UNAVAILABLE: - case NM_DEVICE_STATE_PREPARE: - case NM_DEVICE_STATE_CONFIG: - case NM_DEVICE_STATE_NEED_AUTH: - case NM_DEVICE_STATE_IP_CONFIG: - case NM_DEVICE_STATE_IP_CHECK: - case NM_DEVICE_STATE_SECONDARIES: - case NM_DEVICE_STATE_DEACTIVATING: - /* Prohibit scans when unusable or activating */ - return TRUE; - case NM_DEVICE_STATE_DISCONNECTED: - case NM_DEVICE_STATE_FAILED: - /* Can always scan when disconnected */ - return FALSE; - case NM_DEVICE_STATE_ACTIVATED: - /* Prohibit periodic scans when connected; we ask the supplicant to - * background scan for us, unless the connection is locked to a specific - * BSSID. - */ - if (periodic) - return TRUE; - break; - } - - /* Prohibit scans if the supplicant is busy */ - if ( NM_IN_SET (nm_supplicant_interface_get_state (priv->sup_iface), - NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING, - NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED, - NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE, - NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE) - || nm_supplicant_interface_get_scanning (priv->sup_iface)) - return TRUE; - - /* Allow the scan */ - return FALSE; + nm_device_auth_request (device, + invocation, + NULL, + NM_AUTH_PERMISSION_WIFI_SCAN, + TRUE, + NULL, + dbus_request_scan_cb, + g_steal_pointer (&ssids)); } static gboolean @@ -1410,173 +1541,299 @@ hidden_filter_func (NMSettings *settings, } static GPtrArray * -build_hidden_probe_list (NMDeviceWifi *self) +_scan_request_ssids_build_hidden (NMDeviceWifi *self, + gint64 now_msec, + gboolean *out_has_hidden_profiles) { NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); guint max_scan_ssids = nm_supplicant_interface_get_max_scan_ssids (priv->sup_iface); gs_free NMSettingsConnection **connections = NULL; - guint i, len; - GPtrArray *ssids = NULL; - static GBytes *nullssid = NULL; + gs_unref_ptrarray GPtrArray *ssids = NULL; + gs_unref_hashtable GHashTable *unique_ssids = NULL; + guint connections_len; + guint n_hidden; + guint i; + + NM_SET_OUT (out_has_hidden_profiles, FALSE); + + /* collect all pending explicit SSIDs. */ + ssids = _scan_request_ssids_fetch (priv, now_msec); - /* Need at least two: wildcard SSID and one or more hidden SSIDs */ - if (max_scan_ssids < 2) + if (max_scan_ssids == 0) { + /* no space. @ssids will be ignored. */ return NULL; + } + + if (ssids) { + if (ssids->len < max_scan_ssids) { + /* Add wildcard SSID using a static wildcard SSID used for every scan */ + g_ptr_array_insert (ssids, 0, g_bytes_ref (nm_gbytes_get_empty ())); + } + if (ssids->len >= max_scan_ssids) { + /* there is no more space. Use what we have. */ + g_ptr_array_set_size (ssids, max_scan_ssids); + return g_steal_pointer (&ssids); + } + } connections = nm_settings_get_connections_clone (nm_device_get_settings ((NMDevice *) self), - &len, - hidden_filter_func, NULL, - NULL, NULL); + &connections_len, + hidden_filter_func, + NULL, + NULL, + NULL); if (!connections[0]) - return NULL; + return g_steal_pointer (&ssids); - g_qsort_with_data (connections, len, sizeof (NMSettingsConnection *), nm_settings_connection_cmp_timestamp_p_with_data, NULL); + if (!ssids) { + ssids = g_ptr_array_new_full (max_scan_ssids, (GDestroyNotify) g_bytes_unref); + /* Add wildcard SSID using a static wildcard SSID used for every scan */ + g_ptr_array_insert (ssids, 0, g_bytes_ref (nm_gbytes_get_empty ())); + } - ssids = g_ptr_array_new_full (max_scan_ssids, (GDestroyNotify) g_bytes_unref); + unique_ssids = g_hash_table_new (nm_gbytes_hash, nm_gbytes_equal); + for (i = 1; i < ssids->len; i++) { + if (!g_hash_table_add (unique_ssids, ssids->pdata[i])) + nm_assert_not_reached (); + } - /* Add wildcard SSID using a static wildcard SSID used for every scan */ - if (G_UNLIKELY (nullssid == NULL)) - nullssid = g_bytes_new_static ("", 0); - g_ptr_array_add (ssids, g_bytes_ref (nullssid)); + g_qsort_with_data (connections, + connections_len, + sizeof (NMSettingsConnection *), + nm_settings_connection_cmp_timestamp_p_with_data, + NULL); - for (i = 0; connections[i]; i++) { + n_hidden = 0; + for (i = 0; i < connections_len; i++) { NMSettingWireless *s_wifi; GBytes *ssid; - if (i >= max_scan_ssids - 1) + if (ssids->len >= max_scan_ssids) + break; + + if (n_hidden > 4) { + /* we allow at most 4 hidden profiles to be actively scanned. The + * reason is speed and to not disclose too many SSIDs. */ break; + } - s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (nm_settings_connection_get_connection (connections[i])); + s_wifi = nm_connection_get_setting_wireless (nm_settings_connection_get_connection (connections[i])); ssid = nm_setting_wireless_get_ssid (s_wifi); + + if (!g_hash_table_add (unique_ssids, ssid)) + continue; + g_ptr_array_add (ssids, g_bytes_ref (ssid)); + n_hidden++; } - return ssids; + NM_SET_OUT (out_has_hidden_profiles, n_hidden > 0); + return g_steal_pointer (&ssids); } -static void -request_wireless_scan (NMDeviceWifi *self, - gboolean periodic, - gboolean force_if_scanning, - const GPtrArray *ssids) +static gboolean +_scan_request_delay_cb (gpointer user_data) { + NMDeviceWifi *self = user_data; NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); - gboolean request_started = FALSE; - nm_clear_g_source (&priv->pending_scan_id); + nm_clear_g_source_inst (&priv->scan_request_delay_source); - if (!force_if_scanning && priv->requested_scan) { - /* There's already a scan in progress */ - return; - } + _LOGT_scan ("scan request completed (after extra delay)"); - if (!check_scanning_prohibited (self, periodic)) { - gs_unref_ptrarray GPtrArray *hidden_ssids = NULL; - - _LOGD (LOGD_WIFI, "wifi-scan: scanning requested"); - - if (!ssids) { - hidden_ssids = build_hidden_probe_list (self); - if (hidden_ssids) { - if (priv->hidden_probe_scan_warn) { - priv->hidden_probe_scan_warn = FALSE; - _LOGW (LOGD_WIFI, "wifi-scan: active scanning for networks due to profiles with wifi.hidden=yes. This makes you trackable"); - } - ssids = hidden_ssids; - } else - priv->hidden_probe_scan_warn = TRUE; - } - - if (_LOGD_ENABLED (LOGD_WIFI)) { - if (ssids) { - guint i; + _scan_notify_is_scanning (self); + return G_SOURCE_REMOVE; +} - for (i = 0; i < ssids->len; i++) { - gs_free char *ssid_str = NULL; - GBytes *ssid = ssids->pdata[i]; +static void +_scan_supplicant_request_scan_cb (NMSupplicantInterface *supp_iface, + GCancellable *cancellable, + gpointer user_data) +{ + NMDeviceWifi *self; + NMDeviceWifiPrivate *priv; - ssid_str = g_bytes_get_size (ssid) > 0 - ? _nm_utils_ssid_to_string (ssid) - : NULL; - _LOGD (LOGD_WIFI, "wifi-scan: (%u) probe scanning SSID %s", - i, ssid_str ?: "*any*"); - } - } else - _LOGD (LOGD_WIFI, "wifi-scan: no SSIDs to probe scan"); - } + if (g_cancellable_is_cancelled (cancellable)) + return; - _hw_addr_set_scanning (self, FALSE); + self = user_data; + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); - nm_supplicant_interface_request_scan (priv->sup_iface, - ssids ? (GBytes *const*) ssids->pdata : NULL, - ssids ? ssids->len : 0u); - request_started = TRUE; - } else - _LOGD (LOGD_WIFI, "wifi-scan: scanning requested but not allowed at this time"); + _LOGT_scan ("scan request completed (D-Bus request)"); - _requested_scan_set (self, request_started); + /* we just completed a scan request, but possibly the supplicant's state is not yet toggled + * to "scanning". That means, our internal scanning state "priv->scan_is_scanning" would already + * flip to idle, while in a moment the supplicant would toggle the state again. + * + * Artificially keep the scanning state on, for another SCAN_EXTRA_DELAY_MSEC msec. */ + nm_clear_g_source_inst (&priv->scan_request_delay_source); + priv->scan_request_delay_source = nm_g_source_attach (nm_g_timeout_source_new (SCAN_EXTRA_DELAY_MSEC, + G_PRIORITY_DEFAULT, + _scan_request_delay_cb, + self, + NULL), + NULL); - schedule_scan (self, request_started); + g_clear_object (&priv->scan_request_cancellable); + _scan_notify_is_scanning (self); } static gboolean -request_wireless_scan_periodic (gpointer user_data) +_scan_kickoff_timeout_cb (gpointer user_data) { NMDeviceWifi *self = user_data; NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); - priv->pending_scan_id = 0; - request_wireless_scan (self, TRUE, FALSE, NULL); + priv->scan_kickoff_timeout_id = 0; + _scan_kickoff (self); return G_SOURCE_REMOVE; } -/* - * schedule_scan - * - * Schedule a wireless scan. - * - */ static void -schedule_scan (NMDeviceWifi *self, gboolean backoff) +_scan_kickoff (NMDeviceWifi *self) { NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); - gint32 now = nm_utils_get_monotonic_timestamp_sec (); - - /* Cancel the pending scan if it would happen later than (now + the scan_interval_sec) */ - if (priv->pending_scan_id) { - if (now + priv->scan_interval_sec < priv->scheduled_scan_time) - nm_clear_g_source (&priv->pending_scan_id); - } - - if (!priv->pending_scan_id) { - guint factor = 2; - guint next_scan = priv->scan_interval_sec; - - if ( nm_device_is_activating (NM_DEVICE (self)) - || (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED)) - factor = 1; - - priv->pending_scan_id = g_timeout_add_seconds (next_scan, - request_wireless_scan_periodic, - self); - - priv->scheduled_scan_time = now + priv->scan_interval_sec; - if (backoff && (priv->scan_interval_sec < (SCAN_INTERVAL_SEC_MAX / factor))) { - priv->scan_interval_sec += (SCAN_INTERVAL_SEC_STEP / factor); - /* Ensure the scan interval will never be less than 20s... */ - priv->scan_interval_sec = MAX(priv->scan_interval_sec, SCAN_INTERVAL_SEC_MIN + SCAN_INTERVAL_SEC_STEP); - /* ... or more than 120s */ - priv->scan_interval_sec = MIN(priv->scan_interval_sec, SCAN_INTERVAL_SEC_MAX); - } else if (!backoff && (priv->scan_interval_sec == 0)) { - /* Invalid combination; would cause continual rescheduling of - * the scan and hog CPU. Reset to something minimally sane. - */ - priv->scan_interval_sec = 5; + gs_unref_ptrarray GPtrArray *ssids = NULL; + gboolean is_explict = FALSE; + NMDeviceState device_state; + gboolean has_hidden_profiles; + gint64 now_msec; + gint64 ratelimit_duration_msec; + + if (!priv->sup_iface) { + _LOGT_scan ("kickoff: don't scan (has no supplicant interface)"); + return; + } + + if (priv->scan_request_cancellable) { + _LOGT_scan ("kickoff: don't scan (has scan_request_cancellable)"); + /* We are currently waiting for a scan request to complete. Wait longer. */ + return; + } + + now_msec = nm_utils_get_monotonic_timestamp_msec (); + + _scan_request_ssids_remove_all (priv, now_msec, G_MAXUINT); + + device_state = nm_device_get_state (NM_DEVICE (self)); + if ( device_state > NM_DEVICE_STATE_DISCONNECTED + && device_state <= NM_DEVICE_STATE_ACTIVATED) { + /* while we are activated, we rate limit more. */ + ratelimit_duration_msec = 8000; + } else + ratelimit_duration_msec = 1500; + + if (priv->scan_last_request_started_at_msec + ratelimit_duration_msec > now_msec) { + _LOGT_scan ("kickoff: don't scan (rate limited for another %d.%03d sec%s)", + (int) ((priv->scan_last_request_started_at_msec + ratelimit_duration_msec - now_msec) / 1000), + (int) ((priv->scan_last_request_started_at_msec + ratelimit_duration_msec - now_msec) % 1000), + !priv->scan_kickoff_timeout_id ? ", schedule timeout" : ""); + if ( !priv->scan_kickoff_timeout_id + && ( priv->scan_explicit_allowed + || priv->scan_periodic_allowed)) { + priv->scan_kickoff_timeout_id = g_timeout_add (priv->scan_last_request_started_at_msec + ratelimit_duration_msec - now_msec, + _scan_kickoff_timeout_cb, + self); + } + return; + } + + if (priv->scan_explicit_requested) { + if (!priv->scan_explicit_allowed) { + _LOGT_scan ("kickoff: don't scan (explicit scan requested but not allowed)"); + return; + } + priv->scan_explicit_requested = FALSE; + is_explict = TRUE; + } else { + + if (!priv->scan_periodic_allowed) { + _LOGT_scan ("kickoff: don't scan (periodic scan currently not allowed)"); + priv->scan_periodic_next_msec = 0; + priv->scan_periodic_interval_sec = 0; + nm_clear_g_source (&priv->scan_kickoff_timeout_id); + return; + } + + nm_assert (priv->scan_explicit_allowed); + + if (now_msec < priv->scan_periodic_next_msec) { + _LOGT_scan ("kickoff: don't scan (periodic scan waiting for another %d.%03d sec%s)", + (int) ((priv->scan_periodic_next_msec - now_msec) / 1000), + (int) ((priv->scan_periodic_next_msec - now_msec) % 1000), + !priv->scan_kickoff_timeout_id ? ", schedule timeout" : ""); + if (!priv->scan_kickoff_timeout_id) { + priv->scan_kickoff_timeout_id = g_timeout_add_seconds ((priv->scan_periodic_next_msec - now_msec + 999) / 1000, + _scan_kickoff_timeout_cb, + self); + } + return; } - _LOGD (LOGD_WIFI, "wifi-scan: scheduled in %d seconds (interval now %d seconds)", - next_scan, priv->scan_interval_sec); + priv->scan_periodic_interval_sec = NM_CLAMP (((int) priv->scan_periodic_interval_sec) * 3 / 2, + SCAN_INTERVAL_SEC_MIN, + SCAN_INTERVAL_SEC_MAX); + priv->scan_periodic_next_msec = now_msec + 1000 * priv->scan_periodic_interval_sec; } + + ssids = _scan_request_ssids_build_hidden (self, now_msec, &has_hidden_profiles); + if (has_hidden_profiles) { + if (priv->hidden_probe_scan_warn) { + priv->hidden_probe_scan_warn = FALSE; + _LOGW (LOGD_WIFI, "wifi-scan: active scanning for networks due to profiles with wifi.hidden=yes. This makes you trackable"); + } + } else if (!is_explict) + priv->hidden_probe_scan_warn = TRUE; + + if (_LOGD_ENABLED (LOGD_WIFI)) { + gs_free char *ssids_str = NULL; + guint ssids_len = 0; + + if (ssids) { + gs_strfreev char **strv = NULL; + guint i; + + strv = g_new (char *, ssids->len + 1u); + for (i = 0; i < ssids->len; i++) + strv[i] = _nm_utils_ssid_to_string (ssids->pdata[i]); + strv[i] = NULL; + + nm_assert (ssids->len > 0); + nm_assert (ssids->len == NM_PTRARRAY_LEN (strv)); + + ssids_str = g_strjoinv (", ", strv); + ssids_len = ssids->len; + } + _LOGD (LOGD_WIFI, "wifi-scan: start %s scan (%u SSIDs to probe scan%s%s%s)", + is_explict ? "explicit" : "periodic", + ssids_len, + NM_PRINT_FMT_QUOTED (ssids_str, " [", ssids_str, "]", "")); + } + + priv->scan_last_request_started_at_msec = now_msec; + + if (is_explict) + _LOGT_scan ("kickoff: explicit scan starting"); + else { + _LOGT_scan ("kickoff: periodic scan starting (next scan is scheduled in %d.%03d sec)", + (int) ((priv->scan_periodic_next_msec - now_msec) / 1000), + (int) ((priv->scan_periodic_next_msec - now_msec) % 1000)); + } + + _hw_addr_set_scanning (self, FALSE); + + priv->scan_request_cancellable = g_cancellable_new (); + nm_supplicant_interface_request_scan (priv->sup_iface, + ssids ? (GBytes *const*) ssids->pdata : NULL, + ssids ? ssids->len : 0u, + priv->scan_request_cancellable, + _scan_supplicant_request_scan_cb, + self); + + /* It's OK to call _scan_notify_is_scanning() again. They mutually call each other, + * but _scan_kickoff() sets "priv->scan_request_cancellable" which will stop + * them from recursing indefinitely. */ + _scan_notify_is_scanning (self); } /**************************************************************************** @@ -1597,16 +1854,15 @@ ap_list_dump (gpointer user_data) gint64 now_msec = nm_utils_get_monotonic_timestamp_msec (); char str_buf[100]; - _LOGD (LOGD_WIFI_SCAN, "APs: [now:%u.%03u, last:%s, next:%u]", + _LOGD (LOGD_WIFI_SCAN, "APs: [now:%u.%03u, last:%s]", (guint) (now_msec / NM_UTILS_MSEC_PER_SEC), (guint) (now_msec % NM_UTILS_MSEC_PER_SEC), - priv->last_scan_msec > 0 + priv->scan_last_complete_msec > 0 ? nm_sprintf_buf (str_buf, "%u.%03u", - (guint) (priv->last_scan_msec / NM_UTILS_MSEC_PER_SEC), - (guint) (priv->last_scan_msec % NM_UTILS_MSEC_PER_SEC)) - : "-1", - priv->scheduled_scan_time); + (guint) (priv->scan_last_complete_msec / NM_UTILS_MSEC_PER_SEC), + (guint) (priv->scan_last_complete_msec % NM_UTILS_MSEC_PER_SEC)) + : "-1"); c_list_for_each_entry (ap, &priv->aps_lst_head, aps_lst) _ap_dump (self, LOGL_DEBUG, ap, "dump", now_msec); } @@ -2138,6 +2394,7 @@ supplicant_iface_state (NMDeviceWifi *self, NMDevice *device = NM_DEVICE (self); NMDeviceState devstate; gboolean scanning; + gboolean scan_changed; _LOGI (LOGD_DEVICE | LOGD_WIFI, "supplicant interface state: %s -> %s%s", @@ -2158,7 +2415,8 @@ supplicant_iface_state (NMDeviceWifi *self, nm_device_queue_recheck_available (NM_DEVICE (device), NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); - priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN; + priv->scan_periodic_interval_sec = 0; + priv->scan_periodic_next_msec = 0; } /* In these states we know the supplicant is actually talking to something */ @@ -2225,17 +2483,19 @@ supplicant_iface_state (NMDeviceWifi *self, } break; case NM_SUPPLICANT_INTERFACE_STATE_INACTIVE: - /* we would clear _requested_scan_set() and trigger a new scan. + /* we would clear _scan_has_pending_action_set() and trigger a new scan. * However, we don't want to cancel the current pending action, so force * a new scan request. */ - request_wireless_scan (self, FALSE, TRUE, NULL); break; default: break; } out: - _notify_scanning (self); + scan_changed = _scan_notify_allowed (self, NM_TERNARY_FALSE); + scan_changed |= _scan_notify_is_scanning (self); + if (scan_changed) + _scan_kickoff (self); if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) nm_device_remove_pending_action (device, NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE); @@ -2764,6 +3024,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason) ap = ap_fake; } + _scan_notify_allowed (self, NM_TERNARY_DEFAULT); + set_current_ap (self, ap, FALSE); nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req), nm_dbus_object_get_path (NM_DBUS_OBJECT (ap))); @@ -3103,8 +3365,8 @@ activation_success_handler (NMDevice *device) update_seen_bssids_cache (self, priv->current_ap); - /* Reset scan interval to something reasonable */ - priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN + (SCAN_INTERVAL_SEC_STEP * 2); + priv->scan_periodic_interval_sec = 0; + priv->scan_periodic_next_msec = 0; } static void @@ -3164,9 +3426,6 @@ device_state_changed (NMDevice *device, nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), FALSE); break; case NM_DEVICE_STATE_DISCONNECTED: - /* Kick off a scan to get latest results */ - priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN; - request_wireless_scan (self, FALSE, FALSE, NULL); break; default: break; @@ -3174,6 +3433,8 @@ device_state_changed (NMDevice *device, if (clear_aps) remove_all_aps (self); + + _scan_notify_allowed (self, NM_TERNARY_DEFAULT); } static gboolean @@ -3324,8 +3585,8 @@ get_property (GObject *object, guint prop_id, break; case PROP_LAST_SCAN: g_value_set_int64 (value, - priv->last_scan_msec > 0 - ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan_msec, NM_UTILS_NSEC_PER_MSEC) + priv->scan_last_complete_msec > 0 + ? nm_utils_monotonic_timestamp_as_boottime (priv->scan_last_complete_msec, NM_UTILS_NSEC_PER_MSEC) : (gint64) -1); break; default: @@ -3361,8 +3622,10 @@ nm_device_wifi_init (NMDeviceWifi *self) c_list_init (&priv->aps_lst_head); c_list_init (&priv->scanning_prohibited_lst_head); + c_list_init (&priv->scan_request_ssids_lst_head); priv->aps_idx_by_supplicant_path = g_hash_table_new (nm_direct_hash, NULL); + priv->scan_last_request_started_at_msec = G_MININT64; priv->hidden_probe_scan_warn = TRUE; priv->mode = NM_802_11_MODE_INFRA; priv->wowlan_restore = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE; diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c index f89ec86c..3fdc5ac5 100644 --- a/src/devices/wwan/nm-modem-broadband.c +++ b/src/devices/wwan/nm-modem-broadband.c @@ -1254,9 +1254,9 @@ disconnect (NMModem *modem, /* Already cancelled or no simple-iface? We are done. */ if ( !ctx->self->_priv.simple_iface || g_cancellable_is_cancelled (cancellable)) { - nm_utils_invoke_on_idle (disconnect_context_complete_on_idle, - ctx, - cancellable); + nm_utils_invoke_on_idle (cancellable, + disconnect_context_complete_on_idle, + ctx); return; } diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c index 2fc44881..96b08e7c 100644 --- a/src/devices/wwan/nm-modem-ofono.c +++ b/src/devices/wwan/nm-modem-ofono.c @@ -211,9 +211,9 @@ disconnect (NMModem *modem, if ( state != NM_MODEM_STATE_CONNECTED || g_cancellable_is_cancelled (cancellable)) { - nm_utils_invoke_on_idle (disconnect_context_complete_on_idle, - ctx, - cancellable); + nm_utils_invoke_on_idle (cancellable, + disconnect_context_complete_on_idle, + ctx); return; } diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c index 17f9db7f..365e32e3 100644 --- a/src/dhcp/nm-dhcp-helper.c +++ b/src/dhcp/nm-dhcp-helper.c @@ -115,19 +115,42 @@ main (int argc, char *argv[]) gs_unref_variant GVariant *parameters = NULL; gs_unref_variant GVariant *result = NULL; gboolean success = FALSE; - guint try_count = 0; + guint try_count; + gint64 time_start; gint64 time_end; - /* FIXME: g_dbus_connection_new_for_address_sync() tries to connect to the socket in - * non-blocking mode, which can easily fail with EAGAIN, causing the creation of the - * socket to fail with "Could not connect: Resource temporarily unavailable". - * - * We should instead create the GIOStream ourself and block on connecting to - * the socket. */ + /* Connecting to the unix socket can fail with EAGAIN if there are too + * many pending connections and the server can't accept them in time + * before reaching backlog capacity. Ideally the server should increase + * the backlog length, but GLib doesn't provide a way to change it for a + * GDBus server. Retry for up to 5 seconds in case of failure. */ + time_start = g_get_monotonic_time (); + time_end = time_start + (5000 * 1000L); + try_count = 0; + +do_connect: + try_count++; connection = g_dbus_connection_new_for_address_sync ("unix:path=" NMRUNDIR "/private-dhcp", G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, NULL, NULL, &error); if (!connection) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + gint64 time_remaining = time_end - g_get_monotonic_time (); + gint64 interval; + + if (time_remaining > 0) { + _LOGi ("failure to connect: %s (retry %u, waited %lld ms)", + error->message, try_count, + (long long) (time_end - time_remaining - time_start) / 1000); + interval = NM_CLAMP ((gint64) (100L * (1L << NM_MIN (try_count, 31))), + 5000, + 100000); + g_usleep (NM_MIN (interval, time_remaining)); + g_clear_error (&error); + goto do_connect; + } + } + g_dbus_error_strip_remote_error (error); _LOGE ("could not connect to NetworkManager D-Bus socket: %s", error->message); @@ -135,8 +158,8 @@ main (int argc, char *argv[]) } parameters = build_signal_parameters (); - time_end = g_get_monotonic_time () + (200 * 1000L); /* retry for at most 200 milliseconds */ + try_count = 0; do_notify: try_count++; @@ -158,6 +181,7 @@ do_notify: s_err = g_dbus_error_get_remote_error (error); if (NM_IN_STRSET (s_err, "org.freedesktop.DBus.Error.UnknownMethod")) { gint64 remaining_time = time_end - g_get_monotonic_time (); + gint64 interval; /* I am not sure that a race can actually happen, as we register the object * on the server side during GDBusServer:new-connection signal. @@ -166,7 +190,10 @@ do_notify: * do some retry. */ if (remaining_time > 0) { _LOGi ("failure to call notify: %s (retry %u)", error->message, try_count); - g_usleep (NM_MIN (NM_CLAMP ((gint64) (100L * (1L << try_count)), 5000, 25000), remaining_time)); + interval = NM_CLAMP ((gint64) (100L * (1L << NM_MIN (try_count, 31))), + 5000, + 25000); + g_usleep (NM_MIN (interval, remaining_time)); g_clear_error (&error); goto do_notify; } diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c index b40dd6b6..666bad60 100644 --- a/src/dns/nm-dns-manager.c +++ b/src/dns/nm-dns-manager.c @@ -62,6 +62,7 @@ typedef struct { GPtrArray *options; const char *nis_domain; GPtrArray *nis_servers; + NMTernary has_trust_ad; } NMResolvConfData; /*****************************************************************************/ @@ -410,8 +411,11 @@ merge_one_ip_config (NMResolvConfData *rc, const NMIPConfig *ip_config) { int addr_family; - guint num, i; char buf[NM_UTILS_INET_ADDRSTRLEN + 50]; + gboolean has_trust_ad; + guint num_nameservers; + guint num; + guint i; addr_family = nm_ip_config_get_addr_family (ip_config); @@ -419,8 +423,8 @@ merge_one_ip_config (NMResolvConfData *rc, nm_assert (ifindex > 0); nm_assert (ifindex == nm_ip_config_get_ifindex (ip_config)); - num = nm_ip_config_get_num_nameservers (ip_config); - for (i = 0; i < num; i++) { + num_nameservers = nm_ip_config_get_num_nameservers (ip_config); + for (i = 0; i < num_nameservers; i++) { const NMIPAddr *addr; addr = nm_ip_config_get_nameserver (ip_config, i); @@ -446,11 +450,28 @@ merge_one_ip_config (NMResolvConfData *rc, add_dns_domains (rc->searches, ip_config, FALSE, TRUE); + has_trust_ad = FALSE; num = nm_ip_config_get_num_dns_options (ip_config); for (i = 0; i < num; i++) { + const char *option = nm_ip_config_get_dns_option (ip_config, i); + + if (nm_streq (option, NM_SETTING_DNS_OPTION_TRUST_AD)) { + has_trust_ad = TRUE; + continue; + } add_dns_option_item (rc->options, nm_ip_config_get_dns_option (ip_config, i)); } + if (num_nameservers == 0) { + /* If the @ip_config contributes no DNS servers, ignore whether trust-ad is set or unset + * for this @ip_config. */ + } else if (has_trust_ad) { + /* We only set has_trust_ad to TRUE, if all IP configs agree (or don't contribute). + * Once set to FALSE, it doesn't get reset. */ + if (rc->has_trust_ad == NM_TERNARY_DEFAULT) + rc->has_trust_ad = NM_TERNARY_TRUE; + } else + rc->has_trust_ad = NM_TERNARY_FALSE; if (addr_family == AF_INET) { const NMIP4Config *ip4_config = (const NMIP4Config *) ip_config; @@ -1106,11 +1127,12 @@ _collect_resolv_conf_data (NMDnsManager *self, { NMDnsManagerPrivate *priv; NMResolvConfData rc = { - .nameservers = g_ptr_array_new (), - .searches = g_ptr_array_new (), - .options = g_ptr_array_new (), - .nis_domain = NULL, - .nis_servers = g_ptr_array_new (), + .nameservers = g_ptr_array_new (), + .searches = g_ptr_array_new (), + .options = g_ptr_array_new (), + .nis_domain = NULL, + .nis_servers = g_ptr_array_new (), + .has_trust_ad = NM_TERNARY_DEFAULT, }; priv = NM_DNS_MANAGER_GET_PRIVATE (self); @@ -1174,6 +1196,9 @@ _collect_resolv_conf_data (NMDnsManager *self, } } + if (rc.has_trust_ad == NM_TERNARY_TRUE) + g_ptr_array_add (rc.options, g_strdup (NM_SETTING_DNS_OPTION_TRUST_AD)); + *out_searches = _ptrarray_to_strv (rc.searches); *out_options = _ptrarray_to_strv (rc.options); *out_nameservers = _ptrarray_to_strv (rc.nameservers); diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/ndisc/nm-lndp-ndisc.c index b698489c..b10adc91 100644 --- a/src/ndisc/nm-lndp-ndisc.c +++ b/src/ndisc/nm-lndp-ndisc.c @@ -101,8 +101,6 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) int offset; int hop_limit; guint32 val; - guint32 clamp_pltime; - guint32 clamp_vltime; /* Router discovery is subject to the following RFC documents: * @@ -167,22 +165,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) changed |= NM_NDISC_CONFIG_GATEWAYS; } - /* Addresses & Routes - * - * The Preferred Lifetime and Valid Lifetime of PIOs are capped to Router Lifetime - * and NM_NDISC_VLTIME_MULT * Preferred Lifetime, respectively. - * - * The Lifetime of RIOs is capped to the Router Lifetime (there is no point in - * maintaining a route if it employs a dead router). - * - * See draft-gont-6man-slaac-renum - */ - #define NM_NDISC_VLTIME_MULT ((guint32) 48) - clamp_pltime = ndp_msgra_router_lifetime (msgra); - clamp_vltime = (clamp_pltime < G_MAXUINT32 / NM_NDISC_VLTIME_MULT) - ? clamp_pltime * NM_NDISC_VLTIME_MULT - : G_MAXUINT32; - + /* Addresses & Routes */ ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_PREFIX) { guint8 r_plen; struct in6_addr r_network; @@ -203,7 +186,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) .network = r_network, .plen = r_plen, .timestamp = now, - .lifetime = NM_MIN (ndp_msg_opt_prefix_valid_time (msg, offset), clamp_vltime), + .lifetime = ndp_msg_opt_prefix_valid_time (msg, offset), }; if (nm_ndisc_add_route (ndisc, &route)) @@ -216,8 +199,8 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) NMNDiscAddress address = { .address = r_network, .timestamp = now, - .lifetime = NM_MIN (ndp_msg_opt_prefix_valid_time (msg, offset), clamp_vltime), - .preferred = NM_MIN (ndp_msg_opt_prefix_preferred_time (msg, offset), clamp_pltime), + .lifetime = ndp_msg_opt_prefix_valid_time (msg, offset), + .preferred = ndp_msg_opt_prefix_preferred_time (msg, offset), }; if (address.preferred <= address.lifetime) { @@ -231,7 +214,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) .gateway = gateway_addr, .plen = ndp_msg_opt_route_prefix_len (msg, offset), .timestamp = now, - .lifetime = NM_MIN (ndp_msg_opt_route_lifetime (msg, offset), clamp_pltime), + .lifetime = ndp_msg_opt_route_lifetime (msg, offset), .preference = _route_preference_coerce (ndp_msg_opt_route_preference (msg, offset)), }; diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c index bb3a92b0..5e556084 100644 --- a/src/ndisc/nm-ndisc.c +++ b/src/ndisc/nm-ndisc.c @@ -442,15 +442,8 @@ nm_ndisc_add_address (NMNDisc *ndisc, } if (existing) { - /* A Valid Lifetime of 0 eliminates the corresponding address(es). This deviates - * from RFC4862 Section 5.5.3, item e), as recommended in IETF draft draft-gont-6man-slaac-renum. - */ - if (new->lifetime == 0) { - g_array_remove_index (rdata->addresses, i); - return TRUE; - } - if (from_ra) { + const gint32 NM_NDISC_PREFIX_LFT_MIN = 7200; /* seconds, RFC4862 5.5.3.e */ gint64 old_expiry_lifetime, old_expiry_preferred; old_expiry_lifetime = get_expiry (existing); @@ -459,16 +452,25 @@ nm_ndisc_add_address (NMNDisc *ndisc, if (new->lifetime == NM_NDISC_INFINITY) existing->lifetime = NM_NDISC_INFINITY; else { - gint64 new_lifetime; + gint64 new_lifetime, remaining_lifetime; - /* Honor small valid lifetimes, as discussed in - * draft-gont-6man-slaac-renum, to allow for more timelier - * reaction to renumbering events. This deviates from - * RFC4862 Section 5.5.3, item e). - */ + /* see RFC4862 5.5.3.e */ + if (existing->lifetime == NM_NDISC_INFINITY) + remaining_lifetime = G_MAXINT64; + else + remaining_lifetime = ((gint64) existing->timestamp) + ((gint64) existing->lifetime) - ((gint64) now_s); new_lifetime = ((gint64) new->timestamp) + ((gint64) new->lifetime) - ((gint64) now_s); - existing->timestamp = now_s; - existing->lifetime = CLAMP (new_lifetime, (gint64) 0, (gint64) (G_MAXUINT32 - 1)); + + if ( new_lifetime > (gint64) NM_NDISC_PREFIX_LFT_MIN + || new_lifetime > remaining_lifetime) { + existing->timestamp = now_s; + existing->lifetime = CLAMP (new_lifetime, (gint64) 0, (gint64) (G_MAXUINT32 - 1)); + } else if (remaining_lifetime <= (gint64) NM_NDISC_PREFIX_LFT_MIN) { + /* keep the current lifetime. */ + } else { + existing->timestamp = now_s; + existing->lifetime = NM_NDISC_PREFIX_LFT_MIN; + } } if (new->preferred == NM_NDISC_INFINITY) { @@ -485,6 +487,11 @@ nm_ndisc_add_address (NMNDisc *ndisc, || old_expiry_preferred != get_expiry_preferred (existing); } + if (new->lifetime == 0) { + g_array_remove_index (rdata->addresses, i); + return TRUE; + } + if ( get_expiry (existing) == get_expiry (new) && get_expiry_preferred (existing) == get_expiry_preferred (new)) return FALSE; diff --git a/src/ndisc/tests/test-ndisc-fake.c b/src/ndisc/tests/test-ndisc-fake.c index 7a5eea01..91fe9802 100644 --- a/src/ndisc/tests/test-ndisc-fake.c +++ b/src/ndisc/tests/test-ndisc-fake.c @@ -217,8 +217,9 @@ test_everything_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed g_assert_cmpint (rdata->gateways_n, ==, 1); match_gateway (rdata, 0, "fe80::2", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); - g_assert_cmpint (rdata->addresses_n, ==, 1); - match_address (rdata, 0, "2001:db8:a:b::1", data->timestamp1, 10, 10); + g_assert_cmpint (rdata->addresses_n, ==, 2); + match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 0); + match_address (rdata, 1, "2001:db8:a:b::1", data->timestamp1, 10, 10); g_assert_cmpint (rdata->routes_n, ==, 1); match_route (rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1, 10, 10); g_assert_cmpint (rdata->dns_servers_n, ==, 1); diff --git a/src/nm-auth-utils.c b/src/nm-auth-utils.c index ca2870db..b9efff48 100644 --- a/src/nm-auth-utils.c +++ b/src/nm-auth-utils.c @@ -16,22 +16,38 @@ /*****************************************************************************/ +typedef struct { + const char *tag; + gpointer data; + GDestroyNotify destroy; +} ChainData; + struct _NMAuthChain { CList parent_lst; - CList data_lst_head; + ChainData *data_arr; + guint data_len; + guint data_alloc; CList auth_call_lst_head; GDBusMethodInvocation *context; NMAuthSubject *subject; + GCancellable *cancellable; + + /* if set, it also means that the chain is already started and was cancelled. */ + GSource *cancellable_idle_source; + NMAuthChainResultFunc done_func; gpointer user_data; + gulong cancellable_id; + guint num_pending_auth_calls; + bool is_started:1; bool is_destroyed:1; bool is_finishing:1; }; @@ -78,6 +94,82 @@ _ASSERT_call (AuthCall *call) /*****************************************************************************/ static void +_done_and_destroy (NMAuthChain *self) +{ + self->is_finishing = TRUE; + self->done_func (self, self->context, self->user_data); + nm_assert (self->is_finishing); + _auth_chain_destroy (self); +} + +static gboolean +_cancellable_idle_cb (gpointer user_data) +{ + NMAuthChain *self = user_data; + AuthCall *call; + + nm_assert (g_cancellable_is_cancelled (self->cancellable)); + nm_assert (self->cancellable_idle_source); + + c_list_for_each_entry (call, &self->auth_call_lst_head, auth_call_lst) { + if (call->call_id) { + self->num_pending_auth_calls--; + nm_auth_manager_check_authorization_cancel (g_steal_pointer (&call->call_id)); + } + } + + _done_and_destroy (self); + return G_SOURCE_REMOVE; +} + +static void +_cancellable_on_idle (NMAuthChain *self) +{ + if (self->cancellable_idle_source) + return; + + self->cancellable_idle_source = nm_g_idle_source_new (G_PRIORITY_DEFAULT, + _cancellable_idle_cb, + self, + NULL); + g_source_attach (self->cancellable_idle_source, NULL); +} + +GCancellable * +nm_auth_chain_get_cancellable (NMAuthChain *self) +{ + return self->cancellable; +} + +static void +_cancellable_cancelled (GCancellable *cancellable, + NMAuthChain *self) +{ + _cancellable_on_idle (self); +} + +void +nm_auth_chain_set_cancellable (NMAuthChain *self, + GCancellable *cancellable) +{ + g_return_if_fail (self); + g_return_if_fail (G_IS_CANCELLABLE (cancellable)); + + /* after the chain is started, the cancellable can no longer be changed. + * No need to handle the complexity of swapping the cancellable *after* + * requests are already started. */ + g_return_if_fail (!self->is_started); + nm_assert (c_list_is_empty (&self->auth_call_lst_head)); + + /* also no need to allow setting different cancellables. */ + g_return_if_fail (!self->cancellable); + + self->cancellable = g_object_ref (cancellable); +} + +/*****************************************************************************/ + +static void auth_call_free (AuthCall *call) { _ASSERT_call (call); @@ -87,7 +179,7 @@ auth_call_free (AuthCall *call) call->chain->num_pending_auth_calls--; nm_auth_manager_check_authorization_cancel (call->call_id); } - g_slice_free (AuthCall, call); + nm_g_slice_free (call); } static AuthCall * @@ -104,29 +196,16 @@ _find_auth_call (NMAuthChain *self, const char *permission) /*****************************************************************************/ -typedef struct { - CList data_lst; - const char *tag; - gpointer data; - GDestroyNotify destroy; -} ChainData; - -static void -chain_data_free (ChainData *chain_data) -{ - c_list_unlink_stale (&chain_data->data_lst); - if (chain_data->destroy) - chain_data->destroy (chain_data->data); - g_slice_free (ChainData, chain_data); -} - static ChainData * _get_data (NMAuthChain *self, const char *tag) { - ChainData *chain_data; + guint i; - c_list_for_each_entry (chain_data, &self->data_lst_head, data_lst) { - if (nm_streq (chain_data->tag, tag)) + for (i = 0; i < self->data_len; i++) { + ChainData *chain_data = &self->data_arr[i]; + + if ( chain_data->tag + && nm_streq (chain_data->tag, tag)) return chain_data; } return NULL; @@ -159,7 +238,6 @@ gpointer nm_auth_chain_steal_data (NMAuthChain *self, const char *tag) { ChainData *chain_data; - gpointer value; g_return_val_if_fail (self, NULL); g_return_val_if_fail (tag, NULL); @@ -168,12 +246,13 @@ nm_auth_chain_steal_data (NMAuthChain *self, const char *tag) if (!chain_data) return NULL; - value = chain_data->data; - - /* Make sure the destroy handler isn't called when freeing */ + /* Make sure the destroy handler isn't called when freeing. + * + * We don't bother to really remove the element from the array. + * Just mark the entry as unused by clearing the tag. */ chain_data->destroy = NULL; - chain_data_free (chain_data); - return value; + chain_data->tag = NULL; + return chain_data->data; } /** @@ -184,7 +263,7 @@ nm_auth_chain_steal_data (NMAuthChain *self, const char *tag) * and nothing is attached. * @data_destroy: (allow-none): the destroy function for the data pointer. * - * @tag string is not cloned and must outlife @self. That is why + * @tag string is not cloned and must outlive @self. That is why * the function is "unsafe". Use nm_auth_chain_set_data() with a C literal * instead. * @@ -201,12 +280,8 @@ nm_auth_chain_set_data_unsafe (NMAuthChain *self, g_return_if_fail (self); g_return_if_fail (tag); - /* we should not track a large number of elements via a linked list. If this becomes - * necessary, revert the code to use GHashTable again. */ - nm_assert (c_list_length (&self->data_lst_head) < 25); - - /* The tag must not yet exist. Otherwise we'd have to first search the linked - * list for an existing entry. */ + /* The tag must not yet exist. Otherwise we'd have to first search the + * list for an existing entry. That usage pattern is not supported. */ nm_assert (!_get_data (self, tag)); if (!data) { @@ -218,17 +293,20 @@ nm_auth_chain_set_data_unsafe (NMAuthChain *self, return; } - chain_data = g_slice_new (ChainData); + if (self->data_len + 1 > self->data_alloc) { + if (self->data_alloc == 0) + self->data_alloc = 8; + else + self->data_alloc *= 2; + self->data_arr = g_realloc (self->data_arr, sizeof (self->data_arr[0]) * self->data_alloc); + } + + chain_data = &self->data_arr[self->data_len++]; *chain_data = (ChainData) { .tag = tag, .data = data, .destroy = data_destroy, }; - - /* we assert that no duplicate tags are added. But still, add the new - * element to the front, so that it would shadow the duplicate element - * in the list. */ - c_list_link_front (&self->data_lst_head, &chain_data->data_lst); } /*****************************************************************************/ @@ -255,6 +333,12 @@ nm_auth_chain_get_result (NMAuthChain *self, const char *permission) nm_assert (!auth_call->call_id); + if (self->cancellable_idle_source) { + /* already cancelled. We always return unknown (even if we happen to + * have already received the response. */ + return NM_AUTH_CALL_RESULT_UNKNOWN; + } + return auth_call->result; } @@ -314,10 +398,7 @@ pk_call_cb (NMAuthManager *auth_manager, if (call->chain->num_pending_auth_calls == 0) { /* we are on an idle-handler or a clean call-stack (non-reentrant) so it's safe * to invoke the callback right away. */ - self->is_finishing = TRUE; - self->done_func (self, self->context, self->user_data); - nm_assert (self->is_finishing); - _auth_chain_destroy (self); + _done_and_destroy (self); } } @@ -350,23 +431,36 @@ nm_auth_chain_add_call_unsafe (NMAuthChain *self, g_return_if_fail (!self->is_finishing); g_return_if_fail (!self->is_destroyed); g_return_if_fail (permission && *permission); - nm_assert ( nm_auth_subject_get_subject_type (self->subject) - == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS - || nm_auth_subject_get_subject_type (self->subject) - == NM_AUTH_SUBJECT_TYPE_INTERNAL); + nm_assert (NM_IN_SET (nm_auth_subject_get_subject_type (self->subject), NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, + NM_AUTH_SUBJECT_TYPE_INTERNAL)); /* duplicate permissions are not supported, also because nm_auth_chain_get_result() * can only return one-permission. */ nm_assert (!_find_auth_call (self, permission)); - call = g_slice_new (AuthCall); + if (!self->is_started) { + self->is_started = TRUE; + nm_assert (!self->cancellable_id); + if (self->cancellable) { + if (g_cancellable_is_cancelled (self->cancellable)) { + /* the operation is already cancelled. Schedule the callback on idle. */ + _cancellable_on_idle (self); + } else { + self->cancellable_id = g_signal_connect (self->cancellable, + "cancelled", + G_CALLBACK (_cancellable_cancelled), + self); + } + } + } + call = g_slice_new (AuthCall); *call = (AuthCall) { .chain = self, .call_id = NULL, .result = NM_AUTH_CALL_RESULT_UNKNOWN, - /* we don't clone the permission string. It's the callers responsiblity. */ + /* we don't clone the permission string. It's the callers responsibility. */ .permission = permission, }; @@ -375,21 +469,27 @@ nm_auth_chain_add_call_unsafe (NMAuthChain *self, * call. */ c_list_link_front (&self->auth_call_lst_head, &call->auth_call_lst); - call->call_id = nm_auth_manager_check_authorization (nm_auth_manager_get (), - self->subject, - permission, - allow_interaction, - pk_call_cb, - call); - - self->num_pending_auth_calls++; + if (self->cancellable_idle_source) { + /* already cancelled. No need to actually start the request. */ + nm_assert (call->result == NM_AUTH_CALL_RESULT_UNKNOWN); + } else { + call->call_id = nm_auth_manager_check_authorization (nm_auth_manager_get (), + self->subject, + permission, + allow_interaction, + pk_call_cb, + call); + + self->num_pending_auth_calls++; + } _ASSERT_call (call); /* we track auth-calls in a linked list. If we end up requesting too many permissions this * becomes inefficient. If that ever happens, consider a more efficient data structure for * a large number of requests. */ - nm_assert (self->num_pending_auth_calls < 25); + nm_assert (c_list_length (&self->auth_call_lst_head) < 25); + G_STATIC_ASSERT_EXPR (NM_CLIENT_PERMISSION_LAST < 25); } /*****************************************************************************/ @@ -427,10 +527,8 @@ nm_auth_chain_new_subject (NMAuthSubject *subject, NMAuthChain *self; g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL); - nm_assert ( nm_auth_subject_get_subject_type (subject) - == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS - || nm_auth_subject_get_subject_type (subject) - == NM_AUTH_SUBJECT_TYPE_INTERNAL); + nm_assert (NM_IN_SET (nm_auth_subject_get_subject_type (subject), NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, + NM_AUTH_SUBJECT_TYPE_INTERNAL)); nm_assert (done_func); self = g_slice_new (NMAuthChain); @@ -440,7 +538,6 @@ nm_auth_chain_new_subject (NMAuthSubject *subject, .context = nm_g_object_ref (context), .subject = g_object_ref (subject), .parent_lst = C_LIST_INIT (self->parent_lst), - .data_lst_head = C_LIST_INIT (self->data_lst_head), .auth_call_lst_head = C_LIST_INIT (self->auth_call_lst_head), }; return self; @@ -482,24 +579,33 @@ static void _auth_chain_destroy (NMAuthChain *self) { AuthCall *call; - ChainData *chain_data; c_list_unlink (&self->parent_lst); nm_clear_g_object (&self->subject); nm_clear_g_object (&self->context); - /* we must first destry all AuthCall instances before ChainData. The reason is + nm_clear_g_signal_handler (self->cancellable, &self->cancellable_id); + nm_clear_g_source_inst (&self->cancellable_idle_source); + + /* we must first destroy all AuthCall instances before ChainData. The reason is * that AuthData.permission is not cloned and the lifetime of the string must * be ensured by the caller. A sensible thing to do for the caller is attach the * permission string via nm_auth_chain_set_data(). Hence, first free the AuthCall. */ while ((call = c_list_first_entry (&self->auth_call_lst_head, AuthCall, auth_call_lst))) auth_call_free (call); - while ((chain_data = c_list_first_entry (&self->data_lst_head, ChainData, data_lst))) - chain_data_free (chain_data); + while (self->data_len > 0) { + ChainData *chain_data = &self->data_arr[--self->data_len]; + + if (chain_data->destroy) + chain_data->destroy (chain_data->data); + } + g_free (self->data_arr); + + nm_g_object_unref (self->cancellable); - g_slice_free (NMAuthChain, self); + nm_g_slice_free (self); } /****************************************************************************** @@ -517,10 +623,8 @@ nm_auth_is_subject_in_acl (NMConnection *connection, g_return_val_if_fail (connection, FALSE); g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), FALSE); - nm_assert ( nm_auth_subject_get_subject_type (subject) - == NM_AUTH_SUBJECT_TYPE_INTERNAL - || nm_auth_subject_get_subject_type (subject) - == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS); + nm_assert (NM_IN_SET (nm_auth_subject_get_subject_type (subject), NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, + NM_AUTH_SUBJECT_TYPE_INTERNAL)); if (nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL) return TRUE; diff --git a/src/nm-auth-utils.h b/src/nm-auth-utils.h index 978d607a..a6c6d659 100644 --- a/src/nm-auth-utils.h +++ b/src/nm-auth-utils.h @@ -27,6 +27,10 @@ NMAuthChain *nm_auth_chain_new_subject (NMAuthSubject *subject, NMAuthChainResultFunc done_func, gpointer user_data); +GCancellable *nm_auth_chain_get_cancellable (NMAuthChain *self); +void nm_auth_chain_set_cancellable (NMAuthChain *self, + GCancellable *cancellable); + gpointer nm_auth_chain_get_data (NMAuthChain *chain, const char *tag); gpointer nm_auth_chain_steal_data (NMAuthChain *chain, const char *tag); diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c index ae637aee..0feb7b2c 100644 --- a/src/nm-ip4-config.c +++ b/src/nm-ip4-config.c @@ -303,6 +303,7 @@ typedef struct { NMIPConfigDedupMultiIdxType idx_ip4_routes_; NMDedupMultiIdxType idx_ip4_routes; }; + NMIPConfigFlags config_flags; } NMIP4ConfigPrivate; struct _NMIP4Config { @@ -1202,7 +1203,8 @@ nm_ip4_config_merge (NMIP4Config *dst, nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, src, &r_src) { if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r_src)) { - if (NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES)) + if ( NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES) + && !NM_FLAGS_HAS (src_priv->config_flags, NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES)) continue; if (default_route_metric_penalty) { NMPlatformIP4Route r = *r_src; @@ -2659,6 +2661,28 @@ nm_ip4_config_llmnr_set (NMIP4Config *self, /*****************************************************************************/ +NMIPConfigFlags +nm_ip4_config_get_config_flags (const NMIP4Config *self) +{ + return NM_IP4_CONFIG_GET_PRIVATE (self)->config_flags; +} + +void +nm_ip4_config_set_config_flags (NMIP4Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self); + + if (mask == 0) { + /* for convenience, accept 0 mask to set any flags. */ + mask = flags; + } + + nm_assert (!NM_FLAGS_ANY (flags, ~mask)); + priv->config_flags = (flags & mask) | (priv->config_flags & ~mask); +} + +/*****************************************************************************/ + void nm_ip4_config_set_dns_priority (NMIP4Config *self, int priority) { diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h index 01a42e54..d4694d93 100644 --- a/src/nm-ip4-config.h +++ b/src/nm-ip4-config.h @@ -15,6 +15,14 @@ /*****************************************************************************/ +typedef enum _NMIPConfigFlags { + NM_IP_CONFIG_FLAG_NONE = 0, + + /* if set, then the merge flag NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES gets + * ignored during merge. */ + NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES = (1ull << 0), +} NMIPConfigFlags; + typedef struct { NMDedupMultiIdxType parent; NMPObjectType obj_type; @@ -196,6 +204,9 @@ NMSettingConnectionLlmnr nm_ip4_config_llmnr_get (const NMIP4Config *self); void nm_ip4_config_llmnr_set (NMIP4Config *self, NMSettingConnectionLlmnr llmnr); +void nm_ip4_config_set_config_flags (NMIP4Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask); +NMIPConfigFlags nm_ip4_config_get_config_flags (const NMIP4Config *self); + const NMDedupMultiHeadEntry *nm_ip4_config_lookup_addresses (const NMIP4Config *self); void nm_ip4_config_reset_addresses (NMIP4Config *self); void nm_ip4_config_add_address (NMIP4Config *self, const NMPlatformIP4Address *address); @@ -513,6 +524,18 @@ nm_ip_config_best_default_route_get (const NMIPConfig *self) _NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_best_default_route_get, nm_ip6_config_best_default_route_get); } +static inline NMIPConfigFlags +nm_ip_config_get_config_flags (const NMIPConfig *self) +{ + _NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_config_flags, nm_ip6_config_get_config_flags); +} + +static inline void +nm_ip_config_set_config_flags (NMIPConfig *self, NMIPConfigFlags flags, NMIPConfigFlags mask) +{ + _NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_set_config_flags, nm_ip6_config_set_config_flags, flags, mask); +} + #define _NM_IP_CONFIG_DISPATCH_SET_OP(_return, dst, src, v4_func, v6_func, ...) \ G_STMT_START { \ gpointer _dst = (dst); \ diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c index 5acb3932..b60e8057 100644 --- a/src/nm-ip6-config.c +++ b/src/nm-ip6-config.c @@ -63,6 +63,7 @@ typedef struct { NMIPConfigDedupMultiIdxType idx_ip6_routes_; NMDedupMultiIdxType idx_ip6_routes; }; + NMIPConfigFlags config_flags; bool ipv6_disabled; } NMIP6ConfigPrivate; @@ -875,7 +876,8 @@ nm_ip6_config_merge (NMIP6Config *dst, nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, src, &r_src) { if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r_src)) { - if (NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES)) + if ( NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES) + && !NM_FLAGS_HAS (src_priv->config_flags, NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES)) continue; if (default_route_metric_penalty) { NMPlatformIP6Route r = *r_src; @@ -2296,6 +2298,28 @@ nm_ip6_config_get_dns_option (const NMIP6Config *self, guint i) /*****************************************************************************/ +NMIPConfigFlags +nm_ip6_config_get_config_flags (const NMIP6Config *self) +{ + return NM_IP6_CONFIG_GET_PRIVATE (self)->config_flags; +} + +void +nm_ip6_config_set_config_flags (NMIP6Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask) +{ + NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self); + + if (mask == 0) { + /* for convenience, accept 0 mask to set any flags. */ + mask = flags; + } + + nm_assert (!NM_FLAGS_ANY (flags, ~mask)); + priv->config_flags = (flags & mask) | (priv->config_flags & ~mask); +} + +/*****************************************************************************/ + void nm_ip6_config_set_dns_priority (NMIP6Config *self, int priority) { diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h index a9fa8f14..36e8518a 100644 --- a/src/nm-ip6-config.h +++ b/src/nm-ip6-config.h @@ -128,6 +128,11 @@ gboolean nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboole const NMPObject *nm_ip6_config_best_default_route_get (const NMIP6Config *self); const NMPObject *_nm_ip6_config_best_default_route_find (const NMIP6Config *self); +enum _NMIPConfigFlags; + +void nm_ip6_config_set_config_flags (NMIP6Config *self, enum _NMIPConfigFlags flags, enum _NMIPConfigFlags mask); +enum _NMIPConfigFlags nm_ip6_config_get_config_flags (const NMIP6Config *self); + const NMDedupMultiHeadEntry *nm_ip6_config_lookup_addresses (const NMIP6Config *self); void nm_ip6_config_reset_addresses (NMIP6Config *self); void nm_ip6_config_add_address (NMIP6Config *self, const NMPlatformIP6Address *address); diff --git a/src/nm-manager.c b/src/nm-manager.c index 1f3ba1f6..bbe50125 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -2375,8 +2375,9 @@ device_auth_done_cb (NMAuthChain *chain, gs_free_error GError *error = NULL; NMAuthCallResult result; NMDevice *device; + GCancellable *cancellable; const char *permission; - NMDeviceAuthRequestFunc callback; + NMManagerDeviceAuthRequestFunc callback; NMAuthSubject *subject; nm_assert (G_IS_DBUS_METHOD_INVOCATION (context)); @@ -2390,18 +2391,26 @@ device_auth_done_cb (NMAuthChain *chain, device = nm_auth_chain_get_data (chain, "device"); nm_assert (NM_IS_DEVICE (device)); + cancellable = nm_auth_chain_get_cancellable (chain); + nm_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + result = nm_auth_chain_get_result (chain, permission); subject = nm_auth_chain_get_subject (chain); - if (result != NM_AUTH_CALL_RESULT_YES) { - _LOGD (LOGD_CORE, "%s request failed: not authorized", permission); - error = g_error_new (NM_MANAGER_ERROR, - NM_MANAGER_ERROR_PERMISSION_DENIED, - "%s request failed: not authorized", - permission); - } + if ( cancellable + && g_cancellable_set_error_if_cancelled (cancellable, &error)) { + /* pass. */ + } else { + if (result != NM_AUTH_CALL_RESULT_YES) { + _LOGD (LOGD_CORE, "%s request failed: not authorized", permission); + error = g_error_new (NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "%s request failed: not authorized", + permission); + } - nm_assert (error || (result == NM_AUTH_CALL_RESULT_YES)); + nm_assert (error || (result == NM_AUTH_CALL_RESULT_YES)); + } callback (device, context, @@ -2411,28 +2420,53 @@ device_auth_done_cb (NMAuthChain *chain, } static void -device_auth_request_cb (NMDevice *device, - GDBusMethodInvocation *context, - NMConnection *connection, - const char *permission, - gboolean allow_interaction, - NMDeviceAuthRequestFunc callback, - gpointer user_data, - NMManager *self) +_device_auth_done_fail_on_idle (gpointer user_data, GCancellable *cancellable) +{ + gs_unref_object NMManager *self = NULL; + gs_unref_object NMDevice *device = NULL; + gs_unref_object GDBusMethodInvocation *context = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + gs_free_error GError *error_original = NULL; + gs_free_error GError *error_cancelled = NULL; + NMManagerDeviceAuthRequestFunc callback; + gpointer callback_user_data; + + nm_utils_user_data_unpack (&self, &device, &context, &subject, &error_original, &callback, &callback_user_data); + + g_cancellable_set_error_if_cancelled (cancellable, &error_cancelled); + + callback (device, + context, + subject, + error_cancelled ?: error_original, + callback_user_data); +} + +void +nm_manager_device_auth_request (NMManager *self, + NMDevice *device, + GDBusMethodInvocation *context, + NMConnection *connection, + const char *permission, + gboolean allow_interaction, + GCancellable *cancellable, + NMManagerDeviceAuthRequestFunc callback, + gpointer user_data) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); - GError *error = NULL; - NMAuthSubject *subject = NULL; + gs_free_error GError *error = NULL; + gs_unref_object NMAuthSubject *subject = NULL; NMAuthChain *chain; char *permission_dup; /* Validate the caller */ subject = nm_dbus_manager_new_auth_subject_from_context (context); if (!subject) { - error = g_error_new_literal (NM_MANAGER_ERROR, - NM_MANAGER_ERROR_PERMISSION_DENIED, - NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); - goto done; + g_set_error_literal (&error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + goto fail_on_idle; } /* Ensure the subject has permissions for this connection */ @@ -2442,17 +2476,21 @@ device_auth_request_cb (NMDevice *device, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, &error)) - goto done; + goto fail_on_idle; /* Validate the request */ chain = nm_auth_chain_new_subject (subject, context, device_auth_done_cb, self); if (!chain) { - error = g_error_new_literal (NM_MANAGER_ERROR, - NM_MANAGER_ERROR_PERMISSION_DENIED, - NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); - goto done; + g_set_error (&error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + goto fail_on_idle; } + if (cancellable) + nm_auth_chain_set_cancellable (chain, cancellable); + permission_dup = g_strdup (permission); c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); @@ -2461,13 +2499,18 @@ device_auth_request_cb (NMDevice *device, nm_auth_chain_set_data (chain, "user-data", user_data, NULL); nm_auth_chain_set_data (chain, "perm", permission_dup /* transfer ownership */, g_free); nm_auth_chain_add_call_unsafe (chain, permission_dup, allow_interaction); + return; -done: - if (error) - callback (device, context, subject, error, user_data); - - g_clear_object (&subject); - g_clear_error (&error); +fail_on_idle: + nm_utils_invoke_on_idle (cancellable, + _device_auth_done_fail_on_idle, + nm_utils_user_data_pack (g_object_ref (self), + g_object_ref (device), + g_object_ref (context), + g_steal_pointer (&subject), + g_steal_pointer (&error), + callback, + user_data)); } static gboolean @@ -3130,10 +3173,6 @@ add_device (NMManager *self, NMDevice *device, GError **error) G_CALLBACK (manager_device_state_changed), self); - g_signal_connect (device, NM_DEVICE_AUTH_REQUEST, - G_CALLBACK (device_auth_request_cb), - self); - g_signal_connect (device, NM_DEVICE_REMOVED, G_CALLBACK (device_removed_cb), self); diff --git a/src/nm-manager.h b/src/nm-manager.h index 5873abd2..ab08eaa8 100644 --- a/src/nm-manager.h +++ b/src/nm-manager.h @@ -193,4 +193,16 @@ NMMetered nm_manager_get_metered (NMManager *self); void nm_manager_notify_device_availibility_maybe_changed (NMManager *self); +/*****************************************************************************/ + +void nm_manager_device_auth_request (NMManager *self, + NMDevice *device, + GDBusMethodInvocation *context, + NMConnection *connection, + const char *permission, + gboolean allow_interaction, + GCancellable *cancellable, + NMManagerDeviceAuthRequestFunc callback, + gpointer user_data); + #endif /* __NETWORKMANAGER_MANAGER_H__ */ diff --git a/src/nm-types.h b/src/nm-types.h index 4db9a19b..d2c999fa 100644 --- a/src/nm-types.h +++ b/src/nm-types.h @@ -40,6 +40,12 @@ typedef struct _NMSleepMonitor NMSleepMonitor; typedef struct _NMLldpListener NMLldpListener; typedef struct _NMConfigDeviceStateData NMConfigDeviceStateData; +typedef void (*NMManagerDeviceAuthRequestFunc) (NMDevice *device, + GDBusMethodInvocation *context, + NMAuthSubject *subject, + GError *error, + gpointer user_data); + struct _NMDedupMultiIndex; typedef struct _NMRefString NMRefString; @@ -233,7 +239,9 @@ typedef enum { * NMIPConfigMergeFlags: * @NM_IP_CONFIG_MERGE_DEFAULT: no flags set * @NM_IP_CONFIG_MERGE_NO_ROUTES: don't merge routes - * @NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES: don't merge default routes + * @NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES: don't merge default routes. + * Note that if the source IP config has NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES + * set, this flag gets ignored during merge. * @NM_IP_CONFIG_MERGE_NO_DNS: don't merge DNS information * @NM_IP_CONFIG_MERGE_EXTERNAL: mark new addresses as external */ diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index f7249bb7..014cca71 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -5131,9 +5131,9 @@ sysctl_set_async (NMPlatform *platform, callback, data, error); - nm_utils_invoke_on_idle (sysctl_set_async_return_idle, - packed, - cancellable); + nm_utils_invoke_on_idle (cancellable, + sysctl_set_async_return_idle, + packed); return; } } else @@ -7390,7 +7390,7 @@ out_idle: g_steal_pointer (&error), callback, data); - nm_utils_invoke_on_idle (sriov_idle_cb, packed, cancellable); + nm_utils_invoke_on_idle (cancellable, sriov_idle_cb, packed); } } diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c index 541211cd..a6aa5e22 100644 --- a/src/platform/nmp-object.c +++ b/src/platform/nmp-object.c @@ -1405,15 +1405,15 @@ nmp_object_id_cmp (const NMPObject *obj1, const NMPObject *obj2) nm_assert (klass2); NM_CMP_DIRECT (klass->obj_type, klass2->obj_type); /* resort to pointer comparison */ - if (klass < klass2) - return -1; - return 1; + NM_CMP_DIRECT_PTR (klass, klass2); + return 0; } if (!klass->cmd_plobj_id_cmp) { /* the klass doesn't implement ID cmp(). That means, different objects * never compare equal, but the cmp() according to their pointer value. */ - return (obj1 < obj2) ? -1 : 1; + NM_CMP_DIRECT_PTR (obj1, obj2); + return 0; } return klass->cmd_plobj_id_cmp (&obj1->object, &obj2->object); diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c index 2661914b..ea4a2c5a 100644 --- a/src/platform/tests/test-link.c +++ b/src/platform/tests/test-link.c @@ -3050,18 +3050,29 @@ test_sysctl_netns_switch (void) nmtstp_link_delete (PL, FALSE, ifindex, NULL, TRUE); } -static void -sysctl_set_async_cb_assert_success (GError *error, gpointer data) -{ - g_assert_no_error (error); - g_main_loop_quit (data); -} +typedef struct { + GMainLoop *loop; + const char *path; + gboolean expected_success; + gint32 expected_value; +} SetAsyncData; static void -sysctl_set_async_cb_assert_failure (GError *error, gpointer data) +sysctl_set_async_cb (GError *error, gpointer user_data) { - g_assert (error); - g_main_loop_quit (data); + SetAsyncData *data = user_data; + + if (data->expected_success) { + g_assert_no_error (error); + g_assert_cmpint (nm_platform_sysctl_get_int32 (NM_PLATFORM_GET, + NMP_SYSCTL_PATHID_ABSOLUTE (data->path), + -1), + ==, + data->expected_value); + } else + g_assert (error); + + g_main_loop_quit (data->loop); } static void @@ -3070,43 +3081,53 @@ test_sysctl_set_async (void) NMPlatform *const PL = NM_PLATFORM_GET; const char *const IFNAME = "nm-dummy-0"; const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/rp_filter"; - gs_free GMainLoop *loop = NULL; + GMainLoop *loop; gs_unref_object GCancellable *cancellable = NULL; + gboolean proc_writable; + SetAsyncData data; int ifindex; ifindex = nmtstp_link_dummy_add (PL, -1, IFNAME)->ifindex; loop = g_main_loop_new (NULL, FALSE); cancellable = g_cancellable_new (); + proc_writable = access (PATH, W_OK) == 0; + + data = (SetAsyncData) { + .loop = loop, + .path = PATH, + .expected_success = proc_writable, + .expected_value = 2, + }; nm_platform_sysctl_set_async (PL, NMP_SYSCTL_PATHID_ABSOLUTE (PATH), (const char *[]) { "2", NULL}, - sysctl_set_async_cb_assert_success, - loop, + sysctl_set_async_cb, + &data, cancellable); if (!nmtst_main_loop_run (loop, 1000)) g_assert_not_reached (); - g_assert_cmpint (nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_ABSOLUTE (PATH), -1), - ==, - 2); + data = (SetAsyncData) { + .loop = loop, + .path = PATH, + .expected_success = proc_writable, + .expected_value = 1, + }; nm_platform_sysctl_set_async (PL, NMP_SYSCTL_PATHID_ABSOLUTE (PATH), (const char *[]) { "2", "0", "1", "0", "1", NULL}, - sysctl_set_async_cb_assert_success, - loop, + sysctl_set_async_cb, + &data, cancellable); if (!nmtst_main_loop_run (loop, 2000)) g_assert_not_reached (); - g_assert_cmpint (nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_ABSOLUTE (PATH), -1), - ==, - 1); - nmtstp_link_delete (NULL, -1, ifindex, IFNAME, TRUE); + g_main_loop_unref (loop); } static void @@ -3115,25 +3136,33 @@ test_sysctl_set_async_fail (void) NMPlatform *const PL = NM_PLATFORM_GET; const char *const IFNAME = "nm-dummy-0"; const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/does-not-exist"; - gs_free GMainLoop *loop = NULL; + GMainLoop *loop; gs_unref_object GCancellable *cancellable = NULL; + SetAsyncData data; int ifindex; ifindex = nmtstp_link_dummy_add (PL, -1, IFNAME)->ifindex; loop = g_main_loop_new (NULL, FALSE); cancellable = g_cancellable_new (); + data = (SetAsyncData) { + .loop = loop, + .path = PATH, + .expected_success = FALSE, + }; + nm_platform_sysctl_set_async (PL, NMP_SYSCTL_PATHID_ABSOLUTE (PATH), (const char *[]) { "2", NULL}, - sysctl_set_async_cb_assert_failure, - loop, + sysctl_set_async_cb, + &data, cancellable); if (!nmtst_main_loop_run (loop, 1000)) g_assert_not_reached (); nmtstp_link_delete (NULL, -1, ifindex, IFNAME, TRUE); + g_main_loop_unref (loop); } /*****************************************************************************/ diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c index 8e153412..7adafb58 100644 --- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c +++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c @@ -431,8 +431,8 @@ make_connection_setting (const char *file, (int) svGetValueInt64 (ifcfg, "AUTOCONNECT_RETRIES", 10, -1, G_MAXINT32, -1), NM_SETTING_CONNECTION_MULTI_CONNECT, - (gint) svGetValueInt64 (ifcfg, "MULTI_CONNECT", 10, - G_MININT32, G_MAXINT32, NM_CONNECTION_MULTI_CONNECT_DEFAULT), + (int) svGetValueInt64 (ifcfg, "MULTI_CONNECT", 10, + G_MININT32, G_MAXINT32, NM_CONNECTION_MULTI_CONNECT_DEFAULT), NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES, svGetValueBoolean (ifcfg, "AUTOCONNECT_SLAVES", NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT), NM_SETTING_CONNECTION_LLDP, lldp, diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c index 28f57f50..d3243617 100644 --- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c +++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c @@ -910,6 +910,7 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = { _KEY_TYPE ("PEERROUTES", NMS_IFCFG_KEY_TYPE_IS_PLAIN ), _KEY_TYPE ("PHYSDEV", NMS_IFCFG_KEY_TYPE_IS_PLAIN ), _KEY_TYPE ("PKEY", NMS_IFCFG_KEY_TYPE_IS_PLAIN ), + _KEY_TYPE ("PKEY_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN ), _KEY_TYPE ("PMF", NMS_IFCFG_KEY_TYPE_IS_PLAIN ), _KEY_TYPE ("PORTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN ), _KEY_TYPE ("POWERSAVE", NMS_IFCFG_KEY_TYPE_IS_PLAIN ), diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h index 8941fc19..056d03fa 100644 --- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h +++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h @@ -33,7 +33,7 @@ typedef struct { NMSIfcfgKeyTypeFlags key_flags; } NMSIfcfgKeyTypeInfo; -extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[231]; +extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[232]; const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info (const char *key, gssize *out_idx); diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c index f2a1dd78..cd63661d 100644 --- a/src/settings/plugins/ifcfg-rh/shvar.c +++ b/src/settings/plugins/ifcfg-rh/shvar.c @@ -1492,6 +1492,13 @@ svWriteFile (shvarFile *s, int mode, GError **error) return FALSE; } f = fdopen (tmpfd, "w"); + if (!f) { + errsv = errno; + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv), + "Internal error writing file '%s': %s", + s->fileName, nm_strerror_native (errsv)); + return FALSE; + } fseek (f, 0, SEEK_SET); c_list_for_each (current, &s->lst_head) { const shvarLine *line = c_list_entry (current, shvarLine, lst); diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c index cc3d109e..1c7b9a42 100644 --- a/src/supplicant/nm-supplicant-interface.c +++ b/src/supplicant/nm-supplicant-interface.c @@ -154,6 +154,9 @@ typedef struct _NMSupplicantInterfacePrivate { bool is_ready_main:1; bool is_ready_p2p_device:1; + bool prop_scan_active:1; + bool prop_scan_ssid:1; + } NMSupplicantInterfacePrivate; struct _NMSupplicantInterfaceClass { @@ -1123,7 +1126,7 @@ set_state (NMSupplicantInterface *self, NMSupplicantInterfaceState new_state) if (new_state == priv->state) return; - _LOGT ("set state \"%s\" (was \"%s\")", + _LOGT ("state: set state \"%s\" (was \"%s\")", nm_supplicant_interface_state_to_string (new_state), nm_supplicant_interface_state_to_string (priv->state)); @@ -1167,10 +1170,11 @@ static void parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities) { NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); - gboolean have_active = FALSE; - gboolean have_ssid = FALSE; + const gboolean old_prop_scan_active = priv->prop_scan_active; + const gboolean old_prop_scan_ssid = priv->prop_scan_ssid; + const guint32 old_max_scan_ssids = priv->max_scan_ssids; gboolean have_ft = FALSE; - gint32 max_scan_ssids = -1; + gint32 max_scan_ssids; const char **array; nm_assert (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT)); @@ -1195,23 +1199,37 @@ parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities) } if (g_variant_lookup (capabilities, "Scan", "^a&s", &array)) { - if (g_strv_contains (array, "active")) - have_active = TRUE; - if (g_strv_contains (array, "ssid")) - have_ssid = TRUE; + const char **a; + + priv->prop_scan_active = FALSE; + priv->prop_scan_ssid = FALSE; + for (a = array; *a; a++) { + if (nm_streq (*a, "active")) + priv->prop_scan_active = TRUE; + else if (nm_streq (*a, "ssid")) + priv->prop_scan_ssid = TRUE; + } g_free (array); } if (g_variant_lookup (capabilities, "MaxScanSSID", "i", &max_scan_ssids)) { - /* We need active scan and SSID probe capabilities to care about MaxScanSSIDs */ - if (max_scan_ssids > 0 && have_active && have_ssid) { - /* wpa_supplicant's NM_WPAS_MAX_SCAN_SSIDS value is 16, but for speed - * and to ensure we don't disclose too many SSIDs from the hidden - * list, we'll limit to 5. - */ - priv->max_scan_ssids = CLAMP (max_scan_ssids, 0, 5); - _LOGD ("supports %d scan SSIDs", priv->max_scan_ssids); - } + const gint32 WPAS_MAX_SCAN_SSIDS = 16; + + /* Even if supplicant claims that 20 SSIDs are supported, the Scan request + * still only accepts WPAS_MAX_SCAN_SSIDS SSIDs. Otherwise the D-Bus + * request will be rejected with "fi.w1.wpa_supplicant1.InvalidArgs" + * Body: ('Did not receive correct message arguments.', 'Too many ssids specified. Specify at most four') + * */ + priv->max_scan_ssids = CLAMP (max_scan_ssids, 0, WPAS_MAX_SCAN_SSIDS); + } + + if ( old_max_scan_ssids != priv->max_scan_ssids + || old_prop_scan_active != priv->prop_scan_active + || old_prop_scan_ssid != priv->prop_scan_ssid) { + _LOGD ("supports %u scan SSIDs (scan: %cactive %cssid)", + (guint32) priv->max_scan_ssids, + priv->prop_scan_active ? '+' : '-', + priv->prop_scan_ssid ? '+' : '-'); } } @@ -1789,8 +1807,12 @@ _properties_changed_main (NMSupplicantInterface *self, g_variant_unref (v_v); } - if (nm_g_variant_lookup (properties, "Scanning", "b", &v_b)) - priv->scanning_property = v_b; + if (nm_g_variant_lookup (properties, "Scanning", "b", &v_b)) { + if (priv->scanning_property != (!!v_b)) { + _LOGT ("scanning: %s (plain property)", v_b ? "yes" : "no"); + priv->scanning_property = v_b; + } + } if (nm_g_variant_lookup (properties, "Ifname", "&s", &v_s)) { if (nm_utils_strdup_reset (&priv->ifname, v_s)) @@ -1816,8 +1838,13 @@ _properties_changed_main (NMSupplicantInterface *self, state = wpas_state_string_to_enum (v_s); if (state == NM_SUPPLICANT_INTERFACE_STATE_INVALID) - _LOGT ("ignore unknown supplicant state '%s'", v_s); + _LOGT ("state: ignore unknown supplicant state '%s' (is %s, plain property)", + v_s, + nm_supplicant_interface_state_to_string (priv->supp_state)); else if (priv->supp_state != state) { + _LOGT ("state: %s (was %s, plain property)", + nm_supplicant_interface_state_to_string (state), + nm_supplicant_interface_state_to_string (priv->supp_state)); priv->supp_state = state; if (priv->state > NM_SUPPLICANT_INTERFACE_STATE_STARTING) { /* Only transition to actual wpa_supplicant interface states (ie, @@ -2328,40 +2355,82 @@ nm_supplicant_interface_assoc (NMSupplicantInterface *self, /*****************************************************************************/ +typedef struct { + NMSupplicantInterface *self; + GCancellable *cancellable; + NMSupplicantInterfaceRequestScanCallback callback; + gpointer user_data; +} ScanRequestData; + static void scan_request_cb (GObject *source, GAsyncResult *result, gpointer user_data) { + gs_unref_object NMSupplicantInterface *self_keep_alive = NULL; NMSupplicantInterface *self; gs_unref_variant GVariant *res = NULL; gs_free_error GError *error = NULL; + ScanRequestData *data = user_data; + gboolean cancelled = FALSE; res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error); - if (nm_utils_error_is_cancelled (error)) - return; - - self = NM_SUPPLICANT_INTERFACE (user_data); - if (error) { - if (_nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.Interface.ScanError")) - _LOGD ("request-scan: could not get scan request result: %s", error->message); - else { - g_dbus_error_strip_remote_error (error); - _LOGW ("request-scan: could not get scan request result: %s", error->message); + if (nm_utils_error_is_cancelled (error)) { + if (!data->callback) { + /* the self instance was not kept alive. We also must not touch it. Return. */ + nm_g_object_unref (data->cancellable); + nm_g_slice_free (data); + return; } - } else - _LOGT ("request-scan: request scanning success"); + cancelled = TRUE; + } + + self = data->self; + if (data->callback) { + /* the self instance was kept alive. Balance the reference count. */ + self_keep_alive = self; + } + + /* we don't propagate the error/success. That is, because either answer is not + * reliable. What is important to us is whether the request completed, and + * the current nm_supplicant_interface_get_scanning() state. */ + if (cancelled) + _LOGD ("request-scan: request cancelled"); + else { + if (error) { + if (_nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.Interface.ScanError")) + _LOGD ("request-scan: could not get scan request result: %s", error->message); + else { + g_dbus_error_strip_remote_error (error); + _LOGW ("request-scan: could not get scan request result: %s", error->message); + } + } else + _LOGT ("request-scan: request scanning success"); + } + + if (data->callback) + data->callback (self, data->cancellable, data->user_data); + + nm_g_object_unref (data->cancellable); + nm_g_slice_free (data); } void nm_supplicant_interface_request_scan (NMSupplicantInterface *self, GBytes *const*ssids, - guint ssids_len) + guint ssids_len, + GCancellable *cancellable, + NMSupplicantInterfaceRequestScanCallback callback, + gpointer user_data) { NMSupplicantInterfacePrivate *priv; GVariantBuilder builder; + ScanRequestData *data; guint i; g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self)); + nm_assert ( (!cancellable && !callback) + || (G_IS_CANCELLABLE (cancellable) && callback)); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); _LOGT ("request-scan: request scanning (%u ssids)...", ssids_len); @@ -2381,6 +2450,26 @@ nm_supplicant_interface_request_scan (NMSupplicantInterface *self, g_variant_builder_add (&builder, "{sv}", "SSIDs", g_variant_builder_end (&ssids_builder)); } + data = g_slice_new (ScanRequestData); + *data = (ScanRequestData) { + .self = self, + .callback = callback, + .user_data = user_data, + .cancellable = nm_g_object_ref (cancellable), + }; + + if (callback) { + /* A callback was provided. This keeps @self alive. The caller + * must provide a cancellable as the caller must never leave an asynchronous + * operation pending indefinitely. */ + nm_assert (G_IS_CANCELLABLE (cancellable)); + g_object_ref (self); + } else { + /* We don't keep @self alive, and we don't accept a cancellable either. */ + nm_assert (!cancellable); + cancellable = priv->main_cancellable; + } + _dbus_connection_call (self, NM_WPAS_DBUS_IFACE_INTERFACE, "Scan", @@ -2388,9 +2477,9 @@ nm_supplicant_interface_request_scan (NMSupplicantInterface *self, G_VARIANT_TYPE ("()"), G_DBUS_CALL_FLAGS_NONE, DBUS_TIMEOUT_MSEC, - priv->main_cancellable, + cancellable, scan_request_cb, - self); + data); } /*****************************************************************************/ @@ -2438,9 +2527,14 @@ nm_supplicant_interface_get_ifname (NMSupplicantInterface *self) guint nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self) { + NMSupplicantInterfacePrivate *priv; + g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), 0); - return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->max_scan_ssids; + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + return priv->prop_scan_active && priv->prop_scan_ssid + ? priv->max_scan_ssids + : 0u; } /*****************************************************************************/ @@ -2504,7 +2598,7 @@ nm_supplicant_interface_p2p_connect (NMSupplicantInterface *self, NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, "Connect", g_variant_new ("(a{sv})", &builder), - G_VARIANT_TYPE ("()"), + G_VARIANT_TYPE ("(s)"), "p2p-connect"); } diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h index eb414f26..9ef19816 100644 --- a/src/supplicant/nm-supplicant-interface.h +++ b/src/supplicant/nm-supplicant-interface.h @@ -128,9 +128,16 @@ nm_supplicant_interface_disconnect_async (NMSupplicantInterface * self, NMSupplicantInterfaceDisconnectCb callback, gpointer user_data); +typedef void (*NMSupplicantInterfaceRequestScanCallback) (NMSupplicantInterface *self, + GCancellable *cancellable, + gpointer user_data); + void nm_supplicant_interface_request_scan (NMSupplicantInterface *self, GBytes *const*ssids, - guint ssids_len); + guint ssids_len, + GCancellable *cancellable, + NMSupplicantInterfaceRequestScanCallback callback, + gpointer user_data); NMSupplicantInterfaceState nm_supplicant_interface_get_state (NMSupplicantInterface * self); diff --git a/src/supplicant/nm-supplicant-manager.c b/src/supplicant/nm-supplicant-manager.c index cf5cf111..b1a2dea8 100644 --- a/src/supplicant/nm-supplicant-manager.c +++ b/src/supplicant/nm-supplicant-manager.c @@ -562,6 +562,9 @@ _create_iface_dbus_call_create_interface_cb (GObject *source, NM_HASH_OBFUSCATE_PTR (handle), iface_path_str, handle->name_owner->str); + nm_utils_error_set (&error, + NM_UTILS_ERROR_UNKNOWN, + "Request already cancelled"); } else { _LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: name owner changed, still remove interface %s in %s", NM_HASH_OBFUSCATE_PTR (handle), diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c index 35bf4afb..6d995dc4 100644 --- a/src/vpn/nm-vpn-connection.c +++ b/src/vpn/nm-vpn-connection.c @@ -1595,13 +1595,18 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict) route.plen = plen; route.network = nm_utils_ip4_address_clear_host_address (route.network, plen); - /* Ignore host routes to the VPN gateway since NM adds one itself - * below. Since NM knows more about the routing situation than - * the VPN server, we want to use the NM created route instead of - * whatever the server provides. - */ - if (!(priv->ip4_external_gw && route.network == priv->ip4_external_gw && route.plen == 32)) - nm_ip4_config_add_route (config, &route, NULL); + if ( priv->ip4_external_gw + && route.network == priv->ip4_external_gw + && route.plen == 32) { + /* Ignore host routes to the VPN gateway since NM adds one itself + * below. Since NM knows more about the routing situation than + * the VPN server, we want to use the NM created route instead of + * whatever the server provides. + */ + break; + } + + nm_ip4_config_add_route (config, &route, NULL); break; default: break; @@ -1790,13 +1795,20 @@ nm_vpn_connection_ip6_config_get (NMVpnConnection *self, GVariant *dict) route.metric = route_metric; route.rt_source = NM_IP_CONFIG_SOURCE_VPN; - /* Ignore host routes to the VPN gateway since NM adds one itself. - * Since NM knows more about the routing situation than the VPN - * server, we want to use the NM created route instead of whatever - * the server provides. - */ - if (!(priv->ip6_external_gw && IN6_ARE_ADDR_EQUAL (&route.network, priv->ip6_external_gw) && route.plen == 128)) - nm_ip6_config_add_route (config, &route, NULL); + nm_utils_ip6_address_clear_host_address (&route.network, &route.network, route.plen); + + if ( priv->ip6_external_gw + && IN6_ARE_ADDR_EQUAL (&route.network, priv->ip6_external_gw) + && route.plen == 128) { + /* Ignore host routes to the VPN gateway since NM adds one itself. + * Since NM knows more about the routing situation than the VPN + * server, we want to use the NM created route instead of whatever + * the server provides. + */ + goto next; + } + + nm_ip6_config_add_route (config, &route, NULL); next: g_variant_unref (dest); |