summary refs log tree commit diff
path: root/src/core/dhcp
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/dhcp')
-rw-r--r--src/core/dhcp/README.next.md10
-rw-r--r--src/core/dhcp/nm-dhcp-client.c45
-rw-r--r--src/core/dhcp/nm-dhcp-client.h5
-rw-r--r--src/core/dhcp/nm-dhcp-dhclient-utils.c763
-rw-r--r--src/core/dhcp/nm-dhcp-dhclient-utils.h38
-rw-r--r--src/core/dhcp/nm-dhcp-dhclient.c741
-rw-r--r--src/core/dhcp/nm-dhcp-helper.c3
-rw-r--r--src/core/dhcp/nm-dhcp-listener.c3
-rw-r--r--src/core/dhcp/nm-dhcp-manager.c35
-rw-r--r--src/core/dhcp/nm-dhcp-nettools.c28
-rw-r--r--src/core/dhcp/nm-dhcp-systemd.c48
-rw-r--r--src/core/dhcp/nm-dhcp-utils.c58
-rw-r--r--src/core/dhcp/nm-dhcp-utils.h6
-rw-r--r--src/core/dhcp/tests/meson.build1
-rw-r--r--src/core/dhcp/tests/test-dhclient-commented-duid.leases2
-rw-r--r--src/core/dhcp/tests/test-dhclient-duid.leases2
-rw-r--r--src/core/dhcp/tests/test-dhcp-dhclient.c1478
17 files changed, 64 insertions, 3202 deletions
diff --git a/src/core/dhcp/README.next.md b/src/core/dhcp/README.next.md
index 88fa6683..1e61e75a 100644
--- a/src/core/dhcp/README.next.md
+++ b/src/core/dhcp/README.next.md
@@ -54,17 +54,14 @@ complexity out of `NMDevice`.
       mean that it gave up. It will keep retrying, it's just that there is little
       hope of getting a new lease. This happens, when you try to run DHCP on a Layer3
       link (WireGuard). There is little hope to succeed, but `NMDhcpClient`
-      (theoretically) will retry and may recover from this. Another example is when
-      we fail to start dhclient because it's not installed. In that case, we are not
-      optimistic to recover, however `NMDhcpDhclient` will retry (with backoff
-      timeout) and might still recover from this. For most cases, `NMDevice` will
+      (theoretically) will retry and may recover from this. For most cases, `NMDevice` will
       treat the no-lease cases the same, but in case of "bad" it might give up
       earlier.
 
 When a lease expires, that does not necessarily mean that we are now in a bad
 state. It might mean that the DHCP server is temporarily down, but we might
 recover from that easily. "bad" really means, something is wrong on our side
-which prevents us from getting a lease. Also, imagine `dhclient` dies (we would
+which prevents us from getting a lease. Also, imagine the DHCP client dies (we would
 try to restart, but assume that fails too), but we still have a valid lease,
 then possibly `NMDhcpClient` should still pretend all is good and we still have
 a lease until it expires. It may be we can recover before that happens. The
@@ -88,8 +85,7 @@ optionally does ACD first, then configures the IP address first and calls
 different lease). With this, the above state "has a lease" has actually three
 flavors: "has a lease but not yet ACD probed" and "has a lease but
 accepted/declined" (but `NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED` gets only emitted
-when we get the lease, not when we accept/decline it). With `dhclient`, when we
-receive a lease, it means  "has a lease but accepted" right away.
+when we get the lease, not when we accept/decline it).
 
 - for IPv6 prefix delegation, there is also `needed_prefixes` and
   `NM_DHCP_CLIENT_NOTIFY_TYPE_PREFIX_DELEGATED`. Currently `needed_prefixes` needs
diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c
index 18ad4024..c17e5544 100644
--- a/src/core/dhcp/nm-dhcp-client.c
+++ b/src/core/dhcp/nm-dhcp-client.c
@@ -266,9 +266,8 @@ nm_dhcp_client_create_options_dict(NMDhcpClient *self, gboolean static_keys)
         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. */
+        /* Note that nm-dhcp-helper based plugins may send the used client-id/DUID
+         * via the environment variables and overwrite them yet again. */
 
         nm_dhcp_option_take_option(options,
                                    static_keys,
@@ -786,13 +785,12 @@ _nm_dhcp_client_notify(NMDhcpClient         *self,
                        NMDhcpClientEventType client_event_type,
                        const NML3ConfigData *l3cd)
 {
-    NMDhcpClientPrivate                     *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
-    GHashTable                              *options;
-    gboolean                                 l3cd_changed;
-    NMOptionBool                             acd_state;
-    const int                                IS_IPv4     = NM_IS_IPv4(priv->config.addr_family);
-    nm_auto_unref_l3cd const NML3ConfigData *l3cd_merged = NULL;
-    char                                     sbuf1[NM_HASH_OBFUSCATE_PTR_STR_BUF_SIZE];
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+    GHashTable          *options;
+    gboolean             l3cd_changed;
+    NMOptionBool         acd_state;
+    const int            IS_IPv4 = NM_IS_IPv4(priv->config.addr_family);
+    char                 sbuf1[NM_HASH_OBFUSCATE_PTR_STR_BUF_SIZE];
 
     nm_assert(NM_IN_SET(client_event_type,
                         NM_DHCP_CLIENT_EVENT_TYPE_UNSPECIFIED,
@@ -825,16 +823,6 @@ _nm_dhcp_client_notify(NMDhcpClient         *self,
     if (client_event_type >= NM_DHCP_CLIENT_EVENT_TYPE_TIMEOUT)
         watch_cleanup(self);
 
-    if (!IS_IPv4 && l3cd) {
-        /* nm_dhcp_utils_merge_new_dhcp6_lease() relies on "life_starts" option
-         * for merging, which is only set by dhclient. Internal client never sets that,
-         * but it supports multiple IP addresses per lease. */
-        if (nm_dhcp_utils_merge_new_dhcp6_lease(priv->l3cd_next, l3cd, &l3cd_merged)) {
-            _LOGD("lease merged with existing one");
-            l3cd = nm_l3_config_data_seal(l3cd_merged);
-        }
-    }
-
     if (l3cd) {
         nm_clear_g_source_inst(&priv->no_lease_timeout_source);
     } else
@@ -1460,7 +1448,9 @@ nm_dhcp_client_schedule_ipv6_only_restart(NMDhcpClient *self, guint timeout)
     nm_assert(!priv->is_stopped);
 
     timeout = NM_MAX(priv->v4.ipv6_only_min_wait, timeout);
-    _LOGI("received option \"ipv6-only-preferred\": stopping DHCPv4 for %u seconds", timeout);
+    _LOGI("received option \"ipv6-only-preferred\": stopping DHCPv4 for %u seconds. Set "
+          "ipv4.dhcp-ipv6-only-preferred=no to force the use of IPv4 on this IPv6-mostly network",
+          timeout);
 
     nm_dhcp_client_stop(self, FALSE);
     nm_clear_g_source_inst(&priv->no_lease_timeout_source);
@@ -1690,21 +1680,14 @@ maybe_add_option(NMDhcpClient *self, GHashTable *hash, const char *key, GVariant
 
     g_hash_table_insert(hash, g_strdup(key), str_value);
 
-    /* dhclient has no special labels for private dhcp options: it uses "unknown_xyz"
-     * labels for that. We need to identify those to alias them to our "private_xyz"
-     * format unused in the internal dchp plugins.
-     */
+    /* "unknown_xyz" labels are aliased to our "private_xyz" format. */
     if ((priv_opt_num = label_is_unknown_xyz(key)) > 0) {
         gs_free guint8 *check_val = NULL;
         char           *hex_str   = NULL;
         gsize           len;
 
-        /* dhclient passes values from dhcp private options in its own "string" format:
-         * if the raw values are printable as ascii strings, it will pass the string
-         * representation; if the values are not printable as an ascii string, it will
-         * pass a string displaying the hex values (hex string). Try to enforce passing
-         * always an hex string, converting string representation if needed.
-         */
+        /* Private options may arrive as printable ascii strings or as hex strings.
+         * Normalize to always use hex string format. */
         check_val = nm_utils_hexstr2bin_alloc(str_value, FALSE, TRUE, ":", 0, &len);
         hex_str   = nm_utils_bin2hexstr_full(check_val ?: (guint8 *) str_value,
                                            check_val ? len : strlen(str_value),
diff --git a/src/core/dhcp/nm-dhcp-client.h b/src/core/dhcp/nm-dhcp-client.h
index a7b6ae98..12f3a276 100644
--- a/src/core/dhcp/nm-dhcp-client.h
+++ b/src/core/dhcp/nm-dhcp-client.h
@@ -53,8 +53,8 @@ typedef enum _nm_packed {
      * As such, it's never officially in a non-recoverable state.
      * However, there are cases when it really looks like we won't
      * be able to get a lease. For example, if the underlying interface
-     * is layer 3 only, if we have no IPv6 link local address for a prolonged
-     * time, or if dhclient is not installed.
+     * is layer 3 only, or if we have no IPv6 link local address for a prolonged
+     * time.
      * But even these cases are potentially recoverable. This is only
      * a hint to the user (which they might ignore).
      *
@@ -311,7 +311,6 @@ typedef struct {
 
 GType nm_dhcp_nettools_get_type(void);
 
-extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient;
 extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcd;
 extern const NMDhcpClientFactory _nm_dhcp_client_factory_internal;
 extern const NMDhcpClientFactory _nm_dhcp_client_factory_systemd;
diff --git a/src/core/dhcp/nm-dhcp-dhclient-utils.c b/src/core/dhcp/nm-dhcp-dhclient-utils.c
deleted file mode 100644
index 286f7aa1..00000000
--- a/src/core/dhcp/nm-dhcp-dhclient-utils.c
+++ /dev/null
@@ -1,763 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2011 Red Hat, Inc.
- */
-
-#include "src/core/nm-default-daemon.h"
-
-#include "nm-dhcp-dhclient-utils.h"
-
-#include <ctype.h>
-#include <arpa/inet.h>
-#include <net/if.h>
-#include <linux/if_ether.h>
-
-#include "libnm-glib-aux/nm-dedup-multi.h"
-
-#include "nm-dhcp-utils.h"
-#include "nm-utils.h"
-#include "libnm-platform/nm-platform.h"
-#include "NetworkManagerUtils.h"
-
-#define TIMEOUT_TAG  "timeout "
-#define RETRY_TAG    "retry "
-#define CLIENTID_TAG "send dhcp-client-identifier"
-
-#define HOSTNAME4_TAG    "send host-name"
-#define HOSTNAME4_FORMAT HOSTNAME4_TAG " \"%s\"; # added by NetworkManager"
-
-#define FQDN_TAG_PREFIX "send fqdn."
-#define FQDN_TAG        FQDN_TAG_PREFIX "fqdn"
-#define FQDN_FORMAT     FQDN_TAG " \"%s\"; # added by NetworkManager"
-
-#define ALSOREQ_TAG "also request "
-#define REQ_TAG     "request "
-
-#define MUDURLv4_DEF "option mudurl code 161 = text;\n"
-#define MUDURLv4_FMT "send mudurl \"%s\";\n"
-
-#define MUDURLv6_DEF "option dhcp6.mudurl code 112 = text;\n"
-#define MUDURLv6_FMT "send dhcp6.mudurl \"%s\";\n"
-
-static void
-add_request(GPtrArray *array, const char *item)
-{
-    guint i;
-
-    for (i = 0; i < array->len; i++) {
-        if (nm_streq(array->pdata[i], item))
-            return;
-    }
-    g_ptr_array_add(array, g_strdup(item));
-}
-
-static gboolean
-grab_request_options(GPtrArray *store, const char *line)
-{
-    gs_free const char **line_v = NULL;
-    gsize                i;
-
-    /* Grab each 'request' or 'also request'  option and save for later */
-    line_v = nm_strsplit_set(line, "\t ,");
-    for (i = 0; line_v && line_v[i]; i++) {
-        const char *ss = nm_str_skip_leading_spaces(line_v[i]);
-        gsize       l;
-        gboolean    end = FALSE;
-
-        if (!ss[0])
-            continue;
-        if (ss[0] == ';') {
-            /* all done */
-            return TRUE;
-        }
-
-        if (!g_ascii_isalnum(ss[0]))
-            continue;
-
-        l = strlen(ss);
-
-        while (l > 0 && g_ascii_isspace(ss[l - 1])) {
-            ((char *) ss)[l - 1] = '\0';
-            l--;
-        }
-        if (l > 0 && ss[l - 1] == ';') {
-            /* Remove the EOL marker */
-            ((char *) ss)[l - 1] = '\0';
-            end                  = TRUE;
-        }
-
-        if (ss[0])
-            add_request(store, ss);
-
-        if (end)
-            return TRUE;
-    }
-
-    return FALSE;
-}
-
-static void
-add_ip4_config(GString            *str,
-               GBytes             *client_id,
-               const char         *hostname,
-               gboolean            use_fqdn,
-               NMDhcpHostnameFlags hostname_flags)
-{
-    if (client_id) {
-        const char *p;
-        gsize       l;
-        guint       i;
-
-        p = g_bytes_get_data(client_id, &l);
-        nm_assert(p);
-
-        /* Allow type 0 (non-hardware address) to be represented as a string
-         * as long as all the characters are printable.
-         */
-        for (i = 1; (p[0] == 0) && i < l; i++) {
-            if (!g_ascii_isprint(p[i]) || p[i] == '\\' || p[i] == '"')
-                break;
-        }
-
-        g_string_append(str, CLIENTID_TAG " ");
-        if (l == 0) {
-            /* An empty value effectively unsets the client-id to avoid sending it */
-            g_string_append(str, "\"\"");
-        } else if (i < l) {
-            /* Unprintable; convert to a hex string */
-            for (i = 0; i < l; i++) {
-                if (i > 0)
-                    g_string_append_c(str, ':');
-                g_string_append_printf(str, "%02x", (guint8) p[i]);
-            }
-        } else {
-            /* Printable; just add to the line with type 0 */
-            g_string_append_c(str, '"');
-            g_string_append(str, "\\x00");
-            g_string_append_len(str, p + 1, l - 1);
-            g_string_append_c(str, '"');
-        }
-        g_string_append(str, "; # added by NetworkManager\n");
-    }
-
-    if (hostname) {
-        if (use_fqdn) {
-            g_string_append_printf(str, FQDN_FORMAT "\n", hostname);
-
-            g_string_append_printf(str,
-                                   FQDN_TAG_PREFIX "encoded %s;\n",
-                                   (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED) ? "on"
-                                                                                         : "off");
-
-            g_string_append_printf(
-                str,
-                FQDN_TAG_PREFIX "server-update %s;\n",
-                (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE) ? "on" : "off");
-
-            g_string_append_printf(str,
-                                   FQDN_TAG_PREFIX "no-client-update %s;\n",
-                                   (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE) ? "on"
-                                                                                           : "off");
-        } else
-            g_string_append_printf(str, HOSTNAME4_FORMAT "\n", hostname);
-    }
-
-    g_string_append_c(str, '\n');
-
-    /* Define options for classless static routes */
-    g_string_append(
-        str,
-        "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n");
-    g_string_append(str,
-                    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n");
-    /* Web Proxy Auto-Discovery option (bgo #368423) */
-    g_string_append(str, "option wpad code 252 = string;\n");
-
-    g_string_append_c(str, '\n');
-}
-
-static void
-add_hostname6(GString *str, const char *hostname, NMDhcpHostnameFlags hostname_flags)
-{
-    if (hostname) {
-        g_string_append_printf(str, FQDN_FORMAT "\n", hostname);
-        if (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE)
-            g_string_append(str, FQDN_TAG_PREFIX "server-update on;\n");
-        if (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE)
-            g_string_append(str, FQDN_TAG_PREFIX "no-client-update on;\n");
-        g_string_append_c(str, '\n');
-    }
-}
-
-static void
-add_mud_url_config(GString *str, const char *mud_url, int addr_family)
-{
-    if (mud_url) {
-        if (addr_family == AF_INET) {
-            g_string_append(str, MUDURLv4_DEF);
-            g_string_append_printf(str, MUDURLv4_FMT, mud_url);
-        } else {
-            g_string_append(str, MUDURLv6_DEF);
-            g_string_append_printf(str, MUDURLv6_FMT, mud_url);
-        }
-    }
-}
-
-static GBytes *
-read_client_id(const char *str)
-{
-    gs_free char *s = NULL;
-    char         *p;
-    int           i = 0;
-    int           j = 0;
-    gsize         l;
-
-    nm_assert(NM_STR_HAS_PREFIX(str, CLIENTID_TAG));
-    str += NM_STRLEN(CLIENTID_TAG);
-
-    if (!g_ascii_isspace(*str))
-        return NULL;
-    while (g_ascii_isspace(*str))
-        str++;
-
-    if (*str == '"') {
-        /* Parse string literal with escape sequences */
-        s = g_strdup(str + 1);
-        p = strrchr(s, '"');
-        if (p)
-            *p = '\0';
-        else
-            return NULL;
-
-        if (!s[0])
-            return NULL;
-
-        while (s[i]) {
-            if (s[i] == '\\' && s[i + 1] == 'x' && g_ascii_isxdigit(s[i + 2])
-                && g_ascii_isxdigit(s[i + 3])) {
-                s[j++] = (g_ascii_xdigit_value(s[i + 2]) << 4) + g_ascii_xdigit_value(s[i + 3]);
-                i += 4;
-                continue;
-            }
-            if (s[i] == '\\' && s[i + 1] >= '0' && s[i + 1] <= '7' && s[1 + 2] >= '0'
-                && s[i + 2] <= '7' && s[1 + 3] >= '0' && s[i + 3] <= '7') {
-                s[j++] = ((s[i + 1] - '0') << 6) + ((s[i + 2] - '0') << 3) + (s[i + 3] - '0');
-                i += 4;
-                continue;
-            }
-            s[j++] = s[i++];
-        }
-        return g_bytes_new_take(g_steal_pointer(&s), j);
-    }
-
-    /* Otherwise, try to read a hexadecimal sequence */
-    s = g_strdup(str);
-    g_strchomp(s);
-    l = strlen(s);
-    if (l > 0 && s[l - 1] == ';')
-        s[l - 1] = '\0';
-
-    return nm_utils_hexstr2bin(s);
-}
-
-static gboolean
-read_interface(const char *line, char *interface, guint size)
-{
-    gs_free char *dup = g_strdup(line + NM_STRLEN("interface"));
-    char         *ptr = dup, *end;
-
-    while (g_ascii_isspace(*ptr))
-        ptr++;
-
-    if (*ptr == '"') {
-        ptr++;
-        end = strchr(ptr, '"');
-        if (!end)
-            return FALSE;
-        *end = '\0';
-    } else {
-        end = strchr(ptr, ' ');
-        if (!end)
-            end = strchr(ptr, '{');
-        if (!end)
-            return FALSE;
-        *end = '\0';
-    }
-
-    if (ptr[0] == '\0' || strlen(ptr) + 1 > size)
-        return FALSE;
-
-    g_snprintf(interface, size, "%s", ptr);
-
-    return TRUE;
-}
-
-char *
-nm_dhcp_dhclient_create_config(const char         *interface,
-                               int                 addr_family,
-                               GBytes             *client_id,
-                               gboolean            send_client_id,
-                               const char         *anycast_address,
-                               const char         *hostname,
-                               guint32             timeout,
-                               gboolean            use_fqdn,
-                               NMDhcpHostnameFlags hostname_flags,
-                               const char         *mud_url,
-                               const char *const  *reject_servers,
-                               const char         *orig_path,
-                               const char         *orig_contents,
-                               GBytes            **out_new_client_id)
-{
-    nm_auto_free_gstring GString *new_contents  = NULL;
-    gs_unref_ptrarray GPtrArray  *fqdn_opts     = NULL;
-    gs_unref_ptrarray GPtrArray  *reqs          = NULL;
-    gboolean                      reset_reqlist = FALSE;
-    int                           i;
-
-    g_return_val_if_fail(!anycast_address || nm_utils_hwaddr_valid(anycast_address, ETH_ALEN),
-                         NULL);
-    g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), NULL);
-    g_return_val_if_fail(!reject_servers || addr_family == AF_INET, NULL);
-    nm_assert(!out_new_client_id || !*out_new_client_id);
-
-    new_contents = g_string_new(_("# Created by NetworkManager\n"));
-    reqs         = g_ptr_array_new_full(5, g_free);
-
-    if (orig_contents) {
-        gs_free const char          **lines = NULL;
-        gsize                         line_i;
-        nm_auto_free_gstring GString *blocks_stack = NULL;
-        guint                         blocks_skip  = 0;
-        gboolean                      in_alsoreq   = FALSE;
-        gboolean                      in_req       = FALSE;
-        char                          intf[IFNAMSIZ];
-
-        blocks_stack = g_string_new(NULL);
-        g_string_append_printf(new_contents, _("# Merged from %s\n\n"), orig_path);
-        intf[0] = '\0';
-
-        lines = nm_strsplit_set(orig_contents, "\n\r");
-        for (line_i = 0; lines && lines[line_i]; line_i++) {
-            const char *line = nm_str_skip_leading_spaces(lines[line_i]);
-            const char *p;
-
-            if (line[0] == '\0')
-                continue;
-
-            g_strchomp((char *) line);
-
-            p = line;
-            if (in_req) {
-                /* pass */
-            } else if (strchr(p, '{')) {
-                if (NM_STR_HAS_PREFIX(p, "lease") || NM_STR_HAS_PREFIX(p, "alias")
-                    || NM_STR_HAS_PREFIX(p, "interface") || NM_STR_HAS_PREFIX(p, "pseudo")) {
-                    /* skip over these blocks, except 'interface' when it
-                     * matches the current interface */
-                    blocks_skip++;
-                    g_string_append_c(blocks_stack, 'b');
-                    if (!intf[0] && NM_STR_HAS_PREFIX(p, "interface")) {
-                        if (read_interface(p, intf, sizeof(intf)))
-                            continue;
-                    }
-                } else {
-                    /* allow other blocks (conditionals) */
-                    if (!strchr(p, '}')) /* '} else {'  */
-                        g_string_append_c(blocks_stack, 'c');
-                }
-            } else if (strchr(p, '}')) {
-                if (blocks_stack->len > 0) {
-                    if (blocks_stack->str[blocks_stack->len - 1] == 'b') {
-                        g_string_truncate(blocks_stack, blocks_stack->len - 1);
-                        nm_assert(blocks_skip > 0);
-                        blocks_skip--;
-                        intf[0] = '\0';
-                        continue;
-                    }
-                    g_string_truncate(blocks_stack, blocks_stack->len - 1);
-                }
-            }
-
-            if (blocks_skip > 0 && !intf[0])
-                continue;
-
-            if (intf[0] && !nm_streq(intf, interface))
-                continue;
-
-            /* Some timing parameters in dhclient should not be imported (timeout, retry).
-             * The retry parameter will be simply not used as we will exit on first failure.
-             * The timeout one instead may affect NetworkManager behavior: if the timeout
-             * elapses before dhcp-timeout dhclient will report failure and cause NM to
-             * fail the dhcp process before dhcp-timeout. So, always skip importing timeout
-             * as we will need to add one greater than dhcp-timeout.
-             */
-            if (NM_STR_HAS_PREFIX(p, TIMEOUT_TAG) || NM_STR_HAS_PREFIX(p, RETRY_TAG))
-                continue;
-
-            if (NM_STR_HAS_PREFIX(p, CLIENTID_TAG)) {
-                /* Skip "dhcp-client-id" if the connection has defined a custom one or "none" */
-                if (client_id || !send_client_id)
-                    continue;
-
-                /* Otherwise, capture and return the existing client id */
-                if (out_new_client_id)
-                    nm_clear_pointer(out_new_client_id, g_bytes_unref);
-                NM_SET_OUT(out_new_client_id, read_client_id(p));
-                /* fall-through. We keep the line... */
-            }
-
-            /* Override config file hostname and use one from the connection */
-            if (hostname) {
-                if (NM_STR_HAS_PREFIX(p, HOSTNAME4_TAG))
-                    continue;
-                if (NM_STR_HAS_PREFIX(p, FQDN_TAG))
-                    continue;
-            }
-
-            /* To let user's FQDN options (except "fqdn.fqdn") override the
-             * default ones set by NM, add them later
-             */
-            if (NM_STR_HAS_PREFIX(p, FQDN_TAG_PREFIX)) {
-                if (!fqdn_opts)
-                    fqdn_opts = g_ptr_array_new_full(5, g_free);
-                g_ptr_array_add(fqdn_opts, g_strdup(p + NM_STRLEN(FQDN_TAG_PREFIX)));
-                continue;
-            }
-
-            /* Ignore 'script' since we pass our own */
-            if (g_str_has_prefix(p, "script "))
-                continue;
-
-            /* Check for "request" */
-            if (NM_STR_HAS_PREFIX(p, REQ_TAG)) {
-                in_req = TRUE;
-                p += NM_STRLEN(REQ_TAG);
-                g_ptr_array_set_size(reqs, 0);
-                reset_reqlist = TRUE;
-            }
-
-            /* Save all request options for later use */
-            if (in_req) {
-                in_req = !grab_request_options(reqs, p);
-                continue;
-            }
-
-            /* Check for "also require" */
-            if (NM_STR_HAS_PREFIX(p, ALSOREQ_TAG)) {
-                in_alsoreq = TRUE;
-                p += NM_STRLEN(ALSOREQ_TAG);
-            }
-
-            if (in_alsoreq) {
-                in_alsoreq = !grab_request_options(reqs, p);
-                continue;
-            }
-
-            /* Existing configuration line is OK, add it to new configuration */
-            g_string_append(new_contents, line);
-            g_string_append_c(new_contents, '\n');
-        }
-    } else
-        g_string_append_c(new_contents, '\n');
-
-    /* ensure dhclient timeout is greater than dhcp-timeout: as dhclient timeout default value is
-     * 60 seconds, we need this only if dhcp-timeout is greater than 60.
-     */
-    if (timeout >= 60) {
-        timeout = timeout < G_MAXINT32 ? timeout + 1 : G_MAXINT32;
-        g_string_append_printf(new_contents, "timeout %u;\n", timeout);
-    }
-
-    add_mud_url_config(new_contents, mud_url, addr_family);
-
-    if (reject_servers && reject_servers[0]) {
-        g_string_append(new_contents, "reject ");
-        for (i = 0; reject_servers[i]; i++) {
-            if (i != 0)
-                g_string_append(new_contents, ", ");
-            g_string_append(new_contents, reject_servers[i]);
-        }
-        g_string_append(new_contents, ";\n");
-    }
-
-    if (addr_family == AF_INET) {
-        nm_auto_unref_bytes GBytes *client_id_none = NULL;
-        client_id = send_client_id ? client_id : (client_id_none = g_bytes_new_static("", 0));
-        add_ip4_config(new_contents, client_id, hostname, use_fqdn, hostname_flags);
-        add_request(reqs, "rfc3442-classless-static-routes");
-        add_request(reqs, "ms-classless-static-routes");
-        add_request(reqs, "static-routes");
-        add_request(reqs, "wpad");
-        add_request(reqs, "ntp-servers");
-        add_request(reqs, "root-path");
-    } else {
-        add_hostname6(new_contents, hostname, hostname_flags);
-        add_request(reqs, "dhcp6.name-servers");
-        add_request(reqs, "dhcp6.domain-search");
-
-        /* FIXME: internal client does not support requesting client-id option. Does this even work? */
-        add_request(reqs, "dhcp6.client-id");
-    }
-
-    if (reset_reqlist)
-        g_string_append(new_contents, "request; # override dhclient defaults\n");
-    /* And add it to the dhclient configuration */
-    for (i = 0; i < reqs->len; i++)
-        g_string_append_printf(new_contents, "also request %s;\n", (char *) reqs->pdata[i]);
-
-    if (fqdn_opts) {
-        for (i = 0; i < fqdn_opts->len; i++) {
-            const char *t = fqdn_opts->pdata[i];
-
-            if (i == 0)
-                g_string_append_printf(new_contents, "\n# FQDN options from %s\n", orig_path);
-            g_string_append_printf(new_contents, FQDN_TAG_PREFIX "%s\n", t);
-        }
-    }
-
-    g_string_append_c(new_contents, '\n');
-
-    if (anycast_address) {
-        g_string_append_printf(new_contents,
-                               "interface \"%s\" {\n"
-                               " initial-interval 1; \n"
-                               " anycast-mac ethernet %s;\n"
-                               "}\n",
-                               interface,
-                               anycast_address);
-    }
-
-    return g_string_free(g_steal_pointer(&new_contents), FALSE);
-}
-
-/* In the lease file, dhclient will write "option dhcp6.client-id $HEXSTR". This
- * function does the same. */
-static char *
-nm_dhcp_dhclient_escape_duid_as_hex(GBytes *duid)
-{
-    const guint8 *s;
-    gsize         len;
-
-    nm_assert(duid);
-
-    s = g_bytes_get_data(duid, &len);
-    return nm_utils_bin2hexstr_fuller(s, len, ':', FALSE, FALSE, NULL);
-}
-
-/* Roughly follow what dhclient's quotify_buf() and pretty_escape() functions do */
-char *
-nm_dhcp_dhclient_escape_duid(GBytes *duid)
-{
-    char         *escaped;
-    const guint8 *s, *s0;
-    gsize         len;
-    char         *d;
-
-    g_return_val_if_fail(duid, NULL);
-
-    s0 = g_bytes_get_data(duid, &len);
-    s  = s0;
-
-    d = escaped = g_malloc((len * 4) + 1);
-    while (s < (s0 + len)) {
-        if (!g_ascii_isprint(*s)) {
-            *d++ = '\\';
-            *d++ = '0' + ((*s >> 6) & 0x7);
-            *d++ = '0' + ((*s >> 3) & 0x7);
-            *d++ = '0' + (*s++ & 0x7);
-        } else if (*s == '"' || *s == '\'' || *s == '$' || *s == '`' || *s == '\\' || *s == '|'
-                   || *s == '&') {
-            *d++ = '\\';
-            *d++ = *s++;
-        } else
-            *d++ = *s++;
-    }
-    *d++ = '\0';
-    return escaped;
-}
-
-static gboolean
-isoctal(const guint8 *p)
-{
-    return (p[0] >= '0' && p[0] <= '3' && p[1] >= '0' && p[1] <= '7' && p[2] >= '0' && p[2] <= '7');
-}
-
-GBytes *
-nm_dhcp_dhclient_unescape_duid(const char *duid)
-{
-    GByteArray   *unescaped;
-    const guint8 *p = (const guint8 *) duid;
-    guint         i, len;
-    guint8        octal;
-
-    /* FIXME: it's wrong to have an "unescape-duid" function. dhclient
-     * defines a file format with escaping. So we need a general unescape
-     * function that can handle dhclient syntax. */
-
-    len       = strlen(duid);
-    unescaped = g_byte_array_sized_new(len);
-    for (i = 0; i < len; i++) {
-        if (p[i] == '\\') {
-            i++;
-            if (isdigit(p[i])) {
-                /* Octal escape sequence */
-                if (i + 2 >= len || !isoctal(p + i))
-                    goto error;
-                octal = ((p[i] - '0') << 6) + ((p[i + 1] - '0') << 3) + (p[i + 2] - '0');
-                g_byte_array_append(unescaped, &octal, 1);
-                i += 2;
-            } else {
-                /* FIXME: don't warn on untrusted data. Either signal an error, or accept
-                 * it silently. */
-
-                /* One of ", ', $, `, \, |, or & */
-                g_warn_if_fail(p[i] == '"' || p[i] == '\'' || p[i] == '$' || p[i] == '`'
-                               || p[i] == '\\' || p[i] == '|' || p[i] == '&');
-                g_byte_array_append(unescaped, &p[i], 1);
-            }
-        } else
-            g_byte_array_append(unescaped, &p[i], 1);
-    }
-
-    return g_byte_array_free_to_bytes(unescaped);
-
-error:
-    g_byte_array_free(unescaped, TRUE);
-    return NULL;
-}
-
-#define DEFAULT_DUID_PREFIX "default-duid \""
-
-/* Beware: @error may be unset even if the function returns %NULL. */
-GBytes *
-nm_dhcp_dhclient_read_duid(const char *leasefile, GError **error)
-{
-    gs_free char        *contents   = NULL;
-    gs_free const char **contents_v = NULL;
-    gsize                i;
-
-    if (!g_file_test(leasefile, G_FILE_TEST_EXISTS))
-        return NULL;
-
-    if (!g_file_get_contents(leasefile, &contents, NULL, error))
-        return NULL;
-
-    contents_v = nm_strsplit_set(contents, "\n\r");
-    for (i = 0; contents_v && contents_v[i]; i++) {
-        const char *p = nm_str_skip_leading_spaces(contents_v[i]);
-        GBytes     *duid;
-
-        if (!NM_STR_HAS_PREFIX(p, DEFAULT_DUID_PREFIX))
-            continue;
-
-        p += NM_STRLEN(DEFAULT_DUID_PREFIX);
-
-        g_strchomp((char *) p);
-
-        if (!NM_STR_HAS_SUFFIX(p, "\";"))
-            continue;
-
-        ((char *) p)[strlen(p) - 2] = '\0';
-
-        duid = nm_dhcp_dhclient_unescape_duid(p);
-        if (duid)
-            return duid;
-    }
-
-    return NULL;
-}
-
-gboolean
-nm_dhcp_dhclient_save_duid(const char *leasefile,
-                           GBytes     *duid,
-                           gboolean    enforce_duid,
-                           GError    **error)
-{
-    gs_free char                 *escaped_duid = NULL;
-    gs_free const char          **lines        = NULL;
-    nm_auto_free_gstring GString *s            = NULL;
-    const char *const            *iter;
-    gs_free char                 *conflicting_duid_line = NULL;
-    gs_free char                 *contents              = NULL;
-    gsize                         contents_len          = 0;
-
-    g_return_val_if_fail(leasefile != NULL, FALSE);
-
-    if (!duid) {
-        nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "missing duid");
-        g_return_val_if_reached(FALSE);
-    }
-
-    escaped_duid = nm_dhcp_dhclient_escape_duid(duid);
-    nm_assert(escaped_duid);
-
-    if (g_file_test(leasefile, G_FILE_TEST_EXISTS)) {
-        if (!g_file_get_contents(leasefile, &contents, &contents_len, error)) {
-            g_prefix_error(error, "failed to read lease file %s: ", leasefile);
-            return FALSE;
-        }
-
-        lines = nm_strsplit_set_with_empty(contents, "\n");
-    }
-
-    s = g_string_sized_new(contents_len + 50);
-    g_string_append_printf(s, DEFAULT_DUID_PREFIX "%s\";\n", escaped_duid);
-
-    /* Preserve existing leasefile contents */
-    if (lines) {
-        for (iter = lines; *iter; iter++) {
-            const char *str = *iter;
-            const char *l;
-            gboolean    ends_with_r;
-            gsize       l_len;
-            gsize       prefix_len;
-
-            l          = nm_str_skip_leading_spaces(str);
-            l_len      = strlen(l);
-            prefix_len = l - str;
-
-            ends_with_r = l_len > 0 && l[l_len - 1u] == '\r';
-            if (ends_with_r) {
-                ((char *) l)[--l_len] = '\0';
-            }
-
-            if (NM_STR_HAS_PREFIX(l, DEFAULT_DUID_PREFIX)) {
-                /* We always add our line on top. This line can be skipped. */
-                continue;
-            }
-
-            if (enforce_duid & NM_STR_HAS_PREFIX(l, "option dhcp6.client-id ")) {
-                /* we want to use our duid. Skip the per-lease client-id. */
-                if (!conflicting_duid_line) {
-                    gs_free char *duid_hex = nm_dhcp_dhclient_escape_duid_as_hex(duid);
-
-                    conflicting_duid_line = g_strdup_printf("option dhcp6.client-id %s;", duid_hex);
-                }
-                /* We adjust the duid line and set what we want. */
-                l = conflicting_duid_line;
-            }
-
-            g_string_append_len(s, str, prefix_len);
-            g_string_append(s, l);
-            if (ends_with_r) {
-                g_string_append_c(s, '\r');
-                g_string_append_c(s, '\n');
-            } else if ((iter[1]) != NULL) {
-                /* avoid to add an extra '\n' at the end of file */
-                g_string_append_c(s, '\n');
-            }
-        }
-    }
-
-    if (contents && strlen(contents) == contents_len && nm_streq(contents, s->str)) {
-        /* The file is already as we want it. We are done. */
-        return TRUE;
-    }
-
-    if (!g_file_set_contents(leasefile, s->str, -1, error)) {
-        g_prefix_error(error, "failed to set DUID in lease file %s: ", leasefile);
-        return FALSE;
-    }
-
-    return TRUE;
-}
diff --git a/src/core/dhcp/nm-dhcp-dhclient-utils.h b/src/core/dhcp/nm-dhcp-dhclient-utils.h
deleted file mode 100644
index 34b26175..00000000
--- a/src/core/dhcp/nm-dhcp-dhclient-utils.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2010 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__
-#define __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__
-
-#include "nm-setting-ip4-config.h"
-#include "nm-setting-ip6-config.h"
-
-char *nm_dhcp_dhclient_create_config(const char         *interface,
-                                     int                 addr_family,
-                                     GBytes             *client_id,
-                                     gboolean            send_client_id,
-                                     const char         *anycast_addr,
-                                     const char         *hostname,
-                                     guint32             timeout,
-                                     gboolean            use_fqdn,
-                                     NMDhcpHostnameFlags hostname_flags,
-                                     const char         *mud_url,
-                                     const char *const  *reject_servers,
-                                     const char         *orig_path,
-                                     const char         *orig_contents,
-                                     GBytes            **out_new_client_id);
-
-char *nm_dhcp_dhclient_escape_duid(GBytes *duid);
-
-GBytes *nm_dhcp_dhclient_unescape_duid(const char *duid);
-
-GBytes *nm_dhcp_dhclient_read_duid(const char *leasefile, GError **error);
-
-gboolean nm_dhcp_dhclient_save_duid(const char *leasefile,
-                                    GBytes     *duid,
-                                    gboolean    enforce_duid,
-                                    GError    **error);
-
-#endif /* __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__ */
diff --git a/src/core/dhcp/nm-dhcp-dhclient.c b/src/core/dhcp/nm-dhcp-dhclient.c
deleted file mode 100644
index 7e00599c..00000000
--- a/src/core/dhcp/nm-dhcp-dhclient.c
+++ /dev/null
@@ -1,741 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2005 - 2012 Red Hat, Inc.
- */
-
-#include <config.h>
-#define __CONFIG_H__
-
-#define _XOPEN_SOURCE
-#include <time.h>
-#undef _XOPEN_SOURCE
-
-#include "src/core/nm-default-daemon.h"
-
-#if WITH_DHCLIENT
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <ctype.h>
-
-#include "libnm-glib-aux/nm-dedup-multi.h"
-
-#include "nm-utils.h"
-#include "nm-dhcp-dhclient-utils.h"
-#include "nm-dhcp-manager.h"
-#include "NetworkManagerUtils.h"
-#include "nm-dhcp-listener.h"
-#include "nm-dhcp-client-logging.h"
-
-/*****************************************************************************/
-
-static const char *
-_addr_family_to_path_part(int addr_family)
-{
-    nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
-    return (addr_family == AF_INET6) ? "6" : "";
-}
-
-/*****************************************************************************/
-
-#define NM_TYPE_DHCP_DHCLIENT (nm_dhcp_dhclient_get_type())
-#define NM_DHCP_DHCLIENT(obj) \
-    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclient))
-#define NM_DHCP_DHCLIENT_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass))
-#define NM_IS_DHCP_DHCLIENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_DHCLIENT))
-#define NM_IS_DHCP_DHCLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_DHCLIENT))
-#define NM_DHCP_DHCLIENT_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass))
-
-typedef struct _NMDhcpDhclient      NMDhcpDhclient;
-typedef struct _NMDhcpDhclientClass NMDhcpDhclientClass;
-
-static GType nm_dhcp_dhclient_get_type(void);
-
-/*****************************************************************************/
-
-typedef struct {
-    char           *conf_file;
-    const char     *def_leasefile;
-    char           *lease_file;
-    char           *pid_file;
-    NMDhcpListener *dhcp_listener;
-} NMDhcpDhclientPrivate;
-
-struct _NMDhcpDhclient {
-    NMDhcpClient          parent;
-    NMDhcpDhclientPrivate _priv;
-};
-
-struct _NMDhcpDhclientClass {
-    NMDhcpClientClass parent;
-};
-
-G_DEFINE_TYPE(NMDhcpDhclient, nm_dhcp_dhclient, NM_TYPE_DHCP_CLIENT)
-
-#define NM_DHCP_DHCLIENT_GET_PRIVATE(self) \
-    _NM_GET_PRIVATE(self, NMDhcpDhclient, NM_IS_DHCP_DHCLIENT)
-
-/*****************************************************************************/
-
-static GBytes *read_duid_from_lease(NMDhcpDhclient *self);
-
-/*****************************************************************************/
-
-static const char *
-nm_dhcp_dhclient_get_path(void)
-{
-    return nm_utils_find_helper("dhclient", DHCLIENT_PATH, NULL);
-}
-
-/**
- * get_dhclient_leasefile():
- * @addr_family: AF_INET or AF_INET6
- * @iface: the interface name of the device on which DHCP will be done
- * @uuid: the connection UUID to which the returned lease should belong
- * @out_preferred_path: on return, the "most preferred" leasefile path
- *
- * Returns the path of an existing leasefile (if any) for this interface and
- * connection UUID.  Also returns the "most preferred" leasefile path, which
- * may be different than any found leasefile.
- *
- * Returns: an existing leasefile, or %NULL if no matching leasefile could be found
- */
-static char *
-get_dhclient_leasefile(int         addr_family,
-                       const char *iface,
-                       const char *uuid,
-                       char      **out_preferred_path)
-{
-    gs_free char *path = NULL;
-
-    if (nm_dhcp_utils_get_leasefile_path(addr_family, "dhclient", iface, uuid, &path)) {
-        NM_SET_OUT(out_preferred_path, g_strdup(path));
-        return g_steal_pointer(&path);
-    }
-
-    NM_SET_OUT(out_preferred_path, g_steal_pointer(&path));
-
-    /* If the leasefile we're looking for doesn't exist yet in the new location
-     * (eg, /var/lib/NetworkManager) then look in old locations to maintain
-     * backwards compatibility with external tools (like dracut) that put
-     * leasefiles there.
-     */
-
-    /* Old Debian, SUSE, and Mandriva location */
-    g_free(path);
-    path = g_strdup_printf(LOCALSTATEDIR "/lib/dhcp/dhclient%s-%s-%s.lease",
-                           _addr_family_to_path_part(addr_family),
-                           uuid,
-                           iface);
-    if (g_file_test(path, G_FILE_TEST_EXISTS))
-        return g_steal_pointer(&path);
-
-    /* Old Red Hat and Fedora location */
-    g_free(path);
-    path = g_strdup_printf(LOCALSTATEDIR "/lib/dhclient/dhclient%s-%s-%s.lease",
-                           _addr_family_to_path_part(addr_family),
-                           uuid,
-                           iface);
-    if (g_file_test(path, G_FILE_TEST_EXISTS))
-        return g_steal_pointer(&path);
-
-    /* Fail */
-    return NULL;
-}
-
-static char *
-find_existing_config(NMDhcpDhclient *self, int addr_family, const char *iface, const char *uuid)
-{
-    char *path;
-
-    /* NetworkManager-overridden configuration can be used to ship DHCP config
-     * with NetworkManager itself. It can be uuid-specific, device-specific
-     * or generic.
-     */
-    if (uuid) {
-        path = g_strdup_printf(NMCONFDIR "/dhclient%s-%s.conf",
-                               _addr_family_to_path_part(addr_family),
-                               uuid);
-        _LOGD("looking for existing config %s", path);
-        if (g_file_test(path, G_FILE_TEST_EXISTS))
-            return path;
-        g_free(path);
-    }
-
-    path = g_strdup_printf(NMCONFDIR "/dhclient%s-%s.conf",
-                           _addr_family_to_path_part(addr_family),
-                           iface);
-    _LOGD("looking for existing config %s", path);
-    if (g_file_test(path, G_FILE_TEST_EXISTS))
-        return path;
-    g_free(path);
-
-    path = g_strdup_printf(NMCONFDIR "/dhclient%s.conf", _addr_family_to_path_part(addr_family));
-    _LOGD("looking for existing config %s", path);
-    if (g_file_test(path, G_FILE_TEST_EXISTS))
-        return path;
-    g_free(path);
-
-    /* Distribution's dhclient configuration is used so that we can use
-     * configuration shipped with dhclient (if any).
-     *
-     * This replaces conditional compilation based on distribution name. Fedora
-     * and Debian store the configs in /etc/dhcp while upstream defaults to /etc
-     * which is then used by many other distributions. Some distributions
-     * (including Fedora) don't even provide a default configuration file.
-     */
-    path = g_strdup_printf(SYSCONFDIR "/dhcp/dhclient%s-%s.conf",
-                           _addr_family_to_path_part(addr_family),
-                           iface);
-    _LOGD("looking for existing config %s", path);
-    if (g_file_test(path, G_FILE_TEST_EXISTS))
-        return path;
-    g_free(path);
-
-    path = g_strdup_printf(SYSCONFDIR "/dhclient%s-%s.conf",
-                           _addr_family_to_path_part(addr_family),
-                           iface);
-    _LOGD("looking for existing config %s", path);
-    if (g_file_test(path, G_FILE_TEST_EXISTS))
-        return path;
-    g_free(path);
-
-    path =
-        g_strdup_printf(SYSCONFDIR "/dhcp/dhclient%s.conf", _addr_family_to_path_part(addr_family));
-    _LOGD("looking for existing config %s", path);
-    if (g_file_test(path, G_FILE_TEST_EXISTS))
-        return path;
-    g_free(path);
-
-    path = g_strdup_printf(SYSCONFDIR "/dhclient%s.conf", _addr_family_to_path_part(addr_family));
-    _LOGD("looking for existing config %s", path);
-    if (g_file_test(path, G_FILE_TEST_EXISTS))
-        return path;
-    g_free(path);
-
-    return NULL;
-}
-
-/* NM provides interface-specific options; thus the same dhclient config
- * file cannot be used since DHCP transactions can happen in parallel.
- * Since some distros don't have default per-interface dhclient config files,
- * read their single config file and merge that into a custom per-interface
- * config file along with the NM options.
- */
-static char *
-create_dhclient_config(NMDhcpDhclient     *self,
-                       int                 addr_family,
-                       const char         *iface,
-                       const char         *uuid,
-                       GBytes             *client_id,
-                       gboolean            send_client_id,
-                       const char         *anycast_address,
-                       const char         *hostname,
-                       guint32             timeout,
-                       gboolean            use_fqdn,
-                       NMDhcpHostnameFlags hostname_flags,
-                       const char         *mud_url,
-                       const char *const  *reject_servers,
-                       GBytes            **out_new_client_id)
-{
-    gs_free char *orig_path    = NULL;
-    gs_free char *orig_content = NULL;
-    char         *new_path     = NULL;
-    gs_free char *new_content  = NULL;
-    GError       *error        = NULL;
-
-    g_return_val_if_fail(iface != NULL, NULL);
-
-    new_path = g_strdup_printf(NMSTATEDIR "/dhclient%s-%s.conf",
-                               _addr_family_to_path_part(addr_family),
-                               iface);
-    _LOGD("creating composite dhclient config %s", new_path);
-
-    orig_path = find_existing_config(self, addr_family, iface, uuid);
-    if (orig_path)
-        _LOGD("merging existing dhclient config %s", orig_path);
-    else
-        _LOGD("no existing dhclient configuration to merge");
-
-    if (orig_path && g_file_test(orig_path, G_FILE_TEST_EXISTS)) {
-        if (!g_file_get_contents(orig_path, &orig_content, NULL, &error)) {
-            _LOGW("error reading dhclient configuration %s: %s", orig_path, error->message);
-            g_error_free(error);
-        }
-    }
-
-    new_content = nm_dhcp_dhclient_create_config(iface,
-                                                 addr_family,
-                                                 client_id,
-                                                 send_client_id,
-                                                 anycast_address,
-                                                 hostname,
-                                                 timeout,
-                                                 use_fqdn,
-                                                 hostname_flags,
-                                                 mud_url,
-                                                 reject_servers,
-                                                 orig_path,
-                                                 orig_content,
-                                                 out_new_client_id);
-    nm_assert(new_content);
-
-    if (!g_file_set_contents(new_path, new_content, -1, &error)) {
-        _LOGW("error creating dhclient configuration: %s", error->message);
-        g_error_free(error);
-        g_free(new_path);
-        return NULL;
-    }
-
-    return new_path;
-}
-
-static gboolean
-dhclient_start(NMDhcpClient *client,
-               gboolean      set_mode,
-               gboolean      release,
-               gboolean      set_duid,
-               pid_t        *out_pid,
-               GError      **error)
-{
-    NMDhcpDhclient              *self = NM_DHCP_DHCLIENT(client);
-    NMDhcpDhclientPrivate       *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self);
-    gs_unref_ptrarray GPtrArray *argv = NULL;
-    pid_t                        pid;
-    gs_free_error GError        *local = NULL;
-    const char                  *iface;
-    const char                  *uuid;
-    const char                  *system_bus_address;
-    const char                  *dhclient_path;
-    char                        *binary_name;
-    gs_free char                *cmd_str                  = NULL;
-    gs_free char                *pid_file                 = NULL;
-    gs_free char                *system_bus_address_env   = NULL;
-    gs_free char                *preferred_leasefile_path = NULL;
-    int                          addr_family;
-    const NMDhcpClientConfig    *client_config;
-    char                         pd_length_str[16];
-
-    g_return_val_if_fail(!priv->pid_file, FALSE);
-    client_config = nm_dhcp_client_get_config(client);
-    addr_family   = client_config->addr_family;
-
-    NM_SET_OUT(out_pid, 0);
-
-    dhclient_path = nm_dhcp_dhclient_get_path();
-    if (!dhclient_path) {
-        nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "dhclient binary not found");
-        return FALSE;
-    }
-
-    iface = client_config->iface;
-    uuid  = client_config->uuid;
-
-    pid_file = g_strdup_printf(NMRUNDIR "/dhclient%s-%s.pid",
-                               _addr_family_to_path_part(addr_family),
-                               iface);
-
-    /* Kill any existing dhclient from the pidfile */
-    binary_name = g_path_get_basename(dhclient_path);
-    nm_dhcp_client_stop_existing(pid_file, binary_name);
-    g_free(binary_name);
-
-    if (release) {
-        /* release doesn't use the pidfile after killing an old client */
-        nm_clear_g_free(&pid_file);
-    }
-
-    g_free(priv->lease_file);
-    priv->lease_file = get_dhclient_leasefile(addr_family, iface, uuid, &preferred_leasefile_path);
-    nm_assert(preferred_leasefile_path);
-    if (!priv->lease_file) {
-        /* No existing leasefile, dhclient will create one at the preferred path */
-        priv->lease_file = g_steal_pointer(&preferred_leasefile_path);
-    } else if (!nm_streq0(priv->lease_file, preferred_leasefile_path)) {
-        gs_unref_object GFile *src = g_file_new_for_path(priv->lease_file);
-        gs_unref_object GFile *dst = g_file_new_for_path(preferred_leasefile_path);
-
-        /* Try to copy the existing leasefile to the preferred location */
-        if (!g_file_copy(src, dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &local)) {
-            gs_free char *s_path = NULL;
-            gs_free char *d_path = NULL;
-
-            /* Failure; just use the existing leasefile */
-            _LOGW("failed to copy leasefile %s to %s: %s",
-                  (s_path = g_file_get_path(src)),
-                  (d_path = g_file_get_path(dst)),
-                  local->message);
-            g_clear_error(&local);
-        } else {
-            /* Success; use the preferred leasefile path */
-            g_free(priv->lease_file);
-            priv->lease_file = g_file_get_path(dst);
-        }
-    }
-
-    /* Save the DUID to the leasefile dhclient will actually use */
-    if (set_duid && addr_family == AF_INET6) {
-        if (!nm_dhcp_dhclient_save_duid(priv->lease_file,
-                                        nm_dhcp_client_get_effective_client_id(client),
-                                        client_config->v6.enforce_duid,
-                                        &local)) {
-            nm_utils_error_set(error,
-                               NM_UTILS_ERROR_UNKNOWN,
-                               "failed to save DUID to '%s': %s",
-                               priv->lease_file,
-                               local->message);
-            return FALSE;
-        }
-    }
-
-    argv = g_ptr_array_new();
-    g_ptr_array_add(argv, (gpointer) dhclient_path);
-
-    g_ptr_array_add(argv, (gpointer) "-d");
-
-    /* Be quiet. dhclient logs to syslog anyway. And we duplicate the syslog
-     * to stderr in case of NM running with --debug.
-     */
-    g_ptr_array_add(argv, (gpointer) "-q");
-
-    if (release)
-        g_ptr_array_add(argv, (gpointer) "-r");
-
-    if (!release && client_config->addr_family == AF_INET && client_config->v4.request_broadcast) {
-        g_ptr_array_add(argv, (gpointer) "-B");
-    }
-
-    if (addr_family == AF_INET6) {
-        guint       prefixes = client_config->v6.needed_prefixes;
-        const char *mode_opt;
-
-        g_ptr_array_add(argv, (gpointer) "-6");
-
-        if (!set_mode)
-            mode_opt = NULL;
-        else if (!client_config->v6.info_only)
-            mode_opt = "-N";
-        else if (prefixes == 0)
-            mode_opt = "-S";
-        else
-            mode_opt = NULL;
-
-        if (mode_opt)
-            g_ptr_array_add(argv, (gpointer) mode_opt);
-
-        if (prefixes > 0 && client_config->v6.pd_hint_length > 0) {
-            if (!IN6_IS_ADDR_UNSPECIFIED(&client_config->v6.pd_hint_addr)) {
-                _LOGW("dhclient only supports a length as prefix delegation hint, not a prefix");
-            }
-
-            nm_sprintf_buf(pd_length_str, "%u", client_config->v6.pd_hint_length);
-            g_ptr_array_add(argv, "--prefix-len-hint");
-            g_ptr_array_add(argv, pd_length_str);
-        }
-
-        while (prefixes--)
-            g_ptr_array_add(argv, (gpointer) "-P");
-    }
-    g_ptr_array_add(argv, (gpointer) "-sf"); /* Set script file */
-    g_ptr_array_add(argv, (gpointer) nm_dhcp_helper_path);
-
-    if (pid_file) {
-        g_ptr_array_add(argv, (gpointer) "-pf"); /* Set pid file */
-        g_ptr_array_add(argv, (gpointer) pid_file);
-    }
-
-    g_ptr_array_add(argv, (gpointer) "-lf"); /* Set lease file */
-    g_ptr_array_add(argv, (gpointer) priv->lease_file);
-
-    if (priv->conf_file) {
-        g_ptr_array_add(argv, (gpointer) "-cf"); /* Set interface config file */
-        g_ptr_array_add(argv, (gpointer) priv->conf_file);
-    }
-
-    if (client_config->v4.dscp_explicit) {
-        _LOGW("dhclient does not support specifying a custom DSCP value; the TOS field will be set "
-              "to LOWDELAY (0x10).");
-    }
-
-    if (client_config->v4.ipv6_only_preferred) {
-        _LOGW("the dhclient backend does not support the \"IPv6-Only Preferred\" option; ignoring "
-              "it");
-    }
-
-    /* Usually the system bus address is well-known; but if it's supposed
-     * to be something else, we need to push it to dhclient, since dhclient
-     * sanitizes the environment it gives the action scripts.
-     */
-    system_bus_address = getenv("DBUS_SYSTEM_BUS_ADDRESS");
-    if (system_bus_address) {
-        system_bus_address_env = g_strdup_printf("DBUS_SYSTEM_BUS_ADDRESS=%s", system_bus_address);
-        g_ptr_array_add(argv, (gpointer) "-e");
-        g_ptr_array_add(argv, (gpointer) system_bus_address_env);
-    }
-
-    g_ptr_array_add(argv, (gpointer) iface);
-    g_ptr_array_add(argv, NULL);
-
-    _LOGD("running: %s", (cmd_str = g_strjoinv(" ", (char **) argv->pdata)));
-
-    if (!g_spawn_async(NULL,
-                       (char **) argv->pdata,
-                       NULL,
-                       G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL
-                           | G_SPAWN_STDERR_TO_DEV_NULL,
-                       nm_utils_setpgid,
-                       NULL,
-                       &pid,
-                       &local)) {
-        nm_utils_error_set(error,
-                           NM_UTILS_ERROR_UNKNOWN,
-                           "dhclient failed to start: %s",
-                           local->message);
-        return FALSE;
-    }
-
-    _LOGI("dhclient started with pid %lld", (long long int) pid);
-
-    if (!release)
-        nm_dhcp_client_watch_child(client, pid);
-
-    priv->pid_file = g_steal_pointer(&pid_file);
-
-    NM_SET_OUT(out_pid, pid);
-    return TRUE;
-}
-
-static gboolean
-ip4_start(NMDhcpClient *client, GError **error)
-{
-    NMDhcpDhclient           *self          = NM_DHCP_DHCLIENT(client);
-    NMDhcpDhclientPrivate    *priv          = NM_DHCP_DHCLIENT_GET_PRIVATE(self);
-    gs_unref_bytes GBytes    *new_client_id = NULL;
-    const NMDhcpClientConfig *client_config;
-
-    client_config = nm_dhcp_client_get_config(client);
-
-    nm_assert(client_config->addr_family == AF_INET);
-
-    priv->conf_file = create_dhclient_config(self,
-                                             AF_INET,
-                                             client_config->iface,
-                                             client_config->uuid,
-                                             client_config->client_id,
-                                             client_config->v4.send_client_id,
-                                             client_config->anycast_address,
-                                             client_config->hostname,
-                                             client_config->timeout,
-                                             client_config->use_fqdn,
-                                             client_config->hostname_flags,
-                                             client_config->mud_url,
-                                             client_config->reject_servers,
-                                             &new_client_id);
-    if (!priv->conf_file) {
-        nm_utils_error_set_literal(error,
-                                   NM_UTILS_ERROR_UNKNOWN,
-                                   "error creating dhclient configuration file");
-        return FALSE;
-    }
-
-    /* Note that the effective-client-id for IPv4 here might be unknown/NULL. */
-    nm_assert(!new_client_id || !client_config->client_id);
-    nm_dhcp_client_set_effective_client_id(client, client_config->client_id ?: new_client_id);
-
-    return dhclient_start(client, FALSE, FALSE, FALSE, NULL, error);
-}
-
-static gboolean
-ip6_start(NMDhcpClient *client, const struct in6_addr *ll_addr, GError **error)
-{
-    NMDhcpDhclient           *self = NM_DHCP_DHCLIENT(client);
-    NMDhcpDhclientPrivate    *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self);
-    const NMDhcpClientConfig *config;
-    gs_unref_bytes GBytes    *effective_client_id = NULL;
-
-    config = nm_dhcp_client_get_config(client);
-
-    nm_assert(config->addr_family == AF_INET6);
-
-    if (config->v6.iaid_explicit)
-        _LOGW("dhclient does not support specifying an IAID for DHCPv6, it will be ignored");
-
-    priv->conf_file = create_dhclient_config(self,
-                                             AF_INET6,
-                                             config->iface,
-                                             config->uuid,
-                                             NULL,
-                                             TRUE,
-                                             config->anycast_address,
-                                             config->hostname,
-                                             config->timeout,
-                                             TRUE,
-                                             config->hostname_flags,
-                                             config->mud_url,
-                                             NULL,
-                                             NULL);
-    if (!priv->conf_file) {
-        nm_utils_error_set_literal(error,
-                                   NM_UTILS_ERROR_UNKNOWN,
-                                   "error creating dhclient configuration file");
-        return FALSE;
-    }
-
-    nm_assert(config->client_id);
-    if (!config->v6.enforce_duid)
-        effective_client_id = read_duid_from_lease(self);
-    nm_dhcp_client_set_effective_client_id(client, effective_client_id ?: config->client_id);
-
-    return dhclient_start(client, TRUE, FALSE, TRUE, NULL, error);
-}
-
-static void
-stop(NMDhcpClient *client, gboolean release)
-{
-    NMDhcpDhclient        *self = NM_DHCP_DHCLIENT(client);
-    NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self);
-    int                    errsv;
-
-    NM_DHCP_CLIENT_CLASS(nm_dhcp_dhclient_parent_class)->stop(client, release);
-
-    if (priv->conf_file)
-        if (remove(priv->conf_file) == -1) {
-            errsv = errno;
-            _LOGD("could not remove dhcp config file \"%s\": %d (%s)",
-                  priv->conf_file,
-                  errsv,
-                  nm_strerror_native(errsv));
-        }
-    if (priv->pid_file) {
-        if (remove(priv->pid_file) == -1) {
-            errsv = errno;
-            _LOGD("could not remove dhcp pid file \"%s\": %s (%d)",
-                  priv->pid_file,
-                  nm_strerror_native(errsv),
-                  errsv);
-        }
-        nm_clear_g_free(&priv->pid_file);
-    }
-
-    if (release) {
-        pid_t rpid = -1;
-
-        if (dhclient_start(client, FALSE, TRUE, FALSE, &rpid, NULL)) {
-            /* Wait a few seconds for the release to happen */
-            nm_dhcp_client_stop_pid(rpid, nm_dhcp_client_get_iface(client));
-        }
-    }
-}
-
-static GBytes *
-read_duid_from_lease(NMDhcpDhclient *self)
-{
-    NMDhcpClient             *client = NM_DHCP_CLIENT(self);
-    NMDhcpDhclientPrivate    *priv   = NM_DHCP_DHCLIENT_GET_PRIVATE(self);
-    const NMDhcpClientConfig *client_config;
-    GBytes                   *duid      = NULL;
-    gs_free char             *leasefile = NULL;
-    GError                   *error     = NULL;
-
-    client_config = nm_dhcp_client_get_config(client);
-
-    /* Look in interface-specific leasefile first for backwards compat */
-    leasefile = get_dhclient_leasefile(AF_INET6,
-                                       nm_dhcp_client_get_iface(client),
-                                       client_config->uuid,
-                                       NULL);
-    if (leasefile) {
-        _LOGD("looking for DUID in '%s'", leasefile);
-        duid = nm_dhcp_dhclient_read_duid(leasefile, &error);
-        if (error) {
-            _LOGW("failed to read leasefile '%s': %s", leasefile, error->message);
-            g_clear_error(&error);
-        }
-        if (duid)
-            return duid;
-    }
-
-    /* Otherwise, read the default machine-wide DUID */
-    _LOGD("looking for default DUID in '%s'", priv->def_leasefile);
-    duid = nm_dhcp_dhclient_read_duid(priv->def_leasefile, &error);
-    if (error) {
-        _LOGW("failed to read leasefile '%s': %s", priv->def_leasefile, error->message);
-        g_clear_error(&error);
-    }
-
-    return duid;
-}
-
-/*****************************************************************************/
-
-static void
-nm_dhcp_dhclient_init(NMDhcpDhclient *self)
-{
-    static const char *const FILES[] = {
-        SYSCONFDIR "/dhclient6.leases", /* default */
-        LOCALSTATEDIR "/lib/dhcp/dhclient6.leases",
-        LOCALSTATEDIR "/lib/dhclient/dhclient6.leases",
-    };
-    NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self);
-    int                    i;
-
-    priv->def_leasefile = FILES[0];
-    for (i = 0; i < G_N_ELEMENTS(FILES); i++) {
-        if (g_file_test(FILES[i], G_FILE_TEST_EXISTS)) {
-            priv->def_leasefile = FILES[i];
-            break;
-        }
-    }
-
-    priv->dhcp_listener = g_object_ref(nm_dhcp_listener_get());
-    g_signal_connect(priv->dhcp_listener,
-                     NM_DHCP_LISTENER_EVENT,
-                     G_CALLBACK(nm_dhcp_client_handle_event),
-                     self);
-}
-
-static void
-dispose(GObject *object)
-{
-    NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(object);
-
-    if (priv->dhcp_listener) {
-        g_signal_handlers_disconnect_by_func(priv->dhcp_listener,
-                                             G_CALLBACK(nm_dhcp_client_handle_event),
-                                             NM_DHCP_DHCLIENT(object));
-        g_clear_object(&priv->dhcp_listener);
-    }
-
-    nm_clear_g_free(&priv->pid_file);
-    nm_clear_g_free(&priv->conf_file);
-    nm_clear_g_free(&priv->lease_file);
-
-    G_OBJECT_CLASS(nm_dhcp_dhclient_parent_class)->dispose(object);
-}
-
-static void
-nm_dhcp_dhclient_class_init(NMDhcpDhclientClass *dhclient_class)
-{
-    NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS(dhclient_class);
-    GObjectClass      *object_class = G_OBJECT_CLASS(dhclient_class);
-
-    object_class->dispose = dispose;
-
-    client_class->ip4_start = ip4_start;
-    client_class->ip6_start = ip6_start;
-    client_class->stop      = stop;
-}
-
-const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient = {
-    .name       = "dhclient",
-    .get_type_4 = nm_dhcp_dhclient_get_type,
-    .get_type_6 = nm_dhcp_dhclient_get_type,
-    .get_path   = nm_dhcp_dhclient_get_path,
-};
-
-#endif /* WITH_DHCLIENT */
diff --git a/src/core/dhcp/nm-dhcp-helper.c b/src/core/dhcp/nm-dhcp-helper.c
index 9e4cedf2..144e3cb0 100644
--- a/src/core/dhcp/nm-dhcp-helper.c
+++ b/src/core/dhcp/nm-dhcp-helper.c
@@ -205,8 +205,7 @@ do_notify:
 
     if (!NM_IN_STRSET(s_err, "org.freedesktop.DBus.Error.UnknownMethod")) {
         /* Some unexpected error. We treat that as a failure. In particular,
-         * the daemon will fail the request if ACD fails. This causes nm-dhcp-helper
-         * to fail, which in turn causes dhclient to send a DECLINE. */
+         * the daemon will fail the request if ACD fails. */
         _LOGW("failure to call notify: %s (try signal via Event)", error->message);
         success = FALSE;
         goto out;
diff --git a/src/core/dhcp/nm-dhcp-listener.c b/src/core/dhcp/nm-dhcp-listener.c
index 095131cc..cead1308 100644
--- a/src/core/dhcp/nm-dhcp-listener.c
+++ b/src/core/dhcp/nm-dhcp-listener.c
@@ -36,9 +36,6 @@ const NMDhcpClientFactory *const _nm_dhcp_manager_factories[6] = {
 #endif
     &_nm_dhcp_client_factory_systemd,
     &_nm_dhcp_client_factory_nettools,
-#if WITH_DHCLIENT
-    &_nm_dhcp_client_factory_dhclient,
-#endif
 };
 
 /*****************************************************************************/
diff --git a/src/core/dhcp/nm-dhcp-manager.c b/src/core/dhcp/nm-dhcp-manager.c
index 68bb327b..9faee84e 100644
--- a/src/core/dhcp/nm-dhcp-manager.c
+++ b/src/core/dhcp/nm-dhcp-manager.c
@@ -91,11 +91,6 @@ static const NMDhcpClientFactory *
 _client_factory_available(const NMDhcpClientFactory *client_factory)
 {
     if (client_factory) {
-        if (nm_streq(client_factory->name, "dhclient")) {
-            _LOGW(AF_UNSPEC,
-                  "attempting to used a deprecated DHCP client '%s' ",
-                  client_factory->name);
-        }
         if (!client_factory->get_path || client_factory->get_path())
             return client_factory;
     }
@@ -205,30 +200,10 @@ nm_dhcp_manager_start_client(NMDhcpManager *self, NMDhcpClientConfig *config, GE
 
     client = g_object_new(gtype, NM_DHCP_CLIENT_CONFIG, config, NULL);
 
-    /* unfortunately, our implementations work differently per address-family regarding client-id/DUID.
-     *
-     * - for IPv4, the calling code may determine a client-id (from NM's connection profile).
-     *   If present, it is taken. If not present, the DHCP plugin uses a plugin specific default.
-     *     - for "internal" plugin, the default is just "mac".
-     *     - for "dhclient", we try to get the configuration from dhclient's /etc/dhcp or fallback
-     *       to whatever dhclient uses by default.
-     *   We do it this way, because for dhclient the user may configure a default
-     *   outside of NM, and we want to honor that. Worse, dhclient could be a wapper
-     *   script where the wrapper script overwrites the client-id. We need to distinguish
-     *   between: force a particular client-id and leave it unspecified to whatever dhclient
-     *   wants.
-     *
-     * - for IPv6, the calling code always determines a client-id. It also specifies @enforce_duid,
-     *   to determine whether the given client-id must be used.
-     *     - for "internal" plugin @enforce_duid doesn't matter and the given client-id is
-     *       always used.
-     *     - for "dhclient", @enforce_duid FALSE means to first try to load the DUID from the
-     *       lease file, and only otherwise fallback to the given client-id.
-     *     - other plugins don't support DHCPv6.
-     *   It's done this way, so that existing dhclient setups don't change behavior on upgrade.
+    /* - for IPv4, the calling code may determine a client-id (from NM's connection profile).
+     *   If present, it is taken. If not present, the default is just "mac".
      *
-     * This difference is cumbersome and only exists because of "dhclient" which supports hacking the
-     * default outside of NetworkManager API.
+     * - for IPv6, the calling code always determines a client-id.
      */
 
     if (!nm_dhcp_client_start(client, error))
@@ -289,8 +264,10 @@ nm_dhcp_manager_init(NMDhcpManager *self)
                                  NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
     client = client_free;
     if (client) {
-        client_factory = _client_factory_available(_client_factory_find_by_name(client));
+        client_factory = _client_factory_find_by_name(client);
         if (!client_factory)
+            _LOGW(AF_UNSPEC, "init: unknown DHCP client '%s', ignoring", client);
+        else if (!(client_factory = _client_factory_available(client_factory)))
             _LOGW(AF_UNSPEC, "init: DHCP client '%s' not available", client);
     }
     if (!client_factory) {
diff --git a/src/core/dhcp/nm-dhcp-nettools.c b/src/core/dhcp/nm-dhcp-nettools.c
index 27bb136b..ad9cb893 100644
--- a/src/core/dhcp/nm-dhcp-nettools.c
+++ b/src/core/dhcp/nm-dhcp-nettools.c
@@ -269,7 +269,7 @@ lease_parse_address(NMDhcpNettools    *self /* for logging context only */,
         char str2[NM_INET_ADDRSTRLEN];
 
         /* Some DHCP servers may not set the subnet-mask (issue#1037).
-         * Do the same as the dhclient plugin and use a default. */
+         * Use a default. */
         a_plen    = nm_ip4_addr_get_default_prefix(a_address.s_addr);
         a_netmask = nm_ip4_addr_netmask_from_prefix(a_plen);
         _LOGT("missing subnet mask (option 1). Guess %s based on IP address %s",
@@ -418,7 +418,6 @@ lease_parse_routes(NDhcp4ClientLease *lease,
     in_addr_t     gateway;
     uint8_t       plen;
     guint32       m;
-    gboolean      has_router_from_classless   = FALSE;
     gboolean      has_classless               = FALSE;
     guint32       default_route_metric_offset = 0;
     const guint8 *l_data;
@@ -434,7 +433,7 @@ lease_parse_routes(NDhcp4ClientLease *lease,
      * We will however also parse one of the options into the "l3cd" for configuring routing.
      * Thereby we prefer 121 over 249 over 33.
      *
-     * Preferring 121 over 33 is defined by RFC 3443.
+     * Preferring 121 over 33 is defined by RFC 3442.
      * Preferring 121 over 249 over 33 is made up as it makes sense (the MS docs are not very clear).
      */
     for (i = 0; i < 2; i++) {
@@ -460,8 +459,7 @@ lease_parse_routes(NDhcp4ClientLease *lease,
             if (plen == 0) {
                 /* if there are multiple default routes, we add them with differing
                  * metrics. */
-                m                         = default_route_metric_offset++;
-                has_router_from_classless = TRUE;
+                m = default_route_metric_offset++;
             } else
                 m = 0;
 
@@ -495,7 +493,7 @@ lease_parse_routes(NDhcp4ClientLease *lease,
             nm_str_buf_append_printf(sbuf, "%s/%d %s", dest_str, (int) plen, gateway_str);
 
             if (has_classless) {
-                /* RFC 3443: if the DHCP server returns both a Classless Static Routes
+                /* RFC 3442: if the DHCP server returns both a Classless Static Routes
                  * option and a Static Routes option, the DHCP client MUST ignore the
                  * Static Routes option. */
                 continue;
@@ -539,13 +537,10 @@ lease_parse_routes(NDhcp4ClientLease *lease,
                 continue;
             }
 
-            if (has_router_from_classless) {
-                /* If the DHCP server returns both a Classless Static Routes option and a
-                 * Router option, the DHCP client MUST ignore the Router option [RFC 3442].
-                 *
-                 * Be more lenient and ignore the Router option only if Classless Static
-                 * Routes contain a default gateway (as other DHCP backends do).
-                 */
+            if (has_classless) {
+                /* RFC 3442: if the DHCP server returns both a Classless Static Routes
+                 * option and a Router option, the DHCP client MUST ignore the Router
+                 * option. */
                 continue;
             }
 
@@ -1475,7 +1470,9 @@ ip4_start(NMDhcpClient *client, GError **error)
             } else {
                 fqdn_len = strlen(client_config->hostname);
                 if (fqdn_len > sizeof(buffer) - 3) {
-                    nm_utils_error_set(error, r, "failed to set DHCP FQDN: name too long");
+                    nm_utils_error_set_literal(error,
+                                               NM_UTILS_ERROR_UNKNOWN,
+                                               "failed to set DHCP FQDN: name too long");
                     return FALSE;
                 }
                 memcpy(buffer + 3, client_config->hostname, fqdn_len);
@@ -1530,7 +1527,8 @@ ip4_start(NMDhcpClient *client, GError **error)
 
     _LOGT("dhcp-client4: start " NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(priv->client));
 
-    nm_dhcp_client_set_effective_client_id(client, effective_client_id);
+    if (effective_client_id)
+        nm_dhcp_client_set_effective_client_id(client, effective_client_id);
 
     return TRUE;
 }
diff --git a/src/core/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c
index b570f7e5..547e730b 100644
--- a/src/core/dhcp/nm-dhcp-systemd.c
+++ b/src/core/dhcp/nm-dhcp-systemd.c
@@ -92,6 +92,7 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro
     const char                             *s;
     nm_auto_free_gstring GString           *str = NULL;
     int                                     num, i;
+    gboolean                                has_any_prefix_delegated = FALSE;
 
     nm_assert(lease);
 
@@ -107,6 +108,28 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro
                               NM_DHCP_OPTION_DHCP6_NM_IAID,
                               nm_dhcp_iaid_to_hexstr(config->v6.iaid, iaid_buf));
 
+    {
+        struct in6_addr prefix;
+        uint8_t         prefix_len;
+
+        nm_gstring_prepare(&str);
+        sd_dhcp6_lease_pd_iterator_reset(lease);
+        while (!sd_dhcp6_lease_get_pd_prefix(lease, &prefix, &prefix_len)) {
+            nm_gstring_add_space_delimiter(str);
+            nm_inet6_ntop(&prefix, addr_str);
+            g_string_append_printf(str, "%s/%u", addr_str, prefix_len);
+            sd_dhcp6_lease_pd_iterator_next(lease);
+        }
+        if (str->len > 0) {
+            nm_dhcp_option_add_option(options,
+                                      TRUE,
+                                      AF_INET6,
+                                      NM_DHCP_OPTION_DHCP6_IA_PD,
+                                      str->str);
+            has_any_prefix_delegated = TRUE;
+        }
+    }
+
     if (!config->v6.info_only) {
         gboolean has_any_addresses = FALSE;
         uint64_t lft_pref;
@@ -142,11 +165,11 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro
                                       str->str);
         }
 
-        if (!has_any_addresses) {
+        if (!has_any_addresses && !has_any_prefix_delegated) {
             g_set_error_literal(error,
                                 NM_MANAGER_ERROR,
                                 NM_MANAGER_ERROR_FAILED,
-                                "no address received in managed mode");
+                                "no address or prefix delegation received in managed mode");
             return NULL;
         }
     }
@@ -166,27 +189,6 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro
                                   str->str);
     }
 
-    {
-        struct in6_addr prefix;
-        uint8_t         prefix_len;
-
-        nm_gstring_prepare(&str);
-        sd_dhcp6_lease_pd_iterator_reset(lease);
-        while (!sd_dhcp6_lease_get_pd_prefix(lease, &prefix, &prefix_len)) {
-            nm_gstring_add_space_delimiter(str);
-            nm_inet6_ntop(&prefix, addr_str);
-            g_string_append_printf(str, "%s/%u", addr_str, prefix_len);
-            sd_dhcp6_lease_pd_iterator_next(lease);
-        }
-        if (str->len > 0) {
-            nm_dhcp_option_add_option(options,
-                                      TRUE,
-                                      AF_INET6,
-                                      NM_DHCP_OPTION_DHCP6_IA_PD,
-                                      str->str);
-        }
-    }
-
     num = sd_dhcp6_lease_get_domains(lease, &domains);
     if (num > 0) {
         nm_gstring_prepare(&str);
diff --git a/src/core/dhcp/nm-dhcp-utils.c b/src/core/dhcp/nm-dhcp-utils.c
index 8a9bd58c..8c3001f2 100644
--- a/src/core/dhcp/nm-dhcp-utils.c
+++ b/src/core/dhcp/nm-dhcp-utils.c
@@ -827,64 +827,6 @@ nm_dhcp_utils_get_leasefile_path(int         addr_family,
     return FALSE;
 }
 
-gboolean
-nm_dhcp_utils_merge_new_dhcp6_lease(const NML3ConfigData  *l3cd_old,
-                                    const NML3ConfigData  *l3cd_new,
-                                    const NML3ConfigData **out_l3cd_merged)
-{
-    nm_auto_unref_l3cd_init NML3ConfigData *l3cd_merged = NULL;
-    const NMPlatformIP6Address             *addr;
-    NMDhcpLease                            *lease_old;
-    NMDhcpLease                            *lease_new;
-    NMDedupMultiIter                        iter;
-    const char                             *start;
-    const char                             *iaid;
-
-    nm_assert(out_l3cd_merged && !*out_l3cd_merged);
-
-    if (!l3cd_old)
-        return FALSE;
-    if (!l3cd_new)
-        return FALSE;
-
-    lease_new = nm_l3_config_data_get_dhcp_lease(l3cd_new, AF_INET6);
-    if (!lease_new)
-        return FALSE;
-
-    lease_old = nm_l3_config_data_get_dhcp_lease(l3cd_old, AF_INET6);
-    if (!lease_old)
-        return FALSE;
-
-    start = nm_dhcp_lease_lookup_option(lease_new, "life_starts");
-    if (!start)
-        return FALSE;
-    iaid = nm_dhcp_lease_lookup_option(lease_new, "iaid");
-    if (!iaid)
-        return FALSE;
-
-    if (!nm_streq0(start, nm_dhcp_lease_lookup_option(lease_old, "life_starts")))
-        return FALSE;
-    if (!nm_streq0(iaid, nm_dhcp_lease_lookup_option(lease_old, "iaid")))
-        return FALSE;
-
-    /* If the server sends multiple IPv6 addresses, we receive a state
-     * changed event for each of them. Use the event ID to merge IPv6
-     * addresses from the same transaction into a single configuration.
-     **/
-
-    l3cd_merged = nm_l3_config_data_new_clone(l3cd_old, 0);
-
-    nm_l3_config_data_iter_ip6_address_for_each (&iter, l3cd_new, &addr)
-        nm_l3_config_data_add_address_6(l3cd_merged, addr);
-
-    /* FIXME(l3cfg): Note that we keep the original NMDhcpLease. All we take from the new lease are the
-     * addresses. Maybe this is not right and we should merge the leases too?? */
-    nm_l3_config_data_set_dhcp_lease(l3cd_merged, AF_INET6, lease_old);
-
-    *out_l3cd_merged = nm_l3_config_data_ref_and_seal(g_steal_pointer(&l3cd_merged));
-    return TRUE;
-}
-
 /*****************************************************************************/
 
 void
diff --git a/src/core/dhcp/nm-dhcp-utils.h b/src/core/dhcp/nm-dhcp-utils.h
index 99898524..a2dde94e 100644
--- a/src/core/dhcp/nm-dhcp-utils.h
+++ b/src/core/dhcp/nm-dhcp-utils.h
@@ -33,12 +33,6 @@ gboolean nm_dhcp_utils_get_leasefile_path(int         addr_family,
                                           const char *uuid,
                                           char      **out_leasefile_path);
 
-char *nm_dhcp_utils_get_dhcp6_event_id(GHashTable *lease);
-
-gboolean nm_dhcp_utils_merge_new_dhcp6_lease(const NML3ConfigData  *l3cd_old,
-                                             const NML3ConfigData  *l3cd_new,
-                                             const NML3ConfigData **out_l3cd_merged);
-
 /*****************************************************************************/
 
 static inline gboolean
diff --git a/src/core/dhcp/tests/meson.build b/src/core/dhcp/tests/meson.build
index e43c8cab..8b2a7b76 100644
--- a/src/core/dhcp/tests/meson.build
+++ b/src/core/dhcp/tests/meson.build
@@ -1,7 +1,6 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
 test_units = [
-  'test-dhcp-dhclient',
   'test-dhcp-utils',
 ]
 
diff --git a/src/core/dhcp/tests/test-dhclient-commented-duid.leases b/src/core/dhcp/tests/test-dhclient-commented-duid.leases
deleted file mode 100644
index 3e46ae7d..00000000
--- a/src/core/dhcp/tests/test-dhclient-commented-duid.leases
+++ /dev/null
@@ -1,2 +0,0 @@
-#default-duid "\000\001\000\001\030y\246\023`g \354Lp";
-
diff --git a/src/core/dhcp/tests/test-dhclient-duid.leases b/src/core/dhcp/tests/test-dhclient-duid.leases
deleted file mode 100644
index 229331d4..00000000
--- a/src/core/dhcp/tests/test-dhclient-duid.leases
+++ /dev/null
@@ -1,2 +0,0 @@
-default-duid "\000\001\000\001\030y\246\023`g \354Lp";
-
diff --git a/src/core/dhcp/tests/test-dhcp-dhclient.c b/src/core/dhcp/tests/test-dhcp-dhclient.c
deleted file mode 100644
index 6a7b7185..00000000
--- a/src/core/dhcp/tests/test-dhcp-dhclient.c
+++ /dev/null
@@ -1,1478 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2010 Red Hat, Inc.
- */
-
-#include "src/core/nm-default-daemon.h"
-
-#include <unistd.h>
-#include <arpa/inet.h>
-#include <linux/rtnetlink.h>
-
-#include "libnm-glib-aux/nm-dedup-multi.h"
-
-#include "NetworkManagerUtils.h"
-#include "dhcp/nm-dhcp-dhclient-utils.h"
-#include "dhcp/nm-dhcp-utils.h"
-#include "nm-utils.h"
-#include "libnm-platform/nm-platform.h"
-
-#include "nm-test-utils-core.h"
-
-#define TEST_DIR    NM_BUILD_SRCDIR "/src/core/dhcp/tests"
-#define TEST_MUDURL "https://example.com/mud.json"
-
-static void
-test_config(const char         *orig,
-            const char         *expected,
-            int                 addr_family,
-            const char         *hostname,
-            guint32             timeout,
-            gboolean            use_fqdn,
-            NMDhcpHostnameFlags hostname_flags,
-            const char         *dhcp_client_id,
-            GBytes             *expected_new_client_id,
-            const char         *iface,
-            const char         *anycast_addr,
-            const char         *mud_url)
-{
-    gs_free char *new                     = NULL;
-    gs_unref_bytes GBytes *client_id      = NULL;
-    gs_unref_bytes GBytes *new_client_id  = NULL;
-    gboolean               send_client_id = TRUE;
-
-    if (nm_streq0(dhcp_client_id, "none")) {
-        send_client_id = FALSE;
-    } else if (dhcp_client_id) {
-        client_id = nm_dhcp_utils_client_id_string_to_bytes(dhcp_client_id);
-        g_assert(client_id);
-    }
-
-    new = nm_dhcp_dhclient_create_config(iface,
-                                         addr_family,
-                                         client_id,
-                                         send_client_id,
-                                         anycast_addr,
-                                         hostname,
-                                         timeout,
-                                         use_fqdn,
-                                         hostname_flags,
-                                         mud_url,
-                                         NULL,
-                                         "/path/to/dhclient.conf",
-                                         orig,
-                                         &new_client_id);
-    g_assert(new != NULL);
-
-    if (!nm_streq(new, expected)) {
-        g_message("\n* OLD ---------------------------------\n"
-                  "%s"
-                  "\n- NEW -----------------------------------\n"
-                  "%s"
-                  "\n+ EXPECTED ++++++++++++++++++++++++++++++\n"
-                  "%s"
-                  "\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
-                  orig,
-                  new,
-                  expected);
-    }
-    g_assert_cmpstr(new, ==, expected);
-
-    if (expected_new_client_id) {
-        g_assert(new_client_id);
-        g_assert(g_bytes_equal(new_client_id, expected_new_client_id));
-    } else
-        g_assert(new_client_id == NULL);
-}
-
-/*****************************************************************************/
-
-static const char *orig_missing_expected =
-    "# Created by NetworkManager\n"
-    "\n\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_orig_missing(void)
-{
-    test_config(NULL,
-                orig_missing_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *orig_missing_add_mud_url_expected =
-    "# Created by NetworkManager\n"
-    "\n"
-    "option mudurl code 161 = text;\n"
-    "send mudurl \"https://example.com/mud.json\";\n\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_orig_missing_add_mud_url(void)
-{
-    test_config(NULL,
-                orig_missing_add_mud_url_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                TEST_MUDURL);
-}
-
-/*****************************************************************************/
-
-static const char *override_client_id_orig = "send dhcp-client-identifier 00:30:04:20:7A:08;\n";
-
-static const char *override_client_id_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n"
-    "send dhcp-client-identifier 11:22:33:44:55:66; # added by NetworkManager\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_override_client_id(void)
-{
-    test_config(override_client_id_orig,
-                override_client_id_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                "11:22:33:44:55:66",
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *quote_client_id_expected =
-    "# Created by NetworkManager\n"
-    "\n"
-    "send dhcp-client-identifier \"\\x00abcd\"; # added by NetworkManager\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_quote_client_id(void)
-{
-    test_config(NULL,
-                quote_client_id_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                "abcd",
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *quote_client_id_expected_2 =
-    "# Created by NetworkManager\n"
-    "\n"
-    "send dhcp-client-identifier 00:61:5c:62:63; # added by NetworkManager\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_quote_client_id_2(void)
-{
-    test_config(NULL,
-                quote_client_id_expected_2,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                "a\\bc",
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *hex_zero_client_id_expected =
-    "# Created by NetworkManager\n"
-    "\n"
-    "send dhcp-client-identifier 00:11:22:33; # added by NetworkManager\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_hex_zero_client_id(void)
-{
-    test_config(NULL,
-                hex_zero_client_id_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                "00:11:22:33",
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *ascii_client_id_expected =
-    "# Created by NetworkManager\n"
-    "\n"
-    "send dhcp-client-identifier \"\\x00qb:cd:ef:12:34:56\"; # added by NetworkManager\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_ascii_client_id(void)
-{
-    test_config(NULL,
-                ascii_client_id_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                "qb:cd:ef:12:34:56",
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *hex_single_client_id_expected =
-    "# Created by NetworkManager\n"
-    "\n"
-    "send dhcp-client-identifier ab:cd:0e:12:34:56; # added by NetworkManager\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_hex_single_client_id(void)
-{
-    test_config(NULL,
-                hex_single_client_id_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                "ab:cd:e:12:34:56",
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *existing_hex_client_id_orig = "send dhcp-client-identifier 10:30:04:20:7A:08;\n";
-
-static const char *existing_hex_client_id_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n"
-    "send dhcp-client-identifier 10:30:04:20:7A:08;\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_existing_hex_client_id(void)
-{
-    gs_unref_bytes GBytes *new_client_id = NULL;
-    const guint8           bytes[]       = {0x10, 0x30, 0x04, 0x20, 0x7A, 0x08};
-
-    new_client_id = g_bytes_new(bytes, sizeof(bytes));
-    test_config(existing_hex_client_id_orig,
-                existing_hex_client_id_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                new_client_id,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *existing_escaped_client_id_orig =
-    "send dhcp-client-identifier \"\\044test\\xfe\";\n";
-
-static const char *existing_escaped_client_id_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n"
-    "send dhcp-client-identifier \"\\044test\\xfe\";\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_existing_escaped_client_id(void)
-{
-    gs_unref_bytes GBytes *new_client_id = NULL;
-
-    new_client_id = g_bytes_new("$test\xfe", 6);
-    test_config(existing_escaped_client_id_orig,
-                existing_escaped_client_id_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                new_client_id,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-#define EACID "qb:cd:ef:12:34:56"
-
-static const char *existing_ascii_client_id_orig =
-    "send dhcp-client-identifier \"\\x00" EACID "\";\n";
-
-static const char *existing_ascii_client_id_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n"
-    "send dhcp-client-identifier \"\\x00" EACID "\";\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_existing_ascii_client_id(void)
-{
-    gs_unref_bytes GBytes *new_client_id             = NULL;
-    char                   buf[NM_STRLEN(EACID) + 1] = {0};
-
-    memcpy(buf + 1, EACID, NM_STRLEN(EACID));
-    new_client_id = g_bytes_new(buf, sizeof(buf));
-    test_config(existing_ascii_client_id_orig,
-                existing_ascii_client_id_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                new_client_id,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *none_client_id_orig = "send dhcp-client-identifier 10:30:04:20:7A:08;\n";
-
-static const char *none_client_id_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n"
-    "send dhcp-client-identifier \"\"; # added by NetworkManager\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_none_client_id(void)
-{
-    const char            *connection_client_id = "none";
-    gs_unref_bytes GBytes *expected_client_id   = NULL;
-
-    test_config(none_client_id_orig,
-                none_client_id_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                connection_client_id,
-                expected_client_id,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *missing_client_id_orig = "";
-
-static const char *missing_client_id_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_missing_client_id(void)
-{
-    const char            *connection_client_id = NULL;
-    gs_unref_bytes GBytes *expected_client_id   = NULL;
-
-    test_config(missing_client_id_orig,
-                missing_client_id_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                connection_client_id,
-                expected_client_id,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *fqdn_expected =
-    "# Created by NetworkManager\n"
-    "\n"
-    "send fqdn.fqdn \"foo.bar.com\"; # added by NetworkManager\n"
-    "send fqdn.encoded on;\n"
-    "send fqdn.server-update off;\n"
-    "send fqdn.no-client-update on;\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n\n";
-
-static void
-test_fqdn(void)
-{
-    test_config(NULL,
-                fqdn_expected,
-                AF_INET,
-                "foo.bar.com",
-                0,
-                TRUE,
-                NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED | NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-static const char *fqdn_options_override_orig =
-    "\n"
-    "send fqdn.fqdn \"foobar.com\"\n" /* NM must ignore this ... */
-    "send fqdn.encoded off;\n"        /* ... and honor these */
-    "send fqdn.server-update off;\n";
-
-static const char *fqdn_options_override_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n"
-    "send fqdn.fqdn \"example2.com\"; # added by NetworkManager\n"
-    "send fqdn.encoded off;\n"
-    "send fqdn.server-update on;\n"
-    "send fqdn.no-client-update off;\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n"
-    "# FQDN options from /path/to/dhclient.conf\n"
-    "send fqdn.encoded off;\n"
-    "send fqdn.server-update off;\n\n";
-
-static void
-test_fqdn_options_override(void)
-{
-    test_config(fqdn_options_override_orig,
-                fqdn_options_override_expected,
-                AF_INET,
-                "example2.com",
-                0,
-                NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE,
-                TRUE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *override_hostname_orig = "send host-name \"foobar\";\n";
-
-static const char *override_hostname_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n"
-    "send host-name \"blahblah\"; # added by NetworkManager\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_override_hostname(void)
-{
-    test_config(override_hostname_orig,
-                override_hostname_expected,
-                AF_INET,
-                "blahblah",
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *override_hostname6_orig = "send fqdn.fqdn \"foobar\";\n";
-
-static const char *override_hostname6_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n"
-    "send fqdn.fqdn \"blahblah.local\"; # added by NetworkManager\n"
-    "send fqdn.server-update on;\n"
-    "\n"
-    "also request dhcp6.name-servers;\n"
-    "also request dhcp6.domain-search;\n"
-    "also request dhcp6.client-id;\n"
-    "\n";
-
-static void
-test_override_hostname6(void)
-{
-    test_config(override_hostname6_orig,
-                override_hostname6_expected,
-                AF_INET6,
-                "blahblah.local",
-                0,
-                TRUE,
-                NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *nonfqdn_hostname6_expected =
-    "# Created by NetworkManager\n"
-    "\n"
-    "send fqdn.fqdn \"blahblah\"; # added by NetworkManager\n"
-    "send fqdn.no-client-update on;\n"
-    "\n"
-    "also request dhcp6.name-servers;\n"
-    "also request dhcp6.domain-search;\n"
-    "also request dhcp6.client-id;\n"
-    "\n";
-
-static void
-test_nonfqdn_hostname6(void)
-{
-    /* Non-FQDN hostname can now be used with dhclient */
-    test_config(NULL,
-                nonfqdn_hostname6_expected,
-                AF_INET6,
-                "blahblah",
-                0,
-                TRUE,
-                NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *existing_alsoreq_orig = "also request something;\n"
-                                           "also request another-thing;\n";
-
-static const char *existing_alsoreq_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request something;\n"
-    "also request another-thing;\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_existing_alsoreq(void)
-{
-    test_config(existing_alsoreq_orig,
-                existing_alsoreq_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *existing_req_orig = "request something;\n"
-                                       "also request some-other-thing;\n"
-                                       "request another-thing;\n"
-                                       "also request yet-another-thing;\n";
-
-static const char *existing_req_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "request; # override dhclient defaults\n"
-    "also request another-thing;\n"
-    "also request yet-another-thing;\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_existing_req(void)
-{
-    test_config(existing_req_orig,
-                existing_req_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *existing_multiline_alsoreq_orig =
-    "also request something another-thing yet-another-thing\n"
-    "    foobar baz blah;\n";
-
-static const char *existing_multiline_alsoreq_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request something;\n"
-    "also request another-thing;\n"
-    "also request yet-another-thing;\n"
-    "also request foobar;\n"
-    "also request baz;\n"
-    "also request blah;\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_existing_multiline_alsoreq(void)
-{
-    test_config(existing_multiline_alsoreq_orig,
-                existing_multiline_alsoreq_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static void
-test_one_duid(const char *escaped, const guint8 *unescaped, guint len)
-{
-    gs_unref_bytes GBytes *t1 = NULL;
-    gs_unref_bytes GBytes *t2 = NULL;
-    gs_free char          *w  = NULL;
-
-    t1 = nm_dhcp_dhclient_unescape_duid(escaped);
-    g_assert(t1);
-    g_assert(nm_g_bytes_equal_mem(t1, unescaped, len));
-
-    t2 = g_bytes_new(unescaped, len);
-    w  = nm_dhcp_dhclient_escape_duid(t2);
-    g_assert(w);
-    g_assert_cmpstr(escaped, ==, w);
-}
-
-static void
-test_duids(void)
-{
-    const guint8 test1_u[] =
-        {0x00, 0x01, 0x00, 0x01, 0x13, 0x6f, 0x13, 0x6e, 0x00, 0x22, 0xfa, 0x8c, 0xd6, 0xc2};
-    const char *test1_s = "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302";
-
-    const guint8 test2_u[] =
-        {0x00, 0x01, 0x00, 0x01, 0x17, 0x57, 0xee, 0x39, 0x00, 0x23, 0x15, 0x08, 0x7E, 0xac};
-    const char *test2_s = "\\000\\001\\000\\001\\027W\\3569\\000#\\025\\010~\\254";
-
-    const guint8 test3_u[] =
-        {0x00, 0x01, 0x00, 0x01, 0x17, 0x58, 0xe8, 0x58, 0x00, 0x23, 0x15, 0x08, 0x7e, 0xac};
-    const char *test3_s = "\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254";
-
-    const guint8 test4_u[] =
-        {0x00, 0x01, 0x00, 0x01, 0x15, 0xd5, 0x31, 0x97, 0x00, 0x16, 0xeb, 0x04, 0x45, 0x18};
-    const char *test4_s = "\\000\\001\\000\\001\\025\\3251\\227\\000\\026\\353\\004E\\030";
-
-    const char *bad_s = "\\000\\001\\000\\001\\425\\3251\\227\\000\\026\\353\\004E\\030";
-
-    test_one_duid(test1_s, test1_u, sizeof(test1_u));
-    test_one_duid(test2_s, test2_u, sizeof(test2_u));
-    test_one_duid(test3_s, test3_u, sizeof(test3_u));
-    test_one_duid(test4_s, test4_u, sizeof(test4_u));
-
-    /* Invalid octal digit */
-    g_assert(nm_dhcp_dhclient_unescape_duid(bad_s) == NULL);
-}
-
-static void
-test_read_duid_from_leasefile(void)
-{
-    const guint8 expected[] =
-        {0x00, 0x01, 0x00, 0x01, 0x18, 0x79, 0xa6, 0x13, 0x60, 0x67, 0x20, 0xec, 0x4c, 0x70};
-    gs_unref_bytes GBytes *duid  = NULL;
-    GError                *error = NULL;
-
-    duid = nm_dhcp_dhclient_read_duid(TEST_DIR "/test-dhclient-duid.leases", &error);
-    nmtst_assert_success(duid, error);
-
-    g_assert(nm_g_bytes_equal_mem(duid, expected, G_N_ELEMENTS(expected)));
-}
-
-static void
-test_read_commented_duid_from_leasefile(void)
-{
-    GBytes *duid;
-    GError *error = NULL;
-
-    duid = nm_dhcp_dhclient_read_duid(TEST_DIR "/test-dhclient-commented-duid.leases", &error);
-    g_assert_no_error(error);
-    g_assert(duid == NULL);
-}
-
-/*****************************************************************************/
-
-static void
-_check_duid_impl(const guint8 *duid_bin,
-                 gsize         duid_len,
-                 gboolean      enforce_duid,
-                 const char   *old_content,
-                 const char   *new_content)
-{
-    gs_free_error GError  *error    = NULL;
-    gs_free char          *contents = NULL;
-    gboolean               success;
-    const char            *path = NM_BUILD_BUILDDIR "/src/core/dhcp/tests/check-duid.lease";
-    gs_unref_bytes GBytes *duid = NULL;
-    gsize                  contents_len;
-
-    g_assert(duid_bin);
-    g_assert(duid_len > 0);
-
-    if (!nm_str_is_empty(old_content) || nmtst_get_rand_bool()) {
-        success = g_file_set_contents(path, old_content ?: "", -1, &error);
-        nmtst_assert_success(success, error);
-    } else
-        nmtst_file_unlink_if_exists(path);
-
-    duid = g_bytes_new(duid_bin, duid_len);
-
-    success = nm_dhcp_dhclient_save_duid(path, duid, enforce_duid, &error);
-    nmtst_assert_success(success, error);
-
-    success = g_file_get_contents(path, &contents, &contents_len, &error);
-    nmtst_assert_success(success, error);
-    g_assert(contents);
-
-    nmtst_file_unlink(path);
-
-    if (!nm_streq0(new_content, contents))
-        g_error("FAILING:\n\nEXPECTED:\n%s\nACTUAL:\n%s\n\n", new_content, contents);
-
-    g_assert_cmpstr(new_content, ==, contents);
-    g_assert_cmpint(contents_len, ==, strlen(contents));
-}
-
-#define _DUID(...) ((const guint8[]) {__VA_ARGS__})
-
-#define _check_duid(duid, enforce_duid, old_content, new_content) \
-    _check_duid_impl((duid), sizeof(duid), (enforce_duid), (old_content), (new_content))
-
-static void
-test_write_duid(void)
-{
-    _check_duid(_DUID(000, 001, 000, 001, 027, 'X', 0350, 'X', 0, '#', 025, 010, '~', 0254),
-                FALSE,
-                NULL,
-                "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n");
-
-    _check_duid(
-        _DUID(000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302),
-        FALSE,
-        "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n",
-        "default-duid \"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n");
-
-    _check_duid(_DUID(000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302),
-                FALSE,
-                "#default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n",
-                "default-duid "
-                "\"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n#default-duid "
-                "\"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n");
-    _check_duid(
-        _DUID(000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302),
-        FALSE,
-        "### Commented old DUID ###\n#default-duid "
-        "\"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n",
-        "default-duid \"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n### "
-        "Commented old DUID ###\n#default-duid "
-        "\"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n");
-
-    _check_duid(
-        _DUID(0xaa, 0xb, 0xcc, 0xd, 0xee, 0xf),
-        FALSE,
-        "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n  interface \"eth1\";\n "
-        " ia-na f1:ce:00:01 {\n    starts 1671015678;\n    renew 60;\n    rebind 105;\n    "
-        "iaaddr 192:168:121::1:112c {\n      starts 1671015678;\n      preferred-life 120;\n "
-        "     max-life 120;\n    }\n  }\n  option fqdn.encoded true;\n  option "
-        "fqdn.server-update true;\n  option fqdn.no-client-update false;\n  option fqdn.fqdn "
-        "\"dff6de4fcb0f\";\n  option fqdn.hostname \"dff6de4fcb0f\";\n  option dhcp6.client-id "
-        "aa:b:cc:d:ee:f;\n  option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n  option "
-        "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n  option dhcp6.fqdn "
-        "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n  option dhcp6.status-code success "
-        "\"success\";\n}\n",
-        "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n  interface \"eth1\";\n "
-        " ia-na f1:ce:00:01 {\n    starts 1671015678;\n    renew 60;\n    rebind 105;\n    "
-        "iaaddr 192:168:121::1:112c {\n      starts 1671015678;\n      preferred-life 120;\n "
-        "     max-life 120;\n    }\n  }\n  option fqdn.encoded true;\n  option "
-        "fqdn.server-update true;\n  option fqdn.no-client-update false;\n  option fqdn.fqdn "
-        "\"dff6de4fcb0f\";\n  option fqdn.hostname \"dff6de4fcb0f\";\n  option dhcp6.client-id "
-        "aa:b:cc:d:ee:f;\n  option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n  option "
-        "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n  option dhcp6.fqdn "
-        "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n  option dhcp6.status-code success "
-        "\"success\";\n}\n");
-
-    _check_duid(
-        _DUID(0xaa, 0xb, 0xcc, 0xd, 0xee, 0xf),
-        FALSE,
-        "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n  interface \"eth1\";\n "
-        " ia-na f1:ce:00:01 {\n    starts 1671015678;\n    renew 60;\n    rebind 105;\n    "
-        "iaaddr 192:168:121::1:112c {\n      starts 1671015678;\n      preferred-life 120;\n "
-        "     max-life 120;\n    }\n  }\n  option fqdn.encoded true;\n  option "
-        "fqdn.server-update true;\n  option fqdn.no-client-update false;\n  option fqdn.fqdn "
-        "\"dff6de4fcb0f\";\n  option fqdn.hostname \"dff6de4fcb0f\";\n  option dhcp6.client-id "
-        "aa:b:cc:d:ee:f;\n  option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n  option "
-        "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n  option dhcp6.fqdn "
-        "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n  option dhcp6.status-code success "
-        "\"success\";\r\n}\n",
-        "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n  interface \"eth1\";\n "
-        " ia-na f1:ce:00:01 {\n    starts 1671015678;\n    renew 60;\n    rebind 105;\n    "
-        "iaaddr 192:168:121::1:112c {\n      starts 1671015678;\n      preferred-life 120;\n "
-        "     max-life 120;\n    }\n  }\n  option fqdn.encoded true;\n  option "
-        "fqdn.server-update true;\n  option fqdn.no-client-update false;\n  option fqdn.fqdn "
-        "\"dff6de4fcb0f\";\n  option fqdn.hostname \"dff6de4fcb0f\";\n  option dhcp6.client-id "
-        "aa:b:cc:d:ee:f;\n  option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n  option "
-        "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n  option dhcp6.fqdn "
-        "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n  option dhcp6.status-code success "
-        "\"success\";\r\n}\n");
-
-    _check_duid(
-        _DUID(0xaa, 0xb, 0xcc, 0xd, 0xee, 0xe),
-        FALSE,
-        "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n  interface \"eth1\";\n "
-        " ia-na f1:ce:00:01 {\n    starts 1671015678;\n    renew 60;\n    rebind 105;\n    "
-        "iaaddr 192:168:121::1:112c {\n      starts 1671015678;\n      preferred-life 120;\n "
-        "     max-life 120;\n    }\n  }\n  option fqdn.encoded true;\n  option "
-        "fqdn.server-update true;\n  option fqdn.no-client-update false;\n  option fqdn.fqdn "
-        "\"dff6de4fcb0f\";\n  option fqdn.hostname \"dff6de4fcb0f\";\n  option dhcp6.client-id "
-        "aa:b:cc:d:ee:f;\n  option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n  option "
-        "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n  option dhcp6.fqdn "
-        "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n  option dhcp6.status-code success "
-        "\"success\";\r\n}\n",
-        "default-duid \"\\252\\013\\314\\015\\356\\016\";\nlease6 {\n  interface \"eth1\";\n "
-        " ia-na f1:ce:00:01 {\n    starts 1671015678;\n    renew 60;\n    rebind 105;\n    "
-        "iaaddr 192:168:121::1:112c {\n      starts 1671015678;\n      preferred-life 120;\n "
-        "     max-life 120;\n    }\n  }\n  option fqdn.encoded true;\n  option "
-        "fqdn.server-update true;\n  option fqdn.no-client-update false;\n  option fqdn.fqdn "
-        "\"dff6de4fcb0f\";\n  option fqdn.hostname \"dff6de4fcb0f\";\n  option dhcp6.client-id "
-        "aa:b:cc:d:ee:f;\n  option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n  option "
-        "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n  option dhcp6.fqdn "
-        "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n  option dhcp6.status-code success "
-        "\"success\";\r\n}\n");
-
-    _check_duid(
-        _DUID(0xaa, 0xb, 0xcc, 0xd, 0xee, 0xe),
-        TRUE,
-        "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n  interface \"eth1\";\n "
-        " ia-na f1:ce:00:01 {\n    starts 1671015678;\n    renew 60;\n    rebind 105;\n    "
-        "iaaddr 192:168:121::1:112c {\n      starts 1671015678;\n      preferred-life 120;\n "
-        "     max-life 120;\n    }\n  }\n  option fqdn.encoded true;\n  option "
-        "fqdn.server-update true;\n  option fqdn.no-client-update false;\n  option fqdn.fqdn "
-        "\"dff6de4fcb0f\";\n  option fqdn.hostname \"dff6de4fcb0f\";\n  option dhcp6.client-id "
-        "aa:b:cc:d:ee:f;\n  option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n  option "
-        "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n  option dhcp6.fqdn "
-        "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n  option dhcp6.status-code success "
-        "\"success\";\n}\n",
-        "default-duid \"\\252\\013\\314\\015\\356\\016\";\nlease6 {\n  interface \"eth1\";\n "
-        " ia-na f1:ce:00:01 {\n    starts 1671015678;\n    renew 60;\n    rebind 105;\n    "
-        "iaaddr 192:168:121::1:112c {\n      starts 1671015678;\n      preferred-life 120;\n "
-        "     max-life 120;\n    }\n  }\n  option fqdn.encoded true;\n  option "
-        "fqdn.server-update true;\n  option fqdn.no-client-update false;\n  option fqdn.fqdn "
-        "\"dff6de4fcb0f\";\n  option fqdn.hostname \"dff6de4fcb0f\";\n  option dhcp6.client-id "
-        "aa:b:cc:d:ee:e;\n  option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n  option "
-        "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n  option dhcp6.fqdn "
-        "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n  option dhcp6.status-code success "
-        "\"success\";\n}\n");
-}
-
-/*****************************************************************************/
-
-static const char *interface1_orig = "interface \"eth0\" {\n"
-                                     "\talso request my-option;\n"
-                                     "\tinitial-delay 5;\n"
-                                     "}\n"
-                                     "interface \"eth1\" {\n"
-                                     "\talso request another-option;\n"
-                                     "\tinitial-delay 0;\n"
-                                     "}\n"
-                                     "\n"
-                                     "also request yet-another-option;\n";
-
-static const char *interface1_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n"
-    "initial-delay 5;\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "also request my-option;\n"
-    "also request yet-another-option;\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_interface1(void)
-{
-    test_config(interface1_orig,
-                interface1_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-static const char *interface2_orig = "interface eth0 {\n"
-                                     "\talso request my-option;\n"
-                                     "\tinitial-delay 5;\n"
-                                     " }\n"
-                                     "interface eth1 {\n"
-                                     "\tinitial-delay 0;\n"
-                                     "\trequest another-option;\n"
-                                     " } \n"
-                                     "\n"
-                                     "also request yet-another-option;\n";
-
-static const char *interface2_expected =
-    "# Created by NetworkManager\n"
-    "# Merged from /path/to/dhclient.conf\n"
-    "\n"
-    "initial-delay 0;\n"
-    "\n"
-    "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-    "option wpad code 252 = string;\n"
-    "\n"
-    "request; # override dhclient defaults\n"
-    "also request another-option;\n"
-    "also request yet-another-option;\n"
-    "also request rfc3442-classless-static-routes;\n"
-    "also request ms-classless-static-routes;\n"
-    "also request static-routes;\n"
-    "also request wpad;\n"
-    "also request ntp-servers;\n"
-    "also request root-path;\n"
-    "\n";
-
-static void
-test_interface2(void)
-{
-    test_config(interface2_orig,
-                interface2_expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                NULL,
-                "eth1",
-                NULL,
-                NULL);
-}
-
-static void
-test_structured(void)
-{
-    gs_unref_bytes GBytes *new_client_id = NULL;
-    const guint8           bytes[]       = "sad-and-useless";
-
-    static const char *const orig =
-        "interface \"eth0\"   {  \n"
-        "    send host-name \"useless.example.com\";\n"
-        "    hardware ethernet de:ad:80:86:ba:be;\n"
-        "    send dhcp-client-identifier \"sad-and-useless\";\n"
-        "    script \"/bin/useless\";\n"
-        "    send dhcp-lease-time 8086;\n"
-        "    request subnet-mask, broadcast-address, time-offset, routers,\n"
-        "        domain-search, domain-name, host-name;\n"
-        "    require subnet-mask;\n"
-        "}  \n"
-        "\n"
-        "    interface \"eth1\"   {  \n"
-        "    send host-name \"sad.example.com\";\n"
-        "    hardware ethernet de:ca:f6:66:ca:fe;\n"
-        "    send dhcp-client-identifier \"useless-and-miserable\";\n"
-        "    script \"/bin/miserable\";\n"
-        "    send dhcp-lease-time 1337;\n"
-        "    request subnet-mask, broadcast-address, time-offset, routers,\n"
-        "        domain-search, domain-name, domain-name-servers, host-name;\n"
-        "    require subnet-mask, domain-name-servers;\n"
-        "    if not option domain-name = \"example.org\" {\n"
-        "        prepend domain-name-servers 127.0.0.1;\n"
-        "    } else {\n"
-        "        prepend domain-name-servers 127.0.0.2;\n"
-        "    }  \n"
-        "    }  \n"
-        "\n"
-        "pseudo \"secondary\" \"eth0\"   {  \n"
-        "    send dhcp-client-identifier \"sad-useless-and-secondary\";\n"
-        "    script \"/bin/secondary\";\n"
-        "    send host-name \"secondary.useless.example.com\";\n"
-        "    send dhcp-lease-time 666;\n"
-        "    request routers;\n"
-        "    require routers;\n"
-        "    }  \n"
-        "\n"
-        "    pseudo \"tertiary\" \"eth0\"   {  \n"
-        "   send dhcp-client-identifier \"sad-useless-and-tertiary\";\n"
-        "  script \"/bin/tertiary\";\n"
-        " send host-name \"tertiary.useless.example.com\";\n"
-        "}  \n"
-        "\n"
-        "  alias{  \n"
-        "    interface \"eth0\";\n"
-        "    fixed-address 192.0.2.1;\n"
-        "    option subnet-mask 255.255.255.0;\n"
-        "  }  \n"
-        "  lease   {  \n"
-        "    interface \"eth0\";\n"
-        "    fixed-address 192.0.2.2;\n"
-        "    option subnet-mask 255.255.255.0;\n"
-        "  }  \n"
-        "if not option domain-name = \"example.org\" {\n"
-        "  prepend domain-name-servers 127.0.0.1;\n"
-        "  if not option domain-name = \"useless.example.com\" {\n"
-        "    prepend domain-name-servers 127.0.0.2;\n"
-        "  }\n"
-        "}\n";
-
-    static const char *const expected =
-        "# Created by NetworkManager\n"
-        "# Merged from /path/to/dhclient.conf\n"
-        "\n"
-        "send host-name \"useless.example.com\";\n"
-        "hardware ethernet de:ad:80:86:ba:be;\n"
-        "send dhcp-client-identifier \"sad-and-useless\";\n"
-        "send dhcp-lease-time 8086;\n"
-        "require subnet-mask;\n"
-        "if not option domain-name = \"example.org\" {\n"
-        "prepend domain-name-servers 127.0.0.1;\n"
-        "if not option domain-name = \"useless.example.com\" {\n"
-        "prepend domain-name-servers 127.0.0.2;\n"
-        "}\n"
-        "}\n"
-        "\n"
-        "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-        "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-        "option wpad code 252 = string;\n"
-        "\n"
-        "request; # override dhclient defaults\n"
-        "also request subnet-mask;\n"
-        "also request broadcast-address;\n"
-        "also request time-offset;\n"
-        "also request routers;\n"
-        "also request domain-search;\n"
-        "also request domain-name;\n"
-        "also request host-name;\n"
-        "also request rfc3442-classless-static-routes;\n"
-        "also request ms-classless-static-routes;\n"
-        "also request static-routes;\n"
-        "also request wpad;\n"
-        "also request ntp-servers;\n"
-        "also request root-path;\n"
-        "\n";
-
-    new_client_id = g_bytes_new(bytes, sizeof(bytes) - 1);
-    test_config(orig,
-                expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                new_client_id,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-static void
-test_config_req_intf(void)
-{
-    static const char *const orig = "request subnet-mask, broadcast-address, routers,\n"
-                                    "\trfc3442-classless-static-routes,\n"
-                                    "\tinterface-mtu, host-name, domain-name, domain-search,\n"
-                                    "\tdomain-name-servers, nis-domain, nis-servers,\n"
-                                    "\tnds-context, nds-servers, nds-tree-name,\n"
-                                    "\tnetbios-name-servers, netbios-dd-server,\n"
-                                    "\tnetbios-node-type, netbios-scope, ntp-servers;\n"
-                                    "";
-    static const char *const expected =
-        "# Created by NetworkManager\n"
-        "# Merged from /path/to/dhclient.conf\n"
-        "\n"
-        "\n"
-        "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
-        "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
-        "option wpad code 252 = string;\n"
-        "\n"
-        "request; # override dhclient defaults\n"
-        "also request subnet-mask;\n"
-        "also request broadcast-address;\n"
-        "also request routers;\n"
-        "also request rfc3442-classless-static-routes;\n"
-        "also request interface-mtu;\n"
-        "also request host-name;\n"
-        "also request domain-name;\n"
-        "also request domain-search;\n"
-        "also request domain-name-servers;\n"
-        "also request nis-domain;\n"
-        "also request nis-servers;\n"
-        "also request nds-context;\n"
-        "also request nds-servers;\n"
-        "also request nds-tree-name;\n"
-        "also request netbios-name-servers;\n"
-        "also request netbios-dd-server;\n"
-        "also request netbios-node-type;\n"
-        "also request netbios-scope;\n"
-        "also request ntp-servers;\n"
-        "also request ms-classless-static-routes;\n"
-        "also request static-routes;\n"
-        "also request wpad;\n"
-        "also request root-path;\n"
-        "\n";
-
-    test_config(orig,
-                expected,
-                AF_INET,
-                NULL,
-                0,
-                FALSE,
-                NM_DHCP_HOSTNAME_FLAG_NONE,
-                NULL,
-                NULL,
-                "eth0",
-                NULL,
-                NULL);
-}
-
-/*****************************************************************************/
-
-NMTST_DEFINE();
-
-int
-main(int argc, char **argv)
-{
-    nmtst_init_with_logging(&argc, &argv, NULL, "DEFAULT");
-
-    g_test_add_func("/dhcp/dhclient/orig_missing", test_orig_missing);
-    g_test_add_func("/dhcp/dhclient/orig_missing_add_mud_url", test_orig_missing_add_mud_url);
-    g_test_add_func("/dhcp/dhclient/override_client_id", test_override_client_id);
-    g_test_add_func("/dhcp/dhclient/quote_client_id/1", test_quote_client_id);
-    g_test_add_func("/dhcp/dhclient/quote_client_id/2", test_quote_client_id_2);
-    g_test_add_func("/dhcp/dhclient/hex_zero_client_id", test_hex_zero_client_id);
-    g_test_add_func("/dhcp/dhclient/ascii_client_id", test_ascii_client_id);
-    g_test_add_func("/dhcp/dhclient/hex_single_client_id", test_hex_single_client_id);
-    g_test_add_func("/dhcp/dhclient/existing-hex-client-id", test_existing_hex_client_id);
-    g_test_add_func("/dhcp/dhclient/existing-client-id", test_existing_escaped_client_id);
-    g_test_add_func("/dhcp/dhclient/existing-ascii-client-id", test_existing_ascii_client_id);
-    g_test_add_func("/dhcp/dhclient/none-client-id", test_none_client_id);
-    g_test_add_func("/dhcp/dhclient/missing-client-id", test_missing_client_id);
-    g_test_add_func("/dhcp/dhclient/fqdn", test_fqdn);
-    g_test_add_func("/dhcp/dhclient/fqdn_options_override", test_fqdn_options_override);
-    g_test_add_func("/dhcp/dhclient/override_hostname", test_override_hostname);
-    g_test_add_func("/dhcp/dhclient/override_hostname6", test_override_hostname6);
-    g_test_add_func("/dhcp/dhclient/nonfqdn_hostname6", test_nonfqdn_hostname6);
-    g_test_add_func("/dhcp/dhclient/existing_req", test_existing_req);
-    g_test_add_func("/dhcp/dhclient/existing_alsoreq", test_existing_alsoreq);
-    g_test_add_func("/dhcp/dhclient/existing_multiline_alsoreq", test_existing_multiline_alsoreq);
-    g_test_add_func("/dhcp/dhclient/duids", test_duids);
-    g_test_add_func("/dhcp/dhclient/interface/1", test_interface1);
-    g_test_add_func("/dhcp/dhclient/interface/2", test_interface2);
-    g_test_add_func("/dhcp/dhclient/config/req_intf", test_config_req_intf);
-    g_test_add_func("/dhcp/dhclient/structured", test_structured);
-
-    g_test_add_func("/dhcp/dhclient/read_duid_from_leasefile", test_read_duid_from_leasefile);
-    g_test_add_func("/dhcp/dhclient/read_commented_duid_from_leasefile",
-                    test_read_commented_duid_from_leasefile);
-
-    g_test_add_func("/dhcp/dhclient/test_write_duid", test_write_duid);
-
-    return g_test_run();
-}