summary refs log tree commit diff
path: root/src/libnm-systemd-core
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2022-01-13 22:30:39 +0100
committerMichael Biebl <biebl@debian.org>2022-01-13 22:30:39 +0100
commit88c227d90a6b7b388c5c85d72802a0ca8f05ed5c (patch)
tree71f32df6617802270e8a78574bd8e1637dc532f4 /src/libnm-systemd-core
parente74c568b07b50b97873fb4ee1d776dedefbd54d6 (diff)
New upstream version 1.34.0 upstream/1.34.0
Diffstat (limited to 'src/libnm-systemd-core')
-rw-r--r--src/libnm-systemd-core/meson.build2
-rw-r--r--src/libnm-systemd-core/nm-sd.h3
-rw-r--r--src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h92
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/arp-util.c92
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/arp-util.h28
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c51
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h2
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-internal.h10
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h42
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h14
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c561
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c119
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.h8
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/lldp-network.c3
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/lldp-rx-internal.h (renamed from src/libnm-systemd-core/src/libsystemd-network/lldp-internal.h)24
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/network-common.c15
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/network-common.h26
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c206
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c235
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c181
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c189
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c49
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-lldp-rx.c525
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-lldp.c524
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h2
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c156
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c7
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h2
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c29
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp-client.h2
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h2
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h10
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-id128.h11
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-ipv4acd.h4
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-ipv4ll.h4
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-lldp-rx.h109
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-lldp.h105
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-ndisc.h2
38 files changed, 1852 insertions, 1594 deletions
diff --git a/src/libnm-systemd-core/meson.build b/src/libnm-systemd-core/meson.build
index 7ecf3164..fb472a76 100644
--- a/src/libnm-systemd-core/meson.build
+++ b/src/libnm-systemd-core/meson.build
@@ -20,7 +20,7 @@ libnm_systemd_core = static_library(
     'src/libsystemd-network/sd-dhcp6-lease.c',
     'src/libsystemd-network/sd-ipv4acd.c',
     'src/libsystemd-network/sd-ipv4ll.c',
-    'src/libsystemd-network/sd-lldp.c',
+    'src/libsystemd-network/sd-lldp-rx.c',
     'src/libsystemd/sd-event/event-util.c',
     'src/libsystemd/sd-event/sd-event.c',
     'src/libsystemd/sd-id128/id128-util.c',
diff --git a/src/libnm-systemd-core/nm-sd.h b/src/libnm-systemd-core/nm-sd.h
index f3461974..0ec2f16e 100644
--- a/src/libnm-systemd-core/nm-sd.h
+++ b/src/libnm-systemd-core/nm-sd.h
@@ -8,8 +8,9 @@
 
 #include "src/systemd/sd-dhcp-client.h"
 #include "src/systemd/sd-dhcp6-client.h"
-#include "src/systemd/sd-lldp.h"
 #include "src/systemd/sd-ipv4ll.h"
+#include "src/systemd/sd-lldp-rx.h"
+#include "src/systemd/sd-lldp.h"
 
 /*****************************************************************************/
 
diff --git a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h
index 4aae34b8..7ce1f8c9 100644
--- a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h
+++ b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h
@@ -18,7 +18,7 @@
 #define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_SYSTEMD
 
 #ifndef HAVE_SYS_AUXV_H
-    #define HAVE_SYS_AUXV_H 0
+#define HAVE_SYS_AUXV_H 0
 #endif
 
 /*****************************************************************************
@@ -28,62 +28,64 @@
 
 #if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD
 
-    #include <netinet/in.h>
-    #include <string.h>
-    #include <stdio.h>
-    #include <errno.h>
-    #include <elf.h>
-    #ifdef HAVE_SYS_AUXV_H
-        #include <sys/auxv.h>
-    #endif
-    #include <unistd.h>
-    #include <sys/syscall.h>
-    #include <sys/ioctl.h>
-
-    /* Missing in Linux 3.2.0, in Ubuntu 12.04 */
-    #ifndef BPF_XOR
-        #define BPF_XOR 0xa0
-    #endif
-
-    #ifndef ETHERTYPE_LLDP
-        #define ETHERTYPE_LLDP 0x88cc
-    #endif
-
-    #ifndef HAVE_SECURE_GETENV
-        #ifdef HAVE___SECURE_GETENV
-            #define secure_getenv __secure_getenv
-        #else
-            #error neither secure_getenv nor __secure_getenv is available
-        #endif
-    #endif
-
-    #include "libnm-base/nm-base.h"
+#include <netinet/in.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <elf.h>
+#ifdef HAVE_SYS_AUXV_H
+#include <sys/auxv.h>
+#endif
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <sys/ioctl.h>
+
+/* Missing in Linux 3.2.0, in Ubuntu 12.04 */
+#ifndef BPF_XOR
+#define BPF_XOR 0xa0
+#endif
+
+#ifndef ETHERTYPE_LLDP
+#define ETHERTYPE_LLDP 0x88cc
+#endif
+
+#ifndef HAVE_SECURE_GETENV
+#ifdef HAVE___SECURE_GETENV
+#define secure_getenv __secure_getenv
+#else
+#error neither secure_getenv nor __secure_getenv is available
+#endif
+#endif
+
+#include "libnm-base/nm-base.h"
 
 /*****************************************************************************/
 
+#define HAVE_OPENSSL 0
+
 static inline int
 sd_notify(int unset_environment, const char *state)
 {
     return 0;
 }
 
-    /* Can't include both net/if.h and linux/if.h; so have to define this here */
-    #ifndef IF_NAMESIZE
-        #define IF_NAMESIZE 16
-    #endif
+/* Can't include both net/if.h and linux/if.h; so have to define this here */
+#ifndef IF_NAMESIZE
+#define IF_NAMESIZE 16
+#endif
 
-    #ifndef IFNAMSIZ
-        #define IFNAMSIZ IF_NAMESIZE
-    #endif
+#ifndef IFNAMSIZ
+#define IFNAMSIZ IF_NAMESIZE
+#endif
 
-    #ifndef MAX_HANDLE_SZ
-        #define MAX_HANDLE_SZ 128
-    #endif
+#ifndef MAX_HANDLE_SZ
+#define MAX_HANDLE_SZ 128
+#endif
 
-    #include "sd-id128.h"
-    #include "sparse-endian.h"
-    #include "async.h"
-    #include "util.h"
+#include "sd-id128.h"
+#include "sparse-endian.h"
+#include "async.h"
+#include "util.h"
 
 #endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */
 
diff --git a/src/libnm-systemd-core/src/libsystemd-network/arp-util.c b/src/libnm-systemd-core/src/libsystemd-network/arp-util.c
index 1c777d8a..46c4a8cb 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/arp-util.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/arp-util.c
@@ -10,11 +10,13 @@
 #include <netinet/if_ether.h>
 
 #include "arp-util.h"
+#include "ether-addr-util.h"
 #include "fd-util.h"
+#include "in-addr-util.h"
 #include "unaligned.h"
 #include "util.h"
 
-int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) {
+int arp_update_filter(int fd, const struct in_addr *a, const struct ether_addr *eth_mac) {
         struct sock_filter filter[] = {
                 BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0),                                         /* A <- packet length */
                 BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0),           /* packet >= arp packet ? */
@@ -48,13 +50,13 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),                                  /* A == 0 ? */
                 BPF_STMT(BPF_RET + BPF_K, 0),                                                  /* ignore */
                 /* Sender Protocol Address or Target Protocol Address must be equal to the one we care about */
-                BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)),                                  /* A <- clients IP */
+                BPF_STMT(BPF_LD + BPF_IMM, htobe32(a->s_addr)),                                /* A <- clients IP */
                 BPF_STMT(BPF_MISC + BPF_TAX, 0),                                               /* X <- A */
                 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)),       /* A <- SPA */
                 BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                        /* X xor A */
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),                                  /* A == 0 ? */
                 BPF_STMT(BPF_RET + BPF_K, 65535),                                              /* return all */
-                BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)),                                  /* A <- clients IP */
+                BPF_STMT(BPF_LD + BPF_IMM, htobe32(a->s_addr)),                                /* A <- clients IP */
                 BPF_STMT(BPF_MISC + BPF_TAX, 0),                                               /* X <- A */
                 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)),       /* A <- TPA */
                 BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                        /* X xor A */
@@ -63,15 +65,25 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_
                 BPF_STMT(BPF_RET + BPF_K, 0),                                                  /* ignore */
         };
         struct sock_fprog fprog = {
-                .len = ELEMENTSOF(filter),
-                .filter = (struct sock_filter*) filter
+                .len    = ELEMENTSOF(filter),
+                .filter = (struct sock_filter*) filter,
         };
+
+        assert(fd >= 0);
+
+        if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int arp_network_bind_raw_socket(int ifindex, const struct in_addr *a, const struct ether_addr *eth_mac) {
         union sockaddr_union link = {
-                .ll.sll_family = AF_PACKET,
+                .ll.sll_family   = AF_PACKET,
                 .ll.sll_protocol = htobe16(ETH_P_ARP),
-                .ll.sll_ifindex = ifindex,
-                .ll.sll_halen = ETH_ALEN,
-                .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+                .ll.sll_ifindex  = ifindex,
+                .ll.sll_halen    = ETH_ALEN,
+                .ll.sll_addr     = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
         };
         _cleanup_close_ int s = -1;
         int r;
@@ -82,59 +94,57 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_
         if (s < 0)
                 return -errno;
 
-        r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+        r = arp_update_filter(s, a, eth_mac);
         if (r < 0)
-                return -errno;
+                return r;
 
-        r = bind(s, &link.sa, sizeof(link.ll));
-        if (r < 0)
+        if (bind(s, &link.sa, sizeof(link.ll)) < 0)
                 return -errno;
 
         return TAKE_FD(s);
 }
 
-static int arp_send_packet(int fd, int ifindex,
-                           be32_t pa, const struct ether_addr *ha,
-                           bool announce) {
+int arp_send_packet(
+                int fd,
+                int ifindex,
+                const struct in_addr *pa,
+                const struct ether_addr *ha,
+                bool announce) {
+
         union sockaddr_union link = {
-                .ll.sll_family = AF_PACKET,
+                .ll.sll_family   = AF_PACKET,
                 .ll.sll_protocol = htobe16(ETH_P_ARP),
-                .ll.sll_ifindex = ifindex,
-                .ll.sll_halen = ETH_ALEN,
-                .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+                .ll.sll_ifindex  = ifindex,
+                .ll.sll_halen    = ETH_ALEN,
+                .ll.sll_addr     = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
         };
         struct ether_arp arp = {
-                .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */
-                .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */
-                .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */
-                .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */
-                .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */
+                .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER),  /* HTYPE */
+                .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP),  /* PTYPE */
+                .ea_hdr.ar_hln = ETH_ALEN,               /* HLEN */
+                .ea_hdr.ar_pln = sizeof(struct in_addr), /* PLEN */
+                .ea_hdr.ar_op  = htobe16(ARPOP_REQUEST), /* REQUEST */
         };
-        int r;
+        ssize_t n;
 
         assert(fd >= 0);
-        assert(pa != 0);
+        assert(ifindex > 0);
+        assert(pa);
+        assert(in4_addr_is_set(pa));
         assert(ha);
+        assert(!ether_addr_is_null(ha));
 
         memcpy(&arp.arp_sha, ha, ETH_ALEN);
-        memcpy(&arp.arp_tpa, &pa, sizeof(pa));
+        memcpy(&arp.arp_tpa, pa, sizeof(struct in_addr));
 
         if (announce)
-                memcpy(&arp.arp_spa, &pa, sizeof(pa));
+                memcpy(&arp.arp_spa, pa, sizeof(struct in_addr));
 
-        r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll));
-        if (r < 0)
+        n = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll));
+        if (n < 0)
                 return -errno;
+        if (n != sizeof(struct ether_arp))
+                return -EIO;
 
         return 0;
 }
-
-int arp_send_probe(int fd, int ifindex,
-                    be32_t pa, const struct ether_addr *ha) {
-        return arp_send_packet(fd, ifindex, pa, ha, false);
-}
-
-int arp_send_announcement(int fd, int ifindex,
-                          be32_t pa, const struct ether_addr *ha) {
-        return arp_send_packet(fd, ifindex, pa, ha, true);
-}
diff --git a/src/libnm-systemd-core/src/libsystemd-network/arp-util.h b/src/libnm-systemd-core/src/libsystemd-network/arp-util.h
index 2dac8cfb..e8615a15 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/arp-util.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/arp-util.h
@@ -6,13 +6,31 @@
 ***/
 
 #include <net/ethernet.h>
+#include <netinet/in.h>
 
 #include "socket-util.h"
 #include "sparse-endian.h"
 
-int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac);
+int arp_update_filter(int fd, const struct in_addr *a, const struct ether_addr *eth_mac);
+int arp_network_bind_raw_socket(int ifindex, const struct in_addr *a, const struct ether_addr *eth_mac);
 
-int arp_send_probe(int fd, int ifindex,
-                   be32_t pa, const struct ether_addr *ha);
-int arp_send_announcement(int fd, int ifindex,
-                          be32_t pa, const struct ether_addr *ha);
+int arp_send_packet(
+                int fd,
+                int ifindex,
+                const struct in_addr *pa,
+                const struct ether_addr *ha,
+                bool announce);
+static inline int arp_send_probe(
+                int fd,
+                int ifindex,
+                const struct in_addr *pa,
+                const struct ether_addr *ha) {
+        return arp_send_packet(fd, ifindex, pa, ha, false);
+}
+static inline int arp_send_announcement(
+                int fd,
+                int ifindex,
+                const struct in_addr *pa,
+                const struct ether_addr *ha) {
+        return arp_send_packet(fd, ifindex, pa, ha, true);
+}
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 0720e381..bd47ff41 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c
@@ -14,6 +14,7 @@
 #include "network-util.h"
 #include "siphash24.h"
 #include "sparse-endian.h"
+#include "stat-util.h"
 #include "stdio-util.h"
 #include "udev-util.h"
 #include "virt.h"
@@ -166,38 +167,40 @@ int dhcp_identifier_set_iaid(
                 const uint8_t *mac,
                 size_t mac_len,
                 bool legacy_unstable_byteorder,
+                bool use_mac,
                 void *_id) {
 #if 0 /* NM_IGNORED */
+
         /* name is a pointer to memory in the sd_device struct, so must
          * have the same scope */
         _cleanup_(sd_device_unrefp) sd_device *device = NULL;
         const char *name = NULL;
-        uint64_t id;
         uint32_t id32;
+        uint64_t id;
+        int r;
+
+        if (path_is_read_only_fs("/sys") <= 0 && !use_mac) {
+                /* udev should be around */
+
+                r = sd_device_new_from_ifindex(&device, ifindex);
+                if (r < 0)
+                        return r;
+
+                r = sd_device_get_is_initialized(device);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        /* not yet ready */
+                        return -EBUSY;
+
+                r = device_is_renaming(device);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        /* device is under renaming */
+                        return -EBUSY;
 
-        if (detect_container() <= 0) {
-                /* not in a container, udev will be around */
-                char ifindex_str[1 + DECIMAL_STR_MAX(int)];
-                int r;
-
-                xsprintf(ifindex_str, "n%d", ifindex);
-                if (sd_device_new_from_device_id(&device, ifindex_str) >= 0) {
-                        r = sd_device_get_is_initialized(device);
-                        if (r < 0)
-                                return r;
-                        if (r == 0)
-                                /* not yet ready */
-                                return -EBUSY;
-
-                        r = device_is_renaming(device);
-                        if (r < 0)
-                                return r;
-                        if (r > 0)
-                                /* device is under renaming */
-                                return -EBUSY;
-
-                        name = net_get_name_persistent(device);
-                }
+                name = net_get_name_persistent(device);
         }
 
         if (name)
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 1d9a9c55..6c24af03 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h
@@ -59,4 +59,4 @@ int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *add
 int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
 int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len);
 int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len);
-int dhcp_identifier_set_iaid(int ifindex, const uint8_t *mac, size_t mac_len, bool legacy_unstable_byteorder, void *_id);
+int dhcp_identifier_set_iaid(int ifindex, const uint8_t *mac, size_t mac_len, bool legacy_unstable_byteorder, bool use_mac, void *_id);
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-internal.h
index d8c42757..6538f05b 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-internal.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-internal.h
@@ -12,7 +12,7 @@
 #include "sd-dhcp-client.h"
 
 #include "dhcp-protocol.h"
-#include "log-link.h"
+#include "network-common.h"
 #include "socket-util.h"
 
 typedef struct sd_dhcp_option {
@@ -30,6 +30,8 @@ typedef struct DHCPServerData {
 
 extern const struct hash_ops dhcp_option_hash_ops;
 
+typedef struct sd_dhcp_client sd_dhcp_client;
+
 int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid,
                                  const uint8_t *mac_addr, size_t mac_addr_len,
                                  const uint8_t *bcast_addr, size_t bcast_addr_len,
@@ -62,6 +64,8 @@ void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
 
 int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port);
 
+void dhcp_client_set_test_mode(sd_dhcp_client *client, bool test_mode);
+
 /* If we are invoking callbacks of a dhcp-client, ensure unreffing the
  * client from the callback doesn't destroy the object we are working
  * on */
@@ -71,10 +75,10 @@ int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, ui
 #define log_dhcp_client_errno(client, error, fmt, ...)          \
         log_interface_prefix_full_errno(                        \
                 "DHCPv4 client: ",                              \
-                sd_dhcp_client_get_ifname(client),              \
+                sd_dhcp_client, client,                         \
                 error, fmt, ##__VA_ARGS__)
 #define log_dhcp_client(client, fmt, ...)                       \
         log_interface_prefix_full_errno_zerook(                 \
                 "DHCPv4 client: ",                              \
-                sd_dhcp_client_get_ifname(client),              \
+                sd_dhcp_client, client,                         \
                 0, fmt, ##__VA_ARGS__)
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 34e1f61c..4eb56aa8 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h
@@ -13,8 +13,8 @@
 
 #include "hashmap.h"
 #include "list.h"
-#include "log-link.h"
 #include "macro.h"
+#include "network-common.h"
 #include "sparse-endian.h"
 
 typedef struct sd_dhcp6_option {
@@ -91,6 +91,8 @@ typedef struct DHCP6IA {
         LIST_HEAD(DHCP6Address, addresses);
 } DHCP6IA;
 
+typedef struct sd_dhcp6_client sd_dhcp6_client;
+
 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);
@@ -99,15 +101,29 @@ 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(uint8_t **buf, size_t *buflen, uint16_t *optcode,
-                       size_t *optlen, uint8_t **optvalue);
-int dhcp6_option_parse_status(DHCP6Option *option, size_t len);
-int dhcp6_option_parse_ia(sd_dhcp6_client *client, DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code);
-int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
-                                struct in6_addr **addrs, size_t count);
-int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen,
-                                       char ***str_arr);
-int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str);
+
+int dhcp6_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,
@@ -118,13 +134,15 @@ 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_;
 
+void dhcp6_client_set_test_mode(sd_dhcp6_client *client, bool test_mode);
+
 #define log_dhcp6_client_errno(client, error, fmt, ...)         \
         log_interface_prefix_full_errno(                        \
                 "DHCPv6 client: ",                              \
-                sd_dhcp6_client_get_ifname(client),             \
+                sd_dhcp6_client, client,                        \
                 error, fmt, ##__VA_ARGS__)
 #define log_dhcp6_client(client, fmt, ...)                      \
         log_interface_prefix_full_errno_zerook(                 \
                 "DHCPv6 client: ",                              \
-                sd_dhcp6_client_get_ifname(client),             \
+                sd_dhcp6_client, client,                        \
                 0, fmt, ##__VA_ARGS__)
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 391b4f1f..8801497b 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
@@ -28,11 +28,11 @@ struct sd_dhcp6_lease {
         struct in6_addr *dns;
         size_t dns_count;
         char **domains;
-        size_t domains_count;
         struct in6_addr *ntp;
         size_t ntp_count;
         char **ntp_fqdn;
-        size_t ntp_fqdn_count;
+        struct in6_addr *sntp;
+        size_t sntp_count;
         char *fqdn;
 };
 
@@ -50,12 +50,10 @@ int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit);
 int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid);
 int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid);
 
-int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen);
-int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval,
-                            size_t optlen);
-int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen);
-int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval,
-                         size_t optlen) ;
+int dhcp6_lease_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);
+int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
+int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) ;
 int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
 
 int dhcp6_lease_new(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 d9e24585..4249f90d 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c
@@ -16,29 +16,12 @@
 #include "dhcp6-lease-internal.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"
 
-typedef struct DHCP6StatusOption {
-        struct DHCP6Option option;
-        be16_t status;
-        char msg[];
-} _packed_ DHCP6StatusOption;
-
-typedef struct DHCP6AddressOption {
-        struct DHCP6Option option;
-        struct iaaddr iaaddr;
-        uint8_t options[];
-} _packed_ DHCP6AddressOption;
-
-typedef struct DHCP6PDPrefixOption {
-        struct DHCP6Option option;
-        struct iapdprefix iapdprefix;
-        uint8_t options[];
-} _packed_ DHCP6PDPrefixOption;
-
 #define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na))
 #define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd))
 #define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta))
@@ -372,337 +355,410 @@ int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const
         return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_CLASS, total, p);
 }
 
-static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
-        DHCP6Option *option = (DHCP6Option*) *buf;
-        uint16_t len;
+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) {
 
-        assert_return(buf, -EINVAL);
-        assert_return(optcode, -EINVAL);
-        assert_return(optlen, -EINVAL);
+        const DHCP6Option *option;
+        size_t len;
 
-        if (*buflen < offsetof(DHCP6Option, data))
-                return -ENOMSG;
+        assert(buf);
+        assert(offset);
+        assert(ret_option_code);
+        assert(ret_option_data_len);
+        assert(ret_option_data);
 
-        len = be16toh(option->len);
+        if (buflen < offsetof(DHCP6Option, data))
+                return -EBADMSG;
 
-        if (len > *buflen)
-                return -ENOMSG;
+        if (*offset >= buflen - offsetof(DHCP6Option, data))
+                return -EBADMSG;
+
+        option = (const DHCP6Option*) (buf + *offset);
+        len = be16toh(option->len);
 
-        *optcode = be16toh(option->code);
-        *optlen = len;
+        if (len > buflen - offsetof(DHCP6Option, data) - *offset)
+                return -EBADMSG;
 
-        *buf += 4;
-        *buflen -= 4;
+        *offset += offsetof(DHCP6Option, data) + len;
+        *ret_option_code = be16toh(option->code);
+        *ret_option_data_len = len;
+        *ret_option_data = option->data;
 
         return 0;
 }
 
-int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
-                       size_t *optlen, uint8_t **optvalue) {
-        int r;
+int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message) {
+        assert(data);
 
-        assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
+        if (data_len < sizeof(uint16_t))
+                return -EBADMSG;
 
-        r = option_parse_hdr(buf, buflen, optcode, optlen);
-        if (r < 0)
-                return r;
+        if (ret_status_message) {
+                char *msg;
 
-        if (*optlen > *buflen)
-                return -ENOBUFS;
+                /* The status message MUST NOT be null-terminated. See section 21.13 of RFC8415.
+                 * Let's escape unsafe characters for safety. */
+                msg = cescape_length((const char*) (data + sizeof(uint16_t)), data_len - sizeof(uint16_t));
+                if (!msg)
+                        return -ENOMEM;
 
-        *optvalue = *buf;
-        *buflen -= *optlen;
-        *buf += *optlen;
+                *ret_status_message = msg;
+        }
 
-        return 0;
+        return unaligned_read_be16(data);
 }
 
-int dhcp6_option_parse_status(DHCP6Option *option, size_t len) {
-        DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
+static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) {
+        int r;
 
-        if (len < sizeof(DHCP6StatusOption) ||
-            be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption))
-                return -ENOBUFS;
+        assert(buf);
+
+        for(size_t offset = 0; offset < buflen;) {
+                const uint8_t *data;
+                size_t data_len;
+                uint16_t code;
+
+                r = dhcp6_option_parse(buf, buflen, &offset, &code, &data_len, &data);
+                if (r < 0)
+                        return r;
+
+                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)
+                                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));
+                        break;
+                }
+                default:
+                        log_dhcp6_client(client, "Received an unknown sub option %u in IA address or PD prefix, ignoring.", code);
+                }
+        }
 
-        return be16toh(statusopt->status);
+        return 0;
 }
 
-static int dhcp6_option_parse_address(sd_dhcp6_client *client, DHCP6Option *option, DHCP6IA *ia, uint32_t *ret_lifetime_valid) {
-        DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option;
-        DHCP6Address *addr;
+static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, const uint8_t *data, size_t len, DHCP6Address **ret) {
         uint32_t lt_valid, lt_pref;
+        DHCP6Address *a;
         int r;
 
-        if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option))
-                return -ENOBUFS;
+        assert(data);
+        assert(ret);
+
+        if (len < sizeof(struct iaaddr))
+                return -EBADMSG;
 
-        lt_valid = be32toh(addr_option->iaaddr.lifetime_valid);
-        lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred);
+        lt_valid = be32toh(((const struct iaaddr*) data)->lifetime_valid);
+        lt_pref = be32toh(((const struct iaaddr*) data)->lifetime_preferred);
 
-        if (lt_valid == 0 || lt_pref > lt_valid)
+        if (lt_valid == 0)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                              "Valid lifetime of an IA address is zero or "
-                                              "preferred lifetime %"PRIu32" > valid lifetime %"PRIu32,
+                                              "Received an IA address with zero valid lifetime, ignoring.");
+        if (lt_pref > lt_valid)
+                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+                                              "Received an IA address with preferred lifetime %"PRIu32
+                                              " larger than valid lifetime %"PRIu32", ignoring.",
                                               lt_pref, lt_valid);
 
-        if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) {
-                r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option));
+        if (len > sizeof(struct iaaddr)) {
+                r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iaaddr), len - sizeof(struct iaaddr));
                 if (r < 0)
                         return r;
-                if (r > 0)
-                        return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                                      "Non-zero status code '%s' for address is received",
-                                                      dhcp6_message_status_to_string(r));
         }
 
-        addr = new0(DHCP6Address, 1);
-        if (!addr)
+        a = new(DHCP6Address, 1);
+        if (!a)
                 return -ENOMEM;
 
-        LIST_INIT(addresses, addr);
-        memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr));
-
-        LIST_PREPEND(addresses, ia->addresses, addr);
-
-        *ret_lifetime_valid = be32toh(addr->iaaddr.lifetime_valid);
+        LIST_INIT(addresses, a);
+        memcpy(&a->iaaddr, data, sizeof(struct iaaddr));
 
+        *ret = a;
         return 0;
 }
 
-static int dhcp6_option_parse_pdprefix(sd_dhcp6_client *client, DHCP6Option *option, DHCP6IA *ia, uint32_t *ret_lifetime_valid) {
-        DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option;
-        DHCP6Address *prefix;
+static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, const uint8_t *data, size_t len, DHCP6Address **ret) {
         uint32_t lt_valid, lt_pref;
+        DHCP6Address *a;
         int r;
 
-        if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option))
-                return -ENOBUFS;
+        if (len < sizeof(struct iapdprefix))
+                return -ENOMSG;
 
-        lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid);
-        lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred);
+        lt_valid = be32toh(((const struct iapdprefix*) data)->lifetime_valid);
+        lt_pref = be32toh(((const struct iapdprefix*) data)->lifetime_preferred);
 
-        if (lt_valid == 0 || lt_pref > lt_valid)
+        if (lt_valid == 0)
+                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+                                              "Received a PD prefix with zero valid lifetime, ignoring.");
+        if (lt_pref > lt_valid)
                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                              "Valid lifetieme of a PD prefix is zero or "
-                                              "preferred lifetime %"PRIu32" > valid lifetime %"PRIu32,
+                                              "Received a PD prefix with preferred lifetime %"PRIu32
+                                              " larger than valid lifetime %"PRIu32", ignoring.",
                                               lt_pref, lt_valid);
 
-        if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) {
-                r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option));
+        if (len > sizeof(struct iapdprefix)) {
+                r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iapdprefix), len - sizeof(struct iapdprefix));
                 if (r < 0)
                         return r;
-                if (r > 0)
-                        return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                                      "Non-zero status code '%s' for PD prefix is received",
-                                                      dhcp6_message_status_to_string(r));
         }
 
-        prefix = new0(DHCP6Address, 1);
-        if (!prefix)
+        a = new(DHCP6Address, 1);
+        if (!a)
                 return -ENOMEM;
 
-        LIST_INIT(addresses, prefix);
-        memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix));
-
-        LIST_PREPEND(addresses, ia->addresses, prefix);
-
-        *ret_lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid);
+        LIST_INIT(addresses, a);
+        memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix));
 
+        *ret = a;
         return 0;
 }
 
-int dhcp6_option_parse_ia(sd_dhcp6_client *client, DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code) {
-        uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
-        uint16_t iatype, optlen;
-        size_t iaaddr_offset;
-        int r = 0, status;
-        size_t i, len;
-        uint16_t opt;
+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) {
+
+        _cleanup_(dhcp6_lease_free_ia) DHCP6IA ia = {};
+        uint32_t lt_t1, lt_t2, lt_min = UINT32_MAX;
+        be32_t received_iaid;
+        size_t offset;
+        int r;
 
-        assert_return(ia, -EINVAL);
-        assert_return(!ia->addresses, -EINVAL);
+        assert(IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA, SD_DHCP6_OPTION_IA_PD));
+        assert(option_data);
+        assert(ret);
 
-        iatype = be16toh(iaoption->code);
-        len = be16toh(iaoption->len);
+        /* This will return the following:
+         * -ENOMEM: memory allocation error,
+         * -ENOANO: unmatching IAID,
+         * -EINVAL: non-zero status code, or invalid lifetime,
+         * -EBADMSG: invalid message format,
+         * -ENODATA: no valid address or PD prefix,
+         * 0: success. */
 
-        switch (iatype) {
+        switch (option_code) {
         case SD_DHCP6_OPTION_IA_NA:
 
-                if (len < DHCP6_OPTION_IA_NA_LEN)
-                        return -ENOBUFS;
-
-                iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
-                memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na));
-
-                lt_t1 = be32toh(ia->ia_na.lifetime_t1);
-                lt_t2 = be32toh(ia->ia_na.lifetime_t2);
+                if (option_data_len < DHCP6_OPTION_IA_NA_LEN)
+                        return -EBADMSG;
 
-                if (lt_t1 > lt_t2)
-                        return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                                      "IA NA T1 %"PRIu32"sec > T2 %"PRIu32"sec",
-                                                      lt_t1, lt_t2);
+                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 (len < sizeof(ia->ia_pd))
-                        return -ENOBUFS;
-
-                iaaddr_offset = sizeof(ia->ia_pd);
-                memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd));
-
-                lt_t1 = be32toh(ia->ia_pd.lifetime_t1);
-                lt_t2 = be32toh(ia->ia_pd.lifetime_t2);
+                if (option_data_len < DHCP6_OPTION_IA_PD_LEN)
+                        return -EBADMSG;
 
-                if (lt_t1 > lt_t2)
-                        return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                                      "IA PD T1 %"PRIu32"sec > T2 %"PRIu32"sec",
-                                                      lt_t1, lt_t2);
+                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);
                 break;
 
         case SD_DHCP6_OPTION_IA_TA:
-                if (len < DHCP6_OPTION_IA_TA_LEN)
-                        return -ENOBUFS;
+                if (option_data_len < DHCP6_OPTION_IA_TA_LEN)
+                        return -ENOMSG;
 
-                iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
-                memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta));
+                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. */
                 break;
 
         default:
-                return -ENOMSG;
+                assert_not_reached();
         }
 
-        ia->type = iatype;
-        i = iaaddr_offset;
+        /* 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)
+                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.");
 
-        while (i < len) {
-                DHCP6Option *option = (DHCP6Option *)&iaoption->data[i];
+        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 (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len))
-                        return -ENOBUFS;
+        for (; offset < option_data_len;) {
+                const uint8_t *subdata;
+                size_t subdata_len;
+                uint16_t subopt;
 
-                opt = be16toh(option->code);
-                optlen = be16toh(option->len);
+                r = dhcp6_option_parse(option_data, option_data_len, &offset, &subopt, &subdata_len, &subdata);
+                if (r < 0)
+                        return r;
 
-                switch (opt) {
-                case SD_DHCP6_OPTION_IAADDR:
+                switch (subopt) {
+                case SD_DHCP6_OPTION_IAADDR: {
+                        DHCP6Address *a;
 
-                        if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA))
-                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                                              "IA Address option not in IA NA or TA option");
+                        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_address(client, option, ia, &lt_valid);
-                        if (r < 0 && r != -EINVAL)
+                        r = dhcp6_option_parse_ia_address(client, subdata, subdata_len, &a);
+                        if (r == -ENOMEM)
                                 return r;
-                        if (r >= 0 && lt_valid < lt_min)
-                                lt_min = lt_valid;
+                        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);
                         break;
+                }
+                case SD_DHCP6_OPTION_IA_PD_PREFIX: {
+                        DHCP6Address *a;
 
-                case SD_DHCP6_OPTION_IA_PD_PREFIX:
-
-                        if (ia->type != SD_DHCP6_OPTION_IA_PD)
-                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                                              "IA PD Prefix option not in IA PD option");
+                        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_pdprefix(client, option, ia, &lt_valid);
-                        if (r < 0 && r != -EINVAL)
+                        r = dhcp6_option_parse_ia_pdprefix(client, subdata, subdata_len, &a);
+                        if (r == -ENOMEM)
                                 return r;
-                        if (r >= 0 && lt_valid < lt_min)
-                                lt_min = lt_valid;
+                        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);
                         break;
+                }
+                case SD_DHCP6_OPTION_STATUS_CODE: {
+                        _cleanup_free_ char *msg = NULL;
 
-                case SD_DHCP6_OPTION_STATUS_CODE:
-
-                        status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data));
-                        if (status < 0)
-                                return status;
-
-                        if (status > 0) {
-                                if (ret_status_code)
-                                        *ret_status_code = status;
-
-                                log_dhcp6_client(client, "IA status %s",
-                                                 dhcp6_message_status_to_string(status));
-
-                                return 0;
-                        }
-
+                        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)
+                                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));
                         break;
-
+                }
                 default:
-                        log_dhcp6_client(client, "Unknown IA option %d", opt);
-                        break;
+                        log_dhcp6_client(client, "Received an IA option with an unknown sub-option %u, ignoring", subopt);
                 }
+        }
 
-                i += sizeof(*option) + optlen;
+        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(iatype) {
+        switch(option_code) {
         case SD_DHCP6_OPTION_IA_NA:
-                if (ia->ia_na.lifetime_t1 == 0 && ia->ia_na.lifetime_t2 == 0 && lt_min != UINT32_MAX) {
-                        lt_t1 = lt_min / 2;
-                        lt_t2 = lt_min / 10 * 8;
-                        ia->ia_na.lifetime_t1 = htobe32(lt_t1);
-                        ia->ia_na.lifetime_t2 = htobe32(lt_t2);
-
-                        log_dhcp6_client(client, "Computed IA NA T1 %"PRIu32"sec and T2 %"PRIu32"sec as both were zero",
-                                         lt_t1, lt_t2);
-                }
-
+                *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:
-                if (ia->ia_pd.lifetime_t1 == 0 && ia->ia_pd.lifetime_t2 == 0 && lt_min != UINT32_MAX) {
-                        lt_t1 = lt_min / 2;
-                        lt_t2 = lt_min / 10 * 8;
-                        ia->ia_pd.lifetime_t1 = htobe32(lt_t1);
-                        ia->ia_pd.lifetime_t2 = htobe32(lt_t2);
-
-                        log_dhcp6_client(client, "Computed IA PD T1 %"PRIu32"sec and T2 %"PRIu32"sec as both were zero",
-                                         lt_t1, lt_t2);
-                }
-
+                *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:
-                break;
+                assert_not_reached();
         }
 
-        if (ret_status_code)
-                *ret_status_code = 0;
-
-        return 1;
+        return 0;
 }
 
-int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
-                                struct in6_addr **addrs, size_t count) {
+int dhcp6_option_parse_addresses(
+                const uint8_t *optval,
+                size_t optlen,
+                struct in6_addr **addrs,
+                size_t *count) {
+
+        assert(optval);
+        assert(addrs);
+        assert(count);
 
         if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
-                return -EINVAL;
+                return -EBADMSG;
 
-        if (!GREEDY_REALLOC(*addrs, count * sizeof(struct in6_addr) + optlen))
+        if (!GREEDY_REALLOC(*addrs, *count + optlen / sizeof(struct in6_addr)))
                 return -ENOMEM;
 
-        memcpy(*addrs + count, optval, optlen);
+        memcpy(*addrs + *count, optval, optlen);
+        *count += optlen / sizeof(struct in6_addr);
 
-        count += optlen / sizeof(struct in6_addr);
-
-        return count;
+        return 0;
 }
 
-static int parse_domain(const uint8_t **data, uint16_t *len, char **out_domain) {
-        _cleanup_free_ char *ret = NULL;
-        const uint8_t *optval = *data;
-        uint16_t optlen = *len;
-        bool first = true;
-        size_t n = 0;
+static int parse_domain(const uint8_t **data, size_t *len, char **ret) {
+        _cleanup_free_ char *domain = NULL;
+        const uint8_t *optval;
+        size_t optlen, n = 0;
         int r;
 
+        assert(data);
+        assert(*data);
+        assert(len);
+        assert(ret);
+
+        optval = *data;
+        optlen = *len;
+
         if (optlen <= 1)
                 return -ENODATA;
 
@@ -726,42 +782,44 @@ static int parse_domain(const uint8_t **data, uint16_t *len, char **out_domain)
                         return -EMSGSIZE;
 
                 /* Literal label */
-                label = (const char *)optval;
+                label = (const char*) optval;
                 optval += c;
                 optlen -= c;
 
-                if (!GREEDY_REALLOC(ret, n + !first + DNS_LABEL_ESCAPED_MAX))
+                if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX))
                         return -ENOMEM;
 
-                if (first)
-                        first = false;
-                else
-                        ret[n++] = '.';
+                if (n != 0)
+                        domain[n++] = '.';
 
-                r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
+                r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX);
                 if (r < 0)
                         return r;
 
                 n += r;
         }
 
-        if (n) {
-                if (!GREEDY_REALLOC(ret, n + 1))
+        if (n > 0) {
+                if (!GREEDY_REALLOC(domain, n + 1))
                         return -ENOMEM;
-                ret[n] = 0;
+
+                domain[n] = '\0';
         }
 
-        *out_domain = TAKE_PTR(ret);
+        *ret = TAKE_PTR(domain);
         *data = optval;
         *len = optlen;
 
         return n;
 }
 
-int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str) {
+int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret) {
         _cleanup_free_ char *domain = NULL;
         int r;
 
+        assert(optval);
+        assert(ret);
+
         r = parse_domain(&optval, &optlen, &domain);
         if (r < 0)
                 return r;
@@ -770,39 +828,38 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char *
         if (optlen != 0)
                 return -EINVAL;
 
-        *str = TAKE_PTR(domain);
+        *ret = TAKE_PTR(domain);
         return 0;
 }
 
-int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
-        size_t idx = 0;
+int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, char ***ret) {
         _cleanup_strv_free_ char **names = NULL;
         int r;
 
+        assert(optval);
+        assert(ret);
+
         if (optlen <= 1)
                 return -ENODATA;
         if (optval[optlen - 1] != '\0')
                 return -EINVAL;
 
         while (optlen > 0) {
-                _cleanup_free_ char *ret = NULL;
+                _cleanup_free_ char *name = NULL;
 
-                r = parse_domain(&optval, &optlen, &ret);
+                r = parse_domain(&optval, &optlen, &name);
                 if (r < 0)
                         return r;
                 if (r == 0)
                         continue;
 
-                r = strv_extend(&names, ret);
+                r = strv_consume(&names, TAKE_PTR(name));
                 if (r < 0)
                         return r;
-
-                idx++;
         }
 
-        *str_arr = TAKE_PTR(names);
-
-        return idx;
+        *ret = TAKE_PTR(names);
+        return 0;
 }
 
 static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) {
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 1b2108d8..3d6d7e42 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c
@@ -7,13 +7,15 @@
 #include "ether-addr-util.h"
 #include "hexdecoct.h"
 #include "in-addr-util.h"
-#include "lldp-internal.h"
 #include "lldp-neighbor.h"
 #include "memory-util.h"
 #include "missing_network.h"
 #include "unaligned.h"
 
 static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash *state) {
+        assert(id);
+        assert(state);
+
         siphash24_compress(id->chassis_id, id->chassis_id_size, state);
         siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state);
         siphash24_compress(id->port_id, id->port_id_size, state);
@@ -21,16 +23,27 @@ static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash
 }
 
 int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y) {
+        assert(x);
+        assert(y);
+
         return memcmp_nn(x->chassis_id, x->chassis_id_size, y->chassis_id, y->chassis_id_size)
             ?: memcmp_nn(x->port_id, x->port_id_size, y->port_id, y->port_id_size);
 }
 
-DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(lldp_neighbor_hash_ops, LLDPNeighborID, lldp_neighbor_id_hash_func, lldp_neighbor_id_compare_func,
-                                      sd_lldp_neighbor, lldp_neighbor_unlink);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+        lldp_neighbor_hash_ops,
+        LLDPNeighborID,
+        lldp_neighbor_id_hash_func,
+        lldp_neighbor_id_compare_func,
+        sd_lldp_neighbor,
+        lldp_neighbor_unlink);
 
 int lldp_neighbor_prioq_compare_func(const void *a, const void *b) {
         const sd_lldp_neighbor *x = a, *y = b;
 
+        assert(x);
+        assert(y);
+
         return CMP(x->until, y->until);
 }
 
@@ -38,14 +51,15 @@ _public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) {
         if (!n)
                 return NULL;
 
-        assert(n->n_ref > 0 || n->lldp);
+        assert(n->n_ref > 0 || n->lldp_rx);
         n->n_ref++;
 
         return n;
 }
 
-static void lldp_neighbor_free(sd_lldp_neighbor *n) {
-        assert(n);
+static sd_lldp_neighbor *lldp_neighbor_free(sd_lldp_neighbor *n) {
+        if (!n)
+                return NULL;
 
         free(n->id.port_id);
         free(n->id.chassis_id);
@@ -55,7 +69,7 @@ static void lldp_neighbor_free(sd_lldp_neighbor *n) {
         free(n->mud_url);
         free(n->chassis_id_as_string);
         free(n->port_id_as_string);
-        free(n);
+        return mfree(n);
 }
 
 _public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) {
@@ -69,7 +83,7 @@ _public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) {
         assert(n->n_ref > 0);
         n->n_ref--;
 
-        if (n->n_ref <= 0 && !n->lldp)
+        if (n->n_ref <= 0 && !n->lldp_rx)
                 lldp_neighbor_free(n);
 
         return NULL;
@@ -82,18 +96,18 @@ sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) {
         if (!n)
                 return NULL;
 
-        if (!n->lldp)
+        if (!n->lldp_rx)
                 return NULL;
 
         /* Only remove the neighbor object from the hash table if it's in there, don't complain if it isn't. This is
          * because we are used as destructor call for hashmap_clear() and thus sometimes are called to de-register
          * ourselves from the hashtable and sometimes are called after we already are de-registered. */
 
-        (void) hashmap_remove_value(n->lldp->neighbor_by_id, &n->id, n);
+        (void) hashmap_remove_value(n->lldp_rx->neighbor_by_id, &n->id, n);
 
-        assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0);
+        assert_se(prioq_remove(n->lldp_rx->neighbor_by_expiry, n, &n->prioq_idx) >= 0);
 
-        n->lldp = NULL;
+        n->lldp_rx = NULL;
 
         if (n->n_ref <= 0)
                 lldp_neighbor_free(n);
@@ -114,7 +128,7 @@ sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) {
         return n;
 }
 
-static int parse_string(sd_lldp *lldp, char **s, const void *q, size_t n) {
+static int parse_string(sd_lldp_rx *lldp_rx, char **s, const void *q, size_t n) {
         const char *p = q;
         char *k;
 
@@ -122,7 +136,7 @@ static int parse_string(sd_lldp *lldp, char **s, const void *q, size_t n) {
         assert(p || n == 0);
 
         if (*s) {
-                log_lldp(lldp, "Found duplicate string, ignoring field.");
+                log_lldp_rx(lldp_rx, "Found duplicate string, ignoring field.");
                 return 0;
         }
 
@@ -135,7 +149,7 @@ static int parse_string(sd_lldp *lldp, char **s, const void *q, size_t n) {
 
         /* Look for inner NULs */
         if (memchr(p, 0, n)) {
-                log_lldp(lldp, "Found inner NUL in string, ignoring field.");
+                log_lldp_rx(lldp_rx, "Found inner NUL in string, ignoring field.");
                 return 0;
         }
 
@@ -159,14 +173,14 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
         assert(n);
 
         if (n->raw_size < sizeof(struct ether_header))
-                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                      "Received truncated packet, ignoring.");
+                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                         "Received truncated packet, ignoring.");
 
         memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h));
 
         if (h.ether_type != htobe16(ETHERTYPE_LLDP))
-                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                      "Received packet with wrong type, ignoring.");
+                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                         "Received packet with wrong type, ignoring.");
 
         if (h.ether_dhost[0] != 0x01 ||
             h.ether_dhost[1] != 0x80 ||
@@ -174,8 +188,8 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
             h.ether_dhost[3] != 0x00 ||
             h.ether_dhost[4] != 0x00 ||
             !IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e))
-                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                      "Received packet with wrong destination address, ignoring.");
+                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                         "Received packet with wrong destination address, ignoring.");
 
         memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr));
         memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr));
@@ -188,7 +202,7 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
                 uint16_t length;
 
                 if (left < 2)
-                        return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
+                        return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
                                               "TLV lacks header, ignoring.");
 
                 type = p[0] >> 1;
@@ -196,15 +210,15 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
                 p += 2, left -= 2;
 
                 if (left < length)
-                        return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                              "TLV truncated, ignoring datagram.");
+                        return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                                 "TLV truncated, ignoring datagram.");
 
                 switch (type) {
 
                 case SD_LLDP_TYPE_END:
                         if (length != 0)
-                                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                                      "End marker TLV not zero-sized, ignoring datagram.");
+                                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                                         "End marker TLV not zero-sized, ignoring datagram.");
 
                         /* Note that after processing the SD_LLDP_TYPE_END left could still be > 0
                          * as the message may contain padding (see IEEE 802.1AB-2016, sec. 8.5.12) */
@@ -214,12 +228,12 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
                 case SD_LLDP_TYPE_CHASSIS_ID:
                         if (length < 2 || length > 256)
                                 /* includes the chassis subtype, hence one extra byte */
-                                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                                      "Chassis ID field size out of range, ignoring datagram.");
+                                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                                         "Chassis ID field size out of range, ignoring datagram.");
 
                         if (n->id.chassis_id)
-                                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                                      "Duplicate chassis ID field, ignoring datagram.");
+                                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                                         "Duplicate chassis ID field, ignoring datagram.");
 
                         n->id.chassis_id = memdup(p, length);
                         if (!n->id.chassis_id)
@@ -231,12 +245,12 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
                 case SD_LLDP_TYPE_PORT_ID:
                         if (length < 2 || length > 256)
                                 /* includes the port subtype, hence one extra byte */
-                                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                                      "Port ID field size out of range, ignoring datagram.");
+                                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                                         "Port ID field size out of range, ignoring datagram.");
 
                         if (n->id.port_id)
-                                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                                      "Duplicate port ID field, ignoring datagram.");
+                                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                                         "Duplicate port ID field, ignoring datagram.");
 
                         n->id.port_id = memdup(p, length);
                         if (!n->id.port_id)
@@ -247,39 +261,39 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
 
                 case SD_LLDP_TYPE_TTL:
                         if (length != 2)
-                                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                                      "TTL field has wrong size, ignoring datagram.");
+                                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                                         "TTL field has wrong size, ignoring datagram.");
 
                         if (n->has_ttl)
-                                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                                      "Duplicate TTL field, ignoring datagram.");
+                                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                                         "Duplicate TTL field, ignoring datagram.");
 
                         n->ttl = unaligned_read_be16(p);
                         n->has_ttl = true;
                         break;
 
                 case SD_LLDP_TYPE_PORT_DESCRIPTION:
-                        r = parse_string(n->lldp, &n->port_description, p, length);
+                        r = parse_string(n->lldp_rx, &n->port_description, p, length);
                         if (r < 0)
                                 return r;
                         break;
 
                 case SD_LLDP_TYPE_SYSTEM_NAME:
-                        r = parse_string(n->lldp, &n->system_name, p, length);
+                        r = parse_string(n->lldp_rx, &n->system_name, p, length);
                         if (r < 0)
                                 return r;
                         break;
 
                 case SD_LLDP_TYPE_SYSTEM_DESCRIPTION:
-                        r = parse_string(n->lldp, &n->system_description, p, length);
+                        r = parse_string(n->lldp_rx, &n->system_description, p, length);
                         if (r < 0)
                                 return r;
                         break;
 
                 case SD_LLDP_TYPE_SYSTEM_CAPABILITIES:
                         if (length != 4)
-                                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                                      "System capabilities field has wrong size.");
+                                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                                         "System capabilities field has wrong size.");
 
                         n->system_capabilities = unaligned_read_be16(p);
                         n->enabled_capabilities = unaligned_read_be16(p + 2);
@@ -288,14 +302,13 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
 
                 case SD_LLDP_TYPE_PRIVATE:
                         if (length < 4)
-                                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                                      "Found private TLV that is too short, ignoring.");
+                                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                                         "Found private TLV that is too short, ignoring.");
 
                         /* RFC 8520: MUD URL */
-                        if (memcmp(p, SD_LLDP_OUI_MUD, sizeof(SD_LLDP_OUI_MUD)) == 0 &&
-                            p[sizeof(SD_LLDP_OUI_MUD)] == SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION) {
-                                r = parse_string(n->lldp, &n->mud_url, p + sizeof(SD_LLDP_OUI_MUD) + 1,
-                                                 length - 1 - sizeof(SD_LLDP_OUI_MUD));
+                        if (memcmp(p, SD_LLDP_OUI_IANA_MUD, sizeof(SD_LLDP_OUI_IANA_MUD)) == 0) {
+                                r = parse_string(n->lldp_rx, &n->mud_url, p + sizeof(SD_LLDP_OUI_IANA_MUD),
+                                                 length - sizeof(SD_LLDP_OUI_IANA_MUD));
                                 if (r < 0)
                                         return r;
                         }
@@ -307,8 +320,8 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
 
 end_marker:
         if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl)
-                return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG),
-                                      "One or more mandatory TLV missing in datagram. Ignoring.");
+                return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                         "One or more mandatory TLV missing in datagram. Ignoring.");
 
         n->rindex = sizeof(struct ether_header);
 
@@ -330,8 +343,8 @@ void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) {
         } else
                 n->until = 0;
 
-        if (n->lldp)
-                prioq_reshuffle(n->lldp->neighbor_by_expiry, n, &n->prioq_idx);
+        if (n->lldp_rx)
+                prioq_reshuffle(n->lldp_rx->neighbor_by_expiry, n, &n->prioq_idx);
 }
 
 bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) {
diff --git a/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.h b/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.h
index a5718c8c..016286b1 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.h
@@ -5,10 +5,10 @@
 #include <stdbool.h>
 #include <sys/types.h>
 
-#include "sd-lldp.h"
+#include "sd-lldp-rx.h"
 
 #include "hash-funcs.h"
-#include "lldp-internal.h"
+#include "lldp-rx-internal.h"
 #include "time-util.h"
 
 typedef struct LLDPNeighborID {
@@ -21,8 +21,8 @@ typedef struct LLDPNeighborID {
 } LLDPNeighborID;
 
 struct sd_lldp_neighbor {
-        /* Neighbor objects stay around as long as they are linked into an "sd_lldp" object or n_ref > 0. */
-        sd_lldp *lldp;
+        /* Neighbor objects stay around as long as they are linked into an "sd_lldp_rx" object or n_ref > 0. */
+        sd_lldp_rx *lldp_rx;
         unsigned n_ref;
 
         triple_timestamp timestamp;
diff --git a/src/libnm-systemd-core/src/libsystemd-network/lldp-network.c b/src/libnm-systemd-core/src/libsystemd-network/lldp-network.c
index e874e863..34a4f3f0 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/lldp-network.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/lldp-network.c
@@ -58,15 +58,18 @@ int lldp_network_bind_raw_socket(int ifindex) {
         if (r < 0)
                 return -errno;
 
+        /* customer bridge */
         r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
         if (r < 0)
                 return -errno;
 
+        /* non TPMR bridge */
         mreq.mr_address[ETH_ALEN - 1] = 0x03;
         r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
         if (r < 0)
                 return -errno;
 
+        /* nearest bridge */
         mreq.mr_address[ETH_ALEN - 1] = 0x0E;
         r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
         if (r < 0)
diff --git a/src/libnm-systemd-core/src/libsystemd-network/lldp-internal.h b/src/libnm-systemd-core/src/libsystemd-network/lldp-rx-internal.h
index cf0578c5..83d0bc46 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/lldp-internal.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/lldp-rx-internal.h
@@ -2,13 +2,13 @@
 #pragma once
 
 #include "sd-event.h"
-#include "sd-lldp.h"
+#include "sd-lldp-rx.h"
 
 #include "hashmap.h"
-#include "log-link.h"
+#include "network-common.h"
 #include "prioq.h"
 
-struct sd_lldp {
+struct sd_lldp_rx {
         unsigned n_ref;
 
         int ifindex;
@@ -25,7 +25,7 @@ struct sd_lldp {
 
         uint64_t neighbors_max;
 
-        sd_lldp_callback_t callback;
+        sd_lldp_rx_callback_t callback;
         void *userdata;
 
         uint16_t capability_mask;
@@ -33,16 +33,16 @@ struct sd_lldp {
         struct ether_addr filter_address;
 };
 
-const char* lldp_event_to_string(sd_lldp_event_t e) _const_;
-sd_lldp_event_t lldp_event_from_string(const char *s) _pure_;
+const char* lldp_rx_event_to_string(sd_lldp_rx_event_t e) _const_;
+sd_lldp_rx_event_t lldp_rx_event_from_string(const char *s) _pure_;
 
-#define log_lldp_errno(lldp, error, fmt, ...)           \
+#define log_lldp_rx_errno(lldp_rx, error, fmt, ...)     \
         log_interface_prefix_full_errno(                \
-                "LLDP: ",                               \
-                sd_lldp_get_ifname(lldp),               \
+                "LLDP Rx: ",                            \
+                sd_lldp_rx, lldp_rx,                    \
                 error, fmt, ##__VA_ARGS__)
-#define log_lldp(lldp, fmt, ...)                        \
+#define log_lldp_rx(lldp_rx, fmt, ...)                  \
         log_interface_prefix_full_errno_zerook(         \
-                "LLDP: ",                               \
-                sd_lldp_get_ifname(lldp),               \
+                "LLDP Rx: ",                            \
+                sd_lldp_rx, lldp_rx,                    \
                 0, fmt, ##__VA_ARGS__)
diff --git a/src/libnm-systemd-core/src/libsystemd-network/network-common.c b/src/libnm-systemd-core/src/libsystemd-network/network-common.c
index b9494c15..bbc28941 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/network-common.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/network-common.c
@@ -4,23 +4,14 @@
 
 #include "format-util.h"
 #include "network-common.h"
-#include "string-util.h"
-
-const char *get_ifname(int ifindex, char **ifname) {
-        char buf[IF_NAMESIZE + 1];
 
+int get_ifname(int ifindex, char **ifname) {
         assert(ifname);
 
         /* This sets ifname only when it is not set yet. */
 
         if (*ifname)
-                return *ifname;
-
-        if (ifindex <= 0)
-                return NULL;
-
-        if (!format_ifname(ifindex, buf))
-                return NULL;
+                return 0;
 
-        return *ifname = strdup(buf);
+        return format_ifname_alloc(ifindex, ifname);
 }
diff --git a/src/libnm-systemd-core/src/libsystemd-network/network-common.h b/src/libnm-systemd-core/src/libsystemd-network/network-common.h
index 76a6c4a9..d43b76d4 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/network-common.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/network-common.h
@@ -1,4 +1,28 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
-const char *get_ifname(int ifindex, char **ifname);
+#include "log-link.h"
+
+#define log_interface_prefix_full_errno_zerook(prefix, type, val, error, fmt, ...) \
+        ({                                                              \
+                int _e = (error);                                       \
+                if (DEBUG_LOGGING) {                                    \
+                        const char *_n = NULL;                          \
+                                                                        \
+                        (void) type##_get_ifname(val, &_n);             \
+                        log_interface_full_errno_zerook(                \
+                                _n, LOG_DEBUG, _e, prefix fmt,          \
+                                ##__VA_ARGS__);                         \
+                }                                                       \
+                -ERRNO_VALUE(_e);                                       \
+        })
+
+#define log_interface_prefix_full_errno(prefix, type, val, error, fmt, ...) \
+        ({                                                              \
+                int _error = (error);                                   \
+                ASSERT_NON_ZERO(_error);                                \
+                log_interface_prefix_full_errno_zerook(                 \
+                        prefix, type, val, _error, fmt, ##__VA_ARGS__); \
+        })
+
+int get_ifname(int ifindex, char **ifname);
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 94e43da3..cb984b6e 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
@@ -119,6 +119,9 @@ struct sd_dhcp_client {
         sd_dhcp_lease *lease;
         usec_t start_delay;
         int ip_service_type;
+
+        /* Ignore ifindex when generating iaid. See dhcp_identifier_set_iaid(). */
+        bool test_mode;
 };
 
 static const uint8_t default_req_opts[] = {
@@ -191,7 +194,7 @@ int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret) {
                 if (len != sizeof_field(sd_dhcp_client_id, eth))
                         return -EINVAL;
 
-                r = asprintf(&t, "%x:%x:%x:%x:%x:%x",
+                r = asprintf(&t, "%02x:%02x:%02x:%02x:%02x:%02x",
                              client_id->eth.haddr[0],
                              client_id->eth.haddr[1],
                              client_id->eth.haddr[2],
@@ -237,7 +240,7 @@ int sd_dhcp_client_set_callback(
 int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) {
         assert_return(client, -EINVAL);
 
-        client->request_broadcast = !!broadcast;
+        client->request_broadcast = broadcast;
 
         return 0;
 }
@@ -296,11 +299,19 @@ int sd_dhcp_client_set_ifname(sd_dhcp_client *client, const char *ifname) {
         return free_and_strdup(&client->ifname, ifname);
 }
 
-const char *sd_dhcp_client_get_ifname(sd_dhcp_client *client) {
-        if (!client)
-                return NULL;
+int sd_dhcp_client_get_ifname(sd_dhcp_client *client, const char **ret) {
+        int r;
+
+        assert_return(client, -EINVAL);
+
+        r = get_ifname(client->ifindex, &client->ifname);
+        if (r < 0)
+                return r;
 
-        return get_ifname(client->ifindex, &client->ifname);
+        if (ret)
+                *ret = client->ifname;
+
+        return 0;
 }
 
 int sd_dhcp_client_set_mac(
@@ -470,7 +481,8 @@ static int dhcp_client_set_iaid_duid_internal(
                 else {
                         r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr,
                                                      client->mac_addr_len,
-                                                     true,
+                                                     /* legacy_unstable_byteorder = */ true,
+                                                     /* use_mac = */ client->test_mode,
                                                      &client->client_id.ns.iaid);
                         if (r < 0)
                                 return log_dhcp_client_errno(client, r, "Failed to set IAID: %m");
@@ -560,6 +572,12 @@ int sd_dhcp_client_set_duid_llt(
 }
 #endif /* NM_IGNORED */
 
+void dhcp_client_set_test_mode(sd_dhcp_client *client, bool test_mode) {
+        assert(client);
+
+        client->test_mode = test_mode;
+}
+
 int sd_dhcp_client_set_hostname(
                 sd_dhcp_client *client,
                 const char *hostname) {
@@ -721,7 +739,7 @@ static int client_notify(sd_dhcp_client *client, int event) {
 static int client_initialize(sd_dhcp_client *client) {
         assert_return(client, -EINVAL);
 
-        client->receive_message = sd_event_source_unref(client->receive_message);
+        client->receive_message = sd_event_source_disable_unref(client->receive_message);
 
         client->fd = safe_close(client->fd);
 
@@ -865,7 +883,9 @@ static int client_message_init(
                 client->client_id.type = 255;
 
                 r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len,
-                                             true, &client->client_id.ns.iaid);
+                                             /* legacy_unstable_byteorder = */ true,
+                                             /* use_mac = */ client->test_mode,
+                                             &client->client_id.ns.iaid);
                 if (r < 0)
                         return r;
 
@@ -1301,7 +1321,7 @@ static int client_timeout_resend(
                 goto error;
 
         default:
-                assert_not_reached("Unhandled choice");
+                assert_not_reached();
         }
 
         r = event_reset_time(client->event, &client->timeout_resend,
@@ -1485,7 +1505,7 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata)
 
         assert(client);
 
-        client->receive_message = sd_event_source_unref(client->receive_message);
+        client->receive_message = sd_event_source_disable_unref(client->receive_message);
         client->fd = safe_close(client->fd);
 
         client->state = DHCP_STATE_REBINDING;
@@ -1580,18 +1600,17 @@ static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force,
         if (r != DHCP_FORCERENEW)
                 return -ENOMSG;
 
-#if 0 /* NM_IGNORED */
+#if 0
         log_dhcp_client(client, "FORCERENEW");
 
         return 0;
-#else /* NM_IGNORED */
-        /* NM: patch out the handling of FORCERENEW. We don't implement rfc3118 (Authentication
-         * for DHCP Messages) nor rfc6704 (Forcerenew Nonce Authentication) so accepting
-         * unauthenticated FORCERENEW requests is a security issue (CVE-2020-13529)
-         * See: https://github.com/systemd/systemd/issues/16774 */
-        log_dhcp_client(client, "ignore FORCERENEW");
+#else
+        /* FIXME: Ignore FORCERENEW requests until we implement RFC3118 (Authentication for DHCP
+         * Messages) and/or RFC6704 (Forcerenew Nonce Authentication), as unauthenticated FORCERENEW
+         * requests causes a security issue (TALOS-2020-1142, CVE-2020-13529). */
+        log_dhcp_client(client, "Received FORCERENEW, ignoring.");
         return -ENOMSG;
-#endif /* NM_IGNORED */
+#endif
 }
 
 static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) {
@@ -1680,7 +1699,6 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t le
 
 static int client_set_lease_timeouts(sd_dhcp_client *client) {
         usec_t time_now;
-        char time_string[FORMAT_TIMESPAN_MAX];
         int r;
 
         assert(client);
@@ -1742,7 +1760,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
                 return 0;
 
         log_dhcp_client(client, "lease expires in %s",
-                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->expire_time - time_now, USEC_PER_SEC));
+                        FORMAT_TIMESPAN(client->expire_time - time_now, USEC_PER_SEC));
 
         /* arm T2 timeout */
         r = event_reset_time(client->event, &client->timeout_t2,
@@ -1758,7 +1776,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
                 return 0;
 
         log_dhcp_client(client, "T2 expires in %s",
-                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t2_time - time_now, USEC_PER_SEC));
+                        FORMAT_TIMESPAN(client->t2_time - time_now, USEC_PER_SEC));
 
         /* arm T1 timeout */
         r = event_reset_time(client->event, &client->timeout_t1,
@@ -1771,15 +1789,14 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
 
         if (client->t1_time > time_now)
                 log_dhcp_client(client, "T1 expires in %s",
-                                format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t1_time - time_now, USEC_PER_SEC));
+                                FORMAT_TIMESPAN(client->t1_time - time_now, USEC_PER_SEC));
 
         return 0;
 }
 
 static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) {
         DHCP_CLIENT_DONT_DESTROY(client);
-        char time_string[FORMAT_TIMESPAN_MAX];
-        int r = 0, notify_event = 0;
+        int r, notify_event;
 
         assert(client);
         assert(client->event);
@@ -1789,22 +1806,19 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
         case DHCP_STATE_SELECTING:
 
                 r = client_handle_offer(client, message, len);
-                if (r >= 0) {
+                if (r == -ENOMSG)
+                        return 0; /* invalid message, let's ignore it */
+                if (r < 0)
+                        goto error;
 
-                        client->state = DHCP_STATE_REQUESTING;
-                        client->attempt = 0;
-
-                        r = event_reset_time(client->event, &client->timeout_resend,
-                                             clock_boottime_or_monotonic(),
-                                             0, 0,
-                                             client_timeout_resend, client,
-                                             client->event_priority, "dhcp4-resend-timer", true);
-                        if (r < 0)
-                                goto error;
-                } else if (r == -ENOMSG)
-                        /* invalid message, let's ignore it */
-                        return 0;
+                client->state = DHCP_STATE_REQUESTING;
+                client->attempt = 0;
 
+                r = event_reset_time(client->event, &client->timeout_resend,
+                                     clock_boottime_or_monotonic(),
+                                     0, 0,
+                                     client_timeout_resend, client,
+                                     client->event_priority, "dhcp4-resend-timer", true);
                 break;
 
         case DHCP_STATE_REBOOTING:
@@ -1813,47 +1827,9 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
         case DHCP_STATE_REBINDING:
 
                 r = client_handle_ack(client, message, len);
-                if (r >= 0) {
-                        client->start_delay = 0;
-                        (void) event_source_disable(client->timeout_resend);
-                        client->receive_message =
-                                sd_event_source_unref(client->receive_message);
-                        client->fd = safe_close(client->fd);
-
-                        if (IN_SET(client->state, DHCP_STATE_REQUESTING,
-                                   DHCP_STATE_REBOOTING))
-                                notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
-                        else if (r != SD_DHCP_CLIENT_EVENT_IP_ACQUIRE)
-                                notify_event = r;
-
-                        client->state = DHCP_STATE_BOUND;
-                        client->attempt = 0;
-
-                        client->last_addr = client->lease->address;
-
-                        r = client_set_lease_timeouts(client);
-                        if (r < 0) {
-                                log_dhcp_client(client, "could not set lease timeouts");
-                                goto error;
-                        }
-
-                        r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type);
-                        if (r < 0) {
-                                log_dhcp_client(client, "could not bind UDP socket");
-                                goto error;
-                        }
-
-                        client->fd = r;
-
-                        client_initialize_io_events(client, client_receive_message_udp);
-
-                        if (notify_event) {
-                                client_notify(client, notify_event);
-                                if (client->state == DHCP_STATE_STOPPED)
-                                        return 0;
-                        }
-
-                } else if (r == -EADDRNOTAVAIL) {
+                if (r == -ENOMSG)
+                        return 0; /* invalid message, let's ignore it */
+                if (r == -EADDRNOTAVAIL) {
                         /* got a NAK, let's restart the client */
                         client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
 
@@ -1865,39 +1841,75 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
                         if (r < 0)
                                 goto error;
 
-                        log_dhcp_client(client, "REBOOT in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX,
-                                                                                client->start_delay, USEC_PER_SEC));
+                        log_dhcp_client(client, "REBOOT in %s", FORMAT_TIMESPAN(client->start_delay, USEC_PER_SEC));
 
                         client->start_delay = CLAMP(client->start_delay * 2,
                                                     RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC);
-
-                        return 0;
-                } else if (r == -ENOMSG)
-                        /* invalid message, let's ignore it */
                         return 0;
+                }
+                if (r < 0)
+                        goto error;
+
+                if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING))
+                        notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
+                else
+                        notify_event = r;
+
+                client->start_delay = 0;
+                (void) event_source_disable(client->timeout_resend);
+                client->receive_message = sd_event_source_disable_unref(client->receive_message);
+                client->fd = safe_close(client->fd);
+
+                client->state = DHCP_STATE_BOUND;
+                client->attempt = 0;
+
+                client->last_addr = client->lease->address;
+
+                r = client_set_lease_timeouts(client);
+                if (r < 0) {
+                        log_dhcp_client(client, "could not set lease timeouts");
+                        goto error;
+                }
+
+                r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type);
+                if (r < 0) {
+                        log_dhcp_client(client, "could not bind UDP socket");
+                        goto error;
+                }
+
+                client->fd = r;
+
+                client_initialize_io_events(client, client_receive_message_udp);
 
+                if (IN_SET(client->state, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING) &&
+                    notify_event == SD_DHCP_CLIENT_EVENT_IP_ACQUIRE)
+                        /* FIXME: hmm, maybe this is a bug... */
+                        log_dhcp_client(client, "client_handle_ack() returned SD_DHCP_CLIENT_EVENT_IP_ACQUIRE while DHCP client is %s the address, skipping callback.",
+                                        client->state == DHCP_STATE_RENEWING ? "renewing" : "rebinding");
+                else
+                        client_notify(client, notify_event);
                 break;
 
         case DHCP_STATE_BOUND:
                 r = client_handle_forcerenew(client, message, len);
-                if (r >= 0) {
-                        r = client_timeout_t1(NULL, 0, client);
-                        if (r < 0)
-                                goto error;
-                } else if (r == -ENOMSG)
-                        /* invalid message, let's ignore it */
-                        return 0;
+                if (r == -ENOMSG)
+                        return 0; /* invalid message, let's ignore it */
+                if (r < 0)
+                        goto error;
 
+                r = client_timeout_t1(NULL, 0, client);
                 break;
 
         case DHCP_STATE_INIT:
         case DHCP_STATE_INIT_REBOOT:
-
+                r = 0;
                 break;
 
         case DHCP_STATE_STOPPED:
                 r = -EINVAL;
                 goto error;
+        default:
+                assert_not_reached();
         }
 
 error:
@@ -2227,17 +2239,15 @@ static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) {
 
         log_dhcp_client(client, "FREE");
 
+        client_initialize(client);
+
         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
         client->timeout_t1 = sd_event_source_unref(client->timeout_t1);
         client->timeout_t2 = sd_event_source_unref(client->timeout_t2);
         client->timeout_expire = sd_event_source_unref(client->timeout_expire);
 
-        client_initialize(client);
-
         sd_dhcp_client_detach_event(client);
 
-        sd_dhcp_lease_unref(client->lease);
-
         set_free(client->req_opts);
         free(client->hostname);
         free(client->vendor_class_identifier);
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 888dfa2d..5398bdff 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
@@ -90,6 +90,9 @@ struct sd_dhcp6_client {
         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[] = {
@@ -182,11 +185,19 @@ int sd_dhcp6_client_set_ifname(sd_dhcp6_client *client, const char *ifname) {
         return free_and_strdup(&client->ifname, ifname);
 }
 
-const char *sd_dhcp6_client_get_ifname(sd_dhcp6_client *client) {
-        if (!client)
-                return NULL;
+int sd_dhcp6_client_get_ifname(sd_dhcp6_client *client, const char **ret) {
+        int r;
+
+        assert_return(client, -EINVAL);
+
+        r = get_ifname(client->ifindex, &client->ifname);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = client->ifname;
 
-        return get_ifname(client->ifindex, &client->ifname);
+        return 0;
 }
 
 int sd_dhcp6_client_set_local_address(
@@ -408,6 +419,12 @@ int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
         return 0;
 }
 
+void dhcp6_client_set_test_mode(sd_dhcp6_client *client, bool test_mode) {
+        assert(client);
+
+        client->test_mode = test_mode;
+}
+
 int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) {
         assert_return(client, -EINVAL);
         assert_return(iaid, -EINVAL);
@@ -947,7 +964,6 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
         usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0;
         usec_t max_retransmit_duration = 0;
         uint8_t max_retransmit_count = 0;
-        char time_string[FORMAT_TIMESPAN_MAX];
 
         assert(s);
         assert(client);
@@ -1043,7 +1059,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
         }
 
         log_dhcp6_client(client, "Next retransmission in %s",
-                         format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->retransmit_time, USEC_PER_SEC));
+                         FORMAT_TIMESPAN(client->retransmit_time, USEC_PER_SEC));
 
         r = event_reset_time(client->event, &client->timeout_resend,
                              clock_boottime_or_monotonic(),
@@ -1083,7 +1099,10 @@ static int client_ensure_iaid(sd_dhcp6_client *client) {
         if (client->iaid_set)
                 return 0;
 
-        r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, true, &iaid);
+        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;
 
@@ -1100,11 +1119,9 @@ static int client_parse_message(
                 size_t len,
                 sd_dhcp6_lease *lease) {
 
-        uint16_t ia_na_status = 0, ia_pd_status = 0;
-        uint32_t lt_t1 = ~0, lt_t2 = ~0;
+        uint32_t lt_t1 = UINT32_MAX, lt_t2 = UINT32_MAX;
         usec_t irt = IRT_DEFAULT;
         bool clientid = false;
-        size_t pos = 0;
         int r;
 
         assert(client);
@@ -1113,50 +1130,35 @@ static int client_parse_message(
         assert(lease);
 
         len -= sizeof(DHCP6Message);
+        for (size_t offset = 0; offset < len;) {
+                uint16_t optcode;
+                size_t optlen;
+                const uint8_t *optval;
 
-        while (pos < len) {
-                DHCP6Option *option = (DHCP6Option *) &message->options[pos];
-                uint16_t optcode, optlen;
-                be32_t iaid_lease;
-                int  status;
-                uint8_t *optval;
-
-                if (len < pos + offsetof(DHCP6Option, data))
-                        return -ENOBUFS;
-
-                optcode = be16toh(option->code);
-                optlen = be16toh(option->len);
-                optval = option->data;
-
-                if (len < pos + offsetof(DHCP6Option, data) + optlen)
-                        return -ENOBUFS;
+                r = dhcp6_option_parse(message->options, len, &offset, &optcode, &optlen, &optval);
+                if (r < 0)
+                        return r;
 
                 switch (optcode) {
                 case SD_DHCP6_OPTION_CLIENTID:
-                        if (clientid) {
-                                log_dhcp6_client(client, "%s contains multiple clientids",
-                                                 dhcp6_message_type_to_string(message->type));
-                                return -EINVAL;
-                        }
+                        if (clientid)
+                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple clientids",
+                                                              dhcp6_message_type_to_string(message->type));
 
                         if (optlen != client->duid_len ||
-                            memcmp(&client->duid, optval, optlen) != 0) {
-                                log_dhcp6_client(client, "%s DUID does not match",
-                                                 dhcp6_message_type_to_string(message->type));
+                            memcmp(&client->duid, optval, optlen) != 0)
+                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s DUID does not match",
+                                                              dhcp6_message_type_to_string(message->type));
 
-                                return -EINVAL;
-                        }
                         clientid = true;
 
                         break;
 
                 case SD_DHCP6_OPTION_SERVERID:
                         r = dhcp6_lease_get_serverid(lease, NULL, NULL);
-                        if (r >= 0) {
-                                log_dhcp6_client(client, "%s contains multiple serverids",
-                                                 dhcp6_message_type_to_string(message->type));
-                                return -EINVAL;
-                        }
+                        if (r >= 0)
+                                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple serverids",
+                                                              dhcp6_message_type_to_string(message->type));
 
                         r = dhcp6_lease_set_serverid(lease, optval, optlen);
                         if (r < 0)
@@ -1174,87 +1176,75 @@ static int client_parse_message(
 
                         break;
 
-                case SD_DHCP6_OPTION_STATUS_CODE:
-                        status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option));
-                        if (status < 0)
-                                return status;
-
-                        if (status > 0) {
-                                log_dhcp6_client(client, "%s Status %s",
-                                                 dhcp6_message_type_to_string(message->type),
-                                                 dhcp6_message_status_to_string(status));
+                case SD_DHCP6_OPTION_STATUS_CODE: {
+                        _cleanup_free_ char *msg = NULL;
 
-                                return -EINVAL;
-                        }
+                        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 = {};
 
-                case SD_DHCP6_OPTION_IA_NA:
                         if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
-                                log_dhcp6_client(client, "Information request ignoring IA NA option");
-
+                                log_dhcp6_client(client, "Ignoring IA NA option in information requesting mode.");
                                 break;
                         }
 
-                        r = dhcp6_option_parse_ia(client, option, &lease->ia, &ia_na_status);
-                        if (r < 0 && r != -ENOMSG)
+                        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;
 
-                        if (ia_na_status == DHCP6_STATUS_NO_ADDRS_AVAIL) {
-                                pos += offsetof(DHCP6Option, data) + optlen;
+                        if (lease->ia.addresses) {
+                                log_dhcp6_client(client, "Received duplicate matching IA_NA option, ignoring.");
                                 continue;
                         }
 
-                        r = dhcp6_lease_get_iaid(lease, &iaid_lease);
-                        if (r < 0)
-                                return r;
-
-                        if (client->ia_na.ia_na.id != iaid_lease) {
-                                log_dhcp6_client(client, "%s has wrong IAID for IA NA",
-                                                 dhcp6_message_type_to_string(message->type));
-                                return -EINVAL;
-                        }
+                        lease->ia = ia;
+                        ia = (DHCP6IA) {};
 
-                        if (lease->ia.addresses) {
-                                lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1));
-                                lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t1));
-                        }
+                        lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1));
+                        lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t2));
 
                         break;
+                }
+                case SD_DHCP6_OPTION_IA_PD: {
+                        _cleanup_(dhcp6_lease_free_ia) DHCP6IA ia = {};
 
-                case SD_DHCP6_OPTION_IA_PD:
                         if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
-                                log_dhcp6_client(client, "Information request ignoring IA PD option");
-
+                                log_dhcp6_client(client, "Ignoring IA PD option in information requesting mode.");
                                 break;
                         }
 
-                        r = dhcp6_option_parse_ia(client, option, &lease->pd, &ia_pd_status);
-                        if (r < 0 && r != -ENOMSG)
+                        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;
 
-                        if (ia_pd_status == DHCP6_STATUS_NO_PREFIX_AVAIL) {
-                                pos += offsetof(DHCP6Option, data) + optlen;
+                        if (lease->pd.addresses) {
+                                log_dhcp6_client(client, "Received duplicate matching IA_PD option, ignoring.");
                                 continue;
                         }
 
-                        r = dhcp6_lease_get_pd_iaid(lease, &iaid_lease);
-                        if (r < 0)
-                                return r;
-
-                        if (client->ia_pd.ia_pd.id != iaid_lease) {
-                                log_dhcp6_client(client, "%s has wrong IAID for IA PD",
-                                                 dhcp6_message_type_to_string(message->type));
-                                return -EINVAL;
-                        }
+                        lease->pd = ia;
+                        ia = (DHCP6IA) {};
 
-                        if (lease->pd.addresses) {
-                                lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1));
-                                lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2));
-                        }
+                        lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1));
+                        lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2));
 
                         break;
-
+                }
                 case SD_DHCP6_OPTION_RAPID_COMMIT:
                         r = dhcp6_lease_set_rapid_commit(lease);
                         if (r < 0)
@@ -1263,28 +1253,28 @@ static int client_parse_message(
                         break;
 
                 case SD_DHCP6_OPTION_DNS_SERVERS:
-                        r = dhcp6_lease_set_dns(lease, optval, optlen);
+                        r = dhcp6_lease_add_dns(lease, optval, optlen);
                         if (r < 0)
                                 return r;
 
                         break;
 
                 case SD_DHCP6_OPTION_DOMAIN_LIST:
-                        r = dhcp6_lease_set_domains(lease, optval, optlen);
+                        r = dhcp6_lease_add_domains(lease, optval, optlen);
                         if (r < 0)
                                 return r;
 
                         break;
 
                 case SD_DHCP6_OPTION_NTP_SERVER:
-                        r = dhcp6_lease_set_ntp(lease, optval, optlen);
+                        r = dhcp6_lease_add_ntp(lease, optval, optlen);
                         if (r < 0)
                                 return r;
 
                         break;
 
                 case SD_DHCP6_OPTION_SNTP_SERVERS:
-                        r = dhcp6_lease_set_sntp(lease, optval, optlen);
+                        r = dhcp6_lease_add_sntp(lease, optval, optlen);
                         if (r < 0)
                                 return r;
 
@@ -1304,30 +1294,21 @@ static int client_parse_message(
                         irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC;
                         break;
                 }
-
-                pos += offsetof(DHCP6Option, data) + optlen;
-        }
-
-        if (ia_na_status > 0 && ia_pd_status > 0) {
-                log_dhcp6_client(client, "No IA_PD prefix or IA_NA address received. Ignoring.");
-                return -EINVAL;
         }
 
-        if (!clientid) {
-                log_dhcp6_client(client, "%s has incomplete options",
-                                 dhcp6_message_type_to_string(message->type));
-                return -EINVAL;
-        }
+        if (!clientid)
+                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s has incomplete options",
+                                              dhcp6_message_type_to_string(message->type));
 
         if (client->state != DHCP6_STATE_INFORMATION_REQUEST) {
                 r = dhcp6_lease_get_serverid(lease, NULL, NULL);
-                if (r < 0) {
-                        log_dhcp6_client(client, "%s has no server id",
-                                         dhcp6_message_type_to_string(message->type));
-                        return -EINVAL;
-                }
+                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.addresses && !lease->pd.addresses)
+                        return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "No IA_PD prefix or IA_NA address received. Ignoring.");
 
-        } else {
                 if (lease->ia.addresses) {
                         lease->ia.ia_na.lifetime_t1 = htobe32(lt_t1);
                         lease->ia.ia_na.lifetime_t2 = htobe32(lt_t2);
@@ -1478,15 +1459,16 @@ static int client_receive_message(
                 return 0;
         }
 
-        if (client->transaction_id != (message->transaction_id &
-                                       htobe32(0x00ffffff)))
+        if (client->transaction_id != (message->transaction_id & htobe32(0x00ffffff)))
                 return 0;
 
         switch (client->state) {
         case DHCP6_STATE_INFORMATION_REQUEST:
                 r = client_receive_reply(client, message, len);
-                if (r < 0)
+                if (r < 0) {
+                        log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m");
                         return 0;
+                }
 
                 client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST);
 
@@ -1496,10 +1478,13 @@ static int client_receive_message(
 
         case DHCP6_STATE_SOLICITATION:
                 r = client_receive_advertise(client, message, len);
+                if (r < 0) {
+                        log_dhcp6_client_errno(client, r, "Failed to process received advertise message, ignoring: %m");
+                        return 0;
+                }
 
                 if (r == DHCP6_STATE_REQUEST) {
                         client_start(client, r);
-
                         break;
                 }
 
@@ -1509,11 +1494,12 @@ static int client_receive_message(
         case DHCP6_STATE_REBIND:
 
                 r = client_receive_reply(client, message, len);
-                if (r < 0)
+                if (r < 0) {
+                        log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m");
                         return 0;
+                }
 
                 if (r == DHCP6_STATE_BOUND) {
-
                         r = client_start(client, DHCP6_STATE_BOUND);
                         if (r < 0) {
                                 client_stop(client, r);
@@ -1564,7 +1550,6 @@ static int client_get_lifetime(sd_dhcp6_client *client, uint32_t *lifetime_t1,
 static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
         int r;
         usec_t timeout, time_now;
-        char time_string[FORMAT_TIMESPAN_MAX];
         uint32_t lifetime_t1, lifetime_t2;
 
         assert_return(client, -EINVAL);
@@ -1637,8 +1622,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
 
                 timeout = client_timeout_compute_random(lifetime_t1 * USEC_PER_SEC);
 
-                log_dhcp6_client(client, "T1 expires in %s",
-                                 format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
+                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(),
@@ -1650,8 +1634,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
 
                 timeout = client_timeout_compute_random(lifetime_t2 * USEC_PER_SEC);
 
-                log_dhcp6_client(client, "T2 expires in %s",
-                                 format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
+                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(),
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 e37acfe8..85649d92 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
@@ -195,86 +195,69 @@ void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) {
                 lease->prefix_iter = lease->pd.addresses;
 }
 
-int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
-        int r;
-
+int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
         assert_return(lease, -EINVAL);
         assert_return(optval, -EINVAL);
 
-        if (!optlen)
+        if (optlen == 0)
                 return 0;
 
-        r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->dns,
-                                        lease->dns_count);
-        if (r < 0)
-                return r;
-
-        lease->dns_count = r;
-
-        return 0;
+        return dhcp6_option_parse_addresses(optval, optlen, &lease->dns, &lease->dns_count);
 }
 
-int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs) {
+int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret) {
         assert_return(lease, -EINVAL);
-        assert_return(addrs, -EINVAL);
+        assert_return(ret, -EINVAL);
 
-        if (lease->dns_count) {
-                *addrs = lease->dns;
-                return lease->dns_count;
-        }
+        if (!lease->dns)
+                return -ENOENT;
 
-        return -ENOENT;
+        *ret = lease->dns;
+        return lease->dns_count;
 }
 
-int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval,
-                            size_t optlen) {
+int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
+        _cleanup_strv_free_ char **domains = NULL;
         int r;
-        char **domains;
 
         assert_return(lease, -EINVAL);
         assert_return(optval, -EINVAL);
 
-        if (!optlen)
+        if (optlen == 0)
                 return 0;
 
         r = dhcp6_option_parse_domainname_list(optval, optlen, &domains);
         if (r < 0)
-                return 0;
-
-        strv_free_and_replace(lease->domains, domains);
-        lease->domains_count = r;
+                return r;
 
-        return r;
+        return strv_extend_strv(&lease->domains, domains, true);
 }
 
-int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains) {
+int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) {
         assert_return(lease, -EINVAL);
-        assert_return(domains, -EINVAL);
+        assert_return(ret, -EINVAL);
 
-        if (lease->domains_count) {
-                *domains = lease->domains;
-                return lease->domains_count;
-        }
+        if (!lease->domains)
+                return -ENOENT;
 
-        return -ENOENT;
+        *ret = lease->domains;
+        return strv_length(lease->domains);
 }
 
-int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
+int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
         int r;
-        uint16_t subopt;
-        size_t sublen;
-        uint8_t *subval;
 
         assert_return(lease, -EINVAL);
         assert_return(optval, -EINVAL);
 
-        lease->ntp = mfree(lease->ntp);
-        lease->ntp_count = 0;
+        for (size_t offset = 0; offset < optlen;) {
+                const uint8_t *subval;
+                size_t sublen;
+                uint16_t subopt;
 
-        while ((r = dhcp6_option_parse(&optval, &optlen, &subopt, &sublen,
-                                       &subval)) >= 0) {
-                int s;
-                char **servers;
+                r = dhcp6_option_parse(optval, optlen, &offset, &subopt, &sublen, &subval);
+                if (r < 0)
+                        return r;
 
                 switch(subopt) {
                 case DHCP6_NTP_SUBOPTION_SRV_ADDR:
@@ -282,86 +265,74 @@ int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
                         if (sublen != 16)
                                 return 0;
 
-                        s = dhcp6_option_parse_ip6addrs(subval, sublen,
-                                                        &lease->ntp,
-                                                        lease->ntp_count);
-                        if (s < 0)
-                                return s;
-
-                        lease->ntp_count = s;
+                        r = dhcp6_option_parse_addresses(subval, sublen, &lease->ntp, &lease->ntp_count);
+                        if (r < 0)
+                                return r;
 
                         break;
 
-                case DHCP6_NTP_SUBOPTION_SRV_FQDN:
-                        r = dhcp6_option_parse_domainname_list(subval, sublen,
-                                                               &servers);
+                case DHCP6_NTP_SUBOPTION_SRV_FQDN: {
+                        _cleanup_free_ char *server = NULL;
+
+                        r = dhcp6_option_parse_domainname(subval, sublen, &server);
                         if (r < 0)
-                                return 0;
+                                return r;
+
+                        if (strv_contains(lease->ntp_fqdn, server))
+                                continue;
 
-                        strv_free_and_replace(lease->ntp_fqdn, servers);
-                        lease->ntp_fqdn_count = r;
+                        r = strv_consume(&lease->ntp_fqdn, TAKE_PTR(server));
+                        if (r < 0)
+                                return r;
 
                         break;
-                }
+                }}
         }
 
-        if (r != -ENOMSG)
-                return r;
-
         return 0;
 }
 
-int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
-        int r;
-
+int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
         assert_return(lease, -EINVAL);
         assert_return(optval, -EINVAL);
 
-        if (!optlen)
+        if (optlen == 0)
                 return 0;
 
-        if (lease->ntp || lease->ntp_fqdn)
-                return -EEXIST;
-
-        /* Using deprecated SNTP information */
-
-        r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->ntp,
-                                        lease->ntp_count);
-        if (r < 0)
-                return r;
-
-        lease->ntp_count = r;
-
-        return 0;
+        /* SNTP option is defined in RFC4075, and deprecated by RFC5908. */
+        return dhcp6_option_parse_addresses(optval, optlen, &lease->sntp, &lease->sntp_count);
 }
 
-int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease,
-                                 const struct in6_addr **addrs) {
+int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret) {
         assert_return(lease, -EINVAL);
-        assert_return(addrs, -EINVAL);
+        assert_return(ret, -EINVAL);
 
-        if (lease->ntp_count) {
-                *addrs = lease->ntp;
+        if (lease->ntp) {
+                *ret = lease->ntp;
                 return lease->ntp_count;
         }
 
+        if (lease->sntp && !lease->ntp_fqdn) {
+                /* Fallback to the deprecated SNTP option. */
+                *ret = lease->sntp;
+                return lease->sntp_count;
+        }
+
         return -ENOENT;
 }
 
-int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn) {
+int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) {
         assert_return(lease, -EINVAL);
-        assert_return(ntp_fqdn, -EINVAL);
+        assert_return(ret, -EINVAL);
 
-        if (lease->ntp_fqdn_count) {
-                *ntp_fqdn = lease->ntp_fqdn;
-                return lease->ntp_fqdn_count;
-        }
+        if (!lease->ntp_fqdn)
+                return -ENOENT;
 
-        return -ENOENT;
+        *ret = lease->ntp_fqdn;
+        return strv_length(lease->ntp_fqdn);
 }
 
-int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval,
-                         size_t optlen) {
+int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
         int r;
         char *fqdn;
 
@@ -380,33 +351,31 @@ int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval,
         return free_and_replace(lease->fqdn, fqdn);
 }
 
-int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn) {
+int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret) {
         assert_return(lease, -EINVAL);
-        assert_return(fqdn, -EINVAL);
+        assert_return(ret, -EINVAL);
 
-        if (lease->fqdn) {
-                *fqdn = lease->fqdn;
-                return 0;
-        }
+        if (!lease->fqdn)
+                return -ENOENT;
 
-        return -ENOENT;
+        *ret = lease->fqdn;
+        return 0;
 }
 
 static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
-        assert(lease);
+        if (!lease)
+                return NULL;
 
         free(lease->serverid);
         dhcp6_lease_free_ia(&lease->ia);
         dhcp6_lease_free_ia(&lease->pd);
-
         free(lease->dns);
         free(lease->fqdn);
-
-        lease->domains = strv_free(lease->domains);
-
+        strv_free(lease->domains);
         free(lease->ntp);
+        strv_free(lease->ntp_fqdn);
+        free(lease->sntp);
 
-        lease->ntp_fqdn = strv_free(lease->ntp_fqdn);
         return mfree(lease);
 }
 
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c
index 031804ca..f0612ed5 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c
@@ -19,7 +19,7 @@
 #include "event-util.h"
 #include "fd-util.h"
 #include "in-addr-util.h"
-#include "log-link.h"
+#include "memory-util.h"
 #include "network-common.h"
 #include "random-util.h"
 #include "siphash24.h"
@@ -66,7 +66,7 @@ struct sd_ipv4acd {
         sd_event_source *timer_event_source;
 
         usec_t defend_window;
-        be32_t address;
+        struct in_addr address;
 
         /* External */
         struct ether_addr mac_addr;
@@ -74,18 +74,20 @@ struct sd_ipv4acd {
         sd_event *event;
         int event_priority;
         sd_ipv4acd_callback_t callback;
-        void* userdata;
+        void *userdata;
+        sd_ipv4acd_check_mac_callback_t check_mac_callback;
+        void *check_mac_userdata;
 };
 
 #define log_ipv4acd_errno(acd, error, fmt, ...)         \
         log_interface_prefix_full_errno(                \
                 "IPv4ACD: ",                            \
-                sd_ipv4acd_get_ifname(acd),             \
+                sd_ipv4acd, acd,                        \
                 error, fmt, ##__VA_ARGS__)
 #define log_ipv4acd(acd, fmt, ...)                      \
         log_interface_prefix_full_errno_zerook(         \
                 "IPv4ACD: ",                            \
-                sd_ipv4acd_get_ifname(acd),             \
+                sd_ipv4acd, acd,                        \
                 0, fmt, ##__VA_ARGS__)
 
 static const char * const ipv4acd_state_table[_IPV4ACD_STATE_MAX] = {
@@ -119,7 +121,7 @@ static void ipv4acd_reset(sd_ipv4acd *acd) {
         assert(acd);
 
         (void) event_source_disable(acd->timer_event_source);
-        acd->receive_message_event_source = sd_event_source_unref(acd->receive_message_event_source);
+        acd->receive_message_event_source = sd_event_source_disable_unref(acd->receive_message_event_source);
 
         acd->fd = safe_close(acd->fd);
 
@@ -129,9 +131,8 @@ static void ipv4acd_reset(sd_ipv4acd *acd) {
 static sd_ipv4acd *ipv4acd_free(sd_ipv4acd *acd) {
         assert(acd);
 
-        acd->timer_event_source = sd_event_source_unref(acd->timer_event_source);
-
         ipv4acd_reset(acd);
+        sd_event_source_unref(acd->timer_event_source);
         sd_ipv4acd_detach_event(acd);
         free(acd->ifname);
         return mfree(acd);
@@ -210,18 +211,6 @@ static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_u
                                 acd->event_priority, "ipv4acd-timer", true);
 }
 
-static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) {
-        assert(acd);
-        assert(arp);
-
-        /* see the BPF */
-        if (memcmp(arp->arp_spa, &acd->address, sizeof(acd->address)) == 0)
-                return true;
-
-        /* the TPA matched instead of the SPA, this is not a conflict */
-        return false;
-}
-
 static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
         sd_ipv4acd *acd = userdata;
         int r = 0;
@@ -231,38 +220,32 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata)
         switch (acd->state) {
 
         case IPV4ACD_STATE_STARTED:
+                acd->defend_window = 0;
+
                 ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true);
 
                 if (acd->n_conflict >= MAX_CONFLICTS) {
-                        char ts[FORMAT_TIMESPAN_MAX];
-                        log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0));
-
+                        log_ipv4acd(acd, "Max conflicts reached, delaying by %s",
+                                    FORMAT_TIMESPAN(RATE_LIMIT_INTERVAL_USEC, 0));
                         r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC);
-                        if (r < 0)
-                                goto fail;
-                } else {
+                } else
                         r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC);
-                        if (r < 0)
-                                goto fail;
-                }
+                if (r < 0)
+                        goto fail;
 
                 break;
 
         case IPV4ACD_STATE_WAITING_PROBE:
         case IPV4ACD_STATE_PROBING:
                 /* Send a probe */
-                r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
+                r = arp_send_probe(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr);
                 if (r < 0) {
                         log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m");
                         goto fail;
-                } else {
-                        _cleanup_free_ char *address = NULL;
-                        union in_addr_union addr = { .in.s_addr = acd->address };
-
-                        (void) in_addr_to_string(AF_INET, &addr, &address);
-                        log_ipv4acd(acd, "Probing %s", strna(address));
                 }
 
+                log_ipv4acd(acd, "Probing "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(acd->address));
+
                 if (acd->n_iteration < PROBE_NUM - 2) {
                         ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false);
 
@@ -288,12 +271,13 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata)
                 _fallthrough_;
         case IPV4ACD_STATE_WAITING_ANNOUNCE:
                 /* Send announcement packet */
-                r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
+                r = arp_send_announcement(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr);
                 if (r < 0) {
                         log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
                         goto fail;
-                } else
-                        log_ipv4acd(acd, "ANNOUNCE");
+                }
+
+                log_ipv4acd(acd, "Announcing "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(acd->address));
 
                 ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false);
 
@@ -309,7 +293,7 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata)
                 break;
 
         default:
-                assert_not_reached("Invalid state.");
+                assert_not_reached();
         }
 
         return 0;
@@ -319,16 +303,45 @@ fail:
         return 0;
 }
 
-static void ipv4acd_on_conflict(sd_ipv4acd *acd) {
-        _cleanup_free_ char *address = NULL;
-        union in_addr_union addr = { .in.s_addr = acd->address };
+static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, const struct ether_arp *arp, bool announced) {
+        assert(acd);
+        assert(arp);
+
+        /* RFC 5227 section 2.1.1.
+         * "the host receives any ARP packet (Request *or* Reply) on the interface where the probe is
+         * being performed, where the packet's 'sender IP address' is the address being probed for,
+         * then the host MUST treat this address as being in use by some other host" */
+        if (memcmp(arp->arp_spa, &acd->address, sizeof(struct in_addr)) == 0)
+                return true;
 
+        if (announced)
+                /* the TPA matched instead of SPA, this is not a conflict */
+                return false;
+
+        /* "any ARP Probe where the packet's 'target IP address' is the address being probed for, and
+         * the packet's 'sender hardware address' is not the hardware address of any of the host's
+         * interfaces, then the host SHOULD similarly treat this as an address conflict" */
+        if (arp->ea_hdr.ar_op != htobe16(ARPOP_REQUEST))
+                return false; /* not ARP Request, ignoring. */
+        if (memeqzero(arp->arp_spa, sizeof(struct in_addr)) == 0)
+                return false; /* not ARP Probe, ignoring. */
+        if (memcmp(arp->arp_tpa, &acd->address, sizeof(struct in_addr)) != 0)
+                return false; /* target IP address does not match, BPF code is broken? */
+
+        if (acd->check_mac_callback &&
+            acd->check_mac_callback(acd, (const struct ether_addr*) arp->arp_sha, acd->check_mac_userdata) > 0)
+                /* sender hardware is one of the host's interfaces, ignoring. */
+                return true;
+
+        return true; /* conflict! */
+}
+
+static void ipv4acd_on_conflict(sd_ipv4acd *acd) {
         assert(acd);
 
         acd->n_conflict++;
 
-        (void) in_addr_to_string(AF_INET, &addr, &address);
-        log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict);
+        log_ipv4acd(acd, "Conflict on "IPV4_ADDRESS_FMT_STR" (%u)", IPV4_ADDRESS_FMT_VAL(acd->address), acd->n_conflict);
 
         ipv4acd_reset(acd);
         ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT);
@@ -367,7 +380,7 @@ static int ipv4acd_on_packet(
         case IPV4ACD_STATE_ANNOUNCING:
         case IPV4ACD_STATE_RUNNING:
 
-                if (ipv4acd_arp_conflict(acd, &packet)) {
+                if (ipv4acd_arp_conflict(acd, &packet, true)) {
                         usec_t ts;
 
                         assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0);
@@ -375,12 +388,13 @@ static int ipv4acd_on_packet(
                         /* Defend address */
                         if (ts > acd->defend_window) {
                                 acd->defend_window = ts + DEFEND_INTERVAL_USEC;
-                                r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
+                                r = arp_send_announcement(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr);
                                 if (r < 0) {
                                         log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
                                         goto fail;
-                                } else
-                                        log_ipv4acd(acd, "DEFEND");
+                                }
+
+                                log_ipv4acd(acd, "Defending "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(acd->address));
 
                         } else
                                 ipv4acd_on_conflict(acd);
@@ -390,12 +404,12 @@ static int ipv4acd_on_packet(
         case IPV4ACD_STATE_WAITING_PROBE:
         case IPV4ACD_STATE_PROBING:
         case IPV4ACD_STATE_WAITING_ANNOUNCE:
-                /* BPF ensures this packet indicates a conflict */
-                ipv4acd_on_conflict(acd);
+                if (ipv4acd_arp_conflict(acd, &packet, false))
+                        ipv4acd_on_conflict(acd);
                 break;
 
         default:
-                assert_not_reached("Invalid state.");
+                assert_not_reached();
         }
 
         return 0;
@@ -432,20 +446,40 @@ int sd_ipv4acd_set_ifname(sd_ipv4acd *acd, const char *ifname) {
         return free_and_strdup(&acd->ifname, ifname);
 }
 
-const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd) {
-        if (!acd)
-                return NULL;
+int sd_ipv4acd_get_ifname(sd_ipv4acd *acd, const char **ret) {
+        int r;
 
-        return get_ifname(acd->ifindex, &acd->ifname);
+        assert_return(acd, -EINVAL);
+
+        r = get_ifname(acd->ifindex, &acd->ifname);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = acd->ifname;
+
+        return 0;
 }
 
 int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) {
+        int r;
+
         assert_return(acd, -EINVAL);
         assert_return(addr, -EINVAL);
-        assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
+        assert_return(!ether_addr_is_null(addr), -EINVAL);
 
         acd->mac_addr = *addr;
 
+        if (!sd_ipv4acd_is_running(acd))
+                return 0;
+
+        assert(acd->fd >= 0);
+        r = arp_update_filter(acd->fd, &acd->address, &acd->mac_addr);
+        if (r < 0) {
+                ipv4acd_reset(acd);
+                return r;
+        }
+
         return 0;
 }
 
@@ -485,21 +519,51 @@ int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *use
         return 0;
 }
 
+int sd_ipv4acd_set_check_mac_callback(sd_ipv4acd *acd, sd_ipv4acd_check_mac_callback_t cb, void *userdata) {
+        assert_return(acd, -EINVAL);
+
+        acd->check_mac_callback = cb;
+        acd->check_mac_userdata = userdata;
+        return 0;
+}
+
 int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address) {
+        int r;
+
         assert_return(acd, -EINVAL);
         assert_return(address, -EINVAL);
-        assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
+        assert_return(in4_addr_is_set(address), -EINVAL);
+
+        if (in4_addr_equal(&acd->address, address))
+                return 0;
 
-        acd->address = address->s_addr;
+        acd->address = *address;
+
+        if (!sd_ipv4acd_is_running(acd))
+                return 0;
+
+        assert(acd->fd >= 0);
+        r = arp_update_filter(acd->fd, &acd->address, &acd->mac_addr);
+        if (r < 0)
+                goto fail;
 
+        r = ipv4acd_set_next_wakeup(acd, 0, 0);
+        if (r < 0)
+                goto fail;
+
+        ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true);
         return 0;
+
+fail:
+        ipv4acd_reset(acd);
+        return r;
 }
 
 int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address) {
         assert_return(acd, -EINVAL);
         assert_return(address, -EINVAL);
 
-        address->s_addr = acd->address;
+        *address = acd->address;
 
         return 0;
 }
@@ -516,16 +580,15 @@ int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts) {
         assert_return(acd, -EINVAL);
         assert_return(acd->event, -EINVAL);
         assert_return(acd->ifindex > 0, -EINVAL);
-        assert_return(acd->address != 0, -EINVAL);
+        assert_return(in4_addr_is_set(&acd->address), -EINVAL);
         assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL);
         assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
 
-        r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr);
+        r = arp_network_bind_raw_socket(acd->ifindex, &acd->address, &acd->mac_addr);
         if (r < 0)
                 return r;
 
         CLOSE_AND_REPLACE(acd->fd, r);
-        acd->defend_window = 0;
 
         if (reset_conflicts)
                 acd->n_conflict = 0;
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c
index 56275100..4a500dfc 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c
@@ -17,7 +17,7 @@
 #include "alloc-util.h"
 #include "ether-addr-util.h"
 #include "in-addr-util.h"
-#include "log-link.h"
+#include "network-common.h"
 #include "random-util.h"
 #include "siphash24.h"
 #include "sparse-endian.h"
@@ -48,21 +48,25 @@ struct sd_ipv4ll {
         be32_t claimed_address;
 
         sd_ipv4ll_callback_t callback;
-        void* userdata;
+        void *userdata;
+
+        sd_ipv4ll_check_mac_callback_t check_mac_callback;
+        void *check_mac_userdata;
 };
 
 #define log_ipv4ll_errno(ll, error, fmt, ...)           \
         log_interface_prefix_full_errno(                \
                 "IPv4LL: ",                             \
-                sd_ipv4ll_get_ifname(ll),               \
+                sd_ipv4ll, ll,                          \
                 error, fmt, ##__VA_ARGS__)
 #define log_ipv4ll(ll, fmt, ...)                        \
         log_interface_prefix_full_errno_zerook(         \
                 "IPv4LL: ",                             \
-                sd_ipv4ll_get_ifname(ll),               \
+                sd_ipv4ll, ll,                          \
                 0, fmt, ##__VA_ARGS__)
 
-static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
+static void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata);
+static int ipv4ll_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata);
 
 static sd_ipv4ll *ipv4ll_free(sd_ipv4ll *ll) {
         assert(ll);
@@ -93,6 +97,10 @@ int sd_ipv4ll_new(sd_ipv4ll **ret) {
         if (r < 0)
                 return r;
 
+        r = sd_ipv4acd_set_check_mac_callback(ll->acd, ipv4ll_check_mac, ll);
+        if (r < 0)
+                return r;
+
         *ret = TAKE_PTR(ll);
 
         return 0;
@@ -127,11 +135,10 @@ int sd_ipv4ll_set_ifname(sd_ipv4ll *ll, const char *ifname) {
         return sd_ipv4acd_set_ifname(ll->acd, ifname);
 }
 
-const char *sd_ipv4ll_get_ifname(sd_ipv4ll *ll) {
-        if (!ll)
-                return NULL;
+int sd_ipv4ll_get_ifname(sd_ipv4ll *ll, const char **ret) {
+        assert_return(ll, -EINVAL);
 
-        return sd_ipv4acd_get_ifname(ll->acd);
+        return sd_ipv4acd_get_ifname(ll->acd, ret);
 }
 
 int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
@@ -139,7 +146,7 @@ int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
 
         assert_return(ll, -EINVAL);
         assert_return(addr, -EINVAL);
-        assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
+        assert_return(!ether_addr_is_null(addr), -EINVAL);
 
         r = sd_ipv4acd_set_mac(ll->acd, addr);
         if (r < 0)
@@ -170,6 +177,15 @@ int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdat
         return 0;
 }
 
+int sd_ipv4ll_set_check_mac_callback(sd_ipv4ll *ll, sd_ipv4ll_check_mac_callback_t cb, void *userdata) {
+        assert_return(ll, -EINVAL);
+
+        ll->check_mac_callback = cb;
+        ll->check_mac_userdata = userdata;
+
+        return 0;
+}
+
 int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) {
         assert_return(ll, -EINVAL);
         assert_return(address, -EINVAL);
@@ -345,7 +361,7 @@ void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
                 break;
 
         default:
-                assert_not_reached("Invalid IPv4ACD event.");
+                assert_not_reached();
         }
 
         return;
@@ -353,3 +369,14 @@ void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
 error:
         ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
 }
+
+static int ipv4ll_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata) {
+        sd_ipv4ll *ll = userdata;
+
+        assert(ll);
+
+        if (ll->check_mac_callback)
+                return ll->check_mac_callback(ll, mac, ll->check_mac_userdata);
+
+        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
new file mode 100644
index 00000000..9da2b149
--- /dev/null
+++ b/src/libnm-systemd-core/src/libsystemd-network/sd-lldp-rx.c
@@ -0,0 +1,525 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <arpa/inet.h>
+#include <linux/sockios.h>
+#include <sys/ioctl.h>
+
+#include "sd-lldp-rx.h"
+
+#include "alloc-util.h"
+#include "ether-addr-util.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "lldp-neighbor.h"
+#include "lldp-network.h"
+#include "lldp-rx-internal.h"
+#include "memory-util.h"
+#include "network-common.h"
+#include "socket-util.h"
+#include "sort-util.h"
+#include "string-table.h"
+
+#define LLDP_DEFAULT_NEIGHBORS_MAX 128U
+
+static const char * const lldp_rx_event_table[_SD_LLDP_RX_EVENT_MAX] = {
+        [SD_LLDP_RX_EVENT_ADDED]     = "added",
+        [SD_LLDP_RX_EVENT_REMOVED]   = "removed",
+        [SD_LLDP_RX_EVENT_UPDATED]   = "updated",
+        [SD_LLDP_RX_EVENT_REFRESHED] = "refreshed",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(lldp_rx_event, sd_lldp_rx_event_t);
+
+static void lldp_rx_flush_neighbors(sd_lldp_rx *lldp_rx) {
+        assert(lldp_rx);
+
+        hashmap_clear(lldp_rx->neighbor_by_id);
+}
+
+static void lldp_rx_callback(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_lldp_neighbor *n) {
+        assert(lldp_rx);
+        assert(event >= 0 && event < _SD_LLDP_RX_EVENT_MAX);
+
+        if (!lldp_rx->callback)
+                return (void) log_lldp_rx(lldp_rx, "Received '%s' event.", lldp_rx_event_to_string(event));
+
+        log_lldp_rx(lldp_rx, "Invoking callback for '%s' event.", lldp_rx_event_to_string(event));
+        lldp_rx->callback(lldp_rx, event, n, lldp_rx->userdata);
+}
+
+static int lldp_rx_make_space(sd_lldp_rx *lldp_rx, size_t extra) {
+        usec_t t = USEC_INFINITY;
+        bool changed = false;
+
+        assert(lldp_rx);
+
+        /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
+         * are free. */
+
+        for (;;) {
+                _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+
+                n = prioq_peek(lldp_rx->neighbor_by_expiry);
+                if (!n)
+                        break;
+
+                sd_lldp_neighbor_ref(n);
+
+                if (hashmap_size(lldp_rx->neighbor_by_id) > LESS_BY(lldp_rx->neighbors_max, extra))
+                        goto remove_one;
+
+                if (t == USEC_INFINITY)
+                        t = now(clock_boottime_or_monotonic());
+
+                if (n->until > t)
+                        break;
+
+        remove_one:
+                lldp_neighbor_unlink(n);
+                lldp_rx_callback(lldp_rx, SD_LLDP_RX_EVENT_REMOVED, n);
+                changed = true;
+        }
+
+        return changed;
+}
+
+static bool lldp_rx_keep_neighbor(sd_lldp_rx *lldp_rx, sd_lldp_neighbor *n) {
+        assert(lldp_rx);
+        assert(n);
+
+        /* Don't keep data with a zero TTL */
+        if (n->ttl <= 0)
+                return false;
+
+        /* Filter out data from the filter address */
+        if (!ether_addr_is_null(&lldp_rx->filter_address) &&
+            ether_addr_equal(&lldp_rx->filter_address, &n->source_address))
+                return false;
+
+        /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
+         * no caps field set. */
+        if (n->has_capabilities &&
+            (n->enabled_capabilities & lldp_rx->capability_mask) == 0)
+                return false;
+
+        /* Keep everything else */
+        return true;
+}
+
+static int lldp_rx_start_timer(sd_lldp_rx *lldp_rx, sd_lldp_neighbor *neighbor);
+
+static int lldp_rx_add_neighbor(sd_lldp_rx *lldp_rx, sd_lldp_neighbor *n) {
+        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL;
+        bool keep;
+        int r;
+
+        assert(lldp_rx);
+        assert(n);
+        assert(!n->lldp_rx);
+
+        keep = lldp_rx_keep_neighbor(lldp_rx, n);
+
+        /* First retrieve the old entry for this MSAP */
+        old = hashmap_get(lldp_rx->neighbor_by_id, &n->id);
+        if (old) {
+                sd_lldp_neighbor_ref(old);
+
+                if (!keep) {
+                        lldp_neighbor_unlink(old);
+                        lldp_rx_callback(lldp_rx, SD_LLDP_RX_EVENT_REMOVED, old);
+                        return 0;
+                }
+
+                if (lldp_neighbor_equal(n, old)) {
+                        /* Is this equal, then restart the TTL counter, but don't do anything else. */
+                        old->timestamp = n->timestamp;
+                        lldp_rx_start_timer(lldp_rx, old);
+                        lldp_rx_callback(lldp_rx, SD_LLDP_RX_EVENT_REFRESHED, old);
+                        return 0;
+                }
+
+                /* Data changed, remove the old entry, and add a new one */
+                lldp_neighbor_unlink(old);
+
+        } else if (!keep)
+                return 0;
+
+        /* Then, make room for at least one new neighbor */
+        lldp_rx_make_space(lldp_rx, 1);
+
+        r = hashmap_ensure_put(&lldp_rx->neighbor_by_id, &lldp_neighbor_hash_ops, &n->id, n);
+        if (r < 0)
+                goto finish;
+
+        r = prioq_ensure_put(&lldp_rx->neighbor_by_expiry, lldp_neighbor_prioq_compare_func, n, &n->prioq_idx);
+        if (r < 0) {
+                assert_se(hashmap_remove(lldp_rx->neighbor_by_id, &n->id) == n);
+                goto finish;
+        }
+
+        n->lldp_rx = lldp_rx;
+
+        lldp_rx_start_timer(lldp_rx, n);
+        lldp_rx_callback(lldp_rx, old ? SD_LLDP_RX_EVENT_UPDATED : SD_LLDP_RX_EVENT_ADDED, n);
+
+        return 1;
+
+finish:
+        if (old)
+                lldp_rx_callback(lldp_rx, SD_LLDP_RX_EVENT_REMOVED, old);
+
+        return r;
+}
+
+static int lldp_rx_handle_datagram(sd_lldp_rx *lldp_rx, sd_lldp_neighbor *n) {
+        int r;
+
+        assert(lldp_rx);
+        assert(n);
+
+        r = lldp_neighbor_parse(n);
+        if (r < 0)
+                return r;
+
+        r = lldp_rx_add_neighbor(lldp_rx, n);
+        if (r < 0)
+                return log_lldp_rx_errno(lldp_rx, r, "Failed to add datagram. Ignoring.");
+
+        log_lldp_rx(lldp_rx, "Successfully processed LLDP datagram.");
+        return 0;
+}
+
+static int lldp_rx_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+        ssize_t space, length;
+        sd_lldp_rx *lldp_rx = userdata;
+        struct timespec ts;
+
+        assert(fd >= 0);
+        assert(lldp_rx);
+
+        space = next_datagram_size_fd(fd);
+        if (space < 0) {
+                log_lldp_rx_errno(lldp_rx, space, "Failed to determine datagram size to read, ignoring: %m");
+                return 0;
+        }
+
+        n = lldp_neighbor_new(space);
+        if (!n) {
+                log_oom_debug();
+                return 0;
+        }
+
+        length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
+        if (length < 0) {
+                if (IN_SET(errno, EAGAIN, EINTR))
+                        return 0;
+
+                log_lldp_rx_errno(lldp_rx, errno, "Failed to read LLDP datagram, ignoring: %m");
+                return 0;
+        }
+
+        if ((size_t) length != n->raw_size) {
+                log_lldp_rx(lldp_rx, "Packet size mismatch, ignoring");
+                return 0;
+        }
+
+        /* Try to get the timestamp of this packet if it is known */
+        if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
+                triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
+        else
+                triple_timestamp_get(&n->timestamp);
+
+        (void) lldp_rx_handle_datagram(lldp_rx, n);
+        return 0;
+}
+
+static void lldp_rx_reset(sd_lldp_rx *lldp_rx) {
+        assert(lldp_rx);
+
+        (void) event_source_disable(lldp_rx->timer_event_source);
+        lldp_rx->io_event_source = sd_event_source_disable_unref(lldp_rx->io_event_source);
+        lldp_rx->fd = safe_close(lldp_rx->fd);
+}
+
+int sd_lldp_rx_is_running(sd_lldp_rx *lldp_rx) {
+        if (!lldp_rx)
+                return false;
+
+        return lldp_rx->fd >= 0;
+}
+
+_public_ int sd_lldp_rx_start(sd_lldp_rx *lldp_rx) {
+        int r;
+
+        assert_return(lldp_rx, -EINVAL);
+        assert_return(lldp_rx->event, -EINVAL);
+        assert_return(lldp_rx->ifindex > 0, -EINVAL);
+
+        if (sd_lldp_rx_is_running(lldp_rx))
+                return 0;
+
+        assert(!lldp_rx->io_event_source);
+
+        lldp_rx->fd = lldp_network_bind_raw_socket(lldp_rx->ifindex);
+        if (lldp_rx->fd < 0)
+                return lldp_rx->fd;
+
+        r = sd_event_add_io(lldp_rx->event, &lldp_rx->io_event_source, lldp_rx->fd, EPOLLIN, lldp_rx_receive_datagram, lldp_rx);
+        if (r < 0)
+                goto fail;
+
+        r = sd_event_source_set_priority(lldp_rx->io_event_source, lldp_rx->event_priority);
+        if (r < 0)
+                goto fail;
+
+        (void) sd_event_source_set_description(lldp_rx->io_event_source, "lldp-rx-io");
+
+        log_lldp_rx(lldp_rx, "Started LLDP client");
+        return 1;
+
+fail:
+        lldp_rx_reset(lldp_rx);
+        return r;
+}
+
+_public_ int sd_lldp_rx_stop(sd_lldp_rx *lldp_rx) {
+        if (!sd_lldp_rx_is_running(lldp_rx))
+                return 0;
+
+        log_lldp_rx(lldp_rx, "Stopping LLDP client");
+
+        lldp_rx_reset(lldp_rx);
+        lldp_rx_flush_neighbors(lldp_rx);
+
+        return 1;
+}
+
+_public_ int sd_lldp_rx_attach_event(sd_lldp_rx *lldp_rx, sd_event *event, int64_t priority) {
+        int r;
+
+        assert_return(lldp_rx, -EINVAL);
+        assert_return(!sd_lldp_rx_is_running(lldp_rx), -EBUSY);
+        assert_return(!lldp_rx->event, -EBUSY);
+
+        if (event)
+                lldp_rx->event = sd_event_ref(event);
+        else {
+                r = sd_event_default(&lldp_rx->event);
+                if (r < 0)
+                        return r;
+        }
+
+        lldp_rx->event_priority = priority;
+
+        return 0;
+}
+
+_public_ int sd_lldp_rx_detach_event(sd_lldp_rx *lldp_rx) {
+        assert_return(lldp_rx, -EINVAL);
+        assert_return(!sd_lldp_rx_is_running(lldp_rx), -EBUSY);
+
+        lldp_rx->io_event_source = sd_event_source_disable_unref(lldp_rx->io_event_source);
+        lldp_rx->timer_event_source = sd_event_source_disable_unref(lldp_rx->timer_event_source);
+        lldp_rx->event = sd_event_unref(lldp_rx->event);
+        return 0;
+}
+
+_public_ sd_event* sd_lldp_rx_get_event(sd_lldp_rx *lldp_rx) {
+        assert_return(lldp_rx, NULL);
+
+        return lldp_rx->event;
+}
+
+_public_ int sd_lldp_rx_set_callback(sd_lldp_rx *lldp_rx, sd_lldp_rx_callback_t cb, void *userdata) {
+        assert_return(lldp_rx, -EINVAL);
+
+        lldp_rx->callback = cb;
+        lldp_rx->userdata = userdata;
+
+        return 0;
+}
+
+_public_ int sd_lldp_rx_set_ifindex(sd_lldp_rx *lldp_rx, int ifindex) {
+        assert_return(lldp_rx, -EINVAL);
+        assert_return(ifindex > 0, -EINVAL);
+        assert_return(!sd_lldp_rx_is_running(lldp_rx), -EBUSY);
+
+        lldp_rx->ifindex = ifindex;
+        return 0;
+}
+
+int sd_lldp_rx_set_ifname(sd_lldp_rx *lldp_rx, const char *ifname) {
+        assert_return(lldp_rx, -EINVAL);
+        assert_return(ifname, -EINVAL);
+
+        if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
+                return -EINVAL;
+
+        return free_and_strdup(&lldp_rx->ifname, ifname);
+}
+
+int sd_lldp_rx_get_ifname(sd_lldp_rx *lldp_rx, const char **ret) {
+        int r;
+
+        assert_return(lldp_rx, -EINVAL);
+
+        r = get_ifname(lldp_rx->ifindex, &lldp_rx->ifname);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = lldp_rx->ifname;
+
+        return 0;
+}
+
+static sd_lldp_rx *lldp_rx_free(sd_lldp_rx *lldp_rx) {
+        if (!lldp_rx)
+                return NULL;
+
+        lldp_rx_reset(lldp_rx);
+
+        sd_lldp_rx_detach_event(lldp_rx);
+
+        lldp_rx_flush_neighbors(lldp_rx);
+
+        hashmap_free(lldp_rx->neighbor_by_id);
+        prioq_free(lldp_rx->neighbor_by_expiry);
+        free(lldp_rx->ifname);
+        return mfree(lldp_rx);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp_rx, sd_lldp_rx, lldp_rx_free);
+
+_public_ int sd_lldp_rx_new(sd_lldp_rx **ret) {
+        _cleanup_(sd_lldp_rx_unrefp) sd_lldp_rx *lldp_rx = NULL;
+
+        assert_return(ret, -EINVAL);
+
+        lldp_rx = new(sd_lldp_rx, 1);
+        if (!lldp_rx)
+                return -ENOMEM;
+
+        *lldp_rx = (sd_lldp_rx) {
+                .n_ref = 1,
+                .fd = -1,
+                .neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX,
+                .capability_mask = UINT16_MAX,
+        };
+
+        *ret = TAKE_PTR(lldp_rx);
+        return 0;
+}
+
+static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_lldp_rx *lldp_rx = userdata;
+        int r;
+
+        r = lldp_rx_make_space(lldp_rx, 0);
+        if (r < 0) {
+                log_lldp_rx_errno(lldp_rx, r, "Failed to make space, ignoring: %m");
+                return 0;
+        }
+
+        r = lldp_rx_start_timer(lldp_rx, NULL);
+        if (r < 0) {
+                log_lldp_rx_errno(lldp_rx, r, "Failed to restart timer, ignoring: %m");
+                return 0;
+        }
+
+        return 0;
+}
+
+static int lldp_rx_start_timer(sd_lldp_rx *lldp_rx, sd_lldp_neighbor *neighbor) {
+        sd_lldp_neighbor *n;
+
+        assert(lldp_rx);
+        assert(lldp_rx->event);
+
+        if (neighbor)
+                lldp_neighbor_start_ttl(neighbor);
+
+        n = prioq_peek(lldp_rx->neighbor_by_expiry);
+        if (!n)
+                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(),
+                                n->until, 0,
+                                on_timer_event, lldp_rx,
+                                lldp_rx->event_priority, "lldp-rx-timer", true);
+}
+
+static inline int neighbor_compare_func(sd_lldp_neighbor * const *a, sd_lldp_neighbor * const *b) {
+        assert(a);
+        assert(b);
+        assert(*a);
+        assert(*b);
+
+        return lldp_neighbor_id_compare_func(&(*a)->id, &(*b)->id);
+}
+
+_public_ int sd_lldp_rx_get_neighbors(sd_lldp_rx *lldp_rx, sd_lldp_neighbor ***ret) {
+        _cleanup_free_ sd_lldp_neighbor **l = NULL;
+        sd_lldp_neighbor *n;
+        int k = 0;
+
+        assert_return(lldp_rx, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (hashmap_isempty(lldp_rx->neighbor_by_id)) { /* Special shortcut */
+                *ret = NULL;
+                return 0;
+        }
+
+        l = new0(sd_lldp_neighbor*, hashmap_size(lldp_rx->neighbor_by_id));
+        if (!l)
+                return -ENOMEM;
+
+        HASHMAP_FOREACH(n, lldp_rx->neighbor_by_id)
+                l[k++] = sd_lldp_neighbor_ref(n);
+
+        assert((size_t) k == hashmap_size(lldp_rx->neighbor_by_id));
+
+        /* Return things in a stable order */
+        typesafe_qsort(l, k, neighbor_compare_func);
+        *ret = TAKE_PTR(l);
+
+        return k;
+}
+
+_public_ int sd_lldp_rx_set_neighbors_max(sd_lldp_rx *lldp_rx, uint64_t m) {
+        assert_return(lldp_rx, -EINVAL);
+        assert_return(m > 0, -EINVAL);
+
+        lldp_rx->neighbors_max = m;
+        lldp_rx_make_space(lldp_rx, 0);
+
+        return 0;
+}
+
+_public_ int sd_lldp_rx_match_capabilities(sd_lldp_rx *lldp_rx, uint16_t mask) {
+        assert_return(lldp_rx, -EINVAL);
+        assert_return(mask != 0, -EINVAL);
+
+        lldp_rx->capability_mask = mask;
+
+        return 0;
+}
+
+_public_ int sd_lldp_rx_set_filter_address(sd_lldp_rx *lldp_rx, const struct ether_addr *addr) {
+        assert_return(lldp_rx, -EINVAL);
+
+        /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
+         * that our own can be filtered out here. */
+
+        if (addr)
+                lldp_rx->filter_address = *addr;
+        else
+                zero(lldp_rx->filter_address);
+
+        return 0;
+}
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-lldp.c b/src/libnm-systemd-core/src/libsystemd-network/sd-lldp.c
deleted file mode 100644
index 09c2052f..00000000
--- a/src/libnm-systemd-core/src/libsystemd-network/sd-lldp.c
+++ /dev/null
@@ -1,524 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include "nm-sd-adapt-core.h"
-
-#include <arpa/inet.h>
-#include <linux/sockios.h>
-#include <sys/ioctl.h>
-
-#include "sd-lldp.h"
-
-#include "alloc-util.h"
-#include "ether-addr-util.h"
-#include "event-util.h"
-#include "fd-util.h"
-#include "lldp-internal.h"
-#include "lldp-neighbor.h"
-#include "lldp-network.h"
-#include "memory-util.h"
-#include "network-common.h"
-#include "socket-util.h"
-#include "sort-util.h"
-#include "string-table.h"
-
-#define LLDP_DEFAULT_NEIGHBORS_MAX 128U
-
-static const char * const lldp_event_table[_SD_LLDP_EVENT_MAX] = {
-        [SD_LLDP_EVENT_ADDED]     = "added",
-        [SD_LLDP_EVENT_REMOVED]   = "removed",
-        [SD_LLDP_EVENT_UPDATED]   = "updated",
-        [SD_LLDP_EVENT_REFRESHED] = "refreshed",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(lldp_event, sd_lldp_event_t);
-
-static void lldp_flush_neighbors(sd_lldp *lldp) {
-        assert(lldp);
-
-        hashmap_clear(lldp->neighbor_by_id);
-}
-
-static void lldp_callback(sd_lldp *lldp, sd_lldp_event_t event, sd_lldp_neighbor *n) {
-        assert(lldp);
-        assert(event >= 0 && event < _SD_LLDP_EVENT_MAX);
-
-        if (!lldp->callback)
-                return (void) log_lldp(lldp, "Received '%s' event.", lldp_event_to_string(event));
-
-        log_lldp(lldp, "Invoking callback for '%s' event.", lldp_event_to_string(event));
-        lldp->callback(lldp, event, n, lldp->userdata);
-}
-
-static int lldp_make_space(sd_lldp *lldp, size_t extra) {
-        usec_t t = USEC_INFINITY;
-        bool changed = false;
-
-        assert(lldp);
-
-        /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
-         * are free. */
-
-        for (;;) {
-                _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
-
-                n = prioq_peek(lldp->neighbor_by_expiry);
-                if (!n)
-                        break;
-
-                sd_lldp_neighbor_ref(n);
-
-                if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra))
-                        goto remove_one;
-
-                if (t == USEC_INFINITY)
-                        t = now(clock_boottime_or_monotonic());
-
-                if (n->until > t)
-                        break;
-
-        remove_one:
-                lldp_neighbor_unlink(n);
-                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n);
-                changed = true;
-        }
-
-        return changed;
-}
-
-static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
-        assert(lldp);
-        assert(n);
-
-        /* Don't keep data with a zero TTL */
-        if (n->ttl <= 0)
-                return false;
-
-        /* Filter out data from the filter address */
-        if (!ether_addr_is_null(&lldp->filter_address) &&
-            ether_addr_equal(&lldp->filter_address, &n->source_address))
-                return false;
-
-        /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
-         * no caps field set. */
-        if (n->has_capabilities &&
-            (n->enabled_capabilities & lldp->capability_mask) == 0)
-                return false;
-
-        /* Keep everything else */
-        return true;
-}
-
-static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor);
-
-static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
-        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL;
-        bool keep;
-        int r;
-
-        assert(lldp);
-        assert(n);
-        assert(!n->lldp);
-
-        keep = lldp_keep_neighbor(lldp, n);
-
-        /* First retrieve the old entry for this MSAP */
-        old = hashmap_get(lldp->neighbor_by_id, &n->id);
-        if (old) {
-                sd_lldp_neighbor_ref(old);
-
-                if (!keep) {
-                        lldp_neighbor_unlink(old);
-                        lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
-                        return 0;
-                }
-
-                if (lldp_neighbor_equal(n, old)) {
-                        /* Is this equal, then restart the TTL counter, but don't do anything else. */
-                        old->timestamp = n->timestamp;
-                        lldp_start_timer(lldp, old);
-                        lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
-                        return 0;
-                }
-
-                /* Data changed, remove the old entry, and add a new one */
-                lldp_neighbor_unlink(old);
-
-        } else if (!keep)
-                return 0;
-
-        /* Then, make room for at least one new neighbor */
-        lldp_make_space(lldp, 1);
-
-        r = hashmap_put(lldp->neighbor_by_id, &n->id, n);
-        if (r < 0)
-                goto finish;
-
-        r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx);
-        if (r < 0) {
-                assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n);
-                goto finish;
-        }
-
-        n->lldp = lldp;
-
-        lldp_start_timer(lldp, n);
-        lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n);
-
-        return 1;
-
-finish:
-        if (old)
-                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
-
-        return r;
-}
-
-static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
-        int r;
-
-        assert(lldp);
-        assert(n);
-
-        r = lldp_neighbor_parse(n);
-        if (r == -EBADMSG) /* Ignore bad messages */
-                return 0;
-        if (r < 0)
-                return r;
-
-        r = lldp_add_neighbor(lldp, n);
-        if (r < 0) {
-                log_lldp_errno(lldp, r, "Failed to add datagram. Ignoring.");
-                return 0;
-        }
-
-        log_lldp(lldp, "Successfully processed LLDP datagram.");
-        return 0;
-}
-
-static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
-        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
-        ssize_t space, length;
-        sd_lldp *lldp = userdata;
-        struct timespec ts;
-
-        assert(fd >= 0);
-        assert(lldp);
-
-        space = next_datagram_size_fd(fd);
-        if (space < 0) {
-                log_lldp_errno(lldp, space, "Failed to determine datagram size to read, ignoring: %m");
-                return 0;
-        }
-
-        n = lldp_neighbor_new(space);
-        if (!n)
-                return -ENOMEM;
-
-        length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
-        if (length < 0) {
-                if (IN_SET(errno, EAGAIN, EINTR))
-                        return 0;
-
-                log_lldp_errno(lldp, errno, "Failed to read LLDP datagram, ignoring: %m");
-                return 0;
-        }
-
-        if ((size_t) length != n->raw_size) {
-                log_lldp(lldp, "Packet size mismatch, ignoring");
-                return 0;
-        }
-
-        /* Try to get the timestamp of this packet if it is known */
-        if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
-                triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
-        else
-                triple_timestamp_get(&n->timestamp);
-
-        return lldp_handle_datagram(lldp, n);
-}
-
-static void lldp_reset(sd_lldp *lldp) {
-        assert(lldp);
-
-        (void) event_source_disable(lldp->timer_event_source);
-        lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
-        lldp->fd = safe_close(lldp->fd);
-}
-
-_public_ int sd_lldp_start(sd_lldp *lldp) {
-        int r;
-
-        assert_return(lldp, -EINVAL);
-        assert_return(lldp->event, -EINVAL);
-        assert_return(lldp->ifindex > 0, -EINVAL);
-
-        if (lldp->fd >= 0)
-                return 0;
-
-        assert(!lldp->io_event_source);
-
-        lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex);
-        if (lldp->fd < 0)
-                return lldp->fd;
-
-        r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
-        if (r < 0)
-                goto fail;
-
-        r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
-        if (r < 0)
-                goto fail;
-
-        (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
-
-        log_lldp(lldp, "Started LLDP client");
-        return 1;
-
-fail:
-        lldp_reset(lldp);
-        return r;
-}
-
-_public_ int sd_lldp_stop(sd_lldp *lldp) {
-        if (!lldp)
-                return 0;
-
-        if (lldp->fd < 0)
-                return 0;
-
-        log_lldp(lldp, "Stopping LLDP client");
-
-        lldp_reset(lldp);
-        lldp_flush_neighbors(lldp);
-
-        return 1;
-}
-
-_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) {
-        int r;
-
-        assert_return(lldp, -EINVAL);
-        assert_return(lldp->fd < 0, -EBUSY);
-        assert_return(!lldp->event, -EBUSY);
-
-        if (event)
-                lldp->event = sd_event_ref(event);
-        else {
-                r = sd_event_default(&lldp->event);
-                if (r < 0)
-                        return r;
-        }
-
-        lldp->event_priority = priority;
-
-        return 0;
-}
-
-_public_ int sd_lldp_detach_event(sd_lldp *lldp) {
-
-        assert_return(lldp, -EINVAL);
-        assert_return(lldp->fd < 0, -EBUSY);
-
-        lldp->event = sd_event_unref(lldp->event);
-        return 0;
-}
-
-_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) {
-        assert_return(lldp, NULL);
-
-        return lldp->event;
-}
-
-_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
-        assert_return(lldp, -EINVAL);
-
-        lldp->callback = cb;
-        lldp->userdata = userdata;
-
-        return 0;
-}
-
-_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) {
-        assert_return(lldp, -EINVAL);
-        assert_return(ifindex > 0, -EINVAL);
-        assert_return(lldp->fd < 0, -EBUSY);
-
-        lldp->ifindex = ifindex;
-        return 0;
-}
-
-int sd_lldp_set_ifname(sd_lldp *lldp, const char *ifname) {
-        assert_return(lldp, -EINVAL);
-        assert_return(ifname, -EINVAL);
-
-        if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
-                return -EINVAL;
-
-        return free_and_strdup(&lldp->ifname, ifname);
-}
-
-const char *sd_lldp_get_ifname(sd_lldp *lldp) {
-        if (!lldp)
-                return NULL;
-
-        return get_ifname(lldp->ifindex, &lldp->ifname);
-}
-
-static sd_lldp* lldp_free(sd_lldp *lldp) {
-        assert(lldp);
-
-        lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
-
-        lldp_reset(lldp);
-        sd_lldp_detach_event(lldp);
-        lldp_flush_neighbors(lldp);
-
-        hashmap_free(lldp->neighbor_by_id);
-        prioq_free(lldp->neighbor_by_expiry);
-        free(lldp->ifname);
-        return mfree(lldp);
-}
-
-DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp, sd_lldp, lldp_free);
-
-_public_ int sd_lldp_new(sd_lldp **ret) {
-        _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
-        int r;
-
-        assert_return(ret, -EINVAL);
-
-        lldp = new(sd_lldp, 1);
-        if (!lldp)
-                return -ENOMEM;
-
-        *lldp = (sd_lldp) {
-                .n_ref = 1,
-                .fd = -1,
-                .neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX,
-                .capability_mask = UINT16_MAX,
-        };
-
-        lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_hash_ops);
-        if (!lldp->neighbor_by_id)
-                return -ENOMEM;
-
-        r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func);
-        if (r < 0)
-                return r;
-
-        *ret = TAKE_PTR(lldp);
-
-        return 0;
-}
-
-static int neighbor_compare_func(sd_lldp_neighbor * const *a, sd_lldp_neighbor * const *b) {
-        return lldp_neighbor_id_compare_func(&(*a)->id, &(*b)->id);
-}
-
-static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
-        sd_lldp *lldp = userdata;
-        int r;
-
-        r = lldp_make_space(lldp, 0);
-        if (r < 0) {
-                log_lldp_errno(lldp, r, "Failed to make space, ignoring: %m");
-                return 0;
-        }
-
-        r = lldp_start_timer(lldp, NULL);
-        if (r < 0) {
-                log_lldp_errno(lldp, r, "Failed to restart timer, ignoring: %m");
-                return 0;
-        }
-
-        return 0;
-}
-
-static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) {
-        sd_lldp_neighbor *n;
-
-        assert(lldp);
-
-        if (neighbor)
-                lldp_neighbor_start_ttl(neighbor);
-
-        n = prioq_peek(lldp->neighbor_by_expiry);
-        if (!n)
-                return event_source_disable(lldp->timer_event_source);
-
-        if (!lldp->event)
-                return 0;
-
-        return event_reset_time(lldp->event, &lldp->timer_event_source,
-                                clock_boottime_or_monotonic(),
-                                n->until, 0,
-                                on_timer_event, lldp,
-                                lldp->event_priority, "lldp-timer", true);
-}
-
-_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
-        sd_lldp_neighbor **l = NULL, *n;
-        int k = 0, r;
-
-        assert_return(lldp, -EINVAL);
-        assert_return(ret, -EINVAL);
-
-        if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */
-                *ret = NULL;
-                return 0;
-        }
-
-        l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id));
-        if (!l)
-                return -ENOMEM;
-
-        r = lldp_start_timer(lldp, NULL);
-        if (r < 0) {
-                free(l);
-                return r;
-        }
-
-        HASHMAP_FOREACH(n, lldp->neighbor_by_id)
-                l[k++] = sd_lldp_neighbor_ref(n);
-
-        assert((size_t) k == hashmap_size(lldp->neighbor_by_id));
-
-        /* Return things in a stable order */
-        typesafe_qsort(l, k, neighbor_compare_func);
-        *ret = l;
-
-        return k;
-}
-
-_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) {
-        assert_return(lldp, -EINVAL);
-        assert_return(m > 0, -EINVAL);
-
-        lldp->neighbors_max = m;
-        lldp_make_space(lldp, 0);
-
-        return 0;
-}
-
-_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) {
-        assert_return(lldp, -EINVAL);
-        assert_return(mask != 0, -EINVAL);
-
-        lldp->capability_mask = mask;
-
-        return 0;
-}
-
-_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) {
-        assert_return(lldp, -EINVAL);
-
-        /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
-         * that our own can be filtered out here. */
-
-        if (addr)
-                lldp->filter_address = *addr;
-        else
-                zero(lldp->filter_address);
-
-        return 0;
-}
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h b/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h
index d2dc2147..08b409fe 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h
+++ b/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h
@@ -7,8 +7,8 @@
 
 #include "sd-event.h"
 
-#include "fs-util.h"
 #include "hashmap.h"
+#include "inotify-util.h"
 #include "list.h"
 #include "prioq.h"
 #include "ratelimit.h"
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 44926744..0acbdd54 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
@@ -87,6 +87,11 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int);
                SOURCE_DEFER,                    \
                SOURCE_INOTIFY)
 
+/* This is used to assert that we didn't pass an unexpected source type to event_source_time_prioq_put().
+ * Time sources and ratelimited sources can be passed, so effectively this is the same as the
+ * EVENT_SOURCE_CAN_RATE_LIMIT() macro. */
+#define EVENT_SOURCE_USES_TIME_PRIOQ(t) EVENT_SOURCE_CAN_RATE_LIMIT(t)
+
 struct sd_event {
         unsigned n_ref;
 
@@ -170,10 +175,9 @@ static int pending_prioq_compare(const void *a, const void *b) {
         assert(y->pending);
 
         /* Enabled ones first */
-        if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
-                return -1;
-        if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
-                return 1;
+        r = CMP(x->enabled == SD_EVENT_OFF, y->enabled == SD_EVENT_OFF);
+        if (r != 0)
+                return r;
 
         /* Non rate-limited ones first. */
         r = CMP(!!x->ratelimited, !!y->ratelimited);
@@ -197,10 +201,9 @@ static int prepare_prioq_compare(const void *a, const void *b) {
         assert(y->prepare);
 
         /* Enabled ones first */
-        if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
-                return -1;
-        if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
-                return 1;
+        r = CMP(x->enabled == SD_EVENT_OFF, y->enabled == SD_EVENT_OFF);
+        if (r != 0)
+                return r;
 
         /* Non rate-limited ones first. */
         r = CMP(!!x->ratelimited, !!y->ratelimited);
@@ -239,25 +242,6 @@ static usec_t time_event_source_next(const sd_event_source *s) {
         return USEC_INFINITY;
 }
 
-static int earliest_time_prioq_compare(const void *a, const void *b) {
-        const sd_event_source *x = a, *y = b;
-
-        /* Enabled ones first */
-        if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
-                return -1;
-        if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
-                return 1;
-
-        /* Move the pending ones to the end */
-        if (!x->pending && y->pending)
-                return -1;
-        if (x->pending && !y->pending)
-                return 1;
-
-        /* Order by time */
-        return CMP(time_event_source_next(x), time_event_source_next(y));
-}
-
 static usec_t time_event_source_latest(const sd_event_source *s) {
         assert(s);
 
@@ -276,36 +260,51 @@ static usec_t time_event_source_latest(const sd_event_source *s) {
         return USEC_INFINITY;
 }
 
-static int latest_time_prioq_compare(const void *a, const void *b) {
+static bool event_source_timer_candidate(const sd_event_source *s) {
+        assert(s);
+
+        /* Returns true for event sources that either are not pending yet (i.e. where it's worth to mark them pending)
+         * or which are currently ratelimited (i.e. where it's worth leaving the ratelimited state) */
+        return !s->pending || s->ratelimited;
+}
+
+static int time_prioq_compare(const void *a, const void *b, usec_t (*time_func)(const sd_event_source *s)) {
         const sd_event_source *x = a, *y = b;
+        int r;
 
         /* Enabled ones first */
-        if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
-                return -1;
-        if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
-                return 1;
+        r = CMP(x->enabled == SD_EVENT_OFF, y->enabled == SD_EVENT_OFF);
+        if (r != 0)
+                return r;
 
-        /* Move the pending ones to the end */
-        if (!x->pending && y->pending)
-                return -1;
-        if (x->pending && !y->pending)
-                return 1;
+        /* Order "non-pending OR ratelimited" before "pending AND not-ratelimited" */
+        r = CMP(!event_source_timer_candidate(x), !event_source_timer_candidate(y));
+        if (r != 0)
+                return r;
 
         /* Order by time */
-        return CMP(time_event_source_latest(x), time_event_source_latest(y));
+        return CMP(time_func(x), time_func(y));
+}
+
+static int earliest_time_prioq_compare(const void *a, const void *b) {
+        return time_prioq_compare(a, b, time_event_source_next);
+}
+
+static int latest_time_prioq_compare(const void *a, const void *b) {
+        return time_prioq_compare(a, b, time_event_source_latest);
 }
 
 static int exit_prioq_compare(const void *a, const void *b) {
         const sd_event_source *x = a, *y = b;
+        int r;
 
         assert(x->type == SOURCE_EXIT);
         assert(y->type == SOURCE_EXIT);
 
         /* Enabled ones first */
-        if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
-                return -1;
-        if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
-                return 1;
+        r = CMP(x->enabled == SD_EVENT_OFF, y->enabled == SD_EVENT_OFF);
+        if (r != 0)
+                return r;
 
         /* Lower priority values first */
         return CMP(x->priority, y->priority);
@@ -779,14 +778,15 @@ static void event_source_time_prioq_reshuffle(sd_event_source *s) {
         assert(s);
 
         /* Called whenever the event source's timer ordering properties changed, i.e. time, accuracy,
-         * pending, enable state. Makes sure the two prioq's are ordered properly again. */
+         * pending, enable state, and ratelimiting state. Makes sure the two prioq's are ordered
+         * properly again. */
 
         if (s->ratelimited)
                 d = &s->event->monotonic;
-        else {
-                assert(EVENT_SOURCE_IS_TIME(s->type));
+        else if (EVENT_SOURCE_IS_TIME(s->type))
                 assert_se(d = event_get_clock_data(s->event, s->type));
-        }
+        else
+                return; /* no-op for an event source which is neither a timer nor ratelimited. */
 
         prioq_reshuffle(d->earliest, s, &s->earliest_index);
         prioq_reshuffle(d->latest, s, &s->latest_index);
@@ -917,7 +917,7 @@ static void source_disconnect(sd_event_source *s) {
         }
 
         default:
-                assert_not_reached("Wut? I shouldn't exist.");
+                assert_not_reached();
         }
 
         if (s->pending)
@@ -1205,6 +1205,7 @@ static int event_source_time_prioq_put(
 
         assert(s);
         assert(d);
+        assert(EVENT_SOURCE_USES_TIME_PRIOQ(s->type));
 
         r = prioq_put(d->earliest, s, &s->earliest_index);
         if (r < 0)
@@ -2379,14 +2380,6 @@ static int event_source_offline(
                 source_io_unregister(s);
                 break;
 
-        case SOURCE_TIME_REALTIME:
-        case SOURCE_TIME_BOOTTIME:
-        case SOURCE_TIME_MONOTONIC:
-        case SOURCE_TIME_REALTIME_ALARM:
-        case SOURCE_TIME_BOOTTIME_ALARM:
-                event_source_time_prioq_reshuffle(s);
-                break;
-
         case SOURCE_SIGNAL:
                 event_gc_signal_data(s->event, &s->priority, s->signal.sig);
                 break;
@@ -2407,15 +2400,23 @@ static int event_source_offline(
                 prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
                 break;
 
+        case SOURCE_TIME_REALTIME:
+        case SOURCE_TIME_BOOTTIME:
+        case SOURCE_TIME_MONOTONIC:
+        case SOURCE_TIME_REALTIME_ALARM:
+        case SOURCE_TIME_BOOTTIME_ALARM:
         case SOURCE_DEFER:
         case SOURCE_POST:
         case SOURCE_INOTIFY:
                 break;
 
         default:
-                assert_not_reached("Wut? I shouldn't exist.");
+                assert_not_reached();
         }
 
+        /* Always reshuffle time prioq, as the ratelimited flag may be changed. */
+        event_source_time_prioq_reshuffle(s);
+
         return 1;
 }
 
@@ -2498,29 +2499,18 @@ static int event_source_online(
                 break;
 
         default:
-                assert_not_reached("Wut? I shouldn't exist.");
+                assert_not_reached();
         }
 
         s->enabled = enabled;
         s->ratelimited = ratelimited;
 
         /* Non-failing operations below */
-        switch (s->type) {
-        case SOURCE_TIME_REALTIME:
-        case SOURCE_TIME_BOOTTIME:
-        case SOURCE_TIME_MONOTONIC:
-        case SOURCE_TIME_REALTIME_ALARM:
-        case SOURCE_TIME_BOOTTIME_ALARM:
-                event_source_time_prioq_reshuffle(s);
-                break;
-
-        case SOURCE_EXIT:
+        if (s->type == SOURCE_EXIT)
                 prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
-                break;
 
-        default:
-                break;
-        }
+        /* Always reshuffle time prioq, as the ratelimited flag may be changed. */
+        event_source_time_prioq_reshuffle(s);
 
         return 1;
 }
@@ -2990,10 +2980,11 @@ static int event_arm_timer(
 
         if (!d->needs_rearm)
                 return 0;
-        else
-                d->needs_rearm = false;
+
+        d->needs_rearm = false;
 
         a = prioq_peek(d->earliest);
+        assert(!a || EVENT_SOURCE_USES_TIME_PRIOQ(a->type));
         if (!a || a->enabled == SD_EVENT_OFF || time_event_source_next(a) == USEC_INFINITY) {
 
                 if (d->fd < 0)
@@ -3011,7 +3002,8 @@ static int event_arm_timer(
         }
 
         b = prioq_peek(d->latest);
-        assert_se(b && b->enabled != SD_EVENT_OFF);
+        assert(!b || EVENT_SOURCE_USES_TIME_PRIOQ(b->type));
+        assert(b && b->enabled != SD_EVENT_OFF);
 
         t = sleep_between(e, time_event_source_next(a), time_event_source_latest(b));
         if (d->next == t)
@@ -3091,6 +3083,8 @@ static int process_timer(
 
         for (;;) {
                 s = prioq_peek(d->earliest);
+                assert(!s || EVENT_SOURCE_USES_TIME_PRIOQ(s->type));
+
                 if (!s || time_event_source_next(s) > n)
                         break;
 
@@ -3579,7 +3573,7 @@ static int source_dispatch(sd_event_source *s) {
         case SOURCE_WATCHDOG:
         case _SOURCE_EVENT_SOURCE_TYPE_MAX:
         case _SOURCE_EVENT_SOURCE_TYPE_INVALID:
-                assert_not_reached("Wut? I shouldn't exist.");
+                assert_not_reached();
         }
 
         s->dispatching = false;
@@ -3651,6 +3645,8 @@ static int dispatch_exit(sd_event *e) {
         assert(e);
 
         p = prioq_peek(e->exit);
+        assert(!p || p->type == SOURCE_EXIT);
+
         if (!p || event_source_is_offline(p)) {
                 e->state = SD_EVENT_FINISHED;
                 return 0;
@@ -3687,8 +3683,8 @@ static int arm_watchdog(sd_event *e) {
         assert(e->watchdog_fd >= 0);
 
         t = sleep_between(e,
-                          e->watchdog_last + (e->watchdog_period / 2),
-                          e->watchdog_last + (e->watchdog_period * 3 / 4));
+                          usec_add(e->watchdog_last, (e->watchdog_period / 2)),
+                          usec_add(e->watchdog_last, (e->watchdog_period * 3 / 4)));
 
         timespec_store(&its.it_value, t);
 
@@ -3937,7 +3933,7 @@ static int process_epoll(sd_event *e, usec_t timeout, int64_t threshold, int64_t
                                         break;
 
                                 default:
-                                        assert_not_reached("Unexpected event source type");
+                                        assert_not_reached();
                                 }
 
                                 break;
@@ -3961,7 +3957,7 @@ static int process_epoll(sd_event *e, usec_t timeout, int64_t threshold, int64_t
                                 break;
 
                         default:
-                                assert_not_reached("Invalid wake-up pointer");
+                                assert_not_reached();
                         }
                 }
                 if (r < 0)
@@ -4162,7 +4158,7 @@ _public_ int sd_event_loop(sd_event *e) {
         assert_return(!event_pid_changed(e), -ECHILD);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
 
-        _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = NULL;
+        _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e);
 
         while (e->state != SD_EVENT_FINISHED) {
                 r = sd_event_run(e, UINT64_MAX);
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 3a2bffab..d80145a8 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
@@ -7,12 +7,12 @@
 #include <unistd.h>
 
 #include "fd-util.h"
-#include "fs-util.h"
 #include "hexdecoct.h"
 #include "id128-util.h"
 #include "io-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
+#include "sync-util.h"
 
 #if 0 /* NM_IGNORED */
 char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]) {
@@ -172,10 +172,7 @@ int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync) {
                 return r;
 
         if (do_sync) {
-                if (fsync(fd) < 0)
-                        return -errno;
-
-                r = fsync_directory_of_file(fd);
+                r = fsync_full(fd);
                 if (r < 0)
                         return r;
         }
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 053ef0a6..b7327a1f 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
@@ -12,6 +12,8 @@
 
 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 {
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 64b8c787..0860f493 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
@@ -6,6 +6,11 @@
 #include <fcntl.h>
 #include <unistd.h>
 
+#if HAVE_OPENSSL
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+#endif
+
 #include "sd-id128.h"
 
 #include "alloc-util.h"
@@ -13,7 +18,9 @@
 #include "hexdecoct.h"
 #include "id128-util.h"
 #include "io-util.h"
+#if !HAVE_OPENSSL
 #include "khash.h"
+#endif
 #include "macro.h"
 #include "missing_syscall.h"
 #include "random-util.h"
@@ -276,13 +283,28 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
 }
 
 static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) {
-        _cleanup_(khash_unrefp) khash *h = NULL;
         sd_id128_t result;
-        const void *p;
-        int r;
 
         assert(ret);
 
+#if HAVE_OPENSSL
+        /* We prefer doing this in-process, since we this means we are not dependent on kernel configuration,
+         * and this also works in locked down container environments. But some distros don't like OpenSSL's
+         * license and its (in-) compatibility with GPL2, hence also support khash */
+        uint8_t md[256/8];
+        if (!HMAC(EVP_sha256(),
+                  &base, sizeof(base),
+                  (const unsigned char*) &app_id, sizeof(app_id),
+                  md, NULL))
+                return -ENOTRECOVERABLE;
+
+        /* Take only the first half. */
+        memcpy(&result, md, MIN(sizeof(md), sizeof(result)));
+#else
+        _cleanup_(khash_unrefp) khash *h = NULL;
+        const void *p;
+        int r;
+
         r = khash_new_with_key(&h, "hmac(sha256)", &base, sizeof(base));
         if (r < 0)
                 return r;
@@ -297,6 +319,7 @@ static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret)
 
         /* We chop off the trailing 16 bytes */
         memcpy(&result, p, MIN(khash_get_size(h), sizeof(result)));
+#endif
 
         *ret = id128_make_v4_uuid(result);
         return 0;
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 53002340..23979b12 100644
--- a/src/libnm-systemd-core/src/systemd/sd-dhcp-client.h
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp-client.h
@@ -135,7 +135,7 @@ int sd_dhcp_client_set_ifindex(
 int sd_dhcp_client_set_ifname(
                 sd_dhcp_client *client,
                 const char *interface_name);
-const char *sd_dhcp_client_get_ifname(sd_dhcp_client *client);
+int sd_dhcp_client_get_ifname(sd_dhcp_client *client, const char **ret);
 int sd_dhcp_client_set_mac(
                 sd_dhcp_client *client,
                 const uint8_t *addr,
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 e02d6763..f3889782 100644
--- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h
@@ -94,7 +94,7 @@ int sd_dhcp6_client_set_ifindex(
 int sd_dhcp6_client_set_ifname(
                 sd_dhcp6_client *client,
                 const char *interface_name);
-const char * sd_dhcp6_client_get_ifname(sd_dhcp6_client *client);
+int sd_dhcp6_client_get_ifname(sd_dhcp6_client *client, const char **ret);
 int sd_dhcp6_client_set_local_address(
                 sd_dhcp6_client *client,
                 const struct in6_addr *local_address);
diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h
index f77b31ac..a10901e5 100644
--- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h
@@ -39,11 +39,11 @@ int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix,
                           uint32_t *lifetime_preferred,
                           uint32_t *lifetime_valid);
 
-int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs);
-int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains);
-int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **addrs);
-int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn);
-int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn);
+int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret);
+int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret);
+int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret);
+int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret);
+int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret);
 
 sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease);
 sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease);
diff --git a/src/libnm-systemd-core/src/systemd/sd-id128.h b/src/libnm-systemd-core/src/systemd/sd-id128.h
index ab209c8c..e64f3200 100644
--- a/src/libnm-systemd-core/src/systemd/sd-id128.h
+++ b/src/libnm-systemd-core/src/systemd/sd-id128.h
@@ -34,11 +34,13 @@ union sd_id128 {
         uint64_t qwords[2];
 };
 
-#define SD_ID128_STRING_MAX 33
+#define SD_ID128_STRING_MAX 33U
 
 char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]);
 int sd_id128_from_string(const char *s, sd_id128_t *ret);
 
+#define SD_ID128_TO_STRING(id) sd_id128_to_string((id), (char[SD_ID128_STRING_MAX]) {})
+
 int sd_id128_randomize(sd_id128_t *ret);
 
 int sd_id128_get_machine(sd_id128_t *ret);
@@ -63,7 +65,9 @@ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret);
 #define SD_ID128_FORMAT_STR "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
 #define SD_ID128_FORMAT_VAL(x) (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15]
 
-/* Like SD_ID128_FORMAT_STR, but formats as UUID, not in plain format */
+/* Like SD_ID128_FORMAT_STR, but formats as UUID, not in plain format (Strictly Big Endian byte order,
+ * i.e. treats everything as RFC4122 Variant 1 UUIDs, even if variant says otherwise, but matching other
+ * Linux userspace behaviour.) */
 #define SD_ID128_UUID_FORMAT_STR "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
 
 #define SD_ID128_CONST_STR(x)                                           \
@@ -105,6 +109,9 @@ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret);
 #define SD_ID128_MAKE_STR(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \
         #a #b #c #d #e #f #g #h #i #j #k #l #m #n #o #p
 
+#define SD_ID128_MAKE_UUID_STR(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \
+        #a #b #c #d "-" #e #f "-" #g #h "-" #i #j "-" #k #l #m #n #o #p
+
 _sd_pure_ static __inline__ int sd_id128_equal(sd_id128_t a, sd_id128_t b) {
         return memcmp(&a, &b, 16) == 0;
 }
diff --git a/src/libnm-systemd-core/src/systemd/sd-ipv4acd.h b/src/libnm-systemd-core/src/systemd/sd-ipv4acd.h
index 1e89a81b..90d3f0a0 100644
--- a/src/libnm-systemd-core/src/systemd/sd-ipv4acd.h
+++ b/src/libnm-systemd-core/src/systemd/sd-ipv4acd.h
@@ -36,16 +36,18 @@ enum {
 
 typedef struct sd_ipv4acd sd_ipv4acd;
 typedef void (*sd_ipv4acd_callback_t)(sd_ipv4acd *acd, int event, void *userdata);
+typedef int (*sd_ipv4acd_check_mac_callback_t)(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata);
 
 int sd_ipv4acd_detach_event(sd_ipv4acd *acd);
 int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority);
 int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address);
 int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata);
+int sd_ipv4acd_set_check_mac_callback(sd_ipv4acd *acd, sd_ipv4acd_check_mac_callback_t cb, void *userdata);
 int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr);
 int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int interface_index);
 int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd);
 int sd_ipv4acd_set_ifname(sd_ipv4acd *acd, const char *interface_name);
-const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd);
+int sd_ipv4acd_get_ifname(sd_ipv4acd *acd, const char **ret);
 int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address);
 int sd_ipv4acd_is_running(sd_ipv4acd *acd);
 int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts);
diff --git a/src/libnm-systemd-core/src/systemd/sd-ipv4ll.h b/src/libnm-systemd-core/src/systemd/sd-ipv4ll.h
index bf5596ab..ed014b53 100644
--- a/src/libnm-systemd-core/src/systemd/sd-ipv4ll.h
+++ b/src/libnm-systemd-core/src/systemd/sd-ipv4ll.h
@@ -36,16 +36,18 @@ enum {
 
 typedef struct sd_ipv4ll sd_ipv4ll;
 typedef void (*sd_ipv4ll_callback_t)(sd_ipv4ll *ll, int event, void *userdata);
+typedef int (*sd_ipv4ll_check_mac_callback_t)(sd_ipv4ll *ll, const struct ether_addr *mac, void *userdata);
 
 int sd_ipv4ll_detach_event(sd_ipv4ll *ll);
 int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority);
 int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address);
 int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata);
+int sd_ipv4ll_set_check_mac_callback(sd_ipv4ll *ll, sd_ipv4ll_check_mac_callback_t cb, void *userdata);
 int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr);
 int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int interface_index);
 int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll);
 int sd_ipv4ll_set_ifname(sd_ipv4ll *ll, const char *interface_name);
-const char *sd_ipv4ll_get_ifname(sd_ipv4ll *ll);
+int sd_ipv4ll_get_ifname(sd_ipv4ll *ll, const char **ret);
 int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address);
 int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed);
 int sd_ipv4ll_is_running(sd_ipv4ll *ll);
diff --git a/src/libnm-systemd-core/src/systemd/sd-lldp-rx.h b/src/libnm-systemd-core/src/systemd/sd-lldp-rx.h
new file mode 100644
index 00000000..bfeac14c
--- /dev/null
+++ b/src/libnm-systemd-core/src/systemd/sd-lldp-rx.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosdlldprxhfoo
+#define foosdlldprxhfoo
+
+/***
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <inttypes.h>
+#include <net/ethernet.h>
+#include <sys/types.h>
+
+#include "sd-event.h"
+#include "sd-lldp.h"
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_lldp_rx sd_lldp_rx;
+typedef struct sd_lldp_neighbor sd_lldp_neighbor;
+
+typedef enum sd_lldp_rx_event_t {
+        SD_LLDP_RX_EVENT_ADDED,
+        SD_LLDP_RX_EVENT_REMOVED,
+        SD_LLDP_RX_EVENT_UPDATED,
+        SD_LLDP_RX_EVENT_REFRESHED,
+        _SD_LLDP_RX_EVENT_MAX,
+        _SD_LLDP_RX_EVENT_INVALID = -EINVAL,
+        _SD_ENUM_FORCE_S64(LLDP_RX_EVENT),
+} sd_lldp_rx_event_t;
+
+typedef void (*sd_lldp_rx_callback_t)(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_lldp_neighbor *n, void *userdata);
+
+int sd_lldp_rx_new(sd_lldp_rx **ret);
+sd_lldp_rx *sd_lldp_rx_ref(sd_lldp_rx *lldp_rx);
+sd_lldp_rx *sd_lldp_rx_unref(sd_lldp_rx *lldp_rx);
+
+int sd_lldp_rx_start(sd_lldp_rx *lldp_rx);
+int sd_lldp_rx_stop(sd_lldp_rx *lldp_rx);
+int sd_lldp_rx_is_running(sd_lldp_rx *lldp_rx);
+
+int sd_lldp_rx_attach_event(sd_lldp_rx *lldp_rx, sd_event *event, int64_t priority);
+int sd_lldp_rx_detach_event(sd_lldp_rx *lldp_rx);
+sd_event *sd_lldp_rx_get_event(sd_lldp_rx *lldp_rx);
+
+int sd_lldp_rx_set_callback(sd_lldp_rx *lldp_rx, sd_lldp_rx_callback_t cb, void *userdata);
+int sd_lldp_rx_set_ifindex(sd_lldp_rx *lldp_rx, int ifindex);
+int sd_lldp_rx_set_ifname(sd_lldp_rx *lldp_rx, const char *ifname);
+int sd_lldp_rx_get_ifname(sd_lldp_rx *lldp_rx, const char **ret);
+
+/* Controls how much and what to store in the neighbors database */
+int sd_lldp_rx_set_neighbors_max(sd_lldp_rx *lldp_rx, uint64_t n);
+int sd_lldp_rx_match_capabilities(sd_lldp_rx *lldp_rx, uint16_t mask);
+int sd_lldp_rx_set_filter_address(sd_lldp_rx *lldp_rx, const struct ether_addr *address);
+
+int sd_lldp_rx_get_neighbors(sd_lldp_rx *lldp_rx, sd_lldp_neighbor ***neighbors);
+
+int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size);
+sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n);
+sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n);
+
+/* Access to LLDP frame metadata */
+int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address);
+int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address);
+int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret);
+int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
+
+/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */
+int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
+int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
+int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec);
+int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
+int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
+
+/* Low-level, iterative TLV access. This is for everything else, it iteratively goes through all available TLVs
+ * (including the ones covered with the calls above), and allows multiple TLVs for the same fields. */
+int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n);
+int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n);
+int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type);
+int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type);
+int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype);
+int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype);
+int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_rx, sd_lldp_rx_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_neighbor, sd_lldp_neighbor_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/libnm-systemd-core/src/systemd/sd-lldp.h b/src/libnm-systemd-core/src/systemd/sd-lldp.h
index 64047ee8..c32d7894 100644
--- a/src/libnm-systemd-core/src/systemd/sd-lldp.h
+++ b/src/libnm-systemd-core/src/systemd/sd-lldp.h
@@ -17,12 +17,7 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include <errno.h>
 #include <inttypes.h>
-#include <net/ethernet.h>
-#include <sys/types.h>
-
-#include "sd-event.h"
 
 #include "_sd-common.h"
 
@@ -85,20 +80,24 @@ enum {
 
 #define SD_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS                         \
         ((uint16_t)                                                     \
-         (SD_LLDP_SYSTEM_CAPABILITIES_REPEATER|                         \
-          SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE|                           \
-          SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP|                          \
-          SD_LLDP_SYSTEM_CAPABILITIES_ROUTER|                           \
-          SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS|                           \
-          SD_LLDP_SYSTEM_CAPABILITIES_CVLAN|                            \
-          SD_LLDP_SYSTEM_CAPABILITIES_SVLAN|                            \
+         (SD_LLDP_SYSTEM_CAPABILITIES_REPEATER |                        \
+          SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE |                          \
+          SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP |                         \
+          SD_LLDP_SYSTEM_CAPABILITIES_ROUTER |                          \
+          SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS |                          \
+          SD_LLDP_SYSTEM_CAPABILITIES_CVLAN |                           \
+          SD_LLDP_SYSTEM_CAPABILITIES_SVLAN |                           \
           SD_LLDP_SYSTEM_CAPABILITIES_TPMR))
 
-#define SD_LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 }
-#define SD_LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f }
+#define SD_LLDP_OUI_802_1 (const uint8_t[]) { 0x00, 0x80, 0xc2 }
+#define SD_LLDP_OUI_802_3 (const uint8_t[]) { 0x00, 0x12, 0x0f }
+
+#define _SD_LLDP_OUI_IANA 0x00, 0x00, 0x5E
+#define SD_LLDP_OUI_IANA  (const uint8_t[]) { _SD_LLDP_OUI_IANA }
 
-#define SD_LLDP_OUI_MUD   (uint8_t[]) { 0x00, 0x00, 0x5E }
-#define SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION  0x01
+#define SD_LLDP_OUI_IANA_SUBTYPE_MUD  0x01
+#define SD_LLDP_OUI_IANA_MUD                                            \
+        (const uint8_t[]) { _SD_LLDP_OUI_IANA, SD_LLDP_OUI_IANA_SUBTYPE_MUD }
 
 /* IEEE 802.1AB-2009 Annex E */
 enum {
@@ -119,80 +118,6 @@ enum {
         SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE    = 4,
 };
 
-typedef struct sd_lldp sd_lldp;
-typedef struct sd_lldp_neighbor sd_lldp_neighbor;
-
-typedef enum sd_lldp_event_t {
-        SD_LLDP_EVENT_ADDED,
-        SD_LLDP_EVENT_REMOVED,
-        SD_LLDP_EVENT_UPDATED,
-        SD_LLDP_EVENT_REFRESHED,
-        _SD_LLDP_EVENT_MAX,
-        _SD_LLDP_EVENT_INVALID = -EINVAL,
-        _SD_ENUM_FORCE_S64(LLDP_EVENT),
-} sd_lldp_event_t;
-
-typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event_t event, sd_lldp_neighbor *n, void *userdata);
-
-int sd_lldp_new(sd_lldp **ret);
-sd_lldp* sd_lldp_ref(sd_lldp *lldp);
-sd_lldp* sd_lldp_unref(sd_lldp *lldp);
-
-int sd_lldp_start(sd_lldp *lldp);
-int sd_lldp_stop(sd_lldp *lldp);
-
-int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority);
-int sd_lldp_detach_event(sd_lldp *lldp);
-sd_event *sd_lldp_get_event(sd_lldp *lldp);
-
-int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata);
-int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex);
-int sd_lldp_set_ifname(sd_lldp *lldp, const char *ifname);
-const char *sd_lldp_get_ifname(sd_lldp *lldp);
-
-/* Controls how much and what to store in the neighbors database */
-int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t n);
-int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask);
-int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *address);
-
-int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***neighbors);
-
-int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size);
-sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n);
-sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n);
-
-/* Access to LLDP frame metadata */
-int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address);
-int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address);
-int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret);
-int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
-
-/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */
-int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
-int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret);
-int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
-int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret);
-int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec);
-int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret);
-int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret);
-int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret);
-int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret);
-int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
-int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
-
-/* Low-level, iterative TLV access. This is for everything else, it iteratively goes through all available TLVs
- * (including the ones covered with the calls above), and allows multiple TLVs for the same fields. */
-int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n);
-int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n);
-int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type);
-int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type);
-int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype);
-int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype);
-int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
-
-_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp, sd_lldp_unref);
-_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_neighbor, sd_lldp_neighbor_unref);
-
 _SD_END_DECLARATIONS;
 
 #endif
diff --git a/src/libnm-systemd-core/src/systemd/sd-ndisc.h b/src/libnm-systemd-core/src/systemd/sd-ndisc.h
index 6088def1..f45b2ad6 100644
--- a/src/libnm-systemd-core/src/systemd/sd-ndisc.h
+++ b/src/libnm-systemd-core/src/systemd/sd-ndisc.h
@@ -79,7 +79,7 @@ sd_event *sd_ndisc_get_event(sd_ndisc *nd);
 int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t cb, void *userdata);
 int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index);
 int sd_ndisc_set_ifname(sd_ndisc *nd, const char *interface_name);
-const char *sd_ndisc_get_ifname(sd_ndisc *nd);
+int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret);
 int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr);
 
 int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *ret);