summary refs log tree commit diff
path: root/src/core/nm-iface-helper.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
committerMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
commit80ec1decc49c72efec2a8b87c06245c92c0ab807 (patch)
treee3b229aa94e8dcf0590f2317664176e7b8f7607b /src/core/nm-iface-helper.c
parent65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (diff)
New upstream version 1.29.90 upstream/1.29.90
Diffstat (limited to 'src/core/nm-iface-helper.c')
-rw-r--r--src/core/nm-iface-helper.c858
1 files changed, 858 insertions, 0 deletions
diff --git a/src/core/nm-iface-helper.c b/src/core/nm-iface-helper.c
new file mode 100644
index 00000000..b6e9981d
--- /dev/null
+++ b/src/core/nm-iface-helper.c
@@ -0,0 +1,858 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include <glib-unix.h>
+#include <getopt.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-glib-aux/nm-c-list.h"
+
+#include "main-utils.h"
+#include "NetworkManagerUtils.h"
+#include "platform/nm-linux-platform.h"
+#include "nm-platform/nm-platform-utils.h"
+#include "dhcp/nm-dhcp-manager.h"
+#include "ndisc/nm-ndisc.h"
+#include "ndisc/nm-lndp-ndisc.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nm-setting-ip6-config.h"
+#include "systemd/nm-sd.h"
+
+#if !defined(NM_DIST_VERSION)
+    #define NM_DIST_VERSION VERSION
+#endif
+
+#define NMIH_PID_FILE_FMT NMRUNDIR "/nm-iface-helper-%d.pid"
+
+/*****************************************************************************/
+
+static struct {
+    GMainLoop *main_loop;
+    int        ifindex;
+    gboolean   is_vrf_device;
+
+    guint dad_failed_id;
+    CList dad_failed_lst_head;
+} gl /*obal*/ = {
+    .ifindex       = -1,
+    .is_vrf_device = FALSE,
+};
+
+static struct {
+    gboolean                      slaac;
+    gboolean                      show_version;
+    gboolean                      become_daemon;
+    gboolean                      debug;
+    gboolean                      g_fatal_warnings;
+    gboolean                      slaac_required;
+    gboolean                      dhcp4_required;
+    int                           tempaddr;
+    char *                        ifname;
+    char *                        uuid;
+    char *                        stable_id;
+    char *                        dhcp4_address;
+    char *                        dhcp4_clientid;
+    char *                        dhcp4_hostname;
+    char *                        dhcp4_fqdn;
+    char *                        mud_url;
+    char *                        iid_str;
+    NMSettingIP6ConfigAddrGenMode addr_gen_mode;
+    char *                        logging_backend;
+    char *                        opt_log_level;
+    char *                        opt_log_domains;
+    guint32                       priority_v4;
+    guint32                       priority_v6;
+} global_opt = {
+    .tempaddr    = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN,
+    .priority_v4 = NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4,
+    .priority_v6 = NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
+};
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME "nm-iface-helper"
+#define _NMLOG(level, domain, ...) \
+    nm_log((level),                \
+           (domain),               \
+           global_opt.ifname,      \
+           NULL,                   \
+           "iface-helper: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) _NM_UTILS_MACRO_REST(__VA_ARGS__))
+
+/*****************************************************************************/
+
+static void
+dhcp4_state_changed(NMDhcpClient *client,
+                    NMDhcpState   state,
+                    NMIP4Config * ip4_config,
+                    GHashTable *  options,
+                    gpointer      user_data)
+{
+    static NMIP4Config *last_config = NULL;
+    NMIP4Config *       existing;
+    gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
+    gs_free_error GError *error                          = NULL;
+
+    g_return_if_fail(!ip4_config || NM_IS_IP4_CONFIG(ip4_config));
+
+    _LOGD(LOGD_DHCP4, "new DHCPv4 client state %d", state);
+
+    switch (state) {
+    case NM_DHCP_STATE_BOUND:
+    case NM_DHCP_STATE_EXTENDED:
+        g_assert(ip4_config);
+        g_assert(nm_ip4_config_get_ifindex(ip4_config) == gl.ifindex);
+
+        existing = nm_ip4_config_capture(nm_platform_get_multi_idx(NM_PLATFORM_GET),
+                                         NM_PLATFORM_GET,
+                                         gl.ifindex);
+        if (last_config)
+            nm_ip4_config_subtract(existing, last_config, 0);
+
+        nm_ip4_config_merge(existing, ip4_config, NM_IP_CONFIG_MERGE_DEFAULT, 0);
+        nm_ip4_config_add_dependent_routes(existing,
+                                           RT_TABLE_MAIN,
+                                           global_opt.priority_v4,
+                                           gl.is_vrf_device,
+                                           &ip4_dev_route_blacklist);
+        if (!nm_ip4_config_commit(existing, NM_PLATFORM_GET, NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN))
+            _LOGW(LOGD_DHCP4, "failed to apply DHCPv4 config");
+
+        if (!last_config && !nm_dhcp_client_accept(client, &error))
+            _LOGW(LOGD_DHCP4, "failed to accept lease: %s", error->message);
+
+        nm_platform_ip4_dev_route_blacklist_set(NM_PLATFORM_GET,
+                                                gl.ifindex,
+                                                ip4_dev_route_blacklist);
+
+        if (last_config)
+            g_object_unref(last_config);
+        last_config = nm_ip4_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET),
+                                        nm_dhcp_client_get_ifindex(client));
+        nm_ip4_config_replace(last_config, ip4_config, NULL);
+        break;
+    case NM_DHCP_STATE_TIMEOUT:
+    case NM_DHCP_STATE_DONE:
+    case NM_DHCP_STATE_FAIL:
+        if (global_opt.dhcp4_required) {
+            _LOGW(LOGD_DHCP4, "DHCPv4 timed out or failed, quitting...");
+            g_main_loop_quit(gl.main_loop);
+        } else
+            _LOGW(LOGD_DHCP4, "DHCPv4 timed out or failed");
+        break;
+    default:
+        break;
+    }
+}
+
+static void
+ndisc_config_changed(NMNDisc *          ndisc,
+                     const NMNDiscData *rdata,
+                     guint              changed_int,
+                     gpointer           user_data)
+{
+    NMNDiscConfigMap    changed      = changed_int;
+    static NMIP6Config *ndisc_config = NULL;
+    NMIP6Config *       existing;
+
+    existing = nm_ip6_config_capture(nm_platform_get_multi_idx(NM_PLATFORM_GET),
+                                     NM_PLATFORM_GET,
+                                     gl.ifindex,
+                                     global_opt.tempaddr);
+    if (ndisc_config)
+        nm_ip6_config_subtract(existing, ndisc_config, 0);
+    else {
+        ndisc_config = nm_ip6_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), gl.ifindex);
+    }
+
+    if (changed & NM_NDISC_CONFIG_ADDRESSES) {
+        guint8  plen;
+        guint32 ifa_flags;
+
+        /* Check, whether kernel is recent enough to help user space handling RA.
+         * If it's not supported, we have no ipv6-privacy and must add autoconf
+         * addresses as /128. The reason for the /128 is to prevent the kernel
+         * from adding a prefix route for this address. */
+        ifa_flags = 0;
+        if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) {
+            ifa_flags |= IFA_F_NOPREFIXROUTE;
+            if (NM_IN_SET(global_opt.tempaddr,
+                          NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
+                          NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR))
+                ifa_flags |= IFA_F_MANAGETEMPADDR;
+            plen = 64;
+        } else
+            plen = 128;
+
+        nm_ip6_config_reset_addresses_ndisc(ndisc_config,
+                                            rdata->addresses,
+                                            rdata->addresses_n,
+                                            plen,
+                                            ifa_flags);
+    }
+
+    if (NM_FLAGS_ANY(changed, NM_NDISC_CONFIG_ROUTES | NM_NDISC_CONFIG_GATEWAYS)) {
+        nm_ip6_config_reset_routes_ndisc(
+            ndisc_config,
+            rdata->gateways,
+            rdata->gateways_n,
+            rdata->routes,
+            rdata->routes_n,
+            RT_TABLE_MAIN,
+            global_opt.priority_v6,
+            nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF));
+    }
+
+    if (changed & NM_NDISC_CONFIG_DHCP_LEVEL) {
+        /* Unsupported until systemd DHCPv6 is ready */
+    }
+
+    if (changed & NM_NDISC_CONFIG_HOP_LIMIT)
+        nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(NM_PLATFORM_GET,
+                                                           global_opt.ifname,
+                                                           rdata->hop_limit);
+
+    if (changed & NM_NDISC_CONFIG_REACHABLE_TIME) {
+        nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(NM_PLATFORM_GET,
+                                                            global_opt.ifname,
+                                                            rdata->reachable_time_ms);
+    }
+
+    if (changed & NM_NDISC_CONFIG_RETRANS_TIMER) {
+        nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(NM_PLATFORM_GET,
+                                                          global_opt.ifname,
+                                                          rdata->retrans_timer_ms);
+    }
+
+    if (changed & NM_NDISC_CONFIG_MTU) {
+        nm_platform_sysctl_ip_conf_set_int64(NM_PLATFORM_GET,
+                                             AF_INET6,
+                                             global_opt.ifname,
+                                             "mtu",
+                                             rdata->mtu);
+    }
+
+    nm_ip6_config_merge(existing, ndisc_config, NM_IP_CONFIG_MERGE_DEFAULT, 0);
+    nm_ip6_config_add_dependent_routes(existing,
+                                       RT_TABLE_MAIN,
+                                       global_opt.priority_v6,
+                                       gl.is_vrf_device);
+    if (!nm_ip6_config_commit(existing, NM_PLATFORM_GET, NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN, NULL))
+        _LOGW(LOGD_IP6, "failed to apply IPv6 config");
+}
+
+static void
+ndisc_ra_timeout(NMNDisc *ndisc, gpointer user_data)
+{
+    if (global_opt.slaac_required) {
+        _LOGW(LOGD_IP6, "IPv6 timed out or failed, quitting...");
+        g_main_loop_quit(gl.main_loop);
+    } else
+        _LOGW(LOGD_IP6, "IPv6 timed out or failed");
+}
+
+static gboolean
+quit_handler(gpointer user_data)
+{
+    g_main_loop_quit(gl.main_loop);
+    return G_SOURCE_REMOVE;
+}
+
+static void
+setup_signals(void)
+{
+    signal(SIGPIPE, SIG_IGN);
+    g_unix_signal_add(SIGINT, quit_handler, NULL);
+    g_unix_signal_add(SIGTERM, quit_handler, NULL);
+}
+
+static gboolean
+do_early_setup(int *argc, char **argv[])
+{
+    gint64       priority64_v4 = -1;
+    gint64       priority64_v6 = -1;
+    GOptionEntry options[]     = {
+        /* Interface/IP config */
+        {"ifname",
+         'i',
+         0,
+         G_OPTION_ARG_STRING,
+         &global_opt.ifname,
+         N_("The interface to manage"),
+         "eth0"},
+        {"uuid",
+         'u',
+         0,
+         G_OPTION_ARG_STRING,
+         &global_opt.uuid,
+         N_("Connection UUID"),
+         "661e8cd0-b618-46b8-9dc9-31a52baaa16b"},
+        {"stable-id",
+         '\0',
+         0,
+         G_OPTION_ARG_STRING,
+         &global_opt.stable_id,
+         N_("Connection Token for Stable IDs"),
+         "eth"},
+        {"slaac",
+         's',
+         0,
+         G_OPTION_ARG_NONE,
+         &global_opt.slaac,
+         N_("Whether to manage IPv6 SLAAC"),
+         NULL},
+        {"slaac-required",
+         '6',
+         0,
+         G_OPTION_ARG_NONE,
+         &global_opt.slaac_required,
+         N_("Whether SLAAC must be successful"),
+         NULL},
+        {"slaac-tempaddr",
+         't',
+         0,
+         G_OPTION_ARG_INT,
+         &global_opt.tempaddr,
+         N_("Use an IPv6 temporary privacy address"),
+         NULL},
+        {"dhcp4",
+         'd',
+         0,
+         G_OPTION_ARG_STRING,
+         &global_opt.dhcp4_address,
+         N_("Current DHCPv4 address"),
+         NULL},
+        {"dhcp4-required",
+         '4',
+         0,
+         G_OPTION_ARG_NONE,
+         &global_opt.dhcp4_required,
+         N_("Whether DHCPv4 must be successful"),
+         NULL},
+        {"dhcp4-clientid",
+         'c',
+         0,
+         G_OPTION_ARG_STRING,
+         &global_opt.dhcp4_clientid,
+         N_("Hex-encoded DHCPv4 client ID"),
+         NULL},
+        {"dhcp4-hostname",
+         'h',
+         0,
+         G_OPTION_ARG_STRING,
+         &global_opt.dhcp4_hostname,
+         N_("Hostname to send to DHCP server"),
+         N_("barbar")},
+        {"dhcp4-fqdn",
+         'F',
+         0,
+         G_OPTION_ARG_STRING,
+         &global_opt.dhcp4_fqdn,
+         N_("FQDN to send to DHCP server"),
+         N_("host.domain.org")},
+        {"priority4",
+         '\0',
+         0,
+         G_OPTION_ARG_INT64,
+         &priority64_v4,
+         N_("Route priority for IPv4"),
+         N_("0")},
+        {"priority6",
+         '\0',
+         0,
+         G_OPTION_ARG_INT64,
+         &priority64_v6,
+         N_("Route priority for IPv6"),
+         N_("1024")},
+        {"iid",
+         'e',
+         0,
+         G_OPTION_ARG_STRING,
+         &global_opt.iid_str,
+         N_("Hex-encoded Interface Identifier"),
+         ""},
+        {"addr-gen-mode",
+         'e',
+         0,
+         G_OPTION_ARG_INT,
+         &global_opt.addr_gen_mode,
+         N_("IPv6 SLAAC address generation mode"),
+         "eui64"},
+        {"logging-backend",
+         '\0',
+         0,
+         G_OPTION_ARG_STRING,
+         &global_opt.logging_backend,
+         N_("The logging backend configuration value. See logging.backend in NetworkManager.conf"),
+         NULL},
+
+        /* Logging/debugging */
+        {"version",
+         'V',
+         0,
+         G_OPTION_ARG_NONE,
+         &global_opt.show_version,
+         N_("Print NetworkManager version and exit"),
+         NULL},
+        {"no-daemon",
+         'n',
+         G_OPTION_FLAG_REVERSE,
+         G_OPTION_ARG_NONE,
+         &global_opt.become_daemon,
+         N_("Don't become a daemon"),
+         NULL},
+        {"debug",
+         'b',
+         0,
+         G_OPTION_ARG_NONE,
+         &global_opt.debug,
+         N_("Don't become a daemon, and log to stderr"),
+         NULL},
+        {"log-level",
+         0,
+         0,
+         G_OPTION_ARG_STRING,
+         &global_opt.opt_log_level,
+         N_("Log level: one of [%s]"),
+         "INFO"},
+        {"log-domains",
+         0,
+         0,
+         G_OPTION_ARG_STRING,
+         &global_opt.opt_log_domains,
+         N_("Log domains separated by ',': any combination of [%s]"),
+         "PLATFORM,RFKILL,WIFI"},
+        {"g-fatal-warnings",
+         0,
+         0,
+         G_OPTION_ARG_NONE,
+         &global_opt.g_fatal_warnings,
+         N_("Make all warnings fatal"),
+         NULL},
+        {NULL}};
+
+    if (!nm_main_utils_early_setup("nm-iface-helper",
+                                   argc,
+                                   argv,
+                                   options,
+                                   NULL,
+                                   NULL,
+                                   _("nm-iface-helper is a small, standalone process that manages "
+                                     "a single network interface.")))
+        return FALSE;
+
+    if (priority64_v4 >= 0 && priority64_v4 <= G_MAXUINT32)
+        global_opt.priority_v4 = (guint32) priority64_v4;
+    if (priority64_v6 >= 0 && priority64_v6 <= G_MAXUINT32)
+        global_opt.priority_v6 = (guint32) priority64_v6;
+    return TRUE;
+}
+
+typedef struct {
+    NMPlatform *platform;
+    NMNDisc *   ndisc;
+} DadFailedHandleData;
+
+static gboolean
+dad_failed_handle_idle(gpointer user_data)
+{
+    DadFailedHandleData *data = user_data;
+    NMCListElem *        elem;
+
+    while ((elem = c_list_first_entry(&gl.dad_failed_lst_head, NMCListElem, lst))) {
+        nm_auto_nmpobj const NMPObject *obj = elem->data;
+
+        nm_c_list_elem_free(elem);
+
+        if (nm_ndisc_dad_addr_is_fail_candidate(data->platform, obj)) {
+            nm_ndisc_dad_failed(data->ndisc, &NMP_OBJECT_CAST_IP6_ADDRESS(obj)->address, TRUE);
+        }
+    }
+
+    gl.dad_failed_id = 0;
+    return G_SOURCE_REMOVE;
+}
+
+static void
+ip6_address_changed(NMPlatform *                platform,
+                    int                         obj_type_i,
+                    int                         iface,
+                    const NMPlatformIP6Address *addr,
+                    int                         change_type_i,
+                    NMNDisc *                   ndisc)
+{
+    const NMPlatformSignalChangeType change_type = change_type_i;
+    DadFailedHandleData *            data;
+
+    if (!nm_ndisc_dad_addr_is_fail_candidate_event(change_type, addr))
+        return;
+
+    c_list_link_tail(
+        &gl.dad_failed_lst_head,
+        &nm_c_list_elem_new_stale((gpointer) nmp_object_ref(NMP_OBJECT_UP_CAST(addr)))->lst);
+    if (gl.dad_failed_id)
+        return;
+
+    data             = g_slice_new(DadFailedHandleData);
+    data->platform   = platform;
+    data->ndisc      = ndisc;
+    gl.dad_failed_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
+                                       dad_failed_handle_idle,
+                                       data,
+                                       nm_g_slice_free_fcn(DadFailedHandleData));
+}
+
+int
+main(int argc, char *argv[])
+{
+    char *        bad_domains                  = NULL;
+    gs_free_error GError *error                = NULL;
+    gboolean              wrote_pidfile        = FALSE;
+    gs_free char *        pidfile              = NULL;
+    gs_unref_object NMDhcpClient *dhcp4_client = NULL;
+    gs_unref_object NMNDisc *ndisc             = NULL;
+    gs_unref_bytes GBytes *hwaddr              = NULL;
+    gs_unref_bytes GBytes *bcast_hwaddr        = NULL;
+    gs_unref_bytes GBytes *client_id           = NULL;
+    gs_free NMUtilsIPv6IfaceId *iid            = NULL;
+    const NMPlatformLink *      pllink;
+    guint                       sd_id;
+    int                         errsv;
+
+    c_list_init(&gl.dad_failed_lst_head);
+
+    setpgid(getpid(), getpid());
+
+    if (!do_early_setup(&argc, &argv))
+        return 1;
+
+    nm_logging_init_pre("nm-iface-helper",
+                        g_strdup_printf("%s[%ld] (%s): ",
+                                        _NMLOG_PREFIX_NAME,
+                                        (long) getpid(),
+                                        global_opt.ifname ?: "???"));
+
+    if (global_opt.g_fatal_warnings) {
+        GLogLevelFlags fatal_mask;
+
+        fatal_mask = g_log_set_always_fatal(G_LOG_FATAL_MASK);
+        fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL;
+        g_log_set_always_fatal(fatal_mask);
+    }
+
+    if (global_opt.show_version) {
+        fprintf(stdout, NM_DIST_VERSION "\n");
+        return 0;
+    }
+
+    nm_main_utils_ensure_root();
+
+    if (!global_opt.ifname || !global_opt.uuid) {
+        fprintf(stderr, _("An interface name and UUID are required\n"));
+        return 1;
+    }
+
+    gl.ifindex = nmp_utils_if_nametoindex(global_opt.ifname);
+    if (gl.ifindex <= 0) {
+        errsv = errno;
+        fprintf(stderr,
+                _("Failed to find interface index for %s (%s)\n"),
+                global_opt.ifname,
+                nm_strerror_native(errsv));
+        return 1;
+    }
+    pidfile = g_strdup_printf(NMIH_PID_FILE_FMT, gl.ifindex);
+    nm_main_utils_ensure_not_running_pidfile(pidfile);
+
+    nm_main_utils_ensure_rundir();
+
+    if (!nm_logging_setup(global_opt.opt_log_level,
+                          global_opt.opt_log_domains,
+                          &bad_domains,
+                          &error)) {
+        fprintf(stderr,
+                _("%s.  Please use --help to see a list of valid options.\n"),
+                error->message);
+        return 1;
+    } else if (bad_domains) {
+        fprintf(stderr,
+                _("Ignoring unrecognized log domain(s) '%s' passed on command line.\n"),
+                bad_domains);
+        nm_clear_g_free(&bad_domains);
+    }
+
+    if (global_opt.become_daemon && !global_opt.debug) {
+        if (daemon(0, 0) < 0) {
+            errsv = errno;
+            fprintf(stderr,
+                    _("Could not daemonize: %s [error %u]\n"),
+                    nm_strerror_native(errsv),
+                    errsv);
+            return 1;
+        }
+        if (nm_main_utils_write_pidfile(pidfile))
+            wrote_pidfile = TRUE;
+    }
+
+    /* Set up unix signal handling - before creating threads, but after daemonizing! */
+    gl.main_loop = g_main_loop_new(NULL, FALSE);
+    setup_signals();
+
+    nm_logging_init(global_opt.logging_backend, global_opt.debug);
+
+    _LOGI(LOGD_CORE, "nm-iface-helper (version " NM_DIST_VERSION ") is starting...");
+
+    /* Set up platform interaction layer */
+    nm_linux_platform_setup();
+
+    pllink = nm_platform_link_get(NM_PLATFORM_GET, gl.ifindex);
+    if (pllink) {
+        hwaddr       = nmp_link_address_get_as_bytes(&pllink->l_address);
+        bcast_hwaddr = nmp_link_address_get_as_bytes(&pllink->l_broadcast);
+
+        if (pllink->master > 0) {
+            gl.is_vrf_device =
+                nm_platform_link_get_type(NM_PLATFORM_GET, pllink->master) == NM_LINK_TYPE_VRF;
+        }
+    }
+
+    if (global_opt.iid_str) {
+        GBytes *bytes;
+        gsize   ignored = 0;
+
+        bytes = nm_utils_hexstr2bin(global_opt.iid_str);
+        if (!bytes || g_bytes_get_size(bytes) != sizeof(*iid)) {
+            fprintf(stderr, _("(%s): Invalid IID %s\n"), global_opt.ifname, global_opt.iid_str);
+            return 1;
+        }
+        iid = g_bytes_unref_to_data(bytes, &ignored);
+    }
+
+    if (global_opt.dhcp4_clientid) {
+        /* this string is just a plain hex-string. Unlike ipv4.dhcp-client-id, which
+         * is parsed via nm_dhcp_utils_client_id_string_to_bytes(). */
+        client_id = nm_utils_hexstr2bin(global_opt.dhcp4_clientid);
+        if (!client_id || g_bytes_get_size(client_id) < 2) {
+            fprintf(stderr,
+                    _("(%s): Invalid DHCP client-id %s\n"),
+                    global_opt.ifname,
+                    global_opt.dhcp4_clientid);
+            return 1;
+        }
+    }
+
+    if (global_opt.dhcp4_address) {
+        nm_platform_sysctl_ip_conf_set(NM_PLATFORM_GET,
+                                       AF_INET,
+                                       global_opt.ifname,
+                                       "promote_secondaries",
+                                       "1");
+
+        dhcp4_client = nm_dhcp_manager_start_ip4(nm_dhcp_manager_get(),
+                                                 nm_platform_get_multi_idx(NM_PLATFORM_GET),
+                                                 global_opt.ifname,
+                                                 gl.ifindex,
+                                                 hwaddr,
+                                                 bcast_hwaddr,
+                                                 global_opt.uuid,
+                                                 RT_TABLE_MAIN,
+                                                 global_opt.priority_v4,
+                                                 !!global_opt.dhcp4_hostname,
+                                                 global_opt.dhcp4_hostname,
+                                                 global_opt.dhcp4_fqdn,
+                                                 NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP4,
+                                                 global_opt.mud_url,
+                                                 client_id,
+                                                 NM_DHCP_TIMEOUT_DEFAULT,
+                                                 NULL,
+                                                 global_opt.dhcp4_address,
+                                                 NULL,
+                                                 NULL,
+                                                 &error);
+        if (!dhcp4_client)
+            g_error("failure to start DHCP: %s", error->message);
+
+        g_signal_connect(dhcp4_client,
+                         NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
+                         G_CALLBACK(dhcp4_state_changed),
+                         NULL);
+    }
+
+    if (global_opt.slaac) {
+        NMUtilsStableType stable_type = NM_UTILS_STABLE_TYPE_UUID;
+        const char *      stable_id   = global_opt.uuid;
+        int               router_solicitation_interval;
+        int               router_solicitations;
+        guint32           default_ra_timeout;
+        int               max_addresses;
+
+        nm_platform_link_set_user_ipv6ll_enabled(NM_PLATFORM_GET, gl.ifindex, TRUE);
+
+        if (global_opt.stable_id
+            && (global_opt.stable_id[0] >= '0' && global_opt.stable_id[0] <= '9')
+            && global_opt.stable_id[1] == ' ') {
+            /* strict parsing of --stable-id, which is the numeric stable-type
+             * and the ID, joined with one space. For now, only support stable-types
+             * from 0 to 9. */
+            stable_type = (global_opt.stable_id[0] - '0');
+            stable_id   = &global_opt.stable_id[2];
+        }
+
+        nm_lndp_ndisc_get_sysctl(NM_PLATFORM_GET,
+                                 global_opt.ifname,
+                                 &max_addresses,
+                                 &router_solicitations,
+                                 &router_solicitation_interval,
+                                 &default_ra_timeout);
+
+        ndisc = nm_lndp_ndisc_new(NM_PLATFORM_GET,
+                                  gl.ifindex,
+                                  global_opt.ifname,
+                                  stable_type,
+                                  stable_id,
+                                  global_opt.addr_gen_mode,
+                                  NM_NDISC_NODE_TYPE_HOST,
+                                  max_addresses,
+                                  router_solicitations,
+                                  router_solicitation_interval,
+                                  default_ra_timeout,
+                                  NULL);
+        g_assert(ndisc);
+
+        if (iid)
+            nm_ndisc_set_iid(ndisc, *iid);
+
+        nm_platform_sysctl_ip_conf_set(NM_PLATFORM_GET,
+                                       AF_INET6,
+                                       global_opt.ifname,
+                                       "accept_ra",
+                                       "0");
+
+        g_signal_connect(NM_PLATFORM_GET,
+                         NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
+                         G_CALLBACK(ip6_address_changed),
+                         ndisc);
+        g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(ndisc_config_changed), NULL);
+        g_signal_connect(ndisc, NM_NDISC_RA_TIMEOUT_SIGNAL, G_CALLBACK(ndisc_ra_timeout), NULL);
+        nm_ndisc_start(ndisc);
+    }
+
+    sd_id = nm_sd_event_attach_default();
+
+    g_main_loop_run(gl.main_loop);
+
+    nm_clear_g_source(&gl.dad_failed_id);
+    nm_c_list_elem_free_all(&gl.dad_failed_lst_head, (GDestroyNotify) nmp_object_unref);
+
+    if (pidfile && wrote_pidfile)
+        unlink(pidfile);
+
+    _LOGI(LOGD_CORE, "exiting");
+
+    nm_clear_g_source(&sd_id);
+    nm_clear_pointer(&gl.main_loop, g_main_loop_unref);
+    return 0;
+}
+
+/*****************************************************************************/
+
+const NMDhcpClientFactory *const _nm_dhcp_manager_factories[6] = {
+    /* For nm-iface-helper there is no option to choose a DHCP plugin.
+     * It just uses the "internal" one. */
+    &_nm_dhcp_client_factory_internal,
+};
+
+/*****************************************************************************/
+/* Stub functions */
+
+#include "nm-config.h"
+#include "devices/nm-device.h"
+#include "nm-active-connection.h"
+#include "nm-dbus-manager.h"
+
+void
+nm_main_config_reload(int signal)
+{
+    _LOGI(LOGD_CORE, "reloading configuration not supported");
+}
+
+NMConfig *
+nm_config_get(void)
+{
+    return GUINT_TO_POINTER(1);
+}
+
+NMConfigData *
+nm_config_get_data_orig(NMConfig *config)
+{
+    return GUINT_TO_POINTER(1);
+}
+
+char *
+nm_config_data_get_value(const NMConfigData *  config_data,
+                         const char *          group,
+                         const char *          key,
+                         NMConfigGetValueFlags flags)
+{
+    return NULL;
+}
+
+NMConfigConfigureAndQuitType
+nm_config_get_configure_and_quit(NMConfig *config)
+{
+    return NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED;
+}
+
+NMDBusManager *
+nm_dbus_manager_get(void)
+{
+    return NULL;
+}
+
+gboolean
+nm_dbus_manager_is_stopping(NMDBusManager *self)
+{
+    return FALSE;
+}
+
+void
+_nm_dbus_manager_obj_export(NMDBusObject *obj)
+{}
+
+void
+_nm_dbus_manager_obj_unexport(NMDBusObject *obj)
+{}
+
+void
+_nm_dbus_manager_obj_notify(NMDBusObject *obj, guint n_pspecs, const GParamSpec *const *pspecs)
+{}
+
+void
+_nm_dbus_manager_obj_emit_signal(NMDBusObject *                     obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const GDBusSignalInfo *            signal_info,
+                                 GVariant *                         args)
+{}
+
+GType
+nm_device_get_type(void)
+{
+    g_return_val_if_reached(0);
+}
+
+GType
+nm_active_connection_get_type(void)
+{
+    g_return_val_if_reached(0);
+}