diff options
Diffstat (limited to '')
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, ×tamp); - 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); |