summaryrefslogtreecommitdiffstats
path: root/src/libsystemd-network
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:42 +0000
commit78e9bb837c258ac0ec7712b3d612cc2f407e731e (patch)
treef515d16b6efd858a9aeb5b0ef5d6f90bf288283d /src/libsystemd-network
parentAdding debian version 255.5-1. (diff)
downloadsystemd-78e9bb837c258ac0ec7712b3d612cc2f407e731e.tar.xz
systemd-78e9bb837c258ac0ec7712b3d612cc2f407e731e.zip
Merging upstream version 256.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/libsystemd-network')
-rw-r--r--src/libsystemd-network/dhcp-client-id-internal.h60
-rw-r--r--src/libsystemd-network/dhcp-duid-internal.h (renamed from src/libsystemd-network/dhcp-identifier.h)60
-rw-r--r--src/libsystemd-network/dhcp-identifier.c209
-rw-r--r--src/libsystemd-network/dhcp-lease-internal.h6
-rw-r--r--src/libsystemd-network/dhcp-network.c9
-rw-r--r--src/libsystemd-network/dhcp-option.c4
-rw-r--r--src/libsystemd-network/dhcp-server-internal.h33
-rw-r--r--src/libsystemd-network/dhcp-server-lease-internal.h45
-rw-r--r--src/libsystemd-network/dhcp6-internal.h10
-rw-r--r--src/libsystemd-network/dhcp6-network.c18
-rw-r--r--src/libsystemd-network/dhcp6-protocol.h8
-rw-r--r--src/libsystemd-network/fuzz-dhcp-client.c20
-rw-r--r--src/libsystemd-network/fuzz-dhcp-server.c41
-rw-r--r--src/libsystemd-network/fuzz-dhcp6-client.c4
-rw-r--r--src/libsystemd-network/fuzz-lldp-rx.c9
-rw-r--r--src/libsystemd-network/fuzz-ndisc-rs.c74
-rw-r--r--src/libsystemd-network/icmp6-packet.c131
-rw-r--r--src/libsystemd-network/icmp6-packet.h34
-rw-r--r--src/libsystemd-network/icmp6-test-util.c (renamed from src/libsystemd-network/icmp6-util-unix.c)21
-rw-r--r--src/libsystemd-network/icmp6-test-util.h (renamed from src/libsystemd-network/icmp6-util-unix.h)3
-rw-r--r--src/libsystemd-network/icmp6-util.c129
-rw-r--r--src/libsystemd-network/icmp6-util.h24
-rw-r--r--src/libsystemd-network/lldp-neighbor.c69
-rw-r--r--src/libsystemd-network/lldp-neighbor.h2
-rw-r--r--src/libsystemd-network/lldp-rx-internal.h3
-rw-r--r--src/libsystemd-network/meson.build22
-rw-r--r--src/libsystemd-network/ndisc-internal.h1
-rw-r--r--src/libsystemd-network/ndisc-neighbor-internal.h21
-rw-r--r--src/libsystemd-network/ndisc-option.c1502
-rw-r--r--src/libsystemd-network/ndisc-option.h330
-rw-r--r--src/libsystemd-network/ndisc-protocol.c34
-rw-r--r--src/libsystemd-network/ndisc-protocol.h31
-rw-r--r--src/libsystemd-network/ndisc-redirect-internal.h21
-rw-r--r--src/libsystemd-network/ndisc-router-internal.h37
-rw-r--r--src/libsystemd-network/ndisc-router-solicit-internal.h18
-rw-r--r--src/libsystemd-network/ndisc-router.c913
-rw-r--r--src/libsystemd-network/ndisc-router.h49
-rw-r--r--src/libsystemd-network/radv-internal.h16
-rw-r--r--src/libsystemd-network/sd-dhcp-client-id.c196
-rw-r--r--src/libsystemd-network/sd-dhcp-client.c248
-rw-r--r--src/libsystemd-network/sd-dhcp-duid.c288
-rw-r--r--src/libsystemd-network/sd-dhcp-lease.c50
-rw-r--r--src/libsystemd-network/sd-dhcp-server-lease.c513
-rw-r--r--src/libsystemd-network/sd-dhcp-server.c349
-rw-r--r--src/libsystemd-network/sd-dhcp6-client.c80
-rw-r--r--src/libsystemd-network/sd-dhcp6-lease.c3
-rw-r--r--src/libsystemd-network/sd-ipv4acd.c9
-rw-r--r--src/libsystemd-network/sd-ipv4ll.c3
-rw-r--r--src/libsystemd-network/sd-lldp-rx.c25
-rw-r--r--src/libsystemd-network/sd-ndisc-neighbor.c126
-rw-r--r--src/libsystemd-network/sd-ndisc-redirect.c128
-rw-r--r--src/libsystemd-network/sd-ndisc-router-solicit.c83
-rw-r--r--src/libsystemd-network/sd-ndisc-router.c344
-rw-r--r--src/libsystemd-network/sd-ndisc.c329
-rw-r--r--src/libsystemd-network/sd-radv.c463
-rw-r--r--src/libsystemd-network/test-acd.c5
-rw-r--r--src/libsystemd-network/test-dhcp-client.c25
-rw-r--r--src/libsystemd-network/test-dhcp-server.c46
-rw-r--r--src/libsystemd-network/test-dhcp6-client.c12
-rw-r--r--src/libsystemd-network/test-ipv4ll-manual.c5
-rw-r--r--src/libsystemd-network/test-ipv4ll.c35
-rw-r--r--src/libsystemd-network/test-ndisc-ra.c213
-rw-r--r--src/libsystemd-network/test-ndisc-rs.c272
-rw-r--r--src/libsystemd-network/test-ndisc-send.c449
64 files changed, 5738 insertions, 2582 deletions
diff --git a/src/libsystemd-network/dhcp-client-id-internal.h b/src/libsystemd-network/dhcp-client-id-internal.h
new file mode 100644
index 0000000..655f17b
--- /dev/null
+++ b/src/libsystemd-network/dhcp-client-id-internal.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-dhcp-client-id.h"
+
+#include "dhcp-duid-internal.h"
+#include "json.h"
+#include "macro.h"
+#include "siphash24.h"
+#include "sparse-endian.h"
+
+/* RFC 2132 section 9.14: its minimum length is 2.
+ * Note, its maximum is not mentioend in the RFC. Hence, 255. */
+#define MIN_CLIENT_ID_LEN 2
+#define MAX_CLIENT_ID_LEN 255
+#define MIN_CLIENT_ID_DATA_LEN (MIN_CLIENT_ID_LEN - sizeof(uint8_t))
+#define MAX_CLIENT_ID_DATA_LEN (MAX_CLIENT_ID_LEN - sizeof(uint8_t))
+
+typedef struct sd_dhcp_client_id {
+ size_t size;
+ union {
+ struct {
+ uint8_t type;
+ union {
+ struct {
+ /* 0: Generic (non-LL) (RFC 2132) */
+ uint8_t data[MAX_CLIENT_ID_DATA_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;
+ uint8_t data[MAX_CLIENT_ID_DATA_LEN];
+ };
+ } _packed_ id;
+ uint8_t raw[MAX_CLIENT_ID_LEN];
+ };
+} sd_dhcp_client_id;
+
+static inline bool client_id_size_is_valid(size_t size) {
+ return size >= MIN_CLIENT_ID_LEN && size <= MAX_CLIENT_ID_LEN;
+}
+
+static inline bool client_id_data_size_is_valid(size_t size) {
+ return size >= MIN_CLIENT_ID_DATA_LEN && size <= MAX_CLIENT_ID_DATA_LEN;
+}
+
+void client_id_hash_func(const sd_dhcp_client_id *client_id, struct siphash *state);
+int client_id_compare_func(const sd_dhcp_client_id *a, const sd_dhcp_client_id *b);
+
+int json_dispatch_client_id(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-duid-internal.h
index 96db588..f8bc15c 100644
--- a/src/libsystemd-network/dhcp-identifier.h
+++ b/src/libsystemd-network/dhcp-duid-internal.h
@@ -2,31 +2,31 @@
#pragma once
#include "sd-device.h"
+#include "sd-dhcp-duid.h"
#include "sd-id128.h"
#include "ether-addr-util.h"
#include "macro.h"
#include "sparse-endian.h"
-#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_LLT = SD_DUID_TYPE_LLT,
+ DUID_TYPE_EN = SD_DUID_TYPE_EN,
+ DUID_TYPE_LL = SD_DUID_TYPE_LL,
+ DUID_TYPE_UUID = SD_DUID_TYPE_UUID,
_DUID_TYPE_MAX,
- _DUID_TYPE_INVALID = -EINVAL,
- _DUID_TYPE_FORCE_U16 = UINT16_MAX,
+ _DUID_TYPE_INVALID = -EINVAL,
} DUIDType;
/* RFC 8415 section 11.1:
* A DUID consists of a 2-octet type code represented in network byte order, followed by a variable number of
* octets that make up the actual identifier. The length of the DUID (not including the type code) is at
* least 1 octet and at most 128 octets. */
+#define MIN_DUID_DATA_LEN 1
#define MAX_DUID_DATA_LEN 128
+#define MIN_DUID_LEN (sizeof(be16_t) + MIN_DUID_DATA_LEN)
#define MAX_DUID_LEN (sizeof(be16_t) + MAX_DUID_DATA_LEN)
/* https://tools.ietf.org/html/rfc3315#section-9.1 */
@@ -53,35 +53,31 @@ struct duid {
/* DUID_TYPE_UUID */
sd_id128_t uuid;
} _packed_ uuid;
- struct {
- uint8_t data[MAX_DUID_DATA_LEN];
- } _packed_ raw;
+ uint8_t data[MAX_DUID_DATA_LEN];
};
} _packed_;
-int dhcp_identifier_set_duid_llt(
- const struct hw_addr_data *hw_addr,
- uint16_t arp_type,
- usec_t t,
- struct duid *ret_duid,
- size_t *ret_len);
-int dhcp_identifier_set_duid_ll(
- const struct hw_addr_data *hw_addr,
- uint16_t arp_type,
- struct duid *ret_duid,
- size_t *ret_len);
-int dhcp_identifier_set_duid_en(struct duid *ret_duid, size_t *ret_len);
-int dhcp_identifier_set_duid_uuid(struct duid *ret_duid, size_t *ret_len);
-int dhcp_identifier_set_duid_raw(
- DUIDType duid_type,
- const uint8_t *buf,
- size_t buf_len,
- struct duid *ret_duid,
- size_t *ret_len);
+typedef struct sd_dhcp_duid {
+ size_t size;
+ union {
+ struct duid duid;
+ uint8_t raw[MAX_DUID_LEN];
+ };
+} sd_dhcp_duid;
+
+static inline bool duid_size_is_valid(size_t size) {
+ return size >= MIN_DUID_LEN && size <= MAX_DUID_LEN;
+}
+
+static inline bool duid_data_size_is_valid(size_t size) {
+ return size >= MIN_DUID_DATA_LEN && size <= MAX_DUID_DATA_LEN;
+}
+
+const char *duid_type_to_string(DUIDType t) _const_;
+int dhcp_duid_to_string_internal(uint16_t type, const void *data, size_t data_size, char **ret);
+
int dhcp_identifier_set_iaid(
sd_device *dev,
const struct hw_addr_data *hw_addr,
bool legacy_unstable_byteorder,
void *ret);
-
-const char *duid_type_to_string(DUIDType t) _const_;
diff --git a/src/libsystemd-network/dhcp-identifier.c b/src/libsystemd-network/dhcp-identifier.c
deleted file mode 100644
index f65cdbe..0000000
--- a/src/libsystemd-network/dhcp-identifier.c
+++ /dev/null
@@ -1,209 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include <linux/if_infiniband.h>
-#include <net/ethernet.h>
-#include <net/if_arp.h>
-
-#include "dhcp-identifier.h"
-#include "netif-util.h"
-#include "network-common.h"
-#include "siphash24.h"
-#include "sparse-endian.h"
-#include "string-table.h"
-
-#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
-#define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03)
-#define USEC_2000 ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */
-
-static const char * const duid_type_table[_DUID_TYPE_MAX] = {
- [DUID_TYPE_LLT] = "DUID-LLT",
- [DUID_TYPE_EN] = "DUID-EN/Vendor",
- [DUID_TYPE_LL] = "DUID-LL",
- [DUID_TYPE_UUID] = "UUID",
-};
-
-DEFINE_STRING_TABLE_LOOKUP_TO_STRING(duid_type, DUIDType);
-
-int dhcp_identifier_set_duid_llt(
- const struct hw_addr_data *hw_addr,
- uint16_t arp_type,
- usec_t t,
- struct duid *ret_duid,
- size_t *ret_len) {
-
- uint16_t time_from_2000y;
-
- assert(hw_addr);
- assert(ret_duid);
- assert(ret_len);
-
- if (hw_addr->length == 0)
- return -EOPNOTSUPP;
-
- if (arp_type == ARPHRD_ETHER)
- assert_return(hw_addr->length == ETH_ALEN, -EINVAL);
- else if (arp_type == ARPHRD_INFINIBAND)
- assert_return(hw_addr->length == INFINIBAND_ALEN, -EINVAL);
- else
- return -EOPNOTSUPP;
-
- if (t < USEC_2000)
- time_from_2000y = 0;
- else
- time_from_2000y = (uint16_t) (((t - USEC_2000) / USEC_PER_SEC) & 0xffffffff);
-
- unaligned_write_be16(&ret_duid->type, DUID_TYPE_LLT);
- unaligned_write_be16(&ret_duid->llt.htype, arp_type);
- unaligned_write_be32(&ret_duid->llt.time, time_from_2000y);
- memcpy(ret_duid->llt.haddr, hw_addr->bytes, hw_addr->length);
-
- *ret_len = offsetof(struct duid, llt.haddr) + hw_addr->length;
-
- return 0;
-}
-
-int dhcp_identifier_set_duid_ll(
- const struct hw_addr_data *hw_addr,
- uint16_t arp_type,
- struct duid *ret_duid,
- size_t *ret_len) {
-
- assert(hw_addr);
- assert(ret_duid);
- assert(ret_len);
-
- if (hw_addr->length == 0)
- return -EOPNOTSUPP;
-
- if (arp_type == ARPHRD_ETHER)
- assert_return(hw_addr->length == ETH_ALEN, -EINVAL);
- else if (arp_type == ARPHRD_INFINIBAND)
- assert_return(hw_addr->length == INFINIBAND_ALEN, -EINVAL);
- else
- return -EOPNOTSUPP;
-
- unaligned_write_be16(&ret_duid->type, DUID_TYPE_LL);
- unaligned_write_be16(&ret_duid->ll.htype, arp_type);
- memcpy(ret_duid->ll.haddr, hw_addr->bytes, hw_addr->length);
-
- *ret_len = offsetof(struct duid, ll.haddr) + hw_addr->length;
-
- return 0;
-}
-
-int dhcp_identifier_set_duid_en(struct duid *ret_duid, size_t *ret_len) {
- sd_id128_t machine_id;
- bool test_mode;
- uint64_t hash;
- int r;
-
- assert(ret_duid);
- assert(ret_len);
-
- test_mode = network_test_mode_enabled();
-
- if (!test_mode) {
- r = sd_id128_get_machine(&machine_id);
- if (r < 0)
- return r;
- } else
- /* For tests, especially for fuzzers, reproducibility is important.
- * Hence, use a static and constant machine ID.
- * See 9216fddc5a8ac2742e6cfa7660f95c20ca4f2193. */
- machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10);
-
- unaligned_write_be16(&ret_duid->type, DUID_TYPE_EN);
- unaligned_write_be32(&ret_duid->en.pen, SYSTEMD_PEN);
-
- /* a bit of snake-oil perhaps, but no need to expose the machine-id
- * directly; duid->en.id might not be aligned, so we need to copy */
- hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes));
- memcpy(ret_duid->en.id, &hash, sizeof(hash));
-
- *ret_len = offsetof(struct duid, en.id) + sizeof(hash);
-
- if (test_mode)
- assert_se(memcmp(ret_duid, (const uint8_t[]) { 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2 }, *ret_len) == 0);
-
- return 0;
-}
-
-int dhcp_identifier_set_duid_uuid(struct duid *ret_duid, size_t *ret_len) {
- sd_id128_t machine_id;
- int r;
-
- assert(ret_duid);
- assert(ret_len);
-
- r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id);
- if (r < 0)
- return r;
-
- unaligned_write_be16(&ret_duid->type, DUID_TYPE_UUID);
- memcpy(&ret_duid->uuid.uuid, &machine_id, sizeof(machine_id));
-
- *ret_len = offsetof(struct duid, uuid.uuid) + sizeof(machine_id);
-
- return 0;
-}
-
-int dhcp_identifier_set_duid_raw(
- DUIDType duid_type,
- const uint8_t *buf,
- size_t buf_len,
- struct duid *ret_duid,
- size_t *ret_len) {
-
- assert(buf || buf_len == 0);
- assert(ret_duid);
- assert(ret_len);
-
- if (duid_type < 0 || duid_type > UINT16_MAX)
- return -EINVAL;
-
- if (buf_len > MAX_DUID_DATA_LEN)
- return -EINVAL;
-
- unaligned_write_be16(&ret_duid->type, duid_type);
- memcpy_safe(ret_duid->raw.data, buf, buf_len);
-
- *ret_len = offsetof(struct duid, raw.data) + buf_len;
- return 0;
-}
-
-int dhcp_identifier_set_iaid(
- sd_device *dev,
- const struct hw_addr_data *hw_addr,
- bool legacy_unstable_byteorder,
- void *ret) {
-
- const char *name = NULL;
- uint32_t id32;
- uint64_t id;
-
- assert(hw_addr);
- assert(ret);
-
- if (dev)
- name = net_get_persistent_name(dev);
- if (name)
- id = siphash24(name, strlen(name), HASH_KEY.bytes);
- else
- /* fall back to MAC address if no predictable name available */
- id = siphash24(hw_addr->bytes, hw_addr->length, HASH_KEY.bytes);
-
- id32 = (id & 0xffffffff) ^ (id >> 32);
-
- if (legacy_unstable_byteorder)
- /* for historical reasons (a bug), the bits were swapped and thus
- * the result was endianness dependent. Preserve that behavior. */
- id32 = bswap_32(id32);
- else
- /* the fixed behavior returns a stable byte order. Since LE is expected
- * to be more common, swap the bytes on LE to give the same as legacy
- * behavior. */
- id32 = be32toh(id32);
-
- unaligned_write_ne32(ret, id32);
- return 0;
-}
diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h
index a3d8bb4..b7bc142 100644
--- a/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/libsystemd-network/dhcp-lease-internal.h
@@ -8,6 +8,7 @@
#include "sd-dhcp-client.h"
#include "alloc-util.h"
+#include "dhcp-client-id-internal.h"
#include "dhcp-option.h"
#include "list.h"
#include "time-util.h"
@@ -67,8 +68,7 @@ struct sd_dhcp_lease {
char *root_path;
char *captive_portal;
- void *client_id;
- size_t client_id_len;
+ sd_dhcp_client_id client_id;
void *vendor_specific;
size_t vendor_specific_len;
@@ -92,7 +92,7 @@ int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const vo
void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *timestamp);
int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease);
-int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len);
+int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id);
#define dhcp_lease_unref_and_replace(a, b) \
unref_and_replace_full(a, b, sd_dhcp_lease_ref, sd_dhcp_lease_unref)
diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c
index 1f4ad09..2e73983 100644
--- a/src/libsystemd-network/dhcp-network.c
+++ b/src/libsystemd-network/dhcp-network.c
@@ -3,15 +3,16 @@
Copyright © 2013 Intel Corporation. All rights reserved.
***/
+/* Make sure the net/if.h header is included before any linux/ one */
+#include <net/if.h>
#include <errno.h>
+#include <linux/filter.h>
+#include <linux/if_infiniband.h>
+#include <linux/if_packet.h>
#include <net/ethernet.h>
-#include <net/if.h>
#include <net/if_arp.h>
#include <stdio.h>
#include <string.h>
-#include <linux/filter.h>
-#include <linux/if_infiniband.h>
-#include <linux/if_packet.h>
#include "dhcp-network.h"
#include "dhcp-protocol.h"
diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c
index 5679091..e4ba77a 100644
--- a/src/libsystemd-network/dhcp-option.c
+++ b/src/libsystemd-network/dhcp-option.c
@@ -285,7 +285,7 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo
int r;
while (offset < buflen) {
- code = options[offset ++];
+ code = options[offset++];
switch (code) {
case SD_DHCP_OPTION_PAD:
@@ -298,7 +298,7 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo
if (buflen < offset + 1)
return -ENOBUFS;
- len = options[offset ++];
+ len = options[offset++];
if (buflen < offset + len)
return -EINVAL;
diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index da9e56b..0b2fa96 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -8,6 +8,7 @@
#include "sd-dhcp-server.h"
#include "sd-event.h"
+#include "dhcp-client-id-internal.h"
#include "dhcp-option.h"
#include "network-common.h"
#include "ordered-set.h"
@@ -24,25 +25,6 @@ typedef enum DHCPRawOption {
_DHCP_RAW_OPTION_DATA_INVALID,
} DHCPRawOption;
-typedef struct DHCPClientId {
- size_t length;
- uint8_t *data;
-} DHCPClientId;
-
-typedef struct DHCPLease {
- sd_dhcp_server *server;
-
- DHCPClientId client_id;
-
- uint8_t htype; /* e.g. ARPHRD_ETHER */
- uint8_t hlen; /* e.g. ETH_ALEN */
- be32_t address;
- be32_t gateway;
- uint8_t chaddr[16];
- usec_t expiration;
- char *hostname;
-} DHCPLease;
-
struct sd_dhcp_server {
unsigned n_ref;
@@ -93,6 +75,9 @@ struct sd_dhcp_server {
char *agent_circuit_id;
char *agent_remote_id;
+
+ int lease_dir_fd;
+ char *lease_file;
};
typedef struct DHCPRequest {
@@ -100,7 +85,7 @@ typedef struct DHCPRequest {
DHCPMessage *message;
/* options */
- DHCPClientId client_id;
+ sd_dhcp_client_id client_id;
size_t max_optlen;
be32_t server_id;
be32_t requested_ip;
@@ -113,20 +98,12 @@ typedef struct DHCPRequest {
triple_timestamp timestamp;
} DHCPRequest;
-extern const struct hash_ops dhcp_lease_hash_ops;
-
int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
size_t length, const triple_timestamp *timestamp);
int dhcp_server_send_packet(sd_dhcp_server *server,
DHCPRequest *req, DHCPPacket *packet,
int type, size_t optoffset);
-void client_id_hash_func(const DHCPClientId *p, struct siphash *state);
-int client_id_compare_func(const DHCPClientId *a, const DHCPClientId *b);
-
-DHCPLease *dhcp_lease_free(DHCPLease *lease);
-DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPLease*, dhcp_lease_free);
-
#define log_dhcp_server_errno(server, error, fmt, ...) \
log_interface_prefix_full_errno( \
"DHCPv4 server: ", \
diff --git a/src/libsystemd-network/dhcp-server-lease-internal.h b/src/libsystemd-network/dhcp-server-lease-internal.h
new file mode 100644
index 0000000..7626552
--- /dev/null
+++ b/src/libsystemd-network/dhcp-server-lease-internal.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-dhcp-server-lease.h"
+
+#include "dhcp-client-id-internal.h"
+#include "dhcp-server-internal.h"
+#include "json.h"
+#include "time-util.h"
+
+typedef struct sd_dhcp_server_lease {
+ unsigned n_ref;
+
+ sd_dhcp_server *server;
+
+ sd_dhcp_client_id client_id;
+
+ uint8_t htype; /* e.g. ARPHRD_ETHER */
+ uint8_t hlen; /* e.g. ETH_ALEN */
+ be32_t address;
+ be32_t gateway;
+ uint8_t chaddr[16];
+ usec_t expiration;
+ char *hostname;
+} sd_dhcp_server_lease;
+
+extern const struct hash_ops dhcp_server_lease_hash_ops;
+
+int dhcp_server_put_lease(sd_dhcp_server *server, sd_dhcp_server_lease *lease, bool is_static);
+
+int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *req, usec_t expiration);
+int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server);
+
+sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req);
+
+int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, JsonVariant **v);
+int dhcp_server_static_leases_append_json(sd_dhcp_server *server, JsonVariant **v);
+
+int dhcp_server_save_leases(sd_dhcp_server *server);
+int dhcp_server_load_leases(sd_dhcp_server *server);
+int dhcp_server_leases_file_get_server_address(
+ int dir_fd,
+ const char *path,
+ struct in_addr *ret_address,
+ uint8_t *ret_prefixlen);
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index e5b3b13..ecd62ea 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -11,7 +11,7 @@
#include "sd-event.h"
#include "sd-dhcp6-client.h"
-#include "dhcp-identifier.h"
+#include "dhcp-duid-internal.h"
#include "dhcp6-client-internal.h"
#include "dhcp6-option.h"
#include "dhcp6-protocol.h"
@@ -64,8 +64,7 @@ struct sd_dhcp6_client {
DHCP6IA ia_na;
DHCP6IA ia_pd;
DHCP6RequestIA request_ia;
- struct duid duid;
- size_t duid_len;
+ sd_dhcp_duid duid;
be16_t *req_opts;
size_t n_req_opts;
char *fqdn;
@@ -85,9 +84,8 @@ struct sd_dhcp6_client {
bool send_release;
};
-int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address);
-int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
- const void *packet, size_t len);
+int dhcp6_network_bind_udp_socket(int ifindex, const struct in6_addr *address);
+int dhcp6_network_send_udp_socket(int s, const struct in6_addr *address, const void *packet, size_t len);
int dhcp6_client_send_message(sd_dhcp6_client *client);
int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id);
diff --git a/src/libsystemd-network/dhcp6-network.c b/src/libsystemd-network/dhcp6-network.c
index a3e4e19..0aa8469 100644
--- a/src/libsystemd-network/dhcp6-network.c
+++ b/src/libsystemd-network/dhcp6-network.c
@@ -17,9 +17,10 @@
#include "fd-util.h"
#include "socket-util.h"
-int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) {
+int dhcp6_network_bind_udp_socket(int ifindex, const struct in6_addr *local_address) {
union sockaddr_union src = {
.in6.sin6_family = AF_INET6,
+ .in6.sin6_addr = *ASSERT_PTR(local_address),
.in6.sin6_port = htobe16(DHCP6_PORT_CLIENT),
.in6.sin6_scope_id = ifindex,
};
@@ -27,9 +28,6 @@ int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) {
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)
@@ -58,20 +56,14 @@ int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) {
return TAKE_FD(s);
}
-int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
- const void *packet, size_t len) {
+int dhcp6_network_send_udp_socket(int s, const struct in6_addr *server_address, const void *packet, size_t len) {
union sockaddr_union dest = {
.in6.sin6_family = AF_INET6,
+ .in6.sin6_addr = *ASSERT_PTR(server_address),
.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)
+ if (sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6)) < 0)
return -errno;
return 0;
diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h
index c70f932..ab75bad 100644
--- a/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/libsystemd-network/dhcp6-protocol.h
@@ -28,9 +28,11 @@ 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 } } }
+#define IN6_ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS \
+ ((const struct in6_addr) { { { \
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, \
+ } } } )
enum {
DHCP6_PORT_SERVER = 547,
diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c
index 384972f..72787c4 100644
--- a/src/libsystemd-network/fuzz-dhcp-client.c
+++ b/src/libsystemd-network/fuzz-dhcp-client.c
@@ -7,8 +7,12 @@
#include "sd-dhcp-client.c"
#include "alloc-util.h"
+#include "dhcp-lease-internal.h"
#include "dhcp-network.h"
+#include "fs-util.h"
#include "fuzz.h"
+#include "network-internal.h"
+#include "tmpfile-util.h"
int dhcp_network_bind_raw_socket(
int ifindex,
@@ -52,6 +56,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
uint8_t bcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
_cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL;
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+ _cleanup_(unlink_tempfilep) char lease_file[] = "/tmp/fuzz-dhcp-client.XXXXXX";
+ _cleanup_close_ int fd = -1;
int res, r;
assert_se(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", 1) >= 0);
@@ -75,8 +82,19 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
client->xid = 2;
client->state = DHCP_STATE_SELECTING;
- (void) client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size, NULL);
+ if (client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size, NULL) < 0)
+ goto end;
+ fd = mkostemp_safe(lease_file);
+ assert_se(fd >= 0);
+
+ r = dhcp_lease_save(client->lease, lease_file);
+ assert_se(r >= 0);
+
+ r = dhcp_lease_load(&lease, lease_file);
+ assert_se(r >= 0);
+
+end:
assert_se(sd_dhcp_client_stop(client) >= 0);
return 0;
diff --git a/src/libsystemd-network/fuzz-dhcp-server.c b/src/libsystemd-network/fuzz-dhcp-server.c
index fddb3a5..c8b0378 100644
--- a/src/libsystemd-network/fuzz-dhcp-server.c
+++ b/src/libsystemd-network/fuzz-dhcp-server.c
@@ -7,6 +7,9 @@
#include "sd-dhcp-server.c"
#include "fuzz.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "tmpfile-util.h"
/* stub out network so that the server doesn't send */
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) {
@@ -18,40 +21,31 @@ ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) {
}
static int add_lease(sd_dhcp_server *server, const struct in_addr *server_address, uint8_t i) {
- _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL;
+ _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL;
int r;
assert(server);
- lease = new(DHCPLease, 1);
+ lease = new(sd_dhcp_server_lease, 1);
if (!lease)
return -ENOMEM;
- *lease = (DHCPLease) {
+ *lease = (sd_dhcp_server_lease) {
+ .n_ref = 1,
.address = htobe32(UINT32_C(10) << 24 | i),
.chaddr = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
- .expiration = UINT64_MAX,
+ .expiration = usec_add(now(CLOCK_BOOTTIME), USEC_PER_DAY),
.gateway = server_address->s_addr,
.hlen = ETH_ALEN,
.htype = ARPHRD_ETHER,
- .client_id.length = 2,
+ .client_id.size = 2,
};
- lease->client_id.data = new(uint8_t, lease->client_id.length);
- if (!lease->client_id.data)
- return -ENOMEM;
-
- lease->client_id.data[0] = 2;
- lease->client_id.data[1] = i;
-
- lease->server = server; /* This must be set just before hashmap_put(). */
-
- r = hashmap_ensure_put(&server->bound_leases_by_client_id, &dhcp_lease_hash_ops, &lease->client_id, lease);
- if (r < 0)
- return r;
+ lease->client_id.raw[0] = 2;
+ lease->client_id.raw[1] = i;
- r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease);
+ r = dhcp_server_put_lease(server, lease, /* is_static = */ false);
if (r < 0)
return r;
@@ -71,9 +65,11 @@ static int add_static_lease(sd_dhcp_server *server, uint8_t i) {
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
_cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
struct in_addr address = { .s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))};
_cleanup_free_ uint8_t *duped = NULL;
+ _cleanup_close_ int dir_fd = -EBADF;
if (size < sizeof(DHCPMessage))
return 0;
@@ -82,8 +78,12 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
assert_se(duped = memdup(data, size));
+ dir_fd = mkdtemp_open(NULL, 0, &tmpdir);
+ assert_se(dir_fd >= 0);
+
assert_se(sd_dhcp_server_new(&server, 1) >= 0);
assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0);
+ assert_se(sd_dhcp_server_set_lease_file(server, dir_fd, "leases") >= 0);
server->fd = open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY);
assert_se(server->fd >= 0);
assert_se(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0) >= 0);
@@ -98,5 +98,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
(void) dhcp_server_handle_message(server, (DHCPMessage*) duped, size, NULL);
+ assert_se(dhcp_server_save_leases(server) >= 0);
+ server->bound_leases_by_address = hashmap_free(server->bound_leases_by_address);
+ server->bound_leases_by_client_id = hashmap_free(server->bound_leases_by_client_id);
+ assert_se(dhcp_server_load_leases(server) >= 0);
+
return 0;
}
diff --git a/src/libsystemd-network/fuzz-dhcp6-client.c b/src/libsystemd-network/fuzz-dhcp6-client.c
index 2d42844..2b6e335 100644
--- a/src/libsystemd-network/fuzz-dhcp6-client.c
+++ b/src/libsystemd-network/fuzz-dhcp6-client.c
@@ -12,11 +12,11 @@
static int test_dhcp_fd[2] = EBADF_PAIR;
-int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, const void *packet, size_t len) {
+int dhcp6_network_send_udp_socket(int s, const struct in6_addr *server_address, const void *packet, size_t len) {
return len;
}
-int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) {
+int dhcp6_network_bind_udp_socket(int index, const struct in6_addr *local_address) {
assert_se(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_dhcp_fd) >= 0);
return TAKE_FD(test_dhcp_fd[0]);
}
diff --git a/src/libsystemd-network/fuzz-lldp-rx.c b/src/libsystemd-network/fuzz-lldp-rx.c
index 844957c..dad1038 100644
--- a/src/libsystemd-network/fuzz-lldp-rx.c
+++ b/src/libsystemd-network/fuzz-lldp-rx.c
@@ -9,6 +9,8 @@
#include "fd-util.h"
#include "fuzz.h"
#include "lldp-network.h"
+#include "lldp-rx-internal.h"
+#include "memstream-util.h"
static int test_fd[2] = EBADF_PAIR;
@@ -22,6 +24,9 @@ int lldp_network_bind_raw_socket(int ifindex) {
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(sd_lldp_rx_unrefp) sd_lldp_rx *lldp_rx = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_(memstream_done) MemStream m = {};
+ FILE *f;
if (outside_size_range(size, 0, 2048))
return 0;
@@ -37,6 +42,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
assert_se(write(test_fd[1], data, size) == (ssize_t) size);
assert_se(sd_event_run(e, 0) >= 0);
+ assert_se(lldp_rx_build_neighbors_json(lldp_rx, &v) >= 0);
+ assert_se(f = memstream_init(&m));
+ (void) json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR, f, NULL);
+
assert_se(sd_lldp_rx_stop(lldp_rx) >= 0);
assert_se(sd_lldp_rx_detach_event(lldp_rx) >= 0);
test_fd[1] = safe_close(test_fd[1]);
diff --git a/src/libsystemd-network/fuzz-ndisc-rs.c b/src/libsystemd-network/fuzz-ndisc-rs.c
index a89e2b0..e6ee768 100644
--- a/src/libsystemd-network/fuzz-ndisc-rs.c
+++ b/src/libsystemd-network/fuzz-ndisc-rs.c
@@ -5,26 +5,24 @@
#include <unistd.h>
#include "sd-ndisc.h"
+#include "sd-radv.h"
#include "alloc-util.h"
#include "fd-util.h"
#include "fuzz.h"
-#include "icmp6-util-unix.h"
+#include "icmp6-packet.h"
+#include "icmp6-test-util.h"
#include "ndisc-internal.h"
+#include "ndisc-option.h"
#include "socket-util.h"
-int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+static void test_with_sd_ndisc(const uint8_t *data, size_t size) {
struct ether_addr mac_addr = {
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
};
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
- if (outside_size_range(size, 0, 2048))
- return 0;
-
- fuzz_setup_logging();
-
assert_se(sd_event_new(&e) >= 0);
assert_se(sd_ndisc_new(&nd) >= 0);
assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0);
@@ -34,7 +32,67 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
assert_se(write(test_fd[1], data, size) == (ssize_t) size);
(void) sd_event_run(e, UINT64_MAX);
assert_se(sd_ndisc_stop(nd) >= 0);
- close(test_fd[1]);
+ test_fd[1] = safe_close(test_fd[1]);
+ TAKE_FD(test_fd[0]); /* It should be already closed by sd_ndisc_stop(). */
+}
+
+static void test_with_sd_radv(const uint8_t *data, size_t size) {
+ struct ether_addr mac_addr = {
+ .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
+ };
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_radv_unrefp) sd_radv *ra = NULL;
+
+ assert_se(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) >= 0);
+
+ assert_se(sd_event_new(&e) >= 0);
+ assert_se(sd_radv_new(&ra) >= 0);
+ assert_se(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_start(ra) >= 0);
+ assert_se(write(test_fd[0], data, size) == (ssize_t) size);
+ (void) sd_event_run(e, UINT64_MAX);
+ assert_se(sd_radv_stop(ra) >= 0);
+ test_fd[0] = safe_close(test_fd[0]);
+ TAKE_FD(test_fd[1]); /* It should be already closed by sd_radv_stop(). */
+}
+
+static void test_with_icmp6_packet(const uint8_t *data, size_t size) {
+ _cleanup_close_pair_ int fd_pair[2] = EBADF_PAIR;
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
+ _cleanup_set_free_ Set *options = NULL;
+
+ assert_se(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, fd_pair) >= 0);
+ assert_se(write(fd_pair[1], data, size) == (ssize_t) size);
+
+ if (icmp6_packet_receive(fd_pair[0], &packet) < 0)
+ return;
+
+ if (ndisc_parse_options(packet, &options) < 0)
+ return;
+
+ if (ndisc_send(fd_pair[1], &IN6_ADDR_ALL_ROUTERS_MULTICAST,
+ icmp6_packet_get_header(packet), options, /* timestamp = */ 0) < 0)
+ return;
+
+ packet = icmp6_packet_unref(packet);
+ options = set_free(options);
+
+ if (icmp6_packet_receive(fd_pair[0], &packet) < 0)
+ return;
+
+ assert_se(ndisc_parse_options(packet, &options) >= 0);
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (outside_size_range(size, 0, 2048))
+ return 0;
+
+ fuzz_setup_logging();
+ test_with_sd_ndisc(data, size);
+ test_with_sd_radv(data, size);
+ test_with_icmp6_packet(data, size);
return 0;
}
diff --git a/src/libsystemd-network/icmp6-packet.c b/src/libsystemd-network/icmp6-packet.c
new file mode 100644
index 0000000..02865a4
--- /dev/null
+++ b/src/libsystemd-network/icmp6-packet.c
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <netinet/icmp6.h>
+
+#include "icmp6-packet.h"
+#include "icmp6-util.h"
+#include "in-addr-util.h"
+#include "iovec-util.h"
+#include "network-common.h"
+#include "socket-util.h"
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(ICMP6Packet, icmp6_packet, mfree);
+
+static ICMP6Packet* icmp6_packet_new(size_t size) {
+ ICMP6Packet *p;
+
+ if (size > SIZE_MAX - offsetof(ICMP6Packet, raw_packet))
+ return NULL;
+
+ p = malloc0(offsetof(ICMP6Packet, raw_packet) + size);
+ if (!p)
+ return NULL;
+
+ p->raw_size = size;
+ p->n_ref = 1;
+
+ return p;
+}
+
+int icmp6_packet_set_sender_address(ICMP6Packet *p, const struct in6_addr *addr) {
+ assert(p);
+
+ if (addr)
+ p->sender_address = *addr;
+ else
+ p->sender_address = (const struct in6_addr) {};
+
+ return 0;
+}
+
+int icmp6_packet_get_sender_address(ICMP6Packet *p, struct in6_addr *ret) {
+ assert(p);
+
+ if (in6_addr_is_null(&p->sender_address))
+ return -ENODATA;
+
+ if (ret)
+ *ret = p->sender_address;
+ return 0;
+}
+
+int icmp6_packet_get_timestamp(ICMP6Packet *p, clockid_t clock, usec_t *ret) {
+ assert(p);
+ assert(ret);
+
+ if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock) || !clock_supported(clock))
+ return -EOPNOTSUPP;
+
+ if (!triple_timestamp_is_set(&p->timestamp))
+ return -ENODATA;
+
+ *ret = triple_timestamp_by_clock(&p->timestamp, clock);
+ return 0;
+}
+
+const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p) {
+ assert(p);
+
+ if (p->raw_size < sizeof(struct icmp6_hdr))
+ return NULL;
+
+ return (const struct icmp6_hdr*) p->raw_packet;
+}
+
+int icmp6_packet_get_type(ICMP6Packet *p) {
+ const struct icmp6_hdr *hdr = icmp6_packet_get_header(p);
+ if (!hdr)
+ return -EBADMSG;
+
+ return hdr->icmp6_type;
+}
+
+static int icmp6_packet_verify(ICMP6Packet *p) {
+ const struct icmp6_hdr *hdr = icmp6_packet_get_header(p);
+ if (!hdr)
+ return -EBADMSG;
+
+ if (hdr->icmp6_code != 0)
+ return -EBADMSG;
+
+ /* Drop any overly large packets early. We are not interested in jumbograms,
+ * which could cause excessive processing. */
+ if (p->raw_size > ICMP6_MAX_NORMAL_PAYLOAD_SIZE)
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+int icmp6_packet_receive(int fd, ICMP6Packet **ret) {
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *p = NULL;
+ ssize_t len;
+ int r;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ len = next_datagram_size_fd(fd);
+ if (len < 0)
+ return (int) len;
+
+ p = icmp6_packet_new(len);
+ if (!p)
+ return -ENOMEM;
+
+ r = icmp6_receive(fd, p->raw_packet, p->raw_size, &p->sender_address, &p->timestamp);
+ if (r == -EADDRNOTAVAIL)
+ return log_debug_errno(r, "ICMPv6: Received a packet from neither link-local nor null address.");
+ if (r == -EMULTIHOP)
+ return log_debug_errno(r, "ICMPv6: Received a packet with an invalid hop limit.");
+ if (r == -EPFNOSUPPORT)
+ return log_debug_errno(r, "ICMPv6: Received a packet with an invalid source address.");
+ if (r < 0)
+ return log_debug_errno(r, "ICMPv6: Unexpected error while receiving a packet: %m");
+
+ r = icmp6_packet_verify(p);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(p);
+ return 0;
+}
diff --git a/src/libsystemd-network/icmp6-packet.h b/src/libsystemd-network/icmp6-packet.h
new file mode 100644
index 0000000..b402255
--- /dev/null
+++ b/src/libsystemd-network/icmp6-packet.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <netinet/in.h>
+
+#include "macro.h"
+#include "time-util.h"
+
+typedef struct ICMP6Pakcet {
+ unsigned n_ref;
+
+ struct in6_addr sender_address;
+ struct triple_timestamp timestamp;
+
+ size_t raw_size;
+ uint8_t raw_packet[];
+} ICMP6Packet;
+
+ICMP6Packet* icmp6_packet_ref(ICMP6Packet *p);
+ICMP6Packet* icmp6_packet_unref(ICMP6Packet *p);
+DEFINE_TRIVIAL_CLEANUP_FUNC(ICMP6Packet*, icmp6_packet_unref);
+
+/* IPv6 Header is 40 bytes and reserves 2 bytes to represent the Payload Length. Thus, the max payload size,
+ * including extension headers, is 65535 bytes (2^16 - 1). Jumbograms can be larger (2^32 - 1). */
+#define ICMP6_MAX_NORMAL_PAYLOAD_SIZE 65535
+
+int icmp6_packet_set_sender_address(ICMP6Packet *p, const struct in6_addr *addr);
+int icmp6_packet_get_sender_address(ICMP6Packet *p, struct in6_addr *ret);
+int icmp6_packet_get_timestamp(ICMP6Packet *p, clockid_t clock, usec_t *ret);
+const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p);
+int icmp6_packet_get_type(ICMP6Packet *p);
+
+int icmp6_packet_receive(int fd, ICMP6Packet **ret);
diff --git a/src/libsystemd-network/icmp6-util-unix.c b/src/libsystemd-network/icmp6-test-util.c
index 01edb85..3c78109 100644
--- a/src/libsystemd-network/icmp6-util-unix.c
+++ b/src/libsystemd-network/icmp6-test-util.c
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <netinet/icmp6.h>
#include <netinet/ip6.h>
#include <unistd.h>
#include "fd-util.h"
-#include "icmp6-util-unix.h"
+#include "icmp6-test-util.h"
-send_ra_t send_ra_function = NULL;
int test_fd[2] = EBADF_PAIR;
static struct in6_addr dummy_link_local = {
@@ -16,22 +16,15 @@ static struct in6_addr dummy_link_local = {
},
};
-int icmp6_bind_router_solicitation(int ifindex) {
- if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0)
+int icmp6_bind(int ifindex, bool is_router) {
+ if (!is_router && socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0)
return -errno;
- return test_fd[0];
+ return test_fd[is_router];
}
-int icmp6_bind_router_advertisement(int ifindex) {
- return test_fd[1];
-}
-
-int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
- if (!send_ra_function)
- return 0;
-
- return send_ra_function(0);
+int icmp6_send(int fd, const struct in6_addr *dst, const struct iovec *iov, size_t n_iov) {
+ return writev(fd, iov, n_iov);
}
int icmp6_receive(
diff --git a/src/libsystemd-network/icmp6-util-unix.h b/src/libsystemd-network/icmp6-test-util.h
index a9cb05a..d7b0cc8 100644
--- a/src/libsystemd-network/icmp6-util-unix.h
+++ b/src/libsystemd-network/icmp6-test-util.h
@@ -3,7 +3,4 @@
#include "icmp6-util.h"
-typedef int (*send_ra_t)(uint8_t flags);
-
-extern send_ra_t send_ra_function;
extern int test_fd[2];
diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c
index 72c20ba..75a6489 100644
--- a/src/libsystemd-network/icmp6-util.c
+++ b/src/libsystemd-network/icmp6-util.c
@@ -3,7 +3,10 @@
Copyright © 2014 Intel Corporation. All rights reserved.
***/
+/* Make sure the net/if.h header is included before any linux/ one */
+#include <net/if.h>
#include <errno.h>
+#include <linux/if_packet.h>
#include <netinet/icmp6.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
@@ -11,8 +14,6 @@
#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"
@@ -21,37 +22,44 @@
#include "network-common.h"
#include "socket-util.h"
-#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
- { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } }
-
-#define IN6ADDR_ALL_NODES_MULTICAST_INIT \
- { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } }
-
-static int icmp6_bind_router_message(const struct icmp6_filter *filter,
- const struct ipv6_mreq *mreq) {
- int ifindex = mreq->ipv6mr_interface;
+int icmp6_bind(int ifindex, bool is_router) {
+ struct icmp6_filter filter = {};
+ struct ipv6_mreq mreq;
_cleanup_close_ int s = -EBADF;
int r;
- assert(filter);
- assert(mreq);
+ assert(ifindex > 0);
+
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ if (is_router) {
+ mreq = (struct ipv6_mreq) {
+ .ipv6mr_multiaddr = IN6_ADDR_ALL_ROUTERS_MULTICAST,
+ .ipv6mr_interface = ifindex,
+ };
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+ } else {
+ mreq = (struct ipv6_mreq) {
+ .ipv6mr_multiaddr = IN6_ADDR_ALL_NODES_MULTICAST,
+ .ipv6mr_interface = ifindex,
+ };
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+ ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter);
+ ICMP6_FILTER_SETPASS(ND_REDIRECT, &filter);
+ }
s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6);
if (s < 0)
return -errno;
- if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, filter, sizeof(*filter)) < 0)
+ if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) < 0)
return -errno;
- if (setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, mreq, sizeof(*mreq)) < 0)
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
return -errno;
- /* RFC 3315, section 6.7, bullet point 2 may indicate that an
- IPV6_PKTINFO socket option also applies for ICMPv6 multicast.
- Empirical experiments indicates otherwise and therefore an
- IPV6_MULTICAST_IF socket option is used here instead */
+ /* 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;
@@ -83,63 +91,21 @@ static int icmp6_bind_router_message(const struct icmp6_filter *filter,
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 = {
+int icmp6_send(int fd, const struct in6_addr *dst, const struct iovec *iov, size_t n_iov) {
+ struct sockaddr_in6 sa = {
.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),
+ .sin6_addr = *ASSERT_PTR(dst),
};
struct msghdr msg = {
- .msg_name = &dst,
- .msg_namelen = sizeof(dst),
- .msg_iov = &iov,
- .msg_iovlen = 1,
+ .msg_name = &sa,
+ .msg_namelen = sizeof(struct sockaddr_in6),
+ .msg_iov = (struct iovec*) iov,
+ .msg_iovlen = n_iov,
};
- assert(s >= 0);
- assert(ether_addr);
-
- rs.rs_opt_mac = *ether_addr;
+ assert(fd >= 0);
- if (sendmsg(s, &msg, 0) < 0)
+ if (sendmsg(fd, &msg, 0) < 0)
return -errno;
return 0;
@@ -155,7 +121,7 @@ int icmp6_receive(
/* This needs to be initialized with zero. See #20741. */
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int)) + /* ttl */
CMSG_SPACE_TIMEVAL) control = {};
- struct iovec iov = {};
+ struct iovec iov = { buffer, size };
union sockaddr_union sa = {};
struct msghdr msg = {
.msg_name = &sa.sa,
@@ -165,11 +131,8 @@ int icmp6_receive(
.msg_control = &control,
.msg_controllen = sizeof(control),
};
- struct in6_addr addr = {};
ssize_t len;
- iov = IOVEC_MAKE(buffer, size);
-
len = recvmsg_safe(fd, &msg, MSG_DONTWAIT);
if (len < 0)
return (int) len;
@@ -177,17 +140,11 @@ int icmp6_receive(
if ((size_t) len != size)
return -EINVAL;
- if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
- sa.in6.sin6_family == AF_INET6) {
-
- addr = sa.in6.sin6_addr;
- if (!in6_addr_is_link_local(&addr) && !in6_addr_is_null(&addr))
- return -EADDRNOTAVAIL;
-
- } else if (msg.msg_namelen > 0)
+ if (msg.msg_namelen != sizeof(struct sockaddr_in6) || sa.in6.sin6_family != AF_INET6)
return -EPFNOSUPPORT;
- /* namelen == 0 only happens when running the test-suite over a socketpair */
+ if (!in6_addr_is_link_local(&sa.in6.sin6_addr) && !in6_addr_is_null(&sa.in6.sin6_addr))
+ return -EADDRNOTAVAIL;
assert(!(msg.msg_flags & MSG_TRUNC));
@@ -198,6 +155,6 @@ int icmp6_receive(
if (ret_timestamp)
triple_timestamp_from_cmsg(ret_timestamp, &msg);
if (ret_sender)
- *ret_sender = addr;
+ *ret_sender = sa.in6.sin6_addr;
return 0;
}
diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h
index 0a9ecb4..9e5063f 100644
--- a/src/libsystemd-network/icmp6-util.h
+++ b/src/libsystemd-network/icmp6-util.h
@@ -6,20 +6,26 @@
***/
#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <sys/uio.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 IN6_ADDR_ALL_ROUTERS_MULTICAST \
+ ((const struct in6_addr) { { { \
+ 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 } } }
+#define IN6_ADDR_ALL_NODES_MULTICAST \
+ ((const struct in6_addr) { { { \
+ 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_bind(int ifindex, bool is_router);
+int icmp6_send(int fd, const struct in6_addr *dst, const struct iovec *iov, size_t n_iov);
int icmp6_receive(
int fd,
void *buffer,
diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c
index af61c9b..a4384ac 100644
--- a/src/libsystemd-network/lldp-neighbor.c
+++ b/src/libsystemd-network/lldp-neighbor.c
@@ -14,10 +14,10 @@ static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash
assert(id);
assert(state);
- siphash24_compress(id->chassis_id, id->chassis_id_size, state);
- siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state);
- siphash24_compress(id->port_id, id->port_id_size, state);
- siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state);
+ siphash24_compress_safe(id->chassis_id, id->chassis_id_size, state);
+ siphash24_compress_typesafe(id->chassis_id_size, state);
+ siphash24_compress_safe(id->port_id, id->port_id_size, state);
+ siphash24_compress_typesafe(id->port_id_size, state);
}
int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y) {
@@ -376,17 +376,6 @@ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_a
return 0;
}
-int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
- assert_return(n, -EINVAL);
- assert_return(ret, -EINVAL);
- assert_return(size, -EINVAL);
-
- *ret = LLDP_NEIGHBOR_RAW(n);
- *size = n->raw_size;
-
- return 0;
-}
-
int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
assert_return(n, -EINVAL);
assert_return(type, -EINVAL);
@@ -640,28 +629,6 @@ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret
return 0;
}
-int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
- _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
- int r;
-
- assert_return(ret, -EINVAL);
- assert_return(raw || raw_size <= 0, -EINVAL);
-
- n = lldp_neighbor_new(raw_size);
- if (!n)
- return -ENOMEM;
-
- memcpy_safe(LLDP_NEIGHBOR_RAW(n), raw, raw_size);
-
- r = lldp_neighbor_parse(n);
- if (r < 0)
- return r;
-
- *ret = TAKE_PTR(n);
-
- return r;
-}
-
int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) {
assert_return(n, -EINVAL);
@@ -793,3 +760,31 @@ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_
*ret = triple_timestamp_by_clock(&n->timestamp, clock);
return 0;
}
+
+int lldp_neighbor_build_json(sd_lldp_neighbor *n, JsonVariant **ret) {
+ const char *chassis_id = NULL, *port_id = NULL, *port_description = NULL,
+ *system_name = NULL, *system_description = NULL;
+ uint16_t cc = 0;
+ bool valid_cc;
+
+ assert(n);
+ assert(ret);
+
+ (void) sd_lldp_neighbor_get_chassis_id_as_string(n, &chassis_id);
+ (void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id);
+ (void) sd_lldp_neighbor_get_port_description(n, &port_description);
+ (void) sd_lldp_neighbor_get_system_name(n, &system_name);
+ (void) sd_lldp_neighbor_get_system_description(n, &system_description);
+
+ valid_cc = sd_lldp_neighbor_get_enabled_capabilities(n, &cc) >= 0;
+
+ return json_build(ret, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("ChassisID", chassis_id),
+ JSON_BUILD_PAIR_BYTE_ARRAY("RawChassisID", n->id.chassis_id, n->id.chassis_id_size),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("PortID", port_id),
+ JSON_BUILD_PAIR_BYTE_ARRAY("RawPortID", n->id.port_id, n->id.port_id_size),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("PortDescription", port_description),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemName", system_name),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemDescription", system_description),
+ JSON_BUILD_PAIR_CONDITION(valid_cc, "EnabledCapabilities", JSON_BUILD_UNSIGNED(cc))));
+}
diff --git a/src/libsystemd-network/lldp-neighbor.h b/src/libsystemd-network/lldp-neighbor.h
index 016286b..06ba4c7 100644
--- a/src/libsystemd-network/lldp-neighbor.h
+++ b/src/libsystemd-network/lldp-neighbor.h
@@ -8,6 +8,7 @@
#include "sd-lldp-rx.h"
#include "hash-funcs.h"
+#include "json.h"
#include "lldp-rx-internal.h"
#include "time-util.h"
@@ -90,3 +91,4 @@ 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);
+int lldp_neighbor_build_json(sd_lldp_neighbor *n, JsonVariant **ret);
diff --git a/src/libsystemd-network/lldp-rx-internal.h b/src/libsystemd-network/lldp-rx-internal.h
index 83d0bc4..e914c6b 100644
--- a/src/libsystemd-network/lldp-rx-internal.h
+++ b/src/libsystemd-network/lldp-rx-internal.h
@@ -5,6 +5,7 @@
#include "sd-lldp-rx.h"
#include "hashmap.h"
+#include "json.h"
#include "network-common.h"
#include "prioq.h"
@@ -36,6 +37,8 @@ struct sd_lldp_rx {
const char* lldp_rx_event_to_string(sd_lldp_rx_event_t e) _const_;
sd_lldp_rx_event_t lldp_rx_event_from_string(const char *s) _pure_;
+int lldp_rx_build_neighbors_json(sd_lldp_rx *lldp_rx, JsonVariant **ret);
+
#define log_lldp_rx_errno(lldp_rx, error, fmt, ...) \
log_interface_prefix_full_errno( \
"LLDP Rx: ", \
diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build
index 93186e2..718495c 100644
--- a/src/libsystemd-network/meson.build
+++ b/src/libsystemd-network/meson.build
@@ -2,22 +2,24 @@
sources = files(
'arp-util.c',
- 'dhcp-identifier.c',
'dhcp-network.c',
'dhcp-option.c',
'dhcp-packet.c',
'dhcp6-network.c',
'dhcp6-option.c',
'dhcp6-protocol.c',
+ 'icmp6-packet.c',
'icmp6-util.c',
'lldp-neighbor.c',
'lldp-network.c',
- 'ndisc-protocol.c',
- 'ndisc-router.c',
+ 'ndisc-option.c',
'network-common.c',
'network-internal.c',
+ 'sd-dhcp-client-id.c',
'sd-dhcp-client.c',
+ 'sd-dhcp-duid.c',
'sd-dhcp-lease.c',
+ 'sd-dhcp-server-lease.c',
'sd-dhcp-server.c',
'sd-dhcp6-client.c',
'sd-dhcp6-lease.c',
@@ -26,6 +28,10 @@ sources = files(
'sd-lldp-rx.c',
'sd-lldp-tx.c',
'sd-ndisc.c',
+ 'sd-ndisc-neighbor.c',
+ 'sd-ndisc-redirect.c',
+ 'sd-ndisc-router.c',
+ 'sd-ndisc-router-solicit.c',
'sd-radv.c',
)
@@ -85,16 +91,20 @@ executables += [
network_test_template + {
'sources' : files(
'test-ndisc-ra.c',
- 'icmp6-util-unix.c',
+ 'icmp6-test-util.c',
),
},
network_test_template + {
'sources' : files(
'test-ndisc-rs.c',
- 'icmp6-util-unix.c',
+ 'icmp6-test-util.c',
),
},
network_test_template + {
+ 'sources' : files('test-ndisc-send.c'),
+ 'type' : 'manual',
+ },
+ network_test_template + {
'sources' : files('test-sd-dhcp-lease.c'),
},
network_fuzz_template + {
@@ -115,7 +125,7 @@ executables += [
network_fuzz_template + {
'sources' : files(
'fuzz-ndisc-rs.c',
- 'icmp6-util-unix.c',
+ 'icmp6-test-util.c',
),
},
]
diff --git a/src/libsystemd-network/ndisc-internal.h b/src/libsystemd-network/ndisc-internal.h
index 615de0d..4e82fd5 100644
--- a/src/libsystemd-network/ndisc-internal.h
+++ b/src/libsystemd-network/ndisc-internal.h
@@ -24,6 +24,7 @@ struct sd_ndisc {
sd_event *event;
int event_priority;
+ struct in6_addr link_local_addr;
struct ether_addr mac_addr;
sd_event_source *recv_event_source;
diff --git a/src/libsystemd-network/ndisc-neighbor-internal.h b/src/libsystemd-network/ndisc-neighbor-internal.h
new file mode 100644
index 0000000..aee6556
--- /dev/null
+++ b/src/libsystemd-network/ndisc-neighbor-internal.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-ndisc.h"
+
+#include "icmp6-packet.h"
+#include "set.h"
+
+struct sd_ndisc_neighbor {
+ unsigned n_ref;
+
+ ICMP6Packet *packet;
+
+ uint32_t flags;
+ struct in6_addr target_address;
+
+ Set *options;
+};
+
+sd_ndisc_neighbor* ndisc_neighbor_new(ICMP6Packet *packet);
+int ndisc_neighbor_parse(sd_ndisc *nd, sd_ndisc_neighbor *na);
diff --git a/src/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c
new file mode 100644
index 0000000..901a3b3
--- /dev/null
+++ b/src/libsystemd-network/ndisc-option.c
@@ -0,0 +1,1502 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <netinet/icmp6.h>
+
+#include "dns-domain.h"
+#include "ether-addr-util.h"
+#include "hostname-util.h"
+#include "icmp6-util.h"
+#include "in-addr-util.h"
+#include "iovec-util.h"
+#include "missing_network.h"
+#include "ndisc-option.h"
+#include "network-common.h"
+#include "strv.h"
+#include "unaligned.h"
+
+/* RFC does not say anything about the maximum number of options, but let's limit the number of options for
+ * safety. Typically, the number of options in an ICMPv6 message should be only a few. */
+#define MAX_OPTIONS 128
+
+int ndisc_option_parse(
+ ICMP6Packet *p,
+ size_t offset,
+ uint8_t *ret_type,
+ size_t *ret_len,
+ const uint8_t **ret_opt) {
+
+ assert(p);
+
+ if (offset == p->raw_size)
+ return -ESPIPE; /* end of the packet */
+
+ if (offset > p->raw_size)
+ return -EBADMSG;
+
+ if (p->raw_size - offset < sizeof(struct nd_opt_hdr))
+ return -EBADMSG;
+
+ assert_cc(alignof(struct nd_opt_hdr) == 1);
+ const struct nd_opt_hdr *hdr = (const struct nd_opt_hdr*) (p->raw_packet + offset);
+ if (hdr->nd_opt_len == 0)
+ return -EBADMSG;
+
+ size_t len = hdr->nd_opt_len * 8;
+ if (p->raw_size - offset < len)
+ return -EBADMSG;
+
+ if (ret_type)
+ *ret_type = hdr->nd_opt_type;
+ if (ret_len)
+ *ret_len = len;
+ if (ret_opt)
+ *ret_opt = p->raw_packet + offset;
+
+ return 0;
+}
+
+static sd_ndisc_option* ndisc_option_new(uint8_t type, size_t offset) {
+ sd_ndisc_option *p = new0(sd_ndisc_option, 1); /* use new0() here to make the fuzzers silent. */
+ if (!p)
+ return NULL;
+
+ /* As the same reason in the above, do not use the structured initializer here. */
+ p->type = type;
+ p->offset = offset;
+
+ return p;
+}
+
+static void ndisc_raw_done(sd_ndisc_raw *raw) {
+ if (!raw)
+ return;
+
+ free(raw->bytes);
+}
+
+static void ndisc_rdnss_done(sd_ndisc_rdnss *rdnss) {
+ if (!rdnss)
+ return;
+
+ free(rdnss->addresses);
+}
+
+static void ndisc_dnssl_done(sd_ndisc_dnssl *dnssl) {
+ if (!dnssl)
+ return;
+
+ strv_free(dnssl->domains);
+}
+
+sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) {
+ if (!option)
+ return NULL;
+
+ switch (option->type) {
+ case 0:
+ ndisc_raw_done(&option->raw);
+ break;
+
+ case SD_NDISC_OPTION_RDNSS:
+ ndisc_rdnss_done(&option->rdnss);
+ break;
+
+ case SD_NDISC_OPTION_DNSSL:
+ ndisc_dnssl_done(&option->dnssl);
+ break;
+
+ case SD_NDISC_OPTION_CAPTIVE_PORTAL:
+ free(option->captive_portal);
+ break;
+ }
+
+ return mfree(option);
+}
+
+static int ndisc_option_compare_func(const sd_ndisc_option *x, const sd_ndisc_option *y) {
+ int r;
+
+ assert(x);
+ assert(y);
+
+ r = CMP(x->type, y->type);
+ if (r != 0)
+ return r;
+
+ switch (x->type) {
+ case 0:
+ return memcmp_nn(x->raw.bytes, x->raw.length, y->raw.bytes, y->raw.length);
+
+ case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
+ case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
+ case SD_NDISC_OPTION_REDIRECTED_HEADER:
+ case SD_NDISC_OPTION_MTU:
+ case SD_NDISC_OPTION_HOME_AGENT:
+ case SD_NDISC_OPTION_FLAGS_EXTENSION:
+ case SD_NDISC_OPTION_CAPTIVE_PORTAL:
+ /* These options cannot be specified multiple times. */
+ return 0;
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION:
+ /* Should not specify the same prefix multiple times. */
+ r = CMP(x->prefix.prefixlen, y->prefix.prefixlen);
+ if (r != 0)
+ return r;
+
+ return memcmp(&x->prefix.address, &y->prefix.address, sizeof(struct in6_addr));
+
+ case SD_NDISC_OPTION_ROUTE_INFORMATION:
+ r = CMP(x->route.prefixlen, y->route.prefixlen);
+ if (r != 0)
+ return r;
+
+ return memcmp(&x->route.address, &y->route.address, sizeof(struct in6_addr));
+
+ case SD_NDISC_OPTION_PREF64:
+ r = CMP(x->prefix64.prefixlen, y->prefix64.prefixlen);
+ if (r != 0)
+ return r;
+
+ return memcmp(&x->prefix64.prefix, &y->prefix64.prefix, sizeof(struct in6_addr));
+
+ default:
+ /* DNSSL, RDNSS, and other unsupported options can be specified multiple times. */
+ return trivial_compare_func(x, y);
+ }
+}
+
+static void ndisc_option_hash_func(const sd_ndisc_option *option, struct siphash *state) {
+ assert(option);
+ assert(state);
+
+ siphash24_compress_typesafe(option->type, state);
+
+ switch (option->type) {
+ case 0:
+ siphash24_compress(option->raw.bytes, option->raw.length, state);
+ break;
+
+ case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
+ case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
+ case SD_NDISC_OPTION_REDIRECTED_HEADER:
+ case SD_NDISC_OPTION_MTU:
+ case SD_NDISC_OPTION_HOME_AGENT:
+ case SD_NDISC_OPTION_FLAGS_EXTENSION:
+ case SD_NDISC_OPTION_CAPTIVE_PORTAL:
+ break;
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION:
+ siphash24_compress_typesafe(option->prefix.prefixlen, state);
+ siphash24_compress_typesafe(option->prefix.address, state);
+ break;
+
+ case SD_NDISC_OPTION_ROUTE_INFORMATION:
+ siphash24_compress_typesafe(option->route.prefixlen, state);
+ siphash24_compress_typesafe(option->route.address, state);
+ break;
+
+ case SD_NDISC_OPTION_PREF64:
+ siphash24_compress_typesafe(option->prefix64.prefixlen, state);
+ siphash24_compress_typesafe(option->prefix64.prefix, state);
+ break;
+
+ default:
+ trivial_hash_func(option, state);
+ }
+}
+
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+ ndisc_option_hash_ops,
+ sd_ndisc_option,
+ ndisc_option_hash_func,
+ ndisc_option_compare_func,
+ ndisc_option_free);
+
+static int ndisc_option_consume(Set **options, sd_ndisc_option *p) {
+ assert(options);
+ assert(p);
+
+ if (set_size(*options) >= MAX_OPTIONS) {
+ ndisc_option_free(p);
+ return -ETOOMANYREFS; /* recognizable error code */
+ }
+
+ return set_ensure_consume(options, &ndisc_option_hash_ops, p);
+}
+
+int ndisc_option_set_raw(Set **options, size_t length, const uint8_t *bytes) {
+ _cleanup_free_ uint8_t *copy = NULL;
+
+ assert(options);
+ assert(bytes);
+
+ if (length == 0)
+ return -EINVAL;
+
+ copy = newdup(uint8_t, bytes, length);
+ if (!copy)
+ return -ENOMEM;
+
+ sd_ndisc_option *p = ndisc_option_new(/* type = */ 0, /* offset = */ 0);
+ if (!p)
+ return -ENOMEM;
+
+ p->raw = (sd_ndisc_raw) {
+ .bytes = TAKE_PTR(copy),
+ .length = length,
+ };
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_build_raw(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == 0);
+ assert(ret);
+
+ _cleanup_free_ uint8_t *buf = newdup(uint8_t, option->raw.bytes, option->raw.length);
+ if (!buf)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
+int ndisc_option_add_link_layer_address(Set **options, uint8_t type, size_t offset, const struct ether_addr *mac) {
+ assert(options);
+ assert(IN_SET(type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS));
+
+ if (!mac || ether_addr_is_null(mac)) {
+ ndisc_option_remove_by_type(*options, type);
+ return 0;
+ }
+
+ sd_ndisc_option *p = ndisc_option_get_by_type(*options, type);
+ if (p) {
+ /* offset == 0 means that we are now building a packet to be sent, and in that case we allow
+ * to override the option we previously set.
+ * offset != 0 means that we are now parsing a received packet, and we refuse to override
+ * conflicting options. */
+ if (offset != 0)
+ return -EEXIST;
+
+ p->mac = *mac;
+ return 0;
+ }
+
+ p = ndisc_option_new(type, offset);
+ if (!p)
+ return -ENOMEM;
+
+ p->mac = *mac;
+
+ return set_ensure_consume(options, &ndisc_option_hash_ops, p);
+}
+
+static int ndisc_option_parse_link_layer_address(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ assert(options);
+ assert(opt);
+
+ if (len != sizeof(struct ether_addr) + 2)
+ return -EBADMSG;
+
+ if (!IN_SET(opt[0], SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS))
+ return -EBADMSG;
+
+ struct ether_addr mac;
+ memcpy(&mac, opt + 2, sizeof(struct ether_addr));
+
+ if (ether_addr_is_null(&mac))
+ return -EBADMSG;
+
+ return ndisc_option_add_link_layer_address(options, opt[0], offset, &mac);
+}
+
+static int ndisc_option_build_link_layer_address(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(IN_SET(option->type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS));
+ assert(ret);
+
+ assert_cc(2 + sizeof(struct ether_addr) == 8);
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, 2 + sizeof(struct ether_addr));
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = option->type;
+ buf[1] = 1;
+ memcpy(buf + 2, &option->mac, sizeof(struct ether_addr));
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
+int ndisc_option_add_prefix_internal(
+ Set **options,
+ size_t offset,
+ uint8_t flags,
+ uint8_t prefixlen,
+ const struct in6_addr *address,
+ usec_t valid_lifetime,
+ usec_t preferred_lifetime,
+ usec_t valid_until,
+ usec_t preferred_until) {
+
+ assert(options);
+ assert(address);
+
+ if (prefixlen > 128)
+ return -EINVAL;
+
+ struct in6_addr addr = *address;
+ in6_addr_mask(&addr, prefixlen);
+
+ /* RFC 4861 and 4862 only state that link-local prefix should be ignored.
+ * But here we also ignore null and multicast addresses. */
+ if (in6_addr_is_link_local(&addr) || in6_addr_is_null(&addr) || in6_addr_is_multicast(&addr))
+ return -EINVAL;
+
+ if (preferred_lifetime > valid_lifetime)
+ return -EINVAL;
+
+ if (preferred_until > valid_until)
+ return -EINVAL;
+
+ sd_ndisc_option *p = ndisc_option_get(
+ *options,
+ &(const sd_ndisc_option) {
+ .type = SD_NDISC_OPTION_PREFIX_INFORMATION,
+ .prefix.prefixlen = prefixlen,
+ .prefix.address = addr,
+ });
+ if (p) {
+ if (offset != 0)
+ return -EEXIST;
+
+ p->prefix.flags = flags;
+ p->prefix.valid_lifetime = valid_lifetime;
+ p->prefix.preferred_lifetime = preferred_lifetime;
+ p->prefix.valid_until = valid_until;
+ p->prefix.preferred_until = preferred_until;
+ return 0;
+ }
+
+ p = ndisc_option_new(SD_NDISC_OPTION_PREFIX_INFORMATION, offset);
+ if (!p)
+ return -ENOMEM;
+
+ p->prefix = (sd_ndisc_prefix) {
+ .flags = flags,
+ .prefixlen = prefixlen,
+ .address = addr,
+ .valid_lifetime = valid_lifetime,
+ .preferred_lifetime = preferred_lifetime,
+ .valid_until = valid_until,
+ .preferred_until = preferred_until,
+ };
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_parse_prefix(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ const struct nd_opt_prefix_info *pi = (const struct nd_opt_prefix_info*) ASSERT_PTR(opt);
+
+ assert(options);
+
+ if (len != sizeof(struct nd_opt_prefix_info))
+ return -EBADMSG;
+
+ if (pi->nd_opt_pi_type != SD_NDISC_OPTION_PREFIX_INFORMATION)
+ return -EBADMSG;
+
+ usec_t valid = be32_sec_to_usec(pi->nd_opt_pi_valid_time, /* max_as_infinity = */ true);
+ usec_t pref = be32_sec_to_usec(pi->nd_opt_pi_preferred_time, /* max_as_infinity = */ true);
+
+ /* We only support 64 bits interface identifier for addrconf. */
+ uint8_t flags = pi->nd_opt_pi_flags_reserved;
+ if (FLAGS_SET(flags, ND_OPT_PI_FLAG_AUTO) && pi->nd_opt_pi_prefix_len != 64)
+ flags &= ~ND_OPT_PI_FLAG_AUTO;
+
+ return ndisc_option_add_prefix(options, offset, flags,
+ pi->nd_opt_pi_prefix_len, &pi->nd_opt_pi_prefix,
+ valid, pref);
+}
+
+static int ndisc_option_build_prefix(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_PREFIX_INFORMATION);
+ assert(ret);
+
+ assert_cc(sizeof(struct nd_opt_prefix_info) % 8 == 0);
+
+ _cleanup_free_ struct nd_opt_prefix_info *buf = new(struct nd_opt_prefix_info, 1);
+ if (!buf)
+ return -ENOMEM;
+
+ usec_t valid = MIN(option->prefix.valid_lifetime,
+ usec_sub_unsigned(option->prefix.valid_until, timestamp));
+ usec_t pref = MIN3(valid,
+ option->prefix.preferred_lifetime,
+ usec_sub_unsigned(option->prefix.preferred_until, timestamp));
+
+ *buf = (struct nd_opt_prefix_info) {
+ .nd_opt_pi_type = SD_NDISC_OPTION_PREFIX_INFORMATION,
+ .nd_opt_pi_len = sizeof(struct nd_opt_prefix_info) / 8,
+ .nd_opt_pi_prefix_len = option->prefix.prefixlen,
+ .nd_opt_pi_flags_reserved = option->prefix.flags,
+ .nd_opt_pi_valid_time = usec_to_be32_sec(valid),
+ .nd_opt_pi_preferred_time = usec_to_be32_sec(pref),
+ .nd_opt_pi_prefix = option->prefix.address,
+ };
+
+ *ret = (uint8_t*) TAKE_PTR(buf);
+ return 0;
+}
+
+int ndisc_option_add_redirected_header(Set **options, size_t offset, const struct ip6_hdr *hdr) {
+ assert(options);
+
+ if (!hdr) {
+ ndisc_option_remove_by_type(*options, SD_NDISC_OPTION_REDIRECTED_HEADER);
+ return 0;
+ }
+
+ sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_REDIRECTED_HEADER);
+ if (p) {
+ if (offset != 0)
+ return -EEXIST;
+
+ memcpy(&p->hdr, hdr, sizeof(struct ip6_hdr));
+ return 0;
+ }
+
+ p = ndisc_option_new(SD_NDISC_OPTION_REDIRECTED_HEADER, offset);
+ if (!p)
+ return -ENOMEM;
+
+ /* For safety, here we copy only IPv6 header. */
+ memcpy(&p->hdr, hdr, sizeof(struct ip6_hdr));
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_parse_redirected_header(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ assert(options);
+ assert(opt);
+
+ if (len < sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr))
+ return -EBADMSG;
+
+ if (opt[0] != SD_NDISC_OPTION_REDIRECTED_HEADER)
+ return -EBADMSG;
+
+ return ndisc_option_add_redirected_header(options, offset, (const struct ip6_hdr*) (opt + sizeof(struct nd_opt_rd_hdr)));
+}
+
+static int ndisc_option_build_redirected_header(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_REDIRECTED_HEADER);
+ assert(ret);
+
+ assert_cc((sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr)) % 8 == 0);
+
+ size_t len = DIV_ROUND_UP(sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr), 8);
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ uint8_t *p;
+ p = mempcpy(buf,
+ &(const struct nd_opt_rd_hdr) {
+ .nd_opt_rh_type = SD_NDISC_OPTION_REDIRECTED_HEADER,
+ .nd_opt_rh_len = len,
+ },
+ sizeof(struct nd_opt_rd_hdr));
+ memcpy(p, &option->hdr, sizeof(struct ip6_hdr));
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
+int ndisc_option_add_mtu(Set **options, size_t offset, uint32_t mtu) {
+ assert(options);
+
+ if (mtu < IPV6_MIN_MTU)
+ return -EINVAL;
+
+ sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_MTU);
+ if (p) {
+ if (offset != 0)
+ return -EEXIST;
+
+ p->mtu = mtu;
+ return 0;
+ }
+
+ p = ndisc_option_new(SD_NDISC_OPTION_MTU, offset);
+ if (!p)
+ return -ENOMEM;
+
+ p->mtu = mtu;
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_parse_mtu(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ const struct nd_opt_mtu *pm = (const struct nd_opt_mtu*) ASSERT_PTR(opt);
+
+ assert(options);
+
+ if (len != sizeof(struct nd_opt_mtu))
+ return -EBADMSG;
+
+ if (pm->nd_opt_mtu_type != SD_NDISC_OPTION_MTU)
+ return -EBADMSG;
+
+ return ndisc_option_add_mtu(options, offset, be32toh(pm->nd_opt_mtu_mtu));
+}
+
+static int ndisc_option_build_mtu(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_MTU);
+ assert(ret);
+
+ assert_cc(sizeof(struct nd_opt_mtu) % 8 == 0);
+
+ _cleanup_free_ struct nd_opt_mtu *buf = new(struct nd_opt_mtu, 1);
+ if (!buf)
+ return -ENOMEM;
+
+ *buf = (struct nd_opt_mtu) {
+ .nd_opt_mtu_type = SD_NDISC_OPTION_MTU,
+ .nd_opt_mtu_len = sizeof(struct nd_opt_mtu) / 8,
+ .nd_opt_mtu_mtu = htobe32(option->mtu),
+ };
+
+ *ret = (uint8_t*) TAKE_PTR(buf);
+ return 0;
+}
+
+int ndisc_option_add_home_agent_internal(
+ Set **options,
+ size_t offset,
+ uint16_t preference,
+ usec_t lifetime,
+ usec_t valid_until) {
+
+ assert(options);
+
+ if (lifetime > UINT16_MAX * USEC_PER_SEC)
+ return -EINVAL;
+
+ sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_HOME_AGENT);
+ if (p) {
+ if (offset != 0)
+ return -EEXIST;
+
+ p->home_agent = (sd_ndisc_home_agent) {
+ .preference = preference,
+ .lifetime = lifetime,
+ .valid_until = valid_until,
+ };
+ return 0;
+ }
+
+ p = ndisc_option_new(SD_NDISC_OPTION_HOME_AGENT, offset);
+ if (!p)
+ return -ENOMEM;
+
+ p->home_agent = (sd_ndisc_home_agent) {
+ .preference = preference,
+ .lifetime = lifetime,
+ .valid_until = valid_until,
+ };
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_parse_home_agent(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ const struct nd_opt_home_agent_info *p = (const struct nd_opt_home_agent_info*) ASSERT_PTR(opt);
+
+ assert(options);
+
+ if (len != sizeof(struct nd_opt_home_agent_info))
+ return -EBADMSG;
+
+ if (p->nd_opt_home_agent_info_type != SD_NDISC_OPTION_HOME_AGENT)
+ return -EBADMSG;
+
+ return ndisc_option_add_home_agent(
+ options, offset,
+ be16toh(p->nd_opt_home_agent_info_preference),
+ be16_sec_to_usec(p->nd_opt_home_agent_info_lifetime, /* max_as_infinity = */ false));
+}
+
+static int ndisc_option_build_home_agent(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_HOME_AGENT);
+ assert(ret);
+
+ assert_cc(sizeof(struct nd_opt_home_agent_info) % 8 == 0);
+
+ usec_t lifetime = MIN(option->home_agent.lifetime,
+ usec_sub_unsigned(option->home_agent.valid_until, timestamp));
+
+ _cleanup_free_ struct nd_opt_home_agent_info *buf = new(struct nd_opt_home_agent_info, 1);
+ if (!buf)
+ return -ENOMEM;
+
+ *buf = (struct nd_opt_home_agent_info) {
+ .nd_opt_home_agent_info_type = SD_NDISC_OPTION_HOME_AGENT,
+ .nd_opt_home_agent_info_len = sizeof(struct nd_opt_home_agent_info) / 8,
+ .nd_opt_home_agent_info_preference = htobe16(option->home_agent.preference),
+ .nd_opt_home_agent_info_lifetime = usec_to_be16_sec(lifetime),
+ };
+
+ *ret = (uint8_t*) TAKE_PTR(buf);
+ return 0;
+}
+
+int ndisc_option_add_route_internal(
+ Set **options,
+ size_t offset,
+ uint8_t preference,
+ uint8_t prefixlen,
+ const struct in6_addr *prefix,
+ usec_t lifetime,
+ usec_t valid_until) {
+
+ assert(options);
+ assert(prefix);
+
+ if (prefixlen > 128)
+ return -EINVAL;
+
+ /* RFC 4191 section 2.3
+ * Prf (Route Preference)
+ * 2-bit signed integer. The Route Preference indicates whether to prefer the router associated with
+ * this prefix over others, when multiple identical prefixes (for different routers) have been
+ * received. If the Reserved (10) value is received, the Route Information Option MUST be ignored. */
+ if (!IN_SET(preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH))
+ return -EINVAL;
+
+ struct in6_addr addr = *prefix;
+ in6_addr_mask(&addr, prefixlen);
+
+ sd_ndisc_option *p = ndisc_option_get(
+ *options,
+ &(const sd_ndisc_option) {
+ .type = SD_NDISC_OPTION_ROUTE_INFORMATION,
+ .route.prefixlen = prefixlen,
+ .route.address = addr,
+ });
+ if (p) {
+ if (offset != 0)
+ return -EEXIST;
+
+ p->route.preference = preference;
+ p->route.lifetime = lifetime;
+ p->route.valid_until = valid_until;
+ return 0;
+ }
+
+ p = ndisc_option_new(SD_NDISC_OPTION_ROUTE_INFORMATION, offset);
+ if (!p)
+ return -ENOMEM;
+
+ p->route = (sd_ndisc_route) {
+ .preference = preference,
+ .prefixlen = prefixlen,
+ .address = addr,
+ .lifetime = lifetime,
+ .valid_until = valid_until,
+ };
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_parse_route(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ assert(options);
+ assert(opt);
+
+ if (!IN_SET(len, 1*8, 2*8, 3*8))
+ return -EBADMSG;
+
+ if (opt[0] != SD_NDISC_OPTION_ROUTE_INFORMATION)
+ return -EBADMSG;
+
+ uint8_t prefixlen = opt[2];
+ if (prefixlen > 128)
+ return -EBADMSG;
+
+ if (len < (size_t) (DIV_ROUND_UP(prefixlen, 64) + 1) * 8)
+ return -EBADMSG;
+
+ uint8_t preference = (opt[3] >> 3) & 0x03;
+ usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity = */ true);
+
+ struct in6_addr prefix;
+ memcpy(&prefix, opt + 8, len - 8);
+ in6_addr_mask(&prefix, prefixlen);
+
+ return ndisc_option_add_route(options, offset, preference, prefixlen, &prefix, lifetime);
+}
+
+static int ndisc_option_build_route(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_ROUTE_INFORMATION);
+ assert(option->route.prefixlen <= 128);
+ assert(ret);
+
+ size_t len = 1 + DIV_ROUND_UP(option->route.prefixlen, 64);
+ be32_t lifetime = usec_to_be32_sec(MIN(option->route.lifetime,
+ usec_sub_unsigned(option->route.valid_until, timestamp)));
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = SD_NDISC_OPTION_ROUTE_INFORMATION;
+ buf[1] = len;
+ buf[2] = option->route.prefixlen;
+ buf[3] = option->route.preference << 3;
+ memcpy(buf + 4, &lifetime, sizeof(be32_t));
+ memcpy_safe(buf + 8, &option->route.address, (len - 1) * 8);
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
+int ndisc_option_add_rdnss_internal(
+ Set **options,
+ size_t offset,
+ size_t n_addresses,
+ const struct in6_addr *addresses,
+ usec_t lifetime,
+ usec_t valid_until) {
+
+ assert(options);
+ assert(addresses);
+
+ if (n_addresses == 0)
+ return -EINVAL;
+
+ _cleanup_free_ struct in6_addr *addrs = newdup(struct in6_addr, addresses, n_addresses);
+ if (!addrs)
+ return -ENOMEM;
+
+ sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_RDNSS, offset);
+ if (!p)
+ return -ENOMEM;
+
+ p->rdnss = (sd_ndisc_rdnss) {
+ .n_addresses = n_addresses,
+ .addresses = TAKE_PTR(addrs),
+ .lifetime = lifetime,
+ .valid_until = valid_until,
+ };
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_parse_rdnss(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ assert(options);
+ assert(opt);
+
+ if (len < 8 + sizeof(struct in6_addr) || (len % sizeof(struct in6_addr)) != 8)
+ return -EBADMSG;
+
+ if (opt[0] != SD_NDISC_OPTION_RDNSS)
+ return -EBADMSG;
+
+ usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity = */ true);
+ size_t n_addrs = len / sizeof(struct in6_addr);
+
+ return ndisc_option_add_rdnss(options, offset, n_addrs, (const struct in6_addr*) (opt + 8), lifetime);
+}
+
+static int ndisc_option_build_rdnss(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_RDNSS);
+ assert(ret);
+
+ size_t len = option->rdnss.n_addresses * 2 + 1;
+ be32_t lifetime = usec_to_be32_sec(MIN(option->rdnss.lifetime,
+ usec_sub_unsigned(option->rdnss.valid_until, timestamp)));
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = SD_NDISC_OPTION_RDNSS;
+ buf[1] = len;
+ buf[2] = 0;
+ buf[3] = 0;
+ memcpy(buf + 4, &lifetime, sizeof(be32_t));
+ memcpy(buf + 8, option->rdnss.addresses, sizeof(struct in6_addr) * option->rdnss.n_addresses);
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
+int ndisc_option_add_flags_extension(Set **options, size_t offset, uint64_t flags) {
+ assert(options);
+
+ if ((flags & UINT64_C(0x00ffffffffffff00)) != flags)
+ return -EINVAL;
+
+ sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_FLAGS_EXTENSION);
+ if (p) {
+ if (offset != 0)
+ return -EEXIST;
+
+ p->extended_flags = flags;
+ return 0;
+ }
+
+ p = ndisc_option_new(SD_NDISC_OPTION_FLAGS_EXTENSION, offset);
+ if (!p)
+ return -ENOMEM;
+
+ p->extended_flags = flags;
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_parse_flags_extension(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ assert(options);
+ assert(opt);
+
+ if (len != 8)
+ return -EBADMSG;
+
+ if (opt[0] != SD_NDISC_OPTION_FLAGS_EXTENSION)
+ return -EBADMSG;
+
+ uint64_t flags = (unaligned_read_be64(opt) & UINT64_C(0xffffffffffff0000)) >> 8;
+ return ndisc_option_add_flags_extension(options, offset, flags);
+}
+
+static int ndisc_option_build_flags_extension(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_FLAGS_EXTENSION);
+ assert(ret);
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, 8);
+ if (!buf)
+ return 0;
+
+ unaligned_write_be64(buf, (option->extended_flags & UINT64_C(0x00ffffffffffff00)) << 8);
+ buf[0] = SD_NDISC_OPTION_FLAGS_EXTENSION;
+ buf[1] = 1;
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
+int ndisc_option_add_dnssl_internal(
+ Set **options,
+ size_t offset, char *
+ const *domains,
+ usec_t lifetime,
+ usec_t valid_until) {
+
+ int r;
+
+ assert(options);
+
+ if (strv_isempty(domains))
+ return -EINVAL;
+
+ STRV_FOREACH(s, domains) {
+ r = dns_name_is_valid(*s);
+ if (r < 0)
+ return r;
+
+ if (is_localhost(*s) || dns_name_is_root(*s))
+ return -EINVAL;
+ }
+
+ _cleanup_strv_free_ char **copy = strv_copy(domains);
+ if (!copy)
+ return -ENOMEM;
+
+ sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_DNSSL, offset);
+ if (!p)
+ return -ENOMEM;
+
+ p->dnssl = (sd_ndisc_dnssl) {
+ .domains = TAKE_PTR(copy),
+ .lifetime = lifetime,
+ .valid_until = valid_until,
+ };
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_parse_dnssl(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ int r;
+
+ assert(options);
+ assert(opt);
+
+ if (len < 2*8)
+ return -EBADMSG;
+
+ if (opt[0] != SD_NDISC_OPTION_DNSSL)
+ return -EBADMSG;
+
+ usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity = */ true);
+
+ _cleanup_strv_free_ char **l = NULL;
+ _cleanup_free_ char *e = NULL;
+ size_t n = 0;
+ for (size_t c, pos = 8; pos < len; pos += c) {
+
+ c = opt[pos];
+ pos++;
+
+ if (c == 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)) {
+ r = strv_consume(&l, TAKE_PTR(normalized));
+ if (r < 0)
+ return r;
+ }
+ }
+
+ n = 0;
+ continue;
+ }
+
+ /* Check for compression (which is not allowed) */
+ if (c > 63)
+ return -EBADMSG;
+
+ if (pos + c >= len)
+ return -EBADMSG;
+
+ if (!GREEDY_REALLOC(e, n + (n != 0) + DNS_LABEL_ESCAPED_MAX + 1U))
+ return -ENOMEM;
+
+ if (n != 0)
+ e[n++] = '.';
+
+ r = dns_label_escape((const char*) (opt + pos), c, e + n, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ n += r;
+ }
+
+ if (n > 0) /* Not properly NUL terminated */
+ return -EBADMSG;
+
+ return ndisc_option_add_dnssl(options, offset, l, lifetime);
+}
+
+ static int ndisc_option_build_dnssl(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) {
+ int r;
+
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_DNSSL);
+ assert(ret);
+
+ size_t len = 8;
+ STRV_FOREACH(s, option->dnssl.domains)
+ len += strlen(*s) + 2;
+ len = DIV_ROUND_UP(len, 8);
+
+ be32_t lifetime = usec_to_be32_sec(MIN(option->dnssl.lifetime,
+ usec_sub_unsigned(option->dnssl.valid_until, timestamp)));
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = SD_NDISC_OPTION_DNSSL;
+ buf[1] = len;
+ buf[2] = 0;
+ buf[3] = 0;
+ memcpy(buf + 4, &lifetime, sizeof(be32_t));
+
+ size_t remaining = len * 8 - 8;
+ uint8_t *p = buf + 8;
+
+ STRV_FOREACH(s, option->dnssl.domains) {
+ r = dns_name_to_wire_format(*s, p, remaining, /* canonical = */ false);
+ if (r < 0)
+ return r;
+
+ assert(remaining >= (size_t) r);
+ p += r;
+ remaining -= r;
+ }
+
+ memzero(p, remaining);
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
+int ndisc_option_add_captive_portal(Set **options, size_t offset, const char *portal) {
+ assert(options);
+
+ if (isempty(portal))
+ return -EINVAL;
+
+ if (!in_charset(portal, URI_VALID))
+ return -EINVAL;
+
+ sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_CAPTIVE_PORTAL);
+ if (p) {
+ if (offset != 0)
+ return -EEXIST;
+
+ return free_and_strdup(&p->captive_portal, portal);
+ }
+
+ _cleanup_free_ char *copy = strdup(portal);
+ if (!copy)
+ return -ENOMEM;
+
+ p = ndisc_option_new(SD_NDISC_OPTION_CAPTIVE_PORTAL, offset);
+ if (!p)
+ return -ENOMEM;
+
+ p->captive_portal = TAKE_PTR(copy);
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_parse_captive_portal(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ assert(options);
+ assert(opt);
+
+ if (len < 8)
+ return -EBADMSG;
+
+ if (opt[0] != SD_NDISC_OPTION_CAPTIVE_PORTAL)
+ return -EBADMSG;
+
+ _cleanup_free_ char *portal = memdup_suffix0(opt + 2, len - 2);
+ if (!portal)
+ return -ENOMEM;
+
+ size_t size = strlen(portal);
+ if (size == 0)
+ return -EBADMSG;
+
+ /* Check that the message is not truncated by an embedded NUL.
+ * NUL padding to a multiple of 8 is expected. */
+ if (DIV_ROUND_UP(size + 2, 8) * 8 != len && DIV_ROUND_UP(size + 3, 8) * 8 != len)
+ return -EBADMSG;
+
+ return ndisc_option_add_captive_portal(options, offset, portal);
+}
+
+static int ndisc_option_build_captive_portal(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_CAPTIVE_PORTAL);
+ assert(ret);
+
+ size_t len_portal = strlen(option->captive_portal);
+ size_t len = DIV_ROUND_UP(len_portal + 1 + 2, 8);
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = SD_NDISC_OPTION_CAPTIVE_PORTAL;
+ buf[1] = len;
+
+ uint8_t *p = mempcpy(buf + 2, option->captive_portal, len_portal);
+ size_t remaining = len * 8 - 2 - len_portal;
+
+ memzero(p, remaining);
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
+static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = {
+ [PREFIX_LENGTH_CODE_96] = 96,
+ [PREFIX_LENGTH_CODE_64] = 64,
+ [PREFIX_LENGTH_CODE_56] = 56,
+ [PREFIX_LENGTH_CODE_48] = 48,
+ [PREFIX_LENGTH_CODE_40] = 40,
+ [PREFIX_LENGTH_CODE_32] = 32,
+};
+
+int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret) {
+ for (size_t i = 0; i < ELEMENTSOF(prefix_length_code_to_prefix_length); i++)
+ if (prefix_length_code_to_prefix_length[i] == prefixlen) {
+ if (ret)
+ *ret = i;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int pref64_lifetime_and_plc_parse(uint16_t lifetime_and_plc, uint8_t *ret_prefixlen, usec_t *ret_lifetime) {
+ uint16_t plc = lifetime_and_plc & PREF64_PLC_MASK;
+ if (plc >= _PREFIX_LENGTH_CODE_MAX)
+ return -EINVAL;
+
+ if (ret_prefixlen)
+ *ret_prefixlen = prefix_length_code_to_prefix_length[plc];
+ if (ret_lifetime)
+ *ret_lifetime = (lifetime_and_plc & PREF64_SCALED_LIFETIME_MASK) * USEC_PER_SEC;
+ return 0;
+}
+
+int ndisc_option_add_prefix64_internal(
+ Set **options,
+ size_t offset,
+ uint8_t prefixlen,
+ const struct in6_addr *prefix,
+ usec_t lifetime,
+ usec_t valid_until) {
+
+ int r;
+
+ assert(options);
+ assert(prefix);
+
+ r = pref64_prefix_length_to_plc(prefixlen, NULL);
+ if (r < 0)
+ return r;
+
+ if (lifetime > PREF64_MAX_LIFETIME_USEC)
+ return -EINVAL;
+
+ struct in6_addr addr = *prefix;
+ in6_addr_mask(&addr, prefixlen);
+
+ sd_ndisc_option *p = ndisc_option_get(
+ *options,
+ &(const sd_ndisc_option) {
+ .type = SD_NDISC_OPTION_PREF64,
+ .prefix64.prefixlen = prefixlen,
+ .prefix64.prefix = addr,
+ });
+ if (p) {
+ if (offset != 0)
+ return -EEXIST;
+
+ p->prefix64.lifetime = lifetime;
+ p->prefix64.valid_until = valid_until;
+ return 0;
+ }
+
+ p = ndisc_option_new(SD_NDISC_OPTION_PREF64, offset);
+ if (!p)
+ return -ENOMEM;
+
+ p->prefix64 = (sd_ndisc_prefix64) {
+ .prefixlen = prefixlen,
+ .prefix = addr,
+ .lifetime = lifetime,
+ .valid_until = valid_until,
+ };
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_parse_prefix64(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ int r;
+
+ assert(options);
+ assert(opt);
+
+ if (len != 2*8)
+ return -EBADMSG;
+
+ if (opt[0] != SD_NDISC_OPTION_PREF64)
+ return -EBADMSG;
+
+ uint8_t prefixlen;
+ usec_t lifetime;
+ r = pref64_lifetime_and_plc_parse(unaligned_read_be16(opt + 2), &prefixlen, &lifetime);
+ if (r < 0)
+ return r;
+
+ struct in6_addr prefix;
+ memcpy(&prefix, opt + 4, len - 4);
+ in6_addr_mask(&prefix, prefixlen);
+
+ return ndisc_option_add_prefix64(options, offset, prefixlen, &prefix, lifetime);
+}
+
+static int ndisc_option_build_prefix64(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) {
+ int r;
+
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_PREF64);
+ assert(ret);
+
+ uint8_t code;
+ r = pref64_prefix_length_to_plc(option->prefix64.prefixlen, &code);
+ if (r < 0)
+ return r;
+
+ uint16_t lifetime = (uint16_t) DIV_ROUND_UP(MIN(option->prefix64.lifetime,
+ usec_sub_unsigned(option->prefix64.valid_until, timestamp)),
+ USEC_PER_SEC) & PREF64_SCALED_LIFETIME_MASK;
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, 2 * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = SD_NDISC_OPTION_PREF64;
+ buf[1] = 2;
+ unaligned_write_be16(buf + 2, lifetime | code);
+ memcpy(buf + 4, &option->prefix64.prefix, 12);
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
+static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) {
+ assert(options);
+ assert(opt);
+ assert(len > 0);
+
+ sd_ndisc_option *p = ndisc_option_new(opt[0], offset);
+ if (!p)
+ return -ENOMEM;
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_header_size(uint8_t icmp6_type) {
+ switch (icmp6_type) {
+ case ND_ROUTER_SOLICIT:
+ return sizeof(struct nd_router_solicit);
+ case ND_ROUTER_ADVERT:
+ return sizeof(struct nd_router_advert);
+ case ND_NEIGHBOR_SOLICIT:
+ return sizeof(struct nd_neighbor_solicit);
+ case ND_NEIGHBOR_ADVERT:
+ return sizeof(struct nd_neighbor_advert);
+ case ND_REDIRECT:
+ return sizeof(struct nd_redirect);
+ default:
+ return -EINVAL;
+ }
+}
+
+int ndisc_parse_options(ICMP6Packet *packet, Set **ret_options) {
+ _cleanup_set_free_ Set *options = NULL;
+ int r;
+
+ assert(packet);
+ assert(ret_options);
+
+ r = icmp6_packet_get_type(packet);
+ if (r < 0)
+ return r;
+
+ r = ndisc_header_size(r);
+ if (r < 0)
+ return -EBADMSG;
+ size_t header_size = r;
+
+ if (packet->raw_size < header_size)
+ return -EBADMSG;
+
+ for (size_t length, offset = header_size; offset < packet->raw_size; offset += length) {
+ uint8_t type;
+ const uint8_t *opt;
+
+ r = ndisc_option_parse(packet, offset, &type, &length, &opt);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse NDisc option header: %m");
+
+ switch (type) {
+ case 0:
+ r = -EBADMSG;
+ break;
+
+ case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
+ case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
+ r = ndisc_option_parse_link_layer_address(&options, offset, length, opt);
+ break;
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION:
+ r = ndisc_option_parse_prefix(&options, offset, length, opt);
+ break;
+
+ case SD_NDISC_OPTION_REDIRECTED_HEADER:
+ r = ndisc_option_parse_redirected_header(&options, offset, length, opt);
+ break;
+
+ case SD_NDISC_OPTION_MTU:
+ r = ndisc_option_parse_mtu(&options, offset, length, opt);
+ break;
+
+ case SD_NDISC_OPTION_HOME_AGENT:
+ r = ndisc_option_parse_home_agent(&options, offset, length, opt);
+ break;
+
+ case SD_NDISC_OPTION_ROUTE_INFORMATION:
+ r = ndisc_option_parse_route(&options, offset, length, opt);
+ break;
+
+ case SD_NDISC_OPTION_RDNSS:
+ r = ndisc_option_parse_rdnss(&options, offset, length, opt);
+ break;
+
+ case SD_NDISC_OPTION_FLAGS_EXTENSION:
+ r = ndisc_option_parse_flags_extension(&options, offset, length, opt);
+ break;
+
+ case SD_NDISC_OPTION_DNSSL:
+ r = ndisc_option_parse_dnssl(&options, offset, length, opt);
+ break;
+
+ case SD_NDISC_OPTION_CAPTIVE_PORTAL:
+ r = ndisc_option_parse_captive_portal(&options, offset, length, opt);
+ break;
+
+ case SD_NDISC_OPTION_PREF64:
+ r = ndisc_option_parse_prefix64(&options, offset, length, opt);
+ break;
+
+ default:
+ r = ndisc_option_parse_default(&options, offset, length, opt);
+ }
+ if (r == -ENOMEM)
+ return log_oom_debug();
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse NDisc option %u, ignoring: %m", type);
+ }
+
+ *ret_options = TAKE_PTR(options);
+ return 0;
+}
+
+int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret) {
+ assert(IN_SET(type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS));
+
+ sd_ndisc_option *p = ndisc_option_get_by_type(options, type);
+ if (!p)
+ return -ENODATA;
+
+ if (ret)
+ *ret = p->mac;
+ return 0;
+}
+
+int ndisc_send(int fd, const struct in6_addr *dst, const struct icmp6_hdr *hdr, Set *options, usec_t timestamp) {
+ int r;
+
+ assert(fd >= 0);
+ assert(dst);
+ assert(hdr);
+
+ size_t n;
+ _cleanup_free_ sd_ndisc_option **list = NULL;
+ r = set_dump_sorted(options, (void***) &list, &n);
+ if (r < 0)
+ return r;
+
+ struct iovec *iov = NULL;
+ size_t n_iov = 0;
+ CLEANUP_ARRAY(iov, n_iov, iovec_array_free);
+
+ iov = new(struct iovec, 1 + n);
+ if (!iov)
+ return -ENOMEM;
+
+ r = ndisc_header_size(hdr->icmp6_type);
+ if (r < 0)
+ return r;
+ size_t hdr_size = r;
+
+ _cleanup_free_ uint8_t *copy = newdup(uint8_t, hdr, hdr_size);
+ if (!copy)
+ return -ENOMEM;
+
+ iov[n_iov++] = IOVEC_MAKE(TAKE_PTR(copy), hdr_size);
+
+ FOREACH_ARRAY(p, list, n) {
+ _cleanup_free_ uint8_t *buf = NULL;
+ sd_ndisc_option *option = *p;
+
+ switch (option->type) {
+ case 0:
+ r = ndisc_option_build_raw(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
+ case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
+ r = ndisc_option_build_link_layer_address(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION:
+ r = ndisc_option_build_prefix(option, timestamp, &buf);
+ break;
+
+ case SD_NDISC_OPTION_REDIRECTED_HEADER:
+ r = ndisc_option_build_redirected_header(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_MTU:
+ r = ndisc_option_build_mtu(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_HOME_AGENT:
+ r = ndisc_option_build_home_agent(option, timestamp, &buf);
+ break;
+
+ case SD_NDISC_OPTION_ROUTE_INFORMATION:
+ r = ndisc_option_build_route(option, timestamp, &buf);
+ break;
+
+ case SD_NDISC_OPTION_RDNSS:
+ r = ndisc_option_build_rdnss(option, timestamp, &buf);
+ break;
+
+ case SD_NDISC_OPTION_FLAGS_EXTENSION:
+ r = ndisc_option_build_flags_extension(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_DNSSL:
+ r = ndisc_option_build_dnssl(option, timestamp, &buf);
+ break;
+
+ case SD_NDISC_OPTION_CAPTIVE_PORTAL:
+ r = ndisc_option_build_captive_portal(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_PREF64:
+ r = ndisc_option_build_prefix64(option, timestamp, &buf);
+ break;
+
+ default:
+ continue;
+ }
+ if (r == -ENOMEM)
+ return log_oom_debug();
+ if (r < 0)
+ log_debug_errno(r, "Failed to build NDisc option %u, ignoring: %m", option->type);
+
+ iov[n_iov++] = IOVEC_MAKE(buf, buf[1] * 8);
+ TAKE_PTR(buf);
+ }
+
+ return icmp6_send(fd, dst, iov, n_iov);
+}
diff --git a/src/libsystemd-network/ndisc-option.h b/src/libsystemd-network/ndisc-option.h
new file mode 100644
index 0000000..d7bd861
--- /dev/null
+++ b/src/libsystemd-network/ndisc-option.h
@@ -0,0 +1,330 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <net/ethernet.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <sys/uio.h>
+
+#include "sd-ndisc-protocol.h"
+
+#include "icmp6-packet.h"
+#include "macro.h"
+#include "set.h"
+#include "time-util.h"
+
+typedef struct sd_ndisc_raw {
+ uint8_t *bytes;
+ size_t length;
+} sd_ndisc_raw;
+
+/* Mostly equivalent to struct nd_opt_prefix_info, but using usec_t. */
+typedef struct sd_ndisc_prefix {
+ uint8_t flags;
+ uint8_t prefixlen;
+ struct in6_addr address;
+ usec_t valid_lifetime;
+ usec_t preferred_lifetime;
+ /* timestamp in CLOCK_BOOTTIME, used when sending option for adjusting lifetime. */
+ usec_t valid_until;
+ usec_t preferred_until;
+} sd_ndisc_prefix;
+
+typedef struct sd_ndisc_home_agent {
+ uint16_t preference;
+ usec_t lifetime;
+ usec_t valid_until;
+} sd_ndisc_home_agent;
+
+typedef struct sd_ndisc_route {
+ uint8_t preference;
+ uint8_t prefixlen;
+ struct in6_addr address;
+ usec_t lifetime;
+ usec_t valid_until;
+} sd_ndisc_route;
+
+typedef struct sd_ndisc_rdnss {
+ size_t n_addresses;
+ struct in6_addr *addresses;
+ usec_t lifetime;
+ usec_t valid_until;
+} sd_ndisc_rdnss;
+
+typedef struct sd_ndisc_dnssl {
+ char **domains;
+ usec_t lifetime;
+ usec_t valid_until;
+} sd_ndisc_dnssl;
+
+typedef struct sd_ndisc_prefix64 {
+ uint8_t prefixlen;
+ struct in6_addr prefix;
+ usec_t lifetime;
+ usec_t valid_until;
+} sd_ndisc_prefix64;
+
+typedef struct sd_ndisc_option {
+ uint8_t type;
+ size_t offset;
+
+ union {
+ sd_ndisc_raw raw; /* for testing or unsupported options */
+ struct ether_addr mac; /* SD_NDISC_OPTION_SOURCE_LL_ADDRESS or SD_NDISC_OPTION_TARGET_LL_ADDRESS */
+ sd_ndisc_prefix prefix; /* SD_NDISC_OPTION_PREFIX_INFORMATION */
+ struct ip6_hdr hdr; /* SD_NDISC_OPTION_REDIRECTED_HEADER */
+ uint32_t mtu; /* SD_NDISC_OPTION_MTU */
+ sd_ndisc_home_agent home_agent; /* SD_NDISC_OPTION_HOME_AGENT */
+ sd_ndisc_route route; /* SD_NDISC_OPTION_ROUTE_INFORMATION */
+ sd_ndisc_rdnss rdnss; /* SD_NDISC_OPTION_RDNSS */
+ uint64_t extended_flags; /* SD_NDISC_OPTION_FLAGS_EXTENSION */
+ sd_ndisc_dnssl dnssl; /* SD_NDISC_OPTION_DNSSL */
+ char *captive_portal; /* SD_NDISC_OPTION_CAPTIVE_PORTAL */
+ sd_ndisc_prefix64 prefix64; /* SD_NDISC_OPTION_PREF64 */
+ };
+} sd_ndisc_option;
+
+/* RFC 8781: PREF64 or (NAT64 prefix) */
+#define PREF64_SCALED_LIFETIME_MASK 0xfff8
+#define PREF64_PLC_MASK 0x0007
+#define PREF64_MAX_LIFETIME_USEC (65528 * USEC_PER_SEC)
+
+typedef enum PrefixLengthCode {
+ PREFIX_LENGTH_CODE_96,
+ PREFIX_LENGTH_CODE_64,
+ PREFIX_LENGTH_CODE_56,
+ PREFIX_LENGTH_CODE_48,
+ PREFIX_LENGTH_CODE_40,
+ PREFIX_LENGTH_CODE_32,
+ _PREFIX_LENGTH_CODE_MAX,
+ _PREFIX_LENGTH_CODE_INVALID = -EINVAL,
+} PrefixLengthCode;
+
+/* rfc8781: section 4 - Scaled Lifetime: 13-bit unsigned integer. PREFIX_LEN (Prefix Length Code): 3-bit unsigned integer */
+struct nd_opt_prefix64_info {
+ uint8_t type;
+ uint8_t length;
+ uint16_t lifetime_and_plc;
+ uint8_t prefix[12];
+} _packed_;
+
+int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret);
+
+sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option);
+
+int ndisc_option_parse(
+ ICMP6Packet *p,
+ size_t offset,
+ uint8_t *ret_type,
+ size_t *ret_len,
+ const uint8_t **ret_opt);
+
+int ndisc_parse_options(ICMP6Packet *p, Set **ret_options);
+
+static inline sd_ndisc_option* ndisc_option_get(Set *options, const sd_ndisc_option *p) {
+ return set_get(options, ASSERT_PTR(p));
+}
+static inline sd_ndisc_option* ndisc_option_get_by_type(Set *options, uint8_t type) {
+ return ndisc_option_get(options, &(const sd_ndisc_option) { .type = type });
+}
+int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret);
+
+static inline void ndisc_option_remove(Set *options, const sd_ndisc_option *p) {
+ ndisc_option_free(set_remove(options, ASSERT_PTR(p)));
+}
+static inline void ndisc_option_remove_by_type(Set *options, uint8_t type) {
+ ndisc_option_remove(options, &(const sd_ndisc_option) { .type = type });
+}
+
+int ndisc_option_set_raw(
+ Set **options,
+ size_t length,
+ const uint8_t *bytes);
+int ndisc_option_add_link_layer_address(
+ Set **options,
+ uint8_t type,
+ size_t offset,
+ const struct ether_addr *mac);
+static inline int ndisc_option_set_link_layer_address(
+ Set **options,
+ uint8_t type,
+ const struct ether_addr *mac) {
+ return ndisc_option_add_link_layer_address(options, type, 0, mac);
+}
+int ndisc_option_add_prefix_internal(
+ Set **options,
+ size_t offset,
+ uint8_t flags,
+ uint8_t prefixlen,
+ const struct in6_addr *address,
+ usec_t valid_lifetime,
+ usec_t preferred_lifetime,
+ usec_t valid_until,
+ usec_t preferred_until);
+static inline int ndisc_option_add_prefix(
+ Set **options,
+ size_t offset,
+ uint8_t flags,
+ uint8_t prefixlen,
+ const struct in6_addr *address,
+ usec_t valid_lifetime,
+ usec_t preferred_lifetime) {
+ return ndisc_option_add_prefix_internal(options, offset, flags, prefixlen, address,
+ valid_lifetime, preferred_lifetime,
+ USEC_INFINITY, USEC_INFINITY);
+}
+static inline int ndisc_option_set_prefix(
+ Set **options,
+ uint8_t flags,
+ uint8_t prefixlen,
+ const struct in6_addr *address,
+ usec_t valid_lifetime,
+ usec_t preferred_lifetime,
+ usec_t valid_until,
+ usec_t preferred_until) {
+ return ndisc_option_add_prefix_internal(options, 0, flags, prefixlen, address,
+ valid_lifetime, preferred_lifetime,
+ valid_until, preferred_until);
+}
+int ndisc_option_add_redirected_header(
+ Set **options,
+ size_t offset,
+ const struct ip6_hdr *hdr);
+int ndisc_option_add_mtu(
+ Set **options,
+ size_t offset,
+ uint32_t mtu);
+static inline int ndisc_option_set_mtu(
+ Set **options,
+ uint32_t mtu) {
+ return ndisc_option_add_mtu(options, 0, mtu);
+}
+int ndisc_option_add_home_agent_internal(
+ Set **options,
+ size_t offset,
+ uint16_t preference,
+ usec_t lifetime,
+ usec_t valid_until);
+static inline int ndisc_option_add_home_agent(
+ Set **options,
+ size_t offset,
+ uint16_t preference,
+ usec_t lifetime) {
+ return ndisc_option_add_home_agent_internal(options, offset, preference, lifetime, USEC_INFINITY);
+}
+static inline int ndisc_option_set_home_agent(
+ Set **options,
+ uint16_t preference,
+ usec_t lifetime,
+ usec_t valid_until) {
+ return ndisc_option_add_home_agent_internal(options, 0, preference, lifetime, valid_until);
+}
+int ndisc_option_add_route_internal(
+ Set **options,
+ size_t offset,
+ uint8_t preference,
+ uint8_t prefixlen,
+ const struct in6_addr *prefix,
+ usec_t lifetime,
+ usec_t valid_until);
+static inline int ndisc_option_add_route(
+ Set **options,
+ size_t offset,
+ uint8_t preference,
+ uint8_t prefixlen,
+ const struct in6_addr *prefix,
+ usec_t lifetime) {
+ return ndisc_option_add_route_internal(options, offset, preference, prefixlen, prefix, lifetime, USEC_INFINITY);
+}
+static inline int ndisc_option_set_route(
+ Set **options,
+ uint8_t preference,
+ uint8_t prefixlen,
+ const struct in6_addr *prefix,
+ usec_t lifetime,
+ usec_t valid_until) {
+ return ndisc_option_add_route_internal(options, 0, preference, prefixlen, prefix, lifetime, valid_until);
+}
+int ndisc_option_add_rdnss_internal(
+ Set **options,
+ size_t offset,
+ size_t n_addresses,
+ const struct in6_addr *addresses,
+ usec_t lifetime,
+ usec_t valid_until);
+static inline int ndisc_option_add_rdnss(
+ Set **options,
+ size_t offset,
+ size_t n_addresses,
+ const struct in6_addr *addresses,
+ usec_t lifetime) {
+ return ndisc_option_add_rdnss_internal(options, offset, n_addresses, addresses, lifetime, USEC_INFINITY);
+}
+static inline int ndisc_option_set_rdnss(
+ Set **options,
+ size_t n_addresses,
+ const struct in6_addr *addresses,
+ usec_t lifetime,
+ usec_t valid_until) {
+ return ndisc_option_add_rdnss_internal(options, 0, n_addresses, addresses, lifetime, valid_until);
+}
+int ndisc_option_add_flags_extension(
+ Set **options,
+ size_t offset,
+ uint64_t flags);
+int ndisc_option_add_dnssl_internal(
+ Set **options,
+ size_t offset,
+ char * const *domains,
+ usec_t lifetime,
+ usec_t valid_until);
+static inline int ndisc_option_add_dnssl(
+ Set **options,
+ size_t offset,
+ char * const *domains,
+ usec_t lifetime) {
+ return ndisc_option_add_dnssl_internal(options, offset, domains, lifetime, USEC_INFINITY);
+}
+static inline int ndisc_option_set_dnssl(
+ Set **options,
+ char * const *domains,
+ usec_t lifetime,
+ usec_t valid_until) {
+ return ndisc_option_add_dnssl_internal(options, 0, domains, lifetime, valid_until);
+}
+int ndisc_option_add_captive_portal(
+ Set **options,
+ size_t offset,
+ const char *portal);
+static inline int ndisc_option_set_captive_portal(
+ Set **options,
+ const char *portal) {
+ return ndisc_option_add_captive_portal(options, 0, portal);
+}
+int ndisc_option_add_prefix64_internal(
+ Set **options,
+ size_t offset,
+ uint8_t prefixlen,
+ const struct in6_addr *prefix,
+ usec_t lifetime,
+ usec_t valid_until);
+static inline int ndisc_option_add_prefix64(
+ Set **options,
+ size_t offset,
+ uint8_t prefixlen,
+ const struct in6_addr *prefix,
+ usec_t lifetime) {
+ return ndisc_option_add_prefix64_internal(options, offset, prefixlen, prefix, lifetime, USEC_INFINITY);
+}
+static inline int ndisc_option_set_prefix64(
+ Set **options,
+ uint8_t prefixlen,
+ const struct in6_addr *prefix,
+ usec_t lifetime,
+ usec_t valid_until) {
+ return ndisc_option_add_prefix64_internal(options, 0, prefixlen, prefix, lifetime, valid_until);
+}
+
+int ndisc_send(int fd, const struct in6_addr *dst, const struct icmp6_hdr *hdr, Set *options, usec_t timestamp);
diff --git a/src/libsystemd-network/ndisc-protocol.c b/src/libsystemd-network/ndisc-protocol.c
deleted file mode 100644
index fae4a58..0000000
--- a/src/libsystemd-network/ndisc-protocol.c
+++ /dev/null
@@ -1,34 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include "ndisc-protocol.h"
-
-static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = {
- [PREFIX_LENGTH_CODE_96] = 96,
- [PREFIX_LENGTH_CODE_64] = 64,
- [PREFIX_LENGTH_CODE_56] = 56,
- [PREFIX_LENGTH_CODE_48] = 48,
- [PREFIX_LENGTH_CODE_40] = 40,
- [PREFIX_LENGTH_CODE_32] = 32,
-};
-
-int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret) {
- plc &= PREF64_PLC_MASK;
- if (plc >= _PREFIX_LENGTH_CODE_MAX)
- return -EINVAL;
-
- if (ret)
- *ret = prefix_length_code_to_prefix_length[plc];
- return 0;
-}
-
-int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret) {
- assert(ret);
-
- for (size_t i = 0; i < ELEMENTSOF(prefix_length_code_to_prefix_length); i++)
- if (prefix_length_code_to_prefix_length[i] == prefixlen) {
- *ret = i;
- return 0;
- }
-
- return -EINVAL;
-}
diff --git a/src/libsystemd-network/ndisc-protocol.h b/src/libsystemd-network/ndisc-protocol.h
deleted file mode 100644
index 8e403e3..0000000
--- a/src/libsystemd-network/ndisc-protocol.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include "time-util.h"
-
-/* RFC 8781: PREF64 or (NAT64 prefix) */
-#define PREF64_SCALED_LIFETIME_MASK 0xfff8
-#define PREF64_PLC_MASK 0x0007
-#define PREF64_MAX_LIFETIME_USEC (65528 * USEC_PER_SEC)
-
-typedef enum PrefixLengthCode {
- PREFIX_LENGTH_CODE_96,
- PREFIX_LENGTH_CODE_64,
- PREFIX_LENGTH_CODE_56,
- PREFIX_LENGTH_CODE_48,
- PREFIX_LENGTH_CODE_40,
- PREFIX_LENGTH_CODE_32,
- _PREFIX_LENGTH_CODE_MAX,
- _PREFIX_LENGTH_CODE_INVALID = -EINVAL,
-} PrefixLengthCode;
-
-/* rfc8781: section 4 - Scaled Lifetime: 13-bit unsigned integer. PREFIX_LEN (Prefix Length Code): 3-bit unsigned integer */
-struct nd_opt_prefix64_info {
- uint8_t type;
- uint8_t length;
- uint16_t lifetime_and_plc;
- uint8_t prefix[12];
-} __attribute__((__packed__));
-
-int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret);
-int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret);
diff --git a/src/libsystemd-network/ndisc-redirect-internal.h b/src/libsystemd-network/ndisc-redirect-internal.h
new file mode 100644
index 0000000..e35263a
--- /dev/null
+++ b/src/libsystemd-network/ndisc-redirect-internal.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-ndisc.h"
+
+#include "icmp6-packet.h"
+#include "set.h"
+
+struct sd_ndisc_redirect {
+ unsigned n_ref;
+
+ ICMP6Packet *packet;
+
+ struct in6_addr target_address;
+ struct in6_addr destination_address;
+
+ Set *options;
+};
+
+sd_ndisc_redirect* ndisc_redirect_new(ICMP6Packet *packet);
+int ndisc_redirect_parse(sd_ndisc *nd, sd_ndisc_redirect *rd);
diff --git a/src/libsystemd-network/ndisc-router-internal.h b/src/libsystemd-network/ndisc-router-internal.h
new file mode 100644
index 0000000..6df72fd
--- /dev/null
+++ b/src/libsystemd-network/ndisc-router-internal.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include "sd-ndisc.h"
+
+#include "icmp6-packet.h"
+#include "ndisc-option.h"
+#include "time-util.h"
+
+struct sd_ndisc_router {
+ unsigned n_ref;
+
+ ICMP6Packet *packet;
+
+ /* From RA header */
+ uint8_t hop_limit;
+ uint8_t flags;
+ uint8_t preference;
+ usec_t lifetime_usec;
+ usec_t reachable_time_usec;
+ usec_t retransmission_time_usec;
+
+ /* Options */
+ Set *options;
+ Iterator iterator;
+ sd_ndisc_option *current_option;
+};
+
+sd_ndisc_router* ndisc_router_new(ICMP6Packet *packet);
+int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt);
+
+int ndisc_router_flags_to_string(uint64_t flags, char **ret);
+const char* ndisc_router_preference_to_string(int s) _const_;
diff --git a/src/libsystemd-network/ndisc-router-solicit-internal.h b/src/libsystemd-network/ndisc-router-solicit-internal.h
new file mode 100644
index 0000000..6f0b0af
--- /dev/null
+++ b/src/libsystemd-network/ndisc-router-solicit-internal.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-radv.h"
+
+#include "icmp6-packet.h"
+#include "set.h"
+
+struct sd_ndisc_router_solicit {
+ unsigned n_ref;
+
+ ICMP6Packet *packet;
+
+ Set *options;
+};
+
+sd_ndisc_router_solicit* ndisc_router_solicit_new(ICMP6Packet *packet);
+int ndisc_router_solicit_parse(sd_radv *ra, sd_ndisc_router_solicit *rs);
diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c
deleted file mode 100644
index 5162df7..0000000
--- a/src/libsystemd-network/ndisc-router.c
+++ /dev/null
@@ -1,913 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/***
- Copyright © 2014 Intel Corporation. All rights reserved.
-***/
-
-#include <netinet/icmp6.h>
-
-#include "sd-ndisc.h"
-
-#include "alloc-util.h"
-#include "dns-domain.h"
-#include "hostname-util.h"
-#include "memory-util.h"
-#include "missing_network.h"
-#include "ndisc-internal.h"
-#include "ndisc-protocol.h"
-#include "ndisc-router.h"
-#include "strv.h"
-
-DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_router, sd_ndisc_router, mfree);
-
-sd_ndisc_router *ndisc_router_new(size_t raw_size) {
- sd_ndisc_router *rt;
-
- if (raw_size > SIZE_MAX - ALIGN(sizeof(sd_ndisc_router)))
- return NULL;
-
- rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size);
- if (!rt)
- return NULL;
-
- rt->raw_size = raw_size;
- rt->n_ref = 1;
-
- return rt;
-}
-
-int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret) {
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- if (in6_addr_is_null(&rt->address))
- return -ENODATA;
-
- *ret = rt->address;
- return 0;
-}
-
-int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) {
- assert_return(rt, -EINVAL);
- assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
- assert_return(clock_supported(clock), -EOPNOTSUPP);
- assert_return(ret, -EINVAL);
-
- if (!triple_timestamp_is_set(&rt->timestamp))
- return -ENODATA;
-
- *ret = triple_timestamp_by_clock(&rt->timestamp, clock);
- return 0;
-}
-
-#define DEFINE_GET_TIMESTAMP(name) \
- int sd_ndisc_router_##name##_timestamp( \
- sd_ndisc_router *rt, \
- clockid_t clock, \
- uint64_t *ret) { \
- \
- usec_t s, t; \
- int r; \
- \
- assert_return(rt, -EINVAL); \
- assert_return(ret, -EINVAL); \
- \
- r = sd_ndisc_router_##name(rt, &s); \
- if (r < 0) \
- return r; \
- \
- r = sd_ndisc_router_get_timestamp(rt, clock, &t); \
- if (r < 0) \
- return r; \
- \
- *ret = time_span_to_stamp(s, t); \
- return 0; \
- }
-
-DEFINE_GET_TIMESTAMP(get_lifetime);
-DEFINE_GET_TIMESTAMP(prefix_get_valid_lifetime);
-DEFINE_GET_TIMESTAMP(prefix_get_preferred_lifetime);
-DEFINE_GET_TIMESTAMP(route_get_lifetime);
-DEFINE_GET_TIMESTAMP(rdnss_get_lifetime);
-DEFINE_GET_TIMESTAMP(dnssl_get_lifetime);
-DEFINE_GET_TIMESTAMP(prefix64_get_lifetime);
-
-int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size) {
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
- assert_return(ret_size, -EINVAL);
-
- *ret = NDISC_ROUTER_RAW(rt);
- *ret_size = rt->raw_size;
-
- return 0;
-}
-
-static bool pref64_option_verify(const struct nd_opt_prefix64_info *p, size_t length) {
- uint16_t lifetime_and_plc;
-
- assert(p);
-
- if (length != sizeof(struct nd_opt_prefix64_info))
- return false;
-
- lifetime_and_plc = be16toh(p->lifetime_and_plc);
- if (pref64_plc_to_prefix_length(lifetime_and_plc, NULL) < 0)
- return false;
-
- return true;
-}
-
-int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) {
- struct nd_router_advert *a;
- const uint8_t *p;
- bool has_mtu = false, has_flag_extension = false;
- size_t left;
-
- assert(rt);
-
- if (rt->raw_size < sizeof(struct nd_router_advert))
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "Too small to be a router advertisement, ignoring.");
-
- /* Router advertisement packets are neatly aligned to 64-bit boundaries, hence we can access them directly */
- a = NDISC_ROUTER_RAW(rt);
-
- if (a->nd_ra_type != ND_ROUTER_ADVERT)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "Received ND packet that is not a router advertisement, ignoring.");
-
- if (a->nd_ra_code != 0)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "Received ND packet with wrong RA code, ignoring.");
-
- rt->hop_limit = a->nd_ra_curhoplimit;
- rt->flags = a->nd_ra_flags_reserved; /* the first 8 bits */
- rt->lifetime_usec = be16_sec_to_usec(a->nd_ra_router_lifetime, /* max_as_infinity = */ false);
- rt->icmp6_ratelimit_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false);
-
- rt->preference = (rt->flags >> 3) & 3;
- if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
- rt->preference = SD_NDISC_PREFERENCE_MEDIUM;
-
- p = (const uint8_t*) NDISC_ROUTER_RAW(rt) + sizeof(struct nd_router_advert);
- left = rt->raw_size - sizeof(struct nd_router_advert);
-
- for (;;) {
- uint8_t type;
- size_t length;
-
- if (left == 0)
- break;
-
- if (left < 2)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "Option lacks header, ignoring datagram.");
-
- type = p[0];
- length = p[1] * 8;
-
- if (length == 0)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "Zero-length option, ignoring datagram.");
- if (left < length)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "Option truncated, ignoring datagram.");
-
- switch (type) {
-
- case SD_NDISC_OPTION_PREFIX_INFORMATION:
-
- if (length != 4*8)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "Prefix option of invalid size, ignoring datagram.");
-
- if (p[2] > 128)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "Bad prefix length, ignoring datagram.");
-
- break;
-
- case SD_NDISC_OPTION_MTU: {
- uint32_t m;
-
- if (has_mtu) {
- log_ndisc(nd, "MTU option specified twice, ignoring.");
- break;
- }
-
- if (length != 8)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "MTU option of invalid size, ignoring datagram.");
-
- m = be32toh(*(uint32_t*) (p + 4));
- if (m >= IPV6_MIN_MTU) /* ignore invalidly small MTUs */
- rt->mtu = m;
-
- has_mtu = true;
- break;
- }
-
- case SD_NDISC_OPTION_ROUTE_INFORMATION:
- if (length < 1*8 || length > 3*8)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "Route information option of invalid size, ignoring datagram.");
-
- if (p[2] > 128)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "Bad route prefix length, ignoring datagram.");
-
- break;
-
- case SD_NDISC_OPTION_RDNSS:
- if (length < 3*8 || (length % (2*8)) != 1*8)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), "RDNSS option has invalid size.");
-
- break;
-
- case SD_NDISC_OPTION_FLAGS_EXTENSION:
-
- if (has_flag_extension) {
- log_ndisc(nd, "Flags extension option specified twice, ignoring.");
- break;
- }
-
- if (length < 1*8)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "Flags extension option has invalid size.");
-
- /* Add in the additional flags bits */
- rt->flags |=
- ((uint64_t) p[2] << 8) |
- ((uint64_t) p[3] << 16) |
- ((uint64_t) p[4] << 24) |
- ((uint64_t) p[5] << 32) |
- ((uint64_t) p[6] << 40) |
- ((uint64_t) p[7] << 48);
-
- has_flag_extension = true;
- break;
-
- case SD_NDISC_OPTION_DNSSL:
- if (length < 2*8)
- return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "DNSSL option has invalid size.");
-
- break;
- case SD_NDISC_OPTION_PREF64: {
- if (!pref64_option_verify((struct nd_opt_prefix64_info *) p, length))
- log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
- "PREF64 prefix has invalid prefix length.");
- break;
- }}
-
- p += length, left -= length;
- }
-
- rt->rindex = sizeof(struct nd_router_advert);
- return 0;
-}
-
-int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) {
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- *ret = rt->hop_limit;
- return 0;
-}
-
-int sd_ndisc_router_get_icmp6_ratelimit(sd_ndisc_router *rt, uint64_t *ret) {
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- *ret = rt->icmp6_ratelimit_usec;
- return 0;
-}
-
-int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret) {
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- *ret = rt->flags;
- return 0;
-}
-
-int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- *ret = rt->lifetime_usec;
- return 0;
-}
-
-int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) {
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- *ret = rt->preference;
- return 0;
-}
-
-int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) {
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- if (rt->mtu <= 0)
- return -ENODATA;
-
- *ret = rt->mtu;
- return 0;
-}
-
-int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) {
- assert_return(rt, -EINVAL);
-
- assert(rt->raw_size >= sizeof(struct nd_router_advert));
- rt->rindex = sizeof(struct nd_router_advert);
-
- return rt->rindex < rt->raw_size;
-}
-
-int sd_ndisc_router_option_next(sd_ndisc_router *rt) {
- size_t length;
-
- assert_return(rt, -EINVAL);
-
- if (rt->rindex == rt->raw_size) /* EOF */
- return -ESPIPE;
-
- if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
- return -EBADMSG;
-
- length = NDISC_ROUTER_OPTION_LENGTH(rt);
- if (rt->rindex + length > rt->raw_size)
- return -EBADMSG;
-
- rt->rindex += length;
- return rt->rindex < rt->raw_size;
-}
-
-int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) {
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- if (rt->rindex == rt->raw_size) /* EOF */
- return -ESPIPE;
-
- if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
- return -EBADMSG;
-
- *ret = NDISC_ROUTER_OPTION_TYPE(rt);
- return 0;
-}
-
-int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) {
- uint8_t k;
- int r;
-
- assert_return(rt, -EINVAL);
-
- r = sd_ndisc_router_option_get_type(rt, &k);
- if (r < 0)
- return r;
-
- return type == k;
-}
-
-int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size) {
- size_t length;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
- assert_return(ret_size, -EINVAL);
-
- /* Note that this returns the full option, including the option header */
-
- if (rt->rindex + 2 > rt->raw_size)
- return -EBADMSG;
-
- length = NDISC_ROUTER_OPTION_LENGTH(rt);
- if (rt->rindex + length > rt->raw_size)
- return -EBADMSG;
-
- *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
- *ret_size = length;
-
- return 0;
-}
-
-static int get_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix_info **ret) {
- struct nd_opt_prefix_info *ri;
- size_t length;
- int r;
-
- assert(rt);
- assert(ret);
-
- r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREFIX_INFORMATION);
- if (r < 0)
- return r;
- if (r == 0)
- return -EMEDIUMTYPE;
-
- length = NDISC_ROUTER_OPTION_LENGTH(rt);
- if (length != sizeof(struct nd_opt_prefix_info))
- return -EBADMSG;
-
- ri = (struct nd_opt_prefix_info*) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex);
- if (ri->nd_opt_pi_prefix_len > 128)
- return -EBADMSG;
-
- *ret = ri;
- return 0;
-}
-
-int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
- struct nd_opt_prefix_info *ri;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_prefix_info(rt, &ri);
- if (r < 0)
- return r;
-
- *ret = be32_sec_to_usec(ri->nd_opt_pi_valid_time, /* max_as_infinity = */ true);
- return 0;
-}
-
-int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
- struct nd_opt_prefix_info *pi;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_prefix_info(rt, &pi);
- if (r < 0)
- return r;
-
- *ret = be32_sec_to_usec(pi->nd_opt_pi_preferred_time, /* max_as_infinity = */ true);
- return 0;
-}
-
-int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret) {
- struct nd_opt_prefix_info *pi;
- uint8_t flags;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_prefix_info(rt, &pi);
- if (r < 0)
- return r;
-
- flags = pi->nd_opt_pi_flags_reserved;
-
- if ((flags & ND_OPT_PI_FLAG_AUTO) && (pi->nd_opt_pi_prefix_len != 64)) {
- log_ndisc(NULL, "Invalid prefix length, ignoring prefix for stateless autoconfiguration.");
- flags &= ~ND_OPT_PI_FLAG_AUTO;
- }
-
- *ret = flags;
- return 0;
-}
-
-int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret) {
- struct nd_opt_prefix_info *pi;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_prefix_info(rt, &pi);
- if (r < 0)
- return r;
-
- *ret = pi->nd_opt_pi_prefix;
- return 0;
-}
-
-int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
- struct nd_opt_prefix_info *pi;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_prefix_info(rt, &pi);
- if (r < 0)
- return r;
-
- if (pi->nd_opt_pi_prefix_len > 128)
- return -EBADMSG;
-
- *ret = pi->nd_opt_pi_prefix_len;
- return 0;
-}
-
-static int get_route_info(sd_ndisc_router *rt, uint8_t **ret) {
- uint8_t *ri;
- size_t length;
- int r;
-
- assert(rt);
- assert(ret);
-
- r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_ROUTE_INFORMATION);
- if (r < 0)
- return r;
- if (r == 0)
- return -EMEDIUMTYPE;
-
- length = NDISC_ROUTER_OPTION_LENGTH(rt);
- if (length < 1*8 || length > 3*8)
- return -EBADMSG;
-
- ri = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
-
- if (ri[2] > 128)
- return -EBADMSG;
-
- *ret = ri;
- return 0;
-}
-
-int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
- uint8_t *ri;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_route_info(rt, &ri);
- if (r < 0)
- return r;
-
- *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true);
- return 0;
-}
-
-int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret) {
- uint8_t *ri;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_route_info(rt, &ri);
- if (r < 0)
- return r;
-
- zero(*ret);
- memcpy(ret, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8);
-
- return 0;
-}
-
-int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
- uint8_t *ri;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_route_info(rt, &ri);
- if (r < 0)
- return r;
-
- *ret = ri[2];
- return 0;
-}
-
-int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret) {
- uint8_t *ri;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_route_info(rt, &ri);
- if (r < 0)
- return r;
-
- if (!IN_SET((ri[3] >> 3) & 3, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH))
- return -EOPNOTSUPP;
-
- *ret = (ri[3] >> 3) & 3;
- return 0;
-}
-
-static int get_rdnss_info(sd_ndisc_router *rt, uint8_t **ret) {
- size_t length;
- int r;
-
- assert(rt);
- assert(ret);
-
- r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS);
- if (r < 0)
- return r;
- if (r == 0)
- return -EMEDIUMTYPE;
-
- length = NDISC_ROUTER_OPTION_LENGTH(rt);
- if (length < 3*8 || (length % (2*8)) != 1*8)
- return -EBADMSG;
-
- *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
- return 0;
-}
-
-int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) {
- uint8_t *ri;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_rdnss_info(rt, &ri);
- if (r < 0)
- return r;
-
- *ret = (const struct in6_addr*) (ri + 8);
- return (NDISC_ROUTER_OPTION_LENGTH(rt) - 8) / 16;
-}
-
-int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
- uint8_t *ri;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_rdnss_info(rt, &ri);
- if (r < 0)
- return r;
-
- *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true);
- return 0;
-}
-
-static int get_dnssl_info(sd_ndisc_router *rt, uint8_t **ret) {
- size_t length;
- int r;
-
- assert(rt);
- assert(ret);
-
- r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_DNSSL);
- if (r < 0)
- return r;
- if (r == 0)
- return -EMEDIUMTYPE;
-
- length = NDISC_ROUTER_OPTION_LENGTH(rt);
- if (length < 2*8)
- return -EBADMSG;
-
- *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
- return 0;
-}
-
-int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) {
- _cleanup_strv_free_ char **l = NULL;
- _cleanup_free_ char *e = NULL;
- size_t n = 0, left;
- uint8_t *ri, *p;
- bool first = true;
- int r;
- unsigned k = 0;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_dnssl_info(rt, &ri);
- if (r < 0)
- return r;
-
- p = ri + 8;
- left = NDISC_ROUTER_OPTION_LENGTH(rt) - 8;
-
- for (;;) {
- if (left == 0) {
-
- if (n > 0) /* Not properly NUL terminated */
- return -EBADMSG;
-
- break;
- }
-
- if (*p == 0) {
- /* Found NUL termination */
-
- if (n > 0) {
- _cleanup_free_ char *normalized = NULL;
-
- e[n] = 0;
- r = dns_name_normalize(e, 0, &normalized);
- if (r < 0)
- return r;
-
- /* Ignore the root domain name or "localhost" and friends */
- if (!is_localhost(normalized) &&
- !dns_name_is_root(normalized)) {
-
- if (strv_push(&l, normalized) < 0)
- return -ENOMEM;
-
- normalized = NULL;
- k++;
- }
- }
-
- n = 0;
- first = true;
- p++, left--;
- continue;
- }
-
- /* Check for compression (which is not allowed) */
- if (*p > 63)
- return -EBADMSG;
-
- if (1U + *p + 1U > left)
- return -EBADMSG;
-
- if (!GREEDY_REALLOC(e, n + !first + DNS_LABEL_ESCAPED_MAX + 1U))
- return -ENOMEM;
-
- if (first)
- first = false;
- else
- e[n++] = '.';
-
- r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX);
- if (r < 0)
- return r;
-
- n += r;
-
- left -= 1 + *p;
- p += 1 + *p;
- }
-
- if (strv_isempty(l)) {
- *ret = NULL;
- return 0;
- }
-
- *ret = TAKE_PTR(l);
-
- return k;
-}
-
-int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
- uint8_t *ri;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_dnssl_info(rt, &ri);
- if (r < 0)
- return r;
-
- *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true);
- return 0;
-}
-
-int sd_ndisc_router_captive_portal_get_uri(sd_ndisc_router *rt, const char **ret, size_t *ret_size) {
- int r;
- const char *nd_opt_captive_portal;
- size_t length;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
- assert_return(ret_size, -EINVAL);
-
- r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_CAPTIVE_PORTAL);
- if (r < 0)
- return r;
- if (r == 0)
- return -EMEDIUMTYPE;
-
- r = sd_ndisc_router_option_get_raw(rt, (void *)&nd_opt_captive_portal, &length);
- if (r < 0)
- return r;
-
- /* The length field has units of 8 octets */
- assert(length % 8 == 0);
- if (length == 0)
- return -EBADMSG;
-
- /* Check that the message is not truncated by an embedded NUL.
- * NUL padding to a multiple of 8 is expected. */
- size_t size = strnlen(nd_opt_captive_portal + 2, length - 2);
- if (DIV_ROUND_UP(size + 2, 8) != length / 8)
- return -EBADMSG;
-
- /* Let's not return an empty buffer */
- if (size == 0) {
- *ret = NULL;
- *ret_size = 0;
- return 0;
- }
-
- *ret = nd_opt_captive_portal + 2;
- *ret_size = size;
-
- return 0;
-}
-
-static int get_pref64_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix64_info **ret) {
- struct nd_opt_prefix64_info *ri;
- size_t length;
- int r;
-
- assert(rt);
- assert(ret);
-
- r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREF64);
- if (r < 0)
- return r;
- if (r == 0)
- return -EMEDIUMTYPE;
-
- length = NDISC_ROUTER_OPTION_LENGTH(rt);
- if (length != sizeof(struct nd_opt_prefix64_info))
- return -EBADMSG;
-
- ri = (struct nd_opt_prefix64_info *) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex);
- if (!pref64_option_verify(ri, length))
- return -EBADMSG;
-
- *ret = ri;
- return 0;
-}
-
-int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret) {
- struct nd_opt_prefix64_info *pi;
- struct in6_addr a = {};
- unsigned prefixlen;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_pref64_prefix_info(rt, &pi);
- if (r < 0)
- return r;
-
- r = sd_ndisc_router_prefix64_get_prefixlen(rt, &prefixlen);
- if (r < 0)
- return r;
-
- memcpy(&a, pi->prefix, sizeof(pi->prefix));
- in6_addr_mask(&a, prefixlen);
- /* extra safety check for refusing malformed prefix. */
- if (memcmp(&a, pi->prefix, sizeof(pi->prefix)) != 0)
- return -EBADMSG;
-
- *ret = a;
- return 0;
-}
-
-int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
- struct nd_opt_prefix64_info *pi;
- uint16_t lifetime_prefix_len;
- uint8_t prefix_len;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_pref64_prefix_info(rt, &pi);
- if (r < 0)
- return r;
-
- lifetime_prefix_len = be16toh(pi->lifetime_and_plc);
- pref64_plc_to_prefix_length(lifetime_prefix_len, &prefix_len);
-
- *ret = prefix_len;
- return 0;
-}
-
-int sd_ndisc_router_prefix64_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
- struct nd_opt_prefix64_info *pi;
- uint16_t lifetime_prefix_len;
- int r;
-
- assert_return(rt, -EINVAL);
- assert_return(ret, -EINVAL);
-
- r = get_pref64_prefix_info(rt, &pi);
- if (r < 0)
- return r;
-
- lifetime_prefix_len = be16toh(pi->lifetime_and_plc);
-
- *ret = (lifetime_prefix_len & PREF64_SCALED_LIFETIME_MASK) * USEC_PER_SEC;
- return 0;
-}
diff --git a/src/libsystemd-network/ndisc-router.h b/src/libsystemd-network/ndisc-router.h
deleted file mode 100644
index 0a55e1a..0000000
--- a/src/libsystemd-network/ndisc-router.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-/***
- Copyright © 2014 Intel Corporation. All rights reserved.
-***/
-
-#include "sd-ndisc.h"
-
-#include "time-util.h"
-
-struct sd_ndisc_router {
- unsigned n_ref;
-
- triple_timestamp timestamp;
- struct in6_addr address;
-
- /* The raw packet size. The data is appended to the object, accessible via NDIS_ROUTER_RAW() */
- size_t raw_size;
-
- /* The current read index for the iterative option interface */
- size_t rindex;
-
- uint64_t flags;
- unsigned preference;
- uint64_t lifetime_usec;
-
- uint8_t hop_limit;
- uint32_t mtu;
- uint64_t icmp6_ratelimit_usec;
-};
-
-static inline void* NDISC_ROUTER_RAW(const sd_ndisc_router *rt) {
- return (uint8_t*) rt + ALIGN(sizeof(sd_ndisc_router));
-}
-
-static inline void *NDISC_ROUTER_OPTION_DATA(const sd_ndisc_router *rt) {
- return ((uint8_t*) NDISC_ROUTER_RAW(rt)) + rt->rindex;
-}
-
-static inline uint8_t NDISC_ROUTER_OPTION_TYPE(const sd_ndisc_router *rt) {
- return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[0];
-}
-static inline size_t NDISC_ROUTER_OPTION_LENGTH(const sd_ndisc_router *rt) {
- return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[1] * 8;
-}
-
-sd_ndisc_router *ndisc_router_new(size_t raw_size);
-int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt);
diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h
index d6cec90..0a535dd 100644
--- a/src/libsystemd-network/radv-internal.h
+++ b/src/libsystemd-network/radv-internal.h
@@ -10,7 +10,7 @@
#include "sd-radv.h"
#include "list.h"
-#include "ndisc-protocol.h"
+#include "ndisc-option.h"
#include "network-common.h"
#include "sparse-endian.h"
#include "time-util.h"
@@ -43,9 +43,11 @@
#define RADV_MAX_ROUTER_LIFETIME_USEC (9000 * USEC_PER_SEC)
#define RADV_DEFAULT_ROUTER_LIFETIME_USEC (3 * RADV_DEFAULT_MAX_TIMEOUT_USEC)
/* RFC 4861 section 4.2.
- * Retrans Timer
+ * Reachable Time and Retrans Timer
* 32-bit unsigned integer. The time, in milliseconds. */
-#define RADV_MAX_RETRANSMIT_USEC (UINT32_MAX * USEC_PER_MSEC)
+#define RADV_MAX_UINT32_MSEC_USEC (UINT32_MAX * USEC_PER_MSEC)
+#define RADV_MAX_REACHABLE_TIME_USEC RADV_MAX_UINT32_MSEC_USEC
+#define RADV_MAX_RETRANSMIT_USEC RADV_MAX_UINT32_MSEC_USEC
/* draft-ietf-6man-slaac-renum-02 section 4.1.1.
* AdvPreferredLifetime: max(AdvDefaultLifetime, 3 * MaxRtrAdvInterval)
* AdvValidLifetime: 2 * AdvPreferredLifetime */
@@ -79,11 +81,10 @@
/* Pref64 option type (RFC8781, section 4) */
#define RADV_OPT_PREF64 38
-enum RAdvState {
+typedef enum RAdvState {
RADV_STATE_IDLE = 0,
RADV_STATE_ADVERTISING = 1,
-};
-typedef enum RAdvState RAdvState;
+} RAdvState;
struct sd_radv_opt_dns {
uint8_t type;
@@ -98,6 +99,7 @@ struct sd_radv {
int ifindex;
char *ifname;
+ struct in6_addr ipv6ll;
sd_event *event;
int event_priority;
@@ -105,7 +107,9 @@ struct sd_radv {
struct ether_addr mac_addr;
uint8_t hop_limit;
uint8_t flags;
+ uint8_t preference;
uint32_t mtu;
+ usec_t reachable_usec;
usec_t retransmit_usec;
usec_t lifetime_usec; /* timespan */
diff --git a/src/libsystemd-network/sd-dhcp-client-id.c b/src/libsystemd-network/sd-dhcp-client-id.c
new file mode 100644
index 0000000..cab04f0
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp-client-id.c
@@ -0,0 +1,196 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "dhcp-client-id-internal.h"
+#include "iovec-util.h"
+#include "unaligned.h"
+#include "utf8.h"
+
+int sd_dhcp_client_id_clear(sd_dhcp_client_id *client_id) {
+ assert_return(client_id, -EINVAL);
+
+ *client_id = (sd_dhcp_client_id) {};
+ return 0;
+}
+
+int sd_dhcp_client_id_is_set(const sd_dhcp_client_id *client_id) {
+ if (!client_id)
+ return false;
+
+ return client_id_size_is_valid(client_id->size);
+}
+
+int sd_dhcp_client_id_get(const sd_dhcp_client_id *client_id, uint8_t *ret_type, const void **ret_data, size_t *ret_size) {
+ assert_return(sd_dhcp_client_id_is_set(client_id), -EINVAL);
+ assert_return(ret_type, -EINVAL);
+ assert_return(ret_data, -EINVAL);
+ assert_return(ret_size, -EINVAL);
+
+ *ret_type = client_id->id.type;
+ *ret_data = client_id->id.data;
+ *ret_size = client_id->size - offsetof(typeof(client_id->id), data);
+ return 0;
+}
+
+int sd_dhcp_client_id_get_raw(const sd_dhcp_client_id *client_id, const void **ret_data, size_t *ret_size) {
+ assert_return(sd_dhcp_client_id_is_set(client_id), -EINVAL);
+ assert_return(ret_data, -EINVAL);
+ assert_return(ret_size, -EINVAL);
+
+ /* Unlike sd_dhcp_client_id_get(), this returns whole client ID including its type. */
+
+ *ret_data = client_id->raw;
+ *ret_size = client_id->size;
+ return 0;
+}
+
+int sd_dhcp_client_id_set(
+ sd_dhcp_client_id *client_id,
+ uint8_t type,
+ const void *data,
+ size_t data_size) {
+
+ assert_return(client_id, -EINVAL);
+ assert_return(data, -EINVAL);
+
+ if (!client_id_data_size_is_valid(data_size))
+ return -EINVAL;
+
+ client_id->id.type = type;
+ memcpy(client_id->id.data, data, data_size);
+
+ client_id->size = offsetof(typeof(client_id->id), data) + data_size;
+ return 0;
+}
+
+int sd_dhcp_client_id_set_raw(
+ sd_dhcp_client_id *client_id,
+ const void *data,
+ size_t data_size) {
+
+ assert_return(client_id, -EINVAL);
+ assert_return(data, -EINVAL);
+
+ /* Unlike sd_dhcp_client_id_set(), this takes whole client ID including its type. */
+
+ if (!client_id_size_is_valid(data_size))
+ return -EINVAL;
+
+ memcpy(client_id->raw, data, data_size);
+
+ client_id->size = data_size;
+ return 0;
+}
+
+int sd_dhcp_client_id_set_iaid_duid(
+ sd_dhcp_client_id *client_id,
+ uint32_t iaid,
+ sd_dhcp_duid *duid) {
+
+ assert_return(client_id, -EINVAL);
+ assert_return(duid, -EINVAL);
+ assert_return(sd_dhcp_duid_is_set(duid), -ESTALE);
+
+ client_id->id.type = 255;
+ unaligned_write_be32(&client_id->id.ns.iaid, iaid);
+ memcpy(&client_id->id.ns.duid, &duid->duid, duid->size);
+
+ client_id->size = offsetof(typeof(client_id->id), ns.duid) + duid->size;
+ return 0;
+}
+
+int sd_dhcp_client_id_to_string(const sd_dhcp_client_id *client_id, char **ret) {
+ _cleanup_free_ char *t = NULL;
+ size_t len;
+ int r;
+
+ assert_return(sd_dhcp_client_id_is_set(client_id), -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ len = client_id->size - offsetof(typeof(client_id->id), data);
+
+ switch (client_id->id.type) {
+ case 0:
+ if (utf8_is_printable((char *) client_id->id.gen.data, len))
+ r = asprintf(&t, "%.*s", (int) len, client_id->id.gen.data);
+ else
+ r = asprintf(&t, "DATA");
+ break;
+ case 1:
+ if (len == sizeof_field(sd_dhcp_client_id, id.eth))
+ r = asprintf(&t, "%02x:%02x:%02x:%02x:%02x:%02x",
+ client_id->id.eth.haddr[0],
+ client_id->id.eth.haddr[1],
+ client_id->id.eth.haddr[2],
+ client_id->id.eth.haddr[3],
+ client_id->id.eth.haddr[4],
+ client_id->id.eth.haddr[5]);
+ else
+ r = asprintf(&t, "ETHER");
+ break;
+ case 2 ... 254:
+ r = asprintf(&t, "ARP/LL");
+ break;
+ case 255:
+ if (len < sizeof(uint32_t))
+ r = asprintf(&t, "IAID/DUID");
+ else {
+ uint32_t iaid = be32toh(client_id->id.ns.iaid);
+ /* TODO: check and stringify DUID */
+ r = asprintf(&t, "IAID:0x%x/DUID", iaid);
+ }
+ break;
+ default:
+ assert_not_reached();
+ }
+ if (r < 0)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(t);
+ return 0;
+}
+
+int sd_dhcp_client_id_to_string_from_raw(const void *data, size_t data_size, char **ret) {
+ sd_dhcp_client_id client_id;
+ int r;
+
+ assert_return(data, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = sd_dhcp_client_id_set_raw(&client_id, data, data_size);
+ if (r < 0)
+ return r;
+
+ return sd_dhcp_client_id_to_string(&client_id, ret);
+}
+
+void client_id_hash_func(const sd_dhcp_client_id *client_id, struct siphash *state) {
+ assert(sd_dhcp_client_id_is_set(client_id));
+ assert(state);
+
+ siphash24_compress_typesafe(client_id->size, state);
+ siphash24_compress(client_id->raw, client_id->size, state);
+}
+
+int client_id_compare_func(const sd_dhcp_client_id *a, const sd_dhcp_client_id *b) {
+ assert(sd_dhcp_client_id_is_set(a));
+ assert(sd_dhcp_client_id_is_set(b));
+
+ return memcmp_nn(a->raw, a->size, b->raw, b->size);
+}
+
+int json_dispatch_client_id(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ sd_dhcp_client_id *client_id = ASSERT_PTR(userdata);
+ _cleanup_(iovec_done) struct iovec iov = {};
+ int r;
+
+ r = json_dispatch_byte_array_iovec(name, variant, flags, &iov);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp_client_id_set_raw(client_id, iov.iov_base, iov.iov_len);
+ if (r < 0)
+ return json_log(variant, flags, r, "Failed to set DHCP client ID from JSON field '%s': %m", strna(name));
+
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c
index 24bcd74..1eb8509 100644
--- a/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/libsystemd-network/sd-dhcp-client.c
@@ -15,8 +15,8 @@
#include "alloc-util.h"
#include "device-util.h"
+#include "dhcp-client-id-internal.h"
#include "dhcp-client-internal.h"
-#include "dhcp-identifier.h"
#include "dhcp-lease-internal.h"
#include "dhcp-network.h"
#include "dhcp-option.h"
@@ -39,7 +39,6 @@
#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)
@@ -48,32 +47,6 @@
#define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report
* transient failure. */
-typedef struct sd_dhcp_client_id {
- uint8_t type;
- union {
- struct {
- /* 0: Generic (non-LL) (RFC 2132) */
- uint8_t data[MAX_CLIENT_ID_LEN];
- } _packed_ gen;
- struct {
- /* 1: Ethernet Link-Layer (RFC 2132) */
- uint8_t haddr[ETH_ALEN];
- } _packed_ eth;
- struct {
- /* 2 - 254: ARP/Link-Layer (RFC 2132) */
- uint8_t haddr[0];
- } _packed_ ll;
- struct {
- /* 255: Node-specific (RFC 4361) */
- be32_t iaid;
- struct duid duid;
- } _packed_ ns;
- struct {
- uint8_t data[MAX_CLIENT_ID_LEN];
- } _packed_ raw;
- };
-} _packed_ sd_dhcp_client_id;
-
struct sd_dhcp_client {
unsigned n_ref;
@@ -89,6 +62,7 @@ struct sd_dhcp_client {
int fd;
uint16_t port;
+ uint16_t server_port;
union sockaddr_union link;
sd_event_source *receive_message;
bool request_broadcast;
@@ -100,7 +74,6 @@ struct sd_dhcp_client {
struct hw_addr_data bcast_addr;
uint16_t arp_type;
sd_dhcp_client_id client_id;
- size_t client_id_len;
char *hostname;
char *vendor_class_identifier;
char *mudurl;
@@ -181,58 +154,6 @@ static int client_receive_message_udp(
static void client_stop(sd_dhcp_client *client, int error);
static int client_restart(sd_dhcp_client *client);
-int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret) {
- const sd_dhcp_client_id *client_id = data;
- _cleanup_free_ char *t = NULL;
- int r = 0;
-
- assert_return(data, -EINVAL);
- assert_return(len >= 1, -EINVAL);
- assert_return(ret, -EINVAL);
-
- len -= 1;
- if (len > MAX_CLIENT_ID_LEN)
- return -EINVAL;
-
- switch (client_id->type) {
- case 0:
- if (utf8_is_printable((char *) client_id->gen.data, len))
- r = asprintf(&t, "%.*s", (int) len, client_id->gen.data);
- else
- r = asprintf(&t, "DATA");
- break;
- case 1:
- if (len == sizeof_field(sd_dhcp_client_id, eth))
- r = asprintf(&t, "%02x:%02x:%02x:%02x:%02x:%02x",
- client_id->eth.haddr[0],
- client_id->eth.haddr[1],
- client_id->eth.haddr[2],
- client_id->eth.haddr[3],
- client_id->eth.haddr[4],
- client_id->eth.haddr[5]);
- else
- r = asprintf(&t, "ETHER");
- break;
- case 2 ... 254:
- r = asprintf(&t, "ARP/LL");
- break;
- case 255:
- if (len < sizeof(uint32_t))
- r = asprintf(&t, "IAID/DUID");
- else {
- uint32_t iaid = be32toh(client_id->ns.iaid);
- /* TODO: check and stringify DUID */
- r = asprintf(&t, "IAID:0x%x/DUID", iaid);
- }
- break;
- }
- if (r < 0)
- return -ENOMEM;
-
- *ret = TAKE_PTR(t);
- return 0;
-}
-
int dhcp_client_set_state_callback(
sd_dhcp_client *client,
sd_dhcp_client_callback_t cb,
@@ -363,34 +284,14 @@ int sd_dhcp_client_set_mac(
return 0;
}
-int sd_dhcp_client_get_client_id(
- sd_dhcp_client *client,
- uint8_t *ret_type,
- const uint8_t **ret_data,
- size_t *ret_data_len) {
-
+int sd_dhcp_client_get_client_id(sd_dhcp_client *client, const sd_dhcp_client_id **ret) {
assert_return(client, -EINVAL);
+ assert_return(ret, -EINVAL);
- if (client->client_id_len > 0) {
- if (client->client_id_len <= offsetof(sd_dhcp_client_id, raw.data))
- return -EINVAL;
-
- if (ret_type)
- *ret_type = client->client_id.type;
- if (ret_data)
- *ret_data = client->client_id.raw.data;
- if (ret_data_len)
- *ret_data_len = client->client_id_len - offsetof(sd_dhcp_client_id, raw.data);
- return 1;
- }
-
- if (ret_type)
- *ret_type = 0;
- if (ret_data)
- *ret_data = NULL;
- if (ret_data_len)
- *ret_data_len = 0;
+ if (!sd_dhcp_client_id_is_set(&client->client_id))
+ return -ENODATA;
+ *ret = &client->client_id;
return 0;
}
@@ -403,7 +304,7 @@ int sd_dhcp_client_set_client_id(
assert_return(client, -EINVAL);
assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
assert_return(data, -EINVAL);
- assert_return(data_len > 0 && data_len <= MAX_CLIENT_ID_LEN, -EINVAL);
+ assert_return(client_id_data_size_is_valid(data_len), -EINVAL);
/* For hardware types, log debug message about unexpected data length.
*
@@ -416,42 +317,28 @@ int sd_dhcp_client_set_client_id(
"Changing client ID to hardware type %u with unexpected address length %zu",
type, data_len);
- client->client_id.type = type;
- memcpy(&client->client_id.raw.data, data, data_len);
- client->client_id_len = data_len + sizeof (client->client_id.type);
-
- return 0;
+ return sd_dhcp_client_id_set(&client->client_id, type, data, data_len);
}
-/**
- * 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(
+static int dhcp_client_set_iaid_duid(
sd_dhcp_client *client,
bool iaid_set,
- uint32_t iaid) {
+ uint32_t iaid,
+ sd_dhcp_duid *duid) {
int r;
- assert_return(client, -EINVAL);
- assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
-
- zero(client->client_id);
- client->client_id.type = 255;
-
- if (iaid_set)
- client->client_id.ns.iaid = htobe32(iaid);
- else {
+ if (!iaid_set) {
r = dhcp_identifier_set_iaid(client->dev, &client->hw_addr,
/* legacy_unstable_byteorder = */ true,
- &client->client_id.ns.iaid);
+ &iaid);
if (r < 0)
- return log_dhcp_client_errno(client, r, "Failed to set IAID: %m");
+ return r;
+
+ iaid = be32toh(iaid);
}
- return 0;
+ return sd_dhcp_client_id_set_iaid_duid(&client->client_id, iaid, duid);
}
int sd_dhcp_client_set_iaid_duid_llt(
@@ -460,23 +347,17 @@ int sd_dhcp_client_set_iaid_duid_llt(
uint32_t iaid,
usec_t llt_time) {
- size_t len;
+ sd_dhcp_duid duid;
int r;
assert_return(client, -EINVAL);
assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
- r = dhcp_client_set_iaid(client, iaid_set, iaid);
+ r = sd_dhcp_duid_set_llt(&duid, client->hw_addr.bytes, client->hw_addr.length, client->arp_type, llt_time);
if (r < 0)
return r;
- r = dhcp_identifier_set_duid_llt(&client->hw_addr, client->arp_type, llt_time, &client->client_id.ns.duid, &len);
- if (r < 0)
- return log_dhcp_client_errno(client, r, "Failed to set DUID-LLT: %m");
-
- client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len;
-
- return 0;
+ return dhcp_client_set_iaid_duid(client, iaid_set, iaid, &duid);
}
int sd_dhcp_client_set_iaid_duid_ll(
@@ -484,23 +365,17 @@ int sd_dhcp_client_set_iaid_duid_ll(
bool iaid_set,
uint32_t iaid) {
- size_t len;
+ sd_dhcp_duid duid;
int r;
assert_return(client, -EINVAL);
assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
- r = dhcp_client_set_iaid(client, iaid_set, iaid);
+ r = sd_dhcp_duid_set_ll(&duid, client->hw_addr.bytes, client->hw_addr.length, client->arp_type);
if (r < 0)
return r;
- r = dhcp_identifier_set_duid_ll(&client->hw_addr, client->arp_type, &client->client_id.ns.duid, &len);
- if (r < 0)
- return log_dhcp_client_errno(client, r, "Failed to set DUID-LL: %m");
-
- client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len;
-
- return 0;
+ return dhcp_client_set_iaid_duid(client, iaid_set, iaid, &duid);
}
int sd_dhcp_client_set_iaid_duid_en(
@@ -508,23 +383,17 @@ int sd_dhcp_client_set_iaid_duid_en(
bool iaid_set,
uint32_t iaid) {
- size_t len;
+ sd_dhcp_duid duid;
int r;
assert_return(client, -EINVAL);
assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
- r = dhcp_client_set_iaid(client, iaid_set, iaid);
+ r = sd_dhcp_duid_set_en(&duid);
if (r < 0)
return r;
- r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len);
- if (r < 0)
- return log_dhcp_client_errno(client, r, "Failed to set DUID-EN: %m");
-
- client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len;
-
- return 0;
+ return dhcp_client_set_iaid_duid(client, iaid_set, iaid, &duid);
}
int sd_dhcp_client_set_iaid_duid_uuid(
@@ -532,23 +401,17 @@ int sd_dhcp_client_set_iaid_duid_uuid(
bool iaid_set,
uint32_t iaid) {
- size_t len;
+ sd_dhcp_duid duid;
int r;
assert_return(client, -EINVAL);
assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
- r = dhcp_client_set_iaid(client, iaid_set, iaid);
+ r = sd_dhcp_duid_set_uuid(&duid);
if (r < 0)
return r;
- r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len);
- if (r < 0)
- return log_dhcp_client_errno(client, r, "Failed to set DUID-UUID: %m");
-
- client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len;
-
- return 0;
+ return dhcp_client_set_iaid_duid(client, iaid_set, iaid, &duid);
}
int sd_dhcp_client_set_iaid_duid_raw(
@@ -556,27 +419,21 @@ int sd_dhcp_client_set_iaid_duid_raw(
bool iaid_set,
uint32_t iaid,
uint16_t duid_type,
- const uint8_t *duid,
- size_t duid_len) {
+ const uint8_t *duid_data,
+ size_t duid_data_len) {
- size_t len;
+ sd_dhcp_duid duid;
int r;
assert_return(client, -EINVAL);
assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
- assert_return(duid || duid_len == 0, -EINVAL);
+ assert_return(duid_data || duid_data_len == 0, -EINVAL);
- r = dhcp_client_set_iaid(client, iaid_set, iaid);
+ r = sd_dhcp_duid_set(&duid, duid_type, duid_data, duid_data_len);
if (r < 0)
return r;
- r = dhcp_identifier_set_duid_raw(duid_type, duid, duid_len, &client->client_id.ns.duid, &len);
- if (r < 0)
- return log_dhcp_client_errno(client, r, "Failed to set DUID: %m");
-
- client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len;
-
- return 0;
+ return dhcp_client_set_iaid_duid(client, iaid_set, iaid, &duid);
}
int sd_dhcp_client_set_rapid_commit(sd_dhcp_client *client, bool rapid_commit) {
@@ -660,6 +517,18 @@ int sd_dhcp_client_set_client_port(
return 0;
}
+int sd_dhcp_client_set_port(
+ sd_dhcp_client *client,
+ uint16_t port) {
+
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
+
+ client->server_port = port;
+
+ return 0;
+}
+
int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) {
assert_return(client, -EINVAL);
assert_return(mtu >= DHCP_MIN_PACKET_SIZE, -ERANGE);
@@ -925,8 +794,8 @@ static int client_message_init(
Identifier option is not set */
r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
SD_DHCP_OPTION_CLIENT_IDENTIFIER,
- client->client_id_len,
- &client->client_id);
+ client->client_id.size,
+ client->client_id.raw);
if (r < 0)
return r;
@@ -1035,7 +904,7 @@ static int dhcp_client_send_raw(
size_t len) {
dhcp_packet_append_ip_headers(packet, INADDR_ANY, client->port,
- INADDR_BROADCAST, DHCP_PORT_SERVER, len, client->ip_service_type);
+ INADDR_BROADCAST, client->server_port, len, client->ip_service_type);
return dhcp_network_send_raw_socket(client->fd, &client->link,
packet, len);
@@ -1257,7 +1126,7 @@ static int client_send_request(sd_dhcp_client *client) {
if (client->state == DHCP_STATE_RENEWING)
r = dhcp_network_send_udp_socket(client->fd,
client->lease->server_address,
- DHCP_PORT_SERVER,
+ client->server_port,
&request->dhcp,
sizeof(DHCPMessage) + optoffset);
else
@@ -1599,10 +1468,8 @@ static int client_parse_message(
if (r < 0)
return r;
- if (client->client_id_len > 0) {
- r = dhcp_lease_set_client_id(lease,
- (uint8_t *) &client->client_id,
- client->client_id_len);
+ if (sd_dhcp_client_id_is_set(&client->client_id)) {
+ r = dhcp_lease_set_client_id(lease, &client->client_id);
if (r < 0)
return r;
}
@@ -2302,7 +2169,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
return r;
/* If no client identifier exists, construct an RFC 4361-compliant one */
- if (client->client_id_len == 0) {
+ if (!sd_dhcp_client_id_is_set(&client->client_id)) {
r = sd_dhcp_client_set_iaid_duid_en(client, /* iaid_set = */ false, /* iaid = */ 0);
if (r < 0)
return r;
@@ -2349,7 +2216,7 @@ int sd_dhcp_client_send_release(sd_dhcp_client *client) {
r = dhcp_network_send_udp_socket(client->fd,
client->lease->server_address,
- DHCP_PORT_SERVER,
+ client->server_port,
&release->dhcp,
sizeof(DHCPMessage) + optoffset);
if (r < 0)
@@ -2383,7 +2250,7 @@ int sd_dhcp_client_send_decline(sd_dhcp_client *client) {
r = dhcp_network_send_udp_socket(client->fd,
client->lease->server_address,
- DHCP_PORT_SERVER,
+ client->server_port,
&release->dhcp,
sizeof(DHCPMessage) + optoffset);
if (r < 0)
@@ -2528,6 +2395,7 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
.fd = -EBADF,
.mtu = DHCP_MIN_PACKET_SIZE,
.port = DHCP_PORT_CLIENT,
+ .server_port = DHCP_PORT_SERVER,
.anonymize = !!anonymize,
.max_discover_attempts = UINT64_MAX,
.max_request_attempts = 5,
diff --git a/src/libsystemd-network/sd-dhcp-duid.c b/src/libsystemd-network/sd-dhcp-duid.c
new file mode 100644
index 0000000..4782ec6
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp-duid.c
@@ -0,0 +1,288 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/if_infiniband.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+
+#include "dhcp-duid-internal.h"
+#include "hexdecoct.h"
+#include "netif-util.h"
+#include "network-common.h"
+#include "siphash24.h"
+#include "string-table.h"
+#include "unaligned.h"
+
+#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
+#define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03)
+#define USEC_2000 ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */
+
+static const char * const duid_type_table[_DUID_TYPE_MAX] = {
+ [DUID_TYPE_LLT] = "DUID-LLT",
+ [DUID_TYPE_EN] = "DUID-EN/Vendor",
+ [DUID_TYPE_LL] = "DUID-LL",
+ [DUID_TYPE_UUID] = "UUID",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(duid_type, DUIDType);
+
+int sd_dhcp_duid_clear(sd_dhcp_duid *duid) {
+ assert_return(duid, -EINVAL);
+
+ *duid = (sd_dhcp_duid) {};
+ return 0;
+}
+
+int sd_dhcp_duid_is_set(const sd_dhcp_duid *duid) {
+ if (!duid)
+ return false;
+
+ return duid_size_is_valid(duid->size);
+}
+
+int sd_dhcp_duid_get(const sd_dhcp_duid *duid, uint16_t *ret_type, const void **ret_data, size_t *ret_size) {
+ assert_return(sd_dhcp_duid_is_set(duid), -EINVAL);
+ assert_return(ret_type, -EINVAL);
+ assert_return(ret_data, -EINVAL);
+ assert_return(ret_size, -EINVAL);
+
+ *ret_type = be16toh(duid->duid.type);
+ *ret_data = duid->duid.data;
+ *ret_size = duid->size - offsetof(struct duid, data);
+ return 0;
+}
+
+int sd_dhcp_duid_get_raw(const sd_dhcp_duid *duid, const void **ret_data, size_t *ret_size) {
+ assert_return(sd_dhcp_duid_is_set(duid), -EINVAL);
+ assert_return(ret_data, -EINVAL);
+ assert_return(ret_size, -EINVAL);
+
+ /* Unlike sd_dhcp_duid_get(), this returns whole DUID including its type. */
+
+ *ret_data = duid->raw;
+ *ret_size = duid->size;
+ return 0;
+}
+
+int sd_dhcp_duid_set(
+ sd_dhcp_duid *duid,
+ uint16_t duid_type,
+ const void *data,
+ size_t data_size) {
+
+ assert_return(duid, -EINVAL);
+ assert_return(data, -EINVAL);
+
+ if (!duid_data_size_is_valid(data_size))
+ return -EINVAL;
+
+ unaligned_write_be16(&duid->duid.type, duid_type);
+ memcpy(duid->duid.data, data, data_size);
+
+ duid->size = offsetof(struct duid, data) + data_size;
+ return 0;
+}
+
+int sd_dhcp_duid_set_raw(
+ sd_dhcp_duid *duid,
+ const void *data,
+ size_t data_size) {
+
+ assert_return(duid, -EINVAL);
+ assert_return(data, -EINVAL);
+
+ /* Unlike sd_dhcp_duid_set(), this takes whole DUID including its type. */
+
+ if (!duid_size_is_valid(data_size))
+ return -EINVAL;
+
+ memcpy(duid->raw, data, data_size);
+
+ duid->size = data_size;
+ return 0;
+}
+
+int sd_dhcp_duid_set_llt(
+ sd_dhcp_duid *duid,
+ const void *hw_addr,
+ size_t hw_addr_size,
+ uint16_t arp_type,
+ uint64_t usec) {
+
+ uint16_t time_from_2000y;
+
+ assert_return(duid, -EINVAL);
+ assert_return(hw_addr, -EINVAL);
+
+ if (arp_type == ARPHRD_ETHER)
+ assert_return(hw_addr_size == ETH_ALEN, -EINVAL);
+ else if (arp_type == ARPHRD_INFINIBAND)
+ assert_return(hw_addr_size == INFINIBAND_ALEN, -EINVAL);
+ else
+ return -EOPNOTSUPP;
+
+ time_from_2000y = (uint16_t) ((usec_sub_unsigned(usec, USEC_2000) / USEC_PER_SEC) & 0xffffffff);
+
+ unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_LLT);
+ unaligned_write_be16(&duid->duid.llt.htype, arp_type);
+ unaligned_write_be32(&duid->duid.llt.time, time_from_2000y);
+ memcpy(duid->duid.llt.haddr, hw_addr, hw_addr_size);
+
+ duid->size = offsetof(struct duid, llt.haddr) + hw_addr_size;
+ return 0;
+}
+
+int sd_dhcp_duid_set_ll(
+ sd_dhcp_duid *duid,
+ const void *hw_addr,
+ size_t hw_addr_size,
+ uint16_t arp_type) {
+
+ assert_return(duid, -EINVAL);
+ assert_return(hw_addr, -EINVAL);
+
+ if (arp_type == ARPHRD_ETHER)
+ assert_return(hw_addr_size == ETH_ALEN, -EINVAL);
+ else if (arp_type == ARPHRD_INFINIBAND)
+ assert_return(hw_addr_size == INFINIBAND_ALEN, -EINVAL);
+ else
+ return -EOPNOTSUPP;
+
+ unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_LL);
+ unaligned_write_be16(&duid->duid.ll.htype, arp_type);
+ memcpy(duid->duid.ll.haddr, hw_addr, hw_addr_size);
+
+ duid->size = offsetof(struct duid, ll.haddr) + hw_addr_size;
+ return 0;
+}
+
+int sd_dhcp_duid_set_en(sd_dhcp_duid *duid) {
+ sd_id128_t machine_id;
+ bool test_mode;
+ uint64_t hash;
+ int r;
+
+ assert_return(duid, -EINVAL);
+
+ test_mode = network_test_mode_enabled();
+
+ if (!test_mode) {
+ r = sd_id128_get_machine(&machine_id);
+ if (r < 0)
+ return r;
+ } else
+ /* For tests, especially for fuzzers, reproducibility is important.
+ * Hence, use a static and constant machine ID.
+ * See 9216fddc5a8ac2742e6cfa7660f95c20ca4f2193. */
+ machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10);
+
+ unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_EN);
+ unaligned_write_be32(&duid->duid.en.pen, SYSTEMD_PEN);
+
+ /* a bit of snake-oil perhaps, but no need to expose the machine-id
+ * directly; duid->en.id might not be aligned, so we need to copy */
+ hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes));
+ memcpy(duid->duid.en.id, &hash, sizeof(hash));
+
+ duid->size = offsetof(struct duid, en.id) + sizeof(hash);
+
+ if (test_mode)
+ assert_se(memcmp(&duid->duid, (const uint8_t[]) { 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2 }, duid->size) == 0);
+
+ return 0;
+}
+
+int sd_dhcp_duid_set_uuid(sd_dhcp_duid *duid) {
+ sd_id128_t machine_id;
+ int r;
+
+ assert_return(duid, -EINVAL);
+
+ r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id);
+ if (r < 0)
+ return r;
+
+ unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_UUID);
+ memcpy(&duid->duid.uuid.uuid, &machine_id, sizeof(machine_id));
+
+ duid->size = offsetof(struct duid, uuid.uuid) + sizeof(machine_id);
+ return 0;
+}
+
+int dhcp_duid_to_string_internal(uint16_t type, const void *data, size_t data_size, char **ret) {
+ _cleanup_free_ char *p = NULL, *x = NULL;
+ const char *t;
+
+ assert(data);
+ assert(ret);
+
+ if (!duid_data_size_is_valid(data_size))
+ return -EINVAL;
+
+ x = hexmem(data, data_size);
+ if (!x)
+ return -ENOMEM;
+
+ t = duid_type_to_string(type);
+ if (!t)
+ return asprintf(ret, "%04x:%s", htobe16(type), x);
+
+ p = strjoin(t, ":", x);
+ if (!p)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(p);
+ return 0;
+}
+
+int sd_dhcp_duid_to_string(const sd_dhcp_duid *duid, char **ret) {
+ uint16_t type;
+ const void *data;
+ size_t data_size;
+ int r;
+
+ assert_return(sd_dhcp_duid_is_set(duid), -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = sd_dhcp_duid_get(duid, &type, &data, &data_size);
+ if (r < 0)
+ return r;
+
+ return dhcp_duid_to_string_internal(type, data, data_size, ret);
+}
+
+int dhcp_identifier_set_iaid(
+ sd_device *dev,
+ const struct hw_addr_data *hw_addr,
+ bool legacy_unstable_byteorder,
+ void *ret) {
+
+ const char *name = NULL;
+ uint32_t id32;
+ uint64_t id;
+
+ assert(hw_addr);
+ assert(ret);
+
+ if (dev)
+ name = net_get_persistent_name(dev);
+ if (name)
+ id = siphash24(name, strlen(name), HASH_KEY.bytes);
+ else
+ /* fall back to MAC address if no predictable name available */
+ id = siphash24(hw_addr->bytes, hw_addr->length, HASH_KEY.bytes);
+
+ id32 = (id & 0xffffffff) ^ (id >> 32);
+
+ if (legacy_unstable_byteorder)
+ /* for historical reasons (a bug), the bits were swapped and thus
+ * the result was endianness dependent. Preserve that behavior. */
+ id32 = bswap_32(id32);
+ else
+ /* the fixed behavior returns a stable byte order. Since LE is expected
+ * to be more common, swap the bytes on LE to give the same as legacy
+ * behavior. */
+ id32 = be32toh(id32);
+
+ unaligned_write_ne32(ret, id32);
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c
index 202d75f..37f4b3b 100644
--- a/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/libsystemd-network/sd-dhcp-lease.c
@@ -420,7 +420,6 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) {
free(lease->static_routes);
free(lease->classless_routes);
- free(lease->client_id);
free(lease->vendor_specific);
strv_free(lease->search_domains);
free(lease->sixrd_br_addresses);
@@ -1070,8 +1069,8 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
_cleanup_fclose_ FILE *f = NULL;
struct in_addr address;
const struct in_addr *addresses;
- const void *client_id, *data;
- size_t client_id_len, data_len;
+ const void *data;
+ size_t data_len;
const char *string;
uint16_t mtu;
_cleanup_free_ sd_dhcp_route **routes = NULL;
@@ -1187,11 +1186,10 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
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) {
+ if (sd_dhcp_client_id_is_set(&lease->client_id)) {
_cleanup_free_ char *client_id_hex = NULL;
- client_id_hex = hexmem(client_id, client_id_len);
+ client_id_hex = hexmem(lease->client_id.raw, lease->client_id.size);
if (!client_id_hex)
return -ENOMEM;
fprintf(f, "CLIENTID=%s\n", client_id_hex);
@@ -1482,13 +1480,20 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
}
if (client_id_hex) {
- r = unhexmem(client_id_hex, SIZE_MAX, &lease->client_id, &lease->client_id_len);
+ _cleanup_free_ void *data = NULL;
+ size_t data_size;
+
+ r = unhexmem(client_id_hex, &data, &data_size);
if (r < 0)
log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex);
+
+ r = sd_dhcp_client_id_set_raw(&lease->client_id, data, data_size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to assign client ID, ignoring: %m");
}
if (vendor_specific_hex) {
- r = unhexmem(vendor_specific_hex, SIZE_MAX, &lease->vendor_specific, &lease->vendor_specific_len);
+ r = unhexmem(vendor_specific_hex, &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);
}
@@ -1500,7 +1505,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
if (!options[i])
continue;
- r = unhexmem(options[i], SIZE_MAX, &data, &len);
+ r = unhexmem(options[i], &data, &len);
if (r < 0) {
log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]);
continue;
@@ -1541,36 +1546,25 @@ int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) {
return 0;
}
-int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len) {
+int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id **ret) {
assert_return(lease, -EINVAL);
- assert_return(client_id, -EINVAL);
- assert_return(client_id_len, -EINVAL);
+ assert_return(ret, -EINVAL);
- if (!lease->client_id)
+ if (!sd_dhcp_client_id_is_set(&lease->client_id))
return -ENODATA;
- *client_id = lease->client_id;
- *client_id_len = lease->client_id_len;
+ *ret = &lease->client_id;
return 0;
}
-int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len) {
+int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id) {
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;
+ if (!sd_dhcp_client_id_is_set(client_id))
+ return sd_dhcp_client_id_clear(&lease->client_id);
- free_and_replace(lease->client_id, p);
- lease->client_id_len = client_id_len;
- }
+ lease->client_id = *client_id;
return 0;
}
diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c
new file mode 100644
index 0000000..2f84d51
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp-server-lease.c
@@ -0,0 +1,513 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "dhcp-server-lease-internal.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "mkdir.h"
+#include "tmpfile-util.h"
+
+static sd_dhcp_server_lease* dhcp_server_lease_free(sd_dhcp_server_lease *lease) {
+ if (!lease)
+ return NULL;
+
+ if (lease->server) {
+ hashmap_remove_value(lease->server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease);
+ hashmap_remove_value(lease->server->bound_leases_by_client_id, &lease->client_id, lease);
+ hashmap_remove_value(lease->server->static_leases_by_address, UINT32_TO_PTR(lease->address), lease);
+ hashmap_remove_value(lease->server->static_leases_by_client_id, &lease->client_id, lease);
+ }
+
+ free(lease->hostname);
+ return mfree(lease);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_server_lease, sd_dhcp_server_lease, dhcp_server_lease_free);
+
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ dhcp_server_lease_hash_ops,
+ sd_dhcp_client_id,
+ client_id_hash_func,
+ client_id_compare_func,
+ sd_dhcp_server_lease,
+ sd_dhcp_server_lease_unref);
+
+int dhcp_server_put_lease(sd_dhcp_server *server, sd_dhcp_server_lease *lease, bool is_static) {
+ int r;
+
+ assert(server);
+ assert(lease);
+
+ lease->server = server; /* This must be set before hashmap_put(). */
+
+ r = hashmap_ensure_put(is_static ? &server->static_leases_by_client_id : &server->bound_leases_by_client_id,
+ &dhcp_server_lease_hash_ops, &lease->client_id, lease);
+ if (r < 0)
+ return r;
+
+ r = hashmap_ensure_put(is_static ? &server->static_leases_by_address : &server->bound_leases_by_address,
+ NULL, UINT32_TO_PTR(lease->address), lease);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *req, usec_t expiration) {
+ _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL;
+ int r;
+
+ assert(server);
+ assert(address != 0);
+ assert(req);
+ assert(expiration != 0);
+
+ /* If a lease for the host already exists, update it. */
+ lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id);
+ if (lease) {
+ if (lease->address != address) {
+ hashmap_remove_value(server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease);
+ lease->address = address;
+
+ r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease);
+ if (r < 0)
+ return r;
+ }
+
+ lease->expiration = expiration;
+
+ TAKE_PTR(lease);
+ return 0;
+ }
+
+ /* Otherwise, add a new lease. */
+
+ lease = new(sd_dhcp_server_lease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ *lease = (sd_dhcp_server_lease) {
+ .n_ref = 1,
+ .address = address,
+ .client_id = req->client_id,
+ .htype = req->message->htype,
+ .hlen = req->message->hlen,
+ .gateway = req->message->giaddr,
+ .expiration = expiration,
+ };
+
+ memcpy(lease->chaddr, req->message->chaddr, req->message->hlen);
+
+ if (req->hostname) {
+ lease->hostname = strdup(req->hostname);
+ if (!lease->hostname)
+ return -ENOMEM;
+ }
+
+ r = dhcp_server_put_lease(server, lease, /* is_static = */ false);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(lease);
+ return 0;
+}
+
+int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) {
+ sd_dhcp_server_lease *lease;
+ usec_t time_now;
+ int r;
+
+ assert(server);
+
+ r = sd_event_now(server->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH(lease, server->bound_leases_by_client_id)
+ if (lease->expiration < time_now) {
+ log_dhcp_server(server, "CLEAN (0x%x)", be32toh(lease->address));
+ sd_dhcp_server_lease_unref(lease);
+ }
+
+ return 0;
+}
+
+sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req) {
+ sd_dhcp_server_lease *static_lease;
+ sd_dhcp_client_id client_id;
+
+ assert(server);
+ assert(req);
+
+ static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id);
+ if (static_lease)
+ goto verify;
+
+ /* when no lease is found based on the client id fall back to chaddr */
+ if (!client_id_data_size_is_valid(req->message->hlen))
+ return NULL;
+
+ if (sd_dhcp_client_id_set(&client_id, /* type = */ 1, req->message->chaddr, req->message->hlen) < 0)
+ return NULL;
+
+ static_lease = hashmap_get(server->static_leases_by_client_id, &client_id);
+ if (!static_lease)
+ return NULL;
+
+verify:
+ /* Check if the address is in the same subnet. */
+ if ((static_lease->address & server->netmask) != server->subnet)
+ return NULL;
+
+ /* Check if the address is different from the server address. */
+ if (static_lease->address == server->address)
+ return NULL;
+
+ return static_lease;
+}
+
+int sd_dhcp_server_set_static_lease(
+ sd_dhcp_server *server,
+ const struct in_addr *address,
+ uint8_t *client_id_raw,
+ size_t client_id_size) {
+
+ _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL;
+ sd_dhcp_client_id client_id;
+ int r;
+
+ assert_return(server, -EINVAL);
+ assert_return(client_id_raw, -EINVAL);
+ assert_return(client_id_size_is_valid(client_id_size), -EINVAL);
+ assert_return(!sd_dhcp_server_is_running(server), -EBUSY);
+
+ r = sd_dhcp_client_id_set_raw(&client_id, client_id_raw, client_id_size);
+ if (r < 0)
+ return r;
+
+ /* Static lease with an empty or omitted address is a valid entry,
+ * the server removes any static lease with the specified mac address. */
+ if (!address || address->s_addr == 0) {
+ sd_dhcp_server_lease_unref(hashmap_get(server->static_leases_by_client_id, &client_id));
+ return 0;
+ }
+
+ lease = new(sd_dhcp_server_lease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ *lease = (sd_dhcp_server_lease) {
+ .n_ref = 1,
+ .address = address->s_addr,
+ .client_id = client_id,
+ };
+
+ r = dhcp_server_put_lease(server, lease, /* is_static = */ true);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(lease);
+ return 0;
+}
+
+static int dhcp_server_lease_append_json(sd_dhcp_server_lease *lease, JsonVariant **ret) {
+ assert(lease);
+ assert(ret);
+
+ return json_build(ret,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_BYTE_ARRAY("ClientId", lease->client_id.raw, lease->client_id.size),
+ JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Address", &(struct in_addr) { .s_addr = lease->address }),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", lease->hostname)));
+}
+
+int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, JsonVariant **v) {
+ _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+ sd_dhcp_server_lease *lease;
+ usec_t now_b, now_r;
+ int r;
+
+ assert(server);
+ assert(v);
+
+ r = sd_event_now(server->event, CLOCK_BOOTTIME, &now_b);
+ if (r < 0)
+ return r;
+
+ r = sd_event_now(server->event, CLOCK_REALTIME, &now_r);
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) {
+ _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+
+ r = dhcp_server_lease_append_json(lease, &w);
+ if (r < 0)
+ return r;
+
+ usec_t exp_r = map_clock_usec_raw(lease->expiration, now_b, now_r);
+
+ r = json_variant_merge_objectb(&w,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_UNSIGNED("ExpirationUSec", lease->expiration),
+ JSON_BUILD_PAIR_UNSIGNED("ExpirationRealtimeUSec", exp_r)));
+ if (r < 0)
+ return r;
+
+ r = json_variant_append_array(&array, w);
+ if (r < 0)
+ return r;
+ }
+
+ return json_variant_set_field_non_null(v, "Leases", array);
+}
+
+int dhcp_server_static_leases_append_json(sd_dhcp_server *server, JsonVariant **v) {
+ _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+ sd_dhcp_server_lease *lease;
+ int r;
+
+ assert(server);
+ assert(v);
+
+ HASHMAP_FOREACH(lease, server->static_leases_by_client_id) {
+ _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+
+ r = dhcp_server_lease_append_json(lease, &w);
+ if (r < 0)
+ return r;
+
+ r = json_variant_append_array(&array, w);
+ if (r < 0)
+ return r;
+ }
+
+ return json_variant_set_field_non_null(v, "StaticLeases", array);
+}
+
+int dhcp_server_save_leases(sd_dhcp_server *server) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ sd_id128_t boot_id;
+ int r;
+
+ assert(server);
+
+ if (!server->lease_file)
+ return 0;
+
+ if (hashmap_isempty(server->bound_leases_by_client_id)) {
+ if (unlink(server->lease_file) < 0 && errno != ENOENT)
+ return -errno;
+
+ return 0;
+ }
+
+ r = sd_id128_get_boot(&boot_id);
+ if (r < 0)
+ return r;
+
+ r = json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_ID128("BootID", boot_id),
+ JSON_BUILD_PAIR_IN4_ADDR("Address", &(struct in_addr) { .s_addr = server->address }),
+ JSON_BUILD_PAIR_UNSIGNED("PrefixLength",
+ in4_addr_netmask_to_prefixlen(&(struct in_addr) { .s_addr = server->netmask }))));
+ if (r < 0)
+ return r;
+
+ r = dhcp_server_bound_leases_append_json(server, &v);
+ if (r < 0)
+ return r;
+
+ r = mkdirat_parents(server->lease_dir_fd, server->lease_file, 0755);
+ if (r < 0)
+ return r;
+
+ r = fopen_temporary_at(server->lease_dir_fd, server->lease_file, &f, &temp_path);
+ if (r < 0)
+ return r;
+
+ (void) fchmod(fileno(f), 0644);
+
+ r = json_variant_dump(v, JSON_FORMAT_NEWLINE | JSON_FORMAT_FLUSH, f, /* prefix = */ NULL);
+ if (r < 0)
+ goto failure;
+
+ r = conservative_renameat(server->lease_dir_fd, temp_path, server->lease_dir_fd, server->lease_file);
+ if (r < 0)
+ goto failure;
+
+ return 0;
+
+failure:
+ (void) unlinkat(server->lease_dir_fd, temp_path, /* flags = */ 0);
+ return r;
+}
+
+static int json_dispatch_dhcp_lease(sd_dhcp_server *server, JsonVariant *v, bool use_boottime) {
+ static const JsonDispatch dispatch_table_boottime[] = {
+ { "ClientId", JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), JSON_MANDATORY },
+ { "Address", JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), JSON_MANDATORY },
+ { "Hostname", JSON_VARIANT_STRING, json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 },
+ { "ExpirationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), JSON_MANDATORY },
+ { "ExpirationRealtimeUSec", _JSON_VARIANT_TYPE_INVALID, NULL, 0, JSON_MANDATORY },
+ {}
+ }, dispatch_table_realtime[] = {
+ { "ClientId", JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), JSON_MANDATORY },
+ { "Address", JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), JSON_MANDATORY },
+ { "Hostname", JSON_VARIANT_STRING, json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 },
+ { "ExpirationUSec", _JSON_VARIANT_TYPE_INVALID, NULL, 0, JSON_MANDATORY },
+ { "ExpirationRealtimeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), JSON_MANDATORY },
+ {}
+ };
+
+ _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL;
+ usec_t now_b;
+ int r;
+
+ assert(server);
+ assert(v);
+
+ lease = new(sd_dhcp_server_lease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ *lease = (sd_dhcp_server_lease) {
+ .n_ref = 1,
+ };
+
+ r = json_dispatch(v, use_boottime ? dispatch_table_boottime : dispatch_table_realtime, JSON_ALLOW_EXTENSIONS, lease);
+ if (r < 0)
+ return r;
+
+ r = sd_event_now(server->event, CLOCK_BOOTTIME, &now_b);
+ if (r < 0)
+ return r;
+
+ if (use_boottime) {
+ if (lease->expiration < now_b)
+ return 0; /* already expired */
+ } else {
+ usec_t now_r;
+
+ r = sd_event_now(server->event, CLOCK_REALTIME, &now_r);
+ if (r < 0)
+ return r;
+
+ if (lease->expiration < now_r)
+ return 0; /* already expired */
+
+ lease->expiration = map_clock_usec_raw(lease->expiration, now_r, now_b);
+ }
+
+ r = dhcp_server_put_lease(server, lease, /* is_static = */ false);
+ if (r == -EEXIST)
+ return 0;
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(lease);
+ return 0;
+}
+
+typedef struct SavedInfo {
+ sd_id128_t boot_id;
+ struct in_addr address;
+ uint8_t prefixlen;
+ JsonVariant *leases;
+} SavedInfo;
+
+static void saved_info_done(SavedInfo *info) {
+ if (!info)
+ return;
+
+ json_variant_unref(info->leases);
+}
+
+static int load_leases_file(int dir_fd, const char *path, SavedInfo *ret) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(ret);
+
+ r = json_parse_file_at(
+ /* f = */ NULL,
+ dir_fd,
+ path,
+ /* flags = */ 0,
+ &v,
+ /* ret_line = */ NULL,
+ /* ret_column = */ NULL);
+ if (r < 0)
+ return r;
+
+ static const JsonDispatch dispatch_lease_file_table[] = {
+ { "BootID", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(SavedInfo, boot_id), JSON_MANDATORY },
+ { "Address", JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(SavedInfo, address), JSON_MANDATORY },
+ { "PrefixLength", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint8, offsetof(SavedInfo, prefixlen), JSON_MANDATORY },
+ { "Leases", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(SavedInfo, leases), JSON_MANDATORY },
+ {}
+ };
+
+ return json_dispatch(v, dispatch_lease_file_table, JSON_ALLOW_EXTENSIONS, ret);
+}
+
+int dhcp_server_load_leases(sd_dhcp_server *server) {
+ _cleanup_(saved_info_done) SavedInfo info = {};
+ sd_id128_t boot_id;
+ size_t n, m;
+ int r;
+
+ assert(server);
+ assert(server->event);
+
+ if (!server->lease_file)
+ return 0;
+
+ r = load_leases_file(server->lease_dir_fd, server->lease_file, &info);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return r;
+
+ r = sd_id128_get_boot(&boot_id);
+ if (r < 0)
+ return r;
+
+ n = hashmap_size(server->bound_leases_by_client_id);
+
+ JsonVariant *i;
+ JSON_VARIANT_ARRAY_FOREACH(i, info.leases)
+ RET_GATHER(r, json_dispatch_dhcp_lease(server, i, /* use_boottime = */ sd_id128_equal(info.boot_id, boot_id)));
+
+ m = hashmap_size(server->bound_leases_by_client_id);
+ assert(m >= n);
+ log_dhcp_server(server, "Loaded %zu lease(s) from %s.", m - n, server->lease_file);
+
+ return r;
+}
+
+int dhcp_server_leases_file_get_server_address(
+ int dir_fd,
+ const char *path,
+ struct in_addr *ret_address,
+ uint8_t *ret_prefixlen) {
+
+ _cleanup_(saved_info_done) SavedInfo info = {};
+ int r;
+
+ if (!ret_address && !ret_prefixlen)
+ return 0;
+
+ r = load_leases_file(dir_fd, path, &info);
+ if (r < 0)
+ return r;
+
+ if (ret_address)
+ *ret_address = info.address;
+ if (ret_prefixlen)
+ *ret_prefixlen = info.prefixlen;
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index b87e4d6..c3b0f82 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -14,6 +14,7 @@
#include "dhcp-option.h"
#include "dhcp-packet.h"
#include "dhcp-server-internal.h"
+#include "dhcp-server-lease-internal.h"
#include "dns-domain.h"
#include "fd-util.h"
#include "in-addr-util.h"
@@ -21,6 +22,7 @@
#include "memory-util.h"
#include "network-common.h"
#include "ordered-set.h"
+#include "path-util.h"
#include "siphash24.h"
#include "string-util.h"
#include "unaligned.h"
@@ -29,20 +31,17 @@
#define DHCP_DEFAULT_LEASE_TIME_USEC USEC_PER_HOUR
#define DHCP_MAX_LEASE_TIME_USEC (USEC_PER_HOUR*12)
-DHCPLease *dhcp_lease_free(DHCPLease *lease) {
- if (!lease)
- return NULL;
+static void server_on_lease_change(sd_dhcp_server *server) {
+ int r;
- if (lease->server) {
- hashmap_remove_value(lease->server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease);
- hashmap_remove_value(lease->server->bound_leases_by_client_id, &lease->client_id, lease);
- hashmap_remove_value(lease->server->static_leases_by_address, UINT32_TO_PTR(lease->address), lease);
- hashmap_remove_value(lease->server->static_leases_by_client_id, &lease->client_id, lease);
- }
+ assert(server);
- free(lease->client_id.data);
- free(lease->hostname);
- return mfree(lease);
+ r = dhcp_server_save_leases(server);
+ if (r < 0)
+ log_dhcp_server_errno(server, r, "Failed to save leases, ignoring: %m");
+
+ if (server->callback)
+ server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata);
}
/* configures the server's address and subnet, and optionally the pool's size and offset into the subnet
@@ -102,13 +101,6 @@ int sd_dhcp_server_configure_pool(
server->address = address->s_addr;
server->netmask = netmask;
server->subnet = address->s_addr & netmask;
-
- /* Drop any leases associated with the old address range */
- hashmap_clear(server->bound_leases_by_address);
- hashmap_clear(server->bound_leases_by_client_id);
-
- if (server->callback)
- server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata);
}
return 0;
@@ -127,38 +119,6 @@ int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server) {
return in4_addr_is_set(&server->relay_target);
}
-void client_id_hash_func(const DHCPClientId *id, struct siphash *state) {
- assert(id);
- assert(id->length > 0);
- assert(id->data);
-
- siphash24_compress(&id->length, sizeof(id->length), state);
- siphash24_compress(id->data, id->length, state);
-}
-
-int client_id_compare_func(const DHCPClientId *a, const DHCPClientId *b) {
- int r;
-
- assert(a->length > 0);
- assert(a->data);
- assert(b->length > 0);
- assert(b->data);
-
- r = CMP(a->length, b->length);
- if (r != 0)
- return r;
-
- return memcmp(a->data, b->data, a->length);
-}
-
-DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
- dhcp_lease_hash_ops,
- DHCPClientId,
- client_id_hash_func,
- client_id_compare_func,
- DHCPLease,
- dhcp_lease_free);
-
static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) {
assert(server);
@@ -184,6 +144,9 @@ static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) {
free(server->agent_circuit_id);
free(server->agent_remote_id);
+ safe_close(server->lease_dir_fd);
+ free(server->lease_file);
+
free(server->ifname);
return mfree(server);
}
@@ -212,6 +175,7 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
.default_lease_time = DHCP_DEFAULT_LEASE_TIME_USEC,
.max_lease_time = DHCP_MAX_LEASE_TIME_USEC,
.rapid_commit = true,
+ .lease_dir_fd = -EBADF,
};
*ret = TAKE_PTR(server);
@@ -786,16 +750,8 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us
break;
case SD_DHCP_OPTION_CLIENT_IDENTIFIER:
- if (len >= 2) {
- uint8_t *data;
-
- data = memdup(option, len);
- if (!data)
- return -ENOMEM;
-
- free_and_replace(req->client_id.data, data);
- req->client_id.length = len;
- }
+ if (client_id_size_is_valid(len))
+ (void) sd_dhcp_client_id_set_raw(&req->client_id, option, len);
break;
case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
@@ -835,7 +791,6 @@ static DHCPRequest* dhcp_request_free(DHCPRequest *req) {
if (!req)
return NULL;
- free(req->client_id.data);
free(req->hostname);
return mfree(req);
}
@@ -843,6 +798,8 @@ static DHCPRequest* dhcp_request_free(DHCPRequest *req) {
DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) {
+ int r;
+
assert(req);
assert(message);
@@ -852,39 +809,39 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes
return -EBADMSG;
/* set client id based on MAC address if client did not send an explicit one */
- if (!req->client_id.data) {
- uint8_t *data;
-
- if (message->hlen == 0)
+ if (!sd_dhcp_client_id_is_set(&req->client_id)) {
+ if (!client_id_data_size_is_valid(message->hlen))
return -EBADMSG;
- data = new0(uint8_t, message->hlen + 1);
- if (!data)
- return -ENOMEM;
-
- data[0] = 0x01;
- memcpy(data + 1, message->chaddr, message->hlen);
-
- req->client_id.length = message->hlen + 1;
- req->client_id.data = data;
+ r = sd_dhcp_client_id_set(&req->client_id, /* type = */ 1, message->chaddr, message->hlen);
+ if (r < 0)
+ return r;
}
if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) {
+ uint8_t type;
+ const void *data;
+ size_t size;
+
/* See RFC2131 section 4.1.1.
* hlen and chaddr may not be set for non-ethernet interface.
* Let's try to retrieve it from the client ID. */
- if (!req->client_id.data)
+ if (!sd_dhcp_client_id_is_set(&req->client_id))
return -EBADMSG;
- if (req->client_id.length <= 1 || req->client_id.length > sizeof(message->chaddr) + 1)
+ r = sd_dhcp_client_id_get(&req->client_id, &type, &data, &size);
+ if (r < 0)
+ return r;
+
+ if (type != 1)
return -EBADMSG;
- if (req->client_id.data[0] != 0x01)
+ if (size > sizeof(message->chaddr))
return -EBADMSG;
- message->hlen = req->client_id.length - 1;
- memcpy(message->chaddr, req->client_id.data + 1, message->hlen);
+ memcpy(message->chaddr, data, size);
+ message->hlen = size;
}
if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
@@ -1020,44 +977,7 @@ static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *messag
return -EBADMSG;
}
-static int prepare_new_lease(DHCPLease **ret_lease, be32_t address, DHCPRequest *req, usec_t expiration) {
- _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL;
-
- assert(ret_lease);
- assert(address != 0);
- assert(req);
- assert(expiration != 0);
-
- lease = new(DHCPLease, 1);
- if (!lease)
- return -ENOMEM;
-
- *lease = (DHCPLease) {
- .address = address,
- .client_id.length = req->client_id.length,
- .htype = req->message->htype,
- .hlen = req->message->hlen,
- .gateway = req->message->giaddr,
- .expiration = expiration,
- };
- lease->client_id.data = memdup(req->client_id.data, req->client_id.length);
- if (!lease->client_id.data)
- return -ENOMEM;
-
- memcpy(lease->chaddr, req->message->chaddr, req->message->hlen);
-
- if (req->hostname) {
- lease->hostname = strdup(req->hostname);
- if (!lease->hostname)
- return -ENOMEM;
- }
-
- *ret_lease = TAKE_PTR(lease);
-
- return 0;
-}
-
-static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, DHCPLease *existing_lease, be32_t address) {
+static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, be32_t address) {
usec_t expiration;
int r;
@@ -1069,30 +989,9 @@ static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, DHCPLeas
if (r < 0)
return r;
- if (existing_lease) {
- assert(existing_lease->server);
- assert(existing_lease->address == address);
- existing_lease->expiration = expiration;
-
- } else {
- _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL;
-
- r = prepare_new_lease(&lease, address, req, expiration);
- if (r < 0)
- return log_dhcp_server_errno(server, r, "Failed to create new lease: %m");
-
- lease->server = server; /* This must be set just before hashmap_put(). */
-
- r = hashmap_ensure_put(&server->bound_leases_by_client_id, &dhcp_lease_hash_ops, &lease->client_id, lease);
- if (r < 0)
- return log_dhcp_server_errno(server, r, "Could not save lease: %m");
-
- r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease);
- if (r < 0)
- return log_dhcp_server_errno(server, r, "Could not save lease: %m");
-
- TAKE_PTR(lease);
- }
+ r = dhcp_server_set_lease(server, address, req, expiration);
+ if (r < 0)
+ return log_dhcp_server_errno(server, r, "Failed to create new lease: %m");
r = server_send_offer_or_ack(server, req, address, DHCP_ACK);
if (r < 0)
@@ -1100,32 +999,11 @@ static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, DHCPLeas
log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->xid));
- if (server->callback)
- server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata);
+ server_on_lease_change(server);
return DHCP_ACK;
}
-static int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) {
- DHCPLease *lease;
- usec_t time_now;
- int r;
-
- assert(server);
-
- r = sd_event_now(server->event, CLOCK_BOOTTIME, &time_now);
- if (r < 0)
- return r;
-
- HASHMAP_FOREACH(lease, server->bound_leases_by_client_id)
- if (lease->expiration < time_now) {
- log_dhcp_server(server, "CLEAN (0x%x)", be32toh(lease->address));
- dhcp_lease_free(lease);
- }
-
- return 0;
-}
-
static bool address_available(sd_dhcp_server *server, be32_t address) {
assert(server);
@@ -1137,46 +1015,12 @@ static bool address_available(sd_dhcp_server *server, be32_t address) {
return true;
}
-static int server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req, DHCPLease **ret) {
- DHCPLease *static_lease;
- _cleanup_free_ uint8_t *data = NULL;
-
- assert(server);
- assert(req);
- assert(ret);
-
- static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id);
- if (static_lease) {
- *ret = static_lease;
- return 0;
- }
-
- /* when no lease is found based on the client id fall back to chaddr */
- data = new(uint8_t, req->message->hlen + 1);
- if (!data)
- return -ENOMEM;
-
- /* set client id type to 1: Ethernet Link-Layer (RFC 2132) */
- data[0] = 0x01;
- memcpy(data + 1, req->message->chaddr, req->message->hlen);
-
- static_lease = hashmap_get(server->static_leases_by_client_id,
- &(DHCPClientId) {
- .length = req->message->hlen + 1,
- .data = data,
- });
-
- *ret = static_lease;
-
- return 0;
-}
-
#define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30)
int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp) {
_cleanup_(dhcp_request_freep) DHCPRequest *req = NULL;
_cleanup_free_ char *error_message = NULL;
- DHCPLease *existing_lease, *static_lease;
+ sd_dhcp_server_lease *existing_lease, *static_lease;
int type, r;
assert(server);
@@ -1204,9 +1048,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
return r;
existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id);
- r = server_get_static_lease(server, req, &static_lease);
- if (r < 0)
- return r;
+ static_lease = dhcp_server_get_static_lease(server, req);
switch (type) {
@@ -1220,10 +1062,19 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
return 0;
/* for now pick a random free address from the pool */
- if (static_lease)
+ if (static_lease) {
+ if (existing_lease != hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(static_lease->address)))
+ /* The address is already assigned to another host. Refusing. */
+ return 0;
+
+ /* Found a matching static lease. */
address = static_lease->address;
- else if (existing_lease)
+
+ } else if (existing_lease && address_is_in_pool(server, existing_lease->address))
+
+ /* If we previously assigned an address to the host, then reuse it. */
address = existing_lease->address;
+
else {
struct siphash state;
uint64_t hash;
@@ -1252,7 +1103,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
return 0;
if (server->rapid_commit && req->rapid_commit)
- return server_ack_request(server, req, existing_lease, address);
+ return server_ack_request(server, req, address);
r = server_send_offer_or_ack(server, req, address, DHCP_OFFER);
if (r < 0)
@@ -1321,30 +1172,24 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
/* Silently ignore Rapid Commit option in REQUEST message. */
req->rapid_commit = false;
- /* disallow our own address */
- if (address == server->address)
- return 0;
-
if (static_lease) {
- /* Found a static lease for the client ID. */
-
if (static_lease->address != address)
- /* The client requested an address which is different from the static lease. Refuse. */
+ /* The client requested an address which is different from the static lease. Refusing. */
return server_send_nak_or_ignore(server, init_reboot, req);
- return server_ack_request(server, req, existing_lease, address);
- }
-
- if (address_is_in_pool(server, address)) {
- /* The requested address is in the pool. */
-
- if (existing_lease && existing_lease->address != address)
- /* We previously assigned an address, but the client requested another one. Refuse. */
+ if (existing_lease != hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(address)))
+ /* The requested address is already assigned to another host. Refusing. */
return server_send_nak_or_ignore(server, init_reboot, req);
- return server_ack_request(server, req, existing_lease, address);
+ /* Found a static lease for the client ID. */
+ return server_ack_request(server, req, address);
}
+ if (address_is_in_pool(server, address))
+ /* The requested address is in the pool. */
+ return server_ack_request(server, req, address);
+
+ /* Refuse otherwise. */
return server_send_nak_or_ignore(server, init_reboot, req);
}
@@ -1358,10 +1203,9 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
if (existing_lease->address != req->message->ciaddr)
return 0;
- dhcp_lease_free(existing_lease);
+ sd_dhcp_server_lease_unref(existing_lease);
- if (server->callback)
- server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata);
+ server_on_lease_change(server);
return 0;
}}
@@ -1515,6 +1359,10 @@ int sd_dhcp_server_start(sd_dhcp_server *server) {
goto on_error;
}
+ r = dhcp_server_load_leases(server);
+ if (r < 0)
+ log_dhcp_server_errno(server, r, "Failed to load lease file %s, ignoring: %m", strna(server->lease_file));
+
log_dhcp_server(server, "STARTED");
return 0;
@@ -1525,7 +1373,7 @@ on_error:
}
int sd_dhcp_server_forcerenew(sd_dhcp_server *server) {
- DHCPLease *lease;
+ sd_dhcp_server_lease *lease;
int r = 0;
assert_return(server, -EINVAL);
@@ -1740,55 +1588,32 @@ int sd_dhcp_server_set_relay_agent_information(
return 0;
}
-int sd_dhcp_server_set_static_lease(
- sd_dhcp_server *server,
- const struct in_addr *address,
- uint8_t *client_id,
- size_t client_id_size) {
-
- _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL;
+int sd_dhcp_server_set_lease_file(sd_dhcp_server *server, int dir_fd, const char *path) {
int r;
assert_return(server, -EINVAL);
- assert_return(client_id, -EINVAL);
- assert_return(client_id_size > 0, -EINVAL);
+ assert_return(!path || (dir_fd >= 0 || dir_fd == AT_FDCWD), -EBADF);
assert_return(!sd_dhcp_server_is_running(server), -EBUSY);
- /* Static lease with an empty or omitted address is a valid entry,
- * the server removes any static lease with the specified mac address. */
- if (!address || address->s_addr == 0) {
- DHCPClientId c;
-
- c = (DHCPClientId) {
- .length = client_id_size,
- .data = client_id,
- };
-
- dhcp_lease_free(hashmap_get(server->static_leases_by_client_id, &c));
+ if (!path) {
+ /* When NULL, clear the previous assignment. */
+ server->lease_file = mfree(server->lease_file);
+ server->lease_dir_fd = safe_close(server->lease_dir_fd);
return 0;
}
- lease = new(DHCPLease, 1);
- if (!lease)
- return -ENOMEM;
-
- *lease = (DHCPLease) {
- .address = address->s_addr,
- .client_id.length = client_id_size,
- };
- lease->client_id.data = memdup(client_id, client_id_size);
- if (!lease->client_id.data)
- return -ENOMEM;
+ if (!path_is_safe(path))
+ return -EINVAL;
- lease->server = server; /* This must be set just before hashmap_put(). */
+ _cleanup_close_ int fd = fd_reopen(dir_fd, O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (fd < 0)
+ return fd;
- r = hashmap_ensure_put(&server->static_leases_by_client_id, &dhcp_lease_hash_ops, &lease->client_id, lease);
- if (r < 0)
- return r;
- r = hashmap_ensure_put(&server->static_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease);
+ r = free_and_strdup(&server->lease_file, path);
if (r < 0)
return r;
- TAKE_PTR(lease);
+ close_and_replace(server->lease_dir_fd, fd);
+
return 0;
}
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index c20367d..3e992d7 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -12,13 +12,12 @@
#include "alloc-util.h"
#include "device-util.h"
-#include "dhcp-identifier.h"
+#include "dhcp-duid-internal.h"
#include "dhcp6-internal.h"
#include "dhcp6-lease-internal.h"
#include "dns-domain.h"
#include "event-util.h"
#include "fd-util.h"
-#include "hexdecoct.h"
#include "hostname-util.h"
#include "in-addr-util.h"
#include "iovec-util.h"
@@ -191,10 +190,10 @@ int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *
static int client_ensure_duid(sd_dhcp6_client *client) {
assert(client);
- if (client->duid_len != 0)
+ if (sd_dhcp_duid_is_set(&client->duid))
return 0;
- return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
+ return sd_dhcp6_client_set_duid_en(client);
}
/**
@@ -208,7 +207,7 @@ int sd_dhcp6_client_set_duid_llt(sd_dhcp6_client *client, uint64_t llt_time) {
assert_return(client, -EINVAL);
assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
- r = dhcp_identifier_set_duid_llt(&client->hw_addr, client->arp_type, llt_time, &client->duid, &client->duid_len);
+ r = sd_dhcp_duid_set_llt(&client->duid, client->hw_addr.bytes, client->hw_addr.length, client->arp_type, llt_time);
if (r < 0)
return log_dhcp6_client_errno(client, r, "Failed to set DUID-LLT: %m");
@@ -221,7 +220,7 @@ int sd_dhcp6_client_set_duid_ll(sd_dhcp6_client *client) {
assert_return(client, -EINVAL);
assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
- r = dhcp_identifier_set_duid_ll(&client->hw_addr, client->arp_type, &client->duid, &client->duid_len);
+ r = sd_dhcp_duid_set_ll(&client->duid, client->hw_addr.bytes, client->hw_addr.length, client->arp_type);
if (r < 0)
return log_dhcp6_client_errno(client, r, "Failed to set DUID-LL: %m");
@@ -234,7 +233,7 @@ int sd_dhcp6_client_set_duid_en(sd_dhcp6_client *client) {
assert_return(client, -EINVAL);
assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
- r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
+ r = sd_dhcp_duid_set_en(&client->duid);
if (r < 0)
return log_dhcp6_client_errno(client, r, "Failed to set DUID-EN: %m");
@@ -247,7 +246,7 @@ int sd_dhcp6_client_set_duid_uuid(sd_dhcp6_client *client) {
assert_return(client, -EINVAL);
assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
- r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len);
+ r = sd_dhcp_duid_set_uuid(&client->duid);
if (r < 0)
return log_dhcp6_client_errno(client, r, "Failed to set DUID-UUID: %m");
@@ -261,46 +260,41 @@ int sd_dhcp6_client_set_duid_raw(sd_dhcp6_client *client, uint16_t duid_type, co
assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
assert_return(duid || duid_len == 0, -EINVAL);
- r = dhcp_identifier_set_duid_raw(duid_type, duid, duid_len, &client->duid, &client->duid_len);
+ r = sd_dhcp_duid_set(&client->duid, duid_type, duid, duid_len);
if (r < 0)
return log_dhcp6_client_errno(client, r, "Failed to set DUID: %m");
return 0;
}
-int sd_dhcp6_client_duid_as_string(
- sd_dhcp6_client *client,
- char **duid) {
- _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL;
- const char *v;
- int r;
+int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, const sd_dhcp_duid *duid) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+ assert_return(sd_dhcp_duid_is_set(duid), -EINVAL);
+
+ client->duid = *duid;
+ return 0;
+}
+int sd_dhcp6_client_get_duid(sd_dhcp6_client *client, const sd_dhcp_duid **ret) {
assert_return(client, -EINVAL);
- assert_return(client->duid_len > offsetof(struct duid, raw.data), -ENODATA);
- assert_return(duid, -EINVAL);
+ assert_return(ret, -EINVAL);
- v = duid_type_to_string(be16toh(client->duid.type));
- if (v) {
- s = strdup(v);
- if (!s)
- return -ENOMEM;
- } else {
- r = asprintf(&s, "%0x", client->duid.type);
- if (r < 0)
- return -ENOMEM;
- }
+ if (!sd_dhcp_duid_is_set(&client->duid))
+ return -ENODATA;
- t = hexmem(client->duid.raw.data, client->duid_len - offsetof(struct duid, raw.data));
- if (!t)
- return -ENOMEM;
+ *ret = &client->duid;
+ return 0;
+}
- p = strjoin(s, ":", t);
- if (!p)
- return -ENOMEM;
+int sd_dhcp6_client_get_duid_as_string(sd_dhcp6_client *client, char **ret) {
+ assert_return(client, -EINVAL);
+ assert_return(ret, -EINVAL);
- *duid = TAKE_PTR(p);
+ if (!sd_dhcp_duid_is_set(&client->duid))
+ return -ENODATA;
- return 0;
+ return sd_dhcp_duid_to_string(&client->duid, ret);
}
int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
@@ -517,7 +511,6 @@ int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable) {
int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable) {
assert_return(client, -EINVAL);
- assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
client->send_release = enable;
return 0;
@@ -750,8 +743,6 @@ static int client_append_mudurl(sd_dhcp6_client *client, uint8_t **buf, size_t *
int dhcp6_client_send_message(sd_dhcp6_client *client) {
_cleanup_free_ uint8_t *buf = NULL;
- struct in6_addr all_servers =
- IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
struct sd_dhcp6_option *j;
usec_t elapsed_usec, time_now;
be16_t elapsed_time;
@@ -825,9 +816,9 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) {
if (r < 0)
return r;
- assert(client->duid_len > 0);
+ assert(sd_dhcp_duid_is_set(&client->duid));
r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_CLIENTID,
- client->duid_len, &client->duid);
+ client->duid.size, &client->duid.duid);
if (r < 0)
return r;
@@ -846,7 +837,7 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) {
if (r < 0)
return r;
- r = dhcp6_network_send_udp_socket(client->fd, &all_servers, buf, offset);
+ r = dhcp6_network_send_udp_socket(client->fd, &IN6_ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS, buf, offset);
if (r < 0)
return r;
@@ -1337,7 +1328,7 @@ static int client_receive_message(
return 0;
}
if ((size_t) len < sizeof(DHCP6Message)) {
- log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring");
+ log_dhcp6_client(client, "Too small to be DHCPv6 message: ignoring");
return 0;
}
@@ -1413,7 +1404,7 @@ int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
r = client_send_release(client);
if (r < 0)
log_dhcp6_client_errno(client, r,
- "Failed to send DHCP6 release message, ignoring: %m");
+ "Failed to send DHCPv6 release message, ignoring: %m");
client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
@@ -1424,7 +1415,8 @@ int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
}
int sd_dhcp6_client_is_running(sd_dhcp6_client *client) {
- assert_return(client, -EINVAL);
+ if (!client)
+ return false;
return client->state != DHCP6_STATE_STOPPED;
}
diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c
index 674248b..e5d6547 100644
--- a/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/libsystemd-network/sd-dhcp6-lease.c
@@ -10,6 +10,7 @@
#include "dhcp6-lease-internal.h"
#include "network-common.h"
#include "strv.h"
+#include "unaligned.h"
#define IRT_DEFAULT (1 * USEC_PER_DAY)
#define IRT_MINIMUM (600 * USEC_PER_SEC)
@@ -866,7 +867,7 @@ static int dhcp6_lease_parse_message(
"%s message does not contain client ID. Ignoring.",
dhcp6_message_type_to_string(message->type));
- if (memcmp_nn(clientid, clientid_len, &client->duid, client->duid_len) != 0)
+ if (memcmp_nn(clientid, clientid_len, &client->duid.duid, client->duid.size) != 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
"The client ID in %s message does not match. Ignoring.",
dhcp6_message_type_to_string(message->type));
diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c
index 0cc37a6..51d2b22 100644
--- a/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/libsystemd-network/sd-ipv4acd.c
@@ -564,7 +564,8 @@ int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address) {
}
int sd_ipv4acd_is_running(sd_ipv4acd *acd) {
- assert_return(acd, false);
+ if (!acd)
+ return false;
return acd->state != IPV4ACD_STATE_INIT;
}
@@ -585,6 +586,12 @@ int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts) {
assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL);
assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
+ r = sd_event_get_state(acd->event);
+ if (r < 0)
+ return r;
+ if (r == SD_EVENT_FINISHED)
+ return -ESTALE;
+
r = arp_network_bind_raw_socket(acd->ifindex, &acd->address, &acd->mac_addr);
if (r < 0)
return r;
diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c
index a29279e..5bf9833 100644
--- a/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/libsystemd-network/sd-ipv4ll.c
@@ -206,7 +206,8 @@ int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) {
}
int sd_ipv4ll_is_running(sd_ipv4ll *ll) {
- assert_return(ll, false);
+ if (!ll)
+ return false;
return sd_ipv4acd_is_running(ll->acd);
}
diff --git a/src/libsystemd-network/sd-lldp-rx.c b/src/libsystemd-network/sd-lldp-rx.c
index 2fc9a55..74000ff 100644
--- a/src/libsystemd-network/sd-lldp-rx.c
+++ b/src/libsystemd-network/sd-lldp-rx.c
@@ -10,6 +10,7 @@
#include "ether-addr-util.h"
#include "event-util.h"
#include "fd-util.h"
+#include "json.h"
#include "lldp-neighbor.h"
#include "lldp-network.h"
#include "lldp-rx-internal.h"
@@ -490,6 +491,30 @@ int sd_lldp_rx_get_neighbors(sd_lldp_rx *lldp_rx, sd_lldp_neighbor ***ret) {
return k;
}
+int lldp_rx_build_neighbors_json(sd_lldp_rx *lldp_rx, JsonVariant **ret) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ int r;
+
+ assert(lldp_rx);
+ assert(ret);
+
+ sd_lldp_neighbor *n;
+ HASHMAP_FOREACH(n, lldp_rx->neighbor_by_id) {
+ _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+
+ r = lldp_neighbor_build_json(n, &w);
+ if (r < 0)
+ return r;
+
+ r = json_variant_append_array(&v, w);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(v);
+ return 0;
+}
+
int sd_lldp_rx_set_neighbors_max(sd_lldp_rx *lldp_rx, uint64_t m) {
assert_return(lldp_rx, -EINVAL);
assert_return(m > 0, -EINVAL);
diff --git a/src/libsystemd-network/sd-ndisc-neighbor.c b/src/libsystemd-network/sd-ndisc-neighbor.c
new file mode 100644
index 0000000..1bb6ebf
--- /dev/null
+++ b/src/libsystemd-network/sd-ndisc-neighbor.c
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <netinet/icmp6.h>
+
+#include "sd-ndisc.h"
+
+#include "alloc-util.h"
+#include "in-addr-util.h"
+#include "ndisc-internal.h"
+#include "ndisc-neighbor-internal.h"
+#include "ndisc-option.h"
+
+static sd_ndisc_neighbor* ndisc_neighbor_free(sd_ndisc_neighbor *na) {
+ if (!na)
+ return NULL;
+
+ icmp6_packet_unref(na->packet);
+ set_free(na->options);
+ return mfree(na);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_neighbor, sd_ndisc_neighbor, ndisc_neighbor_free);
+
+sd_ndisc_neighbor* ndisc_neighbor_new(ICMP6Packet *packet) {
+ sd_ndisc_neighbor *na;
+
+ assert(packet);
+
+ na = new(sd_ndisc_neighbor, 1);
+ if (!na)
+ return NULL;
+
+ *na = (sd_ndisc_neighbor) {
+ .n_ref = 1,
+ .packet = icmp6_packet_ref(packet),
+ };
+
+ return na;
+}
+
+int ndisc_neighbor_parse(sd_ndisc *nd, sd_ndisc_neighbor *na) {
+ int r;
+
+ assert(na);
+
+ if (na->packet->raw_size < sizeof(struct nd_neighbor_advert))
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Too small to be a neighbor advertisement, ignoring datagram.");
+
+ /* Neighbor advertisement packets are neatly aligned to 64-bit boundaries, hence we can access them directly */
+ const struct nd_neighbor_advert *a = (const struct nd_neighbor_advert*) na->packet->raw_packet;
+ assert(a->nd_na_type == ND_NEIGHBOR_ADVERT);
+ assert(a->nd_na_code == 0);
+
+ na->flags = a->nd_na_flags_reserved; /* the first 3 bits */
+ na->target_address = a->nd_na_target;
+
+ /* RFC 4861 section 4.4:
+ * For solicited advertisements, the Target Address field in the Neighbor Solicitation message that
+ * prompted this advertisement. For an unsolicited advertisement, the address whose link-layer
+ * address has changed. The Target Address MUST NOT be a multicast address.
+ *
+ * Here, we only check if the target address is a link-layer address (or a null address, for safety)
+ * when the message is an unsolicited neighbor advertisement. */
+ if (!FLAGS_SET(na->flags, ND_NA_FLAG_SOLICITED))
+ if (!in6_addr_is_link_local(&na->target_address) && !in6_addr_is_null(&na->target_address))
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Received ND packet with an invalid target address (%s), ignoring datagram.",
+ IN6_ADDR_TO_STRING(&na->target_address));
+
+ r = ndisc_parse_options(na->packet, &na->options);
+ if (r < 0)
+ return log_ndisc_errno(nd, r, "Failed to parse NDisc options in neighbor advertisement message, ignoring: %m");
+
+ return 0;
+}
+
+int sd_ndisc_neighbor_get_sender_address(sd_ndisc_neighbor *na, struct in6_addr *ret) {
+ assert_return(na, -EINVAL);
+
+ return icmp6_packet_get_sender_address(na->packet, ret);
+}
+
+int sd_ndisc_neighbor_get_target_address(sd_ndisc_neighbor *na, struct in6_addr *ret) {
+ assert_return(na, -EINVAL);
+
+ if (in6_addr_is_null(&na->target_address))
+ /* fall back to the sender address, for safety. */
+ return sd_ndisc_neighbor_get_sender_address(na, ret);
+
+ if (ret)
+ *ret = na->target_address;
+ return 0;
+}
+
+int sd_ndisc_neighbor_get_target_mac(sd_ndisc_neighbor *na, struct ether_addr *ret) {
+ assert_return(na, -EINVAL);
+
+ return ndisc_option_get_mac(na->options, SD_NDISC_OPTION_TARGET_LL_ADDRESS, ret);
+}
+
+int sd_ndisc_neighbor_get_flags(sd_ndisc_neighbor *na, uint32_t *ret) {
+ assert_return(na, -EINVAL);
+
+ if (ret)
+ *ret = na->flags;
+ return 0;
+}
+
+int sd_ndisc_neighbor_is_router(sd_ndisc_neighbor *na) {
+ assert_return(na, -EINVAL);
+
+ return FLAGS_SET(na->flags, ND_NA_FLAG_ROUTER);
+}
+
+int sd_ndisc_neighbor_is_solicited(sd_ndisc_neighbor *na) {
+ assert_return(na, -EINVAL);
+
+ return FLAGS_SET(na->flags, ND_NA_FLAG_SOLICITED);
+}
+
+int sd_ndisc_neighbor_is_override(sd_ndisc_neighbor *na) {
+ assert_return(na, -EINVAL);
+
+ return FLAGS_SET(na->flags, ND_NA_FLAG_OVERRIDE);
+}
diff --git a/src/libsystemd-network/sd-ndisc-redirect.c b/src/libsystemd-network/sd-ndisc-redirect.c
new file mode 100644
index 0000000..a1fceb2
--- /dev/null
+++ b/src/libsystemd-network/sd-ndisc-redirect.c
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <netinet/icmp6.h>
+
+#include "sd-ndisc.h"
+
+#include "alloc-util.h"
+#include "in-addr-util.h"
+#include "ndisc-internal.h"
+#include "ndisc-option.h"
+#include "ndisc-redirect-internal.h"
+
+static sd_ndisc_redirect* ndisc_redirect_free(sd_ndisc_redirect *rd) {
+ if (!rd)
+ return NULL;
+
+ icmp6_packet_unref(rd->packet);
+ set_free(rd->options);
+ return mfree(rd);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_redirect, sd_ndisc_redirect, ndisc_redirect_free);
+
+sd_ndisc_redirect* ndisc_redirect_new(ICMP6Packet *packet) {
+ sd_ndisc_redirect *rd;
+
+ assert(packet);
+
+ rd = new(sd_ndisc_redirect, 1);
+ if (!rd)
+ return NULL;
+
+ *rd = (sd_ndisc_redirect) {
+ .n_ref = 1,
+ .packet = icmp6_packet_ref(packet),
+ };
+
+ return rd;
+}
+
+int ndisc_redirect_parse(sd_ndisc *nd, sd_ndisc_redirect *rd) {
+ int r;
+
+ assert(rd);
+ assert(rd->packet);
+
+ if (rd->packet->raw_size < sizeof(struct nd_redirect))
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Too small to be a redirect message, ignoring.");
+
+ const struct nd_redirect *a = (const struct nd_redirect*) rd->packet->raw_packet;
+ assert(a->nd_rd_type == ND_REDIRECT);
+ assert(a->nd_rd_code == 0);
+
+ rd->target_address = a->nd_rd_target;
+ rd->destination_address = a->nd_rd_dst;
+
+ /* RFC 4861 section 8.1
+ * The ICMP Destination Address field in the redirect message does not contain a multicast address. */
+ if (in6_addr_is_null(&rd->destination_address) || in6_addr_is_multicast(&rd->destination_address))
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Received Redirect message with an invalid destination address, ignoring datagram: %m");
+
+ /* RFC 4861 section 8.1
+ * The ICMP Target Address is either a link-local address (when redirected to a router) or the same
+ * as the ICMP Destination Address (when redirected to the on-link destination). */
+ if (!in6_addr_is_link_local(&rd->target_address) && !in6_addr_equal(&rd->target_address, &rd->destination_address))
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Received Redirect message with an invalid target address, ignoring datagram: %m");
+
+ r = ndisc_parse_options(rd->packet, &rd->options);
+ if (r < 0)
+ return log_ndisc_errno(nd, r, "Failed to parse NDisc options in Redirect message, ignoring datagram: %m");
+
+ return 0;
+}
+
+int sd_ndisc_redirect_set_sender_address(sd_ndisc_redirect *rd, const struct in6_addr *addr) {
+ assert_return(rd, -EINVAL);
+
+ return icmp6_packet_set_sender_address(rd->packet, addr);
+}
+
+int sd_ndisc_redirect_get_sender_address(sd_ndisc_redirect *rd, struct in6_addr *ret) {
+ assert_return(rd, -EINVAL);
+
+ return icmp6_packet_get_sender_address(rd->packet, ret);
+}
+
+int sd_ndisc_redirect_get_target_address(sd_ndisc_redirect *rd, struct in6_addr *ret) {
+ assert_return(rd, -EINVAL);
+
+ if (in6_addr_is_null(&rd->target_address))
+ return -ENODATA;
+
+ if (ret)
+ *ret = rd->target_address;
+ return 0;
+}
+
+int sd_ndisc_redirect_get_destination_address(sd_ndisc_redirect *rd, struct in6_addr *ret) {
+ assert_return(rd, -EINVAL);
+
+ if (in6_addr_is_null(&rd->destination_address))
+ return -ENODATA;
+
+ if (ret)
+ *ret = rd->destination_address;
+ return 0;
+}
+
+int sd_ndisc_redirect_get_target_mac(sd_ndisc_redirect *rd, struct ether_addr *ret) {
+ assert_return(rd, -EINVAL);
+
+ return ndisc_option_get_mac(rd->options, SD_NDISC_OPTION_TARGET_LL_ADDRESS, ret);
+}
+
+int sd_ndisc_redirect_get_redirected_header(sd_ndisc_redirect *rd, struct ip6_hdr *ret) {
+ assert_return(rd, -EINVAL);
+
+ sd_ndisc_option *p = ndisc_option_get_by_type(rd->options, SD_NDISC_OPTION_REDIRECTED_HEADER);
+ if (!p)
+ return -ENODATA;
+
+ if (ret)
+ *ret = p->hdr;
+ return 0;
+}
diff --git a/src/libsystemd-network/sd-ndisc-router-solicit.c b/src/libsystemd-network/sd-ndisc-router-solicit.c
new file mode 100644
index 0000000..04e7c26
--- /dev/null
+++ b/src/libsystemd-network/sd-ndisc-router-solicit.c
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <netinet/icmp6.h>
+
+#include "sd-radv.h"
+
+#include "alloc-util.h"
+#include "in-addr-util.h"
+#include "ndisc-option.h"
+#include "ndisc-router-solicit-internal.h"
+#include "radv-internal.h"
+
+static sd_ndisc_router_solicit* ndisc_router_solicit_free(sd_ndisc_router_solicit *rs) {
+ if (!rs)
+ return NULL;
+
+ icmp6_packet_unref(rs->packet);
+ set_free(rs->options);
+ return mfree(rs);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_router_solicit, sd_ndisc_router_solicit, ndisc_router_solicit_free);
+
+sd_ndisc_router_solicit* ndisc_router_solicit_new(ICMP6Packet *packet) {
+ sd_ndisc_router_solicit *rs;
+
+ assert(packet);
+
+ rs = new(sd_ndisc_router_solicit, 1);
+ if (!rs)
+ return NULL;
+
+ *rs = (sd_ndisc_router_solicit) {
+ .n_ref = 1,
+ .packet = icmp6_packet_ref(packet),
+ };
+
+ return rs;
+}
+
+int ndisc_router_solicit_parse(sd_radv *ra, sd_ndisc_router_solicit *rs) {
+ int r;
+
+ assert(rs);
+ assert(rs->packet);
+
+ if (rs->packet->raw_size < sizeof(struct nd_router_solicit))
+ return log_radv_errno(ra, SYNTHETIC_ERRNO(EBADMSG),
+ "Too small to be a router solicit, ignoring.");
+
+ const struct nd_router_solicit *a = (const struct nd_router_solicit*) rs->packet->raw_packet;
+ assert(a);
+ assert(a->nd_rs_type == ND_ROUTER_SOLICIT);
+ assert(a->nd_rs_code == 0);
+
+ r = ndisc_parse_options(rs->packet, &rs->options);
+ if (r < 0)
+ return log_radv_errno(ra, r, "Failed to parse NDisc options in router solicit, ignoring datagram: %m");
+
+ /* RFC 4861 section 4.1.
+ * Source link-layer address:
+ * The link-layer address of the sender, if known. MUST NOT be included if the Source
+ * Address is the unspecified address. Otherwise, it SHOULD be included on link
+ * layers that have addresses. */
+ if (ndisc_option_get_mac(rs->options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, NULL) >= 0&&
+ sd_ndisc_router_solicit_get_sender_address(rs, NULL) == -ENODATA)
+ return log_radv_errno(ra, SYNTHETIC_ERRNO(EBADMSG),
+ "Router Solicitation message from null address unexpectedly contains source link-layer address option, ignoring datagaram.");
+
+ return 0;
+}
+
+int sd_ndisc_router_solicit_get_sender_address(sd_ndisc_router_solicit *rs, struct in6_addr *ret) {
+ assert_return(rs, -EINVAL);
+
+ return icmp6_packet_get_sender_address(rs->packet, ret);
+}
+
+int sd_ndisc_router_solicit_get_sender_mac(sd_ndisc_router_solicit *rs, struct ether_addr *ret) {
+ assert_return(rs, -EINVAL);
+
+ return ndisc_option_get_mac(rs->options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, ret);
+}
diff --git a/src/libsystemd-network/sd-ndisc-router.c b/src/libsystemd-network/sd-ndisc-router.c
new file mode 100644
index 0000000..9ca737d
--- /dev/null
+++ b/src/libsystemd-network/sd-ndisc-router.c
@@ -0,0 +1,344 @@
+/* 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 "ndisc-internal.h"
+#include "ndisc-router-internal.h"
+#include "string-table.h"
+
+static sd_ndisc_router* ndisc_router_free(sd_ndisc_router *rt) {
+ if (!rt)
+ return NULL;
+
+ icmp6_packet_unref(rt->packet);
+ set_free(rt->options);
+ return mfree(rt);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_router, sd_ndisc_router, ndisc_router_free);
+
+sd_ndisc_router* ndisc_router_new(ICMP6Packet *packet) {
+ sd_ndisc_router *rt;
+
+ assert(packet);
+
+ rt = new(sd_ndisc_router, 1);
+ if (!rt)
+ return NULL;
+
+ *rt = (sd_ndisc_router) {
+ .n_ref = 1,
+ .packet = icmp6_packet_ref(packet),
+ .iterator = ITERATOR_FIRST,
+ };
+
+ return rt;
+}
+
+int sd_ndisc_router_set_sender_address(sd_ndisc_router *rt, const struct in6_addr *addr) {
+ assert_return(rt, -EINVAL);
+
+ return icmp6_packet_set_sender_address(rt->packet, addr);
+}
+
+int sd_ndisc_router_get_sender_address(sd_ndisc_router *rt, struct in6_addr *ret) {
+ assert_return(rt, -EINVAL);
+
+ return icmp6_packet_get_sender_address(rt->packet, ret);
+}
+
+int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ return icmp6_packet_get_timestamp(rt->packet, clock, ret);
+}
+
+#define DEFINE_GET_TIMESTAMP(name) \
+ int sd_ndisc_router_##name##_timestamp( \
+ sd_ndisc_router *rt, \
+ clockid_t clock, \
+ uint64_t *ret) { \
+ \
+ usec_t s, t; \
+ int r; \
+ \
+ assert_return(rt, -EINVAL); \
+ assert_return(ret, -EINVAL); \
+ \
+ r = sd_ndisc_router_##name(rt, &s); \
+ if (r < 0) \
+ return r; \
+ \
+ r = sd_ndisc_router_get_timestamp(rt, clock, &t); \
+ if (r < 0) \
+ return r; \
+ \
+ *ret = time_span_to_stamp(s, t); \
+ return 0; \
+ }
+
+DEFINE_GET_TIMESTAMP(get_lifetime);
+DEFINE_GET_TIMESTAMP(prefix_get_valid_lifetime);
+DEFINE_GET_TIMESTAMP(prefix_get_preferred_lifetime);
+DEFINE_GET_TIMESTAMP(route_get_lifetime);
+DEFINE_GET_TIMESTAMP(rdnss_get_lifetime);
+DEFINE_GET_TIMESTAMP(dnssl_get_lifetime);
+DEFINE_GET_TIMESTAMP(prefix64_get_lifetime);
+
+int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) {
+ const struct nd_router_advert *a;
+ int r;
+
+ assert(rt);
+ assert(rt->packet);
+
+ if (rt->packet->raw_size < sizeof(struct nd_router_advert))
+ return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
+ "Too small to be a router advertisement, ignoring.");
+
+ a = (const struct nd_router_advert*) rt->packet->raw_packet;
+ assert(a->nd_ra_type == ND_ROUTER_ADVERT);
+ assert(a->nd_ra_code == 0);
+
+ rt->hop_limit = a->nd_ra_curhoplimit;
+ rt->flags = a->nd_ra_flags_reserved; /* the first 8 bits */
+ rt->lifetime_usec = be16_sec_to_usec(a->nd_ra_router_lifetime, /* max_as_infinity = */ false);
+ rt->reachable_time_usec = be32_msec_to_usec(a->nd_ra_reachable, /* mas_as_infinity = */ false);
+ rt->retransmission_time_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false);
+
+ /* RFC 4191 section 2.2
+ * Prf (Default Router Preference)
+ * 2-bit signed integer. Indicates whether to prefer this router over other default routers. If the
+ * Router Lifetime is zero, the preference value MUST be set to (00) by the sender and MUST be
+ * ignored by the receiver. If the Reserved (10) value is received, the receiver MUST treat the value
+ * as if it were (00). */
+ rt->preference = (rt->flags >> 3) & 3;
+ if (rt->preference == SD_NDISC_PREFERENCE_RESERVED)
+ rt->preference = SD_NDISC_PREFERENCE_MEDIUM;
+
+ r = ndisc_parse_options(rt->packet, &rt->options);
+ if (r < 0)
+ return log_ndisc_errno(nd, r, "Failed to parse NDisc options in router advertisement message, ignoring: %m");
+
+ return 0;
+}
+
+int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->hop_limit;
+ return 0;
+}
+
+int sd_ndisc_router_get_reachable_time(sd_ndisc_router *rt, uint64_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->reachable_time_usec;
+ return 0;
+}
+
+int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->retransmission_time_usec;
+ return 0;
+}
+
+int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ sd_ndisc_option *p = ndisc_option_get_by_type(rt->options, SD_NDISC_OPTION_FLAGS_EXTENSION);
+
+ *ret = rt->flags | (p ? p->extended_flags : 0);
+ return 0;
+}
+
+int ndisc_router_flags_to_string(uint64_t flags, char **ret) {
+ _cleanup_free_ char *s = NULL;
+
+ assert(ret);
+
+ if (FLAGS_SET(flags, ND_RA_FLAG_MANAGED) &&
+ !strextend_with_separator(&s, ", ", "managed"))
+ return -ENOMEM;
+
+ if (FLAGS_SET(flags, ND_RA_FLAG_OTHER) &&
+ !strextend_with_separator(&s, ", ", "other"))
+ return -ENOMEM;
+
+ if (FLAGS_SET(flags, ND_RA_FLAG_HOME_AGENT) &&
+ !strextend_with_separator(&s, ", ", "home-agent"))
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(s);
+ return 0;
+}
+
+int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) {
+ assert_return(rt, -EINVAL);
+
+ if (ret)
+ *ret = rt->lifetime_usec;
+
+ return rt->lifetime_usec > 0; /* Indicate if the router is still valid or not. */
+}
+
+int sd_ndisc_router_get_preference(sd_ndisc_router *rt, uint8_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->preference;
+ return 0;
+}
+
+static const char* const ndisc_router_preference_table[] = {
+ [SD_NDISC_PREFERENCE_LOW] = "low",
+ [SD_NDISC_PREFERENCE_MEDIUM] = "medium",
+ [SD_NDISC_PREFERENCE_HIGH] = "high",
+ [SD_NDISC_PREFERENCE_RESERVED] = "reserved",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(ndisc_router_preference, int);
+
+int sd_ndisc_router_get_sender_mac(sd_ndisc_router *rt, struct ether_addr *ret) {
+ assert_return(rt, -EINVAL);
+
+ return ndisc_option_get_mac(rt->options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, ret);
+}
+
+int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ sd_ndisc_option *p = ndisc_option_get_by_type(rt->options, SD_NDISC_OPTION_MTU);
+ if (!p)
+ return -ENODATA;
+
+ *ret = p->mtu;
+ return 0;
+}
+
+int sd_ndisc_router_get_captive_portal(sd_ndisc_router *rt, const char **ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ sd_ndisc_option *p = ndisc_option_get_by_type(rt->options, SD_NDISC_OPTION_CAPTIVE_PORTAL);
+ if (!p)
+ return -ENODATA;
+
+ *ret = p->captive_portal;
+ return 0;
+}
+
+int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) {
+ assert_return(rt, -EINVAL);
+
+ rt->iterator = ITERATOR_FIRST;
+ return sd_ndisc_router_option_next(rt);
+}
+
+int sd_ndisc_router_option_next(sd_ndisc_router *rt) {
+ assert_return(rt, -EINVAL);
+
+ return set_iterate(rt->options, &rt->iterator, (void**) &rt->current_option);
+}
+
+int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!rt->current_option)
+ return -ENODATA;
+
+ *ret = rt->current_option->type;
+ return 0;
+}
+
+int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) {
+ uint8_t t;
+ int r;
+
+ assert_return(rt, -EINVAL);
+
+ r = sd_ndisc_router_option_get_type(rt, &t);
+ if (r < 0)
+ return r;
+
+ return t == type;
+}
+
+int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const uint8_t **ret, size_t *ret_size) {
+ assert_return(rt, -EINVAL);
+
+ if (!rt->current_option)
+ return -ENODATA;
+
+ return ndisc_option_parse(rt->packet, rt->current_option->offset, NULL, ret_size, ret);
+}
+
+#define DEFINE_GETTER(name, type, element, element_type) \
+ int sd_ndisc_router_##name##_get_##element( \
+ sd_ndisc_router *rt, \
+ element_type *ret) { \
+ \
+ int r; \
+ \
+ assert_return(rt, -EINVAL); \
+ assert_return(ret, -EINVAL); \
+ \
+ r = sd_ndisc_router_option_is_type(rt, type); \
+ if (r < 0) \
+ return r; \
+ if (r == 0) \
+ return -EMEDIUMTYPE; \
+ \
+ *ret = rt->current_option->name.element; \
+ return 0; \
+ }
+
+DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, flags, uint8_t);
+DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, prefixlen, uint8_t);
+DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, address, struct in6_addr);
+DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, valid_lifetime, uint64_t);
+DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, preferred_lifetime, uint64_t);
+
+DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, preference, uint8_t);
+DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, prefixlen, uint8_t);
+DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, address, struct in6_addr);
+DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, lifetime, uint64_t);
+
+DEFINE_GETTER(rdnss, SD_NDISC_OPTION_RDNSS, lifetime, uint64_t);
+
+int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) {
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EMEDIUMTYPE;
+
+ *ret = rt->current_option->rdnss.addresses;
+ return (int) rt->current_option->rdnss.n_addresses;
+}
+
+DEFINE_GETTER(dnssl, SD_NDISC_OPTION_DNSSL, domains, char**);
+DEFINE_GETTER(dnssl, SD_NDISC_OPTION_DNSSL, lifetime, uint64_t);
+
+DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefixlen, uint8_t);
+DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefix, struct in6_addr);
+DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, lifetime, uint64_t);
diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c
index 1beed5d..ca15f94 100644
--- a/src/libsystemd-network/sd-ndisc.c
+++ b/src/libsystemd-network/sd-ndisc.c
@@ -9,13 +9,16 @@
#include "sd-ndisc.h"
#include "alloc-util.h"
+#include "ether-addr-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 "ndisc-neighbor-internal.h"
+#include "ndisc-redirect-internal.h"
+#include "ndisc-router-internal.h"
#include "network-common.h"
#include "random-util.h"
#include "socket-util.h"
@@ -25,13 +28,15 @@
#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",
+ [SD_NDISC_EVENT_TIMEOUT] = "timeout",
+ [SD_NDISC_EVENT_ROUTER] = "router",
+ [SD_NDISC_EVENT_NEIGHBOR] = "neighbor",
+ [SD_NDISC_EVENT_REDIRECT] = "redirect",
};
DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t);
-static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, sd_ndisc_router *rt) {
+static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, void *message) {
assert(ndisc);
assert(event >= 0 && event < _SD_NDISC_EVENT_MAX);
@@ -39,7 +44,14 @@ static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, sd_ndisc_rou
return (void) log_ndisc(ndisc, "Received '%s' event.", ndisc_event_to_string(event));
log_ndisc(ndisc, "Invoking callback for '%s' event.", ndisc_event_to_string(event));
- ndisc->callback(ndisc, event, rt, ndisc->userdata);
+ ndisc->callback(ndisc, event, message, ndisc->userdata);
+}
+
+int sd_ndisc_is_running(sd_ndisc *nd) {
+ if (!nd)
+ return false;
+
+ return sd_event_source_get_enabled(nd->recv_event_source, NULL) > 0;
}
int sd_ndisc_set_callback(
@@ -58,7 +70,7 @@ int sd_ndisc_set_callback(
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);
+ assert_return(!sd_ndisc_is_running(nd), -EBUSY);
nd->ifindex = ifindex;
return 0;
@@ -89,6 +101,18 @@ int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret) {
return 0;
}
+int sd_ndisc_set_link_local_address(sd_ndisc *nd, const struct in6_addr *addr) {
+ assert_return(nd, -EINVAL);
+ assert_return(!addr || in6_addr_is_link_local(addr), -EINVAL);
+
+ if (addr)
+ nd->link_local_addr = *addr;
+ else
+ zero(nd->link_local_addr);
+
+ return 0;
+}
+
int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
assert_return(nd, -EINVAL);
@@ -104,7 +128,7 @@ 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(!sd_ndisc_is_running(nd), -EBUSY);
assert_return(!nd->event, -EBUSY);
if (event)
@@ -123,7 +147,7 @@ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
int sd_ndisc_detach_event(sd_ndisc *nd) {
assert_return(nd, -EINVAL);
- assert_return(nd->fd < 0, -EBUSY);
+ assert_return(!sd_ndisc_is_running(nd), -EBUSY);
nd->event = sd_event_unref(nd->event);
return 0;
@@ -179,78 +203,207 @@ int sd_ndisc_new(sd_ndisc **ret) {
return 0;
}
-static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
+static int ndisc_handle_router(sd_ndisc *nd, ICMP6Packet *packet) {
+ _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
int r;
assert(nd);
- assert(rt);
+ assert(packet);
+
+ rt = ndisc_router_new(packet);
+ if (!rt)
+ return -ENOMEM;
r = ndisc_router_parse(nd, rt);
if (r < 0)
return r;
- log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %s",
- rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none",
- rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium",
- FORMAT_TIMESPAN(rt->lifetime_usec, USEC_PER_SEC));
+ (void) event_source_disable(nd->timeout_event_source);
+ (void) event_source_disable(nd->timeout_no_ra);
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *s = NULL;
+ struct in6_addr a;
+ uint64_t flags;
+ uint8_t pref;
+ usec_t lifetime;
+
+ r = sd_ndisc_router_get_sender_address(rt, &a);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_router_get_flags(rt, &flags);
+ if (r < 0)
+ return r;
+
+ r = ndisc_router_flags_to_string(flags, &s);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_router_get_preference(rt, &pref);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_router_get_lifetime(rt, &lifetime);
+ if (r < 0)
+ return r;
+
+ log_ndisc(nd, "Received Router Advertisement from %s: flags=0x%0*"PRIx64"(%s), preference=%s, lifetime=%s",
+ IN6_ADDR_TO_STRING(&a),
+ flags & UINT64_C(0x00ffffffffffff00) ? 14 : 2, flags, /* suppress too many zeros if no extension */
+ s ?: "none",
+ ndisc_router_preference_to_string(pref),
+ FORMAT_TIMESPAN(lifetime, USEC_PER_SEC));
+ }
ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
return 0;
}
+static int ndisc_handle_neighbor(sd_ndisc *nd, ICMP6Packet *packet) {
+ _cleanup_(sd_ndisc_neighbor_unrefp) sd_ndisc_neighbor *na = NULL;
+ int r;
+
+ assert(nd);
+ assert(packet);
+
+ na = ndisc_neighbor_new(packet);
+ if (!na)
+ return -ENOMEM;
+
+ r = ndisc_neighbor_parse(nd, na);
+ if (r < 0)
+ return r;
+
+ if (DEBUG_LOGGING) {
+ struct in6_addr a;
+
+ r = sd_ndisc_neighbor_get_sender_address(na, &a);
+ if (r < 0)
+ return r;
+
+ log_ndisc(nd, "Received Neighbor Advertisement from %s: Router=%s, Solicited=%s, Override=%s",
+ IN6_ADDR_TO_STRING(&a),
+ yes_no(sd_ndisc_neighbor_is_router(na) > 0),
+ yes_no(sd_ndisc_neighbor_is_solicited(na) > 0),
+ yes_no(sd_ndisc_neighbor_is_override(na) > 0));
+ }
+
+ ndisc_callback(nd, SD_NDISC_EVENT_NEIGHBOR, na);
+ return 0;
+}
+
+static int ndisc_handle_redirect(sd_ndisc *nd, ICMP6Packet *packet) {
+ _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *rd = NULL;
+ int r;
+
+ assert(nd);
+ assert(packet);
+
+ rd = ndisc_redirect_new(packet);
+ if (!rd)
+ return -ENOMEM;
+
+ r = ndisc_redirect_parse(nd, rd);
+ if (r < 0)
+ return r;
+
+ if (DEBUG_LOGGING) {
+ struct in6_addr sender, target, dest;
+
+ r = sd_ndisc_redirect_get_sender_address(rd, &sender);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_redirect_get_target_address(rd, &target);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_redirect_get_destination_address(rd, &dest);
+ if (r < 0)
+ return r;
+
+ log_ndisc(nd, "Received Redirect message from %s: Target=%s, Destination=%s",
+ IN6_ADDR_TO_STRING(&sender),
+ IN6_ADDR_TO_STRING(&target),
+ IN6_ADDR_TO_STRING(&dest));
+ }
+
+ ndisc_callback(nd, SD_NDISC_EVENT_REDIRECT, rd);
+ 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;
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
sd_ndisc *nd = ASSERT_PTR(userdata);
- ssize_t buflen;
int r;
assert(s);
assert(nd->event);
- buflen = next_datagram_size_fd(fd);
- if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
+ r = icmp6_packet_receive(fd, &packet);
+ if (r < 0) {
+ log_ndisc_errno(nd, r, "Failed to receive ICMPv6 packet, ignoring: %m");
return 0;
- if (buflen < 0) {
- log_ndisc_errno(nd, buflen, "Failed to determine datagram size to read, ignoring: %m");
+ }
+
+ /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states
+ * that hosts MUST discard messages with the null source address. */
+ if (in6_addr_is_null(&packet->sender_address)) {
+ log_ndisc(nd, "Received an ICMPv6 packet from null address, ignoring.");
return 0;
}
- rt = ndisc_router_new(buflen);
- if (!rt)
- return -ENOMEM;
+ if (in6_addr_equal(&packet->sender_address, &nd->link_local_addr)) {
+ log_ndisc(nd, "Received an ICMPv6 packet sent by the same interface, ignoring.");
+ return 0;
+ }
- r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, &rt->timestamp);
- if (ERRNO_IS_NEG_TRANSIENT(r) || ERRNO_IS_NEG_DISCONNECT(r))
+ r = icmp6_packet_get_type(packet);
+ if (r < 0) {
+ log_ndisc_errno(nd, r, "Received an invalid ICMPv6 packet, ignoring: %m");
return 0;
- if (r < 0)
- switch (r) {
- case -EADDRNOTAVAIL:
- log_ndisc(nd, "Received RA from neither link-local nor null address. Ignoring.");
- return 0;
+ }
- case -EMULTIHOP:
- log_ndisc(nd, "Received RA with invalid hop limit. Ignoring.");
- return 0;
+ switch (r) {
+ case ND_ROUTER_ADVERT:
+ (void) ndisc_handle_router(nd, packet);
+ break;
- case -EPFNOSUPPORT:
- log_ndisc(nd, "Received invalid source address from ICMPv6 socket. Ignoring.");
- return 0;
+ case ND_NEIGHBOR_ADVERT:
+ (void) ndisc_handle_neighbor(nd, packet);
+ break;
- default:
- log_ndisc_errno(nd, r, "Unexpected error while reading from ICMPv6, ignoring: %m");
- return 0;
- }
+ case ND_REDIRECT:
+ (void) ndisc_handle_redirect(nd, packet);
+ break;
- /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states
- * that hosts MUST discard messages with the null source address. */
- if (in6_addr_is_null(&rt->address))
- log_ndisc(nd, "Received RA from null address. Ignoring.");
+ default:
+ log_ndisc(nd, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r);
+ }
- (void) event_source_disable(nd->timeout_event_source);
- (void) ndisc_handle_datagram(nd, rt);
return 0;
}
+static int ndisc_send_router_solicitation(sd_ndisc *nd) {
+ static const struct nd_router_solicit header = {
+ .nd_rs_type = ND_ROUTER_SOLICIT,
+ };
+
+ _cleanup_set_free_ Set *options = NULL;
+ int r;
+
+ assert(nd);
+
+ if (!ether_addr_is_null(&nd->mac_addr)) {
+ r = ndisc_option_set_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &nd->mac_addr);
+ if (r < 0)
+ return r;
+ }
+
+ return ndisc_send(nd->fd, &IN6_ADDR_ALL_ROUTERS_MULTICAST, &header.nd_rs_hdr, options, USEC_INFINITY);
+}
+
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 +
@@ -284,7 +437,7 @@ static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
if (r < 0)
goto fail;
- r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
+ r = ndisc_send_router_solicitation(nd);
if (r < 0)
log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m",
FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
@@ -316,7 +469,7 @@ int sd_ndisc_stop(sd_ndisc *nd) {
if (!nd)
return 0;
- if (nd->fd < 0)
+ if (!sd_ndisc_is_running(nd))
return 0;
log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
@@ -325,50 +478,74 @@ int sd_ndisc_stop(sd_ndisc *nd) {
return 1;
}
-int sd_ndisc_start(sd_ndisc *nd) {
+static int ndisc_setup_recv_event(sd_ndisc *nd) {
int r;
- usec_t time_now;
- assert_return(nd, -EINVAL);
- assert_return(nd->event, -EINVAL);
- assert_return(nd->ifindex > 0, -EINVAL);
+ assert(nd);
+ assert(nd->event);
+ assert(nd->ifindex > 0);
- if (nd->fd >= 0)
- return 0;
+ _cleanup_close_ int fd = -EBADF;
+ fd = icmp6_bind(nd->ifindex, /* is_router = */ false);
+ if (fd < 0)
+ return fd;
- assert(!nd->recv_event_source);
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+ r = sd_event_add_io(nd->event, &s, fd, EPOLLIN, ndisc_recv, nd);
+ if (r < 0)
+ return r;
- r = sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now);
+ r = sd_event_source_set_priority(s, nd->event_priority);
if (r < 0)
- goto fail;
+ return r;
+
+ (void) sd_event_source_set_description(s, "ndisc-receive-router-message");
- nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
- if (nd->fd < 0)
- return nd->fd;
+ nd->fd = TAKE_FD(fd);
+ nd->recv_event_source = TAKE_PTR(s);
+ return 1;
+}
- r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
+static int ndisc_setup_timer(sd_ndisc *nd) {
+ int r;
+
+ assert(nd);
+ assert(nd->event);
+
+ r = event_reset_time_relative(nd->event, &nd->timeout_event_source,
+ CLOCK_BOOTTIME,
+ 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;
+ return r;
- r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
+ r = event_reset_time_relative(nd->event, &nd->timeout_no_ra,
+ CLOCK_BOOTTIME,
+ 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;
+ return r;
- (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
+ return 0;
+}
- r = event_reset_time(nd->event, &nd->timeout_event_source,
- CLOCK_BOOTTIME,
- time_now + USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */
- ndisc_timeout, nd,
- nd->event_priority, "ndisc-timeout", true);
+int sd_ndisc_start(sd_ndisc *nd) {
+ int r;
+
+ assert_return(nd, -EINVAL);
+ assert_return(nd->event, -EINVAL);
+ assert_return(nd->ifindex > 0, -EINVAL);
+
+ if (sd_ndisc_is_running(nd))
+ return 0;
+
+ r = ndisc_setup_recv_event(nd);
if (r < 0)
goto fail;
- r = event_reset_time(nd->event, &nd->timeout_no_ra,
- CLOCK_BOOTTIME,
- time_now + NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC,
- ndisc_timeout_no_ra, nd,
- nd->event_priority, "ndisc-timeout-no-ra", true);
+ r = ndisc_setup_timer(nd);
if (r < 0)
goto fail;
diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c
index 97d306c..c384d4e 100644
--- a/src/libsystemd-network/sd-radv.c
+++ b/src/libsystemd-network/sd-radv.c
@@ -19,6 +19,7 @@
#include "iovec-util.h"
#include "macro.h"
#include "memory-util.h"
+#include "ndisc-router-solicit-internal.h"
#include "network-common.h"
#include "radv-internal.h"
#include "random-util.h"
@@ -81,7 +82,8 @@ sd_event *sd_radv_get_event(sd_radv *ra) {
}
int sd_radv_is_running(sd_radv *ra) {
- assert_return(ra, false);
+ if (!ra)
+ return false;
return ra->state != RADV_STATE_IDLE;
}
@@ -121,30 +123,63 @@ static sd_radv *radv_free(sd_radv *ra) {
DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv, sd_radv, radv_free);
static bool router_lifetime_is_valid(usec_t lifetime_usec) {
+ assert_cc(RADV_MAX_ROUTER_LIFETIME_USEC <= UINT16_MAX * USEC_PER_SEC);
return lifetime_usec == 0 ||
(lifetime_usec >= RADV_MIN_ROUTER_LIFETIME_USEC &&
lifetime_usec <= RADV_MAX_ROUTER_LIFETIME_USEC);
}
-static int radv_send(sd_radv *ra, const struct in6_addr *dst, usec_t lifetime_usec) {
+static int radv_send_router_on_stop(sd_radv *ra) {
+ static const struct nd_router_advert adv = {
+ .nd_ra_type = ND_ROUTER_ADVERT,
+ };
+
+ _cleanup_set_free_ Set *options = NULL;
+ usec_t time_now;
+ int r;
+
+ assert(ra);
+
+ r = sd_event_now(ra->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ return r;
+
+ if (!ether_addr_is_null(&ra->mac_addr)) {
+ r = ndisc_option_set_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &ra->mac_addr);
+ if (r < 0)
+ return r;
+ }
+
+ return ndisc_send(ra->fd, &IN6_ADDR_ALL_NODES_MULTICAST, &adv.nd_ra_hdr, options, time_now);
+}
+
+static int radv_send_router(sd_radv *ra, const struct in6_addr *dst) {
+ assert(ra);
+
struct sockaddr_in6 dst_addr = {
.sin6_family = AF_INET6,
- .sin6_addr = IN6ADDR_ALL_NODES_MULTICAST_INIT,
+ .sin6_addr = IN6_ADDR_ALL_NODES_MULTICAST,
+ };
+ struct nd_router_advert adv = {
+ .nd_ra_type = ND_ROUTER_ADVERT,
+ .nd_ra_router_lifetime = usec_to_be16_sec(ra->lifetime_usec),
+ .nd_ra_reachable = usec_to_be32_msec(ra->reachable_usec),
+ .nd_ra_retransmit = usec_to_be32_msec(ra->retransmit_usec),
};
- 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,
+ .nd_opt_len = DIV_ROUND_UP(sizeof(struct nd_opt_hdr) + sizeof(struct ether_addr), 8),
},
+ .slladdr = ra->mac_addr,
};
struct nd_opt_mtu opt_mtu = {
.nd_opt_mtu_type = ND_OPT_MTU,
.nd_opt_mtu_len = 1,
+ .nd_opt_mtu_mtu = htobe32(ra->mtu),
};
/* Reserve iov space for RA header, linkaddr, MTU, N prefixes, N routes, N pref64 prefixes, RDNSS,
* DNSSL, and home agent. */
@@ -157,9 +192,6 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst, usec_t lifetime_us
usec_t time_now;
int r;
- assert(ra);
- assert(router_lifetime_is_valid(lifetime_usec));
-
r = sd_event_now(ra->event, CLOCK_BOOTTIME, &time_now);
if (r < 0)
return r;
@@ -167,25 +199,21 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst, usec_t lifetime_us
if (dst && in6_addr_is_set(dst))
dst_addr.sin6_addr = *dst;
- adv.nd_ra_type = ND_ROUTER_ADVERT;
+ /* The nd_ra_curhoplimit and nd_ra_flags_reserved fields cannot specified with nd_ra_router_lifetime
+ * simultaneously in the structured initializer in the above. */
adv.nd_ra_curhoplimit = ra->hop_limit;
- adv.nd_ra_retransmit = usec_to_be32_msec(ra->retransmit_usec);
- adv.nd_ra_flags_reserved = ra->flags;
- assert_cc(RADV_MAX_ROUTER_LIFETIME_USEC <= UINT16_MAX * USEC_PER_SEC);
- adv.nd_ra_router_lifetime = usec_to_be16_sec(lifetime_usec);
+ /* RFC 4191, Section 2.2,
+ * "...If the Router Lifetime is zero, the preference value MUST be set to (00) by the sender..." */
+ adv.nd_ra_flags_reserved = ra->flags | (ra->lifetime_usec > 0 ? (ra->preference << 3) : 0);
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;
+ /* 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))
iov[msg.msg_iovlen++] = IOVEC_MAKE(&opt_mac, sizeof(opt_mac));
- }
- if (ra->mtu > 0) {
- opt_mtu.nd_opt_mtu_mtu = htobe32(ra->mtu);
+ if (ra->mtu > 0)
iov[msg.msg_iovlen++] = IOVEC_MAKE(&opt_mtu, sizeof(opt_mtu));
- }
LIST_FOREACH(prefix, p, ra->prefixes) {
usec_t lifetime_valid_usec, lifetime_preferred_usec;
@@ -236,87 +264,90 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst, usec_t lifetime_us
return 0;
}
-static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- sd_radv *ra = ASSERT_PTR(userdata);
- struct in6_addr src;
- triple_timestamp timestamp;
+static int radv_process_packet(sd_radv *ra, ICMP6Packet *packet) {
int r;
- assert(s);
- assert(ra->event);
+ assert(ra);
+ assert(packet);
- ssize_t buflen = next_datagram_size_fd(fd);
- if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
- return 0;
- if (buflen < 0) {
- log_radv_errno(ra, buflen, "Failed to determine datagram size to read, ignoring: %m");
- return 0;
- }
+ if (icmp6_packet_get_type(packet) != ND_ROUTER_SOLICIT)
+ return log_radv_errno(ra, SYNTHETIC_ERRNO(EBADMSG), "Received ICMP6 packet with unexpected type, ignoring.");
- _cleanup_free_ char *buf = new0(char, buflen);
- if (!buf)
- return -ENOMEM;
+ _cleanup_(sd_ndisc_router_solicit_unrefp) sd_ndisc_router_solicit *rs = NULL;
+ rs = ndisc_router_solicit_new(packet);
+ if (!rs)
+ return log_oom_debug();
- r = icmp6_receive(fd, buf, buflen, &src, &timestamp);
- if (ERRNO_IS_NEG_TRANSIENT(r) || ERRNO_IS_NEG_DISCONNECT(r))
- return 0;
+ r = ndisc_router_solicit_parse(ra, rs);
if (r < 0)
- switch (r) {
- case -EADDRNOTAVAIL:
- log_radv(ra, "Received RS from neither link-local nor null address. Ignoring");
- return 0;
+ return r;
- case -EMULTIHOP:
- log_radv(ra, "Received RS with invalid hop limit. Ignoring.");
- return 0;
+ struct in6_addr src;
+ r = sd_ndisc_router_solicit_get_sender_address(rs, &src);
+ if (r == -ENODATA) /* null address is allowed */
+ return sd_radv_send(ra); /* When an unsolicited RA, we need to also update timer. */
+ if (r < 0)
+ return log_radv_errno(ra, r, "Failed to get sender address of RS, ignoring: %m");
+ if (in6_addr_equal(&src, &ra->ipv6ll))
+ /* This should be definitely caused by a misconfiguration. If we send RA to ourself, the
+ * kernel complains about that. Let's ignore the packet. */
+ return log_radv_errno(ra, SYNTHETIC_ERRNO(EADDRINUSE), "Received RS from the same interface, ignoring.");
- case -EPFNOSUPPORT:
- log_radv(ra, "Received invalid source address from ICMPv6 socket. Ignoring.");
- return 0;
+ r = radv_send_router(ra, &src);
+ if (r < 0)
+ return log_radv_errno(ra, r, "Unable to send solicited Router Advertisement to %s, ignoring: %m", IN6_ADDR_TO_STRING(&src));
- default:
- log_radv_errno(ra, r, "Unexpected error receiving from ICMPv6 socket, ignoring: %m");
- return 0;
- }
+ log_radv(ra, "Sent solicited Router Advertisement to %s.", IN6_ADDR_TO_STRING(&src));
+ return 0;
+}
- if ((size_t) buflen < sizeof(struct nd_router_solicit)) {
- log_radv(ra, "Too short packet received, ignoring");
+static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
+ sd_radv *ra = ASSERT_PTR(userdata);
+ int r;
+
+ assert(fd >= 0);
+
+ r = icmp6_packet_receive(fd, &packet);
+ if (r < 0) {
+ log_radv_errno(ra, r, "Failed to receive ICMPv6 packet, ignoring: %m");
return 0;
}
- /* TODO: if the sender address is null, check that the message does not have the source link-layer
- * address option. See RFC 4861 Section 6.1.1. */
+ (void) radv_process_packet(ra, packet);
+ return 0;
+}
- const char *addr = IN6_ADDR_TO_STRING(&src);
+static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_radv *ra = ASSERT_PTR(userdata);
- r = radv_send(ra, &src, ra->lifetime_usec);
- if (r < 0)
- log_radv_errno(ra, r, "Unable to send solicited Router Advertisement to %s, ignoring: %m", addr);
- else
- log_radv(ra, "Sent solicited Router Advertisement to %s", addr);
+ if (sd_radv_send(ra) < 0)
+ (void) sd_radv_stop(ra);
return 0;
}
-static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+int sd_radv_send(sd_radv *ra) {
usec_t min_timeout, max_timeout, time_now, timeout;
- sd_radv *ra = ASSERT_PTR(userdata);
int r;
- assert(s);
- assert(ra->event);
+ assert_return(ra, -EINVAL);
+ assert_return(ra->event, -EINVAL);
+ assert_return(sd_radv_is_running(ra), -EINVAL);
assert(router_lifetime_is_valid(ra->lifetime_usec));
r = sd_event_now(ra->event, CLOCK_BOOTTIME, &time_now);
if (r < 0)
- goto fail;
+ return r;
- r = radv_send(ra, NULL, ra->lifetime_usec);
+ r = radv_send_router(ra, NULL);
if (r < 0)
- log_radv_errno(ra, r, "Unable to send Router Advertisement, ignoring: %m");
+ return log_radv_errno(ra, r, "Unable to send Router Advertisement: %m");
+
+ ra->ra_sent++;
/* RFC 4861, Section 6.2.4, sending initial Router Advertisements */
- if (ra->ra_sent < RADV_MAX_INITIAL_RTR_ADVERTISEMENTS)
+ if (ra->ra_sent <= RADV_MAX_INITIAL_RTR_ADVERTISEMENTS)
max_timeout = RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC;
else
max_timeout = RADV_DEFAULT_MAX_TIMEOUT_USEC;
@@ -340,47 +371,64 @@ static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
assert(min_timeout <= max_timeout * 3 / 4);
timeout = min_timeout + random_u64_range(max_timeout - min_timeout);
- log_radv(ra, "Next Router Advertisement in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC));
+ log_radv(ra, "Sent unsolicited Router Advertisement. Next advertisement will be in %s.",
+ FORMAT_TIMESPAN(timeout, USEC_PER_SEC));
+
+ return event_reset_time(
+ ra->event, &ra->timeout_event_source,
+ CLOCK_BOOTTIME,
+ usec_add(time_now, timeout), MSEC_PER_SEC,
+ radv_timeout, ra,
+ ra->event_priority, "radv-timeout", true);
+}
- r = event_reset_time(ra->event, &ra->timeout_event_source,
- CLOCK_BOOTTIME,
- usec_add(time_now, timeout), MSEC_PER_SEC,
- radv_timeout, ra,
- ra->event_priority, "radv-timeout", true);
- if (r < 0)
- goto fail;
+int sd_radv_stop(sd_radv *ra) {
+ int r;
- ra->ra_sent++;
+ if (!sd_radv_is_running(ra))
+ return 0; /* Already stopped. */
- return 0;
+ log_radv(ra, "Stopping IPv6 Router Advertisement daemon");
-fail:
- sd_radv_stop(ra);
+ /* RFC 4861, Section 6.2.5:
+ * the router SHOULD transmit one or more (but not more than MAX_FINAL_RTR_ADVERTISEMENTS) final
+ * multicast Router Advertisements on the interface with a Router Lifetime field of zero. */
+ r = radv_send_router_on_stop(ra);
+ if (r < 0)
+ log_radv_errno(ra, r, "Unable to send last Router Advertisement with router lifetime set to zero, ignoring: %m");
+
+ radv_reset(ra);
+ ra->fd = safe_close(ra->fd);
+ ra->state = RADV_STATE_IDLE;
return 0;
}
-int sd_radv_stop(sd_radv *ra) {
+static int radv_setup_recv_event(sd_radv *ra) {
int r;
- if (!ra)
- return 0;
+ assert(ra);
+ assert(ra->event);
+ assert(ra->ifindex > 0);
- if (ra->state == RADV_STATE_IDLE)
- return 0;
+ _cleanup_close_ int fd = -EBADF;
+ fd = icmp6_bind(ra->ifindex, /* is_router = */ true);
+ if (fd < 0)
+ return fd;
- log_radv(ra, "Stopping IPv6 Router Advertisement daemon");
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+ r = sd_event_add_io(ra->event, &s, fd, EPOLLIN, radv_recv, ra);
+ if (r < 0)
+ return r;
- /* RFC 4861, Section 6.2.5, send at least one Router Advertisement
- with zero lifetime */
- r = radv_send(ra, NULL, 0);
+ r = sd_event_source_set_priority(s, ra->event_priority);
if (r < 0)
- log_radv_errno(ra, r, "Unable to send last Router Advertisement with router lifetime set to zero, ignoring: %m");
+ return r;
- radv_reset(ra);
- ra->fd = safe_close(ra->fd);
- ra->state = RADV_STATE_IDLE;
+ (void) sd_event_source_set_description(s, "radv-receive-message");
+ ra->fd = TAKE_FD(fd);
+ ra->recv_event_source = TAKE_PTR(s);
return 0;
}
@@ -391,8 +439,12 @@ int sd_radv_start(sd_radv *ra) {
assert_return(ra->event, -EINVAL);
assert_return(ra->ifindex > 0, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return 0;
+ if (sd_radv_is_running(ra))
+ return 0; /* Already started. */
+
+ r = radv_setup_recv_event(ra);
+ if (r < 0)
+ goto fail;
r = event_reset_time(ra->event, &ra->timeout_event_source,
CLOCK_BOOTTIME,
@@ -402,22 +454,6 @@ int sd_radv_start(sd_radv *ra) {
if (r < 0)
goto fail;
- r = icmp6_bind_router_advertisement(ra->ifindex);
- if (r < 0)
- goto fail;
-
- ra->fd = r;
-
- r = sd_event_add_io(ra->event, &ra->recv_event_source, ra->fd, EPOLLIN, radv_recv, ra);
- if (r < 0)
- goto fail;
-
- r = sd_event_source_set_priority(ra->recv_event_source, ra->event_priority);
- if (r < 0)
- goto fail;
-
- (void) sd_event_source_set_description(ra->recv_event_source, "radv-receive-message");
-
ra->state = RADV_STATE_ADVERTISING;
log_radv(ra, "Started IPv6 Router Advertisement daemon");
@@ -432,13 +468,10 @@ int sd_radv_start(sd_radv *ra) {
int sd_radv_set_ifindex(sd_radv *ra, int ifindex) {
assert_return(ra, -EINVAL);
+ assert_return(!sd_radv_is_running(ra), -EBUSY);
assert_return(ifindex > 0, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
ra->ifindex = ifindex;
-
return 0;
}
@@ -467,11 +500,20 @@ int sd_radv_get_ifname(sd_radv *ra, const char **ret) {
return 0;
}
-int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr) {
+int sd_radv_set_link_local_address(sd_radv *ra, const struct in6_addr *addr) {
assert_return(ra, -EINVAL);
+ assert_return(!addr || in6_addr_is_link_local(addr), -EINVAL);
+
+ if (addr)
+ ra->ipv6ll = *addr;
+ else
+ zero(ra->ipv6ll);
+
+ return 0;
+}
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
+int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr) {
+ assert_return(ra, -EINVAL);
if (mac_addr)
ra->mac_addr = *mac_addr;
@@ -493,22 +535,19 @@ int sd_radv_set_mtu(sd_radv *ra, uint32_t mtu) {
int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
ra->hop_limit = hop_limit;
-
return 0;
}
-int sd_radv_set_retransmit(sd_radv *ra, uint64_t usec) {
+int sd_radv_set_reachable_time(sd_radv *ra, uint64_t usec) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
+ ra->reachable_usec = usec;
+ return 0;
+}
- if (usec > RADV_MAX_RETRANSMIT_USEC)
- return -EINVAL;
+int sd_radv_set_retransmit(sd_radv *ra, uint64_t usec) {
+ assert_return(ra, -EINVAL);
ra->retransmit_usec = usec;
return 0;
@@ -517,89 +556,55 @@ int sd_radv_set_retransmit(sd_radv *ra, uint64_t usec) {
int sd_radv_set_router_lifetime(sd_radv *ra, uint64_t usec) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
if (!router_lifetime_is_valid(usec))
return -EINVAL;
- /* RFC 4191, Section 2.2, "...If the Router Lifetime is zero, the preference value MUST be set
- * to (00) by the sender..." */
- if (usec == 0 &&
- (ra->flags & (0x3 << 3)) != (SD_NDISC_PREFERENCE_MEDIUM << 3))
- return -EINVAL;
-
ra->lifetime_usec = usec;
return 0;
}
-int sd_radv_set_managed_information(sd_radv *ra, int managed) {
+int sd_radv_set_managed_information(sd_radv *ra, int b) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
- SET_FLAG(ra->flags, ND_RA_FLAG_MANAGED, managed);
-
+ SET_FLAG(ra->flags, ND_RA_FLAG_MANAGED, b);
return 0;
}
-int sd_radv_set_other_information(sd_radv *ra, int other) {
+int sd_radv_set_other_information(sd_radv *ra, int b) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
- SET_FLAG(ra->flags, ND_RA_FLAG_OTHER, other);
-
+ SET_FLAG(ra->flags, ND_RA_FLAG_OTHER, b);
return 0;
}
-int sd_radv_set_preference(sd_radv *ra, unsigned preference) {
+int sd_radv_set_preference(sd_radv *ra, uint8_t preference) {
assert_return(ra, -EINVAL);
assert_return(IN_SET(preference,
SD_NDISC_PREFERENCE_LOW,
SD_NDISC_PREFERENCE_MEDIUM,
SD_NDISC_PREFERENCE_HIGH), -EINVAL);
- /* RFC 4191, Section 2.2, "...If the Router Lifetime is zero, the preference value MUST be set
- * to (00) by the sender..." */
- if (ra->lifetime_usec == 0 && preference != SD_NDISC_PREFERENCE_MEDIUM)
- return -EINVAL;
-
- ra->flags = (ra->flags & ~(0x3 << 3)) | (preference << 3);
-
+ ra->preference = preference;
return 0;
}
int sd_radv_set_home_agent_information(sd_radv *ra, int home_agent) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
SET_FLAG(ra->flags, ND_RA_FLAG_HOME_AGENT, home_agent);
-
return 0;
}
int sd_radv_set_home_agent_preference(sd_radv *ra, uint16_t preference) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
ra->home_agent.nd_opt_home_agent_info_preference = htobe16(preference);
-
return 0;
}
int sd_radv_set_home_agent_lifetime(sd_radv *ra, uint64_t lifetime_usec) {
assert_return(ra, -EINVAL);
- if (ra->state != RADV_STATE_IDLE)
- return -EBUSY;
-
if (lifetime_usec > RADV_HOME_AGENT_MAX_LIFETIME_USEC)
return -EINVAL;
@@ -609,7 +614,6 @@ int sd_radv_set_home_agent_lifetime(sd_radv *ra, uint64_t lifetime_usec) {
int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) {
sd_radv_prefix *found = NULL;
- int r;
assert_return(ra, -EINVAL);
assert_return(p, -EINVAL);
@@ -621,19 +625,13 @@ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) {
const char *addr_p = IN6_ADDR_PREFIX_TO_STRING(&p->opt.in6_addr, p->opt.prefixlen);
LIST_FOREACH(prefix, cur, ra->prefixes) {
- r = in_addr_prefix_intersect(AF_INET6,
- (const union in_addr_union*) &cur->opt.in6_addr,
- cur->opt.prefixlen,
- (const union in_addr_union*) &p->opt.in6_addr,
- p->opt.prefixlen);
- if (r < 0)
- return r;
- if (r == 0)
- continue;
+ if (!in6_addr_prefix_intersect(&cur->opt.in6_addr, cur->opt.prefixlen,
+ &p->opt.in6_addr, p->opt.prefixlen))
+ continue; /* no intersection */
if (cur->opt.prefixlen == p->opt.prefixlen) {
found = cur;
- break;
+ break; /* same prefix */
}
return log_radv_errno(ra, SYNTHETIC_ERRNO(EEXIST),
@@ -667,19 +665,6 @@ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) {
log_radv(ra, "Added prefix %s", addr_p);
}
- if (ra->state == RADV_STATE_IDLE)
- return 0;
-
- if (ra->ra_sent == 0)
- return 0;
-
- /* If RAs have already been sent, send an RA immediately to announce the newly-added prefix */
- r = radv_send(ra, NULL, ra->lifetime_usec);
- if (r < 0)
- log_radv_errno(ra, r, "Unable to send Router Advertisement for added prefix %s, ignoring: %m", addr_p);
- else
- log_radv(ra, "Sent Router Advertisement for added/updated prefix %s.", addr_p);
-
return 0;
}
@@ -710,35 +695,17 @@ void sd_radv_remove_prefix(
int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p) {
sd_radv_route_prefix *found = NULL;
- int r;
assert_return(ra, -EINVAL);
assert_return(p, -EINVAL);
- const char *addr_p = IN6_ADDR_PREFIX_TO_STRING(&p->opt.in6_addr, p->opt.prefixlen);
-
- LIST_FOREACH(prefix, cur, ra->route_prefixes) {
- r = in_addr_prefix_intersect(AF_INET6,
- (const union in_addr_union*) &cur->opt.in6_addr,
- cur->opt.prefixlen,
- (const union in_addr_union*) &p->opt.in6_addr,
- p->opt.prefixlen);
- if (r < 0)
- return r;
- if (r == 0)
- continue;
-
- if (cur->opt.prefixlen == p->opt.prefixlen) {
+ LIST_FOREACH(prefix, cur, ra->route_prefixes)
+ if (cur->opt.prefixlen == p->opt.prefixlen &&
+ in6_addr_equal(&cur->opt.in6_addr, &p->opt.in6_addr)) {
found = cur;
break;
}
- return log_radv_errno(ra, SYNTHETIC_ERRNO(EEXIST),
- "IPv6 route prefix %s conflicts with %s, ignoring.",
- addr_p,
- IN6_ADDR_PREFIX_TO_STRING(&cur->opt.in6_addr, cur->opt.prefixlen));
- }
-
if (found) {
/* p and cur may be equivalent. First increment the reference counter. */
sd_radv_route_prefix_ref(p);
@@ -751,7 +718,7 @@ int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p) {
LIST_APPEND(prefix, ra->route_prefixes, p);
log_radv(ra, "Updated/replaced IPv6 route prefix %s (lifetime: %s)",
- strna(addr_p),
+ IN6_ADDR_PREFIX_TO_STRING(&p->opt.in6_addr, p->opt.prefixlen),
FORMAT_TIMESPAN(p->lifetime_usec, USEC_PER_SEC));
} else {
/* The route prefix is new. Let's simply add it. */
@@ -760,57 +727,26 @@ int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p) {
LIST_APPEND(prefix, ra->route_prefixes, p);
ra->n_route_prefixes++;
- log_radv(ra, "Added route prefix %s", strna(addr_p));
+ log_radv(ra, "Added route prefix %s",
+ IN6_ADDR_PREFIX_TO_STRING(&p->opt.in6_addr, p->opt.prefixlen));
}
- if (ra->state == RADV_STATE_IDLE)
- return 0;
-
- if (ra->ra_sent == 0)
- return 0;
-
- /* If RAs have already been sent, send an RA immediately to announce the newly-added route prefix */
- r = radv_send(ra, NULL, ra->lifetime_usec);
- if (r < 0)
- log_radv_errno(ra, r, "Unable to send Router Advertisement for added route prefix %s, ignoring: %m",
- strna(addr_p));
- else
- log_radv(ra, "Sent Router Advertisement for added route prefix %s.", strna(addr_p));
-
return 0;
}
int sd_radv_add_pref64_prefix(sd_radv *ra, sd_radv_pref64_prefix *p) {
sd_radv_pref64_prefix *found = NULL;
- int r;
assert_return(ra, -EINVAL);
assert_return(p, -EINVAL);
- const char *addr_p = IN6_ADDR_PREFIX_TO_STRING(&p->in6_addr, p->prefixlen);
-
- LIST_FOREACH(prefix, cur, ra->pref64_prefixes) {
- r = in_addr_prefix_intersect(AF_INET6,
- (const union in_addr_union*) &cur->in6_addr,
- cur->prefixlen,
- (const union in_addr_union*) &p->in6_addr,
- p->prefixlen);
- if (r < 0)
- return r;
- if (r == 0)
- continue;
-
- if (cur->prefixlen == p->prefixlen) {
+ LIST_FOREACH(prefix, cur, ra->pref64_prefixes)
+ if (cur->prefixlen == p->prefixlen &&
+ in6_addr_equal(&cur->in6_addr, &p->in6_addr)) {
found = cur;
break;
}
- return log_radv_errno(ra, SYNTHETIC_ERRNO(EEXIST),
- "IPv6 PREF64 prefix %s conflicts with %s, ignoring.",
- addr_p,
- IN6_ADDR_PREFIX_TO_STRING(&cur->in6_addr, cur->prefixlen));
- }
-
if (found) {
/* p and cur may be equivalent. First increment the reference counter. */
sd_radv_pref64_prefix_ref(p);
@@ -823,7 +759,7 @@ int sd_radv_add_pref64_prefix(sd_radv *ra, sd_radv_pref64_prefix *p) {
LIST_APPEND(prefix, ra->pref64_prefixes, p);
log_radv(ra, "Updated/replaced IPv6 PREF64 prefix %s (lifetime: %s)",
- strna(addr_p),
+ IN6_ADDR_PREFIX_TO_STRING(&p->in6_addr, p->prefixlen),
FORMAT_TIMESPAN(p->lifetime_usec, USEC_PER_SEC));
} else {
/* The route prefix is new. Let's simply add it. */
@@ -832,23 +768,10 @@ int sd_radv_add_pref64_prefix(sd_radv *ra, sd_radv_pref64_prefix *p) {
LIST_APPEND(prefix, ra->pref64_prefixes, p);
ra->n_pref64_prefixes++;
- log_radv(ra, "Added PREF64 prefix %s", strna(addr_p));
+ log_radv(ra, "Added PREF64 prefix %s",
+ IN6_ADDR_PREFIX_TO_STRING(&p->in6_addr, p->prefixlen));
}
- if (ra->state == RADV_STATE_IDLE)
- return 0;
-
- if (ra->ra_sent == 0)
- return 0;
-
- /* If RAs have already been sent, send an RA immediately to announce the newly-added route prefix */
- r = radv_send(ra, NULL, ra->lifetime_usec);
- if (r < 0)
- log_radv_errno(ra, r, "Unable to send Router Advertisement for added PREF64 prefix %s, ignoring: %m",
- strna(addr_p));
- else
- log_radv(ra, "Sent Router Advertisement for added PREF64 prefix %s.", strna(addr_p));
-
return 0;
}
diff --git a/src/libsystemd-network/test-acd.c b/src/libsystemd-network/test-acd.c
index 4b5ad70..6ed7260 100644
--- a/src/libsystemd-network/test-acd.c
+++ b/src/libsystemd-network/test-acd.c
@@ -1,11 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* Make sure the net/if.h header is included before any linux/ one */
+#include <net/if.h>
#include <errno.h>
+#include <linux/veth.h>
#include <stdlib.h>
#include <unistd.h>
-#include <linux/veth.h>
-#include <net/if.h>
#include "sd-event.h"
#include "sd-ipv4acd.h"
diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c
index e3f148d..5b4ce3e 100644
--- a/src/libsystemd-network/test-dhcp-client.c
+++ b/src/libsystemd-network/test-dhcp-client.c
@@ -17,7 +17,7 @@
#include "sd-event.h"
#include "alloc-util.h"
-#include "dhcp-identifier.h"
+#include "dhcp-duid-internal.h"
#include "dhcp-network.h"
#include "dhcp-option.h"
#include "dhcp-packet.h"
@@ -57,14 +57,14 @@ static void test_request_basic(sd_event *e) {
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_RETURN_EXPECTED_SE(sd_dhcp_client_set_request_option(NULL, 0) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_request_address(NULL, NULL) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(client, -42) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(client, -1) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_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);
@@ -165,19 +165,18 @@ static int check_options(uint8_t code, uint8_t len, const void *option, void *us
switch (code) {
case SD_DHCP_OPTION_CLIENT_IDENTIFIER:
{
+ sd_dhcp_duid duid;
uint32_t iaid;
- struct duid duid;
- size_t duid_len;
- assert_se(dhcp_identifier_set_duid_en(&duid, &duid_len) >= 0);
+ assert_se(sd_dhcp_duid_set_en(&duid) >= 0);
assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy = */ true, &iaid) >= 0);
- assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid_len);
+ assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid.size);
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);
+ assert_se(memcmp((uint8_t*) option + 5, &duid.duid, duid.size) == 0);
break;
}
@@ -515,7 +514,7 @@ static void test_addr_acq(sd_event *e) {
callback_recv = test_addr_acq_recv_discover;
assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
- 2 * USEC_PER_SEC, 0,
+ 30 * USEC_PER_SEC, 0,
NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0);
res = sd_dhcp_client_start(client);
diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c
index b2e6034..ecd030e 100644
--- a/src/libsystemd-network/test-dhcp-server.c
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -17,7 +17,7 @@ static void test_pool(struct in_addr *address, unsigned size, int ret) {
assert_se(sd_dhcp_server_new(&server, 1) >= 0);
- assert_se(sd_dhcp_server_configure_pool(server, address, 8, 0, size) == ret);
+ ASSERT_RETURN_IS_CRITICAL(ret >= 0, assert_se(sd_dhcp_server_configure_pool(server, address, 8, 0, size) == ret));
}
static int test_basic(bool bind_to_interface) {
@@ -41,20 +41,20 @@ static int test_basic(bool bind_to_interface) {
server->bind_to_interface = bind_to_interface;
assert_se(sd_dhcp_server_attach_event(server, event, 0) >= 0);
- assert_se(sd_dhcp_server_attach_event(server, event, 0) == -EBUSY);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_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_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_dhcp_server_configure_pool(server, &address_any, 28, 0, 0) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_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);
@@ -62,7 +62,9 @@ static int test_basic(bool bind_to_interface) {
test_pool(&address_lo, 1, 0);
r = sd_dhcp_server_start(server);
- if (r == -EPERM)
+ /* skip test if running in an environment with no full networking support, CONFIG_PACKET not
+ * compiled in kernel, nor af_packet module available. */
+ if (r == -EPERM || r == -EAFNOSUPPORT)
return r;
assert_se(r >= 0);
@@ -184,7 +186,7 @@ static void test_message_handler(void) {
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
test.option_server_id.address = htobe32(INADDR_LOOPBACK);
test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 4);
- assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
@@ -200,7 +202,7 @@ static void test_message_handler(void) {
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30);
- assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
+ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
/* request address reserved for static lease (unmatching client ID) */
test.option_client_id.id[6] = 'H';
@@ -223,7 +225,7 @@ static void test_message_handler(void) {
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
}
-static uint64_t client_id_hash_helper(DHCPClientId *id, uint8_t key[HASH_KEY_SIZE]) {
+static uint64_t client_id_hash_helper(sd_dhcp_client_id *id, uint8_t key[HASH_KEY_SIZE]) {
struct siphash state;
siphash24_init(&state, key);
@@ -233,10 +235,10 @@ static uint64_t client_id_hash_helper(DHCPClientId *id, uint8_t key[HASH_KEY_SIZ
}
static void test_client_id_hash(void) {
- DHCPClientId a = {
- .length = 4,
+ sd_dhcp_client_id a = {
+ .size = 4,
}, b = {
- .length = 4,
+ .size = 4,
};
uint8_t hash_key[HASH_KEY_SIZE] = {
'0', '1', '2', '3', '4', '5', '6', '7',
@@ -245,29 +247,25 @@ static void test_client_id_hash(void) {
log_debug("/* %s */", __func__);
- a.data = (uint8_t*)strdup("abcd");
- b.data = (uint8_t*)strdup("abcd");
+ memcpy(a.raw, "abcd", 4);
+ memcpy(b.raw, "abcd", 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));
- a.length = 3;
+ a.size = 3;
assert_se(client_id_compare_func(&a, &b) != 0);
- a.length = 4;
+ a.size = 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;
+ b.size = 3;
assert_se(client_id_compare_func(&a, &b) != 0);
- b.length = 4;
+ b.size = 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");
+ memcpy(b.raw, "abce", 4);
assert_se(client_id_compare_func(&a, &b) != 0);
-
- free(a.data);
- free(b.data);
}
static void test_static_lease(void) {
diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index ae3cdb8..7afd464 100644
--- a/src/libsystemd-network/test-dhcp6-client.c
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -13,7 +13,7 @@
#include "sd-dhcp6-client.h"
#include "sd-event.h"
-#include "dhcp-identifier.h"
+#include "dhcp-duid-internal.h"
#include "dhcp6-internal.h"
#include "dhcp6-lease-internal.h"
#include "dhcp6-protocol.h"
@@ -25,6 +25,7 @@
#include "strv.h"
#include "tests.h"
#include "time-util.h"
+#include "unaligned.h"
#define DHCP6_CLIENT_EVENT_TEST_ADVERTISED 77
#define IA_ID_BYTES \
@@ -58,8 +59,7 @@
static const struct in6_addr local_address =
{ { { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, } } };
-static const struct in6_addr mcast_address =
- IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
+static const struct in6_addr mcast_address = IN6_ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS;
static const struct in6_addr ia_na_address1 = { { { IA_NA_ADDRESS1_BYTES } } };
static const struct in6_addr ia_na_address2 = { { { IA_NA_ADDRESS2_BYTES } } };
static const struct in6_addr ia_pd_prefix1 = { { { IA_PD_PREFIX1_BYTES } } };
@@ -1027,7 +1027,7 @@ static void test_client_callback(sd_dhcp6_client *client, int event, void *userd
}
}
-int dhcp6_network_send_udp_socket(int s, struct in6_addr *a, const void *packet, size_t len) {
+int dhcp6_network_send_udp_socket(int s, const struct in6_addr *a, const void *packet, size_t len) {
log_debug("/* %s(count=%u) */", __func__, test_client_sent_message_count);
assert_se(a);
@@ -1071,7 +1071,7 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *a, const void *packet,
return len;
}
-int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *a) {
+int dhcp6_network_bind_udp_socket(int ifindex, const struct in6_addr *a) {
assert_se(ifindex == test_ifindex);
assert_se(a);
assert_se(in6_addr_equal(a, &local_address));
@@ -1086,7 +1086,7 @@ TEST(dhcp6_client) {
assert_se(sd_event_new(&e) >= 0);
assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
- 2 * USEC_PER_SEC, 0,
+ 30 * USEC_PER_SEC, 0,
NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0);
assert_se(sd_dhcp6_client_new(&client) >= 0);
diff --git a/src/libsystemd-network/test-ipv4ll-manual.c b/src/libsystemd-network/test-ipv4ll-manual.c
index 5dc6b10..b4e6d24 100644
--- a/src/libsystemd-network/test-ipv4ll-manual.c
+++ b/src/libsystemd-network/test-ipv4ll-manual.c
@@ -1,10 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include <errno.h>
+/* Make sure the net/if.h header is included before any linux/ one */
#include <net/if.h>
+#include <errno.h>
+#include <linux/veth.h>
#include <stdlib.h>
#include <unistd.h>
-#include <linux/veth.h>
#include "sd-event.h"
#include "sd-ipv4ll.h"
diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c
index bb42930..e7dbd7f 100644
--- a/src/libsystemd-network/test-ipv4ll.c
+++ b/src/libsystemd-network/test-ipv4ll.c
@@ -85,33 +85,34 @@ static void test_public_api_setters(sd_event *e) {
assert_se(sd_ipv4ll_new(&ll) == 0);
assert_se(ll);
- assert_se(sd_ipv4ll_attach_event(NULL, NULL, 0) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_ipv4ll_attach_event(ll, e, 0) == -EBUSY);
- assert_se(sd_ipv4ll_set_callback(NULL, NULL, NULL) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_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);
+ ASSERT_RETURN_EXPECTED_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);
+ ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_address(ll, &address) == -EINVAL);
address.s_addr |= htobe32(0x00FF);
- assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_ipv4ll_set_address(ll, &address) == -EINVAL);
- assert_se(sd_ipv4ll_set_address_seed(NULL, seed) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_ipv4ll_set_mac(NULL, NULL) == -EINVAL);
+
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_ipv4ll_set_ifindex(NULL, -1) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_ifindex(ll, -1) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_ifindex(ll, -99) == -EINVAL);
assert_se(sd_ipv4ll_set_ifindex(ll, 1) == 0);
assert_se(sd_ipv4ll_ref(ll) == ll);
@@ -134,17 +135,17 @@ static void test_basic_request(sd_event *e, const struct in_addr *start_address)
assert_se(sd_ipv4ll_new(&ll) == 0);
if (in4_addr_is_set(start_address))
assert_se(sd_ipv4ll_set_address(ll, start_address) >= 0);
- assert_se(sd_ipv4ll_start(ll) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_start(ll) == -EINVAL);
assert_se(sd_ipv4ll_attach_event(ll, e, 0) == 0);
- assert_se(sd_ipv4ll_start(ll) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_start(ll) == -EINVAL);
assert_se(sd_ipv4ll_set_mac(ll, &mac_addr) == 0);
- assert_se(sd_ipv4ll_start(ll) == -EINVAL);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_ipv4ll_start(ll) == -EINVAL);
assert_se(sd_ipv4ll_set_ifindex(ll, 1) == 0);
assert_se(sd_ipv4ll_start(ll) == 1);
diff --git a/src/libsystemd-network/test-ndisc-ra.c b/src/libsystemd-network/test-ndisc-ra.c
index 23abe78..b2ea8f5 100644
--- a/src/libsystemd-network/test-ndisc-ra.c
+++ b/src/libsystemd-network/test-ndisc-ra.c
@@ -11,7 +11,7 @@
#include "alloc-util.h"
#include "hexdecoct.h"
-#include "icmp6-util-unix.h"
+#include "icmp6-test-util.h"
#include "socket-util.h"
#include "strv.h"
#include "tests.h"
@@ -20,37 +20,6 @@ static struct ether_addr mac_addr = {
.ether_addr_octet = { 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53 }
};
-static uint8_t advertisement[] = {
- /* ICMPv6 Router Advertisement, no checksum */
- 0x86, 0x00, 0x00, 0x00, 0x40, 0xc0, 0x00, 0xb4,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- /* Source Link Layer Address Option */
- 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53,
- /* Prefix Information Option */
- 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4,
- 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00,
- 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- /* Prefix Information Option */
- 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x0e, 0x10,
- 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00,
- 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- /* Prefix Information Option */
- 0x03, 0x04, 0x30, 0xc0, 0x00, 0x00, 0x0e, 0x10,
- 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00,
- 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- /* Recursive DNS Server Option */
- 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
- 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- /* DNS Search List Option */
- 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
- 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74,
- 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-};
-
static bool test_stopped;
static struct {
struct in6_addr address;
@@ -103,26 +72,26 @@ TEST(radv_prefix) {
assert_se(sd_radv_prefix_new(&p) >= 0);
- assert_se(sd_radv_prefix_set_onlink(NULL, true) < 0);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_radv_prefix_set_address_autoconfiguration(NULL, true) < 0);
assert_se(sd_radv_prefix_set_address_autoconfiguration(p, true) >= 0);
assert_se(sd_radv_prefix_set_address_autoconfiguration(p, false) >= 0);
- assert_se(sd_radv_prefix_set_valid_lifetime(NULL, 1, 1) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_valid_lifetime(NULL, 1, 1) < 0);
assert_se(sd_radv_prefix_set_valid_lifetime(p, 0, 0) >= 0);
assert_se(sd_radv_prefix_set_valid_lifetime(p, 300 * USEC_PER_SEC, USEC_INFINITY) >= 0);
assert_se(sd_radv_prefix_set_valid_lifetime(p, 300 * USEC_PER_SEC, USEC_PER_YEAR) >= 0);
- assert_se(sd_radv_prefix_set_preferred_lifetime(NULL, 1, 1) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_preferred_lifetime(NULL, 1, 1) < 0);
assert_se(sd_radv_prefix_set_preferred_lifetime(p, 0, 0) >= 0);
assert_se(sd_radv_prefix_set_preferred_lifetime(p, 300 * USEC_PER_SEC, USEC_INFINITY) >= 0);
assert_se(sd_radv_prefix_set_preferred_lifetime(p, 300 * USEC_PER_SEC, USEC_PER_YEAR) >= 0);
- assert_se(sd_radv_prefix_set_prefix(NULL, NULL, 0) < 0);
- assert_se(sd_radv_prefix_set_prefix(p, NULL, 0) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_prefix(NULL, NULL, 0) < 0);
+ ASSERT_RETURN_EXPECTED_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);
@@ -131,8 +100,8 @@ TEST(radv_prefix) {
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 3) >= 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 125) >= 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 128) >= 0);
- assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 129) < 0);
- assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 255) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_prefix(p, &prefix[0].address, 129) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_prefix(p, &prefix[0].address, 255) < 0);
assert_se(!sd_radv_prefix_unref(p));
}
@@ -142,13 +111,13 @@ TEST(radv_route_prefix) {
assert_se(sd_radv_route_prefix_new(&p) >= 0);
- assert_se(sd_radv_route_prefix_set_lifetime(NULL, 1, 1) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_route_prefix_set_lifetime(NULL, 1, 1) < 0);
assert_se(sd_radv_route_prefix_set_lifetime(p, 0, 0) >= 0);
assert_se(sd_radv_route_prefix_set_lifetime(p, 300 * USEC_PER_SEC, USEC_INFINITY) >= 0);
assert_se(sd_radv_route_prefix_set_lifetime(p, 300 * USEC_PER_SEC, USEC_PER_YEAR) >= 0);
- assert_se(sd_radv_route_prefix_set_prefix(NULL, NULL, 0) < 0);
- assert_se(sd_radv_route_prefix_set_prefix(p, NULL, 0) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_route_prefix_set_prefix(NULL, NULL, 0) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_route_prefix_set_prefix(p, NULL, 0) < 0);
assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 64) >= 0);
assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 0) >= 0);
@@ -157,8 +126,8 @@ TEST(radv_route_prefix) {
assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 3) >= 0);
assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 125) >= 0);
assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 128) >= 0);
- assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 129) < 0);
- assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 255) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 129) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 255) < 0);
assert_se(!sd_radv_route_prefix_unref(p));
}
@@ -168,8 +137,8 @@ TEST(radv_pref64_prefix) {
assert_se(sd_radv_pref64_prefix_new(&p) >= 0);
- assert_se(sd_radv_pref64_prefix_set_prefix(NULL, NULL, 0, 0) < 0);
- assert_se(sd_radv_pref64_prefix_set_prefix(p, NULL, 0, 0) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_pref64_prefix_set_prefix(NULL, NULL, 0, 0) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_pref64_prefix_set_prefix(p, NULL, 0, 0) < 0);
assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 32, 300 * USEC_PER_SEC) >= 0);
assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 40, 300 * USEC_PER_SEC) >= 0);
@@ -190,60 +159,59 @@ TEST(radv) {
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_RETURN_EXPECTED_SE(sd_radv_set_ifindex(NULL, 0) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_set_ifindex(ra, 0) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_set_ifindex(ra, -1) < 0);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_radv_set_mac(NULL, NULL) < 0);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_radv_set_mtu(NULL, 0) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_set_mtu(ra, 0) < 0);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_radv_set_router_lifetime(NULL, 0) < 0);
assert_se(sd_radv_set_router_lifetime(ra, 0) >= 0);
assert_se(sd_radv_set_router_lifetime(ra, USEC_INFINITY) < 0);
assert_se(sd_radv_set_router_lifetime(ra, USEC_PER_YEAR) < 0);
assert_se(sd_radv_set_router_lifetime(ra, 300 * USEC_PER_SEC) >= 0);
- assert_se(sd_radv_set_preference(NULL, 0) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_set_preference(NULL, 0) < 0);
assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_LOW) >= 0);
assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_MEDIUM) >= 0);
assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0);
- assert_se(sd_radv_set_preference(ra, ~0) < 0);
-
- assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0);
- assert_se(sd_radv_set_router_lifetime(ra, 300 * USEC_PER_SEC) >= 0);
- assert_se(sd_radv_set_router_lifetime(ra, 0) < 0);
- assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_MEDIUM) >= 0);
- assert_se(sd_radv_set_router_lifetime(ra, 0) >= 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_set_preference(ra, ~0) < 0);
- assert_se(sd_radv_set_managed_information(NULL, true) < 0);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_radv_set_other_information(NULL, true) < 0);
assert_se(sd_radv_set_other_information(ra, true) >= 0);
assert_se(sd_radv_set_other_information(ra, false) >= 0);
- assert_se(sd_radv_set_retransmit(NULL, 10 * USEC_PER_MSEC) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_set_reachable_time(NULL, 10 * USEC_PER_MSEC) < 0);
+ assert_se(sd_radv_set_reachable_time(ra, 10 * USEC_PER_MSEC) >= 0);
+ assert_se(sd_radv_set_reachable_time(ra, 0) >= 0);
+ assert_se(sd_radv_set_reachable_time(ra, USEC_INFINITY) >= 0);
+
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_set_retransmit(NULL, 10 * USEC_PER_MSEC) < 0);
assert_se(sd_radv_set_retransmit(ra, 10 * USEC_PER_MSEC) >= 0);
assert_se(sd_radv_set_retransmit(ra, 0) >= 0);
- assert_se(sd_radv_set_retransmit(ra, usec_add(UINT32_MAX * USEC_PER_MSEC, USEC_PER_MSEC)) < 0);
+ assert_se(sd_radv_set_retransmit(ra, USEC_INFINITY) >= 0);
- assert_se(sd_radv_set_rdnss(NULL, 0, NULL, 0) < 0);
+ ASSERT_RETURN_EXPECTED_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_RETURN_EXPECTED_SE(sd_radv_set_rdnss(ra, 0, NULL, 128) < 0);
assert_se(sd_radv_set_rdnss(ra, 600 * USEC_PER_SEC, &test_rdnss, 0) >= 0);
assert_se(sd_radv_set_rdnss(ra, 600 * USEC_PER_SEC, &test_rdnss, 1) >= 0);
assert_se(sd_radv_set_rdnss(ra, 0, &test_rdnss, 1) >= 0);
@@ -254,15 +222,15 @@ TEST(radv) {
assert_se(sd_radv_set_dnssl(ra, 0, (char **)test_dnssl) >= 0);
assert_se(sd_radv_set_dnssl(ra, 600 * USEC_PER_SEC, (char **)test_dnssl) >= 0);
- assert_se(sd_radv_set_home_agent_information(NULL, true) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_set_home_agent_information(NULL, true) < 0);
assert_se(sd_radv_set_home_agent_information(ra, true) >= 0);
assert_se(sd_radv_set_home_agent_information(ra, false) >= 0);
- assert_se(sd_radv_set_home_agent_preference(NULL, 10) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_set_home_agent_preference(NULL, 10) < 0);
assert_se(sd_radv_set_home_agent_preference(ra, 10) >= 0);
assert_se(sd_radv_set_home_agent_preference(ra, 0) >= 0);
- assert_se(sd_radv_set_home_agent_lifetime(NULL, 300 * USEC_PER_SEC) < 0);
+ ASSERT_RETURN_EXPECTED_SE(sd_radv_set_home_agent_lifetime(NULL, 300 * USEC_PER_SEC) < 0);
assert_se(sd_radv_set_home_agent_lifetime(ra, 300 * USEC_PER_SEC) >= 0);
assert_se(sd_radv_set_home_agent_lifetime(ra, 0) >= 0);
assert_se(sd_radv_set_home_agent_lifetime(ra, USEC_PER_DAY) < 0);
@@ -271,49 +239,96 @@ TEST(radv) {
assert_se(!ra);
}
-static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- sd_radv *ra = userdata;
- unsigned char buf[168];
- size_t i;
+static void dump_message(const uint8_t *buf, size_t len) {
+ assert(len >= sizeof(struct nd_router_advert));
- assert_se(read(test_fd[0], &buf, sizeof(buf)) == sizeof(buf));
+ printf("Received Router Advertisement with lifetime %i sec\n",
+ (buf[6] << 8) + buf[7]);
- /* router lifetime must be zero when test is stopped */
- if (test_stopped) {
- advertisement[6] = 0x00;
- advertisement[7] = 0x00;
- }
-
- printf ("Received Router Advertisement with lifetime %i\n",
- (advertisement[6] << 8) + advertisement[7]);
-
- /* test only up to buf size, rest is not yet implemented */
- for (i = 0; i < sizeof(buf); i++) {
+ for (size_t i = 0; i < len; i++) {
if (!(i % 8))
printf("%3zu: ", i);
printf("0x%02x", buf[i]);
- assert_se(buf[i] == advertisement[i]);
-
if ((i + 1) % 8)
printf(", ");
else
printf("\n");
}
+}
- if (test_stopped) {
- sd_event *e;
+static void verify_message(const uint8_t *buf, size_t len) {
+ static const uint8_t advertisement[] = {
+ /* ICMPv6 Router Advertisement, no checksum */
+ 0x86, 0x00, 0x00, 0x00, 0x40, 0xc0, 0x00, 0xb4,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Source Link Layer Address Option */
+ 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53,
+ /* Prefix Information Option */
+ 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4,
+ 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Prefix Information Option */
+ 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x0e, 0x10,
+ 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Prefix Information Option */
+ 0x03, 0x04, 0x30, 0xc0, 0x00, 0x00, 0x0e, 0x10,
+ 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Recursive DNS Server Option */
+ 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ /* DNS Search List Option */
+ 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
+ 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74,
+ 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+
+ /* verify only up to known options, rest is not yet implemented */
+ for (size_t i = 0, m = MIN(len, sizeof(advertisement)); i < m; i++) {
+ if (test_stopped)
+ /* on stop, many header fields are zero */
+ switch (i) {
+ case 4: /* hop limit */
+ case 5: /* flags */
+ case 6 ... 7: /* router lifetime */
+ case 8 ... 11: /* reachable time */
+ case 12 ... 15: /* retrans timer */
+ assert_se(buf[i] == 0);
+ continue;
+ }
- e = sd_radv_get_event(ra);
- sd_event_exit(e, 0);
+ assert_se(buf[i] == advertisement[i]);
+ }
+}
+static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ sd_radv *ra = ASSERT_PTR(userdata);
+ _cleanup_free_ uint8_t *buf = NULL;
+ ssize_t buflen;
+
+ buflen = next_datagram_size_fd(fd);
+ assert_se(buflen >= 0);
+ assert_se(buf = new0(uint8_t, buflen));
+
+ assert_se(read(fd, buf, buflen) == buflen);
+
+ dump_message(buf, buflen);
+ verify_message(buf, buflen);
+
+ if (test_stopped) {
+ assert_se(sd_event_exit(sd_radv_get_event(ra), 0) >= 0);
return 0;
}
assert_se(sd_radv_stop(ra) >= 0);
test_stopped = true;
-
return 0;
}
@@ -365,7 +380,7 @@ TEST(ra) {
assert_se(sd_event_source_set_io_fd_own(recv_router_advertisement, true) >= 0);
assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
- 2 * USEC_PER_SEC, 0,
+ 30 * USEC_PER_SEC, 0,
NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0);
assert_se(sd_radv_start(ra) >= 0);
diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c
index d94cc1c..66aad26 100644
--- a/src/libsystemd-network/test-ndisc-rs.c
+++ b/src/libsystemd-network/test-ndisc-rs.c
@@ -12,7 +12,8 @@
#include "alloc-util.h"
#include "fd-util.h"
#include "hexdecoct.h"
-#include "icmp6-util-unix.h"
+#include "icmp6-packet.h"
+#include "icmp6-test-util.h"
#include "socket-util.h"
#include "strv.h"
#include "ndisc-internal.h"
@@ -23,21 +24,20 @@ static struct ether_addr mac_addr = {
};
static bool verbose = false;
-static sd_ndisc *test_timeout_nd;
static void router_dump(sd_ndisc_router *rt) {
struct in6_addr addr;
uint8_t hop_limit;
- usec_t t, lifetime;
+ usec_t t, lifetime, retrans_time;
uint64_t flags;
uint32_t mtu;
- unsigned preference;
+ uint8_t preference;
int r;
assert_se(rt);
log_info("--");
- assert_se(sd_ndisc_router_get_address(rt, &addr) >= 0);
+ assert_se(sd_ndisc_router_get_sender_address(rt, &addr) >= 0);
log_info("Sender: %s", IN6_ADDR_TO_STRING(&addr));
assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
@@ -65,6 +65,9 @@ static void router_dump(sd_ndisc_router *rt) {
assert_se(sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
log_info("Lifetime: %s (%s)", FORMAT_TIMESPAN(lifetime, USEC_PER_SEC), FORMAT_TIMESTAMP(t));
+ assert_se(sd_ndisc_router_get_retransmission_time(rt, &retrans_time) >= 0);
+ log_info("Retransmission Time: %s", FORMAT_TIMESPAN(retrans_time, USEC_PER_SEC));
+
if (sd_ndisc_router_get_mtu(rt, &mtu) < 0)
log_info("No MTU set");
else
@@ -88,20 +91,19 @@ static void router_dump(sd_ndisc_router *rt) {
case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
case SD_NDISC_OPTION_TARGET_LL_ADDRESS: {
_cleanup_free_ char *c = NULL;
- const void *p;
+ const uint8_t *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));
+ assert_se(c = hexmem(p + 2, n - 2));
log_info("Address: %s", c);
break;
}
case SD_NDISC_OPTION_PREFIX_INFORMATION: {
- unsigned prefix_len;
- uint8_t pfl;
+ uint8_t prefix_len, pfl;
struct in6_addr a;
assert_se(sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime) >= 0);
@@ -143,14 +145,12 @@ static void router_dump(sd_ndisc_router *rt) {
}
case SD_NDISC_OPTION_DNSSL: {
- _cleanup_strv_free_ char **l = NULL;
- int n, i;
+ char **l;
- n = sd_ndisc_router_dnssl_get_domains(rt, &l);
- assert_se(n > 0);
+ assert_se(sd_ndisc_router_dnssl_get_domains(rt, &l) >= 0);
- for (i = 0; i < n; i++)
- log_info("Domain: %s", l[i]);
+ STRV_FOREACH(s, l)
+ log_info("Domain: %s", *s);
assert_se(sd_ndisc_router_dnssl_get_lifetime(rt, &lifetime) >= 0);
assert_se(sd_ndisc_router_dnssl_get_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
@@ -164,18 +164,23 @@ static void router_dump(sd_ndisc_router *rt) {
static int send_ra(uint8_t flags) {
uint8_t advertisement[] = {
+ /* struct nd_router_advert */
0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* type = 0x03 (SD_NDISC_OPTION_PREFIX_INFORMATION), length = 32 */
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,
+ /* type = 0x19 (SD_NDISC_OPTION_RDNSS), length = 24 */
0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ /* type = 0x1f (SD_NDISC_OPTION_DNSSL), length = 24 */
0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74,
0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* type = 0x01 (SD_NDISC_OPTION_SOURCE_LL_ADDRESS), length = 8 */
0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53,
};
@@ -190,7 +195,7 @@ static int send_ra(uint8_t flags) {
return 0;
}
-static void test_callback(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router *rt, void *userdata) {
+static void test_callback_ra(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata) {
sd_event *e = userdata;
static unsigned idx = 0;
uint64_t flags_array[] = {
@@ -207,6 +212,8 @@ static void test_callback(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router
if (event != SD_NDISC_EVENT_ROUTER)
return;
+ sd_ndisc_router *rt = ASSERT_PTR(message);
+
router_dump(rt);
assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0);
@@ -221,15 +228,22 @@ static void test_callback(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router
return;
}
+ idx = 0;
sd_event_exit(e, 0);
}
+static int on_recv_rs(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
+ assert_se(icmp6_packet_receive(fd, &packet) >= 0);
+
+ return send_ra(0);
+}
+
TEST(rs) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
- send_ra_function = send_ra;
-
assert_se(sd_event_new(&e) >= 0);
assert_se(sd_ndisc_new(&nd) >= 0);
@@ -239,7 +253,7 @@ TEST(rs) {
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_ndisc_set_callback(nd, test_callback_ra, e) >= 0);
assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
30 * USEC_PER_SEC, 0,
@@ -253,19 +267,219 @@ TEST(rs) {
assert_se(sd_ndisc_start(nd) >= 0);
+ assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs, nd) >= 0);
+ assert_se(sd_event_source_set_io_fd_own(s, true) >= 0);
+
assert_se(sd_event_loop(e) >= 0);
- test_fd[1] = safe_close(test_fd[1]);
+ test_fd[1] = -EBADF;
}
-static int test_timeout_value(uint8_t flags) {
+static int send_ra_invalid_domain(uint8_t flags) {
+ uint8_t advertisement[] = {
+ /* struct nd_router_advert */
+ 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* type = 0x03 (SD_NDISC_OPTION_PREFIX_INFORMATION), length = 32 */
+ 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,
+ /* type = 0x19 (SD_NDISC_OPTION_RDNSS), length = 24 */
+ 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
+ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ /* type = 0x1f (SD_NDISC_OPTION_DNSSL), length = 112 */
+ 0x1f, 0x0e, 0xee, 0x68, 0xb0, 0xf4, 0x36, 0x39,
+ 0x2c, 0xbc, 0x0b, 0xbc, 0xa9, 0x97, 0x71, 0x37,
+ 0xad, 0x86, 0x80, 0x14, 0x2e, 0x58, 0xaa, 0x8a,
+ 0xb7, 0xa1, 0xbe, 0x91, 0x59, 0x00, 0xc4, 0xe8,
+ 0xdd, 0xd8, 0x6d, 0xe5, 0x4a, 0x7a, 0x71, 0x42,
+ 0x74, 0x45, 0x9e, 0x2e, 0xfd, 0x9d, 0x71, 0x1d,
+ 0xd0, 0xc0, 0x54, 0x0c, 0x4d, 0x1f, 0xbf, 0x90,
+ 0xd9, 0x79, 0x58, 0xc0, 0x1d, 0xa3, 0x39, 0xcf,
+ 0xb8, 0xec, 0xd2, 0xe4, 0xcd, 0xb6, 0x13, 0x2f,
+ 0xc0, 0x46, 0xe8, 0x07, 0x3f, 0xaa, 0x28, 0xa5,
+ 0x23, 0xf1, 0xf0, 0xca, 0xd3, 0x19, 0x3f, 0xfa,
+ 0x6c, 0x7c, 0xec, 0x1b, 0xcf, 0x71, 0xeb, 0xba,
+ 0x68, 0x1b, 0x8e, 0x7d, 0x93, 0x7e, 0x0b, 0x9f,
+ 0xdb, 0x12, 0x9c, 0x75, 0x22, 0x5f, 0x12, 0x00,
+ /* type = 0x01 (SD_NDISC_OPTION_SOURCE_LL_ADDRESS), length = 8 */
+ 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53,
+ };
+
+ advertisement[5] = flags;
+
+ printf("sizeof(nd_router_advert)=%zu\n", sizeof(struct nd_router_advert));
+
+ assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) ==
+ sizeof(advertisement));
+
+ if (verbose)
+ printf(" sent RA with flag 0x%02x\n", flags);
+
+ return 0;
+}
+
+static int on_recv_rs_invalid_domain(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
+ assert_se(icmp6_packet_receive(fd, &packet) >= 0);
+
+ return send_ra_invalid_domain(0);
+}
+
+TEST(invalid_domain) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+ _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
+
+ 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_ra, e) >= 0);
+
+ assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
+ 30 * USEC_PER_SEC, 0,
+ NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0);
+
+ assert_se(sd_ndisc_start(nd) >= 0);
+
+ assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_invalid_domain, nd) >= 0);
+ assert_se(sd_event_source_set_io_fd_own(s, true) >= 0);
+
+ assert_se(sd_event_loop(e) >= 0);
+
+ test_fd[1] = -EBADF;
+}
+
+static void neighbor_dump(sd_ndisc_neighbor *na) {
+ struct in6_addr addr;
+ uint32_t flags;
+
+ assert_se(na);
+
+ log_info("--");
+ assert_se(sd_ndisc_neighbor_get_sender_address(na, &addr) >= 0);
+ log_info("Sender: %s", IN6_ADDR_TO_STRING(&addr));
+
+ assert_se(sd_ndisc_neighbor_get_flags(na, &flags) >= 0);
+ log_info("Flags: Router:%s, Solicited:%s, Override: %s",
+ yes_no(flags & ND_NA_FLAG_ROUTER),
+ yes_no(flags & ND_NA_FLAG_SOLICITED),
+ yes_no(flags & ND_NA_FLAG_OVERRIDE));
+
+ assert_se(sd_ndisc_neighbor_is_router(na) == FLAGS_SET(flags, ND_NA_FLAG_ROUTER));
+ assert_se(sd_ndisc_neighbor_is_solicited(na) == FLAGS_SET(flags, ND_NA_FLAG_SOLICITED));
+ assert_se(sd_ndisc_neighbor_is_override(na) == FLAGS_SET(flags, ND_NA_FLAG_OVERRIDE));
+}
+
+static int send_na(uint32_t flags) {
+ uint8_t advertisement[] = {
+ /* struct nd_neighbor_advert */
+ 0x88, 0x00, 0xde, 0x83, 0x00, 0x00, 0x00, 0x00,
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ /* type = 0x02 (SD_NDISC_OPTION_TARGET_LL_ADDRESS), length = 8 */
+ 0x01, 0x01, 'A', 'B', 'C', '1', '2', '3',
+ };
+
+ ((struct nd_neighbor_advert*) advertisement)->nd_na_flags_reserved = flags;
+
+ assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) == sizeof(advertisement));
+ if (verbose)
+ printf(" sent NA with flag 0x%02x\n", flags);
+
+ return 0;
+}
+
+static void test_callback_na(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata) {
+ sd_event *e = userdata;
+ static unsigned idx = 0;
+ uint32_t flags_array[] = {
+ 0,
+ 0,
+ ND_NA_FLAG_ROUTER,
+ ND_NA_FLAG_SOLICITED,
+ ND_NA_FLAG_SOLICITED | ND_NA_FLAG_OVERRIDE,
+ };
+ uint32_t flags;
+
+ assert_se(nd);
+
+ if (event != SD_NDISC_EVENT_NEIGHBOR)
+ return;
+
+ sd_ndisc_neighbor *rt = ASSERT_PTR(message);
+
+ neighbor_dump(rt);
+
+ assert_se(sd_ndisc_neighbor_get_flags(rt, &flags) >= 0);
+ assert_se(flags == flags_array[idx]);
+ idx++;
+
+ if (verbose)
+ printf(" got event 0x%02" PRIx32 "\n", flags);
+
+ if (idx < ELEMENTSOF(flags_array)) {
+ send_na(flags_array[idx]);
+ return;
+ }
+
+ idx = 0;
+ sd_event_exit(e, 0);
+}
+
+static int on_recv_rs_na(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
+ assert_se(icmp6_packet_receive(fd, &packet) >= 0);
+
+ return send_na(0);
+}
+
+TEST(na) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+ _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
+
+ 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_na, e) >= 0);
+
+ assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
+ 30 * USEC_PER_SEC, 0,
+ NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0);
+
+ assert_se(sd_ndisc_start(nd) >= 0);
+
+ assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_na, nd) >= 0);
+ assert_se(sd_event_source_set_io_fd_own(s, true) >= 0);
+
+ assert_se(sd_event_loop(e) >= 0);
+
+ test_fd[1] = -EBADF;
+}
+
+static int on_recv_rs_timeout(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
+ sd_ndisc *nd = ASSERT_PTR(userdata);
static int count = 0;
static usec_t last = 0;
- sd_ndisc *nd = test_timeout_nd;
usec_t min, max;
- assert_se(nd);
- assert_se(nd->event);
+ assert_se(icmp6_packet_receive(fd, &packet) >= 0);
if (++count >= 20)
sd_event_exit(nd->event, 0);
@@ -309,17 +523,14 @@ static int test_timeout_value(uint8_t flags) {
TEST(timeout) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
- send_ra_function = test_timeout_value;
-
assert_se(sd_event_new(&e) >= 0);
assert_se(sd_ndisc_new(&nd) >= 0);
assert_se(nd);
- test_timeout_nd = nd;
-
assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0);
assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0);
@@ -331,9 +542,12 @@ TEST(timeout) {
assert_se(sd_ndisc_start(nd) >= 0);
+ assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_timeout, nd) >= 0);
+ assert_se(sd_event_source_set_io_fd_own(s, true) >= 0);
+
assert_se(sd_event_loop(e) >= 0);
- test_fd[1] = safe_close(test_fd[1]);
+ test_fd[1] = -EBADF;
}
DEFINE_TEST_MAIN(LOG_DEBUG);
diff --git a/src/libsystemd-network/test-ndisc-send.c b/src/libsystemd-network/test-ndisc-send.c
new file mode 100644
index 0000000..1b1b27d
--- /dev/null
+++ b/src/libsystemd-network/test-ndisc-send.c
@@ -0,0 +1,449 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <getopt.h>
+
+#include "build.h"
+#include "ether-addr-util.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "icmp6-util.h"
+#include "in-addr-util.h"
+#include "main-func.h"
+#include "ndisc-option.h"
+#include "netlink-util.h"
+#include "network-common.h"
+#include "parse-util.h"
+#include "socket-util.h"
+#include "strv.h"
+#include "time-util.h"
+
+static int arg_ifindex = 0;
+static int arg_icmp6_type = 0;
+static union in_addr_union arg_dest = IN_ADDR_NULL;
+static uint8_t arg_hop_limit = 0;
+static uint8_t arg_ra_flags = 0;
+static uint8_t arg_preference = false;
+static usec_t arg_lifetime = 0;
+static usec_t arg_reachable = 0;
+static usec_t arg_retransmit = 0;
+static uint32_t arg_na_flags = 0;
+static union in_addr_union arg_target_address = IN_ADDR_NULL;
+static union in_addr_union arg_redirect_destination = IN_ADDR_NULL;
+static bool arg_set_source_mac = false;
+static struct ether_addr arg_source_mac = {};
+static bool arg_set_target_mac = false;
+static struct ether_addr arg_target_mac = {};
+static struct ip6_hdr *arg_redirected_header = NULL;
+static bool arg_set_mtu = false;
+static uint32_t arg_mtu = 0;
+
+STATIC_DESTRUCTOR_REGISTER(arg_redirected_header, freep);
+
+static int parse_icmp6_type(const char *str) {
+ if (STR_IN_SET(str, "router-solicit", "rs", "RS"))
+ return ND_ROUTER_SOLICIT;
+ if (STR_IN_SET(str, "router-advertisement", "ra", "RA"))
+ return ND_ROUTER_ADVERT;
+ if (STR_IN_SET(str, "neighbor-solicit", "ns", "NS"))
+ return ND_NEIGHBOR_SOLICIT;
+ if (STR_IN_SET(str, "neighbor-advertisement", "na", "NA"))
+ return ND_NEIGHBOR_ADVERT;
+ if (STR_IN_SET(str, "redirect", "rd", "RD"))
+ return ND_REDIRECT;
+ return -EINVAL;
+}
+
+static int parse_preference(const char *str) {
+ if (streq(str, "low"))
+ return SD_NDISC_PREFERENCE_LOW;
+ if (streq(str, "medium"))
+ return SD_NDISC_PREFERENCE_MEDIUM;
+ if (streq(str, "high"))
+ return SD_NDISC_PREFERENCE_HIGH;
+ if (streq(str, "reserved"))
+ return SD_NDISC_PREFERENCE_RESERVED;
+ return -EINVAL;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_RA_HOP_LIMIT,
+ ARG_RA_MANAGED,
+ ARG_RA_OTHER,
+ ARG_RA_HOME_AGENT,
+ ARG_RA_PREFERENCE,
+ ARG_RA_LIFETIME,
+ ARG_RA_REACHABLE,
+ ARG_RA_RETRANSMIT,
+ ARG_NA_ROUTER,
+ ARG_NA_SOLICITED,
+ ARG_NA_OVERRIDE,
+ ARG_TARGET_ADDRESS,
+ ARG_REDIRECT_DESTINATION,
+ ARG_OPTION_SOURCE_LL,
+ ARG_OPTION_TARGET_LL,
+ ARG_OPTION_REDIRECTED_HEADER,
+ ARG_OPTION_MTU,
+ };
+
+ static const struct option options[] = {
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "interface", required_argument, NULL, 'i' },
+ { "type", required_argument, NULL, 't' },
+ { "dest", required_argument, NULL, 'd' },
+ /* For Router Advertisement */
+ { "hop-limit", required_argument, NULL, ARG_RA_HOP_LIMIT },
+ { "managed", required_argument, NULL, ARG_RA_MANAGED },
+ { "other", required_argument, NULL, ARG_RA_OTHER },
+ { "home-agent", required_argument, NULL, ARG_RA_HOME_AGENT },
+ { "preference", required_argument, NULL, ARG_RA_PREFERENCE },
+ { "lifetime", required_argument, NULL, ARG_RA_LIFETIME },
+ { "reachable-time", required_argument, NULL, ARG_RA_REACHABLE },
+ { "retransmit-timer", required_argument, NULL, ARG_RA_RETRANSMIT },
+ /* For Neighbor Advertisement */
+ { "is-router", required_argument, NULL, ARG_NA_ROUTER },
+ { "is-solicited", required_argument, NULL, ARG_NA_SOLICITED },
+ { "is-override", required_argument, NULL, ARG_NA_OVERRIDE },
+ /* For Neighbor Solicit, Neighbor Advertisement, and Redirect */
+ { "target-address", required_argument, NULL, ARG_TARGET_ADDRESS },
+ /* For Redirect */
+ { "redirect-destination", required_argument, NULL, ARG_REDIRECT_DESTINATION },
+ /* Options */
+ { "source-ll-address", required_argument, NULL, ARG_OPTION_SOURCE_LL },
+ { "target-ll-address", required_argument, NULL, ARG_OPTION_TARGET_LL },
+ { "redirected-header", required_argument, NULL, ARG_OPTION_REDIRECTED_HEADER },
+ { "mtu", required_argument, NULL, ARG_OPTION_MTU },
+ {}
+ };
+
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "i:t:d:", options, NULL)) >= 0) {
+
+ switch (c) {
+
+ case ARG_VERSION:
+ return version();
+
+ case 'i':
+ r = rtnl_resolve_interface_or_warn(&rtnl, optarg);
+ if (r < 0)
+ return r;
+ arg_ifindex = r;
+ break;
+
+ case 't':
+ r = parse_icmp6_type(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse message type: %m");
+ arg_icmp6_type = r;
+ break;
+
+ case 'd':
+ r = in_addr_from_string(AF_INET6, optarg, &arg_dest);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse destination address: %m");
+ if (!in6_addr_is_link_local(&arg_dest.in6))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "The destination address %s is not a link-local address.", optarg);
+ break;
+
+ case ARG_RA_HOP_LIMIT:
+ r = safe_atou8(optarg, &arg_hop_limit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse hop limit: %m");
+ break;
+
+ case ARG_RA_MANAGED:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse managed flag: %m");
+ SET_FLAG(arg_ra_flags, ND_RA_FLAG_MANAGED, r);
+ break;
+
+ case ARG_RA_OTHER:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse other flag: %m");
+ SET_FLAG(arg_ra_flags, ND_RA_FLAG_OTHER, r);
+ break;
+
+ case ARG_RA_HOME_AGENT:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse home-agent flag: %m");
+ SET_FLAG(arg_ra_flags, ND_RA_FLAG_HOME_AGENT, r);
+ break;
+
+ case ARG_RA_PREFERENCE:
+ r = parse_preference(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse preference: %m");
+ arg_preference = r;
+ break;
+
+ case ARG_RA_LIFETIME:
+ r = parse_sec(optarg, &arg_lifetime);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse lifetime: %m");
+ break;
+
+ case ARG_RA_REACHABLE:
+ r = parse_sec(optarg, &arg_reachable);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse reachable time: %m");
+ break;
+
+ case ARG_RA_RETRANSMIT:
+ r = parse_sec(optarg, &arg_retransmit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse retransmit timer: %m");
+ break;
+
+ case ARG_NA_ROUTER:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse is-router flag: %m");
+ SET_FLAG(arg_na_flags, ND_NA_FLAG_ROUTER, r);
+ break;
+
+ case ARG_NA_SOLICITED:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse is-solicited flag: %m");
+ SET_FLAG(arg_na_flags, ND_NA_FLAG_SOLICITED, r);
+ break;
+
+ case ARG_NA_OVERRIDE:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse is-override flag: %m");
+ SET_FLAG(arg_na_flags, ND_NA_FLAG_OVERRIDE, r);
+ break;
+
+ case ARG_TARGET_ADDRESS:
+ r = in_addr_from_string(AF_INET6, optarg, &arg_target_address);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse target address: %m");
+ break;
+
+ case ARG_REDIRECT_DESTINATION:
+ r = in_addr_from_string(AF_INET6, optarg, &arg_redirect_destination);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse destination address: %m");
+ break;
+
+ case ARG_OPTION_SOURCE_LL:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse source LL address option: %m");
+ arg_set_source_mac = r;
+ break;
+
+ case ARG_OPTION_TARGET_LL:
+ r = parse_ether_addr(optarg, &arg_target_mac);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse target LL address option: %m");
+ arg_set_target_mac = true;
+ break;
+
+ case ARG_OPTION_REDIRECTED_HEADER: {
+ _cleanup_free_ void *p = NULL;
+ size_t len;
+
+ r = unbase64mem(optarg, &p, &len);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse redirected header: %m");
+
+ if (len < sizeof(struct ip6_hdr))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid redirected header.");
+
+ arg_redirected_header = TAKE_PTR(p);
+ break;
+ }
+ case ARG_OPTION_MTU:
+ r = safe_atou32(optarg, &arg_mtu);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse MTU: %m");
+ arg_set_mtu = true;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+ }
+
+ if (arg_ifindex <= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--interface/-i option is mandatory.");
+
+ if (arg_icmp6_type <= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--type/-t option is mandatory.");
+
+ if (in6_addr_is_null(&arg_dest.in6)) {
+ if (IN_SET(arg_icmp6_type, ND_ROUTER_ADVERT, ND_NEIGHBOR_ADVERT, ND_REDIRECT))
+ arg_dest.in6 = IN6_ADDR_ALL_NODES_MULTICAST;
+ else
+ arg_dest.in6 = IN6_ADDR_ALL_ROUTERS_MULTICAST;
+ }
+
+ if (arg_set_source_mac) {
+ struct hw_addr_data hw_addr;
+
+ r = rtnl_get_link_info(&rtnl, arg_ifindex,
+ /* ret_iftype = */ NULL,
+ /* ret_flags = */ NULL,
+ /* ret_kind = */ NULL,
+ &hw_addr,
+ /* ret_permanent_hw_addr = */ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get the source link-layer address: %m");
+
+ if (hw_addr.length != sizeof(struct ether_addr))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Unsupported hardware address length %zu: %m",
+ hw_addr.length);
+
+ arg_source_mac = hw_addr.ether;
+ }
+
+ return 1;
+}
+
+static int send_icmp6(int fd, const struct icmp6_hdr *hdr) {
+ _cleanup_set_free_ Set *options = NULL;
+ int r;
+
+ assert(fd >= 0);
+ assert(hdr);
+
+ if (arg_set_source_mac) {
+ r = ndisc_option_add_link_layer_address(&options, 0, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &arg_source_mac);
+ if (r < 0)
+ return r;
+ }
+
+ if (arg_set_target_mac) {
+ r = ndisc_option_add_link_layer_address(&options, 0, SD_NDISC_OPTION_TARGET_LL_ADDRESS, &arg_target_mac);
+ if (r < 0)
+ return r;
+ }
+
+ if (arg_redirected_header) {
+ r = ndisc_option_add_redirected_header(&options, 0, arg_redirected_header);
+ if (r < 0)
+ return r;
+ }
+
+ if (arg_set_mtu) {
+ r = ndisc_option_add_mtu(&options, 0, arg_mtu);
+ if (r < 0)
+ return r;
+ }
+
+ return ndisc_send(fd, &arg_dest.in6, hdr, options, now(CLOCK_BOOTTIME));
+}
+
+static int send_router_solicit(int fd) {
+ struct nd_router_solicit hdr = {
+ .nd_rs_type = ND_ROUTER_SOLICIT,
+ };
+
+ assert(fd >= 0);
+
+ return send_icmp6(fd, &hdr.nd_rs_hdr);
+}
+
+static int send_router_advertisement(int fd) {
+ struct nd_router_advert hdr = {
+ .nd_ra_type = ND_ROUTER_ADVERT,
+ .nd_ra_router_lifetime = usec_to_be16_sec(arg_lifetime),
+ .nd_ra_reachable = usec_to_be32_msec(arg_reachable),
+ .nd_ra_retransmit = usec_to_be32_msec(arg_retransmit),
+ };
+
+ assert(fd >= 0);
+
+ /* The nd_ra_curhoplimit and nd_ra_flags_reserved fields cannot specified with nd_ra_router_lifetime
+ * simultaneously in the structured initializer in the above. */
+ hdr.nd_ra_curhoplimit = arg_hop_limit;
+ hdr.nd_ra_flags_reserved = arg_ra_flags;
+
+ return send_icmp6(fd, &hdr.nd_ra_hdr);
+}
+
+static int send_neighbor_solicit(int fd) {
+ struct nd_neighbor_solicit hdr = {
+ .nd_ns_type = ND_NEIGHBOR_SOLICIT,
+ .nd_ns_target = arg_target_address.in6,
+ };
+
+ assert(fd >= 0);
+
+ return send_icmp6(fd, &hdr.nd_ns_hdr);
+}
+
+static int send_neighbor_advertisement(int fd) {
+ struct nd_neighbor_advert hdr = {
+ .nd_na_type = ND_NEIGHBOR_ADVERT,
+ .nd_na_flags_reserved = arg_na_flags,
+ .nd_na_target = arg_target_address.in6,
+ };
+
+ assert(fd >= 0);
+
+ return send_icmp6(fd, &hdr.nd_na_hdr);
+}
+
+static int send_redirect(int fd) {
+ struct nd_redirect hdr = {
+ .nd_rd_type = ND_REDIRECT,
+ .nd_rd_target = arg_target_address.in6,
+ .nd_rd_dst = arg_redirect_destination.in6,
+ };
+
+ assert(fd >= 0);
+
+ return send_icmp6(fd, &hdr.nd_rd_hdr);
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ log_setup();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ fd = icmp6_bind(arg_ifindex, /* is_router = */ false);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to bind socket to interface: %m");
+
+ switch (arg_icmp6_type) {
+ case ND_ROUTER_SOLICIT:
+ return send_router_solicit(fd);
+ case ND_ROUTER_ADVERT:
+ return send_router_advertisement(fd);
+ case ND_NEIGHBOR_SOLICIT:
+ return send_neighbor_solicit(fd);
+ case ND_NEIGHBOR_ADVERT:
+ return send_neighbor_advertisement(fd);
+ case ND_REDIRECT:
+ return send_redirect(fd);
+ default:
+ assert_not_reached();
+ }
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);