diff options
| author | Michael Biebl <biebl@debian.org> | 2023-02-10 11:51:50 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2023-02-10 11:51:50 +0100 |
| commit | 58dddb802071d967ff5731f3333239ead78248e8 (patch) | |
| tree | def9e578e4786f8cbc56ce1b6cabf7670e00ae81 /src/core/devices/wifi | |
| parent | 2c1b7a7ea3c332b4d690c0595ef76c81de1f0cc8 (diff) | |
| parent | 1372848511cb896b80b51ed1a3e9606bd9816631 (diff) | |
Update upstream source from tag 'upstream/1.42.0'
Update to upstream version '1.42.0' with Debian dir 2b9fa2718b2bd34fa58008e335a0d81b87ad57cf
Diffstat (limited to 'src/core/devices/wifi')
| -rw-r--r-- | src/core/devices/wifi/nm-device-iwd-p2p.h | 2 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-device-iwd.c | 466 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-device-iwd.h | 4 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-device-olpc-mesh.h | 2 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-device-wifi-p2p.c | 1 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-device-wifi-p2p.h | 2 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-device-wifi.c | 49 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-device-wifi.h | 3 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-iwd-manager.c | 261 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-iwd-manager.h | 7 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-wifi-ap.h | 2 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-wifi-factory.c | 2 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-wifi-p2p-peer.h | 2 | ||||
| -rw-r--r-- | src/core/devices/wifi/nm-wifi-utils.c | 175 |
14 files changed, 790 insertions, 188 deletions
diff --git a/src/core/devices/wifi/nm-device-iwd-p2p.h b/src/core/devices/wifi/nm-device-iwd-p2p.h index 9ce353c0..98cf3762 100644 --- a/src/core/devices/wifi/nm-device-iwd-p2p.h +++ b/src/core/devices/wifi/nm-device-iwd-p2p.h @@ -11,7 +11,7 @@ #define NM_TYPE_DEVICE_IWD_P2P (nm_device_iwd_p2p_get_type()) #define NM_DEVICE_IWD_P2P(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_IWD_P2P, NMDeviceIwdP2P)) + (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_IWD_P2P, NMDeviceIwdP2P)) #define NM_DEVICE_IWD_P2P_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_IWD_P2P, NMDeviceIwdP2PClass)) #define NM_IS_DEVICE_IWD_P2P(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_IWD_P2P)) diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c index ab37cbec..e03227cd 100644 --- a/src/core/devices/wifi/nm-device-iwd.c +++ b/src/core/devices/wifi/nm-device-iwd.c @@ -8,6 +8,7 @@ #include "nm-device-iwd.h" #include <linux/if_ether.h> +#include <linux/rtnetlink.h> #include "devices/nm-device-private.h" #include "devices/nm-device.h" @@ -32,6 +33,7 @@ #include "supplicant/nm-supplicant-types.h" #include "nm-auth-utils.h" #include "nm-manager.h" +#include "nm-l3-config-data.h" #define _NMLOG_DEVICE_TYPE NMDeviceIwd #include "devices/nm-device-logging.h" @@ -70,6 +72,7 @@ typedef struct { bool secrets_failed : 1; bool networks_requested : 1; bool networks_changed : 1; + bool assuming : 1; gint64 last_scan; uint32_t ap_id; guint32 rate; @@ -77,6 +80,16 @@ typedef struct { GDBusMethodInvocation *pending_agent_request; NMActiveConnection *assumed_ac; guint assumed_ac_timeout; + + union { + struct { + const NML3ConfigData *pending_l3cd_6; + const NML3ConfigData *pending_l3cd_4; + }; + const NML3ConfigData *pending_l3cd_x[2]; + }; + + NMIwdManager *manager; } NMDeviceIwdPrivate; struct _NMDeviceIwd { @@ -289,6 +302,7 @@ insert_ap_from_network(NMDeviceIwd *self, gint64 last_seen_msec, int16_t signal) { + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); gs_unref_object GDBusProxy *network_proxy = NULL; nm_auto_ref_string NMRefString *bss_path = nm_ref_string_new(path); NMWifiAP *ap; @@ -299,7 +313,7 @@ insert_ap_from_network(NMDeviceIwd *self, } network_proxy = - nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(), path, NM_IWD_NETWORK_INTERFACE); + nm_iwd_manager_get_dbus_interface(priv->manager, path, NM_IWD_NETWORK_INTERFACE); ap = ap_from_network(self, network_proxy, bss_path, last_seen_msec, signal); if (!ap) @@ -525,6 +539,9 @@ cleanup_association_attempt(NMDeviceIwd *self, gboolean disconnect) if (disconnect && priv->dbus_station_proxy) send_disconnect(self); + + nm_clear_l3cd(&priv->pending_l3cd_6); + nm_clear_l3cd(&priv->pending_l3cd_4); } static void @@ -581,6 +598,10 @@ deactivate(NMDevice *device) if (!priv->dbus_obj) return; + /* Don't cause IWD to break the connection being assumed */ + if (priv->assuming) + return; + if (priv->dbus_station_proxy) { gs_unref_variant GVariant *value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); @@ -673,7 +694,7 @@ deactivate_async(NMDevice *device, } static gboolean -is_connection_known_network(NMConnection *connection) +is_connection_known_network(NMIwdManager *manager, NMConnection *connection) { NMIwdNetworkSecurity security; gs_free char *ssid = NULL; @@ -681,17 +702,17 @@ is_connection_known_network(NMConnection *connection) if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, &ssid, &security)) return FALSE; - return nm_iwd_manager_is_known_network(nm_iwd_manager_get(), ssid, security); + return nm_iwd_manager_is_known_network(manager, ssid, security); } static gboolean -is_ap_known_network(NMWifiAP *ap) +is_ap_known_network(NMIwdManager *manager, NMWifiAP *ap) { gs_unref_object GDBusProxy *network_proxy = NULL; gs_unref_variant GVariant *known_network = NULL; network_proxy = - nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(), + nm_iwd_manager_get_dbus_interface(manager, nm_ref_string_get_str(nm_wifi_ap_get_supplicant_path(ap)), NM_IWD_NETWORK_INTERFACE); if (!network_proxy) @@ -794,7 +815,8 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * * thus are Known Networks. */ if (security == NM_IWD_NETWORK_SECURITY_8021X) { - if (!is_connection_known_network(connection)) { + if (!is_connection_known_network(priv->manager, connection) + && !nm_iwd_manager_is_recently_mirrored(priv->manager, ssid)) { nm_utils_error_set_literal(error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, "802.1x connections must have IWD provisioning files"); @@ -927,7 +949,9 @@ check_connection_available(NMDevice *device, */ if (nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security) && security == NM_IWD_NETWORK_SECURITY_8021X) { - if (!is_ap_known_network(ap)) { + if (!is_ap_known_network(priv->manager, ap) + && !nm_iwd_manager_is_recently_mirrored(priv->manager, + nm_setting_wireless_get_ssid(s_wifi))) { nm_utils_error_set_literal( error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, @@ -2044,7 +2068,7 @@ assume_connection(NMDeviceIwd *self, NMWifiAP *ap) * becomes "managed" only when ACTIVATED but for IWD it's really * managed when IP_CONFIG starts. */ - sett_conn = nm_iwd_manager_get_ap_mirror_connection(nm_iwd_manager_get(), ap); + sett_conn = nm_iwd_manager_get_ap_mirror_connection(priv->manager, ap); if (!sett_conn) goto error; @@ -2217,7 +2241,8 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) * for a first-time connection to a hidden network. If a hidden network is * a Known Network it should still have been in the AP list. */ - if (!nm_setting_wireless_get_hidden(s_wireless) || is_connection_known_network(connection)) + if (!nm_setting_wireless_get_hidden(s_wireless) + || is_connection_known_network(priv->manager, connection)) return NM_ACT_STAGE_RETURN_FAILURE; add_new: @@ -2270,6 +2295,18 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) goto out_fail; } + /* With priv->iwd_autoconnect we have to let IWD handle retries for + * infrastructure networks. IWD will not necessarily retry the same + * network after a failure but it will likely go into an autoconnect + * mode and we don't want to try to override the logic. We don't need + * to reset the retry count so we set no timeout. + */ + if (priv->iwd_autoconnect) { + NMSettingsConnection *sett_conn = nm_act_request_get_settings_connection(req); + + nm_settings_connection_autoconnect_retries_set(sett_conn, 0); + } + /* With priv->iwd_autoconnect, if we're assuming a connection because * of a state change to "connecting", signal stage 2 is still running. * If "connected" or "roaming", we can go right to the IP_CONFIG state @@ -2310,7 +2347,9 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) * fail, for other combinations we will let the Connect call fail * or ask us for any missing secrets through the Agent. */ - if (nm_connection_get_setting_802_1x(connection) && !is_ap_known_network(ap)) { + if (nm_connection_get_setting_802_1x(connection) && !is_ap_known_network(priv->manager, ap) + && !nm_iwd_manager_is_recently_mirrored(priv->manager, + nm_setting_wireless_get_ssid(s_wireless))) { _LOGI(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) access point '%s' has 802.1x security but is not configured " "in IWD.", @@ -2351,7 +2390,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) } network_proxy = nm_iwd_manager_get_dbus_interface( - nm_iwd_manager_get(), + priv->manager, nm_ref_string_get_str(nm_wifi_ap_get_supplicant_path(ap)), NM_IWD_NETWORK_INTERFACE); if (!network_proxy) { @@ -2421,6 +2460,38 @@ out_fail: return NM_ACT_STAGE_RETURN_FAILURE; } +static void +act_stage3_ip_config(NMDevice *device, int addr_family) +{ + NMDeviceIwd *self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + if (!nm_iwd_manager_get_netconfig_enabled(nm_iwd_manager_get())) { + NMDeviceClass *device_class = NM_DEVICE_CLASS(nm_device_iwd_parent_class); + + if (device_class->act_stage3_ip_config) + device_class->act_stage3_ip_config(device, addr_family); + + return; + } + + if (!priv->pending_l3cd_x[IS_IPv4]) + return; + + nm_device_devip_set_state(device, + addr_family, + NM_DEVICE_IP_STATE_READY, + priv->pending_l3cd_x[IS_IPv4]); + nm_clear_l3cd(&priv->pending_l3cd_x[IS_IPv4]); +} + +static gboolean +ready_for_ip_config(NMDevice *device, gboolean is_manual) +{ + return !nm_iwd_manager_get_netconfig_enabled(nm_iwd_manager_get()); +} + static guint32 get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force) { @@ -2719,12 +2790,20 @@ state_changed(NMDeviceIwd *self, const char *new_state) "IWD is connecting to the wrong AP, %s activation", switch_ap ? "replacing" : "aborting"); cleanup_association_attempt(self, !switch_ap); + + if (!switch_ap) { + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + return; + } + + priv->assuming = TRUE; /* Don't send Station.Disconnect() */ nm_device_state_changed(device, - NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); - - if (switch_ap) - assume_connection(self, ap); + priv->assuming = FALSE; + assume_connection(self, ap); return; } @@ -3101,7 +3180,7 @@ nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object) goto error; } - adapter_proxy = nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(), + adapter_proxy = nm_iwd_manager_get_dbus_interface(priv->manager, g_variant_get_string(value, NULL), NM_IWD_WIPHY_INTERFACE); if (!adapter_proxy) { @@ -3278,7 +3357,7 @@ nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation) * it. IWD only queries us if it has not saved the PSK (e.g. by policy) * or a previous attempt has failed with current secrets so it wants a * fresh value. It doesn't know about agent-owned secrets so whenever - * possible and the PSK is saved and not asked from NM. However if this + * possible, the PSK is saved and not asked from NM. However if this * is a new connection it may include all of the needed settings already * so allow using these, too. Connection timestamp is set after * activation or after first activation failure (to 0). @@ -3368,6 +3447,341 @@ nm_device_iwd_network_add_remove(NMDeviceIwd *self, GDBusProxy *network, bool ad } } +static const NML3ConfigData * +nm_device_iwd_parse_netconfig(NMDeviceIwd *self, int addr_family, GVariantIter *config_iter) +{ + NMDevice *device = NM_DEVICE(self); + nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; + const char *key; + GVariant *value; + NMIPConfigSource source = NM_IP_CONFIG_SOURCE_UNKNOWN; + nm_auto_free_variant_iter GVariantIter *address_iter = NULL; + nm_auto_free_variant_iter GVariantIter *route_iter = NULL; + nm_auto_free_variant_iter GVariantIter *dns_iter = NULL; + nm_auto_free_variant_iter GVariantIter *domain_iter = NULL; + NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT; + const char *str_value; + GVariantIter *entry_iter; + uint32_t timestamp = nm_utils_get_monotonic_timestamp_sec(); + + while (g_variant_iter_next(config_iter, "{&sv}", &key, &value)) { + _nm_unused gs_unref_variant GVariant *value_free = value; + + if (nm_streq(key, "Method")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING) + || source != NM_IP_CONFIG_SOURCE_UNKNOWN) + goto param_error; + + str_value = g_variant_get_string(value, NULL); + if (nm_streq(str_value, "static")) + source = NM_IP_CONFIG_SOURCE_USER; + else if (nm_streq(str_value, "auto")) + /* Use SOURCE_DHCP as shorthand for the various autoconfiguration protocols */ + source = NM_IP_CONFIG_SOURCE_DHCP; + else + _LOGW(LOGD_WIFI, "iwd_parse_netconfig: Uknown Method value \"%s\"", str_value); + } + + if (nm_streq(key, "Addresses")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")) || address_iter) + goto param_error; + + g_variant_get(value, "aa{sv}", &address_iter); + } + + if (nm_streq(key, "Routes")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")) || route_iter) + goto param_error; + + g_variant_get(value, "aa{sv}", &route_iter); + } + + if (nm_streq(key, "DomainNameServers")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING_ARRAY) || dns_iter) + goto param_error; + + g_variant_get(value, "as", &dns_iter); + } + + if (nm_streq(key, "DomainNames")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING_ARRAY) || domain_iter) + goto param_error; + + g_variant_get(value, "as", &domain_iter); + } + + if (nm_streq(key, "MDNS")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING) + || mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) + goto param_error; + + str_value = g_variant_get_string(value, NULL); + if (nm_streq(str_value, "yes")) + mdns = NM_SETTING_CONNECTION_MDNS_YES; + else if (nm_streq(str_value, "no")) + mdns = NM_SETTING_CONNECTION_MDNS_NO; + else if (nm_streq(str_value, "resolve")) + mdns = NM_SETTING_CONNECTION_MDNS_RESOLVE; + else + _LOGW(LOGD_WIFI, "iwd_parse_netconfig: Uknown MDNS value \"%s\"", str_value); + } + } + + if (!address_iter || !route_iter) + goto param_error; + + l3cd = nm_l3_config_data_new(nm_device_get_multi_index(device), + nm_device_get_ip_ifindex(device), + source); + + while (g_variant_iter_next(address_iter, "a{sv}", &entry_iter)) { + _nm_unused nm_auto_free_variant_iter GVariantIter *entry_iter_free = entry_iter; + const char *addr_str = NULL; + NMIPAddr addr_bin; + uint8_t plen = 0; + const char *bcast_str = NULL; + NMIPAddr bcast_bin = {}; + uint32_t valid_lifetime = NM_PLATFORM_LIFETIME_PERMANENT; + uint32_t preferred_lifetime = NM_PLATFORM_LIFETIME_PERMANENT; + NMPlatformIPXAddress a; + + while (g_variant_iter_next(entry_iter, "{&sv}", &key, &value)) { + _nm_unused gs_unref_variant GVariant *value_free = value; + + if (nm_streq(key, "Address")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING) || addr_str) + goto param_error; + + addr_str = g_variant_get_string(value, NULL); + if (inet_pton(addr_family, addr_str, &addr_bin) != 1) + goto param_error; + } + + if (nm_streq(key, "PrefixLength")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BYTE)) + goto param_error; + + plen = g_variant_get_byte(value); + } + + if (nm_streq(key, "Broadcast")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING) || bcast_str) + goto param_error; + + bcast_str = g_variant_get_string(value, NULL); + if (inet_pton(addr_family, bcast_str, &bcast_bin) != 1) + goto param_error; + } + + if (nm_streq(key, "ValidLifetime")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32) + || valid_lifetime != NM_PLATFORM_LIFETIME_PERMANENT) + goto param_error; + + valid_lifetime = g_variant_get_uint32(value); + } + + if (nm_streq(key, "PreferredLifetime")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32) + || preferred_lifetime != NM_PLATFORM_LIFETIME_PERMANENT) + goto param_error; + + preferred_lifetime = g_variant_get_uint32(value); + } + } + + if (addr_str == NULL) + goto param_error; + + if (valid_lifetime != NM_PLATFORM_LIFETIME_PERMANENT + && preferred_lifetime == NM_PLATFORM_LIFETIME_PERMANENT) + preferred_lifetime = valid_lifetime; + + if (addr_family == AF_INET) { + a.a4 = (NMPlatformIP4Address){ + .address = addr_bin.addr4, + .peer_address = addr_bin.addr4, + .plen = plen, + .timestamp = (valid_lifetime != NM_PLATFORM_LIFETIME_PERMANENT) ? timestamp : 0, + .lifetime = valid_lifetime, + .preferred = preferred_lifetime, + .addr_source = source, + .use_ip4_broadcast_address = (bcast_str != NULL), + .broadcast_address = bcast_bin.addr4, + }; + } else { + a.a6 = (NMPlatformIP6Address){ + .address = addr_bin.addr6, + .plen = 128, + .timestamp = (valid_lifetime != NM_PLATFORM_LIFETIME_PERMANENT) ? timestamp : 0, + .lifetime = valid_lifetime, + .preferred = preferred_lifetime, + .addr_source = source, + }; + } + + nm_l3_config_data_add_address(l3cd, addr_family, NULL, &a.ax); + } + + while (g_variant_iter_next(route_iter, "a{sv}", &entry_iter)) { + _nm_unused nm_auto_free_variant_iter GVariantIter *entry_iter_free = entry_iter; + const char *dst_addr_str = NULL; + NMIPAddr dst_addr_bin = {}; + uint8_t dst_plen = 0; + const char *router_str = NULL; + NMIPAddr router_bin = {}; + const char *pref_src_str = NULL; + NMIPAddr pref_src_bin = {}; + _nm_unused uint32_t lifetime = NM_PLATFORM_LIFETIME_PERMANENT; + _nm_unused uint32_t priority = 0; + uint8_t preference = 0; + uint32_t mtu = 0; + NMPlatformIPXRoute r; + + while (g_variant_iter_next(entry_iter, "{&sv}", &key, &value)) { + _nm_unused gs_unref_variant GVariant *value_free = value; + + if (nm_streq(key, "Destination")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE("(sy)")) || dst_addr_str) + goto param_error; + + g_variant_get(value, "(&sy)", &dst_addr_str, &dst_plen); + if (inet_pton(addr_family, dst_addr_str, &dst_addr_bin) != 1) + goto param_error; + } + + if (nm_streq(key, "Router")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING) || router_str) + goto param_error; + + router_str = g_variant_get_string(value, NULL); + if (inet_pton(addr_family, router_str, &router_bin) != 1) + goto param_error; + } + + if (nm_streq(key, "PreferredSource")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING) || pref_src_str) + goto param_error; + + pref_src_str = g_variant_get_string(value, NULL); + if (inet_pton(addr_family, pref_src_str, &pref_src_bin) != 1) + goto param_error; + } + + if (nm_streq(key, "Lifetime")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32) + || lifetime != NM_PLATFORM_LIFETIME_PERMANENT) + goto param_error; + + lifetime = g_variant_get_uint32(value); + } + + if (nm_streq(key, "Priority")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32)) + goto param_error; + + priority = g_variant_get_uint32(value); + } + + if (nm_streq(key, "Preference")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BYTE)) + goto param_error; + + preference = g_variant_get_byte(value); + } + + if (nm_streq(key, "MTU")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32)) + goto param_error; + + mtu = g_variant_get_uint32(value); + } + } + + if (addr_family == AF_INET) { + r.r4 = (NMPlatformIP4Route){ + .network = dst_addr_str ? dst_addr_bin.addr4 : 0, + .plen = dst_addr_str ? dst_plen : 0, + .gateway = router_str ? router_bin.addr4 : 0, + .pref_src = pref_src_str ? pref_src_bin.addr4 : 0, + .scope_inv = + nm_platform_route_scope_inv(router_str ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK), + }; + } else { + r.r6 = (NMPlatformIP6Route){ + .network = dst_addr_str ? dst_addr_bin.addr6 : nm_ip_addr_zero.addr6, + .plen = dst_addr_str ? dst_plen : 0, + .gateway = router_str ? router_bin.addr6 : nm_ip_addr_zero.addr6, + .pref_src = pref_src_str ? pref_src_bin.addr6 : nm_ip_addr_zero.addr6, + .rt_pref = preference, + .r_rtm_flags = RTNH_F_ONLINK, + }; + } + + r.rx.metric_any = TRUE; + r.rx.mtu = mtu; + r.rx.rt_source = source; + r.rx.type_coerced = nm_platform_route_type_coerce(RTN_UNICAST); + r.rx.table_any = TRUE; + + /* TODO: set the lifetime */ + nm_l3_config_data_add_route(l3cd, addr_family, NULL, &r.rx); + } + + if (dns_iter) { + while (g_variant_iter_next(dns_iter, "&s", &str_value)) { + NMIPAddr dns_bin; + + if (inet_pton(addr_family, str_value, &dns_bin) != 1) + goto param_error; + + nm_l3_config_data_add_nameserver_detail(l3cd, addr_family, &dns_bin, NULL); + nm_l3_config_data_set_dns_priority(l3cd, addr_family, NM_DNS_PRIORITY_DEFAULT_NORMAL); + } + } + + if (domain_iter) { + while (g_variant_iter_next(domain_iter, "&s", &str_value)) + nm_l3_config_data_add_search(l3cd, addr_family, str_value); + } + + if (mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) + nm_l3_config_data_set_mdns(l3cd, mdns); + + return nm_l3_config_data_ref_and_seal(l3cd); + +param_error: + return NULL; +} + +bool +nm_device_iwd_set_netconfig(NMDeviceIwd *self, int addr_family, GVariantIter *config_iter) +{ + NMDevice *device = NM_DEVICE(self); + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + const NML3ConfigData *l3cd; + NMDeviceState state = nm_device_get_state(device); + + if (state < NM_DEVICE_STATE_CONFIG || state > NM_DEVICE_STATE_ACTIVATED) + return FALSE; + + l3cd = nm_device_iwd_parse_netconfig(self, addr_family, config_iter); + if (!l3cd) { + _LOGE(LOGD_WIFI, "Malformed netconfig DBus structure"); + return FALSE; + } + + if (state == NM_DEVICE_STATE_CONFIG) { + nm_l3_config_data_unref(priv->pending_l3cd_x[NM_IS_IPv4(addr_family)]); + priv->pending_l3cd_x[NM_IS_IPv4(addr_family)] = l3cd; + } else { + nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, l3cd); + nm_l3_config_data_unref(l3cd); + } + + return TRUE; +} + static void autoconnect_changed(NMDevice *device, GParamSpec *pspec, NMDeviceIwd *self) { @@ -3411,7 +3825,7 @@ nm_device_iwd_init(NMDeviceIwd *self) g_signal_connect(self, "notify::" NM_DEVICE_AUTOCONNECT, G_CALLBACK(autoconnect_changed), self); /* Make sure the manager is running */ - (void) nm_iwd_manager_get(); + priv->manager = g_object_ref(nm_iwd_manager_get()); } NMDevice * @@ -3443,6 +3857,8 @@ dispose(GObject *object) G_OBJECT_CLASS(nm_device_iwd_parent_class)->dispose(object); nm_assert(c_list_is_empty(&priv->aps_lst_head)); + + g_clear_object(&priv->manager); } static void @@ -3472,12 +3888,14 @@ nm_device_iwd_class_init(NMDeviceIwdClass *klass) device_class->set_enabled = set_enabled; device_class->get_type_description = get_type_description; - device_class->act_stage1_prepare = act_stage1_prepare; - device_class->act_stage2_config = act_stage2_config; - device_class->get_configured_mtu = get_configured_mtu; - device_class->deactivate = deactivate; - device_class->deactivate_async = deactivate_async; - device_class->can_reapply_change = can_reapply_change; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->act_stage3_ip_config = act_stage3_ip_config; + device_class->ready_for_ip_config = ready_for_ip_config; + device_class->get_configured_mtu = get_configured_mtu; + device_class->deactivate = deactivate; + device_class->deactivate_async = deactivate_async; + device_class->can_reapply_change = can_reapply_change; /* Stage 1 needed only for the set_current_ap() call. Stage 2 is * needed if we're assuming a connection still in the "connecting" diff --git a/src/core/devices/wifi/nm-device-iwd.h b/src/core/devices/wifi/nm-device-iwd.h index bb136bf6..8449f7f6 100644 --- a/src/core/devices/wifi/nm-device-iwd.h +++ b/src/core/devices/wifi/nm-device-iwd.h @@ -11,7 +11,7 @@ #include "nm-device-wifi.h" #define NM_TYPE_DEVICE_IWD (nm_device_iwd_get_type()) -#define NM_DEVICE_IWD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_IWD, NMDeviceIwd)) +#define NM_DEVICE_IWD(obj) (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_IWD, NMDeviceIwd)) #define NM_DEVICE_IWD_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_IWD, NMDeviceIwdClass)) #define NM_IS_DEVICE_IWD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_IWD)) @@ -46,4 +46,6 @@ void _nm_device_iwd_request_scan(NMDeviceIwd *self, void nm_device_iwd_network_add_remove(NMDeviceIwd *device, GDBusProxy *network, bool add); +bool nm_device_iwd_set_netconfig(NMDeviceIwd *device, int addr_family, GVariantIter *config_iter); + #endif /* __NETWORKMANAGER_DEVICE_IWD_H__ */ diff --git a/src/core/devices/wifi/nm-device-olpc-mesh.h b/src/core/devices/wifi/nm-device-olpc-mesh.h index 79b7fd5d..af3abbc0 100644 --- a/src/core/devices/wifi/nm-device-olpc-mesh.h +++ b/src/core/devices/wifi/nm-device-olpc-mesh.h @@ -15,7 +15,7 @@ #define NM_TYPE_DEVICE_OLPC_MESH (nm_device_olpc_mesh_get_type()) #define NM_DEVICE_OLPC_MESH(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMesh)) + (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMesh)) #define NM_DEVICE_OLPC_MESH_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMeshClass)) #define NM_IS_DEVICE_OLPC_MESH(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_OLPC_MESH)) diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c index 4576af95..6c0a4d5e 100644 --- a/src/core/devices/wifi/nm-device-wifi-p2p.c +++ b/src/core/devices/wifi/nm-device-wifi-p2p.c @@ -22,7 +22,6 @@ #include "nm-act-request.h" #include "nm-l3-config-data.h" #include "nm-manager.h" -#include "nm-manager.h" #include "nm-setting-wifi-p2p.h" #include "nm-utils.h" #include "nm-wifi-p2p-peer.h" diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.h b/src/core/devices/wifi/nm-device-wifi-p2p.h index d1aadd8e..08780464 100644 --- a/src/core/devices/wifi/nm-device-wifi-p2p.h +++ b/src/core/devices/wifi/nm-device-wifi-p2p.h @@ -11,7 +11,7 @@ #define NM_TYPE_DEVICE_WIFI_P2P (nm_device_wifi_p2p_get_type()) #define NM_DEVICE_WIFI_P2P(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2P)) + (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2P)) #define NM_DEVICE_WIFI_P2P_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2PClass)) #define NM_IS_DEVICE_WIFI_P2P(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_WIFI_P2P)) diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c index 8af974d0..171743d0 100644 --- a/src/core/devices/wifi/nm-device-wifi.c +++ b/src/core/devices/wifi/nm-device-wifi.c @@ -99,13 +99,13 @@ typedef struct { NMSupplMgrCreateIfaceHandle *sup_create_handle; NMSupplicantInterface *sup_iface; + GSource *scan_kickoff_timeout_source; + gint64 scan_last_complete_msec; gint64 scan_periodic_next_msec; gint64 scan_last_request_started_at_msec; - guint scan_kickoff_timeout_id; - guint ap_dump_id; guint periodic_update_id; @@ -508,7 +508,10 @@ _scan_notify_allowed(NMDeviceWifi *self, NMTernary do_kickoff) /* 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. */ + * disrupt connected clients or peers. + * + * Explicit scans are allowed however. */ + explicit_allowed = TRUE; } else if (NM_IN_SET(state, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_FAILED)) { /* Can always scan when disconnected */ explicit_allowed = TRUE; @@ -659,7 +662,7 @@ supplicant_interface_release(NMDeviceWifi *self) NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE); - nm_clear_g_source(&priv->scan_kickoff_timeout_id); + nm_clear_g_source_inst(&priv->scan_kickoff_timeout_source); nm_clear_g_source_inst(&priv->scan_request_delay_source); nm_clear_g_cancellable(&priv->scan_request_cancellable); @@ -1718,9 +1721,9 @@ _scan_kickoff_timeout_cb(gpointer user_data) NMDeviceWifi *self = user_data; NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self); - priv->scan_kickoff_timeout_id = 0; + nm_clear_g_source_inst(&priv->scan_kickoff_timeout_source); _scan_kickoff(self); - return G_SOURCE_REMOVE; + return G_SOURCE_CONTINUE; } static void @@ -1763,13 +1766,13 @@ _scan_kickoff(NMDeviceWifi *self) / 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_kickoff_timeout_source ? ", schedule timeout" : ""); + if (!priv->scan_kickoff_timeout_source && (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); + priv->scan_kickoff_timeout_source = nm_g_timeout_add_source( + priv->scan_last_request_started_at_msec + ratelimit_duration_msec - now_msec, + _scan_kickoff_timeout_cb, + self); } return; } @@ -1786,14 +1789,16 @@ _scan_kickoff(NMDeviceWifi *self) _LOGT_scan("kickoff: don't scan (rate limited for another %d.%03d sec after previous scan)", timeout_msec / 1000, timeout_msec % 1000); - nm_clear_g_source(&priv->scan_kickoff_timeout_id); - priv->scan_kickoff_timeout_id = g_timeout_add(timeout_msec, _scan_kickoff_timeout_cb, self); + nm_clear_g_source_inst(&priv->scan_kickoff_timeout_source); + priv->scan_kickoff_timeout_source = + nm_g_timeout_add_source(timeout_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)"); + _LOGT_scan( + "kickoff: don't scan (explicit scan requested but not allowed at the moment)"); return; } priv->scan_explicit_requested = FALSE; @@ -1803,7 +1808,7 @@ _scan_kickoff(NMDeviceWifi *self) _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); + nm_clear_g_source_inst(&priv->scan_kickoff_timeout_source); return; } @@ -1813,12 +1818,12 @@ _scan_kickoff(NMDeviceWifi *self) _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); + !priv->scan_kickoff_timeout_source ? ", schedule timeout" : ""); + if (!priv->scan_kickoff_timeout_source) { + priv->scan_kickoff_timeout_source = nm_g_timeout_add_seconds_source( + (priv->scan_periodic_next_msec - now_msec + 999) / 1000, + _scan_kickoff_timeout_cb, + self); } return; } diff --git a/src/core/devices/wifi/nm-device-wifi.h b/src/core/devices/wifi/nm-device-wifi.h index 3a85192e..17893a1b 100644 --- a/src/core/devices/wifi/nm-device-wifi.h +++ b/src/core/devices/wifi/nm-device-wifi.h @@ -10,7 +10,8 @@ #include "devices/nm-device.h" #define NM_TYPE_DEVICE_WIFI (nm_device_wifi_get_type()) -#define NM_DEVICE_WIFI(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_WIFI, NMDeviceWifi)) +#define NM_DEVICE_WIFI(obj) \ + (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_WIFI, NMDeviceWifi)) #define NM_DEVICE_WIFI_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_WIFI, NMDeviceWifiClass)) #define NM_IS_DEVICE_WIFI(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_WIFI)) diff --git a/src/core/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c index 2e0d51e5..0d4daf21 100644 --- a/src/core/devices/wifi/nm-iwd-manager.c +++ b/src/core/devices/wifi/nm-iwd-manager.c @@ -47,13 +47,18 @@ typedef struct { } KnownNetworkData; typedef struct { + GBytes *ssid; + gint64 timestamp; +} RecentlyMirroredData; + +typedef struct { NMManager *manager; NMSettings *settings; GCancellable *cancellable; gboolean running; GDBusObjectManager *object_manager; guint agent_id; - char *agent_path; + guint netconfig_agent_id; GHashTable *known_networks; NMDeviceIwd *last_agent_call_device; char *last_state_dir; @@ -62,6 +67,7 @@ typedef struct { GHashTable *p2p_devices; NMIwdWfdInfo wfd_info; guint wfd_use_count; + GSList *recently_mirrored; } NMIwdManagerPrivate; struct _NMIwdManager { @@ -271,6 +277,77 @@ return_error: "Secrets not available for this connection"); } +static void +netconfig_agent_dbus_method_cb(GDBusConnection *connection, + const char *sender, + const char *object_path, + const char *interface_name, + const char *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + NMIwdManager *self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + gs_free char *name_owner = NULL; + const char *device_path; + gs_unref_object GDBusInterface *device_obj = NULL; + NMDevice *device; + nm_auto_free_variant_iter GVariantIter *config_iter = NULL; + const char *ifname; + int addr_family; + + /* Be paranoid and check the sender address */ + name_owner = g_dbus_object_manager_client_get_name_owner( + G_DBUS_OBJECT_MANAGER_CLIENT(priv->object_manager)); + if (!nm_streq0(name_owner, sender)) + goto return_error; + + if (nm_streq(method_name, "ConfigureIPv4")) + addr_family = AF_INET; + else if (nm_streq(method_name, "ConfigureIPv6")) + addr_family = AF_INET6; + else + goto return_error; + + g_variant_get(parameters, "(&oa{sv})", &device_path, &config_iter); + + device_obj = g_dbus_object_manager_get_interface(priv->object_manager, + device_path, + NM_IWD_DEVICE_INTERFACE); + if (!device_obj) { + _LOGE("netconfig-agent-request: unable to find the device object"); + goto return_error; + } + + ifname = get_property_string_or_null(G_DBUS_PROXY(device_obj), "Name"); + if (!ifname) { + _LOGD("Name not cached for device at %s", device_path); + goto return_error; + } + + device = nm_manager_get_device(priv->manager, ifname, NM_DEVICE_TYPE_WIFI); + if (!device || !NM_IS_DEVICE_IWD(device)) { + _LOGD("NM device %s is not an IWD-managed device", ifname); + goto return_error; + } + + if (nm_device_iwd_set_netconfig(NM_DEVICE_IWD(device), addr_family, config_iter)) { + g_dbus_method_invocation_return_value(invocation, g_variant_new("()")); + return; + } + + _LOGD("netconfig-agent-request: device %s did not handle the IWD Netconfig Agent request", + ifname); + +return_error: + /* IWD doesn't look at the specific error */ + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "Couldn't set netconfig data"); +} + static const GDBusInterfaceInfo iwd_agent_iface_info = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( "net.connman.iwd.Agent", .methods = NM_DEFINE_GDBUS_METHOD_INFOS( @@ -296,36 +373,55 @@ static const GDBusInterfaceInfo iwd_agent_iface_info = NM_DEFINE_GDBUS_INTERFACE .in_args = NM_DEFINE_GDBUS_ARG_INFOS( NM_DEFINE_GDBUS_ARG_INFO("reason", "s"), ), ), ), ); +static const GDBusInterfaceInfo iwd_netconfig_agent_iface_info = + NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + "net.connman.iwd.NetworkConfigurationAgent", + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_GDBUS_METHOD_INFO("ConfigureIPv4", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("device", "o"), + NM_DEFINE_GDBUS_ARG_INFO("config", "a{sv}"), ), ), + NM_DEFINE_GDBUS_METHOD_INFO("ConfigureIPv6", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("device", "o"), + NM_DEFINE_GDBUS_ARG_INFO("config", "a{sv}"), ), ), ), ); + static guint -iwd_agent_export(GDBusConnection *connection, gpointer user_data, char **agent_path, GError **error) +iwd_agent_export(GDBusConnection *connection, gpointer user_data, GError **error) { static const GDBusInterfaceVTable vtable = { .method_call = agent_dbus_method_cb, }; - char path[50]; - unsigned int rnd; - guint id; - nm_random_get_bytes(&rnd, sizeof(rnd)); - - nm_sprintf_buf(path, "/agent/%u", rnd); + return g_dbus_connection_register_object( + connection, + NM_IWD_AGENT_PATH, + NM_UNCONST_PTR(GDBusInterfaceInfo, &iwd_agent_iface_info), + &vtable, + user_data, + NULL, + error); +} - id = - g_dbus_connection_register_object(connection, - path, - NM_UNCONST_PTR(GDBusInterfaceInfo, &iwd_agent_iface_info), - &vtable, - user_data, - NULL, - error); +static guint +iwd_netconfig_agent_export(GDBusConnection *connection, gpointer user_data, GError **error) +{ + static const GDBusInterfaceVTable vtable = { + .method_call = netconfig_agent_dbus_method_cb, + }; - if (id) - *agent_path = g_strdup(path); - return id; + return g_dbus_connection_register_object( + connection, + NM_IWD_AGENT_PATH, + NM_UNCONST_PTR(GDBusInterfaceInfo, &iwd_netconfig_agent_iface_info), + &vtable, + user_data, + NULL, + error); } static void -register_agent(NMIwdManager *self) +register_agent(NMIwdManager *self, const char *method) { NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); GDBusInterface *agent_manager; @@ -334,14 +430,14 @@ register_agent(NMIwdManager *self) "/net/connman/iwd", /* IWD 1.0+ */ NM_IWD_AGENT_MANAGER_INTERFACE); if (!agent_manager) { - _LOGE("unable to register the IWD Agent: PSK/8021x Wi-Fi networks may not work"); + _LOGE("unable to register the IWD Agent"); return; } /* Register our agent */ g_dbus_proxy_call(G_DBUS_PROXY(agent_manager), - "RegisterAgent", - g_variant_new("(o)", priv->agent_path), + method, + g_variant_new("(o)", NM_IWD_AGENT_PATH), G_DBUS_CALL_FLAGS_NONE, -1, NULL, @@ -353,6 +449,70 @@ register_agent(NMIwdManager *self) /*****************************************************************************/ +static void +recently_mirrored_data_free(void *data) +{ + RecentlyMirroredData *rmd = data; + + g_bytes_unref(rmd->ssid); + g_free(rmd); +} + +/* When we mirror an 802.1x connection to an IWD config file, and there's an + * AP in range with matching SSID, that connection should become available + * for activation. In IWD terms when an 802.1x network becomes a Known + * Network, it can be connected to using the .Connect D-Bus method. + * + * However there's a delay between writing the IWD config file and receiving + * the InterfaceAdded event for the Known Network so we don't immediately + * find out that the network can now be used. If an NM client creates a + * new connection for an 802.1x AP and tries to activate it immediately, + * NMDeviceIWD will not allow it to because it doesn't know the network is + * known yet. To work around this, we save the SSIDs of 802.1x connections + * we recently mirrored to IWD config files, for 2 seconds, and we treat + * them as Known Networks in that period since in theory activations should + * succeed. + */ +bool +nm_iwd_manager_is_recently_mirrored(NMIwdManager *self, const GBytes *ssid) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + gint64 now = nm_utils_get_monotonic_timestamp_nsec(); + GSList *iter; + RecentlyMirroredData *rmd; + + /* Drop entries older than 2 seconds */ + while (priv->recently_mirrored) { + rmd = priv->recently_mirrored->data; + if (now < rmd->timestamp + 2000000000) + break; + + priv->recently_mirrored = g_slist_remove(priv->recently_mirrored, rmd); + recently_mirrored_data_free(rmd); + } + + for (iter = priv->recently_mirrored; iter; iter = iter->next) { + rmd = iter->data; + if (g_bytes_equal(ssid, rmd->ssid)) + return TRUE; + } + + return FALSE; +} + +static void +save_mirrored(NMIwdManager *self, GBytes *ssid) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + RecentlyMirroredData *rmd = g_malloc(sizeof(RecentlyMirroredData)); + + rmd->ssid = g_bytes_ref(ssid); + rmd->timestamp = nm_utils_get_monotonic_timestamp_nsec(); + priv->recently_mirrored = g_slist_append(priv->recently_mirrored, rmd); +} + +/*****************************************************************************/ + static KnownNetworkId * known_network_id_new(const char *name, NMIwdNetworkSecurity security) { @@ -721,6 +881,9 @@ sett_conn_changed(NMSettingsConnection *sett_conn, "iwd: changed Wi-Fi connection %s mirrored as IWD profile %s", nm_settings_connection_get_id(sett_conn), full_path); + + if (security == NM_IWD_NETWORK_SECURITY_8021X) + save_mirrored(nm_iwd_manager_get(), ssid); } /* Look up an existing NMSettingsConnection for a network that has been @@ -1283,6 +1446,7 @@ connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer gs_free_error GError *error = NULL; nm_auto_unref_keyfile GKeyFile *iwd_config = NULL; NMSettingsConnectionIntFlags flags; + NMIwdNetworkSecurity security; if (!nm_streq(nm_settings_connection_get_connection_type(sett_conn), "802-11-wireless")) return; @@ -1338,6 +1502,12 @@ connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer _LOGD("New Wi-Fi connection %s mirrored as IWD profile %s", nm_settings_connection_get_id(sett_conn), full_path); + + if (nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &security) + && security == NM_IWD_NETWORK_SECURITY_8021X) { + NMSettingWireless *s_wifi = nm_connection_get_setting_wireless(conn); + save_mirrored(nm_iwd_manager_get(), nm_setting_wireless_get_ssid(s_wifi)); + } } static gboolean @@ -1355,26 +1525,33 @@ _om_has_name_owner(GDBusObjectManager *object_manager) static void release_object_manager(NMIwdManager *self) { - NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + GDBusConnection *agent_connection; + GDBusObjectManagerClient *omc; if (!priv->object_manager) return; g_signal_handlers_disconnect_by_data(priv->object_manager, self); - if (priv->agent_id) { - GDBusConnection *agent_connection; - GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT(priv->object_manager); - - agent_connection = g_dbus_object_manager_client_get_connection(omc); + omc = G_DBUS_OBJECT_MANAGER_CLIENT(priv->object_manager); + agent_connection = g_dbus_object_manager_client_get_connection(omc); - /* We're is called when we're shutting down (i.e. our DBus connection - * is being closed, and IWD will detect this) or IWD was stopped so - * in either case calling UnregisterAgent will not do anything. - */ + /* We're called when we're shutting down (i.e. our DBus connection + * is being closed, and IWD will detect this) or IWD was stopped so + * in either case calling UnregisterAgent will not do anything. + * Just unregister the agent interfaces. The agents are on the same + * object (same path) but it seems g_dbus_connection_unregister_object() + * should be called for each interface on the object separately. + */ + if (priv->agent_id) { g_dbus_connection_unregister_object(agent_connection, priv->agent_id); priv->agent_id = 0; - nm_clear_g_free(&priv->agent_path); + } + + if (priv->netconfig_agent_id) { + g_dbus_connection_unregister_object(agent_connection, priv->netconfig_agent_id); + priv->netconfig_agent_id = 0; } g_clear_object(&priv->object_manager); @@ -1589,6 +1766,10 @@ next: } g_variant_iter_free(properties_iter); + + /* Register the netconfig agent only once we know netconfig is enabled */ + if (nm_iwd_manager_get_netconfig_enabled(self) && priv->netconfig_agent_id) + register_agent(self, "RegisterNetworkConfigurationAgent"); } static void @@ -1620,13 +1801,19 @@ got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data) connection = g_dbus_object_manager_client_get_connection(G_DBUS_OBJECT_MANAGER_CLIENT(object_manager)); - priv->agent_id = iwd_agent_export(connection, self, &priv->agent_path, &error); + priv->agent_id = iwd_agent_export(connection, self, &error); if (!priv->agent_id) { _LOGE("failed to export the IWD Agent: PSK/8021x Wi-Fi networks may not work: %s", error->message); g_clear_error(&error); } + priv->netconfig_agent_id = iwd_netconfig_agent_export(connection, self, &error); + if (!priv->netconfig_agent_id) { + _LOGE("failed to export the IWD Netconfig Agent: %s", error->message); + g_clear_error(&error); + } + if (_om_has_name_owner(object_manager)) { GList *objects, *iter; gs_unref_object GDBusInterface *daemon = NULL; @@ -1654,7 +1841,7 @@ got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data) g_list_free_full(objects, g_object_unref); if (priv->agent_id) - register_agent(self); + register_agent(self, "RegisterAgent"); priv->netconfig_enabled = false; /* Assume false until GetInfo() results come in */ @@ -1952,6 +2139,8 @@ dispose(GObject *object) g_hash_table_unref(nm_steal_pointer(&priv->p2p_devices)); + g_slist_free_full(nm_steal_pointer(&priv->recently_mirrored), recently_mirrored_data_free); + G_OBJECT_CLASS(nm_iwd_manager_parent_class)->dispose(object); } diff --git a/src/core/devices/wifi/nm-iwd-manager.h b/src/core/devices/wifi/nm-iwd-manager.h index 02cd6bba..80123c9a 100644 --- a/src/core/devices/wifi/nm-iwd-manager.h +++ b/src/core/devices/wifi/nm-iwd-manager.h @@ -30,8 +30,11 @@ #define NM_IWD_P2P_SERVICE_MANAGER_INTERFACE "net.connman.iwd.p2p.ServiceManager" #define NM_IWD_P2P_WFD_INTERFACE "net.connman.iwd.p2p.Display" +#define NM_IWD_AGENT_PATH NM_DBUS_PATH "/iwd/agent" + #define NM_TYPE_IWD_MANAGER (nm_iwd_manager_get_type()) -#define NM_IWD_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IWD_MANAGER, NMIwdManager)) +#define NM_IWD_MANAGER(obj) \ + (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IWD_MANAGER, NMIwdManager)) #define NM_IWD_MANAGER_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_IWD_MANAGER, NMIwdManagerClass)) #define NM_IS_IWD_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_IWD_MANAGER)) @@ -63,4 +66,6 @@ gboolean nm_iwd_manager_check_wfd_info_compatible(NMIwdManager *self, const NMIw gboolean nm_iwd_manager_register_wfd(NMIwdManager *self, const NMIwdWfdInfo *wfd_info); void nm_iwd_manager_unregister_wfd(NMIwdManager *self); +bool nm_iwd_manager_is_recently_mirrored(NMIwdManager *self, const GBytes *ssid); + #endif /* __NETWORKMANAGER_IWD_MANAGER_H__ */ diff --git a/src/core/devices/wifi/nm-wifi-ap.h b/src/core/devices/wifi/nm-wifi-ap.h index c0422900..93dc8edf 100644 --- a/src/core/devices/wifi/nm-wifi-ap.h +++ b/src/core/devices/wifi/nm-wifi-ap.h @@ -13,7 +13,7 @@ #include "libnm-base/nm-base.h" #define NM_TYPE_WIFI_AP (nm_wifi_ap_get_type()) -#define NM_WIFI_AP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_AP, NMWifiAP)) +#define NM_WIFI_AP(obj) (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_AP, NMWifiAP)) #define NM_WIFI_AP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_AP, NMWifiAPClass)) #define NM_IS_WIFI_AP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_AP)) #define NM_IS_WIFI_AP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_AP)) diff --git a/src/core/devices/wifi/nm-wifi-factory.c b/src/core/devices/wifi/nm-wifi-factory.c index 5e354c6b..19880a90 100644 --- a/src/core/devices/wifi/nm-wifi-factory.c +++ b/src/core/devices/wifi/nm-wifi-factory.c @@ -24,7 +24,7 @@ #define NM_TYPE_WIFI_FACTORY (nm_wifi_factory_get_type()) #define NM_WIFI_FACTORY(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_FACTORY, NMWifiFactory)) + (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_FACTORY, NMWifiFactory)) #define NM_WIFI_FACTORY_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_FACTORY, NMWifiFactoryClass)) #define NM_IS_WIFI_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_FACTORY)) diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.h b/src/core/devices/wifi/nm-wifi-p2p-peer.h index 5124d1de..b0abeb37 100644 --- a/src/core/devices/wifi/nm-wifi-p2p-peer.h +++ b/src/core/devices/wifi/nm-wifi-p2p-peer.h @@ -12,7 +12,7 @@ #define NM_TYPE_WIFI_P2P_PEER (nm_wifi_p2p_peer_get_type()) #define NM_WIFI_P2P_PEER(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeer)) + (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeer)) #define NM_WIFI_P2P_PEER_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerClass)) #define NM_IS_WIFI_P2P_PEER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_P2P_PEER)) diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c index 1a8204b0..8a8d062f 100644 --- a/src/core/devices/wifi/nm-wifi-utils.c +++ b/src/core/devices/wifi/nm-wifi-utils.c @@ -11,6 +11,7 @@ #include <netinet/if_ether.h> #include <stdlib.h> +#include "libnm-glib-aux/nm-str-buf.h" #include "nm-utils.h" #include "libnm-core-intern/nm-core-internal.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h" @@ -812,13 +813,12 @@ nm_wifi_utils_complete_connection(GBytes *ap_ssid, * setting. Since there's so much configuration required for it, there's * no way it can be automatically completed. */ - } else if (nm_streq0(key_mgmt, "wpa-psk") - || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE - && (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK - || ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK))) { + } else if (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE + && (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK + || ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) { g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, - "wpa-psk", + nm_streq0(key_mgmt, "sae") ? "sae" : "wpa-psk", NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", NULL); @@ -828,7 +828,7 @@ nm_wifi_utils_complete_connection(GBytes *ap_ssid, || NM_FLAGS_ANY(ap_rsn_flags, NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) { g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "owe", NULL); - } else if (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK + } else if (nm_streq0(key_mgmt, "wpa-psk") || ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK || ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) { g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, @@ -1004,7 +1004,7 @@ psk_setting_to_iwd_config(GKeyFile *file, NMSettingWirelessSecurity *s_wsec, GEr if (NM_FLAGS_ANY(psk_flags, SECRETS_DONT_STORE_FLAGS)) { nm_log_info( LOGD_WIFI, - "IWD network config is being created wihout the PSK but IWD will save the PSK on " + "IWD network config is being created without the PSK but IWD will save the PSK on " "successful activation not honoring the psk-flags property"); } return TRUE; @@ -1549,10 +1549,16 @@ eap_setting_to_iwd_config(GKeyFile *file, NMSetting8021x *s_8021x, GError **erro } static gboolean -ip4_config_to_iwd_config(GKeyFile *file, NMSettingIPConfig *s_ip, GError **error) +ip_config_to_iwd_config(int addr_family, GKeyFile *file, NMSettingIPConfig *s_ip, GError **error) { - guint num; - struct in_addr ip; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT_A(NM_UTILS_GET_NEXT_REALLOC_SIZE_488, FALSE); + NMIPAddress *addr; + guint num; + guint i; + char buf[NM_INET_ADDRSTRLEN + 10]; + const char *kf_group = IS_IPv4 ? "IPv4" : "IPv6"; + const char *gw; /* These settings are not acutally used unless global * [General].EnableNetworkConfiguration is true, which we don't support. @@ -1564,15 +1570,26 @@ ip4_config_to_iwd_config(GKeyFile *file, NMSettingIPConfig *s_ip, GError **error if (!s_ip) return TRUE; + nm_assert(NM_IS_IPv4(addr_family) ? NM_IS_SETTING_IP4_CONFIG(s_ip) + : NM_IS_SETTING_IP6_CONFIG(s_ip)); + num = nm_setting_ip_config_get_num_dns(s_ip); if (num) { - nm_auto_free_gstring GString *s = g_string_sized_new(128); - guint i; - + nm_str_buf_reset(&strbuf); for (i = 0; i < num; i++) { - if (s->len) - g_string_append_c(s, ' '); - g_string_append(s, nm_setting_ip_config_get_dns(s_ip, i)); + char sbuf[NM_INET_ADDRSTRLEN]; + NMIPAddr a; + + if (!nm_utils_dnsname_parse_assert(addr_family, + nm_setting_ip_config_get_dns(s_ip, i), + NULL, + &a, + NULL)) + continue; + + if (strbuf.len > 0) + nm_str_buf_append_c(&strbuf, ' '); + nm_str_buf_append(&strbuf, nm_inet_ntop(addr_family, &a, sbuf)); } /* It doesn't matter whether we add the DNS under [IPv4] or [IPv6] * except that with method=auto the list will override the @@ -1581,97 +1598,61 @@ ip4_config_to_iwd_config(GKeyFile *file, NMSettingIPConfig *s_ip, GError **error * Note ignore-auto-dns=false isn't supported, this list always * overrides the DHCP DNSes. */ - g_key_file_set_string(file, "IPv4", "DNS", s->str); + g_key_file_set_string(file, kf_group, "DNS", nm_str_buf_get_str(&strbuf)); } - if (!nm_streq0(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) - return TRUE; - - num = nm_setting_ip_config_get_num_addresses(s_ip); - if (num) { - NMIPAddress *addr = nm_setting_ip_config_get_address(s_ip, 0); - guint prefix = nm_ip_address_get_prefix(addr); - in_addr_t netmask = _nm_utils_ip4_prefix_to_netmask(prefix); - char buf[INET_ADDRSTRLEN]; - - nm_ip_address_get_address_binary(addr, &ip); - g_key_file_set_string(file, "IPv4", "Address", nm_ip_address_get_address(addr)); - g_key_file_set_string(file, "IPv4", "Netmask", _nm_utils_inet4_ntop(netmask, buf)); - } else { - inet_pton(AF_INET, "10.42.0.100", &ip); - g_key_file_set_string(file, "IPv4", "Address", "10.42.0.100"); + if (!IS_IPv4) { + if (!NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip), + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_DHCP, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) + return TRUE; + g_key_file_set_boolean(file, kf_group, "Enabled", TRUE); } - if (nm_setting_ip_config_get_gateway(s_ip)) { - g_key_file_set_string(file, "IPv4", "Gateway", nm_setting_ip_config_get_gateway(s_ip)); - } else { - uint32_t val; - char buf[INET_ADDRSTRLEN]; - - /* IWD won't enable static IP unless both Address and Gateway are - * set so generate a gateway address if not known. - */ - val = (ntohl(ip.s_addr) & 0xfffffff0) + 1; - if (val == ntohl(ip.s_addr)) - val += 1; - g_key_file_set_string(file, "IPv4", "Gateway", _nm_utils_inet4_ntop(htonl(val), buf)); - } - - return TRUE; -} - -static gboolean -ip6_config_to_iwd_config(GKeyFile *file, NMSettingIPConfig *s_ip, GError **error) -{ - guint num; - NMIPAddress *addr; - char buf[INET6_ADDRSTRLEN + 10]; - - if (!s_ip) + num = nm_setting_ip_config_get_num_addresses(s_ip); + if (num == 0) return TRUE; - num = nm_setting_ip_config_get_num_dns(s_ip); - if (num) { - nm_auto_free_gstring GString *s = g_string_sized_new(128); - guint i; + addr = nm_setting_ip_config_get_address(s_ip, 0); + gw = nm_setting_ip_config_get_gateway(s_ip); - for (i = 0; i < num; i++) { - if (s->len) - g_string_append_c(s, ' '); - g_string_append(s, nm_setting_ip_config_get_dns(s_ip, i)); - } - g_key_file_set_string(file, "IPv6", "DNS", s->str); - } + if (IS_IPv4) { + in_addr_t ip; - if (!NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip), - NM_SETTING_IP6_CONFIG_METHOD_AUTO, - NM_SETTING_IP6_CONFIG_METHOD_DHCP, - NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) - return TRUE; + nm_ip_address_get_address_binary(addr, &ip); - g_key_file_set_boolean(file, "IPv6", "Enabled", TRUE); + g_key_file_set_string(file, kf_group, "Address", nm_ip_address_get_address(addr)); + g_key_file_set_string( + file, + kf_group, + "Netmask", + nm_inet4_ntop(nm_ip4_addr_netmask_from_prefix(nm_ip_address_get_prefix(addr)), buf)); - if (!nm_streq0(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) - return TRUE; + if (!gw) { + guint32 val; - if (!nm_setting_ip_config_get_num_addresses(s_ip)) { - g_set_error_literal(error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_INVALID_PROPERTY, - "IP address required for IPv6 manual config"); - return FALSE; + /* IWD won't enable static IP unless both Address and Gateway are + * set so generate a gateway address if not known. + */ + val = (ntohl(ip) & 0xfffffff0) + 1; + if (val == ntohl(ip)) + val += 1; + gw = nm_inet4_ntop(htonl(val), buf); + } + g_key_file_set_string(file, kf_group, "Gateway", gw); + } else { + g_key_file_set_string(file, + kf_group, + "Address", + nm_sprintf_buf(buf, + "%s/%u", + nm_ip_address_get_address(addr), + nm_ip_address_get_prefix(addr))); + if (gw) + g_key_file_set_string(file, kf_group, "Gateway", gw); } - addr = nm_setting_ip_config_get_address(s_ip, 0); - g_key_file_set_string(file, - "IPv6", - "Address", - nm_sprintf_buf(buf, - "%s/%u", - nm_ip_address_get_address(addr), - nm_ip_address_get_prefix(addr))); - if (nm_setting_ip_config_get_gateway(s_ip)) - g_key_file_set_string(file, "IPv6", "Gateway", nm_setting_ip_config_get_gateway(s_ip)); return TRUE; } @@ -1759,13 +1740,15 @@ nm_wifi_utils_connection_to_iwd_config(NMConnection *connection, else if (cloned_mac_addr && nm_utils_hwaddr_valid(cloned_mac_addr, ETH_ALEN)) g_key_file_set_string(file, "Settings", "AddressOverride", cloned_mac_addr); - if (!ip4_config_to_iwd_config( + if (!ip_config_to_iwd_config( + AF_INET, file, NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip4_config(connection)), error)) return NULL; - if (!ip6_config_to_iwd_config( + if (!ip_config_to_iwd_config( + AF_INET6, file, NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip6_config(connection)), error)) |