about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/devices/nm-device.c15
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c23
-rw-r--r--src/core/devices/wwan/nm-modem-ofono.c17
-rw-r--r--src/core/dhcp/nm-dhcp-client.c1
-rw-r--r--src/core/ndisc/nm-ndisc.c19
-rw-r--r--src/core/nm-core-utils.c10
-rw-r--r--src/core/nm-l3-config-data.c54
-rw-r--r--src/core/nm-manager.c10
-rw-r--r--src/core/platform/nm-fake-platform.c2
-rw-r--r--src/core/platform/tests/test-common.c19
-rw-r--r--src/core/platform/tests/test-link.c133
-rw-r--r--src/core/settings/nm-settings-connection.c8
-rw-r--r--src/core/settings/nm-settings-connection.h2
-rw-r--r--src/core/supplicant/nm-supplicant-config.c7
-rw-r--r--src/core/supplicant/nm-supplicant-config.h6
-rw-r--r--src/core/supplicant/tests/test-supplicant-config.c2
-rw-r--r--src/libnm-client-aux-extern/nm-libnm-aux.c4
-rw-r--r--src/libnm-core-impl/nm-setting-ip-config.c8
-rw-r--r--src/libnm-core-impl/nm-setting-ip-tunnel.c8
-rw-r--r--src/libnm-core-impl/tests/test-keyfile.c4
-rw-r--r--src/libnm-core-public/nm-setting-ip-config.h1
-rw-r--r--src/libnm-core-public/nm-version-macros.h2
-rw-r--r--src/libnm-glib-aux/nm-random-utils.c68
-rw-r--r--src/libnm-glib-aux/nm-random-utils.h35
-rw-r--r--src/libnm-glib-aux/tests/test-shared-general.c50
-rw-r--r--src/libnm-platform/nm-linux-platform.c4
-rw-r--r--src/libnm-platform/nm-platform.c4
-rw-r--r--src/libnm-platform/nm-platform.h8
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils-nl80211.c45
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils-private.h5
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils-wext.c2
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils.c4
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils.h2
-rw-r--r--src/libnmc-base/nm-polkit-listener.c4
-rw-r--r--src/libnmc-setting/settings-docs.h6
-rw-r--r--src/libnmc-setting/settings-docs.h.in6
-rw-r--r--src/nm-cloud-setup/nm-http-client.c161
-rw-r--r--src/nm-cloud-setup/nm-http-client.h24
-rw-r--r--src/nm-cloud-setup/nmcs-provider-aliyun.c27
-rw-r--r--src/nm-cloud-setup/nmcs-provider-azure.c31
-rw-r--r--src/nm-cloud-setup/nmcs-provider-ec2.c82
-rw-r--r--src/nm-cloud-setup/nmcs-provider-gcp.c25
-rw-r--r--src/nm-cloud-setup/nmcs-provider.h28
-rw-r--r--src/nmcli/devices.c30
-rw-r--r--src/nmcli/gen-metadata-nm-settings-nmcli.xml.in6
45 files changed, 688 insertions, 324 deletions
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 3565c04d..d080b540 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -10339,14 +10339,14 @@ _dev_ipdhcpx_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_d
 
         if (notify_data->lease_update.accepted) {
             nm_manager_write_device_state(priv->manager, self, NULL);
+            nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_X(IS_IPv4),
+                                      self,
+                                      NULL,
+                                      NULL,
+                                      NULL,
+                                      NULL);
             if (priv->ipdhcp_data_x[IS_IPv4].state != NM_DEVICE_IP_STATE_READY) {
                 _dev_ipdhcpx_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY);
-                nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_X(IS_IPv4),
-                                          self,
-                                          NULL,
-                                          NULL,
-                                          NULL,
-                                          NULL);
                 _dev_ip_state_check_async(self, addr_family);
             }
         }
@@ -14542,6 +14542,9 @@ _set_unmanaged_flags(NMDevice           *self,
 
     if (transition_state) {
         new_state = was_managed ? NM_DEVICE_STATE_UNMANAGED : NM_DEVICE_STATE_UNAVAILABLE;
+        if (new_state == NM_DEVICE_STATE_UNMANAGED) {
+            _cancel_activation(self);
+        }
         if (now)
             nm_device_state_changed(self, new_state, reason);
         else
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index 171743d0..03625f8d 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -2912,10 +2912,11 @@ supplicant_connection_timeout_cb(gpointer user_data)
 }
 
 static NMSupplicantConfig *
-build_supplicant_config(NMDeviceWifi *self,
-                        NMConnection *connection,
-                        guint32       fixed_freq,
-                        GError      **error)
+build_supplicant_config(NMDeviceWifi         *self,
+                        NMSettingsConnection *sett_conn,
+                        NMConnection         *connection,
+                        guint32               fixed_freq,
+                        GError              **error)
 {
     NMDeviceWifiPrivate          *priv   = NM_DEVICE_WIFI_GET_PRIVATE(self);
     NMSupplicantConfig           *config = NULL;
@@ -2944,7 +2945,10 @@ build_supplicant_config(NMDeviceWifi *self,
         goto error;
     }
 
-    if (!nm_supplicant_config_add_bgscan(config, connection, error)) {
+    if (!nm_supplicant_config_add_bgscan(config,
+                                         connection,
+                                         nm_settings_connection_get_num_seen_bssids(sett_conn),
+                                         error)) {
         g_prefix_error(error, "bgscan: ");
         goto error;
     }
@@ -3227,7 +3231,8 @@ ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP
 
     freq = nm_platform_wifi_find_frequency(nm_device_get_platform(device),
                                            nm_device_get_ifindex(device),
-                                           rnd_freqs);
+                                           rnd_freqs,
+                                           TRUE);
     if (freq == 0)
         freq = rnd_freqs[0];
 
@@ -3278,6 +3283,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     NMActRequest                       *req;
     NMWifiAP                           *ap;
     NMConnection                       *connection;
+    NMSettingsConnection               *sett_conn;
     const char                         *setting_name;
     NMSettingWireless                  *s_wireless;
     GError                             *error = NULL;
@@ -3301,6 +3307,9 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
     ap_mode = nm_wifi_ap_get_mode(ap);
 
+    sett_conn = nm_act_request_get_settings_connection(req);
+    nm_assert(sett_conn);
+
     connection = nm_act_request_get_applied_connection(req);
     s_wireless = nm_connection_get_setting_wireless(connection);
     nm_assert(s_wireless);
@@ -3349,7 +3358,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         set_powersave(device);
 
     /* Build up the supplicant configuration */
-    config = build_supplicant_config(self, connection, nm_wifi_ap_get_freq(ap), &error);
+    config = build_supplicant_config(self, sett_conn, connection, nm_wifi_ap_get_freq(ap), &error);
     if (!config) {
         _LOGE(LOGD_DEVICE | LOGD_WIFI,
               "Activation: (wifi) couldn't build wireless configuration: %s",
diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c
index 33a19e93..de105bbd 100644
--- a/src/core/devices/wwan/nm-modem-ofono.c
+++ b/src/core/devices/wwan/nm-modem-ofono.c
@@ -1320,9 +1320,18 @@ handle_settings(NMModemOfono *self, GVariant *v_dict)
         }
     }
 
-    if (g_variant_lookup(v_dict, "MessageProxy", "&s", &s)) {
-        _LOGI("MessageProxy: %s", s);
-        if (s && nm_inet_parse_bin(AF_INET, s, NULL, &address_network)) {
+    if (g_variant_lookup(v_dict, "Proxy", "&s", &s) && s) {
+        gs_free char *proxy = g_strdup(s);
+        char         *colon;
+
+        _LOGI("(MMS) Proxy: %s", s);
+
+        /* Strip the port out. We can do this as we know this is IPv4. */
+        colon = strchr(proxy, ':');
+        if (colon)
+            *colon = '\0';
+
+        if (nm_inet_parse_bin(AF_INET, proxy, NULL, &address_network)) {
             const NMPlatformIP4Route mms_route = {
                 .network       = address_network,
                 .plen          = 32,
@@ -1335,7 +1344,7 @@ handle_settings(NMModemOfono *self, GVariant *v_dict)
 
             nm_l3_config_data_add_route_4(priv->l3cd_4, &mms_route);
         } else
-            _LOGW("invalid MessageProxy: %s", s);
+            _LOGW("invalid (MMS) Proxy: %s", s);
     }
 
     ret = TRUE;
diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c
index 1fc2d944..b10ce410 100644
--- a/src/core/dhcp/nm-dhcp-client.c
+++ b/src/core/dhcp/nm-dhcp-client.c
@@ -871,6 +871,7 @@ _nm_dhcp_client_notify(NMDhcpClient         *self,
         nm_clear_g_source_inst(&priv->previous_lease_timeout_source);
 
     nm_l3_config_data_reset(&priv->l3cd_curr, priv->l3cd_next);
+    priv->l3cfg_notify.wait_ipv6_dad = FALSE;
 
     if (client_event_type == NM_DHCP_CLIENT_EVENT_TYPE_BOUND && priv->l3cd_curr
         && nm_l3_config_data_get_num_addresses(priv->l3cd_curr, priv->config.addr_family) > 0)
diff --git a/src/core/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c
index 39a4df48..20dd2121 100644
--- a/src/core/ndisc/nm-ndisc.c
+++ b/src/core/ndisc/nm-ndisc.c
@@ -10,6 +10,7 @@
 #include <arpa/inet.h>
 #include <stdlib.h>
 
+#include "libnm-glib-aux/nm-random-utils.h"
 #include "libnm-platform/nm-platform-utils.h"
 #include "libnm-platform/nm-platform.h"
 #include "libnm-platform/nmp-netns.h"
@@ -858,7 +859,7 @@ solicit_retransmit_time_jitter(gint32 solicit_retransmit_time_msec)
     ten_percent = NM_MAX(1, solicit_retransmit_time_msec / 10);
 
     return solicit_retransmit_time_msec - ten_percent
-           + ((gint32) (g_random_int() % (2u * ((guint32) ten_percent))));
+           + ((gint32) (nm_random_u32() % (2u * ((guint32) ten_percent))));
 }
 
 static gboolean
@@ -936,7 +937,7 @@ solicit_timer_start(NMNDisc *ndisc)
      * a suitable delay in 2021. Wait only up to 250 msec instead. */
 
     delay_msec =
-        g_random_int() % ((guint32) (NM_NDISC_RFC4861_MAX_RTR_SOLICITATION_DELAY * 1000 / 4));
+        nm_random_u32() % ((guint32) (NM_NDISC_RFC4861_MAX_RTR_SOLICITATION_DELAY * 1000 / 4));
 
     _LOGD("solicit: schedule sending first solicitation (of %d) in %.3f seconds",
           priv->config.router_solicitations,
@@ -974,8 +975,9 @@ announce_router(NMNDisc *ndisc)
 
         /* Schedule next initial announcement retransmit. */
         priv->send_ra_id =
-            g_timeout_add_seconds(g_random_int_range(NM_NDISC_ROUTER_ADVERT_DELAY,
-                                                     NM_NDISC_ROUTER_ADVERT_INITIAL_INTERVAL),
+            g_timeout_add_seconds(nm_random_u64_range_full(NM_NDISC_ROUTER_ADVERT_DELAY,
+                                                           NM_NDISC_ROUTER_ADVERT_INITIAL_INTERVAL,
+                                                           FALSE),
                                   (GSourceFunc) announce_router,
                                   ndisc);
     } else {
@@ -1009,10 +1011,9 @@ announce_router_initial(NMNDisc *ndisc)
     /* Schedule the initial send rather early. Clamp the delay by minimal
      * delay and not the initial advert internal so that we start fast. */
     if (G_LIKELY(!priv->send_ra_id)) {
-        priv->send_ra_id =
-            g_timeout_add_seconds(g_random_int_range(0, NM_NDISC_ROUTER_ADVERT_DELAY),
-                                  (GSourceFunc) announce_router,
-                                  ndisc);
+        priv->send_ra_id = g_timeout_add_seconds(nm_random_u64_range(NM_NDISC_ROUTER_ADVERT_DELAY),
+                                                 (GSourceFunc) announce_router,
+                                                 ndisc);
     }
 }
 
@@ -1028,7 +1029,7 @@ announce_router_solicited(NMNDisc *ndisc)
         nm_clear_g_source(&priv->send_ra_id);
 
     if (!priv->send_ra_id) {
-        priv->send_ra_id = g_timeout_add(g_random_int_range(0, NM_NDISC_ROUTER_ADVERT_DELAY_MS),
+        priv->send_ra_id = g_timeout_add(nm_random_u64_range(NM_NDISC_ROUTER_ADVERT_DELAY_MS),
                                          (GSourceFunc) announce_router,
                                          ndisc);
     }
diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c
index 500bffb9..28d9a788 100644
--- a/src/core/nm-core-utils.c
+++ b/src/core/nm-core-utils.c
@@ -3673,9 +3673,7 @@ _hw_addr_eth_complete(struct ether_addr *addr,
 
     nm_assert((ouis == NULL) ^ (ouis_len != 0));
     if (ouis) {
-        /* g_random_int() is good enough here. It uses a static GRand instance
-         * that is seeded from /dev/urandom. */
-        oui = ouis[g_random_int() % ouis_len];
+        oui = ouis[nm_random_u64_range(ouis_len)];
         g_free(ouis);
     } else {
         if (!nm_utils_hwaddr_aton(current_mac_address, &oui, ETH_ALEN))
@@ -5156,11 +5154,11 @@ nm_utils_spawn_helper(const char *const  *args,
     g_source_attach(info->timeout_source, g_main_context_get_thread_default());
 
     /* Set file descriptors as non-blocking */
-    fd_flags = fcntl(info->child_stdin, F_GETFD, 0);
+    fd_flags = fcntl(info->child_stdin, F_GETFL, 0);
     fcntl(info->child_stdin, F_SETFL, fd_flags | O_NONBLOCK);
-    fd_flags = fcntl(info->child_stdout, F_GETFD, 0);
+    fd_flags = fcntl(info->child_stdout, F_GETFL, 0);
     fcntl(info->child_stdout, F_SETFL, fd_flags | O_NONBLOCK);
-    fd_flags = fcntl(info->child_stderr, F_GETFD, 0);
+    fd_flags = fcntl(info->child_stderr, F_GETFL, 0);
     fcntl(info->child_stderr, F_SETFL, fd_flags | O_NONBLOCK);
 
     /* Watch process stdin */
diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index 17bb9db8..d5dedb9c 100644
--- a/src/core/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -2297,35 +2297,37 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a,
         const NMPObject *def_route_a = a->best_default_route_x[IS_IPv4];
         const NMPObject *def_route_b = b->best_default_route_x[IS_IPv4];
 
-        NM_CMP_SELF(def_route_a, def_route_b);
-
-        if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES)) {
-            NM_CMP_RETURN(nmp_object_cmp_full(def_route_a,
-                                              def_route_b,
-                                              NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX)
-                                                  ? NMP_OBJECT_CMP_FLAGS_NONE
-                                                  : NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX));
-        } else if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES_ID)) {
-            if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX)) {
-                NM_CMP_DIRECT(def_route_a->obj_with_ifindex.ifindex,
-                              def_route_b->obj_with_ifindex.ifindex);
-            }
+        if (def_route_a != def_route_b) {
+            if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES)) {
+                NM_CMP_RETURN(
+                    nmp_object_cmp_full(def_route_a,
+                                        def_route_b,
+                                        NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX)
+                                            ? NMP_OBJECT_CMP_FLAGS_NONE
+                                            : NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX));
+            } else if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES_ID)) {
+                if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX)) {
+                    NM_CMP_DIRECT(def_route_a->obj_with_ifindex.ifindex,
+                                  def_route_b->obj_with_ifindex.ifindex);
+                }
 
-            if (IS_IPv4) {
-                NMPlatformIP4Route ra = def_route_a->ip4_route;
-                NMPlatformIP4Route rb = def_route_b->ip4_route;
+                if (IS_IPv4) {
+                    NMPlatformIP4Route ra = def_route_a->ip4_route;
+                    NMPlatformIP4Route rb = def_route_b->ip4_route;
 
-                NM_CMP_DIRECT(ra.metric, rb.metric);
-                NM_CMP_DIRECT(ra.plen, rb.plen);
-                NM_CMP_RETURN_DIRECT(nm_ip4_addr_same_prefix_cmp(ra.network, rb.network, ra.plen));
-            } else {
-                NMPlatformIP6Route ra = def_route_a->ip6_route;
-                NMPlatformIP6Route rb = def_route_b->ip6_route;
+                    NM_CMP_DIRECT(ra.metric, rb.metric);
+                    NM_CMP_DIRECT(ra.plen, rb.plen);
+                    NM_CMP_RETURN_DIRECT(
+                        nm_ip4_addr_same_prefix_cmp(ra.network, rb.network, ra.plen));
+                } else {
+                    NMPlatformIP6Route ra = def_route_a->ip6_route;
+                    NMPlatformIP6Route rb = def_route_b->ip6_route;
 
-                NM_CMP_DIRECT(ra.metric, rb.metric);
-                NM_CMP_DIRECT(ra.plen, rb.plen);
-                NM_CMP_RETURN_DIRECT(
-                    nm_ip6_addr_same_prefix_cmp(&ra.network, &rb.network, ra.plen));
+                    NM_CMP_DIRECT(ra.metric, rb.metric);
+                    NM_CMP_DIRECT(ra.plen, rb.plen);
+                    NM_CMP_RETURN_DIRECT(
+                        nm_ip6_addr_same_prefix_cmp(&ra.network, &rb.network, ra.plen));
+                }
             }
         }
 
diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c
index 66547294..19ca1d1e 100644
--- a/src/core/nm-manager.c
+++ b/src/core/nm-manager.c
@@ -2582,7 +2582,7 @@ _rfkill_update_system(NMManager *self, NMRfkillType rtype, gboolean enabled)
 
     nm_assert(NM_IN_SET(rtype, NM_RFKILL_TYPE_WLAN, NM_RFKILL_TYPE_WWAN));
 
-    fd = open("/dev/rfkill", O_RDWR | O_CLOEXEC);
+    fd = open("/dev/rfkill", O_RDWR | O_NONBLOCK | O_CLOEXEC);
     if (fd < 0) {
         if (errno == EACCES)
             _LOGW(LOGD_RFKILL,
@@ -2591,14 +2591,6 @@ _rfkill_update_system(NMManager *self, NMRfkillType rtype, gboolean enabled)
         return;
     }
 
-    if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) {
-        _LOGW(LOGD_RFKILL,
-              "rfkill: (%s): failed to set killswitch device for "
-              "non-blocking operation",
-              nm_rfkill_type_to_string(rtype));
-        return;
-    }
-
     memset(&event, 0, sizeof(event));
     event.op = KERN_RFKILL_OP_CHANGE_ALL;
     switch (rtype) {
diff --git a/src/core/platform/nm-fake-platform.c b/src/core/platform/nm-fake-platform.c
index c92d9aef..86d6bc5f 100644
--- a/src/core/platform/nm-fake-platform.c
+++ b/src/core/platform/nm-fake-platform.c
@@ -835,7 +835,7 @@ wifi_set_mode(NMPlatform *platform, int ifindex, _NM80211Mode mode)
 }
 
 static guint32
-wifi_find_frequency(NMPlatform *platform, int ifindex, const guint32 *freqs)
+wifi_find_frequency(NMPlatform *platform, int ifindex, const guint32 *freqs, gboolean ap)
 {
     return freqs[0];
 }
diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c
index 571fedfe..65bdfeae 100644
--- a/src/core/platform/tests/test-common.c
+++ b/src/core/platform/tests/test-common.c
@@ -2520,7 +2520,9 @@ nmtstp_link_ip6gre_add(NMPlatform                *platform,
     gboolean              success;
     char                  b1[NM_INET_ADDRSTRLEN];
     char                  b2[NM_INET_ADDRSTRLEN];
+    char                  encap[100];
     char                  tclass[20];
+    gboolean              encap_ignore;
     gboolean              tclass_inherit;
 
     g_assert(nm_utils_ifname_valid_kernel(name, NULL));
@@ -2530,6 +2532,7 @@ nmtstp_link_ip6gre_add(NMPlatform                *platform,
 
     _init_platform(&platform, external_command);
 
+again:
     if (external_command) {
         gs_free char *dev = NULL;
 
@@ -2538,9 +2541,11 @@ nmtstp_link_ip6gre_add(NMPlatform                *platform,
                 g_strdup_printf("dev %s", nm_platform_link_get_name(platform, lnk->parent_ifindex));
 
         tclass_inherit = NM_FLAGS_HAS(lnk->flags, IP6_TNL_F_USE_ORIG_TCLASS);
+        encap_ignore   = NM_FLAGS_HAS(lnk->flags, IP6_TNL_F_IGN_ENCAP_LIMIT);
 
         success = !nmtstp_run_command(
-            "ip link add %s type %s%s%s local %s remote %s ttl %u tclass %s flowlabel %x",
+            "ip link add %s type %s%s%s local %s remote %s ttl %u tclass %s%s "
+            "flowlabel %x",
             name,
             lnk->is_tap ? "ip6gretap" : "ip6gre",
             NM_PRINT_FMT_QUOTED2(dev, " ", dev, ""),
@@ -2548,7 +2553,19 @@ nmtstp_link_ip6gre_add(NMPlatform                *platform,
             nm_inet6_ntop(&lnk->remote, b2),
             lnk->ttl,
             tclass_inherit ? "inherit" : nm_sprintf_buf(tclass, "%02x", lnk->tclass),
+            encap_ignore
+                ? "encaplimit none"
+                : (lnk->encap_limit != 4 ? nm_sprintf_buf(encap, "encaplimit %u", lnk->encap_limit)
+                                         : ""),
             lnk->flow_label);
+
+        if (!success && (encap_ignore || lnk->encap_limit != 4)) {
+            /* The command failed. That might be because iproute2 does not support the
+             * encaplimit parameter yet. Retry, now with platform code. */
+            external_command = FALSE;
+            goto again;
+        }
+
         if (success) {
             pllink = nmtstp_assert_wait_for_link(platform,
                                                  name,
diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c
index cc1ec6d1..9a1eaf89 100644
--- a/src/core/platform/tests/test-link.c
+++ b/src/core/platform/tests/test-link.c
@@ -1439,12 +1439,25 @@ test_software_detect(gconstpointer user_data)
 
         module_loaded = nmtstp_ensure_module("ip6_gre");
 
-        lnk_ip6tnl.local          = nmtst_inet6_from_string("fd01::42");
-        lnk_ip6tnl.remote         = nmtst_inet6_from_string("fd01::aaaa");
-        lnk_ip6tnl.parent_ifindex = ifindex_parent;
-        lnk_ip6tnl.tclass         = 21;
-        lnk_ip6tnl.flow_label     = 1338;
-        lnk_ip6tnl.is_gre         = TRUE;
+        switch (test_data->test_mode) {
+        case 0:
+            lnk_ip6tnl.local          = nmtst_inet6_from_string("fd01::43");
+            lnk_ip6tnl.remote         = nmtst_inet6_from_string("fd01::aaaa");
+            lnk_ip6tnl.parent_ifindex = ifindex_parent;
+            lnk_ip6tnl.tclass         = 21;
+            lnk_ip6tnl.flow_label     = 1338;
+            lnk_ip6tnl.is_gre         = TRUE;
+            break;
+        case 1:
+            lnk_ip6tnl.local          = nmtst_inet6_from_string("fd01::44");
+            lnk_ip6tnl.remote         = nmtst_inet6_from_string("fd01::aaab");
+            lnk_ip6tnl.parent_ifindex = ifindex_parent;
+            lnk_ip6tnl.tclass         = 0;
+            lnk_ip6tnl.flow_label     = 1339;
+            lnk_ip6tnl.is_gre         = TRUE;
+            lnk_ip6tnl.flags          = IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS;
+            break;
+        }
 
         if (!nmtstp_link_ip6gre_add(NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) {
             if (!module_loaded) {
@@ -1463,14 +1476,30 @@ test_software_detect(gconstpointer user_data)
 
         module_loaded = nmtstp_ensure_module("ip6_gre");
 
-        lnk_ip6tnl.local          = nmtst_inet6_from_string("fe80::abcd");
-        lnk_ip6tnl.remote         = nmtst_inet6_from_string("fc01::bbbb");
-        lnk_ip6tnl.parent_ifindex = ifindex_parent;
-        lnk_ip6tnl.ttl            = 10;
-        lnk_ip6tnl.tclass         = 22;
-        lnk_ip6tnl.flow_label     = 1339;
-        lnk_ip6tnl.is_gre         = TRUE;
-        lnk_ip6tnl.is_tap         = TRUE;
+        switch (test_data->test_mode) {
+        case 0:
+            lnk_ip6tnl.local          = nmtst_inet6_from_string("fe80::abcd");
+            lnk_ip6tnl.remote         = nmtst_inet6_from_string("fc01::bbbb");
+            lnk_ip6tnl.parent_ifindex = ifindex_parent;
+            lnk_ip6tnl.ttl            = 10;
+            lnk_ip6tnl.tclass         = 23;
+            lnk_ip6tnl.flow_label     = 1340;
+            lnk_ip6tnl.is_gre         = TRUE;
+            lnk_ip6tnl.is_tap         = TRUE;
+            break;
+        case 1:
+            lnk_ip6tnl.local          = nmtst_inet6_from_string("fe80::abce");
+            lnk_ip6tnl.remote         = nmtst_inet6_from_string("fc01::bbbc");
+            lnk_ip6tnl.parent_ifindex = ifindex_parent;
+            lnk_ip6tnl.ttl            = 10;
+            lnk_ip6tnl.tclass         = 0;
+            lnk_ip6tnl.flow_label     = 1341;
+            lnk_ip6tnl.is_gre         = TRUE;
+            lnk_ip6tnl.is_tap         = TRUE;
+            lnk_ip6tnl.encap_limit    = 4;
+            lnk_ip6tnl.flags          = IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS;
+            break;
+        }
 
         if (!nmtstp_link_ip6gre_add(NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) {
             if (!module_loaded) {
@@ -1851,29 +1880,65 @@ test_software_detect(gconstpointer user_data)
         {
             const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl;
 
-            g_assert(plnk == nm_platform_link_get_lnk_ip6gre(NM_PLATFORM_GET, ifindex, NULL));
-            g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent);
-            nmtst_assert_ip6_address(&plnk->local, "fd01::42");
-            nmtst_assert_ip6_address(&plnk->remote, "fd01::aaaa");
-            g_assert_cmpint(plnk->tclass, ==, 21);
-            g_assert_cmpint(plnk->flow_label, ==, 1338);
-            g_assert_cmpint(plnk->is_gre, ==, TRUE);
-            g_assert_cmpint(plnk->is_tap, ==, FALSE);
+            switch (test_data->test_mode) {
+            case 0:
+                g_assert(plnk == nm_platform_link_get_lnk_ip6gre(NM_PLATFORM_GET, ifindex, NULL));
+                g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent);
+                nmtst_assert_ip6_address(&plnk->local, "fd01::43");
+                nmtst_assert_ip6_address(&plnk->remote, "fd01::aaaa");
+                g_assert_cmpint(plnk->tclass, ==, 21);
+                g_assert_cmpint(plnk->flow_label, ==, 1338);
+                g_assert_cmpint(plnk->is_gre, ==, TRUE);
+                g_assert_cmpint(plnk->is_tap, ==, FALSE);
+                break;
+            case 1:
+                g_assert(plnk == nm_platform_link_get_lnk_ip6gre(NM_PLATFORM_GET, ifindex, NULL));
+                g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent);
+                nmtst_assert_ip6_address(&plnk->local, "fd01::44");
+                nmtst_assert_ip6_address(&plnk->remote, "fd01::aaab");
+                g_assert_cmpint(plnk->flow_label, ==, 1339);
+                g_assert_cmpint(plnk->is_gre, ==, TRUE);
+                g_assert_cmpint(plnk->is_tap, ==, FALSE);
+                g_assert_cmpint(plnk->flags & 0xFFFF, /* ignore kernel internal flags */
+                                ==,
+                                IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS);
+                break;
+            }
+
             break;
         }
         case NM_LINK_TYPE_IP6GRETAP:
         {
             const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl;
 
-            g_assert(plnk == nm_platform_link_get_lnk_ip6gretap(NM_PLATFORM_GET, ifindex, NULL));
-            g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent);
-            nmtst_assert_ip6_address(&plnk->local, "fe80::abcd");
-            nmtst_assert_ip6_address(&plnk->remote, "fc01::bbbb");
-            g_assert_cmpint(plnk->ttl, ==, 10);
-            g_assert_cmpint(plnk->tclass, ==, 22);
-            g_assert_cmpint(plnk->flow_label, ==, 1339);
-            g_assert_cmpint(plnk->is_gre, ==, TRUE);
-            g_assert_cmpint(plnk->is_tap, ==, TRUE);
+            switch (test_data->test_mode) {
+            case 0:
+                g_assert(plnk
+                         == nm_platform_link_get_lnk_ip6gretap(NM_PLATFORM_GET, ifindex, NULL));
+                g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent);
+                nmtst_assert_ip6_address(&plnk->local, "fe80::abcd");
+                nmtst_assert_ip6_address(&plnk->remote, "fc01::bbbb");
+                g_assert_cmpint(plnk->ttl, ==, 10);
+                g_assert_cmpint(plnk->tclass, ==, 23);
+                g_assert_cmpint(plnk->flow_label, ==, 1340);
+                g_assert_cmpint(plnk->is_gre, ==, TRUE);
+                g_assert_cmpint(plnk->is_tap, ==, TRUE);
+                break;
+            case 1:
+                g_assert(plnk
+                         == nm_platform_link_get_lnk_ip6gretap(NM_PLATFORM_GET, ifindex, NULL));
+                g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent);
+                nmtst_assert_ip6_address(&plnk->local, "fe80::abce");
+                nmtst_assert_ip6_address(&plnk->remote, "fc01::bbbc");
+                g_assert_cmpint(plnk->ttl, ==, 10);
+                g_assert_cmpint(plnk->flow_label, ==, 1341);
+                g_assert_cmpint(plnk->is_gre, ==, TRUE);
+                g_assert_cmpint(plnk->is_tap, ==, TRUE);
+                g_assert_cmpint(plnk->flags & 0xFFFF, /* ignore kernel internal flags */
+                                ==,
+                                IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS);
+                break;
+            }
             break;
         }
         case NM_LINK_TYPE_IPIP:
@@ -3888,8 +3953,10 @@ _nmtstp_setup_tests(void)
         test_software_detect_add("/link/software/detect/gretap", NM_LINK_TYPE_GRETAP, 0);
         test_software_detect_add("/link/software/detect/ip6tnl/0", NM_LINK_TYPE_IP6TNL, 0);
         test_software_detect_add("/link/software/detect/ip6tnl/1", NM_LINK_TYPE_IP6TNL, 1);
-        test_software_detect_add("/link/software/detect/ip6gre", NM_LINK_TYPE_IP6GRE, 0);
-        test_software_detect_add("/link/software/detect/ip6gretap", NM_LINK_TYPE_IP6GRETAP, 0);
+        test_software_detect_add("/link/software/detect/ip6gre/0", NM_LINK_TYPE_IP6GRE, 0);
+        test_software_detect_add("/link/software/detect/ip6gre/1", NM_LINK_TYPE_IP6GRE, 1);
+        test_software_detect_add("/link/software/detect/ip6gretap/0", NM_LINK_TYPE_IP6GRETAP, 0);
+        test_software_detect_add("/link/software/detect/ip6gretap/1", NM_LINK_TYPE_IP6GRETAP, 1);
         test_software_detect_add("/link/software/detect/ipip", NM_LINK_TYPE_IPIP, 0);
         test_software_detect_add("/link/software/detect/macvlan", NM_LINK_TYPE_MACVLAN, 0);
         test_software_detect_add("/link/software/detect/macvtap", NM_LINK_TYPE_MACVTAP, 0);
diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c
index 1638efcd..024c0009 100644
--- a/src/core/settings/nm-settings-connection.c
+++ b/src/core/settings/nm-settings-connection.c
@@ -2532,6 +2532,14 @@ nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const char *se
     nm_key_file_db_set_string_list(priv->kf_db_seen_bssids, connection_uuid, seen_bssids_strv, i);
 }
 
+guint
+nm_settings_connection_get_num_seen_bssids(NMSettingsConnection *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), 0);
+
+    return nm_g_hash_table_size(NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->seen_bssids_hash);
+}
+
 /*****************************************************************************/
 
 /**
diff --git a/src/core/settings/nm-settings-connection.h b/src/core/settings/nm-settings-connection.h
index 47599c3d..fce7497c 100644
--- a/src/core/settings/nm-settings-connection.h
+++ b/src/core/settings/nm-settings-connection.h
@@ -345,6 +345,8 @@ gboolean nm_settings_connection_has_seen_bssid(NMSettingsConnection *self, const
 
 void nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const char *seen_bssid);
 
+guint nm_settings_connection_get_num_seen_bssids(NMSettingsConnection *self);
+
 int  nm_settings_connection_autoconnect_retries_get(NMSettingsConnection *self);
 void nm_settings_connection_autoconnect_retries_set(NMSettingsConnection *self, int retries);
 void nm_settings_connection_autoconnect_retries_reset(NMSettingsConnection *self);
diff --git a/src/core/supplicant/nm-supplicant-config.c b/src/core/supplicant/nm-supplicant-config.c
index c63a0058..1d9372e0 100644
--- a/src/core/supplicant/nm-supplicant-config.c
+++ b/src/core/supplicant/nm-supplicant-config.c
@@ -592,7 +592,10 @@ nm_supplicant_config_add_setting_wireless(NMSupplicantConfig *self,
 }
 
 gboolean
-nm_supplicant_config_add_bgscan(NMSupplicantConfig *self, NMConnection *connection, GError **error)
+nm_supplicant_config_add_bgscan(NMSupplicantConfig *self,
+                                NMConnection       *connection,
+                                guint               num_seen_bssids,
+                                GError            **error)
 {
     NMSettingWireless         *s_wifi;
     NMSettingWirelessSecurity *s_wsec;
@@ -629,7 +632,7 @@ nm_supplicant_config_add_bgscan(NMSupplicantConfig *self, NMConnection *connecti
      * when the signal is still somewhat OK so we have an up-to-date roam
      * candidate list when the signal gets bad.
      */
-    if (nm_setting_wireless_get_num_seen_bssids(s_wifi) > 1
+    if (num_seen_bssids > 1u
         || ((s_wsec = nm_connection_get_setting_wireless_security(connection))
             && NM_IN_STRSET(nm_setting_wireless_security_get_key_mgmt(s_wsec),
                             "ieee8021x",
diff --git a/src/core/supplicant/nm-supplicant-config.h b/src/core/supplicant/nm-supplicant-config.h
index 063f7f55..585cf958 100644
--- a/src/core/supplicant/nm-supplicant-config.h
+++ b/src/core/supplicant/nm-supplicant-config.h
@@ -44,8 +44,10 @@ gboolean nm_supplicant_config_add_setting_wireless(NMSupplicantConfig *self,
                                                    guint32             fixed_freq,
                                                    GError            **error);
 
-gboolean
-nm_supplicant_config_add_bgscan(NMSupplicantConfig *self, NMConnection *connection, GError **error);
+gboolean nm_supplicant_config_add_bgscan(NMSupplicantConfig *self,
+                                         NMConnection       *connection,
+                                         guint               num_seen_bssids,
+                                         GError            **error);
 
 gboolean nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig        *self,
                                                             NMSettingWirelessSecurity *setting,
diff --git a/src/core/supplicant/tests/test-supplicant-config.c b/src/core/supplicant/tests/test-supplicant-config.c
index 237b1a96..1ca5b26e 100644
--- a/src/core/supplicant/tests/test-supplicant-config.c
+++ b/src/core/supplicant/tests/test-supplicant-config.c
@@ -127,7 +127,7 @@ build_supplicant_config(NMConnection  *connection,
     g_assert_no_error(error);
     g_assert(success);
 
-    success = nm_supplicant_config_add_bgscan(config, connection, &error);
+    success = nm_supplicant_config_add_bgscan(config, connection, 0, &error);
     g_assert_no_error(error);
     g_assert(success);
 
diff --git a/src/libnm-client-aux-extern/nm-libnm-aux.c b/src/libnm-client-aux-extern/nm-libnm-aux.c
index 82a75de6..5855bc29 100644
--- a/src/libnm-client-aux-extern/nm-libnm-aux.c
+++ b/src/libnm-client-aux-extern/nm-libnm-aux.c
@@ -4,6 +4,8 @@
 
 #include "nm-libnm-aux.h"
 
+#include "libnm-glib-aux/nm-random-utils.h"
+
 /*****************************************************************************/
 
 NMClient *
@@ -101,7 +103,7 @@ nmc_client_new_waitsync(GCancellable *cancellable,
      * code no longer uses that, we hardly test those code paths. But they should
      * work just the same. Randomly use instead the sync initialization in a debug
      * build... */
-    if ((g_random_int() % 2) == 0) {
+    if (nm_random_bool()) {
         gboolean success;
 
         va_start(ap, first_property_name);
diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c
index 5d947e29..3e49eb15 100644
--- a/src/libnm-core-impl/nm-setting-ip-config.c
+++ b/src/libnm-core-impl/nm-setting-ip-config.c
@@ -51,6 +51,7 @@ const NMUtilsDNSOptionDesc _nm_utils_dns_option_descs[] = {
     {NM_SETTING_DNS_OPTION_USE_VC, FALSE, FALSE},
     {NM_SETTING_DNS_OPTION_NO_RELOAD, FALSE, FALSE},
     {NM_SETTING_DNS_OPTION_TRUST_AD, FALSE, FALSE},
+    {NM_SETTING_DNS_OPTION_NO_AAAA, FALSE, FALSE},
     {NULL, FALSE, FALSE}};
 
 static char *
@@ -6370,9 +6371,10 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      * distinct from an empty list of properties.
      *
      * The currently supported options are "attempts", "debug", "edns0",
-     * "inet6", "ip6-bytestring", "ip6-dotint", "ndots", "no-check-names",
-     * "no-ip6-dotint", "no-reload", "no-tld-query", "rotate", "single-request",
-     * "single-request-reopen", "timeout", "trust-ad", "use-vc".
+     * "inet6", "ip6-bytestring", "ip6-dotint", "ndots", "no-aaaa",
+     * "no-check-names", "no-ip6-dotint", "no-reload", "no-tld-query",
+     * "rotate", "single-request", "single-request-reopen", "timeout",
+     * "trust-ad", "use-vc".
      *
      * The "trust-ad" setting is only honored if the profile contributes
      * name servers to resolv.conf, and if all contributing profiles have
diff --git a/src/libnm-core-impl/nm-setting-ip-tunnel.c b/src/libnm-core-impl/nm-setting-ip-tunnel.c
index 7fb8b017..d28eb73f 100644
--- a/src/libnm-core-impl/nm-setting-ip-tunnel.c
+++ b/src/libnm-core-impl/nm-setting-ip-tunnel.c
@@ -491,7 +491,11 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     }
 
     flags = priv->flags;
-    if (NM_IN_SET(priv->mode, NM_IP_TUNNEL_MODE_IPIP6, NM_IP_TUNNEL_MODE_IP6IP6))
+    if (NM_IN_SET(priv->mode,
+                  NM_IP_TUNNEL_MODE_IPIP6,
+                  NM_IP_TUNNEL_MODE_IP6IP6,
+                  NM_IP_TUNNEL_MODE_IP6GRE,
+                  NM_IP_TUNNEL_MODE_IP6GRETAP))
         flags &= (guint32) (~_NM_IP_TUNNEL_FLAG_ALL_IP6TNL);
     if (flags) {
         g_set_error(error,
@@ -727,7 +731,7 @@ nm_setting_ip_tunnel_class_init(NMSettingIPTunnelClass *klass)
      * NMSettingIPTunnel:encapsulation-limit:
      *
      * How many additional levels of encapsulation are permitted to be prepended
-     * to packets. This property applies only to IPv6 tunnels.
+     * to packets. This property applies only to IPv6 tunnels. To disable this option, add %NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT to ip-tunnel flags.
      *
      * Since: 1.2
      **/
diff --git a/src/libnm-core-impl/tests/test-keyfile.c b/src/libnm-core-impl/tests/test-keyfile.c
index c163c429..2b21e583 100644
--- a/src/libnm-core-impl/tests/test-keyfile.c
+++ b/src/libnm-core-impl/tests/test-keyfile.c
@@ -901,8 +901,8 @@ _invalid_option_write_handler(NMConnection         *connection,
                               NMKeyfileHandlerData *handler_data,
                               void                 *user_data)
 {
-    InvalidOptionWriteData *data = user_data;
-    const char             *message;
+    InvalidOptionWriteData *data    = user_data;
+    const char             *message = NULL;
     NMKeyfileWarnSeverity   severity;
 
     g_assert(data);
diff --git a/src/libnm-core-public/nm-setting-ip-config.h b/src/libnm-core-public/nm-setting-ip-config.h
index f3bdc796..7e1d3889 100644
--- a/src/libnm-core-public/nm-setting-ip-config.h
+++ b/src/libnm-core-public/nm-setting-ip-config.h
@@ -362,6 +362,7 @@ char *nm_ip_routing_rule_to_string(const NMIPRoutingRule       *self,
 #define NM_SETTING_DNS_OPTION_USE_VC                "use-vc"
 #define NM_SETTING_DNS_OPTION_NO_RELOAD             "no-reload"
 #define NM_SETTING_DNS_OPTION_TRUST_AD              "trust-ad"
+#define NM_SETTING_DNS_OPTION_NO_AAAA               "no-aaaa"
 
 typedef struct _NMSettingIPConfigClass NMSettingIPConfigClass;
 
diff --git a/src/libnm-core-public/nm-version-macros.h b/src/libnm-core-public/nm-version-macros.h
index 159d3bd8..80e7f251 100644
--- a/src/libnm-core-public/nm-version-macros.h
+++ b/src/libnm-core-public/nm-version-macros.h
@@ -30,7 +30,7 @@
  * Evaluates to the micro version number of NetworkManager which this source
  * compiled against.
  */
-#define NM_MICRO_VERSION (4)
+#define NM_MICRO_VERSION (6)
 
 /**
  * NM_CHECK_VERSION:
diff --git a/src/libnm-glib-aux/nm-random-utils.c b/src/libnm-glib-aux/nm-random-utils.c
index 93eee7c4..2050d2f9 100644
--- a/src/libnm-glib-aux/nm-random-utils.c
+++ b/src/libnm-glib-aux/nm-random-utils.c
@@ -187,9 +187,9 @@ _bad_random_init_seed(BadRandSeed *seed)
     int           seed_idx;
     GRand        *rand;
 
-    /* g_rand_new() reads /dev/urandom, but we already noticed that
+    /* g_rand_new() reads /dev/urandom too, but we already know that
      * /dev/urandom fails to give us good randomness (which is why
-     * we hit the "bad randomness" code path). So this may not be as
+     * we hit the "bad random" code path). So this may not be as
      * good as we wish, but let's hope that it it does something smart
      * to give some extra entropy... */
     rand = g_rand_new();
@@ -231,14 +231,14 @@ _bad_random_bytes(guint8 *buf, gsize n)
      * to give us good randomness. Try our best.
      *
      * Our ability to get entropy for the CPRNG is very limited and thus the overall
-     * result will not be good randomness. See _bad_random_init_seed().
+     * result will be bad randomness.
      *
      * Once we have some seed material, we combine GRand (which is not a cryptographically
      * secure PRNG) with some iterative sha256 hashing. It would be nice if we had
      * easy access to chacha20, but it's probably more cumbersome to fork those
      * implementations than hack a bad CPRNG by using sha256 hashing. After all, this
-     * is fallback code to get *some* randomness. And with the inability to get a good
-     * seed, the CPRNG is not going to give us truly good randomness. */
+     * is fallback code to get *some* bad randomness. And with the inability to get a good
+     * seed, any CPRNG can only give us bad randomness. */
 
     {
         static BadRandState gl_state;
@@ -277,11 +277,11 @@ _bad_random_bytes(guint8 *buf, gsize n)
             nm_utils_checksum_get_digest(sum, gl_state.sha_digest.full);
 
             /* gl_state.sha_digest.full and gl_state.rand_vals contain now our
-             * random values, but they are also the state for the next iteration.
+             * bad random values, but they are also the state for the next iteration.
              * We must not directly expose that state to the caller, so XOR the values.
              *
-             * That means, per iteration we can generate 16 bytes of randomness. That
-             * is for example required to generate a random UUID. */
+             * That means, per iteration we can generate 16 bytes of bad randomness. That
+             * is suitable to initialize a random UUID. */
             for (i = 0; i < (int) (NM_UTILS_CHECKSUM_LENGTH_SHA256 / 2); i++) {
                 nm_assert(n > 0);
                 buf[0] = gl_state.sha_digest.half_1[i] ^ gl_state.sha_digest.half_2[i]
@@ -447,3 +447,55 @@ again_getrandom:
 
     return nm_utils_fd_read_loop_exact(fd, p, n, FALSE);
 }
+
+/*****************************************************************************/
+
+guint64
+nm_random_u64_range_full(guint64 begin, guint64 end, gboolean crypto_bytes)
+{
+    gboolean bad_crypto_bytes = FALSE;
+    guint64  remainder;
+    guint64  maxvalue;
+    guint64  x;
+    guint64  m;
+
+    /* Returns a random #guint64 equally distributed in the range [@begin..@end-1].
+     *
+     * The function always set errno. It either sets it to zero or to EAGAIN
+     * (if crypto_bytes were requested but not obtained). In any case, the function
+     * will always return a random number in the requested range (worst case, it's
+     * not crypto_bytes despite being requested). Check errno if you care. */
+
+    if (begin >= end) {
+        /* systemd's random_u64_range(0) is an alias for nm_random_u64().
+         * Not for us. It's a caller error to request an element from an empty range. */
+        return nm_assert_unreachable_val(begin);
+    }
+
+    m = end - begin;
+
+    if (m == 1) {
+        x = 0;
+        goto out;
+    }
+
+    remainder = G_MAXUINT64 % m;
+    maxvalue  = G_MAXUINT64 - remainder;
+
+    do
+        if (crypto_bytes) {
+            if (nm_random_get_crypto_bytes(&x, sizeof(x)) < 0) {
+                /* Cannot get good crypto numbers. We will try our best, but fail
+                 * and set errno below. */
+                crypto_bytes     = FALSE;
+                bad_crypto_bytes = TRUE;
+                continue;
+            }
+        } else
+            nm_random_get_bytes(&x, sizeof(x));
+    while (x >= maxvalue);
+
+out:
+    errno = bad_crypto_bytes ? EAGAIN : 0;
+    return begin + (x % m);
+}
diff --git a/src/libnm-glib-aux/nm-random-utils.h b/src/libnm-glib-aux/nm-random-utils.h
index ab8aee1b..729d71a4 100644
--- a/src/libnm-glib-aux/nm-random-utils.h
+++ b/src/libnm-glib-aux/nm-random-utils.h
@@ -16,4 +16,39 @@ nm_random_get_bytes(void *p, size_t n)
 
 int nm_random_get_crypto_bytes(void *p, size_t n);
 
+static inline guint32
+nm_random_u32(void)
+{
+    guint32 v;
+
+    nm_random_get_bytes(&v, sizeof(v));
+    return v;
+}
+
+static inline guint64
+nm_random_u64(void)
+{
+    guint64 v;
+
+    nm_random_get_bytes(&v, sizeof(v));
+    return v;
+}
+
+static inline bool
+nm_random_bool(void)
+{
+    guint8 ch;
+
+    nm_random_get_bytes(&ch, sizeof(ch));
+    return ch % 2u;
+}
+
+guint64 nm_random_u64_range_full(guint64 begin, guint64 end, gboolean crypto_bytes);
+
+static inline guint64
+nm_random_u64_range(guint64 end)
+{
+    return nm_random_u64_range_full(0, end, FALSE);
+}
+
 #endif /* __NM_RANDOM_UTILS_H__ */
diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c
index 7503dc9b..3eaca547 100644
--- a/src/libnm-glib-aux/tests/test-shared-general.c
+++ b/src/libnm-glib-aux/tests/test-shared-general.c
@@ -137,6 +137,55 @@ test_nmhash(void)
 
 /*****************************************************************************/
 
+static void
+test_nm_random(void)
+{
+    int i_run;
+
+    for (i_run = 0; i_run < 1000; i_run++) {
+        guint64 begin;
+        guint64 end;
+        guint64 m;
+        guint64 x;
+
+        m = nmtst_get_rand_uint64();
+        m = m >> (nmtst_get_rand_uint32() % 64);
+
+        if (m == 0)
+            continue;
+
+        switch (nmtst_get_rand_uint32() % 4) {
+        case 0:
+            begin = 0;
+            break;
+        case 1:
+            begin = nmtst_get_rand_uint64() % 1000;
+            break;
+        case 2:
+            begin = ((G_MAXUINT64 - m) - 500) + (nmtst_get_rand_uint64() % 1000);
+            break;
+        default:
+            begin = nmtst_get_rand_uint64() % (G_MAXUINT64 - m);
+            break;
+        }
+
+        end = (begin + m) - 10 + (nmtst_get_rand_uint64() % 5);
+
+        if (begin >= end)
+            continue;
+
+        if (begin == 0 && nmtst_get_rand_bool())
+            x = nm_random_u64_range(end);
+        else
+            x = nm_random_u64_range_full(begin, end, nmtst_get_rand_bool());
+
+        g_assert_cmpuint(x, >=, begin);
+        g_assert_cmpuint(x, <, end);
+    }
+}
+
+/*****************************************************************************/
+
 static const char *
 _make_strv_foo(void)
 {
@@ -2417,6 +2466,7 @@ main(int argc, char **argv)
     g_test_add_func("/general/test_inet_utils", test_inet_utils);
     g_test_add_func("/general/test_garray", test_garray);
     g_test_add_func("/general/test_nm_prioq", test_nm_prioq);
+    g_test_add_func("/general/test_nm_random", test_nm_random);
 
     return g_test_run();
 }
diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c
index 30ad1275..049095b2 100644
--- a/src/libnm-platform/nm-linux-platform.c
+++ b/src/libnm-platform/nm-linux-platform.c
@@ -9291,11 +9291,11 @@ wifi_set_powersave(NMPlatform *platform, int ifindex, guint32 powersave)
 }
 
 static guint32
-wifi_find_frequency(NMPlatform *platform, int ifindex, const guint32 *freqs)
+wifi_find_frequency(NMPlatform *platform, int ifindex, const guint32 *freqs, gboolean ap)
 {
     WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, 0);
 
-    return nm_wifi_utils_find_freq(wifi_data, freqs);
+    return nm_wifi_utils_find_freq(wifi_data, freqs, ap);
 }
 
 static void
diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c
index 2e9e940c..dc643ed2 100644
--- a/src/libnm-platform/nm-platform.c
+++ b/src/libnm-platform/nm-platform.c
@@ -3192,14 +3192,14 @@ nm_platform_wifi_set_powersave(NMPlatform *self, int ifindex, guint32 powersave)
 }
 
 guint32
-nm_platform_wifi_find_frequency(NMPlatform *self, int ifindex, const guint32 *freqs)
+nm_platform_wifi_find_frequency(NMPlatform *self, int ifindex, const guint32 *freqs, gboolean ap)
 {
     _CHECK_SELF(self, klass, 0);
 
     g_return_val_if_fail(ifindex > 0, 0);
     g_return_val_if_fail(freqs != NULL, 0);
 
-    return klass->wifi_find_frequency(self, ifindex, freqs);
+    return klass->wifi_find_frequency(self, ifindex, freqs, ap);
 }
 
 void
diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h
index 1cd9c6c6..ca6cdff8 100644
--- a/src/libnm-platform/nm-platform.h
+++ b/src/libnm-platform/nm-platform.h
@@ -1183,7 +1183,10 @@ typedef struct {
     _NM80211Mode (*wifi_get_mode)(NMPlatform *self, int ifindex);
     void (*wifi_set_mode)(NMPlatform *self, int ifindex, _NM80211Mode mode);
     void (*wifi_set_powersave)(NMPlatform *self, int ifindex, guint32 powersave);
-    guint32 (*wifi_find_frequency)(NMPlatform *self, int ifindex, const guint32 *freqs);
+    guint32 (*wifi_find_frequency)(NMPlatform    *self,
+                                   int            ifindex,
+                                   const guint32 *freqs,
+                                   gboolean       ap);
     void (*wifi_indicate_addressing_running)(NMPlatform *self, int ifindex, gboolean running);
     _NMSettingWirelessWakeOnWLan (*wifi_get_wake_on_wlan)(NMPlatform *self, int ifindex);
     gboolean (*wifi_set_wake_on_wlan)(NMPlatform                  *self,
@@ -2063,7 +2066,8 @@ gboolean     nm_platform_wifi_get_station(NMPlatform  *self,
 _NM80211Mode nm_platform_wifi_get_mode(NMPlatform *self, int ifindex);
 void         nm_platform_wifi_set_mode(NMPlatform *self, int ifindex, _NM80211Mode mode);
 void         nm_platform_wifi_set_powersave(NMPlatform *self, int ifindex, guint32 powersave);
-guint32      nm_platform_wifi_find_frequency(NMPlatform *self, int ifindex, const guint32 *freqs);
+guint32
+nm_platform_wifi_find_frequency(NMPlatform *self, int ifindex, const guint32 *freqs, gboolean ap);
 void nm_platform_wifi_indicate_addressing_running(NMPlatform *self, int ifindex, gboolean running);
 _NMSettingWirelessWakeOnWLan nm_platform_wifi_get_wake_on_wlan(NMPlatform *self, int ifindex);
 gboolean
diff --git a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
index c7ee0473..beddaf22 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
@@ -42,9 +42,15 @@
     G_STMT_END
 
 typedef struct {
+    guint32 freq;
+    bool    disabled : 1;
+    bool    no_ir : 1;
+} Nl80211Freq;
+
+typedef struct {
     NMWifiUtils     parent;
     struct nl_sock *nl_sock;
-    guint32        *freqs;
+    Nl80211Freq    *freqs;
     int             num_freqs;
     int             phy;
     guint16         genl_family_id;
@@ -379,7 +385,7 @@ wifi_nl80211_get_freq(NMWifiUtils *data)
 }
 
 static guint32
-wifi_nl80211_find_freq(NMWifiUtils *data, const guint32 *freqs)
+wifi_nl80211_find_freq(NMWifiUtils *data, const guint32 *freqs, gboolean ap)
 {
     NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
     int                 i;
@@ -389,7 +395,11 @@ wifi_nl80211_find_freq(NMWifiUtils *data, const guint32 *freqs)
      * that array might be sorted to contain preferred frequencies first. */
     for (j = 0; freqs[j] != 0; j++) {
         for (i = 0; i < self->num_freqs; i++) {
-            if (self->freqs[i] == freqs[j])
+            if (self->freqs[i].disabled)
+                continue;
+            if (ap && self->freqs[i].no_ir)
+                continue;
+            if (self->freqs[i].freq == freqs[j])
                 return freqs[j];
         }
     }
@@ -555,7 +565,7 @@ nla_put_failure:
 struct nl80211_device_info {
     NMWifiUtilsNl80211 *self;
     int                 phy;
-    guint32            *freqs;
+    Nl80211Freq        *freqs;
     int                 num_freqs;
     guint32             freq;
     guint32             caps;
@@ -657,40 +667,45 @@ nl80211_wiphy_info_handler(const struct nl_msg *msg, void *arg)
     /* Read supported frequencies */
     num_alloc       = 32;
     info->num_freqs = 0;
-    info->freqs     = g_new(guint32, num_alloc);
+    info->freqs     = g_new(Nl80211Freq, num_alloc);
 
     nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
         if (nla_parse_nested_arr(tb_band, nl_band, NULL) < 0)
             return NL_SKIP;
 
         nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) {
+            Nl80211Freq *f;
+
             if (nla_parse_nested_arr(tb_freq, nl_freq, freq_policy) < 0)
                 continue;
 
             if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
                 continue;
 
-            if (tb_freq[NL80211_FREQUENCY_ATTR_DISABLED])
-                continue;
-
             if (info->num_freqs >= num_alloc) {
                 num_alloc *= 2;
-                info->freqs = g_renew(guint32, info->freqs, num_alloc);
+                info->freqs = g_renew(Nl80211Freq, info->freqs, num_alloc);
             }
 
-            info->freqs[info->num_freqs] = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
+            f  = &info->freqs[info->num_freqs];
+            *f = (Nl80211Freq){
+                .freq     = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]),
+                .disabled = !!tb_freq[NL80211_FREQUENCY_ATTR_DISABLED],
+                .no_ir    = !!tb_freq[NL80211_FREQUENCY_ATTR_NO_IR],
+            };
+
             info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_VALID;
 
-            if (info->freqs[info->num_freqs] > 2400 && info->freqs[info->num_freqs] < 2500)
+            if (f->freq > 2400 && f->freq < 2500)
                 info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_2GHZ;
-            if (info->freqs[info->num_freqs] > 4900 && info->freqs[info->num_freqs] < 6000)
+            if (f->freq > 4900 && f->freq < 6000)
                 info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_5GHZ;
 
             info->num_freqs++;
         }
     }
 
-    info->freqs = g_renew(guint32, info->freqs, info->num_freqs);
+    info->freqs = g_renew(Nl80211Freq, info->freqs, info->num_freqs);
 
     /* Read security/encryption support */
     if (tb[NL80211_ATTR_CIPHER_SUITES]) {
@@ -769,7 +784,7 @@ wifi_nl80211_get_mesh_channel(NMWifiUtils *data)
     }
 
     for (i = 0; i < self->num_freqs; i++) {
-        if (device_info.freq == self->freqs[i])
+        if (device_info.freq == self->freqs[i].freq)
             return i + 1;
     }
     return 0;
@@ -786,7 +801,7 @@ wifi_nl80211_set_mesh_channel(NMWifiUtils *data, guint32 channel)
         return FALSE;
 
     msg = nl80211_alloc_msg(self, NL80211_CMD_SET_WIPHY, 0);
-    NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, self->freqs[channel - 1]);
+    NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, self->freqs[channel - 1].freq);
     err = nl80211_send_and_recv(self, msg, NULL, NULL);
     return err >= 0;
 
diff --git a/src/libnm-platform/wifi/nm-wifi-utils-private.h b/src/libnm-platform/wifi/nm-wifi-utils-private.h
index 7fe157f2..8fa593c0 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils-private.h
+++ b/src/libnm-platform/wifi/nm-wifi-utils-private.h
@@ -28,8 +28,9 @@ typedef struct {
     /* Return current frequency in MHz (really associated BSS frequency) */
     guint32 (*get_freq)(NMWifiUtils *data);
 
-    /* Return first supported frequency in the zero-terminated list */
-    guint32 (*find_freq)(NMWifiUtils *data, const guint32 *freqs);
+    /* Return first supported frequency in the zero-terminated list. @ap
+     * indicates that the frequency must be suited for AP mode. */
+    guint32 (*find_freq)(NMWifiUtils *data, const guint32 *freqs, gboolean ap);
 
     /*
      * @out_bssid: must be NULL or an ETH_ALEN-byte buffer
diff --git a/src/libnm-platform/wifi/nm-wifi-utils-wext.c b/src/libnm-platform/wifi/nm-wifi-utils-wext.c
index eac3c929..0cc8b6a6 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils-wext.c
+++ b/src/libnm-platform/wifi/nm-wifi-utils-wext.c
@@ -249,7 +249,7 @@ wifi_wext_get_freq(NMWifiUtils *data)
 }
 
 static guint32
-wifi_wext_find_freq(NMWifiUtils *data, const guint32 *freqs)
+wifi_wext_find_freq(NMWifiUtils *data, const guint32 *freqs, gboolean ap)
 {
     NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
     guint            i;
diff --git a/src/libnm-platform/wifi/nm-wifi-utils.c b/src/libnm-platform/wifi/nm-wifi-utils.c
index 0238b74a..6f87e8c0 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils.c
+++ b/src/libnm-platform/wifi/nm-wifi-utils.c
@@ -124,12 +124,12 @@ nm_wifi_utils_get_freq(NMWifiUtils *data)
 }
 
 guint32
-nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs)
+nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs, gboolean ap)
 {
     g_return_val_if_fail(data != NULL, 0);
     g_return_val_if_fail(freqs != NULL, 0);
 
-    return NM_WIFI_UTILS_GET_CLASS(data)->find_freq(data, freqs);
+    return NM_WIFI_UTILS_GET_CLASS(data)->find_freq(data, freqs, ap);
 }
 
 gboolean
diff --git a/src/libnm-platform/wifi/nm-wifi-utils.h b/src/libnm-platform/wifi/nm-wifi-utils.h
index aa5a34cd..84d724a5 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils.h
+++ b/src/libnm-platform/wifi/nm-wifi-utils.h
@@ -39,7 +39,7 @@ guint32 nm_wifi_utils_get_freq(NMWifiUtils *data);
 
 /* Return the first supported frequency in the zero-terminated list.
  * Frequencies are specified in MHz. */
-guint32 nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs);
+guint32 nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs, gboolean ap);
 
 /*
  * @out_bssid: must be NULL or an ETH_ALEN-byte buffer
diff --git a/src/libnmc-base/nm-polkit-listener.c b/src/libnmc-base/nm-polkit-listener.c
index e7972faa..fbc2c704 100644
--- a/src/libnmc-base/nm-polkit-listener.c
+++ b/src/libnmc-base/nm-polkit-listener.c
@@ -534,10 +534,10 @@ begin_authentication(AuthRequest *request)
         return;
     }
 
-    fd_flags = fcntl(request->child_stdin, F_GETFD, 0);
+    fd_flags = fcntl(request->child_stdin, F_GETFL, 0);
     fcntl(request->child_stdin, F_SETFL, fd_flags | O_NONBLOCK);
 
-    fd_flags = fcntl(request->child_stdout, F_GETFD, 0);
+    fd_flags = fcntl(request->child_stdout, F_GETFL, 0);
     fcntl(request->child_stdout, F_SETFL, fd_flags | O_NONBLOCK);
 
     request->child_stdout_watch_source = nm_g_unix_fd_source_new(request->child_stdout,
diff --git a/src/libnmc-setting/settings-docs.h b/src/libnmc-setting/settings-docs.h
index cd5b231b..c10ece6a 100644
--- a/src/libnmc-setting/settings-docs.h
+++ b/src/libnmc-setting/settings-docs.h
@@ -169,7 +169,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_TIMEOUT N_("A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER N_("The Vendor Class Identifier DHCP option (60). Special characters in the data string may be escaped using C-style escapes, nevertheless this property cannot contain nul bytes. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the DHCP option is not sent to the server.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS N_("Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending \"#example.com\" to the IP address of the DNS server. This currently only has effect when using systemd-resolved.")
-#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
+#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_GATEWAY N_("The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if \"never-default\" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See \"ip4-auto-default-route\".")
@@ -197,7 +197,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_SEND_HOSTNAME N_("If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer.  If the \"dhcp-hostname\" property is NULL and this property is TRUE, the current persistent hostname of the computer is sent.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_TIMEOUT N_("A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS N_("Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending \"#example.com\" to the IP address of the DNS server. This currently only has effect when using systemd-resolved.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_GATEWAY N_("The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if \"never-default\" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See \"ip4-auto-default-route\".")
@@ -216,7 +216,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ROUTES N_("Array of IP routes.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ROUTING_RULES N_("A comma separated list of routing rules for policy routing.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_TOKEN N_("Configure the token for draft-chown-6man-tokenised-ipv6-identifiers-02 IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode.")
-#define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_ENCAPSULATION_LIMIT N_("How many additional levels of encapsulation are permitted to be prepended to packets. This property applies only to IPv6 tunnels.")
+#define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_ENCAPSULATION_LIMIT N_("How many additional levels of encapsulation are permitted to be prepended to packets. This property applies only to IPv6 tunnels. To disable this option, add NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1) to ip-tunnel flags.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_FLAGS N_("Tunnel flags. Currently, the following values are supported: NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_TCLASS (0x2), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FLOWLABEL (0x4), NM_IP_TUNNEL_FLAG_IP6_MIP6_DEV (0x8), NM_IP_TUNNEL_FLAG_IP6_RCV_DSCP_COPY (0x10), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FWMARK (0x20). They are valid only for IPv6 tunnels.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_FLOW_LABEL N_("The flow label to assign to tunnel packets. This property applies only to IPv6 tunnels.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_FWMARK N_("The fwmark value to assign to tunnel packets. This property can be set to a non zero value only on VTI and VTI6 tunnels.")
diff --git a/src/libnmc-setting/settings-docs.h.in b/src/libnmc-setting/settings-docs.h.in
index cd5b231b..c10ece6a 100644
--- a/src/libnmc-setting/settings-docs.h.in
+++ b/src/libnmc-setting/settings-docs.h.in
@@ -169,7 +169,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_TIMEOUT N_("A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER N_("The Vendor Class Identifier DHCP option (60). Special characters in the data string may be escaped using C-style escapes, nevertheless this property cannot contain nul bytes. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the DHCP option is not sent to the server.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS N_("Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending \"#example.com\" to the IP address of the DNS server. This currently only has effect when using systemd-resolved.")
-#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
+#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_GATEWAY N_("The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if \"never-default\" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See \"ip4-auto-default-route\".")
@@ -197,7 +197,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_SEND_HOSTNAME N_("If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer.  If the \"dhcp-hostname\" property is NULL and this property is TRUE, the current persistent hostname of the computer is sent.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_TIMEOUT N_("A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS N_("Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending \"#example.com\" to the IP address of the DNS server. This currently only has effect when using systemd-resolved.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_GATEWAY N_("The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if \"never-default\" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See \"ip4-auto-default-route\".")
@@ -216,7 +216,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ROUTES N_("Array of IP routes.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ROUTING_RULES N_("A comma separated list of routing rules for policy routing.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_TOKEN N_("Configure the token for draft-chown-6man-tokenised-ipv6-identifiers-02 IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode.")
-#define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_ENCAPSULATION_LIMIT N_("How many additional levels of encapsulation are permitted to be prepended to packets. This property applies only to IPv6 tunnels.")
+#define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_ENCAPSULATION_LIMIT N_("How many additional levels of encapsulation are permitted to be prepended to packets. This property applies only to IPv6 tunnels. To disable this option, add NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1) to ip-tunnel flags.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_FLAGS N_("Tunnel flags. Currently, the following values are supported: NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_TCLASS (0x2), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FLOWLABEL (0x4), NM_IP_TUNNEL_FLAG_IP6_MIP6_DEV (0x8), NM_IP_TUNNEL_FLAG_IP6_RCV_DSCP_COPY (0x10), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FWMARK (0x20). They are valid only for IPv6 tunnels.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_FLOW_LABEL N_("The flow label to assign to tunnel packets. This property applies only to IPv6 tunnels.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_FWMARK N_("The fwmark value to assign to tunnel packets. This property can be set to a non zero value only on VTI and VTI6 tunnels.")
diff --git a/src/nm-cloud-setup/nm-http-client.c b/src/nm-cloud-setup/nm-http-client.c
index a0964e21..db123f10 100644
--- a/src/nm-cloud-setup/nm-http-client.c
+++ b/src/nm-cloud-setup/nm-http-client.c
@@ -104,12 +104,12 @@ typedef struct {
 } GetResult;
 
 static void
-_get_result_free(gpointer data)
+_req_result_free(gpointer data)
 {
-    GetResult *get_result = data;
+    GetResult *req_result = data;
 
-    g_bytes_unref(get_result->response_data);
-    nm_g_slice_free(get_result);
+    g_bytes_unref(req_result->response_data);
+    nm_g_slice_free(req_result);
 }
 
 typedef struct {
@@ -154,7 +154,7 @@ _ehandle_free(EHandleData *edata)
 static void
 _ehandle_complete(EHandleData *edata, GError *error_take)
 {
-    GetResult    *get_result;
+    GetResult    *req_result;
     gs_free char *str_tmp_1     = NULL;
     long          response_code = -1;
 
@@ -200,15 +200,15 @@ _ehandle_complete(EHandleData *edata, GError *error_take)
 
     _ehandle_free_ehandle(edata);
 
-    get_result  = g_slice_new(GetResult);
-    *get_result = (GetResult){
+    req_result  = g_slice_new(GetResult);
+    *req_result = (GetResult){
         .response_code = response_code,
         /* This ensures that response_data is always NUL terminated. This is an important guarantee
          * that NMHttpClient makes. */
         .response_data = nm_str_buf_finalize_to_gbytes(&edata->recv_data),
     };
 
-    g_task_return_pointer(edata->task, get_result, _get_result_free);
+    g_task_return_pointer(edata->task, req_result, _req_result_free);
 
     _ehandle_free(edata);
 }
@@ -256,12 +256,13 @@ _get_cancelled_cb(GObject *object, gpointer user_data)
     _ehandle_complete(edata, error);
 }
 
-void
-nm_http_client_get(NMHttpClient       *self,
+static void
+nm_http_client_req(NMHttpClient       *self,
                    const char         *url,
                    int                 timeout_msec,
                    gssize              max_data,
                    const char *const  *http_headers,
+                   const char         *http_method,
                    GCancellable       *cancellable,
                    GAsyncReadyCallback callback,
                    gpointer            user_data)
@@ -280,7 +281,7 @@ nm_http_client_get(NMHttpClient       *self,
 
     edata  = g_slice_new(EHandleData);
     *edata = (EHandleData){
-        .task      = nm_g_task_new(self, cancellable, nm_http_client_get, callback, user_data),
+        .task      = nm_g_task_new(self, cancellable, nm_http_client_req, callback, user_data),
         .recv_data = NM_STR_BUF_INIT(0, FALSE),
         .max_data  = max_data,
         .url       = g_strdup(url),
@@ -289,7 +290,7 @@ nm_http_client_get(NMHttpClient       *self,
 
     nmcs_wait_for_objects_register(edata->task);
 
-    _LOG2D(edata, "start get ...");
+    _LOG2D(edata, "start %s ...", http_method ?: "get");
 
     edata->ehandle = curl_easy_init();
     if (!edata->ehandle) {
@@ -328,6 +329,9 @@ nm_http_client_get(NMHttpClient       *self,
         curl_easy_setopt(edata->ehandle, CURLOPT_HTTPHEADER, edata->headers);
     }
 
+    if (http_method)
+        curl_easy_setopt(edata->ehandle, CURLOPT_CUSTOMREQUEST, http_method);
+
     if (timeout_msec > 0) {
         edata->timeout_source = _source_attach(self,
                                                nm_g_timeout_source_new(timeout_msec,
@@ -352,7 +356,7 @@ nm_http_client_get(NMHttpClient       *self,
 }
 
 /**
- * nm_http_client_get_finish:
+ * nm_http_client_req_finish:
  * @self: the #NMHttpClient instance
  * @result: the #GAsyncResult which to complete.
  * @out_response_code: (allow-none) (out): the HTTP response code or -1 on other error.
@@ -366,34 +370,34 @@ nm_http_client_get(NMHttpClient       *self,
  *
  * Returns: %TRUE on success or %FALSE with an error code.
  */
-gboolean
-nm_http_client_get_finish(NMHttpClient *self,
+static gboolean
+nm_http_client_req_finish(NMHttpClient *self,
                           GAsyncResult *result,
                           long         *out_response_code,
                           GBytes      **out_response_data,
                           GError      **error)
 {
-    GetResult *get_result;
+    GetResult *req_result;
 
     g_return_val_if_fail(NM_IS_HTTP_CLIENT(self), FALSE);
-    g_return_val_if_fail(nm_g_task_is_valid(result, self, nm_http_client_get), FALSE);
+    g_return_val_if_fail(nm_g_task_is_valid(result, self, nm_http_client_req), FALSE);
 
-    get_result = g_task_propagate_pointer(G_TASK(result), error);
+    req_result = g_task_propagate_pointer(G_TASK(result), error);
 
-    nm_assert(!error || (!!get_result) == (!*error));
+    nm_assert(!error || (!!req_result) == (!*error));
 
-    if (!get_result) {
+    if (!req_result) {
         NM_SET_OUT(out_response_code, -1);
         NM_SET_OUT(out_response_data, NULL);
         return FALSE;
     }
 
-    NM_SET_OUT(out_response_code, get_result->response_code);
+    NM_SET_OUT(out_response_code, req_result->response_code);
 
     /* response_data is binary, but is also guaranteed to be NUL terminated! */
-    NM_SET_OUT(out_response_data, g_steal_pointer(&get_result->response_data));
+    NM_SET_OUT(out_response_data, g_steal_pointer(&req_result->response_data));
 
-    _get_result_free(get_result);
+    _req_result_free(req_result);
     return TRUE;
 }
 
@@ -403,63 +407,65 @@ typedef struct {
     GTask                      *task;
     char                       *uri;
     const char *const          *http_headers;
-    NMHttpClientPollGetCheckFcn check_fcn;
+    const char                 *http_method;
+    NMHttpClientPollReqCheckFcn check_fcn;
     gpointer                    check_user_data;
     GBytes                     *response_data;
     gsize                       request_max_data;
     long                        response_code;
     int                         request_timeout_ms;
-} PollGetData;
+} PollReqData;
 
 static void
-_poll_get_data_free(gpointer data)
+_poll_req_data_free(gpointer data)
 {
-    PollGetData *poll_get_data = data;
+    PollReqData *poll_req_data = data;
 
-    g_free(poll_get_data->uri);
+    g_free(poll_req_data->uri);
 
-    nm_clear_pointer(&poll_get_data->response_data, g_bytes_unref);
-    g_strfreev((char **) poll_get_data->http_headers);
+    nm_clear_pointer(&poll_req_data->response_data, g_bytes_unref);
+    g_free((gpointer) poll_req_data->http_headers);
 
-    nm_g_slice_free(poll_get_data);
+    nm_g_slice_free(poll_req_data);
 }
 
 static void
-_poll_get_probe_start_fcn(GCancellable       *cancellable,
+_poll_req_probe_start_fcn(GCancellable       *cancellable,
                           gpointer            probe_user_data,
                           GAsyncReadyCallback callback,
                           gpointer            user_data)
 {
-    PollGetData *poll_get_data = probe_user_data;
+    PollReqData *poll_req_data = probe_user_data;
 
-    /* balanced by _poll_get_probe_finish_fcn() */
-    g_object_ref(poll_get_data->task);
+    /* balanced by _poll_req_probe_finish_fcn() */
+    g_object_ref(poll_req_data->task);
 
-    nm_http_client_get(g_task_get_source_object(poll_get_data->task),
-                       poll_get_data->uri,
-                       poll_get_data->request_timeout_ms,
-                       poll_get_data->request_max_data,
-                       poll_get_data->http_headers,
+    nm_http_client_req(g_task_get_source_object(poll_req_data->task),
+                       poll_req_data->uri,
+                       poll_req_data->request_timeout_ms,
+                       poll_req_data->request_max_data,
+                       poll_req_data->http_headers,
+                       poll_req_data->http_method,
                        cancellable,
                        callback,
                        user_data);
 }
 
 static gboolean
-_poll_get_probe_finish_fcn(GObject      *source,
+_poll_req_probe_finish_fcn(GObject      *source,
                            GAsyncResult *result,
                            gpointer      probe_user_data,
                            GError      **error)
 {
-    PollGetData                      *poll_get_data = probe_user_data;
+    PollReqData                      *poll_req_data = probe_user_data;
     _nm_unused gs_unref_object GTask *task =
-        poll_get_data->task; /* balance ref from _poll_get_probe_start_fcn() */
+        poll_req_data->task; /* balance ref from _poll_req_probe_start_fcn() */
     gboolean               success;
     gs_free_error GError  *local_error   = NULL;
     gs_unref_bytes GBytes *response_data = NULL;
     long                   response_code = -1;
 
-    success = nm_http_client_get_finish(g_task_get_source_object(poll_get_data->task),
+    success = nm_http_client_req_finish(g_task_get_source_object(poll_req_data->task),
                                         result,
                                         &response_code,
                                         &response_data,
@@ -476,10 +482,10 @@ _poll_get_probe_finish_fcn(GObject      *source,
         return FALSE;
     }
 
-    if (poll_get_data->check_fcn) {
-        success = poll_get_data->check_fcn(response_code,
+    if (poll_req_data->check_fcn) {
+        success = poll_req_data->check_fcn(response_code,
                                            response_data,
-                                           poll_get_data->check_user_data,
+                                           poll_req_data->check_user_data,
                                            &local_error);
     } else
         success = (response_code == 200);
@@ -494,15 +500,15 @@ _poll_get_probe_finish_fcn(GObject      *source,
         return FALSE;
     }
 
-    poll_get_data->response_code = response_code;
-    poll_get_data->response_data = g_steal_pointer(&response_data);
+    poll_req_data->response_code = response_code;
+    poll_req_data->response_data = g_steal_pointer(&response_data);
     return TRUE;
 }
 
 static void
-_poll_get_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+_poll_req_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    PollGetData          *poll_get_data = user_data;
+    PollReqData          *poll_req_data = user_data;
     gs_free_error GError *error         = NULL;
     gboolean              success;
 
@@ -511,29 +517,30 @@ _poll_get_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     nm_assert((!!success) == (!error));
 
     if (error)
-        g_task_return_error(poll_get_data->task, g_steal_pointer(&error));
+        g_task_return_error(poll_req_data->task, g_steal_pointer(&error));
     else
-        g_task_return_boolean(poll_get_data->task, TRUE);
+        g_task_return_boolean(poll_req_data->task, TRUE);
 
-    g_object_unref(poll_get_data->task);
+    g_object_unref(poll_req_data->task);
 }
 
 void
-nm_http_client_poll_get(NMHttpClient               *self,
+nm_http_client_poll_req(NMHttpClient               *self,
                         const char                 *uri,
                         int                         request_timeout_ms,
                         gssize                      request_max_data,
                         int                         poll_timeout_ms,
                         int                         ratelimit_timeout_ms,
                         const char *const          *http_headers,
+                        const char                 *http_method,
                         GCancellable               *cancellable,
-                        NMHttpClientPollGetCheckFcn check_fcn,
+                        NMHttpClientPollReqCheckFcn check_fcn,
                         gpointer                    check_user_data,
                         GAsyncReadyCallback         callback,
                         gpointer                    user_data)
 {
     nm_auto_pop_gmaincontext GMainContext *context = NULL;
-    PollGetData                           *poll_get_data;
+    PollReqData                           *poll_req_data;
 
     g_return_if_fail(NM_IS_HTTP_CLIENT(self));
     g_return_if_fail(uri && uri[0]);
@@ -543,21 +550,27 @@ nm_http_client_poll_get(NMHttpClient               *self,
     g_return_if_fail(ratelimit_timeout_ms >= -1);
     g_return_if_fail(!cancellable || G_CANCELLABLE(cancellable));
 
-    poll_get_data  = g_slice_new(PollGetData);
-    *poll_get_data = (PollGetData){
-        .task = nm_g_task_new(self, cancellable, nm_http_client_poll_get, callback, user_data),
+    poll_req_data  = g_slice_new(PollReqData);
+    *poll_req_data = (PollReqData){
+        .task = nm_g_task_new(self, cancellable, nm_http_client_poll_req, callback, user_data),
         .uri  = g_strdup(uri),
         .request_timeout_ms = request_timeout_ms,
         .request_max_data   = request_max_data,
         .check_fcn          = check_fcn,
         .check_user_data    = check_user_data,
         .response_code      = -1,
-        .http_headers       = NM_CAST_STRV_CC(g_strdupv((char **) http_headers)),
+        .http_headers       = NULL,
+        .http_method        = http_method,
     };
 
-    nmcs_wait_for_objects_register(poll_get_data->task);
+    if (http_headers) {
+        poll_req_data->http_headers =
+            nm_strv_dup_packed(http_headers, -1) ?: g_new(const char *, 1);
+    }
+
+    nmcs_wait_for_objects_register(poll_req_data->task);
 
-    g_task_set_task_data(poll_get_data->task, poll_get_data, _poll_get_data_free);
+    g_task_set_task_data(poll_req_data->task, poll_req_data, _poll_req_data_free);
 
     context =
         nm_g_main_context_push_thread_default_if_necessary(nm_http_client_get_main_context(self));
@@ -565,28 +578,28 @@ nm_http_client_poll_get(NMHttpClient               *self,
     nmcs_utils_poll(poll_timeout_ms,
                     ratelimit_timeout_ms,
                     0,
-                    _poll_get_probe_start_fcn,
-                    _poll_get_probe_finish_fcn,
-                    poll_get_data,
+                    _poll_req_probe_start_fcn,
+                    _poll_req_probe_finish_fcn,
+                    poll_req_data,
                     cancellable,
-                    _poll_get_done_cb,
-                    poll_get_data);
+                    _poll_req_done_cb,
+                    poll_req_data);
 }
 
 gboolean
-nm_http_client_poll_get_finish(NMHttpClient *self,
+nm_http_client_poll_req_finish(NMHttpClient *self,
                                GAsyncResult *result,
                                long         *out_response_code,
                                GBytes      **out_response_data,
                                GError      **error)
 {
-    PollGetData          *poll_get_data;
+    PollReqData          *poll_req_data;
     GTask                *task;
     gboolean              success;
     gs_free_error GError *local_error = NULL;
 
     g_return_val_if_fail(NM_HTTP_CLIENT(self), FALSE);
-    g_return_val_if_fail(nm_g_task_is_valid(result, self, nm_http_client_poll_get), FALSE);
+    g_return_val_if_fail(nm_g_task_is_valid(result, self, nm_http_client_poll_req), FALSE);
 
     task = G_TASK(result);
 
@@ -601,10 +614,10 @@ nm_http_client_poll_get_finish(NMHttpClient *self,
         return FALSE;
     }
 
-    poll_get_data = g_task_get_task_data(task);
+    poll_req_data = g_task_get_task_data(task);
 
-    NM_SET_OUT(out_response_code, poll_get_data->response_code);
-    NM_SET_OUT(out_response_data, g_steal_pointer(&poll_get_data->response_data));
+    NM_SET_OUT(out_response_code, poll_req_data->response_code);
+    NM_SET_OUT(out_response_data, g_steal_pointer(&poll_req_data->response_data));
     return TRUE;
 }
 
diff --git a/src/nm-cloud-setup/nm-http-client.h b/src/nm-cloud-setup/nm-http-client.h
index b041e99f..60310340 100644
--- a/src/nm-cloud-setup/nm-http-client.h
+++ b/src/nm-cloud-setup/nm-http-client.h
@@ -28,40 +28,26 @@ GMainContext *nm_http_client_get_main_context(NMHttpClient *self);
 
 /*****************************************************************************/
 
-void nm_http_client_get(NMHttpClient       *self,
-                        const char         *uri,
-                        int                 timeout_msec,
-                        gssize              max_data,
-                        const char *const  *http_headers,
-                        GCancellable       *cancellable,
-                        GAsyncReadyCallback callback,
-                        gpointer            user_data);
-
-gboolean nm_http_client_get_finish(NMHttpClient *self,
-                                   GAsyncResult *result,
-                                   long         *out_response_code,
-                                   GBytes      **out_response_data,
-                                   GError      **error);
-
-typedef gboolean (*NMHttpClientPollGetCheckFcn)(long     response_code,
+typedef gboolean (*NMHttpClientPollReqCheckFcn)(long     response_code,
                                                 GBytes  *response_data,
                                                 gpointer check_user_data,
                                                 GError **error);
 
-void nm_http_client_poll_get(NMHttpClient               *self,
+void nm_http_client_poll_req(NMHttpClient               *self,
                              const char                 *uri,
                              int                         request_timeout_ms,
                              gssize                      request_max_data,
                              int                         poll_timeout_ms,
                              int                         ratelimit_timeout_ms,
                              const char *const          *http_headers,
+                             const char                 *http_method,
                              GCancellable               *cancellable,
-                             NMHttpClientPollGetCheckFcn check_fcn,
+                             NMHttpClientPollReqCheckFcn check_fcn,
                              gpointer                    check_user_data,
                              GAsyncReadyCallback         callback,
                              gpointer                    user_data);
 
-gboolean nm_http_client_poll_get_finish(NMHttpClient *self,
+gboolean nm_http_client_poll_req_finish(NMHttpClient *self,
                                         GAsyncResult *result,
                                         long         *out_response_code,
                                         GBytes      **out_response_data,
diff --git a/src/nm-cloud-setup/nmcs-provider-aliyun.c b/src/nm-cloud-setup/nmcs-provider-aliyun.c
index 4b109609..86ecd76b 100644
--- a/src/nm-cloud-setup/nmcs-provider-aliyun.c
+++ b/src/nm-cloud-setup/nmcs-provider-aliyun.c
@@ -77,7 +77,7 @@ _detect_get_meta_data_done_cb(GObject *source, GAsyncResult *result, gpointer us
     gs_free_error GError  *get_error = NULL;
     gs_free_error GError  *error     = NULL;
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &get_error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &get_error);
 
     if (nm_utils_error_is_cancelled(get_error)) {
         g_task_return_error(task, g_steal_pointer(&get_error));
@@ -104,13 +104,14 @@ detect(NMCSProvider *provider, GTask *task)
 
     http_client = nmcs_provider_get_http_client(provider);
 
-    nm_http_client_poll_get(http_client,
+    nm_http_client_poll_req(http_client,
                             (uri = _aliyun_uri_concat(NM_ALIYUN_API_VERSION "/meta-data/")),
                             HTTP_TIMEOUT_MS,
                             256 * 1024,
                             7000,
                             1000,
                             NULL,
+                            NULL,
                             g_task_get_cancellable(task),
                             NULL,
                             NULL,
@@ -144,7 +145,7 @@ _get_config_fetch_done_cb(NMHttpClient                   *http_client,
     gsize                  i;
     gsize                  len;
 
-    nm_http_client_poll_get_finish(http_client, result, NULL, &response, &error);
+    nm_http_client_poll_req_finish(http_client, result, NULL, &response, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -302,7 +303,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
     GHashTableIter                 h_iter;
     NMHttpClient                  *http_client;
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -356,7 +357,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
               v_mac_data->path);
 
         get_config_data->n_pending++;
-        nm_http_client_poll_get(
+        nm_http_client_poll_req(
             http_client,
             (uri1 = _aliyun_uri_interfaces(v_mac_data->path,
                                            NM_STR_HAS_SUFFIX(v_mac_data->path, "/") ? "" : "/",
@@ -366,6 +367,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             10000,
             1000,
             NULL,
+            NULL,
             get_config_data->intern_cancellable,
             NULL,
             NULL,
@@ -373,7 +375,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             config_iface_data);
 
         get_config_data->n_pending++;
-        nm_http_client_poll_get(
+        nm_http_client_poll_req(
             http_client,
             (uri2 = _aliyun_uri_interfaces(v_mac_data->path,
                                            NM_STR_HAS_SUFFIX(v_mac_data->path, "/") ? "" : "/",
@@ -383,6 +385,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             10000,
             1000,
             NULL,
+            NULL,
             get_config_data->intern_cancellable,
             NULL,
             NULL,
@@ -390,7 +393,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             config_iface_data);
 
         get_config_data->n_pending++;
-        nm_http_client_poll_get(
+        nm_http_client_poll_req(
             http_client,
             (uri3 = _aliyun_uri_interfaces(v_mac_data->path,
                                            NM_STR_HAS_SUFFIX(v_mac_data->path, "/") ? "" : "/",
@@ -400,6 +403,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             10000,
             1000,
             NULL,
+            NULL,
             get_config_data->intern_cancellable,
             NULL,
             NULL,
@@ -407,7 +411,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             config_iface_data);
 
         get_config_data->n_pending++;
-        nm_http_client_poll_get(
+        nm_http_client_poll_req(
             http_client,
             (uri4 = _aliyun_uri_interfaces(v_mac_data->path,
                                            NM_STR_HAS_SUFFIX(v_mac_data->path, "/") ? "" : "/",
@@ -417,6 +421,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             10000,
             1000,
             NULL,
+            NULL,
             get_config_data->intern_cancellable,
             NULL,
             NULL,
@@ -424,7 +429,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             config_iface_data);
 
         get_config_data->n_pending++;
-        nm_http_client_poll_get(
+        nm_http_client_poll_req(
             http_client,
             (uri5 = _aliyun_uri_interfaces(v_mac_data->path,
                                            NM_STR_HAS_SUFFIX(v_mac_data->path, "/") ? "" : "/",
@@ -434,6 +439,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             10000,
             1000,
             NULL,
+            NULL,
             get_config_data->intern_cancellable,
             NULL,
             NULL,
@@ -524,13 +530,14 @@ get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_dat
      * MAC addresses, then we poll until we see them. They might not yet be
      * around from the start...
      */
-    nm_http_client_poll_get(nmcs_provider_get_http_client(provider),
+    nm_http_client_poll_req(nmcs_provider_get_http_client(provider),
                             (uri = _aliyun_uri_interfaces()),
                             HTTP_TIMEOUT_MS,
                             256 * 1024,
                             15000,
                             1000,
                             NULL,
+                            NULL,
                             get_config_data->intern_cancellable,
                             _get_config_metadata_ready_check,
                             get_config_data,
diff --git a/src/nm-cloud-setup/nmcs-provider-azure.c b/src/nm-cloud-setup/nmcs-provider-azure.c
index b59235bf..69946f5c 100644
--- a/src/nm-cloud-setup/nmcs-provider-azure.c
+++ b/src/nm-cloud-setup/nmcs-provider-azure.c
@@ -42,7 +42,7 @@ _detect_get_meta_data_done_cb(GObject *source, GAsyncResult *result, gpointer us
     gs_free_error GError  *get_error = NULL;
     gs_free_error GError  *error     = NULL;
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &get_error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &get_error);
 
     if (nm_utils_error_is_cancelled(get_error)) {
         g_task_return_error(task, g_steal_pointer(&get_error));
@@ -69,13 +69,14 @@ detect(NMCSProvider *provider, GTask *task)
 
     http_client = nmcs_provider_get_http_client(provider);
 
-    nm_http_client_poll_get(http_client,
+    nm_http_client_poll_req(http_client,
                             (uri = _azure_uri_concat("/metadata/instance")),
                             HTTP_TIMEOUT_MS,
                             256 * 1024,
                             7000,
                             1000,
                             NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
+                            NULL,
                             g_task_get_cancellable(task),
                             NULL,
                             NULL,
@@ -121,7 +122,7 @@ _get_config_fetch_done_cb(NMHttpClient      *http_client,
     in_addr_t                       tmp_addr;
     int                             tmp_prefix = -1;
 
-    nm_http_client_poll_get_finish(http_client, result, NULL, &response, &error);
+    nm_http_client_poll_req_finish(http_client, result, NULL, &response, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -241,7 +242,7 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u
     gsize                          line_len;
     char                           iface_idx_str[30];
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -283,7 +284,7 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u
 
         iface_data->n_iface_data_pending++;
 
-        nm_http_client_poll_get(
+        nm_http_client_poll_req(
             NM_HTTP_CLIENT(source),
             (uri = _azure_uri_interfaces(iface_idx_str,
                                          "/ipv4/ipAddress/",
@@ -294,6 +295,7 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u
             10000,
             1000,
             NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
+            NULL,
             get_config_data->intern_cancellable,
             NULL,
             NULL,
@@ -308,7 +310,7 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u
         gs_free char *uri = NULL;
 
         iface_data->n_iface_data_pending++;
-        nm_http_client_poll_get(
+        nm_http_client_poll_req(
             NM_HTTP_CLIENT(source),
             (uri = _azure_uri_interfaces(iface_idx_str, "/ipv4/subnet/0/address/")),
             HTTP_TIMEOUT_MS,
@@ -316,6 +318,7 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u
             10000,
             1000,
             NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
+            NULL,
             get_config_data->intern_cancellable,
             NULL,
             NULL,
@@ -325,7 +328,7 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u
         nm_clear_g_free(&uri);
 
         iface_data->n_iface_data_pending++;
-        nm_http_client_poll_get(
+        nm_http_client_poll_req(
             NM_HTTP_CLIENT(source),
             (uri = _azure_uri_interfaces(iface_idx_str, "/ipv4/subnet/0/prefix/")),
             HTTP_TIMEOUT_MS,
@@ -333,6 +336,7 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u
             10000,
             1000,
             NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
+            NULL,
             get_config_data->intern_cancellable,
             NULL,
             NULL,
@@ -357,7 +361,7 @@ _get_config_iface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     gs_free const char            *uri        = NULL;
     char                           buf[100];
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -408,13 +412,14 @@ _get_config_iface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 
     nm_sprintf_buf(buf, "%" G_GSSIZE_FORMAT "/ipv4/ipAddress/", iface_data->intern_iface_idx);
 
-    nm_http_client_poll_get(NM_HTTP_CLIENT(source),
+    nm_http_client_poll_req(NM_HTTP_CLIENT(source),
                             (uri = _azure_uri_interfaces(buf)),
                             HTTP_TIMEOUT_MS,
                             512 * 1024,
                             10000,
                             1000,
                             NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
+                            NULL,
                             get_config_data->intern_cancellable,
                             NULL,
                             NULL,
@@ -441,7 +446,7 @@ _get_net_ifaces_list_cb(GObject *source, GAsyncResult *result, gpointer user_dat
     guint                          i;
     gssize                         extern_iface_idx_cnt = 0;
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -508,13 +513,14 @@ _get_net_ifaces_list_cb(GObject *source, GAsyncResult *result, gpointer user_dat
         nm_sprintf_buf(buf, "%" G_GSSIZE_FORMAT "/macAddress", iface_data->intern_iface_idx);
 
         get_config_data->n_pending++;
-        nm_http_client_poll_get(NM_HTTP_CLIENT(source),
+        nm_http_client_poll_req(NM_HTTP_CLIENT(source),
                                 (uri = _azure_uri_interfaces(buf)),
                                 HTTP_TIMEOUT_MS,
                                 512 * 1024,
                                 10000,
                                 1000,
                                 NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
+                                NULL,
                                 get_config_data->intern_cancellable,
                                 NULL,
                                 NULL,
@@ -531,13 +537,14 @@ get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_dat
 {
     gs_free const char *uri = NULL;
 
-    nm_http_client_poll_get(nmcs_provider_get_http_client(provider),
+    nm_http_client_poll_req(nmcs_provider_get_http_client(provider),
                             (uri = _azure_uri_interfaces()),
                             HTTP_TIMEOUT_MS,
                             256 * 1024,
                             15000,
                             1000,
                             NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
+                            NULL,
                             get_config_data->intern_cancellable,
                             NULL,
                             NULL,
diff --git a/src/nm-cloud-setup/nmcs-provider-ec2.c b/src/nm-cloud-setup/nmcs-provider-ec2.c
index 58d691c7..65a8f629 100644
--- a/src/nm-cloud-setup/nmcs-provider-ec2.c
+++ b/src/nm-cloud-setup/nmcs-provider-ec2.c
@@ -16,6 +16,11 @@
 #define NM_EC2_METADATA_URL_BASE /* $NM_EC2_BASE/$NM_EC2_API_VERSION */ \
     "/meta-data/network/interfaces/macs/"
 
+/* Token TTL of 180 seconds is chosen abitrarily, in hope that it is
+ * surely more than enough to read all relevant metadata. */
+#define NM_EC2_TOKEN_TTL_HEADER "X-aws-ec2-metadata-token-ttl-seconds: 180"
+#define NM_EC2_TOKEN_HEADER     "X-aws-ec2-metadata-token: "
+
 static const char *
 _ec2_base(void)
 {
@@ -44,8 +49,15 @@ again:
 
 /*****************************************************************************/
 
+enum {
+    NM_EC2_HTTP_HEADER_TOKEN,
+    NM_EC2_HTTP_HEADER_SENTINEL,
+    _NM_EC2_HTTP_HEADER_NUM,
+};
+
 struct _NMCSProviderEC2 {
     NMCSProvider parent;
+    char        *token;
 };
 
 struct _NMCSProviderEC2Class {
@@ -56,23 +68,18 @@ G_DEFINE_TYPE(NMCSProviderEC2, nmcs_provider_ec2, NMCS_TYPE_PROVIDER);
 
 /*****************************************************************************/
 
-static gboolean
-_detect_get_meta_data_check_cb(long     response_code,
-                               GBytes  *response,
-                               gpointer check_user_data,
-                               GError **error)
-{
-    return response_code == 200 && nmcs_utils_parse_get_full_line(response, "ami-id");
-}
-
 static void
-_detect_get_meta_data_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+_detect_get_token_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
     gs_unref_object GTask *task      = user_data;
+    NMCSProviderEC2       *self      = NMCS_PROVIDER_EC2(g_task_get_source_object(task));
+    gs_unref_bytes GBytes *response  = NULL;
     gs_free_error GError  *get_error = NULL;
     gs_free_error GError  *error     = NULL;
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &get_error);
+    nm_clear_g_free(&self->token);
+
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &get_error);
 
     if (nm_utils_error_is_cancelled(get_error)) {
         g_task_return_error(task, g_steal_pointer(&get_error));
@@ -88,6 +95,12 @@ _detect_get_meta_data_done_cb(GObject *source, GAsyncResult *result, gpointer us
         return;
     }
 
+    /* We use the token as-is. Special characters can cause confusion (e.g.
+     * response splitting), but we're not crossing a security boundary.
+     * None of the examples in AWS documentation does any sort of
+     * sanitization either.  */
+    self->token = g_strconcat(NM_EC2_TOKEN_HEADER, g_bytes_get_data(response, NULL), NULL);
+
     g_task_return_boolean(task, TRUE);
 }
 
@@ -99,17 +112,18 @@ detect(NMCSProvider *provider, GTask *task)
 
     http_client = nmcs_provider_get_http_client(provider);
 
-    nm_http_client_poll_get(http_client,
-                            (uri = _ec2_uri_concat("latest/meta-data/")),
+    nm_http_client_poll_req(http_client,
+                            (uri = _ec2_uri_concat("latest/api/token")),
                             HTTP_TIMEOUT_MS,
                             256 * 1024,
                             7000,
                             1000,
-                            NULL,
+                            NM_MAKE_STRV(NM_EC2_TOKEN_TTL_HEADER),
+                            "PUT",
                             g_task_get_cancellable(task),
-                            _detect_get_meta_data_check_cb,
                             NULL,
-                            _detect_get_meta_data_done_cb,
+                            NULL,
+                            _detect_get_token_done_cb,
                             task);
 }
 
@@ -126,7 +140,7 @@ _get_config_fetch_done_cb(NMHttpClient                   *http_client,
     in_addr_t              tmp_addr;
     int                    tmp_prefix;
 
-    nm_http_client_poll_get_finish(http_client, result, NULL, &response, &error);
+    nm_http_client_poll_req_finish(http_client, result, NULL, &response, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -197,6 +211,7 @@ static void
 _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
     NMCSProviderGetConfigTaskData *get_config_data;
+    NMCSProviderEC2               *self;
     gs_unref_hashtable GHashTable *response_parsed = NULL;
     gs_free_error GError          *error           = NULL;
     GetConfigMetadataMac          *v_mac_data;
@@ -204,12 +219,13 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
     GHashTableIter                 h_iter;
     NMHttpClient                  *http_client;
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
 
     get_config_data = user_data;
+    self            = NMCS_PROVIDER_EC2(get_config_data->self);
 
     response_parsed                     = g_steal_pointer(&get_config_data->extra_data);
     get_config_data->extra_data_destroy = NULL;
@@ -254,7 +270,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
               v_mac_data->path);
 
         get_config_data->n_pending++;
-        nm_http_client_poll_get(
+        nm_http_client_poll_req(
             http_client,
             (uri1 = _ec2_uri_interfaces(v_mac_data->path,
                                         NM_STR_HAS_SUFFIX(v_mac_data->path, "/") ? "" : "/",
@@ -263,6 +279,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             512 * 1024,
             10000,
             1000,
+            NM_MAKE_STRV(self->token),
             NULL,
             get_config_data->intern_cancellable,
             NULL,
@@ -271,7 +288,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             config_iface_data);
 
         get_config_data->n_pending++;
-        nm_http_client_poll_get(
+        nm_http_client_poll_req(
             http_client,
             (uri2 = _ec2_uri_interfaces(v_mac_data->path,
                                         NM_STR_HAS_SUFFIX(v_mac_data->path, "/") ? "" : "/",
@@ -280,6 +297,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             512 * 1024,
             10000,
             1000,
+            NM_MAKE_STRV(self->token),
             NULL,
             get_config_data->intern_cancellable,
             NULL,
@@ -365,18 +383,25 @@ _get_config_metadata_ready_check(long     response_code,
 static void
 get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_data)
 {
-    gs_free char *uri = NULL;
+    NMCSProviderEC2 *self = NMCS_PROVIDER_EC2(provider);
+    gs_free char    *uri  = NULL;
+
+    /* This can be called only if detect() succeeded, which implies
+     * there must be a token.
+     */
+    nm_assert(self->token);
 
     /* First we fetch the "macs/". If the caller requested some particular
      * MAC addresses, then we poll until we see them. They might not yet be
      * around from the start...
      */
-    nm_http_client_poll_get(nmcs_provider_get_http_client(provider),
+    nm_http_client_poll_req(nmcs_provider_get_http_client(provider),
                             (uri = _ec2_uri_interfaces()),
                             HTTP_TIMEOUT_MS,
                             256 * 1024,
                             15000,
                             1000,
+                            NM_MAKE_STRV(self->token),
                             NULL,
                             get_config_data->intern_cancellable,
                             _get_config_metadata_ready_check,
@@ -392,10 +417,23 @@ nmcs_provider_ec2_init(NMCSProviderEC2 *self)
 {}
 
 static void
+dispose(GObject *object)
+{
+    NMCSProviderEC2 *self = NMCS_PROVIDER_EC2(object);
+
+    nm_clear_g_free(&self->token);
+
+    G_OBJECT_CLASS(nmcs_provider_ec2_parent_class)->dispose(object);
+}
+
+static void
 nmcs_provider_ec2_class_init(NMCSProviderEC2Class *klass)
 {
+    GObjectClass      *object_class   = G_OBJECT_CLASS(klass);
     NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS(klass);
 
+    object_class->dispose = dispose;
+
     provider_class->_name                 = "ec2";
     provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_EC2");
     provider_class->detect                = detect;
diff --git a/src/nm-cloud-setup/nmcs-provider-gcp.c b/src/nm-cloud-setup/nmcs-provider-gcp.c
index a325f31a..ca354865 100644
--- a/src/nm-cloud-setup/nmcs-provider-gcp.c
+++ b/src/nm-cloud-setup/nmcs-provider-gcp.c
@@ -45,7 +45,7 @@ _detect_get_meta_data_done_cb(GObject *source, GAsyncResult *result, gpointer us
     gs_free_error GError  *get_error = NULL;
     gs_free_error GError  *error     = NULL;
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &get_error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &get_error);
 
     if (nm_utils_error_is_cancelled(get_error)) {
         g_task_return_error(task, g_steal_pointer(&get_error));
@@ -72,13 +72,14 @@ detect(NMCSProvider *provider, GTask *task)
 
     http_client = nmcs_provider_get_http_client(provider);
 
-    nm_http_client_poll_get(http_client,
+    nm_http_client_poll_req(http_client,
                             (uri = _gcp_uri_concat("id")),
                             HTTP_TIMEOUT_MS,
                             256 * 1024,
                             7000,
                             1000,
                             NM_MAKE_STRV(NM_GCP_METADATA_HEADER),
+                            NULL,
                             g_task_get_cancellable(task),
                             NULL,
                             NULL,
@@ -114,7 +115,7 @@ _get_config_fip_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     NMIPRoute                     **routes_arr;
     NMIPRoute                      *route_new;
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -171,7 +172,7 @@ _get_config_ips_list_cb(GObject *source, GAsyncResult *result, gpointer user_dat
     gsize                          line_len;
     guint                          i;
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -220,13 +221,14 @@ _get_config_ips_list_cb(GObject *source, GAsyncResult *result, gpointer user_dat
         const char         *str = uri_arr->pdata[i];
         gs_free const char *uri = NULL;
 
-        nm_http_client_poll_get(NM_HTTP_CLIENT(source),
+        nm_http_client_poll_req(NM_HTTP_CLIENT(source),
                                 (uri = _gcp_uri_interfaces(str)),
                                 HTTP_TIMEOUT_MS,
                                 HTTP_REQ_MAX_DATA,
                                 HTTP_POLL_TIMEOUT_MS,
                                 HTTP_RATE_LIMIT_MS,
                                 NM_MAKE_STRV(NM_GCP_METADATA_HEADER),
+                                NULL,
                                 get_config_data->intern_cancellable,
                                 NULL,
                                 NULL,
@@ -252,7 +254,7 @@ _get_config_iface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     NMCSProviderGetConfigTaskData *get_config_data;
     gboolean                       is_requested;
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -306,13 +308,14 @@ _get_config_iface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 
     nm_sprintf_buf(sbuf, "%" G_GSSIZE_FORMAT "/forwarded-ips/", iface_data->intern_iface_idx);
 
-    nm_http_client_poll_get(NM_HTTP_CLIENT(source),
+    nm_http_client_poll_req(NM_HTTP_CLIENT(source),
                             (uri = _gcp_uri_interfaces(sbuf)),
                             HTTP_TIMEOUT_MS,
                             HTTP_REQ_MAX_DATA,
                             HTTP_POLL_TIMEOUT_MS,
                             HTTP_RATE_LIMIT_MS,
                             NM_MAKE_STRV(NM_GCP_METADATA_HEADER),
+                            NULL,
                             get_config_data->intern_cancellable,
                             NULL,
                             NULL,
@@ -339,7 +342,7 @@ _get_net_ifaces_list_cb(GObject *source, GAsyncResult *result, gpointer user_dat
     guint                          i;
     gssize                         extern_iface_idx_cnt = 0;
 
-    nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
+    nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -405,13 +408,14 @@ _get_net_ifaces_list_cb(GObject *source, GAsyncResult *result, gpointer user_dat
         nm_sprintf_buf(sbuf, "%" G_GSSIZE_FORMAT "/mac", data->intern_iface_idx);
 
         get_config_data->n_pending++;
-        nm_http_client_poll_get(NM_HTTP_CLIENT(source),
+        nm_http_client_poll_req(NM_HTTP_CLIENT(source),
                                 (uri = _gcp_uri_interfaces(sbuf)),
                                 HTTP_TIMEOUT_MS,
                                 HTTP_REQ_MAX_DATA,
                                 HTTP_POLL_TIMEOUT_MS,
                                 HTTP_RATE_LIMIT_MS,
                                 NM_MAKE_STRV(NM_GCP_METADATA_HEADER),
+                                NULL,
                                 get_config_data->intern_cancellable,
                                 NULL,
                                 NULL,
@@ -428,13 +432,14 @@ get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_dat
 {
     gs_free const char *uri = NULL;
 
-    nm_http_client_poll_get(nmcs_provider_get_http_client(provider),
+    nm_http_client_poll_req(nmcs_provider_get_http_client(provider),
                             (uri = _gcp_uri_interfaces()),
                             HTTP_TIMEOUT_MS,
                             HTTP_REQ_MAX_DATA,
                             HTTP_POLL_TIMEOUT_MS,
                             HTTP_RATE_LIMIT_MS,
                             NM_MAKE_STRV(NM_GCP_METADATA_HEADER),
+                            NULL,
                             get_config_data->intern_cancellable,
                             NULL,
                             NULL,
diff --git a/src/nm-cloud-setup/nmcs-provider.h b/src/nm-cloud-setup/nmcs-provider.h
index 994b1e24..09cdb414 100644
--- a/src/nm-cloud-setup/nmcs-provider.h
+++ b/src/nm-cloud-setup/nmcs-provider.h
@@ -152,8 +152,36 @@ typedef struct {
     const char  *_name;
     const char  *_env_provider_enabled;
 
+    /**
+     * detect:
+     * @self: the #NMCSProvider
+     * @task: a #GTask that's completed when the detection finishes.
+     *
+     * Checks whether the metadata of a particular cloud provider is
+     * accessible on the host machine. The check runs asynchronously.
+     *
+     * When the check finishes, @task is completed. If the check was
+     * successful, @task returns a gboolean of %TRUE. Otherwise
+     * a %FALSE value or an error is returned.
+     *
+     * The routine has to be called before the get_config() can be
+     * used.
+     */
     void (*detect)(NMCSProvider *self, GTask *task);
 
+    /**
+     * get_config:
+     * @self: the #NMCSProvider
+     * @get_config_data: encapsulates a #GTask and network configuration data
+     *
+     * Collects the network configuration from metadata service of a
+     * particular cloud provider. The metadata is traversed and checked
+     * asynchronously, completing a task encapsulated in @get_config_data
+     * upon finishing.
+     *
+     * Call to detect() with a successful result is necessary before
+     * using this routine.
+     */
     void (*get_config)(NMCSProvider *self, NMCSProviderGetConfigTaskData *get_config_data);
 
 } NMCSProviderClass;
diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c
index c160fff6..d408b47f 100644
--- a/src/nmcli/devices.c
+++ b/src/nmcli/devices.c
@@ -17,6 +17,7 @@
 #include <linux/if_ether.h>
 
 #include "libnm-glib-aux/nm-secret-utils.h"
+#include "libnm-glib-aux/nm-random-utils.h"
 #include "common.h"
 #include "connections.h"
 #include "libnmc-base/nm-client-utils.h"
@@ -4090,7 +4091,7 @@ generate_ssid_for_hotspot(void)
     return ssid_bytes;
 }
 
-#define WPA_PASSKEY_SIZE 8
+#define WPA_PASSKEY_SIZE 12
 static void
 generate_wpa_key(char *key, size_t len)
 {
@@ -4099,13 +4100,14 @@ generate_wpa_key(char *key, size_t len)
     g_return_if_fail(key);
     g_return_if_fail(len > WPA_PASSKEY_SIZE);
 
-    /* generate a 8-chars ASCII WPA key */
     for (i = 0; i < WPA_PASSKEY_SIZE; i++) {
         int c;
-        c = g_random_int_range(33, 126);
-        /* too many non alphanumeric characters are hard to remember for humans */
-        while (!g_ascii_isalnum(c))
-            c = g_random_int_range(33, 126);
+
+        do {
+            c = nm_random_u64_range_full(48, 122, TRUE);
+            /* skip characters that look similar */
+        } while (NM_IN_SET(c, '1', 'l', 'I', '0', 'O', 'Q', '8', 'B', '5', 'S')
+                 || !g_ascii_isalnum(c));
 
         key[i] = (char) c;
     }
@@ -4124,7 +4126,8 @@ generate_wep_key(char *key, size_t len)
     /* generate a 10-digit hex WEP key */
     for (i = 0; i < 10; i++) {
         int digit;
-        digit  = g_random_int_range(0, 16);
+
+        digit  = nm_random_u64_range_full(0, 16, TRUE);
         key[i] = hexdigits[digit];
     }
     key[10] = '\0';
@@ -4138,7 +4141,7 @@ set_wireless_security_for_hotspot(NMSettingWirelessSecurity *s_wsec,
                                   gboolean                   show_password,
                                   GError                   **error)
 {
-    char        generated_key[11];
+    char        generated_key[20];
     const char *key;
     const char *key_mgmt;
 
@@ -4264,6 +4267,8 @@ create_hotspot_conn(const GPtrArray *connections,
     NMSettingIPConfig         *s_ip4, *s_ip6;
     NMSettingProxy            *s_proxy;
 
+    nm_assert(channel_int == -1 || band);
+
     connection = nm_simple_connection_new();
     s_con      = (NMSettingConnection *) nm_setting_connection_new();
     nm_connection_add_setting(connection, NM_SETTING(s_con));
@@ -4294,6 +4299,8 @@ create_hotspot_conn(const GPtrArray *connections,
                      NM_SETTING_WIRELESS_BAND,
                      band,
                      NULL);
+    } else if (band) {
+        g_object_set(s_wifi, NM_SETTING_WIRELESS_BAND, band, NULL);
     }
 
     s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
@@ -4439,13 +4446,6 @@ do_device_wifi_hotspot(const NMCCommand *cmd, NmCli *nmc, int argc, const char *
     if (nmc->complete)
         return;
 
-    /* Verify band and channel parameters */
-    if (!channel) {
-        if (g_strcmp0(band, "bg") == 0)
-            channel = "1";
-        if (g_strcmp0(band, "a") == 0)
-            channel = "7";
-    }
     if (channel) {
         unsigned long int value;
 
diff --git a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
index dfea3c34..a3409afb 100644
--- a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
+++ b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
@@ -640,7 +640,7 @@
         <property name="output-key"
                   description="The key used for tunnel output packets; the property is valid only for certain tunnel modes (GRE, IP6GRE). If empty, no key is used." />
         <property name="encapsulation-limit"
-                  description="How many additional levels of encapsulation are permitted to be prepended to packets. This property applies only to IPv6 tunnels." />
+                  description="How many additional levels of encapsulation are permitted to be prepended to packets. This property applies only to IPv6 tunnels. To disable this option, add NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1) to ip-tunnel flags." />
         <property name="flow-label"
                   description="The flow label to assign to tunnel packets. This property applies only to IPv6 tunnels." />
         <property name="fwmark"
@@ -658,7 +658,7 @@
         <property name="dns-search"
                   description="List of DNS search domains. Domains starting with a tilde (&apos;~&apos;) are considered &apos;routing&apos; domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting &quot;ignore-auto-dns&quot;. Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." />
         <property name="dns-options"
-                  description="Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are &quot;attempts&quot;, &quot;debug&quot;, &quot;edns0&quot;, &quot;inet6&quot;, &quot;ip6-bytestring&quot;, &quot;ip6-dotint&quot;, &quot;ndots&quot;, &quot;no-check-names&quot;, &quot;no-ip6-dotint&quot;, &quot;no-reload&quot;, &quot;no-tld-query&quot;, &quot;rotate&quot;, &quot;single-request&quot;, &quot;single-request-reopen&quot;, &quot;timeout&quot;, &quot;trust-ad&quot;, &quot;use-vc&quot;. The &quot;trust-ad&quot; setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have &quot;trust-ad&quot; enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then &quot;edns0&quot; and &quot;trust-ad&quot; are automatically added." />
+                  description="Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are &quot;attempts&quot;, &quot;debug&quot;, &quot;edns0&quot;, &quot;inet6&quot;, &quot;ip6-bytestring&quot;, &quot;ip6-dotint&quot;, &quot;ndots&quot;, &quot;no-aaaa&quot;, &quot;no-check-names&quot;, &quot;no-ip6-dotint&quot;, &quot;no-reload&quot;, &quot;no-tld-query&quot;, &quot;rotate&quot;, &quot;single-request&quot;, &quot;single-request-reopen&quot;, &quot;timeout&quot;, &quot;trust-ad&quot;, &quot;use-vc&quot;. The &quot;trust-ad&quot; setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have &quot;trust-ad&quot; enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then &quot;edns0&quot; and &quot;trust-ad&quot; are automatically added." />
         <property name="dns-priority"
                   description="DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the &quot;rotate&quot; option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the &apos;~.&apos; special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured." />
         <property name="addresses"
@@ -719,7 +719,7 @@
         <property name="dns-search"
                   description="List of DNS search domains. Domains starting with a tilde (&apos;~&apos;) are considered &apos;routing&apos; domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting &quot;ignore-auto-dns&quot;. Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." />
         <property name="dns-options"
-                  description="Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are &quot;attempts&quot;, &quot;debug&quot;, &quot;edns0&quot;, &quot;inet6&quot;, &quot;ip6-bytestring&quot;, &quot;ip6-dotint&quot;, &quot;ndots&quot;, &quot;no-check-names&quot;, &quot;no-ip6-dotint&quot;, &quot;no-reload&quot;, &quot;no-tld-query&quot;, &quot;rotate&quot;, &quot;single-request&quot;, &quot;single-request-reopen&quot;, &quot;timeout&quot;, &quot;trust-ad&quot;, &quot;use-vc&quot;. The &quot;trust-ad&quot; setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have &quot;trust-ad&quot; enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then &quot;edns0&quot; and &quot;trust-ad&quot; are automatically added." />
+                  description="Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are &quot;attempts&quot;, &quot;debug&quot;, &quot;edns0&quot;, &quot;inet6&quot;, &quot;ip6-bytestring&quot;, &quot;ip6-dotint&quot;, &quot;ndots&quot;, &quot;no-aaaa&quot;, &quot;no-check-names&quot;, &quot;no-ip6-dotint&quot;, &quot;no-reload&quot;, &quot;no-tld-query&quot;, &quot;rotate&quot;, &quot;single-request&quot;, &quot;single-request-reopen&quot;, &quot;timeout&quot;, &quot;trust-ad&quot;, &quot;use-vc&quot;. The &quot;trust-ad&quot; setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have &quot;trust-ad&quot; enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then &quot;edns0&quot; and &quot;trust-ad&quot; are automatically added." />
         <property name="dns-priority"
                   description="DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the &quot;rotate&quot; option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the &apos;~.&apos; special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured." />
         <property name="addresses"