summaryrefslogtreecommitdiffstats
path: root/src/libsystemd-network
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:49:52 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:49:52 +0000
commit55944e5e40b1be2afc4855d8d2baf4b73d1876b5 (patch)
tree33f869f55a1b149e9b7c2b7e201867ca5dd52992 /src/libsystemd-network
parentInitial commit. (diff)
downloadsystemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.tar.xz
systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.zip
Adding upstream version 255.4.upstream/255.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/libsystemd-network')
-rw-r--r--src/libsystemd-network/arp-util.c139
-rw-r--r--src/libsystemd-network/arp-util.h36
-rw-r--r--src/libsystemd-network/dhcp-client-internal.h50
-rw-r--r--src/libsystemd-network/dhcp-identifier.c209
-rw-r--r--src/libsystemd-network/dhcp-identifier.h87
-rw-r--r--src/libsystemd-network/dhcp-lease-internal.h98
-rw-r--r--src/libsystemd-network/dhcp-network.c287
-rw-r--r--src/libsystemd-network/dhcp-network.h35
-rw-r--r--src/libsystemd-network/dhcp-option.c461
-rw-r--r--src/libsystemd-network/dhcp-option.h46
-rw-r--r--src/libsystemd-network/dhcp-packet.c193
-rw-r--r--src/libsystemd-network/dhcp-packet.h31
-rw-r--r--src/libsystemd-network/dhcp-protocol.h102
-rw-r--r--src/libsystemd-network/dhcp-server-internal.h139
-rw-r--r--src/libsystemd-network/dhcp6-client-internal.h10
-rw-r--r--src/libsystemd-network/dhcp6-internal.h104
-rw-r--r--src/libsystemd-network/dhcp6-lease-internal.h90
-rw-r--r--src/libsystemd-network/dhcp6-network.c78
-rw-r--r--src/libsystemd-network/dhcp6-option.c979
-rw-r--r--src/libsystemd-network/dhcp6-option.h105
-rw-r--r--src/libsystemd-network/dhcp6-protocol.c96
-rw-r--r--src/libsystemd-network/dhcp6-protocol.h158
-rw-r--r--src/libsystemd-network/fuzz-dhcp-client.c83
-rw-r--r--src/libsystemd-network/fuzz-dhcp-server-relay.c48
-rw-r--r--src/libsystemd-network/fuzz-dhcp-server.c102
-rw-r--r--src/libsystemd-network/fuzz-dhcp6-client.c111
-rw-r--r--src/libsystemd-network/fuzz-dhcp6-client.options2
-rw-r--r--src/libsystemd-network/fuzz-lldp-rx.c45
-rw-r--r--src/libsystemd-network/fuzz-lldp-rx.options2
-rw-r--r--src/libsystemd-network/fuzz-ndisc-rs.c40
-rw-r--r--src/libsystemd-network/fuzz-ndisc-rs.options2
-rw-r--r--src/libsystemd-network/icmp6-util-unix.c53
-rw-r--r--src/libsystemd-network/icmp6-util-unix.h9
-rw-r--r--src/libsystemd-network/icmp6-util.c203
-rw-r--r--src/libsystemd-network/icmp6-util.h28
-rw-r--r--src/libsystemd-network/lldp-neighbor.c795
-rw-r--r--src/libsystemd-network/lldp-neighbor.h92
-rw-r--r--src/libsystemd-network/lldp-network.c70
-rw-r--r--src/libsystemd-network/lldp-network.h6
-rw-r--r--src/libsystemd-network/lldp-rx-internal.h48
-rw-r--r--src/libsystemd-network/meson.build121
-rw-r--r--src/libsystemd-network/ndisc-internal.h51
-rw-r--r--src/libsystemd-network/ndisc-protocol.c34
-rw-r--r--src/libsystemd-network/ndisc-protocol.h31
-rw-r--r--src/libsystemd-network/ndisc-router.c913
-rw-r--r--src/libsystemd-network/ndisc-router.h49
-rw-r--r--src/libsystemd-network/network-common.c126
-rw-r--r--src/libsystemd-network/network-common.h49
-rw-r--r--src/libsystemd-network/network-internal.c239
-rw-r--r--src/libsystemd-network/network-internal.h31
-rw-r--r--src/libsystemd-network/radv-internal.h222
-rw-r--r--src/libsystemd-network/sd-dhcp-client.c2568
-rw-r--r--src/libsystemd-network/sd-dhcp-lease.c1607
-rw-r--r--src/libsystemd-network/sd-dhcp-server.c1792
-rw-r--r--src/libsystemd-network/sd-dhcp6-client.c1594
-rw-r--r--src/libsystemd-network/sd-dhcp6-lease.c964
-rw-r--r--src/libsystemd-network/sd-ipv4acd.c617
-rw-r--r--src/libsystemd-network/sd-ipv4ll.c365
-rw-r--r--src/libsystemd-network/sd-lldp-rx.c524
-rw-r--r--src/libsystemd-network/sd-lldp-tx.c628
-rw-r--r--src/libsystemd-network/sd-ndisc.c381
-rw-r--r--src/libsystemd-network/sd-radv.c1161
-rw-r--r--src/libsystemd-network/test-acd.c94
-rw-r--r--src/libsystemd-network/test-dhcp-client.c562
-rw-r--r--src/libsystemd-network/test-dhcp-option.c386
-rw-r--r--src/libsystemd-network/test-dhcp-server.c330
-rw-r--r--src/libsystemd-network/test-dhcp6-client.c1127
-rw-r--r--src/libsystemd-network/test-ipv4ll-manual.c115
-rw-r--r--src/libsystemd-network/test-ipv4ll.c206
-rw-r--r--src/libsystemd-network/test-lldp-rx.c378
-rw-r--r--src/libsystemd-network/test-ndisc-ra.c376
-rw-r--r--src/libsystemd-network/test-ndisc-rs.c339
-rw-r--r--src/libsystemd-network/test-sd-dhcp-lease.c86
73 files changed, 23338 insertions, 0 deletions
diff --git a/src/libsystemd-network/arp-util.c b/src/libsystemd-network/arp-util.c
new file mode 100644
index 0000000..ad61614
--- /dev/null
+++ b/src/libsystemd-network/arp-util.c
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014 Axis Communications AB. All rights reserved.
+***/
+
+#include <arpa/inet.h>
+#include <linux/filter.h>
+#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"
+
+int arp_update_filter(int fd, const struct in_addr *a, const struct ether_addr *mac) {
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */
+ BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)), /* A <- hardware address length */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0), /* length == sizeof(ether_addr)? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)), /* A <- protocol address length */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0), /* length == sizeof(in_addr) ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), /* protocol == request ? */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0), /* protocol == reply ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ /* Sender Hardware Address must be different from our own */
+ BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be32(&mac->ether_addr_octet[0])), /* X <- 4 bytes of client's MAC */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)), /* A <- 4 bytes of SHA */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 0, 4), /* A == X ? */
+ BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be16(&mac->ether_addr_octet[4])), /* X <- remainder of client's MAC */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4), /* A <- remainder of SHA */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 0, 1), /* A == X ? */
+ 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_LDX + BPF_IMM, htobe32(a->s_addr)), /* X <- clients IP */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 0, 1), /* A == X ? */
+ BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 0, 1), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ };
+ struct sock_fprog fprog = {
+ .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 *mac) {
+ union sockaddr_union link = {
+ .ll.sll_family = AF_PACKET,
+ .ll.sll_protocol = htobe16(ETH_P_ARP),
+ .ll.sll_ifindex = ifindex,
+ .ll.sll_halen = ETH_ALEN,
+ .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+ };
+ _cleanup_close_ int s = -EBADF;
+ int r;
+
+ assert(ifindex > 0);
+ assert(mac);
+
+ s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (s < 0)
+ return -errno;
+
+ r = arp_update_filter(s, a, mac);
+ if (r < 0)
+ return r;
+
+ if (bind(s, &link.sa, sizeof(link.ll)) < 0)
+ return -errno;
+
+ return TAKE_FD(s);
+}
+
+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_protocol = htobe16(ETH_P_ARP),
+ .ll.sll_ifindex = ifindex,
+ .ll.sll_halen = ETH_ALEN,
+ .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+ };
+ struct ether_arp arp = {
+ .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */
+ .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */
+ .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */
+ .ea_hdr.ar_pln = sizeof(struct in_addr), /* PLEN */
+ .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */
+ };
+ ssize_t n;
+
+ assert(fd >= 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(struct in_addr));
+
+ if (announce)
+ memcpy(&arp.arp_spa, pa, sizeof(struct in_addr));
+
+ 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;
+}
diff --git a/src/libsystemd-network/arp-util.h b/src/libsystemd-network/arp-util.h
new file mode 100644
index 0000000..b66a81b
--- /dev/null
+++ b/src/libsystemd-network/arp-util.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014 Axis Communications AB. All rights reserved.
+***/
+
+#include <net/ethernet.h>
+#include <netinet/in.h>
+
+#include "socket-util.h"
+#include "sparse-endian.h"
+
+int arp_update_filter(int fd, const struct in_addr *a, const struct ether_addr *mac);
+int arp_network_bind_raw_socket(int ifindex, const struct in_addr *a, const struct ether_addr *mac);
+
+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/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h
new file mode 100644
index 0000000..28ce80c
--- /dev/null
+++ b/src/libsystemd-network/dhcp-client-internal.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <errno.h>
+
+#include "sd-dhcp-client.h"
+
+#include "macro.h"
+#include "network-common.h"
+
+typedef enum DHCPState {
+ DHCP_STATE_STOPPED,
+ DHCP_STATE_INIT,
+ DHCP_STATE_SELECTING,
+ DHCP_STATE_INIT_REBOOT,
+ DHCP_STATE_REBOOTING,
+ DHCP_STATE_REQUESTING,
+ DHCP_STATE_BOUND,
+ DHCP_STATE_RENEWING,
+ DHCP_STATE_REBINDING,
+ _DHCP_STATE_MAX,
+ _DHCP_STATE_INVALID = -EINVAL,
+} DHCPState;
+
+const char *dhcp_state_to_string(DHCPState s) _const_;
+
+typedef struct sd_dhcp_client sd_dhcp_client;
+
+int dhcp_client_set_state_callback(
+ sd_dhcp_client *client,
+ sd_dhcp_client_callback_t cb,
+ void *userdata);
+int dhcp_client_get_state(sd_dhcp_client *client);
+
+/* If we are invoking callbacks of a dhcp-client, ensure unreffing the
+ * client from the callback doesn't destroy the object we are working
+ * on */
+#define DHCP_CLIENT_DONT_DESTROY(client) \
+ _cleanup_(sd_dhcp_client_unrefp) _unused_ sd_dhcp_client *_dont_destroy_##client = sd_dhcp_client_ref(client)
+
+#define log_dhcp_client_errno(client, error, fmt, ...) \
+ log_interface_prefix_full_errno( \
+ "DHCPv4 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, client, \
+ 0, fmt, ##__VA_ARGS__)
diff --git a/src/libsystemd-network/dhcp-identifier.c b/src/libsystemd-network/dhcp-identifier.c
new file mode 100644
index 0000000..f65cdbe
--- /dev/null
+++ b/src/libsystemd-network/dhcp-identifier.c
@@ -0,0 +1,209 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/if_infiniband.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+
+#include "dhcp-identifier.h"
+#include "netif-util.h"
+#include "network-common.h"
+#include "siphash24.h"
+#include "sparse-endian.h"
+#include "string-table.h"
+
+#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
+#define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03)
+#define USEC_2000 ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */
+
+static const char * const duid_type_table[_DUID_TYPE_MAX] = {
+ [DUID_TYPE_LLT] = "DUID-LLT",
+ [DUID_TYPE_EN] = "DUID-EN/Vendor",
+ [DUID_TYPE_LL] = "DUID-LL",
+ [DUID_TYPE_UUID] = "UUID",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(duid_type, DUIDType);
+
+int dhcp_identifier_set_duid_llt(
+ const struct hw_addr_data *hw_addr,
+ uint16_t arp_type,
+ usec_t t,
+ struct duid *ret_duid,
+ size_t *ret_len) {
+
+ uint16_t time_from_2000y;
+
+ assert(hw_addr);
+ assert(ret_duid);
+ assert(ret_len);
+
+ if (hw_addr->length == 0)
+ return -EOPNOTSUPP;
+
+ if (arp_type == ARPHRD_ETHER)
+ assert_return(hw_addr->length == ETH_ALEN, -EINVAL);
+ else if (arp_type == ARPHRD_INFINIBAND)
+ assert_return(hw_addr->length == INFINIBAND_ALEN, -EINVAL);
+ else
+ return -EOPNOTSUPP;
+
+ if (t < USEC_2000)
+ time_from_2000y = 0;
+ else
+ time_from_2000y = (uint16_t) (((t - USEC_2000) / USEC_PER_SEC) & 0xffffffff);
+
+ unaligned_write_be16(&ret_duid->type, DUID_TYPE_LLT);
+ unaligned_write_be16(&ret_duid->llt.htype, arp_type);
+ unaligned_write_be32(&ret_duid->llt.time, time_from_2000y);
+ memcpy(ret_duid->llt.haddr, hw_addr->bytes, hw_addr->length);
+
+ *ret_len = offsetof(struct duid, llt.haddr) + hw_addr->length;
+
+ return 0;
+}
+
+int dhcp_identifier_set_duid_ll(
+ const struct hw_addr_data *hw_addr,
+ uint16_t arp_type,
+ struct duid *ret_duid,
+ size_t *ret_len) {
+
+ assert(hw_addr);
+ assert(ret_duid);
+ assert(ret_len);
+
+ if (hw_addr->length == 0)
+ return -EOPNOTSUPP;
+
+ if (arp_type == ARPHRD_ETHER)
+ assert_return(hw_addr->length == ETH_ALEN, -EINVAL);
+ else if (arp_type == ARPHRD_INFINIBAND)
+ assert_return(hw_addr->length == INFINIBAND_ALEN, -EINVAL);
+ else
+ return -EOPNOTSUPP;
+
+ unaligned_write_be16(&ret_duid->type, DUID_TYPE_LL);
+ unaligned_write_be16(&ret_duid->ll.htype, arp_type);
+ memcpy(ret_duid->ll.haddr, hw_addr->bytes, hw_addr->length);
+
+ *ret_len = offsetof(struct duid, ll.haddr) + hw_addr->length;
+
+ return 0;
+}
+
+int dhcp_identifier_set_duid_en(struct duid *ret_duid, size_t *ret_len) {
+ sd_id128_t machine_id;
+ bool test_mode;
+ uint64_t hash;
+ int r;
+
+ assert(ret_duid);
+ assert(ret_len);
+
+ test_mode = network_test_mode_enabled();
+
+ if (!test_mode) {
+ r = sd_id128_get_machine(&machine_id);
+ if (r < 0)
+ return r;
+ } else
+ /* For tests, especially for fuzzers, reproducibility is important.
+ * Hence, use a static and constant machine ID.
+ * See 9216fddc5a8ac2742e6cfa7660f95c20ca4f2193. */
+ machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10);
+
+ unaligned_write_be16(&ret_duid->type, DUID_TYPE_EN);
+ unaligned_write_be32(&ret_duid->en.pen, SYSTEMD_PEN);
+
+ /* a bit of snake-oil perhaps, but no need to expose the machine-id
+ * directly; duid->en.id might not be aligned, so we need to copy */
+ hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes));
+ memcpy(ret_duid->en.id, &hash, sizeof(hash));
+
+ *ret_len = offsetof(struct duid, en.id) + sizeof(hash);
+
+ if (test_mode)
+ assert_se(memcmp(ret_duid, (const uint8_t[]) { 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2 }, *ret_len) == 0);
+
+ return 0;
+}
+
+int dhcp_identifier_set_duid_uuid(struct duid *ret_duid, size_t *ret_len) {
+ sd_id128_t machine_id;
+ int r;
+
+ assert(ret_duid);
+ assert(ret_len);
+
+ r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id);
+ if (r < 0)
+ return r;
+
+ unaligned_write_be16(&ret_duid->type, DUID_TYPE_UUID);
+ memcpy(&ret_duid->uuid.uuid, &machine_id, sizeof(machine_id));
+
+ *ret_len = offsetof(struct duid, uuid.uuid) + sizeof(machine_id);
+
+ return 0;
+}
+
+int dhcp_identifier_set_duid_raw(
+ DUIDType duid_type,
+ const uint8_t *buf,
+ size_t buf_len,
+ struct duid *ret_duid,
+ size_t *ret_len) {
+
+ assert(buf || buf_len == 0);
+ assert(ret_duid);
+ assert(ret_len);
+
+ if (duid_type < 0 || duid_type > UINT16_MAX)
+ return -EINVAL;
+
+ if (buf_len > MAX_DUID_DATA_LEN)
+ return -EINVAL;
+
+ unaligned_write_be16(&ret_duid->type, duid_type);
+ memcpy_safe(ret_duid->raw.data, buf, buf_len);
+
+ *ret_len = offsetof(struct duid, raw.data) + buf_len;
+ return 0;
+}
+
+int dhcp_identifier_set_iaid(
+ sd_device *dev,
+ const struct hw_addr_data *hw_addr,
+ bool legacy_unstable_byteorder,
+ void *ret) {
+
+ const char *name = NULL;
+ uint32_t id32;
+ uint64_t id;
+
+ assert(hw_addr);
+ assert(ret);
+
+ if (dev)
+ name = net_get_persistent_name(dev);
+ if (name)
+ id = siphash24(name, strlen(name), HASH_KEY.bytes);
+ else
+ /* fall back to MAC address if no predictable name available */
+ id = siphash24(hw_addr->bytes, hw_addr->length, HASH_KEY.bytes);
+
+ id32 = (id & 0xffffffff) ^ (id >> 32);
+
+ if (legacy_unstable_byteorder)
+ /* for historical reasons (a bug), the bits were swapped and thus
+ * the result was endianness dependent. Preserve that behavior. */
+ id32 = bswap_32(id32);
+ else
+ /* the fixed behavior returns a stable byte order. Since LE is expected
+ * to be more common, swap the bytes on LE to give the same as legacy
+ * behavior. */
+ id32 = be32toh(id32);
+
+ unaligned_write_ne32(ret, id32);
+ return 0;
+}
diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-identifier.h
new file mode 100644
index 0000000..96db588
--- /dev/null
+++ b/src/libsystemd-network/dhcp-identifier.h
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-device.h"
+#include "sd-id128.h"
+
+#include "ether-addr-util.h"
+#include "macro.h"
+#include "sparse-endian.h"
+#include "time-util.h"
+#include "unaligned.h"
+
+#define SYSTEMD_PEN 43793
+
+typedef enum DUIDType {
+ DUID_TYPE_LLT = 1,
+ DUID_TYPE_EN = 2,
+ DUID_TYPE_LL = 3,
+ DUID_TYPE_UUID = 4,
+ _DUID_TYPE_MAX,
+ _DUID_TYPE_INVALID = -EINVAL,
+ _DUID_TYPE_FORCE_U16 = UINT16_MAX,
+} DUIDType;
+
+/* RFC 8415 section 11.1:
+ * A DUID consists of a 2-octet type code represented in network byte order, followed by a variable number of
+ * octets that make up the actual identifier. The length of the DUID (not including the type code) is at
+ * least 1 octet and at most 128 octets. */
+#define MAX_DUID_DATA_LEN 128
+#define MAX_DUID_LEN (sizeof(be16_t) + MAX_DUID_DATA_LEN)
+
+/* https://tools.ietf.org/html/rfc3315#section-9.1 */
+struct duid {
+ be16_t type;
+ union {
+ struct {
+ /* DUID_TYPE_LLT */
+ be16_t htype;
+ be32_t time;
+ uint8_t haddr[];
+ } _packed_ llt;
+ struct {
+ /* DUID_TYPE_EN */
+ be32_t pen;
+ uint8_t id[];
+ } _packed_ en;
+ struct {
+ /* DUID_TYPE_LL */
+ be16_t htype;
+ uint8_t haddr[];
+ } _packed_ ll;
+ struct {
+ /* DUID_TYPE_UUID */
+ sd_id128_t uuid;
+ } _packed_ uuid;
+ struct {
+ uint8_t data[MAX_DUID_DATA_LEN];
+ } _packed_ raw;
+ };
+} _packed_;
+
+int dhcp_identifier_set_duid_llt(
+ const struct hw_addr_data *hw_addr,
+ uint16_t arp_type,
+ usec_t t,
+ struct duid *ret_duid,
+ size_t *ret_len);
+int dhcp_identifier_set_duid_ll(
+ const struct hw_addr_data *hw_addr,
+ uint16_t arp_type,
+ struct duid *ret_duid,
+ size_t *ret_len);
+int dhcp_identifier_set_duid_en(struct duid *ret_duid, size_t *ret_len);
+int dhcp_identifier_set_duid_uuid(struct duid *ret_duid, size_t *ret_len);
+int dhcp_identifier_set_duid_raw(
+ DUIDType duid_type,
+ const uint8_t *buf,
+ size_t buf_len,
+ struct duid *ret_duid,
+ size_t *ret_len);
+int dhcp_identifier_set_iaid(
+ sd_device *dev,
+ const struct hw_addr_data *hw_addr,
+ bool legacy_unstable_byteorder,
+ void *ret);
+
+const char *duid_type_to_string(DUIDType t) _const_;
diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h
new file mode 100644
index 0000000..a3d8bb4
--- /dev/null
+++ b/src/libsystemd-network/dhcp-lease-internal.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include "sd-dhcp-client.h"
+
+#include "alloc-util.h"
+#include "dhcp-option.h"
+#include "list.h"
+#include "time-util.h"
+
+struct sd_dhcp_route {
+ struct in_addr dst_addr;
+ struct in_addr gw_addr;
+ unsigned char dst_prefixlen;
+};
+
+struct sd_dhcp_raw_option {
+ LIST_FIELDS(struct sd_dhcp_raw_option, options);
+
+ uint8_t tag;
+ uint8_t length;
+ void *data;
+};
+
+struct sd_dhcp_lease {
+ unsigned n_ref;
+
+ /* each 0 if unset */
+ usec_t t1;
+ usec_t t2;
+ usec_t lifetime;
+ triple_timestamp timestamp;
+ usec_t ipv6_only_preferred_usec;
+
+ /* each 0 if unset */
+ be32_t address;
+ be32_t server_address;
+ be32_t next_server;
+
+ bool have_subnet_mask;
+ be32_t subnet_mask;
+
+ bool have_broadcast;
+ be32_t broadcast;
+
+ struct in_addr *router;
+ size_t router_size;
+
+ bool rapid_commit;
+
+ DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
+
+ struct sd_dhcp_route *static_routes;
+ size_t n_static_routes;
+ struct sd_dhcp_route *classless_routes;
+ size_t n_classless_routes;
+
+ uint16_t mtu; /* 0 if unset */
+
+ char *domainname;
+ char **search_domains;
+ char *hostname;
+ char *root_path;
+ char *captive_portal;
+
+ void *client_id;
+ size_t client_id_len;
+
+ void *vendor_specific;
+ size_t vendor_specific_len;
+
+ char *timezone;
+
+ uint8_t sixrd_ipv4masklen;
+ uint8_t sixrd_prefixlen;
+ struct in6_addr sixrd_prefix;
+ struct in_addr *sixrd_br_addresses;
+ size_t sixrd_n_br_addresses;
+
+ LIST_HEAD(struct sd_dhcp_raw_option, private_options);
+};
+
+int dhcp_lease_new(sd_dhcp_lease **ret);
+
+int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata);
+int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains);
+int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len);
+
+void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *timestamp);
+int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease);
+int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len);
+
+#define dhcp_lease_unref_and_replace(a, b) \
+ unref_and_replace_full(a, b, sd_dhcp_lease_ref, sd_dhcp_lease_unref)
diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c
new file mode 100644
index 0000000..1f4ad09
--- /dev/null
+++ b/src/libsystemd-network/dhcp-network.c
@@ -0,0 +1,287 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <stdio.h>
+#include <string.h>
+#include <linux/filter.h>
+#include <linux/if_infiniband.h>
+#include <linux/if_packet.h>
+
+#include "dhcp-network.h"
+#include "dhcp-protocol.h"
+#include "fd-util.h"
+#include "unaligned.h"
+
+static int _bind_raw_socket(
+ int ifindex,
+ union sockaddr_union *link,
+ uint32_t xid,
+ const struct hw_addr_data *hw_addr,
+ const struct hw_addr_data *bcast_addr,
+ uint16_t arp_type,
+ uint16_t port,
+ bool so_priority_set,
+ int so_priority) {
+
+ assert(ifindex > 0);
+ assert(link);
+ assert(hw_addr);
+ assert(bcast_addr);
+ assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND));
+
+ switch (arp_type) {
+ case ARPHRD_ETHER:
+ assert(hw_addr->length == ETH_ALEN);
+ assert(bcast_addr->length == ETH_ALEN);
+ break;
+ case ARPHRD_INFINIBAND:
+ assert(hw_addr->length == 0);
+ assert(bcast_addr->length == INFINIBAND_ALEN);
+ break;
+ default:
+ assert_not_reached();
+ }
+
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */
+ BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(DHCPPacket), 1, 0), /* packet >= DHCPPacket ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.protocol)), /* A <- IP protocol */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags */
+ BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x20), /* A <- A & 0x20 (More Fragments bit) */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags + Fragment offset */
+ BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x1fff), /* A <- A & 0x1fff (Fragment offset) */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, udp.dest)), /* A <- UDP destination port */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, port, 1, 0), /* UDP destination port == DHCP client port ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.op)), /* A <- DHCP op */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.htype)), /* A <- DHCP header type */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arp_type, 1, 0), /* header type == arp_type ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.xid)), /* A <- client identifier */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, xid, 1, 0), /* client identifier == xid ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)), /* A <- MAC address length */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (uint8_t) hw_addr->length, 1, 0), /* address length == hw_addr->length ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ /* We only support MAC address length to be either 0 or 6 (ETH_ALEN). Optionally
+ * compare chaddr for ETH_ALEN bytes. */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 8), /* A (the MAC address length) == ETH_ALEN ? */
+ BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be32(hw_addr->bytes)), /* X <- 4 bytes of client's MAC */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr)), /* A <- 4 bytes of MAC from dhcp.chaddr */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A == X ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be16(hw_addr->bytes + 4)), /* X <- remainder of client's MAC */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr) + 4), /* A <- remainder of MAC from dhcp.chaddr */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A == X ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.magic)), /* A <- DHCP magic cookie */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0), /* cookie == DHCP magic cookie ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept */
+ };
+ struct sock_fprog fprog = {
+ .len = ELEMENTSOF(filter),
+ .filter = filter
+ };
+ _cleanup_close_ int s = -EBADF;
+ int r;
+
+ s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (s < 0)
+ return -errno;
+
+ r = setsockopt_int(s, SOL_PACKET, PACKET_AUXDATA, true);
+ if (r < 0)
+ return r;
+
+ r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true);
+ if (r < 0)
+ return r;
+
+ if (so_priority_set) {
+ r = setsockopt_int(s, SOL_SOCKET, SO_PRIORITY, so_priority);
+ if (r < 0)
+ return r;
+ }
+
+ link->ll = (struct sockaddr_ll) {
+ .sll_family = AF_PACKET,
+ .sll_protocol = htobe16(ETH_P_IP),
+ .sll_ifindex = ifindex,
+ .sll_hatype = htobe16(arp_type),
+ .sll_halen = bcast_addr->length,
+ };
+ /* We may overflow link->ll. link->ll_buffer ensures we have enough space. */
+ memcpy(link->ll.sll_addr, bcast_addr->bytes, bcast_addr->length);
+
+ r = bind(s, &link->sa, SOCKADDR_LL_LEN(link->ll));
+ if (r < 0)
+ return -errno;
+
+ return TAKE_FD(s);
+}
+
+int dhcp_network_bind_raw_socket(
+ int ifindex,
+ union sockaddr_union *link,
+ uint32_t xid,
+ const struct hw_addr_data *hw_addr,
+ const struct hw_addr_data *bcast_addr,
+ uint16_t arp_type,
+ uint16_t port,
+ bool so_priority_set,
+ int so_priority) {
+
+ static struct hw_addr_data default_eth_bcast = {
+ .length = ETH_ALEN,
+ .ether = {{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }},
+ }, default_ib_bcast = {
+ .length = INFINIBAND_ALEN,
+ .infiniband = {
+ 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff
+ },
+ };
+
+ assert(ifindex > 0);
+ assert(link);
+ assert(hw_addr);
+
+ switch (arp_type) {
+ case ARPHRD_ETHER:
+ return _bind_raw_socket(ifindex, link, xid,
+ hw_addr,
+ (bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_eth_bcast,
+ arp_type, port, so_priority_set, so_priority);
+
+ case ARPHRD_INFINIBAND:
+ return _bind_raw_socket(ifindex, link, xid,
+ &HW_ADDR_NULL,
+ (bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_ib_bcast,
+ arp_type, port, so_priority_set, so_priority);
+ default:
+ return -EINVAL;
+ }
+}
+
+int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) {
+ union sockaddr_union src = {
+ .in.sin_family = AF_INET,
+ .in.sin_port = htobe16(port),
+ .in.sin_addr.s_addr = address,
+ };
+ _cleanup_close_ int s = -EBADF;
+ int r;
+
+ s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (s < 0)
+ return -errno;
+
+ if (ip_service_type >= 0)
+ r = setsockopt_int(s, IPPROTO_IP, IP_TOS, ip_service_type);
+ else
+ r = setsockopt_int(s, IPPROTO_IP, IP_TOS, IPTOS_CLASS_CS6);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true);
+ if (r < 0)
+ return r;
+
+ if (ifindex > 0) {
+ r = socket_bind_to_ifindex(s, ifindex);
+ if (r < 0)
+ return r;
+ }
+
+ if (port == DHCP_PORT_SERVER) {
+ r = setsockopt_int(s, SOL_SOCKET, SO_BROADCAST, true);
+ if (r < 0)
+ return r;
+ if (address == INADDR_ANY) {
+ /* IP_PKTINFO filter should not be applied when packets are
+ allowed to enter/leave through the interface other than
+ DHCP server sits on(BindToInterface option). */
+ r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true);
+ if (r < 0)
+ return r;
+ }
+ } else {
+ r = setsockopt_int(s, IPPROTO_IP, IP_FREEBIND, true);
+ if (r < 0)
+ return r;
+ }
+
+ if (bind(s, &src.sa, sizeof(src.in)) < 0)
+ return -errno;
+
+ return TAKE_FD(s);
+}
+
+int dhcp_network_send_raw_socket(
+ int s,
+ const union sockaddr_union *link,
+ const void *packet,
+ size_t len) {
+
+ /* Do not add assert(s >= 0) here, as this is called in fuzz-dhcp-server, and in that case this
+ * function should fail with negative errno. */
+
+ assert(link);
+ assert(packet);
+ assert(len > 0);
+
+ if (sendto(s, packet, len, 0, &link->sa, SOCKADDR_LL_LEN(link->ll)) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int dhcp_network_send_udp_socket(
+ int s,
+ be32_t address,
+ uint16_t port,
+ const void *packet,
+ size_t len) {
+
+ union sockaddr_union dest = {
+ .in.sin_family = AF_INET,
+ .in.sin_port = htobe16(port),
+ .in.sin_addr.s_addr = address,
+ };
+
+ assert(s >= 0);
+ assert(packet);
+ assert(len > 0);
+
+ if (sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in)) < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/src/libsystemd-network/dhcp-network.h b/src/libsystemd-network/dhcp-network.h
new file mode 100644
index 0000000..eb9dab4
--- /dev/null
+++ b/src/libsystemd-network/dhcp-network.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "ether-addr-util.h"
+#include "socket-util.h"
+
+int dhcp_network_bind_raw_socket(
+ int ifindex,
+ union sockaddr_union *link,
+ uint32_t xid,
+ const struct hw_addr_data *hw_addr,
+ const struct hw_addr_data *bcast_addr,
+ uint16_t arp_type,
+ uint16_t port,
+ bool so_priority_set,
+ int so_priority);
+int dhcp_network_bind_udp_socket(
+ int ifindex,
+ be32_t address,
+ uint16_t port,
+ int ip_service_type);
+int dhcp_network_send_raw_socket(
+ int s,
+ const union sockaddr_union *link,
+ const void *packet,
+ size_t len);
+int dhcp_network_send_udp_socket(
+ int s,
+ be32_t address,
+ uint16_t port,
+ const void *packet,
+ size_t len);
diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c
new file mode 100644
index 0000000..5e216c5
--- /dev/null
+++ b/src/libsystemd-network/dhcp-option.c
@@ -0,0 +1,461 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "alloc-util.h"
+#include "dhcp-option.h"
+#include "dhcp-server-internal.h"
+#include "memory-util.h"
+#include "ordered-set.h"
+#include "strv.h"
+#include "utf8.h"
+
+/* Append type-length value structure to the options buffer */
+static int dhcp_option_append_tlv(uint8_t options[], size_t size, size_t *offset, uint8_t code, size_t optlen, const void *optval) {
+ assert(options);
+ assert(size > 0);
+ assert(offset);
+ assert(optlen <= UINT8_MAX);
+ assert(*offset < size);
+
+ if (*offset + 2 + optlen > size)
+ return -ENOBUFS;
+
+ options[*offset] = code;
+ options[*offset + 1] = optlen;
+
+ memcpy_safe(&options[*offset + 2], optval, optlen);
+ *offset += 2 + optlen;
+ return 0;
+}
+
+static int option_append(uint8_t options[], size_t size, size_t *offset,
+ uint8_t code, size_t optlen, const void *optval) {
+ assert(options);
+ assert(size > 0);
+ assert(offset);
+
+ int r;
+
+ if (code != SD_DHCP_OPTION_END)
+ /* always make sure there is space for an END option */
+ size--;
+
+ switch (code) {
+
+ case SD_DHCP_OPTION_PAD:
+ case SD_DHCP_OPTION_END:
+ if (*offset + 1 > size)
+ return -ENOBUFS;
+
+ options[*offset] = code;
+ *offset += 1;
+ break;
+
+ case SD_DHCP_OPTION_USER_CLASS: {
+ size_t total = 0;
+
+ if (strv_isempty((char **) optval))
+ return -EINVAL;
+
+ STRV_FOREACH(s, (const char* const*) optval) {
+ size_t len = strlen(*s);
+
+ if (len > 255 || len == 0)
+ return -EINVAL;
+
+ total += 1 + len;
+ }
+
+ if (*offset + 2 + total > size)
+ return -ENOBUFS;
+
+ options[*offset] = code;
+ options[*offset + 1] = total;
+ *offset += 2;
+
+ STRV_FOREACH(s, (const char* const*) optval) {
+ size_t len = strlen(*s);
+
+ options[*offset] = len;
+ memcpy(&options[*offset + 1], *s, len);
+ *offset += 1 + len;
+ }
+
+ break;
+ }
+ case SD_DHCP_OPTION_SIP_SERVER:
+ if (*offset + 3 + optlen > size)
+ return -ENOBUFS;
+
+ options[*offset] = code;
+ options[*offset + 1] = optlen + 1;
+ options[*offset + 2] = 1;
+
+ memcpy_safe(&options[*offset + 3], optval, optlen);
+ *offset += 3 + optlen;
+
+ break;
+ case SD_DHCP_OPTION_VENDOR_SPECIFIC: {
+ OrderedSet *s = (OrderedSet *) optval;
+ struct sd_dhcp_option *p;
+ size_t l = 0;
+
+ ORDERED_SET_FOREACH(p, s)
+ l += p->length + 2;
+
+ if (*offset + l + 2 > size)
+ return -ENOBUFS;
+
+ options[*offset] = code;
+ options[*offset + 1] = l;
+ *offset += 2;
+
+ ORDERED_SET_FOREACH(p, s) {
+ r = dhcp_option_append_tlv(options, size, offset, p->option, p->length, p->data);
+ if (r < 0)
+ return r;
+ }
+ break;
+ }
+ case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: {
+ sd_dhcp_server *server = (sd_dhcp_server *) optval;
+ size_t current_offset = *offset + 2;
+
+ if (server->agent_circuit_id) {
+ r = dhcp_option_append_tlv(options, size, &current_offset, SD_DHCP_RELAY_AGENT_CIRCUIT_ID,
+ strlen(server->agent_circuit_id), server->agent_circuit_id);
+ if (r < 0)
+ return r;
+ }
+ if (server->agent_remote_id) {
+ r = dhcp_option_append_tlv(options, size, &current_offset, SD_DHCP_RELAY_AGENT_REMOTE_ID,
+ strlen(server->agent_remote_id), server->agent_remote_id);
+ if (r < 0)
+ return r;
+ }
+
+ options[*offset] = code;
+ options[*offset + 1] = current_offset - *offset - 2;
+ assert(current_offset - *offset - 2 <= UINT8_MAX);
+ *offset = current_offset;
+ break;
+ }
+ default:
+ return dhcp_option_append_tlv(options, size, offset, code, optlen, optval);
+ }
+ return 0;
+}
+
+static int option_length(uint8_t *options, size_t length, size_t offset) {
+ assert(options);
+ assert(offset < length);
+
+ if (IN_SET(options[offset], SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END))
+ return 1;
+ if (length < offset + 2)
+ return -ENOBUFS;
+
+ /* validating that buffer is long enough */
+ if (length < offset + 2 + options[offset + 1])
+ return -ENOBUFS;
+
+ return options[offset + 1] + 2;
+}
+
+int dhcp_option_find_option(uint8_t *options, size_t length, uint8_t code, size_t *ret_offset) {
+ int r;
+
+ assert(options);
+ assert(ret_offset);
+
+ for (size_t offset = 0; offset < length; offset += r) {
+ r = option_length(options, length, offset);
+ if (r < 0)
+ return r;
+
+ if (code == options[offset]) {
+ *ret_offset = offset;
+ return r;
+ }
+ }
+ return -ENOENT;
+}
+
+int dhcp_option_remove_option(uint8_t *options, size_t length, uint8_t option_code) {
+ int r;
+ size_t offset;
+
+ assert(options);
+
+ r = dhcp_option_find_option(options, length, option_code, &offset);
+ if (r < 0)
+ return r;
+
+ memmove(options + offset, options + offset + r, length - offset - r);
+ return length - r;
+}
+
+int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
+ uint8_t overload,
+ uint8_t code, size_t optlen, const void *optval) {
+ const bool use_file = overload & DHCP_OVERLOAD_FILE;
+ const bool use_sname = overload & DHCP_OVERLOAD_SNAME;
+ int r;
+
+ assert(message);
+ assert(offset);
+
+ /* If *offset is in range [0, size), we are writing to ->options,
+ * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file,
+ * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname))
+ * and use_sname, we are writing to ->sname.
+ */
+
+ if (*offset < size) {
+ /* still space in the options array */
+ r = option_append(message->options, size, offset, code, optlen, optval);
+ if (r >= 0)
+ return 0;
+ else if (r == -ENOBUFS && (use_file || use_sname)) {
+ /* did not fit, but we have more buffers to try
+ close the options array and move the offset to its end */
+ r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ *offset = size;
+ } else
+ return r;
+ }
+
+ if (use_file) {
+ size_t file_offset = *offset - size;
+
+ if (file_offset < sizeof(message->file)) {
+ /* still space in the 'file' array */
+ r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval);
+ if (r >= 0) {
+ *offset = size + file_offset;
+ return 0;
+ } else if (r == -ENOBUFS && use_sname) {
+ /* did not fit, but we have more buffers to try
+ close the file array and move the offset to its end */
+ r = option_append(message->file, sizeof(message->file), &file_offset, SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ *offset = size + sizeof(message->file);
+ } else
+ return r;
+ }
+ }
+
+ if (use_sname) {
+ size_t sname_offset = *offset - size - use_file*sizeof(message->file);
+
+ if (sname_offset < sizeof(message->sname)) {
+ /* still space in the 'sname' array */
+ r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval);
+ if (r >= 0) {
+ *offset = size + use_file*sizeof(message->file) + sname_offset;
+ return 0;
+ } else
+ /* no space, or other error, give up */
+ return r;
+ }
+ }
+
+ return -ENOBUFS;
+}
+
+static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
+ uint8_t *message_type, char **error_message, dhcp_option_callback_t cb,
+ void *userdata) {
+ uint8_t code, len;
+ const uint8_t *option;
+ size_t offset = 0;
+ int r;
+
+ while (offset < buflen) {
+ code = options[offset ++];
+
+ switch (code) {
+ case SD_DHCP_OPTION_PAD:
+ continue;
+
+ case SD_DHCP_OPTION_END:
+ return 0;
+ }
+
+ if (buflen < offset + 1)
+ return -ENOBUFS;
+
+ len = options[offset ++];
+
+ if (buflen < offset + len)
+ return -EINVAL;
+
+ option = &options[offset];
+
+ switch (code) {
+ case SD_DHCP_OPTION_MESSAGE_TYPE:
+ if (len != 1)
+ return -EINVAL;
+
+ if (message_type)
+ *message_type = *option;
+
+ break;
+
+ case SD_DHCP_OPTION_ERROR_MESSAGE:
+ if (len == 0)
+ return -EINVAL;
+
+ if (error_message) {
+ _cleanup_free_ char *string = NULL;
+
+ r = make_cstring((const char*) option, len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &string);
+ if (r < 0)
+ return r;
+
+ if (!ascii_is_valid(string))
+ return -EINVAL;
+
+ free_and_replace(*error_message, string);
+ }
+
+ break;
+ case SD_DHCP_OPTION_OVERLOAD:
+ if (len != 1)
+ return -EINVAL;
+
+ if (overload)
+ *overload = *option;
+
+ break;
+
+ default:
+ if (cb)
+ cb(code, len, option, userdata);
+
+ break;
+ }
+
+ offset += len;
+ }
+
+ if (offset < buflen)
+ return -EINVAL;
+
+ return 0;
+}
+
+int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **ret_error_message) {
+ _cleanup_free_ char *error_message = NULL;
+ uint8_t overload = 0;
+ uint8_t message_type = 0;
+ int r;
+
+ if (!message)
+ return -EINVAL;
+
+ if (len < sizeof(DHCPMessage))
+ return -EINVAL;
+
+ len -= sizeof(DHCPMessage);
+
+ r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
+ if (r < 0)
+ return r;
+
+ if (overload & DHCP_OVERLOAD_FILE) {
+ r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
+ if (r < 0)
+ return r;
+ }
+
+ if (overload & DHCP_OVERLOAD_SNAME) {
+ r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
+ if (r < 0)
+ return r;
+ }
+
+ if (message_type == 0)
+ return -ENOMSG;
+
+ if (ret_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE))
+ *ret_error_message = TAKE_PTR(error_message);
+
+ return message_type;
+}
+
+int dhcp_option_parse_string(const uint8_t *option, size_t len, char **ret) {
+ int r;
+
+ assert(option);
+ assert(ret);
+
+ if (len <= 0)
+ *ret = mfree(*ret);
+ else {
+ char *string;
+
+ /*
+ * One trailing NUL byte is OK, we don't mind. See:
+ * https://github.com/systemd/systemd/issues/1337
+ */
+ r = make_cstring((const char *) option, len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &string);
+ if (r < 0)
+ return r;
+
+ free_and_replace(*ret, string);
+ }
+
+ return 0;
+}
+
+static sd_dhcp_option* dhcp_option_free(sd_dhcp_option *i) {
+ if (!i)
+ return NULL;
+
+ free(i->data);
+ return mfree(i);
+}
+
+int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret) {
+ assert_return(ret, -EINVAL);
+ assert_return(length == 0 || data, -EINVAL);
+
+ _cleanup_free_ void *q = memdup(data, length);
+ if (!q)
+ return -ENOMEM;
+
+ sd_dhcp_option *p = new(sd_dhcp_option, 1);
+ if (!p)
+ return -ENOMEM;
+
+ *p = (sd_dhcp_option) {
+ .n_ref = 1,
+ .option = option,
+ .length = length,
+ .data = TAKE_PTR(q),
+ };
+
+ *ret = TAKE_PTR(p);
+ return 0;
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option, sd_dhcp_option, dhcp_option_free);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ dhcp_option_hash_ops,
+ void,
+ trivial_hash_func,
+ trivial_compare_func,
+ sd_dhcp_option,
+ sd_dhcp_option_unref);
diff --git a/src/libsystemd-network/dhcp-option.h b/src/libsystemd-network/dhcp-option.h
new file mode 100644
index 0000000..425f5b5
--- /dev/null
+++ b/src/libsystemd-network/dhcp-option.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdint.h>
+
+#include "sd-dhcp-option.h"
+
+#include "dhcp-protocol.h"
+#include "hash-funcs.h"
+
+struct sd_dhcp_option {
+ unsigned n_ref;
+
+ uint8_t option;
+ void *data;
+ size_t length;
+};
+
+extern const struct hash_ops dhcp_option_hash_ops;
+
+typedef struct DHCPServerData {
+ struct in_addr *addr;
+ size_t size;
+} DHCPServerData;
+
+int dhcp_option_append(
+ DHCPMessage *message,
+ size_t size,
+ size_t *offset,
+ uint8_t overload,
+ uint8_t code,
+ size_t optlen,
+ const void *optval);
+int dhcp_option_find_option(uint8_t *options, size_t length, uint8_t wanted_code, size_t *ret_offset);
+int dhcp_option_remove_option(uint8_t *options, size_t buflen, uint8_t option_code);
+
+typedef int (*dhcp_option_callback_t)(uint8_t code, uint8_t len, const void *option, void *userdata);
+
+int dhcp_option_parse(
+ DHCPMessage *message,
+ size_t len,
+ dhcp_option_callback_t cb,
+ void *userdata,
+ char **ret_error_message);
+
+int dhcp_option_parse_string(const uint8_t *option, size_t len, char **ret);
diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c
new file mode 100644
index 0000000..75b1d7e
--- /dev/null
+++ b/src/libsystemd-network/dhcp-packet.c
@@ -0,0 +1,193 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+#include <string.h>
+
+#include "dhcp-option.h"
+#include "dhcp-packet.h"
+#include "memory-util.h"
+
+#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312
+
+int dhcp_message_init(
+ DHCPMessage *message,
+ uint8_t op,
+ uint32_t xid,
+ uint8_t type,
+ uint16_t arp_type,
+ uint8_t hlen,
+ const uint8_t *chaddr,
+ size_t optlen,
+ size_t *optoffset) {
+
+ size_t offset = 0;
+ int r;
+
+ assert(IN_SET(op, BOOTREQUEST, BOOTREPLY));
+ assert(chaddr || hlen == 0);
+
+ message->op = op;
+ message->htype = arp_type;
+
+ /* RFC2131 section 4.1.1:
+ The client MUST include its hardware address in the ’chaddr’ field, if
+ necessary for delivery of DHCP reply messages.
+
+ RFC 4390 section 2.1:
+ A DHCP client, when working over an IPoIB interface, MUST follow the
+ following rules:
+ "htype" (hardware address type) MUST be 32 [ARPPARAM].
+ "hlen" (hardware address length) MUST be 0.
+ "chaddr" (client hardware address) field MUST be zeroed.
+ */
+ message->hlen = arp_type == ARPHRD_INFINIBAND ? 0 : hlen;
+ memcpy_safe(message->chaddr, chaddr, message->hlen);
+
+ message->xid = htobe32(xid);
+ message->magic = htobe32(DHCP_MAGIC_COOKIE);
+
+ r = dhcp_option_append(message, optlen, &offset, 0,
+ SD_DHCP_OPTION_MESSAGE_TYPE, 1, &type);
+ if (r < 0)
+ return r;
+
+ *optoffset = offset;
+
+ return 0;
+}
+
+uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) {
+ uint64_t *buf_64 = (uint64_t*)buf;
+ uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t));
+ uint64_t sum = 0;
+
+ /* See RFC1071 */
+
+ while (buf_64 < end_64) {
+ sum += *buf_64;
+ if (sum < *buf_64)
+ /* wrap around in one's complement */
+ sum++;
+
+ buf_64++;
+ }
+
+ if (len % sizeof(uint64_t)) {
+ /* If the buffer is not aligned to 64-bit, we need
+ to zero-pad the last few bytes and add them in */
+ uint64_t buf_tail = 0;
+
+ memcpy(&buf_tail, buf_64, len % sizeof(uint64_t));
+
+ sum += buf_tail;
+ if (sum < buf_tail)
+ /* wrap around */
+ sum++;
+ }
+
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+
+ return ~sum;
+}
+
+void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
+ uint16_t source_port, be32_t destination_addr,
+ uint16_t destination_port, uint16_t len, int ip_service_type) {
+ packet->ip.version = IPVERSION;
+ packet->ip.ihl = DHCP_IP_SIZE / 4;
+ packet->ip.tot_len = htobe16(len);
+
+ if (ip_service_type >= 0)
+ packet->ip.tos = ip_service_type;
+ else
+ packet->ip.tos = IPTOS_CLASS_CS6;
+
+ packet->ip.protocol = IPPROTO_UDP;
+ packet->ip.saddr = source_addr;
+ packet->ip.daddr = destination_addr;
+
+ packet->udp.source = htobe16(source_port);
+ packet->udp.dest = htobe16(destination_port);
+
+ packet->udp.len = htobe16(len - DHCP_IP_SIZE);
+
+ packet->ip.check = packet->udp.len;
+ packet->udp.check = dhcp_packet_checksum((uint8_t*)&packet->ip.ttl, len - 8);
+
+ packet->ip.ttl = IPDEFTTL;
+ packet->ip.check = 0;
+ packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE);
+}
+
+int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) {
+ size_t hdrlen;
+
+ assert(packet);
+
+ if (len < sizeof(DHCPPacket))
+ return 0;
+
+ /* IP */
+
+ if (packet->ip.version != IPVERSION)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: not IPv4");
+
+ if (packet->ip.ihl < 5)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: IPv4 IHL (%i words) invalid",
+ packet->ip.ihl);
+
+ hdrlen = packet->ip.ihl * 4;
+ if (hdrlen < 20)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: IPv4 IHL (%zu bytes) smaller than minimum (20 bytes)",
+ hdrlen);
+
+ if (len < hdrlen)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: packet (%zu bytes) smaller than expected (%zu) by IP header",
+ len, hdrlen);
+
+ /* UDP */
+
+ if (packet->ip.protocol != IPPROTO_UDP)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: not UDP");
+
+ if (len < hdrlen + be16toh(packet->udp.len))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: packet (%zu bytes) smaller than expected (%zu) by UDP header",
+ len, hdrlen + be16toh(packet->udp.len));
+
+ if (be16toh(packet->udp.dest) != port)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: to port %u, which is not the DHCP client port (%u)",
+ be16toh(packet->udp.dest), port);
+
+ /* checksums - computing these is relatively expensive, so only do it
+ if all the other checks have passed
+ */
+
+ if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: invalid IP checksum");
+
+ if (checksum && packet->udp.check) {
+ packet->ip.check = packet->udp.len;
+ packet->ip.ttl = 0;
+
+ if (dhcp_packet_checksum((uint8_t*)&packet->ip.ttl,
+ be16toh(packet->udp.len) + 12))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: invalid UDP checksum");
+ }
+
+ return 0;
+}
diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h
new file mode 100644
index 0000000..751321b
--- /dev/null
+++ b/src/libsystemd-network/dhcp-packet.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "dhcp-protocol.h"
+
+int dhcp_message_init(
+ DHCPMessage *message,
+ uint8_t op,
+ uint32_t xid,
+ uint8_t type,
+ uint16_t arp_type,
+ uint8_t hlen,
+ const uint8_t *chaddr,
+ size_t optlen,
+ size_t *optoffset);
+
+uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len);
+
+void dhcp_packet_append_ip_headers(
+ DHCPPacket *packet,
+ be32_t source_addr,
+ uint16_t source,
+ be32_t destination_addr,
+ uint16_t destination,
+ uint16_t len,
+ int ip_service_type);
+
+int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port);
diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h
new file mode 100644
index 0000000..d7bb203
--- /dev/null
+++ b/src/libsystemd-network/dhcp-protocol.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <stdint.h>
+
+#include "sd-dhcp-protocol.h"
+
+#include "macro.h"
+#include "sparse-endian.h"
+#include "time-util.h"
+
+/* RFC 8925 - IPv6-Only Preferred Option for DHCPv4 3.4.
+ * MIN_V6ONLY_WAIT: The lower boundary for V6ONLY_WAIT. Value: 300 seconds */
+#define MIN_V6ONLY_WAIT_USEC (300U * USEC_PER_SEC)
+
+struct DHCPMessage {
+ uint8_t op;
+ uint8_t htype;
+ uint8_t hlen;
+ uint8_t hops;
+ be32_t xid;
+ be16_t secs;
+ be16_t flags;
+ be32_t ciaddr;
+ be32_t yiaddr;
+ be32_t siaddr;
+ be32_t giaddr;
+ uint8_t chaddr[16];
+ uint8_t sname[64];
+ uint8_t file[128];
+ be32_t magic;
+ uint8_t options[];
+} _packed_;
+
+typedef struct DHCPMessage DHCPMessage;
+
+struct DHCPPacket {
+ struct iphdr ip;
+ struct udphdr udp;
+ DHCPMessage dhcp;
+} _packed_;
+
+typedef struct DHCPPacket DHCPPacket;
+
+#define DHCP_IP_SIZE (int32_t)(sizeof(struct iphdr))
+#define DHCP_IP_UDP_SIZE (int32_t)(sizeof(struct udphdr) + DHCP_IP_SIZE)
+#define DHCP_HEADER_SIZE (int32_t)(sizeof(DHCPMessage))
+#define DHCP_MIN_MESSAGE_SIZE 576 /* the minimum internet hosts must be able to receive, see RFC 2132 Section 9.10 */
+#define DHCP_MIN_OPTIONS_SIZE (DHCP_MIN_MESSAGE_SIZE - DHCP_HEADER_SIZE)
+#define DHCP_MIN_PACKET_SIZE (DHCP_MIN_MESSAGE_SIZE + DHCP_IP_UDP_SIZE)
+#define DHCP_MAGIC_COOKIE (uint32_t)(0x63825363)
+
+enum {
+ DHCP_PORT_SERVER = 67,
+ DHCP_PORT_CLIENT = 68,
+};
+
+enum {
+ BOOTREQUEST = 1,
+ BOOTREPLY = 2,
+};
+
+enum {
+ DHCP_DISCOVER = 1, /* [RFC2132] */
+ DHCP_OFFER = 2, /* [RFC2132] */
+ DHCP_REQUEST = 3, /* [RFC2132] */
+ DHCP_DECLINE = 4, /* [RFC2132] */
+ DHCP_ACK = 5, /* [RFC2132] */
+ DHCP_NAK = 6, /* [RFC2132] */
+ DHCP_RELEASE = 7, /* [RFC2132] */
+ DHCP_INFORM = 8, /* [RFC2132] */
+ DHCP_FORCERENEW = 9, /* [RFC3203] */
+ DHCPLEASEQUERY = 10, /* [RFC4388] */
+ DHCPLEASEUNASSIGNED = 11, /* [RFC4388] */
+ DHCPLEASEUNKNOWN = 12, /* [RFC4388] */
+ DHCPLEASEACTIVE = 13, /* [RFC4388] */
+ DHCPBULKLEASEQUERY = 14, /* [RFC6926] */
+ DHCPLEASEQUERYDONE = 15, /* [RFC6926] */
+ DHCPACTIVELEASEQUERY = 16, /* [RFC7724] */
+ DHCPLEASEQUERYSTATUS = 17, /* [RFC7724] */
+ DHCPTLS = 18, /* [RFC7724] */
+};
+
+enum {
+ DHCP_OVERLOAD_FILE = 1,
+ DHCP_OVERLOAD_SNAME = 2,
+};
+
+#define DHCP_MAX_FQDN_LENGTH 255
+
+enum {
+ DHCP_FQDN_FLAG_S = (1 << 0),
+ DHCP_FQDN_FLAG_O = (1 << 1),
+ DHCP_FQDN_FLAG_E = (1 << 2),
+ DHCP_FQDN_FLAG_N = (1 << 3),
+};
diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
new file mode 100644
index 0000000..da9e56b
--- /dev/null
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include "sd-dhcp-server.h"
+#include "sd-event.h"
+
+#include "dhcp-option.h"
+#include "network-common.h"
+#include "ordered-set.h"
+#include "time-util.h"
+
+typedef enum DHCPRawOption {
+ DHCP_RAW_OPTION_DATA_UINT8,
+ DHCP_RAW_OPTION_DATA_UINT16,
+ DHCP_RAW_OPTION_DATA_UINT32,
+ DHCP_RAW_OPTION_DATA_STRING,
+ DHCP_RAW_OPTION_DATA_IPV4ADDRESS,
+ DHCP_RAW_OPTION_DATA_IPV6ADDRESS,
+ _DHCP_RAW_OPTION_DATA_MAX,
+ _DHCP_RAW_OPTION_DATA_INVALID,
+} DHCPRawOption;
+
+typedef struct DHCPClientId {
+ size_t length;
+ uint8_t *data;
+} DHCPClientId;
+
+typedef struct DHCPLease {
+ sd_dhcp_server *server;
+
+ DHCPClientId client_id;
+
+ uint8_t htype; /* e.g. ARPHRD_ETHER */
+ uint8_t hlen; /* e.g. ETH_ALEN */
+ be32_t address;
+ be32_t gateway;
+ uint8_t chaddr[16];
+ usec_t expiration;
+ char *hostname;
+} DHCPLease;
+
+struct sd_dhcp_server {
+ unsigned n_ref;
+
+ sd_event *event;
+ int event_priority;
+ sd_event_source *receive_message;
+ sd_event_source *receive_broadcast;
+ int fd;
+ int fd_raw;
+ int fd_broadcast;
+
+ int ifindex;
+ char *ifname;
+ bool bind_to_interface;
+ be32_t address;
+ be32_t netmask;
+ be32_t subnet;
+ uint32_t pool_offset;
+ uint32_t pool_size;
+
+ char *timezone;
+
+ DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
+ struct in_addr boot_server_address;
+ char *boot_server_name;
+ char *boot_filename;
+
+ OrderedSet *extra_options;
+ OrderedSet *vendor_options;
+
+ bool emit_router;
+ struct in_addr router_address;
+
+ Hashmap *bound_leases_by_client_id;
+ Hashmap *bound_leases_by_address;
+ Hashmap *static_leases_by_client_id;
+ Hashmap *static_leases_by_address;
+
+ usec_t max_lease_time;
+ usec_t default_lease_time;
+ usec_t ipv6_only_preferred_usec;
+ bool rapid_commit;
+
+ sd_dhcp_server_callback_t callback;
+ void *callback_userdata;
+
+ struct in_addr relay_target;
+
+ char *agent_circuit_id;
+ char *agent_remote_id;
+};
+
+typedef struct DHCPRequest {
+ /* received message */
+ DHCPMessage *message;
+
+ /* options */
+ DHCPClientId client_id;
+ size_t max_optlen;
+ be32_t server_id;
+ be32_t requested_ip;
+ usec_t lifetime;
+ const uint8_t *agent_info_option;
+ char *hostname;
+ const uint8_t *parameter_request_list;
+ size_t parameter_request_list_len;
+ bool rapid_commit;
+ triple_timestamp timestamp;
+} DHCPRequest;
+
+extern const struct hash_ops dhcp_lease_hash_ops;
+
+int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
+ size_t length, const triple_timestamp *timestamp);
+int dhcp_server_send_packet(sd_dhcp_server *server,
+ DHCPRequest *req, DHCPPacket *packet,
+ int type, size_t optoffset);
+
+void client_id_hash_func(const DHCPClientId *p, struct siphash *state);
+int client_id_compare_func(const DHCPClientId *a, const DHCPClientId *b);
+
+DHCPLease *dhcp_lease_free(DHCPLease *lease);
+DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPLease*, dhcp_lease_free);
+
+#define log_dhcp_server_errno(server, error, fmt, ...) \
+ log_interface_prefix_full_errno( \
+ "DHCPv4 server: ", \
+ sd_dhcp_server, server, \
+ error, fmt, ##__VA_ARGS__)
+#define log_dhcp_server(server, fmt, ...) \
+ log_interface_prefix_full_errno_zerook( \
+ "DHCPv4 server: ", \
+ sd_dhcp_server, server, \
+ 0, fmt, ##__VA_ARGS__)
diff --git a/src/libsystemd-network/dhcp6-client-internal.h b/src/libsystemd-network/dhcp6-client-internal.h
new file mode 100644
index 0000000..6c17f57
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-client-internal.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-dhcp6-client.h"
+
+int dhcp6_client_set_state_callback(
+ sd_dhcp6_client *client,
+ sd_dhcp6_client_callback_t cb,
+ void *userdata);
+int dhcp6_client_get_state(sd_dhcp6_client *client);
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
new file mode 100644
index 0000000..e5b3b13
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include <net/ethernet.h>
+#include <netinet/in.h>
+
+#include "sd-event.h"
+#include "sd-dhcp6-client.h"
+
+#include "dhcp-identifier.h"
+#include "dhcp6-client-internal.h"
+#include "dhcp6-option.h"
+#include "dhcp6-protocol.h"
+#include "ether-addr-util.h"
+#include "hashmap.h"
+#include "macro.h"
+#include "network-common.h"
+#include "ordered-set.h"
+#include "sparse-endian.h"
+#include "time-util.h"
+
+/* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */
+typedef enum DHCP6RequestIA {
+ DHCP6_REQUEST_IA_NA = 1 << 0,
+ DHCP6_REQUEST_IA_TA = 1 << 1, /* currently not used */
+ DHCP6_REQUEST_IA_PD = 1 << 2,
+} DHCP6RequestIA;
+
+struct sd_dhcp6_client {
+ unsigned n_ref;
+
+ int ifindex;
+ char *ifname;
+
+ struct in6_addr local_address;
+ struct hw_addr_data hw_addr;
+ uint16_t arp_type;
+
+ sd_event *event;
+ sd_event_source *receive_message;
+ sd_event_source *timeout_resend;
+ sd_event_source *timeout_expire;
+ sd_event_source *timeout_t1;
+ sd_event_source *timeout_t2;
+ int event_priority;
+ int fd;
+
+ sd_device *dev;
+
+ DHCP6State state;
+ bool information_request;
+ usec_t information_request_time_usec;
+ usec_t information_refresh_time_usec;
+ be32_t transaction_id;
+ usec_t transaction_start;
+ usec_t retransmit_time;
+ uint8_t retransmit_count;
+
+ bool iaid_set;
+ DHCP6IA ia_na;
+ DHCP6IA ia_pd;
+ DHCP6RequestIA request_ia;
+ struct duid duid;
+ size_t duid_len;
+ be16_t *req_opts;
+ size_t n_req_opts;
+ char *fqdn;
+ char *mudurl;
+ char **user_class;
+ char **vendor_class;
+ OrderedHashmap *extra_options;
+ OrderedSet *vendor_options;
+ bool rapid_commit;
+
+ struct sd_dhcp6_lease *lease;
+
+ sd_dhcp6_client_callback_t callback;
+ void *userdata;
+ sd_dhcp6_client_callback_t state_callback;
+ void *state_userdata;
+ bool send_release;
+};
+
+int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address);
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
+ const void *packet, size_t len);
+
+int dhcp6_client_send_message(sd_dhcp6_client *client);
+int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id);
+
+#define log_dhcp6_client_errno(client, error, fmt, ...) \
+ log_interface_prefix_full_errno( \
+ "DHCPv6 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, client, \
+ 0, fmt, ##__VA_ARGS__)
diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h
new file mode 100644
index 0000000..e76a108
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-lease-internal.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include <inttypes.h>
+
+#include "sd-dhcp6-lease.h"
+
+#include "dhcp6-option.h"
+#include "dhcp6-protocol.h"
+#include "macro.h"
+#include "set.h"
+#include "time-util.h"
+
+struct sd_dhcp6_lease {
+ unsigned n_ref;
+
+ uint8_t *clientid;
+ size_t clientid_len;
+ uint8_t *serverid;
+ size_t serverid_len;
+ uint8_t preference;
+ bool rapid_commit;
+ triple_timestamp timestamp;
+ usec_t lifetime_t1;
+ usec_t lifetime_t2;
+ usec_t lifetime_valid;
+ struct in6_addr server_address;
+
+ DHCP6IA *ia_na; /* Identity association non-temporary addresses */
+ DHCP6IA *ia_pd; /* Identity association prefix delegation */
+
+ DHCP6Address *addr_iter;
+ DHCP6Address *prefix_iter;
+
+ struct in6_addr *dns;
+ size_t dns_count;
+ char **domains;
+ struct in6_addr *ntp;
+ size_t ntp_count;
+ char **ntp_fqdn;
+ struct in6_addr *sntp;
+ size_t sntp_count;
+ char *fqdn;
+ char *captive_portal;
+ struct sd_dhcp6_option **sorted_vendor_options;
+ Set *vendor_options;
+};
+
+int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len);
+int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len);
+int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len);
+int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len);
+int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference);
+int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *ret);
+int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease);
+int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *ret);
+
+int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
+int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
+int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
+int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
+int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
+int dhcp6_lease_set_captive_portal(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
+
+int dhcp6_lease_new(sd_dhcp6_lease **ret);
+int dhcp6_lease_new_from_message(
+ sd_dhcp6_client *client,
+ const DHCP6Message *message,
+ size_t len,
+ const triple_timestamp *timestamp,
+ const struct in6_addr *server_address,
+ sd_dhcp6_lease **ret);
+
+#define _FOREACH_DHCP6_ADDRESS(lease, it) \
+ for (int it = sd_dhcp6_lease_address_iterator_reset(lease); \
+ it > 0; \
+ it = sd_dhcp6_lease_address_iterator_next(lease))
+#define FOREACH_DHCP6_ADDRESS(lease) \
+ _FOREACH_DHCP6_ADDRESS(lease, UNIQ_T(i, UNIQ))
+
+#define _FOREACH_DHCP6_PD_PREFIX(lease, it) \
+ for (int it = sd_dhcp6_lease_pd_iterator_reset(lease); \
+ it > 0; \
+ it = sd_dhcp6_lease_pd_iterator_next(lease))
+#define FOREACH_DHCP6_PD_PREFIX(lease) \
+ _FOREACH_DHCP6_PD_PREFIX(lease, UNIQ_T(i, UNIQ))
diff --git a/src/libsystemd-network/dhcp6-network.c b/src/libsystemd-network/dhcp6-network.c
new file mode 100644
index 0000000..a3e4e19
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-network.c
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <linux/if_packet.h>
+
+#include "dhcp6-internal.h"
+#include "dhcp6-protocol.h"
+#include "fd-util.h"
+#include "socket-util.h"
+
+int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) {
+ union sockaddr_union src = {
+ .in6.sin6_family = AF_INET6,
+ .in6.sin6_port = htobe16(DHCP6_PORT_CLIENT),
+ .in6.sin6_scope_id = ifindex,
+ };
+ _cleanup_close_ int s = -EBADF;
+ int r;
+
+ assert(ifindex > 0);
+ assert(local_address);
+
+ src.in6.sin6_addr = *local_address;
+
+ s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_UDP);
+ if (s < 0)
+ return -errno;
+
+ r = setsockopt_int(s, IPPROTO_IPV6, IPV6_V6ONLY, true);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, false);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true);
+ if (r < 0)
+ return r;
+
+ r = bind(s, &src.sa, sizeof(src.in6));
+ if (r < 0)
+ return -errno;
+
+ return TAKE_FD(s);
+}
+
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
+ const void *packet, size_t len) {
+ union sockaddr_union dest = {
+ .in6.sin6_family = AF_INET6,
+ .in6.sin6_port = htobe16(DHCP6_PORT_SERVER),
+ };
+ int r;
+
+ assert(server_address);
+
+ memcpy(&dest.in6.sin6_addr, server_address, sizeof(dest.in6.sin6_addr));
+
+ r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6));
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c
new file mode 100644
index 0000000..83f40f3
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-option.c
@@ -0,0 +1,979 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <netinet/in.h>
+
+#include "sd-dhcp6-client.h"
+
+#include "alloc-util.h"
+#include "dhcp6-internal.h"
+#include "dhcp6-option.h"
+#include "dhcp6-protocol.h"
+#include "dns-domain.h"
+#include "escape.h"
+#include "memory-util.h"
+#include "network-common.h"
+#include "strv.h"
+#include "unaligned.h"
+
+#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))
+
+bool dhcp6_option_can_request(uint16_t option) {
+ /* See Client ORO field in
+ * https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-2 */
+
+ switch (option) {
+ case SD_DHCP6_OPTION_CLIENTID:
+ case SD_DHCP6_OPTION_SERVERID:
+ case SD_DHCP6_OPTION_IA_NA:
+ case SD_DHCP6_OPTION_IA_TA:
+ case SD_DHCP6_OPTION_IAADDR:
+ case SD_DHCP6_OPTION_ORO:
+ case SD_DHCP6_OPTION_PREFERENCE:
+ case SD_DHCP6_OPTION_ELAPSED_TIME:
+ case SD_DHCP6_OPTION_RELAY_MSG:
+ case SD_DHCP6_OPTION_AUTH:
+ case SD_DHCP6_OPTION_UNICAST:
+ case SD_DHCP6_OPTION_STATUS_CODE:
+ case SD_DHCP6_OPTION_RAPID_COMMIT:
+ case SD_DHCP6_OPTION_USER_CLASS:
+ case SD_DHCP6_OPTION_VENDOR_CLASS:
+ return false;
+ case SD_DHCP6_OPTION_VENDOR_OPTS:
+ return true;
+ case SD_DHCP6_OPTION_INTERFACE_ID:
+ case SD_DHCP6_OPTION_RECONF_MSG:
+ case SD_DHCP6_OPTION_RECONF_ACCEPT:
+ return false;
+ case SD_DHCP6_OPTION_SIP_SERVER_DOMAIN_NAME:
+ case SD_DHCP6_OPTION_SIP_SERVER_ADDRESS:
+ case SD_DHCP6_OPTION_DNS_SERVER:
+ case SD_DHCP6_OPTION_DOMAIN:
+ return true;
+ case SD_DHCP6_OPTION_IA_PD:
+ case SD_DHCP6_OPTION_IA_PD_PREFIX:
+ return false;
+ case SD_DHCP6_OPTION_NIS_SERVER:
+ case SD_DHCP6_OPTION_NISP_SERVER:
+ case SD_DHCP6_OPTION_NIS_DOMAIN_NAME:
+ case SD_DHCP6_OPTION_NISP_DOMAIN_NAME:
+ case SD_DHCP6_OPTION_SNTP_SERVER:
+ return true;
+ case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
+ return false; /* This is automatically set when sending INFORMATION_REQUEST message. */
+ case SD_DHCP6_OPTION_BCMCS_SERVER_D:
+ case SD_DHCP6_OPTION_BCMCS_SERVER_A:
+ case SD_DHCP6_OPTION_GEOCONF_CIVIC:
+ return true;
+ case SD_DHCP6_OPTION_REMOTE_ID:
+ case SD_DHCP6_OPTION_SUBSCRIBER_ID:
+ return false;
+ case SD_DHCP6_OPTION_CLIENT_FQDN:
+ case SD_DHCP6_OPTION_PANA_AGENT:
+ case SD_DHCP6_OPTION_POSIX_TIMEZONE:
+ case SD_DHCP6_OPTION_TZDB_TIMEZONE:
+ return true;
+ case SD_DHCP6_OPTION_ERO:
+ case SD_DHCP6_OPTION_LQ_QUERY:
+ case SD_DHCP6_OPTION_CLIENT_DATA:
+ case SD_DHCP6_OPTION_CLT_TIME:
+ case SD_DHCP6_OPTION_LQ_RELAY_DATA:
+ case SD_DHCP6_OPTION_LQ_CLIENT_LINK:
+ return false;
+ case SD_DHCP6_OPTION_MIP6_HNIDF:
+ case SD_DHCP6_OPTION_MIP6_VDINF:
+ case SD_DHCP6_OPTION_V6_LOST:
+ case SD_DHCP6_OPTION_CAPWAP_AC_V6:
+ return true;
+ case SD_DHCP6_OPTION_RELAY_ID:
+ return false;
+ case SD_DHCP6_OPTION_IPV6_ADDRESS_MOS:
+ case SD_DHCP6_OPTION_IPV6_FQDN_MOS:
+ case SD_DHCP6_OPTION_NTP_SERVER:
+ case SD_DHCP6_OPTION_V6_ACCESS_DOMAIN:
+ case SD_DHCP6_OPTION_SIP_UA_CS_LIST:
+ case SD_DHCP6_OPTION_BOOTFILE_URL:
+ case SD_DHCP6_OPTION_BOOTFILE_PARAM:
+ return true;
+ case SD_DHCP6_OPTION_CLIENT_ARCH_TYPE:
+ return false;
+ case SD_DHCP6_OPTION_NII:
+ case SD_DHCP6_OPTION_GEOLOCATION:
+ case SD_DHCP6_OPTION_AFTR_NAME:
+ case SD_DHCP6_OPTION_ERP_LOCAL_DOMAIN_NAME:
+ return true;
+ case SD_DHCP6_OPTION_RSOO:
+ return false;
+ case SD_DHCP6_OPTION_PD_EXCLUDE:
+ return true;
+ case SD_DHCP6_OPTION_VSS:
+ return false;
+ case SD_DHCP6_OPTION_MIP6_IDINF:
+ case SD_DHCP6_OPTION_MIP6_UDINF:
+ case SD_DHCP6_OPTION_MIP6_HNP:
+ case SD_DHCP6_OPTION_MIP6_HAA:
+ case SD_DHCP6_OPTION_MIP6_HAF:
+ case SD_DHCP6_OPTION_RDNSS_SELECTION:
+ case SD_DHCP6_OPTION_KRB_PRINCIPAL_NAME:
+ case SD_DHCP6_OPTION_KRB_REALM_NAME:
+ case SD_DHCP6_OPTION_KRB_DEFAULT_REALM_NAME:
+ case SD_DHCP6_OPTION_KRB_KDC:
+ return true;
+ case SD_DHCP6_OPTION_CLIENT_LINKLAYER_ADDR:
+ case SD_DHCP6_OPTION_LINK_ADDRESS:
+ case SD_DHCP6_OPTION_RADIUS:
+ case SD_DHCP6_OPTION_SOL_MAX_RT: /* Automatically set when sending SOLICIT message. */
+ case SD_DHCP6_OPTION_INF_MAX_RT: /* Automatically set when sending INFORMATION_REQUEST message. */
+ return false;
+ case SD_DHCP6_OPTION_ADDRSEL:
+ case SD_DHCP6_OPTION_ADDRSEL_TABLE:
+ case SD_DHCP6_OPTION_V6_PCP_SERVER:
+ return true;
+ case SD_DHCP6_OPTION_DHCPV4_MSG:
+ return false;
+ case SD_DHCP6_OPTION_DHCP4_O_DHCP6_SERVER:
+ return true;
+ case SD_DHCP6_OPTION_S46_RULE:
+ return false;
+ case SD_DHCP6_OPTION_S46_BR:
+ return true;
+ case SD_DHCP6_OPTION_S46_DMR:
+ case SD_DHCP6_OPTION_S46_V4V6BIND:
+ case SD_DHCP6_OPTION_S46_PORTPARAMS:
+ return false;
+ case SD_DHCP6_OPTION_S46_CONT_MAPE:
+ case SD_DHCP6_OPTION_S46_CONT_MAPT:
+ case SD_DHCP6_OPTION_S46_CONT_LW:
+ case SD_DHCP6_OPTION_4RD:
+ case SD_DHCP6_OPTION_4RD_MAP_RULE:
+ case SD_DHCP6_OPTION_4RD_NON_MAP_RULE:
+ return true;
+ case SD_DHCP6_OPTION_LQ_BASE_TIME:
+ case SD_DHCP6_OPTION_LQ_START_TIME:
+ case SD_DHCP6_OPTION_LQ_END_TIME:
+ return false;
+ case SD_DHCP6_OPTION_CAPTIVE_PORTAL:
+ case SD_DHCP6_OPTION_MPL_PARAMETERS:
+ return true;
+ case SD_DHCP6_OPTION_ANI_ATT:
+ case SD_DHCP6_OPTION_ANI_NETWORK_NAME:
+ case SD_DHCP6_OPTION_ANI_AP_NAME:
+ case SD_DHCP6_OPTION_ANI_AP_BSSID:
+ case SD_DHCP6_OPTION_ANI_OPERATOR_ID:
+ case SD_DHCP6_OPTION_ANI_OPERATOR_REALM:
+ return false;
+ case SD_DHCP6_OPTION_S46_PRIORITY:
+ return true;
+ case SD_DHCP6_OPTION_MUD_URL_V6:
+ return false;
+ case SD_DHCP6_OPTION_V6_PREFIX64:
+ return true;
+ case SD_DHCP6_OPTION_F_BINDING_STATUS:
+ case SD_DHCP6_OPTION_F_CONNECT_FLAGS:
+ case SD_DHCP6_OPTION_F_DNS_REMOVAL_INFO:
+ case SD_DHCP6_OPTION_F_DNS_HOST_NAME:
+ case SD_DHCP6_OPTION_F_DNS_ZONE_NAME:
+ case SD_DHCP6_OPTION_F_DNS_FLAGS:
+ case SD_DHCP6_OPTION_F_EXPIRATION_TIME:
+ case SD_DHCP6_OPTION_F_MAX_UNACKED_BNDUPD:
+ case SD_DHCP6_OPTION_F_MCLT:
+ case SD_DHCP6_OPTION_F_PARTNER_LIFETIME:
+ case SD_DHCP6_OPTION_F_PARTNER_LIFETIME_SENT:
+ case SD_DHCP6_OPTION_F_PARTNER_DOWN_TIME:
+ case SD_DHCP6_OPTION_F_PARTNER_RAW_CLT_TIME:
+ case SD_DHCP6_OPTION_F_PROTOCOL_VERSION:
+ case SD_DHCP6_OPTION_F_KEEPALIVE_TIME:
+ case SD_DHCP6_OPTION_F_RECONFIGURE_DATA:
+ case SD_DHCP6_OPTION_F_RELATIONSHIP_NAME:
+ case SD_DHCP6_OPTION_F_SERVER_FLAGS:
+ case SD_DHCP6_OPTION_F_SERVER_STATE:
+ case SD_DHCP6_OPTION_F_START_TIME_OF_STATE:
+ case SD_DHCP6_OPTION_F_STATE_EXPIRATION_TIME:
+ case SD_DHCP6_OPTION_RELAY_PORT:
+ return false;
+ case SD_DHCP6_OPTION_V6_SZTP_REDIRECT:
+ case SD_DHCP6_OPTION_S46_BIND_IPV6_PREFIX:
+ return true;
+ case SD_DHCP6_OPTION_IA_LL:
+ case SD_DHCP6_OPTION_LLADDR:
+ case SD_DHCP6_OPTION_SLAP_QUAD:
+ return false;
+ case SD_DHCP6_OPTION_V6_DOTS_RI:
+ case SD_DHCP6_OPTION_V6_DOTS_ADDRESS:
+ case SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int option_append_hdr(uint8_t **buf, size_t *offset, uint16_t optcode, size_t optlen) {
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+
+ if (optlen > 0xffff)
+ return -ENOBUFS;
+
+ if (optlen + offsetof(DHCP6Option, data) > SIZE_MAX - *offset)
+ return -ENOBUFS;
+
+ if (!GREEDY_REALLOC(*buf, *offset + optlen + offsetof(DHCP6Option, data)))
+ return -ENOMEM;
+
+ unaligned_write_be16(*buf + *offset + offsetof(DHCP6Option, code), optcode);
+ unaligned_write_be16(*buf + *offset + offsetof(DHCP6Option, len), optlen);
+
+ *offset += offsetof(DHCP6Option, data);
+ return 0;
+}
+
+int dhcp6_option_append(
+ uint8_t **buf,
+ size_t *offset,
+ uint16_t code,
+ size_t optlen,
+ const void *optval) {
+
+ int r;
+
+ assert(optval || optlen == 0);
+
+ r = option_append_hdr(buf, offset, code, optlen);
+ if (r < 0)
+ return r;
+
+ memcpy_safe(*buf + *offset, optval, optlen);
+ *offset += optlen;
+
+ return 0;
+}
+
+int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *offset, OrderedSet *vendor_options) {
+ sd_dhcp6_option *options;
+ int r;
+
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+
+ ORDERED_SET_FOREACH(options, vendor_options) {
+ _cleanup_free_ uint8_t *p = NULL;
+ size_t total;
+
+ total = 4 + 2 + 2 + options->length;
+
+ p = malloc(total);
+ if (!p)
+ return -ENOMEM;
+
+ unaligned_write_be32(p, options->enterprise_identifier);
+ unaligned_write_be16(p + 4, options->option);
+ unaligned_write_be16(p + 6, options->length);
+ memcpy(p + 8, options->data, options->length);
+
+ r = dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_VENDOR_OPTS, total, p);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int option_append_ia_address(uint8_t **buf, size_t *offset, const struct iaaddr *address) {
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+ assert(address);
+
+ /* Do not append T1 and T2. */
+ const struct iaaddr a = {
+ .address = address->address,
+ };
+
+ return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_IAADDR, sizeof(struct iaaddr), &a);
+}
+
+static int option_append_pd_prefix(uint8_t **buf, size_t *offset, const struct iapdprefix *prefix) {
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+ assert(prefix);
+
+ if (prefix->prefixlen == 0)
+ return -EINVAL;
+
+ /* Do not append T1 and T2. */
+ const struct iapdprefix p = {
+ .prefixlen = prefix->prefixlen,
+ .address = prefix->address,
+ };
+
+ return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_IA_PD_PREFIX, sizeof(struct iapdprefix), &p);
+}
+
+int dhcp6_option_append_ia(uint8_t **buf, size_t *offset, const DHCP6IA *ia) {
+ _cleanup_free_ uint8_t *data = NULL;
+ struct ia_header header;
+ size_t len;
+ int r;
+
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+ assert(ia);
+
+ /* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */
+
+ switch (ia->type) {
+ case SD_DHCP6_OPTION_IA_NA:
+ case SD_DHCP6_OPTION_IA_PD:
+ len = sizeof(struct ia_header);
+ header = (struct ia_header) {
+ .id = ia->header.id,
+ };
+ break;
+
+ case SD_DHCP6_OPTION_IA_TA:
+ len = sizeof(header.id); /* IA_TA does not have lifetime. */
+ header = (struct ia_header) {
+ .id = ia->header.id,
+ };
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (!GREEDY_REALLOC(data, len))
+ return -ENOMEM;
+
+ memcpy(data, &header, len);
+
+ LIST_FOREACH(addresses, addr, ia->addresses) {
+ if (ia->type == SD_DHCP6_OPTION_IA_PD)
+ r = option_append_pd_prefix(&data, &len, &addr->iapdprefix);
+ else
+ r = option_append_ia_address(&data, &len, &addr->iaaddr);
+ if (r < 0)
+ return r;
+ }
+
+ return dhcp6_option_append(buf, offset, ia->type, len, data);
+}
+
+int dhcp6_option_append_fqdn(uint8_t **buf, size_t *offset, const char *fqdn) {
+ uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX];
+ int r;
+
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+
+ if (isempty(fqdn))
+ return 0;
+
+ buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
+
+ /* Store domain name after flags field */
+ r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1, false);
+ if (r <= 0)
+ return r;
+
+ /*
+ * According to RFC 4704, chapter 4.2 only add terminating zero-length
+ * label in case a FQDN is provided. Since dns_name_to_wire_format
+ * always adds terminating zero-length label remove if only a hostname
+ * is provided.
+ */
+ if (dns_name_is_single_label(fqdn))
+ r--;
+
+ return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_CLIENT_FQDN, 1 + r, buffer);
+}
+
+int dhcp6_option_append_user_class(uint8_t **buf, size_t *offset, char * const *user_class) {
+ _cleanup_free_ uint8_t *p = NULL;
+ size_t n = 0;
+
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+
+ if (strv_isempty(user_class))
+ return 0;
+
+ STRV_FOREACH(s, user_class) {
+ size_t len = strlen(*s);
+
+ if (len > UINT16_MAX || len == 0)
+ return -EINVAL;
+
+ if (!GREEDY_REALLOC(p, n + len + 2))
+ return -ENOMEM;
+
+ unaligned_write_be16(p + n, len);
+ memcpy(p + n + 2, *s, len);
+ n += len + 2;
+ }
+
+ return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_USER_CLASS, n, p);
+}
+
+int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *offset, char * const *vendor_class) {
+ _cleanup_free_ uint8_t *p = NULL;
+ size_t n = 0;
+
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+
+ if (strv_isempty(vendor_class))
+ return 0;
+
+ if (!GREEDY_REALLOC(p, sizeof(be32_t)))
+ return -ENOMEM;
+
+ /* Enterprise Identifier */
+ unaligned_write_be32(p, SYSTEMD_PEN);
+ n += sizeof(be32_t);
+
+ STRV_FOREACH(s, vendor_class) {
+ size_t len = strlen(*s);
+
+ if (len > UINT16_MAX || len == 0)
+ return -EINVAL;
+
+ if (!GREEDY_REALLOC(p, n + len + 2))
+ return -ENOMEM;
+
+ unaligned_write_be16(p + n, len);
+ memcpy(p + n + 2, *s, len);
+ n += len + 2;
+ }
+
+ return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_VENDOR_CLASS, n, p);
+}
+
+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) {
+
+ size_t len;
+
+ assert(buf);
+ assert(offset);
+ assert(ret_option_code);
+ assert(ret_option_data_len);
+ assert(ret_option_data);
+
+ if (buflen < offsetof(DHCP6Option, data))
+ return -EBADMSG;
+
+ if (*offset > buflen - offsetof(DHCP6Option, data))
+ return -EBADMSG;
+
+ len = unaligned_read_be16(buf + *offset + offsetof(DHCP6Option, len));
+
+ if (len > buflen - offsetof(DHCP6Option, data) - *offset)
+ return -EBADMSG;
+
+ *ret_option_code = unaligned_read_be16(buf + *offset + offsetof(DHCP6Option, code));
+ *ret_option_data_len = len;
+ *ret_option_data = len == 0 ? NULL : buf + *offset + offsetof(DHCP6Option, data);
+ *offset += offsetof(DHCP6Option, data) + len;
+
+ return 0;
+}
+
+int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message) {
+ DHCP6Status status;
+
+ assert(data || data_len == 0);
+
+ if (data_len < sizeof(uint16_t))
+ return -EBADMSG;
+
+ status = unaligned_read_be16(data);
+
+ if (ret_status_message) {
+ _cleanup_free_ char *msg = NULL;
+ const char *s;
+
+ /* 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;
+
+ s = dhcp6_message_status_to_string(status);
+ if (s && !strextend_with_separator(&msg, ": ", s))
+ return -ENOMEM;
+
+ *ret_status_message = TAKE_PTR(msg);
+ }
+
+ return status;
+}
+
+/* parse a string from dhcp option field. *ret must be initialized */
+int dhcp6_option_parse_string(const uint8_t *data, size_t data_len, char **ret) {
+ _cleanup_free_ char *string = NULL;
+ int r;
+
+ assert(data || data_len == 0);
+ assert(ret);
+
+ if (data_len <= 0) {
+ *ret = mfree(*ret);
+ return 0;
+ }
+
+ r = make_cstring((const char *) data, data_len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &string);
+ if (r < 0)
+ return r;
+
+ return free_and_replace(*ret, string);
+}
+
+static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) {
+ int r;
+
+ assert(buf || buflen == 0);
+
+ 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)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received an IA address or PD prefix option with non-zero status%s%s",
+ isempty(msg) ? "." : ": ", strempty(msg));
+ if (r < 0)
+ /* Let's log but ignore the invalid status option. */
+ log_dhcp6_client_errno(client, r,
+ "Received an IA address or PD prefix option with an invalid status sub option, ignoring: %m");
+ break;
+ }
+ default:
+ log_dhcp6_client(client, "Received an unknown sub option %u in IA address or PD prefix, ignoring.", code);
+ }
+ }
+
+ return 0;
+}
+
+static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) {
+ _cleanup_free_ DHCP6Address *a = NULL;
+ usec_t lt_valid, lt_pref;
+ int r;
+
+ assert(ia);
+ assert(data || len == 0);
+
+ if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA))
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received an IA address sub-option in an invalid option, ignoring.");
+
+ if (len < sizeof(struct iaaddr))
+ return -EBADMSG;
+
+ a = new(DHCP6Address, 1);
+ if (!a)
+ return -ENOMEM;
+
+ memcpy(&a->iaaddr, data, sizeof(struct iaaddr));
+
+ lt_valid = be32_sec_to_usec(a->iaaddr.lifetime_valid, /* max_as_infinity = */ true);
+ lt_pref = be32_sec_to_usec(a->iaaddr.lifetime_preferred, /* max_as_infinity = */ true);
+
+ if (lt_valid == 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received an IA address with zero valid lifetime, ignoring.");
+ if (lt_pref > lt_valid)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received an IA address with preferred lifetime %s "
+ "larger than valid lifetime %s, ignoring.",
+ FORMAT_TIMESPAN(lt_pref, USEC_PER_SEC),
+ FORMAT_TIMESPAN(lt_valid, USEC_PER_SEC));
+
+ if (len > sizeof(struct iaaddr)) {
+ r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iaaddr), len - sizeof(struct iaaddr));
+ if (r < 0)
+ return r;
+ }
+
+ LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a));
+ return 0;
+}
+
+static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) {
+ _cleanup_free_ DHCP6Address *a = NULL;
+ usec_t lt_valid, lt_pref;
+ int r;
+
+ assert(ia);
+ assert(data || len == 0);
+
+ if (ia->type != SD_DHCP6_OPTION_IA_PD)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received an PD prefix sub-option in an invalid option, ignoring");
+
+ if (len < sizeof(struct iapdprefix))
+ return -EBADMSG;
+
+ a = new(DHCP6Address, 1);
+ if (!a)
+ return -ENOMEM;
+
+ memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix));
+
+ lt_valid = be32_sec_to_usec(a->iapdprefix.lifetime_valid, /* max_as_infinity = */ true);
+ lt_pref = be32_sec_to_usec(a->iapdprefix.lifetime_preferred, /* max_as_infinity = */ true);
+
+ if (lt_valid == 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received a PD prefix with zero valid lifetime, ignoring.");
+ if (lt_pref > lt_valid)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received a PD prefix with preferred lifetime %s "
+ "larger than valid lifetime %s, ignoring.",
+ FORMAT_TIMESPAN(lt_pref, USEC_PER_SEC),
+ FORMAT_TIMESPAN(lt_valid, USEC_PER_SEC));
+
+ if (len > sizeof(struct iapdprefix)) {
+ r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iapdprefix), len - sizeof(struct iapdprefix));
+ if (r < 0)
+ return r;
+ }
+
+ LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a));
+ return 0;
+}
+
+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_ia_freep) DHCP6IA *ia = NULL;
+ usec_t lt_t1, lt_t2;
+ size_t header_len;
+ int r;
+
+ assert(IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA, SD_DHCP6_OPTION_IA_PD));
+ assert(option_data || option_data_len == 0);
+ assert(ret);
+
+ /* 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 (option_code) {
+ case SD_DHCP6_OPTION_IA_NA:
+ case SD_DHCP6_OPTION_IA_PD:
+ header_len = sizeof(struct ia_header);
+ break;
+
+ case SD_DHCP6_OPTION_IA_TA:
+ header_len = sizeof(be32_t); /* IA_TA does not have lifetime. */
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (option_data_len < header_len)
+ return -EBADMSG;
+
+ ia = new(DHCP6IA, 1);
+ if (!ia)
+ return -ENOMEM;
+
+ *ia = (DHCP6IA) {
+ .type = option_code,
+ };
+ memcpy(&ia->header, option_data, header_len);
+
+ /* According to RFC8415, IAs which do not match the client's IAID should be ignored,
+ * but not necessary to ignore or refuse the whole message. */
+ if (ia->header.id != iaid)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENOANO),
+ "Received an IA option with a different IAID "
+ "from the one chosen by the client, ignoring.");
+
+ /* It is not necessary to check if the lifetime_t2 is zero here, as in that case it will be updated later. */
+ lt_t1 = be32_sec_to_usec(ia->header.lifetime_t1, /* max_as_infinity = */ true);
+ lt_t2 = be32_sec_to_usec(ia->header.lifetime_t2, /* max_as_infinity = */ true);
+
+ if (lt_t1 > lt_t2)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received an IA option with T1 %s > T2 %s, ignoring.",
+ FORMAT_TIMESPAN(lt_t1, USEC_PER_SEC),
+ FORMAT_TIMESPAN(lt_t2, USEC_PER_SEC));
+ if (lt_t1 == 0 && lt_t2 > 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received an IA option with zero T1 and non-zero T2 (%s), ignoring.",
+ FORMAT_TIMESPAN(lt_t2, USEC_PER_SEC));
+
+ for (size_t offset = header_len; offset < option_data_len;) {
+ const uint8_t *subdata;
+ size_t subdata_len;
+ uint16_t subopt;
+
+ r = dhcp6_option_parse(option_data, option_data_len, &offset, &subopt, &subdata_len, &subdata);
+ if (r < 0)
+ return r;
+
+ switch (subopt) {
+ case SD_DHCP6_OPTION_IAADDR: {
+ r = dhcp6_option_parse_ia_address(client, ia, subdata, subdata_len);
+ if (r == -ENOMEM)
+ return r;
+
+ /* Ignore non-critical errors in the sub-option. */
+ break;
+ }
+ case SD_DHCP6_OPTION_IA_PD_PREFIX: {
+ r = dhcp6_option_parse_ia_pdprefix(client, ia, subdata, subdata_len);
+ if (r == -ENOMEM)
+ return r;
+
+ /* Ignore non-critical errors in the sub-option. */
+ break;
+ }
+ case SD_DHCP6_OPTION_STATUS_CODE: {
+ _cleanup_free_ char *msg = NULL;
+
+ r = dhcp6_option_parse_status(subdata, subdata_len, &msg);
+ if (r == -ENOMEM)
+ return r;
+ if (r > 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received an IA option with non-zero status%s%s",
+ isempty(msg) ? "." : ": ", strempty(msg));
+ if (r < 0)
+ log_dhcp6_client_errno(client, r,
+ "Received an IA option with an invalid status sub option, ignoring: %m");
+ break;
+ }
+ default:
+ log_dhcp6_client(client, "Received an IA option with an unknown sub-option %u, ignoring", subopt);
+ }
+ }
+
+ if (!ia->addresses)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENODATA),
+ "Received an IA option without valid IA addresses or PD prefixes, ignoring.");
+
+ *ret = TAKE_PTR(ia);
+ return 0;
+}
+
+int dhcp6_option_parse_addresses(
+ const uint8_t *optval,
+ size_t optlen,
+ struct in6_addr **addrs,
+ size_t *count) {
+
+ assert(optval || optlen == 0);
+ assert(addrs);
+ assert(count);
+
+ if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
+ return -EBADMSG;
+
+ if (!GREEDY_REALLOC(*addrs, *count + optlen / sizeof(struct in6_addr)))
+ return -ENOMEM;
+
+ memcpy(*addrs + *count, optval, optlen);
+ *count += optlen / sizeof(struct in6_addr);
+
+ return 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(len);
+ assert(*data || *len == 0);
+ assert(ret);
+
+ optval = *data;
+ optlen = *len;
+
+ if (optlen <= 1)
+ return -ENODATA;
+
+ for (;;) {
+ const char *label;
+ uint8_t c;
+
+ if (optlen == 0)
+ break;
+
+ c = *optval;
+ optval++;
+ optlen--;
+
+ if (c == 0)
+ /* End label */
+ break;
+ if (c > 63)
+ return -EBADMSG;
+ if (c > optlen)
+ return -EMSGSIZE;
+
+ /* Literal label */
+ label = (const char*) optval;
+ optval += c;
+ optlen -= c;
+
+ if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX))
+ return -ENOMEM;
+
+ if (n != 0)
+ domain[n++] = '.';
+
+ r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ n += r;
+ }
+
+ if (n > 0) {
+ if (!GREEDY_REALLOC(domain, n + 1))
+ return -ENOMEM;
+
+ domain[n] = '\0';
+ }
+
+ *ret = TAKE_PTR(domain);
+ *data = optval;
+ *len = optlen;
+
+ return n;
+}
+
+int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret) {
+ _cleanup_free_ char *domain = NULL;
+ int r;
+
+ assert(optval || optlen == 0);
+ assert(ret);
+
+ r = parse_domain(&optval, &optlen, &domain);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENODATA;
+ if (optlen != 0)
+ return -EINVAL;
+
+ *ret = TAKE_PTR(domain);
+ return 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 || optlen == 0);
+ assert(ret);
+
+ if (optlen <= 1)
+ return -ENODATA;
+ if (optval[optlen - 1] != '\0')
+ return -EINVAL;
+
+ while (optlen > 0) {
+ _cleanup_free_ char *name = NULL;
+
+ r = parse_domain(&optval, &optlen, &name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = strv_consume(&names, TAKE_PTR(name));
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(names);
+ return 0;
+}
+
+static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) {
+ if (!i)
+ return NULL;
+
+ free(i->data);
+ return mfree(i);
+}
+
+int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) {
+ assert_return(ret, -EINVAL);
+ assert_return(length == 0 || data, -EINVAL);
+
+ _cleanup_free_ void *q = memdup(data, length);
+ if (!q)
+ return -ENOMEM;
+
+ sd_dhcp6_option *p = new(sd_dhcp6_option, 1);
+ if (!p)
+ return -ENOMEM;
+
+ *p = (sd_dhcp6_option) {
+ .n_ref = 1,
+ .option = option,
+ .enterprise_identifier = enterprise_identifier,
+ .length = length,
+ .data = TAKE_PTR(q),
+ };
+
+ *ret = p;
+ return 0;
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ dhcp6_option_hash_ops,
+ void,
+ trivial_hash_func,
+ trivial_compare_func,
+ sd_dhcp6_option,
+ sd_dhcp6_option_unref);
diff --git a/src/libsystemd-network/dhcp6-option.h b/src/libsystemd-network/dhcp6-option.h
new file mode 100644
index 0000000..614b4f8
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-option.h
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-dhcp6-client.h"
+
+#include "hash-funcs.h"
+#include "list.h"
+#include "macro.h"
+#include "ordered-set.h"
+#include "sparse-endian.h"
+
+typedef struct sd_dhcp6_option {
+ unsigned n_ref;
+
+ uint32_t enterprise_identifier;
+ uint16_t option;
+ void *data;
+ size_t length;
+} sd_dhcp6_option;
+
+extern const struct hash_ops dhcp6_option_hash_ops;
+
+/* Common option header */
+typedef struct DHCP6Option {
+ be16_t code;
+ be16_t len;
+ uint8_t data[];
+} _packed_ DHCP6Option;
+
+/* Address option */
+struct iaaddr {
+ struct in6_addr address;
+ be32_t lifetime_preferred;
+ be32_t lifetime_valid;
+} _packed_;
+
+/* Prefix Delegation Prefix option */
+struct iapdprefix {
+ be32_t lifetime_preferred;
+ be32_t lifetime_valid;
+ uint8_t prefixlen;
+ struct in6_addr address;
+} _packed_;
+
+typedef struct DHCP6Address DHCP6Address;
+
+struct DHCP6Address {
+ LIST_FIELDS(DHCP6Address, addresses);
+
+ union {
+ struct iaaddr iaaddr;
+ struct iapdprefix iapdprefix;
+ };
+};
+
+struct ia_header {
+ be32_t id;
+ be32_t lifetime_t1;
+ be32_t lifetime_t2;
+} _packed_;
+
+typedef struct DHCP6IA {
+ uint16_t type;
+ struct ia_header header;
+
+ LIST_HEAD(DHCP6Address, addresses);
+} DHCP6IA;
+
+void dhcp6_ia_clear_addresses(DHCP6IA *ia);
+DHCP6IA *dhcp6_ia_free(DHCP6IA *ia);
+DEFINE_TRIVIAL_CLEANUP_FUNC(DHCP6IA*, dhcp6_ia_free);
+
+bool dhcp6_option_can_request(uint16_t option);
+
+int dhcp6_option_append(uint8_t **buf, size_t *offset, uint16_t code,
+ size_t optlen, const void *optval);
+int dhcp6_option_append_ia(uint8_t **buf, size_t *offset, const DHCP6IA *ia);
+int dhcp6_option_append_fqdn(uint8_t **buf, size_t *offset, const char *fqdn);
+int dhcp6_option_append_user_class(uint8_t **buf, size_t *offset, char * const *user_class);
+int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *offset, char * const *vendor_class);
+int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *offset, OrderedSet *vendor_options);
+
+int dhcp6_option_parse(
+ const uint8_t *buf,
+ size_t buflen,
+ size_t *offset,
+ uint16_t *ret_option_code,
+ size_t *ret_option_data_len,
+ const uint8_t **ret_option_data);
+int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message);
+int dhcp6_option_parse_string(const uint8_t *data, size_t data_len, char **ret);
+int dhcp6_option_parse_ia(
+ sd_dhcp6_client *client,
+ be32_t iaid,
+ uint16_t option_code,
+ size_t option_data_len,
+ const uint8_t *option_data,
+ DHCP6IA **ret);
+int dhcp6_option_parse_addresses(
+ const uint8_t *optval,
+ size_t optlen,
+ struct in6_addr **addrs,
+ size_t *count);
+int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, char ***ret);
+int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret);
diff --git a/src/libsystemd-network/dhcp6-protocol.c b/src/libsystemd-network/dhcp6-protocol.c
new file mode 100644
index 0000000..be0f651
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-protocol.c
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "dhcp6-protocol.h"
+#include "string-table.h"
+
+static const char * const dhcp6_state_table[_DHCP6_STATE_MAX] = {
+ [DHCP6_STATE_STOPPED] = "stopped",
+ [DHCP6_STATE_INFORMATION_REQUEST] = "information-request",
+ [DHCP6_STATE_SOLICITATION] = "solicitation",
+ [DHCP6_STATE_REQUEST] = "request",
+ [DHCP6_STATE_BOUND] = "bound",
+ [DHCP6_STATE_RENEW] = "renew",
+ [DHCP6_STATE_REBIND] = "rebind",
+ [DHCP6_STATE_STOPPING] = "stopping",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_state, DHCP6State);
+
+static const char * const dhcp6_message_type_table[_DHCP6_MESSAGE_TYPE_MAX] = {
+ [DHCP6_MESSAGE_SOLICIT] = "Solicit",
+ [DHCP6_MESSAGE_ADVERTISE] = "Advertise",
+ [DHCP6_MESSAGE_REQUEST] = "Request",
+ [DHCP6_MESSAGE_CONFIRM] = "Confirm",
+ [DHCP6_MESSAGE_RENEW] = "Renew",
+ [DHCP6_MESSAGE_REBIND] = "Rebind",
+ [DHCP6_MESSAGE_REPLY] = "Reply",
+ [DHCP6_MESSAGE_RELEASE] = "Release",
+ [DHCP6_MESSAGE_DECLINE] = "Decline",
+ [DHCP6_MESSAGE_RECONFIGURE] = "Reconfigure",
+ [DHCP6_MESSAGE_INFORMATION_REQUEST] = "Information Request",
+ [DHCP6_MESSAGE_RELAY_FORWARD] = "Relay Forward",
+ [DHCP6_MESSAGE_RELAY_REPLY] = "Relay Reply",
+ [DHCP6_MESSAGE_LEASE_QUERY] = "Lease Query",
+ [DHCP6_MESSAGE_LEASE_QUERY_REPLY] = "Lease Query Reply",
+ [DHCP6_MESSAGE_LEASE_QUERY_DONE] = "Lease Query Done",
+ [DHCP6_MESSAGE_LEASE_QUERY_DATA] = "Lease Query Data",
+ [DHCP6_MESSAGE_RECONFIGURE_REQUEST] = "Reconfigure Request",
+ [DHCP6_MESSAGE_RECONFIGURE_REPLY] = "Reconfigure Reply",
+ [DHCP6_MESSAGE_DHCPV4_QUERY] = "DHCPv4 Query",
+ [DHCP6_MESSAGE_DHCPV4_RESPONSE] = "DHCPv4 Response",
+ [DHCP6_MESSAGE_ACTIVE_LEASE_QUERY] = "Active Lease Query",
+ [DHCP6_MESSAGE_START_TLS] = "Start TLS",
+ [DHCP6_MESSAGE_BINDING_UPDATE] = "Binding Update",
+ [DHCP6_MESSAGE_BINDING_REPLY] = "Binding Reply",
+ [DHCP6_MESSAGE_POOL_REQUEST] = "Pool Request",
+ [DHCP6_MESSAGE_POOL_RESPONSE] = "Pool Response",
+ [DHCP6_MESSAGE_UPDATE_REQUEST] = "Update Request",
+ [DHCP6_MESSAGE_UPDATE_REQUEST_ALL] = "Update Request All",
+ [DHCP6_MESSAGE_UPDATE_DONE] = "Update Done",
+ [DHCP6_MESSAGE_CONNECT] = "Connect",
+ [DHCP6_MESSAGE_CONNECT_REPLY] = "Connect Reply",
+ [DHCP6_MESSAGE_DISCONNECT] = "Disconnect",
+ [DHCP6_MESSAGE_STATE] = "State",
+ [DHCP6_MESSAGE_CONTACT] = "Contact",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, DHCP6MessageType);
+
+static const char * const dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
+ [DHCP6_STATUS_SUCCESS] = "Success",
+ [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure",
+ [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available",
+ [DHCP6_STATUS_NO_BINDING] = "Binding unavailable",
+ [DHCP6_STATUS_NOT_ON_LINK] = "Not on link",
+ [DHCP6_STATUS_USE_MULTICAST] = "Use multicast",
+ [DHCP6_STATUS_NO_PREFIX_AVAIL] = "No prefix available",
+ [DHCP6_STATUS_UNKNOWN_QUERY_TYPE] = "Unknown query type",
+ [DHCP6_STATUS_MALFORMED_QUERY] = "Malformed query",
+ [DHCP6_STATUS_NOT_CONFIGURED] = "Not configured",
+ [DHCP6_STATUS_NOT_ALLOWED] = "Not allowed",
+ [DHCP6_STATUS_QUERY_TERMINATED] = "Query terminated",
+ [DHCP6_STATUS_DATA_MISSING] = "Data missing",
+ [DHCP6_STATUS_CATCHUP_COMPLETE] = "Catch up complete",
+ [DHCP6_STATUS_NOT_SUPPORTED] = "Not supported",
+ [DHCP6_STATUS_TLS_CONNECTION_REFUSED] = "TLS connection refused",
+ [DHCP6_STATUS_ADDRESS_IN_USE] = "Address in use",
+ [DHCP6_STATUS_CONFIGURATION_CONFLICT] = "Configuration conflict",
+ [DHCP6_STATUS_MISSING_BINDING_INFORMATION] = "Missing binding information",
+ [DHCP6_STATUS_OUTDATED_BINDING_INFORMATION] = "Outdated binding information",
+ [DHCP6_STATUS_SERVER_SHUTTING_DOWN] = "Server shutting down",
+ [DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED] = "DNS update not supported",
+ [DHCP6_STATUS_EXCESSIVE_TIME_SKEW] = "Excessive time skew",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, DHCP6Status);
+
+int dhcp6_message_status_to_errno(DHCP6Status s) {
+ switch (s) {
+ case DHCP6_STATUS_SUCCESS:
+ return 0;
+ case DHCP6_STATUS_NO_BINDING:
+ return -EADDRNOTAVAIL;
+ default:
+ return -EINVAL;
+ }
+}
diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h
new file mode 100644
index 0000000..c70f932
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-protocol.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+
+#include "macro.h"
+#include "sparse-endian.h"
+
+struct DHCP6Message {
+ union {
+ struct {
+ uint8_t type;
+ uint8_t _pad[3];
+ } _packed_;
+ be32_t transaction_id;
+ };
+ uint8_t options[];
+} _packed_;
+
+typedef struct DHCP6Message DHCP6Message;
+
+#define DHCP6_MIN_OPTIONS_SIZE \
+ 1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr)
+
+#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } }
+
+enum {
+ DHCP6_PORT_SERVER = 547,
+ DHCP6_PORT_CLIENT = 546,
+};
+
+#define DHCP6_INF_TIMEOUT (1 * USEC_PER_SEC)
+#define DHCP6_INF_MAX_RT (120 * USEC_PER_SEC)
+#define DHCP6_SOL_MAX_DELAY (1 * USEC_PER_SEC)
+#define DHCP6_SOL_TIMEOUT (1 * USEC_PER_SEC)
+#define DHCP6_SOL_MAX_RT (120 * USEC_PER_SEC)
+#define DHCP6_REQ_TIMEOUT (1 * USEC_PER_SEC)
+#define DHCP6_REQ_MAX_RT (120 * USEC_PER_SEC)
+#define DHCP6_REQ_MAX_RC 10
+#define DHCP6_REN_TIMEOUT (10 * USEC_PER_SEC)
+#define DHCP6_REN_MAX_RT (600 * USEC_PER_SEC)
+#define DHCP6_REB_TIMEOUT (10 * USEC_PER_SEC)
+#define DHCP6_REB_MAX_RT (600 * USEC_PER_SEC)
+
+typedef enum DHCP6State {
+ DHCP6_STATE_STOPPED,
+ DHCP6_STATE_INFORMATION_REQUEST,
+ DHCP6_STATE_SOLICITATION,
+ DHCP6_STATE_REQUEST,
+ DHCP6_STATE_BOUND,
+ DHCP6_STATE_RENEW,
+ DHCP6_STATE_REBIND,
+ DHCP6_STATE_STOPPING,
+ _DHCP6_STATE_MAX,
+ _DHCP6_STATE_INVALID = -EINVAL,
+} DHCP6State;
+
+/* https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-1 */
+typedef enum DHCP6MessageType {
+ DHCP6_MESSAGE_SOLICIT = 1, /* RFC 8415 */
+ DHCP6_MESSAGE_ADVERTISE = 2, /* RFC 8415 */
+ DHCP6_MESSAGE_REQUEST = 3, /* RFC 8415 */
+ DHCP6_MESSAGE_CONFIRM = 4, /* RFC 8415 */
+ DHCP6_MESSAGE_RENEW = 5, /* RFC 8415 */
+ DHCP6_MESSAGE_REBIND = 6, /* RFC 8415 */
+ DHCP6_MESSAGE_REPLY = 7, /* RFC 8415 */
+ DHCP6_MESSAGE_RELEASE = 8, /* RFC 8415 */
+ DHCP6_MESSAGE_DECLINE = 9, /* RFC 8415 */
+ DHCP6_MESSAGE_RECONFIGURE = 10, /* RFC 8415 */
+ DHCP6_MESSAGE_INFORMATION_REQUEST = 11, /* RFC 8415 */
+ DHCP6_MESSAGE_RELAY_FORWARD = 12, /* RFC 8415 */
+ DHCP6_MESSAGE_RELAY_REPLY = 13, /* RFC 8415 */
+ DHCP6_MESSAGE_LEASE_QUERY = 14, /* RFC 5007 */
+ DHCP6_MESSAGE_LEASE_QUERY_REPLY = 15, /* RFC 5007 */
+ DHCP6_MESSAGE_LEASE_QUERY_DONE = 16, /* RFC 5460 */
+ DHCP6_MESSAGE_LEASE_QUERY_DATA = 17, /* RFC 5460 */
+ DHCP6_MESSAGE_RECONFIGURE_REQUEST = 18, /* RFC 6977 */
+ DHCP6_MESSAGE_RECONFIGURE_REPLY = 19, /* RFC 6977 */
+ DHCP6_MESSAGE_DHCPV4_QUERY = 20, /* RFC 7341 */
+ DHCP6_MESSAGE_DHCPV4_RESPONSE = 21, /* RFC 7341 */
+ DHCP6_MESSAGE_ACTIVE_LEASE_QUERY = 22, /* RFC 7653 */
+ DHCP6_MESSAGE_START_TLS = 23, /* RFC 7653 */
+ DHCP6_MESSAGE_BINDING_UPDATE = 24, /* RFC 8156 */
+ DHCP6_MESSAGE_BINDING_REPLY = 25, /* RFC 8156 */
+ DHCP6_MESSAGE_POOL_REQUEST = 26, /* RFC 8156 */
+ DHCP6_MESSAGE_POOL_RESPONSE = 27, /* RFC 8156 */
+ DHCP6_MESSAGE_UPDATE_REQUEST = 28, /* RFC 8156 */
+ DHCP6_MESSAGE_UPDATE_REQUEST_ALL = 29, /* RFC 8156 */
+ DHCP6_MESSAGE_UPDATE_DONE = 30, /* RFC 8156 */
+ DHCP6_MESSAGE_CONNECT = 31, /* RFC 8156 */
+ DHCP6_MESSAGE_CONNECT_REPLY = 32, /* RFC 8156 */
+ DHCP6_MESSAGE_DISCONNECT = 33, /* RFC 8156 */
+ DHCP6_MESSAGE_STATE = 34, /* RFC 8156 */
+ DHCP6_MESSAGE_CONTACT = 35, /* RFC 8156 */
+ _DHCP6_MESSAGE_TYPE_MAX,
+ _DHCP6_MESSAGE_TYPE_INVALID = -EINVAL,
+} DHCP6MessageType;
+
+typedef enum DHCP6NTPSubOption {
+ DHCP6_NTP_SUBOPTION_SRV_ADDR = 1,
+ DHCP6_NTP_SUBOPTION_MC_ADDR = 2,
+ DHCP6_NTP_SUBOPTION_SRV_FQDN = 3,
+ _DHCP6_NTP_SUBOPTION_MAX,
+ _DHCP6_NTP_SUBOPTION_INVALID = -EINVAL,
+} DHCP6NTPSubOption;
+
+/*
+ * RFC 8415, RFC 5007 and RFC 7653 status codes:
+ * https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-5
+ */
+typedef enum DHCP6Status {
+ DHCP6_STATUS_SUCCESS = 0,
+ DHCP6_STATUS_UNSPEC_FAIL = 1,
+ DHCP6_STATUS_NO_ADDRS_AVAIL = 2,
+ DHCP6_STATUS_NO_BINDING = 3,
+ DHCP6_STATUS_NOT_ON_LINK = 4,
+ DHCP6_STATUS_USE_MULTICAST = 5,
+ DHCP6_STATUS_NO_PREFIX_AVAIL = 6,
+ DHCP6_STATUS_UNKNOWN_QUERY_TYPE = 7,
+ DHCP6_STATUS_MALFORMED_QUERY = 8,
+ DHCP6_STATUS_NOT_CONFIGURED = 9,
+ DHCP6_STATUS_NOT_ALLOWED = 10,
+ DHCP6_STATUS_QUERY_TERMINATED = 11,
+ DHCP6_STATUS_DATA_MISSING = 12,
+ DHCP6_STATUS_CATCHUP_COMPLETE = 13,
+ DHCP6_STATUS_NOT_SUPPORTED = 14,
+ DHCP6_STATUS_TLS_CONNECTION_REFUSED = 15,
+ DHCP6_STATUS_ADDRESS_IN_USE = 16,
+ DHCP6_STATUS_CONFIGURATION_CONFLICT = 17,
+ DHCP6_STATUS_MISSING_BINDING_INFORMATION = 18,
+ DHCP6_STATUS_OUTDATED_BINDING_INFORMATION = 19,
+ DHCP6_STATUS_SERVER_SHUTTING_DOWN = 20,
+ DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED = 21,
+ DHCP6_STATUS_EXCESSIVE_TIME_SKEW = 22,
+ _DHCP6_STATUS_MAX,
+ _DHCP6_STATUS_INVALID = -EINVAL,
+} DHCP6Status;
+
+typedef enum DHCP6FQDNFlag {
+ DHCP6_FQDN_FLAG_S = 1 << 0,
+ DHCP6_FQDN_FLAG_O = 1 << 1,
+ DHCP6_FQDN_FLAG_N = 1 << 2,
+} DHCP6FQDNFlag;
+
+const char *dhcp6_state_to_string(DHCP6State s) _const_;
+const char *dhcp6_message_type_to_string(DHCP6MessageType s) _const_;
+DHCP6MessageType dhcp6_message_type_from_string(const char *s) _pure_;
+const char *dhcp6_message_status_to_string(DHCP6Status s) _const_;
+DHCP6Status dhcp6_message_status_from_string(const char *s) _pure_;
+int dhcp6_message_status_to_errno(DHCP6Status s);
diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c
new file mode 100644
index 0000000..384972f
--- /dev/null
+++ b/src/libsystemd-network/fuzz-dhcp-client.c
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "sd-dhcp-client.c"
+
+#include "alloc-util.h"
+#include "dhcp-network.h"
+#include "fuzz.h"
+
+int dhcp_network_bind_raw_socket(
+ int ifindex,
+ union sockaddr_union *link,
+ uint32_t id,
+ const struct hw_addr_data *hw_addr,
+ const struct hw_addr_data *bcast_addr,
+ uint16_t arp_type,
+ uint16_t port,
+ bool so_priority_set,
+ int so_priority) {
+
+ int fd;
+ fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ return -errno;
+
+ return fd;
+}
+
+int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len) {
+ return len;
+}
+
+int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) {
+ int fd;
+
+ fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ return -errno;
+
+ return fd;
+}
+
+int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const void *packet, size_t len) {
+ return len;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ uint8_t mac_addr[] = {'A', 'B', 'C', '1', '2', '3'};
+ uint8_t bcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+ _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ int res, r;
+
+ assert_se(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", 1) >= 0);
+
+ fuzz_setup_logging();
+
+ r = sd_dhcp_client_new(&client, false);
+ assert_se(r >= 0);
+ assert_se(client);
+
+ assert_se(sd_event_new(&e) >= 0);
+
+ r = sd_dhcp_client_attach_event(client, e, 0);
+ assert_se(r >= 0);
+
+ assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0);
+ assert_se(sd_dhcp_client_set_mac(client, mac_addr, bcast_addr, ETH_ALEN, ARPHRD_ETHER) >= 0);
+
+ res = sd_dhcp_client_start(client);
+ assert_se(IN_SET(res, 0, -EINPROGRESS));
+ client->xid = 2;
+ client->state = DHCP_STATE_SELECTING;
+
+ (void) client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size, NULL);
+
+ assert_se(sd_dhcp_client_stop(client) >= 0);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/fuzz-dhcp-server-relay.c b/src/libsystemd-network/fuzz-dhcp-server-relay.c
new file mode 100644
index 0000000..5520003
--- /dev/null
+++ b/src/libsystemd-network/fuzz-dhcp-server-relay.c
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "sd-dhcp-server.c"
+
+#include "fuzz.h"
+
+ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) {
+ return len;
+}
+
+ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) {
+ return 0;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
+ struct in_addr address = {.s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))};
+ union in_addr_union relay_address;
+ _cleanup_free_ uint8_t *message = NULL;
+
+ if (size < sizeof(DHCPMessage))
+ return 0;
+
+ fuzz_setup_logging();
+
+ assert_se(sd_dhcp_server_new(&server, 1) >= 0);
+ assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0);
+ assert_se(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0) >= 0);
+ assert_se(in_addr_from_string(AF_INET, "192.168.5.1", &relay_address) >= 0);
+ assert_se(sd_dhcp_server_set_relay_target(server, &relay_address.in) >= 0);
+ assert_se(sd_dhcp_server_set_bind_to_interface(server, false) >= 0);
+ assert_se(sd_dhcp_server_set_relay_agent_information(server, "string:sample_circuit_id", "string:sample_remote_id") >= 0);
+
+ size_t buflen = size;
+ buflen += relay_agent_information_length(server->agent_circuit_id, server->agent_remote_id) + 2;
+ assert_se(message = malloc(buflen));
+ memcpy(message, data, size);
+
+ server->fd = open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY);
+ assert_se(server->fd >= 0);
+
+ (void) dhcp_server_relay_message(server, (DHCPMessage *) message, size - sizeof(DHCPMessage), buflen);
+ return 0;
+}
diff --git a/src/libsystemd-network/fuzz-dhcp-server.c b/src/libsystemd-network/fuzz-dhcp-server.c
new file mode 100644
index 0000000..fddb3a5
--- /dev/null
+++ b/src/libsystemd-network/fuzz-dhcp-server.c
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "sd-dhcp-server.c"
+
+#include "fuzz.h"
+
+/* stub out network so that the server doesn't send */
+ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) {
+ return len;
+}
+
+ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) {
+ return 0;
+}
+
+static int add_lease(sd_dhcp_server *server, const struct in_addr *server_address, uint8_t i) {
+ _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL;
+ int r;
+
+ assert(server);
+
+ lease = new(DHCPLease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ *lease = (DHCPLease) {
+ .address = htobe32(UINT32_C(10) << 24 | i),
+ .chaddr = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+ .expiration = UINT64_MAX,
+ .gateway = server_address->s_addr,
+ .hlen = ETH_ALEN,
+ .htype = ARPHRD_ETHER,
+
+ .client_id.length = 2,
+ };
+
+ lease->client_id.data = new(uint8_t, lease->client_id.length);
+ if (!lease->client_id.data)
+ return -ENOMEM;
+
+ lease->client_id.data[0] = 2;
+ lease->client_id.data[1] = i;
+
+ lease->server = server; /* This must be set just before hashmap_put(). */
+
+ r = hashmap_ensure_put(&server->bound_leases_by_client_id, &dhcp_lease_hash_ops, &lease->client_id, lease);
+ if (r < 0)
+ return r;
+
+ r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(lease);
+ return 0;
+}
+
+static int add_static_lease(sd_dhcp_server *server, uint8_t i) {
+ uint8_t id[2] = { 2, i };
+
+ assert(server);
+
+ return sd_dhcp_server_set_static_lease(
+ server,
+ &(struct in_addr) { .s_addr = htobe32(UINT32_C(10) << 24 | i)},
+ id, ELEMENTSOF(id));
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
+ struct in_addr address = { .s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))};
+ _cleanup_free_ uint8_t *duped = NULL;
+
+ if (size < sizeof(DHCPMessage))
+ return 0;
+
+ fuzz_setup_logging();
+
+ assert_se(duped = memdup(data, size));
+
+ assert_se(sd_dhcp_server_new(&server, 1) >= 0);
+ assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0);
+ server->fd = open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY);
+ assert_se(server->fd >= 0);
+ assert_se(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0) >= 0);
+
+ /* add leases to the pool to expose additional code paths */
+ assert_se(add_lease(server, &address, 2) >= 0);
+ assert_se(add_lease(server, &address, 3) >= 0);
+
+ /* add static leases */
+ assert_se(add_static_lease(server, 3) >= 0);
+ assert_se(add_static_lease(server, 4) >= 0);
+
+ (void) dhcp_server_handle_message(server, (DHCPMessage*) duped, size, NULL);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/fuzz-dhcp6-client.c b/src/libsystemd-network/fuzz-dhcp6-client.c
new file mode 100644
index 0000000..2d42844
--- /dev/null
+++ b/src/libsystemd-network/fuzz-dhcp6-client.c
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+
+#include "sd-dhcp6-client.h"
+#include "sd-event.h"
+
+#include "dhcp6-internal.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "fuzz.h"
+
+static int test_dhcp_fd[2] = EBADF_PAIR;
+
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, const void *packet, size_t len) {
+ return len;
+}
+
+int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) {
+ assert_se(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_dhcp_fd) >= 0);
+ return TAKE_FD(test_dhcp_fd[0]);
+}
+
+static void fuzz_client(sd_dhcp6_client *client, const uint8_t *data, size_t size, DHCP6State state) {
+ assert_se(sd_dhcp6_client_set_information_request(client, state == DHCP6_STATE_INFORMATION_REQUEST) >= 0);
+ assert_se(sd_dhcp6_client_start(client) >= 0);
+
+ client->state = state;
+
+ if (size >= sizeof(DHCP6Message))
+ assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message *) data)->transaction_id) == 0);
+
+ /* These states does not require lease to send message. */
+ if (IN_SET(client->state, DHCP6_STATE_INFORMATION_REQUEST, DHCP6_STATE_SOLICITATION))
+ assert_se(dhcp6_client_send_message(client) >= 0);
+
+ assert_se(write(test_dhcp_fd[1], data, size) == (ssize_t) size);
+
+ assert_se(sd_event_run(sd_dhcp6_client_get_event(client), UINT64_MAX) > 0);
+
+ /* Check the state transition. */
+ if (client->state != state)
+ switch (state) {
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ assert_se(client->state == DHCP6_STATE_STOPPED);
+ break;
+ case DHCP6_STATE_SOLICITATION:
+ assert_se(IN_SET(client->state, DHCP6_STATE_REQUEST, DHCP6_STATE_BOUND));
+ break;
+ case DHCP6_STATE_REQUEST:
+ assert_se(IN_SET(client->state, DHCP6_STATE_BOUND, DHCP6_STATE_SOLICITATION));
+ break;
+ default:
+ assert_not_reached();
+ }
+
+ /* Send message if the client has a lease. */
+ if (state != DHCP6_STATE_INFORMATION_REQUEST && sd_dhcp6_client_get_lease(client, NULL) >= 0) {
+ client->state = DHCP6_STATE_REQUEST;
+ dhcp6_client_send_message(client);
+ }
+
+ assert_se(sd_dhcp6_client_stop(client) >= 0);
+
+ test_dhcp_fd[1] = safe_close(test_dhcp_fd[1]);
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+ _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *v1 = NULL, *v2 = NULL;
+ struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } };
+ struct in6_addr hint = { { { 0x3f, 0xfe, 0x05, 0x01, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } };
+ static const char *v1_data = "hogehoge", *v2_data = "foobar";
+
+ assert_se(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", 1) >= 0);
+
+ fuzz_setup_logging();
+
+ if (outside_size_range(size, 0, 65536))
+ return 0;
+
+ assert_se(sd_event_new(&e) >= 0);
+ assert_se(sd_dhcp6_client_new(&client) >= 0);
+ assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0);
+ assert_se(sd_dhcp6_client_set_ifindex(client, 42) >= 0);
+ assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0);
+
+ /* Used when sending message. */
+ assert_se(sd_dhcp6_client_set_fqdn(client, "example.com") == 1);
+ assert_se(sd_dhcp6_client_set_request_mud_url(client, "https://www.example.com/mudfile.json") >= 0);
+ assert_se(sd_dhcp6_client_set_request_user_class(client, STRV_MAKE("u1", "u2", "u3")) >= 0);
+ assert_se(sd_dhcp6_client_set_request_vendor_class(client, STRV_MAKE("v1", "v2", "v3")) >= 0);
+ assert_se(sd_dhcp6_client_set_prefix_delegation_hint(client, 48, &hint) >= 0);
+ assert_se(sd_dhcp6_option_new(123, v1_data, strlen(v1_data), 12345, &v1) >= 0);
+ assert_se(sd_dhcp6_option_new(456, v2_data, strlen(v2_data), 45678, &v2) >= 0);
+ assert_se(sd_dhcp6_client_add_vendor_option(client, v1) >= 0);
+ assert_se(sd_dhcp6_client_add_vendor_option(client, v2) >= 0);
+
+ fuzz_client(client, data, size, DHCP6_STATE_INFORMATION_REQUEST);
+ fuzz_client(client, data, size, DHCP6_STATE_SOLICITATION);
+
+ /* If size is zero, then the resend timer will be triggered at first,
+ * but in the REQUEST state the client must have a lease. */
+ if (size == 0)
+ return 0;
+
+ fuzz_client(client, data, size, DHCP6_STATE_REQUEST);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/fuzz-dhcp6-client.options b/src/libsystemd-network/fuzz-dhcp6-client.options
new file mode 100644
index 0000000..678d526
--- /dev/null
+++ b/src/libsystemd-network/fuzz-dhcp6-client.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/src/libsystemd-network/fuzz-lldp-rx.c b/src/libsystemd-network/fuzz-lldp-rx.c
new file mode 100644
index 0000000..844957c
--- /dev/null
+++ b/src/libsystemd-network/fuzz-lldp-rx.c
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "sd-event.h"
+#include "sd-lldp-rx.h"
+
+#include "fd-util.h"
+#include "fuzz.h"
+#include "lldp-network.h"
+
+static int test_fd[2] = EBADF_PAIR;
+
+int lldp_network_bind_raw_socket(int ifindex) {
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0)
+ return -errno;
+
+ return test_fd[0];
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_lldp_rx_unrefp) sd_lldp_rx *lldp_rx = NULL;
+
+ if (outside_size_range(size, 0, 2048))
+ return 0;
+
+ fuzz_setup_logging();
+
+ assert_se(sd_event_new(&e) == 0);
+ assert_se(sd_lldp_rx_new(&lldp_rx) >= 0);
+ assert_se(sd_lldp_rx_set_ifindex(lldp_rx, 42) >= 0);
+ assert_se(sd_lldp_rx_attach_event(lldp_rx, e, 0) >= 0);
+ assert_se(sd_lldp_rx_start(lldp_rx) >= 0);
+
+ assert_se(write(test_fd[1], data, size) == (ssize_t) size);
+ assert_se(sd_event_run(e, 0) >= 0);
+
+ assert_se(sd_lldp_rx_stop(lldp_rx) >= 0);
+ assert_se(sd_lldp_rx_detach_event(lldp_rx) >= 0);
+ test_fd[1] = safe_close(test_fd[1]);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/fuzz-lldp-rx.options b/src/libsystemd-network/fuzz-lldp-rx.options
new file mode 100644
index 0000000..60bd9b0
--- /dev/null
+++ b/src/libsystemd-network/fuzz-lldp-rx.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 2048
diff --git a/src/libsystemd-network/fuzz-ndisc-rs.c b/src/libsystemd-network/fuzz-ndisc-rs.c
new file mode 100644
index 0000000..a89e2b0
--- /dev/null
+++ b/src/libsystemd-network/fuzz-ndisc-rs.c
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <arpa/inet.h>
+#include <netinet/icmp6.h>
+#include <unistd.h>
+
+#include "sd-ndisc.h"
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fuzz.h"
+#include "icmp6-util-unix.h"
+#include "ndisc-internal.h"
+#include "socket-util.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ struct ether_addr mac_addr = {
+ .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
+ };
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
+
+ if (outside_size_range(size, 0, 2048))
+ return 0;
+
+ fuzz_setup_logging();
+
+ assert_se(sd_event_new(&e) >= 0);
+ assert_se(sd_ndisc_new(&nd) >= 0);
+ assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0);
+ assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0);
+ assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0);
+ assert_se(sd_ndisc_start(nd) >= 0);
+ assert_se(write(test_fd[1], data, size) == (ssize_t) size);
+ (void) sd_event_run(e, UINT64_MAX);
+ assert_se(sd_ndisc_stop(nd) >= 0);
+ close(test_fd[1]);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/fuzz-ndisc-rs.options b/src/libsystemd-network/fuzz-ndisc-rs.options
new file mode 100644
index 0000000..60bd9b0
--- /dev/null
+++ b/src/libsystemd-network/fuzz-ndisc-rs.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 2048
diff --git a/src/libsystemd-network/icmp6-util-unix.c b/src/libsystemd-network/icmp6-util-unix.c
new file mode 100644
index 0000000..01edb85
--- /dev/null
+++ b/src/libsystemd-network/icmp6-util-unix.c
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <netinet/ip6.h>
+#include <unistd.h>
+
+#include "fd-util.h"
+#include "icmp6-util-unix.h"
+
+send_ra_t send_ra_function = NULL;
+int test_fd[2] = EBADF_PAIR;
+
+static struct in6_addr dummy_link_local = {
+ .s6_addr = {
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x12, 0x34, 0x56, 0xff, 0xfe, 0x78, 0x9a, 0xbc,
+ },
+};
+
+int icmp6_bind_router_solicitation(int ifindex) {
+ if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0)
+ return -errno;
+
+ return test_fd[0];
+}
+
+int icmp6_bind_router_advertisement(int ifindex) {
+ return test_fd[1];
+}
+
+int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
+ if (!send_ra_function)
+ return 0;
+
+ return send_ra_function(0);
+}
+
+int icmp6_receive(
+ int fd,
+ void *iov_base,
+ size_t iov_len,
+ struct in6_addr *ret_sender,
+ triple_timestamp *ret_timestamp) {
+
+ assert_se(read (fd, iov_base, iov_len) == (ssize_t) iov_len);
+
+ if (ret_timestamp)
+ triple_timestamp_now(ret_timestamp);
+
+ if (ret_sender)
+ *ret_sender = dummy_link_local;
+
+ return 0;
+}
diff --git a/src/libsystemd-network/icmp6-util-unix.h b/src/libsystemd-network/icmp6-util-unix.h
new file mode 100644
index 0000000..a9cb05a
--- /dev/null
+++ b/src/libsystemd-network/icmp6-util-unix.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "icmp6-util.h"
+
+typedef int (*send_ra_t)(uint8_t flags);
+
+extern send_ra_t send_ra_function;
+extern int test_fd[2];
diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c
new file mode 100644
index 0000000..72c20ba
--- /dev/null
+++ b/src/libsystemd-network/icmp6-util.c
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <net/if.h>
+#include <linux/if_packet.h>
+
+#include "fd-util.h"
+#include "icmp6-util.h"
+#include "in-addr-util.h"
+#include "iovec-util.h"
+#include "network-common.h"
+#include "socket-util.h"
+
+#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } }
+
+#define IN6ADDR_ALL_NODES_MULTICAST_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } }
+
+static int icmp6_bind_router_message(const struct icmp6_filter *filter,
+ const struct ipv6_mreq *mreq) {
+ int ifindex = mreq->ipv6mr_interface;
+ _cleanup_close_ int s = -EBADF;
+ int r;
+
+ assert(filter);
+ assert(mreq);
+
+ s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6);
+ if (s < 0)
+ return -errno;
+
+ if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, filter, sizeof(*filter)) < 0)
+ return -errno;
+
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, mreq, sizeof(*mreq)) < 0)
+ return -errno;
+
+ /* RFC 3315, section 6.7, bullet point 2 may indicate that an
+ IPV6_PKTINFO socket option also applies for ICMPv6 multicast.
+ Empirical experiments indicates otherwise and therefore an
+ IPV6_MULTICAST_IF socket option is used here instead */
+ r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, ifindex);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, false);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 255);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, 255);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, SOL_IPV6, IPV6_RECVHOPLIMIT, true);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true);
+ if (r < 0)
+ return r;
+
+ r = socket_bind_to_ifindex(s, ifindex);
+ if (r < 0)
+ return r;
+
+ return TAKE_FD(s);
+}
+
+int icmp6_bind_router_solicitation(int ifindex) {
+ struct icmp6_filter filter = {};
+ struct ipv6_mreq mreq = {
+ .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT,
+ .ipv6mr_interface = ifindex,
+ };
+
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+
+ return icmp6_bind_router_message(&filter, &mreq);
+}
+
+int icmp6_bind_router_advertisement(int ifindex) {
+ struct icmp6_filter filter = {};
+ struct ipv6_mreq mreq = {
+ .ipv6mr_multiaddr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
+ .ipv6mr_interface = ifindex,
+ };
+
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+
+ return icmp6_bind_router_message(&filter, &mreq);
+}
+
+int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
+ struct sockaddr_in6 dst = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
+ };
+ struct {
+ struct nd_router_solicit rs;
+ struct nd_opt_hdr rs_opt;
+ struct ether_addr rs_opt_mac;
+ } _packed_ rs = {
+ .rs.nd_rs_type = ND_ROUTER_SOLICIT,
+ .rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR,
+ .rs_opt.nd_opt_len = 1,
+ };
+ struct iovec iov = {
+ .iov_base = &rs,
+ .iov_len = sizeof(rs),
+ };
+ struct msghdr msg = {
+ .msg_name = &dst,
+ .msg_namelen = sizeof(dst),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+
+ assert(s >= 0);
+ assert(ether_addr);
+
+ rs.rs_opt_mac = *ether_addr;
+
+ if (sendmsg(s, &msg, 0) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int icmp6_receive(
+ int fd,
+ void *buffer,
+ size_t size,
+ struct in6_addr *ret_sender,
+ triple_timestamp *ret_timestamp) {
+
+ /* This needs to be initialized with zero. See #20741. */
+ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int)) + /* ttl */
+ CMSG_SPACE_TIMEVAL) control = {};
+ struct iovec iov = {};
+ union sockaddr_union sa = {};
+ struct msghdr msg = {
+ .msg_name = &sa.sa,
+ .msg_namelen = sizeof(sa),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct in6_addr addr = {};
+ ssize_t len;
+
+ iov = IOVEC_MAKE(buffer, size);
+
+ len = recvmsg_safe(fd, &msg, MSG_DONTWAIT);
+ if (len < 0)
+ return (int) len;
+
+ if ((size_t) len != size)
+ return -EINVAL;
+
+ if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
+ sa.in6.sin6_family == AF_INET6) {
+
+ addr = sa.in6.sin6_addr;
+ if (!in6_addr_is_link_local(&addr) && !in6_addr_is_null(&addr))
+ return -EADDRNOTAVAIL;
+
+ } else if (msg.msg_namelen > 0)
+ return -EPFNOSUPPORT;
+
+ /* namelen == 0 only happens when running the test-suite over a socketpair */
+
+ assert(!(msg.msg_flags & MSG_TRUNC));
+
+ int *hops = CMSG_FIND_DATA(&msg, SOL_IPV6, IPV6_HOPLIMIT, int);
+ if (hops && *hops != 255)
+ return -EMULTIHOP;
+
+ if (ret_timestamp)
+ triple_timestamp_from_cmsg(ret_timestamp, &msg);
+ if (ret_sender)
+ *ret_sender = addr;
+ return 0;
+}
diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h
new file mode 100644
index 0000000..0a9ecb4
--- /dev/null
+++ b/src/libsystemd-network/icmp6-util.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include <net/ethernet.h>
+
+#include "time-util.h"
+
+#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } }
+
+#define IN6ADDR_ALL_NODES_MULTICAST_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } }
+
+int icmp6_bind_router_solicitation(int ifindex);
+int icmp6_bind_router_advertisement(int ifindex);
+int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr);
+int icmp6_receive(
+ int fd,
+ void *buffer,
+ size_t size,
+ struct in6_addr *ret_sender,
+ triple_timestamp *ret_timestamp);
diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c
new file mode 100644
index 0000000..af61c9b
--- /dev/null
+++ b/src/libsystemd-network/lldp-neighbor.c
@@ -0,0 +1,795 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "ether-addr-util.h"
+#include "hexdecoct.h"
+#include "in-addr-util.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);
+ siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state);
+}
+
+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);
+
+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);
+}
+
+sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) {
+ if (!n)
+ return NULL;
+
+ assert(n->n_ref > 0 || n->lldp_rx);
+ n->n_ref++;
+
+ return 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);
+ free(n->port_description);
+ free(n->system_name);
+ free(n->system_description);
+ free(n->mud_url);
+ free(n->chassis_id_as_string);
+ free(n->port_id_as_string);
+ return mfree(n);
+}
+
+sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) {
+
+ /* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from
+ * the sd_lldp object. */
+
+ if (!n)
+ return NULL;
+
+ assert(n->n_ref > 0);
+ n->n_ref--;
+
+ if (n->n_ref <= 0 && !n->lldp_rx)
+ lldp_neighbor_free(n);
+
+ return NULL;
+}
+
+sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) {
+
+ /* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */
+
+ if (!n)
+ return NULL;
+
+ if (!n->lldp_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_rx->neighbor_by_id, &n->id, n);
+
+ assert_se(prioq_remove(n->lldp_rx->neighbor_by_expiry, n, &n->prioq_idx) >= 0);
+
+ n->lldp_rx = NULL;
+
+ if (n->n_ref <= 0)
+ lldp_neighbor_free(n);
+
+ return NULL;
+}
+
+sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) {
+ sd_lldp_neighbor *n;
+
+ if (raw_size > SIZE_MAX - ALIGN(sizeof(sd_lldp_neighbor)))
+ return NULL;
+
+ n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size);
+ if (!n)
+ return NULL;
+
+ n->raw_size = raw_size;
+ n->n_ref = 1;
+
+ return n;
+}
+
+static int parse_string(sd_lldp_rx *lldp_rx, char **s, const void *q, size_t n) {
+ const char *p = q;
+ char *k;
+
+ assert(s);
+ assert(p || n == 0);
+
+ if (*s) {
+ log_lldp_rx(lldp_rx, "Found duplicate string, ignoring field.");
+ return 0;
+ }
+
+ /* Strip trailing NULs, just to be nice */
+ while (n > 0 && p[n-1] == 0)
+ n--;
+
+ if (n <= 0) /* Ignore empty strings */
+ return 0;
+
+ /* Look for inner NULs */
+ if (memchr(p, 0, n)) {
+ log_lldp_rx(lldp_rx, "Found inner NUL in string, ignoring field.");
+ return 0;
+ }
+
+ /* Let's escape weird chars, for security reasons */
+ k = cescape_length(p, n);
+ if (!k)
+ return log_oom_debug();
+
+ free_and_replace(*s, k);
+
+ return 1;
+}
+
+int lldp_neighbor_parse(sd_lldp_neighbor *n) {
+ struct ether_header h;
+ const uint8_t *p;
+ size_t left;
+ int r;
+
+ assert(n);
+
+ if (n->raw_size < sizeof(struct ether_header))
+ 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_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 ||
+ h.ether_dhost[2] != 0xc2 ||
+ h.ether_dhost[3] != 0x00 ||
+ h.ether_dhost[4] != 0x00 ||
+ !IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e))
+ 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));
+
+ p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header);
+ left = n->raw_size - sizeof(struct ether_header);
+
+ for (;;) {
+ uint8_t type;
+ uint16_t length;
+
+ if (left < 2)
+ return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+ "TLV lacks header, ignoring.");
+
+ type = p[0] >> 1;
+ length = p[1] + (((uint16_t) (p[0] & 1)) << 8);
+ p += 2, left -= 2;
+
+ if (left < length)
+ 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_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) */
+
+ goto end_marker;
+
+ case SD_LLDP_TYPE_CHASSIS_ID:
+ if (length < 2 || length > 256)
+ /* includes the chassis subtype, hence one extra byte */
+ 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_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)
+ return log_oom_debug();
+
+ n->id.chassis_id_size = length;
+ break;
+
+ case SD_LLDP_TYPE_PORT_ID:
+ if (length < 2 || length > 256)
+ /* includes the port subtype, hence one extra byte */
+ 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_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)
+ return log_oom_debug();
+
+ n->id.port_id_size = length;
+ break;
+
+ case SD_LLDP_TYPE_TTL:
+ if (length != 2)
+ 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_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_rx, &n->port_description, p, length);
+ if (r < 0)
+ return r;
+ break;
+
+ case SD_LLDP_TYPE_SYSTEM_NAME:
+ 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_rx, &n->system_description, p, length);
+ if (r < 0)
+ return r;
+ break;
+
+ case SD_LLDP_TYPE_SYSTEM_CAPABILITIES:
+ if (length != 4)
+ 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);
+ n->has_capabilities = true;
+ break;
+
+ case SD_LLDP_TYPE_PRIVATE:
+ if (length < 4)
+ 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_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;
+ }
+ break;
+ }
+
+ p += length, left -= length;
+ }
+
+end_marker:
+ if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl)
+ 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);
+
+ return 0;
+}
+
+void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) {
+ assert(n);
+
+ if (n->ttl > 0) {
+ usec_t base;
+
+ /* Use the packet's timestamp if there is one known */
+ base = triple_timestamp_by_clock(&n->timestamp, CLOCK_BOOTTIME);
+ if (!timestamp_is_set(base))
+ base = now(CLOCK_BOOTTIME); /* Otherwise, take the current time */
+
+ n->until = usec_add(base, n->ttl * USEC_PER_SEC);
+ } else
+ n->until = 0;
+
+ 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) {
+ if (a == b)
+ return true;
+
+ if (!a || !b)
+ return false;
+
+ if (a->raw_size != b->raw_size)
+ return false;
+
+ return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0;
+}
+
+int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) {
+ assert_return(n, -EINVAL);
+ assert_return(address, -EINVAL);
+
+ *address = n->source_address;
+ return 0;
+}
+
+int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) {
+ assert_return(n, -EINVAL);
+ assert_return(address, -EINVAL);
+
+ *address = n->destination_address;
+ return 0;
+}
+
+int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ *ret = LLDP_NEIGHBOR_RAW(n);
+ *size = n->raw_size;
+
+ return 0;
+}
+
+int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
+ assert_return(n, -EINVAL);
+ assert_return(type, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ assert(n->id.chassis_id_size > 0);
+
+ *type = *(uint8_t*) n->id.chassis_id;
+ *ret = (uint8_t*) n->id.chassis_id + 1;
+ *size = n->id.chassis_id_size - 1;
+
+ return 0;
+}
+
+static int format_mac_address(const void *data, size_t sz, char **ret) {
+ struct ether_addr a;
+ char *k;
+
+ assert(data || sz <= 0);
+
+ if (sz != 7)
+ return 0;
+
+ memcpy(&a, (uint8_t*) data + 1, sizeof(a));
+
+ k = new(char, ETHER_ADDR_TO_STRING_MAX);
+ if (!k)
+ return -ENOMEM;
+
+ *ret = ether_addr_to_string(&a, k);
+ return 1;
+}
+
+static int format_network_address(const void *data, size_t sz, char **ret) {
+ union in_addr_union a;
+ int family, r;
+
+ if (sz == 6 && ((uint8_t*) data)[1] == 1) {
+ memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in));
+ family = AF_INET;
+ } else if (sz == 18 && ((uint8_t*) data)[1] == 2) {
+ memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6));
+ family = AF_INET6;
+ } else
+ return 0;
+
+ r = in_addr_to_string(family, &a, ret);
+ if (r < 0)
+ return r;
+ return 1;
+}
+
+int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) {
+ char *k;
+ int r;
+
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (n->chassis_id_as_string) {
+ *ret = n->chassis_id_as_string;
+ return 0;
+ }
+
+ assert(n->id.chassis_id_size > 0);
+
+ switch (*(uint8_t*) n->id.chassis_id) {
+
+ case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
+ case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
+ case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT:
+ case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
+ case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
+ k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1);
+ if (!k)
+ return -ENOMEM;
+
+ goto done;
+
+ case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
+ r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto done;
+
+ break;
+
+ case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS:
+ r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto done;
+
+ break;
+ }
+
+ /* Generic fallback */
+ k = hexmem(n->id.chassis_id, n->id.chassis_id_size);
+ if (!k)
+ return -ENOMEM;
+
+done:
+ *ret = n->chassis_id_as_string = k;
+ return 0;
+}
+
+int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
+ assert_return(n, -EINVAL);
+ assert_return(type, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ assert(n->id.port_id_size > 0);
+
+ *type = *(uint8_t*) n->id.port_id;
+ *ret = (uint8_t*) n->id.port_id + 1;
+ *size = n->id.port_id_size - 1;
+
+ return 0;
+}
+
+int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) {
+ char *k;
+ int r;
+
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (n->port_id_as_string) {
+ *ret = n->port_id_as_string;
+ return 0;
+ }
+
+ assert(n->id.port_id_size > 0);
+
+ switch (*(uint8_t*) n->id.port_id) {
+
+ case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
+ case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT:
+ case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME:
+ case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
+ k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1);
+ if (!k)
+ return -ENOMEM;
+
+ goto done;
+
+ case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS:
+ r = format_mac_address(n->id.port_id, n->id.port_id_size, &k);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto done;
+
+ break;
+
+ case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS:
+ r = format_network_address(n->id.port_id, n->id.port_id_size, &k);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto done;
+
+ break;
+ }
+
+ /* Generic fallback */
+ k = hexmem(n->id.port_id, n->id.port_id_size);
+ if (!k)
+ return -ENOMEM;
+
+done:
+ *ret = n->port_id_as_string = k;
+ return 0;
+}
+
+int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) {
+ assert_return(n, -EINVAL);
+ assert_return(ret_sec, -EINVAL);
+
+ *ret_sec = n->ttl;
+ return 0;
+}
+
+int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->system_name)
+ return -ENODATA;
+
+ *ret = n->system_name;
+ return 0;
+}
+
+int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->system_description)
+ return -ENODATA;
+
+ *ret = n->system_description;
+ return 0;
+}
+
+int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->port_description)
+ return -ENODATA;
+
+ *ret = n->port_description;
+ return 0;
+}
+
+int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->mud_url)
+ return -ENODATA;
+
+ *ret = n->mud_url;
+ return 0;
+}
+
+int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->has_capabilities)
+ return -ENODATA;
+
+ *ret = n->system_capabilities;
+ return 0;
+}
+
+int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->has_capabilities)
+ return -ENODATA;
+
+ *ret = n->enabled_capabilities;
+ return 0;
+}
+
+int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
+ _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+ assert_return(raw || raw_size <= 0, -EINVAL);
+
+ n = lldp_neighbor_new(raw_size);
+ if (!n)
+ return -ENOMEM;
+
+ memcpy_safe(LLDP_NEIGHBOR_RAW(n), raw, raw_size);
+
+ r = lldp_neighbor_parse(n);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(n);
+
+ return r;
+}
+
+int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) {
+ assert_return(n, -EINVAL);
+
+ assert(n->raw_size >= sizeof(struct ether_header));
+ n->rindex = sizeof(struct ether_header);
+
+ return n->rindex < n->raw_size;
+}
+
+int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) {
+ size_t length;
+
+ assert_return(n, -EINVAL);
+
+ if (n->rindex == n->raw_size) /* EOF */
+ return -ESPIPE;
+
+ if (n->rindex + 2 > n->raw_size) /* Truncated message */
+ return -EBADMSG;
+
+ length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+ if (n->rindex + 2 + length > n->raw_size)
+ return -EBADMSG;
+
+ n->rindex += 2 + length;
+ return n->rindex < n->raw_size;
+}
+
+int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) {
+ assert_return(n, -EINVAL);
+ assert_return(type, -EINVAL);
+
+ if (n->rindex == n->raw_size) /* EOF */
+ return -ESPIPE;
+
+ if (n->rindex + 2 > n->raw_size)
+ return -EBADMSG;
+
+ *type = LLDP_NEIGHBOR_TLV_TYPE(n);
+ return 0;
+}
+
+int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) {
+ uint8_t k;
+ int r;
+
+ assert_return(n, -EINVAL);
+
+ r = sd_lldp_neighbor_tlv_get_type(n, &k);
+ if (r < 0)
+ return r;
+
+ return type == k;
+}
+
+int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype) {
+ const uint8_t *d;
+ size_t length;
+ int r;
+
+ assert_return(n, -EINVAL);
+ assert_return(oui, -EINVAL);
+ assert_return(subtype, -EINVAL);
+
+ r = sd_lldp_neighbor_tlv_is_type(n, SD_LLDP_TYPE_PRIVATE);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENXIO;
+
+ length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+ if (length < 4)
+ return -EBADMSG;
+
+ if (n->rindex + 2 + length > n->raw_size)
+ return -EBADMSG;
+
+ d = LLDP_NEIGHBOR_TLV_DATA(n);
+ memcpy(oui, d, 3);
+ *subtype = d[3];
+
+ return 0;
+}
+
+int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype) {
+ uint8_t k[3], st;
+ int r;
+
+ r = sd_lldp_neighbor_tlv_get_oui(n, k, &st);
+ if (r == -ENXIO)
+ return 0;
+ if (r < 0)
+ return r;
+
+ return memcmp(k, oui, 3) == 0 && st == subtype;
+}
+
+int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
+ size_t length;
+
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ /* Note that this returns the full TLV, including the TLV header */
+
+ if (n->rindex + 2 > n->raw_size)
+ return -EBADMSG;
+
+ length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+ if (n->rindex + 2 + length > n->raw_size)
+ return -EBADMSG;
+
+ *ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
+ *size = length + 2;
+
+ return 0;
+}
+
+int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) {
+ assert_return(n, -EINVAL);
+ assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
+ assert_return(clock_supported(clock), -EOPNOTSUPP);
+ assert_return(ret, -EINVAL);
+
+ if (!triple_timestamp_is_set(&n->timestamp))
+ return -ENODATA;
+
+ *ret = triple_timestamp_by_clock(&n->timestamp, clock);
+ return 0;
+}
diff --git a/src/libsystemd-network/lldp-neighbor.h b/src/libsystemd-network/lldp-neighbor.h
new file mode 100644
index 0000000..016286b
--- /dev/null
+++ b/src/libsystemd-network/lldp-neighbor.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "sd-lldp-rx.h"
+
+#include "hash-funcs.h"
+#include "lldp-rx-internal.h"
+#include "time-util.h"
+
+typedef struct LLDPNeighborID {
+ /* The spec calls this an "MSAP identifier" */
+ void *chassis_id;
+ size_t chassis_id_size;
+
+ void *port_id;
+ size_t port_id_size;
+} LLDPNeighborID;
+
+struct sd_lldp_neighbor {
+ /* Neighbor objects stay around as long as they are linked into an "sd_lldp_rx" object or n_ref > 0. */
+ sd_lldp_rx *lldp_rx;
+ unsigned n_ref;
+
+ triple_timestamp timestamp;
+
+ usec_t until;
+ unsigned prioq_idx;
+
+ struct ether_addr source_address;
+ struct ether_addr destination_address;
+
+ LLDPNeighborID id;
+
+ /* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */
+ size_t raw_size;
+
+ /* The current read index for the iterative TLV interface */
+ size_t rindex;
+
+ /* And a couple of fields parsed out. */
+ bool has_ttl:1;
+ bool has_capabilities:1;
+ bool has_port_vlan_id:1;
+
+ uint16_t ttl;
+
+ uint16_t system_capabilities;
+ uint16_t enabled_capabilities;
+
+ char *port_description;
+ char *system_name;
+ char *system_description;
+ char *mud_url;
+
+ uint16_t port_vlan_id;
+
+ char *chassis_id_as_string;
+ char *port_id_as_string;
+};
+
+static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) {
+ return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor));
+}
+
+static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) {
+ return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1;
+}
+
+static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) {
+ uint8_t *p;
+
+ p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
+ return p[1] + (((size_t) (p[0] & 1)) << 8);
+}
+
+static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) {
+ return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2;
+}
+
+extern const struct hash_ops lldp_neighbor_hash_ops;
+int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y);
+int lldp_neighbor_prioq_compare_func(const void *a, const void *b);
+
+sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n);
+sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size);
+int lldp_neighbor_parse(sd_lldp_neighbor *n);
+void lldp_neighbor_start_ttl(sd_lldp_neighbor *n);
+bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b);
diff --git a/src/libsystemd-network/lldp-network.c b/src/libsystemd-network/lldp-network.c
new file mode 100644
index 0000000..598669f
--- /dev/null
+++ b/src/libsystemd-network/lldp-network.c
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/filter.h>
+#include <netinet/if_ether.h>
+
+#include "fd-util.h"
+#include "lldp-network.h"
+#include "missing_network.h"
+#include "socket-util.h"
+
+int lldp_network_bind_raw_socket(int ifindex) {
+ static const struct sock_filter filter[] = {
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ethhdr, h_dest)), /* A <- 4 bytes of destination MAC */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0), /* A != 01:80:c2:00 */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_dest) + 4), /* A <- remaining 2 bytes of destination MAC */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0), /* A != 00:00 */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0), /* A != 00:03 */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0), /* A != 00:0e */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_proto)), /* A <- protocol */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_LLDP, 1, 0), /* A != ETHERTYPE_LLDP */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
+ BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept packet */
+ };
+ static const struct sock_fprog fprog = {
+ .len = ELEMENTSOF(filter),
+ .filter = (struct sock_filter*) filter,
+ };
+ struct packet_mreq mreq = {
+ .mr_ifindex = ifindex,
+ .mr_type = PACKET_MR_MULTICAST,
+ .mr_alen = ETH_ALEN,
+ .mr_address = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00 }
+ };
+ union sockaddr_union saddrll = {
+ .ll.sll_family = AF_PACKET,
+ .ll.sll_ifindex = ifindex,
+ };
+ _cleanup_close_ int fd = -EBADF;
+
+ assert(ifindex > 0);
+
+ fd = socket(AF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK,
+ htobe16(ETHERTYPE_LLDP));
+ if (fd < 0)
+ return -errno;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0)
+ return -errno;
+
+ /* customer bridge */
+ if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
+ return -errno;
+
+ /* non TPMR bridge */
+ mreq.mr_address[ETH_ALEN - 1] = 0x03;
+ if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
+ return -errno;
+
+ /* nearest bridge */
+ mreq.mr_address[ETH_ALEN - 1] = 0x0E;
+ if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
+ return -errno;
+
+ if (bind(fd, &saddrll.sa, sizeof(saddrll.ll)) < 0)
+ return -errno;
+
+ return TAKE_FD(fd);
+}
diff --git a/src/libsystemd-network/lldp-network.h b/src/libsystemd-network/lldp-network.h
new file mode 100644
index 0000000..bc69b32
--- /dev/null
+++ b/src/libsystemd-network/lldp-network.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-event.h"
+
+int lldp_network_bind_raw_socket(int ifindex);
diff --git a/src/libsystemd-network/lldp-rx-internal.h b/src/libsystemd-network/lldp-rx-internal.h
new file mode 100644
index 0000000..83d0bc4
--- /dev/null
+++ b/src/libsystemd-network/lldp-rx-internal.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-event.h"
+#include "sd-lldp-rx.h"
+
+#include "hashmap.h"
+#include "network-common.h"
+#include "prioq.h"
+
+struct sd_lldp_rx {
+ unsigned n_ref;
+
+ int ifindex;
+ char *ifname;
+ int fd;
+
+ sd_event *event;
+ int64_t event_priority;
+ sd_event_source *io_event_source;
+ sd_event_source *timer_event_source;
+
+ Prioq *neighbor_by_expiry;
+ Hashmap *neighbor_by_id;
+
+ uint64_t neighbors_max;
+
+ sd_lldp_rx_callback_t callback;
+ void *userdata;
+
+ uint16_t capability_mask;
+
+ struct ether_addr filter_address;
+};
+
+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_rx_errno(lldp_rx, error, fmt, ...) \
+ log_interface_prefix_full_errno( \
+ "LLDP Rx: ", \
+ sd_lldp_rx, lldp_rx, \
+ error, fmt, ##__VA_ARGS__)
+#define log_lldp_rx(lldp_rx, fmt, ...) \
+ log_interface_prefix_full_errno_zerook( \
+ "LLDP Rx: ", \
+ sd_lldp_rx, lldp_rx, \
+ 0, fmt, ##__VA_ARGS__)
diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build
new file mode 100644
index 0000000..93186e2
--- /dev/null
+++ b/src/libsystemd-network/meson.build
@@ -0,0 +1,121 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+sources = files(
+ 'arp-util.c',
+ 'dhcp-identifier.c',
+ 'dhcp-network.c',
+ 'dhcp-option.c',
+ 'dhcp-packet.c',
+ 'dhcp6-network.c',
+ 'dhcp6-option.c',
+ 'dhcp6-protocol.c',
+ 'icmp6-util.c',
+ 'lldp-neighbor.c',
+ 'lldp-network.c',
+ 'ndisc-protocol.c',
+ 'ndisc-router.c',
+ 'network-common.c',
+ 'network-internal.c',
+ 'sd-dhcp-client.c',
+ 'sd-dhcp-lease.c',
+ 'sd-dhcp-server.c',
+ 'sd-dhcp6-client.c',
+ 'sd-dhcp6-lease.c',
+ 'sd-ipv4acd.c',
+ 'sd-ipv4ll.c',
+ 'sd-lldp-rx.c',
+ 'sd-lldp-tx.c',
+ 'sd-ndisc.c',
+ 'sd-radv.c',
+)
+
+libsystemd_network = static_library(
+ 'systemd-network',
+ sources,
+ include_directories : includes,
+ dependencies : userspace,
+ build_by_default : false)
+
+libsystemd_network_includes = [includes, include_directories('.')]
+
+############################################################
+
+network_test_template = test_template + {
+ 'link_with' : [
+ libshared,
+ libsystemd_network,
+ ],
+ 'suite' : 'network',
+}
+
+network_fuzz_template = fuzz_template + {
+ 'link_with' : [
+ libshared,
+ libsystemd_network,
+ ],
+}
+
+executables += [
+ network_test_template + {
+ 'sources' : files('test-acd.c'),
+ 'type' : 'manual',
+ },
+ network_test_template + {
+ 'sources' : files('test-dhcp-client.c'),
+ },
+ network_test_template + {
+ 'sources' : files('test-dhcp-option.c'),
+ },
+ network_test_template + {
+ 'sources' : files('test-dhcp-server.c'),
+ },
+ network_test_template + {
+ 'sources' : files('test-dhcp6-client.c'),
+ },
+ network_test_template + {
+ 'sources' : files('test-ipv4ll-manual.c'),
+ 'type' : 'manual',
+ },
+ network_test_template + {
+ 'sources' : files('test-ipv4ll.c'),
+ },
+ network_test_template + {
+ 'sources' : files('test-lldp-rx.c'),
+ },
+ network_test_template + {
+ 'sources' : files(
+ 'test-ndisc-ra.c',
+ 'icmp6-util-unix.c',
+ ),
+ },
+ network_test_template + {
+ 'sources' : files(
+ 'test-ndisc-rs.c',
+ 'icmp6-util-unix.c',
+ ),
+ },
+ network_test_template + {
+ 'sources' : files('test-sd-dhcp-lease.c'),
+ },
+ network_fuzz_template + {
+ 'sources' : files('fuzz-dhcp-client.c'),
+ },
+ network_fuzz_template + {
+ 'sources' : files('fuzz-dhcp6-client.c'),
+ },
+ network_fuzz_template + {
+ 'sources' : files('fuzz-dhcp-server.c'),
+ },
+ network_fuzz_template + {
+ 'sources' : files('fuzz-dhcp-server-relay.c'),
+ },
+ network_fuzz_template + {
+ 'sources' : files('fuzz-lldp-rx.c'),
+ },
+ network_fuzz_template + {
+ 'sources' : files(
+ 'fuzz-ndisc-rs.c',
+ 'icmp6-util-unix.c',
+ ),
+ },
+]
diff --git a/src/libsystemd-network/ndisc-internal.h b/src/libsystemd-network/ndisc-internal.h
new file mode 100644
index 0000000..615de0d
--- /dev/null
+++ b/src/libsystemd-network/ndisc-internal.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include "sd-ndisc.h"
+
+#include "network-common.h"
+#include "time-util.h"
+
+#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
+#define NDISC_MAX_ROUTER_SOLICITATION_INTERVAL (3600U * USEC_PER_SEC)
+#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
+
+struct sd_ndisc {
+ unsigned n_ref;
+
+ int ifindex;
+ char *ifname;
+ int fd;
+
+ sd_event *event;
+ int event_priority;
+
+ struct ether_addr mac_addr;
+
+ sd_event_source *recv_event_source;
+ sd_event_source *timeout_event_source;
+ sd_event_source *timeout_no_ra;
+
+ usec_t retransmit_time;
+
+ sd_ndisc_callback_t callback;
+ void *userdata;
+};
+
+const char* ndisc_event_to_string(sd_ndisc_event_t e) _const_;
+sd_ndisc_event_t ndisc_event_from_string(const char *s) _pure_;
+
+#define log_ndisc_errno(ndisc, error, fmt, ...) \
+ log_interface_prefix_full_errno( \
+ "NDISC: ", \
+ sd_ndisc, ndisc, \
+ error, fmt, ##__VA_ARGS__)
+#define log_ndisc(ndisc, fmt, ...) \
+ log_interface_prefix_full_errno_zerook( \
+ "NDISC: ", \
+ sd_ndisc, ndisc, \
+ 0, fmt, ##__VA_ARGS__)
diff --git a/src/libsystemd-network/ndisc-protocol.c b/src/libsystemd-network/ndisc-protocol.c
new file mode 100644
index 0000000..fae4a58
--- /dev/null
+++ b/src/libsystemd-network/ndisc-protocol.c
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "ndisc-protocol.h"
+
+static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = {
+ [PREFIX_LENGTH_CODE_96] = 96,
+ [PREFIX_LENGTH_CODE_64] = 64,
+ [PREFIX_LENGTH_CODE_56] = 56,
+ [PREFIX_LENGTH_CODE_48] = 48,
+ [PREFIX_LENGTH_CODE_40] = 40,
+ [PREFIX_LENGTH_CODE_32] = 32,
+};
+
+int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret) {
+ plc &= PREF64_PLC_MASK;
+ if (plc >= _PREFIX_LENGTH_CODE_MAX)
+ return -EINVAL;
+
+ if (ret)
+ *ret = prefix_length_code_to_prefix_length[plc];
+ return 0;
+}
+
+int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret) {
+ assert(ret);
+
+ for (size_t i = 0; i < ELEMENTSOF(prefix_length_code_to_prefix_length); i++)
+ if (prefix_length_code_to_prefix_length[i] == prefixlen) {
+ *ret = i;
+ return 0;
+ }
+
+ return -EINVAL;
+}
diff --git a/src/libsystemd-network/ndisc-protocol.h b/src/libsystemd-network/ndisc-protocol.h
new file mode 100644
index 0000000..8e403e3
--- /dev/null
+++ b/src/libsystemd-network/ndisc-protocol.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "time-util.h"
+
+/* RFC 8781: PREF64 or (NAT64 prefix) */
+#define PREF64_SCALED_LIFETIME_MASK 0xfff8
+#define PREF64_PLC_MASK 0x0007
+#define PREF64_MAX_LIFETIME_USEC (65528 * USEC_PER_SEC)
+
+typedef enum PrefixLengthCode {
+ PREFIX_LENGTH_CODE_96,
+ PREFIX_LENGTH_CODE_64,
+ PREFIX_LENGTH_CODE_56,
+ PREFIX_LENGTH_CODE_48,
+ PREFIX_LENGTH_CODE_40,
+ PREFIX_LENGTH_CODE_32,
+ _PREFIX_LENGTH_CODE_MAX,
+ _PREFIX_LENGTH_CODE_INVALID = -EINVAL,
+} PrefixLengthCode;
+
+/* rfc8781: section 4 - Scaled Lifetime: 13-bit unsigned integer. PREFIX_LEN (Prefix Length Code): 3-bit unsigned integer */
+struct nd_opt_prefix64_info {
+ uint8_t type;
+ uint8_t length;
+ uint16_t lifetime_and_plc;
+ uint8_t prefix[12];
+} __attribute__((__packed__));
+
+int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret);
+int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret);
diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c
new file mode 100644
index 0000000..5162df7
--- /dev/null
+++ b/src/libsystemd-network/ndisc-router.c
@@ -0,0 +1,913 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/icmp6.h>
+
+#include "sd-ndisc.h"
+
+#include "alloc-util.h"
+#include "dns-domain.h"
+#include "hostname-util.h"
+#include "memory-util.h"
+#include "missing_network.h"
+#include "ndisc-internal.h"
+#include "ndisc-protocol.h"
+#include "ndisc-router.h"
+#include "strv.h"
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_router, sd_ndisc_router, mfree);
+
+sd_ndisc_router *ndisc_router_new(size_t raw_size) {
+ sd_ndisc_router *rt;
+
+ if (raw_size > SIZE_MAX - ALIGN(sizeof(sd_ndisc_router)))
+ return NULL;
+
+ rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size);
+ if (!rt)
+ return NULL;
+
+ rt->raw_size = raw_size;
+ rt->n_ref = 1;
+
+ return rt;
+}
+
+int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (in6_addr_is_null(&rt->address))
+ return -ENODATA;
+
+ *ret = rt->address;
+ return 0;
+}
+
+int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
+ assert_return(clock_supported(clock), -EOPNOTSUPP);
+ assert_return(ret, -EINVAL);
+
+ if (!triple_timestamp_is_set(&rt->timestamp))
+ return -ENODATA;
+
+ *ret = triple_timestamp_by_clock(&rt->timestamp, clock);
+ return 0;
+}
+
+#define DEFINE_GET_TIMESTAMP(name) \
+ int sd_ndisc_router_##name##_timestamp( \
+ sd_ndisc_router *rt, \
+ clockid_t clock, \
+ uint64_t *ret) { \
+ \
+ usec_t s, t; \
+ int r; \
+ \
+ assert_return(rt, -EINVAL); \
+ assert_return(ret, -EINVAL); \
+ \
+ r = sd_ndisc_router_##name(rt, &s); \
+ if (r < 0) \
+ return r; \
+ \
+ r = sd_ndisc_router_get_timestamp(rt, clock, &t); \
+ if (r < 0) \
+ return r; \
+ \
+ *ret = time_span_to_stamp(s, t); \
+ return 0; \
+ }
+
+DEFINE_GET_TIMESTAMP(get_lifetime);
+DEFINE_GET_TIMESTAMP(prefix_get_valid_lifetime);
+DEFINE_GET_TIMESTAMP(prefix_get_preferred_lifetime);
+DEFINE_GET_TIMESTAMP(route_get_lifetime);
+DEFINE_GET_TIMESTAMP(rdnss_get_lifetime);
+DEFINE_GET_TIMESTAMP(dnssl_get_lifetime);
+DEFINE_GET_TIMESTAMP(prefix64_get_lifetime);
+
+int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(ret_size, -EINVAL);
+
+ *ret = NDISC_ROUTER_RAW(rt);
+ *ret_size = rt->raw_size;
+
+ return 0;
+}
+
+static bool pref64_option_verify(const struct nd_opt_prefix64_info *p, size_t length) {
+ uint16_t lifetime_and_plc;
+
+ assert(p);
+
+ if (length != sizeof(struct nd_opt_prefix64_info))
+ return false;
+
+ lifetime_and_plc = be16toh(p->lifetime_and_plc);
+ if (pref64_plc_to_prefix_length(lifetime_and_plc, NULL) < 0)
+ return false;
+
+ return true;
+}
+
+int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) {
+ struct nd_router_advert *a;
+ const uint8_t *p;
+ bool has_mtu = false, has_flag_extension = false;
+ size_t left;
+
+ assert(rt);
+
+ if (rt->raw_size < sizeof(struct nd_router_advert))
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Too small to be a router advertisement, ignoring.");
+
+ /* Router advertisement packets are neatly aligned to 64-bit boundaries, hence we can access them directly */
+ a = NDISC_ROUTER_RAW(rt);
+
+ if (a->nd_ra_type != ND_ROUTER_ADVERT)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Received ND packet that is not a router advertisement, ignoring.");
+
+ if (a->nd_ra_code != 0)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Received ND packet with wrong RA code, ignoring.");
+
+ rt->hop_limit = a->nd_ra_curhoplimit;
+ rt->flags = a->nd_ra_flags_reserved; /* the first 8 bits */
+ rt->lifetime_usec = be16_sec_to_usec(a->nd_ra_router_lifetime, /* max_as_infinity = */ false);
+ rt->icmp6_ratelimit_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false);
+
+ rt->preference = (rt->flags >> 3) & 3;
+ if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
+ rt->preference = SD_NDISC_PREFERENCE_MEDIUM;
+
+ p = (const uint8_t*) NDISC_ROUTER_RAW(rt) + sizeof(struct nd_router_advert);
+ left = rt->raw_size - sizeof(struct nd_router_advert);
+
+ for (;;) {
+ uint8_t type;
+ size_t length;
+
+ if (left == 0)
+ break;
+
+ if (left < 2)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Option lacks header, ignoring datagram.");
+
+ type = p[0];
+ length = p[1] * 8;
+
+ if (length == 0)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Zero-length option, ignoring datagram.");
+ if (left < length)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Option truncated, ignoring datagram.");
+
+ switch (type) {
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION:
+
+ if (length != 4*8)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Prefix option of invalid size, ignoring datagram.");
+
+ if (p[2] > 128)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Bad prefix length, ignoring datagram.");
+
+ break;
+
+ case SD_NDISC_OPTION_MTU: {
+ uint32_t m;
+
+ if (has_mtu) {
+ log_ndisc(nd, "MTU option specified twice, ignoring.");
+ break;
+ }
+
+ if (length != 8)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "MTU option of invalid size, ignoring datagram.");
+
+ m = be32toh(*(uint32_t*) (p + 4));
+ if (m >= IPV6_MIN_MTU) /* ignore invalidly small MTUs */
+ rt->mtu = m;
+
+ has_mtu = true;
+ break;
+ }
+
+ case SD_NDISC_OPTION_ROUTE_INFORMATION:
+ if (length < 1*8 || length > 3*8)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Route information option of invalid size, ignoring datagram.");
+
+ if (p[2] > 128)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Bad route prefix length, ignoring datagram.");
+
+ break;
+
+ case SD_NDISC_OPTION_RDNSS:
+ if (length < 3*8 || (length % (2*8)) != 1*8)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), "RDNSS option has invalid size.");
+
+ break;
+
+ case SD_NDISC_OPTION_FLAGS_EXTENSION:
+
+ if (has_flag_extension) {
+ log_ndisc(nd, "Flags extension option specified twice, ignoring.");
+ break;
+ }
+
+ if (length < 1*8)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Flags extension option has invalid size.");
+
+ /* Add in the additional flags bits */
+ rt->flags |=
+ ((uint64_t) p[2] << 8) |
+ ((uint64_t) p[3] << 16) |
+ ((uint64_t) p[4] << 24) |
+ ((uint64_t) p[5] << 32) |
+ ((uint64_t) p[6] << 40) |
+ ((uint64_t) p[7] << 48);
+
+ has_flag_extension = true;
+ break;
+
+ case SD_NDISC_OPTION_DNSSL:
+ if (length < 2*8)
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "DNSSL option has invalid size.");
+
+ break;
+ case SD_NDISC_OPTION_PREF64: {
+ if (!pref64_option_verify((struct nd_opt_prefix64_info *) p, length))
+ log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "PREF64 prefix has invalid prefix length.");
+ break;
+ }}
+
+ p += length, left -= length;
+ }
+
+ rt->rindex = sizeof(struct nd_router_advert);
+ return 0;
+}
+
+int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->hop_limit;
+ return 0;
+}
+
+int sd_ndisc_router_get_icmp6_ratelimit(sd_ndisc_router *rt, uint64_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->icmp6_ratelimit_usec;
+ return 0;
+}
+
+int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->flags;
+ return 0;
+}
+
+int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->lifetime_usec;
+ return 0;
+}
+
+int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->preference;
+ return 0;
+}
+
+int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (rt->mtu <= 0)
+ return -ENODATA;
+
+ *ret = rt->mtu;
+ return 0;
+}
+
+int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) {
+ assert_return(rt, -EINVAL);
+
+ assert(rt->raw_size >= sizeof(struct nd_router_advert));
+ rt->rindex = sizeof(struct nd_router_advert);
+
+ return rt->rindex < rt->raw_size;
+}
+
+int sd_ndisc_router_option_next(sd_ndisc_router *rt) {
+ size_t length;
+
+ assert_return(rt, -EINVAL);
+
+ if (rt->rindex == rt->raw_size) /* EOF */
+ return -ESPIPE;
+
+ if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
+ return -EBADMSG;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (rt->rindex + length > rt->raw_size)
+ return -EBADMSG;
+
+ rt->rindex += length;
+ return rt->rindex < rt->raw_size;
+}
+
+int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (rt->rindex == rt->raw_size) /* EOF */
+ return -ESPIPE;
+
+ if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
+ return -EBADMSG;
+
+ *ret = NDISC_ROUTER_OPTION_TYPE(rt);
+ return 0;
+}
+
+int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) {
+ uint8_t k;
+ int r;
+
+ assert_return(rt, -EINVAL);
+
+ r = sd_ndisc_router_option_get_type(rt, &k);
+ if (r < 0)
+ return r;
+
+ return type == k;
+}
+
+int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size) {
+ size_t length;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(ret_size, -EINVAL);
+
+ /* Note that this returns the full option, including the option header */
+
+ if (rt->rindex + 2 > rt->raw_size)
+ return -EBADMSG;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (rt->rindex + length > rt->raw_size)
+ return -EBADMSG;
+
+ *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
+ *ret_size = length;
+
+ return 0;
+}
+
+static int get_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix_info **ret) {
+ struct nd_opt_prefix_info *ri;
+ size_t length;
+ int r;
+
+ assert(rt);
+ assert(ret);
+
+ r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREFIX_INFORMATION);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EMEDIUMTYPE;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (length != sizeof(struct nd_opt_prefix_info))
+ return -EBADMSG;
+
+ ri = (struct nd_opt_prefix_info*) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex);
+ if (ri->nd_opt_pi_prefix_len > 128)
+ return -EBADMSG;
+
+ *ret = ri;
+ return 0;
+}
+
+int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
+ struct nd_opt_prefix_info *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_prefix_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = be32_sec_to_usec(ri->nd_opt_pi_valid_time, /* max_as_infinity = */ true);
+ return 0;
+}
+
+int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
+ struct nd_opt_prefix_info *pi;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ *ret = be32_sec_to_usec(pi->nd_opt_pi_preferred_time, /* max_as_infinity = */ true);
+ return 0;
+}
+
+int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret) {
+ struct nd_opt_prefix_info *pi;
+ uint8_t flags;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ flags = pi->nd_opt_pi_flags_reserved;
+
+ if ((flags & ND_OPT_PI_FLAG_AUTO) && (pi->nd_opt_pi_prefix_len != 64)) {
+ log_ndisc(NULL, "Invalid prefix length, ignoring prefix for stateless autoconfiguration.");
+ flags &= ~ND_OPT_PI_FLAG_AUTO;
+ }
+
+ *ret = flags;
+ return 0;
+}
+
+int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret) {
+ struct nd_opt_prefix_info *pi;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ *ret = pi->nd_opt_pi_prefix;
+ return 0;
+}
+
+int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
+ struct nd_opt_prefix_info *pi;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ if (pi->nd_opt_pi_prefix_len > 128)
+ return -EBADMSG;
+
+ *ret = pi->nd_opt_pi_prefix_len;
+ return 0;
+}
+
+static int get_route_info(sd_ndisc_router *rt, uint8_t **ret) {
+ uint8_t *ri;
+ size_t length;
+ int r;
+
+ assert(rt);
+ assert(ret);
+
+ r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_ROUTE_INFORMATION);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EMEDIUMTYPE;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (length < 1*8 || length > 3*8)
+ return -EBADMSG;
+
+ ri = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
+
+ if (ri[2] > 128)
+ return -EBADMSG;
+
+ *ret = ri;
+ return 0;
+}
+
+int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_route_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true);
+ return 0;
+}
+
+int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_route_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ zero(*ret);
+ memcpy(ret, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8);
+
+ return 0;
+}
+
+int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_route_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = ri[2];
+ return 0;
+}
+
+int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_route_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ if (!IN_SET((ri[3] >> 3) & 3, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH))
+ return -EOPNOTSUPP;
+
+ *ret = (ri[3] >> 3) & 3;
+ return 0;
+}
+
+static int get_rdnss_info(sd_ndisc_router *rt, uint8_t **ret) {
+ size_t length;
+ int r;
+
+ assert(rt);
+ assert(ret);
+
+ r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EMEDIUMTYPE;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (length < 3*8 || (length % (2*8)) != 1*8)
+ return -EBADMSG;
+
+ *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
+ return 0;
+}
+
+int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_rdnss_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = (const struct in6_addr*) (ri + 8);
+ return (NDISC_ROUTER_OPTION_LENGTH(rt) - 8) / 16;
+}
+
+int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_rdnss_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true);
+ return 0;
+}
+
+static int get_dnssl_info(sd_ndisc_router *rt, uint8_t **ret) {
+ size_t length;
+ int r;
+
+ assert(rt);
+ assert(ret);
+
+ r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_DNSSL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EMEDIUMTYPE;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (length < 2*8)
+ return -EBADMSG;
+
+ *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
+ return 0;
+}
+
+int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) {
+ _cleanup_strv_free_ char **l = NULL;
+ _cleanup_free_ char *e = NULL;
+ size_t n = 0, left;
+ uint8_t *ri, *p;
+ bool first = true;
+ int r;
+ unsigned k = 0;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_dnssl_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ p = ri + 8;
+ left = NDISC_ROUTER_OPTION_LENGTH(rt) - 8;
+
+ for (;;) {
+ if (left == 0) {
+
+ if (n > 0) /* Not properly NUL terminated */
+ return -EBADMSG;
+
+ break;
+ }
+
+ if (*p == 0) {
+ /* Found NUL termination */
+
+ if (n > 0) {
+ _cleanup_free_ char *normalized = NULL;
+
+ e[n] = 0;
+ r = dns_name_normalize(e, 0, &normalized);
+ if (r < 0)
+ return r;
+
+ /* Ignore the root domain name or "localhost" and friends */
+ if (!is_localhost(normalized) &&
+ !dns_name_is_root(normalized)) {
+
+ if (strv_push(&l, normalized) < 0)
+ return -ENOMEM;
+
+ normalized = NULL;
+ k++;
+ }
+ }
+
+ n = 0;
+ first = true;
+ p++, left--;
+ continue;
+ }
+
+ /* Check for compression (which is not allowed) */
+ if (*p > 63)
+ return -EBADMSG;
+
+ if (1U + *p + 1U > left)
+ return -EBADMSG;
+
+ if (!GREEDY_REALLOC(e, n + !first + DNS_LABEL_ESCAPED_MAX + 1U))
+ return -ENOMEM;
+
+ if (first)
+ first = false;
+ else
+ e[n++] = '.';
+
+ r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ n += r;
+
+ left -= 1 + *p;
+ p += 1 + *p;
+ }
+
+ if (strv_isempty(l)) {
+ *ret = NULL;
+ return 0;
+ }
+
+ *ret = TAKE_PTR(l);
+
+ return k;
+}
+
+int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_dnssl_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true);
+ return 0;
+}
+
+int sd_ndisc_router_captive_portal_get_uri(sd_ndisc_router *rt, const char **ret, size_t *ret_size) {
+ int r;
+ const char *nd_opt_captive_portal;
+ size_t length;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(ret_size, -EINVAL);
+
+ r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_CAPTIVE_PORTAL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EMEDIUMTYPE;
+
+ r = sd_ndisc_router_option_get_raw(rt, (void *)&nd_opt_captive_portal, &length);
+ if (r < 0)
+ return r;
+
+ /* The length field has units of 8 octets */
+ assert(length % 8 == 0);
+ if (length == 0)
+ return -EBADMSG;
+
+ /* Check that the message is not truncated by an embedded NUL.
+ * NUL padding to a multiple of 8 is expected. */
+ size_t size = strnlen(nd_opt_captive_portal + 2, length - 2);
+ if (DIV_ROUND_UP(size + 2, 8) != length / 8)
+ return -EBADMSG;
+
+ /* Let's not return an empty buffer */
+ if (size == 0) {
+ *ret = NULL;
+ *ret_size = 0;
+ return 0;
+ }
+
+ *ret = nd_opt_captive_portal + 2;
+ *ret_size = size;
+
+ return 0;
+}
+
+static int get_pref64_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix64_info **ret) {
+ struct nd_opt_prefix64_info *ri;
+ size_t length;
+ int r;
+
+ assert(rt);
+ assert(ret);
+
+ r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREF64);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EMEDIUMTYPE;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (length != sizeof(struct nd_opt_prefix64_info))
+ return -EBADMSG;
+
+ ri = (struct nd_opt_prefix64_info *) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex);
+ if (!pref64_option_verify(ri, length))
+ return -EBADMSG;
+
+ *ret = ri;
+ return 0;
+}
+
+int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret) {
+ struct nd_opt_prefix64_info *pi;
+ struct in6_addr a = {};
+ unsigned prefixlen;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_pref64_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_router_prefix64_get_prefixlen(rt, &prefixlen);
+ if (r < 0)
+ return r;
+
+ memcpy(&a, pi->prefix, sizeof(pi->prefix));
+ in6_addr_mask(&a, prefixlen);
+ /* extra safety check for refusing malformed prefix. */
+ if (memcmp(&a, pi->prefix, sizeof(pi->prefix)) != 0)
+ return -EBADMSG;
+
+ *ret = a;
+ return 0;
+}
+
+int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
+ struct nd_opt_prefix64_info *pi;
+ uint16_t lifetime_prefix_len;
+ uint8_t prefix_len;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_pref64_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ lifetime_prefix_len = be16toh(pi->lifetime_and_plc);
+ pref64_plc_to_prefix_length(lifetime_prefix_len, &prefix_len);
+
+ *ret = prefix_len;
+ return 0;
+}
+
+int sd_ndisc_router_prefix64_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
+ struct nd_opt_prefix64_info *pi;
+ uint16_t lifetime_prefix_len;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_pref64_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ lifetime_prefix_len = be16toh(pi->lifetime_and_plc);
+
+ *ret = (lifetime_prefix_len & PREF64_SCALED_LIFETIME_MASK) * USEC_PER_SEC;
+ return 0;
+}
diff --git a/src/libsystemd-network/ndisc-router.h b/src/libsystemd-network/ndisc-router.h
new file mode 100644
index 0000000..0a55e1a
--- /dev/null
+++ b/src/libsystemd-network/ndisc-router.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include "sd-ndisc.h"
+
+#include "time-util.h"
+
+struct sd_ndisc_router {
+ unsigned n_ref;
+
+ triple_timestamp timestamp;
+ struct in6_addr address;
+
+ /* The raw packet size. The data is appended to the object, accessible via NDIS_ROUTER_RAW() */
+ size_t raw_size;
+
+ /* The current read index for the iterative option interface */
+ size_t rindex;
+
+ uint64_t flags;
+ unsigned preference;
+ uint64_t lifetime_usec;
+
+ uint8_t hop_limit;
+ uint32_t mtu;
+ uint64_t icmp6_ratelimit_usec;
+};
+
+static inline void* NDISC_ROUTER_RAW(const sd_ndisc_router *rt) {
+ return (uint8_t*) rt + ALIGN(sizeof(sd_ndisc_router));
+}
+
+static inline void *NDISC_ROUTER_OPTION_DATA(const sd_ndisc_router *rt) {
+ return ((uint8_t*) NDISC_ROUTER_RAW(rt)) + rt->rindex;
+}
+
+static inline uint8_t NDISC_ROUTER_OPTION_TYPE(const sd_ndisc_router *rt) {
+ return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[0];
+}
+static inline size_t NDISC_ROUTER_OPTION_LENGTH(const sd_ndisc_router *rt) {
+ return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[1] * 8;
+}
+
+sd_ndisc_router *ndisc_router_new(size_t raw_size);
+int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt);
diff --git a/src/libsystemd-network/network-common.c b/src/libsystemd-network/network-common.c
new file mode 100644
index 0000000..b639e9c
--- /dev/null
+++ b/src/libsystemd-network/network-common.c
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "env-util.h"
+#include "format-util.h"
+#include "network-common.h"
+#include "socket-util.h"
+#include "unaligned.h"
+
+int get_ifname(int ifindex, char **ifname) {
+ assert(ifname);
+
+ /* This sets ifname only when it is not set yet. */
+
+ if (*ifname)
+ return 0;
+
+ return format_ifname_alloc(ifindex, ifname);
+}
+
+usec_t unaligned_be32_sec_to_usec(const void *p, bool max_as_infinity) {
+ uint32_t s = unaligned_read_be32(ASSERT_PTR(p));
+
+ if (s == UINT32_MAX && max_as_infinity)
+ return USEC_INFINITY;
+
+ return s * USEC_PER_SEC;
+}
+
+usec_t be32_sec_to_usec(be32_t t, bool max_as_infinity) {
+ uint32_t s = be32toh(t);
+
+ if (s == UINT32_MAX && max_as_infinity)
+ return USEC_INFINITY;
+
+ return s * USEC_PER_SEC;
+}
+
+usec_t be32_msec_to_usec(be32_t t, bool max_as_infinity) {
+ uint32_t s = be32toh(t);
+
+ if (s == UINT32_MAX && max_as_infinity)
+ return USEC_INFINITY;
+
+ return s * USEC_PER_MSEC;
+}
+
+usec_t be16_sec_to_usec(be16_t t, bool max_as_infinity) {
+ uint16_t s = be16toh(t);
+
+ if (s == UINT16_MAX && max_as_infinity)
+ return USEC_INFINITY;
+
+ return s * USEC_PER_SEC;
+}
+
+be32_t usec_to_be32_sec(usec_t t) {
+ if (t == USEC_INFINITY)
+ /* Some settings, e.g. a lifetime of an address, UINT32_MAX is handled as infinity. so let's
+ * map USEC_INFINITY to UINT32_MAX. */
+ return htobe32(UINT32_MAX);
+
+ if (t >= (UINT32_MAX - 1) * USEC_PER_SEC)
+ /* Finite but too large. Let's use the largest (or off-by-one from the largest) finite value. */
+ return htobe32(UINT32_MAX - 1);
+
+ return htobe32((uint32_t) DIV_ROUND_UP(t, USEC_PER_SEC));
+}
+
+be32_t usec_to_be32_msec(usec_t t) {
+ if (t == USEC_INFINITY)
+ return htobe32(UINT32_MAX);
+
+ if (t >= (UINT32_MAX - 1) * USEC_PER_MSEC)
+ return htobe32(UINT32_MAX - 1);
+
+ return htobe32((uint32_t) DIV_ROUND_UP(t, USEC_PER_MSEC));
+}
+
+be16_t usec_to_be16_sec(usec_t t) {
+ if (t == USEC_INFINITY)
+ return htobe16(UINT16_MAX);
+
+ if (t >= (UINT16_MAX - 1) * USEC_PER_SEC)
+ return htobe16(UINT16_MAX - 1);
+
+ return htobe16((uint16_t) DIV_ROUND_UP(t, USEC_PER_SEC));
+}
+
+usec_t time_span_to_stamp(usec_t span, usec_t base) {
+ /* Typically, 0 lifetime (timespan) indicates the corresponding configuration (address or so) must be
+ * dropped. So, when the timespan is zero, here we return 0 rather than 'base'. This makes the caller
+ * easily understand that the configuration needs to be dropped immediately. */
+ if (span == 0)
+ return 0;
+
+ return usec_add(base, span);
+}
+
+bool network_test_mode_enabled(void) {
+ static int test_mode = -1;
+ int r;
+
+ if (test_mode < 0) {
+ r = getenv_bool("SYSTEMD_NETWORK_TEST_MODE");
+ if (r < 0) {
+ if (r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_NETWORK_TEST_MODE environment variable, ignoring: %m");
+
+ test_mode = false;
+ } else
+ test_mode = r;
+ }
+
+ return test_mode;
+}
+
+triple_timestamp* triple_timestamp_from_cmsg(triple_timestamp *t, struct msghdr *mh) {
+ assert(t);
+ assert(mh);
+
+ struct timeval *tv = CMSG_FIND_AND_COPY_DATA(mh, SOL_SOCKET, SCM_TIMESTAMP, struct timeval);
+ if (tv)
+ return triple_timestamp_from_realtime(t, timeval_load(tv));
+
+ return triple_timestamp_now(t);
+}
diff --git a/src/libsystemd-network/network-common.h b/src/libsystemd-network/network-common.h
new file mode 100644
index 0000000..1750f18
--- /dev/null
+++ b/src/libsystemd-network/network-common.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/socket.h>
+
+#include "log-link.h"
+#include "sparse-endian.h"
+#include "time-util.h"
+
+#define log_interface_prefix_full_errno_zerook(prefix, type, val, error, fmt, ...) \
+ ({ \
+ int _e = (error); \
+ if (DEBUG_LOGGING) { \
+ const char *_n = NULL; \
+ type *_v = (val); \
+ \
+ if (_v) \
+ (void) type##_get_ifname(_v, &_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);
+
+usec_t unaligned_be32_sec_to_usec(const void *p, bool max_as_infinity);
+usec_t be32_sec_to_usec(be32_t t, bool max_as_infinity);
+usec_t be32_msec_to_usec(be32_t t, bool max_as_infinity);
+usec_t be16_sec_to_usec(be16_t t, bool max_as_infinity);
+be32_t usec_to_be32_sec(usec_t t);
+be32_t usec_to_be32_msec(usec_t t);
+be16_t usec_to_be16_sec(usec_t t);
+usec_t time_span_to_stamp(usec_t span, usec_t base);
+
+bool network_test_mode_enabled(void);
+
+triple_timestamp* triple_timestamp_from_cmsg(triple_timestamp *t, struct msghdr *mh);
+#define TRIPLE_TIMESTAMP_FROM_CMSG(mh) \
+ triple_timestamp_from_cmsg(&(triple_timestamp) {}, mh)
diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c
new file mode 100644
index 0000000..c8aa021
--- /dev/null
+++ b/src/libsystemd-network/network-internal.c
@@ -0,0 +1,239 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <arpa/inet.h>
+#include <linux/if.h>
+#include <netinet/ether.h>
+
+#include "sd-ndisc.h"
+
+#include "alloc-util.h"
+#include "dhcp-lease-internal.h"
+#include "extract-word.h"
+#include "hexdecoct.h"
+#include "in-addr-util.h"
+#include "log.h"
+#include "network-internal.h"
+#include "parse-util.h"
+
+size_t serialize_in_addrs(FILE *f,
+ const struct in_addr *addresses,
+ size_t size,
+ bool *with_leading_space,
+ bool (*predicate)(const struct in_addr *addr)) {
+ assert(f);
+ assert(addresses);
+
+ size_t count = 0;
+ bool _space = false;
+ if (!with_leading_space)
+ with_leading_space = &_space;
+
+ for (size_t i = 0; i < size; i++) {
+ if (predicate && !predicate(&addresses[i]))
+ continue;
+
+ if (*with_leading_space)
+ fputc(' ', f);
+ fputs(IN4_ADDR_TO_STRING(&addresses[i]), f);
+ count++;
+ *with_leading_space = true;
+ }
+
+ return count;
+}
+
+int deserialize_in_addrs(struct in_addr **ret, const char *string) {
+ _cleanup_free_ struct in_addr *addresses = NULL;
+ int size = 0;
+
+ assert(ret);
+ assert(string);
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ struct in_addr *new_addresses;
+ int r;
+
+ r = extract_first_word(&string, &word, NULL, 0);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ new_addresses = reallocarray(addresses, size + 1, sizeof(struct in_addr));
+ if (!new_addresses)
+ return -ENOMEM;
+ else
+ addresses = new_addresses;
+
+ r = inet_pton(AF_INET, word, &(addresses[size]));
+ if (r <= 0)
+ continue;
+
+ size++;
+ }
+
+ *ret = size > 0 ? TAKE_PTR(addresses) : NULL;
+
+ return size;
+}
+
+void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size, bool *with_leading_space) {
+ assert(f);
+ assert(addresses);
+ assert(size);
+
+ bool _space = false;
+ if (!with_leading_space)
+ with_leading_space = &_space;
+
+ for (size_t i = 0; i < size; i++) {
+ if (*with_leading_space)
+ fputc(' ', f);
+ fputs(IN6_ADDR_TO_STRING(&addresses[i]), f);
+ *with_leading_space = true;
+ }
+}
+
+int deserialize_in6_addrs(struct in6_addr **ret, const char *string) {
+ _cleanup_free_ struct in6_addr *addresses = NULL;
+ int size = 0;
+
+ assert(ret);
+ assert(string);
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ struct in6_addr *new_addresses;
+ int r;
+
+ r = extract_first_word(&string, &word, NULL, 0);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ new_addresses = reallocarray(addresses, size + 1, sizeof(struct in6_addr));
+ if (!new_addresses)
+ return -ENOMEM;
+ else
+ addresses = new_addresses;
+
+ r = inet_pton(AF_INET6, word, &(addresses[size]));
+ if (r <= 0)
+ continue;
+
+ size++;
+ }
+
+ *ret = TAKE_PTR(addresses);
+
+ return size;
+}
+
+void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) {
+ assert(f);
+ assert(key);
+ assert(routes);
+ assert(size);
+
+ fprintf(f, "%s=", key);
+
+ for (size_t i = 0; i < size; i++) {
+ struct in_addr dest, gw;
+ uint8_t length;
+
+ assert_se(sd_dhcp_route_get_destination(routes[i], &dest) >= 0);
+ assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0);
+ assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0);
+
+ fprintf(f, "%s,%s%s",
+ IN4_ADDR_PREFIX_TO_STRING(&dest, length),
+ IN4_ADDR_TO_STRING(&gw),
+ i < size - 1 ? " ": "");
+ }
+
+ fputs("\n", f);
+}
+
+int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, const char *string) {
+ _cleanup_free_ struct sd_dhcp_route *routes = NULL;
+ size_t size = 0;
+
+ assert(ret);
+ assert(ret_size);
+ assert(string);
+
+ /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ char *tok, *tok_end;
+ unsigned n;
+ int r;
+
+ r = extract_first_word(&string, &word, NULL, 0);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (!GREEDY_REALLOC(routes, size + 1))
+ return -ENOMEM;
+
+ tok = word;
+
+ /* get the subnet */
+ tok_end = strchr(tok, '/');
+ if (!tok_end)
+ continue;
+ *tok_end = '\0';
+
+ r = inet_aton(tok, &routes[size].dst_addr);
+ if (r == 0)
+ continue;
+
+ tok = tok_end + 1;
+
+ /* get the prefixlen */
+ tok_end = strchr(tok, ',');
+ if (!tok_end)
+ continue;
+
+ *tok_end = '\0';
+
+ r = safe_atou(tok, &n);
+ if (r < 0 || n > 32)
+ continue;
+
+ routes[size].dst_prefixlen = (uint8_t) n;
+ tok = tok_end + 1;
+
+ /* get the gateway */
+ r = inet_aton(tok, &routes[size].gw_addr);
+ if (r == 0)
+ continue;
+
+ size++;
+ }
+
+ *ret_size = size;
+ *ret = TAKE_PTR(routes);
+
+ return 0;
+}
+
+int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size) {
+ _cleanup_free_ char *hex_buf = NULL;
+
+ assert(f);
+ assert(key);
+ assert(data);
+
+ hex_buf = hexmem(data, size);
+ if (!hex_buf)
+ return -ENOMEM;
+
+ fprintf(f, "%s=%s\n", key, hex_buf);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h
new file mode 100644
index 0000000..5aa225e
--- /dev/null
+++ b/src/libsystemd-network/network-internal.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "sd-dhcp-lease.h"
+
+size_t serialize_in_addrs(FILE *f,
+ const struct in_addr *addresses,
+ size_t size,
+ bool *with_leading_space,
+ bool (*predicate)(const struct in_addr *addr));
+int deserialize_in_addrs(struct in_addr **addresses, const char *string);
+void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses,
+ size_t size,
+ bool *with_leading_space);
+int deserialize_in6_addrs(struct in6_addr **addresses, const char *string);
+
+/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */
+struct sd_dhcp_route;
+struct sd_dhcp_lease;
+
+void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route **routes, size_t size);
+int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, const char *string);
+
+/* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */
+int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size);
+
+int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file);
+int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file);
diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h
new file mode 100644
index 0000000..d6cec90
--- /dev/null
+++ b/src/libsystemd-network/radv-internal.h
@@ -0,0 +1,222 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2017 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/icmp6.h>
+
+#include "sd-radv.h"
+
+#include "list.h"
+#include "ndisc-protocol.h"
+#include "network-common.h"
+#include "sparse-endian.h"
+#include "time-util.h"
+
+/* RFC 4861 section 6.2.1.
+ * MaxRtrAdvInterval
+ * The maximum time allowed between sending unsolicited multicast Router Advertisements from the
+ * interface, in seconds. MUST be no less than 4 seconds and no greater than 1800 seconds.
+ * Default: 600 seconds */
+#define RADV_MIN_MAX_TIMEOUT_USEC (4 * USEC_PER_SEC)
+#define RADV_MAX_MAX_TIMEOUT_USEC (1800 * USEC_PER_SEC)
+#define RADV_DEFAULT_MAX_TIMEOUT_USEC (600 * USEC_PER_SEC)
+/* RFC 4861 section 6.2.1.
+ * MinRtrAdvInterval
+ * The minimum time allowed between sending unsolicited multicast Router Advertisements from the
+ * interface, in seconds. MUST be no less than 3 seconds and no greater than .75 * MaxRtrAdvInterval.
+ * Default: 0.33 * MaxRtrAdvInterval If MaxRtrAdvInterval >= 9 seconds; otherwise, the Default is
+ * MaxRtrAdvInterval (Note, this should be a typo. We use 0.75 * MaxRtrAdvInterval). */
+#define RADV_MIN_MIN_TIMEOUT_USEC (3 * USEC_PER_SEC)
+/* RFC 4861 section 6.2.4.
+ * AdvDefaultLifetime
+ * The value to be placed in the Router Lifetime field of Router Advertisements sent from the interface,
+ * in seconds. MUST be either zero or between MaxRtrAdvInterval and 9000 seconds. A value of zero
+ * indicates that the router is not to be used as a default router. These limits may be overridden by
+ * specific documents that describe how IPv6 operates over different link layers. For instance, in a
+ * point-to-point link the peers may have enough information about the number and status of devices at
+ * the other end so that advertisements are needed less frequently.
+ * Default: 3 * MaxRtrAdvInterval */
+#define RADV_MIN_ROUTER_LIFETIME_USEC RADV_MIN_MAX_TIMEOUT_USEC
+#define RADV_MAX_ROUTER_LIFETIME_USEC (9000 * USEC_PER_SEC)
+#define RADV_DEFAULT_ROUTER_LIFETIME_USEC (3 * RADV_DEFAULT_MAX_TIMEOUT_USEC)
+/* RFC 4861 section 4.2.
+ * Retrans Timer
+ * 32-bit unsigned integer. The time, in milliseconds. */
+#define RADV_MAX_RETRANSMIT_USEC (UINT32_MAX * USEC_PER_MSEC)
+/* draft-ietf-6man-slaac-renum-02 section 4.1.1.
+ * AdvPreferredLifetime: max(AdvDefaultLifetime, 3 * MaxRtrAdvInterval)
+ * AdvValidLifetime: 2 * AdvPreferredLifetime */
+#define RADV_DEFAULT_PREFERRED_LIFETIME_USEC CONST_MAX(RADV_DEFAULT_ROUTER_LIFETIME_USEC, 3 * RADV_DEFAULT_MAX_TIMEOUT_USEC)
+#define RADV_DEFAULT_VALID_LIFETIME_USEC (2 * RADV_DEFAULT_PREFERRED_LIFETIME_USEC)
+/* RFC 4861 section 10.
+ * MAX_INITIAL_RTR_ADVERT_INTERVAL 16 seconds
+ * MAX_INITIAL_RTR_ADVERTISEMENTS 3 transmissions
+ * MAX_FINAL_RTR_ADVERTISEMENTS 3 transmissions
+ * MIN_DELAY_BETWEEN_RAS 3 seconds
+ * MAX_RA_DELAY_TIME .5 seconds */
+#define RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC (16 * USEC_PER_SEC)
+#define RADV_MAX_INITIAL_RTR_ADVERTISEMENTS 3
+#define RADV_MAX_FINAL_RTR_ADVERTISEMENTS 3
+#define RADV_MIN_DELAY_BETWEEN_RAS 3
+#define RADV_MAX_RA_DELAY_TIME_USEC (500 * USEC_PER_MSEC)
+/* From RFC 8781 section 4.1
+ * By default, the value of the Scaled Lifetime field SHOULD be set to the lesser of 3 x MaxRtrAdvInterval */
+#define RADV_PREF64_DEFAULT_LIFETIME_USEC (3 * RADV_DEFAULT_MAX_TIMEOUT_USEC)
+
+#define RADV_RDNSS_MAX_LIFETIME_USEC (UINT32_MAX * USEC_PER_SEC)
+#define RADV_DNSSL_MAX_LIFETIME_USEC (UINT32_MAX * USEC_PER_SEC)
+/* rfc6275 7.4 Neighbor Discovery Home Agent Lifetime.
+ * The default value is the same as the Router Lifetime.
+ * The maximum value corresponds to 18.2 hours. 0 MUST NOT be used. */
+#define RADV_HOME_AGENT_MAX_LIFETIME_USEC (UINT16_MAX * USEC_PER_SEC)
+
+#define RADV_OPT_ROUTE_INFORMATION 24
+#define RADV_OPT_RDNSS 25
+#define RADV_OPT_DNSSL 31
+/* Pref64 option type (RFC8781, section 4) */
+#define RADV_OPT_PREF64 38
+
+enum RAdvState {
+ RADV_STATE_IDLE = 0,
+ RADV_STATE_ADVERTISING = 1,
+};
+typedef enum RAdvState RAdvState;
+
+struct sd_radv_opt_dns {
+ uint8_t type;
+ uint8_t length;
+ uint16_t reserved;
+ be32_t lifetime;
+} _packed_;
+
+struct sd_radv {
+ unsigned n_ref;
+ RAdvState state;
+
+ int ifindex;
+ char *ifname;
+
+ sd_event *event;
+ int event_priority;
+
+ struct ether_addr mac_addr;
+ uint8_t hop_limit;
+ uint8_t flags;
+ uint32_t mtu;
+ usec_t retransmit_usec;
+ usec_t lifetime_usec; /* timespan */
+
+ int fd;
+ unsigned ra_sent;
+ sd_event_source *recv_event_source;
+ sd_event_source *timeout_event_source;
+
+ unsigned n_prefixes;
+ LIST_HEAD(sd_radv_prefix, prefixes);
+
+ unsigned n_route_prefixes;
+ LIST_HEAD(sd_radv_route_prefix, route_prefixes);
+
+ unsigned n_pref64_prefixes;
+ LIST_HEAD(sd_radv_pref64_prefix, pref64_prefixes);
+
+ size_t n_rdnss;
+ struct sd_radv_opt_dns *rdnss;
+ struct sd_radv_opt_dns *dnssl;
+
+ /* Mobile IPv6 extension: Home Agent Info. */
+ struct nd_opt_home_agent_info home_agent;
+};
+
+#define radv_prefix_opt__contents { \
+ uint8_t type; \
+ uint8_t length; \
+ uint8_t prefixlen; \
+ uint8_t flags; \
+ be32_t lifetime_valid; \
+ be32_t lifetime_preferred; \
+ uint32_t reserved; \
+ struct in6_addr in6_addr; \
+}
+
+struct radv_prefix_opt radv_prefix_opt__contents;
+
+/* We need the opt substructure to be packed, because we use it in send(). But
+ * if we use _packed_, this means that the structure cannot be used directly in
+ * normal code in general, because the fields might not be properly aligned.
+ * But in this particular case, the structure is defined in a way that gives
+ * proper alignment, even without the explicit _packed_ attribute. To appease
+ * the compiler we use the "unpacked" structure, but we also verify that
+ * structure contains no holes, so offsets are the same when _packed_ is used.
+ */
+struct radv_prefix_opt__packed radv_prefix_opt__contents _packed_;
+assert_cc(sizeof(struct radv_prefix_opt) == sizeof(struct radv_prefix_opt__packed));
+
+struct sd_radv_prefix {
+ unsigned n_ref;
+
+ struct radv_prefix_opt opt;
+
+ LIST_FIELDS(struct sd_radv_prefix, prefix);
+
+ /* These are timespans, NOT points in time. */
+ usec_t lifetime_valid_usec;
+ usec_t lifetime_preferred_usec;
+ /* These are points in time specified with clock_boottime_or_monotonic(), NOT timespans. */
+ usec_t valid_until;
+ usec_t preferred_until;
+};
+
+#define radv_route_prefix_opt__contents { \
+ uint8_t type; \
+ uint8_t length; \
+ uint8_t prefixlen; \
+ uint8_t flags_reserved; \
+ be32_t lifetime; \
+ struct in6_addr in6_addr; \
+}
+
+struct radv_route_prefix_opt radv_route_prefix_opt__contents;
+
+struct radv_route_prefix_opt__packed radv_route_prefix_opt__contents _packed_;
+assert_cc(sizeof(struct radv_route_prefix_opt) == sizeof(struct radv_route_prefix_opt__packed));
+
+struct sd_radv_route_prefix {
+ unsigned n_ref;
+
+ struct radv_route_prefix_opt opt;
+
+ LIST_FIELDS(struct sd_radv_route_prefix, prefix);
+
+ /* This is a timespan, NOT a point in time. */
+ usec_t lifetime_usec;
+ /* This is a point in time specified with clock_boottime_or_monotonic(), NOT a timespan. */
+ usec_t valid_until;
+};
+
+struct sd_radv_pref64_prefix {
+ unsigned n_ref;
+
+ struct nd_opt_prefix64_info opt;
+
+ struct in6_addr in6_addr;
+ uint8_t prefixlen;
+
+ usec_t lifetime_usec;
+
+ LIST_FIELDS(struct sd_radv_pref64_prefix, prefix);
+};
+
+#define log_radv_errno(radv, error, fmt, ...) \
+ log_interface_prefix_full_errno( \
+ "RADV: ", \
+ sd_radv, radv, \
+ error, fmt, ##__VA_ARGS__)
+#define log_radv(radv, fmt, ...) \
+ log_interface_prefix_full_errno_zerook( \
+ "RADV: ", \
+ sd_radv, radv, \
+ 0, fmt, ##__VA_ARGS__)
diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c
new file mode 100644
index 0000000..24bcd74
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp-client.c
@@ -0,0 +1,2568 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <linux/if_infiniband.h>
+
+#include "sd-dhcp-client.h"
+
+#include "alloc-util.h"
+#include "device-util.h"
+#include "dhcp-client-internal.h"
+#include "dhcp-identifier.h"
+#include "dhcp-lease-internal.h"
+#include "dhcp-network.h"
+#include "dhcp-option.h"
+#include "dhcp-packet.h"
+#include "dns-domain.h"
+#include "ether-addr-util.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "hostname-util.h"
+#include "iovec-util.h"
+#include "memory-util.h"
+#include "network-common.h"
+#include "random-util.h"
+#include "set.h"
+#include "sort-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+#include "utf8.h"
+#include "web-util.h"
+
+#define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN) /* Arbitrary limit */
+#define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN)
+
+#define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC)
+#define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE)
+
+#define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report
+ * transient failure. */
+
+typedef struct sd_dhcp_client_id {
+ uint8_t type;
+ union {
+ struct {
+ /* 0: Generic (non-LL) (RFC 2132) */
+ uint8_t data[MAX_CLIENT_ID_LEN];
+ } _packed_ gen;
+ struct {
+ /* 1: Ethernet Link-Layer (RFC 2132) */
+ uint8_t haddr[ETH_ALEN];
+ } _packed_ eth;
+ struct {
+ /* 2 - 254: ARP/Link-Layer (RFC 2132) */
+ uint8_t haddr[0];
+ } _packed_ ll;
+ struct {
+ /* 255: Node-specific (RFC 4361) */
+ be32_t iaid;
+ struct duid duid;
+ } _packed_ ns;
+ struct {
+ uint8_t data[MAX_CLIENT_ID_LEN];
+ } _packed_ raw;
+ };
+} _packed_ sd_dhcp_client_id;
+
+struct sd_dhcp_client {
+ unsigned n_ref;
+
+ DHCPState state;
+ sd_event *event;
+ int event_priority;
+ sd_event_source *timeout_resend;
+
+ int ifindex;
+ char *ifname;
+
+ sd_device *dev;
+
+ int fd;
+ uint16_t port;
+ union sockaddr_union link;
+ sd_event_source *receive_message;
+ bool request_broadcast;
+ Set *req_opts;
+ bool anonymize;
+ bool rapid_commit;
+ be32_t last_addr;
+ struct hw_addr_data hw_addr;
+ struct hw_addr_data bcast_addr;
+ uint16_t arp_type;
+ sd_dhcp_client_id client_id;
+ size_t client_id_len;
+ char *hostname;
+ char *vendor_class_identifier;
+ char *mudurl;
+ char **user_class;
+ uint32_t mtu;
+ usec_t fallback_lease_lifetime;
+ uint32_t xid;
+ usec_t start_time;
+ usec_t t1_time;
+ usec_t t2_time;
+ usec_t expire_time;
+ uint64_t discover_attempt;
+ uint64_t request_attempt;
+ uint64_t max_discover_attempts;
+ uint64_t max_request_attempts;
+ OrderedHashmap *extra_options;
+ OrderedHashmap *vendor_options;
+ sd_event_source *timeout_t1;
+ sd_event_source *timeout_t2;
+ sd_event_source *timeout_expire;
+ sd_event_source *timeout_ipv6_only_mode;
+ sd_dhcp_client_callback_t callback;
+ void *userdata;
+ sd_dhcp_client_callback_t state_callback;
+ void *state_userdata;
+ sd_dhcp_lease *lease;
+ usec_t start_delay;
+ int ip_service_type;
+ int socket_priority;
+ bool socket_priority_set;
+ bool ipv6_acquired;
+};
+
+static const uint8_t default_req_opts[] = {
+ SD_DHCP_OPTION_SUBNET_MASK,
+ SD_DHCP_OPTION_ROUTER,
+ SD_DHCP_OPTION_HOST_NAME,
+ SD_DHCP_OPTION_DOMAIN_NAME,
+ SD_DHCP_OPTION_DOMAIN_NAME_SERVER,
+};
+
+/* RFC7844 section 3:
+ MAY contain the Parameter Request List option.
+ RFC7844 section 3.6:
+ The client intending to protect its privacy SHOULD only request a
+ minimal number of options in the PRL and SHOULD also randomly shuffle
+ the ordering of option codes in the PRL. If this random ordering
+ cannot be implemented, the client MAY order the option codes in the
+ PRL by option code number (lowest to highest).
+*/
+/* NOTE: using PRL options that Windows 10 RFC7844 implementation uses */
+static const uint8_t default_req_opts_anonymize[] = {
+ SD_DHCP_OPTION_SUBNET_MASK, /* 1 */
+ SD_DHCP_OPTION_ROUTER, /* 3 */
+ SD_DHCP_OPTION_DOMAIN_NAME_SERVER, /* 6 */
+ SD_DHCP_OPTION_DOMAIN_NAME, /* 15 */
+ SD_DHCP_OPTION_ROUTER_DISCOVERY, /* 31 */
+ SD_DHCP_OPTION_STATIC_ROUTE, /* 33 */
+ SD_DHCP_OPTION_VENDOR_SPECIFIC, /* 43 */
+ SD_DHCP_OPTION_NETBIOS_NAME_SERVER, /* 44 */
+ SD_DHCP_OPTION_NETBIOS_NODE_TYPE, /* 46 */
+ SD_DHCP_OPTION_NETBIOS_SCOPE, /* 47 */
+ SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, /* 121 */
+ SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, /* 249 */
+ SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */
+};
+
+static int client_receive_message_raw(
+ sd_event_source *s,
+ int fd,
+ uint32_t revents,
+ void *userdata);
+static int client_receive_message_udp(
+ sd_event_source *s,
+ int fd,
+ uint32_t revents,
+ void *userdata);
+static void client_stop(sd_dhcp_client *client, int error);
+static int client_restart(sd_dhcp_client *client);
+
+int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret) {
+ const sd_dhcp_client_id *client_id = data;
+ _cleanup_free_ char *t = NULL;
+ int r = 0;
+
+ assert_return(data, -EINVAL);
+ assert_return(len >= 1, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ len -= 1;
+ if (len > MAX_CLIENT_ID_LEN)
+ return -EINVAL;
+
+ switch (client_id->type) {
+ case 0:
+ if (utf8_is_printable((char *) client_id->gen.data, len))
+ r = asprintf(&t, "%.*s", (int) len, client_id->gen.data);
+ else
+ r = asprintf(&t, "DATA");
+ break;
+ case 1:
+ if (len == sizeof_field(sd_dhcp_client_id, eth))
+ r = asprintf(&t, "%02x:%02x:%02x:%02x:%02x:%02x",
+ client_id->eth.haddr[0],
+ client_id->eth.haddr[1],
+ client_id->eth.haddr[2],
+ client_id->eth.haddr[3],
+ client_id->eth.haddr[4],
+ client_id->eth.haddr[5]);
+ else
+ r = asprintf(&t, "ETHER");
+ break;
+ case 2 ... 254:
+ r = asprintf(&t, "ARP/LL");
+ break;
+ case 255:
+ if (len < sizeof(uint32_t))
+ r = asprintf(&t, "IAID/DUID");
+ else {
+ uint32_t iaid = be32toh(client_id->ns.iaid);
+ /* TODO: check and stringify DUID */
+ r = asprintf(&t, "IAID:0x%x/DUID", iaid);
+ }
+ break;
+ }
+ if (r < 0)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(t);
+ return 0;
+}
+
+int dhcp_client_set_state_callback(
+ sd_dhcp_client *client,
+ sd_dhcp_client_callback_t cb,
+ void *userdata) {
+
+ assert_return(client, -EINVAL);
+
+ client->state_callback = cb;
+ client->state_userdata = userdata;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_callback(
+ sd_dhcp_client *client,
+ sd_dhcp_client_callback_t cb,
+ void *userdata) {
+
+ assert_return(client, -EINVAL);
+
+ client->callback = cb;
+ client->userdata = userdata;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ client->request_broadcast = broadcast;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ switch (option) {
+
+ case SD_DHCP_OPTION_PAD:
+ case SD_DHCP_OPTION_OVERLOAD:
+ case SD_DHCP_OPTION_MESSAGE_TYPE:
+ case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST:
+ case SD_DHCP_OPTION_END:
+ return -EINVAL;
+
+ default:
+ break;
+ }
+
+ return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option));
+}
+
+static int client_request_contains(sd_dhcp_client *client, uint8_t option) {
+ assert(client);
+
+ return set_contains(client->req_opts, UINT8_TO_PTR(option));
+}
+
+int sd_dhcp_client_set_request_address(
+ sd_dhcp_client *client,
+ const struct in_addr *last_addr) {
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ if (last_addr)
+ client->last_addr = last_addr->s_addr;
+ else
+ client->last_addr = INADDR_ANY;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_ifindex(sd_dhcp_client *client, int ifindex) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+ assert_return(ifindex > 0, -EINVAL);
+
+ client->ifindex = ifindex;
+ return 0;
+}
+
+int sd_dhcp_client_set_ifname(sd_dhcp_client *client, const char *ifname) {
+ assert_return(client, -EINVAL);
+ assert_return(ifname, -EINVAL);
+
+ if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
+ return -EINVAL;
+
+ return free_and_strdup(&client->ifname, ifname);
+}
+
+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;
+
+ if (ret)
+ *ret = client->ifname;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_mac(
+ sd_dhcp_client *client,
+ const uint8_t *hw_addr,
+ const uint8_t *bcast_addr,
+ size_t addr_len,
+ uint16_t arp_type) {
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+ assert_return(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND), -EINVAL);
+ assert_return(hw_addr, -EINVAL);
+ assert_return(addr_len == (arp_type == ARPHRD_ETHER ? ETH_ALEN : INFINIBAND_ALEN), -EINVAL);
+
+ client->arp_type = arp_type;
+ hw_addr_set(&client->hw_addr, hw_addr, addr_len);
+ hw_addr_set(&client->bcast_addr, bcast_addr, bcast_addr ? addr_len : 0);
+
+ return 0;
+}
+
+int sd_dhcp_client_get_client_id(
+ sd_dhcp_client *client,
+ uint8_t *ret_type,
+ const uint8_t **ret_data,
+ size_t *ret_data_len) {
+
+ assert_return(client, -EINVAL);
+
+ if (client->client_id_len > 0) {
+ if (client->client_id_len <= offsetof(sd_dhcp_client_id, raw.data))
+ return -EINVAL;
+
+ if (ret_type)
+ *ret_type = client->client_id.type;
+ if (ret_data)
+ *ret_data = client->client_id.raw.data;
+ if (ret_data_len)
+ *ret_data_len = client->client_id_len - offsetof(sd_dhcp_client_id, raw.data);
+ return 1;
+ }
+
+ if (ret_type)
+ *ret_type = 0;
+ if (ret_data)
+ *ret_data = NULL;
+ if (ret_data_len)
+ *ret_data_len = 0;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_client_id(
+ sd_dhcp_client *client,
+ uint8_t type,
+ const uint8_t *data,
+ size_t data_len) {
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+ assert_return(data, -EINVAL);
+ assert_return(data_len > 0 && data_len <= MAX_CLIENT_ID_LEN, -EINVAL);
+
+ /* For hardware types, log debug message about unexpected data length.
+ *
+ * Note that infiniband's INFINIBAND_ALEN is 20 bytes long, but only
+ * the last 8 bytes of the address are stable and suitable to put into
+ * the client-id. The caller is advised to account for that. */
+ if ((type == ARPHRD_ETHER && data_len != ETH_ALEN) ||
+ (type == ARPHRD_INFINIBAND && data_len != 8))
+ log_dhcp_client(client,
+ "Changing client ID to hardware type %u with unexpected address length %zu",
+ type, data_len);
+
+ client->client_id.type = type;
+ memcpy(&client->client_id.raw.data, data, data_len);
+ client->client_id_len = data_len + sizeof (client->client_id.type);
+
+ return 0;
+}
+
+/**
+ * Sets IAID and DUID. If duid is non-null, the DUID is set to duid_type + duid
+ * without further modification. Otherwise, if duid_type is supported, DUID
+ * is set based on that type. Otherwise, an error is returned.
+ */
+static int dhcp_client_set_iaid(
+ sd_dhcp_client *client,
+ bool iaid_set,
+ uint32_t iaid) {
+
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ zero(client->client_id);
+ client->client_id.type = 255;
+
+ if (iaid_set)
+ client->client_id.ns.iaid = htobe32(iaid);
+ else {
+ r = dhcp_identifier_set_iaid(client->dev, &client->hw_addr,
+ /* legacy_unstable_byteorder = */ true,
+ &client->client_id.ns.iaid);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to set IAID: %m");
+ }
+
+ return 0;
+}
+
+int sd_dhcp_client_set_iaid_duid_llt(
+ sd_dhcp_client *client,
+ bool iaid_set,
+ uint32_t iaid,
+ usec_t llt_time) {
+
+ size_t len;
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ r = dhcp_client_set_iaid(client, iaid_set, iaid);
+ if (r < 0)
+ return r;
+
+ r = dhcp_identifier_set_duid_llt(&client->hw_addr, client->arp_type, llt_time, &client->client_id.ns.duid, &len);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to set DUID-LLT: %m");
+
+ client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_iaid_duid_ll(
+ sd_dhcp_client *client,
+ bool iaid_set,
+ uint32_t iaid) {
+
+ size_t len;
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ r = dhcp_client_set_iaid(client, iaid_set, iaid);
+ if (r < 0)
+ return r;
+
+ r = dhcp_identifier_set_duid_ll(&client->hw_addr, client->arp_type, &client->client_id.ns.duid, &len);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to set DUID-LL: %m");
+
+ client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_iaid_duid_en(
+ sd_dhcp_client *client,
+ bool iaid_set,
+ uint32_t iaid) {
+
+ size_t len;
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ r = dhcp_client_set_iaid(client, iaid_set, iaid);
+ if (r < 0)
+ return r;
+
+ r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to set DUID-EN: %m");
+
+ client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_iaid_duid_uuid(
+ sd_dhcp_client *client,
+ bool iaid_set,
+ uint32_t iaid) {
+
+ size_t len;
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ r = dhcp_client_set_iaid(client, iaid_set, iaid);
+ if (r < 0)
+ return r;
+
+ r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to set DUID-UUID: %m");
+
+ client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_iaid_duid_raw(
+ sd_dhcp_client *client,
+ bool iaid_set,
+ uint32_t iaid,
+ uint16_t duid_type,
+ const uint8_t *duid,
+ size_t duid_len) {
+
+ size_t len;
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+ assert_return(duid || duid_len == 0, -EINVAL);
+
+ r = dhcp_client_set_iaid(client, iaid_set, iaid);
+ if (r < 0)
+ return r;
+
+ r = dhcp_identifier_set_duid_raw(duid_type, duid, duid_len, &client->client_id.ns.duid, &len);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to set DUID: %m");
+
+ client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_rapid_commit(sd_dhcp_client *client, bool rapid_commit) {
+ assert_return(client, -EINVAL);
+
+ client->rapid_commit = !client->anonymize && rapid_commit;
+ return 0;
+}
+
+int sd_dhcp_client_set_hostname(
+ sd_dhcp_client *client,
+ const char *hostname) {
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ /* Make sure hostnames qualify as DNS and as Linux hostnames */
+ if (hostname &&
+ !(hostname_is_valid(hostname, 0) && dns_name_is_valid(hostname) > 0))
+ return -EINVAL;
+
+ return free_and_strdup(&client->hostname, hostname);
+}
+
+int sd_dhcp_client_set_vendor_class_identifier(
+ sd_dhcp_client *client,
+ const char *vci) {
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ return free_and_strdup(&client->vendor_class_identifier, vci);
+}
+
+int sd_dhcp_client_set_mud_url(
+ sd_dhcp_client *client,
+ const char *mudurl) {
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+ assert_return(mudurl, -EINVAL);
+ assert_return(strlen(mudurl) <= 255, -EINVAL);
+ assert_return(http_url_is_valid(mudurl), -EINVAL);
+
+ return free_and_strdup(&client->mudurl, mudurl);
+}
+
+int sd_dhcp_client_set_user_class(
+ sd_dhcp_client *client,
+ char * const *user_class) {
+
+ char **s = NULL;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+ assert_return(!strv_isempty(user_class), -EINVAL);
+
+ STRV_FOREACH(p, user_class) {
+ size_t n = strlen(*p);
+
+ if (n > 255 || n == 0)
+ return -EINVAL;
+ }
+
+ s = strv_copy(user_class);
+ if (!s)
+ return -ENOMEM;
+
+ return strv_free_and_replace(client->user_class, s);
+}
+
+int sd_dhcp_client_set_client_port(
+ sd_dhcp_client *client,
+ uint16_t port) {
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ client->port = port;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) {
+ assert_return(client, -EINVAL);
+ assert_return(mtu >= DHCP_MIN_PACKET_SIZE, -ERANGE);
+
+ /* MTU may be changed by the acquired lease. Hence, we cannot require that the client is stopped here.
+ * Please do not add assertion for !sd_dhcp_client_is_running(client) here. */
+
+ client->mtu = mtu;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempts) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ client->max_discover_attempts = max_attempts;
+
+ return 0;
+}
+
+int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+ assert_return(v, -EINVAL);
+
+ r = ordered_hashmap_ensure_put(&client->extra_options, &dhcp_option_hash_ops, UINT_TO_PTR(v->option), v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp_option_ref(v);
+ return 0;
+}
+
+int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+ assert_return(v, -EINVAL);
+
+ r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp_option_hash_ops);
+ if (r < 0)
+ return -ENOMEM;
+
+ r = ordered_hashmap_put(client->vendor_options, v, v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp_option_ref(v);
+
+ return 1;
+}
+
+int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
+ assert_return(client, -EINVAL);
+
+ if (!client->lease)
+ return -EADDRNOTAVAIL;
+
+ if (ret)
+ *ret = client->lease;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_service_type(sd_dhcp_client *client, int type) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ client->ip_service_type = type;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_socket_priority(sd_dhcp_client *client, int socket_priority) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ client->socket_priority_set = true;
+ client->socket_priority = socket_priority;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_fallback_lease_lifetime(sd_dhcp_client *client, uint64_t fallback_lease_lifetime) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+ assert_return(fallback_lease_lifetime > 0, -EINVAL);
+
+ assert_cc(sizeof(usec_t) == sizeof(uint64_t));
+ client->fallback_lease_lifetime = fallback_lease_lifetime;
+
+ return 0;
+}
+
+static void client_set_state(sd_dhcp_client *client, DHCPState state) {
+ assert(client);
+
+ if (client->state == state)
+ return;
+
+ log_dhcp_client(client, "State changed: %s -> %s",
+ dhcp_state_to_string(client->state), dhcp_state_to_string(state));
+
+ client->state = state;
+
+ if (client->state_callback)
+ client->state_callback(client, state, client->state_userdata);
+}
+
+int dhcp_client_get_state(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+
+ return client->state;
+}
+
+static int client_notify(sd_dhcp_client *client, int event) {
+ assert(client);
+
+ if (client->callback)
+ return client->callback(client, event, client->userdata);
+
+ return 0;
+}
+
+static int client_initialize(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+
+ client->receive_message = sd_event_source_disable_unref(client->receive_message);
+
+ client->fd = safe_close(client->fd);
+
+ (void) event_source_disable(client->timeout_resend);
+ (void) event_source_disable(client->timeout_t1);
+ (void) event_source_disable(client->timeout_t2);
+ (void) event_source_disable(client->timeout_expire);
+ (void) event_source_disable(client->timeout_ipv6_only_mode);
+
+ client->discover_attempt = 0;
+ client->request_attempt = 0;
+
+ client_set_state(client, DHCP_STATE_STOPPED);
+ client->xid = 0;
+
+ client->lease = sd_dhcp_lease_unref(client->lease);
+
+ return 0;
+}
+
+static void client_stop(sd_dhcp_client *client, int error) {
+ assert(client);
+
+ if (error < 0)
+ log_dhcp_client_errno(client, error, "STOPPED: %m");
+ else if (error == SD_DHCP_CLIENT_EVENT_STOP)
+ log_dhcp_client(client, "STOPPED");
+ else
+ log_dhcp_client(client, "STOPPED: Unknown event");
+
+ client_notify(client, error);
+
+ client_initialize(client);
+}
+
+/* RFC2131 section 4.1:
+ * retransmission delays should include -1 to +1 sec of random 'fuzz'. */
+#define RFC2131_RANDOM_FUZZ \
+ ((int64_t)(random_u64() % (2 * USEC_PER_SEC)) - (int64_t)USEC_PER_SEC)
+
+/* RFC2131 section 4.1:
+ * for retransmission delays, timeout should start at 4s then double
+ * each attempt with max of 64s, with -1 to +1 sec of random 'fuzz' added.
+ * This assumes the first call will be using attempt 1. */
+static usec_t client_compute_request_timeout(usec_t now, uint64_t attempt) {
+ usec_t timeout = (UINT64_C(1) << MIN(attempt + 1, UINT64_C(6))) * USEC_PER_SEC;
+
+ return usec_sub_signed(usec_add(now, timeout), RFC2131_RANDOM_FUZZ);
+}
+
+/* RFC2131 section 4.4.5:
+ * T1 defaults to (0.5 * duration_of_lease).
+ * T2 defaults to (0.875 * duration_of_lease). */
+#define T1_DEFAULT(lifetime) ((lifetime) / 2)
+#define T2_DEFAULT(lifetime) (((lifetime) * 7) / 8)
+
+/* RFC2131 section 4.4.5:
+ * the client SHOULD wait one-half of the remaining time until T2 (in RENEWING state)
+ * and one-half of the remaining lease time (in REBINDING state), down to a minimum
+ * of 60 seconds.
+ * Note that while the default T1/T2 initial times do have random 'fuzz' applied,
+ * the RFC sec 4.4.5 does not mention adding any fuzz to retries. */
+static usec_t client_compute_reacquisition_timeout(usec_t now, usec_t expire) {
+ return now + MAX(usec_sub_unsigned(expire, now) / 2, 60 * USEC_PER_SEC);
+}
+
+static int cmp_uint8(const uint8_t *a, const uint8_t *b) {
+ return CMP(*a, *b);
+}
+
+static int client_message_init(
+ sd_dhcp_client *client,
+ DHCPPacket **ret,
+ uint8_t type,
+ size_t *_optlen,
+ size_t *_optoffset) {
+
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ size_t optlen, optoffset, size;
+ usec_t time_now;
+ uint16_t secs;
+ int r;
+
+ assert(client);
+ assert(client->start_time);
+ assert(ret);
+ assert(_optlen);
+ assert(_optoffset);
+ assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE));
+
+ optlen = DHCP_MIN_OPTIONS_SIZE;
+ size = sizeof(DHCPPacket) + optlen;
+
+ packet = malloc0(size);
+ if (!packet)
+ return -ENOMEM;
+
+ r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, type,
+ client->arp_type, client->hw_addr.length, client->hw_addr.bytes,
+ optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers
+ refuse to issue an DHCP lease if 'secs' is set to zero */
+ r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ return r;
+ assert(time_now >= client->start_time);
+
+ /* seconds between sending first and last DISCOVER
+ * must always be strictly positive to deal with broken servers */
+ secs = ((time_now - client->start_time) / USEC_PER_SEC) ?: 1;
+ packet->dhcp.secs = htobe16(secs);
+
+ /* RFC2131 section 4.1
+ A client that cannot receive unicast IP datagrams until its protocol
+ software has been configured with an IP address SHOULD set the
+ BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or
+ DHCPREQUEST messages that client sends. The BROADCAST bit will
+ provide a hint to the DHCP server and BOOTP relay agent to broadcast
+ any messages to the client on the client's subnet.
+
+ Note: some interfaces needs this to be enabled, but some networks
+ needs this to be disabled as broadcasts are filteretd, so this
+ needs to be configurable */
+ if (client->request_broadcast || client->arp_type != ARPHRD_ETHER)
+ packet->dhcp.flags = htobe16(0x8000);
+
+ /* Some DHCP servers will refuse to issue an DHCP lease if the Client
+ Identifier option is not set */
+ r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_CLIENT_IDENTIFIER,
+ client->client_id_len,
+ &client->client_id);
+ if (r < 0)
+ return r;
+
+ /* RFC2131 section 3.5:
+ in its initial DHCPDISCOVER or DHCPREQUEST message, a
+ client may provide the server with a list of specific
+ parameters the client is interested in. If the client
+ includes a list of parameters in a DHCPDISCOVER message,
+ it MUST include that list in any subsequent DHCPREQUEST
+ messages.
+ */
+
+ /* RFC7844 section 3:
+ MAY contain the Parameter Request List option. */
+ /* NOTE: in case that there would be an option to do not send
+ * any PRL at all, the size should be checked before sending */
+ if (!set_isempty(client->req_opts) && type != DHCP_RELEASE) {
+ _cleanup_free_ uint8_t *opts = NULL;
+ size_t n_opts, i = 0;
+ void *val;
+
+ n_opts = set_size(client->req_opts);
+ opts = new(uint8_t, n_opts);
+ if (!opts)
+ return -ENOMEM;
+
+ SET_FOREACH(val, client->req_opts)
+ opts[i++] = PTR_TO_UINT8(val);
+ assert(i == n_opts);
+
+ /* For anonymizing the request, let's sort the options. */
+ typesafe_qsort(opts, n_opts, cmp_uint8);
+
+ r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
+ n_opts, opts);
+ if (r < 0)
+ return r;
+ }
+
+ /* RFC2131 section 3.5:
+ The client SHOULD include the ’maximum DHCP message size’ option to
+ let the server know how large the server may make its DHCP messages.
+
+ Note (from ConnMan): Some DHCP servers will send bigger DHCP packets
+ than the defined default size unless the Maximum Message Size option
+ is explicitly set
+
+ RFC3442 "Requirements to Avoid Sizing Constraints":
+ Because a full routing table can be quite large, the standard 576
+ octet maximum size for a DHCP message may be too short to contain
+ some legitimate Classless Static Route options. Because of this,
+ clients implementing the Classless Static Route option SHOULD send a
+ Maximum DHCP Message Size [4] option if the DHCP client's TCP/IP
+ stack is capable of receiving larger IP datagrams. In this case, the
+ client SHOULD set the value of this option to at least the MTU of the
+ interface that the client is configuring. The client MAY set the
+ value of this option higher, up to the size of the largest UDP packet
+ it is prepared to accept. (Note that the value specified in the
+ Maximum DHCP Message Size option is the total maximum packet size,
+ including IP and UDP headers.)
+ */
+ /* RFC7844 section 3:
+ SHOULD NOT contain any other option. */
+ if (!client->anonymize && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) {
+ be16_t max_size = htobe16(MIN(client->mtu - DHCP_IP_UDP_SIZE, (uint32_t) UINT16_MAX));
+ r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
+ 2, &max_size);
+ if (r < 0)
+ return r;
+ }
+
+ *_optlen = optlen;
+ *_optoffset = optoffset;
+ *ret = TAKE_PTR(packet);
+
+ return 0;
+}
+
+static int client_append_fqdn_option(
+ DHCPMessage *message,
+ size_t optlen,
+ size_t *optoffset,
+ const char *fqdn) {
+
+ uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH];
+ int r;
+
+ buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */
+ DHCP_FQDN_FLAG_E; /* Canonical wire format */
+ buffer[1] = 0; /* RCODE1 (deprecated) */
+ buffer[2] = 0; /* RCODE2 (deprecated) */
+
+ r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false);
+ if (r > 0)
+ r = dhcp_option_append(message, optlen, optoffset, 0,
+ SD_DHCP_OPTION_FQDN, 3 + r, buffer);
+
+ return r;
+}
+
+static int dhcp_client_send_raw(
+ sd_dhcp_client *client,
+ DHCPPacket *packet,
+ size_t len) {
+
+ dhcp_packet_append_ip_headers(packet, INADDR_ANY, client->port,
+ INADDR_BROADCAST, DHCP_PORT_SERVER, len, client->ip_service_type);
+
+ return dhcp_network_send_raw_socket(client->fd, &client->link,
+ packet, len);
+}
+
+static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) {
+ sd_dhcp_option *j;
+ int r;
+
+ assert(client);
+
+ if (client->hostname) {
+ /* According to RFC 4702 "clients that send the Client FQDN option in
+ their messages MUST NOT also send the Host Name option". Just send
+ one of the two depending on the hostname type.
+ */
+ if (dns_name_is_single_label(client->hostname)) {
+ /* it is unclear from RFC 2131 if client should send hostname in
+ DHCPDISCOVER but dhclient does and so we do as well
+ */
+ r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+ SD_DHCP_OPTION_HOST_NAME,
+ strlen(client->hostname), client->hostname);
+ } else
+ r = client_append_fqdn_option(&packet->dhcp, optlen, optoffset,
+ client->hostname);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->vendor_class_identifier) {
+ r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+ SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER,
+ strlen(client->vendor_class_identifier),
+ client->vendor_class_identifier);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->mudurl) {
+ r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+ SD_DHCP_OPTION_MUD_URL,
+ strlen(client->mudurl),
+ client->mudurl);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->user_class) {
+ r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+ SD_DHCP_OPTION_USER_CLASS,
+ strv_length(client->user_class),
+ client->user_class);
+ if (r < 0)
+ return r;
+ }
+
+ ORDERED_HASHMAP_FOREACH(j, client->extra_options) {
+ r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+ j->option, j->length, j->data);
+ if (r < 0)
+ return r;
+ }
+
+ if (!ordered_hashmap_isempty(client->vendor_options)) {
+ r = dhcp_option_append(
+ &packet->dhcp, optlen, optoffset, 0,
+ SD_DHCP_OPTION_VENDOR_SPECIFIC,
+ ordered_hashmap_size(client->vendor_options), client->vendor_options);
+ if (r < 0)
+ return r;
+ }
+
+
+ return 0;
+}
+
+static int client_send_discover(sd_dhcp_client *client) {
+ _cleanup_free_ DHCPPacket *discover = NULL;
+ size_t optoffset, optlen;
+ int r;
+
+ assert(client);
+ assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING));
+
+ r = client_message_init(client, &discover, DHCP_DISCOVER,
+ &optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ /* the client may suggest values for the network address
+ and lease time in the DHCPDISCOVER message. The client may include
+ the ’requested IP address’ option to suggest that a particular IP
+ address be assigned, and may include the ’IP address lease time’
+ option to suggest the lease time it would like.
+ */
+ /* RFC7844 section 3:
+ SHOULD NOT contain any other option. */
+ if (!client->anonymize && client->last_addr != INADDR_ANY) {
+ r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
+ 4, &client->last_addr);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->rapid_commit) {
+ r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_RAPID_COMMIT, 0, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ r = client_append_common_discover_request_options(client, discover, &optoffset, optlen);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ /* We currently ignore:
+ The client SHOULD wait a random time between one and ten seconds to
+ desynchronize the use of DHCP at startup.
+ */
+ r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset);
+ if (r < 0)
+ return r;
+
+ log_dhcp_client(client, "DISCOVER");
+
+ return 0;
+}
+
+static int client_send_request(sd_dhcp_client *client) {
+ _cleanup_free_ DHCPPacket *request = NULL;
+ size_t optoffset, optlen;
+ int r;
+
+ assert(client);
+
+ r = client_message_init(client, &request, DHCP_REQUEST, &optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ switch (client->state) {
+ /* See RFC2131 section 4.3.2 (note that there is a typo in the RFC,
+ SELECTING should be REQUESTING)
+ */
+
+ case DHCP_STATE_REQUESTING:
+ /* Client inserts the address of the selected server in ’server
+ identifier’, ’ciaddr’ MUST be zero, ’requested IP address’ MUST be
+ filled in with the yiaddr value from the chosen DHCPOFFER.
+ */
+
+ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_SERVER_IDENTIFIER,
+ 4, &client->lease->server_address);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
+ 4, &client->lease->address);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case DHCP_STATE_INIT_REBOOT:
+ /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
+ option MUST be filled in with client’s notion of its previously
+ assigned address. ’ciaddr’ MUST be zero.
+ */
+ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
+ 4, &client->last_addr);
+ if (r < 0)
+ return r;
+ break;
+
+ case DHCP_STATE_RENEWING:
+ /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
+ option MUST NOT be filled in, ’ciaddr’ MUST be filled in with
+ client’s IP address.
+ */
+
+ case DHCP_STATE_REBINDING:
+ /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
+ option MUST NOT be filled in, ’ciaddr’ MUST be filled in with
+ client’s IP address.
+
+ This message MUST be broadcast to the 0xffffffff IP broadcast address.
+ */
+ request->dhcp.ciaddr = client->lease->address;
+
+ break;
+
+ case DHCP_STATE_INIT:
+ case DHCP_STATE_SELECTING:
+ case DHCP_STATE_REBOOTING:
+ case DHCP_STATE_BOUND:
+ case DHCP_STATE_STOPPED:
+ default:
+ return -EINVAL;
+ }
+
+ r = client_append_common_discover_request_options(client, request, &optoffset, optlen);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ if (client->state == DHCP_STATE_RENEWING)
+ r = dhcp_network_send_udp_socket(client->fd,
+ client->lease->server_address,
+ DHCP_PORT_SERVER,
+ &request->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+ else
+ r = dhcp_client_send_raw(client, request, sizeof(DHCPPacket) + optoffset);
+ if (r < 0)
+ return r;
+
+ switch (client->state) {
+
+ case DHCP_STATE_REQUESTING:
+ log_dhcp_client(client, "REQUEST (requesting)");
+ break;
+
+ case DHCP_STATE_INIT_REBOOT:
+ log_dhcp_client(client, "REQUEST (init-reboot)");
+ break;
+
+ case DHCP_STATE_RENEWING:
+ log_dhcp_client(client, "REQUEST (renewing)");
+ break;
+
+ case DHCP_STATE_REBINDING:
+ log_dhcp_client(client, "REQUEST (rebinding)");
+ break;
+
+ default:
+ log_dhcp_client(client, "REQUEST (invalid)");
+ break;
+ }
+
+ return 0;
+}
+
+static int client_start(sd_dhcp_client *client);
+
+static int client_timeout_resend(
+ sd_event_source *s,
+ uint64_t usec,
+ void *userdata) {
+
+ sd_dhcp_client *client = ASSERT_PTR(userdata);
+ DHCP_CLIENT_DONT_DESTROY(client);
+ usec_t time_now, next_timeout;
+ int r;
+
+ assert(s);
+ assert(client->event);
+
+ r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ goto error;
+
+ switch (client->state) {
+
+ case DHCP_STATE_RENEWING:
+ next_timeout = client_compute_reacquisition_timeout(time_now, client->t2_time);
+ break;
+
+ case DHCP_STATE_REBINDING:
+ next_timeout = client_compute_reacquisition_timeout(time_now, client->expire_time);
+ break;
+
+ case DHCP_STATE_REBOOTING:
+ /* start over as we did not receive a timely ack or nak */
+ r = client_initialize(client);
+ if (r < 0)
+ goto error;
+
+ r = client_start(client);
+ if (r < 0)
+ goto error;
+
+ log_dhcp_client(client, "REBOOTED");
+ return 0;
+
+ case DHCP_STATE_INIT:
+ case DHCP_STATE_INIT_REBOOT:
+ case DHCP_STATE_SELECTING:
+ if (client->discover_attempt >= client->max_discover_attempts)
+ goto error;
+
+ client->discover_attempt++;
+ next_timeout = client_compute_request_timeout(time_now, client->discover_attempt);
+ break;
+ case DHCP_STATE_REQUESTING:
+ case DHCP_STATE_BOUND:
+ if (client->request_attempt >= client->max_request_attempts)
+ goto error;
+
+ client->request_attempt++;
+ next_timeout = client_compute_request_timeout(time_now, client->request_attempt);
+ break;
+
+ case DHCP_STATE_STOPPED:
+ r = -EINVAL;
+ goto error;
+
+ default:
+ assert_not_reached();
+ }
+
+ r = event_reset_time(client->event, &client->timeout_resend,
+ CLOCK_BOOTTIME,
+ next_timeout, 10 * USEC_PER_MSEC,
+ client_timeout_resend, client,
+ client->event_priority, "dhcp4-resend-timer", true);
+ if (r < 0)
+ goto error;
+
+ switch (client->state) {
+ case DHCP_STATE_INIT:
+ r = client_send_discover(client);
+ if (r >= 0) {
+ client_set_state(client, DHCP_STATE_SELECTING);
+ client->discover_attempt = 0;
+ } else if (client->discover_attempt >= client->max_discover_attempts)
+ goto error;
+ break;
+
+ case DHCP_STATE_SELECTING:
+ r = client_send_discover(client);
+ if (r < 0 && client->discover_attempt >= client->max_discover_attempts)
+ goto error;
+ break;
+
+ case DHCP_STATE_INIT_REBOOT:
+ case DHCP_STATE_REQUESTING:
+ case DHCP_STATE_RENEWING:
+ case DHCP_STATE_REBINDING:
+ r = client_send_request(client);
+ if (r < 0 && client->request_attempt >= client->max_request_attempts)
+ goto error;
+
+ if (client->state == DHCP_STATE_INIT_REBOOT)
+ client_set_state(client, DHCP_STATE_REBOOTING);
+ break;
+
+ case DHCP_STATE_REBOOTING:
+ case DHCP_STATE_BOUND:
+ break;
+
+ case DHCP_STATE_STOPPED:
+ default:
+ r = -EINVAL;
+ goto error;
+ }
+
+ if (client->discover_attempt >= TRANSIENT_FAILURE_ATTEMPTS)
+ client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE);
+
+ return 0;
+
+error:
+ /* Avoid REQUEST infinite loop. Per RFC 2131 section 3.1.5: if the client receives
+ neither a DHCPACK or a DHCPNAK message after employing the retransmission algorithm,
+ the client reverts to INIT state and restarts the initialization process */
+ if (client->request_attempt >= client->max_request_attempts) {
+ log_dhcp_client(client, "Max REQUEST attempts reached. Restarting...");
+ client_restart(client);
+ return 0;
+ }
+ client_stop(client, r);
+
+ /* Errors were dealt with when stopping the client, don't spill
+ errors into the event loop handler */
+ return 0;
+}
+
+static int client_initialize_io_events(
+ sd_dhcp_client *client,
+ sd_event_io_handler_t io_callback) {
+
+ int r;
+
+ assert(client);
+ assert(client->event);
+
+ r = sd_event_add_io(client->event, &client->receive_message,
+ client->fd, EPOLLIN, io_callback,
+ client);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_priority(client->receive_message,
+ client->event_priority);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_description(client->receive_message, "dhcp4-receive-message");
+ if (r < 0)
+ goto error;
+
+error:
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+}
+
+static int client_initialize_time_events(sd_dhcp_client *client) {
+ usec_t usec = 0;
+ int r;
+
+ assert(client);
+ assert(client->event);
+
+ (void) event_source_disable(client->timeout_ipv6_only_mode);
+
+ if (client->start_delay > 0) {
+ assert_se(sd_event_now(client->event, CLOCK_BOOTTIME, &usec) >= 0);
+ usec = usec_add(usec, client->start_delay);
+ }
+
+ r = event_reset_time(client->event, &client->timeout_resend,
+ CLOCK_BOOTTIME,
+ usec, 0,
+ client_timeout_resend, client,
+ client->event_priority, "dhcp4-resend-timer", true);
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+
+}
+
+static int client_initialize_events(sd_dhcp_client *client, sd_event_io_handler_t io_callback) {
+ client_initialize_io_events(client, io_callback);
+ client_initialize_time_events(client);
+
+ return 0;
+}
+
+static int client_start_delayed(sd_dhcp_client *client) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->event, -EINVAL);
+ assert_return(client->ifindex > 0, -EINVAL);
+ assert_return(client->fd < 0, -EBUSY);
+ assert_return(client->xid == 0, -EINVAL);
+ assert_return(IN_SET(client->state, DHCP_STATE_STOPPED, DHCP_STATE_INIT_REBOOT), -EBUSY);
+
+ client->xid = random_u32();
+
+ r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
+ &client->hw_addr, &client->bcast_addr,
+ client->arp_type, client->port,
+ client->socket_priority_set, client->socket_priority);
+ if (r < 0) {
+ client_stop(client, r);
+ return r;
+ }
+ client->fd = r;
+
+ client->start_time = now(CLOCK_BOOTTIME);
+
+ if (client->state == DHCP_STATE_STOPPED)
+ client->state = DHCP_STATE_INIT;
+
+ return client_initialize_events(client, client_receive_message_raw);
+}
+
+static int client_start(sd_dhcp_client *client) {
+ client->start_delay = 0;
+ return client_start_delayed(client);
+}
+
+static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp_client *client = userdata;
+ DHCP_CLIENT_DONT_DESTROY(client);
+
+ log_dhcp_client(client, "EXPIRED");
+
+ client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
+
+ /* lease was lost, start over if not freed or stopped in callback */
+ if (client->state != DHCP_STATE_STOPPED) {
+ client_initialize(client);
+ client_start(client);
+ }
+
+ return 0;
+}
+
+static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp_client *client = ASSERT_PTR(userdata);
+ DHCP_CLIENT_DONT_DESTROY(client);
+ int r;
+
+ client->receive_message = sd_event_source_disable_unref(client->receive_message);
+ client->fd = safe_close(client->fd);
+
+ client_set_state(client, DHCP_STATE_REBINDING);
+ client->discover_attempt = 0;
+ client->request_attempt = 0;
+
+ r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
+ &client->hw_addr, &client->bcast_addr,
+ client->arp_type, client->port,
+ client->socket_priority_set, client->socket_priority);
+ if (r < 0) {
+ client_stop(client, r);
+ return 0;
+ }
+ client->fd = r;
+
+ return client_initialize_events(client, client_receive_message_raw);
+}
+
+static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp_client *client = userdata;
+ DHCP_CLIENT_DONT_DESTROY(client);
+
+ if (client->lease)
+ client_set_state(client, DHCP_STATE_RENEWING);
+ else if (client->state != DHCP_STATE_INIT)
+ client_set_state(client, DHCP_STATE_INIT_REBOOT);
+ client->discover_attempt = 0;
+ client->request_attempt = 0;
+
+ return client_initialize_time_events(client);
+}
+
+static int client_parse_message(
+ sd_dhcp_client *client,
+ DHCPMessage *message,
+ size_t len,
+ sd_dhcp_lease **ret) {
+
+ _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+ _cleanup_free_ char *error_message = NULL;
+ int r;
+
+ assert(client);
+ assert(message);
+ assert(ret);
+
+ r = dhcp_lease_new(&lease);
+ if (r < 0)
+ return r;
+
+ if (client->client_id_len > 0) {
+ r = dhcp_lease_set_client_id(lease,
+ (uint8_t *) &client->client_id,
+ client->client_id_len);
+ if (r < 0)
+ return r;
+ }
+
+ r = dhcp_option_parse(message, len, dhcp_lease_parse_options, lease, &error_message);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to parse DHCP options, ignoring: %m");
+
+ switch (client->state) {
+ case DHCP_STATE_SELECTING:
+ if (r == DHCP_ACK) {
+ if (!client->rapid_commit)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
+ "received unexpected ACK, ignoring.");
+ if (!lease->rapid_commit)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
+ "received rapid ACK without Rapid Commit option, ignoring.");
+ } else if (r == DHCP_OFFER) {
+ if (lease->rapid_commit) {
+ /* Some RFC incompliant servers provides an OFFER with a rapid commit option.
+ * See https://github.com/systemd/systemd/issues/29904.
+ * Let's support such servers gracefully. */
+ log_dhcp_client(client, "received OFFER with Rapid Commit option, ignoring.");
+ lease->rapid_commit = false;
+ }
+ if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0)
+ lease->lifetime = client->fallback_lease_lifetime;
+ } else
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
+ "received unexpected message, ignoring.");
+
+ break;
+
+ case DHCP_STATE_REBOOTING:
+ case DHCP_STATE_REQUESTING:
+ case DHCP_STATE_RENEWING:
+ case DHCP_STATE_REBINDING:
+ if (r == DHCP_NAK) {
+ if (client->lease && client->lease->server_address != lease->server_address)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
+ "NAK from unexpected server, ignoring: %s",
+ strna(error_message));
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EADDRNOTAVAIL),
+ "NAK: %s", strna(error_message));
+ }
+ if (r != DHCP_ACK)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
+ "received message was not an ACK, ignoring.");
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ lease->next_server = message->siaddr;
+ lease->address = message->yiaddr;
+
+ if (lease->address == 0 ||
+ lease->server_address == 0 ||
+ lease->lifetime == 0)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
+ "received lease lacks address, server address or lease lifetime, ignoring.");
+
+ r = dhcp_lease_set_default_subnet_mask(lease);
+ if (r < 0)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
+ "received lease lacks subnet mask, and a fallback one cannot be generated, ignoring.");
+
+ /* RFC 8925 section 3.2
+ * If the client did not include the IPv6-Only Preferred option code in the Parameter Request List in
+ * the DHCPDISCOVER or DHCPREQUEST message, it MUST ignore the IPv6-Only Preferred option in any
+ * messages received from the server. */
+ if (lease->ipv6_only_preferred_usec > 0 &&
+ !client_request_contains(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) {
+ log_dhcp_client(client, "Received message with unrequested IPv6-only preferred option, ignoring the option.");
+ lease->ipv6_only_preferred_usec = 0;
+ }
+
+ *ret = TAKE_PTR(lease);
+ return 0;
+}
+
+static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) {
+ _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+ int r;
+
+ assert(client);
+ assert(message);
+
+ r = client_parse_message(client, message, len, &lease);
+ if (r < 0)
+ return r;
+
+ dhcp_lease_set_timestamp(lease, timestamp);
+
+ dhcp_lease_unref_and_replace(client->lease, lease);
+
+ if (client->lease->rapid_commit) {
+ log_dhcp_client(client, "ACK");
+ return SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
+ }
+
+ if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0)
+ return -ENOMSG;
+
+ log_dhcp_client(client, "OFFER");
+ return 0;
+}
+
+static int client_enter_requesting_now(sd_dhcp_client *client) {
+ assert(client);
+
+ client_set_state(client, DHCP_STATE_REQUESTING);
+ client->discover_attempt = 0;
+ client->request_attempt = 0;
+
+ return event_reset_time(client->event, &client->timeout_resend,
+ CLOCK_BOOTTIME, 0, 0,
+ client_timeout_resend, client,
+ client->event_priority, "dhcp4-resend-timer",
+ /* force_reset = */ true);
+}
+
+static int client_enter_requesting_delayed(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp_client *client = ASSERT_PTR(userdata);
+ DHCP_CLIENT_DONT_DESTROY(client);
+ int r;
+
+ r = client_enter_requesting_now(client);
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+}
+
+static int client_enter_requesting(sd_dhcp_client *client) {
+ assert(client);
+ assert(client->lease);
+
+ (void) event_source_disable(client->timeout_resend);
+
+ if (client->lease->ipv6_only_preferred_usec > 0) {
+ if (client->ipv6_acquired) {
+ log_dhcp_client(client,
+ "Received an OFFER with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client.");
+ return sd_dhcp_client_stop(client);
+ }
+
+ log_dhcp_client(client,
+ "Received an OFFER with IPv6-only preferred option, delaying to send REQUEST with %s.",
+ FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC));
+
+ return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode,
+ CLOCK_BOOTTIME,
+ client->lease->ipv6_only_preferred_usec, 0,
+ client_enter_requesting_delayed, client,
+ client->event_priority, "dhcp4-ipv6-only-mode-timer",
+ /* force_reset = */ true);
+ }
+
+ return client_enter_requesting_now(client);
+}
+
+static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, size_t len) {
+ int r;
+
+ r = dhcp_option_parse(force, len, NULL, NULL, NULL);
+ if (r != DHCP_FORCERENEW)
+ return -ENOMSG;
+
+#if 0
+ log_dhcp_client(client, "FORCERENEW");
+ return 0;
+#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). */
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
+ "Received FORCERENEW, ignoring.");
+#endif
+}
+
+static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) {
+ if (a->address != b->address)
+ return false;
+
+ if (a->subnet_mask != b->subnet_mask)
+ return false;
+
+ if (a->router_size != b->router_size)
+ return false;
+
+ for (size_t i = 0; i < a->router_size; i++)
+ if (a->router[i].s_addr != b->router[i].s_addr)
+ return false;
+
+ return true;
+}
+
+static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) {
+ _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+ int r;
+
+ assert(client);
+ assert(message);
+
+ r = client_parse_message(client, message, len, &lease);
+ if (r < 0)
+ return r;
+
+ dhcp_lease_set_timestamp(lease, timestamp);
+
+ if (!client->lease)
+ r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
+ else if (lease_equal(client->lease, lease))
+ r = SD_DHCP_CLIENT_EVENT_RENEW;
+ else
+ r = SD_DHCP_CLIENT_EVENT_IP_CHANGE;
+
+ dhcp_lease_unref_and_replace(client->lease, lease);
+
+ log_dhcp_client(client, "ACK");
+ return r;
+}
+
+static int client_set_lease_timeouts(sd_dhcp_client *client) {
+ usec_t time_now;
+ int r;
+
+ assert(client);
+ assert(client->event);
+ assert(client->lease);
+ assert(client->lease->lifetime > 0);
+ assert(triple_timestamp_is_set(&client->lease->timestamp));
+
+ /* don't set timers for infinite leases */
+ if (client->lease->lifetime == USEC_INFINITY) {
+ (void) event_source_disable(client->timeout_t1);
+ (void) event_source_disable(client->timeout_t2);
+ (void) event_source_disable(client->timeout_expire);
+
+ return 0;
+ }
+
+ r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ return r;
+
+ /* verify that 0 < t2 < lifetime */
+ if (client->lease->t2 == 0 || client->lease->t2 >= client->lease->lifetime)
+ client->lease->t2 = T2_DEFAULT(client->lease->lifetime);
+ /* verify that 0 < t1 < lifetime */
+ if (client->lease->t1 == 0 || client->lease->t1 >= client->lease->t2)
+ client->lease->t1 = T1_DEFAULT(client->lease->lifetime);
+ /* now, if t1 >= t2, t1 *must* be T1_DEFAULT, since the previous check
+ * could not evaluate to false if t1 >= t2; so setting t2 to T2_DEFAULT
+ * guarantees t1 < t2. */
+ if (client->lease->t1 >= client->lease->t2)
+ client->lease->t2 = T2_DEFAULT(client->lease->lifetime);
+
+ assert(client->lease->t1 > 0);
+ assert(client->lease->t1 < client->lease->t2);
+ assert(client->lease->t2 < client->lease->lifetime);
+
+ r = sd_dhcp_lease_get_lifetime_timestamp(client->lease, CLOCK_BOOTTIME, &client->expire_time);
+ if (r < 0)
+ return r;
+ r = sd_dhcp_lease_get_t1_timestamp(client->lease, CLOCK_BOOTTIME, &client->t1_time);
+ if (r < 0)
+ return r;
+ r = sd_dhcp_lease_get_t2_timestamp(client->lease, CLOCK_BOOTTIME, &client->t2_time);
+ if (r < 0)
+ return r;
+
+ /* RFC2131 section 4.4.5:
+ * Times T1 and T2 SHOULD be chosen with some random "fuzz".
+ * Since the RFC doesn't specify here the exact 'fuzz' to use,
+ * we use the range from section 4.1: -1 to +1 sec. */
+ client->t1_time = usec_sub_signed(client->t1_time, RFC2131_RANDOM_FUZZ);
+ client->t2_time = usec_sub_signed(client->t2_time, RFC2131_RANDOM_FUZZ);
+
+ /* after fuzzing, ensure t2 is still >= t1 */
+ client->t2_time = MAX(client->t1_time, client->t2_time);
+
+ /* arm lifetime timeout */
+ r = event_reset_time(client->event, &client->timeout_expire,
+ CLOCK_BOOTTIME,
+ client->expire_time, 10 * USEC_PER_MSEC,
+ client_timeout_expire, client,
+ client->event_priority, "dhcp4-lifetime", true);
+ if (r < 0)
+ return r;
+
+ /* don't arm earlier timeouts if this has already expired */
+ if (client->expire_time <= time_now)
+ return 0;
+
+ log_dhcp_client(client, "lease expires in %s",
+ FORMAT_TIMESPAN(client->expire_time - time_now, USEC_PER_SEC));
+
+ /* arm T2 timeout */
+ r = event_reset_time(client->event, &client->timeout_t2,
+ CLOCK_BOOTTIME,
+ client->t2_time, 10 * USEC_PER_MSEC,
+ client_timeout_t2, client,
+ client->event_priority, "dhcp4-t2-timeout", true);
+ if (r < 0)
+ return r;
+
+ /* don't arm earlier timeout if this has already expired */
+ if (client->t2_time <= time_now)
+ return 0;
+
+ log_dhcp_client(client, "T2 expires in %s",
+ FORMAT_TIMESPAN(client->t2_time - time_now, USEC_PER_SEC));
+
+ /* arm T1 timeout */
+ r = event_reset_time(client->event, &client->timeout_t1,
+ CLOCK_BOOTTIME,
+ client->t1_time, 10 * USEC_PER_MSEC,
+ client_timeout_t1, client,
+ client->event_priority, "dhcp4-t1-timer", true);
+ if (r < 0)
+ return r;
+
+ if (client->t1_time > time_now)
+ log_dhcp_client(client, "T1 expires in %s",
+ FORMAT_TIMESPAN(client->t1_time - time_now, USEC_PER_SEC));
+
+ return 0;
+}
+
+static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) {
+ int r;
+
+ assert(client);
+
+ if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING))
+ notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
+
+ client_set_state(client, DHCP_STATE_BOUND);
+ client->discover_attempt = 0;
+ client->request_attempt = 0;
+
+ client->last_addr = client->lease->address;
+
+ r = client_set_lease_timeouts(client);
+ if (r < 0)
+ log_dhcp_client_errno(client, r, "could not set lease timeouts: %m");
+
+ r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "could not bind UDP socket: %m");
+
+ client->receive_message = sd_event_source_disable_unref(client->receive_message);
+ close_and_replace(client->fd, r);
+ client_initialize_io_events(client, client_receive_message_udp);
+
+ client_notify(client, notify_event);
+
+ return 0;
+}
+
+static int client_enter_bound_delayed(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp_client *client = ASSERT_PTR(userdata);
+ DHCP_CLIENT_DONT_DESTROY(client);
+ int r;
+
+ r = client_enter_bound_now(client, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE);
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+}
+
+static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
+ assert(client);
+ assert(client->lease);
+
+ client->start_delay = 0;
+ (void) event_source_disable(client->timeout_resend);
+
+ /* RFC 8925 section 3.2
+ * If the client is in the INIT-REBOOT state, it SHOULD stop the DHCPv4 configuration process or
+ * disable the IPv4 stack completely for V6ONLY_WAIT seconds or until the network attachment event,
+ * whichever happens first.
+ *
+ * In the below, the condition uses REBOOTING, instead of INIT-REBOOT, as the client state has
+ * already transitioned from INIT-REBOOT to REBOOTING after sending a DHCPREQUEST message. */
+ if (client->state == DHCP_STATE_REBOOTING && client->lease->ipv6_only_preferred_usec > 0) {
+ if (client->ipv6_acquired) {
+ log_dhcp_client(client,
+ "Received an ACK with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client.");
+ return sd_dhcp_client_stop(client);
+ }
+
+ log_dhcp_client(client,
+ "Received an ACK with IPv6-only preferred option, delaying to enter bound state with %s.",
+ FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC));
+
+ return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode,
+ CLOCK_BOOTTIME,
+ client->lease->ipv6_only_preferred_usec, 0,
+ client_enter_bound_delayed, client,
+ client->event_priority, "dhcp4-ipv6-only-mode",
+ /* force_reset = */ true);
+ }
+
+ return client_enter_bound_now(client, notify_event);
+}
+
+static int client_restart(sd_dhcp_client *client) {
+ int r;
+ assert(client);
+
+ client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
+
+ r = client_initialize(client);
+ if (r < 0)
+ return r;
+
+ r = client_start_delayed(client);
+ if (r < 0)
+ return r;
+
+ 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;
+}
+
+static int client_verify_message_header(sd_dhcp_client *client, DHCPMessage *message, size_t len) {
+ const uint8_t *expected_chaddr = NULL;
+ uint8_t expected_hlen = 0;
+
+ assert(client);
+ assert(message);
+
+ if (len < sizeof(DHCPMessage))
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
+ "Too small to be a DHCP message, ignoring.");
+
+ if (be32toh(message->magic) != DHCP_MAGIC_COOKIE)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
+ "Not a DHCP message, ignoring.");
+
+ if (message->op != BOOTREPLY)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
+ "Not a BOOTREPLY message, ignoring.");
+
+ if (message->htype != client->arp_type)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
+ "Packet type does not match client type, ignoring.");
+
+ if (client->arp_type == ARPHRD_ETHER) {
+ expected_hlen = ETH_ALEN;
+ expected_chaddr = client->hw_addr.bytes;
+ }
+
+ if (message->hlen != expected_hlen)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
+ "Received packet hlen (%u) does not match expected (%u), ignoring.",
+ message->hlen, expected_hlen);
+
+ if (memcmp_safe(message->chaddr, expected_chaddr, expected_hlen))
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
+ "Received chaddr does not match expected, ignoring.");
+
+ if (client->state != DHCP_STATE_BOUND &&
+ be32toh(message->xid) != client->xid)
+ /* in BOUND state, we may receive FORCERENEW with xid set by server,
+ so ignore the xid in this case */
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
+ "Received xid (%u) does not match expected (%u), ignoring.",
+ be32toh(message->xid), client->xid);
+
+ return 0;
+}
+
+static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) {
+ DHCP_CLIENT_DONT_DESTROY(client);
+ int r;
+
+ assert(client);
+ assert(message);
+ assert(timestamp);
+
+ if (client_verify_message_header(client, message, len) < 0)
+ return 0;
+
+ switch (client->state) {
+ case DHCP_STATE_SELECTING:
+
+ r = client_handle_offer_or_rapid_ack(client, message, len, timestamp);
+ if (ERRNO_IS_NEG_RESOURCE(r))
+ return r;
+ if (r == -EADDRNOTAVAIL)
+ /* got a rapid NAK, let's restart the client */
+ return client_restart(client);
+ if (r < 0)
+ return 0; /* invalid message, let's ignore it */
+
+ if (client->lease->rapid_commit)
+ /* got a successful rapid commit */
+ return client_enter_bound(client, r);
+
+ return client_enter_requesting(client);
+
+ case DHCP_STATE_REBOOTING:
+ case DHCP_STATE_REQUESTING:
+ case DHCP_STATE_RENEWING:
+ case DHCP_STATE_REBINDING:
+
+ r = client_handle_ack(client, message, len, timestamp);
+ if (ERRNO_IS_NEG_RESOURCE(r))
+ return r;
+ if (r == -EADDRNOTAVAIL)
+ /* got a NAK, let's restart the client */
+ return client_restart(client);
+ if (r < 0)
+ return 0; /* invalid message, let's ignore it */
+
+ return client_enter_bound(client, r);
+
+ case DHCP_STATE_BOUND:
+ r = client_handle_forcerenew(client, message, len);
+ if (ERRNO_IS_NEG_RESOURCE(r))
+ return r;
+ if (r < 0)
+ return 0; /* invalid message, let's ignore it */
+
+ return client_timeout_t1(NULL, 0, client);
+
+ case DHCP_STATE_INIT:
+ case DHCP_STATE_INIT_REBOOT:
+ log_dhcp_client(client, "Unexpectedly receive message without sending any requests, ignoring.");
+ return 0;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 0;
+}
+
+static int client_receive_message_udp(
+ sd_event_source *s,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+
+ sd_dhcp_client *client = ASSERT_PTR(userdata);
+ _cleanup_free_ DHCPMessage *message = NULL;
+ ssize_t len, buflen;
+ /* This needs to be initialized with zero. See #20741. */
+ CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL) control = {};
+ struct iovec iov;
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ int r;
+
+ assert(s);
+
+ buflen = next_datagram_size_fd(fd);
+ if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
+ return 0;
+ if (buflen < 0) {
+ log_dhcp_client_errno(client, buflen, "Failed to determine datagram size to read, ignoring: %m");
+ return 0;
+ }
+
+ message = malloc0(buflen);
+ if (!message)
+ return -ENOMEM;
+
+ iov = IOVEC_MAKE(message, buflen);
+
+ len = recvmsg_safe(fd, &msg, MSG_DONTWAIT);
+ if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len))
+ return 0;
+ if (len < 0) {
+ log_dhcp_client_errno(client, len, "Could not receive message from UDP socket, ignoring: %m");
+ return 0;
+ }
+
+ log_dhcp_client(client, "Received message from UDP socket, processing.");
+ r = client_handle_message(client, message, len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg));
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+}
+
+static int client_receive_message_raw(
+ sd_event_source *s,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+
+ sd_dhcp_client *client = ASSERT_PTR(userdata);
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ /* This needs to be initialized with zero. See #20741. */
+ CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL +
+ CMSG_SPACE(sizeof(struct tpacket_auxdata))) control = {};
+ struct iovec iov = {};
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ bool checksum = true;
+ ssize_t buflen, len;
+ int r;
+
+ assert(s);
+
+ buflen = next_datagram_size_fd(fd);
+ if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
+ return 0;
+ if (buflen < 0) {
+ log_dhcp_client_errno(client, buflen, "Failed to determine datagram size to read, ignoring: %m");
+ return 0;
+ }
+
+ packet = malloc0(buflen);
+ if (!packet)
+ return -ENOMEM;
+
+ iov = IOVEC_MAKE(packet, buflen);
+
+ len = recvmsg_safe(fd, &msg, 0);
+ if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len))
+ return 0;
+ if (len < 0) {
+ log_dhcp_client_errno(client, len, "Could not receive message from raw socket, ignoring: %m");
+ return 0;
+ }
+
+ struct tpacket_auxdata *aux = CMSG_FIND_DATA(&msg, SOL_PACKET, PACKET_AUXDATA, struct tpacket_auxdata);
+ if (aux)
+ checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
+
+ if (dhcp_packet_verify_headers(packet, len, checksum, client->port) < 0)
+ return 0;
+
+ len -= DHCP_IP_UDP_SIZE;
+
+ log_dhcp_client(client, "Received message from RAW socket, processing.");
+ r = client_handle_message(client, &packet->dhcp, len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg));
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+}
+
+int sd_dhcp_client_send_renew(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+ assert_return(sd_dhcp_client_is_running(client), -ESTALE);
+ assert_return(client->fd >= 0, -EINVAL);
+
+ if (client->state != DHCP_STATE_BOUND)
+ return 0;
+
+ assert(client->lease);
+
+ client->start_delay = 0;
+ client->discover_attempt = 1;
+ client->request_attempt = 1;
+ client_set_state(client, DHCP_STATE_RENEWING);
+
+ return client_initialize_time_events(client);
+}
+
+int sd_dhcp_client_is_running(sd_dhcp_client *client) {
+ if (!client)
+ return 0;
+
+ return client->state != DHCP_STATE_STOPPED;
+}
+
+int sd_dhcp_client_start(sd_dhcp_client *client) {
+ int r;
+
+ assert_return(client, -EINVAL);
+
+ /* Note, do not reset the flag in client_initialize(), as it is also called on expire. */
+ client->ipv6_acquired = false;
+
+ r = client_initialize(client);
+ if (r < 0)
+ return r;
+
+ /* If no client identifier exists, construct an RFC 4361-compliant one */
+ if (client->client_id_len == 0) {
+ r = sd_dhcp_client_set_iaid_duid_en(client, /* iaid_set = */ false, /* iaid = */ 0);
+ if (r < 0)
+ return r;
+ }
+
+ /* RFC7844 section 3.3:
+ SHOULD perform a complete four-way handshake, starting with a
+ DHCPDISCOVER, to obtain a new address lease. If the client can
+ ascertain that this is exactly the same network to which it was
+ previously connected, and if the link-layer address did not change,
+ the client MAY issue a DHCPREQUEST to try to reclaim the current
+ address. */
+ if (client->last_addr && !client->anonymize)
+ client_set_state(client, DHCP_STATE_INIT_REBOOT);
+
+ r = client_start(client);
+ if (r >= 0)
+ log_dhcp_client(client, "STARTED on ifindex %i", client->ifindex);
+
+ return r;
+}
+
+int sd_dhcp_client_send_release(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+ assert_return(sd_dhcp_client_is_running(client), -ESTALE);
+ assert_return(client->lease, -EUNATCH);
+
+ _cleanup_free_ DHCPPacket *release = NULL;
+ size_t optoffset, optlen;
+ int r;
+
+ r = client_message_init(client, &release, DHCP_RELEASE, &optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ /* Fill up release IP and MAC */
+ release->dhcp.ciaddr = client->lease->address;
+ memcpy(&release->dhcp.chaddr, client->hw_addr.bytes, client->hw_addr.length);
+
+ r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ r = dhcp_network_send_udp_socket(client->fd,
+ client->lease->server_address,
+ DHCP_PORT_SERVER,
+ &release->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+ if (r < 0)
+ return r;
+
+ log_dhcp_client(client, "RELEASE");
+
+ return 0;
+}
+
+int sd_dhcp_client_send_decline(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+ assert_return(sd_dhcp_client_is_running(client), -ESTALE);
+ assert_return(client->lease, -EUNATCH);
+
+ _cleanup_free_ DHCPPacket *release = NULL;
+ size_t optoffset, optlen;
+ int r;
+
+ r = client_message_init(client, &release, DHCP_DECLINE, &optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ release->dhcp.ciaddr = client->lease->address;
+ memcpy(&release->dhcp.chaddr, client->hw_addr.bytes, client->hw_addr.length);
+
+ r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ r = dhcp_network_send_udp_socket(client->fd,
+ client->lease->server_address,
+ DHCP_PORT_SERVER,
+ &release->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+ if (r < 0)
+ return r;
+
+ log_dhcp_client(client, "DECLINE");
+
+ client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+
+ if (client->state != DHCP_STATE_STOPPED) {
+ r = sd_dhcp_client_start(client);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int sd_dhcp_client_stop(sd_dhcp_client *client) {
+ if (!client)
+ return 0;
+
+ DHCP_CLIENT_DONT_DESTROY(client);
+
+ client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+
+ return 0;
+}
+
+int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have) {
+ if (!client)
+ return 0;
+
+ /* We have already received a message with IPv6-Only preferred option, and are waiting for IPv6
+ * connectivity or timeout, let's stop the client. */
+ if (have && sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) > 0)
+ return sd_dhcp_client_stop(client);
+
+ /* Otherwise, save that the host already has IPv6 connectivity. */
+ client->ipv6_acquired = have;
+ return 0;
+}
+
+int sd_dhcp_client_interrupt_ipv6_only_mode(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+ assert_return(sd_dhcp_client_is_running(client), -ESTALE);
+ assert_return(client->fd >= 0, -EINVAL);
+
+ if (sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) <= 0)
+ return 0;
+
+ client_initialize(client);
+ return client_start(client);
+}
+
+int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!client->event, -EBUSY);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ if (event)
+ client->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&client->event);
+ if (r < 0)
+ return 0;
+ }
+
+ client->event_priority = priority;
+
+ return 0;
+}
+
+int sd_dhcp_client_detach_event(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ client->event = sd_event_unref(client->event);
+
+ return 0;
+}
+
+sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) {
+ assert_return(client, NULL);
+
+ return client->event;
+}
+
+int sd_dhcp_client_attach_device(sd_dhcp_client *client, sd_device *dev) {
+ assert_return(client, -EINVAL);
+
+ return device_unref_and_replace(client->dev, dev);
+}
+
+static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) {
+ if (!client)
+ return NULL;
+
+ 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);
+
+ sd_dhcp_client_detach_event(client);
+
+ sd_device_unref(client->dev);
+
+ set_free(client->req_opts);
+ free(client->hostname);
+ free(client->vendor_class_identifier);
+ free(client->mudurl);
+ client->user_class = strv_free(client->user_class);
+ ordered_hashmap_free(client->extra_options);
+ ordered_hashmap_free(client->vendor_options);
+ free(client->ifname);
+ return mfree(client);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client, sd_dhcp_client, dhcp_client_free);
+
+int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
+ const uint8_t *opts;
+ size_t n_opts;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = new(sd_dhcp_client, 1);
+ if (!client)
+ return -ENOMEM;
+
+ *client = (sd_dhcp_client) {
+ .n_ref = 1,
+ .state = DHCP_STATE_STOPPED,
+ .ifindex = -1,
+ .fd = -EBADF,
+ .mtu = DHCP_MIN_PACKET_SIZE,
+ .port = DHCP_PORT_CLIENT,
+ .anonymize = !!anonymize,
+ .max_discover_attempts = UINT64_MAX,
+ .max_request_attempts = 5,
+ .ip_service_type = -1,
+ };
+ /* NOTE: this could be moved to a function. */
+ if (anonymize) {
+ n_opts = ELEMENTSOF(default_req_opts_anonymize);
+ opts = default_req_opts_anonymize;
+ } else {
+ n_opts = ELEMENTSOF(default_req_opts);
+ opts = default_req_opts;
+ }
+
+ for (size_t i = 0; i < n_opts; i++) {
+ r = sd_dhcp_client_set_request_option(client, opts[i]);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(client);
+
+ return 0;
+}
+
+static const char* const dhcp_state_table[_DHCP_STATE_MAX] = {
+ [DHCP_STATE_STOPPED] = "stopped",
+ [DHCP_STATE_INIT] = "initialization",
+ [DHCP_STATE_SELECTING] = "selecting",
+ [DHCP_STATE_INIT_REBOOT] = "init-reboot",
+ [DHCP_STATE_REBOOTING] = "rebooting",
+ [DHCP_STATE_REQUESTING] = "requesting",
+ [DHCP_STATE_BOUND] = "bound",
+ [DHCP_STATE_RENEWING] = "renewing",
+ [DHCP_STATE_REBINDING] = "rebinding",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState);
diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c
new file mode 100644
index 0000000..4e3be98
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp-lease.c
@@ -0,0 +1,1607 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "sd-dhcp-lease.h"
+
+#include "alloc-util.h"
+#include "dhcp-lease-internal.h"
+#include "dhcp-option.h"
+#include "dns-domain.h"
+#include "env-file.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "hexdecoct.h"
+#include "hostname-util.h"
+#include "in-addr-util.h"
+#include "network-common.h"
+#include "network-internal.h"
+#include "parse-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+#include "tmpfile-util.h"
+#include "unaligned.h"
+
+void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *timestamp) {
+ assert(lease);
+
+ if (timestamp && triple_timestamp_is_set(timestamp))
+ lease->timestamp = *timestamp;
+ else
+ triple_timestamp_now(&lease->timestamp);
+}
+
+int sd_dhcp_lease_get_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t *ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
+ assert_return(clock_supported(clock), -EOPNOTSUPP);
+ assert_return(ret, -EINVAL);
+
+ if (!triple_timestamp_is_set(&lease->timestamp))
+ return -ENODATA;
+
+ *ret = triple_timestamp_by_clock(&lease->timestamp, clock);
+ return 0;
+}
+
+int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (lease->address == 0)
+ return -ENODATA;
+
+ addr->s_addr = lease->address;
+ return 0;
+}
+
+int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (!lease->have_broadcast)
+ return -ENODATA;
+
+ addr->s_addr = lease->broadcast;
+ return 0;
+}
+
+int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint64_t *ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (lease->lifetime <= 0)
+ return -ENODATA;
+
+ *ret = lease->lifetime;
+ return 0;
+}
+
+int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint64_t *ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (lease->t1 <= 0)
+ return -ENODATA;
+
+ *ret = lease->t1;
+ return 0;
+}
+
+int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint64_t *ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (lease->t2 <= 0)
+ return -ENODATA;
+
+ *ret = lease->t2;
+ return 0;
+}
+
+#define DEFINE_GET_TIMESTAMP(name) \
+ int sd_dhcp_lease_get_##name##_timestamp( \
+ sd_dhcp_lease *lease, \
+ clockid_t clock, \
+ uint64_t *ret) { \
+ \
+ usec_t t, timestamp; \
+ int r; \
+ \
+ assert_return(ret, -EINVAL); \
+ \
+ r = sd_dhcp_lease_get_##name(lease, &t); \
+ if (r < 0) \
+ return r; \
+ \
+ r = sd_dhcp_lease_get_timestamp(lease, clock, &timestamp); \
+ if (r < 0) \
+ return r; \
+ \
+ *ret = usec_add(t, timestamp); \
+ return 0; \
+ }
+
+DEFINE_GET_TIMESTAMP(lifetime);
+DEFINE_GET_TIMESTAMP(t1);
+DEFINE_GET_TIMESTAMP(t2);
+
+int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu) {
+ assert_return(lease, -EINVAL);
+ assert_return(mtu, -EINVAL);
+
+ if (lease->mtu <= 0)
+ return -ENODATA;
+
+ *mtu = lease->mtu;
+ return 0;
+}
+
+int sd_dhcp_lease_get_servers(
+ sd_dhcp_lease *lease,
+ sd_dhcp_lease_server_type_t what,
+ const struct in_addr **addr) {
+
+ assert_return(lease, -EINVAL);
+ assert_return(what >= 0, -EINVAL);
+ assert_return(what < _SD_DHCP_LEASE_SERVER_TYPE_MAX, -EINVAL);
+
+ if (lease->servers[what].size <= 0)
+ return -ENODATA;
+
+ if (addr)
+ *addr = lease->servers[what].addr;
+
+ return (int) lease->servers[what].size;
+}
+
+int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_DNS, addr);
+}
+int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_NTP, addr);
+}
+int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SIP, addr);
+}
+int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_POP3, addr);
+}
+int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SMTP, addr);
+}
+int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_LPR, addr);
+}
+
+int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) {
+ assert_return(lease, -EINVAL);
+ assert_return(domainname, -EINVAL);
+
+ if (!lease->domainname)
+ return -ENODATA;
+
+ *domainname = lease->domainname;
+ return 0;
+}
+
+int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname) {
+ assert_return(lease, -EINVAL);
+ assert_return(hostname, -EINVAL);
+
+ if (!lease->hostname)
+ return -ENODATA;
+
+ *hostname = lease->hostname;
+ return 0;
+}
+
+int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path) {
+ assert_return(lease, -EINVAL);
+ assert_return(root_path, -EINVAL);
+
+ if (!lease->root_path)
+ return -ENODATA;
+
+ *root_path = lease->root_path;
+ return 0;
+}
+
+int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!lease->captive_portal)
+ return -ENODATA;
+
+ *ret = lease->captive_portal;
+ return 0;
+}
+
+int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (lease->router_size <= 0)
+ return -ENODATA;
+
+ *addr = lease->router;
+ return (int) lease->router_size;
+}
+
+int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (!lease->have_subnet_mask)
+ return -ENODATA;
+
+ addr->s_addr = lease->subnet_mask;
+ return 0;
+}
+
+int sd_dhcp_lease_get_prefix(sd_dhcp_lease *lease, struct in_addr *ret_prefix, uint8_t *ret_prefixlen) {
+ struct in_addr address, netmask;
+ uint8_t prefixlen;
+ int r;
+
+ assert_return(lease, -EINVAL);
+
+ r = sd_dhcp_lease_get_address(lease, &address);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp_lease_get_netmask(lease, &netmask);
+ if (r < 0)
+ return r;
+
+ prefixlen = in4_addr_netmask_to_prefixlen(&netmask);
+
+ r = in4_addr_mask(&address, prefixlen);
+ if (r < 0)
+ return r;
+
+ if (ret_prefix)
+ *ret_prefix = address;
+ if (ret_prefixlen)
+ *ret_prefixlen = prefixlen;
+ return 0;
+}
+
+int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (lease->server_address == 0)
+ return -ENODATA;
+
+ addr->s_addr = lease->server_address;
+ return 0;
+}
+
+int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (lease->next_server == 0)
+ return -ENODATA;
+
+ addr->s_addr = lease->next_server;
+ return 0;
+}
+
+/*
+ * The returned routes array must be freed by the caller.
+ * Route objects have the same lifetime of the lease and must not be freed.
+ */
+static int dhcp_lease_get_routes(sd_dhcp_route *routes, size_t n_routes, sd_dhcp_route ***ret) {
+ assert(routes || n_routes == 0);
+
+ if (n_routes <= 0)
+ return -ENODATA;
+
+ if (ret) {
+ sd_dhcp_route **buf;
+
+ buf = new(sd_dhcp_route*, n_routes);
+ if (!buf)
+ return -ENOMEM;
+
+ for (size_t i = 0; i < n_routes; i++)
+ buf[i] = &routes[i];
+
+ *ret = buf;
+ }
+
+ return (int) n_routes;
+}
+
+int sd_dhcp_lease_get_static_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret) {
+ assert_return(lease, -EINVAL);
+
+ return dhcp_lease_get_routes(lease->static_routes, lease->n_static_routes, ret);
+}
+
+int sd_dhcp_lease_get_classless_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret) {
+ assert_return(lease, -EINVAL);
+
+ return dhcp_lease_get_routes(lease->classless_routes, lease->n_classless_routes, ret);
+}
+
+int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) {
+ size_t r;
+
+ assert_return(lease, -EINVAL);
+ assert_return(domains, -EINVAL);
+
+ r = strv_length(lease->search_domains);
+ if (r > 0) {
+ *domains = lease->search_domains;
+ return (int) r;
+ }
+
+ return -ENODATA;
+}
+
+int sd_dhcp_lease_get_6rd(
+ sd_dhcp_lease *lease,
+ uint8_t *ret_ipv4masklen,
+ uint8_t *ret_prefixlen,
+ struct in6_addr *ret_prefix,
+ const struct in_addr **ret_br_addresses,
+ size_t *ret_n_br_addresses) {
+
+ assert_return(lease, -EINVAL);
+
+ if (lease->sixrd_n_br_addresses <= 0)
+ return -ENODATA;
+
+ if (ret_ipv4masklen)
+ *ret_ipv4masklen = lease->sixrd_ipv4masklen;
+ if (ret_prefixlen)
+ *ret_prefixlen = lease->sixrd_prefixlen;
+ if (ret_prefix)
+ *ret_prefix = lease->sixrd_prefix;
+ if (ret_br_addresses)
+ *ret_br_addresses = lease->sixrd_br_addresses;
+ if (ret_n_br_addresses)
+ *ret_n_br_addresses = lease->sixrd_n_br_addresses;
+
+ return 0;
+}
+
+int sd_dhcp_lease_has_6rd(sd_dhcp_lease *lease) {
+ return lease && lease->sixrd_n_br_addresses > 0;
+}
+
+int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) {
+ assert_return(lease, -EINVAL);
+ assert_return(data, -EINVAL);
+ assert_return(data_len, -EINVAL);
+
+ if (lease->vendor_specific_len <= 0)
+ return -ENODATA;
+
+ *data = lease->vendor_specific;
+ *data_len = lease->vendor_specific_len;
+ return 0;
+}
+
+static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) {
+ struct sd_dhcp_raw_option *option;
+
+ assert(lease);
+
+ while ((option = LIST_POP(options, lease->private_options))) {
+ free(option->data);
+ free(option);
+ }
+
+ free(lease->root_path);
+ free(lease->router);
+ free(lease->timezone);
+ free(lease->hostname);
+ free(lease->domainname);
+ free(lease->captive_portal);
+
+ for (sd_dhcp_lease_server_type_t i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++)
+ free(lease->servers[i].addr);
+
+ free(lease->static_routes);
+ free(lease->classless_routes);
+ free(lease->client_id);
+ free(lease->vendor_specific);
+ strv_free(lease->search_domains);
+ free(lease->sixrd_br_addresses);
+ return mfree(lease);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_lease, sd_dhcp_lease, dhcp_lease_free);
+
+static int lease_parse_be32_seconds(const uint8_t *option, size_t len, bool max_as_infinity, usec_t *ret) {
+ assert(option);
+ assert(ret);
+
+ if (len != 4)
+ return -EINVAL;
+
+ *ret = unaligned_be32_sec_to_usec(option, max_as_infinity);
+ return 0;
+}
+
+static int lease_parse_u16(const uint8_t *option, size_t len, uint16_t *ret, uint16_t min) {
+ assert(option);
+ assert(ret);
+
+ if (len != 2)
+ return -EINVAL;
+
+ *ret = unaligned_read_be16((be16_t*) option);
+ if (*ret < min)
+ *ret = min;
+
+ return 0;
+}
+
+static int lease_parse_be32(const uint8_t *option, size_t len, be32_t *ret) {
+ assert(option);
+ assert(ret);
+
+ if (len != 4)
+ return -EINVAL;
+
+ memcpy(ret, option, 4);
+ return 0;
+}
+
+static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) {
+ _cleanup_free_ char *name = NULL, *normalized = NULL;
+ int r;
+
+ assert(option);
+ assert(ret);
+
+ r = dhcp_option_parse_string(option, len, &name);
+ if (r < 0)
+ return r;
+ if (!name) {
+ *ret = mfree(*ret);
+ return 0;
+ }
+
+ r = dns_name_normalize(name, 0, &normalized);
+ if (r < 0)
+ return r;
+
+ if (is_localhost(normalized))
+ return -EINVAL;
+
+ if (dns_name_is_root(normalized))
+ return -EINVAL;
+
+ free_and_replace(*ret, normalized);
+
+ return 0;
+}
+
+static int lease_parse_captive_portal(const uint8_t *option, size_t len, char **ret) {
+ _cleanup_free_ char *uri = NULL;
+ int r;
+
+ assert(option);
+ assert(ret);
+
+ r = dhcp_option_parse_string(option, len, &uri);
+ if (r < 0)
+ return r;
+ if (uri && !in_charset(uri, URI_VALID))
+ return -EINVAL;
+
+ return free_and_replace(*ret, uri);
+}
+
+static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) {
+ assert(option || len == 0);
+ assert(ret);
+ assert(n_ret);
+
+ if (len <= 0) {
+ *ret = mfree(*ret);
+ *n_ret = 0;
+ } else {
+ size_t n_addresses;
+ struct in_addr *addresses;
+
+ if (len % 4 != 0)
+ return -EINVAL;
+
+ n_addresses = len / 4;
+
+ addresses = newdup(struct in_addr, option, n_addresses);
+ if (!addresses)
+ return -ENOMEM;
+
+ free_and_replace(*ret, addresses);
+ *n_ret = n_addresses;
+ }
+
+ return 0;
+}
+
+static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) {
+ assert(option || len == 0);
+ assert(ret);
+ assert(n_ret);
+
+ if (len <= 0)
+ return -EINVAL;
+
+ /* The SIP record is like the other, regular server records, but prefixed with a single "encoding"
+ * byte that is either 0 or 1. We only support it to be 1 for now. Let's drop it and parse it like
+ * the other fields */
+
+ if (option[0] != 1) { /* We only support IP address encoding for now */
+ *ret = mfree(*ret);
+ *n_ret = 0;
+ return 0;
+ }
+
+ return lease_parse_in_addrs(option + 1, len - 1, ret, n_ret);
+}
+
+static int lease_parse_static_routes(sd_dhcp_lease *lease, const uint8_t *option, size_t len) {
+ int r;
+
+ assert(lease);
+ assert(option || len <= 0);
+
+ if (len % 8 != 0)
+ return -EINVAL;
+
+ while (len >= 8) {
+ struct in_addr dst, gw;
+ uint8_t prefixlen;
+
+ assert_se(lease_parse_be32(option, 4, &dst.s_addr) >= 0);
+ option += 4;
+
+ assert_se(lease_parse_be32(option, 4, &gw.s_addr) >= 0);
+ option += 4;
+
+ len -= 8;
+
+ r = in4_addr_default_prefixlen(&dst, &prefixlen);
+ if (r < 0) {
+ log_debug("sd-dhcp-lease: cannot determine class of received static route, ignoring.");
+ continue;
+ }
+
+ (void) in4_addr_mask(&dst, prefixlen);
+
+ if (!GREEDY_REALLOC(lease->static_routes, lease->n_static_routes + 1))
+ return -ENOMEM;
+
+ lease->static_routes[lease->n_static_routes++] = (struct sd_dhcp_route) {
+ .dst_addr = dst,
+ .gw_addr = gw,
+ .dst_prefixlen = prefixlen,
+ };
+ }
+
+ return 0;
+}
+
+/* parses RFC3442 Classless Static Route Option */
+static int lease_parse_classless_routes(sd_dhcp_lease *lease, const uint8_t *option, size_t len) {
+ assert(lease);
+ assert(option || len <= 0);
+
+ /* option format: (subnet-mask-width significant-subnet-octets gateway-ip) */
+
+ while (len > 0) {
+ uint8_t prefixlen, dst_octets;
+ struct in_addr dst = {}, gw;
+
+ prefixlen = *option;
+ option++;
+ len--;
+
+ dst_octets = DIV_ROUND_UP(prefixlen, 8);
+
+ /* can't have more than 4 octets in IPv4 */
+ if (dst_octets > 4 || len < dst_octets)
+ return -EINVAL;
+
+ memcpy(&dst, option, dst_octets);
+ option += dst_octets;
+ len -= dst_octets;
+
+ if (len < 4)
+ return -EINVAL;
+
+ assert_se(lease_parse_be32(option, 4, &gw.s_addr) >= 0);
+ option += 4;
+ len -= 4;
+
+ if (!GREEDY_REALLOC(lease->classless_routes, lease->n_classless_routes + 1))
+ return -ENOMEM;
+
+ lease->classless_routes[lease->n_classless_routes++] = (struct sd_dhcp_route) {
+ .dst_addr = dst,
+ .gw_addr = gw,
+ .dst_prefixlen = prefixlen,
+ };
+ }
+
+ return 0;
+}
+
+static int lease_parse_6rd(sd_dhcp_lease *lease, const uint8_t *option, size_t len) {
+ uint8_t ipv4masklen, prefixlen;
+ struct in6_addr prefix;
+ _cleanup_free_ struct in_addr *br_addresses = NULL;
+ size_t n_br_addresses;
+
+ assert(lease);
+ assert(option);
+
+ /* See RFC 5969 Section 7.1.1 */
+
+ if (lease->sixrd_n_br_addresses > 0)
+ /* Multiple 6rd option?? */
+ return -EINVAL;
+
+ /* option-length: The length of the DHCP option in octets (22 octets with one BR IPv4 address). */
+ if (len < 2 + sizeof(struct in6_addr) + sizeof(struct in_addr) ||
+ (len - 2 - sizeof(struct in6_addr)) % sizeof(struct in_addr) != 0)
+ return -EINVAL;
+
+ /* IPv4MaskLen: The number of high-order bits that are identical across all CE IPv4 addresses
+ * within a given 6rd domain. This may be any value between 0 and 32. Any value
+ * greater than 32 is invalid. */
+ ipv4masklen = option[0];
+ if (ipv4masklen > 32)
+ return -EINVAL;
+
+ /* 6rdPrefixLen: The IPv6 prefix length of the SP's 6rd IPv6 prefix in number of bits. For the
+ * purpose of bounds checking by DHCP option processing, the sum of
+ * (32 - IPv4MaskLen) + 6rdPrefixLen MUST be less than or equal to 128. */
+ prefixlen = option[1];
+ if (32 - ipv4masklen + prefixlen > 128)
+ return -EINVAL;
+
+ /* 6rdPrefix: The service provider's 6rd IPv6 prefix represented as a 16-octet IPv6 address.
+ * The bits in the prefix after the 6rdPrefixlen number of bits are reserved and
+ * MUST be initialized to zero by the sender and ignored by the receiver. */
+ memcpy(&prefix, option + 2, sizeof(struct in6_addr));
+ (void) in6_addr_mask(&prefix, prefixlen);
+
+ /* 6rdBRIPv4Address: One or more IPv4 addresses of the 6rd Border Relays for a given 6rd domain. */
+ n_br_addresses = (len - 2 - sizeof(struct in6_addr)) / sizeof(struct in_addr);
+ br_addresses = newdup(struct in_addr, option + 2 + sizeof(struct in6_addr), n_br_addresses);
+ if (!br_addresses)
+ return -ENOMEM;
+
+ lease->sixrd_ipv4masklen = ipv4masklen;
+ lease->sixrd_prefixlen = prefixlen;
+ lease->sixrd_prefix = prefix;
+ lease->sixrd_br_addresses = TAKE_PTR(br_addresses);
+ lease->sixrd_n_br_addresses = n_br_addresses;
+
+ return 0;
+}
+
+int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata) {
+ sd_dhcp_lease *lease = ASSERT_PTR(userdata);
+ int r;
+
+ switch (code) {
+
+ case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
+ r = lease_parse_be32_seconds(option, len, /* max_as_infinity = */ true, &lease->lifetime);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse lease time, ignoring: %m");
+
+ break;
+
+ case SD_DHCP_OPTION_SERVER_IDENTIFIER:
+ r = lease_parse_be32(option, len, &lease->server_address);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse server identifier, ignoring: %m");
+
+ break;
+
+ case SD_DHCP_OPTION_SUBNET_MASK:
+ r = lease_parse_be32(option, len, &lease->subnet_mask);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse subnet mask, ignoring: %m");
+ else
+ lease->have_subnet_mask = true;
+ break;
+
+ case SD_DHCP_OPTION_BROADCAST:
+ r = lease_parse_be32(option, len, &lease->broadcast);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse broadcast address, ignoring: %m");
+ else
+ lease->have_broadcast = true;
+ break;
+
+ case SD_DHCP_OPTION_ROUTER:
+ r = lease_parse_in_addrs(option, len, &lease->router, &lease->router_size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse router addresses, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_RAPID_COMMIT:
+ if (len > 0)
+ log_debug("Invalid DHCP Rapid Commit option, ignoring.");
+ lease->rapid_commit = true;
+ break;
+
+ case SD_DHCP_OPTION_DOMAIN_NAME_SERVER:
+ r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_DNS].addr, &lease->servers[SD_DHCP_LEASE_DNS].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse DNS server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_NTP_SERVER:
+ r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_NTP].addr, &lease->servers[SD_DHCP_LEASE_NTP].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse NTP server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_SIP_SERVER:
+ r = lease_parse_sip_server(option, len, &lease->servers[SD_DHCP_LEASE_SIP].addr, &lease->servers[SD_DHCP_LEASE_SIP].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse SIP server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_POP3_SERVER:
+ r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_POP3].addr, &lease->servers[SD_DHCP_LEASE_POP3].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse POP3 server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_SMTP_SERVER:
+ r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_SMTP].addr, &lease->servers[SD_DHCP_LEASE_SMTP].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse SMTP server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_LPR_SERVER:
+ r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_LPR].addr, &lease->servers[SD_DHCP_LEASE_LPR].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse LPR server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL:
+ r = lease_parse_captive_portal(option, len, &lease->captive_portal);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse captive portal, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_STATIC_ROUTE:
+ r = lease_parse_static_routes(lease, option, len);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse static routes, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_MTU_INTERFACE:
+ r = lease_parse_u16(option, len, &lease->mtu, 68);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse MTU, ignoring: %m");
+ if (lease->mtu < DHCP_MIN_PACKET_SIZE) {
+ log_debug("MTU value of %" PRIu16 " too small. Using default MTU value of %d instead.", lease->mtu, DHCP_MIN_PACKET_SIZE);
+ lease->mtu = DHCP_MIN_PACKET_SIZE;
+ }
+
+ break;
+
+ case SD_DHCP_OPTION_DOMAIN_NAME:
+ r = lease_parse_domain(option, len, &lease->domainname);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse domain name, ignoring: %m");
+ return 0;
+ }
+
+ break;
+
+ case SD_DHCP_OPTION_DOMAIN_SEARCH:
+ r = dhcp_lease_parse_search_domains(option, len, &lease->search_domains);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse Domain Search List, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_HOST_NAME:
+ r = lease_parse_domain(option, len, &lease->hostname);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse hostname, ignoring: %m");
+ return 0;
+ }
+
+ break;
+
+ case SD_DHCP_OPTION_ROOT_PATH:
+ r = dhcp_option_parse_string(option, len, &lease->root_path);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse root path, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_RENEWAL_TIME:
+ r = lease_parse_be32_seconds(option, len, /* max_as_infinity = */ true, &lease->t1);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse T1 time, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_REBINDING_TIME:
+ r = lease_parse_be32_seconds(option, len, /* max_as_infinity = */ true, &lease->t2);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse T2 time, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
+ r = lease_parse_classless_routes(lease, option, len);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse classless routes, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_TZDB_TIMEZONE: {
+ _cleanup_free_ char *tz = NULL;
+
+ r = dhcp_option_parse_string(option, len, &tz);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse timezone option, ignoring: %m");
+ return 0;
+ }
+
+ if (!timezone_is_valid(tz, LOG_DEBUG)) {
+ log_debug("Timezone is not valid, ignoring.");
+ return 0;
+ }
+
+ free_and_replace(lease->timezone, tz);
+
+ break;
+ }
+
+ case SD_DHCP_OPTION_VENDOR_SPECIFIC:
+
+ if (len <= 0)
+ lease->vendor_specific = mfree(lease->vendor_specific);
+ else {
+ void *p;
+
+ p = memdup(option, len);
+ if (!p)
+ return -ENOMEM;
+
+ free_and_replace(lease->vendor_specific, p);
+ }
+
+ lease->vendor_specific_len = len;
+ break;
+
+ case SD_DHCP_OPTION_6RD:
+ r = lease_parse_6rd(lease, option, len);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse 6rd option, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_IPV6_ONLY_PREFERRED:
+ r = lease_parse_be32_seconds(option, len, /* max_as_infinity = */ false, &lease->ipv6_only_preferred_usec);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse IPv6 only preferred option, ignoring: %m");
+
+ else if (lease->ipv6_only_preferred_usec < MIN_V6ONLY_WAIT_USEC &&
+ !network_test_mode_enabled())
+ lease->ipv6_only_preferred_usec = MIN_V6ONLY_WAIT_USEC;
+ break;
+
+ case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST:
+ r = dhcp_lease_insert_private_option(lease, code, option, len);
+ if (r < 0)
+ return r;
+
+ break;
+
+ default:
+ log_debug("Ignoring DHCP option %"PRIu8" while parsing.", code);
+ break;
+ }
+
+ return 0;
+}
+
+/* Parses compressed domain names. */
+int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains) {
+ _cleanup_strv_free_ char **names = NULL;
+ size_t pos = 0, cnt = 0;
+ int r;
+
+ assert(domains);
+ assert(option || len == 0);
+
+ if (len == 0)
+ return -EBADMSG;
+
+ while (pos < len) {
+ _cleanup_free_ char *name = NULL;
+ size_t n = 0;
+ size_t jump_barrier = pos, next_chunk = 0;
+ bool first = true;
+
+ for (;;) {
+ uint8_t c;
+ c = option[pos++];
+
+ if (c == 0) {
+ /* End of name */
+ break;
+ } else if (c <= 63) {
+ const char *label;
+
+ /* Literal label */
+ label = (const char*) (option + pos);
+ pos += c;
+ if (pos >= len)
+ return -EBADMSG;
+
+ if (!GREEDY_REALLOC(name, n + !first + DNS_LABEL_ESCAPED_MAX))
+ return -ENOMEM;
+
+ if (first)
+ first = false;
+ else
+ name[n++] = '.';
+
+ r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ n += r;
+ } else if (FLAGS_SET(c, 0xc0)) {
+ /* Pointer */
+
+ uint8_t d;
+ uint16_t ptr;
+
+ if (pos >= len)
+ return -EBADMSG;
+
+ d = option[pos++];
+ ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d;
+
+ /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */
+ if (ptr >= jump_barrier)
+ return -EBADMSG;
+ jump_barrier = ptr;
+
+ /* Save current location so we don't end up re-parsing what's parsed so far. */
+ if (next_chunk == 0)
+ next_chunk = pos;
+
+ pos = ptr;
+ } else
+ return -EBADMSG;
+ }
+
+ if (!GREEDY_REALLOC(name, n + 1))
+ return -ENOMEM;
+ name[n] = 0;
+
+ r = strv_extend(&names, name);
+ if (r < 0)
+ return r;
+
+ cnt++;
+
+ if (next_chunk != 0)
+ pos = next_chunk;
+ }
+
+ strv_free_and_replace(*domains, names);
+
+ return cnt;
+}
+
+int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) {
+ struct sd_dhcp_raw_option *option, *before = NULL;
+
+ assert(lease);
+
+ LIST_FOREACH(options, cur, lease->private_options) {
+ if (tag < cur->tag) {
+ before = cur;
+ break;
+ }
+ if (tag == cur->tag) {
+ log_debug("Ignoring duplicate option, tagged %i.", tag);
+ return 0;
+ }
+ }
+
+ option = new(struct sd_dhcp_raw_option, 1);
+ if (!option)
+ return -ENOMEM;
+
+ option->tag = tag;
+ option->length = len;
+ option->data = memdup(data, len);
+ if (!option->data) {
+ free(option);
+ return -ENOMEM;
+ }
+
+ LIST_INSERT_BEFORE(options, lease->private_options, before, option);
+ return 0;
+}
+
+int dhcp_lease_new(sd_dhcp_lease **ret) {
+ sd_dhcp_lease *lease;
+
+ lease = new0(sd_dhcp_lease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ lease->n_ref = 1;
+
+ *ret = lease;
+ return 0;
+}
+
+int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
+ _cleanup_(unlink_and_freep) char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ struct in_addr address;
+ const struct in_addr *addresses;
+ const void *client_id, *data;
+ size_t client_id_len, data_len;
+ const char *string;
+ uint16_t mtu;
+ _cleanup_free_ sd_dhcp_route **routes = NULL;
+ char **search_domains;
+ usec_t t;
+ int r;
+
+ assert(lease);
+ assert(lease_file);
+
+ r = fopen_temporary(lease_file, &f, &temp_path);
+ if (r < 0)
+ return r;
+
+ (void) fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n");
+
+ r = sd_dhcp_lease_get_address(lease, &address);
+ if (r >= 0)
+ fprintf(f, "ADDRESS=%s\n", IN4_ADDR_TO_STRING(&address));
+
+ r = sd_dhcp_lease_get_netmask(lease, &address);
+ if (r >= 0)
+ fprintf(f, "NETMASK=%s\n", IN4_ADDR_TO_STRING(&address));
+
+ r = sd_dhcp_lease_get_router(lease, &addresses);
+ if (r > 0) {
+ fputs("ROUTER=", f);
+ serialize_in_addrs(f, addresses, r, NULL, NULL);
+ fputc('\n', f);
+ }
+
+ r = sd_dhcp_lease_get_server_identifier(lease, &address);
+ if (r >= 0)
+ fprintf(f, "SERVER_ADDRESS=%s\n", IN4_ADDR_TO_STRING(&address));
+
+ r = sd_dhcp_lease_get_next_server(lease, &address);
+ if (r >= 0)
+ fprintf(f, "NEXT_SERVER=%s\n", IN4_ADDR_TO_STRING(&address));
+
+ r = sd_dhcp_lease_get_broadcast(lease, &address);
+ if (r >= 0)
+ fprintf(f, "BROADCAST=%s\n", IN4_ADDR_TO_STRING(&address));
+
+ r = sd_dhcp_lease_get_mtu(lease, &mtu);
+ if (r >= 0)
+ fprintf(f, "MTU=%" PRIu16 "\n", mtu);
+
+ r = sd_dhcp_lease_get_t1(lease, &t);
+ if (r >= 0)
+ fprintf(f, "T1=%s\n", FORMAT_TIMESPAN(t, USEC_PER_SEC));
+
+ r = sd_dhcp_lease_get_t2(lease, &t);
+ if (r >= 0)
+ fprintf(f, "T2=%s\n", FORMAT_TIMESPAN(t, USEC_PER_SEC));
+
+ r = sd_dhcp_lease_get_lifetime(lease, &t);
+ if (r >= 0)
+ fprintf(f, "LIFETIME=%s\n", FORMAT_TIMESPAN(t, USEC_PER_SEC));
+
+ r = sd_dhcp_lease_get_dns(lease, &addresses);
+ if (r > 0) {
+ fputs("DNS=", f);
+ serialize_in_addrs(f, addresses, r, NULL, NULL);
+ fputc('\n', f);
+ }
+
+ r = sd_dhcp_lease_get_ntp(lease, &addresses);
+ if (r > 0) {
+ fputs("NTP=", f);
+ serialize_in_addrs(f, addresses, r, NULL, NULL);
+ fputc('\n', f);
+ }
+
+ r = sd_dhcp_lease_get_sip(lease, &addresses);
+ if (r > 0) {
+ fputs("SIP=", f);
+ serialize_in_addrs(f, addresses, r, NULL, NULL);
+ fputc('\n', f);
+ }
+
+ r = sd_dhcp_lease_get_domainname(lease, &string);
+ if (r >= 0)
+ fprintf(f, "DOMAINNAME=%s\n", string);
+
+ r = sd_dhcp_lease_get_search_domains(lease, &search_domains);
+ if (r > 0) {
+ fputs("DOMAIN_SEARCH_LIST=", f);
+ fputstrv(f, search_domains, NULL, NULL);
+ fputc('\n', f);
+ }
+
+ r = sd_dhcp_lease_get_hostname(lease, &string);
+ if (r >= 0)
+ fprintf(f, "HOSTNAME=%s\n", string);
+
+ r = sd_dhcp_lease_get_root_path(lease, &string);
+ if (r >= 0)
+ fprintf(f, "ROOT_PATH=%s\n", string);
+
+ r = sd_dhcp_lease_get_static_routes(lease, &routes);
+ if (r > 0)
+ serialize_dhcp_routes(f, "STATIC_ROUTES", routes, r);
+
+ routes = mfree(routes);
+ r = sd_dhcp_lease_get_classless_routes(lease, &routes);
+ if (r > 0)
+ serialize_dhcp_routes(f, "CLASSLESS_ROUTES", routes, r);
+
+ r = sd_dhcp_lease_get_timezone(lease, &string);
+ if (r >= 0)
+ fprintf(f, "TIMEZONE=%s\n", string);
+
+ r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len);
+ if (r >= 0) {
+ _cleanup_free_ char *client_id_hex = NULL;
+
+ client_id_hex = hexmem(client_id, client_id_len);
+ if (!client_id_hex)
+ return -ENOMEM;
+ fprintf(f, "CLIENTID=%s\n", client_id_hex);
+ }
+
+ r = sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len);
+ if (r >= 0) {
+ _cleanup_free_ char *option_hex = NULL;
+
+ option_hex = hexmem(data, data_len);
+ if (!option_hex)
+ return -ENOMEM;
+ fprintf(f, "VENDOR_SPECIFIC=%s\n", option_hex);
+ }
+
+ LIST_FOREACH(options, option, lease->private_options) {
+ char key[STRLEN("OPTION_000")+1];
+
+ xsprintf(key, "OPTION_%" PRIu8, option->tag);
+ r = serialize_dhcp_option(f, key, option->data, option->length);
+ if (r < 0)
+ return r;
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return r;
+
+ r = conservative_rename(temp_path, lease_file);
+ if (r < 0)
+ return r;
+
+ temp_path = mfree(temp_path);
+
+ return 0;
+}
+
+static char **private_options_free(char **options) {
+ if (!options)
+ return NULL;
+
+ free_many_charp(options, SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1);
+
+ return mfree(options);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(char**, private_options_free);
+
+int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
+ _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+ _cleanup_free_ char
+ *address = NULL,
+ *router = NULL,
+ *netmask = NULL,
+ *server_address = NULL,
+ *next_server = NULL,
+ *broadcast = NULL,
+ *dns = NULL,
+ *ntp = NULL,
+ *sip = NULL,
+ *pop3 = NULL,
+ *smtp = NULL,
+ *lpr = NULL,
+ *mtu = NULL,
+ *static_routes = NULL,
+ *classless_routes = NULL,
+ *domains = NULL,
+ *client_id_hex = NULL,
+ *vendor_specific_hex = NULL,
+ *lifetime = NULL,
+ *t1 = NULL,
+ *t2 = NULL;
+ _cleanup_(private_options_freep) char **options = NULL;
+
+ int r, i;
+
+ assert(lease_file);
+ assert(ret);
+
+ r = dhcp_lease_new(&lease);
+ if (r < 0)
+ return r;
+
+ options = new0(char*, SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1);
+ if (!options)
+ return -ENOMEM;
+
+ r = parse_env_file(NULL, lease_file,
+ "ADDRESS", &address,
+ "ROUTER", &router,
+ "NETMASK", &netmask,
+ "SERVER_ADDRESS", &server_address,
+ "NEXT_SERVER", &next_server,
+ "BROADCAST", &broadcast,
+ "DNS", &dns,
+ "NTP", &ntp,
+ "SIP", &sip,
+ "POP3", &pop3,
+ "SMTP", &smtp,
+ "LPR", &lpr,
+ "MTU", &mtu,
+ "DOMAINNAME", &lease->domainname,
+ "HOSTNAME", &lease->hostname,
+ "DOMAIN_SEARCH_LIST", &domains,
+ "ROOT_PATH", &lease->root_path,
+ "STATIC_ROUTES", &static_routes,
+ "CLASSLESS_ROUTES", &classless_routes,
+ "CLIENTID", &client_id_hex,
+ "TIMEZONE", &lease->timezone,
+ "VENDOR_SPECIFIC", &vendor_specific_hex,
+ "LIFETIME", &lifetime,
+ "T1", &t1,
+ "T2", &t2,
+ "OPTION_224", &options[0],
+ "OPTION_225", &options[1],
+ "OPTION_226", &options[2],
+ "OPTION_227", &options[3],
+ "OPTION_228", &options[4],
+ "OPTION_229", &options[5],
+ "OPTION_230", &options[6],
+ "OPTION_231", &options[7],
+ "OPTION_232", &options[8],
+ "OPTION_233", &options[9],
+ "OPTION_234", &options[10],
+ "OPTION_235", &options[11],
+ "OPTION_236", &options[12],
+ "OPTION_237", &options[13],
+ "OPTION_238", &options[14],
+ "OPTION_239", &options[15],
+ "OPTION_240", &options[16],
+ "OPTION_241", &options[17],
+ "OPTION_242", &options[18],
+ "OPTION_243", &options[19],
+ "OPTION_244", &options[20],
+ "OPTION_245", &options[21],
+ "OPTION_246", &options[22],
+ "OPTION_247", &options[23],
+ "OPTION_248", &options[24],
+ "OPTION_249", &options[25],
+ "OPTION_250", &options[26],
+ "OPTION_251", &options[27],
+ "OPTION_252", &options[28],
+ "OPTION_253", &options[29],
+ "OPTION_254", &options[30]);
+ if (r < 0)
+ return r;
+
+ if (address) {
+ r = inet_pton(AF_INET, address, &lease->address);
+ if (r <= 0)
+ log_debug("Failed to parse address %s, ignoring.", address);
+ }
+
+ if (router) {
+ r = deserialize_in_addrs(&lease->router, router);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize router addresses %s, ignoring: %m", router);
+ else
+ lease->router_size = r;
+ }
+
+ if (netmask) {
+ r = inet_pton(AF_INET, netmask, &lease->subnet_mask);
+ if (r <= 0)
+ log_debug("Failed to parse netmask %s, ignoring.", netmask);
+ else
+ lease->have_subnet_mask = true;
+ }
+
+ if (server_address) {
+ r = inet_pton(AF_INET, server_address, &lease->server_address);
+ if (r <= 0)
+ log_debug("Failed to parse server address %s, ignoring.", server_address);
+ }
+
+ if (next_server) {
+ r = inet_pton(AF_INET, next_server, &lease->next_server);
+ if (r <= 0)
+ log_debug("Failed to parse next server %s, ignoring.", next_server);
+ }
+
+ if (broadcast) {
+ r = inet_pton(AF_INET, broadcast, &lease->broadcast);
+ if (r <= 0)
+ log_debug("Failed to parse broadcast address %s, ignoring.", broadcast);
+ else
+ lease->have_broadcast = true;
+ }
+
+ if (dns) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_DNS].addr, dns);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize DNS servers %s, ignoring: %m", dns);
+ else
+ lease->servers[SD_DHCP_LEASE_DNS].size = r;
+ }
+
+ if (ntp) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_NTP].addr, ntp);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize NTP servers %s, ignoring: %m", ntp);
+ else
+ lease->servers[SD_DHCP_LEASE_NTP].size = r;
+ }
+
+ if (sip) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SIP].addr, sip);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize SIP servers %s, ignoring: %m", sip);
+ else
+ lease->servers[SD_DHCP_LEASE_SIP].size = r;
+ }
+
+ if (pop3) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_POP3].addr, pop3);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize POP3 server %s, ignoring: %m", pop3);
+ else
+ lease->servers[SD_DHCP_LEASE_POP3].size = r;
+ }
+
+ if (smtp) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SMTP].addr, smtp);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize SMTP server %s, ignoring: %m", smtp);
+ else
+ lease->servers[SD_DHCP_LEASE_SMTP].size = r;
+ }
+
+ if (lpr) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_LPR].addr, lpr);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize LPR server %s, ignoring: %m", lpr);
+ else
+ lease->servers[SD_DHCP_LEASE_LPR].size = r;
+ }
+
+ if (mtu) {
+ r = safe_atou16(mtu, &lease->mtu);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse MTU %s, ignoring: %m", mtu);
+ }
+
+ if (domains) {
+ _cleanup_strv_free_ char **a = NULL;
+ a = strv_split(domains, " ");
+ if (!a)
+ return -ENOMEM;
+
+ if (!strv_isempty(a))
+ lease->search_domains = TAKE_PTR(a);
+ }
+
+ if (static_routes) {
+ r = deserialize_dhcp_routes(
+ &lease->static_routes,
+ &lease->n_static_routes,
+ static_routes);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse DHCP static routes %s, ignoring: %m", static_routes);
+ }
+
+ if (classless_routes) {
+ r = deserialize_dhcp_routes(
+ &lease->classless_routes,
+ &lease->n_classless_routes,
+ classless_routes);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse DHCP classless routes %s, ignoring: %m", classless_routes);
+ }
+
+ if (lifetime) {
+ r = parse_sec(lifetime, &lease->lifetime);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse lifetime %s, ignoring: %m", lifetime);
+ }
+
+ if (t1) {
+ r = parse_sec(t1, &lease->t1);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse T1 %s, ignoring: %m", t1);
+ }
+
+ if (t2) {
+ r = parse_sec(t2, &lease->t2);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse T2 %s, ignoring: %m", t2);
+ }
+
+ if (client_id_hex) {
+ r = unhexmem(client_id_hex, SIZE_MAX, &lease->client_id, &lease->client_id_len);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex);
+ }
+
+ if (vendor_specific_hex) {
+ r = unhexmem(vendor_specific_hex, SIZE_MAX, &lease->vendor_specific, &lease->vendor_specific_len);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex);
+ }
+
+ for (i = 0; i <= SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE; i++) {
+ _cleanup_free_ void *data = NULL;
+ size_t len;
+
+ if (!options[i])
+ continue;
+
+ r = unhexmem(options[i], SIZE_MAX, &data, &len);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]);
+ continue;
+ }
+
+ r = dhcp_lease_insert_private_option(lease, SD_DHCP_OPTION_PRIVATE_BASE + i, data, len);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(lease);
+
+ return 0;
+}
+
+int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) {
+ struct in_addr address, mask;
+ int r;
+
+ assert(lease);
+
+ if (lease->have_subnet_mask)
+ return 0;
+
+ if (lease->address == 0)
+ return -ENODATA;
+
+ address.s_addr = lease->address;
+
+ /* fall back to the default subnet masks based on address class */
+ r = in4_addr_default_subnet_mask(&address, &mask);
+ if (r < 0)
+ return r;
+
+ lease->subnet_mask = mask.s_addr;
+ lease->have_subnet_mask = true;
+
+ return 0;
+}
+
+int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len) {
+ assert_return(lease, -EINVAL);
+ assert_return(client_id, -EINVAL);
+ assert_return(client_id_len, -EINVAL);
+
+ if (!lease->client_id)
+ return -ENODATA;
+
+ *client_id = lease->client_id;
+ *client_id_len = lease->client_id_len;
+
+ return 0;
+}
+
+int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len) {
+ assert_return(lease, -EINVAL);
+ assert_return(client_id || client_id_len <= 0, -EINVAL);
+
+ if (client_id_len <= 0)
+ lease->client_id = mfree(lease->client_id);
+ else {
+ void *p;
+
+ p = memdup(client_id, client_id_len);
+ if (!p)
+ return -ENOMEM;
+
+ free_and_replace(lease->client_id, p);
+ lease->client_id_len = client_id_len;
+ }
+
+ return 0;
+}
+
+int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **tz) {
+ assert_return(lease, -EINVAL);
+ assert_return(tz, -EINVAL);
+
+ if (!lease->timezone)
+ return -ENODATA;
+
+ *tz = lease->timezone;
+ return 0;
+}
+
+int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination) {
+ assert_return(route, -EINVAL);
+ assert_return(destination, -EINVAL);
+
+ *destination = route->dst_addr;
+ return 0;
+}
+
+int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length) {
+ assert_return(route, -EINVAL);
+ assert_return(length, -EINVAL);
+
+ *length = route->dst_prefixlen;
+ return 0;
+}
+
+int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) {
+ assert_return(route, -EINVAL);
+ assert_return(gateway, -EINVAL);
+
+ *gateway = route->gw_addr;
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
new file mode 100644
index 0000000..fcc5b74
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -0,0 +1,1792 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <net/if_arp.h>
+#include <sys/ioctl.h>
+
+#include "sd-dhcp-server.h"
+#include "sd-id128.h"
+
+#include "alloc-util.h"
+#include "dhcp-network.h"
+#include "dhcp-option.h"
+#include "dhcp-packet.h"
+#include "dhcp-server-internal.h"
+#include "dns-domain.h"
+#include "fd-util.h"
+#include "in-addr-util.h"
+#include "iovec-util.h"
+#include "memory-util.h"
+#include "network-common.h"
+#include "ordered-set.h"
+#include "siphash24.h"
+#include "string-util.h"
+#include "unaligned.h"
+#include "utf8.h"
+
+#define DHCP_DEFAULT_LEASE_TIME_USEC USEC_PER_HOUR
+#define DHCP_MAX_LEASE_TIME_USEC (USEC_PER_HOUR*12)
+
+DHCPLease *dhcp_lease_free(DHCPLease *lease) {
+ if (!lease)
+ return NULL;
+
+ if (lease->server) {
+ hashmap_remove_value(lease->server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease);
+ hashmap_remove_value(lease->server->bound_leases_by_client_id, &lease->client_id, lease);
+ hashmap_remove_value(lease->server->static_leases_by_address, UINT32_TO_PTR(lease->address), lease);
+ hashmap_remove_value(lease->server->static_leases_by_client_id, &lease->client_id, lease);
+ }
+
+ free(lease->client_id.data);
+ free(lease->hostname);
+ return mfree(lease);
+}
+
+/* configures the server's address and subnet, and optionally the pool's size and offset into the subnet
+ * the whole pool must fit into the subnet, and may not contain the first (any) nor last (broadcast) address
+ * moreover, the server's own address may be in the pool, and is in that case reserved in order not to
+ * accidentally hand it out */
+int sd_dhcp_server_configure_pool(
+ sd_dhcp_server *server,
+ const struct in_addr *address,
+ unsigned char prefixlen,
+ uint32_t offset,
+ uint32_t size) {
+
+ struct in_addr netmask_addr;
+ be32_t netmask;
+ uint32_t server_off, broadcast_off, size_max;
+
+ assert_return(server, -EINVAL);
+ assert_return(address, -EINVAL);
+ assert_return(address->s_addr != INADDR_ANY, -EINVAL);
+ assert_return(prefixlen <= 32, -ERANGE);
+
+ assert_se(in4_addr_prefixlen_to_netmask(&netmask_addr, prefixlen));
+ netmask = netmask_addr.s_addr;
+
+ server_off = be32toh(address->s_addr & ~netmask);
+ broadcast_off = be32toh(~netmask);
+
+ /* the server address cannot be the subnet address */
+ assert_return(server_off != 0, -ERANGE);
+
+ /* nor the broadcast address */
+ assert_return(server_off != broadcast_off, -ERANGE);
+
+ /* 0 offset means we should set a default, we skip the first (subnet) address
+ and take the next one */
+ if (offset == 0)
+ offset = 1;
+
+ size_max = (broadcast_off + 1) /* the number of addresses in the subnet */
+ - offset /* exclude the addresses before the offset */
+ - 1; /* exclude the last (broadcast) address */
+
+ /* The pool must contain at least one address */
+ assert_return(size_max >= 1, -ERANGE);
+
+ if (size != 0)
+ assert_return(size <= size_max, -ERANGE);
+ else
+ size = size_max;
+
+ if (server->address != address->s_addr || server->netmask != netmask || server->pool_size != size || server->pool_offset != offset) {
+
+ server->pool_offset = offset;
+ server->pool_size = size;
+
+ server->address = address->s_addr;
+ server->netmask = netmask;
+ server->subnet = address->s_addr & netmask;
+
+ /* Drop any leases associated with the old address range */
+ hashmap_clear(server->bound_leases_by_address);
+ hashmap_clear(server->bound_leases_by_client_id);
+
+ if (server->callback)
+ server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata);
+ }
+
+ return 0;
+}
+
+int sd_dhcp_server_is_running(sd_dhcp_server *server) {
+ if (!server)
+ return false;
+
+ return !!server->receive_message;
+}
+
+int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server) {
+ assert_return(server, -EINVAL);
+
+ return in4_addr_is_set(&server->relay_target);
+}
+
+void client_id_hash_func(const DHCPClientId *id, struct siphash *state) {
+ assert(id);
+ assert(id->length > 0);
+ assert(id->data);
+
+ siphash24_compress(&id->length, sizeof(id->length), state);
+ siphash24_compress(id->data, id->length, state);
+}
+
+int client_id_compare_func(const DHCPClientId *a, const DHCPClientId *b) {
+ int r;
+
+ assert(a->length > 0);
+ assert(a->data);
+ assert(b->length > 0);
+ assert(b->data);
+
+ r = CMP(a->length, b->length);
+ if (r != 0)
+ return r;
+
+ return memcmp(a->data, b->data, a->length);
+}
+
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ dhcp_lease_hash_ops,
+ DHCPClientId,
+ client_id_hash_func,
+ client_id_compare_func,
+ DHCPLease,
+ dhcp_lease_free);
+
+static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) {
+ assert(server);
+
+ sd_dhcp_server_stop(server);
+
+ sd_event_unref(server->event);
+
+ free(server->boot_server_name);
+ free(server->boot_filename);
+ free(server->timezone);
+
+ for (sd_dhcp_lease_server_type_t i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++)
+ free(server->servers[i].addr);
+
+ server->bound_leases_by_address = hashmap_free(server->bound_leases_by_address);
+ server->bound_leases_by_client_id = hashmap_free(server->bound_leases_by_client_id);
+ server->static_leases_by_address = hashmap_free(server->static_leases_by_address);
+ server->static_leases_by_client_id = hashmap_free(server->static_leases_by_client_id);
+
+ ordered_set_free(server->extra_options);
+ ordered_set_free(server->vendor_options);
+
+ free(server->agent_circuit_id);
+ free(server->agent_remote_id);
+
+ free(server->ifname);
+ return mfree(server);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_server, sd_dhcp_server, dhcp_server_free);
+
+int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
+ _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
+
+ assert_return(ret, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+
+ server = new(sd_dhcp_server, 1);
+ if (!server)
+ return -ENOMEM;
+
+ *server = (sd_dhcp_server) {
+ .n_ref = 1,
+ .fd_raw = -EBADF,
+ .fd = -EBADF,
+ .fd_broadcast = -EBADF,
+ .address = htobe32(INADDR_ANY),
+ .netmask = htobe32(INADDR_ANY),
+ .ifindex = ifindex,
+ .bind_to_interface = true,
+ .default_lease_time = DHCP_DEFAULT_LEASE_TIME_USEC,
+ .max_lease_time = DHCP_MAX_LEASE_TIME_USEC,
+ .rapid_commit = true,
+ };
+
+ *ret = TAKE_PTR(server);
+
+ return 0;
+}
+
+int sd_dhcp_server_set_ifname(sd_dhcp_server *server, const char *ifname) {
+ assert_return(server, -EINVAL);
+ assert_return(ifname, -EINVAL);
+
+ if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
+ return -EINVAL;
+
+ return free_and_strdup(&server->ifname, ifname);
+}
+
+int sd_dhcp_server_get_ifname(sd_dhcp_server *server, const char **ret) {
+ int r;
+
+ assert_return(server, -EINVAL);
+
+ r = get_ifname(server->ifindex, &server->ifname);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = server->ifname;
+
+ return 0;
+}
+
+int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(server, -EINVAL);
+ assert_return(!server->event, -EBUSY);
+
+ if (event)
+ server->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&server->event);
+ if (r < 0)
+ return r;
+ }
+
+ server->event_priority = priority;
+
+ return 0;
+}
+
+int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
+ assert_return(server, -EINVAL);
+
+ server->event = sd_event_unref(server->event);
+
+ return 0;
+}
+
+sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
+ assert_return(server, NULL);
+
+ return server->event;
+}
+
+int sd_dhcp_server_set_boot_server_address(sd_dhcp_server *server, const struct in_addr *address) {
+ assert_return(server, -EINVAL);
+
+ if (address)
+ server->boot_server_address = *address;
+ else
+ server->boot_server_address = (struct in_addr) {};
+
+ return 0;
+}
+
+int sd_dhcp_server_set_boot_server_name(sd_dhcp_server *server, const char *name) {
+ int r;
+
+ assert_return(server, -EINVAL);
+
+ if (name) {
+ r = dns_name_is_valid(name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+ }
+
+ return free_and_strdup(&server->boot_server_name, name);
+}
+
+int sd_dhcp_server_set_boot_filename(sd_dhcp_server *server, const char *filename) {
+ assert_return(server, -EINVAL);
+
+ if (filename && (!string_is_safe(filename) || !ascii_is_valid(filename)))
+ return -EINVAL;
+
+ return free_and_strdup(&server->boot_filename, filename);
+}
+
+int sd_dhcp_server_stop(sd_dhcp_server *server) {
+ bool running;
+
+ if (!server)
+ return 0;
+
+ running = sd_dhcp_server_is_running(server);
+
+ server->receive_message = sd_event_source_disable_unref(server->receive_message);
+ server->receive_broadcast = sd_event_source_disable_unref(server->receive_broadcast);
+
+ server->fd_raw = safe_close(server->fd_raw);
+ server->fd = safe_close(server->fd);
+ server->fd_broadcast = safe_close(server->fd_broadcast);
+
+ if (running)
+ log_dhcp_server(server, "STOPPED");
+
+ return 0;
+}
+
+static bool dhcp_request_contains(DHCPRequest *req, uint8_t option) {
+ assert(req);
+
+ if (!req->parameter_request_list)
+ return false;
+
+ return memchr(req->parameter_request_list, option, req->parameter_request_list_len);
+}
+
+static int dhcp_server_send_unicast_raw(
+ sd_dhcp_server *server,
+ uint8_t hlen,
+ const uint8_t *chaddr,
+ DHCPPacket *packet,
+ size_t len) {
+
+ union sockaddr_union link = {
+ .ll.sll_family = AF_PACKET,
+ .ll.sll_protocol = htobe16(ETH_P_IP),
+ .ll.sll_ifindex = server->ifindex,
+ .ll.sll_halen = hlen,
+ };
+
+ assert(server);
+ assert(server->ifindex > 0);
+ assert(server->address != 0);
+ assert(hlen > 0);
+ assert(chaddr);
+ assert(packet);
+ assert(len > sizeof(DHCPPacket));
+
+ memcpy(link.ll.sll_addr, chaddr, hlen);
+
+ if (len > UINT16_MAX)
+ return -EOVERFLOW;
+
+ dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
+ packet->dhcp.yiaddr,
+ DHCP_PORT_CLIENT, len, -1);
+
+ return dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len);
+}
+
+static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
+ uint16_t destination_port,
+ DHCPMessage *message, size_t len) {
+ union sockaddr_union dest = {
+ .in.sin_family = AF_INET,
+ .in.sin_port = htobe16(destination_port),
+ .in.sin_addr.s_addr = destination,
+ };
+ struct iovec iov = {
+ .iov_base = message,
+ .iov_len = len,
+ };
+ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {};
+ struct msghdr msg = {
+ .msg_name = &dest,
+ .msg_namelen = sizeof(dest.in),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ struct cmsghdr *cmsg;
+ struct in_pktinfo *pktinfo;
+
+ assert(server);
+ assert(server->fd >= 0);
+ assert(message);
+ assert(len >= sizeof(DHCPMessage));
+
+ if (server->bind_to_interface) {
+ msg.msg_control = &control;
+ msg.msg_controllen = sizeof(control);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ assert(cmsg);
+
+ cmsg->cmsg_level = IPPROTO_IP;
+ cmsg->cmsg_type = IP_PKTINFO;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+
+ /* we attach source interface and address info to the message
+ rather than binding the socket. This will be mostly useful
+ when we gain support for arbitrary number of server addresses
+ */
+ pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
+ assert(pktinfo);
+
+ pktinfo->ipi_ifindex = server->ifindex;
+ pktinfo->ipi_spec_dst.s_addr = server->address;
+ }
+
+ if (sendmsg(server->fd, &msg, 0) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static bool requested_broadcast(DHCPMessage *message) {
+ assert(message);
+ return message->flags & htobe16(0x8000);
+}
+
+static int dhcp_server_send(
+ sd_dhcp_server *server,
+ uint8_t hlen,
+ const uint8_t *chaddr,
+ be32_t destination,
+ uint16_t destination_port,
+ DHCPPacket *packet,
+ size_t optoffset,
+ bool l2_broadcast) {
+
+ if (destination != INADDR_ANY)
+ return dhcp_server_send_udp(server, destination,
+ destination_port, &packet->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+ else if (l2_broadcast)
+ return dhcp_server_send_udp(server, INADDR_BROADCAST,
+ destination_port, &packet->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+ else
+ /* we cannot send UDP packet to specific MAC address when the
+ address is not yet configured, so must fall back to raw
+ packets */
+ return dhcp_server_send_unicast_raw(server, hlen, chaddr, packet,
+ sizeof(DHCPPacket) + optoffset);
+}
+
+int dhcp_server_send_packet(sd_dhcp_server *server,
+ DHCPRequest *req, DHCPPacket *packet,
+ int type, size_t optoffset) {
+ be32_t destination = INADDR_ANY;
+ uint16_t destination_port = DHCP_PORT_CLIENT;
+ int r;
+
+ assert(server);
+ assert(req);
+ assert(req->max_optlen > 0);
+ assert(req->message);
+ assert(optoffset <= req->max_optlen);
+ assert(packet);
+
+ r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
+ SD_DHCP_OPTION_SERVER_IDENTIFIER,
+ 4, &server->address);
+ if (r < 0)
+ return r;
+
+ if (req->agent_info_option) {
+ size_t opt_full_length = *(req->agent_info_option + 1) + 2;
+ /* there must be space left for SD_DHCP_OPTION_END */
+ if (optoffset + opt_full_length < req->max_optlen) {
+ memcpy(packet->dhcp.options + optoffset, req->agent_info_option, opt_full_length);
+ optoffset += opt_full_length;
+ }
+ }
+
+ r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
+ SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ /* RFC 2131 Section 4.1
+
+ If the ’giaddr’ field in a DHCP message from a client is non-zero,
+ the server sends any return messages to the ’DHCP server’ port on the
+ BOOTP relay agent whose address appears in ’giaddr’. If the ’giaddr’
+ field is zero and the ’ciaddr’ field is nonzero, then the server
+ unicasts DHCPOFFER and DHCPACK messages to the address in ’ciaddr’.
+ If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is
+ set, then the server broadcasts DHCPOFFER and DHCPACK messages to
+ 0xffffffff. If the broadcast bit is not set and ’giaddr’ is zero and
+ ’ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK
+ messages to the client’s hardware address and ’yiaddr’ address. In
+ all cases, when ’giaddr’ is zero, the server broadcasts any DHCPNAK
+ messages to 0xffffffff.
+
+ Section 4.3.2
+
+ If ’giaddr’ is set in the DHCPREQUEST message, the client is on a
+ different subnet. The server MUST set the broadcast bit in the
+ DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the
+ client, because the client may not have a correct network address
+ or subnet mask, and the client may not be answering ARP requests.
+ */
+ if (req->message->giaddr != 0) {
+ destination = req->message->giaddr;
+ destination_port = DHCP_PORT_SERVER;
+ if (type == DHCP_NAK)
+ packet->dhcp.flags = htobe16(0x8000);
+ } else if (req->message->ciaddr != 0 && type != DHCP_NAK)
+ destination = req->message->ciaddr;
+
+ bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK;
+ return dhcp_server_send(server, req->message->hlen, req->message->chaddr,
+ destination, destination_port, packet, optoffset, l2_broadcast);
+}
+
+static int server_message_init(
+ sd_dhcp_server *server,
+ DHCPPacket **ret,
+ uint8_t type,
+ size_t *ret_optoffset,
+ DHCPRequest *req) {
+
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ size_t optoffset = 0;
+ int r;
+
+ assert(server);
+ assert(ret);
+ assert(ret_optoffset);
+ assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK));
+ assert(req);
+
+ packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
+ if (!packet)
+ return -ENOMEM;
+
+ r = dhcp_message_init(&packet->dhcp, BOOTREPLY,
+ be32toh(req->message->xid), type,
+ req->message->htype, req->message->hlen, req->message->chaddr,
+ req->max_optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ packet->dhcp.flags = req->message->flags;
+ packet->dhcp.giaddr = req->message->giaddr;
+
+ *ret_optoffset = optoffset;
+ *ret = TAKE_PTR(packet);
+
+ return 0;
+}
+
+static int server_send_offer_or_ack(
+ sd_dhcp_server *server,
+ DHCPRequest *req,
+ be32_t address,
+ uint8_t type) {
+
+ static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = {
+ [SD_DHCP_LEASE_DNS] = SD_DHCP_OPTION_DOMAIN_NAME_SERVER,
+ [SD_DHCP_LEASE_NTP] = SD_DHCP_OPTION_NTP_SERVER,
+ [SD_DHCP_LEASE_SIP] = SD_DHCP_OPTION_SIP_SERVER,
+ [SD_DHCP_LEASE_POP3] = SD_DHCP_OPTION_POP3_SERVER,
+ [SD_DHCP_LEASE_SMTP] = SD_DHCP_OPTION_SMTP_SERVER,
+ [SD_DHCP_LEASE_LPR] = SD_DHCP_OPTION_LPR_SERVER,
+ };
+
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ sd_dhcp_option *j;
+ be32_t lease_time;
+ size_t offset;
+ int r;
+
+ assert(server);
+ assert(req);
+ assert(IN_SET(type, DHCP_OFFER, DHCP_ACK));
+
+ r = server_message_init(server, &packet, type, &offset, req);
+ if (r < 0)
+ return r;
+
+ packet->dhcp.yiaddr = address;
+ packet->dhcp.siaddr = server->boot_server_address.s_addr;
+
+ lease_time = usec_to_be32_sec(req->lifetime);
+ r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+ SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4,
+ &lease_time);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+ SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask);
+ if (r < 0)
+ return r;
+
+ if (server->emit_router) {
+ r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+ SD_DHCP_OPTION_ROUTER, 4,
+ in4_addr_is_set(&server->router_address) ?
+ &server->router_address.s_addr :
+ &server->address);
+ if (r < 0)
+ return r;
+ }
+
+ if (server->boot_server_name) {
+ r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+ SD_DHCP_OPTION_BOOT_SERVER_NAME,
+ strlen(server->boot_server_name), server->boot_server_name);
+ if (r < 0)
+ return r;
+ }
+
+ if (server->boot_filename) {
+ r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+ SD_DHCP_OPTION_BOOT_FILENAME,
+ strlen(server->boot_filename), server->boot_filename);
+ if (r < 0)
+ return r;
+ }
+
+ for (sd_dhcp_lease_server_type_t k = 0; k < _SD_DHCP_LEASE_SERVER_TYPE_MAX; k++) {
+ if (server->servers[k].size <= 0)
+ continue;
+
+ r = dhcp_option_append(
+ &packet->dhcp, req->max_optlen, &offset, 0,
+ option_map[k],
+ sizeof(struct in_addr) * server->servers[k].size,
+ server->servers[k].addr);
+ if (r < 0)
+ return r;
+ }
+
+ if (server->timezone) {
+ r = dhcp_option_append(
+ &packet->dhcp, req->max_optlen, &offset, 0,
+ SD_DHCP_OPTION_TZDB_TIMEZONE,
+ strlen(server->timezone), server->timezone);
+ if (r < 0)
+ return r;
+ }
+
+ /* RFC 8925 section 3.3. DHCPv4 Server Behavior
+ * The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if
+ * the option was not present in the Parameter Request List sent by the client. */
+ if (dhcp_request_contains(req, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED) &&
+ server->ipv6_only_preferred_usec > 0) {
+ be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec);
+
+ r = dhcp_option_append(
+ &packet->dhcp, req->max_optlen, &offset, 0,
+ SD_DHCP_OPTION_IPV6_ONLY_PREFERRED,
+ sizeof(sec), &sec);
+ if (r < 0)
+ return r;
+ }
+
+ ORDERED_SET_FOREACH(j, server->extra_options) {
+ r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+ j->option, j->length, j->data);
+ if (r < 0)
+ return r;
+ }
+
+ if (!ordered_set_isempty(server->vendor_options)) {
+ r = dhcp_option_append(
+ &packet->dhcp, req->max_optlen, &offset, 0,
+ SD_DHCP_OPTION_VENDOR_SPECIFIC,
+ ordered_set_size(server->vendor_options), server->vendor_options);
+ if (r < 0)
+ return r;
+ }
+
+ if (server->rapid_commit && req->rapid_commit && type == DHCP_ACK) {
+ r = dhcp_option_append(
+ &packet->dhcp, req->max_optlen, &offset, 0,
+ SD_DHCP_OPTION_RAPID_COMMIT,
+ 0, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ return dhcp_server_send_packet(server, req, packet, type, offset);
+}
+
+static int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req) {
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ size_t offset;
+ int r;
+
+ /* When a request is refused, RFC 2131, section 4.3.2 mentioned we should send NAK when the
+ * client is in INITREBOOT. If the client is in other state, there is nothing mentioned in the
+ * RFC whether we should send NAK or not. Hence, let's silently ignore the request. */
+
+ if (!init_reboot)
+ return 0;
+
+ r = server_message_init(server, &packet, DHCP_NAK, &offset, req);
+ if (r < 0)
+ return log_dhcp_server_errno(server, r, "Failed to create NAK message: %m");
+
+ r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset);
+ if (r < 0)
+ return log_dhcp_server_errno(server, r, "Could not send NAK message: %m");
+
+ log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->xid));
+ return DHCP_NAK;
+}
+
+static int server_send_forcerenew(
+ sd_dhcp_server *server,
+ be32_t address,
+ be32_t gateway,
+ uint8_t htype,
+ uint8_t hlen,
+ const uint8_t *chaddr) {
+
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ size_t optoffset = 0;
+ int r;
+
+ assert(server);
+ assert(address != INADDR_ANY);
+ assert(chaddr);
+
+ packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE);
+ if (!packet)
+ return -ENOMEM;
+
+ r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0,
+ DHCP_FORCERENEW, htype, hlen, chaddr,
+ DHCP_MIN_OPTIONS_SIZE, &optoffset);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE,
+ &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ return dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT,
+ &packet->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+}
+
+static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) {
+ DHCPRequest *req = ASSERT_PTR(userdata);
+ int r;
+
+ switch (code) {
+ case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
+ if (len == 4)
+ req->lifetime = unaligned_be32_sec_to_usec(option, /* max_as_infinity = */ true);
+
+ break;
+ case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS:
+ if (len == 4)
+ memcpy(&req->requested_ip, option, sizeof(be32_t));
+
+ break;
+ case SD_DHCP_OPTION_SERVER_IDENTIFIER:
+ if (len == 4)
+ memcpy(&req->server_id, option, sizeof(be32_t));
+
+ break;
+ case SD_DHCP_OPTION_CLIENT_IDENTIFIER:
+ if (len >= 2) {
+ uint8_t *data;
+
+ data = memdup(option, len);
+ if (!data)
+ return -ENOMEM;
+
+ free_and_replace(req->client_id.data, data);
+ req->client_id.length = len;
+ }
+
+ break;
+ case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
+
+ if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket))
+ req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket);
+
+ break;
+ case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION:
+ req->agent_info_option = (uint8_t*)option - 2;
+
+ break;
+ case SD_DHCP_OPTION_HOST_NAME:
+ r = dhcp_option_parse_string(option, len, &req->hostname);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse hostname, ignoring: %m");
+ return 0;
+ }
+
+ break;
+ case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST:
+ req->parameter_request_list = option;
+ req->parameter_request_list_len = len;
+ break;
+
+ case SD_DHCP_OPTION_RAPID_COMMIT:
+ req->rapid_commit = true;
+ break;
+ }
+
+ return 0;
+}
+
+static DHCPRequest* dhcp_request_free(DHCPRequest *req) {
+ if (!req)
+ return NULL;
+
+ free(req->client_id.data);
+ free(req->hostname);
+ return mfree(req);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
+
+static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) {
+ assert(req);
+ assert(message);
+
+ req->message = message;
+
+ if (message->hlen > sizeof(message->chaddr))
+ return -EBADMSG;
+
+ /* set client id based on MAC address if client did not send an explicit one */
+ if (!req->client_id.data) {
+ uint8_t *data;
+
+ if (message->hlen == 0)
+ return -EBADMSG;
+
+ data = new0(uint8_t, message->hlen + 1);
+ if (!data)
+ return -ENOMEM;
+
+ data[0] = 0x01;
+ memcpy(data + 1, message->chaddr, message->hlen);
+
+ req->client_id.length = message->hlen + 1;
+ req->client_id.data = data;
+ }
+
+ if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) {
+ /* See RFC2131 section 4.1.1.
+ * hlen and chaddr may not be set for non-ethernet interface.
+ * Let's try to retrieve it from the client ID. */
+
+ if (!req->client_id.data)
+ return -EBADMSG;
+
+ if (req->client_id.length <= 1 || req->client_id.length > sizeof(message->chaddr) + 1)
+ return -EBADMSG;
+
+ if (req->client_id.data[0] != 0x01)
+ return -EBADMSG;
+
+ message->hlen = req->client_id.length - 1;
+ memcpy(message->chaddr, req->client_id.data + 1, message->hlen);
+ }
+
+ if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
+ req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
+
+ if (req->lifetime <= 0)
+ req->lifetime = MAX(USEC_PER_SEC, server->default_lease_time);
+
+ if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time)
+ req->lifetime = server->max_lease_time;
+
+ return 0;
+}
+
+static void request_set_timestamp(DHCPRequest *req, const triple_timestamp *timestamp) {
+ assert(req);
+
+ if (timestamp && triple_timestamp_is_set(timestamp))
+ req->timestamp = *timestamp;
+ else
+ triple_timestamp_now(&req->timestamp);
+}
+
+static int request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret) {
+ assert(req);
+ assert(TRIPLE_TIMESTAMP_HAS_CLOCK(clock));
+ assert(clock_supported(clock));
+ assert(ret);
+
+ if (req->lifetime <= 0)
+ return -ENODATA;
+
+ if (!triple_timestamp_is_set(&req->timestamp))
+ return -ENODATA;
+
+ *ret = usec_add(triple_timestamp_by_clock(&req->timestamp, clock), req->lifetime);
+ return 0;
+}
+
+static bool address_is_in_pool(sd_dhcp_server *server, be32_t address) {
+ assert(server);
+
+ if (server->pool_size == 0)
+ return false;
+
+ if (address == server->address)
+ return false;
+
+ if (be32toh(address) < (be32toh(server->subnet) | server->pool_offset) ||
+ be32toh(address) >= (be32toh(server->subnet) | (server->pool_offset + server->pool_size)))
+ return false;
+
+ if (hashmap_contains(server->static_leases_by_address, UINT32_TO_PTR(address)))
+ return false;
+
+ return true;
+}
+
+static int append_agent_information_option(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length, size_t size) {
+ int r;
+ size_t offset;
+
+ assert(server);
+ assert(message);
+
+ r = dhcp_option_find_option(message->options, opt_length, SD_DHCP_OPTION_END, &offset);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(message, size, &offset, 0, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, 0, server);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(message, size, &offset, 0, SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+ return offset;
+}
+
+static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length, size_t buflen) {
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ int r;
+
+ assert(server);
+ assert(message);
+ assert(sd_dhcp_server_is_in_relay_mode(server));
+
+ if (message->hlen == 0 || message->hlen > sizeof(message->chaddr) || memeqzero(message->chaddr, message->hlen))
+ return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG),
+ "(relay agent) received message without/invalid hardware address, discarding.");
+
+ if (message->op == BOOTREQUEST) {
+ log_dhcp_server(server, "(relay agent) BOOTREQUEST (0x%x)", be32toh(message->xid));
+ if (message->hops >= 16)
+ return -ETIME;
+ message->hops++;
+
+ /* https://tools.ietf.org/html/rfc1542#section-4.1.1 */
+ if (message->giaddr == 0)
+ message->giaddr = server->address;
+
+ if (server->agent_circuit_id || server->agent_remote_id) {
+ r = append_agent_information_option(server, message, opt_length, buflen - sizeof(DHCPMessage));
+ if (r < 0)
+ return log_dhcp_server_errno(server, r, "could not append relay option: %m");
+ opt_length = r;
+ }
+
+ return dhcp_server_send_udp(server, server->relay_target.s_addr, DHCP_PORT_SERVER, message, sizeof(DHCPMessage) + opt_length);
+ } else if (message->op == BOOTREPLY) {
+ log_dhcp_server(server, "(relay agent) BOOTREPLY (0x%x)", be32toh(message->xid));
+ if (message->giaddr != server->address)
+ return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG),
+ "(relay agent) BOOTREPLY giaddr mismatch, discarding");
+
+ int message_type = dhcp_option_parse(message, sizeof(DHCPMessage) + opt_length, NULL, NULL, NULL);
+ if (message_type < 0)
+ return message_type;
+
+ packet = malloc0(sizeof(DHCPPacket) + opt_length);
+ if (!packet)
+ return -ENOMEM;
+ memcpy(&packet->dhcp, message, sizeof(DHCPMessage) + opt_length);
+
+ r = dhcp_option_remove_option(packet->dhcp.options, opt_length, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION);
+ if (r > 0)
+ opt_length = r;
+
+ bool l2_broadcast = requested_broadcast(message) || message_type == DHCP_NAK;
+ const be32_t destination = message_type == DHCP_NAK ? INADDR_ANY : message->ciaddr;
+ return dhcp_server_send(server, message->hlen, message->chaddr, destination, DHCP_PORT_CLIENT, packet, opt_length, l2_broadcast);
+ }
+ return -EBADMSG;
+}
+
+static int prepare_new_lease(DHCPLease **ret_lease, be32_t address, DHCPRequest *req, usec_t expiration) {
+ _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL;
+
+ assert(ret_lease);
+ assert(address != 0);
+ assert(req);
+ assert(expiration != 0);
+
+ lease = new(DHCPLease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ *lease = (DHCPLease) {
+ .address = address,
+ .client_id.length = req->client_id.length,
+ .htype = req->message->htype,
+ .hlen = req->message->hlen,
+ .gateway = req->message->giaddr,
+ .expiration = expiration,
+ };
+ lease->client_id.data = memdup(req->client_id.data, req->client_id.length);
+ if (!lease->client_id.data)
+ return -ENOMEM;
+
+ memcpy(lease->chaddr, req->message->chaddr, req->message->hlen);
+
+ if (req->hostname) {
+ lease->hostname = strdup(req->hostname);
+ if (!lease->hostname)
+ return -ENOMEM;
+ }
+
+ *ret_lease = TAKE_PTR(lease);
+
+ return 0;
+}
+
+static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, DHCPLease *existing_lease, be32_t address) {
+ usec_t expiration;
+ int r;
+
+ assert(server);
+ assert(req);
+ assert(address != 0);
+
+ r = request_get_lifetime_timestamp(req, CLOCK_BOOTTIME, &expiration);
+ if (r < 0)
+ return r;
+
+ if (existing_lease) {
+ assert(existing_lease->server);
+ assert(existing_lease->address == address);
+ existing_lease->expiration = expiration;
+
+ } else {
+ _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL;
+
+ r = prepare_new_lease(&lease, address, req, expiration);
+ if (r < 0)
+ return log_dhcp_server_errno(server, r, "Failed to create new lease: %m");
+
+ lease->server = server; /* This must be set just before hashmap_put(). */
+
+ r = hashmap_ensure_put(&server->bound_leases_by_client_id, &dhcp_lease_hash_ops, &lease->client_id, lease);
+ if (r < 0)
+ return log_dhcp_server_errno(server, r, "Could not save lease: %m");
+
+ r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease);
+ if (r < 0)
+ return log_dhcp_server_errno(server, r, "Could not save lease: %m");
+
+ TAKE_PTR(lease);
+ }
+
+ r = server_send_offer_or_ack(server, req, address, DHCP_ACK);
+ if (r < 0)
+ return log_dhcp_server_errno(server, r, "Could not send ACK: %m");
+
+ log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->xid));
+
+ if (server->callback)
+ server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata);
+
+ return DHCP_ACK;
+}
+
+static int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) {
+ DHCPLease *lease;
+ usec_t time_now;
+ int r;
+
+ assert(server);
+
+ r = sd_event_now(server->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH(lease, server->bound_leases_by_client_id)
+ if (lease->expiration < time_now) {
+ log_dhcp_server(server, "CLEAN (0x%x)", be32toh(lease->address));
+ dhcp_lease_free(lease);
+ }
+
+ return 0;
+}
+
+static bool address_available(sd_dhcp_server *server, be32_t address) {
+ assert(server);
+
+ if (hashmap_contains(server->bound_leases_by_address, UINT32_TO_PTR(address)) ||
+ hashmap_contains(server->static_leases_by_address, UINT32_TO_PTR(address)) ||
+ address == server->address)
+ return false;
+
+ return true;
+}
+
+static int server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req, DHCPLease **ret) {
+ DHCPLease *static_lease;
+ _cleanup_free_ uint8_t *data = NULL;
+
+ assert(server);
+ assert(req);
+ assert(ret);
+
+ static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id);
+ if (static_lease) {
+ *ret = static_lease;
+ return 0;
+ }
+
+ /* when no lease is found based on the client id fall back to chaddr */
+ data = new(uint8_t, req->message->hlen + 1);
+ if (!data)
+ return -ENOMEM;
+
+ /* set client id type to 1: Ethernet Link-Layer (RFC 2132) */
+ data[0] = 0x01;
+ memcpy(data + 1, req->message->chaddr, req->message->hlen);
+
+ static_lease = hashmap_get(server->static_leases_by_client_id,
+ &(DHCPClientId) {
+ .length = req->message->hlen + 1,
+ .data = data,
+ });
+
+ *ret = static_lease;
+
+ return 0;
+}
+
+#define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30)
+
+int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp) {
+ _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL;
+ _cleanup_free_ char *error_message = NULL;
+ DHCPLease *existing_lease, *static_lease;
+ int type, r;
+
+ assert(server);
+ assert(message);
+
+ if (message->op != BOOTREQUEST)
+ return 0;
+
+ req = new0(DHCPRequest, 1);
+ if (!req)
+ return -ENOMEM;
+
+ type = dhcp_option_parse(message, length, parse_request, req, &error_message);
+ if (type < 0)
+ return type;
+
+ r = ensure_sane_request(server, req, message);
+ if (r < 0)
+ return r;
+
+ request_set_timestamp(req, timestamp);
+
+ r = dhcp_server_cleanup_expired_leases(server);
+ if (r < 0)
+ return r;
+
+ existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id);
+ r = server_get_static_lease(server, req, &static_lease);
+ if (r < 0)
+ return r;
+
+ switch (type) {
+
+ case DHCP_DISCOVER: {
+ be32_t address = INADDR_ANY;
+
+ log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->xid));
+
+ if (server->pool_size == 0)
+ /* no pool allocated */
+ return 0;
+
+ /* for now pick a random free address from the pool */
+ if (static_lease)
+ address = static_lease->address;
+ else if (existing_lease)
+ address = existing_lease->address;
+ else {
+ struct siphash state;
+ uint64_t hash;
+
+ /* even with no persistence of leases, we try to offer the same client
+ the same IP address. we do this by using the hash of the client id
+ as the offset into the pool of leases when finding the next free one */
+
+ siphash24_init(&state, HASH_KEY.bytes);
+ client_id_hash_func(&req->client_id, &state);
+ hash = htole64(siphash24_finalize(&state));
+
+ for (unsigned i = 0; i < server->pool_size; i++) {
+ be32_t tmp_address;
+
+ tmp_address = server->subnet | htobe32(server->pool_offset + (hash + i) % server->pool_size);
+ if (address_available(server, tmp_address)) {
+ address = tmp_address;
+ break;
+ }
+ }
+ }
+
+ if (address == INADDR_ANY)
+ /* no free addresses left */
+ return 0;
+
+ if (server->rapid_commit && req->rapid_commit)
+ return server_ack_request(server, req, existing_lease, address);
+
+ r = server_send_offer_or_ack(server, req, address, DHCP_OFFER);
+ if (r < 0)
+ /* this only fails on critical errors */
+ return log_dhcp_server_errno(server, r, "Could not send offer: %m");
+
+ log_dhcp_server(server, "OFFER (0x%x)", be32toh(req->message->xid));
+ return DHCP_OFFER;
+ }
+ case DHCP_DECLINE:
+ log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message));
+
+ /* TODO: make sure we don't offer this address again */
+
+ return 1;
+
+ case DHCP_REQUEST: {
+ be32_t address;
+ bool init_reboot = false;
+
+ /* see RFC 2131, section 4.3.2 */
+
+ if (req->server_id != 0) {
+ log_dhcp_server(server, "REQUEST (selecting) (0x%x)",
+ be32toh(req->message->xid));
+
+ /* SELECTING */
+ if (req->server_id != server->address)
+ /* client did not pick us */
+ return 0;
+
+ if (req->message->ciaddr != 0)
+ /* this MUST be zero */
+ return 0;
+
+ if (req->requested_ip == 0)
+ /* this must be filled in with the yiaddr
+ from the chosen OFFER */
+ return 0;
+
+ address = req->requested_ip;
+ } else if (req->requested_ip != 0) {
+ log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)",
+ be32toh(req->message->xid));
+
+ /* INIT-REBOOT */
+ if (req->message->ciaddr != 0)
+ /* this MUST be zero */
+ return 0;
+
+ /* TODO: check more carefully if IP is correct */
+ address = req->requested_ip;
+ init_reboot = true;
+ } else {
+ log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)",
+ be32toh(req->message->xid));
+
+ /* REBINDING / RENEWING */
+ if (req->message->ciaddr == 0)
+ /* this MUST be filled in with clients IP address */
+ return 0;
+
+ address = req->message->ciaddr;
+ }
+
+ /* Silently ignore Rapid Commit option in REQUEST message. */
+ req->rapid_commit = false;
+
+ /* disallow our own address */
+ if (address == server->address)
+ return 0;
+
+ if (static_lease) {
+ /* Found a static lease for the client ID. */
+
+ if (static_lease->address != address)
+ /* The client requested an address which is different from the static lease. Refuse. */
+ return server_send_nak_or_ignore(server, init_reboot, req);
+
+ return server_ack_request(server, req, existing_lease, address);
+ }
+
+ if (address_is_in_pool(server, address)) {
+ /* The requested address is in the pool. */
+
+ if (existing_lease && existing_lease->address != address)
+ /* We previously assigned an address, but the client requested another one. Refuse. */
+ return server_send_nak_or_ignore(server, init_reboot, req);
+
+ return server_ack_request(server, req, existing_lease, address);
+ }
+
+ return server_send_nak_or_ignore(server, init_reboot, req);
+ }
+
+ case DHCP_RELEASE: {
+ log_dhcp_server(server, "RELEASE (0x%x)",
+ be32toh(req->message->xid));
+
+ if (!existing_lease)
+ return 0;
+
+ if (existing_lease->address != req->message->ciaddr)
+ return 0;
+
+ dhcp_lease_free(existing_lease);
+
+ if (server->callback)
+ server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata);
+
+ return 0;
+ }}
+
+ return 0;
+}
+
+static size_t relay_agent_information_length(const char* agent_circuit_id, const char* agent_remote_id) {
+ size_t sum = 0;
+ if (agent_circuit_id)
+ sum += 2 + strlen(agent_circuit_id);
+ if (agent_remote_id)
+ sum += 2 + strlen(agent_remote_id);
+ return sum;
+}
+
+static int server_receive_message(sd_event_source *s, int fd,
+ uint32_t revents, void *userdata) {
+ _cleanup_free_ DHCPMessage *message = NULL;
+ /* This needs to be initialized with zero. See #20741. */
+ CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL +
+ CMSG_SPACE(sizeof(struct in_pktinfo))) control = {};
+ sd_dhcp_server *server = ASSERT_PTR(userdata);
+ struct iovec iov = {};
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ ssize_t datagram_size, len;
+ int r;
+
+ datagram_size = next_datagram_size_fd(fd);
+ if (ERRNO_IS_NEG_TRANSIENT(datagram_size) || ERRNO_IS_NEG_DISCONNECT(datagram_size))
+ return 0;
+ if (datagram_size < 0) {
+ log_dhcp_server_errno(server, datagram_size, "Failed to determine datagram size to read, ignoring: %m");
+ return 0;
+ }
+
+ size_t buflen = datagram_size;
+ if (sd_dhcp_server_is_in_relay_mode(server))
+ /* Preallocate the additional size for DHCP Relay Agent Information Option if needed */
+ buflen += relay_agent_information_length(server->agent_circuit_id, server->agent_remote_id) + 2;
+
+ message = malloc(buflen);
+ if (!message)
+ return -ENOMEM;
+
+ iov = IOVEC_MAKE(message, datagram_size);
+
+ len = recvmsg_safe(fd, &msg, 0);
+ if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len))
+ return 0;
+ if (len < 0) {
+ log_dhcp_server_errno(server, len, "Could not receive message, ignoring: %m");
+ return 0;
+ }
+
+ if ((size_t) len < sizeof(DHCPMessage))
+ return 0;
+
+ /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
+ struct in_pktinfo *info = CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo);
+ if (info && info->ipi_ifindex != server->ifindex)
+ return 0;
+
+ if (sd_dhcp_server_is_in_relay_mode(server)) {
+ r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage), buflen);
+ if (r < 0)
+ log_dhcp_server_errno(server, r, "Couldn't relay message, ignoring: %m");
+ } else {
+ r = dhcp_server_handle_message(server, message, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg));
+ if (r < 0)
+ log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m");
+ }
+ return 0;
+}
+
+static void dhcp_server_update_lease_servers(sd_dhcp_server *server) {
+ assert(server);
+ assert(server->address != 0);
+
+ /* Convert null address -> server address */
+
+ for (sd_dhcp_lease_server_type_t k = 0; k < _SD_DHCP_LEASE_SERVER_TYPE_MAX; k++)
+ for (size_t i = 0; i < server->servers[k].size; i++)
+ if (in4_addr_is_null(&server->servers[k].addr[i]))
+ server->servers[k].addr[i].s_addr = server->address;
+}
+
+int sd_dhcp_server_start(sd_dhcp_server *server) {
+ int r;
+
+ assert_return(server, -EINVAL);
+ assert_return(server->event, -EINVAL);
+
+ if (sd_dhcp_server_is_running(server))
+ return 0;
+
+ assert_return(!server->receive_message, -EBUSY);
+ assert_return(server->fd_raw < 0, -EBUSY);
+ assert_return(server->fd < 0, -EBUSY);
+ assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH);
+
+ dhcp_server_update_lease_servers(server);
+
+ r = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (r < 0) {
+ r = -errno;
+ goto on_error;
+ }
+ server->fd_raw = r;
+
+ if (server->bind_to_interface)
+ r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1);
+ else
+ r = dhcp_network_bind_udp_socket(0, server->address, DHCP_PORT_SERVER, -1);
+ if (r < 0)
+ goto on_error;
+ server->fd = r;
+
+ r = sd_event_add_io(server->event, &server->receive_message,
+ server->fd, EPOLLIN,
+ server_receive_message, server);
+ if (r < 0)
+ goto on_error;
+
+ r = sd_event_source_set_priority(server->receive_message,
+ server->event_priority);
+ if (r < 0)
+ goto on_error;
+
+ if (!server->bind_to_interface) {
+ r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_BROADCAST, DHCP_PORT_SERVER, -1);
+ if (r < 0)
+ goto on_error;
+
+ server->fd_broadcast = r;
+
+ r = sd_event_add_io(server->event, &server->receive_broadcast,
+ server->fd_broadcast, EPOLLIN,
+ server_receive_message, server);
+ if (r < 0)
+ goto on_error;
+
+ r = sd_event_source_set_priority(server->receive_broadcast,
+ server->event_priority);
+ if (r < 0)
+ goto on_error;
+ }
+
+ log_dhcp_server(server, "STARTED");
+
+ return 0;
+
+on_error:
+ sd_dhcp_server_stop(server);
+ return r;
+}
+
+int sd_dhcp_server_forcerenew(sd_dhcp_server *server) {
+ DHCPLease *lease;
+ int r = 0;
+
+ assert_return(server, -EINVAL);
+
+ log_dhcp_server(server, "FORCERENEW");
+
+ HASHMAP_FOREACH(lease, server->bound_leases_by_client_id)
+ RET_GATHER(r,
+ server_send_forcerenew(server, lease->address, lease->gateway,
+ lease->htype, lease->hlen, lease->chaddr));
+ return r;
+}
+
+int sd_dhcp_server_set_bind_to_interface(sd_dhcp_server *server, int enabled) {
+ assert_return(server, -EINVAL);
+ assert_return(!sd_dhcp_server_is_running(server), -EBUSY);
+
+ if (!!enabled == server->bind_to_interface)
+ return 0;
+
+ server->bind_to_interface = enabled;
+
+ return 1;
+}
+
+int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz) {
+ int r;
+
+ assert_return(server, -EINVAL);
+ assert_return(timezone_is_valid(tz, LOG_DEBUG), -EINVAL);
+
+ if (streq_ptr(tz, server->timezone))
+ return 0;
+
+ r = free_and_strdup(&server->timezone, tz);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint64_t t) {
+ assert_return(server, -EINVAL);
+
+ server->max_lease_time = t;
+ return 0;
+}
+
+int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint64_t t) {
+ assert_return(server, -EINVAL);
+
+ server->default_lease_time = t;
+ return 0;
+}
+
+int sd_dhcp_server_set_ipv6_only_preferred_usec(sd_dhcp_server *server, uint64_t t) {
+ assert_return(server, -EINVAL);
+
+ /* When 0 is set, disables the IPv6 only mode. */
+
+ /* Refuse too short timespan unless test mode is enabled. */
+ if (t > 0 && t < MIN_V6ONLY_WAIT_USEC && !network_test_mode_enabled())
+ return -EINVAL;
+
+ server->ipv6_only_preferred_usec = t;
+ return 0;
+}
+
+int sd_dhcp_server_set_rapid_commit(sd_dhcp_server *server, int enabled) {
+ assert_return(server, -EINVAL);
+
+ server->rapid_commit = enabled;
+ return 0;
+}
+
+int sd_dhcp_server_set_servers(
+ sd_dhcp_server *server,
+ sd_dhcp_lease_server_type_t what,
+ const struct in_addr addresses[],
+ size_t n_addresses) {
+
+ struct in_addr *c = NULL;
+
+ assert_return(server, -EINVAL);
+ assert_return(!sd_dhcp_server_is_running(server), -EBUSY);
+ assert_return(addresses || n_addresses == 0, -EINVAL);
+ assert_return(what >= 0, -EINVAL);
+ assert_return(what < _SD_DHCP_LEASE_SERVER_TYPE_MAX, -EINVAL);
+
+ if (server->servers[what].size == n_addresses &&
+ memcmp(server->servers[what].addr, addresses, sizeof(struct in_addr) * n_addresses) == 0)
+ return 0;
+
+ if (n_addresses > 0) {
+ c = newdup(struct in_addr, addresses, n_addresses);
+ if (!c)
+ return -ENOMEM;
+ }
+
+ free_and_replace(server->servers[what].addr, c);
+ server->servers[what].size = n_addresses;
+ return 1;
+}
+
+int sd_dhcp_server_set_dns(sd_dhcp_server *server, const struct in_addr dns[], size_t n) {
+ return sd_dhcp_server_set_servers(server, SD_DHCP_LEASE_DNS, dns, n);
+}
+int sd_dhcp_server_set_ntp(sd_dhcp_server *server, const struct in_addr ntp[], size_t n) {
+ return sd_dhcp_server_set_servers(server, SD_DHCP_LEASE_NTP, ntp, n);
+}
+int sd_dhcp_server_set_sip(sd_dhcp_server *server, const struct in_addr sip[], size_t n) {
+ return sd_dhcp_server_set_servers(server, SD_DHCP_LEASE_SIP, sip, n);
+}
+int sd_dhcp_server_set_pop3(sd_dhcp_server *server, const struct in_addr pop3[], size_t n) {
+ return sd_dhcp_server_set_servers(server, SD_DHCP_LEASE_POP3, pop3, n);
+}
+int sd_dhcp_server_set_smtp(sd_dhcp_server *server, const struct in_addr smtp[], size_t n) {
+ return sd_dhcp_server_set_servers(server, SD_DHCP_LEASE_SMTP, smtp, n);
+}
+int sd_dhcp_server_set_lpr(sd_dhcp_server *server, const struct in_addr lpr[], size_t n) {
+ return sd_dhcp_server_set_servers(server, SD_DHCP_LEASE_LPR, lpr, n);
+}
+
+int sd_dhcp_server_set_router(sd_dhcp_server *server, const struct in_addr *router) {
+ assert_return(server, -EINVAL);
+
+ /* router is NULL: router option will not be appended.
+ * router is null address (0.0.0.0): the server address will be used as the router address.
+ * otherwise: the specified address will be used as the router address. */
+
+ server->emit_router = router;
+ if (router)
+ server->router_address = *router;
+
+ return 0;
+}
+
+int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v) {
+ int r;
+
+ assert_return(server, -EINVAL);
+ assert_return(v, -EINVAL);
+
+ r = ordered_set_ensure_put(&server->extra_options, &dhcp_option_hash_ops, v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp_option_ref(v);
+ return 0;
+}
+
+int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v) {
+ int r;
+
+ assert_return(server, -EINVAL);
+ assert_return(v, -EINVAL);
+
+ r = ordered_set_ensure_put(&server->vendor_options, &dhcp_option_hash_ops, v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp_option_ref(v);
+
+ return 1;
+}
+
+int sd_dhcp_server_set_callback(sd_dhcp_server *server, sd_dhcp_server_callback_t cb, void *userdata) {
+ assert_return(server, -EINVAL);
+
+ server->callback = cb;
+ server->callback_userdata = userdata;
+
+ return 0;
+}
+
+int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr *address) {
+ assert_return(server, -EINVAL);
+ assert_return(!sd_dhcp_server_is_running(server), -EBUSY);
+
+ if (memcmp(address, &server->relay_target, sizeof(struct in_addr)) == 0)
+ return 0;
+
+ server->relay_target = *address;
+ return 1;
+}
+
+int sd_dhcp_server_set_relay_agent_information(
+ sd_dhcp_server *server,
+ const char *agent_circuit_id,
+ const char *agent_remote_id) {
+ _cleanup_free_ char *circuit_id_dup = NULL, *remote_id_dup = NULL;
+
+ assert_return(server, -EINVAL);
+
+ if (relay_agent_information_length(agent_circuit_id, agent_remote_id) > UINT8_MAX)
+ return -ENOBUFS;
+
+ if (agent_circuit_id) {
+ circuit_id_dup = strdup(agent_circuit_id);
+ if (!circuit_id_dup)
+ return -ENOMEM;
+ }
+
+ if (agent_remote_id) {
+ remote_id_dup = strdup(agent_remote_id);
+ if (!remote_id_dup)
+ return -ENOMEM;
+ }
+
+ free_and_replace(server->agent_circuit_id, circuit_id_dup);
+ free_and_replace(server->agent_remote_id, remote_id_dup);
+ return 0;
+}
+
+int sd_dhcp_server_set_static_lease(
+ sd_dhcp_server *server,
+ const struct in_addr *address,
+ uint8_t *client_id,
+ size_t client_id_size) {
+
+ _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL;
+ int r;
+
+ assert_return(server, -EINVAL);
+ assert_return(client_id, -EINVAL);
+ assert_return(client_id_size > 0, -EINVAL);
+ assert_return(!sd_dhcp_server_is_running(server), -EBUSY);
+
+ /* Static lease with an empty or omitted address is a valid entry,
+ * the server removes any static lease with the specified mac address. */
+ if (!address || address->s_addr == 0) {
+ DHCPClientId c;
+
+ c = (DHCPClientId) {
+ .length = client_id_size,
+ .data = client_id,
+ };
+
+ dhcp_lease_free(hashmap_get(server->static_leases_by_client_id, &c));
+ return 0;
+ }
+
+ lease = new(DHCPLease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ *lease = (DHCPLease) {
+ .address = address->s_addr,
+ .client_id.length = client_id_size,
+ };
+ lease->client_id.data = memdup(client_id, client_id_size);
+ if (!lease->client_id.data)
+ return -ENOMEM;
+
+ lease->server = server; /* This must be set just before hashmap_put(). */
+
+ r = hashmap_ensure_put(&server->static_leases_by_client_id, &dhcp_lease_hash_ops, &lease->client_id, lease);
+ if (r < 0)
+ return r;
+ r = hashmap_ensure_put(&server->static_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(lease);
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
new file mode 100644
index 0000000..c20367d
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -0,0 +1,1594 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <linux/if_arp.h>
+#include <linux/if_infiniband.h>
+
+#include "sd-dhcp6-client.h"
+
+#include "alloc-util.h"
+#include "device-util.h"
+#include "dhcp-identifier.h"
+#include "dhcp6-internal.h"
+#include "dhcp6-lease-internal.h"
+#include "dns-domain.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "hostname-util.h"
+#include "in-addr-util.h"
+#include "iovec-util.h"
+#include "random-util.h"
+#include "socket-util.h"
+#include "sort-util.h"
+#include "strv.h"
+#include "web-util.h"
+
+#define DHCP6_CLIENT_DONT_DESTROY(client) \
+ _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
+
+static int client_start_transaction(sd_dhcp6_client *client, DHCP6State state);
+
+int sd_dhcp6_client_set_callback(
+ sd_dhcp6_client *client,
+ sd_dhcp6_client_callback_t cb,
+ void *userdata) {
+
+ assert_return(client, -EINVAL);
+
+ client->callback = cb;
+ client->userdata = userdata;
+
+ return 0;
+}
+
+int dhcp6_client_set_state_callback(
+ sd_dhcp6_client *client,
+ sd_dhcp6_client_callback_t cb,
+ void *userdata) {
+
+ assert_return(client, -EINVAL);
+
+ client->state_callback = cb;
+ client->state_userdata = userdata;
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+ assert_return(ifindex > 0, -EINVAL);
+
+ client->ifindex = ifindex;
+ return 0;
+}
+
+int sd_dhcp6_client_set_ifname(sd_dhcp6_client *client, const char *ifname) {
+ assert_return(client, -EINVAL);
+ assert_return(ifname, -EINVAL);
+
+ if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
+ return -EINVAL;
+
+ return free_and_strdup(&client->ifname, ifname);
+}
+
+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 0;
+}
+
+int sd_dhcp6_client_set_local_address(
+ sd_dhcp6_client *client,
+ const struct in6_addr *local_address) {
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+ assert_return(local_address, -EINVAL);
+ assert_return(in6_addr_is_link_local(local_address) > 0, -EINVAL);
+
+ client->local_address = *local_address;
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_mac(
+ sd_dhcp6_client *client,
+ const uint8_t *addr,
+ size_t addr_len,
+ uint16_t arp_type) {
+
+ assert_return(client, -EINVAL);
+ assert_return(addr, -EINVAL);
+ assert_return(addr_len <= sizeof(client->hw_addr.bytes), -EINVAL);
+
+ /* Unlike the other setters, it is OK to set a new MAC address while the client is running,
+ * as the MAC address is used only when setting DUID or IAID. */
+
+ if (arp_type == ARPHRD_ETHER)
+ assert_return(addr_len == ETH_ALEN, -EINVAL);
+ else if (arp_type == ARPHRD_INFINIBAND)
+ assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
+ else {
+ client->arp_type = ARPHRD_NONE;
+ client->hw_addr.length = 0;
+ return 0;
+ }
+
+ client->arp_type = arp_type;
+ hw_addr_set(&client->hw_addr, addr, addr_len);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_prefix_delegation_hint(
+ sd_dhcp6_client *client,
+ uint8_t prefixlen,
+ const struct in6_addr *pd_prefix) {
+
+ _cleanup_free_ DHCP6Address *prefix = NULL;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ if (!pd_prefix) {
+ /* clear previous assignments. */
+ dhcp6_ia_clear_addresses(&client->ia_pd);
+ return 0;
+ }
+
+ assert_return(prefixlen > 0 && prefixlen <= 128, -EINVAL);
+
+ prefix = new(DHCP6Address, 1);
+ if (!prefix)
+ return -ENOMEM;
+
+ *prefix = (DHCP6Address) {
+ .iapdprefix.address = *pd_prefix,
+ .iapdprefix.prefixlen = prefixlen,
+ };
+
+ LIST_PREPEND(addresses, client->ia_pd.addresses, TAKE_PTR(prefix));
+ return 1;
+}
+
+int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ if (!v) {
+ /* Clear the previous assignments. */
+ ordered_set_clear(client->vendor_options);
+ return 0;
+ }
+
+ r = ordered_set_ensure_put(&client->vendor_options, &dhcp6_option_hash_ops, v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp6_option_ref(v);
+
+ return 1;
+}
+
+static int client_ensure_duid(sd_dhcp6_client *client) {
+ assert(client);
+
+ if (client->duid_len != 0)
+ return 0;
+
+ return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
+}
+
+/**
+ * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid
+ * without further modification. Otherwise, if duid_type is supported, DUID
+ * is set based on that type. Otherwise, an error is returned.
+ */
+int sd_dhcp6_client_set_duid_llt(sd_dhcp6_client *client, uint64_t llt_time) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ r = dhcp_identifier_set_duid_llt(&client->hw_addr, client->arp_type, llt_time, &client->duid, &client->duid_len);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set DUID-LLT: %m");
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_duid_ll(sd_dhcp6_client *client) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ r = dhcp_identifier_set_duid_ll(&client->hw_addr, client->arp_type, &client->duid, &client->duid_len);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set DUID-LL: %m");
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_duid_en(sd_dhcp6_client *client) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set DUID-EN: %m");
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_duid_uuid(sd_dhcp6_client *client) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set DUID-UUID: %m");
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_duid_raw(sd_dhcp6_client *client, uint16_t duid_type, const uint8_t *duid, size_t duid_len) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+ assert_return(duid || duid_len == 0, -EINVAL);
+
+ r = dhcp_identifier_set_duid_raw(duid_type, duid, duid_len, &client->duid, &client->duid_len);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set DUID: %m");
+
+ return 0;
+}
+
+int sd_dhcp6_client_duid_as_string(
+ sd_dhcp6_client *client,
+ char **duid) {
+ _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL;
+ const char *v;
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->duid_len > offsetof(struct duid, raw.data), -ENODATA);
+ assert_return(duid, -EINVAL);
+
+ v = duid_type_to_string(be16toh(client->duid.type));
+ if (v) {
+ s = strdup(v);
+ if (!s)
+ return -ENOMEM;
+ } else {
+ r = asprintf(&s, "%0x", client->duid.type);
+ if (r < 0)
+ return -ENOMEM;
+ }
+
+ t = hexmem(client->duid.raw.data, client->duid_len - offsetof(struct duid, raw.data));
+ if (!t)
+ return -ENOMEM;
+
+ p = strjoin(s, ":", t);
+ if (!p)
+ return -ENOMEM;
+
+ *duid = TAKE_PTR(p);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ client->ia_na.header.id = htobe32(iaid);
+ client->ia_pd.header.id = htobe32(iaid);
+ client->iaid_set = true;
+
+ return 0;
+}
+
+static int client_ensure_iaid(sd_dhcp6_client *client) {
+ int r;
+ uint32_t iaid;
+
+ assert(client);
+
+ if (client->iaid_set)
+ return 0;
+
+ r = dhcp_identifier_set_iaid(client->dev, &client->hw_addr,
+ /* legacy_unstable_byteorder = */ true,
+ &iaid);
+ if (r < 0)
+ return r;
+
+ client->ia_na.header.id = iaid;
+ client->ia_pd.header.id = iaid;
+ client->iaid_set = true;
+
+ return 0;
+}
+
+int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) {
+ assert_return(client, -EINVAL);
+ assert_return(iaid, -EINVAL);
+
+ if (!client->iaid_set)
+ return -ENODATA;
+
+ *iaid = be32toh(client->ia_na.header.id);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_fqdn(
+ sd_dhcp6_client *client,
+ const char *fqdn) {
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ /* Make sure FQDN qualifies as DNS and as Linux hostname */
+ if (fqdn &&
+ !(hostname_is_valid(fqdn, 0) && dns_name_is_valid(fqdn) > 0))
+ return -EINVAL;
+
+ return free_and_strdup(&client->fqdn, fqdn);
+}
+
+int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ client->information_request = enabled;
+
+ return 0;
+}
+
+int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled) {
+ assert_return(client, -EINVAL);
+ assert_return(enabled, -EINVAL);
+
+ *enabled = client->information_request;
+
+ return 0;
+}
+
+static int be16_compare_func(const be16_t *a, const be16_t *b) {
+ return CMP(be16toh(*a), be16toh(*b));
+}
+
+int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) {
+ be16_t opt;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ if (!dhcp6_option_can_request(option))
+ return -EINVAL;
+
+ opt = htobe16(option);
+ if (typesafe_bsearch(&opt, client->req_opts, client->n_req_opts, be16_compare_func))
+ return -EEXIST;
+
+ if (!GREEDY_REALLOC(client->req_opts, client->n_req_opts + 1))
+ return -ENOMEM;
+
+ client->req_opts[client->n_req_opts++] = opt;
+
+ /* Sort immediately to make the above binary search will work for the next time. */
+ typesafe_qsort(client->req_opts, client->n_req_opts, be16_compare_func);
+ return 0;
+}
+
+int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, const char *mudurl) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+ assert_return(mudurl, -EINVAL);
+ assert_return(strlen(mudurl) <= UINT8_MAX, -EINVAL);
+ assert_return(http_url_is_valid(mudurl), -EINVAL);
+
+ return free_and_strdup(&client->mudurl, mudurl);
+}
+
+int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client *client, char * const *user_class) {
+ char **s;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+ assert_return(!strv_isempty(user_class), -EINVAL);
+
+ STRV_FOREACH(p, user_class) {
+ size_t len = strlen(*p);
+
+ if (len > UINT16_MAX || len == 0)
+ return -EINVAL;
+ }
+
+ s = strv_copy(user_class);
+ if (!s)
+ return -ENOMEM;
+
+ return strv_free_and_replace(client->user_class, s);
+}
+
+int sd_dhcp6_client_set_request_vendor_class(sd_dhcp6_client *client, char * const *vendor_class) {
+ char **s;
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+ assert_return(!strv_isempty(vendor_class), -EINVAL);
+
+ STRV_FOREACH(p, vendor_class) {
+ size_t len = strlen(*p);
+
+ if (len > UINT16_MAX || len == 0)
+ return -EINVAL;
+ }
+
+ s = strv_copy(vendor_class);
+ if (!s)
+ return -ENOMEM;
+
+ return strv_free_and_replace(client->vendor_class, s);
+}
+
+int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegation) {
+ assert_return(client, -EINVAL);
+ assert_return(delegation, -EINVAL);
+
+ *delegation = FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, int delegation) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ SET_FLAG(client->request_ia, DHCP6_REQUEST_IA_PD, delegation);
+
+ return 0;
+}
+
+int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request) {
+ assert_return(client, -EINVAL);
+ assert_return(request, -EINVAL);
+
+ *request = FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ SET_FLAG(client->request_ia, DHCP6_REQUEST_IA_NA, request);
+
+ return 0;
+}
+
+int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) {
+ assert(client);
+ assert_se(network_test_mode_enabled());
+
+ /* This is for tests or fuzzers. */
+
+ client->transaction_id = transaction_id & htobe32(0x00ffffff);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ client->rapid_commit = enable;
+ return 0;
+}
+
+int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ client->send_release = enable;
+ return 0;
+}
+
+int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
+ assert_return(client, -EINVAL);
+
+ if (!client->lease)
+ return -ENOMSG;
+
+ if (ret)
+ *ret = client->lease;
+
+ return 0;
+}
+
+int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(v, -EINVAL);
+
+ r = ordered_hashmap_ensure_put(&client->extra_options, &dhcp6_option_hash_ops, UINT_TO_PTR(v->option), v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp6_option_ref(v);
+ return 0;
+}
+
+static void client_set_state(sd_dhcp6_client *client, DHCP6State state) {
+ assert(client);
+
+ if (client->state == state)
+ return;
+
+ log_dhcp6_client(client, "State changed: %s -> %s",
+ dhcp6_state_to_string(client->state), dhcp6_state_to_string(state));
+
+ client->state = state;
+
+ if (client->state_callback)
+ client->state_callback(client, state, client->state_userdata);
+}
+
+int dhcp6_client_get_state(sd_dhcp6_client *client) {
+ assert_return(client, -EINVAL);
+
+ return client->state;
+}
+
+static void client_notify(sd_dhcp6_client *client, int event) {
+ assert(client);
+
+ if (client->callback)
+ client->callback(client, event, client->userdata);
+}
+
+static void client_cleanup(sd_dhcp6_client *client) {
+ assert(client);
+
+ client->lease = sd_dhcp6_lease_unref(client->lease);
+
+ /* Reset IRT here. Otherwise, we cannot restart the client in the information requesting mode,
+ * even though the lease is freed below. */
+ client->information_request_time_usec = 0;
+ client->information_refresh_time_usec = 0;
+
+ (void) event_source_disable(client->receive_message);
+ (void) event_source_disable(client->timeout_resend);
+ (void) event_source_disable(client->timeout_expire);
+ (void) event_source_disable(client->timeout_t1);
+ (void) event_source_disable(client->timeout_t2);
+
+ client_set_state(client, DHCP6_STATE_STOPPED);
+}
+
+static void client_stop(sd_dhcp6_client *client, int error) {
+ DHCP6_CLIENT_DONT_DESTROY(client);
+
+ assert(client);
+
+ client_notify(client, error);
+
+ client_cleanup(client);
+}
+
+static int client_append_common_options_in_managed_mode(
+ sd_dhcp6_client *client,
+ uint8_t **buf,
+ size_t *offset,
+ const DHCP6IA *ia_na,
+ const DHCP6IA *ia_pd) {
+
+ int r;
+
+ assert(client);
+ assert(IN_SET(client->state,
+ DHCP6_STATE_SOLICITATION,
+ DHCP6_STATE_REQUEST,
+ DHCP6_STATE_RENEW,
+ DHCP6_STATE_REBIND,
+ DHCP6_STATE_STOPPING));
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+
+ if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA) && ia_na) {
+ r = dhcp6_option_append_ia(buf, offset, ia_na);
+ if (r < 0)
+ return r;
+ }
+
+ if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD) && ia_pd) {
+ r = dhcp6_option_append_ia(buf, offset, ia_pd);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->state != DHCP6_STATE_STOPPING) {
+ r = dhcp6_option_append_fqdn(buf, offset, client->fqdn);
+ if (r < 0)
+ return r;
+ }
+
+ r = dhcp6_option_append_user_class(buf, offset, client->user_class);
+ if (r < 0)
+ return r;
+
+ r = dhcp6_option_append_vendor_class(buf, offset, client->vendor_class);
+ if (r < 0)
+ return r;
+
+ r = dhcp6_option_append_vendor_option(buf, offset, client->vendor_options);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static DHCP6MessageType client_message_type_from_state(sd_dhcp6_client *client) {
+ assert(client);
+
+ switch (client->state) {
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ return DHCP6_MESSAGE_INFORMATION_REQUEST;
+ case DHCP6_STATE_SOLICITATION:
+ return DHCP6_MESSAGE_SOLICIT;
+ case DHCP6_STATE_REQUEST:
+ return DHCP6_MESSAGE_REQUEST;
+ case DHCP6_STATE_RENEW:
+ return DHCP6_MESSAGE_RENEW;
+ case DHCP6_STATE_REBIND:
+ return DHCP6_MESSAGE_REBIND;
+ case DHCP6_STATE_STOPPING:
+ return DHCP6_MESSAGE_RELEASE;
+ default:
+ assert_not_reached();
+ }
+}
+
+static int client_append_oro(sd_dhcp6_client *client, uint8_t **buf, size_t *offset) {
+ _cleanup_free_ be16_t *p = NULL;
+ be16_t *req_opts;
+ size_t n;
+
+ assert(client);
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+
+ switch (client->state) {
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ n = client->n_req_opts;
+ p = new(be16_t, n + 2);
+ if (!p)
+ return -ENOMEM;
+
+ memcpy_safe(p, client->req_opts, n * sizeof(be16_t));
+ p[n++] = htobe16(SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME); /* RFC 8415 section 21.23 */
+ p[n++] = htobe16(SD_DHCP6_OPTION_INF_MAX_RT); /* RFC 8415 section 21.25 */
+
+ typesafe_qsort(p, n, be16_compare_func);
+ req_opts = p;
+ break;
+
+ case DHCP6_STATE_SOLICITATION:
+ n = client->n_req_opts;
+ p = new(be16_t, n + 1);
+ if (!p)
+ return -ENOMEM;
+
+ memcpy_safe(p, client->req_opts, n * sizeof(be16_t));
+ p[n++] = htobe16(SD_DHCP6_OPTION_SOL_MAX_RT); /* RFC 8415 section 21.24 */
+
+ typesafe_qsort(p, n, be16_compare_func);
+ req_opts = p;
+ break;
+
+ case DHCP6_STATE_STOPPING:
+ return 0;
+
+ default:
+ n = client->n_req_opts;
+ req_opts = client->req_opts;
+ }
+
+ if (n == 0)
+ return 0;
+
+ return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_ORO, n * sizeof(be16_t), req_opts);
+}
+
+static int client_append_mudurl(sd_dhcp6_client *client, uint8_t **buf, size_t *offset) {
+ assert(client);
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+
+ if (!client->mudurl)
+ return 0;
+
+ if (client->state == DHCP6_STATE_STOPPING)
+ return 0;
+
+ return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_MUD_URL_V6,
+ strlen(client->mudurl), client->mudurl);
+}
+
+int dhcp6_client_send_message(sd_dhcp6_client *client) {
+ _cleanup_free_ uint8_t *buf = NULL;
+ struct in6_addr all_servers =
+ IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
+ struct sd_dhcp6_option *j;
+ usec_t elapsed_usec, time_now;
+ be16_t elapsed_time;
+ DHCP6Message *message;
+ size_t offset;
+ int r;
+
+ assert(client);
+ assert(client->event);
+
+ r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ return r;
+
+ if (!GREEDY_REALLOC0(buf, offsetof(DHCP6Message, options)))
+ return -ENOMEM;
+
+ message = (DHCP6Message*) buf;
+ message->transaction_id = client->transaction_id;
+ message->type = client_message_type_from_state(client);
+ offset = offsetof(DHCP6Message, options);
+
+ switch (client->state) {
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ break;
+
+ case DHCP6_STATE_SOLICITATION:
+ if (client->rapid_commit) {
+ r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ r = client_append_common_options_in_managed_mode(client, &buf, &offset,
+ &client->ia_na, &client->ia_pd);
+ if (r < 0)
+ return r;
+ break;
+
+ case DHCP6_STATE_REQUEST:
+ case DHCP6_STATE_RENEW:
+ case DHCP6_STATE_STOPPING:
+ r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_SERVERID,
+ client->lease->serverid_len,
+ client->lease->serverid);
+ if (r < 0)
+ return r;
+
+ _fallthrough_;
+ case DHCP6_STATE_REBIND:
+
+ assert(client->lease);
+
+ r = client_append_common_options_in_managed_mode(client, &buf, &offset,
+ client->lease->ia_na, client->lease->ia_pd);
+ if (r < 0)
+ return r;
+ break;
+
+ case DHCP6_STATE_BOUND:
+ case DHCP6_STATE_STOPPED:
+ default:
+ assert_not_reached();
+ }
+
+ r = client_append_mudurl(client, &buf, &offset);
+ if (r < 0)
+ return r;
+
+ r = client_append_oro(client, &buf, &offset);
+ if (r < 0)
+ return r;
+
+ assert(client->duid_len > 0);
+ r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_CLIENTID,
+ client->duid_len, &client->duid);
+ if (r < 0)
+ return r;
+
+ ORDERED_HASHMAP_FOREACH(j, client->extra_options) {
+ r = dhcp6_option_append(&buf, &offset, j->option, j->length, j->data);
+ if (r < 0)
+ return r;
+ }
+
+ /* RFC 8415 Section 21.9.
+ * A client MUST include an Elapsed Time option in messages to indicate how long the client has
+ * been trying to complete a DHCP message exchange. */
+ elapsed_usec = MIN(usec_sub_unsigned(time_now, client->transaction_start) / USEC_PER_MSEC / 10, (usec_t) UINT16_MAX);
+ elapsed_time = htobe16(elapsed_usec);
+ r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_ELAPSED_TIME, sizeof(elapsed_time), &elapsed_time);
+ if (r < 0)
+ return r;
+
+ r = dhcp6_network_send_udp_socket(client->fd, &all_servers, buf, offset);
+ if (r < 0)
+ return r;
+
+ log_dhcp6_client(client, "Sent %s",
+ dhcp6_message_type_to_string(client_message_type_from_state(client)));
+ return 0;
+}
+
+static usec_t client_timeout_compute_random(usec_t val) {
+ return usec_sub_unsigned(val, random_u64_range(val / 10));
+}
+
+static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp6_client *client = ASSERT_PTR(userdata);
+ usec_t init_retransmit_time, max_retransmit_time;
+ int r;
+
+ assert(client->event);
+
+ switch (client->state) {
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ init_retransmit_time = DHCP6_INF_TIMEOUT;
+ max_retransmit_time = DHCP6_INF_MAX_RT;
+ break;
+
+ case DHCP6_STATE_SOLICITATION:
+
+ if (client->retransmit_count > 0 && client->lease) {
+ (void) client_start_transaction(client, DHCP6_STATE_REQUEST);
+ return 0;
+ }
+
+ init_retransmit_time = DHCP6_SOL_TIMEOUT;
+ max_retransmit_time = DHCP6_SOL_MAX_RT;
+ break;
+
+ case DHCP6_STATE_REQUEST:
+
+ if (client->retransmit_count >= DHCP6_REQ_MAX_RC) {
+ client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX);
+ return 0;
+ }
+
+ init_retransmit_time = DHCP6_REQ_TIMEOUT;
+ max_retransmit_time = DHCP6_REQ_MAX_RT;
+ break;
+
+ case DHCP6_STATE_RENEW:
+ init_retransmit_time = DHCP6_REN_TIMEOUT;
+ max_retransmit_time = DHCP6_REN_MAX_RT;
+
+ /* RFC 3315, section 18.1.3. says max retransmit duration will
+ be the remaining time until T2. Instead of setting MRD,
+ wait for T2 to trigger with the same end result */
+ break;
+
+ case DHCP6_STATE_REBIND:
+ init_retransmit_time = DHCP6_REB_TIMEOUT;
+ max_retransmit_time = DHCP6_REB_MAX_RT;
+
+ /* Also, instead of setting MRD, the expire timer is already set in client_enter_bound_state(). */
+ break;
+
+ case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_STOPPING:
+ case DHCP6_STATE_BOUND:
+ default:
+ assert_not_reached();
+ }
+
+ r = dhcp6_client_send_message(client);
+ if (r >= 0)
+ client->retransmit_count++;
+
+ if (client->retransmit_time == 0) {
+ client->retransmit_time = client_timeout_compute_random(init_retransmit_time);
+
+ if (client->state == DHCP6_STATE_SOLICITATION)
+ client->retransmit_time += init_retransmit_time / 10;
+
+ } else if (client->retransmit_time > max_retransmit_time / 2)
+ client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
+ else
+ client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
+
+ log_dhcp6_client(client, "Next retransmission in %s",
+ FORMAT_TIMESPAN(client->retransmit_time, USEC_PER_SEC));
+
+ r = event_reset_time_relative(client->event, &client->timeout_resend,
+ CLOCK_BOOTTIME,
+ client->retransmit_time, 10 * USEC_PER_MSEC,
+ client_timeout_resend, client,
+ client->event_priority, "dhcp6-resend-timer", true);
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+}
+
+static int client_start_transaction(sd_dhcp6_client *client, DHCP6State state) {
+ int r;
+
+ assert(client);
+ assert(client->event);
+
+ switch (state) {
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ case DHCP6_STATE_SOLICITATION:
+ assert(client->state == DHCP6_STATE_STOPPED);
+ break;
+ case DHCP6_STATE_REQUEST:
+ assert(client->state == DHCP6_STATE_SOLICITATION);
+ break;
+ case DHCP6_STATE_RENEW:
+ assert(client->state == DHCP6_STATE_BOUND);
+ break;
+ case DHCP6_STATE_REBIND:
+ assert(IN_SET(client->state, DHCP6_STATE_BOUND, DHCP6_STATE_RENEW));
+ break;
+ case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_STOPPING:
+ case DHCP6_STATE_BOUND:
+ default:
+ assert_not_reached();
+ }
+
+ client_set_state(client, state);
+
+ client->retransmit_time = 0;
+ client->retransmit_count = 0;
+ client->transaction_id = random_u32() & htobe32(0x00ffffff);
+
+ r = sd_event_now(client->event, CLOCK_BOOTTIME, &client->transaction_start);
+ if (r < 0)
+ goto error;
+
+ r = event_reset_time(client->event, &client->timeout_resend,
+ CLOCK_BOOTTIME,
+ 0, 0,
+ client_timeout_resend, client,
+ client->event_priority, "dhcp6-resend-timeout", true);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_enabled(client->receive_message, SD_EVENT_ON);
+ if (r < 0)
+ goto error;
+
+ return 0;
+
+error:
+ client_stop(client, r);
+ return r;
+}
+
+static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp6_client *client = ASSERT_PTR(userdata);
+ DHCP6_CLIENT_DONT_DESTROY(client);
+ DHCP6State state;
+
+ (void) event_source_disable(client->timeout_expire);
+ (void) event_source_disable(client->timeout_t2);
+ (void) event_source_disable(client->timeout_t1);
+
+ state = client->state;
+
+ client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE);
+
+ /* RFC 3315, section 18.1.4., says that "...the client may choose to
+ use a Solicit message to locate a new DHCP server..." */
+ if (state == DHCP6_STATE_REBIND)
+ (void) client_start_transaction(client, DHCP6_STATE_SOLICITATION);
+
+ return 0;
+}
+
+static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp6_client *client = ASSERT_PTR(userdata);
+
+ (void) event_source_disable(client->timeout_t2);
+ (void) event_source_disable(client->timeout_t1);
+
+ log_dhcp6_client(client, "Timeout T2");
+
+ (void) client_start_transaction(client, DHCP6_STATE_REBIND);
+
+ return 0;
+}
+
+static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp6_client *client = ASSERT_PTR(userdata);
+
+ (void) event_source_disable(client->timeout_t1);
+
+ log_dhcp6_client(client, "Timeout T1");
+
+ (void) client_start_transaction(client, DHCP6_STATE_RENEW);
+
+ return 0;
+}
+
+static int client_enter_bound_state(sd_dhcp6_client *client) {
+ usec_t lifetime_t1, lifetime_t2, lifetime_valid;
+ int r;
+
+ assert(client);
+ assert(client->lease);
+ assert(IN_SET(client->state,
+ DHCP6_STATE_SOLICITATION,
+ DHCP6_STATE_REQUEST,
+ DHCP6_STATE_RENEW,
+ DHCP6_STATE_REBIND));
+
+ (void) event_source_disable(client->receive_message);
+ (void) event_source_disable(client->timeout_resend);
+
+ r = sd_dhcp6_lease_get_t1(client->lease, &lifetime_t1);
+ if (r < 0)
+ goto error;
+
+ r = sd_dhcp6_lease_get_t2(client->lease, &lifetime_t2);
+ if (r < 0)
+ goto error;
+
+ r = sd_dhcp6_lease_get_valid_lifetime(client->lease, &lifetime_valid);
+ if (r < 0)
+ goto error;
+
+ lifetime_t2 = client_timeout_compute_random(lifetime_t2);
+ lifetime_t1 = client_timeout_compute_random(MIN(lifetime_t1, lifetime_t2));
+
+ if (lifetime_t1 == USEC_INFINITY) {
+ log_dhcp6_client(client, "Infinite T1");
+ event_source_disable(client->timeout_t1);
+ } else {
+ log_dhcp6_client(client, "T1 expires in %s", FORMAT_TIMESPAN(lifetime_t1, USEC_PER_SEC));
+ r = event_reset_time_relative(client->event, &client->timeout_t1,
+ CLOCK_BOOTTIME,
+ lifetime_t1, 10 * USEC_PER_SEC,
+ client_timeout_t1, client,
+ client->event_priority, "dhcp6-t1-timeout", true);
+ if (r < 0)
+ goto error;
+ }
+
+ if (lifetime_t2 == USEC_INFINITY) {
+ log_dhcp6_client(client, "Infinite T2");
+ event_source_disable(client->timeout_t2);
+ } else {
+ log_dhcp6_client(client, "T2 expires in %s", FORMAT_TIMESPAN(lifetime_t2, USEC_PER_SEC));
+ r = event_reset_time_relative(client->event, &client->timeout_t2,
+ CLOCK_BOOTTIME,
+ lifetime_t2, 10 * USEC_PER_SEC,
+ client_timeout_t2, client,
+ client->event_priority, "dhcp6-t2-timeout", true);
+ if (r < 0)
+ goto error;
+ }
+
+ if (lifetime_valid == USEC_INFINITY) {
+ log_dhcp6_client(client, "Infinite valid lifetime");
+ event_source_disable(client->timeout_expire);
+ } else {
+ log_dhcp6_client(client, "Valid lifetime expires in %s", FORMAT_TIMESPAN(lifetime_valid, USEC_PER_SEC));
+
+ r = event_reset_time_relative(client->event, &client->timeout_expire,
+ CLOCK_BOOTTIME,
+ lifetime_valid, USEC_PER_SEC,
+ client_timeout_expire, client,
+ client->event_priority, "dhcp6-lease-expire", true);
+ if (r < 0)
+ goto error;
+ }
+
+ client_set_state(client, DHCP6_STATE_BOUND);
+ client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE);
+ return 0;
+
+error:
+ client_stop(client, r);
+ return r;
+}
+
+static int log_invalid_message_type(sd_dhcp6_client *client, const DHCP6Message *message) {
+ const char *type_str;
+
+ assert(client);
+ assert(message);
+
+ type_str = dhcp6_message_type_to_string(message->type);
+ if (type_str)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received unexpected %s message, ignoring.", type_str);
+ else
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received unsupported message type %u, ignoring.", message->type);
+}
+
+static int client_process_information(
+ sd_dhcp6_client *client,
+ DHCP6Message *message,
+ size_t len,
+ const triple_timestamp *timestamp,
+ const struct in6_addr *server_address) {
+
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+ int r;
+
+ assert(client);
+ assert(message);
+
+ if (message->type != DHCP6_MESSAGE_REPLY)
+ return log_invalid_message_type(client, message);
+
+ r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m");
+
+ log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
+
+ sd_dhcp6_lease_unref(client->lease);
+ client->lease = TAKE_PTR(lease);
+
+ /* Do not call client_stop() here, as it frees the acquired lease. */
+ (void) event_source_disable(client->receive_message);
+ (void) event_source_disable(client->timeout_resend);
+ client_set_state(client, DHCP6_STATE_STOPPED);
+
+ client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST);
+ return 0;
+}
+
+static int client_process_reply(
+ sd_dhcp6_client *client,
+ DHCP6Message *message,
+ size_t len,
+ const triple_timestamp *timestamp,
+ const struct in6_addr *server_address) {
+
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+ int r;
+
+ assert(client);
+ assert(message);
+
+ if (message->type != DHCP6_MESSAGE_REPLY)
+ return log_invalid_message_type(client, message);
+
+ r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease);
+ if (r == -EADDRNOTAVAIL) {
+
+ /* If NoBinding status code is received, we cannot request the address anymore.
+ * Let's restart transaction from the beginning. */
+
+ if (client->state == DHCP6_STATE_REQUEST)
+ /* The lease is not acquired yet, hence it is not necessary to notify the restart. */
+ client_cleanup(client);
+ else
+ /* We need to notify the previous lease was expired. */
+ client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE);
+
+ return client_start_transaction(client, DHCP6_STATE_SOLICITATION);
+ }
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m");
+
+ log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
+
+ sd_dhcp6_lease_unref(client->lease);
+ client->lease = TAKE_PTR(lease);
+
+ return client_enter_bound_state(client);
+}
+
+static int client_process_advertise_or_rapid_commit_reply(
+ sd_dhcp6_client *client,
+ DHCP6Message *message,
+ size_t len,
+ const triple_timestamp *timestamp,
+ const struct in6_addr *server_address) {
+
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+ uint8_t pref_advertise, pref_lease = 0;
+ int r;
+
+ assert(client);
+ assert(message);
+
+ if (!IN_SET(message->type, DHCP6_MESSAGE_ADVERTISE, DHCP6_MESSAGE_REPLY))
+ return log_invalid_message_type(client, message);
+
+ r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to process received %s message, ignoring: %m",
+ dhcp6_message_type_to_string(message->type));
+
+ if (message->type == DHCP6_MESSAGE_REPLY) {
+ bool rapid_commit;
+
+ if (!client->rapid_commit)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received unexpected reply message, even we sent a solicit message without the rapid commit option, ignoring.");
+
+ r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit);
+ if (r < 0)
+ return r;
+
+ if (!rapid_commit)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received reply message without rapid commit flag, ignoring.");
+
+ log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
+
+ sd_dhcp6_lease_unref(client->lease);
+ client->lease = TAKE_PTR(lease);
+
+ return client_enter_bound_state(client);
+ }
+
+ r = dhcp6_lease_get_preference(lease, &pref_advertise);
+ if (r < 0)
+ return r;
+
+ if (client->lease) {
+ r = dhcp6_lease_get_preference(client->lease, &pref_lease);
+ if (r < 0)
+ return r;
+ }
+
+ log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type));
+
+ if (!client->lease || pref_advertise > pref_lease) {
+ /* If this is the first advertise message or has higher preference, then save the lease. */
+ sd_dhcp6_lease_unref(client->lease);
+ client->lease = TAKE_PTR(lease);
+ }
+
+ if (pref_advertise == 255 || client->retransmit_count > 1)
+ (void) client_start_transaction(client, DHCP6_STATE_REQUEST);
+
+ return 0;
+}
+
+static int client_receive_message(
+ sd_event_source *s,
+ int fd, uint32_t
+ revents,
+ void *userdata) {
+
+ sd_dhcp6_client *client = ASSERT_PTR(userdata);
+ DHCP6_CLIENT_DONT_DESTROY(client);
+ /* This needs to be initialized with zero. See #20741. */
+ CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL) control = {};
+ struct iovec iov;
+ union sockaddr_union sa = {};
+ struct msghdr msg = {
+ .msg_name = &sa.sa,
+ .msg_namelen = sizeof(sa),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ triple_timestamp t;
+ _cleanup_free_ DHCP6Message *message = NULL;
+ struct in6_addr *server_address = NULL;
+ ssize_t buflen, len;
+
+ buflen = next_datagram_size_fd(fd);
+ if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
+ return 0;
+ if (buflen < 0) {
+ log_dhcp6_client_errno(client, buflen, "Failed to determine datagram size to read, ignoring: %m");
+ return 0;
+ }
+
+ message = malloc(buflen);
+ if (!message)
+ return -ENOMEM;
+
+ iov = IOVEC_MAKE(message, buflen);
+
+ len = recvmsg_safe(fd, &msg, MSG_DONTWAIT);
+ if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len))
+ return 0;
+ if (len < 0) {
+ log_dhcp6_client_errno(client, len, "Could not receive message from UDP socket, ignoring: %m");
+ return 0;
+ }
+ if ((size_t) len < sizeof(DHCP6Message)) {
+ log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring");
+ return 0;
+ }
+
+ /* msg_namelen == 0 happens when running the test-suite over a socketpair */
+ if (msg.msg_namelen > 0) {
+ if (msg.msg_namelen != sizeof(struct sockaddr_in6) || sa.in6.sin6_family != AF_INET6) {
+ log_dhcp6_client(client, "Received message from invalid source, ignoring.");
+ return 0;
+ }
+
+ server_address = &sa.in6.sin6_addr;
+ }
+
+ triple_timestamp_from_cmsg(&t, &msg);
+
+ if (client->transaction_id != (message->transaction_id & htobe32(0x00ffffff)))
+ return 0;
+
+ switch (client->state) {
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ if (client_process_information(client, message, len, &t, server_address) < 0)
+ return 0;
+ break;
+
+ case DHCP6_STATE_SOLICITATION:
+ if (client_process_advertise_or_rapid_commit_reply(client, message, len, &t, server_address) < 0)
+ return 0;
+ break;
+
+ case DHCP6_STATE_REQUEST:
+ case DHCP6_STATE_RENEW:
+ case DHCP6_STATE_REBIND:
+ if (client_process_reply(client, message, len, &t, server_address) < 0)
+ return 0;
+ break;
+
+ case DHCP6_STATE_BOUND:
+ case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_STOPPING:
+ default:
+ assert_not_reached();
+ }
+
+ return 0;
+}
+
+static int client_send_release(sd_dhcp6_client *client) {
+ sd_dhcp6_lease *lease;
+
+ assert(client);
+
+ if (!client->send_release)
+ return 0;
+
+ if (sd_dhcp6_client_get_lease(client, &lease) < 0)
+ return 0;
+
+ if (!lease->ia_na && !lease->ia_pd)
+ return 0;
+
+ client_set_state(client, DHCP6_STATE_STOPPING);
+ return dhcp6_client_send_message(client);
+}
+
+int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
+ int r;
+
+ if (!client)
+ return 0;
+
+ /* Intentionally ignoring failure to send DHCP6 release. The DHCPv6 client
+ * engine is about to release its UDP socket unconditionally. */
+ r = client_send_release(client);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r,
+ "Failed to send DHCP6 release message, ignoring: %m");
+
+ client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
+
+ client->receive_message = sd_event_source_unref(client->receive_message);
+ client->fd = safe_close(client->fd);
+
+ return 0;
+}
+
+int sd_dhcp6_client_is_running(sd_dhcp6_client *client) {
+ assert_return(client, -EINVAL);
+
+ return client->state != DHCP6_STATE_STOPPED;
+}
+
+int sd_dhcp6_client_start(sd_dhcp6_client *client) {
+ DHCP6State state = DHCP6_STATE_SOLICITATION;
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->event, -EINVAL);
+ assert_return(client->ifindex > 0, -EINVAL);
+ assert_return(in6_addr_is_link_local(&client->local_address) > 0, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+ assert_return(client->information_request || client->request_ia != 0, -EINVAL);
+
+ /* Even if the client is in the STOPPED state, the lease acquired in the previous information
+ * request may be stored. */
+ client->lease = sd_dhcp6_lease_unref(client->lease);
+
+ r = client_ensure_iaid(client);
+ if (r < 0)
+ return r;
+
+ r = client_ensure_duid(client);
+ if (r < 0)
+ return r;
+
+ if (client->fd < 0) {
+ r = dhcp6_network_bind_udp_socket(client->ifindex, &client->local_address);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r,
+ "Failed to bind to UDP socket at address %s: %m",
+ IN6_ADDR_TO_STRING(&client->local_address));
+
+ client->fd = r;
+ }
+
+ if (!client->receive_message) {
+ _cleanup_(sd_event_source_disable_unrefp) sd_event_source *s = NULL;
+
+ r = sd_event_add_io(client->event, &s, client->fd, EPOLLIN, client_receive_message, client);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(s, client->event_priority);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_description(s, "dhcp6-receive-message");
+ if (r < 0)
+ return r;
+
+ client->receive_message = TAKE_PTR(s);
+ }
+
+ if (client->information_request) {
+ usec_t t = now(CLOCK_MONOTONIC);
+
+ if (t < usec_add(client->information_request_time_usec, client->information_refresh_time_usec))
+ return 0;
+
+ client->information_request_time_usec = t;
+ state = DHCP6_STATE_INFORMATION_REQUEST;
+ }
+
+ log_dhcp6_client(client, "Starting in %s mode",
+ client->information_request ? "Information request" : "Solicit");
+
+ return client_start_transaction(client, state);
+}
+
+int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!client->event, -EBUSY);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ if (event)
+ client->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&client->event);
+ if (r < 0)
+ return 0;
+ }
+
+ client->event_priority = priority;
+
+ return 0;
+}
+
+int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ client->event = sd_event_unref(client->event);
+
+ return 0;
+}
+
+sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
+ assert_return(client, NULL);
+
+ return client->event;
+}
+
+int sd_dhcp6_client_attach_device(sd_dhcp6_client *client, sd_device *dev) {
+ assert_return(client, -EINVAL);
+
+ return device_unref_and_replace(client->dev, dev);
+}
+
+static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
+ if (!client)
+ return NULL;
+
+ sd_dhcp6_lease_unref(client->lease);
+
+ sd_event_source_disable_unref(client->receive_message);
+ sd_event_source_disable_unref(client->timeout_resend);
+ sd_event_source_disable_unref(client->timeout_expire);
+ sd_event_source_disable_unref(client->timeout_t1);
+ sd_event_source_disable_unref(client->timeout_t2);
+ sd_event_unref(client->event);
+
+ client->fd = safe_close(client->fd);
+
+ sd_device_unref(client->dev);
+
+ free(client->req_opts);
+ free(client->fqdn);
+ free(client->mudurl);
+ dhcp6_ia_clear_addresses(&client->ia_pd);
+ ordered_hashmap_free(client->extra_options);
+ ordered_set_free(client->vendor_options);
+ strv_free(client->user_class);
+ strv_free(client->vendor_class);
+ free(client->ifname);
+
+ return mfree(client);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_client, sd_dhcp6_client, dhcp6_client_free);
+
+int sd_dhcp6_client_new(sd_dhcp6_client **ret) {
+ _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+
+ assert_return(ret, -EINVAL);
+
+ client = new(sd_dhcp6_client, 1);
+ if (!client)
+ return -ENOMEM;
+
+ *client = (sd_dhcp6_client) {
+ .n_ref = 1,
+ .ia_na.type = SD_DHCP6_OPTION_IA_NA,
+ .ia_pd.type = SD_DHCP6_OPTION_IA_PD,
+ .ifindex = -1,
+ .request_ia = DHCP6_REQUEST_IA_NA | DHCP6_REQUEST_IA_PD,
+ .fd = -EBADF,
+ .rapid_commit = true,
+ };
+
+ *ret = TAKE_PTR(client);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c
new file mode 100644
index 0000000..674248b
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp6-lease.c
@@ -0,0 +1,964 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+
+#include "alloc-util.h"
+#include "dhcp6-internal.h"
+#include "dhcp6-lease-internal.h"
+#include "network-common.h"
+#include "strv.h"
+
+#define IRT_DEFAULT (1 * USEC_PER_DAY)
+#define IRT_MINIMUM (600 * USEC_PER_SEC)
+
+static void dhcp6_lease_set_timestamp(sd_dhcp6_lease *lease, const triple_timestamp *timestamp) {
+ assert(lease);
+
+ if (timestamp && triple_timestamp_is_set(timestamp))
+ lease->timestamp = *timestamp;
+ else
+ triple_timestamp_now(&lease->timestamp);
+}
+
+int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
+ assert_return(clock_supported(clock), -EOPNOTSUPP);
+ assert_return(ret, -EINVAL);
+
+ if (!triple_timestamp_is_set(&lease->timestamp))
+ return -ENODATA;
+
+ *ret = triple_timestamp_by_clock(&lease->timestamp, clock);
+ return 0;
+}
+
+static void dhcp6_lease_set_lifetime(sd_dhcp6_lease *lease) {
+ usec_t t1 = USEC_INFINITY, t2 = USEC_INFINITY, min_valid_lt = USEC_INFINITY;
+
+ assert(lease);
+ assert(lease->ia_na || lease->ia_pd);
+
+ if (lease->ia_na) {
+ t1 = MIN(t1, be32_sec_to_usec(lease->ia_na->header.lifetime_t1, /* max_as_infinity = */ true));
+ t2 = MIN(t2, be32_sec_to_usec(lease->ia_na->header.lifetime_t2, /* max_as_infinity = */ true));
+
+ LIST_FOREACH(addresses, a, lease->ia_na->addresses)
+ min_valid_lt = MIN(min_valid_lt, be32_sec_to_usec(a->iaaddr.lifetime_valid, /* max_as_infinity = */ true));
+ }
+
+ if (lease->ia_pd) {
+ t1 = MIN(t1, be32_sec_to_usec(lease->ia_pd->header.lifetime_t1, /* max_as_infinity = */ true));
+ t2 = MIN(t2, be32_sec_to_usec(lease->ia_pd->header.lifetime_t2, /* max_as_infinity = */ true));
+
+ LIST_FOREACH(addresses, a, lease->ia_pd->addresses)
+ min_valid_lt = MIN(min_valid_lt, be32_sec_to_usec(a->iapdprefix.lifetime_valid, /* max_as_infinity = */ true));
+ }
+
+ if (t2 == 0 || t2 > min_valid_lt) {
+ /* If T2 is zero or longer than the minimum valid lifetime of the addresses or prefixes,
+ * then adjust lifetime with it. */
+ t1 = min_valid_lt / 2;
+ t2 = min_valid_lt / 10 * 8;
+ }
+
+ lease->lifetime_valid = min_valid_lt;
+ lease->lifetime_t1 = t1;
+ lease->lifetime_t2 = t2;
+}
+
+#define DEFINE_GET_TIME_FUNCTIONS(name, val) \
+ int sd_dhcp6_lease_get_##name( \
+ sd_dhcp6_lease *lease, \
+ uint64_t *ret) { \
+ \
+ assert_return(lease, -EINVAL); \
+ \
+ if (!lease->ia_na && !lease->ia_pd) \
+ return -ENODATA; \
+ \
+ if (ret) \
+ *ret = lease->val; \
+ return 0; \
+ } \
+ \
+ int sd_dhcp6_lease_get_##name##_timestamp( \
+ sd_dhcp6_lease *lease, \
+ clockid_t clock, \
+ uint64_t *ret) { \
+ \
+ usec_t s, t; \
+ int r; \
+ \
+ assert_return(lease, -EINVAL); \
+ \
+ r = sd_dhcp6_lease_get_##name(lease, &s); \
+ if (r < 0) \
+ return r; \
+ \
+ r = sd_dhcp6_lease_get_timestamp(lease, clock, &t); \
+ if (r < 0) \
+ return r; \
+ \
+ if (ret) \
+ *ret = time_span_to_stamp(s, t); \
+ return 0; \
+ }
+
+DEFINE_GET_TIME_FUNCTIONS(t1, lifetime_t1);
+DEFINE_GET_TIME_FUNCTIONS(t2, lifetime_t1);
+DEFINE_GET_TIME_FUNCTIONS(valid_lifetime, lifetime_valid);
+
+static void dhcp6_lease_set_server_address(sd_dhcp6_lease *lease, const struct in6_addr *server_address) {
+ assert(lease);
+
+ if (server_address)
+ lease->server_address = *server_address;
+ else
+ lease->server_address = (struct in6_addr) {};
+}
+
+int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = lease->server_address;
+ return 0;
+}
+
+void dhcp6_ia_clear_addresses(DHCP6IA *ia) {
+ assert(ia);
+
+ LIST_FOREACH(addresses, a, ia->addresses)
+ free(a);
+
+ ia->addresses = NULL;
+}
+
+DHCP6IA *dhcp6_ia_free(DHCP6IA *ia) {
+ if (!ia)
+ return NULL;
+
+ dhcp6_ia_clear_addresses(ia);
+
+ return mfree(ia);
+}
+
+int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) {
+ uint8_t *clientid = NULL;
+
+ assert(lease);
+ assert(id || len == 0);
+
+ if (len > 0) {
+ clientid = memdup(id, len);
+ if (!clientid)
+ return -ENOMEM;
+ }
+
+ free_and_replace(lease->clientid, clientid);
+ lease->clientid_len = len;
+
+ return 0;
+}
+
+int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) {
+ assert(lease);
+
+ if (!lease->clientid)
+ return -ENODATA;
+
+ if (ret_id)
+ *ret_id = lease->clientid;
+ if (ret_len)
+ *ret_len = lease->clientid_len;
+
+ return 0;
+}
+
+int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) {
+ uint8_t *serverid = NULL;
+
+ assert(lease);
+ assert(id || len == 0);
+
+ if (len > 0) {
+ serverid = memdup(id, len);
+ if (!serverid)
+ return -ENOMEM;
+ }
+
+ free_and_replace(lease->serverid, serverid);
+ lease->serverid_len = len;
+
+ return 0;
+}
+
+int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) {
+ assert(lease);
+
+ if (!lease->serverid)
+ return -ENODATA;
+
+ if (ret_id)
+ *ret_id = lease->serverid;
+ if (ret_len)
+ *ret_len = lease->serverid_len;
+ return 0;
+}
+
+int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) {
+ assert(lease);
+
+ lease->preference = preference;
+ return 0;
+}
+
+int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *ret) {
+ assert(lease);
+ assert(ret);
+
+ *ret = lease->preference;
+ return 0;
+}
+
+int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) {
+ assert(lease);
+
+ lease->rapid_commit = true;
+ return 0;
+}
+
+int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *ret) {
+ assert(lease);
+ assert(ret);
+
+ *ret = lease->rapid_commit;
+ return 0;
+}
+
+int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *ret) {
+ assert_return(lease, -EINVAL);
+
+ if (!lease->addr_iter)
+ return -ENODATA;
+
+ if (ret)
+ *ret = lease->addr_iter->iaaddr.address;
+ return 0;
+}
+
+int sd_dhcp6_lease_get_address_lifetime(
+ sd_dhcp6_lease *lease,
+ usec_t *ret_lifetime_preferred,
+ usec_t *ret_lifetime_valid) {
+
+ const struct iaaddr *a;
+
+ assert_return(lease, -EINVAL);
+
+ if (!lease->addr_iter)
+ return -ENODATA;
+
+ a = &lease->addr_iter->iaaddr;
+
+ if (ret_lifetime_preferred)
+ *ret_lifetime_preferred = be32_sec_to_usec(a->lifetime_preferred, /* max_as_infinity = */ true);
+ if (ret_lifetime_valid)
+ *ret_lifetime_valid = be32_sec_to_usec(a->lifetime_valid, /* max_as_infinity = */ true);
+ return 0;
+}
+
+int sd_dhcp6_lease_address_iterator_reset(sd_dhcp6_lease *lease) {
+ if (!lease)
+ return false;
+
+ lease->addr_iter = lease->ia_na ? lease->ia_na->addresses : NULL;
+ return !!lease->addr_iter;
+}
+
+int sd_dhcp6_lease_address_iterator_next(sd_dhcp6_lease *lease) {
+ if (!lease || !lease->addr_iter)
+ return false;
+
+ lease->addr_iter = lease->addr_iter->addresses_next;
+ return !!lease->addr_iter;
+}
+
+int sd_dhcp6_lease_has_address(sd_dhcp6_lease *lease) {
+ return lease && lease->ia_na;
+}
+
+int sd_dhcp6_lease_get_pd_prefix(
+ sd_dhcp6_lease *lease,
+ struct in6_addr *ret_prefix,
+ uint8_t *ret_prefix_len) {
+
+ const struct iapdprefix *a;
+
+ assert_return(lease, -EINVAL);
+
+ if (!lease->prefix_iter)
+ return -ENODATA;
+
+ a = &lease->prefix_iter->iapdprefix;
+
+ if (ret_prefix)
+ *ret_prefix = a->address;
+ if (ret_prefix_len)
+ *ret_prefix_len = a->prefixlen;
+ return 0;
+}
+
+int sd_dhcp6_lease_get_pd_lifetime(
+ sd_dhcp6_lease *lease,
+ uint64_t *ret_lifetime_preferred,
+ uint64_t *ret_lifetime_valid) {
+
+ const struct iapdprefix *a;
+
+ assert_return(lease, -EINVAL);
+
+ if (!lease->prefix_iter)
+ return -ENODATA;
+
+ a = &lease->prefix_iter->iapdprefix;
+
+ if (ret_lifetime_preferred)
+ *ret_lifetime_preferred = be32_sec_to_usec(a->lifetime_preferred, /* max_as_infinity = */ true);
+ if (ret_lifetime_valid)
+ *ret_lifetime_valid = be32_sec_to_usec(a->lifetime_valid, /* max_as_infinity = */ true);
+ return 0;
+}
+
+int sd_dhcp6_lease_pd_iterator_reset(sd_dhcp6_lease *lease) {
+ if (!lease)
+ return false;
+
+ lease->prefix_iter = lease->ia_pd ? lease->ia_pd->addresses : NULL;
+ return !!lease->prefix_iter;
+}
+
+int sd_dhcp6_lease_pd_iterator_next(sd_dhcp6_lease *lease) {
+ if (!lease || !lease->prefix_iter)
+ return false;
+
+ lease->prefix_iter = lease->prefix_iter->addresses_next;
+ return !!lease->prefix_iter;
+}
+
+#define DEFINE_GET_TIMESTAMP2(name) \
+ int sd_dhcp6_lease_get_##name##_lifetime_timestamp( \
+ sd_dhcp6_lease *lease, \
+ clockid_t clock, \
+ uint64_t *ret_lifetime_preferred, \
+ uint64_t *ret_lifetime_valid) { \
+ \
+ usec_t t, p, v; \
+ int r; \
+ \
+ assert_return(lease, -EINVAL); \
+ \
+ r = sd_dhcp6_lease_get_##name##_lifetime( \
+ lease, \
+ ret_lifetime_preferred ? &p : NULL, \
+ ret_lifetime_valid ? &v : NULL); \
+ if (r < 0) \
+ return r; \
+ \
+ r = sd_dhcp6_lease_get_timestamp(lease, clock, &t); \
+ if (r < 0) \
+ return r; \
+ \
+ if (ret_lifetime_preferred) \
+ *ret_lifetime_preferred = time_span_to_stamp(p, t); \
+ if (ret_lifetime_valid) \
+ *ret_lifetime_valid = time_span_to_stamp(v, t); \
+ return 0; \
+ }
+
+DEFINE_GET_TIMESTAMP2(address);
+DEFINE_GET_TIMESTAMP2(pd);
+
+int sd_dhcp6_lease_has_pd_prefix(sd_dhcp6_lease *lease) {
+ return lease && lease->ia_pd;
+}
+
+int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
+ assert(lease);
+ assert(optval || optlen == 0);
+
+ if (optlen == 0)
+ 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 **ret) {
+ assert_return(lease, -EINVAL);
+
+ if (!lease->dns)
+ return -ENODATA;
+
+ if (ret)
+ *ret = lease->dns;
+
+ return lease->dns_count;
+}
+
+int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
+ _cleanup_strv_free_ char **domains = NULL;
+ int r;
+
+ assert(lease);
+ assert(optval || optlen == 0);
+
+ if (optlen == 0)
+ return 0;
+
+ r = dhcp6_option_parse_domainname_list(optval, optlen, &domains);
+ if (r < 0)
+ return r;
+
+ return strv_extend_strv(&lease->domains, domains, true);
+}
+
+int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!lease->domains)
+ return -ENODATA;
+
+ *ret = lease->domains;
+ return strv_length(lease->domains);
+}
+
+int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
+ int r;
+
+ assert(lease);
+ assert(optval || optlen == 0);
+
+ for (size_t offset = 0; offset < optlen;) {
+ const uint8_t *subval;
+ size_t sublen;
+ uint16_t subopt;
+
+ r = dhcp6_option_parse(optval, optlen, &offset, &subopt, &sublen, &subval);
+ if (r < 0)
+ return r;
+
+ switch (subopt) {
+ case DHCP6_NTP_SUBOPTION_SRV_ADDR:
+ case DHCP6_NTP_SUBOPTION_MC_ADDR:
+ if (sublen != 16)
+ return -EINVAL;
+
+ r = dhcp6_option_parse_addresses(subval, sublen, &lease->ntp, &lease->ntp_count);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case DHCP6_NTP_SUBOPTION_SRV_FQDN: {
+ _cleanup_free_ char *server = NULL;
+
+ r = dhcp6_option_parse_domainname(subval, sublen, &server);
+ if (r < 0)
+ return r;
+
+ if (strv_contains(lease->ntp_fqdn, server))
+ continue;
+
+ r = strv_consume(&lease->ntp_fqdn, TAKE_PTR(server));
+ if (r < 0)
+ return r;
+
+ break;
+ }}
+ }
+
+ return 0;
+}
+
+int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
+ assert(lease);
+ assert(optval || optlen == 0);
+
+ if (optlen == 0)
+ 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 **ret) {
+ assert_return(lease, -EINVAL);
+
+ if (lease->ntp) {
+ if (ret)
+ *ret = lease->ntp;
+ return lease->ntp_count;
+ }
+
+ if (lease->sntp && !lease->ntp_fqdn) {
+ /* Fallback to the deprecated SNTP option. */
+ if (ret)
+ *ret = lease->sntp;
+ return lease->sntp_count;
+ }
+
+ return -ENODATA;
+}
+
+int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) {
+ assert_return(lease, -EINVAL);
+
+ if (!lease->ntp_fqdn)
+ return -ENODATA;
+
+ if (ret)
+ *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) {
+ char *fqdn;
+ int r;
+
+ assert(lease);
+ assert(optval || optlen == 0);
+
+ if (optlen == 0)
+ return 0;
+
+ if (optlen < 2)
+ return -ENODATA;
+
+ /* Ignore the flags field, it doesn't carry any useful
+ information for clients. */
+ r = dhcp6_option_parse_domainname(optval + 1, optlen - 1, &fqdn);
+ if (r < 0)
+ return r;
+
+ return free_and_replace(lease->fqdn, fqdn);
+}
+
+int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!lease->fqdn)
+ return -ENODATA;
+
+ *ret = lease->fqdn;
+ return 0;
+}
+
+int dhcp6_lease_set_captive_portal(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
+ _cleanup_free_ char *uri = NULL;
+ int r;
+
+ assert(lease);
+ assert(optval || optlen == 0);
+
+ r = dhcp6_option_parse_string(optval, optlen, &uri);
+ if (r < 0)
+ return r;
+
+ if (uri && !in_charset(uri, URI_VALID))
+ return -EINVAL;
+
+ return free_and_replace(lease->captive_portal, uri);
+}
+
+int sd_dhcp6_lease_get_captive_portal(sd_dhcp6_lease *lease, const char **ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!lease->captive_portal)
+ return -ENODATA;
+
+ *ret = lease->captive_portal;
+ return 0;
+}
+
+int sd_dhcp6_lease_get_vendor_options(sd_dhcp6_lease *lease, sd_dhcp6_option ***ret) {
+ int r;
+
+ assert_return(lease, -EINVAL);
+
+ if (set_isempty(lease->vendor_options))
+ return -ENODATA;
+
+ if (ret) {
+ if (!lease->sorted_vendor_options) {
+ r = set_dump_sorted(lease->vendor_options, (void***) &lease->sorted_vendor_options, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = lease->sorted_vendor_options;
+ }
+
+ return set_size(lease->vendor_options);
+}
+
+static int dhcp6_lease_insert_vendor_option(
+ sd_dhcp6_lease *lease,
+ uint16_t option_code,
+ const void *data,
+ size_t len,
+ uint32_t enterprise_id) {
+
+ _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *option = NULL;
+
+ assert(lease);
+
+ option = new(sd_dhcp6_option, 1);
+ if (!option)
+ return -ENOMEM;
+
+ *option = (sd_dhcp6_option) {
+ .n_ref = 1,
+ .enterprise_identifier = enterprise_id,
+ .option = option_code,
+ .length = len,
+ };
+ option->data = memdup_suffix0(data, len);
+ if (!option->data)
+ return -ENOMEM;
+
+ return set_ensure_consume(&lease->vendor_options, &dhcp6_option_hash_ops, TAKE_PTR(option));
+}
+
+static int dhcp6_lease_add_vendor_option(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
+ int r;
+ uint32_t enterprise_id;
+
+ assert(lease);
+ assert(optval || optlen == 0);
+
+ if (optlen < sizeof(be32_t))
+ return -EBADMSG;
+
+ enterprise_id = unaligned_read_be32(optval);
+
+ for (size_t offset = 4; offset < optlen;) {
+ const uint8_t *subval;
+ size_t sublen;
+ uint16_t subopt;
+
+ r = dhcp6_option_parse(optval, optlen, &offset, &subopt, &sublen, &subval);
+ if (r < 0)
+ return r;
+
+ r = dhcp6_lease_insert_vendor_option(lease, subopt, subval, sublen, enterprise_id);
+ if (r < 0)
+ return r;
+ }
+ return 0;
+}
+
+static int dhcp6_lease_parse_message(
+ sd_dhcp6_client *client,
+ sd_dhcp6_lease *lease,
+ const DHCP6Message *message,
+ size_t len) {
+
+ usec_t irt = IRT_DEFAULT;
+ int r;
+
+ assert(client);
+ assert(lease);
+ assert(message);
+ assert(len >= sizeof(DHCP6Message));
+
+ len -= sizeof(DHCP6Message);
+ for (size_t offset = 0; offset < len;) {
+ uint16_t optcode;
+ size_t optlen;
+ const uint8_t *optval;
+
+ if (len - offset < offsetof(DHCP6Option, data)) {
+ log_dhcp6_client(client, "Ignoring %zu invalid byte(s) at the end of the packet", len - offset);
+ break;
+ }
+
+ r = dhcp6_option_parse(message->options, len, &offset, &optcode, &optlen, &optval);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r,
+ "Failed to parse option header at offset %zu of total length %zu: %m",
+ offset, len);
+
+ switch (optcode) {
+ case SD_DHCP6_OPTION_CLIENTID:
+ if (dhcp6_lease_get_clientid(lease, NULL, NULL) >= 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple client IDs",
+ dhcp6_message_type_to_string(message->type));
+
+ r = dhcp6_lease_set_clientid(lease, optval, optlen);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set client ID: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_SERVERID:
+ if (dhcp6_lease_get_serverid(lease, NULL, NULL) >= 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple server IDs",
+ dhcp6_message_type_to_string(message->type));
+
+ r = dhcp6_lease_set_serverid(lease, optval, optlen);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set server ID: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_PREFERENCE:
+ if (optlen != 1)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Received invalid length for preference.");
+
+ r = dhcp6_lease_set_preference(lease, optval[0]);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set preference: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_STATUS_CODE: {
+ _cleanup_free_ char *msg = NULL;
+
+ r = dhcp6_option_parse_status(optval, optlen, &msg);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to parse status code: %m");
+ if (r > 0)
+ return log_dhcp6_client_errno(client, dhcp6_message_status_to_errno(r),
+ "Received %s message with non-zero status%s%s",
+ dhcp6_message_type_to_string(message->type),
+ isempty(msg) ? "." : ": ", strempty(msg));
+ break;
+ }
+ case SD_DHCP6_OPTION_IA_NA: {
+ _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
+
+ if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+ log_dhcp6_client(client, "Ignoring IA NA option in information requesting mode.");
+ break;
+ }
+
+ r = dhcp6_option_parse_ia(client, client->ia_na.header.id, optcode, optlen, optval, &ia);
+ if (r == -ENOMEM)
+ return log_oom_debug();
+ if (r < 0) {
+ log_dhcp6_client_errno(client, r, "Failed to parse IA_NA option, ignoring: %m");
+ continue;
+ }
+
+ if (lease->ia_na) {
+ log_dhcp6_client(client, "Received duplicate matching IA_NA option, ignoring.");
+ continue;
+ }
+
+ dhcp6_ia_free(lease->ia_na);
+ lease->ia_na = TAKE_PTR(ia);
+ break;
+ }
+ case SD_DHCP6_OPTION_IA_PD: {
+ _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
+
+ if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+ log_dhcp6_client(client, "Ignoring IA PD option in information requesting mode.");
+ break;
+ }
+
+ r = dhcp6_option_parse_ia(client, client->ia_pd.header.id, optcode, optlen, optval, &ia);
+ if (r == -ENOMEM)
+ return log_oom_debug();
+ if (r < 0) {
+ log_dhcp6_client_errno(client, r, "Failed to parse IA_PD option, ignoring: %m");
+ continue;
+ }
+
+ if (lease->ia_pd) {
+ log_dhcp6_client(client, "Received duplicate matching IA_PD option, ignoring.");
+ continue;
+ }
+
+ dhcp6_ia_free(lease->ia_pd);
+ lease->ia_pd = TAKE_PTR(ia);
+ break;
+ }
+ case SD_DHCP6_OPTION_RAPID_COMMIT:
+ if (optlen != 0)
+ log_dhcp6_client(client, "Received rapid commit option with an invalid length (%zu), ignoring.", optlen);
+
+ r = dhcp6_lease_set_rapid_commit(lease);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set rapid commit flag: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_DNS_SERVER:
+ r = dhcp6_lease_add_dns(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse DNS server option, ignoring: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_DOMAIN:
+ r = dhcp6_lease_add_domains(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse domain list option, ignoring: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_NTP_SERVER:
+ r = dhcp6_lease_add_ntp(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse NTP server option, ignoring: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_SNTP_SERVER:
+ r = dhcp6_lease_add_sntp(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse SNTP server option, ignoring: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_CAPTIVE_PORTAL:
+ r = dhcp6_lease_set_captive_portal(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse captive portal option, ignoring: %m");
+ break;
+
+ case SD_DHCP6_OPTION_CLIENT_FQDN:
+ r = dhcp6_lease_set_fqdn(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse FQDN option, ignoring: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
+ if (optlen != 4)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received information refresh time option with an invalid length (%zu).", optlen);
+
+ irt = unaligned_be32_sec_to_usec(optval, /* max_as_infinity = */ false);
+ break;
+
+ case SD_DHCP6_OPTION_VENDOR_OPTS:
+ r = dhcp6_lease_add_vendor_option(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse vendor option, ignoring: %m");
+
+ break;
+ }
+ }
+
+ uint8_t *clientid;
+ size_t clientid_len;
+ if (dhcp6_lease_get_clientid(lease, &clientid, &clientid_len) < 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "%s message does not contain client ID. Ignoring.",
+ dhcp6_message_type_to_string(message->type));
+
+ if (memcmp_nn(clientid, clientid_len, &client->duid, client->duid_len) != 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "The client ID in %s message does not match. Ignoring.",
+ dhcp6_message_type_to_string(message->type));
+
+ if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+ client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM);
+ log_dhcp6_client(client, "New information request will be refused in %s.",
+ FORMAT_TIMESPAN(client->information_refresh_time_usec, USEC_PER_SEC));
+
+ } else {
+ r = dhcp6_lease_get_serverid(lease, NULL, NULL);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "%s has no server id",
+ dhcp6_message_type_to_string(message->type));
+
+ if (!lease->ia_na && !lease->ia_pd)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "No IA_PD prefix or IA_NA address received. Ignoring.");
+
+ dhcp6_lease_set_lifetime(lease);
+ }
+
+ return 0;
+}
+
+static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
+ if (!lease)
+ return NULL;
+
+ set_free(lease->vendor_options);
+ free(lease->sorted_vendor_options);
+ free(lease->clientid);
+ free(lease->serverid);
+ dhcp6_ia_free(lease->ia_na);
+ dhcp6_ia_free(lease->ia_pd);
+ free(lease->dns);
+ free(lease->fqdn);
+ free(lease->captive_portal);
+ strv_free(lease->domains);
+ free(lease->ntp);
+ strv_free(lease->ntp_fqdn);
+ free(lease->sntp);
+
+ return mfree(lease);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_lease, sd_dhcp6_lease, dhcp6_lease_free);
+
+int dhcp6_lease_new(sd_dhcp6_lease **ret) {
+ sd_dhcp6_lease *lease;
+
+ assert(ret);
+
+ lease = new(sd_dhcp6_lease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ *lease = (sd_dhcp6_lease) {
+ .n_ref = 1,
+ };
+
+ *ret = lease;
+ return 0;
+}
+
+int dhcp6_lease_new_from_message(
+ sd_dhcp6_client *client,
+ const DHCP6Message *message,
+ size_t len,
+ const triple_timestamp *timestamp,
+ const struct in6_addr *server_address,
+ sd_dhcp6_lease **ret) {
+
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+ int r;
+
+ assert(client);
+ assert(message);
+ assert(len >= sizeof(DHCP6Message));
+ assert(ret);
+
+ r = dhcp6_lease_new(&lease);
+ if (r < 0)
+ return r;
+
+ dhcp6_lease_set_timestamp(lease, timestamp);
+ dhcp6_lease_set_server_address(lease, server_address);
+
+ r = dhcp6_lease_parse_message(client, lease, message, len);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(lease);
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c
new file mode 100644
index 0000000..0cc37a6
--- /dev/null
+++ b/src/libsystemd-network/sd-ipv4acd.c
@@ -0,0 +1,617 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014 Axis Communications AB. All rights reserved.
+***/
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/if_ether.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "sd-ipv4acd.h"
+
+#include "alloc-util.h"
+#include "arp-util.h"
+#include "ether-addr-util.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "in-addr-util.h"
+#include "memory-util.h"
+#include "network-common.h"
+#include "random-util.h"
+#include "siphash24.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "time-util.h"
+
+/* Constants from the RFC */
+#define PROBE_WAIT_USEC (1U * USEC_PER_SEC)
+#define PROBE_NUM 3U
+#define PROBE_MIN_USEC (1U * USEC_PER_SEC)
+#define PROBE_MAX_USEC (2U * USEC_PER_SEC)
+#define ANNOUNCE_WAIT_USEC (2U * USEC_PER_SEC)
+#define ANNOUNCE_NUM 2U
+#define ANNOUNCE_INTERVAL_USEC (2U * USEC_PER_SEC)
+#define MAX_CONFLICTS 10U
+#define RATE_LIMIT_INTERVAL_USEC (60U * USEC_PER_SEC)
+#define DEFEND_INTERVAL_USEC (10U * USEC_PER_SEC)
+
+typedef enum IPv4ACDState {
+ IPV4ACD_STATE_INIT,
+ IPV4ACD_STATE_STARTED,
+ IPV4ACD_STATE_WAITING_PROBE,
+ IPV4ACD_STATE_PROBING,
+ IPV4ACD_STATE_WAITING_ANNOUNCE,
+ IPV4ACD_STATE_ANNOUNCING,
+ IPV4ACD_STATE_RUNNING,
+ _IPV4ACD_STATE_MAX,
+ _IPV4ACD_STATE_INVALID = -EINVAL,
+} IPv4ACDState;
+
+struct sd_ipv4acd {
+ unsigned n_ref;
+
+ IPv4ACDState state;
+ int ifindex;
+ int fd;
+
+ char *ifname;
+ unsigned n_iteration;
+ unsigned n_conflict;
+
+ sd_event_source *receive_message_event_source;
+ sd_event_source *timer_event_source;
+
+ usec_t defend_window;
+ struct in_addr address;
+
+ /* External */
+ struct ether_addr mac_addr;
+
+ sd_event *event;
+ int event_priority;
+ sd_ipv4acd_callback_t callback;
+ 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, acd, \
+ error, fmt, ##__VA_ARGS__)
+#define log_ipv4acd(acd, fmt, ...) \
+ log_interface_prefix_full_errno_zerook( \
+ "IPv4ACD: ", \
+ sd_ipv4acd, acd, \
+ 0, fmt, ##__VA_ARGS__)
+
+static const char * const ipv4acd_state_table[_IPV4ACD_STATE_MAX] = {
+ [IPV4ACD_STATE_INIT] = "init",
+ [IPV4ACD_STATE_STARTED] = "started",
+ [IPV4ACD_STATE_WAITING_PROBE] = "waiting-probe",
+ [IPV4ACD_STATE_PROBING] = "probing",
+ [IPV4ACD_STATE_WAITING_ANNOUNCE] = "waiting-announce",
+ [IPV4ACD_STATE_ANNOUNCING] = "announcing",
+ [IPV4ACD_STATE_RUNNING] = "running",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(ipv4acd_state, IPv4ACDState);
+
+static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_counter) {
+ assert(acd);
+ assert(st < _IPV4ACD_STATE_MAX);
+
+ if (st != acd->state)
+ log_ipv4acd(acd, "%s -> %s", ipv4acd_state_to_string(acd->state), ipv4acd_state_to_string(st));
+
+ if (st == acd->state && !reset_counter)
+ acd->n_iteration++;
+ else {
+ acd->state = st;
+ acd->n_iteration = 0;
+ }
+}
+
+static void ipv4acd_reset(sd_ipv4acd *acd) {
+ assert(acd);
+
+ (void) event_source_disable(acd->timer_event_source);
+ acd->receive_message_event_source = sd_event_source_disable_unref(acd->receive_message_event_source);
+
+ acd->fd = safe_close(acd->fd);
+
+ ipv4acd_set_state(acd, IPV4ACD_STATE_INIT, true);
+}
+
+static sd_ipv4acd *ipv4acd_free(sd_ipv4acd *acd) {
+ assert(acd);
+
+ ipv4acd_reset(acd);
+ sd_event_source_unref(acd->timer_event_source);
+ sd_ipv4acd_detach_event(acd);
+ free(acd->ifname);
+ return mfree(acd);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4acd, sd_ipv4acd, ipv4acd_free);
+
+int sd_ipv4acd_new(sd_ipv4acd **ret) {
+ _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL;
+
+ assert_return(ret, -EINVAL);
+
+ acd = new(sd_ipv4acd, 1);
+ if (!acd)
+ return -ENOMEM;
+
+ *acd = (sd_ipv4acd) {
+ .n_ref = 1,
+ .state = IPV4ACD_STATE_INIT,
+ .ifindex = -1,
+ .fd = -EBADF,
+ };
+
+ *ret = TAKE_PTR(acd);
+
+ return 0;
+}
+
+static void ipv4acd_client_notify(sd_ipv4acd *acd, int event) {
+ assert(acd);
+
+ if (!acd->callback)
+ return;
+
+ acd->callback(acd, event, acd->userdata);
+}
+
+int sd_ipv4acd_stop(sd_ipv4acd *acd) {
+ IPv4ACDState old_state;
+
+ if (!acd)
+ return 0;
+
+ old_state = acd->state;
+
+ ipv4acd_reset(acd);
+
+ if (old_state == IPV4ACD_STATE_INIT)
+ return 0;
+
+ log_ipv4acd(acd, "STOPPED");
+
+ ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_STOP);
+
+ return 0;
+}
+
+static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata);
+
+static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_usec) {
+ usec_t next_timeout, time_now;
+
+ assert(acd);
+
+ next_timeout = usec;
+
+ if (random_usec > 0)
+ next_timeout += (usec_t) random_u64() % random_usec;
+
+ assert_se(sd_event_now(acd->event, CLOCK_BOOTTIME, &time_now) >= 0);
+
+ return event_reset_time(acd->event, &acd->timer_event_source,
+ CLOCK_BOOTTIME,
+ time_now + next_timeout, 0,
+ ipv4acd_on_timeout, acd,
+ acd->event_priority, "ipv4acd-timer", true);
+}
+
+static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_ipv4acd *acd = ASSERT_PTR(userdata);
+ int r = 0;
+
+ 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) {
+ 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);
+ } else
+ r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC);
+ if (r < 0)
+ goto fail;
+
+ break;
+
+ case IPV4ACD_STATE_WAITING_PROBE:
+ case IPV4ACD_STATE_PROBING:
+ /* Send a probe */
+ r = arp_send_probe(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr);
+ if (r < 0) {
+ log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m");
+ goto fail;
+ }
+
+ 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);
+
+ r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC));
+ if (r < 0)
+ goto fail;
+ } else {
+ ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true);
+
+ r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0);
+ if (r < 0)
+ goto fail;
+ }
+
+ break;
+
+ case IPV4ACD_STATE_ANNOUNCING:
+ if (acd->n_iteration >= ANNOUNCE_NUM - 1) {
+ ipv4acd_set_state(acd, IPV4ACD_STATE_RUNNING, false);
+ break;
+ }
+
+ _fallthrough_;
+ case IPV4ACD_STATE_WAITING_ANNOUNCE:
+ /* Send announcement packet */
+ r = arp_send_announcement(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr);
+ if (r < 0) {
+ log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
+ goto fail;
+ }
+
+ log_ipv4acd(acd, "Announcing "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(acd->address));
+
+ ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false);
+
+ r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_INTERVAL_USEC, 0);
+ if (r < 0)
+ goto fail;
+
+ if (acd->n_iteration == 0) {
+ acd->n_conflict = 0;
+ ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_BIND);
+ }
+
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 0;
+
+fail:
+ sd_ipv4acd_stop(acd);
+ return 0;
+}
+
+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 false;
+
+ return true; /* conflict! */
+}
+
+static void ipv4acd_on_conflict(sd_ipv4acd *acd) {
+ assert(acd);
+
+ 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);
+}
+
+static int ipv4acd_on_packet(
+ sd_event_source *s,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+
+ sd_ipv4acd *acd = ASSERT_PTR(userdata);
+ struct ether_arp packet;
+ ssize_t n;
+ int r;
+
+ assert(s);
+ assert(fd >= 0);
+
+ n = recv(fd, &packet, sizeof(struct ether_arp), 0);
+ if (n < 0) {
+ if (ERRNO_IS_TRANSIENT(errno) || ERRNO_IS_DISCONNECT(errno))
+ return 0;
+
+ log_ipv4acd_errno(acd, errno, "Failed to read ARP packet: %m");
+ goto fail;
+ }
+ if ((size_t) n != sizeof(struct ether_arp)) {
+ log_ipv4acd(acd, "Ignoring too short ARP packet.");
+ return 0;
+ }
+
+ switch (acd->state) {
+
+ case IPV4ACD_STATE_ANNOUNCING:
+ case IPV4ACD_STATE_RUNNING:
+
+ if (ipv4acd_arp_conflict(acd, &packet, true)) {
+ usec_t ts;
+
+ assert_se(sd_event_now(acd->event, CLOCK_BOOTTIME, &ts) >= 0);
+
+ /* Defend address */
+ if (ts > acd->defend_window) {
+ acd->defend_window = ts + DEFEND_INTERVAL_USEC;
+ r = arp_send_announcement(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr);
+ if (r < 0) {
+ log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
+ goto fail;
+ }
+
+ log_ipv4acd(acd, "Defending "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(acd->address));
+
+ } else
+ ipv4acd_on_conflict(acd);
+ }
+ break;
+
+ case IPV4ACD_STATE_WAITING_PROBE:
+ case IPV4ACD_STATE_PROBING:
+ case IPV4ACD_STATE_WAITING_ANNOUNCE:
+ if (ipv4acd_arp_conflict(acd, &packet, false))
+ ipv4acd_on_conflict(acd);
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 0;
+
+fail:
+ sd_ipv4acd_stop(acd);
+ return 0;
+}
+
+int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int ifindex) {
+ assert_return(acd, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+ assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+ acd->ifindex = ifindex;
+
+ return 0;
+}
+
+int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd) {
+ if (!acd)
+ return -EINVAL;
+
+ return acd->ifindex;
+}
+
+int sd_ipv4acd_set_ifname(sd_ipv4acd *acd, const char *ifname) {
+ assert_return(acd, -EINVAL);
+ assert_return(ifname, -EINVAL);
+
+ if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
+ return -EINVAL;
+
+ return free_and_strdup(&acd->ifname, ifname);
+}
+
+int sd_ipv4acd_get_ifname(sd_ipv4acd *acd, const char **ret) {
+ int r;
+
+ 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(!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;
+}
+
+int sd_ipv4acd_detach_event(sd_ipv4acd *acd) {
+ assert_return(acd, -EINVAL);
+
+ acd->event = sd_event_unref(acd->event);
+
+ return 0;
+}
+
+int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(acd, -EINVAL);
+ assert_return(!acd->event, -EBUSY);
+
+ if (event)
+ acd->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&acd->event);
+ if (r < 0)
+ return r;
+ }
+
+ acd->event_priority = priority;
+
+ return 0;
+}
+
+int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata) {
+ assert_return(acd, -EINVAL);
+
+ acd->callback = cb;
+ acd->userdata = userdata;
+
+ return 0;
+}
+
+int sd_ipv4acd_set_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(in4_addr_is_set(address), -EINVAL);
+
+ if (in4_addr_equal(&acd->address, address))
+ return 0;
+
+ 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 = acd->address;
+
+ return 0;
+}
+
+int sd_ipv4acd_is_running(sd_ipv4acd *acd) {
+ assert_return(acd, false);
+
+ return acd->state != IPV4ACD_STATE_INIT;
+}
+
+int sd_ipv4acd_is_bound(sd_ipv4acd *acd) {
+ assert_return(acd, false);
+
+ return IN_SET(acd->state, IPV4ACD_STATE_ANNOUNCING, IPV4ACD_STATE_RUNNING);
+}
+
+int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts) {
+ int r;
+
+ assert_return(acd, -EINVAL);
+ assert_return(acd->event, -EINVAL);
+ assert_return(acd->ifindex > 0, -EINVAL);
+ assert_return(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);
+ if (r < 0)
+ return r;
+
+ close_and_replace(acd->fd, r);
+
+ if (reset_conflicts)
+ acd->n_conflict = 0;
+
+ r = sd_event_add_io(acd->event, &acd->receive_message_event_source, acd->fd, EPOLLIN, ipv4acd_on_packet, acd);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_priority(acd->receive_message_event_source, acd->event_priority);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(acd->receive_message_event_source, "ipv4acd-receive-message");
+
+ r = ipv4acd_set_next_wakeup(acd, 0, 0);
+ if (r < 0)
+ goto fail;
+
+ ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true);
+ return 0;
+
+fail:
+ ipv4acd_reset(acd);
+ return r;
+}
diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c
new file mode 100644
index 0000000..a29279e
--- /dev/null
+++ b/src/libsystemd-network/sd-ipv4ll.c
@@ -0,0 +1,365 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014 Axis Communications AB. All rights reserved.
+***/
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "sd-id128.h"
+#include "sd-ipv4acd.h"
+#include "sd-ipv4ll.h"
+
+#include "alloc-util.h"
+#include "ether-addr-util.h"
+#include "in-addr-util.h"
+#include "network-common.h"
+#include "random-util.h"
+#include "siphash24.h"
+#include "sparse-endian.h"
+#include "string-util.h"
+
+#define IPV4LL_NETWORK UINT32_C(0xA9FE0000)
+#define IPV4LL_NETMASK UINT32_C(0xFFFF0000)
+
+#define IPV4LL_DONT_DESTROY(ll) \
+ _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll)
+
+struct sd_ipv4ll {
+ unsigned n_ref;
+
+ sd_ipv4acd *acd;
+
+ be32_t address; /* the address pushed to ACD */
+ struct ether_addr mac;
+
+ struct {
+ le64_t value;
+ le64_t generation;
+ } seed;
+ bool seed_set;
+
+ /* External */
+ be32_t claimed_address;
+
+ sd_ipv4ll_callback_t callback;
+ void *userdata;
+
+ 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, ll, \
+ error, fmt, ##__VA_ARGS__)
+#define log_ipv4ll(ll, fmt, ...) \
+ log_interface_prefix_full_errno_zerook( \
+ "IPv4LL: ", \
+ sd_ipv4ll, ll, \
+ 0, fmt, ##__VA_ARGS__)
+
+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);
+
+ sd_ipv4acd_unref(ll->acd);
+ return mfree(ll);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4ll, sd_ipv4ll, ipv4ll_free);
+
+int sd_ipv4ll_new(sd_ipv4ll **ret) {
+ _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ ll = new0(sd_ipv4ll, 1);
+ if (!ll)
+ return -ENOMEM;
+
+ ll->n_ref = 1;
+
+ r = sd_ipv4acd_new(&ll->acd);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4acd_set_callback(ll->acd, ipv4ll_on_acd, ll);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4acd_set_check_mac_callback(ll->acd, ipv4ll_check_mac, ll);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(ll);
+
+ return 0;
+}
+
+int sd_ipv4ll_stop(sd_ipv4ll *ll) {
+ if (!ll)
+ return 0;
+
+ return sd_ipv4acd_stop(ll->acd);
+}
+
+int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) {
+ assert_return(ll, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+ assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
+
+ return sd_ipv4acd_set_ifindex(ll->acd, ifindex);
+}
+
+int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll) {
+ if (!ll)
+ return -EINVAL;
+
+ return sd_ipv4acd_get_ifindex(ll->acd);
+}
+
+int sd_ipv4ll_set_ifname(sd_ipv4ll *ll, const char *ifname) {
+ assert_return(ll, -EINVAL);
+ assert_return(ifname, -EINVAL);
+
+ return sd_ipv4acd_set_ifname(ll->acd, ifname);
+}
+
+int sd_ipv4ll_get_ifname(sd_ipv4ll *ll, const char **ret) {
+ assert_return(ll, -EINVAL);
+
+ return sd_ipv4acd_get_ifname(ll->acd, ret);
+}
+
+int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
+ int r;
+
+ assert_return(ll, -EINVAL);
+ assert_return(addr, -EINVAL);
+ assert_return(!ether_addr_is_null(addr), -EINVAL);
+
+ r = sd_ipv4acd_set_mac(ll->acd, addr);
+ if (r < 0)
+ return r;
+
+ ll->mac = *addr;
+ return 0;
+}
+
+int sd_ipv4ll_detach_event(sd_ipv4ll *ll) {
+ assert_return(ll, -EINVAL);
+
+ return sd_ipv4acd_detach_event(ll->acd);
+}
+
+int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) {
+ assert_return(ll, -EINVAL);
+
+ return sd_ipv4acd_attach_event(ll->acd, event, priority);
+}
+
+int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) {
+ assert_return(ll, -EINVAL);
+
+ ll->callback = cb;
+ ll->userdata = userdata;
+
+ return 0;
+}
+
+int sd_ipv4ll_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);
+
+ if (ll->claimed_address == 0)
+ return -ENOENT;
+
+ address->s_addr = ll->claimed_address;
+
+ return 0;
+}
+
+int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) {
+ assert_return(ll, -EINVAL);
+ assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
+
+ ll->seed.value = htole64(seed);
+ ll->seed_set = true;
+
+ return 0;
+}
+
+int sd_ipv4ll_is_running(sd_ipv4ll *ll) {
+ assert_return(ll, false);
+
+ return sd_ipv4acd_is_running(ll->acd);
+}
+
+int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) {
+ int r;
+
+ assert_return(ll, -EINVAL);
+ assert_return(address, -EINVAL);
+ assert_return(in4_addr_is_link_local_dynamic(address), -EINVAL);
+
+ r = sd_ipv4acd_set_address(ll->acd, address);
+ if (r < 0)
+ return r;
+
+ ll->address = address->s_addr;
+
+ return 0;
+}
+
+#define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b)
+
+static int ipv4ll_pick_address(sd_ipv4ll *ll) {
+ be32_t addr;
+
+ assert(ll);
+
+ do {
+ uint64_t h;
+
+ h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes);
+
+ /* Increase the generation counter by one */
+ ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1);
+
+ addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK);
+ } while (addr == ll->address ||
+ IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U));
+
+ log_ipv4ll(ll, "Picked new IP address %s.", IN4_ADDR_TO_STRING((const struct in_addr*) &addr));
+
+ return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr });
+}
+
+#define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
+
+static int ipv4ll_start_internal(sd_ipv4ll *ll, bool reset_generation) {
+ int r;
+ bool picked_address = false;
+
+ assert_return(ll, -EINVAL);
+ assert_return(!ether_addr_is_null(&ll->mac), -EINVAL);
+
+ /* If no random seed is set, generate some from the MAC address */
+ if (!ll->seed_set)
+ ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes));
+
+ if (reset_generation)
+ ll->seed.generation = 0;
+
+ if (ll->address == 0) {
+ r = ipv4ll_pick_address(ll);
+ if (r < 0)
+ return r;
+
+ picked_address = true;
+ }
+
+ r = sd_ipv4acd_start(ll->acd, reset_generation);
+ if (r < 0) {
+
+ /* We couldn't start? If so, let's forget the picked address again, the user might make a change and
+ * retry, and we want the new data to take effect when picking an address. */
+ if (picked_address)
+ ll->address = 0;
+
+ return r;
+ }
+
+ return 1;
+}
+
+int sd_ipv4ll_start(sd_ipv4ll *ll) {
+ assert_return(ll, -EINVAL);
+
+ if (sd_ipv4ll_is_running(ll))
+ return 0;
+
+ return ipv4ll_start_internal(ll, true);
+}
+
+int sd_ipv4ll_restart(sd_ipv4ll *ll) {
+ ll->address = 0;
+
+ return ipv4ll_start_internal(ll, false);
+}
+
+static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) {
+ assert(ll);
+
+ if (ll->callback)
+ ll->callback(ll, event, ll->userdata);
+}
+
+void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
+ sd_ipv4ll *ll = ASSERT_PTR(userdata);
+ IPV4LL_DONT_DESTROY(ll);
+ int r;
+
+ assert(acd);
+
+ switch (event) {
+
+ case SD_IPV4ACD_EVENT_STOP:
+ ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
+ ll->claimed_address = 0;
+ break;
+
+ case SD_IPV4ACD_EVENT_BIND:
+ ll->claimed_address = ll->address;
+ ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND);
+ break;
+
+ case SD_IPV4ACD_EVENT_CONFLICT:
+ /* if an address was already bound we must call up to the
+ user to handle this, otherwise we just try again */
+ if (ll->claimed_address != 0) {
+ ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_CONFLICT);
+
+ ll->claimed_address = 0;
+ } else {
+ r = sd_ipv4ll_restart(ll);
+ if (r < 0)
+ goto error;
+ }
+
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ return;
+
+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 = ASSERT_PTR(userdata);
+
+ if (ll->check_mac_callback)
+ return ll->check_mac_callback(ll, mac, ll->check_mac_userdata);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-lldp-rx.c b/src/libsystemd-network/sd-lldp-rx.c
new file mode 100644
index 0000000..2fc9a55
--- /dev/null
+++ b/src/libsystemd-network/sd-lldp-rx.c
@@ -0,0 +1,524 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#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);
+
+ 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 = ASSERT_PTR(userdata);
+ struct timespec ts;
+
+ assert(fd >= 0);
+
+ space = next_datagram_size_fd(fd);
+ if (ERRNO_IS_NEG_TRANSIENT(space) || ERRNO_IS_NEG_DISCONNECT(space))
+ return 0;
+ 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 (ERRNO_IS_TRANSIENT(errno) || ERRNO_IS_DISCONNECT(errno))
+ 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_now(&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;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+sd_event* sd_lldp_rx_get_event(sd_lldp_rx *lldp_rx) {
+ assert_return(lldp_rx, NULL);
+
+ return lldp_rx->event;
+}
+
+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;
+}
+
+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);
+
+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 = -EBADF,
+ .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,
+ n->until, 0,
+ on_timer_event, lldp_rx,
+ lldp_rx->event_priority, "lldp-rx-timer", true);
+}
+
+static 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);
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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/libsystemd-network/sd-lldp-tx.c b/src/libsystemd-network/sd-lldp-tx.c
new file mode 100644
index 0000000..2b822af
--- /dev/null
+++ b/src/libsystemd-network/sd-lldp-tx.c
@@ -0,0 +1,628 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <arpa/inet.h>
+#include <linux/sockios.h>
+#include <sys/ioctl.h>
+
+#include "sd-event.h"
+#include "sd-id128.h"
+#include "sd-lldp-tx.h"
+
+#include "alloc-util.h"
+#include "ether-addr-util.h"
+#include "fd-util.h"
+#include "hostname-util.h"
+#include "network-common.h"
+#include "random-util.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "time-util.h"
+#include "unaligned.h"
+#include "web-util.h"
+
+/* The LLDP spec calls this "txFastInit", see 9.2.5.19 */
+#define LLDP_FAST_TX_INIT 4U
+
+/* The LLDP spec calls this "msgTxHold", see 9.2.5.6 */
+#define LLDP_TX_HOLD 4U
+
+/* The jitter range to add, see 9.2.2. */
+#define LLDP_TX_JITTER_USEC (400U * USEC_PER_MSEC)
+
+/* The LLDP spec calls this msgTxInterval, but we subtract half the jitter off it. */
+#define LLDP_TX_INTERVAL_USEC (30U * USEC_PER_SEC - LLDP_TX_JITTER_USEC / 2)
+
+/* The LLDP spec calls this msgFastTx, but we subtract half the jitter off it. */
+#define LLDP_FAST_TX_INTERVAL_USEC (1U * USEC_PER_SEC - LLDP_TX_JITTER_USEC / 2)
+
+#define LLDP_TX_TTL ((uint16_t) DIV_ROUND_UP(LLDP_TX_INTERVAL_USEC * LLDP_TX_HOLD + 1, USEC_PER_SEC))
+
+static const struct ether_addr lldp_multicast_addr[_SD_LLDP_MULTICAST_MODE_MAX] = {
+ [SD_LLDP_MULTICAST_MODE_NEAREST_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }},
+ [SD_LLDP_MULTICAST_MODE_NON_TPMR_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }},
+ [SD_LLDP_MULTICAST_MODE_CUSTOMER_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }},
+};
+
+struct sd_lldp_tx {
+ unsigned n_ref;
+
+ int ifindex;
+ char *ifname;
+
+ sd_event *event;
+ int64_t event_priority;
+ sd_event_source *timer_event_source;
+
+ unsigned fast_tx;
+
+ sd_lldp_multicast_mode_t mode;
+ struct ether_addr hwaddr;
+
+ char *port_description;
+ char *hostname;
+ char *pretty_hostname;
+ char *mud_url;
+ uint16_t supported_capabilities;
+ uint16_t enabled_capabilities;
+};
+
+#define log_lldp_tx_errno(lldp_tx, error, fmt, ...) \
+ log_interface_prefix_full_errno( \
+ "LLDP Tx: ", \
+ sd_lldp_tx, lldp_tx, \
+ error, fmt, ##__VA_ARGS__)
+#define log_lldp_tx(lldp_tx, fmt, ...) \
+ log_interface_prefix_full_errno_zerook( \
+ "LLDP Tx: ", \
+ sd_lldp_tx, lldp_tx, \
+ 0, fmt, ##__VA_ARGS__)
+
+static sd_lldp_tx *lldp_tx_free(sd_lldp_tx *lldp_tx) {
+ if (!lldp_tx)
+ return NULL;
+
+ sd_lldp_tx_detach_event(lldp_tx);
+
+ free(lldp_tx->port_description);
+ free(lldp_tx->hostname);
+ free(lldp_tx->pretty_hostname);
+ free(lldp_tx->mud_url);
+
+ free(lldp_tx->ifname);
+ return mfree(lldp_tx);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp_tx, sd_lldp_tx, lldp_tx_free);
+
+int sd_lldp_tx_new(sd_lldp_tx **ret) {
+ _cleanup_(sd_lldp_tx_unrefp) sd_lldp_tx *lldp_tx = NULL;
+
+ assert_return(ret, -EINVAL);
+
+ lldp_tx = new(sd_lldp_tx, 1);
+ if (!lldp_tx)
+ return -ENOMEM;
+
+ *lldp_tx = (sd_lldp_tx) {
+ .n_ref = 1,
+ .mode = _SD_LLDP_MULTICAST_MODE_INVALID,
+ };
+
+ *ret = TAKE_PTR(lldp_tx);
+ return 0;
+}
+
+int sd_lldp_tx_set_ifindex(sd_lldp_tx *lldp_tx, int ifindex) {
+ assert_return(lldp_tx, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+
+ lldp_tx->ifindex = ifindex;
+ return 0;
+}
+
+int sd_lldp_tx_set_ifname(sd_lldp_tx *lldp_tx, const char *ifname) {
+ assert_return(lldp_tx, -EINVAL);
+ assert_return(ifname, -EINVAL);
+
+ if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
+ return -EINVAL;
+
+ return free_and_strdup(&lldp_tx->ifname, ifname);
+}
+
+int sd_lldp_tx_get_ifname(sd_lldp_tx *lldp_tx, const char **ret) {
+ int r;
+
+ assert_return(lldp_tx, -EINVAL);
+
+ r = get_ifname(lldp_tx->ifindex, &lldp_tx->ifname);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = lldp_tx->ifname;
+
+ return 0;
+}
+
+int sd_lldp_tx_set_multicast_mode(sd_lldp_tx *lldp_tx, sd_lldp_multicast_mode_t mode) {
+ assert_return(lldp_tx, -EINVAL);
+ assert_return(mode >= 0 && mode < _SD_LLDP_MULTICAST_MODE_MAX, -EINVAL);
+
+ lldp_tx->mode = mode;
+ return 0;
+}
+
+int sd_lldp_tx_set_hwaddr(sd_lldp_tx *lldp_tx, const struct ether_addr *hwaddr) {
+ assert_return(lldp_tx, -EINVAL);
+ assert_return(!ether_addr_is_null(hwaddr), -EINVAL);
+
+ lldp_tx->hwaddr = *hwaddr;
+ return 0;
+}
+
+int sd_lldp_tx_set_capabilities(sd_lldp_tx *lldp_tx, uint16_t supported, uint16_t enabled) {
+ assert_return(lldp_tx, -EINVAL);
+ assert_return((enabled & ~supported) == 0, -EINVAL);
+
+ lldp_tx->supported_capabilities = supported;
+ lldp_tx->enabled_capabilities = enabled;
+ return 0;
+}
+
+int sd_lldp_tx_set_port_description(sd_lldp_tx *lldp_tx, const char *port_description) {
+ assert_return(lldp_tx, -EINVAL);
+
+ /* An empty string unset the previously set hostname. */
+ if (strlen_ptr(port_description) >= 512)
+ return -EINVAL;
+
+ return free_and_strdup(&lldp_tx->port_description, empty_to_null(port_description));
+}
+
+int sd_lldp_tx_set_hostname(sd_lldp_tx *lldp_tx, const char *hostname) {
+ assert_return(lldp_tx, -EINVAL);
+
+ /* An empty string unset the previously set hostname. */
+ if (!isempty(hostname)) {
+ assert_cc(HOST_NAME_MAX < 512);
+
+ if (!hostname_is_valid(hostname, 0))
+ return -EINVAL;
+ }
+
+ return free_and_strdup(&lldp_tx->hostname, empty_to_null(hostname));
+}
+
+int sd_lldp_tx_set_pretty_hostname(sd_lldp_tx *lldp_tx, const char *pretty_hostname) {
+ assert_return(lldp_tx, -EINVAL);
+
+ /* An empty string unset the previously set hostname. */
+ if (strlen_ptr(pretty_hostname) >= 512)
+ return -EINVAL;
+
+ return free_and_strdup(&lldp_tx->pretty_hostname, empty_to_null(pretty_hostname));
+}
+
+int sd_lldp_tx_set_mud_url(sd_lldp_tx *lldp_tx, const char *mud_url) {
+ assert_return(lldp_tx, -EINVAL);
+
+ /* An empty string unset the previously set hostname. */
+ if (!isempty(mud_url)) {
+ /* Unless the maximum length of each value is 511, the MUD url must be smaller than 256.
+ * See RFC 8520. */
+ if (strlen(mud_url) >= 256)
+ return -EINVAL;
+
+ if (!http_url_is_valid(mud_url))
+ return -EINVAL;
+ }
+
+ return free_and_strdup(&lldp_tx->mud_url, empty_to_null(mud_url));
+}
+
+static size_t lldp_tx_calculate_maximum_packet_size(sd_lldp_tx *lldp_tx, const char *hostname, const char *pretty_hostname) {
+ assert(lldp_tx);
+ assert(lldp_tx->ifindex > 0);
+
+ return sizeof(struct ether_header) +
+ /* Chassis ID */
+ 2 + 1 + (SD_ID128_STRING_MAX - 1) +
+ /* Port ID */
+ 2 + 1 + strlen_ptr(lldp_tx->ifname) +
+ /* TTL */
+ 2 + 2 +
+ /* Port description */
+ 2 + strlen_ptr(lldp_tx->port_description) +
+ /* System name */
+ 2 + strlen_ptr(hostname) +
+ /* System description */
+ 2 + strlen_ptr(pretty_hostname) +
+ /* MUD URL */
+ 2 + sizeof(SD_LLDP_OUI_IANA_MUD) + strlen_ptr(lldp_tx->mud_url) +
+ /* System Capabilities */
+ 2 + 4 +
+ /* End */
+ 2;
+}
+
+static int packet_append_tlv_header(uint8_t *packet, size_t packet_size, size_t *offset, uint8_t type, size_t data_len) {
+ assert(packet);
+ assert(offset);
+
+ /*
+ * +--------+--------+--------------
+ * |TLV Type| len | value
+ * |(7 bits)|(9 bits)|(0-511 octets)
+ * +--------+--------+--------------
+ * where:
+ *
+ * len = indicates the length of value
+ */
+
+ /* The type field is 7-bits. */
+ if (type >= 128)
+ return -EINVAL;
+
+ /* The data length field is 9-bits. */
+ if (data_len >= 512)
+ return -EINVAL;
+
+ if (packet_size < 2 + data_len)
+ return -ENOBUFS;
+
+ if (*offset > packet_size - 2 - data_len)
+ return -ENOBUFS;
+
+ packet[(*offset)++] = (type << 1) | !!(data_len >> 8);
+ packet[(*offset)++] = data_len & (size_t) UINT8_MAX;
+
+ return 0;
+}
+
+static int packet_append_prefixed_string(
+ uint8_t *packet,
+ size_t packet_size,
+ size_t *offset,
+ uint8_t type,
+ size_t prefix_len,
+ const void *prefix,
+ const char *str) {
+
+ size_t len;
+ int r;
+
+ assert(packet);
+ assert(offset);
+ assert(prefix_len == 0 || prefix);
+
+ if (isempty(str))
+ return 0;
+
+ len = strlen(str);
+
+ /* Check for overflow */
+ if (len > SIZE_MAX - prefix_len)
+ return -ENOBUFS;
+
+ r = packet_append_tlv_header(packet, packet_size, offset, type, prefix_len + len);
+ if (r < 0)
+ return r;
+
+ memcpy_safe(packet + *offset, prefix, prefix_len);
+ *offset += prefix_len;
+
+ memcpy(packet + *offset, str, len);
+ *offset += len;
+
+ return 0;
+}
+
+static int packet_append_string(
+ uint8_t *packet,
+ size_t packet_size,
+ size_t *offset,
+ uint8_t type,
+ const char *str) {
+
+ return packet_append_prefixed_string(packet, packet_size, offset, type, 0, NULL, str);
+}
+
+static int lldp_tx_create_packet(sd_lldp_tx *lldp_tx, size_t *ret_packet_size, uint8_t **ret_packet) {
+ _cleanup_free_ char *hostname = NULL, *pretty_hostname = NULL;
+ _cleanup_free_ uint8_t *packet = NULL;
+ struct ether_header *header;
+ size_t packet_size, offset;
+ sd_id128_t machine_id;
+ int r;
+
+ assert(lldp_tx);
+ assert(lldp_tx->ifindex > 0);
+ assert(ret_packet_size);
+ assert(ret_packet);
+
+ /* If ifname is not set yet, set ifname from ifindex. */
+ r = sd_lldp_tx_get_ifname(lldp_tx, NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_id128_get_machine(&machine_id);
+ if (r < 0)
+ return r;
+
+ if (!lldp_tx->hostname)
+ (void) gethostname_strict(&hostname);
+ if (!lldp_tx->pretty_hostname)
+ (void) get_pretty_hostname(&pretty_hostname);
+
+ packet_size = lldp_tx_calculate_maximum_packet_size(lldp_tx,
+ lldp_tx->hostname ?: hostname,
+ lldp_tx->pretty_hostname ?: pretty_hostname);
+
+ packet = new(uint8_t, packet_size);
+ if (!packet)
+ return -ENOMEM;
+
+ header = (struct ether_header*) packet;
+ header->ether_type = htobe16(ETHERTYPE_LLDP);
+ memcpy(header->ether_dhost, lldp_multicast_addr + lldp_tx->mode, ETH_ALEN);
+ memcpy(header->ether_shost, &lldp_tx->hwaddr, ETH_ALEN);
+
+ offset = sizeof(struct ether_header);
+
+ /* The three mandatory TLVs must appear first, in this specific order:
+ * 1. Chassis ID
+ * 2. Port ID
+ * 3. Time To Live
+ */
+
+ r = packet_append_prefixed_string(packet, packet_size, &offset, SD_LLDP_TYPE_CHASSIS_ID,
+ 1, (const uint8_t[]) { SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED },
+ SD_ID128_TO_STRING(machine_id));
+ if (r < 0)
+ return r;
+
+ r = packet_append_prefixed_string(packet, packet_size, &offset, SD_LLDP_TYPE_PORT_ID,
+ 1, (const uint8_t[]) { SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME },
+ lldp_tx->ifname);
+ if (r < 0)
+ return r;
+
+ r = packet_append_tlv_header(packet, packet_size, &offset, SD_LLDP_TYPE_TTL, 2);
+ if (r < 0)
+ return r;
+
+ unaligned_write_be16(packet + offset, LLDP_TX_TTL);
+ offset += 2;
+
+ /* Optional TLVs follow, in no specific order: */
+
+ r = packet_append_string(packet, packet_size, &offset, SD_LLDP_TYPE_PORT_DESCRIPTION,
+ lldp_tx->port_description);
+ if (r < 0)
+ return r;
+
+ r = packet_append_string(packet, packet_size, &offset, SD_LLDP_TYPE_SYSTEM_NAME,
+ lldp_tx->hostname ?: hostname);
+ if (r < 0)
+ return r;
+
+ r = packet_append_string(packet, packet_size, &offset, SD_LLDP_TYPE_SYSTEM_DESCRIPTION,
+ lldp_tx->pretty_hostname ?: pretty_hostname);
+ if (r < 0)
+ return r;
+
+ /* See section 12 of RFC 8520.
+ * +--------+--------+----------+---------+--------------
+ * |TLV Type| len | OUI |subtype | MUDString
+ * | =127 | |= 00 00 5E| = 1 |
+ * |(7 bits)|(9 bits)|(3 octets)|(1 octet)|(1-255 octets)
+ * +--------+--------+----------+---------+--------------
+ * where:
+ *
+ * o TLV Type = 127 indicates a vendor-specific TLV
+ * o len = indicates the TLV string length
+ * o OUI = 00 00 5E is the organizationally unique identifier of IANA
+ * o subtype = 1 (as assigned by IANA for the MUDstring)
+ * o MUDstring = the length MUST NOT exceed 255 octets
+ */
+ r = packet_append_prefixed_string(packet, packet_size, &offset, SD_LLDP_TYPE_PRIVATE,
+ sizeof(SD_LLDP_OUI_IANA_MUD), SD_LLDP_OUI_IANA_MUD,
+ lldp_tx->mud_url);
+ if (r < 0)
+ return r;
+
+ r = packet_append_tlv_header(packet, packet_size, &offset, SD_LLDP_TYPE_SYSTEM_CAPABILITIES, 4);
+ if (r < 0)
+ return r;
+
+ unaligned_write_be16(packet + offset, lldp_tx->supported_capabilities);
+ offset += 2;
+ unaligned_write_be16(packet + offset, lldp_tx->enabled_capabilities);
+ offset += 2;
+
+ r = packet_append_tlv_header(packet, packet_size, &offset, SD_LLDP_TYPE_END, 0);
+ if (r < 0)
+ return r;
+
+ *ret_packet_size = offset;
+ *ret_packet = TAKE_PTR(packet);
+ return 0;
+}
+
+static int lldp_tx_send_packet(sd_lldp_tx *lldp_tx, size_t packet_size, const uint8_t *packet) {
+ _cleanup_close_ int fd = -EBADF;
+ union sockaddr_union sa;
+ ssize_t l;
+
+ assert(lldp_tx);
+ assert(lldp_tx->ifindex > 0);
+ assert(packet_size > sizeof(struct ether_header));
+ assert(packet);
+
+ sa = (union sockaddr_union) {
+ .ll.sll_family = AF_PACKET,
+ .ll.sll_protocol = htobe16(ETHERTYPE_LLDP),
+ .ll.sll_ifindex = lldp_tx->ifindex,
+ .ll.sll_halen = ETH_ALEN,
+ };
+ memcpy(sa.ll.sll_addr, lldp_multicast_addr + lldp_tx->mode, ETH_ALEN);
+
+ fd = socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_RAW);
+ if (fd < 0)
+ return -errno;
+
+ l = sendto(fd, packet, packet_size, MSG_NOSIGNAL, &sa.sa, sizeof(sa.ll));
+ if (l < 0)
+ return -errno;
+
+ if ((size_t) l != packet_size)
+ return -EIO;
+
+ return 0;
+}
+
+static int lldp_tx_send(sd_lldp_tx *lldp_tx) {
+ _cleanup_free_ uint8_t *packet = NULL;
+ size_t packet_size = 0; /* avoid false maybe-uninitialized warning */
+ int r;
+
+ assert(lldp_tx);
+
+ r = lldp_tx_create_packet(lldp_tx, &packet_size, &packet);
+ if (r < 0)
+ return r;
+
+ return lldp_tx_send_packet(lldp_tx, packet_size, packet);
+}
+
+int sd_lldp_tx_attach_event(sd_lldp_tx *lldp_tx, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(lldp_tx, -EINVAL);
+ assert_return(!lldp_tx->event, -EBUSY);
+
+ if (event)
+ lldp_tx->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&lldp_tx->event);
+ if (r < 0)
+ return r;
+ }
+
+ lldp_tx->event_priority = priority;
+
+ return 0;
+}
+
+int sd_lldp_tx_detach_event(sd_lldp_tx *lldp_tx) {
+ assert_return(lldp_tx, -EINVAL);
+
+ lldp_tx->timer_event_source = sd_event_source_disable_unref(lldp_tx->timer_event_source);
+ lldp_tx->event = sd_event_unref(lldp_tx->event);
+ return 0;
+}
+
+static usec_t lldp_tx_get_delay(sd_lldp_tx *lldp_tx) {
+ assert(lldp_tx);
+
+ return usec_add(lldp_tx->fast_tx > 0 ? LLDP_FAST_TX_INTERVAL_USEC : LLDP_TX_INTERVAL_USEC,
+ (usec_t) random_u64() % LLDP_TX_JITTER_USEC);
+}
+
+static int lldp_tx_reset_timer(sd_lldp_tx *lldp_tx) {
+ usec_t delay;
+ int r;
+
+ assert(lldp_tx);
+ assert(lldp_tx->timer_event_source);
+
+ delay = lldp_tx_get_delay(lldp_tx);
+
+ r = sd_event_source_set_time_relative(lldp_tx->timer_event_source, delay);
+ if (r < 0)
+ return r;
+
+ return sd_event_source_set_enabled(lldp_tx->timer_event_source, SD_EVENT_ONESHOT);
+}
+
+static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_lldp_tx *lldp_tx = ASSERT_PTR(userdata);
+ int r;
+
+ r = lldp_tx_send(lldp_tx);
+ if (r < 0)
+ log_lldp_tx_errno(lldp_tx, r, "Failed to send packet, ignoring: %m");
+
+ if (lldp_tx->fast_tx > 0)
+ lldp_tx->fast_tx--;
+
+ r = lldp_tx_reset_timer(lldp_tx);
+ if (r < 0)
+ log_lldp_tx_errno(lldp_tx, r, "Failed to reset timer: %m");
+
+ return 0;
+}
+
+int sd_lldp_tx_is_running(sd_lldp_tx *lldp_tx) {
+ int enabled;
+
+ if (!lldp_tx)
+ return 0;
+
+ if (!lldp_tx->timer_event_source)
+ return 0;
+
+ if (sd_event_source_get_enabled(lldp_tx->timer_event_source, &enabled) < 0)
+ return 0;
+
+ return enabled == SD_EVENT_ONESHOT;
+}
+
+int sd_lldp_tx_stop(sd_lldp_tx *lldp_tx) {
+ if (!lldp_tx)
+ return 0;
+
+ if (!lldp_tx->timer_event_source)
+ return 0;
+
+ (void) sd_event_source_set_enabled(lldp_tx->timer_event_source, SD_EVENT_OFF);
+
+ return 1;
+}
+int sd_lldp_tx_start(sd_lldp_tx *lldp_tx) {
+ usec_t delay;
+ int r;
+
+ assert_return(lldp_tx, -EINVAL);
+ assert_return(lldp_tx->event, -EINVAL);
+ assert_return(lldp_tx->ifindex > 0, -EINVAL);
+ assert_return(lldp_tx->mode >= 0 && lldp_tx->mode < _SD_LLDP_MULTICAST_MODE_MAX, -EINVAL);
+ assert_return(!ether_addr_is_null(&lldp_tx->hwaddr), -EINVAL);
+
+ if (sd_lldp_tx_is_running(lldp_tx))
+ return 0;
+
+ lldp_tx->fast_tx = LLDP_FAST_TX_INIT;
+
+ if (lldp_tx->timer_event_source) {
+ r = lldp_tx_reset_timer(lldp_tx);
+ if (r < 0)
+ return log_lldp_tx_errno(lldp_tx, r, "Failed to re-enable timer: %m");
+
+ return 0;
+ }
+
+ delay = lldp_tx_get_delay(lldp_tx);
+
+ r = sd_event_add_time_relative(lldp_tx->event, &lldp_tx->timer_event_source,
+ CLOCK_BOOTTIME, delay, 0,
+ on_timer_event, lldp_tx);
+ if (r < 0)
+ return r;
+
+ (void) sd_event_source_set_description(lldp_tx->timer_event_source, "lldp-tx-timer");
+ (void) sd_event_source_set_priority(lldp_tx->timer_event_source, lldp_tx->event_priority);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c
new file mode 100644
index 0000000..1beed5d
--- /dev/null
+++ b/src/libsystemd-network/sd-ndisc.c
@@ -0,0 +1,381 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+
+#include "sd-ndisc.h"
+
+#include "alloc-util.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "icmp6-util.h"
+#include "in-addr-util.h"
+#include "memory-util.h"
+#include "ndisc-internal.h"
+#include "ndisc-router.h"
+#include "network-common.h"
+#include "random-util.h"
+#include "socket-util.h"
+#include "string-table.h"
+#include "string-util.h"
+
+#define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
+
+static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = {
+ [SD_NDISC_EVENT_TIMEOUT] = "timeout",
+ [SD_NDISC_EVENT_ROUTER] = "router",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t);
+
+static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, sd_ndisc_router *rt) {
+ assert(ndisc);
+ assert(event >= 0 && event < _SD_NDISC_EVENT_MAX);
+
+ if (!ndisc->callback)
+ return (void) log_ndisc(ndisc, "Received '%s' event.", ndisc_event_to_string(event));
+
+ log_ndisc(ndisc, "Invoking callback for '%s' event.", ndisc_event_to_string(event));
+ ndisc->callback(ndisc, event, rt, ndisc->userdata);
+}
+
+int sd_ndisc_set_callback(
+ sd_ndisc *nd,
+ sd_ndisc_callback_t callback,
+ void *userdata) {
+
+ assert_return(nd, -EINVAL);
+
+ nd->callback = callback;
+ nd->userdata = userdata;
+
+ return 0;
+}
+
+int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
+ assert_return(nd, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+ assert_return(nd->fd < 0, -EBUSY);
+
+ nd->ifindex = ifindex;
+ return 0;
+}
+
+int sd_ndisc_set_ifname(sd_ndisc *nd, const char *ifname) {
+ assert_return(nd, -EINVAL);
+ assert_return(ifname, -EINVAL);
+
+ if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
+ return -EINVAL;
+
+ return free_and_strdup(&nd->ifname, ifname);
+}
+
+int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret) {
+ int r;
+
+ assert_return(nd, -EINVAL);
+
+ r = get_ifname(nd->ifindex, &nd->ifname);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = nd->ifname;
+
+ return 0;
+}
+
+int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
+ assert_return(nd, -EINVAL);
+
+ if (mac_addr)
+ nd->mac_addr = *mac_addr;
+ else
+ zero(nd->mac_addr);
+
+ return 0;
+}
+
+int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(nd, -EINVAL);
+ assert_return(nd->fd < 0, -EBUSY);
+ assert_return(!nd->event, -EBUSY);
+
+ if (event)
+ nd->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&nd->event);
+ if (r < 0)
+ return 0;
+ }
+
+ nd->event_priority = priority;
+
+ return 0;
+}
+
+int sd_ndisc_detach_event(sd_ndisc *nd) {
+
+ assert_return(nd, -EINVAL);
+ assert_return(nd->fd < 0, -EBUSY);
+
+ nd->event = sd_event_unref(nd->event);
+ return 0;
+}
+
+sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
+ assert_return(nd, NULL);
+
+ return nd->event;
+}
+
+static void ndisc_reset(sd_ndisc *nd) {
+ assert(nd);
+
+ (void) event_source_disable(nd->timeout_event_source);
+ (void) event_source_disable(nd->timeout_no_ra);
+ nd->retransmit_time = 0;
+ nd->recv_event_source = sd_event_source_disable_unref(nd->recv_event_source);
+ nd->fd = safe_close(nd->fd);
+}
+
+static sd_ndisc *ndisc_free(sd_ndisc *nd) {
+ assert(nd);
+
+ ndisc_reset(nd);
+
+ sd_event_source_unref(nd->timeout_event_source);
+ sd_event_source_unref(nd->timeout_no_ra);
+ sd_ndisc_detach_event(nd);
+
+ free(nd->ifname);
+ return mfree(nd);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc, sd_ndisc, ndisc_free);
+
+int sd_ndisc_new(sd_ndisc **ret) {
+ _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
+
+ assert_return(ret, -EINVAL);
+
+ nd = new(sd_ndisc, 1);
+ if (!nd)
+ return -ENOMEM;
+
+ *nd = (sd_ndisc) {
+ .n_ref = 1,
+ .fd = -EBADF,
+ };
+
+ *ret = TAKE_PTR(nd);
+
+ return 0;
+}
+
+static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
+ int r;
+
+ assert(nd);
+ assert(rt);
+
+ r = ndisc_router_parse(nd, rt);
+ if (r < 0)
+ return r;
+
+ log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %s",
+ rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none",
+ rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium",
+ FORMAT_TIMESPAN(rt->lifetime_usec, USEC_PER_SEC));
+
+ ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
+ return 0;
+}
+
+static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
+ sd_ndisc *nd = ASSERT_PTR(userdata);
+ ssize_t buflen;
+ int r;
+
+ assert(s);
+ assert(nd->event);
+
+ buflen = next_datagram_size_fd(fd);
+ if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
+ return 0;
+ if (buflen < 0) {
+ log_ndisc_errno(nd, buflen, "Failed to determine datagram size to read, ignoring: %m");
+ return 0;
+ }
+
+ rt = ndisc_router_new(buflen);
+ if (!rt)
+ return -ENOMEM;
+
+ r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, &rt->timestamp);
+ if (ERRNO_IS_NEG_TRANSIENT(r) || ERRNO_IS_NEG_DISCONNECT(r))
+ return 0;
+ if (r < 0)
+ switch (r) {
+ case -EADDRNOTAVAIL:
+ log_ndisc(nd, "Received RA from neither link-local nor null address. Ignoring.");
+ return 0;
+
+ case -EMULTIHOP:
+ log_ndisc(nd, "Received RA with invalid hop limit. Ignoring.");
+ return 0;
+
+ case -EPFNOSUPPORT:
+ log_ndisc(nd, "Received invalid source address from ICMPv6 socket. Ignoring.");
+ return 0;
+
+ default:
+ log_ndisc_errno(nd, r, "Unexpected error while reading from ICMPv6, ignoring: %m");
+ return 0;
+ }
+
+ /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states
+ * that hosts MUST discard messages with the null source address. */
+ if (in6_addr_is_null(&rt->address))
+ log_ndisc(nd, "Received RA from null address. Ignoring.");
+
+ (void) event_source_disable(nd->timeout_event_source);
+ (void) ndisc_handle_datagram(nd, rt);
+ return 0;
+}
+
+static usec_t ndisc_timeout_compute_random(usec_t val) {
+ /* compute a time that is random within ±10% of the given value */
+ return val - val / 10 +
+ (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
+}
+
+static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_ndisc *nd = ASSERT_PTR(userdata);
+ usec_t time_now;
+ int r;
+
+ assert(s);
+ assert(nd->event);
+
+ assert_se(sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now) >= 0);
+
+ if (!nd->retransmit_time)
+ nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL);
+ else {
+ if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2)
+ nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL);
+ else
+ nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time);
+ }
+
+ r = event_reset_time(nd->event, &nd->timeout_event_source,
+ CLOCK_BOOTTIME,
+ time_now + nd->retransmit_time, 10 * USEC_PER_MSEC,
+ ndisc_timeout, nd,
+ nd->event_priority, "ndisc-timeout-no-ra", true);
+ if (r < 0)
+ goto fail;
+
+ r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
+ if (r < 0)
+ log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m",
+ FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
+ else
+ log_ndisc(nd, "Sent Router Solicitation, next solicitation in %s",
+ FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
+
+ return 0;
+
+fail:
+ (void) sd_ndisc_stop(nd);
+ return 0;
+}
+
+static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_ndisc *nd = ASSERT_PTR(userdata);
+
+ assert(s);
+
+ log_ndisc(nd, "No RA received before link confirmation timeout");
+
+ (void) event_source_disable(nd->timeout_no_ra);
+ ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
+
+ return 0;
+}
+
+int sd_ndisc_stop(sd_ndisc *nd) {
+ if (!nd)
+ return 0;
+
+ if (nd->fd < 0)
+ return 0;
+
+ log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
+
+ ndisc_reset(nd);
+ return 1;
+}
+
+int sd_ndisc_start(sd_ndisc *nd) {
+ int r;
+ usec_t time_now;
+
+ assert_return(nd, -EINVAL);
+ assert_return(nd->event, -EINVAL);
+ assert_return(nd->ifindex > 0, -EINVAL);
+
+ if (nd->fd >= 0)
+ return 0;
+
+ assert(!nd->recv_event_source);
+
+ r = sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ goto fail;
+
+ nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
+ if (nd->fd < 0)
+ return nd->fd;
+
+ r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
+
+ r = event_reset_time(nd->event, &nd->timeout_event_source,
+ CLOCK_BOOTTIME,
+ time_now + USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */
+ ndisc_timeout, nd,
+ nd->event_priority, "ndisc-timeout", true);
+ if (r < 0)
+ goto fail;
+
+ r = event_reset_time(nd->event, &nd->timeout_no_ra,
+ CLOCK_BOOTTIME,
+ time_now + NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC,
+ ndisc_timeout_no_ra, nd,
+ nd->event_priority, "ndisc-timeout-no-ra", true);
+ if (r < 0)
+ goto fail;
+
+ log_ndisc(nd, "Started IPv6 Router Solicitation client");
+ return 1;
+
+fail:
+ ndisc_reset(nd);
+ return r;
+}
diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c
new file mode 100644
index 0000000..97d306c
--- /dev/null
+++ b/src/libsystemd-network/sd-radv.c
@@ -0,0 +1,1161 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2017 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "sd-radv.h"
+
+#include "alloc-util.h"
+#include "dns-domain.h"
+#include "ether-addr-util.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "icmp6-util.h"
+#include "in-addr-util.h"
+#include "iovec-util.h"
+#include "macro.h"
+#include "memory-util.h"
+#include "network-common.h"
+#include "radv-internal.h"
+#include "random-util.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "unaligned.h"
+
+int sd_radv_new(sd_radv **ret) {
+ _cleanup_(sd_radv_unrefp) sd_radv *ra = NULL;
+
+ assert_return(ret, -EINVAL);
+
+ ra = new(sd_radv, 1);
+ if (!ra)
+ return -ENOMEM;
+
+ *ra = (sd_radv) {
+ .n_ref = 1,
+ .fd = -EBADF,
+ .lifetime_usec = RADV_DEFAULT_ROUTER_LIFETIME_USEC,
+ };
+
+ *ret = TAKE_PTR(ra);
+
+ return 0;
+}
+
+int sd_radv_attach_event(sd_radv *ra, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(ra, -EINVAL);
+ assert_return(!ra->event, -EBUSY);
+
+ if (event)
+ ra->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&ra->event);
+ if (r < 0)
+ return 0;
+ }
+
+ ra->event_priority = priority;
+
+ return 0;
+}
+
+int sd_radv_detach_event(sd_radv *ra) {
+
+ assert_return(ra, -EINVAL);
+
+ ra->event = sd_event_unref(ra->event);
+ return 0;
+}
+
+sd_event *sd_radv_get_event(sd_radv *ra) {
+ assert_return(ra, NULL);
+
+ return ra->event;
+}
+
+int sd_radv_is_running(sd_radv *ra) {
+ assert_return(ra, false);
+
+ return ra->state != RADV_STATE_IDLE;
+}
+
+static void radv_reset(sd_radv *ra) {
+ assert(ra);
+
+ (void) event_source_disable(ra->timeout_event_source);
+
+ ra->recv_event_source = sd_event_source_disable_unref(ra->recv_event_source);
+
+ ra->ra_sent = 0;
+}
+
+static sd_radv *radv_free(sd_radv *ra) {
+ if (!ra)
+ return NULL;
+
+ LIST_CLEAR(prefix, ra->prefixes, sd_radv_prefix_unref);
+ LIST_CLEAR(prefix, ra->route_prefixes, sd_radv_route_prefix_unref);
+ LIST_CLEAR(prefix, ra->pref64_prefixes, sd_radv_pref64_prefix_unref);
+
+ free(ra->rdnss);
+ free(ra->dnssl);
+
+ radv_reset(ra);
+
+ sd_event_source_unref(ra->timeout_event_source);
+ sd_radv_detach_event(ra);
+
+ ra->fd = safe_close(ra->fd);
+ free(ra->ifname);
+
+ return mfree(ra);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv, sd_radv, radv_free);
+
+static bool router_lifetime_is_valid(usec_t lifetime_usec) {
+ return lifetime_usec == 0 ||
+ (lifetime_usec >= RADV_MIN_ROUTER_LIFETIME_USEC &&
+ lifetime_usec <= RADV_MAX_ROUTER_LIFETIME_USEC);
+}
+
+static int radv_send(sd_radv *ra, const struct in6_addr *dst, usec_t lifetime_usec) {
+ struct sockaddr_in6 dst_addr = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_ALL_NODES_MULTICAST_INIT,
+ };
+ struct nd_router_advert adv = {};
+ struct {
+ struct nd_opt_hdr opthdr;
+ struct ether_addr slladdr;
+ } _packed_ opt_mac = {
+ .opthdr = {
+ .nd_opt_type = ND_OPT_SOURCE_LINKADDR,
+ .nd_opt_len = (sizeof(struct nd_opt_hdr) +
+ sizeof(struct ether_addr) - 1) /8 + 1,
+ },
+ };
+ struct nd_opt_mtu opt_mtu = {
+ .nd_opt_mtu_type = ND_OPT_MTU,
+ .nd_opt_mtu_len = 1,
+ };
+ /* Reserve iov space for RA header, linkaddr, MTU, N prefixes, N routes, N pref64 prefixes, RDNSS,
+ * DNSSL, and home agent. */
+ struct iovec iov[6 + ra->n_prefixes + ra->n_route_prefixes + ra->n_pref64_prefixes];
+ struct msghdr msg = {
+ .msg_name = &dst_addr,
+ .msg_namelen = sizeof(dst_addr),
+ .msg_iov = iov,
+ };
+ usec_t time_now;
+ int r;
+
+ assert(ra);
+ assert(router_lifetime_is_valid(lifetime_usec));
+
+ r = sd_event_now(ra->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ return r;
+
+ if (dst && in6_addr_is_set(dst))
+ dst_addr.sin6_addr = *dst;
+
+ adv.nd_ra_type = ND_ROUTER_ADVERT;
+ adv.nd_ra_curhoplimit = ra->hop_limit;
+ adv.nd_ra_retransmit = usec_to_be32_msec(ra->retransmit_usec);
+ adv.nd_ra_flags_reserved = ra->flags;
+ assert_cc(RADV_MAX_ROUTER_LIFETIME_USEC <= UINT16_MAX * USEC_PER_SEC);
+ adv.nd_ra_router_lifetime = usec_to_be16_sec(lifetime_usec);
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(&adv, sizeof(adv));
+
+ /* MAC address is optional, either because the link does not use L2
+ addresses or load sharing is desired. See RFC 4861, Section 4.2 */
+ if (!ether_addr_is_null(&ra->mac_addr)) {
+ opt_mac.slladdr = ra->mac_addr;
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(&opt_mac, sizeof(opt_mac));
+ }
+
+ if (ra->mtu > 0) {
+ opt_mtu.nd_opt_mtu_mtu = htobe32(ra->mtu);
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(&opt_mtu, sizeof(opt_mtu));
+ }
+
+ LIST_FOREACH(prefix, p, ra->prefixes) {
+ usec_t lifetime_valid_usec, lifetime_preferred_usec;
+
+ lifetime_valid_usec = MIN(usec_sub_unsigned(p->valid_until, time_now),
+ p->lifetime_valid_usec);
+
+ lifetime_preferred_usec = MIN3(usec_sub_unsigned(p->preferred_until, time_now),
+ p->lifetime_preferred_usec,
+ lifetime_valid_usec);
+
+ p->opt.lifetime_valid = usec_to_be32_sec(lifetime_valid_usec);
+ p->opt.lifetime_preferred = usec_to_be32_sec(lifetime_preferred_usec);
+
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(&p->opt, sizeof(p->opt));
+ }
+
+ LIST_FOREACH(prefix, rt, ra->route_prefixes) {
+ rt->opt.lifetime = usec_to_be32_sec(MIN(usec_sub_unsigned(rt->valid_until, time_now),
+ rt->lifetime_usec));
+
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(&rt->opt, sizeof(rt->opt));
+ }
+
+ LIST_FOREACH(prefix, p, ra->pref64_prefixes)
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(&p->opt, sizeof(p->opt));
+
+ if (ra->rdnss)
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(ra->rdnss, ra->rdnss->length * 8);
+
+ if (ra->dnssl)
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(ra->dnssl, ra->dnssl->length * 8);
+
+ if (FLAGS_SET(ra->flags, ND_RA_FLAG_HOME_AGENT)) {
+ ra->home_agent.nd_opt_home_agent_info_type = ND_OPT_HOME_AGENT_INFO;
+ ra->home_agent.nd_opt_home_agent_info_len = 1;
+
+ /* 0 means to place the current Router Lifetime value */
+ if (ra->home_agent.nd_opt_home_agent_info_lifetime == 0)
+ ra->home_agent.nd_opt_home_agent_info_lifetime = adv.nd_ra_router_lifetime;
+
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(&ra->home_agent, sizeof(ra->home_agent));
+ }
+
+ if (sendmsg(ra->fd, &msg, 0) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ sd_radv *ra = ASSERT_PTR(userdata);
+ struct in6_addr src;
+ triple_timestamp timestamp;
+ int r;
+
+ assert(s);
+ assert(ra->event);
+
+ ssize_t buflen = next_datagram_size_fd(fd);
+ if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
+ return 0;
+ if (buflen < 0) {
+ log_radv_errno(ra, buflen, "Failed to determine datagram size to read, ignoring: %m");
+ return 0;
+ }
+
+ _cleanup_free_ char *buf = new0(char, buflen);
+ if (!buf)
+ return -ENOMEM;
+
+ r = icmp6_receive(fd, buf, buflen, &src, &timestamp);
+ if (ERRNO_IS_NEG_TRANSIENT(r) || ERRNO_IS_NEG_DISCONNECT(r))
+ return 0;
+ if (r < 0)
+ switch (r) {
+ case -EADDRNOTAVAIL:
+ log_radv(ra, "Received RS from neither link-local nor null address. Ignoring");
+ return 0;
+
+ case -EMULTIHOP:
+ log_radv(ra, "Received RS with invalid hop limit. Ignoring.");
+ return 0;
+
+ case -EPFNOSUPPORT:
+ log_radv(ra, "Received invalid source address from ICMPv6 socket. Ignoring.");
+ return 0;
+
+ default:
+ log_radv_errno(ra, r, "Unexpected error receiving from ICMPv6 socket, ignoring: %m");
+ return 0;
+ }
+
+ if ((size_t) buflen < sizeof(struct nd_router_solicit)) {
+ log_radv(ra, "Too short packet received, ignoring");
+ return 0;
+ }
+
+ /* TODO: if the sender address is null, check that the message does not have the source link-layer
+ * address option. See RFC 4861 Section 6.1.1. */
+
+ const char *addr = IN6_ADDR_TO_STRING(&src);
+
+ r = radv_send(ra, &src, ra->lifetime_usec);
+ if (r < 0)
+ log_radv_errno(ra, r, "Unable to send solicited Router Advertisement to %s, ignoring: %m", addr);
+ else
+ log_radv(ra, "Sent solicited Router Advertisement to %s", addr);
+
+ return 0;
+}
+
+static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+ usec_t min_timeout, max_timeout, time_now, timeout;
+ sd_radv *ra = ASSERT_PTR(userdata);
+ int r;
+
+ assert(s);
+ assert(ra->event);
+ assert(router_lifetime_is_valid(ra->lifetime_usec));
+
+ r = sd_event_now(ra->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ goto fail;
+
+ r = radv_send(ra, NULL, ra->lifetime_usec);
+ if (r < 0)
+ log_radv_errno(ra, r, "Unable to send Router Advertisement, ignoring: %m");
+
+ /* RFC 4861, Section 6.2.4, sending initial Router Advertisements */
+ if (ra->ra_sent < RADV_MAX_INITIAL_RTR_ADVERTISEMENTS)
+ max_timeout = RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC;
+ else
+ max_timeout = RADV_DEFAULT_MAX_TIMEOUT_USEC;
+
+ /* RFC 4861, Section 6.2.1, lifetime must be at least MaxRtrAdvInterval,
+ * so lower the interval here */
+ if (ra->lifetime_usec > 0)
+ max_timeout = MIN(max_timeout, ra->lifetime_usec);
+
+ if (max_timeout >= 9 * USEC_PER_SEC)
+ min_timeout = max_timeout / 3;
+ else
+ min_timeout = max_timeout * 3 / 4;
+
+ /* RFC 4861, Section 6.2.1.
+ * MaxRtrAdvInterval MUST be no less than 4 seconds and no greater than 1800 seconds.
+ * MinRtrAdvInterval MUST be no less than 3 seconds and no greater than .75 * MaxRtrAdvInterval. */
+ assert(max_timeout >= RADV_MIN_MAX_TIMEOUT_USEC);
+ assert(max_timeout <= RADV_MAX_MAX_TIMEOUT_USEC);
+ assert(min_timeout >= RADV_MIN_MIN_TIMEOUT_USEC);
+ assert(min_timeout <= max_timeout * 3 / 4);
+
+ timeout = min_timeout + random_u64_range(max_timeout - min_timeout);
+ log_radv(ra, "Next Router Advertisement in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC));
+
+ r = event_reset_time(ra->event, &ra->timeout_event_source,
+ CLOCK_BOOTTIME,
+ usec_add(time_now, timeout), MSEC_PER_SEC,
+ radv_timeout, ra,
+ ra->event_priority, "radv-timeout", true);
+ if (r < 0)
+ goto fail;
+
+ ra->ra_sent++;
+
+ return 0;
+
+fail:
+ sd_radv_stop(ra);
+
+ return 0;
+}
+
+int sd_radv_stop(sd_radv *ra) {
+ int r;
+
+ if (!ra)
+ return 0;
+
+ if (ra->state == RADV_STATE_IDLE)
+ return 0;
+
+ log_radv(ra, "Stopping IPv6 Router Advertisement daemon");
+
+ /* RFC 4861, Section 6.2.5, send at least one Router Advertisement
+ with zero lifetime */
+ r = radv_send(ra, NULL, 0);
+ if (r < 0)
+ log_radv_errno(ra, r, "Unable to send last Router Advertisement with router lifetime set to zero, ignoring: %m");
+
+ radv_reset(ra);
+ ra->fd = safe_close(ra->fd);
+ ra->state = RADV_STATE_IDLE;
+
+ return 0;
+}
+
+int sd_radv_start(sd_radv *ra) {
+ int r;
+
+ assert_return(ra, -EINVAL);
+ assert_return(ra->event, -EINVAL);
+ assert_return(ra->ifindex > 0, -EINVAL);
+
+ if (ra->state != RADV_STATE_IDLE)
+ return 0;
+
+ r = event_reset_time(ra->event, &ra->timeout_event_source,
+ CLOCK_BOOTTIME,
+ 0, 0,
+ radv_timeout, ra,
+ ra->event_priority, "radv-timeout", true);
+ if (r < 0)
+ goto fail;
+
+ r = icmp6_bind_router_advertisement(ra->ifindex);
+ if (r < 0)
+ goto fail;
+
+ ra->fd = r;
+
+ r = sd_event_add_io(ra->event, &ra->recv_event_source, ra->fd, EPOLLIN, radv_recv, ra);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_priority(ra->recv_event_source, ra->event_priority);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(ra->recv_event_source, "radv-receive-message");
+
+ ra->state = RADV_STATE_ADVERTISING;
+
+ log_radv(ra, "Started IPv6 Router Advertisement daemon");
+
+ return 0;
+
+ fail:
+ radv_reset(ra);
+
+ return r;
+}
+
+int sd_radv_set_ifindex(sd_radv *ra, int ifindex) {
+ assert_return(ra, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+
+ if (ra->state != RADV_STATE_IDLE)
+ return -EBUSY;
+
+ ra->ifindex = ifindex;
+
+ return 0;
+}
+
+int sd_radv_set_ifname(sd_radv *ra, const char *ifname) {
+ assert_return(ra, -EINVAL);
+ assert_return(ifname, -EINVAL);
+
+ if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
+ return -EINVAL;
+
+ return free_and_strdup(&ra->ifname, ifname);
+}
+
+int sd_radv_get_ifname(sd_radv *ra, const char **ret) {
+ int r;
+
+ assert_return(ra, -EINVAL);
+
+ r = get_ifname(ra->ifindex, &ra->ifname);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = ra->ifname;
+
+ return 0;
+}
+
+int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != RADV_STATE_IDLE)
+ return -EBUSY;
+
+ if (mac_addr)
+ ra->mac_addr = *mac_addr;
+ else
+ zero(ra->mac_addr);
+
+ return 0;
+}
+
+int sd_radv_set_mtu(sd_radv *ra, uint32_t mtu) {
+ assert_return(ra, -EINVAL);
+ assert_return(mtu >= 1280, -EINVAL);
+
+ ra->mtu = mtu;
+
+ return 0;
+}
+
+int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != RADV_STATE_IDLE)
+ return -EBUSY;
+
+ ra->hop_limit = hop_limit;
+
+ return 0;
+}
+
+int sd_radv_set_retransmit(sd_radv *ra, uint64_t usec) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != RADV_STATE_IDLE)
+ return -EBUSY;
+
+ if (usec > RADV_MAX_RETRANSMIT_USEC)
+ return -EINVAL;
+
+ ra->retransmit_usec = usec;
+ return 0;
+}
+
+int sd_radv_set_router_lifetime(sd_radv *ra, uint64_t usec) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != RADV_STATE_IDLE)
+ return -EBUSY;
+
+ if (!router_lifetime_is_valid(usec))
+ return -EINVAL;
+
+ /* RFC 4191, Section 2.2, "...If the Router Lifetime is zero, the preference value MUST be set
+ * to (00) by the sender..." */
+ if (usec == 0 &&
+ (ra->flags & (0x3 << 3)) != (SD_NDISC_PREFERENCE_MEDIUM << 3))
+ return -EINVAL;
+
+ ra->lifetime_usec = usec;
+ return 0;
+}
+
+int sd_radv_set_managed_information(sd_radv *ra, int managed) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != RADV_STATE_IDLE)
+ return -EBUSY;
+
+ SET_FLAG(ra->flags, ND_RA_FLAG_MANAGED, managed);
+
+ return 0;
+}
+
+int sd_radv_set_other_information(sd_radv *ra, int other) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != RADV_STATE_IDLE)
+ return -EBUSY;
+
+ SET_FLAG(ra->flags, ND_RA_FLAG_OTHER, other);
+
+ return 0;
+}
+
+int sd_radv_set_preference(sd_radv *ra, unsigned preference) {
+ assert_return(ra, -EINVAL);
+ assert_return(IN_SET(preference,
+ SD_NDISC_PREFERENCE_LOW,
+ SD_NDISC_PREFERENCE_MEDIUM,
+ SD_NDISC_PREFERENCE_HIGH), -EINVAL);
+
+ /* RFC 4191, Section 2.2, "...If the Router Lifetime is zero, the preference value MUST be set
+ * to (00) by the sender..." */
+ if (ra->lifetime_usec == 0 && preference != SD_NDISC_PREFERENCE_MEDIUM)
+ return -EINVAL;
+
+ ra->flags = (ra->flags & ~(0x3 << 3)) | (preference << 3);
+
+ return 0;
+}
+
+int sd_radv_set_home_agent_information(sd_radv *ra, int home_agent) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != RADV_STATE_IDLE)
+ return -EBUSY;
+
+ SET_FLAG(ra->flags, ND_RA_FLAG_HOME_AGENT, home_agent);
+
+ return 0;
+}
+
+int sd_radv_set_home_agent_preference(sd_radv *ra, uint16_t preference) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != RADV_STATE_IDLE)
+ return -EBUSY;
+
+ ra->home_agent.nd_opt_home_agent_info_preference = htobe16(preference);
+
+ return 0;
+}
+
+int sd_radv_set_home_agent_lifetime(sd_radv *ra, uint64_t lifetime_usec) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != RADV_STATE_IDLE)
+ return -EBUSY;
+
+ if (lifetime_usec > RADV_HOME_AGENT_MAX_LIFETIME_USEC)
+ return -EINVAL;
+
+ ra->home_agent.nd_opt_home_agent_info_lifetime = usec_to_be16_sec(lifetime_usec);
+ return 0;
+}
+
+int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) {
+ sd_radv_prefix *found = NULL;
+ int r;
+
+ assert_return(ra, -EINVAL);
+ assert_return(p, -EINVAL);
+
+ /* Refuse prefixes that don't have a prefix set */
+ if (in6_addr_is_null(&p->opt.in6_addr))
+ return -ENOEXEC;
+
+ const char *addr_p = IN6_ADDR_PREFIX_TO_STRING(&p->opt.in6_addr, p->opt.prefixlen);
+
+ LIST_FOREACH(prefix, cur, ra->prefixes) {
+ r = in_addr_prefix_intersect(AF_INET6,
+ (const union in_addr_union*) &cur->opt.in6_addr,
+ cur->opt.prefixlen,
+ (const union in_addr_union*) &p->opt.in6_addr,
+ p->opt.prefixlen);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (cur->opt.prefixlen == p->opt.prefixlen) {
+ found = cur;
+ break;
+ }
+
+ return log_radv_errno(ra, SYNTHETIC_ERRNO(EEXIST),
+ "IPv6 prefix %s conflicts with %s, ignoring.",
+ addr_p,
+ IN6_ADDR_PREFIX_TO_STRING(&cur->opt.in6_addr, cur->opt.prefixlen));
+ }
+
+ if (found) {
+ /* p and cur may be equivalent. First increment the reference counter. */
+ sd_radv_prefix_ref(p);
+
+ /* Then, remove the old entry. */
+ LIST_REMOVE(prefix, ra->prefixes, found);
+ sd_radv_prefix_unref(found);
+
+ /* Finally, add the new entry. */
+ LIST_APPEND(prefix, ra->prefixes, p);
+
+ log_radv(ra, "Updated/replaced IPv6 prefix %s (preferred: %s, valid: %s)",
+ addr_p,
+ FORMAT_TIMESPAN(p->lifetime_preferred_usec, USEC_PER_SEC),
+ FORMAT_TIMESPAN(p->lifetime_valid_usec, USEC_PER_SEC));
+ } else {
+ /* The prefix is new. Let's simply add it. */
+
+ sd_radv_prefix_ref(p);
+ LIST_APPEND(prefix, ra->prefixes, p);
+ ra->n_prefixes++;
+
+ log_radv(ra, "Added prefix %s", addr_p);
+ }
+
+ if (ra->state == RADV_STATE_IDLE)
+ return 0;
+
+ if (ra->ra_sent == 0)
+ return 0;
+
+ /* If RAs have already been sent, send an RA immediately to announce the newly-added prefix */
+ r = radv_send(ra, NULL, ra->lifetime_usec);
+ if (r < 0)
+ log_radv_errno(ra, r, "Unable to send Router Advertisement for added prefix %s, ignoring: %m", addr_p);
+ else
+ log_radv(ra, "Sent Router Advertisement for added/updated prefix %s.", addr_p);
+
+ return 0;
+}
+
+void sd_radv_remove_prefix(
+ sd_radv *ra,
+ const struct in6_addr *prefix,
+ unsigned char prefixlen) {
+
+ if (!ra)
+ return;
+
+ if (!prefix)
+ return;
+
+ LIST_FOREACH(prefix, cur, ra->prefixes) {
+ if (prefixlen != cur->opt.prefixlen)
+ continue;
+
+ if (!in6_addr_equal(prefix, &cur->opt.in6_addr))
+ continue;
+
+ LIST_REMOVE(prefix, ra->prefixes, cur);
+ ra->n_prefixes--;
+ sd_radv_prefix_unref(cur);
+ return;
+ }
+}
+
+int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p) {
+ sd_radv_route_prefix *found = NULL;
+ int r;
+
+ assert_return(ra, -EINVAL);
+ assert_return(p, -EINVAL);
+
+ const char *addr_p = IN6_ADDR_PREFIX_TO_STRING(&p->opt.in6_addr, p->opt.prefixlen);
+
+ LIST_FOREACH(prefix, cur, ra->route_prefixes) {
+ r = in_addr_prefix_intersect(AF_INET6,
+ (const union in_addr_union*) &cur->opt.in6_addr,
+ cur->opt.prefixlen,
+ (const union in_addr_union*) &p->opt.in6_addr,
+ p->opt.prefixlen);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (cur->opt.prefixlen == p->opt.prefixlen) {
+ found = cur;
+ break;
+ }
+
+ return log_radv_errno(ra, SYNTHETIC_ERRNO(EEXIST),
+ "IPv6 route prefix %s conflicts with %s, ignoring.",
+ addr_p,
+ IN6_ADDR_PREFIX_TO_STRING(&cur->opt.in6_addr, cur->opt.prefixlen));
+ }
+
+ if (found) {
+ /* p and cur may be equivalent. First increment the reference counter. */
+ sd_radv_route_prefix_ref(p);
+
+ /* Then, remove the old entry. */
+ LIST_REMOVE(prefix, ra->route_prefixes, found);
+ sd_radv_route_prefix_unref(found);
+
+ /* Finally, add the new entry. */
+ LIST_APPEND(prefix, ra->route_prefixes, p);
+
+ log_radv(ra, "Updated/replaced IPv6 route prefix %s (lifetime: %s)",
+ strna(addr_p),
+ FORMAT_TIMESPAN(p->lifetime_usec, USEC_PER_SEC));
+ } else {
+ /* The route prefix is new. Let's simply add it. */
+
+ sd_radv_route_prefix_ref(p);
+ LIST_APPEND(prefix, ra->route_prefixes, p);
+ ra->n_route_prefixes++;
+
+ log_radv(ra, "Added route prefix %s", strna(addr_p));
+ }
+
+ if (ra->state == RADV_STATE_IDLE)
+ return 0;
+
+ if (ra->ra_sent == 0)
+ return 0;
+
+ /* If RAs have already been sent, send an RA immediately to announce the newly-added route prefix */
+ r = radv_send(ra, NULL, ra->lifetime_usec);
+ if (r < 0)
+ log_radv_errno(ra, r, "Unable to send Router Advertisement for added route prefix %s, ignoring: %m",
+ strna(addr_p));
+ else
+ log_radv(ra, "Sent Router Advertisement for added route prefix %s.", strna(addr_p));
+
+ return 0;
+}
+
+int sd_radv_add_pref64_prefix(sd_radv *ra, sd_radv_pref64_prefix *p) {
+ sd_radv_pref64_prefix *found = NULL;
+ int r;
+
+ assert_return(ra, -EINVAL);
+ assert_return(p, -EINVAL);
+
+ const char *addr_p = IN6_ADDR_PREFIX_TO_STRING(&p->in6_addr, p->prefixlen);
+
+ LIST_FOREACH(prefix, cur, ra->pref64_prefixes) {
+ r = in_addr_prefix_intersect(AF_INET6,
+ (const union in_addr_union*) &cur->in6_addr,
+ cur->prefixlen,
+ (const union in_addr_union*) &p->in6_addr,
+ p->prefixlen);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (cur->prefixlen == p->prefixlen) {
+ found = cur;
+ break;
+ }
+
+ return log_radv_errno(ra, SYNTHETIC_ERRNO(EEXIST),
+ "IPv6 PREF64 prefix %s conflicts with %s, ignoring.",
+ addr_p,
+ IN6_ADDR_PREFIX_TO_STRING(&cur->in6_addr, cur->prefixlen));
+ }
+
+ if (found) {
+ /* p and cur may be equivalent. First increment the reference counter. */
+ sd_radv_pref64_prefix_ref(p);
+
+ /* Then, remove the old entry. */
+ LIST_REMOVE(prefix, ra->pref64_prefixes, found);
+ sd_radv_pref64_prefix_unref(found);
+
+ /* Finally, add the new entry. */
+ LIST_APPEND(prefix, ra->pref64_prefixes, p);
+
+ log_radv(ra, "Updated/replaced IPv6 PREF64 prefix %s (lifetime: %s)",
+ strna(addr_p),
+ FORMAT_TIMESPAN(p->lifetime_usec, USEC_PER_SEC));
+ } else {
+ /* The route prefix is new. Let's simply add it. */
+
+ sd_radv_pref64_prefix_ref(p);
+ LIST_APPEND(prefix, ra->pref64_prefixes, p);
+ ra->n_pref64_prefixes++;
+
+ log_radv(ra, "Added PREF64 prefix %s", strna(addr_p));
+ }
+
+ if (ra->state == RADV_STATE_IDLE)
+ return 0;
+
+ if (ra->ra_sent == 0)
+ return 0;
+
+ /* If RAs have already been sent, send an RA immediately to announce the newly-added route prefix */
+ r = radv_send(ra, NULL, ra->lifetime_usec);
+ if (r < 0)
+ log_radv_errno(ra, r, "Unable to send Router Advertisement for added PREF64 prefix %s, ignoring: %m",
+ strna(addr_p));
+ else
+ log_radv(ra, "Sent Router Advertisement for added PREF64 prefix %s.", strna(addr_p));
+
+ return 0;
+}
+
+int sd_radv_set_rdnss(
+ sd_radv *ra,
+ uint64_t lifetime_usec,
+ const struct in6_addr *dns,
+ size_t n_dns) {
+
+ _cleanup_free_ struct sd_radv_opt_dns *opt_rdnss = NULL;
+ size_t len;
+
+ assert_return(ra, -EINVAL);
+ assert_return(n_dns < 128, -EINVAL);
+
+ if (lifetime_usec > RADV_RDNSS_MAX_LIFETIME_USEC)
+ return -EINVAL;
+
+ if (!dns || n_dns == 0) {
+ ra->rdnss = mfree(ra->rdnss);
+ ra->n_rdnss = 0;
+
+ return 0;
+ }
+
+ len = sizeof(struct sd_radv_opt_dns) + sizeof(struct in6_addr) * n_dns;
+
+ opt_rdnss = malloc0(len);
+ if (!opt_rdnss)
+ return -ENOMEM;
+
+ opt_rdnss->type = RADV_OPT_RDNSS;
+ opt_rdnss->length = len / 8;
+ opt_rdnss->lifetime = usec_to_be32_sec(lifetime_usec);
+
+ memcpy(opt_rdnss + 1, dns, n_dns * sizeof(struct in6_addr));
+
+ free_and_replace(ra->rdnss, opt_rdnss);
+
+ ra->n_rdnss = n_dns;
+
+ return 0;
+}
+
+int sd_radv_set_dnssl(
+ sd_radv *ra,
+ uint64_t lifetime_usec,
+ char **search_list) {
+
+ _cleanup_free_ struct sd_radv_opt_dns *opt_dnssl = NULL;
+ size_t len = 0;
+ uint8_t *p;
+
+ assert_return(ra, -EINVAL);
+
+ if (lifetime_usec > RADV_DNSSL_MAX_LIFETIME_USEC)
+ return -EINVAL;
+
+ if (strv_isempty(search_list)) {
+ ra->dnssl = mfree(ra->dnssl);
+ return 0;
+ }
+
+ STRV_FOREACH(s, search_list)
+ len += strlen(*s) + 2;
+
+ len = (sizeof(struct sd_radv_opt_dns) + len + 7) & ~0x7;
+
+ opt_dnssl = malloc0(len);
+ if (!opt_dnssl)
+ return -ENOMEM;
+
+ opt_dnssl->type = RADV_OPT_DNSSL;
+ opt_dnssl->length = len / 8;
+ opt_dnssl->lifetime = usec_to_be32_sec(lifetime_usec);
+
+ p = (uint8_t *)(opt_dnssl + 1);
+ len -= sizeof(struct sd_radv_opt_dns);
+
+ STRV_FOREACH(s, search_list) {
+ int r;
+
+ r = dns_name_to_wire_format(*s, p, len, false);
+ if (r < 0)
+ return r;
+
+ if (len < (size_t)r)
+ return -ENOBUFS;
+
+ p += r;
+ len -= r;
+ }
+
+ free_and_replace(ra->dnssl, opt_dnssl);
+
+ return 0;
+}
+
+int sd_radv_prefix_new(sd_radv_prefix **ret) {
+ sd_radv_prefix *p;
+
+ assert_return(ret, -EINVAL);
+
+ p = new(sd_radv_prefix, 1);
+ if (!p)
+ return -ENOMEM;
+
+ *p = (sd_radv_prefix) {
+ .n_ref = 1,
+
+ .opt.type = ND_OPT_PREFIX_INFORMATION,
+ .opt.length = (sizeof(p->opt) - 1)/8 + 1,
+ .opt.prefixlen = 64,
+
+ /* RFC 4861, Section 6.2.1 */
+ .opt.flags = ND_OPT_PI_FLAG_ONLINK|ND_OPT_PI_FLAG_AUTO,
+
+ .lifetime_valid_usec = RADV_DEFAULT_VALID_LIFETIME_USEC,
+ .lifetime_preferred_usec = RADV_DEFAULT_PREFERRED_LIFETIME_USEC,
+ .valid_until = USEC_INFINITY,
+ .preferred_until = USEC_INFINITY,
+ };
+
+ *ret = p;
+ return 0;
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv_prefix, sd_radv_prefix, mfree);
+
+int sd_radv_prefix_set_prefix(
+ sd_radv_prefix *p,
+ const struct in6_addr *in6_addr,
+ unsigned char prefixlen) {
+
+ assert_return(p, -EINVAL);
+ assert_return(in6_addr, -EINVAL);
+
+ if (prefixlen < 3 || prefixlen > 128)
+ return -EINVAL;
+
+ if (prefixlen > 64)
+ /* unusual but allowed, log it */
+ log_radv(NULL, "Unusual prefix length %d greater than 64", prefixlen);
+
+ p->opt.in6_addr = *in6_addr;
+ p->opt.prefixlen = prefixlen;
+
+ return 0;
+}
+
+int sd_radv_prefix_get_prefix(
+ sd_radv_prefix *p,
+ struct in6_addr *ret_in6_addr,
+ unsigned char *ret_prefixlen) {
+
+ assert_return(p, -EINVAL);
+ assert_return(ret_in6_addr, -EINVAL);
+ assert_return(ret_prefixlen, -EINVAL);
+
+ *ret_in6_addr = p->opt.in6_addr;
+ *ret_prefixlen = p->opt.prefixlen;
+
+ return 0;
+}
+
+int sd_radv_prefix_set_onlink(sd_radv_prefix *p, int onlink) {
+ assert_return(p, -EINVAL);
+
+ SET_FLAG(p->opt.flags, ND_OPT_PI_FLAG_ONLINK, onlink);
+
+ return 0;
+}
+
+int sd_radv_prefix_set_address_autoconfiguration(sd_radv_prefix *p, int address_autoconfiguration) {
+ assert_return(p, -EINVAL);
+
+ SET_FLAG(p->opt.flags, ND_OPT_PI_FLAG_AUTO, address_autoconfiguration);
+
+ return 0;
+}
+
+int sd_radv_prefix_set_valid_lifetime(sd_radv_prefix *p, uint64_t lifetime_usec, uint64_t valid_until) {
+ assert_return(p, -EINVAL);
+
+ p->lifetime_valid_usec = lifetime_usec;
+ p->valid_until = valid_until;
+
+ return 0;
+}
+
+int sd_radv_prefix_set_preferred_lifetime(sd_radv_prefix *p, uint64_t lifetime_usec, uint64_t valid_until) {
+ assert_return(p, -EINVAL);
+
+ p->lifetime_preferred_usec = lifetime_usec;
+ p->preferred_until = valid_until;
+
+ return 0;
+}
+
+int sd_radv_route_prefix_new(sd_radv_route_prefix **ret) {
+ sd_radv_route_prefix *p;
+
+ assert_return(ret, -EINVAL);
+
+ p = new(sd_radv_route_prefix, 1);
+ if (!p)
+ return -ENOMEM;
+
+ *p = (sd_radv_route_prefix) {
+ .n_ref = 1,
+
+ .opt.type = RADV_OPT_ROUTE_INFORMATION,
+ .opt.length = DIV_ROUND_UP(sizeof(p->opt), 8),
+ .opt.prefixlen = 64,
+
+ .lifetime_usec = RADV_DEFAULT_VALID_LIFETIME_USEC,
+ .valid_until = USEC_INFINITY,
+ };
+
+ *ret = p;
+ return 0;
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv_route_prefix, sd_radv_route_prefix, mfree);
+
+int sd_radv_route_prefix_set_prefix(
+ sd_radv_route_prefix *p,
+ const struct in6_addr *in6_addr,
+ unsigned char prefixlen) {
+
+ assert_return(p, -EINVAL);
+ assert_return(in6_addr, -EINVAL);
+
+ if (prefixlen > 128)
+ return -EINVAL;
+
+ if (prefixlen > 64)
+ /* unusual but allowed, log it */
+ log_radv(NULL, "Unusual prefix length %u greater than 64", prefixlen);
+
+ p->opt.in6_addr = *in6_addr;
+ p->opt.prefixlen = prefixlen;
+
+ return 0;
+}
+
+int sd_radv_route_prefix_set_lifetime(sd_radv_route_prefix *p, uint64_t lifetime_usec, uint64_t valid_until) {
+ assert_return(p, -EINVAL);
+
+ p->lifetime_usec = lifetime_usec;
+ p->valid_until = valid_until;
+
+ return 0;
+}
+
+int sd_radv_pref64_prefix_new(sd_radv_pref64_prefix **ret) {
+ sd_radv_pref64_prefix *p;
+
+ assert_return(ret, -EINVAL);
+
+ p = new(sd_radv_pref64_prefix, 1);
+ if (!p)
+ return -ENOMEM;
+
+ *p = (sd_radv_pref64_prefix) {
+ .n_ref = 1,
+
+ .opt.type = RADV_OPT_PREF64,
+ .opt.length = 2,
+ };
+
+ *ret = p;
+ return 0;
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv_pref64_prefix, sd_radv_pref64_prefix, mfree);
+
+int sd_radv_pref64_prefix_set_prefix(
+ sd_radv_pref64_prefix *p,
+ const struct in6_addr *prefix,
+ uint8_t prefixlen,
+ uint64_t lifetime_usec) {
+
+ uint16_t pref64_lifetime;
+ uint8_t prefixlen_code;
+ int r;
+
+ assert_return(p, -EINVAL);
+ assert_return(prefix, -EINVAL);
+
+ r = pref64_prefix_length_to_plc(prefixlen, &prefixlen_code);
+ if (r < 0)
+ return log_radv_errno(NULL, r,
+ "Unsupported PREF64 prefix length %u. Valid lengths are 32, 40, 48, 56, 64 and 96", prefixlen);
+
+ if (lifetime_usec > PREF64_MAX_LIFETIME_USEC)
+ return -EINVAL;
+
+ /* RFC 8781 - 4.1 rounding up lifetime to multiply of 8 */
+ pref64_lifetime = DIV_ROUND_UP(lifetime_usec, 8 * USEC_PER_SEC) << 3;
+ pref64_lifetime |= prefixlen_code;
+
+ unaligned_write_be16(&p->opt.lifetime_and_plc, pref64_lifetime);
+ memcpy(&p->opt.prefix, prefix, sizeof(p->opt.prefix));
+
+ p->in6_addr = *prefix;
+ p->prefixlen = prefixlen;
+
+ return 0;
+}
diff --git a/src/libsystemd-network/test-acd.c b/src/libsystemd-network/test-acd.c
new file mode 100644
index 0000000..4b5ad70
--- /dev/null
+++ b/src/libsystemd-network/test-acd.c
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <linux/veth.h>
+#include <net/if.h>
+
+#include "sd-event.h"
+#include "sd-ipv4acd.h"
+#include "sd-netlink.h"
+
+#include "in-addr-util.h"
+#include "tests.h"
+
+static void acd_handler(sd_ipv4acd *acd, int event, void *userdata) {
+ assert_se(acd);
+
+ switch (event) {
+ case SD_IPV4ACD_EVENT_BIND:
+ log_info("bound");
+ break;
+ case SD_IPV4ACD_EVENT_CONFLICT:
+ log_info("conflict");
+ break;
+ case SD_IPV4ACD_EVENT_STOP:
+ log_error("the client was stopped");
+ break;
+ default:
+ assert_not_reached();
+ }
+}
+
+static int client_run(int ifindex, const struct in_addr *pa, const struct ether_addr *ha, sd_event *e) {
+ sd_ipv4acd *acd;
+
+ assert_se(sd_ipv4acd_new(&acd) >= 0);
+ assert_se(sd_ipv4acd_attach_event(acd, e, 0) >= 0);
+
+ assert_se(sd_ipv4acd_set_ifindex(acd, ifindex) >= 0);
+ assert_se(sd_ipv4acd_set_mac(acd, ha) >= 0);
+ assert_se(sd_ipv4acd_set_address(acd, pa) >= 0);
+ assert_se(sd_ipv4acd_set_callback(acd, acd_handler, NULL) >= 0);
+
+ log_info("starting IPv4ACD client");
+
+ assert_se(sd_ipv4acd_start(acd, true) >= 0);
+
+ assert_se(sd_event_loop(e) >= 0);
+
+ assert_se(!sd_ipv4acd_unref(acd));
+
+ return EXIT_SUCCESS;
+}
+
+static int test_acd(const char *ifname, const char *address) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL;
+ union in_addr_union pa;
+ struct ether_addr ha;
+ int ifindex;
+
+ assert_se(in_addr_from_string(AF_INET, address, &pa) >= 0);
+
+ assert_se(sd_event_new(&e) >= 0);
+
+ assert_se(sd_netlink_open(&rtnl) >= 0);
+ assert_se(sd_netlink_attach_event(rtnl, e, 0) >= 0);
+
+ assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, 0) >= 0);
+ assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, ifname) >= 0);
+ assert_se(sd_netlink_call(rtnl, m, 0, &reply) >= 0);
+
+ assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0);
+ assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &ha) >= 0);
+
+ client_run(ifindex, &pa.in, &ha, e);
+
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[]) {
+ test_setup_logging(LOG_DEBUG);
+
+ if (argc == 3)
+ return test_acd(argv[1], argv[2]);
+ else {
+ log_error("This program takes two arguments.\n"
+ "\t %s <ifname> <IPv4 address>", program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+}
diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c
new file mode 100644
index 0000000..e3f148d
--- /dev/null
+++ b/src/libsystemd-network/test-dhcp-client.c
@@ -0,0 +1,562 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#if HAVE_VALGRIND_VALGRIND_H
+# include <valgrind/valgrind.h>
+#endif
+
+#include "sd-dhcp-client.h"
+#include "sd-event.h"
+
+#include "alloc-util.h"
+#include "dhcp-identifier.h"
+#include "dhcp-network.h"
+#include "dhcp-option.h"
+#include "dhcp-packet.h"
+#include "ether-addr-util.h"
+#include "fd-util.h"
+#include "random-util.h"
+#include "tests.h"
+
+static struct hw_addr_data hw_addr = {
+ .length = ETH_ALEN,
+ .ether = {{ 'A', 'B', 'C', '1', '2', '3' }},
+}, bcast_addr = {
+ .length = ETH_ALEN,
+ .ether = {{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }},
+};
+typedef int (*test_callback_recv_t)(size_t size, DHCPMessage *dhcp);
+
+static bool verbose = true;
+static int test_fd[2];
+static test_callback_recv_t callback_recv;
+static be32_t xid;
+
+static void test_request_basic(sd_event *e) {
+ int r;
+
+ sd_dhcp_client *client;
+
+ if (verbose)
+ printf("* %s\n", __func__);
+
+ /* Initialize client without Anonymize settings. */
+ r = sd_dhcp_client_new(&client, false);
+
+ assert_se(r >= 0);
+ assert_se(client);
+
+ r = sd_dhcp_client_attach_event(client, e, 0);
+ assert_se(r >= 0);
+
+ assert_se(sd_dhcp_client_set_request_option(NULL, 0) == -EINVAL);
+ assert_se(sd_dhcp_client_set_request_address(NULL, NULL) == -EINVAL);
+ assert_se(sd_dhcp_client_set_ifindex(NULL, 0) == -EINVAL);
+
+ assert_se(sd_dhcp_client_set_ifindex(client, 15) == 0);
+ assert_se(sd_dhcp_client_set_ifindex(client, -42) == -EINVAL);
+ assert_se(sd_dhcp_client_set_ifindex(client, -1) == -EINVAL);
+ assert_se(sd_dhcp_client_set_ifindex(client, 0) == -EINVAL);
+ assert_se(sd_dhcp_client_set_ifindex(client, 1) == 0);
+
+ assert_se(sd_dhcp_client_set_hostname(client, "host") == 1);
+ assert_se(sd_dhcp_client_set_hostname(client, "host.domain") == 1);
+ assert_se(sd_dhcp_client_set_hostname(client, NULL) == 1);
+ assert_se(sd_dhcp_client_set_hostname(client, "~host") == -EINVAL);
+ assert_se(sd_dhcp_client_set_hostname(client, "~host.domain") == -EINVAL);
+
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_SUBNET_MASK) == 0);
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_ROUTER) == 0);
+ /* This PRL option is not set when using Anonymize, but in this test
+ * Anonymize settings are not being used. */
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME) == 0);
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_DOMAIN_NAME) == 0);
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_DOMAIN_NAME_SERVER) == 0);
+
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PAD) == -EINVAL);
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_END) == -EINVAL);
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_MESSAGE_TYPE) == -EINVAL);
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_OVERLOAD) == -EINVAL);
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST) == -EINVAL);
+
+ /* RFC7844: option 33 (SD_DHCP_OPTION_STATIC_ROUTE) is set in the
+ * default PRL when using Anonymize, so it is changed to other option
+ * that is not set by default, to check that it was set successfully.
+ * Options not set by default (using or not anonymize) are option 17
+ * (SD_DHCP_OPTION_ROOT_PATH) and 42 (SD_DHCP_OPTION_NTP_SERVER) */
+ assert_se(sd_dhcp_client_set_request_option(client, 17) == 1);
+ assert_se(sd_dhcp_client_set_request_option(client, 17) == 0);
+ assert_se(sd_dhcp_client_set_request_option(client, 42) == 1);
+ assert_se(sd_dhcp_client_set_request_option(client, 17) == 0);
+
+ sd_dhcp_client_unref(client);
+}
+
+static void test_request_anonymize(sd_event *e) {
+ int r;
+
+ sd_dhcp_client *client;
+
+ if (verbose)
+ printf("* %s\n", __func__);
+
+ /* Initialize client with Anonymize settings. */
+ r = sd_dhcp_client_new(&client, true);
+
+ assert_se(r >= 0);
+ assert_se(client);
+
+ r = sd_dhcp_client_attach_event(client, e, 0);
+ assert_se(r >= 0);
+
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_NETBIOS_NAME_SERVER) == 0);
+ /* This PRL option is not set when using Anonymize */
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME) == 1);
+ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST) == -EINVAL);
+
+ /* RFC7844: option 101 (SD_DHCP_OPTION_NEW_TZDB_TIMEZONE) is not set in the
+ * default PRL when using Anonymize, */
+ assert_se(sd_dhcp_client_set_request_option(client, 101) == 1);
+ assert_se(sd_dhcp_client_set_request_option(client, 101) == 0);
+
+ sd_dhcp_client_unref(client);
+}
+
+static void test_checksum(void) {
+ uint8_t buf[20] = {
+ 0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff
+ };
+
+ if (verbose)
+ printf("* %s\n", __func__);
+
+ assert_se(dhcp_packet_checksum((uint8_t*)&buf, 20) == be16toh(0x78ae));
+}
+
+static void test_dhcp_identifier_set_iaid(void) {
+ uint32_t iaid_legacy;
+ be32_t iaid;
+
+ assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy = */ true, &iaid_legacy) >= 0);
+ assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy = */ false, &iaid) >= 0);
+
+ /* we expect, that the MAC address was hashed. The legacy value is in native
+ * endianness. */
+ assert_se(iaid_legacy == 0x8dde4ba8u);
+ assert_se(iaid == htole32(0x8dde4ba8u));
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ assert_se(iaid == iaid_legacy);
+#else
+ assert_se(iaid == bswap_32(iaid_legacy));
+#endif
+}
+
+static int check_options(uint8_t code, uint8_t len, const void *option, void *userdata) {
+ switch (code) {
+ case SD_DHCP_OPTION_CLIENT_IDENTIFIER:
+ {
+ uint32_t iaid;
+ struct duid duid;
+ size_t duid_len;
+
+ assert_se(dhcp_identifier_set_duid_en(&duid, &duid_len) >= 0);
+ assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy = */ true, &iaid) >= 0);
+
+ assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid_len);
+ assert_se(len == 19);
+ assert_se(((uint8_t*) option)[0] == 0xff);
+
+ assert_se(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)) == 0);
+ assert_se(memcmp((uint8_t*) option + 5, &duid, duid_len) == 0);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len) {
+ size_t size;
+ _cleanup_free_ DHCPPacket *discover = NULL;
+ uint16_t ip_check, udp_check;
+
+ assert_se(s >= 0);
+ assert_se(packet);
+
+ size = sizeof(DHCPPacket);
+ assert_se(len > size);
+
+ discover = memdup(packet, len);
+
+ assert_se(discover->ip.ttl == IPDEFTTL);
+ assert_se(discover->ip.protocol == IPPROTO_UDP);
+ assert_se(discover->ip.saddr == INADDR_ANY);
+ assert_se(discover->ip.daddr == INADDR_BROADCAST);
+ assert_se(discover->udp.source == be16toh(DHCP_PORT_CLIENT));
+ assert_se(discover->udp.dest == be16toh(DHCP_PORT_SERVER));
+
+ ip_check = discover->ip.check;
+
+ discover->ip.ttl = 0;
+ discover->ip.check = discover->udp.len;
+
+ udp_check = ~dhcp_packet_checksum((uint8_t*)&discover->ip.ttl, len - 8);
+ assert_se(udp_check == 0xffff);
+
+ discover->ip.ttl = IPDEFTTL;
+ discover->ip.check = ip_check;
+
+ ip_check = ~dhcp_packet_checksum((uint8_t*)&discover->ip, sizeof(discover->ip));
+ assert_se(ip_check == 0xffff);
+
+ assert_se(discover->dhcp.xid);
+ assert_se(memcmp(discover->dhcp.chaddr, hw_addr.bytes, hw_addr.length) == 0);
+
+ size = len - sizeof(struct iphdr) - sizeof(struct udphdr);
+
+ assert_se(callback_recv);
+ callback_recv(size, &discover->dhcp);
+
+ return 575;
+}
+
+int dhcp_network_bind_raw_socket(
+ int ifindex,
+ union sockaddr_union *link,
+ uint32_t id,
+ const struct hw_addr_data *_hw_addr,
+ const struct hw_addr_data *_bcast_addr,
+ uint16_t arp_type,
+ uint16_t port,
+ bool so_priority_set,
+ int so_priority) {
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0)
+ return -errno;
+
+ return test_fd[0];
+}
+
+int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) {
+ int fd;
+
+ fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ return -errno;
+
+ return fd;
+}
+
+int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const void *packet, size_t len) {
+ return 0;
+}
+
+static int test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) {
+ int res;
+
+ res = dhcp_option_parse(dhcp, size, check_options, NULL, NULL);
+ assert_se(res == DHCP_DISCOVER);
+
+ if (verbose)
+ printf(" recv DHCP Discover 0x%08x\n", be32toh(dhcp->xid));
+
+ return 0;
+}
+
+static void test_discover_message(sd_event *e) {
+ sd_dhcp_client *client;
+ int res, r;
+
+ if (verbose)
+ printf("* %s\n", __func__);
+
+ r = sd_dhcp_client_new(&client, false);
+ assert_se(r >= 0);
+ assert_se(client);
+
+ r = sd_dhcp_client_attach_event(client, e, 0);
+ assert_se(r >= 0);
+
+ assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0);
+ assert_se(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER) >= 0);
+
+ assert_se(sd_dhcp_client_set_request_option(client, 248) >= 0);
+
+ callback_recv = test_discover_message_verify;
+
+ res = sd_dhcp_client_start(client);
+
+ assert_se(IN_SET(res, 0, -EINPROGRESS));
+
+ sd_event_run(e, UINT64_MAX);
+
+ sd_dhcp_client_stop(client);
+ sd_dhcp_client_unref(client);
+
+ test_fd[1] = safe_close(test_fd[1]);
+
+ callback_recv = NULL;
+}
+
+static uint8_t test_addr_acq_offer[] = {
+ 0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x11, 0xb3, 0x84, 0xc0, 0xa8, 0x02, 0x01,
+ 0xc0, 0xa8, 0x02, 0xbf, 0x00, 0x43, 0x00, 0x44,
+ 0x01, 0x34, 0x00, 0x00, 0x02, 0x01, 0x06, 0x00,
+ 0x6f, 0x95, 0x2f, 0x30, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0xbf,
+ 0xc0, 0xa8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x02, 0x36,
+ 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x33, 0x04, 0x00,
+ 0x00, 0x02, 0x58, 0x01, 0x04, 0xff, 0xff, 0xff,
+ 0x00, 0x2a, 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x0f,
+ 0x09, 0x6c, 0x61, 0x62, 0x2e, 0x69, 0x6e, 0x74,
+ 0x72, 0x61, 0x03, 0x04, 0xc0, 0xa8, 0x02, 0x01,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static uint8_t test_addr_acq_ack[] = {
+ 0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x11, 0xb3, 0x84, 0xc0, 0xa8, 0x02, 0x01,
+ 0xc0, 0xa8, 0x02, 0xbf, 0x00, 0x43, 0x00, 0x44,
+ 0x01, 0x34, 0x00, 0x00, 0x02, 0x01, 0x06, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0xbf,
+ 0xc0, 0xa8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36,
+ 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x33, 0x04, 0x00,
+ 0x00, 0x02, 0x58, 0x01, 0x04, 0xff, 0xff, 0xff,
+ 0x00, 0x2a, 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x0f,
+ 0x09, 0x6c, 0x61, 0x62, 0x2e, 0x69, 0x6e, 0x74,
+ 0x72, 0x61, 0x03, 0x04, 0xc0, 0xa8, 0x02, 0x01,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static int test_addr_acq_acquired(sd_dhcp_client *client, int event,
+ void *userdata) {
+ sd_event *e = userdata;
+ sd_dhcp_lease *lease;
+ struct in_addr addr;
+ const struct in_addr *addrs;
+
+ assert_se(client);
+ assert_se(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING));
+
+ assert_se(sd_dhcp_client_get_lease(client, &lease) >= 0);
+ assert_se(lease);
+
+ assert_se(sd_dhcp_lease_get_address(lease, &addr) >= 0);
+ assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[44],
+ sizeof(addr.s_addr)) == 0);
+
+ assert_se(sd_dhcp_lease_get_netmask(lease, &addr) >= 0);
+ assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[285],
+ sizeof(addr.s_addr)) == 0);
+
+ assert_se(sd_dhcp_lease_get_router(lease, &addrs) == 1);
+ assert_se(memcmp(&addrs[0].s_addr, &test_addr_acq_ack[308],
+ sizeof(addrs[0].s_addr)) == 0);
+
+ if (verbose)
+ printf(" DHCP address acquired\n");
+
+ sd_event_exit(e, 0);
+
+ return 0;
+}
+
+static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) {
+ uint16_t udp_check = 0;
+ uint8_t *msg_bytes = (uint8_t *)request;
+ int res;
+
+ res = dhcp_option_parse(request, size, check_options, NULL, NULL);
+ assert_se(res == DHCP_REQUEST);
+ assert_se(xid == request->xid);
+
+ assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END);
+
+ if (verbose)
+ printf(" recv DHCP Request 0x%08x\n", be32toh(xid));
+
+ memcpy(&test_addr_acq_ack[26], &udp_check, sizeof(udp_check));
+ memcpy(&test_addr_acq_ack[32], &xid, sizeof(xid));
+ memcpy(&test_addr_acq_ack[56], hw_addr.bytes, hw_addr.length);
+
+ callback_recv = NULL;
+
+ res = write(test_fd[1], test_addr_acq_ack,
+ sizeof(test_addr_acq_ack));
+ assert_se(res == sizeof(test_addr_acq_ack));
+
+ if (verbose)
+ printf(" send DHCP Ack\n");
+
+ return 0;
+};
+
+static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) {
+ uint16_t udp_check = 0;
+ uint8_t *msg_bytes = (uint8_t *)discover;
+ int res;
+
+ res = dhcp_option_parse(discover, size, check_options, NULL, NULL);
+ assert_se(res == DHCP_DISCOVER);
+
+ assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END);
+
+ xid = discover->xid;
+
+ if (verbose)
+ printf(" recv DHCP Discover 0x%08x\n", be32toh(xid));
+
+ memcpy(&test_addr_acq_offer[26], &udp_check, sizeof(udp_check));
+ memcpy(&test_addr_acq_offer[32], &xid, sizeof(xid));
+ memcpy(&test_addr_acq_offer[56], hw_addr.bytes, hw_addr.length);
+
+ callback_recv = test_addr_acq_recv_request;
+
+ res = write(test_fd[1], test_addr_acq_offer,
+ sizeof(test_addr_acq_offer));
+ assert_se(res == sizeof(test_addr_acq_offer));
+
+ if (verbose)
+ printf(" sent DHCP Offer\n");
+
+ return 0;
+}
+
+static void test_addr_acq(sd_event *e) {
+ sd_dhcp_client *client;
+ int res, r;
+
+ if (verbose)
+ printf("* %s\n", __func__);
+
+ r = sd_dhcp_client_new(&client, false);
+ assert_se(r >= 0);
+ assert_se(client);
+
+ r = sd_dhcp_client_attach_event(client, e, 0);
+ assert_se(r >= 0);
+
+ assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0);
+ assert_se(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER) >= 0);
+
+ assert_se(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, e) >= 0);
+
+ callback_recv = test_addr_acq_recv_discover;
+
+ assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
+ 2 * USEC_PER_SEC, 0,
+ NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0);
+
+ res = sd_dhcp_client_start(client);
+ assert_se(IN_SET(res, 0, -EINPROGRESS));
+
+ assert_se(sd_event_loop(e) >= 0);
+
+ assert_se(sd_dhcp_client_set_callback(client, NULL, NULL) >= 0);
+ assert_se(sd_dhcp_client_stop(client) >= 0);
+ sd_dhcp_client_unref(client);
+
+ test_fd[1] = safe_close(test_fd[1]);
+
+ callback_recv = NULL;
+ xid = 0;
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_(sd_event_unrefp) sd_event *e;
+
+ assert_se(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", 1) >= 0);
+
+ test_setup_logging(LOG_DEBUG);
+
+ assert_se(sd_event_new(&e) >= 0);
+
+ test_request_basic(e);
+ test_request_anonymize(e);
+ test_checksum();
+ test_dhcp_identifier_set_iaid();
+
+ test_discover_message(e);
+ test_addr_acq(e);
+
+#if HAVE_VALGRIND_VALGRIND_H
+ /* Make sure the async_close thread has finished.
+ * valgrind would report some of the phread_* structures
+ * as not cleaned up properly. */
+ if (RUNNING_ON_VALGRIND)
+ sleep(1);
+#endif
+
+ return 0;
+}
diff --git a/src/libsystemd-network/test-dhcp-option.c b/src/libsystemd-network/test-dhcp-option.c
new file mode 100644
index 0000000..bcd46e4
--- /dev/null
+++ b/src/libsystemd-network/test-dhcp-option.c
@@ -0,0 +1,386 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <net/if_arp.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "dhcp-option.h"
+#include "dhcp-packet.h"
+#include "ether-addr-util.h"
+#include "macro.h"
+#include "memory-util.h"
+#include "tests.h"
+
+struct option_desc {
+ uint8_t sname[64];
+ int snamelen;
+ uint8_t file[128];
+ int filelen;
+ uint8_t options[128];
+ int len;
+ bool success;
+ int filepos;
+ int snamepos;
+ int pos;
+};
+
+static bool verbose = false;
+
+static struct option_desc option_tests[] = {
+ { {}, 0, {}, 0, { 42, 5, 65, 66, 67, 68, 69 }, 7, false, },
+ { {}, 0, {}, 0, { 42, 5, 65, 66, 67, 68, 69, 0, 0,
+ SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 12, true, },
+ { {}, 0, {}, 0, { 8, 255, 70, 71, 72 }, 5, false, },
+ { {}, 0, {}, 0, { 0x35, 0x01, 0x05, 0x36, 0x04, 0x01, 0x00, 0xa8,
+ 0xc0, 0x33, 0x04, 0x00, 0x01, 0x51, 0x80, 0x01,
+ 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0xc0,
+ 0xa8, 0x00, 0x01, 0x06, 0x04, 0xc0, 0xa8, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
+ 40, true, },
+ { {}, 0, {}, 0, { SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_OFFER,
+ 42, 3, 0, 0, 0 }, 8, true, },
+ { {}, 0, {}, 0, { 42, 2, 1, 2, 44 }, 5, false, },
+
+ { {}, 0,
+ { 222, 3, 1, 2, 3, SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_NAK }, 8,
+ { SD_DHCP_OPTION_OVERLOAD, 1, DHCP_OVERLOAD_FILE }, 3, true, },
+
+ { { 1, 4, 1, 2, 3, 4, SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 9,
+ { 222, 3, 1, 2, 3 }, 5,
+ { SD_DHCP_OPTION_OVERLOAD, 1,
+ DHCP_OVERLOAD_FILE|DHCP_OVERLOAD_SNAME }, 3, true, },
+};
+
+static const char *dhcp_type(int type) {
+ switch (type) {
+ case DHCP_DISCOVER:
+ return "DHCPDISCOVER";
+ case DHCP_OFFER:
+ return "DHCPOFFER";
+ case DHCP_REQUEST:
+ return "DHCPREQUEST";
+ case DHCP_DECLINE:
+ return "DHCPDECLINE";
+ case DHCP_ACK:
+ return "DHCPACK";
+ case DHCP_NAK:
+ return "DHCPNAK";
+ case DHCP_RELEASE:
+ return "DHCPRELEASE";
+ default:
+ return "unknown";
+ }
+}
+
+static void test_invalid_buffer_length(void) {
+ DHCPMessage message;
+
+ assert_se(dhcp_option_parse(&message, 0, NULL, NULL, NULL) == -EINVAL);
+ assert_se(dhcp_option_parse(&message, sizeof(DHCPMessage) - 1, NULL, NULL, NULL) == -EINVAL);
+}
+
+static void test_message_init(void) {
+ _cleanup_free_ DHCPMessage *message = NULL;
+ size_t optlen = 4, optoffset;
+ size_t len = sizeof(DHCPMessage) + optlen;
+ uint8_t *magic;
+
+ message = malloc0(len);
+
+ assert_se(dhcp_message_init(message, BOOTREQUEST, 0x12345678,
+ DHCP_DISCOVER, ARPHRD_ETHER, ETH_ALEN, (uint8_t[16]){},
+ optlen, &optoffset) >= 0);
+
+ assert_se(message->xid == htobe32(0x12345678));
+ assert_se(message->op == BOOTREQUEST);
+
+ magic = (uint8_t*)&message->magic;
+
+ assert_se(magic[0] == 99);
+ assert_se(magic[1] == 130);
+ assert_se(magic[2] == 83);
+ assert_se(magic[3] == 99);
+
+ assert_se(dhcp_option_parse(message, len, NULL, NULL, NULL) >= 0);
+}
+
+static DHCPMessage *create_message(uint8_t *options, uint16_t optlen,
+ uint8_t *file, uint8_t filelen,
+ uint8_t *sname, uint8_t snamelen) {
+ DHCPMessage *message;
+ size_t len = sizeof(DHCPMessage) + optlen;
+
+ message = malloc0(len);
+ assert_se(message);
+
+ memcpy_safe(&message->options, options, optlen);
+ memcpy_safe(&message->file, file, filelen);
+ memcpy_safe(&message->sname, sname, snamelen);
+
+ return message;
+}
+
+static void test_ignore_opts(uint8_t *descoption, int *descpos, int *desclen) {
+ assert_se(*descpos >= 0);
+
+ while (*descpos < *desclen) {
+ switch (descoption[*descpos]) {
+ case SD_DHCP_OPTION_PAD:
+ *descpos += 1;
+ break;
+
+ case SD_DHCP_OPTION_MESSAGE_TYPE:
+ case SD_DHCP_OPTION_OVERLOAD:
+ *descpos += 3;
+ break;
+
+ default:
+ return;
+ }
+ }
+}
+
+static int test_options_cb(uint8_t code, uint8_t len, const void *option, void *userdata) {
+ struct option_desc *desc = userdata;
+ uint8_t *descoption = NULL;
+ int *desclen = NULL, *descpos = NULL;
+ uint8_t optcode = 0;
+ uint8_t optlen = 0;
+
+ assert_se((!desc && !code && !len) || desc);
+
+ if (!desc)
+ return -EINVAL;
+
+ assert_se(code != SD_DHCP_OPTION_PAD);
+ assert_se(code != SD_DHCP_OPTION_END);
+ assert_se(code != SD_DHCP_OPTION_MESSAGE_TYPE);
+ assert_se(code != SD_DHCP_OPTION_OVERLOAD);
+
+ while (desc->pos >= 0 || desc->filepos >= 0 || desc->snamepos >= 0) {
+
+ if (desc->pos >= 0) {
+ descoption = &desc->options[0];
+ desclen = &desc->len;
+ descpos = &desc->pos;
+ } else if (desc->filepos >= 0) {
+ descoption = &desc->file[0];
+ desclen = &desc->filelen;
+ descpos = &desc->filepos;
+ } else if (desc->snamepos >= 0) {
+ descoption = &desc->sname[0];
+ desclen = &desc->snamelen;
+ descpos = &desc->snamepos;
+ }
+
+ assert_se(descoption && desclen && descpos);
+
+ if (*desclen)
+ test_ignore_opts(descoption, descpos, desclen);
+
+ if (*descpos < *desclen)
+ break;
+
+ if (*descpos == *desclen)
+ *descpos = -1;
+ }
+
+ assert_se(descpos);
+ assert_se(*descpos != -1);
+
+ optcode = descoption[*descpos];
+ optlen = descoption[*descpos + 1];
+
+ if (verbose)
+ printf("DHCP code %2d(%2d) len %2d(%2d) ", code, optcode,
+ len, optlen);
+
+ assert_se(code == optcode);
+ assert_se(len == optlen);
+
+ for (unsigned i = 0; i < len; i++) {
+ if (verbose)
+ printf("0x%02x(0x%02x) ",
+ ((uint8_t*) option)[i],
+ descoption[*descpos + 2 + i]);
+
+ assert_se(((uint8_t*) option)[i] == descoption[*descpos + 2 + i]);
+ }
+
+ if (verbose)
+ printf("\n");
+
+ *descpos += optlen + 2;
+
+ test_ignore_opts(descoption, descpos, desclen);
+
+ if (desc->pos != -1 && desc->pos == desc->len)
+ desc->pos = -1;
+
+ if (desc->filepos != -1 && desc->filepos == desc->filelen)
+ desc->filepos = -1;
+
+ if (desc->snamepos != -1 && desc->snamepos == desc->snamelen)
+ desc->snamepos = -1;
+
+ return 0;
+}
+
+static void test_options(struct option_desc *desc) {
+ uint8_t *options = NULL;
+ uint8_t *file = NULL;
+ uint8_t *sname = NULL;
+ int optlen = 0;
+ int filelen = 0;
+ int snamelen = 0;
+ int buflen = 0;
+ _cleanup_free_ DHCPMessage *message = NULL;
+ int res;
+
+ if (desc) {
+ file = &desc->file[0];
+ filelen = desc->filelen;
+ if (!filelen)
+ desc->filepos = -1;
+
+ sname = &desc->sname[0];
+ snamelen = desc->snamelen;
+ if (!snamelen)
+ desc->snamepos = -1;
+
+ options = &desc->options[0];
+ optlen = desc->len;
+ desc->pos = 0;
+ }
+ message = create_message(options, optlen, file, filelen,
+ sname, snamelen);
+
+ buflen = sizeof(DHCPMessage) + optlen;
+
+ if (!desc) {
+ assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, NULL, NULL)) == -ENOMSG);
+ } else if (desc->success) {
+ assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, desc, NULL)) >= 0);
+ assert_se(desc->pos == -1 && desc->filepos == -1 && desc->snamepos == -1);
+ } else
+ assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, desc, NULL)) < 0);
+
+ if (verbose)
+ printf("DHCP type %s\n", dhcp_type(res));
+}
+
+static void test_option_removal(struct option_desc *desc) {
+ _cleanup_free_ DHCPMessage *message = create_message(&desc->options[0], desc->len, NULL, 0, NULL, 0);
+
+ assert_se(dhcp_option_parse(message, sizeof(DHCPMessage) + desc->len, NULL, NULL, NULL) >= 0);
+ assert_se((desc->len = dhcp_option_remove_option(message->options, desc->len, SD_DHCP_OPTION_MESSAGE_TYPE)) >= 0);
+ assert_se(dhcp_option_parse(message, sizeof(DHCPMessage) + desc->len, NULL, NULL, NULL) < 0);
+}
+
+static uint8_t the_options[64] = {
+ 'A', 'B', 'C', 'D',
+ 160, 2, 0x11, 0x12,
+ 0,
+ 31, 8, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
+ 0,
+ 55, 3, 0x51, 0x52, 0x53,
+ 17, 7, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 255
+};
+
+static void test_option_set(void) {
+ _cleanup_free_ DHCPMessage *result = NULL;
+ size_t offset = 0, len, pos;
+
+ result = malloc0(sizeof(DHCPMessage) + 11);
+ assert_se(result);
+
+ result->options[0] = 'A';
+ result->options[1] = 'B';
+ result->options[2] = 'C';
+ result->options[3] = 'D';
+
+ assert_se(dhcp_option_append(result, 0, &offset, 0, SD_DHCP_OPTION_PAD,
+ 0, NULL) == -ENOBUFS);
+ assert_se(offset == 0);
+
+ offset = 4;
+ assert_se(dhcp_option_append(result, 5, &offset, 0, SD_DHCP_OPTION_PAD,
+ 0, NULL) == -ENOBUFS);
+ assert_se(offset == 4);
+ assert_se(dhcp_option_append(result, 6, &offset, 0, SD_DHCP_OPTION_PAD,
+ 0, NULL) >= 0);
+ assert_se(offset == 5);
+
+ offset = pos = 4;
+ len = 11;
+ while (pos < len && the_options[pos] != SD_DHCP_OPTION_END) {
+ assert_se(dhcp_option_append(result, len, &offset, DHCP_OVERLOAD_SNAME,
+ the_options[pos],
+ the_options[pos + 1],
+ &the_options[pos + 2]) >= 0);
+
+ if (the_options[pos] == SD_DHCP_OPTION_PAD)
+ pos++;
+ else
+ pos += 2 + the_options[pos + 1];
+
+ if (pos < len)
+ assert_se(offset == pos);
+ }
+
+ for (unsigned i = 0; i < 9; i++) {
+ if (verbose)
+ printf("%2u: 0x%02x(0x%02x) (options)\n", i, result->options[i],
+ the_options[i]);
+ assert_se(result->options[i] == the_options[i]);
+ }
+
+ if (verbose)
+ printf("%2d: 0x%02x(0x%02x) (options)\n", 9, result->options[9],
+ (unsigned) SD_DHCP_OPTION_END);
+
+ assert_se(result->options[9] == SD_DHCP_OPTION_END);
+
+ if (verbose)
+ printf("%2d: 0x%02x(0x%02x) (options)\n", 10, result->options[10],
+ (unsigned) SD_DHCP_OPTION_PAD);
+
+ assert_se(result->options[10] == SD_DHCP_OPTION_PAD);
+
+ for (unsigned i = 0; i < pos - 8; i++) {
+ if (verbose)
+ printf("%2u: 0x%02x(0x%02x) (sname)\n", i, result->sname[i],
+ the_options[i + 9]);
+ assert_se(result->sname[i] == the_options[i + 9]);
+ }
+
+ if (verbose)
+ printf ("\n");
+}
+
+int main(int argc, char *argv[]) {
+ test_setup_logging(LOG_DEBUG);
+
+ test_invalid_buffer_length();
+ test_message_init();
+
+ test_options(NULL);
+
+ for (unsigned i = 0; i < ELEMENTSOF(option_tests); i++)
+ test_options(&option_tests[i]);
+
+ test_option_set();
+
+ for (unsigned i = 0; i < ELEMENTSOF(option_tests); i++) {
+ struct option_desc *desc = &option_tests[i];
+ if (!desc->success || desc->snamelen > 0 || desc->filelen > 0)
+ continue;
+ test_option_removal(desc);
+ }
+
+ return 0;
+}
diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c
new file mode 100644
index 0000000..b2e6034
--- /dev/null
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -0,0 +1,330 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <net/if_arp.h>
+
+#include "sd-dhcp-server.h"
+#include "sd-event.h"
+
+#include "dhcp-server-internal.h"
+#include "tests.h"
+
+static void test_pool(struct in_addr *address, unsigned size, int ret) {
+ _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
+
+ assert_se(sd_dhcp_server_new(&server, 1) >= 0);
+
+ assert_se(sd_dhcp_server_configure_pool(server, address, 8, 0, size) == ret);
+}
+
+static int test_basic(bool bind_to_interface) {
+ _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ struct in_addr address_lo = {
+ .s_addr = htobe32(INADDR_LOOPBACK),
+ };
+ struct in_addr address_any = {
+ .s_addr = htobe32(INADDR_ANY),
+ };
+ int r;
+
+ log_debug("/* %s(bind_to_interface=%s) */", __func__, yes_no(bind_to_interface));
+
+ assert_se(sd_event_new(&event) >= 0);
+
+ /* attach to loopback interface */
+ assert_se(sd_dhcp_server_new(&server, 1) >= 0);
+ assert_se(server);
+ server->bind_to_interface = bind_to_interface;
+
+ assert_se(sd_dhcp_server_attach_event(server, event, 0) >= 0);
+ assert_se(sd_dhcp_server_attach_event(server, event, 0) == -EBUSY);
+ assert_se(sd_dhcp_server_get_event(server) == event);
+ assert_se(sd_dhcp_server_detach_event(server) >= 0);
+ assert_se(!sd_dhcp_server_get_event(server));
+ assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0);
+ assert_se(sd_dhcp_server_attach_event(server, NULL, 0) == -EBUSY);
+
+ assert_se(sd_dhcp_server_ref(server) == server);
+ assert_se(!sd_dhcp_server_unref(server));
+
+ assert_se(sd_dhcp_server_start(server) == -EUNATCH);
+
+ assert_se(sd_dhcp_server_configure_pool(server, &address_any, 28, 0, 0) == -EINVAL);
+ assert_se(sd_dhcp_server_configure_pool(server, &address_lo, 38, 0, 0) == -ERANGE);
+ assert_se(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0) >= 0);
+ assert_se(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0) >= 0);
+
+ test_pool(&address_any, 1, -EINVAL);
+ test_pool(&address_lo, 1, 0);
+
+ r = sd_dhcp_server_start(server);
+ if (r == -EPERM)
+ return r;
+ assert_se(r >= 0);
+
+ assert_se(sd_dhcp_server_start(server) >= 0);
+ assert_se(sd_dhcp_server_stop(server) >= 0);
+ assert_se(sd_dhcp_server_stop(server) >= 0);
+ assert_se(sd_dhcp_server_start(server) >= 0);
+
+ return 0;
+}
+
+static void test_message_handler(void) {
+ _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
+ struct {
+ DHCPMessage message;
+ struct {
+ uint8_t code;
+ uint8_t length;
+ uint8_t type;
+ } _packed_ option_type;
+ struct {
+ uint8_t code;
+ uint8_t length;
+ be32_t address;
+ } _packed_ option_requested_ip;
+ struct {
+ uint8_t code;
+ uint8_t length;
+ be32_t address;
+ } _packed_ option_server_id;
+ struct {
+ uint8_t code;
+ uint8_t length;
+ uint8_t id[7];
+ } _packed_ option_client_id;
+ struct {
+ uint8_t code;
+ uint8_t length;
+ uint8_t hostname[6];
+ } _packed_ option_hostname;
+ uint8_t end;
+ } _packed_ test = {
+ .message.op = BOOTREQUEST,
+ .message.htype = ARPHRD_ETHER,
+ .message.hlen = ETHER_ADDR_LEN,
+ .message.xid = htobe32(0x12345678),
+ .message.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' },
+ .option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE,
+ .option_type.length = 1,
+ .option_type.type = DHCP_DISCOVER,
+ .option_hostname.code = SD_DHCP_OPTION_HOST_NAME,
+ .option_hostname.length = 6,
+ .option_hostname.hostname = { 'T', 'E', 'S', 'T', 'H', 'N' },
+ .end = SD_DHCP_OPTION_END,
+ };
+ struct in_addr address_lo = {
+ .s_addr = htobe32(INADDR_LOOPBACK),
+ };
+ struct in_addr static_lease_address = {
+ .s_addr = htobe32(INADDR_LOOPBACK + 42),
+ };
+ static uint8_t static_lease_client_id[7] = {0x01, 'A', 'B', 'C', 'D', 'E', 'G' };
+
+ log_debug("/* %s */", __func__);
+
+ assert_se(sd_dhcp_server_new(&server, 1) >= 0);
+ assert_se(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0) >= 0);
+ assert_se(sd_dhcp_server_set_static_lease(server, &static_lease_address, static_lease_client_id,
+ ELEMENTSOF(static_lease_client_id)) >= 0);
+ assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0);
+ assert_se(sd_dhcp_server_start(server) >= 0);
+
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
+
+ test.end = 0;
+ /* TODO, shouldn't this fail? */
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
+ test.end = SD_DHCP_OPTION_END;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
+
+ test.option_type.code = 0;
+ test.option_type.length = 0;
+ test.option_type.type = 0;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == -ENOMSG);
+ test.option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE;
+ test.option_type.length = 1;
+ test.option_type.type = DHCP_DISCOVER;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
+
+ test.message.op = 0;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
+ test.message.op = BOOTREQUEST;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
+
+ test.message.htype = 0;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
+ test.message.htype = ARPHRD_ETHER;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
+
+ test.message.hlen = 0;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == -EBADMSG);
+ test.message.hlen = ETHER_ADDR_LEN;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
+
+ test.option_type.type = DHCP_REQUEST;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
+ test.option_requested_ip.code = SD_DHCP_OPTION_REQUESTED_IP_ADDRESS;
+ test.option_requested_ip.length = 4;
+ test.option_requested_ip.address = htobe32(0x12345678);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_NAK);
+ test.option_server_id.code = SD_DHCP_OPTION_SERVER_IDENTIFIER;
+ test.option_server_id.length = 4;
+ test.option_server_id.address = htobe32(INADDR_LOOPBACK);
+ test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
+
+ test.option_server_id.address = htobe32(0x12345678);
+ test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
+ test.option_server_id.address = htobe32(INADDR_LOOPBACK);
+ test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 4);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
+ test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
+
+ test.option_client_id.code = SD_DHCP_OPTION_CLIENT_IDENTIFIER;
+ test.option_client_id.length = 7;
+ test.option_client_id.id[0] = 0x01;
+ test.option_client_id.id[1] = 'A';
+ test.option_client_id.id[2] = 'B';
+ test.option_client_id.id[3] = 'C';
+ test.option_client_id.id[4] = 'D';
+ test.option_client_id.id[5] = 'E';
+ test.option_client_id.id[6] = 'F';
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
+
+ test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
+
+ /* request address reserved for static lease (unmatching client ID) */
+ test.option_client_id.id[6] = 'H';
+ test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
+
+ /* request unmatching address */
+ test.option_client_id.id[6] = 'G';
+ test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 41);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
+
+ /* request matching address */
+ test.option_client_id.id[6] = 'G';
+ test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
+
+ /* try again */
+ test.option_client_id.id[6] = 'G';
+ test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
+}
+
+static uint64_t client_id_hash_helper(DHCPClientId *id, uint8_t key[HASH_KEY_SIZE]) {
+ struct siphash state;
+
+ siphash24_init(&state, key);
+ client_id_hash_func(id, &state);
+
+ return htole64(siphash24_finalize(&state));
+}
+
+static void test_client_id_hash(void) {
+ DHCPClientId a = {
+ .length = 4,
+ }, b = {
+ .length = 4,
+ };
+ uint8_t hash_key[HASH_KEY_SIZE] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+ };
+
+ log_debug("/* %s */", __func__);
+
+ a.data = (uint8_t*)strdup("abcd");
+ b.data = (uint8_t*)strdup("abcd");
+
+ assert_se(client_id_compare_func(&a, &b) == 0);
+ assert_se(client_id_hash_helper(&a, hash_key) == client_id_hash_helper(&b, hash_key));
+ a.length = 3;
+ assert_se(client_id_compare_func(&a, &b) != 0);
+ a.length = 4;
+ assert_se(client_id_compare_func(&a, &b) == 0);
+ assert_se(client_id_hash_helper(&a, hash_key) == client_id_hash_helper(&b, hash_key));
+
+ b.length = 3;
+ assert_se(client_id_compare_func(&a, &b) != 0);
+ b.length = 4;
+ assert_se(client_id_compare_func(&a, &b) == 0);
+ assert_se(client_id_hash_helper(&a, hash_key) == client_id_hash_helper(&b, hash_key));
+
+ free(b.data);
+ b.data = (uint8_t*)strdup("abce");
+ assert_se(client_id_compare_func(&a, &b) != 0);
+
+ free(a.data);
+ free(b.data);
+}
+
+static void test_static_lease(void) {
+ _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
+
+ log_debug("/* %s */", __func__);
+
+ assert_se(sd_dhcp_server_new(&server, 1) >= 0);
+
+ assert_se(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020304 },
+ (uint8_t*) &(uint32_t) { 0x01020304 }, sizeof(uint32_t)) >= 0);
+ /* Duplicated entry. */
+ assert_se(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020304 },
+ (uint8_t*) &(uint32_t) { 0x01020304 }, sizeof(uint32_t)) == -EEXIST);
+ /* Address is conflicted. */
+ assert_se(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020304 },
+ (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)) == -EEXIST);
+ /* Client ID is conflicted. */
+ assert_se(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020305 },
+ (uint8_t*) &(uint32_t) { 0x01020304 }, sizeof(uint32_t)) == -EEXIST);
+
+ assert_se(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020305 },
+ (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)) >= 0);
+ /* Remove the previous entry. */
+ assert_se(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 },
+ (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)) >= 0);
+ /* Then, set a different address. */
+ assert_se(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020306 },
+ (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)) >= 0);
+ /* Remove again. */
+ assert_se(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 },
+ (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)) >= 0);
+ /* Try to remove non-existent entry. */
+ assert_se(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 },
+ (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)) >= 0);
+ /* Try to remove non-existent entry. */
+ assert_se(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 },
+ (uint8_t*) &(uint32_t) { 0x01020306 }, sizeof(uint32_t)) >= 0);
+}
+
+int main(int argc, char *argv[]) {
+ int r;
+
+ test_setup_logging(LOG_DEBUG);
+
+ test_client_id_hash();
+ test_static_lease();
+
+ r = test_basic(true);
+ if (r < 0)
+ return log_tests_skipped_errno(r, "cannot start dhcp server(bound to interface)");
+
+ r = test_basic(false);
+ if (r < 0)
+ return log_tests_skipped_errno(r, "cannot start dhcp server(non-bound to interface)");
+
+ test_message_handler();
+
+ return 0;
+}
diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
new file mode 100644
index 0000000..ae3cdb8
--- /dev/null
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -0,0 +1,1127 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "sd-dhcp6-client.h"
+#include "sd-event.h"
+
+#include "dhcp-identifier.h"
+#include "dhcp6-internal.h"
+#include "dhcp6-lease-internal.h"
+#include "dhcp6-protocol.h"
+#include "fd-util.h"
+#include "macro.h"
+#include "memory-util.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "tests.h"
+#include "time-util.h"
+
+#define DHCP6_CLIENT_EVENT_TEST_ADVERTISED 77
+#define IA_ID_BYTES \
+ 0x0e, 0xcf, 0xa3, 0x7d
+#define IA_NA_ADDRESS1_BYTES \
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3, 0x09, 0x3c, 0x55, 0xad
+#define IA_NA_ADDRESS2_BYTES \
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3, 0x09, 0x3c, 0x55, 0xae
+#define IA_PD_PREFIX1_BYTES \
+ 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+#define IA_PD_PREFIX2_BYTES \
+ 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+#define DNS1_BYTES \
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+#define DNS2_BYTES \
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02
+#define SNTP1_BYTES \
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03
+#define SNTP2_BYTES \
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04
+#define NTP1_BYTES \
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05
+#define NTP2_BYTES \
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06
+#define CLIENT_ID_BYTES \
+ 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2
+#define SERVER_ID_BYTES \
+ 0x00, 0x01, 0x00, 0x01, 0x19, 0x40, 0x5c, 0x53, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53
+#define VENDOR_SUBOPTION_BYTES \
+ 0x01
+
+static const struct in6_addr local_address =
+ { { { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, } } };
+static const struct in6_addr mcast_address =
+ IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
+static const struct in6_addr ia_na_address1 = { { { IA_NA_ADDRESS1_BYTES } } };
+static const struct in6_addr ia_na_address2 = { { { IA_NA_ADDRESS2_BYTES } } };
+static const struct in6_addr ia_pd_prefix1 = { { { IA_PD_PREFIX1_BYTES } } };
+static const struct in6_addr ia_pd_prefix2 = { { { IA_PD_PREFIX2_BYTES } } };
+static const struct in6_addr dns1 = { { { DNS1_BYTES } } };
+static const struct in6_addr dns2 = { { { DNS2_BYTES } } };
+static const struct in6_addr sntp1 = { { { SNTP1_BYTES } } };
+static const struct in6_addr sntp2 = { { { SNTP2_BYTES } } };
+static const struct in6_addr ntp1 = { { { NTP1_BYTES } } };
+static const struct in6_addr ntp2 = { { { NTP2_BYTES } } };
+static const uint8_t client_id[] = { CLIENT_ID_BYTES };
+static const uint8_t server_id[] = { SERVER_ID_BYTES };
+static uint8_t vendor_suboption_data[] = { VENDOR_SUBOPTION_BYTES };
+static const struct ether_addr mac = {
+ .ether_addr_octet = { 'A', 'B', 'C', '1', '2', '3' },
+};
+static int test_fd[2] = EBADF_PAIR;
+static sd_dhcp6_option vendor_suboption = {
+ .n_ref = 1,
+ .enterprise_identifier = 32,
+ .option = 247,
+ .data = vendor_suboption_data,
+ .length = 1,
+};
+static int test_ifindex = 42;
+static unsigned test_client_sent_message_count = 0;
+static sd_dhcp6_client *client_ref = NULL;
+
+TEST(client_basic) {
+ _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+ int v;
+
+ assert_se(sd_dhcp6_client_new(&client) >= 0);
+ assert_se(client);
+
+ assert_se(sd_dhcp6_client_set_ifindex(client, 15) == 0);
+ assert_se(sd_dhcp6_client_set_ifindex(client, 42) >= 0);
+
+ assert_se(sd_dhcp6_client_set_mac(client, mac.ether_addr_octet, sizeof(mac), ARPHRD_ETHER) >= 0);
+
+ assert_se(sd_dhcp6_client_set_fqdn(client, "host") == 1);
+ assert_se(sd_dhcp6_client_set_fqdn(client, "host.domain") == 1);
+ assert_se(sd_dhcp6_client_set_fqdn(client, NULL) == 1);
+ assert_se(sd_dhcp6_client_set_fqdn(client, "~host") == -EINVAL);
+ assert_se(sd_dhcp6_client_set_fqdn(client, "~host.domain") == -EINVAL);
+
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_CLIENTID) == -EINVAL);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER) >= 0);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER) >= 0);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SNTP_SERVER) >= 0);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_VENDOR_OPTS) >= 0);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN) >= 0);
+ assert_se(sd_dhcp6_client_set_request_option(client, 10) == -EINVAL);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NIS_SERVER) >= 0);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NISP_SERVER) >= 0);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NIS_SERVER) == -EEXIST);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NISP_SERVER) == -EEXIST);
+
+ assert_se(sd_dhcp6_client_set_information_request(client, 1) >= 0);
+ v = 0;
+ assert_se(sd_dhcp6_client_get_information_request(client, &v) >= 0);
+ assert_se(v);
+ assert_se(sd_dhcp6_client_set_information_request(client, 0) >= 0);
+ v = 42;
+ assert_se(sd_dhcp6_client_get_information_request(client, &v) >= 0);
+ assert_se(v == 0);
+
+ v = 0;
+ assert_se(sd_dhcp6_client_get_address_request(client, &v) >= 0);
+ assert_se(v);
+ v = 0;
+ assert_se(sd_dhcp6_client_set_address_request(client, 1) >= 0);
+ assert_se(sd_dhcp6_client_get_address_request(client, &v) >= 0);
+ assert_se(v);
+ v = 42;
+ assert_se(sd_dhcp6_client_set_address_request(client, 1) >= 0);
+ assert_se(sd_dhcp6_client_get_address_request(client, &v) >= 0);
+ assert_se(v);
+
+ assert_se(sd_dhcp6_client_set_address_request(client, 1) >= 0);
+ assert_se(sd_dhcp6_client_set_prefix_delegation(client, 1) >= 0);
+ v = 0;
+ assert_se(sd_dhcp6_client_get_address_request(client, &v) >= 0);
+ assert_se(v);
+ v = 0;
+ assert_se(sd_dhcp6_client_get_prefix_delegation(client, &v) >= 0);
+ assert_se(v);
+
+ assert_se(sd_dhcp6_client_set_callback(client, NULL, NULL) >= 0);
+
+ assert_se(sd_dhcp6_client_detach_event(client) >= 0);
+}
+
+TEST(parse_domain) {
+ _cleanup_free_ char *domain = NULL;
+ _cleanup_strv_free_ char **list = NULL;
+ uint8_t *data;
+
+ data = (uint8_t []) { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0 };
+ assert_se(dhcp6_option_parse_domainname(data, 13, &domain) >= 0);
+ assert_se(domain);
+ assert_se(streq(domain, "example.com"));
+ domain = mfree(domain);
+
+ data = (uint8_t []) { 4, 't', 'e', 's', 't' };
+ assert_se(dhcp6_option_parse_domainname(data, 5, &domain) >= 0);
+ assert_se(domain);
+ assert_se(streq(domain, "test"));
+ domain = mfree(domain);
+
+ data = (uint8_t []) { 0 };
+ assert_se(dhcp6_option_parse_domainname(data, 1, &domain) < 0);
+
+ data = (uint8_t []) { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0,
+ 6, 'f', 'o', 'o', 'b', 'a', 'r', 0 };
+ assert_se(dhcp6_option_parse_domainname_list(data, 21, &list) >= 0);
+ assert_se(list);
+ assert_se(streq(list[0], "example.com"));
+ assert_se(streq(list[1], "foobar"));
+ assert_se(!list[2]);
+ list = strv_free(list);
+
+ data = (uint8_t []) { 1, 'a', 0, 20, 'b', 'c' };
+ assert_se(dhcp6_option_parse_domainname_list(data, 6, &list) < 0);
+
+ data = (uint8_t []) { 0 , 0 };
+ assert_se(dhcp6_option_parse_domainname_list(data, 2, &list) < 0);
+}
+
+TEST(option) {
+ static const uint8_t packet[] = {
+ 'F', 'O', 'O', 'H', 'O', 'G', 'E',
+ 0x00, SD_DHCP6_OPTION_ORO, 0x00, 0x07,
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G',
+ 0x00, SD_DHCP6_OPTION_VENDOR_CLASS, 0x00, 0x09,
+ '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'B', 'A', 'R',
+ };
+ static const uint8_t result[] = {
+ 'F', 'O', 'O', 'H', 'O', 'G', 'E',
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 'B', 'A', 'R',
+ };
+ _cleanup_free_ uint8_t *buf = NULL;
+ size_t offset, pos, optlen;
+ const uint8_t *optval;
+ uint16_t optcode;
+
+ assert_se(sizeof(packet) == sizeof(result));
+
+ offset = 0;
+ assert_se(dhcp6_option_parse(packet, 0, &offset, &optcode, &optlen, &optval) == -EBADMSG);
+
+ offset = 3;
+ assert_se(dhcp6_option_parse(packet, 0, &offset, &optcode, &optlen, &optval) == -EBADMSG);
+
+ /* Tests for reading unaligned data. */
+ assert_se(buf = new(uint8_t, sizeof(packet)));
+ for (size_t i = 0; i <= 7; i++) {
+ memcpy(buf, packet + i, sizeof(packet) - i);
+ offset = 7 - i;
+ assert_se(dhcp6_option_parse(buf, sizeof(packet), &offset, &optcode, &optlen, &optval) >= 0);
+
+ assert_se(optcode == SD_DHCP6_OPTION_ORO);
+ assert_se(optlen == 7);
+ assert_se(optval == buf + 11 - i);
+ }
+
+ offset = 7;
+ assert_se(dhcp6_option_parse(packet, sizeof(packet), &offset, &optcode, &optlen, &optval) >= 0);
+
+ assert_se(optcode == SD_DHCP6_OPTION_ORO);
+ assert_se(optlen == 7);
+ assert_se(optval == packet + 11);
+
+ free(buf);
+ assert_se(buf = memdup(result, sizeof(result)));
+ pos = 7;
+ assert_se(dhcp6_option_append(&buf, &pos, optcode, optlen, optval) >= 0);
+
+ assert_se(dhcp6_option_parse(packet, sizeof(packet), &offset, &optcode, &optlen, &optval) >= 0);
+
+ assert_se(optcode == SD_DHCP6_OPTION_VENDOR_CLASS);
+ assert_se(optlen == 9);
+ assert_se(optval == packet + 22);
+
+ assert_se(dhcp6_option_append(&buf, &pos, optcode, optlen, optval) >= 0);
+
+ assert_se(memcmp(packet, buf, sizeof(packet)) == 0);
+}
+
+TEST(option_status) {
+ uint8_t option1[] = {
+ /* IA NA */
+ 0x00, 0x03, 0x00, 0x12, 0x1a, 0x1d, 0x1a, 0x1d,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02,
+ /* status option */
+ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x01,
+ };
+ static const uint8_t option2[] = {
+ /* IA NA */
+ 0x00, 0x03, 0x00, 0x2e, 0x1a, 0x1d, 0x1a, 0x1d,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02,
+ /* IA Addr */
+ 0x00, 0x05, 0x00, 0x1e,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+ 0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d,
+ /* IA address status option */
+ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x01,
+ };
+ static const uint8_t option3[] = {
+ /* IA NA */
+ 0x00, 0x03, 0x00, 0x34, 0x1a, 0x1d, 0x1a, 0x1d,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02,
+ /* IA Addr */
+ 0x00, 0x05, 0x00, 0x24,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+ 0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d,
+ /* IA address status option */
+ 0x00, 0x0d, 0x00, 0x08, 0x00, 0x00, 'f', 'o',
+ 'o', 'b', 'a', 'r',
+ };
+ static const uint8_t option4[] = {
+ /* IA PD */
+ 0x00, 0x19, 0x00, 0x2f, 0x1a, 0x1d, 0x1a, 0x1d,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02,
+ /* IA PD Prefix */
+ 0x00, 0x1a, 0x00, 0x1f,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe,
+ 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ /* PD prefix status option */
+ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00,
+ };
+ static const uint8_t option5[] = {
+ /* IA PD */
+ 0x00, 0x19, 0x00, 0x52, 0x1a, 0x1d, 0x1a, 0x1d,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02,
+ /* IA PD Prefix #1 */
+ 0x00, 0x1a, 0x00, 0x1f,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe,
+ 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ /* PD prefix status option */
+ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00,
+ /* IA PD Prefix #2 */
+ 0x00, 0x1a, 0x00, 0x1f,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x0l, 0xd0,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ /* PD prefix status option */
+ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00,
+ };
+ _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
+ DHCP6Option *option;
+ be32_t iaid;
+ int r;
+
+ memcpy(&iaid, option1 + 4, sizeof(iaid));
+
+ option = (DHCP6Option*) option1;
+ assert_se(sizeof(option1) == sizeof(DHCP6Option) + be16toh(option->len));
+
+ r = dhcp6_option_parse_ia(NULL, 0, be16toh(option->code), be16toh(option->len), option->data, &ia);
+ assert_se(r == -ENOANO);
+
+ r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia);
+ assert_se(r == -EINVAL);
+
+ option->len = htobe16(17);
+ r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia);
+ assert_se(r == -EBADMSG);
+
+ option->len = htobe16(sizeof(DHCP6Option));
+ r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia);
+ assert_se(r == -EBADMSG);
+
+ option = (DHCP6Option*) option2;
+ assert_se(sizeof(option2) == sizeof(DHCP6Option) + be16toh(option->len));
+ r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia);
+ assert_se(r == -ENODATA);
+
+ option = (DHCP6Option*) option3;
+ assert_se(sizeof(option3) == sizeof(DHCP6Option) + be16toh(option->len));
+ r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia);
+ assert_se(r >= 0);
+ assert_se(ia);
+ assert_se(ia->addresses);
+ ia = dhcp6_ia_free(ia);
+
+ option = (DHCP6Option*) option4;
+ assert_se(sizeof(option4) == sizeof(DHCP6Option) + be16toh(option->len));
+ r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia);
+ assert_se(r >= 0);
+ assert_se(ia);
+ assert_se(ia->addresses);
+ assert_se(memcmp(&ia->header.id, &option4[4], 4) == 0);
+ assert_se(memcmp(&ia->header.lifetime_t1, &option4[8], 4) == 0);
+ assert_se(memcmp(&ia->header.lifetime_t2, &option4[12], 4) == 0);
+ ia = dhcp6_ia_free(ia);
+
+ option = (DHCP6Option*) option5;
+ assert_se(sizeof(option5) == sizeof(DHCP6Option) + be16toh(option->len));
+ r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia);
+ assert_se(r >= 0);
+ assert_se(ia);
+ assert_se(ia->addresses);
+ ia = dhcp6_ia_free(ia);
+}
+
+TEST(client_parse_message_issue_22099) {
+ static const uint8_t msg[] = {
+ /* Message type */
+ DHCP6_MESSAGE_REPLY,
+ /* Transaction ID */
+ 0x7c, 0x4c, 0x16,
+ /* Rapid commit */
+ 0x00, SD_DHCP6_OPTION_RAPID_COMMIT, 0x00, 0x00,
+ /* NTP servers */
+ 0x00, SD_DHCP6_OPTION_NTP_SERVER, 0x00, 0x14,
+ /* NTP server (broken sub option and sub option length) */
+ 0x01, 0x00, 0x10, 0x00,
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x15, 0xc8, 0xff, 0xfe, 0xef, 0x1e, 0x4e,
+ /* Client ID */
+ 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e,
+ 0x00, 0x02, /* DUID-EN */
+ 0x00, 0x00, 0xab, 0x11, /* pen */
+ 0x5c, 0x6b, 0x90, 0xec, 0xda, 0x95, 0x15, 0x45, /* id */
+ /* Server ID */
+ 0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0a,
+ 0x00, 0x03, /* DUID-LL */
+ 0x00, 0x01, /* htype */
+ 0xdc, 0x15, 0xc8, 0xef, 0x1e, 0x4e, /* haddr */
+ /* preference */
+ 0x00, SD_DHCP6_OPTION_PREFERENCE, 0x00, 0x01,
+ 0x00,
+ /* DNS servers */
+ 0x00, SD_DHCP6_OPTION_DNS_SERVER, 0x00, 0x10,
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x15, 0xc8, 0xff, 0xfe, 0xef, 0x1e, 0x4e,
+ /* v6 pcp server */
+ 0x00, SD_DHCP6_OPTION_V6_PCP_SERVER, 0x00, 0x10,
+ 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0x00, 0xde, 0x15, 0xc8, 0xff, 0xfe, 0xef, 0x1e, 0x4e,
+ /* IA_NA */
+ 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x28,
+ 0xcc, 0x59, 0x11, 0x7b, /* iaid */
+ 0x00, 0x00, 0x07, 0x08, /* lifetime T1 */
+ 0x00, 0x00, 0x0b, 0x40, /* lifetime T2 */
+ /* IA_NA (iaaddr suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0x00, 0x6a, 0x05, 0xca, 0xff, 0xfe, 0xf1, 0x51, 0x53, /* address */
+ 0x00, 0x00, 0x0e, 0x10, /* preferred lifetime */
+ 0x00, 0x00, 0x1c, 0x20, /* valid lifetime */
+ /* IA_PD */
+ 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x29,
+ 0xcc, 0x59, 0x11, 0x7b, /* iaid */
+ 0x00, 0x00, 0x07, 0x08, /* lifetime T1 */
+ 0x00, 0x00, 0x0b, 0x40, /* lifetime T2 */
+ /* IA_PD (iaprefix suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x0e, 0x10, /* preferred lifetime */
+ 0x00, 0x00, 0x1c, 0x20, /* valid lifetime */
+ 0x3a, /* prefixlen */
+ 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* prefix */
+ };
+ static const uint8_t duid[] = {
+ 0x00, 0x00, 0xab, 0x11, 0x5c, 0x6b, 0x90, 0xec, 0xda, 0x95, 0x15, 0x45,
+ };
+ _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+
+ assert_se(sd_dhcp6_client_new(&client) >= 0);
+ assert_se(sd_dhcp6_client_set_iaid(client, 0xcc59117b) >= 0);
+ assert_se(sd_dhcp6_client_set_duid_raw(client, 2, duid, sizeof(duid)) >= 0);
+
+ assert_se(dhcp6_lease_new_from_message(client, (const DHCP6Message*) msg, sizeof(msg), NULL, NULL, &lease) >= 0);
+}
+
+TEST(client_parse_message_issue_24002) {
+ static const uint8_t msg[] = {
+ /* Message Type */
+ 0x07,
+ /* Transaction ID */
+ 0x0e, 0xa5, 0x7c,
+ /* Client ID */
+ 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e,
+ 0x00, 0x02, /* DUID-EN */
+ 0x00, 0x00, 0xab, 0x11, /* pen */
+ 0x5c, 0x6b, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, /* id */
+ /* Server ID */
+ 0x00, 0x02, 0x00, 0x1a,
+ 0x00, 0x02, 0x00, 0x00, 0x05, 0x83, 0x30, 0x63, 0x3a, 0x38, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
+ /* IA_PD */
+ 0x00, 0x19, 0x00, 0x29,
+ 0xaa, 0xbb, 0xcc, 0xdd, /* iaid */
+ 0x00, 0x00, 0x03, 0x84, /* lifetime (T1) */
+ 0x00, 0x00, 0x05, 0xa0, /* lifetime (T2) */
+ /* IA_PD (iaprefix suboption) */
+ 0x00, 0x1a, 0x00, 0x19,
+ 0x00, 0x00, 0x07, 0x08, /* preferred lifetime */
+ 0x00, 0x00, 0x38, 0x40, /* valid lifetime */
+ 0x38, /* prefixlen */
+ 0x20, 0x03, 0x00, 0xff, 0xaa, 0xbb, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* prefix */
+ /* Rapid commit */
+ 0x00, 0x0e, 0x00, 0x00,
+ /* Trailing invalid byte at the end. See issue #28183. */
+ 00,
+ };
+ static const uint8_t duid[] = {
+ 0x00, 0x00, 0xab, 0x11, 0x5c, 0x6b, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
+ };
+ _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+
+ assert_se(sd_dhcp6_client_new(&client) >= 0);
+ assert_se(sd_dhcp6_client_set_iaid(client, 0xaabbccdd) >= 0);
+ assert_se(sd_dhcp6_client_set_duid_raw(client, 2, duid, sizeof(duid)) >= 0);
+
+ assert_se(dhcp6_lease_new_from_message(client, (const DHCP6Message*) msg, sizeof(msg), NULL, NULL, &lease) >= 0);
+}
+
+static const uint8_t msg_information_request[] = {
+ /* Message type */
+ DHCP6_MESSAGE_INFORMATION_REQUEST,
+ /* Transaction ID */
+ 0x0f, 0xb4, 0xe5,
+ /* MUD URL */
+ /* ORO */
+ 0x00, SD_DHCP6_OPTION_ORO, 0x00, 0x0c,
+ 0x00, SD_DHCP6_OPTION_DNS_SERVER,
+ 0x00, SD_DHCP6_OPTION_DOMAIN,
+ 0x00, SD_DHCP6_OPTION_SNTP_SERVER,
+ 0x00, SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME,
+ 0x00, SD_DHCP6_OPTION_NTP_SERVER,
+ 0x00, SD_DHCP6_OPTION_INF_MAX_RT,
+ /* Client ID */
+ 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e,
+ CLIENT_ID_BYTES,
+ /* Extra options */
+ /* Elapsed time */
+ 0x00, SD_DHCP6_OPTION_ELAPSED_TIME, 0x00, 0x02,
+ 0x00, 0x00,
+};
+
+static const uint8_t msg_solicit[] = {
+ /* Message type */
+ DHCP6_MESSAGE_SOLICIT,
+ /* Transaction ID */
+ 0x0f, 0xb4, 0xe5,
+ /* Rapid commit */
+ 0x00, SD_DHCP6_OPTION_RAPID_COMMIT, 0x00, 0x00,
+ /* IA_NA */
+ 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x0c,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+ /* IA_PD */
+ 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x0c,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+ /* Client FQDN */
+ 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x11,
+ DHCP6_FQDN_FLAG_S,
+ 0x04, 'h', 'o', 's', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
+ /* User Class */
+ /* Vendor Class */
+ /* Vendor Options */
+ /* MUD URL */
+ /* ORO */
+ 0x00, SD_DHCP6_OPTION_ORO, 0x00, 0x0a,
+ 0x00, SD_DHCP6_OPTION_DNS_SERVER,
+ 0x00, SD_DHCP6_OPTION_DOMAIN,
+ 0x00, SD_DHCP6_OPTION_SNTP_SERVER,
+ 0x00, SD_DHCP6_OPTION_NTP_SERVER,
+ 0x00, SD_DHCP6_OPTION_SOL_MAX_RT,
+ /* Client ID */
+ 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e,
+ CLIENT_ID_BYTES,
+ /* Extra options */
+ /* Elapsed time */
+ 0x00, SD_DHCP6_OPTION_ELAPSED_TIME, 0x00, 0x02,
+ 0x00, 0x00,
+};
+
+static const uint8_t msg_request[] = {
+ /* Message type */
+ DHCP6_MESSAGE_REQUEST,
+ /* Transaction ID */
+ 0x00, 0x00, 0x00,
+ /* Server ID */
+ 0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0e,
+ SERVER_ID_BYTES,
+ /* IA_NA */
+ 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x44,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS1_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS2_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ /* IA_PD */
+ 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x46,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX1_BYTES,
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX2_BYTES,
+ /* Client FQDN */
+ 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x11,
+ DHCP6_FQDN_FLAG_S,
+ 0x04, 'h', 'o', 's', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
+ /* User Class */
+ /* Vendor Class */
+ /* Vendor Options */
+ /* MUD URL */
+ /* ORO */
+ 0x00, SD_DHCP6_OPTION_ORO, 0x00, 0x08,
+ 0x00, SD_DHCP6_OPTION_DNS_SERVER,
+ 0x00, SD_DHCP6_OPTION_DOMAIN,
+ 0x00, SD_DHCP6_OPTION_SNTP_SERVER,
+ 0x00, SD_DHCP6_OPTION_NTP_SERVER,
+ /* Client ID */
+ 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e,
+ CLIENT_ID_BYTES,
+ /* Extra options */
+ /* Elapsed time */
+ 0x00, SD_DHCP6_OPTION_ELAPSED_TIME, 0x00, 0x02,
+ 0x00, 0x00,
+};
+
+/* RFC 3315 section 18.1.6. The DHCP6 Release message must include:
+ - transaction id
+ - server identifier
+ - client identifier
+ - all released IA with addresses included
+ - elapsed time (required for all messages).
+ All other options aren't required. */
+static const uint8_t msg_release[] = {
+ /* Message type */
+ DHCP6_MESSAGE_RELEASE,
+ /* Transaction ID */
+ 0x00, 0x00, 0x00,
+ /* Server ID */
+ 0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0e,
+ SERVER_ID_BYTES,
+ /* IA_NA */
+ 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x44,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS1_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS2_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ /* IA_PD */
+ 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x46,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX1_BYTES,
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX2_BYTES,
+ /* Client ID */
+ 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e,
+ CLIENT_ID_BYTES,
+ /* Extra options */
+ /* Elapsed time */
+ 0x00, SD_DHCP6_OPTION_ELAPSED_TIME, 0x00, 0x02,
+ 0x00, 0x00,
+};
+
+static const uint8_t msg_reply[] = {
+ /* Message type */
+ DHCP6_MESSAGE_REPLY,
+ /* Transaction ID */
+ 0x0f, 0xb4, 0xe5,
+ /* Client ID */
+ 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e,
+ CLIENT_ID_BYTES,
+ /* Server ID */
+ 0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0e,
+ SERVER_ID_BYTES,
+ /* Rapid commit */
+ 0x00, SD_DHCP6_OPTION_RAPID_COMMIT, 0x00, 0x01,
+ 0x00,
+ /* IA_NA */
+ 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x66,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x50, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x78, /* lifetime T2 */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS2_BYTES,
+ 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS1_BYTES,
+ 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */
+ /* IA_NA (status code suboption) */
+ 0x00, SD_DHCP6_OPTION_STATUS_CODE, 0x00, 0x1e,
+ 0x00, 0x00, /* status code */
+ 0x41, 0x6c, 0x6c, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, 0x77, 0x65,
+ 0x72, 0x65, 0x20, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x2e, /* status message */
+ /* IA_PD */
+ 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x46,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x50, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x78, /* lifetime T2 */
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX2_BYTES,
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX1_BYTES,
+ /* DNS servers */
+ 0x00, SD_DHCP6_OPTION_DNS_SERVER, 0x00, 0x20,
+ DNS1_BYTES,
+ DNS2_BYTES,
+ /* SNTP servers */
+ 0x00, SD_DHCP6_OPTION_SNTP_SERVER, 0x00, 0x20,
+ SNTP1_BYTES,
+ SNTP2_BYTES,
+ /* NTP servers */
+ 0x00, SD_DHCP6_OPTION_NTP_SERVER, 0x00, 0x37,
+ /* NTP server (address suboption) */
+ 0x00, DHCP6_NTP_SUBOPTION_SRV_ADDR, 0x00, 0x10,
+ NTP1_BYTES,
+ /* NTP server (address suboption) */
+ 0x00, DHCP6_NTP_SUBOPTION_SRV_ADDR, 0x00, 0x10,
+ NTP2_BYTES,
+ /* NTP server (fqdn suboption) */
+ 0x00, DHCP6_NTP_SUBOPTION_SRV_FQDN, 0x00, 0x0b,
+ 0x03, 'n', 't', 'p', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
+ /* Domain list */
+ 0x00, SD_DHCP6_OPTION_DOMAIN, 0x00, 0x0b,
+ 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
+ /* Client FQDN */
+ 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x12,
+ 0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a',
+ /* Vendor specific options */
+ 0x00, SD_DHCP6_OPTION_VENDOR_OPTS, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0xf7, 0x00, 0x01, VENDOR_SUBOPTION_BYTES,
+};
+
+static const uint8_t msg_advertise[] = {
+ /* Message type */
+ DHCP6_MESSAGE_ADVERTISE,
+ /* Transaction ID */
+ 0x0f, 0xb4, 0xe5,
+ /* Client ID */
+ 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e,
+ CLIENT_ID_BYTES,
+ /* Server ID */
+ 0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0e,
+ SERVER_ID_BYTES,
+ /* Preference */
+ 0x00, SD_DHCP6_OPTION_PREFERENCE, 0x00, 0x01,
+ 0xff,
+ /* IA_NA */
+ 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x7a,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x50, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x78, /* lifetime T2 */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS2_BYTES, /* address */
+ 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS1_BYTES, /* address */
+ 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */
+ /* IA_NA (status code suboption) */
+ 0x00, SD_DHCP6_OPTION_STATUS_CODE, 0x00, 0x32,
+ 0x00, 0x00, /* status code */
+ 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x28, 0x65, 0x73, 0x29, 0x20, 0x72, 0x65, 0x6e, 0x65,
+ 0x77, 0x65, 0x64, 0x2e, 0x20, 0x47, 0x72, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x20, 0x66,
+ 0x72, 0x6f, 0x6d, 0x20, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x74, 0x20, 0x45, 0x61, 0x72, 0x74, 0x68, /* status message */
+ /* IA_PD */
+ 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x46,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x50, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x78, /* lifetime T2 */
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX2_BYTES,
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX1_BYTES,
+ /* DNS servers */
+ 0x00, SD_DHCP6_OPTION_DNS_SERVER, 0x00, 0x20,
+ DNS1_BYTES,
+ DNS2_BYTES,
+ /* SNTP servers */
+ 0x00, SD_DHCP6_OPTION_SNTP_SERVER, 0x00, 0x20,
+ SNTP1_BYTES,
+ SNTP2_BYTES,
+ /* NTP servers */
+ 0x00, SD_DHCP6_OPTION_NTP_SERVER, 0x00, 0x37,
+ /* NTP server (address suboption) */
+ 0x00, DHCP6_NTP_SUBOPTION_SRV_ADDR, 0x00, 0x10,
+ NTP1_BYTES,
+ /* NTP server (address suboption) */
+ 0x00, DHCP6_NTP_SUBOPTION_SRV_ADDR, 0x00, 0x10,
+ NTP2_BYTES,
+ /* NTP server (fqdn suboption) */
+ 0x00, DHCP6_NTP_SUBOPTION_SRV_FQDN, 0x00, 0x0b,
+ 0x03, 'n', 't', 'p', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
+ /* Domain list */
+ 0x00, SD_DHCP6_OPTION_DOMAIN, 0x00, 0x0b,
+ 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
+ /* Client FQDN */
+ 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x12,
+ 0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a',
+ /* Vendor specific options */
+ 0x00, SD_DHCP6_OPTION_VENDOR_OPTS, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0xf7, 0x00, 0x01, VENDOR_SUBOPTION_BYTES,
+};
+
+static void test_client_verify_information_request(const DHCP6Message *msg, size_t len) {
+ log_debug("/* %s */", __func__);
+
+ assert_se(len == sizeof(msg_information_request));
+ /* The elapsed time value is not deterministic. Skip it. */
+ assert_se(memcmp(msg, msg_information_request, len - sizeof(be16_t)) == 0);
+}
+
+static void test_client_verify_solicit(const DHCP6Message *msg, size_t len) {
+ log_debug("/* %s */", __func__);
+
+ assert_se(len == sizeof(msg_solicit));
+ /* The elapsed time value is not deterministic. Skip it. */
+ assert_se(memcmp(msg, msg_solicit, len - sizeof(be16_t)) == 0);
+}
+
+static void test_client_verify_release(const DHCP6Message *msg, size_t len) {
+ log_debug("/* %s */", __func__);
+
+ assert_se(len == sizeof(msg_release));
+ assert_se(msg->type == DHCP6_MESSAGE_RELEASE);
+ /* The transaction ID and elapsed time value are not deterministic. Skip them. */
+ assert_se(memcmp(msg->options, msg_release + offsetof(DHCP6Message, options),
+ len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0);
+}
+
+static void test_client_verify_request(const DHCP6Message *msg, size_t len) {
+ log_debug("/* %s */", __func__);
+
+ assert_se(len == sizeof(msg_request));
+ assert_se(msg->type == DHCP6_MESSAGE_REQUEST);
+ /* The transaction ID and elapsed time value are not deterministic. Skip them. */
+ assert_se(memcmp(msg->options, msg_request + offsetof(DHCP6Message, options),
+ len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0);
+}
+
+static void test_lease_common(sd_dhcp6_client *client) {
+ sd_dhcp6_lease *lease;
+ sd_dhcp6_option **suboption;
+ const struct in6_addr *addrs;
+ const char *str;
+ char **strv;
+ uint8_t *id;
+ size_t len;
+
+ assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0);
+
+ assert_se(dhcp6_lease_get_clientid(lease, &id, &len) >= 0);
+ assert_se(memcmp_nn(id, len, client_id, sizeof(client_id)) == 0);
+
+ assert_se(sd_dhcp6_lease_get_domains(lease, &strv) == 1);
+ assert_se(streq(strv[0], "lab.intra"));
+ assert_se(!strv[1]);
+
+ assert_se(sd_dhcp6_lease_get_fqdn(lease, &str) >= 0);
+ assert_se(streq(str, "client.lab.intra"));
+
+ assert_se(sd_dhcp6_lease_get_dns(lease, &addrs) == 2);
+ assert_se(in6_addr_equal(&addrs[0], &dns1));
+ assert_se(in6_addr_equal(&addrs[1], &dns2));
+
+ assert_se(sd_dhcp6_lease_get_ntp_addrs(lease, &addrs) == 2);
+ assert_se(in6_addr_equal(&addrs[0], &ntp1));
+ assert_se(in6_addr_equal(&addrs[1], &ntp2));
+
+ assert_se(sd_dhcp6_lease_get_ntp_fqdn(lease, &strv) == 1);
+ assert_se(streq(strv[0], "ntp.intra"));
+ assert_se(!strv[1]);
+
+ assert_se(lease->sntp_count == 2);
+ assert_se(in6_addr_equal(&lease->sntp[0], &sntp1));
+ assert_se(in6_addr_equal(&lease->sntp[1], &sntp2));
+
+ assert_se(sd_dhcp6_lease_get_vendor_options(lease, &suboption) > 0);
+ assert_se((*suboption)->enterprise_identifier == vendor_suboption.enterprise_identifier);
+ assert_se((*suboption)->option == vendor_suboption.option);
+ assert_se(*(uint8_t*)(*suboption)->data == *(uint8_t*)vendor_suboption.data);
+}
+
+static void test_lease_managed(sd_dhcp6_client *client) {
+ sd_dhcp6_lease *lease;
+ struct in6_addr addr;
+ usec_t lt_pref, lt_valid;
+ uint8_t *id, prefixlen;
+ size_t len;
+
+ assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0);
+
+ assert_se(dhcp6_lease_get_serverid(lease, &id, &len) >= 0);
+ assert_se(memcmp_nn(id, len, server_id, sizeof(server_id)) == 0);
+
+ assert_se(sd_dhcp6_lease_has_address(lease));
+ assert_se(sd_dhcp6_lease_has_pd_prefix(lease));
+
+ for (unsigned i = 0; i < 2; i++) {
+ assert_se(sd_dhcp6_lease_address_iterator_reset(lease));
+ assert_se(sd_dhcp6_lease_get_address(lease, &addr) >= 0);
+ assert_se(sd_dhcp6_lease_get_address_lifetime(lease, &lt_pref, &lt_valid) >= 0);
+ assert_se(in6_addr_equal(&addr, &ia_na_address1));
+ assert_se(lt_pref == 150 * USEC_PER_SEC);
+ assert_se(lt_valid == 180 * USEC_PER_SEC);
+ assert_se(sd_dhcp6_lease_address_iterator_next(lease));
+ assert_se(sd_dhcp6_lease_get_address(lease, &addr) >= 0);
+ assert_se(sd_dhcp6_lease_get_address_lifetime(lease, &lt_pref, &lt_valid) >= 0);
+ assert_se(in6_addr_equal(&addr, &ia_na_address2));
+ assert_se(lt_pref == 150 * USEC_PER_SEC);
+ assert_se(lt_valid == 180 * USEC_PER_SEC);
+ assert_se(!sd_dhcp6_lease_address_iterator_next(lease));
+
+ assert_se(sd_dhcp6_lease_pd_iterator_reset(lease));
+ assert_se(sd_dhcp6_lease_get_pd_prefix(lease, &addr, &prefixlen) >= 0);
+ assert_se(sd_dhcp6_lease_get_pd_lifetime(lease, &lt_pref, &lt_valid) >= 0);
+ assert_se(in6_addr_equal(&addr, &ia_pd_prefix1));
+ assert_se(prefixlen == 64);
+ assert_se(lt_pref == 150 * USEC_PER_SEC);
+ assert_se(lt_valid == 180 * USEC_PER_SEC);
+ assert_se(sd_dhcp6_lease_pd_iterator_next(lease));
+ assert_se(sd_dhcp6_lease_get_pd_prefix(lease, &addr, &prefixlen) >= 0);
+ assert_se(sd_dhcp6_lease_get_pd_lifetime(lease, &lt_pref, &lt_valid) >= 0);
+ assert_se(in6_addr_equal(&addr, &ia_pd_prefix2));
+ assert_se(prefixlen == 64);
+ assert_se(lt_pref == 150 * USEC_PER_SEC);
+ assert_se(lt_valid == 180 * USEC_PER_SEC);
+ assert_se(!sd_dhcp6_lease_pd_iterator_next(lease));
+ }
+
+ test_lease_common(client);
+}
+
+static void test_client_callback(sd_dhcp6_client *client, int event, void *userdata) {
+ switch (event) {
+ case SD_DHCP6_CLIENT_EVENT_STOP:
+ log_debug("/* %s (event=stop) */", __func__);
+ return;
+
+ case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST:
+ log_debug("/* %s (event=information-request) */", __func__);
+
+ assert_se(test_client_sent_message_count == 1);
+
+ test_lease_common(client);
+
+ assert_se(sd_dhcp6_client_set_information_request(client, false) >= 0);
+ assert_se(sd_dhcp6_client_start(client) >= 0);
+ assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message*) msg_advertise)->transaction_id) >= 0);
+ break;
+
+ case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE:
+ log_debug("/* %s (event=ip-acquire) */", __func__);
+
+ assert_se(IN_SET(test_client_sent_message_count, 3, 5));
+
+ test_lease_managed(client);
+
+ switch (test_client_sent_message_count) {
+ case 3:
+ assert_se(sd_dhcp6_client_stop(client) >= 0);
+ assert_se(sd_dhcp6_client_start(client) >= 0);
+ assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message*) msg_reply)->transaction_id) >= 0);
+ break;
+
+ case 5:
+ assert_se(sd_event_exit(sd_dhcp6_client_get_event(client), 0) >= 0);
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ break;
+
+ case DHCP6_CLIENT_EVENT_TEST_ADVERTISED: {
+ sd_dhcp6_lease *lease;
+ uint8_t preference;
+
+ log_debug("/* %s (event=test-advertised) */", __func__);
+
+ assert_se(test_client_sent_message_count == 2);
+
+ test_lease_managed(client);
+
+ assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0);
+ assert_se(dhcp6_lease_get_preference(lease, &preference) >= 0);
+ assert_se(preference == 0xff);
+
+ assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message*) msg_reply)->transaction_id) >= 0);
+ break;
+ }
+ default:
+ assert_not_reached();
+ }
+}
+
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *a, const void *packet, size_t len) {
+ log_debug("/* %s(count=%u) */", __func__, test_client_sent_message_count);
+
+ assert_se(a);
+ assert_se(in6_addr_equal(a, &mcast_address));
+ assert_se(packet);
+ assert_se(len >= sizeof(DHCP6Message));
+
+ switch (test_client_sent_message_count) {
+ case 0:
+ test_client_verify_information_request(packet, len);
+ assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply));
+ break;
+
+ case 1:
+ test_client_verify_solicit(packet, len);
+ assert_se(write(test_fd[1], msg_advertise, sizeof(msg_advertise)) == sizeof(msg_advertise));
+ break;
+
+ case 2:
+ test_client_callback(client_ref, DHCP6_CLIENT_EVENT_TEST_ADVERTISED, NULL);
+ test_client_verify_request(packet, len);
+ assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply));
+ break;
+
+ case 3:
+ test_client_verify_release(packet, len);
+ /* when stopping, dhcp6 client doesn't wait for release server reply */
+ assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply));
+ break;
+
+ case 4:
+ test_client_verify_solicit(packet, len);
+ assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply));
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ test_client_sent_message_count++;
+ return len;
+}
+
+int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *a) {
+ assert_se(ifindex == test_ifindex);
+ assert_se(a);
+ assert_se(in6_addr_equal(a, &local_address));
+
+ assert_se(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) >= 0);
+ return TAKE_FD(test_fd[0]);
+}
+
+TEST(dhcp6_client) {
+ _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+
+ assert_se(sd_event_new(&e) >= 0);
+ assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
+ 2 * USEC_PER_SEC, 0,
+ NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0);
+
+ assert_se(sd_dhcp6_client_new(&client) >= 0);
+ assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0);
+ assert_se(sd_dhcp6_client_set_ifindex(client, test_ifindex) == 0);
+ assert_se(sd_dhcp6_client_set_local_address(client, &local_address) >= 0);
+ assert_se(sd_dhcp6_client_set_fqdn(client, "host.lab.intra") >= 0);
+ assert_se(sd_dhcp6_client_set_iaid(client, unaligned_read_be32((uint8_t[]) { IA_ID_BYTES })) >= 0);
+ assert_se(sd_dhcp6_client_set_send_release(client, true) >= 0);
+
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER) >= 0);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN) >= 0);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER) >= 0);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SNTP_SERVER) >= 0);
+
+ assert_se(sd_dhcp6_client_set_information_request(client, true) >= 0);
+ assert_se(sd_dhcp6_client_set_callback(client, test_client_callback, NULL) >= 0);
+
+ assert_se(sd_dhcp6_client_start(client) >= 0);
+
+ assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message*) msg_reply)->transaction_id) >= 0);
+
+ assert_se(client_ref = sd_dhcp6_client_ref(client));
+
+ assert_se(sd_event_loop(e) >= 0);
+
+ assert_se(test_client_sent_message_count == 5);
+
+ assert_se(!sd_dhcp6_client_unref(client_ref));
+ test_fd[1] = safe_close(test_fd[1]);
+}
+
+static int intro(void) {
+ assert_se(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", 1) >= 0);
+ return 0;
+}
+
+DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro);
diff --git a/src/libsystemd-network/test-ipv4ll-manual.c b/src/libsystemd-network/test-ipv4ll-manual.c
new file mode 100644
index 0000000..5dc6b10
--- /dev/null
+++ b/src/libsystemd-network/test-ipv4ll-manual.c
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <net/if.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <linux/veth.h>
+
+#include "sd-event.h"
+#include "sd-ipv4ll.h"
+#include "sd-netlink.h"
+
+#include "alloc-util.h"
+#include "in-addr-util.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "tests.h"
+
+static void ll_handler(sd_ipv4ll *ll, int event, void *userdata) {
+ assert_se(ll);
+
+ struct in_addr addr;
+ const char *pretty = sd_ipv4ll_get_address(ll, &addr) >= 0 ? IN4_ADDR_TO_STRING(&addr) : NULL;
+
+ switch (event) {
+ case SD_IPV4LL_EVENT_BIND:
+ log_info("bound %s", strna(pretty));
+ break;
+ case SD_IPV4LL_EVENT_CONFLICT:
+ log_info("conflict on %s", strna(pretty));
+ break;
+ case SD_IPV4LL_EVENT_STOP:
+ log_error("the client was stopped with address %s", strna(pretty));
+ break;
+ default:
+ assert_not_reached();
+ }
+}
+
+static int client_run(int ifindex, const char *seed_str, const struct in_addr *start_address, const struct ether_addr *ha, sd_event *e) {
+ sd_ipv4ll *ll;
+
+ assert_se(sd_ipv4ll_new(&ll) >= 0);
+ assert_se(sd_ipv4ll_attach_event(ll, e, 0) >= 0);
+
+ assert_se(sd_ipv4ll_set_ifindex(ll, ifindex) >= 0);
+ assert_se(sd_ipv4ll_set_mac(ll, ha) >= 0);
+ assert_se(sd_ipv4ll_set_callback(ll, ll_handler, NULL) >= 0);
+
+ if (seed_str) {
+ unsigned seed;
+
+ assert_se(safe_atou(seed_str, &seed) >= 0);
+
+ assert_se(sd_ipv4ll_set_address_seed(ll, seed) >= 0);
+ }
+
+ if (start_address && in4_addr_is_set(start_address))
+ assert_se(sd_ipv4ll_set_address(ll, start_address) >= 0);
+
+ log_info("starting IPv4LL client");
+
+ assert_se(sd_ipv4ll_start(ll) >= 0);
+
+ assert_se(sd_event_loop(e) >= 0);
+
+ assert_se(!sd_ipv4ll_unref(ll));
+
+ return EXIT_SUCCESS;
+}
+
+static int test_ll(const char *ifname, const char *seed, const struct in_addr *start_address) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL;
+ struct ether_addr ha;
+ int ifindex;
+
+ assert_se(sd_event_new(&e) >= 0);
+
+ assert_se(sd_netlink_open(&rtnl) >= 0);
+ assert_se(sd_netlink_attach_event(rtnl, e, 0) >= 0);
+
+ assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, 0) >= 0);
+ assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, ifname) >= 0);
+ assert_se(sd_netlink_call(rtnl, m, 0, &reply) >= 0);
+
+ assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0);
+ assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &ha) >= 0);
+
+ client_run(ifindex, seed, start_address, &ha, e);
+
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[]) {
+ test_setup_logging(LOG_DEBUG);
+
+ if (argc == 2)
+ return test_ll(argv[1], NULL, NULL);
+ else if (argc == 3) {
+ int r;
+ union in_addr_union a;
+
+ r = in_addr_from_string(AF_INET, argv[2], &a);
+ if (r < 0)
+ return test_ll(argv[1], argv[2], NULL);
+ else
+ return test_ll(argv[1], NULL, &a.in);
+ } else {
+ log_error("This program takes one or two arguments.\n"
+ "\t %s <ifname> [<seed>|<start_address>]", program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+}
diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c
new file mode 100644
index 0000000..bb42930
--- /dev/null
+++ b/src/libsystemd-network/test-ipv4ll.c
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014 Axis Communications AB. All rights reserved.
+***/
+
+#include <errno.h>
+#include <netinet/if_ether.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "sd-ipv4ll.h"
+
+#include "arp-util.h"
+#include "fd-util.h"
+#include "socket-util.h"
+#include "tests.h"
+
+static bool verbose = false;
+static bool extended = false;
+static int test_fd[2];
+
+static int basic_request_handler_bind = 0;
+static int basic_request_handler_stop = 0;
+static void* basic_request_handler_userdata = (void*) 0xCABCAB;
+
+static void basic_request_handler(sd_ipv4ll *ll, int event, void *userdata) {
+ assert_se(userdata == basic_request_handler_userdata);
+
+ switch (event) {
+ case SD_IPV4LL_EVENT_STOP:
+ basic_request_handler_stop = 1;
+ break;
+ case SD_IPV4LL_EVENT_BIND:
+ basic_request_handler_bind = 1;
+ break;
+ default:
+ assert_se(0);
+ break;
+ }
+}
+
+int arp_send_packet(
+ int fd,
+ int ifindex,
+ const struct in_addr *pa,
+ const struct ether_addr *ha,
+ bool announce) {
+
+ struct ether_arp ea = {};
+
+ assert_se(fd >= 0);
+ assert_se(ifindex > 0);
+ assert_se(pa);
+ assert_se(ha);
+
+ if (send(fd, &ea, sizeof(struct ether_arp), 0) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int arp_update_filter(int fd, const struct in_addr *a, const struct ether_addr *eth_mac) {
+ return 0;
+}
+
+int arp_network_bind_raw_socket(int ifindex, const struct in_addr *a, const struct ether_addr *eth_mac) {
+ if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0)
+ return -errno;
+
+ return test_fd[0];
+}
+
+static void test_public_api_setters(sd_event *e) {
+ struct in_addr address = {};
+ uint64_t seed = 0;
+ sd_ipv4ll *ll;
+ struct ether_addr mac_addr = {
+ .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}};
+
+ if (verbose)
+ printf("* %s\n", __func__);
+
+ assert_se(sd_ipv4ll_new(&ll) == 0);
+ assert_se(ll);
+
+ assert_se(sd_ipv4ll_attach_event(NULL, NULL, 0) == -EINVAL);
+ assert_se(sd_ipv4ll_attach_event(ll, e, 0) == 0);
+ assert_se(sd_ipv4ll_attach_event(ll, e, 0) == -EBUSY);
+
+ assert_se(sd_ipv4ll_set_callback(NULL, NULL, NULL) == -EINVAL);
+ assert_se(sd_ipv4ll_set_callback(ll, NULL, NULL) == 0);
+
+ assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL);
+ address.s_addr |= htobe32(169U << 24 | 254U << 16);
+ assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL);
+ address.s_addr |= htobe32(0x00FF);
+ assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL);
+ address.s_addr |= htobe32(0xF000);
+ assert_se(sd_ipv4ll_set_address(ll, &address) == 0);
+ address.s_addr |= htobe32(0x0F00);
+ assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL);
+
+ assert_se(sd_ipv4ll_set_address_seed(NULL, seed) == -EINVAL);
+ assert_se(sd_ipv4ll_set_address_seed(ll, seed) == 0);
+
+ assert_se(sd_ipv4ll_set_mac(NULL, NULL) == -EINVAL);
+ assert_se(sd_ipv4ll_set_mac(ll, NULL) == -EINVAL);
+ assert_se(sd_ipv4ll_set_mac(ll, &mac_addr) == 0);
+
+ assert_se(sd_ipv4ll_set_ifindex(NULL, -1) == -EINVAL);
+ assert_se(sd_ipv4ll_set_ifindex(ll, -1) == -EINVAL);
+ assert_se(sd_ipv4ll_set_ifindex(ll, -99) == -EINVAL);
+ assert_se(sd_ipv4ll_set_ifindex(ll, 1) == 0);
+
+ assert_se(sd_ipv4ll_ref(ll) == ll);
+ assert_se(sd_ipv4ll_unref(ll) == NULL);
+
+ /* Cleanup */
+ assert_se(sd_ipv4ll_unref(ll) == NULL);
+}
+
+static void test_basic_request(sd_event *e, const struct in_addr *start_address) {
+
+ sd_ipv4ll *ll;
+ struct ether_arp arp;
+ struct ether_addr mac_addr = {
+ .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}};
+
+ if (verbose)
+ printf("* %s\n", __func__);
+
+ assert_se(sd_ipv4ll_new(&ll) == 0);
+ if (in4_addr_is_set(start_address))
+ assert_se(sd_ipv4ll_set_address(ll, start_address) >= 0);
+ assert_se(sd_ipv4ll_start(ll) == -EINVAL);
+
+ assert_se(sd_ipv4ll_attach_event(ll, e, 0) == 0);
+ assert_se(sd_ipv4ll_start(ll) == -EINVAL);
+
+ assert_se(sd_ipv4ll_set_mac(ll, &mac_addr) == 0);
+ assert_se(sd_ipv4ll_start(ll) == -EINVAL);
+
+ assert_se(sd_ipv4ll_set_callback(ll, basic_request_handler,
+ basic_request_handler_userdata) == 0);
+ assert_se(sd_ipv4ll_start(ll) == -EINVAL);
+
+ assert_se(sd_ipv4ll_set_ifindex(ll, 1) == 0);
+ assert_se(sd_ipv4ll_start(ll) == 1);
+
+ sd_event_run(e, UINT64_MAX);
+ assert_se(sd_ipv4ll_start(ll) == 0);
+
+ assert_se(sd_ipv4ll_is_running(ll));
+
+ /* PROBE */
+ sd_event_run(e, UINT64_MAX);
+ assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp));
+
+ if (extended) {
+ /* PROBE */
+ sd_event_run(e, UINT64_MAX);
+ assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp));
+
+ /* PROBE */
+ sd_event_run(e, UINT64_MAX);
+ assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp));
+
+ sd_event_run(e, UINT64_MAX);
+ assert_se(basic_request_handler_bind == 1);
+
+ if (in4_addr_is_set(start_address)) {
+ struct in_addr address;
+
+ assert_se(sd_ipv4ll_get_address(ll, &address) >= 0);
+ assert_se(start_address->s_addr == address.s_addr);
+ }
+ }
+
+ sd_ipv4ll_stop(ll);
+ assert_se(basic_request_handler_stop == 1);
+
+ /* Cleanup */
+ assert_se(sd_ipv4ll_unref(ll) == NULL);
+ safe_close(test_fd[1]);
+}
+
+int main(int argc, char *argv[]) {
+ struct in_addr start_address = {};
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+
+ test_setup_logging(LOG_DEBUG);
+
+ assert_se(sd_event_new(&e) >= 0);
+
+ test_public_api_setters(e);
+ test_basic_request(e, &start_address);
+
+ basic_request_handler_bind = 0;
+ basic_request_handler_stop = 0;
+ start_address.s_addr = htobe32(169U << 24 | 254U << 16 | 1U << 8 | 2U);
+ test_basic_request(e, &start_address);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/test-lldp-rx.c b/src/libsystemd-network/test-lldp-rx.c
new file mode 100644
index 0000000..feb53b5
--- /dev/null
+++ b/src/libsystemd-network/test-lldp-rx.c
@@ -0,0 +1,378 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <net/ethernet.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "sd-event.h"
+#include "sd-lldp-rx.h"
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "lldp-network.h"
+#include "macro.h"
+#include "string-util.h"
+#include "tests.h"
+
+#define TEST_LLDP_PORT "em1"
+#define TEST_LLDP_TYPE_SYSTEM_NAME "systemd-lldp"
+#define TEST_LLDP_TYPE_SYSTEM_DESC "systemd-lldp-desc"
+
+static int test_fd[2] = EBADF_PAIR;
+static int lldp_rx_handler_calls;
+
+int lldp_network_bind_raw_socket(int ifindex) {
+ if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0)
+ return -errno;
+
+ return test_fd[0];
+}
+
+static void lldp_rx_handler(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_lldp_neighbor *n, void *userdata) {
+ lldp_rx_handler_calls++;
+}
+
+static int start_lldp_rx(sd_lldp_rx **lldp_rx, sd_event *e, sd_lldp_rx_callback_t cb, void *cb_data) {
+ int r;
+
+ r = sd_lldp_rx_new(lldp_rx);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_rx_set_ifindex(*lldp_rx, 42);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_rx_set_callback(*lldp_rx, cb, cb_data);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_rx_attach_event(*lldp_rx, e, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_rx_start(*lldp_rx);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int stop_lldp_rx(sd_lldp_rx *lldp_rx) {
+ int r;
+
+ r = sd_lldp_rx_stop(lldp_rx);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_rx_detach_event(lldp_rx);
+ if (r < 0)
+ return r;
+
+ sd_lldp_rx_unref(lldp_rx);
+ safe_close(test_fd[1]);
+
+ return 0;
+}
+
+static void test_receive_basic_packet(sd_event *e) {
+
+ static const uint8_t frame[] = {
+ /* Ethernet header */
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */
+ 0x88, 0xcc, /* Ethertype */
+ /* LLDP mandatory TLVs */
+ 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */
+ 0x03, 0x04, 0x05,
+ 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */
+ 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds */
+ /* LLDP optional TLVs */
+ 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, /* Port Description: "Port" */
+ 0x0a, 0x03, 0x53, 0x59, 0x53, /* System Name: "SYS" */
+ 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, /* System Description: "foo" (NULL-terminated) */
+ 0x00, 0x00 /* End Of LLDPDU */
+ };
+
+ sd_lldp_rx *lldp_rx;
+ sd_lldp_neighbor **neighbors;
+ uint8_t type;
+ const void *data;
+ uint16_t ttl;
+ size_t length;
+ const char *str;
+
+ lldp_rx_handler_calls = 0;
+ assert_se(start_lldp_rx(&lldp_rx, e, lldp_rx_handler, NULL) == 0);
+
+ assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
+ sd_event_run(e, 0);
+ assert_se(lldp_rx_handler_calls == 1);
+ assert_se(sd_lldp_rx_get_neighbors(lldp_rx, &neighbors) == 1);
+
+ assert_se(sd_lldp_neighbor_get_chassis_id(neighbors[0], &type, &data, &length) == 0);
+ assert_se(type == SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS);
+ assert_se(length == ETH_ALEN);
+ assert_se(!memcmp(data, "\x00\x01\x02\x03\x04\x05", ETH_ALEN));
+
+ assert_se(sd_lldp_neighbor_get_port_id(neighbors[0], &type, &data, &length) == 0);
+ assert_se(type == SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME);
+ assert_se(length == 3);
+ assert_se(!memcmp(data, "1/3", 3));
+
+ assert_se(sd_lldp_neighbor_get_port_description(neighbors[0], &str) == 0);
+ assert_se(streq(str, "Port"));
+
+ assert_se(sd_lldp_neighbor_get_system_name(neighbors[0], &str) == 0);
+ assert_se(streq(str, "SYS"));
+
+ assert_se(sd_lldp_neighbor_get_system_description(neighbors[0], &str) == 0);
+ assert_se(streq(str, "foo"));
+
+ assert_se(sd_lldp_neighbor_get_ttl(neighbors[0], &ttl) == 0);
+ assert_se(ttl == 120);
+
+ sd_lldp_neighbor_unref(neighbors[0]);
+ free(neighbors);
+
+ assert_se(stop_lldp_rx(lldp_rx) == 0);
+}
+
+static void test_receive_incomplete_packet(sd_event *e) {
+ sd_lldp_rx *lldp_rx;
+ sd_lldp_neighbor **neighbors;
+ uint8_t frame[] = {
+ /* Ethernet header */
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */
+ 0x88, 0xcc, /* Ethertype */
+ /* LLDP mandatory TLVs */
+ 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */
+ 0x03, 0x04, 0x05,
+ 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */
+ /* Missing TTL */
+ 0x00, 0x00 /* End Of LLDPDU */
+ };
+
+ lldp_rx_handler_calls = 0;
+ assert_se(start_lldp_rx(&lldp_rx, e, lldp_rx_handler, NULL) == 0);
+
+ assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
+ sd_event_run(e, 0);
+ assert_se(lldp_rx_handler_calls == 0);
+ assert_se(sd_lldp_rx_get_neighbors(lldp_rx, &neighbors) == 0);
+
+ assert_se(stop_lldp_rx(lldp_rx) == 0);
+}
+
+static void test_receive_oui_packet(sd_event *e) {
+ sd_lldp_rx *lldp_rx;
+ sd_lldp_neighbor **neighbors;
+ uint8_t frame[] = {
+ /* Ethernet header */
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */
+ 0x88, 0xcc, /* Ethertype */
+ /* LLDP mandatory TLVs */
+ 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */
+ 0x03, 0x04, 0x05,
+ 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port TLV: interface name, "1/3" */
+ 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds */
+ /* LLDP optional TLVs */
+ 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01, /* Port VLAN ID: 0x1234 */
+ 0x12, 0x34,
+ 0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02, /* Port and protocol: flag 1, PPVID 0x7788 */
+ 0x01, 0x77, 0x88,
+ 0xfe, 0x0d, 0x00, 0x80, 0xc2, 0x03, /* VLAN Name: ID 0x1234, name "Vlan51" */
+ 0x12, 0x34, 0x06, 0x56, 0x6c, 0x61,
+ 0x6e, 0x35, 0x31,
+ 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x06, /* Management VID: 0x0102 */
+ 0x01, 0x02,
+ 0xfe, 0x09, 0x00, 0x80, 0xc2, 0x07, /* Link aggregation: status 1, ID 0x00140012 */
+ 0x01, 0x00, 0x14, 0x00, 0x12,
+ 0xfe, 0x07, 0x00, 0x12, 0x0f, 0x02, /* 802.3 Power via MDI: PSE, MDI enabled */
+ 0x07, 0x01, 0x00,
+ 0x00, 0x00 /* End of LLDPDU */
+ };
+
+ lldp_rx_handler_calls = 0;
+ assert_se(start_lldp_rx(&lldp_rx, e, lldp_rx_handler, NULL) == 0);
+
+ assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
+ sd_event_run(e, 0);
+ assert_se(lldp_rx_handler_calls == 1);
+ assert_se(sd_lldp_rx_get_neighbors(lldp_rx, &neighbors) == 1);
+
+ assert_se(sd_lldp_neighbor_tlv_rewind(neighbors[0]) >= 0);
+ assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_CHASSIS_ID) > 0);
+ assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+ assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_PORT_ID) > 0);
+ assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+ assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_TTL) > 0);
+ assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+ assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID) > 0);
+ assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+ assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID) > 0);
+ assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+ assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME) > 0);
+ assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+ assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID) > 0);
+ assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+ assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION) > 0);
+ assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+ assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_3, SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI) > 0);
+ assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+ assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_END) > 0);
+ assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) == 0);
+
+ sd_lldp_neighbor_unref(neighbors[0]);
+ free(neighbors);
+
+ assert_se(stop_lldp_rx(lldp_rx) == 0);
+}
+
+static void test_multiple_neighbors_sorted(sd_event *e) {
+
+ static const uint8_t frame1[] = {
+ /* Ethernet header */
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */
+ 0x88, 0xcc, /* Ethertype */
+ /* LLDP mandatory TLVs */
+ 0x02, 0x04, 0x01, '1', '/', '2', /* Chassis component: "1/2" */
+ 0x04, 0x04, 0x02, '2', '/', '3', /* Port component: "2/3" */
+ 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds */
+ 0x00, 0x00 /* End Of LLDPDU */
+ };
+ static const uint8_t frame2[] = {
+ /* Ethernet header */
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */
+ 0x88, 0xcc, /* Ethertype */
+ /* LLDP mandatory TLVs */
+ 0x02, 0x04, 0x01, '2', '/', '1', /* Chassis component: "2/1" */
+ 0x04, 0x04, 0x02, '1', '/', '3', /* Port component: "1/3" */
+ 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds */
+ 0x00, 0x00 /* End Of LLDPDU */
+ };
+ static const uint8_t frame3[] = {
+ /* Ethernet header */
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */
+ 0x88, 0xcc, /* Ethertype */
+ /* LLDP mandatory TLVs */
+ 0x02, 0x05, 0x01, '2', '/', '1', '0', /* Chassis component: "2/10" */
+ 0x04, 0x04, 0x02, '1', '/', '0', /* Port component: "1/0" */
+ 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds */
+ 0x00, 0x00 /* End Of LLDPDU */
+ };
+ static const uint8_t frame4[] = {
+ /* Ethernet header */
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */
+ 0x88, 0xcc, /* Ethertype */
+ /* LLDP mandatory TLVs */
+ 0x02, 0x05, 0x01, '2', '/', '1', '9', /* Chassis component: "2/19" */
+ 0x04, 0x04, 0x02, '1', '/', '0', /* Port component: "1/0" */
+ 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds */
+ 0x00, 0x00 /* End Of LLDPDU */
+ };
+ static const uint8_t frame5[] = {
+ /* Ethernet header */
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */
+ 0x88, 0xcc, /* Ethertype */
+ /* LLDP mandatory TLVs */
+ 0x02, 0x04, 0x01, '1', '/', '2', /* Chassis component: "1/2" */
+ 0x04, 0x05, 0x02, '2', '/', '1', '0', /* Port component: "2/10" */
+ 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds */
+ 0x00, 0x00 /* End Of LLDPDU */
+ };
+ static const uint8_t frame6[] = {
+ /* Ethernet header */
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */
+ 0x88, 0xcc, /* Ethertype */
+ /* LLDP mandatory TLVs */
+ 0x02, 0x04, 0x01, '1', '/', '2', /* Chassis component: "1/2" */
+ 0x04, 0x05, 0x02, '2', '/', '3', '9', /* Port component: "2/10" */
+ 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds */
+ 0x00, 0x00 /* End Of LLDPDU */
+ };
+ static const char* expected[] = {
+ /* ordered pairs of Chassis+Port */
+ "1/2", "2/10",
+ "1/2", "2/3",
+ "1/2", "2/39",
+ "2/1", "1/3",
+ "2/10", "1/0",
+ "2/19", "1/0",
+ };
+
+ sd_lldp_rx *lldp_rx;
+ sd_lldp_neighbor **neighbors;
+ int i;
+ uint8_t type;
+ const void *data;
+ size_t length, expected_length;
+ uint16_t ttl;
+
+ lldp_rx_handler_calls = 0;
+ assert_se(start_lldp_rx(&lldp_rx, e, lldp_rx_handler, NULL) == 0);
+
+ assert_se(write(test_fd[1], frame1, sizeof(frame1)) == sizeof(frame1));
+ sd_event_run(e, 0);
+ assert_se(write(test_fd[1], frame2, sizeof(frame2)) == sizeof(frame2));
+ sd_event_run(e, 0);
+ assert_se(write(test_fd[1], frame3, sizeof(frame3)) == sizeof(frame3));
+ sd_event_run(e, 0);
+ assert_se(write(test_fd[1], frame4, sizeof(frame4)) == sizeof(frame4));
+ sd_event_run(e, 0);
+ assert_se(write(test_fd[1], frame5, sizeof(frame5)) == sizeof(frame5));
+ sd_event_run(e, 0);
+ assert_se(write(test_fd[1], frame6, sizeof(frame6)) == sizeof(frame6));
+ sd_event_run(e, 0);
+ assert_se(lldp_rx_handler_calls == 6);
+
+ assert_se(sd_lldp_rx_get_neighbors(lldp_rx, &neighbors) == 6);
+
+ for (i = 0; i < 6; i++) {
+ assert_se(sd_lldp_neighbor_get_chassis_id(neighbors[i], &type, &data, &length) == 0);
+ assert_se(type == SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT);
+ expected_length = strlen(expected[2 * i]);
+ assert_se(length == expected_length);
+ assert_se(memcmp(data, expected[2 * i], expected_length) == 0);
+
+ assert_se(sd_lldp_neighbor_get_port_id(neighbors[i], &type, &data, &length) == 0);
+ assert_se(type == SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT);
+ expected_length = strlen(expected[2 * i + 1]);
+ assert_se(length == expected_length);
+ assert_se(memcmp(data, expected[2 * i + 1], expected_length) == 0);
+
+ assert_se(sd_lldp_neighbor_get_ttl(neighbors[i], &ttl) == 0);
+ assert_se(ttl == 120);
+ }
+
+ for (i = 0; i < 6; i++)
+ sd_lldp_neighbor_unref(neighbors[i]);
+ free(neighbors);
+
+ assert_se(stop_lldp_rx(lldp_rx) == 0);
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+
+ test_setup_logging(LOG_DEBUG);
+
+ /* LLDP reception tests */
+ assert_se(sd_event_new(&e) == 0);
+ test_receive_basic_packet(e);
+ test_receive_incomplete_packet(e);
+ test_receive_oui_packet(e);
+ test_multiple_neighbors_sorted(e);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/test-ndisc-ra.c b/src/libsystemd-network/test-ndisc-ra.c
new file mode 100644
index 0000000..23abe78
--- /dev/null
+++ b/src/libsystemd-network/test-ndisc-ra.c
@@ -0,0 +1,376 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2017 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/icmp6.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#include "sd-radv.h"
+
+#include "alloc-util.h"
+#include "hexdecoct.h"
+#include "icmp6-util-unix.h"
+#include "socket-util.h"
+#include "strv.h"
+#include "tests.h"
+
+static struct ether_addr mac_addr = {
+ .ether_addr_octet = { 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53 }
+};
+
+static uint8_t advertisement[] = {
+ /* ICMPv6 Router Advertisement, no checksum */
+ 0x86, 0x00, 0x00, 0x00, 0x40, 0xc0, 0x00, 0xb4,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Source Link Layer Address Option */
+ 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53,
+ /* Prefix Information Option */
+ 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4,
+ 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Prefix Information Option */
+ 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x0e, 0x10,
+ 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Prefix Information Option */
+ 0x03, 0x04, 0x30, 0xc0, 0x00, 0x00, 0x0e, 0x10,
+ 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Recursive DNS Server Option */
+ 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ /* DNS Search List Option */
+ 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
+ 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74,
+ 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static bool test_stopped;
+static struct {
+ struct in6_addr address;
+ unsigned char prefixlen;
+ uint32_t valid;
+ uint32_t preferred;
+ bool successful;
+} prefix[] = {
+ { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 64,
+ 500, 440, true },
+ { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 64,
+ /* indicate default valid and preferred lifetimes for the test code */
+ 0, 0, true },
+ { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 58,
+ 0, 0,
+ /* indicate that this prefix already exists */
+ false },
+ { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 120,
+ 0, 0,
+ /* indicate that this prefix already exists */
+ false },
+ { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 12,
+ 0, 0,
+ /* indicate that this prefix already exists */
+ false },
+ { { { { 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 48,
+ 0, 0, true },
+ { { { { 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 60,
+ 0, 0,
+ /* indicate that this prefix already exists */
+ false },
+};
+
+static const struct in6_addr test_rdnss = { { { 0x20, 0x01, 0x0d, 0xb8,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01 } } };
+static const char *test_dnssl[] = { "lab.intra",
+ NULL };
+
+TEST(radv_prefix) {
+ sd_radv_prefix *p;
+
+ assert_se(sd_radv_prefix_new(&p) >= 0);
+
+ assert_se(sd_radv_prefix_set_onlink(NULL, true) < 0);
+ assert_se(sd_radv_prefix_set_onlink(p, true) >= 0);
+ assert_se(sd_radv_prefix_set_onlink(p, false) >= 0);
+
+ assert_se(sd_radv_prefix_set_address_autoconfiguration(NULL, true) < 0);
+ assert_se(sd_radv_prefix_set_address_autoconfiguration(p, true) >= 0);
+ assert_se(sd_radv_prefix_set_address_autoconfiguration(p, false) >= 0);
+
+ assert_se(sd_radv_prefix_set_valid_lifetime(NULL, 1, 1) < 0);
+ assert_se(sd_radv_prefix_set_valid_lifetime(p, 0, 0) >= 0);
+ assert_se(sd_radv_prefix_set_valid_lifetime(p, 300 * USEC_PER_SEC, USEC_INFINITY) >= 0);
+ assert_se(sd_radv_prefix_set_valid_lifetime(p, 300 * USEC_PER_SEC, USEC_PER_YEAR) >= 0);
+
+ assert_se(sd_radv_prefix_set_preferred_lifetime(NULL, 1, 1) < 0);
+ assert_se(sd_radv_prefix_set_preferred_lifetime(p, 0, 0) >= 0);
+ assert_se(sd_radv_prefix_set_preferred_lifetime(p, 300 * USEC_PER_SEC, USEC_INFINITY) >= 0);
+ assert_se(sd_radv_prefix_set_preferred_lifetime(p, 300 * USEC_PER_SEC, USEC_PER_YEAR) >= 0);
+
+ assert_se(sd_radv_prefix_set_prefix(NULL, NULL, 0) < 0);
+ assert_se(sd_radv_prefix_set_prefix(p, NULL, 0) < 0);
+
+ assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 64) >= 0);
+ assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 0) < 0);
+ assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 1) < 0);
+ assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 2) < 0);
+ assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 3) >= 0);
+ assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 125) >= 0);
+ assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 128) >= 0);
+ assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 129) < 0);
+ assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 255) < 0);
+
+ assert_se(!sd_radv_prefix_unref(p));
+}
+
+TEST(radv_route_prefix) {
+ sd_radv_route_prefix *p;
+
+ assert_se(sd_radv_route_prefix_new(&p) >= 0);
+
+ assert_se(sd_radv_route_prefix_set_lifetime(NULL, 1, 1) < 0);
+ assert_se(sd_radv_route_prefix_set_lifetime(p, 0, 0) >= 0);
+ assert_se(sd_radv_route_prefix_set_lifetime(p, 300 * USEC_PER_SEC, USEC_INFINITY) >= 0);
+ assert_se(sd_radv_route_prefix_set_lifetime(p, 300 * USEC_PER_SEC, USEC_PER_YEAR) >= 0);
+
+ assert_se(sd_radv_route_prefix_set_prefix(NULL, NULL, 0) < 0);
+ assert_se(sd_radv_route_prefix_set_prefix(p, NULL, 0) < 0);
+
+ assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 64) >= 0);
+ assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 0) >= 0);
+ assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 1) >= 0);
+ assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 2) >= 0);
+ assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 3) >= 0);
+ assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 125) >= 0);
+ assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 128) >= 0);
+ assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 129) < 0);
+ assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 255) < 0);
+
+ assert_se(!sd_radv_route_prefix_unref(p));
+}
+
+TEST(radv_pref64_prefix) {
+ sd_radv_pref64_prefix *p;
+
+ assert_se(sd_radv_pref64_prefix_new(&p) >= 0);
+
+ assert_se(sd_radv_pref64_prefix_set_prefix(NULL, NULL, 0, 0) < 0);
+ assert_se(sd_radv_pref64_prefix_set_prefix(p, NULL, 0, 0) < 0);
+
+ assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 32, 300 * USEC_PER_SEC) >= 0);
+ assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 40, 300 * USEC_PER_SEC) >= 0);
+ assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 48, 300 * USEC_PER_SEC) >= 0);
+ assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 56, 300 * USEC_PER_SEC) >= 0);
+ assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 64, 300 * USEC_PER_SEC) >= 0);
+ assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 96, 300 * USEC_PER_SEC) >= 0);
+
+ assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 80, 300 * USEC_PER_SEC) < 0);
+ assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 80, USEC_PER_DAY) < 0);
+
+ assert_se(!sd_radv_pref64_prefix_unref(p));
+}
+
+TEST(radv) {
+ sd_radv *ra;
+
+ assert_se(sd_radv_new(&ra) >= 0);
+ assert_se(ra);
+
+ assert_se(sd_radv_set_ifindex(NULL, 0) < 0);
+ assert_se(sd_radv_set_ifindex(ra, 0) < 0);
+ assert_se(sd_radv_set_ifindex(ra, -1) < 0);
+ assert_se(sd_radv_set_ifindex(ra, -2) < 0);
+ assert_se(sd_radv_set_ifindex(ra, 42) >= 0);
+
+ assert_se(sd_radv_set_mac(NULL, NULL) < 0);
+ assert_se(sd_radv_set_mac(ra, NULL) >= 0);
+ assert_se(sd_radv_set_mac(ra, &mac_addr) >= 0);
+
+ assert_se(sd_radv_set_mtu(NULL, 0) < 0);
+ assert_se(sd_radv_set_mtu(ra, 0) < 0);
+ assert_se(sd_radv_set_mtu(ra, 1279) < 0);
+ assert_se(sd_radv_set_mtu(ra, 1280) >= 0);
+ assert_se(sd_radv_set_mtu(ra, ~0) >= 0);
+
+ assert_se(sd_radv_set_hop_limit(NULL, 0) < 0);
+ assert_se(sd_radv_set_hop_limit(ra, 0) >= 0);
+ assert_se(sd_radv_set_hop_limit(ra, ~0) >= 0);
+
+ assert_se(sd_radv_set_router_lifetime(NULL, 0) < 0);
+ assert_se(sd_radv_set_router_lifetime(ra, 0) >= 0);
+ assert_se(sd_radv_set_router_lifetime(ra, USEC_INFINITY) < 0);
+ assert_se(sd_radv_set_router_lifetime(ra, USEC_PER_YEAR) < 0);
+ assert_se(sd_radv_set_router_lifetime(ra, 300 * USEC_PER_SEC) >= 0);
+
+ assert_se(sd_radv_set_preference(NULL, 0) < 0);
+ assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_LOW) >= 0);
+ assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_MEDIUM) >= 0);
+ assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0);
+ assert_se(sd_radv_set_preference(ra, ~0) < 0);
+
+ assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0);
+ assert_se(sd_radv_set_router_lifetime(ra, 300 * USEC_PER_SEC) >= 0);
+ assert_se(sd_radv_set_router_lifetime(ra, 0) < 0);
+ assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_MEDIUM) >= 0);
+ assert_se(sd_radv_set_router_lifetime(ra, 0) >= 0);
+
+ assert_se(sd_radv_set_managed_information(NULL, true) < 0);
+ assert_se(sd_radv_set_managed_information(ra, true) >= 0);
+ assert_se(sd_radv_set_managed_information(ra, false) >= 0);
+
+ assert_se(sd_radv_set_other_information(NULL, true) < 0);
+ assert_se(sd_radv_set_other_information(ra, true) >= 0);
+ assert_se(sd_radv_set_other_information(ra, false) >= 0);
+
+ assert_se(sd_radv_set_retransmit(NULL, 10 * USEC_PER_MSEC) < 0);
+ assert_se(sd_radv_set_retransmit(ra, 10 * USEC_PER_MSEC) >= 0);
+ assert_se(sd_radv_set_retransmit(ra, 0) >= 0);
+ assert_se(sd_radv_set_retransmit(ra, usec_add(UINT32_MAX * USEC_PER_MSEC, USEC_PER_MSEC)) < 0);
+
+ assert_se(sd_radv_set_rdnss(NULL, 0, NULL, 0) < 0);
+ assert_se(sd_radv_set_rdnss(ra, 0, NULL, 0) >= 0);
+ assert_se(sd_radv_set_rdnss(ra, 0, NULL, 128) < 0);
+ assert_se(sd_radv_set_rdnss(ra, 600 * USEC_PER_SEC, &test_rdnss, 0) >= 0);
+ assert_se(sd_radv_set_rdnss(ra, 600 * USEC_PER_SEC, &test_rdnss, 1) >= 0);
+ assert_se(sd_radv_set_rdnss(ra, 0, &test_rdnss, 1) >= 0);
+ assert_se(sd_radv_set_rdnss(ra, 0, NULL, 0) >= 0);
+
+ assert_se(sd_radv_set_dnssl(ra, 0, NULL) >= 0);
+ assert_se(sd_radv_set_dnssl(ra, 600 * USEC_PER_SEC, NULL) >= 0);
+ assert_se(sd_radv_set_dnssl(ra, 0, (char **)test_dnssl) >= 0);
+ assert_se(sd_radv_set_dnssl(ra, 600 * USEC_PER_SEC, (char **)test_dnssl) >= 0);
+
+ assert_se(sd_radv_set_home_agent_information(NULL, true) < 0);
+ assert_se(sd_radv_set_home_agent_information(ra, true) >= 0);
+ assert_se(sd_radv_set_home_agent_information(ra, false) >= 0);
+
+ assert_se(sd_radv_set_home_agent_preference(NULL, 10) < 0);
+ assert_se(sd_radv_set_home_agent_preference(ra, 10) >= 0);
+ assert_se(sd_radv_set_home_agent_preference(ra, 0) >= 0);
+
+ assert_se(sd_radv_set_home_agent_lifetime(NULL, 300 * USEC_PER_SEC) < 0);
+ assert_se(sd_radv_set_home_agent_lifetime(ra, 300 * USEC_PER_SEC) >= 0);
+ assert_se(sd_radv_set_home_agent_lifetime(ra, 0) >= 0);
+ assert_se(sd_radv_set_home_agent_lifetime(ra, USEC_PER_DAY) < 0);
+
+ ra = sd_radv_unref(ra);
+ assert_se(!ra);
+}
+
+static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ sd_radv *ra = userdata;
+ unsigned char buf[168];
+ size_t i;
+
+ assert_se(read(test_fd[0], &buf, sizeof(buf)) == sizeof(buf));
+
+ /* router lifetime must be zero when test is stopped */
+ if (test_stopped) {
+ advertisement[6] = 0x00;
+ advertisement[7] = 0x00;
+ }
+
+ printf ("Received Router Advertisement with lifetime %i\n",
+ (advertisement[6] << 8) + advertisement[7]);
+
+ /* test only up to buf size, rest is not yet implemented */
+ for (i = 0; i < sizeof(buf); i++) {
+ if (!(i % 8))
+ printf("%3zu: ", i);
+
+ printf("0x%02x", buf[i]);
+
+ assert_se(buf[i] == advertisement[i]);
+
+ if ((i + 1) % 8)
+ printf(", ");
+ else
+ printf("\n");
+ }
+
+ if (test_stopped) {
+ sd_event *e;
+
+ e = sd_radv_get_event(ra);
+ sd_event_exit(e, 0);
+
+ return 0;
+ }
+
+ assert_se(sd_radv_stop(ra) >= 0);
+ test_stopped = true;
+
+ return 0;
+}
+
+TEST(ra) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *recv_router_advertisement = NULL;
+ _cleanup_(sd_radv_unrefp) sd_radv *ra = NULL;
+
+ assert_se(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) >= 0);
+
+ assert_se(sd_event_new(&e) >= 0);
+
+ assert_se(sd_radv_new(&ra) >= 0);
+ assert_se(ra);
+
+ assert_se(sd_radv_attach_event(ra, e, 0) >= 0);
+
+ assert_se(sd_radv_set_ifindex(ra, 42) >= 0);
+ assert_se(sd_radv_set_mac(ra, &mac_addr) >= 0);
+ assert_se(sd_radv_set_router_lifetime(ra, 180 * USEC_PER_SEC) >= 0);
+ assert_se(sd_radv_set_hop_limit(ra, 64) >= 0);
+ assert_se(sd_radv_set_managed_information(ra, true) >= 0);
+ assert_se(sd_radv_set_other_information(ra, true) >= 0);
+ assert_se(sd_radv_set_rdnss(ra, 60 * USEC_PER_SEC, &test_rdnss, 1) >= 0);
+ assert_se(sd_radv_set_dnssl(ra, 60 * USEC_PER_SEC, (char **)test_dnssl) >= 0);
+
+ for (unsigned i = 0; i < ELEMENTSOF(prefix); i++) {
+ sd_radv_prefix *p;
+
+ printf("Test prefix %u\n", i);
+ assert_se(sd_radv_prefix_new(&p) >= 0);
+
+ assert_se(sd_radv_prefix_set_prefix(p, &prefix[i].address,
+ prefix[i].prefixlen) >= 0);
+ if (prefix[i].valid > 0)
+ assert_se(sd_radv_prefix_set_valid_lifetime(p, prefix[i].valid * USEC_PER_SEC, USEC_INFINITY) >= 0);
+ if (prefix[i].preferred > 0)
+ assert_se(sd_radv_prefix_set_preferred_lifetime(p, prefix[i].preferred * USEC_PER_SEC, USEC_INFINITY) >= 0);
+
+ assert_se((sd_radv_add_prefix(ra, p) >= 0) == prefix[i].successful);
+ /* If the previous sd_radv_add_prefix() succeeds, then also the second call should also succeed. */
+ assert_se((sd_radv_add_prefix(ra, p) >= 0) == prefix[i].successful);
+
+ p = sd_radv_prefix_unref(p);
+ assert_se(!p);
+ }
+
+ assert_se(sd_event_add_io(e, &recv_router_advertisement, test_fd[0], EPOLLIN, radv_recv, ra) >= 0);
+ assert_se(sd_event_source_set_io_fd_own(recv_router_advertisement, true) >= 0);
+
+ assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
+ 2 * USEC_PER_SEC, 0,
+ NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0);
+
+ assert_se(sd_radv_start(ra) >= 0);
+
+ assert_se(sd_event_loop(e) >= 0);
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c
new file mode 100644
index 0000000..d94cc1c
--- /dev/null
+++ b/src/libsystemd-network/test-ndisc-rs.c
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/icmp6.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#include "sd-ndisc.h"
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "icmp6-util-unix.h"
+#include "socket-util.h"
+#include "strv.h"
+#include "ndisc-internal.h"
+#include "tests.h"
+
+static struct ether_addr mac_addr = {
+ .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
+};
+
+static bool verbose = false;
+static sd_ndisc *test_timeout_nd;
+
+static void router_dump(sd_ndisc_router *rt) {
+ struct in6_addr addr;
+ uint8_t hop_limit;
+ usec_t t, lifetime;
+ uint64_t flags;
+ uint32_t mtu;
+ unsigned preference;
+ int r;
+
+ assert_se(rt);
+
+ log_info("--");
+ assert_se(sd_ndisc_router_get_address(rt, &addr) >= 0);
+ log_info("Sender: %s", IN6_ADDR_TO_STRING(&addr));
+
+ assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
+ log_info("Timestamp: %s", FORMAT_TIMESTAMP(t));
+
+ assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_MONOTONIC, &t) >= 0);
+ log_info("Monotonic: %" PRIu64, t);
+
+ if (sd_ndisc_router_get_hop_limit(rt, &hop_limit) < 0)
+ log_info("No hop limit set");
+ else
+ log_info("Hop limit: %u", hop_limit);
+
+ assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0);
+ log_info("Flags: <%s|%s>",
+ flags & ND_RA_FLAG_OTHER ? "OTHER" : "",
+ flags & ND_RA_FLAG_MANAGED ? "MANAGED" : "");
+
+ assert_se(sd_ndisc_router_get_preference(rt, &preference) >= 0);
+ log_info("Preference: %s",
+ preference == SD_NDISC_PREFERENCE_LOW ? "low" :
+ preference == SD_NDISC_PREFERENCE_HIGH ? "high" : "medium");
+
+ assert_se(sd_ndisc_router_get_lifetime(rt, &lifetime) >= 0);
+ assert_se(sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
+ log_info("Lifetime: %s (%s)", FORMAT_TIMESPAN(lifetime, USEC_PER_SEC), FORMAT_TIMESTAMP(t));
+
+ if (sd_ndisc_router_get_mtu(rt, &mtu) < 0)
+ log_info("No MTU set");
+ else
+ log_info("MTU: %" PRIu32, mtu);
+
+ r = sd_ndisc_router_option_rewind(rt);
+ for (;;) {
+ uint8_t type;
+
+ assert_se(r >= 0);
+
+ if (r == 0)
+ break;
+
+ assert_se(sd_ndisc_router_option_get_type(rt, &type) >= 0);
+
+ log_info(">> Option %u", type);
+
+ switch (type) {
+
+ case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
+ case SD_NDISC_OPTION_TARGET_LL_ADDRESS: {
+ _cleanup_free_ char *c = NULL;
+ const void *p;
+ size_t n;
+
+ assert_se(sd_ndisc_router_option_get_raw(rt, &p, &n) >= 0);
+ assert_se(n > 2);
+ assert_se(c = hexmem((uint8_t*) p + 2, n - 2));
+
+ log_info("Address: %s", c);
+ break;
+ }
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION: {
+ unsigned prefix_len;
+ uint8_t pfl;
+ struct in6_addr a;
+
+ assert_se(sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime) >= 0);
+ assert_se(sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
+ log_info("Valid Lifetime: %s (%s)", FORMAT_TIMESPAN(lifetime, USEC_PER_SEC), FORMAT_TIMESTAMP(t));
+
+ assert_se(sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime) >= 0);
+ assert_se(sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
+ log_info("Preferred Lifetime: %s (%s)", FORMAT_TIMESPAN(lifetime, USEC_PER_SEC), FORMAT_TIMESTAMP(t));
+
+ assert_se(sd_ndisc_router_prefix_get_flags(rt, &pfl) >= 0);
+ log_info("Flags: <%s|%s>",
+ pfl & ND_OPT_PI_FLAG_ONLINK ? "ONLINK" : "",
+ pfl & ND_OPT_PI_FLAG_AUTO ? "AUTO" : "");
+
+ assert_se(sd_ndisc_router_prefix_get_prefixlen(rt, &prefix_len) >= 0);
+ log_info("Prefix Length: %u", prefix_len);
+
+ assert_se(sd_ndisc_router_prefix_get_address(rt, &a) >= 0);
+ log_info("Prefix: %s", IN6_ADDR_TO_STRING(&a));
+
+ break;
+ }
+
+ case SD_NDISC_OPTION_RDNSS: {
+ const struct in6_addr *a;
+ int n, i;
+
+ n = sd_ndisc_router_rdnss_get_addresses(rt, &a);
+ assert_se(n > 0);
+
+ for (i = 0; i < n; i++)
+ log_info("DNS: %s", IN6_ADDR_TO_STRING(a + i));
+
+ assert_se(sd_ndisc_router_rdnss_get_lifetime(rt, &lifetime) >= 0);
+ assert_se(sd_ndisc_router_rdnss_get_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
+ log_info("Lifetime: %s (%s)", FORMAT_TIMESPAN(lifetime, USEC_PER_SEC), FORMAT_TIMESTAMP(t));
+ break;
+ }
+
+ case SD_NDISC_OPTION_DNSSL: {
+ _cleanup_strv_free_ char **l = NULL;
+ int n, i;
+
+ n = sd_ndisc_router_dnssl_get_domains(rt, &l);
+ assert_se(n > 0);
+
+ for (i = 0; i < n; i++)
+ log_info("Domain: %s", l[i]);
+
+ assert_se(sd_ndisc_router_dnssl_get_lifetime(rt, &lifetime) >= 0);
+ assert_se(sd_ndisc_router_dnssl_get_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
+ log_info("Lifetime: %s (%s)", FORMAT_TIMESPAN(lifetime, USEC_PER_SEC), FORMAT_TIMESTAMP(t));
+ break;
+ }}
+
+ r = sd_ndisc_router_option_next(rt);
+ }
+}
+
+static int send_ra(uint8_t flags) {
+ uint8_t advertisement[] = {
+ 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4,
+ 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
+ 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74,
+ 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53,
+ };
+
+ advertisement[5] = flags;
+
+ assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) ==
+ sizeof(advertisement));
+
+ if (verbose)
+ printf(" sent RA with flag 0x%02x\n", flags);
+
+ return 0;
+}
+
+static void test_callback(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router *rt, void *userdata) {
+ sd_event *e = userdata;
+ static unsigned idx = 0;
+ uint64_t flags_array[] = {
+ 0,
+ 0,
+ 0,
+ ND_RA_FLAG_OTHER,
+ ND_RA_FLAG_MANAGED
+ };
+ uint64_t flags;
+
+ assert_se(nd);
+
+ if (event != SD_NDISC_EVENT_ROUTER)
+ return;
+
+ router_dump(rt);
+
+ assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0);
+ assert_se(flags == flags_array[idx]);
+ idx++;
+
+ if (verbose)
+ printf(" got event 0x%02" PRIx64 "\n", flags);
+
+ if (idx < ELEMENTSOF(flags_array)) {
+ send_ra(flags_array[idx]);
+ return;
+ }
+
+ sd_event_exit(e, 0);
+}
+
+TEST(rs) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
+
+ send_ra_function = send_ra;
+
+ assert_se(sd_event_new(&e) >= 0);
+
+ assert_se(sd_ndisc_new(&nd) >= 0);
+ assert_se(nd);
+
+ assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0);
+
+ assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0);
+ assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0);
+ assert_se(sd_ndisc_set_callback(nd, test_callback, e) >= 0);
+
+ assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
+ 30 * USEC_PER_SEC, 0,
+ NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0);
+
+ assert_se(sd_ndisc_stop(nd) >= 0);
+ assert_se(sd_ndisc_start(nd) >= 0);
+ assert_se(sd_ndisc_start(nd) >= 0);
+ assert_se(sd_ndisc_stop(nd) >= 0);
+ test_fd[1] = safe_close(test_fd[1]);
+
+ assert_se(sd_ndisc_start(nd) >= 0);
+
+ assert_se(sd_event_loop(e) >= 0);
+
+ test_fd[1] = safe_close(test_fd[1]);
+}
+
+static int test_timeout_value(uint8_t flags) {
+ static int count = 0;
+ static usec_t last = 0;
+ sd_ndisc *nd = test_timeout_nd;
+ usec_t min, max;
+
+ assert_se(nd);
+ assert_se(nd->event);
+
+ if (++count >= 20)
+ sd_event_exit(nd->event, 0);
+
+ if (last == 0) {
+ /* initial RT = IRT + RAND*IRT */
+ min = NDISC_ROUTER_SOLICITATION_INTERVAL -
+ NDISC_ROUTER_SOLICITATION_INTERVAL / 10;
+ max = NDISC_ROUTER_SOLICITATION_INTERVAL +
+ NDISC_ROUTER_SOLICITATION_INTERVAL / 10;
+ } else {
+ /* next RT = 2*RTprev + RAND*RTprev */
+ min = 2 * last - last / 10;
+ max = 2 * last + last / 10;
+ }
+
+ /* final RT > MRT */
+ if (last * 2 > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL) {
+ min = NDISC_MAX_ROUTER_SOLICITATION_INTERVAL -
+ NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 10;
+ max = NDISC_MAX_ROUTER_SOLICITATION_INTERVAL +
+ NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 10;
+ }
+
+ log_info("backoff timeout interval %2d %s%s <= %s <= %s",
+ count,
+ last * 2 > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL ? "(max) ": "",
+ FORMAT_TIMESPAN(min, USEC_PER_MSEC),
+ FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_MSEC),
+ FORMAT_TIMESPAN(max, USEC_PER_MSEC));
+
+ assert_se(min <= nd->retransmit_time);
+ assert_se(max >= nd->retransmit_time);
+
+ last = nd->retransmit_time;
+
+ assert_se(sd_event_source_set_time(nd->timeout_event_source, 0) >= 0);
+
+ return 0;
+}
+
+TEST(timeout) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
+
+ send_ra_function = test_timeout_value;
+
+ assert_se(sd_event_new(&e) >= 0);
+
+ assert_se(sd_ndisc_new(&nd) >= 0);
+ assert_se(nd);
+
+ test_timeout_nd = nd;
+
+ assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0);
+
+ assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0);
+ assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0);
+
+ assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
+ 30 * USEC_PER_SEC, 0,
+ NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0);
+
+ assert_se(sd_ndisc_start(nd) >= 0);
+
+ assert_se(sd_event_loop(e) >= 0);
+
+ test_fd[1] = safe_close(test_fd[1]);
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
diff --git a/src/libsystemd-network/test-sd-dhcp-lease.c b/src/libsystemd-network/test-sd-dhcp-lease.c
new file mode 100644
index 0000000..910b622
--- /dev/null
+++ b/src/libsystemd-network/test-sd-dhcp-lease.c
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "dhcp-lease-internal.h"
+#include "macro.h"
+#include "string-util.h"
+#include "strv.h"
+#include "tests.h"
+
+/* According to RFC1035 section 4.1.4, a domain name in a message can be either:
+ * - a sequence of labels ending in a zero octet
+ * - a pointer
+ * - a sequence of labels ending with a pointer
+ */
+TEST(dhcp_lease_parse_search_domains_basic) {
+ int r;
+ _cleanup_strv_free_ char **domains = NULL;
+ static const uint8_t optionbuf[] = {
+ 0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00,
+ 0x04, 'A', 'B', 'C', 'D', 0x03, 'E', 'F', 'G', 0x00,
+ };
+
+ r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains);
+ assert_se(r == 2);
+ assert_se(streq(domains[0], "FOO.BAR"));
+ assert_se(streq(domains[1], "ABCD.EFG"));
+}
+
+TEST(dhcp_lease_parse_search_domains_ptr) {
+ int r;
+ _cleanup_strv_free_ char **domains = NULL;
+ static const uint8_t optionbuf[] = {
+ 0x03, 'F', 'O', 'O', 0x00, 0xC0, 0x00,
+ };
+
+ r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains);
+ assert_se(r == 2);
+ assert_se(streq(domains[0], "FOO"));
+ assert_se(streq(domains[1], "FOO"));
+}
+
+TEST(dhcp_lease_parse_search_domains_labels_and_ptr) {
+ int r;
+ _cleanup_strv_free_ char **domains = NULL;
+ static const uint8_t optionbuf[] = {
+ 0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00,
+ 0x03, 'A', 'B', 'C', 0xC0, 0x04,
+ };
+
+ r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains);
+ assert_se(r == 2);
+ assert_se(streq(domains[0], "FOO.BAR"));
+ assert_se(streq(domains[1], "ABC.BAR"));
+}
+
+/* Tests for exceptions. */
+
+TEST(dhcp_lease_parse_search_domains_no_data) {
+ _cleanup_strv_free_ char **domains = NULL;
+ static const uint8_t optionbuf[3] = {0, 0, 0};
+
+ assert_se(dhcp_lease_parse_search_domains(NULL, 0, &domains) == -EBADMSG);
+ assert_se(dhcp_lease_parse_search_domains(optionbuf, 0, &domains) == -EBADMSG);
+}
+
+TEST(dhcp_lease_parse_search_domains_loops) {
+ _cleanup_strv_free_ char **domains = NULL;
+ static const uint8_t optionbuf[] = {
+ 0x03, 'F', 'O', 'O', 0x00, 0x03, 'B', 'A', 'R', 0xC0, 0x06,
+ };
+
+ assert_se(dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains) == -EBADMSG);
+}
+
+TEST(dhcp_lease_parse_search_domains_wrong_len) {
+ _cleanup_strv_free_ char **domains = NULL;
+ static const uint8_t optionbuf[] = {
+ 0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00,
+ 0x04, 'A', 'B', 'C', 'D', 0x03, 'E', 'F', 'G', 0x00,
+ };
+
+ assert_se(dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf) - 5, &domains) == -EBADMSG);
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);