about summary refs log tree commit diff
path: root/src/core/systemd
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/systemd')
-rw-r--r--src/core/systemd/meson.build62
-rw-r--r--src/core/systemd/nm-default-systemd.h18
-rw-r--r--src/core/systemd/nm-sd-utils-core.c27
-rw-r--r--src/core/systemd/nm-sd-utils-core.h17
-rw-r--r--src/core/systemd/nm-sd-utils-dhcp.c41
-rw-r--r--src/core/systemd/nm-sd-utils-dhcp.h19
-rw-r--r--src/core/systemd/nm-sd.c113
-rw-r--r--src/core/systemd/nm-sd.h29
-rw-r--r--src/core/systemd/sd-adapt-core/condition.h5
-rw-r--r--src/core/systemd/sd-adapt-core/conf-parser.h3
-rw-r--r--src/core/systemd/sd-adapt-core/device-util.h3
-rw-r--r--src/core/systemd/sd-adapt-core/khash.h3
-rw-r--r--src/core/systemd/sd-adapt-core/network-util.h3
-rw-r--r--src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c21
-rw-r--r--src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h88
-rw-r--r--src/core/systemd/sd-adapt-core/sd-daemon.h3
-rw-r--r--src/core/systemd/sd-adapt-core/sd-device.h3
-rw-r--r--src/core/systemd/sd-adapt-core/udev-util.h3
-rw-r--r--src/core/systemd/src/libsystemd-network/arp-util.c140
-rw-r--r--src/core/systemd/src/libsystemd-network/arp-util.h18
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-identifier.c228
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-identifier.h62
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-internal.h69
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h84
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-network.c246
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-option.c358
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-packet.c173
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-protocol.h99
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp6-internal.h123
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h63
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp6-network.c76
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp6-option.c795
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp6-protocol.h120
-rw-r--r--src/core/systemd/src/libsystemd-network/lldp-internal.h39
-rw-r--r--src/core/systemd/src/libsystemd-network/lldp-neighbor.c794
-rw-r--r--src/core/systemd/src/libsystemd-network/lldp-neighbor.h92
-rw-r--r--src/core/systemd/src/libsystemd-network/lldp-network.c80
-rw-r--r--src/core/systemd/src/libsystemd-network/lldp-network.h6
-rw-r--r--src/core/systemd/src/libsystemd-network/network-internal.c245
-rw-r--r--src/core/systemd/src/libsystemd-network/network-internal.h31
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-dhcp-client.c2258
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c1401
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c1857
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c435
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-ipv4acd.c543
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-ipv4ll.c342
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-lldp.c500
-rw-r--r--src/core/systemd/src/libsystemd/sd-event/event-source.h220
-rw-r--r--src/core/systemd/src/libsystemd/sd-event/event-util.c101
-rw-r--r--src/core/systemd/src/libsystemd/sd-event/event-util.h13
-rw-r--r--src/core/systemd/src/libsystemd/sd-event/sd-event.c4329
-rw-r--r--src/core/systemd/src/libsystemd/sd-id128/id128-util.c218
-rw-r--r--src/core/systemd/src/libsystemd/sd-id128/id128-util.h38
-rw-r--r--src/core/systemd/src/libsystemd/sd-id128/sd-id128.c330
-rw-r--r--src/core/systemd/src/systemd/_sd-common.h102
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp-client.h224
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp-lease.h82
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp-option.h38
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp6-client.h180
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp6-lease.h55
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp6-option.h37
-rw-r--r--src/core/systemd/src/systemd/sd-event.h176
-rw-r--r--src/core/systemd/src/systemd/sd-id128.h124
-rw-r--r--src/core/systemd/src/systemd/sd-ipv4acd.h60
-rw-r--r--src/core/systemd/src/systemd/sd-ipv4ll.h62
-rw-r--r--src/core/systemd/src/systemd/sd-lldp.h194
-rw-r--r--src/core/systemd/src/systemd/sd-ndisc.h131
67 files changed, 18452 insertions, 0 deletions
diff --git a/src/core/systemd/meson.build b/src/core/systemd/meson.build
new file mode 100644
index 00000000..70f6b542
--- /dev/null
+++ b/src/core/systemd/meson.build
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+incs = include_directories(
+  'sd-adapt-core',
+  'src/libsystemd-network',
+  'src/libsystemd/sd-event',
+  'src/systemd',
+)
+
+libnm_systemd_core = static_library(
+  'nm-systemd-core',
+  sources: files(
+    'src/libsystemd-network/arp-util.c',
+    'src/libsystemd-network/dhcp-identifier.c',
+    'src/libsystemd-network/dhcp-network.c',
+    'src/libsystemd-network/dhcp-option.c',
+    'src/libsystemd-network/dhcp-packet.c',
+    'src/libsystemd-network/dhcp6-network.c',
+    'src/libsystemd-network/dhcp6-option.c',
+    'src/libsystemd-network/lldp-neighbor.c',
+    'src/libsystemd-network/lldp-network.c',
+    'src/libsystemd-network/network-internal.c',
+    'src/libsystemd-network/sd-dhcp-client.c',
+    'src/libsystemd-network/sd-dhcp-lease.c',
+    'src/libsystemd-network/sd-dhcp6-client.c',
+    'src/libsystemd-network/sd-dhcp6-lease.c',
+    'src/libsystemd-network/sd-ipv4acd.c',
+    'src/libsystemd-network/sd-ipv4ll.c',
+    'src/libsystemd-network/sd-lldp.c',
+    'src/libsystemd/sd-event/event-util.c',
+    'src/libsystemd/sd-event/sd-event.c',
+    'src/libsystemd/sd-id128/id128-util.c',
+    'src/libsystemd/sd-id128/sd-id128.c',
+    'nm-sd.c',
+    'nm-sd-utils-core.c',
+    'nm-sd-utils-dhcp.c',
+    'sd-adapt-core/nm-sd-adapt-core.c',
+  ),
+  include_directories: [
+    incs,
+    src_inc,
+  ],
+  dependencies: [
+    glib_nm_default_dep,
+    libnm_core_dep,
+    libnm_systemd_shared_dep,
+  ],
+  c_args: [
+    '-DG_LOG_DOMAIN="NetworkManager"',
+  ],
+  link_with: libc_siphash,
+)
+
+libnm_systemd_core_dep = declare_dependency(
+  include_directories: incs,
+  dependencies: [
+    glib_dep,
+    libnm_core_dep,
+    libnm_systemd_shared_dep,
+  ],
+  link_with: libnm_systemd_core,
+)
diff --git a/src/core/systemd/nm-default-systemd.h b/src/core/systemd/nm-default-systemd.h
new file mode 100644
index 00000000..1979d9c9
--- /dev/null
+++ b/src/core/systemd/nm-default-systemd.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEFAULT_SYSTEMD_H__
+#define __NM_DEFAULT_SYSTEMD_H__
+
+/*****************************************************************************/
+
+#include "nm-default-daemon.h"
+
+#undef NETWORKMANAGER_COMPILATION
+#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_SYSTEMD
+
+/*****************************************************************************/
+
+#endif /* __NM_DEFAULT_SYSTEMD_H__ */
diff --git a/src/core/systemd/nm-sd-utils-core.c b/src/core/systemd/nm-sd-utils-core.c
new file mode 100644
index 00000000..af68603e
--- /dev/null
+++ b/src/core/systemd/nm-sd-utils-core.c
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "src/core/systemd/nm-default-systemd.h"
+
+#include "nm-sd-utils-core.h"
+
+#include "nm-core-internal.h"
+
+#include "nm-sd-adapt-core.h"
+
+#include "sd-id128.h"
+
+/*****************************************************************************/
+
+NMUuid *
+nm_sd_utils_id128_get_machine(NMUuid *out_uuid)
+{
+    g_assert(out_uuid);
+
+    G_STATIC_ASSERT_EXPR(sizeof(*out_uuid) == sizeof(sd_id128_t));
+    if (sd_id128_get_machine((sd_id128_t *) out_uuid) < 0)
+        return NULL;
+    return out_uuid;
+}
diff --git a/src/core/systemd/nm-sd-utils-core.h b/src/core/systemd/nm-sd-utils-core.h
new file mode 100644
index 00000000..ccad0029
--- /dev/null
+++ b/src/core/systemd/nm-sd-utils-core.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_SD_UTILS_CORE_H__
+#define __NM_SD_UTILS_CORE_H__
+
+/*****************************************************************************/
+
+struct _NMUuid;
+
+struct _NMUuid *nm_sd_utils_id128_get_machine(struct _NMUuid *out_uuid);
+
+/*****************************************************************************/
+
+#endif /* __NM_SD_UTILS_CORE_H__ */
diff --git a/src/core/systemd/nm-sd-utils-dhcp.c b/src/core/systemd/nm-sd-utils-dhcp.c
new file mode 100644
index 00000000..b5aa315a
--- /dev/null
+++ b/src/core/systemd/nm-sd-utils-dhcp.c
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#include "src/core/systemd/nm-default-systemd.h"
+
+#include "nm-sd-utils-dhcp.h"
+
+#include "sd-adapt-core/nm-sd-adapt-core.h"
+#include "src/libsystemd-network/dhcp-lease-internal.h"
+
+int
+nm_sd_dhcp_lease_get_private_options(sd_dhcp_lease *lease, nm_sd_dhcp_option **out_options)
+{
+    struct sd_dhcp_raw_option *raw_option;
+    int                        cnt = 0;
+
+    g_return_val_if_fail(lease, -EINVAL);
+    g_return_val_if_fail(out_options, -EINVAL);
+    g_return_val_if_fail(*out_options == NULL, -EINVAL);
+
+    if (lease->private_options == NULL)
+        return -ENODATA;
+
+    LIST_FOREACH(options, raw_option, lease->private_options)
+    cnt++;
+
+    *out_options = g_new(nm_sd_dhcp_option, cnt);
+    cnt          = 0;
+
+    LIST_FOREACH(options, raw_option, lease->private_options)
+    {
+        (*out_options)[cnt].code     = raw_option->tag;
+        (*out_options)[cnt].data     = raw_option->data;
+        (*out_options)[cnt].data_len = raw_option->length;
+        cnt++;
+    }
+
+    return cnt;
+}
diff --git a/src/core/systemd/nm-sd-utils-dhcp.h b/src/core/systemd/nm-sd-utils-dhcp.h
new file mode 100644
index 00000000..5d126d5b
--- /dev/null
+++ b/src/core/systemd/nm-sd-utils-dhcp.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__
+#define __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__
+
+#include "nm-sd.h"
+
+typedef struct {
+    uint8_t code;
+    uint8_t data_len;
+    void *  data;
+} nm_sd_dhcp_option;
+
+int nm_sd_dhcp_lease_get_private_options(sd_dhcp_lease *lease, nm_sd_dhcp_option **out_options);
+
+#endif /* __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__ */
diff --git a/src/core/systemd/nm-sd.c b/src/core/systemd/nm-sd.c
new file mode 100644
index 00000000..11f3abbd
--- /dev/null
+++ b/src/core/systemd/nm-sd.c
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2014 - 2016 Red Hat, Inc.
+ */
+
+#include "src/core/systemd/nm-default-systemd.h"
+
+#include "nm-sd.h"
+
+#include "sd-event.h"
+
+/*****************************************************************************
+ * Integrating sd_event into glib. Taken and adjusted from
+ * https://www.freedesktop.org/software/systemd/man/sd_event_get_fd.html
+ *****************************************************************************/
+
+typedef struct SDEventSource {
+    GSource   source;
+    GPollFD   pollfd;
+    sd_event *event;
+} SDEventSource;
+
+static gboolean
+event_prepare(GSource *source, int *timeout_)
+{
+    return sd_event_prepare(((SDEventSource *) source)->event) > 0;
+}
+
+static gboolean
+event_check(GSource *source)
+{
+    return sd_event_wait(((SDEventSource *) source)->event, 0) > 0;
+}
+
+static gboolean
+event_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
+{
+    return sd_event_dispatch(((SDEventSource *) source)->event) > 0;
+}
+
+static void
+event_finalize(GSource *source)
+{
+    SDEventSource *s = (SDEventSource *) source;
+
+    sd_event_unref(s->event);
+}
+
+static SDEventSource *
+event_create_source(sd_event *event)
+{
+    static const GSourceFuncs event_funcs = {
+        .prepare  = event_prepare,
+        .check    = event_check,
+        .dispatch = event_dispatch,
+        .finalize = event_finalize,
+    };
+    SDEventSource *source;
+    gboolean       is_default_event = FALSE;
+    int            r;
+
+    if (!event) {
+        is_default_event = TRUE;
+        r                = sd_event_default(&event);
+        if (r < 0)
+            g_return_val_if_reached(NULL);
+    }
+
+    source = (SDEventSource *) g_source_new((GSourceFuncs *) &event_funcs, sizeof(SDEventSource));
+
+    source->event = is_default_event ? g_steal_pointer(&event) : sd_event_ref(event);
+
+    source->pollfd = (GPollFD){
+        .fd     = sd_event_get_fd(source->event),
+        .events = G_IO_IN | G_IO_HUP | G_IO_ERR,
+    };
+
+    g_source_add_poll(&source->source, &source->pollfd);
+
+    return source;
+}
+
+static guint
+event_attach(sd_event *event, GMainContext *context)
+{
+    SDEventSource *source;
+    guint          id;
+
+    source = event_create_source(event);
+
+    g_return_val_if_fail(source, 0);
+
+    id = g_source_attach((GSource *) source, context);
+    g_source_unref((GSource *) source);
+
+    nm_assert(id != 0);
+    return id;
+}
+
+guint
+nm_sd_event_attach_default(void)
+{
+    return event_attach(NULL, NULL);
+}
+
+/*****************************************************************************/
+
+/* ensure that defines in nm-sd.h correspond to the internal defines. */
+
+#include "nm-sd-adapt-core.h"
+#include "dhcp-lease-internal.h"
+
+/*****************************************************************************/
diff --git a/src/core/systemd/nm-sd.h b/src/core/systemd/nm-sd.h
new file mode 100644
index 00000000..0ea4be22
--- /dev/null
+++ b/src/core/systemd/nm-sd.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2014 - 2016 Red Hat, Inc.
+ */
+
+#ifndef __NM_SD_H__
+#define __NM_SD_H__
+
+#include "systemd/src/systemd/sd-dhcp-client.h"
+#include "systemd/src/systemd/sd-dhcp6-client.h"
+#include "systemd/src/systemd/sd-lldp.h"
+#include "systemd/src/systemd/sd-ipv4ll.h"
+
+/*****************************************************************************/
+
+guint nm_sd_event_attach_default(void);
+
+/*****************************************************************************
+ * expose internal systemd API
+ *
+ * FIXME: don't use any internal systemd API.
+ *****************************************************************************/
+
+struct sd_dhcp_lease;
+
+int dhcp_lease_save(struct sd_dhcp_lease *lease, const char *lease_file);
+int dhcp_lease_load(struct sd_dhcp_lease **ret, const char *lease_file);
+
+#endif /* __NM_SD_H__ */
diff --git a/src/core/systemd/sd-adapt-core/condition.h b/src/core/systemd/sd-adapt-core/condition.h
new file mode 100644
index 00000000..d3a6812a
--- /dev/null
+++ b/src/core/systemd/sd-adapt-core/condition.h
@@ -0,0 +1,5 @@
+#pragma once
+
+/* dummy header */
+
+typedef struct _sd_adapt_Condition Condition;
diff --git a/src/core/systemd/sd-adapt-core/conf-parser.h b/src/core/systemd/sd-adapt-core/conf-parser.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/core/systemd/sd-adapt-core/conf-parser.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/core/systemd/sd-adapt-core/device-util.h b/src/core/systemd/sd-adapt-core/device-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/core/systemd/sd-adapt-core/device-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/core/systemd/sd-adapt-core/khash.h b/src/core/systemd/sd-adapt-core/khash.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/core/systemd/sd-adapt-core/khash.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/core/systemd/sd-adapt-core/network-util.h b/src/core/systemd/sd-adapt-core/network-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/core/systemd/sd-adapt-core/network-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c
new file mode 100644
index 00000000..9339b33d
--- /dev/null
+++ b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2014 - 2016 Red Hat, Inc.
+ */
+
+#include "src/core/systemd/nm-default-systemd.h"
+
+#include "nm-sd-adapt-core.h"
+
+#include "fd-util.h"
+
+/*****************************************************************************/
+
+int
+asynchronous_close(int fd)
+{
+    safe_close(fd);
+    return -1;
+}
+
+/*****************************************************************************/
diff --git a/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h
new file mode 100644
index 00000000..f627d7d4
--- /dev/null
+++ b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2014 - 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_SD_ADAPT_CORE_H__
+#define __NM_SD_ADAPT_CORE_H__
+
+#include "src/core/systemd/nm-default-systemd.h"
+
+#include <stdbool.h>
+#include <sys/resource.h>
+#include <time.h>
+
+#undef NETWORKMANAGER_COMPILATION
+#include "systemd/sd-adapt-shared/nm-sd-adapt-shared.h"
+#undef NETWORKMANAGER_COMPILATION
+#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_SYSTEMD
+
+#ifndef HAVE_SYS_AUXV_H
+    #define HAVE_SYS_AUXV_H 0
+#endif
+
+/*****************************************************************************
+ * The remainder of the header is only enabled when building the systemd code
+ * itself.
+ *****************************************************************************/
+
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD
+
+    #include <netinet/in.h>
+    #include <string.h>
+    #include <stdio.h>
+    #include <errno.h>
+    #include <elf.h>
+    #ifdef HAVE_SYS_AUXV_H
+        #include <sys/auxv.h>
+    #endif
+    #include <unistd.h>
+    #include <sys/syscall.h>
+    #include <sys/ioctl.h>
+
+    /* Missing in Linux 3.2.0, in Ubuntu 12.04 */
+    #ifndef BPF_XOR
+        #define BPF_XOR 0xa0
+    #endif
+
+    #ifndef ETHERTYPE_LLDP
+        #define ETHERTYPE_LLDP 0x88cc
+    #endif
+
+    #ifndef HAVE_SECURE_GETENV
+        #ifdef HAVE___SECURE_GETENV
+            #define secure_getenv __secure_getenv
+        #else
+            #error neither secure_getenv nor __secure_getenv is available
+        #endif
+    #endif
+
+/*****************************************************************************/
+
+static inline int
+sd_notify(int unset_environment, const char *state)
+{
+    return 0;
+}
+
+    /* Can't include both net/if.h and linux/if.h; so have to define this here */
+    #ifndef IF_NAMESIZE
+        #define IF_NAMESIZE 16
+    #endif
+
+    #ifndef IFNAMSIZ
+        #define IFNAMSIZ IF_NAMESIZE
+    #endif
+
+    #ifndef MAX_HANDLE_SZ
+        #define MAX_HANDLE_SZ 128
+    #endif
+
+    #include "sd-id128.h"
+    #include "sparse-endian.h"
+    #include "async.h"
+    #include "util.h"
+
+#endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */
+
+#endif /* __NM_SD_ADAPT_CORE_H__ */
diff --git a/src/core/systemd/sd-adapt-core/sd-daemon.h b/src/core/systemd/sd-adapt-core/sd-daemon.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/core/systemd/sd-adapt-core/sd-daemon.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/core/systemd/sd-adapt-core/sd-device.h b/src/core/systemd/sd-adapt-core/sd-device.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/core/systemd/sd-adapt-core/sd-device.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/core/systemd/sd-adapt-core/udev-util.h b/src/core/systemd/sd-adapt-core/udev-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/core/systemd/sd-adapt-core/udev-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/core/systemd/src/libsystemd-network/arp-util.c b/src/core/systemd/src/libsystemd-network/arp-util.c
new file mode 100644
index 00000000..1c777d8a
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/arp-util.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2014 Axis Communications AB. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <arpa/inet.h>
+#include <linux/filter.h>
+#include <netinet/if_ether.h>
+
+#include "arp-util.h"
+#include "fd-util.h"
+#include "unaligned.h"
+#include "util.h"
+
+int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) {
+        struct sock_filter filter[] = {
+                BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0),                                         /* A <- packet length */
+                BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0),           /* packet >= arp packet ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                  /* ignore */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0),                       /* header == ethernet ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                  /* ignore */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0),                       /* protocol == IP ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                  /* ignore */
+                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)), /* A <- hardware address length */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0),          /* length == sizeof(ether_addr)? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                  /* ignore */
+                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)), /* A <- protocol address length */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0),             /* length == sizeof(in_addr) ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                  /* ignore */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)),  /* A <- operation */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0),                      /* protocol == request ? */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0),                        /* protocol == reply ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                  /* ignore */
+                /* Sender Hardware Address must be different from our own */
+                BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be32(&eth_mac->ether_addr_octet[0])),/* A <- 4 bytes of client's MAC */
+                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                               /* X <- A */
+                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)),       /* A <- 4 bytes of SHA */
+                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                        /* A xor X */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6),                                  /* A == 0 ? */
+                BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be16(&eth_mac->ether_addr_octet[4])),/* A <- remainder of client's MAC */
+                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                               /* X <- A */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4),   /* A <- remainder of SHA */
+                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                        /* A xor X */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),                                  /* A == 0 ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                  /* ignore */
+                /* Sender Protocol Address or Target Protocol Address must be equal to the one we care about */
+                BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)),                                  /* A <- clients IP */
+                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                               /* X <- A */
+                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)),       /* A <- SPA */
+                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                        /* X xor A */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),                                  /* A == 0 ? */
+                BPF_STMT(BPF_RET + BPF_K, 65535),                                              /* return all */
+                BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)),                                  /* A <- clients IP */
+                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                               /* X <- A */
+                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)),       /* A <- TPA */
+                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                        /* X xor A */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),                                  /* A == 0 ? */
+                BPF_STMT(BPF_RET + BPF_K, 65535),                                              /* return all */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                  /* ignore */
+        };
+        struct sock_fprog fprog = {
+                .len = ELEMENTSOF(filter),
+                .filter = (struct sock_filter*) filter
+        };
+        union sockaddr_union link = {
+                .ll.sll_family = AF_PACKET,
+                .ll.sll_protocol = htobe16(ETH_P_ARP),
+                .ll.sll_ifindex = ifindex,
+                .ll.sll_halen = ETH_ALEN,
+                .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+        };
+        _cleanup_close_ int s = -1;
+        int r;
+
+        assert(ifindex > 0);
+
+        s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+        if (s < 0)
+                return -errno;
+
+        r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+        if (r < 0)
+                return -errno;
+
+        r = bind(s, &link.sa, sizeof(link.ll));
+        if (r < 0)
+                return -errno;
+
+        return TAKE_FD(s);
+}
+
+static int arp_send_packet(int fd, int ifindex,
+                           be32_t pa, const struct ether_addr *ha,
+                           bool announce) {
+        union sockaddr_union link = {
+                .ll.sll_family = AF_PACKET,
+                .ll.sll_protocol = htobe16(ETH_P_ARP),
+                .ll.sll_ifindex = ifindex,
+                .ll.sll_halen = ETH_ALEN,
+                .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+        };
+        struct ether_arp arp = {
+                .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */
+                .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */
+                .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */
+                .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */
+                .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */
+        };
+        int r;
+
+        assert(fd >= 0);
+        assert(pa != 0);
+        assert(ha);
+
+        memcpy(&arp.arp_sha, ha, ETH_ALEN);
+        memcpy(&arp.arp_tpa, &pa, sizeof(pa));
+
+        if (announce)
+                memcpy(&arp.arp_spa, &pa, sizeof(pa));
+
+        r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll));
+        if (r < 0)
+                return -errno;
+
+        return 0;
+}
+
+int arp_send_probe(int fd, int ifindex,
+                    be32_t pa, const struct ether_addr *ha) {
+        return arp_send_packet(fd, ifindex, pa, ha, false);
+}
+
+int arp_send_announcement(int fd, int ifindex,
+                          be32_t pa, const struct ether_addr *ha) {
+        return arp_send_packet(fd, ifindex, pa, ha, true);
+}
diff --git a/src/core/systemd/src/libsystemd-network/arp-util.h b/src/core/systemd/src/libsystemd-network/arp-util.h
new file mode 100644
index 00000000..2dac8cfb
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/arp-util.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+  Copyright © 2014 Axis Communications AB. All rights reserved.
+***/
+
+#include <net/ethernet.h>
+
+#include "socket-util.h"
+#include "sparse-endian.h"
+
+int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac);
+
+int arp_send_probe(int fd, int ifindex,
+                   be32_t pa, const struct ether_addr *ha);
+int arp_send_announcement(int fd, int ifindex,
+                          be32_t pa, const struct ether_addr *ha);
diff --git a/src/core/systemd/src/libsystemd-network/dhcp-identifier.c b/src/core/systemd/src/libsystemd-network/dhcp-identifier.c
new file mode 100644
index 00000000..0720e381
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp-identifier.c
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <linux/if_infiniband.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+
+#include "sd-device.h"
+#include "sd-id128.h"
+
+#include "dhcp-identifier.h"
+#include "dhcp6-protocol.h"
+#include "network-util.h"
+#include "siphash24.h"
+#include "sparse-endian.h"
+#include "stdio-util.h"
+#include "udev-util.h"
+#include "virt.h"
+
+#define HASH_KEY       SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
+#define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03)
+#define USEC_2000       ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */
+
+int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict) {
+        struct duid d;
+
+        assert_cc(sizeof(d.raw) >= MAX_DUID_LEN);
+        if (duid_len > MAX_DUID_LEN)
+                return -EINVAL;
+
+        if (!strict)
+                /* Strict validation is not requested. We only ensure that the
+                 * DUID is not too long. */
+                return 0;
+
+        switch (duid_type) {
+        case DUID_TYPE_LLT:
+                if (duid_len <= sizeof(d.llt))
+                        return -EINVAL;
+                break;
+        case DUID_TYPE_EN:
+                if (duid_len != sizeof(d.en))
+                        return -EINVAL;
+                break;
+        case DUID_TYPE_LL:
+                if (duid_len <= sizeof(d.ll))
+                        return -EINVAL;
+                break;
+        case DUID_TYPE_UUID:
+                if (duid_len != sizeof(d.uuid))
+                        return -EINVAL;
+                break;
+        default:
+                /* accept unknown type in order to be forward compatible */
+                break;
+        }
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) {
+        uint16_t time_from_2000y;
+
+        assert(duid);
+        assert(len);
+        assert(addr);
+
+        if (arp_type == ARPHRD_ETHER)
+                assert_return(addr_len == ETH_ALEN, -EINVAL);
+        else if (arp_type == ARPHRD_INFINIBAND)
+                assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
+        else
+                return -EINVAL;
+
+        if (t < USEC_2000)
+                time_from_2000y = 0;
+        else
+                time_from_2000y = (uint16_t) (((t - USEC_2000) / USEC_PER_SEC) & 0xffffffff);
+
+        unaligned_write_be16(&duid->type, DUID_TYPE_LLT);
+        unaligned_write_be16(&duid->llt.htype, arp_type);
+        unaligned_write_be32(&duid->llt.time, time_from_2000y);
+        memcpy(duid->llt.haddr, addr, addr_len);
+
+        *len = sizeof(duid->type) + sizeof(duid->llt.htype) + sizeof(duid->llt.time) + addr_len;
+
+        return 0;
+}
+
+int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) {
+        assert(duid);
+        assert(len);
+        assert(addr);
+
+        if (arp_type == ARPHRD_ETHER)
+                assert_return(addr_len == ETH_ALEN, -EINVAL);
+        else if (arp_type == ARPHRD_INFINIBAND)
+                assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
+        else
+                return -EINVAL;
+
+        unaligned_write_be16(&duid->type, DUID_TYPE_LL);
+        unaligned_write_be16(&duid->ll.htype, arp_type);
+        memcpy(duid->ll.haddr, addr, addr_len);
+
+        *len = sizeof(duid->type) + sizeof(duid->ll.htype) + addr_len;
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) {
+        sd_id128_t machine_id;
+        uint64_t hash;
+        int r;
+
+        assert(duid);
+        assert(len);
+
+        r = sd_id128_get_machine(&machine_id);
+        if (r < 0) {
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+                machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10);
+#else
+                return r;
+#endif
+        }
+
+        unaligned_write_be16(&duid->type, DUID_TYPE_EN);
+        unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN);
+
+        *len = sizeof(duid->type) + sizeof(duid->en);
+
+        /* a bit of snake-oil perhaps, but no need to expose the machine-id
+         * directly; duid->en.id might not be aligned, so we need to copy */
+        hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes));
+        memcpy(duid->en.id, &hash, sizeof(duid->en.id));
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len) {
+        sd_id128_t machine_id;
+        int r;
+
+        assert(duid);
+        assert(len);
+
+        r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id);
+        if (r < 0)
+                return r;
+
+        unaligned_write_be16(&duid->type, DUID_TYPE_UUID);
+        memcpy(&duid->raw.data, &machine_id, sizeof(machine_id));
+
+        *len = sizeof(duid->type) + sizeof(machine_id);
+
+        return 0;
+}
+#endif
+
+int dhcp_identifier_set_iaid(
+                int ifindex,
+                const uint8_t *mac,
+                size_t mac_len,
+                bool legacy_unstable_byteorder,
+                void *_id) {
+#if 0 /* NM_IGNORED */
+        /* name is a pointer to memory in the sd_device struct, so must
+         * have the same scope */
+        _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+        const char *name = NULL;
+        uint64_t id;
+        uint32_t id32;
+
+        if (detect_container() <= 0) {
+                /* not in a container, udev will be around */
+                char ifindex_str[1 + DECIMAL_STR_MAX(int)];
+                int r;
+
+                xsprintf(ifindex_str, "n%d", ifindex);
+                if (sd_device_new_from_device_id(&device, ifindex_str) >= 0) {
+                        r = sd_device_get_is_initialized(device);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                /* not yet ready */
+                                return -EBUSY;
+
+                        r = device_is_renaming(device);
+                        if (r < 0)
+                                return r;
+                        if (r > 0)
+                                /* device is under renaming */
+                                return -EBUSY;
+
+                        name = net_get_name_persistent(device);
+                }
+        }
+
+        if (name)
+                id = siphash24(name, strlen(name), HASH_KEY.bytes);
+        else
+                /* fall back to MAC address if no predictable name available */
+                id = siphash24(mac, mac_len, HASH_KEY.bytes);
+
+        id32 = (id & 0xffffffff) ^ (id >> 32);
+
+        if (legacy_unstable_byteorder)
+                /* for historical reasons (a bug), the bits were swapped and thus
+                 * the result was endianness dependent. Preserve that behavior. */
+                id32 = __bswap_32(id32);
+        else
+                /* the fixed behavior returns a stable byte order. Since LE is expected
+                 * to be more common, swap the bytes on LE to give the same as legacy
+                 * behavior. */
+                id32 = be32toh(id32);
+
+        unaligned_write_ne32(_id, id32);
+        return 0;
+#else /* NM_IGNORED */
+        /* for NetworkManager, we don't use this function and we should never call here.
+         * This got replaced by nm_utils_create_dhcp_iaid(). */
+        g_return_val_if_reached (-EINVAL);
+#endif /* NM_IGNORED */
+}
diff --git a/src/core/systemd/src/libsystemd-network/dhcp-identifier.h b/src/core/systemd/src/libsystemd-network/dhcp-identifier.h
new file mode 100644
index 00000000..e9f2ea7e
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp-identifier.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-id128.h"
+
+#include "macro.h"
+#include "sparse-endian.h"
+#include "time-util.h"
+#include "unaligned.h"
+
+#define SYSTEMD_PEN    43793
+
+typedef enum DUIDType {
+        DUID_TYPE_LLT       = 1,
+        DUID_TYPE_EN        = 2,
+        DUID_TYPE_LL        = 3,
+        DUID_TYPE_UUID      = 4,
+        _DUID_TYPE_MAX,
+        _DUID_TYPE_INVALID  = -1,
+} DUIDType;
+
+/* RFC 3315 section 9.1:
+ *      A DUID can be no more than 128 octets long (not including the type code).
+ */
+#define MAX_DUID_LEN 128
+
+/* https://tools.ietf.org/html/rfc3315#section-9.1 */
+struct duid {
+        be16_t type;
+        union {
+                struct {
+                        /* DUID_TYPE_LLT */
+                        be16_t htype;
+                        be32_t time;
+                        uint8_t haddr[0];
+                } _packed_ llt;
+                struct {
+                        /* DUID_TYPE_EN */
+                        be32_t pen;
+                        uint8_t id[8];
+                } _packed_ en;
+                struct {
+                        /* DUID_TYPE_LL */
+                        be16_t htype;
+                        uint8_t haddr[0];
+                } _packed_ ll;
+                struct {
+                        /* DUID_TYPE_UUID */
+                        sd_id128_t uuid;
+                } _packed_ uuid;
+                struct {
+                        uint8_t data[MAX_DUID_LEN];
+                } _packed_ raw;
+        };
+} _packed_;
+
+int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict);
+int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
+int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
+int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len);
+int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len);
+int dhcp_identifier_set_iaid(int ifindex, const uint8_t *mac, size_t mac_len, bool legacy_unstable_byteorder, void *_id);
diff --git a/src/core/systemd/src/libsystemd-network/dhcp-internal.h b/src/core/systemd/src/libsystemd-network/dhcp-internal.h
new file mode 100644
index 00000000..40e6b1f2
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp-internal.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+  Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
+#include <stdint.h>
+
+#include "sd-dhcp-client.h"
+
+#include "dhcp-protocol.h"
+#include "socket-util.h"
+
+typedef struct sd_dhcp_option {
+        unsigned n_ref;
+
+        uint8_t option;
+        void *data;
+        size_t length;
+} sd_dhcp_option;
+
+typedef struct DHCPServerData {
+        struct in_addr *addr;
+        size_t size;
+} DHCPServerData;
+
+extern const struct hash_ops dhcp_option_hash_ops;
+
+int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid,
+                                 const uint8_t *mac_addr, size_t mac_addr_len,
+                                 const uint8_t *bcast_addr, size_t bcast_addr_len,
+                                 uint16_t arp_type, uint16_t port);
+int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type);
+int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link,
+                                 const void *packet, size_t len);
+int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port,
+                                 const void *packet, size_t len);
+
+int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, uint8_t overload,
+                       uint8_t code, size_t optlen, const void *optval);
+
+typedef int (*dhcp_option_callback_t)(uint8_t code, uint8_t len,
+                                const void *option, void *userdata);
+
+int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **error_message);
+
+int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid,
+                      uint8_t type, uint16_t arp_type, size_t optlen,
+                      size_t *optoffset);
+
+uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len);
+
+void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
+                                   uint16_t source, be32_t destination_addr,
+                                   uint16_t destination, uint16_t len, int ip_service_type);
+
+int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port);
+
+/* If we are invoking callbacks of a dhcp-client, ensure unreffing the
+ * client from the callback doesn't destroy the object we are working
+ * on */
+#define DHCP_CLIENT_DONT_DESTROY(client) \
+        _cleanup_(sd_dhcp_client_unrefp) _unused_ sd_dhcp_client *_dont_destroy_##client = sd_dhcp_client_ref(client)
+
+#define log_dhcp_client_errno(client, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "DHCP CLIENT (0x%x): " fmt, client->xid, ##__VA_ARGS__)
+#define log_dhcp_client(client, fmt, ...) log_dhcp_client_errno(client, 0, fmt, ##__VA_ARGS__)
diff --git a/src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h b/src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h
new file mode 100644
index 00000000..49392d1b
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+  Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include "sd-dhcp-client.h"
+
+#include "dhcp-internal.h"
+#include "dhcp-protocol.h"
+#include "list.h"
+#include "util.h"
+
+struct sd_dhcp_route {
+        struct in_addr dst_addr;
+        struct in_addr gw_addr;
+        unsigned char dst_prefixlen;
+
+        uint8_t option;
+};
+
+struct sd_dhcp_raw_option {
+        LIST_FIELDS(struct sd_dhcp_raw_option, options);
+
+        uint8_t tag;
+        uint8_t length;
+        void *data;
+};
+
+struct sd_dhcp_lease {
+        unsigned n_ref;
+
+        /* each 0 if unset */
+        uint32_t t1;
+        uint32_t t2;
+        uint32_t lifetime;
+
+        /* each 0 if unset */
+        be32_t address;
+        be32_t server_address;
+        be32_t next_server;
+
+        bool have_subnet_mask;
+        be32_t subnet_mask;
+
+        bool have_broadcast;
+        be32_t broadcast;
+
+        struct in_addr *router;
+        size_t router_size;
+
+        DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
+
+        struct sd_dhcp_route *static_route;
+        size_t static_route_size, static_route_allocated;
+
+        uint16_t mtu; /* 0 if unset */
+
+        char *domainname;
+        char **search_domains;
+        char *hostname;
+        char *root_path;
+
+        void *client_id;
+        size_t client_id_len;
+
+        void *vendor_specific;
+        size_t vendor_specific_len;
+
+        char *timezone;
+
+        LIST_HEAD(struct sd_dhcp_raw_option, private_options);
+};
+
+int dhcp_lease_new(sd_dhcp_lease **ret);
+
+int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata);
+int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains);
+int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len);
+
+int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease);
+
+int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len);
diff --git a/src/core/systemd/src/libsystemd-network/dhcp-network.c b/src/core/systemd/src/libsystemd-network/dhcp-network.c
new file mode 100644
index 00000000..4477f46f
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp-network.c
@@ -0,0 +1,246 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <errno.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <stdio.h>
+#include <string.h>
+#include <linux/filter.h>
+#include <linux/if_infiniband.h>
+#include <linux/if_packet.h>
+
+#include "dhcp-internal.h"
+#include "fd-util.h"
+#include "socket-util.h"
+#include "unaligned.h"
+
+static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
+                            uint32_t xid,
+                            const uint8_t *bcast_addr,
+                            size_t bcast_addr_len,
+                            const struct ether_addr *eth_mac,
+                            uint16_t arp_type, uint8_t dhcp_hlen,
+                            uint16_t port) {
+        struct sock_filter filter[] = {
+                BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0),                                 /* A <- packet length */
+                BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(DHCPPacket), 1, 0),         /* packet >= DHCPPacket ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.protocol)), /* A <- IP protocol */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0),                /* IP protocol == UDP ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags */
+                BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x20),                             /* A <- A & 0x20 (More Fragments bit) */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0),                          /* A == 0 ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags + Fragment offset */
+                BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x1fff),                           /* A <- A & 0x1fff (Fragment offset) */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0),                          /* A == 0 ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, udp.dest)),    /* A <- UDP destination port */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, port, 1, 0),                       /* UDP destination port == DHCP client port ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.op)),     /* A <- DHCP op */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0),                  /* op == BOOTREPLY ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.htype)),  /* A <- DHCP header type */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arp_type, 1, 0),                   /* header type == arp_type ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.xid)),    /* A <- client identifier */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, xid, 1, 0),                        /* client identifier == xid ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)),   /* A <- MAC address length */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, dhcp_hlen, 1, 0),                  /* address length == dhcp_hlen ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+
+                /* We only support MAC address length to be either 0 or 6 (ETH_ALEN). Optionally
+                 * compare chaddr for ETH_ALEN bytes. */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 12),                                  /* A (the MAC address length) == ETH_ALEN ? */
+                BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be32(&eth_mac->ether_addr_octet[0])),        /* A <- 4 bytes of client's MAC */
+                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                       /* X <- A */
+                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr)),                 /* A <- 4 bytes of MAC from dhcp.chaddr */
+                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                /* A xor X */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0),                                          /* A == 0 ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                          /* ignore */
+                BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be16(&eth_mac->ether_addr_octet[4])),        /* A <- remainder of client's MAC */
+                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                       /* X <- A */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr) + 4),             /* A <- remainder of MAC from dhcp.chaddr */
+                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                /* A xor X */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0),                                          /* A == 0 ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                          /* ignore */
+
+                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.magic)),  /* A <- DHCP magic cookie */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0),          /* cookie == DHCP magic cookie ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+                BPF_STMT(BPF_RET + BPF_K, 65535),                                      /* return all */
+        };
+        struct sock_fprog fprog = {
+                .len = ELEMENTSOF(filter),
+                .filter = filter
+        };
+        _cleanup_close_ int s = -1;
+        int r;
+
+        assert(ifindex > 0);
+        assert(link);
+
+        s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+        if (s < 0)
+                return -errno;
+
+        r = setsockopt_int(s, SOL_PACKET, PACKET_AUXDATA, true);
+        if (r < 0)
+                return r;
+
+        r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+        if (r < 0)
+                return -errno;
+
+        link->ll = (struct sockaddr_ll) {
+                .sll_family = AF_PACKET,
+                .sll_protocol = htobe16(ETH_P_IP),
+                .sll_ifindex = ifindex,
+                .sll_hatype = htobe16(arp_type),
+                .sll_halen = bcast_addr_len,
+        };
+        memcpy(link->ll.sll_addr, bcast_addr, bcast_addr_len); /* We may overflow link->ll. link->ll_buffer ensures we have enough space. */
+
+        r = bind(s, &link->sa, SOCKADDR_LL_LEN(link->ll));
+        if (r < 0)
+                return -errno;
+
+        return TAKE_FD(s);
+}
+
+int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid,
+                                 const uint8_t *mac_addr, size_t mac_addr_len,
+                                 const uint8_t *bcast_addr, size_t bcast_addr_len,
+                                 uint16_t arp_type, uint16_t port) {
+        static const uint8_t eth_bcast[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+        /* Default broadcast address for IPoIB */
+        static const uint8_t ib_bcast[] = {
+                0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0xff, 0xff, 0xff, 0xff
+        };
+        struct ether_addr eth_mac = { { 0, 0, 0, 0, 0, 0 } };
+        const uint8_t *default_bcast_addr;
+        size_t expected_bcast_addr_len;
+        uint8_t dhcp_hlen = 0;
+
+        if (arp_type == ARPHRD_ETHER) {
+                assert_return(mac_addr_len == ETH_ALEN, -EINVAL);
+                memcpy(&eth_mac, mac_addr, ETH_ALEN);
+                dhcp_hlen = ETH_ALEN;
+
+                default_bcast_addr = eth_bcast;
+                expected_bcast_addr_len = ETH_ALEN;
+        } else if (arp_type == ARPHRD_INFINIBAND) {
+                default_bcast_addr = ib_bcast;
+                expected_bcast_addr_len = INFINIBAND_ALEN;
+        } else
+                return -EINVAL;
+
+        if (bcast_addr && bcast_addr_len > 0)
+                assert_return(bcast_addr_len == expected_bcast_addr_len, -EINVAL);
+        else {
+                bcast_addr = default_bcast_addr;
+                bcast_addr_len = expected_bcast_addr_len;
+        }
+
+        return _bind_raw_socket(ifindex, link, xid, bcast_addr, bcast_addr_len,
+                                &eth_mac, arp_type, dhcp_hlen, port);
+}
+
+int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) {
+        union sockaddr_union src = {
+                .in.sin_family = AF_INET,
+                .in.sin_port = htobe16(port),
+                .in.sin_addr.s_addr = address,
+        };
+        _cleanup_close_ int s = -1;
+        int r;
+
+        s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+        if (s < 0)
+                return -errno;
+
+        if (ip_service_type >= 0)
+                r = setsockopt_int(s, IPPROTO_IP, IP_TOS, ip_service_type);
+        else
+                r = setsockopt_int(s, IPPROTO_IP, IP_TOS, IPTOS_CLASS_CS6);
+
+        if (r < 0)
+                return r;
+
+        r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
+        if (r < 0)
+                return r;
+
+        if (ifindex > 0) {
+                r = socket_bind_to_ifindex(s, ifindex);
+                if (r < 0)
+                        return r;
+        }
+
+        if (address == INADDR_ANY) {
+                r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true);
+                if (r < 0)
+                        return r;
+
+                r = setsockopt_int(s, SOL_SOCKET, SO_BROADCAST, true);
+                if (r < 0)
+                        return r;
+
+        } else {
+                r = setsockopt_int(s, IPPROTO_IP, IP_FREEBIND, true);
+                if (r < 0)
+                        return r;
+        }
+
+        r = bind(s, &src.sa, sizeof(src.in));
+        if (r < 0)
+                return -errno;
+
+        return TAKE_FD(s);
+}
+
+int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link,
+                                 const void *packet, size_t len) {
+        int r;
+
+        assert(link);
+        assert(packet);
+        assert(len);
+
+        r = sendto(s, packet, len, 0, &link->sa, SOCKADDR_LL_LEN(link->ll));
+        if (r < 0)
+                return -errno;
+
+        return 0;
+}
+
+int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port,
+                                 const void *packet, size_t len) {
+        union sockaddr_union dest = {
+                .in.sin_family = AF_INET,
+                .in.sin_port = htobe16(port),
+                .in.sin_addr.s_addr = address,
+        };
+        int r;
+
+        assert(s >= 0);
+        assert(packet);
+        assert(len);
+
+        r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in));
+        if (r < 0)
+                return -errno;
+
+        return 0;
+}
diff --git a/src/core/systemd/src/libsystemd-network/dhcp-option.c b/src/core/systemd/src/libsystemd-network/dhcp-option.c
new file mode 100644
index 00000000..2c15d85a
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp-option.c
@@ -0,0 +1,358 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "alloc-util.h"
+#include "dhcp-internal.h"
+#include "dhcp-server-internal.h"
+#include "memory-util.h"
+#include "strv.h"
+#include "utf8.h"
+
+static int option_append(uint8_t options[], size_t size, size_t *offset,
+                         uint8_t code, size_t optlen, const void *optval) {
+        assert(options);
+        assert(offset);
+
+        if (code != SD_DHCP_OPTION_END)
+                /* always make sure there is space for an END option */
+                size--;
+
+        switch (code) {
+
+        case SD_DHCP_OPTION_PAD:
+        case SD_DHCP_OPTION_END:
+                if (*offset + 1 > size)
+                        return -ENOBUFS;
+
+                options[*offset] = code;
+                *offset += 1;
+                break;
+
+        case SD_DHCP_OPTION_USER_CLASS: {
+                size_t total = 0;
+                char **s;
+
+                STRV_FOREACH(s, (char **) optval) {
+                        size_t len = strlen(*s);
+
+                        if (len > 255)
+                                return -ENAMETOOLONG;
+
+                        total += 1 + len;
+                }
+
+                if (*offset + 2 + total > size)
+                        return -ENOBUFS;
+
+                options[*offset] = code;
+                options[*offset + 1] =  total;
+                *offset += 2;
+
+                STRV_FOREACH(s, (char **) optval) {
+                        size_t len = strlen(*s);
+
+                        options[*offset] = len;
+
+                        memcpy(&options[*offset + 1], *s, len);
+                        *offset += 1 + len;
+                }
+
+                break;
+        }
+        case SD_DHCP_OPTION_SIP_SERVER:
+                if (*offset + 3 + optlen > size)
+                        return -ENOBUFS;
+
+                options[*offset] = code;
+                options[*offset + 1] = optlen + 1;
+                options[*offset + 2] = 1;
+
+                memcpy_safe(&options[*offset + 3], optval, optlen);
+                *offset += 3 + optlen;
+
+                break;
+        case SD_DHCP_OPTION_VENDOR_SPECIFIC: {
+                OrderedHashmap *s = (OrderedHashmap *) optval;
+                struct sd_dhcp_option *p;
+                size_t l = 0;
+
+                ORDERED_HASHMAP_FOREACH(p, s)
+                        l += p->length + 2;
+
+                if (*offset + l + 2 > size)
+                        return -ENOBUFS;
+
+                options[*offset] = code;
+                options[*offset + 1] = l;
+
+                *offset += 2;
+
+                ORDERED_HASHMAP_FOREACH(p, s) {
+                        options[*offset] = p->option;
+                        options[*offset + 1] = p->length;
+                        memcpy(&options[*offset + 2], p->data, p->length);
+                        *offset += 2 + p->length;
+                }
+
+                break;
+        }
+        default:
+                if (*offset + 2 + optlen > size)
+                        return -ENOBUFS;
+
+                options[*offset] = code;
+                options[*offset + 1] = optlen;
+
+                memcpy_safe(&options[*offset + 2], optval, optlen);
+                *offset += 2 + optlen;
+
+                break;
+        }
+
+        return 0;
+}
+
+int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
+                       uint8_t overload,
+                       uint8_t code, size_t optlen, const void *optval) {
+        const bool use_file = overload & DHCP_OVERLOAD_FILE;
+        const bool use_sname = overload & DHCP_OVERLOAD_SNAME;
+        int r;
+
+        assert(message);
+        assert(offset);
+
+        /* If *offset is in range [0, size), we are writing to ->options,
+         * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file,
+         * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname))
+         * and use_sname, we are writing to ->sname.
+         */
+
+        if (*offset < size) {
+                /* still space in the options array */
+                r = option_append(message->options, size, offset, code, optlen, optval);
+                if (r >= 0)
+                        return 0;
+                else if (r == -ENOBUFS && (use_file || use_sname)) {
+                        /* did not fit, but we have more buffers to try
+                           close the options array and move the offset to its end */
+                        r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
+                        if (r < 0)
+                                return r;
+
+                        *offset = size;
+                } else
+                        return r;
+        }
+
+        if (use_file) {
+                size_t file_offset = *offset - size;
+
+                if (file_offset < sizeof(message->file)) {
+                        /* still space in the 'file' array */
+                        r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval);
+                        if (r >= 0) {
+                                *offset = size + file_offset;
+                                return 0;
+                        } else if (r == -ENOBUFS && use_sname) {
+                                /* did not fit, but we have more buffers to try
+                                   close the file array and move the offset to its end */
+                                r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
+                                if (r < 0)
+                                        return r;
+
+                                *offset = size + sizeof(message->file);
+                        } else
+                                return r;
+                }
+        }
+
+        if (use_sname) {
+                size_t sname_offset = *offset - size - use_file*sizeof(message->file);
+
+                if (sname_offset < sizeof(message->sname)) {
+                        /* still space in the 'sname' array */
+                        r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval);
+                        if (r >= 0) {
+                                *offset = size + use_file*sizeof(message->file) + sname_offset;
+                                return 0;
+                        } else
+                                /* no space, or other error, give up */
+                                return r;
+                }
+        }
+
+        return -ENOBUFS;
+}
+
+static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
+                         uint8_t *message_type, char **error_message, dhcp_option_callback_t cb,
+                         void *userdata) {
+        uint8_t code, len;
+        const uint8_t *option;
+        size_t offset = 0;
+
+        while (offset < buflen) {
+                code = options[offset ++];
+
+                switch (code) {
+                case SD_DHCP_OPTION_PAD:
+                        continue;
+
+                case SD_DHCP_OPTION_END:
+                        return 0;
+                }
+
+                if (buflen < offset + 1)
+                        return -ENOBUFS;
+
+                len = options[offset ++];
+
+                if (buflen < offset + len)
+                        return -EINVAL;
+
+                option = &options[offset];
+
+                switch (code) {
+                case SD_DHCP_OPTION_MESSAGE_TYPE:
+                        if (len != 1)
+                                return -EINVAL;
+
+                        if (message_type)
+                                *message_type = *option;
+
+                        break;
+
+                case SD_DHCP_OPTION_ERROR_MESSAGE:
+                        if (len == 0)
+                                return -EINVAL;
+
+                        if (error_message) {
+                                _cleanup_free_ char *string = NULL;
+
+                                /* Accept a trailing NUL byte */
+                                if (memchr(option, 0, len - 1))
+                                        return -EINVAL;
+
+                                string = memdup_suffix0((const char *) option, len);
+                                if (!string)
+                                        return -ENOMEM;
+
+                                if (!ascii_is_valid(string))
+                                        return -EINVAL;
+
+                                free_and_replace(*error_message, string);
+                        }
+
+                        break;
+                case SD_DHCP_OPTION_OVERLOAD:
+                        if (len != 1)
+                                return -EINVAL;
+
+                        if (overload)
+                                *overload = *option;
+
+                        break;
+
+                default:
+                        if (cb)
+                                cb(code, len, option, userdata);
+
+                        break;
+                }
+
+                offset += len;
+        }
+
+        if (offset < buflen)
+                return -EINVAL;
+
+        return 0;
+}
+
+int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) {
+        _cleanup_free_ char *error_message = NULL;
+        uint8_t overload = 0;
+        uint8_t message_type = 0;
+        int r;
+
+        if (!message)
+                return -EINVAL;
+
+        if (len < sizeof(DHCPMessage))
+                return -EINVAL;
+
+        len -= sizeof(DHCPMessage);
+
+        r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
+        if (r < 0)
+                return r;
+
+        if (overload & DHCP_OVERLOAD_FILE) {
+                r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
+                if (r < 0)
+                        return r;
+        }
+
+        if (overload & DHCP_OVERLOAD_SNAME) {
+                r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
+                if (r < 0)
+                        return r;
+        }
+
+        if (message_type == 0)
+                return -ENOMSG;
+
+        if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE))
+                *_error_message = TAKE_PTR(error_message);
+
+        return message_type;
+}
+
+static sd_dhcp_option* dhcp_option_free(sd_dhcp_option *i) {
+        if (!i)
+                return NULL;
+
+        free(i->data);
+        return mfree(i);
+}
+
+int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret) {
+        assert_return(ret, -EINVAL);
+        assert_return(length == 0 || data, -EINVAL);
+
+        _cleanup_free_ void *q = memdup(data, length);
+        if (!q)
+                return -ENOMEM;
+
+        sd_dhcp_option *p = new(sd_dhcp_option, 1);
+        if (!p)
+                return -ENOMEM;
+
+        *p = (sd_dhcp_option) {
+                .n_ref = 1,
+                .option = option,
+                .length = length,
+                .data = TAKE_PTR(q),
+        };
+
+        *ret = TAKE_PTR(p);
+        return 0;
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option, sd_dhcp_option, dhcp_option_free);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+                dhcp_option_hash_ops,
+                void,
+                trivial_hash_func,
+                trivial_compare_func,
+                sd_dhcp_option,
+                sd_dhcp_option_unref);
diff --git a/src/core/systemd/src/libsystemd-network/dhcp-packet.c b/src/core/systemd/src/libsystemd-network/dhcp-packet.c
new file mode 100644
index 00000000..eeadd562
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp-packet.c
@@ -0,0 +1,173 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <errno.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+#include <string.h>
+
+#include "dhcp-internal.h"
+#include "dhcp-protocol.h"
+
+#define DHCP_CLIENT_MIN_OPTIONS_SIZE            312
+
+int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid,
+                      uint8_t type, uint16_t arp_type, size_t optlen,
+                      size_t *optoffset) {
+        size_t offset = 0;
+        int r;
+
+        assert(IN_SET(op, BOOTREQUEST, BOOTREPLY));
+        assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND));
+
+        message->op = op;
+        message->htype = arp_type;
+        message->hlen = (arp_type == ARPHRD_ETHER) ? ETHER_ADDR_LEN : 0;
+        message->xid = htobe32(xid);
+        message->magic = htobe32(DHCP_MAGIC_COOKIE);
+
+        r = dhcp_option_append(message, optlen, &offset, 0,
+                               SD_DHCP_OPTION_MESSAGE_TYPE, 1, &type);
+        if (r < 0)
+                return r;
+
+        *optoffset = offset;
+
+        return 0;
+}
+
+uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) {
+        uint64_t *buf_64 = (uint64_t*)buf;
+        uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t));
+        uint64_t sum = 0;
+
+        /* See RFC1071 */
+
+        while (buf_64 < end_64) {
+                sum += *buf_64;
+                if (sum < *buf_64)
+                        /* wrap around in one's complement */
+                        sum++;
+
+                buf_64++;
+        }
+
+        if (len % sizeof(uint64_t)) {
+                /* If the buffer is not aligned to 64-bit, we need
+                   to zero-pad the last few bytes and add them in */
+                uint64_t buf_tail = 0;
+
+                memcpy(&buf_tail, buf_64, len % sizeof(uint64_t));
+
+                sum += buf_tail;
+                if (sum < buf_tail)
+                        /* wrap around */
+                        sum++;
+        }
+
+        while (sum >> 16)
+                sum = (sum & 0xffff) + (sum >> 16);
+
+        return ~sum;
+}
+
+void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
+                                   uint16_t source_port, be32_t destination_addr,
+                                   uint16_t destination_port, uint16_t len, int ip_service_type) {
+        packet->ip.version = IPVERSION;
+        packet->ip.ihl = DHCP_IP_SIZE / 4;
+        packet->ip.tot_len = htobe16(len);
+
+        if (ip_service_type >= 0)
+                packet->ip.tos = ip_service_type;
+        else
+                packet->ip.tos = IPTOS_CLASS_CS6;
+
+        packet->ip.protocol = IPPROTO_UDP;
+        packet->ip.saddr = source_addr;
+        packet->ip.daddr = destination_addr;
+
+        packet->udp.source = htobe16(source_port);
+        packet->udp.dest = htobe16(destination_port);
+
+        packet->udp.len = htobe16(len - DHCP_IP_SIZE);
+
+        packet->ip.check = packet->udp.len;
+        packet->udp.check = dhcp_packet_checksum((uint8_t*)&packet->ip.ttl, len - 8);
+
+        packet->ip.ttl = IPDEFTTL;
+        packet->ip.check = 0;
+        packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE);
+}
+
+int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) {
+        size_t hdrlen;
+
+        assert(packet);
+
+        /* IP */
+
+        if (packet->ip.version != IPVERSION)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: not IPv4");
+
+        if (packet->ip.ihl < 5)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: IPv4 IHL (%u words) invalid",
+                                       packet->ip.ihl);
+
+        hdrlen = packet->ip.ihl * 4;
+        if (hdrlen < 20)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: IPv4 IHL (%zu bytes) "
+                                       "smaller than minimum (20 bytes)",
+                                       hdrlen);
+
+        if (len < hdrlen)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: packet (%zu bytes) "
+                                       "smaller than expected (%zu) by IP header",
+                                       len, hdrlen);
+
+        /* UDP */
+
+        if (packet->ip.protocol != IPPROTO_UDP)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: not UDP");
+
+        if (len < hdrlen + be16toh(packet->udp.len))
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: packet (%zu bytes) "
+                                       "smaller than expected (%zu) by UDP header",
+                                       len, hdrlen + be16toh(packet->udp.len));
+
+        if (be16toh(packet->udp.dest) != port)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: to port %u, which "
+                                       "is not the DHCP client port (%u)",
+                                       be16toh(packet->udp.dest), port);
+
+        /* checksums - computing these is relatively expensive, so only do it
+           if all the other checks have passed
+         */
+
+        if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen))
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: invalid IP checksum");
+
+        if (checksum && packet->udp.check) {
+                packet->ip.check = packet->udp.len;
+                packet->ip.ttl = 0;
+
+                if (dhcp_packet_checksum((uint8_t*)&packet->ip.ttl,
+                                  be16toh(packet->udp.len) + 12))
+                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "ignoring packet: invalid UDP checksum");
+        }
+
+        return 0;
+}
diff --git a/src/core/systemd/src/libsystemd-network/dhcp-protocol.h b/src/core/systemd/src/libsystemd-network/dhcp-protocol.h
new file mode 100644
index 00000000..11f4201a
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp-protocol.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+  Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <stdint.h>
+
+#include "macro.h"
+#include "sparse-endian.h"
+
+struct DHCPMessage {
+        uint8_t op;
+        uint8_t htype;
+        uint8_t hlen;
+        uint8_t hops;
+        be32_t xid;
+        be16_t secs;
+        be16_t flags;
+        be32_t ciaddr;
+        be32_t yiaddr;
+        be32_t siaddr;
+        be32_t giaddr;
+        uint8_t chaddr[16];
+        uint8_t sname[64];
+        uint8_t file[128];
+        be32_t magic;
+        uint8_t options[0];
+} _packed_;
+
+typedef struct DHCPMessage DHCPMessage;
+
+struct DHCPPacket {
+        struct iphdr ip;
+        struct udphdr udp;
+        DHCPMessage dhcp;
+} _packed_;
+
+typedef struct DHCPPacket DHCPPacket;
+
+#define DHCP_IP_SIZE            (int32_t)(sizeof(struct iphdr))
+#define DHCP_IP_UDP_SIZE        (int32_t)(sizeof(struct udphdr) + DHCP_IP_SIZE)
+#define DHCP_MESSAGE_SIZE       (int32_t)(sizeof(DHCPMessage))
+#define DHCP_DEFAULT_MIN_SIZE   576 /* the minimum internet hosts must be able to receive */
+#define DHCP_MIN_OPTIONS_SIZE   (DHCP_DEFAULT_MIN_SIZE - DHCP_IP_UDP_SIZE - DHCP_MESSAGE_SIZE)
+#define DHCP_MAGIC_COOKIE       (uint32_t)(0x63825363)
+
+enum {
+        DHCP_PORT_SERVER                        = 67,
+        DHCP_PORT_CLIENT                        = 68,
+};
+
+enum DHCPState {
+        DHCP_STATE_INIT                         = 0,
+        DHCP_STATE_SELECTING                    = 1,
+        DHCP_STATE_INIT_REBOOT                  = 2,
+        DHCP_STATE_REBOOTING                    = 3,
+        DHCP_STATE_REQUESTING                   = 4,
+        DHCP_STATE_BOUND                        = 5,
+        DHCP_STATE_RENEWING                     = 6,
+        DHCP_STATE_REBINDING                    = 7,
+        DHCP_STATE_STOPPED                      = 8,
+};
+
+typedef enum DHCPState DHCPState;
+
+enum {
+        BOOTREQUEST                             = 1,
+        BOOTREPLY                               = 2,
+};
+
+enum {
+        DHCP_DISCOVER                           = 1,
+        DHCP_OFFER                              = 2,
+        DHCP_REQUEST                            = 3,
+        DHCP_DECLINE                            = 4,
+        DHCP_ACK                                = 5,
+        DHCP_NAK                                = 6,
+        DHCP_RELEASE                            = 7,
+        DHCP_INFORM                             = 8,
+        DHCP_FORCERENEW                         = 9,
+};
+
+enum {
+        DHCP_OVERLOAD_FILE                      = 1,
+        DHCP_OVERLOAD_SNAME                     = 2,
+};
+
+#define DHCP_MAX_FQDN_LENGTH 255
+
+enum {
+        DHCP_FQDN_FLAG_S = (1 << 0),
+        DHCP_FQDN_FLAG_O = (1 << 1),
+        DHCP_FQDN_FLAG_E = (1 << 2),
+        DHCP_FQDN_FLAG_N = (1 << 3),
+};
diff --git a/src/core/systemd/src/libsystemd-network/dhcp6-internal.h b/src/core/systemd/src/libsystemd-network/dhcp6-internal.h
new file mode 100644
index 00000000..24d8a314
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp6-internal.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include <net/ethernet.h>
+#include <netinet/in.h>
+
+#include "sd-event.h"
+
+#include "list.h"
+#include "hashmap.h"
+#include "macro.h"
+#include "sparse-endian.h"
+
+typedef struct sd_dhcp6_option {
+        unsigned n_ref;
+
+        uint32_t enterprise_identifier;
+        uint16_t option;
+        void *data;
+        size_t length;
+} sd_dhcp6_option;
+
+extern const struct hash_ops dhcp6_option_hash_ops;
+
+/* Common option header */
+typedef struct DHCP6Option {
+        be16_t code;
+        be16_t len;
+        uint8_t data[];
+} _packed_ DHCP6Option;
+
+/* Address option */
+struct iaaddr {
+        struct in6_addr address;
+        be32_t lifetime_preferred;
+        be32_t lifetime_valid;
+} _packed_;
+
+/* Prefix Delegation Prefix option */
+struct iapdprefix {
+        be32_t lifetime_preferred;
+        be32_t lifetime_valid;
+        uint8_t prefixlen;
+        struct in6_addr address;
+} _packed_;
+
+typedef struct DHCP6Address DHCP6Address;
+
+struct DHCP6Address {
+        LIST_FIELDS(DHCP6Address, addresses);
+
+        union {
+                struct iaaddr iaaddr;
+                struct iapdprefix iapdprefix;
+        };
+};
+
+/* Non-temporary Address option */
+struct ia_na {
+        be32_t id;
+        be32_t lifetime_t1;
+        be32_t lifetime_t2;
+} _packed_;
+
+/* Prefix Delegation option */
+struct ia_pd {
+        be32_t id;
+        be32_t lifetime_t1;
+        be32_t lifetime_t2;
+} _packed_;
+
+/* Temporary Address option */
+struct ia_ta {
+        be32_t id;
+} _packed_;
+
+struct DHCP6IA {
+        uint16_t type;
+        union {
+                struct ia_na ia_na;
+                struct ia_pd ia_pd;
+                struct ia_ta ia_ta;
+        };
+
+        LIST_HEAD(DHCP6Address, addresses);
+};
+
+typedef struct DHCP6IA DHCP6IA;
+
+#define log_dhcp6_client_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__)
+#define log_dhcp6_client(p, fmt, ...) log_dhcp6_client_errno(p, 0, fmt, ##__VA_ARGS__)
+
+int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
+                        size_t optlen, const void *optval);
+int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia);
+int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix);
+int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn);
+int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char **user_class);
+int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char **user_class);
+int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options);
+int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
+                       size_t *optlen, uint8_t **optvalue);
+int dhcp6_option_parse_status(DHCP6Option *option, size_t len);
+int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code);
+int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
+                                struct in6_addr **addrs, size_t count,
+                                size_t *allocated);
+int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen,
+                                       char ***str_arr);
+int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str);
+
+int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address);
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
+                                  const void *packet, size_t len);
+
+const char *dhcp6_message_type_to_string(int s) _const_;
+int dhcp6_message_type_from_string(const char *s) _pure_;
+const char *dhcp6_message_status_to_string(int s) _const_;
+int dhcp6_message_status_from_string(const char *s) _pure_;
diff --git a/src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h b/src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h
new file mode 100644
index 00000000..e9e2362d
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include <stdint.h>
+
+#include "sd-dhcp6-lease.h"
+
+#include "dhcp6-internal.h"
+
+struct sd_dhcp6_lease {
+        unsigned n_ref;
+
+        uint8_t *serverid;
+        size_t serverid_len;
+        uint8_t preference;
+        bool rapid_commit;
+
+        DHCP6IA ia;
+        DHCP6IA pd;
+
+        DHCP6Address *addr_iter;
+        DHCP6Address *prefix_iter;
+
+        struct in6_addr *dns;
+        size_t dns_count;
+        size_t dns_allocated;
+        char **domains;
+        size_t domains_count;
+        struct in6_addr *ntp;
+        size_t ntp_count;
+        size_t ntp_allocated;
+        char **ntp_fqdn;
+        size_t ntp_fqdn_count;
+        char *fqdn;
+};
+
+int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire);
+DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia);
+
+int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id,
+                             size_t len);
+int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len);
+int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference);
+int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference);
+int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease);
+int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit);
+
+int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid);
+int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid);
+
+int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen);
+int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval,
+                            size_t optlen);
+int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen);
+int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval,
+                         size_t optlen) ;
+int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
+
+int dhcp6_lease_new(sd_dhcp6_lease **ret);
diff --git a/src/core/systemd/src/libsystemd-network/dhcp6-network.c b/src/core/systemd/src/libsystemd-network/dhcp6-network.c
new file mode 100644
index 00000000..b6816de4
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp6-network.c
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <errno.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <linux/if_packet.h>
+
+#include "dhcp6-internal.h"
+#include "dhcp6-protocol.h"
+#include "fd-util.h"
+#include "socket-util.h"
+
+int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) {
+        union sockaddr_union src = {
+                .in6.sin6_family = AF_INET6,
+                .in6.sin6_port = htobe16(DHCP6_PORT_CLIENT),
+                .in6.sin6_scope_id = ifindex,
+        };
+        _cleanup_close_ int s = -1;
+        int r;
+
+        assert(ifindex > 0);
+        assert(local_address);
+
+        src.in6.sin6_addr = *local_address;
+
+        s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_UDP);
+        if (s < 0)
+                return -errno;
+
+        r = setsockopt_int(s, IPPROTO_IPV6, IPV6_V6ONLY, true);
+        if (r < 0)
+                return r;
+
+        r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, false);
+        if (r < 0)
+                return r;
+
+        r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
+        if (r < 0)
+                return r;
+
+        r = bind(s, &src.sa, sizeof(src.in6));
+        if (r < 0)
+                return -errno;
+
+        return TAKE_FD(s);
+}
+
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
+                                  const void *packet, size_t len) {
+        union sockaddr_union dest = {
+                .in6.sin6_family = AF_INET6,
+                .in6.sin6_port = htobe16(DHCP6_PORT_SERVER),
+        };
+        int r;
+
+        assert(server_address);
+
+        memcpy(&dest.in6.sin6_addr, server_address, sizeof(dest.in6.sin6_addr));
+
+        r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6));
+        if (r < 0)
+                return -errno;
+
+        return 0;
+}
diff --git a/src/core/systemd/src/libsystemd-network/dhcp6-option.c b/src/core/systemd/src/libsystemd-network/dhcp6-option.c
new file mode 100644
index 00000000..21e2d3f0
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp6-option.c
@@ -0,0 +1,795 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <errno.h>
+#include <netinet/in.h>
+
+#include "sd-dhcp6-client.h"
+
+#include "alloc-util.h"
+#include "dhcp-identifier.h"
+#include "dhcp6-internal.h"
+#include "dhcp6-lease-internal.h"
+#include "dhcp6-protocol.h"
+#include "dns-domain.h"
+#include "memory-util.h"
+#include "sparse-endian.h"
+#include "strv.h"
+#include "unaligned.h"
+
+typedef struct DHCP6StatusOption {
+        struct DHCP6Option option;
+        be16_t status;
+        char msg[];
+} _packed_ DHCP6StatusOption;
+
+typedef struct DHCP6AddressOption {
+        struct DHCP6Option option;
+        struct iaaddr iaaddr;
+        uint8_t options[];
+} _packed_ DHCP6AddressOption;
+
+typedef struct DHCP6PDPrefixOption {
+        struct DHCP6Option option;
+        struct iapdprefix iapdprefix;
+        uint8_t options[];
+} _packed_ DHCP6PDPrefixOption;
+
+#define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na))
+#define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd))
+#define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta))
+
+static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
+                             size_t optlen) {
+        DHCP6Option *option = (DHCP6Option*) *buf;
+
+        assert_return(buf, -EINVAL);
+        assert_return(*buf, -EINVAL);
+        assert_return(buflen, -EINVAL);
+
+        if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data))
+                return -ENOBUFS;
+
+        option->code = htobe16(optcode);
+        option->len = htobe16(optlen);
+
+        *buf += offsetof(DHCP6Option, data);
+        *buflen -= offsetof(DHCP6Option, data);
+
+        return 0;
+}
+
+int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
+                        size_t optlen, const void *optval) {
+        int r;
+
+        assert_return(optval || optlen == 0, -EINVAL);
+
+        r = option_append_hdr(buf, buflen, code, optlen);
+        if (r < 0)
+                return r;
+
+        memcpy_safe(*buf, optval, optlen);
+
+        *buf += optlen;
+        *buflen -= optlen;
+
+        return 0;
+}
+
+int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) {
+        sd_dhcp6_option *options;
+        int r;
+
+        assert(buf);
+        assert(*buf);
+        assert(buflen);
+        assert(vendor_options);
+
+        ORDERED_HASHMAP_FOREACH(options, vendor_options) {
+                _cleanup_free_ uint8_t *p = NULL;
+                size_t total;
+
+                total = 4 + 2 + 2 + options->length;
+
+                p = malloc(total);
+                if (!p)
+                        return -ENOMEM;
+
+                unaligned_write_be32(p, options->enterprise_identifier);
+                unaligned_write_be16(p + 4, options->option);
+                unaligned_write_be16(p + 6, options->length);
+                memcpy(p + 8, options->data, options->length);
+
+                r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_OPTS, total, p);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
+        uint16_t len;
+        uint8_t *ia_hdr;
+        size_t iaid_offset, ia_buflen, ia_addrlen = 0;
+        DHCP6Address *addr;
+        int r;
+
+        assert_return(buf, -EINVAL);
+        assert_return(*buf, -EINVAL);
+        assert_return(buflen, -EINVAL);
+        assert_return(ia, -EINVAL);
+
+        switch (ia->type) {
+        case SD_DHCP6_OPTION_IA_NA:
+                len = DHCP6_OPTION_IA_NA_LEN;
+                iaid_offset = offsetof(DHCP6IA, ia_na);
+                break;
+
+        case SD_DHCP6_OPTION_IA_TA:
+                len = DHCP6_OPTION_IA_TA_LEN;
+                iaid_offset = offsetof(DHCP6IA, ia_ta);
+                break;
+
+        default:
+                return -EINVAL;
+        }
+
+        if (*buflen < offsetof(DHCP6Option, data) + len)
+                return -ENOBUFS;
+
+        ia_hdr = *buf;
+        ia_buflen = *buflen;
+
+        *buf += offsetof(DHCP6Option, data);
+        *buflen -= offsetof(DHCP6Option, data);
+
+        memcpy(*buf, (char*) ia + iaid_offset, len);
+
+        *buf += len;
+        *buflen -= len;
+
+        LIST_FOREACH(addresses, addr, ia->addresses) {
+                r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR,
+                                      sizeof(addr->iaaddr));
+                if (r < 0)
+                        return r;
+
+                memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
+
+                *buf += sizeof(addr->iaaddr);
+                *buflen -= sizeof(addr->iaaddr);
+
+                ia_addrlen += offsetof(DHCP6Option, data) + sizeof(addr->iaaddr);
+        }
+
+        r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
+        uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX];
+        int r;
+
+        assert_return(buf && *buf && buflen && fqdn, -EINVAL);
+
+        buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
+
+        /* Store domain name after flags field */
+        r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1,  false);
+        if (r <= 0)
+                return r;
+
+        /*
+         * According to RFC 4704, chapter 4.2 only add terminating zero-length
+         * label in case a FQDN is provided. Since dns_name_to_wire_format
+         * always adds terminating zero-length label remove if only a hostname
+         * is provided.
+         */
+        if (dns_name_is_single_label(fqdn))
+                r--;
+
+        r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer);
+
+        return r;
+}
+
+int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char **user_class) {
+        _cleanup_free_ uint8_t *p = NULL;
+        size_t total = 0, offset = 0;
+        char **s;
+
+        assert_return(buf && *buf && buflen && user_class, -EINVAL);
+
+        STRV_FOREACH(s, user_class) {
+                size_t len = strlen(*s);
+                uint8_t *q;
+
+                if (len > 0xffff)
+                        return -ENAMETOOLONG;
+                q = realloc(p, total + len + 2);
+                if (!q)
+                        return -ENOMEM;
+
+                p = q;
+
+                unaligned_write_be16(&p[offset], len);
+                memcpy(&p[offset + 2], *s, len);
+
+                offset += 2 + len;
+                total += 2 + len;
+        }
+
+        return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_USER_CLASS, total, p);
+}
+
+int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char **vendor_class) {
+        _cleanup_free_ uint8_t *p = NULL;
+        uint32_t enterprise_identifier;
+        size_t total, offset;
+        char **s;
+
+        assert(buf);
+        assert(*buf);
+        assert(buflen);
+        assert(vendor_class);
+
+        enterprise_identifier = htobe32(SYSTEMD_PEN);
+
+        p = memdup(&enterprise_identifier, sizeof(enterprise_identifier));
+        if (!p)
+                return -ENOMEM;
+
+        total = sizeof(enterprise_identifier);
+        offset = total;
+
+        STRV_FOREACH(s, vendor_class) {
+                size_t len = strlen(*s);
+                uint8_t *q;
+
+                q = realloc(p, total + len + 2);
+                if (!q)
+                        return -ENOMEM;
+
+                p = q;
+
+                unaligned_write_be16(&p[offset], len);
+                memcpy(&p[offset + 2], *s, len);
+
+                offset += 2 + len;
+                total += 2 + len;
+        }
+
+        return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_CLASS, total, p);
+}
+
+int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix) {
+        DHCP6Option *option = (DHCP6Option *)buf;
+        size_t i = sizeof(*option) + sizeof(pd->ia_pd);
+        DHCP6PDPrefixOption *prefix_opt;
+        DHCP6Address *prefix;
+
+        assert_return(buf, -EINVAL);
+        assert_return(pd, -EINVAL);
+        assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL);
+
+        if (len < i)
+                return -ENOBUFS;
+
+        option->code = htobe16(SD_DHCP6_OPTION_IA_PD);
+
+        memcpy(&option->data, &pd->ia_pd, sizeof(pd->ia_pd));
+        LIST_FOREACH(addresses, prefix, pd->addresses) {
+                if (len < i + sizeof(*prefix_opt))
+                        return -ENOBUFS;
+
+                prefix_opt = (DHCP6PDPrefixOption *)&buf[i];
+                prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX);
+                prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix));
+
+                memcpy(&prefix_opt->iapdprefix, &prefix->iapdprefix, sizeof(struct iapdprefix));
+                i += sizeof(*prefix_opt);
+        }
+
+        if (hint_pd_prefix && hint_pd_prefix->iapdprefix.prefixlen > 0) {
+                if (len < i + sizeof(*prefix_opt))
+                        return -ENOBUFS;
+
+                prefix_opt = (DHCP6PDPrefixOption *)&buf[i];
+                prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX);
+                prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix));
+
+                memcpy(&prefix_opt->iapdprefix, &hint_pd_prefix->iapdprefix, sizeof(struct iapdprefix));
+                i += sizeof(*prefix_opt);
+        }
+
+        option->len = htobe16(i - sizeof(*option));
+
+        return i;
+}
+
+static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
+        DHCP6Option *option = (DHCP6Option*) *buf;
+        uint16_t len;
+
+        assert_return(buf, -EINVAL);
+        assert_return(optcode, -EINVAL);
+        assert_return(optlen, -EINVAL);
+
+        if (*buflen < offsetof(DHCP6Option, data))
+                return -ENOMSG;
+
+        len = be16toh(option->len);
+
+        if (len > *buflen)
+                return -ENOMSG;
+
+        *optcode = be16toh(option->code);
+        *optlen = len;
+
+        *buf += 4;
+        *buflen -= 4;
+
+        return 0;
+}
+
+int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
+                       size_t *optlen, uint8_t **optvalue) {
+        int r;
+
+        assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
+
+        r = option_parse_hdr(buf, buflen, optcode, optlen);
+        if (r < 0)
+                return r;
+
+        if (*optlen > *buflen)
+                return -ENOBUFS;
+
+        *optvalue = *buf;
+        *buflen -= *optlen;
+        *buf += *optlen;
+
+        return 0;
+}
+
+int dhcp6_option_parse_status(DHCP6Option *option, size_t len) {
+        DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
+
+        if (len < sizeof(DHCP6StatusOption) ||
+            be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption))
+                return -ENOBUFS;
+
+        return be16toh(statusopt->status);
+}
+
+static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia,
+                                      uint32_t *lifetime_valid) {
+        DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option;
+        DHCP6Address *addr;
+        uint32_t lt_valid, lt_pref;
+        int r;
+
+        if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option))
+                return -ENOBUFS;
+
+        lt_valid = be32toh(addr_option->iaaddr.lifetime_valid);
+        lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred);
+
+        if (lt_valid == 0 || lt_pref > lt_valid) {
+                log_dhcp6_client(client, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
+                                 lt_pref, lt_valid);
+
+                return 0;
+        }
+
+        if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) {
+                r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option));
+                if (r != 0)
+                        return r < 0 ? r: 0;
+        }
+
+        addr = new0(DHCP6Address, 1);
+        if (!addr)
+                return -ENOMEM;
+
+        LIST_INIT(addresses, addr);
+        memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr));
+
+        LIST_PREPEND(addresses, ia->addresses, addr);
+
+        *lifetime_valid = be32toh(addr->iaaddr.lifetime_valid);
+
+        return 0;
+}
+
+static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia,
+                                       uint32_t *lifetime_valid) {
+        DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option;
+        DHCP6Address *prefix;
+        uint32_t lt_valid, lt_pref;
+        int r;
+
+        if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option))
+                return -ENOBUFS;
+
+        lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid);
+        lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred);
+
+        if (lt_valid == 0 || lt_pref > lt_valid) {
+                log_dhcp6_client(client, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
+                                 lt_pref, lt_valid);
+
+                return 0;
+        }
+
+        if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) {
+                r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option));
+                if (r != 0)
+                        return r < 0 ? r: 0;
+        }
+
+        prefix = new0(DHCP6Address, 1);
+        if (!prefix)
+                return -ENOMEM;
+
+        LIST_INIT(addresses, prefix);
+        memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix));
+
+        LIST_PREPEND(addresses, ia->addresses, prefix);
+
+        *lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid);
+
+        return 0;
+}
+
+int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code) {
+        uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
+        uint16_t iatype, optlen;
+        size_t iaaddr_offset;
+        int r = 0, status;
+        size_t i, len;
+        uint16_t opt;
+
+        assert_return(ia, -EINVAL);
+        assert_return(!ia->addresses, -EINVAL);
+
+        iatype = be16toh(iaoption->code);
+        len = be16toh(iaoption->len);
+
+        switch (iatype) {
+        case SD_DHCP6_OPTION_IA_NA:
+
+                if (len < DHCP6_OPTION_IA_NA_LEN)
+                        return -ENOBUFS;
+
+                iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
+                memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na));
+
+                lt_t1 = be32toh(ia->ia_na.lifetime_t1);
+                lt_t2 = be32toh(ia->ia_na.lifetime_t2);
+
+                if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
+                        log_dhcp6_client(client, "IA NA T1 %ds > T2 %ds",
+                                         lt_t1, lt_t2);
+                        return -EINVAL;
+                }
+
+                break;
+
+        case SD_DHCP6_OPTION_IA_PD:
+
+                if (len < sizeof(ia->ia_pd))
+                        return -ENOBUFS;
+
+                iaaddr_offset = sizeof(ia->ia_pd);
+                memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd));
+
+                lt_t1 = be32toh(ia->ia_pd.lifetime_t1);
+                lt_t2 = be32toh(ia->ia_pd.lifetime_t2);
+
+                if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
+                        log_dhcp6_client(client, "IA PD T1 %ds > T2 %ds",
+                                         lt_t1, lt_t2);
+                        return -EINVAL;
+                }
+
+                break;
+
+        case SD_DHCP6_OPTION_IA_TA:
+                if (len < DHCP6_OPTION_IA_TA_LEN)
+                        return -ENOBUFS;
+
+                iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
+                memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta));
+
+                break;
+
+        default:
+                return -ENOMSG;
+        }
+
+        ia->type = iatype;
+        i = iaaddr_offset;
+
+        while (i < len) {
+                DHCP6Option *option = (DHCP6Option *)&iaoption->data[i];
+
+                if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len))
+                        return -ENOBUFS;
+
+                opt = be16toh(option->code);
+                optlen = be16toh(option->len);
+
+                switch (opt) {
+                case SD_DHCP6_OPTION_IAADDR:
+
+                        if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) {
+                                log_dhcp6_client(client, "IA Address option not in IA NA or TA option");
+                                return -EINVAL;
+                        }
+
+                        r = dhcp6_option_parse_address(option, ia, &lt_valid);
+                        if (r < 0)
+                                return r;
+
+                        if (lt_valid < lt_min)
+                                lt_min = lt_valid;
+
+                        break;
+
+                case SD_DHCP6_OPTION_IA_PD_PREFIX:
+
+                        if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_PD)) {
+                                log_dhcp6_client(client, "IA PD Prefix option not in IA PD option");
+                                return -EINVAL;
+                        }
+
+                        r = dhcp6_option_parse_pdprefix(option, ia, &lt_valid);
+                        if (r < 0)
+                                return r;
+
+                        if (lt_valid < lt_min)
+                                lt_min = lt_valid;
+
+                        break;
+
+                case SD_DHCP6_OPTION_STATUS_CODE:
+
+                        status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data));
+                        if (status < 0)
+                                return status;
+
+                        if (status > 0) {
+                                if (ret_status_code)
+                                        *ret_status_code = status;
+
+                                log_dhcp6_client(client, "IA status %s",
+                                                 dhcp6_message_status_to_string(status));
+
+                                return 0;
+                        }
+
+                        break;
+
+                default:
+                        log_dhcp6_client(client, "Unknown IA option %d", opt);
+                        break;
+                }
+
+                i += sizeof(*option) + optlen;
+        }
+
+        switch(iatype) {
+        case SD_DHCP6_OPTION_IA_NA:
+                if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) {
+                        lt_t1 = lt_min / 2;
+                        lt_t2 = lt_min / 10 * 8;
+                        ia->ia_na.lifetime_t1 = htobe32(lt_t1);
+                        ia->ia_na.lifetime_t2 = htobe32(lt_t2);
+
+                        log_dhcp6_client(client, "Computed IA NA T1 %ds and T2 %ds as both were zero",
+                                         lt_t1, lt_t2);
+                }
+
+                break;
+
+        case SD_DHCP6_OPTION_IA_PD:
+                if (!ia->ia_pd.lifetime_t1 && !ia->ia_pd.lifetime_t2) {
+                        lt_t1 = lt_min / 2;
+                        lt_t2 = lt_min / 10 * 8;
+                        ia->ia_pd.lifetime_t1 = htobe32(lt_t1);
+                        ia->ia_pd.lifetime_t2 = htobe32(lt_t2);
+
+                        log_dhcp6_client(client, "Computed IA PD T1 %ds and T2 %ds as both were zero",
+                                         lt_t1, lt_t2);
+                }
+
+                break;
+
+        default:
+                break;
+        }
+
+        if (ret_status_code)
+                *ret_status_code = 0;
+
+        return 1;
+}
+
+int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
+                                struct in6_addr **addrs, size_t count,
+                                size_t *allocated) {
+
+        if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
+                return -EINVAL;
+
+        if (!GREEDY_REALLOC(*addrs, *allocated,
+                            count * sizeof(struct in6_addr) + optlen))
+                return -ENOMEM;
+
+        memcpy(*addrs + count, optval, optlen);
+
+        count += optlen / sizeof(struct in6_addr);
+
+        return count;
+}
+
+static int parse_domain(const uint8_t **data, uint16_t *len, char **out_domain) {
+        _cleanup_free_ char *ret = NULL;
+        size_t n = 0, allocated = 0;
+        const uint8_t *optval = *data;
+        uint16_t optlen = *len;
+        bool first = true;
+        int r;
+
+        if (optlen <= 1)
+                return -ENODATA;
+
+        for (;;) {
+                const char *label;
+                uint8_t c;
+
+                if (optlen == 0)
+                        break;
+
+                c = *optval;
+                optval++;
+                optlen--;
+
+                if (c == 0)
+                        /* End label */
+                        break;
+                if (c > 63)
+                        return -EBADMSG;
+                if (c > optlen)
+                        return -EMSGSIZE;
+
+                /* Literal label */
+                label = (const char *)optval;
+                optval += c;
+                optlen -= c;
+
+                if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+                        return -ENOMEM;
+
+                if (first)
+                        first = false;
+                else
+                        ret[n++] = '.';
+
+                r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
+                if (r < 0)
+                        return r;
+
+                n += r;
+        }
+
+        if (n) {
+                if (!GREEDY_REALLOC(ret, allocated, n + 1))
+                        return -ENOMEM;
+                ret[n] = 0;
+        }
+
+        *out_domain = TAKE_PTR(ret);
+        *data = optval;
+        *len = optlen;
+
+        return n;
+}
+
+int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str) {
+        _cleanup_free_ char *domain = NULL;
+        int r;
+
+        r = parse_domain(&optval, &optlen, &domain);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -ENODATA;
+        if (optlen != 0)
+                return -EINVAL;
+
+        *str = TAKE_PTR(domain);
+        return 0;
+}
+
+int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
+        size_t idx = 0;
+        _cleanup_strv_free_ char **names = NULL;
+        int r;
+
+        if (optlen <= 1)
+                return -ENODATA;
+        if (optval[optlen - 1] != '\0')
+                return -EINVAL;
+
+        while (optlen > 0) {
+                _cleanup_free_ char *ret = NULL;
+
+                r = parse_domain(&optval, &optlen, &ret);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        continue;
+
+                r = strv_extend(&names, ret);
+                if (r < 0)
+                        return r;
+
+                idx++;
+        }
+
+        *str_arr = TAKE_PTR(names);
+
+        return idx;
+}
+
+static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) {
+        if (!i)
+                return NULL;
+
+        free(i->data);
+        return mfree(i);
+}
+
+int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) {
+        assert_return(ret, -EINVAL);
+        assert_return(length == 0 || data, -EINVAL);
+
+        _cleanup_free_ void *q = memdup(data, length);
+        if (!q)
+                return -ENOMEM;
+
+        sd_dhcp6_option *p = new(sd_dhcp6_option, 1);
+        if (!p)
+                return -ENOMEM;
+
+        *p = (sd_dhcp6_option) {
+                .n_ref = 1,
+                .option = option,
+                .enterprise_identifier = enterprise_identifier,
+                .length = length,
+                .data = TAKE_PTR(q),
+        };
+
+        *ret = p;
+        return 0;
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+                dhcp6_option_hash_ops,
+                void,
+                trivial_hash_func,
+                trivial_compare_func,
+                sd_dhcp6_option,
+                sd_dhcp6_option_unref);
diff --git a/src/core/systemd/src/libsystemd-network/dhcp6-protocol.h b/src/core/systemd/src/libsystemd-network/dhcp6-protocol.h
new file mode 100644
index 00000000..c7003638
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/dhcp6-protocol.h
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+  Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+
+#include "macro.h"
+#include "sparse-endian.h"
+
+struct DHCP6Message {
+        union {
+                struct {
+                        uint8_t type;
+                        uint8_t _pad[3];
+                } _packed_;
+                be32_t transaction_id;
+        };
+        uint8_t options[];
+} _packed_;
+
+typedef struct DHCP6Message DHCP6Message;
+
+#define DHCP6_MIN_OPTIONS_SIZE \
+        1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr)
+
+#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \
+        { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+              0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } }
+
+enum {
+        DHCP6_PORT_SERVER                       = 547,
+        DHCP6_PORT_CLIENT                       = 546,
+};
+
+#define DHCP6_INF_TIMEOUT                       1 * USEC_PER_SEC
+#define DHCP6_INF_MAX_RT                        120 * USEC_PER_SEC
+#define DHCP6_SOL_MAX_DELAY                     1 * USEC_PER_SEC
+#define DHCP6_SOL_TIMEOUT                       1 * USEC_PER_SEC
+#define DHCP6_SOL_MAX_RT                        120 * USEC_PER_SEC
+#define DHCP6_REQ_TIMEOUT                       1 * USEC_PER_SEC
+#define DHCP6_REQ_MAX_RT                        120 * USEC_PER_SEC
+#define DHCP6_REQ_MAX_RC                        10
+#define DHCP6_REN_TIMEOUT                       10 * USEC_PER_SEC
+#define DHCP6_REN_MAX_RT                        600 * USEC_PER_SEC
+#define DHCP6_REB_TIMEOUT                       10 * USEC_PER_SEC
+#define DHCP6_REB_MAX_RT                        600 * USEC_PER_SEC
+
+enum DHCP6State {
+        DHCP6_STATE_STOPPED                     = 0,
+        DHCP6_STATE_INFORMATION_REQUEST         = 1,
+        DHCP6_STATE_SOLICITATION                = 2,
+        DHCP6_STATE_REQUEST                     = 3,
+        DHCP6_STATE_BOUND                       = 4,
+        DHCP6_STATE_RENEW                       = 5,
+        DHCP6_STATE_REBIND                      = 6,
+};
+
+enum {
+        DHCP6_SOLICIT                           = 1,
+        DHCP6_ADVERTISE                         = 2,
+        DHCP6_REQUEST                           = 3,
+        DHCP6_CONFIRM                           = 4,
+        DHCP6_RENEW                             = 5,
+        DHCP6_REBIND                            = 6,
+        DHCP6_REPLY                             = 7,
+        DHCP6_RELEASE                           = 8,
+        DHCP6_DECLINE                           = 9,
+        DHCP6_RECONFIGURE                       = 10,
+        DHCP6_INFORMATION_REQUEST               = 11,
+        DHCP6_RELAY_FORW                        = 12,
+        DHCP6_RELAY_REPL                        = 13,
+        _DHCP6_MESSAGE_MAX                      = 14,
+};
+
+enum {
+        DHCP6_NTP_SUBOPTION_SRV_ADDR            = 1,
+        DHCP6_NTP_SUBOPTION_MC_ADDR             = 2,
+        DHCP6_NTP_SUBOPTION_SRV_FQDN            = 3,
+};
+
+/*
+ * RFC 8415, RFC 5007 and RFC 7653 status codes:
+ * https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-5
+ */
+enum {
+        DHCP6_STATUS_SUCCESS                      = 0,
+        DHCP6_STATUS_UNSPEC_FAIL                  = 1,
+        DHCP6_STATUS_NO_ADDRS_AVAIL               = 2,
+        DHCP6_STATUS_NO_BINDING                   = 3,
+        DHCP6_STATUS_NOT_ON_LINK                  = 4,
+        DHCP6_STATUS_USE_MULTICAST                = 5,
+        DHCP6_STATUS_NO_PREFIX_AVAIL              = 6,
+        DHCP6_STATUS_UNKNOWN_QUERY_TYPE           = 7,
+        DHCP6_STATUS_MALFORMED_QUERY              = 8,
+        DHCP6_STATUS_NOT_CONFIGURED               = 9,
+        DHCP6_STATUS_NOT_ALLOWED                  = 10,
+        DHCP6_STATUS_QUERY_TERMINATED             = 11,
+        DHCP6_STATUS_DATA_MISSING                 = 12,
+        DHCP6_STATUS_CATCHUP_COMPLETE             = 13,
+        DHCP6_STATUS_NOT_SUPPORTED                = 14,
+        DHCP6_STATUS_TLS_CONNECTION_REFUSED       = 15,
+        DHCP6_STATUS_ADDRESS_IN_USE               = 16,
+        DHCP6_STATUS_CONFIGURATION_CONFLICT       = 17,
+        DHCP6_STATUS_MISSING_BINDING_INFORMATION  = 18,
+        DHCP6_STATUS_OUTDATED_BINDING_INFORMATION = 19,
+        DHCP6_STATUS_SERVER_SHUTTING_DOWN         = 20,
+        DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED     = 21,
+        DHCP6_STATUS_EXCESSIVE_TIME_SKEW          = 22,
+        _DHCP6_STATUS_MAX                         = 23,
+};
+
+enum {
+        DHCP6_FQDN_FLAG_S = (1 << 0),
+        DHCP6_FQDN_FLAG_O = (1 << 1),
+        DHCP6_FQDN_FLAG_N = (1 << 2),
+};
diff --git a/src/core/systemd/src/libsystemd-network/lldp-internal.h b/src/core/systemd/src/libsystemd-network/lldp-internal.h
new file mode 100644
index 00000000..f23695f9
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/lldp-internal.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-event.h"
+#include "sd-lldp.h"
+
+#include "hashmap.h"
+#include "log.h"
+#include "prioq.h"
+
+struct sd_lldp {
+        unsigned n_ref;
+
+        int ifindex;
+        int fd;
+
+        sd_event *event;
+        int64_t event_priority;
+        sd_event_source *io_event_source;
+        sd_event_source *timer_event_source;
+
+        Prioq *neighbor_by_expiry;
+        Hashmap *neighbor_by_id;
+
+        uint64_t neighbors_max;
+
+        sd_lldp_callback_t callback;
+        void *userdata;
+
+        uint16_t capability_mask;
+
+        struct ether_addr filter_address;
+};
+
+#define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__)
+#define log_lldp(fmt, ...) log_lldp_errno(0, fmt, ##__VA_ARGS__)
+
+const char* lldp_event_to_string(sd_lldp_event e) _const_;
+sd_lldp_event lldp_event_from_string(const char *s) _pure_;
diff --git a/src/core/systemd/src/libsystemd-network/lldp-neighbor.c b/src/core/systemd/src/libsystemd-network/lldp-neighbor.c
new file mode 100644
index 00000000..58ff0e0f
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/lldp-neighbor.c
@@ -0,0 +1,794 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "ether-addr-util.h"
+#include "hexdecoct.h"
+#include "in-addr-util.h"
+#include "lldp-internal.h"
+#include "lldp-neighbor.h"
+#include "memory-util.h"
+#include "missing_network.h"
+#include "unaligned.h"
+
+static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash *state) {
+        siphash24_compress(id->chassis_id, id->chassis_id_size, state);
+        siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state);
+        siphash24_compress(id->port_id, id->port_id_size, state);
+        siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state);
+}
+
+int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y) {
+        return memcmp_nn(x->chassis_id, x->chassis_id_size, y->chassis_id, y->chassis_id_size)
+            ?: memcmp_nn(x->port_id, x->port_id_size, y->port_id, y->port_id_size);
+}
+
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(lldp_neighbor_hash_ops, LLDPNeighborID, lldp_neighbor_id_hash_func, lldp_neighbor_id_compare_func,
+                                      sd_lldp_neighbor, lldp_neighbor_unlink);
+
+int lldp_neighbor_prioq_compare_func(const void *a, const void *b) {
+        const sd_lldp_neighbor *x = a, *y = b;
+
+        return CMP(x->until, y->until);
+}
+
+_public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) {
+        if (!n)
+                return NULL;
+
+        assert(n->n_ref > 0 || n->lldp);
+        n->n_ref++;
+
+        return n;
+}
+
+static void lldp_neighbor_free(sd_lldp_neighbor *n) {
+        assert(n);
+
+        free(n->id.port_id);
+        free(n->id.chassis_id);
+        free(n->port_description);
+        free(n->system_name);
+        free(n->system_description);
+        free(n->mud_url);
+        free(n->chassis_id_as_string);
+        free(n->port_id_as_string);
+        free(n);
+}
+
+_public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) {
+
+        /* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from
+         * the sd_lldp object. */
+
+        if (!n)
+                return NULL;
+
+        assert(n->n_ref > 0);
+        n->n_ref--;
+
+        if (n->n_ref <= 0 && !n->lldp)
+                lldp_neighbor_free(n);
+
+        return NULL;
+}
+
+sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) {
+
+        /* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */
+
+        if (!n)
+                return NULL;
+
+        if (!n->lldp)
+                return NULL;
+
+        /* Only remove the neighbor object from the hash table if it's in there, don't complain if it isn't. This is
+         * because we are used as destructor call for hashmap_clear() and thus sometimes are called to de-register
+         * ourselves from the hashtable and sometimes are called after we already are de-registered. */
+
+        (void) hashmap_remove_value(n->lldp->neighbor_by_id, &n->id, n);
+
+        assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0);
+
+        n->lldp = NULL;
+
+        if (n->n_ref <= 0)
+                lldp_neighbor_free(n);
+
+        return NULL;
+}
+
+sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) {
+        sd_lldp_neighbor *n;
+
+        n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size);
+        if (!n)
+                return NULL;
+
+        n->raw_size = raw_size;
+        n->n_ref = 1;
+
+        return n;
+}
+
+static int parse_string(char **s, const void *q, size_t n) {
+        const char *p = q;
+        char *k;
+
+        assert(s);
+        assert(p || n == 0);
+
+        if (*s) {
+                log_lldp("Found duplicate string, ignoring field.");
+                return 0;
+        }
+
+        /* Strip trailing NULs, just to be nice */
+        while (n > 0 && p[n-1] == 0)
+                n--;
+
+        if (n <= 0) /* Ignore empty strings */
+                return 0;
+
+        /* Look for inner NULs */
+        if (memchr(p, 0, n)) {
+                log_lldp("Found inner NUL in string, ignoring field.");
+                return 0;
+        }
+
+        /* Let's escape weird chars, for security reasons */
+        k = cescape_length(p, n);
+        if (!k)
+                return -ENOMEM;
+
+        free(*s);
+        *s = k;
+
+        return 1;
+}
+
+int lldp_neighbor_parse(sd_lldp_neighbor *n) {
+        struct ether_header h;
+        const uint8_t *p;
+        size_t left;
+        int r;
+
+        assert(n);
+
+        if (n->raw_size < sizeof(struct ether_header)) {
+                log_lldp("Received truncated packet, ignoring.");
+                return -EBADMSG;
+        }
+
+        memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h));
+
+        if (h.ether_type != htobe16(ETHERTYPE_LLDP)) {
+                log_lldp("Received packet with wrong type, ignoring.");
+                return -EBADMSG;
+        }
+
+        if (h.ether_dhost[0] != 0x01 ||
+            h.ether_dhost[1] != 0x80 ||
+            h.ether_dhost[2] != 0xc2 ||
+            h.ether_dhost[3] != 0x00 ||
+            h.ether_dhost[4] != 0x00 ||
+            !IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) {
+                log_lldp("Received packet with wrong destination address, ignoring.");
+                return -EBADMSG;
+        }
+
+        memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr));
+        memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr));
+
+        p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header);
+        left = n->raw_size - sizeof(struct ether_header);
+
+        for (;;) {
+                uint8_t type;
+                uint16_t length;
+
+                if (left < 2) {
+                        log_lldp("TLV lacks header, ignoring.");
+                        return -EBADMSG;
+                }
+
+                type = p[0] >> 1;
+                length = p[1] + (((uint16_t) (p[0] & 1)) << 8);
+                p += 2, left -= 2;
+
+                if (left < length) {
+                        log_lldp("TLV truncated, ignoring datagram.");
+                        return -EBADMSG;
+                }
+
+                switch (type) {
+
+                case SD_LLDP_TYPE_END:
+                        if (length != 0) {
+                                log_lldp("End marker TLV not zero-sized, ignoring datagram.");
+                                return -EBADMSG;
+                        }
+
+                        /* Note that after processing the SD_LLDP_TYPE_END left could still be > 0
+                         * as the message may contain padding (see IEEE 802.1AB-2016, sec. 8.5.12) */
+
+                        goto end_marker;
+
+                case SD_LLDP_TYPE_CHASSIS_ID:
+                        if (length < 2 || length > 256) { /* includes the chassis subtype, hence one extra byte */
+                                log_lldp("Chassis ID field size out of range, ignoring datagram.");
+                                return -EBADMSG;
+                        }
+                        if (n->id.chassis_id) {
+                                log_lldp("Duplicate chassis ID field, ignoring datagram.");
+                                return -EBADMSG;
+                        }
+
+                        n->id.chassis_id = memdup(p, length);
+                        if (!n->id.chassis_id)
+                                return -ENOMEM;
+
+                        n->id.chassis_id_size = length;
+                        break;
+
+                case SD_LLDP_TYPE_PORT_ID:
+                        if (length < 2 || length > 256) { /* includes the port subtype, hence one extra byte */
+                                log_lldp("Port ID field size out of range, ignoring datagram.");
+                                return -EBADMSG;
+                        }
+                        if (n->id.port_id) {
+                                log_lldp("Duplicate port ID field, ignoring datagram.");
+                                return -EBADMSG;
+                        }
+
+                        n->id.port_id = memdup(p, length);
+                        if (!n->id.port_id)
+                                return -ENOMEM;
+
+                        n->id.port_id_size = length;
+                        break;
+
+                case SD_LLDP_TYPE_TTL:
+                        if (length != 2) {
+                                log_lldp("TTL field has wrong size, ignoring datagram.");
+                                return -EBADMSG;
+                        }
+
+                        if (n->has_ttl) {
+                                log_lldp("Duplicate TTL field, ignoring datagram.");
+                                return -EBADMSG;
+                        }
+
+                        n->ttl = unaligned_read_be16(p);
+                        n->has_ttl = true;
+                        break;
+
+                case SD_LLDP_TYPE_PORT_DESCRIPTION:
+                        r = parse_string(&n->port_description, p, length);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case SD_LLDP_TYPE_SYSTEM_NAME:
+                        r = parse_string(&n->system_name, p, length);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case SD_LLDP_TYPE_SYSTEM_DESCRIPTION:
+                        r = parse_string(&n->system_description, p, length);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case SD_LLDP_TYPE_SYSTEM_CAPABILITIES:
+                        if (length != 4)
+                                log_lldp("System capabilities field has wrong size, ignoring.");
+                        else {
+                                n->system_capabilities = unaligned_read_be16(p);
+                                n->enabled_capabilities = unaligned_read_be16(p + 2);
+                                n->has_capabilities = true;
+                        }
+
+                        break;
+
+                case SD_LLDP_TYPE_PRIVATE: {
+                        if (length < 4)
+                                log_lldp("Found private TLV that is too short, ignoring.");
+                        else {
+                                /* RFC 8520: MUD URL */
+                                if (memcmp(p, SD_LLDP_OUI_MUD, sizeof(SD_LLDP_OUI_MUD)) == 0 &&
+                                    p[sizeof(SD_LLDP_OUI_MUD)] == SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION) {
+                                        r = parse_string(&n->mud_url, p + sizeof(SD_LLDP_OUI_MUD) + 1,
+                                                         length - 1 - sizeof(SD_LLDP_OUI_MUD));
+                                        if (r < 0)
+                                                return r;
+                                }
+                        }
+                }
+
+                        break;
+                }
+
+                p += length, left -= length;
+        }
+
+end_marker:
+        if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) {
+                log_lldp("One or more mandatory TLV missing in datagram. Ignoring.");
+                return -EBADMSG;
+
+        }
+
+        n->rindex = sizeof(struct ether_header);
+
+        return 0;
+}
+
+void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) {
+        assert(n);
+
+        if (n->ttl > 0) {
+                usec_t base;
+
+                /* Use the packet's timestamp if there is one known */
+                base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic());
+                if (base <= 0 || base == USEC_INFINITY)
+                        base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */
+
+                n->until = usec_add(base, n->ttl * USEC_PER_SEC);
+        } else
+                n->until = 0;
+
+        if (n->lldp)
+                prioq_reshuffle(n->lldp->neighbor_by_expiry, n, &n->prioq_idx);
+}
+
+bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) {
+        if (a == b)
+                return true;
+
+        if (!a || !b)
+                return false;
+
+        if (a->raw_size != b->raw_size)
+                return false;
+
+        return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0;
+}
+
+_public_ int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) {
+        assert_return(n, -EINVAL);
+        assert_return(address, -EINVAL);
+
+        *address = n->source_address;
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) {
+        assert_return(n, -EINVAL);
+        assert_return(address, -EINVAL);
+
+        *address = n->destination_address;
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+        assert_return(size, -EINVAL);
+
+        *ret = LLDP_NEIGHBOR_RAW(n);
+        *size = n->raw_size;
+
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
+        assert_return(n, -EINVAL);
+        assert_return(type, -EINVAL);
+        assert_return(ret, -EINVAL);
+        assert_return(size, -EINVAL);
+
+        assert(n->id.chassis_id_size > 0);
+
+        *type = *(uint8_t*) n->id.chassis_id;
+        *ret = (uint8_t*) n->id.chassis_id + 1;
+        *size = n->id.chassis_id_size - 1;
+
+        return 0;
+}
+
+static int format_mac_address(const void *data, size_t sz, char **ret) {
+        struct ether_addr a;
+        char *k;
+
+        assert(data || sz <= 0);
+
+        if (sz != 7)
+                return 0;
+
+        memcpy(&a, (uint8_t*) data + 1, sizeof(a));
+
+        k = new(char, ETHER_ADDR_TO_STRING_MAX);
+        if (!k)
+                return -ENOMEM;
+
+        *ret = ether_addr_to_string(&a, k);
+        return 1;
+}
+
+static int format_network_address(const void *data, size_t sz, char **ret) {
+        union in_addr_union a;
+        int family, r;
+
+        if (sz == 6 && ((uint8_t*) data)[1] == 1) {
+                memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in));
+                family = AF_INET;
+        } else if (sz == 18 && ((uint8_t*) data)[1] == 2) {
+                memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6));
+                family = AF_INET6;
+        } else
+                return 0;
+
+        r = in_addr_to_string(family, &a, ret);
+        if (r < 0)
+                return r;
+        return 1;
+}
+
+_public_ int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) {
+        char *k;
+        int r;
+
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (n->chassis_id_as_string) {
+                *ret = n->chassis_id_as_string;
+                return 0;
+        }
+
+        assert(n->id.chassis_id_size > 0);
+
+        switch (*(uint8_t*) n->id.chassis_id) {
+
+        case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
+        case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
+        case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT:
+        case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
+        case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
+                k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1);
+                if (!k)
+                        return -ENOMEM;
+
+                goto done;
+
+        case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
+                r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        goto done;
+
+                break;
+
+        case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS:
+                r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        goto done;
+
+                break;
+        }
+
+        /* Generic fallback */
+        k = hexmem(n->id.chassis_id, n->id.chassis_id_size);
+        if (!k)
+                return -ENOMEM;
+
+done:
+        *ret = n->chassis_id_as_string = k;
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
+        assert_return(n, -EINVAL);
+        assert_return(type, -EINVAL);
+        assert_return(ret, -EINVAL);
+        assert_return(size, -EINVAL);
+
+        assert(n->id.port_id_size > 0);
+
+        *type = *(uint8_t*) n->id.port_id;
+        *ret = (uint8_t*) n->id.port_id + 1;
+        *size = n->id.port_id_size - 1;
+
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) {
+        char *k;
+        int r;
+
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (n->port_id_as_string) {
+                *ret = n->port_id_as_string;
+                return 0;
+        }
+
+        assert(n->id.port_id_size > 0);
+
+        switch (*(uint8_t*) n->id.port_id) {
+
+        case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
+        case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT:
+        case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME:
+        case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
+                k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1);
+                if (!k)
+                        return -ENOMEM;
+
+                goto done;
+
+        case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS:
+                r = format_mac_address(n->id.port_id, n->id.port_id_size, &k);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        goto done;
+
+                break;
+
+        case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS:
+                r = format_network_address(n->id.port_id, n->id.port_id_size, &k);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        goto done;
+
+                break;
+        }
+
+        /* Generic fallback */
+        k = hexmem(n->id.port_id, n->id.port_id_size);
+        if (!k)
+                return -ENOMEM;
+
+done:
+        *ret = n->port_id_as_string = k;
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) {
+        assert_return(n, -EINVAL);
+        assert_return(ret_sec, -EINVAL);
+
+        *ret_sec = n->ttl;
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) {
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!n->system_name)
+                return -ENODATA;
+
+        *ret = n->system_name;
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) {
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!n->system_description)
+                return -ENODATA;
+
+        *ret = n->system_description;
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) {
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!n->port_description)
+                return -ENODATA;
+
+        *ret = n->port_description;
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret) {
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!n->mud_url)
+                return -ENODATA;
+
+        *ret = n->mud_url;
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!n->has_capabilities)
+                return -ENODATA;
+
+        *ret = n->system_capabilities;
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!n->has_capabilities)
+                return -ENODATA;
+
+        *ret = n->enabled_capabilities;
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
+        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        assert_return(raw || raw_size <= 0, -EINVAL);
+
+        n = lldp_neighbor_new(raw_size);
+        if (!n)
+                return -ENOMEM;
+
+        memcpy(LLDP_NEIGHBOR_RAW(n), raw, raw_size);
+        r = lldp_neighbor_parse(n);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(n);
+
+        return r;
+}
+
+_public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) {
+        assert_return(n, -EINVAL);
+
+        assert(n->raw_size >= sizeof(struct ether_header));
+        n->rindex = sizeof(struct ether_header);
+
+        return n->rindex < n->raw_size;
+}
+
+_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) {
+        size_t length;
+
+        assert_return(n, -EINVAL);
+
+        if (n->rindex == n->raw_size) /* EOF */
+                return -ESPIPE;
+
+        if (n->rindex + 2 > n->raw_size) /* Truncated message */
+                return -EBADMSG;
+
+        length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+        if (n->rindex + 2 + length > n->raw_size)
+                return -EBADMSG;
+
+        n->rindex += 2 + length;
+        return n->rindex < n->raw_size;
+}
+
+_public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) {
+        assert_return(n, -EINVAL);
+        assert_return(type, -EINVAL);
+
+        if (n->rindex == n->raw_size) /* EOF */
+                return -ESPIPE;
+
+        if (n->rindex + 2 > n->raw_size)
+                return -EBADMSG;
+
+        *type = LLDP_NEIGHBOR_TLV_TYPE(n);
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) {
+        uint8_t k;
+        int r;
+
+        assert_return(n, -EINVAL);
+
+        r = sd_lldp_neighbor_tlv_get_type(n, &k);
+        if (r < 0)
+                return r;
+
+        return type == k;
+}
+
+_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype) {
+        const uint8_t *d;
+        size_t length;
+        int r;
+
+        assert_return(n, -EINVAL);
+        assert_return(oui, -EINVAL);
+        assert_return(subtype, -EINVAL);
+
+        r = sd_lldp_neighbor_tlv_is_type(n, SD_LLDP_TYPE_PRIVATE);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -ENXIO;
+
+        length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+        if (length < 4)
+                return -EBADMSG;
+
+        if (n->rindex + 2 + length > n->raw_size)
+                return -EBADMSG;
+
+        d = LLDP_NEIGHBOR_TLV_DATA(n);
+        memcpy(oui, d, 3);
+        *subtype = d[3];
+
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype) {
+        uint8_t k[3], st;
+        int r;
+
+        r = sd_lldp_neighbor_tlv_get_oui(n, k, &st);
+        if (r == -ENXIO)
+                return 0;
+        if (r < 0)
+                return r;
+
+        return memcmp(k, oui, 3) == 0 && st == subtype;
+}
+
+_public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
+        size_t length;
+
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+        assert_return(size, -EINVAL);
+
+        /* Note that this returns the full TLV, including the TLV header */
+
+        if (n->rindex + 2 > n->raw_size)
+                return -EBADMSG;
+
+        length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+        if (n->rindex + 2 + length > n->raw_size)
+                return -EBADMSG;
+
+        *ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
+        *size = length + 2;
+
+        return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) {
+        assert_return(n, -EINVAL);
+        assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
+        assert_return(clock_supported(clock), -EOPNOTSUPP);
+        assert_return(ret, -EINVAL);
+
+        if (!triple_timestamp_is_set(&n->timestamp))
+                return -ENODATA;
+
+        *ret = triple_timestamp_by_clock(&n->timestamp, clock);
+        return 0;
+}
diff --git a/src/core/systemd/src/libsystemd-network/lldp-neighbor.h b/src/core/systemd/src/libsystemd-network/lldp-neighbor.h
new file mode 100644
index 00000000..a5718c8c
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/lldp-neighbor.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "sd-lldp.h"
+
+#include "hash-funcs.h"
+#include "lldp-internal.h"
+#include "time-util.h"
+
+typedef struct LLDPNeighborID {
+        /* The spec calls this an "MSAP identifier" */
+        void *chassis_id;
+        size_t chassis_id_size;
+
+        void *port_id;
+        size_t port_id_size;
+} LLDPNeighborID;
+
+struct sd_lldp_neighbor {
+        /* Neighbor objects stay around as long as they are linked into an "sd_lldp" object or n_ref > 0. */
+        sd_lldp *lldp;
+        unsigned n_ref;
+
+        triple_timestamp timestamp;
+
+        usec_t until;
+        unsigned prioq_idx;
+
+        struct ether_addr source_address;
+        struct ether_addr destination_address;
+
+        LLDPNeighborID id;
+
+        /* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */
+        size_t raw_size;
+
+        /* The current read index for the iterative TLV interface */
+        size_t rindex;
+
+        /* And a couple of fields parsed out. */
+        bool has_ttl:1;
+        bool has_capabilities:1;
+        bool has_port_vlan_id:1;
+
+        uint16_t ttl;
+
+        uint16_t system_capabilities;
+        uint16_t enabled_capabilities;
+
+        char *port_description;
+        char *system_name;
+        char *system_description;
+        char *mud_url;
+
+        uint16_t port_vlan_id;
+
+        char *chassis_id_as_string;
+        char *port_id_as_string;
+};
+
+static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) {
+        return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor));
+}
+
+static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) {
+        return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1;
+}
+
+static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) {
+        uint8_t *p;
+
+        p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
+        return p[1] + (((size_t) (p[0] & 1)) << 8);
+}
+
+static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) {
+        return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2;
+}
+
+extern const struct hash_ops lldp_neighbor_hash_ops;
+int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y);
+int lldp_neighbor_prioq_compare_func(const void *a, const void *b);
+
+sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n);
+sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size);
+int lldp_neighbor_parse(sd_lldp_neighbor *n);
+void lldp_neighbor_start_ttl(sd_lldp_neighbor *n);
+bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b);
diff --git a/src/core/systemd/src/libsystemd-network/lldp-network.c b/src/core/systemd/src/libsystemd-network/lldp-network.c
new file mode 100644
index 00000000..75dce58c
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/lldp-network.c
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <linux/filter.h>
+#include <netinet/if_ether.h>
+
+#include "fd-util.h"
+#include "lldp-network.h"
+#include "missing_network.h"
+#include "socket-util.h"
+
+int lldp_network_bind_raw_socket(int ifindex) {
+
+        static const struct sock_filter filter[] = {
+                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ethhdr, h_dest)),      /* A <- 4 bytes of destination MAC */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0),                    /* A != 01:80:c2:00 */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                             /* drop packet */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_dest) + 4),  /* A <- remaining 2 bytes of destination MAC */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0),                        /* A != 00:00 */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0),                        /* A != 00:03 */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0),                        /* A != 00:0e */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                             /* drop packet */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_proto)),     /* A <- protocol */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_LLDP, 1, 0),                /* A != ETHERTYPE_LLDP */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                             /* drop packet */
+                BPF_STMT(BPF_RET + BPF_K, (uint32_t) -1),                                 /* accept packet */
+        };
+
+        static const struct sock_fprog fprog = {
+                .len = ELEMENTSOF(filter),
+                .filter = (struct sock_filter*) filter,
+        };
+
+        struct packet_mreq mreq = {
+                .mr_ifindex = ifindex,
+                .mr_type = PACKET_MR_MULTICAST,
+                .mr_alen = ETH_ALEN,
+                .mr_address = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00 }
+        };
+
+        union sockaddr_union saddrll = {
+                .ll.sll_family = AF_PACKET,
+                .ll.sll_ifindex = ifindex,
+        };
+
+        _cleanup_close_ int fd = -1;
+        int r;
+
+        assert(ifindex > 0);
+
+        fd = socket(AF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK,
+                    htobe16(ETHERTYPE_LLDP));
+        if (fd < 0)
+                return -errno;
+
+        r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+        if (r < 0)
+                return -errno;
+
+        r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+        if (r < 0)
+                return -errno;
+
+        mreq.mr_address[ETH_ALEN - 1] = 0x03;
+        r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+        if (r < 0)
+                return -errno;
+
+        mreq.mr_address[ETH_ALEN - 1] = 0x0E;
+        r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+        if (r < 0)
+                return -errno;
+
+        r = bind(fd, &saddrll.sa, sizeof(saddrll.ll));
+        if (r < 0)
+                return -errno;
+
+        return TAKE_FD(fd);
+}
diff --git a/src/core/systemd/src/libsystemd-network/lldp-network.h b/src/core/systemd/src/libsystemd-network/lldp-network.h
new file mode 100644
index 00000000..bc69b324
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/lldp-network.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-event.h"
+
+int lldp_network_bind_raw_socket(int ifindex);
diff --git a/src/core/systemd/src/libsystemd-network/network-internal.c b/src/core/systemd/src/libsystemd-network/network-internal.c
new file mode 100644
index 00000000..efa3dcfa
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/network-internal.c
@@ -0,0 +1,245 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <arpa/inet.h>
+#include <linux/if.h>
+#include <netinet/ether.h>
+
+#include "sd-ndisc.h"
+
+#include "alloc-util.h"
+#include "dhcp-lease-internal.h"
+#include "extract-word.h"
+#include "hexdecoct.h"
+#include "log.h"
+#include "network-internal.h"
+#include "parse-util.h"
+
+size_t serialize_in_addrs(FILE *f,
+                          const struct in_addr *addresses,
+                          size_t size,
+                          bool *with_leading_space,
+                          bool (*predicate)(const struct in_addr *addr)) {
+        assert(f);
+        assert(addresses);
+
+        size_t count = 0;
+        bool _space = false;
+        if (!with_leading_space)
+                with_leading_space = &_space;
+
+        for (size_t i = 0; i < size; i++) {
+                char sbuf[INET_ADDRSTRLEN];
+
+                if (predicate && !predicate(&addresses[i]))
+                        continue;
+
+                if (*with_leading_space)
+                        fputc(' ', f);
+                fputs(inet_ntop(AF_INET, &addresses[i], sbuf, sizeof(sbuf)), f);
+                count++;
+                *with_leading_space = true;
+        }
+
+        return count;
+}
+
+int deserialize_in_addrs(struct in_addr **ret, const char *string) {
+        _cleanup_free_ struct in_addr *addresses = NULL;
+        int size = 0;
+
+        assert(ret);
+        assert(string);
+
+        for (;;) {
+                _cleanup_free_ char *word = NULL;
+                struct in_addr *new_addresses;
+                int r;
+
+                r = extract_first_word(&string, &word, NULL, 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                new_addresses = reallocarray(addresses, size + 1, sizeof(struct in_addr));
+                if (!new_addresses)
+                        return -ENOMEM;
+                else
+                        addresses = new_addresses;
+
+                r = inet_pton(AF_INET, word, &(addresses[size]));
+                if (r <= 0)
+                        continue;
+
+                size++;
+        }
+
+        *ret = size > 0 ? TAKE_PTR(addresses) : NULL;
+
+        return size;
+}
+
+void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size, bool *with_leading_space) {
+        assert(f);
+        assert(addresses);
+        assert(size);
+
+        bool _space = false;
+        if (!with_leading_space)
+                with_leading_space = &_space;
+
+        for (size_t i = 0; i < size; i++) {
+                char buffer[INET6_ADDRSTRLEN];
+
+                if (*with_leading_space)
+                        fputc(' ', f);
+                fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f);
+                *with_leading_space = true;
+        }
+}
+
+int deserialize_in6_addrs(struct in6_addr **ret, const char *string) {
+        _cleanup_free_ struct in6_addr *addresses = NULL;
+        int size = 0;
+
+        assert(ret);
+        assert(string);
+
+        for (;;) {
+                _cleanup_free_ char *word = NULL;
+                struct in6_addr *new_addresses;
+                int r;
+
+                r = extract_first_word(&string, &word, NULL, 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                new_addresses = reallocarray(addresses, size + 1, sizeof(struct in6_addr));
+                if (!new_addresses)
+                        return -ENOMEM;
+                else
+                        addresses = new_addresses;
+
+                r = inet_pton(AF_INET6, word, &(addresses[size]));
+                if (r <= 0)
+                        continue;
+
+                size++;
+        }
+
+        *ret = TAKE_PTR(addresses);
+
+        return size;
+}
+
+void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) {
+        assert(f);
+        assert(key);
+        assert(routes);
+        assert(size);
+
+        fprintf(f, "%s=", key);
+
+        for (size_t i = 0; i < size; i++) {
+                char sbuf[INET_ADDRSTRLEN];
+                struct in_addr dest, gw;
+                uint8_t length;
+
+                assert_se(sd_dhcp_route_get_destination(routes[i], &dest) >= 0);
+                assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0);
+                assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0);
+
+                fprintf(f, "%s/%" PRIu8, inet_ntop(AF_INET, &dest, sbuf, sizeof sbuf), length);
+                fprintf(f, ",%s%s", inet_ntop(AF_INET, &gw, sbuf, sizeof sbuf), i < size - 1 ? " ": "");
+        }
+
+        fputs("\n", f);
+}
+
+int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string) {
+        _cleanup_free_ struct sd_dhcp_route *routes = NULL;
+        size_t size = 0, allocated = 0;
+
+        assert(ret);
+        assert(ret_size);
+        assert(ret_allocated);
+        assert(string);
+
+         /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */
+        for (;;) {
+                _cleanup_free_ char *word = NULL;
+                char *tok, *tok_end;
+                unsigned n;
+                int r;
+
+                r = extract_first_word(&string, &word, NULL, 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                if (!GREEDY_REALLOC(routes, allocated, size + 1))
+                        return -ENOMEM;
+
+                tok = word;
+
+                /* get the subnet */
+                tok_end = strchr(tok, '/');
+                if (!tok_end)
+                        continue;
+                *tok_end = '\0';
+
+                r = inet_aton(tok, &routes[size].dst_addr);
+                if (r == 0)
+                        continue;
+
+                tok = tok_end + 1;
+
+                /* get the prefixlen */
+                tok_end = strchr(tok, ',');
+                if (!tok_end)
+                        continue;
+
+                *tok_end = '\0';
+
+                r = safe_atou(tok, &n);
+                if (r < 0 || n > 32)
+                        continue;
+
+                routes[size].dst_prefixlen = (uint8_t) n;
+                tok = tok_end + 1;
+
+                /* get the gateway */
+                r = inet_aton(tok, &routes[size].gw_addr);
+                if (r == 0)
+                        continue;
+
+                size++;
+        }
+
+        *ret_size = size;
+        *ret_allocated = allocated;
+        *ret = TAKE_PTR(routes);
+
+        return 0;
+}
+
+int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size) {
+        _cleanup_free_ char *hex_buf = NULL;
+
+        assert(f);
+        assert(key);
+        assert(data);
+
+        hex_buf = hexmem(data, size);
+        if (!hex_buf)
+                return -ENOMEM;
+
+        fprintf(f, "%s=%s\n", key, hex_buf);
+
+        return 0;
+}
diff --git a/src/core/systemd/src/libsystemd-network/network-internal.h b/src/core/systemd/src/libsystemd-network/network-internal.h
new file mode 100644
index 00000000..e5b853c0
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/network-internal.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "sd-dhcp-lease.h"
+
+size_t serialize_in_addrs(FILE *f,
+                          const struct in_addr *addresses,
+                          size_t size,
+                          bool *with_leading_space,
+                          bool (*predicate)(const struct in_addr *addr));
+int deserialize_in_addrs(struct in_addr **addresses, const char *string);
+void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses,
+                         size_t size,
+                         bool *with_leading_space);
+int deserialize_in6_addrs(struct in6_addr **addresses, const char *string);
+
+/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */
+struct sd_dhcp_route;
+struct sd_dhcp_lease;
+
+void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size);
+int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string);
+
+/* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */
+int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size);
+
+int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file);
+int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file);
diff --git a/src/core/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/core/systemd/src/libsystemd-network/sd-dhcp-client.c
new file mode 100644
index 00000000..27a3476f
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -0,0 +1,2258 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <errno.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <linux/if_infiniband.h>
+
+#include "sd-dhcp-client.h"
+
+#include "alloc-util.h"
+#include "dhcp-identifier.h"
+#include "dhcp-internal.h"
+#include "dhcp-lease-internal.h"
+#include "dhcp-protocol.h"
+#include "dns-domain.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "hostname-util.h"
+#include "io-util.h"
+#include "memory-util.h"
+#include "random-util.h"
+#include "set.h"
+#include "sort-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+#include "utf8.h"
+#include "web-util.h"
+
+#define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN)  /* Arbitrary limit */
+#define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN)
+
+#define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC)
+#define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE)
+
+#define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report
+                                      * transient failure. */
+
+typedef struct sd_dhcp_client_id {
+        uint8_t type;
+        union {
+                struct {
+                        /* 0: Generic (non-LL) (RFC 2132) */
+                        uint8_t data[MAX_CLIENT_ID_LEN];
+                } _packed_ gen;
+                struct {
+                        /* 1: Ethernet Link-Layer (RFC 2132) */
+                        uint8_t haddr[ETH_ALEN];
+                } _packed_ eth;
+                struct {
+                        /* 2 - 254: ARP/Link-Layer (RFC 2132) */
+                        uint8_t haddr[0];
+                } _packed_ ll;
+                struct {
+                        /* 255: Node-specific (RFC 4361) */
+                        be32_t iaid;
+                        struct duid duid;
+                } _packed_ ns;
+                struct {
+                        uint8_t data[MAX_CLIENT_ID_LEN];
+                } _packed_ raw;
+        };
+} _packed_ sd_dhcp_client_id;
+
+struct sd_dhcp_client {
+        unsigned n_ref;
+
+        DHCPState state;
+        sd_event *event;
+        int event_priority;
+        sd_event_source *timeout_resend;
+        int ifindex;
+        int fd;
+        uint16_t port;
+        union sockaddr_union link;
+        sd_event_source *receive_message;
+        bool request_broadcast;
+        Set *req_opts;
+        bool anonymize;
+        be32_t last_addr;
+        uint8_t mac_addr[MAX_MAC_ADDR_LEN];
+        size_t mac_addr_len;
+        uint8_t bcast_addr[MAX_MAC_ADDR_LEN];
+        size_t bcast_addr_len;
+        uint16_t arp_type;
+        sd_dhcp_client_id client_id;
+        size_t client_id_len;
+        char *hostname;
+        char *vendor_class_identifier;
+        char *mudurl;
+        char **user_class;
+        uint32_t mtu;
+        uint32_t fallback_lease_lifetime;
+        uint32_t xid;
+        usec_t start_time;
+        usec_t t1_time;
+        usec_t t2_time;
+        usec_t expire_time;
+        uint64_t attempt;
+        uint64_t max_attempts;
+        OrderedHashmap *extra_options;
+        OrderedHashmap *vendor_options;
+        usec_t request_sent;
+        sd_event_source *timeout_t1;
+        sd_event_source *timeout_t2;
+        sd_event_source *timeout_expire;
+        sd_dhcp_client_callback_t callback;
+        void *userdata;
+        sd_dhcp_lease *lease;
+        usec_t start_delay;
+        int ip_service_type;
+};
+
+static const uint8_t default_req_opts[] = {
+        SD_DHCP_OPTION_SUBNET_MASK,
+        SD_DHCP_OPTION_ROUTER,
+        SD_DHCP_OPTION_HOST_NAME,
+        SD_DHCP_OPTION_DOMAIN_NAME,
+        SD_DHCP_OPTION_DOMAIN_NAME_SERVER,
+};
+
+/* RFC7844 section 3:
+   MAY contain the Parameter Request List option.
+   RFC7844 section 3.6:
+   The client intending to protect its privacy SHOULD only request a
+   minimal number of options in the PRL and SHOULD also randomly shuffle
+   the ordering of option codes in the PRL.  If this random ordering
+   cannot be implemented, the client MAY order the option codes in the
+   PRL by option code number (lowest to highest).
+*/
+/* NOTE: using PRL options that Windows 10 RFC7844 implementation uses */
+static const uint8_t default_req_opts_anonymize[] = {
+        SD_DHCP_OPTION_SUBNET_MASK,                     /* 1 */
+        SD_DHCP_OPTION_ROUTER,                          /* 3 */
+        SD_DHCP_OPTION_DOMAIN_NAME_SERVER,              /* 6 */
+        SD_DHCP_OPTION_DOMAIN_NAME,                     /* 15 */
+        SD_DHCP_OPTION_ROUTER_DISCOVER,                 /* 31 */
+        SD_DHCP_OPTION_STATIC_ROUTE,                    /* 33 */
+        SD_DHCP_OPTION_VENDOR_SPECIFIC,                 /* 43 */
+        SD_DHCP_OPTION_NETBIOS_NAMESERVER,              /* 44 */
+        SD_DHCP_OPTION_NETBIOS_NODETYPE,                /* 46 */
+        SD_DHCP_OPTION_NETBIOS_SCOPE,                   /* 47 */
+        SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,          /* 121 */
+        SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE,  /* 249 */
+        SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,     /* 252 */
+};
+
+static int client_receive_message_raw(
+                sd_event_source *s,
+                int fd,
+                uint32_t revents,
+                void *userdata);
+static int client_receive_message_udp(
+                sd_event_source *s,
+                int fd,
+                uint32_t revents,
+                void *userdata);
+static void client_stop(sd_dhcp_client *client, int error);
+
+int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret) {
+        const sd_dhcp_client_id *client_id = data;
+        _cleanup_free_ char *t = NULL;
+        int r = 0;
+
+        assert_return(data, -EINVAL);
+        assert_return(len >= 1, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        len -= 1;
+        if (len > MAX_CLIENT_ID_LEN)
+                return -EINVAL;
+
+        switch (client_id->type) {
+        case 0:
+                if (utf8_is_printable((char *) client_id->gen.data, len))
+                        r = asprintf(&t, "%.*s", (int) len, client_id->gen.data);
+                else
+                        r = asprintf(&t, "DATA");
+                break;
+        case 1:
+                if (len != sizeof_field(sd_dhcp_client_id, eth))
+                        return -EINVAL;
+
+                r = asprintf(&t, "%x:%x:%x:%x:%x:%x",
+                             client_id->eth.haddr[0],
+                             client_id->eth.haddr[1],
+                             client_id->eth.haddr[2],
+                             client_id->eth.haddr[3],
+                             client_id->eth.haddr[4],
+                             client_id->eth.haddr[5]);
+                break;
+        case 2 ... 254:
+                r = asprintf(&t, "ARP/LL");
+                break;
+        case 255:
+                if (len < 6)
+                        return -EINVAL;
+
+                uint32_t iaid = be32toh(client_id->ns.iaid);
+                uint16_t duid_type = be16toh(client_id->ns.duid.type);
+                if (dhcp_validate_duid_len(duid_type, len - 6, true) < 0)
+                        return -EINVAL;
+
+                r = asprintf(&t, "IAID:0x%x/DUID", iaid);
+                break;
+        }
+
+        if (r < 0)
+                return -ENOMEM;
+        *ret = TAKE_PTR(t);
+        return 0;
+}
+
+int sd_dhcp_client_set_callback(
+                sd_dhcp_client *client,
+                sd_dhcp_client_callback_t cb,
+                void *userdata) {
+
+        assert_return(client, -EINVAL);
+
+        client->callback = cb;
+        client->userdata = userdata;
+
+        return 0;
+}
+
+int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) {
+        assert_return(client, -EINVAL);
+
+        client->request_broadcast = !!broadcast;
+
+        return 0;
+}
+
+int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) {
+        assert_return(client, -EINVAL);
+        assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY);
+
+        switch(option) {
+
+        case SD_DHCP_OPTION_PAD:
+        case SD_DHCP_OPTION_OVERLOAD:
+        case SD_DHCP_OPTION_MESSAGE_TYPE:
+        case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST:
+        case SD_DHCP_OPTION_END:
+                return -EINVAL;
+
+        default:
+                break;
+        }
+
+        return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option));
+}
+
+int sd_dhcp_client_set_request_address(
+                sd_dhcp_client *client,
+                const struct in_addr *last_addr) {
+
+        assert_return(client, -EINVAL);
+        assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY);
+
+        if (last_addr)
+                client->last_addr = last_addr->s_addr;
+        else
+                client->last_addr = INADDR_ANY;
+
+        return 0;
+}
+
+int sd_dhcp_client_set_ifindex(sd_dhcp_client *client, int ifindex) {
+
+        assert_return(client, -EINVAL);
+        assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY);
+        assert_return(ifindex > 0, -EINVAL);
+
+        client->ifindex = ifindex;
+        return 0;
+}
+
+int sd_dhcp_client_set_mac(
+                sd_dhcp_client *client,
+                const uint8_t *addr,
+                const uint8_t *bcast_addr,
+                size_t addr_len,
+                uint16_t arp_type) {
+
+        DHCP_CLIENT_DONT_DESTROY(client);
+        bool need_restart = false;
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(addr, -EINVAL);
+        assert_return(addr_len > 0 && addr_len <= MAX_MAC_ADDR_LEN, -EINVAL);
+        assert_return(arp_type > 0, -EINVAL);
+
+        if (arp_type == ARPHRD_ETHER)
+                assert_return(addr_len == ETH_ALEN, -EINVAL);
+        else if (arp_type == ARPHRD_INFINIBAND)
+                assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
+        else
+                return -EINVAL;
+
+        if (client->mac_addr_len == addr_len &&
+            memcmp(&client->mac_addr, addr, addr_len) == 0 &&
+            (client->bcast_addr_len > 0) == !!bcast_addr &&
+            (!bcast_addr || memcmp(&client->bcast_addr, bcast_addr, addr_len) == 0))
+                return 0;
+
+        if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
+                log_dhcp_client(client, "Changing MAC address on running DHCP client, restarting");
+                need_restart = true;
+                client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+        }
+
+        memcpy(&client->mac_addr, addr, addr_len);
+        client->mac_addr_len = addr_len;
+        client->arp_type = arp_type;
+        client->bcast_addr_len = 0;
+
+        if (bcast_addr) {
+                memcpy(&client->bcast_addr, bcast_addr, addr_len);
+                client->bcast_addr_len = addr_len;
+        }
+
+        if (need_restart && client->state != DHCP_STATE_STOPPED) {
+                r = sd_dhcp_client_start(client);
+                if (r < 0)
+                        return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m");
+        }
+
+        return 0;
+}
+
+int sd_dhcp_client_get_client_id(
+                sd_dhcp_client *client,
+                uint8_t *type,
+                const uint8_t **data,
+                size_t *data_len) {
+
+        assert_return(client, -EINVAL);
+        assert_return(type, -EINVAL);
+        assert_return(data, -EINVAL);
+        assert_return(data_len, -EINVAL);
+
+        *type = 0;
+        *data = NULL;
+        *data_len = 0;
+        if (client->client_id_len) {
+                *type = client->client_id.type;
+                *data = client->client_id.raw.data;
+                *data_len = client->client_id_len - sizeof(client->client_id.type);
+        }
+
+        return 0;
+}
+
+int sd_dhcp_client_set_client_id(
+                sd_dhcp_client *client,
+                uint8_t type,
+                const uint8_t *data,
+                size_t data_len) {
+
+        DHCP_CLIENT_DONT_DESTROY(client);
+        bool need_restart = false;
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(data, -EINVAL);
+        assert_return(data_len > 0 && data_len <= MAX_CLIENT_ID_LEN, -EINVAL);
+        G_STATIC_ASSERT_EXPR (_NM_SD_MAX_CLIENT_ID_LEN == MAX_CLIENT_ID_LEN);
+
+        if (client->client_id_len == data_len + sizeof(client->client_id.type) &&
+            client->client_id.type == type &&
+            memcmp(&client->client_id.raw.data, data, data_len) == 0)
+                return 0;
+
+        /* For hardware types, log debug message about unexpected data length.
+         *
+         * Note that infiniband's INFINIBAND_ALEN is 20 bytes long, but only
+         * the last 8 bytes of the address are stable and suitable to put into
+         * the client-id. The caller is advised to account for that. */
+        if ((type == ARPHRD_ETHER && data_len != ETH_ALEN) ||
+            (type == ARPHRD_INFINIBAND && data_len != 8))
+                log_dhcp_client(client, "Changing client ID to hardware type %u with "
+                                "unexpected address length %zu",
+                                type, data_len);
+
+        if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
+                log_dhcp_client(client, "Changing client ID on running DHCP "
+                                "client, restarting");
+                need_restart = true;
+                client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+        }
+
+        client->client_id.type = type;
+        memcpy(&client->client_id.raw.data, data, data_len);
+        client->client_id_len = data_len + sizeof (client->client_id.type);
+
+        if (need_restart && client->state != DHCP_STATE_STOPPED) {
+                r = sd_dhcp_client_start(client);
+                if (r < 0)
+                        return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m");
+        }
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+/**
+ * Sets IAID and DUID. If duid is non-null, the DUID is set to duid_type + duid
+ * without further modification. Otherwise, if duid_type is supported, DUID
+ * is set based on that type. Otherwise, an error is returned.
+ */
+static int dhcp_client_set_iaid_duid_internal(
+                sd_dhcp_client *client,
+                bool iaid_append,
+                bool iaid_set,
+                uint32_t iaid,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len,
+                usec_t llt_time) {
+
+        DHCP_CLIENT_DONT_DESTROY(client);
+        int r;
+        size_t len;
+
+        assert_return(client, -EINVAL);
+        assert_return(duid_len == 0 || duid, -EINVAL);
+
+        if (duid) {
+                r = dhcp_validate_duid_len(duid_type, duid_len, true);
+                if (r < 0)
+                        return log_dhcp_client_errno(client, r, "Failed to validate length of DUID: %m");
+        }
+
+        zero(client->client_id);
+        client->client_id.type = 255;
+
+        if (iaid_append) {
+                if (iaid_set)
+                        client->client_id.ns.iaid = htobe32(iaid);
+                else {
+                        r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr,
+                                                     client->mac_addr_len,
+                                                     true,
+                                                     &client->client_id.ns.iaid);
+                        if (r < 0)
+                                return log_dhcp_client_errno(client, r, "Failed to set IAID: %m");
+                }
+        }
+
+        if (duid) {
+                client->client_id.ns.duid.type = htobe16(duid_type);
+                memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len);
+                len = sizeof(client->client_id.ns.duid.type) + duid_len;
+        } else
+                switch (duid_type) {
+                case DUID_TYPE_LLT:
+                        if (client->mac_addr_len == 0)
+                                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set.");
+
+                        r = dhcp_identifier_set_duid_llt(&client->client_id.ns.duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
+                        if (r < 0)
+                                return log_dhcp_client_errno(client, r, "Failed to set DUID-LLT: %m");
+                        break;
+                case DUID_TYPE_EN:
+                        r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len);
+                        if (r < 0)
+                                return log_dhcp_client_errno(client, r, "Failed to set DUID-EN: %m");
+                        break;
+                case DUID_TYPE_LL:
+                        if (client->mac_addr_len == 0)
+                                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set.");
+
+                        r = dhcp_identifier_set_duid_ll(&client->client_id.ns.duid, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
+                        if (r < 0)
+                                return log_dhcp_client_errno(client, r, "Failed to set DUID-LL: %m");
+                        break;
+                case DUID_TYPE_UUID:
+                        r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len);
+                        if (r < 0)
+                                return log_dhcp_client_errno(client, r, "Failed to set DUID-UUID: %m");
+                        break;
+                default:
+                        return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type");
+                }
+
+        client->client_id_len = sizeof(client->client_id.type) + len +
+                                (iaid_append ? sizeof(client->client_id.ns.iaid) : 0);
+
+        if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
+                log_dhcp_client(client, "Configured %sDUID, restarting.", iaid_append ? "IAID+" : "");
+                client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+                r = sd_dhcp_client_start(client);
+                if (r < 0)
+                        return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m");
+        }
+
+        return 0;
+}
+
+int sd_dhcp_client_set_iaid_duid(
+                sd_dhcp_client *client,
+                bool iaid_set,
+                uint32_t iaid,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len) {
+        return dhcp_client_set_iaid_duid_internal(client, true, iaid_set, iaid, duid_type, duid, duid_len, 0);
+}
+
+int sd_dhcp_client_set_iaid_duid_llt(
+                sd_dhcp_client *client,
+                bool iaid_set,
+                uint32_t iaid,
+                usec_t llt_time) {
+        return dhcp_client_set_iaid_duid_internal(client, true, iaid_set, iaid, DUID_TYPE_LLT, NULL, 0, llt_time);
+}
+
+int sd_dhcp_client_set_duid(
+                sd_dhcp_client *client,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len) {
+        return dhcp_client_set_iaid_duid_internal(client, false, false, 0, duid_type, duid, duid_len, 0);
+}
+
+int sd_dhcp_client_set_duid_llt(
+                sd_dhcp_client *client,
+                usec_t llt_time) {
+        return dhcp_client_set_iaid_duid_internal(client, false, false, 0, DUID_TYPE_LLT, NULL, 0, llt_time);
+}
+#endif /* NM_IGNORED */
+
+int sd_dhcp_client_set_hostname(
+                sd_dhcp_client *client,
+                const char *hostname) {
+
+        assert_return(client, -EINVAL);
+
+        /* Make sure hostnames qualify as DNS and as Linux hostnames */
+        if (hostname &&
+            !(hostname_is_valid(hostname, 0) && dns_name_is_valid(hostname) > 0))
+                return -EINVAL;
+
+        return free_and_strdup(&client->hostname, hostname);
+}
+
+int sd_dhcp_client_set_vendor_class_identifier(
+                sd_dhcp_client *client,
+                const char *vci) {
+
+        assert_return(client, -EINVAL);
+
+        return free_and_strdup(&client->vendor_class_identifier, vci);
+}
+
+int sd_dhcp_client_set_mud_url(
+                sd_dhcp_client *client,
+                const char *mudurl) {
+
+        assert_return(client, -EINVAL);
+        assert_return(mudurl, -EINVAL);
+        assert_return(strlen(mudurl) <= 255, -EINVAL);
+        assert_return(http_url_is_valid(mudurl), -EINVAL);
+
+        return free_and_strdup(&client->mudurl, mudurl);
+}
+
+int sd_dhcp_client_set_user_class(
+                sd_dhcp_client *client,
+                const char* const* user_class) {
+
+        _cleanup_strv_free_ char **s = NULL;
+        char **p;
+
+        STRV_FOREACH(p, (char **) user_class)
+                if (strlen(*p) > 255)
+                        return -ENAMETOOLONG;
+
+        s = strv_copy((char **) user_class);
+        if (!s)
+                return -ENOMEM;
+
+        client->user_class = TAKE_PTR(s);
+
+        return 0;
+}
+
+int sd_dhcp_client_set_client_port(
+                sd_dhcp_client *client,
+                uint16_t port) {
+
+        assert_return(client, -EINVAL);
+
+        client->port = port;
+
+        return 0;
+}
+
+int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) {
+        assert_return(client, -EINVAL);
+        assert_return(mtu >= DHCP_DEFAULT_MIN_SIZE, -ERANGE);
+
+        client->mtu = mtu;
+
+        return 0;
+}
+
+int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempts) {
+        assert_return(client, -EINVAL);
+
+        client->max_attempts = max_attempts;
+
+        return 0;
+}
+
+int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(v, -EINVAL);
+
+        r = ordered_hashmap_ensure_allocated(&client->extra_options, &dhcp_option_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = ordered_hashmap_put(client->extra_options, UINT_TO_PTR(v->option), v);
+        if (r < 0)
+                return r;
+
+        sd_dhcp_option_ref(v);
+        return 0;
+}
+
+int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(v, -EINVAL);
+
+        r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp_option_hash_ops);
+        if (r < 0)
+                return -ENOMEM;
+
+        r = ordered_hashmap_put(client->vendor_options, v, v);
+        if (r < 0)
+                return r;
+
+        sd_dhcp_option_ref(v);
+
+        return 1;
+}
+
+int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
+        assert_return(client, -EINVAL);
+
+        if (!IN_SET(client->state, DHCP_STATE_SELECTING, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING))
+                return -EADDRNOTAVAIL;
+
+        if (ret)
+                *ret = client->lease;
+
+        return 0;
+}
+
+int sd_dhcp_client_set_service_type(sd_dhcp_client *client, int type) {
+        assert_return(client, -EINVAL);
+
+        client->ip_service_type = type;
+
+        return 0;
+}
+
+int sd_dhcp_client_set_fallback_lease_lifetime(sd_dhcp_client *client, uint32_t fallback_lease_lifetime) {
+        assert_return(client, -EINVAL);
+        assert_return(fallback_lease_lifetime > 0, -EINVAL);
+
+        client->fallback_lease_lifetime = fallback_lease_lifetime;
+
+        return 0;
+}
+
+static int client_notify(sd_dhcp_client *client, int event) {
+        assert(client);
+
+        if (client->callback)
+                return client->callback(client, event, client->userdata);
+
+        return 0;
+}
+
+static int client_initialize(sd_dhcp_client *client) {
+        assert_return(client, -EINVAL);
+
+        client->receive_message = sd_event_source_unref(client->receive_message);
+
+        client->fd = safe_close(client->fd);
+
+        (void) event_source_disable(client->timeout_resend);
+        (void) event_source_disable(client->timeout_t1);
+        (void) event_source_disable(client->timeout_t2);
+        (void) event_source_disable(client->timeout_expire);
+
+        client->attempt = 0;
+
+        client->state = DHCP_STATE_INIT;
+        client->xid = 0;
+
+        client->lease = sd_dhcp_lease_unref(client->lease);
+
+        return 0;
+}
+
+static void client_stop(sd_dhcp_client *client, int error) {
+        assert(client);
+
+        if (error < 0)
+                log_dhcp_client_errno(client, error, "STOPPED: %m");
+        else if (error == SD_DHCP_CLIENT_EVENT_STOP)
+                log_dhcp_client(client, "STOPPED");
+        else
+                log_dhcp_client(client, "STOPPED: Unknown event");
+
+        client_notify(client, error);
+
+        client_initialize(client);
+}
+
+/* RFC2131 section 4.1:
+ * retransmission delays should include -1 to +1 sec of random 'fuzz'. */
+#define RFC2131_RANDOM_FUZZ \
+        ((int64_t)(random_u64() % (2 * USEC_PER_SEC)) - (int64_t)USEC_PER_SEC)
+
+/* RFC2131 section 4.1:
+ * for retransmission delays, timeout should start at 4s then double
+ * each attempt with max of 64s, with -1 to +1 sec of random 'fuzz' added.
+ * This assumes the first call will be using attempt 1. */
+static usec_t client_compute_request_timeout(usec_t now, uint64_t attempt) {
+        usec_t timeout = (UINT64_C(1) << MIN(attempt + 1, UINT64_C(6))) * USEC_PER_SEC;
+
+        return usec_sub_signed(usec_add(now, timeout), RFC2131_RANDOM_FUZZ);
+}
+
+/* RFC2131 section 4.4.5:
+ * T1 defaults to (0.5 * duration_of_lease).
+ * T2 defaults to (0.875 * duration_of_lease). */
+#define T1_DEFAULT(lifetime) ((lifetime) / 2)
+#define T2_DEFAULT(lifetime) (((lifetime) * 7) / 8)
+
+/* RFC2131 section 4.4.5:
+ * the client SHOULD wait one-half of the remaining time until T2 (in RENEWING state)
+ * and one-half of the remaining lease time (in REBINDING state), down to a minimum
+ * of 60 seconds.
+ * Note that while the default T1/T2 initial times do have random 'fuzz' applied,
+ * the RFC sec 4.4.5 does not mention adding any fuzz to retries. */
+static usec_t client_compute_reacquisition_timeout(usec_t now, usec_t expire) {
+        return now + MAX(usec_sub_unsigned(expire, now) / 2, 60 * USEC_PER_SEC);
+}
+
+static int cmp_uint8(const uint8_t *a, const uint8_t *b) {
+        return CMP(*a, *b);
+}
+
+static int client_message_init(
+                sd_dhcp_client *client,
+                DHCPPacket **ret,
+                uint8_t type,
+                size_t *_optlen,
+                size_t *_optoffset) {
+
+        _cleanup_free_ DHCPPacket *packet = NULL;
+        size_t optlen, optoffset, size;
+        be16_t max_size;
+        usec_t time_now;
+        uint16_t secs;
+        int r;
+
+        assert(client);
+        assert(client->start_time);
+        assert(ret);
+        assert(_optlen);
+        assert(_optoffset);
+        assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE));
+
+        optlen = DHCP_MIN_OPTIONS_SIZE;
+        size = sizeof(DHCPPacket) + optlen;
+
+        packet = malloc0(size);
+        if (!packet)
+                return -ENOMEM;
+
+        r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, type,
+                              client->arp_type, optlen, &optoffset);
+        if (r < 0)
+                return r;
+
+        /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers
+           refuse to issue an DHCP lease if 'secs' is set to zero */
+        r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+        if (r < 0)
+                return r;
+        assert(time_now >= client->start_time);
+
+        /* seconds between sending first and last DISCOVER
+         * must always be strictly positive to deal with broken servers */
+        secs = ((time_now - client->start_time) / USEC_PER_SEC) ? : 1;
+        packet->dhcp.secs = htobe16(secs);
+
+        /* RFC2132 section 4.1
+           A client that cannot receive unicast IP datagrams until its protocol
+           software has been configured with an IP address SHOULD set the
+           BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or
+           DHCPREQUEST messages that client sends.  The BROADCAST bit will
+           provide a hint to the DHCP server and BOOTP relay agent to broadcast
+           any messages to the client on the client's subnet.
+
+           Note: some interfaces needs this to be enabled, but some networks
+           needs this to be disabled as broadcasts are filteretd, so this
+           needs to be configurable */
+        if (client->request_broadcast || client->arp_type != ARPHRD_ETHER)
+                packet->dhcp.flags = htobe16(0x8000);
+
+        /* RFC2132 section 4.1.1:
+           The client MUST include its hardware address in the ’chaddr’ field, if
+           necessary for delivery of DHCP reply messages.  Non-Ethernet
+           interfaces will leave 'chaddr' empty and use the client identifier
+           instead (eg, RFC 4390 section 2.1).
+         */
+        if (client->arp_type == ARPHRD_ETHER)
+                memcpy(&packet->dhcp.chaddr, &client->mac_addr, ETH_ALEN);
+
+        /* If no client identifier exists, construct an RFC 4361-compliant one */
+        if (client->client_id_len == 0) {
+                size_t duid_len;
+
+                client->client_id.type = 255;
+
+                r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len,
+                                             true, &client->client_id.ns.iaid);
+                if (r < 0)
+                        return r;
+
+                r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &duid_len);
+                if (r < 0)
+                        return r;
+
+                client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + duid_len;
+        }
+
+        /* Some DHCP servers will refuse to issue an DHCP lease if the Client
+           Identifier option is not set */
+        if (client->client_id_len) {
+                r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
+                                       SD_DHCP_OPTION_CLIENT_IDENTIFIER,
+                                       client->client_id_len,
+                                       &client->client_id);
+                if (r < 0)
+                        return r;
+        }
+
+        /* RFC2131 section 3.5:
+           in its initial DHCPDISCOVER or DHCPREQUEST message, a
+           client may provide the server with a list of specific
+           parameters the client is interested in. If the client
+           includes a list of parameters in a DHCPDISCOVER message,
+           it MUST include that list in any subsequent DHCPREQUEST
+           messages.
+         */
+
+        /* RFC7844 section 3:
+           MAY contain the Parameter Request List option. */
+        /* NOTE: in case that there would be an option to do not send
+         * any PRL at all, the size should be checked before sending */
+        if (!set_isempty(client->req_opts) && type != DHCP_RELEASE) {
+                _cleanup_free_ uint8_t *opts = NULL;
+                size_t n_opts, i = 0;
+                void *val;
+
+                n_opts = set_size(client->req_opts);
+                opts = new(uint8_t, n_opts);
+                if (!opts)
+                        return -ENOMEM;
+
+                SET_FOREACH(val, client->req_opts)
+                        opts[i++] = PTR_TO_UINT8(val);
+                assert(i == n_opts);
+
+                /* For anonymizing the request, let's sort the options. */
+                typesafe_qsort(opts, n_opts, cmp_uint8);
+
+                r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
+                                       SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
+                                       n_opts, opts);
+                if (r < 0)
+                        return r;
+        }
+
+        /* RFC2131 section 3.5:
+           The client SHOULD include the ’maximum DHCP message size’ option to
+           let the server know how large the server may make its DHCP messages.
+
+           Note (from ConnMan): Some DHCP servers will send bigger DHCP packets
+           than the defined default size unless the Maximum Message Size option
+           is explicitly set
+
+           RFC3442 "Requirements to Avoid Sizing Constraints":
+           Because a full routing table can be quite large, the standard 576
+           octet maximum size for a DHCP message may be too short to contain
+           some legitimate Classless Static Route options.  Because of this,
+           clients implementing the Classless Static Route option SHOULD send a
+           Maximum DHCP Message Size [4] option if the DHCP client's TCP/IP
+           stack is capable of receiving larger IP datagrams.  In this case, the
+           client SHOULD set the value of this option to at least the MTU of the
+           interface that the client is configuring.  The client MAY set the
+           value of this option higher, up to the size of the largest UDP packet
+           it is prepared to accept.  (Note that the value specified in the
+           Maximum DHCP Message Size option is the total maximum packet size,
+           including IP and UDP headers.)
+         */
+        /* RFC7844 section 3:
+           SHOULD NOT contain any other option. */
+        if (!client->anonymize && type != DHCP_RELEASE) {
+                max_size = htobe16(size);
+                r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0,
+                                       SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
+                                       2, &max_size);
+                if (r < 0)
+                        return r;
+        }
+
+        *_optlen = optlen;
+        *_optoffset = optoffset;
+        *ret = TAKE_PTR(packet);
+
+        return 0;
+}
+
+static int client_append_fqdn_option(
+                DHCPMessage *message,
+                size_t optlen,
+                size_t *optoffset,
+                const char *fqdn) {
+
+        uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH];
+        int r;
+
+        buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */
+                    DHCP_FQDN_FLAG_E;  /* Canonical wire format */
+        buffer[1] = 0;                 /* RCODE1 (deprecated) */
+        buffer[2] = 0;                 /* RCODE2 (deprecated) */
+
+        r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false);
+        if (r > 0)
+                r = dhcp_option_append(message, optlen, optoffset, 0,
+                                       SD_DHCP_OPTION_FQDN, 3 + r, buffer);
+
+        return r;
+}
+
+static int dhcp_client_send_raw(
+                sd_dhcp_client *client,
+                DHCPPacket *packet,
+                size_t len) {
+
+        dhcp_packet_append_ip_headers(packet, INADDR_ANY, client->port,
+                                      INADDR_BROADCAST, DHCP_PORT_SERVER, len, client->ip_service_type);
+
+        return dhcp_network_send_raw_socket(client->fd, &client->link,
+                                            packet, len);
+}
+
+static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) {
+        sd_dhcp_option *j;
+        int r;
+
+        assert(client);
+
+        if (client->hostname) {
+                /* According to RFC 4702 "clients that send the Client FQDN option in
+                   their messages MUST NOT also send the Host Name option". Just send
+                   one of the two depending on the hostname type.
+                */
+                if (dns_name_is_single_label(client->hostname)) {
+                        /* it is unclear from RFC 2131 if client should send hostname in
+                           DHCPDISCOVER but dhclient does and so we do as well
+                        */
+                        r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+                                               SD_DHCP_OPTION_HOST_NAME,
+                                               strlen(client->hostname), client->hostname);
+                } else
+                        r = client_append_fqdn_option(&packet->dhcp, optlen, optoffset,
+                                                      client->hostname);
+                if (r < 0)
+                        return r;
+        }
+
+        if (client->vendor_class_identifier) {
+                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+                                       SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER,
+                                       strlen(client->vendor_class_identifier),
+                                       client->vendor_class_identifier);
+                if (r < 0)
+                        return r;
+        }
+
+        if (client->mudurl) {
+                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+                                       SD_DHCP_OPTION_MUD_URL,
+                                       strlen(client->mudurl),
+                                       client->mudurl);
+                if (r < 0)
+                        return r;
+        }
+
+        if (client->user_class) {
+                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+                                       SD_DHCP_OPTION_USER_CLASS,
+                                       strv_length(client->user_class),
+                                       client->user_class);
+                if (r < 0)
+                        return r;
+        }
+
+        ORDERED_HASHMAP_FOREACH(j, client->extra_options) {
+                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+                                       j->option, j->length, j->data);
+                if (r < 0)
+                        return r;
+        }
+
+        if (!ordered_hashmap_isempty(client->vendor_options)) {
+                r = dhcp_option_append(
+                                &packet->dhcp, optlen, optoffset, 0,
+                                SD_DHCP_OPTION_VENDOR_SPECIFIC,
+                                ordered_hashmap_size(client->vendor_options), client->vendor_options);
+                if (r < 0)
+                        return r;
+        }
+
+
+        return 0;
+}
+
+static int client_send_discover(sd_dhcp_client *client) {
+        _cleanup_free_ DHCPPacket *discover = NULL;
+        size_t optoffset, optlen;
+        int r;
+
+        assert(client);
+        assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING));
+
+        r = client_message_init(client, &discover, DHCP_DISCOVER,
+                                &optlen, &optoffset);
+        if (r < 0)
+                return r;
+
+        /* the client may suggest values for the network address
+           and lease time in the DHCPDISCOVER message. The client may include
+           the ’requested IP address’ option to suggest that a particular IP
+           address be assigned, and may include the ’IP address lease time’
+           option to suggest the lease time it would like.
+         */
+        /* RFC7844 section 3:
+           SHOULD NOT contain any other option. */
+        if (!client->anonymize && client->last_addr != INADDR_ANY) {
+                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+                                       SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
+                                       4, &client->last_addr);
+                if (r < 0)
+                        return r;
+        }
+
+        r = client_append_common_discover_request_options(client, discover, &optoffset, optlen);
+        if (r < 0)
+                return r;
+
+        r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+                               SD_DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
+
+        /* We currently ignore:
+           The client SHOULD wait a random time between one and ten seconds to
+           desynchronize the use of DHCP at startup.
+         */
+        r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset);
+        if (r < 0)
+                return r;
+
+        log_dhcp_client(client, "DISCOVER");
+
+        return 0;
+}
+
+static int client_send_request(sd_dhcp_client *client) {
+        _cleanup_free_ DHCPPacket *request = NULL;
+        size_t optoffset, optlen;
+        int r;
+
+        assert(client);
+
+        r = client_message_init(client, &request, DHCP_REQUEST, &optlen, &optoffset);
+        if (r < 0)
+                return r;
+
+        switch (client->state) {
+        /* See RFC2131 section 4.3.2 (note that there is a typo in the RFC,
+           SELECTING should be REQUESTING)
+         */
+
+        case DHCP_STATE_REQUESTING:
+                /* Client inserts the address of the selected server in ’server
+                   identifier’, ’ciaddr’ MUST be zero, ’requested IP address’ MUST be
+                   filled in with the yiaddr value from the chosen DHCPOFFER.
+                 */
+
+                r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+                                       SD_DHCP_OPTION_SERVER_IDENTIFIER,
+                                       4, &client->lease->server_address);
+                if (r < 0)
+                        return r;
+
+                r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+                                       SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
+                                       4, &client->lease->address);
+                if (r < 0)
+                        return r;
+
+                break;
+
+        case DHCP_STATE_INIT_REBOOT:
+                /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
+                   option MUST be filled in with client’s notion of its previously
+                   assigned address. ’ciaddr’ MUST be zero.
+                 */
+                r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+                                       SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
+                                       4, &client->last_addr);
+                if (r < 0)
+                        return r;
+                break;
+
+        case DHCP_STATE_RENEWING:
+                /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
+                   option MUST NOT be filled in, ’ciaddr’ MUST be filled in with
+                   client’s IP address.
+                */
+
+        case DHCP_STATE_REBINDING:
+                /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
+                   option MUST NOT be filled in, ’ciaddr’ MUST be filled in with
+                   client’s IP address.
+
+                   This message MUST be broadcast to the 0xffffffff IP broadcast address.
+                 */
+                request->dhcp.ciaddr = client->lease->address;
+
+                break;
+
+        case DHCP_STATE_INIT:
+        case DHCP_STATE_SELECTING:
+        case DHCP_STATE_REBOOTING:
+        case DHCP_STATE_BOUND:
+        case DHCP_STATE_STOPPED:
+                return -EINVAL;
+        }
+
+        r = client_append_common_discover_request_options(client, request, &optoffset, optlen);
+        if (r < 0)
+                return r;
+
+        r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+                               SD_DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
+
+        if (client->state == DHCP_STATE_RENEWING)
+                r = dhcp_network_send_udp_socket(client->fd,
+                                                 client->lease->server_address,
+                                                 DHCP_PORT_SERVER,
+                                                 &request->dhcp,
+                                                 sizeof(DHCPMessage) + optoffset);
+        else
+                r = dhcp_client_send_raw(client, request, sizeof(DHCPPacket) + optoffset);
+        if (r < 0)
+                return r;
+
+        switch (client->state) {
+
+        case DHCP_STATE_REQUESTING:
+                log_dhcp_client(client, "REQUEST (requesting)");
+                break;
+
+        case DHCP_STATE_INIT_REBOOT:
+                log_dhcp_client(client, "REQUEST (init-reboot)");
+                break;
+
+        case DHCP_STATE_RENEWING:
+                log_dhcp_client(client, "REQUEST (renewing)");
+                break;
+
+        case DHCP_STATE_REBINDING:
+                log_dhcp_client(client, "REQUEST (rebinding)");
+                break;
+
+        default:
+                log_dhcp_client(client, "REQUEST (invalid)");
+                break;
+        }
+
+        return 0;
+}
+
+static int client_start(sd_dhcp_client *client);
+
+static int client_timeout_resend(
+                sd_event_source *s,
+                uint64_t usec,
+                void *userdata) {
+
+        sd_dhcp_client *client = userdata;
+        DHCP_CLIENT_DONT_DESTROY(client);
+        usec_t next_timeout;
+        uint64_t time_now;
+        int r;
+
+        assert(s);
+        assert(client);
+        assert(client->event);
+
+        r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+        if (r < 0)
+                goto error;
+
+        switch (client->state) {
+
+        case DHCP_STATE_RENEWING:
+                next_timeout = client_compute_reacquisition_timeout(time_now, client->t2_time);
+                break;
+
+        case DHCP_STATE_REBINDING:
+                next_timeout = client_compute_reacquisition_timeout(time_now, client->expire_time);
+                break;
+
+        case DHCP_STATE_REBOOTING:
+                /* start over as we did not receive a timely ack or nak */
+                r = client_initialize(client);
+                if (r < 0)
+                        goto error;
+
+                r = client_start(client);
+                if (r < 0)
+                        goto error;
+
+                log_dhcp_client(client, "REBOOTED");
+                return 0;
+
+        case DHCP_STATE_INIT:
+        case DHCP_STATE_INIT_REBOOT:
+        case DHCP_STATE_SELECTING:
+        case DHCP_STATE_REQUESTING:
+        case DHCP_STATE_BOUND:
+                if (client->attempt >= client->max_attempts)
+                        goto error;
+
+                client->attempt++;
+                next_timeout = client_compute_request_timeout(time_now, client->attempt);
+                break;
+
+        case DHCP_STATE_STOPPED:
+                r = -EINVAL;
+                goto error;
+
+        default:
+                assert_not_reached("Unhandled choice");
+        }
+
+        r = event_reset_time(client->event, &client->timeout_resend,
+                             clock_boottime_or_monotonic(),
+                             next_timeout, 10 * USEC_PER_MSEC,
+                             client_timeout_resend, client,
+                             client->event_priority, "dhcp4-resend-timer", true);
+        if (r < 0)
+                goto error;
+
+        switch (client->state) {
+        case DHCP_STATE_INIT:
+                r = client_send_discover(client);
+                if (r >= 0) {
+                        client->state = DHCP_STATE_SELECTING;
+                        client->attempt = 0;
+                } else if (client->attempt >= client->max_attempts)
+                        goto error;
+
+                break;
+
+        case DHCP_STATE_SELECTING:
+                r = client_send_discover(client);
+                if (r < 0 && client->attempt >= client->max_attempts)
+                        goto error;
+
+                break;
+
+        case DHCP_STATE_INIT_REBOOT:
+        case DHCP_STATE_REQUESTING:
+        case DHCP_STATE_RENEWING:
+        case DHCP_STATE_REBINDING:
+                r = client_send_request(client);
+                if (r < 0 && client->attempt >= client->max_attempts)
+                         goto error;
+
+                if (client->state == DHCP_STATE_INIT_REBOOT)
+                        client->state = DHCP_STATE_REBOOTING;
+
+                client->request_sent = time_now;
+                break;
+
+        case DHCP_STATE_REBOOTING:
+        case DHCP_STATE_BOUND:
+                break;
+
+        case DHCP_STATE_STOPPED:
+                r = -EINVAL;
+                goto error;
+        }
+
+        if (client->attempt >= TRANSIENT_FAILURE_ATTEMPTS)
+                client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE);
+
+        return 0;
+
+error:
+        client_stop(client, r);
+
+        /* Errors were dealt with when stopping the client, don't spill
+           errors into the event loop handler */
+        return 0;
+}
+
+static int client_initialize_io_events(
+                sd_dhcp_client *client,
+                sd_event_io_handler_t io_callback) {
+
+        int r;
+
+        assert(client);
+        assert(client->event);
+
+        r = sd_event_add_io(client->event, &client->receive_message,
+                            client->fd, EPOLLIN, io_callback,
+                            client);
+        if (r < 0)
+                goto error;
+
+        r = sd_event_source_set_priority(client->receive_message,
+                                         client->event_priority);
+        if (r < 0)
+                goto error;
+
+        r = sd_event_source_set_description(client->receive_message, "dhcp4-receive-message");
+        if (r < 0)
+                goto error;
+
+error:
+        if (r < 0)
+                client_stop(client, r);
+
+        return 0;
+}
+
+static int client_initialize_time_events(sd_dhcp_client *client) {
+        uint64_t usec = 0;
+        int r;
+
+        assert(client);
+        assert(client->event);
+
+        if (client->start_delay > 0) {
+                assert_se(sd_event_now(client->event, clock_boottime_or_monotonic(), &usec) >= 0);
+                usec += client->start_delay;
+        }
+
+        r = event_reset_time(client->event, &client->timeout_resend,
+                             clock_boottime_or_monotonic(),
+                             usec, 0,
+                             client_timeout_resend, client,
+                             client->event_priority, "dhcp4-resend-timer", true);
+        if (r < 0)
+                client_stop(client, r);
+
+        return 0;
+
+}
+
+static int client_initialize_events(sd_dhcp_client *client, sd_event_io_handler_t io_callback) {
+        client_initialize_io_events(client, io_callback);
+        client_initialize_time_events(client);
+
+        return 0;
+}
+
+static int client_start_delayed(sd_dhcp_client *client) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->event, -EINVAL);
+        assert_return(client->ifindex > 0, -EINVAL);
+        assert_return(client->fd < 0, -EBUSY);
+        assert_return(client->xid == 0, -EINVAL);
+        assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT), -EBUSY);
+
+        client->xid = random_u32();
+
+        r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
+                                         client->mac_addr, client->mac_addr_len,
+                                         client->bcast_addr, client->bcast_addr_len,
+                                         client->arp_type, client->port);
+        if (r < 0) {
+                client_stop(client, r);
+                return r;
+        }
+        client->fd = r;
+
+        if (IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT))
+                client->start_time = now(clock_boottime_or_monotonic());
+
+        return client_initialize_events(client, client_receive_message_raw);
+}
+
+static int client_start(sd_dhcp_client *client) {
+        client->start_delay = 0;
+        return client_start_delayed(client);
+}
+
+static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_dhcp_client *client = userdata;
+        DHCP_CLIENT_DONT_DESTROY(client);
+
+        log_dhcp_client(client, "EXPIRED");
+
+        client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
+
+        /* lease was lost, start over if not freed or stopped in callback */
+        if (client->state != DHCP_STATE_STOPPED) {
+                client_initialize(client);
+                client_start(client);
+        }
+
+        return 0;
+}
+
+static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_dhcp_client *client = userdata;
+        DHCP_CLIENT_DONT_DESTROY(client);
+        int r;
+
+        assert(client);
+
+        client->receive_message = sd_event_source_unref(client->receive_message);
+        client->fd = safe_close(client->fd);
+
+        client->state = DHCP_STATE_REBINDING;
+        client->attempt = 0;
+
+        r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
+                                         client->mac_addr, client->mac_addr_len,
+                                         client->bcast_addr, client->bcast_addr_len,
+                                         client->arp_type, client->port);
+        if (r < 0) {
+                client_stop(client, r);
+                return 0;
+        }
+        client->fd = r;
+
+        return client_initialize_events(client, client_receive_message_raw);
+}
+
+static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_dhcp_client *client = userdata;
+        DHCP_CLIENT_DONT_DESTROY(client);
+
+        if (client->lease)
+                client->state = DHCP_STATE_RENEWING;
+        else if (client->state != DHCP_STATE_INIT)
+                client->state = DHCP_STATE_INIT_REBOOT;
+        client->attempt = 0;
+
+        return client_initialize_time_events(client);
+}
+
+static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_t len) {
+        _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+        int r;
+
+        r = dhcp_lease_new(&lease);
+        if (r < 0)
+                return r;
+
+        if (client->client_id_len) {
+                r = dhcp_lease_set_client_id(lease,
+                                             (uint8_t *) &client->client_id,
+                                             client->client_id_len);
+                if (r < 0)
+                        return r;
+        }
+
+        r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease, NULL);
+        if (r != DHCP_OFFER) {
+                log_dhcp_client(client, "received message was not an OFFER, ignoring");
+                return -ENOMSG;
+        }
+
+        lease->next_server = offer->siaddr;
+        lease->address = offer->yiaddr;
+
+        if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0)
+                lease->lifetime = client->fallback_lease_lifetime;
+
+        if (lease->address == 0 ||
+            lease->server_address == 0 ||
+            lease->lifetime == 0) {
+                log_dhcp_client(client, "received lease lacks address, server address or lease lifetime, ignoring");
+                return -ENOMSG;
+        }
+
+        if (!lease->have_subnet_mask) {
+                r = dhcp_lease_set_default_subnet_mask(lease);
+                if (r < 0) {
+                        log_dhcp_client(client,
+                                        "received lease lacks subnet mask, "
+                                        "and a fallback one cannot be generated, ignoring");
+                        return -ENOMSG;
+                }
+        }
+
+        sd_dhcp_lease_unref(client->lease);
+        client->lease = TAKE_PTR(lease);
+
+        if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0)
+                return -ENOMSG;
+
+        log_dhcp_client(client, "OFFER");
+
+        return 0;
+}
+
+static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, size_t len) {
+        int r;
+
+        r = dhcp_option_parse(force, len, NULL, NULL, NULL);
+        if (r != DHCP_FORCERENEW)
+                return -ENOMSG;
+
+        log_dhcp_client(client, "FORCERENEW");
+
+        return 0;
+}
+
+static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) {
+        if (a->address != b->address)
+                return false;
+
+        if (a->subnet_mask != b->subnet_mask)
+                return false;
+
+        if (a->router_size != b->router_size)
+                return false;
+
+        for (size_t i = 0; i < a->router_size; i++)
+                if (a->router[i].s_addr != b->router[i].s_addr)
+                        return false;
+
+        return true;
+}
+
+static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t len) {
+        _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+        _cleanup_free_ char *error_message = NULL;
+        int r;
+
+        r = dhcp_lease_new(&lease);
+        if (r < 0)
+                return r;
+
+        if (client->client_id_len) {
+                r = dhcp_lease_set_client_id(lease,
+                                             (uint8_t *) &client->client_id,
+                                             client->client_id_len);
+                if (r < 0)
+                        return r;
+        }
+
+        r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease, &error_message);
+        if (r == DHCP_NAK) {
+                log_dhcp_client(client, "NAK: %s", strna(error_message));
+                return -EADDRNOTAVAIL;
+        }
+
+        if (r != DHCP_ACK) {
+                log_dhcp_client(client, "received message was not an ACK, ignoring");
+                return -ENOMSG;
+        }
+
+        lease->next_server = ack->siaddr;
+
+        lease->address = ack->yiaddr;
+
+        if (lease->address == INADDR_ANY ||
+            lease->server_address == INADDR_ANY ||
+            lease->lifetime == 0) {
+                log_dhcp_client(client, "received lease lacks address, server "
+                                "address or lease lifetime, ignoring");
+                return -ENOMSG;
+        }
+
+        if (lease->subnet_mask == INADDR_ANY) {
+                r = dhcp_lease_set_default_subnet_mask(lease);
+                if (r < 0) {
+                        log_dhcp_client(client,
+                                        "received lease lacks subnet mask, "
+                                        "and a fallback one cannot be generated, ignoring");
+                        return -ENOMSG;
+                }
+        }
+
+        r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
+        if (client->lease) {
+                if (lease_equal(client->lease, lease))
+                        r = SD_DHCP_CLIENT_EVENT_RENEW;
+                else
+                        r = SD_DHCP_CLIENT_EVENT_IP_CHANGE;
+
+                client->lease = sd_dhcp_lease_unref(client->lease);
+        }
+
+        client->lease = TAKE_PTR(lease);
+
+        log_dhcp_client(client, "ACK");
+
+        return r;
+}
+
+static int client_set_lease_timeouts(sd_dhcp_client *client) {
+        usec_t time_now;
+        char time_string[FORMAT_TIMESPAN_MAX];
+        int r;
+
+        assert(client);
+        assert(client->event);
+        assert(client->lease);
+        assert(client->lease->lifetime);
+
+        /* don't set timers for infinite leases */
+        if (client->lease->lifetime == 0xffffffff) {
+                (void) event_source_disable(client->timeout_t1);
+                (void) event_source_disable(client->timeout_t2);
+                (void) event_source_disable(client->timeout_expire);
+
+                return 0;
+        }
+
+        r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+        if (r < 0)
+                return r;
+        assert(client->request_sent <= time_now);
+
+        /* verify that 0 < t2 < lifetime */
+        if (client->lease->t2 == 0 || client->lease->t2 >= client->lease->lifetime)
+                client->lease->t2 = T2_DEFAULT(client->lease->lifetime);
+        /* verify that 0 < t1 < lifetime */
+        if (client->lease->t1 == 0 || client->lease->t1 >= client->lease->t2)
+                client->lease->t1 = T1_DEFAULT(client->lease->lifetime);
+        /* now, if t1 >= t2, t1 *must* be T1_DEFAULT, since the previous check
+         * could not evalate to false if t1 >= t2; so setting t2 to T2_DEFAULT
+         * guarantees t1 < t2. */
+        if (client->lease->t1 >= client->lease->t2)
+                client->lease->t2 = T2_DEFAULT(client->lease->lifetime);
+
+        client->expire_time = client->request_sent + client->lease->lifetime * USEC_PER_SEC;
+        client->t1_time = client->request_sent + client->lease->t1 * USEC_PER_SEC;
+        client->t2_time = client->request_sent + client->lease->t2 * USEC_PER_SEC;
+
+        /* RFC2131 section 4.4.5:
+         * Times T1 and T2 SHOULD be chosen with some random "fuzz".
+         * Since the RFC doesn't specify here the exact 'fuzz' to use,
+         * we use the range from section 4.1: -1 to +1 sec. */
+        client->t1_time = usec_sub_signed(client->t1_time, RFC2131_RANDOM_FUZZ);
+        client->t2_time = usec_sub_signed(client->t2_time, RFC2131_RANDOM_FUZZ);
+
+        /* after fuzzing, ensure t2 is still >= t1 */
+        client->t2_time = MAX(client->t1_time, client->t2_time);
+
+        /* arm lifetime timeout */
+        r = event_reset_time(client->event, &client->timeout_expire,
+                             clock_boottime_or_monotonic(),
+                             client->expire_time, 10 * USEC_PER_MSEC,
+                             client_timeout_expire, client,
+                             client->event_priority, "dhcp4-lifetime", true);
+        if (r < 0)
+                return r;
+
+        /* don't arm earlier timeouts if this has already expired */
+        if (client->expire_time <= time_now)
+                return 0;
+
+        log_dhcp_client(client, "lease expires in %s",
+                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->expire_time - time_now, USEC_PER_SEC));
+
+        /* arm T2 timeout */
+        r = event_reset_time(client->event, &client->timeout_t2,
+                             clock_boottime_or_monotonic(),
+                             client->t2_time, 10 * USEC_PER_MSEC,
+                             client_timeout_t2, client,
+                             client->event_priority, "dhcp4-t2-timeout", true);
+        if (r < 0)
+                return r;
+
+        /* don't arm earlier timeout if this has already expired */
+        if (client->t2_time <= time_now)
+                return 0;
+
+        log_dhcp_client(client, "T2 expires in %s",
+                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t2_time - time_now, USEC_PER_SEC));
+
+        /* arm T1 timeout */
+        r = event_reset_time(client->event, &client->timeout_t1,
+                             clock_boottime_or_monotonic(),
+                             client->t1_time, 10 * USEC_PER_MSEC,
+                             client_timeout_t1, client,
+                             client->event_priority, "dhcp4-t1-timer", true);
+        if (r < 0)
+                return r;
+
+        if (client->t1_time > time_now)
+                log_dhcp_client(client, "T1 expires in %s",
+                                format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t1_time - time_now, USEC_PER_SEC));
+
+        return 0;
+}
+
+static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) {
+        DHCP_CLIENT_DONT_DESTROY(client);
+        char time_string[FORMAT_TIMESPAN_MAX];
+        int r = 0, notify_event = 0;
+
+        assert(client);
+        assert(client->event);
+        assert(message);
+
+        switch (client->state) {
+        case DHCP_STATE_SELECTING:
+
+                r = client_handle_offer(client, message, len);
+                if (r >= 0) {
+
+                        client->state = DHCP_STATE_REQUESTING;
+                        client->attempt = 0;
+
+                        r = event_reset_time(client->event, &client->timeout_resend,
+                                             clock_boottime_or_monotonic(),
+                                             0, 0,
+                                             client_timeout_resend, client,
+                                             client->event_priority, "dhcp4-resend-timer", true);
+                        if (r < 0)
+                                goto error;
+                } else if (r == -ENOMSG)
+                        /* invalid message, let's ignore it */
+                        return 0;
+
+                break;
+
+        case DHCP_STATE_REBOOTING:
+        case DHCP_STATE_REQUESTING:
+        case DHCP_STATE_RENEWING:
+        case DHCP_STATE_REBINDING:
+
+                r = client_handle_ack(client, message, len);
+                if (r >= 0) {
+                        client->start_delay = 0;
+                        (void) event_source_disable(client->timeout_resend);
+                        client->receive_message =
+                                sd_event_source_unref(client->receive_message);
+                        client->fd = safe_close(client->fd);
+
+                        if (IN_SET(client->state, DHCP_STATE_REQUESTING,
+                                   DHCP_STATE_REBOOTING))
+                                notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
+                        else if (r != SD_DHCP_CLIENT_EVENT_IP_ACQUIRE)
+                                notify_event = r;
+
+                        client->state = DHCP_STATE_BOUND;
+                        client->attempt = 0;
+
+                        client->last_addr = client->lease->address;
+
+                        r = client_set_lease_timeouts(client);
+                        if (r < 0) {
+                                log_dhcp_client(client, "could not set lease timeouts");
+                                goto error;
+                        }
+
+                        r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type);
+                        if (r < 0) {
+                                log_dhcp_client(client, "could not bind UDP socket");
+                                goto error;
+                        }
+
+                        client->fd = r;
+
+                        client_initialize_io_events(client, client_receive_message_udp);
+
+                        if (notify_event) {
+                                client_notify(client, notify_event);
+                                if (client->state == DHCP_STATE_STOPPED)
+                                        return 0;
+                        }
+
+                } else if (r == -EADDRNOTAVAIL) {
+                        /* got a NAK, let's restart the client */
+                        client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
+
+                        r = client_initialize(client);
+                        if (r < 0)
+                                goto error;
+
+                        r = client_start_delayed(client);
+                        if (r < 0)
+                                goto error;
+
+                        log_dhcp_client(client, "REBOOT in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX,
+                                                                                client->start_delay, USEC_PER_SEC));
+
+                        client->start_delay = CLAMP(client->start_delay * 2,
+                                                    RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC);
+
+                        return 0;
+                } else if (r == -ENOMSG)
+                        /* invalid message, let's ignore it */
+                        return 0;
+
+                break;
+
+        case DHCP_STATE_BOUND:
+                r = client_handle_forcerenew(client, message, len);
+                if (r >= 0) {
+                        r = client_timeout_t1(NULL, 0, client);
+                        if (r < 0)
+                                goto error;
+                } else if (r == -ENOMSG)
+                        /* invalid message, let's ignore it */
+                        return 0;
+
+                break;
+
+        case DHCP_STATE_INIT:
+        case DHCP_STATE_INIT_REBOOT:
+
+                break;
+
+        case DHCP_STATE_STOPPED:
+                r = -EINVAL;
+                goto error;
+        }
+
+error:
+        if (r < 0)
+                client_stop(client, r);
+
+        return r;
+}
+
+static int client_receive_message_udp(
+                sd_event_source *s,
+                int fd,
+                uint32_t revents,
+                void *userdata) {
+
+        sd_dhcp_client *client = userdata;
+        _cleanup_free_ DHCPMessage *message = NULL;
+        const uint8_t *expected_chaddr = NULL;
+        uint8_t expected_hlen = 0;
+        ssize_t len, buflen;
+
+        assert(s);
+        assert(client);
+
+        buflen = next_datagram_size_fd(fd);
+        if (buflen == -ENETDOWN)
+                /* the link is down. Don't return an error or the I/O event
+                   source will be disconnected and we won't be able to receive
+                   packets again when the link comes back. */
+                return 0;
+        if (buflen < 0)
+                return buflen;
+
+        message = malloc0(buflen);
+        if (!message)
+                return -ENOMEM;
+
+        len = recv(fd, message, buflen, 0);
+        if (len < 0) {
+                /* see comment above for why we shouldn't error out on ENETDOWN. */
+                if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
+                        return 0;
+
+                return log_dhcp_client_errno(client, errno,
+                                             "Could not receive message from UDP socket: %m");
+        }
+        if ((size_t) len < sizeof(DHCPMessage)) {
+                log_dhcp_client(client, "Too small to be a DHCP message: ignoring");
+                return 0;
+        }
+
+        if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) {
+                log_dhcp_client(client, "Not a DHCP message: ignoring");
+                return 0;
+        }
+
+        if (message->op != BOOTREPLY) {
+                log_dhcp_client(client, "Not a BOOTREPLY message: ignoring");
+                return 0;
+        }
+
+        if (message->htype != client->arp_type) {
+                log_dhcp_client(client, "Packet type does not match client type");
+                return 0;
+        }
+
+        if (client->arp_type == ARPHRD_ETHER) {
+                expected_hlen = ETH_ALEN;
+                expected_chaddr = &client->mac_addr[0];
+        }
+
+        if (message->hlen != expected_hlen) {
+                log_dhcp_client(client, "Unexpected packet hlen %d", message->hlen);
+                return 0;
+        }
+
+        if (expected_hlen > 0 && memcmp(&message->chaddr[0], expected_chaddr, expected_hlen)) {
+                log_dhcp_client(client, "Received chaddr does not match expected: ignoring");
+                return 0;
+        }
+
+        if (client->state != DHCP_STATE_BOUND &&
+            be32toh(message->xid) != client->xid) {
+                /* in BOUND state, we may receive FORCERENEW with xid set by server,
+                   so ignore the xid in this case */
+                log_dhcp_client(client, "Received xid (%u) does not match expected (%u): ignoring",
+                                be32toh(message->xid), client->xid);
+                return 0;
+        }
+
+        return client_handle_message(client, message, len);
+}
+
+static int client_receive_message_raw(
+                sd_event_source *s,
+                int fd,
+                uint32_t revents,
+                void *userdata) {
+
+        sd_dhcp_client *client = userdata;
+        _cleanup_free_ DHCPPacket *packet = NULL;
+        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct tpacket_auxdata))) control;
+        struct iovec iov = {};
+        struct msghdr msg = {
+                .msg_iov = &iov,
+                .msg_iovlen = 1,
+                .msg_control = &control,
+                .msg_controllen = sizeof(control),
+        };
+        struct cmsghdr *cmsg;
+        bool checksum = true;
+        ssize_t buflen, len;
+        int r;
+
+        assert(s);
+        assert(client);
+
+        buflen = next_datagram_size_fd(fd);
+        if (buflen == -ENETDOWN)
+                return 0;
+        if (buflen < 0)
+                return buflen;
+
+        packet = malloc0(buflen);
+        if (!packet)
+                return -ENOMEM;
+
+        iov = IOVEC_MAKE(packet, buflen);
+
+        len = recvmsg_safe(fd, &msg, 0);
+        if (IN_SET(len, -EAGAIN, -EINTR, -ENETDOWN))
+                return 0;
+        if (len < 0)
+                return log_dhcp_client_errno(client, len,
+                                             "Could not receive message from raw socket: %m");
+
+        if ((size_t) len < sizeof(DHCPPacket))
+                return 0;
+
+        cmsg = cmsg_find(&msg, SOL_PACKET, PACKET_AUXDATA, CMSG_LEN(sizeof(struct tpacket_auxdata)));
+        if (cmsg) {
+                struct tpacket_auxdata *aux = (struct tpacket_auxdata*) CMSG_DATA(cmsg);
+                checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
+        }
+
+        r = dhcp_packet_verify_headers(packet, len, checksum, client->port);
+        if (r < 0)
+                return 0;
+
+        len -= DHCP_IP_UDP_SIZE;
+
+        return client_handle_message(client, &packet->dhcp, len);
+}
+
+int sd_dhcp_client_send_renew(sd_dhcp_client *client) {
+        assert_return(client, -EINVAL);
+        assert_return(client->fd >= 0, -EINVAL);
+
+        if (!client->lease)
+                return 0;
+
+        client->start_delay = 0;
+        client->attempt = 1;
+        client->state = DHCP_STATE_RENEWING;
+
+        return client_initialize_time_events(client);
+}
+
+int sd_dhcp_client_start(sd_dhcp_client *client) {
+        int r;
+
+        assert_return(client, -EINVAL);
+
+        r = client_initialize(client);
+        if (r < 0)
+                return r;
+
+        /* RFC7844 section 3.3:
+           SHOULD perform a complete four-way handshake, starting with a
+           DHCPDISCOVER, to obtain a new address lease.  If the client can
+           ascertain that this is exactly the same network to which it was
+           previously connected, and if the link-layer address did not change,
+           the client MAY issue a DHCPREQUEST to try to reclaim the current
+           address. */
+        if (client->last_addr && !client->anonymize)
+                client->state = DHCP_STATE_INIT_REBOOT;
+
+        r = client_start(client);
+        if (r >= 0)
+                log_dhcp_client(client, "STARTED on ifindex %i", client->ifindex);
+
+        return r;
+}
+
+int sd_dhcp_client_send_release(sd_dhcp_client *client) {
+        assert_return(client, -EINVAL);
+        assert_return(client->state != DHCP_STATE_STOPPED, -ESTALE);
+        assert_return(client->lease, -EUNATCH);
+
+        _cleanup_free_ DHCPPacket *release = NULL;
+        size_t optoffset, optlen;
+        int r;
+
+        r = client_message_init(client, &release, DHCP_RELEASE, &optlen, &optoffset);
+        if (r < 0)
+                return r;
+
+        /* Fill up release IP and MAC */
+        release->dhcp.ciaddr = client->lease->address;
+        memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len);
+
+        r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0,
+                               SD_DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
+
+        r = dhcp_network_send_udp_socket(client->fd,
+                                         client->lease->server_address,
+                                         DHCP_PORT_SERVER,
+                                         &release->dhcp,
+                                         sizeof(DHCPMessage) + optoffset);
+        if (r < 0)
+                return r;
+
+        log_dhcp_client(client, "RELEASE");
+
+        return 0;
+}
+
+int sd_dhcp_client_send_decline(sd_dhcp_client *client) {
+        assert_return(client, -EINVAL);
+        assert_return(client->state != DHCP_STATE_STOPPED, -ESTALE);
+        assert_return(client->lease, -EUNATCH);
+
+        _cleanup_free_ DHCPPacket *release = NULL;
+        size_t optoffset, optlen;
+        int r;
+
+        r = client_message_init(client, &release, DHCP_DECLINE, &optlen, &optoffset);
+        if (r < 0)
+                return r;
+
+        release->dhcp.ciaddr = client->lease->address;
+        memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len);
+
+        r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0,
+                               SD_DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
+
+        r = dhcp_network_send_udp_socket(client->fd,
+                                         client->lease->server_address,
+                                         DHCP_PORT_SERVER,
+                                         &release->dhcp,
+                                         sizeof(DHCPMessage) + optoffset);
+        if (r < 0)
+                return r;
+
+        log_dhcp_client(client, "DECLINE");
+
+        client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+
+        if (client->state != DHCP_STATE_STOPPED) {
+                r = sd_dhcp_client_start(client);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int sd_dhcp_client_stop(sd_dhcp_client *client) {
+        if (!client)
+                return 0;
+
+        DHCP_CLIENT_DONT_DESTROY(client);
+
+        client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+        client->state = DHCP_STATE_STOPPED;
+
+        return 0;
+}
+
+int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(!client->event, -EBUSY);
+
+        if (event)
+                client->event = sd_event_ref(event);
+        else {
+                r = sd_event_default(&client->event);
+                if (r < 0)
+                        return 0;
+        }
+
+        client->event_priority = priority;
+
+        return 0;
+}
+
+int sd_dhcp_client_detach_event(sd_dhcp_client *client) {
+        assert_return(client, -EINVAL);
+
+        client->event = sd_event_unref(client->event);
+
+        return 0;
+}
+
+sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) {
+        assert_return(client, NULL);
+
+        return client->event;
+}
+
+static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) {
+        if (!client)
+                return NULL;
+
+        log_dhcp_client(client, "FREE");
+
+        client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+        client->timeout_t1 = sd_event_source_unref(client->timeout_t1);
+        client->timeout_t2 = sd_event_source_unref(client->timeout_t2);
+        client->timeout_expire = sd_event_source_unref(client->timeout_expire);
+
+        client_initialize(client);
+
+        sd_dhcp_client_detach_event(client);
+
+        sd_dhcp_lease_unref(client->lease);
+
+        set_free(client->req_opts);
+        free(client->hostname);
+        free(client->vendor_class_identifier);
+        free(client->mudurl);
+        client->user_class = strv_free(client->user_class);
+        ordered_hashmap_free(client->extra_options);
+        ordered_hashmap_free(client->vendor_options);
+        return mfree(client);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client, sd_dhcp_client, dhcp_client_free);
+
+int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
+        const uint8_t *opts;
+        size_t n_opts;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = new(sd_dhcp_client, 1);
+        if (!client)
+                return -ENOMEM;
+
+        *client = (sd_dhcp_client) {
+                .n_ref = 1,
+                .state = DHCP_STATE_INIT,
+                .ifindex = -1,
+                .fd = -1,
+                .mtu = DHCP_DEFAULT_MIN_SIZE,
+                .port = DHCP_PORT_CLIENT,
+                .anonymize = !!anonymize,
+                .max_attempts = (uint64_t) -1,
+                .ip_service_type = -1,
+        };
+        /* NOTE: this could be moved to a function. */
+        if (anonymize) {
+                n_opts = ELEMENTSOF(default_req_opts_anonymize);
+                opts = default_req_opts_anonymize;
+        } else {
+                n_opts = ELEMENTSOF(default_req_opts);
+                opts = default_req_opts;
+        }
+
+        for (size_t i = 0; i < n_opts; i++) {
+                r = sd_dhcp_client_set_request_option(client, opts[i]);
+                if (r < 0)
+                        return r;
+        }
+
+        *ret = TAKE_PTR(client);
+
+        return 0;
+}
diff --git a/src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c
new file mode 100644
index 00000000..94b0d35c
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -0,0 +1,1401 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "sd-dhcp-lease.h"
+
+#include "alloc-util.h"
+#include "dhcp-lease-internal.h"
+#include "dhcp-protocol.h"
+#include "dns-domain.h"
+#include "env-file.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "hexdecoct.h"
+#include "hostname-util.h"
+#include "in-addr-util.h"
+#include "network-internal.h"
+#include "parse-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+#include "unaligned.h"
+
+int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr) {
+        assert_return(lease, -EINVAL);
+        assert_return(addr, -EINVAL);
+
+        if (lease->address == 0)
+                return -ENODATA;
+
+        addr->s_addr = lease->address;
+        return 0;
+}
+
+int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr) {
+        assert_return(lease, -EINVAL);
+        assert_return(addr, -EINVAL);
+
+        if (!lease->have_broadcast)
+                return -ENODATA;
+
+        addr->s_addr = lease->broadcast;
+        return 0;
+}
+
+int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime) {
+        assert_return(lease, -EINVAL);
+        assert_return(lifetime, -EINVAL);
+
+        if (lease->lifetime <= 0)
+                return -ENODATA;
+
+        *lifetime = lease->lifetime;
+        return 0;
+}
+
+int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1) {
+        assert_return(lease, -EINVAL);
+        assert_return(t1, -EINVAL);
+
+        if (lease->t1 <= 0)
+                return -ENODATA;
+
+        *t1 = lease->t1;
+        return 0;
+}
+
+int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2) {
+        assert_return(lease, -EINVAL);
+        assert_return(t2, -EINVAL);
+
+        if (lease->t2 <= 0)
+                return -ENODATA;
+
+        *t2 = lease->t2;
+        return 0;
+}
+
+int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu) {
+        assert_return(lease, -EINVAL);
+        assert_return(mtu, -EINVAL);
+
+        if (lease->mtu <= 0)
+                return -ENODATA;
+
+        *mtu = lease->mtu;
+        return 0;
+}
+
+int sd_dhcp_lease_get_servers(
+                sd_dhcp_lease *lease,
+                sd_dhcp_lease_server_type what,
+                const struct in_addr **addr) {
+
+        assert_return(lease, -EINVAL);
+        assert_return(what >= 0, -EINVAL);
+        assert_return(what < _SD_DHCP_LEASE_SERVER_TYPE_MAX, -EINVAL);
+        assert_return(addr, -EINVAL);
+
+        if (lease->servers[what].size <= 0)
+                return -ENODATA;
+
+        *addr = lease->servers[what].addr;
+        return (int) lease->servers[what].size;
+}
+
+int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_DNS, addr);
+}
+int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_NTP, addr);
+}
+int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SIP, addr);
+}
+int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_POP3, addr);
+}
+int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SMTP, addr);
+}
+int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_LPR, addr);
+}
+
+int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) {
+        assert_return(lease, -EINVAL);
+        assert_return(domainname, -EINVAL);
+
+        if (!lease->domainname)
+                return -ENODATA;
+
+        *domainname = lease->domainname;
+        return 0;
+}
+
+int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname) {
+        assert_return(lease, -EINVAL);
+        assert_return(hostname, -EINVAL);
+
+        if (!lease->hostname)
+                return -ENODATA;
+
+        *hostname = lease->hostname;
+        return 0;
+}
+
+int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path) {
+        assert_return(lease, -EINVAL);
+        assert_return(root_path, -EINVAL);
+
+        if (!lease->root_path)
+                return -ENODATA;
+
+        *root_path = lease->root_path;
+        return 0;
+}
+
+int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        assert_return(lease, -EINVAL);
+        assert_return(addr, -EINVAL);
+
+        if (lease->router_size <= 0)
+                return -ENODATA;
+
+        *addr = lease->router;
+        return (int) lease->router_size;
+}
+
+int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) {
+        assert_return(lease, -EINVAL);
+        assert_return(addr, -EINVAL);
+
+        if (!lease->have_subnet_mask)
+                return -ENODATA;
+
+        addr->s_addr = lease->subnet_mask;
+        return 0;
+}
+
+int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr) {
+        assert_return(lease, -EINVAL);
+        assert_return(addr, -EINVAL);
+
+        if (lease->server_address == 0)
+                return -ENODATA;
+
+        addr->s_addr = lease->server_address;
+        return 0;
+}
+
+int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr) {
+        assert_return(lease, -EINVAL);
+        assert_return(addr, -EINVAL);
+
+        if (lease->next_server == 0)
+                return -ENODATA;
+
+        addr->s_addr = lease->next_server;
+        return 0;
+}
+
+/*
+ * The returned routes array must be freed by the caller.
+ * Route objects have the same lifetime of the lease and must not be freed.
+ */
+int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes) {
+        sd_dhcp_route **ret;
+        unsigned i;
+
+        assert_return(lease, -EINVAL);
+        assert_return(routes, -EINVAL);
+
+        if (lease->static_route_size <= 0)
+                return -ENODATA;
+
+        ret = new(sd_dhcp_route *, lease->static_route_size);
+        if (!ret)
+                return -ENOMEM;
+
+        for (i = 0; i < lease->static_route_size; i++)
+                ret[i] = &lease->static_route[i];
+
+        *routes = ret;
+        return (int) lease->static_route_size;
+}
+
+int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) {
+        size_t r;
+
+        assert_return(lease, -EINVAL);
+        assert_return(domains, -EINVAL);
+
+        r = strv_length(lease->search_domains);
+        if (r > 0) {
+                *domains = lease->search_domains;
+                return (int) r;
+        }
+
+        return -ENODATA;
+}
+
+int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) {
+        assert_return(lease, -EINVAL);
+        assert_return(data, -EINVAL);
+        assert_return(data_len, -EINVAL);
+
+        if (lease->vendor_specific_len <= 0)
+                return -ENODATA;
+
+        *data = lease->vendor_specific;
+        *data_len = lease->vendor_specific_len;
+        return 0;
+}
+
+static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) {
+        assert(lease);
+
+        while (lease->private_options) {
+                struct sd_dhcp_raw_option *option = lease->private_options;
+
+                LIST_REMOVE(options, lease->private_options, option);
+
+                free(option->data);
+                free(option);
+        }
+
+        free(lease->root_path);
+        free(lease->router);
+        free(lease->timezone);
+        free(lease->hostname);
+        free(lease->domainname);
+
+        for (sd_dhcp_lease_server_type i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++)
+                free(lease->servers[i].addr);
+
+        free(lease->static_route);
+        free(lease->client_id);
+        free(lease->vendor_specific);
+        strv_free(lease->search_domains);
+        return mfree(lease);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_lease, sd_dhcp_lease, dhcp_lease_free);
+
+static int lease_parse_u32(const uint8_t *option, size_t len, uint32_t *ret, uint32_t min) {
+        assert(option);
+        assert(ret);
+
+        if (len != 4)
+                return -EINVAL;
+
+        *ret = unaligned_read_be32((be32_t*) option);
+        if (*ret < min)
+                *ret = min;
+
+        return 0;
+}
+
+static int lease_parse_u16(const uint8_t *option, size_t len, uint16_t *ret, uint16_t min) {
+        assert(option);
+        assert(ret);
+
+        if (len != 2)
+                return -EINVAL;
+
+        *ret = unaligned_read_be16((be16_t*) option);
+        if (*ret < min)
+                *ret = min;
+
+        return 0;
+}
+
+static int lease_parse_be32(const uint8_t *option, size_t len, be32_t *ret) {
+        assert(option);
+        assert(ret);
+
+        if (len != 4)
+                return -EINVAL;
+
+        memcpy(ret, option, 4);
+        return 0;
+}
+
+static int lease_parse_string(const uint8_t *option, size_t len, char **ret) {
+        assert(option);
+        assert(ret);
+
+        if (len <= 0)
+                *ret = mfree(*ret);
+        else {
+                char *string;
+
+                /*
+                 * One trailing NUL byte is OK, we don't mind. See:
+                 * https://github.com/systemd/systemd/issues/1337
+                 */
+                if (memchr(option, 0, len - 1))
+                        return -EINVAL;
+
+                string = memdup_suffix0((const char *) option, len);
+                if (!string)
+                        return -ENOMEM;
+
+                free_and_replace(*ret, string);
+        }
+
+        return 0;
+}
+
+static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) {
+        _cleanup_free_ char *name = NULL, *normalized = NULL;
+        int r;
+
+        assert(option);
+        assert(ret);
+
+        r = lease_parse_string(option, len, &name);
+        if (r < 0)
+                return r;
+        if (!name) {
+                *ret = mfree(*ret);
+                return 0;
+        }
+
+        r = dns_name_normalize(name, 0, &normalized);
+        if (r < 0)
+                return r;
+
+        if (is_localhost(normalized))
+                return -EINVAL;
+
+        if (dns_name_is_root(normalized))
+                return -EINVAL;
+
+        free_and_replace(*ret, normalized);
+
+        return 0;
+}
+
+static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) {
+        assert(option || len == 0);
+        assert(ret);
+        assert(n_ret);
+
+        if (len <= 0) {
+                *ret = mfree(*ret);
+                *n_ret = 0;
+        } else {
+                size_t n_addresses;
+                struct in_addr *addresses;
+
+                if (len % 4 != 0)
+                        return -EINVAL;
+
+                n_addresses = len / 4;
+
+                addresses = newdup(struct in_addr, option, n_addresses);
+                if (!addresses)
+                        return -ENOMEM;
+
+                free(*ret);
+                *ret = addresses;
+                *n_ret = n_addresses;
+        }
+
+        return 0;
+}
+
+static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) {
+        assert(option || len == 0);
+        assert(ret);
+        assert(n_ret);
+
+        if (len <= 0)
+                return -EINVAL;
+
+        /* The SIP record is like the other, regular server records, but prefixed with a single "encoding"
+         * byte that is either 0 or 1. We only support it to be 1 for now. Let's drop it and parse it like
+         * the other fields */
+
+        if (option[0] != 1) { /* We only support IP address encoding for now */
+                *ret = mfree(*ret);
+                *n_ret = 0;
+                return 0;
+        }
+
+        return lease_parse_in_addrs(option + 1, len - 1, ret, n_ret);
+}
+
+static int lease_parse_routes(
+                const uint8_t *option, size_t len,
+                struct sd_dhcp_route **routes, size_t *routes_size, size_t *routes_allocated) {
+
+        struct in_addr addr;
+
+        assert(option || len <= 0);
+        assert(routes);
+        assert(routes_size);
+        assert(routes_allocated);
+
+        if (len <= 0)
+                return 0;
+
+        if (len % 8 != 0)
+                return -EINVAL;
+
+        if (!GREEDY_REALLOC(*routes, *routes_allocated, *routes_size + (len / 8)))
+                return -ENOMEM;
+
+        while (len >= 8) {
+                struct sd_dhcp_route *route = *routes + *routes_size;
+                int r;
+
+                route->option = SD_DHCP_OPTION_STATIC_ROUTE;
+                r = in4_addr_default_prefixlen((struct in_addr*) option, &route->dst_prefixlen);
+                if (r < 0) {
+                        log_debug("Failed to determine destination prefix length from class based IP, ignoring");
+                        continue;
+                }
+
+                assert_se(lease_parse_be32(option, 4, &addr.s_addr) >= 0);
+                route->dst_addr = inet_makeaddr(inet_netof(addr), 0);
+                option += 4;
+
+                assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0);
+                option += 4;
+
+                len -= 8;
+                (*routes_size)++;
+        }
+
+        return 0;
+}
+
+/* parses RFC3442 Classless Static Route Option */
+static int lease_parse_classless_routes(
+                const uint8_t *option, size_t len,
+                struct sd_dhcp_route **routes, size_t *routes_size, size_t *routes_allocated) {
+
+        assert(option || len <= 0);
+        assert(routes);
+        assert(routes_size);
+        assert(routes_allocated);
+
+        if (len <= 0)
+                return 0;
+
+        /* option format: (subnet-mask-width significant-subnet-octets gateway-ip)*  */
+
+        while (len > 0) {
+                uint8_t dst_octets;
+                struct sd_dhcp_route *route;
+
+                if (!GREEDY_REALLOC(*routes, *routes_allocated, *routes_size + 1))
+                        return -ENOMEM;
+
+                route = *routes + *routes_size;
+                route->option = SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE;
+
+                dst_octets = (*option == 0 ? 0 : ((*option - 1) / 8) + 1);
+                route->dst_prefixlen = *option;
+                option++;
+                len--;
+
+                /* can't have more than 4 octets in IPv4 */
+                if (dst_octets > 4 || len < dst_octets)
+                        return -EINVAL;
+
+                route->dst_addr.s_addr = 0;
+                memcpy(&route->dst_addr.s_addr, option, dst_octets);
+                option += dst_octets;
+                len -= dst_octets;
+
+                if (len < 4)
+                        return -EINVAL;
+
+                assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0);
+                option += 4;
+                len -= 4;
+
+                (*routes_size)++;
+        }
+
+        return 0;
+}
+
+int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata) {
+        sd_dhcp_lease *lease = userdata;
+        int r;
+
+        assert(lease);
+
+        switch(code) {
+
+        case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
+                r = lease_parse_u32(option, len, &lease->lifetime, 1);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse lease time, ignoring: %m");
+
+                break;
+
+        case SD_DHCP_OPTION_SERVER_IDENTIFIER:
+                r = lease_parse_be32(option, len, &lease->server_address);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse server identifier, ignoring: %m");
+
+                break;
+
+        case SD_DHCP_OPTION_SUBNET_MASK:
+                r = lease_parse_be32(option, len, &lease->subnet_mask);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse subnet mask, ignoring: %m");
+                else
+                        lease->have_subnet_mask = true;
+                break;
+
+        case SD_DHCP_OPTION_BROADCAST:
+                r = lease_parse_be32(option, len, &lease->broadcast);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse broadcast address, ignoring: %m");
+                else
+                        lease->have_broadcast = true;
+                break;
+
+        case SD_DHCP_OPTION_ROUTER:
+                r = lease_parse_in_addrs(option, len, &lease->router, &lease->router_size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse router addresses, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_DOMAIN_NAME_SERVER:
+                r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_DNS].addr, &lease->servers[SD_DHCP_LEASE_DNS].size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse DNS server, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_NTP_SERVER:
+                r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_NTP].addr, &lease->servers[SD_DHCP_LEASE_NTP].size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse NTP server, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_SIP_SERVER:
+                r = lease_parse_sip_server(option, len, &lease->servers[SD_DHCP_LEASE_SIP].addr, &lease->servers[SD_DHCP_LEASE_SIP].size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse SIP server, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_POP3_SERVER:
+                r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_POP3].addr, &lease->servers[SD_DHCP_LEASE_POP3].size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse POP3 server, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_SMTP_SERVER:
+                r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_SMTP].addr, &lease->servers[SD_DHCP_LEASE_SMTP].size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse SMTP server, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_LPR_SERVER:
+                r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_LPR].addr, &lease->servers[SD_DHCP_LEASE_LPR].size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse LPR server, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_STATIC_ROUTE:
+                r = lease_parse_routes(option, len, &lease->static_route, &lease->static_route_size, &lease->static_route_allocated);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse static routes, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_INTERFACE_MTU:
+                r = lease_parse_u16(option, len, &lease->mtu, 68);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse MTU, ignoring: %m");
+                if (lease->mtu < DHCP_DEFAULT_MIN_SIZE) {
+                        log_debug("MTU value of %" PRIu16 " too small. Using default MTU value of %d instead.", lease->mtu, DHCP_DEFAULT_MIN_SIZE);
+                        lease->mtu = DHCP_DEFAULT_MIN_SIZE;
+                }
+
+                break;
+
+        case SD_DHCP_OPTION_DOMAIN_NAME:
+                r = lease_parse_domain(option, len, &lease->domainname);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to parse domain name, ignoring: %m");
+                        return 0;
+                }
+
+                break;
+
+        case SD_DHCP_OPTION_DOMAIN_SEARCH_LIST:
+                r = dhcp_lease_parse_search_domains(option, len, &lease->search_domains);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse Domain Search List, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_HOST_NAME:
+                r = lease_parse_domain(option, len, &lease->hostname);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to parse hostname, ignoring: %m");
+                        return 0;
+                }
+
+                break;
+
+        case SD_DHCP_OPTION_ROOT_PATH:
+                r = lease_parse_string(option, len, &lease->root_path);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse root path, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_RENEWAL_T1_TIME:
+                r = lease_parse_u32(option, len, &lease->t1, 1);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse T1 time, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_REBINDING_T2_TIME:
+                r = lease_parse_u32(option, len, &lease->t2, 1);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse T2 time, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
+                r = lease_parse_classless_routes(
+                                option, len,
+                                &lease->static_route,
+                                &lease->static_route_size,
+                                &lease->static_route_allocated);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse classless routes, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_NEW_TZDB_TIMEZONE: {
+                _cleanup_free_ char *tz = NULL;
+
+                r = lease_parse_string(option, len, &tz);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to parse timezone option, ignoring: %m");
+                        return 0;
+                }
+
+                if (!timezone_is_valid(tz, LOG_DEBUG)) {
+                        log_debug_errno(r, "Timezone is not valid, ignoring: %m");
+                        return 0;
+                }
+
+                free_and_replace(lease->timezone, tz);
+
+                break;
+        }
+
+        case SD_DHCP_OPTION_VENDOR_SPECIFIC:
+
+                if (len <= 0)
+                        lease->vendor_specific = mfree(lease->vendor_specific);
+                else {
+                        void *p;
+
+                        p = memdup(option, len);
+                        if (!p)
+                                return -ENOMEM;
+
+                        free(lease->vendor_specific);
+                        lease->vendor_specific = p;
+                }
+
+                lease->vendor_specific_len = len;
+                break;
+
+        case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST:
+                r = dhcp_lease_insert_private_option(lease, code, option, len);
+                if (r < 0)
+                        return r;
+
+                break;
+
+        default:
+                log_debug("Ignoring option DHCP option %"PRIu8" while parsing.", code);
+                break;
+        }
+
+        return 0;
+}
+
+/* Parses compressed domain names. */
+int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains) {
+        _cleanup_strv_free_ char **names = NULL;
+        size_t pos = 0, cnt = 0;
+        int r;
+
+        assert(domains);
+        assert_return(option && len > 0, -ENODATA);
+
+        while (pos < len) {
+                _cleanup_free_ char *name = NULL;
+                size_t n = 0, allocated = 0;
+                size_t jump_barrier = pos, next_chunk = 0;
+                bool first = true;
+
+                for (;;) {
+                        uint8_t c;
+                        c = option[pos++];
+
+                        if (c == 0) {
+                                /* End of name */
+                                break;
+                        } else if (c <= 63) {
+                                const char *label;
+
+                                /* Literal label */
+                                label = (const char*) (option + pos);
+                                pos += c;
+                                if (pos >= len)
+                                        return -EBADMSG;
+
+                                if (!GREEDY_REALLOC(name, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+                                        return -ENOMEM;
+
+                                if (first)
+                                        first = false;
+                                else
+                                        name[n++] = '.';
+
+                                r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX);
+                                if (r < 0)
+                                        return r;
+
+                                n += r;
+                        } else if (FLAGS_SET(c, 0xc0)) {
+                                /* Pointer */
+
+                                uint8_t d;
+                                uint16_t ptr;
+
+                                if (pos >= len)
+                                        return -EBADMSG;
+
+                                d = option[pos++];
+                                ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d;
+
+                                /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */
+                                if (ptr >= jump_barrier)
+                                        return -EBADMSG;
+                                jump_barrier = ptr;
+
+                                /* Save current location so we don't end up re-parsing what's parsed so far. */
+                                if (next_chunk == 0)
+                                        next_chunk = pos;
+
+                                pos = ptr;
+                        } else
+                                return -EBADMSG;
+                }
+
+                if (!GREEDY_REALLOC(name, allocated, n + 1))
+                        return -ENOMEM;
+                name[n] = 0;
+
+                r = strv_extend(&names, name);
+                if (r < 0)
+                        return r;
+
+                cnt++;
+
+                if (next_chunk != 0)
+                      pos = next_chunk;
+        }
+
+        *domains = TAKE_PTR(names);
+
+        return cnt;
+}
+
+int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) {
+        struct sd_dhcp_raw_option *cur, *option;
+
+        assert(lease);
+
+        LIST_FOREACH(options, cur, lease->private_options) {
+                if (tag < cur->tag)
+                        break;
+                if (tag == cur->tag) {
+                        log_debug("Ignoring duplicate option, tagged %i.", tag);
+                        return 0;
+                }
+        }
+
+        option = new(struct sd_dhcp_raw_option, 1);
+        if (!option)
+                return -ENOMEM;
+
+        option->tag = tag;
+        option->length = len;
+        option->data = memdup(data, len);
+        if (!option->data) {
+                free(option);
+                return -ENOMEM;
+        }
+
+        LIST_INSERT_BEFORE(options, lease->private_options, cur, option);
+        return 0;
+}
+
+int dhcp_lease_new(sd_dhcp_lease **ret) {
+        sd_dhcp_lease *lease;
+
+        lease = new0(sd_dhcp_lease, 1);
+        if (!lease)
+                return -ENOMEM;
+
+        lease->n_ref = 1;
+
+        *ret = lease;
+        return 0;
+}
+
+int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
+        _cleanup_free_ char *temp_path = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        struct sd_dhcp_raw_option *option;
+        struct in_addr address;
+        const struct in_addr *addresses;
+        const void *client_id, *data;
+        size_t client_id_len, data_len;
+        char sbuf[INET_ADDRSTRLEN];
+        const char *string;
+        uint16_t mtu;
+        _cleanup_free_ sd_dhcp_route **routes = NULL;
+        char **search_domains = NULL;
+        uint32_t t1, t2, lifetime;
+        int r;
+
+        assert(lease);
+        assert(lease_file);
+
+        r = fopen_temporary(lease_file, &f, &temp_path);
+        if (r < 0)
+                goto fail;
+
+        (void) fchmod(fileno(f), 0644);
+
+        fprintf(f,
+                "# This is private data. Do not parse.\n");
+
+        r = sd_dhcp_lease_get_address(lease, &address);
+        if (r >= 0)
+                fprintf(f, "ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
+
+        r = sd_dhcp_lease_get_netmask(lease, &address);
+        if (r >= 0)
+                fprintf(f, "NETMASK=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
+
+        r = sd_dhcp_lease_get_router(lease, &addresses);
+        if (r > 0) {
+                fputs("ROUTER=", f);
+                serialize_in_addrs(f, addresses, r, false, NULL);
+                fputc('\n', f);
+        }
+
+        r = sd_dhcp_lease_get_server_identifier(lease, &address);
+        if (r >= 0)
+                fprintf(f, "SERVER_ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
+
+        r = sd_dhcp_lease_get_next_server(lease, &address);
+        if (r >= 0)
+                fprintf(f, "NEXT_SERVER=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
+
+        r = sd_dhcp_lease_get_broadcast(lease, &address);
+        if (r >= 0)
+                fprintf(f, "BROADCAST=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
+
+        r = sd_dhcp_lease_get_mtu(lease, &mtu);
+        if (r >= 0)
+                fprintf(f, "MTU=%" PRIu16 "\n", mtu);
+
+        r = sd_dhcp_lease_get_t1(lease, &t1);
+        if (r >= 0)
+                fprintf(f, "T1=%" PRIu32 "\n", t1);
+
+        r = sd_dhcp_lease_get_t2(lease, &t2);
+        if (r >= 0)
+                fprintf(f, "T2=%" PRIu32 "\n", t2);
+
+        r = sd_dhcp_lease_get_lifetime(lease, &lifetime);
+        if (r >= 0)
+                fprintf(f, "LIFETIME=%" PRIu32 "\n", lifetime);
+
+        r = sd_dhcp_lease_get_dns(lease, &addresses);
+        if (r > 0) {
+                fputs("DNS=", f);
+                serialize_in_addrs(f, addresses, r, false, NULL);
+                fputc('\n', f);
+        }
+
+        r = sd_dhcp_lease_get_ntp(lease, &addresses);
+        if (r > 0) {
+                fputs("NTP=", f);
+                serialize_in_addrs(f, addresses, r, false, NULL);
+                fputc('\n', f);
+        }
+
+        r = sd_dhcp_lease_get_sip(lease, &addresses);
+        if (r > 0) {
+                fputs("SIP=", f);
+                serialize_in_addrs(f, addresses, r, false, NULL);
+                fputc('\n', f);
+        }
+
+        r = sd_dhcp_lease_get_domainname(lease, &string);
+        if (r >= 0)
+                fprintf(f, "DOMAINNAME=%s\n", string);
+
+        r = sd_dhcp_lease_get_search_domains(lease, &search_domains);
+        if (r > 0) {
+                fputs("DOMAIN_SEARCH_LIST=", f);
+                fputstrv(f, search_domains, NULL, NULL);
+                fputc('\n', f);
+        }
+
+        r = sd_dhcp_lease_get_hostname(lease, &string);
+        if (r >= 0)
+                fprintf(f, "HOSTNAME=%s\n", string);
+
+        r = sd_dhcp_lease_get_root_path(lease, &string);
+        if (r >= 0)
+                fprintf(f, "ROOT_PATH=%s\n", string);
+
+        r = sd_dhcp_lease_get_routes(lease, &routes);
+        if (r > 0)
+                serialize_dhcp_routes(f, "ROUTES", routes, r);
+
+        r = sd_dhcp_lease_get_timezone(lease, &string);
+        if (r >= 0)
+                fprintf(f, "TIMEZONE=%s\n", string);
+
+        r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len);
+        if (r >= 0) {
+                _cleanup_free_ char *client_id_hex = NULL;
+
+                client_id_hex = hexmem(client_id, client_id_len);
+                if (!client_id_hex) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+                fprintf(f, "CLIENTID=%s\n", client_id_hex);
+        }
+
+        r = sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len);
+        if (r >= 0) {
+                _cleanup_free_ char *option_hex = NULL;
+
+                option_hex = hexmem(data, data_len);
+                if (!option_hex) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+                fprintf(f, "VENDOR_SPECIFIC=%s\n", option_hex);
+        }
+
+        LIST_FOREACH(options, option, lease->private_options) {
+                char key[STRLEN("OPTION_000")+1];
+
+                xsprintf(key, "OPTION_%" PRIu8, option->tag);
+                r = serialize_dhcp_option(f, key, option->data, option->length);
+                if (r < 0)
+                        goto fail;
+        }
+
+        r = fflush_and_check(f);
+        if (r < 0)
+                goto fail;
+
+        if (rename(temp_path, lease_file) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        return 0;
+
+fail:
+        if (temp_path)
+                (void) unlink(temp_path);
+
+        return log_error_errno(r, "Failed to save lease data %s: %m", lease_file);
+}
+
+int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
+
+        _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+        _cleanup_free_ char
+                *address = NULL,
+                *router = NULL,
+                *netmask = NULL,
+                *server_address = NULL,
+                *next_server = NULL,
+                *broadcast = NULL,
+                *dns = NULL,
+                *ntp = NULL,
+                *sip = NULL,
+                *pop3 = NULL,
+                *smtp = NULL,
+                *lpr = NULL,
+                *mtu = NULL,
+                *routes = NULL,
+                *domains = NULL,
+                *client_id_hex = NULL,
+                *vendor_specific_hex = NULL,
+                *lifetime = NULL,
+                *t1 = NULL,
+                *t2 = NULL,
+                *options[SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1] = {};
+
+        int r, i;
+
+        assert(lease_file);
+        assert(ret);
+
+        r = dhcp_lease_new(&lease);
+        if (r < 0)
+                return r;
+
+        r = parse_env_file(NULL, lease_file,
+                           "ADDRESS", &address,
+                           "ROUTER", &router,
+                           "NETMASK", &netmask,
+                           "SERVER_ADDRESS", &server_address,
+                           "NEXT_SERVER", &next_server,
+                           "BROADCAST", &broadcast,
+                           "DNS", &dns,
+                           "NTP", &ntp,
+                           "SIP", &sip,
+                           "POP3", &pop3,
+                           "SMTP", &smtp,
+                           "LPR", &lpr,
+                           "MTU", &mtu,
+                           "DOMAINNAME", &lease->domainname,
+                           "HOSTNAME", &lease->hostname,
+                           "DOMAIN_SEARCH_LIST", &domains,
+                           "ROOT_PATH", &lease->root_path,
+                           "ROUTES", &routes,
+                           "CLIENTID", &client_id_hex,
+                           "TIMEZONE", &lease->timezone,
+                           "VENDOR_SPECIFIC", &vendor_specific_hex,
+                           "LIFETIME", &lifetime,
+                           "T1", &t1,
+                           "T2", &t2,
+                           "OPTION_224", &options[0],
+                           "OPTION_225", &options[1],
+                           "OPTION_226", &options[2],
+                           "OPTION_227", &options[3],
+                           "OPTION_228", &options[4],
+                           "OPTION_229", &options[5],
+                           "OPTION_230", &options[6],
+                           "OPTION_231", &options[7],
+                           "OPTION_232", &options[8],
+                           "OPTION_233", &options[9],
+                           "OPTION_234", &options[10],
+                           "OPTION_235", &options[11],
+                           "OPTION_236", &options[12],
+                           "OPTION_237", &options[13],
+                           "OPTION_238", &options[14],
+                           "OPTION_239", &options[15],
+                           "OPTION_240", &options[16],
+                           "OPTION_241", &options[17],
+                           "OPTION_242", &options[18],
+                           "OPTION_243", &options[19],
+                           "OPTION_244", &options[20],
+                           "OPTION_245", &options[21],
+                           "OPTION_246", &options[22],
+                           "OPTION_247", &options[23],
+                           "OPTION_248", &options[24],
+                           "OPTION_249", &options[25],
+                           "OPTION_250", &options[26],
+                           "OPTION_251", &options[27],
+                           "OPTION_252", &options[28],
+                           "OPTION_253", &options[29],
+                           "OPTION_254", &options[30]);
+        if (r < 0)
+                return r;
+
+        if (address) {
+                r = inet_pton(AF_INET, address, &lease->address);
+                if (r <= 0)
+                        log_debug("Failed to parse address %s, ignoring.", address);
+        }
+
+        if (router) {
+                r = deserialize_in_addrs(&lease->router, router);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize router addresses %s, ignoring: %m", router);
+                else
+                        lease->router_size = r;
+        }
+
+        if (netmask) {
+                r = inet_pton(AF_INET, netmask, &lease->subnet_mask);
+                if (r <= 0)
+                        log_debug("Failed to parse netmask %s, ignoring.", netmask);
+                else
+                        lease->have_subnet_mask = true;
+        }
+
+        if (server_address) {
+                r = inet_pton(AF_INET, server_address, &lease->server_address);
+                if (r <= 0)
+                        log_debug("Failed to parse server address %s, ignoring.", server_address);
+        }
+
+        if (next_server) {
+                r = inet_pton(AF_INET, next_server, &lease->next_server);
+                if (r <= 0)
+                        log_debug("Failed to parse next server %s, ignoring.", next_server);
+        }
+
+        if (broadcast) {
+                r = inet_pton(AF_INET, broadcast, &lease->broadcast);
+                if (r <= 0)
+                        log_debug("Failed to parse broadcast address %s, ignoring.", broadcast);
+                else
+                        lease->have_broadcast = true;
+        }
+
+        if (dns) {
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_DNS].addr, dns);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize DNS servers %s, ignoring: %m", dns);
+                else
+                        lease->servers[SD_DHCP_LEASE_DNS].size = r;
+        }
+
+        if (ntp) {
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_NTP].addr, ntp);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize NTP servers %s, ignoring: %m", ntp);
+                else
+                        lease->servers[SD_DHCP_LEASE_NTP].size = r;
+        }
+
+        if (sip) {
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SIP].addr, sip);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize SIP servers %s, ignoring: %m", sip);
+                else
+                        lease->servers[SD_DHCP_LEASE_SIP].size = r;
+        }
+
+        if (pop3) {
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_POP3].addr, pop3);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize POP3 server %s, ignoring: %m", pop3);
+                else
+                        lease->servers[SD_DHCP_LEASE_POP3].size = r;
+        }
+
+        if (smtp) {
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SMTP].addr, smtp);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize SMTP server %s, ignoring: %m", smtp);
+                else
+                        lease->servers[SD_DHCP_LEASE_SMTP].size = r;
+        }
+
+        if (lpr) {
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_LPR].addr, lpr);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize LPR server %s, ignoring: %m", lpr);
+                else
+                        lease->servers[SD_DHCP_LEASE_LPR].size = r;
+        }
+
+        if (mtu) {
+                r = safe_atou16(mtu, &lease->mtu);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse MTU %s, ignoring: %m", mtu);
+        }
+
+        if (domains) {
+                _cleanup_strv_free_ char **a = NULL;
+                a = strv_split(domains, " ");
+                if (!a)
+                        return -ENOMEM;
+
+                if (!strv_isempty(a))
+                        lease->search_domains = TAKE_PTR(a);
+        }
+
+        if (routes) {
+                r = deserialize_dhcp_routes(
+                                &lease->static_route,
+                                &lease->static_route_size,
+                                &lease->static_route_allocated,
+                                routes);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse DHCP routes %s, ignoring: %m", routes);
+        }
+
+        if (lifetime) {
+                r = safe_atou32(lifetime, &lease->lifetime);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse lifetime %s, ignoring: %m", lifetime);
+        }
+
+        if (t1) {
+                r = safe_atou32(t1, &lease->t1);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse T1 %s, ignoring: %m", t1);
+        }
+
+        if (t2) {
+                r = safe_atou32(t2, &lease->t2);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse T2 %s, ignoring: %m", t2);
+        }
+
+        if (client_id_hex) {
+                r = unhexmem(client_id_hex, (size_t) -1, &lease->client_id, &lease->client_id_len);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex);
+        }
+
+        if (vendor_specific_hex) {
+                r = unhexmem(vendor_specific_hex, (size_t) -1, &lease->vendor_specific, &lease->vendor_specific_len);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex);
+        }
+
+        for (i = 0; i <= SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE; i++) {
+                _cleanup_free_ void *data = NULL;
+                size_t len;
+
+                if (!options[i])
+                        continue;
+
+                r = unhexmem(options[i], (size_t) -1, &data, &len);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]);
+                        continue;
+                }
+
+                r = dhcp_lease_insert_private_option(lease, SD_DHCP_OPTION_PRIVATE_BASE + i, data, len);
+                if (r < 0)
+                        return r;
+        }
+
+        *ret = TAKE_PTR(lease);
+
+        return 0;
+}
+
+int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) {
+        struct in_addr address, mask;
+        int r;
+
+        assert(lease);
+
+        if (lease->address == 0)
+                return -ENODATA;
+
+        address.s_addr = lease->address;
+
+        /* fall back to the default subnet masks based on address class */
+        r = in4_addr_default_subnet_mask(&address, &mask);
+        if (r < 0)
+                return r;
+
+        lease->subnet_mask = mask.s_addr;
+        lease->have_subnet_mask = true;
+
+        return 0;
+}
+
+int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len) {
+        assert_return(lease, -EINVAL);
+        assert_return(client_id, -EINVAL);
+        assert_return(client_id_len, -EINVAL);
+
+        if (!lease->client_id)
+                return -ENODATA;
+
+        *client_id = lease->client_id;
+        *client_id_len = lease->client_id_len;
+
+        return 0;
+}
+
+int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len) {
+        assert_return(lease, -EINVAL);
+        assert_return(client_id || client_id_len <= 0, -EINVAL);
+
+        if (client_id_len <= 0)
+                lease->client_id = mfree(lease->client_id);
+        else {
+                void *p;
+
+                p = memdup(client_id, client_id_len);
+                if (!p)
+                        return -ENOMEM;
+
+                free(lease->client_id);
+                lease->client_id = p;
+                lease->client_id_len = client_id_len;
+        }
+
+        return 0;
+}
+
+int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **tz) {
+        assert_return(lease, -EINVAL);
+        assert_return(tz, -EINVAL);
+
+        if (!lease->timezone)
+                return -ENODATA;
+
+        *tz = lease->timezone;
+        return 0;
+}
+
+int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination) {
+        assert_return(route, -EINVAL);
+        assert_return(destination, -EINVAL);
+
+        *destination = route->dst_addr;
+        return 0;
+}
+
+int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length) {
+        assert_return(route, -EINVAL);
+        assert_return(length, -EINVAL);
+
+        *length = route->dst_prefixlen;
+        return 0;
+}
+
+int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) {
+        assert_return(route, -EINVAL);
+        assert_return(gateway, -EINVAL);
+
+        *gateway = route->gw_addr;
+        return 0;
+}
+
+int sd_dhcp_route_get_option(sd_dhcp_route *route) {
+        assert_return(route, -EINVAL);
+
+        return route->option;
+}
diff --git a/src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c
new file mode 100644
index 00000000..3fafd3c0
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -0,0 +1,1857 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <errno.h>
+#include <sys/ioctl.h>
+#if 0 /* NM_IGNORED */
+#include <linux/if_arp.h>
+#else /* NM_IGNORED */
+#include <net/if_arp.h>
+#endif /* NM_IGNORED */
+#include <linux/if_infiniband.h>
+
+#include "sd-dhcp6-client.h"
+
+#include "alloc-util.h"
+#include "dhcp-identifier.h"
+#include "dhcp6-internal.h"
+#include "dhcp6-lease-internal.h"
+#include "dhcp6-protocol.h"
+#include "dns-domain.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "hostname-util.h"
+#include "in-addr-util.h"
+#include "random-util.h"
+#include "socket-util.h"
+#include "string-table.h"
+#include "strv.h"
+#include "util.h"
+#include "web-util.h"
+
+#define MAX_MAC_ADDR_LEN INFINIBAND_ALEN
+
+#define IRT_DEFAULT (1 * USEC_PER_DAY)
+#define IRT_MINIMUM (600 * USEC_PER_SEC)
+
+/* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */
+enum {
+        DHCP6_REQUEST_IA_NA                     = 1,
+        DHCP6_REQUEST_IA_TA                     = 2, /* currently not used */
+        DHCP6_REQUEST_IA_PD                     = 4,
+};
+
+struct sd_dhcp6_client {
+        unsigned n_ref;
+
+        enum DHCP6State state;
+        sd_event *event;
+        int event_priority;
+        int ifindex;
+        DHCP6Address hint_pd_prefix;
+        struct in6_addr local_address;
+        uint8_t mac_addr[MAX_MAC_ADDR_LEN];
+        size_t mac_addr_len;
+        uint16_t arp_type;
+        DHCP6IA ia_na;
+        DHCP6IA ia_pd;
+        sd_event_source *timeout_t1;
+        sd_event_source *timeout_t2;
+        unsigned request;
+        be32_t transaction_id;
+        usec_t transaction_start;
+        struct sd_dhcp6_lease *lease;
+        int fd;
+        bool information_request;
+        bool iaid_set;
+        be16_t *req_opts;
+        size_t req_opts_allocated;
+        size_t req_opts_len;
+        char *fqdn;
+        char *mudurl;
+        char **user_class;
+        char **vendor_class;
+        sd_event_source *receive_message;
+        usec_t retransmit_time;
+        uint8_t retransmit_count;
+        sd_event_source *timeout_resend;
+        sd_event_source *timeout_resend_expire;
+        sd_dhcp6_client_callback_t callback;
+        void *userdata;
+        struct duid duid;
+        size_t duid_len;
+        usec_t information_request_time_usec;
+        usec_t information_refresh_time_usec;
+        OrderedHashmap *extra_options;
+        OrderedHashmap *vendor_options;
+};
+
+static const uint16_t default_req_opts[] = {
+        SD_DHCP6_OPTION_DNS_SERVERS,
+        SD_DHCP6_OPTION_DOMAIN_LIST,
+        SD_DHCP6_OPTION_NTP_SERVER,
+        SD_DHCP6_OPTION_SNTP_SERVERS,
+};
+
+const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
+        [DHCP6_SOLICIT] = "SOLICIT",
+        [DHCP6_ADVERTISE] = "ADVERTISE",
+        [DHCP6_REQUEST] = "REQUEST",
+        [DHCP6_CONFIRM] = "CONFIRM",
+        [DHCP6_RENEW] = "RENEW",
+        [DHCP6_REBIND] = "REBIND",
+        [DHCP6_REPLY] = "REPLY",
+        [DHCP6_RELEASE] = "RELEASE",
+        [DHCP6_DECLINE] = "DECLINE",
+        [DHCP6_RECONFIGURE] = "RECONFIGURE",
+        [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST",
+        [DHCP6_RELAY_FORW] = "RELAY-FORW",
+        [DHCP6_RELAY_REPL] = "RELAY-REPL",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
+
+const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
+        [DHCP6_STATUS_SUCCESS]                      = "Success",
+        [DHCP6_STATUS_UNSPEC_FAIL]                  = "Unspecified failure",
+        [DHCP6_STATUS_NO_ADDRS_AVAIL]               = "No addresses available",
+        [DHCP6_STATUS_NO_BINDING]                   = "Binding unavailable",
+        [DHCP6_STATUS_NOT_ON_LINK]                  = "Not on link",
+        [DHCP6_STATUS_USE_MULTICAST]                = "Use multicast",
+        [DHCP6_STATUS_NO_PREFIX_AVAIL]              = "No prefix available",
+        [DHCP6_STATUS_UNKNOWN_QUERY_TYPE]           = "Unknown query type",
+        [DHCP6_STATUS_MALFORMED_QUERY]              = "Malformed query",
+        [DHCP6_STATUS_NOT_CONFIGURED]               = "Not configured",
+        [DHCP6_STATUS_NOT_ALLOWED]                  = "Not allowed",
+        [DHCP6_STATUS_QUERY_TERMINATED]             = "Query terminated",
+        [DHCP6_STATUS_DATA_MISSING]                 = "Data missing",
+        [DHCP6_STATUS_CATCHUP_COMPLETE]             = "Catch up complete",
+        [DHCP6_STATUS_NOT_SUPPORTED]                = "Not supported",
+        [DHCP6_STATUS_TLS_CONNECTION_REFUSED]       = "TLS connection refused",
+        [DHCP6_STATUS_ADDRESS_IN_USE]               = "Address in use",
+        [DHCP6_STATUS_CONFIGURATION_CONFLICT]       = "Configuration conflict",
+        [DHCP6_STATUS_MISSING_BINDING_INFORMATION]  = "Missing binding information",
+        [DHCP6_STATUS_OUTDATED_BINDING_INFORMATION] = "Outdated binding information",
+        [DHCP6_STATUS_SERVER_SHUTTING_DOWN]         = "Server shutting down",
+        [DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED]     = "DNS update not supported",
+        [DHCP6_STATUS_EXCESSIVE_TIME_SKEW]          = "Excessive time skew",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
+
+#define DHCP6_CLIENT_DONT_DESTROY(client) \
+        _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
+
+static int client_start(sd_dhcp6_client *client, enum DHCP6State state);
+
+int sd_dhcp6_client_set_callback(
+                sd_dhcp6_client *client,
+                sd_dhcp6_client_callback_t cb,
+                void *userdata) {
+
+        assert_return(client, -EINVAL);
+
+        client->callback = cb;
+        client->userdata = userdata;
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) {
+
+        assert_return(client, -EINVAL);
+        assert_return(ifindex > 0, -EINVAL);
+        assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+        client->ifindex = ifindex;
+        return 0;
+}
+
+int sd_dhcp6_client_set_local_address(
+                sd_dhcp6_client *client,
+                const struct in6_addr *local_address) {
+
+        assert_return(client, -EINVAL);
+        assert_return(local_address, -EINVAL);
+        assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) local_address) > 0, -EINVAL);
+
+        assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+        client->local_address = *local_address;
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_mac(
+                sd_dhcp6_client *client,
+                const uint8_t *addr, size_t addr_len,
+                uint16_t arp_type) {
+
+        assert_return(client, -EINVAL);
+        assert_return(addr, -EINVAL);
+        assert_return(addr_len <= MAX_MAC_ADDR_LEN, -EINVAL);
+
+        assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+        if (arp_type == ARPHRD_ETHER)
+                assert_return(addr_len == ETH_ALEN, -EINVAL);
+        else if (arp_type == ARPHRD_INFINIBAND)
+                assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
+        else {
+                client->arp_type = ARPHRD_NONE;
+                client->mac_addr_len = 0;
+                return 0;
+        }
+
+        if (client->mac_addr_len == addr_len &&
+            memcmp(&client->mac_addr, addr, addr_len) == 0)
+                return 0;
+
+        memcpy(&client->mac_addr, addr, addr_len);
+        client->mac_addr_len = addr_len;
+        client->arp_type = arp_type;
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_prefix_delegation_hint(
+                sd_dhcp6_client *client,
+                uint8_t prefixlen,
+                const struct in6_addr *pd_address) {
+
+        assert_return(client, -EINVAL);
+        assert_return(pd_address, -EINVAL);
+
+        assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+        client->hint_pd_prefix.iapdprefix.address = *pd_address;
+        client->hint_pd_prefix.iapdprefix.prefixlen = prefixlen;
+
+        return 0;
+}
+
+int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(v, -EINVAL);
+
+        r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp6_option_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = ordered_hashmap_put(client->vendor_options, v, v);
+        if (r < 0)
+                return r;
+
+        sd_dhcp6_option_ref(v);
+
+        return 1;
+}
+
+static int client_ensure_duid(sd_dhcp6_client *client) {
+        if (client->duid_len != 0)
+                return 0;
+
+        return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
+}
+
+/**
+ * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid
+ * without further modification. Otherwise, if duid_type is supported, DUID
+ * is set based on that type. Otherwise, an error is returned.
+ */
+static int dhcp6_client_set_duid_internal(
+                sd_dhcp6_client *client,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len,
+                usec_t llt_time) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(duid_len == 0 || duid != NULL, -EINVAL);
+        assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+        if (duid) {
+                r = dhcp_validate_duid_len(duid_type, duid_len, true);
+                if (r < 0) {
+                        r = dhcp_validate_duid_len(duid_type, duid_len, false);
+                        if (r < 0)
+                                return log_dhcp6_client_errno(client, r, "Failed to validate length of DUID: %m");
+
+                        log_dhcp6_client(client, "Using DUID of type %u of incorrect length, proceeding.", duid_type);
+                }
+
+                client->duid.type = htobe16(duid_type);
+                memcpy(&client->duid.raw.data, duid, duid_len);
+                client->duid_len = sizeof(client->duid.type) + duid_len;
+        } else
+#if 0 /* NM_IGNORED */
+                switch (duid_type) {
+                case DUID_TYPE_LLT:
+                        if (client->mac_addr_len == 0)
+                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set.");
+
+                        r = dhcp_identifier_set_duid_llt(&client->duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
+                        if (r < 0)
+                                return log_dhcp6_client_errno(client, r, "Failed to set DUID-LLT: %m");
+                        break;
+                case DUID_TYPE_EN:
+                        r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
+                        if (r < 0)
+                                return log_dhcp6_client_errno(client, r, "Failed to set DUID-EN: %m");
+                        break;
+                case DUID_TYPE_LL:
+                        if (client->mac_addr_len == 0)
+                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set.");
+
+                        r = dhcp_identifier_set_duid_ll(&client->duid, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
+                        if (r < 0)
+                                return log_dhcp6_client_errno(client, r, "Failed to set DUID-LL: %m");
+                        break;
+                case DUID_TYPE_UUID:
+                        r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len);
+                        if (r < 0)
+                                return log_dhcp6_client_errno(client, r, "Failed to set DUID-UUID: %m");
+                        break;
+                default:
+                        return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type");
+                }
+#else /* NM_IGNORED */
+                g_return_val_if_reached (-EINVAL);
+#endif /* NM_IGNORED */
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_duid(
+                sd_dhcp6_client *client,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len) {
+        return dhcp6_client_set_duid_internal(client, duid_type, duid, duid_len, 0);
+}
+
+int sd_dhcp6_client_set_duid_llt(
+                sd_dhcp6_client *client,
+                usec_t llt_time) {
+        return dhcp6_client_set_duid_internal(client, DUID_TYPE_LLT, NULL, 0, llt_time);
+}
+
+static const char* const dhcp6_duid_type_table[_DUID_TYPE_MAX] = {
+        [DUID_TYPE_LLT]  = "DUID-LLT",
+        [DUID_TYPE_EN]   = "DUID-EN/Vendor",
+        [DUID_TYPE_LL]   = "DUID-LL",
+        [DUID_TYPE_UUID] = "UUID",
+};
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_duid_type, DUIDType);
+
+int sd_dhcp6_client_duid_as_string(
+                sd_dhcp6_client *client,
+                char **duid) {
+        _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL;
+        const char *v;
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->duid_len > 0, -ENODATA);
+
+        v = dhcp6_duid_type_to_string(be16toh(client->duid.type));
+        if (v) {
+                s = strdup(v);
+                if (!s)
+                        return -ENOMEM;
+        } else {
+                r = asprintf(&s, "%0x", client->duid.type);
+                if (r < 0)
+                        return -ENOMEM;
+        }
+
+        t = hexmem(&client->duid.raw.data, client->duid_len);
+        if (!t)
+                return -ENOMEM;
+
+        p = strjoin(s, ":", t);
+        if (!p)
+                return -ENOMEM;
+
+        *duid = TAKE_PTR(p);
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
+        assert_return(client, -EINVAL);
+        assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+        client->ia_na.ia_na.id = htobe32(iaid);
+        client->ia_pd.ia_pd.id = htobe32(iaid);
+        client->iaid_set = true;
+
+        return 0;
+}
+
+int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) {
+        assert_return(client, -EINVAL);
+        assert_return(iaid, -EINVAL);
+
+        if (!client->iaid_set)
+                return -ENODATA;
+
+        *iaid = be32toh(client->ia_na.ia_na.id);
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_fqdn(
+                sd_dhcp6_client *client,
+                const char *fqdn) {
+
+        assert_return(client, -EINVAL);
+
+        /* Make sure FQDN qualifies as DNS and as Linux hostname */
+        if (fqdn &&
+            !(hostname_is_valid(fqdn, 0) && dns_name_is_valid(fqdn) > 0))
+                return -EINVAL;
+
+        return free_and_strdup(&client->fqdn, fqdn);
+}
+
+int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) {
+        assert_return(client, -EINVAL);
+        assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+        client->information_request = enabled;
+
+        return 0;
+}
+
+int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled) {
+        assert_return(client, -EINVAL);
+        assert_return(enabled, -EINVAL);
+
+        *enabled = client->information_request;
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) {
+        size_t t;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+
+        if (option <= 0 || option >= UINT8_MAX)
+                return -EINVAL;
+
+        for (t = 0; t < client->req_opts_len; t++)
+                if (client->req_opts[t] == htobe16(option))
+                        return -EEXIST;
+
+        if (!GREEDY_REALLOC(client->req_opts, client->req_opts_allocated,
+                            client->req_opts_len + 1))
+                return -ENOMEM;
+
+        client->req_opts[client->req_opts_len++] = htobe16(option);
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, const char *mudurl) {
+
+        assert_return(client, -EINVAL);
+        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(mudurl, -EINVAL);
+        assert_return(strlen(mudurl) <= UINT8_MAX, -EINVAL);
+        assert_return(http_url_is_valid(mudurl), -EINVAL);
+
+        return free_and_strdup(&client->mudurl, mudurl);
+}
+
+int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client *client, char **user_class) {
+        _cleanup_strv_free_ char **s = NULL;
+        char **p;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+
+        assert_return(user_class, -EINVAL);
+
+        STRV_FOREACH(p, user_class)
+                if (strlen(*p) > UINT16_MAX)
+                        return -ENAMETOOLONG;
+
+        s = strv_copy(user_class);
+        if (!s)
+                return -ENOMEM;
+
+        client->user_class = TAKE_PTR(s);
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_request_vendor_class(sd_dhcp6_client *client, char **vendor_class) {
+        _cleanup_strv_free_ char **s = NULL;
+        char **p;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(vendor_class, -EINVAL);
+
+        STRV_FOREACH(p, vendor_class)
+                if (strlen(*p) > UINT8_MAX)
+                        return -ENAMETOOLONG;
+
+        s = strv_copy(vendor_class);
+        if (!s)
+                return -ENOMEM;
+
+        client->vendor_class = TAKE_PTR(s);
+
+        return 0;
+}
+
+int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegation) {
+        assert_return(client, -EINVAL);
+        assert_return(delegation, -EINVAL);
+
+        *delegation = FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD);
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, int delegation) {
+        assert_return(client, -EINVAL);
+
+        SET_FLAG(client->request, DHCP6_REQUEST_IA_PD, delegation);
+
+        return 0;
+}
+
+int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request) {
+        assert_return(client, -EINVAL);
+        assert_return(request, -EINVAL);
+
+        *request = FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA);
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request) {
+        assert_return(client, -EINVAL);
+
+        SET_FLAG(client->request, DHCP6_REQUEST_IA_NA, request);
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) {
+        assert_return(client, -EINVAL);
+
+        client->transaction_id = transaction_id;
+
+        return 0;
+}
+
+int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
+        assert_return(client, -EINVAL);
+
+        if (!client->lease)
+                return -ENOMSG;
+
+        if (ret)
+                *ret = client->lease;
+
+        return 0;
+}
+
+int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(v, -EINVAL);
+
+        r = ordered_hashmap_ensure_allocated(&client->extra_options, &dhcp6_option_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = ordered_hashmap_put(client->extra_options, UINT_TO_PTR(v->option), v);
+        if (r < 0)
+                return r;
+
+        sd_dhcp6_option_ref(v);
+        return 0;
+}
+
+static void client_notify(sd_dhcp6_client *client, int event) {
+        assert(client);
+
+        if (client->callback)
+                client->callback(client, event, client->userdata);
+}
+
+static int client_reset(sd_dhcp6_client *client) {
+        assert(client);
+
+        client->lease = sd_dhcp6_lease_unref(client->lease);
+
+        client->receive_message =
+                sd_event_source_unref(client->receive_message);
+
+        client->transaction_id = 0;
+        client->transaction_start = 0;
+
+        client->retransmit_time = 0;
+        client->retransmit_count = 0;
+
+        (void) event_source_disable(client->timeout_resend);
+        (void) event_source_disable(client->timeout_resend_expire);
+        (void) event_source_disable(client->timeout_t1);
+        (void) event_source_disable(client->timeout_t2);
+
+        client->state = DHCP6_STATE_STOPPED;
+
+        return 0;
+}
+
+static void client_stop(sd_dhcp6_client *client, int error) {
+        DHCP6_CLIENT_DONT_DESTROY(client);
+
+        assert(client);
+
+        client_notify(client, error);
+
+        client_reset(client);
+}
+
+static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
+        _cleanup_free_ DHCP6Message *message = NULL;
+        struct in6_addr all_servers =
+                IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
+        struct sd_dhcp6_option *j;
+        size_t len, optlen = 512;
+        uint8_t *opt;
+        int r;
+        usec_t elapsed_usec;
+        be16_t elapsed_time;
+
+        assert(client);
+
+        len = sizeof(DHCP6Message) + optlen;
+
+        message = malloc0(len);
+        if (!message)
+                return -ENOMEM;
+
+        opt = (uint8_t *)(message + 1);
+
+        message->transaction_id = client->transaction_id;
+
+        switch(client->state) {
+        case DHCP6_STATE_INFORMATION_REQUEST:
+                message->type = DHCP6_INFORMATION_REQUEST;
+
+                if (client->mudurl) {
+                        r = dhcp6_option_append(&opt, &optlen,
+                                                SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+                                                client->mudurl);
+                        if (r < 0)
+                                return r;
+                }
+
+                break;
+
+        case DHCP6_STATE_SOLICITATION:
+                message->type = DHCP6_SOLICIT;
+
+                r = dhcp6_option_append(&opt, &optlen,
+                                        SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
+                if (r < 0)
+                        return r;
+
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) {
+                        r = dhcp6_option_append_ia(&opt, &optlen,
+                                                   &client->ia_na);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->fqdn) {
+                        r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->mudurl) {
+                        r = dhcp6_option_append(&opt, &optlen,
+                                                SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+                                                client->mudurl);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->user_class) {
+                        r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->vendor_class) {
+                        r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (!ordered_hashmap_isempty(client->vendor_options)) {
+                        r = dhcp6_option_append_vendor_option(&opt, &optlen,
+                                                       client->vendor_options);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
+                        r = dhcp6_option_append_pd(opt, optlen, &client->ia_pd, &client->hint_pd_prefix);
+                        if (r < 0)
+                                return r;
+
+                        opt += r;
+                        optlen -= r;
+                }
+
+                break;
+
+        case DHCP6_STATE_REQUEST:
+        case DHCP6_STATE_RENEW:
+
+                if (client->state == DHCP6_STATE_REQUEST)
+                        message->type = DHCP6_REQUEST;
+                else
+                        message->type = DHCP6_RENEW;
+
+                r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_SERVERID,
+                                        client->lease->serverid_len,
+                                        client->lease->serverid);
+                if (r < 0)
+                        return r;
+
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) {
+                        r = dhcp6_option_append_ia(&opt, &optlen,
+                                                   &client->lease->ia);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->fqdn) {
+                        r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->mudurl) {
+                        r = dhcp6_option_append(&opt, &optlen,
+                                                SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+                                                client->mudurl);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->user_class) {
+                        r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->vendor_class) {
+                        r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (!ordered_hashmap_isempty(client->vendor_options)) {
+                        r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) {
+                        r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL);
+                        if (r < 0)
+                                return r;
+
+                        opt += r;
+                        optlen -= r;
+                }
+
+                break;
+
+        case DHCP6_STATE_REBIND:
+                message->type = DHCP6_REBIND;
+
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) {
+                        r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->fqdn) {
+                        r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->mudurl) {
+                        r = dhcp6_option_append(&opt, &optlen,
+                                                SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+                                                client->mudurl);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->user_class) {
+                        r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->vendor_class) {
+                        r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (!ordered_hashmap_isempty(client->vendor_options)) {
+                        r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
+                        r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL);
+                        if (r < 0)
+                                return r;
+
+                        opt += r;
+                        optlen -= r;
+                }
+
+                break;
+
+        case DHCP6_STATE_STOPPED:
+        case DHCP6_STATE_BOUND:
+                return -EINVAL;
+        }
+
+        r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ORO,
+                                client->req_opts_len * sizeof(be16_t),
+                                client->req_opts);
+        if (r < 0)
+                return r;
+
+        assert(client->duid_len);
+        r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_CLIENTID,
+                                client->duid_len, &client->duid);
+        if (r < 0)
+                return r;
+
+        elapsed_usec = time_now - client->transaction_start;
+        if (elapsed_usec < 0xffff * USEC_PER_MSEC * 10)
+                elapsed_time = htobe16(elapsed_usec / USEC_PER_MSEC / 10);
+        else
+                elapsed_time = 0xffff;
+
+        r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ELAPSED_TIME,
+                                sizeof(elapsed_time), &elapsed_time);
+        if (r < 0)
+                return r;
+
+        ORDERED_HASHMAP_FOREACH(j, client->extra_options) {
+                r = dhcp6_option_append(&opt, &optlen, j->option, j->length, j->data);
+                if (r < 0)
+                        return r;
+        }
+
+        r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
+                                          len - optlen);
+        if (r < 0)
+                return r;
+
+        log_dhcp6_client(client, "Sent %s",
+                         dhcp6_message_type_to_string(message->type));
+
+        return 0;
+}
+
+static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_dhcp6_client *client = userdata;
+
+        assert(s);
+        assert(client);
+        assert(client->lease);
+
+        (void) event_source_disable(client->timeout_t2);
+
+        log_dhcp6_client(client, "Timeout T2");
+
+        client_start(client, DHCP6_STATE_REBIND);
+
+        return 0;
+}
+
+static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_dhcp6_client *client = userdata;
+
+        assert(s);
+        assert(client);
+        assert(client->lease);
+
+        (void) event_source_disable(client->timeout_t1);
+
+        log_dhcp6_client(client, "Timeout T1");
+
+        client_start(client, DHCP6_STATE_RENEW);
+
+        return 0;
+}
+
+static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_dhcp6_client *client = userdata;
+        DHCP6_CLIENT_DONT_DESTROY(client);
+        enum DHCP6State state;
+
+        assert(s);
+        assert(client);
+        assert(client->event);
+
+        state = client->state;
+
+        client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE);
+
+        /* RFC 3315, section 18.1.4., says that "...the client may choose to
+           use a Solicit message to locate a new DHCP server..." */
+        if (state == DHCP6_STATE_REBIND)
+                client_start(client, DHCP6_STATE_SOLICITATION);
+
+        return 0;
+}
+
+static usec_t client_timeout_compute_random(usec_t val) {
+        return val - (random_u32() % USEC_PER_SEC) * val / 10 / USEC_PER_SEC;
+}
+
+static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) {
+        int r = 0;
+        sd_dhcp6_client *client = userdata;
+        usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0;
+        usec_t max_retransmit_duration = 0;
+        uint8_t max_retransmit_count = 0;
+        char time_string[FORMAT_TIMESPAN_MAX];
+
+        assert(s);
+        assert(client);
+        assert(client->event);
+
+        (void) event_source_disable(client->timeout_resend);
+
+        switch (client->state) {
+        case DHCP6_STATE_INFORMATION_REQUEST:
+                init_retransmit_time = DHCP6_INF_TIMEOUT;
+                max_retransmit_time = DHCP6_INF_MAX_RT;
+
+                break;
+
+        case DHCP6_STATE_SOLICITATION:
+
+                if (client->retransmit_count && client->lease) {
+                        client_start(client, DHCP6_STATE_REQUEST);
+                        return 0;
+                }
+
+                init_retransmit_time = DHCP6_SOL_TIMEOUT;
+                max_retransmit_time = DHCP6_SOL_MAX_RT;
+
+                break;
+
+        case DHCP6_STATE_REQUEST:
+                init_retransmit_time = DHCP6_REQ_TIMEOUT;
+                max_retransmit_time = DHCP6_REQ_MAX_RT;
+                max_retransmit_count = DHCP6_REQ_MAX_RC;
+
+                break;
+
+        case DHCP6_STATE_RENEW:
+                init_retransmit_time = DHCP6_REN_TIMEOUT;
+                max_retransmit_time = DHCP6_REN_MAX_RT;
+
+                /* RFC 3315, section 18.1.3. says max retransmit duration will
+                   be the remaining time until T2. Instead of setting MRD,
+                   wait for T2 to trigger with the same end result */
+
+                break;
+
+        case DHCP6_STATE_REBIND:
+                init_retransmit_time = DHCP6_REB_TIMEOUT;
+                max_retransmit_time = DHCP6_REB_MAX_RT;
+
+                if (event_source_is_enabled(client->timeout_resend_expire) <= 0) {
+                        uint32_t expire = 0;
+
+                        r = dhcp6_lease_ia_rebind_expire(&client->lease->ia, &expire);
+                        if (r < 0) {
+                                client_stop(client, r);
+                                return 0;
+                        }
+                        max_retransmit_duration = expire * USEC_PER_SEC;
+                }
+
+                break;
+
+        case DHCP6_STATE_STOPPED:
+        case DHCP6_STATE_BOUND:
+                return 0;
+        }
+
+        if (max_retransmit_count > 0 &&
+            client->retransmit_count >= max_retransmit_count) {
+                client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX);
+                return 0;
+        }
+
+        r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+        if (r < 0)
+                goto error;
+
+        r = client_send_message(client, time_now);
+        if (r >= 0)
+                client->retransmit_count++;
+
+        if (client->retransmit_time == 0) {
+                client->retransmit_time =
+                        client_timeout_compute_random(init_retransmit_time);
+
+                if (client->state == DHCP6_STATE_SOLICITATION)
+                        client->retransmit_time += init_retransmit_time / 10;
+
+        } else {
+                if (max_retransmit_time > 0 &&
+                    client->retransmit_time > max_retransmit_time / 2)
+                        client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
+                else
+                        client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
+        }
+
+        log_dhcp6_client(client, "Next retransmission in %s",
+                         format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->retransmit_time, USEC_PER_SEC));
+
+        r = event_reset_time(client->event, &client->timeout_resend,
+                             clock_boottime_or_monotonic(),
+                             time_now + client->retransmit_time, 10 * USEC_PER_MSEC,
+                             client_timeout_resend, client,
+                             client->event_priority, "dhcp6-resend-timer", true);
+        if (r < 0)
+                goto error;
+
+        if (max_retransmit_duration > 0 && event_source_is_enabled(client->timeout_resend_expire) <= 0) {
+
+                log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
+                                 max_retransmit_duration / USEC_PER_SEC);
+
+                r = event_reset_time(client->event, &client->timeout_resend_expire,
+                                     clock_boottime_or_monotonic(),
+                                     time_now + max_retransmit_duration, USEC_PER_SEC,
+                                     client_timeout_resend_expire, client,
+                                     client->event_priority, "dhcp6-resend-expire-timer", true);
+                if (r < 0)
+                        goto error;
+        }
+
+error:
+        if (r < 0)
+                client_stop(client, r);
+
+        return 0;
+}
+
+static int client_ensure_iaid(sd_dhcp6_client *client) {
+        int r;
+        uint32_t iaid;
+
+        assert(client);
+
+        if (client->iaid_set)
+                return 0;
+
+        r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, true, &iaid);
+        if (r < 0)
+                return r;
+
+        client->ia_na.ia_na.id = iaid;
+        client->ia_pd.ia_pd.id = iaid;
+        client->iaid_set = true;
+
+        return 0;
+}
+
+static int client_parse_message(
+                sd_dhcp6_client *client,
+                DHCP6Message *message,
+                size_t len,
+                sd_dhcp6_lease *lease) {
+
+        uint16_t ia_na_status = 0, ia_pd_status = 0;
+        uint32_t lt_t1 = ~0, lt_t2 = ~0;
+        usec_t irt = IRT_DEFAULT;
+        bool clientid = false;
+        size_t pos = 0;
+        int r;
+
+        assert(client);
+        assert(message);
+        assert(len >= sizeof(DHCP6Message));
+        assert(lease);
+
+        len -= sizeof(DHCP6Message);
+
+        while (pos < len) {
+                DHCP6Option *option = (DHCP6Option *) &message->options[pos];
+                uint16_t optcode, optlen;
+                be32_t iaid_lease;
+                int  status;
+                uint8_t *optval;
+
+                if (len < pos + offsetof(DHCP6Option, data))
+                        return -ENOBUFS;
+
+                optcode = be16toh(option->code);
+                optlen = be16toh(option->len);
+                optval = option->data;
+
+                if (len < pos + offsetof(DHCP6Option, data) + optlen)
+                        return -ENOBUFS;
+
+                switch (optcode) {
+                case SD_DHCP6_OPTION_CLIENTID:
+                        if (clientid) {
+                                log_dhcp6_client(client, "%s contains multiple clientids",
+                                                 dhcp6_message_type_to_string(message->type));
+                                return -EINVAL;
+                        }
+
+                        if (optlen != client->duid_len ||
+                            memcmp(&client->duid, optval, optlen) != 0) {
+                                log_dhcp6_client(client, "%s DUID does not match",
+                                                 dhcp6_message_type_to_string(message->type));
+
+                                return -EINVAL;
+                        }
+                        clientid = true;
+
+                        break;
+
+                case SD_DHCP6_OPTION_SERVERID:
+                        r = dhcp6_lease_get_serverid(lease, NULL, NULL);
+                        if (r >= 0) {
+                                log_dhcp6_client(client, "%s contains multiple serverids",
+                                                 dhcp6_message_type_to_string(message->type));
+                                return -EINVAL;
+                        }
+
+                        r = dhcp6_lease_set_serverid(lease, optval, optlen);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_PREFERENCE:
+                        if (optlen != 1)
+                                return -EINVAL;
+
+                        r = dhcp6_lease_set_preference(lease, optval[0]);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_STATUS_CODE:
+                        status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option));
+                        if (status < 0)
+                                return status;
+
+                        if (status > 0) {
+                                log_dhcp6_client(client, "%s Status %s",
+                                                 dhcp6_message_type_to_string(message->type),
+                                                 dhcp6_message_status_to_string(status));
+
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case SD_DHCP6_OPTION_IA_NA:
+                        if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+                                log_dhcp6_client(client, "Information request ignoring IA NA option");
+
+                                break;
+                        }
+
+                        r = dhcp6_option_parse_ia(option, &lease->ia, &ia_na_status);
+                        if (r < 0 && r != -ENOMSG)
+                                return r;
+
+                        if (ia_na_status == DHCP6_STATUS_NO_ADDRS_AVAIL) {
+                                pos += offsetof(DHCP6Option, data) + optlen;
+                                continue;
+                        }
+
+                        r = dhcp6_lease_get_iaid(lease, &iaid_lease);
+                        if (r < 0)
+                                return r;
+
+                        if (client->ia_na.ia_na.id != iaid_lease) {
+                                log_dhcp6_client(client, "%s has wrong IAID for IA NA",
+                                                 dhcp6_message_type_to_string(message->type));
+                                return -EINVAL;
+                        }
+
+                        if (lease->ia.addresses) {
+                                lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1));
+                                lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t1));
+                        }
+
+                        break;
+
+                case SD_DHCP6_OPTION_IA_PD:
+                        if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+                                log_dhcp6_client(client, "Information request ignoring IA PD option");
+
+                                break;
+                        }
+
+                        r = dhcp6_option_parse_ia(option, &lease->pd, &ia_pd_status);
+                        if (r < 0 && r != -ENOMSG)
+                                return r;
+
+                        if (ia_pd_status == DHCP6_STATUS_NO_PREFIX_AVAIL) {
+                                pos += offsetof(DHCP6Option, data) + optlen;
+                                continue;
+                        }
+
+                        r = dhcp6_lease_get_pd_iaid(lease, &iaid_lease);
+                        if (r < 0)
+                                return r;
+
+                        if (client->ia_pd.ia_pd.id != iaid_lease) {
+                                log_dhcp6_client(client, "%s has wrong IAID for IA PD",
+                                                 dhcp6_message_type_to_string(message->type));
+                                return -EINVAL;
+                        }
+
+                        if (lease->pd.addresses) {
+                                lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1));
+                                lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2));
+                        }
+
+                        break;
+
+                case SD_DHCP6_OPTION_RAPID_COMMIT:
+                        r = dhcp6_lease_set_rapid_commit(lease);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_DNS_SERVERS:
+                        r = dhcp6_lease_set_dns(lease, optval, optlen);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_DOMAIN_LIST:
+                        r = dhcp6_lease_set_domains(lease, optval, optlen);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_NTP_SERVER:
+                        r = dhcp6_lease_set_ntp(lease, optval, optlen);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_SNTP_SERVERS:
+                        r = dhcp6_lease_set_sntp(lease, optval, optlen);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_FQDN:
+                        r = dhcp6_lease_set_fqdn(lease, optval, optlen);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
+                        if (optlen != 4)
+                                return -EINVAL;
+
+                        irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC;
+                        break;
+                }
+
+                pos += offsetof(DHCP6Option, data) + optlen;
+        }
+
+        if (ia_na_status > 0 && ia_pd_status > 0) {
+                log_dhcp6_client(client, "No IA_PD prefix or IA_NA address received. Ignoring.");
+                return -EINVAL;
+        }
+
+        if (!clientid) {
+                log_dhcp6_client(client, "%s has incomplete options",
+                                 dhcp6_message_type_to_string(message->type));
+                return -EINVAL;
+        }
+
+        if (client->state != DHCP6_STATE_INFORMATION_REQUEST) {
+                r = dhcp6_lease_get_serverid(lease, NULL, NULL);
+                if (r < 0) {
+                        log_dhcp6_client(client, "%s has no server id",
+                                         dhcp6_message_type_to_string(message->type));
+                        return -EINVAL;
+                }
+
+        } else {
+                if (lease->ia.addresses) {
+                        lease->ia.ia_na.lifetime_t1 = htobe32(lt_t1);
+                        lease->ia.ia_na.lifetime_t2 = htobe32(lt_t2);
+                }
+
+                if (lease->pd.addresses) {
+                        lease->pd.ia_pd.lifetime_t1 = htobe32(lt_t1);
+                        lease->pd.ia_pd.lifetime_t2 = htobe32(lt_t2);
+                }
+        }
+
+        client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM);
+
+        return 0;
+}
+
+static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, size_t len) {
+        _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+        bool rapid_commit;
+        int r;
+
+        assert(client);
+        assert(reply);
+
+        if (reply->type != DHCP6_REPLY)
+                return 0;
+
+        r = dhcp6_lease_new(&lease);
+        if (r < 0)
+                return -ENOMEM;
+
+        r = client_parse_message(client, reply, len, lease);
+        if (r < 0)
+                return r;
+
+        if (client->state == DHCP6_STATE_SOLICITATION) {
+                r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit);
+                if (r < 0)
+                        return r;
+
+                if (!rapid_commit)
+                        return 0;
+        }
+
+        sd_dhcp6_lease_unref(client->lease);
+        client->lease = TAKE_PTR(lease);
+
+        return DHCP6_STATE_BOUND;
+}
+
+static int client_receive_advertise(sd_dhcp6_client *client, DHCP6Message *advertise, size_t len) {
+        _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+        uint8_t pref_advertise = 0, pref_lease = 0;
+        int r;
+
+        if (advertise->type != DHCP6_ADVERTISE)
+                return 0;
+
+        r = dhcp6_lease_new(&lease);
+        if (r < 0)
+                return r;
+
+        r = client_parse_message(client, advertise, len, lease);
+        if (r < 0)
+                return r;
+
+        r = dhcp6_lease_get_preference(lease, &pref_advertise);
+        if (r < 0)
+                return r;
+
+        r = dhcp6_lease_get_preference(client->lease, &pref_lease);
+
+        if (r < 0 || pref_advertise > pref_lease) {
+                sd_dhcp6_lease_unref(client->lease);
+                client->lease = TAKE_PTR(lease);
+                r = 0;
+        }
+
+        if (pref_advertise == 255 || client->retransmit_count > 1)
+                r = DHCP6_STATE_REQUEST;
+
+        return r;
+}
+
+static int client_receive_message(
+                sd_event_source *s,
+                int fd, uint32_t
+                revents,
+                void *userdata) {
+
+        sd_dhcp6_client *client = userdata;
+        DHCP6_CLIENT_DONT_DESTROY(client);
+        _cleanup_free_ DHCP6Message *message = NULL;
+        ssize_t buflen, len;
+        int r = 0;
+
+        assert(s);
+        assert(client);
+        assert(client->event);
+
+        buflen = next_datagram_size_fd(fd);
+        if (buflen == -ENETDOWN)
+                /* the link is down. Don't return an error or the I/O event
+                   source will be disconnected and we won't be able to receive
+                   packets again when the link comes back. */
+                return 0;
+        if (buflen < 0)
+                return buflen;
+
+        message = malloc(buflen);
+        if (!message)
+                return -ENOMEM;
+
+        len = recv(fd, message, buflen, 0);
+        if (len < 0) {
+                /* see comment above for why we shouldn't error out on ENETDOWN. */
+                if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
+                        return 0;
+
+                return log_dhcp6_client_errno(client, errno, "Could not receive message from UDP socket: %m");
+
+        }
+        if ((size_t) len < sizeof(DHCP6Message)) {
+                log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring");
+                return 0;
+        }
+
+        switch(message->type) {
+        case DHCP6_SOLICIT:
+        case DHCP6_REQUEST:
+        case DHCP6_CONFIRM:
+        case DHCP6_RENEW:
+        case DHCP6_REBIND:
+        case DHCP6_RELEASE:
+        case DHCP6_DECLINE:
+        case DHCP6_INFORMATION_REQUEST:
+        case DHCP6_RELAY_FORW:
+        case DHCP6_RELAY_REPL:
+                return 0;
+
+        case DHCP6_ADVERTISE:
+        case DHCP6_REPLY:
+        case DHCP6_RECONFIGURE:
+                break;
+
+        default:
+                log_dhcp6_client(client, "Unknown message type %d", message->type);
+                return 0;
+        }
+
+        if (client->transaction_id != (message->transaction_id &
+                                       htobe32(0x00ffffff)))
+                return 0;
+
+        switch (client->state) {
+        case DHCP6_STATE_INFORMATION_REQUEST:
+                r = client_receive_reply(client, message, len);
+                if (r < 0)
+                        return 0;
+
+                client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST);
+
+                client_start(client, DHCP6_STATE_STOPPED);
+
+                break;
+
+        case DHCP6_STATE_SOLICITATION:
+                r = client_receive_advertise(client, message, len);
+
+                if (r == DHCP6_STATE_REQUEST) {
+                        client_start(client, r);
+
+                        break;
+                }
+
+                _fallthrough_; /* for Solicitation Rapid Commit option check */
+        case DHCP6_STATE_REQUEST:
+        case DHCP6_STATE_RENEW:
+        case DHCP6_STATE_REBIND:
+
+                r = client_receive_reply(client, message, len);
+                if (r < 0)
+                        return 0;
+
+                if (r == DHCP6_STATE_BOUND) {
+
+                        r = client_start(client, DHCP6_STATE_BOUND);
+                        if (r < 0) {
+                                client_stop(client, r);
+                                return 0;
+                        }
+
+                        client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE);
+                }
+
+                break;
+
+        case DHCP6_STATE_BOUND:
+
+                break;
+
+        case DHCP6_STATE_STOPPED:
+                return 0;
+        }
+
+        log_dhcp6_client(client, "Recv %s",
+                         dhcp6_message_type_to_string(message->type));
+
+        return 0;
+}
+
+static int client_get_lifetime(sd_dhcp6_client *client, uint32_t *lifetime_t1,
+                               uint32_t *lifetime_t2) {
+        assert_return(client, -EINVAL);
+        assert_return(client->lease, -EINVAL);
+
+        if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) {
+                *lifetime_t1 = be32toh(client->lease->ia.ia_na.lifetime_t1);
+                *lifetime_t2 = be32toh(client->lease->ia.ia_na.lifetime_t2);
+
+                return 0;
+        }
+
+        if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) {
+                *lifetime_t1 = be32toh(client->lease->pd.ia_pd.lifetime_t1);
+                *lifetime_t2 = be32toh(client->lease->pd.ia_pd.lifetime_t2);
+
+                return 0;
+        }
+
+        return -ENOMSG;
+}
+
+static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
+        int r;
+        usec_t timeout, time_now;
+        char time_string[FORMAT_TIMESPAN_MAX];
+        uint32_t lifetime_t1, lifetime_t2;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->event, -EINVAL);
+        assert_return(client->ifindex > 0, -EINVAL);
+        assert_return(client->state != state, -EINVAL);
+
+        (void) event_source_disable(client->timeout_resend_expire);
+        (void) event_source_disable(client->timeout_resend);
+        client->retransmit_time = 0;
+        client->retransmit_count = 0;
+
+        r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+        if (r < 0)
+                return r;
+
+        if (!client->receive_message) {
+                r = sd_event_add_io(client->event, &client->receive_message,
+                                    client->fd, EPOLLIN, client_receive_message,
+                                    client);
+                if (r < 0)
+                        goto error;
+
+                r = sd_event_source_set_priority(client->receive_message,
+                                                 client->event_priority);
+                if (r < 0)
+                        goto error;
+
+                r = sd_event_source_set_description(client->receive_message,
+                                                    "dhcp6-receive-message");
+                if (r < 0)
+                        goto error;
+        }
+
+        switch (state) {
+        case DHCP6_STATE_STOPPED:
+                if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+                        client->state = DHCP6_STATE_STOPPED;
+
+                        return 0;
+                }
+
+                _fallthrough_;
+        case DHCP6_STATE_SOLICITATION:
+                client->state = DHCP6_STATE_SOLICITATION;
+
+                break;
+
+        case DHCP6_STATE_INFORMATION_REQUEST:
+        case DHCP6_STATE_REQUEST:
+        case DHCP6_STATE_RENEW:
+        case DHCP6_STATE_REBIND:
+
+                client->state = state;
+
+                break;
+
+        case DHCP6_STATE_BOUND:
+
+                r = client_get_lifetime(client, &lifetime_t1, &lifetime_t2);
+                if (r < 0)
+                        goto error;
+
+                if (lifetime_t1 == 0xffffffff || lifetime_t2 == 0xffffffff) {
+                        log_dhcp6_client(client, "Infinite T1 0x%08x or T2 0x%08x",
+                                         lifetime_t1, lifetime_t2);
+
+                        return 0;
+                }
+
+                timeout = client_timeout_compute_random(lifetime_t1 * USEC_PER_SEC);
+
+                log_dhcp6_client(client, "T1 expires in %s",
+                                 format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
+
+                r = event_reset_time(client->event, &client->timeout_t1,
+                                     clock_boottime_or_monotonic(),
+                                     time_now + timeout, 10 * USEC_PER_SEC,
+                                     client_timeout_t1, client,
+                                     client->event_priority, "dhcp6-t1-timeout", true);
+                if (r < 0)
+                        goto error;
+
+                timeout = client_timeout_compute_random(lifetime_t2 * USEC_PER_SEC);
+
+                log_dhcp6_client(client, "T2 expires in %s",
+                                 format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
+
+                r = event_reset_time(client->event, &client->timeout_t2,
+                                     clock_boottime_or_monotonic(),
+                                     time_now + timeout, 10 * USEC_PER_SEC,
+                                     client_timeout_t2, client,
+                                     client->event_priority, "dhcp6-t2-timeout", true);
+                if (r < 0)
+                        goto error;
+
+                client->state = state;
+
+                return 0;
+        }
+
+        client->transaction_id = random_u32() & htobe32(0x00ffffff);
+        client->transaction_start = time_now;
+
+        r = event_reset_time(client->event, &client->timeout_resend,
+                             clock_boottime_or_monotonic(),
+                             0, 0,
+                             client_timeout_resend, client,
+                             client->event_priority, "dhcp6-resend-timeout", true);
+        if (r < 0)
+                goto error;
+
+        return 0;
+
+ error:
+        client_reset(client);
+        return r;
+}
+
+int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
+        if (!client)
+                return 0;
+
+        client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
+
+        client->fd = safe_close(client->fd);
+
+        return 0;
+}
+
+int sd_dhcp6_client_is_running(sd_dhcp6_client *client) {
+        assert_return(client, -EINVAL);
+
+        return client->state != DHCP6_STATE_STOPPED;
+}
+
+int sd_dhcp6_client_start(sd_dhcp6_client *client) {
+        enum DHCP6State state = DHCP6_STATE_SOLICITATION;
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->event, -EINVAL);
+        assert_return(client->ifindex > 0, -EINVAL);
+        assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &client->local_address) > 0, -EINVAL);
+
+        if (!IN_SET(client->state, DHCP6_STATE_STOPPED))
+                return -EBUSY;
+
+        if (!client->information_request && !client->request)
+                return -EINVAL;
+
+        r = client_reset(client);
+        if (r < 0)
+                return r;
+
+        r = client_ensure_iaid(client);
+        if (r < 0)
+                return r;
+
+        r = client_ensure_duid(client);
+        if (r < 0)
+                return r;
+
+        if (client->fd < 0) {
+                r = dhcp6_network_bind_udp_socket(client->ifindex, &client->local_address);
+                if (r < 0) {
+                        _cleanup_free_ char *p = NULL;
+
+                        (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p);
+                        return log_dhcp6_client_errno(client, r,
+                                                      "Failed to bind to UDP socket at address %s: %m", strna(p));
+                }
+
+                client->fd = r;
+        }
+
+        if (client->information_request) {
+                usec_t t = now(CLOCK_MONOTONIC);
+
+                if (t < usec_add(client->information_request_time_usec, client->information_refresh_time_usec))
+                        return 0;
+
+                client->information_request_time_usec = t;
+                state = DHCP6_STATE_INFORMATION_REQUEST;
+        }
+
+        log_dhcp6_client(client, "Started in %s mode",
+                         client->information_request ? "Information request" : "Managed");
+
+        return client_start(client, state);
+}
+
+int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64_t priority) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(!client->event, -EBUSY);
+
+        if (event)
+                client->event = sd_event_ref(event);
+        else {
+                r = sd_event_default(&client->event);
+                if (r < 0)
+                        return 0;
+        }
+
+        client->event_priority = priority;
+
+        return 0;
+}
+
+int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
+        assert_return(client, -EINVAL);
+
+        client->event = sd_event_unref(client->event);
+
+        return 0;
+}
+
+sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
+        assert_return(client, NULL);
+
+        return client->event;
+}
+
+static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
+        assert(client);
+
+        client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+        client->timeout_resend_expire = sd_event_source_unref(client->timeout_resend_expire);
+        client->timeout_t1 = sd_event_source_unref(client->timeout_t1);
+        client->timeout_t2 = sd_event_source_unref(client->timeout_t2);
+
+        client_reset(client);
+
+        client->fd = safe_close(client->fd);
+
+        sd_dhcp6_client_detach_event(client);
+
+        free(client->req_opts);
+        free(client->fqdn);
+        free(client->mudurl);
+
+        ordered_hashmap_free(client->extra_options);
+        strv_free(client->user_class);
+        strv_free(client->vendor_class);
+
+        return mfree(client);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_client, sd_dhcp6_client, dhcp6_client_free);
+
+int sd_dhcp6_client_new(sd_dhcp6_client **ret) {
+        _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+        _cleanup_free_ be16_t *req_opts = NULL;
+        size_t t;
+
+        assert_return(ret, -EINVAL);
+
+        req_opts = new(be16_t, ELEMENTSOF(default_req_opts));
+        if (!req_opts)
+                return -ENOMEM;
+
+        for (t = 0; t < ELEMENTSOF(default_req_opts); t++)
+                req_opts[t] = htobe16(default_req_opts[t]);
+
+        client = new(sd_dhcp6_client, 1);
+        if (!client)
+                return -ENOMEM;
+
+        *client = (sd_dhcp6_client) {
+                .n_ref = 1,
+                .ia_na.type = SD_DHCP6_OPTION_IA_NA,
+                .ia_pd.type = SD_DHCP6_OPTION_IA_PD,
+                .ifindex = -1,
+                .request = DHCP6_REQUEST_IA_NA,
+                .fd = -1,
+                .req_opts_len = ELEMENTSOF(default_req_opts),
+                .hint_pd_prefix.iapdprefix.lifetime_preferred = (be32_t) -1,
+                .hint_pd_prefix.iapdprefix.lifetime_valid = (be32_t) -1,
+                .req_opts = TAKE_PTR(req_opts),
+        };
+
+        *ret = TAKE_PTR(client);
+
+        return 0;
+}
diff --git a/src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c b/src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c
new file mode 100644
index 00000000..5792f688
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c
@@ -0,0 +1,435 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <errno.h>
+
+#include "alloc-util.h"
+#include "dhcp6-lease-internal.h"
+#include "dhcp6-protocol.h"
+#include "strv.h"
+#include "util.h"
+
+int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) {
+        DHCP6Address *addr;
+        uint32_t valid = 0, t;
+
+        assert_return(ia, -EINVAL);
+        assert_return(expire, -EINVAL);
+
+        LIST_FOREACH(addresses, addr, ia->addresses) {
+                t = be32toh(addr->iaaddr.lifetime_valid);
+                if (valid < t)
+                        valid = t;
+        }
+
+        t = be32toh(ia->ia_na.lifetime_t2);
+        if (t > valid)
+                return -EINVAL;
+
+        *expire = valid - t;
+
+        return 0;
+}
+
+DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) {
+        DHCP6Address *address;
+
+        if (!ia)
+                return NULL;
+
+        while (ia->addresses) {
+                address = ia->addresses;
+
+                LIST_REMOVE(addresses, ia->addresses, address);
+
+                free(address);
+        }
+
+        return NULL;
+}
+
+int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id,
+                             size_t len) {
+        uint8_t *serverid;
+
+        assert_return(lease, -EINVAL);
+        assert_return(id, -EINVAL);
+
+        serverid = memdup(id, len);
+        if (!serverid)
+                return -ENOMEM;
+
+        free_and_replace(lease->serverid, serverid);
+        lease->serverid_len = len;
+
+        return 0;
+}
+
+int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len) {
+        assert_return(lease, -EINVAL);
+
+        if (!lease->serverid)
+                return -ENOMSG;
+
+        if (id)
+                *id = lease->serverid;
+        if (len)
+                *len = lease->serverid_len;
+
+        return 0;
+}
+
+int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) {
+        assert_return(lease, -EINVAL);
+
+        lease->preference = preference;
+
+        return 0;
+}
+
+int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference) {
+        assert_return(preference, -EINVAL);
+
+        if (!lease)
+                return -EINVAL;
+
+        *preference = lease->preference;
+
+        return 0;
+}
+
+int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) {
+        assert_return(lease, -EINVAL);
+
+        lease->rapid_commit = true;
+
+        return 0;
+}
+
+int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit) {
+        assert_return(lease, -EINVAL);
+        assert_return(rapid_commit, -EINVAL);
+
+        *rapid_commit = lease->rapid_commit;
+
+        return 0;
+}
+
+int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid) {
+        assert_return(lease, -EINVAL);
+        assert_return(iaid, -EINVAL);
+
+        *iaid = lease->ia.ia_na.id;
+
+        return 0;
+}
+
+int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid) {
+        assert_return(lease, -EINVAL);
+        assert_return(iaid, -EINVAL);
+
+        *iaid = lease->pd.ia_pd.id;
+
+        return 0;
+}
+
+int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *addr,
+                               uint32_t *lifetime_preferred,
+                               uint32_t *lifetime_valid) {
+        assert_return(lease, -EINVAL);
+        assert_return(addr, -EINVAL);
+        assert_return(lifetime_preferred, -EINVAL);
+        assert_return(lifetime_valid, -EINVAL);
+
+        if (!lease->addr_iter)
+                return -ENOMSG;
+
+        memcpy(addr, &lease->addr_iter->iaaddr.address,
+                sizeof(struct in6_addr));
+        *lifetime_preferred =
+                be32toh(lease->addr_iter->iaaddr.lifetime_preferred);
+        *lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid);
+
+        lease->addr_iter = lease->addr_iter->addresses_next;
+
+        return 0;
+}
+
+void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) {
+        if (lease)
+                lease->addr_iter = lease->ia.addresses;
+}
+
+int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix,
+                          uint8_t *prefix_len,
+                          uint32_t *lifetime_preferred,
+                          uint32_t *lifetime_valid) {
+        assert_return(lease, -EINVAL);
+        assert_return(prefix, -EINVAL);
+        assert_return(prefix_len, -EINVAL);
+        assert_return(lifetime_preferred, -EINVAL);
+        assert_return(lifetime_valid, -EINVAL);
+
+        if (!lease->prefix_iter)
+                return -ENOMSG;
+
+        memcpy(prefix, &lease->prefix_iter->iapdprefix.address,
+               sizeof(struct in6_addr));
+        *prefix_len = lease->prefix_iter->iapdprefix.prefixlen;
+        *lifetime_preferred =
+                be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred);
+        *lifetime_valid =
+                be32toh(lease->prefix_iter->iapdprefix.lifetime_valid);
+
+        lease->prefix_iter = lease->prefix_iter->addresses_next;
+
+        return 0;
+}
+
+void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) {
+        if (lease)
+                lease->prefix_iter = lease->pd.addresses;
+}
+
+int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
+        int r;
+
+        assert_return(lease, -EINVAL);
+        assert_return(optval, -EINVAL);
+
+        if (!optlen)
+                return 0;
+
+        r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->dns,
+                                        lease->dns_count,
+                                        &lease->dns_allocated);
+        if (r < 0)
+                return log_dhcp6_client_errno(client, r, "Invalid DNS server option: %m");
+
+        lease->dns_count = r;
+
+        return 0;
+}
+
+int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs) {
+        assert_return(lease, -EINVAL);
+        assert_return(addrs, -EINVAL);
+
+        if (lease->dns_count) {
+                *addrs = lease->dns;
+                return lease->dns_count;
+        }
+
+        return -ENOENT;
+}
+
+int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval,
+                            size_t optlen) {
+        int r;
+        char **domains;
+
+        assert_return(lease, -EINVAL);
+        assert_return(optval, -EINVAL);
+
+        if (!optlen)
+                return 0;
+
+        r = dhcp6_option_parse_domainname_list(optval, optlen, &domains);
+        if (r < 0)
+                return 0;
+
+        strv_free_and_replace(lease->domains, domains);
+        lease->domains_count = r;
+
+        return r;
+}
+
+int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains) {
+        assert_return(lease, -EINVAL);
+        assert_return(domains, -EINVAL);
+
+        if (lease->domains_count) {
+                *domains = lease->domains;
+                return lease->domains_count;
+        }
+
+        return -ENOENT;
+}
+
+int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
+        int r;
+        uint16_t subopt;
+        size_t sublen;
+        uint8_t *subval;
+
+        assert_return(lease, -EINVAL);
+        assert_return(optval, -EINVAL);
+
+        lease->ntp = mfree(lease->ntp);
+        lease->ntp_count = 0;
+        lease->ntp_allocated = 0;
+
+        while ((r = dhcp6_option_parse(&optval, &optlen, &subopt, &sublen,
+                                       &subval)) >= 0) {
+                int s;
+                char **servers;
+
+                switch(subopt) {
+                case DHCP6_NTP_SUBOPTION_SRV_ADDR:
+                case DHCP6_NTP_SUBOPTION_MC_ADDR:
+                        if (sublen != 16)
+                                return 0;
+
+                        s = dhcp6_option_parse_ip6addrs(subval, sublen,
+                                                        &lease->ntp,
+                                                        lease->ntp_count,
+                                                        &lease->ntp_allocated);
+                        if (s < 0)
+                                return s;
+
+                        lease->ntp_count = s;
+
+                        break;
+
+                case DHCP6_NTP_SUBOPTION_SRV_FQDN:
+                        r = dhcp6_option_parse_domainname_list(subval, sublen,
+                                                               &servers);
+                        if (r < 0)
+                                return 0;
+
+                        strv_free_and_replace(lease->ntp_fqdn, servers);
+                        lease->ntp_fqdn_count = r;
+
+                        break;
+                }
+        }
+
+        if (r != -ENOMSG)
+                return r;
+
+        return 0;
+}
+
+int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
+        int r;
+
+        assert_return(lease, -EINVAL);
+        assert_return(optval, -EINVAL);
+
+        if (!optlen)
+                return 0;
+
+        if (lease->ntp || lease->ntp_fqdn) {
+                log_dhcp6_client(client, "NTP information already provided");
+
+                return 0;
+        }
+
+        log_dhcp6_client(client, "Using deprecated SNTP information");
+
+        r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->ntp,
+                                        lease->ntp_count,
+                                        &lease->ntp_allocated);
+        if (r < 0)
+                return log_dhcp6_client_errno(client, r, "Invalid SNTP server option: %m");
+
+        lease->ntp_count = r;
+
+        return 0;
+}
+
+int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease,
+                                 const struct in6_addr **addrs) {
+        assert_return(lease, -EINVAL);
+        assert_return(addrs, -EINVAL);
+
+        if (lease->ntp_count) {
+                *addrs = lease->ntp;
+                return lease->ntp_count;
+        }
+
+        return -ENOENT;
+}
+
+int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn) {
+        assert_return(lease, -EINVAL);
+        assert_return(ntp_fqdn, -EINVAL);
+
+        if (lease->ntp_fqdn_count) {
+                *ntp_fqdn = lease->ntp_fqdn;
+                return lease->ntp_fqdn_count;
+        }
+
+        return -ENOENT;
+}
+
+int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval,
+                         size_t optlen) {
+        int r;
+        char *fqdn;
+
+        assert_return(lease, -EINVAL);
+        assert_return(optval, -EINVAL);
+
+        if (optlen < 2)
+                return -ENODATA;
+
+        /* Ignore the flags field, it doesn't carry any useful
+           information for clients. */
+        r = dhcp6_option_parse_domainname(optval + 1, optlen - 1, &fqdn);
+        if (r < 0)
+                return r;
+
+        return free_and_replace(lease->fqdn, fqdn);
+}
+
+int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn) {
+        assert_return(lease, -EINVAL);
+        assert_return(fqdn, -EINVAL);
+
+        if (lease->fqdn) {
+                *fqdn = lease->fqdn;
+                return 0;
+        }
+
+        return -ENOENT;
+}
+
+static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
+        assert(lease);
+
+        free(lease->serverid);
+        dhcp6_lease_free_ia(&lease->ia);
+        dhcp6_lease_free_ia(&lease->pd);
+
+        free(lease->dns);
+        free(lease->fqdn);
+
+        lease->domains = strv_free(lease->domains);
+
+        free(lease->ntp);
+
+        lease->ntp_fqdn = strv_free(lease->ntp_fqdn);
+        return mfree(lease);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_lease, sd_dhcp6_lease, dhcp6_lease_free);
+
+int dhcp6_lease_new(sd_dhcp6_lease **ret) {
+        sd_dhcp6_lease *lease;
+
+        lease = new0(sd_dhcp6_lease, 1);
+        if (!lease)
+                return -ENOMEM;
+
+        lease->n_ref = 1;
+
+        LIST_HEAD_INIT(lease->ia.addresses);
+
+        *ret = lease;
+        return 0;
+}
diff --git a/src/core/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/core/systemd/src/libsystemd-network/sd-ipv4acd.c
new file mode 100644
index 00000000..096afbd8
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/sd-ipv4acd.c
@@ -0,0 +1,543 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2014 Axis Communications AB. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/if_ether.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "sd-ipv4acd.h"
+
+#include "alloc-util.h"
+#include "arp-util.h"
+#include "ether-addr-util.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "format-util.h"
+#include "in-addr-util.h"
+#include "log-link.h"
+#include "random-util.h"
+#include "siphash24.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "time-util.h"
+
+/* Constants from the RFC */
+#define PROBE_WAIT_USEC (1U * USEC_PER_SEC)
+#define PROBE_NUM 3U
+#define PROBE_MIN_USEC (1U * USEC_PER_SEC)
+#define PROBE_MAX_USEC (2U * USEC_PER_SEC)
+#define ANNOUNCE_WAIT_USEC (2U * USEC_PER_SEC)
+#define ANNOUNCE_NUM 2U
+#define ANNOUNCE_INTERVAL_USEC (2U * USEC_PER_SEC)
+#define MAX_CONFLICTS 10U
+#define RATE_LIMIT_INTERVAL_USEC (60U * USEC_PER_SEC)
+#define DEFEND_INTERVAL_USEC (10U * USEC_PER_SEC)
+
+typedef enum IPv4ACDState {
+        IPV4ACD_STATE_INIT,
+        IPV4ACD_STATE_STARTED,
+        IPV4ACD_STATE_WAITING_PROBE,
+        IPV4ACD_STATE_PROBING,
+        IPV4ACD_STATE_WAITING_ANNOUNCE,
+        IPV4ACD_STATE_ANNOUNCING,
+        IPV4ACD_STATE_RUNNING,
+        _IPV4ACD_STATE_MAX,
+        _IPV4ACD_STATE_INVALID = -1
+} IPv4ACDState;
+
+struct sd_ipv4acd {
+        unsigned n_ref;
+
+        IPv4ACDState state;
+        int ifindex;
+        int fd;
+
+        char ifname[IF_NAMESIZE + 1];
+        unsigned n_iteration;
+        unsigned n_conflict;
+
+        sd_event_source *receive_message_event_source;
+        sd_event_source *timer_event_source;
+
+        usec_t defend_window;
+        be32_t address;
+
+        /* External */
+        struct ether_addr mac_addr;
+
+        sd_event *event;
+        int event_priority;
+        sd_ipv4acd_callback_t callback;
+        void* userdata;
+};
+
+#define log_ipv4acd_errno(acd, error, fmt, ...)                         \
+        log_interface_full_errno(sd_ipv4acd_get_ifname(acd), LOG_DEBUG, error, "IPV4ACD: " fmt, ##__VA_ARGS__)
+#define log_ipv4acd(acd, fmt, ...)                      \
+        log_ipv4acd_errno(acd, 0, fmt, ##__VA_ARGS__)
+
+static const char * const ipv4acd_state_table[_IPV4ACD_STATE_MAX] = {
+        [IPV4ACD_STATE_INIT]             = "init",
+        [IPV4ACD_STATE_STARTED]          = "started",
+        [IPV4ACD_STATE_WAITING_PROBE]    = "waiting-probe",
+        [IPV4ACD_STATE_PROBING]          = "probing",
+        [IPV4ACD_STATE_WAITING_ANNOUNCE] = "waiting-announce",
+        [IPV4ACD_STATE_ANNOUNCING]       = "announcing",
+        [IPV4ACD_STATE_RUNNING]          = "running",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(ipv4acd_state, IPv4ACDState);
+
+static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_counter) {
+        assert(acd);
+        assert(st < _IPV4ACD_STATE_MAX);
+
+        if (st != acd->state)
+                log_ipv4acd(acd, "%s -> %s", ipv4acd_state_to_string(acd->state), ipv4acd_state_to_string(st));
+
+        if (st == acd->state && !reset_counter)
+                acd->n_iteration++;
+        else {
+                acd->state = st;
+                acd->n_iteration = 0;
+        }
+}
+
+static void ipv4acd_reset(sd_ipv4acd *acd) {
+        assert(acd);
+
+        (void) event_source_disable(acd->timer_event_source);
+        acd->receive_message_event_source = sd_event_source_unref(acd->receive_message_event_source);
+
+        acd->fd = safe_close(acd->fd);
+
+        ipv4acd_set_state(acd, IPV4ACD_STATE_INIT, true);
+}
+
+static sd_ipv4acd *ipv4acd_free(sd_ipv4acd *acd) {
+        assert(acd);
+
+        acd->timer_event_source = sd_event_source_unref(acd->timer_event_source);
+
+        ipv4acd_reset(acd);
+        sd_ipv4acd_detach_event(acd);
+
+        return mfree(acd);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4acd, sd_ipv4acd, ipv4acd_free);
+
+int sd_ipv4acd_new(sd_ipv4acd **ret) {
+        _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL;
+
+        assert_return(ret, -EINVAL);
+
+        acd = new(sd_ipv4acd, 1);
+        if (!acd)
+                return -ENOMEM;
+
+        *acd = (sd_ipv4acd) {
+                .n_ref = 1,
+                .state = IPV4ACD_STATE_INIT,
+                .ifindex = -1,
+                .fd = -1,
+        };
+
+        *ret = TAKE_PTR(acd);
+
+        return 0;
+}
+
+static void ipv4acd_client_notify(sd_ipv4acd *acd, int event) {
+        assert(acd);
+
+        if (!acd->callback)
+                return;
+
+        acd->callback(acd, event, acd->userdata);
+}
+
+int sd_ipv4acd_stop(sd_ipv4acd *acd) {
+        IPv4ACDState old_state;
+
+        if (!acd)
+                return 0;
+
+        old_state = acd->state;
+
+        ipv4acd_reset(acd);
+
+        if (old_state == IPV4ACD_STATE_INIT)
+                return 0;
+
+        log_ipv4acd(acd, "STOPPED");
+
+        ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_STOP);
+
+        return 0;
+}
+
+static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata);
+
+static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_usec) {
+        usec_t next_timeout, time_now;
+
+        assert(acd);
+
+        next_timeout = usec;
+
+        if (random_usec > 0)
+                next_timeout += (usec_t) random_u64() % random_usec;
+
+        assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
+
+        return event_reset_time(acd->event, &acd->timer_event_source,
+                                clock_boottime_or_monotonic(),
+                                time_now + next_timeout, 0,
+                                ipv4acd_on_timeout, acd,
+                                acd->event_priority, "ipv4acd-timer", true);
+}
+
+static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) {
+        assert(acd);
+        assert(arp);
+
+        /* see the BPF */
+        if (memcmp(arp->arp_spa, &acd->address, sizeof(acd->address)) == 0)
+                return true;
+
+        /* the TPA matched instead of the SPA, this is not a conflict */
+        return false;
+}
+
+static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_ipv4acd *acd = userdata;
+        int r = 0;
+
+        assert(acd);
+
+        switch (acd->state) {
+
+        case IPV4ACD_STATE_STARTED:
+                ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true);
+
+                if (acd->n_conflict >= MAX_CONFLICTS) {
+                        char ts[FORMAT_TIMESPAN_MAX];
+                        log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0));
+
+                        r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC);
+                        if (r < 0)
+                                goto fail;
+                } else {
+                        r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC);
+                        if (r < 0)
+                                goto fail;
+                }
+
+                break;
+
+        case IPV4ACD_STATE_WAITING_PROBE:
+        case IPV4ACD_STATE_PROBING:
+                /* Send a probe */
+                r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
+                if (r < 0) {
+                        log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m");
+                        goto fail;
+                } else {
+                        _cleanup_free_ char *address = NULL;
+                        union in_addr_union addr = { .in.s_addr = acd->address };
+
+                        (void) in_addr_to_string(AF_INET, &addr, &address);
+                        log_ipv4acd(acd, "Probing %s", strna(address));
+                }
+
+                if (acd->n_iteration < PROBE_NUM - 2) {
+                        ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false);
+
+                        r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC));
+                        if (r < 0)
+                                goto fail;
+                } else {
+                        ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true);
+
+                        r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0);
+                        if (r < 0)
+                                goto fail;
+                }
+
+                break;
+
+        case IPV4ACD_STATE_ANNOUNCING:
+                if (acd->n_iteration >= ANNOUNCE_NUM - 1) {
+                        ipv4acd_set_state(acd, IPV4ACD_STATE_RUNNING, false);
+                        break;
+                }
+
+                _fallthrough_;
+        case IPV4ACD_STATE_WAITING_ANNOUNCE:
+                /* Send announcement packet */
+                r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
+                if (r < 0) {
+                        log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
+                        goto fail;
+                } else
+                        log_ipv4acd(acd, "ANNOUNCE");
+
+                ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false);
+
+                r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_INTERVAL_USEC, 0);
+                if (r < 0)
+                        goto fail;
+
+                if (acd->n_iteration == 0) {
+                        acd->n_conflict = 0;
+                        ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_BIND);
+                }
+
+                break;
+
+        default:
+                assert_not_reached("Invalid state.");
+        }
+
+        return 0;
+
+fail:
+        sd_ipv4acd_stop(acd);
+        return 0;
+}
+
+static void ipv4acd_on_conflict(sd_ipv4acd *acd) {
+        _cleanup_free_ char *address = NULL;
+        union in_addr_union addr = { .in.s_addr = acd->address };
+
+        assert(acd);
+
+        acd->n_conflict++;
+
+        (void) in_addr_to_string(AF_INET, &addr, &address);
+        log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict);
+
+        ipv4acd_reset(acd);
+        ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT);
+}
+
+static int ipv4acd_on_packet(
+                sd_event_source *s,
+                int fd,
+                uint32_t revents,
+                void *userdata) {
+
+        sd_ipv4acd *acd = userdata;
+        struct ether_arp packet;
+        ssize_t n;
+        int r;
+
+        assert(s);
+        assert(acd);
+        assert(fd >= 0);
+
+        n = recv(fd, &packet, sizeof(struct ether_arp), 0);
+        if (n < 0) {
+                if (IN_SET(errno, EAGAIN, EINTR))
+                        return 0;
+
+                log_ipv4acd_errno(acd, errno, "Failed to read ARP packet: %m");
+                goto fail;
+        }
+        if ((size_t) n != sizeof(struct ether_arp)) {
+                log_ipv4acd(acd, "Ignoring too short ARP packet.");
+                return 0;
+        }
+
+        switch (acd->state) {
+
+        case IPV4ACD_STATE_ANNOUNCING:
+        case IPV4ACD_STATE_RUNNING:
+
+                if (ipv4acd_arp_conflict(acd, &packet)) {
+                        usec_t ts;
+
+                        assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0);
+
+                        /* Defend address */
+                        if (ts > acd->defend_window) {
+                                acd->defend_window = ts + DEFEND_INTERVAL_USEC;
+                                r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
+                                if (r < 0) {
+                                        log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
+                                        goto fail;
+                                } else
+                                        log_ipv4acd(acd, "DEFEND");
+
+                        } else
+                                ipv4acd_on_conflict(acd);
+                }
+                break;
+
+        case IPV4ACD_STATE_WAITING_PROBE:
+        case IPV4ACD_STATE_PROBING:
+        case IPV4ACD_STATE_WAITING_ANNOUNCE:
+                /* BPF ensures this packet indicates a conflict */
+                ipv4acd_on_conflict(acd);
+                break;
+
+        default:
+                assert_not_reached("Invalid state.");
+        }
+
+        return 0;
+
+fail:
+        sd_ipv4acd_stop(acd);
+        return 0;
+}
+
+int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int ifindex) {
+        char ifname[IF_NAMESIZE + 1];
+
+        assert_return(acd, -EINVAL);
+        assert_return(ifindex > 0, -EINVAL);
+        assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+        if (!format_ifname(ifindex, ifname))
+                return -ENODEV;
+
+        strcpy(acd->ifname, ifname);
+        acd->ifindex = ifindex;
+
+        return 0;
+}
+
+int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd) {
+        if (!acd)
+                return -EINVAL;
+
+        return acd->ifindex;
+}
+
+const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd) {
+        if (!acd)
+                return NULL;
+
+        return empty_to_null(acd->ifname);
+}
+
+int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) {
+        assert_return(acd, -EINVAL);
+        assert_return(addr, -EINVAL);
+        assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+        acd->mac_addr = *addr;
+
+        return 0;
+}
+
+int sd_ipv4acd_detach_event(sd_ipv4acd *acd) {
+        assert_return(acd, -EINVAL);
+
+        acd->event = sd_event_unref(acd->event);
+
+        return 0;
+}
+
+int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority) {
+        int r;
+
+        assert_return(acd, -EINVAL);
+        assert_return(!acd->event, -EBUSY);
+
+        if (event)
+                acd->event = sd_event_ref(event);
+        else {
+                r = sd_event_default(&acd->event);
+                if (r < 0)
+                        return r;
+        }
+
+        acd->event_priority = priority;
+
+        return 0;
+}
+
+int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata) {
+        assert_return(acd, -EINVAL);
+
+        acd->callback = cb;
+        acd->userdata = userdata;
+
+        return 0;
+}
+
+int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address) {
+        assert_return(acd, -EINVAL);
+        assert_return(address, -EINVAL);
+        assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+        acd->address = address->s_addr;
+
+        return 0;
+}
+
+int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address) {
+        assert_return(acd, -EINVAL);
+        assert_return(address, -EINVAL);
+
+        address->s_addr = acd->address;
+
+        return 0;
+}
+
+int sd_ipv4acd_is_running(sd_ipv4acd *acd) {
+        assert_return(acd, false);
+
+        return acd->state != IPV4ACD_STATE_INIT;
+}
+
+int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts) {
+        int r;
+
+        assert_return(acd, -EINVAL);
+        assert_return(acd->event, -EINVAL);
+        assert_return(acd->ifindex > 0, -EINVAL);
+        assert_return(acd->address != 0, -EINVAL);
+        assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL);
+        assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+        r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr);
+        if (r < 0)
+                return r;
+
+        CLOSE_AND_REPLACE(acd->fd, r);
+        acd->defend_window = 0;
+
+        if (reset_conflicts)
+                acd->n_conflict = 0;
+
+        r = sd_event_add_io(acd->event, &acd->receive_message_event_source, acd->fd, EPOLLIN, ipv4acd_on_packet, acd);
+        if (r < 0)
+                goto fail;
+
+        r = sd_event_source_set_priority(acd->receive_message_event_source, acd->event_priority);
+        if (r < 0)
+                goto fail;
+
+        (void) sd_event_source_set_description(acd->receive_message_event_source, "ipv4acd-receive-message");
+
+        r = ipv4acd_set_next_wakeup(acd, 0, 0);
+        if (r < 0)
+                goto fail;
+
+        ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true);
+        return 0;
+
+fail:
+        ipv4acd_reset(acd);
+        return r;
+}
diff --git a/src/core/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/core/systemd/src/libsystemd-network/sd-ipv4ll.c
new file mode 100644
index 00000000..c5e7a91f
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/sd-ipv4ll.c
@@ -0,0 +1,342 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+  Copyright © 2014 Axis Communications AB. All rights reserved.
+***/
+
+#include "nm-sd-adapt-core.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "sd-id128.h"
+#include "sd-ipv4acd.h"
+#include "sd-ipv4ll.h"
+
+#include "alloc-util.h"
+#include "ether-addr-util.h"
+#include "in-addr-util.h"
+#include "log-link.h"
+#include "random-util.h"
+#include "siphash24.h"
+#include "sparse-endian.h"
+#include "string-util.h"
+#include "util.h"
+
+#define IPV4LL_NETWORK UINT32_C(0xA9FE0000)
+#define IPV4LL_NETMASK UINT32_C(0xFFFF0000)
+
+#define IPV4LL_DONT_DESTROY(ll) \
+        _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll)
+
+struct sd_ipv4ll {
+        unsigned n_ref;
+
+        sd_ipv4acd *acd;
+
+        be32_t address; /* the address pushed to ACD */
+        struct ether_addr mac;
+
+        struct {
+                le64_t value;
+                le64_t generation;
+        } seed;
+        bool seed_set;
+
+        /* External */
+        be32_t claimed_address;
+
+        sd_ipv4ll_callback_t callback;
+        void* userdata;
+};
+
+#define log_ipv4ll_errno(ll, error, fmt, ...)                           \
+        log_interface_full_errno(sd_ipv4ll_get_ifname(ll), LOG_DEBUG, error, "IPV4LL: " fmt, ##__VA_ARGS__)
+#define log_ipv4ll(ll, fmt, ...)                        \
+        log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__)
+
+static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
+
+static sd_ipv4ll *ipv4ll_free(sd_ipv4ll *ll) {
+        assert(ll);
+
+        sd_ipv4acd_unref(ll->acd);
+        return mfree(ll);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4ll, sd_ipv4ll, ipv4ll_free);
+
+int sd_ipv4ll_new(sd_ipv4ll **ret) {
+        _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        ll = new0(sd_ipv4ll, 1);
+        if (!ll)
+                return -ENOMEM;
+
+        ll->n_ref = 1;
+
+        r = sd_ipv4acd_new(&ll->acd);
+        if (r < 0)
+                return r;
+
+        r = sd_ipv4acd_set_callback(ll->acd, ipv4ll_on_acd, ll);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(ll);
+
+        return 0;
+}
+
+int sd_ipv4ll_stop(sd_ipv4ll *ll) {
+        if (!ll)
+                return 0;
+
+        return sd_ipv4acd_stop(ll->acd);
+}
+
+int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) {
+        assert_return(ll, -EINVAL);
+        assert_return(ifindex > 0, -EINVAL);
+        assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
+
+        return sd_ipv4acd_set_ifindex(ll->acd, ifindex);
+}
+
+int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll) {
+        if (!ll)
+                return -EINVAL;
+
+        return sd_ipv4acd_get_ifindex(ll->acd);
+}
+
+const char *sd_ipv4ll_get_ifname(sd_ipv4ll *ll) {
+        if (!ll)
+                return NULL;
+
+        return sd_ipv4acd_get_ifname(ll->acd);
+}
+
+int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
+        int r;
+
+        assert_return(ll, -EINVAL);
+        assert_return(addr, -EINVAL);
+        assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
+
+        r = sd_ipv4acd_set_mac(ll->acd, addr);
+        if (r < 0)
+                return r;
+
+        ll->mac = *addr;
+        return 0;
+}
+
+int sd_ipv4ll_detach_event(sd_ipv4ll *ll) {
+        assert_return(ll, -EINVAL);
+
+        return sd_ipv4acd_detach_event(ll->acd);
+}
+
+int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) {
+        assert_return(ll, -EINVAL);
+
+        return sd_ipv4acd_attach_event(ll->acd, event, priority);
+}
+
+int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) {
+        assert_return(ll, -EINVAL);
+
+        ll->callback = cb;
+        ll->userdata = userdata;
+
+        return 0;
+}
+
+int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) {
+        assert_return(ll, -EINVAL);
+        assert_return(address, -EINVAL);
+
+        if (ll->claimed_address == 0)
+                return -ENOENT;
+
+        address->s_addr = ll->claimed_address;
+
+        return 0;
+}
+
+int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) {
+        assert_return(ll, -EINVAL);
+        assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
+
+        ll->seed.value = htole64(seed);
+        ll->seed_set = true;
+
+        return 0;
+}
+
+int sd_ipv4ll_is_running(sd_ipv4ll *ll) {
+        assert_return(ll, false);
+
+        return sd_ipv4acd_is_running(ll->acd);
+}
+
+static bool ipv4ll_address_is_valid(const struct in_addr *address) {
+        assert(address);
+
+        if (!in_addr_is_link_local(AF_INET, (const union in_addr_union *) address))
+                return false;
+
+        return !IN_SET(be32toh(address->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U);
+}
+
+int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) {
+        int r;
+
+        assert_return(ll, -EINVAL);
+        assert_return(address, -EINVAL);
+        assert_return(ipv4ll_address_is_valid(address), -EINVAL);
+
+        r = sd_ipv4acd_set_address(ll->acd, address);
+        if (r < 0)
+                return r;
+
+        ll->address = address->s_addr;
+
+        return 0;
+}
+
+#define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b)
+
+static int ipv4ll_pick_address(sd_ipv4ll *ll) {
+        _cleanup_free_ char *address = NULL;
+        be32_t addr;
+
+        assert(ll);
+
+        do {
+                uint64_t h;
+
+                h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes);
+
+                /* Increase the generation counter by one */
+                ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1);
+
+                addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK);
+        } while (addr == ll->address ||
+                 IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U));
+
+        (void) in_addr_to_string(AF_INET, &(union in_addr_union) { .in.s_addr = addr }, &address);
+        log_ipv4ll(ll, "Picked new IP address %s.", strna(address));
+
+        return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr });
+}
+
+#define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
+
+static int ipv4ll_start_internal(sd_ipv4ll *ll, bool reset_generation) {
+        int r;
+        bool picked_address = false;
+
+        assert_return(ll, -EINVAL);
+        assert_return(!ether_addr_is_null(&ll->mac), -EINVAL);
+
+        /* If no random seed is set, generate some from the MAC address */
+        if (!ll->seed_set)
+                ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes));
+
+        if (reset_generation)
+                ll->seed.generation = 0;
+
+        if (ll->address == 0) {
+                r = ipv4ll_pick_address(ll);
+                if (r < 0)
+                        return r;
+
+                picked_address = true;
+        }
+
+        r = sd_ipv4acd_start(ll->acd, reset_generation);
+        if (r < 0) {
+
+                /* We couldn't start? If so, let's forget the picked address again, the user might make a change and
+                 * retry, and we want the new data to take effect when picking an address. */
+                if (picked_address)
+                        ll->address = 0;
+
+                return r;
+        }
+
+        return 1;
+}
+
+int sd_ipv4ll_start(sd_ipv4ll *ll) {
+        assert_return(ll, -EINVAL);
+
+        if (sd_ipv4ll_is_running(ll))
+                return 0;
+
+        return ipv4ll_start_internal(ll, true);
+}
+
+int sd_ipv4ll_restart(sd_ipv4ll *ll) {
+        ll->address = 0;
+
+        return ipv4ll_start_internal(ll, false);
+}
+
+static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) {
+        assert(ll);
+
+        if (ll->callback)
+                ll->callback(ll, event, ll->userdata);
+}
+
+void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
+        sd_ipv4ll *ll = userdata;
+        IPV4LL_DONT_DESTROY(ll);
+        int r;
+
+        assert(acd);
+        assert(ll);
+
+        switch (event) {
+
+        case SD_IPV4ACD_EVENT_STOP:
+                ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
+                ll->claimed_address = 0;
+                break;
+
+        case SD_IPV4ACD_EVENT_BIND:
+                ll->claimed_address = ll->address;
+                ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND);
+                break;
+
+        case SD_IPV4ACD_EVENT_CONFLICT:
+                /* if an address was already bound we must call up to the
+                   user to handle this, otherwise we just try again */
+                if (ll->claimed_address != 0) {
+                        ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_CONFLICT);
+
+                        ll->claimed_address = 0;
+                } else {
+                        r = sd_ipv4ll_restart(ll);
+                        if (r < 0)
+                                goto error;
+                }
+
+                break;
+
+        default:
+                assert_not_reached("Invalid IPv4ACD event.");
+        }
+
+        return;
+
+error:
+        ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
+}
diff --git a/src/core/systemd/src/libsystemd-network/sd-lldp.c b/src/core/systemd/src/libsystemd-network/sd-lldp.c
new file mode 100644
index 00000000..b12d8e27
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/sd-lldp.c
@@ -0,0 +1,500 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <arpa/inet.h>
+#include <linux/sockios.h>
+#include <sys/ioctl.h>
+
+#include "sd-lldp.h"
+
+#include "alloc-util.h"
+#include "ether-addr-util.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "lldp-internal.h"
+#include "lldp-neighbor.h"
+#include "lldp-network.h"
+#include "memory-util.h"
+#include "socket-util.h"
+#include "sort-util.h"
+#include "string-table.h"
+
+#define LLDP_DEFAULT_NEIGHBORS_MAX 128U
+
+static const char * const lldp_event_table[_SD_LLDP_EVENT_MAX] = {
+        [SD_LLDP_EVENT_ADDED]   = "added",
+        [SD_LLDP_EVENT_REMOVED] = "removed",
+        [SD_LLDP_EVENT_UPDATED]   = "updated",
+        [SD_LLDP_EVENT_REFRESHED] = "refreshed",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(lldp_event, sd_lldp_event);
+
+static void lldp_flush_neighbors(sd_lldp *lldp) {
+        assert(lldp);
+
+        hashmap_clear(lldp->neighbor_by_id);
+}
+
+static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) {
+        assert(lldp);
+        assert(event >= 0 && event < _SD_LLDP_EVENT_MAX);
+
+        if (!lldp->callback) {
+                log_lldp("Received '%s' event.", lldp_event_to_string(event));
+                return;
+        }
+
+        log_lldp("Invoking callback for '%s' event.", lldp_event_to_string(event));
+        lldp->callback(lldp, event, n, lldp->userdata);
+}
+
+static int lldp_make_space(sd_lldp *lldp, size_t extra) {
+        usec_t t = USEC_INFINITY;
+        bool changed = false;
+
+        assert(lldp);
+
+        /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
+         * are free. */
+
+        for (;;) {
+                _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+
+                n = prioq_peek(lldp->neighbor_by_expiry);
+                if (!n)
+                        break;
+
+                sd_lldp_neighbor_ref(n);
+
+                if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra))
+                        goto remove_one;
+
+                if (t == USEC_INFINITY)
+                        t = now(clock_boottime_or_monotonic());
+
+                if (n->until > t)
+                        break;
+
+        remove_one:
+                lldp_neighbor_unlink(n);
+                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n);
+                changed = true;
+        }
+
+        return changed;
+}
+
+static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
+        assert(lldp);
+        assert(n);
+
+        /* Don't keep data with a zero TTL */
+        if (n->ttl <= 0)
+                return false;
+
+        /* Filter out data from the filter address */
+        if (!ether_addr_is_null(&lldp->filter_address) &&
+            ether_addr_equal(&lldp->filter_address, &n->source_address))
+                return false;
+
+        /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
+         * no caps field set. */
+        if (n->has_capabilities &&
+            (n->enabled_capabilities & lldp->capability_mask) == 0)
+                return false;
+
+        /* Keep everything else */
+        return true;
+}
+
+static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor);
+
+static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
+        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL;
+        bool keep;
+        int r;
+
+        assert(lldp);
+        assert(n);
+        assert(!n->lldp);
+
+        keep = lldp_keep_neighbor(lldp, n);
+
+        /* First retrieve the old entry for this MSAP */
+        old = hashmap_get(lldp->neighbor_by_id, &n->id);
+        if (old) {
+                sd_lldp_neighbor_ref(old);
+
+                if (!keep) {
+                        lldp_neighbor_unlink(old);
+                        lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
+                        return 0;
+                }
+
+                if (lldp_neighbor_equal(n, old)) {
+                        /* Is this equal, then restart the TTL counter, but don't do anything else. */
+                        old->timestamp = n->timestamp;
+                        lldp_start_timer(lldp, old);
+                        lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
+                        return 0;
+                }
+
+                /* Data changed, remove the old entry, and add a new one */
+                lldp_neighbor_unlink(old);
+
+        } else if (!keep)
+                return 0;
+
+        /* Then, make room for at least one new neighbor */
+        lldp_make_space(lldp, 1);
+
+        r = hashmap_put(lldp->neighbor_by_id, &n->id, n);
+        if (r < 0)
+                goto finish;
+
+        r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx);
+        if (r < 0) {
+                assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n);
+                goto finish;
+        }
+
+        n->lldp = lldp;
+
+        lldp_start_timer(lldp, n);
+        lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n);
+
+        return 1;
+
+finish:
+        if (old)
+                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
+
+        return r;
+}
+
+static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
+        int r;
+
+        assert(lldp);
+        assert(n);
+
+        r = lldp_neighbor_parse(n);
+        if (r == -EBADMSG) /* Ignore bad messages */
+                return 0;
+        if (r < 0)
+                return r;
+
+        r = lldp_add_neighbor(lldp, n);
+        if (r < 0) {
+                log_lldp_errno(r, "Failed to add datagram. Ignoring.");
+                return 0;
+        }
+
+        log_lldp("Successfully processed LLDP datagram.");
+        return 0;
+}
+
+static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+        ssize_t space, length;
+        sd_lldp *lldp = userdata;
+        struct timespec ts;
+
+        assert(fd >= 0);
+        assert(lldp);
+
+        space = next_datagram_size_fd(fd);
+        if (space < 0)
+                return log_lldp_errno(space, "Failed to determine datagram size to read: %m");
+
+        n = lldp_neighbor_new(space);
+        if (!n)
+                return -ENOMEM;
+
+        length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
+        if (length < 0) {
+                if (IN_SET(errno, EAGAIN, EINTR))
+                        return 0;
+
+                return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
+        }
+
+        if ((size_t) length != n->raw_size) {
+                log_lldp("Packet size mismatch.");
+                return -EINVAL;
+        }
+
+        /* Try to get the timestamp of this packet if it is known */
+        if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
+                triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
+        else
+                triple_timestamp_get(&n->timestamp);
+
+        return lldp_handle_datagram(lldp, n);
+}
+
+static void lldp_reset(sd_lldp *lldp) {
+        assert(lldp);
+
+        (void) event_source_disable(lldp->timer_event_source);
+        lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
+        lldp->fd = safe_close(lldp->fd);
+}
+
+_public_ int sd_lldp_start(sd_lldp *lldp) {
+        int r;
+
+        assert_return(lldp, -EINVAL);
+        assert_return(lldp->event, -EINVAL);
+        assert_return(lldp->ifindex > 0, -EINVAL);
+
+        if (lldp->fd >= 0)
+                return 0;
+
+        assert(!lldp->io_event_source);
+
+        lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex);
+        if (lldp->fd < 0)
+                return lldp->fd;
+
+        r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
+        if (r < 0)
+                goto fail;
+
+        r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
+        if (r < 0)
+                goto fail;
+
+        (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
+
+        log_lldp("Started LLDP client");
+        return 1;
+
+fail:
+        lldp_reset(lldp);
+        return r;
+}
+
+_public_ int sd_lldp_stop(sd_lldp *lldp) {
+        if (!lldp)
+                return 0;
+
+        if (lldp->fd < 0)
+                return 0;
+
+        log_lldp("Stopping LLDP client");
+
+        lldp_reset(lldp);
+        lldp_flush_neighbors(lldp);
+
+        return 1;
+}
+
+_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) {
+        int r;
+
+        assert_return(lldp, -EINVAL);
+        assert_return(lldp->fd < 0, -EBUSY);
+        assert_return(!lldp->event, -EBUSY);
+
+        if (event)
+                lldp->event = sd_event_ref(event);
+        else {
+                r = sd_event_default(&lldp->event);
+                if (r < 0)
+                        return r;
+        }
+
+        lldp->event_priority = priority;
+
+        return 0;
+}
+
+_public_ int sd_lldp_detach_event(sd_lldp *lldp) {
+
+        assert_return(lldp, -EINVAL);
+        assert_return(lldp->fd < 0, -EBUSY);
+
+        lldp->event = sd_event_unref(lldp->event);
+        return 0;
+}
+
+_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) {
+        assert_return(lldp, NULL);
+
+        return lldp->event;
+}
+
+_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
+        assert_return(lldp, -EINVAL);
+
+        lldp->callback = cb;
+        lldp->userdata = userdata;
+
+        return 0;
+}
+
+_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) {
+        assert_return(lldp, -EINVAL);
+        assert_return(ifindex > 0, -EINVAL);
+        assert_return(lldp->fd < 0, -EBUSY);
+
+        lldp->ifindex = ifindex;
+        return 0;
+}
+
+static sd_lldp* lldp_free(sd_lldp *lldp) {
+        assert(lldp);
+
+        lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
+
+        lldp_reset(lldp);
+        sd_lldp_detach_event(lldp);
+        lldp_flush_neighbors(lldp);
+
+        hashmap_free(lldp->neighbor_by_id);
+        prioq_free(lldp->neighbor_by_expiry);
+        return mfree(lldp);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp, sd_lldp, lldp_free);
+
+_public_ int sd_lldp_new(sd_lldp **ret) {
+        _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        lldp = new(sd_lldp, 1);
+        if (!lldp)
+                return -ENOMEM;
+
+        *lldp = (sd_lldp) {
+                .n_ref = 1,
+                .fd = -1,
+                .neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX,
+                .capability_mask = (uint16_t) -1,
+        };
+
+        lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_hash_ops);
+        if (!lldp->neighbor_by_id)
+                return -ENOMEM;
+
+        r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(lldp);
+
+        return 0;
+}
+
+static int neighbor_compare_func(sd_lldp_neighbor * const *a, sd_lldp_neighbor * const *b) {
+        return lldp_neighbor_id_compare_func(&(*a)->id, &(*b)->id);
+}
+
+static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_lldp *lldp = userdata;
+        int r;
+
+        r = lldp_make_space(lldp, 0);
+        if (r < 0)
+                return log_lldp_errno(r, "Failed to make space: %m");
+
+        r = lldp_start_timer(lldp, NULL);
+        if (r < 0)
+                return log_lldp_errno(r, "Failed to restart timer: %m");
+
+        return 0;
+}
+
+static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) {
+        sd_lldp_neighbor *n;
+
+        assert(lldp);
+
+        if (neighbor)
+                lldp_neighbor_start_ttl(neighbor);
+
+        n = prioq_peek(lldp->neighbor_by_expiry);
+        if (!n)
+                return event_source_disable(lldp->timer_event_source);
+
+        if (!lldp->event)
+                return 0;
+
+        return event_reset_time(lldp->event, &lldp->timer_event_source,
+                                clock_boottime_or_monotonic(),
+                                n->until, 0,
+                                on_timer_event, lldp,
+                                lldp->event_priority, "lldp-timer", true);
+}
+
+_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
+        sd_lldp_neighbor **l = NULL, *n;
+        int k = 0, r;
+
+        assert_return(lldp, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */
+                *ret = NULL;
+                return 0;
+        }
+
+        l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id));
+        if (!l)
+                return -ENOMEM;
+
+        r = lldp_start_timer(lldp, NULL);
+        if (r < 0) {
+                free(l);
+                return r;
+        }
+
+        HASHMAP_FOREACH(n, lldp->neighbor_by_id)
+                l[k++] = sd_lldp_neighbor_ref(n);
+
+        assert((size_t) k == hashmap_size(lldp->neighbor_by_id));
+
+        /* Return things in a stable order */
+        typesafe_qsort(l, k, neighbor_compare_func);
+        *ret = l;
+
+        return k;
+}
+
+_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) {
+        assert_return(lldp, -EINVAL);
+        assert_return(m > 0, -EINVAL);
+
+        lldp->neighbors_max = m;
+        lldp_make_space(lldp, 0);
+
+        return 0;
+}
+
+_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) {
+        assert_return(lldp, -EINVAL);
+        assert_return(mask != 0, -EINVAL);
+
+        lldp->capability_mask = mask;
+
+        return 0;
+}
+
+_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) {
+        assert_return(lldp, -EINVAL);
+
+        /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
+         * that our own can be filtered out here. */
+
+        if (addr)
+                lldp->filter_address = *addr;
+        else
+                zero(lldp->filter_address);
+
+        return 0;
+}
diff --git a/src/core/systemd/src/libsystemd/sd-event/event-source.h b/src/core/systemd/src/libsystemd/sd-event/event-source.h
new file mode 100644
index 00000000..f0d2a1b9
--- /dev/null
+++ b/src/core/systemd/src/libsystemd/sd-event/event-source.h
@@ -0,0 +1,220 @@
+#pragma once
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <sys/wait.h>
+
+#include "sd-event.h"
+
+#include "fs-util.h"
+#include "hashmap.h"
+#include "list.h"
+#include "prioq.h"
+#include "ratelimit.h"
+
+typedef enum EventSourceType {
+        SOURCE_IO,
+        SOURCE_TIME_REALTIME,
+        SOURCE_TIME_BOOTTIME,
+        SOURCE_TIME_MONOTONIC,
+        SOURCE_TIME_REALTIME_ALARM,
+        SOURCE_TIME_BOOTTIME_ALARM,
+        SOURCE_SIGNAL,
+        SOURCE_CHILD,
+        SOURCE_DEFER,
+        SOURCE_POST,
+        SOURCE_EXIT,
+        SOURCE_WATCHDOG,
+        SOURCE_INOTIFY,
+        _SOURCE_EVENT_SOURCE_TYPE_MAX,
+        _SOURCE_EVENT_SOURCE_TYPE_INVALID = -1
+} EventSourceType;
+
+/* All objects we use in epoll events start with this value, so that
+ * we know how to dispatch it */
+typedef enum WakeupType {
+        WAKEUP_NONE,
+        WAKEUP_EVENT_SOURCE, /* either I/O or pidfd wakeup */
+        WAKEUP_CLOCK_DATA,
+        WAKEUP_SIGNAL_DATA,
+        WAKEUP_INOTIFY_DATA,
+        _WAKEUP_TYPE_MAX,
+        _WAKEUP_TYPE_INVALID = -1,
+} WakeupType;
+
+struct inode_data;
+
+struct sd_event_source {
+        WakeupType wakeup;
+
+        unsigned n_ref;
+
+        sd_event *event;
+        void *userdata;
+        sd_event_handler_t prepare;
+
+        char *description;
+
+        EventSourceType type:5;
+        signed int enabled:3;
+        bool pending:1;
+        bool dispatching:1;
+        bool floating:1;
+        bool exit_on_failure:1;
+        bool ratelimited:1;
+
+        int64_t priority;
+        unsigned pending_index;
+        unsigned prepare_index;
+        uint64_t pending_iteration;
+        uint64_t prepare_iteration;
+
+        sd_event_destroy_t destroy_callback;
+
+        LIST_FIELDS(sd_event_source, sources);
+
+        RateLimit rate_limit;
+
+        /* These are primarily fields relevant for time event sources, but since any event source can
+         * effectively become one when rate-limited, this is part of the common fields. */
+        unsigned earliest_index;
+        unsigned latest_index;
+
+        union {
+                struct {
+                        sd_event_io_handler_t callback;
+                        int fd;
+                        uint32_t events;
+                        uint32_t revents;
+                        bool registered:1;
+                        bool owned:1;
+                } io;
+                struct {
+                        sd_event_time_handler_t callback;
+                        usec_t next, accuracy;
+                } time;
+                struct {
+                        sd_event_signal_handler_t callback;
+                        struct signalfd_siginfo siginfo;
+                        int sig;
+                } signal;
+                struct {
+                        sd_event_child_handler_t callback;
+                        siginfo_t siginfo;
+                        pid_t pid;
+                        int options;
+                        int pidfd;
+                        bool registered:1; /* whether the pidfd is registered in the epoll */
+                        bool pidfd_owned:1; /* close pidfd when event source is freed */
+                        bool process_owned:1; /* kill+reap process when event source is freed */
+                        bool exited:1; /* true if process exited (i.e. if there's value in SIGKILLing it if we want to get rid of it) */
+                        bool waited:1; /* true if process was waited for (i.e. if there's value in waitid(P_PID)'ing it if we want to get rid of it) */
+                } child;
+                struct {
+                        sd_event_handler_t callback;
+                } defer;
+                struct {
+                        sd_event_handler_t callback;
+                } post;
+                struct {
+                        sd_event_handler_t callback;
+                        unsigned prioq_index;
+                } exit;
+                struct {
+                        sd_event_inotify_handler_t callback;
+                        uint32_t mask;
+                        struct inode_data *inode_data;
+                        LIST_FIELDS(sd_event_source, by_inode_data);
+                } inotify;
+        };
+};
+
+struct clock_data {
+        WakeupType wakeup;
+        int fd;
+
+        /* For all clocks we maintain two priority queues each, one
+         * ordered for the earliest times the events may be
+         * dispatched, and one ordered by the latest times they must
+         * have been dispatched. The range between the top entries in
+         * the two prioqs is the time window we can freely schedule
+         * wakeups in */
+
+        Prioq *earliest;
+        Prioq *latest;
+        usec_t next;
+
+        bool needs_rearm:1;
+};
+
+struct signal_data {
+        WakeupType wakeup;
+
+        /* For each priority we maintain one signal fd, so that we
+         * only have to dequeue a single event per priority at a
+         * time. */
+
+        int fd;
+        int64_t priority;
+        sigset_t sigset;
+        sd_event_source *current;
+};
+
+/* A structure listing all event sources currently watching a specific inode */
+struct inode_data {
+        /* The identifier for the inode, the combination of the .st_dev + .st_ino fields of the file */
+        ino_t ino;
+        dev_t dev;
+
+        /* An fd of the inode to watch. The fd is kept open until the next iteration of the loop, so that we can
+         * rearrange the priority still until then, as we need the original inode to change the priority as we need to
+         * add a watch descriptor to the right inotify for the priority which we can only do if we have a handle to the
+         * original inode. We keep a list of all inode_data objects with an open fd in the to_close list (see below) of
+         * the sd-event object, so that it is efficient to close everything, before entering the next event loop
+         * iteration. */
+        int fd;
+
+        /* The inotify "watch descriptor" */
+        int wd;
+
+        /* The combination of the mask of all inotify watches on this inode we manage. This is also the mask that has
+         * most recently been set on the watch descriptor. */
+        uint32_t combined_mask;
+
+        /* All event sources subscribed to this inode */
+        LIST_HEAD(sd_event_source, event_sources);
+
+        /* The inotify object we watch this inode with */
+        struct inotify_data *inotify_data;
+
+        /* A linked list of all inode data objects with fds to close (see above) */
+        LIST_FIELDS(struct inode_data, to_close);
+};
+
+/* A structure encapsulating an inotify fd */
+struct inotify_data {
+        WakeupType wakeup;
+
+        /* For each priority we maintain one inotify fd, so that we only have to dequeue a single event per priority at
+         * a time */
+
+        int fd;
+        int64_t priority;
+
+        Hashmap *inodes; /* The inode_data structures keyed by dev+ino */
+        Hashmap *wd;     /* The inode_data structures keyed by the watch descriptor for each */
+
+        /* The buffer we read inotify events into */
+        union inotify_event_buffer buffer;
+        size_t buffer_filled; /* fill level of the buffer */
+
+        /* How many event sources are currently marked pending for this inotify. We won't read new events off the
+         * inotify fd as long as there are still pending events on the inotify (because we have no strategy of queuing
+         * the events locally if they can't be coalesced). */
+        unsigned n_pending;
+
+        /* A linked list of all inotify objects with data already read, that still need processing. We keep this list
+         * to make it efficient to figure out what inotify objects to process data on next. */
+        LIST_FIELDS(struct inotify_data, buffered);
+};
diff --git a/src/core/systemd/src/libsystemd/sd-event/event-util.c b/src/core/systemd/src/libsystemd/sd-event/event-util.c
new file mode 100644
index 00000000..7cc55be1
--- /dev/null
+++ b/src/core/systemd/src/libsystemd/sd-event/event-util.c
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <errno.h>
+
+#include "event-source.h"
+#include "event-util.h"
+#include "log.h"
+#include "string-util.h"
+
+int event_reset_time(
+                sd_event *e,
+                sd_event_source **s,
+                clockid_t clock,
+                uint64_t usec,
+                uint64_t accuracy,
+                sd_event_time_handler_t callback,
+                void *userdata,
+                int64_t priority,
+                const char *description,
+                bool force_reset) {
+
+        bool created = false;
+        int enabled, r;
+        clockid_t c;
+
+        assert(e);
+        assert(s);
+
+        if (*s) {
+                if (!force_reset) {
+                        r = sd_event_source_get_enabled(*s, &enabled);
+                        if (r < 0)
+                                return log_debug_errno(r, "sd-event: Failed to query whether event source \"%s\" is enabled or not: %m",
+                                                       strna((*s)->description ?: description));
+
+                        if (enabled != SD_EVENT_OFF)
+                                return 0;
+                }
+
+                r = sd_event_source_get_time_clock(*s, &c);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to get clock id of event source \"%s\": %m", strna((*s)->description ?: description));
+
+                if (c != clock)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "sd-event: Current clock id %i of event source \"%s\" is different from specified one %i.",
+                                               (int)c,
+                                               strna((*s)->description ? : description),
+                                               (int)clock);
+
+                r = sd_event_source_set_time(*s, usec);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to set time for event source \"%s\": %m", strna((*s)->description ?: description));
+
+                r = sd_event_source_set_time_accuracy(*s, accuracy);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to set accuracy for event source \"%s\": %m", strna((*s)->description ?: description));
+
+                /* callback function is not updated, as we do not have sd_event_source_set_time_callback(). */
+
+                (void) sd_event_source_set_userdata(*s, userdata);
+
+                r = sd_event_source_set_enabled(*s, SD_EVENT_ONESHOT);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to enable event source \"%s\": %m", strna((*s)->description ?: description));
+        } else {
+                r = sd_event_add_time(e, s, clock, usec, accuracy, callback, userdata);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to create timer event \"%s\": %m", strna(description));
+
+                created = true;
+        }
+
+        r = sd_event_source_set_priority(*s, priority);
+        if (r < 0)
+                return log_debug_errno(r, "sd-event: Failed to set priority for event source \"%s\": %m", strna((*s)->description ?: description));
+
+        if (description) {
+                r = sd_event_source_set_description(*s, description);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to set description for event source \"%s\": %m", description);
+        }
+
+        return created;
+}
+
+int event_source_disable(sd_event_source *s) {
+        if (!s)
+                return 0;
+
+        return sd_event_source_set_enabled(s, SD_EVENT_OFF);
+}
+
+int event_source_is_enabled(sd_event_source *s) {
+        if (!s)
+                return false;
+
+        return sd_event_source_get_enabled(s, NULL);
+}
diff --git a/src/core/systemd/src/libsystemd/sd-event/event-util.h b/src/core/systemd/src/libsystemd/sd-event/event-util.h
new file mode 100644
index 00000000..c8f97bc8
--- /dev/null
+++ b/src/core/systemd/src/libsystemd/sd-event/event-util.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+#include "sd-event.h"
+
+int event_reset_time(sd_event *e, sd_event_source **s,
+                     clockid_t clock, uint64_t usec, uint64_t accuracy,
+                     sd_event_time_handler_t callback, void *userdata,
+                     int64_t priority, const char *description, bool force_reset);
+int event_source_disable(sd_event_source *s);
+int event_source_is_enabled(sd_event_source *s);
diff --git a/src/core/systemd/src/libsystemd/sd-event/sd-event.c b/src/core/systemd/src/libsystemd/sd-event/sd-event.c
new file mode 100644
index 00000000..dda4be00
--- /dev/null
+++ b/src/core/systemd/src/libsystemd/sd-event/sd-event.c
@@ -0,0 +1,4329 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <sys/wait.h>
+
+#include "sd-daemon.h"
+#include "sd-event.h"
+#include "sd-id128.h"
+
+#include "alloc-util.h"
+#include "env-util.h"
+#include "event-source.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "hashmap.h"
+#include "list.h"
+#include "macro.h"
+#include "memory-util.h"
+#include "missing_syscall.h"
+#include "prioq.h"
+#include "process-util.h"
+#include "set.h"
+#include "signal-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strxcpyx.h"
+#include "time-util.h"
+
+#define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC)
+
+static bool EVENT_SOURCE_WATCH_PIDFD(sd_event_source *s) {
+        /* Returns true if this is a PID event source and can be implemented by watching EPOLLIN */
+        return s &&
+                s->type == SOURCE_CHILD &&
+                s->child.pidfd >= 0 &&
+                s->child.options == WEXITED;
+}
+
+static bool event_source_is_online(sd_event_source *s) {
+        assert(s);
+        return s->enabled != SD_EVENT_OFF && !s->ratelimited;
+}
+
+static bool event_source_is_offline(sd_event_source *s) {
+        assert(s);
+        return s->enabled == SD_EVENT_OFF || s->ratelimited;
+}
+
+static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = {
+        [SOURCE_IO] = "io",
+        [SOURCE_TIME_REALTIME] = "realtime",
+        [SOURCE_TIME_BOOTTIME] = "bootime",
+        [SOURCE_TIME_MONOTONIC] = "monotonic",
+        [SOURCE_TIME_REALTIME_ALARM] = "realtime-alarm",
+        [SOURCE_TIME_BOOTTIME_ALARM] = "boottime-alarm",
+        [SOURCE_SIGNAL] = "signal",
+        [SOURCE_CHILD] = "child",
+        [SOURCE_DEFER] = "defer",
+        [SOURCE_POST] = "post",
+        [SOURCE_EXIT] = "exit",
+        [SOURCE_WATCHDOG] = "watchdog",
+        [SOURCE_INOTIFY] = "inotify",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int);
+
+#define EVENT_SOURCE_IS_TIME(t)                 \
+        IN_SET((t),                             \
+               SOURCE_TIME_REALTIME,            \
+               SOURCE_TIME_BOOTTIME,            \
+               SOURCE_TIME_MONOTONIC,           \
+               SOURCE_TIME_REALTIME_ALARM,      \
+               SOURCE_TIME_BOOTTIME_ALARM)
+
+#define EVENT_SOURCE_CAN_RATE_LIMIT(t)          \
+        IN_SET((t),                             \
+               SOURCE_IO,                       \
+               SOURCE_TIME_REALTIME,            \
+               SOURCE_TIME_BOOTTIME,            \
+               SOURCE_TIME_MONOTONIC,           \
+               SOURCE_TIME_REALTIME_ALARM,      \
+               SOURCE_TIME_BOOTTIME_ALARM,      \
+               SOURCE_SIGNAL,                   \
+               SOURCE_DEFER,                    \
+               SOURCE_INOTIFY)
+
+struct sd_event {
+        unsigned n_ref;
+
+        int epoll_fd;
+        int watchdog_fd;
+
+        Prioq *pending;
+        Prioq *prepare;
+
+        /* timerfd_create() only supports these five clocks so far. We
+         * can add support for more clocks when the kernel learns to
+         * deal with them, too. */
+        struct clock_data realtime;
+        struct clock_data boottime;
+        struct clock_data monotonic;
+        struct clock_data realtime_alarm;
+        struct clock_data boottime_alarm;
+
+        usec_t perturb;
+
+        sd_event_source **signal_sources; /* indexed by signal number */
+        Hashmap *signal_data; /* indexed by priority */
+
+        Hashmap *child_sources;
+        unsigned n_online_child_sources;
+
+        Set *post_sources;
+
+        Prioq *exit;
+
+        Hashmap *inotify_data; /* indexed by priority */
+
+        /* A list of inode structures that still have an fd open, that we need to close before the next loop iteration */
+        LIST_HEAD(struct inode_data, inode_data_to_close);
+
+        /* A list of inotify objects that already have events buffered which aren't processed yet */
+        LIST_HEAD(struct inotify_data, inotify_data_buffered);
+
+        pid_t original_pid;
+
+        uint64_t iteration;
+        triple_timestamp timestamp;
+        int state;
+
+        bool exit_requested:1;
+        bool need_process_child:1;
+        bool watchdog:1;
+        bool profile_delays:1;
+
+        int exit_code;
+
+        pid_t tid;
+        sd_event **default_event_ptr;
+
+        usec_t watchdog_last, watchdog_period;
+
+        unsigned n_sources;
+
+        struct epoll_event *event_queue;
+        size_t event_queue_allocated;
+
+        LIST_HEAD(sd_event_source, sources);
+
+        usec_t last_run_usec, last_log_usec;
+        unsigned delays[sizeof(usec_t) * 8];
+};
+
+static thread_local sd_event *default_event = NULL;
+
+static void source_disconnect(sd_event_source *s);
+static void event_gc_inode_data(sd_event *e, struct inode_data *d);
+
+static sd_event *event_resolve(sd_event *e) {
+        return e == SD_EVENT_DEFAULT ? default_event : e;
+}
+
+static int pending_prioq_compare(const void *a, const void *b) {
+        const sd_event_source *x = a, *y = b;
+        int r;
+
+        assert(x->pending);
+        assert(y->pending);
+
+        /* Enabled ones first */
+        if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
+                return -1;
+        if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
+                return 1;
+
+        /* Non rate-limited ones first. */
+        r = CMP(!!x->ratelimited, !!y->ratelimited);
+        if (r != 0)
+                return r;
+
+        /* Lower priority values first */
+        r = CMP(x->priority, y->priority);
+        if (r != 0)
+                return r;
+
+        /* Older entries first */
+        return CMP(x->pending_iteration, y->pending_iteration);
+}
+
+static int prepare_prioq_compare(const void *a, const void *b) {
+        const sd_event_source *x = a, *y = b;
+        int r;
+
+        assert(x->prepare);
+        assert(y->prepare);
+
+        /* Enabled ones first */
+        if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
+                return -1;
+        if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
+                return 1;
+
+        /* Non rate-limited ones first. */
+        r = CMP(!!x->ratelimited, !!y->ratelimited);
+        if (r != 0)
+                return r;
+
+        /* Move most recently prepared ones last, so that we can stop
+         * preparing as soon as we hit one that has already been
+         * prepared in the current iteration */
+        r = CMP(x->prepare_iteration, y->prepare_iteration);
+        if (r != 0)
+                return r;
+
+        /* Lower priority values first */
+        return CMP(x->priority, y->priority);
+}
+
+static usec_t time_event_source_next(const sd_event_source *s) {
+        assert(s);
+
+        /* We have two kinds of event sources that have elapsation times associated with them: the actual
+         * time based ones and the ones for which a ratelimit can be in effect (where we want to be notified
+         * once the ratelimit time window ends). Let's return the next elapsing time depending on what we are
+         * looking at here. */
+
+        if (s->ratelimited) { /* If rate-limited the next elapsation is when the ratelimit time window ends */
+                assert(s->rate_limit.begin != 0);
+                assert(s->rate_limit.interval != 0);
+                return usec_add(s->rate_limit.begin, s->rate_limit.interval);
+        }
+
+        /* Otherwise this must be a time event source, if not ratelimited */
+        if (EVENT_SOURCE_IS_TIME(s->type))
+                return s->time.next;
+
+        return USEC_INFINITY;
+}
+
+static int earliest_time_prioq_compare(const void *a, const void *b) {
+        const sd_event_source *x = a, *y = b;
+
+        /* Enabled ones first */
+        if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
+                return -1;
+        if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
+                return 1;
+
+        /* Move the pending ones to the end */
+        if (!x->pending && y->pending)
+                return -1;
+        if (x->pending && !y->pending)
+                return 1;
+
+        /* Order by time */
+        return CMP(time_event_source_next(x), time_event_source_next(y));
+}
+
+static usec_t time_event_source_latest(const sd_event_source *s) {
+        assert(s);
+
+        if (s->ratelimited) { /* For ratelimited stuff the earliest and the latest time shall actually be the
+                               * same, as we should avoid adding additional inaccuracy on an inaccuracy time
+                               * window */
+                assert(s->rate_limit.begin != 0);
+                assert(s->rate_limit.interval != 0);
+                return usec_add(s->rate_limit.begin, s->rate_limit.interval);
+        }
+
+        /* Must be a time event source, if not ratelimited */
+        if (EVENT_SOURCE_IS_TIME(s->type))
+                return usec_add(s->time.next, s->time.accuracy);
+
+        return USEC_INFINITY;
+}
+
+static int latest_time_prioq_compare(const void *a, const void *b) {
+        const sd_event_source *x = a, *y = b;
+
+        /* Enabled ones first */
+        if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
+                return -1;
+        if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
+                return 1;
+
+        /* Move the pending ones to the end */
+        if (!x->pending && y->pending)
+                return -1;
+        if (x->pending && !y->pending)
+                return 1;
+
+        /* Order by time */
+        return CMP(time_event_source_latest(x), time_event_source_latest(y));
+}
+
+static int exit_prioq_compare(const void *a, const void *b) {
+        const sd_event_source *x = a, *y = b;
+
+        assert(x->type == SOURCE_EXIT);
+        assert(y->type == SOURCE_EXIT);
+
+        /* Enabled ones first */
+        if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
+                return -1;
+        if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
+                return 1;
+
+        /* Lower priority values first */
+        return CMP(x->priority, y->priority);
+}
+
+static void free_clock_data(struct clock_data *d) {
+        assert(d);
+        assert(d->wakeup == WAKEUP_CLOCK_DATA);
+
+        safe_close(d->fd);
+        prioq_free(d->earliest);
+        prioq_free(d->latest);
+}
+
+static sd_event *event_free(sd_event *e) {
+        sd_event_source *s;
+
+        assert(e);
+
+        while ((s = e->sources)) {
+                assert(s->floating);
+                source_disconnect(s);
+                sd_event_source_unref(s);
+        }
+
+        assert(e->n_sources == 0);
+
+        if (e->default_event_ptr)
+                *(e->default_event_ptr) = NULL;
+
+        safe_close(e->epoll_fd);
+        safe_close(e->watchdog_fd);
+
+        free_clock_data(&e->realtime);
+        free_clock_data(&e->boottime);
+        free_clock_data(&e->monotonic);
+        free_clock_data(&e->realtime_alarm);
+        free_clock_data(&e->boottime_alarm);
+
+        prioq_free(e->pending);
+        prioq_free(e->prepare);
+        prioq_free(e->exit);
+
+        free(e->signal_sources);
+        hashmap_free(e->signal_data);
+
+        hashmap_free(e->inotify_data);
+
+        hashmap_free(e->child_sources);
+        set_free(e->post_sources);
+
+        free(e->event_queue);
+
+        return mfree(e);
+}
+
+_public_ int sd_event_new(sd_event** ret) {
+        sd_event *e;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        e = new(sd_event, 1);
+        if (!e)
+                return -ENOMEM;
+
+        *e = (sd_event) {
+                .n_ref = 1,
+                .epoll_fd = -1,
+                .watchdog_fd = -1,
+                .realtime.wakeup = WAKEUP_CLOCK_DATA,
+                .realtime.fd = -1,
+                .realtime.next = USEC_INFINITY,
+                .boottime.wakeup = WAKEUP_CLOCK_DATA,
+                .boottime.fd = -1,
+                .boottime.next = USEC_INFINITY,
+                .monotonic.wakeup = WAKEUP_CLOCK_DATA,
+                .monotonic.fd = -1,
+                .monotonic.next = USEC_INFINITY,
+                .realtime_alarm.wakeup = WAKEUP_CLOCK_DATA,
+                .realtime_alarm.fd = -1,
+                .realtime_alarm.next = USEC_INFINITY,
+                .boottime_alarm.wakeup = WAKEUP_CLOCK_DATA,
+                .boottime_alarm.fd = -1,
+                .boottime_alarm.next = USEC_INFINITY,
+                .perturb = USEC_INFINITY,
+                .original_pid = getpid_cached(),
+        };
+
+        r = prioq_ensure_allocated(&e->pending, pending_prioq_compare);
+        if (r < 0)
+                goto fail;
+
+        e->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
+        if (e->epoll_fd < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        e->epoll_fd = fd_move_above_stdio(e->epoll_fd);
+
+        if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) {
+                log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 ... 2^63 us will be logged every 5s.");
+                e->profile_delays = true;
+        }
+
+        *ret = e;
+        return 0;
+
+fail:
+        event_free(e);
+        return r;
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event, sd_event, event_free);
+
+_public_ sd_event_source* sd_event_source_disable_unref(sd_event_source *s) {
+        if (s)
+                (void) sd_event_source_set_enabled(s, SD_EVENT_OFF);
+        return sd_event_source_unref(s);
+}
+
+static bool event_pid_changed(sd_event *e) {
+        assert(e);
+
+        /* We don't support people creating an event loop and keeping
+         * it around over a fork(). Let's complain. */
+
+        return e->original_pid != getpid_cached();
+}
+
+static void source_io_unregister(sd_event_source *s) {
+        assert(s);
+        assert(s->type == SOURCE_IO);
+
+        if (event_pid_changed(s->event))
+                return;
+
+        if (!s->io.registered)
+                return;
+
+        if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL) < 0)
+                log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m",
+                                strna(s->description), event_source_type_to_string(s->type));
+
+        s->io.registered = false;
+}
+
+static int source_io_register(
+                sd_event_source *s,
+                int enabled,
+                uint32_t events) {
+
+        assert(s);
+        assert(s->type == SOURCE_IO);
+        assert(enabled != SD_EVENT_OFF);
+
+        struct epoll_event ev = {
+                .events = events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0),
+                .data.ptr = s,
+        };
+
+        if (epoll_ctl(s->event->epoll_fd,
+                      s->io.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD,
+                      s->io.fd, &ev) < 0)
+                return -errno;
+
+        s->io.registered = true;
+
+        return 0;
+}
+
+static void source_child_pidfd_unregister(sd_event_source *s) {
+        assert(s);
+        assert(s->type == SOURCE_CHILD);
+
+        if (event_pid_changed(s->event))
+                return;
+
+        if (!s->child.registered)
+                return;
+
+        if (EVENT_SOURCE_WATCH_PIDFD(s))
+                if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->child.pidfd, NULL) < 0)
+                        log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m",
+                                        strna(s->description), event_source_type_to_string(s->type));
+
+        s->child.registered = false;
+}
+
+static int source_child_pidfd_register(sd_event_source *s, int enabled) {
+        assert(s);
+        assert(s->type == SOURCE_CHILD);
+        assert(enabled != SD_EVENT_OFF);
+
+        if (EVENT_SOURCE_WATCH_PIDFD(s)) {
+                struct epoll_event ev = {
+                        .events = EPOLLIN | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0),
+                        .data.ptr = s,
+                };
+
+                if (epoll_ctl(s->event->epoll_fd,
+                              s->child.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD,
+                              s->child.pidfd, &ev) < 0)
+                        return -errno;
+        }
+
+        s->child.registered = true;
+        return 0;
+}
+
+static clockid_t event_source_type_to_clock(EventSourceType t) {
+
+        switch (t) {
+
+        case SOURCE_TIME_REALTIME:
+                return CLOCK_REALTIME;
+
+        case SOURCE_TIME_BOOTTIME:
+                return CLOCK_BOOTTIME;
+
+        case SOURCE_TIME_MONOTONIC:
+                return CLOCK_MONOTONIC;
+
+        case SOURCE_TIME_REALTIME_ALARM:
+                return CLOCK_REALTIME_ALARM;
+
+        case SOURCE_TIME_BOOTTIME_ALARM:
+                return CLOCK_BOOTTIME_ALARM;
+
+        default:
+                return (clockid_t) -1;
+        }
+}
+
+static EventSourceType clock_to_event_source_type(clockid_t clock) {
+
+        switch (clock) {
+
+        case CLOCK_REALTIME:
+                return SOURCE_TIME_REALTIME;
+
+        case CLOCK_BOOTTIME:
+                return SOURCE_TIME_BOOTTIME;
+
+        case CLOCK_MONOTONIC:
+                return SOURCE_TIME_MONOTONIC;
+
+        case CLOCK_REALTIME_ALARM:
+                return SOURCE_TIME_REALTIME_ALARM;
+
+        case CLOCK_BOOTTIME_ALARM:
+                return SOURCE_TIME_BOOTTIME_ALARM;
+
+        default:
+                return _SOURCE_EVENT_SOURCE_TYPE_INVALID;
+        }
+}
+
+static struct clock_data* event_get_clock_data(sd_event *e, EventSourceType t) {
+        assert(e);
+
+        switch (t) {
+
+        case SOURCE_TIME_REALTIME:
+                return &e->realtime;
+
+        case SOURCE_TIME_BOOTTIME:
+                return &e->boottime;
+
+        case SOURCE_TIME_MONOTONIC:
+                return &e->monotonic;
+
+        case SOURCE_TIME_REALTIME_ALARM:
+                return &e->realtime_alarm;
+
+        case SOURCE_TIME_BOOTTIME_ALARM:
+                return &e->boottime_alarm;
+
+        default:
+                return NULL;
+        }
+}
+
+static void event_free_signal_data(sd_event *e, struct signal_data *d) {
+        assert(e);
+
+        if (!d)
+                return;
+
+        hashmap_remove(e->signal_data, &d->priority);
+        safe_close(d->fd);
+        free(d);
+}
+
+static int event_make_signal_data(
+                sd_event *e,
+                int sig,
+                struct signal_data **ret) {
+
+        struct signal_data *d;
+        bool added = false;
+        sigset_t ss_copy;
+        int64_t priority;
+        int r;
+
+        assert(e);
+
+        if (event_pid_changed(e))
+                return -ECHILD;
+
+        if (e->signal_sources && e->signal_sources[sig])
+                priority = e->signal_sources[sig]->priority;
+        else
+                priority = SD_EVENT_PRIORITY_NORMAL;
+
+        d = hashmap_get(e->signal_data, &priority);
+        if (d) {
+                if (sigismember(&d->sigset, sig) > 0) {
+                        if (ret)
+                                *ret = d;
+                        return 0;
+                }
+        } else {
+                r = hashmap_ensure_allocated(&e->signal_data, &uint64_hash_ops);
+                if (r < 0)
+                        return r;
+
+                d = new(struct signal_data, 1);
+                if (!d)
+                        return -ENOMEM;
+
+                *d = (struct signal_data) {
+                        .wakeup = WAKEUP_SIGNAL_DATA,
+                        .fd = -1,
+                        .priority = priority,
+                };
+
+                r = hashmap_put(e->signal_data, &d->priority, d);
+                if (r < 0) {
+                        free(d);
+                        return r;
+                }
+
+                added = true;
+        }
+
+        ss_copy = d->sigset;
+        assert_se(sigaddset(&ss_copy, sig) >= 0);
+
+        r = signalfd(d->fd, &ss_copy, SFD_NONBLOCK|SFD_CLOEXEC);
+        if (r < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        d->sigset = ss_copy;
+
+        if (d->fd >= 0) {
+                if (ret)
+                        *ret = d;
+                return 0;
+        }
+
+        d->fd = fd_move_above_stdio(r);
+
+        struct epoll_event ev = {
+                .events = EPOLLIN,
+                .data.ptr = d,
+        };
+
+        if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        if (ret)
+                *ret = d;
+
+        return 0;
+
+fail:
+        if (added)
+                event_free_signal_data(e, d);
+
+        return r;
+}
+
+static void event_unmask_signal_data(sd_event *e, struct signal_data *d, int sig) {
+        assert(e);
+        assert(d);
+
+        /* Turns off the specified signal in the signal data
+         * object. If the signal mask of the object becomes empty that
+         * way removes it. */
+
+        if (sigismember(&d->sigset, sig) == 0)
+                return;
+
+        assert_se(sigdelset(&d->sigset, sig) >= 0);
+
+        if (sigisemptyset(&d->sigset)) {
+                /* If all the mask is all-zero we can get rid of the structure */
+                event_free_signal_data(e, d);
+                return;
+        }
+
+        assert(d->fd >= 0);
+
+        if (signalfd(d->fd, &d->sigset, SFD_NONBLOCK|SFD_CLOEXEC) < 0)
+                log_debug_errno(errno, "Failed to unset signal bit, ignoring: %m");
+}
+
+static void event_gc_signal_data(sd_event *e, const int64_t *priority, int sig) {
+        struct signal_data *d;
+        static const int64_t zero_priority = 0;
+
+        assert(e);
+
+        /* Rechecks if the specified signal is still something we are interested in. If not, we'll unmask it,
+         * and possibly drop the signalfd for it. */
+
+        if (sig == SIGCHLD &&
+            e->n_online_child_sources > 0)
+                return;
+
+        if (e->signal_sources &&
+            e->signal_sources[sig] &&
+            event_source_is_online(e->signal_sources[sig]))
+                return;
+
+        /*
+         * The specified signal might be enabled in three different queues:
+         *
+         * 1) the one that belongs to the priority passed (if it is non-NULL)
+         * 2) the one that belongs to the priority of the event source of the signal (if there is one)
+         * 3) the 0 priority (to cover the SIGCHLD case)
+         *
+         * Hence, let's remove it from all three here.
+         */
+
+        if (priority) {
+                d = hashmap_get(e->signal_data, priority);
+                if (d)
+                        event_unmask_signal_data(e, d, sig);
+        }
+
+        if (e->signal_sources && e->signal_sources[sig]) {
+                d = hashmap_get(e->signal_data, &e->signal_sources[sig]->priority);
+                if (d)
+                        event_unmask_signal_data(e, d, sig);
+        }
+
+        d = hashmap_get(e->signal_data, &zero_priority);
+        if (d)
+                event_unmask_signal_data(e, d, sig);
+}
+
+static void event_source_pp_prioq_reshuffle(sd_event_source *s) {
+        assert(s);
+
+        /* Reshuffles the pending + prepare prioqs. Called whenever the dispatch order changes, i.e. when
+         * they are enabled/disabled or marked pending and such. */
+
+        if (s->pending)
+                prioq_reshuffle(s->event->pending, s, &s->pending_index);
+
+        if (s->prepare)
+                prioq_reshuffle(s->event->prepare, s, &s->prepare_index);
+}
+
+static void event_source_time_prioq_reshuffle(sd_event_source *s) {
+        struct clock_data *d;
+
+        assert(s);
+
+        /* Called whenever the event source's timer ordering properties changed, i.e. time, accuracy,
+         * pending, enable state. Makes sure the two prioq's are ordered properly again. */
+
+        if (s->ratelimited)
+                d = &s->event->monotonic;
+        else {
+                assert(EVENT_SOURCE_IS_TIME(s->type));
+                assert_se(d = event_get_clock_data(s->event, s->type));
+        }
+
+        prioq_reshuffle(d->earliest, s, &s->earliest_index);
+        prioq_reshuffle(d->latest, s, &s->latest_index);
+        d->needs_rearm = true;
+}
+
+static void event_source_time_prioq_remove(
+                sd_event_source *s,
+                struct clock_data *d) {
+
+        assert(s);
+        assert(d);
+
+        prioq_remove(d->earliest, s, &s->earliest_index);
+        prioq_remove(d->latest, s, &s->latest_index);
+        s->earliest_index = s->latest_index = PRIOQ_IDX_NULL;
+        d->needs_rearm = true;
+}
+
+static void source_disconnect(sd_event_source *s) {
+        sd_event *event;
+
+        assert(s);
+
+        if (!s->event)
+                return;
+
+        assert(s->event->n_sources > 0);
+
+        switch (s->type) {
+
+        case SOURCE_IO:
+                if (s->io.fd >= 0)
+                        source_io_unregister(s);
+
+                break;
+
+        case SOURCE_TIME_REALTIME:
+        case SOURCE_TIME_BOOTTIME:
+        case SOURCE_TIME_MONOTONIC:
+        case SOURCE_TIME_REALTIME_ALARM:
+        case SOURCE_TIME_BOOTTIME_ALARM:
+                /* Only remove this event source from the time event source here if it is not ratelimited. If
+                 * it is ratelimited, we'll remove it below, separately. Why? Because the clock used might
+                 * differ: ratelimiting always uses CLOCK_MONOTONIC, but timer events might use any clock */
+
+                if (!s->ratelimited) {
+                        struct clock_data *d;
+                        assert_se(d = event_get_clock_data(s->event, s->type));
+                        event_source_time_prioq_remove(s, d);
+                }
+
+                break;
+
+        case SOURCE_SIGNAL:
+                if (s->signal.sig > 0) {
+
+                        if (s->event->signal_sources)
+                                s->event->signal_sources[s->signal.sig] = NULL;
+
+                        event_gc_signal_data(s->event, &s->priority, s->signal.sig);
+                }
+
+                break;
+
+        case SOURCE_CHILD:
+                if (s->child.pid > 0) {
+                        if (event_source_is_online(s)) {
+                                assert(s->event->n_online_child_sources > 0);
+                                s->event->n_online_child_sources--;
+                        }
+
+                        (void) hashmap_remove(s->event->child_sources, PID_TO_PTR(s->child.pid));
+                }
+
+                if (EVENT_SOURCE_WATCH_PIDFD(s))
+                        source_child_pidfd_unregister(s);
+                else
+                        event_gc_signal_data(s->event, &s->priority, SIGCHLD);
+
+                break;
+
+        case SOURCE_DEFER:
+                /* nothing */
+                break;
+
+        case SOURCE_POST:
+                set_remove(s->event->post_sources, s);
+                break;
+
+        case SOURCE_EXIT:
+                prioq_remove(s->event->exit, s, &s->exit.prioq_index);
+                break;
+
+        case SOURCE_INOTIFY: {
+                struct inode_data *inode_data;
+
+                inode_data = s->inotify.inode_data;
+                if (inode_data) {
+                        struct inotify_data *inotify_data;
+                        assert_se(inotify_data = inode_data->inotify_data);
+
+                        /* Detach this event source from the inode object */
+                        LIST_REMOVE(inotify.by_inode_data, inode_data->event_sources, s);
+                        s->inotify.inode_data = NULL;
+
+                        if (s->pending) {
+                                assert(inotify_data->n_pending > 0);
+                                inotify_data->n_pending--;
+                        }
+
+                        /* Note that we don't reduce the inotify mask for the watch descriptor here if the inode is
+                         * continued to being watched. That's because inotify doesn't really have an API for that: we
+                         * can only change watch masks with access to the original inode either by fd or by path. But
+                         * paths aren't stable, and keeping an O_PATH fd open all the time would mean wasting an fd
+                         * continuously and keeping the mount busy which we can't really do. We could reconstruct the
+                         * original inode from /proc/self/fdinfo/$INOTIFY_FD (as all watch descriptors are listed
+                         * there), but given the need for open_by_handle_at() which is privileged and not universally
+                         * available this would be quite an incomplete solution. Hence we go the other way, leave the
+                         * mask set, even if it is not minimized now, and ignore all events we aren't interested in
+                         * anymore after reception. Yes, this sucks, but … Linux … */
+
+                        /* Maybe release the inode data (and its inotify) */
+                        event_gc_inode_data(s->event, inode_data);
+                }
+
+                break;
+        }
+
+        default:
+                assert_not_reached("Wut? I shouldn't exist.");
+        }
+
+        if (s->pending)
+                prioq_remove(s->event->pending, s, &s->pending_index);
+
+        if (s->prepare)
+                prioq_remove(s->event->prepare, s, &s->prepare_index);
+
+        if (s->ratelimited)
+                event_source_time_prioq_remove(s, &s->event->monotonic);
+
+        event = TAKE_PTR(s->event);
+        LIST_REMOVE(sources, event->sources, s);
+        event->n_sources--;
+
+        /* Note that we don't invalidate the type here, since we still need it in order to close the fd or
+         * pidfd associated with this event source, which we'll do only on source_free(). */
+
+        if (!s->floating)
+                sd_event_unref(event);
+}
+
+static void source_free(sd_event_source *s) {
+        assert(s);
+
+        source_disconnect(s);
+
+        if (s->type == SOURCE_IO && s->io.owned)
+                s->io.fd = safe_close(s->io.fd);
+
+        if (s->type == SOURCE_CHILD) {
+                /* Eventually the kernel will do this automatically for us, but for now let's emulate this (unreliably) in userspace. */
+
+                if (s->child.process_owned) {
+
+                        if (!s->child.exited) {
+                                bool sent = false;
+
+                                if (s->child.pidfd >= 0) {
+                                        if (pidfd_send_signal(s->child.pidfd, SIGKILL, NULL, 0) < 0) {
+                                                if (errno == ESRCH) /* Already dead */
+                                                        sent = true;
+                                                else if (!ERRNO_IS_NOT_SUPPORTED(errno))
+                                                        log_debug_errno(errno, "Failed to kill process " PID_FMT " via pidfd_send_signal(), re-trying via kill(): %m",
+                                                                        s->child.pid);
+                                        } else
+                                                sent = true;
+                                }
+
+                                if (!sent)
+                                        if (kill(s->child.pid, SIGKILL) < 0)
+                                                if (errno != ESRCH) /* Already dead */
+                                                        log_debug_errno(errno, "Failed to kill process " PID_FMT " via kill(), ignoring: %m",
+                                                                        s->child.pid);
+                        }
+
+                        if (!s->child.waited) {
+                                siginfo_t si = {};
+
+                                /* Reap the child if we can */
+                                (void) waitid(P_PID, s->child.pid, &si, WEXITED);
+                        }
+                }
+
+                if (s->child.pidfd_owned)
+                        s->child.pidfd = safe_close(s->child.pidfd);
+        }
+
+        if (s->destroy_callback)
+                s->destroy_callback(s->userdata);
+
+        free(s->description);
+        free(s);
+}
+DEFINE_TRIVIAL_CLEANUP_FUNC(sd_event_source*, source_free);
+
+static int source_set_pending(sd_event_source *s, bool b) {
+        int r;
+
+        assert(s);
+        assert(s->type != SOURCE_EXIT);
+
+        if (s->pending == b)
+                return 0;
+
+        s->pending = b;
+
+        if (b) {
+                s->pending_iteration = s->event->iteration;
+
+                r = prioq_put(s->event->pending, s, &s->pending_index);
+                if (r < 0) {
+                        s->pending = false;
+                        return r;
+                }
+        } else
+                assert_se(prioq_remove(s->event->pending, s, &s->pending_index));
+
+        if (EVENT_SOURCE_IS_TIME(s->type))
+                event_source_time_prioq_reshuffle(s);
+
+        if (s->type == SOURCE_SIGNAL && !b) {
+                struct signal_data *d;
+
+                d = hashmap_get(s->event->signal_data, &s->priority);
+                if (d && d->current == s)
+                        d->current = NULL;
+        }
+
+        if (s->type == SOURCE_INOTIFY) {
+
+                assert(s->inotify.inode_data);
+                assert(s->inotify.inode_data->inotify_data);
+
+                if (b)
+                        s->inotify.inode_data->inotify_data->n_pending ++;
+                else {
+                        assert(s->inotify.inode_data->inotify_data->n_pending > 0);
+                        s->inotify.inode_data->inotify_data->n_pending --;
+                }
+        }
+
+        return 0;
+}
+
+static sd_event_source *source_new(sd_event *e, bool floating, EventSourceType type) {
+        sd_event_source *s;
+
+        assert(e);
+
+        s = new(sd_event_source, 1);
+        if (!s)
+                return NULL;
+
+        *s = (struct sd_event_source) {
+                .n_ref = 1,
+                .event = e,
+                .floating = floating,
+                .type = type,
+                .pending_index = PRIOQ_IDX_NULL,
+                .prepare_index = PRIOQ_IDX_NULL,
+        };
+
+        if (!floating)
+                sd_event_ref(e);
+
+        LIST_PREPEND(sources, e->sources, s);
+        e->n_sources++;
+
+        return s;
+}
+
+static int io_exit_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        assert(s);
+
+        return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
+}
+
+_public_ int sd_event_add_io(
+                sd_event *e,
+                sd_event_source **ret,
+                int fd,
+                uint32_t events,
+                sd_event_io_handler_t callback,
+                void *userdata) {
+
+        _cleanup_(source_freep) sd_event_source *s = NULL;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(fd >= 0, -EBADF);
+        assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (!callback)
+                callback = io_exit_callback;
+
+        s = source_new(e, !ret, SOURCE_IO);
+        if (!s)
+                return -ENOMEM;
+
+        s->wakeup = WAKEUP_EVENT_SOURCE;
+        s->io.fd = fd;
+        s->io.events = events;
+        s->io.callback = callback;
+        s->userdata = userdata;
+        s->enabled = SD_EVENT_ON;
+
+        r = source_io_register(s, s->enabled, events);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = s;
+        TAKE_PTR(s);
+
+        return 0;
+}
+
+static void initialize_perturb(sd_event *e) {
+        sd_id128_t bootid = {};
+
+        /* When we sleep for longer, we try to realign the wakeup to
+           the same time within each minute/second/250ms, so that
+           events all across the system can be coalesced into a single
+           CPU wakeup. However, let's take some system-specific
+           randomness for this value, so that in a network of systems
+           with synced clocks timer events are distributed a
+           bit. Here, we calculate a perturbation usec offset from the
+           boot ID. */
+
+        if (_likely_(e->perturb != USEC_INFINITY))
+                return;
+
+        if (sd_id128_get_boot(&bootid) >= 0)
+                e->perturb = (bootid.qwords[0] ^ bootid.qwords[1]) % USEC_PER_MINUTE;
+}
+
+static int event_setup_timer_fd(
+                sd_event *e,
+                struct clock_data *d,
+                clockid_t clock) {
+
+        assert(e);
+        assert(d);
+
+        if (_likely_(d->fd >= 0))
+                return 0;
+
+        _cleanup_close_ int fd = -1;
+
+        fd = timerfd_create(clock, TFD_NONBLOCK|TFD_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        fd = fd_move_above_stdio(fd);
+
+        struct epoll_event ev = {
+                .events = EPOLLIN,
+                .data.ptr = d,
+        };
+
+        if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
+                return -errno;
+
+        d->fd = TAKE_FD(fd);
+        return 0;
+}
+
+static int time_exit_callback(sd_event_source *s, uint64_t usec, void *userdata) {
+        assert(s);
+
+        return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
+}
+
+static int setup_clock_data(sd_event *e, struct clock_data *d, clockid_t clock) {
+        int r;
+
+        assert(d);
+
+        if (d->fd < 0) {
+                r = event_setup_timer_fd(e, d, clock);
+                if (r < 0)
+                        return r;
+        }
+
+        r = prioq_ensure_allocated(&d->earliest, earliest_time_prioq_compare);
+        if (r < 0)
+                return r;
+
+        r = prioq_ensure_allocated(&d->latest, latest_time_prioq_compare);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int event_source_time_prioq_put(
+                sd_event_source *s,
+                struct clock_data *d) {
+
+        int r;
+
+        assert(s);
+        assert(d);
+
+        r = prioq_put(d->earliest, s, &s->earliest_index);
+        if (r < 0)
+                return r;
+
+        r = prioq_put(d->latest, s, &s->latest_index);
+        if (r < 0) {
+                assert_se(prioq_remove(d->earliest, s, &s->earliest_index) > 0);
+                s->earliest_index = PRIOQ_IDX_NULL;
+                return r;
+        }
+
+        d->needs_rearm = true;
+        return 0;
+}
+
+_public_ int sd_event_add_time(
+                sd_event *e,
+                sd_event_source **ret,
+                clockid_t clock,
+                uint64_t usec,
+                uint64_t accuracy,
+                sd_event_time_handler_t callback,
+                void *userdata) {
+
+        EventSourceType type;
+        _cleanup_(source_freep) sd_event_source *s = NULL;
+        struct clock_data *d;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(accuracy != (uint64_t) -1, -EINVAL);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (!clock_supported(clock)) /* Checks whether the kernel supports the clock */
+                return -EOPNOTSUPP;
+
+        type = clock_to_event_source_type(clock); /* checks whether sd-event supports this clock */
+        if (type < 0)
+                return -EOPNOTSUPP;
+
+        if (!callback)
+                callback = time_exit_callback;
+
+        assert_se(d = event_get_clock_data(e, type));
+
+        r = setup_clock_data(e, d, clock);
+        if (r < 0)
+                return r;
+
+        s = source_new(e, !ret, type);
+        if (!s)
+                return -ENOMEM;
+
+        s->time.next = usec;
+        s->time.accuracy = accuracy == 0 ? DEFAULT_ACCURACY_USEC : accuracy;
+        s->time.callback = callback;
+        s->earliest_index = s->latest_index = PRIOQ_IDX_NULL;
+        s->userdata = userdata;
+        s->enabled = SD_EVENT_ONESHOT;
+
+        r = event_source_time_prioq_put(s, d);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = s;
+        TAKE_PTR(s);
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+_public_ int sd_event_add_time_relative(
+                sd_event *e,
+                sd_event_source **ret,
+                clockid_t clock,
+                uint64_t usec,
+                uint64_t accuracy,
+                sd_event_time_handler_t callback,
+                void *userdata) {
+
+        usec_t t;
+        int r;
+
+        /* Same as sd_event_add_time() but operates relative to the event loop's current point in time, and
+         * checks for overflow. */
+
+        r = sd_event_now(e, clock, &t);
+        if (r < 0)
+                return r;
+
+        if (usec >= USEC_INFINITY - t)
+                return -EOVERFLOW;
+
+        return sd_event_add_time(e, ret, clock, t + usec, accuracy, callback, userdata);
+}
+
+static int signal_exit_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
+        assert(s);
+
+        return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
+}
+
+_public_ int sd_event_add_signal(
+                sd_event *e,
+                sd_event_source **ret,
+                int sig,
+                sd_event_signal_handler_t callback,
+                void *userdata) {
+
+        _cleanup_(source_freep) sd_event_source *s = NULL;
+        struct signal_data *d;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(SIGNAL_VALID(sig), -EINVAL);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (!callback)
+                callback = signal_exit_callback;
+
+        r = signal_is_blocked(sig);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EBUSY;
+
+        if (!e->signal_sources) {
+                e->signal_sources = new0(sd_event_source*, _NSIG);
+                if (!e->signal_sources)
+                        return -ENOMEM;
+        } else if (e->signal_sources[sig])
+                return -EBUSY;
+
+        s = source_new(e, !ret, SOURCE_SIGNAL);
+        if (!s)
+                return -ENOMEM;
+
+        s->signal.sig = sig;
+        s->signal.callback = callback;
+        s->userdata = userdata;
+        s->enabled = SD_EVENT_ON;
+
+        e->signal_sources[sig] = s;
+
+        r = event_make_signal_data(e, sig, &d);
+        if (r < 0)
+                return r;
+
+        /* Use the signal name as description for the event source by default */
+        (void) sd_event_source_set_description(s, signal_to_string(sig));
+
+        if (ret)
+                *ret = s;
+        TAKE_PTR(s);
+
+        return 0;
+}
+
+static int child_exit_callback(sd_event_source *s, const siginfo_t *si, void *userdata) {
+        assert(s);
+
+        return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
+}
+
+static bool shall_use_pidfd(void) {
+        /* Mostly relevant for debugging, i.e. this is used in test-event.c to test the event loop once with and once without pidfd */
+        return getenv_bool_secure("SYSTEMD_PIDFD") != 0;
+}
+
+_public_ int sd_event_add_child(
+                sd_event *e,
+                sd_event_source **ret,
+                pid_t pid,
+                int options,
+                sd_event_child_handler_t callback,
+                void *userdata) {
+
+        _cleanup_(source_freep) sd_event_source *s = NULL;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(pid > 1, -EINVAL);
+        assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
+        assert_return(options != 0, -EINVAL);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (!callback)
+                callback = child_exit_callback;
+
+        if (e->n_online_child_sources == 0) {
+                /* Caller must block SIGCHLD before using us to watch children, even if pidfd is available,
+                 * for compatibility with pre-pidfd and because we don't want the reap the child processes
+                 * ourselves, i.e. call waitid(), and don't want Linux' default internal logic for that to
+                 * take effect.
+                 *
+                 * (As an optimization we only do this check on the first child event source created.) */
+                r = signal_is_blocked(SIGCHLD);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EBUSY;
+        }
+
+        r = hashmap_ensure_allocated(&e->child_sources, NULL);
+        if (r < 0)
+                return r;
+
+        if (hashmap_contains(e->child_sources, PID_TO_PTR(pid)))
+                return -EBUSY;
+
+        s = source_new(e, !ret, SOURCE_CHILD);
+        if (!s)
+                return -ENOMEM;
+
+        s->wakeup = WAKEUP_EVENT_SOURCE;
+        s->child.pid = pid;
+        s->child.options = options;
+        s->child.callback = callback;
+        s->userdata = userdata;
+        s->enabled = SD_EVENT_ONESHOT;
+
+        /* We always take a pidfd here if we can, even if we wait for anything else than WEXITED, so that we
+         * pin the PID, and make regular waitid() handling race-free. */
+
+        if (shall_use_pidfd()) {
+                s->child.pidfd = pidfd_open(s->child.pid, 0);
+                if (s->child.pidfd < 0) {
+                        /* Propagate errors unless the syscall is not supported or blocked */
+                        if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
+                                return -errno;
+                } else
+                        s->child.pidfd_owned = true; /* If we allocate the pidfd we own it by default */
+        } else
+                s->child.pidfd = -1;
+
+        r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s);
+        if (r < 0)
+                return r;
+
+        if (EVENT_SOURCE_WATCH_PIDFD(s)) {
+                /* We have a pidfd and we only want to watch for exit */
+                r = source_child_pidfd_register(s, s->enabled);
+                if (r < 0)
+                        return r;
+
+        } else {
+                /* We have no pidfd or we shall wait for some other event than WEXITED */
+                r = event_make_signal_data(e, SIGCHLD, NULL);
+                if (r < 0)
+                        return r;
+
+                e->need_process_child = true;
+        }
+
+        e->n_online_child_sources++;
+
+        if (ret)
+                *ret = s;
+        TAKE_PTR(s);
+        return 0;
+}
+
+_public_ int sd_event_add_child_pidfd(
+                sd_event *e,
+                sd_event_source **ret,
+                int pidfd,
+                int options,
+                sd_event_child_handler_t callback,
+                void *userdata) {
+
+
+        _cleanup_(source_freep) sd_event_source *s = NULL;
+        pid_t pid;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(pidfd >= 0, -EBADF);
+        assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
+        assert_return(options != 0, -EINVAL);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (!callback)
+                callback = child_exit_callback;
+
+        if (e->n_online_child_sources == 0) {
+                r = signal_is_blocked(SIGCHLD);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EBUSY;
+        }
+
+        r = hashmap_ensure_allocated(&e->child_sources, NULL);
+        if (r < 0)
+                return r;
+
+        r = pidfd_get_pid(pidfd, &pid);
+        if (r < 0)
+                return r;
+
+        if (hashmap_contains(e->child_sources, PID_TO_PTR(pid)))
+                return -EBUSY;
+
+        s = source_new(e, !ret, SOURCE_CHILD);
+        if (!s)
+                return -ENOMEM;
+
+        s->wakeup = WAKEUP_EVENT_SOURCE;
+        s->child.pidfd = pidfd;
+        s->child.pid = pid;
+        s->child.options = options;
+        s->child.callback = callback;
+        s->child.pidfd_owned = false; /* If we got the pidfd passed in we don't own it by default (similar to the IO fd case) */
+        s->userdata = userdata;
+        s->enabled = SD_EVENT_ONESHOT;
+
+        r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s);
+        if (r < 0)
+                return r;
+
+        if (EVENT_SOURCE_WATCH_PIDFD(s)) {
+                /* We only want to watch for WEXITED */
+                r = source_child_pidfd_register(s, s->enabled);
+                if (r < 0)
+                        return r;
+        } else {
+                /* We shall wait for some other event than WEXITED */
+                r = event_make_signal_data(e, SIGCHLD, NULL);
+                if (r < 0)
+                        return r;
+
+                e->need_process_child = true;
+        }
+
+        e->n_online_child_sources++;
+
+        if (ret)
+                *ret = s;
+        TAKE_PTR(s);
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+static int generic_exit_callback(sd_event_source *s, void *userdata) {
+        assert(s);
+
+        return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
+}
+
+_public_ int sd_event_add_defer(
+                sd_event *e,
+                sd_event_source **ret,
+                sd_event_handler_t callback,
+                void *userdata) {
+
+        _cleanup_(source_freep) sd_event_source *s = NULL;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (!callback)
+                callback = generic_exit_callback;
+
+        s = source_new(e, !ret, SOURCE_DEFER);
+        if (!s)
+                return -ENOMEM;
+
+        s->defer.callback = callback;
+        s->userdata = userdata;
+        s->enabled = SD_EVENT_ONESHOT;
+
+        r = source_set_pending(s, true);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = s;
+        TAKE_PTR(s);
+
+        return 0;
+}
+
+_public_ int sd_event_add_post(
+                sd_event *e,
+                sd_event_source **ret,
+                sd_event_handler_t callback,
+                void *userdata) {
+
+        _cleanup_(source_freep) sd_event_source *s = NULL;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (!callback)
+                callback = generic_exit_callback;
+
+        s = source_new(e, !ret, SOURCE_POST);
+        if (!s)
+                return -ENOMEM;
+
+        s->post.callback = callback;
+        s->userdata = userdata;
+        s->enabled = SD_EVENT_ON;
+
+        r = set_ensure_put(&e->post_sources, NULL, s);
+        if (r < 0)
+                return r;
+        assert(r > 0);
+
+        if (ret)
+                *ret = s;
+        TAKE_PTR(s);
+
+        return 0;
+}
+
+_public_ int sd_event_add_exit(
+                sd_event *e,
+                sd_event_source **ret,
+                sd_event_handler_t callback,
+                void *userdata) {
+
+        _cleanup_(source_freep) sd_event_source *s = NULL;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(callback, -EINVAL);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        r = prioq_ensure_allocated(&e->exit, exit_prioq_compare);
+        if (r < 0)
+                return r;
+
+        s = source_new(e, !ret, SOURCE_EXIT);
+        if (!s)
+                return -ENOMEM;
+
+        s->exit.callback = callback;
+        s->userdata = userdata;
+        s->exit.prioq_index = PRIOQ_IDX_NULL;
+        s->enabled = SD_EVENT_ONESHOT;
+
+        r = prioq_put(s->event->exit, s, &s->exit.prioq_index);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = s;
+        TAKE_PTR(s);
+
+        return 0;
+}
+
+static void event_free_inotify_data(sd_event *e, struct inotify_data *d) {
+        assert(e);
+
+        if (!d)
+                return;
+
+        assert(hashmap_isempty(d->inodes));
+        assert(hashmap_isempty(d->wd));
+
+        if (d->buffer_filled > 0)
+                LIST_REMOVE(buffered, e->inotify_data_buffered, d);
+
+        hashmap_free(d->inodes);
+        hashmap_free(d->wd);
+
+        assert_se(hashmap_remove(e->inotify_data, &d->priority) == d);
+
+        if (d->fd >= 0) {
+                if (epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, d->fd, NULL) < 0)
+                        log_debug_errno(errno, "Failed to remove inotify fd from epoll, ignoring: %m");
+
+                safe_close(d->fd);
+        }
+        free(d);
+}
+
+static int event_make_inotify_data(
+                sd_event *e,
+                int64_t priority,
+                struct inotify_data **ret) {
+
+        _cleanup_close_ int fd = -1;
+        struct inotify_data *d;
+        int r;
+
+        assert(e);
+
+        d = hashmap_get(e->inotify_data, &priority);
+        if (d) {
+                if (ret)
+                        *ret = d;
+                return 0;
+        }
+
+        fd = inotify_init1(IN_NONBLOCK|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        fd = fd_move_above_stdio(fd);
+
+        r = hashmap_ensure_allocated(&e->inotify_data, &uint64_hash_ops);
+        if (r < 0)
+                return r;
+
+        d = new(struct inotify_data, 1);
+        if (!d)
+                return -ENOMEM;
+
+        *d = (struct inotify_data) {
+                .wakeup = WAKEUP_INOTIFY_DATA,
+                .fd = TAKE_FD(fd),
+                .priority = priority,
+        };
+
+        r = hashmap_put(e->inotify_data, &d->priority, d);
+        if (r < 0) {
+                d->fd = safe_close(d->fd);
+                free(d);
+                return r;
+        }
+
+        struct epoll_event ev = {
+                .events = EPOLLIN,
+                .data.ptr = d,
+        };
+
+        if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev) < 0) {
+                r = -errno;
+                d->fd = safe_close(d->fd); /* let's close this ourselves, as event_free_inotify_data() would otherwise
+                                            * remove the fd from the epoll first, which we don't want as we couldn't
+                                            * add it in the first place. */
+                event_free_inotify_data(e, d);
+                return r;
+        }
+
+        if (ret)
+                *ret = d;
+
+        return 1;
+}
+
+static int inode_data_compare(const struct inode_data *x, const struct inode_data *y) {
+        int r;
+
+        assert(x);
+        assert(y);
+
+        r = CMP(x->dev, y->dev);
+        if (r != 0)
+                return r;
+
+        return CMP(x->ino, y->ino);
+}
+
+static void inode_data_hash_func(const struct inode_data *d, struct siphash *state) {
+        assert(d);
+
+        siphash24_compress(&d->dev, sizeof(d->dev), state);
+        siphash24_compress(&d->ino, sizeof(d->ino), state);
+}
+
+DEFINE_PRIVATE_HASH_OPS(inode_data_hash_ops, struct inode_data, inode_data_hash_func, inode_data_compare);
+
+static void event_free_inode_data(
+                sd_event *e,
+                struct inode_data *d) {
+
+        assert(e);
+
+        if (!d)
+                return;
+
+        assert(!d->event_sources);
+
+        if (d->fd >= 0) {
+                LIST_REMOVE(to_close, e->inode_data_to_close, d);
+                safe_close(d->fd);
+        }
+
+        if (d->inotify_data) {
+
+                if (d->wd >= 0) {
+                        if (d->inotify_data->fd >= 0) {
+                                /* So here's a problem. At the time this runs the watch descriptor might already be
+                                 * invalidated, because an IN_IGNORED event might be queued right the moment we enter
+                                 * the syscall. Hence, whenever we get EINVAL, ignore it entirely, since it's a very
+                                 * likely case to happen. */
+
+                                if (inotify_rm_watch(d->inotify_data->fd, d->wd) < 0 && errno != EINVAL)
+                                        log_debug_errno(errno, "Failed to remove watch descriptor %i from inotify, ignoring: %m", d->wd);
+                        }
+
+                        assert_se(hashmap_remove(d->inotify_data->wd, INT_TO_PTR(d->wd)) == d);
+                }
+
+                assert_se(hashmap_remove(d->inotify_data->inodes, d) == d);
+        }
+
+        free(d);
+}
+
+static void event_gc_inode_data(
+                sd_event *e,
+                struct inode_data *d) {
+
+        struct inotify_data *inotify_data;
+
+        assert(e);
+
+        if (!d)
+                return;
+
+        if (d->event_sources)
+                return;
+
+        inotify_data = d->inotify_data;
+        event_free_inode_data(e, d);
+
+        if (inotify_data && hashmap_isempty(inotify_data->inodes))
+                event_free_inotify_data(e, inotify_data);
+}
+
+static int event_make_inode_data(
+                sd_event *e,
+                struct inotify_data *inotify_data,
+                dev_t dev,
+                ino_t ino,
+                struct inode_data **ret) {
+
+        struct inode_data *d, key;
+        int r;
+
+        assert(e);
+        assert(inotify_data);
+
+        key = (struct inode_data) {
+                .ino = ino,
+                .dev = dev,
+        };
+
+        d = hashmap_get(inotify_data->inodes, &key);
+        if (d) {
+                if (ret)
+                        *ret = d;
+
+                return 0;
+        }
+
+        r = hashmap_ensure_allocated(&inotify_data->inodes, &inode_data_hash_ops);
+        if (r < 0)
+                return r;
+
+        d = new(struct inode_data, 1);
+        if (!d)
+                return -ENOMEM;
+
+        *d = (struct inode_data) {
+                .dev = dev,
+                .ino = ino,
+                .wd = -1,
+                .fd = -1,
+                .inotify_data = inotify_data,
+        };
+
+        r = hashmap_put(inotify_data->inodes, d, d);
+        if (r < 0) {
+                free(d);
+                return r;
+        }
+
+        if (ret)
+                *ret = d;
+
+        return 1;
+}
+
+static uint32_t inode_data_determine_mask(struct inode_data *d) {
+        bool excl_unlink = true;
+        uint32_t combined = 0;
+        sd_event_source *s;
+
+        assert(d);
+
+        /* Combines the watch masks of all event sources watching this inode. We generally just OR them together, but
+         * the IN_EXCL_UNLINK flag is ANDed instead.
+         *
+         * Note that we add all sources to the mask here, regardless whether enabled, disabled or oneshot. That's
+         * because we cannot change the mask anymore after the event source was created once, since the kernel has no
+         * API for that. Hence we need to subscribe to the maximum mask we ever might be interested in, and suppress
+         * events we don't care for client-side. */
+
+        LIST_FOREACH(inotify.by_inode_data, s, d->event_sources) {
+
+                if ((s->inotify.mask & IN_EXCL_UNLINK) == 0)
+                        excl_unlink = false;
+
+                combined |= s->inotify.mask;
+        }
+
+        return (combined & ~(IN_ONESHOT|IN_DONT_FOLLOW|IN_ONLYDIR|IN_EXCL_UNLINK)) | (excl_unlink ? IN_EXCL_UNLINK : 0);
+}
+
+static int inode_data_realize_watch(sd_event *e, struct inode_data *d) {
+        uint32_t combined_mask;
+        int wd, r;
+
+        assert(d);
+        assert(d->fd >= 0);
+
+        combined_mask = inode_data_determine_mask(d);
+
+        if (d->wd >= 0 && combined_mask == d->combined_mask)
+                return 0;
+
+        r = hashmap_ensure_allocated(&d->inotify_data->wd, NULL);
+        if (r < 0)
+                return r;
+
+        wd = inotify_add_watch_fd(d->inotify_data->fd, d->fd, combined_mask);
+        if (wd < 0)
+                return -errno;
+
+        if (d->wd < 0) {
+                r = hashmap_put(d->inotify_data->wd, INT_TO_PTR(wd), d);
+                if (r < 0) {
+                        (void) inotify_rm_watch(d->inotify_data->fd, wd);
+                        return r;
+                }
+
+                d->wd = wd;
+
+        } else if (d->wd != wd) {
+
+                log_debug("Weird, the watch descriptor we already knew for this inode changed?");
+                (void) inotify_rm_watch(d->fd, wd);
+                return -EINVAL;
+        }
+
+        d->combined_mask = combined_mask;
+        return 1;
+}
+
+static int inotify_exit_callback(sd_event_source *s, const struct inotify_event *event, void *userdata) {
+        assert(s);
+
+        return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
+}
+
+_public_ int sd_event_add_inotify(
+                sd_event *e,
+                sd_event_source **ret,
+                const char *path,
+                uint32_t mask,
+                sd_event_inotify_handler_t callback,
+                void *userdata) {
+
+        struct inotify_data *inotify_data = NULL;
+        struct inode_data *inode_data = NULL;
+        _cleanup_close_ int fd = -1;
+        _cleanup_(source_freep) sd_event_source *s = NULL;
+        struct stat st;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(path, -EINVAL);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (!callback)
+                callback = inotify_exit_callback;
+
+        /* Refuse IN_MASK_ADD since we coalesce watches on the same inode, and hence really don't want to merge
+         * masks. Or in other words, this whole code exists only to manage IN_MASK_ADD type operations for you, hence
+         * the user can't use them for us. */
+        if (mask & IN_MASK_ADD)
+                return -EINVAL;
+
+        fd = open(path, O_PATH|O_CLOEXEC|
+                  (mask & IN_ONLYDIR ? O_DIRECTORY : 0)|
+                  (mask & IN_DONT_FOLLOW ? O_NOFOLLOW : 0));
+        if (fd < 0)
+                return -errno;
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        s = source_new(e, !ret, SOURCE_INOTIFY);
+        if (!s)
+                return -ENOMEM;
+
+        s->enabled = mask & IN_ONESHOT ? SD_EVENT_ONESHOT : SD_EVENT_ON;
+        s->inotify.mask = mask;
+        s->inotify.callback = callback;
+        s->userdata = userdata;
+
+        /* Allocate an inotify object for this priority, and an inode object within it */
+        r = event_make_inotify_data(e, SD_EVENT_PRIORITY_NORMAL, &inotify_data);
+        if (r < 0)
+                return r;
+
+        r = event_make_inode_data(e, inotify_data, st.st_dev, st.st_ino, &inode_data);
+        if (r < 0) {
+                event_free_inotify_data(e, inotify_data);
+                return r;
+        }
+
+        /* Keep the O_PATH fd around until the first iteration of the loop, so that we can still change the priority of
+         * the event source, until then, for which we need the original inode. */
+        if (inode_data->fd < 0) {
+                inode_data->fd = TAKE_FD(fd);
+                LIST_PREPEND(to_close, e->inode_data_to_close, inode_data);
+        }
+
+        /* Link our event source to the inode data object */
+        LIST_PREPEND(inotify.by_inode_data, inode_data->event_sources, s);
+        s->inotify.inode_data = inode_data;
+
+        /* Actually realize the watch now */
+        r = inode_data_realize_watch(e, inode_data);
+        if (r < 0)
+                return r;
+
+        (void) sd_event_source_set_description(s, path);
+
+        if (ret)
+                *ret = s;
+        TAKE_PTR(s);
+
+        return 0;
+}
+
+static sd_event_source* event_source_free(sd_event_source *s) {
+        if (!s)
+                return NULL;
+
+        /* Here's a special hack: when we are called from a
+         * dispatch handler we won't free the event source
+         * immediately, but we will detach the fd from the
+         * epoll. This way it is safe for the caller to unref
+         * the event source and immediately close the fd, but
+         * we still retain a valid event source object after
+         * the callback. */
+
+        if (s->dispatching) {
+                if (s->type == SOURCE_IO)
+                        source_io_unregister(s);
+
+                source_disconnect(s);
+        } else
+                source_free(s);
+
+        return NULL;
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event_source, sd_event_source, event_source_free);
+
+_public_ int sd_event_source_set_description(sd_event_source *s, const char *description) {
+        assert_return(s, -EINVAL);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        return free_and_strdup(&s->description, description);
+}
+
+_public_ int sd_event_source_get_description(sd_event_source *s, const char **description) {
+        assert_return(s, -EINVAL);
+        assert_return(description, -EINVAL);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        if (!s->description)
+                return -ENXIO;
+
+        *description = s->description;
+        return 0;
+}
+
+_public_ sd_event *sd_event_source_get_event(sd_event_source *s) {
+        assert_return(s, NULL);
+
+        return s->event;
+}
+
+_public_ int sd_event_source_get_pending(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type != SOURCE_EXIT, -EDOM);
+        assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        return s->pending;
+}
+
+_public_ int sd_event_source_get_io_fd(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_IO, -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        return s->io.fd;
+}
+
+_public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) {
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(fd >= 0, -EBADF);
+        assert_return(s->type == SOURCE_IO, -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        if (s->io.fd == fd)
+                return 0;
+
+        if (event_source_is_offline(s)) {
+                s->io.fd = fd;
+                s->io.registered = false;
+        } else {
+                int saved_fd;
+
+                saved_fd = s->io.fd;
+                assert(s->io.registered);
+
+                s->io.fd = fd;
+                s->io.registered = false;
+
+                r = source_io_register(s, s->enabled, s->io.events);
+                if (r < 0) {
+                        s->io.fd = saved_fd;
+                        s->io.registered = true;
+                        return r;
+                }
+
+                (void) epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL);
+        }
+
+        return 0;
+}
+
+_public_ int sd_event_source_get_io_fd_own(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_IO, -EDOM);
+
+        return s->io.owned;
+}
+
+_public_ int sd_event_source_set_io_fd_own(sd_event_source *s, int own) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_IO, -EDOM);
+
+        s->io.owned = own;
+        return 0;
+}
+
+_public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events) {
+        assert_return(s, -EINVAL);
+        assert_return(events, -EINVAL);
+        assert_return(s->type == SOURCE_IO, -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        *events = s->io.events;
+        return 0;
+}
+
+_public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events) {
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_IO, -EDOM);
+        assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
+        assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        /* edge-triggered updates are never skipped, so we can reset edges */
+        if (s->io.events == events && !(events & EPOLLET))
+                return 0;
+
+        r = source_set_pending(s, false);
+        if (r < 0)
+                return r;
+
+        if (event_source_is_online(s)) {
+                r = source_io_register(s, s->enabled, events);
+                if (r < 0)
+                        return r;
+        }
+
+        s->io.events = events;
+
+        return 0;
+}
+
+_public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents) {
+        assert_return(s, -EINVAL);
+        assert_return(revents, -EINVAL);
+        assert_return(s->type == SOURCE_IO, -EDOM);
+        assert_return(s->pending, -ENODATA);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        *revents = s->io.revents;
+        return 0;
+}
+
+_public_ int sd_event_source_get_signal(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_SIGNAL, -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        return s->signal.sig;
+}
+
+_public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority) {
+        assert_return(s, -EINVAL);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        *priority = s->priority;
+        return 0;
+}
+
+_public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority) {
+        bool rm_inotify = false, rm_inode = false;
+        struct inotify_data *new_inotify_data = NULL;
+        struct inode_data *new_inode_data = NULL;
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        if (s->priority == priority)
+                return 0;
+
+        if (s->type == SOURCE_INOTIFY) {
+                struct inode_data *old_inode_data;
+
+                assert(s->inotify.inode_data);
+                old_inode_data = s->inotify.inode_data;
+
+                /* We need the original fd to change the priority. If we don't have it we can't change the priority,
+                 * anymore. Note that we close any fds when entering the next event loop iteration, i.e. for inotify
+                 * events we allow priority changes only until the first following iteration. */
+                if (old_inode_data->fd < 0)
+                        return -EOPNOTSUPP;
+
+                r = event_make_inotify_data(s->event, priority, &new_inotify_data);
+                if (r < 0)
+                        return r;
+                rm_inotify = r > 0;
+
+                r = event_make_inode_data(s->event, new_inotify_data, old_inode_data->dev, old_inode_data->ino, &new_inode_data);
+                if (r < 0)
+                        goto fail;
+                rm_inode = r > 0;
+
+                if (new_inode_data->fd < 0) {
+                        /* Duplicate the fd for the new inode object if we don't have any yet */
+                        new_inode_data->fd = fcntl(old_inode_data->fd, F_DUPFD_CLOEXEC, 3);
+                        if (new_inode_data->fd < 0) {
+                                r = -errno;
+                                goto fail;
+                        }
+
+                        LIST_PREPEND(to_close, s->event->inode_data_to_close, new_inode_data);
+                }
+
+                /* Move the event source to the new inode data structure */
+                LIST_REMOVE(inotify.by_inode_data, old_inode_data->event_sources, s);
+                LIST_PREPEND(inotify.by_inode_data, new_inode_data->event_sources, s);
+                s->inotify.inode_data = new_inode_data;
+
+                /* Now create the new watch */
+                r = inode_data_realize_watch(s->event, new_inode_data);
+                if (r < 0) {
+                        /* Move it back */
+                        LIST_REMOVE(inotify.by_inode_data, new_inode_data->event_sources, s);
+                        LIST_PREPEND(inotify.by_inode_data, old_inode_data->event_sources, s);
+                        s->inotify.inode_data = old_inode_data;
+                        goto fail;
+                }
+
+                s->priority = priority;
+
+                event_gc_inode_data(s->event, old_inode_data);
+
+        } else if (s->type == SOURCE_SIGNAL && event_source_is_online(s)) {
+                struct signal_data *old, *d;
+
+                /* Move us from the signalfd belonging to the old
+                 * priority to the signalfd of the new priority */
+
+                assert_se(old = hashmap_get(s->event->signal_data, &s->priority));
+
+                s->priority = priority;
+
+                r = event_make_signal_data(s->event, s->signal.sig, &d);
+                if (r < 0) {
+                        s->priority = old->priority;
+                        return r;
+                }
+
+                event_unmask_signal_data(s->event, old, s->signal.sig);
+        } else
+                s->priority = priority;
+
+        event_source_pp_prioq_reshuffle(s);
+
+        if (s->type == SOURCE_EXIT)
+                prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
+
+        return 0;
+
+fail:
+        if (rm_inode)
+                event_free_inode_data(s->event, new_inode_data);
+
+        if (rm_inotify)
+                event_free_inotify_data(s->event, new_inotify_data);
+
+        return r;
+}
+
+_public_ int sd_event_source_get_enabled(sd_event_source *s, int *ret) {
+        assert_return(s, -EINVAL);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        if (ret)
+                *ret = s->enabled;
+
+        return s->enabled != SD_EVENT_OFF;
+}
+
+static int event_source_offline(
+                sd_event_source *s,
+                int enabled,
+                bool ratelimited) {
+
+        bool was_offline;
+        int r;
+
+        assert(s);
+        assert(enabled == SD_EVENT_OFF || ratelimited);
+
+        /* Unset the pending flag when this event source is disabled */
+        if (s->enabled != SD_EVENT_OFF &&
+            enabled == SD_EVENT_OFF &&
+            !IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) {
+                r = source_set_pending(s, false);
+                if (r < 0)
+                        return r;
+        }
+
+        was_offline = event_source_is_offline(s);
+        s->enabled = enabled;
+        s->ratelimited = ratelimited;
+
+        switch (s->type) {
+
+        case SOURCE_IO:
+                source_io_unregister(s);
+                break;
+
+        case SOURCE_TIME_REALTIME:
+        case SOURCE_TIME_BOOTTIME:
+        case SOURCE_TIME_MONOTONIC:
+        case SOURCE_TIME_REALTIME_ALARM:
+        case SOURCE_TIME_BOOTTIME_ALARM:
+                event_source_time_prioq_reshuffle(s);
+                break;
+
+        case SOURCE_SIGNAL:
+                event_gc_signal_data(s->event, &s->priority, s->signal.sig);
+                break;
+
+        case SOURCE_CHILD:
+                if (!was_offline) {
+                        assert(s->event->n_online_child_sources > 0);
+                        s->event->n_online_child_sources--;
+                }
+
+                if (EVENT_SOURCE_WATCH_PIDFD(s))
+                        source_child_pidfd_unregister(s);
+                else
+                        event_gc_signal_data(s->event, &s->priority, SIGCHLD);
+                break;
+
+        case SOURCE_EXIT:
+                prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
+                break;
+
+        case SOURCE_DEFER:
+        case SOURCE_POST:
+        case SOURCE_INOTIFY:
+                break;
+
+        default:
+                assert_not_reached("Wut? I shouldn't exist.");
+        }
+
+        return 1;
+}
+
+static int event_source_online(
+                sd_event_source *s,
+                int enabled,
+                bool ratelimited) {
+
+        bool was_online;
+        int r;
+
+        assert(s);
+        assert(enabled != SD_EVENT_OFF || !ratelimited);
+
+        /* Unset the pending flag when this event source is enabled */
+        if (s->enabled == SD_EVENT_OFF &&
+            enabled != SD_EVENT_OFF &&
+            !IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) {
+                r = source_set_pending(s, false);
+                if (r < 0)
+                        return r;
+        }
+
+        /* Are we really ready for onlining? */
+        if (enabled == SD_EVENT_OFF || ratelimited) {
+                /* Nope, we are not ready for onlining, then just update the precise state and exit */
+                s->enabled = enabled;
+                s->ratelimited = ratelimited;
+                return 0;
+        }
+
+        was_online = event_source_is_online(s);
+
+        switch (s->type) {
+        case SOURCE_IO:
+                r = source_io_register(s, enabled, s->io.events);
+                if (r < 0)
+                        return r;
+                break;
+
+        case SOURCE_SIGNAL:
+                r = event_make_signal_data(s->event, s->signal.sig, NULL);
+                if (r < 0) {
+                        event_gc_signal_data(s->event, &s->priority, s->signal.sig);
+                        return r;
+                }
+
+                break;
+
+        case SOURCE_CHILD:
+                if (EVENT_SOURCE_WATCH_PIDFD(s)) {
+                        /* yes, we have pidfd */
+
+                        r = source_child_pidfd_register(s, enabled);
+                        if (r < 0)
+                                return r;
+                } else {
+                        /* no pidfd, or something other to watch for than WEXITED */
+
+                        r = event_make_signal_data(s->event, SIGCHLD, NULL);
+                        if (r < 0) {
+                                event_gc_signal_data(s->event, &s->priority, SIGCHLD);
+                                return r;
+                        }
+                }
+
+                if (!was_online)
+                        s->event->n_online_child_sources++;
+                break;
+
+        case SOURCE_TIME_REALTIME:
+        case SOURCE_TIME_BOOTTIME:
+        case SOURCE_TIME_MONOTONIC:
+        case SOURCE_TIME_REALTIME_ALARM:
+        case SOURCE_TIME_BOOTTIME_ALARM:
+        case SOURCE_EXIT:
+        case SOURCE_DEFER:
+        case SOURCE_POST:
+        case SOURCE_INOTIFY:
+                break;
+
+        default:
+                assert_not_reached("Wut? I shouldn't exist.");
+        }
+
+        s->enabled = enabled;
+        s->ratelimited = ratelimited;
+
+        /* Non-failing operations below */
+        switch (s->type) {
+        case SOURCE_TIME_REALTIME:
+        case SOURCE_TIME_BOOTTIME:
+        case SOURCE_TIME_MONOTONIC:
+        case SOURCE_TIME_REALTIME_ALARM:
+        case SOURCE_TIME_BOOTTIME_ALARM:
+                event_source_time_prioq_reshuffle(s);
+                break;
+
+        case SOURCE_EXIT:
+                prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
+                break;
+
+        default:
+                break;
+        }
+
+        return 1;
+}
+
+_public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(IN_SET(m, SD_EVENT_OFF, SD_EVENT_ON, SD_EVENT_ONESHOT), -EINVAL);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        /* If we are dead anyway, we are fine with turning off sources, but everything else needs to fail. */
+        if (s->event->state == SD_EVENT_FINISHED)
+                return m == SD_EVENT_OFF ? 0 : -ESTALE;
+
+        if (s->enabled == m) /* No change? */
+                return 0;
+
+        if (m == SD_EVENT_OFF)
+                r = event_source_offline(s, m, s->ratelimited);
+        else {
+                if (s->enabled != SD_EVENT_OFF) {
+                        /* Switching from "on" to "oneshot" or back? If that's the case, we can take a shortcut, the
+                         * event source is already enabled after all. */
+                        s->enabled = m;
+                        return 0;
+                }
+
+                r = event_source_online(s, m, s->ratelimited);
+        }
+        if (r < 0)
+                return r;
+
+        event_source_pp_prioq_reshuffle(s);
+        return 0;
+}
+
+_public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) {
+        assert_return(s, -EINVAL);
+        assert_return(usec, -EINVAL);
+        assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        *usec = s->time.next;
+        return 0;
+}
+
+_public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) {
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
+        assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        r = source_set_pending(s, false);
+        if (r < 0)
+                return r;
+
+        s->time.next = usec;
+
+        event_source_time_prioq_reshuffle(s);
+        return 0;
+}
+
+_public_ int sd_event_source_set_time_relative(sd_event_source *s, uint64_t usec) {
+        usec_t t;
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
+
+        r = sd_event_now(s->event, event_source_type_to_clock(s->type), &t);
+        if (r < 0)
+                return r;
+
+        if (usec >= USEC_INFINITY - t)
+                return -EOVERFLOW;
+
+        return sd_event_source_set_time(s, t + usec);
+}
+
+_public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec) {
+        assert_return(s, -EINVAL);
+        assert_return(usec, -EINVAL);
+        assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        *usec = s->time.accuracy;
+        return 0;
+}
+
+_public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec) {
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(usec != (uint64_t) -1, -EINVAL);
+        assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
+        assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        r = source_set_pending(s, false);
+        if (r < 0)
+                return r;
+
+        if (usec == 0)
+                usec = DEFAULT_ACCURACY_USEC;
+
+        s->time.accuracy = usec;
+
+        event_source_time_prioq_reshuffle(s);
+        return 0;
+}
+
+_public_ int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock) {
+        assert_return(s, -EINVAL);
+        assert_return(clock, -EINVAL);
+        assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        *clock = event_source_type_to_clock(s->type);
+        return 0;
+}
+
+_public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) {
+        assert_return(s, -EINVAL);
+        assert_return(pid, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        *pid = s->child.pid;
+        return 0;
+}
+
+_public_ int sd_event_source_get_child_pidfd(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        if (s->child.pidfd < 0)
+                return -EOPNOTSUPP;
+
+        return s->child.pidfd;
+}
+
+_public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo_t *si, unsigned flags) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(SIGNAL_VALID(sig), -EINVAL);
+
+        /* If we already have seen indication the process exited refuse sending a signal early. This way we
+         * can be sure we don't accidentally kill the wrong process on PID reuse when pidfds are not
+         * available. */
+        if (s->child.exited)
+                return -ESRCH;
+
+        if (s->child.pidfd >= 0) {
+                siginfo_t copy;
+
+                /* pidfd_send_signal() changes the siginfo_t argument. This is weird, let's hence copy the
+                 * structure here */
+                if (si)
+                        copy = *si;
+
+                if (pidfd_send_signal(s->child.pidfd, sig, si ? &copy : NULL, 0) < 0) {
+                        /* Let's propagate the error only if the system call is not implemented or prohibited */
+                        if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
+                                return -errno;
+                } else
+                        return 0;
+        }
+
+        /* Flags are only supported for pidfd_send_signal(), not for rt_sigqueueinfo(), hence let's refuse
+         * this here. */
+        if (flags != 0)
+                return -EOPNOTSUPP;
+
+        if (si) {
+                /* We use rt_sigqueueinfo() only if siginfo_t is specified. */
+                siginfo_t copy = *si;
+
+                if (rt_sigqueueinfo(s->child.pid, sig, &copy) < 0)
+                        return -errno;
+        } else if (kill(s->child.pid, sig) < 0)
+                return -errno;
+
+        return 0;
+}
+
+_public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+
+        if (s->child.pidfd < 0)
+                return -EOPNOTSUPP;
+
+        return s->child.pidfd_owned;
+}
+
+_public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+
+        if (s->child.pidfd < 0)
+                return -EOPNOTSUPP;
+
+        s->child.pidfd_owned = own;
+        return 0;
+}
+
+_public_ int sd_event_source_get_child_process_own(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+
+        return s->child.process_owned;
+}
+
+_public_ int sd_event_source_set_child_process_own(sd_event_source *s, int own) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+
+        s->child.process_owned = own;
+        return 0;
+}
+
+_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *mask) {
+        assert_return(s, -EINVAL);
+        assert_return(mask, -EINVAL);
+        assert_return(s->type == SOURCE_INOTIFY, -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        *mask = s->inotify.mask;
+        return 0;
+}
+
+_public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback) {
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(s->type != SOURCE_EXIT, -EDOM);
+        assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        if (s->prepare == callback)
+                return 0;
+
+        if (callback && s->prepare) {
+                s->prepare = callback;
+                return 0;
+        }
+
+        r = prioq_ensure_allocated(&s->event->prepare, prepare_prioq_compare);
+        if (r < 0)
+                return r;
+
+        s->prepare = callback;
+
+        if (callback) {
+                r = prioq_put(s->event->prepare, s, &s->prepare_index);
+                if (r < 0)
+                        return r;
+        } else
+                prioq_remove(s->event->prepare, s, &s->prepare_index);
+
+        return 0;
+}
+
+_public_ void* sd_event_source_get_userdata(sd_event_source *s) {
+        assert_return(s, NULL);
+
+        return s->userdata;
+}
+
+_public_ void *sd_event_source_set_userdata(sd_event_source *s, void *userdata) {
+        void *ret;
+
+        assert_return(s, NULL);
+
+        ret = s->userdata;
+        s->userdata = userdata;
+
+        return ret;
+}
+
+static int event_source_enter_ratelimited(sd_event_source *s) {
+        int r;
+
+        assert(s);
+
+        /* When an event source becomes ratelimited, we place it in the CLOCK_MONOTONIC priority queue, with
+         * the end of the rate limit time window, much as if it was a timer event source. */
+
+        if (s->ratelimited)
+                return 0; /* Already ratelimited, this is a NOP hence */
+
+        /* Make sure we can install a CLOCK_MONOTONIC event further down. */
+        r = setup_clock_data(s->event, &s->event->monotonic, CLOCK_MONOTONIC);
+        if (r < 0)
+                return r;
+
+        /* Timer event sources are already using the earliest/latest queues for the timer scheduling. Let's
+         * first remove them from the prioq appropriate for their own clock, so that we can use the prioq
+         * fields of the event source then for adding it to the CLOCK_MONOTONIC prioq instead. */
+        if (EVENT_SOURCE_IS_TIME(s->type))
+                event_source_time_prioq_remove(s, event_get_clock_data(s->event, s->type));
+
+        /* Now, let's add the event source to the monotonic clock instead */
+        r = event_source_time_prioq_put(s, &s->event->monotonic);
+        if (r < 0)
+                goto fail;
+
+        /* And let's take the event source officially offline */
+        r = event_source_offline(s, s->enabled, /* ratelimited= */ true);
+        if (r < 0) {
+                event_source_time_prioq_remove(s, &s->event->monotonic);
+                goto fail;
+        }
+
+        event_source_pp_prioq_reshuffle(s);
+
+        log_debug("Event source %p (%s) entered rate limit state.", s, strna(s->description));
+        return 0;
+
+fail:
+        /* Reinstall time event sources in the priority queue as before. This shouldn't fail, since the queue
+         * space for it should already be allocated. */
+        if (EVENT_SOURCE_IS_TIME(s->type))
+                assert_se(event_source_time_prioq_put(s, event_get_clock_data(s->event, s->type)) >= 0);
+
+        return r;
+}
+
+static int event_source_leave_ratelimit(sd_event_source *s) {
+        int r;
+
+        assert(s);
+
+        if (!s->ratelimited)
+                return 0;
+
+        /* Let's take the event source out of the monotonic prioq first. */
+        event_source_time_prioq_remove(s, &s->event->monotonic);
+
+        /* Let's then add the event source to its native clock prioq again — if this is a timer event source */
+        if (EVENT_SOURCE_IS_TIME(s->type)) {
+                r = event_source_time_prioq_put(s, event_get_clock_data(s->event, s->type));
+                if (r < 0)
+                        goto fail;
+        }
+
+        /* Let's try to take it online again.  */
+        r = event_source_online(s, s->enabled, /* ratelimited= */ false);
+        if (r < 0) {
+                /* Do something roughly sensible when this failed: undo the two prioq ops above */
+                if (EVENT_SOURCE_IS_TIME(s->type))
+                        event_source_time_prioq_remove(s, event_get_clock_data(s->event, s->type));
+
+                goto fail;
+        }
+
+        event_source_pp_prioq_reshuffle(s);
+        ratelimit_reset(&s->rate_limit);
+
+        log_debug("Event source %p (%s) left rate limit state.", s, strna(s->description));
+        return 0;
+
+fail:
+        /* Do something somewhat reasonable when we cannot move an event sources out of ratelimited mode:
+         * simply put it back in it, maybe we can then process it more successfully next iteration. */
+        assert_se(event_source_time_prioq_put(s, &s->event->monotonic) >= 0);
+
+        return r;
+}
+
+static usec_t sleep_between(sd_event *e, usec_t a, usec_t b) {
+        usec_t c;
+        assert(e);
+        assert(a <= b);
+
+        if (a <= 0)
+                return 0;
+        if (a >= USEC_INFINITY)
+                return USEC_INFINITY;
+
+        if (b <= a + 1)
+                return a;
+
+        initialize_perturb(e);
+
+        /*
+          Find a good time to wake up again between times a and b. We
+          have two goals here:
+
+          a) We want to wake up as seldom as possible, hence prefer
+             later times over earlier times.
+
+          b) But if we have to wake up, then let's make sure to
+             dispatch as much as possible on the entire system.
+
+          We implement this by waking up everywhere at the same time
+          within any given minute if we can, synchronised via the
+          perturbation value determined from the boot ID. If we can't,
+          then we try to find the same spot in every 10s, then 1s and
+          then 250ms step. Otherwise, we pick the last possible time
+          to wake up.
+        */
+
+        c = (b / USEC_PER_MINUTE) * USEC_PER_MINUTE + e->perturb;
+        if (c >= b) {
+                if (_unlikely_(c < USEC_PER_MINUTE))
+                        return b;
+
+                c -= USEC_PER_MINUTE;
+        }
+
+        if (c >= a)
+                return c;
+
+        c = (b / (USEC_PER_SEC*10)) * (USEC_PER_SEC*10) + (e->perturb % (USEC_PER_SEC*10));
+        if (c >= b) {
+                if (_unlikely_(c < USEC_PER_SEC*10))
+                        return b;
+
+                c -= USEC_PER_SEC*10;
+        }
+
+        if (c >= a)
+                return c;
+
+        c = (b / USEC_PER_SEC) * USEC_PER_SEC + (e->perturb % USEC_PER_SEC);
+        if (c >= b) {
+                if (_unlikely_(c < USEC_PER_SEC))
+                        return b;
+
+                c -= USEC_PER_SEC;
+        }
+
+        if (c >= a)
+                return c;
+
+        c = (b / (USEC_PER_MSEC*250)) * (USEC_PER_MSEC*250) + (e->perturb % (USEC_PER_MSEC*250));
+        if (c >= b) {
+                if (_unlikely_(c < USEC_PER_MSEC*250))
+                        return b;
+
+                c -= USEC_PER_MSEC*250;
+        }
+
+        if (c >= a)
+                return c;
+
+        return b;
+}
+
+static int event_arm_timer(
+                sd_event *e,
+                struct clock_data *d) {
+
+        struct itimerspec its = {};
+        sd_event_source *a, *b;
+        usec_t t;
+
+        assert(e);
+        assert(d);
+
+        if (!d->needs_rearm)
+                return 0;
+        else
+                d->needs_rearm = false;
+
+        a = prioq_peek(d->earliest);
+        if (!a || a->enabled == SD_EVENT_OFF || time_event_source_next(a) == USEC_INFINITY) {
+
+                if (d->fd < 0)
+                        return 0;
+
+                if (d->next == USEC_INFINITY)
+                        return 0;
+
+                /* disarm */
+                if (timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL) < 0)
+                        return -errno;
+
+                d->next = USEC_INFINITY;
+                return 0;
+        }
+
+        b = prioq_peek(d->latest);
+        assert_se(b && b->enabled != SD_EVENT_OFF);
+
+        t = sleep_between(e, time_event_source_next(a), time_event_source_latest(b));
+        if (d->next == t)
+                return 0;
+
+        assert_se(d->fd >= 0);
+
+        if (t == 0) {
+                /* We don' want to disarm here, just mean some time looooong ago. */
+                its.it_value.tv_sec = 0;
+                its.it_value.tv_nsec = 1;
+        } else
+                timespec_store(&its.it_value, t);
+
+        if (timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL) < 0)
+                return -errno;
+
+        d->next = t;
+        return 0;
+}
+
+static int process_io(sd_event *e, sd_event_source *s, uint32_t revents) {
+        assert(e);
+        assert(s);
+        assert(s->type == SOURCE_IO);
+
+        /* If the event source was already pending, we just OR in the
+         * new revents, otherwise we reset the value. The ORing is
+         * necessary to handle EPOLLONESHOT events properly where
+         * readability might happen independently of writability, and
+         * we need to keep track of both */
+
+        if (s->pending)
+                s->io.revents |= revents;
+        else
+                s->io.revents = revents;
+
+        return source_set_pending(s, true);
+}
+
+static int flush_timer(sd_event *e, int fd, uint32_t events, usec_t *next) {
+        uint64_t x;
+        ssize_t ss;
+
+        assert(e);
+        assert(fd >= 0);
+
+        assert_return(events == EPOLLIN, -EIO);
+
+        ss = read(fd, &x, sizeof(x));
+        if (ss < 0) {
+                if (IN_SET(errno, EAGAIN, EINTR))
+                        return 0;
+
+                return -errno;
+        }
+
+        if (_unlikely_(ss != sizeof(x)))
+                return -EIO;
+
+        if (next)
+                *next = USEC_INFINITY;
+
+        return 0;
+}
+
+static int process_timer(
+                sd_event *e,
+                usec_t n,
+                struct clock_data *d) {
+
+        sd_event_source *s;
+        int r;
+
+        assert(e);
+        assert(d);
+
+        for (;;) {
+                s = prioq_peek(d->earliest);
+                if (!s || time_event_source_next(s) > n)
+                        break;
+
+                if (s->ratelimited) {
+                        /* This is an event sources whose ratelimit window has ended. Let's turn it on
+                         * again. */
+                        assert(s->ratelimited);
+
+                        r = event_source_leave_ratelimit(s);
+                        if (r < 0)
+                                return r;
+
+                        continue;
+                }
+
+                if (s->enabled == SD_EVENT_OFF || s->pending)
+                        break;
+
+                r = source_set_pending(s, true);
+                if (r < 0)
+                        return r;
+
+                event_source_time_prioq_reshuffle(s);
+        }
+
+        return 0;
+}
+
+static int process_child(sd_event *e) {
+        sd_event_source *s;
+        int r;
+
+        assert(e);
+
+        e->need_process_child = false;
+
+        /*
+           So, this is ugly. We iteratively invoke waitid() with P_PID
+           + WNOHANG for each PID we wait for, instead of using
+           P_ALL. This is because we only want to get child
+           information of very specific child processes, and not all
+           of them. We might not have processed the SIGCHLD even of a
+           previous invocation and we don't want to maintain a
+           unbounded *per-child* event queue, hence we really don't
+           want anything flushed out of the kernel's queue that we
+           don't care about. Since this is O(n) this means that if you
+           have a lot of processes you probably want to handle SIGCHLD
+           yourself.
+
+           We do not reap the children here (by using WNOWAIT), this
+           is only done after the event source is dispatched so that
+           the callback still sees the process as a zombie.
+        */
+
+        HASHMAP_FOREACH(s, e->child_sources) {
+                assert(s->type == SOURCE_CHILD);
+
+                if (s->pending)
+                        continue;
+
+                if (event_source_is_offline(s))
+                        continue;
+
+                if (s->child.exited)
+                        continue;
+
+                if (EVENT_SOURCE_WATCH_PIDFD(s)) /* There's a usable pidfd known for this event source? then don't waitid() for it here */
+                        continue;
+
+                zero(s->child.siginfo);
+                if (waitid(P_PID, s->child.pid, &s->child.siginfo,
+                           WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options) < 0)
+                        return -errno;
+
+                if (s->child.siginfo.si_pid != 0) {
+                        bool zombie = IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED);
+
+                        if (zombie)
+                                s->child.exited = true;
+
+                        if (!zombie && (s->child.options & WEXITED)) {
+                                /* If the child isn't dead then let's
+                                 * immediately remove the state change
+                                 * from the queue, since there's no
+                                 * benefit in leaving it queued */
+
+                                assert(s->child.options & (WSTOPPED|WCONTINUED));
+                                (void) waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED)));
+                        }
+
+                        r = source_set_pending(s, true);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        return 0;
+}
+
+static int process_pidfd(sd_event *e, sd_event_source *s, uint32_t revents) {
+        assert(e);
+        assert(s);
+        assert(s->type == SOURCE_CHILD);
+
+        if (s->pending)
+                return 0;
+
+        if (event_source_is_offline(s))
+                return 0;
+
+        if (!EVENT_SOURCE_WATCH_PIDFD(s))
+                return 0;
+
+        zero(s->child.siginfo);
+        if (waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG | WNOWAIT | s->child.options) < 0)
+                return -errno;
+
+        if (s->child.siginfo.si_pid == 0)
+                return 0;
+
+        if (IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED))
+                s->child.exited = true;
+
+        return source_set_pending(s, true);
+}
+
+static int process_signal(sd_event *e, struct signal_data *d, uint32_t events) {
+        bool read_one = false;
+        int r;
+
+        assert(e);
+        assert(d);
+        assert_return(events == EPOLLIN, -EIO);
+
+        /* If there's a signal queued on this priority and SIGCHLD is
+           on this priority too, then make sure to recheck the
+           children we watch. This is because we only ever dequeue
+           the first signal per priority, and if we dequeue one, and
+           SIGCHLD might be enqueued later we wouldn't know, but we
+           might have higher priority children we care about hence we
+           need to check that explicitly. */
+
+        if (sigismember(&d->sigset, SIGCHLD))
+                e->need_process_child = true;
+
+        /* If there's already an event source pending for this
+         * priority we don't read another */
+        if (d->current)
+                return 0;
+
+        for (;;) {
+                struct signalfd_siginfo si;
+                ssize_t n;
+                sd_event_source *s = NULL;
+
+                n = read(d->fd, &si, sizeof(si));
+                if (n < 0) {
+                        if (IN_SET(errno, EAGAIN, EINTR))
+                                return read_one;
+
+                        return -errno;
+                }
+
+                if (_unlikely_(n != sizeof(si)))
+                        return -EIO;
+
+                assert(SIGNAL_VALID(si.ssi_signo));
+
+                read_one = true;
+
+                if (e->signal_sources)
+                        s = e->signal_sources[si.ssi_signo];
+                if (!s)
+                        continue;
+                if (s->pending)
+                        continue;
+
+                s->signal.siginfo = si;
+                d->current = s;
+
+                r = source_set_pending(s, true);
+                if (r < 0)
+                        return r;
+
+                return 1;
+        }
+}
+
+static int event_inotify_data_read(sd_event *e, struct inotify_data *d, uint32_t revents) {
+        ssize_t n;
+
+        assert(e);
+        assert(d);
+
+        assert_return(revents == EPOLLIN, -EIO);
+
+        /* If there's already an event source pending for this priority, don't read another */
+        if (d->n_pending > 0)
+                return 0;
+
+        /* Is the read buffer non-empty? If so, let's not read more */
+        if (d->buffer_filled > 0)
+                return 0;
+
+        n = read(d->fd, &d->buffer, sizeof(d->buffer));
+        if (n < 0) {
+                if (IN_SET(errno, EAGAIN, EINTR))
+                        return 0;
+
+                return -errno;
+        }
+
+        assert(n > 0);
+        d->buffer_filled = (size_t) n;
+        LIST_PREPEND(buffered, e->inotify_data_buffered, d);
+
+        return 1;
+}
+
+static void event_inotify_data_drop(sd_event *e, struct inotify_data *d, size_t sz) {
+        assert(e);
+        assert(d);
+        assert(sz <= d->buffer_filled);
+
+        if (sz == 0)
+                return;
+
+        /* Move the rest to the buffer to the front, in order to get things properly aligned again */
+        memmove(d->buffer.raw, d->buffer.raw + sz, d->buffer_filled - sz);
+        d->buffer_filled -= sz;
+
+        if (d->buffer_filled == 0)
+                LIST_REMOVE(buffered, e->inotify_data_buffered, d);
+}
+
+static int event_inotify_data_process(sd_event *e, struct inotify_data *d) {
+        int r;
+
+        assert(e);
+        assert(d);
+
+        /* If there's already an event source pending for this priority, don't read another */
+        if (d->n_pending > 0)
+                return 0;
+
+        while (d->buffer_filled > 0) {
+                size_t sz;
+
+                /* Let's validate that the event structures are complete */
+                if (d->buffer_filled < offsetof(struct inotify_event, name))
+                        return -EIO;
+
+                sz = offsetof(struct inotify_event, name) + d->buffer.ev.len;
+                if (d->buffer_filled < sz)
+                        return -EIO;
+
+                if (d->buffer.ev.mask & IN_Q_OVERFLOW) {
+                        struct inode_data *inode_data;
+
+                        /* The queue overran, let's pass this event to all event sources connected to this inotify
+                         * object */
+
+                        HASHMAP_FOREACH(inode_data, d->inodes) {
+                                sd_event_source *s;
+
+                                LIST_FOREACH(inotify.by_inode_data, s, inode_data->event_sources) {
+
+                                        if (event_source_is_offline(s))
+                                                continue;
+
+                                        r = source_set_pending(s, true);
+                                        if (r < 0)
+                                                return r;
+                                }
+                        }
+                } else {
+                        struct inode_data *inode_data;
+                        sd_event_source *s;
+
+                        /* Find the inode object for this watch descriptor. If IN_IGNORED is set we also remove it from
+                         * our watch descriptor table. */
+                        if (d->buffer.ev.mask & IN_IGNORED) {
+
+                                inode_data = hashmap_remove(d->wd, INT_TO_PTR(d->buffer.ev.wd));
+                                if (!inode_data) {
+                                        event_inotify_data_drop(e, d, sz);
+                                        continue;
+                                }
+
+                                /* The watch descriptor was removed by the kernel, let's drop it here too */
+                                inode_data->wd = -1;
+                        } else {
+                                inode_data = hashmap_get(d->wd, INT_TO_PTR(d->buffer.ev.wd));
+                                if (!inode_data) {
+                                        event_inotify_data_drop(e, d, sz);
+                                        continue;
+                                }
+                        }
+
+                        /* Trigger all event sources that are interested in these events. Also trigger all event
+                         * sources if IN_IGNORED or IN_UNMOUNT is set. */
+                        LIST_FOREACH(inotify.by_inode_data, s, inode_data->event_sources) {
+
+                                if (event_source_is_offline(s))
+                                        continue;
+
+                                if ((d->buffer.ev.mask & (IN_IGNORED|IN_UNMOUNT)) == 0 &&
+                                    (s->inotify.mask & d->buffer.ev.mask & IN_ALL_EVENTS) == 0)
+                                        continue;
+
+                                r = source_set_pending(s, true);
+                                if (r < 0)
+                                        return r;
+                        }
+                }
+
+                /* Something pending now? If so, let's finish, otherwise let's read more. */
+                if (d->n_pending > 0)
+                        return 1;
+        }
+
+        return 0;
+}
+
+static int process_inotify(sd_event *e) {
+        struct inotify_data *d;
+        int r, done = 0;
+
+        assert(e);
+
+        LIST_FOREACH(buffered, d, e->inotify_data_buffered) {
+                r = event_inotify_data_process(e, d);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        done ++;
+        }
+
+        return done;
+}
+
+static int source_dispatch(sd_event_source *s) {
+        _cleanup_(sd_event_unrefp) sd_event *saved_event = NULL;
+        EventSourceType saved_type;
+        int r = 0;
+
+        assert(s);
+        assert(s->pending || s->type == SOURCE_EXIT);
+
+        /* Save the event source type, here, so that we still know it after the event callback which might
+         * invalidate the event. */
+        saved_type = s->type;
+
+        /* Similar, store a reference to the event loop object, so that we can still access it after the
+         * callback might have invalidated/disconnected the event source. */
+        saved_event = sd_event_ref(s->event);
+
+        /* Check if we hit the ratelimit for this event source, if so, let's disable it. */
+        assert(!s->ratelimited);
+        if (!ratelimit_below(&s->rate_limit)) {
+                r = event_source_enter_ratelimited(s);
+                if (r < 0)
+                        return r;
+
+                return 1;
+        }
+
+        if (!IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) {
+                r = source_set_pending(s, false);
+                if (r < 0)
+                        return r;
+        }
+
+        if (s->type != SOURCE_POST) {
+                sd_event_source *z;
+
+                /* If we execute a non-post source, let's mark all
+                 * post sources as pending */
+
+                SET_FOREACH(z, s->event->post_sources) {
+                        if (event_source_is_offline(z))
+                                continue;
+
+                        r = source_set_pending(z, true);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        if (s->enabled == SD_EVENT_ONESHOT) {
+                r = sd_event_source_set_enabled(s, SD_EVENT_OFF);
+                if (r < 0)
+                        return r;
+        }
+
+        s->dispatching = true;
+
+        switch (s->type) {
+
+        case SOURCE_IO:
+                r = s->io.callback(s, s->io.fd, s->io.revents, s->userdata);
+                break;
+
+        case SOURCE_TIME_REALTIME:
+        case SOURCE_TIME_BOOTTIME:
+        case SOURCE_TIME_MONOTONIC:
+        case SOURCE_TIME_REALTIME_ALARM:
+        case SOURCE_TIME_BOOTTIME_ALARM:
+                r = s->time.callback(s, s->time.next, s->userdata);
+                break;
+
+        case SOURCE_SIGNAL:
+                r = s->signal.callback(s, &s->signal.siginfo, s->userdata);
+                break;
+
+        case SOURCE_CHILD: {
+                bool zombie;
+
+                zombie = IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED);
+
+                r = s->child.callback(s, &s->child.siginfo, s->userdata);
+
+                /* Now, reap the PID for good. */
+                if (zombie) {
+                        (void) waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|WEXITED);
+                        s->child.waited = true;
+                }
+
+                break;
+        }
+
+        case SOURCE_DEFER:
+                r = s->defer.callback(s, s->userdata);
+                break;
+
+        case SOURCE_POST:
+                r = s->post.callback(s, s->userdata);
+                break;
+
+        case SOURCE_EXIT:
+                r = s->exit.callback(s, s->userdata);
+                break;
+
+        case SOURCE_INOTIFY: {
+                struct sd_event *e = s->event;
+                struct inotify_data *d;
+                size_t sz;
+
+                assert(s->inotify.inode_data);
+                assert_se(d = s->inotify.inode_data->inotify_data);
+
+                assert(d->buffer_filled >= offsetof(struct inotify_event, name));
+                sz = offsetof(struct inotify_event, name) + d->buffer.ev.len;
+                assert(d->buffer_filled >= sz);
+
+                r = s->inotify.callback(s, &d->buffer.ev, s->userdata);
+
+                /* When no event is pending anymore on this inotify object, then let's drop the event from the
+                 * buffer. */
+                if (d->n_pending == 0)
+                        event_inotify_data_drop(e, d, sz);
+
+                break;
+        }
+
+        case SOURCE_WATCHDOG:
+        case _SOURCE_EVENT_SOURCE_TYPE_MAX:
+        case _SOURCE_EVENT_SOURCE_TYPE_INVALID:
+                assert_not_reached("Wut? I shouldn't exist.");
+        }
+
+        s->dispatching = false;
+
+        if (r < 0) {
+                log_debug_errno(r, "Event source %s (type %s) returned error, %s: %m",
+                                strna(s->description),
+                                event_source_type_to_string(saved_type),
+                                s->exit_on_failure ? "exiting" : "disabling");
+
+                if (s->exit_on_failure)
+                        (void) sd_event_exit(saved_event, r);
+        }
+
+        if (s->n_ref == 0)
+                source_free(s);
+        else if (r < 0)
+                sd_event_source_set_enabled(s, SD_EVENT_OFF);
+
+        return 1;
+}
+
+static int event_prepare(sd_event *e) {
+        int r;
+
+        assert(e);
+
+        for (;;) {
+                sd_event_source *s;
+
+                s = prioq_peek(e->prepare);
+                if (!s || s->prepare_iteration == e->iteration || event_source_is_offline(s))
+                        break;
+
+                s->prepare_iteration = e->iteration;
+                r = prioq_reshuffle(e->prepare, s, &s->prepare_index);
+                if (r < 0)
+                        return r;
+
+                assert(s->prepare);
+
+                s->dispatching = true;
+                r = s->prepare(s, s->userdata);
+                s->dispatching = false;
+
+                if (r < 0) {
+                        log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, %s: %m",
+                                        strna(s->description),
+                                        event_source_type_to_string(s->type),
+                                        s->exit_on_failure ? "exiting" : "disabling");
+
+                        if (s->exit_on_failure)
+                                (void) sd_event_exit(e, r);
+                }
+
+                if (s->n_ref == 0)
+                        source_free(s);
+                else if (r < 0)
+                        sd_event_source_set_enabled(s, SD_EVENT_OFF);
+        }
+
+        return 0;
+}
+
+static int dispatch_exit(sd_event *e) {
+        sd_event_source *p;
+        int r;
+
+        assert(e);
+
+        p = prioq_peek(e->exit);
+        if (!p || event_source_is_offline(p)) {
+                e->state = SD_EVENT_FINISHED;
+                return 0;
+        }
+
+        _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e);
+        e->iteration++;
+        e->state = SD_EVENT_EXITING;
+        r = source_dispatch(p);
+        e->state = SD_EVENT_INITIAL;
+        return r;
+}
+
+static sd_event_source* event_next_pending(sd_event *e) {
+        sd_event_source *p;
+
+        assert(e);
+
+        p = prioq_peek(e->pending);
+        if (!p)
+                return NULL;
+
+        if (event_source_is_offline(p))
+                return NULL;
+
+        return p;
+}
+
+static int arm_watchdog(sd_event *e) {
+        struct itimerspec its = {};
+        usec_t t;
+
+        assert(e);
+        assert(e->watchdog_fd >= 0);
+
+        t = sleep_between(e,
+                          e->watchdog_last + (e->watchdog_period / 2),
+                          e->watchdog_last + (e->watchdog_period * 3 / 4));
+
+        timespec_store(&its.it_value, t);
+
+        /* Make sure we never set the watchdog to 0, which tells the
+         * kernel to disable it. */
+        if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0)
+                its.it_value.tv_nsec = 1;
+
+        if (timerfd_settime(e->watchdog_fd, TFD_TIMER_ABSTIME, &its, NULL) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int process_watchdog(sd_event *e) {
+        assert(e);
+
+        if (!e->watchdog)
+                return 0;
+
+        /* Don't notify watchdog too often */
+        if (e->watchdog_last + e->watchdog_period / 4 > e->timestamp.monotonic)
+                return 0;
+
+        sd_notify(false, "WATCHDOG=1");
+        e->watchdog_last = e->timestamp.monotonic;
+
+        return arm_watchdog(e);
+}
+
+static void event_close_inode_data_fds(sd_event *e) {
+        struct inode_data *d;
+
+        assert(e);
+
+        /* Close the fds pointing to the inodes to watch now. We need to close them as they might otherwise pin
+         * filesystems. But we can't close them right-away as we need them as long as the user still wants to make
+         * adjustments to the even source, such as changing the priority (which requires us to remove and re-add a watch
+         * for the inode). Hence, let's close them when entering the first iteration after they were added, as a
+         * compromise. */
+
+        while ((d = e->inode_data_to_close)) {
+                assert(d->fd >= 0);
+                d->fd = safe_close(d->fd);
+
+                LIST_REMOVE(to_close, e->inode_data_to_close, d);
+        }
+}
+
+_public_ int sd_event_prepare(sd_event *e) {
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
+
+        /* Let's check that if we are a default event loop we are executed in the correct thread. We only do
+         * this check here once, since gettid() is typically not cached, and thus want to minimize
+         * syscalls */
+        assert_return(!e->default_event_ptr || e->tid == gettid(), -EREMOTEIO);
+
+        /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */
+        _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e);
+
+        if (e->exit_requested)
+                goto pending;
+
+        e->iteration++;
+
+        e->state = SD_EVENT_PREPARING;
+        r = event_prepare(e);
+        e->state = SD_EVENT_INITIAL;
+        if (r < 0)
+                return r;
+
+        r = event_arm_timer(e, &e->realtime);
+        if (r < 0)
+                return r;
+
+        r = event_arm_timer(e, &e->boottime);
+        if (r < 0)
+                return r;
+
+        r = event_arm_timer(e, &e->monotonic);
+        if (r < 0)
+                return r;
+
+        r = event_arm_timer(e, &e->realtime_alarm);
+        if (r < 0)
+                return r;
+
+        r = event_arm_timer(e, &e->boottime_alarm);
+        if (r < 0)
+                return r;
+
+        event_close_inode_data_fds(e);
+
+        if (event_next_pending(e) || e->need_process_child)
+                goto pending;
+
+        e->state = SD_EVENT_ARMED;
+
+        return 0;
+
+pending:
+        e->state = SD_EVENT_ARMED;
+        r = sd_event_wait(e, 0);
+        if (r == 0)
+                e->state = SD_EVENT_ARMED;
+
+        return r;
+}
+
+_public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
+        size_t event_queue_max;
+        int r, m, i;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(e->state == SD_EVENT_ARMED, -EBUSY);
+
+        if (e->exit_requested) {
+                e->state = SD_EVENT_PENDING;
+                return 1;
+        }
+
+        event_queue_max = MAX(e->n_sources, 1u);
+        if (!GREEDY_REALLOC(e->event_queue, e->event_queue_allocated, event_queue_max))
+                return -ENOMEM;
+
+        /* If we still have inotify data buffered, then query the other fds, but don't wait on it */
+        if (e->inotify_data_buffered)
+                timeout = 0;
+
+        m = epoll_wait(e->epoll_fd, e->event_queue, event_queue_max,
+                       timeout == (uint64_t) -1 ? -1 : (int) DIV_ROUND_UP(timeout, USEC_PER_MSEC));
+        if (m < 0) {
+                if (errno == EINTR) {
+                        e->state = SD_EVENT_PENDING;
+                        return 1;
+                }
+
+                r = -errno;
+                goto finish;
+        }
+
+        triple_timestamp_get(&e->timestamp);
+
+        for (i = 0; i < m; i++) {
+
+                if (e->event_queue[i].data.ptr == INT_TO_PTR(SOURCE_WATCHDOG))
+                        r = flush_timer(e, e->watchdog_fd, e->event_queue[i].events, NULL);
+                else {
+                        WakeupType *t = e->event_queue[i].data.ptr;
+
+                        switch (*t) {
+
+                        case WAKEUP_EVENT_SOURCE: {
+                                sd_event_source *s = e->event_queue[i].data.ptr;
+
+                                assert(s);
+
+                                switch (s->type) {
+
+                                case SOURCE_IO:
+                                        r = process_io(e, s, e->event_queue[i].events);
+                                        break;
+
+                                case SOURCE_CHILD:
+                                        r = process_pidfd(e, s, e->event_queue[i].events);
+                                        break;
+
+                                default:
+                                        assert_not_reached("Unexpected event source type");
+                                }
+
+                                break;
+                        }
+
+                        case WAKEUP_CLOCK_DATA: {
+                                struct clock_data *d = e->event_queue[i].data.ptr;
+
+                                assert(d);
+
+                                r = flush_timer(e, d->fd, e->event_queue[i].events, &d->next);
+                                break;
+                        }
+
+                        case WAKEUP_SIGNAL_DATA:
+                                r = process_signal(e, e->event_queue[i].data.ptr, e->event_queue[i].events);
+                                break;
+
+                        case WAKEUP_INOTIFY_DATA:
+                                r = event_inotify_data_read(e, e->event_queue[i].data.ptr, e->event_queue[i].events);
+                                break;
+
+                        default:
+                                assert_not_reached("Invalid wake-up pointer");
+                        }
+                }
+                if (r < 0)
+                        goto finish;
+        }
+
+        r = process_watchdog(e);
+        if (r < 0)
+                goto finish;
+
+        r = process_timer(e, e->timestamp.realtime, &e->realtime);
+        if (r < 0)
+                goto finish;
+
+        r = process_timer(e, e->timestamp.boottime, &e->boottime);
+        if (r < 0)
+                goto finish;
+
+        r = process_timer(e, e->timestamp.monotonic, &e->monotonic);
+        if (r < 0)
+                goto finish;
+
+        r = process_timer(e, e->timestamp.realtime, &e->realtime_alarm);
+        if (r < 0)
+                goto finish;
+
+        r = process_timer(e, e->timestamp.boottime, &e->boottime_alarm);
+        if (r < 0)
+                goto finish;
+
+        if (e->need_process_child) {
+                r = process_child(e);
+                if (r < 0)
+                        goto finish;
+        }
+
+        r = process_inotify(e);
+        if (r < 0)
+                goto finish;
+
+        if (event_next_pending(e)) {
+                e->state = SD_EVENT_PENDING;
+
+                return 1;
+        }
+
+        r = 0;
+
+finish:
+        e->state = SD_EVENT_INITIAL;
+
+        return r;
+}
+
+_public_ int sd_event_dispatch(sd_event *e) {
+        sd_event_source *p;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(e->state == SD_EVENT_PENDING, -EBUSY);
+
+        if (e->exit_requested)
+                return dispatch_exit(e);
+
+        p = event_next_pending(e);
+        if (p) {
+                _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e);
+
+                e->state = SD_EVENT_RUNNING;
+                r = source_dispatch(p);
+                e->state = SD_EVENT_INITIAL;
+                return r;
+        }
+
+        e->state = SD_EVENT_INITIAL;
+
+        return 1;
+}
+
+static void event_log_delays(sd_event *e) {
+        char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1], *p;
+        size_t l, i;
+
+        p = b;
+        l = sizeof(b);
+        for (i = 0; i < ELEMENTSOF(e->delays); i++) {
+                l = strpcpyf(&p, l, "%u ", e->delays[i]);
+                e->delays[i] = 0;
+        }
+        log_debug("Event loop iterations: %s", b);
+}
+
+_public_ int sd_event_run(sd_event *e, uint64_t timeout) {
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
+
+        if (e->profile_delays && e->last_run_usec != 0) {
+                usec_t this_run;
+                unsigned l;
+
+                this_run = now(CLOCK_MONOTONIC);
+
+                l = u64log2(this_run - e->last_run_usec);
+                assert(l < ELEMENTSOF(e->delays));
+                e->delays[l]++;
+
+                if (this_run - e->last_log_usec >= 5*USEC_PER_SEC) {
+                        event_log_delays(e);
+                        e->last_log_usec = this_run;
+                }
+        }
+
+        /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */
+        _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e);
+
+        r = sd_event_prepare(e);
+        if (r == 0)
+                /* There was nothing? Then wait... */
+                r = sd_event_wait(e, timeout);
+
+        if (e->profile_delays)
+                e->last_run_usec = now(CLOCK_MONOTONIC);
+
+        if (r > 0) {
+                /* There's something now, then let's dispatch it */
+                r = sd_event_dispatch(e);
+                if (r < 0)
+                        return r;
+
+                return 1;
+        }
+
+        return r;
+}
+
+_public_ int sd_event_loop(sd_event *e) {
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
+
+        _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = NULL;
+
+        while (e->state != SD_EVENT_FINISHED) {
+                r = sd_event_run(e, (uint64_t) -1);
+                if (r < 0)
+                        return r;
+        }
+
+        return e->exit_code;
+}
+
+_public_ int sd_event_get_fd(sd_event *e) {
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        return e->epoll_fd;
+}
+
+_public_ int sd_event_get_state(sd_event *e) {
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        return e->state;
+}
+
+_public_ int sd_event_get_exit_code(sd_event *e, int *code) {
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(code, -EINVAL);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (!e->exit_requested)
+                return -ENODATA;
+
+        *code = e->exit_code;
+        return 0;
+}
+
+_public_ int sd_event_exit(sd_event *e, int code) {
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        e->exit_requested = true;
+        e->exit_code = code;
+
+        return 0;
+}
+
+_public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) {
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(usec, -EINVAL);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock))
+                return -EOPNOTSUPP;
+
+        /* Generate a clean error in case CLOCK_BOOTTIME is not available. Note that don't use clock_supported() here,
+         * for a reason: there are systems where CLOCK_BOOTTIME is supported, but CLOCK_BOOTTIME_ALARM is not, but for
+         * the purpose of getting the time this doesn't matter. */
+        if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && !clock_boottime_supported())
+                return -EOPNOTSUPP;
+
+        if (!triple_timestamp_is_set(&e->timestamp)) {
+                /* Implicitly fall back to now() if we never ran before and thus have no cached time. */
+                *usec = now(clock);
+                return 1;
+        }
+
+        *usec = triple_timestamp_by_clock(&e->timestamp, clock);
+        return 0;
+}
+
+_public_ int sd_event_default(sd_event **ret) {
+        sd_event *e = NULL;
+        int r;
+
+        if (!ret)
+                return !!default_event;
+
+        if (default_event) {
+                *ret = sd_event_ref(default_event);
+                return 0;
+        }
+
+        r = sd_event_new(&e);
+        if (r < 0)
+                return r;
+
+        e->default_event_ptr = &default_event;
+        e->tid = gettid();
+        default_event = e;
+
+        *ret = e;
+        return 1;
+}
+
+#if 0 /* NM_IGNORED */
+_public_ int sd_event_get_tid(sd_event *e, pid_t *tid) {
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(tid, -EINVAL);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (e->tid != 0) {
+                *tid = e->tid;
+                return 0;
+        }
+
+        return -ENXIO;
+}
+
+_public_ int sd_event_set_watchdog(sd_event *e, int b) {
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (e->watchdog == !!b)
+                return e->watchdog;
+
+        if (b) {
+                r = sd_watchdog_enabled(false, &e->watchdog_period);
+                if (r <= 0)
+                        return r;
+
+                /* Issue first ping immediately */
+                sd_notify(false, "WATCHDOG=1");
+                e->watchdog_last = now(CLOCK_MONOTONIC);
+
+                e->watchdog_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
+                if (e->watchdog_fd < 0)
+                        return -errno;
+
+                r = arm_watchdog(e);
+                if (r < 0)
+                        goto fail;
+
+                struct epoll_event ev = {
+                        .events = EPOLLIN,
+                        .data.ptr = INT_TO_PTR(SOURCE_WATCHDOG),
+                };
+
+                if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, e->watchdog_fd, &ev) < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+
+        } else {
+                if (e->watchdog_fd >= 0) {
+                        (void) epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, e->watchdog_fd, NULL);
+                        e->watchdog_fd = safe_close(e->watchdog_fd);
+                }
+        }
+
+        e->watchdog = !!b;
+        return e->watchdog;
+
+fail:
+        e->watchdog_fd = safe_close(e->watchdog_fd);
+        return r;
+}
+
+_public_ int sd_event_get_watchdog(sd_event *e) {
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        return e->watchdog;
+}
+
+_public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) {
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        *ret = e->iteration;
+        return 0;
+}
+
+_public_ int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback) {
+        assert_return(s, -EINVAL);
+
+        s->destroy_callback = callback;
+        return 0;
+}
+
+_public_ int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret) {
+        assert_return(s, -EINVAL);
+
+        if (ret)
+                *ret = s->destroy_callback;
+
+        return !!s->destroy_callback;
+}
+
+_public_ int sd_event_source_get_floating(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+
+        return s->floating;
+}
+
+_public_ int sd_event_source_set_floating(sd_event_source *s, int b) {
+        assert_return(s, -EINVAL);
+
+        if (s->floating == !!b)
+                return 0;
+
+        if (!s->event) /* Already disconnected */
+                return -ESTALE;
+
+        s->floating = b;
+
+        if (b) {
+                sd_event_source_ref(s);
+                sd_event_unref(s->event);
+        } else {
+                sd_event_ref(s->event);
+                sd_event_source_unref(s);
+        }
+
+        return 1;
+}
+
+_public_ int sd_event_source_get_exit_on_failure(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type != SOURCE_EXIT, -EDOM);
+
+        return s->exit_on_failure;
+}
+
+_public_ int sd_event_source_set_exit_on_failure(sd_event_source *s, int b) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type != SOURCE_EXIT, -EDOM);
+
+        if (s->exit_on_failure == !!b)
+                return 0;
+
+        s->exit_on_failure = b;
+        return 1;
+}
+
+_public_ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval, unsigned burst) {
+        int r;
+
+        assert_return(s, -EINVAL);
+
+        /* Turning on ratelimiting on event source types that don't support it, is a loggable offense. Doing
+         * so is a programming error. */
+        assert_return(EVENT_SOURCE_CAN_RATE_LIMIT(s->type), -EDOM);
+
+        /* When ratelimiting is configured we'll always reset the rate limit state first and start fresh,
+         * non-ratelimited. */
+        r = event_source_leave_ratelimit(s);
+        if (r < 0)
+                return r;
+
+        s->rate_limit = (RateLimit) { interval, burst };
+        return 0;
+}
+
+_public_ int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval, unsigned *ret_burst) {
+        assert_return(s, -EINVAL);
+
+        /* Querying whether an event source has ratelimiting configured is not a loggable offsense, hence
+         * don't use assert_return(). Unlike turning on ratelimiting it's not really a programming error */
+        if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type))
+                return -EDOM;
+
+        if (!ratelimit_configured(&s->rate_limit))
+                return -ENOEXEC;
+
+        if (ret_interval)
+                *ret_interval = s->rate_limit.interval;
+        if (ret_burst)
+                *ret_burst = s->rate_limit.burst;
+
+        return 0;
+}
+
+_public_ int sd_event_source_is_ratelimited(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+
+        if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type))
+                return false;
+
+        if (!ratelimit_configured(&s->rate_limit))
+                return false;
+
+        return s->ratelimited;
+}
+#endif /* NM_IGNORED */
diff --git a/src/core/systemd/src/libsystemd/sd-id128/id128-util.c b/src/core/systemd/src/libsystemd/sd-id128/id128-util.c
new file mode 100644
index 00000000..b61cfdb0
--- /dev/null
+++ b/src/core/systemd/src/libsystemd/sd-id128/id128-util.c
@@ -0,0 +1,218 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "fd-util.h"
+#include "fs-util.h"
+#include "hexdecoct.h"
+#include "id128-util.h"
+#include "io-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+
+#if 0 /* NM_IGNORED */
+char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]) {
+        unsigned n, k = 0;
+
+        assert(s);
+
+        /* Similar to sd_id128_to_string() but formats the result as UUID instead of plain hex chars */
+
+        for (n = 0; n < 16; n++) {
+
+                if (IN_SET(n, 4, 6, 8, 10))
+                        s[k++] = '-';
+
+                s[k++] = hexchar(id.bytes[n] >> 4);
+                s[k++] = hexchar(id.bytes[n] & 0xF);
+        }
+
+        assert(k == 36);
+
+        s[k] = 0;
+
+        return s;
+}
+
+bool id128_is_valid(const char *s) {
+        size_t i, l;
+
+        assert(s);
+
+        l = strlen(s);
+        if (l == 32) {
+
+                /* Plain formatted 128bit hex string */
+
+                for (i = 0; i < l; i++) {
+                        char c = s[i];
+
+                        if (!(c >= '0' && c <= '9') &&
+                            !(c >= 'a' && c <= 'z') &&
+                            !(c >= 'A' && c <= 'Z'))
+                                return false;
+                }
+
+        } else if (l == 36) {
+
+                /* Formatted UUID */
+
+                for (i = 0; i < l; i++) {
+                        char c = s[i];
+
+                        if (IN_SET(i, 8, 13, 18, 23)) {
+                                if (c != '-')
+                                        return false;
+                        } else {
+                                if (!(c >= '0' && c <= '9') &&
+                                    !(c >= 'a' && c <= 'z') &&
+                                    !(c >= 'A' && c <= 'Z'))
+                                        return false;
+                        }
+                }
+
+        } else
+                return false;
+
+        return true;
+}
+#endif /* NM_IGNORED */
+
+int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) {
+        char buffer[36 + 2];
+        ssize_t l;
+
+        assert(fd >= 0);
+        assert(f < _ID128_FORMAT_MAX);
+
+        /* Reads an 128bit ID from a file, which may either be in plain format (32 hex digits), or in UUID format, both
+         * optionally followed by a newline and nothing else. ID files should really be newline terminated, but if they
+         * aren't that's OK too, following the rule of "Be conservative in what you send, be liberal in what you
+         * accept". */
+
+        l = loop_read(fd, buffer, sizeof(buffer), false); /* we expect a short read of either 32/33 or 36/37 chars */
+        if (l < 0)
+                return (int) l;
+        if (l == 0) /* empty? */
+                return -ENOMEDIUM;
+
+        switch (l) {
+
+        case 13:
+        case 14:
+                /* Treat an "uninitialized" id file like an empty one */
+                return f == ID128_PLAIN_OR_UNINIT && strneq(buffer, "uninitialized\n", l) ? -ENOMEDIUM : -EINVAL;
+
+        case 33: /* plain UUID with trailing newline */
+                if (buffer[32] != '\n')
+                        return -EINVAL;
+
+                _fallthrough_;
+        case 32: /* plain UUID without trailing newline */
+                if (f == ID128_UUID)
+                        return -EINVAL;
+
+                buffer[32] = 0;
+                break;
+
+        case 37: /* RFC UUID with trailing newline */
+                if (buffer[36] != '\n')
+                        return -EINVAL;
+
+                _fallthrough_;
+        case 36: /* RFC UUID without trailing newline */
+                if (IN_SET(f, ID128_PLAIN, ID128_PLAIN_OR_UNINIT))
+                        return -EINVAL;
+
+                buffer[36] = 0;
+                break;
+
+        default:
+                return -EINVAL;
+        }
+
+        return sd_id128_from_string(buffer, ret);
+}
+
+int id128_read(const char *p, Id128Format f, sd_id128_t *ret) {
+        _cleanup_close_ int fd = -1;
+
+        fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        if (fd < 0)
+                return -errno;
+
+        return id128_read_fd(fd, f, ret);
+}
+
+#if 0 /* NM_IGNORED */
+int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync) {
+        char buffer[36 + 2];
+        size_t sz;
+        int r;
+
+        assert(fd >= 0);
+        assert(f < _ID128_FORMAT_MAX);
+
+        if (f != ID128_UUID) {
+                sd_id128_to_string(id, buffer);
+                buffer[32] = '\n';
+                sz = 33;
+        } else {
+                id128_to_uuid_string(id, buffer);
+                buffer[36] = '\n';
+                sz = 37;
+        }
+
+        r = loop_write(fd, buffer, sz, false);
+        if (r < 0)
+                return r;
+
+        if (do_sync) {
+                if (fsync(fd) < 0)
+                        return -errno;
+
+                r = fsync_directory_of_file(fd);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync) {
+        _cleanup_close_ int fd = -1;
+
+        fd = open(p, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, 0444);
+        if (fd < 0)
+                return -errno;
+
+        return id128_write_fd(fd, f, id, do_sync);
+}
+
+void id128_hash_func(const sd_id128_t *p, struct siphash *state) {
+        siphash24_compress(p, sizeof(sd_id128_t), state);
+}
+
+int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) {
+        return memcmp(a, b, 16);
+}
+
+sd_id128_t id128_make_v4_uuid(sd_id128_t id) {
+        /* Stolen from generate_random_uuid() of drivers/char/random.c
+         * in the kernel sources */
+
+        /* Set UUID version to 4 --- truly random generation */
+        id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40;
+
+        /* Set the UUID variant to DCE */
+        id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80;
+
+        return id;
+}
+
+DEFINE_HASH_OPS(id128_hash_ops, sd_id128_t, id128_hash_func, id128_compare_func);
+#endif /* NM_IGNORED */
diff --git a/src/core/systemd/src/libsystemd/sd-id128/id128-util.h b/src/core/systemd/src/libsystemd/sd-id128/id128-util.h
new file mode 100644
index 00000000..6b09bcd9
--- /dev/null
+++ b/src/core/systemd/src/libsystemd/sd-id128/id128-util.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+#include "sd-id128.h"
+
+#include "hash-funcs.h"
+#include "macro.h"
+
+#define ID128_UUID_STRING_MAX 37
+
+char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]);
+
+bool id128_is_valid(const char *s) _pure_;
+
+typedef enum Id128Format {
+        ID128_ANY,
+        ID128_PLAIN,  /* formatted as 32 hex chars as-is */
+        ID128_PLAIN_OR_UNINIT,  /* formatted as 32 hex chars as-is; allow special "uninitialized"
+                                 * value when reading from file (id128_read() and id128_read_fd()).
+                                 *
+                                 * This format should be used when reading a machine-id file. */
+        ID128_UUID,   /* formatted as 36 character uuid string */
+        _ID128_FORMAT_MAX,
+} Id128Format;
+
+int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret);
+int id128_read(const char *p, Id128Format f, sd_id128_t *ret);
+
+int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync);
+int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync);
+
+void id128_hash_func(const sd_id128_t *p, struct siphash *state);
+int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_;
+extern const struct hash_ops id128_hash_ops;
+
+sd_id128_t id128_make_v4_uuid(sd_id128_t id);
diff --git a/src/core/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/core/systemd/src/libsystemd/sd-id128/sd-id128.c
new file mode 100644
index 00000000..64b8c787
--- /dev/null
+++ b/src/core/systemd/src/libsystemd/sd-id128/sd-id128.c
@@ -0,0 +1,330 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sd-id128.h"
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "id128-util.h"
+#include "io-util.h"
+#include "khash.h"
+#include "macro.h"
+#include "missing_syscall.h"
+#include "random-util.h"
+#include "user-util.h"
+#include "util.h"
+
+#if 0 /* NM_IGNORED */
+_public_ char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]) {
+        unsigned n;
+
+        assert_return(s, NULL);
+
+        for (n = 0; n < 16; n++) {
+                s[n*2] = hexchar(id.bytes[n] >> 4);
+                s[n*2+1] = hexchar(id.bytes[n] & 0xF);
+        }
+
+        s[32] = 0;
+
+        return s;
+}
+#endif /* NM_IGNORED */
+
+_public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) {
+        unsigned n, i;
+        sd_id128_t t;
+        bool is_guid = false;
+
+        assert_return(s, -EINVAL);
+
+        for (n = 0, i = 0; n < 16;) {
+                int a, b;
+
+                if (s[i] == '-') {
+                        /* Is this a GUID? Then be nice, and skip over
+                         * the dashes */
+
+                        if (i == 8)
+                                is_guid = true;
+                        else if (IN_SET(i, 13, 18, 23)) {
+                                if (!is_guid)
+                                        return -EINVAL;
+                        } else
+                                return -EINVAL;
+
+                        i++;
+                        continue;
+                }
+
+                a = unhexchar(s[i++]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unhexchar(s[i++]);
+                if (b < 0)
+                        return -EINVAL;
+
+                t.bytes[n++] = (a << 4) | b;
+        }
+
+        if (i != (is_guid ? 36 : 32))
+                return -EINVAL;
+
+        if (s[i] != 0)
+                return -EINVAL;
+
+        if (ret)
+                *ret = t;
+        return 0;
+}
+
+_public_ int sd_id128_get_machine(sd_id128_t *ret) {
+        static thread_local sd_id128_t saved_machine_id = {};
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        if (sd_id128_is_null(saved_machine_id)) {
+                r = id128_read("/etc/machine-id", ID128_PLAIN, &saved_machine_id);
+                if (r < 0)
+                        return r;
+
+                if (sd_id128_is_null(saved_machine_id))
+                        return -ENOMEDIUM;
+        }
+
+        *ret = saved_machine_id;
+        return 0;
+}
+
+_public_ int sd_id128_get_boot(sd_id128_t *ret) {
+        static thread_local sd_id128_t saved_boot_id = {};
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        if (sd_id128_is_null(saved_boot_id)) {
+                r = id128_read("/proc/sys/kernel/random/boot_id", ID128_UUID, &saved_boot_id);
+                if (r < 0)
+                        return r;
+        }
+
+        *ret = saved_boot_id;
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+static int get_invocation_from_keyring(sd_id128_t *ret) {
+        _cleanup_free_ char *description = NULL;
+        char *d, *p, *g, *u, *e;
+        unsigned long perms;
+        key_serial_t key;
+        size_t sz = 256;
+        uid_t uid;
+        gid_t gid;
+        int r, c;
+
+#define MAX_PERMS ((unsigned long) (KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH| \
+                                    KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH))
+
+        assert(ret);
+
+        key = request_key("user", "invocation_id", NULL, 0);
+        if (key == -1) {
+                /* Keyring support not available? No invocation key stored? */
+                if (IN_SET(errno, ENOSYS, ENOKEY))
+                        return -ENXIO;
+
+                return -errno;
+        }
+
+        for (;;) {
+                description = new(char, sz);
+                if (!description)
+                        return -ENOMEM;
+
+                c = keyctl(KEYCTL_DESCRIBE, key, (unsigned long) description, sz, 0);
+                if (c < 0)
+                        return -errno;
+
+                if ((size_t) c <= sz)
+                        break;
+
+                sz = c;
+                free(description);
+        }
+
+        /* The kernel returns a final NUL in the string, verify that. */
+        assert(description[c-1] == 0);
+
+        /* Chop off the final description string */
+        d = strrchr(description, ';');
+        if (!d)
+                return -EIO;
+        *d = 0;
+
+        /* Look for the permissions */
+        p = strrchr(description, ';');
+        if (!p)
+                return -EIO;
+
+        errno = 0;
+        perms = strtoul(p + 1, &e, 16);
+        if (errno > 0)
+                return -errno;
+        if (e == p + 1) /* Read at least one character */
+                return -EIO;
+        if (e != d) /* Must reached the end */
+                return -EIO;
+
+        if ((perms & ~MAX_PERMS) != 0)
+                return -EPERM;
+
+        *p = 0;
+
+        /* Look for the group ID */
+        g = strrchr(description, ';');
+        if (!g)
+                return -EIO;
+        r = parse_gid(g + 1, &gid);
+        if (r < 0)
+                return r;
+        if (gid != 0)
+                return -EPERM;
+        *g = 0;
+
+        /* Look for the user ID */
+        u = strrchr(description, ';');
+        if (!u)
+                return -EIO;
+        r = parse_uid(u + 1, &uid);
+        if (r < 0)
+                return r;
+        if (uid != 0)
+                return -EPERM;
+
+        c = keyctl(KEYCTL_READ, key, (unsigned long) ret, sizeof(sd_id128_t), 0);
+        if (c < 0)
+                return -errno;
+        if (c != sizeof(sd_id128_t))
+                return -EIO;
+
+        return 0;
+}
+
+static int get_invocation_from_environment(sd_id128_t *ret) {
+        const char *e;
+
+        assert(ret);
+
+        e = secure_getenv("INVOCATION_ID");
+        if (!e)
+                return -ENXIO;
+
+        return sd_id128_from_string(e, ret);
+}
+
+_public_ int sd_id128_get_invocation(sd_id128_t *ret) {
+        static thread_local sd_id128_t saved_invocation_id = {};
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        if (sd_id128_is_null(saved_invocation_id)) {
+                /* We first check the environment. The environment variable is primarily relevant for user
+                 * services, and sufficiently safe as long as no privilege boundary is involved. */
+                r = get_invocation_from_environment(&saved_invocation_id);
+                if (r < 0 && r != -ENXIO)
+                        return r;
+
+                /* The kernel keyring is relevant for system services (as for user services we don't store
+                 * the invocation ID in the keyring, as there'd be no trust benefit in that). */
+                r = get_invocation_from_keyring(&saved_invocation_id);
+                if (r < 0)
+                        return r;
+        }
+
+        *ret = saved_invocation_id;
+        return 0;
+}
+
+_public_ int sd_id128_randomize(sd_id128_t *ret) {
+        sd_id128_t t;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        /* We allow usage if x86-64 RDRAND here. It might not be trusted enough for keeping secrets, but it should be
+         * fine for UUIDS. */
+        r = genuine_random_bytes(&t, sizeof t, RANDOM_ALLOW_RDRAND);
+        if (r < 0)
+                return r;
+
+        /* Turn this into a valid v4 UUID, to be nice. Note that we
+         * only guarantee this for newly generated UUIDs, not for
+         * pre-existing ones. */
+
+        *ret = id128_make_v4_uuid(t);
+        return 0;
+}
+
+static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) {
+        _cleanup_(khash_unrefp) khash *h = NULL;
+        sd_id128_t result;
+        const void *p;
+        int r;
+
+        assert(ret);
+
+        r = khash_new_with_key(&h, "hmac(sha256)", &base, sizeof(base));
+        if (r < 0)
+                return r;
+
+        r = khash_put(h, &app_id, sizeof(app_id));
+        if (r < 0)
+                return r;
+
+        r = khash_digest_data(h, &p);
+        if (r < 0)
+                return r;
+
+        /* We chop off the trailing 16 bytes */
+        memcpy(&result, p, MIN(khash_get_size(h), sizeof(result)));
+
+        *ret = id128_make_v4_uuid(result);
+        return 0;
+}
+
+_public_ int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
+        sd_id128_t id;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        r = sd_id128_get_machine(&id);
+        if (r < 0)
+                return r;
+
+        return get_app_specific(id, app_id, ret);
+}
+
+_public_ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
+        sd_id128_t id;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        r = sd_id128_get_boot(&id);
+        if (r < 0)
+                return r;
+
+        return get_app_specific(id, app_id, ret);
+}
+#endif /* NM_IGNORED */
diff --git a/src/core/systemd/src/systemd/_sd-common.h b/src/core/systemd/src/systemd/_sd-common.h
new file mode 100644
index 00000000..e3de2ae5
--- /dev/null
+++ b/src/core/systemd/src/systemd/_sd-common.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosdcommonhfoo
+#define foosdcommonhfoo
+
+/***
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+/* This is a private header; never even think of including this directly! */
+
+#if defined(__INCLUDE_LEVEL__) && __INCLUDE_LEVEL__ <= 1 && !defined(__COVERITY__)
+#  error "Do not include _sd-common.h directly; it is a private header."
+#endif
+
+typedef void (*_sd_destroy_t)(void *userdata);
+
+#ifndef _sd_printf_
+#  if __GNUC__ >= 4
+#    define _sd_printf_(a,b) __attribute__((__format__(printf, a, b)))
+#  else
+#    define _sd_printf_(a,b)
+#  endif
+#endif
+
+#ifndef _sd_sentinel_
+#  define _sd_sentinel_ __attribute__((__sentinel__))
+#endif
+
+#ifndef _sd_packed_
+#  define _sd_packed_ __attribute__((__packed__))
+#endif
+
+#ifndef _sd_pure_
+#  define _sd_pure_ __attribute__((__pure__))
+#endif
+
+/* Note that strictly speaking __deprecated__ has been available before GCC 6. However, starting with GCC 6
+ * it also works on enum values, which we are interested in. Since this is a developer-facing feature anyway
+ * (as opposed to build engineer-facing), let's hence conditionalize this to gcc 6, given that the developers
+ * are probably going to use something newer anyway. */
+#ifndef _sd_deprecated_
+#  if __GNUC__ >= 6
+#    define _sd_deprecated_ __attribute__((__deprecated__))
+#  else
+#    define _sd_deprecated_
+#  endif
+#endif
+
+#ifndef _SD_STRINGIFY
+#  define _SD_XSTRINGIFY(x) #x
+#  define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x)
+#endif
+
+#ifndef _SD_BEGIN_DECLARATIONS
+#  ifdef __cplusplus
+#    define _SD_BEGIN_DECLARATIONS                              \
+        extern "C" {                                            \
+        struct _sd_useless_struct_to_allow_trailing_semicolon_
+#  else
+#    define _SD_BEGIN_DECLARATIONS                              \
+        struct _sd_useless_struct_to_allow_trailing_semicolon_
+#  endif
+#endif
+
+#ifndef _SD_END_DECLARATIONS
+#  ifdef __cplusplus
+#    define _SD_END_DECLARATIONS                                \
+        }                                                       \
+        struct _sd_useless_cpp_struct_to_allow_trailing_semicolon_
+#  else
+#    define _SD_END_DECLARATIONS                                \
+        struct _sd_useless_struct_to_allow_trailing_semicolon_
+#  endif
+#endif
+
+#ifndef _SD_ARRAY_STATIC
+#  if __STDC_VERSION__ >= 199901L
+#    define _SD_ARRAY_STATIC static
+#  else
+#    define _SD_ARRAY_STATIC
+#  endif
+#endif
+
+#define _SD_DEFINE_POINTER_CLEANUP_FUNC(type, func)             \
+        static __inline__ void func##p(type **p) {              \
+                if (*p)                                         \
+                        func(*p);                               \
+        }                                                       \
+        struct _sd_useless_struct_to_allow_trailing_semicolon_
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-dhcp-client.h b/src/core/systemd/src/systemd/sd-dhcp-client.h
new file mode 100644
index 00000000..c35328a9
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-dhcp-client.h
@@ -0,0 +1,224 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosddhcpclienthfoo
+#define foosddhcpclienthfoo
+
+/***
+  Copyright © 2013 Intel Corporation. All rights reserved.
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <stdbool.h>
+
+#include "sd-dhcp-lease.h"
+#include "sd-dhcp-option.h"
+#include "sd-event.h"
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+enum {
+        SD_DHCP_CLIENT_EVENT_STOP               = 0,
+        SD_DHCP_CLIENT_EVENT_IP_ACQUIRE         = 1,
+        SD_DHCP_CLIENT_EVENT_IP_CHANGE          = 2,
+        SD_DHCP_CLIENT_EVENT_EXPIRED            = 3,
+        SD_DHCP_CLIENT_EVENT_RENEW              = 4,
+        SD_DHCP_CLIENT_EVENT_SELECTING          = 5,
+        SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE  = 6, /* Sent when we have not received a reply after the first few attempts.
+                                                      * The client may want to start acquiring link-local addresses. */
+};
+
+enum {
+        SD_DHCP_OPTION_PAD                         = 0,
+        SD_DHCP_OPTION_SUBNET_MASK                 = 1,
+        SD_DHCP_OPTION_TIME_OFFSET                 = 2,
+        SD_DHCP_OPTION_ROUTER                      = 3,
+        SD_DHCP_OPTION_DOMAIN_NAME_SERVER          = 6,
+        SD_DHCP_OPTION_LPR_SERVER                  = 9,
+        SD_DHCP_OPTION_HOST_NAME                   = 12,
+        SD_DHCP_OPTION_BOOT_FILE_SIZE              = 13,
+        SD_DHCP_OPTION_DOMAIN_NAME                 = 15,
+        SD_DHCP_OPTION_ROOT_PATH                   = 17,
+        SD_DHCP_OPTION_ENABLE_IP_FORWARDING        = 19,
+        SD_DHCP_OPTION_ENABLE_IP_FORWARDING_NL     = 20,
+        SD_DHCP_OPTION_POLICY_FILTER               = 21,
+        SD_DHCP_OPTION_INTERFACE_MDR               = 22,
+        SD_DHCP_OPTION_INTERFACE_TTL               = 23,
+        SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24,
+        SD_DHCP_OPTION_INTERFACE_MTU               = 26,
+        SD_DHCP_OPTION_BROADCAST                   = 28,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_ROUTER_DISCOVER             = 31,
+        SD_DHCP_OPTION_STATIC_ROUTE                = 33,
+        SD_DHCP_OPTION_NTP_SERVER                  = 42,
+        SD_DHCP_OPTION_VENDOR_SPECIFIC             = 43,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_NETBIOS_NAMESERVER          = 44,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_NETBIOS_NODETYPE            = 46,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_NETBIOS_SCOPE               = 47,
+        SD_DHCP_OPTION_REQUESTED_IP_ADDRESS        = 50,
+        SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME       = 51,
+        SD_DHCP_OPTION_OVERLOAD                    = 52,
+        SD_DHCP_OPTION_MESSAGE_TYPE                = 53,
+        SD_DHCP_OPTION_SERVER_IDENTIFIER           = 54,
+        SD_DHCP_OPTION_PARAMETER_REQUEST_LIST      = 55,
+        SD_DHCP_OPTION_ERROR_MESSAGE               = 56,
+        SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE        = 57,
+        SD_DHCP_OPTION_RENEWAL_T1_TIME             = 58,
+        SD_DHCP_OPTION_REBINDING_T2_TIME           = 59,
+        SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER     = 60,
+        SD_DHCP_OPTION_CLIENT_IDENTIFIER           = 61,
+        SD_DHCP_OPTION_SMTP_SERVER                 = 69,
+        SD_DHCP_OPTION_POP3_SERVER                 = 70,
+        SD_DHCP_OPTION_USER_CLASS                  = 77,
+        SD_DHCP_OPTION_FQDN                        = 81,
+        SD_DHCP_OPTION_NEW_POSIX_TIMEZONE          = 100,
+        SD_DHCP_OPTION_NEW_TZDB_TIMEZONE           = 101,
+        SD_DHCP_OPTION_DOMAIN_SEARCH_LIST          = 119,
+        SD_DHCP_OPTION_SIP_SERVER                  = 120,
+        SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE      = 121,
+        SD_DHCP_OPTION_MUD_URL                     = 161,
+        SD_DHCP_OPTION_PRIVATE_BASE                = 224,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY = 252,
+        SD_DHCP_OPTION_PRIVATE_LAST                = 254,
+        SD_DHCP_OPTION_END                         = 255,
+};
+
+typedef struct sd_dhcp_client sd_dhcp_client;
+
+typedef int (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata);
+int sd_dhcp_client_set_callback(
+                sd_dhcp_client *client,
+                sd_dhcp_client_callback_t cb,
+                void *userdata);
+
+int sd_dhcp_client_set_request_option(
+                sd_dhcp_client *client,
+                uint8_t option);
+int sd_dhcp_client_set_request_address(
+                sd_dhcp_client *client,
+                const struct in_addr *last_address);
+int sd_dhcp_client_set_request_broadcast(
+                sd_dhcp_client *client,
+                int broadcast);
+int sd_dhcp_client_set_ifindex(
+                sd_dhcp_client *client,
+                int interface_index);
+int sd_dhcp_client_set_mac(
+                sd_dhcp_client *client,
+                const uint8_t *addr,
+                const uint8_t *bcast_addr,
+                size_t addr_len,
+                uint16_t arp_type);
+int sd_dhcp_client_set_client_id(
+                sd_dhcp_client *client,
+                uint8_t type,
+                const uint8_t *data,
+                size_t data_len);
+int sd_dhcp_client_set_iaid_duid(
+                sd_dhcp_client *client,
+                bool iaid_set,
+                uint32_t iaid,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len);
+int sd_dhcp_client_set_iaid_duid_llt(
+                sd_dhcp_client *client,
+                bool iaid_set,
+                uint32_t iaid,
+                uint64_t llt_time);
+int sd_dhcp_client_set_duid(
+                sd_dhcp_client *client,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len);
+int sd_dhcp_client_set_duid_llt(
+                sd_dhcp_client *client,
+                uint64_t llt_time);
+int sd_dhcp_client_get_client_id(
+                sd_dhcp_client *client,
+                uint8_t *type,
+                const uint8_t **data,
+                size_t *data_len);
+int sd_dhcp_client_set_mtu(
+                sd_dhcp_client *client,
+                uint32_t mtu);
+int sd_dhcp_client_set_max_attempts(
+                sd_dhcp_client *client,
+                uint64_t attempt);
+int sd_dhcp_client_set_client_port(
+                sd_dhcp_client *client,
+                uint16_t port);
+int sd_dhcp_client_set_hostname(
+                sd_dhcp_client *client,
+                const char *hostname);
+int sd_dhcp_client_set_vendor_class_identifier(
+                sd_dhcp_client *client,
+                const char *vci);
+int sd_dhcp_client_set_mud_url(
+                sd_dhcp_client *client,
+                const char *mudurl);
+int sd_dhcp_client_set_user_class(
+                sd_dhcp_client *client,
+                const char* const *user_class);
+int sd_dhcp_client_get_lease(
+                sd_dhcp_client *client,
+                sd_dhcp_lease **ret);
+int sd_dhcp_client_set_service_type(
+                sd_dhcp_client *client,
+                int type);
+int sd_dhcp_client_set_fallback_lease_lifetime(
+                sd_dhcp_client *client,
+                uint32_t fallback_lease_lifetime);
+
+int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v);
+int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v);
+
+int sd_dhcp_client_stop(sd_dhcp_client *client);
+int sd_dhcp_client_start(sd_dhcp_client *client);
+int sd_dhcp_client_send_release(sd_dhcp_client *client);
+int sd_dhcp_client_send_decline(sd_dhcp_client *client);
+int sd_dhcp_client_send_renew(sd_dhcp_client *client);
+
+sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client);
+sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client);
+
+/* NOTE: anonymize parameter is used to initialize PRL memory with different
+ * options when using RFC7844 Anonymity Profiles */
+int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize);
+
+int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret);
+
+int sd_dhcp_client_attach_event(
+                sd_dhcp_client *client,
+                sd_event *event,
+                int64_t priority);
+int sd_dhcp_client_detach_event(sd_dhcp_client *client);
+sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client, sd_dhcp_client_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-dhcp-lease.h b/src/core/systemd/src/systemd/sd-dhcp-lease.h
new file mode 100644
index 00000000..c255a1f9
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-dhcp-lease.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosddhcpleasehfoo
+#define foosddhcpleasehfoo
+
+/***
+  Copyright © 2013 Intel Corporation. All rights reserved.
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_dhcp_lease sd_dhcp_lease;
+typedef struct sd_dhcp_route sd_dhcp_route;
+
+sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease);
+sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease);
+
+typedef enum sd_dhcp_lease_server_type {
+        SD_DHCP_LEASE_DNS,
+        SD_DHCP_LEASE_NTP,
+        SD_DHCP_LEASE_SIP,
+        SD_DHCP_LEASE_POP3,
+        SD_DHCP_LEASE_SMTP,
+        SD_DHCP_LEASE_LPR,
+        _SD_DHCP_LEASE_SERVER_TYPE_MAX,
+        _SD_DHCP_LEASE_SERVER_TYPE_INVALID = -1,
+} sd_dhcp_lease_server_type;
+
+int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr);
+int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime);
+int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1);
+int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2);
+int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr);
+int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr);
+int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr);
+int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr);
+int sd_dhcp_lease_get_servers(sd_dhcp_lease *lease, sd_dhcp_lease_server_type what, const struct in_addr **addr);
+int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu);
+int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname);
+int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains);
+int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname);
+int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path);
+int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes);
+int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len);
+int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len);
+int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone);
+
+int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination);
+int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length);
+int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway);
+int sd_dhcp_route_get_option(sd_dhcp_route *route);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-dhcp-option.h b/src/core/systemd/src/systemd/sd-dhcp-option.h
new file mode 100644
index 00000000..71aa479b
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-dhcp-option.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosddhcpoptionhfoo
+#define foosddhcpoptionhfoo
+
+/***
+  Copyright © 2013 Intel Corporation. All rights reserved.
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_dhcp_option sd_dhcp_option;
+
+int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret);
+sd_dhcp_option *sd_dhcp_option_ref(sd_dhcp_option *ra);
+sd_dhcp_option *sd_dhcp_option_unref(sd_dhcp_option *ra);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_option, sd_dhcp_option_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-dhcp6-client.h b/src/core/systemd/src/systemd/sd-dhcp6-client.h
new file mode 100644
index 00000000..75ee27d6
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-dhcp6-client.h
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosddhcp6clienthfoo
+#define foosddhcp6clienthfoo
+
+/***
+  Copyright © 2014 Intel Corporation. All rights reserved.
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <net/ethernet.h>
+#include <sys/types.h>
+
+#include "sd-dhcp6-lease.h"
+#include "sd-dhcp6-option.h"
+#include "sd-event.h"
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+enum {
+        SD_DHCP6_CLIENT_EVENT_STOP                      = 0,
+        SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE             = 10,
+        SD_DHCP6_CLIENT_EVENT_RETRANS_MAX               = 11,
+        SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE                = 12,
+        SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST       = 13,
+};
+
+enum {
+        SD_DHCP6_OPTION_CLIENTID                   = 1,
+        SD_DHCP6_OPTION_SERVERID                   = 2,
+        SD_DHCP6_OPTION_IA_NA                      = 3,
+        SD_DHCP6_OPTION_IA_TA                      = 4,
+        SD_DHCP6_OPTION_IAADDR                     = 5,
+        SD_DHCP6_OPTION_ORO                        = 6,
+        SD_DHCP6_OPTION_PREFERENCE                 = 7,
+        SD_DHCP6_OPTION_ELAPSED_TIME               = 8,
+        SD_DHCP6_OPTION_RELAY_MSG                  = 9,
+        /* option code 10 is unassigned */
+        SD_DHCP6_OPTION_AUTH                       = 11,
+        SD_DHCP6_OPTION_UNICAST                    = 12,
+        SD_DHCP6_OPTION_STATUS_CODE                = 13,
+        SD_DHCP6_OPTION_RAPID_COMMIT               = 14,
+        SD_DHCP6_OPTION_USER_CLASS                 = 15,
+        SD_DHCP6_OPTION_VENDOR_CLASS               = 16,
+        SD_DHCP6_OPTION_VENDOR_OPTS                = 17,
+        SD_DHCP6_OPTION_INTERFACE_ID               = 18,
+        SD_DHCP6_OPTION_RECONF_MSG                 = 19,
+        SD_DHCP6_OPTION_RECONF_ACCEPT              = 20,
+
+        SD_DHCP6_OPTION_DNS_SERVERS                = 23,  /* RFC 3646 */
+        SD_DHCP6_OPTION_DOMAIN_LIST                = 24,  /* RFC 3646 */
+        SD_DHCP6_OPTION_IA_PD                      = 25,  /* RFC 3633, prefix delegation */
+        SD_DHCP6_OPTION_IA_PD_PREFIX               = 26,  /* RFC 3633, prefix delegation */
+
+        SD_DHCP6_OPTION_SNTP_SERVERS               = 31,  /* RFC 4075, deprecated */
+        SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME   = 32,  /* RFC 8415, sec. 21.23 */
+
+        /* option code 35 is unassigned */
+
+        SD_DHCP6_OPTION_FQDN                       = 39,  /* RFC 4704 */
+
+        SD_DHCP6_OPTION_NTP_SERVER                 = 56,  /* RFC 5908 */
+        SD_DHCP6_OPTION_MUD_URL                    = 112, /* RFC 8250 */
+
+        /* option codes 89-142 are unassigned */
+        /* option codes 144-65535 are unassigned */
+};
+
+typedef struct sd_dhcp6_client sd_dhcp6_client;
+
+typedef void (*sd_dhcp6_client_callback_t)(sd_dhcp6_client *client, int event, void *userdata);
+int sd_dhcp6_client_set_callback(
+                sd_dhcp6_client *client,
+                sd_dhcp6_client_callback_t cb,
+                void *userdata);
+
+int sd_dhcp6_client_set_ifindex(
+                sd_dhcp6_client *client,
+                int interface_index);
+int sd_dhcp6_client_set_local_address(
+                sd_dhcp6_client *client,
+                const struct in6_addr *local_address);
+int sd_dhcp6_client_set_mac(
+                sd_dhcp6_client *client,
+                const uint8_t *addr,
+                size_t addr_len,
+                uint16_t arp_type);
+int sd_dhcp6_client_set_duid(
+                sd_dhcp6_client *client,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len);
+int sd_dhcp6_client_set_duid_llt(
+                sd_dhcp6_client *client,
+                uint64_t llt_time);
+int sd_dhcp6_client_set_iaid(
+                sd_dhcp6_client *client,
+                uint32_t iaid);
+int sd_dhcp6_client_get_iaid(
+                sd_dhcp6_client *client,
+                uint32_t *iaid);
+int sd_dhcp6_client_duid_as_string(
+                sd_dhcp6_client *client,
+                char **duid);
+int sd_dhcp6_client_set_fqdn(
+                sd_dhcp6_client *client,
+                const char *fqdn);
+int sd_dhcp6_client_set_information_request(
+                sd_dhcp6_client *client,
+                int enabled);
+int sd_dhcp6_client_get_information_request(
+                sd_dhcp6_client *client,
+                int *enabled);
+int sd_dhcp6_client_set_request_option(
+                sd_dhcp6_client *client,
+                uint16_t option);
+int sd_dhcp6_client_set_request_mud_url(
+                sd_dhcp6_client *client,
+                const char *mudurl);
+int sd_dhcp6_client_set_request_user_class(
+                sd_dhcp6_client *client,
+                char** user_class);
+int sd_dhcp6_client_set_request_vendor_class(
+                sd_dhcp6_client *client,
+                char** vendor_class);
+int sd_dhcp6_client_set_prefix_delegation_hint(
+                sd_dhcp6_client *client,
+                uint8_t prefixlen,
+                const struct in6_addr *pd_address);
+int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client,
+                                          int *delegation);
+int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client,
+                                          int delegation);
+int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client,
+                                        int *request);
+int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client,
+                                        int request);
+int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client,
+                                       uint32_t transaction_id);
+int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client,
+                                      sd_dhcp6_option *v);
+
+int sd_dhcp6_client_get_lease(
+                sd_dhcp6_client *client,
+                sd_dhcp6_lease **ret);
+
+int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v);
+
+int sd_dhcp6_client_stop(sd_dhcp6_client *client);
+int sd_dhcp6_client_start(sd_dhcp6_client *client);
+int sd_dhcp6_client_is_running(sd_dhcp6_client *client);
+int sd_dhcp6_client_attach_event(
+                sd_dhcp6_client *client,
+                sd_event *event,
+                int64_t priority);
+int sd_dhcp6_client_detach_event(sd_dhcp6_client *client);
+sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client);
+sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client);
+sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client);
+int sd_dhcp6_client_new(sd_dhcp6_client **ret);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_client, sd_dhcp6_client_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-dhcp6-lease.h b/src/core/systemd/src/systemd/sd-dhcp6-lease.h
new file mode 100644
index 00000000..f77b31ac
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-dhcp6-lease.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosddhcp6leasehfoo
+#define foosddhcp6leasehfoo
+
+/***
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <netinet/in.h>
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_dhcp6_lease sd_dhcp6_lease;
+
+void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease);
+int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease,
+                               struct in6_addr *addr,
+                               uint32_t *lifetime_preferred,
+                               uint32_t *lifetime_valid);
+void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease);
+int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix,
+                          uint8_t *prefix_len,
+                          uint32_t *lifetime_preferred,
+                          uint32_t *lifetime_valid);
+
+int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs);
+int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains);
+int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **addrs);
+int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn);
+int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn);
+
+sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease);
+sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_lease, sd_dhcp6_lease_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-dhcp6-option.h b/src/core/systemd/src/systemd/sd-dhcp6-option.h
new file mode 100644
index 00000000..ddb2c7ce
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-dhcp6-option.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosddhcp6optionhfoo
+#define foosddhcp6optionhfoo
+
+/***
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_dhcp6_option sd_dhcp6_option;
+
+int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret);
+sd_dhcp6_option *sd_dhcp6_option_ref(sd_dhcp6_option *ra);
+sd_dhcp6_option *sd_dhcp6_option_unref(sd_dhcp6_option *ra);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_option, sd_dhcp6_option_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-event.h b/src/core/systemd/src/systemd/sd-event.h
new file mode 100644
index 00000000..2ae2a0da
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-event.h
@@ -0,0 +1,176 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosdeventhfoo
+#define foosdeventhfoo
+
+/***
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <signal.h>
+#include <sys/epoll.h>
+#include <sys/inotify.h>
+#include <sys/signalfd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+
+#include "_sd-common.h"
+
+/*
+  Why is this better than pure epoll?
+
+  - Supports event source prioritization
+  - Scales better with a large number of time events because it does not require one timerfd each
+  - Automatically tries to coalesce timer events system-wide
+  - Handles signals, child PIDs, inotify events
+  - Supports systemd-style automatic watchdog event generation
+*/
+
+_SD_BEGIN_DECLARATIONS;
+
+#define SD_EVENT_DEFAULT ((sd_event *) 1)
+
+typedef struct sd_event sd_event;
+typedef struct sd_event_source sd_event_source;
+
+enum {
+        SD_EVENT_OFF = 0,
+        SD_EVENT_ON = 1,
+        SD_EVENT_ONESHOT = -1
+};
+
+enum {
+        SD_EVENT_INITIAL,
+        SD_EVENT_ARMED,
+        SD_EVENT_PENDING,
+        SD_EVENT_RUNNING,
+        SD_EVENT_EXITING,
+        SD_EVENT_FINISHED,
+        SD_EVENT_PREPARING
+};
+
+enum {
+        /* And everything in-between and outside is good too */
+        SD_EVENT_PRIORITY_IMPORTANT = -100,
+        SD_EVENT_PRIORITY_NORMAL = 0,
+        SD_EVENT_PRIORITY_IDLE = 100
+};
+
+typedef int (*sd_event_handler_t)(sd_event_source *s, void *userdata);
+typedef int (*sd_event_io_handler_t)(sd_event_source *s, int fd, uint32_t revents, void *userdata);
+typedef int (*sd_event_time_handler_t)(sd_event_source *s, uint64_t usec, void *userdata);
+typedef int (*sd_event_signal_handler_t)(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata);
+#if defined _GNU_SOURCE || (defined _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 199309L)
+typedef int (*sd_event_child_handler_t)(sd_event_source *s, const siginfo_t *si, void *userdata);
+#else
+typedef void* sd_event_child_handler_t;
+#endif
+typedef int (*sd_event_inotify_handler_t)(sd_event_source *s, const struct inotify_event *event, void *userdata);
+typedef _sd_destroy_t sd_event_destroy_t;
+
+int sd_event_default(sd_event **e);
+
+int sd_event_new(sd_event **e);
+sd_event* sd_event_ref(sd_event *e);
+sd_event* sd_event_unref(sd_event *e);
+
+int sd_event_add_io(sd_event *e, sd_event_source **s, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata);
+int sd_event_add_time(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata);
+int sd_event_add_time_relative(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata);
+int sd_event_add_signal(sd_event *e, sd_event_source **s, int sig, sd_event_signal_handler_t callback, void *userdata);
+int sd_event_add_child(sd_event *e, sd_event_source **s, pid_t pid, int options, sd_event_child_handler_t callback, void *userdata);
+int sd_event_add_child_pidfd(sd_event *e, sd_event_source **s, int pidfd, int options, sd_event_child_handler_t callback, void *userdata);
+int sd_event_add_inotify(sd_event *e, sd_event_source **s, const char *path, uint32_t mask, sd_event_inotify_handler_t callback, void *userdata);
+int sd_event_add_defer(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
+int sd_event_add_post(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
+int sd_event_add_exit(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
+
+int sd_event_prepare(sd_event *e);
+int sd_event_wait(sd_event *e, uint64_t usec);
+int sd_event_dispatch(sd_event *e);
+int sd_event_run(sd_event *e, uint64_t usec);
+int sd_event_loop(sd_event *e);
+int sd_event_exit(sd_event *e, int code);
+
+int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec);
+
+int sd_event_get_fd(sd_event *e);
+int sd_event_get_state(sd_event *e);
+int sd_event_get_tid(sd_event *e, pid_t *tid);
+int sd_event_get_exit_code(sd_event *e, int *code);
+int sd_event_set_watchdog(sd_event *e, int b);
+int sd_event_get_watchdog(sd_event *e);
+int sd_event_get_iteration(sd_event *e, uint64_t *ret);
+
+sd_event_source* sd_event_source_ref(sd_event_source *s);
+sd_event_source* sd_event_source_unref(sd_event_source *s);
+sd_event_source* sd_event_source_disable_unref(sd_event_source *s);
+
+sd_event *sd_event_source_get_event(sd_event_source *s);
+void* sd_event_source_get_userdata(sd_event_source *s);
+void* sd_event_source_set_userdata(sd_event_source *s, void *userdata);
+
+int sd_event_source_set_description(sd_event_source *s, const char *description);
+int sd_event_source_get_description(sd_event_source *s, const char **description);
+int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback);
+int sd_event_source_get_pending(sd_event_source *s);
+int sd_event_source_get_priority(sd_event_source *s, int64_t *priority);
+int sd_event_source_set_priority(sd_event_source *s, int64_t priority);
+int sd_event_source_get_enabled(sd_event_source *s, int *enabled);
+int sd_event_source_set_enabled(sd_event_source *s, int enabled);
+int sd_event_source_get_io_fd(sd_event_source *s);
+int sd_event_source_set_io_fd(sd_event_source *s, int fd);
+int sd_event_source_get_io_fd_own(sd_event_source *s);
+int sd_event_source_set_io_fd_own(sd_event_source *s, int own);
+int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events);
+int sd_event_source_set_io_events(sd_event_source *s, uint32_t events);
+int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents);
+int sd_event_source_get_time(sd_event_source *s, uint64_t *usec);
+int sd_event_source_set_time(sd_event_source *s, uint64_t usec);
+int sd_event_source_set_time_relative(sd_event_source *s, uint64_t usec);
+int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec);
+int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec);
+int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock);
+int sd_event_source_get_signal(sd_event_source *s);
+int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid);
+int sd_event_source_get_child_pidfd(sd_event_source *s);
+int sd_event_source_get_child_pidfd_own(sd_event_source *s);
+int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own);
+int sd_event_source_get_child_process_own(sd_event_source *s);
+int sd_event_source_set_child_process_own(sd_event_source *s, int own);
+#if defined _GNU_SOURCE || (defined _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 199309L)
+int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo_t *si, unsigned flags);
+#else
+int sd_event_source_send_child_signal(sd_event_source *s, int sig, const void *si, unsigned flags);
+#endif
+int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret);
+int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback);
+int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret);
+int sd_event_source_get_floating(sd_event_source *s);
+int sd_event_source_set_floating(sd_event_source *s, int b);
+int sd_event_source_get_exit_on_failure(sd_event_source *s);
+int sd_event_source_set_exit_on_failure(sd_event_source *s, int b);
+int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval_usec, unsigned burst);
+int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval_usec, unsigned *ret_burst);
+int sd_event_source_is_ratelimited(sd_event_source *s);
+
+/* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_disable_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-id128.h b/src/core/systemd/src/systemd/sd-id128.h
new file mode 100644
index 00000000..02aa318a
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-id128.h
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosdid128hfoo
+#define foosdid128hfoo
+
+/***
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <string.h>
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+/* 128-bit ID APIs. See sd-id128(3) for more information. */
+
+typedef union sd_id128 sd_id128_t;
+
+union sd_id128 {
+        uint8_t bytes[16];
+        uint64_t qwords[2];
+};
+
+#define SD_ID128_STRING_MAX 33
+
+char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]);
+int sd_id128_from_string(const char *s, sd_id128_t *ret);
+
+int sd_id128_randomize(sd_id128_t *ret);
+
+int sd_id128_get_machine(sd_id128_t *ret);
+int sd_id128_get_boot(sd_id128_t *ret);
+int sd_id128_get_invocation(sd_id128_t *ret);
+
+int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret);
+int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret);
+
+#define SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \
+        { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \
+                     0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }}
+
+#define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \
+        ((const sd_id128_t) SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15))
+
+/* Note that SD_ID128_FORMAT_VAL will evaluate the passed argument 16
+ * times. It is hence not a good idea to call this macro with an
+ * expensive function as parameter or an expression with side
+ * effects */
+
+#define SD_ID128_FORMAT_STR "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+#define SD_ID128_FORMAT_VAL(x) (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15]
+
+/* Like SD_ID128_FORMAT_STR, but formats as UUID, not in plain format */
+#define SD_ID128_UUID_FORMAT_STR "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
+
+#define SD_ID128_CONST_STR(x)                                           \
+        ((const char[SD_ID128_STRING_MAX]) {                            \
+                ((x).bytes[0] >> 4) >= 10 ? 'a' + ((x).bytes[0] >> 4) - 10 : '0' + ((x).bytes[0] >> 4), \
+                ((x).bytes[0] & 15) >= 10 ? 'a' + ((x).bytes[0] & 15) - 10 : '0' + ((x).bytes[0] & 15), \
+                ((x).bytes[1] >> 4) >= 10 ? 'a' + ((x).bytes[1] >> 4) - 10 : '0' + ((x).bytes[1] >> 4), \
+                ((x).bytes[1] & 15) >= 10 ? 'a' + ((x).bytes[1] & 15) - 10 : '0' + ((x).bytes[1] & 15), \
+                ((x).bytes[2] >> 4) >= 10 ? 'a' + ((x).bytes[2] >> 4) - 10 : '0' + ((x).bytes[2] >> 4), \
+                ((x).bytes[2] & 15) >= 10 ? 'a' + ((x).bytes[2] & 15) - 10 : '0' + ((x).bytes[2] & 15), \
+                ((x).bytes[3] >> 4) >= 10 ? 'a' + ((x).bytes[3] >> 4) - 10 : '0' + ((x).bytes[3] >> 4), \
+                ((x).bytes[3] & 15) >= 10 ? 'a' + ((x).bytes[3] & 15) - 10 : '0' + ((x).bytes[3] & 15), \
+                ((x).bytes[4] >> 4) >= 10 ? 'a' + ((x).bytes[4] >> 4) - 10 : '0' + ((x).bytes[4] >> 4), \
+                ((x).bytes[4] & 15) >= 10 ? 'a' + ((x).bytes[4] & 15) - 10 : '0' + ((x).bytes[4] & 15), \
+                ((x).bytes[5] >> 4) >= 10 ? 'a' + ((x).bytes[5] >> 4) - 10 : '0' + ((x).bytes[5] >> 4), \
+                ((x).bytes[5] & 15) >= 10 ? 'a' + ((x).bytes[5] & 15) - 10 : '0' + ((x).bytes[5] & 15), \
+                ((x).bytes[6] >> 4) >= 10 ? 'a' + ((x).bytes[6] >> 4) - 10 : '0' + ((x).bytes[6] >> 4), \
+                ((x).bytes[6] & 15) >= 10 ? 'a' + ((x).bytes[6] & 15) - 10 : '0' + ((x).bytes[6] & 15), \
+                ((x).bytes[7] >> 4) >= 10 ? 'a' + ((x).bytes[7] >> 4) - 10 : '0' + ((x).bytes[7] >> 4), \
+                ((x).bytes[7] & 15) >= 10 ? 'a' + ((x).bytes[7] & 15) - 10 : '0' + ((x).bytes[7] & 15), \
+                ((x).bytes[8] >> 4) >= 10 ? 'a' + ((x).bytes[8] >> 4) - 10 : '0' + ((x).bytes[8] >> 4), \
+                ((x).bytes[8] & 15) >= 10 ? 'a' + ((x).bytes[8] & 15) - 10 : '0' + ((x).bytes[8] & 15), \
+                ((x).bytes[9] >> 4) >= 10 ? 'a' + ((x).bytes[9] >> 4) - 10 : '0' + ((x).bytes[9] >> 4), \
+                ((x).bytes[9] & 15) >= 10 ? 'a' + ((x).bytes[9] & 15) - 10 : '0' + ((x).bytes[9] & 15), \
+                ((x).bytes[10] >> 4) >= 10 ? 'a' + ((x).bytes[10] >> 4) - 10 : '0' + ((x).bytes[10] >> 4), \
+                ((x).bytes[10] & 15) >= 10 ? 'a' + ((x).bytes[10] & 15) - 10 : '0' + ((x).bytes[10] & 15), \
+                ((x).bytes[11] >> 4) >= 10 ? 'a' + ((x).bytes[11] >> 4) - 10 : '0' + ((x).bytes[11] >> 4), \
+                ((x).bytes[11] & 15) >= 10 ? 'a' + ((x).bytes[11] & 15) - 10 : '0' + ((x).bytes[11] & 15), \
+                ((x).bytes[12] >> 4) >= 10 ? 'a' + ((x).bytes[12] >> 4) - 10 : '0' + ((x).bytes[12] >> 4), \
+                ((x).bytes[12] & 15) >= 10 ? 'a' + ((x).bytes[12] & 15) - 10 : '0' + ((x).bytes[12] & 15), \
+                ((x).bytes[13] >> 4) >= 10 ? 'a' + ((x).bytes[13] >> 4) - 10 : '0' + ((x).bytes[13] >> 4), \
+                ((x).bytes[13] & 15) >= 10 ? 'a' + ((x).bytes[13] & 15) - 10 : '0' + ((x).bytes[13] & 15), \
+                ((x).bytes[14] >> 4) >= 10 ? 'a' + ((x).bytes[14] >> 4) - 10 : '0' + ((x).bytes[14] >> 4), \
+                ((x).bytes[14] & 15) >= 10 ? 'a' + ((x).bytes[14] & 15) - 10 : '0' + ((x).bytes[14] & 15), \
+                ((x).bytes[15] >> 4) >= 10 ? 'a' + ((x).bytes[15] >> 4) - 10 : '0' + ((x).bytes[15] >> 4), \
+                ((x).bytes[15] & 15) >= 10 ? 'a' + ((x).bytes[15] & 15) - 10 : '0' + ((x).bytes[15] & 15), \
+                0 })
+
+#define SD_ID128_MAKE_STR(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \
+        #a #b #c #d #e #f #g #h #i #j #k #l #m #n #o #p
+
+_sd_pure_ static __inline__ int sd_id128_equal(sd_id128_t a, sd_id128_t b) {
+        return memcmp(&a, &b, 16) == 0;
+}
+
+_sd_pure_ static __inline__ int sd_id128_is_null(sd_id128_t a) {
+        return a.qwords[0] == 0 && a.qwords[1] == 0;
+}
+
+_sd_pure_ static __inline__ int sd_id128_is_allf(sd_id128_t a) {
+        return a.qwords[0] == UINT64_C(0xFFFFFFFFFFFFFFFF) && a.qwords[1] == UINT64_C(0xFFFFFFFFFFFFFFFF);
+}
+
+#define SD_ID128_NULL ((const sd_id128_t) { .qwords = { 0, 0 }})
+#define SD_ID128_ALLF ((const sd_id128_t) { .qwords = { UINT64_C(0xFFFFFFFFFFFFFFFF), UINT64_C(0xFFFFFFFFFFFFFFFF) }})
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-ipv4acd.h b/src/core/systemd/src/systemd/sd-ipv4acd.h
new file mode 100644
index 00000000..2809d874
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-ipv4acd.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosdipv4acdfoo
+#define foosdipv4acdfoo
+
+/***
+  Copyright © 2014 Axis Communications AB. All rights reserved.
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+
+#include "sd-event.h"
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+enum {
+        SD_IPV4ACD_EVENT_STOP           = 0,
+        SD_IPV4ACD_EVENT_BIND           = 1,
+        SD_IPV4ACD_EVENT_CONFLICT       = 2,
+};
+
+typedef struct sd_ipv4acd sd_ipv4acd;
+typedef void (*sd_ipv4acd_callback_t)(sd_ipv4acd *acd, int event, void *userdata);
+
+int sd_ipv4acd_detach_event(sd_ipv4acd *acd);
+int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority);
+int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address);
+int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata);
+int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr);
+int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int interface_index);
+int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd);
+const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd);
+int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address);
+int sd_ipv4acd_is_running(sd_ipv4acd *acd);
+int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts);
+int sd_ipv4acd_stop(sd_ipv4acd *acd);
+sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *acd);
+sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd);
+int sd_ipv4acd_new(sd_ipv4acd **ret);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4acd, sd_ipv4acd_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-ipv4ll.h b/src/core/systemd/src/systemd/sd-ipv4ll.h
new file mode 100644
index 00000000..aa4d174e
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-ipv4ll.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosdipv4llfoo
+#define foosdipv4llfoo
+
+/***
+  Copyright © 2014 Axis Communications AB. All rights reserved.
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <net/ethernet.h>
+#include <netinet/in.h>
+
+#include "sd-event.h"
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+enum {
+        SD_IPV4LL_EVENT_STOP            = 0,
+        SD_IPV4LL_EVENT_BIND            = 1,
+        SD_IPV4LL_EVENT_CONFLICT        = 2,
+};
+
+typedef struct sd_ipv4ll sd_ipv4ll;
+typedef void (*sd_ipv4ll_callback_t)(sd_ipv4ll *ll, int event, void *userdata);
+
+int sd_ipv4ll_detach_event(sd_ipv4ll *ll);
+int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority);
+int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address);
+int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata);
+int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr);
+int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int interface_index);
+int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll);
+const char *sd_ipv4ll_get_ifname(sd_ipv4ll *ll);
+int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address);
+int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed);
+int sd_ipv4ll_is_running(sd_ipv4ll *ll);
+int sd_ipv4ll_restart(sd_ipv4ll *ll);
+int sd_ipv4ll_start(sd_ipv4ll *ll);
+int sd_ipv4ll_stop(sd_ipv4ll *ll);
+sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll);
+sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll);
+int sd_ipv4ll_new(sd_ipv4ll **ret);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4ll, sd_ipv4ll_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-lldp.h b/src/core/systemd/src/systemd/sd-lldp.h
new file mode 100644
index 00000000..f551f6b4
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-lldp.h
@@ -0,0 +1,194 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosdlldphfoo
+#define foosdlldphfoo
+
+/***
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <net/ethernet.h>
+#include <sys/types.h>
+
+#include "sd-event.h"
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+/* IEEE 802.1AB-2009 Clause 8: TLV Types */
+enum {
+        SD_LLDP_TYPE_END                 = 0,
+        SD_LLDP_TYPE_CHASSIS_ID          = 1,
+        SD_LLDP_TYPE_PORT_ID             = 2,
+        SD_LLDP_TYPE_TTL                 = 3,
+        SD_LLDP_TYPE_PORT_DESCRIPTION    = 4,
+        SD_LLDP_TYPE_SYSTEM_NAME         = 5,
+        SD_LLDP_TYPE_SYSTEM_DESCRIPTION  = 6,
+        SD_LLDP_TYPE_SYSTEM_CAPABILITIES = 7,
+        SD_LLDP_TYPE_MGMT_ADDRESS        = 8,
+        SD_LLDP_TYPE_PRIVATE             = 127,
+};
+
+/* IEEE 802.1AB-2009 Clause 8.5.2: Chassis subtypes */
+enum {
+        SD_LLDP_CHASSIS_SUBTYPE_RESERVED            = 0,
+        SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT   = 1,
+        SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS     = 2,
+        SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT      = 3,
+        SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS         = 4,
+        SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS     = 5,
+        SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME      = 6,
+        SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED    = 7,
+};
+
+/* IEEE 802.1AB-2009 Clause 8.5.3: Port subtype */
+enum {
+        SD_LLDP_PORT_SUBTYPE_RESERVED         = 0,
+        SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS  = 1,
+        SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT   = 2,
+        SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS      = 3,
+        SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS  = 4,
+        SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME   = 5,
+        SD_LLDP_PORT_SUBTYPE_AGENT_CIRCUIT_ID = 6,
+        SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7,
+};
+
+/* IEEE 802.1AB-2009 Clause 8.5.8: System capabilities */
+enum {
+        SD_LLDP_SYSTEM_CAPABILITIES_OTHER    = 1 << 0,
+        SD_LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1,
+        SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE   = 1 << 2,
+        SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP  = 1 << 3,
+        SD_LLDP_SYSTEM_CAPABILITIES_ROUTER   = 1 << 4,
+        SD_LLDP_SYSTEM_CAPABILITIES_PHONE    = 1 << 5,
+        SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS   = 1 << 6,
+        SD_LLDP_SYSTEM_CAPABILITIES_STATION  = 1 << 7,
+        SD_LLDP_SYSTEM_CAPABILITIES_CVLAN    = 1 << 8,
+        SD_LLDP_SYSTEM_CAPABILITIES_SVLAN    = 1 << 9,
+        SD_LLDP_SYSTEM_CAPABILITIES_TPMR     = 1 << 10,
+};
+
+#define SD_LLDP_SYSTEM_CAPABILITIES_ALL ((uint16_t) -1)
+
+#define SD_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS                         \
+        ((uint16_t)                                                     \
+         (SD_LLDP_SYSTEM_CAPABILITIES_REPEATER|                         \
+          SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE|                           \
+          SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP|                          \
+          SD_LLDP_SYSTEM_CAPABILITIES_ROUTER|                           \
+          SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS|                           \
+          SD_LLDP_SYSTEM_CAPABILITIES_CVLAN|                            \
+          SD_LLDP_SYSTEM_CAPABILITIES_SVLAN|                            \
+          SD_LLDP_SYSTEM_CAPABILITIES_TPMR))
+
+#define SD_LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 }
+#define SD_LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f }
+
+#define SD_LLDP_OUI_MUD   (uint8_t[]) { 0x00, 0x00, 0x5E }
+#define SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION  0x01
+
+/* IEEE 802.1AB-2009 Annex E */
+enum {
+        SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID          = 1,
+        SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID = 2,
+        SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME             = 3,
+        SD_LLDP_OUI_802_1_SUBTYPE_PROTOCOL_IDENTITY     = 4,
+        SD_LLDP_OUI_802_1_SUBTYPE_VID_USAGE_DIGEST      = 5,
+        SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID        = 6,
+        SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION      = 7,
+};
+
+/* IEEE 802.1AB-2009 Annex F */
+enum {
+        SD_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS = 1,
+        SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI         = 2,
+        SD_LLDP_OUI_802_3_SUBTYPE_LINK_AGGREGATION      = 3,
+        SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE    = 4,
+};
+
+typedef struct sd_lldp sd_lldp;
+typedef struct sd_lldp_neighbor sd_lldp_neighbor;
+
+typedef enum sd_lldp_event {
+        SD_LLDP_EVENT_ADDED,
+        SD_LLDP_EVENT_REMOVED,
+        SD_LLDP_EVENT_UPDATED,
+        SD_LLDP_EVENT_REFRESHED,
+        _SD_LLDP_EVENT_MAX,
+        _SD_LLDP_EVENT_INVALID = -1,
+} sd_lldp_event;
+
+typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata);
+
+int sd_lldp_new(sd_lldp **ret);
+sd_lldp* sd_lldp_ref(sd_lldp *lldp);
+sd_lldp* sd_lldp_unref(sd_lldp *lldp);
+
+int sd_lldp_start(sd_lldp *lldp);
+int sd_lldp_stop(sd_lldp *lldp);
+
+int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority);
+int sd_lldp_detach_event(sd_lldp *lldp);
+sd_event *sd_lldp_get_event(sd_lldp *lldp);
+
+int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata);
+int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex);
+
+/* Controls how much and what to store in the neighbors database */
+int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t n);
+int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask);
+int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *address);
+
+int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***neighbors);
+
+int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size);
+sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n);
+sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n);
+
+/* Access to LLDP frame metadata */
+int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address);
+int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address);
+int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret);
+int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
+
+/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */
+int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
+int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
+int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec);
+int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
+int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
+
+/* Low-level, iterative TLV access. This is for everything else, it iteratively goes through all available TLVs
+ * (including the ones covered with the calls above), and allows multiple TLVs for the same fields. */
+int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n);
+int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n);
+int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type);
+int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type);
+int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype);
+int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype);
+int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp, sd_lldp_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_neighbor, sd_lldp_neighbor_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/core/systemd/src/systemd/sd-ndisc.h b/src/core/systemd/src/systemd/sd-ndisc.h
new file mode 100644
index 00000000..c0e37899
--- /dev/null
+++ b/src/core/systemd/src/systemd/sd-ndisc.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosdndiscfoo
+#define foosdndiscfoo
+
+/***
+  Copyright © 2014 Intel Corporation. All rights reserved.
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+
+#include "sd-event.h"
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+/* Neighbor Discovery Options, RFC 4861, Section 4.6 and
+ * https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5 */
+enum {
+        SD_NDISC_OPTION_SOURCE_LL_ADDRESS  = 1,
+        SD_NDISC_OPTION_TARGET_LL_ADDRESS  = 2,
+        SD_NDISC_OPTION_PREFIX_INFORMATION = 3,
+        SD_NDISC_OPTION_MTU                = 5,
+        SD_NDISC_OPTION_ROUTE_INFORMATION  = 24,
+        SD_NDISC_OPTION_RDNSS              = 25,
+        SD_NDISC_OPTION_FLAGS_EXTENSION    = 26,
+        SD_NDISC_OPTION_DNSSL              = 31,
+        SD_NDISC_OPTION_CAPTIVE_PORTAL     = 37,
+};
+
+/* Route preference, RFC 4191, Section 2.1 */
+enum {
+        SD_NDISC_PREFERENCE_LOW    = 3U,
+        SD_NDISC_PREFERENCE_MEDIUM = 0U,
+        SD_NDISC_PREFERENCE_HIGH   = 1U,
+};
+
+typedef struct sd_ndisc sd_ndisc;
+typedef struct sd_ndisc_router sd_ndisc_router;
+
+typedef enum sd_ndisc_event {
+        SD_NDISC_EVENT_TIMEOUT,
+        SD_NDISC_EVENT_ROUTER,
+        _SD_NDISC_EVENT_MAX,
+        _SD_NDISC_EVENT_INVALID = -1,
+} sd_ndisc_event;
+
+typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata);
+
+int sd_ndisc_new(sd_ndisc **ret);
+sd_ndisc *sd_ndisc_ref(sd_ndisc *nd);
+sd_ndisc *sd_ndisc_unref(sd_ndisc *nd);
+
+int sd_ndisc_start(sd_ndisc *nd);
+int sd_ndisc_stop(sd_ndisc *nd);
+
+int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority);
+int sd_ndisc_detach_event(sd_ndisc *nd);
+sd_event *sd_ndisc_get_event(sd_ndisc *nd);
+
+int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t cb, void *userdata);
+int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index);
+int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr);
+
+int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *ret);
+int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret);
+
+int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size);
+sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt);
+sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt);
+
+int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr);
+int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret);
+int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size);
+
+int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret);
+int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags);
+int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret);
+int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime);
+int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret);
+
+/* Generic option access */
+int sd_ndisc_router_option_rewind(sd_ndisc_router *rt);
+int sd_ndisc_router_option_next(sd_ndisc_router *rt);
+int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret);
+int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type);
+int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size);
+
+/* Specific option access: SD_NDISC_OPTION_PREFIX_INFORMATION */
+int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret);
+int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret);
+int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret);
+int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr);
+int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen);
+
+/* Specific option access: SD_NDISC_OPTION_ROUTE_INFORMATION */
+int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret);
+int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr);
+int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen);
+int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret);
+
+/* Specific option access: SD_NDISC_OPTION_RDNSS */
+int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret);
+int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret);
+
+/* Specific option access: SD_NDISC_OPTION_DNSSL */
+int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret);
+int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif