summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/devices/wifi/nm-device-iwd.c44
-rw-r--r--src/devices/wifi/nm-iwd-manager.c52
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.c15
-rw-r--r--src/dns/nm-dns-manager.c34
-rw-r--r--src/dns/nm-dns-manager.h2
-rw-r--r--src/initrd/nmi-cmdline-reader.c28
-rw-r--r--src/initrd/tests/test-cmdline-reader.c56
-rw-r--r--src/nm-ip4-config.c32
-rw-r--r--src/nm-ip4-config.h18
-rw-r--r--src/nm-ip6-config.c21
-rw-r--r--src/nm-ip6-config.h3
-rw-r--r--src/platform/nm-linux-platform.c25
-rw-r--r--src/platform/tests/test-platform-general.c37
-rw-r--r--src/supplicant/nm-supplicant-interface.c11
-rw-r--r--src/supplicant/nm-supplicant-types.h2
15 files changed, 314 insertions, 66 deletions
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index fb113f83..64ac3de7 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -1952,36 +1952,39 @@ schedule_periodic_scan(NMDeviceIwd *self, gboolean initial_scan)
 {
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
     GVariant *          value;
-    gboolean            disconnected;
+    gboolean            disconnected = TRUE;
     guint               interval;
 
-    if (!priv->can_scan || priv->scan_requested)
-        return;
-
-    value        = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
-    disconnected = nm_streq0(get_variant_state(value), "disconnected");
-    g_variant_unref(value);
+    if (priv->can_scan) {
+        value        = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
+        disconnected = nm_streq0(get_variant_state(value), "disconnected");
+        g_variant_unref(value);
+    }
 
     /* Start scan immediately after a disconnect, mode change or
-     * device UP, otherwise wait a period dependent on the current
-     * state.
+     * device UP, otherwise wait 10 seconds.  When connected, update
+     * AP list mainly on UI requests.
      *
      * (initial_scan && disconnected) override priv->scanning below
      * because of an IWD quirk where a device will often be in the
      * autoconnect state and scanning at the time of our initial_scan,
-     * but our logic will the send it a Disconnect() causing IWD to
+     * but our logic will then send it a Disconnect() causing IWD to
      * exit autoconnect and interrupt the ongoing scan, meaning that
      * we still want a new scan ASAP.
      */
-    if (initial_scan && disconnected)
+    if (!priv->can_scan || !disconnected || priv->scan_requested || priv->scanning)
+        interval = -1;
+    else if (initial_scan)
         interval = 0;
-    else if (!priv->periodic_scan_id && !priv->scanning)
-        interval = disconnected ? 10 : 20;
+    else if (!priv->periodic_scan_id)
+        interval = 10;
     else
         return;
 
     nm_clear_g_source(&priv->periodic_scan_id);
-    priv->periodic_scan_id = g_timeout_add_seconds(interval, periodic_scan_timeout_cb, self);
+
+    if (interval != (guint) -1)
+        priv->periodic_scan_id = g_timeout_add_seconds(interval, periodic_scan_timeout_cb, self);
 }
 
 static void
@@ -2384,7 +2387,6 @@ powered_changed(NMDeviceIwd *self, gboolean new_powered)
         update_aps(self);
     } else {
         set_can_scan(self, FALSE);
-        nm_clear_g_source(&priv->periodic_scan_id);
         priv->scanning       = FALSE;
         priv->scan_requested = FALSE;
         priv->can_connect    = FALSE;
@@ -2600,17 +2602,7 @@ dispose(GObject *object)
 
     nm_clear_g_cancellable(&priv->cancellable);
 
-    nm_clear_g_source(&priv->periodic_scan_id);
-
-    cleanup_association_attempt(self, TRUE);
-
-    g_clear_object(&priv->dbus_device_proxy);
-    g_clear_object(&priv->dbus_station_proxy);
-    g_clear_object(&priv->dbus_ap_proxy);
-    g_clear_object(&priv->dbus_adhoc_proxy);
-    g_clear_object(&priv->dbus_obj);
-
-    remove_all_aps(self);
+    nm_device_iwd_set_dbus_object(self, NULL);
 
     G_OBJECT_CLASS(nm_device_iwd_parent_class)->dispose(object);
 
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index bdff20ac..6a562014 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -632,6 +632,38 @@ interface_removed(GDBusObjectManager *object_manager,
 }
 
 static void
+object_added(GDBusObjectManager *object_manager, GDBusObject *object, gpointer user_data)
+{
+    GList *interfaces, *iter;
+
+    interfaces = g_dbus_object_get_interfaces(object);
+
+    for (iter = interfaces; iter; iter = iter->next) {
+        GDBusInterface *interface = G_DBUS_INTERFACE(iter->data);
+
+        interface_added(NULL, object, interface, user_data);
+    }
+
+    g_list_free_full(interfaces, g_object_unref);
+}
+
+static void
+object_removed(GDBusObjectManager *object_manager, GDBusObject *object, gpointer user_data)
+{
+    GList *interfaces, *iter;
+
+    interfaces = g_dbus_object_get_interfaces(object);
+
+    for (iter = interfaces; iter; iter = iter->next) {
+        GDBusInterface *interface = G_DBUS_INTERFACE(iter->data);
+
+        interface_removed(NULL, object, interface, user_data);
+    }
+
+    g_list_free_full(interfaces, g_object_unref);
+}
+
+static void
 connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data)
 {
     NMIwdManager *       self = user_data;
@@ -697,22 +729,6 @@ _om_has_name_owner(GDBusObjectManager *object_manager)
 }
 
 static void
-object_added(NMIwdManager *self, GDBusObject *object)
-{
-    GList *interfaces, *iter;
-
-    interfaces = g_dbus_object_get_interfaces(object);
-
-    for (iter = interfaces; iter; iter = iter->next) {
-        GDBusInterface *interface = G_DBUS_INTERFACE(iter->data);
-
-        interface_added(NULL, object, interface, self);
-    }
-
-    g_list_free_full(interfaces, g_object_unref);
-}
-
-static void
 release_object_manager(NMIwdManager *self)
 {
     NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
@@ -852,12 +868,14 @@ got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data)
                          "interface-removed",
                          G_CALLBACK(interface_removed),
                          self);
+        g_signal_connect(priv->object_manager, "object-added", G_CALLBACK(object_added), self);
+        g_signal_connect(priv->object_manager, "object-removed", G_CALLBACK(object_removed), self);
 
         g_hash_table_remove_all(priv->known_networks);
 
         objects = g_dbus_object_manager_get_objects(object_manager);
         for (iter = objects; iter; iter = iter->next)
-            object_added(self, G_DBUS_OBJECT(iter->data));
+            object_added(NULL, G_DBUS_OBJECT(iter->data), self);
 
         g_list_free_full(objects, g_object_unref);
 
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.c b/src/devices/wifi/nm-wifi-p2p-peer.c
index fe6ad0a0..a194f168 100644
--- a/src/devices/wifi/nm-wifi-p2p-peer.c
+++ b/src/devices/wifi/nm-wifi-p2p-peer.c
@@ -47,7 +47,8 @@ struct _NMWifiP2PPeerPrivate {
     char *address;
 
     GBytes *wfd_ies;
-    char ** groups;
+
+    const char **groups;
 
     guint8 strength;
 
@@ -281,7 +282,7 @@ nm_wifi_p2p_peer_get_groups(const NMWifiP2PPeer *peer)
 {
     g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), NULL);
 
-    return (const char *const *) NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->groups;
+    return NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->groups;
 }
 
 const char *
@@ -404,6 +405,14 @@ nm_wifi_p2p_peer_update_from_properties(NMWifiP2PPeer *peer, const NMSupplicantP
     changed |= nm_wifi_p2p_peer_set_wfd_ies(peer, peer_info->ies);
     changed |= nm_wifi_p2p_peer_set_last_seen(peer, peer_info->last_seen_msec / 1000u);
 
+    /* We currently only use the groups information internally to check if
+     * the peer is still joined. */
+    if (!_nm_utils_strv_equal((char **) priv->groups, (char **) peer_info->groups)) {
+        g_free(priv->groups);
+        priv->groups = nm_utils_strv_dup_packed(peer_info->groups, -1);
+        changed |= TRUE;
+    }
+
     g_object_thaw_notify(G_OBJECT(peer));
 
     return changed;
@@ -566,7 +575,7 @@ finalize(GObject *object)
     g_free(priv->serial);
     g_free(priv->address);
     g_bytes_unref(priv->wfd_ies);
-    g_strfreev(priv->groups);
+    g_free(priv->groups);
 
     G_OBJECT_CLASS(nm_wifi_p2p_peer_parent_class)->finalize(object);
 }
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index 87187d69..635e94f6 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -1326,19 +1326,37 @@ static void
 rebuild_domain_lists(NMDnsManager *self)
 {
     NMDnsIPConfigData *ip_data;
-    gs_unref_hashtable GHashTable *ht                  = NULL;
-    gboolean                       default_route_found = FALSE;
+    gs_unref_hashtable GHashTable *ht               = NULL;
+    gs_unref_hashtable GHashTable *wildcard_entries = NULL;
     CList *                        head;
 
     head = _ip_config_lst_head(self);
     c_list_for_each_entry (ip_data, head, ip_config_lst) {
-        NMIPConfig *ip_config = ip_data->ip_config;
+        NMIPConfig *ip_config    = ip_data->ip_config;
+        gboolean    add_wildcard = FALSE;
 
         if (!nm_ip_config_get_num_nameservers(ip_config))
             continue;
-        if (nm_ip_config_best_default_route_get(ip_config)) {
-            default_route_found = TRUE;
-            break;
+        if (nm_ip_config_best_default_route_get(ip_config))
+            add_wildcard = TRUE;
+        else {
+            /* If a VPN has never-default=no but doesn't get a default
+             * route (this can happen for example when the server
+             * pushes routes with openconnect), and there are no
+             * search or routing domains, then the name servers pushed
+             * by the server would be unused. It is preferable in this
+             * case to use the VPN DNS server for all queries. */
+            if (ip_data->ip_config_type == NM_DNS_IP_CONFIG_TYPE_VPN
+                && !nm_ip_config_get_never_default(ip_data->ip_config)
+                && nm_ip_config_get_num_searches(ip_data->ip_config) == 0
+                && nm_ip_config_get_num_domains(ip_data->ip_config) == 0)
+                add_wildcard = TRUE;
+        }
+
+        if (add_wildcard) {
+            if (!wildcard_entries)
+                wildcard_entries = g_hash_table_new(nm_direct_hash, NULL);
+            g_hash_table_add(wildcard_entries, ip_data);
         }
     }
 
@@ -1373,13 +1391,13 @@ rebuild_domain_lists(NMDnsManager *self)
         /* Add wildcard lookup domain to connections with the default route.
          * If there is no default route, add the wildcard domain to all non-VPN
          * connections */
-        if (default_route_found) {
+        if (wildcard_entries) {
             /* FIXME: this heuristic of which device has a default route does
              * not work with policy routing (as used by default with WireGuard).
              * We should have a more stable mechanism where an NMIPConfig indicates
              * whether it is suitable for certain operations (like having an automatically
              * added "~" domain). */
-            if (nm_ip_config_best_default_route_get(ip_config))
+            if (g_hash_table_contains(wildcard_entries, ip_data))
                 domains[num_dom1++] = "~";
         } else {
             if (ip_data->ip_config_type != NM_DNS_IP_CONFIG_TYPE_VPN)
diff --git a/src/dns/nm-dns-manager.h b/src/dns/nm-dns-manager.h
index 3f90dc87..f91d1556 100644
--- a/src/dns/nm-dns-manager.h
+++ b/src/dns/nm-dns-manager.h
@@ -22,7 +22,7 @@ typedef enum {
 
 enum {
     NM_DNS_PRIORITY_DEFAULT_NORMAL = 100,
-    NM_DNS_PRIORITY_DEFAULT_VPN    = 50,
+    NM_DNS_PRIORITY_DEFAULT_VPN    = -50,
 };
 
 struct _NMDnsConfigData;
diff --git a/src/initrd/nmi-cmdline-reader.c b/src/initrd/nmi-cmdline-reader.c
index f2c60c25..5e610e15 100644
--- a/src/initrd/nmi-cmdline-reader.c
+++ b/src/initrd/nmi-cmdline-reader.c
@@ -137,10 +137,21 @@ reader_create_connection(Reader *                 reader,
                  multi_connect,
                  NULL);
 
-    if (mac) {
-        setting = nm_setting_wired_new();
+    if (nm_streq0(type_name, NM_SETTING_INFINIBAND_SETTING_NAME)) {
+        setting = nm_setting_infiniband_new();
         nm_connection_add_setting(connection, setting);
-        g_object_set(setting, NM_SETTING_WIRED_MAC_ADDRESS, mac, NULL);
+        g_object_set(setting, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL);
+    }
+
+    if (mac) {
+        if (nm_streq0(type_name, NM_SETTING_INFINIBAND_SETTING_NAME)) {
+            setting = (NMSetting *) nm_connection_get_setting_infiniband(connection);
+            g_object_set(setting, NM_SETTING_INFINIBAND_MAC_ADDRESS, mac, NULL);
+        } else {
+            setting = nm_setting_wired_new();
+            nm_connection_add_setting(connection, setting);
+            g_object_set(setting, NM_SETTING_WIRED_MAC_ADDRESS, mac, NULL);
+        }
     }
 
     return connection;
@@ -180,7 +191,7 @@ reader_get_connection(Reader *    reader,
         if (nm_utils_is_valid_iface_name(iface_spec, NULL))
             ifname = iface_spec;
         else {
-            mac = nm_utils_hwaddr_canonical(iface_spec, ETH_ALEN);
+            mac = nm_utils_hwaddr_canonical(iface_spec, -1);
             if (!mac)
                 _LOGW(LOGD_CORE, "invalid interface '%s'", iface_spec);
         }
@@ -219,8 +230,13 @@ reader_get_connection(Reader *    reader,
         if (!create_if_missing)
             return NULL;
 
-        if (!type_name)
-            type_name = NM_SETTING_WIRED_SETTING_NAME;
+        if (!type_name) {
+            if (NM_STR_HAS_PREFIX(ifname, "ib")
+                || (mac && nm_utils_hwaddr_valid(mac, INFINIBAND_ALEN)))
+                type_name = NM_SETTING_INFINIBAND_SETTING_NAME;
+            else
+                type_name = NM_SETTING_WIRED_SETTING_NAME;
+        }
 
         connection = reader_create_connection(reader,
                                               ifname ?: mac,
diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/initrd/tests/test-cmdline-reader.c
index 6de4dd9d..c3511332 100644
--- a/src/initrd/tests/test-cmdline-reader.c
+++ b/src/initrd/tests/test-cmdline-reader.c
@@ -1722,6 +1722,60 @@ test_dhcp_vendor_class_id(void)
     g_assert(nm_setting_ip4_config_get_dhcp_vendor_class_identifier(s_ip4) == NULL);
 }
 
+static void
+test_infiniband_iface(void)
+{
+    gs_unref_hashtable GHashTable *connections = NULL;
+    const char *const *            ARGV        = NM_MAKE_STRV("ip=ib1:dhcp");
+    NMConnection *                 connection;
+    NMSettingInfiniband *          s_ib;
+    gs_free char *                 hostname = NULL;
+
+    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
+    g_assert(connections);
+    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
+    g_assert_cmpstr(hostname, ==, NULL);
+
+    connection = g_hash_table_lookup(connections, "ib1");
+    g_assert(connection);
+    nmtst_assert_connection_verifies_without_normalization(connection);
+    g_assert_cmpstr(nm_connection_get_connection_type(connection),
+                    ==,
+                    NM_SETTING_INFINIBAND_SETTING_NAME);
+    s_ib = nm_connection_get_setting_infiniband(connection);
+    g_assert(s_ib);
+}
+
+static void
+test_infiniband_mac(void)
+{
+    gs_unref_hashtable GHashTable *connections = NULL;
+    const char *const *            ARGV =
+        NM_MAKE_STRV("ip=00-11-22-33-44-55-66-77-88-99-aa-bb-cc-dd-ee-ff-00-11-22-33:dhcp");
+    NMConnection *       connection;
+    NMSettingInfiniband *s_ib;
+    gs_free char *       hostname = NULL;
+
+    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
+    g_assert(connections);
+    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
+    g_assert_cmpstr(hostname, ==, NULL);
+
+    connection = g_hash_table_lookup(connections,
+                                     "00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33");
+    g_assert(connection);
+    nmtst_assert_connection_verifies_without_normalization(connection);
+    g_assert_cmpstr(nm_connection_get_connection_type(connection),
+                    ==,
+                    NM_SETTING_INFINIBAND_SETTING_NAME);
+    g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, NULL);
+    s_ib = nm_connection_get_setting_infiniband(connection);
+    g_assert(s_ib);
+    g_assert_cmpstr(nm_setting_infiniband_get_mac_address(s_ib),
+                    ==,
+                    "00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33");
+}
+
 NMTST_DEFINE();
 
 int
@@ -1762,6 +1816,8 @@ main(int argc, char **argv)
     g_test_add_func("/initrd/cmdline/bootif/off", test_bootif_off);
     g_test_add_func("/initrd/cmdline/neednet", test_neednet);
     g_test_add_func("/initrd/cmdline/dhcp/vendor_class_id", test_dhcp_vendor_class_id);
+    g_test_add_func("/initrd/cmdline/infiniband/iface", test_infiniband_iface);
+    g_test_add_func("/initrd/cmdline/infiniband/mac", test_infiniband_mac);
 
     return g_test_run();
 }
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index ea08450d..9d2b459d 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -283,6 +283,7 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMIP4Config,
 
 typedef struct {
     bool                     metered : 1;
+    bool                     never_default : 1;
     guint32                  mtu;
     int                      ifindex;
     NMIPConfigSource         mtu_source;
@@ -895,6 +896,8 @@ nm_ip4_config_merge_setting(NMIP4Config *            self,
     nm_ip4_config_mdns_set(self, mdns);
     nm_ip4_config_llmnr_set(self, llmnr);
 
+    nm_ip4_config_set_never_default(self, nm_setting_ip_config_get_never_default(setting));
+
     g_object_thaw_notify(G_OBJECT(self));
 }
 
@@ -1122,6 +1125,11 @@ nm_ip4_config_merge(NMIP4Config *        dst,
     nm_ip4_config_set_metered(dst,
                               nm_ip4_config_get_metered(dst) || nm_ip4_config_get_metered(src));
 
+    /* never default */
+    nm_ip4_config_set_never_default(dst,
+                                    nm_ip4_config_get_never_default(dst)
+                                        || nm_ip4_config_get_never_default(src));
+
     /* DNS priority */
     if (nm_ip4_config_get_dns_priority(src))
         nm_ip4_config_set_dns_priority(dst, nm_ip4_config_get_dns_priority(src));
@@ -1856,6 +1864,12 @@ nm_ip4_config_replace(NMIP4Config *dst, const NMIP4Config *src, gboolean *releva
         has_minor_changes = TRUE;
     }
 
+    /* never default */
+    if (src_priv->never_default != dst_priv->never_default) {
+        dst_priv->never_default = src_priv->never_default;
+        has_minor_changes       = TRUE;
+    }
+
 #if NM_MORE_ASSERTS
     /* config_equal does not compare *all* the fields, therefore, we might have has_minor_changes
      * regardless of config_equal. But config_equal must correspond to has_relevant_changes. */
@@ -2729,6 +2743,24 @@ nm_ip4_config_get_metered(const NMIP4Config *self)
 
 /*****************************************************************************/
 
+void
+nm_ip4_config_set_never_default(NMIP4Config *self, gboolean never_default)
+{
+    NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self);
+
+    priv->never_default = never_default;
+}
+
+gboolean
+nm_ip4_config_get_never_default(const NMIP4Config *self)
+{
+    const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self);
+
+    return priv->never_default;
+}
+
+/*****************************************************************************/
+
 const NMPObject *
 nm_ip4_config_nmpobj_lookup(const NMIP4Config *self, const NMPObject *needle)
 {
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index fcbdd714..ccfca74c 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -244,6 +244,9 @@ NMIPConfigSource nm_ip4_config_get_mtu_source(const NMIP4Config *self);
 void     nm_ip4_config_set_metered(NMIP4Config *self, gboolean metered);
 gboolean nm_ip4_config_get_metered(const NMIP4Config *self);
 
+void     nm_ip4_config_set_never_default(NMIP4Config *self, gboolean never_default);
+gboolean nm_ip4_config_get_never_default(const NMIP4Config *self);
+
 const NMPObject *nm_ip4_config_nmpobj_lookup(const NMIP4Config *self, const NMPObject *needle);
 gboolean         nm_ip4_config_nmpobj_remove(NMIP4Config *self, const NMPObject *needle);
 
@@ -519,6 +522,21 @@ nm_ip_config_set_config_flags(NMIPConfig *self, NMIPConfigFlags flags, NMIPConfi
                                 mask);
 }
 
+static inline gboolean
+nm_ip_config_get_never_default(const NMIPConfig *self)
+{
+    _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_never_default, nm_ip6_config_get_never_default);
+}
+
+static inline void
+nm_ip_config_set_never_default(NMIPConfig *self, gboolean never_default)
+{
+    _NM_IP_CONFIG_DISPATCH_VOID(self,
+                                nm_ip4_config_set_never_default,
+                                nm_ip6_config_set_never_default,
+                                never_default);
+}
+
 #define _NM_IP_CONFIG_DISPATCH_SET_OP(_return, dst, src, v4_func, v6_func, ...)               \
     G_STMT_START                                                                              \
     {                                                                                         \
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index bd250875..407aa1d6 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -64,6 +64,7 @@ typedef struct {
         NMDedupMultiIdxType         idx_ip6_routes;
     };
     NMIPConfigFlags config_flags;
+    bool            never_default : 1;
 } NMIP6ConfigPrivate;
 
 struct _NMIP6Config {
@@ -128,6 +129,24 @@ nm_ip6_config_set_privacy(NMIP6Config *self, NMSettingIP6ConfigPrivacy privacy)
 
 /*****************************************************************************/
 
+void
+nm_ip6_config_set_never_default(NMIP6Config *self, gboolean never_default)
+{
+    NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self);
+
+    priv->never_default = never_default;
+}
+
+gboolean
+nm_ip6_config_get_never_default(const NMIP6Config *self)
+{
+    const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self);
+
+    return priv->never_default;
+}
+
+/*****************************************************************************/
+
 const NMDedupMultiHeadEntry *
 nm_ip6_config_lookup_addresses(const NMIP6Config *self)
 {
@@ -635,6 +654,8 @@ nm_ip6_config_merge_setting(NMIP6Config *      self,
     if (priority)
         nm_ip6_config_set_dns_priority(self, priority);
 
+    nm_ip6_config_set_never_default(self, nm_setting_ip_config_get_never_default(setting));
+
     g_object_thaw_notify(G_OBJECT(self));
 }
 
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index 49046dc8..a5a28a1a 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -175,6 +175,9 @@ const char *nm_ip6_config_get_dns_option(const NMIP6Config *self, guint i);
 void nm_ip6_config_set_dns_priority(NMIP6Config *self, int priority);
 int  nm_ip6_config_get_dns_priority(const NMIP6Config *self);
 
+void     nm_ip6_config_set_never_default(NMIP6Config *self, gboolean never_default);
+gboolean nm_ip6_config_get_never_default(const NMIP6Config *self);
+
 const NMPObject *nm_ip6_config_nmpobj_lookup(const NMIP6Config *self, const NMPObject *needle);
 gboolean         nm_ip6_config_nmpobj_remove(NMIP6Config *self, const NMPObject *needle);
 
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 05125fa8..3fa70bbc 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -26,6 +26,7 @@
 #include <poll.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
+#include <sys/statvfs.h>
 #include <unistd.h>
 
 #include "nm-std-aux/unaligned.h"
@@ -9523,12 +9524,34 @@ constructed(GObject *_object)
     }
 }
 
+/* Similar to systemd's path_is_read_only_fs(), at
+ * https://github.com/systemd/systemd/blob/v246/src/basic/stat-util.c#L132 */
+static int
+path_is_read_only_fs(const char *path)
+{
+    struct statvfs st;
+
+    if (statvfs(path, &st) < 0)
+        return -errno;
+
+    if (st.f_flag & ST_RDONLY)
+        return TRUE;
+
+    /* On NFS, statvfs() might not reflect whether we can actually
+	 * write to the remote share. Let's try again with
+	 * access(W_OK) which is more reliable, at least sometimes. */
+    if (access(path, W_OK) < 0 && errno == EROFS)
+        return TRUE;
+
+    return FALSE;
+}
+
 NMPlatform *
 nm_linux_platform_new(gboolean log_with_ptr, gboolean netns_support)
 {
     gboolean use_udev = FALSE;
 
-    if (nmp_netns_is_initial() && access("/sys", W_OK) == 0)
+    if (nmp_netns_is_initial() && path_is_read_only_fs("/sys") == FALSE)
         use_udev = TRUE;
 
     return g_object_new(NM_TYPE_LINUX_PLATFORM,
diff --git a/src/platform/tests/test-platform-general.c b/src/platform/tests/test-platform-general.c
index f98f84c9..703c886e 100644
--- a/src/platform/tests/test-platform-general.c
+++ b/src/platform/tests/test-platform-general.c
@@ -555,6 +555,20 @@ test_platform_ip_address_pretty_sort_cmp(gconstpointer test_data)
     gs_free guint64 *rand_map    = NULL;
     gsize            i, j;
 
+#if !defined(__amd64__)
+    /* The test generates a random array of NMPlatformIPXAddress (by crudely randomizing the memory,
+     * not the structures themself) and then compares the sorted result with the expected output.
+     * The sole purpose is to ensure that the sorting order stays stable.
+     *
+     * This only works on an architecture for which the test was made, otherwise
+     * the expected data does not match (due to different layout of the structures
+     * in memory).
+     *
+     * That's fine. Skip the test. */
+    g_test_skip("skip test on non-amd64 architecture");
+    return;
+#endif
+
     /*
      * First we create a list of addresses filled with (stable) random bytes.
      * We tweak some fields explicitly (stable randomly), so that we cover all
@@ -692,14 +706,16 @@ test_platform_ip_address_pretty_sort_cmp(gconstpointer test_data)
     }
 
     if (PRINT_RESULT) {
-        g_print("\n\n\t\t[%d] = (\n", TEST_DATA_I);
+        g_print("\n        [%d] = (", TEST_DATA_I);
         for (i = 0; i < ELM_SIZE * N_ADDRESSES;) {
-            g_print("\t\t\t\"");
+            if (i > 0)
+                g_print("\n               ");
+            g_print("\"");
             for (j = 0; j < 40 && i < ELM_SIZE * N_ADDRESSES; j++, i++)
                 g_print("%02x", addresses[i]);
-            g_print("\"\n");
+            g_print("\"");
         }
-        g_print("\t\t),\n\n");
+        g_print("),\n");
         return;
     }
 
@@ -713,6 +729,19 @@ test_platform_ip_address_pretty_sort_cmp(gconstpointer test_data)
                                             NULL,
                                             0,
                                             &bin_len);
+
+        if (bin_len != ELM_SIZE * N_ADDRESSES || memcmp(addresses, bin_arr, bin_len) != 0) {
+            char *addresses_str = nm_utils_bin2hexstr(addresses, ELM_SIZE * N_ADDRESSES, -1);
+
+            g_error(">>> test_platform_ip_address_pretty_sort_cmp() will fail:\n"
+                    ">>> addresses[%zu]: %s\n"
+                    ">>> expected [%zu]: %s\n",
+                    ELM_SIZE * N_ADDRESSES,
+                    addresses_str,
+                    bin_len,
+                    EXPECTED_BUFFER[TEST_DATA_I]);
+        }
+
         g_assert_cmpmem(addresses, ELM_SIZE * N_ADDRESSES, bin_arr, bin_len);
     }
 }
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 1f6252f6..77254c7e 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -833,8 +833,11 @@ _peer_info_destroy(NMSupplicantPeerInfo *peer_info)
     g_free(peer_info->model);
     g_free(peer_info->model_number);
     g_free(peer_info->serial);
+    g_free(peer_info->groups);
     g_bytes_unref(peer_info->ies);
 
+    nm_ref_string_unref(peer_info->peer_path);
+
     nm_g_slice_free(peer_info);
 }
 
@@ -854,6 +857,7 @@ _peer_info_properties_changed(NMSupplicantInterface *self,
 {
     GVariant *    v_v;
     const char *  v_s;
+    const char ** v_strv;
     gint32        v_i32;
     const guint8 *arr_data;
     gsize         arr_len;
@@ -878,6 +882,13 @@ _peer_info_properties_changed(NMSupplicantInterface *self,
     if (nm_g_variant_lookup(properties, "Serial", "&s", &v_s))
         nm_utils_strdup_reset(&peer_info->serial, v_s);
 
+    if (nm_g_variant_lookup(properties, "Groups", "^a&o", &v_strv)) {
+        g_free(peer_info->groups);
+        peer_info->groups = nm_utils_strv_dup_packed(v_strv, -1);
+
+        g_free(v_strv);
+    }
+
     v_v = nm_g_variant_lookup_value(properties, "DeviceAddress", G_VARIANT_TYPE_BYTESTRING);
     if (v_v) {
         arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1);
diff --git a/src/supplicant/nm-supplicant-types.h b/src/supplicant/nm-supplicant-types.h
index dc1e3c35..e35059af 100644
--- a/src/supplicant/nm-supplicant-types.h
+++ b/src/supplicant/nm-supplicant-types.h
@@ -185,6 +185,8 @@ typedef struct _NMSupplicantPeerInfo {
     char *model_number;
     char *serial;
 
+    const char **groups;
+
     GBytes *ies;
 
     gint64 last_seen_msec;