diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:45 +0000 |
commit | efeb864cb547a2cbf96dc0053a8bdb4d9190b364 (patch) | |
tree | c0b83368f18be983fcc763200c4c24d633244588 /src/libsystemd | |
parent | Releasing progress-linux version 255.5-1~progress7.99u1. (diff) | |
download | systemd-efeb864cb547a2cbf96dc0053a8bdb4d9190b364.tar.xz systemd-efeb864cb547a2cbf96dc0053a8bdb4d9190b364.zip |
Merging upstream version 256.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
143 files changed, 8232 insertions, 3917 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); diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 4113920..78b4453 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -834,3 +834,12 @@ global: sd_id128_get_app_specific; sd_device_enumerator_add_match_property_required; } LIBSYSTEMD_254; + +LIBSYSTEMD_256 { +global: + sd_bus_creds_get_pidfd_dup; + sd_bus_creds_new_from_pidfd; + sd_id128_get_invocation_app_specific; + sd_journal_stream_fd_with_namespace; + sd_event_source_get_inotify_path; +} LIBSYSTEMD_255; diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 5d18f97..6d4337d 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -118,8 +118,7 @@ libsystemd_static = static_library( libsystemd_sources, include_directories : libsystemd_includes, c_args : libsystemd_c_args, - link_with : [libbasic, - libbasic_compress], + link_with : [libbasic], dependencies : [threads, librt, userspace], @@ -159,6 +158,10 @@ libsystemd_tests += [ 'sources' : files('sd-journal/test-journal-enum.c'), 'timeout' : 360, }, + { + 'sources' : files('sd-event/test-event.c'), + 'timeout' : 120, + } ] ############################################################ @@ -171,7 +174,6 @@ simple_tests += files( 'sd-device/test-device-util.c', 'sd-device/test-sd-device-monitor.c', 'sd-device/test-sd-device.c', - 'sd-event/test-event.c', 'sd-journal/test-journal-flush.c', 'sd-journal/test-journal-interleaving.c', 'sd-journal/test-journal-stream.c', diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index df26fd7..de12ec5 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -105,11 +105,13 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY), SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRODUCT_UUID, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_HARDWARE_SERIAL, EOPNOTSUPP), SD_BUS_ERROR_MAP(BUS_ERROR_FILE_IS_PROTECTED, EACCES), SD_BUS_ERROR_MAP(BUS_ERROR_READ_ONLY_FILESYSTEM, EROFS), SD_BUS_ERROR_MAP(BUS_ERROR_SPEED_METER_INACTIVE, EOPNOTSUPP), SD_BUS_ERROR_MAP(BUS_ERROR_UNMANAGED_INTERFACE, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_ALREADY_RELOADING, EBUSY), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_HOME, EEXIST), SD_BUS_ERROR_MAP(BUS_ERROR_UID_IN_USE, EEXIST), @@ -146,6 +148,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_HOME_CANT_AUTHENTICATE, EKEYREVOKED), SD_BUS_ERROR_MAP(BUS_ERROR_HOME_IN_USE, EADDRINUSE), SD_BUS_ERROR_MAP(BUS_ERROR_REBALANCE_NOT_NEEDED, EALREADY), + SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_REFERENCED, EBADR), SD_BUS_ERROR_MAP_END }; diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 3a0eef4..94dc85d 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -106,11 +106,13 @@ #define BUS_ERROR_TRANSFER_IN_PROGRESS "org.freedesktop.import1.TransferInProgress" #define BUS_ERROR_NO_PRODUCT_UUID "org.freedesktop.hostname1.NoProductUUID" +#define BUS_ERROR_NO_HARDWARE_SERIAL "org.freedesktop.hostname1.NoHardwareSerial" #define BUS_ERROR_FILE_IS_PROTECTED "org.freedesktop.hostname1.FileIsProtected" #define BUS_ERROR_READ_ONLY_FILESYSTEM "org.freedesktop.hostname1.ReadOnlyFilesystem" #define BUS_ERROR_SPEED_METER_INACTIVE "org.freedesktop.network1.SpeedMeterInactive" #define BUS_ERROR_UNMANAGED_INTERFACE "org.freedesktop.network1.UnmanagedInterface" +#define BUS_ERROR_NETWORK_ALREADY_RELOADING "org.freedesktop.network1.AlreadyReloading" #define BUS_ERROR_NO_SUCH_HOME "org.freedesktop.home1.NoSuchHome" #define BUS_ERROR_UID_IN_USE "org.freedesktop.home1.UIDInUse" @@ -151,5 +153,6 @@ #define BUS_ERROR_HOME_CANT_AUTHENTICATE "org.freedesktop.home1.HomeCantAuthenticate" #define BUS_ERROR_HOME_IN_USE "org.freedesktop.home1.HomeInUse" #define BUS_ERROR_REBALANCE_NOT_NEEDED "org.freedesktop.home1.RebalanceNotNeeded" +#define BUS_ERROR_HOME_NOT_REFERENCED "org.freedesktop.home1.HomeNotReferenced" BUS_ERROR_MAP_ELF_USE(bus_common_errors); diff --git a/src/libsystemd/sd-bus/bus-container.c b/src/libsystemd/sd-bus/bus-container.c index 4146a6e..2eca82b 100644 --- a/src/libsystemd/sd-bus/bus-container.c +++ b/src/libsystemd/sd-bus/bus-container.c @@ -34,7 +34,7 @@ int bus_container_connect_socket(sd_bus *b) { log_debug("sd-bus: connecting bus%s%s to namespace of PID "PID_FMT"...", b->description ? " " : "", strempty(b->description), b->nspid); - r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd); if (r < 0) return log_debug_errno(r, "Failed to open namespace of PID "PID_FMT": %m", b->nspid); diff --git a/src/libsystemd/sd-bus/bus-control.c b/src/libsystemd/sd-bus/bus-control.c index 1355e41..c420584 100644 --- a/src/libsystemd/sd-bus/bus-control.c +++ b/src/libsystemd/sd-bus/bus-control.c @@ -14,6 +14,7 @@ #include "bus-internal.h" #include "bus-message.h" #include "capability-util.h" +#include "fd-util.h" #include "process-util.h" #include "stdio-util.h" #include "string-util.h" @@ -430,7 +431,6 @@ _public_ int sd_bus_get_name_creds( _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_unique = NULL, *reply = NULL; _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; const char *unique; - pid_t pid = 0; int r; assert_return(bus, -EINVAL); @@ -483,8 +483,9 @@ _public_ int sd_bus_get_name_creds( } if (mask != 0) { + bool need_pid, need_uid, need_gids, need_selinux, need_separate_calls, need_pidfd, need_augment; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool need_pid, need_uid, need_selinux, need_separate_calls; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; c = bus_creds_new(); if (!c) @@ -498,20 +499,25 @@ _public_ int sd_bus_get_name_creds( c->mask |= SD_BUS_CREDS_UNIQUE_NAME; } - need_pid = (mask & SD_BUS_CREDS_PID) || - ((mask & SD_BUS_CREDS_AUGMENT) && - (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| - SD_BUS_CREDS_SUPPLEMENTARY_GIDS| - SD_BUS_CREDS_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| - SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| - SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT| - SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID))); + need_augment = + (mask & SD_BUS_CREDS_AUGMENT) && + (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| + SD_BUS_CREDS_SUPPLEMENTARY_GIDS| + SD_BUS_CREDS_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| + SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| + SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT| + SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID| + SD_BUS_CREDS_PIDFD)); + + need_pid = (mask & SD_BUS_CREDS_PID) || need_augment; need_uid = mask & SD_BUS_CREDS_EUID; + need_gids = mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS; need_selinux = mask & SD_BUS_CREDS_SELINUX_CONTEXT; + need_pidfd = (mask & SD_BUS_CREDS_PIDFD) || need_augment; - if (need_pid + need_uid + need_selinux > 1) { + if (need_pid + need_uid + need_selinux + need_pidfd + need_gids > 1) { /* If we need more than one of the credentials, then use GetConnectionCredentials() */ @@ -572,7 +578,9 @@ _public_ int sd_bus_get_name_creds( if (r < 0) return r; - pid = p; + if (!pidref_is_set(&pidref)) + pidref = PIDREF_MAKE_FROM_PID(p); + if (mask & SD_BUS_CREDS_PID) { c->pid = p; c->mask |= SD_BUS_CREDS_PID; @@ -599,6 +607,69 @@ _public_ int sd_bus_get_name_creds( r = sd_bus_message_exit_container(reply); if (r < 0) return r; + } else if (need_pidfd && streq(m, "ProcessFD")) { + int fd; + + r = sd_bus_message_read(reply, "v", "h", &fd); + if (r < 0) + return r; + + pidref_done(&pidref); + r = pidref_set_pidfd(&pidref, fd); + if (r < 0) + return r; + + if (mask & SD_BUS_CREDS_PIDFD) { + fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (fd < 0) + return -errno; + + close_and_replace(c->pidfd, fd); + c->mask |= SD_BUS_CREDS_PIDFD; + } + } else if (need_gids && streq(m, "UnixGroupIDs")) { + + /* Note that D-Bus actually only gives us a combined list of + * primary gid and supplementary gids. And we don't know + * which one the primary one is. We'll take the whole shebang + * hence and use it as the supplementary group list, and not + * initialize the primary gid field. This is slightly + * incorrect of course, but only slightly, as in effect if + * the primary gid is also listed in the supplementary gid + * it has zero effect. */ + + r = sd_bus_message_enter_container(reply, 'v', "au"); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "u"); + if (r < 0) + return r; + + for (;;) { + uint32_t u; + + r = sd_bus_message_read(reply, "u", &u); + if (r < 0) + return r; + if (r == 0) + break; + + if (!GREEDY_REALLOC(c->supplementary_gids, c->n_supplementary_gids+1)) + return -ENOMEM; + + c->supplementary_gids[c->n_supplementary_gids++] = (gid_t) u; + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; } else { r = sd_bus_message_skip(reply, "v"); if (r < 0) @@ -614,7 +685,7 @@ _public_ int sd_bus_get_name_creds( if (r < 0) return r; - if (need_pid && pid == 0) + if (need_pid && !pidref_is_set(&pidref)) return -EPROTO; } @@ -642,7 +713,9 @@ _public_ int sd_bus_get_name_creds( if (r < 0) return r; - pid = u; + if (!pidref_is_set(&pidref)) + pidref = PIDREF_MAKE_FROM_PID(u); + if (mask & SD_BUS_CREDS_PID) { c->pid = u; c->mask |= SD_BUS_CREDS_PID; @@ -710,9 +783,11 @@ _public_ int sd_bus_get_name_creds( } } - r = bus_creds_add_more(c, mask, pid, 0); - if (r < 0 && r != -ESRCH) /* Return the error, but ignore ESRCH which just means the process is already gone */ - return r; + if (pidref_is_set(&pidref)) { + r = bus_creds_add_more(c, mask, &pidref, 0); + if (r < 0 && r != -ESRCH) /* Return the error, but ignore ESRCH which just means the process is already gone */ + return r; + } } if (creds) @@ -765,8 +840,8 @@ not_found: _public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; - bool do_label, do_groups, do_sockaddr_peer; - pid_t pid = 0; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + bool do_label, do_groups, do_sockaddr_peer, do_pidfd; int r; assert_return(bus, -EINVAL); @@ -786,9 +861,10 @@ _public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **r do_sockaddr_peer = bus->sockaddr_size_peer >= offsetof(struct sockaddr_un, sun_path) + 1 && bus->sockaddr_peer.sa.sa_family == AF_UNIX && bus->sockaddr_peer.un.sun_path[0] == 0; + do_pidfd = bus->pidfd >= 0 && (mask & SD_BUS_CREDS_PIDFD); /* Avoid allocating anything if we have no chance of returning useful data */ - if (!bus->ucred_valid && !do_label && !do_groups && !do_sockaddr_peer) + if (!bus->ucred_valid && !do_label && !do_groups && !do_sockaddr_peer && !do_pidfd) return -ENODATA; c = bus_creds_new(); @@ -797,8 +873,10 @@ _public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **r if (bus->ucred_valid) { if (pid_is_valid(bus->ucred.pid)) { - pid = c->pid = bus->ucred.pid; + c->pid = bus->ucred.pid; c->mask |= SD_BUS_CREDS_PID & mask; + + pidref = PIDREF_MAKE_FROM_PID(c->pid); } if (uid_is_valid(bus->ucred.uid)) { @@ -859,7 +937,20 @@ _public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **r } } - r = bus_creds_add_more(c, mask, pid, 0); + if (do_pidfd) { + c->pidfd = fcntl(bus->pidfd, F_DUPFD_CLOEXEC, 3); + if (c->pidfd < 0) + return -errno; + + pidref_done(&pidref); + r = pidref_set_pidfd(&pidref, bus->pidfd); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_PIDFD; + } + + r = bus_creds_add_more(c, mask, &pidref, 0); if (r < 0 && r != -ESRCH) /* If the process vanished, then don't complain, just return what we got */ return r; diff --git a/src/libsystemd/sd-bus/bus-convenience.c b/src/libsystemd/sd-bus/bus-convenience.c index 989e577..14d8073 100644 --- a/src/libsystemd/sd-bus/bus-convenience.c +++ b/src/libsystemd/sd-bus/bus-convenience.c @@ -640,8 +640,8 @@ _public_ int sd_bus_set_property( } _public_ int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_bus_creds **ret) { + uint64_t missing; sd_bus_creds *c; - int r; assert_return(call, -EINVAL); assert_return(call->sealed, -EPERM); @@ -653,36 +653,22 @@ _public_ int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_b return -ENOTCONN; c = sd_bus_message_get_creds(call); - - /* All data we need? */ - if (c && (mask & ~SD_BUS_CREDS_AUGMENT & ~c->mask) == 0) { + if (c) + missing = mask & ~SD_BUS_CREDS_AUGMENT & ~c->mask; + else + missing = mask & ~SD_BUS_CREDS_AUGMENT; + if (missing == 0) { /* All data we need? */ *ret = sd_bus_creds_ref(c); return 0; } - /* No data passed? Or not enough data passed to retrieve the missing bits? */ - if (!c || !(c->mask & SD_BUS_CREDS_PID)) { - /* We couldn't read anything from the call, let's try - * to get it from the sender or peer. */ - - if (call->sender) - /* There's a sender, but the creds are missing. */ - return sd_bus_get_name_creds(call->bus, call->sender, mask, ret); - else - /* There's no sender. For direct connections - * the credentials of the AF_UNIX peer matter, - * which may be queried via sd_bus_get_owner_creds(). */ - return sd_bus_get_owner_creds(call->bus, mask, ret); - } - - r = bus_creds_extend_by_pid(c, mask, ret); - if (r == -ESRCH) { - /* Process doesn't exist anymore? propagate the few things we have */ - *ret = sd_bus_creds_ref(c); - return 0; - } + /* There's a sender, use that */ + if (call->sender && call->bus->bus_client) + return sd_bus_get_name_creds(call->bus, call->sender, mask, ret); - return r; + /* There's no sender. For direct connections the credentials of the AF_UNIX peer matter, which may be + * queried via sd_bus_get_owner_creds(). */ + return sd_bus_get_owner_creds(call->bus, mask, ret); } _public_ int sd_bus_query_sender_privilege(sd_bus_message *call, int capability) { diff --git a/src/libsystemd/sd-bus/bus-creds.c b/src/libsystemd/sd-bus/bus-creds.c index c6d8caa..adbdeaa 100644 --- a/src/libsystemd/sd-bus/bus-creds.c +++ b/src/libsystemd/sd-bus/bus-creds.c @@ -53,6 +53,8 @@ void bus_creds_done(sd_bus_creds *c) { * below. */ strv_free(c->cmdline_array); + + safe_close(c->pidfd); } _public_ sd_bus_creds *sd_bus_creds_ref(sd_bus_creds *c) { @@ -129,46 +131,72 @@ _public_ uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c) { sd_bus_creds* bus_creds_new(void) { sd_bus_creds *c; - c = new0(sd_bus_creds, 1); + c = new(sd_bus_creds, 1); if (!c) return NULL; - c->allocated = true; - c->n_ref = 1; + *c = (sd_bus_creds) { + .allocated = true, + .n_ref = 1, + SD_BUS_CREDS_INIT_FIELDS, + }; + return c; } -_public_ int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t mask) { +static int bus_creds_new_from_pidref(sd_bus_creds **ret, PidRef *pidref, uint64_t mask) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; int r; - assert_return(pid >= 0, -EINVAL); assert_return(mask <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); assert_return(ret, -EINVAL); - if (pid == 0) - pid = getpid_cached(); - c = bus_creds_new(); if (!c) return -ENOMEM; - r = bus_creds_add_more(c, mask | SD_BUS_CREDS_AUGMENT, pid, 0); + r = bus_creds_add_more(c, mask | SD_BUS_CREDS_AUGMENT, pidref, 0); if (r < 0) return r; - /* Check if the process existed at all, in case we haven't - * figured that out already */ - r = pid_is_alive(pid); + r = pidref_verify(pidref); if (r < 0) return r; - if (r == 0) - return -ESRCH; *ret = TAKE_PTR(c); return 0; } +_public_ int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t mask) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + assert_return(pid >= 0, -EINVAL); + assert_return(mask <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); + assert_return(ret, -EINVAL); + + r = pidref_set_pid(&pidref, pid); + if (r < 0) + return r; + + return bus_creds_new_from_pidref(ret, &pidref, mask); +} + +_public_ int sd_bus_creds_new_from_pidfd(sd_bus_creds **ret, int pidfd, uint64_t mask) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + assert_return(mask <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); + assert_return(ret, -EINVAL); + assert_return(pidfd >= 0, -EBADF); + + r = pidref_set_pidfd(&pidref, pidfd); + if (r < 0) + return r; + + return bus_creds_new_from_pidref(ret, &pidref, mask); +} + _public_ int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid) { assert_return(c, -EINVAL); assert_return(uid, -EINVAL); @@ -280,6 +308,23 @@ _public_ int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid) { return 0; } +_public_ int sd_bus_creds_get_pidfd_dup(sd_bus_creds *c, int *ret_fd) { + _cleanup_close_ int copy = -EBADF; + + assert_return(c, -EINVAL); + assert_return(ret_fd, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_PIDFD)) + return -ENODATA; + + copy = fcntl(c->pidfd, F_DUPFD_CLOEXEC, 3); + if (copy < 0) + return -errno; + + *ret_fd = TAKE_FD(copy); + return 0; +} + _public_ int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid) { assert_return(c, -EINVAL); assert_return(ppid, -EINVAL); @@ -731,7 +776,7 @@ static int parse_caps(sd_bus_creds *c, unsigned offset, const char *p) { return -ENOMEM; } - for (i = 0; i < sz; i ++) { + for (i = 0; i < sz; i++) { uint32_t v = 0; for (j = 0; j < 8; ++j) { @@ -750,7 +795,8 @@ static int parse_caps(sd_bus_creds *c, unsigned offset, const char *p) { return 0; } -int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { +int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, PidRef *pidref, pid_t tid) { + _cleanup_(pidref_done) PidRef pidref_buf = PIDREF_NULL; uint64_t missing; int r; @@ -761,12 +807,26 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { return 0; /* Try to retrieve PID from creds if it wasn't passed to us */ - if (pid > 0) { - c->pid = pid; + if (pidref_is_set(pidref)) { + if ((c->mask & SD_BUS_CREDS_PID) && c->pid != pidref->pid) /* Insist that things match if already set */ + return -EBUSY; + + c->pid = pidref->pid; c->mask |= SD_BUS_CREDS_PID; - } else if (c->mask & SD_BUS_CREDS_PID) - pid = c->pid; - else + } else if (c->mask & SD_BUS_CREDS_PIDFD) { + r = pidref_set_pidfd(&pidref_buf, c->pidfd); + if (r < 0) + return r; + + pidref = &pidref_buf; + + } else if (c->mask & SD_BUS_CREDS_PID) { + r = pidref_set_pid(&pidref_buf, c->pid); + if (r < 0) + return r; + + pidref = &pidref_buf; + } else /* Without pid we cannot do much... */ return 0; @@ -784,6 +844,14 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { c->mask |= SD_BUS_CREDS_TID; } + if ((missing & SD_BUS_CREDS_PIDFD) && pidref->fd >= 0) { + c->pidfd = fcntl(pidref->fd, F_DUPFD_CLOEXEC, 3); + if (c->pidfd < 0) + return -errno; + + c->mask |= SD_BUS_CREDS_PIDFD; + } + if (missing & (SD_BUS_CREDS_PPID | SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_SUID | SD_BUS_CREDS_FSUID | SD_BUS_CREDS_GID | SD_BUS_CREDS_EGID | SD_BUS_CREDS_SGID | SD_BUS_CREDS_FSGID | @@ -794,13 +862,13 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { _cleanup_fclose_ FILE *f = NULL; const char *p; - p = procfs_file_alloca(pid, "status"); + p = procfs_file_alloca(pidref->pid, "status"); f = fopen(p, "re"); if (!f) { if (errno == ENOENT) return -ESRCH; - else if (!ERRNO_IS_PRIVILEGE(errno)) + if (!ERRNO_IS_PRIVILEGE(errno)) return -errno; } else { @@ -958,7 +1026,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { if (missing & SD_BUS_CREDS_SELINUX_CONTEXT) { const char *p; - p = procfs_file_alloca(pid, "attr/current"); + p = procfs_file_alloca(pidref->pid, "attr/current"); r = read_one_line_file(p, &c->label); if (r < 0) { if (!IN_SET(r, -ENOENT, -EINVAL, -EPERM, -EACCES)) @@ -968,7 +1036,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { } if (missing & SD_BUS_CREDS_COMM) { - r = pid_get_comm(pid, &c->comm); + r = pid_get_comm(pidref->pid, &c->comm); if (r < 0) { if (!ERRNO_IS_PRIVILEGE(r)) return r; @@ -977,7 +1045,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { } if (missing & SD_BUS_CREDS_EXE) { - r = get_process_exe(pid, &c->exe); + r = get_process_exe(pidref->pid, &c->exe); if (r == -ESRCH) { /* Unfortunately we cannot really distinguish * the case here where the process does not @@ -998,7 +1066,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { if (missing & SD_BUS_CREDS_CMDLINE) { const char *p; - p = procfs_file_alloca(pid, "cmdline"); + p = procfs_file_alloca(pidref->pid, "cmdline"); r = read_full_virtual_file(p, &c->cmdline, &c->cmdline_size); if (r == -ENOENT) return -ESRCH; @@ -1016,7 +1084,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { if (tid > 0 && (missing & SD_BUS_CREDS_TID_COMM)) { _cleanup_free_ char *p = NULL; - if (asprintf(&p, "/proc/"PID_FMT"/task/"PID_FMT"/comm", pid, tid) < 0) + if (asprintf(&p, "/proc/"PID_FMT"/task/"PID_FMT"/comm", pidref->pid, tid) < 0) return -ENOMEM; r = read_one_line_file(p, &c->tid_comm); @@ -1032,7 +1100,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { if (missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID)) { if (!c->cgroup) { - r = cg_pid_get_path(NULL, pid, &c->cgroup); + r = cg_pid_get_path(NULL, pidref->pid, &c->cgroup); if (r < 0) { if (!ERRNO_IS_PRIVILEGE(r)) return r; @@ -1050,7 +1118,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { } if (missing & SD_BUS_CREDS_AUDIT_SESSION_ID) { - r = audit_session_from_pid(pid, &c->audit_session_id); + r = audit_session_from_pid(pidref->pid, &c->audit_session_id); if (r == -ENODATA) { /* ENODATA means: no audit session id assigned */ c->audit_session_id = AUDIT_SESSION_INVALID; @@ -1063,7 +1131,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { } if (missing & SD_BUS_CREDS_AUDIT_LOGIN_UID) { - r = audit_loginuid_from_pid(pid, &c->audit_login_uid); + r = audit_loginuid_from_pid(pidref->pid, &c->audit_login_uid); if (r == -ENODATA) { /* ENODATA means: no audit login uid assigned */ c->audit_login_uid = UID_INVALID; @@ -1076,7 +1144,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { } if (missing & SD_BUS_CREDS_TTY) { - r = get_ctty(pid, NULL, &c->tty); + r = get_ctty(pidref->pid, NULL, &c->tty); if (r == -ENXIO) { /* ENXIO means: process has no controlling TTY */ c->tty = NULL; @@ -1088,16 +1156,12 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { c->mask |= SD_BUS_CREDS_TTY; } - /* In case only the exe path was to be read we cannot distinguish the case where the exe path was - * unreadable because the process was a kernel thread, or when the process didn't exist at - * all. Hence, let's do a final check, to be sure. */ - r = pid_is_alive(pid); + r = pidref_verify(pidref); if (r < 0) return r; - if (r == 0) - return -ESRCH; - if (tid > 0 && tid != pid && pid_is_unwaited(tid) == 0) + /* Validate tid is still valid, too */ + if (tid > 0 && tid != pidref->pid && pid_is_unwaited(tid) == 0) return -ESRCH; c->augmented = missing & c->mask; @@ -1131,6 +1195,13 @@ int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret) n->mask |= SD_BUS_CREDS_PID; } + if (c->mask & mask & SD_BUS_CREDS_PIDFD) { + n->pidfd = fcntl(c->pidfd, F_DUPFD_CLOEXEC, 3); + if (n->pidfd < 0) + return -errno; + n->mask |= SD_BUS_CREDS_PIDFD; + } + if (c->mask & mask & SD_BUS_CREDS_TID) { n->tid = c->tid; n->mask |= SD_BUS_CREDS_TID; diff --git a/src/libsystemd/sd-bus/bus-creds.h b/src/libsystemd/sd-bus/bus-creds.h index 7806d9e..f45de1c 100644 --- a/src/libsystemd/sd-bus/bus-creds.h +++ b/src/libsystemd/sd-bus/bus-creds.h @@ -5,6 +5,9 @@ #include "sd-bus.h" +#include "pidref.h" +#include "user-util.h" + struct sd_bus_creds { bool allocated; unsigned n_ref; @@ -27,6 +30,7 @@ struct sd_bus_creds { pid_t ppid; pid_t pid; pid_t tid; + int pidfd; char *comm; char *tid_comm; @@ -63,10 +67,22 @@ struct sd_bus_creds { char *description, *unescaped_description; }; +#define SD_BUS_CREDS_INIT_FIELDS \ + .uid = UID_INVALID, \ + .euid = UID_INVALID, \ + .suid = UID_INVALID, \ + .fsuid = UID_INVALID, \ + .gid = GID_INVALID, \ + .egid = GID_INVALID, \ + .sgid = GID_INVALID, \ + .fsgid = GID_INVALID, \ + .pidfd = -EBADF, \ + .audit_login_uid = UID_INVALID + sd_bus_creds* bus_creds_new(void); void bus_creds_done(sd_bus_creds *c); -int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid); +int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, PidRef *pidref, pid_t tid); int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret); diff --git a/src/libsystemd/sd-bus/bus-dump.c b/src/libsystemd/sd-bus/bus-dump.c index 6d24f3b..aa46fec 100644 --- a/src/libsystemd/sd-bus/bus-dump.c +++ b/src/libsystemd/sd-bus/bus-dump.c @@ -355,6 +355,8 @@ int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse) { if (c->mask & SD_BUS_CREDS_PID) fprintf(f, "%sPID=%s"PID_FMT"%s", prefix, color, c->pid, suffix); + if (c->mask & SD_BUS_CREDS_PIDFD) + fprintf(f, "%sPIDFD=%syes%s", prefix, color, suffix); if (c->mask & SD_BUS_CREDS_TID) fprintf(f, "%sTID=%s"PID_FMT"%s", prefix, color, c->tid, suffix); if (c->mask & SD_BUS_CREDS_PPID) { diff --git a/src/libsystemd/sd-bus/bus-internal.h b/src/libsystemd/sd-bus/bus-internal.h index 098a518..e0f4746 100644 --- a/src/libsystemd/sd-bus/bus-internal.h +++ b/src/libsystemd/sd-bus/bus-internal.h @@ -254,6 +254,9 @@ struct sd_bus { char *address; unsigned address_index; + uid_t connect_as_uid; + gid_t connect_as_gid; + int last_connect_error; enum bus_auth auth; @@ -269,6 +272,7 @@ struct sd_bus { size_t n_groups; union sockaddr_union sockaddr_peer; socklen_t sockaddr_size_peer; + int pidfd; uint64_t creds_mask; diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index ab8b068..296f450 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -373,6 +373,7 @@ static int message_from_header( if (!m) return -ENOMEM; + m->creds = (sd_bus_creds) { SD_BUS_CREDS_INIT_FIELDS }; m->sealed = true; m->header = buffer; @@ -469,6 +470,7 @@ _public_ int sd_bus_message_new( return -ENOMEM; t->n_ref = 1; + t->creds = (sd_bus_creds) { SD_BUS_CREDS_INIT_FIELDS }; t->bus = sd_bus_ref(bus); t->header = (struct bus_header*) ((uint8_t*) t + ALIGN(sizeof(struct sd_bus_message))); t->header->endian = BUS_NATIVE_ENDIAN; @@ -627,7 +629,7 @@ static int message_new_reply( return r; } - t->dont_send = !!(call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED); + t->dont_send = FLAGS_SET(call->header->flags, BUS_MESSAGE_NO_REPLY_EXPECTED); t->enforced_reply_signature = call->enforced_reply_signature; /* let's copy the sensitive flag over. Let's do that as a safety precaution to keep a transaction diff --git a/src/libsystemd/sd-bus/bus-objects.c b/src/libsystemd/sd-bus/bus-objects.c index c25c40f..e528987 100644 --- a/src/libsystemd/sd-bus/bus-objects.c +++ b/src/libsystemd/sd-bus/bus-objects.c @@ -1701,7 +1701,7 @@ static bool names_are_valid(const char *signature, const char **names, names_fla if ((*flags & NAMES_FIRST_PART || *flags & NAMES_SINGLE_PART) && **names != '\0') *flags |= NAMES_PRESENT; - for (;*flags & NAMES_PRESENT;) { + while (*flags & NAMES_PRESENT) { size_t l; if (!*signature) diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c index 5ade8e9..07179e0 100644 --- a/src/libsystemd/sd-bus/bus-socket.c +++ b/src/libsystemd/sd-bus/bus-socket.c @@ -217,7 +217,7 @@ static int bus_socket_auth_verify_client(sd_bus *b) { /* And possibly check the third line, too */ if (b->accept_fd) { l = lines[i++]; - b->can_fds = !!memory_startswith(l, lines[i] - l, "AGREE_UNIX_FD"); + b->can_fds = memory_startswith(l, lines[i] - l, "AGREE_UNIX_FD"); } assert(i == n); @@ -266,7 +266,7 @@ static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) { if (l % 2 != 0) return 0; - r = unhexmem(p, l, (void **) &token, &len); + r = unhexmem_full(p, l, /* secure = */ false, (void**) &token, &len); if (r < 0) return 0; @@ -298,7 +298,7 @@ static int verify_external_token(sd_bus *b, const char *p, size_t l) { if (l % 2 != 0) return 0; - r = unhexmem(p, l, (void**) &token, &len); + r = unhexmem_full(p, l, /* secure = */ false, (void**) &token, &len); if (r < 0) return 0; @@ -503,11 +503,38 @@ static int bus_socket_write_auth(sd_bus *b) { if (b->prefer_writev) k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); else { + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control = {}; + struct msghdr mh = { .msg_iov = b->auth_iovec + b->auth_index, .msg_iovlen = ELEMENTSOF(b->auth_iovec) - b->auth_index, }; + if (uid_is_valid(b->connect_as_uid) || gid_is_valid(b->connect_as_gid)) { + + /* If we shall connect under some specific UID/GID, then synthesize an + * SCM_CREDENTIALS record accordingly. After all we want to adopt this UID/GID both + * for SO_PEERCRED (where we have to fork()) and SCM_CREDENTIALS (where we can just + * fake it via sendmsg()) */ + + struct ucred ucred = { + .pid = getpid_cached(), + .uid = uid_is_valid(b->connect_as_uid) ? b->connect_as_uid : getuid(), + .gid = gid_is_valid(b->connect_as_gid) ? b->connect_as_gid : getgid(), + }; + + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&mh); + *cmsg = (struct cmsghdr) { + .cmsg_level = SOL_SOCKET, + .cmsg_type = SCM_CREDENTIALS, + .cmsg_len = CMSG_LEN(sizeof(struct ucred)), + }; + + memcpy(CMSG_DATA(cmsg), &ucred, sizeof(struct ucred)); + } + k = sendmsg(b->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); if (k < 0 && errno == ENOTSOCK) { b->prefer_writev = true; @@ -643,14 +670,20 @@ static void bus_get_peercred(sd_bus *b) { /* Get the SELinux context of the peer */ r = getpeersec(b->input_fd, &b->label); if (r < 0 && !IN_SET(r, -EOPNOTSUPP, -ENOPROTOOPT)) - log_debug_errno(r, "Failed to determine peer security context: %m"); + log_debug_errno(r, "Failed to determine peer security context, ignoring: %m"); /* Get the list of auxiliary groups of the peer */ r = getpeergroups(b->input_fd, &b->groups); if (r >= 0) b->n_groups = (size_t) r; else if (!IN_SET(r, -EOPNOTSUPP, -ENOPROTOOPT)) - log_debug_errno(r, "Failed to determine peer's group list: %m"); + log_debug_errno(r, "Failed to determine peer's group list, ignoring: %m"); + + r = getpeerpidfd(b->input_fd); + if (r < 0) + log_debug_errno(r, "Failed to determine peer pidfd, ignoring: %m"); + else + close_and_replace(b->pidfd, r); /* Let's query the peers socket address, it might carry information such as the peer's comm or * description string */ @@ -943,6 +976,66 @@ static int bind_description(sd_bus *b, int fd, int family) { return 0; } +static int connect_as(int fd, const struct sockaddr *sa, socklen_t salen, uid_t uid, gid_t gid) { + _cleanup_(close_pairp) int pfd[2] = EBADF_PAIR; + ssize_t n; + int r; + + /* Shortcut if we are not supposed to drop privileges */ + if (!uid_is_valid(uid) && !gid_is_valid(gid)) + return RET_NERRNO(connect(fd, sa, salen)); + + /* This changes identity to the specified uid/gid and issues connect() as that. This is useful to + * make sure SO_PEERCRED reports the selected UID/GID rather than the usual one of the caller. */ + + if (pipe2(pfd, O_CLOEXEC) < 0) + return -errno; + + r = safe_fork("(sd-setresuid)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, /* ret_pid= */ NULL); + if (r < 0) + return r; + if (r == 0) { + /* child */ + + pfd[0] = safe_close(pfd[0]); + + r = RET_NERRNO(setgroups(0, NULL)); + if (r < 0) + goto child_finish; + + if (gid_is_valid(gid)) { + r = RET_NERRNO(setresgid(gid, gid, gid)); + if (r < 0) + goto child_finish; + } + + if (uid_is_valid(uid)) { + r = RET_NERRNO(setresuid(uid, uid, uid)); + if (r < 0) + goto child_finish; + } + + r = RET_NERRNO(connect(fd, sa, salen)); + if (r < 0) + goto child_finish; + + r = 0; + + child_finish: + n = write(pfd[1], &r, sizeof(r)); + if (n != sizeof(r)) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + n = read(pfd[0], &r, sizeof(r)); + if (n != sizeof(r)) + return -EIO; + + return r; +} + int bus_socket_connect(sd_bus *b) { bool inotify_done = false; int r; @@ -974,8 +1067,9 @@ int bus_socket_connect(sd_bus *b) { b->output_fd = b->input_fd; bus_socket_setup(b); - if (connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size) < 0) { - if (errno == EINPROGRESS) { + r = connect_as(b->input_fd, &b->sockaddr.sa, b->sockaddr_size, b->connect_as_uid, b->connect_as_gid); + if (r < 0) { + if (r == -EINPROGRESS) { /* If we have any inotify watches open, close them now, we don't need them anymore, as * we have successfully initiated a connection */ @@ -988,7 +1082,7 @@ int bus_socket_connect(sd_bus *b) { return 1; } - if (IN_SET(errno, ENOENT, ECONNREFUSED) && /* ENOENT → unix socket doesn't exist at all; ECONNREFUSED → unix socket stale */ + if (IN_SET(r, -ENOENT, -ECONNREFUSED) && /* ENOENT → unix socket doesn't exist at all; ECONNREFUSED → unix socket stale */ b->watch_bind && b->sockaddr.sa.sa_family == AF_UNIX && b->sockaddr.un.sun_path[0] != 0) { @@ -1016,7 +1110,7 @@ int bus_socket_connect(sd_bus *b) { inotify_done = true; } else - return -errno; + return r; } else break; } diff --git a/src/libsystemd/sd-bus/bus-track.c b/src/libsystemd/sd-bus/bus-track.c index f9c59a1..6f6fa2d 100644 --- a/src/libsystemd/sd-bus/bus-track.c +++ b/src/libsystemd/sd-bus/bus-track.c @@ -69,7 +69,7 @@ static void bus_track_add_to_queue(sd_bus_track *track) { return; /* still referenced? */ - if (hashmap_size(track->names) > 0) + if (!hashmap_isempty(track->names)) return; /* Nothing to call? */ diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c index 8befc97..1a642cb 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -30,6 +30,7 @@ #include "constants.h" #include "errno-util.h" #include "fd-util.h" +#include "format-util.h" #include "glyph-util.h" #include "hexdecoct.h" #include "hostname-util.h" @@ -151,6 +152,14 @@ void bus_close_inotify_fd(sd_bus *b) { b->n_inotify_watches = 0; } +static void bus_close_fds(sd_bus *b) { + assert(b); + + bus_close_io_fds(b); + bus_close_inotify_fd(b); + b->pidfd = safe_close(b->pidfd); +} + static void bus_reset_queues(sd_bus *b) { assert(b); @@ -191,8 +200,7 @@ static sd_bus* bus_free(sd_bus *b) { if (b->default_bus_ptr) *b->default_bus_ptr = NULL; - bus_close_io_fds(b); - bus_close_inotify_fd(b); + bus_close_fds(b); free(b->label); free(b->groups); @@ -256,7 +264,10 @@ _public_ int sd_bus_new(sd_bus **ret) { .n_groups = SIZE_MAX, .close_on_exit = true, .ucred = UCRED_INVALID, + .pidfd = -EBADF, .runtime_scope = _RUNTIME_SCOPE_INVALID, + .connect_as_uid = UID_INVALID, + .connect_as_gid = GID_INVALID, }; /* We guarantee that wqueue always has space for at least one entry */ @@ -321,7 +332,7 @@ _public_ int sd_bus_set_bus_client(sd_bus *bus, int b) { assert_return(!bus->patch_sender, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->bus_client = !!b; + bus->bus_client = b; return 0; } @@ -331,7 +342,7 @@ _public_ int sd_bus_set_monitor(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->is_monitor = !!b; + bus->is_monitor = b; return 0; } @@ -341,7 +352,7 @@ _public_ int sd_bus_negotiate_fds(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->accept_fd = !!b; + bus->accept_fd = b; return 0; } @@ -353,7 +364,7 @@ _public_ int sd_bus_negotiate_timestamp(sd_bus *bus, int b) { /* This is not actually supported by any of our transports these days, but we do honour it for synthetic * replies, and maybe one day classic D-Bus learns this too */ - bus->attach_timestamp = !!b; + bus->attach_timestamp = b; return 0; } @@ -380,7 +391,7 @@ _public_ int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t server_id) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->is_server = !!b; + bus->is_server = b; bus->server_id = server_id; return 0; } @@ -391,7 +402,7 @@ _public_ int sd_bus_set_anonymous(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->anonymous_auth = !!b; + bus->anonymous_auth = b; return 0; } @@ -401,7 +412,7 @@ _public_ int sd_bus_set_trusted(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->trusted = !!b; + bus->trusted = b; return 0; } @@ -419,7 +430,7 @@ _public_ int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b) { assert_return(bus = bus_resolve(bus), -ENOPKG); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->allow_interactive_authorization = !!b; + bus->allow_interactive_authorization = b; return 0; } @@ -437,7 +448,7 @@ _public_ int sd_bus_set_watch_bind(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->watch_bind = !!b; + bus->watch_bind = b; return 0; } @@ -455,7 +466,7 @@ _public_ int sd_bus_set_connected_signal(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->connected_signal = !!b; + bus->connected_signal = b; return 0; } @@ -640,7 +651,7 @@ int bus_start_running(sd_bus *bus) { static int parse_address_key(const char **p, const char *key, char **value) { _cleanup_free_ char *r = NULL; - size_t l, n = 0; + size_t n = 0; const char *a; assert(p); @@ -648,17 +659,14 @@ static int parse_address_key(const char **p, const char *key, char **value) { assert(value); if (key) { - l = strlen(key); - if (strncmp(*p, key, l) != 0) - return 0; - - if ((*p)[l] != '=') + a = startswith(*p, key); + if (!a || *a != '=') return 0; if (*value) return -EINVAL; - a = *p + l + 1; + a++; } else a = *p; @@ -717,7 +725,7 @@ static void skip_address_key(const char **p) { } static int parse_unix_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *path = NULL, *abstract = NULL; + _cleanup_free_ char *path = NULL, *abstract = NULL, *uids = NULL, *gids = NULL; size_t l; int r; @@ -745,6 +753,18 @@ static int parse_unix_address(sd_bus *b, const char **p, char **guid) { else if (r > 0) continue; + r = parse_address_key(p, "uid", &uids); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "gid", &gids); + if (r < 0) + return r; + else if (r > 0) + continue; + skip_address_key(p); } @@ -781,6 +801,17 @@ static int parse_unix_address(sd_bus *b, const char **p, char **guid) { b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l; } + if (uids) { + r = parse_uid(uids, &b->connect_as_uid); + if (r < 0) + return r; + } + if (gids) { + r = parse_gid(gids, &b->connect_as_gid); + if (r < 0) + return r; + } + b->is_local = true; return 0; @@ -1102,8 +1133,7 @@ static int bus_start_address(sd_bus *b) { assert(b); for (;;) { - bus_close_io_fds(b); - bus_close_inotify_fd(b); + bus_close_fds(b); bus_kill_exec(b); @@ -1486,9 +1516,15 @@ interpret_port_as_machine_old_syntax: return -ENOMEM; } - a = strjoin("unixexec:path=ssh,argv1=-xT", p ? ",argv2=-p,argv3=" : "", strempty(p), - ",argv", p ? "4" : "2", "=--,argv", p ? "5" : "3", "=", e, - ",argv", p ? "6" : "4", "=systemd-stdio-bridge", c); + const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh"; + _cleanup_free_ char *ssh_escaped = bus_address_escape(ssh); + if (!ssh_escaped) + return -ENOMEM; + + a = strjoin("unixexec:path=", ssh_escaped, ",argv1=-xT", + p ? ",argv2=-p,argv3=" : "", strempty(p), + ",argv", p ? "4" : "2", "=--,argv", p ? "5" : "3", "=", e, + ",argv", p ? "6" : "4", "=systemd-stdio-bridge", c); if (!a) return -ENOMEM; @@ -1668,10 +1704,7 @@ static int user_and_machine_equivalent(const char *user_and_machine) { return true; /* Otherwise, we have to figure out our user id and name, and compare things with that. */ - char buf[DECIMAL_STR_MAX(uid_t)]; - xsprintf(buf, UID_FMT, uid); - - f = startswith(user_and_machine, buf); + f = startswith(user_and_machine, FORMAT_UID(uid)); if (!f) { un = getusername_malloc(); if (!un) @@ -1775,8 +1808,7 @@ _public_ void sd_bus_close(sd_bus *bus) { * the bus object and the bus may be freed */ bus_reset_queues(bus); - bus_close_io_fds(bus); - bus_close_inotify_fd(bus); + bus_close_fds(bus); } _public_ sd_bus *sd_bus_close_unref(sd_bus *bus) { @@ -4123,13 +4155,13 @@ _public_ int sd_bus_path_decode_many(const char *path, const char *path_template for (template_pos = path_template; *template_pos; ) { const char *sep; - size_t length; + size_t length, path_length; char *label; /* verify everything until the next '%' matches verbatim */ sep = strchrnul(template_pos, '%'); length = sep - template_pos; - if (strncmp(path_pos, template_pos, length)) + if (!strneq(path_pos, template_pos, length)) return 0; path_pos += length; @@ -4150,8 +4182,8 @@ _public_ int sd_bus_path_decode_many(const char *path, const char *path_template /* verify the suffixes match */ sep = strchrnul(path_pos, '/'); - if (sep - path_pos < (ssize_t)length || - strncmp(sep - length, template_pos, length)) + path_length = sep - path_pos; + if (length > path_length || !strneq(sep - length, template_pos, length)) return 0; template_pos += length; /* skip over matched label */ diff --git a/src/libsystemd/sd-bus/test-bus-chat.c b/src/libsystemd/sd-bus/test-bus-chat.c index da1340f..d06853b 100644 --- a/src/libsystemd/sd-bus/test-bus-chat.c +++ b/src/libsystemd/sd-bus/test-bus-chat.c @@ -432,8 +432,7 @@ static void* client2(void *p) { if (r < 0) log_debug("Failed to issue method call: %s", bus_error_message(&error, r)); else { - log_error("Slow call unexpectedly succeed."); - r = -ENOANO; + r = log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Slow call unexpectedly succeeded."); goto finish; } diff --git a/src/libsystemd/sd-bus/test-bus-cleanup.c b/src/libsystemd/sd-bus/test-bus-cleanup.c index 3e14627..b569986 100644 --- a/src/libsystemd/sd-bus/test-bus-cleanup.c +++ b/src/libsystemd/sd-bus/test-bus-cleanup.c @@ -30,7 +30,7 @@ static void test_bus_fork(void) { r = safe_fork("(bus-fork-test)", FORK_WAIT|FORK_LOG, NULL); if (r == 0) { assert_se(bus); - assert_se(sd_bus_is_ready(bus) == -ECHILD); + ASSERT_RETURN_EXPECTED_SE(sd_bus_is_ready(bus) == -ECHILD); assert_se(sd_bus_flush_close_unref(bus) == NULL); assert_se(sd_bus_close_unref(bus) == NULL); assert_se(sd_bus_unref(bus) == NULL); diff --git a/src/libsystemd/sd-bus/test-bus-creds.c b/src/libsystemd/sd-bus/test-bus-creds.c index 13801be..7eb7a38 100644 --- a/src/libsystemd/sd-bus/test-bus-creds.c +++ b/src/libsystemd/sd-bus/test-bus-creds.c @@ -4,6 +4,7 @@ #include "bus-dump.h" #include "cgroup-util.h" +#include "errno-util.h" #include "tests.h" int main(int argc, char *argv[]) { @@ -12,7 +13,7 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - if (cg_unified() == -ENOMEDIUM) + if (IN_SET(cg_unified(), -ENOMEDIUM, -ENOENT)) return log_tests_skipped("/sys/fs/cgroup/ not available"); r = sd_bus_creds_new_from_pid(&creds, 0, _SD_BUS_CREDS_ALL); @@ -24,11 +25,30 @@ int main(int argc, char *argv[]) { creds = sd_bus_creds_unref(creds); r = sd_bus_creds_new_from_pid(&creds, 1, _SD_BUS_CREDS_ALL); - if (r != -EACCES) { + if (!ERRNO_IS_NEG_PRIVILEGE(r)) { assert_se(r >= 0); putchar('\n'); bus_creds_dump(creds, NULL, true); } + creds = sd_bus_creds_unref(creds); + + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + r = sd_bus_default_system(&bus); + if (r < 0) + log_warning_errno(r, "Unable to connect to system bus, skipping rest of test."); + else { + const char *unique; + + assert_se(sd_bus_get_unique_name(bus, &unique) >= 0); + + r = sd_bus_get_name_creds(bus, unique, _SD_BUS_CREDS_ALL, &creds); + log_full_errno(r < 0 ? LOG_ERR : LOG_DEBUG, r, "sd_bus_get_name_creds: %m"); + assert_se(r >= 0); + + putchar('\n'); + bus_creds_dump(creds, NULL, true); + } + return 0; } diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c index a55f3f9..91045c0 100644 --- a/src/libsystemd/sd-bus/test-bus-error.c +++ b/src/libsystemd/sd-bus/test-bus-error.c @@ -213,8 +213,8 @@ TEST(errno_mapping_custom) { assert_se(sd_bus_error_set(NULL, BUS_ERROR_NO_SUCH_UNIT, NULL) == -ENOENT); - assert_se(sd_bus_error_add_map(test_errors_bad1) == -EINVAL); - assert_se(sd_bus_error_add_map(test_errors_bad2) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_bus_error_add_map(test_errors_bad1) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_bus_error_add_map(test_errors_bad2) == -EINVAL); } TEST(sd_bus_error_set_errnof) { diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index 0044d33..92da627 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -42,8 +42,8 @@ static void test_bus_path_encode(void) { assert_se(sd_bus_path_decode(a, "/waldo", &b) == 0 && b == NULL); assert_se(sd_bus_path_decode(a, "/foo/bar", &b) > 0 && streq(b, "waldo")); - assert_se(sd_bus_path_encode("xxxx", "waldo", &c) < 0); - assert_se(sd_bus_path_encode("/foo/", "waldo", &c) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_bus_path_encode("xxxx", "waldo", &c) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_bus_path_encode("/foo/", "waldo", &c) < 0); assert_se(sd_bus_path_encode("/foo/bar", "", &c) >= 0 && streq(c, "/foo/bar/_")); assert_se(sd_bus_path_decode(c, "/foo/bar", &d) > 0 && streq(d, "")); diff --git a/src/libsystemd/sd-bus/test-bus-objects.c b/src/libsystemd/sd-bus/test-bus-objects.c index ccdd0d5..2847ba8 100644 --- a/src/libsystemd/sd-bus/test-bus-objects.c +++ b/src/libsystemd/sd-bus/test-bus-objects.c @@ -494,10 +494,9 @@ static int client(struct context *c) { } assert_se(sd_bus_message_exit_container(reply) >= 0); - if (streq(path, "/value/a")) { + if (streq(path, "/value/a")) /* ObjectManager must be here */ assert_se(found_object_manager_interface); - } } else assert_se(sd_bus_message_skip(reply, "a{sa{sv}}") >= 0); diff --git a/src/libsystemd/sd-bus/test-bus-peersockaddr.c b/src/libsystemd/sd-bus/test-bus-peersockaddr.c index 79556e8..a7bba17 100644 --- a/src/libsystemd/sd-bus/test-bus-peersockaddr.c +++ b/src/libsystemd/sd-bus/test-bus-peersockaddr.c @@ -5,10 +5,39 @@ #include "sd-bus.h" +#include "bus-dump.h" +#include "bus-util.h" #include "fd-util.h" #include "process-util.h" #include "socket-util.h" +#include "sort-util.h" #include "tests.h" +#include "user-util.h" + +static bool gid_list_contained(const gid_t *a, size_t n, const gid_t *b, size_t m) { + assert_se(a || n == 0); + assert_se(b || m == 0); + + /* Checks if every entry in a[] is also in b[] */ + + for (size_t i = 0; i < n; i++) { + size_t j; + + for (j = 0; j < m; j++) + if (a[i] == b[j]) + break; + + if (j >= m) + return false; + } + + return true; +} + +static bool gid_list_same(const gid_t *a, size_t n, const gid_t *b, size_t m) { + return gid_list_contained(a, n, b, m) && + gid_list_contained(b, m, a, n); +} static void *server(void *p) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -27,11 +56,13 @@ static void *server(void *p) { assert_se(sd_bus_set_fd(bus, fd, fd) >= 0); TAKE_FD(fd); assert_se(sd_bus_set_server(bus, true, id) >= 0); - assert_se(sd_bus_negotiate_creds(bus, 1, SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM|SD_BUS_CREDS_DESCRIPTION) >= 0); + assert_se(sd_bus_negotiate_creds(bus, 1, SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM|SD_BUS_CREDS_DESCRIPTION|SD_BUS_CREDS_PIDFD|SD_BUS_CREDS_SUPPLEMENTARY_GIDS) >= 0); assert_se(sd_bus_start(bus) >= 0); - assert_se(sd_bus_get_owner_creds(bus, SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM|SD_BUS_CREDS_DESCRIPTION, &c) >= 0); + assert_se(sd_bus_get_owner_creds(bus, SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM|SD_BUS_CREDS_DESCRIPTION|SD_BUS_CREDS_PIDFD|SD_BUS_CREDS_SUPPLEMENTARY_GIDS, &c) >= 0); + + bus_creds_dump(c, /* f= */ NULL, /* terse= */ false); uid_t u; assert_se(sd_bus_creds_get_euid(c, &u) >= 0); @@ -45,6 +76,26 @@ static void *server(void *p) { assert_se(sd_bus_creds_get_pid(c, &pid) >= 0); assert_se(pid == getpid_cached()); + int pidfd = -EBADF; + if (sd_bus_creds_get_pidfd_dup(c, &pidfd) >= 0) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + assert_se(pidref_set_pidfd_take(&pidref, pidfd) >= 0); + assert_se(pidref.pid == getpid_cached()); + } + + const gid_t *gl = NULL; + int n; + n = sd_bus_creds_get_supplementary_gids(c, &gl); + + if (n >= 0) { + _cleanup_free_ gid_t *gg = NULL; + r = getgroups_alloc(&gg); + assert_se(r >= 0); + + assert_se(gid_list_same(gl, n, gg, r)); + } + const char *comm; assert_se(sd_bus_creds_get_comm(c, &comm) >= 0); assert_se(pid_get_comm(0, &our_comm) >= 0); diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c index 6a60cde..4945d82 100644 --- a/src/libsystemd/sd-daemon/sd-daemon.c +++ b/src/libsystemd/sd-daemon/sd-daemon.c @@ -76,7 +76,7 @@ _public_ int sd_listen_fds(int unset_environment) { goto finish; } - for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { + for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { r = fd_cloexec(fd, true); if (r < 0) goto finish; @@ -456,6 +456,7 @@ static int pid_notify_with_fds_internal( const char *state, const int *fds, unsigned n_fds) { + SocketAddress address; struct iovec iovec; struct msghdr msghdr = { @@ -464,19 +465,12 @@ static int pid_notify_with_fds_internal( .msg_name = &address.sockaddr, }; _cleanup_close_ int fd = -EBADF; - struct cmsghdr *cmsg = NULL; - const char *e; - bool send_ucred; - ssize_t n; int type, r; - if (!state) - return -EINVAL; - - if (n_fds > 0 && !fds) - return -EINVAL; + assert_return(state, -EINVAL); + assert_return(fds || n_fds == 0, -EINVAL); - e = getenv("NOTIFY_SOCKET"); + const char *e = getenv("NOTIFY_SOCKET"); if (!e) return 0; @@ -530,12 +524,14 @@ static int pid_notify_with_fds_internal( iovec = IOVEC_MAKE_STRING(state); - send_ucred = + bool send_ucred = (pid != 0 && pid != getpid_cached()) || getuid() != geteuid() || getgid() != getegid(); if (n_fds > 0 || send_ucred) { + struct cmsghdr *cmsg; + /* CMSG_SPACE(0) may return value different than zero, which results in miscalculated controllen. */ msghdr.msg_controllen = (n_fds > 0 ? CMSG_SPACE(sizeof(int) * n_fds) : 0) + @@ -569,6 +565,8 @@ static int pid_notify_with_fds_internal( } } + ssize_t n; + do { /* First try with fake ucred data, as requested */ n = sendmsg(fd, &msghdr, MSG_NOSIGNAL); @@ -594,6 +592,19 @@ static int pid_notify_with_fds_internal( } } while (!iovec_increment(msghdr.msg_iov, msghdr.msg_iovlen, n)); + if (address.sockaddr.sa.sa_family == AF_VSOCK && IN_SET(type, SOCK_STREAM, SOCK_SEQPACKET)) { + /* For AF_VSOCK, we need to close the socket to signal the end of the message. */ + if (shutdown(fd, SHUT_WR) < 0) + return log_debug_errno(errno, "Failed to shutdown notify socket: %m"); + + char c; + n = recv(fd, &c, sizeof(c), MSG_NOSIGNAL); + if (n < 0) + return log_debug_errno(errno, "Failed to wait for EOF on notify socket: %m"); + if (n > 0) + return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Unexpectedly received data on notify socket."); + } + return 1; } @@ -650,7 +661,7 @@ _public_ int sd_notify(int unset_environment, const char *state) { _public_ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) { _cleanup_free_ char *p = NULL; - int r; + int r = 0, k; if (format) { va_list ap; @@ -659,16 +670,20 @@ _public_ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format r = vasprintf(&p, format, ap); va_end(ap); - if (r < 0 || !p) - return -ENOMEM; + if (r < 0 || !p) { + r = -ENOMEM; + p = mfree(p); /* If vasprintf failed, do not use the string, + * even if something was returned. */ + } } - return sd_pid_notify(pid, unset_environment, p); + k = sd_pid_notify(pid, unset_environment, p); + return r < 0 ? r : k; } _public_ int sd_notifyf(int unset_environment, const char *format, ...) { _cleanup_free_ char *p = NULL; - int r; + int r = 0, k; if (format) { va_list ap; @@ -677,11 +692,15 @@ _public_ int sd_notifyf(int unset_environment, const char *format, ...) { r = vasprintf(&p, format, ap); va_end(ap); - if (r < 0 || !p) - return -ENOMEM; + if (r < 0 || !p) { + r = -ENOMEM; + p = mfree(p); /* If vasprintf failed, do not use the string, + * even if something was returned. */ + } } - return sd_pid_notify(0, unset_environment, p); + k = sd_pid_notify(0, unset_environment, p); + return r < 0 ? r : k; } _public_ int sd_pid_notifyf_with_fds( @@ -691,27 +710,31 @@ _public_ int sd_pid_notifyf_with_fds( const char *format, ...) { _cleanup_free_ char *p = NULL; - int r; + int r = 0, k; /* Paranoia check: we traditionally used 'unsigned' as array size, but we nowadays more correctly use * 'size_t'. sd_pid_notifyf_with_fds() and sd_pid_notify_with_fds() are from different eras, hence * differ in this. Let's catch resulting incompatibilites early, even though they are pretty much * theoretic only */ if (n_fds > UINT_MAX) - return -E2BIG; + r = -E2BIG; - if (format) { + else if (format) { va_list ap; va_start(ap, format); r = vasprintf(&p, format, ap); va_end(ap); - if (r < 0 || !p) - return -ENOMEM; + if (r < 0 || !p) { + r = -ENOMEM; + p = mfree(p); /* If vasprintf failed, do not use the string, + * even if something was returned. */ + } } - return sd_pid_notify_with_fds(pid, unset_environment, p, fds, n_fds); + k = sd_pid_notify_with_fds(pid, unset_environment, p, fds, n_fds); + return r < 0 ? r : k; } _public_ int sd_booted(void) { diff --git a/src/libsystemd/sd-device/device-enumerator.c b/src/libsystemd/sd-device/device-enumerator.c index 15c5c42..71ab3d8 100644 --- a/src/libsystemd/sd-device/device-enumerator.c +++ b/src/libsystemd/sd-device/device-enumerator.c @@ -370,12 +370,8 @@ static int enumerator_sort_devices(sd_device_enumerator *enumerator) { HASHMAP_FOREACH_KEY(device, syspath, enumerator->devices_by_syspath) { _cleanup_free_ char *p = NULL; - const char *subsys; - if (sd_device_get_subsystem(device, &subsys) < 0) - continue; - - if (!streq(subsys, *prioritized_subsystem)) + if (!device_in_subsystem(device, *prioritized_subsystem)) continue; devices[n++] = sd_device_ref(device); @@ -662,10 +658,8 @@ static int enumerator_add_parent_devices( continue; r = device_enumerator_add_device(enumerator, device); - if (r < 0) + if (r <= 0) /* r == 0 means the device already exists, then no need to go further up. */ return r; - if (r == 0) /* Exists already? Then no need to go further up. */ - return 0; } } diff --git a/src/libsystemd/sd-device/device-monitor.c b/src/libsystemd/sd-device/device-monitor.c index bb4f9bd..a7ef03a 100644 --- a/src/libsystemd/sd-device/device-monitor.c +++ b/src/libsystemd/sd-device/device-monitor.c @@ -47,7 +47,7 @@ struct sd_device_monitor { union sockaddr_union snl_trusted_sender; bool bound; - UidRange *mapped_userns_uid_range; + UIDRange *mapped_userns_uid_range; Hashmap *subsystem_filter; Set *tag_filter; @@ -402,8 +402,7 @@ static sd_device_monitor *device_monitor_free(sd_device_monitor *m) { DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_device_monitor, sd_device_monitor, device_monitor_free); static int check_subsystem_filter(sd_device_monitor *m, sd_device *device) { - const char *s, *subsystem, *d, *devtype = NULL; - int r; + const char *s, *d; assert(m); assert(device); @@ -411,20 +410,14 @@ static int check_subsystem_filter(sd_device_monitor *m, sd_device *device) { if (hashmap_isempty(m->subsystem_filter)) return true; - r = sd_device_get_subsystem(device, &subsystem); - if (r < 0) - return r; - - r = sd_device_get_devtype(device, &devtype); - if (r < 0 && r != -ENOENT) - return r; - HASHMAP_FOREACH_KEY(d, s, m->subsystem_filter) { - if (!streq(s, subsystem)) + if (!device_in_subsystem(device, s)) continue; - if (!d || streq_ptr(d, devtype)) - return true; + if (d && !device_is_devtype(device, d)) + continue; + + return true; } return false; @@ -480,7 +473,7 @@ static bool check_sender_uid(sd_device_monitor *m, uid_t uid) { return true; if (!m->mapped_userns_uid_range) { - r = uid_range_load_userns(&m->mapped_userns_uid_range, NULL); + r = uid_range_load_userns(/* path = */ NULL, UID_RANGE_USERNS_INSIDE, &m->mapped_userns_uid_range); if (r < 0) log_monitor_errno(m, r, "Failed to load UID ranges mapped to the current user namespace, ignoring: %m"); } diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c index 0edabfb..cd85ec9 100644 --- a/src/libsystemd/sd-device/device-private.c +++ b/src/libsystemd/sd-device/device-private.c @@ -768,55 +768,96 @@ static bool device_has_info(sd_device *device) { return false; } +bool device_should_have_db(sd_device *device) { + assert(device); + + if (device_has_info(device)) + return true; + + if (major(device->devnum) != 0) + return true; + + if (device->ifindex != 0) + return true; + + return false; +} + void device_set_db_persist(sd_device *device) { assert(device); device->db_persist = true; } -int device_update_db(sd_device *device) { +static int device_get_db_path(sd_device *device, char **ret) { const char *id; char *path; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_(unlink_and_freep) char *path_tmp = NULL; - bool has_info; int r; assert(device); - - has_info = device_has_info(device); + assert(ret); r = device_get_device_id(device, &id); if (r < 0) return r; - path = strjoina("/run/udev/data/", id); + path = path_join("/run/udev/data/", id); + if (!path) + return -ENOMEM; + + *ret = path; + return 0; +} + +int device_has_db(sd_device *device) { + _cleanup_free_ char *path = NULL; + int r; + + assert(device); + + r = device_get_db_path(device, &path); + if (r < 0) + return r; + + return access(path, F_OK) >= 0; +} + +int device_update_db(sd_device *device) { + _cleanup_(unlink_and_freep) char *path = NULL, *path_tmp = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(device); /* do not store anything for otherwise empty devices */ - if (!has_info && major(device->devnum) == 0 && device->ifindex == 0) { - if (unlink(path) < 0 && errno != ENOENT) - return -errno; + if (!device_should_have_db(device)) + return device_delete_db(device); - return 0; - } + r = device_get_db_path(device, &path); + if (r < 0) + return r; /* write a database file */ r = mkdir_parents(path, 0755); if (r < 0) - return r; + return log_device_debug_errno(device, r, + "sd-device: Failed to create parent directories of '%s': %m", + path); r = fopen_temporary(path, &f, &path_tmp); if (r < 0) - return r; + return log_device_debug_errno(device, r, + "sd-device: Failed to create temporary file for database file '%s': %m", + path); /* set 'sticky' bit to indicate that we should not clean the database when we transition from initrd * to the real root */ - if (fchmod(fileno(f), device->db_persist ? 01644 : 0644) < 0) { - r = -errno; - goto fail; - } + if (fchmod(fileno(f), device->db_persist ? 01644 : 0644) < 0) + return log_device_debug_errno(device, errno, + "sd-device: Failed to chmod temporary database file '%s': %m", + path_tmp); - if (has_info) { + if (device_has_info(device)) { const char *property, *value, *ct; if (major(device->devnum) > 0) { @@ -846,45 +887,55 @@ int device_update_db(sd_device *device) { r = fflush_and_check(f); if (r < 0) - goto fail; + return log_device_debug_errno(device, r, + "sd-device: Failed to flush temporary database file '%s': %m", + path_tmp); - if (rename(path_tmp, path) < 0) { - r = -errno; - goto fail; - } + if (rename(path_tmp, path) < 0) + return log_device_debug_errno(device, errno, + "sd-device: Failed to rename temporary database file '%s' to '%s': %m", + path_tmp, path); - path_tmp = mfree(path_tmp); + log_device_debug(device, "sd-device: Created database file '%s' for '%s'.", path, device->devpath); - log_device_debug(device, "sd-device: Created %s file '%s' for '%s'", has_info ? "db" : "empty", - path, device->devpath); + path_tmp = mfree(path_tmp); + path = mfree(path); return 0; - -fail: - (void) unlink(path); - - return log_device_debug_errno(device, r, "sd-device: Failed to create %s file '%s' for '%s'", has_info ? "db" : "empty", path, device->devpath); } int device_delete_db(sd_device *device) { - const char *id; - char *path; + _cleanup_free_ char *path = NULL; int r; assert(device); - r = device_get_device_id(device, &id); + r = device_get_db_path(device, &path); if (r < 0) return r; - path = strjoina("/run/udev/data/", id); - if (unlink(path) < 0 && errno != ENOENT) return -errno; return 0; } +int device_read_db_internal(sd_device *device, bool force) { + _cleanup_free_ char *path = NULL; + int r; + + assert(device); + + if (device->db_loaded || (!force && device->sealed)) + return 0; + + r = device_get_db_path(device, &path); + if (r < 0) + return r; + + return device_read_db_internal_filename(device, path); +} + static const char* const device_action_table[_SD_DEVICE_ACTION_MAX] = { [SD_DEVICE_ADD] = "add", [SD_DEVICE_REMOVE] = "remove", diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h index e8a6d52..85e8609 100644 --- a/src/libsystemd/sd-device/device-private.h +++ b/src/libsystemd/sd-device/device-private.h @@ -24,6 +24,7 @@ int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, uns static inline int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value) { return device_get_sysattr_unsigned_full(device, sysattr, 0, ret_value); } +int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value); int device_get_sysattr_bool(sd_device *device, const char *sysattr); int device_get_device_id(sd_device *device, const char **ret); int device_get_devlink_priority(sd_device *device, int *ret); @@ -61,6 +62,8 @@ int device_get_properties_strv(sd_device *device, char ***ret); int device_clone_with_db(sd_device *device, sd_device **ret); int device_tag_index(sd_device *dev, sd_device *dev_old, bool add); +bool device_should_have_db(sd_device *device); +int device_has_db(sd_device *device); int device_update_db(sd_device *device); int device_delete_db(sd_device *device); int device_read_db_internal_filename(sd_device *device, const char *filename); /* For fuzzer */ diff --git a/src/libsystemd/sd-device/device-util.c b/src/libsystemd/sd-device/device-util.c index 529eff2..123629c 100644 --- a/src/libsystemd/sd-device/device-util.c +++ b/src/libsystemd/sd-device/device-util.c @@ -9,7 +9,6 @@ int devname_from_devnum(mode_t mode, dev_t devnum, char **ret) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - _cleanup_free_ char *s = NULL; const char *devname; int r; @@ -26,15 +25,10 @@ int devname_from_devnum(mode_t mode, dev_t devnum, char **ret) { if (r < 0) return r; - s = strdup(devname); - if (!s) - return -ENOMEM; - - *ret = TAKE_PTR(s); - return 0; + return strdup_to(ret, devname); } -int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret) { +int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret_devname) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; _cleanup_close_ int fd = -EBADF; int r; @@ -47,19 +41,16 @@ int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret) { if (fd < 0) return fd; - if (ret) { + if (ret_devname) { const char *devname; - char *s; r = sd_device_get_devname(dev, &devname); if (r < 0) return r; - s = strdup(devname); - if (!s) - return -ENOMEM; - - *ret = s; + r = strdup_to(ret_devname, devname); + if (r < 0) + return r; } return TAKE_FD(fd); @@ -139,3 +130,21 @@ char** device_make_log_fields(sd_device *device) { return TAKE_PTR(strv); } + +bool device_in_subsystem(sd_device *device, const char *subsystem) { + const char *s = NULL; + + assert(device); + + (void) sd_device_get_subsystem(device, &s); + return streq_ptr(s, subsystem); +} + +bool device_is_devtype(sd_device *device, const char *devtype) { + const char *s = NULL; + + assert(device); + + (void) sd_device_get_devtype(device, &s); + return streq_ptr(s, devtype); +} diff --git a/src/libsystemd/sd-device/device-util.h b/src/libsystemd/sd-device/device-util.h index bf86ddc..b17993d 100644 --- a/src/libsystemd/sd-device/device-util.h +++ b/src/libsystemd/sd-device/device-util.h @@ -10,6 +10,7 @@ #include "alloc-util.h" #include "log.h" #include "macro.h" +#include "strv.h" #define device_unref_and_replace(a, b) \ unref_and_replace_full(a, b, sd_device_ref, sd_device_unref) @@ -99,6 +100,16 @@ static inline int devname_from_stat_rdev(const struct stat *st, char **ret) { assert(st); return devname_from_devnum(st->st_mode, st->st_rdev, ret); } -int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret); +int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret_devname); char** device_make_log_fields(sd_device *device); + +bool device_in_subsystem(sd_device *device, const char *subsystem); +bool device_is_devtype(sd_device *device, const char *devtype); + +static inline bool device_property_can_set(const char *property) { + return property && + !STR_IN_SET(property, + "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER", + "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS"); +} diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index 01e66b4..d8d1518 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -214,7 +214,7 @@ int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { /* Only operate on sysfs, i.e. refuse going down into /sys/fs/cgroup/ or similar places where * things are not arranged as kobjects in kernel, and hence don't necessarily have * kobject/attribute structure. */ - r = getenv_bool_secure("SYSTEMD_DEVICE_VERIFY_SYSFS"); + r = secure_getenv_bool("SYSTEMD_DEVICE_VERIFY_SYSFS"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_DEVICE_VERIFY_SYSFS value: %m"); if (r != 0) { @@ -283,7 +283,7 @@ _public_ int sd_device_new_from_syspath(sd_device **ret, const char *syspath) { int device_new_from_mode_and_devnum(sd_device **ret, mode_t mode, dev_t devnum) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; _cleanup_free_ char *syspath = NULL; - const char *t, *subsystem = NULL; + const char *t; dev_t n; int r; @@ -314,10 +314,7 @@ int device_new_from_mode_and_devnum(sd_device **ret, mode_t mode, dev_t devnum) if (n != devnum) return -ENXIO; - r = sd_device_get_subsystem(dev, &subsystem); - if (r < 0 && r != -ENOENT) - return r; - if (streq_ptr(subsystem, "block") != !!S_ISBLK(mode)) + if (device_in_subsystem(dev, "block") != !!S_ISBLK(mode)) return -ENXIO; *ret = TAKE_PTR(dev); @@ -348,17 +345,11 @@ _public_ int sd_device_new_from_ifname(sd_device **ret, const char *ifname) { assert_return(ret, -EINVAL); assert_return(ifname, -EINVAL); - r = parse_ifindex(ifname); - if (r > 0) - return sd_device_new_from_ifindex(ret, r); - - if (ifname_valid(ifname)) { - r = device_new_from_main_ifname(ret, ifname); - if (r >= 0) - return r; - } + r = device_new_from_main_ifname(ret, ifname); + if (r >= 0) + return r; - r = rtnl_resolve_link_alternative_name(NULL, ifname, &main_name); + r = rtnl_resolve_ifname_full(NULL, RESOLVE_IFNAME_ALTERNATIVE | RESOLVE_IFNAME_NUMERIC, ifname, &main_name, NULL); if (r < 0) return r; @@ -367,14 +358,15 @@ _public_ int sd_device_new_from_ifname(sd_device **ret, const char *ifname) { _public_ int sd_device_new_from_ifindex(sd_device **ret, int ifindex) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - char ifname[IF_NAMESIZE]; + _cleanup_free_ char *ifname = NULL; int r, i; assert_return(ret, -EINVAL); assert_return(ifindex > 0, -EINVAL); - if (format_ifname(ifindex, ifname) < 0) - return -ENODEV; + r = rtnl_get_ifname_full(NULL, ifindex, &ifname, NULL); + if (r < 0) + return r; r = device_new_from_main_ifname(&dev, ifname); if (r < 0) @@ -1222,37 +1214,27 @@ _public_ int sd_device_get_devtype(sd_device *device, const char **devtype) { return !!device->devtype; } -_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret) { - sd_device *parent = NULL; +_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *device, const char *subsystem, const char *devtype, sd_device **ret) { int r; - assert_return(child, -EINVAL); + assert_return(device, -EINVAL); assert_return(subsystem, -EINVAL); - r = sd_device_get_parent(child, &parent); - while (r >= 0) { - const char *parent_subsystem = NULL; + for (;;) { + r = sd_device_get_parent(device, &device); + if (r < 0) + return r; - (void) sd_device_get_subsystem(parent, &parent_subsystem); - if (streq_ptr(parent_subsystem, subsystem)) { - const char *parent_devtype = NULL; + if (!device_in_subsystem(device, subsystem)) + continue; - if (!devtype) - break; + if (devtype && !device_is_devtype(device, devtype)) + continue; - (void) sd_device_get_devtype(parent, &parent_devtype); - if (streq_ptr(parent_devtype, devtype)) - break; - } - r = sd_device_get_parent(parent, &parent); + if (ret) + *ret = device; + return 0; } - - if (r < 0) - return r; - - if (ret) - *ret = parent; - return 0; } _public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) { @@ -1778,24 +1760,6 @@ int device_read_db_internal_filename(sd_device *device, const char *filename) { return 0; } -int device_read_db_internal(sd_device *device, bool force) { - const char *id, *path; - int r; - - assert(device); - - if (device->db_loaded || (!force && device->sealed)) - return 0; - - r = device_get_device_id(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/data/", id); - - return device_read_db_internal_filename(device, path); -} - _public_ int sd_device_get_is_initialized(sd_device *device) { int r; @@ -2454,6 +2418,25 @@ int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, uns return v > 0; } +int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value) { + const char *value; + int r; + + r = sd_device_get_sysattr_value(device, sysattr, &value); + if (r < 0) + return r; + + uint32_t v; + r = safe_atou32(value, &v); + if (r < 0) + return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); + + if (ret_value) + *ret_value = v; + /* We return "true" if the value is positive. */ + return v > 0; +} + int device_get_sysattr_bool(sd_device *device, const char *sysattr) { const char *value; int r; @@ -2506,7 +2489,7 @@ _public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, /* drop trailing newlines */ while (len > 0 && strchr(NEWLINE, _value[len - 1])) - len --; + len--; /* value length is limited to 4k */ if (len > 4096) @@ -2609,7 +2592,7 @@ _public_ int sd_device_trigger_with_uuid( _public_ int sd_device_open(sd_device *device, int flags) { _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; - const char *devname, *subsystem = NULL; + const char *devname; uint64_t q, diskseq = 0; struct stat st; dev_t devnum; @@ -2630,10 +2613,6 @@ _public_ int sd_device_open(sd_device *device, int flags) { if (r < 0) return r; - r = sd_device_get_subsystem(device, &subsystem); - if (r < 0 && r != -ENOENT) - return r; - fd = open(devname, FLAGS_SET(flags, O_PATH) ? flags : O_CLOEXEC|O_NOFOLLOW|O_PATH); if (fd < 0) return -errno; @@ -2644,7 +2623,7 @@ _public_ int sd_device_open(sd_device *device, int flags) { if (st.st_rdev != devnum) return -ENXIO; - if (streq_ptr(subsystem, "block") ? !S_ISBLK(st.st_mode) : !S_ISCHR(st.st_mode)) + if (device_in_subsystem(device, "block") ? !S_ISBLK(st.st_mode) : !S_ISCHR(st.st_mode)) return -ENXIO; /* If flags has O_PATH, then we cannot check diskseq. Let's return earlier. */ diff --git a/src/libsystemd/sd-device/test-device-util.c b/src/libsystemd/sd-device/test-device-util.c index bc8ab66..f7c9deb 100644 --- a/src/libsystemd/sd-device/test-device-util.c +++ b/src/libsystemd/sd-device/test-device-util.c @@ -1,23 +1,91 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "device-util.h" +#include "mountpoint-util.h" #include "tests.h" TEST(log_device_full) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; int r; + (void) sd_device_new_from_subsystem_sysname(&dev, "net", "lo"); + for (int level = LOG_ERR; level <= LOG_DEBUG; level++) { - log_device_full(NULL, level, "test level=%d: %m", level); + log_device_full(dev, level, "test level=%d: %m", level); - r = log_device_full_errno(NULL, level, EUCLEAN, "test level=%d errno=EUCLEAN: %m", level); + r = log_device_full_errno(dev, level, EUCLEAN, "test level=%d errno=EUCLEAN: %m", level); assert_se(r == -EUCLEAN); - r = log_device_full_errno(NULL, level, 0, "test level=%d errno=0: %m", level); + r = log_device_full_errno(dev, level, 0, "test level=%d errno=0: %m", level); assert_se(r == 0); - r = log_device_full_errno(NULL, level, SYNTHETIC_ERRNO(ENODATA), "test level=%d errno=S(ENODATA): %m", level); + r = log_device_full_errno(dev, level, SYNTHETIC_ERRNO(ENODATA), "test level=%d errno=S(ENODATA).", level); assert_se(r == -ENODATA); } } -DEFINE_TEST_MAIN(LOG_INFO); +TEST(device_in_subsystem) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + int r; + + r = sd_device_new_from_subsystem_sysname(&dev, "net", "lo"); + if (r == -ENODEV) + return (void) log_tests_skipped("net/lo does not exist"); + assert_se(r >= 0); + + assert_se(device_in_subsystem(dev, "net")); + assert_se(!device_in_subsystem(dev, "disk")); + assert_se(!device_in_subsystem(dev, "subsystem")); + assert_se(!device_in_subsystem(dev, "")); + assert_se(!device_in_subsystem(dev, NULL)); + + dev = sd_device_unref(dev); + + assert_se(sd_device_new_from_syspath(&dev, "/sys/class/net") >= 0); + assert_se(!device_in_subsystem(dev, "net")); + assert_se(!device_in_subsystem(dev, "disk")); + assert_se(device_in_subsystem(dev, "subsystem")); + assert_se(!device_in_subsystem(dev, "")); + assert_se(!device_in_subsystem(dev, NULL)); + + dev = sd_device_unref(dev); + + assert_se(sd_device_new_from_syspath(&dev, "/sys/class") >= 0); + assert_se(!device_in_subsystem(dev, "net")); + assert_se(!device_in_subsystem(dev, "disk")); + assert_se(!device_in_subsystem(dev, "subsystem")); + assert_se(!device_in_subsystem(dev, "")); + assert_se(device_in_subsystem(dev, NULL)); +} + +TEST(device_is_devtype) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + + assert_se(sd_device_enumerator_new(&e) >= 0); + assert_se(sd_device_enumerator_add_match_subsystem(e, "disk", true) >= 0); + + FOREACH_DEVICE(e, d) { + const char *t; + + assert_se(sd_device_get_devtype(d, &t) >= 0); + assert_se(device_is_devtype(d, t)); + assert_se(!device_is_devtype(d, "hoge")); + assert_se(!device_is_devtype(d, "")); + assert_se(!device_is_devtype(d, NULL)); + } + + assert_se(sd_device_new_from_syspath(&dev, "/sys/class/net") >= 0); + assert_se(!device_is_devtype(dev, "hoge")); + assert_se(!device_is_devtype(dev, "")); + assert_se(device_is_devtype(dev, NULL)); +} + +static int intro(void) { + if (path_is_mount_point("/sys") <= 0) + return log_tests_skipped("/sys is not mounted"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/libsystemd/sd-device/test-sd-device-monitor.c b/src/libsystemd/sd-device/test-sd-device-monitor.c index e124e00..3dbf987 100644 --- a/src/libsystemd/sd-device/test-sd-device-monitor.c +++ b/src/libsystemd/sd-device/test-sd-device-monitor.c @@ -10,6 +10,7 @@ #include "device-private.h" #include "device-util.h" #include "macro.h" +#include "mountpoint-util.h" #include "path-util.h" #include "stat-util.h" #include "string-util.h" @@ -298,6 +299,9 @@ int main(int argc, char *argv[]) { if (getuid() != 0) return log_tests_skipped("not root"); + if (path_is_mount_point("/sys") <= 0) + return log_tests_skipped("/sys is not mounted"); + if (path_is_read_only_fs("/sys") > 0) return log_tests_skipped("Running in container"); diff --git a/src/libsystemd/sd-device/test-sd-device-thread.c b/src/libsystemd/sd-device/test-sd-device-thread.c index c99d179..539dabd 100644 --- a/src/libsystemd/sd-device/test-sd-device-thread.c +++ b/src/libsystemd/sd-device/test-sd-device-thread.c @@ -8,6 +8,7 @@ #include "sd-device.h" #include "device-util.h" +#include "tests.h" #define handle_error_errno(error, msg) \ ({ \ @@ -30,6 +31,8 @@ int main(int argc, char *argv[]) { int r; r = sd_device_new_from_syspath(&loopback, "/sys/class/net/lo"); + if (r == -ENODEV) + return log_tests_skipped("Loopback device not found"); if (r < 0) return handle_error_errno(r, "Failed to create loopback device object"); diff --git a/src/libsystemd/sd-device/test-sd-device.c b/src/libsystemd/sd-device/test-sd-device.c index bce99b5..9fde1a0 100644 --- a/src/libsystemd/sd-device/test-sd-device.c +++ b/src/libsystemd/sd-device/test-sd-device.c @@ -11,6 +11,7 @@ #include "errno-util.h" #include "fd-util.h" #include "hashmap.h" +#include "mountpoint-util.h" #include "nulstr-util.h" #include "path-util.h" #include "rm-rf.h" @@ -675,4 +676,11 @@ TEST(devname_from_devnum) { } } -DEFINE_TEST_MAIN(LOG_INFO); +static int intro(void) { + if (path_is_mount_point("/sys") <= 0) + return log_tests_skipped("/sys is not mounted"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/libsystemd/sd-event/event-source.h b/src/libsystemd/sd-event/event-source.h index f4e38d7..d05bcf0 100644 --- a/src/libsystemd/sd-event/event-source.h +++ b/src/libsystemd/sd-event/event-source.h @@ -189,6 +189,9 @@ struct inode_data { * iteration. */ int fd; + /* The path that the fd points to. The field is optional. */ + char *path; + /* The inotify "watch descriptor" */ int wd; diff --git a/src/libsystemd/sd-event/event-util.c b/src/libsystemd/sd-event/event-util.c index a310122..862455e 100644 --- a/src/libsystemd/sd-event/event-util.c +++ b/src/libsystemd/sd-event/event-util.c @@ -151,3 +151,29 @@ int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handle return 0; } + +int event_add_child_pidref( + sd_event *e, + sd_event_source **s, + const PidRef *pid, + int options, + sd_event_child_handler_t callback, + void *userdata) { + + if (!pidref_is_set(pid)) + return -ESRCH; + + if (pid->fd >= 0) + return sd_event_add_child_pidfd(e, s, pid->fd, options, callback, userdata); + + return sd_event_add_child(e, s, pid->pid, options, callback, userdata); +} + +dual_timestamp* event_dual_timestamp_now(sd_event *e, dual_timestamp *ts) { + assert(e); + assert(ts); + + assert_se(sd_event_now(e, CLOCK_REALTIME, &ts->realtime) >= 0); + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &ts->monotonic) >= 0); + return ts; +} diff --git a/src/libsystemd/sd-event/event-util.h b/src/libsystemd/sd-event/event-util.h index c185584..7002ca3 100644 --- a/src/libsystemd/sd-event/event-util.h +++ b/src/libsystemd/sd-event/event-util.h @@ -5,6 +5,8 @@ #include "sd-event.h" +#include "pidref.h" + int event_reset_time( sd_event *e, sd_event_source **s, @@ -32,3 +34,7 @@ static inline int event_source_disable(sd_event_source *s) { } int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handler_t callback, void *userdata); + +int event_add_child_pidref(sd_event *e, sd_event_source **s, const PidRef *pid, int options, sd_event_child_handler_t callback, void *userdata); + +dual_timestamp* event_dual_timestamp_now(sd_event *e, dual_timestamp *ts); diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index b6899df..a1305ef 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include <sys/epoll.h> +#if HAVE_PIDFD_OPEN +#include <sys/pidfd.h> +#endif #include <sys/timerfd.h> #include <sys/wait.h> @@ -1165,10 +1168,10 @@ static int source_set_pending(sd_event_source *s, bool b) { assert(s->inotify.inode_data->inotify_data); if (b) - s->inotify.inode_data->inotify_data->n_pending ++; + s->inotify.inode_data->inotify_data->n_pending++; else { assert(s->inotify.inode_data->inotify_data->n_pending > 0); - s->inotify.inode_data->inotify_data->n_pending --; + s->inotify.inode_data->inotify_data->n_pending--; } } @@ -1574,7 +1577,7 @@ static int child_exit_callback(sd_event_source *s, const siginfo_t *si, void *us static bool shall_use_pidfd(void) { /* Mostly relevant for debugging, i.e. this is used in test-event.c to test the event loop once with and once without pidfd */ - return getenv_bool_secure("SYSTEMD_PIDFD") != 0; + return secure_getenv_bool("SYSTEMD_PIDFD") != 0; } _public_ int sd_event_add_child( @@ -1976,7 +1979,7 @@ _public_ int sd_event_add_memory_pressure( env = secure_getenv("MEMORY_PRESSURE_WRITE"); if (env) { - r = unbase64mem(env, SIZE_MAX, &write_buffer, &write_buffer_size); + r = unbase64mem(env, &write_buffer, &write_buffer_size); if (r < 0) return r; } @@ -2231,8 +2234,8 @@ static int inode_data_compare(const struct inode_data *x, const struct inode_dat static void inode_data_hash_func(const struct inode_data *d, struct siphash *state) { assert(d); - siphash24_compress(&d->dev, sizeof(d->dev), state); - siphash24_compress(&d->ino, sizeof(d->ino), state); + siphash24_compress_typesafe(d->dev, state); + siphash24_compress_typesafe(d->ino, state); } DEFINE_PRIVATE_HASH_OPS(inode_data_hash_ops, struct inode_data, inode_data_hash_func, inode_data_compare); @@ -2272,6 +2275,7 @@ static void event_free_inode_data( assert_se(hashmap_remove(d->inotify_data->inodes, d) == d); } + free(d->path); free(d); } @@ -2512,6 +2516,15 @@ static int event_add_inotify_fd_internal( } LIST_PREPEND(to_close, e->inode_data_to_close_list, inode_data); + + _cleanup_free_ char *path = NULL; + r = fd_get_path(inode_data->fd, &path); + if (r < 0 && r != -ENOSYS) { /* The path is optional, hence ignore -ENOSYS. */ + event_gc_inode_data(e, inode_data); + return r; + } + + free_and_replace(inode_data->path, path); } /* Link our event source to the inode data object */ @@ -2797,6 +2810,13 @@ _public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority) } LIST_PREPEND(to_close, s->event->inode_data_to_close_list, new_inode_data); + + _cleanup_free_ char *path = NULL; + r = fd_get_path(new_inode_data->fd, &path); + if (r < 0 && r != -ENOSYS) + goto fail; + + free_and_replace(new_inode_data->path, path); } /* Move the event source to the new inode data structure */ @@ -3281,13 +3301,29 @@ _public_ int sd_event_source_set_child_process_own(sd_event_source *s, int own) return 0; } -_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *mask) { +_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret) { assert_return(s, -EINVAL); - assert_return(mask, -EINVAL); + assert_return(ret, -EINVAL); assert_return(s->type == SOURCE_INOTIFY, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - *mask = s->inotify.mask; + *ret = s->inotify.mask; + return 0; +} + +_public_ int sd_event_source_get_inotify_path(sd_event_source *s, const char **ret) { + assert_return(s, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(s->type == SOURCE_INOTIFY, -EDOM); + assert_return(!event_origin_changed(s->event), -ECHILD); + + if (!s->inotify.inode_data) + return -ESTALE; /* already disconnected. */ + + if (!s->inotify.inode_data->path) + return -ENOSYS; /* /proc was not mounted? */ + + *ret = s->inotify.inode_data->path; return 0; } @@ -3999,7 +4035,7 @@ static int process_inotify(sd_event *e) { if (r < 0) return r; if (r > 0) - done ++; + done++; } return done; @@ -4914,13 +4950,13 @@ _public_ int sd_event_get_state(sd_event *e) { _public_ int sd_event_get_exit_code(sd_event *e, int *code) { assert_return(e, -EINVAL); assert_return(e = event_resolve(e), -ENOPKG); - assert_return(code, -EINVAL); assert_return(!event_origin_changed(e), -ECHILD); if (!e->exit_requested) return -ENODATA; - *code = e->exit_code; + if (code) + *code = e->exit_code; return 0; } @@ -5037,7 +5073,7 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) { } } - e->watchdog = !!b; + e->watchdog = b; return e->watchdog; fail: diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c index cc3d84e..57dee39 100644 --- a/src/libsystemd/sd-event/test-event.c +++ b/src/libsystemd/sd-event/test-event.c @@ -1,5 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#if HAVE_PIDFD_OPEN +#include <sys/pidfd.h> +#endif #include <sys/wait.h> #include <unistd.h> @@ -92,7 +95,7 @@ static int signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, assert_se(userdata == INT_TO_PTR('e')); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGUSR2, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGUSR2) >= 0); pid = fork(); assert_se(pid >= 0); @@ -142,7 +145,7 @@ static int defer_handler(sd_event_source *s, void *userdata) { assert_se(userdata == INT_TO_PTR('d')); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGUSR1, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGUSR1) >= 0); assert_se(sd_event_add_signal(sd_event_source_get_event(s), &p, SIGUSR1, signal_handler, INT_TO_PTR('e')) >= 0); assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); @@ -254,7 +257,7 @@ static void test_basic_one(bool with_pidfd) { assert_se(sd_event_source_set_prepare(z, prepare_handler) >= 0); /* Test for floating event sources */ - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+1, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+1) >= 0); assert_se(sd_event_add_signal(e, NULL, SIGRTMIN+1, NULL, NULL) >= 0); assert_se(write(a[1], &ch, 1) >= 0); @@ -346,7 +349,7 @@ TEST(rtqueue) { assert_se(sd_event_default(&e) >= 0); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+2, SIGRTMIN+3, SIGUSR2, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+2, SIGRTMIN+3, SIGUSR2) >= 0); assert_se(sd_event_add_signal(e, &u, SIGRTMIN+2, rtqueue_handler, NULL) >= 0); assert_se(sd_event_add_signal(e, &v, SIGRTMIN+3, rtqueue_handler, NULL) >= 0); assert_se(sd_event_add_signal(e, &s, SIGUSR2, rtqueue_handler, NULL) >= 0); @@ -396,6 +399,7 @@ struct inotify_context { unsigned create_called[CREATE_EVENTS_MAX]; unsigned create_overflow; unsigned n_create_events; + const char *path; }; static void maybe_exit(sd_event_source *s, struct inotify_context *c) { @@ -422,10 +426,12 @@ static void maybe_exit(sd_event_source *s, struct inotify_context *c) { } static int inotify_handler(sd_event_source *s, const struct inotify_event *ev, void *userdata) { - struct inotify_context *c = userdata; - const char *description; + struct inotify_context *c = ASSERT_PTR(userdata); + const char *path, *description; unsigned bit, n; + assert_se(sd_event_source_get_inotify_path(s, &path) >= 0); + assert_se(sd_event_source_get_description(s, &description) >= 0); assert_se(safe_atou(description, &n) >= 0); @@ -433,11 +439,12 @@ static int inotify_handler(sd_event_source *s, const struct inotify_event *ev, v bit = 1U << n; if (ev->mask & IN_Q_OVERFLOW) { - log_info("inotify-handler <%s>: overflow", description); + log_info("inotify-handler for %s <%s>: overflow", path, description); c->create_overflow |= bit; } else if (ev->mask & IN_CREATE) { + assert_se(path_equal_or_inode_same(path, c->path, 0)); if (streq(ev->name, "sub")) - log_debug("inotify-handler <%s>: create on %s", description, ev->name); + log_debug("inotify-handler for %s <%s>: create on %s", path, description, ev->name); else { unsigned i; @@ -446,7 +453,7 @@ static int inotify_handler(sd_event_source *s, const struct inotify_event *ev, v c->create_called[i] |= bit; } } else if (ev->mask & IN_DELETE) { - log_info("inotify-handler <%s>: delete of %s", description, ev->name); + log_info("inotify-handler for %s <%s>: delete of %s", path, description, ev->name); assert_se(streq(ev->name, "sub")); } else assert_not_reached(); @@ -456,16 +463,19 @@ static int inotify_handler(sd_event_source *s, const struct inotify_event *ev, v } static int delete_self_handler(sd_event_source *s, const struct inotify_event *ev, void *userdata) { - struct inotify_context *c = userdata; + struct inotify_context *c = ASSERT_PTR(userdata); + const char *path; + + assert_se(sd_event_source_get_inotify_path(s, &path) >= 0); if (ev->mask & IN_Q_OVERFLOW) { - log_info("delete-self-handler: overflow"); + log_info("delete-self-handler for %s: overflow", path); c->delete_self_handler_called = true; } else if (ev->mask & IN_DELETE_SELF) { - log_info("delete-self-handler: delete-self"); + log_info("delete-self-handler for %s: delete-self", path); c->delete_self_handler_called = true; } else if (ev->mask & IN_IGNORED) { - log_info("delete-self-handler: ignore"); + log_info("delete-self-handler for %s: ignore", path); } else assert_not_reached(); @@ -480,7 +490,7 @@ static void test_inotify_one(unsigned n_create_events) { .n_create_events = n_create_events, }; sd_event *e = NULL; - const char *q; + const char *q, *pp; unsigned i; log_info("/* %s(%u) */", __func__, n_create_events); @@ -488,6 +498,7 @@ static void test_inotify_one(unsigned n_create_events) { assert_se(sd_event_default(&e) >= 0); assert_se(mkdtemp_malloc("/tmp/test-inotify-XXXXXX", &p) >= 0); + context.path = p; assert_se(sd_event_add_inotify(e, &a, p, IN_CREATE|IN_ONLYDIR, inotify_handler, &context) >= 0); assert_se(sd_event_add_inotify(e, &b, p, IN_CREATE|IN_DELETE|IN_DONT_FOLLOW, inotify_handler, &context) >= 0); @@ -500,6 +511,13 @@ static void test_inotify_one(unsigned n_create_events) { assert_se(sd_event_source_set_description(b, "1") >= 0); assert_se(sd_event_source_set_description(c, "2") >= 0); + assert_se(sd_event_source_get_inotify_path(a, &pp) >= 0); + assert_se(path_equal_or_inode_same(pp, p, 0)); + assert_se(sd_event_source_get_inotify_path(b, &pp) >= 0); + assert_se(path_equal_or_inode_same(pp, p, 0)); + assert_se(sd_event_source_get_inotify_path(b, &pp) >= 0); + assert_se(path_equal_or_inode_same(pp, p, 0)); + q = strjoina(p, "/sub"); assert_se(touch(q) >= 0); assert_se(sd_event_add_inotify(e, &d, q, IN_DELETE_SELF, delete_self_handler, &context) >= 0); @@ -556,7 +574,7 @@ TEST(pidfd) { int pidfd; pid_t pid, pid2; - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0); pid = fork(); if (pid == 0) diff --git a/src/libsystemd/sd-hwdb/sd-hwdb.c b/src/libsystemd/sd-hwdb/sd-hwdb.c index f163314..f623d47 100644 --- a/src/libsystemd/sd-hwdb/sd-hwdb.c +++ b/src/libsystemd/sd-hwdb/sd-hwdb.c @@ -313,15 +313,15 @@ static int hwdb_new(const char *path, sd_hwdb **ret) { if (!hwdb->f) return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), - "hwdb.bin does not exist, please run 'systemd-hwdb update'"); + "hwdb.bin does not exist, please run 'systemd-hwdb update'."); } if (fstat(fileno(hwdb->f), &hwdb->st) < 0) return log_debug_errno(errno, "Failed to stat %s: %m", path); if (hwdb->st.st_size < (off_t) offsetof(struct trie_header_f, strings_len) + 8) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "File %s is too short: %m", path); + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "File %s is too short.", path); if (file_offset_beyond_memory_size(hwdb->st.st_size)) - return log_debug_errno(SYNTHETIC_ERRNO(EFBIG), "File %s is too long: %m", path); + return log_debug_errno(SYNTHETIC_ERRNO(EFBIG), "File %s is too long.", path); hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0); if (hwdb->map == MAP_FAILED) @@ -330,7 +330,7 @@ static int hwdb_new(const char *path, sd_hwdb **ret) { if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 || (size_t) hwdb->st.st_size != le64toh(hwdb->head->file_size)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to recognize the format of %s", path); + "Failed to recognize the format of %s.", path); log_debug("=== trie on-disk ==="); log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version)); diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c index b9714ee..298d21e 100644 --- a/src/libsystemd/sd-id128/id128-util.c +++ b/src/libsystemd/sd-id128/id128-util.c @@ -9,9 +9,12 @@ #include "hexdecoct.h" #include "id128-util.h" #include "io-util.h" +#include "namespace-util.h" +#include "process-util.h" #include "sha256.h" #include "stdio-util.h" #include "string-util.h" +#include "strv.h" #include "sync-util.h" #include "virt.h" @@ -192,11 +195,11 @@ int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id) { } void id128_hash_func(const sd_id128_t *p, struct siphash *state) { - siphash24_compress(p, sizeof(sd_id128_t), state); + siphash24_compress_typesafe(*p, state); } int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) { - return memcmp(a, b, 16); + return memcmp(a, b, sizeof(sd_id128_t)); } sd_id128_t id128_make_v4_uuid(sd_id128_t id) { @@ -231,11 +234,15 @@ int id128_get_product(sd_id128_t *ret) { * of the host */ return -ENOENT; - r = id128_read("/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid); - if (r == -ENOENT) - r = id128_read("/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid); - if (r == -ENOENT) - r = id128_read("/sys/hypervisor/uuid", ID128_FORMAT_UUID, &uuid); + FOREACH_STRING(i, + "/sys/class/dmi/id/product_uuid", /* KVM */ + "/proc/device-tree/vm,uuid", /* Device tree */ + "/sys/hypervisor/uuid") { /* Xen */ + + r = id128_read(i, ID128_FORMAT_UUID, &uuid); + if (r != -ENOENT) + break; + } if (r < 0) return r; @@ -263,3 +270,64 @@ sd_id128_t id128_digest(const void *data, size_t size) { return id128_make_v4_uuid(id); } + +int id128_get_boot_for_machine(const char *machine, sd_id128_t *ret) { + _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, rootfd = -EBADF; + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; + pid_t pid, child; + sd_id128_t id; + ssize_t k; + int r; + + assert(ret); + + if (isempty(machine)) + return sd_id128_get_boot(ret); + + r = container_get_leader(machine, &pid); + if (r < 0) + return r; + + r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, /* ret_userns_fd = */ NULL, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + r = namespace_fork("(sd-bootidns)", "(sd-bootid)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, + pidnsfd, mntnsfd, -1, -1, rootfd, &child); + if (r < 0) + return r; + if (r == 0) { + pair[0] = safe_close(pair[0]); + + r = id128_get_boot(&id); + if (r < 0) + _exit(EXIT_FAILURE); + + k = send(pair[1], &id, sizeof(id), MSG_NOSIGNAL); + if (k != sizeof(id)) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate_and_check("(sd-bootidns)", child, 0); + if (r < 0) + return r; + if (r != EXIT_SUCCESS) + return -EIO; + + k = recv(pair[0], &id, sizeof(id), 0); + if (k != sizeof(id)) + return -EIO; + + if (sd_id128_is_null(id)) + return -EIO; + + *ret = id; + return 0; +} diff --git a/src/libsystemd/sd-id128/id128-util.h b/src/libsystemd/sd-id128/id128-util.h index 53ba50a..458d430 100644 --- a/src/libsystemd/sd-id128/id128-util.h +++ b/src/libsystemd/sd-id128/id128-util.h @@ -49,6 +49,9 @@ int id128_get_product(sd_id128_t *ret); sd_id128_t id128_digest(const void *data, size_t size); +int id128_get_boot(sd_id128_t *ret); +int id128_get_boot_for_machine(const char *machine, sd_id128_t *ret); + /* A helper to check for the three relevant cases of "machine ID not initialized" */ #define ERRNO_IS_NEG_MACHINE_ID_UNSET(r) \ IN_SET(r, \ diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c index 9fda79a..fc1107b 100644 --- a/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libsystemd/sd-id128/sd-id128.c @@ -13,6 +13,7 @@ #include "hmac.h" #include "id128-util.h" #include "io-util.h" +#include "keyring-util.h" #include "macro.h" #include "missing_syscall.h" #include "missing_threads.h" @@ -170,14 +171,24 @@ int id128_get_machine(const char *root, sd_id128_t *ret) { return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret); } +int id128_get_boot(sd_id128_t *ret) { + int r; + + assert(ret); + + r = id128_read("/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID | ID128_REFUSE_NULL, ret); + if (r == -ENOENT && proc_mounted() == 0) + return -ENOSYS; + + return r; +} + _public_ int sd_id128_get_boot(sd_id128_t *ret) { static thread_local sd_id128_t saved_boot_id = {}; int r; if (sd_id128_is_null(saved_boot_id)) { - r = id128_read("/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID | ID128_REFUSE_NULL, &saved_boot_id); - if (r == -ENOENT && proc_mounted() == 0) - return -ENOSYS; + r = id128_get_boot(&saved_boot_id); if (r < 0) return r; } @@ -192,7 +203,6 @@ static int get_invocation_from_keyring(sd_id128_t *ret) { char *d, *p, *g, *u, *e; unsigned long perms; key_serial_t key; - size_t sz = 256; uid_t uid; gid_t gid; int r, c; @@ -211,24 +221,9 @@ static int get_invocation_from_keyring(sd_id128_t *ret) { return -errno; } - for (;;) { - description = new(char, sz); - if (!description) - return -ENOMEM; - - c = keyctl(KEYCTL_DESCRIBE, key, (unsigned long) description, sz, 0); - if (c < 0) - return -errno; - - if ((size_t) c <= sz) - break; - - sz = c; - free(description); - } - - /* The kernel returns a final NUL in the string, verify that. */ - assert(description[c-1] == 0); + r = keyring_describe(key, &description); + if (r < 0) + return r; /* Chop off the final description string */ d = strrchr(description, ';'); @@ -380,3 +375,16 @@ _public_ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret) return sd_id128_get_app_specific(id, app_id, ret); } + +_public_ int sd_id128_get_invocation_app_specific(sd_id128_t app_id, sd_id128_t *ret) { + sd_id128_t id; + int r; + + assert_return(ret, -EINVAL); + + r = sd_id128_get_invocation(&id); + if (r < 0) + return r; + + return sd_id128_get_app_specific(id, app_id, ret); +} diff --git a/src/libsystemd/sd-journal/catalog.c b/src/libsystemd/sd-journal/catalog.c index ae91534..a0b673f 100644 --- a/src/libsystemd/sd-journal/catalog.c +++ b/src/libsystemd/sd-journal/catalog.c @@ -16,6 +16,7 @@ #include "conf-files.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "hashmap.h" #include "log.h" #include "memory-util.h" @@ -54,7 +55,7 @@ typedef struct CatalogItem { } CatalogItem; static void catalog_hash_func(const CatalogItem *i, struct siphash *state) { - siphash24_compress(&i->id, sizeof(i->id), state); + siphash24_compress_typesafe(i->id, state); siphash24_compress_string(i->language, state); } @@ -222,10 +223,7 @@ static int catalog_entry_lang( const char* deflang, char **ret) { - size_t c; - char *z; - - c = strlen(t); + size_t c = strlen(t); if (c < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "[%s:%u] Language too short.", filename, line); @@ -242,12 +240,7 @@ static int catalog_entry_lang( log_warning("[%s:%u] language differs from default for file", filename, line); } - z = strdup(t); - if (!z) - return -ENOMEM; - - *ret = z; - return 0; + return strdup_to(ret, t); } int catalog_import_file(OrderedHashmap *h, const char *path) { @@ -382,10 +375,8 @@ static int64_t write_catalog( CatalogItem *items, size_t n) { + _cleanup_(unlink_and_freep) char *p = NULL; _cleanup_fclose_ FILE *w = NULL; - _cleanup_free_ char *p = NULL; - CatalogHeader header; - size_t k; int r; r = mkdir_parents(database, 0755); @@ -394,54 +385,35 @@ static int64_t write_catalog( r = fopen_temporary(database, &w, &p); if (r < 0) - return log_error_errno(r, "Failed to open database for writing: %s: %m", - database); + return log_error_errno(r, "Failed to open database for writing: %s: %m", database); - header = (CatalogHeader) { + CatalogHeader header = { .signature = CATALOG_SIGNATURE, .header_size = htole64(CONST_ALIGN_TO(sizeof(CatalogHeader), 8)), .catalog_item_size = htole64(sizeof(CatalogItem)), .n_items = htole64(n), }; - r = -EIO; + if (fwrite(&header, sizeof(header), 1, w) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "%s: failed to write header.", p); - k = fwrite(&header, 1, sizeof(header), w); - if (k != sizeof(header)) { - log_error("%s: failed to write header.", p); - goto error; - } + if (fwrite(items, sizeof(CatalogItem), n, w) != n) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "%s: failed to write database.", p); - k = fwrite(items, 1, n * sizeof(CatalogItem), w); - if (k != n * sizeof(CatalogItem)) { - log_error("%s: failed to write database.", p); - goto error; - } - - k = fwrite(sb->buf, 1, sb->len, w); - if (k != sb->len) { - log_error("%s: failed to write strings.", p); - goto error; - } + if (fwrite(sb->buf, sb->len, 1, w) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "%s: failed to write strings.", p); r = fflush_and_check(w); - if (r < 0) { - log_error_errno(r, "%s: failed to write database: %m", p); - goto error; - } + if (r < 0) + return log_error_errno(r, "%s: failed to write database: %m", p); (void) fchmod(fileno(w), 0644); - if (rename(p, database) < 0) { - r = log_error_errno(errno, "rename (%s -> %s) failed: %m", p, database); - goto error; - } + if (rename(p, database) < 0) + return log_error_errno(errno, "rename (%s -> %s) failed: %m", p, database); + p = mfree(p); /* free without unlinking */ return ftello(w); - -error: - (void) unlink(p); - return r; } int catalog_update(const char* database, const char* root, const char* const* dirs) { @@ -472,11 +444,12 @@ int catalog_update(const char* database, const char* root, const char* const* di return log_error_errno(r, "Failed to import file '%s': %m", *f); } - if (ordered_hashmap_size(h) <= 0) { + if (ordered_hashmap_isempty(h)) { log_info("No items in catalog."); return 0; - } else - log_debug("Found %u items in catalog.", ordered_hashmap_size(h)); + } + + log_debug("Found %u items in catalog.", ordered_hashmap_size(h)); items = new(CatalogItem, ordered_hashmap_size(h)); if (!items) @@ -607,15 +580,14 @@ static const char *find_id(void *p, sd_id128_t id) { le64toh(f->offset); } -int catalog_get(const char* database, sd_id128_t id, char **_text) { +int catalog_get(const char* database, sd_id128_t id, char **ret_text) { _cleanup_close_ int fd = -EBADF; void *p = NULL; - struct stat st = {}; - char *text = NULL; + struct stat st; int r; const char *s; - assert(_text); + assert(ret_text); r = open_mmap(database, &fd, &st, &p); if (r < 0) @@ -627,18 +599,9 @@ int catalog_get(const char* database, sd_id128_t id, char **_text) { goto finish; } - text = strdup(s); - if (!text) { - r = -ENOMEM; - goto finish; - } - - *_text = text; - r = 0; - + r = strdup_to(ret_text, s); finish: - if (p) - munmap(p, st.st_size); + (void) munmap(p, st.st_size); return r; } diff --git a/src/libsystemd/sd-journal/catalog.h b/src/libsystemd/sd-journal/catalog.h index df27869..b5a97fa 100644 --- a/src/libsystemd/sd-journal/catalog.h +++ b/src/libsystemd/sd-journal/catalog.h @@ -11,7 +11,7 @@ int catalog_import_file(OrderedHashmap *h, const char *path); int catalog_update(const char* database, const char* root, const char* const* dirs); -int catalog_get(const char* database, sd_id128_t id, char **data); +int catalog_get(const char* database, sd_id128_t id, char **ret_text); int catalog_list(FILE *f, const char* database, bool oneline); int catalog_list_items(FILE *f, const char* database, bool oneline, char **items); int catalog_file_lang(const char *filename, char **lang); diff --git a/src/libsystemd/sd-journal/fsprg.c b/src/libsystemd/sd-journal/fsprg.c index e86be6a..85632b0 100644 --- a/src/libsystemd/sd-journal/fsprg.c +++ b/src/libsystemd/sd-journal/fsprg.c @@ -3,21 +3,6 @@ * fsprg v0.1 - (seekable) forward-secure pseudorandom generator * Copyright © 2012 B. Poettering * Contact: fsprg@point-at-infinity.org - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301 USA */ /* @@ -50,11 +35,11 @@ static void mpi_export(void *buf, size_t buflen, const gcry_mpi_t x) { unsigned len; size_t nwritten; - assert(gcry_mpi_cmp_ui(x, 0) >= 0); - len = (gcry_mpi_get_nbits(x) + 7) / 8; + assert(sym_gcry_mpi_cmp_ui(x, 0) >= 0); + len = (sym_gcry_mpi_get_nbits(x) + 7) / 8; assert(len <= buflen); memzero(buf, buflen); - gcry_mpi_print(GCRYMPI_FMT_USG, buf + (buflen - len), len, &nwritten, x); + sym_gcry_mpi_print(GCRYMPI_FMT_USG, buf + (buflen - len), len, &nwritten, x); assert(nwritten == len); } @@ -62,10 +47,10 @@ static gcry_mpi_t mpi_import(const void *buf, size_t buflen) { gcry_mpi_t h; _unused_ unsigned len; - assert_se(gcry_mpi_scan(&h, GCRYMPI_FMT_USG, buf, buflen, NULL) == 0); - len = (gcry_mpi_get_nbits(h) + 7) / 8; + assert_se(sym_gcry_mpi_scan(&h, GCRYMPI_FMT_USG, buf, buflen, NULL) == 0); + len = (sym_gcry_mpi_get_nbits(h) + 7) / 8; assert(len <= buflen); - assert(gcry_mpi_cmp_ui(h, 0) >= 0); + assert(sym_gcry_mpi_cmp_ui(h, 0) >= 0); return h; } @@ -102,30 +87,30 @@ static void det_randomize(void *buf, size_t buflen, const void *seed, size_t see gcry_error_t err; uint32_t ctr; - olen = gcry_md_get_algo_dlen(RND_HASH); - err = gcry_md_open(&hd, RND_HASH, 0); + olen = sym_gcry_md_get_algo_dlen(RND_HASH); + err = sym_gcry_md_open(&hd, RND_HASH, 0); assert_se(gcry_err_code(err) == GPG_ERR_NO_ERROR); /* This shouldn't happen */ - gcry_md_write(hd, seed, seedlen); - gcry_md_putc(hd, (idx >> 24) & 0xff); - gcry_md_putc(hd, (idx >> 16) & 0xff); - gcry_md_putc(hd, (idx >> 8) & 0xff); - gcry_md_putc(hd, (idx >> 0) & 0xff); + sym_gcry_md_write(hd, seed, seedlen); + sym_gcry_md_putc(hd, (idx >> 24) & 0xff); + sym_gcry_md_putc(hd, (idx >> 16) & 0xff); + sym_gcry_md_putc(hd, (idx >> 8) & 0xff); + sym_gcry_md_putc(hd, (idx >> 0) & 0xff); for (ctr = 0; buflen; ctr++) { - err = gcry_md_copy(&hd2, hd); + err = sym_gcry_md_copy(&hd2, hd); assert_se(gcry_err_code(err) == GPG_ERR_NO_ERROR); /* This shouldn't happen */ - gcry_md_putc(hd2, (ctr >> 24) & 0xff); - gcry_md_putc(hd2, (ctr >> 16) & 0xff); - gcry_md_putc(hd2, (ctr >> 8) & 0xff); - gcry_md_putc(hd2, (ctr >> 0) & 0xff); - gcry_md_final(hd2); + sym_gcry_md_putc(hd2, (ctr >> 24) & 0xff); + sym_gcry_md_putc(hd2, (ctr >> 16) & 0xff); + sym_gcry_md_putc(hd2, (ctr >> 8) & 0xff); + sym_gcry_md_putc(hd2, (ctr >> 0) & 0xff); + sym_gcry_md_ctl(hd2, GCRYCTL_FINALIZE, NULL, 0); cpylen = (buflen < olen) ? buflen : olen; - memcpy(buf, gcry_md_read(hd2, RND_HASH), cpylen); - gcry_md_close(hd2); + memcpy(buf, sym_gcry_md_read(hd2, RND_HASH), cpylen); + sym_gcry_md_close(hd2); buf += cpylen; buflen -= cpylen; } - gcry_md_close(hd); + sym_gcry_md_close(hd); } /* deterministically generate from seed/idx a prime of length `bits' that is 3 (mod 4) */ @@ -142,8 +127,8 @@ static gcry_mpi_t genprime3mod4(int bits, const void *seed, size_t seedlen, uint buf[buflen - 1] |= 0x03; /* set lower two bits, to have result 3 (mod 4) */ p = mpi_import(buf, buflen); - while (gcry_prime_check(p, 0)) - gcry_mpi_add_ui(p, p, 4); + while (sym_gcry_prime_check(p, 0)) + sym_gcry_mpi_add_ui(p, p, 4); return p; } @@ -157,8 +142,8 @@ static gcry_mpi_t gensquare(const gcry_mpi_t n, const void *seed, size_t seedlen det_randomize(buf, buflen, seed, seedlen, idx); buf[0] &= 0x7f; /* clear upper bit, so that we have x < n */ x = mpi_import(buf, buflen); - assert(gcry_mpi_cmp(x, n) < 0); - gcry_mpi_mulm(x, x, x, n); + assert(sym_gcry_mpi_cmp(x, n) < 0); + sym_gcry_mpi_mulm(x, x, x, n); return x; } @@ -167,51 +152,51 @@ static gcry_mpi_t twopowmodphi(uint64_t m, const gcry_mpi_t p) { gcry_mpi_t phi, r; int n; - phi = gcry_mpi_new(0); - gcry_mpi_sub_ui(phi, p, 1); + phi = sym_gcry_mpi_new(0); + sym_gcry_mpi_sub_ui(phi, p, 1); /* count number of used bits in m */ for (n = 0; (1ULL << n) <= m; n++) ; - r = gcry_mpi_new(0); - gcry_mpi_set_ui(r, 1); + r = sym_gcry_mpi_new(0); + sym_gcry_mpi_set_ui(r, 1); while (n) { /* square and multiply algorithm for fast exponentiation */ n--; - gcry_mpi_mulm(r, r, r, phi); + sym_gcry_mpi_mulm(r, r, r, phi); if (m & ((uint64_t)1 << n)) { - gcry_mpi_add(r, r, r); - if (gcry_mpi_cmp(r, phi) >= 0) - gcry_mpi_sub(r, r, phi); + sym_gcry_mpi_add(r, r, r); + if (sym_gcry_mpi_cmp(r, phi) >= 0) + sym_gcry_mpi_sub(r, r, phi); } } - gcry_mpi_release(phi); + sym_gcry_mpi_release(phi); return r; } /* Decompose $x \in Z_n$ into $(xp,xq) \in Z_p \times Z_q$ using Chinese Remainder Theorem */ static void CRT_decompose(gcry_mpi_t *xp, gcry_mpi_t *xq, const gcry_mpi_t x, const gcry_mpi_t p, const gcry_mpi_t q) { - *xp = gcry_mpi_new(0); - *xq = gcry_mpi_new(0); - gcry_mpi_mod(*xp, x, p); - gcry_mpi_mod(*xq, x, q); + *xp = sym_gcry_mpi_new(0); + *xq = sym_gcry_mpi_new(0); + sym_gcry_mpi_mod(*xp, x, p); + sym_gcry_mpi_mod(*xq, x, q); } /* Compose $(xp,xq) \in Z_p \times Z_q$ into $x \in Z_n$ using Chinese Remainder Theorem */ static void CRT_compose(gcry_mpi_t *x, const gcry_mpi_t xp, const gcry_mpi_t xq, const gcry_mpi_t p, const gcry_mpi_t q) { gcry_mpi_t a, u; - a = gcry_mpi_new(0); - u = gcry_mpi_new(0); - *x = gcry_mpi_new(0); - gcry_mpi_subm(a, xq, xp, q); - gcry_mpi_invm(u, p, q); - gcry_mpi_mulm(a, a, u, q); /* a = (xq - xp) / p (mod q) */ - gcry_mpi_mul(*x, p, a); - gcry_mpi_add(*x, *x, xp); /* x = p * ((xq - xp) / p mod q) + xp */ - gcry_mpi_release(a); - gcry_mpi_release(u); + a = sym_gcry_mpi_new(0); + u = sym_gcry_mpi_new(0); + *x = sym_gcry_mpi_new(0); + sym_gcry_mpi_subm(a, xq, xp, q); + sym_gcry_mpi_invm(u, p, q); + sym_gcry_mpi_mulm(a, a, u, q); /* a = (xq - xp) / p (mod q) */ + sym_gcry_mpi_mul(*x, p, a); + sym_gcry_mpi_add(*x, *x, xp); /* x = p * ((xq - xp) / p mod q) + xp */ + sym_gcry_mpi_release(a); + sym_gcry_mpi_release(u); } /******************************************************************************/ @@ -245,18 +230,21 @@ static uint16_t read_secpar(const void *buf) { return 16 * (secpar + 1); } -void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned _secpar) { +int FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned _secpar) { uint8_t iseed[FSPRG_RECOMMENDED_SEEDLEN]; gcry_mpi_t n, p, q; uint16_t secpar; + int r; VALIDATE_SECPAR(_secpar); secpar = _secpar; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; if (!seed) { - gcry_randomize(iseed, FSPRG_RECOMMENDED_SEEDLEN, GCRY_STRONG_RANDOM); + sym_gcry_randomize(iseed, FSPRG_RECOMMENDED_SEEDLEN, GCRY_STRONG_RANDOM); seed = iseed; seedlen = FSPRG_RECOMMENDED_SEEDLEN; } @@ -271,25 +259,30 @@ void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigne } if (mpk) { - n = gcry_mpi_new(0); - gcry_mpi_mul(n, p, q); - assert(gcry_mpi_get_nbits(n) == secpar); + n = sym_gcry_mpi_new(0); + sym_gcry_mpi_mul(n, p, q); + assert(sym_gcry_mpi_get_nbits(n) == secpar); store_secpar(mpk + 0, secpar); mpi_export(mpk + 2, secpar / 8, n); - gcry_mpi_release(n); + sym_gcry_mpi_release(n); } - gcry_mpi_release(p); - gcry_mpi_release(q); + sym_gcry_mpi_release(p); + sym_gcry_mpi_release(q); + + return 0; } -void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen) { +int FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen) { gcry_mpi_t n, x; uint16_t secpar; + int r; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; secpar = read_secpar(mpk + 0); n = mpi_import(mpk + 2, secpar / 8); @@ -299,30 +292,37 @@ void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seed mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x); memzero(state + 2 + 2 * secpar / 8, 8); - gcry_mpi_release(n); - gcry_mpi_release(x); + sym_gcry_mpi_release(n); + sym_gcry_mpi_release(x); + + return 0; } -void FSPRG_Evolve(void *state) { +int FSPRG_Evolve(void *state) { gcry_mpi_t n, x; uint16_t secpar; uint64_t epoch; + int r; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; secpar = read_secpar(state + 0); n = mpi_import(state + 2 + 0 * secpar / 8, secpar / 8); x = mpi_import(state + 2 + 1 * secpar / 8, secpar / 8); epoch = uint64_import(state + 2 + 2 * secpar / 8, 8); - gcry_mpi_mulm(x, x, x, n); + sym_gcry_mpi_mulm(x, x, x, n); epoch++; mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x); uint64_export(state + 2 + 2 * secpar / 8, 8, epoch); - gcry_mpi_release(n); - gcry_mpi_release(x); + sym_gcry_mpi_release(n); + sym_gcry_mpi_release(x); + + return 0; } uint64_t FSPRG_GetEpoch(const void *state) { @@ -331,18 +331,21 @@ uint64_t FSPRG_GetEpoch(const void *state) { return uint64_import(state + 2 + 2 * secpar / 8, 8); } -void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen) { +int FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen) { gcry_mpi_t p, q, n, x, xp, xq, kp, kq, xm; uint16_t secpar; + int r; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; secpar = read_secpar(msk + 0); p = mpi_import(msk + 2 + 0 * (secpar / 2) / 8, (secpar / 2) / 8); q = mpi_import(msk + 2 + 1 * (secpar / 2) / 8, (secpar / 2) / 8); - n = gcry_mpi_new(0); - gcry_mpi_mul(n, p, q); + n = sym_gcry_mpi_new(0); + sym_gcry_mpi_mul(n, p, q); x = gensquare(n, seed, seedlen, RND_GEN_X, secpar); CRT_decompose(&xp, &xq, x, p, q); /* split (mod n) into (mod p) and (mod q) using CRT */ @@ -350,8 +353,8 @@ void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, kp = twopowmodphi(epoch, p); /* compute 2^epoch (mod phi(p)) */ kq = twopowmodphi(epoch, q); /* compute 2^epoch (mod phi(q)) */ - gcry_mpi_powm(xp, xp, kp, p); /* compute x^(2^epoch) (mod p) */ - gcry_mpi_powm(xq, xq, kq, q); /* compute x^(2^epoch) (mod q) */ + sym_gcry_mpi_powm(xp, xp, kp, p); /* compute x^(2^epoch) (mod p) */ + sym_gcry_mpi_powm(xq, xq, kq, q); /* compute x^(2^epoch) (mod q) */ CRT_compose(&xm, xp, xq, p, q); /* combine (mod p) and (mod q) to (mod n) using CRT */ @@ -360,22 +363,29 @@ void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, xm); uint64_export(state + 2 + 2 * secpar / 8, 8, epoch); - gcry_mpi_release(p); - gcry_mpi_release(q); - gcry_mpi_release(n); - gcry_mpi_release(x); - gcry_mpi_release(xp); - gcry_mpi_release(xq); - gcry_mpi_release(kp); - gcry_mpi_release(kq); - gcry_mpi_release(xm); + sym_gcry_mpi_release(p); + sym_gcry_mpi_release(q); + sym_gcry_mpi_release(n); + sym_gcry_mpi_release(x); + sym_gcry_mpi_release(xp); + sym_gcry_mpi_release(xq); + sym_gcry_mpi_release(kp); + sym_gcry_mpi_release(kq); + sym_gcry_mpi_release(xm); + + return 0; } -void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx) { +int FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx) { uint16_t secpar; + int r; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; secpar = read_secpar(state + 0); det_randomize(key, keylen, state + 2, 2 * secpar / 8 + 8, idx); + + return 0; } diff --git a/src/libsystemd/sd-journal/fsprg.h b/src/libsystemd/sd-journal/fsprg.h index d3d88aa..a0594cd 100644 --- a/src/libsystemd/sd-journal/fsprg.h +++ b/src/libsystemd/sd-journal/fsprg.h @@ -5,21 +5,6 @@ * fsprg v0.1 - (seekable) forward-secure pseudorandom generator * Copyright © 2012 B. Poettering * Contact: fsprg@point-at-infinity.org - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301 USA */ #include <inttypes.h> @@ -39,22 +24,22 @@ size_t FSPRG_mpkinbytes(unsigned secpar) _const_; size_t FSPRG_stateinbytes(unsigned secpar) _const_; /* Setup msk and mpk. Providing seed != NULL makes this algorithm deterministic. */ -void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned secpar); +int FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned secpar); /* Initialize state deterministically in dependence on seed. */ /* Note: in case one wants to run only one GenState0 per GenMK it is safe to use the same seed for both GenMK and GenState0. */ -void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen); +int FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen); -void FSPRG_Evolve(void *state); +int FSPRG_Evolve(void *state); uint64_t FSPRG_GetEpoch(const void *state) _pure_; /* Seek to any arbitrary state (by providing msk together with seed from GenState0). */ -void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen); +int FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen); -void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx); +int FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx); #ifdef __cplusplus } diff --git a/src/libsystemd/sd-journal/journal-authenticate.c b/src/libsystemd/sd-journal/journal-authenticate.c index 8e7533e..64f5739 100644 --- a/src/libsystemd/sd-journal/journal-authenticate.c +++ b/src/libsystemd/sd-journal/journal-authenticate.c @@ -71,7 +71,7 @@ int journal_file_append_tag(JournalFile *f) { return r; /* Get the HMAC tag and store it in the object */ - memcpy(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH); + memcpy(o->tag.tag, sym_gcry_md_read(f->hmac, 0), TAG_LENGTH); f->hmac_running = false; return 0; @@ -80,6 +80,7 @@ int journal_file_append_tag(JournalFile *f) { int journal_file_hmac_start(JournalFile *f) { uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */ gcry_error_t err; + int r; assert(f); @@ -90,13 +91,17 @@ int journal_file_hmac_start(JournalFile *f) { return 0; /* Prepare HMAC for next cycle */ - gcry_md_reset(f->hmac); - FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0); - err = gcry_md_setkey(f->hmac, key, sizeof(key)); + sym_gcry_md_reset(f->hmac); + + r = FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0); + if (r < 0) + return r; + + err = sym_gcry_md_setkey(f->hmac, key, sizeof(key)); if (gcry_err_code(err) != GPG_ERR_NO_ERROR) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "gcry_md_setkey() failed with error code: %s", - gcry_strerror(err)); + "sym_gcry_md_setkey() failed with error code: %s", + sym_gcry_strerror(err)); f->hmac_running = true; @@ -167,7 +172,10 @@ int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) { if (epoch == goal) return 0; - FSPRG_Evolve(f->fsprg_state); + r = FSPRG_Evolve(f->fsprg_state); + if (r < 0) + return r; + epoch = FSPRG_GetEpoch(f->fsprg_state); if (epoch < goal) { r = journal_file_append_tag(f); @@ -180,6 +188,7 @@ int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) { int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { void *msk; uint64_t epoch; + int r; assert(f); @@ -195,10 +204,8 @@ int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { if (goal == epoch) return 0; - if (goal == epoch + 1) { - FSPRG_Evolve(f->fsprg_state); - return 0; - } + if (goal == epoch + 1) + return FSPRG_Evolve(f->fsprg_state); } else { f->fsprg_state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR); f->fsprg_state = malloc(f->fsprg_state_size); @@ -209,10 +216,12 @@ int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { log_debug("Seeking FSPRG key to %"PRIu64".", goal); msk = alloca_safe(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR)); - FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR); - FSPRG_Seek(f->fsprg_state, goal, msk, f->fsprg_seed, f->fsprg_seed_size); - return 0; + r = FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR); + if (r < 0) + return r; + + return FSPRG_Seek(f->fsprg_state, goal, msk, f->fsprg_seed, f->fsprg_seed_size); } int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) { @@ -260,25 +269,25 @@ int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uin } else if (type > OBJECT_UNUSED && o->object.type != type) return -EBADMSG; - gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload)); + sym_gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload)); switch (o->object.type) { case OBJECT_DATA: /* All but hash and payload are mutable */ - gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash)); - gcry_md_write(f->hmac, journal_file_data_payload_field(f, o), le64toh(o->object.size) - journal_file_data_payload_offset(f)); + sym_gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash)); + sym_gcry_md_write(f->hmac, journal_file_data_payload_field(f, o), le64toh(o->object.size) - journal_file_data_payload_offset(f)); break; case OBJECT_FIELD: /* Same here */ - gcry_md_write(f->hmac, &o->field.hash, sizeof(o->field.hash)); - gcry_md_write(f->hmac, o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload)); + sym_gcry_md_write(f->hmac, &o->field.hash, sizeof(o->field.hash)); + sym_gcry_md_write(f->hmac, o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload)); break; case OBJECT_ENTRY: /* All */ - gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(Object, entry.seqnum)); + sym_gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(Object, entry.seqnum)); break; case OBJECT_FIELD_HASH_TABLE: @@ -289,8 +298,8 @@ int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uin case OBJECT_TAG: /* All but the tag itself */ - gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum)); - gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch)); + sym_gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum)); + sym_gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch)); break; default: return -EINVAL; @@ -318,10 +327,10 @@ int journal_file_hmac_put_header(JournalFile *f) { * tail_entry_monotonic, n_data, n_fields, n_tags, * n_entry_arrays. */ - gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature)); - gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, tail_entry_boot_id) - offsetof(Header, file_id)); - gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id)); - gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset)); + sym_gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature)); + sym_gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, tail_entry_boot_id) - offsetof(Header, file_id)); + sym_gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id)); + sym_gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset)); return 0; } @@ -406,13 +415,16 @@ int journal_file_fss_load(JournalFile *f) { int journal_file_hmac_setup(JournalFile *f) { gcry_error_t e; + int r; if (!JOURNAL_HEADER_SEALED(f->header)) return 0; - initialize_libgcrypt(true); + r = initialize_libgcrypt(true); + if (r < 0) + return r; - e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); + e = sym_gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); if (e != 0) return -EOPNOTSUPP; diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 08cbf86..7e941ed 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -20,6 +20,7 @@ #include "fd-util.h" #include "format-util.h" #include "fs-util.h" +#include "gcrypt-util.h" #include "id128-util.h" #include "journal-authenticate.h" #include "journal-def.h" @@ -47,10 +48,6 @@ #define DEFAULT_COMPRESS_THRESHOLD (512ULL) #define MIN_COMPRESS_THRESHOLD (8ULL) -#define U64_KB UINT64_C(1024) -#define U64_MB (UINT64_C(1024) * U64_KB) -#define U64_GB (UINT64_C(1024) * U64_MB) - /* This is the minimum journal file size */ #define JOURNAL_FILE_SIZE_MIN (512 * U64_KB) /* 512 KiB */ #define JOURNAL_COMPACT_SIZE_MAX ((uint64_t) UINT32_MAX) /* 4 GiB */ @@ -285,6 +282,8 @@ JournalFile* journal_file_close(JournalFile *f) { assert(f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL); + sd_event_source_disable_unref(f->post_change_timer); + if (f->cache_fd) mmap_cache_fd_free(f->cache_fd); @@ -309,7 +308,7 @@ JournalFile* journal_file_close(JournalFile *f) { free(f->fsprg_seed); if (f->hmac) - gcry_md_close(f->hmac); + sym_gcry_md_close(f->hmac); #endif return mfree(f); @@ -2249,7 +2248,6 @@ static int journal_file_link_entry( f->header->tail_entry_monotonic = o->entry.monotonic; if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset)) f->header->tail_entry_offset = htole64(offset); - f->newest_mtime = 0; /* we have a new tail entry now, explicitly invalidate newest boot id/timestamp info */ /* Link up the items */ for (uint64_t i = 0; i < n_items; i++) { @@ -2762,7 +2760,7 @@ static int generic_array_get( Object **ret_object, /* The found object. */ uint64_t *ret_offset) { /* The offset of the found object. */ - uint64_t a, t = 0, k; + uint64_t a, t = 0, k = 0; /* Explicit initialization of k to appease gcc */ ChainCacheItem *ci; Object *o = NULL; int r; @@ -3052,7 +3050,7 @@ static int generic_array_bisect( left = 0; right = m - 1; - if (direction == DIRECTION_UP) { + if (direction == DIRECTION_UP && left < right) { /* If we're going upwards, the last entry of the previous array may pass the test, * and the first entry of the current array may not pass. In that case, the last * entry of the previous array must be returned. Hence, we need to test the first @@ -3167,10 +3165,21 @@ previous: if (direction == DIRECTION_DOWN) return 0; - /* Indicate to go to the previous array later. Note, do not move to the previous array here, - * as that may invalidate the current array object in the mmap cache and - * journal_file_entry_array_item() below may read invalid address. */ - i = UINT64_MAX; + /* Get the last entry of the previous array. */ + r = bump_entry_array(f, NULL, a, first, DIRECTION_UP, &a); + if (r <= 0) + return r; + + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array); + if (r < 0) + return r; + + p = journal_file_entry_array_n_items(f, array); + if (p == 0 || t < p) + return -EBADMSG; + + t -= p; + i = p - 1; found: p = journal_file_entry_array_item(f, array, 0); @@ -3180,27 +3189,6 @@ found: /* Let's cache this item for the next invocation */ chain_cache_put(f->chain_cache, ci, first, a, p, t, i); - if (i == UINT64_MAX) { - uint64_t m; - - /* Get the last entry of the previous array. */ - - r = bump_entry_array(f, NULL, a, first, DIRECTION_UP, &a); - if (r <= 0) - return r; - - r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array); - if (r < 0) - return r; - - m = journal_file_entry_array_n_items(f, array); - if (m == 0 || t < m) - return -EBADMSG; - - t -= m; - i = m - 1; - } - p = journal_file_entry_array_item(f, array, i); if (p == 0) return -EBADMSG; @@ -3267,7 +3255,7 @@ static int generic_array_bisect_for_data( } else { /* If we are going upwards, then we need to return the last object that passes the test. - * When there is no object that passes the test, we need to return the the last object that + * When there is no object that passes the test, we need to return the last object that * test_object() returns TEST_LEFT for. */ if (r == TEST_RIGHT) return 0; /* Not only the 'extra' object, but also all objects in the chained arrays @@ -3554,7 +3542,8 @@ int journal_file_next_entry( p, test_object_offset, direction, - ret_object ? &o : NULL, &q, &i); + NULL, &q, &i); /* Here, do not read entry object, as the result object + * may not be the one we want, and it may be broken. */ if (r <= 0) return r; @@ -3564,12 +3553,11 @@ int journal_file_next_entry( * the same offset, and the index needs to be shifted. Otherwise, use the found object as is, * as it is the nearest entry object from the input offset 'p'. */ - if (p != q) - goto found; - - r = bump_array_index(&i, direction, n); - if (r <= 0) - return r; + if (p == q) { + r = bump_array_index(&i, direction, n); + if (r <= 0) + return r; + } /* And jump to it */ r = generic_array_get(f, le64toh(f->header->entry_array_offset), i, direction, ret_object ? &o : NULL, &q); @@ -3581,7 +3569,7 @@ int journal_file_next_entry( return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: entry array not properly ordered at entry index %" PRIu64, f->path, i); -found: + if (ret_object) *ret_object = o; if (ret_offset) @@ -3813,35 +3801,35 @@ void journal_file_dump(JournalFile *f) { case OBJECT_ENTRY: assert(s); - printf("Type: %s seqnum=%"PRIu64" monotonic=%"PRIu64" realtime=%"PRIu64"\n", - s, - le64toh(o->entry.seqnum), - le64toh(o->entry.monotonic), - le64toh(o->entry.realtime)); + log_info("Type: %s seqnum=%"PRIu64" monotonic=%"PRIu64" realtime=%"PRIu64"\n", + s, + le64toh(o->entry.seqnum), + le64toh(o->entry.monotonic), + le64toh(o->entry.realtime)); break; case OBJECT_TAG: assert(s); - printf("Type: %s seqnum=%"PRIu64" epoch=%"PRIu64"\n", - s, - le64toh(o->tag.seqnum), - le64toh(o->tag.epoch)); + log_info("Type: %s seqnum=%"PRIu64" epoch=%"PRIu64"\n", + s, + le64toh(o->tag.seqnum), + le64toh(o->tag.epoch)); break; default: if (s) - printf("Type: %s \n", s); + log_info("Type: %s \n", s); else - printf("Type: unknown (%i)", o->object.type); + log_info("Type: unknown (%i)", o->object.type); break; } c = COMPRESSION_FROM_OBJECT(o); if (c > COMPRESSION_NONE) - printf("Flags: %s\n", - compression_to_string(c)); + log_info("Flags: %s\n", + compression_to_string(c)); if (p == le64toh(f->header->tail_object_offset)) p = 0; @@ -4374,11 +4362,9 @@ int journal_file_archive(JournalFile *f, char **ret_previous_path) { (void) fsync_directory_of_file(f->fd); if (ret_previous_path) - *ret_previous_path = f->path; - else - free(f->path); + *ret_previous_path = TAKE_PTR(f->path); - f->path = TAKE_PTR(p); + free_and_replace(f->path, p); /* Set as archive so offlining commits w/state=STATE_ARCHIVED. Previously we would set old_file->header->state * to STATE_ARCHIVED directly here, but journal_file_set_offline() short-circuits when state != STATE_ONLINE, diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index 81fafb9..8100388 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -129,7 +129,8 @@ typedef struct JournalFile { uint64_t newest_monotonic_usec; uint64_t newest_realtime_usec; unsigned newest_boot_id_prioq_idx; - usec_t newest_mtime; + uint64_t newest_entry_offset; + uint8_t newest_state; } JournalFile; typedef enum JournalFileFlags { @@ -309,7 +310,6 @@ void journal_file_print_header(JournalFile *f); int journal_file_archive(JournalFile *f, char **ret_previous_path); int journal_file_parse_uid_from_filename(const char *path, uid_t *uid); -JournalFile* journal_initiate_close(JournalFile *f, Set *deferred_closes); int journal_file_dispose(int dir_fd, const char *fname); diff --git a/src/libsystemd/sd-journal/journal-internal.h b/src/libsystemd/sd-journal/journal-internal.h index 259aac8..b95080c 100644 --- a/src/libsystemd/sd-journal/journal-internal.h +++ b/src/libsystemd/sd-journal/journal-internal.h @@ -12,7 +12,7 @@ #include "journal-def.h" #include "journal-file.h" #include "list.h" -#include "set.h" +#include "prioq.h" #define JOURNAL_FILES_MAX 7168u @@ -62,12 +62,18 @@ struct Location { }; struct Directory { + sd_journal *journal; char *path; int wd; bool is_root; unsigned last_seen_generation; }; +typedef struct NewestByBootId { + sd_id128_t boot_id; + Prioq *prioq; /* JournalFile objects ordered by monotonic timestamp of last update. */ +} NewestByBootId; + struct sd_journal { int toplevel_fd; @@ -78,7 +84,10 @@ struct sd_journal { OrderedHashmap *files; IteratedCache *files_cache; MMapCache *mmap; - Hashmap *newest_by_boot_id; /* key: boot_id, value: prioq, ordered by monotonic timestamp of last update */ + + /* a bisectable array of NewestByBootId, ordered by boot id. */ + NewestByBootId *newest_by_boot_id; + size_t n_newest_by_boot_id; Location current_location; @@ -86,6 +95,7 @@ struct sd_journal { uint64_t current_field; Match *level0, *level1, *level2; + Set *exclude_syslog_identifiers; uint64_t origin_id; @@ -128,6 +138,10 @@ struct sd_journal { char *journal_make_match_string(sd_journal *j); void journal_print_header(sd_journal *j); +int journal_get_directories(sd_journal *j, char ***ret); + +int journal_add_match_pair(sd_journal *j, const char *field, const char *value); +int journal_add_matchf(sd_journal *j, const char *format, ...) _printf_(2, 3); #define JOURNAL_FOREACH_DATA_RETVAL(j, data, l, retval) \ for (sd_journal_restart_data(j); ((retval) = sd_journal_enumerate_data((j), &(data), &(l))) > 0; ) diff --git a/src/libsystemd/sd-journal/journal-send.c b/src/libsystemd/sd-journal/journal-send.c index be23b2f..7d02b57 100644 --- a/src/libsystemd/sd-journal/journal-send.c +++ b/src/libsystemd/sd-journal/journal-send.c @@ -121,7 +121,7 @@ _public_ int sd_journal_printv(int priority, const char *format, va_list ap) { assert_return(priority <= 7, -EINVAL); assert_return(format, -EINVAL); - xsprintf(p, "PRIORITY=%i", priority & LOG_PRIMASK); + xsprintf(p, "PRIORITY=%i", LOG_PRI(priority)); va_copy(aq, ap); len = vsnprintf(buffer + 8, LINE_MAX, format, aq); @@ -398,20 +398,28 @@ _public_ int sd_journal_perror(const char *message) { return fill_iovec_perror_and_send(message, 0, iovec); } -_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) { +_public_ int sd_journal_stream_fd_with_namespace( + const char *name_space, + const char *identifier, + int priority, + int level_prefix) { + _cleanup_close_ int fd = -EBADF; - char *header; - size_t l; + const char *path; int r; assert_return(priority >= 0, -EINVAL); assert_return(priority <= 7, -EINVAL); + path = journal_stream_path(name_space); + if (!path) + return -EINVAL; + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); if (fd < 0) return -errno; - r = connect_unix_path(fd, AT_FDCWD, "/run/systemd/journal/stdout"); + r = connect_unix_path(fd, AT_FDCWD, path); if (r < 0) return r; @@ -422,6 +430,9 @@ _public_ int sd_journal_stream_fd(const char *identifier, int priority, int leve identifier = strempty(identifier); + char *header; + size_t l; + l = strlen(identifier); header = newa(char, l + 1 + 1 + 2 + 2 + 2 + 2 + 2); @@ -446,6 +457,10 @@ _public_ int sd_journal_stream_fd(const char *identifier, int priority, int leve return TAKE_FD(fd); } +_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) { + return sd_journal_stream_fd_with_namespace(NULL, identifier, priority, level_prefix); +} + _public_ int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) { int r; va_list ap; @@ -470,7 +485,7 @@ _public_ int sd_journal_printv_with_location(int priority, const char *file, con assert_return(priority <= 7, -EINVAL); assert_return(format, -EINVAL); - xsprintf(p, "PRIORITY=%i", priority & LOG_PRIMASK); + xsprintf(p, "PRIORITY=%i", LOG_PRI(priority)); va_copy(aq, ap); len = vsnprintf(buffer + 8, LINE_MAX, format, aq); diff --git a/src/libsystemd/sd-journal/journal-send.h b/src/libsystemd/sd-journal/journal-send.h index 24315e2..6fe6325 100644 --- a/src/libsystemd/sd-journal/journal-send.h +++ b/src/libsystemd/sd-journal/journal-send.h @@ -2,6 +2,23 @@ #pragma once #include <stdbool.h> +#include <stddef.h> + +#include "syslog-util.h" int journal_fd_nonblock(bool nonblock); void close_journal_fd(void); + +/* We declare sd_journal_stream_fd() as async-signal-safe. So instead of strjoin(), which calls malloc() + * internally, use a macro + alloca(). */ +#define journal_stream_path(log_namespace) \ + ({ \ + const char *_ns = (log_namespace), *_ret; \ + if (!_ns) \ + _ret = "/run/systemd/journal/stdout"; \ + else if (log_namespace_name_valid(_ns)) \ + _ret = strjoina("/run/systemd/journal.", _ns, "/stdout"); \ + else \ + _ret = NULL; \ + _ret; \ + }) diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index b5ce55a..e852591 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -10,6 +10,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "gcrypt-util.h" #include "journal-authenticate.h" #include "journal-def.h" #include "journal-file.h" @@ -1224,7 +1225,7 @@ int journal_file_verify( if (r < 0) goto fail; - if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) { + if (memcmp(o->tag.tag, sym_gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) { error(p, "Tag failed verification"); r = -EBADMSG; goto fail; diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index ca1ef0c..0aa3726 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -38,12 +38,13 @@ #include "prioq.h" #include "process-util.h" #include "replace-var.h" +#include "sort-util.h" #include "stat-util.h" #include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "syslog-util.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #define JOURNAL_FILES_RECHECK_USEC (2 * USEC_PER_SEC) @@ -232,7 +233,11 @@ _public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) assert_return(!journal_origin_changed(j), -ECHILD); assert_return(data, -EINVAL); - if (size == 0) + /* If the size is unspecified, assume it's a string. Note: 0 is the public value we document for + * this, for historical reasons. Internally, we pretty widely started using SIZE_MAX for this in + * similar cases however, hence accept that too. And internally we actually prefer it, to make things + * less surprising. */ + if (IN_SET(size, 0, SIZE_MAX)) size = strlen(data); if (!match_is_valid(data, size)) @@ -324,6 +329,37 @@ fail: return -ENOMEM; } +int journal_add_match_pair(sd_journal *j, const char *field, const char *value) { + _cleanup_free_ char *s = NULL; + + assert(j); + assert(field); + assert(value); + + s = strjoin(field, "=", value); + if (!s) + return -ENOMEM; + + return sd_journal_add_match(j, s, SIZE_MAX); +} + +int journal_add_matchf(sd_journal *j, const char *format, ...) { + _cleanup_free_ char *s = NULL; + va_list ap; + int r; + + assert(j); + assert(format); + + va_start(ap, format); + r = vasprintf(&s, format, ap); + va_end(ap); + if (r < 0) + return -ENOMEM; + + return sd_journal_add_match(j, s, SIZE_MAX); +} + _public_ int sd_journal_add_conjunction(sd_journal *j) { assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); @@ -413,6 +449,99 @@ _public_ void sd_journal_flush_matches(sd_journal *j) { detach_location(j); } +static int newest_by_boot_id_compare(const NewestByBootId *a, const NewestByBootId *b) { + return id128_compare_func(&a->boot_id, &b->boot_id); +} + +static void journal_file_unlink_newest_by_boot_id(sd_journal *j, JournalFile *f) { + NewestByBootId *found; + + assert(j); + assert(f); + + if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) /* not linked currently, hence this is a NOP */ + return; + + found = typesafe_bsearch(&(NewestByBootId) { .boot_id = f->newest_boot_id }, + j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare); + assert(found); + + assert_se(prioq_remove(found->prioq, f, &f->newest_boot_id_prioq_idx) > 0); + f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL; + + /* The prioq may be empty, but that should not cause any issue. Let's keep it. */ +} + +static void journal_clear_newest_by_boot_id(sd_journal *j) { + FOREACH_ARRAY(i, j->newest_by_boot_id, j->n_newest_by_boot_id) { + JournalFile *f; + + while ((f = prioq_peek(i->prioq))) + journal_file_unlink_newest_by_boot_id(j, f); + + prioq_free(i->prioq); + } + + j->newest_by_boot_id = mfree(j->newest_by_boot_id); + j->n_newest_by_boot_id = 0; +} + +static int journal_file_newest_monotonic_compare(const void *a, const void *b) { + const JournalFile *x = a, *y = b; + + return -CMP(x->newest_monotonic_usec, y->newest_monotonic_usec); /* Invert order, we want newest first! */ +} + +static int journal_file_reshuffle_newest_by_boot_id(sd_journal *j, JournalFile *f) { + NewestByBootId *found; + int r; + + assert(j); + assert(f); + + found = typesafe_bsearch(&(NewestByBootId) { .boot_id = f->newest_boot_id }, + j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare); + if (found) { + /* There's already a priority queue for this boot ID */ + + if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) { + r = prioq_put(found->prioq, f, &f->newest_boot_id_prioq_idx); /* Insert if we aren't in there yet */ + if (r < 0) + return r; + } else + prioq_reshuffle(found->prioq, f, &f->newest_boot_id_prioq_idx); /* Reshuffle otherwise */ + + } else { + _cleanup_(prioq_freep) Prioq *q = NULL; + + /* No priority queue yet, then allocate one */ + + assert(f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL); /* we can't be a member either */ + + q = prioq_new(journal_file_newest_monotonic_compare); + if (!q) + return -ENOMEM; + + r = prioq_put(q, f, &f->newest_boot_id_prioq_idx); + if (r < 0) + return r; + + if (!GREEDY_REALLOC(j->newest_by_boot_id, j->n_newest_by_boot_id + 1)) { + f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL; + return -ENOMEM; + } + + j->newest_by_boot_id[j->n_newest_by_boot_id++] = (NewestByBootId) { + .boot_id = f->newest_boot_id, + .prioq = TAKE_PTR(q), + }; + + typesafe_qsort(j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare); + } + + return 0; +} + static int journal_file_find_newest_for_boot_id( sd_journal *j, sd_id128_t id, @@ -427,16 +556,17 @@ static int journal_file_find_newest_for_boot_id( /* Before we use it, let's refresh the timestamp from the header, and reshuffle our prioq * accordingly. We do this only a bunch of times, to not be caught in some update loop. */ for (unsigned n_tries = 0;; n_tries++) { + NewestByBootId *found; JournalFile *f; - Prioq *q; - q = hashmap_get(j->newest_by_boot_id, &id); - if (!q) + found = typesafe_bsearch(&(NewestByBootId) { .boot_id = id }, + j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare); + + f = found ? prioq_peek(found->prioq) : NULL; + if (!f) return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "Requested delta for boot ID %s, but we have no information about that boot ID.", SD_ID128_TO_STRING(id)); - assert_se(f = prioq_peek(q)); /* we delete hashmap entries once the prioq is empty, so this must hold */ - if (f == prev || n_tries >= 5) { /* This was already the best answer in the previous run, or we tried too often, use it */ *ret = f; @@ -449,6 +579,11 @@ static int journal_file_find_newest_for_boot_id( r = journal_file_read_tail_timestamp(j, f); if (r < 0) return log_debug_errno(r, "Failed to read tail timestamp while trying to find newest journal file for boot ID %s.", SD_ID128_TO_STRING(id)); + if (r == 0) { + /* No new entry found. */ + *ret = f; + return 0; + } /* Refreshing the timestamp we read might have reshuffled the prioq, hence let's check the * prioq again and only use the information once we reached an equilibrium or hit a limit */ @@ -932,8 +1067,8 @@ static int real_journal_next(sd_journal *j, direction_t direction) { if (r < 0) return r; - for (unsigned i = 0; i < n_files; i++) { - JournalFile *f = (JournalFile *)files[i]; + FOREACH_ARRAY(_f, files, n_files) { + JournalFile *f = (JournalFile*) *_f; bool found; r = next_beyond_location(j, f, direction); @@ -1373,17 +1508,6 @@ static void track_file_disposition(sd_journal *j, JournalFile *f) { j->has_persistent_files = true; } -static const char *skip_slash(const char *p) { - - if (!p) - return NULL; - - while (*p == '/') - p++; - - return p; -} - static int add_any_file( sd_journal *j, int fd, @@ -1403,7 +1527,7 @@ static int add_any_file( /* If there's a top-level fd defined make the path relative, explicitly, since otherwise * openat() ignores the first argument. */ - fd = our_fd = openat(j->toplevel_fd, skip_slash(path), O_RDONLY|O_CLOEXEC|O_NONBLOCK); + fd = our_fd = openat(j->toplevel_fd, skip_leading_slash(path), O_RDONLY|O_CLOEXEC|O_NONBLOCK); else fd = our_fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK); if (fd < 0) { @@ -1494,6 +1618,41 @@ error: return r; } +int journal_get_directories(sd_journal *j, char ***ret) { + _cleanup_strv_free_ char **paths = NULL; + JournalFile *f; + const char *p; + size_t n = SIZE_MAX; + int r; + + assert(j); + assert(ret); + + /* This returns parent directories of opened journal files. */ + + ORDERED_HASHMAP_FOREACH_KEY(f, p, j->files) { + _cleanup_free_ char *d = NULL; + + /* Ignore paths generated from fd. */ + if (path_startswith(p, "/proc/")) + continue; + + r = path_extract_directory(p, &d); + if (r < 0) + return r; + + if (path_strv_contains(paths, d)) + continue; + + r = strv_extend_with_size(&paths, &n, d); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(paths); + return 0; +} + static int add_file_by_name( sd_journal *j, const char *prefix, @@ -1676,7 +1835,7 @@ static int directory_open(sd_journal *j, const char *path, DIR **ret) { else /* Open the specified directory relative to the toplevel fd. Enforce that the path specified is * relative, by dropping the initial slash */ - d = xopendirat(j->toplevel_fd, skip_slash(path), 0); + d = xopendirat(j->toplevel_fd, skip_leading_slash(path), 0); if (!d) return -errno; @@ -1684,6 +1843,100 @@ static int directory_open(sd_journal *j, const char *path, DIR **ret) { return 0; } +static Directory* directory_free(Directory *d) { + if (!d) + return NULL; + + if (d->journal) { + if (d->wd > 0 && + hashmap_remove_value(d->journal->directories_by_wd, INT_TO_PTR(d->wd), d) && + d->journal->inotify_fd >= 0) + (void) inotify_rm_watch(d->journal->inotify_fd, d->wd); + + if (d->path) + hashmap_remove_value(d->journal->directories_by_path, d->path, d); + } + + if (d->path) { + if (d->is_root) + log_debug("Root directory %s removed.", d->path); + else + log_debug("Directory %s removed.", d->path); + + free(d->path); + } + + return mfree(d); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Directory*, directory_free); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + directories_by_path_hash_ops, + char, + path_hash_func, + path_compare, + Directory, + directory_free); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + directories_by_wd_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + Directory, + directory_free); + +static int add_directory_impl(sd_journal *j, const char *path, bool is_root, Directory **ret) { + _cleanup_(directory_freep) Directory *m = NULL; + Directory *existing; + int r; + + assert(j); + assert(path); + assert(ret); + + existing = hashmap_get(j->directories_by_path, path); + if (existing) { + if (existing->is_root != is_root) { + /* Don't 'downgrade' from root directory */ + *ret = NULL; + return 0; + } + + *ret = existing; + return 1; + } + + m = new(Directory, 1); + if (!m) + return -ENOMEM; + + *m = (Directory) { + .journal = j, + .is_root = is_root, + .path = strdup(path), + .wd = -1, + }; + + if (!m->path) + return -ENOMEM; + + r = hashmap_ensure_put(&j->directories_by_path, &directories_by_path_hash_ops, m->path, m); + if (r < 0) + return r; + + j->current_invalidate_counter++; + + if (is_root) + log_debug("Root directory %s added.", m->path); + else + log_debug("Directory %s added.", m->path); + + *ret = TAKE_PTR(m); + return 1; +} + static int add_directory(sd_journal *j, const char *prefix, const char *dirname); static void directory_enumerate(sd_journal *j, Directory *m, DIR *d) { @@ -1724,12 +1977,14 @@ static void directory_watch(sd_journal *j, Directory *m, int fd, uint32_t mask) return; } - r = hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m); - if (r == -EEXIST) - log_debug_errno(r, "Directory '%s' already being watched under a different path, ignoring: %m", m->path); + r = hashmap_ensure_put(&j->directories_by_wd, &directories_by_wd_hash_ops, INT_TO_PTR(m->wd), m); if (r < 0) { - log_debug_errno(r, "Failed to add watch for journal directory '%s' to hashmap, ignoring: %m", m->path); - (void) inotify_rm_watch(j->inotify_fd, m->wd); + if (r == -EEXIST) + log_debug_errno(r, "Directory '%s' already being watched under a different path, ignoring: %m", m->path); + else { + log_debug_errno(r, "Failed to add watch for journal directory '%s' to hashmap, ignoring: %m", m->path); + (void) inotify_rm_watch(j->inotify_fd, m->wd); + } m->wd = -1; } } @@ -1775,32 +2030,11 @@ static int add_directory( goto fail; } - m = hashmap_get(j->directories_by_path, path); - if (!m) { - m = new(Directory, 1); - if (!m) { - r = -ENOMEM; - goto fail; - } - - *m = (Directory) { - .is_root = false, - .path = path, - }; - - if (hashmap_put(j->directories_by_path, m->path, m) < 0) { - free(m); - r = -ENOMEM; - goto fail; - } - - path = NULL; /* avoid freeing in cleanup */ - j->current_invalidate_counter++; - - log_debug("Directory %s added.", m->path); - - } else if (m->is_root) - return 0; /* Don't 'downgrade' from root directory */ + r = add_directory_impl(j, path, /* is_root = */ false, &m); + if (r < 0) + goto fail; + if (r == 0) + return 0; m->last_seen_generation = j->generation; @@ -1878,35 +2112,10 @@ static int add_root_directory(sd_journal *j, const char *p, bool missing_ok) { rewinddir(d); } - m = hashmap_get(j->directories_by_path, p); - if (!m) { - m = new0(Directory, 1); - if (!m) { - r = -ENOMEM; - goto fail; - } - - m->is_root = true; - - m->path = strdup(p); - if (!m->path) { - free(m); - r = -ENOMEM; - goto fail; - } - - if (hashmap_put(j->directories_by_path, m->path, m) < 0) { - free(m->path); - free(m); - r = -ENOMEM; - goto fail; - } - - j->current_invalidate_counter++; - - log_debug("Root directory %s added.", m->path); - - } else if (!m->is_root) + r = add_directory_impl(j, p, /* is_root = */ true, &m); + if (r < 0) + goto fail; + if (r == 0) return 0; directory_watch(j, m, dirfd(d), @@ -1928,27 +2137,6 @@ fail: return r; } -static void remove_directory(sd_journal *j, Directory *d) { - assert(j); - - if (d->wd > 0) { - hashmap_remove(j->directories_by_wd, INT_TO_PTR(d->wd)); - - if (j->inotify_fd >= 0) - (void) inotify_rm_watch(j->inotify_fd, d->wd); - } - - hashmap_remove(j->directories_by_path, d->path); - - if (d->is_root) - log_debug("Root directory %s removed.", d->path); - else - log_debug("Directory %s removed.", d->path); - - free(d->path); - free(d); -} - static int add_search_paths(sd_journal *j) { static const char search_paths[] = @@ -2003,7 +2191,7 @@ static int allocate_inotify(sd_journal *j) { return -errno; } - return hashmap_ensure_allocated(&j->directories_by_wd, NULL); + return 0; } static sd_journal *journal_new(int flags, const char *path, const char *namespace) { @@ -2045,9 +2233,8 @@ static sd_journal *journal_new(int flags, const char *path, const char *namespac return NULL; j->files_cache = ordered_hashmap_iterated_cache_new(j->files); - j->directories_by_path = hashmap_new(&path_hash_ops); j->mmap = mmap_cache_new(); - if (!j->files_cache || !j->directories_by_path || !j->mmap) + if (!j->files_cache || !j->mmap) return NULL; return TAKE_PTR(j); @@ -2059,7 +2246,8 @@ static sd_journal *journal_new(int flags, const char *path, const char *namespac SD_JOURNAL_SYSTEM | \ SD_JOURNAL_CURRENT_USER | \ SD_JOURNAL_ALL_NAMESPACES | \ - SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE) + SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE | \ + SD_JOURNAL_ASSUME_IMMUTABLE) _public_ int sd_journal_open_namespace(sd_journal **ret, const char *namespace, int flags) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; @@ -2085,7 +2273,9 @@ _public_ int sd_journal_open(sd_journal **ret, int flags) { } #define OPEN_CONTAINER_ALLOWED_FLAGS \ - (SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_SYSTEM) + (SD_JOURNAL_LOCAL_ONLY | \ + SD_JOURNAL_SYSTEM | \ + SD_JOURNAL_ASSUME_IMMUTABLE) _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, int flags) { _cleanup_free_ char *root = NULL, *class = NULL; @@ -2129,7 +2319,9 @@ _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, in #define OPEN_DIRECTORY_ALLOWED_FLAGS \ (SD_JOURNAL_OS_ROOT | \ - SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER ) + SD_JOURNAL_SYSTEM | \ + SD_JOURNAL_CURRENT_USER | \ + SD_JOURNAL_ASSUME_IMMUTABLE) _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; @@ -2154,12 +2346,15 @@ _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int f return 0; } +#define OPEN_FILES_ALLOWED_FLAGS \ + (SD_JOURNAL_ASSUME_IMMUTABLE) + _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int flags) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; int r; assert_return(ret, -EINVAL); - assert_return(flags == 0, -EINVAL); + assert_return((flags & ~OPEN_FILES_ALLOWED_FLAGS) == 0, -EINVAL); j = journal_new(flags, NULL, NULL); if (!j) @@ -2181,7 +2376,8 @@ _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int fla (SD_JOURNAL_OS_ROOT | \ SD_JOURNAL_SYSTEM | \ SD_JOURNAL_CURRENT_USER | \ - SD_JOURNAL_TAKE_DIRECTORY_FD) + SD_JOURNAL_TAKE_DIRECTORY_FD | \ + SD_JOURNAL_ASSUME_IMMUTABLE) _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; @@ -2219,6 +2415,9 @@ _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { return 0; } +#define OPEN_FILES_FD_ALLOWED_FLAGS \ + (SD_JOURNAL_ASSUME_IMMUTABLE) + _public_ int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fds, int flags) { JournalFile *f; _cleanup_(sd_journal_closep) sd_journal *j = NULL; @@ -2226,7 +2425,7 @@ _public_ int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fd assert_return(ret, -EINVAL); assert_return(n_fds > 0, -EBADF); - assert_return(flags == 0, -EINVAL); + assert_return((flags & ~OPEN_FILES_FD_ALLOWED_FLAGS) == 0, -EINVAL); j = journal_new(flags, NULL, NULL); if (!j) @@ -2270,27 +2469,16 @@ fail: } _public_ void sd_journal_close(sd_journal *j) { - Directory *d; - Prioq *p; - if (!j || journal_origin_changed(j)) return; - while ((p = hashmap_first(j->newest_by_boot_id))) - journal_file_unlink_newest_by_boot_id(j, prioq_peek(p)); - hashmap_free(j->newest_by_boot_id); + journal_clear_newest_by_boot_id(j); sd_journal_flush_matches(j); ordered_hashmap_free_with_destructor(j->files, journal_file_close); iterated_cache_free(j->files_cache); - while ((d = hashmap_first(j->directories_by_path))) - remove_directory(j, d); - - while ((d = hashmap_first(j->directories_by_wd))) - remove_directory(j, d); - hashmap_free(j->directories_by_path); hashmap_free(j->directories_by_wd); @@ -2306,6 +2494,8 @@ _public_ void sd_journal_close(sd_journal *j) { hashmap_free_free(j->errors); + set_free(j->exclude_syslog_identifiers); + free(j->path); free(j->prefix); free(j->namespace); @@ -2314,84 +2504,6 @@ _public_ void sd_journal_close(sd_journal *j) { free(j); } -static void journal_file_unlink_newest_by_boot_id(sd_journal *j, JournalFile *f) { - JournalFile *nf; - Prioq *p; - - assert(j); - assert(f); - - if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) /* not linked currently, hence this is a NOP */ - return; - - assert_se(p = hashmap_get(j->newest_by_boot_id, &f->newest_boot_id)); - assert_se(prioq_remove(p, f, &f->newest_boot_id_prioq_idx) > 0); - - nf = prioq_peek(p); - if (nf) - /* There's still a member in the prioq? Then make sure the hashmap key now points to its - * .newest_boot_id field (and not ours!). Not we only replace the memory of the key here, the - * value of the key (and the data associated with it) remain the same. */ - assert_se(hashmap_replace(j->newest_by_boot_id, &nf->newest_boot_id, p) >= 0); - else { - assert_se(hashmap_remove(j->newest_by_boot_id, &f->newest_boot_id) == p); - prioq_free(p); - } - - f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL; -} - -static int journal_file_newest_monotonic_compare(const void *a, const void *b) { - const JournalFile *x = a, *y = b; - - return -CMP(x->newest_monotonic_usec, y->newest_monotonic_usec); /* Invert order, we want newest first! */ -} - -static int journal_file_reshuffle_newest_by_boot_id(sd_journal *j, JournalFile *f) { - Prioq *p; - int r; - - assert(j); - assert(f); - - p = hashmap_get(j->newest_by_boot_id, &f->newest_boot_id); - if (p) { - /* There's already a priority queue for this boot ID */ - - if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) { - r = prioq_put(p, f, &f->newest_boot_id_prioq_idx); /* Insert if we aren't in there yet */ - if (r < 0) - return r; - } else - prioq_reshuffle(p, f, &f->newest_boot_id_prioq_idx); /* Reshuffle otherwise */ - - } else { - _cleanup_(prioq_freep) Prioq *q = NULL; - - /* No priority queue yet, then allocate one */ - - assert(f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL); /* we can't be a member either */ - - q = prioq_new(journal_file_newest_monotonic_compare); - if (!q) - return -ENOMEM; - - r = prioq_put(q, f, &f->newest_boot_id_prioq_idx); - if (r < 0) - return r; - - r = hashmap_ensure_put(&j->newest_by_boot_id, &id128_hash_ops, &f->newest_boot_id, q); - if (r < 0) { - f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL; - return r; - } - - TAKE_PTR(q); - } - - return 0; -} - static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { uint64_t offset, mo, rt; sd_id128_t id; @@ -2405,11 +2517,13 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { /* Tries to read the timestamp of the most recently written entry. */ - r = journal_file_fstat(f); - if (r < 0) - return r; - if (f->newest_mtime == timespec_load(&f->last_stat.st_mtim)) - return 0; /* mtime didn't change since last time, don't bother */ + if (FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE) && f->newest_entry_offset != 0) + return 0; /* We have already read the file, and we assume that the file is immutable. */ + + if (f->header->state == f->newest_state && + f->header->state == STATE_ARCHIVED && + f->newest_entry_offset != 0) + return 0; /* We have already read archived file. */ if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset)) { offset = le64toh(READ_NOW(f->header->tail_entry_offset)); @@ -2420,6 +2534,8 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { } if (offset == 0) return -ENODATA; /* not a single object/entry, hence no tail timestamp */ + if (offset == f->newest_entry_offset) + return 0; /* No new entry is added after we read last time. */ /* Move to the last object in the journal file, in the hope it is an entry (which it usually will * be). If we lack the "tail_entry_offset" field in the header, we specify the type as OBJECT_UNUSED @@ -2429,6 +2545,7 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { if (r < 0) { log_debug_errno(r, "Failed to move to last object in journal file, ignoring: %m"); o = NULL; + offset = 0; } if (o && o->object.type == OBJECT_ENTRY) { /* Yay, last object is an entry, let's use the data. */ @@ -2446,10 +2563,11 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { mo = le64toh(f->header->tail_entry_monotonic); rt = le64toh(f->header->tail_entry_realtime); id = f->header->tail_entry_boot_id; + offset = UINT64_MAX; } else { /* Otherwise let's find the last entry manually (this possibly means traversing the * chain of entry arrays, till the end */ - r = journal_file_next_entry(f, 0, DIRECTION_UP, &o, NULL); + r = journal_file_next_entry(f, 0, DIRECTION_UP, &o, offset == 0 ? &offset : NULL); if (r < 0) return r; if (r == 0) @@ -2464,6 +2582,17 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { if (mo > rt) /* monotonic clock is further ahead than realtime? that's weird, refuse to use the data */ return -ENODATA; + if (offset == f->newest_entry_offset) { + /* Cached data and the current one should be equivalent. */ + if (!sd_id128_equal(f->newest_machine_id, f->header->machine_id) || + !sd_id128_equal(f->newest_boot_id, id) || + f->newest_monotonic_usec != mo || + f->newest_realtime_usec != rt) + return -EBADMSG; + + return 0; /* No new entry is added after we read last time. */ + } + if (!sd_id128_equal(f->newest_boot_id, id)) journal_file_unlink_newest_by_boot_id(j, f); @@ -2471,13 +2600,14 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { f->newest_monotonic_usec = mo; f->newest_realtime_usec = rt; f->newest_machine_id = f->header->machine_id; - f->newest_mtime = timespec_load(&f->last_stat.st_mtim); + f->newest_entry_offset = offset; + f->newest_state = f->header->state; r = journal_file_reshuffle_newest_by_boot_id(j, f); if (r < 0) return r; - return 0; + return 1; /* Updated. */ } _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) { @@ -2526,9 +2656,7 @@ _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id12 if (r < 0) return r; - if (ret_boot_id) - *ret_boot_id = o->entry.boot_id; - else { + if (!ret_boot_id) { sd_id128_t id; r = sd_id128_get_boot(&id); @@ -2545,6 +2673,8 @@ _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id12 if (ret) *ret = t; + if (ret_boot_id) + *ret_boot_id = o->entry.boot_id; return 0; } @@ -2748,6 +2878,7 @@ _public_ int sd_journal_get_fd(sd_journal *j) { assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); + assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH); if (j->no_inotify) return -EMEDIUMTYPE; @@ -2774,6 +2905,7 @@ _public_ int sd_journal_get_events(sd_journal *j) { assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); + assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH); fd = sd_journal_get_fd(j); if (fd < 0) @@ -2787,6 +2919,7 @@ _public_ int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec) { assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); + assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH); assert_return(timeout_usec, -EINVAL); fd = sd_journal_get_fd(j); @@ -2839,7 +2972,7 @@ static void process_q_overflow(sd_journal *j) { continue; log_debug("Directory '%s' hasn't been seen in this enumeration, removing.", f->path); - remove_directory(j, m); + directory_free(m); } log_debug("Reiteration complete."); @@ -2875,7 +3008,7 @@ static void process_inotify_event(sd_journal *j, const struct inotify_event *e) /* Event for a subdirectory */ if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) - remove_directory(j, d); + directory_free(d); } else if (d->is_root && (e->mask & IN_ISDIR) && e->len > 0 && id128_is_valid(e->name)) { @@ -2914,6 +3047,8 @@ _public_ int sd_journal_process(sd_journal *j) { if (j->inotify_fd < 0) /* We have no inotify fd yet? Then there's noting to process. */ return 0; + assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH); + j->last_process_usec = now(CLOCK_MONOTONIC); j->last_invalidate_counter = j->current_invalidate_counter; @@ -2942,6 +3077,7 @@ _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) { assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); + assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH); if (j->inotify_fd < 0) { JournalFile *f; diff --git a/src/libsystemd/sd-journal/test-journal-append.c b/src/libsystemd/sd-journal/test-journal-append.c index 24b98c8..b7e7c78 100644 --- a/src/libsystemd/sd-journal/test-journal-append.c +++ b/src/libsystemd/sd-journal/test-journal-append.c @@ -62,7 +62,7 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { log_debug("Opening journal %s/system.journal", tempdir); r = journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, "system.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, @@ -114,7 +114,7 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { * the corrupted journal */ mj = journal_file_offline_close(mj); r = journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, "system.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, diff --git a/src/libsystemd/sd-journal/test-journal-enum.c b/src/libsystemd/sd-journal/test-journal-enum.c index 03fe8e2..d101fe7 100644 --- a/src/libsystemd/sd-journal/test-journal-enum.c +++ b/src/libsystemd/sd-journal/test-journal-enum.c @@ -15,10 +15,10 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - assert_se(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY) >= 0); + assert_se(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); - assert_se(sd_journal_add_match(j, "_TRANSPORT=syslog", 0) >= 0); - assert_se(sd_journal_add_match(j, "_UID=0", 0) >= 0); + assert_se(sd_journal_add_match(j, "_TRANSPORT=syslog", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "_UID=0", SIZE_MAX) >= 0); SD_JOURNAL_FOREACH_BACKWARDS(j) { const void *d; diff --git a/src/libsystemd/sd-journal/test-journal-flush.c b/src/libsystemd/sd-journal/test-journal-flush.c index 3f07835..b06645a 100644 --- a/src/libsystemd/sd-journal/test-journal-flush.c +++ b/src/libsystemd/sd-journal/test-journal-flush.c @@ -7,6 +7,8 @@ #include "alloc-util.h" #include "chattr-util.h" +#include "dirent-util.h" +#include "fd-util.h" #include "journal-file-util.h" #include "journal-internal.h" #include "logs-show.h" @@ -17,6 +19,83 @@ #include "tests.h" #include "tmpfile-util.h" +static int open_archive_file(sd_journal **ret) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_close_ int newest_fd = -EBADF; + unsigned long long newest_realtime = 0; + bool newest_is_system = false; + sd_id128_t machine_id; + const char *p; + int r; + + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return r; + + p = strjoina("/var/log/journal/", SD_ID128_TO_STRING(machine_id), "/"); + + d = opendir(p); + if (!d) + return -errno; + + FOREACH_DIRENT_ALL(de, d, return -errno) { + unsigned long long realtime; + bool is_system; + size_t q; + int fd; + + if (!dirent_is_file_with_suffix(de, ".journal")) + continue; + + is_system = startswith(de->d_name, "system@"); + if (newest_is_system && !is_system) + continue; + + q = strlen(de->d_name); + + if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) + continue; + + if (de->d_name[q-8-16-1] != '-' || + de->d_name[q-8-16-1-16-1] != '-' || + de->d_name[q-8-16-1-16-1-32-1] != '@') + continue; + + if (sscanf(de->d_name + q-8-16, "%16llx.journal", &realtime) != 1) + continue; + + if (newest_realtime >= realtime) + continue; + + fd = openat(dirfd(d), de->d_name, O_CLOEXEC | O_NONBLOCK | O_RDONLY); + if (fd < 0) { + log_info_errno(errno, "Failed to open /var/log/journal/%s, ignoring: %m", de->d_name); + continue; + } + + close_and_replace(newest_fd, fd); + newest_realtime = realtime; + newest_is_system = is_system; + } + + if (newest_fd < 0) + return log_info_errno(SYNTHETIC_ERRNO(ENOENT), "No archive journal found."); + + r = sd_journal_open_files_fd(ret, &newest_fd, 1, SD_JOURNAL_ASSUME_IMMUTABLE); + + _cleanup_free_ char *path = NULL; + (void) fd_get_path(newest_fd, &path); + + if (r < 0) + log_info_errno(r, "Failed to open %s, ignoring: %m", strna(path)); + else { + log_info("Opened %s.", strna(path)); + TAKE_FD(newest_fd); + } + + return r; +} + static void test_journal_flush_one(int argc, char *argv[]) { _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; _cleanup_free_ char *fn = NULL; @@ -32,13 +111,16 @@ static void test_journal_flush_one(int argc, char *argv[]) { assert_se(fn = path_join(dn, "test.journal")); - r = journal_file_open(-1, fn, O_CREAT|O_RDWR, 0, 0644, 0, NULL, m, NULL, &new_journal); + r = journal_file_open(-EBADF, fn, O_CREAT|O_RDWR, 0, 0644, 0, NULL, m, NULL, &new_journal); assert_se(r >= 0); if (argc > 1) - r = sd_journal_open_files(&j, (const char **) strv_skip(argv, 1), 0); - else - r = sd_journal_open(&j, 0); + r = sd_journal_open_files(&j, (const char **) strv_skip(argv, 1), SD_JOURNAL_ASSUME_IMMUTABLE); + else { + r = open_archive_file(&j); + if (r < 0) + r = sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE); + } assert_se(r == 0); sd_journal_set_data_threshold(j, 0); @@ -75,7 +157,7 @@ static void test_journal_flush_one(int argc, char *argv[]) { /* Open the new journal before archiving and offlining the file. */ sd_journal_close(j); - assert_se(sd_journal_open_directory(&j, dn, 0) >= 0); + assert_se(sd_journal_open_directory(&j, dn, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); /* Read the online journal. */ assert_se(sd_journal_seek_tail(j) >= 0); diff --git a/src/libsystemd/sd-journal/test-journal-init.c b/src/libsystemd/sd-journal/test-journal-init.c index c8a1977..ef66efd 100644 --- a/src/libsystemd/sd-journal/test-journal-init.c +++ b/src/libsystemd/sd-journal/test-journal-init.c @@ -31,12 +31,12 @@ int main(int argc, char *argv[]) { (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL); for (i = 0; i < I; i++) { - r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE); assert_se(r == 0); sd_journal_close(j); - r = sd_journal_open_directory(&j, t, 0); + r = sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE); assert_se(r == 0); assert_se(sd_journal_seek_head(j) == 0); @@ -45,8 +45,8 @@ int main(int argc, char *argv[]) { r = safe_fork("(journal-fork-test)", FORK_WAIT|FORK_LOG, NULL); if (r == 0) { assert_se(j); - assert_se(sd_journal_get_realtime_usec(j, NULL) == -ECHILD); - assert_se(sd_journal_seek_tail(j) == -ECHILD); + ASSERT_RETURN_EXPECTED_SE(sd_journal_get_realtime_usec(j, NULL) == -ECHILD); + ASSERT_RETURN_EXPECTED_SE(sd_journal_seek_tail(j) == -ECHILD); assert_se(j->current_location.type == LOCATION_HEAD); sd_journal_close(j); _exit(EXIT_SUCCESS); @@ -57,8 +57,7 @@ int main(int argc, char *argv[]) { sd_journal_close(j); j = NULL; - r = sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY); - assert_se(r == -EINVAL); + ASSERT_RETURN_EXPECTED(assert_se(sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY) == -EINVAL)); assert_se(j == NULL); } diff --git a/src/libsystemd/sd-journal/test-journal-interleaving.c b/src/libsystemd/sd-journal/test-journal-interleaving.c index 8aeef8f..d98b3ce 100644 --- a/src/libsystemd/sd-journal/test-journal-interleaving.c +++ b/src/libsystemd/sd-journal/test-journal-interleaving.c @@ -43,7 +43,7 @@ static JournalFile *test_open_internal(const char *name, JournalFileFlags flags) m = mmap_cache_new(); assert_se(m != NULL); - assert_ret(journal_file_open(-1, name, O_RDWR|O_CREAT, flags, 0644, UINT64_MAX, NULL, m, NULL, &f)); + assert_ret(journal_file_open(-EBADF, name, O_RDWR|O_CREAT, flags, 0644, UINT64_MAX, NULL, m, NULL, &f)); return f; } @@ -74,9 +74,9 @@ static void test_done(const char *t) { } static void append_number(JournalFile *f, int n, const sd_id128_t *boot_id, uint64_t *seqnum, uint64_t *ret_offset) { - _cleanup_free_ char *p = NULL, *q = NULL; + _cleanup_free_ char *p = NULL, *q = NULL, *s = NULL; dual_timestamp ts; - struct iovec iovec[2]; + struct iovec iovec[3]; size_t n_iov = 0; dual_timestamp_now(&ts); @@ -92,6 +92,9 @@ static void append_number(JournalFile *f, int n, const sd_id128_t *boot_id, uint assert_se(asprintf(&p, "NUMBER=%d", n) >= 0); iovec[n_iov++] = IOVEC_MAKE_STRING(p); + assert_se(s = strjoin("LESS_THAN_FIVE=%d", yes_no(n < 5))); + iovec[n_iov++] = IOVEC_MAKE_STRING(s); + if (boot_id) { assert_se(q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id))); iovec[n_iov++] = IOVEC_MAKE_STRING(q); @@ -250,6 +253,37 @@ static void mkdtemp_chdir_chattr(char *path) { (void) chattr_path(path, FS_NOCOW_FL, FS_NOCOW_FL, NULL); } +static void test_cursor(sd_journal *j) { + _cleanup_strv_free_ char **cursors = NULL; + int r; + + assert_se(sd_journal_seek_head(j) >= 0); + + for (;;) { + r = sd_journal_next(j); + assert_se(r >= 0); + if (r == 0) + break; + + _cleanup_free_ char *cursor = NULL; + assert_se(sd_journal_get_cursor(j, &cursor) >= 0); + assert_se(sd_journal_test_cursor(j, cursor) > 0); + assert_se(strv_consume(&cursors, TAKE_PTR(cursor)) >= 0); + } + + STRV_FOREACH(c, cursors) { + assert_se(sd_journal_seek_cursor(j, *c) >= 0); + assert_se(sd_journal_next(j) >= 0); + assert_se(sd_journal_test_cursor(j, *c) > 0); + } + + assert_se(sd_journal_seek_head(j) >= 0); + STRV_FOREACH(c, cursors) { + assert_se(sd_journal_next(j) >= 0); + assert_se(sd_journal_test_cursor(j, *c) > 0); + } +} + static void test_skip_one(void (*setup)(void)) { char t[] = "/var/tmp/journal-skip-XXXXXX"; sd_journal *j; @@ -260,14 +294,14 @@ static void test_skip_one(void (*setup)(void)) { setup(); /* Seek to head, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ test_check_numbers_down(j, 9); sd_journal_close(j); /* Seek to head, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ assert_se(sd_journal_previous(j) == 0); /* no-op */ @@ -275,7 +309,7 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to head twice, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ assert_ret(sd_journal_seek_head(j)); @@ -284,7 +318,7 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to head, move to previous, then iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_previous(j) == 0); /* no-op */ assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ @@ -292,7 +326,7 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to head, walk several steps, then iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_previous(j) == 0); /* no-op */ assert_se(sd_journal_previous(j) == 0); /* no-op */ @@ -304,14 +338,14 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to tail, iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ test_check_numbers_up(j, 9); sd_journal_close(j); /* Seek to tail twice, iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ assert_ret(sd_journal_seek_tail(j)); @@ -320,7 +354,7 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to tail, move to next, then iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_next(j) == 0); /* no-op */ assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ @@ -328,7 +362,7 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to tail, walk several steps, then iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_next(j) == 0); /* no-op */ assert_se(sd_journal_next(j) == 0); /* no-op */ @@ -340,14 +374,14 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to tail, skip to head, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_previous_skip(j, 9) == 9); /* pointing to the first entry. */ test_check_numbers_down(j, 9); sd_journal_close(j); /* Seek to tail, skip to head in a more complex way, then iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_next(j) == 0); assert_se(sd_journal_previous_skip(j, 4) == 4); @@ -366,14 +400,14 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to head, skip to tail, iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_next_skip(j, 9) == 9); test_check_numbers_up(j, 9); sd_journal_close(j); /* Seek to head, skip to tail in a more complex way, then iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_previous(j) == 0); assert_se(sd_journal_next_skip(j, 4) == 4); @@ -391,6 +425,30 @@ static void test_skip_one(void (*setup)(void)) { test_check_numbers_up(j, 9); sd_journal_close(j); + /* For issue #31516. */ + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); + test_cursor(j); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=yes", SIZE_MAX) >= 0); + test_cursor(j); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=no", SIZE_MAX) >= 0); + test_cursor(j); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=hoge", SIZE_MAX) >= 0); + test_cursor(j); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=yes", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=3", SIZE_MAX) >= 0); + test_cursor(j); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=yes", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=3", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=4", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=5", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=6", SIZE_MAX) >= 0); + test_cursor(j); + test_done(t); } @@ -401,7 +459,7 @@ TEST(skip) { static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) { char t[] = "/var/tmp/journal-boot-id-XXXXXX"; - sd_journal *j; + _cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_free_ BootId *boots = NULL; size_t n_boots; @@ -409,28 +467,59 @@ static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) { setup(); - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_se(journal_get_boots(j, &boots, &n_boots) >= 0); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); + assert_se(journal_get_boots( + j, + /* advance_older = */ false, /* max_ids = */ SIZE_MAX, + &boots, &n_boots) >= 0); assert_se(boots); assert_se(n_boots == n_boots_expected); - sd_journal_close(j); - FOREACH_ARRAY(b, boots, n_boots) { - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_se(journal_find_boot_by_id(j, b->id) == 1); - sd_journal_close(j); + for (size_t i = 0; i < n_boots; i++) { + sd_id128_t id; + + /* positive offset */ + assert_se(journal_find_boot(j, SD_ID128_NULL, (int) (i + 1), &id) == 1); + assert_se(sd_id128_equal(id, boots[i].id)); + + /* negative offset */ + assert_se(journal_find_boot(j, SD_ID128_NULL, (int) (i + 1) - (int) n_boots, &id) == 1); + assert_se(sd_id128_equal(id, boots[i].id)); + + for (size_t k = 0; k < n_boots; k++) { + int offset = (int) k - (int) i; + + /* relative offset */ + assert_se(journal_find_boot(j, boots[i].id, offset, &id) == 1); + assert_se(sd_id128_equal(id, boots[k].id)); + } } - for (int i = - (int) n_boots + 1; i <= (int) n_boots; i++) { - sd_id128_t id; + for (size_t i = 0; i <= n_boots_expected + 1; i++) { + _cleanup_free_ BootId *boots_limited = NULL; + size_t n_boots_limited; + + assert_se(journal_get_boots( + j, + /* advance_older = */ false, /* max_ids = */ i, + &boots_limited, &n_boots_limited) >= 0); + assert_se(boots_limited || i == 0); + assert_se(n_boots_limited == MIN(i, n_boots_expected)); + assert_se(memcmp_safe(boots, boots_limited, n_boots_limited * sizeof(BootId)) == 0); + } - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_se(journal_find_boot_by_offset(j, i, &id) == 1); - if (i <= 0) - assert_se(sd_id128_equal(id, boots[n_boots + i - 1].id)); - else - assert_se(sd_id128_equal(id, boots[i - 1].id)); - sd_journal_close(j); + for (size_t i = 0; i <= n_boots_expected + 1; i++) { + _cleanup_free_ BootId *boots_limited = NULL; + size_t n_boots_limited; + + assert_se(journal_get_boots( + j, + /* advance_older = */ true, /* max_ids = */ i, + &boots_limited, &n_boots_limited) >= 0); + assert_se(boots_limited || i == 0); + assert_se(n_boots_limited == MIN(i, n_boots_expected)); + for (size_t k = 0; k < n_boots_limited; k++) + assert_se(memcmp(&boots[n_boots - k - 1], &boots_limited[k], sizeof(BootId)) == 0); } test_done(t); @@ -453,7 +542,7 @@ static void test_sequence_numbers_one(void) { mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, + assert_se(journal_file_open(-EBADF, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, UINT64_MAX, NULL, m, NULL, &one) == 0); append_number(one, 1, NULL, &seqnum, NULL); @@ -470,7 +559,7 @@ static void test_sequence_numbers_one(void) { memcpy(&seqnum_id, &one->header->seqnum_id, sizeof(sd_id128_t)); - assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, + assert_se(journal_file_open(-EBADF, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, UINT64_MAX, NULL, m, one, &two) == 0); assert_se(two->header->state == STATE_ONLINE); @@ -502,12 +591,12 @@ static void test_sequence_numbers_one(void) { test_close(one); /* If the machine-id is not initialized, the header file verification - * (which happens when re-opening a journal file) will fail. */ + * (which happens when reopening a journal file) will fail. */ if (sd_id128_get_machine(NULL) >= 0) { /* restart server */ seqnum = 0; - assert_se(journal_file_open(-1, "two.journal", O_RDWR, JOURNAL_COMPRESS, 0, + assert_se(journal_file_open(-EBADF, "two.journal", O_RDWR, JOURNAL_COMPRESS, 0, UINT64_MAX, NULL, m, NULL, &two) == 0); assert_se(sd_id128_equal(two->header->seqnum_id, seqnum_id)); @@ -567,7 +656,41 @@ static int expected_result(uint64_t needle, const uint64_t *candidates, const ui } } -static void verify(JournalFile *f, const uint64_t *seqnum, const uint64_t *offset, size_t n) { +static int expected_result_next(uint64_t needle, const uint64_t *candidates, const uint64_t *offset, size_t n, direction_t direction, uint64_t *ret) { + switch (direction) { + case DIRECTION_DOWN: + for (size_t i = 0; i < n; i++) + if (needle < offset[i]) { + *ret = candidates[i]; + return candidates[i] > 0; + } + *ret = 0; + return 0; + + case DIRECTION_UP: + for (size_t i = 0; i < n; i++) + if (needle <= offset[i]) { + n = i; + break; + } + + for (; n > 0 && candidates[n - 1] == 0; n--) + ; + + if (n == 0) { + *ret = 0; + return 0; + } + + *ret = candidates[n - 1]; + return candidates[n - 1] > 0; + + default: + assert_not_reached(); + } +} + +static void verify(JournalFile *f, const uint64_t *seqnum, const uint64_t *offset_candidates, const uint64_t *offset, size_t n) { uint64_t p, q; int r, e; @@ -664,12 +787,81 @@ static void verify(JournalFile *f, const uint64_t *seqnum, const uint64_t *offse assert_se(r == e); assert_se(p == q); } + + /* by journal_file_next_entry() */ + for (size_t i = 0; i < n; i++) { + p = 0; + r = journal_file_next_entry(f, offset[i] - 2, DIRECTION_DOWN, NULL, &p); + e = expected_result_next(offset[i] - 2, offset_candidates, offset, n, DIRECTION_DOWN, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i] - 1, DIRECTION_DOWN, NULL, &p); + e = expected_result_next(offset[i] - 1, offset_candidates, offset, n, DIRECTION_DOWN, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i], DIRECTION_DOWN, NULL, &p); + e = expected_result_next(offset[i], offset_candidates, offset, n, DIRECTION_DOWN, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i] + 1, DIRECTION_DOWN, NULL, &p); + e = expected_result_next(offset[i] + 1, offset_candidates, offset, n, DIRECTION_DOWN, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i] - 1, DIRECTION_UP, NULL, &p); + e = expected_result_next(offset[i] - 1, offset_candidates, offset, n, DIRECTION_UP, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i], DIRECTION_UP, NULL, &p); + e = expected_result_next(offset[i], offset_candidates, offset, n, DIRECTION_UP, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i] + 1, DIRECTION_UP, NULL, &p); + e = expected_result_next(offset[i] + 1, offset_candidates, offset, n, DIRECTION_UP, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i] + 2, DIRECTION_UP, NULL, &p); + e = expected_result_next(offset[i] + 2, offset_candidates, offset, n, DIRECTION_UP, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + } + for (size_t trial = 0; trial < 3 * n; trial++) { + uint64_t i = offset[0] - 1 + random_u64_range(offset[n-1] - offset[0] + 2); + + p = 0; + r = journal_file_next_entry(f, i, DIRECTION_DOWN, NULL, &p); + e = expected_result_next(i, offset_candidates, offset, n, DIRECTION_DOWN, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + } + for (size_t trial = 0; trial < 3 * n; trial++) { + uint64_t i = offset[0] - 1 + random_u64_range(offset[n-1] - offset[0] + 2); + + p = 0; + r = journal_file_next_entry(f, i, DIRECTION_UP, NULL, &p); + e = expected_result_next(i, offset_candidates, offset, n, DIRECTION_UP, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + } } static void test_generic_array_bisect_one(size_t n, size_t num_corrupted) { _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; char t[] = "/var/tmp/journal-seq-XXXXXX"; - _cleanup_free_ uint64_t *seqnum = NULL, *offset = NULL; + _cleanup_free_ uint64_t *seqnum = NULL, *offset = NULL, *offset_candidates = NULL; JournalFile *f; log_info("/* %s(%zu, %zu) */", __func__, n, num_corrupted); @@ -678,7 +870,7 @@ static void test_generic_array_bisect_one(size_t n, size_t num_corrupted) { mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, + assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, UINT64_MAX, NULL, m, NULL, &f) == 0); assert_se(seqnum = new0(uint64_t, n)); @@ -695,7 +887,9 @@ static void test_generic_array_bisect_one(size_t n, size_t num_corrupted) { } } - verify(f, seqnum, offset, n); + assert_se(offset_candidates = newdup(uint64_t, offset, n)); + + verify(f, seqnum, offset_candidates, offset, n); /* Reset chain cache. */ assert_se(journal_file_move_to_entry_by_offset(f, offset[0], DIRECTION_DOWN, NULL, NULL) > 0); @@ -708,9 +902,10 @@ static void test_generic_array_bisect_one(size_t n, size_t num_corrupted) { assert_se(o); o->entry.seqnum = 0; seqnum[i] = 0; + offset_candidates[i] = 0; } - verify(f, seqnum, offset, n); + verify(f, seqnum, offset_candidates, offset, n); test_close(f); test_done(t); diff --git a/src/libsystemd/sd-journal/test-journal-match.c b/src/libsystemd/sd-journal/test-journal-match.c index 571a88c..c2c345f 100644 --- a/src/libsystemd/sd-journal/test-journal-match.c +++ b/src/libsystemd/sd-journal/test-journal-match.c @@ -16,40 +16,40 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - assert_se(sd_journal_open(&j, 0) >= 0); + assert_se(sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); - assert_se(sd_journal_add_match(j, "foobar", 0) < 0); - assert_se(sd_journal_add_match(j, "foobar=waldo", 0) < 0); - assert_se(sd_journal_add_match(j, "", 0) < 0); - assert_se(sd_journal_add_match(j, "=", 0) < 0); - assert_se(sd_journal_add_match(j, "=xxxxx", 0) < 0); + assert_se(sd_journal_add_match(j, "foobar", SIZE_MAX) < 0); + assert_se(sd_journal_add_match(j, "foobar=waldo", SIZE_MAX) < 0); + assert_se(sd_journal_add_match(j, "", SIZE_MAX) < 0); + assert_se(sd_journal_add_match(j, "=", SIZE_MAX) < 0); + assert_se(sd_journal_add_match(j, "=xxxxx", SIZE_MAX) < 0); assert_se(sd_journal_add_match(j, (uint8_t[4]){'A', '=', '\1', '\2'}, 4) >= 0); assert_se(sd_journal_add_match(j, (uint8_t[5]){'B', '=', 'C', '\0', 'D'}, 5) >= 0); - assert_se(sd_journal_add_match(j, "HALLO=WALDO", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=mmmm", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0); - assert_se(sd_journal_add_match(j, "HALLO=", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=yyyyy", 0) >= 0); - assert_se(sd_journal_add_match(j, "PIFF=paff", 0) >= 0); + assert_se(sd_journal_add_match(j, "HALLO=WALDO", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=mmmm", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "HALLO=", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=yyyyy", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "PIFF=paff", SIZE_MAX) >= 0); assert_se(sd_journal_add_disjunction(j) >= 0); - assert_se(sd_journal_add_match(j, "ONE=one", 0) >= 0); - assert_se(sd_journal_add_match(j, "ONE=two", 0) >= 0); - assert_se(sd_journal_add_match(j, "TWO=two", 0) >= 0); + assert_se(sd_journal_add_match(j, "ONE=one", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "ONE=two", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "TWO=two", SIZE_MAX) >= 0); assert_se(sd_journal_add_conjunction(j) >= 0); - assert_se(sd_journal_add_match(j, "L4_1=yes", 0) >= 0); - assert_se(sd_journal_add_match(j, "L4_1=ok", 0) >= 0); - assert_se(sd_journal_add_match(j, "L4_2=yes", 0) >= 0); - assert_se(sd_journal_add_match(j, "L4_2=ok", 0) >= 0); + assert_se(sd_journal_add_match(j, "L4_1=yes", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "L4_1=ok", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "L4_2=yes", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "L4_2=ok", SIZE_MAX) >= 0); assert_se(sd_journal_add_disjunction(j) >= 0); - assert_se(sd_journal_add_match(j, "L3=yes", 0) >= 0); - assert_se(sd_journal_add_match(j, "L3=ok", 0) >= 0); + assert_se(sd_journal_add_match(j, "L3=yes", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "L3=ok", SIZE_MAX) >= 0); assert_se(t = journal_make_match_string(j)); diff --git a/src/libsystemd/sd-journal/test-journal-stream.c b/src/libsystemd/sd-journal/test-journal-stream.c index 3a370ef..00c0435 100644 --- a/src/libsystemd/sd-journal/test-journal-stream.c +++ b/src/libsystemd/sd-journal/test-journal-stream.c @@ -76,9 +76,9 @@ static void run_test(void) { assert_se(chdir(t) >= 0); (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL); - assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &one) == 0); - assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &two) == 0); - assert_se(journal_file_open(-1, "three.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &three) == 0); + assert_se(journal_file_open(-EBADF, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &one) == 0); + assert_se(journal_file_open(-EBADF, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &two) == 0); + assert_se(journal_file_open(-EBADF, "three.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &three) == 0); for (i = 0; i < N_ENTRIES; i++) { char *p, *q; @@ -119,9 +119,9 @@ static void run_test(void) { (void) journal_file_offline_close(two); (void) journal_file_offline_close(three); - assert_se(sd_journal_open_directory(&j, t, 0) >= 0); + assert_se(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); - assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); + assert_se(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX) >= 0); SD_JOURNAL_FOREACH_BACKWARDS(j) { _cleanup_free_ char *c; @@ -147,7 +147,7 @@ static void run_test(void) { verify_contents(j, 1); printf("NEXT TEST\n"); - assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); + assert_se(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX) >= 0); assert_se(z = journal_make_match_string(j)); printf("resulting match expression is: %s\n", z); @@ -157,10 +157,10 @@ static void run_test(void) { printf("NEXT TEST\n"); sd_journal_flush_matches(j); - assert_se(sd_journal_add_match(j, "MAGIC=waldo", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=10", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=11", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=12", 0) >= 0); + assert_se(sd_journal_add_match(j, "MAGIC=waldo", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=10", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=11", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=12", SIZE_MAX) >= 0); assert_se(z = journal_make_match_string(j)); printf("resulting match expression is: %s\n", z); diff --git a/src/libsystemd/sd-journal/test-journal-verify.c b/src/libsystemd/sd-journal/test-journal-verify.c index edce440..396ebe1 100644 --- a/src/libsystemd/sd-journal/test-journal-verify.c +++ b/src/libsystemd/sd-journal/test-journal-verify.c @@ -47,7 +47,7 @@ static int raw_verify(const char *fn, const char *verification_key) { assert_se(m != NULL); r = journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, fn, O_RDONLY, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), @@ -92,7 +92,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { log_info("Generating a test journal"); assert_se(journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), @@ -128,7 +128,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { log_info("Verifying with key: %s", strna(verification_key)); assert_se(journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, "test.journal", O_RDONLY, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), diff --git a/src/libsystemd/sd-journal/test-journal.c b/src/libsystemd/sd-journal/test-journal.c index 96f2b67..19a6d2d 100644 --- a/src/libsystemd/sd-journal/test-journal.c +++ b/src/libsystemd/sd-journal/test-journal.c @@ -39,7 +39,7 @@ static void test_non_empty_one(void) { mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f) == 0); + assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f) == 0); assert_se(dual_timestamp_now(&ts)); assert_se(sd_id128_randomize(&fake_boot_id) == 0); @@ -136,10 +136,10 @@ static void test_empty_one(void) { mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0, 0666, UINT64_MAX, NULL, m, NULL, &f1) == 0); - assert_se(journal_file_open(-1, "test-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &f2) == 0); - assert_se(journal_file_open(-1, "test-seal.journal", O_RDWR|O_CREAT, JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f3) == 0); - assert_se(journal_file_open(-1, "test-seal-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f4) == 0); + assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, 0, 0666, UINT64_MAX, NULL, m, NULL, &f1) == 0); + assert_se(journal_file_open(-EBADF, "test-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &f2) == 0); + assert_se(journal_file_open(-EBADF, "test-seal.journal", O_RDWR|O_CREAT, JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f3) == 0); + assert_se(journal_file_open(-EBADF, "test-seal-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f4) == 0); journal_file_print_header(f1); puts(""); @@ -194,7 +194,7 @@ static bool check_compressed(uint64_t compress_threshold, uint64_t data_size) { mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, compress_threshold, NULL, m, NULL, &f) == 0); + assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, compress_threshold, NULL, m, NULL, &f) == 0); dual_timestamp_now(&ts); diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c index f9e86c6..4d91ba9 100644 --- a/src/libsystemd/sd-login/sd-login.c +++ b/src/libsystemd/sd-login/sd-login.c @@ -579,10 +579,7 @@ _public_ int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat) if (isempty(content)) return 0; - char t[DECIMAL_STR_MAX(uid_t)]; - xsprintf(t, UID_FMT, uid); - - return string_contains_word(content, NULL, t); + return string_contains_word(content, NULL, FORMAT_UID(uid)); } static int uid_get_array(uid_t uid, const char *variable, char ***array) { @@ -1275,7 +1272,7 @@ _public_ int sd_login_monitor_new(const char *category, sd_login_monitor **m) { _public_ sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m) { if (m) - (void) close_nointr(MONITOR_TO_FD(m)); + (void) close(MONITOR_TO_FD(m)); return NULL; } diff --git a/src/libsystemd/sd-login/test-login.c b/src/libsystemd/sd-login/test-login.c index 819f86f..66e4274 100644 --- a/src/libsystemd/sd-login/test-login.c +++ b/src/libsystemd/sd-login/test-login.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include <poll.h> +#if HAVE_PIDFD_OPEN +#include <sys/pidfd.h> +#endif #include "sd-login.h" @@ -10,6 +13,7 @@ #include "format-util.h" #include "log.h" #include "missing_syscall.h" +#include "mountpoint-util.h" #include "process-util.h" #include "string-util.h" #include "strv.h" @@ -103,7 +107,7 @@ TEST(login) { assert_se(IN_SET(r, 0, -ENOMEDIUM)); } - r = sd_uid_get_display(u2, &display_session); + r = ASSERT_RETURN_IS_CRITICAL(uid_is_valid(u2), sd_uid_get_display(u2, &display_session)); log_info("sd_uid_get_display("UID_FMT", …) → %s / \"%s\"", u2, e(r), strnull(display_session)); if (u2 == UID_INVALID) assert_se(r == -EINVAL); @@ -115,7 +119,7 @@ TEST(login) { sd_peer_get_session(pair[1], &qq); assert_se(streq_ptr(pp, qq)); - r = sd_uid_get_sessions(u2, false, &sessions); + r = ASSERT_RETURN_IS_CRITICAL(uid_is_valid(u2), sd_uid_get_sessions(u2, false, &sessions)); assert_se(t = strv_join(sessions, " ")); log_info("sd_uid_get_sessions("UID_FMT", …) → %s \"%s\"", u2, e(r), t); if (u2 == UID_INVALID) @@ -127,9 +131,9 @@ TEST(login) { sessions = strv_free(sessions); free(t); - assert_se(r == sd_uid_get_sessions(u2, false, NULL)); + assert_se(r == ASSERT_RETURN_IS_CRITICAL(uid_is_valid(u2), sd_uid_get_sessions(u2, false, NULL))); - r = sd_uid_get_seats(u2, false, &seats); + r = ASSERT_RETURN_IS_CRITICAL(uid_is_valid(u2), sd_uid_get_seats(u2, false, &seats)); assert_se(t = strv_join(seats, " ")); log_info("sd_uid_get_seats("UID_FMT", …) → %s \"%s\"", u2, e(r), t); if (u2 == UID_INVALID) @@ -141,7 +145,7 @@ TEST(login) { seats = strv_free(seats); free(t); - assert_se(r == sd_uid_get_seats(u2, false, NULL)); + assert_se(r == ASSERT_RETURN_IS_CRITICAL(uid_is_valid(u2), sd_uid_get_seats(u2, false, NULL))); if (session) { r = sd_session_is_active(session); @@ -327,6 +331,9 @@ TEST(monitor) { } static int intro(void) { + if (IN_SET(cg_unified(), -ENOENT, -ENOMEDIUM)) + return log_tests_skipped("cgroupfs is not mounted"); + log_info("/* Information printed is from the live system */"); return EXIT_SUCCESS; } diff --git a/src/libsystemd/sd-netlink/netlink-message-rtnl.c b/src/libsystemd/sd-netlink/netlink-message-rtnl.c index 008e802..fb11c7e 100644 --- a/src/libsystemd/sd-netlink/netlink-message-rtnl.c +++ b/src/libsystemd/sd-netlink/netlink-message-rtnl.c @@ -56,6 +56,10 @@ static bool rtnl_message_type_is_mdb(uint16_t type) { return IN_SET(type, RTM_NEWMDB, RTM_DELMDB, RTM_GETMDB); } +static bool rtnl_message_type_is_nsid(uint16_t type) { + return IN_SET(type, RTM_NEWNSID, RTM_DELNSID, RTM_GETNSID); +} + int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { struct rtmsg *rtm; @@ -92,6 +96,20 @@ int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char return 0; } +int sd_rtnl_message_route_set_tos(sd_netlink_message *m, unsigned char tos) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_tos = tos; + + return 0; +} + int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope) { struct rtmsg *rtm; @@ -336,7 +354,7 @@ int sd_rtnl_message_new_nexthop(sd_netlink *rtnl, sd_netlink_message **ret, return r; if (nlmsg_type == RTM_NEWNEXTHOP) - (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; nhm = NLMSG_DATA((*ret)->hdr); @@ -1202,3 +1220,24 @@ int sd_rtnl_message_new_mdb( return 0; } + +int sd_rtnl_message_new_nsid( + sd_netlink *rtnl, + sd_netlink_message **ret, + uint16_t nlmsg_type) { + + struct rtgenmsg *rt; + int r; + + assert_return(rtnl_message_type_is_nsid(nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + rt = NLMSG_DATA((*ret)->hdr); + rt->rtgen_family = AF_UNSPEC; + + return 0; +} diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c index 000a50e..49d000d 100644 --- a/src/libsystemd/sd-netlink/netlink-message.c +++ b/src/libsystemd/sd-netlink/netlink-message.c @@ -17,9 +17,6 @@ #define GET_CONTAINER(m, i) ((struct rtattr*)((uint8_t*)(m)->hdr + (m)->containers[i].offset)) -#define RTA_TYPE(rta) ((rta)->rta_type & NLA_TYPE_MASK) -#define RTA_FLAGS(rta) ((rta)->rta_type & ~NLA_TYPE_MASK) - int message_new_empty(sd_netlink *nl, sd_netlink_message **ret) { sd_netlink_message *m; @@ -789,32 +786,6 @@ int sd_netlink_message_read_data(sd_netlink_message *m, uint16_t attr_type, size if (ret_data) { void *data; - data = memdup(attr_data, r); - if (!data) - return -ENOMEM; - - *ret_data = data; - } - - if (ret_size) - *ret_size = r; - - return r; -} - -int sd_netlink_message_read_data_suffix0(sd_netlink_message *m, uint16_t attr_type, size_t *ret_size, void **ret_data) { - void *attr_data; - int r; - - assert_return(m, -EINVAL); - - r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); - if (r < 0) - return r; - - if (ret_data) { - void *data; - data = memdup_suffix0(attr_data, r); if (!data) return -ENOMEM; diff --git a/src/libsystemd/sd-netlink/netlink-types-genl.c b/src/libsystemd/sd-netlink/netlink-types-genl.c index 6fe9adc..226ac86 100644 --- a/src/libsystemd/sd-netlink/netlink-types-genl.c +++ b/src/libsystemd/sd-netlink/netlink-types-genl.c @@ -199,6 +199,7 @@ static const NLAPolicy genl_nl80211_policies[] = { [NL80211_ATTR_SSID] = BUILD_POLICY_WITH_SIZE(BINARY, IEEE80211_MAX_SSID_LEN), [NL80211_ATTR_STATUS_CODE] = BUILD_POLICY(U16), [NL80211_ATTR_4ADDR] = BUILD_POLICY(U8), + [NL80211_ATTR_NETNS_FD] = BUILD_POLICY(U32), }; /***************** genl wireguard type systems *****************/ diff --git a/src/libsystemd/sd-netlink/netlink-types-rtnl.c b/src/libsystemd/sd-netlink/netlink-types-rtnl.c index 0153456..e39a75c 100644 --- a/src/libsystemd/sd-netlink/netlink-types-rtnl.c +++ b/src/libsystemd/sd-netlink/netlink-types-rtnl.c @@ -17,6 +17,7 @@ #include <linux/if_tunnel.h> #include <linux/ip.h> #include <linux/l2tp.h> +#include <linux/net_namespace.h> #include <linux/netlink.h> #include <linux/nexthop.h> #include <linux/nl80211.h> @@ -123,6 +124,7 @@ static const NLAPolicy rtnl_link_info_data_bond_policies[] = { [IFLA_BOND_AD_ACTOR_SYSTEM] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), [IFLA_BOND_TLB_DYNAMIC_LB] = BUILD_POLICY(U8), [IFLA_BOND_PEER_NOTIF_DELAY] = BUILD_POLICY(U32), + [IFLA_BOND_MISSED_MAX] = BUILD_POLICY(U8), }; static const NLAPolicy rtnl_link_info_data_bridge_policies[] = { @@ -306,6 +308,7 @@ static const NLAPolicy rtnl_link_info_data_macvlan_policies[] = { [IFLA_MACVLAN_MACADDR_COUNT] = BUILD_POLICY(U32), [IFLA_MACVLAN_BC_QUEUE_LEN] = BUILD_POLICY(U32), [IFLA_MACVLAN_BC_QUEUE_LEN_USED] = BUILD_POLICY(U32), + [IFLA_MACVLAN_BC_CUTOFF] = BUILD_POLICY(S32), }; static const NLAPolicy rtnl_link_info_data_tun_policies[] = { @@ -1185,6 +1188,13 @@ static const NLAPolicy rtnl_mdb_policies[] = { DEFINE_POLICY_SET(rtnl_mdb); +static const NLAPolicy rtnl_nsid_policies[] = { + [NETNSA_FD] = BUILD_POLICY(S32), + [NETNSA_NSID] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_nsid); + static const NLAPolicy rtnl_policies[] = { [RTM_NEWLINK] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), [RTM_DELLINK] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), @@ -1220,6 +1230,9 @@ static const NLAPolicy rtnl_policies[] = { [RTM_NEWMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)), [RTM_DELMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)), [RTM_GETMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)), + [RTM_NEWNSID] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)), + [RTM_DELNSID] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)), + [RTM_GETNSID] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)), }; DEFINE_POLICY_SET(rtnl); diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c index 832159a..9061609 100644 --- a/src/libsystemd/sd-netlink/netlink-util.c +++ b/src/libsystemd/sd-netlink/netlink-util.c @@ -11,6 +11,154 @@ #include "process-util.h" #include "strv.h" +static int parse_newlink_message( + sd_netlink_message *message, + char **ret_name, + char ***ret_altnames) { + + _cleanup_strv_free_ char **altnames = NULL; + int r, ifindex; + + assert(message); + + uint16_t type; + r = sd_netlink_message_get_type(message, &type); + if (r < 0) + return r; + if (type != RTM_NEWLINK) + return -EPROTO; + + r = sd_rtnl_message_link_get_ifindex(message, &ifindex); + if (r < 0) + return r; + if (ifindex <= 0) + return -EPROTO; + + if (ret_altnames) { + r = sd_netlink_message_read_strv(message, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &altnames); + if (r < 0 && r != -ENODATA) + return r; + } + + if (ret_name) { + r = sd_netlink_message_read_string_strdup(message, IFLA_IFNAME, ret_name); + if (r < 0) + return r; + } + + if (ret_altnames) + *ret_altnames = TAKE_PTR(altnames); + + return ifindex; +} + +int rtnl_get_ifname_full(sd_netlink **rtnl, int ifindex, char **ret_name, char ***ret_altnames) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL; + int r; + + assert(ifindex > 0); + + /* This is similar to if_indextoname(), but also optionally provides alternative names. */ + + if (!rtnl) + rtnl = &our_rtnl; + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, ifindex); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, &reply); + if (r < 0) + return r; + + return parse_newlink_message(reply, ret_name, ret_altnames); +} + +int rtnl_resolve_ifname_full( + sd_netlink **rtnl, + ResolveInterfaceNameFlag flags, + const char *name, + char **ret_name, + char ***ret_altnames) { + + _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL; + int r; + + assert(name); + assert(flags > 0); + + /* This is similar to if_nametoindex(), but also resolves alternative names and decimal formatted + * ifindex too. Returns ifindex, and optionally provides the main interface name and alternative + * names.*/ + + if (!rtnl) + rtnl = &our_rtnl; + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + /* First, use IFLA_IFNAME */ + if (FLAGS_SET(flags, RESOLVE_IFNAME_MAIN) && ifname_valid(name)) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, &reply); + if (r >= 0) + return parse_newlink_message(reply, ret_name, ret_altnames); + if (r != -ENODEV) + return r; + } + + /* Next, try IFLA_ALT_IFNAME */ + if (FLAGS_SET(flags, RESOLVE_IFNAME_ALTERNATIVE) && + ifname_valid_full(name, IFNAME_VALID_ALTERNATIVE)) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(message, IFLA_ALT_IFNAME, name); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, &reply); + if (r >= 0) + return parse_newlink_message(reply, ret_name, ret_altnames); + /* The kernels older than 76c9ac0ee878f6693d398d3a95ccaf85e1f597a6 (v5.5) return -EINVAL. */ + if (!IN_SET(r, -ENODEV, -EINVAL)) + return r; + } + + /* Finally, assume the string is a decimal formatted ifindex. */ + if (FLAGS_SET(flags, RESOLVE_IFNAME_NUMERIC)) { + int ifindex; + + ifindex = parse_ifindex(name); + if (ifindex <= 0) + return -ENODEV; + + return rtnl_get_ifname_full(rtnl, ifindex, ret_name, ret_altnames); + } + + return -ENODEV; +} + static int set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; int r; @@ -20,6 +168,13 @@ static int set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { assert(name); /* Assign the requested name. */ + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); if (r < 0) return r; @@ -31,6 +186,37 @@ static int set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { return sd_netlink_call(*rtnl, message, 0, NULL); } +int rtnl_rename_link(sd_netlink **rtnl, const char *orig_name, const char *new_name) { + _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL; + int r, ifindex; + + assert(orig_name); + assert(new_name); + + /* This does not check alternative names. Callers must check the requested name is not used as an + * alternative name. */ + + if (streq(orig_name, new_name)) + return 0; + + if (!ifname_valid(new_name)) + return -EINVAL; + + if (!rtnl) + rtnl = &our_rtnl; + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + ifindex = rtnl_resolve_ifname(rtnl, orig_name); + if (ifindex < 0) + return ifindex; + + return set_link_name(rtnl, ifindex, new_name); +} + int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const *alternative_names) { _cleanup_strv_free_ char **original_altnames = NULL, **new_altnames = NULL; bool altname_deleted = false; @@ -204,38 +390,6 @@ int rtnl_set_link_properties( return 0; } -int rtnl_get_link_alternative_names(sd_netlink **rtnl, int ifindex, char ***ret) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; - _cleanup_strv_free_ char **names = NULL; - int r; - - assert(rtnl); - assert(ifindex > 0); - assert(ret); - - if (!*rtnl) { - r = sd_netlink_open(rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, ifindex); - if (r < 0) - return r; - - r = sd_netlink_call(*rtnl, message, 0, &reply); - if (r < 0) - return r; - - r = sd_netlink_message_read_strv(reply, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &names); - if (r < 0 && r != -ENODATA) - return r; - - *ret = TAKE_PTR(names); - - return 0; -} - static int rtnl_update_link_alternative_names( sd_netlink **rtnl, uint16_t nlmsg_type, @@ -336,92 +490,6 @@ int rtnl_set_link_alternative_names_by_ifname( return 0; } -int rtnl_resolve_link_alternative_name(sd_netlink **rtnl, const char *name, char **ret) { - _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL; - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; - int r, ifindex; - - assert(name); - - /* This returns ifindex and the main interface name. */ - - if (!ifname_valid_full(name, IFNAME_VALID_ALTERNATIVE)) - return -EINVAL; - - if (!rtnl) - rtnl = &our_rtnl; - if (!*rtnl) { - r = sd_netlink_open(rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, 0); - if (r < 0) - return r; - - r = sd_netlink_message_append_string(message, IFLA_ALT_IFNAME, name); - if (r < 0) - return r; - - r = sd_netlink_call(*rtnl, message, 0, &reply); - if (r == -EINVAL) - return -ENODEV; /* The device doesn't exist */ - if (r < 0) - return r; - - r = sd_rtnl_message_link_get_ifindex(reply, &ifindex); - if (r < 0) - return r; - assert(ifindex > 0); - - if (ret) { - r = sd_netlink_message_read_string_strdup(reply, IFLA_IFNAME, ret); - if (r < 0) - return r; - } - - return ifindex; -} - -int rtnl_resolve_ifname(sd_netlink **rtnl, const char *name) { - int r; - - /* Like if_nametoindex, but resolves "alternative names" too. */ - - assert(name); - - r = if_nametoindex(name); - if (r > 0) - return r; - - return rtnl_resolve_link_alternative_name(rtnl, name, NULL); -} - -int rtnl_resolve_interface(sd_netlink **rtnl, const char *name) { - int r; - - /* Like rtnl_resolve_ifname, but resolves interface numbers too. */ - - assert(name); - - r = parse_ifindex(name); - if (r > 0) - return r; - assert(r < 0); - - return rtnl_resolve_ifname(rtnl, name); -} - -int rtnl_resolve_interface_or_warn(sd_netlink **rtnl, const char *name) { - int r; - - r = rtnl_resolve_interface(rtnl, name); - if (r < 0) - return log_error_errno(r, "Failed to resolve interface \"%s\": %m", name); - return r; -} - int rtnl_get_link_info( sd_netlink **rtnl, int ifindex, @@ -441,7 +509,7 @@ int rtnl_get_link_info( assert(rtnl); assert(ifindex > 0); - if (!ret_iftype && !ret_flags) + if (!ret_iftype && !ret_flags && !ret_kind && !ret_hw_addr && !ret_permanent_hw_addr) return 0; if (!*rtnl) { @@ -575,121 +643,6 @@ int rtattr_append_attribute(struct rtattr **rta, unsigned short type, const void return 0; } -MultipathRoute *multipath_route_free(MultipathRoute *m) { - if (!m) - return NULL; - - free(m->ifname); - - return mfree(m); -} - -int multipath_route_dup(const MultipathRoute *m, MultipathRoute **ret) { - _cleanup_(multipath_route_freep) MultipathRoute *n = NULL; - _cleanup_free_ char *ifname = NULL; - - assert(m); - assert(ret); - - if (m->ifname) { - ifname = strdup(m->ifname); - if (!ifname) - return -ENOMEM; - } - - n = new(MultipathRoute, 1); - if (!n) - return -ENOMEM; - - *n = (MultipathRoute) { - .gateway = m->gateway, - .weight = m->weight, - .ifindex = m->ifindex, - .ifname = TAKE_PTR(ifname), - }; - - *ret = TAKE_PTR(n); - - return 0; -} - -int rtattr_read_nexthop(const struct rtnexthop *rtnh, size_t size, int family, OrderedSet **ret) { - _cleanup_ordered_set_free_free_ OrderedSet *set = NULL; - int r; - - assert(rtnh); - assert(IN_SET(family, AF_INET, AF_INET6)); - - if (size < sizeof(struct rtnexthop)) - return -EBADMSG; - - for (; size >= sizeof(struct rtnexthop); ) { - _cleanup_(multipath_route_freep) MultipathRoute *m = NULL; - - if (NLMSG_ALIGN(rtnh->rtnh_len) > size) - return -EBADMSG; - - if (rtnh->rtnh_len < sizeof(struct rtnexthop)) - return -EBADMSG; - - m = new(MultipathRoute, 1); - if (!m) - return -ENOMEM; - - *m = (MultipathRoute) { - .ifindex = rtnh->rtnh_ifindex, - .weight = rtnh->rtnh_hops, - }; - - if (rtnh->rtnh_len > sizeof(struct rtnexthop)) { - size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop); - - for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) { - if (attr->rta_type == RTA_GATEWAY) { - if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(family))) - return -EBADMSG; - - m->gateway.family = family; - memcpy(&m->gateway.address, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(family)); - break; - } else if (attr->rta_type == RTA_VIA) { - uint16_t gw_family; - - if (family != AF_INET) - return -EINVAL; - - if (attr->rta_len < RTA_LENGTH(sizeof(uint16_t))) - return -EBADMSG; - - gw_family = *(uint16_t *) RTA_DATA(attr); - - if (gw_family != AF_INET6) - return -EBADMSG; - - if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(gw_family) + sizeof(gw_family))) - return -EBADMSG; - - memcpy(&m->gateway, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(gw_family) + sizeof(gw_family)); - break; - } - } - } - - r = ordered_set_ensure_put(&set, NULL, m); - if (r < 0) - return r; - - TAKE_PTR(m); - - size -= NLMSG_ALIGN(rtnh->rtnh_len); - rtnh = RTNH_NEXT(rtnh); - } - - if (ret) - *ret = TAKE_PTR(set); - return 0; -} - bool netlink_pid_changed(sd_netlink *nl) { /* We don't support people creating an nl connection and * keeping it around over a fork(). Let's complain. */ diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h index 369f5d5..4ba64f0 100644 --- a/src/libsystemd/sd-netlink/netlink-util.h +++ b/src/libsystemd/sd-netlink/netlink-util.h @@ -10,24 +10,32 @@ #include "ordered-set.h" #include "socket-util.h" +#define RTA_FLAGS(rta) ((rta)->rta_type & ~NLA_TYPE_MASK) +#define RTA_TYPE(rta) ((rta)->rta_type & NLA_TYPE_MASK) + /* See struct rtvia in rtnetlink.h */ typedef struct RouteVia { uint16_t family; union in_addr_union address; } _packed_ RouteVia; -typedef struct MultipathRoute { - RouteVia gateway; - uint32_t weight; - int ifindex; - char *ifname; -} MultipathRoute; +int rtnl_get_ifname_full(sd_netlink **rtnl, int ifindex, char **ret_name, char ***ret_altnames); -MultipathRoute *multipath_route_free(MultipathRoute *m); -DEFINE_TRIVIAL_CLEANUP_FUNC(MultipathRoute*, multipath_route_free); +typedef enum ResolveInterfaceNameFlag { + RESOLVE_IFNAME_MAIN = 1 << 0, /* resolve main interface name */ + RESOLVE_IFNAME_ALTERNATIVE = 1 << 1, /* resolve alternative name */ + RESOLVE_IFNAME_NUMERIC = 1 << 2, /* resolve decimal formatted ifindex */ + _RESOLVE_IFNAME_ALL = RESOLVE_IFNAME_MAIN | RESOLVE_IFNAME_ALTERNATIVE | RESOLVE_IFNAME_NUMERIC, +} ResolveInterfaceNameFlag; -int multipath_route_dup(const MultipathRoute *m, MultipathRoute **ret); +int rtnl_resolve_ifname_full( + sd_netlink **rtnl, + ResolveInterfaceNameFlag flags, + const char *name, + char **ret_name, + char ***ret_altnames); +int rtnl_rename_link(sd_netlink **rtnl, const char *orig_name, const char *new_name); int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const* alternative_names); static inline int rtnl_append_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names) { return rtnl_set_link_name(rtnl, ifindex, NULL, alternative_names); @@ -43,14 +51,32 @@ int rtnl_set_link_properties( uint32_t mtu, uint32_t gso_max_size, size_t gso_max_segments); -int rtnl_get_link_alternative_names(sd_netlink **rtnl, int ifindex, char ***ret); +static inline int rtnl_get_link_alternative_names(sd_netlink **rtnl, int ifindex, char ***ret) { + return rtnl_get_ifname_full(rtnl, ifindex, NULL, ret); +} int rtnl_set_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names); int rtnl_set_link_alternative_names_by_ifname(sd_netlink **rtnl, const char *ifname, char* const *alternative_names); int rtnl_delete_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names); -int rtnl_resolve_link_alternative_name(sd_netlink **rtnl, const char *name, char **ret); -int rtnl_resolve_ifname(sd_netlink **rtnl, const char *name); -int rtnl_resolve_interface(sd_netlink **rtnl, const char *name); -int rtnl_resolve_interface_or_warn(sd_netlink **rtnl, const char *name); +static inline int rtnl_resolve_link_alternative_name(sd_netlink **rtnl, const char *name, char **ret) { + return rtnl_resolve_ifname_full(rtnl, RESOLVE_IFNAME_ALTERNATIVE, name, ret, NULL); +} +static inline int rtnl_resolve_ifname(sd_netlink **rtnl, const char *name) { + return rtnl_resolve_ifname_full(rtnl, RESOLVE_IFNAME_MAIN | RESOLVE_IFNAME_ALTERNATIVE, name, NULL, NULL); +} +static inline int rtnl_resolve_interface(sd_netlink **rtnl, const char *name) { + return rtnl_resolve_ifname_full(rtnl, _RESOLVE_IFNAME_ALL, name, NULL, NULL); +} +static inline int rtnl_resolve_interface_or_warn(sd_netlink **rtnl, const char *name) { + int r; + + assert(name); + + r = rtnl_resolve_interface(rtnl, name); + if (r < 0) + return log_error_errno(r, "Failed to resolve interface \"%s\": %m", name); + return r; +} + int rtnl_get_link_info( sd_netlink **rtnl, int ifindex, @@ -103,8 +129,6 @@ int netlink_message_read_in_addr_union(sd_netlink_message *m, unsigned short typ void rtattr_append_attribute_internal(struct rtattr *rta, unsigned short type, const void *data, size_t data_length); int rtattr_append_attribute(struct rtattr **rta, unsigned short type, const void *data, size_t data_length); -int rtattr_read_nexthop(const struct rtnexthop *rtnh, size_t size, int family, OrderedSet **ret); - void netlink_seal_message(sd_netlink *nl, sd_netlink_message *m); size_t netlink_get_reply_callback_count(sd_netlink *nl); diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c index b6730b7..b347ee6 100644 --- a/src/libsystemd/sd-netlink/sd-netlink.c +++ b/src/libsystemd/sd-netlink/sd-netlink.c @@ -176,7 +176,7 @@ static int dispatch_rqueue(sd_netlink *nl, sd_netlink_message **ret) { assert(nl); assert(ret); - if (ordered_set_size(nl->rqueue) <= 0) { + if (ordered_set_isempty(nl->rqueue)) { /* Try to read a new message */ r = socket_read_message(nl); if (r == -ENOBUFS) /* FIXME: ignore buffer overruns for now */ @@ -443,7 +443,7 @@ int sd_netlink_wait(sd_netlink *nl, uint64_t timeout_usec) { assert_return(nl, -EINVAL); assert_return(!netlink_pid_changed(nl), -ECHILD); - if (ordered_set_size(nl->rqueue) > 0) + if (!ordered_set_isempty(nl->rqueue)) return 0; r = netlink_poll(nl, false, timeout_usec); @@ -623,7 +623,7 @@ int sd_netlink_get_events(sd_netlink *nl) { assert_return(nl, -EINVAL); assert_return(!netlink_pid_changed(nl), -ECHILD); - return ordered_set_size(nl->rqueue) == 0 ? POLLIN : 0; + return ordered_set_isempty(nl->rqueue) ? POLLIN : 0; } int sd_netlink_get_timeout(sd_netlink *nl, uint64_t *timeout_usec) { @@ -633,7 +633,7 @@ int sd_netlink_get_timeout(sd_netlink *nl, uint64_t *timeout_usec) { assert_return(timeout_usec, -EINVAL); assert_return(!netlink_pid_changed(nl), -ECHILD); - if (ordered_set_size(nl->rqueue) > 0) { + if (!ordered_set_isempty(nl->rqueue)) { *timeout_usec = 0; return 1; } diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c index 13aedc4..cf19c48 100644 --- a/src/libsystemd/sd-netlink/test-netlink.c +++ b/src/libsystemd/sd-netlink/test-netlink.c @@ -1,5 +1,6 @@ /* 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 <netinet/ether.h> #include <netinet/in.h> @@ -681,6 +682,19 @@ TEST(rtnl_set_link_name) { _cleanup_free_ char *resolved = NULL; assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-additional-name", &resolved) == ifindex); assert_se(streq_ptr(resolved, "test-shortname")); + resolved = mfree(resolved); + + assert_se(rtnl_rename_link(&rtnl, "test-shortname", "test-shortname") >= 0); + assert_se(rtnl_rename_link(&rtnl, "test-shortname", "test-shortname2") >= 0); + assert_se(rtnl_rename_link(NULL, "test-shortname2", "test-shortname3") >= 0); + + assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-additional-name", &resolved) == ifindex); + assert_se(streq_ptr(resolved, "test-shortname3")); + resolved = mfree(resolved); + + assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-shortname3", &resolved) == ifindex); + assert_se(streq_ptr(resolved, "test-shortname3")); + resolved = mfree(resolved); } DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd/sd-network/network-util.c b/src/libsystemd/sd-network/network-util.c index 2059567..25c6e44 100644 --- a/src/libsystemd/sd-network/network-util.c +++ b/src/libsystemd/sd-network/network-util.c @@ -90,49 +90,48 @@ static const char *const link_online_state_table[_LINK_ONLINE_STATE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(link_online_state, LinkOnlineState); -int parse_operational_state_range(const char *str, LinkOperationalStateRange *out) { - LinkOperationalStateRange range = { _LINK_OPERSTATE_INVALID, _LINK_OPERSTATE_INVALID }; - _cleanup_free_ const char *min = NULL; +int parse_operational_state_range(const char *s, LinkOperationalStateRange *ret) { + LinkOperationalStateRange range = LINK_OPERSTATE_RANGE_INVALID; + _cleanup_free_ char *buf = NULL; const char *p; - assert(str); - assert(out); - - p = strchr(str, ':'); - if (p) { - min = strndup(str, p - str); + assert(s); + assert(ret); - if (!isempty(p + 1)) { - range.max = link_operstate_from_string(p + 1); - if (range.max < 0) - return -EINVAL; - } - } else - min = strdup(str); + /* allowed formats: "min", "min:", "min:max", ":max" */ - if (!min) - return -ENOMEM; + if (isempty(s) || streq(s, ":")) + return -EINVAL; - if (!isempty(min)) { - range.min = link_operstate_from_string(min); - if (range.min < 0) + p = strchr(s, ':'); + if (!p || isempty(p + 1)) + range.max = LINK_OPERSTATE_ROUTABLE; + else { + range.max = link_operstate_from_string(p + 1); + if (range.max < 0) return -EINVAL; } - /* Fail on empty strings. */ - if (range.min == _LINK_OPERSTATE_INVALID && range.max == _LINK_OPERSTATE_INVALID) - return -EINVAL; + if (p) { + buf = strndup(s, p - s); + if (!buf) + return -ENOMEM; - if (range.min == _LINK_OPERSTATE_INVALID) + s = buf; + } + + if (isempty(s)) range.min = LINK_OPERSTATE_MISSING; - if (range.max == _LINK_OPERSTATE_INVALID) - range.max = LINK_OPERSTATE_ROUTABLE; + else { + range.min = link_operstate_from_string(s); + if (range.min < 0) + return -EINVAL; + } - if (range.min > range.max) + if (!operational_state_range_is_valid(&range)) return -EINVAL; - *out = range; - + *ret = range; return 0; } diff --git a/src/libsystemd/sd-network/network-util.h b/src/libsystemd/sd-network/network-util.h index c47e271..6fc6015 100644 --- a/src/libsystemd/sd-network/network-util.h +++ b/src/libsystemd/sd-network/network-util.h @@ -79,8 +79,30 @@ typedef struct LinkOperationalStateRange { LinkOperationalState max; } LinkOperationalStateRange; -#define LINK_OPERSTATE_RANGE_DEFAULT (LinkOperationalStateRange) { LINK_OPERSTATE_DEGRADED, \ - LINK_OPERSTATE_ROUTABLE } - -int parse_operational_state_range(const char *str, LinkOperationalStateRange *out); +#define LINK_OPERSTATE_RANGE_DEFAULT \ + (const LinkOperationalStateRange) { \ + .min = LINK_OPERSTATE_DEGRADED, \ + .max = LINK_OPERSTATE_ROUTABLE, \ + } + +#define LINK_OPERSTATE_RANGE_INVALID \ + (const LinkOperationalStateRange) { \ + .min = _LINK_OPERSTATE_INVALID, \ + .max = _LINK_OPERSTATE_INVALID, \ + } + +int parse_operational_state_range(const char *s, LinkOperationalStateRange *ret); int network_link_get_operational_state(int ifindex, LinkOperationalState *ret); + +static inline bool operational_state_is_valid(LinkOperationalState s) { + return s >= 0 && s < _LINK_OPERSTATE_MAX; +} +static inline bool operational_state_range_is_valid(const LinkOperationalStateRange *range) { + return range && + operational_state_is_valid(range->min) && + operational_state_is_valid(range->max) && + range->min <= range->max; +} +static inline bool operational_state_is_in_range(LinkOperationalState s, const LinkOperationalStateRange *range) { + return range && range->min <= s && s <= range->max; +} diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c index cf3c400..9d11dce 100644 --- a/src/libsystemd/sd-network/sd-network.c +++ b/src/libsystemd/sd-network/sd-network.c @@ -394,7 +394,7 @@ int sd_network_monitor_new(sd_network_monitor **m, const char *category) { sd_network_monitor* sd_network_monitor_unref(sd_network_monitor *m) { if (m) - (void) close_nointr(MONITOR_TO_FD(m)); + (void) close(MONITOR_TO_FD(m)); return NULL; } diff --git a/src/libsystemd/sd-path/sd-path.c b/src/libsystemd/sd-path/sd-path.c index 7290d1c..8edbde9 100644 --- a/src/libsystemd/sd-path/sd-path.c +++ b/src/libsystemd/sd-path/sd-path.c @@ -443,7 +443,7 @@ _public_ int sd_path_lookup(uint64_t type, const char *suffix, char **path) { } static int search_from_environment( - char ***list, + char ***ret, const char *env_home, const char *home_suffix, const char *env_search, @@ -455,7 +455,7 @@ static int search_from_environment( char *h = NULL; int r; - assert(list); + assert(ret); if (env_search) { e = secure_getenv(env_search); @@ -465,7 +465,7 @@ static int search_from_environment( return -ENOMEM; if (env_search_sufficient) { - *list = TAKE_PTR(l); + *ret = TAKE_PTR(l); return 0; } } @@ -506,7 +506,7 @@ static int search_from_environment( return -ENOMEM; } - *list = TAKE_PTR(l); + *ret = TAKE_PTR(l); return 0; } @@ -516,15 +516,15 @@ static int search_from_environment( # define ARRAY_SBIN_BIN(x) x "bin" #endif -static int get_search(uint64_t type, char ***list) { +static int get_search(uint64_t type, char ***ret) { int r; - assert(list); + assert(ret); switch (type) { case SD_PATH_SEARCH_BINARIES: - return search_from_environment(list, + return search_from_environment(ret, NULL, ".local/bin", "PATH", @@ -534,7 +534,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_LIBRARY_PRIVATE: - return search_from_environment(list, + return search_from_environment(ret, NULL, ".local/lib", NULL, @@ -544,7 +544,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_LIBRARY_ARCH: - return search_from_environment(list, + return search_from_environment(ret, NULL, ".local/lib/" LIB_ARCH_TUPLE, "LD_LIBRARY_PATH", @@ -553,7 +553,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_SHARED: - return search_from_environment(list, + return search_from_environment(ret, "XDG_DATA_HOME", ".local/share", "XDG_DATA_DIRS", @@ -563,7 +563,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_CONFIGURATION_FACTORY: - return search_from_environment(list, + return search_from_environment(ret, NULL, NULL, NULL, @@ -573,7 +573,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_STATE_FACTORY: - return search_from_environment(list, + return search_from_environment(ret, NULL, NULL, NULL, @@ -583,7 +583,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_CONFIGURATION: - return search_from_environment(list, + return search_from_environment(ret, "XDG_CONFIG_HOME", ".config", "XDG_CONFIG_DIRS", @@ -591,12 +591,18 @@ static int get_search(uint64_t type, char ***list) { "/etc", NULL); - case SD_PATH_SEARCH_BINARIES_DEFAULT: - return strv_from_nulstr(list, DEFAULT_PATH_NULSTR); + case SD_PATH_SEARCH_BINARIES_DEFAULT: { + char **t = strv_split(default_PATH(), ":"); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; + } case SD_PATH_SYSTEMD_SEARCH_SYSTEM_UNIT: case SD_PATH_SYSTEMD_SEARCH_USER_UNIT: { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; RuntimeScope scope = type == SD_PATH_SYSTEMD_SEARCH_SYSTEM_UNIT ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER; @@ -604,7 +610,7 @@ static int get_search(uint64_t type, char ***list) { if (r < 0) return r; - *list = TAKE_PTR(lp.search_path); + *ret = TAKE_PTR(lp.search_path); return 0; } @@ -618,7 +624,7 @@ static int get_search(uint64_t type, char ***list) { if (!t) return -ENOMEM; - *list = t; + *ret = t; return 0; } @@ -631,12 +637,12 @@ static int get_search(uint64_t type, char ***list) { if (!t) return -ENOMEM; - *list = t; + *ret = t; return 0; } case SD_PATH_SYSTEMD_SEARCH_NETWORK: - return strv_from_nulstr(list, NETWORK_DIRS_NULSTR); + return strv_from_nulstr(ret, NETWORK_DIRS_NULSTR); } |