about summary refs log tree commit diff
path: root/src/libnm-systemd-core
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-systemd-core')
-rw-r--r--src/libnm-systemd-core/meson.build1
-rw-r--r--src/libnm-systemd-core/nm-sd-utils-dhcp.c3
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c131
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h26
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-lease-internal.h8
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-option.c1
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-protocol.h27
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h179
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h28
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c419
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.h104
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.c86
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h16
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c6
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/network-internal.h2
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c78
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-lease.c226
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c1496
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c524
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-lldp-rx.c4
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c41
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h2
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c21
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c51
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h8
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c32
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp-client.h224
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp-lease.h4
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h8
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-id128.h3
30 files changed, 1925 insertions, 1834 deletions
diff --git a/src/libnm-systemd-core/meson.build b/src/libnm-systemd-core/meson.build
index 1fa94486..80b95c42 100644
--- a/src/libnm-systemd-core/meson.build
+++ b/src/libnm-systemd-core/meson.build
@@ -10,6 +10,7 @@ libnm_systemd_core = static_library(
     'src/libsystemd-network/dhcp-packet.c',
     'src/libsystemd-network/dhcp6-network.c',
     'src/libsystemd-network/dhcp6-option.c',
+    'src/libsystemd-network/dhcp6-protocol.c',
     'src/libsystemd-network/lldp-neighbor.c',
     'src/libsystemd-network/lldp-network.c',
     'src/libsystemd-network/network-common.c',
diff --git a/src/libnm-systemd-core/nm-sd-utils-dhcp.c b/src/libnm-systemd-core/nm-sd-utils-dhcp.c
index 7fe889fd..13c75055 100644
--- a/src/libnm-systemd-core/nm-sd-utils-dhcp.c
+++ b/src/libnm-systemd-core/nm-sd-utils-dhcp.c
@@ -13,8 +13,7 @@
 int
 nm_sd_dhcp_lease_get_private_options(sd_dhcp_lease *lease, nm_sd_dhcp_option **out_options)
 {
-    struct sd_dhcp_raw_option *raw_option;
-    int                        cnt = 0;
+    int cnt = 0;
 
     g_return_val_if_fail(lease, -EINVAL);
     g_return_val_if_fail(out_options, -EINVAL);
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c
index 74aa19de..f2d94214 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c
@@ -10,20 +10,27 @@
 #include "sd-id128.h"
 
 #include "dhcp-identifier.h"
-#include "dhcp6-protocol.h"
 #include "netif-util.h"
 #include "siphash24.h"
 #include "sparse-endian.h"
 #include "stat-util.h"
-#include "stdio-util.h"
+#include "string-table.h"
 #include "udev-util.h"
-#include "virt.h"
 
 #define HASH_KEY       SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
 #define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03)
 #define USEC_2000       ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */
 
-int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict) {
+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);
@@ -60,107 +67,141 @@ int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict) {
 }
 
 #if 0 /* NM_IGNORED */
-int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) {
+static int dhcp_identifier_set_duid_llt(const uint8_t *addr, size_t addr_len, uint16_t arp_type, usec_t t, struct duid *ret_duid, size_t *ret_len) {
         uint16_t time_from_2000y;
 
-        assert(duid);
-        assert(len);
         assert(addr);
+        assert(ret_duid);
+        assert(ret_len);
+
+        if (addr_len == 0)
+                return -EOPNOTSUPP;
 
         if (arp_type == ARPHRD_ETHER)
                 assert_return(addr_len == ETH_ALEN, -EINVAL);
         else if (arp_type == ARPHRD_INFINIBAND)
                 assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
         else
-                return -EINVAL;
+                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(&duid->type, DUID_TYPE_LLT);
-        unaligned_write_be16(&duid->llt.htype, arp_type);
-        unaligned_write_be32(&duid->llt.time, time_from_2000y);
-        memcpy(duid->llt.haddr, addr, addr_len);
+        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, addr, addr_len);
 
-        *len = sizeof(duid->type) + sizeof(duid->llt.htype) + sizeof(duid->llt.time) + addr_len;
+        *ret_len = offsetof(struct duid, llt.haddr) + addr_len;
 
         return 0;
 }
 
-int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) {
-        assert(duid);
-        assert(len);
+static int dhcp_identifier_set_duid_ll(const uint8_t *addr, size_t addr_len, uint16_t arp_type, struct duid *ret_duid, size_t *ret_len) {
         assert(addr);
+        assert(ret_duid);
+        assert(ret_len);
+
+        if (addr_len == 0)
+                return -EOPNOTSUPP;
 
         if (arp_type == ARPHRD_ETHER)
                 assert_return(addr_len == ETH_ALEN, -EINVAL);
         else if (arp_type == ARPHRD_INFINIBAND)
                 assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
         else
-                return -EINVAL;
+                return -EOPNOTSUPP;
 
-        unaligned_write_be16(&duid->type, DUID_TYPE_LL);
-        unaligned_write_be16(&duid->ll.htype, arp_type);
-        memcpy(duid->ll.haddr, addr, addr_len);
+        unaligned_write_be16(&ret_duid->type, DUID_TYPE_LL);
+        unaligned_write_be16(&ret_duid->ll.htype, arp_type);
+        memcpy(ret_duid->ll.haddr, addr, addr_len);
 
-        *len = sizeof(duid->type) + sizeof(duid->ll.htype) + addr_len;
+        *ret_len = offsetof(struct duid, ll.haddr) + addr_len;
 
         return 0;
 }
 #endif /* NM_IGNORED */
 
-int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) {
+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(duid);
-        assert(len);
+        assert(ret_duid);
+        assert(ret_len);
 
-        r = sd_id128_get_machine(&machine_id);
-        if (r < 0) {
-#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+        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);
-#else
-                return r;
-#endif
-        }
 
-        unaligned_write_be16(&duid->type, DUID_TYPE_EN);
-        unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN);
-
-        *len = sizeof(duid->type) + sizeof(duid->en);
+        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(duid->en.id, &hash, sizeof(duid->en.id));
+        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 */
-int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len) {
+static int dhcp_identifier_set_duid_uuid(struct duid *ret_duid, size_t *ret_len) {
         sd_id128_t machine_id;
         int r;
 
-        assert(duid);
-        assert(len);
+        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(&duid->type, DUID_TYPE_UUID);
-        memcpy(&duid->raw.data, &machine_id, sizeof(machine_id));
+        unaligned_write_be16(&ret_duid->type, DUID_TYPE_UUID);
+        memcpy(&ret_duid->uuid.uuid, &machine_id, sizeof(machine_id));
 
-        *len = sizeof(duid->type) + sizeof(machine_id);
+        *ret_len = offsetof(struct duid, uuid.uuid) + sizeof(machine_id);
 
         return 0;
 }
-#endif
+
+int dhcp_identifier_set_duid(
+                DUIDType duid_type,
+                const uint8_t *addr,
+                size_t addr_len,
+                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(addr, addr_len, 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(addr, addr_len, 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(
                 int ifindex,
@@ -168,7 +209,7 @@ int dhcp_identifier_set_iaid(
                 size_t mac_len,
                 bool legacy_unstable_byteorder,
                 bool use_mac,
-                void *_id) {
+                void *ret) {
 #if 0 /* NM_IGNORED */
 
         /* name is a pointer to memory in the sd_device struct, so must
@@ -221,7 +262,7 @@ int dhcp_identifier_set_iaid(
                  * behavior. */
                 id32 = be32toh(id32);
 
-        unaligned_write_ne32(_id, 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.
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h
index 6c24af03..697ba3bf 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h
@@ -54,9 +54,23 @@ struct duid {
         };
 } _packed_;
 
-int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict);
-int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
-int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
-int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len);
-int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len);
-int dhcp_identifier_set_iaid(int ifindex, const uint8_t *mac, size_t mac_len, bool legacy_unstable_byteorder, bool use_mac, void *_id);
+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 uint8_t *addr,
+                size_t addr_len,
+                uint16_t arp_type,
+                usec_t llt_time,
+                bool test_mode,
+                struct duid *ret_duid,
+                size_t *ret_len);
+int dhcp_identifier_set_iaid(
+                int ifindex,
+                const uint8_t *mac,
+                size_t mac_len,
+                bool legacy_unstable_byteorder,
+                bool use_mac,
+                void *ret);
+
+const char *duid_type_to_string(DUIDType t) _const_;
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-lease-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-lease-internal.h
index 992ac9f3..c67e9511 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-lease-internal.h
@@ -16,8 +16,6 @@ struct sd_dhcp_route {
         struct in_addr dst_addr;
         struct in_addr gw_addr;
         unsigned char dst_prefixlen;
-
-        uint8_t option;
 };
 
 struct sd_dhcp_raw_option {
@@ -52,8 +50,10 @@ struct sd_dhcp_lease {
 
         DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
 
-        struct sd_dhcp_route *static_route;
-        size_t static_route_size;
+        struct sd_dhcp_route *static_routes;
+        size_t n_static_routes;
+        struct sd_dhcp_route *classless_routes;
+        size_t n_classless_routes;
 
         uint16_t mtu; /* 0 if unset */
 
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-option.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp-option.c
index 1710f696..712f6097 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-option.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-option.c
@@ -60,7 +60,6 @@ static int option_append(uint8_t options[], size_t size, size_t *offset,
 
         case SD_DHCP_OPTION_USER_CLASS: {
                 size_t total = 0;
-                char **s;
 
                 if (strv_isempty((char **) optval))
                         return -EINVAL;
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-protocol.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-protocol.h
index 11f4201a..dd54bcf6 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-protocol.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-protocol.h
@@ -73,15 +73,24 @@ enum {
 };
 
 enum {
-        DHCP_DISCOVER                           = 1,
-        DHCP_OFFER                              = 2,
-        DHCP_REQUEST                            = 3,
-        DHCP_DECLINE                            = 4,
-        DHCP_ACK                                = 5,
-        DHCP_NAK                                = 6,
-        DHCP_RELEASE                            = 7,
-        DHCP_INFORM                             = 8,
-        DHCP_FORCERENEW                         = 9,
+        DHCP_DISCOVER                           = 1,  /* [RFC2132] */
+        DHCP_OFFER                              = 2,  /* [RFC2132] */
+        DHCP_REQUEST                            = 3,  /* [RFC2132] */
+        DHCP_DECLINE                            = 4,  /* [RFC2132] */
+        DHCP_ACK                                = 5,  /* [RFC2132] */
+        DHCP_NAK                                = 6,  /* [RFC2132] */
+        DHCP_RELEASE                            = 7,  /* [RFC2132] */
+        DHCP_INFORM                             = 8,  /* [RFC2132] */
+        DHCP_FORCERENEW                         = 9,  /* [RFC3203] */
+        DHCPLEASEQUERY                          = 10, /* [RFC4388] */
+        DHCPLEASEUNASSIGNED                     = 11, /* [RFC4388] */
+        DHCPLEASEUNKNOWN                        = 12, /* [RFC4388] */
+        DHCPLEASEACTIVE                         = 13, /* [RFC4388] */
+        DHCPBULKLEASEQUERY                      = 14, /* [RFC6926] */
+        DHCPLEASEQUERYDONE                      = 15, /* [RFC6926] */
+        DHCPACTIVELEASEQUERY                    = 16, /* [RFC7724] */
+        DHCPLEASEQUERYSTATUS                    = 17, /* [RFC7724] */
+        DHCPTLS                                 = 18, /* [RFC7724] */
 };
 
 enum {
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 f9434098..0d7813f6 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h
@@ -11,138 +11,83 @@
 #include "sd-event.h"
 #include "sd-dhcp6-client.h"
 
+#include "dhcp-identifier.h"
+#include "dhcp6-option.h"
 #include "dhcp6-protocol.h"
+#include "ether-addr-util.h"
 #include "hashmap.h"
-#include "list.h"
 #include "macro.h"
 #include "network-common.h"
+#include "ordered-set.h"
 #include "sparse-endian.h"
+#include "time-util.h"
 
-typedef struct sd_dhcp6_option {
+/* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */
+typedef enum DHCP6RequestIA {
+        DHCP6_REQUEST_IA_NA = 1 << 0,
+        DHCP6_REQUEST_IA_TA = 1 << 1, /* currently not used */
+        DHCP6_REQUEST_IA_PD = 1 << 2,
+} DHCP6RequestIA;
+
+struct sd_dhcp6_client {
         unsigned n_ref;
 
-        uint32_t enterprise_identifier;
-        uint16_t option;
-        void *data;
-        size_t length;
-} sd_dhcp6_option;
-
-extern const struct hash_ops dhcp6_option_hash_ops;
-
-/* Common option header */
-typedef struct DHCP6Option {
-        be16_t code;
-        be16_t len;
-        uint8_t data[];
-} _packed_ DHCP6Option;
-
-/* Address option */
-struct iaaddr {
-        struct in6_addr address;
-        be32_t lifetime_preferred;
-        be32_t lifetime_valid;
-} _packed_;
-
-/* Prefix Delegation Prefix option */
-struct iapdprefix {
-        be32_t lifetime_preferred;
-        be32_t lifetime_valid;
-        uint8_t prefixlen;
-        struct in6_addr address;
-} _packed_;
-
-typedef struct DHCP6Address DHCP6Address;
-
-struct DHCP6Address {
-        LIST_FIELDS(DHCP6Address, addresses);
-
-        union {
-                struct iaaddr iaaddr;
-                struct iapdprefix iapdprefix;
-        };
+        int ifindex;
+        char *ifname;
+
+        struct in6_addr local_address;
+        struct hw_addr_data hw_addr;
+        uint16_t arp_type;
+
+        sd_event *event;
+        sd_event_source *receive_message;
+        sd_event_source *timeout_resend;
+        sd_event_source *timeout_expire;
+        sd_event_source *timeout_t1;
+        sd_event_source *timeout_t2;
+        int event_priority;
+        int fd;
+
+        DHCP6State state;
+        bool information_request;
+        usec_t information_request_time_usec;
+        usec_t information_refresh_time_usec;
+        be32_t transaction_id;
+        usec_t transaction_start;
+        usec_t retransmit_time;
+        uint8_t retransmit_count;
+
+        bool iaid_set;
+        DHCP6IA ia_na;
+        DHCP6IA ia_pd;
+        DHCP6RequestIA request_ia;
+        struct duid duid;
+        size_t duid_len;
+        be16_t *req_opts;
+        size_t req_opts_len;
+        char *fqdn;
+        char *mudurl;
+        char **user_class;
+        char **vendor_class;
+        OrderedHashmap *extra_options;
+        OrderedSet *vendor_options;
+
+        struct sd_dhcp6_lease *lease;
+
+        sd_dhcp6_client_callback_t callback;
+        void *userdata;
+
+        /* Ignore ifindex when generating iaid. See dhcp_identifier_set_iaid(). */
+        bool test_mode;
 };
 
-/* Non-temporary Address option */
-struct ia_na {
-        be32_t id;
-        be32_t lifetime_t1;
-        be32_t lifetime_t2;
-} _packed_;
-
-/* Prefix Delegation option */
-struct ia_pd {
-        be32_t id;
-        be32_t lifetime_t1;
-        be32_t lifetime_t2;
-} _packed_;
-
-/* Temporary Address option */
-struct ia_ta {
-        be32_t id;
-} _packed_;
-
-typedef struct DHCP6IA {
-        uint16_t type;
-        union {
-                struct ia_na ia_na;
-                struct ia_pd ia_pd;
-                struct ia_ta ia_ta;
-        };
-
-        LIST_HEAD(DHCP6Address, addresses);
-} DHCP6IA;
-
-typedef struct sd_dhcp6_client sd_dhcp6_client;
-
-bool dhcp6_option_can_request(uint16_t option);
-int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
-                        size_t optlen, const void *optval);
-int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia);
-int dhcp6_option_append_pd(uint8_t **buf, size_t *buflen, const DHCP6IA *pd, const DHCP6Address *hint_pd_prefix);
-int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn);
-int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class);
-int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *user_class);
-int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options);
-
-int dhcp6_option_parse(
-                const uint8_t *buf,
-                size_t buflen,
-                size_t *offset,
-                uint16_t *ret_option_code,
-                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_ia(
-                sd_dhcp6_client *client,
-                be32_t iaid,
-                uint16_t option_code,
-                size_t option_data_len,
-                const uint8_t *option_data,
-                DHCP6IA *ret);
-int dhcp6_option_parse_addresses(
-                const uint8_t *optval,
-                size_t optlen,
-                struct in6_addr **addrs,
-                size_t *count);
-int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, char ***ret);
-int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret);
-
 int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address);
 int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
                                   const void *packet, size_t len);
 
-int client_parse_message(
-                sd_dhcp6_client *client,
-                DHCP6Message *message,
-                size_t len,
-                sd_dhcp6_lease *lease);
-
-const char *dhcp6_message_type_to_string(int s) _const_;
-int dhcp6_message_type_from_string(const char *s) _pure_;
-const char *dhcp6_message_status_to_string(int s) _const_;
-int dhcp6_message_status_from_string(const char *s) _pure_;
-
+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, ...)         \
         log_interface_prefix_full_errno(                        \
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 8ae2ecd6..1f10dccb 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
@@ -5,11 +5,13 @@
   Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
 
-#include <stdint.h>
+#include <inttypes.h>
 
 #include "sd-dhcp6-lease.h"
 
-#include "dhcp6-internal.h"
+#include "dhcp6-option.h"
+#include "macro.h"
+#include "time-util.h"
 
 struct sd_dhcp6_lease {
         unsigned n_ref;
@@ -21,10 +23,13 @@ struct sd_dhcp6_lease {
         uint8_t preference;
         bool rapid_commit;
         triple_timestamp timestamp;
+        usec_t lifetime_t1;
+        usec_t lifetime_t2;
+        usec_t lifetime_valid;
         struct in6_addr server_address;
 
-        DHCP6IA ia;
-        DHCP6IA pd;
+        DHCP6IA *ia_na; /* Identity association non-temporary addresses */
+        DHCP6IA *ia_pd; /* Identity association prefix delegation */
 
         DHCP6Address *addr_iter;
         DHCP6Address *prefix_iter;
@@ -40,17 +45,15 @@ struct sd_dhcp6_lease {
         char *fqdn;
 };
 
-int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire);
-DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia);
-
+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);
 int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len);
 int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference);
-int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference);
+int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *ret);
 int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease);
-int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit);
+int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *ret);
 
 int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
 int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
@@ -59,3 +62,10 @@ int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t op
 int dhcp6_lease_set_fqdn(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(
+                sd_dhcp6_client *client,
+                const DHCP6Message *message,
+                size_t len,
+                const triple_timestamp *timestamp,
+                const struct in6_addr *server_address,
+                sd_dhcp6_lease **ret);
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 237771e6..03d56371 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c
@@ -11,14 +11,12 @@
 #include "sd-dhcp6-client.h"
 
 #include "alloc-util.h"
-#include "dhcp-identifier.h"
 #include "dhcp6-internal.h"
-#include "dhcp6-lease-internal.h"
+#include "dhcp6-option.h"
 #include "dhcp6-protocol.h"
 #include "dns-domain.h"
 #include "escape.h"
 #include "memory-util.h"
-#include "sparse-endian.h"
 #include "strv.h"
 #include "unaligned.h"
 
@@ -76,8 +74,8 @@ bool dhcp6_option_can_request(uint16_t option) {
                 return false;
         case SD_DHCP6_OPTION_CLIENT_FQDN:
         case SD_DHCP6_OPTION_PANA_AGENT:
-        case SD_DHCP6_OPTION_NEW_POSIX_TIMEZONE:
-        case SD_DHCP6_OPTION_NEW_TZDB_TIMEZONE:
+        case SD_DHCP6_OPTION_POSIX_TIMEZONE:
+        case SD_DHCP6_OPTION_TZDB_TIMEZONE:
                 return true;
         case SD_DHCP6_OPTION_ERO:
         case SD_DHCP6_OPTION_LQ_QUERY:
@@ -214,19 +212,15 @@ bool dhcp6_option_can_request(uint16_t option) {
 }
 
 static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, size_t optlen) {
-        DHCP6Option *option;
-
         assert_return(buf, -EINVAL);
         assert_return(*buf, -EINVAL);
         assert_return(buflen, -EINVAL);
 
-        option = (DHCP6Option*) *buf;
-
         if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data))
                 return -ENOBUFS;
 
-        option->code = htobe16(optcode);
-        option->len = htobe16(optlen);
+        unaligned_write_be16(*buf + offsetof(DHCP6Option, code), optcode);
+        unaligned_write_be16(*buf + offsetof(DHCP6Option, len), optlen);
 
         *buf += offsetof(DHCP6Option, data);
         *buflen -= offsetof(DHCP6Option, data);
@@ -244,15 +238,13 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
         if (r < 0)
                 return r;
 
-        memcpy_safe(*buf, optval, optlen);
-
-        *buf += optlen;
+        *buf = mempcpy_safe(*buf, optval, optlen);
         *buflen -= optlen;
 
         return 0;
 }
 
-int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) {
+int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedSet *vendor_options) {
         sd_dhcp6_option *options;
         int r;
 
@@ -261,7 +253,7 @@ int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHash
         assert(buflen);
         assert(vendor_options);
 
-        ORDERED_HASHMAP_FOREACH(options, vendor_options) {
+        ORDERED_SET_FOREACH(options, vendor_options) {
                 _cleanup_free_ uint8_t *p = NULL;
                 size_t total;
 
@@ -284,79 +276,31 @@ int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHash
         return 0;
 }
 
-int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
-        size_t ia_buflen, ia_addrlen = 0;
-        struct ia_na ia_na;
-        struct ia_ta ia_ta;
-        DHCP6Address *addr;
-        uint8_t *ia_hdr;
-        uint16_t len;
-        void *p;
+static int option_append_ia_address(uint8_t **buf, size_t *buflen, const struct iaaddr *address) {
+        struct iaaddr a;
         int r;
 
-        assert_return(buf, -EINVAL);
-        assert_return(*buf, -EINVAL);
-        assert_return(buflen, -EINVAL);
-        assert_return(ia, -EINVAL);
-
-        /* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */
-
-        switch (ia->type) {
-        case SD_DHCP6_OPTION_IA_NA:
-                len = DHCP6_OPTION_IA_NA_LEN;
-                ia_na = (struct ia_na) {
-                        .id = ia->ia_na.id,
-                };
-                p = &ia_na;
-                break;
-
-        case SD_DHCP6_OPTION_IA_TA:
-                len = DHCP6_OPTION_IA_TA_LEN;
-                ia_ta = (struct ia_ta) {
-                        .id = ia->ia_ta.id,
-                };
-                p = &ia_ta;
-                break;
-
-        default:
-                return -EINVAL;
-        }
-
-        if (*buflen < offsetof(DHCP6Option, data) + len)
-                return -ENOBUFS;
-
-        ia_hdr = *buf;
-        ia_buflen = *buflen;
-
-        *buf += offsetof(DHCP6Option, data);
-        *buflen -= offsetof(DHCP6Option, data);
-
-        memcpy(*buf, p, len);
-
-        *buf += len;
-        *buflen -= len;
-
-        LIST_FOREACH(addresses, addr, ia->addresses) {
-                struct iaaddr a = {
-                        .address = addr->iaaddr.address,
-                };
-
-                r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(struct iaaddr));
-                if (r < 0)
-                        return r;
+        assert(buf);
+        assert(*buf);
+        assert(buflen);
+        assert(address);
 
-                memcpy(*buf, &a, sizeof(struct iaaddr));
+        /* Do not append T1 and T2. */
+        a = (struct iaaddr) {
+                .address = address->address,
+        };
 
-                *buf += sizeof(struct iaaddr);
-                *buflen -= sizeof(struct iaaddr);
+        r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(struct iaaddr));
+        if (r < 0)
+                return r;
 
-                ia_addrlen += offsetof(DHCP6Option, data) + sizeof(struct iaaddr);
-        }
+        *buf = mempcpy(*buf, &a, sizeof(struct iaaddr));
+        *buflen -= sizeof(struct iaaddr);
 
-        return option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
+        return offsetof(DHCP6Option, data) + sizeof(struct iaaddr);
 }
 
-static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const DHCP6Address *prefix) {
+static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const struct iapdprefix *prefix) {
         struct iapdprefix p;
         int r;
 
@@ -365,79 +309,84 @@ static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const DHCP6Add
         assert(buflen);
         assert(prefix);
 
-        if (prefix->iapdprefix.prefixlen == 0)
+        if (prefix->prefixlen == 0)
                 return -EINVAL;
 
         /* Do not append T1 and T2. */
-
         p = (struct iapdprefix) {
-                .prefixlen = prefix->iapdprefix.prefixlen,
-                .address = prefix->iapdprefix.address,
+                .prefixlen = prefix->prefixlen,
+                .address = prefix->address,
         };
 
         r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IA_PD_PREFIX, sizeof(struct iapdprefix));
         if (r < 0)
                 return r;
 
-        memcpy(*buf, &p, sizeof(struct iapdprefix));
-
-        *buf += sizeof(struct iapdprefix);
+        *buf = mempcpy(*buf, &p, sizeof(struct iapdprefix));
         *buflen -= sizeof(struct iapdprefix);
 
         return offsetof(DHCP6Option, data) + sizeof(struct iapdprefix);
 }
 
-int dhcp6_option_append_pd(uint8_t **buf, size_t *buflen, const DHCP6IA *pd, const DHCP6Address *hint_pd_prefix) {
-        struct ia_pd ia_pd;
-        size_t len, pd_buflen;
-        uint8_t *pd_hdr;
+int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
+        struct ia_header header;
+        size_t ia_buflen;
+        uint8_t *ia_hdr;
+        uint16_t len;
         int r;
 
         assert_return(buf, -EINVAL);
         assert_return(*buf, -EINVAL);
         assert_return(buflen, -EINVAL);
-        assert_return(pd, -EINVAL);
-        assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL);
+        assert_return(ia, -EINVAL);
 
-        /* Do not set T1 and T2. */
-        ia_pd = (struct ia_pd) {
-                .id = pd->ia_pd.id,
-        };
-        len = sizeof(struct ia_pd);
+        /* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */
+
+        switch (ia->type) {
+        case SD_DHCP6_OPTION_IA_NA:
+        case SD_DHCP6_OPTION_IA_PD:
+                len = sizeof(struct ia_header);
+                header = (struct ia_header) {
+                        .id = ia->header.id,
+                };
+                break;
+
+        case SD_DHCP6_OPTION_IA_TA:
+                len = sizeof(header.id); /* IA_TA does not have lifetime. */
+                header = (struct ia_header) {
+                        .id = ia->header.id,
+                };
+                break;
+
+        default:
+                assert_not_reached();
+        }
 
         if (*buflen < offsetof(DHCP6Option, data) + len)
                 return -ENOBUFS;
 
-        pd_hdr = *buf;
-        pd_buflen = *buflen;
+        ia_hdr = *buf;
+        ia_buflen = *buflen;
 
         /* The header will be written at the end of this function. */
         *buf += offsetof(DHCP6Option, data);
         *buflen -= offsetof(DHCP6Option, data);
 
-        memcpy(*buf, &ia_pd, len);
-
-        *buf += sizeof(struct ia_pd);
-        *buflen -= sizeof(struct ia_pd);
-
-        DHCP6Address *prefix;
-        LIST_FOREACH(addresses, prefix, pd->addresses) {
-                r = option_append_pd_prefix(buf, buflen, prefix);
-                if (r < 0)
-                        return r;
-
-                len += r;
-        }
+        *buf = mempcpy(*buf, &header, len);
+        *buflen -= len;
 
-        if (hint_pd_prefix && hint_pd_prefix->iapdprefix.prefixlen > 0) {
-                r = option_append_pd_prefix(buf, buflen, hint_pd_prefix);
+        LIST_FOREACH(addresses, addr, ia->addresses) {
+                if (ia->type == SD_DHCP6_OPTION_IA_PD)
+                        r = option_append_pd_prefix(buf, buflen, &addr->iapdprefix);
+                else
+                        r = option_append_ia_address(buf, buflen, &addr->iaaddr);
                 if (r < 0)
                         return r;
 
                 len += r;
         }
 
-        return option_append_hdr(&pd_hdr, &pd_buflen, pd->type, len);
+        return option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len);
 }
 
 int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
@@ -470,7 +419,6 @@ int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
 int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class) {
         _cleanup_free_ uint8_t *p = NULL;
         size_t total = 0, offset = 0;
-        char * const *s;
 
         assert(buf);
         assert(*buf);
@@ -503,7 +451,6 @@ int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const
         _cleanup_free_ uint8_t *p = NULL;
         uint32_t enterprise_identifier;
         size_t total, offset;
-        char * const *s;
 
         assert(buf);
         assert(*buf);
@@ -550,7 +497,6 @@ int dhcp6_option_parse(
                 size_t *ret_option_data_len,
                 const uint8_t **ret_option_data) {
 
-        const DHCP6Option *option;
         size_t len;
 
         assert(buf);
@@ -565,16 +511,15 @@ int dhcp6_option_parse(
         if (*offset >= buflen - offsetof(DHCP6Option, data))
                 return -EBADMSG;
 
-        option = (const DHCP6Option*) (buf + *offset);
-        len = be16toh(option->len);
+        len = unaligned_read_be16(buf + *offset + offsetof(DHCP6Option, len));
 
         if (len > buflen - offsetof(DHCP6Option, data) - *offset)
                 return -EBADMSG;
 
-        *offset += offsetof(DHCP6Option, data) + len;
-        *ret_option_code = be16toh(option->code);
+        *ret_option_code = unaligned_read_be16(buf + *offset + offsetof(DHCP6Option, code));
         *ret_option_data_len = len;
-        *ret_option_data = option->data;
+        *ret_option_data = buf + *offset + offsetof(DHCP6Option, data);
+        *offset += offsetof(DHCP6Option, data) + len;
 
         return 0;
 }
@@ -603,9 +548,9 @@ int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_s
 static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) {
         int r;
 
-        assert(buf);
+        assert(buf || buflen == 0);
 
-        for(size_t offset = 0; offset < buflen;) {
+        for (size_t offset = 0; offset < buflen;) {
                 const uint8_t *data;
                 size_t data_len;
                 uint16_t code;
@@ -614,22 +559,22 @@ static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t
                 if (r < 0)
                         return r;
 
-                switch(code) {
+                switch (code) {
                 case SD_DHCP6_OPTION_STATUS_CODE: {
                         _cleanup_free_ char *msg = NULL;
 
                         r = dhcp6_option_parse_status(data, data_len, &msg);
                         if (r == -ENOMEM)
                                 return r;
-                        if (r < 0)
-                                /* Let's log but ignore the invalid status option. */
-                                log_dhcp6_client_errno(client, r,
-                                                       "Received an IA address or PD prefix option with an invalid status sub option, ignoring: %m");
-                        else if (r > 0)
+                        if (r > 0)
                                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
                                                               "Received an IA address or PD prefix option with non-zero status: %s%s%s",
                                                               strempty(msg), isempty(msg) ? "" : ": ",
                                                               dhcp6_message_status_to_string(r));
+                        if (r < 0)
+                                /* Let's log but ignore the invalid status option. */
+                                log_dhcp6_client_errno(client, r,
+                                                       "Received an IA address or PD prefix option with an invalid status sub option, ignoring: %m");
                         break;
                 }
                 default:
@@ -640,19 +585,29 @@ static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t
         return 0;
 }
 
-static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, const uint8_t *data, size_t len, DHCP6Address **ret) {
+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;
-        DHCP6Address *a;
         int r;
 
-        assert(data);
-        assert(ret);
+        assert(ia);
+        assert(data || len == 0);
+
+        if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA))
+                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+                                              "Received an IA address sub-option in an invalid option, ignoring.");
 
         if (len < sizeof(struct iaaddr))
                 return -EBADMSG;
 
-        lt_valid = be32toh(((const struct iaaddr*) data)->lifetime_valid);
-        lt_pref = be32toh(((const struct iaaddr*) data)->lifetime_preferred);
+        a = new(DHCP6Address, 1);
+        if (!a)
+                return -ENOMEM;
+
+        memcpy(&a->iaaddr, data, sizeof(struct iaaddr));
+
+        lt_valid = be32toh(a->iaaddr.lifetime_valid);
+        lt_pref = be32toh(a->iaaddr.lifetime_preferred);
 
         if (lt_valid == 0)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
@@ -669,27 +624,33 @@ static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, const uint8_t
                         return r;
         }
 
-        a = new(DHCP6Address, 1);
-        if (!a)
-                return -ENOMEM;
-
-        LIST_INIT(addresses, a);
-        memcpy(&a->iaaddr, data, sizeof(struct iaaddr));
-
-        *ret = a;
+        LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a));
         return 0;
 }
 
-static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, const uint8_t *data, size_t len, DHCP6Address **ret) {
+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;
-        DHCP6Address *a;
         int r;
 
+        assert(ia);
+        assert(data || len == 0);
+
+        if (ia->type != SD_DHCP6_OPTION_IA_PD)
+                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+                                              "Received an PD prefix sub-option in an invalid option, ignoring");
+
         if (len < sizeof(struct iapdprefix))
-                return -ENOMSG;
+                return -EBADMSG;
+
+        a = new(DHCP6Address, 1);
+        if (!a)
+                return -ENOMEM;
+
+        memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix));
 
-        lt_valid = be32toh(((const struct iapdprefix*) data)->lifetime_valid);
-        lt_pref = be32toh(((const struct iapdprefix*) data)->lifetime_preferred);
+        lt_valid = be32toh(a->iapdprefix.lifetime_valid);
+        lt_pref = be32toh(a->iapdprefix.lifetime_preferred);
 
         if (lt_valid == 0)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
@@ -706,14 +667,7 @@ static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, const uint8_t
                         return r;
         }
 
-        a = new(DHCP6Address, 1);
-        if (!a)
-                return -ENOMEM;
-
-        LIST_INIT(addresses, a);
-        memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix));
-
-        *ret = a;
+        LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a));
         return 0;
 }
 
@@ -723,16 +677,15 @@ int dhcp6_option_parse_ia(
                 uint16_t option_code,
                 size_t option_data_len,
                 const uint8_t *option_data,
-                DHCP6IA *ret) {
+                DHCP6IA **ret) {
 
-        _cleanup_(dhcp6_lease_free_ia) DHCP6IA ia = {};
-        uint32_t lt_t1, lt_t2, lt_min = UINT32_MAX;
-        be32_t received_iaid;
-        size_t offset;
+        _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
+        uint32_t lt_t1, lt_t2;
+        size_t header_len;
         int r;
 
         assert(IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA, SD_DHCP6_OPTION_IA_PD));
-        assert(option_data);
+        assert(option_data || option_data_len == 0);
         assert(ret);
 
         /* This will return the following:
@@ -745,56 +698,51 @@ int dhcp6_option_parse_ia(
 
         switch (option_code) {
         case SD_DHCP6_OPTION_IA_NA:
-
-                if (option_data_len < DHCP6_OPTION_IA_NA_LEN)
-                        return -EBADMSG;
-
-                offset = DHCP6_OPTION_IA_NA_LEN;
-
-                received_iaid = ((const struct ia_na*) option_data)->id;
-                lt_t1 = be32toh(((const struct ia_na*) option_data)->lifetime_t1);
-                lt_t2 = be32toh(((const struct ia_na*) option_data)->lifetime_t2);
-                break;
-
         case SD_DHCP6_OPTION_IA_PD:
-
-                if (option_data_len < DHCP6_OPTION_IA_PD_LEN)
-                        return -EBADMSG;
-
-                offset = DHCP6_OPTION_IA_PD_LEN;
-
-                received_iaid = ((const struct ia_pd*) option_data)->id;
-                lt_t1 = be32toh(((const struct ia_pd*) option_data)->lifetime_t1);
-                lt_t2 = be32toh(((const struct ia_pd*) option_data)->lifetime_t2);
+                header_len = sizeof(struct ia_header);
                 break;
 
         case SD_DHCP6_OPTION_IA_TA:
-                if (option_data_len < DHCP6_OPTION_IA_TA_LEN)
-                        return -ENOMSG;
-
-                offset = DHCP6_OPTION_IA_TA_LEN;
-
-                received_iaid = ((const struct ia_ta*) option_data)->id;
-                lt_t1 = lt_t2 = 0; /* No lifetime for IA_TA. */
+                header_len = sizeof(be32_t); /* IA_TA does not have lifetime. */
                 break;
 
         default:
                 assert_not_reached();
         }
 
+        if (option_data_len < header_len)
+                return -EBADMSG;
+
+        ia = new(DHCP6IA, 1);
+        if (!ia)
+                return -ENOMEM;
+
+        *ia = (DHCP6IA) {
+                .type = option_code,
+        };
+        memcpy(&ia->header, option_data, header_len);
+
         /* According to RFC8415, IAs which do not match the client's IAID should be ignored,
          * but not necessary to ignore or refuse the whole message. */
-        if (received_iaid != iaid)
+        if (ia->header.id != iaid)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENOANO),
                                               "Received an IA option with a different IAID "
                                               "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);
+
         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);
+        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);
 
-        for (; offset < option_data_len;) {
+        for (size_t offset = header_len; offset < option_data_len;) {
                 const uint8_t *subdata;
                 size_t subdata_len;
                 uint16_t subopt;
@@ -805,41 +753,19 @@ int dhcp6_option_parse_ia(
 
                 switch (subopt) {
                 case SD_DHCP6_OPTION_IAADDR: {
-                        DHCP6Address *a;
-
-                        if (!IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) {
-                                log_dhcp6_client(client, "Received an IA_PD option with an IA address, ignoring.");
-                                continue;
-                        }
-
-                        r = dhcp6_option_parse_ia_address(client, subdata, subdata_len, &a);
+                        r = dhcp6_option_parse_ia_address(client, ia, subdata, subdata_len);
                         if (r == -ENOMEM)
                                 return r;
-                        if (r < 0)
-                                /* Ignore the sub-option on non-critical errors. */
-                                continue;
 
-                        lt_min = MIN(lt_min, be32toh(a->iaaddr.lifetime_valid));
-                        LIST_PREPEND(addresses, ia.addresses, a);
+                        /* Ignore non-critical errors in the sub-option. */
                         break;
                 }
                 case SD_DHCP6_OPTION_IA_PD_PREFIX: {
-                        DHCP6Address *a;
-
-                        if (option_code != SD_DHCP6_OPTION_IA_PD) {
-                                log_dhcp6_client(client, "Received an IA_NA or IA_TA option with an PD prefix, ignoring");
-                                continue;
-                        }
-
-                        r = dhcp6_option_parse_ia_pdprefix(client, subdata, subdata_len, &a);
+                        r = dhcp6_option_parse_ia_pdprefix(client, ia, subdata, subdata_len);
                         if (r == -ENOMEM)
                                 return r;
-                        if (r < 0)
-                                /* Ignore the sub-option on non-critical errors. */
-                                continue;
 
-                        lt_min = MIN(lt_min, be32toh(a->iapdprefix.lifetime_valid));
-                        LIST_PREPEND(addresses, ia.addresses, a);
+                        /* Ignore non-critical errors in the sub-option. */
                         break;
                 }
                 case SD_DHCP6_OPTION_STATUS_CODE: {
@@ -848,14 +774,14 @@ int dhcp6_option_parse_ia(
                         r = dhcp6_option_parse_status(subdata, subdata_len, &msg);
                         if (r == -ENOMEM)
                                 return r;
-                        if (r < 0)
-                                log_dhcp6_client_errno(client, r,
-                                                       "Received an IA option with an invalid status sub option, ignoring: %m");
-                        else if (r > 0)
+                        if (r > 0)
                                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
                                                               "Received an IA option with non-zero status: %s%s%s",
                                                               strempty(msg), isempty(msg) ? "" : ": ",
                                                               dhcp6_message_status_to_string(r));
+                        if (r < 0)
+                                log_dhcp6_client_errno(client, r,
+                                                       "Received an IA option with an invalid status sub option, ignoring: %m");
                         break;
                 }
                 default:
@@ -863,50 +789,11 @@ int dhcp6_option_parse_ia(
                 }
         }
 
-        if (!ia.addresses)
+        if (!ia->addresses)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENODATA),
                                               "Received an IA option without valid IA addresses or PD prefixes, ignoring.");
 
-        if (IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_PD) &&
-            lt_t1 == 0 && lt_t2 == 0 && lt_min != UINT32_MAX) {
-                lt_t1 = lt_min / 2;
-                lt_t2 = lt_min / 10 * 8;
-
-                log_dhcp6_client(client, "Received an IA option with both T1 and T2 equal to zero. "
-                                 "Adjusting them based on the minimum valid lifetime of IA addresses or PD prefixes: "
-                                 "T1=%"PRIu32"sec, T2=%"PRIu32"sec", lt_t1, lt_t2);
-        }
-
-        switch(option_code) {
-        case SD_DHCP6_OPTION_IA_NA:
-                *ret = (DHCP6IA) {
-                        .type = option_code,
-                        .ia_na.id = iaid,
-                        .ia_na.lifetime_t1 = htobe32(lt_t1),
-                        .ia_na.lifetime_t2 = htobe32(lt_t2),
-                        .addresses = TAKE_PTR(ia.addresses),
-                };
-                break;
-        case SD_DHCP6_OPTION_IA_TA:
-                *ret = (DHCP6IA) {
-                        .type = option_code,
-                        .ia_ta.id = iaid,
-                        .addresses = TAKE_PTR(ia.addresses),
-                };
-                break;
-        case SD_DHCP6_OPTION_IA_PD:
-                *ret = (DHCP6IA) {
-                        .type = option_code,
-                        .ia_pd.id = iaid,
-                        .ia_pd.lifetime_t1 = htobe32(lt_t1),
-                        .ia_pd.lifetime_t2 = htobe32(lt_t2),
-                        .addresses = TAKE_PTR(ia.addresses),
-                };
-                break;
-        default:
-                assert_not_reached();
-        }
-
+        *ret = TAKE_PTR(ia);
         return 0;
 }
 
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.h
new file mode 100644
index 00000000..80aba7f3
--- /dev/null
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-dhcp6-client.h"
+
+#include "hash-funcs.h"
+#include "list.h"
+#include "macro.h"
+#include "ordered-set.h"
+#include "sparse-endian.h"
+
+typedef struct sd_dhcp6_option {
+        unsigned n_ref;
+
+        uint32_t enterprise_identifier;
+        uint16_t option;
+        void *data;
+        size_t length;
+} sd_dhcp6_option;
+
+extern const struct hash_ops dhcp6_option_hash_ops;
+
+/* Common option header */
+typedef struct DHCP6Option {
+        be16_t code;
+        be16_t len;
+        uint8_t data[];
+} _packed_ DHCP6Option;
+
+/* Address option */
+struct iaaddr {
+        struct in6_addr address;
+        be32_t lifetime_preferred;
+        be32_t lifetime_valid;
+} _packed_;
+
+/* Prefix Delegation Prefix option */
+struct iapdprefix {
+        be32_t lifetime_preferred;
+        be32_t lifetime_valid;
+        uint8_t prefixlen;
+        struct in6_addr address;
+} _packed_;
+
+typedef struct DHCP6Address DHCP6Address;
+
+struct DHCP6Address {
+        LIST_FIELDS(DHCP6Address, addresses);
+
+        union {
+                struct iaaddr iaaddr;
+                struct iapdprefix iapdprefix;
+        };
+};
+
+struct ia_header {
+        be32_t id;
+        be32_t lifetime_t1;
+        be32_t lifetime_t2;
+} _packed_;
+
+typedef struct DHCP6IA {
+        uint16_t type;
+        struct ia_header header;
+
+        LIST_HEAD(DHCP6Address, addresses);
+} DHCP6IA;
+
+void dhcp6_ia_clear_addresses(DHCP6IA *ia);
+DHCP6IA *dhcp6_ia_free(DHCP6IA *ia);
+DEFINE_TRIVIAL_CLEANUP_FUNC(DHCP6IA*, dhcp6_ia_free);
+
+bool dhcp6_option_can_request(uint16_t option);
+
+int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
+                        size_t optlen, const void *optval);
+int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia);
+int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn);
+int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class);
+int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *user_class);
+int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedSet *vendor_options);
+
+int dhcp6_option_parse(
+                const uint8_t *buf,
+                size_t buflen,
+                size_t *offset,
+                uint16_t *ret_option_code,
+                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_ia(
+                sd_dhcp6_client *client,
+                be32_t iaid,
+                uint16_t option_code,
+                size_t option_data_len,
+                const uint8_t *option_data,
+                DHCP6IA **ret);
+int dhcp6_option_parse_addresses(
+                const uint8_t *optval,
+                size_t optlen,
+                struct in6_addr **addrs,
+                size_t *count);
+int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, char ***ret);
+int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret);
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.c
new file mode 100644
index 00000000..e19faa51
--- /dev/null
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.c
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include "dhcp6-protocol.h"
+#include "string-table.h"
+
+static const char * const dhcp6_state_table[_DHCP6_STATE_MAX] = {
+        [DHCP6_STATE_STOPPED]             = "stopped",
+        [DHCP6_STATE_INFORMATION_REQUEST] = "information-request",
+        [DHCP6_STATE_SOLICITATION]        = "solicitation",
+        [DHCP6_STATE_REQUEST]             = "request",
+        [DHCP6_STATE_BOUND]               = "bound",
+        [DHCP6_STATE_RENEW]               = "renew",
+        [DHCP6_STATE_REBIND]              = "rebind",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_state, DHCP6State);
+
+static const char * const dhcp6_message_type_table[_DHCP6_MESSAGE_TYPE_MAX] = {
+        [DHCP6_MESSAGE_SOLICIT]             = "Solicit",
+        [DHCP6_MESSAGE_ADVERTISE]           = "Advertise",
+        [DHCP6_MESSAGE_REQUEST]             = "Request",
+        [DHCP6_MESSAGE_CONFIRM]             = "Confirm",
+        [DHCP6_MESSAGE_RENEW]               = "Renew",
+        [DHCP6_MESSAGE_REBIND]              = "Rebind",
+        [DHCP6_MESSAGE_REPLY]               = "Reply",
+        [DHCP6_MESSAGE_RELEASE]             = "Release",
+        [DHCP6_MESSAGE_DECLINE]             = "Decline",
+        [DHCP6_MESSAGE_RECONFIGURE]         = "Reconfigure",
+        [DHCP6_MESSAGE_INFORMATION_REQUEST] = "Information Request",
+        [DHCP6_MESSAGE_RELAY_FORWARD]       = "Relay Forward",
+        [DHCP6_MESSAGE_RELAY_REPLY]         = "Relay Reply",
+        [DHCP6_MESSAGE_LEASE_QUERY]         = "Lease Query",
+        [DHCP6_MESSAGE_LEASE_QUERY_REPLY]   = "Lease Query Reply",
+        [DHCP6_MESSAGE_LEASE_QUERY_DONE]    = "Lease Query Done",
+        [DHCP6_MESSAGE_LEASE_QUERY_DATA]    = "Lease Query Data",
+        [DHCP6_MESSAGE_RECONFIGURE_REQUEST] = "Reconfigure Request",
+        [DHCP6_MESSAGE_RECONFIGURE_REPLY]   = "Reconfigure Reply",
+        [DHCP6_MESSAGE_DHCPV4_QUERY]        = "DHCPv4 Query",
+        [DHCP6_MESSAGE_DHCPV4_RESPONSE]     = "DHCPv4 Response",
+        [DHCP6_MESSAGE_ACTIVE_LEASE_QUERY]  = "Active Lease Query",
+        [DHCP6_MESSAGE_START_TLS]           = "Start TLS",
+        [DHCP6_MESSAGE_BINDING_UPDATE]      = "Binding Update",
+        [DHCP6_MESSAGE_BINDING_REPLY]       = "Binding Reply",
+        [DHCP6_MESSAGE_POOL_REQUEST]        = "Pool Request",
+        [DHCP6_MESSAGE_POOL_RESPONSE]       = "Pool Response",
+        [DHCP6_MESSAGE_UPDATE_REQUEST]      = "Update Request",
+        [DHCP6_MESSAGE_UPDATE_REQUEST_ALL]  = "Update Request All",
+        [DHCP6_MESSAGE_UPDATE_DONE]         = "Update Done",
+        [DHCP6_MESSAGE_CONNECT]             = "Connect",
+        [DHCP6_MESSAGE_CONNECT_REPLY]       = "Connect Reply",
+        [DHCP6_MESSAGE_DISCONNECT]          = "Disconnect",
+        [DHCP6_MESSAGE_STATE]               = "State",
+        [DHCP6_MESSAGE_CONTACT]             = "Contact",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, DHCP6MessageType);
+
+static const char * const dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
+        [DHCP6_STATUS_SUCCESS]                      = "Success",
+        [DHCP6_STATUS_UNSPEC_FAIL]                  = "Unspecified failure",
+        [DHCP6_STATUS_NO_ADDRS_AVAIL]               = "No addresses available",
+        [DHCP6_STATUS_NO_BINDING]                   = "Binding unavailable",
+        [DHCP6_STATUS_NOT_ON_LINK]                  = "Not on link",
+        [DHCP6_STATUS_USE_MULTICAST]                = "Use multicast",
+        [DHCP6_STATUS_NO_PREFIX_AVAIL]              = "No prefix available",
+        [DHCP6_STATUS_UNKNOWN_QUERY_TYPE]           = "Unknown query type",
+        [DHCP6_STATUS_MALFORMED_QUERY]              = "Malformed query",
+        [DHCP6_STATUS_NOT_CONFIGURED]               = "Not configured",
+        [DHCP6_STATUS_NOT_ALLOWED]                  = "Not allowed",
+        [DHCP6_STATUS_QUERY_TERMINATED]             = "Query terminated",
+        [DHCP6_STATUS_DATA_MISSING]                 = "Data missing",
+        [DHCP6_STATUS_CATCHUP_COMPLETE]             = "Catch up complete",
+        [DHCP6_STATUS_NOT_SUPPORTED]                = "Not supported",
+        [DHCP6_STATUS_TLS_CONNECTION_REFUSED]       = "TLS connection refused",
+        [DHCP6_STATUS_ADDRESS_IN_USE]               = "Address in use",
+        [DHCP6_STATUS_CONFIGURATION_CONFLICT]       = "Configuration conflict",
+        [DHCP6_STATUS_MISSING_BINDING_INFORMATION]  = "Missing binding information",
+        [DHCP6_STATUS_OUTDATED_BINDING_INFORMATION] = "Outdated binding information",
+        [DHCP6_STATUS_SERVER_SHUTTING_DOWN]         = "Server shutting down",
+        [DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED]     = "DNS update not supported",
+        [DHCP6_STATUS_EXCESSIVE_TIME_SKEW]          = "Excessive time skew",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, DHCP6Status);
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h
index 5d2af439..f4e47857 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h
@@ -28,8 +28,8 @@ typedef struct DHCP6Message DHCP6Message;
 #define DHCP6_MIN_OPTIONS_SIZE \
         1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr)
 
-#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \
-        { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT                 \
+        { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,           \
               0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } }
 
 enum {
@@ -100,13 +100,15 @@ typedef enum DHCP6MessageType {
         DHCP6_MESSAGE_STATE                     = 34, /* RFC 8156 */
         DHCP6_MESSAGE_CONTACT                   = 35, /* RFC 8156 */
         _DHCP6_MESSAGE_TYPE_MAX,
-        _DHCP6_MESSAGE_TYPE_INVALID = -EINVAL,
+        _DHCP6_MESSAGE_TYPE_INVALID             = -EINVAL,
 } DHCP6MessageType;
 
 typedef enum DHCP6NTPSubOption {
         DHCP6_NTP_SUBOPTION_SRV_ADDR            = 1,
         DHCP6_NTP_SUBOPTION_MC_ADDR             = 2,
         DHCP6_NTP_SUBOPTION_SRV_FQDN            = 3,
+        _DHCP6_NTP_SUBOPTION_MAX,
+        _DHCP6_NTP_SUBOPTION_INVALID            = -EINVAL,
 } DHCP6NTPSubOption;
 
 /*
@@ -138,7 +140,7 @@ typedef enum DHCP6Status {
         DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED     = 21,
         DHCP6_STATUS_EXCESSIVE_TIME_SKEW          = 22,
         _DHCP6_STATUS_MAX,
-        _DHCP6_STATUS_INVALID = -EINVAL,
+        _DHCP6_STATUS_INVALID                     = -EINVAL,
 } DHCP6Status;
 
 typedef enum DHCP6FQDNFlag {
@@ -146,3 +148,9 @@ typedef enum DHCP6FQDNFlag {
         DHCP6_FQDN_FLAG_O = 1 << 1,
         DHCP6_FQDN_FLAG_N = 1 << 2,
 } DHCP6FQDNFlag;
+
+const char *dhcp6_state_to_string(DHCP6State s) _const_;
+const char *dhcp6_message_type_to_string(DHCP6MessageType s) _const_;
+DHCP6MessageType dhcp6_message_type_from_string(const char *s) _pure_;
+const char *dhcp6_message_status_to_string(DHCP6Status s) _const_;
+DHCP6Status dhcp6_message_status_from_string(const char *s) _pure_;
diff --git a/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c b/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c
index b4d7c327..f56929ad 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c
@@ -335,9 +335,9 @@ void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) {
                 usec_t base;
 
                 /* Use the packet's timestamp if there is one known */
-                base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic());
-                if (base <= 0 || base == USEC_INFINITY)
-                        base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */
+                base = triple_timestamp_by_clock(&n->timestamp, CLOCK_BOOTTIME);
+                if (!timestamp_is_set(base))
+                        base = now(CLOCK_BOOTTIME); /* Otherwise, take the current time */
 
                 n->until = usec_add(base, n->ttl * USEC_PER_SEC);
         } else
diff --git a/src/libnm-systemd-core/src/libsystemd-network/network-internal.h b/src/libnm-systemd-core/src/libsystemd-network/network-internal.h
index 895a00d0..5aa225e9 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/network-internal.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/network-internal.h
@@ -21,7 +21,7 @@ int deserialize_in6_addrs(struct in6_addr **addresses, const char *string);
 struct sd_dhcp_route;
 struct sd_dhcp_lease;
 
-void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size);
+void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route **routes, size_t size);
 int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, const char *string);
 
 /* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c
index 1600f806..d56978ad 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c
@@ -147,11 +147,11 @@ static const uint8_t default_req_opts_anonymize[] = {
         SD_DHCP_OPTION_ROUTER,                          /* 3 */
         SD_DHCP_OPTION_DOMAIN_NAME_SERVER,              /* 6 */
         SD_DHCP_OPTION_DOMAIN_NAME,                     /* 15 */
-        SD_DHCP_OPTION_ROUTER_DISCOVER,                 /* 31 */
+        SD_DHCP_OPTION_ROUTER_DISCOVERY,                /* 31 */
         SD_DHCP_OPTION_STATIC_ROUTE,                    /* 33 */
         SD_DHCP_OPTION_VENDOR_SPECIFIC,                 /* 43 */
-        SD_DHCP_OPTION_NETBIOS_NAMESERVER,              /* 44 */
-        SD_DHCP_OPTION_NETBIOS_NODETYPE,                /* 46 */
+        SD_DHCP_OPTION_NETBIOS_NAME_SERVER,             /* 44 */
+        SD_DHCP_OPTION_NETBIOS_NODE_TYPE,               /* 46 */
         SD_DHCP_OPTION_NETBIOS_SCOPE,                   /* 47 */
         SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,          /* 121 */
         SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE,  /* 249 */
@@ -249,7 +249,7 @@ int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) {
         assert_return(client, -EINVAL);
         assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY);
 
-        switch(option) {
+        switch (option) {
 
         case SD_DHCP_OPTION_PAD:
         case SD_DHCP_OPTION_OVERLOAD:
@@ -454,7 +454,7 @@ static int dhcp_client_set_iaid_duid_internal(
                 bool iaid_append,
                 bool iaid_set,
                 uint32_t iaid,
-                uint16_t duid_type,
+                DUIDType duid_type,
                 const void *duid,
                 size_t duid_len,
                 usec_t llt_time) {
@@ -493,37 +493,20 @@ static int dhcp_client_set_iaid_duid_internal(
                 client->client_id.ns.duid.type = htobe16(duid_type);
                 memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len);
                 len = sizeof(client->client_id.ns.duid.type) + duid_len;
-        } else
-                switch (duid_type) {
-                case DUID_TYPE_LLT:
-                        if (client->mac_addr_len == 0)
-                                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set.");
 
-                        r = dhcp_identifier_set_duid_llt(&client->client_id.ns.duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
-                        if (r < 0)
-                                return log_dhcp_client_errno(client, r, "Failed to set DUID-LLT: %m");
-                        break;
-                case DUID_TYPE_EN:
-                        r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len);
-                        if (r < 0)
-                                return log_dhcp_client_errno(client, r, "Failed to set DUID-EN: %m");
-                        break;
-                case DUID_TYPE_LL:
-                        if (client->mac_addr_len == 0)
-                                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set.");
-
-                        r = dhcp_identifier_set_duid_ll(&client->client_id.ns.duid, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
-                        if (r < 0)
-                                return log_dhcp_client_errno(client, r, "Failed to set DUID-LL: %m");
-                        break;
-                case DUID_TYPE_UUID:
-                        r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len);
-                        if (r < 0)
-                                return log_dhcp_client_errno(client, r, "Failed to set DUID-UUID: %m");
-                        break;
-                default:
-                        return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type");
-                }
+        } else {
+                r = dhcp_identifier_set_duid(duid_type, client->mac_addr, client->mac_addr_len,
+                                             client->arp_type, llt_time, client->test_mode,
+                                             &client->client_id.ns.duid, &len);
+                if (r == -EOPNOTSUPP)
+                        return log_dhcp_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_dhcp_client_errno(client, r, "Failed to set %s: %m",
+                                                     duid_type_to_string(duid_type));
+        }
 
         client->client_id_len = sizeof(client->client_id.type) + len +
                                 (iaid_append ? sizeof(client->client_id.ns.iaid) : 0);
@@ -617,7 +600,6 @@ int sd_dhcp_client_set_user_class(
                 sd_dhcp_client *client,
                 char * const *user_class) {
 
-        char * const *p;
         char **s = NULL;
 
         assert_return(client, -EINVAL);
@@ -844,7 +826,7 @@ static int client_message_init(
 
         /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers
            refuse to issue an DHCP lease if 'secs' is set to zero */
-        r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+        r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
         if (r < 0)
                 return r;
         assert(time_now >= client->start_time);
@@ -881,7 +863,7 @@ static int client_message_init(
                 if (r < 0)
                         return r;
 
-                r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &duid_len);
+                r = dhcp_identifier_set_duid_en(client->test_mode, &client->client_id.ns.duid, &duid_len);
                 if (r < 0)
                         return r;
 
@@ -1269,7 +1251,7 @@ static int client_timeout_resend(
         assert(client);
         assert(client->event);
 
-        r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+        r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
         if (r < 0)
                 goto error;
 
@@ -1317,7 +1299,7 @@ static int client_timeout_resend(
         }
 
         r = event_reset_time(client->event, &client->timeout_resend,
-                             clock_boottime_or_monotonic(),
+                             CLOCK_BOOTTIME,
                              next_timeout, 10 * USEC_PER_MSEC,
                              client_timeout_resend, client,
                              client->event_priority, "dhcp4-resend-timer", true);
@@ -1417,12 +1399,12 @@ static int client_initialize_time_events(sd_dhcp_client *client) {
         assert(client->event);
 
         if (client->start_delay > 0) {
-                assert_se(sd_event_now(client->event, clock_boottime_or_monotonic(), &usec) >= 0);
+                assert_se(sd_event_now(client->event, CLOCK_BOOTTIME, &usec) >= 0);
                 usec += client->start_delay;
         }
 
         r = event_reset_time(client->event, &client->timeout_resend,
-                             clock_boottime_or_monotonic(),
+                             CLOCK_BOOTTIME,
                              usec, 0,
                              client_timeout_resend, client,
                              client->event_priority, "dhcp4-resend-timer", true);
@@ -1463,7 +1445,7 @@ static int client_start_delayed(sd_dhcp_client *client) {
         client->fd = r;
 
         if (IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT))
-                client->start_time = now(clock_boottime_or_monotonic());
+                client->start_time = now(CLOCK_BOOTTIME);
 
         return client_initialize_events(client, client_receive_message_raw);
 }
@@ -1707,7 +1689,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
                 return 0;
         }
 
-        r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+        r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
         if (r < 0)
                 return r;
         assert(client->request_sent <= time_now);
@@ -1740,7 +1722,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
 
         /* arm lifetime timeout */
         r = event_reset_time(client->event, &client->timeout_expire,
-                             clock_boottime_or_monotonic(),
+                             CLOCK_BOOTTIME,
                              client->expire_time, 10 * USEC_PER_MSEC,
                              client_timeout_expire, client,
                              client->event_priority, "dhcp4-lifetime", true);
@@ -1756,7 +1738,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
 
         /* arm T2 timeout */
         r = event_reset_time(client->event, &client->timeout_t2,
-                             clock_boottime_or_monotonic(),
+                             CLOCK_BOOTTIME,
                              client->t2_time, 10 * USEC_PER_MSEC,
                              client_timeout_t2, client,
                              client->event_priority, "dhcp4-t2-timeout", true);
@@ -1772,7 +1754,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
 
         /* arm T1 timeout */
         r = event_reset_time(client->event, &client->timeout_t1,
-                             clock_boottime_or_monotonic(),
+                             CLOCK_BOOTTIME,
                              client->t1_time, 10 * USEC_PER_MSEC,
                              client_timeout_t1, client,
                              client->event_priority, "dhcp4-t1-timer", true);
@@ -1807,7 +1789,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
                 client->attempt = 0;
 
                 r = event_reset_time(client->event, &client->timeout_resend,
-                                     clock_boottime_or_monotonic(),
+                                     CLOCK_BOOTTIME,
                                      0, 0,
                                      client_timeout_resend, client,
                                      client->event_priority, "dhcp4-resend-timer", true);
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-lease.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-lease.c
index 7a250534..5f83fa66 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-lease.c
@@ -217,25 +217,38 @@ int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr) {
  * The returned routes array must be freed by the caller.
  * Route objects have the same lifetime of the lease and must not be freed.
  */
-int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes) {
-        sd_dhcp_route **ret;
-        unsigned i;
+static int dhcp_lease_get_routes(sd_dhcp_route *routes, size_t n_routes, sd_dhcp_route ***ret) {
+        assert(routes || n_routes == 0);
 
-        assert_return(lease, -EINVAL);
-        assert_return(routes, -EINVAL);
-
-        if (lease->static_route_size <= 0)
+        if (n_routes <= 0)
                 return -ENODATA;
 
-        ret = new(sd_dhcp_route *, lease->static_route_size);
-        if (!ret)
-                return -ENOMEM;
+        if (ret) {
+                sd_dhcp_route **buf;
+
+                buf = new(sd_dhcp_route*, n_routes);
+                if (!buf)
+                        return -ENOMEM;
+
+                for (size_t i = 0; i < n_routes; i++)
+                        buf[i] = &routes[i];
+
+                *ret = buf;
+        }
 
-        for (i = 0; i < lease->static_route_size; i++)
-                ret[i] = &lease->static_route[i];
+        return (int) n_routes;
+}
+
+int sd_dhcp_lease_get_static_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret) {
+        assert_return(lease, -EINVAL);
+
+        return dhcp_lease_get_routes(lease->static_routes, lease->n_static_routes, ret);
+}
+
+int sd_dhcp_lease_get_classless_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret) {
+        assert_return(lease, -EINVAL);
 
-        *routes = ret;
-        return (int) lease->static_route_size;
+        return dhcp_lease_get_routes(lease->classless_routes, lease->n_classless_routes, ret);
 }
 
 int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) {
@@ -314,7 +327,8 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) {
         for (sd_dhcp_lease_server_type_t i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++)
                 free(lease->servers[i].addr);
 
-        free(lease->static_route);
+        free(lease->static_routes);
+        free(lease->classless_routes);
         free(lease->client_id);
         free(lease->vendor_specific);
         strv_free(lease->search_domains);
@@ -469,96 +483,88 @@ static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_a
         return lease_parse_in_addrs(option + 1, len - 1, ret, n_ret);
 }
 
-static int lease_parse_routes(
-                const uint8_t *option, size_t len,
-                struct sd_dhcp_route **routes, size_t *routes_size) {
-
-        struct in_addr addr;
+static int lease_parse_static_routes(sd_dhcp_lease *lease, const uint8_t *option, size_t len) {
+        int r;
 
+        assert(lease);
         assert(option || len <= 0);
-        assert(routes);
-        assert(routes_size);
-
-        if (len <= 0)
-                return 0;
 
         if (len % 8 != 0)
                 return -EINVAL;
 
-        if (!GREEDY_REALLOC(*routes, *routes_size + (len / 8)))
-                return -ENOMEM;
-
         while (len >= 8) {
-                struct sd_dhcp_route *route = *routes + *routes_size;
-                int r;
+                struct in_addr dst, gw;
+                uint8_t prefixlen;
 
-                route->option = SD_DHCP_OPTION_STATIC_ROUTE;
-                r = in4_addr_default_prefixlen((struct in_addr*) option, &route->dst_prefixlen);
+                assert_se(lease_parse_be32(option, 4, &dst.s_addr) >= 0);
+                option += 4;
+
+                assert_se(lease_parse_be32(option, 4, &gw.s_addr) >= 0);
+                option += 4;
+
+                len -= 8;
+
+                r = in4_addr_default_prefixlen(&dst, &prefixlen);
                 if (r < 0) {
-                        log_debug("Failed to determine destination prefix length from class based IP, ignoring");
+                        log_debug("sd-dhcp-lease: cannot determine class of received static route, ignoring.");
                         continue;
                 }
 
-                assert_se(lease_parse_be32(option, 4, &addr.s_addr) >= 0);
-                route->dst_addr = inet_makeaddr(inet_netof(addr), 0);
-                option += 4;
+                (void) in4_addr_mask(&dst, prefixlen);
 
-                assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0);
-                option += 4;
+                if (!GREEDY_REALLOC(lease->static_routes, lease->n_static_routes + 1))
+                        return -ENOMEM;
 
-                len -= 8;
-                (*routes_size)++;
+                lease->static_routes[lease->n_static_routes++] = (struct sd_dhcp_route) {
+                        .dst_addr = dst,
+                        .gw_addr = gw,
+                        .dst_prefixlen = prefixlen,
+                };
         }
 
         return 0;
 }
 
 /* parses RFC3442 Classless Static Route Option */
-static int lease_parse_classless_routes(
-                const uint8_t *option, size_t len,
-                struct sd_dhcp_route **routes, size_t *routes_size) {
-
+static int lease_parse_classless_routes(sd_dhcp_lease *lease, const uint8_t *option, size_t len) {
+        assert(lease);
         assert(option || len <= 0);
-        assert(routes);
-        assert(routes_size);
 
-        if (len <= 0)
-                return 0;
-
-        /* option format: (subnet-mask-width significant-subnet-octets gateway-ip)*  */
+        /* option format: (subnet-mask-width significant-subnet-octets gateway-ip) */
 
         while (len > 0) {
-                uint8_t dst_octets;
-                struct sd_dhcp_route *route;
-
-                if (!GREEDY_REALLOC(*routes, *routes_size + 1))
-                        return -ENOMEM;
-
-                route = *routes + *routes_size;
-                route->option = SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE;
+                uint8_t prefixlen, dst_octets;
+                struct in_addr dst = {}, gw;
 
-                dst_octets = (*option == 0 ? 0 : ((*option - 1) / 8) + 1);
-                route->dst_prefixlen = *option;
+                prefixlen = *option;
                 option++;
                 len--;
 
+                dst_octets = DIV_ROUND_UP(prefixlen, 8);
+
                 /* can't have more than 4 octets in IPv4 */
                 if (dst_octets > 4 || len < dst_octets)
                         return -EINVAL;
 
-                route->dst_addr.s_addr = 0;
-                memcpy(&route->dst_addr.s_addr, option, dst_octets);
+                memcpy(&dst, option, dst_octets);
                 option += dst_octets;
                 len -= dst_octets;
 
                 if (len < 4)
                         return -EINVAL;
 
-                assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0);
+                assert_se(lease_parse_be32(option, 4, &gw.s_addr) >= 0);
                 option += 4;
                 len -= 4;
 
-                (*routes_size)++;
+                if (!GREEDY_REALLOC(lease->classless_routes, lease->n_classless_routes + 1))
+                        return -ENOMEM;
+
+                lease->classless_routes[lease->n_classless_routes++] = (struct sd_dhcp_route) {
+                        .dst_addr = dst,
+                        .gw_addr = gw,
+                        .dst_prefixlen = prefixlen,
+                };
         }
 
         return 0;
@@ -625,7 +631,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
 
         assert(lease);
 
-        switch(code) {
+        switch (code) {
 
         case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
                 r = lease_parse_u32(option, len, &lease->lifetime, 1);
@@ -700,12 +706,12 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
                 break;
 
         case SD_DHCP_OPTION_STATIC_ROUTE:
-                r = lease_parse_routes(option, len, &lease->static_route, &lease->static_route_size);
+                r = lease_parse_static_routes(lease, option, len);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse static routes, ignoring: %m");
                 break;
 
-        case SD_DHCP_OPTION_INTERFACE_MTU:
+        case SD_DHCP_OPTION_MTU_INTERFACE:
                 r = lease_parse_u16(option, len, &lease->mtu, 68);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse MTU, ignoring: %m");
@@ -725,7 +731,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
 
                 break;
 
-        case SD_DHCP_OPTION_DOMAIN_SEARCH_LIST:
+        case SD_DHCP_OPTION_DOMAIN_SEARCH:
                 r = dhcp_lease_parse_search_domains(option, len, &lease->search_domains);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse Domain Search List, ignoring: %m");
@@ -746,28 +752,25 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
                         log_debug_errno(r, "Failed to parse root path, ignoring: %m");
                 break;
 
-        case SD_DHCP_OPTION_RENEWAL_T1_TIME:
+        case SD_DHCP_OPTION_RENEWAL_TIME:
                 r = lease_parse_u32(option, len, &lease->t1, 1);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse T1 time, ignoring: %m");
                 break;
 
-        case SD_DHCP_OPTION_REBINDING_T2_TIME:
+        case SD_DHCP_OPTION_REBINDING_TIME:
                 r = lease_parse_u32(option, len, &lease->t2, 1);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse T2 time, ignoring: %m");
                 break;
 
         case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
-                r = lease_parse_classless_routes(
-                                option, len,
-                                &lease->static_route,
-                                &lease->static_route_size);
+                r = lease_parse_classless_routes(lease, option, len);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse classless routes, ignoring: %m");
                 break;
 
-        case SD_DHCP_OPTION_NEW_TZDB_TIMEZONE: {
+        case SD_DHCP_OPTION_TZDB_TIMEZONE: {
                 _cleanup_free_ char *tz = NULL;
 
                 r = lease_parse_string(option, len, &tz);
@@ -909,19 +912,21 @@ int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***d
                       pos = next_chunk;
         }
 
-        *domains = TAKE_PTR(names);
+        strv_free_and_replace(*domains, names);
 
         return cnt;
 }
 
 int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) {
-        struct sd_dhcp_raw_option *cur, *option;
+        struct sd_dhcp_raw_option *option, *before = NULL;
 
         assert(lease);
 
         LIST_FOREACH(options, cur, lease->private_options) {
-                if (tag < cur->tag)
+                if (tag < cur->tag) {
+                        before = cur;
                         break;
+                }
                 if (tag == cur->tag) {
                         log_debug("Ignoring duplicate option, tagged %i.", tag);
                         return 0;
@@ -940,7 +945,7 @@ int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const vo
                 return -ENOMEM;
         }
 
-        LIST_INSERT_BEFORE(options, lease->private_options, cur, option);
+        LIST_INSERT_BEFORE(options, lease->private_options, before, option);
         return 0;
 }
 
@@ -960,7 +965,6 @@ int dhcp_lease_new(sd_dhcp_lease **ret) {
 int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         _cleanup_(unlink_and_freep) char *temp_path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
-        struct sd_dhcp_raw_option *option;
         struct in_addr address;
         const struct in_addr *addresses;
         const void *client_id, *data;
@@ -969,7 +973,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         const char *string;
         uint16_t mtu;
         _cleanup_free_ sd_dhcp_route **routes = NULL;
-        char **search_domains = NULL;
+        char **search_domains;
         uint32_t t1, t2, lifetime;
         int r;
 
@@ -1068,9 +1072,14 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         if (r >= 0)
                 fprintf(f, "ROOT_PATH=%s\n", string);
 
-        r = sd_dhcp_lease_get_routes(lease, &routes);
+        r = sd_dhcp_lease_get_static_routes(lease, &routes);
+        if (r > 0)
+                serialize_dhcp_routes(f, "STATIC_ROUTES", routes, r);
+
+        routes = mfree(routes);
+        r = sd_dhcp_lease_get_classless_routes(lease, &routes);
         if (r > 0)
-                serialize_dhcp_routes(f, "ROUTES", routes, r);
+                serialize_dhcp_routes(f, "CLASSLESS_ROUTES", routes, r);
 
         r = sd_dhcp_lease_get_timezone(lease, &string);
         if (r >= 0)
@@ -1118,6 +1127,18 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         return 0;
 }
 
+static char **private_options_free(char **options) {
+        if (!options)
+                return NULL;
+
+        for (unsigned i = 0; i < SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1; i++)
+                free(options[i]);
+
+        return mfree(options);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(char**, private_options_free);
+
 int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
         _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
         _cleanup_free_ char
@@ -1134,14 +1155,15 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                 *smtp = NULL,
                 *lpr = NULL,
                 *mtu = NULL,
-                *routes = NULL,
+                *static_routes = NULL,
+                *classless_routes = NULL,
                 *domains = NULL,
                 *client_id_hex = NULL,
                 *vendor_specific_hex = NULL,
                 *lifetime = NULL,
                 *t1 = NULL,
-                *t2 = NULL,
-                *options[SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1] = {};
+                *t2 = NULL;
+        _cleanup_(private_options_freep) char **options = NULL;
 
         int r, i;
 
@@ -1152,6 +1174,10 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
         if (r < 0)
                 return r;
 
+        options = new0(char*, SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1);
+        if (!options)
+                return -ENOMEM;
+
         r = parse_env_file(NULL, lease_file,
                            "ADDRESS", &address,
                            "ROUTER", &router,
@@ -1170,7 +1196,8 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                            "HOSTNAME", &lease->hostname,
                            "DOMAIN_SEARCH_LIST", &domains,
                            "ROOT_PATH", &lease->root_path,
-                           "ROUTES", &routes,
+                           "STATIC_ROUTES", &static_routes,
+                           "CLASSLESS_ROUTES", &classless_routes,
                            "CLIENTID", &client_id_hex,
                            "TIMEZONE", &lease->timezone,
                            "VENDOR_SPECIFIC", &vendor_specific_hex,
@@ -1317,13 +1344,22 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                         lease->search_domains = TAKE_PTR(a);
         }
 
-        if (routes) {
+        if (static_routes) {
                 r = deserialize_dhcp_routes(
-                                &lease->static_route,
-                                &lease->static_route_size,
-                                routes);
+                                &lease->static_routes,
+                                &lease->n_static_routes,
+                                static_routes);
                 if (r < 0)
-                        log_debug_errno(r, "Failed to parse DHCP routes %s, ignoring: %m", routes);
+                        log_debug_errno(r, "Failed to parse DHCP static routes %s, ignoring: %m", static_routes);
+        }
+
+        if (classless_routes) {
+                r = deserialize_dhcp_routes(
+                                &lease->classless_routes,
+                                &lease->n_classless_routes,
+                                classless_routes);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse DHCP classless routes %s, ignoring: %m", classless_routes);
         }
 
         if (lifetime) {
@@ -1470,9 +1506,3 @@ int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) {
         *gateway = route->gw_addr;
         return 0;
 }
-
-int sd_dhcp_route_get_option(sd_dhcp_route *route) {
-        assert_return(route, -EINVAL);
-
-        return route->option;
-}
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c
index 0c2206ff..4394ef49 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
@@ -20,7 +20,6 @@
 #include "dhcp-identifier.h"
 #include "dhcp6-internal.h"
 #include "dhcp6-lease-internal.h"
-#include "dhcp6-protocol.h"
 #include "dns-domain.h"
 #include "event-util.h"
 #include "fd-util.h"
@@ -28,74 +27,11 @@
 #include "hostname-util.h"
 #include "in-addr-util.h"
 #include "io-util.h"
-#include "network-common.h"
 #include "random-util.h"
 #include "socket-util.h"
-#include "string-table.h"
 #include "strv.h"
-#include "util.h"
 #include "web-util.h"
 
-#define MAX_MAC_ADDR_LEN INFINIBAND_ALEN
-
-#define IRT_DEFAULT (1 * USEC_PER_DAY)
-#define IRT_MINIMUM (600 * USEC_PER_SEC)
-
-/* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */
-typedef enum DHCP6RequestIA {
-        DHCP6_REQUEST_IA_NA = 1 << 0,
-        DHCP6_REQUEST_IA_TA = 1 << 1, /* currently not used */
-        DHCP6_REQUEST_IA_PD = 1 << 2,
-} DHCP6RequestIA;
-
-struct sd_dhcp6_client {
-        unsigned n_ref;
-
-        DHCP6State state;
-        sd_event *event;
-        int event_priority;
-        int ifindex;
-        char *ifname;
-        DHCP6Address hint_pd_prefix;
-        struct in6_addr local_address;
-        uint8_t mac_addr[MAX_MAC_ADDR_LEN];
-        size_t mac_addr_len;
-        uint16_t arp_type;
-        DHCP6IA ia_na;
-        DHCP6IA ia_pd;
-        sd_event_source *timeout_t1;
-        sd_event_source *timeout_t2;
-        DHCP6RequestIA request_ia;
-        be32_t transaction_id;
-        usec_t transaction_start;
-        struct sd_dhcp6_lease *lease;
-        int fd;
-        bool information_request;
-        bool iaid_set;
-        be16_t *req_opts;
-        size_t req_opts_len;
-        char *fqdn;
-        char *mudurl;
-        char **user_class;
-        char **vendor_class;
-        sd_event_source *receive_message;
-        usec_t retransmit_time;
-        uint8_t retransmit_count;
-        sd_event_source *timeout_resend;
-        sd_event_source *timeout_resend_expire;
-        sd_dhcp6_client_callback_t callback;
-        void *userdata;
-        struct duid duid;
-        size_t duid_len;
-        usec_t information_request_time_usec;
-        usec_t information_refresh_time_usec;
-        OrderedHashmap *extra_options;
-        OrderedHashmap *vendor_options;
-
-        /* Ignore ifindex when generating iaid. See dhcp_identifier_set_iaid(). */
-        bool test_mode;
-};
-
 static const uint16_t default_req_opts[] = {
         SD_DHCP6_OPTION_DNS_SERVERS,
         SD_DHCP6_OPTION_DOMAIN_LIST,
@@ -103,78 +39,10 @@ static const uint16_t default_req_opts[] = {
         SD_DHCP6_OPTION_SNTP_SERVERS,
 };
 
-const char * dhcp6_message_type_table[_DHCP6_MESSAGE_TYPE_MAX] = {
-        [DHCP6_MESSAGE_SOLICIT]             = "Solicit",
-        [DHCP6_MESSAGE_ADVERTISE]           = "Advertise",
-        [DHCP6_MESSAGE_REQUEST]             = "Request",
-        [DHCP6_MESSAGE_CONFIRM]             = "Confirm",
-        [DHCP6_MESSAGE_RENEW]               = "Renew",
-        [DHCP6_MESSAGE_REBIND]              = "Rebind",
-        [DHCP6_MESSAGE_REPLY]               = "Reply",
-        [DHCP6_MESSAGE_RELEASE]             = "Release",
-        [DHCP6_MESSAGE_DECLINE]             = "Decline",
-        [DHCP6_MESSAGE_RECONFIGURE]         = "Reconfigure",
-        [DHCP6_MESSAGE_INFORMATION_REQUEST] = "Information Request",
-        [DHCP6_MESSAGE_RELAY_FORWARD]       = "Relay Forward",
-        [DHCP6_MESSAGE_RELAY_REPLY]         = "Relay Reply",
-        [DHCP6_MESSAGE_LEASE_QUERY]         = "Lease Query",
-        [DHCP6_MESSAGE_LEASE_QUERY_REPLY]   = "Lease Query Reply",
-        [DHCP6_MESSAGE_LEASE_QUERY_DONE]    = "Lease Query Done",
-        [DHCP6_MESSAGE_LEASE_QUERY_DATA]    = "Lease Query Data",
-        [DHCP6_MESSAGE_RECONFIGURE_REQUEST] = "Reconfigure Request",
-        [DHCP6_MESSAGE_RECONFIGURE_REPLY]   = "Reconfigure Reply",
-        [DHCP6_MESSAGE_DHCPV4_QUERY]        = "DHCPv4 Query",
-        [DHCP6_MESSAGE_DHCPV4_RESPONSE]     = "DHCPv4 Response",
-        [DHCP6_MESSAGE_ACTIVE_LEASE_QUERY]  = "Active Lease Query",
-        [DHCP6_MESSAGE_START_TLS]           = "Start TLS",
-        [DHCP6_MESSAGE_BINDING_UPDATE]      = "Binding Update",
-        [DHCP6_MESSAGE_BINDING_REPLY]       = "Binding Reply",
-        [DHCP6_MESSAGE_POOL_REQUEST]        = "Pool Request",
-        [DHCP6_MESSAGE_POOL_RESPONSE]       = "Pool Response",
-        [DHCP6_MESSAGE_UPDATE_REQUEST]      = "Update Request",
-        [DHCP6_MESSAGE_UPDATE_REQUEST_ALL]  = "Update Request All",
-        [DHCP6_MESSAGE_UPDATE_DONE]         = "Update Done",
-        [DHCP6_MESSAGE_CONNECT]             = "Connect",
-        [DHCP6_MESSAGE_CONNECT_REPLY]       = "Connect Reply",
-        [DHCP6_MESSAGE_DISCONNECT]          = "Disconnect",
-        [DHCP6_MESSAGE_STATE]               = "State",
-        [DHCP6_MESSAGE_CONTACT]             = "Contact",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
-
-const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
-        [DHCP6_STATUS_SUCCESS]                      = "Success",
-        [DHCP6_STATUS_UNSPEC_FAIL]                  = "Unspecified failure",
-        [DHCP6_STATUS_NO_ADDRS_AVAIL]               = "No addresses available",
-        [DHCP6_STATUS_NO_BINDING]                   = "Binding unavailable",
-        [DHCP6_STATUS_NOT_ON_LINK]                  = "Not on link",
-        [DHCP6_STATUS_USE_MULTICAST]                = "Use multicast",
-        [DHCP6_STATUS_NO_PREFIX_AVAIL]              = "No prefix available",
-        [DHCP6_STATUS_UNKNOWN_QUERY_TYPE]           = "Unknown query type",
-        [DHCP6_STATUS_MALFORMED_QUERY]              = "Malformed query",
-        [DHCP6_STATUS_NOT_CONFIGURED]               = "Not configured",
-        [DHCP6_STATUS_NOT_ALLOWED]                  = "Not allowed",
-        [DHCP6_STATUS_QUERY_TERMINATED]             = "Query terminated",
-        [DHCP6_STATUS_DATA_MISSING]                 = "Data missing",
-        [DHCP6_STATUS_CATCHUP_COMPLETE]             = "Catch up complete",
-        [DHCP6_STATUS_NOT_SUPPORTED]                = "Not supported",
-        [DHCP6_STATUS_TLS_CONNECTION_REFUSED]       = "TLS connection refused",
-        [DHCP6_STATUS_ADDRESS_IN_USE]               = "Address in use",
-        [DHCP6_STATUS_CONFIGURATION_CONFLICT]       = "Configuration conflict",
-        [DHCP6_STATUS_MISSING_BINDING_INFORMATION]  = "Missing binding information",
-        [DHCP6_STATUS_OUTDATED_BINDING_INFORMATION] = "Outdated binding information",
-        [DHCP6_STATUS_SERVER_SHUTTING_DOWN]         = "Server shutting down",
-        [DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED]     = "DNS update not supported",
-        [DHCP6_STATUS_EXCESSIVE_TIME_SKEW]          = "Excessive time skew",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
-
 #define DHCP6_CLIENT_DONT_DESTROY(client) \
         _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
 
-static int client_start(sd_dhcp6_client *client, DHCP6State state);
+static int client_start_transaction(sd_dhcp6_client *client, DHCP6State state);
 
 int sd_dhcp6_client_set_callback(
                 sd_dhcp6_client *client,
@@ -191,8 +59,8 @@ int sd_dhcp6_client_set_callback(
 
 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);
         assert_return(ifindex > 0, -EINVAL);
-        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
 
         client->ifindex = ifindex;
         return 0;
@@ -228,9 +96,9 @@ int sd_dhcp6_client_set_local_address(
                 const struct in6_addr *local_address) {
 
         assert_return(client, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
         assert_return(local_address, -EINVAL);
         assert_return(in6_addr_is_link_local(local_address) > 0, -EINVAL);
-        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
 
         client->local_address = *local_address;
 
@@ -239,13 +107,16 @@ int sd_dhcp6_client_set_local_address(
 
 int sd_dhcp6_client_set_mac(
                 sd_dhcp6_client *client,
-                const uint8_t *addr, size_t addr_len,
+                const uint8_t *addr,
+                size_t addr_len,
                 uint16_t arp_type) {
 
         assert_return(client, -EINVAL);
         assert_return(addr, -EINVAL);
-        assert_return(addr_len <= MAX_MAC_ADDR_LEN, -EINVAL);
-        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(addr_len <= sizeof(client->hw_addr.bytes), -EINVAL);
+
+        /* Unlike the other setters, it is OK to set a new MAC address while the client is running,
+         * as the MAC address is used only when setting DUID or IAID. */
 
         if (arp_type == ARPHRD_ETHER)
                 assert_return(addr_len == ETH_ALEN, -EINVAL);
@@ -253,12 +124,12 @@ int sd_dhcp6_client_set_mac(
                 assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
         else {
                 client->arp_type = ARPHRD_NONE;
-                client->mac_addr_len = 0;
+                client->hw_addr.length = 0;
                 return 0;
         }
 
-        memcpy(&client->mac_addr, addr, addr_len);
-        client->mac_addr_len = addr_len;
+        memcpy(client->hw_addr.bytes, addr, addr_len);
+        client->hw_addr.length = addr_len;
         client->arp_type = arp_type;
 
         return 0;
@@ -267,25 +138,47 @@ int sd_dhcp6_client_set_mac(
 int sd_dhcp6_client_set_prefix_delegation_hint(
                 sd_dhcp6_client *client,
                 uint8_t prefixlen,
-                const struct in6_addr *pd_address) {
+                const struct in6_addr *pd_prefix) {
+
+        _cleanup_free_ DHCP6Address *prefix = NULL;
 
         assert_return(client, -EINVAL);
-        assert_return(pd_address, -EINVAL);
-        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+        if (!pd_prefix) {
+                /* clear previous assignments. */
+                dhcp6_ia_clear_addresses(&client->ia_pd);
+                return 0;
+        }
 
-        client->hint_pd_prefix.iapdprefix.address = *pd_address;
-        client->hint_pd_prefix.iapdprefix.prefixlen = prefixlen;
+        assert_return(prefixlen > 0 && prefixlen <= 128, -EINVAL);
 
-        return 0;
+        prefix = new(DHCP6Address, 1);
+        if (!prefix)
+                return -ENOMEM;
+
+        *prefix = (DHCP6Address) {
+                .iapdprefix.address = *pd_prefix,
+                .iapdprefix.prefixlen = prefixlen,
+        };
+
+        LIST_PREPEND(addresses, client->ia_pd.addresses, TAKE_PTR(prefix));
+        return 1;
 }
 
 int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
         int r;
 
         assert_return(client, -EINVAL);
-        assert_return(v, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+        if (!v) {
+                /* Clear the previous assignments. */
+                ordered_set_clear(client->vendor_options);
+                return 0;
+        }
 
-        r = ordered_hashmap_ensure_put(&client->vendor_options, &dhcp6_option_hash_ops, v, v);
+        r = ordered_set_ensure_put(&client->vendor_options, &dhcp6_option_hash_ops, v);
         if (r < 0)
                 return r;
 
@@ -295,10 +188,12 @@ 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)
                 return 0;
 
-        return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
+        return dhcp_identifier_set_duid_en(client->test_mode, &client->duid, &client->duid_len);
 }
 
 /**
@@ -308,15 +203,15 @@ static int client_ensure_duid(sd_dhcp6_client *client) {
  */
 static int dhcp6_client_set_duid_internal(
                 sd_dhcp6_client *client,
-                uint16_t duid_type,
+                DUIDType duid_type,
                 const void *duid,
                 size_t duid_len,
                 usec_t llt_time) {
         int r;
 
         assert_return(client, -EINVAL);
-        assert_return(duid_len == 0 || duid != NULL, -EINVAL);
-        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        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);
@@ -331,41 +226,23 @@ static int dhcp6_client_set_duid_internal(
                 client->duid.type = htobe16(duid_type);
                 memcpy(&client->duid.raw.data, duid, duid_len);
                 client->duid_len = sizeof(client->duid.type) + duid_len;
-        } else
-#if 0 /* NM_IGNORED */
-                switch (duid_type) {
-                case DUID_TYPE_LLT:
-                        if (client->mac_addr_len == 0)
-                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set.");
 
-                        r = dhcp_identifier_set_duid_llt(&client->duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
-                        if (r < 0)
-                                return log_dhcp6_client_errno(client, r, "Failed to set DUID-LLT: %m");
-                        break;
-                case DUID_TYPE_EN:
-                        r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
-                        if (r < 0)
-                                return log_dhcp6_client_errno(client, r, "Failed to set DUID-EN: %m");
-                        break;
-                case DUID_TYPE_LL:
-                        if (client->mac_addr_len == 0)
-                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set.");
-
-                        r = dhcp_identifier_set_duid_ll(&client->duid, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
-                        if (r < 0)
-                                return log_dhcp6_client_errno(client, r, "Failed to set DUID-LL: %m");
-                        break;
-                case DUID_TYPE_UUID:
-                        r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len);
-                        if (r < 0)
-                                return log_dhcp6_client_errno(client, r, "Failed to set DUID-UUID: %m");
-                        break;
-                default:
-                        return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type");
-                }
+        } else {
+#if 0 /* NM_IGNORED */
+                r = dhcp_identifier_set_duid(duid_type, client->hw_addr.bytes, client->hw_addr.length,
+                                             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 */
+        }
 
         return 0;
 }
@@ -384,14 +261,6 @@ int sd_dhcp6_client_set_duid_llt(
         return dhcp6_client_set_duid_internal(client, DUID_TYPE_LLT, NULL, 0, llt_time);
 }
 
-static const char* const dhcp6_duid_type_table[_DUID_TYPE_MAX] = {
-        [DUID_TYPE_LLT]  = "DUID-LLT",
-        [DUID_TYPE_EN]   = "DUID-EN/Vendor",
-        [DUID_TYPE_LL]   = "DUID-LL",
-        [DUID_TYPE_UUID] = "UUID",
-};
-DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_duid_type, DUIDType);
-
 int sd_dhcp6_client_duid_as_string(
                 sd_dhcp6_client *client,
                 char **duid) {
@@ -403,7 +272,7 @@ int sd_dhcp6_client_duid_as_string(
         assert_return(client->duid_len > 0, -ENODATA);
         assert_return(duid, -EINVAL);
 
-        v = dhcp6_duid_type_to_string(be16toh(client->duid.type));
+        v = duid_type_to_string(be16toh(client->duid.type));
         if (v) {
                 s = strdup(v);
                 if (!s)
@@ -429,19 +298,36 @@ int sd_dhcp6_client_duid_as_string(
 
 int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
         assert_return(client, -EINVAL);
-        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
 
-        client->ia_na.ia_na.id = htobe32(iaid);
-        client->ia_pd.ia_pd.id = htobe32(iaid);
+        client->ia_na.header.id = htobe32(iaid);
+        client->ia_pd.header.id = htobe32(iaid);
         client->iaid_set = true;
 
         return 0;
 }
 
-void dhcp6_client_set_test_mode(sd_dhcp6_client *client, bool test_mode) {
+static int client_ensure_iaid(sd_dhcp6_client *client) {
+        int r;
+        uint32_t iaid;
+
         assert(client);
 
-        client->test_mode = test_mode;
+        if (client->iaid_set)
+                return 0;
+
+        r = dhcp_identifier_set_iaid(client->ifindex, client->hw_addr.bytes, client->hw_addr.length,
+                                     /* legacy_unstable_byteorder = */ true,
+                                     /* use_mac = */ client->test_mode,
+                                     &iaid);
+        if (r < 0)
+                return r;
+
+        client->ia_na.header.id = iaid;
+        client->ia_pd.header.id = iaid;
+        client->iaid_set = true;
+
+        return 0;
 }
 
 int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) {
@@ -451,16 +337,23 @@ int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) {
         if (!client->iaid_set)
                 return -ENODATA;
 
-        *iaid = be32toh(client->ia_na.ia_na.id);
+        *iaid = be32toh(client->ia_na.header.id);
 
         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) {
 
         assert_return(client, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
 
         /* Make sure FQDN qualifies as DNS and as Linux hostname */
         if (fqdn &&
@@ -472,7 +365,7 @@ int sd_dhcp6_client_set_fqdn(
 
 int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) {
         assert_return(client, -EINVAL);
-        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
 
         client->information_request = enabled;
 
@@ -492,7 +385,7 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option)
         size_t t;
 
         assert_return(client, -EINVAL);
-        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
 
         if (!dhcp6_option_can_request(option))
                 return -EINVAL;
@@ -511,7 +404,7 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option)
 
 int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, const char *mudurl) {
         assert_return(client, -EINVAL);
-        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
         assert_return(mudurl, -EINVAL);
         assert_return(strlen(mudurl) <= UINT8_MAX, -EINVAL);
         assert_return(http_url_is_valid(mudurl), -EINVAL);
@@ -520,11 +413,10 @@ int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, const char *mud
 }
 
 int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client *client, char * const *user_class) {
-        char * const *p;
         char **s;
 
         assert_return(client, -EINVAL);
-        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
         assert_return(!strv_isempty(user_class), -EINVAL);
 
         STRV_FOREACH(p, user_class) {
@@ -542,11 +434,10 @@ int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client *client, char * const
 }
 
 int sd_dhcp6_client_set_request_vendor_class(sd_dhcp6_client *client, char * const *vendor_class) {
-        char * const *p;
         char **s;
 
         assert_return(client, -EINVAL);
-        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
         assert_return(!strv_isempty(vendor_class), -EINVAL);
 
         STRV_FOREACH(p, vendor_class) {
@@ -574,6 +465,7 @@ int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegati
 
 int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, int delegation) {
         assert_return(client, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
 
         SET_FLAG(client->request_ia, DHCP6_REQUEST_IA_PD, delegation);
 
@@ -591,16 +483,20 @@ int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request) {
 
 int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request) {
         assert_return(client, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
 
         SET_FLAG(client->request_ia, DHCP6_REQUEST_IA_NA, request);
 
         return 0;
 }
 
-int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) {
-        assert_return(client, -EINVAL);
+int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) {
+        assert(client);
+        assert(client->test_mode);
 
-        client->transaction_id = transaction_id;
+        /* This is for tests or fuzzers. */
+
+        client->transaction_id = transaction_id & htobe32(0x00ffffff);
 
         return 0;
 }
@@ -631,6 +527,18 @@ int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
         return 0;
 }
 
+static void client_set_state(sd_dhcp6_client *client, DHCP6State state) {
+        assert(client);
+
+        if (client->state == state)
+                return;
+
+        log_dhcp6_client(client, "State changed: %s -> %s",
+                         dhcp6_state_to_string(client->state), dhcp6_state_to_string(state));
+
+        client->state = state;
+}
+
 static void client_notify(sd_dhcp6_client *client, int event) {
         assert(client);
 
@@ -638,52 +546,122 @@ static void client_notify(sd_dhcp6_client *client, int event) {
                 client->callback(client, event, client->userdata);
 }
 
-static int client_reset(sd_dhcp6_client *client) {
-        assert(client);
+static void client_stop(sd_dhcp6_client *client, int error) {
+        DHCP6_CLIENT_DONT_DESTROY(client);
 
-        client->lease = sd_dhcp6_lease_unref(client->lease);
+        assert(client);
 
-        client->receive_message =
-                sd_event_source_unref(client->receive_message);
+        client_notify(client, error);
 
-        client->transaction_id = 0;
-        client->transaction_start = 0;
+        client->lease = sd_dhcp6_lease_unref(client->lease);
 
-        client->retransmit_time = 0;
-        client->retransmit_count = 0;
+        /* Reset IRT here. Otherwise, we cannot restart the client in the information requesting mode,
+         * even though the lease is freed below. */
+        client->information_request_time_usec = 0;
+        client->information_refresh_time_usec = 0;
 
+        (void) event_source_disable(client->receive_message);
         (void) event_source_disable(client->timeout_resend);
-        (void) event_source_disable(client->timeout_resend_expire);
+        (void) event_source_disable(client->timeout_expire);
         (void) event_source_disable(client->timeout_t1);
         (void) event_source_disable(client->timeout_t2);
 
-        client->state = DHCP6_STATE_STOPPED;
-
-        return 0;
+        client_set_state(client, DHCP6_STATE_STOPPED);
 }
 
-static void client_stop(sd_dhcp6_client *client, int error) {
-        DHCP6_CLIENT_DONT_DESTROY(client);
+static int client_append_common_options_in_managed_mode(
+                sd_dhcp6_client *client,
+                uint8_t **opt,
+                size_t *optlen,
+                const DHCP6IA *ia_na,
+                const DHCP6IA *ia_pd) {
+
+        int r;
 
         assert(client);
+        assert(IN_SET(client->state,
+                      DHCP6_STATE_SOLICITATION,
+                      DHCP6_STATE_REQUEST,
+                      DHCP6_STATE_RENEW,
+                      DHCP6_STATE_REBIND));
+        assert(opt);
+        assert(optlen);
+
+        if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA) && ia_na) {
+                r = dhcp6_option_append_ia(opt, optlen, ia_na);
+                if (r < 0)
+                        return r;
+        }
 
-        client_notify(client, error);
+        if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD) && ia_pd) {
+                r = dhcp6_option_append_ia(opt, optlen, ia_pd);
+                if (r < 0)
+                        return r;
+        }
 
-        client_reset(client);
+        if (client->fqdn) {
+                r = dhcp6_option_append_fqdn(opt, optlen, client->fqdn);
+                if (r < 0)
+                        return r;
+        }
+
+        if (client->user_class) {
+                r = dhcp6_option_append_user_class(opt, optlen, client->user_class);
+                if (r < 0)
+                        return r;
+        }
+
+        if (client->vendor_class) {
+                r = dhcp6_option_append_vendor_class(opt, optlen, client->vendor_class);
+                if (r < 0)
+                        return r;
+        }
+
+        if (!ordered_set_isempty(client->vendor_options)) {
+                r = dhcp6_option_append_vendor_option(opt, optlen, client->vendor_options);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
 }
 
-static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
+static DHCP6MessageType client_message_type_from_state(sd_dhcp6_client *client) {
+        assert(client);
+
+        switch (client->state) {
+        case DHCP6_STATE_INFORMATION_REQUEST:
+                return DHCP6_MESSAGE_INFORMATION_REQUEST;
+        case DHCP6_STATE_SOLICITATION:
+                return DHCP6_MESSAGE_SOLICIT;
+        case DHCP6_STATE_REQUEST:
+                return DHCP6_MESSAGE_REQUEST;
+        case DHCP6_STATE_RENEW:
+                return DHCP6_MESSAGE_RENEW;
+        case DHCP6_STATE_REBIND:
+                return DHCP6_MESSAGE_REBIND;
+        default:
+                assert_not_reached();
+        }
+}
+
+int dhcp6_client_send_message(sd_dhcp6_client *client) {
         _cleanup_free_ DHCP6Message *message = NULL;
         struct in6_addr all_servers =
                 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
         struct sd_dhcp6_option *j;
         size_t len, optlen = 512;
         uint8_t *opt;
-        int r;
-        usec_t elapsed_usec;
+        usec_t elapsed_usec, time_now;
         be16_t elapsed_time;
+        int r;
 
         assert(client);
+        assert(client->event);
+
+        r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
+        if (r < 0)
+                return r;
 
         len = sizeof(DHCP6Message) + optlen;
 
@@ -694,194 +672,56 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
         opt = (uint8_t *)(message + 1);
 
         message->transaction_id = client->transaction_id;
+        message->type = client_message_type_from_state(client);
 
-        switch(client->state) {
+        switch (client->state) {
         case DHCP6_STATE_INFORMATION_REQUEST:
-                message->type = DHCP6_MESSAGE_INFORMATION_REQUEST;
-
-                if (client->mudurl) {
-                        r = dhcp6_option_append(&opt, &optlen,
-                                                SD_DHCP6_OPTION_MUD_URL_V6, strlen(client->mudurl),
-                                                client->mudurl);
-                        if (r < 0)
-                                return r;
-                }
-
                 break;
 
         case DHCP6_STATE_SOLICITATION:
-                message->type = DHCP6_MESSAGE_SOLICIT;
-
-                r = dhcp6_option_append(&opt, &optlen,
-                                        SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
+                r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
                 if (r < 0)
                         return r;
 
-                if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA)) {
-                        r = dhcp6_option_append_ia(&opt, &optlen,
-                                                   &client->ia_na);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (client->fqdn) {
-                        r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (client->mudurl) {
-                        r = dhcp6_option_append(&opt, &optlen,
-                                                SD_DHCP6_OPTION_MUD_URL_V6, strlen(client->mudurl),
-                                                client->mudurl);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (client->user_class) {
-                        r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (client->vendor_class) {
-                        r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (!ordered_hashmap_isempty(client->vendor_options)) {
-                        r = dhcp6_option_append_vendor_option(&opt, &optlen,
-                                                       client->vendor_options);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD)) {
-                        r = dhcp6_option_append_pd(&opt, &optlen, &client->ia_pd, &client->hint_pd_prefix);
-                        if (r < 0)
-                                return r;
-                }
-
+                r = client_append_common_options_in_managed_mode(client, &opt, &optlen,
+                                                                 &client->ia_na, &client->ia_pd);
+                if (r < 0)
+                        return r;
                 break;
 
         case DHCP6_STATE_REQUEST:
         case DHCP6_STATE_RENEW:
 
-                if (client->state == DHCP6_STATE_REQUEST)
-                        message->type = DHCP6_MESSAGE_REQUEST;
-                else
-                        message->type = DHCP6_MESSAGE_RENEW;
-
                 r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_SERVERID,
                                         client->lease->serverid_len,
                                         client->lease->serverid);
                 if (r < 0)
                         return r;
 
-                if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) {
-                        r = dhcp6_option_append_ia(&opt, &optlen,
-                                                   &client->lease->ia);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (client->fqdn) {
-                        r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (client->mudurl) {
-                        r = dhcp6_option_append(&opt, &optlen,
-                                                SD_DHCP6_OPTION_MUD_URL_V6, strlen(client->mudurl),
-                                                client->mudurl);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (client->user_class) {
-                        r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (client->vendor_class) {
-                        r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (!ordered_hashmap_isempty(client->vendor_options)) {
-                        r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) {
-                        r = dhcp6_option_append_pd(&opt, &optlen, &client->lease->pd, NULL);
-                        if (r < 0)
-                                return r;
-                }
-
-                break;
-
+                _fallthrough_;
         case DHCP6_STATE_REBIND:
-                message->type = DHCP6_MESSAGE_REBIND;
-
-                if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA)) {
-                        r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (client->fqdn) {
-                        r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (client->mudurl) {
-                        r = dhcp6_option_append(&opt, &optlen,
-                                                SD_DHCP6_OPTION_MUD_URL_V6, strlen(client->mudurl),
-                                                client->mudurl);
-                        if (r < 0)
-                                return r;
-                }
 
-                if (client->user_class) {
-                        r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (client->vendor_class) {
-                        r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (!ordered_hashmap_isempty(client->vendor_options)) {
-                        r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options);
-                        if (r < 0)
-                                return r;
-                }
-
-                if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) {
-                        r = dhcp6_option_append_pd(&opt, &optlen, &client->lease->pd, NULL);
-                        if (r < 0)
-                                return r;
-                }
+                assert(client->lease);
 
+                r = client_append_common_options_in_managed_mode(client, &opt, &optlen,
+                                                                 client->lease->ia_na, client->lease->ia_pd);
+                if (r < 0)
+                        return r;
                 break;
 
         case DHCP6_STATE_STOPPED:
         case DHCP6_STATE_BOUND:
-                return -EINVAL;
         default:
                 assert_not_reached();
         }
 
+        if (client->mudurl) {
+                r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_MUD_URL_V6,
+                                        strlen(client->mudurl), client->mudurl);
+                if (r < 0)
+                        return r;
+        }
+
         r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ORO,
                                 client->req_opts_len * sizeof(be16_t),
                                 client->req_opts);
@@ -894,6 +734,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
         if (r < 0)
                 return r;
 
+        ORDERED_HASHMAP_FOREACH(j, client->extra_options) {
+                r = dhcp6_option_append(&opt, &optlen, j->option, j->length, j->data);
+                if (r < 0)
+                        return r;
+        }
+
         /* 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. */
@@ -903,12 +749,6 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
         if (r < 0)
                 return r;
 
-        ORDERED_HASHMAP_FOREACH(j, client->extra_options) {
-                r = dhcp6_option_append(&opt, &optlen, j->option, j->length, j->data);
-                if (r < 0)
-                        return r;
-        }
-
         r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
                                           len - optlen);
         if (r < 0)
@@ -920,100 +760,43 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
         return 0;
 }
 
-static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) {
-        sd_dhcp6_client *client = userdata;
-
-        assert(s);
-        assert(client);
-        assert(client->lease);
-
-        (void) event_source_disable(client->timeout_t2);
-
-        log_dhcp6_client(client, "Timeout T2");
-
-        client_start(client, DHCP6_STATE_REBIND);
-
-        return 0;
-}
-
-static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) {
-        sd_dhcp6_client *client = userdata;
-
-        assert(s);
-        assert(client);
-        assert(client->lease);
-
-        (void) event_source_disable(client->timeout_t1);
-
-        log_dhcp6_client(client, "Timeout T1");
-
-        client_start(client, DHCP6_STATE_RENEW);
-
-        return 0;
-}
-
-static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, void *userdata) {
-        sd_dhcp6_client *client = userdata;
-        DHCP6_CLIENT_DONT_DESTROY(client);
-        DHCP6State state;
-
-        assert(s);
-        assert(client);
-        assert(client->event);
-
-        state = client->state;
-
-        client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE);
-
-        /* RFC 3315, section 18.1.4., says that "...the client may choose to
-           use a Solicit message to locate a new DHCP server..." */
-        if (state == DHCP6_STATE_REBIND)
-                client_start(client, DHCP6_STATE_SOLICITATION);
-
-        return 0;
-}
-
 static usec_t client_timeout_compute_random(usec_t val) {
-        return val - (random_u32() % USEC_PER_SEC) * val / 10 / USEC_PER_SEC;
+        return usec_sub_unsigned(val, random_u64_range(val / 10));
 }
 
 static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) {
-        int r = 0;
-        sd_dhcp6_client *client = userdata;
-        usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0;
-        usec_t max_retransmit_duration = 0;
-        uint8_t max_retransmit_count = 0;
+        sd_dhcp6_client *client = ASSERT_PTR(userdata);
+        usec_t init_retransmit_time, max_retransmit_time;
+        int r;
 
-        assert(s);
-        assert(client);
         assert(client->event);
 
-        (void) event_source_disable(client->timeout_resend);
-
         switch (client->state) {
         case DHCP6_STATE_INFORMATION_REQUEST:
                 init_retransmit_time = DHCP6_INF_TIMEOUT;
                 max_retransmit_time = DHCP6_INF_MAX_RT;
-
                 break;
 
         case DHCP6_STATE_SOLICITATION:
 
                 if (client->retransmit_count > 0 && client->lease) {
-                        client_start(client, DHCP6_STATE_REQUEST);
+                        (void) client_start_transaction(client, DHCP6_STATE_REQUEST);
                         return 0;
                 }
 
                 init_retransmit_time = DHCP6_SOL_TIMEOUT;
                 max_retransmit_time = DHCP6_SOL_MAX_RT;
-
                 break;
 
         case DHCP6_STATE_REQUEST:
+
+                if (client->retransmit_count >= DHCP6_REQ_MAX_RC) {
+                        client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX);
+                        return 0;
+                }
+
                 init_retransmit_time = DHCP6_REQ_TIMEOUT;
                 max_retransmit_time = DHCP6_REQ_MAX_RT;
-                max_retransmit_count = DHCP6_REQ_MAX_RC;
-
                 break;
 
         case DHCP6_STATE_RENEW:
@@ -1023,422 +806,365 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
                 /* RFC 3315, section 18.1.3. says max retransmit duration will
                    be the remaining time until T2. Instead of setting MRD,
                    wait for T2 to trigger with the same end result */
-
                 break;
 
         case DHCP6_STATE_REBIND:
                 init_retransmit_time = DHCP6_REB_TIMEOUT;
                 max_retransmit_time = DHCP6_REB_MAX_RT;
 
-                if (event_source_is_enabled(client->timeout_resend_expire) <= 0) {
-                        uint32_t expire = 0;
-
-                        r = dhcp6_lease_ia_rebind_expire(&client->lease->ia, &expire);
-                        if (r < 0) {
-                                client_stop(client, r);
-                                return 0;
-                        }
-                        max_retransmit_duration = expire * USEC_PER_SEC;
-                }
-
+                /* Also, instead of setting MRD, the expire timer is already set in client_enter_bound_state(). */
                 break;
 
         case DHCP6_STATE_STOPPED:
         case DHCP6_STATE_BOUND:
-                return 0;
         default:
                 assert_not_reached();
         }
 
-        if (max_retransmit_count > 0 &&
-            client->retransmit_count >= max_retransmit_count) {
-                client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX);
-                return 0;
-        }
-
-        r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
-        if (r < 0)
-                goto error;
-
-        r = client_send_message(client, time_now);
+        r = dhcp6_client_send_message(client);
         if (r >= 0)
                 client->retransmit_count++;
 
         if (client->retransmit_time == 0) {
-                client->retransmit_time =
-                        client_timeout_compute_random(init_retransmit_time);
+                client->retransmit_time = client_timeout_compute_random(init_retransmit_time);
 
                 if (client->state == DHCP6_STATE_SOLICITATION)
                         client->retransmit_time += init_retransmit_time / 10;
 
-        } else {
-                assert(max_retransmit_time > 0);
-                if (client->retransmit_time > max_retransmit_time / 2)
-                        client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
-                else
-                        client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
-        }
+        } else if (client->retransmit_time > max_retransmit_time / 2)
+                client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
+        else
+                client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
 
         log_dhcp6_client(client, "Next retransmission in %s",
                          FORMAT_TIMESPAN(client->retransmit_time, USEC_PER_SEC));
 
-        r = event_reset_time(client->event, &client->timeout_resend,
-                             clock_boottime_or_monotonic(),
-                             time_now + client->retransmit_time, 10 * USEC_PER_MSEC,
-                             client_timeout_resend, client,
-                             client->event_priority, "dhcp6-resend-timer", true);
-        if (r < 0)
-                goto error;
-
-        if (max_retransmit_duration > 0 && event_source_is_enabled(client->timeout_resend_expire) <= 0) {
-
-                log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
-                                 max_retransmit_duration / USEC_PER_SEC);
-
-                r = event_reset_time(client->event, &client->timeout_resend_expire,
-                                     clock_boottime_or_monotonic(),
-                                     time_now + max_retransmit_duration, USEC_PER_SEC,
-                                     client_timeout_resend_expire, client,
-                                     client->event_priority, "dhcp6-resend-expire-timer", true);
-                if (r < 0)
-                        goto error;
-        }
-
-error:
+        r = event_reset_time_relative(client->event, &client->timeout_resend,
+                                      CLOCK_BOOTTIME,
+                                      client->retransmit_time, 10 * USEC_PER_MSEC,
+                                      client_timeout_resend, client,
+                                      client->event_priority, "dhcp6-resend-timer", true);
         if (r < 0)
                 client_stop(client, r);
 
         return 0;
 }
 
-static int client_ensure_iaid(sd_dhcp6_client *client) {
+static int client_start_transaction(sd_dhcp6_client *client, DHCP6State state) {
         int r;
-        uint32_t iaid;
 
         assert(client);
+        assert(client->event);
 
-        if (client->iaid_set)
-                return 0;
-
-        r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len,
-                                     /* legacy_unstable_byteorder = */ true,
-                                     /* use_mac = */ client->test_mode,
-                                     &iaid);
-        if (r < 0)
-                return r;
-
-        client->ia_na.ia_na.id = iaid;
-        client->ia_pd.ia_pd.id = iaid;
-        client->iaid_set = true;
-
-        return 0;
-}
-
-int client_parse_message(
-                sd_dhcp6_client *client,
-                DHCP6Message *message,
-                size_t len,
-                sd_dhcp6_lease *lease) {
-
-        uint32_t lt_t1 = UINT32_MAX, lt_t2 = UINT32_MAX;
-        usec_t irt = IRT_DEFAULT;
-        int r;
-
-        assert(client);
-        assert(message);
-        assert(len >= sizeof(DHCP6Message));
-        assert(lease);
+        switch (state) {
+        case DHCP6_STATE_INFORMATION_REQUEST:
+        case DHCP6_STATE_SOLICITATION:
+                assert(client->state == DHCP6_STATE_STOPPED);
+                break;
+        case DHCP6_STATE_REQUEST:
+                assert(client->state == DHCP6_STATE_SOLICITATION);
+                break;
+        case DHCP6_STATE_RENEW:
+                assert(client->state == DHCP6_STATE_BOUND);
+                break;
+        case DHCP6_STATE_REBIND:
+                assert(IN_SET(client->state, DHCP6_STATE_BOUND, DHCP6_STATE_RENEW));
+                break;
+        case DHCP6_STATE_STOPPED:
+        case DHCP6_STATE_BOUND:
+        default:
+                assert_not_reached();
+        }
 
-        len -= sizeof(DHCP6Message);
-        for (size_t offset = 0; offset < len;) {
-                uint16_t optcode;
-                size_t optlen;
-                const uint8_t *optval;
+        client_set_state(client, state);
 
-                r = dhcp6_option_parse(message->options, len, &offset, &optcode, &optlen, &optval);
-                if (r < 0)
-                        return r;
+        client->retransmit_time = 0;
+        client->retransmit_count = 0;
+        client->transaction_id = random_u32() & htobe32(0x00ffffff);
 
-                switch (optcode) {
-                case SD_DHCP6_OPTION_CLIENTID:
-                        if (dhcp6_lease_get_clientid(lease, NULL, NULL) >= 0)
-                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple client IDs",
-                                                              dhcp6_message_type_to_string(message->type));
+        r = sd_event_now(client->event, CLOCK_BOOTTIME, &client->transaction_start);
+        if (r < 0)
+                goto error;
 
-                        r = dhcp6_lease_set_clientid(lease, optval, optlen);
-                        if (r < 0)
-                                return r;
+        r = event_reset_time(client->event, &client->timeout_resend,
+                             CLOCK_BOOTTIME,
+                             0, 0,
+                             client_timeout_resend, client,
+                             client->event_priority, "dhcp6-resend-timeout", true);
+        if (r < 0)
+                goto error;
 
-                        break;
+        r = sd_event_source_set_enabled(client->receive_message, SD_EVENT_ON);
+        if (r < 0)
+                goto error;
 
-                case SD_DHCP6_OPTION_SERVERID:
-                        if (dhcp6_lease_get_serverid(lease, NULL, NULL) >= 0)
-                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple server IDs",
-                                                              dhcp6_message_type_to_string(message->type));
+        return 0;
 
-                        r = dhcp6_lease_set_serverid(lease, optval, optlen);
-                        if (r < 0)
-                                return r;
+error:
+        client_stop(client, r);
+        return r;
+}
 
-                        break;
+static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_dhcp6_client *client = ASSERT_PTR(userdata);
+        DHCP6_CLIENT_DONT_DESTROY(client);
+        DHCP6State state;
 
-                case SD_DHCP6_OPTION_PREFERENCE:
-                        if (optlen != 1)
-                                return -EINVAL;
+        (void) event_source_disable(client->timeout_expire);
+        (void) event_source_disable(client->timeout_t2);
+        (void) event_source_disable(client->timeout_t1);
 
-                        r = dhcp6_lease_set_preference(lease, optval[0]);
-                        if (r < 0)
-                                return r;
+        state = client->state;
 
-                        break;
+        client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE);
 
-                case SD_DHCP6_OPTION_STATUS_CODE: {
-                        _cleanup_free_ char *msg = NULL;
+        /* RFC 3315, section 18.1.4., says that "...the client may choose to
+           use a Solicit message to locate a new DHCP server..." */
+        if (state == DHCP6_STATE_REBIND)
+                (void) client_start_transaction(client, DHCP6_STATE_SOLICITATION);
 
-                        r = dhcp6_option_parse_status(optval, optlen, &msg);
-                        if (r < 0)
-                                return r;
-
-                        if (r > 0)
-                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                                              "Received %s message with non-zero status: %s%s%s",
-                                                              dhcp6_message_type_to_string(message->type),
-                                                              strempty(msg), isempty(msg) ? "" : ": ",
-                                                              dhcp6_message_status_to_string(r));
-                        break;
-                }
-                case SD_DHCP6_OPTION_IA_NA: {
-                        _cleanup_(dhcp6_lease_free_ia) DHCP6IA ia = {};
+        return 0;
+}
 
-                        if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
-                                log_dhcp6_client(client, "Ignoring IA NA option in information requesting mode.");
-                                break;
-                        }
+static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_dhcp6_client *client = ASSERT_PTR(userdata);
 
-                        r = dhcp6_option_parse_ia(client, client->ia_pd.ia_na.id, optcode, optlen, optval, &ia);
-                        if (r == -ENOMEM)
-                                return r;
-                        if (r < 0)
-                                continue;
+        (void) event_source_disable(client->timeout_t2);
+        (void) event_source_disable(client->timeout_t1);
 
-                        if (lease->ia.addresses) {
-                                log_dhcp6_client(client, "Received duplicate matching IA_NA option, ignoring.");
-                                continue;
-                        }
+        log_dhcp6_client(client, "Timeout T2");
 
-                        lease->ia = ia;
-                        ia = (DHCP6IA) {};
+        (void) client_start_transaction(client, DHCP6_STATE_REBIND);
 
-                        lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1));
-                        lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t2));
+        return 0;
+}
 
-                        break;
-                }
-                case SD_DHCP6_OPTION_IA_PD: {
-                        _cleanup_(dhcp6_lease_free_ia) DHCP6IA ia = {};
+static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_dhcp6_client *client = ASSERT_PTR(userdata);
 
-                        if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
-                                log_dhcp6_client(client, "Ignoring IA PD option in information requesting mode.");
-                                break;
-                        }
+        (void) event_source_disable(client->timeout_t1);
 
-                        r = dhcp6_option_parse_ia(client, client->ia_pd.ia_pd.id, optcode, optlen, optval, &ia);
-                        if (r == -ENOMEM)
-                                return r;
-                        if (r < 0)
-                                continue;
+        log_dhcp6_client(client, "Timeout T1");
 
-                        if (lease->pd.addresses) {
-                                log_dhcp6_client(client, "Received duplicate matching IA_PD option, ignoring.");
-                                continue;
-                        }
+        (void) client_start_transaction(client, DHCP6_STATE_RENEW);
 
-                        lease->pd = ia;
-                        ia = (DHCP6IA) {};
+        return 0;
+}
 
-                        lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1));
-                        lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2));
+static int client_enter_bound_state(sd_dhcp6_client *client) {
+        usec_t lifetime_t1, lifetime_t2, lifetime_valid;
+        int r;
 
-                        break;
-                }
-                case SD_DHCP6_OPTION_RAPID_COMMIT:
-                        r = dhcp6_lease_set_rapid_commit(lease);
-                        if (r < 0)
-                                return r;
+        assert(client);
+        assert(client->lease);
+        assert(IN_SET(client->state,
+                      DHCP6_STATE_SOLICITATION,
+                      DHCP6_STATE_REQUEST,
+                      DHCP6_STATE_RENEW,
+                      DHCP6_STATE_REBIND));
 
-                        break;
+        (void) event_source_disable(client->receive_message);
+        (void) event_source_disable(client->timeout_resend);
 
-                case SD_DHCP6_OPTION_DNS_SERVERS:
-                        r = dhcp6_lease_add_dns(lease, optval, optlen);
-                        if (r < 0)
-                                log_dhcp6_client_errno(client, r, "Failed to parse DNS server option, ignoring: %m");
+        r = dhcp6_lease_get_lifetime(client->lease, &lifetime_t1, &lifetime_t2, &lifetime_valid);
+        if (r < 0)
+                goto error;
 
-                        break;
+        lifetime_t2 = client_timeout_compute_random(lifetime_t2);
+        lifetime_t1 = client_timeout_compute_random(MIN(lifetime_t1, lifetime_t2));
 
-                case SD_DHCP6_OPTION_DOMAIN_LIST:
-                        r = dhcp6_lease_add_domains(lease, optval, optlen);
-                        if (r < 0)
-                                log_dhcp6_client_errno(client, r, "Failed to parse domain list option, ignoring: %m");
+        if (lifetime_t1 == USEC_INFINITY) {
+                log_dhcp6_client(client, "Infinite T1");
+                event_source_disable(client->timeout_t1);
+        } else {
+                log_dhcp6_client(client, "T1 expires in %s", FORMAT_TIMESPAN(lifetime_t1, USEC_PER_SEC));
+                r = event_reset_time_relative(client->event, &client->timeout_t1,
+                                              CLOCK_BOOTTIME,
+                                              lifetime_t1, 10 * USEC_PER_SEC,
+                                              client_timeout_t1, client,
+                                              client->event_priority, "dhcp6-t1-timeout", true);
+                if (r < 0)
+                        goto error;
+        }
 
-                        break;
+        if (lifetime_t2 == USEC_INFINITY) {
+                log_dhcp6_client(client, "Infinite T2");
+                event_source_disable(client->timeout_t2);
+        } else {
+                log_dhcp6_client(client, "T2 expires in %s", FORMAT_TIMESPAN(lifetime_t2, USEC_PER_SEC));
+                r = event_reset_time_relative(client->event, &client->timeout_t2,
+                                              CLOCK_BOOTTIME,
+                                              lifetime_t2, 10 * USEC_PER_SEC,
+                                              client_timeout_t2, client,
+                                              client->event_priority, "dhcp6-t2-timeout", true);
+                if (r < 0)
+                        goto error;
+        }
 
-                case SD_DHCP6_OPTION_NTP_SERVER:
-                        r = dhcp6_lease_add_ntp(lease, optval, optlen);
-                        if (r < 0)
-                                log_dhcp6_client_errno(client, r, "Failed to parse NTP server option, ignoring: %m");
+        if (lifetime_valid == USEC_INFINITY) {
+                log_dhcp6_client(client, "Infinite valid lifetime");
+                event_source_disable(client->timeout_expire);
+        } else {
+                log_dhcp6_client(client, "Valid lifetime expires in %s", FORMAT_TIMESPAN(lifetime_valid, USEC_PER_SEC));
 
-                        break;
+                r = event_reset_time_relative(client->event, &client->timeout_expire,
+                                              CLOCK_BOOTTIME,
+                                              lifetime_valid, USEC_PER_SEC,
+                                              client_timeout_expire, client,
+                                              client->event_priority, "dhcp6-lease-expire", true);
+                if (r < 0)
+                        goto error;
+        }
 
-                case SD_DHCP6_OPTION_SNTP_SERVERS:
-                        r = dhcp6_lease_add_sntp(lease, optval, optlen);
-                        if (r < 0)
-                                log_dhcp6_client_errno(client, r, "Failed to parse SNTP server option, ignoring: %m");
+        client_set_state(client, DHCP6_STATE_BOUND);
+        client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE);
+        return 0;
 
-                        break;
+error:
+        client_stop(client, r);
+        return r;
+}
 
-                case SD_DHCP6_OPTION_CLIENT_FQDN:
-                        r = dhcp6_lease_set_fqdn(lease, optval, optlen);
-                        if (r < 0)
-                                log_dhcp6_client_errno(client, r, "Failed to parse FQDN option, ignoring: %m");
+static int log_invalid_message_type(sd_dhcp6_client *client, const DHCP6Message *message) {
+        const char *type_str;
 
-                        break;
+        assert(client);
+        assert(message);
 
-                case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
-                        if (optlen != 4)
-                                return -EINVAL;
+        type_str = dhcp6_message_type_to_string(message->type);
+        if (type_str)
+                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+                                              "Received unexpected %s message, ignoring.", type_str);
+        else
+                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+                                              "Received unsupported message type %u, ignoring.", message->type);
+}
 
-                        irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC;
-                        break;
-                }
-        }
+static int client_process_information(
+                sd_dhcp6_client *client,
+                DHCP6Message *message,
+                size_t len,
+                const triple_timestamp *timestamp,
+                const struct in6_addr *server_address) {
 
-        uint8_t *clientid;
-        size_t clientid_len;
-        if (dhcp6_lease_get_clientid(lease, &clientid, &clientid_len) < 0)
-                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s message does not contain client ID. Ignoring.",
-                                              dhcp6_message_type_to_string(message->type));
+        _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+        int r;
 
-        if (clientid_len != client->duid_len ||
-            memcmp(clientid, &client->duid, clientid_len) != 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));
+        assert(client);
+        assert(message);
 
-        if (client->state != DHCP6_STATE_INFORMATION_REQUEST) {
-                r = dhcp6_lease_get_serverid(lease, NULL, NULL);
-                if (r < 0)
-                        return log_dhcp6_client_errno(client, r, "%s has no server id",
-                                                      dhcp6_message_type_to_string(message->type));
+        if (message->type != DHCP6_MESSAGE_REPLY)
+                return log_invalid_message_type(client, message);
 
-                if (!lease->ia.addresses && !lease->pd.addresses)
-                        return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "No IA_PD prefix or IA_NA address received. Ignoring.");
+        r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease);
+        if (r < 0)
+                return log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m");
 
-                if (lease->ia.addresses) {
-                        lease->ia.ia_na.lifetime_t1 = htobe32(lt_t1);
-                        lease->ia.ia_na.lifetime_t2 = htobe32(lt_t2);
-                }
+        log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
 
-                if (lease->pd.addresses) {
-                        lease->pd.ia_pd.lifetime_t1 = htobe32(lt_t1);
-                        lease->pd.ia_pd.lifetime_t2 = htobe32(lt_t2);
-                }
-        }
+        sd_dhcp6_lease_unref(client->lease);
+        client->lease = TAKE_PTR(lease);
 
-        client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM);
+        /* Do not call client_stop() here, as it frees the acquired lease. */
+        (void) event_source_disable(client->receive_message);
+        (void) event_source_disable(client->timeout_resend);
+        client_set_state(client, DHCP6_STATE_STOPPED);
 
+        client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST);
         return 0;
 }
 
-static int client_receive_reply(
+static int client_process_reply(
                 sd_dhcp6_client *client,
-                DHCP6Message *reply,
+                DHCP6Message *message,
                 size_t len,
-                const triple_timestamp *t,
+                const triple_timestamp *timestamp,
                 const struct in6_addr *server_address) {
 
         _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
-        bool rapid_commit;
         int r;
 
         assert(client);
-        assert(reply);
-        assert(t);
+        assert(message);
 
-        if (reply->type != DHCP6_MESSAGE_REPLY)
-                return 0;
+        if (message->type != DHCP6_MESSAGE_REPLY)
+                return log_invalid_message_type(client, message);
 
-        r = dhcp6_lease_new(&lease);
+        r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease);
         if (r < 0)
-                return -ENOMEM;
+                return log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m");
 
-        lease->timestamp = *t;
-        if (server_address)
-                lease->server_address = *server_address;
-
-        r = client_parse_message(client, reply, len, lease);
-        if (r < 0)
-                return r;
-
-        if (client->state == DHCP6_STATE_SOLICITATION) {
-                r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit);
-                if (r < 0)
-                        return r;
-
-                if (!rapid_commit)
-                        return 0;
-        }
+        log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
 
         sd_dhcp6_lease_unref(client->lease);
         client->lease = TAKE_PTR(lease);
 
-        return DHCP6_STATE_BOUND;
+        return client_enter_bound_state(client);
 }
 
-static int client_receive_advertise(
+static int client_process_advertise_or_rapid_commit_reply(
                 sd_dhcp6_client *client,
-                DHCP6Message *advertise,
+                DHCP6Message *message,
                 size_t len,
-                const triple_timestamp *t,
+                const triple_timestamp *timestamp,
                 const struct in6_addr *server_address) {
 
         _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
-        uint8_t pref_advertise = 0, pref_lease = 0;
+        uint8_t pref_advertise, pref_lease = 0;
         int r;
 
         assert(client);
-        assert(advertise);
-        assert(t);
+        assert(message);
 
-        if (advertise->type != DHCP6_MESSAGE_ADVERTISE)
-                return 0;
+        if (!IN_SET(message->type, DHCP6_MESSAGE_ADVERTISE, DHCP6_MESSAGE_REPLY))
+                return log_invalid_message_type(client, message);
 
-        r = dhcp6_lease_new(&lease);
+        r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease);
         if (r < 0)
-                return r;
+                return log_dhcp6_client_errno(client, r, "Failed to process received %s message, ignoring: %m",
+                                              dhcp6_message_type_to_string(message->type));
 
-        lease->timestamp = *t;
-        if (server_address)
-                lease->server_address = *server_address;
+        if (message->type == DHCP6_MESSAGE_REPLY) {
+                bool rapid_commit;
 
-        r = client_parse_message(client, advertise, len, lease);
-        if (r < 0)
-                return r;
+                r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit);
+                if (r < 0)
+                        return r;
+
+                if (!rapid_commit)
+                        return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+                                                      "Received reply message without rapid commit flag, ignoring.");
+
+                log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
+
+                sd_dhcp6_lease_unref(client->lease);
+                client->lease = TAKE_PTR(lease);
+
+                return client_enter_bound_state(client);
+        }
 
         r = dhcp6_lease_get_preference(lease, &pref_advertise);
         if (r < 0)
                 return r;
 
-        r = dhcp6_lease_get_preference(client->lease, &pref_lease);
+        if (client->lease) {
+                r = dhcp6_lease_get_preference(client->lease, &pref_lease);
+                if (r < 0)
+                        return r;
+        }
 
-        if (r < 0 || pref_advertise > pref_lease) {
+        log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
+
+        if (!client->lease || pref_advertise > pref_lease) {
+                /* If this is the first advertise message or has higher preference, then save the lease. */
                 sd_dhcp6_lease_unref(client->lease);
                 client->lease = TAKE_PTR(lease);
-                r = 0;
         }
 
         if (pref_advertise == 255 || client->retransmit_count > 1)
-                r = DHCP6_STATE_REQUEST;
+                (void) client_start_transaction(client, DHCP6_STATE_REQUEST);
 
-        return r;
+        return 0;
 }
 
 static int client_receive_message(
@@ -1447,7 +1173,7 @@ static int client_receive_message(
                 revents,
                 void *userdata) {
 
-        sd_dhcp6_client *client = userdata;
+        sd_dhcp6_client *client = ASSERT_PTR(userdata);
         DHCP6_CLIENT_DONT_DESTROY(client);
         /* This needs to be initialized with zero. See #20741. */
         CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL) control = {};
@@ -1466,11 +1192,6 @@ static int client_receive_message(
         _cleanup_free_ DHCP6Message *message = NULL;
         struct in6_addr *server_address = NULL;
         ssize_t buflen, len;
-        int r = 0;
-
-        assert(s);
-        assert(client);
-        assert(client->event);
 
         buflen = next_datagram_size_fd(fd);
         if (buflen < 0) {
@@ -1517,236 +1238,43 @@ static int client_receive_message(
                         triple_timestamp_from_realtime(&t, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
         }
 
-        if (!triple_timestamp_is_set(&t))
-                triple_timestamp_get(&t);
-
-        if (!IN_SET(message->type, DHCP6_MESSAGE_ADVERTISE, DHCP6_MESSAGE_REPLY, DHCP6_MESSAGE_RECONFIGURE)) {
-                const char *type_str = dhcp6_message_type_to_string(message->type);
-                if (type_str)
-                        log_dhcp6_client(client, "Received unexpected %s message, ignoring.", type_str);
-                else
-                        log_dhcp6_client(client, "Received unsupported message type %u, ignoring.", message->type);
-                return 0;
-        }
-
         if (client->transaction_id != (message->transaction_id & htobe32(0x00ffffff)))
                 return 0;
 
         switch (client->state) {
         case DHCP6_STATE_INFORMATION_REQUEST:
-                r = client_receive_reply(client, message, len, &t, server_address);
-                if (r < 0) {
-                        log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m");
+                if (client_process_information(client, message, len, &t, server_address) < 0)
                         return 0;
-                }
-
-                client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST);
-
-                client_start(client, DHCP6_STATE_STOPPED);
-
                 break;
 
         case DHCP6_STATE_SOLICITATION:
-                r = client_receive_advertise(client, message, len, &t, server_address);
-                if (r < 0) {
-                        log_dhcp6_client_errno(client, r, "Failed to process received advertise message, ignoring: %m");
+                if (client_process_advertise_or_rapid_commit_reply(client, message, len, &t, server_address) < 0)
                         return 0;
-                }
-
-                if (r == DHCP6_STATE_REQUEST) {
-                        client_start(client, r);
-                        break;
-                }
+                break;
 
-                _fallthrough_; /* for Solicitation Rapid Commit option check */
         case DHCP6_STATE_REQUEST:
         case DHCP6_STATE_RENEW:
         case DHCP6_STATE_REBIND:
-
-                r = client_receive_reply(client, message, len, &t, server_address);
-                if (r < 0) {
-                        log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m");
+                if (client_process_reply(client, message, len, &t, server_address) < 0)
                         return 0;
-                }
-
-                if (r == DHCP6_STATE_BOUND) {
-                        r = client_start(client, DHCP6_STATE_BOUND);
-                        if (r < 0) {
-                                client_stop(client, r);
-                                return 0;
-                        }
-
-                        client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE);
-                }
-
                 break;
 
         case DHCP6_STATE_BOUND:
-
-                break;
-
         case DHCP6_STATE_STOPPED:
-                return 0;
         default:
                 assert_not_reached();
         }
 
-        log_dhcp6_client(client, "Recv %s",
-                         dhcp6_message_type_to_string(message->type));
-
         return 0;
 }
 
-static int client_get_lifetime(sd_dhcp6_client *client, uint32_t *lifetime_t1,
-                               uint32_t *lifetime_t2) {
-        assert_return(client, -EINVAL);
-        assert_return(client->lease, -EINVAL);
-
-        if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) {
-                *lifetime_t1 = be32toh(client->lease->ia.ia_na.lifetime_t1);
-                *lifetime_t2 = be32toh(client->lease->ia.ia_na.lifetime_t2);
-
-                return 0;
-        }
-
-        if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) {
-                *lifetime_t1 = be32toh(client->lease->pd.ia_pd.lifetime_t1);
-                *lifetime_t2 = be32toh(client->lease->pd.ia_pd.lifetime_t2);
-
-                return 0;
-        }
-
-        return -ENOMSG;
-}
-
-static int client_start(sd_dhcp6_client *client, DHCP6State state) {
-        int r;
-        usec_t timeout, time_now;
-        uint32_t lifetime_t1, lifetime_t2;
-
-        assert_return(client, -EINVAL);
-        assert_return(client->event, -EINVAL);
-        assert_return(client->ifindex > 0, -EINVAL);
-        assert_return(client->state != state, -EINVAL);
-
-        (void) event_source_disable(client->timeout_resend_expire);
-        (void) event_source_disable(client->timeout_resend);
-        client->retransmit_time = 0;
-        client->retransmit_count = 0;
-
-        r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
-        if (r < 0)
-                return r;
-
-        if (!client->receive_message) {
-                r = sd_event_add_io(client->event, &client->receive_message,
-                                    client->fd, EPOLLIN, client_receive_message,
-                                    client);
-                if (r < 0)
-                        goto error;
-
-                r = sd_event_source_set_priority(client->receive_message,
-                                                 client->event_priority);
-                if (r < 0)
-                        goto error;
-
-                r = sd_event_source_set_description(client->receive_message,
-                                                    "dhcp6-receive-message");
-                if (r < 0)
-                        goto error;
-        }
-
-        switch (state) {
-        case DHCP6_STATE_STOPPED:
-                if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
-                        client->state = DHCP6_STATE_STOPPED;
-
-                        return 0;
-                }
-
-                _fallthrough_;
-        case DHCP6_STATE_SOLICITATION:
-                client->state = DHCP6_STATE_SOLICITATION;
-
-                break;
-
-        case DHCP6_STATE_INFORMATION_REQUEST:
-        case DHCP6_STATE_REQUEST:
-        case DHCP6_STATE_RENEW:
-        case DHCP6_STATE_REBIND:
-
-                client->state = state;
-
-                break;
-
-        case DHCP6_STATE_BOUND:
-
-                r = client_get_lifetime(client, &lifetime_t1, &lifetime_t2);
-                if (r < 0)
-                        goto error;
-
-                if (lifetime_t1 == 0xffffffff || lifetime_t2 == 0xffffffff) {
-                        log_dhcp6_client(client, "Infinite T1 0x%08x or T2 0x%08x",
-                                         lifetime_t1, lifetime_t2);
-
-                        return 0;
-                }
-
-                timeout = client_timeout_compute_random(lifetime_t1 * USEC_PER_SEC);
-
-                log_dhcp6_client(client, "T1 expires in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC));
-
-                r = event_reset_time(client->event, &client->timeout_t1,
-                                     clock_boottime_or_monotonic(),
-                                     time_now + timeout, 10 * USEC_PER_SEC,
-                                     client_timeout_t1, client,
-                                     client->event_priority, "dhcp6-t1-timeout", true);
-                if (r < 0)
-                        goto error;
-
-                timeout = client_timeout_compute_random(lifetime_t2 * USEC_PER_SEC);
-
-                log_dhcp6_client(client, "T2 expires in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC));
-
-                r = event_reset_time(client->event, &client->timeout_t2,
-                                     clock_boottime_or_monotonic(),
-                                     time_now + timeout, 10 * USEC_PER_SEC,
-                                     client_timeout_t2, client,
-                                     client->event_priority, "dhcp6-t2-timeout", true);
-                if (r < 0)
-                        goto error;
-
-                client->state = state;
-
-                return 0;
-        default:
-                assert_not_reached();
-        }
-
-        client->transaction_id = random_u32() & htobe32(0x00ffffff);
-        client->transaction_start = time_now;
-
-        r = event_reset_time(client->event, &client->timeout_resend,
-                             clock_boottime_or_monotonic(),
-                             0, 0,
-                             client_timeout_resend, client,
-                             client->event_priority, "dhcp6-resend-timeout", true);
-        if (r < 0)
-                goto error;
-
-        return 0;
-
- error:
-        client_reset(client);
-        return r;
-}
-
 int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
         if (!client)
                 return 0;
 
         client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
 
+        client->receive_message = sd_event_source_unref(client->receive_message);
         client->fd = safe_close(client->fd);
 
         return 0;
@@ -1766,16 +1294,12 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) {
         assert_return(client->event, -EINVAL);
         assert_return(client->ifindex > 0, -EINVAL);
         assert_return(in6_addr_is_link_local(&client->local_address) > 0, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+        assert_return(client->information_request || client->request_ia != 0, -EINVAL);
 
-        if (client->state != DHCP6_STATE_STOPPED)
-                return -EBUSY;
-
-        if (!client->information_request && client->request_ia == 0)
-                return -EINVAL;
-
-        r = client_reset(client);
-        if (r < 0)
-                return r;
+        /* Even if the client is in the STOPPED state, the lease acquired in the previous information
+         * request may be stored. */
+        client->lease = sd_dhcp6_lease_unref(client->lease);
 
         r = client_ensure_iaid(client);
         if (r < 0)
@@ -1790,7 +1314,7 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) {
                 if (r < 0) {
                         _cleanup_free_ char *p = NULL;
 
-                        (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p);
+                        (void) in6_addr_to_string(&client->local_address, &p);
                         return log_dhcp6_client_errno(client, r,
                                                       "Failed to bind to UDP socket at address %s: %m", strna(p));
                 }
@@ -1798,6 +1322,24 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) {
                 client->fd = r;
         }
 
+        if (!client->receive_message) {
+                _cleanup_(sd_event_source_disable_unrefp) sd_event_source *s = NULL;
+
+                r = sd_event_add_io(client->event, &s, client->fd, EPOLLIN, client_receive_message, client);
+                if (r < 0)
+                        return r;
+
+                r = sd_event_source_set_priority(s, client->event_priority);
+                if (r < 0)
+                        return r;
+
+                r = sd_event_source_set_description(s, "dhcp6-receive-message");
+                if (r < 0)
+                        return r;
+
+                client->receive_message = TAKE_PTR(s);
+        }
+
         if (client->information_request) {
                 usec_t t = now(CLOCK_MONOTONIC);
 
@@ -1808,10 +1350,10 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) {
                 state = DHCP6_STATE_INFORMATION_REQUEST;
         }
 
-        log_dhcp6_client(client, "Started in %s mode",
+        log_dhcp6_client(client, "Starting in %s mode",
                          client->information_request ? "Information request" : "Managed");
 
-        return client_start(client, state);
+        return client_start_transaction(client, state);
 }
 
 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64_t priority) {
@@ -1819,6 +1361,7 @@ int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64
 
         assert_return(client, -EINVAL);
         assert_return(!client->event, -EBUSY);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
 
         if (event)
                 client->event = sd_event_ref(event);
@@ -1835,6 +1378,7 @@ int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64
 
 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
         assert_return(client, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
 
         client->event = sd_event_unref(client->event);
 
@@ -1848,24 +1392,26 @@ sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
 }
 
 static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
-        assert(client);
+        if (!client)
+                return NULL;
 
-        client->timeout_resend = sd_event_source_unref(client->timeout_resend);
-        client->timeout_resend_expire = sd_event_source_unref(client->timeout_resend_expire);
-        client->timeout_t1 = sd_event_source_unref(client->timeout_t1);
-        client->timeout_t2 = sd_event_source_unref(client->timeout_t2);
+        sd_dhcp6_lease_unref(client->lease);
 
-        client_reset(client);
+        sd_event_source_disable_unref(client->receive_message);
+        sd_event_source_disable_unref(client->timeout_resend);
+        sd_event_source_disable_unref(client->timeout_expire);
+        sd_event_source_disable_unref(client->timeout_t1);
+        sd_event_source_disable_unref(client->timeout_t2);
+        sd_event_unref(client->event);
 
         client->fd = safe_close(client->fd);
 
-        sd_dhcp6_client_detach_event(client);
-
         free(client->req_opts);
         free(client->fqdn);
         free(client->mudurl);
-
+        dhcp6_ia_clear_addresses(&client->ia_pd);
         ordered_hashmap_free(client->extra_options);
+        ordered_set_free(client->vendor_options);
         strv_free(client->user_class);
         strv_free(client->vendor_class);
         free(client->ifname);
@@ -1901,8 +1447,6 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret) {
                 .request_ia = DHCP6_REQUEST_IA_NA | DHCP6_REQUEST_IA_PD,
                 .fd = -1,
                 .req_opts_len = ELEMENTSOF(default_req_opts),
-                .hint_pd_prefix.iapdprefix.lifetime_preferred = (be32_t) -1,
-                .hint_pd_prefix.iapdprefix.lifetime_valid = (be32_t) -1,
                 .req_opts = TAKE_PTR(req_opts),
         };
 
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 b46a2e33..fd47cb73 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
@@ -8,10 +8,21 @@
 #include <errno.h>
 
 #include "alloc-util.h"
+#include "dhcp6-internal.h"
 #include "dhcp6-lease-internal.h"
-#include "dhcp6-protocol.h"
 #include "strv.h"
-#include "util.h"
+
+#define IRT_DEFAULT (1 * USEC_PER_DAY)
+#define IRT_MINIMUM (600 * USEC_PER_SEC)
+
+static void dhcp6_lease_set_timestamp(sd_dhcp6_lease *lease, const triple_timestamp *timestamp) {
+        assert(lease);
+
+        if (timestamp && triple_timestamp_is_set(timestamp))
+                lease->timestamp = *timestamp;
+        else
+                triple_timestamp_get(&lease->timestamp);
+}
 
 int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret) {
         assert_return(lease, -EINVAL);
@@ -26,63 +37,105 @@ int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_
         return 0;
 }
 
-int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *ret) {
-        assert_return(lease, -EINVAL);
-        assert_return(ret, -EINVAL);
-
-        *ret = lease->server_address;
-        return 0;
+static usec_t sec2usec(uint32_t sec) {
+        return sec == UINT32_MAX ? USEC_INFINITY : sec * USEC_PER_SEC;
 }
 
-int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) {
-        DHCP6Address *addr;
-        uint32_t valid = 0, t;
+static void dhcp6_lease_set_lifetime(sd_dhcp6_lease *lease) {
+        uint32_t t1 = UINT32_MAX, t2 = UINT32_MAX, min_valid_lt = UINT32_MAX;
+
+        assert(lease);
+        assert(lease->ia_na || lease->ia_pd);
 
-        assert_return(ia, -EINVAL);
-        assert_return(expire, -EINVAL);
+        if (lease->ia_na) {
+                t1 = MIN(t1, be32toh(lease->ia_na->header.lifetime_t1));
+                t2 = MIN(t2, be32toh(lease->ia_na->header.lifetime_t2));
 
-        LIST_FOREACH(addresses, addr, ia->addresses) {
-                t = be32toh(addr->iaaddr.lifetime_valid);
-                if (valid < t)
-                        valid = t;
+                LIST_FOREACH(addresses, a, lease->ia_na->addresses)
+                        min_valid_lt = MIN(min_valid_lt, be32toh(a->iaaddr.lifetime_valid));
         }
 
-        t = be32toh(ia->ia_na.lifetime_t2);
-        if (t > valid)
-                return -EINVAL;
+        if (lease->ia_pd) {
+                t1 = MIN(t1, be32toh(lease->ia_pd->header.lifetime_t1));
+                t2 = MIN(t2, be32toh(lease->ia_pd->header.lifetime_t2));
 
-        *expire = valid - t;
+                LIST_FOREACH(addresses, a, lease->ia_pd->addresses)
+                        min_valid_lt = MIN(min_valid_lt, be32toh(a->iapdprefix.lifetime_valid));
+        }
 
+        if (t2 == 0 || t2 > min_valid_lt) {
+                /* If T2 is zero or longer than the minimum valid lifetime of the addresses or prefixes,
+                 * then adjust lifetime with it. */
+                t1 = min_valid_lt / 2;
+                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);
+
+        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;
 }
 
-DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) {
-        DHCP6Address *address;
+static void dhcp6_lease_set_server_address(sd_dhcp6_lease *lease, const struct in6_addr *server_address) {
+        assert(lease);
 
-        if (!ia)
-                return NULL;
+        if (server_address)
+                lease->server_address = *server_address;
+        else
+                lease->server_address = (struct in6_addr) {};
+}
 
-        while (ia->addresses) {
-                address = ia->addresses;
+int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *ret) {
+        assert_return(lease, -EINVAL);
+        assert_return(ret, -EINVAL);
 
-                LIST_REMOVE(addresses, ia->addresses, address);
+        *ret = lease->server_address;
+        return 0;
+}
 
-                free(address);
-        }
+void dhcp6_ia_clear_addresses(DHCP6IA *ia) {
+        assert(ia);
+
+        LIST_FOREACH(addresses, a, ia->addresses)
+                free(a);
+
+        ia->addresses = NULL;
+}
+
+DHCP6IA *dhcp6_ia_free(DHCP6IA *ia) {
+        if (!ia)
+                return NULL;
+
+        dhcp6_ia_clear_addresses(ia);
 
-        return NULL;
+        return mfree(ia);
 }
 
 int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) {
-        uint8_t *clientid;
+        uint8_t *clientid = NULL;
 
-        assert_return(lease, -EINVAL);
-        assert_return(id, -EINVAL);
-        assert_return(len > 0, -EINVAL);
+        assert(lease);
+        assert(id || len == 0);
 
-        clientid = memdup(id, len);
-        if (!clientid)
-                return -ENOMEM;
+        if (len > 0) {
+                clientid = memdup(id, len);
+                if (!clientid)
+                        return -ENOMEM;
+        }
 
         free_and_replace(lease->clientid, clientid);
         lease->clientid_len = len;
@@ -91,7 +144,7 @@ int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t le
 }
 
 int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) {
-        assert_return(lease, -EINVAL);
+        assert(lease);
 
         if (!lease->clientid)
                 return -ENODATA;
@@ -105,15 +158,16 @@ int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *re
 }
 
 int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) {
-        uint8_t *serverid;
+        uint8_t *serverid = NULL;
 
-        assert_return(lease, -EINVAL);
-        assert_return(id, -EINVAL);
-        assert_return(len > 0, -EINVAL);
+        assert(lease);
+        assert(id || len == 0);
 
-        serverid = memdup(id, len);
-        if (!serverid)
-                return -ENOMEM;
+        if (len > 0) {
+                serverid = memdup(id, len);
+                if (!serverid)
+                        return -ENOMEM;
+        }
 
         free_and_replace(lease->serverid, serverid);
         lease->serverid_len = len;
@@ -122,7 +176,7 @@ int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t le
 }
 
 int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) {
-        assert_return(lease, -EINVAL);
+        assert(lease);
 
         if (!lease->serverid)
                 return -ENODATA;
@@ -131,107 +185,99 @@ int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *re
                 *ret_id = lease->serverid;
         if (ret_len)
                 *ret_len = lease->serverid_len;
-
         return 0;
 }
 
 int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) {
-        assert_return(lease, -EINVAL);
+        assert(lease);
 
         lease->preference = preference;
-
         return 0;
 }
 
-int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference) {
-        assert_return(preference, -EINVAL);
-
-        if (!lease)
-                return -EINVAL;
-
-        *preference = lease->preference;
+int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *ret) {
+        assert(lease);
+        assert(ret);
 
+        *ret = lease->preference;
         return 0;
 }
 
 int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) {
-        assert_return(lease, -EINVAL);
+        assert(lease);
 
         lease->rapid_commit = true;
-
         return 0;
 }
 
-int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit) {
-        assert_return(lease, -EINVAL);
-        assert_return(rapid_commit, -EINVAL);
-
-        *rapid_commit = lease->rapid_commit;
+int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *ret) {
+        assert(lease);
+        assert(ret);
 
+        *ret = lease->rapid_commit;
         return 0;
 }
 
-int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *addr,
-                               uint32_t *lifetime_preferred,
-                               uint32_t *lifetime_valid) {
+int sd_dhcp6_lease_get_address(
+                sd_dhcp6_lease *lease,
+                struct in6_addr *ret_addr,
+                uint32_t *ret_lifetime_preferred,
+                uint32_t *ret_lifetime_valid) {
+
         assert_return(lease, -EINVAL);
-        assert_return(addr, -EINVAL);
-        assert_return(lifetime_preferred, -EINVAL);
-        assert_return(lifetime_valid, -EINVAL);
 
         if (!lease->addr_iter)
-                return -ENOMSG;
+                return -ENODATA;
 
-        memcpy(addr, &lease->addr_iter->iaaddr.address,
-                sizeof(struct in6_addr));
-        *lifetime_preferred =
-                be32toh(lease->addr_iter->iaaddr.lifetime_preferred);
-        *lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid);
+        if (ret_addr)
+                *ret_addr = lease->addr_iter->iaaddr.address;
+        if (ret_lifetime_preferred)
+                *ret_lifetime_preferred = be32toh(lease->addr_iter->iaaddr.lifetime_preferred);
+        if (ret_lifetime_valid)
+                *ret_lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid);
 
         lease->addr_iter = lease->addr_iter->addresses_next;
-
         return 0;
 }
 
 void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) {
         if (lease)
-                lease->addr_iter = lease->ia.addresses;
+                lease->addr_iter = lease->ia_na ? lease->ia_na->addresses : NULL;
 }
 
-int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix,
-                          uint8_t *prefix_len,
-                          uint32_t *lifetime_preferred,
-                          uint32_t *lifetime_valid) {
+int sd_dhcp6_lease_get_pd(
+                sd_dhcp6_lease *lease,
+                struct in6_addr *ret_prefix,
+                uint8_t *ret_prefix_len,
+                uint32_t *ret_lifetime_preferred,
+                uint32_t *ret_lifetime_valid) {
+
         assert_return(lease, -EINVAL);
-        assert_return(prefix, -EINVAL);
-        assert_return(prefix_len, -EINVAL);
-        assert_return(lifetime_preferred, -EINVAL);
-        assert_return(lifetime_valid, -EINVAL);
 
         if (!lease->prefix_iter)
-                return -ENOMSG;
+                return -ENODATA;
 
-        memcpy(prefix, &lease->prefix_iter->iapdprefix.address,
-               sizeof(struct in6_addr));
-        *prefix_len = lease->prefix_iter->iapdprefix.prefixlen;
-        *lifetime_preferred =
-                be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred);
-        *lifetime_valid =
-                be32toh(lease->prefix_iter->iapdprefix.lifetime_valid);
+        if (ret_prefix)
+                *ret_prefix = lease->prefix_iter->iapdprefix.address;
+        if (ret_prefix_len)
+                *ret_prefix_len = lease->prefix_iter->iapdprefix.prefixlen;
+        if (ret_lifetime_preferred)
+                *ret_lifetime_preferred = be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred);
+        if (ret_lifetime_valid)
+                *ret_lifetime_valid = be32toh(lease->prefix_iter->iapdprefix.lifetime_valid);
 
         lease->prefix_iter = lease->prefix_iter->addresses_next;
-
         return 0;
 }
 
 void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) {
         if (lease)
-                lease->prefix_iter = lease->pd.addresses;
+                lease->prefix_iter = lease->ia_pd ? lease->ia_pd->addresses : NULL;
 }
 
 int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
-        assert_return(lease, -EINVAL);
-        assert_return(optval, -EINVAL);
+        assert(lease);
+        assert(optval || optlen == 0);
 
         if (optlen == 0)
                 return 0;
@@ -243,7 +289,7 @@ int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret) {
         assert_return(lease, -EINVAL);
 
         if (!lease->dns)
-                return -ENOENT;
+                return -ENODATA;
 
         if (ret)
                 *ret = lease->dns;
@@ -255,8 +301,8 @@ int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t
         _cleanup_strv_free_ char **domains = NULL;
         int r;
 
-        assert_return(lease, -EINVAL);
-        assert_return(optval, -EINVAL);
+        assert(lease);
+        assert(optval || optlen == 0);
 
         if (optlen == 0)
                 return 0;
@@ -273,7 +319,7 @@ int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) {
         assert_return(ret, -EINVAL);
 
         if (!lease->domains)
-                return -ENOENT;
+                return -ENODATA;
 
         *ret = lease->domains;
         return strv_length(lease->domains);
@@ -282,8 +328,8 @@ int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) {
 int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
         int r;
 
-        assert_return(lease, -EINVAL);
-        assert_return(optval, -EINVAL);
+        assert(lease);
+        assert(optval || optlen == 0);
 
         for (size_t offset = 0; offset < optlen;) {
                 const uint8_t *subval;
@@ -294,11 +340,11 @@ int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t opt
                 if (r < 0)
                         return r;
 
-                switch(subopt) {
+                switch (subopt) {
                 case DHCP6_NTP_SUBOPTION_SRV_ADDR:
                 case DHCP6_NTP_SUBOPTION_MC_ADDR:
                         if (sublen != 16)
-                                return 0;
+                                return -EINVAL;
 
                         r = dhcp6_option_parse_addresses(subval, sublen, &lease->ntp, &lease->ntp_count);
                         if (r < 0)
@@ -328,8 +374,8 @@ int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t opt
 }
 
 int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
-        assert_return(lease, -EINVAL);
-        assert_return(optval, -EINVAL);
+        assert(lease);
+        assert(optval || optlen == 0);
 
         if (optlen == 0)
                 return 0;
@@ -354,14 +400,14 @@ int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **
                 return lease->sntp_count;
         }
 
-        return -ENOENT;
+        return -ENODATA;
 }
 
 int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) {
         assert_return(lease, -EINVAL);
 
         if (!lease->ntp_fqdn)
-                return -ENOENT;
+                return -ENODATA;
 
         if (ret)
                 *ret = lease->ntp_fqdn;
@@ -369,11 +415,14 @@ int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) {
 }
 
 int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
-        int r;
         char *fqdn;
+        int r;
 
-        assert_return(lease, -EINVAL);
-        assert_return(optval, -EINVAL);
+        assert(lease);
+        assert(optval || optlen == 0);
+
+        if (optlen == 0)
+                return 0;
 
         if (optlen < 2)
                 return -ENODATA;
@@ -392,20 +441,222 @@ int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret) {
         assert_return(ret, -EINVAL);
 
         if (!lease->fqdn)
-                return -ENOENT;
+                return -ENODATA;
 
         *ret = lease->fqdn;
         return 0;
 }
 
+static int dhcp6_lease_parse_message(
+                sd_dhcp6_client *client,
+                sd_dhcp6_lease *lease,
+                const DHCP6Message *message,
+                size_t len) {
+
+        usec_t irt = IRT_DEFAULT;
+        int r;
+
+        assert(client);
+        assert(lease);
+        assert(message);
+        assert(len >= sizeof(DHCP6Message));
+
+        len -= sizeof(DHCP6Message);
+        for (size_t offset = 0; offset < len;) {
+                uint16_t optcode;
+                size_t optlen;
+                const uint8_t *optval;
+
+                r = dhcp6_option_parse(message->options, len, &offset, &optcode, &optlen, &optval);
+                if (r < 0)
+                        return r;
+
+                switch (optcode) {
+                case SD_DHCP6_OPTION_CLIENTID:
+                        if (dhcp6_lease_get_clientid(lease, NULL, NULL) >= 0)
+                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple client IDs",
+                                                              dhcp6_message_type_to_string(message->type));
+
+                        r = dhcp6_lease_set_clientid(lease, optval, optlen);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_SERVERID:
+                        if (dhcp6_lease_get_serverid(lease, NULL, NULL) >= 0)
+                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple server IDs",
+                                                              dhcp6_message_type_to_string(message->type));
+
+                        r = dhcp6_lease_set_serverid(lease, optval, optlen);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_PREFERENCE:
+                        if (optlen != 1)
+                                return -EINVAL;
+
+                        r = dhcp6_lease_set_preference(lease, optval[0]);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_STATUS_CODE: {
+                        _cleanup_free_ char *msg = NULL;
+
+                        r = dhcp6_option_parse_status(optval, optlen, &msg);
+                        if (r < 0)
+                                return r;
+
+                        if (r > 0)
+                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+                                                              "Received %s message with non-zero status: %s%s%s",
+                                                              dhcp6_message_type_to_string(message->type),
+                                                              strempty(msg), isempty(msg) ? "" : ": ",
+                                                              dhcp6_message_status_to_string(r));
+                        break;
+                }
+                case SD_DHCP6_OPTION_IA_NA: {
+                        _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
+
+                        if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+                                log_dhcp6_client(client, "Ignoring IA NA option in information requesting mode.");
+                                break;
+                        }
+
+                        r = dhcp6_option_parse_ia(client, client->ia_na.header.id, optcode, optlen, optval, &ia);
+                        if (r == -ENOMEM)
+                                return r;
+                        if (r < 0)
+                                continue;
+
+                        if (lease->ia_na) {
+                                log_dhcp6_client(client, "Received duplicate matching IA_NA option, ignoring.");
+                                continue;
+                        }
+
+                        dhcp6_ia_free(lease->ia_na);
+                        lease->ia_na = TAKE_PTR(ia);
+                        break;
+                }
+                case SD_DHCP6_OPTION_IA_PD: {
+                        _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
+
+                        if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+                                log_dhcp6_client(client, "Ignoring IA PD option in information requesting mode.");
+                                break;
+                        }
+
+                        r = dhcp6_option_parse_ia(client, client->ia_pd.header.id, optcode, optlen, optval, &ia);
+                        if (r == -ENOMEM)
+                                return r;
+                        if (r < 0)
+                                continue;
+
+                        if (lease->ia_pd) {
+                                log_dhcp6_client(client, "Received duplicate matching IA_PD option, ignoring.");
+                                continue;
+                        }
+
+                        dhcp6_ia_free(lease->ia_pd);
+                        lease->ia_pd = TAKE_PTR(ia);
+                        break;
+                }
+                case SD_DHCP6_OPTION_RAPID_COMMIT:
+                        r = dhcp6_lease_set_rapid_commit(lease);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case SD_DHCP6_OPTION_DNS_SERVERS:
+                        r = dhcp6_lease_add_dns(lease, optval, optlen);
+                        if (r < 0)
+                                log_dhcp6_client_errno(client, r, "Failed to parse DNS server option, ignoring: %m");
+
+                        break;
+
+                case SD_DHCP6_OPTION_DOMAIN_LIST:
+                        r = dhcp6_lease_add_domains(lease, optval, optlen);
+                        if (r < 0)
+                                log_dhcp6_client_errno(client, r, "Failed to parse domain list option, ignoring: %m");
+
+                        break;
+
+                case SD_DHCP6_OPTION_NTP_SERVER:
+                        r = dhcp6_lease_add_ntp(lease, optval, optlen);
+                        if (r < 0)
+                                log_dhcp6_client_errno(client, r, "Failed to parse NTP server option, ignoring: %m");
+
+                        break;
+
+                case SD_DHCP6_OPTION_SNTP_SERVERS:
+                        r = dhcp6_lease_add_sntp(lease, optval, optlen);
+                        if (r < 0)
+                                log_dhcp6_client_errno(client, r, "Failed to parse SNTP server option, ignoring: %m");
+
+                        break;
+
+                case SD_DHCP6_OPTION_CLIENT_FQDN:
+                        r = dhcp6_lease_set_fqdn(lease, optval, optlen);
+                        if (r < 0)
+                                log_dhcp6_client_errno(client, r, "Failed to parse FQDN option, ignoring: %m");
+
+                        break;
+
+                case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
+                        if (optlen != 4)
+                                return -EINVAL;
+
+                        irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC;
+                        break;
+                }
+        }
+
+        uint8_t *clientid;
+        size_t clientid_len;
+        if (dhcp6_lease_get_clientid(lease, &clientid, &clientid_len) < 0)
+                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+                                              "%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)
+                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));
+
+        if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+                client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM);
+                log_dhcp6_client(client, "New information request will be refused in %s.",
+                                 FORMAT_TIMESPAN(client->information_refresh_time_usec, USEC_PER_SEC));
+
+        } else {
+                r = dhcp6_lease_get_serverid(lease, NULL, NULL);
+                if (r < 0)
+                        return log_dhcp6_client_errno(client, r, "%s has no server id",
+                                                      dhcp6_message_type_to_string(message->type));
+
+                if (!lease->ia_na && !lease->ia_pd)
+                        return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+                                                      "No IA_PD prefix or IA_NA address received. Ignoring.");
+
+                dhcp6_lease_set_lifetime(lease);
+        }
+
+        return 0;
+}
+
 static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
         if (!lease)
                 return NULL;
 
         free(lease->clientid);
         free(lease->serverid);
-        dhcp6_lease_free_ia(&lease->ia);
-        dhcp6_lease_free_ia(&lease->pd);
+        dhcp6_ia_free(lease->ia_na);
+        dhcp6_ia_free(lease->ia_pd);
         free(lease->dns);
         free(lease->fqdn);
         strv_free(lease->domains);
@@ -421,14 +672,47 @@ DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_lease, sd_dhcp6_lease, dhcp6_lease_free);
 int dhcp6_lease_new(sd_dhcp6_lease **ret) {
         sd_dhcp6_lease *lease;
 
-        lease = new0(sd_dhcp6_lease, 1);
+        assert(ret);
+
+        lease = new(sd_dhcp6_lease, 1);
         if (!lease)
                 return -ENOMEM;
 
-        lease->n_ref = 1;
-
-        LIST_HEAD_INIT(lease->ia.addresses);
+        *lease = (sd_dhcp6_lease) {
+                .n_ref = 1,
+        };
 
         *ret = lease;
         return 0;
 }
+
+int dhcp6_lease_new_from_message(
+                sd_dhcp6_client *client,
+                const DHCP6Message *message,
+                size_t len,
+                const triple_timestamp *timestamp,
+                const struct in6_addr *server_address,
+                sd_dhcp6_lease **ret) {
+
+        _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+        int r;
+
+        assert(client);
+        assert(message);
+        assert(len >= sizeof(DHCP6Message));
+        assert(ret);
+
+        r = dhcp6_lease_new(&lease);
+        if (r < 0)
+                return r;
+
+        dhcp6_lease_set_timestamp(lease, timestamp);
+        dhcp6_lease_set_server_address(lease, server_address);
+
+        r = dhcp6_lease_parse_message(client, lease, message, len);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(lease);
+        return 0;
+}
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-lldp-rx.c b/src/libnm-systemd-core/src/libsystemd-network/sd-lldp-rx.c
index f50e9c8d..496eb8f6 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/sd-lldp-rx.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/sd-lldp-rx.c
@@ -71,7 +71,7 @@ static int lldp_rx_make_space(sd_lldp_rx *lldp_rx, size_t extra) {
                         goto remove_one;
 
                 if (t == USEC_INFINITY)
-                        t = now(clock_boottime_or_monotonic());
+                        t = now(CLOCK_BOOTTIME);
 
                 if (n->until > t)
                         break;
@@ -450,7 +450,7 @@ static int lldp_rx_start_timer(sd_lldp_rx *lldp_rx, sd_lldp_neighbor *neighbor)
                 return event_source_disable(lldp_rx->timer_event_source);
 
         return event_reset_time(lldp_rx->event, &lldp_rx->timer_event_source,
-                                clock_boottime_or_monotonic(),
+                                CLOCK_BOOTTIME,
                                 n->until, 0,
                                 on_timer_event, lldp_rx,
                                 lldp_rx->event_priority, "lldp-rx-timer", true);
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 13e16f9b..6e3c2ce9 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
@@ -6,6 +6,7 @@
 
 #include "event-source.h"
 #include "event-util.h"
+#include "fd-util.h"
 #include "log.h"
 #include "string-util.h"
 
@@ -123,3 +124,43 @@ int event_source_is_enabled(sd_event_source *s) {
 
         return sd_event_source_get_enabled(s, NULL);
 }
+
+#if 0 /* NM_IGNORED */
+int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handler_t callback, void *userdata) {
+        _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+        _cleanup_close_ int fd = -1;
+        int r;
+
+        assert(e);
+
+        /* Allocates an IO event source that gets woken up whenever the clock changes. Needs to be recreated on each event */
+
+        fd = time_change_fd();
+        if (fd < 0)
+                return fd;
+
+        r = sd_event_add_io(e, &s, fd, EPOLLIN, callback, userdata);
+        if (r < 0)
+                return r;
+
+        r = sd_event_source_set_io_fd_own(s, true);
+        if (r < 0)
+                return r;
+
+        TAKE_FD(fd);
+
+        r = sd_event_source_set_description(s, "time-change");
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = TAKE_PTR(s);
+        else {
+                r = sd_event_source_set_floating(s, true);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+#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 64a41992..abd043bd 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
@@ -29,3 +29,5 @@ int event_reset_time_relative(
                 bool force_reset);
 int event_source_disable(sd_event_source *s);
 int event_source_is_enabled(sd_event_source *s);
+
+int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handler_t callback, void *userdata);
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 e87d900c..240476e6 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
@@ -1924,7 +1924,6 @@ static int event_make_inode_data(
 static uint32_t inode_data_determine_mask(struct inode_data *d) {
         bool excl_unlink = true;
         uint32_t combined = 0;
-        sd_event_source *s;
 
         assert(d);
 
@@ -3462,9 +3461,7 @@ static int event_inotify_data_process(sd_event *e, struct inotify_data *d) {
                         /* The queue overran, let's pass this event to all event sources connected to this inotify
                          * object */
 
-                        HASHMAP_FOREACH(inode_data, d->inodes) {
-                                sd_event_source *s;
-
+                        HASHMAP_FOREACH(inode_data, d->inodes)
                                 LIST_FOREACH(inotify.by_inode_data, s, inode_data->event_sources) {
 
                                         if (event_source_is_offline(s))
@@ -3474,10 +3471,8 @@ static int event_inotify_data_process(sd_event *e, struct inotify_data *d) {
                                         if (r < 0)
                                                 return r;
                                 }
-                        }
                 } else {
                         struct inode_data *inode_data;
-                        sd_event_source *s;
 
                         /* Find the inode object for this watch descriptor. If IN_IGNORED is set we also remove it from
                          * our watch descriptor table. */
@@ -3525,7 +3520,6 @@ static int event_inotify_data_process(sd_event *e, struct inotify_data *d) {
 }
 
 static int process_inotify(sd_event *e) {
-        struct inotify_data *d;
         int r, done = 0;
 
         assert(e);
@@ -3910,6 +3904,7 @@ static int epoll_wait_usec(
         int msec;
 #if 0
         static bool epoll_pwait2_absent = false;
+        int r;
 
         /* A wrapper that uses epoll_pwait2() if available, and falls back to epoll_wait() if not.
          *
@@ -3918,12 +3913,10 @@ static int epoll_wait_usec(
          * https://github.com/systemd/systemd/issues/19052. */
 
         if (!epoll_pwait2_absent && timeout != USEC_INFINITY) {
-                struct timespec ts;
-
                 r = epoll_pwait2(fd,
                                  events,
                                  maxevents,
-                                 timespec_store(&ts, timeout),
+                                 TIMESPEC_STORE(timeout),
                                  NULL);
                 if (r >= 0)
                         return r;
@@ -4324,12 +4317,6 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) {
         if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock))
                 return -EOPNOTSUPP;
 
-        /* Generate a clean error in case CLOCK_BOOTTIME is not available. Note that don't use clock_supported() here,
-         * for a reason: there are systems where CLOCK_BOOTTIME is supported, but CLOCK_BOOTTIME_ALARM is not, but for
-         * the purpose of getting the time this doesn't matter. */
-        if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && !clock_boottime_supported())
-                return -EOPNOTSUPP;
-
         if (!triple_timestamp_is_set(&e->timestamp)) {
                 /* Implicitly fall back to now() if we never ran before and thus have no cached time. */
                 *usec = now(clock);
@@ -4469,6 +4456,7 @@ _public_ int sd_event_source_get_floating(sd_event_source *s) {
 
         return s->floating;
 }
+#endif /* NM_IGNORED */
 
 _public_ int sd_event_source_set_floating(sd_event_source *s, int b) {
         assert_return(s, -EINVAL);
@@ -4492,6 +4480,7 @@ _public_ int sd_event_source_set_floating(sd_event_source *s, int b) {
         return 1;
 }
 
+#if 0 /* NM_IGNORED */
 _public_ int sd_event_source_get_exit_on_failure(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type != SOURCE_EXIT, -EDOM);
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 9ec2abc8..f1a3a42e 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
@@ -15,29 +15,6 @@
 #include "sync-util.h"
 
 #if 0 /* NM_IGNORED */
-char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]) {
-        unsigned n, k = 0;
-
-        assert(s);
-
-        /* Similar to sd_id128_to_string() but formats the result as UUID instead of plain hex chars */
-
-        for (n = 0; n < 16; n++) {
-
-                if (IN_SET(n, 4, 6, 8, 10))
-                        s[k++] = '-';
-
-                s[k++] = hexchar(id.bytes[n] >> 4);
-                s[k++] = hexchar(id.bytes[n] & 0xF);
-        }
-
-        assert(k == 36);
-
-        s[k] = 0;
-
-        return s;
-}
-
 bool id128_is_valid(const char *s) {
         size_t i, l;
 
@@ -158,13 +135,13 @@ int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync) {
         assert(f < _ID128_FORMAT_MAX);
 
         if (f != ID128_UUID) {
-                sd_id128_to_string(id, buffer);
-                buffer[32] = '\n';
-                sz = 33;
+                assert_se(sd_id128_to_string(id, buffer));
+                buffer[SD_ID128_STRING_MAX - 1] = '\n';
+                sz = SD_ID128_STRING_MAX;
         } else {
-                id128_to_uuid_string(id, buffer);
-                buffer[36] = '\n';
-                sz = 37;
+                assert_se(sd_id128_to_uuid_string(id, buffer));
+                buffer[SD_ID128_UUID_STRING_MAX - 1] = '\n';
+                sz = SD_ID128_UUID_STRING_MAX;
         }
 
         r = loop_write(fd, buffer, sz, false);
@@ -234,4 +211,20 @@ int id128_get_product(sd_id128_t *ret) {
         *ret = uuid;
         return 0;
 }
+
+int id128_equal_string(const char *s, sd_id128_t id) {
+        sd_id128_t parsed;
+        int r;
+
+        if (!s)
+                return false;
+
+        /* Checks if the specified string matches a valid string representation of the specified 128 bit ID/uuid */
+
+        r = sd_id128_from_string(s, &parsed);
+        if (r < 0)
+                return r;
+
+        return sd_id128_equal(parsed, 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 b7327a1f..65a278c8 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
@@ -8,12 +8,6 @@
 #include "hash-funcs.h"
 #include "macro.h"
 
-#define ID128_UUID_STRING_MAX 37
-
-char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]);
-
-#define ID128_TO_UUID_STRING(id) id128_to_uuid_string((id), (char[ID128_UUID_STRING_MAX]) {})
-
 bool id128_is_valid(const char *s) _pure_;
 
 typedef enum Id128Format {
@@ -40,3 +34,5 @@ extern const struct hash_ops id128_hash_ops;
 sd_id128_t id128_make_v4_uuid(sd_id128_t id);
 
 int id128_get_product(sd_id128_t *ret);
+
+int id128_equal_string(const char *s, sd_id128_t id);
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 c3ed3bbb..8e47e26e 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
@@ -22,16 +22,36 @@
 
 #if 0 /* NM_IGNORED */
 _public_ char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]) {
-        unsigned n;
-
         assert_return(s, NULL);
 
-        for (n = 0; n < 16; n++) {
+        for (size_t n = 0; n < 16; n++) {
                 s[n*2] = hexchar(id.bytes[n] >> 4);
                 s[n*2+1] = hexchar(id.bytes[n] & 0xF);
         }
 
-        s[32] = 0;
+        s[SD_ID128_STRING_MAX-1] = 0;
+
+        return s;
+}
+
+_public_ char *sd_id128_to_uuid_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_UUID_STRING_MAX]) {
+        size_t k = 0;
+
+        assert_return(s, NULL);
+
+        /* Similar to sd_id128_to_string() but formats the result as UUID instead of plain hex chars */
+
+        for (size_t n = 0; n < 16; n++) {
+
+                if (IN_SET(n, 4, 6, 8, 10))
+                        s[k++] = '-';
+
+                s[k++] = hexchar(id.bytes[n] >> 4);
+                s[k++] = hexchar(id.bytes[n] & 0xF);
+        }
+
+        assert(k == SD_ID128_UUID_STRING_MAX - 1);
+        s[k] = 0;
 
         return s;
 }
@@ -261,9 +281,7 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
 
         assert_return(ret, -EINVAL);
 
-        /* We allow usage if x86-64 RDRAND here. It might not be trusted enough for keeping secrets, but it should be
-         * fine for UUIDS. */
-        r = genuine_random_bytes(&t, sizeof t, RANDOM_ALLOW_RDRAND);
+        r = genuine_random_bytes(&t, sizeof(t), 0);
         if (r < 0)
                 return r;
 
diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp-client.h b/src/libnm-systemd-core/src/systemd/sd-dhcp-client.h
index 4af4b45f..834f80b4 100644
--- a/src/libnm-systemd-core/src/systemd/sd-dhcp-client.h
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp-client.h
@@ -44,67 +44,175 @@ enum {
                                                       * The client may want to start acquiring link-local addresses. */
 };
 
+/* https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#options */
 enum {
-        SD_DHCP_OPTION_PAD                            = 0,
-        SD_DHCP_OPTION_SUBNET_MASK                    = 1,
-        SD_DHCP_OPTION_TIME_OFFSET                    = 2,
-        SD_DHCP_OPTION_ROUTER                         = 3,
-        SD_DHCP_OPTION_DOMAIN_NAME_SERVER             = 6,
-        SD_DHCP_OPTION_LPR_SERVER                     = 9,
-        SD_DHCP_OPTION_HOST_NAME                      = 12,
-        SD_DHCP_OPTION_BOOT_FILE_SIZE                 = 13,
-        SD_DHCP_OPTION_DOMAIN_NAME                    = 15,
-        SD_DHCP_OPTION_ROOT_PATH                      = 17,
-        SD_DHCP_OPTION_ENABLE_IP_FORWARDING           = 19,
-        SD_DHCP_OPTION_ENABLE_IP_FORWARDING_NL        = 20,
-        SD_DHCP_OPTION_POLICY_FILTER                  = 21,
-        SD_DHCP_OPTION_INTERFACE_MDR                  = 22,
-        SD_DHCP_OPTION_INTERFACE_TTL                  = 23,
-        SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT    = 24,
-        SD_DHCP_OPTION_INTERFACE_MTU                  = 26,
-        SD_DHCP_OPTION_BROADCAST                      = 28,
-        /* Windows 10 option to send when Anonymize=true */
-        SD_DHCP_OPTION_ROUTER_DISCOVER                = 31,
-        SD_DHCP_OPTION_STATIC_ROUTE                   = 33,
-        SD_DHCP_OPTION_NTP_SERVER                     = 42,
-        SD_DHCP_OPTION_VENDOR_SPECIFIC                = 43,
-        /* Windows 10 option to send when Anonymize=true */
-        SD_DHCP_OPTION_NETBIOS_NAMESERVER             = 44,
-        /* Windows 10 option to send when Anonymize=true */
-        SD_DHCP_OPTION_NETBIOS_NODETYPE               = 46,
-        /* Windows 10 option to send when Anonymize=true */
-        SD_DHCP_OPTION_NETBIOS_SCOPE                  = 47,
-        SD_DHCP_OPTION_REQUESTED_IP_ADDRESS           = 50,
-        SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME          = 51,
-        SD_DHCP_OPTION_OVERLOAD                       = 52,
-        SD_DHCP_OPTION_MESSAGE_TYPE                   = 53,
-        SD_DHCP_OPTION_SERVER_IDENTIFIER              = 54,
-        SD_DHCP_OPTION_PARAMETER_REQUEST_LIST         = 55,
-        SD_DHCP_OPTION_ERROR_MESSAGE                  = 56,
-        SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE           = 57,
-        SD_DHCP_OPTION_RENEWAL_T1_TIME                = 58,
-        SD_DHCP_OPTION_REBINDING_T2_TIME              = 59,
-        SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER        = 60,
-        SD_DHCP_OPTION_CLIENT_IDENTIFIER              = 61,
-        SD_DHCP_OPTION_SMTP_SERVER                    = 69,
-        SD_DHCP_OPTION_POP3_SERVER                    = 70,
-        SD_DHCP_OPTION_USER_CLASS                     = 77,
-        SD_DHCP_OPTION_FQDN                           = 81,
-        SD_DHCP_OPTION_RELAY_AGENT_INFORMATION        = 82,
-        SD_DHCP_OPTION_NEW_POSIX_TIMEZONE             = 100,
-        SD_DHCP_OPTION_NEW_TZDB_TIMEZONE              = 101,
-        SD_DHCP_OPTION_DOMAIN_SEARCH_LIST             = 119,
-        SD_DHCP_OPTION_SIP_SERVER                     = 120,
-        SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE         = 121,
-        SD_DHCP_OPTION_MUD_URL                        = 161,
-        SD_DHCP_OPTION_6RD                            = 212,
+        SD_DHCP_OPTION_PAD                            = 0,   /* [RFC2132] */
+        SD_DHCP_OPTION_SUBNET_MASK                    = 1,   /* [RFC2132] */
+        SD_DHCP_OPTION_TIME_OFFSET                    = 2,   /* [RFC2132], deprecated by 100 and 101 */
+        SD_DHCP_OPTION_ROUTER                         = 3,   /* [RFC2132] */
+        SD_DHCP_OPTION_TIME_SERVER                    = 4,   /* [RFC2132] */
+        SD_DHCP_OPTION_NAME_SERVER                    = 5,   /* [RFC2132] */
+        SD_DHCP_OPTION_DOMAIN_NAME_SERVER             = 6,   /* [RFC2132] */
+        SD_DHCP_OPTION_LOG_SERVER                     = 7,   /* [RFC2132] */
+        SD_DHCP_OPTION_QUOTES_SERVER                  = 8,   /* [RFC2132] */
+        SD_DHCP_OPTION_LPR_SERVER                     = 9,   /* [RFC2132] */
+        SD_DHCP_OPTION_IMPRESS_SERVER                 = 10,  /* [RFC2132] */
+        SD_DHCP_OPTION_RLP_SERVER                     = 11,  /* [RFC2132] */
+        SD_DHCP_OPTION_HOST_NAME                      = 12,  /* [RFC2132] */
+        SD_DHCP_OPTION_BOOT_FILE_SIZE                 = 13,  /* [RFC2132] */
+        SD_DHCP_OPTION_MERIT_DUMP_FILE                = 14,  /* [RFC2132] */
+        SD_DHCP_OPTION_DOMAIN_NAME                    = 15,  /* [RFC2132] */
+        SD_DHCP_OPTION_SWAP_SERVER                    = 16,  /* [RFC2132] */
+        SD_DHCP_OPTION_ROOT_PATH                      = 17,  /* [RFC2132] */
+        SD_DHCP_OPTION_EXTENSION_FILE                 = 18,  /* [RFC2132] */
+        SD_DHCP_OPTION_FORWARD                        = 19,  /* [RFC2132] */
+        SD_DHCP_OPTION_SOURCE_ROUTE                   = 20,  /* [RFC2132] */
+        SD_DHCP_OPTION_POLICY_FILTER                  = 21,  /* [RFC2132] */
+        SD_DHCP_OPTION_MAX_DATAGRAM_ASSEMBLY          = 22,  /* [RFC2132] */
+        SD_DHCP_OPTION_DEFAULT_IP_TTL                 = 23,  /* [RFC2132] */
+        SD_DHCP_OPTION_MTU_TIMEOUT                    = 24,  /* [RFC2132] */
+        SD_DHCP_OPTION_MTU_PLATEAU                    = 25,  /* [RFC2132] */
+        SD_DHCP_OPTION_MTU_INTERFACE                  = 26,  /* [RFC2132] */
+        SD_DHCP_OPTION_MTU_SUBNET                     = 27,  /* [RFC2132] */
+        SD_DHCP_OPTION_BROADCAST                      = 28,  /* [RFC2132] */
+        SD_DHCP_OPTION_MASK_DISCOVERY                 = 29,  /* [RFC2132] */
+        SD_DHCP_OPTION_MASK_SUPPLIER                  = 30,  /* [RFC2132] */
+        SD_DHCP_OPTION_ROUTER_DISCOVERY               = 31,  /* [RFC2132] */
+        SD_DHCP_OPTION_ROUTER_REQUEST                 = 32,  /* [RFC2132] */
+        SD_DHCP_OPTION_STATIC_ROUTE                   = 33,  /* [RFC2132] */
+        SD_DHCP_OPTION_TRAILERS                       = 34,  /* [RFC2132] */
+        SD_DHCP_OPTION_ARP_TIMEOUT                    = 35,  /* [RFC2132] */
+        SD_DHCP_OPTION_ETHERNET                       = 36,  /* [RFC2132] */
+        SD_DHCP_OPTION_DEFAULT_TCP_TTL                = 37,  /* [RFC2132] */
+        SD_DHCP_OPTION_KEEPALIVE_TIME                 = 38,  /* [RFC2132] */
+        SD_DHCP_OPTION_KEEPALIVE_DATA                 = 39,  /* [RFC2132] */
+        SD_DHCP_OPTION_NIS_DOMAIN                     = 40,  /* [RFC2132] */
+        SD_DHCP_OPTION_NIS_SERVER                     = 41,  /* [RFC2132] */
+        SD_DHCP_OPTION_NTP_SERVER                     = 42,  /* [RFC2132] */
+        SD_DHCP_OPTION_VENDOR_SPECIFIC                = 43,  /* [RFC2132] */
+        SD_DHCP_OPTION_NETBIOS_NAME_SERVER            = 44,  /* [RFC2132] */
+        SD_DHCP_OPTION_NETBIOS_DIST_SERVER            = 45,  /* [RFC2132] */
+        SD_DHCP_OPTION_NETBIOS_NODE_TYPE              = 46,  /* [RFC2132] */
+        SD_DHCP_OPTION_NETBIOS_SCOPE                  = 47,  /* [RFC2132] */
+        SD_DHCP_OPTION_X_WINDOW_FONT                  = 48,  /* [RFC2132] */
+        SD_DHCP_OPTION_X_WINDOW_MANAGER               = 49,  /* [RFC2132] */
+        SD_DHCP_OPTION_REQUESTED_IP_ADDRESS           = 50,  /* [RFC2132] */
+        SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME          = 51,  /* [RFC2132] */
+        SD_DHCP_OPTION_OVERLOAD                       = 52,  /* [RFC2132] */
+        SD_DHCP_OPTION_MESSAGE_TYPE                   = 53,  /* [RFC2132] */
+        SD_DHCP_OPTION_SERVER_IDENTIFIER              = 54,  /* [RFC2132] */
+        SD_DHCP_OPTION_PARAMETER_REQUEST_LIST         = 55,  /* [RFC2132] */
+        SD_DHCP_OPTION_ERROR_MESSAGE                  = 56,  /* [RFC2132] */
+        SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE           = 57,  /* [RFC2132] */
+        SD_DHCP_OPTION_RENEWAL_TIME                   = 58,  /* [RFC2132] */
+        SD_DHCP_OPTION_REBINDING_TIME                 = 59,  /* [RFC2132] */
+        SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER        = 60,  /* [RFC2132] */
+        SD_DHCP_OPTION_CLIENT_IDENTIFIER              = 61,  /* [RFC2132] */
+        SD_DHCP_OPTION_NETWARE_IP_DOMAIN              = 62,  /* [RFC2242] */
+        SD_DHCP_OPTION_NETWARE_IP_OPTION              = 63,  /* [RFC2242] */
+        SD_DHCP_OPTION_NIS_DOMAIN_NAME                = 64,  /* [RFC2132] */
+        SD_DHCP_OPTION_NIS_SERVER_ADDR                = 65,  /* [RFC2132] */
+        SD_DHCP_OPTION_BOOT_SERVER_NAME               = 66,  /* [RFC2132] */
+        SD_DHCP_OPTION_BOOT_FILENAME                  = 67,  /* [RFC2132] */
+        SD_DHCP_OPTION_HOME_AGENT_ADDRESSES           = 68,  /* [RFC2132] */
+        SD_DHCP_OPTION_SMTP_SERVER                    = 69,  /* [RFC2132] */
+        SD_DHCP_OPTION_POP3_SERVER                    = 70,  /* [RFC2132] */
+        SD_DHCP_OPTION_NNTP_SERVER                    = 71,  /* [RFC2132] */
+        SD_DHCP_OPTION_WWW_SERVER                     = 72,  /* [RFC2132] */
+        SD_DHCP_OPTION_FINGER_SERVER                  = 73,  /* [RFC2132] */
+        SD_DHCP_OPTION_IRC_SERVER                     = 74,  /* [RFC2132] */
+        SD_DHCP_OPTION_STREETTALK_SERVER              = 75,  /* [RFC2132] */
+        SD_DHCP_OPTION_STDA_SERVER                    = 76,  /* [RFC2132] */
+        SD_DHCP_OPTION_USER_CLASS                     = 77,  /* [RFC3004] */
+        SD_DHCP_OPTION_DIRECTORY_AGENT                = 78,  /* [RFC2610] */
+        SD_DHCP_OPTION_SERVICE_SCOPE                  = 79,  /* [RFC2610] */
+        SD_DHCP_OPTION_RAPID_COMMIT                   = 80,  /* [RFC4039] */
+        SD_DHCP_OPTION_FQDN                           = 81,  /* [RFC4702] */
+        SD_DHCP_OPTION_RELAY_AGENT_INFORMATION        = 82,  /* [RFC3046] */
+        SD_DHCP_OPTION_ISNS                           = 83,  /* [RFC4174] */
+        /* option code 84 is unassigned [RFC3679] */
+        SD_DHCP_OPTION_NDS_SERVER                     = 85,  /* [RFC2241] */
+        SD_DHCP_OPTION_NDS_TREE_NAME                  = 86,  /* [RFC2241] */
+        SD_DHCP_OPTION_NDS_CONTEXT                    = 87,  /* [RFC2241] */
+        SD_DHCP_OPTION_BCMCS_CONTROLLER_DOMAIN_NAM    = 88,  /* [RFC4280] */
+        SD_DHCP_OPTION_BCMCS_CONTROLLER_ADDRESS       = 89,  /* [RFC4280] */
+        SD_DHCP_OPTION_AUTHENTICATION                 = 90,  /* [RFC3118] */
+        SD_DHCP_OPTION_CLIENT_LAST_TRANSACTION_TIME   = 91,  /* [RFC4388] */
+        SD_DHCP_OPTION_ASSOCIATED_IP                  = 92,  /* [RFC4388] */
+        SD_DHCP_OPTION_CLIENT_SYSTEM                  = 93,  /* [RFC4578] */
+        SD_DHCP_OPTION_CLIENT_NDI                     = 94,  /* [RFC4578] */
+        SD_DHCP_OPTION_LDAP                           = 95,  /* [RFC3679] */
+        /* option code 96 is unassigned [RFC3679] */
+        SD_DHCP_OPTION_UUID                           = 97,  /* [RFC4578] */
+        SD_DHCP_OPTION_USER_AUTHENTICATION            = 98,  /* [RFC2485] */
+        SD_DHCP_OPTION_GEOCONF_CIVIC                  = 99,  /* [RFC4776] */
+        SD_DHCP_OPTION_POSIX_TIMEZONE                 = 100, /* [RFC4833] */
+        SD_DHCP_OPTION_TZDB_TIMEZONE                  = 101, /* [RFC4833] */
+        /* option codes 102-107 are unassigned [RFC3679] */
+        SD_DHCP_OPTION_IPV6_ONLY_PREFERRED            = 108, /* [RFC8925] */
+        SD_DHCP_OPTION_DHCP4O6_SOURCE_ADDRESS         = 109, /* [RFC8539] */
+        /* option codes 110-111 are unassigned [RFC3679] */
+        SD_DHCP_OPTION_NETINFO_ADDRESS                = 112, /* [RFC3679] */
+        SD_DHCP_OPTION_NETINFO_TAG                    = 113, /* [RFC3679] */
+        SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL            = 114, /* [RFC8910] */
+        /* option code 115 is unassigned [RFC3679] */
+        SD_DHCP_OPTION_AUTO_CONFIG                    = 116, /* [RFC2563] */
+        SD_DHCP_OPTION_NAME_SERVICE_SEARCH            = 117, /* [RFC2937] */
+        SD_DHCP_OPTION_SUBNET_SELECTION               = 118, /* [RFC3011] */
+        SD_DHCP_OPTION_DOMAIN_SEARCH                  = 119, /* [RFC3397] */
+        SD_DHCP_OPTION_SIP_SERVER                     = 120, /* [RFC3361] */
+        SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE         = 121, /* [RFC3442] */
+        SD_DHCP_OPTION_CABLELABS_CLIENT_CONFIGURATION = 122, /* [RFC3495] */
+        SD_DHCP_OPTION_GEOCONF                        = 123, /* [RFC6225] */
+        SD_DHCP_OPTION_VENDOR_CLASS                   = 124, /* [RFC3925] */
+        SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION    = 125, /* [RFC3925] */
+        /* option codes 126-127 are unassigned [RFC3679] */
+        /* option codes 128-135 are assigned to use by PXE, but they are vendor specific [RFC4578] */
+        SD_DHCP_OPTION_PANA_AGENT                     = 136, /* [RFC5192] */
+        SD_DHCP_OPTION_LOST_SERVER_FQDN               = 137, /* [RFC5223] */
+        SD_DHCP_OPTION_CAPWAP_AC_ADDRESS              = 138, /* [RFC5417] */
+        SD_DHCP_OPTION_MOS_ADDRESS                    = 139, /* [RFC5678] */
+        SD_DHCP_OPTION_MOS_FQDN                       = 140, /* [RFC5678] */
+        SD_DHCP_OPTION_SIP_SERVICE_DOMAINS            = 141, /* [RFC6011] */
+        SD_DHCP_OPTION_ANDSF_ADDRESS                  = 142, /* [RFC6153] */
+        SD_DHCP_OPTION_SZTP_REDIRECT                  = 143, /* [RFC8572] */
+        SD_DHCP_OPTION_GEOLOC                         = 144, /* [RFC6225] */
+        SD_DHCP_OPTION_FORCERENEW_NONCE_CAPABLE       = 145, /* [RFC6704] */
+        SD_DHCP_OPTION_RDNSS_SELECTION                = 146, /* [RFC6731] */
+        SD_DHCP_OPTION_DOTS_RI                        = 147, /* [RFC8973] */
+        SD_DHCP_OPTION_DOTS_ADDRESS                   = 148, /* [RFC8973] */
+        /* option code 149 is unassigned [RFC3942] */
+        SD_DHCP_OPTION_TFTP_SERVER_ADDRESS            = 150, /* [RFC5859] */
+        SD_DHCP_OPTION_STATUS_CODE                    = 151, /* [RFC6926] */
+        SD_DHCP_OPTION_BASE_TIME                      = 152, /* [RFC6926] */
+        SD_DHCP_OPTION_START_TIME_OF_STATE            = 153, /* [RFC6926] */
+        SD_DHCP_OPTION_QUERY_START_TIME               = 154, /* [RFC6926] */
+        SD_DHCP_OPTION_QUERY_END_TIME                 = 155, /* [RFC6926] */
+        SD_DHCP_OPTION_DHCP_STATE                     = 156, /* [RFC6926] */
+        SD_DHCP_OPTION_DATA_SOURCE                    = 157, /* [RFC6926] */
+        SD_DHCP_OPTION_PCP_SERVER                     = 158, /* [RFC7291] */
+        SD_DHCP_OPTION_PORT_PARAMS                    = 159, /* [RFC7618] */
+        /* option code 160 is unassigned [RFC7710][RFC8910] */
+        SD_DHCP_OPTION_MUD_URL                        = 161, /* [RFC8520] */
+        /* option codes 162-174 are unassigned [RFC3942] */
+        /* option codes 175-177 are temporary assigned. */
+        /* option codes 178-207 are unassigned [RFC3942] */
+        SD_DHCP_OPTION_PXELINUX_MAGIC                 = 208, /* [RFC5071] Deprecated */
+        SD_DHCP_OPTION_CONFIGURATION_FILE             = 209, /* [RFC5071] */
+        SD_DHCP_OPTION_PATH_PREFIX                    = 210, /* [RFC5071] */
+        SD_DHCP_OPTION_REBOOT_TIME                    = 211, /* [RFC5071] */
+        SD_DHCP_OPTION_6RD                            = 212, /* [RFC5969] */
+        SD_DHCP_OPTION_ACCESS_DOMAIN                  = 213, /* [RFC5986] */
+        /* option codes 214-219 are unassigned */
+        SD_DHCP_OPTION_SUBNET_ALLOCATION              = 220, /* [RFC6656] */
+        SD_DHCP_OPTION_VIRTUAL_SUBNET_SELECTION       = 221, /* [RFC6607] */
+        /* option codes 222-223 are unassigned [RFC3942] */
+        /* option codes 224-254 are reserved for private use */
         SD_DHCP_OPTION_PRIVATE_BASE                   = 224,
-        /* Windows 10 option to send when Anonymize=true */
-        SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249,
-        /* Windows 10 option to send when Anonymize=true */
-        SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY    = 252,
+        SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249, /* [RFC7844] */
+        SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY    = 252, /* [RFC7844] */
         SD_DHCP_OPTION_PRIVATE_LAST                   = 254,
-        SD_DHCP_OPTION_END                            = 255,
+        SD_DHCP_OPTION_END                            = 255, /* [RFC2132] */
 };
 
 /* Suboptions for SD_DHCP_OPTION_RELAY_AGENT_INFORMATION option */
diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp-lease.h b/src/libnm-systemd-core/src/systemd/sd-dhcp-lease.h
index 478bbfd7..578ac6d4 100644
--- a/src/libnm-systemd-core/src/systemd/sd-dhcp-lease.h
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp-lease.h
@@ -67,7 +67,8 @@ int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname);
 int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains);
 int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname);
 int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path);
-int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes);
+int sd_dhcp_lease_get_static_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret);
+int sd_dhcp_lease_get_classless_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret);
 int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len);
 int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len);
 int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone);
@@ -82,7 +83,6 @@ int sd_dhcp_lease_get_6rd(
 int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination);
 int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length);
 int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway);
-int sd_dhcp_route_get_option(sd_dhcp_route *route);
 
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref);
 
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 0e23c84e..d89b7d1c 100644
--- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h
@@ -81,8 +81,8 @@ enum {
         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_NEW_POSIX_TIMEZONE         = 41,  /* RFC 4833 */
-        SD_DHCP6_OPTION_NEW_TZDB_TIMEZONE          = 42,  /* RFC 4833 */
+        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 */
@@ -251,7 +251,7 @@ int sd_dhcp6_client_set_request_vendor_class(
 int sd_dhcp6_client_set_prefix_delegation_hint(
                 sd_dhcp6_client *client,
                 uint8_t prefixlen,
-                const struct in6_addr *pd_address);
+                const struct in6_addr *pd_prefix);
 int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client,
                                           int *delegation);
 int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client,
@@ -260,8 +260,6 @@ int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client,
                                         int *request);
 int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client,
                                         int request);
-int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client,
-                                       uint32_t transaction_id);
 int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client,
                                       sd_dhcp6_option *v);
 
diff --git a/src/libnm-systemd-core/src/systemd/sd-id128.h b/src/libnm-systemd-core/src/systemd/sd-id128.h
index e64f3200..f7d3244b 100644
--- a/src/libnm-systemd-core/src/systemd/sd-id128.h
+++ b/src/libnm-systemd-core/src/systemd/sd-id128.h
@@ -35,11 +35,14 @@ union sd_id128 {
 };
 
 #define SD_ID128_STRING_MAX 33U
+#define SD_ID128_UUID_STRING_MAX 37U
 
 char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]);
+char *sd_id128_to_uuid_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_UUID_STRING_MAX]);
 int sd_id128_from_string(const char *s, sd_id128_t *ret);
 
 #define SD_ID128_TO_STRING(id) sd_id128_to_string((id), (char[SD_ID128_STRING_MAX]) {})
+#define SD_ID128_TO_UUID_STRING(id) sd_id128_to_uuid_string((id), (char[SD_ID128_UUID_STRING_MAX]) {})
 
 int sd_id128_randomize(sd_id128_t *ret);