summary refs log tree commit diff
path: root/src/libnm-systemd-core
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2024-05-05 00:07:30 +0200
committerMichael Biebl <biebl@debian.org>2024-05-05 00:07:30 +0200
commit34bb501be08aa2b313d88e67d6e0a7e0a3f9cfa6 (patch)
tree4e6220877828be4c6f261de09ec0cb2d80e32389 /src/libnm-systemd-core
parentbba2e4b4de668db525cbfdfc35292e5a0b51671a (diff)
New upstream version 1.47.90 upstream/1.47.90
Diffstat (limited to 'src/libnm-systemd-core')
-rw-r--r--src/libnm-systemd-core/README.md23
-rw-r--r--src/libnm-systemd-core/meson.build6
-rw-r--r--src/libnm-systemd-core/sd-adapt-core/netif-util.c221
-rw-r--r--src/libnm-systemd-core/sd-adapt-core/netif-util.h22
-rw-r--r--src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c9
-rw-r--r--src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h1
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-duid-internal.h83
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c252
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h75
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-client-internal.h10
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h12
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h21
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c62
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.h1
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/network-common.c111
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/network-common.h19
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-duid.c290
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c217
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c355
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h117
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c962
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h77
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h74
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c2724
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c32
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h6
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c22
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c71
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h16
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c22
-rw-r--r--src/libnm-systemd-core/src/systemd/_sd-common.h2
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-device.h1
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp-duid.h70
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h168
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h52
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h2
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp6-protocol.h174
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-id128.h1
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-ndisc.h73
39 files changed, 5625 insertions, 831 deletions
diff --git a/src/libnm-systemd-core/README.md b/src/libnm-systemd-core/README.md
new file mode 100644
index 00000000..e47eb8ed
--- /dev/null
+++ b/src/libnm-systemd-core/README.md
@@ -0,0 +1,23 @@
+libnm-systemd-core
+==================
+
+This is a fork of systemd source files that are compiled
+as a static library with network helpers.
+
+We use systemd's DHCPv6 and LLDP library, by forking their code.
+
+We also still use their DHCPv4 library, but that is about to be replaced
+by nettools' n-dhcp4 and not used unless you configure the undocumented
+`[main].dhcp=systemd` plugin.
+
+This approach of code-reuse is very cumbersome, and we should replace
+systemd code by a proper library (like [nettools](https://github.com/nettools/)).
+
+We should not use systemd directly from our sources, beyond what
+we really need.
+
+
+Reimport Upstream Code
+----------------------
+
+Read [here](../libnm-systemd-shared/README.md#reimport-upstream-code).
diff --git a/src/libnm-systemd-core/meson.build b/src/libnm-systemd-core/meson.build
index 6175e42c..15e1c8a6 100644
--- a/src/libnm-systemd-core/meson.build
+++ b/src/libnm-systemd-core/meson.build
@@ -3,18 +3,21 @@
 libnm_systemd_core = static_library(
   'nm-systemd-core',
   sources: files(
-    'src/libsystemd-network/dhcp-identifier.c',
     'src/libsystemd-network/dhcp6-network.c',
     'src/libsystemd-network/dhcp6-option.c',
     'src/libsystemd-network/dhcp6-protocol.c',
     'src/libsystemd-network/network-common.c',
+    'src/libsystemd-network/sd-dhcp-duid.c',
     'src/libsystemd-network/sd-dhcp6-client.c',
     'src/libsystemd-network/sd-dhcp6-lease.c',
+    'src/libsystemd/sd-device/device-private.c',
+    'src/libsystemd/sd-device/sd-device.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',
+    'sd-adapt-core/netif-util.c',
     'sd-adapt-core/nm-sd-adapt-core.c',
   ),
   include_directories: [
@@ -28,6 +31,7 @@ libnm_systemd_core = static_library(
     top_inc,
     src_inc,
   ],
+  c_args: libnm_systemd_common_cflags,
   dependencies: [
     libnm_systemd_shared_dep_inc,
     glib_dep,
diff --git a/src/libnm-systemd-core/sd-adapt-core/netif-util.c b/src/libnm-systemd-core/sd-adapt-core/netif-util.c
new file mode 100644
index 00000000..cfb361a3
--- /dev/null
+++ b/src/libnm-systemd-core/sd-adapt-core/netif-util.c
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <linux/if.h>
+#ifdef __GLIBC__
+#include <linux/if_arp.h>
+#endif
+
+#include "arphrd-util.h"
+#include "device-util.h"
+#include "log-link.h"
+#include "memory-util.h"
+#include "netif-util.h"
+#include "siphash24.h"
+#include "sparse-endian.h"
+#include "strv.h"
+
+#if 0  /* NM_IGNORED */
+bool netif_has_carrier(uint8_t operstate, unsigned flags) {
+        /* see Documentation/networking/operstates.txt in the kernel sources */
+
+        if (operstate == IF_OPER_UP)
+                return true;
+
+        if (operstate != IF_OPER_UNKNOWN)
+                return false;
+
+        /* operstate may not be implemented, so fall back to flags */
+        return FLAGS_SET(flags, IFF_LOWER_UP | IFF_RUNNING) &&
+                !FLAGS_SET(flags, IFF_DORMANT);
+}
+
+int net_get_type_string(sd_device *device, uint16_t iftype, char **ret) {
+        const char *t;
+        char *p;
+
+        if (device &&
+            sd_device_get_devtype(device, &t) >= 0 &&
+            !isempty(t)) {
+                p = strdup(t);
+                if (!p)
+                        return -ENOMEM;
+
+                *ret = p;
+                return 0;
+        }
+
+        t = arphrd_to_name(iftype);
+        if (!t)
+                return -ENOENT;
+
+        p = strdup(t);
+        if (!p)
+                return -ENOMEM;
+
+        *ret = ascii_strlower(p);
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+const char *
+net_get_persistent_name(sd_device *device)
+{
+    assert(device);
+
+    /* fetch some persistent data unique (on this machine) to this device */
+    FOREACH_STRING(field,
+                   "ID_NET_NAME_ONBOARD",
+                   "ID_NET_NAME_SLOT",
+                   "ID_NET_NAME_PATH",
+                   "ID_NET_NAME_MAC")
+    {
+        const char *name;
+
+        if (sd_device_get_property_value(device, field, &name) >= 0)
+            return name;
+    }
+
+    return NULL;
+}
+
+#if 0 /* NM_IGNORED */
+/* Used when generating hardware address by udev, and IPv4LL seed by networkd. */
+#define HASH_KEY SD_ID128_MAKE(d3, 1e, 48, fa, 90, fe, 4b, 4c, 9d, af, d5, d7, a1, b1, 2e, 8a)
+
+int net_get_unique_predictable_data(sd_device *device, bool use_sysname, uint64_t *ret) {
+        const char *name;
+
+        assert(device);
+        assert(ret);
+
+        /* net_get_persistent_name() will return one of the device names based on stable information about
+         * the device. If this is not available, we fall back to using the actual device name. */
+        name = net_get_persistent_name(device);
+        if (!name && use_sysname)
+                (void) sd_device_get_sysname(device, &name);
+        if (!name)
+                return log_device_debug_errno(device, SYNTHETIC_ERRNO(ENODATA),
+                                              "No stable identifying information found");
+
+        log_device_debug(device, "Using \"%s\" as stable identifying information", name);
+
+        return net_get_unique_predictable_data_from_name(name, &HASH_KEY, ret);
+}
+
+int net_get_unique_predictable_data_from_name(
+                const char *name,
+                const sd_id128_t *key,
+                uint64_t *ret) {
+
+        size_t l, sz;
+        uint8_t *v;
+        int r;
+
+        assert(name);
+        assert(key);
+        assert(ret);
+
+        l = strlen(name);
+        sz = sizeof(sd_id128_t) + l;
+        v = newa(uint8_t, sz);
+
+        /* Fetch some persistent data unique to this machine */
+        r = sd_id128_get_machine((sd_id128_t*) v);
+        if (r < 0)
+                 return r;
+
+        memcpy(v + sizeof(sd_id128_t), name, l);
+
+        /* Let's hash the machine ID plus the device name. We use
+         * a fixed, but originally randomly created hash key here. */
+        *ret = htole64(siphash24(v, sz, key->bytes));
+        return 0;
+}
+
+typedef struct Link {
+        const char *ifname;
+} Link;
+
+int net_verify_hardware_address(
+                const char *ifname,
+                bool is_static,
+                uint16_t iftype,
+                const struct hw_addr_data *ib_hw_addr, /* current or parent HW address */
+                struct hw_addr_data *new_hw_addr) {
+
+        Link link = { .ifname = ifname };
+
+        assert(new_hw_addr);
+
+        if (new_hw_addr->length == 0)
+                return 0;
+
+        if (new_hw_addr->length != arphrd_to_hw_addr_len(iftype)) {
+                if (is_static)
+                        log_link_warning(&link,
+                                         "Specified MAC address with invalid length (%zu, expected %zu), refusing.",
+                                         new_hw_addr->length, arphrd_to_hw_addr_len(iftype));
+                return -EINVAL;
+        }
+
+        switch (iftype) {
+        case ARPHRD_ETHER:
+                /* see eth_random_addr() in the kernel */
+
+                if (ether_addr_is_null(&new_hw_addr->ether)) {
+                        if (is_static)
+                                log_link_warning(&link, "Specified MAC address is null, refusing.");
+                        return -EINVAL;
+                }
+
+                if (ether_addr_is_broadcast(&new_hw_addr->ether)) {
+                        if (is_static)
+                                log_link_warning(&link, "Specified MAC address is broadcast, refusing.");
+                        return -EINVAL;
+                }
+
+                if (ether_addr_is_multicast(&new_hw_addr->ether)) {
+                        if (is_static)
+                                log_link_warning(&link, "Specified MAC address has the multicast bit set, clearing the bit.");
+
+                        new_hw_addr->bytes[0] &= 0xfe;
+                }
+
+                if (!is_static && !ether_addr_is_local(&new_hw_addr->ether))
+                        /* Adjust local assignment bit when the MAC address is generated randomly. */
+                        new_hw_addr->bytes[0] |= 0x02;
+
+                break;
+
+        case ARPHRD_INFINIBAND:
+                /* see ipoib_check_lladdr() in the kernel */
+
+                assert(ib_hw_addr);
+                assert(ib_hw_addr->length == INFINIBAND_ALEN);
+
+                if (is_static &&
+                    (!memeqzero(new_hw_addr->bytes, INFINIBAND_ALEN - 8) ||
+                     memcmp(new_hw_addr->bytes, ib_hw_addr->bytes, INFINIBAND_ALEN - 8) != 0))
+                        log_link_warning(&link, "Only the last 8 bytes of the InifniBand MAC address can be changed, ignoring the first 12 bytes.");
+
+                if (memeqzero(new_hw_addr->bytes + INFINIBAND_ALEN - 8, 8)) {
+                        if (is_static)
+                                log_link_warning(&link, "The last 8 bytes of the InfiniBand MAC address cannot be null, refusing.");
+                        return -EINVAL;
+                }
+
+                memcpy(new_hw_addr->bytes, ib_hw_addr->bytes, INFINIBAND_ALEN - 8);
+                break;
+
+        default:
+                if (is_static)
+                        log_link_warning(&link, "Unsupported interface type %s%u to set MAC address, refusing.",
+                                         strna(arphrd_to_name(iftype)), iftype);
+                return -EINVAL;
+        }
+
+        return 0;
+}
+#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-core/sd-adapt-core/netif-util.h b/src/libnm-systemd-core/sd-adapt-core/netif-util.h
new file mode 100644
index 00000000..59a80866
--- /dev/null
+++ b/src/libnm-systemd-core/sd-adapt-core/netif-util.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "sd-device.h"
+#include "sd-id128.h"
+
+#include "ether-addr-util.h"
+
+bool        netif_has_carrier(uint8_t operstate, unsigned flags);
+int         net_get_type_string(sd_device *device, uint16_t iftype, char **ret);
+const char *net_get_persistent_name(sd_device *device);
+int         net_get_unique_predictable_data(sd_device *device, bool use_sysname, uint64_t *ret);
+int
+net_get_unique_predictable_data_from_name(const char *name, const sd_id128_t *key, uint64_t *ret);
+int net_verify_hardware_address(const char                *ifname,
+                                bool                       is_static,
+                                uint16_t                   iftype,
+                                const struct hw_addr_data *ib_hw_addr,
+                                struct hw_addr_data       *new_hw_addr);
diff --git a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c
index c5ef63eb..13effd12 100644
--- a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c
+++ b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c
@@ -12,15 +12,6 @@
 
 /*****************************************************************************/
 
-int
-asynchronous_close(int fd)
-{
-    safe_close(fd);
-    return -1;
-}
-
-/*****************************************************************************/
-
 sd_device *
 sd_device_ref(sd_device *self)
 {
diff --git a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h
index 9c317801..9cb5574d 100644
--- a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h
+++ b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h
@@ -86,7 +86,6 @@ sd_notify(int unset_environment, const char *state)
 
 #include "sd-id128.h"
 #include "sparse-endian.h"
-#include "async.h"
 
 #endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */
 
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-duid-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-duid-internal.h
new file mode 100644
index 00000000..f8bc15c4
--- /dev/null
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-duid-internal.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-device.h"
+#include "sd-dhcp-duid.h"
+#include "sd-id128.h"
+
+#include "ether-addr-util.h"
+#include "macro.h"
+#include "sparse-endian.h"
+
+#define SYSTEMD_PEN    43793
+
+typedef enum DUIDType {
+        DUID_TYPE_LLT      = SD_DUID_TYPE_LLT,
+        DUID_TYPE_EN       = SD_DUID_TYPE_EN,
+        DUID_TYPE_LL       = SD_DUID_TYPE_LL,
+        DUID_TYPE_UUID     = SD_DUID_TYPE_UUID,
+        _DUID_TYPE_MAX,
+        _DUID_TYPE_INVALID = -EINVAL,
+} DUIDType;
+
+/* RFC 8415 section 11.1:
+ * A DUID consists of a 2-octet type code represented in network byte order, followed by a variable number of
+ * octets that make up the actual identifier. The length of the DUID (not including the type code) is at
+ * least 1 octet and at most 128 octets. */
+#define MIN_DUID_DATA_LEN 1
+#define MAX_DUID_DATA_LEN 128
+#define MIN_DUID_LEN (sizeof(be16_t) + MIN_DUID_DATA_LEN)
+#define MAX_DUID_LEN (sizeof(be16_t) + MAX_DUID_DATA_LEN)
+
+/* 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[];
+                } _packed_ llt;
+                struct {
+                        /* DUID_TYPE_EN */
+                        be32_t pen;
+                        uint8_t id[];
+                } _packed_ en;
+                struct {
+                        /* DUID_TYPE_LL */
+                        be16_t htype;
+                        uint8_t haddr[];
+                } _packed_ ll;
+                struct {
+                        /* DUID_TYPE_UUID */
+                        sd_id128_t uuid;
+                } _packed_ uuid;
+                uint8_t data[MAX_DUID_DATA_LEN];
+        };
+} _packed_;
+
+typedef struct sd_dhcp_duid {
+        size_t size;
+        union {
+                struct duid duid;
+                uint8_t raw[MAX_DUID_LEN];
+        };
+} sd_dhcp_duid;
+
+static inline bool duid_size_is_valid(size_t size) {
+        return size >= MIN_DUID_LEN && size <= MAX_DUID_LEN;
+}
+
+static inline bool duid_data_size_is_valid(size_t size) {
+        return size >= MIN_DUID_DATA_LEN && size <= MAX_DUID_DATA_LEN;
+}
+
+const char *duid_type_to_string(DUIDType t) _const_;
+int dhcp_duid_to_string_internal(uint16_t type, const void *data, size_t data_size, char **ret);
+
+int dhcp_identifier_set_iaid(
+                sd_device *dev,
+                const struct hw_addr_data *hw_addr,
+                bool legacy_unstable_byteorder,
+                void *ret);
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c
deleted file mode 100644
index 05d0585a..00000000
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c
+++ /dev/null
@@ -1,252 +0,0 @@
-/* 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 "dhcp-identifier.h"
-#include "netif-util.h"
-#include "siphash24.h"
-#include "sparse-endian.h"
-#include "string-table.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 */
-
-static const char * const 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_STRING_TABLE_LOOKUP_TO_STRING(duid_type, DUIDType);
-
-int dhcp_validate_duid_len(DUIDType 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 */
-static int dhcp_identifier_set_duid_llt(
-                const struct hw_addr_data *hw_addr,
-                uint16_t arp_type,
-                usec_t t,
-                struct duid *ret_duid,
-                size_t *ret_len) {
-
-        uint16_t time_from_2000y;
-
-        assert(hw_addr);
-        assert(ret_duid);
-        assert(ret_len);
-
-        if (hw_addr->length == 0)
-                return -EOPNOTSUPP;
-
-        if (arp_type == ARPHRD_ETHER)
-                assert_return(hw_addr->length == ETH_ALEN, -EINVAL);
-        else if (arp_type == ARPHRD_INFINIBAND)
-                assert_return(hw_addr->length == INFINIBAND_ALEN, -EINVAL);
-        else
-                return -EOPNOTSUPP;
-
-        if (t < USEC_2000)
-                time_from_2000y = 0;
-        else
-                time_from_2000y = (uint16_t) (((t - USEC_2000) / USEC_PER_SEC) & 0xffffffff);
-
-        unaligned_write_be16(&ret_duid->type, DUID_TYPE_LLT);
-        unaligned_write_be16(&ret_duid->llt.htype, arp_type);
-        unaligned_write_be32(&ret_duid->llt.time, time_from_2000y);
-        memcpy(ret_duid->llt.haddr, hw_addr->bytes, hw_addr->length);
-
-        *ret_len = offsetof(struct duid, llt.haddr) + hw_addr->length;
-
-        return 0;
-}
-
-static int dhcp_identifier_set_duid_ll(
-                const struct hw_addr_data *hw_addr,
-                uint16_t arp_type,
-                struct duid *ret_duid,
-                size_t *ret_len) {
-
-        assert(hw_addr);
-        assert(ret_duid);
-        assert(ret_len);
-
-        if (hw_addr->length == 0)
-                return -EOPNOTSUPP;
-
-        if (arp_type == ARPHRD_ETHER)
-                assert_return(hw_addr->length == ETH_ALEN, -EINVAL);
-        else if (arp_type == ARPHRD_INFINIBAND)
-                assert_return(hw_addr->length == INFINIBAND_ALEN, -EINVAL);
-        else
-                return -EOPNOTSUPP;
-
-        unaligned_write_be16(&ret_duid->type, DUID_TYPE_LL);
-        unaligned_write_be16(&ret_duid->ll.htype, arp_type);
-        memcpy(ret_duid->ll.haddr, hw_addr->bytes, hw_addr->length);
-
-        *ret_len = offsetof(struct duid, ll.haddr) + hw_addr->length;
-
-        return 0;
-}
-#endif /* NM_IGNORED */
-
-int dhcp_identifier_set_duid_en(bool test_mode, struct duid *ret_duid, size_t *ret_len) {
-        sd_id128_t machine_id;
-        uint64_t hash;
-        int r;
-
-        assert(ret_duid);
-        assert(ret_len);
-
-        if (!test_mode) {
-                r = sd_id128_get_machine(&machine_id);
-                if (r < 0)
-                        return r;
-        } else
-                /* For tests, especially for fuzzers, reproducibility is important.
-                 * Hence, use a static and constant machine ID.
-                 * See 9216fddc5a8ac2742e6cfa7660f95c20ca4f2193. */
-                machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10);
-
-        unaligned_write_be16(&ret_duid->type, DUID_TYPE_EN);
-        unaligned_write_be32(&ret_duid->en.pen, SYSTEMD_PEN);
-
-        /* 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(ret_duid->en.id, &hash, sizeof(ret_duid->en.id));
-
-        *ret_len = offsetof(struct duid, en.id) + sizeof(ret_duid->en.id);
-
-        if (test_mode)
-                assert_se(memcmp(ret_duid, (const uint8_t[]) { 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2 }, *ret_len) == 0);
-
-        return 0;
-}
-
-#if 0 /* NM_IGNORED */
-static int dhcp_identifier_set_duid_uuid(struct duid *ret_duid, size_t *ret_len) {
-        sd_id128_t machine_id;
-        int r;
-
-        assert(ret_duid);
-        assert(ret_len);
-
-        r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id);
-        if (r < 0)
-                return r;
-
-        unaligned_write_be16(&ret_duid->type, DUID_TYPE_UUID);
-        memcpy(&ret_duid->uuid.uuid, &machine_id, sizeof(machine_id));
-
-        *ret_len = offsetof(struct duid, uuid.uuid) + sizeof(machine_id);
-
-        return 0;
-}
-
-int dhcp_identifier_set_duid(
-                DUIDType duid_type,
-                const struct hw_addr_data *hw_addr,
-                uint16_t arp_type,
-                usec_t llt_time,
-                bool test_mode,
-                struct duid *ret_duid,
-                size_t *ret_len) {
-
-        switch (duid_type) {
-        case DUID_TYPE_LLT:
-                return dhcp_identifier_set_duid_llt(hw_addr, arp_type, llt_time, ret_duid, ret_len);
-        case DUID_TYPE_EN:
-                return dhcp_identifier_set_duid_en(test_mode, ret_duid, ret_len);
-        case DUID_TYPE_LL:
-                return dhcp_identifier_set_duid_ll(hw_addr, arp_type, ret_duid, ret_len);
-        case DUID_TYPE_UUID:
-                return dhcp_identifier_set_duid_uuid(ret_duid, ret_len);
-        default:
-                return -EINVAL;
-        }
-}
-#endif /* NM_IGNORED */
-
-int dhcp_identifier_set_iaid(
-                sd_device *dev,
-                const struct hw_addr_data *hw_addr,
-                bool legacy_unstable_byteorder,
-                void *ret) {
-#if 0 /* NM_IGNORED */
-
-        const char *name = NULL;
-        uint32_t id32;
-        uint64_t id;
-
-        assert(hw_addr);
-        assert(ret);
-
-        if (dev)
-                name = net_get_persistent_name(dev);
-        if (name)
-                id = siphash24(name, strlen(name), HASH_KEY.bytes);
-        else
-                /* fall back to MAC address if no predictable name available */
-                id = siphash24(hw_addr->bytes, hw_addr->length, 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(ret, 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/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h
deleted file mode 100644
index 523dfc4a..00000000
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include "sd-device.h"
-#include "sd-id128.h"
-
-#include "ether-addr-util.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  = -EINVAL,
-} 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(DUIDType duid_type, size_t duid_len, bool strict);
-int dhcp_identifier_set_duid_en(bool test_mode, struct duid *ret_duid, size_t *ret_len);
-int dhcp_identifier_set_duid(
-                DUIDType duid_type,
-                const struct hw_addr_data *hw_addr,
-                uint16_t arp_type,
-                usec_t llt_time,
-                bool test_mode,
-                struct duid *ret_duid,
-                size_t *ret_len);
-int dhcp_identifier_set_iaid(
-                sd_device *dev,
-                const struct hw_addr_data *hw_addr,
-                bool legacy_unstable_byteorder,
-                void *ret);
-
-const char *duid_type_to_string(DUIDType t) _const_;
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-client-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-client-internal.h
new file mode 100644
index 00000000..6c17f574
--- /dev/null
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-client-internal.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-dhcp6-client.h"
+
+int dhcp6_client_set_state_callback(
+                sd_dhcp6_client *client,
+                sd_dhcp6_client_callback_t cb,
+                void *userdata);
+int dhcp6_client_get_state(sd_dhcp6_client *client);
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h
index fa43f28e..3fbfc028 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h
@@ -11,7 +11,8 @@
 #include "sd-event.h"
 #include "sd-dhcp6-client.h"
 
-#include "dhcp-identifier.h"
+#include "dhcp-duid-internal.h"
+#include "dhcp6-client-internal.h"
 #include "dhcp6-option.h"
 #include "dhcp6-protocol.h"
 #include "ether-addr-util.h"
@@ -63,8 +64,7 @@ struct sd_dhcp6_client {
         DHCP6IA ia_na;
         DHCP6IA ia_pd;
         DHCP6RequestIA request_ia;
-        struct duid duid;
-        size_t duid_len;
+        sd_dhcp_duid duid;
         be16_t *req_opts;
         size_t n_req_opts;
         char *fqdn;
@@ -79,10 +79,9 @@ struct sd_dhcp6_client {
 
         sd_dhcp6_client_callback_t callback;
         void *userdata;
+        sd_dhcp6_client_callback_t state_callback;
+        void *state_userdata;
         bool send_release;
-
-        /* Ignore machine-ID when generating DUID. See dhcp_identifier_set_duid_en(). */
-        bool test_mode;
 };
 
 int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address);
@@ -90,7 +89,6 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
                                   const void *packet, size_t len);
 
 int dhcp6_client_send_message(sd_dhcp6_client *client);
-void dhcp6_client_set_test_mode(sd_dhcp6_client *client, bool test_mode);
 int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id);
 
 #define log_dhcp6_client_errno(client, error, fmt, ...)         \
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h
index 1f10dccb..e76a108f 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h
@@ -10,7 +10,9 @@
 #include "sd-dhcp6-lease.h"
 
 #include "dhcp6-option.h"
+#include "dhcp6-protocol.h"
 #include "macro.h"
+#include "set.h"
 #include "time-util.h"
 
 struct sd_dhcp6_lease {
@@ -43,9 +45,11 @@ struct sd_dhcp6_lease {
         struct in6_addr *sntp;
         size_t sntp_count;
         char *fqdn;
+        char *captive_portal;
+        struct sd_dhcp6_option **sorted_vendor_options;
+        Set *vendor_options;
 };
 
-int dhcp6_lease_get_lifetime(sd_dhcp6_lease *lease, usec_t *ret_t1, usec_t *ret_t2, usec_t *ret_valid);
 int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len);
 int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len);
 int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len);
@@ -60,6 +64,7 @@ int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t
 int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
 int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const 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_set_captive_portal(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
 
 int dhcp6_lease_new(sd_dhcp6_lease **ret);
 int dhcp6_lease_new_from_message(
@@ -69,3 +74,17 @@ int dhcp6_lease_new_from_message(
                 const triple_timestamp *timestamp,
                 const struct in6_addr *server_address,
                 sd_dhcp6_lease **ret);
+
+#define _FOREACH_DHCP6_ADDRESS(lease, it)                               \
+        for (int it = sd_dhcp6_lease_address_iterator_reset(lease);     \
+             it > 0;                                                    \
+             it = sd_dhcp6_lease_address_iterator_next(lease))
+#define FOREACH_DHCP6_ADDRESS(lease)                                    \
+        _FOREACH_DHCP6_ADDRESS(lease, UNIQ_T(i, UNIQ))
+
+#define _FOREACH_DHCP6_PD_PREFIX(lease, it)                             \
+        for (int it = sd_dhcp6_lease_pd_iterator_reset(lease);          \
+             it > 0;                                                    \
+             it = sd_dhcp6_lease_pd_iterator_next(lease))
+#define FOREACH_DHCP6_PD_PREFIX(lease)                                  \
+        _FOREACH_DHCP6_PD_PREFIX(lease, UNIQ_T(i, UNIQ))
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c
index 23cf8a89..5fa9b265 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c
@@ -17,6 +17,7 @@
 #include "dns-domain.h"
 #include "escape.h"
 #include "memory-util.h"
+#include "network-common.h"
 #include "strv.h"
 #include "unaligned.h"
 
@@ -526,6 +527,26 @@ int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_s
         return status;
 }
 
+/* parse a string from dhcp option field. *ret must be initialized */
+int dhcp6_option_parse_string(const uint8_t *data, size_t data_len, char **ret) {
+        _cleanup_free_ char *string = NULL;
+        int r;
+
+        assert(data || data_len == 0);
+        assert(ret);
+
+        if (data_len <= 0) {
+                *ret = mfree(*ret);
+                return 0;
+        }
+
+        r = make_cstring((const char *) data, data_len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &string);
+        if (r < 0)
+                return r;
+
+        return free_and_replace(*ret, string);
+}
+
 static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) {
         int r;
 
@@ -567,7 +588,7 @@ static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t
 
 static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) {
         _cleanup_free_ DHCP6Address *a = NULL;
-        uint32_t lt_valid, lt_pref;
+        usec_t lt_valid, lt_pref;
         int r;
 
         assert(ia);
@@ -586,17 +607,18 @@ static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, DHCP6IA *ia, c
 
         memcpy(&a->iaaddr, data, sizeof(struct iaaddr));
 
-        lt_valid = be32toh(a->iaaddr.lifetime_valid);
-        lt_pref = be32toh(a->iaaddr.lifetime_preferred);
+        lt_valid = be32_sec_to_usec(a->iaaddr.lifetime_valid, /* max_as_infinity = */ true);
+        lt_pref = be32_sec_to_usec(a->iaaddr.lifetime_preferred, /* max_as_infinity = */ true);
 
         if (lt_valid == 0)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
                                               "Received an IA address with zero valid lifetime, ignoring.");
         if (lt_pref > lt_valid)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                              "Received an IA address with preferred lifetime %"PRIu32
-                                              " larger than valid lifetime %"PRIu32", ignoring.",
-                                              lt_pref, lt_valid);
+                                              "Received an IA address with preferred lifetime %s "
+                                              "larger than valid lifetime %s, ignoring.",
+                                              FORMAT_TIMESPAN(lt_pref, USEC_PER_SEC),
+                                              FORMAT_TIMESPAN(lt_valid, USEC_PER_SEC));
 
         if (len > sizeof(struct iaaddr)) {
                 r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iaaddr), len - sizeof(struct iaaddr));
@@ -610,7 +632,7 @@ static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, DHCP6IA *ia, c
 
 static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) {
         _cleanup_free_ DHCP6Address *a = NULL;
-        uint32_t lt_valid, lt_pref;
+        usec_t lt_valid, lt_pref;
         int r;
 
         assert(ia);
@@ -629,17 +651,18 @@ static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, DHCP6IA *ia,
 
         memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix));
 
-        lt_valid = be32toh(a->iapdprefix.lifetime_valid);
-        lt_pref = be32toh(a->iapdprefix.lifetime_preferred);
+        lt_valid = be32_sec_to_usec(a->iapdprefix.lifetime_valid, /* max_as_infinity = */ true);
+        lt_pref = be32_sec_to_usec(a->iapdprefix.lifetime_preferred, /* max_as_infinity = */ true);
 
         if (lt_valid == 0)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
                                               "Received a PD prefix with zero valid lifetime, ignoring.");
         if (lt_pref > lt_valid)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                              "Received a PD prefix with preferred lifetime %"PRIu32
-                                              " larger than valid lifetime %"PRIu32", ignoring.",
-                                              lt_pref, lt_valid);
+                                              "Received a PD prefix with preferred lifetime %s "
+                                              "larger than valid lifetime %s, ignoring.",
+                                              FORMAT_TIMESPAN(lt_pref, USEC_PER_SEC),
+                                              FORMAT_TIMESPAN(lt_valid, USEC_PER_SEC));
 
         if (len > sizeof(struct iapdprefix)) {
                 r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iapdprefix), len - sizeof(struct iapdprefix));
@@ -660,7 +683,7 @@ int dhcp6_option_parse_ia(
                 DHCP6IA **ret) {
 
         _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
-        uint32_t lt_t1, lt_t2;
+        usec_t lt_t1, lt_t2;
         size_t header_len;
         int r;
 
@@ -710,17 +733,18 @@ int dhcp6_option_parse_ia(
                                               "from the one chosen by the client, ignoring.");
 
         /* It is not necessary to check if the lifetime_t2 is zero here, as in that case it will be updated later. */
-        lt_t1 = be32toh(ia->header.lifetime_t1);
-        lt_t2 = be32toh(ia->header.lifetime_t2);
+        lt_t1 = be32_sec_to_usec(ia->header.lifetime_t1, /* max_as_infinity = */ true);
+        lt_t2 = be32_sec_to_usec(ia->header.lifetime_t2, /* max_as_infinity = */ true);
 
         if (lt_t1 > lt_t2)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                              "Received an IA option with T1 %"PRIu32"sec > T2 %"PRIu32"sec, ignoring.",
-                                              lt_t1, lt_t2);
+                                              "Received an IA option with T1 %s > T2 %s, ignoring.",
+                                              FORMAT_TIMESPAN(lt_t1, USEC_PER_SEC),
+                                              FORMAT_TIMESPAN(lt_t2, USEC_PER_SEC));
         if (lt_t1 == 0 && lt_t2 > 0)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                              "Received an IA option with zero T1 and non-zero T2 (%"PRIu32"sec), ignoring.",
-                                              lt_t2);
+                                              "Received an IA option with zero T1 and non-zero T2 (%s), ignoring.",
+                                              FORMAT_TIMESPAN(lt_t2, USEC_PER_SEC));
 
         for (size_t offset = header_len; offset < option_data_len;) {
                 const uint8_t *subdata;
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.h
index 36841dd2..614b4f8a 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.h
@@ -88,6 +88,7 @@ int dhcp6_option_parse(
                 size_t *ret_option_data_len,
                 const uint8_t **ret_option_data);
 int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message);
+int dhcp6_option_parse_string(const uint8_t *data, size_t data_len, char **ret);
 int dhcp6_option_parse_ia(
                 sd_dhcp6_client *client,
                 be32_t iaid,
diff --git a/src/libnm-systemd-core/src/libsystemd-network/network-common.c b/src/libnm-systemd-core/src/libsystemd-network/network-common.c
index bbc28941..8d62e6ff 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/network-common.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/network-common.c
@@ -2,8 +2,11 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include "env-util.h"
 #include "format-util.h"
 #include "network-common.h"
+#include "socket-util.h"
+#include "unaligned.h"
 
 int get_ifname(int ifindex, char **ifname) {
         assert(ifname);
@@ -15,3 +18,111 @@ int get_ifname(int ifindex, char **ifname) {
 
         return format_ifname_alloc(ifindex, ifname);
 }
+
+usec_t unaligned_be32_sec_to_usec(const void *p, bool max_as_infinity) {
+        uint32_t s = unaligned_read_be32(ASSERT_PTR(p));
+
+        if (s == UINT32_MAX && max_as_infinity)
+                return USEC_INFINITY;
+
+        return s * USEC_PER_SEC;
+}
+
+usec_t be32_sec_to_usec(be32_t t, bool max_as_infinity) {
+        uint32_t s = be32toh(t);
+
+        if (s == UINT32_MAX && max_as_infinity)
+                return USEC_INFINITY;
+
+        return s * USEC_PER_SEC;
+}
+
+usec_t be32_msec_to_usec(be32_t t, bool max_as_infinity) {
+        uint32_t s = be32toh(t);
+
+        if (s == UINT32_MAX && max_as_infinity)
+                return USEC_INFINITY;
+
+        return s * USEC_PER_MSEC;
+}
+
+usec_t be16_sec_to_usec(be16_t t, bool max_as_infinity) {
+        uint16_t s = be16toh(t);
+
+        if (s == UINT16_MAX && max_as_infinity)
+                return USEC_INFINITY;
+
+        return s * USEC_PER_SEC;
+}
+
+be32_t usec_to_be32_sec(usec_t t) {
+        if (t == USEC_INFINITY)
+                /* Some settings, e.g. a lifetime of an address, UINT32_MAX is handled as infinity. so let's
+                 * map USEC_INFINITY to UINT32_MAX. */
+                return htobe32(UINT32_MAX);
+
+        if (t >= (UINT32_MAX - 1) * USEC_PER_SEC)
+                /* Finite but too large. Let's use the largest (or off-by-one from the largest) finite value. */
+                return htobe32(UINT32_MAX - 1);
+
+        return htobe32((uint32_t) DIV_ROUND_UP(t, USEC_PER_SEC));
+}
+
+be32_t usec_to_be32_msec(usec_t t) {
+        if (t == USEC_INFINITY)
+                return htobe32(UINT32_MAX);
+
+        if (t >= (UINT32_MAX - 1) * USEC_PER_MSEC)
+                return htobe32(UINT32_MAX - 1);
+
+        return htobe32((uint32_t) DIV_ROUND_UP(t, USEC_PER_MSEC));
+}
+
+be16_t usec_to_be16_sec(usec_t t) {
+        if (t == USEC_INFINITY)
+                return htobe16(UINT16_MAX);
+
+        if (t >= (UINT16_MAX - 1) * USEC_PER_SEC)
+                return htobe16(UINT16_MAX - 1);
+
+        return htobe16((uint16_t) DIV_ROUND_UP(t, USEC_PER_SEC));
+}
+
+usec_t time_span_to_stamp(usec_t span, usec_t base) {
+        /* Typically, 0 lifetime (timespan) indicates the corresponding configuration (address or so) must be
+         * dropped. So, when the timespan is zero, here we return 0 rather than 'base'. This makes the caller
+         * easily understand that the configuration needs to be dropped immediately. */
+        if (span == 0)
+                return 0;
+
+        return usec_add(base, span);
+}
+
+bool network_test_mode_enabled(void) {
+        static int test_mode = -1;
+        int r;
+
+        if (test_mode < 0) {
+                r = getenv_bool("SYSTEMD_NETWORK_TEST_MODE");
+                if (r < 0) {
+                        if (r != -ENXIO)
+                                log_debug_errno(r, "Failed to parse $SYSTEMD_NETWORK_TEST_MODE environment variable, ignoring: %m");
+
+                        test_mode = false;
+                } else
+                        test_mode = r;
+        }
+
+        return test_mode;
+}
+
+triple_timestamp* triple_timestamp_from_cmsg(triple_timestamp *t, struct msghdr *mh) {
+        assert(t);
+        assert(mh);
+
+        struct timeval *tv = CMSG_FIND_AND_COPY_DATA(mh, SOL_SOCKET, SCM_TIMESTAMP, struct timeval);
+        if (tv)
+                return triple_timestamp_from_realtime(t, timeval_load(tv));
+
+        return triple_timestamp_now(t);
+}
diff --git a/src/libnm-systemd-core/src/libsystemd-network/network-common.h b/src/libnm-systemd-core/src/libsystemd-network/network-common.h
index 2b0e3b56..1750f181 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/network-common.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/network-common.h
@@ -1,7 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <sys/socket.h>
+
 #include "log-link.h"
+#include "sparse-endian.h"
+#include "time-util.h"
 
 #define log_interface_prefix_full_errno_zerook(prefix, type, val, error, fmt, ...) \
         ({                                                              \
@@ -28,3 +32,18 @@
         })
 
 int get_ifname(int ifindex, char **ifname);
+
+usec_t unaligned_be32_sec_to_usec(const void *p, bool max_as_infinity);
+usec_t be32_sec_to_usec(be32_t t, bool max_as_infinity);
+usec_t be32_msec_to_usec(be32_t t, bool max_as_infinity);
+usec_t be16_sec_to_usec(be16_t t, bool max_as_infinity);
+be32_t usec_to_be32_sec(usec_t t);
+be32_t usec_to_be32_msec(usec_t t);
+be16_t usec_to_be16_sec(usec_t t);
+usec_t time_span_to_stamp(usec_t span, usec_t base);
+
+bool network_test_mode_enabled(void);
+
+triple_timestamp* triple_timestamp_from_cmsg(triple_timestamp *t, struct msghdr *mh);
+#define TRIPLE_TIMESTAMP_FROM_CMSG(mh)          \
+        triple_timestamp_from_cmsg(&(triple_timestamp) {}, mh)
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-duid.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-duid.c
new file mode 100644
index 00000000..e664a4a7
--- /dev/null
+++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-duid.c
@@ -0,0 +1,290 @@
+/* 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 "dhcp-duid-internal.h"
+#include "hexdecoct.h"
+#include "netif-util.h"
+#include "network-common.h"
+#include "siphash24.h"
+#include "string-table.h"
+#include "unaligned.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 */
+
+static const char * const 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_STRING_TABLE_LOOKUP_TO_STRING(duid_type, DUIDType);
+
+int sd_dhcp_duid_clear(sd_dhcp_duid *duid) {
+        assert_return(duid, -EINVAL);
+
+        *duid = (sd_dhcp_duid) {};
+        return 0;
+}
+
+int sd_dhcp_duid_is_set(const sd_dhcp_duid *duid) {
+        if (!duid)
+                return false;
+
+        return duid_size_is_valid(duid->size);
+}
+
+int sd_dhcp_duid_get(const sd_dhcp_duid *duid, uint16_t *ret_type, const void **ret_data, size_t *ret_size) {
+        assert_return(sd_dhcp_duid_is_set(duid), -EINVAL);
+        assert_return(ret_type, -EINVAL);
+        assert_return(ret_data, -EINVAL);
+        assert_return(ret_size, -EINVAL);
+
+        *ret_type = be16toh(duid->duid.type);
+        *ret_data = duid->duid.data;
+        *ret_size = duid->size - offsetof(struct duid, data);
+        return 0;
+}
+
+int sd_dhcp_duid_get_raw(const sd_dhcp_duid *duid, const void **ret_data, size_t *ret_size) {
+        assert_return(sd_dhcp_duid_is_set(duid), -EINVAL);
+        assert_return(ret_data, -EINVAL);
+        assert_return(ret_size, -EINVAL);
+
+        /* Unlike sd_dhcp_duid_get(), this returns whole DUID including its type. */
+
+        *ret_data = duid->raw;
+        *ret_size = duid->size;
+        return 0;
+}
+
+int sd_dhcp_duid_set(
+                sd_dhcp_duid *duid,
+                uint16_t duid_type,
+                const void *data,
+                size_t data_size) {
+
+        assert_return(duid, -EINVAL);
+        assert_return(data, -EINVAL);
+
+        if (!duid_data_size_is_valid(data_size))
+                return -EINVAL;
+
+        unaligned_write_be16(&duid->duid.type, duid_type);
+        memcpy(duid->duid.data, data, data_size);
+
+        duid->size = offsetof(struct duid, data) + data_size;
+        return 0;
+}
+
+int sd_dhcp_duid_set_raw(
+                sd_dhcp_duid *duid,
+                const void *data,
+                size_t data_size) {
+
+        assert_return(duid, -EINVAL);
+        assert_return(data, -EINVAL);
+
+        /* Unlike sd_dhcp_duid_set(), this takes whole DUID including its type. */
+
+        if (!duid_size_is_valid(data_size))
+                return -EINVAL;
+
+        memcpy(duid->raw, data, data_size);
+
+        duid->size = data_size;
+        return 0;
+}
+
+int sd_dhcp_duid_set_llt(
+                sd_dhcp_duid *duid,
+                const void *hw_addr,
+                size_t hw_addr_size,
+                uint16_t arp_type,
+                uint64_t usec) {
+
+        uint16_t time_from_2000y;
+
+        assert_return(duid, -EINVAL);
+        assert_return(hw_addr, -EINVAL);
+
+        if (arp_type == ARPHRD_ETHER)
+                assert_return(hw_addr_size == ETH_ALEN, -EINVAL);
+        else if (arp_type == ARPHRD_INFINIBAND)
+                assert_return(hw_addr_size == INFINIBAND_ALEN, -EINVAL);
+        else
+                return -EOPNOTSUPP;
+
+        time_from_2000y = (uint16_t) ((usec_sub_unsigned(usec, USEC_2000) / USEC_PER_SEC) & 0xffffffff);
+
+        unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_LLT);
+        unaligned_write_be16(&duid->duid.llt.htype, arp_type);
+        unaligned_write_be32(&duid->duid.llt.time, time_from_2000y);
+        memcpy(duid->duid.llt.haddr, hw_addr, hw_addr_size);
+
+        duid->size = offsetof(struct duid, llt.haddr) + hw_addr_size;
+        return 0;
+}
+
+int sd_dhcp_duid_set_ll(
+                sd_dhcp_duid *duid,
+                const void *hw_addr,
+                size_t hw_addr_size,
+                uint16_t arp_type) {
+
+        assert_return(duid, -EINVAL);
+        assert_return(hw_addr, -EINVAL);
+
+        if (arp_type == ARPHRD_ETHER)
+                assert_return(hw_addr_size == ETH_ALEN, -EINVAL);
+        else if (arp_type == ARPHRD_INFINIBAND)
+                assert_return(hw_addr_size == INFINIBAND_ALEN, -EINVAL);
+        else
+                return -EOPNOTSUPP;
+
+        unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_LL);
+        unaligned_write_be16(&duid->duid.ll.htype, arp_type);
+        memcpy(duid->duid.ll.haddr, hw_addr, hw_addr_size);
+
+        duid->size = offsetof(struct duid, ll.haddr) + hw_addr_size;
+        return 0;
+}
+
+int sd_dhcp_duid_set_en(sd_dhcp_duid *duid) {
+        sd_id128_t machine_id;
+        bool test_mode;
+        uint64_t hash;
+        int r;
+
+        assert_return(duid, -EINVAL);
+
+        test_mode = network_test_mode_enabled();
+
+        if (!test_mode) {
+                r = sd_id128_get_machine(&machine_id);
+                if (r < 0)
+                        return r;
+        } else
+                /* For tests, especially for fuzzers, reproducibility is important.
+                 * Hence, use a static and constant machine ID.
+                 * See 9216fddc5a8ac2742e6cfa7660f95c20ca4f2193. */
+                machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10);
+
+        unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_EN);
+        unaligned_write_be32(&duid->duid.en.pen, SYSTEMD_PEN);
+
+        /* 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->duid.en.id, &hash, sizeof(hash));
+
+        duid->size = offsetof(struct duid, en.id) + sizeof(hash);
+
+        if (test_mode)
+                assert_se(memcmp(&duid->duid, (const uint8_t[]) { 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2 }, duid->size) == 0);
+
+        return 0;
+}
+
+int sd_dhcp_duid_set_uuid(sd_dhcp_duid *duid) {
+        sd_id128_t machine_id;
+        int r;
+
+        assert_return(duid, -EINVAL);
+
+        r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id);
+        if (r < 0)
+                return r;
+
+        unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_UUID);
+        memcpy(&duid->duid.uuid.uuid, &machine_id, sizeof(machine_id));
+
+        duid->size = offsetof(struct duid, uuid.uuid) + sizeof(machine_id);
+        return 0;
+}
+
+int dhcp_duid_to_string_internal(uint16_t type, const void *data, size_t data_size, char **ret) {
+        _cleanup_free_ char *p = NULL, *x = NULL;
+        const char *t;
+
+        assert(data);
+        assert(ret);
+
+        if (!duid_data_size_is_valid(data_size))
+                return -EINVAL;
+
+        x = hexmem(data, data_size);
+        if (!x)
+                return -ENOMEM;
+
+        t = duid_type_to_string(type);
+        if (!t)
+                return asprintf(ret, "%04x:%s", htobe16(type), x);
+
+        p = strjoin(t, ":", x);
+        if (!p)
+                return -ENOMEM;
+
+        *ret = TAKE_PTR(p);
+        return 0;
+}
+
+int sd_dhcp_duid_to_string(const sd_dhcp_duid *duid, char **ret) {
+        uint16_t type;
+        const void *data;
+        size_t data_size;
+        int r;
+
+        assert_return(sd_dhcp_duid_is_set(duid), -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        r = sd_dhcp_duid_get(duid, &type, &data, &data_size);
+        if (r < 0)
+                return r;
+
+        return dhcp_duid_to_string_internal(type, data, data_size, ret);
+}
+
+int dhcp_identifier_set_iaid(
+                sd_device *dev,
+                const struct hw_addr_data *hw_addr,
+                bool legacy_unstable_byteorder,
+                void *ret) {
+
+        const char *name = NULL;
+        uint32_t id32;
+        uint64_t id;
+
+        assert(hw_addr);
+        assert(ret);
+
+        if (dev)
+                name = net_get_persistent_name(dev);
+        if (name)
+                id = siphash24(name, strlen(name), HASH_KEY.bytes);
+        else
+                /* fall back to MAC address if no predictable name available */
+                id = siphash24(hw_addr->bytes, hw_addr->length, 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(ret, id32);
+        return 0;
+}
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c
index 0e6f21f4..7c201164 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c
@@ -7,27 +7,24 @@
 
 #include <errno.h>
 #include <sys/ioctl.h>
-#if 0 /* NM_IGNORED */
+#ifdef __GLIBC__
 #include <linux/if_arp.h>
-#else /* NM_IGNORED */
-#include <net/if_arp.h>
-#endif /* NM_IGNORED */
+#endif
 #include <linux/if_infiniband.h>
 
 #include "sd-dhcp6-client.h"
 
 #include "alloc-util.h"
 #include "device-util.h"
-#include "dhcp-identifier.h"
+#include "dhcp-duid-internal.h"
 #include "dhcp6-internal.h"
 #include "dhcp6-lease-internal.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 "io-util.h"
+#include "iovec-util.h"
 #include "random-util.h"
 #include "socket-util.h"
 #include "sort-util.h"
@@ -52,6 +49,19 @@ int sd_dhcp6_client_set_callback(
         return 0;
 }
 
+int dhcp6_client_set_state_callback(
+                sd_dhcp6_client *client,
+                sd_dhcp6_client_callback_t cb,
+                void *userdata) {
+
+        assert_return(client, -EINVAL);
+
+        client->state_callback = cb;
+        client->state_userdata = userdata;
+
+        return 0;
+}
+
 int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) {
         assert_return(client, -EINVAL);
         assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
@@ -184,10 +194,10 @@ int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *
 static int client_ensure_duid(sd_dhcp6_client *client) {
         assert(client);
 
-        if (client->duid_len != 0)
+        if (sd_dhcp_duid_is_set(&client->duid))
                 return 0;
 
-        return dhcp_identifier_set_duid_en(client->test_mode, &client->duid, &client->duid_len);
+        return sd_dhcp6_client_set_duid_en(client);
 }
 
 /**
@@ -195,101 +205,102 @@ static int client_ensure_duid(sd_dhcp6_client *client) {
  * 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,
-                DUIDType duid_type,
-                const void *duid,
-                size_t duid_len,
-                usec_t llt_time) {
+int sd_dhcp6_client_set_duid_llt(sd_dhcp6_client *client, uint64_t llt_time) {
         int r;
 
         assert_return(client, -EINVAL);
         assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
-        assert_return(duid_len == 0 || duid, -EINVAL);
 
-        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");
+        r = sd_dhcp_duid_set_llt(&client->duid, client->hw_addr.bytes, client->hw_addr.length, client->arp_type, llt_time);
+        if (r < 0)
+                return log_dhcp6_client_errno(client, r, "Failed to set DUID-LLT: %m");
 
-                        log_dhcp6_client(client, "Using DUID of type %i of incorrect length, proceeding.", duid_type);
-                }
+        return 0;
+}
 
-                client->duid.type = htobe16(duid_type);
-                memcpy(&client->duid.raw.data, duid, duid_len);
-                client->duid_len = sizeof(client->duid.type) + duid_len;
+int sd_dhcp6_client_set_duid_ll(sd_dhcp6_client *client) {
+        int r;
 
-        } else {
-#if 0 /* NM_IGNORED */
-                r = dhcp_identifier_set_duid(duid_type, &client->hw_addr, client->arp_type, llt_time,
-                                             client->test_mode, &client->duid, &client->duid_len);
-                if (r == -EOPNOTSUPP)
-                        return log_dhcp6_client_errno(client, r,
-                                                      "Failed to set %s. MAC address is not set or "
-                                                      "interface type is not supported.",
-                                                      duid_type_to_string(duid_type));
-                if (r < 0)
-                        return log_dhcp6_client_errno(client, r, "Failed to set %s: %m",
-                                                      duid_type_to_string(duid_type));
-#else /* NM_IGNORED */
-                g_return_val_if_reached (-EINVAL);
-#endif /* NM_IGNORED */
-        }
+        assert_return(client, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+        r = sd_dhcp_duid_set_ll(&client->duid, client->hw_addr.bytes, client->hw_addr.length, client->arp_type);
+        if (r < 0)
+                return log_dhcp6_client_errno(client, r, "Failed to set DUID-LL: %m");
 
         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_en(sd_dhcp6_client *client) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+        r = sd_dhcp_duid_set_en(&client->duid);
+        if (r < 0)
+                return log_dhcp6_client_errno(client, r, "Failed to set DUID-EN: %m");
+
+        return 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);
+int sd_dhcp6_client_set_duid_uuid(sd_dhcp6_client *client) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+        r = sd_dhcp_duid_set_uuid(&client->duid);
+        if (r < 0)
+                return log_dhcp6_client_errno(client, r, "Failed to set DUID-UUID: %m");
+
+        return 0;
 }
 
-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 sd_dhcp6_client_set_duid_raw(sd_dhcp6_client *client, uint16_t duid_type, const uint8_t *duid, size_t duid_len) {
         int r;
 
         assert_return(client, -EINVAL);
-        assert_return(client->duid_len > 0, -ENODATA);
-        assert_return(duid, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+        assert_return(duid || duid_len == 0, -EINVAL);
 
-        v = 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;
-        }
+        r = sd_dhcp_duid_set(&client->duid, duid_type, duid, duid_len);
+        if (r < 0)
+                return log_dhcp6_client_errno(client, r, "Failed to set DUID: %m");
 
-        t = hexmem(&client->duid.raw.data, client->duid_len);
-        if (!t)
-                return -ENOMEM;
+        return 0;
+}
 
-        p = strjoin(s, ":", t);
-        if (!p)
-                return -ENOMEM;
+int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, const sd_dhcp_duid *duid) {
+        assert_return(client, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+        assert_return(sd_dhcp_duid_is_set(duid), -EINVAL);
 
-        *duid = TAKE_PTR(p);
+        client->duid = *duid;
+        return 0;
+}
 
+int sd_dhcp6_client_get_duid(sd_dhcp6_client *client, const sd_dhcp_duid **ret) {
+        assert_return(client, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!sd_dhcp_duid_is_set(&client->duid))
+                return -ENODATA;
+
+        *ret = &client->duid;
         return 0;
 }
 
+int sd_dhcp6_client_get_duid_as_string(sd_dhcp6_client *client, char **ret) {
+        assert_return(client, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!sd_dhcp_duid_is_set(&client->duid))
+                return -ENODATA;
+
+        return sd_dhcp_duid_to_string(&client->duid, ret);
+}
+
 int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
         assert_return(client, -EINVAL);
         assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
@@ -335,12 +346,6 @@ int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) {
         return 0;
 }
 
-void dhcp6_client_set_test_mode(sd_dhcp6_client *client, bool test_mode) {
-        assert(client);
-
-        client->test_mode = test_mode;
-}
-
 int sd_dhcp6_client_set_fqdn(
                 sd_dhcp6_client *client,
                 const char *fqdn) {
@@ -491,7 +496,7 @@ int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request) {
 
 int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) {
         assert(client);
-        assert(client->test_mode);
+        assert_se(network_test_mode_enabled());
 
         /* This is for tests or fuzzers. */
 
@@ -510,7 +515,6 @@ int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable) {
 
 int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable) {
         assert_return(client, -EINVAL);
-        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
 
         client->send_release = enable;
         return 0;
@@ -552,6 +556,15 @@ static void client_set_state(sd_dhcp6_client *client, DHCP6State state) {
                          dhcp6_state_to_string(client->state), dhcp6_state_to_string(state));
 
         client->state = state;
+
+        if (client->state_callback)
+                client->state_callback(client, state, client->state_userdata);
+}
+
+int dhcp6_client_get_state(sd_dhcp6_client *client) {
+        assert_return(client, -EINVAL);
+
+        return client->state;
 }
 
 static void client_notify(sd_dhcp6_client *client, int event) {
@@ -809,9 +822,9 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) {
         if (r < 0)
                 return r;
 
-        assert(client->duid_len > 0);
+        assert(sd_dhcp_duid_is_set(&client->duid));
         r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_CLIENTID,
-                                client->duid_len, &client->duid);
+                                client->duid.size, &client->duid.duid);
         if (r < 0)
                 return r;
 
@@ -824,7 +837,7 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) {
         /* RFC 8415 Section 21.9.
          * A client MUST include an Elapsed Time option in messages to indicate how long the client has
          * been trying to complete a DHCP message exchange. */
-        elapsed_usec = NM_MIN(usec_sub_unsigned(time_now, client->transaction_start) / USEC_PER_MSEC / 10, (usec_t) UINT16_MAX);
+        elapsed_usec = MIN(usec_sub_unsigned(time_now, client->transaction_start) / USEC_PER_MSEC / 10, (usec_t) UINT16_MAX);
         elapsed_time = htobe16(elapsed_usec);
         r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_ELAPSED_TIME, sizeof(elapsed_time), &elapsed_time);
         if (r < 0)
@@ -1047,12 +1060,20 @@ static int client_enter_bound_state(sd_dhcp6_client *client) {
         (void) event_source_disable(client->receive_message);
         (void) event_source_disable(client->timeout_resend);
 
-        r = dhcp6_lease_get_lifetime(client->lease, &lifetime_t1, &lifetime_t2, &lifetime_valid);
+        r = sd_dhcp6_lease_get_t1(client->lease, &lifetime_t1);
+        if (r < 0)
+                goto error;
+
+        r = sd_dhcp6_lease_get_t2(client->lease, &lifetime_t2);
+        if (r < 0)
+                goto error;
+
+        r = sd_dhcp6_lease_get_valid_lifetime(client->lease, &lifetime_valid);
         if (r < 0)
                 goto error;
 
         lifetime_t2 = client_timeout_compute_random(lifetime_t2);
-        lifetime_t1 = client_timeout_compute_random(NM_MIN(lifetime_t1, lifetime_t2));
+        lifetime_t1 = client_timeout_compute_random(MIN(lifetime_t1, lifetime_t2));
 
         if (lifetime_t1 == USEC_INFINITY) {
                 log_dhcp6_client(client, "Infinite T1");
@@ -1286,16 +1307,15 @@ static int client_receive_message(
                 .msg_control = &control,
                 .msg_controllen = sizeof(control),
         };
-        triple_timestamp t = {};
+        triple_timestamp t;
         _cleanup_free_ DHCP6Message *message = NULL;
         struct in6_addr *server_address = NULL;
         ssize_t buflen, len;
 
         buflen = next_datagram_size_fd(fd);
+        if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
+                return 0;
         if (buflen < 0) {
-                if (ERRNO_IS_TRANSIENT(buflen) || ERRNO_IS_DISCONNECT(buflen))
-                        return 0;
-
                 log_dhcp6_client_errno(client, buflen, "Failed to determine datagram size to read, ignoring: %m");
                 return 0;
         }
@@ -1307,10 +1327,9 @@ static int client_receive_message(
         iov = IOVEC_MAKE(message, buflen);
 
         len = recvmsg_safe(fd, &msg, MSG_DONTWAIT);
+        if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len))
+                return 0;
         if (len < 0) {
-                if (ERRNO_IS_TRANSIENT(len) || ERRNO_IS_DISCONNECT(len))
-                        return 0;
-
                 log_dhcp6_client_errno(client, len, "Could not receive message from UDP socket, ignoring: %m");
                 return 0;
         }
@@ -1329,9 +1348,7 @@ static int client_receive_message(
                 server_address = &sa.in6.sin6_addr;
         }
 
-        struct timeval *tv = CMSG_FIND_AND_COPY_DATA(&msg, SOL_SOCKET, SCM_TIMESTAMP, struct timeval);
-        if (tv)
-                triple_timestamp_from_realtime(&t, timeval_load(tv));
+        triple_timestamp_from_cmsg(&t, &msg);
 
         if (client->transaction_id != (message->transaction_id & htobe32(0x00ffffff)))
                 return 0;
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c
index 3e25b4e8..a42df243 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c
@@ -10,7 +10,9 @@
 #include "alloc-util.h"
 #include "dhcp6-internal.h"
 #include "dhcp6-lease-internal.h"
+#include "network-common.h"
 #include "strv.h"
+#include "unaligned.h"
 
 #define IRT_DEFAULT (1 * USEC_PER_DAY)
 #define IRT_MINIMUM (600 * USEC_PER_SEC)
@@ -21,7 +23,7 @@ static void dhcp6_lease_set_timestamp(sd_dhcp6_lease *lease, const triple_timest
         if (timestamp && triple_timestamp_is_set(timestamp))
                 lease->timestamp = *timestamp;
         else
-                triple_timestamp_get(&lease->timestamp);
+                triple_timestamp_now(&lease->timestamp);
 }
 
 int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret) {
@@ -37,30 +39,26 @@ int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_
         return 0;
 }
 
-static usec_t sec2usec(uint32_t sec) {
-        return sec == UINT32_MAX ? USEC_INFINITY : sec * USEC_PER_SEC;
-}
-
 static void dhcp6_lease_set_lifetime(sd_dhcp6_lease *lease) {
-        uint32_t t1 = UINT32_MAX, t2 = UINT32_MAX, min_valid_lt = UINT32_MAX;
+        usec_t t1 = USEC_INFINITY, t2 = USEC_INFINITY, min_valid_lt = USEC_INFINITY;
 
         assert(lease);
         assert(lease->ia_na || lease->ia_pd);
 
         if (lease->ia_na) {
-                t1 = MIN(t1, be32toh(lease->ia_na->header.lifetime_t1));
-                t2 = MIN(t2, be32toh(lease->ia_na->header.lifetime_t2));
+                t1 = MIN(t1, be32_sec_to_usec(lease->ia_na->header.lifetime_t1, /* max_as_infinity = */ true));
+                t2 = MIN(t2, be32_sec_to_usec(lease->ia_na->header.lifetime_t2, /* max_as_infinity = */ true));
 
                 LIST_FOREACH(addresses, a, lease->ia_na->addresses)
-                        min_valid_lt = MIN(min_valid_lt, be32toh(a->iaaddr.lifetime_valid));
+                        min_valid_lt = MIN(min_valid_lt, be32_sec_to_usec(a->iaaddr.lifetime_valid, /* max_as_infinity = */ true));
         }
 
         if (lease->ia_pd) {
-                t1 = MIN(t1, be32toh(lease->ia_pd->header.lifetime_t1));
-                t2 = MIN(t2, be32toh(lease->ia_pd->header.lifetime_t2));
+                t1 = MIN(t1, be32_sec_to_usec(lease->ia_pd->header.lifetime_t1, /* max_as_infinity = */ true));
+                t2 = MIN(t2, be32_sec_to_usec(lease->ia_pd->header.lifetime_t2, /* max_as_infinity = */ true));
 
                 LIST_FOREACH(addresses, a, lease->ia_pd->addresses)
-                        min_valid_lt = MIN(min_valid_lt, be32toh(a->iapdprefix.lifetime_valid));
+                        min_valid_lt = MIN(min_valid_lt, be32_sec_to_usec(a->iapdprefix.lifetime_valid, /* max_as_infinity = */ true));
         }
 
         if (t2 == 0 || t2 > min_valid_lt) {
@@ -70,25 +68,52 @@ static void dhcp6_lease_set_lifetime(sd_dhcp6_lease *lease) {
                 t2 = min_valid_lt / 10 * 8;
         }
 
-        lease->lifetime_valid = sec2usec(min_valid_lt);
-        lease->lifetime_t1 = sec2usec(t1);
-        lease->lifetime_t2 = sec2usec(t2);
-}
-
-int dhcp6_lease_get_lifetime(sd_dhcp6_lease *lease, usec_t *ret_t1, usec_t *ret_t2, usec_t *ret_valid) {
-        assert(lease);
+        lease->lifetime_valid = min_valid_lt;
+        lease->lifetime_t1 = t1;
+        lease->lifetime_t2 = t2;
+}
+
+#define DEFINE_GET_TIME_FUNCTIONS(name, val)                            \
+        int sd_dhcp6_lease_get_##name(                                  \
+                        sd_dhcp6_lease *lease,                          \
+                        uint64_t *ret) {                                \
+                                                                        \
+                assert_return(lease, -EINVAL);                          \
+                                                                        \
+                if (!lease->ia_na && !lease->ia_pd)                     \
+                        return -ENODATA;                                \
+                                                                        \
+                if (ret)                                                \
+                        *ret = lease->val;                              \
+                return 0;                                               \
+        }                                                               \
+                                                                        \
+        int sd_dhcp6_lease_get_##name##_timestamp(                      \
+                        sd_dhcp6_lease *lease,                          \
+                        clockid_t clock,                                \
+                        uint64_t *ret) {                                \
+                                                                        \
+                usec_t s, t;                                            \
+                int r;                                                  \
+                                                                        \
+                assert_return(lease, -EINVAL);                          \
+                                                                        \
+                r = sd_dhcp6_lease_get_##name(lease, &s);               \
+                if (r < 0)                                              \
+                        return r;                                       \
+                                                                        \
+                r = sd_dhcp6_lease_get_timestamp(lease, clock, &t);     \
+                if (r < 0)                                              \
+                        return r;                                       \
+                                                                        \
+                if (ret)                                                \
+                        *ret = time_span_to_stamp(s, t);                \
+                return 0;                                               \
+        }
 
-        if (!lease->ia_na && !lease->ia_pd)
-                return -ENODATA;
-
-        if (ret_t1)
-                *ret_t1 = lease->lifetime_t1;
-        if (ret_t2)
-                *ret_t2 = lease->lifetime_t2;
-        if (ret_valid)
-                *ret_valid = lease->lifetime_valid;
-        return 0;
-}
+DEFINE_GET_TIME_FUNCTIONS(t1, lifetime_t1);
+DEFINE_GET_TIME_FUNCTIONS(t2, lifetime_t1);
+DEFINE_GET_TIME_FUNCTIONS(valid_lifetime, lifetime_valid);
 
 static void dhcp6_lease_set_server_address(sd_dhcp6_lease *lease, const struct in6_addr *server_address) {
         assert(lease);
@@ -218,61 +243,151 @@ int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *ret) {
         return 0;
 }
 
-int sd_dhcp6_lease_get_address(
+int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *ret) {
+        assert_return(lease, -EINVAL);
+
+        if (!lease->addr_iter)
+                return -ENODATA;
+
+        if (ret)
+                *ret = lease->addr_iter->iaaddr.address;
+        return 0;
+}
+
+int sd_dhcp6_lease_get_address_lifetime(
                 sd_dhcp6_lease *lease,
-                struct in6_addr *ret_addr,
-                uint32_t *ret_lifetime_preferred,
-                uint32_t *ret_lifetime_valid) {
+                usec_t *ret_lifetime_preferred,
+                usec_t *ret_lifetime_valid) {
+
+        const struct iaaddr *a;
 
         assert_return(lease, -EINVAL);
 
         if (!lease->addr_iter)
                 return -ENODATA;
 
-        if (ret_addr)
-                *ret_addr = lease->addr_iter->iaaddr.address;
+        a = &lease->addr_iter->iaaddr;
+
         if (ret_lifetime_preferred)
-                *ret_lifetime_preferred = be32toh(lease->addr_iter->iaaddr.lifetime_preferred);
+                *ret_lifetime_preferred = be32_sec_to_usec(a->lifetime_preferred, /* max_as_infinity = */ true);
         if (ret_lifetime_valid)
-                *ret_lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid);
+                *ret_lifetime_valid = be32_sec_to_usec(a->lifetime_valid, /* max_as_infinity = */ true);
+        return 0;
+}
+
+int sd_dhcp6_lease_address_iterator_reset(sd_dhcp6_lease *lease) {
+        if (!lease)
+                return false;
+
+        lease->addr_iter = lease->ia_na ? lease->ia_na->addresses : NULL;
+        return !!lease->addr_iter;
+}
+
+int sd_dhcp6_lease_address_iterator_next(sd_dhcp6_lease *lease) {
+        if (!lease || !lease->addr_iter)
+                return false;
 
         lease->addr_iter = lease->addr_iter->addresses_next;
-        return 0;
+        return !!lease->addr_iter;
 }
 
-void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) {
-        if (lease)
-                lease->addr_iter = lease->ia_na ? lease->ia_na->addresses : NULL;
+int sd_dhcp6_lease_has_address(sd_dhcp6_lease *lease) {
+        return lease && lease->ia_na;
 }
 
-int sd_dhcp6_lease_get_pd(
+int sd_dhcp6_lease_get_pd_prefix(
                 sd_dhcp6_lease *lease,
                 struct in6_addr *ret_prefix,
-                uint8_t *ret_prefix_len,
-                uint32_t *ret_lifetime_preferred,
-                uint32_t *ret_lifetime_valid) {
+                uint8_t *ret_prefix_len) {
+
+        const struct iapdprefix *a;
 
         assert_return(lease, -EINVAL);
 
         if (!lease->prefix_iter)
                 return -ENODATA;
 
+        a = &lease->prefix_iter->iapdprefix;
+
         if (ret_prefix)
-                *ret_prefix = lease->prefix_iter->iapdprefix.address;
+                *ret_prefix = a->address;
         if (ret_prefix_len)
-                *ret_prefix_len = lease->prefix_iter->iapdprefix.prefixlen;
+                *ret_prefix_len = a->prefixlen;
+        return 0;
+}
+
+int sd_dhcp6_lease_get_pd_lifetime(
+                sd_dhcp6_lease *lease,
+                uint64_t *ret_lifetime_preferred,
+                uint64_t *ret_lifetime_valid) {
+
+        const struct iapdprefix *a;
+
+        assert_return(lease, -EINVAL);
+
+        if (!lease->prefix_iter)
+                return -ENODATA;
+
+        a = &lease->prefix_iter->iapdprefix;
+
         if (ret_lifetime_preferred)
-                *ret_lifetime_preferred = be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred);
+                *ret_lifetime_preferred = be32_sec_to_usec(a->lifetime_preferred, /* max_as_infinity = */ true);
         if (ret_lifetime_valid)
-                *ret_lifetime_valid = be32toh(lease->prefix_iter->iapdprefix.lifetime_valid);
-
-        lease->prefix_iter = lease->prefix_iter->addresses_next;
+                *ret_lifetime_valid = be32_sec_to_usec(a->lifetime_valid, /* max_as_infinity = */ true);
         return 0;
 }
 
-void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) {
-        if (lease)
-                lease->prefix_iter = lease->ia_pd ? lease->ia_pd->addresses : NULL;
+int sd_dhcp6_lease_pd_iterator_reset(sd_dhcp6_lease *lease) {
+        if (!lease)
+                return false;
+
+        lease->prefix_iter = lease->ia_pd ? lease->ia_pd->addresses : NULL;
+        return !!lease->prefix_iter;
+}
+
+int sd_dhcp6_lease_pd_iterator_next(sd_dhcp6_lease *lease) {
+        if (!lease || !lease->prefix_iter)
+                return false;
+
+        lease->prefix_iter = lease->prefix_iter->addresses_next;
+        return !!lease->prefix_iter;
+}
+
+#define DEFINE_GET_TIMESTAMP2(name)                                     \
+        int sd_dhcp6_lease_get_##name##_lifetime_timestamp(             \
+                        sd_dhcp6_lease *lease,                          \
+                        clockid_t clock,                                \
+                        uint64_t *ret_lifetime_preferred,               \
+                        uint64_t *ret_lifetime_valid) {                 \
+                                                                        \
+                usec_t t, p, v;                                         \
+                int r;                                                  \
+                                                                        \
+                assert_return(lease, -EINVAL);                          \
+                                                                        \
+                r = sd_dhcp6_lease_get_##name##_lifetime(               \
+                                lease,                                  \
+                                ret_lifetime_preferred ? &p : NULL,     \
+                                ret_lifetime_valid ? &v : NULL);        \
+                if (r < 0)                                              \
+                        return r;                                       \
+                                                                        \
+                r = sd_dhcp6_lease_get_timestamp(lease, clock, &t);     \
+                if (r < 0)                                              \
+                        return r;                                       \
+                                                                        \
+                if (ret_lifetime_preferred)                             \
+                        *ret_lifetime_preferred = time_span_to_stamp(p, t); \
+                if (ret_lifetime_valid)                                 \
+                        *ret_lifetime_valid = time_span_to_stamp(v, t); \
+                return 0;                                               \
+        }
+
+DEFINE_GET_TIMESTAMP2(address);
+DEFINE_GET_TIMESTAMP2(pd);
+
+int sd_dhcp6_lease_has_pd_prefix(sd_dhcp6_lease *lease) {
+        return lease && lease->ia_pd;
 }
 
 int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
@@ -447,6 +562,111 @@ int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret) {
         return 0;
 }
 
+int dhcp6_lease_set_captive_portal(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
+        _cleanup_free_ char *uri = NULL;
+        int r;
+
+        assert(lease);
+        assert(optval || optlen == 0);
+
+        r = dhcp6_option_parse_string(optval, optlen, &uri);
+        if (r < 0)
+                return r;
+
+        if (uri && !in_charset(uri, URI_VALID))
+                return -EINVAL;
+
+        return free_and_replace(lease->captive_portal, uri);
+}
+
+int sd_dhcp6_lease_get_captive_portal(sd_dhcp6_lease *lease, const char **ret) {
+        assert_return(lease, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!lease->captive_portal)
+                return -ENODATA;
+
+        *ret = lease->captive_portal;
+        return 0;
+}
+
+int sd_dhcp6_lease_get_vendor_options(sd_dhcp6_lease *lease, sd_dhcp6_option ***ret) {
+        int r;
+
+        assert_return(lease, -EINVAL);
+
+        if (set_isempty(lease->vendor_options))
+                return -ENODATA;
+
+        if (ret) {
+                if (!lease->sorted_vendor_options) {
+                        r = set_dump_sorted(lease->vendor_options, (void***) &lease->sorted_vendor_options, NULL);
+                        if (r < 0)
+                                return r;
+                }
+
+                *ret = lease->sorted_vendor_options;
+        }
+
+        return set_size(lease->vendor_options);
+}
+
+static int dhcp6_lease_insert_vendor_option(
+                sd_dhcp6_lease *lease,
+                uint16_t option_code,
+                const void *data,
+                size_t len,
+                uint32_t enterprise_id) {
+
+        _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *option = NULL;
+
+        assert(lease);
+
+        option = new(sd_dhcp6_option, 1);
+        if (!option)
+                return -ENOMEM;
+
+        *option = (sd_dhcp6_option) {
+                .n_ref = 1,
+                .enterprise_identifier = enterprise_id,
+                .option = option_code,
+                .length = len,
+        };
+        option->data = memdup_suffix0(data, len);
+        if (!option->data)
+                return -ENOMEM;
+
+        return set_ensure_consume(&lease->vendor_options, &dhcp6_option_hash_ops, TAKE_PTR(option));
+}
+
+static int dhcp6_lease_add_vendor_option(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
+        int r;
+        uint32_t enterprise_id;
+
+        assert(lease);
+        assert(optval || optlen == 0);
+
+        if (optlen < sizeof(be32_t))
+                return -EBADMSG;
+
+        enterprise_id = unaligned_read_be32(optval);
+
+        for (size_t offset = 4; offset < optlen;) {
+                const uint8_t *subval;
+                size_t sublen;
+                uint16_t subopt;
+
+                r = dhcp6_option_parse(optval, optlen, &offset, &subopt, &sublen, &subval);
+                if (r < 0)
+                        return r;
+
+                r = dhcp6_lease_insert_vendor_option(lease, subopt, subval, sublen, enterprise_id);
+                if (r < 0)
+                        return r;
+        }
+        return 0;
+}
+
 static int dhcp6_lease_parse_message(
                 sd_dhcp6_client *client,
                 sd_dhcp6_lease *lease,
@@ -467,6 +687,11 @@ static int dhcp6_lease_parse_message(
                 size_t optlen;
                 const uint8_t *optval;
 
+                if (len - offset < offsetof(DHCP6Option, data)) {
+                        log_dhcp6_client(client, "Ignoring %zu invalid byte(s) at the end of the packet", len - offset);
+                        break;
+                }
+
                 r = dhcp6_option_parse(message->options, len, &offset, &optcode, &optlen, &optval);
                 if (r < 0)
                         return log_dhcp6_client_errno(client, r,
@@ -607,6 +832,12 @@ static int dhcp6_lease_parse_message(
 
                         break;
 
+                case SD_DHCP6_OPTION_CAPTIVE_PORTAL:
+                        r = dhcp6_lease_set_captive_portal(lease, optval, optlen);
+                        if (r < 0)
+                                log_dhcp6_client_errno(client, r, "Failed to parse captive portal option, ignoring: %m");
+                        break;
+
                 case SD_DHCP6_OPTION_CLIENT_FQDN:
                         r = dhcp6_lease_set_fqdn(lease, optval, optlen);
                         if (r < 0)
@@ -619,7 +850,14 @@ static int dhcp6_lease_parse_message(
                                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
                                                               "Received information refresh time option with an invalid length (%zu).", optlen);
 
-                        irt = unaligned_read_be32(optval) * USEC_PER_SEC;
+                        irt = unaligned_be32_sec_to_usec(optval, /* max_as_infinity = */ false);
+                        break;
+
+                case SD_DHCP6_OPTION_VENDOR_OPTS:
+                        r = dhcp6_lease_add_vendor_option(lease, optval, optlen);
+                        if (r < 0)
+                                log_dhcp6_client_errno(client, r, "Failed to parse vendor option, ignoring: %m");
+
                         break;
                 }
         }
@@ -631,7 +869,7 @@ static int dhcp6_lease_parse_message(
                                               "%s message does not contain client ID. Ignoring.",
                                               dhcp6_message_type_to_string(message->type));
 
-        if (memcmp_nn(clientid, clientid_len, &client->duid, client->duid_len) != 0)
+        if (memcmp_nn(clientid, clientid_len, &client->duid.duid, client->duid.size) != 0)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
                                               "The client ID in %s message does not match. Ignoring.",
                                               dhcp6_message_type_to_string(message->type));
@@ -661,12 +899,15 @@ static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
         if (!lease)
                 return NULL;
 
+        set_free(lease->vendor_options);
+        free(lease->sorted_vendor_options);
         free(lease->clientid);
         free(lease->serverid);
         dhcp6_ia_free(lease->ia_na);
         dhcp6_ia_free(lease->ia_pd);
         free(lease->dns);
         free(lease->fqdn);
+        free(lease->captive_portal);
         strv_free(lease->domains);
         free(lease->ntp);
         strv_free(lease->ntp_fqdn);
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h b/src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h
new file mode 100644
index 00000000..a465eb25
--- /dev/null
+++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-device.h"
+
+#include "device-private.h"
+#include "hashmap.h"
+#include "set.h"
+#include "time-util.h"
+
+#define LATEST_UDEV_DATABASE_VERSION 1
+
+struct sd_device {
+        unsigned n_ref;
+
+        /* The database version indicates the supported features by the udev database.
+         * This is saved and parsed in V field.
+         *
+         * 0: None of the following features are supported (systemd version <= 246).
+         * 1: The current tags (Q) and the database version (V) features are implemented (>= 247).
+         */
+        unsigned database_version;
+
+        sd_device *parent;
+
+        OrderedHashmap *properties;
+        Iterator properties_iterator;
+        uint64_t properties_generation; /* changes whenever the properties are changed */
+        uint64_t properties_iterator_generation; /* generation when iteration was started */
+
+        /* the subset of the properties that should be written to the db */
+        OrderedHashmap *properties_db;
+
+        Hashmap *sysattr_values; /* cached sysattr values */
+
+        Set *sysattrs; /* names of sysattrs */
+        Iterator sysattrs_iterator;
+
+        Set *all_tags, *current_tags;
+        Iterator all_tags_iterator, current_tags_iterator;
+        uint64_t all_tags_iterator_generation, current_tags_iterator_generation; /* generation when iteration was started */
+        uint64_t tags_generation; /* changes whenever the tags are changed */
+
+        Set *devlinks;
+        Iterator devlinks_iterator;
+        uint64_t devlinks_generation; /* changes whenever the devlinks are changed */
+        uint64_t devlinks_iterator_generation; /* generation when iteration was started */
+        int devlink_priority;
+
+        Hashmap *children;
+        Iterator children_iterator;
+        bool children_enumerated;
+
+        int ifindex;
+        char *devtype;
+        char *devname;
+        dev_t devnum;
+
+        char **properties_strv; /* the properties hashmap as a strv */
+        char *properties_nulstr; /* the same as a nulstr */
+        size_t properties_nulstr_len;
+
+        char *syspath;
+        const char *devpath;
+        const char *sysnum;
+        char *sysname;
+
+        char *subsystem;
+        char *driver_subsystem; /* only set for the 'drivers' subsystem */
+        char *driver;
+
+        char *device_id;
+
+        usec_t usec_initialized;
+
+        mode_t devmode;
+        uid_t devuid;
+        gid_t devgid;
+
+        uint64_t diskseq; /* Block device sequence number, monothonically incremented by the kernel on create/attach */
+
+        /* only set when device is passed through netlink */
+        sd_device_action_t action;
+        uint64_t seqnum;
+
+        bool parent_set:1; /* no need to try to reload parent */
+        bool sysattrs_read:1; /* don't try to re-read sysattrs once read */
+        bool property_tags_outdated:1; /* need to update TAGS= or CURRENT_TAGS= property */
+        bool property_devlinks_outdated:1; /* need to update DEVLINKS= property */
+        bool properties_buf_outdated:1; /* need to reread hashmap */
+        bool subsystem_set:1; /* don't reread subsystem */
+        bool driver_set:1; /* don't reread driver */
+        bool uevent_loaded:1; /* don't reread uevent */
+        bool db_loaded; /* don't reread db */
+
+        bool is_initialized:1;
+        bool sealed:1; /* don't read more information from uevent/db */
+        bool db_persist:1; /* don't clean up the db when switching from initrd to real root */
+};
+
+int device_new_aux(sd_device **ret);
+int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db);
+static inline int device_add_property_internal(sd_device *device, const char *key, const char *value) {
+        return device_add_property_aux(device, key, value, false);
+}
+
+int device_set_syspath(sd_device *device, const char *_syspath, bool verify);
+int device_set_ifindex(sd_device *device, const char *ifindex);
+int device_set_devmode(sd_device *device, const char *devmode);
+int device_set_devname(sd_device *device, const char *devname);
+int device_set_devtype(sd_device *device, const char *devtype);
+int device_set_devnum(sd_device *device, const char *major, const char *minor);
+int device_set_subsystem(sd_device *device, const char *subsystem);
+int device_set_diskseq(sd_device *device, const char *str);
+int device_set_drivers_subsystem(sd_device *device);
+int device_set_driver(sd_device *device, const char *driver);
+int device_set_usec_initialized(sd_device *device, usec_t when);
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c
new file mode 100644
index 00000000..eef3d618
--- /dev/null
+++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c
@@ -0,0 +1,962 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <ctype.h>
+#include <net/if.h>
+#include <sys/types.h>
+
+#include "sd-device.h"
+
+#include "alloc-util.h"
+#include "device-internal.h"
+#include "device-private.h"
+#include "device-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "hashmap.h"
+#include "macro.h"
+#include "mkdir.h"
+#include "nulstr-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "set.h"
+#include "stdio-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "strxcpyx.h"
+#include "tmpfile-util.h"
+#include "user-util.h"
+
+#if 0 /* NM_IGNORED */
+int device_add_property(sd_device *device, const char *key, const char *value) {
+        int r;
+
+        assert(device);
+        assert(key);
+
+        r = device_add_property_aux(device, key, value, false);
+        if (r < 0)
+                return r;
+
+        if (key[0] != '.') {
+                r = device_add_property_aux(device, key, value, true);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int device_add_propertyf(sd_device *device, const char *key, const char *format, ...) {
+        _cleanup_free_ char *value = NULL;
+        va_list ap;
+        int r;
+
+        assert(device);
+        assert(key);
+
+        if (!format)
+                return device_add_property(device, key, NULL);
+
+        va_start(ap, format);
+        r = vasprintf(&value, format, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return -ENOMEM;
+
+        return device_add_property(device, key, value);
+}
+
+void device_set_devlink_priority(sd_device *device, int priority) {
+        assert(device);
+
+        device->devlink_priority = priority;
+}
+
+void device_set_is_initialized(sd_device *device) {
+        assert(device);
+
+        device->is_initialized = true;
+}
+
+int device_ensure_usec_initialized(sd_device *device, sd_device *device_old) {
+        usec_t when;
+
+        assert(device);
+
+        if (device_old && device_old->usec_initialized > 0)
+                when = device_old->usec_initialized;
+        else
+                when = now(CLOCK_MONOTONIC);
+
+        return device_set_usec_initialized(device, when);
+}
+
+uint64_t device_get_properties_generation(sd_device *device) {
+        assert(device);
+
+        return device->properties_generation;
+}
+
+uint64_t device_get_tags_generation(sd_device *device) {
+        assert(device);
+
+        return device->tags_generation;
+}
+
+uint64_t device_get_devlinks_generation(sd_device *device) {
+        assert(device);
+
+        return device->devlinks_generation;
+}
+
+int device_get_devnode_mode(sd_device *device, mode_t *ret) {
+        int r;
+
+        assert(device);
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        if (device->devmode == MODE_INVALID)
+                return -ENOENT;
+
+        if (ret)
+                *ret = device->devmode;
+
+        return 0;
+}
+
+int device_get_devnode_uid(sd_device *device, uid_t *ret) {
+        int r;
+
+        assert(device);
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        if (device->devuid == UID_INVALID)
+                return -ENOENT;
+
+        if (ret)
+                *ret = device->devuid;
+
+        return 0;
+}
+
+static int device_set_devuid(sd_device *device, const char *uid) {
+        uid_t u;
+        int r;
+
+        assert(device);
+        assert(uid);
+
+        r = parse_uid(uid, &u);
+        if (r < 0)
+                return r;
+
+        r = device_add_property_internal(device, "DEVUID", uid);
+        if (r < 0)
+                return r;
+
+        device->devuid = u;
+
+        return 0;
+}
+
+int device_get_devnode_gid(sd_device *device, gid_t *ret) {
+        int r;
+
+        assert(device);
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        if (device->devgid == GID_INVALID)
+                return -ENOENT;
+
+        if (ret)
+                *ret = device->devgid;
+
+        return 0;
+}
+
+static int device_set_devgid(sd_device *device, const char *gid) {
+        gid_t g;
+        int r;
+
+        assert(device);
+        assert(gid);
+
+        r = parse_gid(gid, &g);
+        if (r < 0)
+                return r;
+
+        r = device_add_property_internal(device, "DEVGID", gid);
+        if (r < 0)
+                return r;
+
+        device->devgid = g;
+
+        return 0;
+}
+
+int device_set_action(sd_device *device, sd_device_action_t a) {
+        int r;
+
+        assert(device);
+        assert(a >= 0 && a < _SD_DEVICE_ACTION_MAX);
+
+        r = device_add_property_internal(device, "ACTION", device_action_to_string(a));
+        if (r < 0)
+                return r;
+
+        device->action = a;
+
+        return 0;
+}
+
+static int device_set_action_from_string(sd_device *device, const char *action) {
+        sd_device_action_t a;
+
+        assert(device);
+        assert(action);
+
+        a = device_action_from_string(action);
+        if (a < 0)
+                return a;
+
+        return device_set_action(device, a);
+}
+
+static int device_set_seqnum(sd_device *device, const char *str) {
+        uint64_t seqnum;
+        int r;
+
+        assert(device);
+        assert(str);
+
+        r = safe_atou64(str, &seqnum);
+        if (r < 0)
+                return r;
+        if (seqnum == 0)
+                return -EINVAL;
+
+        r = device_add_property_internal(device, "SEQNUM", str);
+        if (r < 0)
+                return r;
+
+        device->seqnum = seqnum;
+
+        return 0;
+}
+
+static int device_amend(sd_device *device, const char *key, const char *value) {
+        int r;
+
+        assert(device);
+        assert(key);
+        assert(value);
+
+        if (streq(key, "DEVPATH")) {
+                char *path;
+
+                path = strjoina("/sys", value);
+
+                /* the caller must verify or trust this data (e.g., if it comes from the kernel) */
+                r = device_set_syspath(device, path, false);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set syspath to '%s': %m", path);
+        } else if (streq(key, "SUBSYSTEM")) {
+                r = device_set_subsystem(device, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set subsystem to '%s': %m", value);
+        } else if (streq(key, "DEVTYPE")) {
+                r = device_set_devtype(device, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set devtype to '%s': %m", value);
+        } else if (streq(key, "DEVNAME")) {
+                r = device_set_devname(device, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set devname to '%s': %m", value);
+        } else if (streq(key, "USEC_INITIALIZED")) {
+                usec_t t;
+
+                r = safe_atou64(value, &t);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to parse timestamp '%s': %m", value);
+
+                r = device_set_usec_initialized(device, t);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set usec-initialized to '%s': %m", value);
+        } else if (streq(key, "DRIVER")) {
+                r = device_set_driver(device, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set driver to '%s': %m", value);
+        } else if (streq(key, "IFINDEX")) {
+                r = device_set_ifindex(device, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set ifindex to '%s': %m", value);
+        } else if (streq(key, "DEVMODE")) {
+                r = device_set_devmode(device, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set devmode to '%s': %m", value);
+        } else if (streq(key, "DEVUID")) {
+                r = device_set_devuid(device, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set devuid to '%s': %m", value);
+        } else if (streq(key, "DEVGID")) {
+                r = device_set_devgid(device, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set devgid to '%s': %m", value);
+        } else if (streq(key, "ACTION")) {
+                r = device_set_action_from_string(device, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set action to '%s': %m", value);
+        } else if (streq(key, "SEQNUM")) {
+                r = device_set_seqnum(device, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set SEQNUM to '%s': %m", value);
+        } else if (streq(key, "DISKSEQ")) {
+                r = device_set_diskseq(device, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set DISKSEQ to '%s': %m", value);
+        } else if (streq(key, "DEVLINKS")) {
+                for (const char *p = value;;) {
+                        _cleanup_free_ char *word = NULL;
+
+                        /* udev rules may set escaped strings, and sd-device does not modify the input
+                         * strings. So, it is also necessary to keep the strings received through
+                         * sd-device-monitor. */
+                        r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                break;
+
+                        r = device_add_devlink(device, word);
+                        if (r < 0)
+                                return log_device_debug_errno(device, r, "sd-device: Failed to add devlink '%s': %m", word);
+                }
+        } else if (STR_IN_SET(key, "TAGS", "CURRENT_TAGS")) {
+                for (const char *p = value;;) {
+                        _cleanup_free_ char *word = NULL;
+
+                        r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                break;
+                        if (isempty(word))
+                                continue;
+
+                        r = device_add_tag(device, word, streq(key, "CURRENT_TAGS"));
+                        if (r < 0)
+                                return log_device_debug_errno(device, r, "sd-device: Failed to add tag '%s': %m", word);
+                }
+        } else if (streq(key, "UDEV_DATABASE_VERSION")) {
+                r = safe_atou(value, &device->database_version);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to parse udev database version '%s': %m", value);
+        } else {
+                r = device_add_property_internal(device, key, value);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to add property '%s=%s': %m", key, value);
+        }
+
+        return 0;
+}
+
+static int device_append(
+                sd_device *device,
+                char *key,
+                const char **_major,
+                const char **_minor) {
+
+        const char *major = NULL, *minor = NULL;
+        char *value;
+        int r;
+
+        assert(device);
+        assert(key);
+        assert(_major);
+        assert(_minor);
+
+        value = strchr(key, '=');
+        if (!value)
+                return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL),
+                                              "sd-device: Not a key-value pair: '%s'", key);
+
+        *value = '\0';
+
+        value++;
+
+        if (streq(key, "MAJOR"))
+                major = value;
+        else if (streq(key, "MINOR"))
+                minor = value;
+        else {
+                r = device_amend(device, key, value);
+                if (r < 0)
+                        return r;
+        }
+
+        if (major)
+                *_major = major;
+
+        if (minor)
+                *_minor = minor;
+
+        return 0;
+}
+
+void device_seal(sd_device *device) {
+        assert(device);
+
+        device->sealed = true;
+}
+
+static int device_verify(sd_device *device) {
+        int r;
+
+        assert(device);
+
+        if (!device->devpath || !device->subsystem || device->action < 0 || device->seqnum == 0)
+                return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL),
+                                              "sd-device: Device created from strv or nulstr lacks devpath, subsystem, action or seqnum.");
+
+        if (streq(device->subsystem, "drivers")) {
+                r = device_set_drivers_subsystem(device);
+                if (r < 0)
+                        return r;
+        }
+
+        device->sealed = true;
+
+        return 0;
+}
+
+int device_new_from_strv(sd_device **ret, char **strv) {
+        _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+        const char *major = NULL, *minor = NULL;
+        int r;
+
+        assert(ret);
+        assert(strv);
+
+        r = device_new_aux(&device);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(key, strv) {
+                r = device_append(device, *key, &major, &minor);
+                if (r < 0)
+                        return r;
+        }
+
+        if (major) {
+                r = device_set_devnum(device, major, minor);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set devnum %s:%s: %m", major, minor);
+        }
+
+        r = device_verify(device);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(device);
+
+        return 0;
+}
+
+int device_new_from_nulstr(sd_device **ret, char *nulstr, size_t len) {
+        _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+        const char *major = NULL, *minor = NULL;
+        int r;
+
+        assert(ret);
+        assert(nulstr);
+        assert(len);
+
+        r = device_new_aux(&device);
+        if (r < 0)
+                return r;
+
+        for (size_t i = 0; i < len; ) {
+                char *key;
+                const char *end;
+
+                key = nulstr + i;
+                end = memchr(key, '\0', len - i);
+                if (!end)
+                        return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL),
+                                                      "sd-device: Failed to parse nulstr");
+
+                i += end - key + 1;
+
+                /* netlink messages for some devices contain an unwanted newline at the end of value.
+                 * Let's drop the newline and remaining characters after the newline. */
+                truncate_nl(key);
+
+                r = device_append(device, key, &major, &minor);
+                if (r < 0)
+                        return r;
+        }
+
+        if (major) {
+                r = device_set_devnum(device, major, minor);
+                if (r < 0)
+                        return log_device_debug_errno(device, r, "sd-device: Failed to set devnum %s:%s: %m", major, minor);
+        }
+
+        r = device_verify(device);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(device);
+
+        return 0;
+}
+
+static int device_update_properties_bufs(sd_device *device) {
+        _cleanup_free_ char **buf_strv = NULL, *buf_nulstr = NULL;
+        size_t nulstr_len = 0, num = 0;
+
+        assert(device);
+
+        if (!device->properties_buf_outdated)
+                return 0;
+
+        /* append udev database version */
+        buf_nulstr = newdup(char, "UDEV_DATABASE_VERSION=" STRINGIFY(LATEST_UDEV_DATABASE_VERSION) "\0",
+                            STRLEN("UDEV_DATABASE_VERSION=" STRINGIFY(LATEST_UDEV_DATABASE_VERSION)) + 2);
+        if (!buf_nulstr)
+                return -ENOMEM;
+
+        nulstr_len += STRLEN("UDEV_DATABASE_VERSION=" STRINGIFY(LATEST_UDEV_DATABASE_VERSION)) + 1;
+        num++;
+
+        FOREACH_DEVICE_PROPERTY(device, prop, val) {
+                size_t len = 0;
+
+                len = strlen(prop) + 1 + strlen(val);
+
+                buf_nulstr = GREEDY_REALLOC0(buf_nulstr, nulstr_len + len + 2);
+                if (!buf_nulstr)
+                        return -ENOMEM;
+
+                strscpyl(buf_nulstr + nulstr_len, len + 1, prop, "=", val, NULL);
+                nulstr_len += len + 1;
+                num++;
+        }
+
+        /* build buf_strv from buf_nulstr */
+        buf_strv = new0(char*, num + 1);
+        if (!buf_strv)
+                return -ENOMEM;
+
+        size_t i = 0;
+        NULSTR_FOREACH(p, buf_nulstr)
+                buf_strv[i++] = p;
+        assert(i == num);
+
+        free_and_replace(device->properties_nulstr, buf_nulstr);
+        device->properties_nulstr_len = nulstr_len;
+        free_and_replace(device->properties_strv, buf_strv);
+
+        device->properties_buf_outdated = false;
+        return 0;
+}
+
+int device_get_properties_nulstr(sd_device *device, const char **ret_nulstr, size_t *ret_len) {
+        int r;
+
+        assert(device);
+
+        r = device_update_properties_bufs(device);
+        if (r < 0)
+                return r;
+
+        if (ret_nulstr)
+                *ret_nulstr = device->properties_nulstr;
+        if (ret_len)
+                *ret_len = device->properties_nulstr_len;
+
+        return 0;
+}
+
+int device_get_properties_strv(sd_device *device, char ***ret) {
+        int r;
+
+        assert(device);
+
+        r = device_update_properties_bufs(device);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = device->properties_strv;
+
+        return 0;
+}
+
+int device_get_devlink_priority(sd_device *device, int *ret) {
+        int r;
+
+        assert(device);
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = device->devlink_priority;
+
+        return 0;
+}
+
+int device_clone_with_db(sd_device *device, sd_device **ret) {
+        _cleanup_(sd_device_unrefp) sd_device *dest = NULL;
+        const char *key, *val;
+        int r;
+
+        assert(device);
+        assert(ret);
+
+        /* The device may be already removed. Let's copy minimal set of information that was obtained through
+         * netlink socket. */
+
+        r = device_new_aux(&dest);
+        if (r < 0)
+                return r;
+
+        /* Seal device to prevent reading the uevent file, as the device may have been already removed. */
+        dest->sealed = true;
+
+        /* Copy syspath, then also devname, sysname or sysnum can be obtained. */
+        r = device_set_syspath(dest, device->syspath, false);
+        if (r < 0)
+                return r;
+
+        /* Copy other information stored in database. Here, do not use FOREACH_DEVICE_PROPERTY() and
+         * sd_device_get_property_value(), as they calls device_properties_prepare() ->
+         * device_read_uevent_file(), but as commented in the above, the device may be already removed and
+         * reading uevent file may fail. */
+        ORDERED_HASHMAP_FOREACH_KEY(val, key, device->properties) {
+                if (streq(key, "MINOR"))
+                        continue;
+
+                if (streq(key, "MAJOR")) {
+                        const char *minor = NULL;
+
+                        minor = ordered_hashmap_get(device->properties, "MINOR");
+                        r = device_set_devnum(dest, val, minor);
+                } else
+                        r = device_amend(dest, key, val);
+                if (r < 0)
+                        return r;
+
+                if (streq(key, "SUBSYSTEM") && streq(val, "drivers")) {
+                        r = free_and_strdup(&dest->driver_subsystem, device->driver_subsystem);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        /* Finally, read the udev database. */
+        r = device_read_db_internal(dest, /* force = */ true);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(dest);
+        return 0;
+}
+
+void device_cleanup_tags(sd_device *device) {
+        assert(device);
+
+        device->all_tags = set_free_free(device->all_tags);
+        device->current_tags = set_free_free(device->current_tags);
+        device->property_tags_outdated = true;
+        device->tags_generation++;
+}
+
+void device_cleanup_devlinks(sd_device *device) {
+        assert(device);
+
+        set_free_free(device->devlinks);
+        device->devlinks = NULL;
+        device->property_devlinks_outdated = true;
+        device->devlinks_generation++;
+}
+
+void device_remove_tag(sd_device *device, const char *tag) {
+        assert(device);
+        assert(tag);
+
+        free(set_remove(device->current_tags, tag));
+        device->property_tags_outdated = true;
+        device->tags_generation++;
+}
+
+static int device_tag(sd_device *device, const char *tag, bool add) {
+        const char *id;
+        char *path;
+        int r;
+
+        assert(device);
+        assert(tag);
+
+        r = device_get_device_id(device, &id);
+        if (r < 0)
+                return r;
+
+        path = strjoina("/run/udev/tags/", tag, "/", id);
+
+        if (add)
+                return touch_file(path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0444);
+
+        if (unlink(path) < 0 && errno != ENOENT)
+                return -errno;
+
+        return 0;
+}
+
+int device_tag_index(sd_device *device, sd_device *device_old, bool add) {
+        int r = 0, k;
+
+        if (add && device_old)
+                /* delete possible left-over tags */
+                FOREACH_DEVICE_TAG(device_old, tag)
+                        if (!sd_device_has_tag(device, tag)) {
+                                k = device_tag(device_old, tag, false);
+                                if (r >= 0 && k < 0)
+                                        r = k;
+                        }
+
+        FOREACH_DEVICE_TAG(device, tag) {
+                k = device_tag(device, tag, add);
+                if (r >= 0 && k < 0)
+                        r = k;
+        }
+
+        return r;
+}
+
+static bool device_has_info(sd_device *device) {
+        assert(device);
+
+        if (!set_isempty(device->devlinks))
+                return true;
+
+        if (device->devlink_priority != 0)
+                return true;
+
+        if (!ordered_hashmap_isempty(device->properties_db))
+                return true;
+
+        if (!set_isempty(device->all_tags))
+                return true;
+
+        if (!set_isempty(device->current_tags))
+                return true;
+
+        return false;
+}
+
+bool device_should_have_db(sd_device *device) {
+        assert(device);
+
+        if (device_has_info(device))
+                return true;
+
+        if (major(device->devnum) != 0)
+                return true;
+
+        if (device->ifindex != 0)
+                return true;
+
+        return false;
+}
+
+void device_set_db_persist(sd_device *device) {
+        assert(device);
+
+        device->db_persist = true;
+}
+#endif /* NM_IGNORED */
+
+static int device_get_db_path(sd_device *device, char **ret) {
+        const char *id;
+        char *path;
+        int r;
+
+        assert(device);
+        assert(ret);
+
+        r = device_get_device_id(device, &id);
+        if (r < 0)
+                return r;
+
+        path = path_join("/run/udev/data/", id);
+        if (!path)
+                return -ENOMEM;
+
+        *ret = path;
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int device_has_db(sd_device *device) {
+        _cleanup_free_ char *path = NULL;
+        int r;
+
+        assert(device);
+
+        r = device_get_db_path(device, &path);
+        if (r < 0)
+                return r;
+
+        return access(path, F_OK) >= 0;
+}
+
+int device_update_db(sd_device *device) {
+        _cleanup_(unlink_and_freep) char *path = NULL, *path_tmp = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
+
+        assert(device);
+
+        /* do not store anything for otherwise empty devices */
+        if (!device_should_have_db(device))
+                return device_delete_db(device);
+
+        r = device_get_db_path(device, &path);
+        if (r < 0)
+                return r;
+
+        /* write a database file */
+        r = mkdir_parents(path, 0755);
+        if (r < 0)
+                return log_device_debug_errno(device, r,
+                                              "sd-device: Failed to create parent directories of '%s': %m",
+                                              path);
+
+        r = fopen_temporary(path, &f, &path_tmp);
+        if (r < 0)
+                return log_device_debug_errno(device, r,
+                                              "sd-device: Failed to create temporary file for database file '%s': %m",
+                                              path);
+
+        /* set 'sticky' bit to indicate that we should not clean the database when we transition from initrd
+         * to the real root */
+        if (fchmod(fileno(f), device->db_persist ? 01644 : 0644) < 0)
+                return log_device_debug_errno(device, errno,
+                                              "sd-device: Failed to chmod temporary database file '%s': %m",
+                                              path_tmp);
+
+        if (device_has_info(device)) {
+                const char *property, *value, *ct;
+
+                if (major(device->devnum) > 0) {
+                        FOREACH_DEVICE_DEVLINK(device, devlink)
+                                fprintf(f, "S:%s\n", devlink + STRLEN("/dev/"));
+
+                        if (device->devlink_priority != 0)
+                                fprintf(f, "L:%i\n", device->devlink_priority);
+                }
+
+                if (device->usec_initialized > 0)
+                        fprintf(f, "I:"USEC_FMT"\n", device->usec_initialized);
+
+                ORDERED_HASHMAP_FOREACH_KEY(value, property, device->properties_db)
+                        fprintf(f, "E:%s=%s\n", property, value);
+
+                FOREACH_DEVICE_TAG(device, tag)
+                        fprintf(f, "G:%s\n", tag); /* Any tag */
+
+                SET_FOREACH(ct, device->current_tags)
+                        fprintf(f, "Q:%s\n", ct); /* Current tag */
+
+                /* Always write the latest database version here, instead of the value stored in
+                 * device->database_version, as which may be 0. */
+                fputs("V:" STRINGIFY(LATEST_UDEV_DATABASE_VERSION) "\n", f);
+        }
+
+        r = fflush_and_check(f);
+        if (r < 0)
+                return log_device_debug_errno(device, r,
+                                              "sd-device: Failed to flush temporary database file '%s': %m",
+                                              path_tmp);
+
+        if (rename(path_tmp, path) < 0)
+                return log_device_debug_errno(device, errno,
+                                              "sd-device: Failed to rename temporary database file '%s' to '%s': %m",
+                                              path_tmp, path);
+
+        log_device_debug(device, "sd-device: Created database file '%s' for '%s'.", path, device->devpath);
+
+        path_tmp = mfree(path_tmp);
+        path = mfree(path);
+
+        return 0;
+}
+
+int device_delete_db(sd_device *device) {
+        _cleanup_free_ char *path = NULL;
+        int r;
+
+        assert(device);
+
+        r = device_get_db_path(device, &path);
+        if (r < 0)
+                return r;
+
+        if (unlink(path) < 0 && errno != ENOENT)
+                return -errno;
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+int device_read_db_internal(sd_device *device, bool force) {
+        _cleanup_free_ char *path = NULL;
+        int r;
+
+        assert(device);
+
+        if (device->db_loaded || (!force && device->sealed))
+                return 0;
+
+        r = device_get_db_path(device, &path);
+        if (r < 0)
+                return r;
+
+        return device_read_db_internal_filename(device, path);
+}
+
+#if 0 /* NM_IGNORED */
+static const char* const device_action_table[_SD_DEVICE_ACTION_MAX] = {
+        [SD_DEVICE_ADD]     = "add",
+        [SD_DEVICE_REMOVE]  = "remove",
+        [SD_DEVICE_CHANGE]  = "change",
+        [SD_DEVICE_MOVE]    = "move",
+        [SD_DEVICE_ONLINE]  = "online",
+        [SD_DEVICE_OFFLINE] = "offline",
+        [SD_DEVICE_BIND]    = "bind",
+        [SD_DEVICE_UNBIND]  = "unbind",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(device_action, sd_device_action_t);
+
+void dump_device_action_table(void) {
+        DUMP_STRING_TABLE(device_action, sd_device_action_t, _SD_DEVICE_ACTION_MAX);
+}
+#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h
new file mode 100644
index 00000000..e1f3b6e8
--- /dev/null
+++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <dirent.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "sd-device.h"
+
+#include "macro.h"
+
+int device_new_from_mode_and_devnum(sd_device **ret, mode_t mode, dev_t devnum);
+int device_new_from_nulstr(sd_device **ret, char *nulstr, size_t len);
+int device_new_from_strv(sd_device **ret, char **strv);
+
+int device_opendir(sd_device *device, const char *subdir, DIR **ret);
+
+int device_get_property_bool(sd_device *device, const char *key);
+int device_get_property_int(sd_device *device, const char *key, int *ret);
+int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value);
+int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value);
+int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value);
+int device_get_sysattr_bool(sd_device *device, const char *sysattr);
+int device_get_device_id(sd_device *device, const char **ret);
+int device_get_devlink_priority(sd_device *device, int *ret);
+int device_get_devnode_mode(sd_device *device, mode_t *ret);
+int device_get_devnode_uid(sd_device *device, uid_t *ret);
+int device_get_devnode_gid(sd_device *device, gid_t *ret);
+
+void device_clear_sysattr_cache(sd_device *device);
+int device_cache_sysattr_value(sd_device *device, const char *key, char *value);
+int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value);
+
+void device_seal(sd_device *device);
+void device_set_is_initialized(sd_device *device);
+void device_set_db_persist(sd_device *device);
+void device_set_devlink_priority(sd_device *device, int priority);
+int device_ensure_usec_initialized(sd_device *device, sd_device *device_old);
+int device_add_devlink(sd_device *device, const char *devlink);
+int device_remove_devlink(sd_device *device, const char *devlink);
+bool device_has_devlink(sd_device *device, const char *devlink);
+int device_add_property(sd_device *device, const char *property, const char *value);
+int device_add_propertyf(sd_device *device, const char *key, const char *format, ...) _printf_(3, 4);
+int device_add_tag(sd_device *device, const char *tag, bool both);
+void device_remove_tag(sd_device *device, const char *tag);
+void device_cleanup_tags(sd_device *device);
+void device_cleanup_devlinks(sd_device *device);
+
+uint64_t device_get_properties_generation(sd_device *device);
+uint64_t device_get_tags_generation(sd_device *device);
+uint64_t device_get_devlinks_generation(sd_device *device);
+
+int device_properties_prepare(sd_device *device);
+int device_get_properties_nulstr(sd_device *device, const char **ret_nulstr, size_t *ret_len);
+int device_get_properties_strv(sd_device *device, char ***ret);
+
+int device_clone_with_db(sd_device *device, sd_device **ret);
+
+int device_tag_index(sd_device *dev, sd_device *dev_old, bool add);
+bool device_should_have_db(sd_device *device);
+int device_has_db(sd_device *device);
+int device_update_db(sd_device *device);
+int device_delete_db(sd_device *device);
+int device_read_db_internal_filename(sd_device *device, const char *filename); /* For fuzzer */
+int device_read_db_internal(sd_device *device, bool force);
+static inline int device_read_db(sd_device *device) {
+        return device_read_db_internal(device, false);
+}
+
+int device_read_uevent_file(sd_device *device);
+
+int device_set_action(sd_device *device, sd_device_action_t a);
+sd_device_action_t device_action_from_string(const char *s) _pure_;
+const char *device_action_to_string(sd_device_action_t a) _const_;
+void dump_device_action_table(void);
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h b/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h
index a1b5e91e..534a2967 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h
+++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h
@@ -7,39 +7,41 @@
 
 #include "sd-device.h"
 
+#include "alloc-util.h"
 #include "log.h"
 #include "macro.h"
+#include "strv.h"
 
 #define device_unref_and_replace(a, b)                                  \
         unref_and_replace_full(a, b, sd_device_ref, sd_device_unref)
 
-#define FOREACH_DEVICE_PROPERTY(device, key, value)                \
-        for (key = sd_device_get_property_first(device, &(value)); \
-             key;                                                  \
-             key = sd_device_get_property_next(device, &(value)))
+#define FOREACH_DEVICE_PROPERTY(device, key, value)                     \
+        for (const char *value, *key = sd_device_get_property_first(device, &value); \
+             key;                                                       \
+             key = sd_device_get_property_next(device, &value))
 
-#define FOREACH_DEVICE_TAG(device, tag)             \
-        for (tag = sd_device_get_tag_first(device); \
-             tag;                                   \
+#define FOREACH_DEVICE_TAG(device, tag)                                 \
+        for (const char *tag = sd_device_get_tag_first(device);         \
+             tag;                                                       \
              tag = sd_device_get_tag_next(device))
 
-#define FOREACH_DEVICE_CURRENT_TAG(device, tag)             \
-        for (tag = sd_device_get_current_tag_first(device); \
-             tag;                                   \
+#define FOREACH_DEVICE_CURRENT_TAG(device, tag)                         \
+        for (const char *tag = sd_device_get_current_tag_first(device); \
+             tag;                                                       \
              tag = sd_device_get_current_tag_next(device))
 
-#define FOREACH_DEVICE_SYSATTR(device, attr)             \
-        for (attr = sd_device_get_sysattr_first(device); \
-             attr;                                       \
+#define FOREACH_DEVICE_SYSATTR(device, attr)                            \
+        for (const char *attr = sd_device_get_sysattr_first(device);    \
+             attr;                                                      \
              attr = sd_device_get_sysattr_next(device))
 
-#define FOREACH_DEVICE_DEVLINK(device, devlink)             \
-        for (devlink = sd_device_get_devlink_first(device); \
-             devlink;                                   \
+#define FOREACH_DEVICE_DEVLINK(device, devlink)                         \
+        for (const char *devlink = sd_device_get_devlink_first(device); \
+             devlink;                                                   \
              devlink = sd_device_get_devlink_next(device))
 
 #define _FOREACH_DEVICE_CHILD(device, child, suffix_ptr)                \
-        for (child = sd_device_get_child_first(device, suffix_ptr);     \
+        for (sd_device *child = sd_device_get_child_first(device, suffix_ptr); \
              child;                                                     \
              child = sd_device_get_child_next(device, suffix_ptr))
 
@@ -49,14 +51,14 @@
 #define FOREACH_DEVICE_CHILD_WITH_SUFFIX(device, child, suffix)         \
         _FOREACH_DEVICE_CHILD(device, child, &suffix)
 
-#define FOREACH_DEVICE(enumerator, device)                               \
-        for (device = sd_device_enumerator_get_device_first(enumerator); \
-             device;                                                     \
+#define FOREACH_DEVICE(enumerator, device)                              \
+        for (sd_device *device = sd_device_enumerator_get_device_first(enumerator); \
+             device;                                                    \
              device = sd_device_enumerator_get_device_next(enumerator))
 
-#define FOREACH_SUBSYSTEM(enumerator, device)                               \
-        for (device = sd_device_enumerator_get_subsystem_first(enumerator); \
-             device;                                                        \
+#define FOREACH_SUBSYSTEM(enumerator, device)                           \
+        for (sd_device *device = sd_device_enumerator_get_subsystem_first(enumerator); \
+             device;                                                    \
              device = sd_device_enumerator_get_subsystem_next(enumerator))
 
 #define log_device_full_errno_zerook(device, level, error, ...)         \
@@ -81,17 +83,17 @@
 
 #define log_device_full(device, level, ...) (void) log_device_full_errno_zerook(device, level, 0, __VA_ARGS__)
 
-#define log_device_debug(device, ...)   log_device_full(device, LOG_DEBUG, __VA_ARGS__)
-#define log_device_info(device, ...)    log_device_full(device, LOG_INFO, __VA_ARGS__)
-#define log_device_notice(device, ...)  log_device_full(device, LOG_NOTICE, __VA_ARGS__)
+#define log_device_debug(device, ...)   log_device_full(device, LOG_DEBUG,   __VA_ARGS__)
+#define log_device_info(device, ...)    log_device_full(device, LOG_INFO,    __VA_ARGS__)
+#define log_device_notice(device, ...)  log_device_full(device, LOG_NOTICE,  __VA_ARGS__)
 #define log_device_warning(device, ...) log_device_full(device, LOG_WARNING, __VA_ARGS__)
-#define log_device_error(device, ...)   log_device_full(device, LOG_ERR, __VA_ARGS__)
+#define log_device_error(device, ...)   log_device_full(device, LOG_ERR,     __VA_ARGS__)
 
-#define log_device_debug_errno(device, error, ...)   log_device_full_errno(device, LOG_DEBUG, error, __VA_ARGS__)
-#define log_device_info_errno(device, error, ...)    log_device_full_errno(device, LOG_INFO, error, __VA_ARGS__)
-#define log_device_notice_errno(device, error, ...)  log_device_full_errno(device, LOG_NOTICE, error, __VA_ARGS__)
+#define log_device_debug_errno(device, error, ...)   log_device_full_errno(device, LOG_DEBUG,   error, __VA_ARGS__)
+#define log_device_info_errno(device, error, ...)    log_device_full_errno(device, LOG_INFO,    error, __VA_ARGS__)
+#define log_device_notice_errno(device, error, ...)  log_device_full_errno(device, LOG_NOTICE,  error, __VA_ARGS__)
 #define log_device_warning_errno(device, error, ...) log_device_full_errno(device, LOG_WARNING, error, __VA_ARGS__)
-#define log_device_error_errno(device, error, ...)   log_device_full_errno(device, LOG_ERR, error, __VA_ARGS__)
+#define log_device_error_errno(device, error, ...)   log_device_full_errno(device, LOG_ERR,     error, __VA_ARGS__)
 
 int devname_from_devnum(mode_t mode, dev_t devnum, char **ret);
 static inline int devname_from_stat_rdev(const struct stat *st, char **ret) {
@@ -101,3 +103,13 @@ static inline int devname_from_stat_rdev(const struct stat *st, char **ret) {
 int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret);
 
 char** device_make_log_fields(sd_device *device);
+
+bool device_in_subsystem(sd_device *device, const char *subsystem);
+bool device_is_devtype(sd_device *device, const char *devtype);
+
+static inline bool device_property_can_set(const char *property) {
+        return property &&
+                !STR_IN_SET(property,
+                            "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER",
+                            "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS");
+}
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c b/src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c
new file mode 100644
index 00000000..0ca87146
--- /dev/null
+++ b/src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c
@@ -0,0 +1,2724 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <ctype.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#include "sd-device.h"
+
+#include "alloc-util.h"
+#include "chase.h"
+#include "device-internal.h"
+#include "device-private.h"
+#include "device-util.h"
+#include "devnum-util.h"
+#include "dirent-util.h"
+#include "env-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "hashmap.h"
+#if 0 /* NM_IGNORED */
+#include "id128-util.h"
+#endif /* NM_IGNORED */
+#include "macro.h"
+#include "missing_magic.h"
+#if 0 /* NM_IGNORED */
+#include "netlink-util.h"
+#endif /* NM_IGNORED */
+#include "parse-util.h"
+#include "path-util.h"
+#include "set.h"
+#include "socket-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "strxcpyx.h"
+#include "user-util.h"
+
+#if 0 /* NM_IGNORED */
+int device_new_aux(sd_device **ret) {
+        sd_device *device;
+
+        assert(ret);
+
+        device = new(sd_device, 1);
+        if (!device)
+                return -ENOMEM;
+
+        *device = (sd_device) {
+                .n_ref = 1,
+                .devmode = MODE_INVALID,
+                .devuid = UID_INVALID,
+                .devgid = GID_INVALID,
+                .action = _SD_DEVICE_ACTION_INVALID,
+        };
+
+        *ret = device;
+        return 0;
+}
+
+static sd_device *device_free(sd_device *device) {
+        assert(device);
+
+        sd_device_unref(device->parent);
+        free(device->syspath);
+        free(device->sysname);
+        free(device->devtype);
+        free(device->devname);
+        free(device->subsystem);
+        free(device->driver_subsystem);
+        free(device->driver);
+        free(device->device_id);
+        free(device->properties_strv);
+        free(device->properties_nulstr);
+
+        ordered_hashmap_free(device->properties);
+        ordered_hashmap_free(device->properties_db);
+        hashmap_free(device->sysattr_values);
+        set_free(device->sysattrs);
+        set_free(device->all_tags);
+        set_free(device->current_tags);
+        set_free(device->devlinks);
+        hashmap_free(device->children);
+
+        return mfree(device);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_device, sd_device, device_free);
+#endif /* NM_IGNORED */
+
+int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db) {
+        OrderedHashmap **properties;
+
+        assert(device);
+        assert(key);
+
+        if (db)
+                properties = &device->properties_db;
+        else
+                properties = &device->properties;
+
+        if (value) {
+                _unused_ _cleanup_free_ char *old_value = NULL;
+                _cleanup_free_ char *new_key = NULL, *new_value = NULL, *old_key = NULL;
+                int r;
+
+                r = ordered_hashmap_ensure_allocated(properties, &string_hash_ops_free_free);
+                if (r < 0)
+                        return r;
+
+                new_key = strdup(key);
+                if (!new_key)
+                        return -ENOMEM;
+
+                new_value = strdup(value);
+                if (!new_value)
+                        return -ENOMEM;
+
+                old_value = ordered_hashmap_get2(*properties, key, (void**) &old_key);
+
+                /* ordered_hashmap_replace() does not fail when the hashmap already has the entry. */
+                r = ordered_hashmap_replace(*properties, new_key, new_value);
+                if (r < 0)
+                        return r;
+
+                TAKE_PTR(new_key);
+                TAKE_PTR(new_value);
+        } else {
+                _unused_ _cleanup_free_ char *old_value = NULL;
+                _cleanup_free_ char *old_key = NULL;
+
+                old_value = ordered_hashmap_remove2(*properties, key, (void**) &old_key);
+        }
+
+        if (!db) {
+                device->properties_generation++;
+                device->properties_buf_outdated = true;
+        }
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int device_set_syspath(sd_device *device, const char *_syspath, bool verify) {
+        _cleanup_free_ char *syspath = NULL;
+        const char *devpath;
+        int r;
+
+        assert(device);
+        assert(_syspath);
+
+        if (verify) {
+                _cleanup_close_ int fd = -EBADF;
+
+                /* The input path maybe a symlink located outside of /sys. Let's try to chase the symlink at first.
+                 * The primary use case is that e.g. /proc/device-tree is a symlink to /sys/firmware/devicetree/base.
+                 * By chasing symlinks in the path at first, we can call sd_device_new_from_path() with such path. */
+                r = chase(_syspath, NULL, 0, &syspath, &fd);
+                if (r == -ENOENT)
+                         /* the device does not exist (any more?) */
+                        return log_debug_errno(SYNTHETIC_ERRNO(ENODEV),
+                                               "sd-device: Failed to chase symlinks in \"%s\".", _syspath);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-device: Failed to get target of '%s': %m", _syspath);
+
+                if (!path_startswith(syspath, "/sys")) {
+                        _cleanup_free_ char *real_sys = NULL, *new_syspath = NULL;
+                        char *p;
+
+                        /* /sys is a symlink to somewhere sysfs is mounted on? In that case, we convert the path to real sysfs to "/sys". */
+                        r = chase("/sys", NULL, 0, &real_sys, NULL);
+                        if (r < 0)
+                                return log_debug_errno(r, "sd-device: Failed to chase symlink /sys: %m");
+
+                        p = path_startswith(syspath, real_sys);
+                        if (!p)
+                                return log_debug_errno(SYNTHETIC_ERRNO(ENODEV),
+                                                       "sd-device: Canonicalized path '%s' does not starts with sysfs mount point '%s'",
+                                                       syspath, real_sys);
+
+                        new_syspath = path_join("/sys", p);
+                        if (!new_syspath)
+                                return log_oom_debug();
+
+                        free_and_replace(syspath, new_syspath);
+                        path_simplify(syspath);
+                }
+
+                if (path_startswith(syspath, "/sys/devices/")) {
+                        /* For proper devices, stricter rules apply: they must have a 'uevent' file,
+                         * otherwise we won't allow them */
+
+                        if (faccessat(fd, "uevent", F_OK, 0) < 0) {
+                                if (errno == ENOENT)
+                                        /* This is not a valid device.  Note, this condition is quite often
+                                         * satisfied when enumerating devices or finding a parent device.
+                                         * Hence, use log_trace_errno() here. */
+                                        return log_trace_errno(SYNTHETIC_ERRNO(ENODEV),
+                                                               "sd-device: the uevent file \"%s/uevent\" does not exist.", syspath);
+                                if (errno == ENOTDIR)
+                                        /* Not actually a directory. */
+                                        return log_debug_errno(SYNTHETIC_ERRNO(ENODEV),
+                                                               "sd-device: the syspath \"%s\" is not a directory.", syspath);
+
+                                return log_debug_errno(errno, "sd-device: cannot find uevent file for %s: %m", syspath);
+                        }
+                } else {
+                        struct stat st;
+
+                        /* For everything else lax rules apply: they just need to be a directory */
+
+                        if (fstat(fd, &st) < 0)
+                                return log_debug_errno(errno, "sd-device: failed to check if syspath \"%s\" is a directory: %m", syspath);
+                        if (!S_ISDIR(st.st_mode))
+                                return log_debug_errno(SYNTHETIC_ERRNO(ENODEV),
+                                                       "sd-device: the syspath \"%s\" is not a directory.", syspath);
+                }
+
+                /* Only operate on sysfs, i.e. refuse going down into /sys/fs/cgroup/ or similar places where
+                 * things are not arranged as kobjects in kernel, and hence don't necessarily have
+                 * kobject/attribute structure. */
+                r = secure_getenv_bool("SYSTEMD_DEVICE_VERIFY_SYSFS");
+                if (r < 0 && r != -ENXIO)
+                        log_debug_errno(r, "Failed to parse $SYSTEMD_DEVICE_VERIFY_SYSFS value: %m");
+                if (r != 0) {
+                        r = fd_is_fs_type(fd, SYSFS_MAGIC);
+                        if (r < 0)
+                                return log_debug_errno(r, "sd-device: failed to check if syspath \"%s\" is backed by sysfs.", syspath);
+                        if (r == 0)
+                                return log_debug_errno(SYNTHETIC_ERRNO(ENODEV),
+                                                       "sd-device: the syspath \"%s\" is outside of sysfs, refusing.", syspath);
+                }
+        } else {
+                /* must be a subdirectory of /sys */
+                if (!path_startswith(_syspath, "/sys/"))
+                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "sd-device: Syspath '%s' is not a subdirectory of /sys",
+                                               _syspath);
+
+                r = path_simplify_alloc(_syspath, &syspath);
+                if (r < 0)
+                        return r;
+        }
+
+        assert_se(devpath = startswith(syspath, "/sys"));
+        if (devpath[0] != '/')
+                return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "sd-device: \"/sys\" alone is not a valid device path.");
+
+        r = device_add_property_internal(device, "DEVPATH", devpath);
+        if (r < 0)
+                return log_debug_errno(r, "sd-device: Failed to add \"DEVPATH\" property for device \"%s\": %m", syspath);
+
+        free_and_replace(device->syspath, syspath);
+        device->devpath = devpath;
+
+        /* Unset sysname and sysnum, they will be assigned when requested. */
+        device->sysnum = NULL;
+        device->sysname = mfree(device->sysname);
+        return 0;
+}
+
+static int device_new_from_syspath(sd_device **ret, const char *syspath, bool strict) {
+        _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        assert_return(syspath, -EINVAL);
+
+        if (strict && !path_startswith(syspath, "/sys/"))
+                return -EINVAL;
+
+        r = device_new_aux(&device);
+        if (r < 0)
+                return r;
+
+        r = device_set_syspath(device, syspath, /* verify= */ true);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(device);
+        return 0;
+}
+
+_public_ int sd_device_new_from_syspath(sd_device **ret, const char *syspath) {
+        return device_new_from_syspath(ret, syspath, /* strict = */ true);
+}
+
+int device_new_from_mode_and_devnum(sd_device **ret, mode_t mode, dev_t devnum) {
+        _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
+        _cleanup_free_ char *syspath = NULL;
+        const char *t;
+        dev_t n;
+        int r;
+
+        assert(ret);
+
+        if (S_ISCHR(mode))
+                t = "char";
+        else if (S_ISBLK(mode))
+                t = "block";
+        else
+                return -ENOTTY;
+
+        if (major(devnum) == 0)
+                return -ENODEV;
+
+        if (asprintf(&syspath, "/sys/dev/%s/" DEVNUM_FORMAT_STR, t, DEVNUM_FORMAT_VAL(devnum)) < 0)
+                return -ENOMEM;
+
+        r = sd_device_new_from_syspath(&dev, syspath);
+        if (r < 0)
+                return r;
+
+        r = sd_device_get_devnum(dev, &n);
+        if (r == -ENOENT)
+                return -ENXIO;
+        if (r < 0)
+                return r;
+        if (n != devnum)
+                return -ENXIO;
+
+        if (device_in_subsystem(dev, "block") != !!S_ISBLK(mode))
+                return -ENXIO;
+
+        *ret = TAKE_PTR(dev);
+        return 0;
+}
+
+_public_ int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum) {
+        assert_return(ret, -EINVAL);
+        assert_return(IN_SET(type, 'b', 'c'), -EINVAL);
+
+        return device_new_from_mode_and_devnum(ret, type == 'b' ? S_IFBLK : S_IFCHR, devnum);
+}
+
+static int device_new_from_main_ifname(sd_device **ret, const char *ifname) {
+        const char *syspath;
+
+        assert(ret);
+        assert(ifname);
+
+        syspath = strjoina("/sys/class/net/", ifname);
+        return sd_device_new_from_syspath(ret, syspath);
+}
+
+_public_ int sd_device_new_from_ifname(sd_device **ret, const char *ifname) {
+        _cleanup_free_ char *main_name = NULL;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        assert_return(ifname, -EINVAL);
+
+        r = device_new_from_main_ifname(ret, ifname);
+        if (r >= 0)
+                return r;
+
+        r = rtnl_resolve_ifname_full(NULL, RESOLVE_IFNAME_ALTERNATIVE | RESOLVE_IFNAME_NUMERIC, ifname, &main_name, NULL);
+        if (r < 0)
+                return r;
+
+        return device_new_from_main_ifname(ret, main_name);
+}
+
+_public_ int sd_device_new_from_ifindex(sd_device **ret, int ifindex) {
+        _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
+        _cleanup_free_ char *ifname = NULL;
+        int r, i;
+
+        assert_return(ret, -EINVAL);
+        assert_return(ifindex > 0, -EINVAL);
+
+        r = rtnl_get_ifname_full(NULL, ifindex, &ifname, NULL);
+        if (r < 0)
+                return r;
+
+        r = device_new_from_main_ifname(&dev, ifname);
+        if (r < 0)
+                return r;
+
+        r = sd_device_get_ifindex(dev, &i);
+        if (r == -ENOENT)
+                return -ENXIO;
+        if (r < 0)
+                return r;
+        if (i != ifindex)
+                return -ENXIO;
+
+        *ret = TAKE_PTR(dev);
+        return 0;
+}
+
+static int device_strjoin_new(
+                const char *a,
+                const char *b,
+                const char *c,
+                const char *d,
+                sd_device **ret) {
+
+        const char *p;
+        int r;
+
+        p = strjoina(a, b, c, d);
+        if (access(p, F_OK) < 0)
+                return IN_SET(errno, ENOENT, ENAMETOOLONG) ? 0 : -errno; /* If this sysfs is too long then it doesn't exist either */
+
+        r = sd_device_new_from_syspath(ret, p);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+_public_ int sd_device_new_from_subsystem_sysname(
+                sd_device **ret,
+                const char *subsystem,
+                const char *sysname) {
+
+        char *name;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        assert_return(subsystem, -EINVAL);
+        assert_return(sysname, -EINVAL);
+
+        if (!path_is_normalized(subsystem))
+                return -EINVAL;
+        if (!path_is_normalized(sysname))
+                return -EINVAL;
+
+        /* translate sysname back to sysfs filename */
+        name = strdupa_safe(sysname);
+        string_replace_char(name, '/', '!');
+
+        if (streq(subsystem, "subsystem")) {
+                FOREACH_STRING(s, "/sys/bus/", "/sys/class/") {
+                        r = device_strjoin_new(s, name, NULL, NULL, ret);
+                        if (r < 0)
+                                return r;
+                        if (r > 0)
+                                return 0;
+                }
+
+        } else if (streq(subsystem, "module")) {
+                r = device_strjoin_new("/sys/module/", name, NULL, NULL, ret);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        return 0;
+
+        } else if (streq(subsystem, "drivers")) {
+                const char *sep;
+
+                sep = strchr(name, ':');
+                if (sep && sep[1] != '\0') { /* Require ":" and something non-empty after that. */
+
+                        const char *subsys = memdupa_suffix0(name, sep - name);
+                        sep++;
+
+                        if (streq(sep, "drivers")) /* If the sysname is "drivers", then it's the drivers directory itself that is meant. */
+                                r = device_strjoin_new("/sys/bus/", subsys, "/drivers", NULL, ret);
+                        else
+                                r = device_strjoin_new("/sys/bus/", subsys, "/drivers/", sep, ret);
+                        if (r < 0)
+                                return r;
+                        if (r > 0)
+                                return 0;
+                }
+        }
+
+        r = device_strjoin_new("/sys/bus/", subsystem, "/devices/", name, ret);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return 0;
+
+        r = device_strjoin_new("/sys/class/", subsystem, "/", name, ret);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return 0;
+
+        r = device_strjoin_new("/sys/firmware/", subsystem, "/", name, ret);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return 0;
+
+        return -ENODEV;
+}
+
+_public_ int sd_device_new_from_stat_rdev(sd_device **ret, const struct stat *st) {
+        assert_return(ret, -EINVAL);
+        assert_return(st, -EINVAL);
+
+        return device_new_from_mode_and_devnum(ret, st->st_mode, st->st_rdev);
+}
+
+_public_ int sd_device_new_from_devname(sd_device **ret, const char *devname) {
+        struct stat st;
+        dev_t devnum;
+        mode_t mode;
+
+        assert_return(ret, -EINVAL);
+        assert_return(devname, -EINVAL);
+
+        /* This function actually accepts both devlinks and devnames, i.e. both symlinks and device
+         * nodes below /dev/. */
+
+        /* Also ignore when the specified path is "/dev". */
+        if (isempty(path_startswith(devname, "/dev")))
+                return -EINVAL;
+
+        if (device_path_parse_major_minor(devname, &mode, &devnum) >= 0)
+                /* Let's shortcut when "/dev/block/maj:min" or "/dev/char/maj:min" is specified.
+                 * In that case, we can directly convert the path to syspath, hence it is not necessary
+                 * that the specified path exists. So, this works fine without udevd being running. */
+                return device_new_from_mode_and_devnum(ret, mode, devnum);
+
+        if (stat(devname, &st) < 0)
+                return ERRNO_IS_DEVICE_ABSENT(errno) ? -ENODEV : -errno;
+
+        return sd_device_new_from_stat_rdev(ret, &st);
+}
+
+_public_ int sd_device_new_from_path(sd_device **ret, const char *path) {
+        assert_return(ret, -EINVAL);
+        assert_return(path, -EINVAL);
+
+        if (path_startswith(path, "/dev"))
+                return sd_device_new_from_devname(ret, path);
+
+        return device_new_from_syspath(ret, path, /* strict = */ false);
+}
+#endif /* NM_IGNORED */
+
+int device_set_devtype(sd_device *device, const char *devtype) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(device);
+        assert(devtype);
+
+        t = strdup(devtype);
+        if (!t)
+                return -ENOMEM;
+
+        r = device_add_property_internal(device, "DEVTYPE", t);
+        if (r < 0)
+                return r;
+
+        return free_and_replace(device->devtype, t);
+}
+
+int device_set_ifindex(sd_device *device, const char *name) {
+        int r, ifindex;
+
+        assert(device);
+        assert(name);
+
+        ifindex = parse_ifindex(name);
+        if (ifindex < 0)
+                return ifindex;
+
+        r = device_add_property_internal(device, "IFINDEX", name);
+        if (r < 0)
+                return r;
+
+        device->ifindex = ifindex;
+
+        return 0;
+}
+
+static int mangle_devname(const char *p, char **ret) {
+        char *q;
+
+        assert(p);
+        assert(ret);
+
+        if (!path_is_safe(p))
+                return -EINVAL;
+
+        /* When the path is absolute, it must start with "/dev/", but ignore "/dev/" itself. */
+        if (path_is_absolute(p)) {
+                if (isempty(path_startswith(p, "/dev/")))
+                        return -EINVAL;
+
+                q = strdup(p);
+        } else
+                q = path_join("/dev/", p);
+        if (!q)
+                return -ENOMEM;
+
+        path_simplify(q);
+
+        *ret = q;
+        return 0;
+}
+
+int device_set_devname(sd_device *device, const char *devname) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(device);
+        assert(devname);
+
+        r = mangle_devname(devname, &t);
+        if (r < 0)
+                return r;
+
+        r = device_add_property_internal(device, "DEVNAME", t);
+        if (r < 0)
+                return r;
+
+        return free_and_replace(device->devname, t);
+}
+
+int device_set_devmode(sd_device *device, const char *_devmode) {
+        unsigned devmode;
+        int r;
+
+        assert(device);
+        assert(_devmode);
+
+        r = safe_atou(_devmode, &devmode);
+        if (r < 0)
+                return r;
+
+        if (devmode > 07777)
+                return -EINVAL;
+
+        r = device_add_property_internal(device, "DEVMODE", _devmode);
+        if (r < 0)
+                return r;
+
+        device->devmode = devmode;
+
+        return 0;
+}
+
+int device_set_devnum(sd_device *device, const char *major, const char *minor) {
+        unsigned maj, min = 0;
+        int r;
+
+        assert(device);
+        assert(major);
+
+        r = safe_atou(major, &maj);
+        if (r < 0)
+                return r;
+        if (maj == 0)
+                return 0;
+        if (!DEVICE_MAJOR_VALID(maj))
+                return -EINVAL;
+
+        if (minor) {
+                r = safe_atou(minor, &min);
+                if (r < 0)
+                        return r;
+                if (!DEVICE_MINOR_VALID(min))
+                        return -EINVAL;
+        }
+
+        r = device_add_property_internal(device, "MAJOR", major);
+        if (r < 0)
+                return r;
+
+        if (minor) {
+                r = device_add_property_internal(device, "MINOR", minor);
+                if (r < 0)
+                        return r;
+        }
+
+        device->devnum = makedev(maj, min);
+
+        return 0;
+}
+
+int device_set_diskseq(sd_device *device, const char *str) {
+        uint64_t diskseq;
+        int r;
+
+        assert(device);
+        assert(str);
+
+        r = safe_atou64(str, &diskseq);
+        if (r < 0)
+                return r;
+        if (diskseq == 0)
+                return -EINVAL;
+
+        r = device_add_property_internal(device, "DISKSEQ", str);
+        if (r < 0)
+                return r;
+
+        device->diskseq = diskseq;
+
+        return 0;
+}
+
+static int handle_uevent_line(
+                sd_device *device,
+                const char *key,
+                const char *value,
+                const char **major,
+                const char **minor) {
+
+        assert(device);
+        assert(key);
+        assert(value);
+        assert(major);
+        assert(minor);
+
+        if (streq(key, "DEVTYPE"))
+                return device_set_devtype(device, value);
+        if (streq(key, "IFINDEX"))
+                return device_set_ifindex(device, value);
+        if (streq(key, "DEVNAME"))
+                return device_set_devname(device, value);
+        if (streq(key, "DEVMODE"))
+                return device_set_devmode(device, value);
+        if (streq(key, "DISKSEQ"))
+                return device_set_diskseq(device, value);
+        if (streq(key, "MAJOR"))
+                *major = value;
+        else if (streq(key, "MINOR"))
+                *minor = value;
+        else
+                return device_add_property_internal(device, key, value);
+
+        return 0;
+}
+
+int device_read_uevent_file(sd_device *device) {
+        _cleanup_free_ char *uevent = NULL;
+        const char *syspath, *key = NULL, *value = NULL, *major = NULL, *minor = NULL;
+        char *path;
+        size_t uevent_len = 0;
+        int r;
+
+        enum {
+                PRE_KEY,
+                KEY,
+                PRE_VALUE,
+                VALUE,
+                INVALID_LINE,
+        } state = PRE_KEY;
+
+        assert(device);
+
+        if (device->uevent_loaded || device->sealed)
+                return 0;
+
+        r = sd_device_get_syspath(device, &syspath);
+        if (r < 0)
+                return r;
+
+        device->uevent_loaded = true;
+
+        path = strjoina(syspath, "/uevent");
+
+        r = read_full_virtual_file(path, &uevent, &uevent_len);
+        if (r == -EACCES || ERRNO_IS_NEG_DEVICE_ABSENT(r))
+                /* The uevent files may be write-only, the device may be already removed, or the device
+                 * may not have the uevent file. */
+                return 0;
+        if (r < 0)
+                return log_device_debug_errno(device, r, "sd-device: Failed to read uevent file '%s': %m", path);
+
+        for (size_t i = 0; i < uevent_len; i++)
+                switch (state) {
+                case PRE_KEY:
+                        if (!strchr(NEWLINE, uevent[i])) {
+                                key = &uevent[i];
+
+                                state = KEY;
+                        }
+
+                        break;
+                case KEY:
+                        if (uevent[i] == '=') {
+                                uevent[i] = '\0';
+
+                                state = PRE_VALUE;
+                        } else if (strchr(NEWLINE, uevent[i])) {
+                                uevent[i] = '\0';
+                                log_device_debug(device, "sd-device: Invalid uevent line '%s', ignoring", key);
+
+                                state = PRE_KEY;
+                        }
+
+                        break;
+                case PRE_VALUE:
+                        value = &uevent[i];
+                        state = VALUE;
+
+                        _fallthrough_; /* to handle empty property */
+                case VALUE:
+                        if (strchr(NEWLINE, uevent[i])) {
+                                uevent[i] = '\0';
+
+                                r = handle_uevent_line(device, key, value, &major, &minor);
+                                if (r < 0)
+                                        log_device_debug_errno(device, r, "sd-device: Failed to handle uevent entry '%s=%s', ignoring: %m", key, value);
+
+                                state = PRE_KEY;
+                        }
+
+                        break;
+                default:
+                        assert_not_reached();
+                }
+
+        if (major) {
+                r = device_set_devnum(device, major, minor);
+                if (r < 0)
+                        log_device_debug_errno(device, r, "sd-device: Failed to set 'MAJOR=%s' or 'MINOR=%s' from '%s', ignoring: %m", major, strna(minor), path);
+        }
+
+        return 0;
+}
+
+_public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) {
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        r = device_read_uevent_file(device);
+        if (r < 0)
+                return r;
+
+        if (device->ifindex <= 0)
+                return -ENOENT;
+
+        if (ifindex)
+                *ifindex = device->ifindex;
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+_public_ int sd_device_new_from_device_id(sd_device **ret, const char *id) {
+        int r;
+
+        assert_return(ret, -EINVAL);
+        assert_return(id, -EINVAL);
+
+        switch (id[0]) {
+        case 'b':
+        case 'c': {
+                dev_t devt;
+
+                if (isempty(id))
+                        return -EINVAL;
+
+                r = parse_devnum(id + 1, &devt);
+                if (r < 0)
+                        return r;
+
+                return sd_device_new_from_devnum(ret, id[0], devt);
+        }
+
+        case 'n': {
+                int ifindex;
+
+                ifindex = parse_ifindex(id + 1);
+                if (ifindex < 0)
+                        return ifindex;
+
+                return sd_device_new_from_ifindex(ret, ifindex);
+        }
+
+        case '+': {
+                const char *subsys, *sep;
+
+                sep = strchr(id + 1, ':');
+                if (!sep || sep - id - 1 > NAME_MAX)
+                        return -EINVAL;
+
+                subsys = memdupa_suffix0(id + 1, sep - id - 1);
+
+                return sd_device_new_from_subsystem_sysname(ret, subsys, sep + 1);
+        }
+
+        default:
+                return -EINVAL;
+        }
+}
+#endif /* NM_IGNORED */
+
+_public_ int sd_device_get_syspath(sd_device *device, const char **ret) {
+        assert_return(device, -EINVAL);
+
+        assert(path_startswith(device->syspath, "/sys/"));
+
+        if (ret)
+                *ret = device->syspath;
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+DEFINE_PRIVATE_HASH_OPS_FULL(
+        device_by_path_hash_ops,
+        char, path_hash_func, path_compare, free,
+        sd_device, sd_device_unref);
+
+static int device_enumerate_children_internal(sd_device *device, const char *subdir, Set **stack, Hashmap **children) {
+        _cleanup_closedir_ DIR *dir = NULL;
+        int r;
+
+        assert(device);
+        assert(stack);
+        assert(children);
+
+        r = device_opendir(device, subdir, &dir);
+        if (r < 0)
+                return r;
+
+        FOREACH_DIRENT_ALL(de, dir, return -errno) {
+                _cleanup_(sd_device_unrefp) sd_device *child = NULL;
+                _cleanup_free_ char *p = NULL;
+
+                if (dot_or_dot_dot(de->d_name))
+                        continue;
+
+                if (!IN_SET(de->d_type, DT_LNK, DT_DIR))
+                        continue;
+
+                if (subdir)
+                        p = path_join(subdir, de->d_name);
+                else
+                        p = strdup(de->d_name);
+                if (!p)
+                        return -ENOMEM;
+
+                /* Try to create child device. */
+                r = sd_device_new_child(&child, device, p);
+                if (r >= 0) {
+                        /* OK, this is a child device, saving it. */
+                        r = hashmap_ensure_put(children, &device_by_path_hash_ops, p, child);
+                        if (r < 0)
+                                return r;
+
+                        TAKE_PTR(p);
+                        TAKE_PTR(child);
+                } else if (r == -ENODEV) {
+                        /* This is not a child device. Push the sub-directory into stack, and read it later. */
+
+                        if (de->d_type == DT_LNK)
+                                /* Do not follow symlinks, otherwise, we will enter an infinite loop, e.g.,
+                                 * /sys/class/block/nvme0n1/subsystem/nvme0n1/subsystem/nvme0n1/subsystem/… */
+                                continue;
+
+                        r = set_ensure_consume(stack, &path_hash_ops_free, TAKE_PTR(p));
+                        if (r < 0)
+                                return r;
+                } else
+                        return r;
+        }
+
+        return 0;
+}
+
+static int device_enumerate_children(sd_device *device) {
+        _cleanup_hashmap_free_ Hashmap *children = NULL;
+        _cleanup_set_free_ Set *stack = NULL;
+        int r;
+
+        assert(device);
+
+        if (device->children_enumerated)
+                return 0; /* Already enumerated. */
+
+        r = device_enumerate_children_internal(device, NULL, &stack, &children);
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                _cleanup_free_ char *subdir = NULL;
+
+                subdir = set_steal_first(stack);
+                if (!subdir)
+                        break;
+
+                r = device_enumerate_children_internal(device, subdir, &stack, &children);
+                if (r < 0)
+                        return r;
+        }
+
+        device->children_enumerated = true;
+        device->children = TAKE_PTR(children);
+        return 1; /* Enumerated. */
+}
+
+_public_ sd_device *sd_device_get_child_first(sd_device *device, const char **ret_suffix) {
+        int r;
+
+        assert(device);
+
+        r = device_enumerate_children(device);
+        if (r < 0) {
+                log_device_debug_errno(device, r, "sd-device: failed to enumerate child devices: %m");
+                if (ret_suffix)
+                        *ret_suffix = NULL;
+                return NULL;
+        }
+
+        device->children_iterator = ITERATOR_FIRST;
+
+        return sd_device_get_child_next(device, ret_suffix);
+}
+
+_public_ sd_device *sd_device_get_child_next(sd_device *device, const char **ret_suffix) {
+        sd_device *child;
+
+        assert(device);
+
+        (void) hashmap_iterate(device->children, &device->children_iterator, (void**) &child, (const void**) ret_suffix);
+        return child;
+}
+
+_public_ int sd_device_new_child(sd_device **ret, sd_device *device, const char *suffix) {
+        _cleanup_free_ char *path = NULL;
+        sd_device *child;
+        const char *s;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        assert_return(device, -EINVAL);
+        assert_return(suffix, -EINVAL);
+
+        if (!path_is_safe(suffix))
+                return -EINVAL;
+
+        /* If we have already enumerated children, try to find the child from the cache. */
+        child = hashmap_get(device->children, suffix);
+        if (child) {
+                *ret = sd_device_ref(child);
+                return 0;
+        }
+
+        r = sd_device_get_syspath(device, &s);
+        if (r < 0)
+                return r;
+
+        path = path_join(s, suffix);
+        if (!path)
+                return -ENOMEM;
+
+        return sd_device_new_from_syspath(ret, path);
+}
+
+static int device_new_from_child(sd_device **ret, sd_device *child) {
+        _cleanup_free_ char *path = NULL;
+        const char *syspath;
+        int r;
+
+        assert(ret);
+        assert(child);
+
+        r = sd_device_get_syspath(child, &syspath);
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                _cleanup_free_ char *p = NULL;
+
+                r = path_extract_directory(path ?: syspath, &p);
+                if (r < 0)
+                        return r;
+
+                if (path_equal(p, "/sys"))
+                        return -ENODEV;
+
+                r = sd_device_new_from_syspath(ret, p);
+                if (r != -ENODEV)
+                        return r;
+
+                free_and_replace(path, p);
+        }
+}
+
+_public_ int sd_device_get_parent(sd_device *child, sd_device **ret) {
+        int r;
+
+        assert_return(child, -EINVAL);
+
+        if (!child->parent_set) {
+                r = device_new_from_child(&child->parent, child);
+                if (r < 0 && r != -ENODEV)
+                        return r;
+
+                child->parent_set = true;
+        }
+
+        if (!child->parent)
+                return -ENOENT;
+
+        if (ret)
+                *ret = child->parent;
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+int device_set_subsystem(sd_device *device, const char *subsystem) {
+        _cleanup_free_ char *s = NULL;
+        int r;
+
+        assert(device);
+
+        if (subsystem) {
+                s = strdup(subsystem);
+                if (!s)
+                        return -ENOMEM;
+        }
+
+        r = device_add_property_internal(device, "SUBSYSTEM", s);
+        if (r < 0)
+                return r;
+
+        device->subsystem_set = true;
+        return free_and_replace(device->subsystem, s);
+}
+
+int device_set_drivers_subsystem(sd_device *device) {
+        _cleanup_free_ char *subsystem = NULL;
+        const char *devpath, *drivers, *p;
+        int r;
+
+        assert(device);
+
+        r = sd_device_get_devpath(device, &devpath);
+        if (r < 0)
+                return r;
+
+        drivers = strstr(devpath, "/drivers/");
+        if (!drivers)
+                drivers = endswith(devpath, "/drivers");
+        if (!drivers)
+                return -EINVAL;
+
+        /* Find the path component immediately before the "/drivers/" string */
+        r = path_find_last_component(devpath, /* accept_dot_dot= */ false, &drivers, &p);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EINVAL;
+
+        subsystem = strndup(p, r);
+        if (!subsystem)
+                return -ENOMEM;
+
+        r = device_set_subsystem(device, "drivers");
+        if (r < 0)
+                return r;
+
+        return free_and_replace(device->driver_subsystem, subsystem);
+}
+
+_public_ int sd_device_get_subsystem(sd_device *device, const char **ret) {
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        if (!device->subsystem_set) {
+                _cleanup_free_ char *subsystem = NULL;
+                const char *syspath;
+                char *path;
+
+                r = sd_device_get_syspath(device, &syspath);
+                if (r < 0)
+                        return r;
+
+                /* read 'subsystem' link */
+                path = strjoina(syspath, "/subsystem");
+                r = readlink_value(path, &subsystem);
+                if (r < 0 && r != -ENOENT)
+                        return log_device_debug_errno(device, r,
+                                                      "sd-device: Failed to read subsystem for %s: %m",
+                                                      device->devpath);
+
+                if (subsystem)
+                        r = device_set_subsystem(device, subsystem);
+                /* use implicit names */
+                else if (!isempty(path_startswith(device->devpath, "/module/")))
+                        r = device_set_subsystem(device, "module");
+                else if (strstr(syspath, "/drivers/") || endswith(syspath, "/drivers"))
+                        r = device_set_drivers_subsystem(device);
+                else if (!isempty(PATH_STARTSWITH_SET(device->devpath, "/class/", "/bus/")))
+                        r = device_set_subsystem(device, "subsystem");
+                else {
+                        device->subsystem_set = true;
+                        r = 0;
+                }
+                if (r < 0)
+                        return log_device_debug_errno(device, r,
+                                                      "sd-device: Failed to set subsystem for %s: %m",
+                                                      device->devpath);
+        }
+
+        if (!device->subsystem)
+                return -ENOENT;
+
+        if (ret)
+                *ret = device->subsystem;
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+_public_ int sd_device_get_devtype(sd_device *device, const char **devtype) {
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        r = device_read_uevent_file(device);
+        if (r < 0)
+                return r;
+
+        if (!device->devtype)
+                return -ENOENT;
+
+        if (devtype)
+                *devtype = device->devtype;
+
+        return !!device->devtype;
+}
+
+_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *device, const char *subsystem, const char *devtype, sd_device **ret) {
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(subsystem, -EINVAL);
+
+        for (;;) {
+                r = sd_device_get_parent(device, &device);
+                if (r < 0)
+                        return r;
+
+                if (!device_in_subsystem(device, subsystem))
+                        continue;
+
+                if (devtype && !device_is_devtype(device, devtype))
+                        continue;
+
+                if (ret)
+                        *ret = device;
+                return 0;
+        }
+}
+#endif /* NM_IGNORED */
+
+_public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) {
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        r = device_read_uevent_file(device);
+        if (r < 0)
+                return r;
+
+        if (major(device->devnum) <= 0)
+                return -ENOENT;
+
+        if (devnum)
+                *devnum = device->devnum;
+
+        return 0;
+}
+
+int device_set_driver(sd_device *device, const char *driver) {
+        _cleanup_free_ char *d = NULL;
+        int r;
+
+        assert(device);
+
+        if (driver) {
+                d = strdup(driver);
+                if (!d)
+                        return -ENOMEM;
+        }
+
+        r = device_add_property_internal(device, "DRIVER", d);
+        if (r < 0)
+                return r;
+
+        device->driver_set = true;
+        return free_and_replace(device->driver, d);
+}
+
+#if 0 /* NM_IGNORED */
+_public_ int sd_device_get_driver(sd_device *device, const char **ret) {
+        assert_return(device, -EINVAL);
+
+        if (!device->driver_set) {
+                _cleanup_free_ char *driver = NULL;
+                const char *syspath;
+                char *path;
+                int r;
+
+                r = sd_device_get_syspath(device, &syspath);
+                if (r < 0)
+                        return r;
+
+                path = strjoina(syspath, "/driver");
+                r = readlink_value(path, &driver);
+                if (r < 0 && r != -ENOENT)
+                        return log_device_debug_errno(device, r,
+                                                      "sd-device: readlink(\"%s\") failed: %m", path);
+
+                r = device_set_driver(device, driver);
+                if (r < 0)
+                        return log_device_debug_errno(device, r,
+                                                      "sd-device: Failed to set driver \"%s\": %m", driver);
+        }
+
+        if (!device->driver)
+                return -ENOENT;
+
+        if (ret)
+                *ret = device->driver;
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+_public_ int sd_device_get_devpath(sd_device *device, const char **ret) {
+        assert_return(device, -EINVAL);
+
+        assert(device->devpath);
+        assert(device->devpath[0] == '/');
+
+        if (ret)
+                *ret = device->devpath;
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+_public_ int sd_device_get_devname(sd_device *device, const char **devname) {
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        r = device_read_uevent_file(device);
+        if (r < 0)
+                return r;
+
+        if (!device->devname)
+                return -ENOENT;
+
+        assert(!isempty(path_startswith(device->devname, "/dev/")));
+
+        if (devname)
+                *devname = device->devname;
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+static int device_set_sysname_and_sysnum(sd_device *device) {
+        _cleanup_free_ char *sysname = NULL;
+        size_t len, n;
+        int r;
+
+        assert(device);
+
+        r = path_extract_filename(device->devpath, &sysname);
+        if (r < 0)
+                return r;
+        if (r == O_DIRECTORY)
+                return -EINVAL;
+
+        /* some devices have '!' in their name, change that to '/' */
+        string_replace_char(sysname, '!', '/');
+
+        n = strspn_from_end(sysname, DIGITS);
+        len = strlen(sysname);
+        assert(n <= len);
+        if (n == len)
+                n = 0; /* Do not set sysnum for number only sysname. */
+
+        device->sysnum = n > 0 ? sysname + len - n : NULL;
+        return free_and_replace(device->sysname, sysname);
+}
+
+_public_ int sd_device_get_sysname(sd_device *device, const char **ret) {
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        if (!device->sysname) {
+                r = device_set_sysname_and_sysnum(device);
+                if (r < 0)
+                        return r;
+        }
+
+        if (ret)
+                *ret = device->sysname;
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+_public_ int sd_device_get_sysnum(sd_device *device, const char **ret) {
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        if (!device->sysname) {
+                r = device_set_sysname_and_sysnum(device);
+                if (r < 0)
+                        return r;
+        }
+
+        if (!device->sysnum)
+                return -ENOENT;
+
+        if (ret)
+                *ret = device->sysnum;
+        return 0;
+}
+
+_public_ int sd_device_get_action(sd_device *device, sd_device_action_t *ret) {
+        assert_return(device, -EINVAL);
+
+        if (device->action < 0)
+                return -ENOENT;
+
+        if (ret)
+                *ret = device->action;
+
+        return 0;
+}
+
+_public_ int sd_device_get_seqnum(sd_device *device, uint64_t *ret) {
+        assert_return(device, -EINVAL);
+
+        if (device->seqnum == 0)
+                return -ENOENT;
+
+        if (ret)
+                *ret = device->seqnum;
+
+        return 0;
+}
+
+_public_ int sd_device_get_diskseq(sd_device *device, uint64_t *ret) {
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        r = device_read_uevent_file(device);
+        if (r < 0)
+                return r;
+
+        if (device->diskseq == 0)
+                return -ENOENT;
+
+        if (ret)
+                *ret = device->diskseq;
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+static bool is_valid_tag(const char *tag) {
+        assert(tag);
+
+        return in_charset(tag, ALPHANUMERICAL "-_") && filename_is_valid(tag);
+}
+
+int device_add_tag(sd_device *device, const char *tag, bool both) {
+        int r, added;
+
+        assert(device);
+        assert(tag);
+
+        if (!is_valid_tag(tag))
+                return -EINVAL;
+
+        /* Definitely add to the "all" list of tags (i.e. the sticky list) */
+        added = set_put_strdup(&device->all_tags, tag);
+        if (added < 0)
+                return added;
+
+        /* And optionally, also add it to the current list of tags */
+        if (both) {
+                r = set_put_strdup(&device->current_tags, tag);
+                if (r < 0) {
+                        if (added > 0)
+                                (void) set_remove(device->all_tags, tag);
+
+                        return r;
+                }
+        }
+
+        device->tags_generation++;
+        device->property_tags_outdated = true;
+
+        return 0;
+}
+
+int device_add_devlink(sd_device *device, const char *devlink) {
+        char *p;
+        int r;
+
+        assert(device);
+        assert(devlink);
+
+        r = mangle_devname(devlink, &p);
+        if (r < 0)
+                return r;
+
+        r = set_ensure_consume(&device->devlinks, &path_hash_ops_free, p);
+        if (r < 0)
+                return r;
+
+        device->devlinks_generation++;
+        device->property_devlinks_outdated = true;
+
+        return r; /* return 1 when newly added, 0 when already exists */
+}
+
+#if 0 /* NM_IGNORED */
+int device_remove_devlink(sd_device *device, const char *devlink) {
+        _cleanup_free_ char *p = NULL, *s = NULL;
+        int r;
+
+        assert(device);
+        assert(devlink);
+
+        r = mangle_devname(devlink, &p);
+        if (r < 0)
+                return r;
+
+        s = set_remove(device->devlinks, p);
+        if (!s)
+                return 0; /* does not exist */
+
+        device->devlinks_generation++;
+        device->property_devlinks_outdated = true;
+        return 1; /* removed */
+}
+
+bool device_has_devlink(sd_device *device, const char *devlink) {
+        assert(device);
+        assert(devlink);
+
+        return set_contains(device->devlinks, devlink);
+}
+#endif /* NM_IGNORED */
+
+static int device_add_property_internal_from_string(sd_device *device, const char *str) {
+        _cleanup_free_ char *key = NULL;
+        char *value;
+        int r;
+
+        assert(device);
+        assert(str);
+
+        key = strdup(str);
+        if (!key)
+                return -ENOMEM;
+
+        value = strchr(key, '=');
+        if (!value)
+                return -EINVAL;
+
+        *value = '\0';
+
+        if (isempty(++value))
+                value = NULL;
+
+        /* Add the property to both sd_device::properties and sd_device::properties_db,
+         * as this is called by only handle_db_line(). */
+        r = device_add_property_aux(device, key, value, false);
+        if (r < 0)
+                return r;
+
+        return device_add_property_aux(device, key, value, true);
+}
+
+int device_set_usec_initialized(sd_device *device, usec_t when) {
+        char s[DECIMAL_STR_MAX(usec_t)];
+        int r;
+
+        assert(device);
+
+        xsprintf(s, USEC_FMT, when);
+
+        r = device_add_property_internal(device, "USEC_INITIALIZED", s);
+        if (r < 0)
+                return r;
+
+        device->usec_initialized = when;
+        return 0;
+}
+
+static int handle_db_line(sd_device *device, char key, const char *value) {
+        int r;
+
+        assert(device);
+        assert(value);
+
+        switch (key) {
+        case 'G': /* Any tag */
+        case 'Q': /* Current tag */
+                return device_add_tag(device, value, key == 'Q');
+
+        case 'S': {
+                const char *path;
+
+                path = strjoina("/dev/", value);
+                return device_add_devlink(device, path);
+        }
+        case 'E':
+                return device_add_property_internal_from_string(device, value);
+
+        case 'I': {
+                usec_t t;
+
+                r = safe_atou64(value, &t);
+                if (r < 0)
+                        return r;
+
+                return device_set_usec_initialized(device, t);
+        }
+        case 'L':
+                return safe_atoi(value, &device->devlink_priority);
+
+        case 'W':
+                /* Deprecated. Previously, watch handle is both saved in database and /run/udev/watch.
+                 * However, the handle saved in database may not be updated when the handle is updated
+                 * or removed. Moreover, it is not necessary to store the handle within the database,
+                 * as its value becomes meaningless when udevd is restarted. */
+                return 0;
+
+        case 'V':
+                return safe_atou(value, &device->database_version);
+
+        default:
+                log_device_debug(device, "sd-device: Unknown key '%c' in device db, ignoring", key);
+                return 0;
+        }
+}
+
+int device_get_device_id(sd_device *device, const char **ret) {
+        assert(device);
+        assert(ret);
+
+        if (!device->device_id) {
+                _cleanup_free_ char *id = NULL;
+                const char *subsystem;
+                dev_t devnum;
+                int ifindex, r;
+
+                r = sd_device_get_subsystem(device, &subsystem);
+                if (r < 0)
+                        return r;
+
+                if (sd_device_get_devnum(device, &devnum) >= 0) {
+                        /* use dev_t — b259:131072, c254:0 */
+                        if (asprintf(&id, "%c" DEVNUM_FORMAT_STR,
+                                     streq(subsystem, "block") ? 'b' : 'c',
+                                     DEVNUM_FORMAT_VAL(devnum)) < 0)
+                                return -ENOMEM;
+                } else if (sd_device_get_ifindex(device, &ifindex) >= 0) {
+                        /* use netdev ifindex — n3 */
+                        if (asprintf(&id, "n%u", (unsigned) ifindex) < 0)
+                                return -ENOMEM;
+                } else {
+                        _cleanup_free_ char *sysname = NULL;
+
+                        /* use $subsys:$sysname — pci:0000:00:1f.2
+                         * sd_device_get_sysname() has '!' translated, get it from devpath */
+                        r = path_extract_filename(device->devpath, &sysname);
+                        if (r < 0)
+                                return r;
+                        if (r == O_DIRECTORY)
+                                return -EINVAL;
+
+                        if (streq(subsystem, "drivers")) {
+                                /* the 'drivers' pseudo-subsystem is special, and needs the real
+                                 * subsystem encoded as well */
+                                assert(device->driver_subsystem);
+                                id = strjoin("+drivers:", device->driver_subsystem, ":", sysname);
+                        } else
+                                id = strjoin("+", subsystem, ":", sysname);
+                        if (!id)
+                                return -ENOMEM;
+                }
+
+                if (!filename_is_valid(id))
+                        return -EINVAL;
+
+                device->device_id = TAKE_PTR(id);
+        }
+
+        *ret = device->device_id;
+        return 0;
+}
+
+int device_read_db_internal_filename(sd_device *device, const char *filename) {
+        _cleanup_free_ char *db = NULL;
+        const char *value = NULL;
+        size_t db_len = 0;
+        char key = '\0';  /* Unnecessary initialization to appease gcc-12.0.0-0.4.fc36 */
+        int r;
+
+        enum {
+                PRE_KEY,
+                KEY,
+                PRE_VALUE,
+                VALUE,
+                INVALID_LINE,
+        } state = PRE_KEY;
+
+        assert(device);
+        assert(filename);
+
+        r = read_full_file(filename, &db, &db_len);
+        if (r < 0) {
+                if (r == -ENOENT)
+                        return 0;
+
+                return log_device_debug_errno(device, r, "sd-device: Failed to read db '%s': %m", filename);
+        }
+
+        /* devices with a database entry are initialized */
+        device->is_initialized = true;
+
+        device->db_loaded = true;
+
+        for (size_t i = 0; i < db_len; i++)
+                switch (state) {
+                case PRE_KEY:
+                        if (!strchr(NEWLINE, db[i])) {
+                                key = db[i];
+
+                                state = KEY;
+                        }
+
+                        break;
+                case KEY:
+                        if (db[i] != ':') {
+                                log_device_debug(device, "sd-device: Invalid db entry with key '%c', ignoring", key);
+
+                                state = INVALID_LINE;
+                        } else {
+                                db[i] = '\0';
+
+                                state = PRE_VALUE;
+                        }
+
+                        break;
+                case PRE_VALUE:
+                        value = &db[i];
+
+                        state = VALUE;
+
+                        break;
+                case INVALID_LINE:
+                        if (strchr(NEWLINE, db[i]))
+                                state = PRE_KEY;
+
+                        break;
+                case VALUE:
+                        if (strchr(NEWLINE, db[i])) {
+                                db[i] = '\0';
+                                r = handle_db_line(device, key, value);
+                                if (r < 0)
+                                        log_device_debug_errno(device, r, "sd-device: Failed to handle db entry '%c:%s', ignoring: %m",
+                                                               key, value);
+
+                                state = PRE_KEY;
+                        }
+
+                        break;
+                default:
+                        return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL), "sd-device: invalid db syntax.");
+                }
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+_public_ int sd_device_get_is_initialized(sd_device *device) {
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        r = device_read_db(device);
+        if (r == -ENOENT)
+                /* The device may be already removed or renamed. */
+                return false;
+        if (r < 0)
+                return r;
+
+        return device->is_initialized;
+}
+
+_public_ int sd_device_get_usec_initialized(sd_device *device, uint64_t *ret) {
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        r = sd_device_get_is_initialized(device);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EBUSY;
+
+        if (device->usec_initialized == 0)
+                return -ENODATA;
+
+        if (ret)
+                *ret = device->usec_initialized;
+
+        return 0;
+}
+
+_public_ int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *ret) {
+        usec_t now_ts, ts;
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        r = sd_device_get_usec_initialized(device, &ts);
+        if (r < 0)
+                return r;
+
+        now_ts = now(CLOCK_MONOTONIC);
+
+        if (now_ts < ts)
+                return -EIO;
+
+        if (ret)
+                *ret = usec_sub_unsigned(now_ts, ts);
+
+        return 0;
+}
+
+_public_ const char *sd_device_get_tag_first(sd_device *device) {
+        void *v;
+
+        assert_return(device, NULL);
+
+        (void) device_read_db(device);
+
+        device->all_tags_iterator_generation = device->tags_generation;
+        device->all_tags_iterator = ITERATOR_FIRST;
+
+        (void) set_iterate(device->all_tags, &device->all_tags_iterator, &v);
+        return v;
+}
+
+_public_ const char *sd_device_get_tag_next(sd_device *device) {
+        void *v;
+
+        assert_return(device, NULL);
+
+        (void) device_read_db(device);
+
+        if (device->all_tags_iterator_generation != device->tags_generation)
+                return NULL;
+
+        (void) set_iterate(device->all_tags, &device->all_tags_iterator, &v);
+        return v;
+}
+
+static bool device_database_supports_current_tags(sd_device *device) {
+        assert(device);
+
+        (void) device_read_db(device);
+
+        /* The current tags (saved in Q field) feature is implemented in database version 1.
+         * If the database version is 0, then the tags (NOT current tags, saved in G field) are not
+         * sticky. Thus, we can safely bypass the operations for the current tags (Q) to tags (G). */
+
+        return device->database_version >= 1;
+}
+
+_public_ const char *sd_device_get_current_tag_first(sd_device *device) {
+        void *v;
+
+        assert_return(device, NULL);
+
+        if (!device_database_supports_current_tags(device))
+                return sd_device_get_tag_first(device);
+
+        (void) device_read_db(device);
+
+        device->current_tags_iterator_generation = device->tags_generation;
+        device->current_tags_iterator = ITERATOR_FIRST;
+
+        (void) set_iterate(device->current_tags, &device->current_tags_iterator, &v);
+        return v;
+}
+
+_public_ const char *sd_device_get_current_tag_next(sd_device *device) {
+        void *v;
+
+        assert_return(device, NULL);
+
+        if (!device_database_supports_current_tags(device))
+                return sd_device_get_tag_next(device);
+
+        (void) device_read_db(device);
+
+        if (device->current_tags_iterator_generation != device->tags_generation)
+                return NULL;
+
+        (void) set_iterate(device->current_tags, &device->current_tags_iterator, &v);
+        return v;
+}
+
+_public_ const char *sd_device_get_devlink_first(sd_device *device) {
+        void *v;
+
+        assert_return(device, NULL);
+
+        (void) device_read_db(device);
+
+        device->devlinks_iterator_generation = device->devlinks_generation;
+        device->devlinks_iterator = ITERATOR_FIRST;
+
+        (void) set_iterate(device->devlinks, &device->devlinks_iterator, &v);
+        return v;
+}
+
+_public_ const char *sd_device_get_devlink_next(sd_device *device) {
+        void *v;
+
+        assert_return(device, NULL);
+
+        (void) device_read_db(device);
+
+        if (device->devlinks_iterator_generation != device->devlinks_generation)
+                return NULL;
+
+        (void) set_iterate(device->devlinks, &device->devlinks_iterator, &v);
+        return v;
+}
+#endif /* NM_IGNORED */
+
+int device_properties_prepare(sd_device *device) {
+        int r;
+
+        assert(device);
+
+        r = device_read_uevent_file(device);
+        if (r < 0)
+                return r;
+
+        r = device_read_db(device);
+        if (r < 0)
+                return r;
+
+        if (device->property_devlinks_outdated) {
+                _cleanup_free_ char *devlinks = NULL;
+
+                r = set_strjoin(device->devlinks, " ", false, &devlinks);
+                if (r < 0)
+                        return r;
+
+                if (!isempty(devlinks)) {
+                        r = device_add_property_internal(device, "DEVLINKS", devlinks);
+                        if (r < 0)
+                                return r;
+                }
+
+                device->property_devlinks_outdated = false;
+        }
+
+        if (device->property_tags_outdated) {
+                _cleanup_free_ char *tags = NULL;
+
+                r = set_strjoin(device->all_tags, ":", true, &tags);
+                if (r < 0)
+                        return r;
+
+                if (!isempty(tags)) {
+                        r = device_add_property_internal(device, "TAGS", tags);
+                        if (r < 0)
+                                return r;
+                }
+
+                tags = mfree(tags);
+                r = set_strjoin(device->current_tags, ":", true, &tags);
+                if (r < 0)
+                        return r;
+
+                if (!isempty(tags)) {
+                        r = device_add_property_internal(device, "CURRENT_TAGS", tags);
+                        if (r < 0)
+                                return r;
+                }
+
+                device->property_tags_outdated = false;
+        }
+
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+_public_ const char *sd_device_get_property_first(sd_device *device, const char **_value) {
+        const char *key;
+        int r;
+
+        assert_return(device, NULL);
+
+        r = device_properties_prepare(device);
+        if (r < 0)
+                return NULL;
+
+        device->properties_iterator_generation = device->properties_generation;
+        device->properties_iterator = ITERATOR_FIRST;
+
+        (void) ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)_value, (const void**)&key);
+        return key;
+}
+
+_public_ const char *sd_device_get_property_next(sd_device *device, const char **_value) {
+        const char *key;
+        int r;
+
+        assert_return(device, NULL);
+
+        r = device_properties_prepare(device);
+        if (r < 0)
+                return NULL;
+
+        if (device->properties_iterator_generation != device->properties_generation)
+                return NULL;
+
+        (void) ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)_value, (const void**)&key);
+        return key;
+}
+
+static int device_sysattrs_read_all_internal(sd_device *device, const char *subdir, Set **stack) {
+        _cleanup_closedir_ DIR *dir = NULL;
+        int r;
+
+        assert(device);
+        assert(stack);
+
+        r = device_opendir(device, subdir, &dir);
+        if (r == -ENOENT && subdir)
+                return 0; /* Maybe, this is a child device, and is already removed. */
+        if (r < 0)
+                return r;
+
+        if (subdir) {
+                if (faccessat(dirfd(dir), "uevent", F_OK, 0) >= 0)
+                        return 0; /* this is a child device, skipping */
+                if (errno != ENOENT) {
+                        log_device_debug_errno(device, errno,
+                                               "sd-device: Failed to access %s/uevent, ignoring sub-directory %s: %m",
+                                               subdir, subdir);
+                        return 0;
+                }
+        }
+
+        FOREACH_DIRENT_ALL(de, dir, return -errno) {
+                _cleanup_free_ char *p = NULL;
+                struct stat statbuf;
+
+                if (dot_or_dot_dot(de->d_name))
+                        continue;
+
+                /* only handle symlinks, regular files, and directories */
+                if (!IN_SET(de->d_type, DT_LNK, DT_REG, DT_DIR))
+                        continue;
+
+                if (subdir) {
+                        p = path_join(subdir, de->d_name);
+                        if (!p)
+                                return -ENOMEM;
+                }
+
+                if (de->d_type == DT_DIR) {
+                        /* push the sub-directory into the stack, and read it later. */
+                        if (p)
+                                r = set_ensure_consume(stack, &path_hash_ops_free, TAKE_PTR(p));
+                        else
+                                r = set_put_strdup_full(stack, &path_hash_ops_free, de->d_name);
+                        if (r < 0)
+                                return r;
+
+                        continue;
+                }
+
+                if (fstatat(dirfd(dir), de->d_name, &statbuf, AT_SYMLINK_NOFOLLOW) < 0)
+                        continue;
+
+                if ((statbuf.st_mode & (S_IRUSR | S_IWUSR)) == 0)
+                        continue;
+
+                if (p)
+                        r = set_ensure_consume(&device->sysattrs, &path_hash_ops_free, TAKE_PTR(p));
+                else
+                        r = set_put_strdup_full(&device->sysattrs, &path_hash_ops_free, de->d_name);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int device_sysattrs_read_all(sd_device *device) {
+        _cleanup_set_free_ Set *stack = NULL;
+        int r;
+
+        assert(device);
+
+        if (device->sysattrs_read)
+                return 0;
+
+        r = device_sysattrs_read_all_internal(device, NULL, &stack);
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                _cleanup_free_ char *subdir = NULL;
+
+                subdir = set_steal_first(stack);
+                if (!subdir)
+                        break;
+
+                r = device_sysattrs_read_all_internal(device, subdir, &stack);
+                if (r < 0)
+                        return r;
+        }
+
+        device->sysattrs_read = true;
+
+        return 0;
+}
+
+_public_ const char *sd_device_get_sysattr_first(sd_device *device) {
+        void *v;
+        int r;
+
+        assert_return(device, NULL);
+
+        if (!device->sysattrs_read) {
+                r = device_sysattrs_read_all(device);
+                if (r < 0) {
+                        errno = -r;
+                        return NULL;
+                }
+        }
+
+        device->sysattrs_iterator = ITERATOR_FIRST;
+
+        (void) set_iterate(device->sysattrs, &device->sysattrs_iterator, &v);
+        return v;
+}
+
+_public_ const char *sd_device_get_sysattr_next(sd_device *device) {
+        void *v;
+
+        assert_return(device, NULL);
+
+        if (!device->sysattrs_read)
+                return NULL;
+
+        (void) set_iterate(device->sysattrs, &device->sysattrs_iterator, &v);
+        return v;
+}
+
+_public_ int sd_device_has_tag(sd_device *device, const char *tag) {
+        assert_return(device, -EINVAL);
+        assert_return(tag, -EINVAL);
+
+        (void) device_read_db(device);
+
+        return set_contains(device->all_tags, tag);
+}
+
+_public_ int sd_device_has_current_tag(sd_device *device, const char *tag) {
+        assert_return(device, -EINVAL);
+        assert_return(tag, -EINVAL);
+
+        if (!device_database_supports_current_tags(device))
+                return sd_device_has_tag(device, tag);
+
+        (void) device_read_db(device);
+
+        return set_contains(device->current_tags, tag);
+}
+#endif /* NM_IGNORED */
+
+_public_ int sd_device_get_property_value(sd_device *device, const char *key, const char **ret_value) {
+        const char *value;
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(key, -EINVAL);
+
+        r = device_properties_prepare(device);
+        if (r < 0)
+                return r;
+
+        value = ordered_hashmap_get(device->properties, key);
+        if (!value)
+                return -ENOENT;
+
+        if (ret_value)
+                *ret_value = value;
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int device_get_property_bool(sd_device *device, const char *key) {
+        const char *value;
+        int r;
+
+        assert(device);
+        assert(key);
+
+        r = sd_device_get_property_value(device, key, &value);
+        if (r < 0)
+                return r;
+
+        return parse_boolean(value);
+}
+
+int device_get_property_int(sd_device *device, const char *key, int *ret) {
+        const char *value;
+        int r, v;
+
+        assert(device);
+        assert(key);
+
+        r = sd_device_get_property_value(device, key, &value);
+        if (r < 0)
+                return r;
+
+        r = safe_atoi(value, &v);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = v;
+        return 0;
+}
+
+_public_ int sd_device_get_trigger_uuid(sd_device *device, sd_id128_t *ret) {
+        const char *s;
+        sd_id128_t id;
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        /* Retrieves the UUID attached to a uevent when triggering it from userspace via
+         * sd_device_trigger_with_uuid() or an equivalent interface. Returns -ENOENT if the record is not
+         * caused by a synthetic event and -ENODATA if it was but no UUID was specified */
+
+        r = sd_device_get_property_value(device, "SYNTH_UUID", &s);
+        if (r < 0)
+                return r;
+
+        if (streq(s, "0")) /* SYNTH_UUID=0 is set whenever a device is triggered by userspace without specifying a UUID */
+                return -ENODATA;
+
+        r = sd_id128_from_string(s, &id);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = id;
+
+        return 0;
+}
+
+void device_clear_sysattr_cache(sd_device *device) {
+        device->sysattr_values = hashmap_free(device->sysattr_values);
+}
+
+int device_cache_sysattr_value(sd_device *device, const char *key, char *value) {
+        _unused_ _cleanup_free_ char *old_value = NULL;
+        _cleanup_free_ char *new_key = NULL;
+        int r;
+
+        assert(device);
+        assert(key);
+
+        /* This takes the reference of the input value. The input value may be NULL.
+         * This replaces the value if it already exists. */
+
+        /* First, remove the old cache entry. So, we do not need to clear cache on error. */
+        old_value = hashmap_remove2(device->sysattr_values, key, (void **) &new_key);
+        if (!new_key) {
+                new_key = strdup(key);
+                if (!new_key)
+                        return -ENOMEM;
+        }
+
+        r = hashmap_ensure_put(&device->sysattr_values, &path_hash_ops_free_free, new_key, value);
+        if (r < 0)
+                return r;
+
+        TAKE_PTR(new_key);
+
+        return 0;
+}
+
+int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value) {
+        const char *k = NULL, *value;
+
+        assert(device);
+        assert(key);
+
+        value = hashmap_get2(device->sysattr_values, key, (void **) &k);
+        if (!k)
+                return -ESTALE; /* We have not read the attribute. */
+        if (!value)
+                return -ENOENT; /* We have looked up the attribute before and it did not exist. */
+        if (ret_value)
+                *ret_value = value;
+        return 0;
+}
+
+/* We cache all sysattr lookups. If an attribute does not exist, it is stored
+ * with a NULL value in the cache, otherwise the returned string is stored */
+_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret_value) {
+        _cleanup_free_ char *value = NULL, *path = NULL;
+        const char *syspath;
+        struct stat statbuf;
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(sysattr, -EINVAL);
+
+        /* look for possibly already cached result */
+        r = device_get_cached_sysattr_value(device, sysattr, ret_value);
+        if (r != -ESTALE)
+                return r;
+
+        r = sd_device_get_syspath(device, &syspath);
+        if (r < 0)
+                return r;
+
+        path = path_join(syspath, sysattr);
+        if (!path)
+                return -ENOMEM;
+
+        if (lstat(path, &statbuf) < 0) {
+                int k;
+
+                r = -errno;
+
+                /* remember that we could not access the sysattr */
+                k = device_cache_sysattr_value(device, sysattr, NULL);
+                if (k < 0)
+                        log_device_debug_errno(device, k,
+                                               "sd-device: failed to cache attribute '%s' with NULL, ignoring: %m",
+                                               sysattr);
+
+                return r;
+        } else if (S_ISLNK(statbuf.st_mode)) {
+                /* Some core links return only the last element of the target path,
+                 * these are just values, the paths should not be exposed. */
+                if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) {
+                        r = readlink_value(path, &value);
+                        if (r < 0)
+                                return r;
+                } else
+                        return -EINVAL;
+        } else if (S_ISDIR(statbuf.st_mode))
+                /* skip directories */
+                return -EISDIR;
+        else if (!(statbuf.st_mode & S_IRUSR))
+                /* skip non-readable files */
+                return -EPERM;
+        else {
+                size_t size;
+
+                /* Read attribute value, Some attributes contain embedded '\0'. So, it is necessary to
+                 * also get the size of the result. See issue #20025. */
+                r = read_full_virtual_file(path, &value, &size);
+                if (r < 0)
+                        return r;
+
+                /* drop trailing newlines */
+                while (size > 0 && strchr(NEWLINE, value[--size]))
+                        value[size] = '\0';
+        }
+
+        /* Unfortunately, we need to return 'const char*' instead of 'char*'. Hence, failure in caching
+         * sysattr value is critical unlike the other places. */
+        r = device_cache_sysattr_value(device, sysattr, value);
+        if (r < 0) {
+                log_device_debug_errno(device, r,
+                                       "sd-device: failed to cache attribute '%s' with '%s'%s: %m",
+                                       sysattr, value, ret_value ? "" : ", ignoring");
+                if (ret_value)
+                        return r;
+
+                return 0;
+        }
+
+        if (ret_value)
+                *ret_value = value;
+
+        TAKE_PTR(value);
+        return 0;
+}
+
+int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value) {
+        const char *value;
+        int r;
+
+        r = sd_device_get_sysattr_value(device, sysattr, &value);
+        if (r < 0)
+                return r;
+
+        int v;
+        r = safe_atoi(value, &v);
+        if (r < 0)
+                return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr);
+
+        if (ret_value)
+                *ret_value = v;
+        /* We return "true" if the value is positive. */
+        return v > 0;
+}
+
+int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value) {
+        const char *value;
+        int r;
+
+        r = sd_device_get_sysattr_value(device, sysattr, &value);
+        if (r < 0)
+                return r;
+
+        unsigned v;
+        r = safe_atou(value, &v);
+        if (r < 0)
+                return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr);
+
+        if (ret_value)
+                *ret_value = v;
+        /* We return "true" if the value is positive. */
+        return v > 0;
+}
+
+int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value) {
+        const char *value;
+        int r;
+
+        r = sd_device_get_sysattr_value(device, sysattr, &value);
+        if (r < 0)
+                return r;
+
+        uint32_t v;
+        r = safe_atou32(value, &v);
+        if (r < 0)
+                return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr);
+
+        if (ret_value)
+                *ret_value = v;
+        /* We return "true" if the value is positive. */
+        return v > 0;
+}
+
+int device_get_sysattr_bool(sd_device *device, const char *sysattr) {
+        const char *value;
+        int r;
+
+        assert(device);
+        assert(sysattr);
+
+        r = sd_device_get_sysattr_value(device, sysattr, &value);
+        if (r < 0)
+                return r;
+
+        return parse_boolean(value);
+}
+
+static void device_remove_cached_sysattr_value(sd_device *device, const char *_key) {
+        _cleanup_free_ char *key = NULL;
+
+        assert(device);
+        assert(_key);
+
+        free(hashmap_remove2(device->sysattr_values, _key, (void **) &key));
+}
+
+_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, const char *_value) {
+        _cleanup_free_ char *value = NULL, *path = NULL;
+        const char *syspath;
+        size_t len;
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(sysattr, -EINVAL);
+
+        /* Set the attribute and save it in the cache. */
+
+        if (!_value) {
+                /* If input value is NULL, then clear cache and not write anything. */
+                device_remove_cached_sysattr_value(device, sysattr);
+                return 0;
+        }
+
+        r = sd_device_get_syspath(device, &syspath);
+        if (r < 0)
+                return r;
+
+        path = path_join(syspath, sysattr);
+        if (!path)
+                return -ENOMEM;
+
+        len = strlen(_value);
+
+        /* drop trailing newlines */
+        while (len > 0 && strchr(NEWLINE, _value[len - 1]))
+                len--;
+
+        /* value length is limited to 4k */
+        if (len > 4096)
+                return -EINVAL;
+
+        value = strndup(_value, len);
+        if (!value)
+                return -ENOMEM;
+
+        r = write_string_file(path, value, WRITE_STRING_FILE_DISABLE_BUFFER | WRITE_STRING_FILE_NOFOLLOW);
+        if (r < 0) {
+                /* On failure, clear cache entry, as we do not know how it fails. */
+                device_remove_cached_sysattr_value(device, sysattr);
+                return r;
+        }
+
+        /* Do not cache action string written into uevent file. */
+        if (streq(sysattr, "uevent"))
+                return 0;
+
+        r = device_cache_sysattr_value(device, sysattr, value);
+        if (r < 0)
+                log_device_debug_errno(device, r,
+                                       "sd-device: failed to cache attribute '%s' with '%s', ignoring: %m",
+                                       sysattr, value);
+        else
+                TAKE_PTR(value);
+
+        return 0;
+}
+
+_public_ int sd_device_set_sysattr_valuef(sd_device *device, const char *sysattr, const char *format, ...) {
+        _cleanup_free_ char *value = NULL;
+        va_list ap;
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(sysattr, -EINVAL);
+
+        if (!format) {
+                device_remove_cached_sysattr_value(device, sysattr);
+                return 0;
+        }
+
+        va_start(ap, format);
+        r = vasprintf(&value, format, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return -ENOMEM;
+
+        return sd_device_set_sysattr_value(device, sysattr, value);
+}
+
+_public_ int sd_device_trigger(sd_device *device, sd_device_action_t action) {
+        const char *s;
+
+        assert_return(device, -EINVAL);
+
+        s = device_action_to_string(action);
+        if (!s)
+                return -EINVAL;
+
+        /* This uses the simple no-UUID interface of kernel < 4.13 */
+        return sd_device_set_sysattr_value(device, "uevent", s);
+}
+
+_public_ int sd_device_trigger_with_uuid(
+                sd_device *device,
+                sd_device_action_t action,
+                sd_id128_t *ret_uuid) {
+
+        const char *s, *j;
+        sd_id128_t u;
+        int r;
+
+        assert_return(device, -EINVAL);
+
+        /* If no one wants to know the UUID, use the simple interface from pre-4.13 times */
+        if (!ret_uuid)
+                return sd_device_trigger(device, action);
+
+        s = device_action_to_string(action);
+        if (!s)
+                return -EINVAL;
+
+        r = sd_id128_randomize(&u);
+        if (r < 0)
+                return r;
+
+        j = strjoina(s, " ", SD_ID128_TO_UUID_STRING(u));
+
+        r = sd_device_set_sysattr_value(device, "uevent", j);
+        if (r < 0)
+                return r;
+
+        *ret_uuid = u;
+        return 0;
+}
+
+_public_ int sd_device_open(sd_device *device, int flags) {
+        _cleanup_close_ int fd = -EBADF, fd2 = -EBADF;
+        const char *devname;
+        uint64_t q, diskseq = 0;
+        struct stat st;
+        dev_t devnum;
+        int r;
+
+        assert_return(device, -EINVAL);
+        assert_return(FLAGS_SET(flags, O_PATH) || !FLAGS_SET(flags, O_NOFOLLOW), -EINVAL);
+
+        r = sd_device_get_devname(device, &devname);
+        if (r == -ENOENT)
+                return -ENOEXEC;
+        if (r < 0)
+                return r;
+
+        r = sd_device_get_devnum(device, &devnum);
+        if (r == -ENOENT)
+                return -ENOEXEC;
+        if (r < 0)
+                return r;
+
+        fd = open(devname, FLAGS_SET(flags, O_PATH) ? flags : O_CLOEXEC|O_NOFOLLOW|O_PATH);
+        if (fd < 0)
+                return -errno;
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        if (st.st_rdev != devnum)
+                return -ENXIO;
+
+        if (device_in_subsystem(device, "block") ? !S_ISBLK(st.st_mode) : !S_ISCHR(st.st_mode))
+                return -ENXIO;
+
+        /* If flags has O_PATH, then we cannot check diskseq. Let's return earlier. */
+        if (FLAGS_SET(flags, O_PATH))
+                return TAKE_FD(fd);
+
+        /* If the device is not initialized, then we cannot determine if we should check diskseq through
+         * ID_IGNORE_DISKSEQ property. Let's skip to check diskseq in that case. */
+        r = sd_device_get_is_initialized(device);
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                r = device_get_property_bool(device, "ID_IGNORE_DISKSEQ");
+                if (r < 0 && r != -ENOENT)
+                        return r;
+                if (r <= 0) {
+                        r = sd_device_get_diskseq(device, &diskseq);
+                        if (r < 0 && r != -ENOENT)
+                                return r;
+                }
+        }
+
+        fd2 = fd_reopen(fd, flags);
+        if (fd2 < 0)
+                return fd2;
+
+        if (diskseq == 0)
+                return TAKE_FD(fd2);
+
+        r = fd_get_diskseq(fd2, &q);
+        if (r < 0)
+                return r;
+
+        if (q != diskseq)
+                return -ENXIO;
+
+        return TAKE_FD(fd2);
+}
+
+int device_opendir(sd_device *device, const char *subdir, DIR **ret) {
+        _cleanup_closedir_ DIR *d = NULL;
+        _cleanup_free_ char *path = NULL;
+        const char *syspath;
+        int r;
+
+        assert(device);
+        assert(ret);
+
+        r = sd_device_get_syspath(device, &syspath);
+        if (r < 0)
+                return r;
+
+        if (subdir) {
+                if (!path_is_safe(subdir))
+                        return -EINVAL;
+
+                path = path_join(syspath, subdir);
+                if (!path)
+                        return -ENOMEM;
+        }
+
+        d = opendir(path ?: syspath);
+        if (!d)
+                return -errno;
+
+        *ret = TAKE_PTR(d);
+        return 0;
+}
+#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c
index 663296f4..ef0c2d2a 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c
+++ b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c
@@ -99,16 +99,21 @@ int event_reset_time_relative(
                 const char *description,
                 bool force_reset) {
 
-        usec_t usec_now;
         int r;
 
         assert(e);
 
-        r = sd_event_now(e, clock, &usec_now);
-        if (r < 0)
-                return log_debug_errno(r, "sd-event: Failed to get the current time: %m");
+        if (usec > 0) {
+                usec_t usec_now;
+
+                r = sd_event_now(e, clock, &usec_now);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to get the current time: %m");
 
-        return event_reset_time(e, s, clock, usec_add(usec_now, usec), accuracy, callback, userdata, priority, description, force_reset);
+                usec = usec_add(usec_now, usec);
+        }
+
+        return event_reset_time(e, s, clock, usec, accuracy, callback, userdata, priority, description, force_reset);
 }
 
 #if 0 /* NM_IGNORED */
@@ -149,4 +154,21 @@ int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handle
 
         return 0;
 }
+
+int event_add_child_pidref(
+                sd_event *e,
+                sd_event_source **s,
+                const PidRef *pid,
+                int options,
+                sd_event_child_handler_t callback,
+                void *userdata) {
+
+        if (!pidref_is_set(pid))
+                return -ESRCH;
+
+        if (pid->fd >= 0)
+                return sd_event_add_child_pidfd(e, s, pid->fd, options, callback, userdata);
+
+        return sd_event_add_child(e, s, pid->pid, options, callback, userdata);
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h
index c1855844..ad0f2e78 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h
+++ b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h
@@ -5,6 +5,8 @@
 
 #include "sd-event.h"
 
+#include "pidref.h"
+
 int event_reset_time(
                 sd_event *e,
                 sd_event_source **s,
@@ -32,3 +34,7 @@ static inline int event_source_disable(sd_event_source *s) {
 }
 
 int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handler_t callback, void *userdata);
+
+#if 0 /* NM_IGNORED */
+int event_add_child_pidref(sd_event *e, sd_event_source **s, const PidRef *pid, int options, sd_event_child_handler_t callback, void *userdata);
+#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c b/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c
index 5c7ba182..6449b85c 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c
+++ b/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c
@@ -1169,10 +1169,10 @@ static int source_set_pending(sd_event_source *s, bool b) {
                 assert(s->inotify.inode_data->inotify_data);
 
                 if (b)
-                        s->inotify.inode_data->inotify_data->n_pending ++;
+                        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 --;
+                        s->inotify.inode_data->inotify_data->n_pending--;
                 }
         }
 
@@ -1579,7 +1579,7 @@ static int child_exit_callback(sd_event_source *s, const siginfo_t *si, void *us
 
 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;
+        return secure_getenv_bool("SYSTEMD_PIDFD") != 0;
 }
 
 _public_ int sd_event_add_child(
@@ -1983,7 +1983,7 @@ _public_ int sd_event_add_memory_pressure(
 
                 env = secure_getenv("MEMORY_PRESSURE_WRITE");
                 if (env) {
-                        r = unbase64mem(env, SIZE_MAX, &write_buffer, &write_buffer_size);
+                        r = unbase64mem(env, &write_buffer, &write_buffer_size);
                         if (r < 0)
                                 return r;
                 }
@@ -2239,8 +2239,8 @@ static int inode_data_compare(const struct inode_data *x, const struct inode_dat
 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);
+        siphash24_compress_typesafe(d->dev, state);
+        siphash24_compress_typesafe(d->ino, state);
 }
 
 DEFINE_PRIVATE_HASH_OPS(inode_data_hash_ops, struct inode_data, inode_data_hash_func, inode_data_compare);
@@ -4008,7 +4008,7 @@ static int process_inotify(sd_event *e) {
                 if (r < 0)
                         return r;
                 if (r > 0)
-                        done ++;
+                        done++;
         }
 
         return done;
@@ -4620,7 +4620,7 @@ static int process_epoll(sd_event *e, usec_t timeout, int64_t threshold, int64_t
 
         /* Set timestamp only when this is called first time. */
         if (threshold == INT64_MAX)
-                triple_timestamp_get(&e->timestamp);
+                triple_timestamp_now(&e->timestamp);
 
         for (size_t i = 0; i < m; i++) {
 
@@ -4923,13 +4923,13 @@ _public_ int sd_event_get_state(sd_event *e) {
 _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_origin_changed(e), -ECHILD);
 
         if (!e->exit_requested)
                 return -ENODATA;
 
-        *code = e->exit_code;
+        if (code)
+                *code = e->exit_code;
         return 0;
 }
 
@@ -5047,7 +5047,7 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) {
                 }
         }
 
-        e->watchdog = !!b;
+        e->watchdog = b;
         return e->watchdog;
 
 fail:
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c
index c20f7325..58173055 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c
+++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c
@@ -11,9 +11,29 @@
 #include "hexdecoct.h"
 #include "id128-util.h"
 #include "io-util.h"
+#include "sha256.h"
 #include "stdio-util.h"
 #include "string-util.h"
+#include "strv.h"
 #include "sync-util.h"
+#include "virt.h"
+
+int id128_from_string_nonzero(const char *s, sd_id128_t *ret) {
+        sd_id128_t t;
+        int r;
+
+        assert(ret);
+
+        r = sd_id128_from_string(ASSERT_PTR(s), &t);
+        if (r < 0)
+                return r;
+
+        if (sd_id128_is_null(t))
+                return -ENXIO;
+
+        *ret = t;
+        return 0;
+}
 
 #if 0 /* NM_IGNORED */
 bool id128_is_valid(const char *s) {
@@ -24,7 +44,7 @@ bool id128_is_valid(const char *s) {
         l = strlen(s);
 
         if (l == SD_ID128_STRING_MAX - 1)
-                /* Plain formatted 128bit hex string */
+                /* Plain formatted 128-bit hex string */
                 return in_charset(s, HEXDIGITS);
 
         if (l == SD_ID128_UUID_STRING_MAX - 1) {
@@ -53,7 +73,7 @@ int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret) {
 
         assert(fd >= 0);
 
-        /* Reads an 128bit ID from a file, which may either be in plain format (32 hex digits), or in UUID format, both
+        /* Reads an 128-bit 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".
@@ -123,7 +143,7 @@ int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret) {
         assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
         assert(path);
 
-        fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0);
+        fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
         if (fd < 0)
                 return fd;
 
@@ -151,7 +171,7 @@ int id128_write_fd(int fd, Id128Flag f, sd_id128_t id) {
         }
 
         buffer[sz - 1] = '\n';
-        r = loop_write(fd, buffer, sz, false);
+        r = loop_write(fd, buffer, sz);
         if (r < 0)
                 return r;
 
@@ -170,7 +190,7 @@ int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id) {
         assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
         assert(path);
 
-        fd = xopenat(dir_fd, path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, /* xopen_flags = */ 0, 0444);
+        fd = xopenat_full(dir_fd, path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, /* xopen_flags = */ 0, 0444);
         if (fd < 0)
                 return fd;
 
@@ -178,11 +198,11 @@ int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id) {
 }
 
 void id128_hash_func(const sd_id128_t *p, struct siphash *state) {
-        siphash24_compress(p, sizeof(sd_id128_t), state);
+        siphash24_compress_typesafe(*p, state);
 }
 
 int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) {
-        return memcmp(a, b, 16);
+        return memcmp(a, b, sizeof(sd_id128_t));
 }
 
 sd_id128_t id128_make_v4_uuid(sd_id128_t id) {
@@ -210,9 +230,22 @@ int id128_get_product(sd_id128_t *ret) {
         /* Reads the systems product UUID from DMI or devicetree (where it is located on POWER). This is
          * particularly relevant in VM environments, where VM managers typically place a VM uuid there. */
 
-        r = id128_read("/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid);
-        if (r == -ENOENT)
-                r = id128_read("/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid);
+        r = detect_container();
+        if (r < 0)
+                return r;
+        if (r > 0) /* Refuse returning this in containers, as this is not a property of our system then, but
+                    * of the host */
+                return -ENOENT;
+
+        FOREACH_STRING(i,
+                       "/sys/class/dmi/id/product_uuid", /* KVM */
+                       "/proc/device-tree/vm,uuid",      /* Device tree */
+                       "/sys/hypervisor/uuid") {         /* Xen */
+
+                r = id128_read(i, ID128_FORMAT_UUID, &uuid);
+                if (r != -ENOENT)
+                        break;
+        }
         if (r < 0)
                 return r;
 
@@ -222,4 +255,22 @@ int id128_get_product(sd_id128_t *ret) {
         *ret = uuid;
         return 0;
 }
+
+sd_id128_t id128_digest(const void *data, size_t size) {
+        assert(data || size == 0);
+
+        /* Hashes a UUID from some arbitrary data */
+
+        if (size == SIZE_MAX)
+                size = strlen(data);
+
+        uint8_t h[SHA256_DIGEST_SIZE];
+        sd_id128_t id;
+
+        /* Take the first half of the SHA256 result */
+        assert_cc(sizeof(h) >= sizeof(id.bytes));
+        memcpy(id.bytes, sha256_direct(data, size, h), sizeof(id.bytes));
+
+        return id128_make_v4_uuid(id);
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h
index 7bcbd8e5..53ba50a8 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h
+++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h
@@ -6,6 +6,7 @@
 
 #include "sd-id128.h"
 
+#include "errno-util.h"
 #include "hash-funcs.h"
 #include "macro.h"
 
@@ -20,6 +21,8 @@ typedef enum Id128Flag {
         ID128_REFUSE_NULL   = 1 << 3, /* Refuse all zero ID with -ENOMEDIUM. */
 } Id128Flag;
 
+int id128_from_string_nonzero(const char *s, sd_id128_t *ret);
+
 int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret);
 int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret);
 static inline int id128_read(const char *path, Id128Flag f, sd_id128_t *ret) {
@@ -44,9 +47,12 @@ sd_id128_t id128_make_v4_uuid(sd_id128_t id);
 
 int id128_get_product(sd_id128_t *ret);
 
+sd_id128_t id128_digest(const void *data, size_t size);
+
 /* A helper to check for the three relevant cases of "machine ID not initialized" */
-#define ERRNO_IS_MACHINE_ID_UNSET(r)            \
-        IN_SET(abs(r),                          \
-               ENOENT,                          \
-               ENOMEDIUM,                       \
-               ENOPKG)
+#define ERRNO_IS_NEG_MACHINE_ID_UNSET(r)        \
+        IN_SET(r,                               \
+               -ENOENT,                         \
+               -ENOMEDIUM,                      \
+               -ENOPKG)
+_DEFINE_ABS_WRAPPER(MACHINE_ID_UNSET);
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c b/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c
index c63e1a97..ff0db776 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c
@@ -345,18 +345,20 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
         return 0;
 }
 
-static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) {
-        uint8_t hmac[SHA256_DIGEST_SIZE];
-        sd_id128_t result;
+_public_ int sd_id128_get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) {
+        assert_cc(sizeof(sd_id128_t) < SHA256_DIGEST_SIZE); /* Check that we don't need to pad with zeros. */
+        union {
+                uint8_t hmac[SHA256_DIGEST_SIZE];
+                sd_id128_t result;
+        } buf;
 
-        assert(ret);
+        assert_return(ret, -EINVAL);
+        assert_return(!sd_id128_is_null(app_id), -ENXIO);
 
-        hmac_sha256(&base, sizeof(base), &app_id, sizeof(app_id), hmac);
+        hmac_sha256(&base, sizeof(base), &app_id, sizeof(app_id), buf.hmac);
 
         /* Take only the first half. */
-        memcpy(&result, hmac, MIN(sizeof(hmac), sizeof(result)));
-
-        *ret = id128_make_v4_uuid(result);
+        *ret = id128_make_v4_uuid(buf.result);
         return 0;
 }
 
@@ -370,7 +372,7 @@ _public_ int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *re
         if (r < 0)
                 return r;
 
-        return get_app_specific(id, app_id, ret);
+        return sd_id128_get_app_specific(id, app_id, ret);
 }
 
 _public_ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
@@ -383,6 +385,6 @@ _public_ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret)
         if (r < 0)
                 return r;
 
-        return get_app_specific(id, app_id, ret);
+        return sd_id128_get_app_specific(id, app_id, ret);
 }
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-core/src/systemd/_sd-common.h b/src/libnm-systemd-core/src/systemd/_sd-common.h
index 6f657c22..d4381d90 100644
--- a/src/libnm-systemd-core/src/systemd/_sd-common.h
+++ b/src/libnm-systemd-core/src/systemd/_sd-common.h
@@ -99,7 +99,7 @@ typedef void (*_sd_destroy_t)(void *userdata);
         }                                                       \
         struct _sd_useless_struct_to_allow_trailing_semicolon_
 
-/* The following macro should be used in all public enums, to force 64bit wideness on them, so that we can
+/* The following macro should be used in all public enums, to force 64-bit wideness on them, so that we can
  * freely extend them later on, without breaking compatibility. */
 #define _SD_ENUM_FORCE_S64(id)               \
         _SD_##id##_INT64_MIN = INT64_MIN,    \
diff --git a/src/libnm-systemd-core/src/systemd/sd-device.h b/src/libnm-systemd-core/src/systemd/sd-device.h
index e3d647f7..b67ec0f3 100644
--- a/src/libnm-systemd-core/src/systemd/sd-device.h
+++ b/src/libnm-systemd-core/src/systemd/sd-device.h
@@ -129,6 +129,7 @@ sd_device *sd_device_enumerator_get_subsystem_next(sd_device_enumerator *enumera
 int sd_device_enumerator_add_match_subsystem(sd_device_enumerator *enumerator, const char *subsystem, int match);
 int sd_device_enumerator_add_match_sysattr(sd_device_enumerator *enumerator, const char *sysattr, const char *value, int match);
 int sd_device_enumerator_add_match_property(sd_device_enumerator *enumerator, const char *property, const char *value);
+int sd_device_enumerator_add_match_property_required(sd_device_enumerator *enumerator, const char *property, const char *value);
 int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname);
 int sd_device_enumerator_add_nomatch_sysname(sd_device_enumerator *enumerator, const char *sysname);
 int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag);
diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp-duid.h b/src/libnm-systemd-core/src/systemd/sd-dhcp-duid.h
new file mode 100644
index 00000000..555b40e9
--- /dev/null
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp-duid.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosddhcpduidhfoo
+#define foosddhcpduidhfoo
+
+/***
+  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 <https://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+enum {
+        SD_DUID_TYPE_LLT        = 1,
+        SD_DUID_TYPE_EN         = 2,
+        SD_DUID_TYPE_LL         = 3,
+        SD_DUID_TYPE_UUID       = 4
+};
+
+typedef struct sd_dhcp_duid sd_dhcp_duid;
+
+int sd_dhcp_duid_clear(sd_dhcp_duid *duid);
+
+int sd_dhcp_duid_is_set(const sd_dhcp_duid *duid);
+
+int sd_dhcp_duid_get(const sd_dhcp_duid *duid, uint16_t *ret_type, const void **ret_data, size_t *ret_size);
+int sd_dhcp_duid_get_raw(const sd_dhcp_duid *duid, const void **ret_data, size_t *ret_size);
+
+int sd_dhcp_duid_set(
+                sd_dhcp_duid *duid,
+                uint16_t duid_type,
+                const void *data,
+                size_t data_size);
+int sd_dhcp_duid_set_raw(
+                sd_dhcp_duid *duid,
+                const void *data,
+                size_t data_size);
+int sd_dhcp_duid_set_llt(
+                sd_dhcp_duid *duid,
+                const void *hw_addr,
+                size_t hw_addr_size,
+                uint16_t arp_type,
+                uint64_t usec);
+int sd_dhcp_duid_set_ll(
+                sd_dhcp_duid *duid,
+                const void *hw_addr,
+                size_t hw_addr_size,
+                uint16_t arp_type);
+int sd_dhcp_duid_set_en(sd_dhcp_duid *duid);
+int sd_dhcp_duid_set_uuid(sd_dhcp_duid *duid);
+
+int sd_dhcp_duid_to_string(const sd_dhcp_duid *duid, char **ret);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h
index a9fa7856..d551b4dd 100644
--- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h
@@ -24,6 +24,7 @@
 #include <sys/types.h>
 
 #include "sd-device.h"
+#include "sd-dhcp-duid.h"
 #include "sd-dhcp6-lease.h"
 #include "sd-dhcp6-option.h"
 #include "sd-event.h"
@@ -40,154 +41,6 @@ enum {
         SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST       = 13
 };
 
-/* https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-2 */
-enum {
-        SD_DHCP6_OPTION_CLIENTID                   = 1,  /* RFC 8415 */
-        SD_DHCP6_OPTION_SERVERID                   = 2,  /* RFC 8415 */
-        SD_DHCP6_OPTION_IA_NA                      = 3,  /* RFC 8415 */
-        SD_DHCP6_OPTION_IA_TA                      = 4,  /* RFC 8415 */
-        SD_DHCP6_OPTION_IAADDR                     = 5,  /* RFC 8415 */
-        SD_DHCP6_OPTION_ORO                        = 6,  /* RFC 8415 */
-        SD_DHCP6_OPTION_PREFERENCE                 = 7,  /* RFC 8415 */
-        SD_DHCP6_OPTION_ELAPSED_TIME               = 8,  /* RFC 8415 */
-        SD_DHCP6_OPTION_RELAY_MSG                  = 9,  /* RFC 8415 */
-        /* option code 10 is unassigned */
-        SD_DHCP6_OPTION_AUTH                       = 11,  /* RFC 8415 */
-        SD_DHCP6_OPTION_UNICAST                    = 12,  /* RFC 8415 */
-        SD_DHCP6_OPTION_STATUS_CODE                = 13,  /* RFC 8415 */
-        SD_DHCP6_OPTION_RAPID_COMMIT               = 14,  /* RFC 8415 */
-        SD_DHCP6_OPTION_USER_CLASS                 = 15,  /* RFC 8415 */
-        SD_DHCP6_OPTION_VENDOR_CLASS               = 16,  /* RFC 8415 */
-        SD_DHCP6_OPTION_VENDOR_OPTS                = 17,  /* RFC 8415 */
-        SD_DHCP6_OPTION_INTERFACE_ID               = 18,  /* RFC 8415 */
-        SD_DHCP6_OPTION_RECONF_MSG                 = 19,  /* RFC 8415 */
-        SD_DHCP6_OPTION_RECONF_ACCEPT              = 20,  /* RFC 8415 */
-        SD_DHCP6_OPTION_SIP_SERVER_DOMAIN_NAME     = 21,  /* RFC 3319 */
-        SD_DHCP6_OPTION_SIP_SERVER_ADDRESS         = 22,  /* RFC 3319 */
-        SD_DHCP6_OPTION_DNS_SERVER                 = 23,  /* RFC 3646 */
-        SD_DHCP6_OPTION_DOMAIN                     = 24,  /* RFC 3646 */
-        SD_DHCP6_OPTION_IA_PD                      = 25,  /* RFC 3633, RFC 8415 */
-        SD_DHCP6_OPTION_IA_PD_PREFIX               = 26,  /* RFC 3633, RFC 8415 */
-        SD_DHCP6_OPTION_NIS_SERVER                 = 27,  /* RFC 3898 */
-        SD_DHCP6_OPTION_NISP_SERVER                = 28,  /* RFC 3898 */
-        SD_DHCP6_OPTION_NIS_DOMAIN_NAME            = 29,  /* RFC 3898 */
-        SD_DHCP6_OPTION_NISP_DOMAIN_NAME           = 30,  /* RFC 3898 */
-        SD_DHCP6_OPTION_SNTP_SERVER                = 31,  /* RFC 4075, deprecated */
-        SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME   = 32,  /* RFC 4242, 8415, sec. 21.23 */
-        SD_DHCP6_OPTION_BCMCS_SERVER_D             = 33,  /* RFC 4280 */
-        SD_DHCP6_OPTION_BCMCS_SERVER_A             = 34,  /* RFC 4280 */
-        /* option code 35 is unassigned */
-        SD_DHCP6_OPTION_GEOCONF_CIVIC              = 36,  /* RFC 4776 */
-        SD_DHCP6_OPTION_REMOTE_ID                  = 37,  /* RFC 4649 */
-        SD_DHCP6_OPTION_SUBSCRIBER_ID              = 38,  /* RFC 4580 */
-        SD_DHCP6_OPTION_CLIENT_FQDN                = 39,  /* RFC 4704 */
-        SD_DHCP6_OPTION_PANA_AGENT                 = 40,  /* RFC 5192 */
-        SD_DHCP6_OPTION_POSIX_TIMEZONE             = 41,  /* RFC 4833 */
-        SD_DHCP6_OPTION_TZDB_TIMEZONE              = 42,  /* RFC 4833 */
-        SD_DHCP6_OPTION_ERO                        = 43,  /* RFC 4994 */
-        SD_DHCP6_OPTION_LQ_QUERY                   = 44,  /* RFC 5007 */
-        SD_DHCP6_OPTION_CLIENT_DATA                = 45,  /* RFC 5007 */
-        SD_DHCP6_OPTION_CLT_TIME                   = 46,  /* RFC 5007 */
-        SD_DHCP6_OPTION_LQ_RELAY_DATA              = 47,  /* RFC 5007 */
-        SD_DHCP6_OPTION_LQ_CLIENT_LINK             = 48,  /* RFC 5007 */
-        SD_DHCP6_OPTION_MIP6_HNIDF                 = 49,  /* RFC 6610 */
-        SD_DHCP6_OPTION_MIP6_VDINF                 = 50,  /* RFC 6610 */
-        SD_DHCP6_OPTION_V6_LOST                    = 51,  /* RFC 5223 */
-        SD_DHCP6_OPTION_CAPWAP_AC_V6               = 52,  /* RFC 5417 */
-        SD_DHCP6_OPTION_RELAY_ID                   = 53,  /* RFC 5460 */
-        SD_DHCP6_OPTION_IPV6_ADDRESS_MOS           = 54,  /* RFC 5678 */
-        SD_DHCP6_OPTION_IPV6_FQDN_MOS              = 55,  /* RFC 5678 */
-        SD_DHCP6_OPTION_NTP_SERVER                 = 56,  /* RFC 5908 */
-        SD_DHCP6_OPTION_V6_ACCESS_DOMAIN           = 57,  /* RFC 5986 */
-        SD_DHCP6_OPTION_SIP_UA_CS_LIST             = 58,  /* RFC 6011 */
-        SD_DHCP6_OPTION_BOOTFILE_URL               = 59,  /* RFC 5970 */
-        SD_DHCP6_OPTION_BOOTFILE_PARAM             = 60,  /* RFC 5970 */
-        SD_DHCP6_OPTION_CLIENT_ARCH_TYPE           = 61,  /* RFC 5970 */
-        SD_DHCP6_OPTION_NII                        = 62,  /* RFC 5970 */
-        SD_DHCP6_OPTION_GEOLOCATION                = 63,  /* RFC 6225 */
-        SD_DHCP6_OPTION_AFTR_NAME                  = 64,  /* RFC 6334 */
-        SD_DHCP6_OPTION_ERP_LOCAL_DOMAIN_NAME      = 65,  /* RFC 6440 */
-        SD_DHCP6_OPTION_RSOO                       = 66,  /* RFC 6422 */
-        SD_DHCP6_OPTION_PD_EXCLUDE                 = 67,  /* RFC 6603 */
-        SD_DHCP6_OPTION_VSS                        = 68,  /* RFC 6607 */
-        SD_DHCP6_OPTION_MIP6_IDINF                 = 69,  /* RFC 6610 */
-        SD_DHCP6_OPTION_MIP6_UDINF                 = 70,  /* RFC 6610 */
-        SD_DHCP6_OPTION_MIP6_HNP                   = 71,  /* RFC 6610 */
-        SD_DHCP6_OPTION_MIP6_HAA                   = 72,  /* RFC 6610 */
-        SD_DHCP6_OPTION_MIP6_HAF                   = 73,  /* RFC 6610 */
-        SD_DHCP6_OPTION_RDNSS_SELECTION            = 74,  /* RFC 6731 */
-        SD_DHCP6_OPTION_KRB_PRINCIPAL_NAME         = 75,  /* RFC 6784 */
-        SD_DHCP6_OPTION_KRB_REALM_NAME             = 76,  /* RFC 6784 */
-        SD_DHCP6_OPTION_KRB_DEFAULT_REALM_NAME     = 77,  /* RFC 6784 */
-        SD_DHCP6_OPTION_KRB_KDC                    = 78,  /* RFC 6784 */
-        SD_DHCP6_OPTION_CLIENT_LINKLAYER_ADDR      = 79,  /* RFC 6939 */
-        SD_DHCP6_OPTION_LINK_ADDRESS               = 80,  /* RFC 6977 */
-        SD_DHCP6_OPTION_RADIUS                     = 81,  /* RFC 7037 */
-        SD_DHCP6_OPTION_SOL_MAX_RT                 = 82,  /* RFC 7083, RFC 8415 */
-        SD_DHCP6_OPTION_INF_MAX_RT                 = 83,  /* RFC 7083, RFC 8415 */
-        SD_DHCP6_OPTION_ADDRSEL                    = 84,  /* RFC 7078 */
-        SD_DHCP6_OPTION_ADDRSEL_TABLE              = 85,  /* RFC 7078 */
-        SD_DHCP6_OPTION_V6_PCP_SERVER              = 86,  /* RFC 7291 */
-        SD_DHCP6_OPTION_DHCPV4_MSG                 = 87,  /* RFC 7341 */
-        SD_DHCP6_OPTION_DHCP4_O_DHCP6_SERVER       = 88,  /* RFC 7341 */
-        SD_DHCP6_OPTION_S46_RULE                   = 89,  /* RFC 7598 */
-        SD_DHCP6_OPTION_S46_BR                     = 90,  /* RFC 7598, RFC 8539 */
-        SD_DHCP6_OPTION_S46_DMR                    = 91,  /* RFC 7598 */
-        SD_DHCP6_OPTION_S46_V4V6BIND               = 92,  /* RFC 7598 */
-        SD_DHCP6_OPTION_S46_PORTPARAMS             = 93,  /* RFC 7598 */
-        SD_DHCP6_OPTION_S46_CONT_MAPE              = 94,  /* RFC 7598 */
-        SD_DHCP6_OPTION_S46_CONT_MAPT              = 95,  /* RFC 7598 */
-        SD_DHCP6_OPTION_S46_CONT_LW                = 96,  /* RFC 7598 */
-        SD_DHCP6_OPTION_4RD                        = 97,  /* RFC 7600 */
-        SD_DHCP6_OPTION_4RD_MAP_RULE               = 98,  /* RFC 7600 */
-        SD_DHCP6_OPTION_4RD_NON_MAP_RULE           = 99,  /* RFC 7600 */
-        SD_DHCP6_OPTION_LQ_BASE_TIME               = 100, /* RFC 7653 */
-        SD_DHCP6_OPTION_LQ_START_TIME              = 101, /* RFC 7653 */
-        SD_DHCP6_OPTION_LQ_END_TIME                = 102, /* RFC 7653 */
-        SD_DHCP6_OPTION_CAPTIVE_PORTAL             = 103, /* RFC 8910 */
-        SD_DHCP6_OPTION_MPL_PARAMETERS             = 104, /* RFC 7774 */
-        SD_DHCP6_OPTION_ANI_ATT                    = 105, /* RFC 7839 */
-        SD_DHCP6_OPTION_ANI_NETWORK_NAME           = 106, /* RFC 7839 */
-        SD_DHCP6_OPTION_ANI_AP_NAME                = 107, /* RFC 7839 */
-        SD_DHCP6_OPTION_ANI_AP_BSSID               = 108, /* RFC 7839 */
-        SD_DHCP6_OPTION_ANI_OPERATOR_ID            = 109, /* RFC 7839 */
-        SD_DHCP6_OPTION_ANI_OPERATOR_REALM         = 110, /* RFC 7839 */
-        SD_DHCP6_OPTION_S46_PRIORITY               = 111, /* RFC 8026 */
-        SD_DHCP6_OPTION_MUD_URL_V6                 = 112, /* RFC 8520 */
-        SD_DHCP6_OPTION_V6_PREFIX64                = 113, /* RFC 8115 */
-        SD_DHCP6_OPTION_F_BINDING_STATUS           = 114, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_CONNECT_FLAGS            = 115, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_DNS_REMOVAL_INFO         = 116, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_DNS_HOST_NAME            = 117, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_DNS_ZONE_NAME            = 118, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_DNS_FLAGS                = 119, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_EXPIRATION_TIME          = 120, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_MAX_UNACKED_BNDUPD       = 121, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_MCLT                     = 122, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_PARTNER_LIFETIME         = 123, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_PARTNER_LIFETIME_SENT    = 124, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_PARTNER_DOWN_TIME        = 125, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_PARTNER_RAW_CLT_TIME     = 126, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_PROTOCOL_VERSION         = 127, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_KEEPALIVE_TIME           = 128, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_RECONFIGURE_DATA         = 129, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_RELATIONSHIP_NAME        = 130, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_SERVER_FLAGS             = 131, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_SERVER_STATE             = 132, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_START_TIME_OF_STATE      = 133, /* RFC 8156 */
-        SD_DHCP6_OPTION_F_STATE_EXPIRATION_TIME    = 134, /* RFC 8156 */
-        SD_DHCP6_OPTION_RELAY_PORT                 = 135, /* RFC 8357 */
-        SD_DHCP6_OPTION_V6_SZTP_REDIRECT           = 136, /* RFC 8572 */
-        SD_DHCP6_OPTION_S46_BIND_IPV6_PREFIX       = 137, /* RFC 8539 */
-        SD_DHCP6_OPTION_IA_LL                      = 138, /* RFC 8947 */
-        SD_DHCP6_OPTION_LLADDR                     = 139, /* RFC 8947 */
-        SD_DHCP6_OPTION_SLAP_QUAD                  = 140, /* RFC 8948 */
-        SD_DHCP6_OPTION_V6_DOTS_RI                 = 141, /* RFC 8973 */
-        SD_DHCP6_OPTION_V6_DOTS_ADDRESS            = 142, /* RFC 8973 */
-        SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF         = 143 /* RFC 6153 */
-        /* 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);
@@ -211,23 +64,20 @@ int sd_dhcp6_client_set_mac(
                 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_duid_llt(sd_dhcp6_client *client, uint64_t llt_time);
+int sd_dhcp6_client_set_duid_ll(sd_dhcp6_client *client);
+int sd_dhcp6_client_set_duid_en(sd_dhcp6_client *client);
+int sd_dhcp6_client_set_duid_uuid(sd_dhcp6_client *client);
+int sd_dhcp6_client_set_duid_raw(sd_dhcp6_client *client, uint16_t duid_type, const uint8_t *duid, size_t duid_len);
+int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, const sd_dhcp_duid *duid);
+int sd_dhcp6_client_get_duid(sd_dhcp6_client *client, const sd_dhcp_duid **ret);
+int sd_dhcp6_client_get_duid_as_string(sd_dhcp6_client *client, char **ret);
 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);
diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h
index 716f6fc1..e18d5781 100644
--- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h
@@ -23,6 +23,8 @@
 #include <netinet/in.h>
 #include <sys/types.h>
 
+#include "sd-dhcp6-option.h"
+
 #include "_sd-common.h"
 
 _SD_BEGIN_DECLARATIONS;
@@ -30,24 +32,54 @@ _SD_BEGIN_DECLARATIONS;
 typedef struct sd_dhcp6_lease sd_dhcp6_lease;
 
 int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret);
+int sd_dhcp6_lease_get_t1(sd_dhcp6_lease *lease, uint64_t *ret);
+int sd_dhcp6_lease_get_t1_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret);
+int sd_dhcp6_lease_get_t2(sd_dhcp6_lease *lease, uint64_t *ret);
+int sd_dhcp6_lease_get_t2_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret);
+int sd_dhcp6_lease_get_valid_lifetime(sd_dhcp6_lease *lease, uint64_t *ret);
+int sd_dhcp6_lease_get_valid_lifetime_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret);
 int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *ret);
 
-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_address_iterator_reset(sd_dhcp6_lease *lease);
+int sd_dhcp6_lease_address_iterator_next(sd_dhcp6_lease *lease);
+int sd_dhcp6_lease_get_address(
+                sd_dhcp6_lease *lease,
+                struct in6_addr *ret);
+int sd_dhcp6_lease_get_address_lifetime(
+                sd_dhcp6_lease *lease,
+                uint64_t *ret_lifetime_preferred,
+                uint64_t *ret_lifetime_valid);
+int sd_dhcp6_lease_get_address_lifetime_timestamp(
+                sd_dhcp6_lease *lease,
+                clockid_t clock,
+                uint64_t *ret_lifetime_preferred,
+                uint64_t *ret_lifetime_valid);
+int sd_dhcp6_lease_has_address(sd_dhcp6_lease *lease);
+
+int sd_dhcp6_lease_pd_iterator_reset(sd_dhcp6_lease *lease);
+int sd_dhcp6_lease_pd_iterator_next(sd_dhcp6_lease *lease);
+int sd_dhcp6_lease_get_pd_prefix(
+                sd_dhcp6_lease *lease,
+                struct in6_addr *ret_prefix,
+                uint8_t *ret_prefix_length);
+int sd_dhcp6_lease_get_pd_lifetime(
+                sd_dhcp6_lease *lease,
+                uint64_t *ret_lifetime_preferred,
+                uint64_t *ret_lifetime_valid);
+int sd_dhcp6_lease_get_pd_lifetime_timestamp(
+                sd_dhcp6_lease *lease,
+                clockid_t clock,
+                uint64_t *ret_lifetime_preferred,
+                uint64_t *ret_lifetime_valid);
+int sd_dhcp6_lease_has_pd_prefix(sd_dhcp6_lease *lease);
 
 int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret);
 int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret);
 int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret);
 int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret);
 int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret);
+int sd_dhcp6_lease_get_captive_portal(sd_dhcp6_lease *lease, const char **ret);
+int sd_dhcp6_lease_get_vendor_options(sd_dhcp6_lease *lease, sd_dhcp6_option ***ret);
 
 sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease);
 sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease);
diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h
index b4b4671e..32012426 100644
--- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h
@@ -20,6 +20,8 @@
 #include <inttypes.h>
 #include <sys/types.h>
 
+#include "sd-dhcp6-protocol.h"
+
 #include "_sd-common.h"
 
 _SD_BEGIN_DECLARATIONS;
diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-protocol.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-protocol.h
new file mode 100644
index 00000000..78c80f7c
--- /dev/null
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-protocol.h
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosddhcp6protocolhfoo
+#define foosddhcp6protocolhfoo
+
+/***
+  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 <https://www.gnu.org/licenses/>.
+***/
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+/* https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-2 */
+enum {
+        SD_DHCP6_OPTION_CLIENTID                   = 1,  /* RFC 8415 */
+        SD_DHCP6_OPTION_SERVERID                   = 2,  /* RFC 8415 */
+        SD_DHCP6_OPTION_IA_NA                      = 3,  /* RFC 8415 */
+        SD_DHCP6_OPTION_IA_TA                      = 4,  /* RFC 8415 */
+        SD_DHCP6_OPTION_IAADDR                     = 5,  /* RFC 8415 */
+        SD_DHCP6_OPTION_ORO                        = 6,  /* RFC 8415 */
+        SD_DHCP6_OPTION_PREFERENCE                 = 7,  /* RFC 8415 */
+        SD_DHCP6_OPTION_ELAPSED_TIME               = 8,  /* RFC 8415 */
+        SD_DHCP6_OPTION_RELAY_MSG                  = 9,  /* RFC 8415 */
+        /* option code 10 is unassigned */
+        SD_DHCP6_OPTION_AUTH                       = 11,  /* RFC 8415 */
+        SD_DHCP6_OPTION_UNICAST                    = 12,  /* RFC 8415 */
+        SD_DHCP6_OPTION_STATUS_CODE                = 13,  /* RFC 8415 */
+        SD_DHCP6_OPTION_RAPID_COMMIT               = 14,  /* RFC 8415 */
+        SD_DHCP6_OPTION_USER_CLASS                 = 15,  /* RFC 8415 */
+        SD_DHCP6_OPTION_VENDOR_CLASS               = 16,  /* RFC 8415 */
+        SD_DHCP6_OPTION_VENDOR_OPTS                = 17,  /* RFC 8415 */
+        SD_DHCP6_OPTION_INTERFACE_ID               = 18,  /* RFC 8415 */
+        SD_DHCP6_OPTION_RECONF_MSG                 = 19,  /* RFC 8415 */
+        SD_DHCP6_OPTION_RECONF_ACCEPT              = 20,  /* RFC 8415 */
+        SD_DHCP6_OPTION_SIP_SERVER_DOMAIN_NAME     = 21,  /* RFC 3319 */
+        SD_DHCP6_OPTION_SIP_SERVER_ADDRESS         = 22,  /* RFC 3319 */
+        SD_DHCP6_OPTION_DNS_SERVER                 = 23,  /* RFC 3646 */
+        SD_DHCP6_OPTION_DOMAIN                     = 24,  /* RFC 3646 */
+        SD_DHCP6_OPTION_IA_PD                      = 25,  /* RFC 3633, RFC 8415 */
+        SD_DHCP6_OPTION_IA_PD_PREFIX               = 26,  /* RFC 3633, RFC 8415 */
+        SD_DHCP6_OPTION_NIS_SERVER                 = 27,  /* RFC 3898 */
+        SD_DHCP6_OPTION_NISP_SERVER                = 28,  /* RFC 3898 */
+        SD_DHCP6_OPTION_NIS_DOMAIN_NAME            = 29,  /* RFC 3898 */
+        SD_DHCP6_OPTION_NISP_DOMAIN_NAME           = 30,  /* RFC 3898 */
+        SD_DHCP6_OPTION_SNTP_SERVER                = 31,  /* RFC 4075, deprecated */
+        SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME   = 32,  /* RFC 4242, 8415, sec. 21.23 */
+        SD_DHCP6_OPTION_BCMCS_SERVER_D             = 33,  /* RFC 4280 */
+        SD_DHCP6_OPTION_BCMCS_SERVER_A             = 34,  /* RFC 4280 */
+        /* option code 35 is unassigned */
+        SD_DHCP6_OPTION_GEOCONF_CIVIC              = 36,  /* RFC 4776 */
+        SD_DHCP6_OPTION_REMOTE_ID                  = 37,  /* RFC 4649 */
+        SD_DHCP6_OPTION_SUBSCRIBER_ID              = 38,  /* RFC 4580 */
+        SD_DHCP6_OPTION_CLIENT_FQDN                = 39,  /* RFC 4704 */
+        SD_DHCP6_OPTION_PANA_AGENT                 = 40,  /* RFC 5192 */
+        SD_DHCP6_OPTION_POSIX_TIMEZONE             = 41,  /* RFC 4833 */
+        SD_DHCP6_OPTION_TZDB_TIMEZONE              = 42,  /* RFC 4833 */
+        SD_DHCP6_OPTION_ERO                        = 43,  /* RFC 4994 */
+        SD_DHCP6_OPTION_LQ_QUERY                   = 44,  /* RFC 5007 */
+        SD_DHCP6_OPTION_CLIENT_DATA                = 45,  /* RFC 5007 */
+        SD_DHCP6_OPTION_CLT_TIME                   = 46,  /* RFC 5007 */
+        SD_DHCP6_OPTION_LQ_RELAY_DATA              = 47,  /* RFC 5007 */
+        SD_DHCP6_OPTION_LQ_CLIENT_LINK             = 48,  /* RFC 5007 */
+        SD_DHCP6_OPTION_MIP6_HNIDF                 = 49,  /* RFC 6610 */
+        SD_DHCP6_OPTION_MIP6_VDINF                 = 50,  /* RFC 6610 */
+        SD_DHCP6_OPTION_V6_LOST                    = 51,  /* RFC 5223 */
+        SD_DHCP6_OPTION_CAPWAP_AC_V6               = 52,  /* RFC 5417 */
+        SD_DHCP6_OPTION_RELAY_ID                   = 53,  /* RFC 5460 */
+        SD_DHCP6_OPTION_IPV6_ADDRESS_MOS           = 54,  /* RFC 5678 */
+        SD_DHCP6_OPTION_IPV6_FQDN_MOS              = 55,  /* RFC 5678 */
+        SD_DHCP6_OPTION_NTP_SERVER                 = 56,  /* RFC 5908 */
+        SD_DHCP6_OPTION_V6_ACCESS_DOMAIN           = 57,  /* RFC 5986 */
+        SD_DHCP6_OPTION_SIP_UA_CS_LIST             = 58,  /* RFC 6011 */
+        SD_DHCP6_OPTION_BOOTFILE_URL               = 59,  /* RFC 5970 */
+        SD_DHCP6_OPTION_BOOTFILE_PARAM             = 60,  /* RFC 5970 */
+        SD_DHCP6_OPTION_CLIENT_ARCH_TYPE           = 61,  /* RFC 5970 */
+        SD_DHCP6_OPTION_NII                        = 62,  /* RFC 5970 */
+        SD_DHCP6_OPTION_GEOLOCATION                = 63,  /* RFC 6225 */
+        SD_DHCP6_OPTION_AFTR_NAME                  = 64,  /* RFC 6334 */
+        SD_DHCP6_OPTION_ERP_LOCAL_DOMAIN_NAME      = 65,  /* RFC 6440 */
+        SD_DHCP6_OPTION_RSOO                       = 66,  /* RFC 6422 */
+        SD_DHCP6_OPTION_PD_EXCLUDE                 = 67,  /* RFC 6603 */
+        SD_DHCP6_OPTION_VSS                        = 68,  /* RFC 6607 */
+        SD_DHCP6_OPTION_MIP6_IDINF                 = 69,  /* RFC 6610 */
+        SD_DHCP6_OPTION_MIP6_UDINF                 = 70,  /* RFC 6610 */
+        SD_DHCP6_OPTION_MIP6_HNP                   = 71,  /* RFC 6610 */
+        SD_DHCP6_OPTION_MIP6_HAA                   = 72,  /* RFC 6610 */
+        SD_DHCP6_OPTION_MIP6_HAF                   = 73,  /* RFC 6610 */
+        SD_DHCP6_OPTION_RDNSS_SELECTION            = 74,  /* RFC 6731 */
+        SD_DHCP6_OPTION_KRB_PRINCIPAL_NAME         = 75,  /* RFC 6784 */
+        SD_DHCP6_OPTION_KRB_REALM_NAME             = 76,  /* RFC 6784 */
+        SD_DHCP6_OPTION_KRB_DEFAULT_REALM_NAME     = 77,  /* RFC 6784 */
+        SD_DHCP6_OPTION_KRB_KDC                    = 78,  /* RFC 6784 */
+        SD_DHCP6_OPTION_CLIENT_LINKLAYER_ADDR      = 79,  /* RFC 6939 */
+        SD_DHCP6_OPTION_LINK_ADDRESS               = 80,  /* RFC 6977 */
+        SD_DHCP6_OPTION_RADIUS                     = 81,  /* RFC 7037 */
+        SD_DHCP6_OPTION_SOL_MAX_RT                 = 82,  /* RFC 7083, RFC 8415 */
+        SD_DHCP6_OPTION_INF_MAX_RT                 = 83,  /* RFC 7083, RFC 8415 */
+        SD_DHCP6_OPTION_ADDRSEL                    = 84,  /* RFC 7078 */
+        SD_DHCP6_OPTION_ADDRSEL_TABLE              = 85,  /* RFC 7078 */
+        SD_DHCP6_OPTION_V6_PCP_SERVER              = 86,  /* RFC 7291 */
+        SD_DHCP6_OPTION_DHCPV4_MSG                 = 87,  /* RFC 7341 */
+        SD_DHCP6_OPTION_DHCP4_O_DHCP6_SERVER       = 88,  /* RFC 7341 */
+        SD_DHCP6_OPTION_S46_RULE                   = 89,  /* RFC 7598 */
+        SD_DHCP6_OPTION_S46_BR                     = 90,  /* RFC 7598, RFC 8539 */
+        SD_DHCP6_OPTION_S46_DMR                    = 91,  /* RFC 7598 */
+        SD_DHCP6_OPTION_S46_V4V6BIND               = 92,  /* RFC 7598 */
+        SD_DHCP6_OPTION_S46_PORTPARAMS             = 93,  /* RFC 7598 */
+        SD_DHCP6_OPTION_S46_CONT_MAPE              = 94,  /* RFC 7598 */
+        SD_DHCP6_OPTION_S46_CONT_MAPT              = 95,  /* RFC 7598 */
+        SD_DHCP6_OPTION_S46_CONT_LW                = 96,  /* RFC 7598 */
+        SD_DHCP6_OPTION_4RD                        = 97,  /* RFC 7600 */
+        SD_DHCP6_OPTION_4RD_MAP_RULE               = 98,  /* RFC 7600 */
+        SD_DHCP6_OPTION_4RD_NON_MAP_RULE           = 99,  /* RFC 7600 */
+        SD_DHCP6_OPTION_LQ_BASE_TIME               = 100, /* RFC 7653 */
+        SD_DHCP6_OPTION_LQ_START_TIME              = 101, /* RFC 7653 */
+        SD_DHCP6_OPTION_LQ_END_TIME                = 102, /* RFC 7653 */
+        SD_DHCP6_OPTION_CAPTIVE_PORTAL             = 103, /* RFC 8910 */
+        SD_DHCP6_OPTION_MPL_PARAMETERS             = 104, /* RFC 7774 */
+        SD_DHCP6_OPTION_ANI_ATT                    = 105, /* RFC 7839 */
+        SD_DHCP6_OPTION_ANI_NETWORK_NAME           = 106, /* RFC 7839 */
+        SD_DHCP6_OPTION_ANI_AP_NAME                = 107, /* RFC 7839 */
+        SD_DHCP6_OPTION_ANI_AP_BSSID               = 108, /* RFC 7839 */
+        SD_DHCP6_OPTION_ANI_OPERATOR_ID            = 109, /* RFC 7839 */
+        SD_DHCP6_OPTION_ANI_OPERATOR_REALM         = 110, /* RFC 7839 */
+        SD_DHCP6_OPTION_S46_PRIORITY               = 111, /* RFC 8026 */
+        SD_DHCP6_OPTION_MUD_URL_V6                 = 112, /* RFC 8520 */
+        SD_DHCP6_OPTION_V6_PREFIX64                = 113, /* RFC 8115 */
+        SD_DHCP6_OPTION_F_BINDING_STATUS           = 114, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_CONNECT_FLAGS            = 115, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_DNS_REMOVAL_INFO         = 116, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_DNS_HOST_NAME            = 117, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_DNS_ZONE_NAME            = 118, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_DNS_FLAGS                = 119, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_EXPIRATION_TIME          = 120, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_MAX_UNACKED_BNDUPD       = 121, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_MCLT                     = 122, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_PARTNER_LIFETIME         = 123, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_PARTNER_LIFETIME_SENT    = 124, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_PARTNER_DOWN_TIME        = 125, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_PARTNER_RAW_CLT_TIME     = 126, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_PROTOCOL_VERSION         = 127, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_KEEPALIVE_TIME           = 128, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_RECONFIGURE_DATA         = 129, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_RELATIONSHIP_NAME        = 130, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_SERVER_FLAGS             = 131, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_SERVER_STATE             = 132, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_START_TIME_OF_STATE      = 133, /* RFC 8156 */
+        SD_DHCP6_OPTION_F_STATE_EXPIRATION_TIME    = 134, /* RFC 8156 */
+        SD_DHCP6_OPTION_RELAY_PORT                 = 135, /* RFC 8357 */
+        SD_DHCP6_OPTION_V6_SZTP_REDIRECT           = 136, /* RFC 8572 */
+        SD_DHCP6_OPTION_S46_BIND_IPV6_PREFIX       = 137, /* RFC 8539 */
+        SD_DHCP6_OPTION_IA_LL                      = 138, /* RFC 8947 */
+        SD_DHCP6_OPTION_LLADDR                     = 139, /* RFC 8947 */
+        SD_DHCP6_OPTION_SLAP_QUAD                  = 140, /* RFC 8948 */
+        SD_DHCP6_OPTION_V6_DOTS_RI                 = 141, /* RFC 8973 */
+        SD_DHCP6_OPTION_V6_DOTS_ADDRESS            = 142, /* RFC 8973 */
+        SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF         = 143  /* RFC 6153 */
+        /* option codes 144-65535 are unassigned */
+};
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/libnm-systemd-core/src/systemd/sd-id128.h b/src/libnm-systemd-core/src/systemd/sd-id128.h
index 3303c374..a984a9d8 100644
--- a/src/libnm-systemd-core/src/systemd/sd-id128.h
+++ b/src/libnm-systemd-core/src/systemd/sd-id128.h
@@ -50,6 +50,7 @@ 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_app_specific(sd_id128_t base, sd_id128_t app_id, 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);
 
diff --git a/src/libnm-systemd-core/src/systemd/sd-ndisc.h b/src/libnm-systemd-core/src/systemd/sd-ndisc.h
index ee309a42..5f4f6caf 100644
--- a/src/libnm-systemd-core/src/systemd/sd-ndisc.h
+++ b/src/libnm-systemd-core/src/systemd/sd-ndisc.h
@@ -26,34 +26,14 @@
 #include <sys/types.h>
 
 #include "sd-event.h"
+#include "sd-ndisc-protocol.h"
+#include "sd-ndisc-router.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;
 
 __extension__ typedef enum sd_ndisc_event_t {
         SD_NDISC_EVENT_TIMEOUT,
@@ -63,14 +43,16 @@ __extension__ typedef enum sd_ndisc_event_t {
         _SD_ENUM_FORCE_S64(NDISC_EVENT)
 } sd_ndisc_event_t;
 
-typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router *rt, void *userdata);
+typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event_t event, void *message, 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);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref);
 
 int sd_ndisc_start(sd_ndisc *nd);
 int sd_ndisc_stop(sd_ndisc *nd);
+int sd_ndisc_is_running(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);
@@ -80,52 +62,9 @@ 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_ifname(sd_ndisc *nd, const char *interface_name);
 int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret);
+int sd_ndisc_set_link_local_address(sd_ndisc *nd, const struct in6_addr *addr);
 int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr);
 
-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