summary refs log tree commit diff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/devices/nm-device.c127
-rw-r--r--src/core/devices/nm-device.h2
-rw-r--r--src/core/devices/wwan/nm-modem-ofono.c5
-rw-r--r--src/core/dhcp/nm-dhcp-client.c35
-rw-r--r--src/core/dhcp/nm-dhcp-nettools.c62
-rw-r--r--src/core/dhcp/nm-dhcp-options.c36
-rw-r--r--src/core/dhcp/nm-dhcp-options.h28
-rw-r--r--src/core/dhcp/nm-dhcp-systemd.c33
-rw-r--r--src/core/nm-active-connection.c16
-rw-r--r--src/core/nm-active-connection.h1
-rw-r--r--src/core/nm-core-utils.c48
-rw-r--r--src/core/nm-dispatcher.c2
-rw-r--r--src/core/nm-policy.c2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c8
-rw-r--r--src/core/vpn/nm-vpn-connection.c5
18 files changed, 299 insertions, 119 deletions
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 04478614..3565c04d 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -312,7 +312,7 @@ typedef struct {
 
 typedef enum {
     RESOLVER_WAIT_ADDRESS = 0,
-    RESOLVER_IN_PROGRESS,
+    RESOLVER_STARTED,
     RESOLVER_DONE,
 } ResolverState;
 
@@ -1810,6 +1810,7 @@ _prop_get_ipvx_dhcp_iaid(NMDevice     *self,
     const char        *iface;
     const char        *fail_reason;
     gboolean           is_explicit = TRUE;
+    gint64             i64;
 
     s_ip     = nm_connection_get_setting_ip_config(connection, addr_family);
     iaid_str = nm_setting_ip_config_get_dhcp_iaid(s_ip);
@@ -1868,7 +1869,7 @@ _prop_get_ipvx_dhcp_iaid(NMDevice     *self,
 
         iaid = unaligned_read_be32(&hwaddr_buf[hwaddr_len - 4]);
         goto out_good;
-    } else if (nm_streq(iaid_str, "stable")) {
+    } else if (nm_streq(iaid_str, NM_IAID_STABLE)) {
         nm_auto_free_checksum GChecksum *sum = NULL;
         guint8                           digest[NM_UTILS_CHECKSUM_LENGTH_SHA1];
         NMUtilsStableType                stable_type;
@@ -1891,14 +1892,21 @@ _prop_get_ipvx_dhcp_iaid(NMDevice     *self,
 
         iaid = unaligned_read_be32(digest);
         goto out_good;
-    } else if ((iaid = _nm_utils_ascii_str_to_int64(iaid_str, 10, 0, G_MAXUINT32, -1)) != -1) {
-        goto out_good;
-    } else {
+    } else if (nm_streq(iaid_str, NM_IAID_IFNAME)) {
         iface = nm_device_get_ip_iface(self);
         iaid  = nm_utils_create_dhcp_iaid(TRUE, (const guint8 *) iface, strlen(iface));
         goto out_good;
+    } else if (_nm_utils_iaid_verify(iaid_str, &i64)) {
+        if (i64 < 0) {
+            fail_reason = nm_assert_unreachable_val("bug handling iaid value");
+            goto out_fail;
+        }
+        nm_assert(i64 <= G_MAXUINT32);
+        iaid = (guint32) i64;
+        goto out_good;
     }
 
+    fail_reason = nm_assert_unreachable_val("bug handling iaid code");
 out_fail:
     nm_assert(fail_reason);
     if (!log_silent) {
@@ -1912,11 +1920,13 @@ out_fail:
     iaid        = nm_utils_create_dhcp_iaid(TRUE, (const guint8 *) iface, strlen(iface));
 out_good:
     if (!log_silent) {
+        char buf[NM_DHCP_IAID_TO_HEXSTR_BUF_LEN];
+
         _LOGD(LOGD_DEVICE | LOGD_DHCPX(IS_IPv4) | LOGD_IPX(IS_IPv4),
-              "ipv%c.dhcp-iaid: using %u (0x%08x) IAID (str: '%s', explicit %d)",
+              "ipv%c.dhcp-iaid: using %u (%s) IAID (str: '%s', explicit %d)",
               nm_utils_addr_family_to_char(addr_family),
               iaid,
-              iaid,
+              nm_dhcp_iaid_to_hexstr(iaid, buf),
               iaid_str,
               is_explicit);
     }
@@ -7168,6 +7178,9 @@ nm_device_update_from_platform_link(NMDevice *self, const NMPlatformLink *plink)
 
     ifindex_changed = _set_ifindex(self, plink ? plink->ifindex : 0, FALSE);
 
+    nm_device_update_hw_address(self);
+    nm_device_update_permanent_hw_address(self, FALSE);
+
     if (ifindex_changed)
         NM_DEVICE_GET_CLASS(self)->link_changed(self, plink);
 
@@ -8581,6 +8594,7 @@ nm_device_generate_connection(NMDevice *self,
                         nm_device_get_iface(master),
                         local->message);
             g_error_free(local);
+            NM_SET_OUT(out_maybe_later, TRUE);
             return NULL;
         }
     } else {
@@ -9641,6 +9655,18 @@ _routing_rules_sync(NMDevice *self, NMTernary set_mode)
                                               user_tag_1,
                                               NMP_GLOBAL_TRACKER_EXTERN_WEAKLY_TRACKED_USER_TAG);
             }
+
+            if (nm_setting_ip_config_get_replace_local_rule(s_ip) == NM_TERNARY_TRUE) {
+                /* The user specified that the local rule should be replaced.
+                 * In order to do that, we track the local rule with negative
+                 * priority. */
+                nmp_global_tracker_track_local_rule(
+                    global_tracker,
+                    addr_family,
+                    -5,
+                    user_tag_1,
+                    NMP_GLOBAL_TRACKER_EXTERN_WEAKLY_TRACKED_USER_TAG);
+            }
         }
 
         if (klass->get_extra_rules) {
@@ -17093,6 +17119,21 @@ nm_device_auth_retries_try_next(NMDevice *self)
     return TRUE;
 }
 
+static const char *
+_resolver_state_to_string(ResolverState state)
+{
+    switch (state) {
+    case RESOLVER_WAIT_ADDRESS:
+        return "WAIT-ADDRESS";
+    case RESOLVER_STARTED:
+        return "STARTED";
+    case RESOLVER_DONE:
+        return "DONE";
+    }
+    nm_assert_not_reached();
+    return "UNKNOWN";
+}
+
 static void
 hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer user_data)
 {
@@ -17112,7 +17153,9 @@ hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer use
 
     if (error) {
         _LOGD(LOGD_DNS,
-              "hostname-from-dns: lookup error for %s: %s",
+              "hostname-from-dns: ipv%c resolver %s: lookup error for %s: %s",
+              nm_utils_addr_family_to_char(resolver->addr_family),
+              _resolver_state_to_string(RESOLVER_DONE),
               (addr_str = g_inet_address_to_string(resolver->address)),
               error->message);
     } else {
@@ -17122,7 +17165,9 @@ hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer use
         valid              = nm_utils_validate_hostname(resolver->hostname);
 
         _LOGD(LOGD_DNS,
-              "hostname-from-dns: lookup done for %s, result %s%s%s%s",
+              "hostname-from-dns: ipv%c resolver %s: lookup successful for %s, result %s%s%s%s",
+              nm_utils_addr_family_to_char(resolver->addr_family),
+              _resolver_state_to_string(RESOLVER_DONE),
               (addr_str = g_inet_address_to_string(resolver->address)),
               NM_PRINT_FMT_QUOTE_STRING(resolver->hostname),
               valid ? "" : " (invalid)");
@@ -17148,8 +17193,9 @@ hostname_dns_address_timeout(gpointer user_data)
     nm_assert(!resolver->cancellable);
 
     _LOGT(LOGD_DNS,
-          "hostname-from-dns: timed out while waiting IPv%c address",
-          nm_utils_addr_family_to_char(resolver->addr_family));
+          "hostname-from-dns: ipv%c state %s: timed out while waiting for address",
+          nm_utils_addr_family_to_char(resolver->addr_family),
+          _resolver_state_to_string(RESOLVER_DONE));
 
     resolver->timeout_id = 0;
     resolver->state      = RESOLVER_DONE;
@@ -17158,30 +17204,16 @@ hostname_dns_address_timeout(gpointer user_data)
     return G_SOURCE_REMOVE;
 }
 
-static const char *
-_resolver_state_to_string(ResolverState state)
-{
-    switch (state) {
-    case RESOLVER_WAIT_ADDRESS:
-        return "wait-address";
-    case RESOLVER_IN_PROGRESS:
-        return "in-progress";
-    case RESOLVER_DONE:
-        return "done";
-    default:
-        nm_assert_not_reached();
-        return "unknown";
-    }
-}
-
 void
-nm_device_clear_dns_lookup_data(NMDevice *self)
+nm_device_clear_dns_lookup_data(NMDevice *self, const char *reason)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    guint            i;
 
-    for (i = 0; i < 2; i++)
-        nm_clear_pointer(&priv->hostname_resolver_x[i], _hostname_resolver_free);
+    if (priv->hostname_resolver_4 || priv->hostname_resolver_6) {
+        _LOGT(LOGD_DNS, "hostname-from-dns: resetting (%s)", reason);
+        nm_clear_pointer(&priv->hostname_resolver_4, _hostname_resolver_free);
+        nm_clear_pointer(&priv->hostname_resolver_6, _hostname_resolver_free);
+    }
 }
 
 gboolean
@@ -17222,6 +17254,9 @@ get_address_for_hostname_dns_lookup(NMDevice *self, int addr_family)
                 return g_inet_address_new_from_bytes(addr->ax.address_ptr, G_SOCKET_FAMILY_IPV4);
             }
 
+            if (addr->ax.n_ifa_flags & IFA_F_TENTATIVE)
+                continue;
+
             /* For IPv6 prefer, in order:
              * - !link-local, !deprecated
              * - !link-local, deprecated
@@ -17315,29 +17350,35 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean
     } else if (new_address != resolver->address)
         address_changed = TRUE;
 
+    if (address_changed) {
+        /* set new state before logging */
+        if (new_address)
+            resolver->state = RESOLVER_STARTED;
+        else
+            resolver->state = RESOLVER_WAIT_ADDRESS;
+    }
+
     {
         gs_free char *old_str = NULL;
         gs_free char *new_str = NULL;
 
-        _LOGT(LOGD_DNS,
-              "hostname-from-dns: ipv%c resolver state %s, old address %s, new address %s",
-              nm_utils_addr_family_to_char(resolver->addr_family),
-              _resolver_state_to_string(resolver->state),
-              resolver->address ? (old_str = g_inet_address_to_string(resolver->address))
-                                : "(null)",
-              new_address ? (new_str = g_inet_address_to_string(new_address)) : "(null)");
+        if (address_changed) {
+            _LOGT(LOGD_DNS,
+                  "hostname-from-dns: ipv%c resolver %s, address changed from %s to %s",
+                  nm_utils_addr_family_to_char(resolver->addr_family),
+                  _resolver_state_to_string(resolver->state),
+                  resolver->address ? (old_str = g_inet_address_to_string(resolver->address))
+                                    : "(null)",
+                  new_address ? (new_str = g_inet_address_to_string(new_address)) : "(null)");
+        }
     }
 
-    /* In every state, if the address changed, we restart
-     * the resolution with the new address */
     if (address_changed) {
         nm_clear_g_cancellable(&resolver->cancellable);
         g_clear_object(&resolver->address);
-        resolver->state = RESOLVER_WAIT_ADDRESS;
     }
 
     if (address_changed && new_address) {
-        resolver->state       = RESOLVER_IN_PROGRESS;
         resolver->cancellable = g_cancellable_new();
         resolver->address     = g_steal_pointer(&new_address);
 
@@ -17355,7 +17396,7 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean
             resolver->timeout_id = g_timeout_add(30000, hostname_dns_address_timeout, resolver);
         NM_SET_OUT(out_wait, TRUE);
         return NULL;
-    case RESOLVER_IN_PROGRESS:
+    case RESOLVER_STARTED:
         NM_SET_OUT(out_wait, TRUE);
         return NULL;
     case RESOLVER_DONE:
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index f54457d1..bcf4d7b9 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -817,7 +817,7 @@ gboolean nm_device_is_vpn(NMDevice *self);
 const char *
 nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean *out_pending);
 
-void nm_device_clear_dns_lookup_data(NMDevice *self);
+void nm_device_clear_dns_lookup_data(NMDevice *self, const char *reason);
 
 gboolean nm_device_get_allow_autoconnect_on_external(NMDevice *self);
 
diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c
index b3745621..33a19e93 100644
--- a/src/core/devices/wwan/nm-modem-ofono.c
+++ b/src/core/devices/wwan/nm-modem-ofono.c
@@ -734,7 +734,7 @@ update_connection_list(NMModemOfono *self)
 
     g_hash_table_iter_init(&iter, priv->contexts);
     while (g_hash_table_iter_next(&iter, (gpointer *) &uuid, (gpointer *) &octx)) {
-        if (octx->preferred) {
+        if (octx->preferred && nm_streq(octx->type, "internet")) {
             octx_preferred = octx;
             break;
         }
@@ -743,8 +743,7 @@ update_connection_list(NMModemOfono *self)
     g_hash_table_iter_init(&iter, priv->contexts);
     while (g_hash_table_iter_next(&iter, (gpointer *) &uuid, (gpointer *) &octx)) {
         gboolean connection_should_exist =
-            (!octx_preferred || octx_preferred == octx)
-            && (nm_streq(octx->type, "internet") || nm_streq(octx->type, "internet+mms"));
+            octx_preferred == octx || (!octx_preferred && nm_streq(octx->type, "internet"));
         gboolean connection_exists = g_hash_table_contains(priv->connections, uuid);
 
         if (connection_should_exist && !connection_exists) {
diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c
index 600cb930..1fc2d944 100644
--- a/src/core/dhcp/nm-dhcp-client.c
+++ b/src/core/dhcp/nm-dhcp-client.c
@@ -241,7 +241,8 @@ nm_dhcp_client_create_l3cd(NMDhcpClient *self)
 GHashTable *
 nm_dhcp_client_create_options_dict(NMDhcpClient *self, gboolean static_keys)
 {
-    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+    NMDhcpClientPrivate *priv    = NM_DHCP_CLIENT_GET_PRIVATE(self);
+    const int            IS_IPv4 = NM_IS_IPv4(priv->config.addr_family);
     GHashTable          *options;
     GBytes              *effective_client_id;
 
@@ -249,22 +250,18 @@ nm_dhcp_client_create_options_dict(NMDhcpClient *self, gboolean static_keys)
 
     effective_client_id = nm_dhcp_client_get_effective_client_id(self);
     if (effective_client_id) {
-        guint         option = NM_IS_IPv4(priv->config.addr_family) ? NM_DHCP_OPTION_DHCP4_CLIENT_ID
-                                                                    : NM_DHCP_OPTION_DHCP6_CLIENT_ID;
-        gs_free char *str    = nm_dhcp_utils_duid_to_string(effective_client_id);
+        guint option = IS_IPv4 ? NM_DHCP_OPTION_DHCP4_CLIENT_ID : NM_DHCP_OPTION_DHCP6_CLIENT_ID;
+        gs_free char *str = nm_dhcp_utils_duid_to_string(effective_client_id);
 
         /* Note that for the nm-dhcp-helper based plugins (dhclient), the plugin
          * may send the used client-id/DUID via the environment variables and
          * overwrite them yet again. */
 
-        if (static_keys) {
-            nm_dhcp_option_add_option(options, priv->config.addr_family, option, str);
-        } else {
-            g_hash_table_insert(
-                options,
-                g_strdup(nm_dhcp_option_request_string(priv->config.addr_family, option)),
-                g_steal_pointer(&str));
-        }
+        nm_dhcp_option_take_option(options,
+                                   static_keys,
+                                   priv->config.addr_family,
+                                   option,
+                                   g_steal_pointer(&str));
     }
 
     return options;
@@ -1591,6 +1588,20 @@ maybe_add_option(NMDhcpClient *self, GHashTable *hash, const char *key, GVariant
         str_value = nm_dhcp_utils_duid_to_string(bytes);
     }
 
+    if (!IS_IPv4 && nm_streq(key, "iaid")) {
+        gs_free char *str = g_steal_pointer(&str_value);
+        guint32       iaid;
+
+        /* Validate and normalize the iaid. */
+
+        if (!nm_dhcp_iaid_from_hexstr(str, &iaid)) {
+            /* Seems invalid. Ignore */
+            return;
+        }
+
+        str_value = nm_dhcp_iaid_to_hexstr(iaid, g_malloc(NM_DHCP_IAID_TO_HEXSTR_BUF_LEN));
+    }
+
     g_hash_table_insert(hash, g_strdup(key), str_value);
 
     /* dhclient has no special labels for private dhcp options: it uses "unknown_xyz"
diff --git a/src/core/dhcp/nm-dhcp-nettools.c b/src/core/dhcp/nm-dhcp-nettools.c
index 9cdfd9aa..f36dfb4d 100644
--- a/src/core/dhcp/nm-dhcp-nettools.c
+++ b/src/core/dhcp/nm-dhcp-nettools.c
@@ -84,6 +84,11 @@ static void dhcp4_event_pop_all_events_on_idle(NMDhcpNettools *self);
 
 /*****************************************************************************/
 
+#define _add_option(options, option, str) \
+    nm_dhcp_option_add_option((options), TRUE, AF_INET, (option), (str))
+
+/*****************************************************************************/
+
 static void
 set_error_nettools(GError **error, int r, const char *message)
 {
@@ -266,26 +271,34 @@ lease_parse_address(NMDhcpNettools    *self /* for logging context only */,
     }
 
     nm_dhcp_option_add_option_in_addr(options,
+                                      TRUE,
                                       AF_INET,
                                       NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS,
                                       a_address.s_addr);
     nm_dhcp_option_add_option_in_addr(options,
+                                      TRUE,
                                       AF_INET,
                                       NM_DHCP_OPTION_DHCP4_SUBNET_MASK,
                                       a_netmask);
 
     nm_dhcp_option_add_option_u64(options,
+                                  TRUE,
                                   AF_INET,
                                   NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME,
                                   (guint64) a_lifetime);
 
     if (a_expiry != G_MAXUINT64) {
-        nm_dhcp_option_add_option_u64(options, AF_INET, NM_DHCP_OPTION_DHCP4_NM_EXPIRY, a_expiry);
+        nm_dhcp_option_add_option_u64(options,
+                                      TRUE,
+                                      AF_INET,
+                                      NM_DHCP_OPTION_DHCP4_NM_EXPIRY,
+                                      a_expiry);
     }
 
     n_dhcp4_client_lease_get_siaddr(lease, &a_next_server);
     if (a_next_server.s_addr != INADDR_ANY) {
         nm_dhcp_option_add_option_in_addr(options,
+                                          TRUE,
                                           AF_INET,
                                           NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER,
                                           a_next_server.s_addr);
@@ -368,7 +381,7 @@ lease_parse_address_list(NDhcp4ClientLease       *lease,
         }
     }
 
-    nm_dhcp_option_add_option(options, AF_INET, option, nm_str_buf_get_str(sbuf));
+    _add_option(options, option, nm_str_buf_get_str(sbuf));
 }
 
 static void
@@ -446,7 +459,7 @@ lease_parse_routes(NDhcp4ClientLease *lease,
         }
 
         has_classless = TRUE;
-        nm_dhcp_option_add_option(options, AF_INET, option_code, nm_str_buf_get_str(sbuf));
+        _add_option(options, option_code, nm_str_buf_get_str(sbuf));
     }
 
     r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, &l_data, &l_data_len);
@@ -489,10 +502,7 @@ lease_parse_routes(NDhcp4ClientLease *lease,
                                           }));
         }
 
-        nm_dhcp_option_add_option(options,
-                                  AF_INET,
-                                  NM_DHCP_OPTION_DHCP4_STATIC_ROUTE,
-                                  nm_str_buf_get_str(sbuf));
+        _add_option(options, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, nm_str_buf_get_str(sbuf));
     }
 
     r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_ROUTER, &l_data, &l_data_len);
@@ -534,10 +544,7 @@ lease_parse_routes(NDhcp4ClientLease *lease,
                                           }));
         }
 
-        nm_dhcp_option_add_option(options,
-                                  AF_INET,
-                                  NM_DHCP_OPTION_DHCP4_ROUTER,
-                                  nm_str_buf_get_str(sbuf));
+        _add_option(options, NM_DHCP_OPTION_DHCP4_ROUTER, nm_str_buf_get_str(sbuf));
     }
 }
 
@@ -570,6 +577,7 @@ lease_parse_search_domains(NDhcp4ClientLease *lease,
         nm_l3_config_data_add_search(l3cd, AF_INET, domains[i]);
 
     nm_dhcp_option_take_option(options,
+                               TRUE,
                                AF_INET,
                                NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST,
                                g_strjoinv(" ", domains));
@@ -598,7 +606,7 @@ lease_parse_private_options(NDhcp4ClientLease *lease, GHashTable *options)
             continue;
 
         option_string = nm_utils_bin2hexstr_full(l_data, l_data_len, ':', FALSE, NULL);
-        nm_dhcp_option_take_option(options, AF_INET, i, g_steal_pointer(&option_string));
+        nm_dhcp_option_take_option(options, TRUE, AF_INET, i, g_steal_pointer(&option_string));
     }
 }
 
@@ -632,6 +640,7 @@ lease_to_ip4_config(NMDhcpNettools *self, NDhcp4ClientLease *lease, GError **err
     r = n_dhcp4_client_lease_get_server_identifier(lease, &v_inaddr_s);
     if (r == 0) {
         nm_dhcp_option_add_option_in_addr(options,
+                                          TRUE,
                                           AF_INET,
                                           NM_DHCP_OPTION_DHCP4_SERVER_ID,
                                           v_inaddr_s.s_addr);
@@ -645,6 +654,7 @@ lease_to_ip4_config(NMDhcpNettools *self, NDhcp4ClientLease *lease, GError **err
                                             iface,
                                             NM_DHCP_OPTION_DHCP4_BROADCAST)) {
         nm_dhcp_option_add_option_in_addr(options,
+                                          TRUE,
                                           AF_INET,
                                           NM_DHCP_OPTION_DHCP4_BROADCAST,
                                           v_inaddr);
@@ -696,10 +706,7 @@ lease_to_ip4_config(NMDhcpNettools *self, NDhcp4ClientLease *lease, GError **err
         }
 
         if (sbuf.len > 0) {
-            nm_dhcp_option_add_option(options,
-                                      AF_INET,
-                                      NM_DHCP_OPTION_DHCP4_DOMAIN_NAME,
-                                      nm_str_buf_get_str(&sbuf));
+            _add_option(options, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, nm_str_buf_get_str(&sbuf));
         }
     }
 
@@ -713,7 +720,11 @@ lease_to_ip4_config(NMDhcpNettools *self, NDhcp4ClientLease *lease, GError **err
                                         iface,
                                         AF_INET,
                                         NM_DHCP_OPTION_DHCP4_INTERFACE_MTU)) {
-        nm_dhcp_option_add_option_u64(options, AF_INET, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, v_u16);
+        nm_dhcp_option_add_option_u64(options,
+                                      TRUE,
+                                      AF_INET,
+                                      NM_DHCP_OPTION_DHCP4_INTERFACE_MTU,
+                                      v_u16);
         nm_l3_config_data_set_mtu(l3cd, v_u16);
     }
 
@@ -731,7 +742,7 @@ lease_to_ip4_config(NMDhcpNettools *self, NDhcp4ClientLease *lease, GError **err
                                             iface,
                                             AF_INET,
                                             NM_DHCP_OPTION_DHCP4_HOST_NAME)) {
-            nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_HOST_NAME, s);
+            _add_option(options, NM_DHCP_OPTION_DHCP4_HOST_NAME, s);
         }
     }
 
@@ -755,6 +766,7 @@ lease_to_ip4_config(NMDhcpNettools *self, NDhcp4ClientLease *lease, GError **err
             /* "Its minimum length is 1." */
         } else {
             nm_dhcp_option_add_option_utf8safe_escape(options,
+                                                      TRUE,
                                                       AF_INET,
                                                       NM_DHCP_OPTION_DHCP4_ROOT_PATH,
                                                       l_data,
@@ -782,10 +794,7 @@ lease_to_ip4_config(NMDhcpNettools *self, NDhcp4ClientLease *lease, GError **err
         const char   *escaped;
 
         escaped = nm_utils_buf_utf8safe_escape((char *) l_data, l_data_len, 0, &to_free);
-        nm_dhcp_option_add_option(options,
-                                  AF_INET,
-                                  NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY,
-                                  escaped ?: "");
+        _add_option(options, NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, escaped ?: "");
 
         nm_l3_config_data_set_proxy_method(l3cd, NM_PROXY_CONFIG_METHOD_AUTO);
         nm_l3_config_data_set_proxy_pac_url(l3cd, escaped ?: "");
@@ -808,7 +817,7 @@ lease_to_ip4_config(NMDhcpNettools *self, NDhcp4ClientLease *lease, GError **err
                                              NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
                                              &to_free);
 
-        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, v_str ?: "");
+        _add_option(options, NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, v_str ?: "");
         nm_l3_config_data_set_nis_domain(l3cd, v_str ?: "");
     }
 
@@ -820,7 +829,7 @@ lease_to_ip4_config(NMDhcpNettools *self, NDhcp4ClientLease *lease, GError **err
                                              -1,
                                              NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
                                              &to_free);
-        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NM_FILENAME, v_str ?: "");
+        _add_option(options, NM_DHCP_OPTION_DHCP4_NM_FILENAME, v_str ?: "");
     }
 
     r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_BOOTFILE_NAME, &l_data, &l_data_len);
@@ -837,10 +846,7 @@ lease_to_ip4_config(NMDhcpNettools *self, NDhcp4ClientLease *lease, GError **err
                                              l_data_len,
                                              NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
                                              &to_free);
-        nm_dhcp_option_add_option(options,
-                                  AF_INET,
-                                  NM_DHCP_OPTION_DHCP4_BOOTFILE_NAME,
-                                  v_str ?: "");
+        _add_option(options, NM_DHCP_OPTION_DHCP4_BOOTFILE_NAME, v_str ?: "");
     }
 
     lease_parse_address_list(lease, l3cd, iface, NM_DHCP_OPTION_DHCP4_NIS_SERVERS, options, &sbuf);
diff --git a/src/core/dhcp/nm-dhcp-options.c b/src/core/dhcp/nm-dhcp-options.c
index d95fe016..33a9f4ed 100644
--- a/src/core/dhcp/nm-dhcp-options.c
+++ b/src/core/dhcp/nm-dhcp-options.c
@@ -383,8 +383,14 @@ nm_dhcp_option_find(int addr_family, guint option)
 /*****************************************************************************/
 
 void
-nm_dhcp_option_take_option(GHashTable *options, int addr_family, guint option, char *value)
+nm_dhcp_option_take_option(GHashTable *options,
+                           gboolean    static_keys,
+                           int         addr_family,
+                           guint       option,
+                           char       *value)
 {
+    const char *key;
+
     nm_assert_addr_family(addr_family);
     nm_assert(value);
     nm_assert(g_utf8_validate(value, -1, NULL));
@@ -395,19 +401,13 @@ nm_dhcp_option_take_option(GHashTable *options, int addr_family, guint option, c
         return;
     }
 
-    g_hash_table_insert(options,
-                        (gpointer) nm_dhcp_option_request_string(addr_family, option),
-                        value);
-}
-
-void
-nm_dhcp_option_add_option(GHashTable *options, int addr_family, guint option, const char *value)
-{
-    nm_dhcp_option_take_option(options, addr_family, option, g_strdup(value));
+    key = nm_dhcp_option_request_string(addr_family, option),
+    g_hash_table_insert(options, static_keys ? (gpointer) key : g_strdup(key), value);
 }
 
 void
 nm_dhcp_option_add_option_utf8safe_escape(GHashTable   *options,
+                                          gboolean      static_keys,
                                           int           addr_family,
                                           guint         option,
                                           const guint8 *data,
@@ -420,13 +420,18 @@ nm_dhcp_option_add_option_utf8safe_escape(GHashTable   *options,
                                            n_data,
                                            NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
                                            &to_free);
-    nm_dhcp_option_add_option(options, addr_family, option, escaped ?: "");
+    nm_dhcp_option_add_option(options, static_keys, addr_family, option, escaped ?: "");
 }
 
 void
-nm_dhcp_option_add_option_u64(GHashTable *options, int addr_family, guint option, guint64 value)
+nm_dhcp_option_add_option_u64(GHashTable *options,
+                              gboolean    static_keys,
+                              int         addr_family,
+                              guint       option,
+                              guint64     value)
 {
     nm_dhcp_option_take_option(options,
+                               static_keys,
                                addr_family,
                                option,
                                g_strdup_printf("%" G_GUINT64_FORMAT, value));
@@ -434,13 +439,18 @@ nm_dhcp_option_add_option_u64(GHashTable *options, int addr_family, guint option
 
 void
 nm_dhcp_option_add_option_in_addr(GHashTable *options,
+                                  gboolean    static_keys,
                                   int         addr_family,
                                   guint       option,
                                   in_addr_t   value)
 {
     char sbuf[NM_INET_ADDRSTRLEN];
 
-    nm_dhcp_option_add_option(options, addr_family, option, nm_inet4_ntop(value, sbuf));
+    nm_dhcp_option_add_option(options,
+                              static_keys,
+                              addr_family,
+                              option,
+                              nm_inet4_ntop(value, sbuf));
 }
 
 void
diff --git a/src/core/dhcp/nm-dhcp-options.h b/src/core/dhcp/nm-dhcp-options.h
index fcc6f9cd..050080d9 100644
--- a/src/core/dhcp/nm-dhcp-options.h
+++ b/src/core/dhcp/nm-dhcp-options.h
@@ -208,20 +208,38 @@ nm_dhcp_option_request_string(int addr_family, guint option)
     return nm_dhcp_option_get_name(nm_dhcp_option_find(addr_family, option));
 }
 
-void nm_dhcp_option_take_option(GHashTable *options, int addr_family, guint option, char *value);
-void
-nm_dhcp_option_add_option(GHashTable *options, int addr_family, guint option, const char *value);
+void nm_dhcp_option_take_option(GHashTable *options,
+                                gboolean    static_keys,
+                                int         addr_family,
+                                guint       option,
+                                char       *value);
+
+static inline void
+nm_dhcp_option_add_option(GHashTable *options,
+                          gboolean    static_keys,
+                          int         addr_family,
+                          guint       option,
+                          const char *value)
+{
+    nm_dhcp_option_take_option(options, static_keys, addr_family, option, g_strdup(value));
+}
+
 void nm_dhcp_option_add_option_utf8safe_escape(GHashTable   *options,
+                                               gboolean      static_keys,
                                                int           addr_family,
                                                guint         option,
                                                const guint8 *data,
                                                gsize         n_data);
 void nm_dhcp_option_add_option_in_addr(GHashTable *options,
+                                       gboolean    static_keys,
                                        int         addr_family,
                                        guint       option,
                                        in_addr_t   value);
-void
-nm_dhcp_option_add_option_u64(GHashTable *options, int addr_family, guint option, guint64 value);
+void nm_dhcp_option_add_option_u64(GHashTable *options,
+                                   gboolean    static_keys,
+                                   int         addr_family,
+                                   guint       option,
+                                   guint64     value);
 void nm_dhcp_option_add_requests_to_options(GHashTable *options, int addr_family);
 
 GHashTable *nm_dhcp_option_create_options_dict(gboolean static_keys);
diff --git a/src/core/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c
index 10990822..6f9312da 100644
--- a/src/core/dhcp/nm-dhcp-systemd.c
+++ b/src/core/dhcp/nm-dhcp-systemd.c
@@ -70,11 +70,13 @@ G_DEFINE_TYPE(NMDhcpSystemd, nm_dhcp_systemd, NM_TYPE_DHCP_CLIENT)
 static NML3ConfigData *
 lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GError **error)
 {
+    const NMDhcpClientConfig               *config;
     nm_auto_unref_l3cd_init NML3ConfigData *l3cd    = NULL;
     gs_unref_hashtable GHashTable          *options = NULL;
     struct in6_addr                         tmp_addr;
     const struct in6_addr                  *dns;
     char                                    addr_str[NM_INET_ADDRSTRLEN];
+    char                                    iaid_buf[NM_DHCP_IAID_TO_HEXSTR_BUF_LEN];
     char                                  **domains;
     char                                  **ntp_fqdns;
     const struct in6_addr                  *ntp_addrs;
@@ -84,11 +86,19 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro
 
     nm_assert(lease);
 
+    config = nm_dhcp_client_get_config(NM_DHCP_CLIENT(self));
+
     l3cd = nm_dhcp_client_create_l3cd(NM_DHCP_CLIENT(self));
 
     options = nm_dhcp_client_create_options_dict(NM_DHCP_CLIENT(self), TRUE);
 
-    if (!nm_dhcp_client_get_config(NM_DHCP_CLIENT(self))->v6.info_only) {
+    nm_dhcp_option_add_option(options,
+                              TRUE,
+                              AF_INET6,
+                              NM_DHCP_OPTION_DHCP6_NM_IAID,
+                              nm_dhcp_iaid_to_hexstr(config->v6.iaid, iaid_buf));
+
+    if (!config->v6.info_only) {
         gboolean has_any_addresses = FALSE;
         uint32_t lft_pref;
         uint32_t lft_valid;
@@ -115,6 +125,7 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro
 
         if (str->len) {
             nm_dhcp_option_add_option(options,
+                                      TRUE,
                                       AF_INET6,
                                       NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS,
                                       str->str);
@@ -137,7 +148,11 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro
             g_string_append(nm_gstring_add_space_delimiter(str), addr_str);
             nm_l3_config_data_add_nameserver_detail(l3cd, AF_INET6, &dns[i], NULL);
         }
-        nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_DNS_SERVERS, str->str);
+        nm_dhcp_option_add_option(options,
+                                  TRUE,
+                                  AF_INET6,
+                                  NM_DHCP_OPTION_DHCP6_DNS_SERVERS,
+                                  str->str);
     }
 
     num = sd_dhcp6_lease_get_domains(lease, &domains);
@@ -147,11 +162,15 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro
             g_string_append(nm_gstring_add_space_delimiter(str), domains[i]);
             nm_l3_config_data_add_search(l3cd, AF_INET6, domains[i]);
         }
-        nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_DOMAIN_LIST, str->str);
+        nm_dhcp_option_add_option(options,
+                                  TRUE,
+                                  AF_INET6,
+                                  NM_DHCP_OPTION_DHCP6_DOMAIN_LIST,
+                                  str->str);
     }
 
     if (sd_dhcp6_lease_get_fqdn(lease, &s) >= 0) {
-        nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_FQDN, s);
+        nm_dhcp_option_add_option(options, TRUE, AF_INET6, NM_DHCP_OPTION_DHCP6_FQDN, s);
     }
 
     /* RFC 5908, section 4 states: "This option MUST include one, and only
@@ -175,7 +194,11 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro
         }
     }
     if (str->len) {
-        nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_NTP_SERVER, str->str);
+        nm_dhcp_option_add_option(options,
+                                  TRUE,
+                                  AF_INET6,
+                                  NM_DHCP_OPTION_DHCP6_NTP_SERVER,
+                                  str->str);
     }
 
     nm_l3_config_data_set_dhcp_lease_from_options(l3cd, AF_INET6, g_steal_pointer(&options));
diff --git a/src/core/nm-active-connection.c b/src/core/nm-active-connection.c
index 4488d909..6f62a601 100644
--- a/src/core/nm-active-connection.c
+++ b/src/core/nm-active-connection.c
@@ -81,6 +81,7 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMActiveConnection,
                              PROP_DHCP6_CONFIG,
                              PROP_VPN,
                              PROP_MASTER,
+                             PROP_CONTROLLER,
 
                              PROP_INT_SETTINGS_CONNECTION,
                              PROP_INT_APPLIED_CONNECTION,
@@ -803,7 +804,7 @@ check_master_ready(NMActiveConnection *self)
          * ensure that if the master connection was created without a device
          * that we notify clients when the master device is known.
          */
-        _notify(self, PROP_MASTER);
+        nm_gobject_notify_together(self, PROP_MASTER, PROP_CONTROLLER);
     }
 }
 
@@ -1343,6 +1344,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
     case PROP_VPN:
         g_value_set_boolean(value, priv->vpn);
         break;
+    case PROP_CONTROLLER:
     case PROP_MASTER:
         if (priv->master)
             master_device = nm_active_connection_get_device(priv->master);
@@ -1440,8 +1442,6 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
         /* construct-only */
         priv->vpn = g_value_get_boolean(value);
         break;
-    case PROP_MASTER:
-        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
         break;
@@ -1601,6 +1601,9 @@ static const NMDBusInterfaceInfoExtended interface_info_active_connection = {
                                                            "o",
                                                            NM_ACTIVE_CONNECTION_DHCP6_CONFIG),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Vpn", "b", NM_ACTIVE_CONNECTION_VPN),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Controller",
+                                                           "o",
+                                                           NM_ACTIVE_CONNECTION_CONTROLLER),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Master",
                                                            "o",
                                                            NM_ACTIVE_CONNECTION_MASTER), ), ),
@@ -1732,6 +1735,13 @@ nm_active_connection_class_init(NMActiveConnectionClass *ac_class)
                                                       NULL,
                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
+    obj_properties[PROP_CONTROLLER] =
+        g_param_spec_string(NM_ACTIVE_CONNECTION_CONTROLLER,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
     /* Internal properties */
     obj_properties[PROP_INT_SETTINGS_CONNECTION] =
         g_param_spec_object(NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION,
diff --git a/src/core/nm-active-connection.h b/src/core/nm-active-connection.h
index a2353a65..15db68c3 100644
--- a/src/core/nm-active-connection.h
+++ b/src/core/nm-active-connection.h
@@ -38,6 +38,7 @@
 #define NM_ACTIVE_CONNECTION_DHCP6_CONFIG    "dhcp6-config"
 #define NM_ACTIVE_CONNECTION_VPN             "vpn"
 #define NM_ACTIVE_CONNECTION_MASTER          "master"
+#define NM_ACTIVE_CONNECTION_CONTROLLER      "controller"
 
 /* Internal non-exported properties */
 #define NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION "int-settings-connection"
diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c
index 9448ba7b..500bffb9 100644
--- a/src/core/nm-core-utils.c
+++ b/src/core/nm-core-utils.c
@@ -4868,11 +4868,14 @@ typedef struct {
 
     int      child_stdin;
     int      child_stdout;
+    int      child_stderr;
     GSource *input_source;
     GSource *output_source;
+    GSource *error_source;
 
     NMStrBuf in_buffer;
     NMStrBuf out_buffer;
+    NMStrBuf err_buffer;
     gsize    out_buffer_offset;
 } HelperInfo;
 
@@ -4908,13 +4911,17 @@ helper_info_free(gpointer data)
 
     nm_str_buf_destroy(&info->in_buffer);
     nm_str_buf_destroy(&info->out_buffer);
+    nm_str_buf_destroy(&info->err_buffer);
     nm_clear_g_source_inst(&info->input_source);
     nm_clear_g_source_inst(&info->output_source);
+    nm_clear_g_source_inst(&info->error_source);
 
     if (info->child_stdout != -1)
         nm_close(info->child_stdout);
     if (info->child_stdin != -1)
         nm_close(info->child_stdin);
+    if (info->child_stderr != -1)
+        nm_close(info->child_stderr);
 
     if (info->pid != -1) {
         nm_assert(info->pid > 1);
@@ -4928,6 +4935,10 @@ static void
 helper_complete(HelperInfo *info, GError *error)
 {
     if (error) {
+        if (info->err_buffer.len > 0) {
+            _LOG2T(info, "stderr: %s", nm_str_buf_get_str(&info->err_buffer));
+        }
+
         nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task),
                                           &info->cancellable_id);
         g_task_return_error(info->task, error);
@@ -5023,6 +5034,24 @@ helper_have_data(int fd, GIOCondition condition, gpointer user_data)
     return G_SOURCE_CONTINUE;
 }
 
+static gboolean
+helper_have_err_data(int fd, GIOCondition condition, gpointer user_data)
+{
+    HelperInfo *info = user_data;
+    gssize      n_read;
+
+    n_read = nm_utils_fd_read(fd, &info->err_buffer);
+
+    if (n_read > 0)
+        return G_SOURCE_CONTINUE;
+
+    nm_clear_g_source_inst(&info->error_source);
+    nm_close(info->child_stderr);
+    info->child_stderr = -1;
+
+    return G_SOURCE_CONTINUE;
+}
+
 static void
 helper_child_terminated(GPid pid, int status, gpointer user_data)
 {
@@ -5098,10 +5127,11 @@ nm_utils_spawn_helper(const char *const  *args,
                                   &info->pid,
                                   &info->child_stdin,
                                   &info->child_stdout,
-                                  NULL,
+                                  &info->child_stderr,
                                   &error)) {
         info->child_stdin  = -1;
         info->child_stdout = -1;
+        info->child_stderr = -1;
         info->pid          = -1;
         g_task_return_error(info->task,
                             g_error_new(NM_UTILS_ERROR,
@@ -5130,9 +5160,11 @@ nm_utils_spawn_helper(const char *const  *args,
     fcntl(info->child_stdin, F_SETFL, fd_flags | O_NONBLOCK);
     fd_flags = fcntl(info->child_stdout, F_GETFD, 0);
     fcntl(info->child_stdout, F_SETFL, fd_flags | O_NONBLOCK);
+    fd_flags = fcntl(info->child_stderr, F_GETFD, 0);
+    fcntl(info->child_stderr, F_SETFL, fd_flags | O_NONBLOCK);
 
     /* Watch process stdin */
-    info->out_buffer = NM_STR_BUF_INIT(32, TRUE);
+    info->out_buffer = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_40, TRUE);
     for (arg = args; *arg; arg++) {
         nm_str_buf_append(&info->out_buffer, *arg);
         nm_str_buf_append_c(&info->out_buffer, '\0');
@@ -5146,7 +5178,7 @@ nm_utils_spawn_helper(const char *const  *args,
     g_source_attach(info->output_source, g_main_context_get_thread_default());
 
     /* Watch process stdout */
-    info->in_buffer    = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
+    info->in_buffer    = NM_STR_BUF_INIT(0, FALSE);
     info->input_source = nm_g_unix_fd_source_new(info->child_stdout,
                                                  G_IO_IN | G_IO_ERR | G_IO_HUP,
                                                  G_PRIORITY_DEFAULT,
@@ -5155,6 +5187,16 @@ nm_utils_spawn_helper(const char *const  *args,
                                                  NULL);
     g_source_attach(info->input_source, g_main_context_get_thread_default());
 
+    /* Watch process stderr */
+    info->err_buffer   = NM_STR_BUF_INIT(0, FALSE);
+    info->error_source = nm_g_unix_fd_source_new(info->child_stderr,
+                                                 G_IO_IN | G_IO_ERR | G_IO_HUP,
+                                                 G_PRIORITY_DEFAULT,
+                                                 helper_have_err_data,
+                                                 info,
+                                                 NULL);
+    g_source_attach(info->error_source, g_main_context_get_thread_default());
+
     if (cancellable) {
         gulong signal_id;
 
diff --git a/src/core/nm-dispatcher.c b/src/core/nm-dispatcher.c
index 01a63826..cdc07dd6 100644
--- a/src/core/nm-dispatcher.c
+++ b/src/core/nm-dispatcher.c
@@ -231,7 +231,7 @@ dump_ip_to_props(const NML3ConfigData *l3cd, int addr_family, GVariantBuilder *b
             continue;
 
         if (IS_IPv4)
-            g_variant_builder_add(&int_builder, "u", &a);
+            g_variant_builder_add(&int_builder, "u", a.addr4);
         else
             g_variant_builder_add(&int_builder, "@ay", nm_g_variant_new_ay_in6addr(&a.addr6));
     }
diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c
index 4192808a..d7e05b7b 100644
--- a/src/core/nm-policy.c
+++ b/src/core/nm-policy.c
@@ -2581,7 +2581,7 @@ dns_config_changed(NMDnsManager *dns_manager, gpointer user_data)
         return;
 
     nm_manager_for_each_device (priv->manager, device, tmp_lst) {
-        nm_device_clear_dns_lookup_data(device);
+        nm_device_clear_dns_lookup_data(device, "DNS configuration changed");
     }
 
     update_system_hostname(self, "DNS configuration changed");
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 0eeead8a..aa593331 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -1944,6 +1944,8 @@ make_ip4_setting(shvarFile *ifcfg,
                  ipv4_link_local,
                  NM_SETTING_IP_CONFIG_AUTO_ROUTE_EXT_GW,
                  svGetValueTernary(ifcfg, "IPV4_AUTO_ROUTE_EXT_GW"),
+                 NM_SETTING_IP_CONFIG_REPLACE_LOCAL_RULE,
+                 svGetValueTernary(ifcfg, "IPV4_REPLACE_LOCAL_RULE"),
                  NULL);
 
     if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
@@ -2459,6 +2461,8 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
                  ip6_privacy_val,
                  NM_SETTING_IP_CONFIG_AUTO_ROUTE_EXT_GW,
                  svGetValueTernary(ifcfg, "IPV6_AUTO_ROUTE_EXT_GW"),
+                 NM_SETTING_IP_CONFIG_REPLACE_LOCAL_RULE,
+                 svGetValueTernary(ifcfg, "IPV6_REPLACE_LOCAL_RULE"),
                  NULL);
 
     /* Don't bother to read IP, DNS and routes when IPv6 is disabled */
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 85a0eb26..552310dd 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -962,6 +962,7 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
     _KEY_TYPE("IPV4_DNS_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("IPV4_FAILURE_FATAL", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("IPV4_LINK_LOCAL", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV4_REPLACE_LOCAL_RULE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("IPV4_REQUIRED_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("IPV4_ROUTE_METRIC", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("IPV4_ROUTE_TABLE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
@@ -986,6 +987,7 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
     _KEY_TYPE("IPV6_PRIVACY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("IPV6_PRIVACY_PREFER_PUBLIC_IP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("IPV6_RA_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("IPV6_REPLACE_LOCAL_RULE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("IPV6_REQUIRED_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("IPV6_RES_OPTIONS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("IPV6_ROUTE_METRIC", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index 31064274..4fa9f18c 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -33,7 +33,7 @@ typedef struct {
     NMSIfcfgKeyTypeFlags key_flags;
 } NMSIfcfgKeyTypeInfo;
 
-extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[259];
+extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[261];
 
 const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info(const char *key, gssize *out_idx);
 
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index 2ea097fd..41260020 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -2941,6 +2941,10 @@ write_ip4_setting(NMConnection *connection,
     svSetValueTernary(ifcfg,
                       "IPV4_AUTO_ROUTE_EXT_GW",
                       nm_setting_ip_config_get_auto_route_ext_gw(s_ip4));
+
+    svSetValueTernary(ifcfg,
+                      "IPV4_REPLACE_LOCAL_RULE",
+                      nm_setting_ip_config_get_replace_local_rule(s_ip4));
 }
 
 static void
@@ -3205,6 +3209,10 @@ write_ip6_setting(NMConnection *connection, shvarFile *ifcfg, GString **out_rout
     svSetValueTernary(ifcfg,
                       "IPV6_AUTO_ROUTE_EXT_GW",
                       nm_setting_ip_config_get_auto_route_ext_gw(s_ip6));
+
+    svSetValueTernary(ifcfg,
+                      "IPV6_REPLACE_LOCAL_RULE",
+                      nm_setting_ip_config_get_replace_local_rule(s_ip6));
 }
 
 static void
diff --git a/src/core/vpn/nm-vpn-connection.c b/src/core/vpn/nm-vpn-connection.c
index 4fb926a5..d7102a12 100644
--- a/src/core/vpn/nm-vpn-connection.c
+++ b/src/core/vpn/nm-vpn-connection.c
@@ -99,6 +99,7 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMVpnConnection, PROP_VPN_STATE, PROP_BANNER,
 #define PROP_IP4_CONFIG 2000
 #define PROP_IP6_CONFIG 2001
 #define PROP_MASTER     2002
+#define PROP_CONTROLLER 2003
 );
 
 typedef struct {
@@ -2899,6 +2900,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
     case PROP_IP6_CONFIG:
         nm_dbus_utils_g_value_set_object_path(value, priv->ip_data_6.ip_config);
         break;
+    case PROP_CONTROLLER:
     case PROP_MASTER:
         nm_dbus_utils_g_value_set_object_path(
             value,
@@ -3065,6 +3067,9 @@ nm_vpn_connection_class_init(NMVpnConnectionClass *klass)
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
+    g_object_class_override_property(object_class,
+                                     PROP_CONTROLLER,
+                                     NM_ACTIVE_CONNECTION_CONTROLLER);
     g_object_class_override_property(object_class, PROP_MASTER, NM_ACTIVE_CONNECTION_MASTER);
     g_object_class_override_property(object_class,
                                      PROP_IP4_CONFIG,