about summary refs log tree commit diff
path: root/src/core/dhcp/nm-dhcp-client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/dhcp/nm-dhcp-client.c')
-rw-r--r--src/core/dhcp/nm-dhcp-client.c1331
1 files changed, 580 insertions, 751 deletions
diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c
index f88c79c0..ec910389 100644
--- a/src/core/dhcp/nm-dhcp-client.c
+++ b/src/core/dhcp/nm-dhcp-client.c
@@ -20,9 +20,13 @@
 
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
+#include "nm-l3cfg.h"
+#include "nm-l3-config-data.h"
 #include "nm-dhcp-utils.h"
 #include "nm-dhcp-options.h"
 #include "libnm-platform/nm-platform.h"
+#include "nm-hostname-manager.h"
+#include "libnm-systemd-shared/nm-sd-utils-shared.h"
 
 #include "nm-dhcp-client-logging.h"
 
@@ -32,53 +36,24 @@ enum { SIGNAL_NOTIFY, LAST_SIGNAL };
 
 static guint signals[LAST_SIGNAL] = {0};
 
-NM_GOBJECT_PROPERTIES_DEFINE(NMDhcpClient,
-                             PROP_ADDR_FAMILY,
-                             PROP_ANYCAST_ADDRESS,
-                             PROP_FLAGS,
-                             PROP_HWADDR,
-                             PROP_BROADCAST_HWADDR,
-                             PROP_IFACE,
-                             PROP_IFINDEX,
-                             PROP_MULTI_IDX,
-                             PROP_ROUTE_METRIC,
-                             PROP_ROUTE_TABLE,
-                             PROP_TIMEOUT,
-                             PROP_UUID,
-                             PROP_IAID,
-                             PROP_IAID_EXPLICIT,
-                             PROP_HOSTNAME,
-                             PROP_HOSTNAME_FLAGS,
-                             PROP_MUD_URL,
-                             PROP_VENDOR_CLASS_IDENTIFIER,
-                             PROP_REJECT_SERVERS, );
+NM_GOBJECT_PROPERTIES_DEFINE(NMDhcpClient, PROP_CONFIG, );
 
 typedef struct _NMDhcpClientPrivate {
-    NMDedupMultiIndex * multi_idx;
-    char *              iface;
-    GBytes *            hwaddr;
-    GBytes *            bcast_hwaddr;
-    char *              uuid;
-    GBytes *            client_id;
-    char *              hostname;
-    const char **       reject_servers;
-    char *              mud_url;
-    char *              anycast_address;
-    GBytes *            vendor_class_identifier;
-    pid_t               pid;
-    guint               timeout_id;
-    guint               watch_id;
-    int                 addr_family;
-    int                 ifindex;
-    guint32             route_table;
-    guint32             route_metric;
-    guint32             timeout;
-    guint32             iaid;
-    NMDhcpState         state;
-    NMDhcpHostnameFlags hostname_flags;
-    NMDhcpClientFlags   client_flags;
-    bool                iaid_explicit : 1;
-    bool                is_stopped : 1;
+    NMDhcpClientConfig    config;
+    const NML3ConfigData *l3cd;
+    GSource              *no_lease_timeout_source;
+    GSource              *ipv6_lladdr_timeout_source;
+    GBytes               *effective_client_id;
+    pid_t                 pid;
+    guint                 watch_id;
+    NMDhcpState           state;
+    bool                  iaid_explicit : 1;
+    bool                  is_stopped : 1;
+    struct {
+        gulong id;
+        bool   wait_dhcp_commit : 1;
+        bool   wait_ll_address : 1;
+    } l3cfg_notify;
 } NMDhcpClientPrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT)
@@ -87,6 +62,11 @@ G_DEFINE_ABSTRACT_TYPE(NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+static void
+l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcpClient *self);
+
+/*****************************************************************************/
+
 /* we use pid=-1 for invalid PIDs. Ensure that pid_t can hold negative values. */
 G_STATIC_ASSERT(!(((pid_t) -1) > 0));
 
@@ -98,178 +78,55 @@ _emit_notify(NMDhcpClient *self, const NMDhcpClientNotifyData *notify_data)
     g_signal_emit(G_OBJECT(self), signals[SIGNAL_NOTIFY], 0, notify_data);
 }
 
-static void
-_emit_notify_state_changed(NMDhcpClient *self,
-                           NMDhcpState   dhcp_state,
-                           NMIPConfig *  ip_config,
-                           GHashTable *  options)
-{
-    const NMDhcpClientNotifyData notify_data = {
-        .notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_STATE_CHANGED,
-        .state_changed =
-            {
-                .dhcp_state = dhcp_state,
-                .ip_config  = ip_config,
-                .options    = options,
-            },
-    };
-
-    _emit_notify(self, &notify_data);
-}
-
 /*****************************************************************************/
 
-pid_t
-nm_dhcp_client_get_pid(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), -1);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->pid;
-}
-
-NMDedupMultiIndex *
-nm_dhcp_client_get_multi_idx(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->multi_idx;
-}
-
-const char *
-nm_dhcp_client_get_iface(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->iface;
-}
-
-int
-nm_dhcp_client_get_ifindex(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), -1);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->ifindex;
-}
-
-int
-nm_dhcp_client_get_addr_family(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), AF_UNSPEC);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->addr_family;
-}
-
-const char *
-nm_dhcp_client_get_uuid(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->uuid;
-}
-
-GBytes *
-nm_dhcp_client_get_hw_addr(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->hwaddr;
-}
-
-GBytes *
-nm_dhcp_client_get_broadcast_hw_addr(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->bcast_hwaddr;
-}
-
-guint32
-nm_dhcp_client_get_route_table(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), RT_TABLE_MAIN);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->route_table;
-}
-
-void
-nm_dhcp_client_set_route_table(NMDhcpClient *self, guint32 route_table)
+static void
+connect_l3cfg_notify(NMDhcpClient *self)
 {
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+    gboolean             do_connect;
 
-    if (route_table != priv->route_table) {
-        priv->route_table = route_table;
-        _notify(self, PROP_ROUTE_TABLE);
-    }
-}
-
-guint32
-nm_dhcp_client_get_route_metric(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), G_MAXUINT32);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->route_metric;
-}
-
-void
-nm_dhcp_client_set_route_metric(NMDhcpClient *self, guint32 route_metric)
-{
-    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+    do_connect = priv->l3cfg_notify.wait_dhcp_commit | priv->l3cfg_notify.wait_ll_address;
 
-    if (route_metric != priv->route_metric) {
-        priv->route_metric = route_metric;
-        _notify(self, PROP_ROUTE_METRIC);
+    if (!do_connect) {
+        nm_clear_g_signal_handler(priv->config.l3cfg, &priv->l3cfg_notify.id);
+        return;
     }
-}
 
-guint32
-nm_dhcp_client_get_timeout(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), 0);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->timeout;
-}
-
-guint32
-nm_dhcp_client_get_iaid(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), 0);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->iaid;
-}
-
-gboolean
-nm_dhcp_client_get_iaid_explicit(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->iaid_explicit;
+    if (priv->l3cfg_notify.id == 0) {
+        priv->l3cfg_notify.id = g_signal_connect(priv->config.l3cfg,
+                                                 NM_L3CFG_SIGNAL_NOTIFY,
+                                                 G_CALLBACK(l3_cfg_notify_cb),
+                                                 self);
+    }
 }
 
-GBytes *
-nm_dhcp_client_get_client_id(NMDhcpClient *self)
+pid_t
+nm_dhcp_client_get_pid(NMDhcpClient *self)
 {
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL);
+    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), -1);
 
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->client_id;
+    return NM_DHCP_CLIENT_GET_PRIVATE(self)->pid;
 }
 
 static void
-_set_client_id(NMDhcpClient *self, GBytes *client_id, gboolean take)
+_set_effective_client_id(NMDhcpClient *self, GBytes *client_id, gboolean take)
 {
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
 
     nm_assert(!client_id || g_bytes_get_size(client_id) >= 2);
 
-    if (priv->client_id == client_id
-        || (priv->client_id && client_id && g_bytes_equal(priv->client_id, client_id))) {
+    if (priv->effective_client_id == client_id
+        || (priv->effective_client_id && client_id
+            && g_bytes_equal(priv->effective_client_id, client_id))) {
         if (take && client_id)
             g_bytes_unref(client_id);
         return;
     }
 
-    if (priv->client_id)
-        g_bytes_unref(priv->client_id);
-    priv->client_id = client_id;
+    if (priv->effective_client_id)
+        g_bytes_unref(priv->effective_client_id);
+    priv->effective_client_id = client_id;
     if (!take && client_id)
         g_bytes_ref(client_id);
 
@@ -277,94 +134,20 @@ _set_client_id(NMDhcpClient *self, GBytes *client_id, gboolean take)
         gs_free char *s = NULL;
 
         _LOGT("%s: set %s",
-              nm_dhcp_client_get_addr_family(self) == AF_INET6 ? "duid" : "client-id",
-              priv->client_id ? (s = nm_dhcp_utils_duid_to_string(priv->client_id)) : "default");
+              priv->config.addr_family == AF_INET6 ? "duid" : "client-id",
+              priv->effective_client_id
+                  ? (s = nm_dhcp_utils_duid_to_string(priv->effective_client_id))
+                  : "default");
     }
 }
 
 void
-nm_dhcp_client_set_client_id(NMDhcpClient *self, GBytes *client_id)
+nm_dhcp_client_set_effective_client_id(NMDhcpClient *self, GBytes *client_id)
 {
     g_return_if_fail(NM_IS_DHCP_CLIENT(self));
     g_return_if_fail(!client_id || g_bytes_get_size(client_id) >= 2);
 
-    _set_client_id(self, client_id, FALSE);
-}
-
-void
-nm_dhcp_client_set_client_id_bin(NMDhcpClient *self,
-                                 guint8        type,
-                                 const guint8 *client_id,
-                                 gsize         len)
-{
-    guint8 *buf;
-    GBytes *b;
-
-    g_return_if_fail(NM_IS_DHCP_CLIENT(self));
-    g_return_if_fail(client_id);
-    g_return_if_fail(len > 0);
-
-    buf    = g_malloc(len + 1);
-    buf[0] = type;
-    memcpy(buf + 1, client_id, len);
-    b = g_bytes_new_take(buf, len + 1);
-    _set_client_id(self, b, TRUE);
-}
-
-const char *
-nm_dhcp_client_get_anycast_address(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->anycast_address;
-}
-
-const char *
-nm_dhcp_client_get_hostname(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->hostname;
-}
-
-NMDhcpHostnameFlags
-nm_dhcp_client_get_hostname_flags(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NM_DHCP_HOSTNAME_FLAG_NONE);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->hostname_flags;
-}
-
-NMDhcpClientFlags
-nm_dhcp_client_get_client_flags(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NM_DHCP_CLIENT_FLAGS_NONE);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->client_flags;
-}
-
-const char *
-nm_dhcp_client_get_mud_url(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->mud_url;
-}
-
-GBytes *
-nm_dhcp_client_get_vendor_class_identifier(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL);
-
-    return NM_DHCP_CLIENT_GET_PRIVATE(self)->vendor_class_identifier;
-}
-
-const char *const *
-nm_dhcp_client_get_reject_servers(NMDhcpClient *self)
-{
-    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), NULL);
-
-    return (const char *const *) NM_DHCP_CLIENT_GET_PRIVATE(self)->reject_servers;
+    _set_effective_client_id(self, client_id, FALSE);
 }
 
 /*****************************************************************************/
@@ -413,14 +196,6 @@ reason_to_state(NMDhcpClient *self, const char *iface, const char *reason)
 /*****************************************************************************/
 
 static void
-timeout_cleanup(NMDhcpClient *self)
-{
-    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
-
-    nm_clear_g_source(&priv->timeout_id);
-}
-
-static void
 watch_cleanup(NMDhcpClient *self)
 {
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
@@ -457,95 +232,181 @@ stop(NMDhcpClient *self, gboolean release)
     if (priv->pid > 0) {
         /* Clean up the watch handler since we're explicitly killing the daemon */
         watch_cleanup(self);
-        nm_dhcp_client_stop_pid(priv->pid, priv->iface);
+        nm_dhcp_client_stop_pid(priv->pid, priv->config.iface);
     }
     priv->pid = -1;
 }
 
-void
-nm_dhcp_client_set_state(NMDhcpClient *self,
-                         NMDhcpState   new_state,
-                         NMIPConfig *  ip_config,
-                         GHashTable *  options)
+static gboolean
+_no_lease_timeout(gpointer user_data)
 {
+    NMDhcpClient        *self = user_data;
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
 
-    if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) {
-        g_return_if_fail(NM_IS_IP_CONFIG_ADDR_FAMILY(ip_config, priv->addr_family));
-        g_return_if_fail(options);
+    nm_clear_g_source_inst(&priv->no_lease_timeout_source);
+
+    _emit_notify(self,
+                 &((NMDhcpClientNotifyData){
+                     .notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_NO_LEASE_TIMEOUT,
+                 }));
+    return G_SOURCE_CONTINUE;
+}
+
+const NMDhcpClientConfig *
+nm_dhcp_client_get_config(NMDhcpClient *self)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    return &priv->config;
+}
+
+static void
+schedule_no_lease_timeout(NMDhcpClient *self)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    if (priv->no_lease_timeout_source)
+        return;
+
+    if (priv->config.timeout == NM_DHCP_TIMEOUT_INFINITY) {
+        _LOGI("activation: beginning transaction (no timeout)");
+        priv->no_lease_timeout_source = g_source_ref(nm_g_source_sentinel_get(0));
     } else {
-        g_return_if_fail(!ip_config);
-        g_return_if_fail(!options);
+        _LOGI("activation: beginning transaction (timeout in %u seconds)",
+              (guint) priv->config.timeout);
+        priv->no_lease_timeout_source =
+            nm_g_timeout_add_seconds_source(priv->config.timeout, _no_lease_timeout, self);
     }
+}
+
+void
+nm_dhcp_client_set_state(NMDhcpClient *self, NMDhcpState new_state, const NML3ConfigData *l3cd)
+{
+    NMDhcpClientPrivate                     *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+    GHashTable                              *options;
+    const int                                IS_IPv4     = NM_IS_IPv4(priv->config.addr_family);
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd_merged = NULL;
+
+    g_return_if_fail(NM_IS_DHCP_CLIENT(self));
+
+    if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) {
+        g_return_if_fail(NM_IS_L3_CONFIG_DATA(l3cd));
+        g_return_if_fail(nm_l3_config_data_get_dhcp_lease(l3cd, priv->config.addr_family));
+    } else
+        g_return_if_fail(!l3cd);
+
+    if (l3cd)
+        nm_l3_config_data_seal(l3cd);
 
-    if (new_state >= NM_DHCP_STATE_BOUND)
-        timeout_cleanup(self);
     if (new_state >= NM_DHCP_STATE_TIMEOUT)
         watch_cleanup(self);
 
-    /* The client may send same-state transitions for RENEW/REBIND events and
-     * the lease may have changed, so handle same-state transitions for the
-     * EXTENDED and BOUND states.  Ignore same-state transitions for other
-     * events since the lease won't have changed and the state was already handled.
-     */
-    if ((priv->state == new_state)
-        && !NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED))
-        return;
+    if (!IS_IPv4 && l3cd) {
+        if (nm_dhcp_utils_merge_new_dhcp6_lease(priv->l3cd, l3cd, &l3cd_merged)) {
+            l3cd = nm_l3_config_data_seal(l3cd_merged);
+        }
+    }
 
-    if (_LOGD_ENABLED()) {
-        gs_free const char **keys = NULL;
-        guint                i, nkeys;
+    if (priv->l3cd == l3cd)
+        return;
 
-        keys = nm_strdict_get_keys(options, TRUE, &nkeys);
-        for (i = 0; i < nkeys; i++) {
-            _LOGD("option %-20s => '%s'", keys[i], (char *) g_hash_table_lookup(options, keys[i]));
-        }
+    if (l3cd) {
+        nm_clear_g_source_inst(&priv->no_lease_timeout_source);
+    } else {
+        if (priv->l3cd)
+            schedule_no_lease_timeout(self);
     }
 
-    if (_LOGT_ENABLED() && priv->addr_family == AF_INET6) {
-        gs_free char *event_id = NULL;
+    /* FIXME(l3cfg:dhcp): the API of NMDhcpClient is changing to expose a simpler API.
+     * The internals like NMDhcpState should not be exposed (or possibly dropped in large
+     * parts). */
+
+    nm_l3_config_data_reset(&priv->l3cd, l3cd);
+
+    options = l3cd ? nm_dhcp_lease_get_options(
+                  nm_l3_config_data_get_dhcp_lease(l3cd, priv->config.addr_family))
+                   : NULL;
+
+    if (_LOGD_ENABLED()) {
+        if (options) {
+            gs_free const char **keys = NULL;
+            guint                nkeys;
+            guint                i;
+
+            keys = nm_strdict_get_keys(options, TRUE, &nkeys);
+            for (i = 0; i < nkeys; i++) {
+                _LOGD("option %-20s => '%s'",
+                      keys[i],
+                      (char *) g_hash_table_lookup(options, keys[i]));
+            }
+
+            if (priv->config.addr_family == AF_INET6) {
+                gs_free char *event_id = NULL;
 
-        event_id = nm_dhcp_utils_get_dhcp6_event_id(options);
-        if (event_id)
-            _LOGT("event-id: \"%s\"", event_id);
+                event_id = nm_dhcp_utils_get_dhcp6_event_id(options);
+                if (event_id)
+                    _LOGT("event-id: \"%s\"", event_id);
+            }
+        }
     }
 
     if (_LOGI_ENABLED()) {
         const char *req_str =
-            NM_IS_IPv4(priv->addr_family)
-                ? nm_dhcp_option_request_string(AF_INET, NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS)
-                : nm_dhcp_option_request_string(AF_INET6, NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS);
+            IS_IPv4 ? nm_dhcp_option_request_string(AF_INET, NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS)
+                    : nm_dhcp_option_request_string(AF_INET6, NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS);
         const char *addr = nm_g_hash_table_lookup(options, req_str);
 
-        _LOGI("state changed %s -> %s%s%s%s",
-              nm_dhcp_state_to_string(priv->state),
-              nm_dhcp_state_to_string(new_state),
+        _LOGI("state changed %s%s%s%s",
+              priv->l3cd ? "new lease" : "no lease",
               NM_PRINT_FMT_QUOTED(addr, ", address=", addr, "", ""));
     }
 
-    priv->state = new_state;
-
-    _emit_notify_state_changed(self, new_state, ip_config, options);
-}
-
-static gboolean
-transaction_timeout(gpointer user_data)
-{
-    NMDhcpClient *       self = NM_DHCP_CLIENT(user_data);
-    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+    /* FIXME(l3cfg:dhcp:acd): NMDhcpClient must also do ACD. It needs acd_timeout_msec
+     * as a configuration parameter (in NMDhcpClientConfig). When ACD is enabled,
+     * when a new lease gets announced, it must first use NML3Cfg to run ACD on the
+     * interface (the previous lease -- if any -- will still be used at that point).
+     * If ACD fails, we call nm_dhcp_client_decline() and try to get a different
+     * lease.
+     * If ACD passes, we need to notify the new lease, and the user (NMDevice) may
+     * then configure the address. We need to watch the configured addresses (in NML3Cfg),
+     * and if the address appears there, we need to accept the lease. That is complicated
+     * but necessary, because we can only accept the lease after we configured the
+     * address.
+     *
+     * As a whole, ACD is transparent for the user (NMDevice). It's entirely managed
+     * by NMDhcpClient. Note that we do ACD through NML3Cfg, which centralizes IP handling
+     * for one interface, so for example if the same address happens to be configured
+     * as a static address (bypassing ACD), then NML3Cfg is aware of that and signals
+     * immediate success. */
+
+    if (nm_dhcp_client_can_accept(self) && new_state == NM_DHCP_STATE_BOUND && priv->l3cd
+        && nm_l3_config_data_get_num_addresses(priv->l3cd, priv->config.addr_family) > 0) {
+        priv->l3cfg_notify.wait_dhcp_commit = TRUE;
+    } else {
+        priv->l3cfg_notify.wait_dhcp_commit = FALSE;
+    }
+    connect_l3cfg_notify(self);
 
-    priv->timeout_id = 0;
-    _LOGW("request timed out");
-    nm_dhcp_client_set_state(self, NM_DHCP_STATE_TIMEOUT, NULL, NULL);
-    return G_SOURCE_REMOVE;
+    {
+        const NMDhcpClientNotifyData notify_data = {
+            .notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE,
+            .lease_update =
+                {
+                    .l3cd     = priv->l3cd,
+                    .accepted = !priv->l3cfg_notify.wait_dhcp_commit,
+                },
+        };
+
+        _emit_notify(self, &notify_data);
+    }
 }
 
 static void
 daemon_watch_cb(GPid pid, int status, gpointer user_data)
 {
-    NMDhcpClient *       self = NM_DHCP_CLIENT(user_data);
+    NMDhcpClient        *self = NM_DHCP_CLIENT(user_data);
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
-    gs_free char *       desc = NULL;
+    gs_free char        *desc = NULL;
 
     g_return_if_fail(priv->watch_id);
     priv->watch_id = 0;
@@ -554,22 +415,7 @@ daemon_watch_cb(GPid pid, int status, gpointer user_data)
 
     priv->pid = -1;
 
-    nm_dhcp_client_set_state(self, NM_DHCP_STATE_TERMINATED, NULL, NULL);
-}
-
-void
-nm_dhcp_client_start_timeout(NMDhcpClient *self)
-{
-    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
-
-    g_return_if_fail(priv->timeout_id == 0);
-
-    /* Set up a timeout on the transaction to kill it after the timeout */
-
-    if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
-        return;
-
-    priv->timeout_id = g_timeout_add_seconds(priv->timeout, transaction_timeout, self);
+    nm_dhcp_client_set_state(self, NM_DHCP_STATE_TERMINATED, NULL);
 }
 
 void
@@ -580,8 +426,6 @@ nm_dhcp_client_watch_child(NMDhcpClient *self, pid_t pid)
     g_return_if_fail(priv->pid == -1);
     priv->pid = pid;
 
-    nm_dhcp_client_start_timeout(self);
-
     g_return_if_fail(priv->watch_id == 0);
     priv->watch_id = g_child_watch_add(pid, daemon_watch_cb, self);
 }
@@ -595,14 +439,10 @@ nm_dhcp_client_stop_watch_child(NMDhcpClient *self, pid_t pid)
     priv->pid = -1;
 
     watch_cleanup(self);
-    timeout_cleanup(self);
 }
 
 gboolean
-nm_dhcp_client_start_ip4(NMDhcpClient *self,
-                         GBytes *      client_id,
-                         const char *  last_ip4_address,
-                         GError **     error)
+nm_dhcp_client_start_ip4(NMDhcpClient *self, GError **error)
 {
     NMDhcpClientPrivate *priv;
 
@@ -610,24 +450,25 @@ nm_dhcp_client_start_ip4(NMDhcpClient *self,
 
     priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
     g_return_val_if_fail(priv->pid == -1, FALSE);
-    g_return_val_if_fail(priv->addr_family == AF_INET, FALSE);
-    g_return_val_if_fail(priv->uuid != NULL, FALSE);
+    g_return_val_if_fail(priv->config.addr_family == AF_INET, FALSE);
+    g_return_val_if_fail(priv->config.uuid, FALSE);
 
-    if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
-        _LOGI("activation: beginning transaction (no timeout)");
-    else
-        _LOGI("activation: beginning transaction (timeout in %u seconds)", (guint) priv->timeout);
-
-    nm_dhcp_client_set_client_id(self, client_id);
+    schedule_no_lease_timeout(self);
 
-    return NM_DHCP_CLIENT_GET_CLASS(self)->ip4_start(self, last_ip4_address, error);
+    return NM_DHCP_CLIENT_GET_CLASS(self)->ip4_start(self, error);
 }
 
 gboolean
 nm_dhcp_client_accept(NMDhcpClient *self, GError **error)
 {
+    NMDhcpClientPrivate *priv;
+
     g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE);
 
+    priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    g_return_val_if_fail(priv->l3cd, FALSE);
+
     if (NM_DHCP_CLIENT_GET_CLASS(self)->accept) {
         return NM_DHCP_CLIENT_GET_CLASS(self)->accept(self, error);
     }
@@ -652,8 +493,14 @@ nm_dhcp_client_can_accept(NMDhcpClient *self)
 gboolean
 nm_dhcp_client_decline(NMDhcpClient *self, const char *error_message, GError **error)
 {
+    NMDhcpClientPrivate *priv;
+
     g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE);
 
+    priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    g_return_val_if_fail(priv->l3cd, FALSE);
+
     if (NM_DHCP_CLIENT_GET_CLASS(self)->decline) {
         return NM_DHCP_CLIENT_GET_CLASS(self)->decline(self, error_message, error);
     }
@@ -667,43 +514,185 @@ get_duid(NMDhcpClient *self)
     return NULL;
 }
 
+static gboolean
+ipv6_lladdr_timeout(gpointer user_data)
+{
+    NMDhcpClient        *self = user_data;
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    nm_clear_g_source_inst(&priv->ipv6_lladdr_timeout_source);
+
+    _emit_notify(
+        self,
+        &((NMDhcpClientNotifyData){
+            .notify_type         = NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD,
+            .it_looks_bad.reason = "timeout reached while waiting for an IPv6 link-local address",
+        }));
+    return G_SOURCE_CONTINUE;
+}
+
+static const NMPlatformIP6Address *
+ipv6_lladdr_find(NMDhcpClient *self)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+    NML3Cfg             *l3cfg;
+    NMPLookup            lookup;
+    NMDedupMultiIter     iter;
+    const NMPObject     *obj;
+
+    l3cfg = priv->config.l3cfg;
+    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, nm_l3cfg_get_ifindex(l3cfg));
+
+    nm_platform_iter_obj_for_each (&iter, nm_l3cfg_get_platform(l3cfg), &lookup, &obj) {
+        const NMPlatformIP6Address *pladdr = NMP_OBJECT_CAST_IP6_ADDRESS(obj);
+
+        if (!IN6_IS_ADDR_LINKLOCAL(&pladdr->address))
+            continue;
+        if (NM_FLAGS_HAS(pladdr->n_ifa_flags, IFA_F_TENTATIVE)
+            && !NM_FLAGS_HAS(pladdr->n_ifa_flags, IFA_F_OPTIMISTIC))
+            continue;
+        return pladdr;
+    }
+    return NULL;
+}
+
+static void
+l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcpClient *self)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    nm_assert(l3cfg == priv->config.l3cfg);
+
+    switch (notify_data->notify_type) {
+    case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE:
+    {
+        const NMPlatformIP6Address *addr;
+        gs_free_error GError       *error = NULL;
+
+        if (!priv->l3cfg_notify.wait_ll_address)
+            return;
+
+        addr = ipv6_lladdr_find(self);
+        if (addr) {
+            _LOGD("got IPv6LL address, starting transaction");
+            priv->l3cfg_notify.wait_ll_address = FALSE;
+            connect_l3cfg_notify(self);
+            nm_clear_g_source_inst(&priv->ipv6_lladdr_timeout_source);
+
+            schedule_no_lease_timeout(self);
+
+            if (!NM_DHCP_CLIENT_GET_CLASS(self)->ip6_start(self, &addr->address, &error)) {
+                _emit_notify(self,
+                             &((NMDhcpClientNotifyData){
+                                 .notify_type         = NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD,
+                                 .it_looks_bad.reason = error->message,
+                             }));
+            }
+        }
+
+        break;
+    }
+    case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT:
+    {
+        const NML3ConfigData      *committed_l3cd;
+        NMDedupMultiIter           ipconf_iter;
+        const NMPlatformIPAddress *lease_address;
+        gs_free_error GError      *error = NULL;
+
+        /* A new configuration was committed to the interface. If we previously
+         * got a lease, check whether we are waiting for the address to be
+         * configured. If the address was added, we can proceed accepting the
+         * lease and notifying NMDevice. */
+
+        if (!priv->l3cfg_notify.wait_dhcp_commit)
+            return;
+
+        nm_l3_config_data_iter_ip_address_for_each (&ipconf_iter,
+                                                    priv->l3cd,
+                                                    priv->config.addr_family,
+                                                    &lease_address)
+            break;
+        nm_assert(lease_address);
+        committed_l3cd = nm_l3cfg_get_combined_l3cd(l3cfg, TRUE);
+
+        if (priv->config.addr_family == AF_INET) {
+            const NMPlatformIP4Address *address4 = (const NMPlatformIP4Address *) lease_address;
+
+            if (!nm_l3_config_data_lookup_address_4(committed_l3cd,
+                                                    address4->address,
+                                                    address4->plen,
+                                                    address4->peer_address))
+                return;
+        } else {
+            const NMPlatformIP6Address *address6 = (const NMPlatformIP6Address *) lease_address;
+
+            if (!nm_l3_config_data_lookup_address_6(committed_l3cd, &address6->address))
+                return;
+        }
+
+        priv->l3cfg_notify.wait_dhcp_commit = FALSE;
+        connect_l3cfg_notify(self);
+
+        _LOGD("accept address");
+
+        if (!nm_dhcp_client_accept(self, &error)) {
+            gs_free char *reason = g_strdup_printf("error accepting lease: %s", error->message);
+
+            _emit_notify(self,
+                         &((NMDhcpClientNotifyData){
+                             .notify_type         = NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD,
+                             .it_looks_bad.reason = reason,
+                         }));
+            return;
+        }
+
+        _emit_notify(
+            self,
+            &((NMDhcpClientNotifyData){.notify_type  = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE,
+                                       .lease_update = {
+                                           .l3cd     = priv->l3cd,
+                                           .accepted = TRUE,
+                                       }}));
+        break;
+    };
+    default:
+        /* ignore */;
+    }
+}
+
 gboolean
-nm_dhcp_client_start_ip6(NMDhcpClient *            self,
-                         GBytes *                  client_id,
-                         gboolean                  enforce_duid,
-                         const struct in6_addr *   ll_addr,
-                         NMSettingIP6ConfigPrivacy privacy,
-                         guint                     needed_prefixes,
-                         GError **                 error)
+nm_dhcp_client_start_ip6(NMDhcpClient *self, GError **error)
 {
-    NMDhcpClientPrivate *priv;
-    gs_unref_bytes GBytes *own_client_id = NULL;
+    NMDhcpClientPrivate        *priv;
+    gs_unref_bytes GBytes      *own_client_id = NULL;
+    const NMPlatformIP6Address *addr;
 
     g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE);
-    g_return_val_if_fail(client_id, FALSE);
-
     priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
 
     g_return_val_if_fail(priv->pid == -1, FALSE);
-    g_return_val_if_fail(priv->addr_family == AF_INET6, FALSE);
-    g_return_val_if_fail(priv->uuid != NULL, FALSE);
-    g_return_val_if_fail(!priv->client_id, FALSE);
+    g_return_val_if_fail(priv->config.addr_family == AF_INET6, FALSE);
+    g_return_val_if_fail(priv->config.uuid, FALSE);
+    g_return_val_if_fail(!priv->effective_client_id, FALSE);
 
-    if (!enforce_duid)
+    if (!priv->config.v6.enforce_duid)
         own_client_id = NM_DHCP_CLIENT_GET_CLASS(self)->get_duid(self);
 
-    _set_client_id(self, own_client_id ?: client_id, FALSE);
+    _set_effective_client_id(self, own_client_id ?: priv->config.client_id, FALSE);
 
-    if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
-        _LOGI("activation: beginning transaction (no timeout)");
-    else
-        _LOGI("activation: beginning transaction (timeout in %u seconds)", (guint) priv->timeout);
+    addr = ipv6_lladdr_find(self);
+    if (!addr) {
+        _LOGD("waiting for IPv6LL address");
+        priv->l3cfg_notify.wait_ll_address = TRUE;
+        connect_l3cfg_notify(self);
+        priv->ipv6_lladdr_timeout_source =
+            nm_g_timeout_add_seconds_source(10, ipv6_lladdr_timeout, self);
+        return TRUE;
+    }
 
-    return NM_DHCP_CLIENT_GET_CLASS(self)->ip6_start(self,
-                                                     ll_addr,
-                                                     privacy,
-                                                     needed_prefixes,
-                                                     error);
+    schedule_no_lease_timeout(self);
+
+    return NM_DHCP_CLIENT_GET_CLASS(self)->ip6_start(self, &addr->address, error);
 }
 
 void
@@ -711,7 +700,7 @@ nm_dhcp_client_stop_existing(const char *pid_file, const char *binary_name)
 {
     guint64       start_time;
     pid_t         pid, ppid;
-    const char *  exe;
+    const char   *exe;
     char          proc_path[NM_STRLEN("/proc/%lu/cmdline") + 100];
     gs_free char *pid_contents = NULL, *proc_contents = NULL;
 
@@ -780,6 +769,10 @@ nm_dhcp_client_stop(NMDhcpClient *self, gboolean release)
 
     priv->is_stopped = TRUE;
 
+    priv->l3cfg_notify.wait_dhcp_commit = FALSE;
+    priv->l3cfg_notify.wait_ll_address  = FALSE;
+    connect_l3cfg_notify(self);
+
     /* Kill the DHCP client */
     old_pid = priv->pid;
     NM_DHCP_CLIENT_GET_CLASS(self)->stop(self, release);
@@ -789,7 +782,7 @@ nm_dhcp_client_stop(NMDhcpClient *self, gboolean release)
         _LOGI("canceled DHCP transaction");
     nm_assert(priv->pid == -1);
 
-    nm_dhcp_client_set_state(self, NM_DHCP_STATE_TERMINATED, NULL, NULL);
+    nm_dhcp_client_set_state(self, NM_DHCP_STATE_TERMINATED, NULL);
 }
 
 /*****************************************************************************/
@@ -799,10 +792,10 @@ bytearray_variant_to_string(NMDhcpClient *self, GVariant *value, const char *key
 {
     const guint8 *array;
     gsize         length;
-    GString *     str;
+    GString      *str;
     int           i;
     unsigned char c;
-    char *        converted = NULL;
+    char         *converted = NULL;
 
     g_return_val_if_fail(value != NULL, NULL);
 
@@ -879,7 +872,7 @@ maybe_add_option(NMDhcpClient *self, GHashTable *hash, const char *key, GVariant
          */
         if ((priv_opt_num = label_is_unknown_xyz(key)) > 0) {
             gs_free guint8 *check_val = NULL;
-            char *          hex_str   = NULL;
+            char           *hex_str   = NULL;
             gsize           len;
 
             /* dhclient passes values from dhcp private options in its own "string" format:
@@ -915,18 +908,16 @@ nm_dhcp_client_emit_ipv6_prefix_delegated(NMDhcpClient *self, const NMPlatformIP
 
 gboolean
 nm_dhcp_client_handle_event(gpointer      unused,
-                            const char *  iface,
+                            const char   *iface,
                             int           pid,
-                            GVariant *    options,
-                            const char *  reason,
+                            GVariant     *options,
+                            const char   *reason,
                             NMDhcpClient *self)
 {
-    NMDhcpClientPrivate *priv;
-    guint32              old_state;
-    guint32              new_state;
-    gs_unref_hashtable GHashTable *str_options = NULL;
-    gs_unref_object NMIPConfig *ip_config      = NULL;
-    NMPlatformIP6Address        prefix         = {
+    NMDhcpClientPrivate                    *priv;
+    guint32                                 new_state;
+    nm_auto_unref_l3cd_init NML3ConfigData *l3cd   = NULL;
+    NMPlatformIP6Address                    prefix = {
         0,
     };
 
@@ -938,25 +929,26 @@ nm_dhcp_client_handle_event(gpointer      unused,
 
     priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
 
-    if (g_strcmp0(priv->iface, iface) != 0)
+    if (!nm_streq0(priv->config.iface, iface))
         return FALSE;
     if (priv->pid != pid)
         return FALSE;
 
-    old_state = priv->state;
-    new_state = reason_to_state(self, priv->iface, reason);
+    new_state = reason_to_state(self, priv->config.iface, reason);
+    if (new_state == NM_DHCP_STATE_NOOP)
+        return TRUE;
+
     _LOGD("DHCP state '%s' -> '%s' (reason: '%s')",
-          nm_dhcp_state_to_string(old_state),
+          nm_dhcp_state_to_string(priv->state),
           nm_dhcp_state_to_string(new_state),
           reason);
-
-    if (new_state == NM_DHCP_STATE_NOOP)
-        return TRUE;
+    priv->state = new_state;
 
     if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) {
-        GVariantIter iter;
-        const char * name;
-        GVariant *   value;
+        gs_unref_hashtable GHashTable *str_options = NULL;
+        GVariantIter                   iter;
+        const char                    *name;
+        GVariant                      *value;
 
         /* Copy options */
         str_options = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free);
@@ -968,25 +960,29 @@ nm_dhcp_client_handle_event(gpointer      unused,
 
         /* Create the IP config */
         if (g_hash_table_size(str_options) > 0) {
-            if (priv->addr_family == AF_INET) {
-                ip_config = NM_IP_CONFIG_CAST(
-                    nm_dhcp_utils_ip4_config_from_options(nm_dhcp_client_get_multi_idx(self),
-                                                          priv->ifindex,
-                                                          priv->iface,
-                                                          str_options,
-                                                          priv->route_table,
-                                                          priv->route_metric));
+            if (priv->config.addr_family == AF_INET) {
+                l3cd = nm_dhcp_utils_ip4_config_from_options(
+                    nm_l3cfg_get_multi_idx(priv->config.l3cfg),
+                    nm_l3cfg_get_ifindex(priv->config.l3cfg),
+                    priv->config.iface,
+                    str_options);
             } else {
-                prefix    = nm_dhcp_utils_ip6_prefix_from_options(str_options);
-                ip_config = NM_IP_CONFIG_CAST(nm_dhcp_utils_ip6_config_from_options(
-                    nm_dhcp_client_get_multi_idx(self),
-                    priv->ifindex,
-                    priv->iface,
+                prefix = nm_dhcp_utils_ip6_prefix_from_options(str_options);
+                l3cd   = nm_dhcp_utils_ip6_config_from_options(
+                    nm_l3cfg_get_multi_idx(priv->config.l3cfg),
+                    nm_l3cfg_get_ifindex(priv->config.l3cfg),
+                    priv->config.iface,
                     str_options,
-                    NM_FLAGS_HAS(priv->client_flags, NM_DHCP_CLIENT_FLAGS_INFO_ONLY)));
+                    priv->config.v6.info_only);
             }
         } else
             g_warn_if_reached();
+
+        if (l3cd) {
+            nm_l3_config_data_set_dhcp_lease_from_options(l3cd,
+                                                          priv->config.addr_family,
+                                                          g_steal_pointer(&str_options));
+        }
     }
 
     if (!IN6_IS_ADDR_UNSPECIFIED(&prefix.address)) {
@@ -994,17 +990,16 @@ nm_dhcp_client_handle_event(gpointer      unused,
          * of the DHCP client instance. Instead, we just signal the prefix
          * to the device. */
         nm_dhcp_client_emit_ipv6_prefix_delegated(self, &prefix);
-    } else {
-        /* Fail if no valid IP config was received */
-        if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED) && !ip_config) {
-            _LOGW("client bound but IP config not received");
-            new_state = NM_DHCP_STATE_FAIL;
-            nm_clear_pointer(&str_options, g_hash_table_unref);
-        }
+        return TRUE;
+    }
 
-        nm_dhcp_client_set_state(self, new_state, ip_config, str_options);
+    /* Fail if no valid IP config was received */
+    if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED) && !l3cd) {
+        _LOGW("client bound but IP config not received");
+        new_state = NM_DHCP_STATE_FAIL;
     }
 
+    nm_dhcp_client_set_state(self, new_state, l3cd);
     return TRUE;
 }
 
@@ -1016,18 +1011,18 @@ nm_dhcp_client_server_id_is_rejected(NMDhcpClient *self, gconstpointer addr)
     guint                i;
 
     /* IPv6 not implemented yet */
-    nm_assert(priv->addr_family == AF_INET);
+    nm_assert(priv->config.addr_family == AF_INET);
 
-    if (!priv->reject_servers || !priv->reject_servers[0])
+    if (!priv->config.reject_servers || !priv->config.reject_servers[0])
         return FALSE;
 
-    for (i = 0; priv->reject_servers[i]; i++) {
+    for (i = 0; priv->config.reject_servers[i]; i++) {
         in_addr_t r_addr;
         in_addr_t mask;
         int       r_prefix;
 
         if (!nm_utils_parse_inaddr_prefix_bin(AF_INET,
-                                              priv->reject_servers[i],
+                                              priv->config.reject_servers[i],
                                               NULL,
                                               &r_addr,
                                               &r_prefix))
@@ -1040,146 +1035,148 @@ nm_dhcp_client_server_id_is_rejected(NMDhcpClient *self, gconstpointer addr)
     return FALSE;
 }
 
-/*****************************************************************************/
+static void
+config_init(NMDhcpClientConfig *config, const NMDhcpClientConfig *src)
+{
+    *config = *src;
+
+    g_object_ref(config->l3cfg);
+
+    if (config->hwaddr)
+        g_bytes_ref(config->hwaddr);
+    if (config->bcast_hwaddr)
+        g_bytes_ref(config->bcast_hwaddr);
+    if (config->vendor_class_identifier)
+        g_bytes_ref(config->vendor_class_identifier);
+    if (config->client_id)
+        g_bytes_ref(config->client_id);
+
+    config->iface           = g_strdup(config->iface);
+    config->uuid            = g_strdup(config->uuid);
+    config->anycast_address = g_strdup(config->anycast_address);
+    config->hostname        = g_strdup(config->hostname);
+    config->mud_url         = g_strdup(config->mud_url);
+
+    config->reject_servers = (const char *const *) nm_strv_dup(config->reject_servers, -1, TRUE);
+
+    if (config->addr_family == AF_INET) {
+        config->v4.last_address = g_strdup(config->v4.last_address);
+    } else if (config->addr_family == AF_INET6) {
+        config->hwaddr       = NULL;
+        config->bcast_hwaddr = NULL;
+        config->use_fqdn     = TRUE;
+    } else {
+        nm_assert_not_reached();
+    }
+
+    if (!config->hostname && config->send_hostname) {
+        const char   *hostname;
+        gs_free char *hostname_tmp = NULL;
+
+        hostname = nm_hostname_manager_get_static_hostname(nm_hostname_manager_get());
+
+        if (nm_utils_is_specific_hostname(hostname)) {
+            if (config->addr_family == AF_INET) {
+                char *dot;
+
+                hostname_tmp = g_strdup(hostname);
+                dot          = strchr(hostname_tmp, '.');
+                if (dot)
+                    *dot = '\0';
+            }
+            config->hostname = hostname_tmp ? g_steal_pointer(&hostname_tmp) : g_strdup(hostname);
+        }
+    }
+
+    if (config->hostname) {
+        if (!config->send_hostname) {
+            nm_clear_g_free((gpointer *) &config->hostname);
+        } else if ((config->use_fqdn && !nm_sd_dns_name_is_valid(config->hostname))
+                   || (!config->use_fqdn && !nm_sd_hostname_is_valid(config->hostname, FALSE))) {
+            nm_log_warn(LOGD_DHCP,
+                        "dhcp%c: %s '%s' is invalid, will be ignored",
+                        nm_utils_addr_family_to_char(config->addr_family),
+                        config->use_fqdn ? "FQDN" : "hostname",
+                        config->hostname);
+            nm_clear_g_free((gpointer *) &config->hostname);
+        }
+    }
+}
 
 static void
-get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+config_clear(NMDhcpClientConfig *config)
 {
-    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(object);
+    g_object_unref(config->l3cfg);
 
-    switch (prop_id) {
-    case PROP_IFACE:
-        g_value_set_string(value, priv->iface);
-        break;
-    case PROP_IFINDEX:
-        g_value_set_int(value, priv->ifindex);
-        break;
-    case PROP_HWADDR:
-        g_value_set_boxed(value, priv->hwaddr);
-        break;
-    case PROP_BROADCAST_HWADDR:
-        g_value_set_boxed(value, priv->bcast_hwaddr);
-        break;
-    case PROP_ADDR_FAMILY:
-        g_value_set_int(value, priv->addr_family);
-        break;
-    case PROP_UUID:
-        g_value_set_string(value, priv->uuid);
-        break;
-    case PROP_IAID:
-        g_value_set_uint(value, priv->iaid);
-        break;
-    case PROP_IAID_EXPLICIT:
-        g_value_set_boolean(value, priv->iaid_explicit);
-        break;
-    case PROP_HOSTNAME:
-        g_value_set_string(value, priv->hostname);
-        break;
-    case PROP_ROUTE_METRIC:
-        g_value_set_uint(value, priv->route_metric);
-        break;
-    case PROP_ROUTE_TABLE:
-        g_value_set_uint(value, priv->route_table);
-        break;
-    case PROP_TIMEOUT:
-        g_value_set_uint(value, priv->timeout);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
+    nm_clear_pointer(&config->hwaddr, g_bytes_unref);
+    nm_clear_pointer(&config->bcast_hwaddr, g_bytes_unref);
+    nm_clear_pointer(&config->vendor_class_identifier, g_bytes_unref);
+    nm_clear_pointer(&config->client_id, g_bytes_unref);
+
+    nm_clear_g_free((gpointer *) &config->iface);
+    nm_clear_g_free((gpointer *) &config->uuid);
+    nm_clear_g_free((gpointer *) &config->anycast_address);
+    nm_clear_g_free((gpointer *) &config->hostname);
+    nm_clear_g_free((gpointer *) &config->mud_url);
+
+    nm_clear_pointer((gpointer *) &config->reject_servers, g_strfreev);
+
+    if (config->addr_family == AF_INET) {
+        nm_clear_g_free((gpointer *) &config->v4.last_address);
     }
 }
 
+int
+nm_dhcp_client_get_addr_family(NMDhcpClient *self)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    return priv->config.addr_family;
+}
+
+const char *
+nm_dhcp_client_get_iface(NMDhcpClient *self)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    return priv->config.iface;
+}
+
+NMDedupMultiIndex *
+nm_dhcp_client_get_multi_idx(NMDhcpClient *self)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    return nm_l3cfg_get_multi_idx(priv->config.l3cfg);
+}
+
+int
+nm_dhcp_client_get_ifindex(NMDhcpClient *self)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    return nm_l3cfg_get_ifindex(priv->config.l3cfg);
+}
+
+GBytes *
+nm_dhcp_client_get_effective_client_id(NMDhcpClient *self)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    return priv->effective_client_id;
+}
+
+/*****************************************************************************/
+
 static void
 set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(object);
-    guint                flags;
 
     switch (prop_id) {
-    case PROP_FLAGS:
-        /* construct-only */
-        flags = g_value_get_uint(value);
-        nm_assert(!NM_FLAGS_ANY(flags, ~((guint) NM_DHCP_CLIENT_FLAGS_ALL)));
-        priv->client_flags = flags;
-        break;
-    case PROP_MULTI_IDX:
-        /* construct-only */
-        priv->multi_idx = g_value_get_pointer(value);
-        if (!priv->multi_idx)
-            g_return_if_reached();
-        nm_dedup_multi_index_ref(priv->multi_idx);
-        break;
-    case PROP_IFACE:
-        /* construct-only */
-        priv->iface = g_value_dup_string(value);
-        g_return_if_fail(priv->iface);
-        nm_assert(nm_utils_ifname_valid_kernel(priv->iface, NULL));
-        break;
-    case PROP_IFINDEX:
-        /* construct-only */
-        priv->ifindex = g_value_get_int(value);
-        g_return_if_fail(priv->ifindex > 0);
-        break;
-    case PROP_HWADDR:
-        /* construct-only */
-        priv->hwaddr = g_value_dup_boxed(value);
-        break;
-    case PROP_ANYCAST_ADDRESS:
-        /* construct-only */
-        priv->anycast_address = g_value_dup_string(value);
-        break;
-    case PROP_BROADCAST_HWADDR:
-        /* construct-only */
-        priv->bcast_hwaddr = g_value_dup_boxed(value);
-        break;
-    case PROP_ADDR_FAMILY:
-        /* construct-only */
-        priv->addr_family = g_value_get_int(value);
-        if (!NM_IN_SET(priv->addr_family, AF_INET, AF_INET6))
-            g_return_if_reached();
-        break;
-    case PROP_UUID:
-        /* construct-only */
-        priv->uuid = g_value_dup_string(value);
-        break;
-    case PROP_IAID:
-        /* construct-only */
-        priv->iaid = g_value_get_uint(value);
-        break;
-    case PROP_IAID_EXPLICIT:
-        /* construct-only */
-        priv->iaid_explicit = g_value_get_boolean(value);
-        break;
-    case PROP_HOSTNAME:
-        /* construct-only */
-        priv->hostname = g_value_dup_string(value);
-        break;
-    case PROP_HOSTNAME_FLAGS:
+    case PROP_CONFIG:
         /* construct-only */
-        priv->hostname_flags = g_value_get_uint(value);
-        break;
-    case PROP_MUD_URL:
-        /* construct-only */
-        priv->mud_url = g_value_dup_string(value);
-        break;
-    case PROP_ROUTE_TABLE:
-        priv->route_table = g_value_get_uint(value);
-        break;
-    case PROP_ROUTE_METRIC:
-        priv->route_metric = g_value_get_uint(value);
-        break;
-    case PROP_TIMEOUT:
-        /* construct-only */
-        priv->timeout = g_value_get_uint(value);
-        break;
-    case PROP_VENDOR_CLASS_IDENTIFIER:
-        /* construct-only */
-        priv->vendor_class_identifier = g_value_dup_boxed(value);
-        break;
-    case PROP_REJECT_SERVERS:
-        /* construct-only */
-        priv->reject_servers = nm_strv_dup_packed(g_value_get_boxed(value), -1);
+        config_init(&priv->config, g_value_get_pointer(value));
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@@ -1200,53 +1197,31 @@ nm_dhcp_client_init(NMDhcpClient *self)
     priv->pid = -1;
 }
 
-#if NM_MORE_ASSERTS
 static void
-constructed(GObject *object)
+dispose(GObject *object)
 {
-    NMDhcpClient *       self = NM_DHCP_CLIENT(object);
+    NMDhcpClient        *self = NM_DHCP_CLIENT(object);
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
 
-    /* certain flags only make sense with certain address family. Assert
-     * for that. */
-    if (NM_IS_IPv4(priv->addr_family))
-        nm_assert(!NM_FLAGS_ANY(priv->client_flags, NM_DHCP_CLIENT_FLAGS_INFO_ONLY));
-    else {
-        nm_assert(NM_FLAGS_HAS(priv->client_flags, NM_DHCP_CLIENT_FLAGS_USE_FQDN));
-        nm_assert(!NM_FLAGS_ANY(priv->client_flags, NM_DHCP_CLIENT_FLAGS_REQUEST_BROADCAST));
-    }
+    nm_dhcp_client_stop(self, FALSE);
 
-    nm_assert(!priv->anycast_address || nm_utils_hwaddr_valid(priv->anycast_address, ETH_ALEN));
+    watch_cleanup(self);
+
+    nm_clear_g_source_inst(&priv->no_lease_timeout_source);
+    nm_clear_g_source_inst(&priv->ipv6_lladdr_timeout_source);
 
-    G_OBJECT_CLASS(nm_dhcp_client_parent_class)->constructed(object);
+    G_OBJECT_CLASS(nm_dhcp_client_parent_class)->dispose(object);
 }
-#endif
 
 static void
-dispose(GObject *object)
+finalize(GObject *object)
 {
-    NMDhcpClient *       self = NM_DHCP_CLIENT(object);
+    NMDhcpClient        *self = NM_DHCP_CLIENT(object);
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
 
-    nm_dhcp_client_stop(self, FALSE);
-
-    watch_cleanup(self);
-    timeout_cleanup(self);
-
-    nm_clear_g_free(&priv->iface);
-    nm_clear_g_free(&priv->hostname);
-    nm_clear_g_free(&priv->uuid);
-    nm_clear_g_free(&priv->anycast_address);
-    nm_clear_g_free(&priv->mud_url);
-    nm_clear_g_free(&priv->reject_servers);
-    nm_clear_pointer(&priv->client_id, g_bytes_unref);
-    nm_clear_pointer(&priv->hwaddr, g_bytes_unref);
-    nm_clear_pointer(&priv->bcast_hwaddr, g_bytes_unref);
-    nm_clear_pointer(&priv->vendor_class_identifier, g_bytes_unref);
-
-    G_OBJECT_CLASS(nm_dhcp_client_parent_class)->dispose(object);
+    config_clear(&priv->config);
 
-    priv->multi_idx = nm_dedup_multi_index_unref(priv->multi_idx);
+    G_OBJECT_CLASS(nm_dhcp_client_parent_class)->finalize(object);
 }
 
 static void
@@ -1256,165 +1231,19 @@ nm_dhcp_client_class_init(NMDhcpClientClass *client_class)
 
     g_type_class_add_private(client_class, sizeof(NMDhcpClientPrivate));
 
-#if NM_MORE_ASSERTS
-    object_class->constructed = constructed;
-#endif
     object_class->dispose      = dispose;
-    object_class->get_property = get_property;
+    object_class->finalize     = finalize;
     object_class->set_property = set_property;
 
     client_class->stop     = stop;
     client_class->get_duid = get_duid;
 
-    obj_properties[PROP_MULTI_IDX] =
-        g_param_spec_pointer(NM_DHCP_CLIENT_MULTI_IDX,
+    obj_properties[PROP_CONFIG] =
+        g_param_spec_pointer(NM_DHCP_CLIENT_CONFIG,
                              "",
                              "",
                              G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
-    obj_properties[PROP_IFACE] =
-        g_param_spec_string(NM_DHCP_CLIENT_INTERFACE,
-                            "",
-                            "",
-                            NULL,
-                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_IFINDEX] =
-        g_param_spec_int(NM_DHCP_CLIENT_IFINDEX,
-                         "",
-                         "",
-                         -1,
-                         G_MAXINT,
-                         -1,
-                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_HWADDR] =
-        g_param_spec_boxed(NM_DHCP_CLIENT_HWADDR,
-                           "",
-                           "",
-                           G_TYPE_BYTES,
-                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_BROADCAST_HWADDR] =
-        g_param_spec_boxed(NM_DHCP_CLIENT_BROADCAST_HWADDR,
-                           "",
-                           "",
-                           G_TYPE_BYTES,
-                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_ADDR_FAMILY] =
-        g_param_spec_int(NM_DHCP_CLIENT_ADDR_FAMILY,
-                         "",
-                         "",
-                         0,
-                         G_MAXINT,
-                         AF_UNSPEC,
-                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_ANYCAST_ADDRESS] =
-        g_param_spec_string(NM_DHCP_CLIENT_ANYCAST_ADDRESS,
-                            "",
-                            "",
-                            NULL,
-                            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_UUID] =
-        g_param_spec_string(NM_DHCP_CLIENT_UUID,
-                            "",
-                            "",
-                            NULL,
-                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_IAID] =
-        g_param_spec_uint(NM_DHCP_CLIENT_IAID,
-                          "",
-                          "",
-                          0,
-                          G_MAXUINT32,
-                          0,
-                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_IAID_EXPLICIT] =
-        g_param_spec_boolean(NM_DHCP_CLIENT_IAID_EXPLICIT,
-                             "",
-                             "",
-                             FALSE,
-                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_HOSTNAME] =
-        g_param_spec_string(NM_DHCP_CLIENT_HOSTNAME,
-                            "",
-                            "",
-                            NULL,
-                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_HOSTNAME_FLAGS] =
-        g_param_spec_uint(NM_DHCP_CLIENT_HOSTNAME_FLAGS,
-                          "",
-                          "",
-                          0,
-                          G_MAXUINT32,
-                          NM_DHCP_HOSTNAME_FLAG_NONE,
-                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_MUD_URL] =
-        g_param_spec_string(NM_DHCP_CLIENT_MUD_URL,
-                            "",
-                            "",
-                            NULL,
-                            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_ROUTE_TABLE] =
-        g_param_spec_uint(NM_DHCP_CLIENT_ROUTE_TABLE,
-                          "",
-                          "",
-                          0,
-                          G_MAXUINT32,
-                          RT_TABLE_MAIN,
-                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_ROUTE_METRIC] =
-        g_param_spec_uint(NM_DHCP_CLIENT_ROUTE_METRIC,
-                          "",
-                          "",
-                          0,
-                          G_MAXUINT32,
-                          0,
-                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
-
-    G_STATIC_ASSERT_EXPR(G_MAXINT32 == NM_DHCP_TIMEOUT_INFINITY);
-    obj_properties[PROP_TIMEOUT] =
-        g_param_spec_uint(NM_DHCP_CLIENT_TIMEOUT,
-                          "",
-                          "",
-                          1,
-                          G_MAXINT32,
-                          NM_DHCP_TIMEOUT_DEFAULT,
-                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_FLAGS] =
-        g_param_spec_uint(NM_DHCP_CLIENT_FLAGS,
-                          "",
-                          "",
-                          0,
-                          G_MAXUINT32,
-                          0,
-                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_VENDOR_CLASS_IDENTIFIER] =
-        g_param_spec_boxed(NM_DHCP_CLIENT_VENDOR_CLASS_IDENTIFIER,
-                           "",
-                           "",
-                           G_TYPE_BYTES,
-                           G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-    obj_properties[PROP_REJECT_SERVERS] =
-        g_param_spec_boxed(NM_DHCP_CLIENT_REJECT_SERVERS,
-                           "",
-                           "",
-                           G_TYPE_STRV,
-                           G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
     signals[SIGNAL_NOTIFY] =