summaryrefslogtreecommitdiffstats
path: root/src/libsystemd-network
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/libsystemd-network/arp-util.c138
-rw-r--r--src/libsystemd-network/arp-util.h18
-rw-r--r--src/libsystemd-network/dhcp-client-internal.h4
-rw-r--r--src/libsystemd-network/dhcp-identifier.c215
-rw-r--r--src/libsystemd-network/dhcp-identifier.h62
-rw-r--r--src/libsystemd-network/dhcp-internal.h69
-rw-r--r--src/libsystemd-network/dhcp-lease-internal.h84
-rw-r--r--src/libsystemd-network/dhcp-network.c244
-rw-r--r--src/libsystemd-network/dhcp-option.c358
-rw-r--r--src/libsystemd-network/dhcp-packet.c171
-rw-r--r--src/libsystemd-network/dhcp-protocol.h99
-rw-r--r--src/libsystemd-network/dhcp-server-internal.h98
-rw-r--r--src/libsystemd-network/dhcp6-internal.h123
-rw-r--r--src/libsystemd-network/dhcp6-lease-internal.h63
-rw-r--r--src/libsystemd-network/dhcp6-network.c74
-rw-r--r--src/libsystemd-network/dhcp6-option.c799
-rw-r--r--src/libsystemd-network/dhcp6-protocol.h120
-rw-r--r--src/libsystemd-network/icmp6-util.c210
-rw-r--r--src/libsystemd-network/icmp6-util.h24
-rw-r--r--src/libsystemd-network/lldp-internal.h39
-rw-r--r--src/libsystemd-network/lldp-neighbor.c792
-rw-r--r--src/libsystemd-network/lldp-neighbor.h92
-rw-r--r--src/libsystemd-network/lldp-network.c78
-rw-r--r--src/libsystemd-network/lldp-network.h6
-rw-r--r--src/libsystemd-network/meson.build51
-rw-r--r--src/libsystemd-network/ndisc-internal.h44
-rw-r--r--src/libsystemd-network/ndisc-router.c750
-rw-r--r--src/libsystemd-network/ndisc-router.h48
-rw-r--r--src/libsystemd-network/network-internal.c886
-rw-r--r--src/libsystemd-network/network-internal.h73
-rw-r--r--src/libsystemd-network/radv-internal.h129
-rw-r--r--src/libsystemd-network/sd-dhcp-client.c2267
-rw-r--r--src/libsystemd-network/sd-dhcp-lease.c1399
-rw-r--r--src/libsystemd-network/sd-dhcp-server.c1222
-rw-r--r--src/libsystemd-network/sd-dhcp6-client.c1848
-rw-r--r--src/libsystemd-network/sd-dhcp6-lease.c433
-rw-r--r--src/libsystemd-network/sd-ipv4acd.c501
-rw-r--r--src/libsystemd-network/sd-ipv4ll.c324
-rw-r--r--src/libsystemd-network/sd-lldp.c498
-rw-r--r--src/libsystemd-network/sd-ndisc.c389
-rw-r--r--src/libsystemd-network/sd-radv.c953
-rw-r--r--src/libsystemd-network/test-acd.c96
-rw-r--r--src/libsystemd-network/test-dhcp-client.c570
-rw-r--r--src/libsystemd-network/test-dhcp-option.c370
-rw-r--r--src/libsystemd-network/test-dhcp-server.c245
-rw-r--r--src/libsystemd-network/test-dhcp6-client.c1004
-rw-r--r--src/libsystemd-network/test-ipv4ll-manual.c110
-rw-r--r--src/libsystemd-network/test-ipv4ll.c205
-rw-r--r--src/libsystemd-network/test-lldp.c378
-rw-r--r--src/libsystemd-network/test-ndisc-ra.c370
-rw-r--r--src/libsystemd-network/test-ndisc-rs.c418
-rw-r--r--src/libsystemd-network/test-sd-dhcp-lease.c93
52 files changed, 19654 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..327fb2f
--- /dev/null
+++ b/src/libsystemd-network/arp-util.c
@@ -0,0 +1,138 @@
+/* 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 "fd-util.h"
+#include "unaligned.h"
+#include "util.h"
+
+int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) {
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */
+ BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)), /* A <- hardware address length */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0), /* length == sizeof(ether_addr)? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)), /* A <- protocol address length */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0), /* length == sizeof(in_addr) ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), /* protocol == request ? */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0), /* protocol == reply ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ /* Sender Hardware Address must be different from our own */
+ BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be32(&eth_mac->ether_addr_octet[0])),/* A <- 4 bytes of client's MAC */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)), /* A <- 4 bytes of SHA */
+ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6), /* A == 0 ? */
+ BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be16(&eth_mac->ether_addr_octet[4])),/* A <- remainder of client's MAC */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4), /* A <- remainder of SHA */
+ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ /* Sender Protocol Address or Target Protocol Address must be equal to the one we care about */
+ BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */
+ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
+ BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */
+ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ };
+ struct sock_fprog fprog = {
+ .len = ELEMENTSOF(filter),
+ .filter = (struct sock_filter*) filter
+ };
+ union sockaddr_union link = {
+ .ll.sll_family = AF_PACKET,
+ .ll.sll_protocol = htobe16(ETH_P_ARP),
+ .ll.sll_ifindex = ifindex,
+ .ll.sll_halen = ETH_ALEN,
+ .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+ };
+ _cleanup_close_ int s = -1;
+ int r;
+
+ assert(ifindex > 0);
+
+ s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (s < 0)
+ return -errno;
+
+ r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+ if (r < 0)
+ return -errno;
+
+ r = bind(s, &link.sa, sizeof(link.ll));
+ if (r < 0)
+ return -errno;
+
+ return TAKE_FD(s);
+}
+
+static int arp_send_packet(int fd, int ifindex,
+ be32_t pa, const struct ether_addr *ha,
+ bool announce) {
+ union sockaddr_union link = {
+ .ll.sll_family = AF_PACKET,
+ .ll.sll_protocol = htobe16(ETH_P_ARP),
+ .ll.sll_ifindex = ifindex,
+ .ll.sll_halen = ETH_ALEN,
+ .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+ };
+ struct ether_arp arp = {
+ .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */
+ .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */
+ .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */
+ .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */
+ .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */
+ };
+ int r;
+
+ assert(fd >= 0);
+ assert(pa != 0);
+ assert(ha);
+
+ memcpy(&arp.arp_sha, ha, ETH_ALEN);
+ memcpy(&arp.arp_tpa, &pa, sizeof(pa));
+
+ if (announce)
+ memcpy(&arp.arp_spa, &pa, sizeof(pa));
+
+ r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll));
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
+
+int arp_send_probe(int fd, int ifindex,
+ be32_t pa, const struct ether_addr *ha) {
+ return arp_send_packet(fd, ifindex, pa, ha, false);
+}
+
+int arp_send_announcement(int fd, int ifindex,
+ be32_t pa, const struct ether_addr *ha) {
+ return arp_send_packet(fd, ifindex, pa, ha, true);
+}
diff --git a/src/libsystemd-network/arp-util.h b/src/libsystemd-network/arp-util.h
new file mode 100644
index 0000000..2dac8cf
--- /dev/null
+++ b/src/libsystemd-network/arp-util.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014 Axis Communications AB. All rights reserved.
+***/
+
+#include <net/ethernet.h>
+
+#include "socket-util.h"
+#include "sparse-endian.h"
+
+int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac);
+
+int arp_send_probe(int fd, int ifindex,
+ be32_t pa, const struct ether_addr *ha);
+int arp_send_announcement(int fd, int ifindex,
+ be32_t pa, const struct ether_addr *ha);
diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h
new file mode 100644
index 0000000..a6f3752
--- /dev/null
+++ b/src/libsystemd-network/dhcp-client-internal.h
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+extern const struct hash_ops dhcp_option_hash_ops;
diff --git a/src/libsystemd-network/dhcp-identifier.c b/src/libsystemd-network/dhcp-identifier.c
new file mode 100644
index 0000000..ea9c77a
--- /dev/null
+++ b/src/libsystemd-network/dhcp-identifier.c
@@ -0,0 +1,215 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/if_infiniband.h>
+#include <net/if_arp.h>
+
+#include "sd-device.h"
+#include "sd-id128.h"
+
+#include "dhcp-identifier.h"
+#include "dhcp6-protocol.h"
+#include "network-internal.h"
+#include "siphash24.h"
+#include "sparse-endian.h"
+#include "stdio-util.h"
+#include "udev-util.h"
+#include "virt.h"
+
+#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
+#define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03)
+#define USEC_2000 ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */
+
+int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict) {
+ struct duid d;
+
+ assert_cc(sizeof(d.raw) >= MAX_DUID_LEN);
+ if (duid_len > MAX_DUID_LEN)
+ return -EINVAL;
+
+ if (!strict)
+ /* Strict validation is not requested. We only ensure that the
+ * DUID is not too long. */
+ return 0;
+
+ switch (duid_type) {
+ case DUID_TYPE_LLT:
+ if (duid_len <= sizeof(d.llt))
+ return -EINVAL;
+ break;
+ case DUID_TYPE_EN:
+ if (duid_len != sizeof(d.en))
+ return -EINVAL;
+ break;
+ case DUID_TYPE_LL:
+ if (duid_len <= sizeof(d.ll))
+ return -EINVAL;
+ break;
+ case DUID_TYPE_UUID:
+ if (duid_len != sizeof(d.uuid))
+ return -EINVAL;
+ break;
+ default:
+ /* accept unknown type in order to be forward compatible */
+ break;
+ }
+ return 0;
+}
+
+int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) {
+ uint16_t time_from_2000y;
+
+ assert(duid);
+ assert(len);
+ assert(addr);
+
+ if (arp_type == ARPHRD_ETHER)
+ assert_return(addr_len == ETH_ALEN, -EINVAL);
+ else if (arp_type == ARPHRD_INFINIBAND)
+ assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
+ else
+ return -EINVAL;
+
+ if (t < USEC_2000)
+ time_from_2000y = 0;
+ else
+ time_from_2000y = (uint16_t) (((t - USEC_2000) / USEC_PER_SEC) & 0xffffffff);
+
+ unaligned_write_be16(&duid->type, DUID_TYPE_LLT);
+ unaligned_write_be16(&duid->llt.htype, arp_type);
+ unaligned_write_be32(&duid->llt.time, time_from_2000y);
+ memcpy(duid->llt.haddr, addr, addr_len);
+
+ *len = sizeof(duid->type) + sizeof(duid->llt.htype) + sizeof(duid->llt.time) + addr_len;
+
+ return 0;
+}
+
+int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) {
+ assert(duid);
+ assert(len);
+ assert(addr);
+
+ if (arp_type == ARPHRD_ETHER)
+ assert_return(addr_len == ETH_ALEN, -EINVAL);
+ else if (arp_type == ARPHRD_INFINIBAND)
+ assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
+ else
+ return -EINVAL;
+
+ unaligned_write_be16(&duid->type, DUID_TYPE_LL);
+ unaligned_write_be16(&duid->ll.htype, arp_type);
+ memcpy(duid->ll.haddr, addr, addr_len);
+
+ *len = sizeof(duid->type) + sizeof(duid->ll.htype) + addr_len;
+
+ return 0;
+}
+
+int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) {
+ sd_id128_t machine_id;
+ uint64_t hash;
+ int r;
+
+ assert(duid);
+ assert(len);
+
+ r = sd_id128_get_machine(&machine_id);
+ if (r < 0) {
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10);
+#else
+ return r;
+#endif
+ }
+
+ unaligned_write_be16(&duid->type, DUID_TYPE_EN);
+ unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN);
+
+ *len = sizeof(duid->type) + sizeof(duid->en);
+
+ /* a bit of snake-oil perhaps, but no need to expose the machine-id
+ * directly; duid->en.id might not be aligned, so we need to copy */
+ hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes));
+ memcpy(duid->en.id, &hash, sizeof(duid->en.id));
+
+ return 0;
+}
+
+int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len) {
+ sd_id128_t machine_id;
+ int r;
+
+ assert(duid);
+ assert(len);
+
+ r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id);
+ if (r < 0)
+ return r;
+
+ unaligned_write_be16(&duid->type, DUID_TYPE_UUID);
+ memcpy(&duid->raw.data, &machine_id, sizeof(machine_id));
+
+ *len = sizeof(duid->type) + sizeof(machine_id);
+
+ return 0;
+}
+
+int dhcp_identifier_set_iaid(
+ int ifindex,
+ const uint8_t *mac,
+ size_t mac_len,
+ bool legacy_unstable_byteorder,
+ void *_id) {
+ /* name is a pointer to memory in the sd_device struct, so must
+ * have the same scope */
+ _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ const char *name = NULL;
+ uint64_t id;
+ uint32_t id32;
+
+ if (detect_container() <= 0) {
+ /* not in a container, udev will be around */
+ char ifindex_str[1 + DECIMAL_STR_MAX(int)];
+ int r;
+
+ xsprintf(ifindex_str, "n%d", ifindex);
+ if (sd_device_new_from_device_id(&device, ifindex_str) >= 0) {
+ r = sd_device_get_is_initialized(device);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ /* not yet ready */
+ return -EBUSY;
+
+ r = device_is_renaming(device);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ /* device is under renaming */
+ return -EBUSY;
+
+ name = net_get_name_persistent(device);
+ }
+ }
+
+ if (name)
+ id = siphash24(name, strlen(name), HASH_KEY.bytes);
+ else
+ /* fall back to MAC address if no predictable name available */
+ id = siphash24(mac, mac_len, HASH_KEY.bytes);
+
+ id32 = (id & 0xffffffff) ^ (id >> 32);
+
+ if (legacy_unstable_byteorder)
+ /* for historical reasons (a bug), the bits were swapped and thus
+ * the result was endianness dependent. Preserve that behavior. */
+ id32 = __bswap_32(id32);
+ else
+ /* the fixed behavior returns a stable byte order. Since LE is expected
+ * to be more common, swap the bytes on LE to give the same as legacy
+ * behavior. */
+ id32 = be32toh(id32);
+
+ unaligned_write_ne32(_id, id32);
+ return 0;
+}
diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-identifier.h
new file mode 100644
index 0000000..e9f2ea7
--- /dev/null
+++ b/src/libsystemd-network/dhcp-identifier.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-id128.h"
+
+#include "macro.h"
+#include "sparse-endian.h"
+#include "time-util.h"
+#include "unaligned.h"
+
+#define SYSTEMD_PEN 43793
+
+typedef enum DUIDType {
+ DUID_TYPE_LLT = 1,
+ DUID_TYPE_EN = 2,
+ DUID_TYPE_LL = 3,
+ DUID_TYPE_UUID = 4,
+ _DUID_TYPE_MAX,
+ _DUID_TYPE_INVALID = -1,
+} DUIDType;
+
+/* RFC 3315 section 9.1:
+ * A DUID can be no more than 128 octets long (not including the type code).
+ */
+#define MAX_DUID_LEN 128
+
+/* https://tools.ietf.org/html/rfc3315#section-9.1 */
+struct duid {
+ be16_t type;
+ union {
+ struct {
+ /* DUID_TYPE_LLT */
+ be16_t htype;
+ be32_t time;
+ uint8_t haddr[0];
+ } _packed_ llt;
+ struct {
+ /* DUID_TYPE_EN */
+ be32_t pen;
+ uint8_t id[8];
+ } _packed_ en;
+ struct {
+ /* DUID_TYPE_LL */
+ be16_t htype;
+ uint8_t haddr[0];
+ } _packed_ ll;
+ struct {
+ /* DUID_TYPE_UUID */
+ sd_id128_t uuid;
+ } _packed_ uuid;
+ struct {
+ uint8_t data[MAX_DUID_LEN];
+ } _packed_ raw;
+ };
+} _packed_;
+
+int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict);
+int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
+int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
+int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len);
+int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len);
+int dhcp_identifier_set_iaid(int ifindex, const uint8_t *mac, size_t mac_len, bool legacy_unstable_byteorder, void *_id);
diff --git a/src/libsystemd-network/dhcp-internal.h b/src/libsystemd-network/dhcp-internal.h
new file mode 100644
index 0000000..40e6b1f
--- /dev/null
+++ b/src/libsystemd-network/dhcp-internal.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
+#include <stdint.h>
+
+#include "sd-dhcp-client.h"
+
+#include "dhcp-protocol.h"
+#include "socket-util.h"
+
+typedef struct sd_dhcp_option {
+ unsigned n_ref;
+
+ uint8_t option;
+ void *data;
+ size_t length;
+} sd_dhcp_option;
+
+typedef struct DHCPServerData {
+ struct in_addr *addr;
+ size_t size;
+} DHCPServerData;
+
+extern const struct hash_ops dhcp_option_hash_ops;
+
+int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid,
+ const uint8_t *mac_addr, size_t mac_addr_len,
+ const uint8_t *bcast_addr, size_t bcast_addr_len,
+ uint16_t arp_type, uint16_t port);
+int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type);
+int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link,
+ const void *packet, size_t len);
+int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port,
+ const void *packet, size_t len);
+
+int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, uint8_t overload,
+ uint8_t code, size_t optlen, const void *optval);
+
+typedef int (*dhcp_option_callback_t)(uint8_t code, uint8_t len,
+ const void *option, void *userdata);
+
+int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **error_message);
+
+int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid,
+ uint8_t type, uint16_t arp_type, size_t optlen,
+ size_t *optoffset);
+
+uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len);
+
+void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
+ uint16_t source, be32_t destination_addr,
+ uint16_t destination, uint16_t len, int ip_service_type);
+
+int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port);
+
+/* If we are invoking callbacks of a dhcp-client, ensure unreffing the
+ * client from the callback doesn't destroy the object we are working
+ * on */
+#define DHCP_CLIENT_DONT_DESTROY(client) \
+ _cleanup_(sd_dhcp_client_unrefp) _unused_ sd_dhcp_client *_dont_destroy_##client = sd_dhcp_client_ref(client)
+
+#define log_dhcp_client_errno(client, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "DHCP CLIENT (0x%x): " fmt, client->xid, ##__VA_ARGS__)
+#define log_dhcp_client(client, fmt, ...) log_dhcp_client_errno(client, 0, fmt, ##__VA_ARGS__)
diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h
new file mode 100644
index 0000000..49392d1
--- /dev/null
+++ b/src/libsystemd-network/dhcp-lease-internal.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include "sd-dhcp-client.h"
+
+#include "dhcp-internal.h"
+#include "dhcp-protocol.h"
+#include "list.h"
+#include "util.h"
+
+struct sd_dhcp_route {
+ struct in_addr dst_addr;
+ struct in_addr gw_addr;
+ unsigned char dst_prefixlen;
+
+ uint8_t option;
+};
+
+struct sd_dhcp_raw_option {
+ LIST_FIELDS(struct sd_dhcp_raw_option, options);
+
+ uint8_t tag;
+ uint8_t length;
+ void *data;
+};
+
+struct sd_dhcp_lease {
+ unsigned n_ref;
+
+ /* each 0 if unset */
+ uint32_t t1;
+ uint32_t t2;
+ uint32_t lifetime;
+
+ /* each 0 if unset */
+ be32_t address;
+ be32_t server_address;
+ be32_t next_server;
+
+ bool have_subnet_mask;
+ be32_t subnet_mask;
+
+ bool have_broadcast;
+ be32_t broadcast;
+
+ struct in_addr *router;
+ size_t router_size;
+
+ DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
+
+ struct sd_dhcp_route *static_route;
+ size_t static_route_size, static_route_allocated;
+
+ uint16_t mtu; /* 0 if unset */
+
+ char *domainname;
+ char **search_domains;
+ char *hostname;
+ char *root_path;
+
+ void *client_id;
+ size_t client_id_len;
+
+ void *vendor_specific;
+ size_t vendor_specific_len;
+
+ char *timezone;
+
+ LIST_HEAD(struct sd_dhcp_raw_option, private_options);
+};
+
+int dhcp_lease_new(sd_dhcp_lease **ret);
+
+int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata);
+int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains);
+int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len);
+
+int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease);
+
+int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len);
diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c
new file mode 100644
index 0000000..656482b
--- /dev/null
+++ b/src/libsystemd-network/dhcp-network.c
@@ -0,0 +1,244 @@
+/* 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-internal.h"
+#include "fd-util.h"
+#include "socket-util.h"
+#include "unaligned.h"
+
+static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
+ uint32_t xid,
+ const uint8_t *bcast_addr,
+ size_t bcast_addr_len,
+ const struct ether_addr *eth_mac,
+ uint16_t arp_type, uint8_t dhcp_hlen,
+ uint16_t port) {
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */
+ BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(DHCPPacket), 1, 0), /* packet >= DHCPPacket ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.protocol)), /* A <- IP protocol */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags */
+ BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x20), /* A <- A & 0x20 (More Fragments bit) */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags + Fragment offset */
+ BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x1fff), /* A <- A & 0x1fff (Fragment offset) */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, udp.dest)), /* A <- UDP destination port */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, port, 1, 0), /* UDP destination port == DHCP client port ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.op)), /* A <- DHCP op */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.htype)), /* A <- DHCP header type */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arp_type, 1, 0), /* header type == arp_type ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.xid)), /* A <- client identifier */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, xid, 1, 0), /* client identifier == xid ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)), /* A <- MAC address length */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, dhcp_hlen, 1, 0), /* address length == dhcp_hlen ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ /* We only support MAC address length to be either 0 or 6 (ETH_ALEN). Optionally
+ * compare chaddr for ETH_ALEN bytes. */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 12), /* A (the MAC address length) == ETH_ALEN ? */
+ BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be32(&eth_mac->ether_addr_octet[0])), /* A <- 4 bytes of client's MAC */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr)), /* A <- 4 bytes of MAC from dhcp.chaddr */
+ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be16(&eth_mac->ether_addr_octet[4])), /* A <- remainder of client's MAC */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr) + 4), /* A <- remainder of MAC from dhcp.chaddr */
+ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.magic)), /* A <- DHCP magic cookie */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0), /* cookie == DHCP magic cookie ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
+ };
+ struct sock_fprog fprog = {
+ .len = ELEMENTSOF(filter),
+ .filter = filter
+ };
+ _cleanup_close_ int s = -1;
+ int r;
+
+ assert(ifindex > 0);
+ assert(link);
+
+ s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (s < 0)
+ return -errno;
+
+ r = setsockopt_int(s, SOL_PACKET, PACKET_AUXDATA, true);
+ if (r < 0)
+ return r;
+
+ r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+ if (r < 0)
+ return -errno;
+
+ link->ll = (struct sockaddr_ll) {
+ .sll_family = AF_PACKET,
+ .sll_protocol = htobe16(ETH_P_IP),
+ .sll_ifindex = ifindex,
+ .sll_hatype = htobe16(arp_type),
+ .sll_halen = bcast_addr_len,
+ };
+ memcpy(link->ll.sll_addr, bcast_addr, bcast_addr_len); /* We may overflow link->ll. link->ll_buffer ensures we have enough space. */
+
+ r = bind(s, &link->sa, SOCKADDR_LL_LEN(link->ll));
+ if (r < 0)
+ return -errno;
+
+ return TAKE_FD(s);
+}
+
+int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid,
+ const uint8_t *mac_addr, size_t mac_addr_len,
+ const uint8_t *bcast_addr, size_t bcast_addr_len,
+ uint16_t arp_type, uint16_t port) {
+ static const uint8_t eth_bcast[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+ /* Default broadcast address for IPoIB */
+ static const uint8_t ib_bcast[] = {
+ 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff
+ };
+ struct ether_addr eth_mac = { { 0, 0, 0, 0, 0, 0 } };
+ const uint8_t *default_bcast_addr;
+ size_t expected_bcast_addr_len;
+ uint8_t dhcp_hlen = 0;
+
+ if (arp_type == ARPHRD_ETHER) {
+ assert_return(mac_addr_len == ETH_ALEN, -EINVAL);
+ memcpy(&eth_mac, mac_addr, ETH_ALEN);
+ dhcp_hlen = ETH_ALEN;
+
+ default_bcast_addr = eth_bcast;
+ expected_bcast_addr_len = ETH_ALEN;
+ } else if (arp_type == ARPHRD_INFINIBAND) {
+ default_bcast_addr = ib_bcast;
+ expected_bcast_addr_len = INFINIBAND_ALEN;
+ } else
+ return -EINVAL;
+
+ if (bcast_addr && bcast_addr_len > 0)
+ assert_return(bcast_addr_len == expected_bcast_addr_len, -EINVAL);
+ else {
+ bcast_addr = default_bcast_addr;
+ bcast_addr_len = expected_bcast_addr_len;
+ }
+
+ return _bind_raw_socket(ifindex, link, xid, bcast_addr, bcast_addr_len,
+ &eth_mac, arp_type, dhcp_hlen, port);
+}
+
+int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) {
+ union sockaddr_union src = {
+ .in.sin_family = AF_INET,
+ .in.sin_port = htobe16(port),
+ .in.sin_addr.s_addr = address,
+ };
+ _cleanup_close_ int s = -1;
+ int r;
+
+ s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (s < 0)
+ return -errno;
+
+ if (ip_service_type >= 0)
+ r = setsockopt_int(s, IPPROTO_IP, IP_TOS, ip_service_type);
+ else
+ r = setsockopt_int(s, IPPROTO_IP, IP_TOS, IPTOS_CLASS_CS6);
+
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
+ if (r < 0)
+ return r;
+
+ if (ifindex > 0) {
+ r = socket_bind_to_ifindex(s, ifindex);
+ if (r < 0)
+ return r;
+ }
+
+ if (address == INADDR_ANY) {
+ r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, SOL_SOCKET, SO_BROADCAST, true);
+ if (r < 0)
+ return r;
+
+ } else {
+ r = setsockopt_int(s, IPPROTO_IP, IP_FREEBIND, true);
+ if (r < 0)
+ return r;
+ }
+
+ r = bind(s, &src.sa, sizeof(src.in));
+ if (r < 0)
+ return -errno;
+
+ return TAKE_FD(s);
+}
+
+int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link,
+ const void *packet, size_t len) {
+ int r;
+
+ assert(link);
+ assert(packet);
+ assert(len);
+
+ r = sendto(s, packet, len, 0, &link->sa, SOCKADDR_LL_LEN(link->ll));
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
+
+int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port,
+ const void *packet, size_t len) {
+ union sockaddr_union dest = {
+ .in.sin_family = AF_INET,
+ .in.sin_port = htobe16(port),
+ .in.sin_addr.s_addr = address,
+ };
+ int r;
+
+ assert(s >= 0);
+ assert(packet);
+ assert(len);
+
+ r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in));
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c
new file mode 100644
index 0000000..7e3fe43
--- /dev/null
+++ b/src/libsystemd-network/dhcp-option.c
@@ -0,0 +1,358 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "alloc-util.h"
+#include "dhcp-internal.h"
+#include "dhcp-server-internal.h"
+#include "memory-util.h"
+#include "strv.h"
+#include "utf8.h"
+
+static int option_append(uint8_t options[], size_t size, size_t *offset,
+ uint8_t code, size_t optlen, const void *optval) {
+ assert(options);
+ assert(offset);
+
+ if (code != SD_DHCP_OPTION_END)
+ /* always make sure there is space for an END option */
+ size--;
+
+ switch (code) {
+
+ case SD_DHCP_OPTION_PAD:
+ case SD_DHCP_OPTION_END:
+ if (*offset + 1 > size)
+ return -ENOBUFS;
+
+ options[*offset] = code;
+ *offset += 1;
+ break;
+
+ case SD_DHCP_OPTION_USER_CLASS: {
+ size_t total = 0;
+ char **s;
+
+ if (strv_isempty((char **) optval))
+ return -EINVAL;
+
+ STRV_FOREACH(s, (char **) 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, (char **) optval) {
+ size_t len = strlen(*s);
+
+ options[*offset] = len;
+ memcpy(&options[*offset + 1], *s, len);
+ *offset += 1 + len;
+ }
+
+ break;
+ }
+ case SD_DHCP_OPTION_SIP_SERVER:
+ if (*offset + 3 + optlen > size)
+ return -ENOBUFS;
+
+ options[*offset] = code;
+ options[*offset + 1] = optlen + 1;
+ options[*offset + 2] = 1;
+
+ memcpy_safe(&options[*offset + 3], optval, optlen);
+ *offset += 3 + optlen;
+
+ break;
+ case SD_DHCP_OPTION_VENDOR_SPECIFIC: {
+ OrderedHashmap *s = (OrderedHashmap *) optval;
+ struct sd_dhcp_option *p;
+ size_t l = 0;
+
+ ORDERED_HASHMAP_FOREACH(p, s)
+ l += p->length + 2;
+
+ if (*offset + l + 2 > size)
+ return -ENOBUFS;
+
+ options[*offset] = code;
+ options[*offset + 1] = l;
+
+ *offset += 2;
+
+ ORDERED_HASHMAP_FOREACH(p, s) {
+ options[*offset] = p->option;
+ options[*offset + 1] = p->length;
+ memcpy(&options[*offset + 2], p->data, p->length);
+ *offset += 2 + p->length;
+ }
+
+ break;
+ }
+ default:
+ if (*offset + 2 + optlen > size)
+ return -ENOBUFS;
+
+ options[*offset] = code;
+ options[*offset + 1] = optlen;
+
+ memcpy_safe(&options[*offset + 2], optval, optlen);
+ *offset += 2 + optlen;
+
+ break;
+ }
+
+ return 0;
+}
+
+int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
+ uint8_t overload,
+ uint8_t code, size_t optlen, const void *optval) {
+ const bool use_file = overload & DHCP_OVERLOAD_FILE;
+ const bool use_sname = overload & DHCP_OVERLOAD_SNAME;
+ int r;
+
+ assert(message);
+ assert(offset);
+
+ /* If *offset is in range [0, size), we are writing to ->options,
+ * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file,
+ * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname))
+ * and use_sname, we are writing to ->sname.
+ */
+
+ if (*offset < size) {
+ /* still space in the options array */
+ r = option_append(message->options, size, offset, code, optlen, optval);
+ if (r >= 0)
+ return 0;
+ else if (r == -ENOBUFS && (use_file || use_sname)) {
+ /* did not fit, but we have more buffers to try
+ close the options array and move the offset to its end */
+ r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ *offset = size;
+ } else
+ return r;
+ }
+
+ if (use_file) {
+ size_t file_offset = *offset - size;
+
+ if (file_offset < sizeof(message->file)) {
+ /* still space in the 'file' array */
+ r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval);
+ if (r >= 0) {
+ *offset = size + file_offset;
+ return 0;
+ } else if (r == -ENOBUFS && use_sname) {
+ /* did not fit, but we have more buffers to try
+ close the file array and move the offset to its end */
+ r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ *offset = size + sizeof(message->file);
+ } else
+ return r;
+ }
+ }
+
+ if (use_sname) {
+ size_t sname_offset = *offset - size - use_file*sizeof(message->file);
+
+ if (sname_offset < sizeof(message->sname)) {
+ /* still space in the 'sname' array */
+ r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval);
+ if (r >= 0) {
+ *offset = size + use_file*sizeof(message->file) + sname_offset;
+ return 0;
+ } else
+ /* no space, or other error, give up */
+ return r;
+ }
+ }
+
+ return -ENOBUFS;
+}
+
+static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
+ uint8_t *message_type, char **error_message, dhcp_option_callback_t cb,
+ void *userdata) {
+ uint8_t code, len;
+ const uint8_t *option;
+ size_t offset = 0;
+
+ while (offset < buflen) {
+ code = options[offset ++];
+
+ switch (code) {
+ case SD_DHCP_OPTION_PAD:
+ continue;
+
+ case SD_DHCP_OPTION_END:
+ return 0;
+ }
+
+ if (buflen < offset + 1)
+ return -ENOBUFS;
+
+ len = options[offset ++];
+
+ if (buflen < offset + len)
+ return -EINVAL;
+
+ option = &options[offset];
+
+ switch (code) {
+ case SD_DHCP_OPTION_MESSAGE_TYPE:
+ if (len != 1)
+ return -EINVAL;
+
+ if (message_type)
+ *message_type = *option;
+
+ break;
+
+ case SD_DHCP_OPTION_ERROR_MESSAGE:
+ if (len == 0)
+ return -EINVAL;
+
+ if (error_message) {
+ _cleanup_free_ char *string = NULL;
+
+ /* Accept a trailing NUL byte */
+ if (memchr(option, 0, len - 1))
+ return -EINVAL;
+
+ string = memdup_suffix0((const char *) option, len);
+ if (!string)
+ return -ENOMEM;
+
+ if (!ascii_is_valid(string))
+ return -EINVAL;
+
+ free_and_replace(*error_message, string);
+ }
+
+ break;
+ case SD_DHCP_OPTION_OVERLOAD:
+ if (len != 1)
+ return -EINVAL;
+
+ if (overload)
+ *overload = *option;
+
+ break;
+
+ default:
+ if (cb)
+ cb(code, len, option, userdata);
+
+ break;
+ }
+
+ offset += len;
+ }
+
+ if (offset < buflen)
+ return -EINVAL;
+
+ return 0;
+}
+
+int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) {
+ _cleanup_free_ char *error_message = NULL;
+ uint8_t overload = 0;
+ uint8_t message_type = 0;
+ int r;
+
+ if (!message)
+ return -EINVAL;
+
+ if (len < sizeof(DHCPMessage))
+ return -EINVAL;
+
+ len -= sizeof(DHCPMessage);
+
+ r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
+ if (r < 0)
+ return r;
+
+ if (overload & DHCP_OVERLOAD_FILE) {
+ r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
+ if (r < 0)
+ return r;
+ }
+
+ if (overload & DHCP_OVERLOAD_SNAME) {
+ r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
+ if (r < 0)
+ return r;
+ }
+
+ if (message_type == 0)
+ return -ENOMSG;
+
+ if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE))
+ *_error_message = TAKE_PTR(error_message);
+
+ return message_type;
+}
+
+static sd_dhcp_option* dhcp_option_free(sd_dhcp_option *i) {
+ if (!i)
+ return NULL;
+
+ free(i->data);
+ return mfree(i);
+}
+
+int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret) {
+ assert_return(ret, -EINVAL);
+ assert_return(length == 0 || data, -EINVAL);
+
+ _cleanup_free_ void *q = memdup(data, length);
+ if (!q)
+ return -ENOMEM;
+
+ sd_dhcp_option *p = new(sd_dhcp_option, 1);
+ if (!p)
+ return -ENOMEM;
+
+ *p = (sd_dhcp_option) {
+ .n_ref = 1,
+ .option = option,
+ .length = length,
+ .data = TAKE_PTR(q),
+ };
+
+ *ret = TAKE_PTR(p);
+ return 0;
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option, sd_dhcp_option, dhcp_option_free);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ dhcp_option_hash_ops,
+ void,
+ trivial_hash_func,
+ trivial_compare_func,
+ sd_dhcp_option,
+ sd_dhcp_option_unref);
diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c
new file mode 100644
index 0000000..cace916
--- /dev/null
+++ b/src/libsystemd-network/dhcp-packet.c
@@ -0,0 +1,171 @@
+/* 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-internal.h"
+#include "dhcp-protocol.h"
+
+#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312
+
+int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid,
+ uint8_t type, uint16_t arp_type, size_t optlen,
+ size_t *optoffset) {
+ size_t offset = 0;
+ int r;
+
+ assert(IN_SET(op, BOOTREQUEST, BOOTREPLY));
+ assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND));
+
+ message->op = op;
+ message->htype = arp_type;
+ message->hlen = (arp_type == ARPHRD_ETHER) ? ETHER_ADDR_LEN : 0;
+ message->xid = htobe32(xid);
+ message->magic = htobe32(DHCP_MAGIC_COOKIE);
+
+ r = dhcp_option_append(message, optlen, &offset, 0,
+ SD_DHCP_OPTION_MESSAGE_TYPE, 1, &type);
+ if (r < 0)
+ return r;
+
+ *optoffset = offset;
+
+ return 0;
+}
+
+uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) {
+ uint64_t *buf_64 = (uint64_t*)buf;
+ uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t));
+ uint64_t sum = 0;
+
+ /* See RFC1071 */
+
+ while (buf_64 < end_64) {
+ sum += *buf_64;
+ if (sum < *buf_64)
+ /* wrap around in one's complement */
+ sum++;
+
+ buf_64++;
+ }
+
+ if (len % sizeof(uint64_t)) {
+ /* If the buffer is not aligned to 64-bit, we need
+ to zero-pad the last few bytes and add them in */
+ uint64_t buf_tail = 0;
+
+ memcpy(&buf_tail, buf_64, len % sizeof(uint64_t));
+
+ sum += buf_tail;
+ if (sum < buf_tail)
+ /* wrap around */
+ sum++;
+ }
+
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+
+ return ~sum;
+}
+
+void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
+ uint16_t source_port, be32_t destination_addr,
+ uint16_t destination_port, uint16_t len, int ip_service_type) {
+ packet->ip.version = IPVERSION;
+ packet->ip.ihl = DHCP_IP_SIZE / 4;
+ packet->ip.tot_len = htobe16(len);
+
+ if (ip_service_type >= 0)
+ packet->ip.tos = ip_service_type;
+ else
+ packet->ip.tos = IPTOS_CLASS_CS6;
+
+ packet->ip.protocol = IPPROTO_UDP;
+ packet->ip.saddr = source_addr;
+ packet->ip.daddr = destination_addr;
+
+ packet->udp.source = htobe16(source_port);
+ packet->udp.dest = htobe16(destination_port);
+
+ packet->udp.len = htobe16(len - DHCP_IP_SIZE);
+
+ packet->ip.check = packet->udp.len;
+ packet->udp.check = dhcp_packet_checksum((uint8_t*)&packet->ip.ttl, len - 8);
+
+ packet->ip.ttl = IPDEFTTL;
+ packet->ip.check = 0;
+ packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE);
+}
+
+int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) {
+ size_t hdrlen;
+
+ assert(packet);
+
+ /* IP */
+
+ if (packet->ip.version != IPVERSION)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: not IPv4");
+
+ if (packet->ip.ihl < 5)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: IPv4 IHL (%u words) invalid",
+ packet->ip.ihl);
+
+ hdrlen = packet->ip.ihl * 4;
+ if (hdrlen < 20)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: IPv4 IHL (%zu bytes) "
+ "smaller than minimum (20 bytes)",
+ hdrlen);
+
+ if (len < hdrlen)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: packet (%zu bytes) "
+ "smaller than expected (%zu) by IP header",
+ len, hdrlen);
+
+ /* UDP */
+
+ if (packet->ip.protocol != IPPROTO_UDP)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: not UDP");
+
+ if (len < hdrlen + be16toh(packet->udp.len))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: packet (%zu bytes) "
+ "smaller than expected (%zu) by UDP header",
+ len, hdrlen + be16toh(packet->udp.len));
+
+ if (be16toh(packet->udp.dest) != port)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: to port %u, which "
+ "is not the DHCP client port (%u)",
+ be16toh(packet->udp.dest), port);
+
+ /* checksums - computing these is relatively expensive, so only do it
+ if all the other checks have passed
+ */
+
+ if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: invalid IP checksum");
+
+ if (checksum && packet->udp.check) {
+ packet->ip.check = packet->udp.len;
+ packet->ip.ttl = 0;
+
+ if (dhcp_packet_checksum((uint8_t*)&packet->ip.ttl,
+ be16toh(packet->udp.len) + 12))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ignoring packet: invalid UDP checksum");
+ }
+
+ return 0;
+}
diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h
new file mode 100644
index 0000000..11f4201
--- /dev/null
+++ b/src/libsystemd-network/dhcp-protocol.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2013 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <stdint.h>
+
+#include "macro.h"
+#include "sparse-endian.h"
+
+struct DHCPMessage {
+ uint8_t op;
+ uint8_t htype;
+ uint8_t hlen;
+ uint8_t hops;
+ be32_t xid;
+ be16_t secs;
+ be16_t flags;
+ be32_t ciaddr;
+ be32_t yiaddr;
+ be32_t siaddr;
+ be32_t giaddr;
+ uint8_t chaddr[16];
+ uint8_t sname[64];
+ uint8_t file[128];
+ be32_t magic;
+ uint8_t options[0];
+} _packed_;
+
+typedef struct DHCPMessage DHCPMessage;
+
+struct DHCPPacket {
+ struct iphdr ip;
+ struct udphdr udp;
+ DHCPMessage dhcp;
+} _packed_;
+
+typedef struct DHCPPacket DHCPPacket;
+
+#define DHCP_IP_SIZE (int32_t)(sizeof(struct iphdr))
+#define DHCP_IP_UDP_SIZE (int32_t)(sizeof(struct udphdr) + DHCP_IP_SIZE)
+#define DHCP_MESSAGE_SIZE (int32_t)(sizeof(DHCPMessage))
+#define DHCP_DEFAULT_MIN_SIZE 576 /* the minimum internet hosts must be able to receive */
+#define DHCP_MIN_OPTIONS_SIZE (DHCP_DEFAULT_MIN_SIZE - DHCP_IP_UDP_SIZE - DHCP_MESSAGE_SIZE)
+#define DHCP_MAGIC_COOKIE (uint32_t)(0x63825363)
+
+enum {
+ DHCP_PORT_SERVER = 67,
+ DHCP_PORT_CLIENT = 68,
+};
+
+enum DHCPState {
+ DHCP_STATE_INIT = 0,
+ DHCP_STATE_SELECTING = 1,
+ DHCP_STATE_INIT_REBOOT = 2,
+ DHCP_STATE_REBOOTING = 3,
+ DHCP_STATE_REQUESTING = 4,
+ DHCP_STATE_BOUND = 5,
+ DHCP_STATE_RENEWING = 6,
+ DHCP_STATE_REBINDING = 7,
+ DHCP_STATE_STOPPED = 8,
+};
+
+typedef enum DHCPState DHCPState;
+
+enum {
+ BOOTREQUEST = 1,
+ BOOTREPLY = 2,
+};
+
+enum {
+ DHCP_DISCOVER = 1,
+ DHCP_OFFER = 2,
+ DHCP_REQUEST = 3,
+ DHCP_DECLINE = 4,
+ DHCP_ACK = 5,
+ DHCP_NAK = 6,
+ DHCP_RELEASE = 7,
+ DHCP_INFORM = 8,
+ DHCP_FORCERENEW = 9,
+};
+
+enum {
+ DHCP_OVERLOAD_FILE = 1,
+ DHCP_OVERLOAD_SNAME = 2,
+};
+
+#define DHCP_MAX_FQDN_LENGTH 255
+
+enum {
+ DHCP_FQDN_FLAG_S = (1 << 0),
+ DHCP_FQDN_FLAG_O = (1 << 1),
+ DHCP_FQDN_FLAG_E = (1 << 2),
+ DHCP_FQDN_FLAG_N = (1 << 3),
+};
diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
new file mode 100644
index 0000000..b57737e
--- /dev/null
+++ b/src/libsystemd-network/dhcp-server-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-server.h"
+#include "sd-event.h"
+
+#include "dhcp-internal.h"
+#include "hashmap.h"
+#include "log.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;
+ void *data;
+} DHCPClientId;
+
+typedef struct DHCPLease {
+ DHCPClientId client_id;
+
+ be32_t address;
+ be32_t gateway;
+ uint8_t chaddr[16];
+ usec_t expiration;
+} DHCPLease;
+
+struct sd_dhcp_server {
+ unsigned n_ref;
+
+ sd_event *event;
+ int event_priority;
+ sd_event_source *receive_message;
+ int fd;
+ int fd_raw;
+
+ int ifindex;
+ 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];
+
+ OrderedHashmap *extra_options;
+ OrderedHashmap *vendor_options;
+
+ bool emit_router;
+
+ Hashmap *leases_by_client_id;
+ DHCPLease **bound_leases;
+ DHCPLease invalid_lease;
+
+ uint32_t max_lease_time, default_lease_time;
+
+ sd_dhcp_server_callback_t callback;
+ void *callback_userdata;
+};
+
+typedef struct DHCPRequest {
+ /* received message */
+ DHCPMessage *message;
+
+ /* options */
+ DHCPClientId client_id;
+ size_t max_optlen;
+ be32_t server_id;
+ be32_t requested_ip;
+ uint32_t lifetime;
+} DHCPRequest;
+
+#define log_dhcp_server(client, fmt, ...) log_internal(LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__, "DHCP SERVER: " fmt, ##__VA_ARGS__)
+#define log_dhcp_server_errno(client, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "DHCP SERVER: " fmt, ##__VA_ARGS__)
+
+int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
+ size_t length);
+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);
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
new file mode 100644
index 0000000..9a32b00
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include <net/ethernet.h>
+#include <netinet/in.h>
+
+#include "sd-event.h"
+
+#include "list.h"
+#include "hashmap.h"
+#include "macro.h"
+#include "sparse-endian.h"
+
+typedef struct sd_dhcp6_option {
+ unsigned n_ref;
+
+ uint32_t enterprise_identifier;
+ uint16_t option;
+ void *data;
+ size_t length;
+} sd_dhcp6_option;
+
+extern const struct hash_ops dhcp6_option_hash_ops;
+
+/* Common option header */
+typedef struct DHCP6Option {
+ be16_t code;
+ be16_t len;
+ uint8_t data[];
+} _packed_ DHCP6Option;
+
+/* Address option */
+struct iaaddr {
+ struct in6_addr address;
+ be32_t lifetime_preferred;
+ be32_t lifetime_valid;
+} _packed_;
+
+/* Prefix Delegation Prefix option */
+struct iapdprefix {
+ be32_t lifetime_preferred;
+ be32_t lifetime_valid;
+ uint8_t prefixlen;
+ struct in6_addr address;
+} _packed_;
+
+typedef struct DHCP6Address DHCP6Address;
+
+struct DHCP6Address {
+ LIST_FIELDS(DHCP6Address, addresses);
+
+ union {
+ struct iaaddr iaaddr;
+ struct iapdprefix iapdprefix;
+ };
+};
+
+/* Non-temporary Address option */
+struct ia_na {
+ be32_t id;
+ be32_t lifetime_t1;
+ be32_t lifetime_t2;
+} _packed_;
+
+/* Prefix Delegation option */
+struct ia_pd {
+ be32_t id;
+ be32_t lifetime_t1;
+ be32_t lifetime_t2;
+} _packed_;
+
+/* Temporary Address option */
+struct ia_ta {
+ be32_t id;
+} _packed_;
+
+struct DHCP6IA {
+ uint16_t type;
+ union {
+ struct ia_na ia_na;
+ struct ia_pd ia_pd;
+ struct ia_ta ia_ta;
+ };
+
+ LIST_HEAD(DHCP6Address, addresses);
+};
+
+typedef struct DHCP6IA DHCP6IA;
+
+#define log_dhcp6_client_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__)
+#define log_dhcp6_client(p, fmt, ...) log_dhcp6_client_errno(p, 0, fmt, ##__VA_ARGS__)
+
+int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
+ size_t optlen, const void *optval);
+int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia);
+int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix);
+int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn);
+int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class);
+int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *user_class);
+int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options);
+int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
+ size_t *optlen, uint8_t **optvalue);
+int dhcp6_option_parse_status(DHCP6Option *option, size_t len);
+int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code);
+int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
+ struct in6_addr **addrs, size_t count,
+ size_t *allocated);
+int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen,
+ char ***str_arr);
+int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str);
+
+int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address);
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
+ const void *packet, size_t len);
+
+const char *dhcp6_message_type_to_string(int s) _const_;
+int dhcp6_message_type_from_string(const char *s) _pure_;
+const char *dhcp6_message_status_to_string(int s) _const_;
+int dhcp6_message_status_from_string(const char *s) _pure_;
diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h
new file mode 100644
index 0000000..e9e2362
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-lease-internal.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014-2015 Intel Corporation. All rights reserved.
+***/
+
+#include <stdint.h>
+
+#include "sd-dhcp6-lease.h"
+
+#include "dhcp6-internal.h"
+
+struct sd_dhcp6_lease {
+ unsigned n_ref;
+
+ uint8_t *serverid;
+ size_t serverid_len;
+ uint8_t preference;
+ bool rapid_commit;
+
+ DHCP6IA ia;
+ DHCP6IA pd;
+
+ DHCP6Address *addr_iter;
+ DHCP6Address *prefix_iter;
+
+ struct in6_addr *dns;
+ size_t dns_count;
+ size_t dns_allocated;
+ char **domains;
+ size_t domains_count;
+ struct in6_addr *ntp;
+ size_t ntp_count;
+ size_t ntp_allocated;
+ char **ntp_fqdn;
+ size_t ntp_fqdn_count;
+ char *fqdn;
+};
+
+int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire);
+DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia);
+
+int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id,
+ size_t len);
+int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len);
+int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference);
+int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference);
+int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease);
+int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit);
+
+int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid);
+int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid);
+
+int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen);
+int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval,
+ size_t optlen);
+int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen);
+int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval,
+ size_t optlen) ;
+int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
+
+int dhcp6_lease_new(sd_dhcp6_lease **ret);
diff --git a/src/libsystemd-network/dhcp6-network.c b/src/libsystemd-network/dhcp6-network.c
new file mode 100644
index 0000000..4f7bd53
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-network.c
@@ -0,0 +1,74 @@
+/* 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 = -1;
+ int r;
+
+ assert(ifindex > 0);
+ assert(local_address);
+
+ src.in6.sin6_addr = *local_address;
+
+ s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_UDP);
+ if (s < 0)
+ return -errno;
+
+ r = setsockopt_int(s, IPPROTO_IPV6, IPV6_V6ONLY, true);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, false);
+ if (r < 0)
+ return r;
+
+ r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
+ if (r < 0)
+ return r;
+
+ r = bind(s, &src.sa, sizeof(src.in6));
+ if (r < 0)
+ return -errno;
+
+ return TAKE_FD(s);
+}
+
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
+ const void *packet, size_t len) {
+ union sockaddr_union dest = {
+ .in6.sin6_family = AF_INET6,
+ .in6.sin6_port = htobe16(DHCP6_PORT_SERVER),
+ };
+ int r;
+
+ assert(server_address);
+
+ memcpy(&dest.in6.sin6_addr, server_address, sizeof(dest.in6.sin6_addr));
+
+ r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6));
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c
new file mode 100644
index 0000000..9f47c1b
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-option.c
@@ -0,0 +1,799 @@
+/* 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 "dhcp-identifier.h"
+#include "dhcp6-internal.h"
+#include "dhcp6-lease-internal.h"
+#include "dhcp6-protocol.h"
+#include "dns-domain.h"
+#include "memory-util.h"
+#include "sparse-endian.h"
+#include "strv.h"
+#include "unaligned.h"
+
+typedef struct DHCP6StatusOption {
+ struct DHCP6Option option;
+ be16_t status;
+ char msg[];
+} _packed_ DHCP6StatusOption;
+
+typedef struct DHCP6AddressOption {
+ struct DHCP6Option option;
+ struct iaaddr iaaddr;
+ uint8_t options[];
+} _packed_ DHCP6AddressOption;
+
+typedef struct DHCP6PDPrefixOption {
+ struct DHCP6Option option;
+ struct iapdprefix iapdprefix;
+ uint8_t options[];
+} _packed_ DHCP6PDPrefixOption;
+
+#define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na))
+#define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd))
+#define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta))
+
+static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
+ size_t optlen) {
+ DHCP6Option *option = (DHCP6Option*) *buf;
+
+ assert_return(buf, -EINVAL);
+ assert_return(*buf, -EINVAL);
+ assert_return(buflen, -EINVAL);
+
+ if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data))
+ return -ENOBUFS;
+
+ option->code = htobe16(optcode);
+ option->len = htobe16(optlen);
+
+ *buf += offsetof(DHCP6Option, data);
+ *buflen -= offsetof(DHCP6Option, data);
+
+ return 0;
+}
+
+int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
+ size_t optlen, const void *optval) {
+ int r;
+
+ assert_return(optval || optlen == 0, -EINVAL);
+
+ r = option_append_hdr(buf, buflen, code, optlen);
+ if (r < 0)
+ return r;
+
+ memcpy_safe(*buf, optval, optlen);
+
+ *buf += optlen;
+ *buflen -= optlen;
+
+ return 0;
+}
+
+int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) {
+ sd_dhcp6_option *options;
+ int r;
+
+ assert(buf);
+ assert(*buf);
+ assert(buflen);
+ assert(vendor_options);
+
+ ORDERED_HASHMAP_FOREACH(options, vendor_options) {
+ _cleanup_free_ uint8_t *p = NULL;
+ size_t total;
+
+ total = 4 + 2 + 2 + options->length;
+
+ p = malloc(total);
+ if (!p)
+ return -ENOMEM;
+
+ unaligned_write_be32(p, options->enterprise_identifier);
+ unaligned_write_be16(p + 4, options->option);
+ unaligned_write_be16(p + 6, options->length);
+ memcpy(p + 8, options->data, options->length);
+
+ r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_OPTS, total, p);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
+ uint16_t len;
+ uint8_t *ia_hdr;
+ size_t iaid_offset, ia_buflen, ia_addrlen = 0;
+ DHCP6Address *addr;
+ int r;
+
+ assert_return(buf, -EINVAL);
+ assert_return(*buf, -EINVAL);
+ assert_return(buflen, -EINVAL);
+ assert_return(ia, -EINVAL);
+
+ switch (ia->type) {
+ case SD_DHCP6_OPTION_IA_NA:
+ len = DHCP6_OPTION_IA_NA_LEN;
+ iaid_offset = offsetof(DHCP6IA, ia_na);
+ break;
+
+ case SD_DHCP6_OPTION_IA_TA:
+ len = DHCP6_OPTION_IA_TA_LEN;
+ iaid_offset = offsetof(DHCP6IA, ia_ta);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (*buflen < offsetof(DHCP6Option, data) + len)
+ return -ENOBUFS;
+
+ ia_hdr = *buf;
+ ia_buflen = *buflen;
+
+ *buf += offsetof(DHCP6Option, data);
+ *buflen -= offsetof(DHCP6Option, data);
+
+ memcpy(*buf, (char*) ia + iaid_offset, len);
+
+ *buf += len;
+ *buflen -= len;
+
+ LIST_FOREACH(addresses, addr, ia->addresses) {
+ r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR,
+ sizeof(addr->iaaddr));
+ if (r < 0)
+ return r;
+
+ memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
+
+ *buf += sizeof(addr->iaaddr);
+ *buflen -= sizeof(addr->iaaddr);
+
+ ia_addrlen += offsetof(DHCP6Option, data) + sizeof(addr->iaaddr);
+ }
+
+ r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
+ uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX];
+ int r;
+
+ assert_return(buf && *buf && buflen && fqdn, -EINVAL);
+
+ buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
+
+ /* Store domain name after flags field */
+ r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1, false);
+ if (r <= 0)
+ return r;
+
+ /*
+ * According to RFC 4704, chapter 4.2 only add terminating zero-length
+ * label in case a FQDN is provided. Since dns_name_to_wire_format
+ * always adds terminating zero-length label remove if only a hostname
+ * is provided.
+ */
+ if (dns_name_is_single_label(fqdn))
+ r--;
+
+ r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer);
+
+ return r;
+}
+
+int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class) {
+ _cleanup_free_ uint8_t *p = NULL;
+ size_t total = 0, offset = 0;
+ char * const *s;
+
+ assert(buf);
+ assert(*buf);
+ assert(buflen);
+ assert(!strv_isempty(user_class));
+
+ STRV_FOREACH(s, user_class) {
+ size_t len = strlen(*s);
+ uint8_t *q;
+
+ if (len > 0xffff || len == 0)
+ return -EINVAL;
+ q = realloc(p, total + len + 2);
+ if (!q)
+ return -ENOMEM;
+
+ p = q;
+
+ unaligned_write_be16(&p[offset], len);
+ memcpy(&p[offset + 2], *s, len);
+
+ offset += 2 + len;
+ total += 2 + len;
+ }
+
+ return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_USER_CLASS, total, p);
+}
+
+int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *vendor_class) {
+ _cleanup_free_ uint8_t *p = NULL;
+ uint32_t enterprise_identifier;
+ size_t total, offset;
+ char * const *s;
+
+ assert(buf);
+ assert(*buf);
+ assert(buflen);
+ assert(!strv_isempty(vendor_class));
+
+ enterprise_identifier = htobe32(SYSTEMD_PEN);
+
+ p = memdup(&enterprise_identifier, sizeof(enterprise_identifier));
+ if (!p)
+ return -ENOMEM;
+
+ total = sizeof(enterprise_identifier);
+ offset = total;
+
+ STRV_FOREACH(s, vendor_class) {
+ size_t len = strlen(*s);
+ uint8_t *q;
+
+ if (len > UINT16_MAX || len == 0)
+ return -EINVAL;
+
+ q = realloc(p, total + len + 2);
+ if (!q)
+ return -ENOMEM;
+
+ p = q;
+
+ unaligned_write_be16(&p[offset], len);
+ memcpy(&p[offset + 2], *s, len);
+
+ offset += 2 + len;
+ total += 2 + len;
+ }
+
+ return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_CLASS, total, p);
+}
+
+int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix) {
+ DHCP6Option *option = (DHCP6Option *)buf;
+ size_t i = sizeof(*option) + sizeof(pd->ia_pd);
+ DHCP6PDPrefixOption *prefix_opt;
+ DHCP6Address *prefix;
+
+ assert_return(buf, -EINVAL);
+ assert_return(pd, -EINVAL);
+ assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL);
+
+ if (len < i)
+ return -ENOBUFS;
+
+ option->code = htobe16(SD_DHCP6_OPTION_IA_PD);
+
+ memcpy(&option->data, &pd->ia_pd, sizeof(pd->ia_pd));
+ LIST_FOREACH(addresses, prefix, pd->addresses) {
+ if (len < i + sizeof(*prefix_opt))
+ return -ENOBUFS;
+
+ prefix_opt = (DHCP6PDPrefixOption *)&buf[i];
+ prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX);
+ prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix));
+
+ memcpy(&prefix_opt->iapdprefix, &prefix->iapdprefix, sizeof(struct iapdprefix));
+ i += sizeof(*prefix_opt);
+ }
+
+ if (hint_pd_prefix && hint_pd_prefix->iapdprefix.prefixlen > 0) {
+ if (len < i + sizeof(*prefix_opt))
+ return -ENOBUFS;
+
+ prefix_opt = (DHCP6PDPrefixOption *)&buf[i];
+ prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX);
+ prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix));
+
+ memcpy(&prefix_opt->iapdprefix, &hint_pd_prefix->iapdprefix, sizeof(struct iapdprefix));
+ i += sizeof(*prefix_opt);
+ }
+
+ option->len = htobe16(i - sizeof(*option));
+
+ return i;
+}
+
+static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
+ DHCP6Option *option = (DHCP6Option*) *buf;
+ uint16_t len;
+
+ assert_return(buf, -EINVAL);
+ assert_return(optcode, -EINVAL);
+ assert_return(optlen, -EINVAL);
+
+ if (*buflen < offsetof(DHCP6Option, data))
+ return -ENOMSG;
+
+ len = be16toh(option->len);
+
+ if (len > *buflen)
+ return -ENOMSG;
+
+ *optcode = be16toh(option->code);
+ *optlen = len;
+
+ *buf += 4;
+ *buflen -= 4;
+
+ return 0;
+}
+
+int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
+ size_t *optlen, uint8_t **optvalue) {
+ int r;
+
+ assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
+
+ r = option_parse_hdr(buf, buflen, optcode, optlen);
+ if (r < 0)
+ return r;
+
+ if (*optlen > *buflen)
+ return -ENOBUFS;
+
+ *optvalue = *buf;
+ *buflen -= *optlen;
+ *buf += *optlen;
+
+ return 0;
+}
+
+int dhcp6_option_parse_status(DHCP6Option *option, size_t len) {
+ DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
+
+ if (len < sizeof(DHCP6StatusOption) ||
+ be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption))
+ return -ENOBUFS;
+
+ return be16toh(statusopt->status);
+}
+
+static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia,
+ uint32_t *lifetime_valid) {
+ DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option;
+ DHCP6Address *addr;
+ uint32_t lt_valid, lt_pref;
+ int r;
+
+ if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option))
+ return -ENOBUFS;
+
+ lt_valid = be32toh(addr_option->iaaddr.lifetime_valid);
+ lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred);
+
+ if (lt_valid == 0 || lt_pref > lt_valid) {
+ log_dhcp6_client(client, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
+ lt_pref, lt_valid);
+
+ return 0;
+ }
+
+ if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) {
+ r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option));
+ if (r != 0)
+ return r < 0 ? r: 0;
+ }
+
+ addr = new0(DHCP6Address, 1);
+ if (!addr)
+ return -ENOMEM;
+
+ LIST_INIT(addresses, addr);
+ memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr));
+
+ LIST_PREPEND(addresses, ia->addresses, addr);
+
+ *lifetime_valid = be32toh(addr->iaaddr.lifetime_valid);
+
+ return 0;
+}
+
+static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia,
+ uint32_t *lifetime_valid) {
+ DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option;
+ DHCP6Address *prefix;
+ uint32_t lt_valid, lt_pref;
+ int r;
+
+ if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option))
+ return -ENOBUFS;
+
+ lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid);
+ lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred);
+
+ if (lt_valid == 0 || lt_pref > lt_valid) {
+ log_dhcp6_client(client, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
+ lt_pref, lt_valid);
+
+ return 0;
+ }
+
+ if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) {
+ r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option));
+ if (r != 0)
+ return r < 0 ? r: 0;
+ }
+
+ prefix = new0(DHCP6Address, 1);
+ if (!prefix)
+ return -ENOMEM;
+
+ LIST_INIT(addresses, prefix);
+ memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix));
+
+ LIST_PREPEND(addresses, ia->addresses, prefix);
+
+ *lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid);
+
+ return 0;
+}
+
+int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code) {
+ uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
+ uint16_t iatype, optlen;
+ size_t iaaddr_offset;
+ int r = 0, status;
+ size_t i, len;
+ uint16_t opt;
+
+ assert_return(ia, -EINVAL);
+ assert_return(!ia->addresses, -EINVAL);
+
+ iatype = be16toh(iaoption->code);
+ len = be16toh(iaoption->len);
+
+ switch (iatype) {
+ case SD_DHCP6_OPTION_IA_NA:
+
+ if (len < DHCP6_OPTION_IA_NA_LEN)
+ return -ENOBUFS;
+
+ iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
+ memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na));
+
+ lt_t1 = be32toh(ia->ia_na.lifetime_t1);
+ lt_t2 = be32toh(ia->ia_na.lifetime_t2);
+
+ if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
+ log_dhcp6_client(client, "IA NA T1 %ds > T2 %ds",
+ lt_t1, lt_t2);
+ return -EINVAL;
+ }
+
+ break;
+
+ case SD_DHCP6_OPTION_IA_PD:
+
+ if (len < sizeof(ia->ia_pd))
+ return -ENOBUFS;
+
+ iaaddr_offset = sizeof(ia->ia_pd);
+ memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd));
+
+ lt_t1 = be32toh(ia->ia_pd.lifetime_t1);
+ lt_t2 = be32toh(ia->ia_pd.lifetime_t2);
+
+ if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
+ log_dhcp6_client(client, "IA PD T1 %ds > T2 %ds",
+ lt_t1, lt_t2);
+ return -EINVAL;
+ }
+
+ break;
+
+ case SD_DHCP6_OPTION_IA_TA:
+ if (len < DHCP6_OPTION_IA_TA_LEN)
+ return -ENOBUFS;
+
+ iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
+ memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta));
+
+ break;
+
+ default:
+ return -ENOMSG;
+ }
+
+ ia->type = iatype;
+ i = iaaddr_offset;
+
+ while (i < len) {
+ DHCP6Option *option = (DHCP6Option *)&iaoption->data[i];
+
+ if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len))
+ return -ENOBUFS;
+
+ opt = be16toh(option->code);
+ optlen = be16toh(option->len);
+
+ switch (opt) {
+ case SD_DHCP6_OPTION_IAADDR:
+
+ if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) {
+ log_dhcp6_client(client, "IA Address option not in IA NA or TA option");
+ return -EINVAL;
+ }
+
+ r = dhcp6_option_parse_address(option, ia, &lt_valid);
+ if (r < 0)
+ return r;
+
+ if (lt_valid < lt_min)
+ lt_min = lt_valid;
+
+ break;
+
+ case SD_DHCP6_OPTION_IA_PD_PREFIX:
+
+ if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_PD)) {
+ log_dhcp6_client(client, "IA PD Prefix option not in IA PD option");
+ return -EINVAL;
+ }
+
+ r = dhcp6_option_parse_pdprefix(option, ia, &lt_valid);
+ if (r < 0)
+ return r;
+
+ if (lt_valid < lt_min)
+ lt_min = lt_valid;
+
+ break;
+
+ case SD_DHCP6_OPTION_STATUS_CODE:
+
+ status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data));
+ if (status < 0)
+ return status;
+
+ if (status > 0) {
+ if (ret_status_code)
+ *ret_status_code = status;
+
+ log_dhcp6_client(client, "IA status %s",
+ dhcp6_message_status_to_string(status));
+
+ return 0;
+ }
+
+ break;
+
+ default:
+ log_dhcp6_client(client, "Unknown IA option %d", opt);
+ break;
+ }
+
+ i += sizeof(*option) + optlen;
+ }
+
+ switch(iatype) {
+ case SD_DHCP6_OPTION_IA_NA:
+ if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) {
+ lt_t1 = lt_min / 2;
+ lt_t2 = lt_min / 10 * 8;
+ ia->ia_na.lifetime_t1 = htobe32(lt_t1);
+ ia->ia_na.lifetime_t2 = htobe32(lt_t2);
+
+ log_dhcp6_client(client, "Computed IA NA T1 %ds and T2 %ds as both were zero",
+ lt_t1, lt_t2);
+ }
+
+ break;
+
+ case SD_DHCP6_OPTION_IA_PD:
+ if (!ia->ia_pd.lifetime_t1 && !ia->ia_pd.lifetime_t2) {
+ lt_t1 = lt_min / 2;
+ lt_t2 = lt_min / 10 * 8;
+ ia->ia_pd.lifetime_t1 = htobe32(lt_t1);
+ ia->ia_pd.lifetime_t2 = htobe32(lt_t2);
+
+ log_dhcp6_client(client, "Computed IA PD T1 %ds and T2 %ds as both were zero",
+ lt_t1, lt_t2);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ if (ret_status_code)
+ *ret_status_code = 0;
+
+ return 1;
+}
+
+int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
+ struct in6_addr **addrs, size_t count,
+ size_t *allocated) {
+
+ if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
+ return -EINVAL;
+
+ if (!GREEDY_REALLOC(*addrs, *allocated,
+ count * sizeof(struct in6_addr) + optlen))
+ return -ENOMEM;
+
+ memcpy(*addrs + count, optval, optlen);
+
+ count += optlen / sizeof(struct in6_addr);
+
+ return count;
+}
+
+static int parse_domain(const uint8_t **data, uint16_t *len, char **out_domain) {
+ _cleanup_free_ char *ret = NULL;
+ size_t n = 0, allocated = 0;
+ const uint8_t *optval = *data;
+ uint16_t optlen = *len;
+ bool first = true;
+ int r;
+
+ if (optlen <= 1)
+ return -ENODATA;
+
+ for (;;) {
+ const char *label;
+ uint8_t c;
+
+ if (optlen == 0)
+ break;
+
+ c = *optval;
+ optval++;
+ optlen--;
+
+ if (c == 0)
+ /* End label */
+ break;
+ if (c > 63)
+ return -EBADMSG;
+ if (c > optlen)
+ return -EMSGSIZE;
+
+ /* Literal label */
+ label = (const char *)optval;
+ optval += c;
+ optlen -= c;
+
+ if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+ return -ENOMEM;
+
+ if (first)
+ first = false;
+ else
+ ret[n++] = '.';
+
+ r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ n += r;
+ }
+
+ if (n) {
+ if (!GREEDY_REALLOC(ret, allocated, n + 1))
+ return -ENOMEM;
+ ret[n] = 0;
+ }
+
+ *out_domain = TAKE_PTR(ret);
+ *data = optval;
+ *len = optlen;
+
+ return n;
+}
+
+int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str) {
+ _cleanup_free_ char *domain = NULL;
+ int r;
+
+ r = parse_domain(&optval, &optlen, &domain);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENODATA;
+ if (optlen != 0)
+ return -EINVAL;
+
+ *str = TAKE_PTR(domain);
+ return 0;
+}
+
+int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
+ size_t idx = 0;
+ _cleanup_strv_free_ char **names = NULL;
+ int r;
+
+ if (optlen <= 1)
+ return -ENODATA;
+ if (optval[optlen - 1] != '\0')
+ return -EINVAL;
+
+ while (optlen > 0) {
+ _cleanup_free_ char *ret = NULL;
+
+ r = parse_domain(&optval, &optlen, &ret);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = strv_extend(&names, ret);
+ if (r < 0)
+ return r;
+
+ idx++;
+ }
+
+ *str_arr = TAKE_PTR(names);
+
+ return idx;
+}
+
+static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) {
+ if (!i)
+ return NULL;
+
+ free(i->data);
+ return mfree(i);
+}
+
+int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) {
+ assert_return(ret, -EINVAL);
+ assert_return(length == 0 || data, -EINVAL);
+
+ _cleanup_free_ void *q = memdup(data, length);
+ if (!q)
+ return -ENOMEM;
+
+ sd_dhcp6_option *p = new(sd_dhcp6_option, 1);
+ if (!p)
+ return -ENOMEM;
+
+ *p = (sd_dhcp6_option) {
+ .n_ref = 1,
+ .option = option,
+ .enterprise_identifier = enterprise_identifier,
+ .length = length,
+ .data = TAKE_PTR(q),
+ };
+
+ *ret = p;
+ return 0;
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ dhcp6_option_hash_ops,
+ void,
+ trivial_hash_func,
+ trivial_compare_func,
+ sd_dhcp6_option,
+ sd_dhcp6_option_unref);
diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h
new file mode 100644
index 0000000..c700363
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-protocol.h
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+
+#include "macro.h"
+#include "sparse-endian.h"
+
+struct DHCP6Message {
+ union {
+ struct {
+ uint8_t type;
+ uint8_t _pad[3];
+ } _packed_;
+ be32_t transaction_id;
+ };
+ uint8_t options[];
+} _packed_;
+
+typedef struct DHCP6Message DHCP6Message;
+
+#define DHCP6_MIN_OPTIONS_SIZE \
+ 1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr)
+
+#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } }
+
+enum {
+ DHCP6_PORT_SERVER = 547,
+ DHCP6_PORT_CLIENT = 546,
+};
+
+#define DHCP6_INF_TIMEOUT 1 * USEC_PER_SEC
+#define DHCP6_INF_MAX_RT 120 * USEC_PER_SEC
+#define DHCP6_SOL_MAX_DELAY 1 * USEC_PER_SEC
+#define DHCP6_SOL_TIMEOUT 1 * USEC_PER_SEC
+#define DHCP6_SOL_MAX_RT 120 * USEC_PER_SEC
+#define DHCP6_REQ_TIMEOUT 1 * USEC_PER_SEC
+#define DHCP6_REQ_MAX_RT 120 * USEC_PER_SEC
+#define DHCP6_REQ_MAX_RC 10
+#define DHCP6_REN_TIMEOUT 10 * USEC_PER_SEC
+#define DHCP6_REN_MAX_RT 600 * USEC_PER_SEC
+#define DHCP6_REB_TIMEOUT 10 * USEC_PER_SEC
+#define DHCP6_REB_MAX_RT 600 * USEC_PER_SEC
+
+enum DHCP6State {
+ DHCP6_STATE_STOPPED = 0,
+ DHCP6_STATE_INFORMATION_REQUEST = 1,
+ DHCP6_STATE_SOLICITATION = 2,
+ DHCP6_STATE_REQUEST = 3,
+ DHCP6_STATE_BOUND = 4,
+ DHCP6_STATE_RENEW = 5,
+ DHCP6_STATE_REBIND = 6,
+};
+
+enum {
+ DHCP6_SOLICIT = 1,
+ DHCP6_ADVERTISE = 2,
+ DHCP6_REQUEST = 3,
+ DHCP6_CONFIRM = 4,
+ DHCP6_RENEW = 5,
+ DHCP6_REBIND = 6,
+ DHCP6_REPLY = 7,
+ DHCP6_RELEASE = 8,
+ DHCP6_DECLINE = 9,
+ DHCP6_RECONFIGURE = 10,
+ DHCP6_INFORMATION_REQUEST = 11,
+ DHCP6_RELAY_FORW = 12,
+ DHCP6_RELAY_REPL = 13,
+ _DHCP6_MESSAGE_MAX = 14,
+};
+
+enum {
+ DHCP6_NTP_SUBOPTION_SRV_ADDR = 1,
+ DHCP6_NTP_SUBOPTION_MC_ADDR = 2,
+ DHCP6_NTP_SUBOPTION_SRV_FQDN = 3,
+};
+
+/*
+ * RFC 8415, RFC 5007 and RFC 7653 status codes:
+ * https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-5
+ */
+enum {
+ DHCP6_STATUS_SUCCESS = 0,
+ DHCP6_STATUS_UNSPEC_FAIL = 1,
+ DHCP6_STATUS_NO_ADDRS_AVAIL = 2,
+ DHCP6_STATUS_NO_BINDING = 3,
+ DHCP6_STATUS_NOT_ON_LINK = 4,
+ DHCP6_STATUS_USE_MULTICAST = 5,
+ DHCP6_STATUS_NO_PREFIX_AVAIL = 6,
+ DHCP6_STATUS_UNKNOWN_QUERY_TYPE = 7,
+ DHCP6_STATUS_MALFORMED_QUERY = 8,
+ DHCP6_STATUS_NOT_CONFIGURED = 9,
+ DHCP6_STATUS_NOT_ALLOWED = 10,
+ DHCP6_STATUS_QUERY_TERMINATED = 11,
+ DHCP6_STATUS_DATA_MISSING = 12,
+ DHCP6_STATUS_CATCHUP_COMPLETE = 13,
+ DHCP6_STATUS_NOT_SUPPORTED = 14,
+ DHCP6_STATUS_TLS_CONNECTION_REFUSED = 15,
+ DHCP6_STATUS_ADDRESS_IN_USE = 16,
+ DHCP6_STATUS_CONFIGURATION_CONFLICT = 17,
+ DHCP6_STATUS_MISSING_BINDING_INFORMATION = 18,
+ DHCP6_STATUS_OUTDATED_BINDING_INFORMATION = 19,
+ DHCP6_STATUS_SERVER_SHUTTING_DOWN = 20,
+ DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED = 21,
+ DHCP6_STATUS_EXCESSIVE_TIME_SKEW = 22,
+ _DHCP6_STATUS_MAX = 23,
+};
+
+enum {
+ DHCP6_FQDN_FLAG_S = (1 << 0),
+ DHCP6_FQDN_FLAG_O = (1 << 1),
+ DHCP6_FQDN_FLAG_N = (1 << 2),
+};
diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c
new file mode 100644
index 0000000..4af0125
--- /dev/null
+++ b/src/libsystemd-network/icmp6-util.c
@@ -0,0 +1,210 @@
+/* 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 "io-util.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 = -1;
+ int r;
+
+ s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6);
+ if (s < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, filter, sizeof(*filter));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, mreq, sizeof(*mreq));
+ if (r < 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,
+ };
+ int r;
+
+ assert(s >= 0);
+ assert(ether_addr);
+
+ rs.rs_opt_mac = *ether_addr;
+
+ r = sendmsg(s, &msg, 0);
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
+
+int icmp6_receive(int fd, void *buffer, size_t size, struct in6_addr *dst,
+ triple_timestamp *timestamp) {
+
+ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int)) + /* ttl */
+ CMSG_SPACE(sizeof(struct 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 cmsghdr *cmsg;
+ 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) {
+
+ *dst = sa.in6.sin6_addr;
+ if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) dst) <= 0)
+ 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_CTRUNC));
+ assert(!(msg.msg_flags & MSG_TRUNC));
+
+ CMSG_FOREACH(cmsg, &msg) {
+ if (cmsg->cmsg_level == SOL_IPV6 &&
+ cmsg->cmsg_type == IPV6_HOPLIMIT &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
+ int hops = *(int*) CMSG_DATA(cmsg);
+
+ if (hops != 255)
+ return -EMULTIHOP;
+ }
+
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SO_TIMESTAMP &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
+ triple_timestamp_from_realtime(timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
+ }
+
+ if (!triple_timestamp_is_set(timestamp))
+ triple_timestamp_get(timestamp);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h
new file mode 100644
index 0000000..50d21b5
--- /dev/null
+++ b/src/libsystemd-network/icmp6-util.h
@@ -0,0 +1,24 @@
+/* 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 *dst,
+ triple_timestamp *timestamp);
diff --git a/src/libsystemd-network/lldp-internal.h b/src/libsystemd-network/lldp-internal.h
new file mode 100644
index 0000000..f23695f
--- /dev/null
+++ b/src/libsystemd-network/lldp-internal.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-event.h"
+#include "sd-lldp.h"
+
+#include "hashmap.h"
+#include "log.h"
+#include "prioq.h"
+
+struct sd_lldp {
+ unsigned n_ref;
+
+ int ifindex;
+ int fd;
+
+ sd_event *event;
+ int64_t event_priority;
+ sd_event_source *io_event_source;
+ sd_event_source *timer_event_source;
+
+ Prioq *neighbor_by_expiry;
+ Hashmap *neighbor_by_id;
+
+ uint64_t neighbors_max;
+
+ sd_lldp_callback_t callback;
+ void *userdata;
+
+ uint16_t capability_mask;
+
+ struct ether_addr filter_address;
+};
+
+#define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__)
+#define log_lldp(fmt, ...) log_lldp_errno(0, fmt, ##__VA_ARGS__)
+
+const char* lldp_event_to_string(sd_lldp_event e) _const_;
+sd_lldp_event lldp_event_from_string(const char *s) _pure_;
diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c
new file mode 100644
index 0000000..546ae1c
--- /dev/null
+++ b/src/libsystemd-network/lldp-neighbor.c
@@ -0,0 +1,792 @@
+/* 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-internal.h"
+#include "lldp-neighbor.h"
+#include "memory-util.h"
+#include "missing_network.h"
+#include "unaligned.h"
+
+static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash *state) {
+ siphash24_compress(id->chassis_id, id->chassis_id_size, state);
+ siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state);
+ siphash24_compress(id->port_id, id->port_id_size, state);
+ siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state);
+}
+
+int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y) {
+ return memcmp_nn(x->chassis_id, x->chassis_id_size, y->chassis_id, y->chassis_id_size)
+ ?: memcmp_nn(x->port_id, x->port_id_size, y->port_id, y->port_id_size);
+}
+
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(lldp_neighbor_hash_ops, LLDPNeighborID, lldp_neighbor_id_hash_func, lldp_neighbor_id_compare_func,
+ sd_lldp_neighbor, lldp_neighbor_unlink);
+
+int lldp_neighbor_prioq_compare_func(const void *a, const void *b) {
+ const sd_lldp_neighbor *x = a, *y = b;
+
+ return CMP(x->until, y->until);
+}
+
+_public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) {
+ if (!n)
+ return NULL;
+
+ assert(n->n_ref > 0 || n->lldp);
+ n->n_ref++;
+
+ return n;
+}
+
+static void lldp_neighbor_free(sd_lldp_neighbor *n) {
+ assert(n);
+
+ free(n->id.port_id);
+ free(n->id.chassis_id);
+ free(n->port_description);
+ free(n->system_name);
+ free(n->system_description);
+ free(n->mud_url);
+ free(n->chassis_id_as_string);
+ free(n->port_id_as_string);
+ free(n);
+}
+
+_public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) {
+
+ /* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from
+ * the sd_lldp object. */
+
+ if (!n)
+ return NULL;
+
+ assert(n->n_ref > 0);
+ n->n_ref--;
+
+ if (n->n_ref <= 0 && !n->lldp)
+ lldp_neighbor_free(n);
+
+ return NULL;
+}
+
+sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) {
+
+ /* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */
+
+ if (!n)
+ return NULL;
+
+ if (!n->lldp)
+ return NULL;
+
+ /* Only remove the neighbor object from the hash table if it's in there, don't complain if it isn't. This is
+ * because we are used as destructor call for hashmap_clear() and thus sometimes are called to de-register
+ * ourselves from the hashtable and sometimes are called after we already are de-registered. */
+
+ (void) hashmap_remove_value(n->lldp->neighbor_by_id, &n->id, n);
+
+ assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0);
+
+ n->lldp = NULL;
+
+ if (n->n_ref <= 0)
+ lldp_neighbor_free(n);
+
+ return NULL;
+}
+
+sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) {
+ sd_lldp_neighbor *n;
+
+ n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size);
+ if (!n)
+ return NULL;
+
+ n->raw_size = raw_size;
+ n->n_ref = 1;
+
+ return n;
+}
+
+static int parse_string(char **s, const void *q, size_t n) {
+ const char *p = q;
+ char *k;
+
+ assert(s);
+ assert(p || n == 0);
+
+ if (*s) {
+ log_lldp("Found duplicate string, ignoring field.");
+ return 0;
+ }
+
+ /* Strip trailing NULs, just to be nice */
+ while (n > 0 && p[n-1] == 0)
+ n--;
+
+ if (n <= 0) /* Ignore empty strings */
+ return 0;
+
+ /* Look for inner NULs */
+ if (memchr(p, 0, n)) {
+ log_lldp("Found inner NUL in string, ignoring field.");
+ return 0;
+ }
+
+ /* Let's escape weird chars, for security reasons */
+ k = cescape_length(p, n);
+ if (!k)
+ return -ENOMEM;
+
+ free(*s);
+ *s = k;
+
+ return 1;
+}
+
+int lldp_neighbor_parse(sd_lldp_neighbor *n) {
+ struct ether_header h;
+ const uint8_t *p;
+ size_t left;
+ int r;
+
+ assert(n);
+
+ if (n->raw_size < sizeof(struct ether_header)) {
+ log_lldp("Received truncated packet, ignoring.");
+ return -EBADMSG;
+ }
+
+ memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h));
+
+ if (h.ether_type != htobe16(ETHERTYPE_LLDP)) {
+ log_lldp("Received packet with wrong type, ignoring.");
+ return -EBADMSG;
+ }
+
+ if (h.ether_dhost[0] != 0x01 ||
+ h.ether_dhost[1] != 0x80 ||
+ h.ether_dhost[2] != 0xc2 ||
+ h.ether_dhost[3] != 0x00 ||
+ h.ether_dhost[4] != 0x00 ||
+ !IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) {
+ log_lldp("Received packet with wrong destination address, ignoring.");
+ return -EBADMSG;
+ }
+
+ memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr));
+ memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr));
+
+ p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header);
+ left = n->raw_size - sizeof(struct ether_header);
+
+ for (;;) {
+ uint8_t type;
+ uint16_t length;
+
+ if (left < 2) {
+ log_lldp("TLV lacks header, ignoring.");
+ return -EBADMSG;
+ }
+
+ type = p[0] >> 1;
+ length = p[1] + (((uint16_t) (p[0] & 1)) << 8);
+ p += 2, left -= 2;
+
+ if (left < length) {
+ log_lldp("TLV truncated, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ switch (type) {
+
+ case SD_LLDP_TYPE_END:
+ if (length != 0) {
+ log_lldp("End marker TLV not zero-sized, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ /* Note that after processing the SD_LLDP_TYPE_END left could still be > 0
+ * as the message may contain padding (see IEEE 802.1AB-2016, sec. 8.5.12) */
+
+ goto end_marker;
+
+ case SD_LLDP_TYPE_CHASSIS_ID:
+ if (length < 2 || length > 256) { /* includes the chassis subtype, hence one extra byte */
+ log_lldp("Chassis ID field size out of range, ignoring datagram.");
+ return -EBADMSG;
+ }
+ if (n->id.chassis_id) {
+ log_lldp("Duplicate chassis ID field, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ n->id.chassis_id = memdup(p, length);
+ if (!n->id.chassis_id)
+ return -ENOMEM;
+
+ n->id.chassis_id_size = length;
+ break;
+
+ case SD_LLDP_TYPE_PORT_ID:
+ if (length < 2 || length > 256) { /* includes the port subtype, hence one extra byte */
+ log_lldp("Port ID field size out of range, ignoring datagram.");
+ return -EBADMSG;
+ }
+ if (n->id.port_id) {
+ log_lldp("Duplicate port ID field, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ n->id.port_id = memdup(p, length);
+ if (!n->id.port_id)
+ return -ENOMEM;
+
+ n->id.port_id_size = length;
+ break;
+
+ case SD_LLDP_TYPE_TTL:
+ if (length != 2) {
+ log_lldp("TTL field has wrong size, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ if (n->has_ttl) {
+ log_lldp("Duplicate TTL field, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ n->ttl = unaligned_read_be16(p);
+ n->has_ttl = true;
+ break;
+
+ case SD_LLDP_TYPE_PORT_DESCRIPTION:
+ r = parse_string(&n->port_description, p, length);
+ if (r < 0)
+ return r;
+ break;
+
+ case SD_LLDP_TYPE_SYSTEM_NAME:
+ r = parse_string(&n->system_name, p, length);
+ if (r < 0)
+ return r;
+ break;
+
+ case SD_LLDP_TYPE_SYSTEM_DESCRIPTION:
+ r = parse_string(&n->system_description, p, length);
+ if (r < 0)
+ return r;
+ break;
+
+ case SD_LLDP_TYPE_SYSTEM_CAPABILITIES:
+ if (length != 4)
+ log_lldp("System capabilities field has wrong size, ignoring.");
+ else {
+ n->system_capabilities = unaligned_read_be16(p);
+ n->enabled_capabilities = unaligned_read_be16(p + 2);
+ n->has_capabilities = true;
+ }
+
+ break;
+
+ case SD_LLDP_TYPE_PRIVATE: {
+ if (length < 4)
+ log_lldp("Found private TLV that is too short, ignoring.");
+ else {
+ /* RFC 8520: MUD URL */
+ if (memcmp(p, SD_LLDP_OUI_MUD, sizeof(SD_LLDP_OUI_MUD)) == 0 &&
+ p[sizeof(SD_LLDP_OUI_MUD)] == SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION) {
+ r = parse_string(&n->mud_url, p + sizeof(SD_LLDP_OUI_MUD) + 1,
+ length - 1 - sizeof(SD_LLDP_OUI_MUD));
+ if (r < 0)
+ return r;
+ }
+ }
+ }
+
+ break;
+ }
+
+ p += length, left -= length;
+ }
+
+end_marker:
+ if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) {
+ log_lldp("One or more mandatory TLV missing in datagram. Ignoring.");
+ return -EBADMSG;
+
+ }
+
+ n->rindex = sizeof(struct ether_header);
+
+ return 0;
+}
+
+void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) {
+ assert(n);
+
+ if (n->ttl > 0) {
+ usec_t base;
+
+ /* Use the packet's timestamp if there is one known */
+ base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic());
+ if (base <= 0 || base == USEC_INFINITY)
+ base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */
+
+ n->until = usec_add(base, n->ttl * USEC_PER_SEC);
+ } else
+ n->until = 0;
+
+ if (n->lldp)
+ prioq_reshuffle(n->lldp->neighbor_by_expiry, n, &n->prioq_idx);
+}
+
+bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) {
+ if (a == b)
+ return true;
+
+ if (!a || !b)
+ return false;
+
+ if (a->raw_size != b->raw_size)
+ return false;
+
+ return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0;
+}
+
+_public_ int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) {
+ assert_return(n, -EINVAL);
+ assert_return(address, -EINVAL);
+
+ *address = n->source_address;
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) {
+ assert_return(n, -EINVAL);
+ assert_return(address, -EINVAL);
+
+ *address = n->destination_address;
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ *ret = LLDP_NEIGHBOR_RAW(n);
+ *size = n->raw_size;
+
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
+ assert_return(n, -EINVAL);
+ assert_return(type, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ assert(n->id.chassis_id_size > 0);
+
+ *type = *(uint8_t*) n->id.chassis_id;
+ *ret = (uint8_t*) n->id.chassis_id + 1;
+ *size = n->id.chassis_id_size - 1;
+
+ return 0;
+}
+
+static int format_mac_address(const void *data, size_t sz, char **ret) {
+ struct ether_addr a;
+ char *k;
+
+ assert(data || sz <= 0);
+
+ if (sz != 7)
+ return 0;
+
+ memcpy(&a, (uint8_t*) data + 1, sizeof(a));
+
+ k = new(char, ETHER_ADDR_TO_STRING_MAX);
+ if (!k)
+ return -ENOMEM;
+
+ *ret = ether_addr_to_string(&a, k);
+ return 1;
+}
+
+static int format_network_address(const void *data, size_t sz, char **ret) {
+ union in_addr_union a;
+ int family, r;
+
+ if (sz == 6 && ((uint8_t*) data)[1] == 1) {
+ memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in));
+ family = AF_INET;
+ } else if (sz == 18 && ((uint8_t*) data)[1] == 2) {
+ memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6));
+ family = AF_INET6;
+ } else
+ return 0;
+
+ r = in_addr_to_string(family, &a, ret);
+ if (r < 0)
+ return r;
+ return 1;
+}
+
+_public_ int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) {
+ char *k;
+ int r;
+
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (n->chassis_id_as_string) {
+ *ret = n->chassis_id_as_string;
+ return 0;
+ }
+
+ assert(n->id.chassis_id_size > 0);
+
+ switch (*(uint8_t*) n->id.chassis_id) {
+
+ case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
+ case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
+ case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT:
+ case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
+ case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
+ k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1);
+ if (!k)
+ return -ENOMEM;
+
+ goto done;
+
+ case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
+ r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto done;
+
+ break;
+
+ case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS:
+ r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto done;
+
+ break;
+ }
+
+ /* Generic fallback */
+ k = hexmem(n->id.chassis_id, n->id.chassis_id_size);
+ if (!k)
+ return -ENOMEM;
+
+done:
+ *ret = n->chassis_id_as_string = k;
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
+ assert_return(n, -EINVAL);
+ assert_return(type, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ assert(n->id.port_id_size > 0);
+
+ *type = *(uint8_t*) n->id.port_id;
+ *ret = (uint8_t*) n->id.port_id + 1;
+ *size = n->id.port_id_size - 1;
+
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) {
+ char *k;
+ int r;
+
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (n->port_id_as_string) {
+ *ret = n->port_id_as_string;
+ return 0;
+ }
+
+ assert(n->id.port_id_size > 0);
+
+ switch (*(uint8_t*) n->id.port_id) {
+
+ case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
+ case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT:
+ case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME:
+ case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
+ k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1);
+ if (!k)
+ return -ENOMEM;
+
+ goto done;
+
+ case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS:
+ r = format_mac_address(n->id.port_id, n->id.port_id_size, &k);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto done;
+
+ break;
+
+ case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS:
+ r = format_network_address(n->id.port_id, n->id.port_id_size, &k);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto done;
+
+ break;
+ }
+
+ /* Generic fallback */
+ k = hexmem(n->id.port_id, n->id.port_id_size);
+ if (!k)
+ return -ENOMEM;
+
+done:
+ *ret = n->port_id_as_string = k;
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) {
+ assert_return(n, -EINVAL);
+ assert_return(ret_sec, -EINVAL);
+
+ *ret_sec = n->ttl;
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->system_name)
+ return -ENODATA;
+
+ *ret = n->system_name;
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->system_description)
+ return -ENODATA;
+
+ *ret = n->system_description;
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->port_description)
+ return -ENODATA;
+
+ *ret = n->port_description;
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->mud_url)
+ return -ENODATA;
+
+ *ret = n->mud_url;
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->has_capabilities)
+ return -ENODATA;
+
+ *ret = n->system_capabilities;
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->has_capabilities)
+ return -ENODATA;
+
+ *ret = n->enabled_capabilities;
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
+ _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+ assert_return(raw || raw_size <= 0, -EINVAL);
+
+ n = lldp_neighbor_new(raw_size);
+ if (!n)
+ return -ENOMEM;
+
+ memcpy(LLDP_NEIGHBOR_RAW(n), raw, raw_size);
+ r = lldp_neighbor_parse(n);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(n);
+
+ return r;
+}
+
+_public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) {
+ assert_return(n, -EINVAL);
+
+ assert(n->raw_size >= sizeof(struct ether_header));
+ n->rindex = sizeof(struct ether_header);
+
+ return n->rindex < n->raw_size;
+}
+
+_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) {
+ size_t length;
+
+ assert_return(n, -EINVAL);
+
+ if (n->rindex == n->raw_size) /* EOF */
+ return -ESPIPE;
+
+ if (n->rindex + 2 > n->raw_size) /* Truncated message */
+ return -EBADMSG;
+
+ length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+ if (n->rindex + 2 + length > n->raw_size)
+ return -EBADMSG;
+
+ n->rindex += 2 + length;
+ return n->rindex < n->raw_size;
+}
+
+_public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) {
+ assert_return(n, -EINVAL);
+ assert_return(type, -EINVAL);
+
+ if (n->rindex == n->raw_size) /* EOF */
+ return -ESPIPE;
+
+ if (n->rindex + 2 > n->raw_size)
+ return -EBADMSG;
+
+ *type = LLDP_NEIGHBOR_TLV_TYPE(n);
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) {
+ uint8_t k;
+ int r;
+
+ assert_return(n, -EINVAL);
+
+ r = sd_lldp_neighbor_tlv_get_type(n, &k);
+ if (r < 0)
+ return r;
+
+ return type == k;
+}
+
+_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype) {
+ const uint8_t *d;
+ size_t length;
+ int r;
+
+ assert_return(n, -EINVAL);
+ assert_return(oui, -EINVAL);
+ assert_return(subtype, -EINVAL);
+
+ r = sd_lldp_neighbor_tlv_is_type(n, SD_LLDP_TYPE_PRIVATE);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENXIO;
+
+ length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+ if (length < 4)
+ return -EBADMSG;
+
+ if (n->rindex + 2 + length > n->raw_size)
+ return -EBADMSG;
+
+ d = LLDP_NEIGHBOR_TLV_DATA(n);
+ memcpy(oui, d, 3);
+ *subtype = d[3];
+
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype) {
+ uint8_t k[3], st;
+ int r;
+
+ r = sd_lldp_neighbor_tlv_get_oui(n, k, &st);
+ if (r == -ENXIO)
+ return 0;
+ if (r < 0)
+ return r;
+
+ return memcmp(k, oui, 3) == 0 && st == subtype;
+}
+
+_public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
+ size_t length;
+
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ /* Note that this returns the full TLV, including the TLV header */
+
+ if (n->rindex + 2 > n->raw_size)
+ return -EBADMSG;
+
+ length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+ if (n->rindex + 2 + length > n->raw_size)
+ return -EBADMSG;
+
+ *ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
+ *size = length + 2;
+
+ return 0;
+}
+
+_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) {
+ assert_return(n, -EINVAL);
+ assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
+ assert_return(clock_supported(clock), -EOPNOTSUPP);
+ assert_return(ret, -EINVAL);
+
+ if (!triple_timestamp_is_set(&n->timestamp))
+ return -ENODATA;
+
+ *ret = triple_timestamp_by_clock(&n->timestamp, clock);
+ return 0;
+}
diff --git a/src/libsystemd-network/lldp-neighbor.h b/src/libsystemd-network/lldp-neighbor.h
new file mode 100644
index 0000000..a5718c8
--- /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.h"
+
+#include "hash-funcs.h"
+#include "lldp-internal.h"
+#include "time-util.h"
+
+typedef struct LLDPNeighborID {
+ /* The spec calls this an "MSAP identifier" */
+ void *chassis_id;
+ size_t chassis_id_size;
+
+ void *port_id;
+ size_t port_id_size;
+} LLDPNeighborID;
+
+struct sd_lldp_neighbor {
+ /* Neighbor objects stay around as long as they are linked into an "sd_lldp" object or n_ref > 0. */
+ sd_lldp *lldp;
+ unsigned n_ref;
+
+ triple_timestamp timestamp;
+
+ usec_t until;
+ unsigned prioq_idx;
+
+ struct ether_addr source_address;
+ struct ether_addr destination_address;
+
+ LLDPNeighborID id;
+
+ /* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */
+ size_t raw_size;
+
+ /* The current read index for the iterative TLV interface */
+ size_t rindex;
+
+ /* And a couple of fields parsed out. */
+ bool has_ttl:1;
+ bool has_capabilities:1;
+ bool has_port_vlan_id:1;
+
+ uint16_t ttl;
+
+ uint16_t system_capabilities;
+ uint16_t enabled_capabilities;
+
+ char *port_description;
+ char *system_name;
+ char *system_description;
+ char *mud_url;
+
+ uint16_t port_vlan_id;
+
+ char *chassis_id_as_string;
+ char *port_id_as_string;
+};
+
+static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) {
+ return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor));
+}
+
+static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) {
+ return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1;
+}
+
+static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) {
+ uint8_t *p;
+
+ p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
+ return p[1] + (((size_t) (p[0] & 1)) << 8);
+}
+
+static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) {
+ return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2;
+}
+
+extern const struct hash_ops lldp_neighbor_hash_ops;
+int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y);
+int lldp_neighbor_prioq_compare_func(const void *a, const void *b);
+
+sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n);
+sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size);
+int lldp_neighbor_parse(sd_lldp_neighbor *n);
+void lldp_neighbor_start_ttl(sd_lldp_neighbor *n);
+bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b);
diff --git a/src/libsystemd-network/lldp-network.c b/src/libsystemd-network/lldp-network.c
new file mode 100644
index 0000000..9616cb6
--- /dev/null
+++ b/src/libsystemd-network/lldp-network.c
@@ -0,0 +1,78 @@
+/* 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_t) -1), /* accept packet */
+ };
+
+ static const struct sock_fprog fprog = {
+ .len = ELEMENTSOF(filter),
+ .filter = (struct sock_filter*) filter,
+ };
+
+ struct packet_mreq mreq = {
+ .mr_ifindex = ifindex,
+ .mr_type = PACKET_MR_MULTICAST,
+ .mr_alen = ETH_ALEN,
+ .mr_address = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00 }
+ };
+
+ union sockaddr_union saddrll = {
+ .ll.sll_family = AF_PACKET,
+ .ll.sll_ifindex = ifindex,
+ };
+
+ _cleanup_close_ int fd = -1;
+ int r;
+
+ assert(ifindex > 0);
+
+ fd = socket(AF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK,
+ htobe16(ETHERTYPE_LLDP));
+ if (fd < 0)
+ return -errno;
+
+ r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+ if (r < 0)
+ return -errno;
+
+ mreq.mr_address[ETH_ALEN - 1] = 0x03;
+ r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+ if (r < 0)
+ return -errno;
+
+ mreq.mr_address[ETH_ALEN - 1] = 0x0E;
+ r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+ if (r < 0)
+ return -errno;
+
+ r = bind(fd, &saddrll.sa, sizeof(saddrll.ll));
+ if (r < 0)
+ return -errno;
+
+ return TAKE_FD(fd);
+}
diff --git a/src/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/meson.build b/src/libsystemd-network/meson.build
new file mode 100644
index 0000000..604cfd9
--- /dev/null
+++ b/src/libsystemd-network/meson.build
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+sources = files('''
+ sd-dhcp-client.c
+ sd-dhcp-server.c
+ dhcp-client-internal.h
+ dhcp-network.c
+ dhcp-option.c
+ dhcp-packet.c
+ dhcp-internal.h
+ dhcp-server-internal.h
+ dhcp-protocol.h
+ dhcp-lease-internal.h
+ sd-dhcp-lease.c
+ sd-ipv4ll.c
+ sd-ipv4acd.c
+ arp-util.h
+ arp-util.c
+ network-internal.c
+ sd-ndisc.c
+ ndisc-internal.h
+ ndisc-router.h
+ ndisc-router.c
+ sd-radv.c
+ radv-internal.h
+ icmp6-util.h
+ icmp6-util.c
+ sd-dhcp6-client.c
+ dhcp6-internal.h
+ dhcp6-protocol.h
+ dhcp6-network.c
+ dhcp6-option.c
+ dhcp6-lease-internal.h
+ sd-dhcp6-lease.c
+ dhcp-identifier.h
+ dhcp-identifier.c
+ lldp-internal.h
+ lldp-network.h
+ lldp-network.c
+ lldp-neighbor.h
+ lldp-neighbor.c
+ sd-lldp.c
+'''.split())
+
+network_internal_h = files('network-internal.h')
+
+libsystemd_network = static_library(
+ 'systemd-network',
+ sources,
+ network_internal_h,
+ include_directories : includes)
diff --git a/src/libsystemd-network/ndisc-internal.h b/src/libsystemd-network/ndisc-internal.h
new file mode 100644
index 0000000..65f9371
--- /dev/null
+++ b/src/libsystemd-network/ndisc-internal.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include "log.h"
+#include "time-util.h"
+
+#include "sd-ndisc.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;
+ int fd;
+
+ sd_event *event;
+ int event_priority;
+
+ struct ether_addr mac_addr;
+ uint8_t hop_limit;
+ uint32_t mtu;
+
+ 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;
+};
+
+#define log_ndisc_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "NDISC: " fmt, ##__VA_ARGS__)
+#define log_ndisc(fmt, ...) log_ndisc_errno(0, fmt, ##__VA_ARGS__)
+
+const char* ndisc_event_to_string(sd_ndisc_event e) _const_;
+sd_ndisc_event ndisc_event_from_string(const char *s) _pure_;
diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c
new file mode 100644
index 0000000..3cb71db
--- /dev/null
+++ b/src/libsystemd-network/ndisc-router.c
@@ -0,0 +1,750 @@
+/* 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-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;
+
+ rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size);
+ if (!rt)
+ return NULL;
+
+ rt->raw_size = raw_size;
+ rt->n_ref = 1;
+
+ return rt;
+}
+
+_public_ int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size) {
+ _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+ assert_return(raw || raw_size <= 0, -EINVAL);
+
+ rt = ndisc_router_new(raw_size);
+ if (!rt)
+ return -ENOMEM;
+
+ memcpy(NDISC_ROUTER_RAW(rt), raw, raw_size);
+ r = ndisc_router_parse(rt);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(rt);
+
+ return r;
+}
+
+_public_ int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret_addr, -EINVAL);
+
+ if (IN6_IS_ADDR_UNSPECIFIED(&rt->address))
+ return -ENODATA;
+
+ *ret_addr = rt->address;
+ return 0;
+}
+
+_public_ 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;
+}
+
+_public_ int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ *ret = NDISC_ROUTER_RAW(rt);
+ *size = rt->raw_size;
+
+ return 0;
+}
+
+int ndisc_router_parse(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)) {
+ log_ndisc("Too small to be a router advertisement, ignoring.");
+ return -EBADMSG;
+ }
+
+ /* Router advertisement packets are neatly aligned to 64bit boundaries, hence we can access them directly */
+ a = NDISC_ROUTER_RAW(rt);
+
+ if (a->nd_ra_type != ND_ROUTER_ADVERT) {
+ log_ndisc("Received ND packet that is not a router advertisement, ignoring.");
+ return -EBADMSG;
+ }
+
+ if (a->nd_ra_code != 0) {
+ log_ndisc("Received ND packet with wrong RA code, ignoring.");
+ return -EBADMSG;
+ }
+
+ rt->hop_limit = a->nd_ra_curhoplimit;
+ rt->flags = a->nd_ra_flags_reserved; /* the first 8bit */
+ rt->lifetime = be16toh(a->nd_ra_router_lifetime);
+
+ 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) {
+ log_ndisc("Option lacks header, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ type = p[0];
+ length = p[1] * 8;
+
+ if (length == 0) {
+ log_ndisc("Zero-length option, ignoring datagram.");
+ return -EBADMSG;
+ }
+ if (left < length) {
+ log_ndisc("Option truncated, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ switch (type) {
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION:
+
+ if (length != 4*8) {
+ log_ndisc("Prefix option of invalid size, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ if (p[2] > 128) {
+ log_ndisc("Bad prefix length, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ break;
+
+ case SD_NDISC_OPTION_MTU: {
+ uint32_t m;
+
+ if (has_mtu) {
+ log_ndisc("MTU option specified twice, ignoring.");
+ break;
+ }
+
+ if (length != 8) {
+ log_ndisc("MTU option of invalid size, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ 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) {
+ log_ndisc("Route information option of invalid size, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ if (p[2] > 128) {
+ log_ndisc("Bad route prefix length, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ break;
+
+ case SD_NDISC_OPTION_RDNSS:
+ if (length < 3*8 || (length % (2*8)) != 1*8) {
+ log_ndisc("RDNSS option has invalid size.");
+ return -EBADMSG;
+ }
+
+ break;
+
+ case SD_NDISC_OPTION_FLAGS_EXTENSION:
+
+ if (has_flag_extension) {
+ log_ndisc("Flags extension option specified twice, ignoring.");
+ break;
+ }
+
+ if (length < 1*8) {
+ log_ndisc("Flags extension option has invalid size.");
+ return -EBADMSG;
+ }
+
+ /* 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) {
+ log_ndisc("DNSSL option has invalid size.");
+ return -EBADMSG;
+ }
+
+ break;
+ }
+
+ p += length, left -= length;
+ }
+
+ rt->rindex = sizeof(struct nd_router_advert);
+ return 0;
+}
+
+_public_ 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;
+}
+
+_public_ int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret_flags, -EINVAL);
+
+ *ret_flags = rt->flags;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret_lifetime, -EINVAL);
+
+ *ret_lifetime = rt->lifetime;
+ return 0;
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+}
+
+_public_ int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) {
+ size_t length;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(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;
+ *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;
+}
+
+_public_ int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_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 = be32toh(ri->nd_opt_pi_valid_time);
+ return 0;
+}
+
+_public_ int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_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 = be32toh(pi->nd_opt_pi_preferred_time);
+ return 0;
+}
+
+_public_ 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("Invalid prefix length, ignoring prefix for stateless autoconfiguration.");
+ flags &= ~ND_OPT_PI_FLAG_AUTO;
+ }
+
+ *ret = flags;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
+ struct nd_opt_prefix_info *pi;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret_addr, -EINVAL);
+
+ r = get_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ *ret_addr = pi->nd_opt_pi_prefix;
+ return 0;
+}
+
+_public_ 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;
+}
+
+_public_ int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_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 = be32toh(*(uint32_t*) (ri + 4));
+ return 0;
+}
+
+_public_ int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret_addr, -EINVAL);
+
+ r = get_route_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ zero(*ret_addr);
+ memcpy(ret_addr, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8);
+
+ return 0;
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+
+ *ret = (ri[3] >> 3) & 3;
+ if (!IN_SET(*ret, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
+ *ret = SD_NDISC_PREFERENCE_MEDIUM;
+
+ 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;
+}
+
+_public_ 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;
+}
+
+_public_ int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_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 = be32toh(*(uint32_t*) (ri + 4));
+ 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;
+}
+
+_public_ 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 allocated = 0, 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, allocated, 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;
+}
+
+_public_ int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret_sec) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret_sec, -EINVAL);
+
+ r = get_dnssl_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret_sec = be32toh(*(uint32_t*) (ri + 4));
+ return 0;
+}
diff --git a/src/libsystemd-network/ndisc-router.h b/src/libsystemd-network/ndisc-router.h
new file mode 100644
index 0000000..cb3a564
--- /dev/null
+++ b/src/libsystemd-network/ndisc-router.h
@@ -0,0 +1,48 @@
+/* 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;
+ uint16_t lifetime;
+
+ uint8_t hop_limit;
+ uint32_t mtu;
+};
+
+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_router *rt);
diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c
new file mode 100644
index 0000000..e4a0741
--- /dev/null
+++ b/src/libsystemd-network/network-internal.c
@@ -0,0 +1,886 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <arpa/inet.h>
+#include <linux/if.h>
+#include <netinet/ether.h>
+
+#include "sd-id128.h"
+#include "sd-ndisc.h"
+
+#include "alloc-util.h"
+#include "arphrd-list.h"
+#include "condition.h"
+#include "conf-parser.h"
+#include "device-util.h"
+#include "dhcp-lease-internal.h"
+#include "env-util.h"
+#include "ether-addr-util.h"
+#include "hexdecoct.h"
+#include "log.h"
+#include "network-internal.h"
+#include "parse-util.h"
+#include "siphash24.h"
+#include "socket-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "utf8.h"
+#include "util.h"
+
+const char *net_get_name_persistent(sd_device *device) {
+ const char *name, *field;
+
+ assert(device);
+
+ /* fetch some persistent data unique (on this machine) to this device */
+ FOREACH_STRING(field, "ID_NET_NAME_ONBOARD", "ID_NET_NAME_SLOT", "ID_NET_NAME_PATH", "ID_NET_NAME_MAC")
+ if (sd_device_get_property_value(device, field, &name) >= 0)
+ return name;
+
+ return NULL;
+}
+
+#define HASH_KEY SD_ID128_MAKE(d3,1e,48,fa,90,fe,4b,4c,9d,af,d5,d7,a1,b1,2e,8a)
+
+int net_get_unique_predictable_data(sd_device *device, bool use_sysname, uint64_t *result) {
+ size_t l, sz = 0;
+ const char *name;
+ int r;
+ uint8_t *v;
+
+ assert(device);
+
+ /* net_get_name_persistent() will return one of the device names based on stable information about
+ * the device. If this is not available, we fall back to using the actual device name. */
+ name = net_get_name_persistent(device);
+ if (!name && use_sysname)
+ (void) sd_device_get_sysname(device, &name);
+ if (!name)
+ return log_device_debug_errno(device, SYNTHETIC_ERRNO(ENODATA),
+ "No stable identifying information found");
+
+ log_device_debug(device, "Using \"%s\" as stable identifying information", name);
+ l = strlen(name);
+ sz = sizeof(sd_id128_t) + l;
+ v = newa(uint8_t, sz);
+
+ /* Fetch some persistent data unique to this machine */
+ r = sd_id128_get_machine((sd_id128_t*) v);
+ if (r < 0)
+ return r;
+ memcpy(v + sizeof(sd_id128_t), name, l);
+
+ /* Let's hash the machine ID plus the device name. We use
+ * a fixed, but originally randomly created hash key here. */
+ *result = htole64(siphash24(v, sz, HASH_KEY.bytes));
+ return 0;
+}
+
+static bool net_condition_test_strv(char * const *patterns, const char *string) {
+ char * const *p;
+ bool match = false, has_positive_rule = false;
+
+ if (strv_isempty(patterns))
+ return true;
+
+ STRV_FOREACH(p, patterns) {
+ const char *q = *p;
+ bool invert;
+
+ invert = *q == '!';
+ q += invert;
+
+ if (!invert)
+ has_positive_rule = true;
+
+ if (string && fnmatch(q, string, 0) == 0) {
+ if (invert)
+ return false;
+ else
+ match = true;
+ }
+ }
+
+ return has_positive_rule ? match : true;
+}
+
+static bool net_condition_test_ifname(char * const *patterns, const char *ifname, char * const *alternative_names) {
+ if (net_condition_test_strv(patterns, ifname))
+ return true;
+
+ char * const *p;
+ STRV_FOREACH(p, alternative_names)
+ if (net_condition_test_strv(patterns, *p))
+ return true;
+
+ return false;
+}
+
+static int net_condition_test_property(char * const *match_property, sd_device *device) {
+ char * const *p;
+
+ if (strv_isempty(match_property))
+ return true;
+
+ STRV_FOREACH(p, match_property) {
+ _cleanup_free_ char *key = NULL;
+ const char *val, *dev_val;
+ bool invert, v;
+
+ invert = **p == '!';
+
+ val = strchr(*p + invert, '=');
+ if (!val)
+ return -EINVAL;
+
+ key = strndup(*p + invert, val - *p - invert);
+ if (!key)
+ return -ENOMEM;
+
+ val++;
+
+ v = device &&
+ sd_device_get_property_value(device, key, &dev_val) >= 0 &&
+ fnmatch(val, dev_val, 0) == 0;
+
+ if (invert ? v : !v)
+ return false;
+ }
+
+ return true;
+}
+
+static const char *const wifi_iftype_table[NL80211_IFTYPE_MAX+1] = {
+ [NL80211_IFTYPE_ADHOC] = "ad-hoc",
+ [NL80211_IFTYPE_STATION] = "station",
+ [NL80211_IFTYPE_AP] = "ap",
+ [NL80211_IFTYPE_AP_VLAN] = "ap-vlan",
+ [NL80211_IFTYPE_WDS] = "wds",
+ [NL80211_IFTYPE_MONITOR] = "monitor",
+ [NL80211_IFTYPE_MESH_POINT] = "mesh-point",
+ [NL80211_IFTYPE_P2P_CLIENT] = "p2p-client",
+ [NL80211_IFTYPE_P2P_GO] = "p2p-go",
+ [NL80211_IFTYPE_P2P_DEVICE] = "p2p-device",
+ [NL80211_IFTYPE_OCB] = "ocb",
+ [NL80211_IFTYPE_NAN] = "nan",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(wifi_iftype, enum nl80211_iftype);
+
+char *link_get_type_string(unsigned short iftype, sd_device *device) {
+ const char *t, *devtype;
+ char *p;
+
+ if (device &&
+ sd_device_get_devtype(device, &devtype) >= 0 &&
+ !isempty(devtype))
+ return strdup(devtype);
+
+ t = arphrd_to_name(iftype);
+ if (!t)
+ return NULL;
+
+ p = strdup(t);
+ if (!p)
+ return NULL;
+
+ ascii_strlower(p);
+ return p;
+}
+
+bool net_match_config(Set *match_mac,
+ Set *match_permanent_mac,
+ char * const *match_paths,
+ char * const *match_drivers,
+ char * const *match_iftypes,
+ char * const *match_names,
+ char * const *match_property,
+ char * const *match_wifi_iftype,
+ char * const *match_ssid,
+ Set *match_bssid,
+ sd_device *device,
+ const struct ether_addr *dev_mac,
+ const struct ether_addr *dev_permanent_mac,
+ const char *dev_driver,
+ unsigned short dev_iftype,
+ const char *dev_name,
+ char * const *alternative_names,
+ enum nl80211_iftype dev_wifi_iftype,
+ const char *dev_ssid,
+ const struct ether_addr *dev_bssid) {
+
+ _cleanup_free_ char *dev_iftype_str;
+ const char *dev_path = NULL;
+
+ dev_iftype_str = link_get_type_string(dev_iftype, device);
+
+ if (device) {
+ const char *mac_str;
+
+ (void) sd_device_get_property_value(device, "ID_PATH", &dev_path);
+ if (!dev_driver)
+ (void) sd_device_get_property_value(device, "ID_NET_DRIVER", &dev_driver);
+ if (!dev_name)
+ (void) sd_device_get_sysname(device, &dev_name);
+ if (!dev_mac &&
+ sd_device_get_sysattr_value(device, "address", &mac_str) >= 0)
+ dev_mac = ether_aton(mac_str);
+ }
+
+ if (match_mac && (!dev_mac || !set_contains(match_mac, dev_mac)))
+ return false;
+
+ if (match_permanent_mac &&
+ (!dev_permanent_mac ||
+ ether_addr_is_null(dev_permanent_mac) ||
+ !set_contains(match_permanent_mac, dev_permanent_mac)))
+ return false;
+
+ if (!net_condition_test_strv(match_paths, dev_path))
+ return false;
+
+ if (!net_condition_test_strv(match_drivers, dev_driver))
+ return false;
+
+ if (!net_condition_test_strv(match_iftypes, dev_iftype_str))
+ return false;
+
+ if (!net_condition_test_ifname(match_names, dev_name, alternative_names))
+ return false;
+
+ if (!net_condition_test_property(match_property, device))
+ return false;
+
+ if (!net_condition_test_strv(match_wifi_iftype, wifi_iftype_to_string(dev_wifi_iftype)))
+ return false;
+
+ if (!net_condition_test_strv(match_ssid, dev_ssid))
+ return false;
+
+ if (match_bssid && (!dev_bssid || !set_contains(match_bssid, dev_bssid)))
+ return false;
+
+ return true;
+}
+
+int config_parse_net_condition(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ConditionType cond = ltype;
+ Condition **list = data, *c;
+ bool negate;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (isempty(rvalue)) {
+ *list = condition_free_list_type(*list, cond);
+ return 0;
+ }
+
+ negate = rvalue[0] == '!';
+ if (negate)
+ rvalue++;
+
+ c = condition_new(cond, rvalue, false, negate);
+ if (!c)
+ return log_oom();
+
+ /* Drop previous assignment. */
+ *list = condition_free_list_type(*list, cond);
+
+ LIST_PREPEND(conditions, *list, c);
+ return 0;
+}
+
+int config_parse_match_strv(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ const char *p = rvalue;
+ char ***sv = data;
+ bool invert;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (isempty(rvalue)) {
+ *sv = strv_free(*sv);
+ return 0;
+ }
+
+ invert = *p == '!';
+ p += invert;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL, *k = NULL;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
+ if (r == 0)
+ return 0;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (invert) {
+ k = strjoin("!", word);
+ if (!k)
+ return log_oom();
+ } else
+ k = TAKE_PTR(word);
+
+ r = strv_consume(sv, TAKE_PTR(k));
+ if (r < 0)
+ return log_oom();
+ }
+}
+
+int config_parse_match_ifnames(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ const char *p = rvalue;
+ char ***sv = data;
+ bool invert;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ invert = *p == '!';
+ p += invert;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL, *k = NULL;
+
+ r = extract_first_word(&p, &word, NULL, 0);
+ if (r == 0)
+ return 0;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Failed to parse interface name list: %s", rvalue);
+ return 0;
+ }
+
+ if (!ifname_valid_full(word, ltype)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Interface name is not valid or too long, ignoring assignment: %s", word);
+ continue;
+ }
+
+ if (invert) {
+ k = strjoin("!", word);
+ if (!k)
+ return log_oom();
+ } else
+ k = TAKE_PTR(word);
+
+ r = strv_consume(sv, TAKE_PTR(k));
+ if (r < 0)
+ return log_oom();
+ }
+}
+
+int config_parse_match_property(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ const char *p = rvalue;
+ char ***sv = data;
+ bool invert;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ invert = *p == '!';
+ p += invert;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL, *k = NULL;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
+ if (r == 0)
+ return 0;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Invalid syntax, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (!env_assignment_is_valid(word)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Invalid property or value, ignoring assignment: %s", word);
+ continue;
+ }
+
+ if (invert) {
+ k = strjoin("!", word);
+ if (!k)
+ return log_oom();
+ } else
+ k = TAKE_PTR(word);
+
+ r = strv_consume(sv, TAKE_PTR(k));
+ if (r < 0)
+ return log_oom();
+ }
+}
+
+int config_parse_ifalias(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char **s = data;
+ _cleanup_free_ char *n = NULL;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ n = strdup(rvalue);
+ if (!n)
+ return log_oom();
+
+ if (!ascii_is_valid(n) || strlen(n) >= IFALIASZ) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Interface alias is not ASCII clean or is too long, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ if (isempty(n))
+ *s = mfree(*s);
+ else
+ free_and_replace(*s, n);
+
+ return 0;
+}
+
+int config_parse_hwaddr(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_free_ struct ether_addr *n = NULL;
+ struct ether_addr **hwaddr = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ n = new0(struct ether_addr, 1);
+ if (!n)
+ return log_oom();
+
+ r = ether_addr_from_string(rvalue, n);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Not a valid MAC address, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ free_and_replace(*hwaddr, n);
+
+ return 0;
+}
+
+int config_parse_hwaddrs(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_set_free_free_ Set *s = NULL;
+ const char *p = rvalue;
+ Set **hwaddrs = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ *hwaddrs = set_free_free(*hwaddrs);
+ return 0;
+ }
+
+ s = set_new(&ether_addr_hash_ops);
+ if (!s)
+ return log_oom();
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ _cleanup_free_ struct ether_addr *n = NULL;
+
+ r = extract_first_word(&p, &word, NULL, 0);
+ if (r == 0)
+ break;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ n = new(struct ether_addr, 1);
+ if (!n)
+ return log_oom();
+
+ r = ether_addr_from_string(word, n);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Not a valid MAC address, ignoring: %s", word);
+ continue;
+ }
+
+ r = set_put(s, n);
+ if (r < 0)
+ return log_oom();
+ if (r > 0)
+ n = NULL; /* avoid cleanup */
+ }
+
+ r = set_ensure_allocated(hwaddrs, &ether_addr_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ r = set_move(*hwaddrs, s);
+ if (r < 0)
+ return log_oom();
+
+ return 0;
+}
+
+int config_parse_bridge_port_priority(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ uint16_t i;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = safe_atou16(rvalue, &i);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse bridge port priority, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (i > LINK_BRIDGE_PORT_PRIORITY_MAX) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Bridge port priority is larger than maximum %u, ignoring: %s", LINK_BRIDGE_PORT_PRIORITY_MAX, rvalue);
+ return 0;
+ }
+
+ *((uint16_t *)data) = i;
+
+ return 0;
+}
+
+size_t serialize_in_addrs(FILE *f,
+ const struct in_addr *addresses,
+ size_t size,
+ bool *with_leading_space,
+ bool (*predicate)(const struct in_addr *addr)) {
+ assert(f);
+ assert(addresses);
+
+ size_t count = 0;
+ bool _space = false;
+ if (!with_leading_space)
+ with_leading_space = &_space;
+
+ for (size_t i = 0; i < size; i++) {
+ char sbuf[INET_ADDRSTRLEN];
+
+ if (predicate && !predicate(&addresses[i]))
+ continue;
+
+ if (*with_leading_space)
+ fputc(' ', f);
+ fputs(inet_ntop(AF_INET, &addresses[i], sbuf, sizeof(sbuf)), f);
+ count++;
+ *with_leading_space = true;
+ }
+
+ return count;
+}
+
+int deserialize_in_addrs(struct in_addr **ret, const char *string) {
+ _cleanup_free_ struct in_addr *addresses = NULL;
+ int size = 0;
+
+ assert(ret);
+ assert(string);
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ struct in_addr *new_addresses;
+ int r;
+
+ r = extract_first_word(&string, &word, NULL, 0);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ new_addresses = reallocarray(addresses, size + 1, sizeof(struct in_addr));
+ if (!new_addresses)
+ return -ENOMEM;
+ else
+ addresses = new_addresses;
+
+ r = inet_pton(AF_INET, word, &(addresses[size]));
+ if (r <= 0)
+ continue;
+
+ size++;
+ }
+
+ *ret = size > 0 ? TAKE_PTR(addresses) : NULL;
+
+ return size;
+}
+
+void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size, bool *with_leading_space) {
+ assert(f);
+ assert(addresses);
+ assert(size);
+
+ bool _space = false;
+ if (!with_leading_space)
+ with_leading_space = &_space;
+
+ for (size_t i = 0; i < size; i++) {
+ char buffer[INET6_ADDRSTRLEN];
+
+ if (*with_leading_space)
+ fputc(' ', f);
+ fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f);
+ *with_leading_space = true;
+ }
+}
+
+int deserialize_in6_addrs(struct in6_addr **ret, const char *string) {
+ _cleanup_free_ struct in6_addr *addresses = NULL;
+ int size = 0;
+
+ assert(ret);
+ assert(string);
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ struct in6_addr *new_addresses;
+ int r;
+
+ r = extract_first_word(&string, &word, NULL, 0);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ new_addresses = reallocarray(addresses, size + 1, sizeof(struct in6_addr));
+ if (!new_addresses)
+ return -ENOMEM;
+ else
+ addresses = new_addresses;
+
+ r = inet_pton(AF_INET6, word, &(addresses[size]));
+ if (r <= 0)
+ continue;
+
+ size++;
+ }
+
+ *ret = TAKE_PTR(addresses);
+
+ return size;
+}
+
+void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) {
+ assert(f);
+ assert(key);
+ assert(routes);
+ assert(size);
+
+ fprintf(f, "%s=", key);
+
+ for (size_t i = 0; i < size; i++) {
+ char sbuf[INET_ADDRSTRLEN];
+ struct in_addr dest, gw;
+ uint8_t length;
+
+ assert_se(sd_dhcp_route_get_destination(routes[i], &dest) >= 0);
+ assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0);
+ assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0);
+
+ fprintf(f, "%s/%" PRIu8, inet_ntop(AF_INET, &dest, sbuf, sizeof sbuf), length);
+ fprintf(f, ",%s%s", inet_ntop(AF_INET, &gw, sbuf, sizeof sbuf), i < size - 1 ? " ": "");
+ }
+
+ fputs("\n", f);
+}
+
+int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string) {
+ _cleanup_free_ struct sd_dhcp_route *routes = NULL;
+ size_t size = 0, allocated = 0;
+
+ assert(ret);
+ assert(ret_size);
+ assert(ret_allocated);
+ assert(string);
+
+ /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ char *tok, *tok_end;
+ unsigned n;
+ int r;
+
+ r = extract_first_word(&string, &word, NULL, 0);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (!GREEDY_REALLOC(routes, allocated, size + 1))
+ return -ENOMEM;
+
+ tok = word;
+
+ /* get the subnet */
+ tok_end = strchr(tok, '/');
+ if (!tok_end)
+ continue;
+ *tok_end = '\0';
+
+ r = inet_aton(tok, &routes[size].dst_addr);
+ if (r == 0)
+ continue;
+
+ tok = tok_end + 1;
+
+ /* get the prefixlen */
+ tok_end = strchr(tok, ',');
+ if (!tok_end)
+ continue;
+
+ *tok_end = '\0';
+
+ r = safe_atou(tok, &n);
+ if (r < 0 || n > 32)
+ continue;
+
+ routes[size].dst_prefixlen = (uint8_t) n;
+ tok = tok_end + 1;
+
+ /* get the gateway */
+ r = inet_aton(tok, &routes[size].gw_addr);
+ if (r == 0)
+ continue;
+
+ size++;
+ }
+
+ *ret_size = size;
+ *ret_allocated = allocated;
+ *ret = TAKE_PTR(routes);
+
+ return 0;
+}
+
+int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size) {
+ _cleanup_free_ char *hex_buf = NULL;
+
+ assert(f);
+ assert(key);
+ assert(data);
+
+ hex_buf = hexmem(data, size);
+ if (!hex_buf)
+ return -ENOMEM;
+
+ fprintf(f, "%s=%s\n", key, hex_buf);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h
new file mode 100644
index 0000000..5dae5ab
--- /dev/null
+++ b/src/libsystemd-network/network-internal.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <linux/nl80211.h>
+#include <stdbool.h>
+
+#include "sd-device.h"
+#include "sd-dhcp-lease.h"
+
+#include "conf-parser.h"
+#include "set.h"
+#include "strv.h"
+
+#define LINK_BRIDGE_PORT_PRIORITY_INVALID 128
+#define LINK_BRIDGE_PORT_PRIORITY_MAX 63
+
+char *link_get_type_string(unsigned short iftype, sd_device *device);
+bool net_match_config(Set *match_mac,
+ Set *match_permanent_mac,
+ char * const *match_paths,
+ char * const *match_drivers,
+ char * const *match_iftypes,
+ char * const *match_names,
+ char * const *match_property,
+ char * const *match_wifi_iftype,
+ char * const *match_ssid,
+ Set *match_bssid,
+ sd_device *device,
+ const struct ether_addr *dev_mac,
+ const struct ether_addr *dev_permanent_mac,
+ const char *dev_driver,
+ unsigned short dev_iftype,
+ const char *dev_name,
+ char * const *alternative_names,
+ enum nl80211_iftype dev_wifi_iftype,
+ const char *dev_ssid,
+ const struct ether_addr *dev_bssid);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_net_condition);
+CONFIG_PARSER_PROTOTYPE(config_parse_hwaddr);
+CONFIG_PARSER_PROTOTYPE(config_parse_hwaddrs);
+CONFIG_PARSER_PROTOTYPE(config_parse_match_strv);
+CONFIG_PARSER_PROTOTYPE(config_parse_match_ifnames);
+CONFIG_PARSER_PROTOTYPE(config_parse_match_property);
+CONFIG_PARSER_PROTOTYPE(config_parse_ifalias);
+CONFIG_PARSER_PROTOTYPE(config_parse_bridge_port_priority);
+
+int net_get_unique_predictable_data(sd_device *device, bool use_sysname, uint64_t *result);
+const char *net_get_name_persistent(sd_device *device);
+
+size_t serialize_in_addrs(FILE *f,
+ const struct in_addr *addresses,
+ size_t size,
+ bool *with_leading_space,
+ bool (*predicate)(const struct in_addr *addr));
+int deserialize_in_addrs(struct in_addr **addresses, const char *string);
+void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses,
+ size_t size,
+ bool *with_leading_space);
+int deserialize_in6_addrs(struct in6_addr **addresses, const char *string);
+
+/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */
+struct sd_dhcp_route;
+struct sd_dhcp_lease;
+
+void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size);
+int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string);
+
+/* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */
+int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size);
+
+int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file);
+int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file);
diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h
new file mode 100644
index 0000000..3dbeffe
--- /dev/null
+++ b/src/libsystemd-network/radv-internal.h
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2017 Intel Corporation. All rights reserved.
+***/
+
+#include "sd-radv.h"
+
+#include "log.h"
+#include "list.h"
+#include "sparse-endian.h"
+
+assert_cc(SD_RADV_DEFAULT_MIN_TIMEOUT_USEC <= SD_RADV_DEFAULT_MAX_TIMEOUT_USEC);
+
+#define SD_RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC (16*USEC_PER_SEC)
+#define SD_RADV_MAX_INITIAL_RTR_ADVERTISEMENTS 3
+#define SD_RADV_MAX_FINAL_RTR_ADVERTISEMENTS 3
+#define SD_RADV_MIN_DELAY_BETWEEN_RAS 3
+#define SD_RADV_MAX_RA_DELAY_TIME_USEC (500*USEC_PER_MSEC)
+
+#define SD_RADV_OPT_ROUTE_INFORMATION 24
+#define SD_RADV_OPT_RDNSS 25
+#define SD_RADV_OPT_DNSSL 31
+
+enum RAdvState {
+ SD_RADV_STATE_IDLE = 0,
+ SD_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;
+
+ sd_event *event;
+ int event_priority;
+
+ struct ether_addr mac_addr;
+ uint8_t hop_limit;
+ uint8_t flags;
+ uint32_t mtu;
+ uint16_t lifetime;
+
+ 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);
+
+ size_t n_rdnss;
+ struct sd_radv_opt_dns *rdnss;
+ struct sd_radv_opt_dns *dnssl;
+};
+
+#define radv_prefix_opt__contents { \
+ uint8_t type; \
+ uint8_t length; \
+ uint8_t prefixlen; \
+ uint8_t flags; \
+ be32_t valid_lifetime; \
+ be32_t preferred_lifetime; \
+ 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);
+
+ 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);
+};
+
+#define log_radv_full(level, error, fmt, ...) log_internal(level, error, PROJECT_FILE, __LINE__, __func__, "RADV: " fmt, ##__VA_ARGS__)
+#define log_radv_errno(error, fmt, ...) log_radv_full(LOG_DEBUG, error, fmt, ##__VA_ARGS__)
+#define log_radv(fmt, ...) log_radv_errno(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..d472fcd
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp-client.c
@@ -0,0 +1,2267 @@
+/* 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 "dhcp-identifier.h"
+#include "dhcp-internal.h"
+#include "dhcp-lease-internal.h"
+#include "dhcp-protocol.h"
+#include "dns-domain.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "hostname-util.h"
+#include "io-util.h"
+#include "memory-util.h"
+#include "random-util.h"
+#include "set.h"
+#include "sort-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "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)
+
+typedef struct sd_dhcp_client_id {
+ uint8_t type;
+ union {
+ struct {
+ /* 0: Generic (non-LL) (RFC 2132) */
+ uint8_t data[MAX_CLIENT_ID_LEN];
+ } _packed_ gen;
+ struct {
+ /* 1: Ethernet Link-Layer (RFC 2132) */
+ uint8_t haddr[ETH_ALEN];
+ } _packed_ eth;
+ struct {
+ /* 2 - 254: ARP/Link-Layer (RFC 2132) */
+ uint8_t haddr[0];
+ } _packed_ ll;
+ struct {
+ /* 255: Node-specific (RFC 4361) */
+ be32_t iaid;
+ struct duid duid;
+ } _packed_ ns;
+ struct {
+ uint8_t data[MAX_CLIENT_ID_LEN];
+ } _packed_ raw;
+ };
+} _packed_ sd_dhcp_client_id;
+
+struct sd_dhcp_client {
+ unsigned n_ref;
+
+ DHCPState state;
+ sd_event *event;
+ int event_priority;
+ sd_event_source *timeout_resend;
+ int ifindex;
+ int fd;
+ uint16_t port;
+ union sockaddr_union link;
+ sd_event_source *receive_message;
+ bool request_broadcast;
+ Set *req_opts;
+ bool anonymize;
+ be32_t last_addr;
+ uint8_t mac_addr[MAX_MAC_ADDR_LEN];
+ size_t mac_addr_len;
+ uint8_t bcast_addr[MAX_MAC_ADDR_LEN];
+ size_t bcast_addr_len;
+ uint16_t arp_type;
+ sd_dhcp_client_id client_id;
+ size_t client_id_len;
+ char *hostname;
+ char *vendor_class_identifier;
+ char *mudurl;
+ char **user_class;
+ uint32_t mtu;
+ uint32_t fallback_lease_lifetime;
+ uint32_t xid;
+ usec_t start_time;
+ uint64_t attempt;
+ uint64_t max_attempts;
+ OrderedHashmap *extra_options;
+ OrderedHashmap *vendor_options;
+ usec_t request_sent;
+ sd_event_source *timeout_t1;
+ sd_event_source *timeout_t2;
+ sd_event_source *timeout_expire;
+ sd_dhcp_client_callback_t callback;
+ void *userdata;
+ sd_dhcp_lease *lease;
+ usec_t start_delay;
+ int ip_service_type;
+};
+
+static const uint8_t default_req_opts[] = {
+ SD_DHCP_OPTION_SUBNET_MASK,
+ SD_DHCP_OPTION_ROUTER,
+ SD_DHCP_OPTION_HOST_NAME,
+ SD_DHCP_OPTION_DOMAIN_NAME,
+ SD_DHCP_OPTION_DOMAIN_NAME_SERVER,
+};
+
+/* RFC7844 section 3:
+ MAY contain the Parameter Request List option.
+ RFC7844 section 3.6:
+ The client intending to protect its privacy SHOULD only request a
+ minimal number of options in the PRL and SHOULD also randomly shuffle
+ the ordering of option codes in the PRL. If this random ordering
+ cannot be implemented, the client MAY order the option codes in the
+ PRL by option code number (lowest to highest).
+*/
+/* NOTE: using PRL options that Windows 10 RFC7844 implementation uses */
+static const uint8_t default_req_opts_anonymize[] = {
+ SD_DHCP_OPTION_SUBNET_MASK, /* 1 */
+ SD_DHCP_OPTION_ROUTER, /* 3 */
+ SD_DHCP_OPTION_DOMAIN_NAME_SERVER, /* 6 */
+ SD_DHCP_OPTION_DOMAIN_NAME, /* 15 */
+ SD_DHCP_OPTION_ROUTER_DISCOVER, /* 31 */
+ SD_DHCP_OPTION_STATIC_ROUTE, /* 33 */
+ SD_DHCP_OPTION_VENDOR_SPECIFIC, /* 43 */
+ SD_DHCP_OPTION_NETBIOS_NAMESERVER, /* 44 */
+ SD_DHCP_OPTION_NETBIOS_NODETYPE, /* 46 */
+ SD_DHCP_OPTION_NETBIOS_SCOPE, /* 47 */
+ SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, /* 121 */
+ SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, /* 249 */
+ SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */
+};
+
+static int client_receive_message_raw(
+ sd_event_source *s,
+ int fd,
+ uint32_t revents,
+ void *userdata);
+static int client_receive_message_udp(
+ sd_event_source *s,
+ int fd,
+ uint32_t revents,
+ void *userdata);
+static void client_stop(sd_dhcp_client *client, int error);
+
+int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret) {
+ const sd_dhcp_client_id *client_id = data;
+ _cleanup_free_ char *t = NULL;
+ int r = 0;
+
+ assert_return(data, -EINVAL);
+ assert_return(len >= 1, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ len -= 1;
+ if (len > MAX_CLIENT_ID_LEN)
+ return -EINVAL;
+
+ switch (client_id->type) {
+ case 0:
+ if (utf8_is_printable((char *) client_id->gen.data, len))
+ r = asprintf(&t, "%.*s", (int) len, client_id->gen.data);
+ else
+ r = asprintf(&t, "DATA");
+ break;
+ case 1:
+ if (len != sizeof_field(sd_dhcp_client_id, eth))
+ return -EINVAL;
+
+ r = asprintf(&t, "%x:%x:%x:%x:%x:%x",
+ client_id->eth.haddr[0],
+ client_id->eth.haddr[1],
+ client_id->eth.haddr[2],
+ client_id->eth.haddr[3],
+ client_id->eth.haddr[4],
+ client_id->eth.haddr[5]);
+ break;
+ case 2 ... 254:
+ r = asprintf(&t, "ARP/LL");
+ break;
+ case 255:
+ if (len < 6)
+ return -EINVAL;
+
+ uint32_t iaid = be32toh(client_id->ns.iaid);
+ uint16_t duid_type = be16toh(client_id->ns.duid.type);
+ if (dhcp_validate_duid_len(duid_type, len - 6, true) < 0)
+ return -EINVAL;
+
+ r = asprintf(&t, "IAID:0x%x/DUID", iaid);
+ break;
+ }
+
+ if (r < 0)
+ return -ENOMEM;
+ *ret = TAKE_PTR(t);
+ return 0;
+}
+
+int sd_dhcp_client_set_callback(
+ sd_dhcp_client *client,
+ sd_dhcp_client_callback_t cb,
+ void *userdata) {
+
+ assert_return(client, -EINVAL);
+
+ client->callback = cb;
+ client->userdata = userdata;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) {
+ assert_return(client, -EINVAL);
+
+ client->request_broadcast = !!broadcast;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) {
+ assert_return(client, -EINVAL);
+ assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY);
+
+ switch(option) {
+
+ case SD_DHCP_OPTION_PAD:
+ case SD_DHCP_OPTION_OVERLOAD:
+ case SD_DHCP_OPTION_MESSAGE_TYPE:
+ case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST:
+ case SD_DHCP_OPTION_END:
+ return -EINVAL;
+
+ default:
+ break;
+ }
+
+ return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option));
+}
+
+int sd_dhcp_client_set_request_address(
+ sd_dhcp_client *client,
+ const struct in_addr *last_addr) {
+
+ assert_return(client, -EINVAL);
+ assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY);
+
+ if (last_addr)
+ client->last_addr = last_addr->s_addr;
+ else
+ client->last_addr = INADDR_ANY;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_ifindex(sd_dhcp_client *client, int ifindex) {
+
+ assert_return(client, -EINVAL);
+ assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY);
+ assert_return(ifindex > 0, -EINVAL);
+
+ client->ifindex = ifindex;
+ return 0;
+}
+
+int sd_dhcp_client_set_mac(
+ sd_dhcp_client *client,
+ const uint8_t *addr,
+ const uint8_t *bcast_addr,
+ size_t addr_len,
+ uint16_t arp_type) {
+
+ DHCP_CLIENT_DONT_DESTROY(client);
+ bool need_restart = false;
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(addr, -EINVAL);
+ assert_return(addr_len > 0 && addr_len <= MAX_MAC_ADDR_LEN, -EINVAL);
+ assert_return(arp_type > 0, -EINVAL);
+
+ if (arp_type == ARPHRD_ETHER)
+ assert_return(addr_len == ETH_ALEN, -EINVAL);
+ else if (arp_type == ARPHRD_INFINIBAND)
+ assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
+ else
+ return -EINVAL;
+
+ if (client->mac_addr_len == addr_len &&
+ memcmp(&client->mac_addr, addr, addr_len) == 0 &&
+ (client->bcast_addr_len > 0) == !!bcast_addr &&
+ (!bcast_addr || memcmp(&client->bcast_addr, bcast_addr, addr_len) == 0))
+ return 0;
+
+ if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
+ log_dhcp_client(client, "Changing MAC address on running DHCP client, restarting");
+ need_restart = true;
+ client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+ }
+
+ memcpy(&client->mac_addr, addr, addr_len);
+ client->mac_addr_len = addr_len;
+ client->arp_type = arp_type;
+ client->bcast_addr_len = 0;
+
+ if (bcast_addr) {
+ memcpy(&client->bcast_addr, bcast_addr, addr_len);
+ client->bcast_addr_len = addr_len;
+ }
+
+ if (need_restart && client->state != DHCP_STATE_STOPPED) {
+ r = sd_dhcp_client_start(client);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m");
+ }
+
+ return 0;
+}
+
+int sd_dhcp_client_get_client_id(
+ sd_dhcp_client *client,
+ uint8_t *type,
+ const uint8_t **data,
+ size_t *data_len) {
+
+ assert_return(client, -EINVAL);
+ assert_return(type, -EINVAL);
+ assert_return(data, -EINVAL);
+ assert_return(data_len, -EINVAL);
+
+ *type = 0;
+ *data = NULL;
+ *data_len = 0;
+ if (client->client_id_len) {
+ *type = client->client_id.type;
+ *data = client->client_id.raw.data;
+ *data_len = client->client_id_len - sizeof(client->client_id.type);
+ }
+
+ return 0;
+}
+
+int sd_dhcp_client_set_client_id(
+ sd_dhcp_client *client,
+ uint8_t type,
+ const uint8_t *data,
+ size_t data_len) {
+
+ DHCP_CLIENT_DONT_DESTROY(client);
+ bool need_restart = false;
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(data, -EINVAL);
+ assert_return(data_len > 0 && data_len <= MAX_CLIENT_ID_LEN, -EINVAL);
+
+ if (client->client_id_len == data_len + sizeof(client->client_id.type) &&
+ client->client_id.type == type &&
+ memcmp(&client->client_id.raw.data, data, data_len) == 0)
+ return 0;
+
+ /* For hardware types, log debug message about unexpected data length.
+ *
+ * Note that infiniband's INFINIBAND_ALEN is 20 bytes long, but only
+ * the last 8 bytes of the address are stable and suitable to put into
+ * the client-id. The caller is advised to account for that. */
+ if ((type == ARPHRD_ETHER && data_len != ETH_ALEN) ||
+ (type == ARPHRD_INFINIBAND && data_len != 8))
+ log_dhcp_client(client, "Changing client ID to hardware type %u with "
+ "unexpected address length %zu",
+ type, data_len);
+
+ if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
+ log_dhcp_client(client, "Changing client ID on running DHCP "
+ "client, restarting");
+ need_restart = true;
+ client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+ }
+
+ client->client_id.type = type;
+ memcpy(&client->client_id.raw.data, data, data_len);
+ client->client_id_len = data_len + sizeof (client->client_id.type);
+
+ if (need_restart && client->state != DHCP_STATE_STOPPED) {
+ r = sd_dhcp_client_start(client);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m");
+ }
+
+ return 0;
+}
+
+/**
+ * Sets IAID and DUID. If duid is non-null, the DUID is set to duid_type + duid
+ * without further modification. Otherwise, if duid_type is supported, DUID
+ * is set based on that type. Otherwise, an error is returned.
+ */
+static int dhcp_client_set_iaid_duid_internal(
+ sd_dhcp_client *client,
+ bool iaid_append,
+ bool iaid_set,
+ uint32_t iaid,
+ uint16_t duid_type,
+ const void *duid,
+ size_t duid_len,
+ usec_t llt_time) {
+
+ DHCP_CLIENT_DONT_DESTROY(client);
+ int r;
+ size_t len;
+
+ assert_return(client, -EINVAL);
+ assert_return(duid_len == 0 || duid, -EINVAL);
+
+ if (duid) {
+ r = dhcp_validate_duid_len(duid_type, duid_len, true);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to validate length of DUID: %m");
+ }
+
+ zero(client->client_id);
+ client->client_id.type = 255;
+
+ if (iaid_append) {
+ if (iaid_set)
+ client->client_id.ns.iaid = htobe32(iaid);
+ else {
+ r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr,
+ client->mac_addr_len,
+ true,
+ &client->client_id.ns.iaid);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to set IAID: %m");
+ }
+ }
+
+ if (duid) {
+ client->client_id.ns.duid.type = htobe16(duid_type);
+ memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len);
+ len = sizeof(client->client_id.ns.duid.type) + duid_len;
+ } else
+ switch (duid_type) {
+ case DUID_TYPE_LLT:
+ if (client->mac_addr_len == 0)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set.");
+
+ r = dhcp_identifier_set_duid_llt(&client->client_id.ns.duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to set DUID-LLT: %m");
+ break;
+ case DUID_TYPE_EN:
+ r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to set DUID-EN: %m");
+ break;
+ case DUID_TYPE_LL:
+ if (client->mac_addr_len == 0)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set.");
+
+ r = dhcp_identifier_set_duid_ll(&client->client_id.ns.duid, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to set DUID-LL: %m");
+ break;
+ case DUID_TYPE_UUID:
+ r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to set DUID-UUID: %m");
+ break;
+ default:
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type");
+ }
+
+ client->client_id_len = sizeof(client->client_id.type) + len +
+ (iaid_append ? sizeof(client->client_id.ns.iaid) : 0);
+
+ if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
+ log_dhcp_client(client, "Configured %sDUID, restarting.", iaid_append ? "IAID+" : "");
+ client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+ r = sd_dhcp_client_start(client);
+ if (r < 0)
+ return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m");
+ }
+
+ return 0;
+}
+
+int sd_dhcp_client_set_iaid_duid(
+ sd_dhcp_client *client,
+ bool iaid_set,
+ uint32_t iaid,
+ uint16_t duid_type,
+ const void *duid,
+ size_t duid_len) {
+ return dhcp_client_set_iaid_duid_internal(client, true, iaid_set, iaid, duid_type, duid, duid_len, 0);
+}
+
+int sd_dhcp_client_set_iaid_duid_llt(
+ sd_dhcp_client *client,
+ bool iaid_set,
+ uint32_t iaid,
+ usec_t llt_time) {
+ return dhcp_client_set_iaid_duid_internal(client, true, iaid_set, iaid, DUID_TYPE_LLT, NULL, 0, llt_time);
+}
+
+int sd_dhcp_client_set_duid(
+ sd_dhcp_client *client,
+ uint16_t duid_type,
+ const void *duid,
+ size_t duid_len) {
+ return dhcp_client_set_iaid_duid_internal(client, false, false, 0, duid_type, duid, duid_len, 0);
+}
+
+int sd_dhcp_client_set_duid_llt(
+ sd_dhcp_client *client,
+ usec_t llt_time) {
+ return dhcp_client_set_iaid_duid_internal(client, false, false, 0, DUID_TYPE_LLT, NULL, 0, llt_time);
+}
+
+int sd_dhcp_client_set_hostname(
+ sd_dhcp_client *client,
+ const char *hostname) {
+
+ assert_return(client, -EINVAL);
+
+ /* Make sure hostnames qualify as DNS and as Linux hostnames */
+ if (hostname &&
+ !(hostname_is_valid(hostname, false) && dns_name_is_valid(hostname) > 0))
+ return -EINVAL;
+
+ return free_and_strdup(&client->hostname, hostname);
+}
+
+int sd_dhcp_client_set_vendor_class_identifier(
+ sd_dhcp_client *client,
+ const char *vci) {
+
+ assert_return(client, -EINVAL);
+
+ return free_and_strdup(&client->vendor_class_identifier, vci);
+}
+
+int sd_dhcp_client_set_mud_url(
+ sd_dhcp_client *client,
+ const char *mudurl) {
+
+ assert_return(client, -EINVAL);
+ assert_return(mudurl, -EINVAL);
+ assert_return(strlen(mudurl) <= 255, -EINVAL);
+ assert_return(http_url_is_valid(mudurl), -EINVAL);
+
+ return free_and_strdup(&client->mudurl, mudurl);
+}
+
+int sd_dhcp_client_set_user_class(
+ sd_dhcp_client *client,
+ char * const *user_class) {
+
+ char * const *p;
+ char **s = NULL;
+
+ assert_return(client, -EINVAL);
+ 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);
+
+ client->port = port;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) {
+ assert_return(client, -EINVAL);
+ assert_return(mtu >= DHCP_DEFAULT_MIN_SIZE, -ERANGE);
+
+ client->mtu = mtu;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempts) {
+ assert_return(client, -EINVAL);
+
+ client->max_attempts = max_attempts;
+
+ return 0;
+}
+
+int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(v, -EINVAL);
+
+ r = ordered_hashmap_ensure_allocated(&client->extra_options, &dhcp_option_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(client->extra_options, UINT_TO_PTR(v->option), v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp_option_ref(v);
+ return 0;
+}
+
+int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(v, -EINVAL);
+
+ r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp_option_hash_ops);
+ if (r < 0)
+ return -ENOMEM;
+
+ r = ordered_hashmap_put(client->vendor_options, v, v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp_option_ref(v);
+
+ return 1;
+}
+
+int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
+ assert_return(client, -EINVAL);
+
+ if (!IN_SET(client->state, DHCP_STATE_SELECTING, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING))
+ return -EADDRNOTAVAIL;
+
+ if (ret)
+ *ret = client->lease;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_service_type(sd_dhcp_client *client, int type) {
+ assert_return(client, -EINVAL);
+
+ client->ip_service_type = type;
+
+ return 0;
+}
+
+int sd_dhcp_client_set_fallback_lease_lifetime(sd_dhcp_client *client, uint32_t fallback_lease_lifetime) {
+ assert_return(client, -EINVAL);
+ assert_return(fallback_lease_lifetime > 0, -EINVAL);
+
+ client->fallback_lease_lifetime = fallback_lease_lifetime;
+
+ return 0;
+}
+
+static int client_notify(sd_dhcp_client *client, int event) {
+ assert(client);
+
+ if (client->callback)
+ return client->callback(client, event, client->userdata);
+
+ return 0;
+}
+
+static int client_initialize(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+
+ client->receive_message = sd_event_source_unref(client->receive_message);
+
+ client->fd = safe_close(client->fd);
+
+ (void) event_source_disable(client->timeout_resend);
+ (void) event_source_disable(client->timeout_t1);
+ (void) event_source_disable(client->timeout_t2);
+ (void) event_source_disable(client->timeout_expire);
+
+ client->attempt = 0;
+
+ client->state = DHCP_STATE_INIT;
+ client->xid = 0;
+
+ client->lease = sd_dhcp_lease_unref(client->lease);
+
+ return 0;
+}
+
+static void client_stop(sd_dhcp_client *client, int error) {
+ assert(client);
+
+ if (error < 0)
+ log_dhcp_client_errno(client, error, "STOPPED: %m");
+ else if (error == SD_DHCP_CLIENT_EVENT_STOP)
+ log_dhcp_client(client, "STOPPED");
+ else
+ log_dhcp_client(client, "STOPPED: Unknown event");
+
+ client_notify(client, error);
+
+ client_initialize(client);
+}
+
+static int cmp_uint8(const uint8_t *a, const uint8_t *b) {
+ return CMP(*a, *b);
+}
+
+static int client_message_init(
+ sd_dhcp_client *client,
+ DHCPPacket **ret,
+ uint8_t type,
+ size_t *_optlen,
+ size_t *_optoffset) {
+
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ size_t optlen, optoffset, size;
+ be16_t max_size;
+ usec_t time_now;
+ uint16_t secs;
+ int r;
+
+ assert(client);
+ assert(client->start_time);
+ assert(ret);
+ assert(_optlen);
+ assert(_optoffset);
+ assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE));
+
+ optlen = DHCP_MIN_OPTIONS_SIZE;
+ size = sizeof(DHCPPacket) + optlen;
+
+ packet = malloc0(size);
+ if (!packet)
+ return -ENOMEM;
+
+ r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, type,
+ client->arp_type, optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers
+ refuse to issue an DHCP lease if 'secs' is set to zero */
+ r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return r;
+ assert(time_now >= client->start_time);
+
+ /* seconds between sending first and last DISCOVER
+ * must always be strictly positive to deal with broken servers */
+ secs = ((time_now - client->start_time) / USEC_PER_SEC) ? : 1;
+ packet->dhcp.secs = htobe16(secs);
+
+ /* RFC2132 section 4.1
+ A client that cannot receive unicast IP datagrams until its protocol
+ software has been configured with an IP address SHOULD set the
+ BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or
+ DHCPREQUEST messages that client sends. The BROADCAST bit will
+ provide a hint to the DHCP server and BOOTP relay agent to broadcast
+ any messages to the client on the client's subnet.
+
+ Note: some interfaces needs this to be enabled, but some networks
+ needs this to be disabled as broadcasts are filteretd, so this
+ needs to be configurable */
+ if (client->request_broadcast || client->arp_type != ARPHRD_ETHER)
+ packet->dhcp.flags = htobe16(0x8000);
+
+ /* RFC2132 section 4.1.1:
+ The client MUST include its hardware address in the ’chaddr’ field, if
+ necessary for delivery of DHCP reply messages. Non-Ethernet
+ interfaces will leave 'chaddr' empty and use the client identifier
+ instead (eg, RFC 4390 section 2.1).
+ */
+ if (client->arp_type == ARPHRD_ETHER)
+ memcpy(&packet->dhcp.chaddr, &client->mac_addr, ETH_ALEN);
+
+ /* If no client identifier exists, construct an RFC 4361-compliant one */
+ if (client->client_id_len == 0) {
+ size_t duid_len;
+
+ client->client_id.type = 255;
+
+ r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len,
+ true, &client->client_id.ns.iaid);
+ if (r < 0)
+ return r;
+
+ r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &duid_len);
+ if (r < 0)
+ return r;
+
+ client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + duid_len;
+ }
+
+ /* Some DHCP servers will refuse to issue an DHCP lease if the Client
+ Identifier option is not set */
+ if (client->client_id_len) {
+ r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_CLIENT_IDENTIFIER,
+ client->client_id_len,
+ &client->client_id);
+ if (r < 0)
+ return r;
+ }
+
+ /* RFC2131 section 3.5:
+ in its initial DHCPDISCOVER or DHCPREQUEST message, a
+ client may provide the server with a list of specific
+ parameters the client is interested in. If the client
+ includes a list of parameters in a DHCPDISCOVER message,
+ it MUST include that list in any subsequent DHCPREQUEST
+ messages.
+ */
+
+ /* RFC7844 section 3:
+ MAY contain the Parameter Request List option. */
+ /* NOTE: in case that there would be an option to do not send
+ * any PRL at all, the size should be checked before sending */
+ if (!set_isempty(client->req_opts) && type != DHCP_RELEASE) {
+ _cleanup_free_ uint8_t *opts = NULL;
+ size_t n_opts, i = 0;
+ void *val;
+
+ n_opts = set_size(client->req_opts);
+ opts = new(uint8_t, n_opts);
+ if (!opts)
+ return -ENOMEM;
+
+ SET_FOREACH(val, client->req_opts)
+ opts[i++] = PTR_TO_UINT8(val);
+ assert(i == n_opts);
+
+ /* For anonymizing the request, let's sort the options. */
+ typesafe_qsort(opts, n_opts, cmp_uint8);
+
+ r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
+ n_opts, opts);
+ if (r < 0)
+ return r;
+ }
+
+ /* RFC2131 section 3.5:
+ The client SHOULD include the ’maximum DHCP message size’ option to
+ let the server know how large the server may make its DHCP messages.
+
+ Note (from ConnMan): Some DHCP servers will send bigger DHCP packets
+ than the defined default size unless the Maximum Message Size option
+ is explicitly set
+
+ RFC3442 "Requirements to Avoid Sizing Constraints":
+ Because a full routing table can be quite large, the standard 576
+ octet maximum size for a DHCP message may be too short to contain
+ some legitimate Classless Static Route options. Because of this,
+ clients implementing the Classless Static Route option SHOULD send a
+ Maximum DHCP Message Size [4] option if the DHCP client's TCP/IP
+ stack is capable of receiving larger IP datagrams. In this case, the
+ client SHOULD set the value of this option to at least the MTU of the
+ interface that the client is configuring. The client MAY set the
+ value of this option higher, up to the size of the largest UDP packet
+ it is prepared to accept. (Note that the value specified in the
+ Maximum DHCP Message Size option is the total maximum packet size,
+ including IP and UDP headers.)
+ */
+ /* RFC7844 section 3:
+ SHOULD NOT contain any other option. */
+ if (!client->anonymize && type != DHCP_RELEASE) {
+ max_size = htobe16(size);
+ r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0,
+ SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
+ 2, &max_size);
+ if (r < 0)
+ return r;
+ }
+
+ *_optlen = optlen;
+ *_optoffset = optoffset;
+ *ret = TAKE_PTR(packet);
+
+ return 0;
+}
+
+static int client_append_fqdn_option(
+ DHCPMessage *message,
+ size_t optlen,
+ size_t *optoffset,
+ const char *fqdn) {
+
+ uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH];
+ int r;
+
+ buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */
+ DHCP_FQDN_FLAG_E; /* Canonical wire format */
+ buffer[1] = 0; /* RCODE1 (deprecated) */
+ buffer[2] = 0; /* RCODE2 (deprecated) */
+
+ r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false);
+ if (r > 0)
+ r = dhcp_option_append(message, optlen, optoffset, 0,
+ SD_DHCP_OPTION_FQDN, 3 + r, buffer);
+
+ return r;
+}
+
+static int dhcp_client_send_raw(
+ sd_dhcp_client *client,
+ DHCPPacket *packet,
+ size_t len) {
+
+ dhcp_packet_append_ip_headers(packet, INADDR_ANY, client->port,
+ INADDR_BROADCAST, DHCP_PORT_SERVER, len, client->ip_service_type);
+
+ return dhcp_network_send_raw_socket(client->fd, &client->link,
+ packet, len);
+}
+
+static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) {
+ sd_dhcp_option *j;
+ int r;
+
+ assert(client);
+
+ if (client->hostname) {
+ /* According to RFC 4702 "clients that send the Client FQDN option in
+ their messages MUST NOT also send the Host Name option". Just send
+ one of the two depending on the hostname type.
+ */
+ if (dns_name_is_single_label(client->hostname)) {
+ /* it is unclear from RFC 2131 if client should send hostname in
+ DHCPDISCOVER but dhclient does and so we do as well
+ */
+ r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+ SD_DHCP_OPTION_HOST_NAME,
+ strlen(client->hostname), client->hostname);
+ } else
+ r = client_append_fqdn_option(&packet->dhcp, optlen, optoffset,
+ client->hostname);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->vendor_class_identifier) {
+ r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+ SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER,
+ strlen(client->vendor_class_identifier),
+ client->vendor_class_identifier);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->mudurl) {
+ r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+ SD_DHCP_OPTION_MUD_URL,
+ strlen(client->mudurl),
+ client->mudurl);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->user_class) {
+ r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+ SD_DHCP_OPTION_USER_CLASS,
+ strv_length(client->user_class),
+ client->user_class);
+ if (r < 0)
+ return r;
+ }
+
+ ORDERED_HASHMAP_FOREACH(j, client->extra_options) {
+ r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
+ j->option, j->length, j->data);
+ if (r < 0)
+ return r;
+ }
+
+ if (!ordered_hashmap_isempty(client->vendor_options)) {
+ r = dhcp_option_append(
+ &packet->dhcp, optlen, optoffset, 0,
+ SD_DHCP_OPTION_VENDOR_SPECIFIC,
+ ordered_hashmap_size(client->vendor_options), client->vendor_options);
+ if (r < 0)
+ return r;
+ }
+
+
+ return 0;
+}
+
+static int client_send_discover(sd_dhcp_client *client) {
+ _cleanup_free_ DHCPPacket *discover = NULL;
+ size_t optoffset, optlen;
+ int r;
+
+ assert(client);
+ assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING));
+
+ r = client_message_init(client, &discover, DHCP_DISCOVER,
+ &optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ /* the client may suggest values for the network address
+ and lease time in the DHCPDISCOVER message. The client may include
+ the ’requested IP address’ option to suggest that a particular IP
+ address be assigned, and may include the ’IP address lease time’
+ option to suggest the lease time it would like.
+ */
+ /* RFC7844 section 3:
+ SHOULD NOT contain any other option. */
+ if (!client->anonymize && client->last_addr != INADDR_ANY) {
+ r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
+ 4, &client->last_addr);
+ if (r < 0)
+ return r;
+ }
+
+ r = client_append_common_discover_request_options(client, discover, &optoffset, optlen);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ /* We currently ignore:
+ The client SHOULD wait a random time between one and ten seconds to
+ desynchronize the use of DHCP at startup.
+ */
+ r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset);
+ if (r < 0)
+ return r;
+
+ log_dhcp_client(client, "DISCOVER");
+
+ return 0;
+}
+
+static int client_send_request(sd_dhcp_client *client) {
+ _cleanup_free_ DHCPPacket *request = NULL;
+ size_t optoffset, optlen;
+ int r;
+
+ assert(client);
+
+ r = client_message_init(client, &request, DHCP_REQUEST, &optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ switch (client->state) {
+ /* See RFC2131 section 4.3.2 (note that there is a typo in the RFC,
+ SELECTING should be REQUESTING)
+ */
+
+ case DHCP_STATE_REQUESTING:
+ /* Client inserts the address of the selected server in ’server
+ identifier’, ’ciaddr’ MUST be zero, ’requested IP address’ MUST be
+ filled in with the yiaddr value from the chosen DHCPOFFER.
+ */
+
+ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_SERVER_IDENTIFIER,
+ 4, &client->lease->server_address);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
+ 4, &client->lease->address);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case DHCP_STATE_INIT_REBOOT:
+ /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
+ option MUST be filled in with client’s notion of its previously
+ assigned address. ’ciaddr’ MUST be zero.
+ */
+ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
+ 4, &client->last_addr);
+ if (r < 0)
+ return r;
+ break;
+
+ case DHCP_STATE_RENEWING:
+ /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
+ option MUST NOT be filled in, ’ciaddr’ MUST be filled in with
+ client’s IP address.
+ */
+
+ case DHCP_STATE_REBINDING:
+ /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
+ option MUST NOT be filled in, ’ciaddr’ MUST be filled in with
+ client’s IP address.
+
+ This message MUST be broadcast to the 0xffffffff IP broadcast address.
+ */
+ request->dhcp.ciaddr = client->lease->address;
+
+ break;
+
+ case DHCP_STATE_INIT:
+ case DHCP_STATE_SELECTING:
+ case DHCP_STATE_REBOOTING:
+ case DHCP_STATE_BOUND:
+ case DHCP_STATE_STOPPED:
+ return -EINVAL;
+ }
+
+ r = client_append_common_discover_request_options(client, request, &optoffset, optlen);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ if (client->state == DHCP_STATE_RENEWING)
+ r = dhcp_network_send_udp_socket(client->fd,
+ client->lease->server_address,
+ DHCP_PORT_SERVER,
+ &request->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+ else
+ r = dhcp_client_send_raw(client, request, sizeof(DHCPPacket) + optoffset);
+ if (r < 0)
+ return r;
+
+ switch (client->state) {
+
+ case DHCP_STATE_REQUESTING:
+ log_dhcp_client(client, "REQUEST (requesting)");
+ break;
+
+ case DHCP_STATE_INIT_REBOOT:
+ log_dhcp_client(client, "REQUEST (init-reboot)");
+ break;
+
+ case DHCP_STATE_RENEWING:
+ log_dhcp_client(client, "REQUEST (renewing)");
+ break;
+
+ case DHCP_STATE_REBINDING:
+ log_dhcp_client(client, "REQUEST (rebinding)");
+ break;
+
+ default:
+ log_dhcp_client(client, "REQUEST (invalid)");
+ break;
+ }
+
+ return 0;
+}
+
+static int client_start(sd_dhcp_client *client);
+
+static int client_timeout_resend(
+ sd_event_source *s,
+ uint64_t usec,
+ void *userdata) {
+
+ sd_dhcp_client *client = userdata;
+ DHCP_CLIENT_DONT_DESTROY(client);
+ usec_t next_timeout = 0;
+ uint64_t time_now;
+ uint32_t time_left;
+ int r;
+
+ assert(s);
+ assert(client);
+ assert(client->event);
+
+ r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ goto error;
+
+ switch (client->state) {
+
+ case DHCP_STATE_RENEWING:
+
+ time_left = (client->lease->t2 - client->lease->t1) / 2;
+ if (time_left < 60)
+ time_left = 60;
+
+ next_timeout = time_now + time_left * USEC_PER_SEC;
+
+ break;
+
+ case DHCP_STATE_REBINDING:
+
+ time_left = (client->lease->lifetime - client->lease->t2) / 2;
+ if (time_left < 60)
+ time_left = 60;
+
+ next_timeout = time_now + time_left * USEC_PER_SEC;
+ 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;
+ else {
+ log_dhcp_client(client, "REBOOTED");
+ return 0;
+ }
+
+ case DHCP_STATE_INIT:
+ case DHCP_STATE_INIT_REBOOT:
+ case DHCP_STATE_SELECTING:
+ case DHCP_STATE_REQUESTING:
+ case DHCP_STATE_BOUND:
+
+ if (client->attempt < client->max_attempts)
+ client->attempt++;
+ else
+ goto error;
+
+ next_timeout = time_now + ((UINT64_C(1) << MIN(client->attempt, (uint64_t) 6)) - 1) * USEC_PER_SEC;
+
+ break;
+
+ case DHCP_STATE_STOPPED:
+ r = -EINVAL;
+ goto error;
+ }
+
+ next_timeout += (random_u32() & 0x1fffff);
+
+ r = event_reset_time(client->event, &client->timeout_resend,
+ clock_boottime_or_monotonic(),
+ next_timeout, 10 * USEC_PER_MSEC,
+ client_timeout_resend, client,
+ client->event_priority, "dhcp4-resend-timer", true);
+ if (r < 0)
+ goto error;
+
+ switch (client->state) {
+ case DHCP_STATE_INIT:
+ r = client_send_discover(client);
+ if (r >= 0) {
+ client->state = DHCP_STATE_SELECTING;
+ client->attempt = 0;
+ } else if (client->attempt >= client->max_attempts)
+ goto error;
+
+ break;
+
+ case DHCP_STATE_SELECTING:
+ r = client_send_discover(client);
+ if (r < 0 && client->attempt >= client->max_attempts)
+ goto error;
+
+ break;
+
+ case DHCP_STATE_INIT_REBOOT:
+ case DHCP_STATE_REQUESTING:
+ case DHCP_STATE_RENEWING:
+ case DHCP_STATE_REBINDING:
+ r = client_send_request(client);
+ if (r < 0 && client->attempt >= client->max_attempts)
+ goto error;
+
+ if (client->state == DHCP_STATE_INIT_REBOOT)
+ client->state = DHCP_STATE_REBOOTING;
+
+ client->request_sent = time_now;
+
+ break;
+
+ case DHCP_STATE_REBOOTING:
+ case DHCP_STATE_BOUND:
+
+ break;
+
+ case DHCP_STATE_STOPPED:
+ r = -EINVAL;
+ goto error;
+ }
+
+ return 0;
+
+error:
+ client_stop(client, r);
+
+ /* Errors were dealt with when stopping the client, don't spill
+ errors into the event loop handler */
+ return 0;
+}
+
+static int client_initialize_io_events(
+ sd_dhcp_client *client,
+ sd_event_io_handler_t io_callback) {
+
+ int r;
+
+ assert(client);
+ assert(client->event);
+
+ r = sd_event_add_io(client->event, &client->receive_message,
+ client->fd, EPOLLIN, io_callback,
+ client);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_priority(client->receive_message,
+ client->event_priority);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_description(client->receive_message, "dhcp4-receive-message");
+ if (r < 0)
+ goto error;
+
+error:
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+}
+
+static int client_initialize_time_events(sd_dhcp_client *client) {
+ uint64_t usec = 0;
+ int r;
+
+ assert(client);
+ assert(client->event);
+
+ if (client->start_delay > 0) {
+ assert_se(sd_event_now(client->event, clock_boottime_or_monotonic(), &usec) >= 0);
+ usec += client->start_delay;
+ }
+
+ r = event_reset_time(client->event, &client->timeout_resend,
+ clock_boottime_or_monotonic(),
+ usec, 0,
+ client_timeout_resend, client,
+ client->event_priority, "dhcp4-resend-timer", true);
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+
+}
+
+static int client_initialize_events(sd_dhcp_client *client, sd_event_io_handler_t io_callback) {
+ client_initialize_io_events(client, io_callback);
+ client_initialize_time_events(client);
+
+ return 0;
+}
+
+static int client_start_delayed(sd_dhcp_client *client) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->event, -EINVAL);
+ assert_return(client->ifindex > 0, -EINVAL);
+ assert_return(client->fd < 0, -EBUSY);
+ assert_return(client->xid == 0, -EINVAL);
+ assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT), -EBUSY);
+
+ client->xid = random_u32();
+
+ r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
+ client->mac_addr, client->mac_addr_len,
+ client->bcast_addr, client->bcast_addr_len,
+ client->arp_type, client->port);
+ if (r < 0) {
+ client_stop(client, r);
+ return r;
+ }
+ client->fd = r;
+
+ if (IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT))
+ client->start_time = now(clock_boottime_or_monotonic());
+
+ return client_initialize_events(client, client_receive_message_raw);
+}
+
+static int client_start(sd_dhcp_client *client) {
+ client->start_delay = 0;
+ return client_start_delayed(client);
+}
+
+static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp_client *client = userdata;
+ DHCP_CLIENT_DONT_DESTROY(client);
+
+ log_dhcp_client(client, "EXPIRED");
+
+ client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
+
+ /* lease was lost, start over if not freed or stopped in callback */
+ if (client->state != DHCP_STATE_STOPPED) {
+ client_initialize(client);
+ client_start(client);
+ }
+
+ return 0;
+}
+
+static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp_client *client = userdata;
+ DHCP_CLIENT_DONT_DESTROY(client);
+ int r;
+
+ assert(client);
+
+ client->receive_message = sd_event_source_unref(client->receive_message);
+ client->fd = safe_close(client->fd);
+
+ client->state = DHCP_STATE_REBINDING;
+ client->attempt = 0;
+
+ r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
+ client->mac_addr, client->mac_addr_len,
+ client->bcast_addr, client->bcast_addr_len,
+ client->arp_type, client->port);
+ if (r < 0) {
+ client_stop(client, r);
+ return 0;
+ }
+ client->fd = r;
+
+ return client_initialize_events(client, client_receive_message_raw);
+}
+
+static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp_client *client = userdata;
+ DHCP_CLIENT_DONT_DESTROY(client);
+
+ if (client->lease)
+ client->state = DHCP_STATE_RENEWING;
+ else if (client->state != DHCP_STATE_INIT)
+ client->state = DHCP_STATE_INIT_REBOOT;
+ client->attempt = 0;
+
+ return client_initialize_time_events(client);
+}
+
+static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_t len) {
+ _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+ int r;
+
+ r = dhcp_lease_new(&lease);
+ if (r < 0)
+ return r;
+
+ if (client->client_id_len) {
+ r = dhcp_lease_set_client_id(lease,
+ (uint8_t *) &client->client_id,
+ client->client_id_len);
+ if (r < 0)
+ return r;
+ }
+
+ r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease, NULL);
+ if (r != DHCP_OFFER) {
+ log_dhcp_client(client, "received message was not an OFFER, ignoring");
+ return -ENOMSG;
+ }
+
+ lease->next_server = offer->siaddr;
+ lease->address = offer->yiaddr;
+
+ if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0)
+ lease->lifetime = client->fallback_lease_lifetime;
+
+ if (lease->address == 0 ||
+ lease->server_address == 0 ||
+ lease->lifetime == 0) {
+ log_dhcp_client(client, "received lease lacks address, server address or lease lifetime, ignoring");
+ return -ENOMSG;
+ }
+
+ if (!lease->have_subnet_mask) {
+ r = dhcp_lease_set_default_subnet_mask(lease);
+ if (r < 0) {
+ log_dhcp_client(client,
+ "received lease lacks subnet mask, "
+ "and a fallback one cannot be generated, ignoring");
+ return -ENOMSG;
+ }
+ }
+
+ sd_dhcp_lease_unref(client->lease);
+ client->lease = TAKE_PTR(lease);
+
+ if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0)
+ return -ENOMSG;
+
+ log_dhcp_client(client, "OFFER");
+
+ return 0;
+}
+
+static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, size_t len) {
+ int r;
+
+ r = dhcp_option_parse(force, len, NULL, NULL, NULL);
+ if (r != DHCP_FORCERENEW)
+ return -ENOMSG;
+
+ log_dhcp_client(client, "FORCERENEW");
+
+ return 0;
+}
+
+static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) {
+ if (a->address != b->address)
+ return false;
+
+ if (a->subnet_mask != b->subnet_mask)
+ return false;
+
+ if (a->router_size != b->router_size)
+ return false;
+
+ for (size_t i = 0; i < a->router_size; i++)
+ if (a->router[i].s_addr != b->router[i].s_addr)
+ return false;
+
+ return true;
+}
+
+static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t len) {
+ _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+ _cleanup_free_ char *error_message = NULL;
+ int r;
+
+ r = dhcp_lease_new(&lease);
+ if (r < 0)
+ return r;
+
+ if (client->client_id_len) {
+ r = dhcp_lease_set_client_id(lease,
+ (uint8_t *) &client->client_id,
+ client->client_id_len);
+ if (r < 0)
+ return r;
+ }
+
+ r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease, &error_message);
+ if (r == DHCP_NAK) {
+ log_dhcp_client(client, "NAK: %s", strna(error_message));
+ return -EADDRNOTAVAIL;
+ }
+
+ if (r != DHCP_ACK) {
+ log_dhcp_client(client, "received message was not an ACK, ignoring");
+ return -ENOMSG;
+ }
+
+ lease->next_server = ack->siaddr;
+
+ lease->address = ack->yiaddr;
+
+ if (lease->address == INADDR_ANY ||
+ lease->server_address == INADDR_ANY ||
+ lease->lifetime == 0) {
+ log_dhcp_client(client, "received lease lacks address, server "
+ "address or lease lifetime, ignoring");
+ return -ENOMSG;
+ }
+
+ if (lease->subnet_mask == INADDR_ANY) {
+ r = dhcp_lease_set_default_subnet_mask(lease);
+ if (r < 0) {
+ log_dhcp_client(client,
+ "received lease lacks subnet mask, "
+ "and a fallback one cannot be generated, ignoring");
+ return -ENOMSG;
+ }
+ }
+
+ r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
+ if (client->lease) {
+ if (lease_equal(client->lease, lease))
+ r = SD_DHCP_CLIENT_EVENT_RENEW;
+ else
+ r = SD_DHCP_CLIENT_EVENT_IP_CHANGE;
+
+ client->lease = sd_dhcp_lease_unref(client->lease);
+ }
+
+ client->lease = TAKE_PTR(lease);
+
+ log_dhcp_client(client, "ACK");
+
+ return r;
+}
+
+static uint64_t client_compute_timeout(sd_dhcp_client *client, uint32_t lifetime, double factor) {
+ assert(client);
+ assert(client->request_sent);
+ assert(lifetime > 0);
+
+ if (lifetime > 3)
+ lifetime -= 3;
+ else
+ lifetime = 0;
+
+ return client->request_sent + (lifetime * USEC_PER_SEC * factor) +
+ + (random_u32() & 0x1fffff);
+}
+
+static int client_set_lease_timeouts(sd_dhcp_client *client) {
+ usec_t time_now;
+ uint64_t lifetime_timeout;
+ uint64_t t2_timeout;
+ uint64_t t1_timeout;
+ char time_string[FORMAT_TIMESPAN_MAX];
+ int r;
+
+ assert(client);
+ assert(client->event);
+ assert(client->lease);
+ assert(client->lease->lifetime);
+
+ /* don't set timers for infinite leases */
+ if (client->lease->lifetime == 0xffffffff) {
+ (void) event_source_disable(client->timeout_t1);
+ (void) event_source_disable(client->timeout_t2);
+ (void) event_source_disable(client->timeout_expire);
+
+ return 0;
+ }
+
+ r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return r;
+ assert(client->request_sent <= time_now);
+
+ /* convert the various timeouts from relative (secs) to absolute (usecs) */
+ lifetime_timeout = client_compute_timeout(client, client->lease->lifetime, 1);
+ if (client->lease->t1 > 0 && client->lease->t2 > 0) {
+ /* both T1 and T2 are given */
+ if (client->lease->t1 < client->lease->t2 &&
+ client->lease->t2 < client->lease->lifetime) {
+ /* they are both valid */
+ t2_timeout = client_compute_timeout(client, client->lease->t2, 1);
+ t1_timeout = client_compute_timeout(client, client->lease->t1, 1);
+ } else {
+ /* discard both */
+ t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0);
+ client->lease->t2 = (client->lease->lifetime * 7) / 8;
+ t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5);
+ client->lease->t1 = client->lease->lifetime / 2;
+ }
+ } else if (client->lease->t2 > 0 && client->lease->t2 < client->lease->lifetime) {
+ /* only T2 is given, and it is valid */
+ t2_timeout = client_compute_timeout(client, client->lease->t2, 1);
+ t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5);
+ client->lease->t1 = client->lease->lifetime / 2;
+ if (t2_timeout <= t1_timeout) {
+ /* the computed T1 would be invalid, so discard T2 */
+ t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0);
+ client->lease->t2 = (client->lease->lifetime * 7) / 8;
+ }
+ } else if (client->lease->t1 > 0 && client->lease->t1 < client->lease->lifetime) {
+ /* only T1 is given, and it is valid */
+ t1_timeout = client_compute_timeout(client, client->lease->t1, 1);
+ t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0);
+ client->lease->t2 = (client->lease->lifetime * 7) / 8;
+ if (t2_timeout <= t1_timeout) {
+ /* the computed T2 would be invalid, so discard T1 */
+ t2_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5);
+ client->lease->t2 = client->lease->lifetime / 2;
+ }
+ } else {
+ /* fall back to the default timeouts */
+ t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5);
+ client->lease->t1 = client->lease->lifetime / 2;
+ t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0);
+ client->lease->t2 = (client->lease->lifetime * 7) / 8;
+ }
+
+ /* arm lifetime timeout */
+ r = event_reset_time(client->event, &client->timeout_expire,
+ clock_boottime_or_monotonic(),
+ lifetime_timeout, 10 * USEC_PER_MSEC,
+ client_timeout_expire, client,
+ client->event_priority, "dhcp4-lifetime", true);
+ if (r < 0)
+ return r;
+
+ log_dhcp_client(client, "lease expires in %s",
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_timeout - time_now, USEC_PER_SEC));
+
+ /* don't arm earlier timeouts if this has already expired */
+ if (lifetime_timeout <= time_now)
+ return 0;
+
+ /* arm T2 timeout */
+ r = event_reset_time(client->event, &client->timeout_t2,
+ clock_boottime_or_monotonic(),
+ t2_timeout, 10 * USEC_PER_MSEC,
+ client_timeout_t2, client,
+ client->event_priority, "dhcp4-t2-timeout", true);
+ if (r < 0)
+ return r;
+
+ log_dhcp_client(client, "T2 expires in %s",
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX, t2_timeout - time_now, USEC_PER_SEC));
+
+ /* don't arm earlier timeout if this has already expired */
+ if (t2_timeout <= time_now)
+ return 0;
+
+ /* arm T1 timeout */
+ r = event_reset_time(client->event, &client->timeout_t1,
+ clock_boottime_or_monotonic(),
+ t1_timeout, 10 * USEC_PER_MSEC,
+ client_timeout_t1, client,
+ client->event_priority, "dhcp4-t1-timer", true);
+ if (r < 0)
+ return r;
+
+ log_dhcp_client(client, "T1 expires in %s",
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX, t1_timeout - time_now, USEC_PER_SEC));
+
+ return 0;
+}
+
+static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) {
+ DHCP_CLIENT_DONT_DESTROY(client);
+ char time_string[FORMAT_TIMESPAN_MAX];
+ int r = 0, notify_event = 0;
+
+ assert(client);
+ assert(client->event);
+ assert(message);
+
+ switch (client->state) {
+ case DHCP_STATE_SELECTING:
+
+ r = client_handle_offer(client, message, len);
+ if (r >= 0) {
+
+ client->state = DHCP_STATE_REQUESTING;
+ client->attempt = 0;
+
+ r = event_reset_time(client->event, &client->timeout_resend,
+ clock_boottime_or_monotonic(),
+ 0, 0,
+ client_timeout_resend, client,
+ client->event_priority, "dhcp4-resend-timer", true);
+ if (r < 0)
+ goto error;
+ } else if (r == -ENOMSG)
+ /* invalid message, let's ignore it */
+ return 0;
+
+ break;
+
+ case DHCP_STATE_REBOOTING:
+ case DHCP_STATE_REQUESTING:
+ case DHCP_STATE_RENEWING:
+ case DHCP_STATE_REBINDING:
+
+ r = client_handle_ack(client, message, len);
+ if (r >= 0) {
+ client->start_delay = 0;
+ (void) event_source_disable(client->timeout_resend);
+ client->receive_message =
+ sd_event_source_unref(client->receive_message);
+ client->fd = safe_close(client->fd);
+
+ if (IN_SET(client->state, DHCP_STATE_REQUESTING,
+ DHCP_STATE_REBOOTING))
+ notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
+ else if (r != SD_DHCP_CLIENT_EVENT_IP_ACQUIRE)
+ notify_event = r;
+
+ client->state = DHCP_STATE_BOUND;
+ client->attempt = 0;
+
+ client->last_addr = client->lease->address;
+
+ r = client_set_lease_timeouts(client);
+ if (r < 0) {
+ log_dhcp_client(client, "could not set lease timeouts");
+ goto error;
+ }
+
+ r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type);
+ if (r < 0) {
+ log_dhcp_client(client, "could not bind UDP socket");
+ goto error;
+ }
+
+ client->fd = r;
+
+ client_initialize_io_events(client, client_receive_message_udp);
+
+ if (notify_event) {
+ client_notify(client, notify_event);
+ if (client->state == DHCP_STATE_STOPPED)
+ return 0;
+ }
+
+ } else if (r == -EADDRNOTAVAIL) {
+ /* got a NAK, let's restart the client */
+ client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
+
+ r = client_initialize(client);
+ if (r < 0)
+ goto error;
+
+ r = client_start_delayed(client);
+ if (r < 0)
+ goto error;
+
+ log_dhcp_client(client, "REBOOT in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX,
+ client->start_delay, USEC_PER_SEC));
+
+ client->start_delay = CLAMP(client->start_delay * 2,
+ RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC);
+
+ return 0;
+ } else if (r == -ENOMSG)
+ /* invalid message, let's ignore it */
+ return 0;
+
+ break;
+
+ case DHCP_STATE_BOUND:
+ r = client_handle_forcerenew(client, message, len);
+ if (r >= 0) {
+ r = client_timeout_t1(NULL, 0, client);
+ if (r < 0)
+ goto error;
+ } else if (r == -ENOMSG)
+ /* invalid message, let's ignore it */
+ return 0;
+
+ break;
+
+ case DHCP_STATE_INIT:
+ case DHCP_STATE_INIT_REBOOT:
+
+ break;
+
+ case DHCP_STATE_STOPPED:
+ r = -EINVAL;
+ goto error;
+ }
+
+error:
+ if (r < 0)
+ client_stop(client, r);
+
+ return r;
+}
+
+static int client_receive_message_udp(
+ sd_event_source *s,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+
+ sd_dhcp_client *client = userdata;
+ _cleanup_free_ DHCPMessage *message = NULL;
+ const uint8_t *expected_chaddr = NULL;
+ uint8_t expected_hlen = 0;
+ ssize_t len, buflen;
+
+ assert(s);
+ assert(client);
+
+ buflen = next_datagram_size_fd(fd);
+ if (buflen == -ENETDOWN)
+ /* the link is down. Don't return an error or the I/O event
+ source will be disconnected and we won't be able to receive
+ packets again when the link comes back. */
+ return 0;
+ if (buflen < 0)
+ return buflen;
+
+ message = malloc0(buflen);
+ if (!message)
+ return -ENOMEM;
+
+ len = recv(fd, message, buflen, 0);
+ if (len < 0) {
+ /* see comment above for why we shouldn't error out on ENETDOWN. */
+ if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
+ return 0;
+
+ return log_dhcp_client_errno(client, errno,
+ "Could not receive message from UDP socket: %m");
+ }
+ if ((size_t) len < sizeof(DHCPMessage)) {
+ log_dhcp_client(client, "Too small to be a DHCP message: ignoring");
+ return 0;
+ }
+
+ if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) {
+ log_dhcp_client(client, "Not a DHCP message: ignoring");
+ return 0;
+ }
+
+ if (message->op != BOOTREPLY) {
+ log_dhcp_client(client, "Not a BOOTREPLY message: ignoring");
+ return 0;
+ }
+
+ if (message->htype != client->arp_type) {
+ log_dhcp_client(client, "Packet type does not match client type");
+ return 0;
+ }
+
+ if (client->arp_type == ARPHRD_ETHER) {
+ expected_hlen = ETH_ALEN;
+ expected_chaddr = &client->mac_addr[0];
+ }
+
+ if (message->hlen != expected_hlen) {
+ log_dhcp_client(client, "Unexpected packet hlen %d", message->hlen);
+ return 0;
+ }
+
+ if (expected_hlen > 0 && memcmp(&message->chaddr[0], expected_chaddr, expected_hlen)) {
+ log_dhcp_client(client, "Received chaddr does not match expected: ignoring");
+ return 0;
+ }
+
+ if (client->state != DHCP_STATE_BOUND &&
+ be32toh(message->xid) != client->xid) {
+ /* in BOUND state, we may receive FORCERENEW with xid set by server,
+ so ignore the xid in this case */
+ log_dhcp_client(client, "Received xid (%u) does not match expected (%u): ignoring",
+ be32toh(message->xid), client->xid);
+ return 0;
+ }
+
+ return client_handle_message(client, message, len);
+}
+
+static int client_receive_message_raw(
+ sd_event_source *s,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+
+ sd_dhcp_client *client = userdata;
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct tpacket_auxdata))) control;
+ struct iovec iov = {};
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ bool checksum = true;
+ ssize_t buflen, len;
+ int r;
+
+ assert(s);
+ assert(client);
+
+ buflen = next_datagram_size_fd(fd);
+ if (buflen == -ENETDOWN)
+ return 0;
+ if (buflen < 0)
+ return buflen;
+
+ packet = malloc0(buflen);
+ if (!packet)
+ return -ENOMEM;
+
+ iov = IOVEC_MAKE(packet, buflen);
+
+ len = recvmsg_safe(fd, &msg, 0);
+ if (IN_SET(len, -EAGAIN, -EINTR, -ENETDOWN))
+ return 0;
+ if (len < 0)
+ return log_dhcp_client_errno(client, len,
+ "Could not receive message from raw socket: %m");
+
+ if ((size_t) len < sizeof(DHCPPacket))
+ return 0;
+
+ cmsg = cmsg_find(&msg, SOL_PACKET, PACKET_AUXDATA, CMSG_LEN(sizeof(struct tpacket_auxdata)));
+ if (cmsg) {
+ struct tpacket_auxdata *aux = (struct tpacket_auxdata*) CMSG_DATA(cmsg);
+ checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
+ }
+
+ r = dhcp_packet_verify_headers(packet, len, checksum, client->port);
+ if (r < 0)
+ return 0;
+
+ len -= DHCP_IP_UDP_SIZE;
+
+ return client_handle_message(client, &packet->dhcp, len);
+}
+
+int sd_dhcp_client_send_renew(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+ assert_return(client->fd >= 0, -EINVAL);
+
+ if (!client->lease)
+ return 0;
+
+ client->start_delay = 0;
+ client->attempt = 1;
+ client->state = DHCP_STATE_RENEWING;
+
+ return client_initialize_time_events(client);
+}
+
+int sd_dhcp_client_start(sd_dhcp_client *client) {
+ int r;
+
+ assert_return(client, -EINVAL);
+
+ r = client_initialize(client);
+ if (r < 0)
+ return r;
+
+ /* RFC7844 section 3.3:
+ SHOULD perform a complete four-way handshake, starting with a
+ DHCPDISCOVER, to obtain a new address lease. If the client can
+ ascertain that this is exactly the same network to which it was
+ previously connected, and if the link-layer address did not change,
+ the client MAY issue a DHCPREQUEST to try to reclaim the current
+ address. */
+ if (client->last_addr && !client->anonymize)
+ client->state = DHCP_STATE_INIT_REBOOT;
+
+ r = client_start(client);
+ if (r >= 0)
+ log_dhcp_client(client, "STARTED on ifindex %i", client->ifindex);
+
+ return r;
+}
+
+int sd_dhcp_client_send_release(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+ assert_return(client->state != DHCP_STATE_STOPPED, -ESTALE);
+ assert_return(client->lease, -EUNATCH);
+
+ _cleanup_free_ DHCPPacket *release = NULL;
+ size_t optoffset, optlen;
+ int r;
+
+ r = client_message_init(client, &release, DHCP_RELEASE, &optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ /* Fill up release IP and MAC */
+ release->dhcp.ciaddr = client->lease->address;
+ memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len);
+
+ r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ r = dhcp_network_send_udp_socket(client->fd,
+ client->lease->server_address,
+ DHCP_PORT_SERVER,
+ &release->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+ if (r < 0)
+ return r;
+
+ log_dhcp_client(client, "RELEASE");
+
+ return 0;
+}
+
+int sd_dhcp_client_send_decline(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+ assert_return(client->state != DHCP_STATE_STOPPED, -ESTALE);
+ assert_return(client->lease, -EUNATCH);
+
+ _cleanup_free_ DHCPPacket *release = NULL;
+ size_t optoffset, optlen;
+ int r;
+
+ r = client_message_init(client, &release, DHCP_DECLINE, &optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ release->dhcp.ciaddr = client->lease->address;
+ memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len);
+
+ r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ r = dhcp_network_send_udp_socket(client->fd,
+ client->lease->server_address,
+ DHCP_PORT_SERVER,
+ &release->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+ if (r < 0)
+ return r;
+
+ log_dhcp_client(client, "DECLINE");
+
+ client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+
+ if (client->state != DHCP_STATE_STOPPED) {
+ r = sd_dhcp_client_start(client);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int sd_dhcp_client_stop(sd_dhcp_client *client) {
+ if (!client)
+ return 0;
+
+ DHCP_CLIENT_DONT_DESTROY(client);
+
+ client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+ client->state = DHCP_STATE_STOPPED;
+
+ return 0;
+}
+
+int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!client->event, -EBUSY);
+
+ if (event)
+ client->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&client->event);
+ if (r < 0)
+ return 0;
+ }
+
+ client->event_priority = priority;
+
+ return 0;
+}
+
+int sd_dhcp_client_detach_event(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+
+ client->event = sd_event_unref(client->event);
+
+ return 0;
+}
+
+sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) {
+ assert_return(client, NULL);
+
+ return client->event;
+}
+
+static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) {
+ if (!client)
+ return NULL;
+
+ log_dhcp_client(client, "FREE");
+
+ client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+ client->timeout_t1 = sd_event_source_unref(client->timeout_t1);
+ client->timeout_t2 = sd_event_source_unref(client->timeout_t2);
+ client->timeout_expire = sd_event_source_unref(client->timeout_expire);
+
+ client_initialize(client);
+
+ sd_dhcp_client_detach_event(client);
+
+ sd_dhcp_lease_unref(client->lease);
+
+ set_free(client->req_opts);
+ free(client->hostname);
+ free(client->vendor_class_identifier);
+ free(client->mudurl);
+ client->user_class = strv_free(client->user_class);
+ ordered_hashmap_free(client->extra_options);
+ ordered_hashmap_free(client->vendor_options);
+ return mfree(client);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client, sd_dhcp_client, dhcp_client_free);
+
+int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
+ const uint8_t *opts;
+ size_t n_opts;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = new(sd_dhcp_client, 1);
+ if (!client)
+ return -ENOMEM;
+
+ *client = (sd_dhcp_client) {
+ .n_ref = 1,
+ .state = DHCP_STATE_INIT,
+ .ifindex = -1,
+ .fd = -1,
+ .mtu = DHCP_DEFAULT_MIN_SIZE,
+ .port = DHCP_PORT_CLIENT,
+ .anonymize = !!anonymize,
+ .max_attempts = (uint64_t) -1,
+ .ip_service_type = -1,
+ };
+ /* NOTE: this could be moved to a function. */
+ if (anonymize) {
+ n_opts = ELEMENTSOF(default_req_opts_anonymize);
+ opts = default_req_opts_anonymize;
+ } else {
+ n_opts = ELEMENTSOF(default_req_opts);
+ opts = default_req_opts;
+ }
+
+ for (size_t i = 0; i < n_opts; i++) {
+ r = sd_dhcp_client_set_request_option(client, opts[i]);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(client);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c
new file mode 100644
index 0000000..8a138ff
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp-lease.c
@@ -0,0 +1,1399 @@
+/* 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-protocol.h"
+#include "dns-domain.h"
+#include "env-file.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "hexdecoct.h"
+#include "hostname-util.h"
+#include "in-addr-util.h"
+#include "network-internal.h"
+#include "parse-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+#include "unaligned.h"
+
+int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (lease->address == 0)
+ return -ENODATA;
+
+ addr->s_addr = lease->address;
+ return 0;
+}
+
+int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (!lease->have_broadcast)
+ return -ENODATA;
+
+ addr->s_addr = lease->broadcast;
+ return 0;
+}
+
+int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime) {
+ assert_return(lease, -EINVAL);
+ assert_return(lifetime, -EINVAL);
+
+ if (lease->lifetime <= 0)
+ return -ENODATA;
+
+ *lifetime = lease->lifetime;
+ return 0;
+}
+
+int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1) {
+ assert_return(lease, -EINVAL);
+ assert_return(t1, -EINVAL);
+
+ if (lease->t1 <= 0)
+ return -ENODATA;
+
+ *t1 = lease->t1;
+ return 0;
+}
+
+int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2) {
+ assert_return(lease, -EINVAL);
+ assert_return(t2, -EINVAL);
+
+ if (lease->t2 <= 0)
+ return -ENODATA;
+
+ *t2 = lease->t2;
+ return 0;
+}
+
+int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu) {
+ assert_return(lease, -EINVAL);
+ assert_return(mtu, -EINVAL);
+
+ if (lease->mtu <= 0)
+ return -ENODATA;
+
+ *mtu = lease->mtu;
+ return 0;
+}
+
+int sd_dhcp_lease_get_servers(
+ sd_dhcp_lease *lease,
+ sd_dhcp_lease_server_type what,
+ const struct in_addr **addr) {
+
+ assert_return(lease, -EINVAL);
+ assert_return(what >= 0, -EINVAL);
+ assert_return(what < _SD_DHCP_LEASE_SERVER_TYPE_MAX, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (lease->servers[what].size <= 0)
+ return -ENODATA;
+
+ *addr = lease->servers[what].addr;
+ return (int) lease->servers[what].size;
+}
+
+int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_DNS, addr);
+}
+int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_NTP, addr);
+}
+int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SIP, addr);
+}
+int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_POP3, addr);
+}
+int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SMTP, addr);
+}
+int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_LPR, addr);
+}
+
+int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) {
+ assert_return(lease, -EINVAL);
+ assert_return(domainname, -EINVAL);
+
+ if (!lease->domainname)
+ return -ENODATA;
+
+ *domainname = lease->domainname;
+ return 0;
+}
+
+int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname) {
+ assert_return(lease, -EINVAL);
+ assert_return(hostname, -EINVAL);
+
+ if (!lease->hostname)
+ return -ENODATA;
+
+ *hostname = lease->hostname;
+ return 0;
+}
+
+int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path) {
+ assert_return(lease, -EINVAL);
+ assert_return(root_path, -EINVAL);
+
+ if (!lease->root_path)
+ return -ENODATA;
+
+ *root_path = lease->root_path;
+ return 0;
+}
+
+int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (lease->router_size <= 0)
+ return -ENODATA;
+
+ *addr = lease->router;
+ return (int) lease->router_size;
+}
+
+int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (!lease->have_subnet_mask)
+ return -ENODATA;
+
+ addr->s_addr = lease->subnet_mask;
+ return 0;
+}
+
+int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (lease->server_address == 0)
+ return -ENODATA;
+
+ addr->s_addr = lease->server_address;
+ return 0;
+}
+
+int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ if (lease->next_server == 0)
+ return -ENODATA;
+
+ addr->s_addr = lease->next_server;
+ return 0;
+}
+
+/*
+ * The returned routes array must be freed by the caller.
+ * Route objects have the same lifetime of the lease and must not be freed.
+ */
+int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes) {
+ sd_dhcp_route **ret;
+ unsigned i;
+
+ assert_return(lease, -EINVAL);
+ assert_return(routes, -EINVAL);
+
+ if (lease->static_route_size <= 0)
+ return -ENODATA;
+
+ ret = new(sd_dhcp_route *, lease->static_route_size);
+ if (!ret)
+ return -ENOMEM;
+
+ for (i = 0; i < lease->static_route_size; i++)
+ ret[i] = &lease->static_route[i];
+
+ *routes = ret;
+ return (int) lease->static_route_size;
+}
+
+int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) {
+ size_t r;
+
+ assert_return(lease, -EINVAL);
+ assert_return(domains, -EINVAL);
+
+ r = strv_length(lease->search_domains);
+ if (r > 0) {
+ *domains = lease->search_domains;
+ return (int) r;
+ }
+
+ return -ENODATA;
+}
+
+int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) {
+ assert_return(lease, -EINVAL);
+ assert_return(data, -EINVAL);
+ assert_return(data_len, -EINVAL);
+
+ if (lease->vendor_specific_len <= 0)
+ return -ENODATA;
+
+ *data = lease->vendor_specific;
+ *data_len = lease->vendor_specific_len;
+ return 0;
+}
+
+static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) {
+ assert(lease);
+
+ while (lease->private_options) {
+ struct sd_dhcp_raw_option *option = lease->private_options;
+
+ LIST_REMOVE(options, lease->private_options, option);
+
+ free(option->data);
+ free(option);
+ }
+
+ free(lease->root_path);
+ free(lease->router);
+ free(lease->timezone);
+ free(lease->hostname);
+ free(lease->domainname);
+
+ for (sd_dhcp_lease_server_type i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++)
+ free(lease->servers[i].addr);
+
+ free(lease->static_route);
+ free(lease->client_id);
+ free(lease->vendor_specific);
+ strv_free(lease->search_domains);
+ return mfree(lease);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_lease, sd_dhcp_lease, dhcp_lease_free);
+
+static int lease_parse_u32(const uint8_t *option, size_t len, uint32_t *ret, uint32_t min) {
+ assert(option);
+ assert(ret);
+
+ if (len != 4)
+ return -EINVAL;
+
+ *ret = unaligned_read_be32((be32_t*) option);
+ if (*ret < min)
+ *ret = min;
+
+ return 0;
+}
+
+static int lease_parse_u16(const uint8_t *option, size_t len, uint16_t *ret, uint16_t min) {
+ assert(option);
+ assert(ret);
+
+ if (len != 2)
+ return -EINVAL;
+
+ *ret = unaligned_read_be16((be16_t*) option);
+ if (*ret < min)
+ *ret = min;
+
+ return 0;
+}
+
+static int lease_parse_be32(const uint8_t *option, size_t len, be32_t *ret) {
+ assert(option);
+ assert(ret);
+
+ if (len != 4)
+ return -EINVAL;
+
+ memcpy(ret, option, 4);
+ return 0;
+}
+
+static int lease_parse_string(const uint8_t *option, size_t len, char **ret) {
+ assert(option);
+ assert(ret);
+
+ if (len <= 0)
+ *ret = mfree(*ret);
+ else {
+ char *string;
+
+ /*
+ * One trailing NUL byte is OK, we don't mind. See:
+ * https://github.com/systemd/systemd/issues/1337
+ */
+ if (memchr(option, 0, len - 1))
+ return -EINVAL;
+
+ string = memdup_suffix0((const char *) option, len);
+ if (!string)
+ return -ENOMEM;
+
+ free_and_replace(*ret, string);
+ }
+
+ return 0;
+}
+
+static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) {
+ _cleanup_free_ char *name = NULL, *normalized = NULL;
+ int r;
+
+ assert(option);
+ assert(ret);
+
+ r = lease_parse_string(option, len, &name);
+ if (r < 0)
+ return r;
+ if (!name) {
+ *ret = mfree(*ret);
+ return 0;
+ }
+
+ r = dns_name_normalize(name, 0, &normalized);
+ if (r < 0)
+ return r;
+
+ if (is_localhost(normalized))
+ return -EINVAL;
+
+ if (dns_name_is_root(normalized))
+ return -EINVAL;
+
+ free_and_replace(*ret, normalized);
+
+ return 0;
+}
+
+static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) {
+ assert(option || len == 0);
+ assert(ret);
+ assert(n_ret);
+
+ if (len <= 0) {
+ *ret = mfree(*ret);
+ *n_ret = 0;
+ } else {
+ size_t n_addresses;
+ struct in_addr *addresses;
+
+ if (len % 4 != 0)
+ return -EINVAL;
+
+ n_addresses = len / 4;
+
+ addresses = newdup(struct in_addr, option, n_addresses);
+ if (!addresses)
+ return -ENOMEM;
+
+ free(*ret);
+ *ret = addresses;
+ *n_ret = n_addresses;
+ }
+
+ return 0;
+}
+
+static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) {
+ assert(option || len == 0);
+ assert(ret);
+ assert(n_ret);
+
+ if (len <= 0)
+ return -EINVAL;
+
+ /* The SIP record is like the other, regular server records, but prefixed with a single "encoding"
+ * byte that is either 0 or 1. We only support it to be 1 for now. Let's drop it and parse it like
+ * the other fields */
+
+ if (option[0] != 1) { /* We only support IP address encoding for now */
+ *ret = mfree(*ret);
+ *n_ret = 0;
+ return 0;
+ }
+
+ return lease_parse_in_addrs(option + 1, len - 1, ret, n_ret);
+}
+
+static int lease_parse_routes(
+ const uint8_t *option, size_t len,
+ struct sd_dhcp_route **routes, size_t *routes_size, size_t *routes_allocated) {
+
+ struct in_addr addr;
+
+ assert(option || len <= 0);
+ assert(routes);
+ assert(routes_size);
+ assert(routes_allocated);
+
+ if (len <= 0)
+ return 0;
+
+ if (len % 8 != 0)
+ return -EINVAL;
+
+ if (!GREEDY_REALLOC(*routes, *routes_allocated, *routes_size + (len / 8)))
+ return -ENOMEM;
+
+ while (len >= 8) {
+ struct sd_dhcp_route *route = *routes + *routes_size;
+ int r;
+
+ route->option = SD_DHCP_OPTION_STATIC_ROUTE;
+ r = in4_addr_default_prefixlen((struct in_addr*) option, &route->dst_prefixlen);
+ if (r < 0) {
+ log_debug("Failed to determine destination prefix length from class based IP, ignoring");
+ continue;
+ }
+
+ assert_se(lease_parse_be32(option, 4, &addr.s_addr) >= 0);
+ route->dst_addr = inet_makeaddr(inet_netof(addr), 0);
+ option += 4;
+
+ assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0);
+ option += 4;
+
+ len -= 8;
+ (*routes_size)++;
+ }
+
+ return 0;
+}
+
+/* parses RFC3442 Classless Static Route Option */
+static int lease_parse_classless_routes(
+ const uint8_t *option, size_t len,
+ struct sd_dhcp_route **routes, size_t *routes_size, size_t *routes_allocated) {
+
+ assert(option || len <= 0);
+ assert(routes);
+ assert(routes_size);
+ assert(routes_allocated);
+
+ if (len <= 0)
+ return 0;
+
+ /* option format: (subnet-mask-width significant-subnet-octets gateway-ip)* */
+
+ while (len > 0) {
+ uint8_t dst_octets;
+ struct sd_dhcp_route *route;
+
+ if (!GREEDY_REALLOC(*routes, *routes_allocated, *routes_size + 1))
+ return -ENOMEM;
+
+ route = *routes + *routes_size;
+ route->option = SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE;
+
+ dst_octets = (*option == 0 ? 0 : ((*option - 1) / 8) + 1);
+ route->dst_prefixlen = *option;
+ option++;
+ len--;
+
+ /* can't have more than 4 octets in IPv4 */
+ if (dst_octets > 4 || len < dst_octets)
+ return -EINVAL;
+
+ route->dst_addr.s_addr = 0;
+ memcpy(&route->dst_addr.s_addr, option, dst_octets);
+ option += dst_octets;
+ len -= dst_octets;
+
+ if (len < 4)
+ return -EINVAL;
+
+ assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0);
+ option += 4;
+ len -= 4;
+
+ (*routes_size)++;
+ }
+
+ return 0;
+}
+
+int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata) {
+ sd_dhcp_lease *lease = userdata;
+ int r;
+
+ assert(lease);
+
+ switch(code) {
+
+ case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
+ r = lease_parse_u32(option, len, &lease->lifetime, 1);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse lease time, ignoring: %m");
+
+ break;
+
+ case SD_DHCP_OPTION_SERVER_IDENTIFIER:
+ r = lease_parse_be32(option, len, &lease->server_address);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse server identifier, ignoring: %m");
+
+ break;
+
+ case SD_DHCP_OPTION_SUBNET_MASK:
+ r = lease_parse_be32(option, len, &lease->subnet_mask);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse subnet mask, ignoring: %m");
+ else
+ lease->have_subnet_mask = true;
+ break;
+
+ case SD_DHCP_OPTION_BROADCAST:
+ r = lease_parse_be32(option, len, &lease->broadcast);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse broadcast address, ignoring: %m");
+ else
+ lease->have_broadcast = true;
+ break;
+
+ case SD_DHCP_OPTION_ROUTER:
+ r = lease_parse_in_addrs(option, len, &lease->router, &lease->router_size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse router addresses, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_DOMAIN_NAME_SERVER:
+ r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_DNS].addr, &lease->servers[SD_DHCP_LEASE_DNS].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse DNS server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_NTP_SERVER:
+ r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_NTP].addr, &lease->servers[SD_DHCP_LEASE_NTP].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse NTP server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_SIP_SERVER:
+ r = lease_parse_sip_server(option, len, &lease->servers[SD_DHCP_LEASE_SIP].addr, &lease->servers[SD_DHCP_LEASE_SIP].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse SIP server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_POP3_SERVER:
+ r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_POP3].addr, &lease->servers[SD_DHCP_LEASE_POP3].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse POP3 server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_SMTP_SERVER:
+ r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_SMTP].addr, &lease->servers[SD_DHCP_LEASE_SMTP].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse SMTP server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_LPR_SERVER:
+ r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_LPR].addr, &lease->servers[SD_DHCP_LEASE_LPR].size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse LPR server, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_STATIC_ROUTE:
+ r = lease_parse_routes(option, len, &lease->static_route, &lease->static_route_size, &lease->static_route_allocated);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse static routes, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_INTERFACE_MTU:
+ r = lease_parse_u16(option, len, &lease->mtu, 68);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse MTU, ignoring: %m");
+ if (lease->mtu < DHCP_DEFAULT_MIN_SIZE) {
+ log_debug("MTU value of %" PRIu16 " too small. Using default MTU value of %d instead.", lease->mtu, DHCP_DEFAULT_MIN_SIZE);
+ lease->mtu = DHCP_DEFAULT_MIN_SIZE;
+ }
+
+ break;
+
+ case SD_DHCP_OPTION_DOMAIN_NAME:
+ r = lease_parse_domain(option, len, &lease->domainname);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse domain name, ignoring: %m");
+ return 0;
+ }
+
+ break;
+
+ case SD_DHCP_OPTION_DOMAIN_SEARCH_LIST:
+ r = dhcp_lease_parse_search_domains(option, len, &lease->search_domains);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse Domain Search List, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_HOST_NAME:
+ r = lease_parse_domain(option, len, &lease->hostname);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse hostname, ignoring: %m");
+ return 0;
+ }
+
+ break;
+
+ case SD_DHCP_OPTION_ROOT_PATH:
+ r = lease_parse_string(option, len, &lease->root_path);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse root path, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_RENEWAL_T1_TIME:
+ r = lease_parse_u32(option, len, &lease->t1, 1);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse T1 time, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_REBINDING_T2_TIME:
+ r = lease_parse_u32(option, len, &lease->t2, 1);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse T2 time, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
+ r = lease_parse_classless_routes(
+ option, len,
+ &lease->static_route,
+ &lease->static_route_size,
+ &lease->static_route_allocated);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse classless routes, ignoring: %m");
+ break;
+
+ case SD_DHCP_OPTION_NEW_TZDB_TIMEZONE: {
+ _cleanup_free_ char *tz = NULL;
+
+ r = lease_parse_string(option, len, &tz);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse timezone option, ignoring: %m");
+ return 0;
+ }
+
+ if (!timezone_is_valid(tz, LOG_DEBUG)) {
+ log_debug_errno(r, "Timezone is not valid, ignoring: %m");
+ return 0;
+ }
+
+ free_and_replace(lease->timezone, tz);
+
+ break;
+ }
+
+ case SD_DHCP_OPTION_VENDOR_SPECIFIC:
+
+ if (len <= 0)
+ lease->vendor_specific = mfree(lease->vendor_specific);
+ else {
+ void *p;
+
+ p = memdup(option, len);
+ if (!p)
+ return -ENOMEM;
+
+ free(lease->vendor_specific);
+ lease->vendor_specific = p;
+ }
+
+ lease->vendor_specific_len = len;
+ break;
+
+ case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST:
+ r = dhcp_lease_insert_private_option(lease, code, option, len);
+ if (r < 0)
+ return r;
+
+ break;
+
+ default:
+ log_debug("Ignoring option DHCP option %"PRIu8" while parsing.", code);
+ break;
+ }
+
+ return 0;
+}
+
+/* Parses compressed domain names. */
+int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains) {
+ _cleanup_strv_free_ char **names = NULL;
+ size_t pos = 0, cnt = 0;
+ int r;
+
+ assert(domains);
+ assert_return(option && len > 0, -ENODATA);
+
+ while (pos < len) {
+ _cleanup_free_ char *name = NULL;
+ size_t n = 0, allocated = 0;
+ size_t jump_barrier = pos, next_chunk = 0;
+ bool first = true;
+
+ for (;;) {
+ uint8_t c;
+ c = option[pos++];
+
+ if (c == 0) {
+ /* End of name */
+ break;
+ } else if (c <= 63) {
+ const char *label;
+
+ /* Literal label */
+ label = (const char*) (option + pos);
+ pos += c;
+ if (pos >= len)
+ return -EBADMSG;
+
+ if (!GREEDY_REALLOC(name, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+ return -ENOMEM;
+
+ if (first)
+ first = false;
+ else
+ name[n++] = '.';
+
+ r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ n += r;
+ } else if (FLAGS_SET(c, 0xc0)) {
+ /* Pointer */
+
+ uint8_t d;
+ uint16_t ptr;
+
+ if (pos >= len)
+ return -EBADMSG;
+
+ d = option[pos++];
+ ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d;
+
+ /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */
+ if (ptr >= jump_barrier)
+ return -EBADMSG;
+ jump_barrier = ptr;
+
+ /* Save current location so we don't end up re-parsing what's parsed so far. */
+ if (next_chunk == 0)
+ next_chunk = pos;
+
+ pos = ptr;
+ } else
+ return -EBADMSG;
+ }
+
+ if (!GREEDY_REALLOC(name, allocated, n + 1))
+ return -ENOMEM;
+ name[n] = 0;
+
+ r = strv_extend(&names, name);
+ if (r < 0)
+ return r;
+
+ cnt++;
+
+ if (next_chunk != 0)
+ pos = next_chunk;
+ }
+
+ *domains = TAKE_PTR(names);
+
+ return cnt;
+}
+
+int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) {
+ struct sd_dhcp_raw_option *cur, *option;
+
+ assert(lease);
+
+ LIST_FOREACH(options, cur, lease->private_options) {
+ if (tag < cur->tag)
+ break;
+ if (tag == cur->tag) {
+ log_debug("Ignoring duplicate option, tagged %i.", tag);
+ return 0;
+ }
+ }
+
+ option = new(struct sd_dhcp_raw_option, 1);
+ if (!option)
+ return -ENOMEM;
+
+ option->tag = tag;
+ option->length = len;
+ option->data = memdup(data, len);
+ if (!option->data) {
+ free(option);
+ return -ENOMEM;
+ }
+
+ LIST_INSERT_BEFORE(options, lease->private_options, cur, option);
+ return 0;
+}
+
+int dhcp_lease_new(sd_dhcp_lease **ret) {
+ sd_dhcp_lease *lease;
+
+ lease = new0(sd_dhcp_lease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ lease->n_ref = 1;
+
+ *ret = lease;
+ return 0;
+}
+
+int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ struct sd_dhcp_raw_option *option;
+ struct in_addr address;
+ const struct in_addr *addresses;
+ const void *client_id, *data;
+ size_t client_id_len, data_len;
+ char sbuf[INET_ADDRSTRLEN];
+ const char *string;
+ uint16_t mtu;
+ _cleanup_free_ sd_dhcp_route **routes = NULL;
+ char **search_domains = NULL;
+ uint32_t t1, t2, lifetime;
+ int r;
+
+ assert(lease);
+ assert(lease_file);
+
+ r = fopen_temporary(lease_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ (void) fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n");
+
+ r = sd_dhcp_lease_get_address(lease, &address);
+ if (r >= 0)
+ fprintf(f, "ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
+
+ r = sd_dhcp_lease_get_netmask(lease, &address);
+ if (r >= 0)
+ fprintf(f, "NETMASK=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
+
+ r = sd_dhcp_lease_get_router(lease, &addresses);
+ if (r > 0) {
+ fputs("ROUTER=", f);
+ serialize_in_addrs(f, addresses, r, false, NULL);
+ fputc('\n', f);
+ }
+
+ r = sd_dhcp_lease_get_server_identifier(lease, &address);
+ if (r >= 0)
+ fprintf(f, "SERVER_ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
+
+ r = sd_dhcp_lease_get_next_server(lease, &address);
+ if (r >= 0)
+ fprintf(f, "NEXT_SERVER=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
+
+ r = sd_dhcp_lease_get_broadcast(lease, &address);
+ if (r >= 0)
+ fprintf(f, "BROADCAST=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
+
+ r = sd_dhcp_lease_get_mtu(lease, &mtu);
+ if (r >= 0)
+ fprintf(f, "MTU=%" PRIu16 "\n", mtu);
+
+ r = sd_dhcp_lease_get_t1(lease, &t1);
+ if (r >= 0)
+ fprintf(f, "T1=%" PRIu32 "\n", t1);
+
+ r = sd_dhcp_lease_get_t2(lease, &t2);
+ if (r >= 0)
+ fprintf(f, "T2=%" PRIu32 "\n", t2);
+
+ r = sd_dhcp_lease_get_lifetime(lease, &lifetime);
+ if (r >= 0)
+ fprintf(f, "LIFETIME=%" PRIu32 "\n", lifetime);
+
+ r = sd_dhcp_lease_get_dns(lease, &addresses);
+ if (r > 0) {
+ fputs("DNS=", f);
+ serialize_in_addrs(f, addresses, r, false, NULL);
+ fputc('\n', f);
+ }
+
+ r = sd_dhcp_lease_get_ntp(lease, &addresses);
+ if (r > 0) {
+ fputs("NTP=", f);
+ serialize_in_addrs(f, addresses, r, false, NULL);
+ fputc('\n', f);
+ }
+
+ r = sd_dhcp_lease_get_sip(lease, &addresses);
+ if (r > 0) {
+ fputs("SIP=", f);
+ serialize_in_addrs(f, addresses, r, false, NULL);
+ fputc('\n', f);
+ }
+
+ r = sd_dhcp_lease_get_domainname(lease, &string);
+ if (r >= 0)
+ fprintf(f, "DOMAINNAME=%s\n", string);
+
+ r = sd_dhcp_lease_get_search_domains(lease, &search_domains);
+ if (r > 0) {
+ fputs("DOMAIN_SEARCH_LIST=", f);
+ fputstrv(f, search_domains, NULL, NULL);
+ fputc('\n', f);
+ }
+
+ r = sd_dhcp_lease_get_hostname(lease, &string);
+ if (r >= 0)
+ fprintf(f, "HOSTNAME=%s\n", string);
+
+ r = sd_dhcp_lease_get_root_path(lease, &string);
+ if (r >= 0)
+ fprintf(f, "ROOT_PATH=%s\n", string);
+
+ r = sd_dhcp_lease_get_routes(lease, &routes);
+ if (r > 0)
+ serialize_dhcp_routes(f, "ROUTES", routes, r);
+
+ r = sd_dhcp_lease_get_timezone(lease, &string);
+ if (r >= 0)
+ fprintf(f, "TIMEZONE=%s\n", string);
+
+ r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len);
+ if (r >= 0) {
+ _cleanup_free_ char *client_id_hex = NULL;
+
+ client_id_hex = hexmem(client_id, client_id_len);
+ if (!client_id_hex) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ fprintf(f, "CLIENTID=%s\n", client_id_hex);
+ }
+
+ r = sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len);
+ if (r >= 0) {
+ _cleanup_free_ char *option_hex = NULL;
+
+ option_hex = hexmem(data, data_len);
+ if (!option_hex) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ fprintf(f, "VENDOR_SPECIFIC=%s\n", option_hex);
+ }
+
+ LIST_FOREACH(options, option, lease->private_options) {
+ char key[STRLEN("OPTION_000")+1];
+
+ xsprintf(key, "OPTION_%" PRIu8, option->tag);
+ r = serialize_dhcp_option(f, key, option->data, option->length);
+ if (r < 0)
+ goto fail;
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, lease_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (temp_path)
+ (void) unlink(temp_path);
+
+ return log_error_errno(r, "Failed to save lease data %s: %m", lease_file);
+}
+
+int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
+
+ _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+ _cleanup_free_ char
+ *address = NULL,
+ *router = NULL,
+ *netmask = NULL,
+ *server_address = NULL,
+ *next_server = NULL,
+ *broadcast = NULL,
+ *dns = NULL,
+ *ntp = NULL,
+ *sip = NULL,
+ *pop3 = NULL,
+ *smtp = NULL,
+ *lpr = NULL,
+ *mtu = NULL,
+ *routes = NULL,
+ *domains = NULL,
+ *client_id_hex = NULL,
+ *vendor_specific_hex = NULL,
+ *lifetime = NULL,
+ *t1 = NULL,
+ *t2 = NULL,
+ *options[SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1] = {};
+
+ int r, i;
+
+ assert(lease_file);
+ assert(ret);
+
+ r = dhcp_lease_new(&lease);
+ if (r < 0)
+ return r;
+
+ r = parse_env_file(NULL, lease_file,
+ "ADDRESS", &address,
+ "ROUTER", &router,
+ "NETMASK", &netmask,
+ "SERVER_ADDRESS", &server_address,
+ "NEXT_SERVER", &next_server,
+ "BROADCAST", &broadcast,
+ "DNS", &dns,
+ "NTP", &ntp,
+ "SIP", &sip,
+ "POP3", &pop3,
+ "SMTP", &smtp,
+ "LPR", &lpr,
+ "MTU", &mtu,
+ "DOMAINNAME", &lease->domainname,
+ "HOSTNAME", &lease->hostname,
+ "DOMAIN_SEARCH_LIST", &domains,
+ "ROOT_PATH", &lease->root_path,
+ "ROUTES", &routes,
+ "CLIENTID", &client_id_hex,
+ "TIMEZONE", &lease->timezone,
+ "VENDOR_SPECIFIC", &vendor_specific_hex,
+ "LIFETIME", &lifetime,
+ "T1", &t1,
+ "T2", &t2,
+ "OPTION_224", &options[0],
+ "OPTION_225", &options[1],
+ "OPTION_226", &options[2],
+ "OPTION_227", &options[3],
+ "OPTION_228", &options[4],
+ "OPTION_229", &options[5],
+ "OPTION_230", &options[6],
+ "OPTION_231", &options[7],
+ "OPTION_232", &options[8],
+ "OPTION_233", &options[9],
+ "OPTION_234", &options[10],
+ "OPTION_235", &options[11],
+ "OPTION_236", &options[12],
+ "OPTION_237", &options[13],
+ "OPTION_238", &options[14],
+ "OPTION_239", &options[15],
+ "OPTION_240", &options[16],
+ "OPTION_241", &options[17],
+ "OPTION_242", &options[18],
+ "OPTION_243", &options[19],
+ "OPTION_244", &options[20],
+ "OPTION_245", &options[21],
+ "OPTION_246", &options[22],
+ "OPTION_247", &options[23],
+ "OPTION_248", &options[24],
+ "OPTION_249", &options[25],
+ "OPTION_250", &options[26],
+ "OPTION_251", &options[27],
+ "OPTION_252", &options[28],
+ "OPTION_253", &options[29],
+ "OPTION_254", &options[30]);
+ if (r < 0)
+ return r;
+
+ if (address) {
+ r = inet_pton(AF_INET, address, &lease->address);
+ if (r <= 0)
+ log_debug("Failed to parse address %s, ignoring.", address);
+ }
+
+ if (router) {
+ r = deserialize_in_addrs(&lease->router, router);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize router addresses %s, ignoring: %m", router);
+ else
+ lease->router_size = r;
+ }
+
+ if (netmask) {
+ r = inet_pton(AF_INET, netmask, &lease->subnet_mask);
+ if (r <= 0)
+ log_debug("Failed to parse netmask %s, ignoring.", netmask);
+ else
+ lease->have_subnet_mask = true;
+ }
+
+ if (server_address) {
+ r = inet_pton(AF_INET, server_address, &lease->server_address);
+ if (r <= 0)
+ log_debug("Failed to parse server address %s, ignoring.", server_address);
+ }
+
+ if (next_server) {
+ r = inet_pton(AF_INET, next_server, &lease->next_server);
+ if (r <= 0)
+ log_debug("Failed to parse next server %s, ignoring.", next_server);
+ }
+
+ if (broadcast) {
+ r = inet_pton(AF_INET, broadcast, &lease->broadcast);
+ if (r <= 0)
+ log_debug("Failed to parse broadcast address %s, ignoring.", broadcast);
+ else
+ lease->have_broadcast = true;
+ }
+
+ if (dns) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_DNS].addr, dns);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize DNS servers %s, ignoring: %m", dns);
+ else
+ lease->servers[SD_DHCP_LEASE_DNS].size = r;
+ }
+
+ if (ntp) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_NTP].addr, ntp);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize NTP servers %s, ignoring: %m", ntp);
+ else
+ lease->servers[SD_DHCP_LEASE_NTP].size = r;
+ }
+
+ if (sip) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SIP].addr, sip);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize SIP servers %s, ignoring: %m", sip);
+ else
+ lease->servers[SD_DHCP_LEASE_SIP].size = r;
+ }
+
+ if (pop3) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_POP3].addr, pop3);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize POP3 server %s, ignoring: %m", pop3);
+ else
+ lease->servers[SD_DHCP_LEASE_POP3].size = r;
+ }
+
+ if (smtp) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SMTP].addr, smtp);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize SMTP server %s, ignoring: %m", smtp);
+ else
+ lease->servers[SD_DHCP_LEASE_SMTP].size = r;
+ }
+
+ if (lpr) {
+ r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_LPR].addr, lpr);
+ if (r < 0)
+ log_debug_errno(r, "Failed to deserialize LPR server %s, ignoring: %m", lpr);
+ else
+ lease->servers[SD_DHCP_LEASE_LPR].size = r;
+ }
+
+ if (mtu) {
+ r = safe_atou16(mtu, &lease->mtu);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse MTU %s, ignoring: %m", mtu);
+ }
+
+ if (domains) {
+ _cleanup_strv_free_ char **a = NULL;
+ a = strv_split(domains, " ");
+ if (!a)
+ return -ENOMEM;
+
+ if (!strv_isempty(a))
+ lease->search_domains = TAKE_PTR(a);
+ }
+
+ if (routes) {
+ r = deserialize_dhcp_routes(
+ &lease->static_route,
+ &lease->static_route_size,
+ &lease->static_route_allocated,
+ routes);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse DHCP routes %s, ignoring: %m", routes);
+ }
+
+ if (lifetime) {
+ r = safe_atou32(lifetime, &lease->lifetime);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse lifetime %s, ignoring: %m", lifetime);
+ }
+
+ if (t1) {
+ r = safe_atou32(t1, &lease->t1);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse T1 %s, ignoring: %m", t1);
+ }
+
+ if (t2) {
+ r = safe_atou32(t2, &lease->t2);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse T2 %s, ignoring: %m", t2);
+ }
+
+ if (client_id_hex) {
+ r = unhexmem(client_id_hex, (size_t) -1, &lease->client_id, &lease->client_id_len);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex);
+ }
+
+ if (vendor_specific_hex) {
+ r = unhexmem(vendor_specific_hex, (size_t) -1, &lease->vendor_specific, &lease->vendor_specific_len);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex);
+ }
+
+ for (i = 0; i <= SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE; i++) {
+ _cleanup_free_ void *data = NULL;
+ size_t len;
+
+ if (!options[i])
+ continue;
+
+ r = unhexmem(options[i], (size_t) -1, &data, &len);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]);
+ continue;
+ }
+
+ r = dhcp_lease_insert_private_option(lease, SD_DHCP_OPTION_PRIVATE_BASE + i, data, len);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(lease);
+
+ return 0;
+}
+
+int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) {
+ struct in_addr address, mask;
+ int r;
+
+ assert(lease);
+
+ if (lease->address == 0)
+ return -ENODATA;
+
+ address.s_addr = lease->address;
+
+ /* fall back to the default subnet masks based on address class */
+ r = in4_addr_default_subnet_mask(&address, &mask);
+ if (r < 0)
+ return r;
+
+ lease->subnet_mask = mask.s_addr;
+ lease->have_subnet_mask = true;
+
+ return 0;
+}
+
+int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len) {
+ assert_return(lease, -EINVAL);
+ assert_return(client_id, -EINVAL);
+ assert_return(client_id_len, -EINVAL);
+
+ if (!lease->client_id)
+ return -ENODATA;
+
+ *client_id = lease->client_id;
+ *client_id_len = lease->client_id_len;
+
+ return 0;
+}
+
+int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len) {
+ assert_return(lease, -EINVAL);
+ assert_return(client_id || client_id_len <= 0, -EINVAL);
+
+ if (client_id_len <= 0)
+ lease->client_id = mfree(lease->client_id);
+ else {
+ void *p;
+
+ p = memdup(client_id, client_id_len);
+ if (!p)
+ return -ENOMEM;
+
+ free(lease->client_id);
+ lease->client_id = p;
+ lease->client_id_len = client_id_len;
+ }
+
+ return 0;
+}
+
+int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **tz) {
+ assert_return(lease, -EINVAL);
+ assert_return(tz, -EINVAL);
+
+ if (!lease->timezone)
+ return -ENODATA;
+
+ *tz = lease->timezone;
+ return 0;
+}
+
+int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination) {
+ assert_return(route, -EINVAL);
+ assert_return(destination, -EINVAL);
+
+ *destination = route->dst_addr;
+ return 0;
+}
+
+int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length) {
+ assert_return(route, -EINVAL);
+ assert_return(length, -EINVAL);
+
+ *length = route->dst_prefixlen;
+ return 0;
+}
+
+int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) {
+ assert_return(route, -EINVAL);
+ assert_return(gateway, -EINVAL);
+
+ *gateway = route->gw_addr;
+ return 0;
+}
+
+int sd_dhcp_route_get_option(sd_dhcp_route *route) {
+ assert_return(route, -EINVAL);
+
+ return route->option;
+}
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
new file mode 100644
index 0000000..dfced72
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -0,0 +1,1222 @@
+/* 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-internal.h"
+#include "dhcp-server-internal.h"
+#include "fd-util.h"
+#include "in-addr-util.h"
+#include "io-util.h"
+#include "siphash24.h"
+#include "string-util.h"
+#include "unaligned.h"
+
+#define DHCP_DEFAULT_LEASE_TIME_USEC USEC_PER_HOUR
+#define DHCP_MAX_LEASE_TIME_USEC (USEC_PER_HOUR*12)
+
+static DHCPLease *dhcp_lease_free(DHCPLease *lease) {
+ if (!lease)
+ return NULL;
+
+ free(lease->client_id.data);
+ 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) {
+
+ free(server->bound_leases);
+ server->bound_leases = new0(DHCPLease*, size);
+ if (!server->bound_leases)
+ return -ENOMEM;
+
+ server->pool_offset = offset;
+ server->pool_size = size;
+
+ server->address = address->s_addr;
+ server->netmask = netmask;
+ server->subnet = address->s_addr & netmask;
+
+ if (server_off >= offset && server_off - offset < size)
+ server->bound_leases[server_off - offset] = &server->invalid_lease;
+
+ /* Drop any leases associated with the old address range */
+ hashmap_clear(server->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) {
+ assert_return(server, false);
+
+ return !!server->receive_message;
+}
+
+void client_id_hash_func(const DHCPClientId *id, struct siphash *state) {
+ assert(id);
+ assert(id->length);
+ 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 || a->data);
+ assert(!b->length || b->data);
+
+ r = CMP(a->length, b->length);
+ if (r != 0)
+ return r;
+
+ return memcmp(a->data, b->data, a->length);
+}
+
+DEFINE_PRIVATE_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);
+
+ log_dhcp_server(server, "UNREF");
+
+ sd_dhcp_server_stop(server);
+
+ sd_event_unref(server->event);
+
+ free(server->timezone);
+
+ for (sd_dhcp_lease_server_type i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++)
+ free(server->servers[i].addr);
+
+ hashmap_free(server->leases_by_client_id);
+
+ ordered_hashmap_free(server->extra_options);
+ ordered_hashmap_free(server->vendor_options);
+
+ free(server->bound_leases);
+ 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 = new0(sd_dhcp_server, 1);
+ if (!server)
+ return -ENOMEM;
+
+ server->n_ref = 1;
+ server->fd_raw = -1;
+ server->fd = -1;
+ server->address = htobe32(INADDR_ANY);
+ server->netmask = htobe32(INADDR_ANY);
+ server->ifindex = ifindex;
+
+ server->leases_by_client_id = hashmap_new(&dhcp_lease_hash_ops);
+ if (!server->leases_by_client_id)
+ return -ENOMEM;
+
+ server->default_lease_time = DIV_ROUND_UP(DHCP_DEFAULT_LEASE_TIME_USEC, USEC_PER_SEC);
+ server->max_lease_time = DIV_ROUND_UP(DHCP_MAX_LEASE_TIME_USEC, USEC_PER_SEC);
+
+ *ret = TAKE_PTR(server);
+
+ 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_stop(sd_dhcp_server *server) {
+ if (!server)
+ return 0;
+
+ server->receive_message =
+ sd_event_source_unref(server->receive_message);
+
+ server->fd_raw = safe_close(server->fd_raw);
+ server->fd = safe_close(server->fd);
+
+ log_dhcp_server(server, "STOPPED");
+
+ return 0;
+}
+
+static int dhcp_server_send_unicast_raw(sd_dhcp_server *server,
+ 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 = ETH_ALEN,
+ };
+
+ assert(server);
+ assert(server->ifindex > 0);
+ assert(server->address);
+ assert(packet);
+ assert(len > sizeof(DHCPPacket));
+
+ memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN);
+
+ 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,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ struct in_pktinfo *pktinfo;
+
+ assert(server);
+ assert(server->fd >= 0);
+ assert(message);
+ assert(len > sizeof(DHCPMessage));
+
+ 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 = (struct in_pktinfo*) CMSG_DATA(cmsg);
+ 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(DHCPRequest *req) {
+ assert(req);
+
+ return req->message->flags & htobe16(0x8000);
+}
+
+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);
+ 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;
+
+ 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) {
+ destination = req->message->giaddr;
+ destination_port = DHCP_PORT_SERVER;
+ if (type == DHCP_NAK)
+ packet->dhcp.flags = htobe16(0x8000);
+ } else if (req->message->ciaddr && type != DHCP_NAK)
+ destination = req->message->ciaddr;
+
+ if (destination != INADDR_ANY)
+ return dhcp_server_send_udp(server, destination,
+ destination_port, &packet->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+ else if (requested_broadcast(req) || type == DHCP_NAK)
+ 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, packet,
+ sizeof(DHCPPacket) + optoffset);
+}
+
+static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
+ uint8_t type, size_t *_optoffset,
+ DHCPRequest *req) {
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ size_t optoffset = 0;
+ int r;
+
+ assert(server);
+ assert(ret);
+ assert(_optoffset);
+ assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK));
+
+ packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
+ if (!packet)
+ return -ENOMEM;
+
+ r = dhcp_message_init(&packet->dhcp, BOOTREPLY,
+ be32toh(req->message->xid), type, ARPHRD_ETHER,
+ req->max_optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ packet->dhcp.flags = req->message->flags;
+ packet->dhcp.giaddr = req->message->giaddr;
+ memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN);
+
+ *_optoffset = optoffset;
+ *ret = TAKE_PTR(packet);
+
+ return 0;
+}
+
+static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req,
+ be32_t address) {
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ size_t offset;
+ be32_t lease_time;
+ int r;
+
+ r = server_message_init(server, &packet, DHCP_OFFER, &offset, req);
+ if (r < 0)
+ return r;
+
+ packet->dhcp.yiaddr = address;
+
+ lease_time = htobe32(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, &server->address);
+ if (r < 0)
+ return r;
+ }
+
+ r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int server_send_ack(
+ sd_dhcp_server *server,
+ DHCPRequest *req,
+ be32_t address) {
+
+ 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;
+ be32_t lease_time;
+ sd_dhcp_option *j;
+ size_t offset;
+ int r;
+
+ r = server_message_init(server, &packet, DHCP_ACK, &offset, req);
+ if (r < 0)
+ return r;
+
+ packet->dhcp.yiaddr = address;
+
+ lease_time = htobe32(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, &server->address);
+ if (r < 0)
+ return r;
+ }
+
+ for (sd_dhcp_lease_server_type 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_NEW_TZDB_TIMEZONE,
+ strlen(server->timezone), server->timezone);
+ if (r < 0)
+ return r;
+ }
+
+ ORDERED_HASHMAP_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_hashmap_isempty(server->vendor_options)) {
+ r = dhcp_option_append(
+ &packet->dhcp, req->max_optlen, &offset, 0,
+ SD_DHCP_OPTION_VENDOR_SPECIFIC,
+ ordered_hashmap_size(server->vendor_options), server->vendor_options);
+ if (r < 0)
+ return r;
+ }
+
+ r = dhcp_server_send_packet(server, req, packet, DHCP_ACK, offset);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int server_send_nak(sd_dhcp_server *server, DHCPRequest *req) {
+ _cleanup_free_ DHCPPacket *packet = NULL;
+ size_t offset;
+ int r;
+
+ r = server_message_init(server, &packet, DHCP_NAK, &offset, req);
+ if (r < 0)
+ return r;
+
+ return dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset);
+}
+
+static int server_send_forcerenew(sd_dhcp_server *server, be32_t address,
+ be32_t gateway, 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, ARPHRD_ETHER,
+ 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;
+
+ memcpy(&packet->dhcp.chaddr, chaddr, ETH_ALEN);
+
+ r = dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT,
+ &packet->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) {
+ DHCPRequest *req = userdata;
+
+ assert(req);
+
+ switch(code) {
+ case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
+ if (len == 4)
+ req->lifetime = unaligned_read_be32(option);
+
+ 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(req->client_id.data);
+ 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;
+ }
+
+ return 0;
+}
+
+static void dhcp_request_free(DHCPRequest *req) {
+ if (!req)
+ return;
+
+ free(req->client_id.data);
+ free(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;
+
+ /* set client id based on MAC address if client did not send an explicit
+ one */
+ if (!req->client_id.data) {
+ void *data;
+
+ data = malloc0(ETH_ALEN + 1);
+ if (!data)
+ return -ENOMEM;
+
+ ((uint8_t*) data)[0] = 0x01;
+ memcpy((uint8_t*) data + 1, &message->chaddr, ETH_ALEN);
+
+ req->client_id.length = ETH_ALEN + 1;
+ req->client_id.data = data;
+ }
+
+ if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
+ req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
+
+ if (req->lifetime <= 0)
+ req->lifetime = MAX(1ULL, 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 int get_pool_offset(sd_dhcp_server *server, be32_t requested_ip) {
+ assert(server);
+
+ if (!server->pool_size)
+ return -EINVAL;
+
+ if (be32toh(requested_ip) < (be32toh(server->subnet) | server->pool_offset) ||
+ be32toh(requested_ip) >= (be32toh(server->subnet) | (server->pool_offset + server->pool_size)))
+ return -ERANGE;
+
+ return be32toh(requested_ip & ~server->netmask) - server->pool_offset;
+}
+
+#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) {
+ _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL;
+ _cleanup_free_ char *error_message = NULL;
+ DHCPLease *existing_lease;
+ int type, r;
+
+ assert(server);
+ assert(message);
+
+ if (message->op != BOOTREQUEST ||
+ message->htype != ARPHRD_ETHER ||
+ message->hlen != ETHER_ADDR_LEN)
+ 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 0;
+
+ r = ensure_sane_request(server, req, message);
+ if (r < 0)
+ /* this only fails on critical errors */
+ return r;
+
+ existing_lease = hashmap_get(server->leases_by_client_id,
+ &req->client_id);
+
+ switch(type) {
+
+ case DHCP_DISCOVER: {
+ be32_t address = INADDR_ANY;
+ unsigned i;
+
+ log_dhcp_server(server, "DISCOVER (0x%x)",
+ be32toh(req->message->xid));
+
+ if (!server->pool_size)
+ /* no pool allocated */
+ return 0;
+
+ /* for now pick a random free address from the pool */
+ if (existing_lease)
+ address = existing_lease->address;
+ else {
+ struct siphash state;
+ uint64_t hash;
+ uint32_t next_offer;
+
+ /* 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));
+ next_offer = hash % server->pool_size;
+
+ for (i = 0; i < server->pool_size; i++) {
+ if (!server->bound_leases[next_offer]) {
+ address = server->subnet | htobe32(server->pool_offset + next_offer);
+ break;
+ }
+
+ next_offer = (next_offer + 1) % server->pool_size;
+ }
+ }
+
+ if (address == INADDR_ANY)
+ /* no free addresses left */
+ return 0;
+
+ r = server_send_offer(server, req, address);
+ 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;
+ int pool_offset;
+
+ /* see RFC 2131, section 4.3.2 */
+
+ if (req->server_id) {
+ 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)
+ /* this MUST be zero */
+ return 0;
+
+ if (!req->requested_ip)
+ /* this must be filled in with the yiaddr
+ from the chosen OFFER */
+ return 0;
+
+ address = req->requested_ip;
+ } else if (req->requested_ip) {
+ log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)",
+ be32toh(req->message->xid));
+
+ /* INIT-REBOOT */
+ if (req->message->ciaddr)
+ /* 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)
+ /* this MUST be filled in with clients IP address */
+ return 0;
+
+ address = req->message->ciaddr;
+ }
+
+ pool_offset = get_pool_offset(server, address);
+
+ /* verify that the requested address is from the pool, and either
+ owned by the current client or free */
+ if (pool_offset >= 0 &&
+ server->bound_leases[pool_offset] == existing_lease) {
+ DHCPLease *lease;
+ usec_t time_now = 0;
+
+ if (!existing_lease) {
+ lease = new0(DHCPLease, 1);
+ if (!lease)
+ return -ENOMEM;
+ lease->address = address;
+ lease->client_id.data = memdup(req->client_id.data,
+ req->client_id.length);
+ if (!lease->client_id.data) {
+ free(lease);
+ return -ENOMEM;
+ }
+ lease->client_id.length = req->client_id.length;
+ memcpy(&lease->chaddr, &req->message->chaddr,
+ ETH_ALEN);
+ lease->gateway = req->message->giaddr;
+ } else
+ lease = existing_lease;
+
+ r = sd_event_now(server->event,
+ clock_boottime_or_monotonic(),
+ &time_now);
+ if (r < 0) {
+ if (!existing_lease)
+ dhcp_lease_free(lease);
+ return r;
+ }
+
+ lease->expiration = req->lifetime * USEC_PER_SEC + time_now;
+
+ r = server_send_ack(server, req, address);
+ if (r < 0) {
+ /* this only fails on critical errors */
+ log_dhcp_server_errno(server, r, "Could not send ack: %m");
+
+ if (!existing_lease)
+ dhcp_lease_free(lease);
+
+ return r;
+ } else {
+ log_dhcp_server(server, "ACK (0x%x)",
+ be32toh(req->message->xid));
+
+ server->bound_leases[pool_offset] = lease;
+ hashmap_put(server->leases_by_client_id,
+ &lease->client_id, lease);
+
+ if (server->callback)
+ server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata);
+
+ return DHCP_ACK;
+ }
+
+ } else if (init_reboot) {
+ r = server_send_nak(server, req);
+ if (r < 0)
+ /* this only fails on critical errors */
+ return log_dhcp_server_errno(server, r, "Could not send nak: %m");
+
+ log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->xid));
+ return DHCP_NAK;
+ }
+
+ break;
+ }
+
+ case DHCP_RELEASE: {
+ int pool_offset;
+
+ 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;
+
+ pool_offset = get_pool_offset(server, req->message->ciaddr);
+ if (pool_offset < 0)
+ return 0;
+
+ if (server->bound_leases[pool_offset] == existing_lease) {
+ server->bound_leases[pool_offset] = NULL;
+ hashmap_remove(server->leases_by_client_id, existing_lease);
+ 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 int server_receive_message(sd_event_source *s, int fd,
+ uint32_t revents, void *userdata) {
+ _cleanup_free_ DHCPMessage *message = NULL;
+ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control;
+ sd_dhcp_server *server = userdata;
+ struct iovec iov = {};
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ ssize_t buflen, len;
+ int r;
+
+ assert(server);
+
+ buflen = next_datagram_size_fd(fd);
+ if (buflen < 0)
+ return buflen;
+
+ message = malloc(buflen);
+ if (!message)
+ return -ENOMEM;
+
+ iov = IOVEC_MAKE(message, buflen);
+
+ len = recvmsg_safe(fd, &msg, 0);
+ if (IN_SET(len, -EAGAIN, -EINTR))
+ return 0;
+ if (len < 0)
+ return len;
+ if ((size_t) len < sizeof(DHCPMessage))
+ return 0;
+
+ CMSG_FOREACH(cmsg, &msg) {
+ if (cmsg->cmsg_level == IPPROTO_IP &&
+ cmsg->cmsg_type == IP_PKTINFO &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
+ struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
+
+ /* TODO figure out if this can be done as a filter on
+ * the socket, like for IPv6 */
+ if (server->ifindex != info->ipi_ifindex)
+ return 0;
+
+ break;
+ }
+ }
+
+ r = dhcp_server_handle_message(server, message, (size_t) len);
+ if (r < 0)
+ log_dhcp_server_errno(server, r, "Couldn't process incoming message: %m");
+
+ return 0;
+}
+
+int sd_dhcp_server_start(sd_dhcp_server *server) {
+ int r;
+
+ assert_return(server, -EINVAL);
+ assert_return(server->event, -EINVAL);
+ 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);
+
+ r = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (r < 0) {
+ r = -errno;
+ sd_dhcp_server_stop(server);
+ return r;
+ }
+ server->fd_raw = r;
+
+ r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1);
+ if (r < 0) {
+ sd_dhcp_server_stop(server);
+ return r;
+ }
+ server->fd = r;
+
+ r = sd_event_add_io(server->event, &server->receive_message,
+ server->fd, EPOLLIN,
+ server_receive_message, server);
+ if (r < 0) {
+ sd_dhcp_server_stop(server);
+ return r;
+ }
+
+ r = sd_event_source_set_priority(server->receive_message,
+ server->event_priority);
+ if (r < 0) {
+ sd_dhcp_server_stop(server);
+ return r;
+ }
+
+ log_dhcp_server(server, "STARTED");
+
+ return 0;
+}
+
+int sd_dhcp_server_forcerenew(sd_dhcp_server *server) {
+ unsigned i;
+ int r = 0;
+
+ assert_return(server, -EINVAL);
+ assert(server->bound_leases);
+
+ for (i = 0; i < server->pool_size; i++) {
+ DHCPLease *lease = server->bound_leases[i];
+
+ if (!lease || lease == &server->invalid_lease)
+ continue;
+
+ r = server_send_forcerenew(server, lease->address,
+ lease->gateway,
+ lease->chaddr);
+ if (r < 0)
+ return r;
+
+ log_dhcp_server(server, "FORCERENEW");
+ }
+
+ return r;
+}
+
+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, uint32_t t) {
+ assert_return(server, -EINVAL);
+
+ if (t == server->max_lease_time)
+ return 0;
+
+ server->max_lease_time = t;
+ return 1;
+}
+
+int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint32_t t) {
+ assert_return(server, -EINVAL);
+
+ if (t == server->default_lease_time)
+ return 0;
+
+ server->default_lease_time = t;
+ return 1;
+}
+
+int sd_dhcp_server_set_servers(
+ sd_dhcp_server *server,
+ sd_dhcp_lease_server_type what,
+ const struct in_addr addresses[],
+ size_t n_addresses) {
+
+ struct in_addr *c = NULL;
+
+ assert_return(server, -EINVAL);
+ 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(server->servers[what].addr);
+ 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_emit_router(sd_dhcp_server *server, int enabled) {
+ assert_return(server, -EINVAL);
+
+ if (enabled == server->emit_router)
+ return 0;
+
+ server->emit_router = enabled;
+
+ return 1;
+}
+
+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_hashmap_ensure_allocated(&server->extra_options, &dhcp_option_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(server->extra_options, UINT_TO_PTR(v->option), 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_hashmap_ensure_allocated(&server->vendor_options, &dhcp_option_hash_ops);
+ if (r < 0)
+ return -ENOMEM;
+
+ r = ordered_hashmap_put(server->vendor_options, v, 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;
+}
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
new file mode 100644
index 0000000..d502051
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -0,0 +1,1848 @@
+/* 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 "dhcp-identifier.h"
+#include "dhcp6-internal.h"
+#include "dhcp6-lease-internal.h"
+#include "dhcp6-protocol.h"
+#include "dns-domain.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "hostname-util.h"
+#include "in-addr-util.h"
+#include "network-internal.h"
+#include "random-util.h"
+#include "socket-util.h"
+#include "string-table.h"
+#include "util.h"
+#include "web-util.h"
+
+#define MAX_MAC_ADDR_LEN INFINIBAND_ALEN
+
+#define IRT_DEFAULT (1 * USEC_PER_DAY)
+#define IRT_MINIMUM (600 * USEC_PER_SEC)
+
+/* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */
+enum {
+ DHCP6_REQUEST_IA_NA = 1,
+ DHCP6_REQUEST_IA_TA = 2, /* currently not used */
+ DHCP6_REQUEST_IA_PD = 4,
+};
+
+struct sd_dhcp6_client {
+ unsigned n_ref;
+
+ enum DHCP6State state;
+ sd_event *event;
+ int event_priority;
+ int ifindex;
+ DHCP6Address hint_pd_prefix;
+ struct in6_addr local_address;
+ uint8_t mac_addr[MAX_MAC_ADDR_LEN];
+ size_t mac_addr_len;
+ uint16_t arp_type;
+ DHCP6IA ia_na;
+ DHCP6IA ia_pd;
+ sd_event_source *timeout_t1;
+ sd_event_source *timeout_t2;
+ unsigned request;
+ be32_t transaction_id;
+ usec_t transaction_start;
+ struct sd_dhcp6_lease *lease;
+ int fd;
+ bool information_request;
+ bool iaid_set;
+ be16_t *req_opts;
+ size_t req_opts_allocated;
+ size_t req_opts_len;
+ char *fqdn;
+ char *mudurl;
+ char **user_class;
+ char **vendor_class;
+ sd_event_source *receive_message;
+ usec_t retransmit_time;
+ uint8_t retransmit_count;
+ sd_event_source *timeout_resend;
+ sd_event_source *timeout_resend_expire;
+ sd_dhcp6_client_callback_t callback;
+ void *userdata;
+ struct duid duid;
+ size_t duid_len;
+ usec_t information_request_time_usec;
+ usec_t information_refresh_time_usec;
+ OrderedHashmap *extra_options;
+ OrderedHashmap *vendor_options;
+};
+
+static const uint16_t default_req_opts[] = {
+ SD_DHCP6_OPTION_DNS_SERVERS,
+ SD_DHCP6_OPTION_DOMAIN_LIST,
+ SD_DHCP6_OPTION_NTP_SERVER,
+ SD_DHCP6_OPTION_SNTP_SERVERS,
+};
+
+const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
+ [DHCP6_SOLICIT] = "SOLICIT",
+ [DHCP6_ADVERTISE] = "ADVERTISE",
+ [DHCP6_REQUEST] = "REQUEST",
+ [DHCP6_CONFIRM] = "CONFIRM",
+ [DHCP6_RENEW] = "RENEW",
+ [DHCP6_REBIND] = "REBIND",
+ [DHCP6_REPLY] = "REPLY",
+ [DHCP6_RELEASE] = "RELEASE",
+ [DHCP6_DECLINE] = "DECLINE",
+ [DHCP6_RECONFIGURE] = "RECONFIGURE",
+ [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST",
+ [DHCP6_RELAY_FORW] = "RELAY-FORW",
+ [DHCP6_RELAY_REPL] = "RELAY-REPL",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
+
+const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
+ [DHCP6_STATUS_SUCCESS] = "Success",
+ [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure",
+ [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available",
+ [DHCP6_STATUS_NO_BINDING] = "Binding unavailable",
+ [DHCP6_STATUS_NOT_ON_LINK] = "Not on link",
+ [DHCP6_STATUS_USE_MULTICAST] = "Use multicast",
+ [DHCP6_STATUS_NO_PREFIX_AVAIL] = "No prefix available",
+ [DHCP6_STATUS_UNKNOWN_QUERY_TYPE] = "Unknown query type",
+ [DHCP6_STATUS_MALFORMED_QUERY] = "Malformed query",
+ [DHCP6_STATUS_NOT_CONFIGURED] = "Not configured",
+ [DHCP6_STATUS_NOT_ALLOWED] = "Not allowed",
+ [DHCP6_STATUS_QUERY_TERMINATED] = "Query terminated",
+ [DHCP6_STATUS_DATA_MISSING] = "Data missing",
+ [DHCP6_STATUS_CATCHUP_COMPLETE] = "Catch up complete",
+ [DHCP6_STATUS_NOT_SUPPORTED] = "Not supported",
+ [DHCP6_STATUS_TLS_CONNECTION_REFUSED] = "TLS connection refused",
+ [DHCP6_STATUS_ADDRESS_IN_USE] = "Address in use",
+ [DHCP6_STATUS_CONFIGURATION_CONFLICT] = "Configuration conflict",
+ [DHCP6_STATUS_MISSING_BINDING_INFORMATION] = "Missing binding information",
+ [DHCP6_STATUS_OUTDATED_BINDING_INFORMATION] = "Outdated binding information",
+ [DHCP6_STATUS_SERVER_SHUTTING_DOWN] = "Server shutting down",
+ [DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED] = "DNS update not supported",
+ [DHCP6_STATUS_EXCESSIVE_TIME_SKEW] = "Excessive time skew",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
+
+#define DHCP6_CLIENT_DONT_DESTROY(client) \
+ _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
+
+static int client_start(sd_dhcp6_client *client, enum DHCP6State state);
+
+int sd_dhcp6_client_set_callback(
+ sd_dhcp6_client *client,
+ sd_dhcp6_client_callback_t cb,
+ void *userdata) {
+
+ assert_return(client, -EINVAL);
+
+ client->callback = cb;
+ client->userdata = userdata;
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) {
+
+ assert_return(client, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+ assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+ client->ifindex = ifindex;
+ return 0;
+}
+
+int sd_dhcp6_client_set_local_address(
+ sd_dhcp6_client *client,
+ const struct in6_addr *local_address) {
+
+ assert_return(client, -EINVAL);
+ assert_return(local_address, -EINVAL);
+ assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) local_address) > 0, -EINVAL);
+
+ assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+ client->local_address = *local_address;
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_mac(
+ sd_dhcp6_client *client,
+ const uint8_t *addr, size_t addr_len,
+ uint16_t arp_type) {
+
+ assert_return(client, -EINVAL);
+ assert_return(addr, -EINVAL);
+ assert_return(addr_len <= MAX_MAC_ADDR_LEN, -EINVAL);
+
+ assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+ if (arp_type == ARPHRD_ETHER)
+ assert_return(addr_len == ETH_ALEN, -EINVAL);
+ else if (arp_type == ARPHRD_INFINIBAND)
+ assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
+ else {
+ client->arp_type = ARPHRD_NONE;
+ client->mac_addr_len = 0;
+ return 0;
+ }
+
+ if (client->mac_addr_len == addr_len &&
+ memcmp(&client->mac_addr, addr, addr_len) == 0)
+ return 0;
+
+ memcpy(&client->mac_addr, addr, addr_len);
+ client->mac_addr_len = addr_len;
+ client->arp_type = arp_type;
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_prefix_delegation_hint(
+ sd_dhcp6_client *client,
+ uint8_t prefixlen,
+ const struct in6_addr *pd_address) {
+
+ assert_return(client, -EINVAL);
+ assert_return(pd_address, -EINVAL);
+
+ assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+ client->hint_pd_prefix.iapdprefix.address = *pd_address;
+ client->hint_pd_prefix.iapdprefix.prefixlen = prefixlen;
+
+ return 0;
+}
+
+int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(v, -EINVAL);
+
+ r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp6_option_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(client->vendor_options, v, v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp6_option_ref(v);
+
+ return 1;
+}
+
+static int client_ensure_duid(sd_dhcp6_client *client) {
+ if (client->duid_len != 0)
+ return 0;
+
+ return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
+}
+
+/**
+ * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid
+ * without further modification. Otherwise, if duid_type is supported, DUID
+ * is set based on that type. Otherwise, an error is returned.
+ */
+static int dhcp6_client_set_duid_internal(
+ sd_dhcp6_client *client,
+ uint16_t duid_type,
+ const void *duid,
+ size_t duid_len,
+ usec_t llt_time) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(duid_len == 0 || duid != NULL, -EINVAL);
+ assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+ if (duid) {
+ r = dhcp_validate_duid_len(duid_type, duid_len, true);
+ if (r < 0) {
+ r = dhcp_validate_duid_len(duid_type, duid_len, false);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to validate length of DUID: %m");
+
+ log_dhcp6_client(client, "Using DUID of type %u of incorrect length, proceeding.", duid_type);
+ }
+
+ client->duid.type = htobe16(duid_type);
+ memcpy(&client->duid.raw.data, duid, duid_len);
+ client->duid_len = sizeof(client->duid.type) + duid_len;
+ } else
+ switch (duid_type) {
+ case DUID_TYPE_LLT:
+ if (client->mac_addr_len == 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set.");
+
+ r = dhcp_identifier_set_duid_llt(&client->duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set DUID-LLT: %m");
+ break;
+ case DUID_TYPE_EN:
+ r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set DUID-EN: %m");
+ break;
+ case DUID_TYPE_LL:
+ if (client->mac_addr_len == 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set.");
+
+ r = dhcp_identifier_set_duid_ll(&client->duid, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set DUID-LL: %m");
+ break;
+ case DUID_TYPE_UUID:
+ r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set DUID-UUID: %m");
+ break;
+ default:
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type");
+ }
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_duid(
+ sd_dhcp6_client *client,
+ uint16_t duid_type,
+ const void *duid,
+ size_t duid_len) {
+ return dhcp6_client_set_duid_internal(client, duid_type, duid, duid_len, 0);
+}
+
+int sd_dhcp6_client_set_duid_llt(
+ sd_dhcp6_client *client,
+ usec_t llt_time) {
+ return dhcp6_client_set_duid_internal(client, DUID_TYPE_LLT, NULL, 0, llt_time);
+}
+
+static const char* const dhcp6_duid_type_table[_DUID_TYPE_MAX] = {
+ [DUID_TYPE_LLT] = "DUID-LLT",
+ [DUID_TYPE_EN] = "DUID-EN/Vendor",
+ [DUID_TYPE_LL] = "DUID-LL",
+ [DUID_TYPE_UUID] = "UUID",
+};
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_duid_type, DUIDType);
+
+int sd_dhcp6_client_duid_as_string(
+ sd_dhcp6_client *client,
+ char **duid) {
+ _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL;
+ const char *v;
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->duid_len > 0, -ENODATA);
+
+ v = dhcp6_duid_type_to_string(be16toh(client->duid.type));
+ if (v) {
+ s = strdup(v);
+ if (!s)
+ return -ENOMEM;
+ } else {
+ r = asprintf(&s, "%0x", client->duid.type);
+ if (r < 0)
+ return -ENOMEM;
+ }
+
+ t = hexmem(&client->duid.raw.data, client->duid_len);
+ if (!t)
+ return -ENOMEM;
+
+ p = strjoin(s, ":", t);
+ if (!p)
+ return -ENOMEM;
+
+ *duid = TAKE_PTR(p);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
+ assert_return(client, -EINVAL);
+ assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+ client->ia_na.ia_na.id = htobe32(iaid);
+ client->ia_pd.ia_pd.id = htobe32(iaid);
+ client->iaid_set = true;
+
+ return 0;
+}
+
+int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) {
+ assert_return(client, -EINVAL);
+ assert_return(iaid, -EINVAL);
+
+ if (!client->iaid_set)
+ return -ENODATA;
+
+ *iaid = be32toh(client->ia_na.ia_na.id);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_fqdn(
+ sd_dhcp6_client *client,
+ const char *fqdn) {
+
+ assert_return(client, -EINVAL);
+
+ /* Make sure FQDN qualifies as DNS and as Linux hostname */
+ if (fqdn &&
+ !(hostname_is_valid(fqdn, false) && dns_name_is_valid(fqdn) > 0))
+ return -EINVAL;
+
+ return free_and_strdup(&client->fqdn, fqdn);
+}
+
+int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) {
+ assert_return(client, -EINVAL);
+ assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
+
+ client->information_request = enabled;
+
+ return 0;
+}
+
+int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled) {
+ assert_return(client, -EINVAL);
+ assert_return(enabled, -EINVAL);
+
+ *enabled = client->information_request;
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) {
+ size_t t;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+
+ if (option <= 0 || option >= UINT8_MAX)
+ return -EINVAL;
+
+ for (t = 0; t < client->req_opts_len; t++)
+ if (client->req_opts[t] == htobe16(option))
+ return -EEXIST;
+
+ if (!GREEDY_REALLOC(client->req_opts, client->req_opts_allocated,
+ client->req_opts_len + 1))
+ return -ENOMEM;
+
+ client->req_opts[client->req_opts_len++] = htobe16(option);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, const char *mudurl) {
+
+ assert_return(client, -EINVAL);
+ assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+ assert_return(mudurl, -EINVAL);
+ assert_return(strlen(mudurl) <= UINT8_MAX, -EINVAL);
+ assert_return(http_url_is_valid(mudurl), -EINVAL);
+
+ return free_and_strdup(&client->mudurl, mudurl);
+}
+
+int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client *client, char * const *user_class) {
+ char * const *p;
+ char **s;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->state == DHCP6_STATE_STOPPED, -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 * const *p;
+ char **s;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->state == DHCP6_STATE_STOPPED, -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, DHCP6_REQUEST_IA_PD);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, int delegation) {
+ assert_return(client, -EINVAL);
+
+ SET_FLAG(client->request, DHCP6_REQUEST_IA_PD, delegation);
+
+ return 0;
+}
+
+int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request) {
+ assert_return(client, -EINVAL);
+ assert_return(request, -EINVAL);
+
+ *request = FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request) {
+ assert_return(client, -EINVAL);
+
+ SET_FLAG(client->request, DHCP6_REQUEST_IA_NA, request);
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) {
+ assert_return(client, -EINVAL);
+
+ client->transaction_id = transaction_id;
+
+ return 0;
+}
+
+int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
+ assert_return(client, -EINVAL);
+
+ if (!client->lease)
+ return -ENOMSG;
+
+ if (ret)
+ *ret = client->lease;
+
+ return 0;
+}
+
+int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(v, -EINVAL);
+
+ r = ordered_hashmap_ensure_allocated(&client->extra_options, &dhcp6_option_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(client->extra_options, UINT_TO_PTR(v->option), v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp6_option_ref(v);
+ return 0;
+}
+
+static void client_notify(sd_dhcp6_client *client, int event) {
+ assert(client);
+
+ if (client->callback)
+ client->callback(client, event, client->userdata);
+}
+
+static int client_reset(sd_dhcp6_client *client) {
+ assert(client);
+
+ client->lease = sd_dhcp6_lease_unref(client->lease);
+
+ client->receive_message =
+ sd_event_source_unref(client->receive_message);
+
+ client->transaction_id = 0;
+ client->transaction_start = 0;
+
+ client->retransmit_time = 0;
+ client->retransmit_count = 0;
+
+ (void) event_source_disable(client->timeout_resend);
+ (void) event_source_disable(client->timeout_resend_expire);
+ (void) event_source_disable(client->timeout_t1);
+ (void) event_source_disable(client->timeout_t2);
+
+ client->state = DHCP6_STATE_STOPPED;
+
+ return 0;
+}
+
+static void client_stop(sd_dhcp6_client *client, int error) {
+ DHCP6_CLIENT_DONT_DESTROY(client);
+
+ assert(client);
+
+ client_notify(client, error);
+
+ client_reset(client);
+}
+
+static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
+ _cleanup_free_ DHCP6Message *message = NULL;
+ struct in6_addr all_servers =
+ IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
+ struct sd_dhcp6_option *j;
+ size_t len, optlen = 512;
+ uint8_t *opt;
+ int r;
+ usec_t elapsed_usec;
+ be16_t elapsed_time;
+
+ assert(client);
+
+ len = sizeof(DHCP6Message) + optlen;
+
+ message = malloc0(len);
+ if (!message)
+ return -ENOMEM;
+
+ opt = (uint8_t *)(message + 1);
+
+ message->transaction_id = client->transaction_id;
+
+ switch(client->state) {
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ message->type = DHCP6_INFORMATION_REQUEST;
+
+ if (client->mudurl) {
+ r = dhcp6_option_append(&opt, &optlen,
+ SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+ client->mudurl);
+ if (r < 0)
+ return r;
+ }
+
+ break;
+
+ case DHCP6_STATE_SOLICITATION:
+ message->type = DHCP6_SOLICIT;
+
+ r = dhcp6_option_append(&opt, &optlen,
+ SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) {
+ r = dhcp6_option_append_ia(&opt, &optlen,
+ &client->ia_na);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->fqdn) {
+ r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->mudurl) {
+ r = dhcp6_option_append(&opt, &optlen,
+ SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+ client->mudurl);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->user_class) {
+ r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->vendor_class) {
+ r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
+ if (r < 0)
+ return r;
+ }
+
+ if (!ordered_hashmap_isempty(client->vendor_options)) {
+ r = dhcp6_option_append_vendor_option(&opt, &optlen,
+ client->vendor_options);
+ if (r < 0)
+ return r;
+ }
+
+ if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
+ r = dhcp6_option_append_pd(opt, optlen, &client->ia_pd, &client->hint_pd_prefix);
+ if (r < 0)
+ return r;
+
+ opt += r;
+ optlen -= r;
+ }
+
+ break;
+
+ case DHCP6_STATE_REQUEST:
+ case DHCP6_STATE_RENEW:
+
+ if (client->state == DHCP6_STATE_REQUEST)
+ message->type = DHCP6_REQUEST;
+ else
+ message->type = DHCP6_RENEW;
+
+ r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_SERVERID,
+ client->lease->serverid_len,
+ client->lease->serverid);
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) {
+ r = dhcp6_option_append_ia(&opt, &optlen,
+ &client->lease->ia);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->fqdn) {
+ r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->mudurl) {
+ r = dhcp6_option_append(&opt, &optlen,
+ SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+ client->mudurl);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->user_class) {
+ r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->vendor_class) {
+ r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
+ if (r < 0)
+ return r;
+ }
+
+ if (!ordered_hashmap_isempty(client->vendor_options)) {
+ r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options);
+ if (r < 0)
+ return r;
+ }
+
+ if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) {
+ r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL);
+ if (r < 0)
+ return r;
+
+ opt += r;
+ optlen -= r;
+ }
+
+ break;
+
+ case DHCP6_STATE_REBIND:
+ message->type = DHCP6_REBIND;
+
+ if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) {
+ r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->fqdn) {
+ r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->mudurl) {
+ r = dhcp6_option_append(&opt, &optlen,
+ SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+ client->mudurl);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->user_class) {
+ r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
+ if (r < 0)
+ return r;
+ }
+
+ if (client->vendor_class) {
+ r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
+ if (r < 0)
+ return r;
+ }
+
+ if (!ordered_hashmap_isempty(client->vendor_options)) {
+ r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options);
+ if (r < 0)
+ return r;
+ }
+
+ if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
+ r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL);
+ if (r < 0)
+ return r;
+
+ opt += r;
+ optlen -= r;
+ }
+
+ break;
+
+ case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_BOUND:
+ return -EINVAL;
+ }
+
+ r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ORO,
+ client->req_opts_len * sizeof(be16_t),
+ client->req_opts);
+ if (r < 0)
+ return r;
+
+ assert(client->duid_len);
+ r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_CLIENTID,
+ client->duid_len, &client->duid);
+ if (r < 0)
+ return r;
+
+ elapsed_usec = time_now - client->transaction_start;
+ if (elapsed_usec < 0xffff * USEC_PER_MSEC * 10)
+ elapsed_time = htobe16(elapsed_usec / USEC_PER_MSEC / 10);
+ else
+ elapsed_time = 0xffff;
+
+ r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ELAPSED_TIME,
+ sizeof(elapsed_time), &elapsed_time);
+ if (r < 0)
+ return r;
+
+ ORDERED_HASHMAP_FOREACH(j, client->extra_options) {
+ r = dhcp6_option_append(&opt, &optlen, j->option, j->length, j->data);
+ if (r < 0)
+ return r;
+ }
+
+ r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
+ len - optlen);
+ if (r < 0)
+ return r;
+
+ log_dhcp6_client(client, "Sent %s",
+ dhcp6_message_type_to_string(message->type));
+
+ return 0;
+}
+
+static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp6_client *client = userdata;
+
+ assert(s);
+ assert(client);
+ assert(client->lease);
+
+ (void) event_source_disable(client->timeout_t2);
+
+ log_dhcp6_client(client, "Timeout T2");
+
+ client_start(client, DHCP6_STATE_REBIND);
+
+ return 0;
+}
+
+static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp6_client *client = userdata;
+
+ assert(s);
+ assert(client);
+ assert(client->lease);
+
+ (void) event_source_disable(client->timeout_t1);
+
+ log_dhcp6_client(client, "Timeout T1");
+
+ client_start(client, DHCP6_STATE_RENEW);
+
+ return 0;
+}
+
+static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_dhcp6_client *client = userdata;
+ DHCP6_CLIENT_DONT_DESTROY(client);
+ enum DHCP6State state;
+
+ assert(s);
+ assert(client);
+ assert(client->event);
+
+ state = client->state;
+
+ client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE);
+
+ /* RFC 3315, section 18.1.4., says that "...the client may choose to
+ use a Solicit message to locate a new DHCP server..." */
+ if (state == DHCP6_STATE_REBIND)
+ client_start(client, DHCP6_STATE_SOLICITATION);
+
+ return 0;
+}
+
+static usec_t client_timeout_compute_random(usec_t val) {
+ return val - (random_u32() % USEC_PER_SEC) * val / 10 / USEC_PER_SEC;
+}
+
+static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) {
+ int r = 0;
+ sd_dhcp6_client *client = userdata;
+ usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0;
+ usec_t max_retransmit_duration = 0;
+ uint8_t max_retransmit_count = 0;
+ char time_string[FORMAT_TIMESPAN_MAX];
+
+ assert(s);
+ assert(client);
+ assert(client->event);
+
+ (void) event_source_disable(client->timeout_resend);
+
+ switch (client->state) {
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ init_retransmit_time = DHCP6_INF_TIMEOUT;
+ max_retransmit_time = DHCP6_INF_MAX_RT;
+
+ break;
+
+ case DHCP6_STATE_SOLICITATION:
+
+ if (client->retransmit_count && client->lease) {
+ client_start(client, DHCP6_STATE_REQUEST);
+ return 0;
+ }
+
+ init_retransmit_time = DHCP6_SOL_TIMEOUT;
+ max_retransmit_time = DHCP6_SOL_MAX_RT;
+
+ break;
+
+ case DHCP6_STATE_REQUEST:
+ init_retransmit_time = DHCP6_REQ_TIMEOUT;
+ max_retransmit_time = DHCP6_REQ_MAX_RT;
+ max_retransmit_count = DHCP6_REQ_MAX_RC;
+
+ break;
+
+ case DHCP6_STATE_RENEW:
+ init_retransmit_time = DHCP6_REN_TIMEOUT;
+ max_retransmit_time = DHCP6_REN_MAX_RT;
+
+ /* RFC 3315, section 18.1.3. says max retransmit duration will
+ be the remaining time until T2. Instead of setting MRD,
+ wait for T2 to trigger with the same end result */
+
+ break;
+
+ case DHCP6_STATE_REBIND:
+ init_retransmit_time = DHCP6_REB_TIMEOUT;
+ max_retransmit_time = DHCP6_REB_MAX_RT;
+
+ if (event_source_is_enabled(client->timeout_resend_expire) <= 0) {
+ uint32_t expire = 0;
+
+ r = dhcp6_lease_ia_rebind_expire(&client->lease->ia, &expire);
+ if (r < 0) {
+ client_stop(client, r);
+ return 0;
+ }
+ max_retransmit_duration = expire * USEC_PER_SEC;
+ }
+
+ break;
+
+ case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_BOUND:
+ return 0;
+ }
+
+ if (max_retransmit_count > 0 &&
+ client->retransmit_count >= max_retransmit_count) {
+ client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX);
+ return 0;
+ }
+
+ r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ goto error;
+
+ r = client_send_message(client, time_now);
+ if (r >= 0)
+ client->retransmit_count++;
+
+ if (client->retransmit_time == 0) {
+ client->retransmit_time =
+ client_timeout_compute_random(init_retransmit_time);
+
+ if (client->state == DHCP6_STATE_SOLICITATION)
+ client->retransmit_time += init_retransmit_time / 10;
+
+ } else {
+ if (max_retransmit_time > 0 &&
+ client->retransmit_time > max_retransmit_time / 2)
+ client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
+ else
+ client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
+ }
+
+ log_dhcp6_client(client, "Next retransmission in %s",
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->retransmit_time, USEC_PER_SEC));
+
+ r = event_reset_time(client->event, &client->timeout_resend,
+ clock_boottime_or_monotonic(),
+ time_now + client->retransmit_time, 10 * USEC_PER_MSEC,
+ client_timeout_resend, client,
+ client->event_priority, "dhcp6-resend-timer", true);
+ if (r < 0)
+ goto error;
+
+ if (max_retransmit_duration > 0 && event_source_is_enabled(client->timeout_resend_expire) <= 0) {
+
+ log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
+ max_retransmit_duration / USEC_PER_SEC);
+
+ r = event_reset_time(client->event, &client->timeout_resend_expire,
+ clock_boottime_or_monotonic(),
+ time_now + max_retransmit_duration, USEC_PER_SEC,
+ client_timeout_resend_expire, client,
+ client->event_priority, "dhcp6-resend-expire-timer", true);
+ if (r < 0)
+ goto error;
+ }
+
+error:
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+}
+
+static int client_ensure_iaid(sd_dhcp6_client *client) {
+ int r;
+ uint32_t iaid;
+
+ assert(client);
+
+ if (client->iaid_set)
+ return 0;
+
+ r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, true, &iaid);
+ if (r < 0)
+ return r;
+
+ client->ia_na.ia_na.id = iaid;
+ client->ia_pd.ia_pd.id = iaid;
+ client->iaid_set = true;
+
+ return 0;
+}
+
+static int client_parse_message(
+ sd_dhcp6_client *client,
+ DHCP6Message *message,
+ size_t len,
+ sd_dhcp6_lease *lease) {
+
+ uint16_t ia_na_status = 0, ia_pd_status = 0;
+ uint32_t lt_t1 = ~0, lt_t2 = ~0;
+ usec_t irt = IRT_DEFAULT;
+ bool clientid = false;
+ size_t pos = 0;
+ int r;
+
+ assert(client);
+ assert(message);
+ assert(len >= sizeof(DHCP6Message));
+ assert(lease);
+
+ len -= sizeof(DHCP6Message);
+
+ while (pos < len) {
+ DHCP6Option *option = (DHCP6Option *) &message->options[pos];
+ uint16_t optcode, optlen;
+ be32_t iaid_lease;
+ int status;
+ uint8_t *optval;
+
+ if (len < pos + offsetof(DHCP6Option, data))
+ return -ENOBUFS;
+
+ optcode = be16toh(option->code);
+ optlen = be16toh(option->len);
+ optval = option->data;
+
+ if (len < pos + offsetof(DHCP6Option, data) + optlen)
+ return -ENOBUFS;
+
+ switch (optcode) {
+ case SD_DHCP6_OPTION_CLIENTID:
+ if (clientid) {
+ log_dhcp6_client(client, "%s contains multiple clientids",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ if (optlen != client->duid_len ||
+ memcmp(&client->duid, optval, optlen) != 0) {
+ log_dhcp6_client(client, "%s DUID does not match",
+ dhcp6_message_type_to_string(message->type));
+
+ return -EINVAL;
+ }
+ clientid = true;
+
+ break;
+
+ case SD_DHCP6_OPTION_SERVERID:
+ r = dhcp6_lease_get_serverid(lease, NULL, NULL);
+ if (r >= 0) {
+ log_dhcp6_client(client, "%s contains multiple serverids",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ r = dhcp6_lease_set_serverid(lease, optval, optlen);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case SD_DHCP6_OPTION_PREFERENCE:
+ if (optlen != 1)
+ return -EINVAL;
+
+ r = dhcp6_lease_set_preference(lease, optval[0]);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case SD_DHCP6_OPTION_STATUS_CODE:
+ status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option));
+ if (status < 0)
+ return status;
+
+ if (status > 0) {
+ log_dhcp6_client(client, "%s Status %s",
+ dhcp6_message_type_to_string(message->type),
+ dhcp6_message_status_to_string(status));
+
+ return -EINVAL;
+ }
+
+ break;
+
+ case SD_DHCP6_OPTION_IA_NA:
+ if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+ log_dhcp6_client(client, "Information request ignoring IA NA option");
+
+ break;
+ }
+
+ r = dhcp6_option_parse_ia(option, &lease->ia, &ia_na_status);
+ if (r < 0 && r != -ENOMSG)
+ return r;
+
+ if (ia_na_status == DHCP6_STATUS_NO_ADDRS_AVAIL) {
+ pos += offsetof(DHCP6Option, data) + optlen;
+ continue;
+ }
+
+ r = dhcp6_lease_get_iaid(lease, &iaid_lease);
+ if (r < 0)
+ return r;
+
+ if (client->ia_na.ia_na.id != iaid_lease) {
+ log_dhcp6_client(client, "%s has wrong IAID for IA NA",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ if (lease->ia.addresses) {
+ lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1));
+ lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t1));
+ }
+
+ break;
+
+ case SD_DHCP6_OPTION_IA_PD:
+ if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+ log_dhcp6_client(client, "Information request ignoring IA PD option");
+
+ break;
+ }
+
+ r = dhcp6_option_parse_ia(option, &lease->pd, &ia_pd_status);
+ if (r < 0 && r != -ENOMSG)
+ return r;
+
+ if (ia_pd_status == DHCP6_STATUS_NO_PREFIX_AVAIL) {
+ pos += offsetof(DHCP6Option, data) + optlen;
+ continue;
+ }
+
+ r = dhcp6_lease_get_pd_iaid(lease, &iaid_lease);
+ if (r < 0)
+ return r;
+
+ if (client->ia_pd.ia_pd.id != iaid_lease) {
+ log_dhcp6_client(client, "%s has wrong IAID for IA PD",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ if (lease->pd.addresses) {
+ lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1));
+ lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2));
+ }
+
+ break;
+
+ case SD_DHCP6_OPTION_RAPID_COMMIT:
+ r = dhcp6_lease_set_rapid_commit(lease);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case SD_DHCP6_OPTION_DNS_SERVERS:
+ r = dhcp6_lease_set_dns(lease, optval, optlen);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case SD_DHCP6_OPTION_DOMAIN_LIST:
+ r = dhcp6_lease_set_domains(lease, optval, optlen);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case SD_DHCP6_OPTION_NTP_SERVER:
+ r = dhcp6_lease_set_ntp(lease, optval, optlen);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case SD_DHCP6_OPTION_SNTP_SERVERS:
+ r = dhcp6_lease_set_sntp(lease, optval, optlen);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case SD_DHCP6_OPTION_FQDN:
+ r = dhcp6_lease_set_fqdn(lease, optval, optlen);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
+ if (optlen != 4)
+ return -EINVAL;
+
+ irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC;
+ break;
+ }
+
+ pos += offsetof(DHCP6Option, data) + optlen;
+ }
+
+ if (ia_na_status > 0 && ia_pd_status > 0) {
+ log_dhcp6_client(client, "No IA_PD prefix or IA_NA address received. Ignoring.");
+ return -EINVAL;
+ }
+
+ if (!clientid) {
+ log_dhcp6_client(client, "%s has incomplete options",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ if (client->state != DHCP6_STATE_INFORMATION_REQUEST) {
+ r = dhcp6_lease_get_serverid(lease, NULL, NULL);
+ if (r < 0) {
+ log_dhcp6_client(client, "%s has no server id",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ } else {
+ if (lease->ia.addresses) {
+ lease->ia.ia_na.lifetime_t1 = htobe32(lt_t1);
+ lease->ia.ia_na.lifetime_t2 = htobe32(lt_t2);
+ }
+
+ if (lease->pd.addresses) {
+ lease->pd.ia_pd.lifetime_t1 = htobe32(lt_t1);
+ lease->pd.ia_pd.lifetime_t2 = htobe32(lt_t2);
+ }
+ }
+
+ client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM);
+
+ return 0;
+}
+
+static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, size_t len) {
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+ bool rapid_commit;
+ int r;
+
+ assert(client);
+ assert(reply);
+
+ if (reply->type != DHCP6_REPLY)
+ return 0;
+
+ r = dhcp6_lease_new(&lease);
+ if (r < 0)
+ return -ENOMEM;
+
+ r = client_parse_message(client, reply, len, lease);
+ if (r < 0)
+ return r;
+
+ if (client->state == DHCP6_STATE_SOLICITATION) {
+ r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit);
+ if (r < 0)
+ return r;
+
+ if (!rapid_commit)
+ return 0;
+ }
+
+ sd_dhcp6_lease_unref(client->lease);
+ client->lease = TAKE_PTR(lease);
+
+ return DHCP6_STATE_BOUND;
+}
+
+static int client_receive_advertise(sd_dhcp6_client *client, DHCP6Message *advertise, size_t len) {
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+ uint8_t pref_advertise = 0, pref_lease = 0;
+ int r;
+
+ if (advertise->type != DHCP6_ADVERTISE)
+ return 0;
+
+ r = dhcp6_lease_new(&lease);
+ if (r < 0)
+ return r;
+
+ r = client_parse_message(client, advertise, len, lease);
+ if (r < 0)
+ return r;
+
+ r = dhcp6_lease_get_preference(lease, &pref_advertise);
+ if (r < 0)
+ return r;
+
+ r = dhcp6_lease_get_preference(client->lease, &pref_lease);
+
+ if (r < 0 || pref_advertise > pref_lease) {
+ sd_dhcp6_lease_unref(client->lease);
+ client->lease = TAKE_PTR(lease);
+ r = 0;
+ }
+
+ if (pref_advertise == 255 || client->retransmit_count > 1)
+ r = DHCP6_STATE_REQUEST;
+
+ return r;
+}
+
+static int client_receive_message(
+ sd_event_source *s,
+ int fd, uint32_t
+ revents,
+ void *userdata) {
+
+ sd_dhcp6_client *client = userdata;
+ DHCP6_CLIENT_DONT_DESTROY(client);
+ _cleanup_free_ DHCP6Message *message = NULL;
+ ssize_t buflen, len;
+ int r = 0;
+
+ assert(s);
+ assert(client);
+ assert(client->event);
+
+ buflen = next_datagram_size_fd(fd);
+ if (buflen == -ENETDOWN)
+ /* the link is down. Don't return an error or the I/O event
+ source will be disconnected and we won't be able to receive
+ packets again when the link comes back. */
+ return 0;
+ if (buflen < 0)
+ return buflen;
+
+ message = malloc(buflen);
+ if (!message)
+ return -ENOMEM;
+
+ len = recv(fd, message, buflen, 0);
+ if (len < 0) {
+ /* see comment above for why we shouldn't error out on ENETDOWN. */
+ if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
+ return 0;
+
+ return log_dhcp6_client_errno(client, errno, "Could not receive message from UDP socket: %m");
+
+ }
+ if ((size_t) len < sizeof(DHCP6Message)) {
+ log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring");
+ return 0;
+ }
+
+ switch(message->type) {
+ case DHCP6_SOLICIT:
+ case DHCP6_REQUEST:
+ case DHCP6_CONFIRM:
+ case DHCP6_RENEW:
+ case DHCP6_REBIND:
+ case DHCP6_RELEASE:
+ case DHCP6_DECLINE:
+ case DHCP6_INFORMATION_REQUEST:
+ case DHCP6_RELAY_FORW:
+ case DHCP6_RELAY_REPL:
+ return 0;
+
+ case DHCP6_ADVERTISE:
+ case DHCP6_REPLY:
+ case DHCP6_RECONFIGURE:
+ break;
+
+ default:
+ log_dhcp6_client(client, "Unknown message type %d", message->type);
+ return 0;
+ }
+
+ if (client->transaction_id != (message->transaction_id &
+ htobe32(0x00ffffff)))
+ return 0;
+
+ switch (client->state) {
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ r = client_receive_reply(client, message, len);
+ if (r < 0)
+ return 0;
+
+ client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST);
+
+ client_start(client, DHCP6_STATE_STOPPED);
+
+ break;
+
+ case DHCP6_STATE_SOLICITATION:
+ r = client_receive_advertise(client, message, len);
+
+ if (r == DHCP6_STATE_REQUEST) {
+ client_start(client, r);
+
+ break;
+ }
+
+ _fallthrough_; /* for Solicitation Rapid Commit option check */
+ case DHCP6_STATE_REQUEST:
+ case DHCP6_STATE_RENEW:
+ case DHCP6_STATE_REBIND:
+
+ r = client_receive_reply(client, message, len);
+ if (r < 0)
+ return 0;
+
+ if (r == DHCP6_STATE_BOUND) {
+
+ r = client_start(client, DHCP6_STATE_BOUND);
+ if (r < 0) {
+ client_stop(client, r);
+ return 0;
+ }
+
+ client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE);
+ }
+
+ break;
+
+ case DHCP6_STATE_BOUND:
+
+ break;
+
+ case DHCP6_STATE_STOPPED:
+ return 0;
+ }
+
+ log_dhcp6_client(client, "Recv %s",
+ dhcp6_message_type_to_string(message->type));
+
+ return 0;
+}
+
+static int client_get_lifetime(sd_dhcp6_client *client, uint32_t *lifetime_t1,
+ uint32_t *lifetime_t2) {
+ assert_return(client, -EINVAL);
+ assert_return(client->lease, -EINVAL);
+
+ if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) {
+ *lifetime_t1 = be32toh(client->lease->ia.ia_na.lifetime_t1);
+ *lifetime_t2 = be32toh(client->lease->ia.ia_na.lifetime_t2);
+
+ return 0;
+ }
+
+ if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) {
+ *lifetime_t1 = be32toh(client->lease->pd.ia_pd.lifetime_t1);
+ *lifetime_t2 = be32toh(client->lease->pd.ia_pd.lifetime_t2);
+
+ return 0;
+ }
+
+ return -ENOMSG;
+}
+
+static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
+ int r;
+ usec_t timeout, time_now;
+ char time_string[FORMAT_TIMESPAN_MAX];
+ uint32_t lifetime_t1, lifetime_t2;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->event, -EINVAL);
+ assert_return(client->ifindex > 0, -EINVAL);
+ assert_return(client->state != state, -EINVAL);
+
+ (void) event_source_disable(client->timeout_resend_expire);
+ (void) event_source_disable(client->timeout_resend);
+ client->retransmit_time = 0;
+ client->retransmit_count = 0;
+
+ r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return r;
+
+ if (!client->receive_message) {
+ r = sd_event_add_io(client->event, &client->receive_message,
+ client->fd, EPOLLIN, client_receive_message,
+ client);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_priority(client->receive_message,
+ client->event_priority);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_description(client->receive_message,
+ "dhcp6-receive-message");
+ if (r < 0)
+ goto error;
+ }
+
+ switch (state) {
+ case DHCP6_STATE_STOPPED:
+ if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+ client->state = DHCP6_STATE_STOPPED;
+
+ return 0;
+ }
+
+ _fallthrough_;
+ case DHCP6_STATE_SOLICITATION:
+ client->state = DHCP6_STATE_SOLICITATION;
+
+ break;
+
+ case DHCP6_STATE_INFORMATION_REQUEST:
+ case DHCP6_STATE_REQUEST:
+ case DHCP6_STATE_RENEW:
+ case DHCP6_STATE_REBIND:
+
+ client->state = state;
+
+ break;
+
+ case DHCP6_STATE_BOUND:
+
+ r = client_get_lifetime(client, &lifetime_t1, &lifetime_t2);
+ if (r < 0)
+ goto error;
+
+ if (lifetime_t1 == 0xffffffff || lifetime_t2 == 0xffffffff) {
+ log_dhcp6_client(client, "Infinite T1 0x%08x or T2 0x%08x",
+ lifetime_t1, lifetime_t2);
+
+ return 0;
+ }
+
+ timeout = client_timeout_compute_random(lifetime_t1 * USEC_PER_SEC);
+
+ log_dhcp6_client(client, "T1 expires in %s",
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
+
+ r = event_reset_time(client->event, &client->timeout_t1,
+ clock_boottime_or_monotonic(),
+ time_now + timeout, 10 * USEC_PER_SEC,
+ client_timeout_t1, client,
+ client->event_priority, "dhcp6-t1-timeout", true);
+ if (r < 0)
+ goto error;
+
+ timeout = client_timeout_compute_random(lifetime_t2 * USEC_PER_SEC);
+
+ log_dhcp6_client(client, "T2 expires in %s",
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
+
+ r = event_reset_time(client->event, &client->timeout_t2,
+ clock_boottime_or_monotonic(),
+ time_now + timeout, 10 * USEC_PER_SEC,
+ client_timeout_t2, client,
+ client->event_priority, "dhcp6-t2-timeout", true);
+ if (r < 0)
+ goto error;
+
+ client->state = state;
+
+ return 0;
+ }
+
+ client->transaction_id = random_u32() & htobe32(0x00ffffff);
+ client->transaction_start = time_now;
+
+ r = event_reset_time(client->event, &client->timeout_resend,
+ clock_boottime_or_monotonic(),
+ 0, 0,
+ client_timeout_resend, client,
+ client->event_priority, "dhcp6-resend-timeout", true);
+ if (r < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ client_reset(client);
+ return r;
+}
+
+int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
+ if (!client)
+ return 0;
+
+ client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
+
+ client->fd = safe_close(client->fd);
+
+ return 0;
+}
+
+int sd_dhcp6_client_is_running(sd_dhcp6_client *client) {
+ assert_return(client, -EINVAL);
+
+ return client->state != DHCP6_STATE_STOPPED;
+}
+
+int sd_dhcp6_client_start(sd_dhcp6_client *client) {
+ enum DHCP6State state = DHCP6_STATE_SOLICITATION;
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->event, -EINVAL);
+ assert_return(client->ifindex > 0, -EINVAL);
+ assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &client->local_address) > 0, -EINVAL);
+
+ if (!IN_SET(client->state, DHCP6_STATE_STOPPED))
+ return -EBUSY;
+
+ if (!client->information_request && !client->request)
+ return -EINVAL;
+
+ r = client_reset(client);
+ if (r < 0)
+ return r;
+
+ r = client_ensure_iaid(client);
+ if (r < 0)
+ return r;
+
+ r = client_ensure_duid(client);
+ if (r < 0)
+ return r;
+
+ if (client->fd < 0) {
+ r = dhcp6_network_bind_udp_socket(client->ifindex, &client->local_address);
+ if (r < 0) {
+ _cleanup_free_ char *p = NULL;
+
+ (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p);
+ return log_dhcp6_client_errno(client, r,
+ "Failed to bind to UDP socket at address %s: %m", strna(p));
+ }
+
+ client->fd = r;
+ }
+
+ if (client->information_request) {
+ usec_t t = now(CLOCK_MONOTONIC);
+
+ if (t < usec_add(client->information_request_time_usec, client->information_refresh_time_usec))
+ return 0;
+
+ client->information_request_time_usec = t;
+ state = DHCP6_STATE_INFORMATION_REQUEST;
+ }
+
+ log_dhcp6_client(client, "Started in %s mode",
+ client->information_request ? "Information request" : "Managed");
+
+ return client_start(client, state);
+}
+
+int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!client->event, -EBUSY);
+
+ if (event)
+ client->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&client->event);
+ if (r < 0)
+ return 0;
+ }
+
+ client->event_priority = priority;
+
+ return 0;
+}
+
+int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
+ assert_return(client, -EINVAL);
+
+ client->event = sd_event_unref(client->event);
+
+ return 0;
+}
+
+sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
+ assert_return(client, NULL);
+
+ return client->event;
+}
+
+static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
+ assert(client);
+
+ client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+ client->timeout_resend_expire = sd_event_source_unref(client->timeout_resend_expire);
+ client->timeout_t1 = sd_event_source_unref(client->timeout_t1);
+ client->timeout_t2 = sd_event_source_unref(client->timeout_t2);
+
+ client_reset(client);
+
+ client->fd = safe_close(client->fd);
+
+ sd_dhcp6_client_detach_event(client);
+
+ free(client->req_opts);
+ free(client->fqdn);
+ free(client->mudurl);
+
+ ordered_hashmap_free(client->extra_options);
+ strv_free(client->user_class);
+ strv_free(client->vendor_class);
+
+ return mfree(client);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_client, sd_dhcp6_client, dhcp6_client_free);
+
+int sd_dhcp6_client_new(sd_dhcp6_client **ret) {
+ _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+ _cleanup_free_ be16_t *req_opts = NULL;
+ size_t t;
+
+ assert_return(ret, -EINVAL);
+
+ req_opts = new(be16_t, ELEMENTSOF(default_req_opts));
+ if (!req_opts)
+ return -ENOMEM;
+
+ for (t = 0; t < ELEMENTSOF(default_req_opts); t++)
+ req_opts[t] = htobe16(default_req_opts[t]);
+
+ client = new(sd_dhcp6_client, 1);
+ if (!client)
+ return -ENOMEM;
+
+ *client = (sd_dhcp6_client) {
+ .n_ref = 1,
+ .ia_na.type = SD_DHCP6_OPTION_IA_NA,
+ .ia_pd.type = SD_DHCP6_OPTION_IA_PD,
+ .ifindex = -1,
+ .request = DHCP6_REQUEST_IA_NA,
+ .fd = -1,
+ .req_opts_len = ELEMENTSOF(default_req_opts),
+ .hint_pd_prefix.iapdprefix.lifetime_preferred = (be32_t) -1,
+ .hint_pd_prefix.iapdprefix.lifetime_valid = (be32_t) -1,
+ .req_opts = TAKE_PTR(req_opts),
+ };
+
+ *ret = TAKE_PTR(client);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c
new file mode 100644
index 0000000..d6f0708
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp6-lease.c
@@ -0,0 +1,433 @@
+/* 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-lease-internal.h"
+#include "dhcp6-protocol.h"
+#include "strv.h"
+#include "util.h"
+
+int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) {
+ DHCP6Address *addr;
+ uint32_t valid = 0, t;
+
+ assert_return(ia, -EINVAL);
+ assert_return(expire, -EINVAL);
+
+ LIST_FOREACH(addresses, addr, ia->addresses) {
+ t = be32toh(addr->iaaddr.lifetime_valid);
+ if (valid < t)
+ valid = t;
+ }
+
+ t = be32toh(ia->ia_na.lifetime_t2);
+ if (t > valid)
+ return -EINVAL;
+
+ *expire = valid - t;
+
+ return 0;
+}
+
+DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) {
+ DHCP6Address *address;
+
+ if (!ia)
+ return NULL;
+
+ while (ia->addresses) {
+ address = ia->addresses;
+
+ LIST_REMOVE(addresses, ia->addresses, address);
+
+ free(address);
+ }
+
+ return NULL;
+}
+
+int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id,
+ size_t len) {
+ uint8_t *serverid;
+
+ assert_return(lease, -EINVAL);
+ assert_return(id, -EINVAL);
+
+ serverid = memdup(id, len);
+ if (!serverid)
+ return -ENOMEM;
+
+ free_and_replace(lease->serverid, serverid);
+ lease->serverid_len = len;
+
+ return 0;
+}
+
+int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len) {
+ assert_return(lease, -EINVAL);
+
+ if (!lease->serverid)
+ return -ENOMSG;
+
+ if (id)
+ *id = lease->serverid;
+ if (len)
+ *len = lease->serverid_len;
+
+ return 0;
+}
+
+int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) {
+ assert_return(lease, -EINVAL);
+
+ lease->preference = preference;
+
+ return 0;
+}
+
+int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference) {
+ assert_return(preference, -EINVAL);
+
+ if (!lease)
+ return -EINVAL;
+
+ *preference = lease->preference;
+
+ return 0;
+}
+
+int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) {
+ assert_return(lease, -EINVAL);
+
+ lease->rapid_commit = true;
+
+ return 0;
+}
+
+int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit) {
+ assert_return(lease, -EINVAL);
+ assert_return(rapid_commit, -EINVAL);
+
+ *rapid_commit = lease->rapid_commit;
+
+ return 0;
+}
+
+int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid) {
+ assert_return(lease, -EINVAL);
+ assert_return(iaid, -EINVAL);
+
+ *iaid = lease->ia.ia_na.id;
+
+ return 0;
+}
+
+int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid) {
+ assert_return(lease, -EINVAL);
+ assert_return(iaid, -EINVAL);
+
+ *iaid = lease->pd.ia_pd.id;
+
+ return 0;
+}
+
+int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *addr,
+ uint32_t *lifetime_preferred,
+ uint32_t *lifetime_valid) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+ assert_return(lifetime_preferred, -EINVAL);
+ assert_return(lifetime_valid, -EINVAL);
+
+ if (!lease->addr_iter)
+ return -ENOMSG;
+
+ memcpy(addr, &lease->addr_iter->iaaddr.address,
+ sizeof(struct in6_addr));
+ *lifetime_preferred =
+ be32toh(lease->addr_iter->iaaddr.lifetime_preferred);
+ *lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid);
+
+ lease->addr_iter = lease->addr_iter->addresses_next;
+
+ return 0;
+}
+
+void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) {
+ if (lease)
+ lease->addr_iter = lease->ia.addresses;
+}
+
+int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix,
+ uint8_t *prefix_len,
+ uint32_t *lifetime_preferred,
+ uint32_t *lifetime_valid) {
+ assert_return(lease, -EINVAL);
+ assert_return(prefix, -EINVAL);
+ assert_return(prefix_len, -EINVAL);
+ assert_return(lifetime_preferred, -EINVAL);
+ assert_return(lifetime_valid, -EINVAL);
+
+ if (!lease->prefix_iter)
+ return -ENOMSG;
+
+ memcpy(prefix, &lease->prefix_iter->iapdprefix.address,
+ sizeof(struct in6_addr));
+ *prefix_len = lease->prefix_iter->iapdprefix.prefixlen;
+ *lifetime_preferred =
+ be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred);
+ *lifetime_valid =
+ be32toh(lease->prefix_iter->iapdprefix.lifetime_valid);
+
+ lease->prefix_iter = lease->prefix_iter->addresses_next;
+
+ return 0;
+}
+
+void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) {
+ if (lease)
+ lease->prefix_iter = lease->pd.addresses;
+}
+
+int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
+ int r;
+
+ assert_return(lease, -EINVAL);
+ assert_return(optval, -EINVAL);
+
+ if (!optlen)
+ return 0;
+
+ r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->dns,
+ lease->dns_count,
+ &lease->dns_allocated);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Invalid DNS server option: %m");
+
+ lease->dns_count = r;
+
+ return 0;
+}
+
+int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs) {
+ assert_return(lease, -EINVAL);
+ assert_return(addrs, -EINVAL);
+
+ if (lease->dns_count) {
+ *addrs = lease->dns;
+ return lease->dns_count;
+ }
+
+ return -ENOENT;
+}
+
+int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval,
+ size_t optlen) {
+ int r;
+ char **domains;
+
+ assert_return(lease, -EINVAL);
+ assert_return(optval, -EINVAL);
+
+ if (!optlen)
+ return 0;
+
+ r = dhcp6_option_parse_domainname_list(optval, optlen, &domains);
+ if (r < 0)
+ return 0;
+
+ strv_free_and_replace(lease->domains, domains);
+ lease->domains_count = r;
+
+ return r;
+}
+
+int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains) {
+ assert_return(lease, -EINVAL);
+ assert_return(domains, -EINVAL);
+
+ if (lease->domains_count) {
+ *domains = lease->domains;
+ return lease->domains_count;
+ }
+
+ return -ENOENT;
+}
+
+int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
+ int r;
+ uint16_t subopt;
+ size_t sublen;
+ uint8_t *subval;
+
+ assert_return(lease, -EINVAL);
+ assert_return(optval, -EINVAL);
+
+ lease->ntp = mfree(lease->ntp);
+ lease->ntp_count = 0;
+ lease->ntp_allocated = 0;
+
+ while ((r = dhcp6_option_parse(&optval, &optlen, &subopt, &sublen,
+ &subval)) >= 0) {
+ int s;
+ char **servers;
+
+ switch(subopt) {
+ case DHCP6_NTP_SUBOPTION_SRV_ADDR:
+ case DHCP6_NTP_SUBOPTION_MC_ADDR:
+ if (sublen != 16)
+ return 0;
+
+ s = dhcp6_option_parse_ip6addrs(subval, sublen,
+ &lease->ntp,
+ lease->ntp_count,
+ &lease->ntp_allocated);
+ if (s < 0)
+ return s;
+
+ lease->ntp_count = s;
+
+ break;
+
+ case DHCP6_NTP_SUBOPTION_SRV_FQDN:
+ r = dhcp6_option_parse_domainname_list(subval, sublen,
+ &servers);
+ if (r < 0)
+ return 0;
+
+ strv_free_and_replace(lease->ntp_fqdn, servers);
+ lease->ntp_fqdn_count = r;
+
+ break;
+ }
+ }
+
+ if (r != -ENOMSG)
+ return r;
+
+ return 0;
+}
+
+int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
+ int r;
+
+ assert_return(lease, -EINVAL);
+ assert_return(optval, -EINVAL);
+
+ if (!optlen)
+ return 0;
+
+ if (lease->ntp || lease->ntp_fqdn) {
+ log_dhcp6_client(client, "NTP information already provided");
+
+ return 0;
+ }
+
+ log_dhcp6_client(client, "Using deprecated SNTP information");
+
+ r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->ntp,
+ lease->ntp_count,
+ &lease->ntp_allocated);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Invalid SNTP server option: %m");
+
+ lease->ntp_count = r;
+
+ return 0;
+}
+
+int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease,
+ const struct in6_addr **addrs) {
+ assert_return(lease, -EINVAL);
+ assert_return(addrs, -EINVAL);
+
+ if (lease->ntp_count) {
+ *addrs = lease->ntp;
+ return lease->ntp_count;
+ }
+
+ return -ENOENT;
+}
+
+int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn) {
+ assert_return(lease, -EINVAL);
+ assert_return(ntp_fqdn, -EINVAL);
+
+ if (lease->ntp_fqdn_count) {
+ *ntp_fqdn = lease->ntp_fqdn;
+ return lease->ntp_fqdn_count;
+ }
+
+ return -ENOENT;
+}
+
+int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval,
+ size_t optlen) {
+ int r;
+ char *fqdn;
+
+ assert_return(lease, -EINVAL);
+ assert_return(optval, -EINVAL);
+
+ if (optlen < 2)
+ return -ENODATA;
+
+ /* Ignore the flags field, it doesn't carry any useful
+ information for clients. */
+ r = dhcp6_option_parse_domainname(optval + 1, optlen - 1, &fqdn);
+ if (r < 0)
+ return r;
+
+ return free_and_replace(lease->fqdn, fqdn);
+}
+
+int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn) {
+ assert_return(lease, -EINVAL);
+ assert_return(fqdn, -EINVAL);
+
+ if (lease->fqdn) {
+ *fqdn = lease->fqdn;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
+ assert(lease);
+
+ free(lease->serverid);
+ dhcp6_lease_free_ia(&lease->ia);
+ dhcp6_lease_free_ia(&lease->pd);
+
+ free(lease->dns);
+ free(lease->fqdn);
+
+ lease->domains = strv_free(lease->domains);
+
+ free(lease->ntp);
+
+ lease->ntp_fqdn = strv_free(lease->ntp_fqdn);
+ return mfree(lease);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_lease, sd_dhcp6_lease, dhcp6_lease_free);
+
+int dhcp6_lease_new(sd_dhcp6_lease **ret) {
+ sd_dhcp6_lease *lease;
+
+ lease = new0(sd_dhcp6_lease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ lease->n_ref = 1;
+
+ LIST_HEAD_INIT(lease->ia.addresses);
+
+ *ret = lease;
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c
new file mode 100644
index 0000000..2e1e46c
--- /dev/null
+++ b/src/libsystemd-network/sd-ipv4acd.c
@@ -0,0 +1,501 @@
+/* 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 "list.h"
+#include "random-util.h"
+#include "siphash24.h"
+#include "string-util.h"
+#include "time-util.h"
+
+/* Constants from the RFC */
+#define PROBE_WAIT_USEC (1U * USEC_PER_SEC)
+#define PROBE_NUM 3U
+#define PROBE_MIN_USEC (1U * USEC_PER_SEC)
+#define PROBE_MAX_USEC (2U * USEC_PER_SEC)
+#define ANNOUNCE_WAIT_USEC (2U * USEC_PER_SEC)
+#define ANNOUNCE_NUM 2U
+#define ANNOUNCE_INTERVAL_USEC (2U * USEC_PER_SEC)
+#define MAX_CONFLICTS 10U
+#define RATE_LIMIT_INTERVAL_USEC (60U * USEC_PER_SEC)
+#define DEFEND_INTERVAL_USEC (10U * USEC_PER_SEC)
+
+typedef enum IPv4ACDState {
+ IPV4ACD_STATE_INIT,
+ IPV4ACD_STATE_STARTED,
+ IPV4ACD_STATE_WAITING_PROBE,
+ IPV4ACD_STATE_PROBING,
+ IPV4ACD_STATE_WAITING_ANNOUNCE,
+ IPV4ACD_STATE_ANNOUNCING,
+ IPV4ACD_STATE_RUNNING,
+ _IPV4ACD_STATE_MAX,
+ _IPV4ACD_STATE_INVALID = -1
+} IPv4ACDState;
+
+struct sd_ipv4acd {
+ unsigned n_ref;
+
+ IPv4ACDState state;
+ int ifindex;
+ int fd;
+
+ unsigned n_iteration;
+ unsigned n_conflict;
+
+ sd_event_source *receive_message_event_source;
+ sd_event_source *timer_event_source;
+
+ usec_t defend_window;
+ be32_t address;
+
+ /* External */
+ struct ether_addr mac_addr;
+
+ sd_event *event;
+ int event_priority;
+ sd_ipv4acd_callback_t callback;
+ void* userdata;
+};
+
+#define log_ipv4acd_errno(acd, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "IPV4ACD: " fmt, ##__VA_ARGS__)
+#define log_ipv4acd(acd, fmt, ...) log_ipv4acd_errno(acd, 0, fmt, ##__VA_ARGS__)
+
+static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_counter) {
+ assert(acd);
+ assert(st < _IPV4ACD_STATE_MAX);
+
+ if (st == acd->state && !reset_counter)
+ acd->n_iteration++;
+ else {
+ acd->state = st;
+ acd->n_iteration = 0;
+ }
+}
+
+static void ipv4acd_reset(sd_ipv4acd *acd) {
+ assert(acd);
+
+ (void) event_source_disable(acd->timer_event_source);
+ acd->receive_message_event_source = sd_event_source_unref(acd->receive_message_event_source);
+
+ acd->fd = safe_close(acd->fd);
+
+ ipv4acd_set_state(acd, IPV4ACD_STATE_INIT, true);
+}
+
+static sd_ipv4acd *ipv4acd_free(sd_ipv4acd *acd) {
+ assert(acd);
+
+ acd->timer_event_source = sd_event_source_unref(acd->timer_event_source);
+
+ ipv4acd_reset(acd);
+ sd_ipv4acd_detach_event(acd);
+
+ return mfree(acd);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4acd, sd_ipv4acd, ipv4acd_free);
+
+int sd_ipv4acd_new(sd_ipv4acd **ret) {
+ _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL;
+
+ assert_return(ret, -EINVAL);
+
+ acd = new(sd_ipv4acd, 1);
+ if (!acd)
+ return -ENOMEM;
+
+ *acd = (sd_ipv4acd) {
+ .n_ref = 1,
+ .state = IPV4ACD_STATE_INIT,
+ .ifindex = -1,
+ .fd = -1,
+ };
+
+ *ret = TAKE_PTR(acd);
+
+ return 0;
+}
+
+static void ipv4acd_client_notify(sd_ipv4acd *acd, int event) {
+ assert(acd);
+
+ if (!acd->callback)
+ return;
+
+ acd->callback(acd, event, acd->userdata);
+}
+
+int sd_ipv4acd_stop(sd_ipv4acd *acd) {
+ IPv4ACDState old_state;
+
+ if (!acd)
+ return 0;
+
+ old_state = acd->state;
+
+ ipv4acd_reset(acd);
+
+ if (old_state == IPV4ACD_STATE_INIT)
+ return 0;
+
+ log_ipv4acd(acd, "STOPPED");
+
+ ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_STOP);
+
+ return 0;
+}
+
+static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata);
+
+static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_usec) {
+ usec_t next_timeout, time_now;
+
+ assert(acd);
+
+ next_timeout = usec;
+
+ if (random_usec > 0)
+ next_timeout += (usec_t) random_u64() % random_usec;
+
+ assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
+
+ return event_reset_time(acd->event, &acd->timer_event_source,
+ clock_boottime_or_monotonic(),
+ time_now + next_timeout, 0,
+ ipv4acd_on_timeout, acd,
+ acd->event_priority, "ipv4acd-timer", true);
+}
+
+static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) {
+ assert(acd);
+ assert(arp);
+
+ /* see the BPF */
+ if (memcmp(arp->arp_spa, &acd->address, sizeof(acd->address)) == 0)
+ return true;
+
+ /* the TPA matched instead of the SPA, this is not a conflict */
+ return false;
+}
+
+static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_ipv4acd *acd = userdata;
+ int r = 0;
+
+ assert(acd);
+
+ switch (acd->state) {
+
+ case IPV4ACD_STATE_STARTED:
+ ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true);
+
+ if (acd->n_conflict >= MAX_CONFLICTS) {
+ char ts[FORMAT_TIMESPAN_MAX];
+ log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0));
+
+ r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC);
+ if (r < 0)
+ goto fail;
+ } else {
+ r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC);
+ if (r < 0)
+ goto fail;
+ }
+
+ break;
+
+ case IPV4ACD_STATE_WAITING_PROBE:
+ case IPV4ACD_STATE_PROBING:
+ /* Send a probe */
+ r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
+ if (r < 0) {
+ log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m");
+ goto fail;
+ } else {
+ _cleanup_free_ char *address = NULL;
+ union in_addr_union addr = { .in.s_addr = acd->address };
+
+ (void) in_addr_to_string(AF_INET, &addr, &address);
+ log_ipv4acd(acd, "Probing %s", strna(address));
+ }
+
+ if (acd->n_iteration < PROBE_NUM - 2) {
+ ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false);
+
+ r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC));
+ if (r < 0)
+ goto fail;
+ } else {
+ ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true);
+
+ r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0);
+ if (r < 0)
+ goto fail;
+ }
+
+ break;
+
+ case IPV4ACD_STATE_ANNOUNCING:
+ if (acd->n_iteration >= ANNOUNCE_NUM - 1) {
+ ipv4acd_set_state(acd, IPV4ACD_STATE_RUNNING, false);
+ break;
+ }
+
+ _fallthrough_;
+ case IPV4ACD_STATE_WAITING_ANNOUNCE:
+ /* Send announcement packet */
+ r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
+ if (r < 0) {
+ log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
+ goto fail;
+ } else
+ log_ipv4acd(acd, "ANNOUNCE");
+
+ ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false);
+
+ r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_INTERVAL_USEC, 0);
+ if (r < 0)
+ goto fail;
+
+ if (acd->n_iteration == 0) {
+ acd->n_conflict = 0;
+ ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_BIND);
+ }
+
+ break;
+
+ default:
+ assert_not_reached("Invalid state.");
+ }
+
+ return 0;
+
+fail:
+ sd_ipv4acd_stop(acd);
+ return 0;
+}
+
+static void ipv4acd_on_conflict(sd_ipv4acd *acd) {
+ _cleanup_free_ char *address = NULL;
+ union in_addr_union addr = { .in.s_addr = acd->address };
+
+ assert(acd);
+
+ acd->n_conflict++;
+
+ (void) in_addr_to_string(AF_INET, &addr, &address);
+ log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict);
+
+ ipv4acd_reset(acd);
+ ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT);
+}
+
+static int ipv4acd_on_packet(
+ sd_event_source *s,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+
+ sd_ipv4acd *acd = userdata;
+ struct ether_arp packet;
+ ssize_t n;
+ int r;
+
+ assert(s);
+ assert(acd);
+ assert(fd >= 0);
+
+ n = recv(fd, &packet, sizeof(struct ether_arp), 0);
+ if (n < 0) {
+ if (IN_SET(errno, EAGAIN, EINTR))
+ return 0;
+
+ log_ipv4acd_errno(acd, errno, "Failed to read ARP packet: %m");
+ goto fail;
+ }
+ if ((size_t) n != sizeof(struct ether_arp)) {
+ log_ipv4acd(acd, "Ignoring too short ARP packet.");
+ return 0;
+ }
+
+ switch (acd->state) {
+
+ case IPV4ACD_STATE_ANNOUNCING:
+ case IPV4ACD_STATE_RUNNING:
+
+ if (ipv4acd_arp_conflict(acd, &packet)) {
+ usec_t ts;
+
+ assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0);
+
+ /* Defend address */
+ if (ts > acd->defend_window) {
+ acd->defend_window = ts + DEFEND_INTERVAL_USEC;
+ r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
+ if (r < 0) {
+ log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
+ goto fail;
+ } else
+ log_ipv4acd(acd, "DEFEND");
+
+ } else
+ ipv4acd_on_conflict(acd);
+ }
+ break;
+
+ case IPV4ACD_STATE_WAITING_PROBE:
+ case IPV4ACD_STATE_PROBING:
+ case IPV4ACD_STATE_WAITING_ANNOUNCE:
+ /* BPF ensures this packet indicates a conflict */
+ ipv4acd_on_conflict(acd);
+ break;
+
+ default:
+ assert_not_reached("Invalid state.");
+ }
+
+ return 0;
+
+fail:
+ sd_ipv4acd_stop(acd);
+ return 0;
+}
+
+int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int ifindex) {
+ 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_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) {
+ assert_return(acd, -EINVAL);
+ assert_return(addr, -EINVAL);
+ assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+ acd->mac_addr = *addr;
+
+ return 0;
+}
+
+int sd_ipv4acd_detach_event(sd_ipv4acd *acd) {
+ assert_return(acd, -EINVAL);
+
+ acd->event = sd_event_unref(acd->event);
+
+ return 0;
+}
+
+int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(acd, -EINVAL);
+ assert_return(!acd->event, -EBUSY);
+
+ if (event)
+ acd->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&acd->event);
+ if (r < 0)
+ return r;
+ }
+
+ acd->event_priority = priority;
+
+ return 0;
+}
+
+int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata) {
+ assert_return(acd, -EINVAL);
+
+ acd->callback = cb;
+ acd->userdata = userdata;
+
+ return 0;
+}
+
+int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address) {
+ assert_return(acd, -EINVAL);
+ assert_return(address, -EINVAL);
+ assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+ acd->address = address->s_addr;
+
+ return 0;
+}
+
+int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address) {
+ assert_return(acd, -EINVAL);
+ assert_return(address, -EINVAL);
+
+ address->s_addr = acd->address;
+
+ return 0;
+}
+
+int sd_ipv4acd_is_running(sd_ipv4acd *acd) {
+ assert_return(acd, false);
+
+ return acd->state != IPV4ACD_STATE_INIT;
+}
+
+int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts) {
+ int r;
+
+ assert_return(acd, -EINVAL);
+ assert_return(acd->event, -EINVAL);
+ assert_return(acd->ifindex > 0, -EINVAL);
+ assert_return(acd->address != 0, -EINVAL);
+ assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL);
+ assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+ r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr);
+ if (r < 0)
+ return r;
+
+ CLOSE_AND_REPLACE(acd->fd, r);
+ acd->defend_window = 0;
+
+ if (reset_conflicts)
+ acd->n_conflict = 0;
+
+ r = sd_event_add_io(acd->event, &acd->receive_message_event_source, acd->fd, EPOLLIN, ipv4acd_on_packet, acd);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_priority(acd->receive_message_event_source, acd->event_priority);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(acd->receive_message_event_source, "ipv4acd-receive-message");
+
+ r = ipv4acd_set_next_wakeup(acd, 0, 0);
+ if (r < 0)
+ goto fail;
+
+ ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true);
+ return 0;
+
+fail:
+ ipv4acd_reset(acd);
+ return r;
+}
diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c
new file mode 100644
index 0000000..09f2bda
--- /dev/null
+++ b/src/libsystemd-network/sd-ipv4ll.c
@@ -0,0 +1,324 @@
+/* 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 "list.h"
+#include "random-util.h"
+#include "siphash24.h"
+#include "sparse-endian.h"
+#include "string-util.h"
+#include "util.h"
+
+#define IPV4LL_NETWORK UINT32_C(0xA9FE0000)
+#define IPV4LL_NETMASK UINT32_C(0xFFFF0000)
+
+#define IPV4LL_DONT_DESTROY(ll) \
+ _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll)
+
+struct sd_ipv4ll {
+ unsigned n_ref;
+
+ sd_ipv4acd *acd;
+
+ be32_t address; /* the address pushed to ACD */
+ struct ether_addr mac;
+
+ struct {
+ le64_t value;
+ le64_t generation;
+ } seed;
+ bool seed_set;
+
+ /* External */
+ be32_t claimed_address;
+
+ sd_ipv4ll_callback_t callback;
+ void* userdata;
+};
+
+#define log_ipv4ll_errno(ll, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "IPV4LL: " fmt, ##__VA_ARGS__)
+#define log_ipv4ll(ll, fmt, ...) log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__)
+
+static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
+
+static sd_ipv4ll *ipv4ll_free(sd_ipv4ll *ll) {
+ assert(ll);
+
+ sd_ipv4acd_unref(ll->acd);
+ return mfree(ll);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4ll, sd_ipv4ll, ipv4ll_free);
+
+int sd_ipv4ll_new(sd_ipv4ll **ret) {
+ _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ ll = new0(sd_ipv4ll, 1);
+ if (!ll)
+ return -ENOMEM;
+
+ ll->n_ref = 1;
+
+ r = sd_ipv4acd_new(&ll->acd);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4acd_set_callback(ll->acd, ipv4ll_on_acd, ll);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(ll);
+
+ return 0;
+}
+
+int sd_ipv4ll_stop(sd_ipv4ll *ll) {
+ if (!ll)
+ return 0;
+
+ return sd_ipv4acd_stop(ll->acd);
+}
+
+int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) {
+ assert_return(ll, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+ assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
+
+ return sd_ipv4acd_set_ifindex(ll->acd, ifindex);
+}
+
+int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
+ int r;
+
+ assert_return(ll, -EINVAL);
+ assert_return(addr, -EINVAL);
+ assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
+
+ r = sd_ipv4acd_set_mac(ll->acd, addr);
+ if (r < 0)
+ return r;
+
+ ll->mac = *addr;
+ return 0;
+}
+
+int sd_ipv4ll_detach_event(sd_ipv4ll *ll) {
+ assert_return(ll, -EINVAL);
+
+ return sd_ipv4acd_detach_event(ll->acd);
+}
+
+int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) {
+ assert_return(ll, -EINVAL);
+
+ return sd_ipv4acd_attach_event(ll->acd, event, priority);
+}
+
+int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) {
+ assert_return(ll, -EINVAL);
+
+ ll->callback = cb;
+ ll->userdata = userdata;
+
+ return 0;
+}
+
+int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) {
+ assert_return(ll, -EINVAL);
+ assert_return(address, -EINVAL);
+
+ if (ll->claimed_address == 0)
+ return -ENOENT;
+
+ address->s_addr = ll->claimed_address;
+
+ return 0;
+}
+
+int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) {
+ assert_return(ll, -EINVAL);
+ assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
+
+ ll->seed.value = htole64(seed);
+ ll->seed_set = true;
+
+ return 0;
+}
+
+int sd_ipv4ll_is_running(sd_ipv4ll *ll) {
+ assert_return(ll, false);
+
+ return sd_ipv4acd_is_running(ll->acd);
+}
+
+static bool ipv4ll_address_is_valid(const struct in_addr *address) {
+ assert(address);
+
+ if (!in_addr_is_link_local(AF_INET, (const union in_addr_union *) address))
+ return false;
+
+ return !IN_SET(be32toh(address->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U);
+}
+
+int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) {
+ int r;
+
+ assert_return(ll, -EINVAL);
+ assert_return(address, -EINVAL);
+ assert_return(ipv4ll_address_is_valid(address), -EINVAL);
+
+ r = sd_ipv4acd_set_address(ll->acd, address);
+ if (r < 0)
+ return r;
+
+ ll->address = address->s_addr;
+
+ return 0;
+}
+
+#define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b)
+
+static int ipv4ll_pick_address(sd_ipv4ll *ll) {
+ _cleanup_free_ char *address = NULL;
+ be32_t addr;
+
+ assert(ll);
+
+ do {
+ uint64_t h;
+
+ h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes);
+
+ /* Increase the generation counter by one */
+ ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1);
+
+ addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK);
+ } while (addr == ll->address ||
+ IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U));
+
+ (void) in_addr_to_string(AF_INET, &(union in_addr_union) { .in.s_addr = addr }, &address);
+ log_ipv4ll(ll, "Picked new IP address %s.", strna(address));
+
+ return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr });
+}
+
+#define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
+
+static int ipv4ll_start_internal(sd_ipv4ll *ll, bool reset_generation) {
+ int r;
+ bool picked_address = false;
+
+ assert_return(ll, -EINVAL);
+ assert_return(!ether_addr_is_null(&ll->mac), -EINVAL);
+
+ /* If no random seed is set, generate some from the MAC address */
+ if (!ll->seed_set)
+ ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes));
+
+ if (reset_generation)
+ ll->seed.generation = 0;
+
+ if (ll->address == 0) {
+ r = ipv4ll_pick_address(ll);
+ if (r < 0)
+ return r;
+
+ picked_address = true;
+ }
+
+ r = sd_ipv4acd_start(ll->acd, reset_generation);
+ if (r < 0) {
+
+ /* We couldn't start? If so, let's forget the picked address again, the user might make a change and
+ * retry, and we want the new data to take effect when picking an address. */
+ if (picked_address)
+ ll->address = 0;
+
+ return r;
+ }
+
+ return 1;
+}
+
+int sd_ipv4ll_start(sd_ipv4ll *ll) {
+ assert_return(ll, -EINVAL);
+
+ if (sd_ipv4ll_is_running(ll))
+ return 0;
+
+ return ipv4ll_start_internal(ll, true);
+}
+
+int sd_ipv4ll_restart(sd_ipv4ll *ll) {
+ ll->address = 0;
+
+ return ipv4ll_start_internal(ll, false);
+}
+
+static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) {
+ assert(ll);
+
+ if (ll->callback)
+ ll->callback(ll, event, ll->userdata);
+}
+
+void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
+ sd_ipv4ll *ll = userdata;
+ IPV4LL_DONT_DESTROY(ll);
+ int r;
+
+ assert(acd);
+ assert(ll);
+
+ switch (event) {
+
+ case SD_IPV4ACD_EVENT_STOP:
+ ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
+ ll->claimed_address = 0;
+ break;
+
+ case SD_IPV4ACD_EVENT_BIND:
+ ll->claimed_address = ll->address;
+ ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND);
+ break;
+
+ case SD_IPV4ACD_EVENT_CONFLICT:
+ /* if an address was already bound we must call up to the
+ user to handle this, otherwise we just try again */
+ if (ll->claimed_address != 0) {
+ ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_CONFLICT);
+
+ ll->claimed_address = 0;
+ } else {
+ r = sd_ipv4ll_restart(ll);
+ if (r < 0)
+ goto error;
+ }
+
+ break;
+
+ default:
+ assert_not_reached("Invalid IPv4ACD event.");
+ }
+
+ return;
+
+error:
+ ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
+}
diff --git a/src/libsystemd-network/sd-lldp.c b/src/libsystemd-network/sd-lldp.c
new file mode 100644
index 0000000..8b66652
--- /dev/null
+++ b/src/libsystemd-network/sd-lldp.c
@@ -0,0 +1,498 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <arpa/inet.h>
+#include <linux/sockios.h>
+#include <sys/ioctl.h>
+
+#include "sd-lldp.h"
+
+#include "alloc-util.h"
+#include "ether-addr-util.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "lldp-internal.h"
+#include "lldp-neighbor.h"
+#include "lldp-network.h"
+#include "memory-util.h"
+#include "socket-util.h"
+#include "sort-util.h"
+#include "string-table.h"
+
+#define LLDP_DEFAULT_NEIGHBORS_MAX 128U
+
+static const char * const lldp_event_table[_SD_LLDP_EVENT_MAX] = {
+ [SD_LLDP_EVENT_ADDED] = "added",
+ [SD_LLDP_EVENT_REMOVED] = "removed",
+ [SD_LLDP_EVENT_UPDATED] = "updated",
+ [SD_LLDP_EVENT_REFRESHED] = "refreshed",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(lldp_event, sd_lldp_event);
+
+static void lldp_flush_neighbors(sd_lldp *lldp) {
+ assert(lldp);
+
+ hashmap_clear(lldp->neighbor_by_id);
+}
+
+static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) {
+ assert(lldp);
+ assert(event >= 0 && event < _SD_LLDP_EVENT_MAX);
+
+ if (!lldp->callback) {
+ log_lldp("Received '%s' event.", lldp_event_to_string(event));
+ return;
+ }
+
+ log_lldp("Invoking callback for '%s' event.", lldp_event_to_string(event));
+ lldp->callback(lldp, event, n, lldp->userdata);
+}
+
+static int lldp_make_space(sd_lldp *lldp, size_t extra) {
+ usec_t t = USEC_INFINITY;
+ bool changed = false;
+
+ assert(lldp);
+
+ /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
+ * are free. */
+
+ for (;;) {
+ _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+
+ n = prioq_peek(lldp->neighbor_by_expiry);
+ if (!n)
+ break;
+
+ sd_lldp_neighbor_ref(n);
+
+ if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra))
+ goto remove_one;
+
+ if (t == USEC_INFINITY)
+ t = now(clock_boottime_or_monotonic());
+
+ if (n->until > t)
+ break;
+
+ remove_one:
+ lldp_neighbor_unlink(n);
+ lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n);
+ changed = true;
+ }
+
+ return changed;
+}
+
+static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
+ assert(lldp);
+ assert(n);
+
+ /* Don't keep data with a zero TTL */
+ if (n->ttl <= 0)
+ return false;
+
+ /* Filter out data from the filter address */
+ if (!ether_addr_is_null(&lldp->filter_address) &&
+ ether_addr_equal(&lldp->filter_address, &n->source_address))
+ return false;
+
+ /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
+ * no caps field set. */
+ if (n->has_capabilities &&
+ (n->enabled_capabilities & lldp->capability_mask) == 0)
+ return false;
+
+ /* Keep everything else */
+ return true;
+}
+
+static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor);
+
+static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
+ _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL;
+ bool keep;
+ int r;
+
+ assert(lldp);
+ assert(n);
+ assert(!n->lldp);
+
+ keep = lldp_keep_neighbor(lldp, n);
+
+ /* First retrieve the old entry for this MSAP */
+ old = hashmap_get(lldp->neighbor_by_id, &n->id);
+ if (old) {
+ sd_lldp_neighbor_ref(old);
+
+ if (!keep) {
+ lldp_neighbor_unlink(old);
+ lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
+ return 0;
+ }
+
+ if (lldp_neighbor_equal(n, old)) {
+ /* Is this equal, then restart the TTL counter, but don't do anything else. */
+ old->timestamp = n->timestamp;
+ lldp_start_timer(lldp, old);
+ lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
+ return 0;
+ }
+
+ /* Data changed, remove the old entry, and add a new one */
+ lldp_neighbor_unlink(old);
+
+ } else if (!keep)
+ return 0;
+
+ /* Then, make room for at least one new neighbor */
+ lldp_make_space(lldp, 1);
+
+ r = hashmap_put(lldp->neighbor_by_id, &n->id, n);
+ if (r < 0)
+ goto finish;
+
+ r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx);
+ if (r < 0) {
+ assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n);
+ goto finish;
+ }
+
+ n->lldp = lldp;
+
+ lldp_start_timer(lldp, n);
+ lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n);
+
+ return 1;
+
+finish:
+ if (old)
+ lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
+
+ return r;
+}
+
+static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
+ int r;
+
+ assert(lldp);
+ assert(n);
+
+ r = lldp_neighbor_parse(n);
+ if (r == -EBADMSG) /* Ignore bad messages */
+ return 0;
+ if (r < 0)
+ return r;
+
+ r = lldp_add_neighbor(lldp, n);
+ if (r < 0) {
+ log_lldp_errno(r, "Failed to add datagram. Ignoring.");
+ return 0;
+ }
+
+ log_lldp("Successfully processed LLDP datagram.");
+ return 0;
+}
+
+static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+ ssize_t space, length;
+ sd_lldp *lldp = userdata;
+ struct timespec ts;
+
+ assert(fd >= 0);
+ assert(lldp);
+
+ space = next_datagram_size_fd(fd);
+ if (space < 0)
+ return log_lldp_errno(space, "Failed to determine datagram size to read: %m");
+
+ n = lldp_neighbor_new(space);
+ if (!n)
+ return -ENOMEM;
+
+ length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
+ if (length < 0) {
+ if (IN_SET(errno, EAGAIN, EINTR))
+ return 0;
+
+ return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
+ }
+
+ if ((size_t) length != n->raw_size) {
+ log_lldp("Packet size mismatch.");
+ return -EINVAL;
+ }
+
+ /* Try to get the timestamp of this packet if it is known */
+ if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
+ triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
+ else
+ triple_timestamp_get(&n->timestamp);
+
+ return lldp_handle_datagram(lldp, n);
+}
+
+static void lldp_reset(sd_lldp *lldp) {
+ assert(lldp);
+
+ (void) event_source_disable(lldp->timer_event_source);
+ lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
+ lldp->fd = safe_close(lldp->fd);
+}
+
+_public_ int sd_lldp_start(sd_lldp *lldp) {
+ int r;
+
+ assert_return(lldp, -EINVAL);
+ assert_return(lldp->event, -EINVAL);
+ assert_return(lldp->ifindex > 0, -EINVAL);
+
+ if (lldp->fd >= 0)
+ return 0;
+
+ assert(!lldp->io_event_source);
+
+ lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex);
+ if (lldp->fd < 0)
+ return lldp->fd;
+
+ r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
+
+ log_lldp("Started LLDP client");
+ return 1;
+
+fail:
+ lldp_reset(lldp);
+ return r;
+}
+
+_public_ int sd_lldp_stop(sd_lldp *lldp) {
+ if (!lldp)
+ return 0;
+
+ if (lldp->fd < 0)
+ return 0;
+
+ log_lldp("Stopping LLDP client");
+
+ lldp_reset(lldp);
+ lldp_flush_neighbors(lldp);
+
+ return 1;
+}
+
+_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(lldp, -EINVAL);
+ assert_return(lldp->fd < 0, -EBUSY);
+ assert_return(!lldp->event, -EBUSY);
+
+ if (event)
+ lldp->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&lldp->event);
+ if (r < 0)
+ return r;
+ }
+
+ lldp->event_priority = priority;
+
+ return 0;
+}
+
+_public_ int sd_lldp_detach_event(sd_lldp *lldp) {
+
+ assert_return(lldp, -EINVAL);
+ assert_return(lldp->fd < 0, -EBUSY);
+
+ lldp->event = sd_event_unref(lldp->event);
+ return 0;
+}
+
+_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) {
+ assert_return(lldp, NULL);
+
+ return lldp->event;
+}
+
+_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
+ assert_return(lldp, -EINVAL);
+
+ lldp->callback = cb;
+ lldp->userdata = userdata;
+
+ return 0;
+}
+
+_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) {
+ assert_return(lldp, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+ assert_return(lldp->fd < 0, -EBUSY);
+
+ lldp->ifindex = ifindex;
+ return 0;
+}
+
+static sd_lldp* lldp_free(sd_lldp *lldp) {
+ assert(lldp);
+
+ lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
+
+ lldp_reset(lldp);
+ sd_lldp_detach_event(lldp);
+ lldp_flush_neighbors(lldp);
+
+ hashmap_free(lldp->neighbor_by_id);
+ prioq_free(lldp->neighbor_by_expiry);
+ return mfree(lldp);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp, sd_lldp, lldp_free);
+
+_public_ int sd_lldp_new(sd_lldp **ret) {
+ _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ lldp = new(sd_lldp, 1);
+ if (!lldp)
+ return -ENOMEM;
+
+ *lldp = (sd_lldp) {
+ .n_ref = 1,
+ .fd = -1,
+ .neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX,
+ .capability_mask = (uint16_t) -1,
+ };
+
+ lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_hash_ops);
+ if (!lldp->neighbor_by_id)
+ return -ENOMEM;
+
+ r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(lldp);
+
+ return 0;
+}
+
+static int neighbor_compare_func(sd_lldp_neighbor * const *a, sd_lldp_neighbor * const *b) {
+ return lldp_neighbor_id_compare_func(&(*a)->id, &(*b)->id);
+}
+
+static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_lldp *lldp = userdata;
+ int r;
+
+ r = lldp_make_space(lldp, 0);
+ if (r < 0)
+ return log_lldp_errno(r, "Failed to make space: %m");
+
+ r = lldp_start_timer(lldp, NULL);
+ if (r < 0)
+ return log_lldp_errno(r, "Failed to restart timer: %m");
+
+ return 0;
+}
+
+static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) {
+ sd_lldp_neighbor *n;
+
+ assert(lldp);
+
+ if (neighbor)
+ lldp_neighbor_start_ttl(neighbor);
+
+ n = prioq_peek(lldp->neighbor_by_expiry);
+ if (!n)
+ return event_source_disable(lldp->timer_event_source);
+
+ if (!lldp->event)
+ return 0;
+
+ return event_reset_time(lldp->event, &lldp->timer_event_source,
+ clock_boottime_or_monotonic(),
+ n->until, 0,
+ on_timer_event, lldp,
+ lldp->event_priority, "lldp-timer", true);
+}
+
+_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
+ sd_lldp_neighbor **l = NULL, *n;
+ int k = 0, r;
+
+ assert_return(lldp, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */
+ *ret = NULL;
+ return 0;
+ }
+
+ l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id));
+ if (!l)
+ return -ENOMEM;
+
+ r = lldp_start_timer(lldp, NULL);
+ if (r < 0) {
+ free(l);
+ return r;
+ }
+
+ HASHMAP_FOREACH(n, lldp->neighbor_by_id)
+ l[k++] = sd_lldp_neighbor_ref(n);
+
+ assert((size_t) k == hashmap_size(lldp->neighbor_by_id));
+
+ /* Return things in a stable order */
+ typesafe_qsort(l, k, neighbor_compare_func);
+ *ret = l;
+
+ return k;
+}
+
+_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) {
+ assert_return(lldp, -EINVAL);
+ assert_return(m > 0, -EINVAL);
+
+ lldp->neighbors_max = m;
+ lldp_make_space(lldp, 0);
+
+ return 0;
+}
+
+_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) {
+ assert_return(lldp, -EINVAL);
+ assert_return(mask != 0, -EINVAL);
+
+ lldp->capability_mask = mask;
+
+ return 0;
+}
+
+_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) {
+ assert_return(lldp, -EINVAL);
+
+ /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
+ * that our own can be filtered out here. */
+
+ if (addr)
+ lldp->filter_address = *addr;
+ else
+ zero(lldp->filter_address);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c
new file mode 100644
index 0000000..db7ada6
--- /dev/null
+++ b/src/libsystemd-network/sd-ndisc.c
@@ -0,0 +1,389 @@
+/* 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 "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);
+
+static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) {
+ assert(ndisc);
+ assert(event >= 0 && event < _SD_NDISC_EVENT_MAX);
+
+ if (!ndisc->callback) {
+ log_ndisc("Received '%s' event.", ndisc_event_to_string(event));
+ return;
+ }
+
+ log_ndisc("Invoking callback for '%s' event.", ndisc_event_to_string(event));
+ ndisc->callback(ndisc, event, rt, ndisc->userdata);
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+}
+
+_public_ 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_unref(nd->recv_event_source);
+ nd->fd = safe_close(nd->fd);
+}
+
+static sd_ndisc *ndisc_free(sd_ndisc *nd) {
+ assert(nd);
+
+ nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
+ nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra);
+
+ ndisc_reset(nd);
+ sd_ndisc_detach_event(nd);
+ return mfree(nd);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc, sd_ndisc, ndisc_free);
+
+_public_ 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 = -1,
+ };
+
+ *ret = TAKE_PTR(nd);
+
+ return 0;
+}
+
+_public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
+ assert_return(nd, -EINVAL);
+ assert_return(mtu, -EINVAL);
+
+ if (nd->mtu == 0)
+ return -ENODATA;
+
+ *mtu = nd->mtu;
+ return 0;
+}
+
+_public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) {
+ assert_return(nd, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (nd->hop_limit == 0)
+ return -ENODATA;
+
+ *ret = nd->hop_limit;
+ return 0;
+}
+
+static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
+ int r;
+
+ assert(nd);
+ assert(rt);
+
+ r = ndisc_router_parse(rt);
+ if (r == -EBADMSG) /* Bad packet */
+ return 0;
+ if (r < 0)
+ return 0;
+
+ /* Update global variables we keep */
+ if (rt->mtu > 0)
+ nd->mtu = rt->mtu;
+ if (rt->hop_limit > 0)
+ nd->hop_limit = rt->hop_limit;
+
+ log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec",
+ 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",
+ rt->lifetime);
+
+ 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 = userdata;
+ ssize_t buflen;
+ int r;
+ _cleanup_free_ char *addr = NULL;
+
+ assert(s);
+ assert(nd);
+ assert(nd->event);
+
+ buflen = next_datagram_size_fd(fd);
+ if (buflen < 0)
+ return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m");
+
+ 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 (r < 0) {
+ switch (r) {
+ case -EADDRNOTAVAIL:
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &rt->address, &addr);
+ log_ndisc("Received RA from non-link-local address %s. Ignoring", addr);
+ break;
+
+ case -EMULTIHOP:
+ log_ndisc("Received RA with invalid hop limit. Ignoring.");
+ break;
+
+ case -EPFNOSUPPORT:
+ log_ndisc("Received invalid source address from ICMPv6 socket. Ignoring.");
+ break;
+
+ case -EAGAIN: /* ignore spurious wakeups */
+ break;
+
+ default:
+ log_ndisc_errno(r, "Unexpected error while reading from ICMPv6, ignoring: %m");
+ break;
+ }
+
+ return 0;
+ }
+
+ (void) event_source_disable(nd->timeout_event_source);
+
+ return ndisc_handle_datagram(nd, rt);
+}
+
+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) {
+ char time_string[FORMAT_TIMESPAN_MAX];
+ sd_ndisc *nd = userdata;
+ usec_t time_now;
+ int r;
+
+ assert(s);
+ assert(nd);
+ assert(nd->event);
+
+ assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &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_or_monotonic(),
+ 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(r, "Error sending Router Solicitation: %m");
+ goto fail;
+ }
+
+ log_ndisc("Sent Router Solicitation, next solicitation in %s",
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX,
+ 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 = userdata;
+
+ assert(s);
+ assert(nd);
+
+ log_ndisc("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;
+}
+
+_public_ int sd_ndisc_stop(sd_ndisc *nd) {
+ if (!nd)
+ return 0;
+
+ if (nd->fd < 0)
+ return 0;
+
+ log_ndisc("Stopping IPv6 Router Solicitation client");
+
+ ndisc_reset(nd);
+ return 1;
+}
+
+_public_ 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_or_monotonic(), &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_or_monotonic(),
+ 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_or_monotonic(),
+ 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("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..8beb845
--- /dev/null
+++ b/src/libsystemd-network/sd-radv.c
@@ -0,0 +1,953 @@
+/* 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 "io-util.h"
+#include "macro.h"
+#include "memory-util.h"
+#include "radv-internal.h"
+#include "random-util.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "strv.h"
+
+_public_ 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 = -1,
+ };
+
+ *ret = TAKE_PTR(ra);
+
+ return 0;
+}
+
+_public_ 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;
+}
+
+_public_ int sd_radv_detach_event(sd_radv *ra) {
+
+ assert_return(ra, -EINVAL);
+
+ ra->event = sd_event_unref(ra->event);
+ return 0;
+}
+
+_public_ sd_event *sd_radv_get_event(sd_radv *ra) {
+ assert_return(ra, NULL);
+
+ return ra->event;
+}
+
+_public_ int sd_radv_is_running(sd_radv *ra) {
+ assert_return(ra, false);
+
+ return ra->state != SD_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_unref(ra->recv_event_source);
+
+ ra->ra_sent = 0;
+}
+
+static sd_radv *radv_free(sd_radv *ra) {
+ if (!ra)
+ return NULL;
+
+ while (ra->prefixes) {
+ sd_radv_prefix *p = ra->prefixes;
+
+ LIST_REMOVE(prefix, ra->prefixes, p);
+ sd_radv_prefix_unref(p);
+ }
+
+ while (ra->route_prefixes) {
+ sd_radv_route_prefix *p = ra->route_prefixes;
+
+ LIST_REMOVE(prefix, ra->route_prefixes, p);
+ sd_radv_route_prefix_unref(p);
+ }
+
+ free(ra->rdnss);
+ free(ra->dnssl);
+
+ ra->timeout_event_source = sd_event_source_unref(ra->timeout_event_source);
+
+ radv_reset(ra);
+
+ sd_radv_detach_event(ra);
+
+ ra->fd = safe_close(ra->fd);
+
+ return mfree(ra);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv, sd_radv, radv_free);
+
+static int radv_send(sd_radv *ra, const struct in6_addr *dst, uint32_t router_lifetime) {
+ sd_radv_route_prefix *rt;
+ sd_radv_prefix *p;
+ 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, RDNSS
+ and DNSSL */
+ struct iovec iov[5 + ra->n_prefixes + ra->n_route_prefixes];
+ struct msghdr msg = {
+ .msg_name = &dst_addr,
+ .msg_namelen = sizeof(dst_addr),
+ .msg_iov = iov,
+ };
+ usec_t time_now;
+ int r;
+
+ assert(ra);
+
+ r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return r;
+
+ if (dst && !IN6_IS_ADDR_UNSPECIFIED(dst))
+ dst_addr.sin6_addr = *dst;
+
+ adv.nd_ra_type = ND_ROUTER_ADVERT;
+ adv.nd_ra_curhoplimit = ra->hop_limit;
+ adv.nd_ra_flags_reserved = ra->flags;
+ adv.nd_ra_router_lifetime = htobe16(router_lifetime);
+ 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) {
+ 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) {
+ if (p->valid_until) {
+
+ if (time_now > p->valid_until)
+ p->opt.valid_lifetime = 0;
+ else
+ p->opt.valid_lifetime = htobe32((p->valid_until - time_now) / USEC_PER_SEC);
+
+ if (time_now > p->preferred_until)
+ p->opt.preferred_lifetime = 0;
+ else
+ p->opt.preferred_lifetime = htobe32((p->preferred_until - time_now) / USEC_PER_SEC);
+ }
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(&p->opt, sizeof(p->opt));
+ }
+
+ LIST_FOREACH(prefix, rt, ra->route_prefixes)
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(&rt->opt, sizeof(rt->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 (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 = userdata;
+ _cleanup_free_ char *addr = NULL;
+ struct in6_addr src;
+ triple_timestamp timestamp;
+ int r;
+ ssize_t buflen;
+ _cleanup_free_ char *buf = NULL;
+
+ assert(s);
+ assert(ra);
+ assert(ra->event);
+
+ buflen = next_datagram_size_fd(fd);
+ if (buflen < 0)
+ return (int) buflen;
+
+ buf = new0(char, buflen);
+ if (!buf)
+ return -ENOMEM;
+
+ r = icmp6_receive(fd, buf, buflen, &src, &timestamp);
+ if (r < 0) {
+ switch (r) {
+ case -EADDRNOTAVAIL:
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &src, &addr);
+ log_radv("Received RS from non-link-local address %s. Ignoring", addr);
+ break;
+
+ case -EMULTIHOP:
+ log_radv("Received RS with invalid hop limit. Ignoring.");
+ break;
+
+ case -EPFNOSUPPORT:
+ log_radv("Received invalid source address from ICMPv6 socket. Ignoring.");
+ break;
+
+ case -EAGAIN: /* ignore spurious wakeups */
+ break;
+
+ default:
+ log_radv_errno(r, "Unexpected error receiving from ICMPv6 socket: %m");
+ break;
+ }
+
+ return 0;
+ }
+
+ if ((size_t) buflen < sizeof(struct nd_router_solicit)) {
+ log_radv("Too short packet received");
+ return 0;
+ }
+
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &src, &addr);
+
+ r = radv_send(ra, &src, ra->lifetime);
+ if (r < 0)
+ log_radv_errno(r, "Unable to send solicited Router Advertisement to %s: %m", strnull(addr));
+ else
+ log_radv("Sent solicited Router Advertisement to %s", strnull(addr));
+
+ return 0;
+}
+
+static usec_t radv_compute_timeout(usec_t min, usec_t max) {
+ assert_return(min <= max, SD_RADV_DEFAULT_MIN_TIMEOUT_USEC);
+
+ /* RFC 4861: min must be no less than 3s, max must be no less than 4s */
+ min = MAX(min, 3*USEC_PER_SEC);
+ max = MAX(max, 4*USEC_PER_SEC);
+
+ return min + (random_u32() % (max - min));
+}
+
+static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+ int r;
+ sd_radv *ra = userdata;
+ usec_t min_timeout = SD_RADV_DEFAULT_MIN_TIMEOUT_USEC;
+ usec_t max_timeout = SD_RADV_DEFAULT_MAX_TIMEOUT_USEC;
+ usec_t time_now, timeout;
+ char time_string[FORMAT_TIMESPAN_MAX];
+
+ assert(s);
+ assert(ra);
+ assert(ra->event);
+
+ r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ goto fail;
+
+ r = radv_send(ra, NULL, ra->lifetime);
+ if (r < 0)
+ log_radv_errno(r, "Unable to send Router Advertisement: %m");
+
+ /* RFC 4861, Section 6.2.4, sending initial Router Advertisements */
+ if (ra->ra_sent < SD_RADV_MAX_INITIAL_RTR_ADVERTISEMENTS) {
+ max_timeout = SD_RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC;
+ min_timeout = SD_RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC / 3;
+ }
+
+ /* RFC 4861, Section 6.2.1, lifetime must be at least MaxRtrAdvInterval,
+ so lower the interval here */
+ if (ra->lifetime > 0 && (ra->lifetime * USEC_PER_SEC) < max_timeout) {
+ max_timeout = ra->lifetime * USEC_PER_SEC;
+ min_timeout = max_timeout / 3;
+ }
+
+ timeout = radv_compute_timeout(min_timeout, max_timeout);
+
+ log_radv("Next Router Advertisement in %s",
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX,
+ timeout, USEC_PER_SEC));
+
+ r = event_reset_time(ra->event, &ra->timeout_event_source,
+ clock_boottime_or_monotonic(),
+ 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;
+}
+
+_public_ int sd_radv_stop(sd_radv *ra) {
+ int r;
+
+ if (!ra)
+ return 0;
+
+ if (ra->state == SD_RADV_STATE_IDLE)
+ return 0;
+
+ log_radv("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(r, "Unable to send last Router Advertisement with router lifetime set to zero: %m");
+
+ radv_reset(ra);
+ ra->fd = safe_close(ra->fd);
+ ra->state = SD_RADV_STATE_IDLE;
+
+ return 0;
+}
+
+_public_ 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 != SD_RADV_STATE_IDLE)
+ return 0;
+
+ r = event_reset_time(ra->event, &ra->timeout_event_source,
+ clock_boottime_or_monotonic(),
+ 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 = SD_RADV_STATE_ADVERTISING;
+
+ log_radv("Started IPv6 Router Advertisement daemon");
+
+ return 0;
+
+ fail:
+ radv_reset(ra);
+
+ return r;
+}
+
+_public_ int sd_radv_set_ifindex(sd_radv *ra, int ifindex) {
+ assert_return(ra, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ ra->ifindex = ifindex;
+
+ return 0;
+}
+
+_public_ int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ if (mac_addr)
+ ra->mac_addr = *mac_addr;
+ else
+ zero(ra->mac_addr);
+
+ return 0;
+}
+
+_public_ 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;
+}
+
+_public_ int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ ra->hop_limit = hop_limit;
+
+ return 0;
+}
+
+_public_ int sd_radv_set_router_lifetime(sd_radv *ra, uint16_t router_lifetime) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ /* RFC 4191, Section 2.2, "...If the Router Lifetime is zero, the preference value MUST be set
+ * to (00) by the sender..." */
+ if (router_lifetime == 0 &&
+ (ra->flags & (0x3 << 3)) != (SD_NDISC_PREFERENCE_MEDIUM << 3))
+ return -ETIME;
+
+ ra->lifetime = router_lifetime;
+
+ return 0;
+}
+
+_public_ int sd_radv_set_managed_information(sd_radv *ra, int managed) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ SET_FLAG(ra->flags, ND_RA_FLAG_MANAGED, managed);
+
+ return 0;
+}
+
+_public_ int sd_radv_set_other_information(sd_radv *ra, int other) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ SET_FLAG(ra->flags, ND_RA_FLAG_OTHER, other);
+
+ return 0;
+}
+
+_public_ 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 == 0 && preference != SD_NDISC_PREFERENCE_MEDIUM)
+ return -EINVAL;
+
+ ra->flags = (ra->flags & ~(0x3 << 3)) | (preference << 3);
+
+ return 0;
+}
+
+_public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p, int dynamic) {
+ sd_radv_prefix *cur;
+ int r;
+ _cleanup_free_ char *addr_p = NULL;
+ char time_string_preferred[FORMAT_TIMESPAN_MAX];
+ char time_string_valid[FORMAT_TIMESPAN_MAX];
+ usec_t time_now, valid, preferred, valid_until, preferred_until;
+
+ assert_return(ra, -EINVAL);
+
+ if (!p)
+ return -EINVAL;
+
+ /* Refuse prefixes that don't have a prefix set */
+ if (IN6_IS_ADDR_UNSPECIFIED(&p->opt.in6_addr))
+ return -ENOEXEC;
+
+ LIST_FOREACH(prefix, cur, ra->prefixes) {
+
+ r = in_addr_prefix_intersect(AF_INET6,
+ (union in_addr_union*) &cur->opt.in6_addr,
+ cur->opt.prefixlen,
+ (union in_addr_union*) &p->opt.in6_addr,
+ p->opt.prefixlen);
+ if (r > 0) {
+ _cleanup_free_ char *addr_cur = NULL;
+
+ (void) in_addr_to_string(AF_INET6,
+ (union in_addr_union*) &p->opt.in6_addr,
+ &addr_p);
+
+ if (dynamic && cur->opt.prefixlen == p->opt.prefixlen)
+ goto update;
+
+ (void) in_addr_to_string(AF_INET6,
+ (union in_addr_union*) &cur->opt.in6_addr,
+ &addr_cur);
+ log_radv("IPv6 prefix %s/%u already configured, ignoring %s/%u",
+ addr_cur, cur->opt.prefixlen,
+ addr_p, p->opt.prefixlen);
+
+ return -EEXIST;
+ }
+ }
+
+ p = sd_radv_prefix_ref(p);
+
+ LIST_APPEND(prefix, ra->prefixes, p);
+
+ ra->n_prefixes++;
+
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &p->opt.in6_addr, &addr_p);
+
+ if (!dynamic) {
+ log_radv("Added prefix %s/%d", addr_p, p->opt.prefixlen);
+ return 0;
+ }
+
+ cur = p;
+
+ /* If RAs have already been sent, send an RA immediately to announce the newly-added prefix */
+ if (ra->ra_sent > 0) {
+ r = radv_send(ra, NULL, ra->lifetime);
+ if (r < 0)
+ log_radv_errno(r, "Unable to send Router Advertisement for added prefix: %m");
+ else
+ log_radv("Sent Router Advertisement for added prefix");
+ }
+
+ update:
+ r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return r;
+
+ valid = be32toh(p->opt.valid_lifetime) * USEC_PER_SEC;
+ valid_until = usec_add(valid, time_now);
+ if (valid_until == USEC_INFINITY)
+ return -EOVERFLOW;
+
+ preferred = be32toh(p->opt.preferred_lifetime) * USEC_PER_SEC;
+ preferred_until = usec_add(preferred, time_now);
+ if (preferred_until == USEC_INFINITY)
+ return -EOVERFLOW;
+
+ cur->valid_until = valid_until;
+ cur->preferred_until = preferred_until;
+
+ log_radv("Updated prefix %s/%u preferred %s valid %s",
+ addr_p, p->opt.prefixlen,
+ format_timespan(time_string_preferred, FORMAT_TIMESPAN_MAX,
+ preferred, USEC_PER_SEC),
+ format_timespan(time_string_valid, FORMAT_TIMESPAN_MAX,
+ valid, USEC_PER_SEC));
+
+ return 0;
+}
+
+_public_ sd_radv_prefix *sd_radv_remove_prefix(sd_radv *ra,
+ const struct in6_addr *prefix,
+ unsigned char prefixlen) {
+ sd_radv_prefix *cur, *next;
+
+ assert_return(ra, NULL);
+ assert_return(prefix, NULL);
+
+ LIST_FOREACH_SAFE(prefix, cur, next, ra->prefixes) {
+ if (prefixlen != cur->opt.prefixlen)
+ continue;
+
+ if (!in_addr_equal(AF_INET6,
+ (union in_addr_union *)prefix,
+ (union in_addr_union *)&cur->opt.in6_addr))
+ continue;
+
+ LIST_REMOVE(prefix, ra->prefixes, cur);
+ ra->n_prefixes--;
+ sd_radv_prefix_unref(cur);
+
+ break;
+ }
+
+ return cur;
+}
+
+_public_ int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p, int dynamic) {
+ char time_string_valid[FORMAT_TIMESPAN_MAX];
+ usec_t time_now, valid, valid_until;
+ _cleanup_free_ char *pretty = NULL;
+ sd_radv_route_prefix *cur;
+ int r;
+
+ assert_return(ra, -EINVAL);
+
+ if (!p)
+ return -EINVAL;
+
+ (void) in_addr_to_string(AF_INET6,
+ (union in_addr_union*) &p->opt.in6_addr,
+ &pretty);
+
+ LIST_FOREACH(prefix, cur, ra->route_prefixes) {
+ _cleanup_free_ char *addr = NULL;
+
+ r = in_addr_prefix_intersect(AF_INET6,
+ (union in_addr_union*) &cur->opt.in6_addr,
+ cur->opt.prefixlen,
+ (union in_addr_union*) &p->opt.in6_addr,
+ p->opt.prefixlen);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (dynamic && cur->opt.prefixlen == p->opt.prefixlen)
+ goto update;
+
+ (void) in_addr_to_string(AF_INET6,
+ (union in_addr_union*) &cur->opt.in6_addr,
+ &addr);
+ log_radv("IPv6 route prefix %s/%u already configured, ignoring %s/%u",
+ strempty(addr), cur->opt.prefixlen,
+ strempty(pretty), p->opt.prefixlen);
+
+ return -EEXIST;
+ }
+
+ p = sd_radv_route_prefix_ref(p);
+
+ LIST_APPEND(prefix, ra->route_prefixes, p);
+ ra->n_route_prefixes++;
+
+ if (!dynamic) {
+ log_radv("Added prefix %s/%u", strempty(pretty), p->opt.prefixlen);
+ return 0;
+ }
+
+ /* If RAs have already been sent, send an RA immediately to announce the newly-added route prefix */
+ if (ra->ra_sent > 0) {
+ r = radv_send(ra, NULL, ra->lifetime);
+ if (r < 0)
+ log_radv_errno(r, "Unable to send Router Advertisement for added route prefix: %m");
+ else
+ log_radv("Sent Router Advertisement for added route prefix");
+ }
+
+ update:
+ r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return r;
+
+ valid = be32toh(p->opt.lifetime) * USEC_PER_SEC;
+ valid_until = usec_add(valid, time_now);
+ if (valid_until == USEC_INFINITY)
+ return -EOVERFLOW;
+
+ log_radv("Updated route prefix %s/%u valid %s",
+ strempty(pretty), p->opt.prefixlen,
+ format_timespan(time_string_valid, FORMAT_TIMESPAN_MAX, valid, USEC_PER_SEC));
+
+ return 0;
+}
+
+_public_ int sd_radv_set_rdnss(sd_radv *ra, uint32_t lifetime,
+ 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 (!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 = SD_RADV_OPT_RDNSS;
+ opt_rdnss->length = len / 8;
+ opt_rdnss->lifetime = htobe32(lifetime);
+
+ 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;
+}
+
+_public_ int sd_radv_set_dnssl(sd_radv *ra, uint32_t lifetime,
+ char **search_list) {
+ _cleanup_free_ struct sd_radv_opt_dns *opt_dnssl = NULL;
+ size_t len = 0;
+ char **s;
+ uint8_t *p;
+
+ assert_return(ra, -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 = SD_RADV_OPT_DNSSL;
+ opt_dnssl->length = len / 8;
+ opt_dnssl->lifetime = htobe32(lifetime);
+
+ 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;
+}
+
+_public_ 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,
+
+ .opt.preferred_lifetime = htobe32(604800),
+ .opt.valid_lifetime = htobe32(2592000),
+ };
+
+ *ret = p;
+ return 0;
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv_prefix, sd_radv_prefix, mfree);
+
+_public_ 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("Unusual prefix length %d greater than 64", prefixlen);
+
+ p->opt.in6_addr = *in6_addr;
+ p->opt.prefixlen = prefixlen;
+
+ return 0;
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+}
+
+_public_ 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;
+}
+
+_public_ int sd_radv_prefix_set_valid_lifetime(sd_radv_prefix *p,
+ uint32_t valid_lifetime) {
+ assert_return(p, -EINVAL);
+
+ p->opt.valid_lifetime = htobe32(valid_lifetime);
+
+ return 0;
+}
+
+_public_ int sd_radv_prefix_set_preferred_lifetime(sd_radv_prefix *p,
+ uint32_t preferred_lifetime) {
+ assert_return(p, -EINVAL);
+
+ p->opt.preferred_lifetime = htobe32(preferred_lifetime);
+
+ return 0;
+}
+
+_public_ 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 = SD_RADV_OPT_ROUTE_INFORMATION,
+ .opt.length = DIV_ROUND_UP(sizeof(p->opt), 8),
+ .opt.prefixlen = 64,
+
+ .opt.lifetime = htobe32(604800),
+ };
+
+ *ret = p;
+ return 0;
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv_route_prefix, sd_radv_route_prefix, mfree);
+
+_public_ int sd_radv_prefix_set_route_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("Unusual prefix length %u greater than 64", prefixlen);
+
+ p->opt.in6_addr = *in6_addr;
+ p->opt.prefixlen = prefixlen;
+
+ return 0;
+}
+
+_public_ int sd_radv_route_prefix_set_lifetime(sd_radv_route_prefix *p, uint32_t valid_lifetime) {
+ assert_return(p, -EINVAL);
+
+ p->opt.lifetime = htobe32(valid_lifetime);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/test-acd.c b/src/libsystemd-network/test-acd.c
new file mode 100644
index 0000000..f4aab85
--- /dev/null
+++ b/src/libsystemd-network/test-acd.c
@@ -0,0 +1,96 @@
+/* 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 "netlink-util.h"
+#include "tests.h"
+#include "util.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("invalid ACD event");
+ }
+}
+
+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..d39d1f5
--- /dev/null
+++ b/src/libsystemd-network/test-dhcp-client.c
@@ -0,0 +1,570 @@
+/* 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>
+
+#include "sd-dhcp-client.h"
+#include "sd-event.h"
+
+#include "alloc-util.h"
+#include "dhcp-identifier.h"
+#include "dhcp-internal.h"
+#include "dhcp-protocol.h"
+#include "fd-util.h"
+#include "random-util.h"
+#include "tests.h"
+#include "util.h"
+
+static uint8_t mac_addr[] = {'A', 'B', 'C', '1', '2', '3'};
+static uint8_t bcast_addr[] = { 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 sd_event_source *test_hangcheck;
+
+static int test_dhcp_hangcheck(sd_event_source *s, uint64_t usec, void *userdata) {
+ assert_not_reached("Test case should have completed in 2 seconds");
+
+ return 0;
+}
+
+static void test_request_basic(sd_event *e) {
+ int r;
+
+ sd_dhcp_client *client;
+
+ if (verbose)
+ printf("* %s\n", __FUNCTION__);
+
+ /* 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", __FUNCTION__);
+
+ /* 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_NAMESERVER) == 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", __FUNCTION__);
+
+ 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;
+ int ifindex;
+
+ for (;;) {
+ char ifname[IFNAMSIZ];
+
+ /* try to find an ifindex which does not exist. I causes dhcp_identifier_set_iaid()
+ * to hash the MAC address. */
+ pseudo_random_bytes(&ifindex, sizeof(ifindex));
+ if (ifindex > 0 && !if_indextoname(ifindex, ifname))
+ break;
+ }
+
+ assert_se(dhcp_identifier_set_iaid(ifindex, mac_addr, sizeof(mac_addr), true, &iaid_legacy) >= 0);
+ assert_se(dhcp_identifier_set_iaid(ifindex, mac_addr, sizeof(mac_addr), 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(42, mac_addr, ETH_ALEN, 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;
+ 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, &mac_addr, ETH_ALEN) == 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 uint8_t *addr, size_t addr_len,
+ const uint8_t *bcaddr, size_t bcaddr_len,
+ uint16_t arp_type, uint16_t port) {
+
+ 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", __FUNCTION__);
+
+ 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, mac_addr, bcast_addr, ETH_ALEN, 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_t) -1);
+
+ 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], &mac_addr, ETHER_ADDR_LEN);
+
+ 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], &mac_addr, ETHER_ADDR_LEN);
+
+ 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", __FUNCTION__);
+
+ 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, mac_addr, bcast_addr, ETH_ALEN, 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, &test_hangcheck,
+ clock_boottime_or_monotonic(),
+ 2 * USEC_PER_SEC, 0,
+ test_dhcp_hangcheck, NULL) >= 0);
+
+ res = sd_dhcp_client_start(client);
+ assert_se(IN_SET(res, 0, -EINPROGRESS));
+
+ assert_se(sd_event_loop(e) >= 0);
+
+ test_hangcheck = sd_event_source_unref(test_hangcheck);
+
+ 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;
+
+ 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 VALGRIND
+ /* Make sure the async_close thread has finished.
+ * valgrind would report some of the phread_* structures
+ * as not cleaned up properly. */
+ 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..1eebe3a
--- /dev/null
+++ b/src/libsystemd-network/test-dhcp-option.c
@@ -0,0 +1,370 @@
+/* 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-internal.h"
+#include "dhcp-protocol.h"
+#include "macro.h"
+#include "memory-util.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, 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(*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;
+ uint8_t i;
+
+ 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 (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 uint8_t 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;
+ unsigned i;
+
+ 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 && options[pos] != SD_DHCP_OPTION_END) {
+ assert_se(dhcp_option_append(result, len, &offset, DHCP_OVERLOAD_SNAME,
+ options[pos],
+ options[pos + 1],
+ &options[pos + 2]) >= 0);
+
+ if (options[pos] == SD_DHCP_OPTION_PAD)
+ pos++;
+ else
+ pos += 2 + options[pos + 1];
+
+ if (pos < len)
+ assert_se(offset == pos);
+ }
+
+ for (i = 0; i < 9; i++) {
+ if (verbose)
+ printf("%2u: 0x%02x(0x%02x) (options)\n", i, result->options[i],
+ options[i]);
+ assert_se(result->options[i] == options[i]);
+ }
+
+ if (verbose)
+ printf("%2d: 0x%02x(0x%02x) (options)\n", 9, result->options[9],
+ 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],
+ SD_DHCP_OPTION_PAD);
+
+ assert_se(result->options[10] == SD_DHCP_OPTION_PAD);
+
+ for (i = 0; i < pos - 8; i++) {
+ if (verbose)
+ printf("%2u: 0x%02x(0x%02x) (sname)\n", i, result->sname[i],
+ options[i + 9]);
+ assert_se(result->sname[i] == options[i + 9]);
+ }
+
+ if (verbose)
+ printf ("\n");
+}
+
+int main(int argc, char *argv[]) {
+ unsigned i;
+
+ test_invalid_buffer_length();
+ test_message_init();
+
+ test_options(NULL);
+
+ for (i = 0; i < ELEMENTSOF(option_tests); i++)
+ test_options(&option_tests[i]);
+
+ test_option_set();
+
+ 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..e91b440
--- /dev/null
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -0,0 +1,245 @@
+/* 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(sd_event *event) {
+ _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
+ struct in_addr address_lo = {
+ .s_addr = htobe32(INADDR_LOOPBACK),
+ };
+ struct in_addr address_any = {
+ .s_addr = htobe32(INADDR_ANY),
+ };
+ int r;
+
+ /* attach to loopback interface */
+ assert_se(sd_dhcp_server_new(&server, 1) >= 0);
+ assert_se(server);
+
+ 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 log_info_errno(r, "sd_dhcp_server_start failed: %m");
+ assert_se(r >= 0);
+
+ assert_se(sd_dhcp_server_start(server) == -EBUSY);
+ 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;
+ 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,
+ .end = SD_DHCP_OPTION_END,
+ };
+ struct in_addr address_lo = {
+ .s_addr = htobe32(INADDR_LOOPBACK),
+ };
+
+ 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_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)) == DHCP_OFFER);
+
+ test.end = 0;
+ /* TODO, shouldn't this fail? */
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+ test.end = SD_DHCP_OPTION_END;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 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)) == 0);
+ 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)) == DHCP_OFFER);
+
+ test.message.op = 0;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+ test.message.op = BOOTREQUEST;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+
+ test.message.htype = 0;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+ test.message.htype = ARPHRD_ETHER;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+
+ test.message.hlen = 0;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+ test.message.hlen = ETHER_ADDR_LEN;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+
+ test.option_type.type = DHCP_REQUEST;
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 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)) == 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)) == 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)) == 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)) == 0);
+ test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 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)) == DHCP_ACK);
+
+ test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+}
+
+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',
+ };
+
+ 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);
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_(sd_event_unrefp) sd_event *e;
+ int r;
+
+ test_setup_logging(LOG_DEBUG);
+
+ assert_se(sd_event_new(&e) >= 0);
+
+ r = test_basic(e);
+ if (r != 0)
+ return log_tests_skipped("cannot start dhcp server");
+
+ test_message_handler();
+ test_client_id_hash();
+
+ 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..e6a43dc
--- /dev/null
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -0,0 +1,1004 @@
+/* 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 "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"
+#include "virt.h"
+
+static struct ether_addr mac_addr = {
+ .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
+};
+
+static sd_event_source *hangcheck;
+static int test_dhcp_fd[2];
+static int test_ifindex = 42;
+static int test_client_message_num;
+static be32_t test_iaid = 0;
+static uint8_t test_duid[14] = { };
+
+static int test_client_basic(sd_event *e) {
+ sd_dhcp6_client *client;
+ int v;
+
+ log_debug("/* %s */", __func__);
+
+ assert_se(sd_dhcp6_client_new(&client) >= 0);
+ assert_se(client);
+
+ assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0);
+
+ assert_se(sd_dhcp6_client_set_ifindex(client, 15) == 0);
+ assert_se(sd_dhcp6_client_set_ifindex(client, -42) == -EINVAL);
+ assert_se(sd_dhcp6_client_set_ifindex(client, -1) == -EINVAL);
+ assert_se(sd_dhcp6_client_set_ifindex(client, 42) >= 0);
+
+ assert_se(sd_dhcp6_client_set_mac(client, (const uint8_t *) &mac_addr,
+ sizeof (mac_addr),
+ 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) == 0);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVERS) == -EEXIST);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER) == -EEXIST);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SNTP_SERVERS) == -EEXIST);
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN_LIST) == -EEXIST);
+ assert_se(sd_dhcp6_client_set_request_option(client, 10) == 0);
+
+ 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);
+ assert_se(!sd_dhcp6_client_unref(client));
+
+ return 0;
+}
+
+static int test_parse_domain(sd_event *e) {
+ uint8_t *data;
+ char *domain;
+ char **list;
+ int r;
+
+ log_debug("/* %s */", __func__);
+
+ data = (uint8_t []) { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0 };
+ r = dhcp6_option_parse_domainname(data, 13, &domain);
+ assert_se(r == 0);
+ assert_se(domain);
+ assert_se(streq(domain, "example.com"));
+ free(domain);
+
+ data = (uint8_t []) { 4, 't', 'e', 's', 't' };
+ r = dhcp6_option_parse_domainname(data, 5, &domain);
+ assert_se(r == 0);
+ assert_se(domain);
+ assert_se(streq(domain, "test"));
+ free(domain);
+
+ data = (uint8_t []) { 0 };
+ r = dhcp6_option_parse_domainname(data, 1, &domain);
+ assert_se(r < 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 };
+ r = dhcp6_option_parse_domainname_list(data, 21, &list);
+ assert_se(r == 2);
+ assert_se(list);
+ assert_se(streq(list[0], "example.com"));
+ assert_se(streq(list[1], "foobar"));
+ strv_free(list);
+
+ data = (uint8_t []) { 1, 'a', 0, 20, 'b', 'c' };
+ r = dhcp6_option_parse_domainname_list(data, 6, &list);
+ assert_se(r < 0);
+
+ data = (uint8_t []) { 0 , 0 };
+ r = dhcp6_option_parse_domainname_list(data, 2, &list);
+ assert_se(r < 0);
+
+ return 0;
+}
+
+static int test_option(sd_event *e) {
+ uint8_t packet[] = {
+ 'F', 'O', 'O',
+ 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',
+ };
+ uint8_t result[] = {
+ 'F', 'O', 'O',
+ 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',
+ };
+ uint16_t optcode;
+ size_t optlen;
+ uint8_t *optval, *buf, *out;
+ size_t zero = 0, pos = 3;
+ size_t buflen = sizeof(packet), outlen = sizeof(result);
+
+ log_debug("/* %s */", __func__);
+
+ assert_se(buflen == outlen);
+
+ assert_se(dhcp6_option_parse(&buf, &zero, &optcode, &optlen,
+ &optval) == -ENOMSG);
+
+ buflen -= 3;
+ buf = &packet[3];
+ outlen -= 3;
+ out = &result[3];
+
+ assert_se(dhcp6_option_parse(&buf, &buflen, &optcode, &optlen,
+ &optval) >= 0);
+ pos += 4 + optlen;
+ assert_se(buf == &packet[pos]);
+ assert_se(optcode == SD_DHCP6_OPTION_ORO);
+ assert_se(optlen == 7);
+ assert_se(buflen + pos == sizeof(packet));
+
+ assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen,
+ optval) >= 0);
+ assert_se(out == &result[pos]);
+ assert_se(*out == 0x00);
+
+ assert_se(dhcp6_option_parse(&buf, &buflen, &optcode, &optlen,
+ &optval) >= 0);
+ pos += 4 + optlen;
+ assert_se(buf == &packet[pos]);
+ assert_se(optcode == SD_DHCP6_OPTION_VENDOR_CLASS);
+ assert_se(optlen == 9);
+ assert_se(buflen + pos == sizeof(packet));
+
+ assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen,
+ optval) >= 0);
+ assert_se(out == &result[pos]);
+ assert_se(*out == 'B');
+
+ assert_se(memcmp(packet, result, sizeof(packet)) == 0);
+
+ return 0;
+}
+
+static int test_option_status(sd_event *e) {
+ 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,
+ /* 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,
+ /* 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,
+ /* 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,
+ /* 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,
+ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00,
+ };
+ DHCP6Option *option;
+ DHCP6IA ia, pd;
+ int r = 0;
+
+ log_debug("/* %s */", __func__);
+
+ zero(ia);
+ option = (DHCP6Option *)option1;
+ assert_se(sizeof(option1) == sizeof(DHCP6Option) + be16toh(option->len));
+
+ r = dhcp6_option_parse_ia(option, &ia, NULL);
+ assert_se(r == 0);
+ assert_se(ia.addresses == NULL);
+
+ option->len = htobe16(17);
+ r = dhcp6_option_parse_ia(option, &ia, NULL);
+ assert_se(r == -ENOBUFS);
+ assert_se(ia.addresses == NULL);
+
+ option->len = htobe16(sizeof(DHCP6Option));
+ r = dhcp6_option_parse_ia(option, &ia, NULL);
+ assert_se(r == -ENOBUFS);
+ assert_se(ia.addresses == NULL);
+
+ zero(ia);
+ option = (DHCP6Option *)option2;
+ assert_se(sizeof(option2) == sizeof(DHCP6Option) + be16toh(option->len));
+
+ r = dhcp6_option_parse_ia(option, &ia, NULL);
+ assert_se(r >= 0);
+ assert_se(ia.addresses == NULL);
+
+ zero(ia);
+ option = (DHCP6Option *)option3;
+ assert_se(sizeof(option3) == sizeof(DHCP6Option) + be16toh(option->len));
+
+ r = dhcp6_option_parse_ia(option, &ia, NULL);
+ assert_se(r >= 0);
+ assert_se(ia.addresses != NULL);
+ dhcp6_lease_free_ia(&ia);
+
+ zero(pd);
+ option = (DHCP6Option *)option4;
+ assert_se(sizeof(option4) == sizeof(DHCP6Option) + be16toh(option->len));
+
+ r = dhcp6_option_parse_ia(option, &pd, NULL);
+ assert_se(r >= 0);
+ assert_se(pd.addresses != NULL);
+ assert_se(memcmp(&pd.ia_pd.id, &option4[4], 4) == 0);
+ assert_se(memcmp(&pd.ia_pd.lifetime_t1, &option4[8], 4) == 0);
+ assert_se(memcmp(&pd.ia_pd.lifetime_t2, &option4[12], 4) == 0);
+ dhcp6_lease_free_ia(&pd);
+
+ zero(pd);
+ option = (DHCP6Option *)option5;
+ assert_se(sizeof(option5) == sizeof(DHCP6Option) + be16toh(option->len));
+
+ r = dhcp6_option_parse_ia(option, &pd, NULL);
+ assert_se(r >= 0);
+ assert_se(pd.addresses != NULL);
+ dhcp6_lease_free_ia(&pd);
+
+ return 0;
+}
+
+static uint8_t msg_advertise[198] = {
+ 0x02, 0x0f, 0xb4, 0xe5, 0x00, 0x01, 0x00, 0x0e,
+ 0x00, 0x01, 0x00, 0x01, 0x1a, 0x6b, 0xf3, 0x30,
+ 0x3c, 0x97, 0x0e, 0xcf, 0xa3, 0x7d, 0x00, 0x03,
+ 0x00, 0x5e, 0x0e, 0xcf, 0xa3, 0x7d, 0x00, 0x00,
+ 0x00, 0x50, 0x00, 0x00, 0x00, 0x78, 0x00, 0x05,
+ 0x00, 0x18, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad,
+ 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3, 0x09, 0x3c,
+ 0x55, 0xad, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00,
+ 0x00, 0xb4, 0x00, 0x0d, 0x00, 0x32, 0x00, 0x00,
+ 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,
+ 0x00, 0x17, 0x00, 0x10, 0x20, 0x01, 0x0d, 0xb8,
+ 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x0b,
+ 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74,
+ 0x72, 0x61, 0x00, 0x00, 0x1f, 0x00, 0x10, 0x20,
+ 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x02, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x19,
+ 0x40, 0x5c, 0x53, 0x78, 0x2b, 0xcb, 0xb3, 0x6d,
+ 0x53, 0x00, 0x07, 0x00, 0x01, 0x00
+};
+
+static uint8_t msg_reply[191] = {
+ 0x07, 0xf7, 0x4e, 0x57, 0x00, 0x02, 0x00, 0x0e,
+ 0x00, 0x01, 0x00, 0x01, 0x19, 0x40, 0x5c, 0x53,
+ 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, 0x00, 0x01,
+ 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1a, 0x6b,
+ 0xf3, 0x30, 0x3c, 0x97, 0x0e, 0xcf, 0xa3, 0x7d,
+ 0x00, 0x03, 0x00, 0x4a, 0x0e, 0xcf, 0xa3, 0x7d,
+ 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x78,
+ 0x00, 0x05, 0x00, 0x18, 0x20, 0x01, 0x0d, 0xb8,
+ 0xde, 0xad, 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3,
+ 0x09, 0x3c, 0x55, 0xad, 0x00, 0x00, 0x00, 0x96,
+ 0x00, 0x00, 0x00, 0xb4, 0x00, 0x0d, 0x00, 0x1e,
+ 0x00, 0x00, 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, 0x00, 0x17,
+ 0x00, 0x10, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad,
+ 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x18, 0x00, 0x0b, 0x03, 0x6c,
+ 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, 0x72, 0x61,
+ 0x00, 0x00, 0x1f, 0x00, 0x10, 0x20, 0x01, 0x0d,
+ 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x27, 0x00,
+ 0x0e, 0x01, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e,
+ 0x74, 0x05, 0x69, 0x6e, 0x74, 0x72, 0x61
+};
+
+static uint8_t fqdn_wire[16] = {
+ 0x04, 'h', 'o', 's', 't', 0x03, 'l', 'a', 'b',
+ 0x05, 'i', 'n', 't', 'r', 'a', 0x00
+};
+
+static int test_advertise_option(sd_event *e) {
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+ DHCP6Message *advertise = (DHCP6Message *)msg_advertise;
+ size_t len = sizeof(msg_advertise) - sizeof(DHCP6Message), pos = 0;
+ uint32_t lt_pref, lt_valid;
+ bool opt_clientid = false;
+ const struct in6_addr *addrs;
+ uint8_t preference = 255;
+ struct in6_addr addr;
+ char **domains;
+ uint8_t *opt;
+ int r;
+ be32_t val;
+
+ log_debug("/* %s */", __func__);
+
+ assert_se(len >= sizeof(DHCP6Message));
+
+ assert_se(dhcp6_lease_new(&lease) >= 0);
+
+ assert_se(advertise->type == DHCP6_ADVERTISE);
+ assert_se((be32toh(advertise->transaction_id) & 0x00ffffff) ==
+ 0x0fb4e5);
+
+ while (pos < len) {
+ DHCP6Option *option = (DHCP6Option *)&advertise->options[pos];
+ const uint16_t optcode = be16toh(option->code);
+ const uint16_t optlen = be16toh(option->len);
+ uint8_t *optval = option->data;
+
+ switch(optcode) {
+ case SD_DHCP6_OPTION_CLIENTID:
+ assert_se(optlen == 14);
+
+ opt_clientid = true;
+ break;
+
+ case SD_DHCP6_OPTION_IA_NA:
+ assert_se(optlen == 94);
+ assert_se(!memcmp(optval, &msg_advertise[26], optlen));
+
+ val = htobe32(0x0ecfa37d);
+ assert_se(!memcmp(optval, &val, sizeof(val)));
+
+ val = htobe32(80);
+ assert_se(!memcmp(optval + 4, &val, sizeof(val)));
+
+ val = htobe32(120);
+ assert_se(!memcmp(optval + 8, &val, sizeof(val)));
+
+ assert_se(dhcp6_option_parse_ia(option, &lease->ia, NULL) >= 0);
+
+ break;
+
+ case SD_DHCP6_OPTION_SERVERID:
+ assert_se(optlen == 14);
+ assert_se(!memcmp(optval, &msg_advertise[179], optlen));
+
+ assert_se(dhcp6_lease_set_serverid(lease, optval,
+ optlen) >= 0);
+ break;
+
+ case SD_DHCP6_OPTION_PREFERENCE:
+ assert_se(optlen == 1);
+ assert_se(!*optval);
+
+ assert_se(dhcp6_lease_set_preference(lease,
+ *optval) >= 0);
+ break;
+
+ case SD_DHCP6_OPTION_ELAPSED_TIME:
+ assert_se(optlen == 2);
+
+ break;
+
+ case SD_DHCP6_OPTION_DNS_SERVERS:
+ assert_se(optlen == 16);
+ assert_se(dhcp6_lease_set_dns(lease, optval,
+ optlen) >= 0);
+ break;
+
+ case SD_DHCP6_OPTION_DOMAIN_LIST:
+ assert_se(optlen == 11);
+ assert_se(dhcp6_lease_set_domains(lease, optval,
+ optlen) >= 0);
+ break;
+
+ case SD_DHCP6_OPTION_SNTP_SERVERS:
+ assert_se(optlen == 16);
+ assert_se(dhcp6_lease_set_sntp(lease, optval,
+ optlen) >= 0);
+ break;
+
+ default:
+ break;
+ }
+
+ pos += sizeof(*option) + optlen;
+ }
+
+ assert_se(pos == len);
+ assert_se(opt_clientid);
+
+ sd_dhcp6_lease_reset_address_iter(lease);
+ assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+ &lt_valid) >= 0);
+ assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr)));
+ assert_se(lt_pref == 150);
+ assert_se(lt_valid == 180);
+ assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+ &lt_valid) == -ENOMSG);
+
+ sd_dhcp6_lease_reset_address_iter(lease);
+ assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+ &lt_valid) >= 0);
+ assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr)));
+ assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+ &lt_valid) == -ENOMSG);
+ sd_dhcp6_lease_reset_address_iter(lease);
+ assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+ &lt_valid) >= 0);
+ assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr)));
+ assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+ &lt_valid) == -ENOMSG);
+
+ assert_se(dhcp6_lease_get_serverid(lease, &opt, &len) >= 0);
+ assert_se(len == 14);
+ assert_se(!memcmp(opt, &msg_advertise[179], len));
+
+ assert_se(dhcp6_lease_get_preference(lease, &preference) >= 0);
+ assert_se(preference == 0);
+
+ r = sd_dhcp6_lease_get_dns(lease, &addrs);
+ assert_se(r == 1);
+ assert_se(!memcmp(addrs, &msg_advertise[124], r * 16));
+
+ r = sd_dhcp6_lease_get_domains(lease, &domains);
+ assert_se(r == 1);
+ assert_se(!strcmp("lab.intra", domains[0]));
+ assert_se(domains[1] == NULL);
+
+ r = sd_dhcp6_lease_get_ntp_addrs(lease, &addrs);
+ assert_se(r == 1);
+ assert_se(!memcmp(addrs, &msg_advertise[159], r * 16));
+
+ return 0;
+}
+
+static int test_hangcheck(sd_event_source *s, uint64_t usec, void *userdata) {
+ assert_not_reached("Test case should have completed in 2 seconds");
+
+ return 0;
+}
+
+static void test_client_solicit_cb(sd_dhcp6_client *client, int event,
+ void *userdata) {
+ sd_event *e = userdata;
+ sd_dhcp6_lease *lease;
+ const struct in6_addr *addrs;
+ char **domains;
+
+ log_debug("/* %s */", __func__);
+
+ assert_se(e);
+ assert_se(event == SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE);
+
+ assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0);
+
+ assert_se(sd_dhcp6_lease_get_domains(lease, &domains) == 1);
+ assert_se(!strcmp("lab.intra", domains[0]));
+ assert_se(domains[1] == NULL);
+
+ assert_se(sd_dhcp6_lease_get_dns(lease, &addrs) == 1);
+ assert_se(!memcmp(addrs, &msg_advertise[124], 16));
+
+ assert_se(sd_dhcp6_lease_get_ntp_addrs(lease, &addrs) == 1);
+ assert_se(!memcmp(addrs, &msg_advertise[159], 16));
+
+ assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVERS) == -EBUSY);
+
+ sd_event_exit(e, 0);
+}
+
+static int test_client_send_reply(DHCP6Message *request) {
+ DHCP6Message reply;
+
+ reply.transaction_id = request->transaction_id;
+ reply.type = DHCP6_REPLY;
+
+ memcpy(msg_reply, &reply.transaction_id, 4);
+
+ memcpy(&msg_reply[26], test_duid, sizeof(test_duid));
+
+ memcpy(&msg_reply[44], &test_iaid, sizeof(test_iaid));
+
+ assert_se(write(test_dhcp_fd[1], msg_reply, sizeof(msg_reply))
+ == sizeof(msg_reply));
+
+ return 0;
+}
+
+static int test_client_verify_request(DHCP6Message *request, size_t len) {
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+ bool found_clientid = false, found_iana = false, found_serverid = false,
+ found_elapsed_time = false, found_fqdn = false;
+ uint32_t lt_pref, lt_valid;
+ struct in6_addr addr;
+ size_t pos = 0;
+ be32_t val;
+
+ log_debug("/* %s */", __func__);
+
+ assert_se(request->type == DHCP6_REQUEST);
+ assert_se(dhcp6_lease_new(&lease) >= 0);
+
+ len -= sizeof(DHCP6Message);
+
+ while (pos < len) {
+ DHCP6Option *option = (DHCP6Option *)&request->options[pos];
+ uint16_t optcode = be16toh(option->code);
+ uint16_t optlen = be16toh(option->len);
+ uint8_t *optval = option->data;
+
+ switch(optcode) {
+ case SD_DHCP6_OPTION_CLIENTID:
+ assert_se(!found_clientid);
+ found_clientid = true;
+
+ assert_se(!memcmp(optval, &test_duid,
+ sizeof(test_duid)));
+
+ break;
+
+ case SD_DHCP6_OPTION_IA_NA:
+ assert_se(!found_iana);
+ found_iana = true;
+
+ assert_se(optlen == 40);
+ assert_se(!memcmp(optval, &test_iaid, sizeof(test_iaid)));
+
+ val = htobe32(80);
+ assert_se(!memcmp(optval + 4, &val, sizeof(val)));
+
+ val = htobe32(120);
+ assert_se(!memcmp(optval + 8, &val, sizeof(val)));
+
+ assert_se(dhcp6_option_parse_ia(option, &lease->ia, NULL) >= 0);
+
+ break;
+
+ case SD_DHCP6_OPTION_SERVERID:
+ assert_se(!found_serverid);
+ found_serverid = true;
+
+ assert_se(optlen == 14);
+ assert_se(!memcmp(&msg_advertise[179], optval, optlen));
+
+ break;
+
+ case SD_DHCP6_OPTION_ELAPSED_TIME:
+ assert_se(!found_elapsed_time);
+ found_elapsed_time = true;
+
+ assert_se(optlen == 2);
+
+ break;
+ case SD_DHCP6_OPTION_FQDN:
+ assert_se(!found_fqdn);
+ found_fqdn = true;
+
+ assert_se(optlen == 17);
+
+ assert_se(optval[0] == 0x01);
+ assert_se(!memcmp(optval + 1, fqdn_wire, sizeof(fqdn_wire)));
+ break;
+ }
+
+ pos += sizeof(*option) + optlen;
+ }
+
+ assert_se(found_clientid && found_iana && found_serverid &&
+ found_elapsed_time);
+
+ sd_dhcp6_lease_reset_address_iter(lease);
+ assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+ &lt_valid) >= 0);
+ assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr)));
+ assert_se(lt_pref == 150);
+ assert_se(lt_valid == 180);
+
+ assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+ &lt_valid) == -ENOMSG);
+
+ return 0;
+}
+
+static int test_client_send_advertise(DHCP6Message *solicit) {
+ DHCP6Message advertise;
+
+ advertise.transaction_id = solicit->transaction_id;
+ advertise.type = DHCP6_ADVERTISE;
+
+ memcpy(msg_advertise, &advertise.transaction_id, 4);
+
+ memcpy(&msg_advertise[8], test_duid, sizeof(test_duid));
+
+ memcpy(&msg_advertise[26], &test_iaid, sizeof(test_iaid));
+
+ assert_se(write(test_dhcp_fd[1], msg_advertise, sizeof(msg_advertise))
+ == sizeof(msg_advertise));
+
+ return 0;
+}
+
+static int test_client_verify_solicit(DHCP6Message *solicit, size_t len) {
+ bool found_clientid = false, found_iana = false,
+ found_elapsed_time = false, found_fqdn = false;
+ size_t pos = 0;
+
+ log_debug("/* %s */", __func__);
+
+ assert_se(solicit->type == DHCP6_SOLICIT);
+
+ len -= sizeof(DHCP6Message);
+
+ while (pos < len) {
+ DHCP6Option *option = (DHCP6Option *)&solicit->options[pos];
+ uint16_t optcode = be16toh(option->code);
+ uint16_t optlen = be16toh(option->len);
+ uint8_t *optval = option->data;
+
+ switch(optcode) {
+ case SD_DHCP6_OPTION_CLIENTID:
+ assert_se(!found_clientid);
+ found_clientid = true;
+
+ assert_se(optlen == sizeof(test_duid));
+ memcpy(&test_duid, optval, sizeof(test_duid));
+
+ break;
+
+ case SD_DHCP6_OPTION_IA_NA:
+ assert_se(!found_iana);
+ found_iana = true;
+
+ assert_se(optlen == 12);
+
+ memcpy(&test_iaid, optval, sizeof(test_iaid));
+
+ break;
+
+ case SD_DHCP6_OPTION_ELAPSED_TIME:
+ assert_se(!found_elapsed_time);
+ found_elapsed_time = true;
+
+ assert_se(optlen == 2);
+
+ break;
+
+ case SD_DHCP6_OPTION_FQDN:
+ assert_se(!found_fqdn);
+ found_fqdn = true;
+
+ assert_se(optlen == 17);
+
+ assert_se(optval[0] == 0x01);
+ assert_se(!memcmp(optval + 1, fqdn_wire, sizeof(fqdn_wire)));
+
+ break;
+ }
+
+ pos += sizeof(*option) + optlen;
+ }
+
+ assert_se(pos == len);
+ assert_se(found_clientid && found_iana && found_elapsed_time);
+
+ return 0;
+}
+
+static void test_client_information_cb(sd_dhcp6_client *client, int event,
+ void *userdata) {
+ sd_event *e = userdata;
+ sd_dhcp6_lease *lease;
+ const struct in6_addr *addrs;
+ struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } };
+ char **domains;
+ const char *fqdn;
+
+ log_debug("/* %s */", __func__);
+
+ assert_se(e);
+ assert_se(event == SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST);
+
+ assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0);
+
+ assert_se(sd_dhcp6_lease_get_domains(lease, &domains) == 1);
+ assert_se(!strcmp("lab.intra", domains[0]));
+ assert_se(domains[1] == NULL);
+
+ assert_se(sd_dhcp6_lease_get_fqdn(lease, &fqdn) >= 0);
+ assert_se(streq(fqdn, "client.intra"));
+
+ assert_se(sd_dhcp6_lease_get_dns(lease, &addrs) == 1);
+ assert_se(!memcmp(addrs, &msg_advertise[124], 16));
+
+ assert_se(sd_dhcp6_lease_get_ntp_addrs(lease, &addrs) == 1);
+ assert_se(!memcmp(addrs, &msg_advertise[159], 16));
+
+ assert_se(sd_dhcp6_client_set_information_request(client, false) == -EBUSY);
+ assert_se(sd_dhcp6_client_set_callback(client, NULL, e) >= 0);
+ assert_se(sd_dhcp6_client_stop(client) >= 0);
+ assert_se(sd_dhcp6_client_set_information_request(client, false) >= 0);
+
+ assert_se(sd_dhcp6_client_set_callback(client,
+ test_client_solicit_cb, e) >= 0);
+
+ assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0);
+
+ assert_se(sd_dhcp6_client_start(client) >= 0);
+
+}
+
+static int test_client_verify_information_request(DHCP6Message *information_request,
+ size_t len) {
+
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+ size_t pos = 0;
+ bool found_clientid = false, found_elapsed_time = false;
+ struct in6_addr addr;
+ uint32_t lt_pref, lt_valid;
+
+ log_debug("/* %s */", __func__);
+
+ assert_se(information_request->type == DHCP6_INFORMATION_REQUEST);
+ assert_se(dhcp6_lease_new(&lease) >= 0);
+
+ len -= sizeof(DHCP6Message);
+
+ while (pos < len) {
+ DHCP6Option *option = (DHCP6Option *)&information_request->options[pos];
+ uint16_t optcode = be16toh(option->code);
+ uint16_t optlen = be16toh(option->len);
+ uint8_t *optval = option->data;
+
+ switch(optcode) {
+ case SD_DHCP6_OPTION_CLIENTID:
+ assert_se(!found_clientid);
+ found_clientid = true;
+
+ assert_se(optlen == sizeof(test_duid));
+ memcpy(&test_duid, optval, sizeof(test_duid));
+
+ break;
+
+ case SD_DHCP6_OPTION_IA_NA:
+ assert_not_reached("IA TA option must not be present");
+
+ break;
+
+ case SD_DHCP6_OPTION_SERVERID:
+ assert_not_reached("Server ID option must not be present");
+
+ break;
+
+ case SD_DHCP6_OPTION_ELAPSED_TIME:
+ assert_se(!found_elapsed_time);
+ found_elapsed_time = true;
+
+ assert_se(optlen == 2);
+
+ break;
+ }
+
+ pos += sizeof(*option) + optlen;
+ }
+
+ assert_se(pos == len);
+ assert_se(found_clientid && found_elapsed_time);
+
+ sd_dhcp6_lease_reset_address_iter(lease);
+
+ assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+ &lt_valid) == -ENOMSG);
+
+ return 0;
+}
+
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
+ const void *packet, size_t len) {
+ struct in6_addr mcast =
+ IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
+ DHCP6Message *message;
+
+ assert_se(s == test_dhcp_fd[0]);
+ assert_se(server_address);
+ assert_se(packet);
+ assert_se(len > sizeof(DHCP6Message) + 4);
+ assert_se(IN6_ARE_ADDR_EQUAL(server_address, &mcast));
+
+ message = (DHCP6Message *)packet;
+
+ assert_se(message->transaction_id & 0x00ffffff);
+
+ if (test_client_message_num == 0) {
+ test_client_verify_information_request(message, len);
+ test_client_send_reply(message);
+ test_client_message_num++;
+ } else if (test_client_message_num == 1) {
+ test_client_verify_solicit(message, len);
+ test_client_send_advertise(message);
+ test_client_message_num++;
+ } else if (test_client_message_num == 2) {
+ test_client_verify_request(message, len);
+ test_client_send_reply(message);
+ test_client_message_num++;
+ }
+
+ return len;
+}
+
+int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) {
+ assert_se(ifindex == test_ifindex);
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_dhcp_fd) < 0)
+ return -errno;
+
+ return test_dhcp_fd[0];
+}
+
+static int test_client_solicit(sd_event *e) {
+ sd_dhcp6_client *client;
+ struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } };
+ int val;
+
+ log_debug("/* %s */", __func__);
+
+ assert_se(sd_dhcp6_client_new(&client) >= 0);
+ assert_se(client);
+
+ 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_mac(client, (const uint8_t *) &mac_addr,
+ sizeof (mac_addr),
+ ARPHRD_ETHER) >= 0);
+ assert_se(sd_dhcp6_client_set_fqdn(client, "host.lab.intra") == 1);
+
+ assert_se(sd_dhcp6_client_get_information_request(client, &val) >= 0);
+ assert_se(val == 0);
+ assert_se(sd_dhcp6_client_set_information_request(client, 42) >= 0);
+ assert_se(sd_dhcp6_client_get_information_request(client, &val) >= 0);
+ assert_se(val);
+
+ assert_se(sd_dhcp6_client_set_callback(client,
+ test_client_information_cb, e) >= 0);
+
+ assert_se(sd_event_add_time_relative(e, &hangcheck, clock_boottime_or_monotonic(),
+ 2 * USEC_PER_SEC, 0,
+ test_hangcheck, NULL) >= 0);
+
+ assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0);
+
+ assert_se(sd_dhcp6_client_start(client) >= 0);
+
+ sd_event_loop(e);
+
+ hangcheck = sd_event_source_unref(hangcheck);
+
+ assert_se(!sd_dhcp6_client_unref(client));
+
+ test_dhcp_fd[1] = safe_close(test_dhcp_fd[1]);
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_(sd_event_unrefp) sd_event *e;
+
+ assert_se(sd_event_new(&e) >= 0);
+
+ test_setup_logging(LOG_DEBUG);
+
+ test_client_basic(e);
+ test_option(e);
+ test_option_status(e);
+ test_advertise_option(e);
+ test_client_solicit(e);
+ test_parse_domain(e);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/test-ipv4ll-manual.c b/src/libsystemd-network/test-ipv4ll-manual.c
new file mode 100644
index 0000000..a253acb
--- /dev/null
+++ b/src/libsystemd-network/test-ipv4ll-manual.c
@@ -0,0 +1,110 @@
+/* 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 "netlink-util.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "tests.h"
+#include "util.h"
+
+static void ll_handler(sd_ipv4ll *ll, int event, void *userdata) {
+ _cleanup_free_ char *address = NULL;
+ struct in_addr addr = {};
+
+ assert_se(ll);
+
+ if (sd_ipv4ll_get_address(ll, &addr) >= 0)
+ assert_se(in_addr_to_string(AF_INET, (const union in_addr_union*) &addr, &address) >= 0);
+
+ switch (event) {
+ case SD_IPV4LL_EVENT_BIND:
+ log_info("bound %s", strna(address));
+ break;
+ case SD_IPV4LL_EVENT_CONFLICT:
+ log_info("conflict on %s", strna(address));
+ break;
+ case SD_IPV4LL_EVENT_STOP:
+ log_error("the client was stopped with address %s", strna(address));
+ break;
+ default:
+ assert_not_reached("invalid LL event");
+ }
+}
+
+static int client_run(int ifindex, const char *seed_str, 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);
+ }
+
+ 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) {
+ _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, &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);
+ else if (argc == 3)
+ return test_ll(argv[1], argv[2]);
+ else {
+ log_error("This program takes one or two arguments.\n"
+ "\t %s <ifname> [<seed>]", 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..b213f41
--- /dev/null
+++ b/src/libsystemd-network/test-ipv4ll.c
@@ -0,0 +1,205 @@
+/* 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"
+#include "util.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;
+ }
+}
+
+static int arp_network_send_raw_socket(int fd, int ifindex,
+ const struct ether_arp *arp) {
+ assert_se(arp);
+ assert_se(ifindex > 0);
+ assert_se(fd >= 0);
+
+ if (send(fd, arp, sizeof(struct ether_arp), 0) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int arp_send_probe(int fd, int ifindex,
+ be32_t pa, const struct ether_addr *ha) {
+ struct ether_arp ea = {};
+
+ assert_se(fd >= 0);
+ assert_se(ifindex > 0);
+ assert_se(pa != 0);
+ assert_se(ha);
+
+ return arp_network_send_raw_socket(fd, ifindex, &ea);
+}
+
+int arp_send_announcement(int fd, int ifindex,
+ be32_t pa, const struct ether_addr *ha) {
+ struct ether_arp ea = {};
+
+ assert_se(fd >= 0);
+ assert_se(ifindex > 0);
+ assert_se(pa != 0);
+ assert_se(ha);
+
+ return arp_network_send_raw_socket(fd, ifindex, &ea);
+}
+
+int arp_network_bind_raw_socket(int ifindex, be32_t address, 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", __FUNCTION__);
+
+ 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_set_ifindex(ll, 99) == 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) {
+
+ 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", __FUNCTION__);
+
+ assert_se(sd_ipv4ll_new(&ll) == 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_t) -1);
+ assert_se(sd_ipv4ll_start(ll) == 0);
+
+ assert_se(sd_ipv4ll_is_running(ll));
+
+ /* PROBE */
+ sd_event_run(e, (uint64_t) -1);
+ assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp));
+
+ if (extended) {
+ /* PROBE */
+ sd_event_run(e, (uint64_t) -1);
+ assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp));
+
+ /* PROBE */
+ sd_event_run(e, (uint64_t) -1);
+ assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp));
+
+ sd_event_run(e, (uint64_t) -1);
+ assert_se(basic_request_handler_bind == 1);
+ }
+
+ 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[]) {
+ _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);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/test-lldp.c b/src/libsystemd-network/test-lldp.c
new file mode 100644
index 0000000..c52d422
--- /dev/null
+++ b/src/libsystemd-network/test-lldp.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.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] = { -1, -1 };
+static int lldp_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_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata) {
+ lldp_handler_calls++;
+}
+
+static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *cb_data) {
+ int r;
+
+ r = sd_lldp_new(lldp);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_set_ifindex(*lldp, 42);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_set_callback(*lldp, cb, cb_data);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_attach_event(*lldp, e, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_start(*lldp);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int stop_lldp(sd_lldp *lldp) {
+ int r;
+
+ r = sd_lldp_stop(lldp);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_detach_event(lldp);
+ if (r < 0)
+ return r;
+
+ sd_lldp_unref(lldp);
+ 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 *lldp;
+ sd_lldp_neighbor **neighbors;
+ uint8_t type;
+ const void *data;
+ uint16_t ttl;
+ size_t length;
+ const char *str;
+
+ lldp_handler_calls = 0;
+ assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
+
+ assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
+ sd_event_run(e, 0);
+ assert_se(lldp_handler_calls == 1);
+ assert_se(sd_lldp_get_neighbors(lldp, &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(lldp) == 0);
+}
+
+static void test_receive_incomplete_packet(sd_event *e) {
+ sd_lldp *lldp;
+ 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_handler_calls = 0;
+ assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
+
+ assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
+ sd_event_run(e, 0);
+ assert_se(lldp_handler_calls == 0);
+ assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 0);
+
+ assert_se(stop_lldp(lldp) == 0);
+}
+
+static void test_receive_oui_packet(sd_event *e) {
+ sd_lldp *lldp;
+ 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_handler_calls = 0;
+ assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
+
+ assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
+ sd_event_run(e, 0);
+ assert_se(lldp_handler_calls == 1);
+ assert_se(sd_lldp_get_neighbors(lldp, &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(lldp) == 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 *lldp;
+ sd_lldp_neighbor **neighbors;
+ int i;
+ uint8_t type;
+ const void *data;
+ size_t length, expected_length;
+ uint16_t ttl;
+
+ lldp_handler_calls = 0;
+ assert_se(start_lldp(&lldp, e, lldp_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_handler_calls == 6);
+
+ assert_se(sd_lldp_get_neighbors(lldp, &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(lldp) == 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..927e21b
--- /dev/null
+++ b/src/libsystemd-network/test-ndisc-ra.c
@@ -0,0 +1,370 @@
+/* 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.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, 0x27, 0x8d, 0x00,
+ 0x00, 0x09, 0x3a, 0x80, 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, 0x27, 0x8d, 0x00,
+ 0x00, 0x09, 0x3a, 0x80, 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 sd_event_source *test_hangcheck;
+static bool test_stopped;
+static int test_fd[2];
+static sd_event_source *recv_router_advertisement;
+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 };
+
+static int test_rs_hangcheck(sd_event_source *s, uint64_t usec,
+ void *userdata) {
+ assert_se(false);
+
+ return 0;
+}
+
+static void test_radv_prefix(void) {
+ sd_radv_prefix *p;
+
+ printf("* %s\n", __FUNCTION__);
+
+ 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, true) < 0);
+ assert_se(sd_radv_prefix_set_valid_lifetime(p, ~0) >= 0);
+ assert_se(sd_radv_prefix_set_valid_lifetime(p, 42) >= 0);
+ assert_se(sd_radv_prefix_set_valid_lifetime(p, 0) >= 0);
+
+ assert_se(sd_radv_prefix_set_preferred_lifetime(NULL, true) < 0);
+ assert_se(sd_radv_prefix_set_preferred_lifetime(p, ~0) >= 0);
+ assert_se(sd_radv_prefix_set_preferred_lifetime(p, 42) >= 0);
+ assert_se(sd_radv_prefix_set_preferred_lifetime(p, 0) >= 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);
+
+ p = sd_radv_prefix_unref(p);
+ assert_se(!p);
+}
+
+static void test_radv(void) {
+ sd_radv *ra;
+
+ printf("* %s\n", __FUNCTION__);
+
+ 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, ~0) >= 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, 42000) >= 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_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, &test_rdnss, 0) >= 0);
+ assert_se(sd_radv_set_rdnss(ra, 600, &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, NULL) >= 0);
+ assert_se(sd_radv_set_dnssl(ra, 0, (char **)test_dnssl) >= 0);
+ assert_se(sd_radv_set_dnssl(ra, 600, (char **)test_dnssl) >= 0);
+
+ ra = sd_radv_unref(ra);
+ assert_se(!ra);
+}
+
+int icmp6_bind_router_solicitation(int ifindex) {
+ return -ENOSYS;
+}
+
+int icmp6_bind_router_advertisement(int ifindex) {
+ assert_se(ifindex == 42);
+
+ return test_fd[1];
+}
+
+int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
+
+ return 0;
+}
+
+int icmp6_receive(int fd, void *iov_base, size_t iov_len,
+ struct in6_addr *dst, triple_timestamp *timestamp) {
+ assert_se(read (fd, iov_base, iov_len) == (ssize_t)iov_len);
+
+ if (timestamp)
+ triple_timestamp_get(timestamp);
+
+ return 0;
+}
+
+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 %u\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("%3zd: ", 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;
+}
+
+static void test_ra(void) {
+ sd_event *e;
+ sd_radv *ra;
+ unsigned i;
+
+ printf("* %s\n", __FUNCTION__);
+
+ 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) >= 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, &test_rdnss, 1) >= 0);
+ assert_se(sd_radv_set_dnssl(ra, 60, (char **)test_dnssl) >= 0);
+
+ for (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)
+ assert_se(sd_radv_prefix_set_valid_lifetime(p, prefix[i].valid) >= 0);
+ if (prefix[i].preferred)
+ assert_se(sd_radv_prefix_set_preferred_lifetime(p, prefix[i].preferred) >= 0);
+
+ assert_se((sd_radv_add_prefix(ra, p, false) >= 0) == prefix[i].successful);
+ assert_se(sd_radv_add_prefix(ra, p, false) < 0);
+
+ 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_add_time_relative(
+ e, &test_hangcheck, clock_boottime_or_monotonic(),
+ 2 *USEC_PER_SEC, 0,
+ test_rs_hangcheck, NULL) >= 0);
+
+ assert_se(sd_radv_start(ra) >= 0);
+
+ sd_event_loop(e);
+
+ test_hangcheck = sd_event_source_unref(test_hangcheck);
+
+ ra = sd_radv_unref(ra);
+ assert_se(!ra);
+
+ close(test_fd[0]);
+
+ sd_event_unref(e);
+}
+
+int main(int argc, char *argv[]) {
+
+ test_setup_logging(LOG_DEBUG);
+
+ test_radv_prefix();
+ test_radv();
+ test_ra();
+
+ printf("* done\n");
+ return 0;
+}
diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c
new file mode 100644
index 0000000..1b2bba8
--- /dev/null
+++ b/src/libsystemd-network/test-ndisc-rs.c
@@ -0,0 +1,418 @@
+/* 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 "hexdecoct.h"
+#include "icmp6-util.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_event_source *test_hangcheck;
+static int test_fd[2];
+static sd_ndisc *test_timeout_nd;
+
+typedef int (*send_ra_t)(uint8_t flags);
+static send_ra_t send_ra_function;
+
+static void router_dump(sd_ndisc_router *rt) {
+ struct in6_addr addr;
+ char buf[FORMAT_TIMESTAMP_MAX];
+ uint8_t hop_limit;
+ uint64_t t, flags;
+ uint32_t mtu;
+ uint16_t lifetime;
+ unsigned preference;
+ int r;
+
+ assert_se(rt);
+
+ log_info("--");
+ assert_se(sd_ndisc_router_get_address(rt, &addr) == -ENODATA);
+
+ assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
+ log_info("Timestamp: %s", format_timestamp(buf, sizeof(buf), 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);
+ log_info("Lifetime: %" PRIu16, lifetime);
+
+ 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: {
+ uint32_t lifetime_valid, lifetime_preferred;
+ unsigned prefix_len;
+ uint8_t pfl;
+ struct in6_addr a;
+ char buff[INET6_ADDRSTRLEN];
+
+ assert_se(sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid) >= 0);
+ log_info("Valid Lifetime: %" PRIu32, lifetime_valid);
+
+ assert_se(sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred) >= 0);
+ log_info("Preferred Lifetime: %" PRIu32, lifetime_preferred);
+
+ 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", inet_ntop(AF_INET6, &a, buff, sizeof(buff)));
+
+ break;
+ }
+
+ case SD_NDISC_OPTION_RDNSS: {
+ const struct in6_addr *a;
+ uint32_t lt;
+ int n, i;
+
+ n = sd_ndisc_router_rdnss_get_addresses(rt, &a);
+ assert_se(n > 0);
+
+ for (i = 0; i < n; i++) {
+ char buff[INET6_ADDRSTRLEN];
+ log_info("DNS: %s", inet_ntop(AF_INET6, a + i, buff, sizeof(buff)));
+ }
+
+ assert_se(sd_ndisc_router_rdnss_get_lifetime(rt, &lt) >= 0);
+ log_info("Lifetime: %" PRIu32, lt);
+ break;
+ }
+
+ case SD_NDISC_OPTION_DNSSL: {
+ _cleanup_strv_free_ char **l = NULL;
+ uint32_t lt;
+ 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, &lt) >= 0);
+ log_info("Lifetime: %" PRIu32, lt);
+ break;
+ }}
+
+ r = sd_ndisc_router_option_next(rt);
+ }
+}
+
+static int test_rs_hangcheck(sd_event_source *s, uint64_t usec,
+ void *userdata) {
+ assert_se(false);
+
+ return 0;
+}
+
+int icmp6_bind_router_solicitation(int ifindex) {
+ assert_se(ifindex == 42);
+
+ 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 -ENOSYS;
+}
+
+int icmp6_receive(int fd, void *iov_base, size_t iov_len,
+ struct in6_addr *dst, triple_timestamp *timestamp) {
+ assert_se(read (fd, iov_base, iov_len) == (ssize_t)iov_len);
+
+ if (timestamp)
+ triple_timestamp_get(timestamp);
+
+ return 0;
+}
+
+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;
+}
+
+int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
+ if (!send_ra_function)
+ return 0;
+
+ return send_ra_function(0);
+}
+
+static void test_callback(sd_ndisc *nd, sd_ndisc_event 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;
+ uint32_t mtu;
+
+ 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;
+ }
+
+ assert_se(sd_ndisc_get_mtu(nd, &mtu) == -ENODATA);
+
+ sd_event_exit(e, 0);
+}
+
+static void test_rs(void) {
+ sd_event *e;
+ sd_ndisc *nd;
+
+ if (verbose)
+ printf("* %s\n", __FUNCTION__);
+
+ 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, &test_hangcheck, clock_boottime_or_monotonic(),
+ 30 * USEC_PER_SEC, 0,
+ test_rs_hangcheck, NULL) >= 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);
+
+ assert_se(sd_ndisc_start(nd) >= 0);
+
+ sd_event_loop(e);
+
+ test_hangcheck = sd_event_source_unref(test_hangcheck);
+
+ nd = sd_ndisc_unref(nd);
+ assert_se(!nd);
+
+ close(test_fd[1]);
+
+ sd_event_unref(e);
+}
+
+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;
+ char time_string_min[FORMAT_TIMESPAN_MAX];
+ char time_string_nd[FORMAT_TIMESPAN_MAX];
+ char time_string_max[FORMAT_TIMESPAN_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;
+ }
+
+ format_timespan(time_string_min, FORMAT_TIMESPAN_MAX,
+ min, USEC_PER_MSEC);
+ format_timespan(time_string_nd, FORMAT_TIMESPAN_MAX,
+ nd->retransmit_time, USEC_PER_MSEC);
+ format_timespan(time_string_max, FORMAT_TIMESPAN_MAX,
+ max, USEC_PER_MSEC);
+
+ log_info("backoff timeout interval %2d %s%s <= %s <= %s",
+ count,
+ (last * 2 > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL)? "(max) ": "",
+ time_string_min, time_string_nd, time_string_max);
+
+ 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;
+}
+
+static void test_timeout(void) {
+ sd_event *e;
+ sd_ndisc *nd;
+
+ if (verbose)
+ printf("* %s\n", __FUNCTION__);
+
+ 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, &test_hangcheck, clock_boottime_or_monotonic(),
+ 30 * USEC_PER_SEC, 0,
+ test_rs_hangcheck, NULL) >= 0);
+
+ assert_se(sd_ndisc_start(nd) >= 0);
+
+ sd_event_loop(e);
+
+ test_hangcheck = sd_event_source_unref(test_hangcheck);
+
+ nd = sd_ndisc_unref(nd);
+
+ sd_event_unref(e);
+}
+
+int main(int argc, char *argv[]) {
+
+ test_setup_logging(LOG_DEBUG);
+
+ test_rs();
+ test_timeout();
+
+ return 0;
+}
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..9f13226
--- /dev/null
+++ b/src/libsystemd-network/test-sd-dhcp-lease.c
@@ -0,0 +1,93 @@
+/* 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"
+
+/* 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
+ */
+static void test_dhcp_lease_parse_search_domains_basic(void) {
+ 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"));
+}
+
+static void test_dhcp_lease_parse_search_domains_ptr(void) {
+ 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"));
+}
+
+static void test_dhcp_lease_parse_search_domains_labels_and_ptr(void) {
+ 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. */
+
+static void test_dhcp_lease_parse_search_domains_no_data(void) {
+ _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) == -ENODATA);
+ assert_se(dhcp_lease_parse_search_domains(optionbuf, 0, &domains) == -ENODATA);
+}
+
+static void test_dhcp_lease_parse_search_domains_loops(void) {
+ _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);
+}
+
+static void test_dhcp_lease_parse_search_domains_wrong_len(void) {
+ _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);
+}
+
+int main(int argc, char *argv[]) {
+ test_dhcp_lease_parse_search_domains_basic();
+ test_dhcp_lease_parse_search_domains_ptr();
+ test_dhcp_lease_parse_search_domains_labels_and_ptr();
+ test_dhcp_lease_parse_search_domains_no_data();
+ test_dhcp_lease_parse_search_domains_loops();
+ test_dhcp_lease_parse_search_domains_wrong_len();
+ return 0;
+}