diff --git a/src/basic/ordered-set.h b/src/basic/ordered-set.h index 3ee47350b3..c0650e0158 100644 --- a/src/basic/ordered-set.h +++ b/src/basic/ordered-set.h @@ -18,6 +18,14 @@ int _ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops HA int _ordered_set_ensure_put(OrderedSet **s, const struct hash_ops *ops, void *p HASHMAP_DEBUG_PARAMS); #define ordered_set_ensure_put(s, hash_ops, key) _ordered_set_ensure_put(s, hash_ops, key HASHMAP_DEBUG_SRC_ARGS) +static inline void ordered_set_clear(OrderedSet *s) { + return ordered_hashmap_clear((OrderedHashmap*) s); +} + +static inline void ordered_set_clear_free(OrderedSet *s) { + return ordered_hashmap_clear_free((OrderedHashmap*) s); +} + static inline OrderedSet* ordered_set_free(OrderedSet *s) { return (OrderedSet*) ordered_hashmap_free((OrderedHashmap*) s); } diff --git a/src/libsystemd-network/dhcp-identifier.c b/src/libsystemd-network/dhcp-identifier.c index 4f02022cd3..ce59216c1b 100644 --- a/src/libsystemd-network/dhcp-identifier.c +++ b/src/libsystemd-network/dhcp-identifier.c @@ -8,20 +8,27 @@ #include "sd-id128.h" #include "dhcp-identifier.h" -#include "dhcp6-protocol.h" #include "netif-util.h" #include "siphash24.h" #include "sparse-endian.h" #include "stat-util.h" -#include "stdio-util.h" +#include "string-table.h" #include "udev-util.h" -#include "virt.h" #define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09) #define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03) #define USEC_2000 ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */ -int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict) { +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_validate_duid_len(DUIDType duid_type, size_t duid_len, bool strict) { struct duid d; assert_cc(sizeof(d.raw) >= MAX_DUID_LEN); @@ -57,110 +64,146 @@ int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict) { return 0; } -int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) { +static int dhcp_identifier_set_duid_llt(const uint8_t *addr, size_t addr_len, uint16_t arp_type, usec_t t, struct duid *ret_duid, size_t *ret_len) { uint16_t time_from_2000y; - assert(duid); - assert(len); assert(addr); + assert(ret_duid); + assert(ret_len); + + if (addr_len == 0) + return -EOPNOTSUPP; if (arp_type == ARPHRD_ETHER) assert_return(addr_len == ETH_ALEN, -EINVAL); else if (arp_type == ARPHRD_INFINIBAND) assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); else - return -EINVAL; + 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(&duid->type, DUID_TYPE_LLT); - unaligned_write_be16(&duid->llt.htype, arp_type); - unaligned_write_be32(&duid->llt.time, time_from_2000y); - memcpy(duid->llt.haddr, addr, addr_len); + 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, addr, addr_len); - *len = sizeof(duid->type) + sizeof(duid->llt.htype) + sizeof(duid->llt.time) + addr_len; + *ret_len = sizeof(ret_duid->type) + sizeof(ret_duid->llt.htype) + sizeof(ret_duid->llt.time) + addr_len; return 0; } -int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) { - assert(duid); - assert(len); +static int dhcp_identifier_set_duid_ll(const uint8_t *addr, size_t addr_len, uint16_t arp_type, struct duid *ret_duid, size_t *ret_len) { assert(addr); + assert(ret_duid); + assert(ret_len); + + if (addr_len == 0) + return -EOPNOTSUPP; if (arp_type == ARPHRD_ETHER) assert_return(addr_len == ETH_ALEN, -EINVAL); else if (arp_type == ARPHRD_INFINIBAND) assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); else - return -EINVAL; + return -EOPNOTSUPP; - unaligned_write_be16(&duid->type, DUID_TYPE_LL); - unaligned_write_be16(&duid->ll.htype, arp_type); - memcpy(duid->ll.haddr, addr, addr_len); + unaligned_write_be16(&ret_duid->type, DUID_TYPE_LL); + unaligned_write_be16(&ret_duid->ll.htype, arp_type); + memcpy(ret_duid->ll.haddr, addr, addr_len); - *len = sizeof(duid->type) + sizeof(duid->ll.htype) + addr_len; + *ret_len = sizeof(ret_duid->type) + sizeof(ret_duid->ll.htype) + addr_len; return 0; } -int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) { +int dhcp_identifier_set_duid_en(bool test_mode, struct duid *ret_duid, size_t *ret_len) { sd_id128_t machine_id; uint64_t hash; + int r; - assert(duid); - assert(len); + assert(ret_duid); + assert(ret_len); -#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - int r = sd_id128_get_machine(&machine_id); - if (r < 0) - return r; -#else - machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10); -#endif + 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->type, DUID_TYPE_EN); - unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN); - - *len = sizeof(duid->type) + sizeof(duid->en); + 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(duid->en.id, &hash, sizeof(duid->en.id)); + memcpy(ret_duid->en.id, &hash, sizeof(ret_duid->en.id)); + + *ret_len = sizeof(ret_duid->type) + sizeof(ret_duid->en); + + 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 *duid, size_t *len) { +static int dhcp_identifier_set_duid_uuid(struct duid *ret_duid, size_t *ret_len) { sd_id128_t machine_id; int r; - assert(duid); - assert(len); + 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(&duid->type, DUID_TYPE_UUID); - memcpy(&duid->raw.data, &machine_id, sizeof(machine_id)); + unaligned_write_be16(&ret_duid->type, DUID_TYPE_UUID); + memcpy(ret_duid->raw.data, &machine_id, sizeof(machine_id)); - *len = sizeof(duid->type) + sizeof(machine_id); + *ret_len = sizeof(ret_duid->type) + sizeof(machine_id); return 0; } +int dhcp_identifier_set_duid( + DUIDType duid_type, + const uint8_t *addr, + size_t addr_len, + uint16_t arp_type, + usec_t llt_time, + bool test_mode, + struct duid *ret_duid, + size_t *ret_len) { + + switch (duid_type) { + case DUID_TYPE_LLT: + return dhcp_identifier_set_duid_llt(addr, addr_len, arp_type, llt_time, ret_duid, ret_len); + case DUID_TYPE_EN: + return dhcp_identifier_set_duid_en(test_mode, ret_duid, ret_len); + case DUID_TYPE_LL: + return dhcp_identifier_set_duid_ll(addr, addr_len, arp_type, ret_duid, ret_len); + case DUID_TYPE_UUID: + return dhcp_identifier_set_duid_uuid(ret_duid, ret_len); + default: + return -EINVAL; + } +} + int dhcp_identifier_set_iaid( int ifindex, const uint8_t *mac, size_t mac_len, bool legacy_unstable_byteorder, bool use_mac, - void *_id) { + void *ret) { /* name is a pointer to memory in the sd_device struct, so must * have the same scope */ @@ -212,6 +255,6 @@ int dhcp_identifier_set_iaid( * behavior. */ id32 = be32toh(id32); - unaligned_write_ne32(_id, id32); + unaligned_write_ne32(ret, id32); return 0; } diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-identifier.h index 6c24af0326..697ba3bfbb 100644 --- a/src/libsystemd-network/dhcp-identifier.h +++ b/src/libsystemd-network/dhcp-identifier.h @@ -54,9 +54,23 @@ struct duid { }; } _packed_; -int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict); -int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len); -int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len); -int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len); -int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len); -int dhcp_identifier_set_iaid(int ifindex, const uint8_t *mac, size_t mac_len, bool legacy_unstable_byteorder, bool use_mac, void *_id); +int dhcp_validate_duid_len(DUIDType duid_type, size_t duid_len, bool strict); +int dhcp_identifier_set_duid_en(bool test_mode, struct duid *ret_duid, size_t *ret_len); +int dhcp_identifier_set_duid( + DUIDType duid_type, + const uint8_t *addr, + size_t addr_len, + uint16_t arp_type, + usec_t llt_time, + bool test_mode, + struct duid *ret_duid, + size_t *ret_len); +int dhcp_identifier_set_iaid( + int ifindex, + const uint8_t *mac, + size_t mac_len, + bool legacy_unstable_byteorder, + bool use_mac, + void *ret); + +const char *duid_type_to_string(DUIDType t) _const_; diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index f943409856..0d7813f613 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -11,138 +11,83 @@ #include "sd-event.h" #include "sd-dhcp6-client.h" +#include "dhcp-identifier.h" +#include "dhcp6-option.h" #include "dhcp6-protocol.h" +#include "ether-addr-util.h" #include "hashmap.h" -#include "list.h" #include "macro.h" #include "network-common.h" +#include "ordered-set.h" #include "sparse-endian.h" +#include "time-util.h" -typedef struct sd_dhcp6_option { +/* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */ +typedef enum DHCP6RequestIA { + DHCP6_REQUEST_IA_NA = 1 << 0, + DHCP6_REQUEST_IA_TA = 1 << 1, /* currently not used */ + DHCP6_REQUEST_IA_PD = 1 << 2, +} DHCP6RequestIA; + +struct sd_dhcp6_client { unsigned n_ref; - uint32_t enterprise_identifier; - uint16_t option; - void *data; - size_t length; -} sd_dhcp6_option; + int ifindex; + char *ifname; -extern const struct hash_ops dhcp6_option_hash_ops; + struct in6_addr local_address; + struct hw_addr_data hw_addr; + uint16_t arp_type; -/* Common option header */ -typedef struct DHCP6Option { - be16_t code; - be16_t len; - uint8_t data[]; -} _packed_ DHCP6Option; + sd_event *event; + sd_event_source *receive_message; + sd_event_source *timeout_resend; + sd_event_source *timeout_expire; + sd_event_source *timeout_t1; + sd_event_source *timeout_t2; + int event_priority; + int fd; -/* Address option */ -struct iaaddr { - struct in6_addr address; - be32_t lifetime_preferred; - be32_t lifetime_valid; -} _packed_; + DHCP6State state; + bool information_request; + usec_t information_request_time_usec; + usec_t information_refresh_time_usec; + be32_t transaction_id; + usec_t transaction_start; + usec_t retransmit_time; + uint8_t retransmit_count; -/* Prefix Delegation Prefix option */ -struct iapdprefix { - be32_t lifetime_preferred; - be32_t lifetime_valid; - uint8_t prefixlen; - struct in6_addr address; -} _packed_; + bool iaid_set; + DHCP6IA ia_na; + DHCP6IA ia_pd; + DHCP6RequestIA request_ia; + struct duid duid; + size_t duid_len; + be16_t *req_opts; + size_t req_opts_len; + char *fqdn; + char *mudurl; + char **user_class; + char **vendor_class; + OrderedHashmap *extra_options; + OrderedSet *vendor_options; -typedef struct DHCP6Address DHCP6Address; + struct sd_dhcp6_lease *lease; -struct DHCP6Address { - LIST_FIELDS(DHCP6Address, addresses); + sd_dhcp6_client_callback_t callback; + void *userdata; - union { - struct iaaddr iaaddr; - struct iapdprefix iapdprefix; - }; + /* Ignore ifindex when generating iaid. See dhcp_identifier_set_iaid(). */ + bool test_mode; }; -/* Non-temporary Address option */ -struct ia_na { - be32_t id; - be32_t lifetime_t1; - be32_t lifetime_t2; -} _packed_; - -/* Prefix Delegation option */ -struct ia_pd { - be32_t id; - be32_t lifetime_t1; - be32_t lifetime_t2; -} _packed_; - -/* Temporary Address option */ -struct ia_ta { - be32_t id; -} _packed_; - -typedef struct DHCP6IA { - uint16_t type; - union { - struct ia_na ia_na; - struct ia_pd ia_pd; - struct ia_ta ia_ta; - }; - - LIST_HEAD(DHCP6Address, addresses); -} DHCP6IA; - -typedef struct sd_dhcp6_client sd_dhcp6_client; - -bool dhcp6_option_can_request(uint16_t option); -int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, - size_t optlen, const void *optval); -int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia); -int dhcp6_option_append_pd(uint8_t **buf, size_t *buflen, const DHCP6IA *pd, const DHCP6Address *hint_pd_prefix); -int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn); -int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class); -int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *user_class); -int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options); - -int dhcp6_option_parse( - const uint8_t *buf, - size_t buflen, - size_t *offset, - uint16_t *ret_option_code, - size_t *ret_option_data_len, - const uint8_t **ret_option_data); -int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message); -int dhcp6_option_parse_ia( - sd_dhcp6_client *client, - be32_t iaid, - uint16_t option_code, - size_t option_data_len, - const uint8_t *option_data, - DHCP6IA *ret); -int dhcp6_option_parse_addresses( - const uint8_t *optval, - size_t optlen, - struct in6_addr **addrs, - size_t *count); -int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, char ***ret); -int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret); - 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 client_parse_message( - sd_dhcp6_client *client, - DHCP6Message *message, - size_t len, - sd_dhcp6_lease *lease); - -const char *dhcp6_message_type_to_string(int s) _const_; -int dhcp6_message_type_from_string(const char *s) _pure_; -const char *dhcp6_message_status_to_string(int s) _const_; -int dhcp6_message_status_from_string(const char *s) _pure_; - +int dhcp6_client_send_message(sd_dhcp6_client *client); void dhcp6_client_set_test_mode(sd_dhcp6_client *client, bool test_mode); +int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id); #define log_dhcp6_client_errno(client, error, fmt, ...) \ log_interface_prefix_full_errno( \ diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index 8ae2ecd6d9..1fbaab96e9 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -5,11 +5,13 @@ Copyright © 2014-2015 Intel Corporation. All rights reserved. ***/ -#include +#include #include "sd-dhcp6-lease.h" -#include "dhcp6-internal.h" +#include "dhcp6-option.h" +#include "macro.h" +#include "time-util.h" struct sd_dhcp6_lease { unsigned n_ref; @@ -21,10 +23,13 @@ struct sd_dhcp6_lease { uint8_t preference; bool rapid_commit; triple_timestamp timestamp; + usec_t lifetime_t1; + usec_t lifetime_t2; + usec_t lifetime_valid; struct in6_addr server_address; - DHCP6IA ia; - DHCP6IA pd; + DHCP6IA *ia_na; + DHCP6IA *ia_pd; DHCP6Address *addr_iter; DHCP6Address *prefix_iter; @@ -40,17 +45,15 @@ struct sd_dhcp6_lease { char *fqdn; }; -int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire); -DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia); - +int dhcp6_lease_get_lifetime(sd_dhcp6_lease *lease, usec_t *ret_t1, usec_t *ret_t2, usec_t *ret_valid); int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len); int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len); int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len); int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len); int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference); -int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference); +int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *ret); int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease); -int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit); +int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *ret); int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); @@ -59,3 +62,10 @@ int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t op int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); int dhcp6_lease_new(sd_dhcp6_lease **ret); +int dhcp6_lease_new_from_message( + sd_dhcp6_client *client, + const DHCP6Message *message, + size_t len, + const triple_timestamp *timestamp, + const struct in6_addr *server_address, + sd_dhcp6_lease **ret); diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 28fe036a40..27c907d9e9 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -9,14 +9,12 @@ #include "sd-dhcp6-client.h" #include "alloc-util.h" -#include "dhcp-identifier.h" #include "dhcp6-internal.h" -#include "dhcp6-lease-internal.h" +#include "dhcp6-option.h" #include "dhcp6-protocol.h" #include "dns-domain.h" #include "escape.h" #include "memory-util.h" -#include "sparse-endian.h" #include "strv.h" #include "unaligned.h" @@ -212,19 +210,15 @@ bool dhcp6_option_can_request(uint16_t option) { } static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, size_t optlen) { - DHCP6Option *option; - assert_return(buf, -EINVAL); assert_return(*buf, -EINVAL); assert_return(buflen, -EINVAL); - option = (DHCP6Option*) *buf; - if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data)) return -ENOBUFS; - option->code = htobe16(optcode); - option->len = htobe16(optlen); + unaligned_write_be16(*buf + offsetof(DHCP6Option, code), optcode); + unaligned_write_be16(*buf + offsetof(DHCP6Option, len), optlen); *buf += offsetof(DHCP6Option, data); *buflen -= offsetof(DHCP6Option, data); @@ -250,7 +244,7 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, return 0; } -int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) { +int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedSet *vendor_options) { sd_dhcp6_option *options; int r; @@ -259,7 +253,7 @@ int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHash assert(buflen); assert(vendor_options); - ORDERED_HASHMAP_FOREACH(options, vendor_options) { + ORDERED_SET_FOREACH(options, vendor_options) { _cleanup_free_ uint8_t *p = NULL; size_t total; @@ -282,79 +276,33 @@ int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHash return 0; } -int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) { - size_t ia_buflen, ia_addrlen = 0; - struct ia_na ia_na; - struct ia_ta ia_ta; - DHCP6Address *addr; - uint8_t *ia_hdr; - uint16_t len; - void *p; +static int option_append_ia_address(uint8_t **buf, size_t *buflen, const struct iaaddr *address) { + struct iaaddr a; int r; - assert_return(buf, -EINVAL); - assert_return(*buf, -EINVAL); - assert_return(buflen, -EINVAL); - assert_return(ia, -EINVAL); + assert(buf); + assert(*buf); + assert(buflen); + assert(address); - /* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */ + /* Do not append T1 and T2. */ + a = (struct iaaddr) { + .address = address->address, + }; - switch (ia->type) { - case SD_DHCP6_OPTION_IA_NA: - len = DHCP6_OPTION_IA_NA_LEN; - ia_na = (struct ia_na) { - .id = ia->ia_na.id, - }; - p = &ia_na; - break; + r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(struct iaaddr)); + if (r < 0) + return r; - case SD_DHCP6_OPTION_IA_TA: - len = DHCP6_OPTION_IA_TA_LEN; - ia_ta = (struct ia_ta) { - .id = ia->ia_ta.id, - }; - p = &ia_ta; - break; + memcpy(*buf, &a, sizeof(struct iaaddr)); - default: - return -EINVAL; - } + *buf += sizeof(struct iaaddr); + *buflen -= sizeof(struct iaaddr); - if (*buflen < offsetof(DHCP6Option, data) + len) - return -ENOBUFS; - - ia_hdr = *buf; - ia_buflen = *buflen; - - *buf += offsetof(DHCP6Option, data); - *buflen -= offsetof(DHCP6Option, data); - - memcpy(*buf, p, len); - - *buf += len; - *buflen -= len; - - LIST_FOREACH(addresses, addr, ia->addresses) { - struct iaaddr a = { - .address = addr->iaaddr.address, - }; - - r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(struct iaaddr)); - if (r < 0) - return r; - - memcpy(*buf, &a, sizeof(struct iaaddr)); - - *buf += sizeof(struct iaaddr); - *buflen -= sizeof(struct iaaddr); - - ia_addrlen += offsetof(DHCP6Option, data) + sizeof(struct iaaddr); - } - - return option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen); + return offsetof(DHCP6Option, data) + sizeof(struct iaaddr); } -static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const DHCP6Address *prefix) { +static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const struct iapdprefix *prefix) { struct iapdprefix p; int r; @@ -363,14 +311,13 @@ static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const DHCP6Add assert(buflen); assert(prefix); - if (prefix->iapdprefix.prefixlen == 0) + if (prefix->prefixlen == 0) return -EINVAL; /* Do not append T1 and T2. */ - p = (struct iapdprefix) { - .prefixlen = prefix->iapdprefix.prefixlen, - .address = prefix->iapdprefix.address, + .prefixlen = prefix->prefixlen, + .address = prefix->address, }; r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IA_PD_PREFIX, sizeof(struct iapdprefix)); @@ -385,57 +332,67 @@ static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const DHCP6Add return offsetof(DHCP6Option, data) + sizeof(struct iapdprefix); } -int dhcp6_option_append_pd(uint8_t **buf, size_t *buflen, const DHCP6IA *pd, const DHCP6Address *hint_pd_prefix) { - struct ia_pd ia_pd; - size_t len, pd_buflen; - uint8_t *pd_hdr; +int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) { + struct ia_header header; + const DHCP6Address *addr; + size_t ia_buflen; + uint8_t *ia_hdr; + uint16_t len; int r; assert_return(buf, -EINVAL); assert_return(*buf, -EINVAL); assert_return(buflen, -EINVAL); - assert_return(pd, -EINVAL); - assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL); + assert_return(ia, -EINVAL); - /* Do not set T1 and T2. */ - ia_pd = (struct ia_pd) { - .id = pd->ia_pd.id, - }; - len = sizeof(struct ia_pd); + /* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */ + + switch (ia->type) { + case SD_DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_PD: + len = sizeof(struct ia_header); + header = (struct ia_header) { + .id = ia->header.id, + }; + break; + + case SD_DHCP6_OPTION_IA_TA: + len = sizeof(be32_t); /* IA_TA does not have lifetime. */ + header = (struct ia_header) { + .id = ia->header.id, + }; + break; + + default: + assert_not_reached(); + } if (*buflen < offsetof(DHCP6Option, data) + len) return -ENOBUFS; - pd_hdr = *buf; - pd_buflen = *buflen; + ia_hdr = *buf; + ia_buflen = *buflen; /* The header will be written at the end of this function. */ *buf += offsetof(DHCP6Option, data); *buflen -= offsetof(DHCP6Option, data); - memcpy(*buf, &ia_pd, len); + memcpy(*buf, &header, len); + *buf += len; + *buflen -= len; - *buf += sizeof(struct ia_pd); - *buflen -= sizeof(struct ia_pd); - - DHCP6Address *prefix; - LIST_FOREACH(addresses, prefix, pd->addresses) { - r = option_append_pd_prefix(buf, buflen, prefix); + LIST_FOREACH(addresses, addr, ia->addresses) { + if (ia->type == SD_DHCP6_OPTION_IA_PD) + r = option_append_pd_prefix(buf, buflen, &addr->iapdprefix); + else + r = option_append_ia_address(buf, buflen, &addr->iaaddr); if (r < 0) return r; len += r; } - if (hint_pd_prefix && hint_pd_prefix->iapdprefix.prefixlen > 0) { - r = option_append_pd_prefix(buf, buflen, hint_pd_prefix); - if (r < 0) - return r; - - len += r; - } - - return option_append_hdr(&pd_hdr, &pd_buflen, pd->type, len); + return option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len); } int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) { @@ -548,7 +505,6 @@ int dhcp6_option_parse( size_t *ret_option_data_len, const uint8_t **ret_option_data) { - const DHCP6Option *option; size_t len; assert(buf); @@ -563,16 +519,15 @@ int dhcp6_option_parse( if (*offset >= buflen - offsetof(DHCP6Option, data)) return -EBADMSG; - option = (const DHCP6Option*) (buf + *offset); - len = be16toh(option->len); + len = unaligned_read_be16(buf + *offset + offsetof(DHCP6Option, len)); if (len > buflen - offsetof(DHCP6Option, data) - *offset) return -EBADMSG; - *offset += offsetof(DHCP6Option, data) + len; - *ret_option_code = be16toh(option->code); + *ret_option_code = unaligned_read_be16(buf + *offset + offsetof(DHCP6Option, code)); *ret_option_data_len = len; - *ret_option_data = option->data; + *ret_option_data = buf + *offset + offsetof(DHCP6Option, data); + *offset += offsetof(DHCP6Option, data) + len; return 0; } @@ -601,7 +556,7 @@ int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_s static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) { int r; - assert(buf); + assert(buf || buflen == 0); for(size_t offset = 0; offset < buflen;) { const uint8_t *data; @@ -619,15 +574,15 @@ static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t r = dhcp6_option_parse_status(data, data_len, &msg); if (r == -ENOMEM) return r; - if (r < 0) - /* Let's log but ignore the invalid status option. */ - log_dhcp6_client_errno(client, r, - "Received an IA address or PD prefix option with an invalid status sub option, ignoring: %m"); - else if (r > 0) + if (r > 0) return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Received an IA address or PD prefix option with non-zero status: %s%s%s", strempty(msg), isempty(msg) ? "" : ": ", dhcp6_message_status_to_string(r)); + if (r < 0) + /* Let's log but ignore the invalid status option. */ + log_dhcp6_client_errno(client, r, + "Received an IA address or PD prefix option with an invalid status sub option, ignoring: %m"); break; } default: @@ -638,19 +593,29 @@ static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t return 0; } -static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, const uint8_t *data, size_t len, DHCP6Address **ret) { +static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) { + _cleanup_free_ DHCP6Address *a = NULL; uint32_t lt_valid, lt_pref; - DHCP6Address *a; int r; - assert(data); - assert(ret); + assert(ia); + assert(data || len == 0); + + if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received an IA address sub-option in an invalid option, ignoring."); if (len < sizeof(struct iaaddr)) return -EBADMSG; - lt_valid = be32toh(((const struct iaaddr*) data)->lifetime_valid); - lt_pref = be32toh(((const struct iaaddr*) data)->lifetime_preferred); + a = new(DHCP6Address, 1); + if (!a) + return -ENOMEM; + + memcpy(&a->iaaddr, data, sizeof(struct iaaddr)); + + lt_valid = be32toh(a->iaaddr.lifetime_valid); + lt_pref = be32toh(a->iaaddr.lifetime_preferred); if (lt_valid == 0) return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), @@ -667,27 +632,33 @@ static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, const uint8_t return r; } + LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a)); + return 0; +} + +static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) { + _cleanup_free_ DHCP6Address *a = NULL; + uint32_t lt_valid, lt_pref; + int r; + + assert(ia); + assert(data || len == 0); + + if (ia->type != SD_DHCP6_OPTION_IA_PD) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received an PD prefix sub-option in an invalid option, ignoring"); + + if (len < sizeof(struct iapdprefix)) + return -EBADMSG; + a = new(DHCP6Address, 1); if (!a) return -ENOMEM; - LIST_INIT(addresses, a); - memcpy(&a->iaaddr, data, sizeof(struct iaaddr)); + memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix)); - *ret = a; - return 0; -} - -static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, const uint8_t *data, size_t len, DHCP6Address **ret) { - uint32_t lt_valid, lt_pref; - DHCP6Address *a; - int r; - - if (len < sizeof(struct iapdprefix)) - return -ENOMSG; - - lt_valid = be32toh(((const struct iapdprefix*) data)->lifetime_valid); - lt_pref = be32toh(((const struct iapdprefix*) data)->lifetime_preferred); + lt_valid = be32toh(a->iapdprefix.lifetime_valid); + lt_pref = be32toh(a->iapdprefix.lifetime_preferred); if (lt_valid == 0) return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), @@ -704,14 +675,7 @@ static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, const uint8_t return r; } - a = new(DHCP6Address, 1); - if (!a) - return -ENOMEM; - - LIST_INIT(addresses, a); - memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix)); - - *ret = a; + LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a)); return 0; } @@ -721,16 +685,15 @@ int dhcp6_option_parse_ia( uint16_t option_code, size_t option_data_len, const uint8_t *option_data, - DHCP6IA *ret) { + DHCP6IA **ret) { - _cleanup_(dhcp6_lease_free_ia) DHCP6IA ia = {}; - uint32_t lt_t1, lt_t2, lt_min = UINT32_MAX; - be32_t received_iaid; - size_t offset; + _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL; + uint32_t lt_t1, lt_t2; + size_t header_len; int r; assert(IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA, SD_DHCP6_OPTION_IA_PD)); - assert(option_data); + assert(option_data || option_data_len == 0); assert(ret); /* This will return the following: @@ -743,56 +706,51 @@ int dhcp6_option_parse_ia( switch (option_code) { case SD_DHCP6_OPTION_IA_NA: - - if (option_data_len < DHCP6_OPTION_IA_NA_LEN) - return -EBADMSG; - - offset = DHCP6_OPTION_IA_NA_LEN; - - received_iaid = ((const struct ia_na*) option_data)->id; - lt_t1 = be32toh(((const struct ia_na*) option_data)->lifetime_t1); - lt_t2 = be32toh(((const struct ia_na*) option_data)->lifetime_t2); - break; - case SD_DHCP6_OPTION_IA_PD: - - if (option_data_len < DHCP6_OPTION_IA_PD_LEN) - return -EBADMSG; - - offset = DHCP6_OPTION_IA_PD_LEN; - - received_iaid = ((const struct ia_pd*) option_data)->id; - lt_t1 = be32toh(((const struct ia_pd*) option_data)->lifetime_t1); - lt_t2 = be32toh(((const struct ia_pd*) option_data)->lifetime_t2); + header_len = sizeof(struct ia_header); break; case SD_DHCP6_OPTION_IA_TA: - if (option_data_len < DHCP6_OPTION_IA_TA_LEN) - return -ENOMSG; - - offset = DHCP6_OPTION_IA_TA_LEN; - - received_iaid = ((const struct ia_ta*) option_data)->id; - lt_t1 = lt_t2 = 0; /* No lifetime for IA_TA. */ + header_len = sizeof(be32_t); /* IA_TA does not have lifetime. */ break; default: assert_not_reached(); } + if (option_data_len < header_len) + return -EBADMSG; + + ia = new(DHCP6IA, 1); + if (!ia) + return -ENOMEM; + + *ia = (DHCP6IA) { + .type = option_code, + }; + memcpy(&ia->header, option_data, header_len); + /* According to RFC8415, IAs which do not match the client's IAID should be ignored, * but not necessary to ignore or refuse the whole message. */ - if (received_iaid != iaid) + if (ia->header.id != iaid) return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENOANO), "Received an IA option with a different IAID " "from the one chosen by the client, ignoring."); + /* It is not necessary to check if the lifetime_t2 is zero here, as in that case it will be updated later. */ + lt_t1 = be32toh(ia->header.lifetime_t1); + lt_t2 = be32toh(ia->header.lifetime_t2); + if (lt_t1 > lt_t2) return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Received an IA option with T1 %"PRIu32"sec > T2 %"PRIu32"sec, ignoring.", lt_t1, lt_t2); + if (lt_t1 == 0 && lt_t2 > 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received an IA option with zero T1 and non-zero T2 (%"PRIu32"sec), ignoring.", + lt_t2); - for (; offset < option_data_len;) { + for (size_t offset = header_len; offset < option_data_len;) { const uint8_t *subdata; size_t subdata_len; uint16_t subopt; @@ -803,41 +761,19 @@ int dhcp6_option_parse_ia( switch (subopt) { case SD_DHCP6_OPTION_IAADDR: { - DHCP6Address *a; - - if (!IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) { - log_dhcp6_client(client, "Received an IA_PD option with an IA address, ignoring."); - continue; - } - - r = dhcp6_option_parse_ia_address(client, subdata, subdata_len, &a); + r = dhcp6_option_parse_ia_address(client, ia, subdata, subdata_len); if (r == -ENOMEM) return r; - if (r < 0) - /* Ignore the sub-option on non-critical errors. */ - continue; - lt_min = MIN(lt_min, be32toh(a->iaaddr.lifetime_valid)); - LIST_PREPEND(addresses, ia.addresses, a); + /* Ignore non-critical errors in the sub-option. */ break; } case SD_DHCP6_OPTION_IA_PD_PREFIX: { - DHCP6Address *a; - - if (option_code != SD_DHCP6_OPTION_IA_PD) { - log_dhcp6_client(client, "Received an IA_NA or IA_TA option with an PD prefix, ignoring"); - continue; - } - - r = dhcp6_option_parse_ia_pdprefix(client, subdata, subdata_len, &a); + r = dhcp6_option_parse_ia_pdprefix(client, ia, subdata, subdata_len); if (r == -ENOMEM) return r; - if (r < 0) - /* Ignore the sub-option on non-critical errors. */ - continue; - lt_min = MIN(lt_min, be32toh(a->iapdprefix.lifetime_valid)); - LIST_PREPEND(addresses, ia.addresses, a); + /* Ignore non-critical errors in the sub-option. */ break; } case SD_DHCP6_OPTION_STATUS_CODE: { @@ -846,14 +782,14 @@ int dhcp6_option_parse_ia( r = dhcp6_option_parse_status(subdata, subdata_len, &msg); if (r == -ENOMEM) return r; - if (r < 0) - log_dhcp6_client_errno(client, r, - "Received an IA option with an invalid status sub option, ignoring: %m"); - else if (r > 0) + if (r > 0) return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Received an IA option with non-zero status: %s%s%s", strempty(msg), isempty(msg) ? "" : ": ", dhcp6_message_status_to_string(r)); + if (r < 0) + log_dhcp6_client_errno(client, r, + "Received an IA option with an invalid status sub option, ignoring: %m"); break; } default: @@ -861,50 +797,11 @@ int dhcp6_option_parse_ia( } } - if (!ia.addresses) + if (!ia->addresses) return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENODATA), "Received an IA option without valid IA addresses or PD prefixes, ignoring."); - if (IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_PD) && - lt_t1 == 0 && lt_t2 == 0 && lt_min != UINT32_MAX) { - lt_t1 = lt_min / 2; - lt_t2 = lt_min / 10 * 8; - - log_dhcp6_client(client, "Received an IA option with both T1 and T2 equal to zero. " - "Adjusting them based on the minimum valid lifetime of IA addresses or PD prefixes: " - "T1=%"PRIu32"sec, T2=%"PRIu32"sec", lt_t1, lt_t2); - } - - switch(option_code) { - case SD_DHCP6_OPTION_IA_NA: - *ret = (DHCP6IA) { - .type = option_code, - .ia_na.id = iaid, - .ia_na.lifetime_t1 = htobe32(lt_t1), - .ia_na.lifetime_t2 = htobe32(lt_t2), - .addresses = TAKE_PTR(ia.addresses), - }; - break; - case SD_DHCP6_OPTION_IA_TA: - *ret = (DHCP6IA) { - .type = option_code, - .ia_ta.id = iaid, - .addresses = TAKE_PTR(ia.addresses), - }; - break; - case SD_DHCP6_OPTION_IA_PD: - *ret = (DHCP6IA) { - .type = option_code, - .ia_pd.id = iaid, - .ia_pd.lifetime_t1 = htobe32(lt_t1), - .ia_pd.lifetime_t2 = htobe32(lt_t2), - .addresses = TAKE_PTR(ia.addresses), - }; - break; - default: - assert_not_reached(); - } - + *ret = TAKE_PTR(ia); return 0; } diff --git a/src/libsystemd-network/dhcp6-option.h b/src/libsystemd-network/dhcp6-option.h new file mode 100644 index 0000000000..80aba7f37f --- /dev/null +++ b/src/libsystemd-network/dhcp6-option.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-dhcp6-client.h" + +#include "hash-funcs.h" +#include "list.h" +#include "macro.h" +#include "ordered-set.h" +#include "sparse-endian.h" + +typedef struct sd_dhcp6_option { + unsigned n_ref; + + uint32_t enterprise_identifier; + uint16_t option; + void *data; + size_t length; +} sd_dhcp6_option; + +extern const struct hash_ops dhcp6_option_hash_ops; + +/* Common option header */ +typedef struct DHCP6Option { + be16_t code; + be16_t len; + uint8_t data[]; +} _packed_ DHCP6Option; + +/* Address option */ +struct iaaddr { + struct in6_addr address; + be32_t lifetime_preferred; + be32_t lifetime_valid; +} _packed_; + +/* Prefix Delegation Prefix option */ +struct iapdprefix { + be32_t lifetime_preferred; + be32_t lifetime_valid; + uint8_t prefixlen; + struct in6_addr address; +} _packed_; + +typedef struct DHCP6Address DHCP6Address; + +struct DHCP6Address { + LIST_FIELDS(DHCP6Address, addresses); + + union { + struct iaaddr iaaddr; + struct iapdprefix iapdprefix; + }; +}; + +struct ia_header { + be32_t id; + be32_t lifetime_t1; + be32_t lifetime_t2; +} _packed_; + +typedef struct DHCP6IA { + uint16_t type; + struct ia_header header; + + LIST_HEAD(DHCP6Address, addresses); +} DHCP6IA; + +void dhcp6_ia_clear_addresses(DHCP6IA *ia); +DHCP6IA *dhcp6_ia_free(DHCP6IA *ia); +DEFINE_TRIVIAL_CLEANUP_FUNC(DHCP6IA*, dhcp6_ia_free); + +bool dhcp6_option_can_request(uint16_t option); + +int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, + size_t optlen, const void *optval); +int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia); +int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn); +int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class); +int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *user_class); +int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedSet *vendor_options); + +int dhcp6_option_parse( + const uint8_t *buf, + size_t buflen, + size_t *offset, + uint16_t *ret_option_code, + size_t *ret_option_data_len, + const uint8_t **ret_option_data); +int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message); +int dhcp6_option_parse_ia( + sd_dhcp6_client *client, + be32_t iaid, + uint16_t option_code, + size_t option_data_len, + const uint8_t *option_data, + DHCP6IA **ret); +int dhcp6_option_parse_addresses( + const uint8_t *optval, + size_t optlen, + struct in6_addr **addrs, + size_t *count); +int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, char ***ret); +int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret); diff --git a/src/libsystemd-network/dhcp6-protocol.c b/src/libsystemd-network/dhcp6-protocol.c new file mode 100644 index 0000000000..c399a7ac50 --- /dev/null +++ b/src/libsystemd-network/dhcp6-protocol.c @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dhcp6-protocol.h" +#include "string-table.h" + +static const char * const dhcp6_state_table[_DHCP6_STATE_MAX] = { + [DHCP6_STATE_STOPPED] = "stopped", + [DHCP6_STATE_INFORMATION_REQUEST] = "information-request", + [DHCP6_STATE_SOLICITATION] = "solicitation", + [DHCP6_STATE_REQUEST] = "request", + [DHCP6_STATE_BOUND] = "bound", + [DHCP6_STATE_RENEW] = "renew", + [DHCP6_STATE_REBIND] = "rebind", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_state, DHCP6State); + +static const char * const dhcp6_message_type_table[_DHCP6_MESSAGE_TYPE_MAX] = { + [DHCP6_MESSAGE_SOLICIT] = "Solicit", + [DHCP6_MESSAGE_ADVERTISE] = "Advertise", + [DHCP6_MESSAGE_REQUEST] = "Request", + [DHCP6_MESSAGE_CONFIRM] = "Confirm", + [DHCP6_MESSAGE_RENEW] = "Renew", + [DHCP6_MESSAGE_REBIND] = "Rebind", + [DHCP6_MESSAGE_REPLY] = "Reply", + [DHCP6_MESSAGE_RELEASE] = "Release", + [DHCP6_MESSAGE_DECLINE] = "Decline", + [DHCP6_MESSAGE_RECONFIGURE] = "Reconfigure", + [DHCP6_MESSAGE_INFORMATION_REQUEST] = "Information Request", + [DHCP6_MESSAGE_RELAY_FORWARD] = "Relay Forward", + [DHCP6_MESSAGE_RELAY_REPLY] = "Relay Reply", + [DHCP6_MESSAGE_LEASE_QUERY] = "Lease Query", + [DHCP6_MESSAGE_LEASE_QUERY_REPLY] = "Lease Query Reply", + [DHCP6_MESSAGE_LEASE_QUERY_DONE] = "Lease Query Done", + [DHCP6_MESSAGE_LEASE_QUERY_DATA] = "Lease Query Data", + [DHCP6_MESSAGE_RECONFIGURE_REQUEST] = "Reconfigure Request", + [DHCP6_MESSAGE_RECONFIGURE_REPLY] = "Reconfigure Reply", + [DHCP6_MESSAGE_DHCPV4_QUERY] = "DHCPv4 Query", + [DHCP6_MESSAGE_DHCPV4_RESPONSE] = "DHCPv4 Response", + [DHCP6_MESSAGE_ACTIVE_LEASE_QUERY] = "Active Lease Query", + [DHCP6_MESSAGE_START_TLS] = "Start TLS", + [DHCP6_MESSAGE_BINDING_UPDATE] = "Binding Update", + [DHCP6_MESSAGE_BINDING_REPLY] = "Binding Reply", + [DHCP6_MESSAGE_POOL_REQUEST] = "Pool Request", + [DHCP6_MESSAGE_POOL_RESPONSE] = "Pool Response", + [DHCP6_MESSAGE_UPDATE_REQUEST] = "Update Request", + [DHCP6_MESSAGE_UPDATE_REQUEST_ALL] = "Update Request All", + [DHCP6_MESSAGE_UPDATE_DONE] = "Update Done", + [DHCP6_MESSAGE_CONNECT] = "Connect", + [DHCP6_MESSAGE_CONNECT_REPLY] = "Connect Reply", + [DHCP6_MESSAGE_DISCONNECT] = "Disconnect", + [DHCP6_MESSAGE_STATE] = "State", + [DHCP6_MESSAGE_CONTACT] = "Contact", +}; + +DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, DHCP6MessageType); + +static const char * const dhcp6_message_status_table[_DHCP6_STATUS_MAX] = { + [DHCP6_STATUS_SUCCESS] = "Success", + [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure", + [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available", + [DHCP6_STATUS_NO_BINDING] = "Binding unavailable", + [DHCP6_STATUS_NOT_ON_LINK] = "Not on link", + [DHCP6_STATUS_USE_MULTICAST] = "Use multicast", + [DHCP6_STATUS_NO_PREFIX_AVAIL] = "No prefix available", + [DHCP6_STATUS_UNKNOWN_QUERY_TYPE] = "Unknown query type", + [DHCP6_STATUS_MALFORMED_QUERY] = "Malformed query", + [DHCP6_STATUS_NOT_CONFIGURED] = "Not configured", + [DHCP6_STATUS_NOT_ALLOWED] = "Not allowed", + [DHCP6_STATUS_QUERY_TERMINATED] = "Query terminated", + [DHCP6_STATUS_DATA_MISSING] = "Data missing", + [DHCP6_STATUS_CATCHUP_COMPLETE] = "Catch up complete", + [DHCP6_STATUS_NOT_SUPPORTED] = "Not supported", + [DHCP6_STATUS_TLS_CONNECTION_REFUSED] = "TLS connection refused", + [DHCP6_STATUS_ADDRESS_IN_USE] = "Address in use", + [DHCP6_STATUS_CONFIGURATION_CONFLICT] = "Configuration conflict", + [DHCP6_STATUS_MISSING_BINDING_INFORMATION] = "Missing binding information", + [DHCP6_STATUS_OUTDATED_BINDING_INFORMATION] = "Outdated binding information", + [DHCP6_STATUS_SERVER_SHUTTING_DOWN] = "Server shutting down", + [DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED] = "DNS update not supported", + [DHCP6_STATUS_EXCESSIVE_TIME_SKEW] = "Excessive time skew", +}; + +DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, DHCP6Status); diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h index 5d2af439e2..f4e47857e3 100644 --- a/src/libsystemd-network/dhcp6-protocol.h +++ b/src/libsystemd-network/dhcp6-protocol.h @@ -28,8 +28,8 @@ 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, \ +#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } } enum { @@ -100,13 +100,15 @@ typedef enum DHCP6MessageType { DHCP6_MESSAGE_STATE = 34, /* RFC 8156 */ DHCP6_MESSAGE_CONTACT = 35, /* RFC 8156 */ _DHCP6_MESSAGE_TYPE_MAX, - _DHCP6_MESSAGE_TYPE_INVALID = -EINVAL, + _DHCP6_MESSAGE_TYPE_INVALID = -EINVAL, } DHCP6MessageType; typedef enum DHCP6NTPSubOption { DHCP6_NTP_SUBOPTION_SRV_ADDR = 1, DHCP6_NTP_SUBOPTION_MC_ADDR = 2, DHCP6_NTP_SUBOPTION_SRV_FQDN = 3, + _DHCP6_NTP_SUBOPTION_MAX, + _DHCP6_NTP_SUBOPTION_INVALID = -EINVAL, } DHCP6NTPSubOption; /* @@ -138,7 +140,7 @@ typedef enum DHCP6Status { DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED = 21, DHCP6_STATUS_EXCESSIVE_TIME_SKEW = 22, _DHCP6_STATUS_MAX, - _DHCP6_STATUS_INVALID = -EINVAL, + _DHCP6_STATUS_INVALID = -EINVAL, } DHCP6Status; typedef enum DHCP6FQDNFlag { @@ -146,3 +148,9 @@ typedef enum DHCP6FQDNFlag { DHCP6_FQDN_FLAG_O = 1 << 1, DHCP6_FQDN_FLAG_N = 1 << 2, } DHCP6FQDNFlag; + +const char *dhcp6_state_to_string(DHCP6State s) _const_; +const char *dhcp6_message_type_to_string(DHCP6MessageType s) _const_; +DHCP6MessageType dhcp6_message_type_from_string(const char *s) _pure_; +const char *dhcp6_message_status_to_string(DHCP6Status s) _const_; +DHCP6Status dhcp6_message_status_from_string(const char *s) _pure_; diff --git a/src/libsystemd-network/fuzz-dhcp6-client-send.c b/src/libsystemd-network/fuzz-dhcp6-client-send.c deleted file mode 100644 index 39a5f4fd4d..0000000000 --- a/src/libsystemd-network/fuzz-dhcp6-client-send.c +++ /dev/null @@ -1,61 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "fuzz.h" - -#include "sd-dhcp6-client.c" - -int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, - const void *packet, size_t len) { - return len; -} - -int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) { - int fd; - - fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - assert_se(fd >= 0); - - return fd; -} - -int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; - struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } }; - triple_timestamp t = {}; - usec_t time_now; - int r; - - if (size < sizeof(DHCP6Message)) - return 0; - - assert_se(sd_event_new(&e) >= 0); - assert_se(sd_dhcp6_client_new(&client) >= 0); - assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0); - assert_se(sd_dhcp6_client_set_ifindex(client, 42) == 0); - assert_se(sd_dhcp6_client_set_fqdn(client, "example.com") == 1); - assert_se(sd_dhcp6_client_set_request_mud_url(client, "https://www.example.com/mudfile.json") >= 0); - assert_se(sd_dhcp6_client_set_request_user_class(client, STRV_MAKE("u1", "u2", "u3")) >= 0); - assert_se(sd_dhcp6_client_set_request_vendor_class(client, STRV_MAKE("v1", "v2", "v3")) >= 0); - assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0); - assert_se(sd_dhcp6_client_set_information_request(client, false) == 0); - dhcp6_client_set_test_mode(client, true); - assert_se(sd_dhcp6_client_start(client) >= 0); - assert_se(sd_dhcp6_client_set_transaction_id(client, htobe32(0x00ffffff) & ((const DHCP6Message *) data)->transaction_id) == 0); - - triple_timestamp_get(&t); - r = client_receive_advertise(client, (DHCP6Message *) data, size, &t, NULL); - if (r < 0) - goto cleanup; - - r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); - if (r < 0) - goto cleanup; - - if (r == DHCP6_STATE_REQUEST) - client->state = DHCP6_STATE_REQUEST; - (void) client_send_message(client, time_now); -cleanup: - assert_se(sd_dhcp6_client_stop(client) >= 0); - return 0; -} diff --git a/src/libsystemd-network/fuzz-dhcp6-client.c b/src/libsystemd-network/fuzz-dhcp6-client.c index f62ab468df..32e35510e5 100644 --- a/src/libsystemd-network/fuzz-dhcp6-client.c +++ b/src/libsystemd-network/fuzz-dhcp6-client.c @@ -6,43 +6,59 @@ #include "sd-event.h" #include "dhcp6-internal.h" -#include "dhcp6-protocol.h" +#include "event-util.h" #include "fd-util.h" #include "fuzz.h" static int test_dhcp_fd[2] = { -1, -1 }; -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, struct in6_addr *server_address, const void *packet, size_t len) { return len; } int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) { assert_se(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_dhcp_fd) >= 0); - return test_dhcp_fd[0]; + return TAKE_FD(test_dhcp_fd[0]); } -static void fuzz_client(const uint8_t *data, size_t size, bool is_information_request_enabled) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; - struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } }; - - assert_se(sd_event_new(&e) >= 0); - assert_se(sd_dhcp6_client_new(&client) >= 0); - assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0); - assert_se(sd_dhcp6_client_set_ifindex(client, 42) == 0); - assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0); - assert_se(sd_dhcp6_client_set_information_request(client, is_information_request_enabled) == 0); - dhcp6_client_set_test_mode(client, true); - +static void fuzz_client(sd_dhcp6_client *client, const uint8_t *data, size_t size, DHCP6State state) { + assert_se(sd_dhcp6_client_set_information_request(client, state == DHCP6_STATE_INFORMATION_REQUEST) >= 0); assert_se(sd_dhcp6_client_start(client) >= 0); + client->state = state; + if (size >= sizeof(DHCP6Message)) - assert_se(sd_dhcp6_client_set_transaction_id(client, htobe32(0x00ffffff) & ((const DHCP6Message *) data)->transaction_id) == 0); + assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message *) data)->transaction_id) == 0); + + /* These states does not require lease to send message. */ + if (IN_SET(client->state, DHCP6_STATE_INFORMATION_REQUEST, DHCP6_STATE_SOLICITATION)) + assert_se(dhcp6_client_send_message(client) >= 0); assert_se(write(test_dhcp_fd[1], data, size) == (ssize_t) size); - sd_event_run(e, UINT64_MAX); + assert_se(sd_event_run(sd_dhcp6_client_get_event(client), UINT64_MAX) > 0); + + /* Check the state transition. */ + if (client->state != state) + switch (state) { + case DHCP6_STATE_INFORMATION_REQUEST: + assert_se(client->state == DHCP6_STATE_STOPPED); + break; + case DHCP6_STATE_SOLICITATION: + assert_se(IN_SET(client->state, DHCP6_STATE_REQUEST, DHCP6_STATE_BOUND)); + break; + case DHCP6_STATE_REQUEST: + assert_se(client->state == DHCP6_STATE_BOUND); + break; + default: + assert_not_reached(); + } + + /* Send message if the client has a lease. */ + if (state != DHCP6_STATE_INFORMATION_REQUEST && sd_dhcp6_client_get_lease(client, NULL) >= 0) { + client->state = DHCP6_STATE_REQUEST; + dhcp6_client_send_message(client); + } assert_se(sd_dhcp6_client_stop(client) >= 0); @@ -50,14 +66,43 @@ static void fuzz_client(const uint8_t *data, size_t size, bool is_information_re } int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; + _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *v1 = NULL, *v2 = NULL; + struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } }; + struct in6_addr hint = { { { 0x3f, 0xfe, 0x05, 0x01, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } }; + static const char *v1_data = "hogehoge", *v2_data = "foobar"; + if (size > 65536) return 0; - /* This triggers client_receive_advertise */ - fuzz_client(data, size, false); + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_dhcp6_client_new(&client) >= 0); + assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0); + assert_se(sd_dhcp6_client_set_ifindex(client, 42) >= 0); + assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0); + dhcp6_client_set_test_mode(client, true); - /* This triggers client_receive_reply */ - fuzz_client(data, size, true); + /* Used when sending message. */ + assert_se(sd_dhcp6_client_set_fqdn(client, "example.com") == 1); + assert_se(sd_dhcp6_client_set_request_mud_url(client, "https://www.example.com/mudfile.json") >= 0); + assert_se(sd_dhcp6_client_set_request_user_class(client, STRV_MAKE("u1", "u2", "u3")) >= 0); + assert_se(sd_dhcp6_client_set_request_vendor_class(client, STRV_MAKE("v1", "v2", "v3")) >= 0); + assert_se(sd_dhcp6_client_set_prefix_delegation_hint(client, 48, &hint) >= 0); + assert_se(sd_dhcp6_option_new(123, v1_data, strlen(v1_data), 12345, &v1) >= 0); + assert_se(sd_dhcp6_option_new(456, v2_data, strlen(v2_data), 45678, &v2) >= 0); + assert_se(sd_dhcp6_client_add_vendor_option(client, v1) >= 0); + assert_se(sd_dhcp6_client_add_vendor_option(client, v2) >= 0); + + fuzz_client(client, data, size, DHCP6_STATE_INFORMATION_REQUEST); + fuzz_client(client, data, size, DHCP6_STATE_SOLICITATION); + + /* If size is zero, then the resend timer will be triggered at first, + * but in the REQUEST state the client must have a lease. */ + if (size == 0) + return 0; + + fuzz_client(client, data, size, DHCP6_STATE_REQUEST); return 0; } diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index a44e8c0824..63ac8165b7 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -17,6 +17,8 @@ sources = files(''' dhcp6-lease-internal.h dhcp6-network.c dhcp6-option.c + dhcp6-option.h + dhcp6-protocol.c dhcp6-protocol.h icmp6-util.c icmp6-util.h @@ -113,10 +115,6 @@ fuzzers += [ [libshared, libsystemd_network]], - [files('fuzz-dhcp6-client-send.c'), - [libshared, - libsystemd_network]], - [files('fuzz-dhcp-server.c'), [libsystemd_network, libshared]], diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 7b296ae4a0..84ae5cddd6 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -450,7 +450,7 @@ static int dhcp_client_set_iaid_duid_internal( bool iaid_append, bool iaid_set, uint32_t iaid, - uint16_t duid_type, + DUIDType duid_type, const void *duid, size_t duid_len, usec_t llt_time) { @@ -489,37 +489,20 @@ static int dhcp_client_set_iaid_duid_internal( client->client_id.ns.duid.type = htobe16(duid_type); memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len); len = sizeof(client->client_id.ns.duid.type) + duid_len; - } else - switch (duid_type) { - case DUID_TYPE_LLT: - if (client->mac_addr_len == 0) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set."); - r = dhcp_identifier_set_duid_llt(&client->client_id.ns.duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &len); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to set DUID-LLT: %m"); - break; - case DUID_TYPE_EN: - r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to set DUID-EN: %m"); - break; - case DUID_TYPE_LL: - if (client->mac_addr_len == 0) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set."); - - r = dhcp_identifier_set_duid_ll(&client->client_id.ns.duid, client->mac_addr, client->mac_addr_len, client->arp_type, &len); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to set DUID-LL: %m"); - break; - case DUID_TYPE_UUID: - r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to set DUID-UUID: %m"); - break; - default: - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type"); - } + } else { + r = dhcp_identifier_set_duid(duid_type, client->mac_addr, client->mac_addr_len, + client->arp_type, llt_time, client->test_mode, + &client->client_id.ns.duid, &len); + if (r == -EOPNOTSUPP) + return log_dhcp_client_errno(client, r, + "Failed to set %s. MAC address is not set or " + "interface type is not supported.", + duid_type_to_string(duid_type)); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to set %s: %m", + duid_type_to_string(duid_type)); + } client->client_id_len = sizeof(client->client_id.type) + len + (iaid_append ? sizeof(client->client_id.ns.iaid) : 0); @@ -876,7 +859,7 @@ static int client_message_init( if (r < 0) return r; - r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &duid_len); + r = dhcp_identifier_set_duid_en(client->test_mode, &client->client_id.ns.duid, &duid_len); if (r < 0) return r; diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 84bc739bba..163a208a44 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -14,7 +14,6 @@ #include "dhcp-identifier.h" #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" -#include "dhcp6-protocol.h" #include "dns-domain.h" #include "event-util.h" #include "fd-util.h" @@ -22,74 +21,11 @@ #include "hostname-util.h" #include "in-addr-util.h" #include "io-util.h" -#include "network-common.h" #include "random-util.h" #include "socket-util.h" -#include "string-table.h" #include "strv.h" -#include "util.h" #include "web-util.h" -#define MAX_MAC_ADDR_LEN INFINIBAND_ALEN - -#define IRT_DEFAULT (1 * USEC_PER_DAY) -#define IRT_MINIMUM (600 * USEC_PER_SEC) - -/* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */ -typedef enum DHCP6RequestIA { - DHCP6_REQUEST_IA_NA = 1 << 0, - DHCP6_REQUEST_IA_TA = 1 << 1, /* currently not used */ - DHCP6_REQUEST_IA_PD = 1 << 2, -} DHCP6RequestIA; - -struct sd_dhcp6_client { - unsigned n_ref; - - DHCP6State state; - sd_event *event; - int event_priority; - int ifindex; - char *ifname; - DHCP6Address hint_pd_prefix; - struct in6_addr local_address; - uint8_t mac_addr[MAX_MAC_ADDR_LEN]; - size_t mac_addr_len; - uint16_t arp_type; - DHCP6IA ia_na; - DHCP6IA ia_pd; - sd_event_source *timeout_t1; - sd_event_source *timeout_t2; - DHCP6RequestIA request_ia; - be32_t transaction_id; - usec_t transaction_start; - struct sd_dhcp6_lease *lease; - int fd; - bool information_request; - bool iaid_set; - be16_t *req_opts; - size_t req_opts_len; - char *fqdn; - char *mudurl; - char **user_class; - char **vendor_class; - sd_event_source *receive_message; - usec_t retransmit_time; - uint8_t retransmit_count; - sd_event_source *timeout_resend; - sd_event_source *timeout_resend_expire; - sd_dhcp6_client_callback_t callback; - void *userdata; - struct duid duid; - size_t duid_len; - usec_t information_request_time_usec; - usec_t information_refresh_time_usec; - OrderedHashmap *extra_options; - OrderedHashmap *vendor_options; - - /* Ignore ifindex when generating iaid. See dhcp_identifier_set_iaid(). */ - bool test_mode; -}; - static const uint16_t default_req_opts[] = { SD_DHCP6_OPTION_DNS_SERVERS, SD_DHCP6_OPTION_DOMAIN_LIST, @@ -97,78 +33,10 @@ static const uint16_t default_req_opts[] = { SD_DHCP6_OPTION_SNTP_SERVERS, }; -const char * dhcp6_message_type_table[_DHCP6_MESSAGE_TYPE_MAX] = { - [DHCP6_MESSAGE_SOLICIT] = "Solicit", - [DHCP6_MESSAGE_ADVERTISE] = "Advertise", - [DHCP6_MESSAGE_REQUEST] = "Request", - [DHCP6_MESSAGE_CONFIRM] = "Confirm", - [DHCP6_MESSAGE_RENEW] = "Renew", - [DHCP6_MESSAGE_REBIND] = "Rebind", - [DHCP6_MESSAGE_REPLY] = "Reply", - [DHCP6_MESSAGE_RELEASE] = "Release", - [DHCP6_MESSAGE_DECLINE] = "Decline", - [DHCP6_MESSAGE_RECONFIGURE] = "Reconfigure", - [DHCP6_MESSAGE_INFORMATION_REQUEST] = "Information Request", - [DHCP6_MESSAGE_RELAY_FORWARD] = "Relay Forward", - [DHCP6_MESSAGE_RELAY_REPLY] = "Relay Reply", - [DHCP6_MESSAGE_LEASE_QUERY] = "Lease Query", - [DHCP6_MESSAGE_LEASE_QUERY_REPLY] = "Lease Query Reply", - [DHCP6_MESSAGE_LEASE_QUERY_DONE] = "Lease Query Done", - [DHCP6_MESSAGE_LEASE_QUERY_DATA] = "Lease Query Data", - [DHCP6_MESSAGE_RECONFIGURE_REQUEST] = "Reconfigure Request", - [DHCP6_MESSAGE_RECONFIGURE_REPLY] = "Reconfigure Reply", - [DHCP6_MESSAGE_DHCPV4_QUERY] = "DHCPv4 Query", - [DHCP6_MESSAGE_DHCPV4_RESPONSE] = "DHCPv4 Response", - [DHCP6_MESSAGE_ACTIVE_LEASE_QUERY] = "Active Lease Query", - [DHCP6_MESSAGE_START_TLS] = "Start TLS", - [DHCP6_MESSAGE_BINDING_UPDATE] = "Binding Update", - [DHCP6_MESSAGE_BINDING_REPLY] = "Binding Reply", - [DHCP6_MESSAGE_POOL_REQUEST] = "Pool Request", - [DHCP6_MESSAGE_POOL_RESPONSE] = "Pool Response", - [DHCP6_MESSAGE_UPDATE_REQUEST] = "Update Request", - [DHCP6_MESSAGE_UPDATE_REQUEST_ALL] = "Update Request All", - [DHCP6_MESSAGE_UPDATE_DONE] = "Update Done", - [DHCP6_MESSAGE_CONNECT] = "Connect", - [DHCP6_MESSAGE_CONNECT_REPLY] = "Connect Reply", - [DHCP6_MESSAGE_DISCONNECT] = "Disconnect", - [DHCP6_MESSAGE_STATE] = "State", - [DHCP6_MESSAGE_CONTACT] = "Contact", -}; - -DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int); - -const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = { - [DHCP6_STATUS_SUCCESS] = "Success", - [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure", - [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available", - [DHCP6_STATUS_NO_BINDING] = "Binding unavailable", - [DHCP6_STATUS_NOT_ON_LINK] = "Not on link", - [DHCP6_STATUS_USE_MULTICAST] = "Use multicast", - [DHCP6_STATUS_NO_PREFIX_AVAIL] = "No prefix available", - [DHCP6_STATUS_UNKNOWN_QUERY_TYPE] = "Unknown query type", - [DHCP6_STATUS_MALFORMED_QUERY] = "Malformed query", - [DHCP6_STATUS_NOT_CONFIGURED] = "Not configured", - [DHCP6_STATUS_NOT_ALLOWED] = "Not allowed", - [DHCP6_STATUS_QUERY_TERMINATED] = "Query terminated", - [DHCP6_STATUS_DATA_MISSING] = "Data missing", - [DHCP6_STATUS_CATCHUP_COMPLETE] = "Catch up complete", - [DHCP6_STATUS_NOT_SUPPORTED] = "Not supported", - [DHCP6_STATUS_TLS_CONNECTION_REFUSED] = "TLS connection refused", - [DHCP6_STATUS_ADDRESS_IN_USE] = "Address in use", - [DHCP6_STATUS_CONFIGURATION_CONFLICT] = "Configuration conflict", - [DHCP6_STATUS_MISSING_BINDING_INFORMATION] = "Missing binding information", - [DHCP6_STATUS_OUTDATED_BINDING_INFORMATION] = "Outdated binding information", - [DHCP6_STATUS_SERVER_SHUTTING_DOWN] = "Server shutting down", - [DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED] = "DNS update not supported", - [DHCP6_STATUS_EXCESSIVE_TIME_SKEW] = "Excessive time skew", -}; - -DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int); - #define DHCP6_CLIENT_DONT_DESTROY(client) \ _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client) -static int client_start(sd_dhcp6_client *client, DHCP6State state); +static int client_start_transaction(sd_dhcp6_client *client, DHCP6State state); int sd_dhcp6_client_set_callback( sd_dhcp6_client *client, @@ -185,8 +53,8 @@ int sd_dhcp6_client_set_callback( int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) { assert_return(client, -EINVAL); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); assert_return(ifindex > 0, -EINVAL); - assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); client->ifindex = ifindex; return 0; @@ -222,9 +90,9 @@ int sd_dhcp6_client_set_local_address( const struct in6_addr *local_address) { assert_return(client, -EINVAL); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); assert_return(local_address, -EINVAL); assert_return(in6_addr_is_link_local(local_address) > 0, -EINVAL); - assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); client->local_address = *local_address; @@ -233,13 +101,16 @@ int sd_dhcp6_client_set_local_address( int sd_dhcp6_client_set_mac( sd_dhcp6_client *client, - const uint8_t *addr, size_t addr_len, + const uint8_t *addr, + size_t addr_len, uint16_t arp_type) { assert_return(client, -EINVAL); assert_return(addr, -EINVAL); - assert_return(addr_len <= MAX_MAC_ADDR_LEN, -EINVAL); - assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(addr_len <= sizeof(client->hw_addr.bytes), -EINVAL); + + /* Unlike the other setters, it is OK to set a new MAC address while the client is running, + * as the MAC address is used only when setting DUID or IAID. */ if (arp_type == ARPHRD_ETHER) assert_return(addr_len == ETH_ALEN, -EINVAL); @@ -247,12 +118,12 @@ int sd_dhcp6_client_set_mac( assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); else { client->arp_type = ARPHRD_NONE; - client->mac_addr_len = 0; + client->hw_addr.length = 0; return 0; } - memcpy(&client->mac_addr, addr, addr_len); - client->mac_addr_len = addr_len; + memcpy(client->hw_addr.bytes, addr, addr_len); + client->hw_addr.length = addr_len; client->arp_type = arp_type; return 0; @@ -261,25 +132,47 @@ int sd_dhcp6_client_set_mac( int sd_dhcp6_client_set_prefix_delegation_hint( sd_dhcp6_client *client, uint8_t prefixlen, - const struct in6_addr *pd_address) { + const struct in6_addr *pd_prefix) { + + _cleanup_free_ DHCP6Address *prefix = NULL; assert_return(client, -EINVAL); - assert_return(pd_address, -EINVAL); - assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); - client->hint_pd_prefix.iapdprefix.address = *pd_address; - client->hint_pd_prefix.iapdprefix.prefixlen = prefixlen; + if (!pd_prefix) { + /* clear previous assignments. */ + dhcp6_ia_clear_addresses(&client->ia_pd); + return 0; + } - return 0; + assert_return(prefixlen > 0 && prefixlen <= 128, -EINVAL); + + prefix = new(DHCP6Address, 1); + if (!prefix) + return -ENOMEM; + + *prefix = (DHCP6Address) { + .iapdprefix.address = *pd_prefix, + .iapdprefix.prefixlen = prefixlen, + }; + + LIST_PREPEND(addresses, client->ia_pd.addresses, TAKE_PTR(prefix)); + return 1; } int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *v) { int r; assert_return(client, -EINVAL); - assert_return(v, -EINVAL); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); - r = ordered_hashmap_ensure_put(&client->vendor_options, &dhcp6_option_hash_ops, v, v); + if (!v) { + /* Clear the previous assignments. */ + ordered_set_clear(client->vendor_options); + return 0; + } + + r = ordered_set_ensure_put(&client->vendor_options, &dhcp6_option_hash_ops, v); if (r < 0) return r; @@ -289,10 +182,12 @@ 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) return 0; - return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); + return dhcp_identifier_set_duid_en(client->test_mode, &client->duid, &client->duid_len); } /** @@ -302,15 +197,15 @@ static int client_ensure_duid(sd_dhcp6_client *client) { */ static int dhcp6_client_set_duid_internal( sd_dhcp6_client *client, - uint16_t duid_type, + DUIDType duid_type, const void *duid, size_t duid_len, usec_t llt_time) { int r; assert_return(client, -EINVAL); - assert_return(duid_len == 0 || duid != NULL, -EINVAL); - assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); + assert_return(duid_len == 0 || duid, -EINVAL); if (duid) { r = dhcp_validate_duid_len(duid_type, duid_len, true); @@ -325,37 +220,19 @@ static int dhcp6_client_set_duid_internal( client->duid.type = htobe16(duid_type); memcpy(&client->duid.raw.data, duid, duid_len); client->duid_len = sizeof(client->duid.type) + duid_len; - } else - switch (duid_type) { - case DUID_TYPE_LLT: - if (client->mac_addr_len == 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set."); - r = dhcp_identifier_set_duid_llt(&client->duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len); - if (r < 0) - return log_dhcp6_client_errno(client, r, "Failed to set DUID-LLT: %m"); - break; - case DUID_TYPE_EN: - r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); - if (r < 0) - return log_dhcp6_client_errno(client, r, "Failed to set DUID-EN: %m"); - break; - case DUID_TYPE_LL: - if (client->mac_addr_len == 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set."); - - r = dhcp_identifier_set_duid_ll(&client->duid, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len); - if (r < 0) - return log_dhcp6_client_errno(client, r, "Failed to set DUID-LL: %m"); - break; - case DUID_TYPE_UUID: - r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len); - if (r < 0) - return log_dhcp6_client_errno(client, r, "Failed to set DUID-UUID: %m"); - break; - default: - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type"); - } + } else { + r = dhcp_identifier_set_duid(duid_type, client->hw_addr.bytes, client->hw_addr.length, + client->arp_type, llt_time, client->test_mode, &client->duid, &client->duid_len); + if (r == -EOPNOTSUPP) + return log_dhcp6_client_errno(client, r, + "Failed to set %s. MAC address is not set or " + "interface type is not supported.", + duid_type_to_string(duid_type)); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to set %s: %m", + duid_type_to_string(duid_type)); + } return 0; } @@ -374,14 +251,6 @@ int sd_dhcp6_client_set_duid_llt( return dhcp6_client_set_duid_internal(client, DUID_TYPE_LLT, NULL, 0, llt_time); } -static const char* const dhcp6_duid_type_table[_DUID_TYPE_MAX] = { - [DUID_TYPE_LLT] = "DUID-LLT", - [DUID_TYPE_EN] = "DUID-EN/Vendor", - [DUID_TYPE_LL] = "DUID-LL", - [DUID_TYPE_UUID] = "UUID", -}; -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_duid_type, DUIDType); - int sd_dhcp6_client_duid_as_string( sd_dhcp6_client *client, char **duid) { @@ -393,7 +262,7 @@ int sd_dhcp6_client_duid_as_string( assert_return(client->duid_len > 0, -ENODATA); assert_return(duid, -EINVAL); - v = dhcp6_duid_type_to_string(be16toh(client->duid.type)); + v = duid_type_to_string(be16toh(client->duid.type)); if (v) { s = strdup(v); if (!s) @@ -419,38 +288,62 @@ int sd_dhcp6_client_duid_as_string( int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) { assert_return(client, -EINVAL); - assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); - client->ia_na.ia_na.id = htobe32(iaid); - client->ia_pd.ia_pd.id = htobe32(iaid); + client->ia_na.header.id = htobe32(iaid); + client->ia_pd.header.id = htobe32(iaid); client->iaid_set = true; return 0; } +static int client_ensure_iaid(sd_dhcp6_client *client) { + int r; + uint32_t iaid; + + assert(client); + + if (client->iaid_set) + return 0; + + r = dhcp_identifier_set_iaid(client->ifindex, client->hw_addr.bytes, client->hw_addr.length, + /* legacy_unstable_byteorder = */ true, + /* use_mac = */ client->test_mode, + &iaid); + if (r < 0) + return r; + + client->ia_na.header.id = iaid; + client->ia_pd.header.id = iaid; + client->iaid_set = true; + + return 0; +} + +int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) { + assert_return(client, -EINVAL); + assert_return(iaid, -EINVAL); + + if (!client->iaid_set) + return -ENODATA; + + *iaid = be32toh(client->ia_na.header.id); + + return 0; +} + void dhcp6_client_set_test_mode(sd_dhcp6_client *client, bool test_mode) { assert(client); client->test_mode = test_mode; } -int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) { - assert_return(client, -EINVAL); - assert_return(iaid, -EINVAL); - - if (!client->iaid_set) - return -ENODATA; - - *iaid = be32toh(client->ia_na.ia_na.id); - - return 0; -} - int sd_dhcp6_client_set_fqdn( sd_dhcp6_client *client, const char *fqdn) { assert_return(client, -EINVAL); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); /* Make sure FQDN qualifies as DNS and as Linux hostname */ if (fqdn && @@ -462,7 +355,7 @@ int sd_dhcp6_client_set_fqdn( int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) { assert_return(client, -EINVAL); - assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); client->information_request = enabled; @@ -482,7 +375,7 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) size_t t; assert_return(client, -EINVAL); - assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); if (!dhcp6_option_can_request(option)) return -EINVAL; @@ -501,7 +394,7 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, const char *mudurl) { assert_return(client, -EINVAL); - assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); assert_return(mudurl, -EINVAL); assert_return(strlen(mudurl) <= UINT8_MAX, -EINVAL); assert_return(http_url_is_valid(mudurl), -EINVAL); @@ -514,7 +407,7 @@ int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client *client, char * const char **s; assert_return(client, -EINVAL); - assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); assert_return(!strv_isempty(user_class), -EINVAL); STRV_FOREACH(p, user_class) { @@ -536,7 +429,7 @@ int sd_dhcp6_client_set_request_vendor_class(sd_dhcp6_client *client, char * con char **s; assert_return(client, -EINVAL); - assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); assert_return(!strv_isempty(vendor_class), -EINVAL); STRV_FOREACH(p, vendor_class) { @@ -564,6 +457,7 @@ int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegati int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, int delegation) { assert_return(client, -EINVAL); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); SET_FLAG(client->request_ia, DHCP6_REQUEST_IA_PD, delegation); @@ -581,16 +475,20 @@ int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request) { int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request) { assert_return(client, -EINVAL); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); SET_FLAG(client->request_ia, DHCP6_REQUEST_IA_NA, request); return 0; } -int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) { - assert_return(client, -EINVAL); +int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) { + assert(client); + assert(client->test_mode); - client->transaction_id = transaction_id; + /* This is for tests or fuzzers. */ + + client->transaction_id = transaction_id & htobe32(0x00ffffff); return 0; } @@ -621,6 +519,18 @@ int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) { return 0; } +static void client_set_state(sd_dhcp6_client *client, DHCP6State state) { + assert(client); + + if (client->state == state) + return; + + log_dhcp6_client(client, "State changed: %s -> %s", + dhcp6_state_to_string(client->state), dhcp6_state_to_string(state)); + + client->state = state; +} + static void client_notify(sd_dhcp6_client *client, int event) { assert(client); @@ -628,30 +538,6 @@ static void client_notify(sd_dhcp6_client *client, int event) { client->callback(client, event, client->userdata); } -static int client_reset(sd_dhcp6_client *client) { - assert(client); - - client->lease = sd_dhcp6_lease_unref(client->lease); - - client->receive_message = - sd_event_source_unref(client->receive_message); - - client->transaction_id = 0; - client->transaction_start = 0; - - client->retransmit_time = 0; - client->retransmit_count = 0; - - (void) event_source_disable(client->timeout_resend); - (void) event_source_disable(client->timeout_resend_expire); - (void) event_source_disable(client->timeout_t1); - (void) event_source_disable(client->timeout_t2); - - client->state = DHCP6_STATE_STOPPED; - - return 0; -} - static void client_stop(sd_dhcp6_client *client, int error) { DHCP6_CLIENT_DONT_DESTROY(client); @@ -659,21 +545,115 @@ static void client_stop(sd_dhcp6_client *client, int error) { client_notify(client, error); - client_reset(client); + client->lease = sd_dhcp6_lease_unref(client->lease); + + /* Reset IRT here. Otherwise, we cannot restart the client in the information requesting mode, + * even though the lease is freed below. */ + client->information_request_time_usec = 0; + client->information_refresh_time_usec = 0; + + (void) event_source_disable(client->receive_message); + (void) event_source_disable(client->timeout_resend); + (void) event_source_disable(client->timeout_expire); + (void) event_source_disable(client->timeout_t1); + (void) event_source_disable(client->timeout_t2); + + client_set_state(client, DHCP6_STATE_STOPPED); } -static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { +static int client_append_common_options_in_managed_mode( + sd_dhcp6_client *client, + uint8_t **opt, + size_t *optlen, + const DHCP6IA *ia_na, + const DHCP6IA *ia_pd) { + + int r; + + assert(client); + assert(IN_SET(client->state, + DHCP6_STATE_SOLICITATION, + DHCP6_STATE_REQUEST, + DHCP6_STATE_RENEW, + DHCP6_STATE_REBIND)); + assert(opt); + assert(optlen); + + if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA) && ia_na) { + r = dhcp6_option_append_ia(opt, optlen, ia_na); + if (r < 0) + return r; + } + + if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD) && ia_pd) { + r = dhcp6_option_append_ia(opt, optlen, ia_pd); + if (r < 0) + return r; + } + + if (client->fqdn) { + r = dhcp6_option_append_fqdn(opt, optlen, client->fqdn); + if (r < 0) + return r; + } + + if (client->user_class) { + r = dhcp6_option_append_user_class(opt, optlen, client->user_class); + if (r < 0) + return r; + } + + if (client->vendor_class) { + r = dhcp6_option_append_vendor_class(opt, optlen, client->vendor_class); + if (r < 0) + return r; + } + + if (!ordered_set_isempty(client->vendor_options)) { + r = dhcp6_option_append_vendor_option(opt, optlen, client->vendor_options); + if (r < 0) + return r; + } + + return 0; +} + +static DHCP6MessageType client_message_type_from_state(sd_dhcp6_client *client) { + assert(client); + + switch (client->state) { + case DHCP6_STATE_INFORMATION_REQUEST: + return DHCP6_MESSAGE_INFORMATION_REQUEST; + case DHCP6_STATE_SOLICITATION: + return DHCP6_MESSAGE_SOLICIT; + case DHCP6_STATE_REQUEST: + return DHCP6_MESSAGE_REQUEST; + case DHCP6_STATE_RENEW: + return DHCP6_MESSAGE_RENEW; + case DHCP6_STATE_REBIND: + return DHCP6_MESSAGE_REBIND; + default: + assert_not_reached(); + } +} + +int dhcp6_client_send_message(sd_dhcp6_client *client) { _cleanup_free_ DHCP6Message *message = NULL; struct in6_addr all_servers = IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; struct sd_dhcp6_option *j; size_t len, optlen = 512; uint8_t *opt; - int r; - usec_t elapsed_usec; + usec_t elapsed_usec, time_now; be16_t elapsed_time; + int r; assert(client); + assert(client->event); + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; len = sizeof(DHCP6Message) + optlen; @@ -684,194 +664,56 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { opt = (uint8_t *)(message + 1); message->transaction_id = client->transaction_id; + message->type = client_message_type_from_state(client); - switch(client->state) { + switch (client->state) { case DHCP6_STATE_INFORMATION_REQUEST: - message->type = DHCP6_MESSAGE_INFORMATION_REQUEST; - - if (client->mudurl) { - r = dhcp6_option_append(&opt, &optlen, - SD_DHCP6_OPTION_MUD_URL_V6, strlen(client->mudurl), - client->mudurl); - if (r < 0) - return r; - } - break; case DHCP6_STATE_SOLICITATION: - message->type = DHCP6_MESSAGE_SOLICIT; - - r = dhcp6_option_append(&opt, &optlen, - SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL); + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL); if (r < 0) return r; - if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA)) { - r = dhcp6_option_append_ia(&opt, &optlen, - &client->ia_na); - if (r < 0) - return r; - } - - if (client->fqdn) { - r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); - if (r < 0) - return r; - } - - if (client->mudurl) { - r = dhcp6_option_append(&opt, &optlen, - SD_DHCP6_OPTION_MUD_URL_V6, strlen(client->mudurl), - client->mudurl); - if (r < 0) - return r; - } - - if (client->user_class) { - r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); - if (r < 0) - return r; - } - - if (client->vendor_class) { - r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class); - if (r < 0) - return r; - } - - if (!ordered_hashmap_isempty(client->vendor_options)) { - r = dhcp6_option_append_vendor_option(&opt, &optlen, - client->vendor_options); - if (r < 0) - return r; - } - - if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD)) { - r = dhcp6_option_append_pd(&opt, &optlen, &client->ia_pd, &client->hint_pd_prefix); - if (r < 0) - return r; - } - + r = client_append_common_options_in_managed_mode(client, &opt, &optlen, + &client->ia_na, &client->ia_pd); + if (r < 0) + return r; break; case DHCP6_STATE_REQUEST: case DHCP6_STATE_RENEW: - if (client->state == DHCP6_STATE_REQUEST) - message->type = DHCP6_MESSAGE_REQUEST; - else - message->type = DHCP6_MESSAGE_RENEW; - r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_SERVERID, client->lease->serverid_len, client->lease->serverid); if (r < 0) return r; - if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) { - r = dhcp6_option_append_ia(&opt, &optlen, - &client->lease->ia); - if (r < 0) - return r; - } - - if (client->fqdn) { - r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); - if (r < 0) - return r; - } - - if (client->mudurl) { - r = dhcp6_option_append(&opt, &optlen, - SD_DHCP6_OPTION_MUD_URL_V6, strlen(client->mudurl), - client->mudurl); - if (r < 0) - return r; - } - - if (client->user_class) { - r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); - if (r < 0) - return r; - } - - if (client->vendor_class) { - r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class); - if (r < 0) - return r; - } - - if (!ordered_hashmap_isempty(client->vendor_options)) { - r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options); - if (r < 0) - return r; - } - - if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) { - r = dhcp6_option_append_pd(&opt, &optlen, &client->lease->pd, NULL); - if (r < 0) - return r; - } - - break; - + _fallthrough_; case DHCP6_STATE_REBIND: - message->type = DHCP6_MESSAGE_REBIND; - if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA)) { - r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia); - if (r < 0) - return r; - } - - if (client->fqdn) { - r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); - if (r < 0) - return r; - } - - if (client->mudurl) { - r = dhcp6_option_append(&opt, &optlen, - SD_DHCP6_OPTION_MUD_URL_V6, strlen(client->mudurl), - client->mudurl); - if (r < 0) - return r; - } - - if (client->user_class) { - r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); - if (r < 0) - return r; - } - - if (client->vendor_class) { - r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class); - if (r < 0) - return r; - } - - if (!ordered_hashmap_isempty(client->vendor_options)) { - r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options); - if (r < 0) - return r; - } - - if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) { - r = dhcp6_option_append_pd(&opt, &optlen, &client->lease->pd, NULL); - if (r < 0) - return r; - } + assert(client->lease); + r = client_append_common_options_in_managed_mode(client, &opt, &optlen, + client->lease->ia_na, client->lease->ia_pd); + if (r < 0) + return r; break; case DHCP6_STATE_STOPPED: case DHCP6_STATE_BOUND: - return -EINVAL; default: assert_not_reached(); } + if (client->mudurl) { + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_MUD_URL_V6, + strlen(client->mudurl), client->mudurl); + if (r < 0) + return r; + } + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ORO, client->req_opts_len * sizeof(be16_t), client->req_opts); @@ -884,6 +726,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { if (r < 0) return r; + ORDERED_HASHMAP_FOREACH(j, client->extra_options) { + r = dhcp6_option_append(&opt, &optlen, j->option, j->length, j->data); + if (r < 0) + return r; + } + /* RFC 8415 Section 21.9. * A client MUST include an Elapsed Time option in messages to indicate how long the client has * been trying to complete a DHCP message exchange. */ @@ -893,12 +741,6 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { if (r < 0) return r; - ORDERED_HASHMAP_FOREACH(j, client->extra_options) { - r = dhcp6_option_append(&opt, &optlen, j->option, j->length, j->data); - if (r < 0) - return r; - } - r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message, len - optlen); if (r < 0) @@ -910,100 +752,43 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return 0; } -static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { - sd_dhcp6_client *client = userdata; - - assert(s); - assert(client); - assert(client->lease); - - (void) event_source_disable(client->timeout_t2); - - log_dhcp6_client(client, "Timeout T2"); - - client_start(client, DHCP6_STATE_REBIND); - - return 0; -} - -static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { - sd_dhcp6_client *client = userdata; - - assert(s); - assert(client); - assert(client->lease); - - (void) event_source_disable(client->timeout_t1); - - log_dhcp6_client(client, "Timeout T1"); - - client_start(client, DHCP6_STATE_RENEW); - - return 0; -} - -static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, void *userdata) { - sd_dhcp6_client *client = userdata; - DHCP6_CLIENT_DONT_DESTROY(client); - DHCP6State state; - - assert(s); - assert(client); - assert(client->event); - - state = client->state; - - client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE); - - /* RFC 3315, section 18.1.4., says that "...the client may choose to - use a Solicit message to locate a new DHCP server..." */ - if (state == DHCP6_STATE_REBIND) - client_start(client, DHCP6_STATE_SOLICITATION); - - return 0; -} - static usec_t client_timeout_compute_random(usec_t val) { - return val - (random_u32() % USEC_PER_SEC) * val / 10 / USEC_PER_SEC; + return usec_sub_unsigned(val, random_u64_range(val / 10)); } static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) { - int r = 0; - sd_dhcp6_client *client = userdata; - usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0; - usec_t max_retransmit_duration = 0; - uint8_t max_retransmit_count = 0; + sd_dhcp6_client *client = ASSERT_PTR(userdata); + usec_t init_retransmit_time, max_retransmit_time; + int r; - assert(s); - assert(client); assert(client->event); - (void) event_source_disable(client->timeout_resend); - switch (client->state) { case DHCP6_STATE_INFORMATION_REQUEST: init_retransmit_time = DHCP6_INF_TIMEOUT; max_retransmit_time = DHCP6_INF_MAX_RT; - break; case DHCP6_STATE_SOLICITATION: if (client->retransmit_count > 0 && client->lease) { - client_start(client, DHCP6_STATE_REQUEST); + (void) client_start_transaction(client, DHCP6_STATE_REQUEST); return 0; } init_retransmit_time = DHCP6_SOL_TIMEOUT; max_retransmit_time = DHCP6_SOL_MAX_RT; - break; case DHCP6_STATE_REQUEST: + + if (client->retransmit_count >= DHCP6_REQ_MAX_RC) { + client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX); + return 0; + } + init_retransmit_time = DHCP6_REQ_TIMEOUT; max_retransmit_time = DHCP6_REQ_MAX_RT; - max_retransmit_count = DHCP6_REQ_MAX_RC; - break; case DHCP6_STATE_RENEW: @@ -1013,422 +798,365 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda /* RFC 3315, section 18.1.3. says max retransmit duration will be the remaining time until T2. Instead of setting MRD, wait for T2 to trigger with the same end result */ - break; case DHCP6_STATE_REBIND: init_retransmit_time = DHCP6_REB_TIMEOUT; max_retransmit_time = DHCP6_REB_MAX_RT; - if (event_source_is_enabled(client->timeout_resend_expire) <= 0) { - uint32_t expire = 0; - - r = dhcp6_lease_ia_rebind_expire(&client->lease->ia, &expire); - if (r < 0) { - client_stop(client, r); - return 0; - } - max_retransmit_duration = expire * USEC_PER_SEC; - } - + /* Also, instead of setting MRD, the expire timer is already set in client_enter_bound_state(). */ break; case DHCP6_STATE_STOPPED: case DHCP6_STATE_BOUND: - return 0; default: assert_not_reached(); } - if (max_retransmit_count > 0 && - client->retransmit_count >= max_retransmit_count) { - client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX); - return 0; - } - - r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); - if (r < 0) - goto error; - - r = client_send_message(client, time_now); + r = dhcp6_client_send_message(client); if (r >= 0) client->retransmit_count++; if (client->retransmit_time == 0) { - client->retransmit_time = - client_timeout_compute_random(init_retransmit_time); + client->retransmit_time = client_timeout_compute_random(init_retransmit_time); if (client->state == DHCP6_STATE_SOLICITATION) client->retransmit_time += init_retransmit_time / 10; - } else { - assert(max_retransmit_time > 0); - if (client->retransmit_time > max_retransmit_time / 2) - client->retransmit_time = client_timeout_compute_random(max_retransmit_time); - else - client->retransmit_time += client_timeout_compute_random(client->retransmit_time); - } + } else if (client->retransmit_time > max_retransmit_time / 2) + client->retransmit_time = client_timeout_compute_random(max_retransmit_time); + else + client->retransmit_time += client_timeout_compute_random(client->retransmit_time); log_dhcp6_client(client, "Next retransmission in %s", FORMAT_TIMESPAN(client->retransmit_time, USEC_PER_SEC)); - r = event_reset_time(client->event, &client->timeout_resend, - clock_boottime_or_monotonic(), - time_now + client->retransmit_time, 10 * USEC_PER_MSEC, - client_timeout_resend, client, - client->event_priority, "dhcp6-resend-timer", true); - if (r < 0) - goto error; - - if (max_retransmit_duration > 0 && event_source_is_enabled(client->timeout_resend_expire) <= 0) { - - log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs", - max_retransmit_duration / USEC_PER_SEC); - - r = event_reset_time(client->event, &client->timeout_resend_expire, - clock_boottime_or_monotonic(), - time_now + max_retransmit_duration, USEC_PER_SEC, - client_timeout_resend_expire, client, - client->event_priority, "dhcp6-resend-expire-timer", true); - if (r < 0) - goto error; - } - -error: + r = event_reset_time_relative(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + client->retransmit_time, 10 * USEC_PER_MSEC, + client_timeout_resend, client, + client->event_priority, "dhcp6-resend-timer", true); if (r < 0) client_stop(client, r); return 0; } -static int client_ensure_iaid(sd_dhcp6_client *client) { +static int client_start_transaction(sd_dhcp6_client *client, DHCP6State state) { int r; - uint32_t iaid; assert(client); + assert(client->event); - if (client->iaid_set) - return 0; + switch (state) { + case DHCP6_STATE_INFORMATION_REQUEST: + case DHCP6_STATE_SOLICITATION: + assert(client->state == DHCP6_STATE_STOPPED); + break; + case DHCP6_STATE_REQUEST: + assert(client->state == DHCP6_STATE_SOLICITATION); + break; + case DHCP6_STATE_RENEW: + assert(client->state == DHCP6_STATE_BOUND); + break; + case DHCP6_STATE_REBIND: + assert(IN_SET(client->state, DHCP6_STATE_BOUND, DHCP6_STATE_RENEW)); + break; + case DHCP6_STATE_STOPPED: + case DHCP6_STATE_BOUND: + default: + assert_not_reached(); + } - r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, - /* legacy_unstable_byteorder = */ true, - /* use_mac = */ client->test_mode, - &iaid); + client_set_state(client, state); + + client->retransmit_time = 0; + client->retransmit_count = 0; + client->transaction_id = random_u32() & htobe32(0x00ffffff); + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &client->transaction_start); if (r < 0) - return r; + goto error; - client->ia_na.ia_na.id = iaid; - client->ia_pd.ia_pd.id = iaid; - client->iaid_set = true; + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + 0, 0, + client_timeout_resend, client, + client->event_priority, "dhcp6-resend-timeout", true); + if (r < 0) + goto error; + + r = sd_event_source_set_enabled(client->receive_message, SD_EVENT_ON); + if (r < 0) + goto error; + + return 0; + +error: + client_stop(client, r); + return r; +} + +static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp6_client *client = ASSERT_PTR(userdata); + DHCP6_CLIENT_DONT_DESTROY(client); + DHCP6State state; + + (void) event_source_disable(client->timeout_expire); + (void) event_source_disable(client->timeout_t2); + (void) event_source_disable(client->timeout_t1); + + state = client->state; + + client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE); + + /* RFC 3315, section 18.1.4., says that "...the client may choose to + use a Solicit message to locate a new DHCP server..." */ + if (state == DHCP6_STATE_REBIND) + (void) client_start_transaction(client, DHCP6_STATE_SOLICITATION); return 0; } -int client_parse_message( +static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp6_client *client = ASSERT_PTR(userdata); + + (void) event_source_disable(client->timeout_t2); + (void) event_source_disable(client->timeout_t1); + + log_dhcp6_client(client, "Timeout T2"); + + (void) client_start_transaction(client, DHCP6_STATE_REBIND); + + return 0; +} + +static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp6_client *client = ASSERT_PTR(userdata); + + (void) event_source_disable(client->timeout_t1); + + log_dhcp6_client(client, "Timeout T1"); + + (void) client_start_transaction(client, DHCP6_STATE_RENEW); + + return 0; +} + +static int client_enter_bound_state(sd_dhcp6_client *client) { + usec_t lifetime_t1, lifetime_t2, lifetime_valid; + int r; + + assert(client); + assert(client->lease); + assert(IN_SET(client->state, + DHCP6_STATE_SOLICITATION, + DHCP6_STATE_REQUEST, + DHCP6_STATE_RENEW, + DHCP6_STATE_REBIND)); + + (void) event_source_disable(client->receive_message); + (void) event_source_disable(client->timeout_resend); + + r = dhcp6_lease_get_lifetime(client->lease, &lifetime_t1, &lifetime_t2, &lifetime_valid); + if (r < 0) + goto error; + + lifetime_t2 = client_timeout_compute_random(lifetime_t2); + lifetime_t1 = client_timeout_compute_random(MIN(lifetime_t1, lifetime_t2)); + + if (lifetime_t1 == USEC_INFINITY) { + log_dhcp6_client(client, "Infinite T1"); + event_source_disable(client->timeout_t1); + } else { + log_dhcp6_client(client, "T1 expires in %s", FORMAT_TIMESPAN(lifetime_t1, USEC_PER_SEC)); + r = event_reset_time_relative(client->event, &client->timeout_t1, + clock_boottime_or_monotonic(), + lifetime_t1, 10 * USEC_PER_SEC, + client_timeout_t1, client, + client->event_priority, "dhcp6-t1-timeout", true); + if (r < 0) + goto error; + } + + if (lifetime_t2 == USEC_INFINITY) { + log_dhcp6_client(client, "Infinite T2"); + event_source_disable(client->timeout_t2); + } else { + log_dhcp6_client(client, "T2 expires in %s", FORMAT_TIMESPAN(lifetime_t2, USEC_PER_SEC)); + r = event_reset_time_relative(client->event, &client->timeout_t2, + clock_boottime_or_monotonic(), + lifetime_t2, 10 * USEC_PER_SEC, + client_timeout_t2, client, + client->event_priority, "dhcp6-t2-timeout", true); + if (r < 0) + goto error; + } + + if (lifetime_valid == USEC_INFINITY) { + log_dhcp6_client(client, "Infinite valid lifetime"); + event_source_disable(client->timeout_expire); + } else { + log_dhcp6_client(client, "Valid lifetime expires in %s", FORMAT_TIMESPAN(lifetime_valid, USEC_PER_SEC)); + + r = event_reset_time_relative(client->event, &client->timeout_expire, + clock_boottime_or_monotonic(), + lifetime_valid, USEC_PER_SEC, + client_timeout_expire, client, + client->event_priority, "dhcp6-lease-expire", true); + if (r < 0) + goto error; + } + + client_set_state(client, DHCP6_STATE_BOUND); + client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE); + return 0; + +error: + client_stop(client, r); + return r; +} + +static int log_invalid_message_type(sd_dhcp6_client *client, const DHCP6Message *message) { + const char *type_str; + + assert(client); + assert(message); + + type_str = dhcp6_message_type_to_string(message->type); + if (type_str) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received unexpected %s message, ignoring.", type_str); + else + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received unsupported message type %u, ignoring.", message->type); +} + +static int client_process_information( sd_dhcp6_client *client, DHCP6Message *message, size_t len, - sd_dhcp6_lease *lease) { + const triple_timestamp *timestamp, + const struct in6_addr *server_address) { - uint32_t lt_t1 = UINT32_MAX, lt_t2 = UINT32_MAX; - usec_t irt = IRT_DEFAULT; + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; int r; assert(client); assert(message); - assert(len >= sizeof(DHCP6Message)); - assert(lease); - len -= sizeof(DHCP6Message); - for (size_t offset = 0; offset < len;) { - uint16_t optcode; - size_t optlen; - const uint8_t *optval; + if (message->type != DHCP6_MESSAGE_REPLY) + return log_invalid_message_type(client, message); - r = dhcp6_option_parse(message->options, len, &offset, &optcode, &optlen, &optval); - if (r < 0) - return r; + r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m"); - switch (optcode) { - case SD_DHCP6_OPTION_CLIENTID: - if (dhcp6_lease_get_clientid(lease, NULL, NULL) >= 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple client IDs", - dhcp6_message_type_to_string(message->type)); + log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type)); - r = dhcp6_lease_set_clientid(lease, optval, optlen); - if (r < 0) - return r; + sd_dhcp6_lease_unref(client->lease); + client->lease = TAKE_PTR(lease); - break; - - case SD_DHCP6_OPTION_SERVERID: - if (dhcp6_lease_get_serverid(lease, NULL, NULL) >= 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple server IDs", - dhcp6_message_type_to_string(message->type)); - - r = dhcp6_lease_set_serverid(lease, optval, optlen); - if (r < 0) - return r; - - break; - - case SD_DHCP6_OPTION_PREFERENCE: - if (optlen != 1) - return -EINVAL; - - r = dhcp6_lease_set_preference(lease, optval[0]); - if (r < 0) - return r; - - break; - - case SD_DHCP6_OPTION_STATUS_CODE: { - _cleanup_free_ char *msg = NULL; - - r = dhcp6_option_parse_status(optval, optlen, &msg); - if (r < 0) - return r; - - if (r > 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), - "Received %s message with non-zero status: %s%s%s", - dhcp6_message_type_to_string(message->type), - strempty(msg), isempty(msg) ? "" : ": ", - dhcp6_message_status_to_string(r)); - break; - } - case SD_DHCP6_OPTION_IA_NA: { - _cleanup_(dhcp6_lease_free_ia) DHCP6IA ia = {}; - - if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { - log_dhcp6_client(client, "Ignoring IA NA option in information requesting mode."); - break; - } - - r = dhcp6_option_parse_ia(client, client->ia_pd.ia_na.id, optcode, optlen, optval, &ia); - if (r == -ENOMEM) - return r; - if (r < 0) - continue; - - if (lease->ia.addresses) { - log_dhcp6_client(client, "Received duplicate matching IA_NA option, ignoring."); - continue; - } - - lease->ia = ia; - ia = (DHCP6IA) {}; - - lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1)); - lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t2)); - - break; - } - case SD_DHCP6_OPTION_IA_PD: { - _cleanup_(dhcp6_lease_free_ia) DHCP6IA ia = {}; - - if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { - log_dhcp6_client(client, "Ignoring IA PD option in information requesting mode."); - break; - } - - r = dhcp6_option_parse_ia(client, client->ia_pd.ia_pd.id, optcode, optlen, optval, &ia); - if (r == -ENOMEM) - return r; - if (r < 0) - continue; - - if (lease->pd.addresses) { - log_dhcp6_client(client, "Received duplicate matching IA_PD option, ignoring."); - continue; - } - - lease->pd = ia; - ia = (DHCP6IA) {}; - - lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1)); - lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2)); - - break; - } - case SD_DHCP6_OPTION_RAPID_COMMIT: - r = dhcp6_lease_set_rapid_commit(lease); - if (r < 0) - return r; - - break; - - case SD_DHCP6_OPTION_DNS_SERVERS: - r = dhcp6_lease_add_dns(lease, optval, optlen); - if (r < 0) - log_dhcp6_client_errno(client, r, "Failed to parse DNS server option, ignoring: %m"); - - break; - - case SD_DHCP6_OPTION_DOMAIN_LIST: - r = dhcp6_lease_add_domains(lease, optval, optlen); - if (r < 0) - log_dhcp6_client_errno(client, r, "Failed to parse domain list option, ignoring: %m"); - - break; - - case SD_DHCP6_OPTION_NTP_SERVER: - r = dhcp6_lease_add_ntp(lease, optval, optlen); - if (r < 0) - log_dhcp6_client_errno(client, r, "Failed to parse NTP server option, ignoring: %m"); - - break; - - case SD_DHCP6_OPTION_SNTP_SERVERS: - r = dhcp6_lease_add_sntp(lease, optval, optlen); - if (r < 0) - log_dhcp6_client_errno(client, r, "Failed to parse SNTP server option, ignoring: %m"); - - break; - - case SD_DHCP6_OPTION_CLIENT_FQDN: - r = dhcp6_lease_set_fqdn(lease, optval, optlen); - if (r < 0) - log_dhcp6_client_errno(client, r, "Failed to parse FQDN option, ignoring: %m"); - - break; - - case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME: - if (optlen != 4) - return -EINVAL; - - irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC; - break; - } - } - - uint8_t *clientid; - size_t clientid_len; - if (dhcp6_lease_get_clientid(lease, &clientid, &clientid_len) < 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s message does not contain client ID. Ignoring.", - dhcp6_message_type_to_string(message->type)); - - if (clientid_len != client->duid_len || - memcmp(clientid, &client->duid, clientid_len) != 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "The client ID in %s message does not match. Ignoring.", - dhcp6_message_type_to_string(message->type)); - - if (client->state != DHCP6_STATE_INFORMATION_REQUEST) { - r = dhcp6_lease_get_serverid(lease, NULL, NULL); - if (r < 0) - return log_dhcp6_client_errno(client, r, "%s has no server id", - dhcp6_message_type_to_string(message->type)); - - if (!lease->ia.addresses && !lease->pd.addresses) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "No IA_PD prefix or IA_NA address received. Ignoring."); - - if (lease->ia.addresses) { - lease->ia.ia_na.lifetime_t1 = htobe32(lt_t1); - lease->ia.ia_na.lifetime_t2 = htobe32(lt_t2); - } - - if (lease->pd.addresses) { - lease->pd.ia_pd.lifetime_t1 = htobe32(lt_t1); - lease->pd.ia_pd.lifetime_t2 = htobe32(lt_t2); - } - } - - client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM); + /* Do not call client_stop() here, as it frees the acquired lease. */ + (void) event_source_disable(client->receive_message); + (void) event_source_disable(client->timeout_resend); + client_set_state(client, DHCP6_STATE_STOPPED); + client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST); return 0; } -static int client_receive_reply( +static int client_process_reply( sd_dhcp6_client *client, - DHCP6Message *reply, + DHCP6Message *message, size_t len, - const triple_timestamp *t, + const triple_timestamp *timestamp, const struct in6_addr *server_address) { _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; - bool rapid_commit; int r; assert(client); - assert(reply); - assert(t); + assert(message); - if (reply->type != DHCP6_MESSAGE_REPLY) - return 0; + if (message->type != DHCP6_MESSAGE_REPLY) + return log_invalid_message_type(client, message); - r = dhcp6_lease_new(&lease); + r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease); if (r < 0) - return -ENOMEM; + return log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m"); - lease->timestamp = *t; - if (server_address) - lease->server_address = *server_address; + log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type)); - r = client_parse_message(client, reply, len, lease); + sd_dhcp6_lease_unref(client->lease); + client->lease = TAKE_PTR(lease); + + return client_enter_bound_state(client); +} + +static int client_process_advertise_or_rapid_commit_reply( + sd_dhcp6_client *client, + DHCP6Message *message, + size_t len, + const triple_timestamp *timestamp, + const struct in6_addr *server_address) { + + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + uint8_t pref_advertise, pref_lease = 0; + int r; + + assert(client); + assert(message); + + if (!IN_SET(message->type, DHCP6_MESSAGE_ADVERTISE, DHCP6_MESSAGE_REPLY)) + return log_invalid_message_type(client, message); + + r = dhcp6_lease_new_from_message(client, message, len, timestamp, server_address, &lease); if (r < 0) - return r; + return log_dhcp6_client_errno(client, r, "Failed to process received %s message, ignoring: %m", + dhcp6_message_type_to_string(message->type)); + + if (message->type == DHCP6_MESSAGE_REPLY) { + bool rapid_commit; - if (client->state == DHCP6_STATE_SOLICITATION) { r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit); if (r < 0) return r; if (!rapid_commit) - return 0; + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received reply message without rapid commit flag, ignoring."); + + log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type)); + + sd_dhcp6_lease_unref(client->lease); + client->lease = TAKE_PTR(lease); + + return client_enter_bound_state(client); } - sd_dhcp6_lease_unref(client->lease); - client->lease = TAKE_PTR(lease); - - return DHCP6_STATE_BOUND; -} - -static int client_receive_advertise( - sd_dhcp6_client *client, - DHCP6Message *advertise, - size_t len, - const triple_timestamp *t, - const struct in6_addr *server_address) { - - _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; - uint8_t pref_advertise = 0, pref_lease = 0; - int r; - - assert(client); - assert(advertise); - assert(t); - - if (advertise->type != DHCP6_MESSAGE_ADVERTISE) - return 0; - - r = dhcp6_lease_new(&lease); - if (r < 0) - return r; - - lease->timestamp = *t; - if (server_address) - lease->server_address = *server_address; - - r = client_parse_message(client, advertise, len, lease); - if (r < 0) - return r; - r = dhcp6_lease_get_preference(lease, &pref_advertise); if (r < 0) return r; - r = dhcp6_lease_get_preference(client->lease, &pref_lease); + if (client->lease) { + r = dhcp6_lease_get_preference(client->lease, &pref_lease); + if (r < 0) + return r; + } - if (r < 0 || pref_advertise > pref_lease) { + log_dhcp6_client(client, "Processed %s message", dhcp6_message_type_to_string(message->type)); + + if (!client->lease || pref_advertise > pref_lease) { + /* If this is the first advertise message or has higher preference, then save the lease. */ sd_dhcp6_lease_unref(client->lease); client->lease = TAKE_PTR(lease); - r = 0; } if (pref_advertise == 255 || client->retransmit_count > 1) - r = DHCP6_STATE_REQUEST; + (void) client_start_transaction(client, DHCP6_STATE_REQUEST); - return r; + return 0; } static int client_receive_message( @@ -1437,7 +1165,7 @@ static int client_receive_message( revents, void *userdata) { - sd_dhcp6_client *client = userdata; + sd_dhcp6_client *client = ASSERT_PTR(userdata); DHCP6_CLIENT_DONT_DESTROY(client); /* This needs to be initialized with zero. See #20741. */ CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL) control = {}; @@ -1456,11 +1184,6 @@ static int client_receive_message( _cleanup_free_ DHCP6Message *message = NULL; struct in6_addr *server_address = NULL; ssize_t buflen, len; - int r = 0; - - assert(s); - assert(client); - assert(client->event); buflen = next_datagram_size_fd(fd); if (buflen < 0) { @@ -1507,236 +1230,43 @@ static int client_receive_message( triple_timestamp_from_realtime(&t, timeval_load((struct timeval*) CMSG_DATA(cmsg))); } - if (!triple_timestamp_is_set(&t)) - triple_timestamp_get(&t); - - if (!IN_SET(message->type, DHCP6_MESSAGE_ADVERTISE, DHCP6_MESSAGE_REPLY, DHCP6_MESSAGE_RECONFIGURE)) { - const char *type_str = dhcp6_message_type_to_string(message->type); - if (type_str) - log_dhcp6_client(client, "Received unexpected %s message, ignoring.", type_str); - else - log_dhcp6_client(client, "Received unsupported message type %u, ignoring.", message->type); - return 0; - } - if (client->transaction_id != (message->transaction_id & htobe32(0x00ffffff))) return 0; switch (client->state) { case DHCP6_STATE_INFORMATION_REQUEST: - r = client_receive_reply(client, message, len, &t, server_address); - if (r < 0) { - log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m"); + if (client_process_information(client, message, len, &t, server_address) < 0) return 0; - } - - client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST); - - client_start(client, DHCP6_STATE_STOPPED); - break; case DHCP6_STATE_SOLICITATION: - r = client_receive_advertise(client, message, len, &t, server_address); - if (r < 0) { - log_dhcp6_client_errno(client, r, "Failed to process received advertise message, ignoring: %m"); + if (client_process_advertise_or_rapid_commit_reply(client, message, len, &t, server_address) < 0) return 0; - } + break; - if (r == DHCP6_STATE_REQUEST) { - client_start(client, r); - break; - } - - _fallthrough_; /* for Solicitation Rapid Commit option check */ case DHCP6_STATE_REQUEST: case DHCP6_STATE_RENEW: case DHCP6_STATE_REBIND: - - r = client_receive_reply(client, message, len, &t, server_address); - if (r < 0) { - log_dhcp6_client_errno(client, r, "Failed to process received reply message, ignoring: %m"); + if (client_process_reply(client, message, len, &t, server_address) < 0) return 0; - } - - if (r == DHCP6_STATE_BOUND) { - r = client_start(client, DHCP6_STATE_BOUND); - if (r < 0) { - client_stop(client, r); - return 0; - } - - client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE); - } - break; case DHCP6_STATE_BOUND: - - break; - case DHCP6_STATE_STOPPED: - return 0; default: assert_not_reached(); } - log_dhcp6_client(client, "Recv %s", - dhcp6_message_type_to_string(message->type)); - return 0; } -static int client_get_lifetime(sd_dhcp6_client *client, uint32_t *lifetime_t1, - uint32_t *lifetime_t2) { - assert_return(client, -EINVAL); - assert_return(client->lease, -EINVAL); - - if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) { - *lifetime_t1 = be32toh(client->lease->ia.ia_na.lifetime_t1); - *lifetime_t2 = be32toh(client->lease->ia.ia_na.lifetime_t2); - - return 0; - } - - if (FLAGS_SET(client->request_ia, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) { - *lifetime_t1 = be32toh(client->lease->pd.ia_pd.lifetime_t1); - *lifetime_t2 = be32toh(client->lease->pd.ia_pd.lifetime_t2); - - return 0; - } - - return -ENOMSG; -} - -static int client_start(sd_dhcp6_client *client, DHCP6State state) { - int r; - usec_t timeout, time_now; - uint32_t lifetime_t1, lifetime_t2; - - assert_return(client, -EINVAL); - assert_return(client->event, -EINVAL); - assert_return(client->ifindex > 0, -EINVAL); - assert_return(client->state != state, -EINVAL); - - (void) event_source_disable(client->timeout_resend_expire); - (void) event_source_disable(client->timeout_resend); - client->retransmit_time = 0; - client->retransmit_count = 0; - - r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); - if (r < 0) - return r; - - if (!client->receive_message) { - r = sd_event_add_io(client->event, &client->receive_message, - client->fd, EPOLLIN, client_receive_message, - client); - if (r < 0) - goto error; - - r = sd_event_source_set_priority(client->receive_message, - client->event_priority); - if (r < 0) - goto error; - - r = sd_event_source_set_description(client->receive_message, - "dhcp6-receive-message"); - if (r < 0) - goto error; - } - - switch (state) { - case DHCP6_STATE_STOPPED: - if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { - client->state = DHCP6_STATE_STOPPED; - - return 0; - } - - _fallthrough_; - case DHCP6_STATE_SOLICITATION: - client->state = DHCP6_STATE_SOLICITATION; - - break; - - case DHCP6_STATE_INFORMATION_REQUEST: - case DHCP6_STATE_REQUEST: - case DHCP6_STATE_RENEW: - case DHCP6_STATE_REBIND: - - client->state = state; - - break; - - case DHCP6_STATE_BOUND: - - r = client_get_lifetime(client, &lifetime_t1, &lifetime_t2); - if (r < 0) - goto error; - - if (lifetime_t1 == 0xffffffff || lifetime_t2 == 0xffffffff) { - log_dhcp6_client(client, "Infinite T1 0x%08x or T2 0x%08x", - lifetime_t1, lifetime_t2); - - return 0; - } - - timeout = client_timeout_compute_random(lifetime_t1 * USEC_PER_SEC); - - log_dhcp6_client(client, "T1 expires in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); - - r = event_reset_time(client->event, &client->timeout_t1, - clock_boottime_or_monotonic(), - time_now + timeout, 10 * USEC_PER_SEC, - client_timeout_t1, client, - client->event_priority, "dhcp6-t1-timeout", true); - if (r < 0) - goto error; - - timeout = client_timeout_compute_random(lifetime_t2 * USEC_PER_SEC); - - log_dhcp6_client(client, "T2 expires in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); - - r = event_reset_time(client->event, &client->timeout_t2, - clock_boottime_or_monotonic(), - time_now + timeout, 10 * USEC_PER_SEC, - client_timeout_t2, client, - client->event_priority, "dhcp6-t2-timeout", true); - if (r < 0) - goto error; - - client->state = state; - - return 0; - default: - assert_not_reached(); - } - - client->transaction_id = random_u32() & htobe32(0x00ffffff); - client->transaction_start = time_now; - - r = event_reset_time(client->event, &client->timeout_resend, - clock_boottime_or_monotonic(), - 0, 0, - client_timeout_resend, client, - client->event_priority, "dhcp6-resend-timeout", true); - if (r < 0) - goto error; - - return 0; - - error: - client_reset(client); - return r; -} - int sd_dhcp6_client_stop(sd_dhcp6_client *client) { if (!client) return 0; client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP); + client->receive_message = sd_event_source_unref(client->receive_message); client->fd = safe_close(client->fd); return 0; @@ -1756,16 +1286,12 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) { assert_return(client->event, -EINVAL); assert_return(client->ifindex > 0, -EINVAL); assert_return(in6_addr_is_link_local(&client->local_address) > 0, -EINVAL); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); + assert_return(client->information_request || client->request_ia != 0, -EINVAL); - if (client->state != DHCP6_STATE_STOPPED) - return -EBUSY; - - if (!client->information_request && client->request_ia == 0) - return -EINVAL; - - r = client_reset(client); - if (r < 0) - return r; + /* Even if the client is in the STOPPED state, the lease acquired in the previous information + * request may be stored. */ + client->lease = sd_dhcp6_lease_unref(client->lease); r = client_ensure_iaid(client); if (r < 0) @@ -1780,7 +1306,7 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) { if (r < 0) { _cleanup_free_ char *p = NULL; - (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p); + (void) in6_addr_to_string(&client->local_address, &p); return log_dhcp6_client_errno(client, r, "Failed to bind to UDP socket at address %s: %m", strna(p)); } @@ -1788,6 +1314,24 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) { client->fd = r; } + if (!client->receive_message) { + _cleanup_(sd_event_source_disable_unrefp) sd_event_source *s = NULL; + + r = sd_event_add_io(client->event, &s, client->fd, EPOLLIN, client_receive_message, client); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s, client->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(s, "dhcp6-receive-message"); + if (r < 0) + return r; + + client->receive_message = TAKE_PTR(s); + } + if (client->information_request) { usec_t t = now(CLOCK_MONOTONIC); @@ -1798,10 +1342,10 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) { state = DHCP6_STATE_INFORMATION_REQUEST; } - log_dhcp6_client(client, "Started in %s mode", + log_dhcp6_client(client, "Starting in %s mode", client->information_request ? "Information request" : "Managed"); - return client_start(client, state); + return client_start_transaction(client, state); } int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64_t priority) { @@ -1809,6 +1353,7 @@ int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64 assert_return(client, -EINVAL); assert_return(!client->event, -EBUSY); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); if (event) client->event = sd_event_ref(event); @@ -1825,6 +1370,7 @@ int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) { assert_return(client, -EINVAL); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); client->event = sd_event_unref(client->event); @@ -1838,24 +1384,26 @@ sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) { } static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) { - assert(client); + if (!client) + return NULL; - client->timeout_resend = sd_event_source_unref(client->timeout_resend); - client->timeout_resend_expire = sd_event_source_unref(client->timeout_resend_expire); - client->timeout_t1 = sd_event_source_unref(client->timeout_t1); - client->timeout_t2 = sd_event_source_unref(client->timeout_t2); + sd_dhcp6_lease_unref(client->lease); - client_reset(client); + sd_event_source_disable_unref(client->receive_message); + sd_event_source_disable_unref(client->timeout_resend); + sd_event_source_disable_unref(client->timeout_expire); + sd_event_source_disable_unref(client->timeout_t1); + sd_event_source_disable_unref(client->timeout_t2); + sd_event_unref(client->event); client->fd = safe_close(client->fd); - sd_dhcp6_client_detach_event(client); - free(client->req_opts); free(client->fqdn); free(client->mudurl); - + dhcp6_ia_clear_addresses(&client->ia_pd); ordered_hashmap_free(client->extra_options); + ordered_set_free(client->vendor_options); strv_free(client->user_class); strv_free(client->vendor_class); free(client->ifname); @@ -1891,8 +1439,6 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret) { .request_ia = DHCP6_REQUEST_IA_NA | DHCP6_REQUEST_IA_PD, .fd = -1, .req_opts_len = ELEMENTSOF(default_req_opts), - .hint_pd_prefix.iapdprefix.lifetime_preferred = (be32_t) -1, - .hint_pd_prefix.iapdprefix.lifetime_valid = (be32_t) -1, .req_opts = TAKE_PTR(req_opts), }; diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index a47e6a0199..941de2f68c 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -6,10 +6,21 @@ #include #include "alloc-util.h" +#include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" -#include "dhcp6-protocol.h" #include "strv.h" -#include "util.h" + +#define IRT_DEFAULT (1 * USEC_PER_DAY) +#define IRT_MINIMUM (600 * USEC_PER_SEC) + +static void dhcp6_lease_set_timestamp(sd_dhcp6_lease *lease, const triple_timestamp *timestamp) { + assert(lease); + + if (timestamp && triple_timestamp_is_set(timestamp)) + lease->timestamp = *timestamp; + else + triple_timestamp_get(&lease->timestamp); +} int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret) { assert_return(lease, -EINVAL); @@ -24,6 +35,69 @@ int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_ return 0; } +static usec_t sec2usec(uint32_t sec) { + return sec == UINT32_MAX ? USEC_INFINITY : sec * USEC_PER_SEC; +} + +static void dhcp6_lease_set_lifetime(sd_dhcp6_lease *lease) { + uint32_t t1 = UINT32_MAX, t2 = UINT32_MAX, min_valid_lt = UINT32_MAX; + DHCP6Address *a; + + assert(lease); + assert(lease->ia_na || lease->ia_pd); + + if (lease->ia_na) { + t1 = MIN(t1, be32toh(lease->ia_na->header.lifetime_t1)); + t2 = MIN(t2, be32toh(lease->ia_na->header.lifetime_t2)); + + LIST_FOREACH(addresses, a, lease->ia_na->addresses) + min_valid_lt = MIN(min_valid_lt, be32toh(a->iaaddr.lifetime_valid)); + } + + if (lease->ia_pd) { + t1 = MIN(t1, be32toh(lease->ia_pd->header.lifetime_t1)); + t2 = MIN(t2, be32toh(lease->ia_pd->header.lifetime_t2)); + + LIST_FOREACH(addresses, a, lease->ia_pd->addresses) + min_valid_lt = MIN(min_valid_lt, be32toh(a->iapdprefix.lifetime_valid)); + } + + if (t2 == 0 || t2 > min_valid_lt) { + /* If T2 is zero or longer than the minimum valid lifetime of the addresses or prefixes, + * then adjust lifetime with it. */ + t1 = min_valid_lt / 2; + t2 = min_valid_lt / 10 * 8; + } + + lease->lifetime_valid = sec2usec(min_valid_lt); + lease->lifetime_t1 = sec2usec(t1); + lease->lifetime_t2 = sec2usec(t2); +} + +int dhcp6_lease_get_lifetime(sd_dhcp6_lease *lease, usec_t *ret_t1, usec_t *ret_t2, usec_t *ret_valid) { + assert(lease); + + if (!lease->ia_na && !lease->ia_pd) + return -ENODATA; + + if (ret_t1) + *ret_t1 = lease->lifetime_t1; + if (ret_t2) + *ret_t2 = lease->lifetime_t2; + if (ret_valid) + *ret_valid = lease->lifetime_valid; + return 0; +} + +static void dhcp6_lease_set_server_address(sd_dhcp6_lease *lease, const struct in6_addr *server_address) { + assert(lease); + + if (server_address) + lease->server_address = *server_address; + else + lease->server_address = (struct in6_addr) {}; +} + int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *ret) { assert_return(lease, -EINVAL); assert_return(ret, -EINVAL); @@ -32,55 +106,37 @@ int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *re return 0; } -int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) { - DHCP6Address *addr; - uint32_t valid = 0, t; +void dhcp6_ia_clear_addresses(DHCP6IA *ia) { + DHCP6Address *a, *n; - assert_return(ia, -EINVAL); - assert_return(expire, -EINVAL); + assert(ia); - LIST_FOREACH(addresses, addr, ia->addresses) { - t = be32toh(addr->iaaddr.lifetime_valid); - if (valid < t) - valid = t; - } + LIST_FOREACH_SAFE(addresses, a, n, ia->addresses) + free(a); - t = be32toh(ia->ia_na.lifetime_t2); - if (t > valid) - return -EINVAL; - - *expire = valid - t; - - return 0; + ia->addresses = NULL; } -DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) { - DHCP6Address *address; - +DHCP6IA *dhcp6_ia_free(DHCP6IA *ia) { if (!ia) return NULL; - while (ia->addresses) { - address = ia->addresses; + dhcp6_ia_clear_addresses(ia); - LIST_REMOVE(addresses, ia->addresses, address); - - free(address); - } - - return NULL; + return mfree(ia); } int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) { - uint8_t *clientid; + uint8_t *clientid = NULL; - assert_return(lease, -EINVAL); - assert_return(id, -EINVAL); - assert_return(len > 0, -EINVAL); + assert(lease); + assert(id || len == 0); - clientid = memdup(id, len); - if (!clientid) - return -ENOMEM; + if (len > 0) { + clientid = memdup(id, len); + if (!clientid) + return -ENOMEM; + } free_and_replace(lease->clientid, clientid); lease->clientid_len = len; @@ -89,7 +145,7 @@ int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t le } int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) { - assert_return(lease, -EINVAL); + assert(lease); if (!lease->clientid) return -ENODATA; @@ -103,15 +159,16 @@ int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *re } int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) { - uint8_t *serverid; + uint8_t *serverid = NULL; - assert_return(lease, -EINVAL); - assert_return(id, -EINVAL); - assert_return(len > 0, -EINVAL); + assert(lease); + assert(id || len == 0); - serverid = memdup(id, len); - if (!serverid) - return -ENOMEM; + if (len > 0) { + serverid = memdup(id, len); + if (!serverid) + return -ENOMEM; + } free_and_replace(lease->serverid, serverid); lease->serverid_len = len; @@ -120,7 +177,7 @@ int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t le } int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) { - assert_return(lease, -EINVAL); + assert(lease); if (!lease->serverid) return -ENODATA; @@ -129,107 +186,99 @@ int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *re *ret_id = lease->serverid; if (ret_len) *ret_len = lease->serverid_len; - return 0; } int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) { - assert_return(lease, -EINVAL); + assert(lease); lease->preference = preference; - return 0; } -int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference) { - assert_return(preference, -EINVAL); - - if (!lease) - return -EINVAL; - - *preference = lease->preference; +int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *ret) { + assert(lease); + assert(ret); + *ret = lease->preference; return 0; } int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) { - assert_return(lease, -EINVAL); + assert(lease); lease->rapid_commit = true; - return 0; } -int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit) { - assert_return(lease, -EINVAL); - assert_return(rapid_commit, -EINVAL); - - *rapid_commit = lease->rapid_commit; +int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *ret) { + assert(lease); + assert(ret); + *ret = lease->rapid_commit; return 0; } -int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *addr, - uint32_t *lifetime_preferred, - uint32_t *lifetime_valid) { +int sd_dhcp6_lease_get_address( + sd_dhcp6_lease *lease, + struct in6_addr *ret_addr, + uint32_t *ret_lifetime_preferred, + uint32_t *ret_lifetime_valid) { + assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); - assert_return(lifetime_preferred, -EINVAL); - assert_return(lifetime_valid, -EINVAL); if (!lease->addr_iter) - return -ENOMSG; + return -ENODATA; - memcpy(addr, &lease->addr_iter->iaaddr.address, - sizeof(struct in6_addr)); - *lifetime_preferred = - be32toh(lease->addr_iter->iaaddr.lifetime_preferred); - *lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid); + if (ret_addr) + *ret_addr = lease->addr_iter->iaaddr.address; + if (ret_lifetime_preferred) + *ret_lifetime_preferred = be32toh(lease->addr_iter->iaaddr.lifetime_preferred); + if (ret_lifetime_valid) + *ret_lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid); lease->addr_iter = lease->addr_iter->addresses_next; - return 0; } void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) { if (lease) - lease->addr_iter = lease->ia.addresses; + lease->addr_iter = lease->ia_na ? lease->ia_na->addresses : NULL; } -int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix, - uint8_t *prefix_len, - uint32_t *lifetime_preferred, - uint32_t *lifetime_valid) { +int sd_dhcp6_lease_get_pd( + sd_dhcp6_lease *lease, + struct in6_addr *ret_prefix, + uint8_t *ret_prefix_len, + uint32_t *ret_lifetime_preferred, + uint32_t *ret_lifetime_valid) { + assert_return(lease, -EINVAL); - assert_return(prefix, -EINVAL); - assert_return(prefix_len, -EINVAL); - assert_return(lifetime_preferred, -EINVAL); - assert_return(lifetime_valid, -EINVAL); if (!lease->prefix_iter) - return -ENOMSG; + return -ENODATA; - memcpy(prefix, &lease->prefix_iter->iapdprefix.address, - sizeof(struct in6_addr)); - *prefix_len = lease->prefix_iter->iapdprefix.prefixlen; - *lifetime_preferred = - be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred); - *lifetime_valid = - be32toh(lease->prefix_iter->iapdprefix.lifetime_valid); + if (ret_prefix) + *ret_prefix = lease->prefix_iter->iapdprefix.address; + if (ret_prefix_len) + *ret_prefix_len = lease->prefix_iter->iapdprefix.prefixlen; + if (ret_lifetime_preferred) + *ret_lifetime_preferred = be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred); + if (ret_lifetime_valid) + *ret_lifetime_valid = be32toh(lease->prefix_iter->iapdprefix.lifetime_valid); lease->prefix_iter = lease->prefix_iter->addresses_next; - return 0; } void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) { if (lease) - lease->prefix_iter = lease->pd.addresses; + lease->prefix_iter = lease->ia_pd ? lease->ia_pd->addresses : NULL; } int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { - assert_return(lease, -EINVAL); - assert_return(optval, -EINVAL); + assert(lease); + assert(optval || optlen == 0); if (optlen == 0) return 0; @@ -241,7 +290,7 @@ int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret) { assert_return(lease, -EINVAL); if (!lease->dns) - return -ENOENT; + return -ENODATA; if (ret) *ret = lease->dns; @@ -253,8 +302,8 @@ int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t _cleanup_strv_free_ char **domains = NULL; int r; - assert_return(lease, -EINVAL); - assert_return(optval, -EINVAL); + assert(lease); + assert(optval || optlen == 0); if (optlen == 0) return 0; @@ -271,7 +320,7 @@ int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) { assert_return(ret, -EINVAL); if (!lease->domains) - return -ENOENT; + return -ENODATA; *ret = lease->domains; return strv_length(lease->domains); @@ -280,8 +329,8 @@ int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) { int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { int r; - assert_return(lease, -EINVAL); - assert_return(optval, -EINVAL); + assert(lease); + assert(optval || optlen == 0); for (size_t offset = 0; offset < optlen;) { const uint8_t *subval; @@ -296,7 +345,7 @@ int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t opt case DHCP6_NTP_SUBOPTION_SRV_ADDR: case DHCP6_NTP_SUBOPTION_MC_ADDR: if (sublen != 16) - return 0; + return -EINVAL; r = dhcp6_option_parse_addresses(subval, sublen, &lease->ntp, &lease->ntp_count); if (r < 0) @@ -326,8 +375,8 @@ int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t opt } int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { - assert_return(lease, -EINVAL); - assert_return(optval, -EINVAL); + assert(lease); + assert(optval || optlen == 0); if (optlen == 0) return 0; @@ -352,14 +401,14 @@ int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr ** return lease->sntp_count; } - return -ENOENT; + return -ENODATA; } int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) { assert_return(lease, -EINVAL); if (!lease->ntp_fqdn) - return -ENOENT; + return -ENODATA; if (ret) *ret = lease->ntp_fqdn; @@ -367,11 +416,14 @@ int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) { } int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { - int r; char *fqdn; + int r; - assert_return(lease, -EINVAL); - assert_return(optval, -EINVAL); + assert(lease); + assert(optval || optlen == 0); + + if (optlen == 0) + return 0; if (optlen < 2) return -ENODATA; @@ -390,20 +442,222 @@ int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret) { assert_return(ret, -EINVAL); if (!lease->fqdn) - return -ENOENT; + return -ENODATA; *ret = lease->fqdn; return 0; } +static int dhcp6_lease_parse_message( + sd_dhcp6_client *client, + sd_dhcp6_lease *lease, + const DHCP6Message *message, + size_t len) { + + usec_t irt = IRT_DEFAULT; + int r; + + assert(client); + assert(lease); + assert(message); + assert(len >= sizeof(DHCP6Message)); + + len -= sizeof(DHCP6Message); + for (size_t offset = 0; offset < len;) { + uint16_t optcode; + size_t optlen; + const uint8_t *optval; + + r = dhcp6_option_parse(message->options, len, &offset, &optcode, &optlen, &optval); + if (r < 0) + return r; + + switch (optcode) { + case SD_DHCP6_OPTION_CLIENTID: + if (dhcp6_lease_get_clientid(lease, NULL, NULL) >= 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple client IDs", + dhcp6_message_type_to_string(message->type)); + + r = dhcp6_lease_set_clientid(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_SERVERID: + if (dhcp6_lease_get_serverid(lease, NULL, NULL) >= 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple server IDs", + dhcp6_message_type_to_string(message->type)); + + r = dhcp6_lease_set_serverid(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_PREFERENCE: + if (optlen != 1) + return -EINVAL; + + r = dhcp6_lease_set_preference(lease, optval[0]); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_STATUS_CODE: { + _cleanup_free_ char *msg = NULL; + + r = dhcp6_option_parse_status(optval, optlen, &msg); + if (r < 0) + return r; + + if (r > 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received %s message with non-zero status: %s%s%s", + dhcp6_message_type_to_string(message->type), + strempty(msg), isempty(msg) ? "" : ": ", + dhcp6_message_status_to_string(r)); + break; + } + case SD_DHCP6_OPTION_IA_NA: { + _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL; + + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + log_dhcp6_client(client, "Ignoring IA NA option in information requesting mode."); + break; + } + + r = dhcp6_option_parse_ia(client, client->ia_na.header.id, optcode, optlen, optval, &ia); + if (r == -ENOMEM) + return r; + if (r < 0) + continue; + + if (lease->ia_na) { + log_dhcp6_client(client, "Received duplicate matching IA_NA option, ignoring."); + continue; + } + + dhcp6_ia_free(lease->ia_na); + lease->ia_na = TAKE_PTR(ia); + break; + } + case SD_DHCP6_OPTION_IA_PD: { + _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL; + + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + log_dhcp6_client(client, "Ignoring IA PD option in information requesting mode."); + break; + } + + r = dhcp6_option_parse_ia(client, client->ia_pd.header.id, optcode, optlen, optval, &ia); + if (r == -ENOMEM) + return r; + if (r < 0) + continue; + + if (lease->ia_pd) { + log_dhcp6_client(client, "Received duplicate matching IA_PD option, ignoring."); + continue; + } + + dhcp6_ia_free(lease->ia_pd); + lease->ia_pd = TAKE_PTR(ia); + break; + } + case SD_DHCP6_OPTION_RAPID_COMMIT: + r = dhcp6_lease_set_rapid_commit(lease); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_DNS_SERVERS: + r = dhcp6_lease_add_dns(lease, optval, optlen); + if (r < 0) + log_dhcp6_client_errno(client, r, "Failed to parse DNS server option, ignoring: %m"); + + break; + + case SD_DHCP6_OPTION_DOMAIN_LIST: + r = dhcp6_lease_add_domains(lease, optval, optlen); + if (r < 0) + log_dhcp6_client_errno(client, r, "Failed to parse domain list option, ignoring: %m"); + + break; + + case SD_DHCP6_OPTION_NTP_SERVER: + r = dhcp6_lease_add_ntp(lease, optval, optlen); + if (r < 0) + log_dhcp6_client_errno(client, r, "Failed to parse NTP server option, ignoring: %m"); + + break; + + case SD_DHCP6_OPTION_SNTP_SERVERS: + r = dhcp6_lease_add_sntp(lease, optval, optlen); + if (r < 0) + log_dhcp6_client_errno(client, r, "Failed to parse SNTP server option, ignoring: %m"); + + break; + + case SD_DHCP6_OPTION_CLIENT_FQDN: + r = dhcp6_lease_set_fqdn(lease, optval, optlen); + if (r < 0) + log_dhcp6_client_errno(client, r, "Failed to parse FQDN option, ignoring: %m"); + + break; + + case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME: + if (optlen != 4) + return -EINVAL; + + irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC; + break; + } + } + + uint8_t *clientid; + size_t clientid_len; + if (dhcp6_lease_get_clientid(lease, &clientid, &clientid_len) < 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "%s message does not contain client ID. Ignoring.", + dhcp6_message_type_to_string(message->type)); + + if (memcmp_nn(clientid, clientid_len, &client->duid, client->duid_len) != 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "The client ID in %s message does not match. Ignoring.", + dhcp6_message_type_to_string(message->type)); + + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM); + log_dhcp6_client(client, "New information request will be refused in %s.", + FORMAT_TIMESPAN(client->information_refresh_time_usec, USEC_PER_SEC)); + + } else { + r = dhcp6_lease_get_serverid(lease, NULL, NULL); + if (r < 0) + return log_dhcp6_client_errno(client, r, "%s has no server id", + dhcp6_message_type_to_string(message->type)); + + if (!lease->ia_na && !lease->ia_pd) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "No IA_PD prefix or IA_NA address received. Ignoring."); + + dhcp6_lease_set_lifetime(lease); + } + + return 0; +} + static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) { if (!lease) return NULL; free(lease->clientid); free(lease->serverid); - dhcp6_lease_free_ia(&lease->ia); - dhcp6_lease_free_ia(&lease->pd); + dhcp6_ia_free(lease->ia_na); + dhcp6_ia_free(lease->ia_pd); free(lease->dns); free(lease->fqdn); strv_free(lease->domains); @@ -419,14 +673,47 @@ DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_lease, sd_dhcp6_lease, dhcp6_lease_free); int dhcp6_lease_new(sd_dhcp6_lease **ret) { sd_dhcp6_lease *lease; - lease = new0(sd_dhcp6_lease, 1); + assert(ret); + + lease = new(sd_dhcp6_lease, 1); if (!lease) return -ENOMEM; - lease->n_ref = 1; - - LIST_HEAD_INIT(lease->ia.addresses); + *lease = (sd_dhcp6_lease) { + .n_ref = 1, + }; *ret = lease; return 0; } + +int dhcp6_lease_new_from_message( + sd_dhcp6_client *client, + const DHCP6Message *message, + size_t len, + const triple_timestamp *timestamp, + const struct in6_addr *server_address, + sd_dhcp6_lease **ret) { + + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + int r; + + assert(client); + assert(message); + assert(len >= sizeof(DHCP6Message)); + assert(ret); + + r = dhcp6_lease_new(&lease); + if (r < 0) + return r; + + dhcp6_lease_set_timestamp(lease, timestamp); + dhcp6_lease_set_server_address(lease, server_address); + + r = dhcp6_lease_parse_message(client, lease, message, len); + if (r < 0) + return r; + + *ret = TAKE_PTR(lease); + return 0; +} diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 4b11cdbc16..476dcce85e 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -170,7 +170,7 @@ static int check_options(uint8_t code, uint8_t len, const void *option, void *us struct duid duid; size_t duid_len; - assert_se(dhcp_identifier_set_duid_en(&duid, &duid_len) >= 0); + assert_se(dhcp_identifier_set_duid_en(/* test_mode = */ true, &duid, &duid_len) >= 0); assert_se(dhcp_identifier_set_iaid(42, mac_addr, ETH_ALEN, true, /* use_mac = */ true, &iaid) >= 0); assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid_len); diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index bcd0134a8d..3b25df7625 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -26,19 +26,62 @@ #include "tests.h" #include "time-util.h" -static struct ether_addr mac_addr = { - .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} +#define DHCP6_CLIENT_EVENT_TEST_ADVERTISED 77 +#define IA_ID_BYTES \ + 0x0e, 0xcf, 0xa3, 0x7d +#define IA_NA_ADDRESS1_BYTES \ + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3, 0x09, 0x3c, 0x55, 0xad +#define IA_NA_ADDRESS2_BYTES \ + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3, 0x09, 0x3c, 0x55, 0xae +#define IA_PD_PREFIX1_BYTES \ + 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +#define IA_PD_PREFIX2_BYTES \ + 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +#define DNS1_BYTES \ + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 +#define DNS2_BYTES \ + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 +#define SNTP1_BYTES \ + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 +#define SNTP2_BYTES \ + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 +#define NTP1_BYTES \ + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 +#define NTP2_BYTES \ + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 +#define CLIENT_ID_BYTES \ + 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2 +#define SERVER_ID_BYTES \ + 0x00, 0x01, 0x00, 0x01, 0x19, 0x40, 0x5c, 0x53, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53 + +static const struct in6_addr local_address = + { { { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, } } }; +static const struct in6_addr mcast_address = + IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; +static const struct in6_addr ia_na_address1 = { { { IA_NA_ADDRESS1_BYTES } } }; +static const struct in6_addr ia_na_address2 = { { { IA_NA_ADDRESS2_BYTES } } }; +static const struct in6_addr ia_pd_prefix1 = { { { IA_PD_PREFIX1_BYTES } } }; +static const struct in6_addr ia_pd_prefix2 = { { { IA_PD_PREFIX2_BYTES } } }; +static const struct in6_addr dns1 = { { { DNS1_BYTES } } }; +static const struct in6_addr dns2 = { { { DNS2_BYTES } } }; +static const struct in6_addr sntp1 = { { { SNTP1_BYTES } } }; +static const struct in6_addr sntp2 = { { { SNTP2_BYTES } } }; +static const struct in6_addr ntp1 = { { { NTP1_BYTES } } }; +static const struct in6_addr ntp2 = { { { NTP2_BYTES } } }; +static const uint8_t client_id[] = { CLIENT_ID_BYTES }; +static const uint8_t server_id[] = { SERVER_ID_BYTES }; +static const struct ether_addr mac = { + .ether_addr_octet = { 'A', 'B', 'C', '1', '2', '3' }, }; - -static sd_event_source *hangcheck; -static int test_dhcp_fd[2]; +static int test_fd[2] = { -1, -1, }; static int test_ifindex = 42; -static int test_client_message_num; -static be32_t test_iaid = 0; -static uint8_t test_duid[14] = { }; +static unsigned test_client_sent_message_count = 0; +static sd_dhcp6_client *client_ref = NULL; -static void test_client_basic(sd_event *e) { - sd_dhcp6_client *client; +STATIC_DESTRUCTOR_REGISTER(client_ref, sd_dhcp6_client_unrefp); + +static void test_client_basic(void) { + _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; int v; log_debug("/* %s */", __func__); @@ -46,16 +89,10 @@ static void test_client_basic(sd_event *e) { assert_se(sd_dhcp6_client_new(&client) >= 0); assert_se(client); - assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0); - assert_se(sd_dhcp6_client_set_ifindex(client, 15) == 0); - assert_se(sd_dhcp6_client_set_ifindex(client, -42) == -EINVAL); - assert_se(sd_dhcp6_client_set_ifindex(client, -1) == -EINVAL); assert_se(sd_dhcp6_client_set_ifindex(client, 42) >= 0); - assert_se(sd_dhcp6_client_set_mac(client, (const uint8_t *) &mac_addr, - sizeof (mac_addr), - ARPHRD_ETHER) >= 0); + assert_se(sd_dhcp6_client_set_mac(client, mac.ether_addr_octet, sizeof(mac), ARPHRD_ETHER) >= 0); assert_se(sd_dhcp6_client_set_fqdn(client, "host") == 1); assert_se(sd_dhcp6_client_set_fqdn(client, "host.domain") == 1); @@ -107,51 +144,44 @@ static void test_client_basic(sd_event *e) { assert_se(sd_dhcp6_client_set_callback(client, NULL, NULL) >= 0); assert_se(sd_dhcp6_client_detach_event(client) >= 0); - assert_se(!sd_dhcp6_client_unref(client)); } static void test_parse_domain(void) { + _cleanup_free_ char *domain = NULL; + _cleanup_strv_free_ char **list = NULL; uint8_t *data; - char *domain; - char **list; - int r; log_debug("/* %s */", __func__); data = (uint8_t []) { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0 }; - r = dhcp6_option_parse_domainname(data, 13, &domain); - assert_se(r == 0); + assert_se(dhcp6_option_parse_domainname(data, 13, &domain) >= 0); assert_se(domain); assert_se(streq(domain, "example.com")); - free(domain); + domain = mfree(domain); data = (uint8_t []) { 4, 't', 'e', 's', 't' }; - r = dhcp6_option_parse_domainname(data, 5, &domain); - assert_se(r == 0); + assert_se(dhcp6_option_parse_domainname(data, 5, &domain) >= 0); assert_se(domain); assert_se(streq(domain, "test")); - free(domain); + domain = mfree(domain); data = (uint8_t []) { 0 }; - r = dhcp6_option_parse_domainname(data, 1, &domain); - assert_se(r < 0); + assert_se(dhcp6_option_parse_domainname(data, 1, &domain) < 0); data = (uint8_t []) { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 6, 'f', 'o', 'o', 'b', 'a', 'r', 0 }; - r = dhcp6_option_parse_domainname_list(data, 21, &list); - assert_se(r == 0); + assert_se(dhcp6_option_parse_domainname_list(data, 21, &list) >= 0); assert_se(list); assert_se(streq(list[0], "example.com")); assert_se(streq(list[1], "foobar")); - strv_free(list); + assert_se(!list[2]); + list = strv_free(list); data = (uint8_t []) { 1, 'a', 0, 20, 'b', 'c' }; - r = dhcp6_option_parse_domainname_list(data, 6, &list); - assert_se(r < 0); + assert_se(dhcp6_option_parse_domainname_list(data, 6, &list) < 0); data = (uint8_t []) { 0 , 0 }; - r = dhcp6_option_parse_domainname_list(data, 2, &list); - assert_se(r < 0); + assert_se(dhcp6_option_parse_domainname_list(data, 2, &list) < 0); } static void test_option(void) { @@ -297,16 +327,15 @@ static void test_option_status(void) { /* PD prefix status option */ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, }; + _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL; DHCP6Option *option; - DHCP6IA ia, pd; be32_t iaid; - int r = 0; + int r; log_debug("/* %s */", __func__); memcpy(&iaid, option1 + 4, sizeof(iaid)); - zero(ia); option = (DHCP6Option*) option1; assert_se(sizeof(option1) == sizeof(DHCP6Option) + be16toh(option->len)); @@ -315,89 +344,101 @@ static void test_option_status(void) { r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia); assert_se(r == -EINVAL); - assert_se(!ia.addresses); option->len = htobe16(17); r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia); assert_se(r == -EBADMSG); - assert_se(!ia.addresses); option->len = htobe16(sizeof(DHCP6Option)); r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia); assert_se(r == -EBADMSG); - assert_se(!ia.addresses); - zero(ia); option = (DHCP6Option*) option2; assert_se(sizeof(option2) == sizeof(DHCP6Option) + be16toh(option->len)); - r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia); assert_se(r == -ENODATA); - assert_se(!ia.addresses); - zero(ia); option = (DHCP6Option*) option3; assert_se(sizeof(option3) == sizeof(DHCP6Option) + be16toh(option->len)); - r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia); assert_se(r >= 0); - assert_se(ia.addresses); - dhcp6_lease_free_ia(&ia); + assert_se(ia); + assert_se(ia->addresses); + ia = dhcp6_ia_free(ia); - zero(pd); option = (DHCP6Option*) option4; assert_se(sizeof(option4) == sizeof(DHCP6Option) + be16toh(option->len)); - - r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &pd); + r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia); assert_se(r >= 0); - assert_se(pd.addresses); - assert_se(memcmp(&pd.ia_pd.id, &option4[4], 4) == 0); - assert_se(memcmp(&pd.ia_pd.lifetime_t1, &option4[8], 4) == 0); - assert_se(memcmp(&pd.ia_pd.lifetime_t2, &option4[12], 4) == 0); - dhcp6_lease_free_ia(&pd); + assert_se(ia); + assert_se(ia->addresses); + assert_se(memcmp(&ia->header.id, &option4[4], 4) == 0); + assert_se(memcmp(&ia->header.lifetime_t1, &option4[8], 4) == 0); + assert_se(memcmp(&ia->header.lifetime_t2, &option4[12], 4) == 0); + ia = dhcp6_ia_free(ia); - zero(pd); option = (DHCP6Option*) option5; assert_se(sizeof(option5) == sizeof(DHCP6Option) + be16toh(option->len)); - - r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &pd); + r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia); assert_se(r >= 0); - assert_se(pd.addresses); - dhcp6_lease_free_ia(&pd); + assert_se(ia); + assert_se(ia->addresses); + ia = dhcp6_ia_free(ia); } static void test_client_parse_message_issue_22099(void) { static const uint8_t msg[] = { - /* xid */ - 0x07, 0x7c, 0x4c, 0x16, - /* status code (zero length) */ - 0x00, 0x0e, 0x00, 0x00, - /* NTP servers (broken sub option and sub option length) */ - 0x00, 0x38, 0x00, 0x14, 0x01, 0x00, 0x10, 0x00, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xde, 0x15, 0xc8, 0xff, 0xfe, 0xef, 0x1e, 0x4e, - /* client ID */ - 0x00, 0x01, 0x00, 0x0e, 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x5c, 0x6b, 0x90, 0xec, 0xda, 0x95, - 0x15, 0x45, - /* server ID */ - 0x00, 0x02, 0x00, 0x0a, 0x00, 0x03, 0x00, 0x01, 0xdc, 0x15, 0xc8, 0xef, 0x1e, 0x4e, + /* Message type */ + DHCP6_MESSAGE_REPLY, + /* Transaction ID */ + 0x7c, 0x4c, 0x16, + /* Rapid commit */ + 0x00, SD_DHCP6_OPTION_RAPID_COMMIT, 0x00, 0x00, + /* NTP servers */ + 0x00, SD_DHCP6_OPTION_NTP_SERVER, 0x00, 0x14, + /* NTP server (broken sub option and sub option length) */ + 0x01, 0x00, 0x10, 0x00, + 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x15, 0xc8, 0xff, 0xfe, 0xef, 0x1e, 0x4e, + /* Client ID */ + 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e, + 0x00, 0x02, /* DUID-EN */ + 0x00, 0x00, 0xab, 0x11, /* pen */ + 0x5c, 0x6b, 0x90, 0xec, 0xda, 0x95, 0x15, 0x45, /* id */ + /* Server ID */ + 0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0a, + 0x00, 0x03, /* DUID-LL */ + 0x00, 0x01, /* htype */ + 0xdc, 0x15, 0xc8, 0xef, 0x1e, 0x4e, /* haddr */ /* preference */ - 0x00, 0x07, 0x00, 0x01, 0x00, + 0x00, SD_DHCP6_OPTION_PREFERENCE, 0x00, 0x01, + 0x00, /* DNS servers */ - 0x00, 0x17, 0x00, 0x10, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x15, 0xc8, 0xff, - 0xfe, 0xef, 0x1e, 0x4e, + 0x00, SD_DHCP6_OPTION_DNS_SERVERS, 0x00, 0x10, + 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x15, 0xc8, 0xff, 0xfe, 0xef, 0x1e, 0x4e, /* v6 pcp server */ - 0x00, 0x56, 0x00, 0x10, 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0x00, 0xde, 0x15, 0xc8, 0xff, - 0xfe, 0xef, 0x1e, 0x4e, + 0x00, SD_DHCP6_OPTION_V6_PCP_SERVER, 0x00, 0x10, + 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0x00, 0xde, 0x15, 0xc8, 0xff, 0xfe, 0xef, 0x1e, 0x4e, /* IA_NA */ - 0x00, 0x03, 0x00, 0x28, 0xcc, 0x59, 0x11, 0x7b, 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x0b, 0x40, - /* IA_NA (iaaddr) */ - 0x00, 0x05, 0x00, 0x18, 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0x00, 0x6a, 0x05, 0xca, 0xff, - 0xfe, 0xf1, 0x51, 0x53, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x1c, 0x20, + 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x28, + 0xcc, 0x59, 0x11, 0x7b, /* iaid */ + 0x00, 0x00, 0x07, 0x08, /* lifetime T1 */ + 0x00, 0x00, 0x0b, 0x40, /* lifetime T2 */ + /* IA_NA (iaaddr suboption) */ + 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18, + 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0x00, 0x6a, 0x05, 0xca, 0xff, 0xfe, 0xf1, 0x51, 0x53, /* address */ + 0x00, 0x00, 0x0e, 0x10, /* preferred lifetime */ + 0x00, 0x00, 0x1c, 0x20, /* valid lifetime */ /* IA_PD */ - 0x00, 0x19, 0x00, 0x29, 0xcc, 0x59, 0x11, 0x7b, 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x0b, 0x40, - /* IA_PD (iaprefix) */ - 0x00, 0x1a, 0x00, 0x19, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x1c, 0x20, 0x3a, 0x2a, 0x02, 0x81, - 0x0d, 0x98, 0x80, 0x37, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x29, + 0xcc, 0x59, 0x11, 0x7b, /* iaid */ + 0x00, 0x00, 0x07, 0x08, /* lifetime T1 */ + 0x00, 0x00, 0x0b, 0x40, /* lifetime T2 */ + /* IA_PD (iaprefix suboption) */ + 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19, + 0x00, 0x00, 0x0e, 0x10, /* preferred lifetime */ + 0x00, 0x00, 0x1c, 0x20, /* valid lifetime */ + 0x3a, /* prefixlen */ + 0x2a, 0x02, 0x81, 0x0d, 0x98, 0x80, 0x37, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* prefix */ }; static const uint8_t duid[] = { 0x00, 0x00, 0xab, 0x11, 0x5c, 0x6b, 0x90, 0xec, 0xda, 0x95, 0x15, 0x45, @@ -411,620 +452,570 @@ static void test_client_parse_message_issue_22099(void) { assert_se(sd_dhcp6_client_set_iaid(client, 0xcc59117b) >= 0); assert_se(sd_dhcp6_client_set_duid(client, 2, duid, sizeof(duid)) >= 0); - assert_se(dhcp6_lease_new(&lease) >= 0); - - assert_se(client_parse_message(client, (DHCP6Message*) msg, sizeof(msg), lease) >= 0); + assert_se(dhcp6_lease_new_from_message(client, (const DHCP6Message*) msg, sizeof(msg), NULL, NULL, &lease) >= 0); } -static uint8_t msg_advertise[198] = { - 0x02, 0x0f, 0xb4, 0xe5, 0x00, 0x01, 0x00, 0x0e, - 0x00, 0x01, 0x00, 0x01, 0x1a, 0x6b, 0xf3, 0x30, - 0x3c, 0x97, 0x0e, 0xcf, 0xa3, 0x7d, 0x00, 0x03, - 0x00, 0x5e, 0x0e, 0xcf, 0xa3, 0x7d, 0x00, 0x00, - 0x00, 0x50, 0x00, 0x00, 0x00, 0x78, 0x00, 0x05, - 0x00, 0x18, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, - 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3, 0x09, 0x3c, - 0x55, 0xad, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, - 0x00, 0xb4, 0x00, 0x0d, 0x00, 0x32, 0x00, 0x00, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x28, - 0x65, 0x73, 0x29, 0x20, 0x72, 0x65, 0x6e, 0x65, - 0x77, 0x65, 0x64, 0x2e, 0x20, 0x47, 0x72, 0x65, - 0x65, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x20, 0x66, - 0x72, 0x6f, 0x6d, 0x20, 0x70, 0x6c, 0x61, 0x6e, - 0x65, 0x74, 0x20, 0x45, 0x61, 0x72, 0x74, 0x68, - 0x00, 0x17, 0x00, 0x10, 0x20, 0x01, 0x0d, 0xb8, - 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x0b, - 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, - 0x72, 0x61, 0x00, 0x00, 0x1f, 0x00, 0x10, 0x20, - 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x02, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x19, - 0x40, 0x5c, 0x53, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, - 0x53, 0x00, 0x07, 0x00, 0x01, 0x00 +static const uint8_t msg_information_request[] = { + /* Message type */ + DHCP6_MESSAGE_INFORMATION_REQUEST, + /* Transaction ID */ + 0x0f, 0xb4, 0xe5, + /* MUD URL */ + /* ORO */ + 0x00, SD_DHCP6_OPTION_ORO, 0x00, 0x08, + 0x00, SD_DHCP6_OPTION_DNS_SERVERS, + 0x00, SD_DHCP6_OPTION_DOMAIN_LIST, + 0x00, SD_DHCP6_OPTION_NTP_SERVER, + 0x00, SD_DHCP6_OPTION_SNTP_SERVERS, + /* Client ID */ + 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e, + CLIENT_ID_BYTES, + /* Extra options */ + /* Elapsed time */ + 0x00, SD_DHCP6_OPTION_ELAPSED_TIME, 0x00, 0x02, + 0x00, 0x00, }; -static uint8_t msg_reply[191] = { - 0x07, 0xf7, 0x4e, 0x57, 0x00, 0x02, 0x00, 0x0e, - 0x00, 0x01, 0x00, 0x01, 0x19, 0x40, 0x5c, 0x53, - 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, 0x00, 0x01, - 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1a, 0x6b, - 0xf3, 0x30, 0x3c, 0x97, 0x0e, 0xcf, 0xa3, 0x7d, - 0x00, 0x03, 0x00, 0x4a, 0x0e, 0xcf, 0xa3, 0x7d, - 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x78, - 0x00, 0x05, 0x00, 0x18, 0x20, 0x01, 0x0d, 0xb8, - 0xde, 0xad, 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3, - 0x09, 0x3c, 0x55, 0xad, 0x00, 0x00, 0x00, 0x96, - 0x00, 0x00, 0x00, 0xb4, 0x00, 0x0d, 0x00, 0x1e, - 0x00, 0x00, 0x41, 0x6c, 0x6c, 0x20, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, - 0x77, 0x65, 0x72, 0x65, 0x20, 0x61, 0x73, 0x73, - 0x69, 0x67, 0x6e, 0x65, 0x64, 0x2e, 0x00, 0x17, - 0x00, 0x10, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, - 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x18, 0x00, 0x0b, 0x03, 0x6c, - 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, 0x72, 0x61, - 0x00, 0x00, 0x1f, 0x00, 0x10, 0x20, 0x01, 0x0d, - 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x27, 0x00, - 0x0e, 0x01, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x05, 0x69, 0x6e, 0x74, 0x72, 0x61 +static const uint8_t msg_solicit[] = { + /* Message type */ + DHCP6_MESSAGE_SOLICIT, + /* Transaction ID */ + 0x0f, 0xb4, 0xe5, + /* Rapid commit */ + 0x00, SD_DHCP6_OPTION_RAPID_COMMIT, 0x00, 0x00, + /* IA_NA */ + 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x0c, + IA_ID_BYTES, + 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */ + 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */ + /* IA_PD */ + 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x0c, + IA_ID_BYTES, + 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */ + 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */ + /* Client FQDN */ + 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x11, + DHCP6_FQDN_FLAG_S, + 0x04, 'h', 'o', 's', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, + /* User Class */ + /* Vendor Class */ + /* Vendor Options */ + /* MUD URL */ + /* ORO */ + 0x00, SD_DHCP6_OPTION_ORO, 0x00, 0x08, + 0x00, SD_DHCP6_OPTION_DNS_SERVERS, + 0x00, SD_DHCP6_OPTION_DOMAIN_LIST, + 0x00, SD_DHCP6_OPTION_NTP_SERVER, + 0x00, SD_DHCP6_OPTION_SNTP_SERVERS, + /* Client ID */ + 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e, + CLIENT_ID_BYTES, + /* Extra options */ + /* Elapsed time */ + 0x00, SD_DHCP6_OPTION_ELAPSED_TIME, 0x00, 0x02, + 0x00, 0x00, }; -static uint8_t fqdn_wire[16] = { - 0x04, 'h', 'o', 's', 't', 0x03, 'l', 'a', 'b', - 0x05, 'i', 'n', 't', 'r', 'a', 0x00 +static const uint8_t msg_request[] = { + /* Message type */ + DHCP6_MESSAGE_REQUEST, + /* Transaction ID */ + 0x00, 0x00, 0x00, + /* Server ID */ + 0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0e, + SERVER_ID_BYTES, + /* IA_NA */ + 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x44, + IA_ID_BYTES, + 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */ + 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */ + /* IA_NA (IAADDR suboption) */ + 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18, + IA_NA_ADDRESS1_BYTES, + 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0x00, /* valid lifetime */ + /* IA_NA (IAADDR suboption) */ + 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18, + IA_NA_ADDRESS2_BYTES, + 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0x00, /* valid lifetime */ + /* IA_PD */ + 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x46, + IA_ID_BYTES, + 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */ + 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */ + /* IA_PD (IA_PD_PREFIX suboption) */ + 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19, + 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0x00, /* valid lifetime */ + 0x40, /* prefixlen */ + IA_PD_PREFIX1_BYTES, + /* IA_PD (IA_PD_PREFIX suboption) */ + 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19, + 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0x00, /* valid lifetime */ + 0x40, /* prefixlen */ + IA_PD_PREFIX2_BYTES, + /* Client FQDN */ + 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x11, + DHCP6_FQDN_FLAG_S, + 0x04, 'h', 'o', 's', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, + /* User Class */ + /* Vendor Class */ + /* Vendor Options */ + /* MUD URL */ + /* ORO */ + 0x00, SD_DHCP6_OPTION_ORO, 0x00, 0x08, + 0x00, SD_DHCP6_OPTION_DNS_SERVERS, + 0x00, SD_DHCP6_OPTION_DOMAIN_LIST, + 0x00, SD_DHCP6_OPTION_NTP_SERVER, + 0x00, SD_DHCP6_OPTION_SNTP_SERVERS, + /* Client ID */ + 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e, + CLIENT_ID_BYTES, + /* Extra options */ + /* Elapsed time */ + 0x00, SD_DHCP6_OPTION_ELAPSED_TIME, 0x00, 0x02, + 0x00, 0x00, }; -static void test_advertise_option(sd_event *e) { - _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; - DHCP6Message *advertise = (DHCP6Message *)msg_advertise; - size_t len = sizeof(msg_advertise) - sizeof(DHCP6Message), pos = 0; - uint32_t lt_pref, lt_valid; - bool opt_clientid = false; - const struct in6_addr *addrs; - uint8_t preference = 255; - struct in6_addr addr; - char **domains; - uint8_t *opt; - int r; - be32_t val; +static const uint8_t msg_reply[] = { + /* Message type */ + DHCP6_MESSAGE_REPLY, + /* Transaction ID */ + 0x0f, 0xb4, 0xe5, + /* Client ID */ + 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e, + CLIENT_ID_BYTES, + /* Server ID */ + 0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0e, + SERVER_ID_BYTES, + /* Rapid commit */ + 0x00, SD_DHCP6_OPTION_RAPID_COMMIT, 0x00, 0x01, + 0x00, + /* IA_NA */ + 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x66, + IA_ID_BYTES, + 0x00, 0x00, 0x00, 0x50, /* lifetime T1 */ + 0x00, 0x00, 0x00, 0x78, /* lifetime T2 */ + /* IA_NA (IAADDR suboption) */ + 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18, + IA_NA_ADDRESS2_BYTES, + 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */ + /* IA_NA (IAADDR suboption) */ + 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18, + IA_NA_ADDRESS1_BYTES, + 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */ + /* IA_NA (status code suboption) */ + 0x00, SD_DHCP6_OPTION_STATUS_CODE, 0x00, 0x1e, + 0x00, 0x00, /* status code */ + 0x41, 0x6c, 0x6c, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, 0x77, 0x65, + 0x72, 0x65, 0x20, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x2e, /* status message */ + /* IA_PD */ + 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x46, + IA_ID_BYTES, + 0x00, 0x00, 0x00, 0x50, /* lifetime T1 */ + 0x00, 0x00, 0x00, 0x78, /* lifetime T2 */ + /* IA_PD (IA_PD_PREFIX suboption) */ + 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19, + 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */ + 0x40, /* prefixlen */ + IA_PD_PREFIX2_BYTES, + /* IA_PD (IA_PD_PREFIX suboption) */ + 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19, + 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */ + 0x40, /* prefixlen */ + IA_PD_PREFIX1_BYTES, + /* DNS servers */ + 0x00, SD_DHCP6_OPTION_DNS_SERVERS, 0x00, 0x20, + DNS1_BYTES, + DNS2_BYTES, + /* SNTP servers */ + 0x00, SD_DHCP6_OPTION_SNTP_SERVERS, 0x00, 0x20, + SNTP1_BYTES, + SNTP2_BYTES, + /* NTP servers */ + 0x00, SD_DHCP6_OPTION_NTP_SERVER, 0x00, 0x37, + /* NTP server (address suboption) */ + 0x00, DHCP6_NTP_SUBOPTION_SRV_ADDR, 0x00, 0x10, + NTP1_BYTES, + /* NTP server (address suboption) */ + 0x00, DHCP6_NTP_SUBOPTION_SRV_ADDR, 0x00, 0x10, + NTP2_BYTES, + /* NTP server (fqdn suboption) */ + 0x00, DHCP6_NTP_SUBOPTION_SRV_FQDN, 0x00, 0x0b, + 0x03, 'n', 't', 'p', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, + /* Domain list */ + 0x00, SD_DHCP6_OPTION_DOMAIN_LIST, 0x00, 0x0b, + 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, + /* Client FQDN */ + 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x12, + 0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', +}; +static const uint8_t msg_advertise[] = { + /* Message type */ + DHCP6_MESSAGE_ADVERTISE, + /* Transaction ID */ + 0x0f, 0xb4, 0xe5, + /* Client ID */ + 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e, + CLIENT_ID_BYTES, + /* Server ID */ + 0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0e, + SERVER_ID_BYTES, + /* Preference */ + 0x00, SD_DHCP6_OPTION_PREFERENCE, 0x00, 0x01, + 0xff, + /* IA_NA */ + 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x7a, + IA_ID_BYTES, + 0x00, 0x00, 0x00, 0x50, /* lifetime T1 */ + 0x00, 0x00, 0x00, 0x78, /* lifetime T2 */ + /* IA_NA (IAADDR suboption) */ + 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18, + IA_NA_ADDRESS2_BYTES, /* address */ + 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */ + /* IA_NA (IAADDR suboption) */ + 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18, + IA_NA_ADDRESS1_BYTES, /* address */ + 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */ + /* IA_NA (status code suboption) */ + 0x00, SD_DHCP6_OPTION_STATUS_CODE, 0x00, 0x32, + 0x00, 0x00, /* status code */ + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x28, 0x65, 0x73, 0x29, 0x20, 0x72, 0x65, 0x6e, 0x65, + 0x77, 0x65, 0x64, 0x2e, 0x20, 0x47, 0x72, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x20, 0x66, + 0x72, 0x6f, 0x6d, 0x20, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x74, 0x20, 0x45, 0x61, 0x72, 0x74, 0x68, /* status message */ + /* IA_PD */ + 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x46, + IA_ID_BYTES, + 0x00, 0x00, 0x00, 0x50, /* lifetime T1 */ + 0x00, 0x00, 0x00, 0x78, /* lifetime T2 */ + /* IA_PD (IA_PD_PREFIX suboption) */ + 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19, + 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */ + 0x40, /* prefixlen */ + IA_PD_PREFIX2_BYTES, + /* IA_PD (IA_PD_PREFIX suboption) */ + 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19, + 0x00, 0x00, 0x00, 0x96, /* preferred lifetime */ + 0x00, 0x00, 0x00, 0xb4, /* valid lifetime */ + 0x40, /* prefixlen */ + IA_PD_PREFIX1_BYTES, + /* DNS servers */ + 0x00, SD_DHCP6_OPTION_DNS_SERVERS, 0x00, 0x20, + DNS1_BYTES, + DNS2_BYTES, + /* SNTP servers */ + 0x00, SD_DHCP6_OPTION_SNTP_SERVERS, 0x00, 0x20, + SNTP1_BYTES, + SNTP2_BYTES, + /* NTP servers */ + 0x00, SD_DHCP6_OPTION_NTP_SERVER, 0x00, 0x37, + /* NTP server (address suboption) */ + 0x00, DHCP6_NTP_SUBOPTION_SRV_ADDR, 0x00, 0x10, + NTP1_BYTES, + /* NTP server (address suboption) */ + 0x00, DHCP6_NTP_SUBOPTION_SRV_ADDR, 0x00, 0x10, + NTP2_BYTES, + /* NTP server (fqdn suboption) */ + 0x00, DHCP6_NTP_SUBOPTION_SRV_FQDN, 0x00, 0x0b, + 0x03, 'n', 't', 'p', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, + /* Domain list */ + 0x00, SD_DHCP6_OPTION_DOMAIN_LIST, 0x00, 0x0b, + 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, + /* Client FQDN */ + 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x12, + 0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', +}; + +static void test_client_verify_information_request(const DHCP6Message *msg, size_t len) { log_debug("/* %s */", __func__); - assert_se(len >= sizeof(DHCP6Message)); + assert_se(len == sizeof(msg_information_request)); + /* The elapsed time value is not deterministic. Skip it. */ + assert_se(memcmp(msg, msg_information_request, len - sizeof(be16_t)) == 0); +} - assert_se(dhcp6_lease_new(&lease) >= 0); +static void test_client_verify_solicit(const DHCP6Message *msg, size_t len) { + log_debug("/* %s */", __func__); - assert_se(advertise->type == DHCP6_MESSAGE_ADVERTISE); - assert_se((be32toh(advertise->transaction_id) & 0x00ffffff) == 0x0fb4e5); + assert_se(len == sizeof(msg_solicit)); + /* The elapsed time value is not deterministic. Skip it. */ + assert_se(memcmp(msg, msg_solicit, len - sizeof(be16_t)) == 0); +} - while (pos < len) { - DHCP6Option *option = (DHCP6Option *)&advertise->options[pos]; - const uint16_t optcode = be16toh(option->code); - const uint16_t optlen = be16toh(option->len); - uint8_t *optval = option->data; +static void test_client_verify_request(const DHCP6Message *msg, size_t len) { + log_debug("/* %s */", __func__); - switch(optcode) { - case SD_DHCP6_OPTION_CLIENTID: - assert_se(optlen == 14); + assert_se(len == sizeof(msg_request)); + assert_se(msg->type == DHCP6_MESSAGE_REQUEST); + /* The transaction ID and elapsed time value are not deterministic. Skip them. */ + assert_se(memcmp(msg->options, msg_request + offsetof(DHCP6Message, options), len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0); +} - opt_clientid = true; +static void test_lease_common(sd_dhcp6_client *client) { + sd_dhcp6_lease *lease; + const struct in6_addr *addrs; + const char *str; + char **strv; + uint8_t *id; + size_t len; + + assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0); + + assert_se(dhcp6_lease_get_clientid(lease, &id, &len) >= 0); + assert_se(memcmp_nn(id, len, client_id, sizeof(client_id)) == 0); + + assert_se(sd_dhcp6_lease_get_domains(lease, &strv) == 1); + assert_se(streq(strv[0], "lab.intra")); + assert_se(!strv[1]); + + assert_se(sd_dhcp6_lease_get_fqdn(lease, &str) >= 0); + assert_se(streq(str, "client.lab.intra")); + + assert_se(sd_dhcp6_lease_get_dns(lease, &addrs) == 2); + assert_se(in6_addr_equal(&addrs[0], &dns1)); + assert_se(in6_addr_equal(&addrs[1], &dns2)); + + assert_se(sd_dhcp6_lease_get_ntp_addrs(lease, &addrs) == 2); + assert_se(in6_addr_equal(&addrs[0], &ntp1)); + assert_se(in6_addr_equal(&addrs[1], &ntp2)); + + assert_se(sd_dhcp6_lease_get_ntp_fqdn(lease, &strv) == 1); + assert_se(streq(strv[0], "ntp.intra")); + assert_se(!strv[1]); + + assert_se(lease->sntp_count == 2); + assert_se(in6_addr_equal(&lease->sntp[0], &sntp1)); + assert_se(in6_addr_equal(&lease->sntp[1], &sntp2)); +} + +static void test_lease_managed(sd_dhcp6_client *client) { + sd_dhcp6_lease *lease; + struct in6_addr addr; + uint32_t lt_pref, lt_valid; + uint8_t *id, prefixlen; + size_t len; + + assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0); + + assert_se(dhcp6_lease_get_serverid(lease, &id, &len) >= 0); + assert_se(memcmp_nn(id, len, server_id, sizeof(server_id)) == 0); + + sd_dhcp6_lease_reset_address_iter(lease); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) >= 0); + assert_se(in6_addr_equal(&addr, &ia_na_address1)); + assert_se(lt_pref == 150); + assert_se(lt_valid == 180); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) >= 0); + assert_se(in6_addr_equal(&addr, &ia_na_address2)); + assert_se(lt_pref == 150); + assert_se(lt_valid == 180); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) == -ENODATA); + + sd_dhcp6_lease_reset_address_iter(lease); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) >= 0); + assert_se(in6_addr_equal(&addr, &ia_na_address1)); + assert_se(lt_pref == 150); + assert_se(lt_valid == 180); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) >= 0); + assert_se(in6_addr_equal(&addr, &ia_na_address2)); + assert_se(lt_pref == 150); + assert_se(lt_valid == 180); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) == -ENODATA); + + sd_dhcp6_lease_reset_pd_prefix_iter(lease); + assert_se(sd_dhcp6_lease_get_pd(lease, &addr, &prefixlen, <_pref, <_valid) >= 0); + assert_se(in6_addr_equal(&addr, &ia_pd_prefix1)); + assert_se(prefixlen == 64); + assert_se(lt_pref == 150); + assert_se(lt_valid == 180); + assert_se(sd_dhcp6_lease_get_pd(lease, &addr, &prefixlen, <_pref, <_valid) >= 0); + assert_se(in6_addr_equal(&addr, &ia_pd_prefix2)); + assert_se(prefixlen == 64); + assert_se(lt_pref == 150); + assert_se(lt_valid == 180); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) == -ENODATA); + + sd_dhcp6_lease_reset_pd_prefix_iter(lease); + assert_se(sd_dhcp6_lease_get_pd(lease, &addr, &prefixlen, <_pref, <_valid) >= 0); + assert_se(in6_addr_equal(&addr, &ia_pd_prefix1)); + assert_se(prefixlen == 64); + assert_se(lt_pref == 150); + assert_se(lt_valid == 180); + assert_se(sd_dhcp6_lease_get_pd(lease, &addr, &prefixlen, <_pref, <_valid) >= 0); + assert_se(in6_addr_equal(&addr, &ia_pd_prefix2)); + assert_se(prefixlen == 64); + assert_se(lt_pref == 150); + assert_se(lt_valid == 180); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) == -ENODATA); + + test_lease_common(client); +} + +static void test_client_callback(sd_dhcp6_client *client, int event, void *userdata) { + switch (event) { + case SD_DHCP6_CLIENT_EVENT_STOP: + log_debug("/* %s (event=stop) */", __func__); + return; + + case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST: + log_debug("/* %s (event=information-request) */", __func__); + + assert_se(test_client_sent_message_count == 1); + + test_lease_common(client); + + assert_se(sd_dhcp6_client_set_information_request(client, false) >= 0); + assert_se(sd_dhcp6_client_start(client) >= 0); + assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message*) msg_advertise)->transaction_id) >= 0); + break; + + case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE: + log_debug("/* %s (event=ip-acquire) */", __func__); + + assert_se(IN_SET(test_client_sent_message_count, 3, 4)); + + test_lease_managed(client); + + switch (test_client_sent_message_count) { + case 3: + assert_se(sd_dhcp6_client_stop(client) >= 0); + assert_se(sd_dhcp6_client_start(client) >= 0); + assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message*) msg_reply)->transaction_id) >= 0); break; - case SD_DHCP6_OPTION_IA_NA: { - be32_t iaid = htobe32(0x0ecfa37d); - - assert_se(optlen == 94); - assert_se(optval == &msg_advertise[26]); - assert_se(!memcmp(optval, &msg_advertise[26], optlen)); - - assert_se(!memcmp(optval, &iaid, sizeof(val))); - - val = htobe32(80); - assert_se(!memcmp(optval + 4, &val, sizeof(val))); - - val = htobe32(120); - assert_se(!memcmp(optval + 8, &val, sizeof(val))); - - assert_se(dhcp6_option_parse_ia(NULL, iaid, optcode, optlen, optval, &lease->ia) >= 0); - - break; - } - case SD_DHCP6_OPTION_SERVERID: - assert_se(optlen == 14); - assert_se(optval == &msg_advertise[179]); - assert_se(!memcmp(optval, &msg_advertise[179], optlen)); - - assert_se(dhcp6_lease_set_serverid(lease, optval, optlen) >= 0); - break; - - case SD_DHCP6_OPTION_PREFERENCE: - assert_se(optlen == 1); - assert_se(!*optval); - - assert_se(dhcp6_lease_set_preference(lease, *optval) >= 0); - break; - - case SD_DHCP6_OPTION_ELAPSED_TIME: - assert_se(optlen == 2); - - break; - - case SD_DHCP6_OPTION_DNS_SERVERS: - assert_se(optlen == 16); - assert_se(dhcp6_lease_add_dns(lease, optval, optlen) >= 0); - break; - - case SD_DHCP6_OPTION_DOMAIN_LIST: - assert_se(optlen == 11); - assert_se(dhcp6_lease_add_domains(lease, optval, optlen) >= 0); - break; - - case SD_DHCP6_OPTION_SNTP_SERVERS: - assert_se(optlen == 16); - assert_se(dhcp6_lease_add_sntp(lease, optval, optlen) >= 0); + case 4: + assert_se(sd_event_exit(sd_dhcp6_client_get_event(client), 0) >= 0); break; default: - break; - } - - pos += sizeof(*option) + optlen; - } - - assert_se(pos == len); - assert_se(opt_clientid); - - sd_dhcp6_lease_reset_address_iter(lease); - assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) >= 0); - assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr))); - assert_se(lt_pref == 150); - assert_se(lt_valid == 180); - assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) == -ENOMSG); - - sd_dhcp6_lease_reset_address_iter(lease); - assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) >= 0); - assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr))); - assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) == -ENOMSG); - sd_dhcp6_lease_reset_address_iter(lease); - assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) >= 0); - assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr))); - assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) == -ENOMSG); - - assert_se(dhcp6_lease_get_serverid(lease, &opt, &len) >= 0); - assert_se(len == 14); - assert_se(!memcmp(opt, &msg_advertise[179], len)); - - assert_se(dhcp6_lease_get_preference(lease, &preference) >= 0); - assert_se(preference == 0); - - r = sd_dhcp6_lease_get_dns(lease, &addrs); - assert_se(r == 1); - assert_se(!memcmp(addrs, &msg_advertise[124], r * 16)); - - r = sd_dhcp6_lease_get_domains(lease, &domains); - assert_se(r == 1); - assert_se(!strcmp("lab.intra", domains[0])); - assert_se(domains[1] == NULL); - - r = sd_dhcp6_lease_get_ntp_addrs(lease, &addrs); - assert_se(r == 1); - assert_se(!memcmp(addrs, &msg_advertise[159], r * 16)); -} - -static int test_check_completed_in_2_seconds(sd_event_source *s, uint64_t usec, void *userdata) { - assert_not_reached(); -} - -static void test_client_solicit_cb(sd_dhcp6_client *client, int event, - void *userdata) { - sd_event *e = userdata; - sd_dhcp6_lease *lease; - const struct in6_addr *addrs; - char **domains; - - log_debug("/* %s */", __func__); - - assert_se(e); - assert_se(event == SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE); - - assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0); - - assert_se(sd_dhcp6_lease_get_domains(lease, &domains) == 1); - assert_se(!strcmp("lab.intra", domains[0])); - assert_se(domains[1] == NULL); - - assert_se(sd_dhcp6_lease_get_dns(lease, &addrs) == 1); - assert_se(!memcmp(addrs, &msg_advertise[124], 16)); - - assert_se(sd_dhcp6_lease_get_ntp_addrs(lease, &addrs) == 1); - assert_se(!memcmp(addrs, &msg_advertise[159], 16)); - - assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVERS) == -EBUSY); - - sd_event_exit(e, 0); -} - -static void test_client_send_reply(DHCP6Message *request) { - DHCP6Message reply; - - log_debug("/* %s */", __func__); - - reply.transaction_id = request->transaction_id; - reply.type = DHCP6_MESSAGE_REPLY; - - memcpy(msg_reply, &reply.transaction_id, 4); - - memcpy(&msg_reply[26], test_duid, sizeof(test_duid)); - - memcpy(&msg_reply[44], &test_iaid, sizeof(test_iaid)); - - assert_se(write(test_dhcp_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply)); -} - -static void test_client_verify_request(DHCP6Message *request, size_t len) { - _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; - bool found_clientid = false, found_iana = false, found_serverid = false, - found_elapsed_time = false, found_fqdn = false; - uint32_t lt_pref, lt_valid; - struct in6_addr addr; - size_t pos = 0; - be32_t val; - - log_debug("/* %s */", __func__); - - assert_se(request->type == DHCP6_MESSAGE_REQUEST); - assert_se(dhcp6_lease_new(&lease) >= 0); - - len -= sizeof(DHCP6Message); - - while (pos < len) { - DHCP6Option *option = (DHCP6Option *)&request->options[pos]; - uint16_t optcode = be16toh(option->code); - uint16_t optlen = be16toh(option->len); - uint8_t *optval = option->data; - - switch(optcode) { - case SD_DHCP6_OPTION_CLIENTID: - assert_se(!found_clientid); - found_clientid = true; - - assert_se(!memcmp(optval, &test_duid, - sizeof(test_duid))); - - break; - - case SD_DHCP6_OPTION_IA_NA: - assert_se(!found_iana); - found_iana = true; - - assert_se(optlen == 40); - assert_se(!memcmp(optval, &test_iaid, sizeof(test_iaid))); - - /* T1 and T2 should not be set. */ - val = 0; - assert_se(!memcmp(optval + 4, &val, sizeof(val))); - assert_se(!memcmp(optval + 8, &val, sizeof(val))); - - /* Then, this should refuse all addresses. */ - assert_se(dhcp6_option_parse_ia(NULL, test_iaid, optcode, optlen, optval, &lease->ia) == -ENODATA); - - break; - - case SD_DHCP6_OPTION_SERVERID: - assert_se(!found_serverid); - found_serverid = true; - - assert_se(optlen == 14); - assert_se(!memcmp(&msg_advertise[179], optval, optlen)); - - break; - - case SD_DHCP6_OPTION_ELAPSED_TIME: - assert_se(!found_elapsed_time); - found_elapsed_time = true; - - assert_se(optlen == 2); - - break; - case SD_DHCP6_OPTION_CLIENT_FQDN: - assert_se(!found_fqdn); - found_fqdn = true; - - assert_se(optlen == 17); - - assert_se(optval[0] == 0x01); - assert_se(!memcmp(optval + 1, fqdn_wire, sizeof(fqdn_wire))); - break; - } - - pos += sizeof(*option) + optlen; - } - - assert_se(found_clientid && found_iana && found_serverid && found_elapsed_time); - - sd_dhcp6_lease_reset_address_iter(lease); - assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) == -ENOMSG); -} - -static void test_client_send_advertise(DHCP6Message *solicit) { - DHCP6Message advertise; - - log_debug("/* %s */", __func__); - - advertise.transaction_id = solicit->transaction_id; - advertise.type = DHCP6_MESSAGE_ADVERTISE; - - memcpy(msg_advertise, &advertise.transaction_id, 4); - - memcpy(&msg_advertise[8], test_duid, sizeof(test_duid)); - - memcpy(&msg_advertise[26], &test_iaid, sizeof(test_iaid)); - - assert_se(write(test_dhcp_fd[1], msg_advertise, sizeof(msg_advertise)) == sizeof(msg_advertise)); -} - -static void test_client_verify_solicit(DHCP6Message *solicit, size_t len) { - bool found_clientid = false, found_iana = false, - found_elapsed_time = false, found_fqdn = false; - size_t pos = 0; - - log_debug("/* %s */", __func__); - - assert_se(solicit->type == DHCP6_MESSAGE_SOLICIT); - - len -= sizeof(DHCP6Message); - - while (pos < len) { - DHCP6Option *option = (DHCP6Option *)&solicit->options[pos]; - uint16_t optcode = be16toh(option->code); - uint16_t optlen = be16toh(option->len); - uint8_t *optval = option->data; - - switch(optcode) { - case SD_DHCP6_OPTION_CLIENTID: - assert_se(!found_clientid); - found_clientid = true; - - assert_se(optlen == sizeof(test_duid)); - memcpy(&test_duid, optval, sizeof(test_duid)); - - break; - - case SD_DHCP6_OPTION_IA_NA: - assert_se(!found_iana); - found_iana = true; - - assert_se(optlen == 12); - - memcpy(&test_iaid, optval, sizeof(test_iaid)); - - break; - - case SD_DHCP6_OPTION_ELAPSED_TIME: - assert_se(!found_elapsed_time); - found_elapsed_time = true; - - assert_se(optlen == 2); - - break; - - case SD_DHCP6_OPTION_CLIENT_FQDN: - assert_se(!found_fqdn); - found_fqdn = true; - - assert_se(optlen == 17); - - assert_se(optval[0] == 0x01); - assert_se(!memcmp(optval + 1, fqdn_wire, sizeof(fqdn_wire))); - - break; - } - - pos += sizeof(*option) + optlen; - } - - assert_se(pos == len); - assert_se(found_clientid && found_iana && found_elapsed_time); -} - -static void test_client_information_cb(sd_dhcp6_client *client, int event, void *userdata) { - sd_event *e = userdata; - sd_dhcp6_lease *lease; - const struct in6_addr *addrs; - struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } }; - char **domains; - const char *fqdn; - - log_debug("/* %s */", __func__); - - assert_se(e); - assert_se(event == SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST); - - assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0); - - assert_se(sd_dhcp6_lease_get_domains(lease, &domains) == 1); - assert_se(!strcmp("lab.intra", domains[0])); - assert_se(domains[1] == NULL); - - assert_se(sd_dhcp6_lease_get_fqdn(lease, &fqdn) >= 0); - assert_se(streq(fqdn, "client.intra")); - - assert_se(sd_dhcp6_lease_get_dns(lease, &addrs) == 1); - assert_se(!memcmp(addrs, &msg_advertise[124], 16)); - - assert_se(sd_dhcp6_lease_get_ntp_addrs(lease, &addrs) == 1); - assert_se(!memcmp(addrs, &msg_advertise[159], 16)); - - assert_se(sd_dhcp6_client_set_information_request(client, false) == -EBUSY); - assert_se(sd_dhcp6_client_set_callback(client, NULL, e) >= 0); - assert_se(sd_dhcp6_client_stop(client) >= 0); - assert_se(sd_dhcp6_client_set_information_request(client, false) >= 0); - - assert_se(sd_dhcp6_client_set_callback(client, test_client_solicit_cb, e) >= 0); - - assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0); - - assert_se(sd_dhcp6_client_start(client) >= 0); -} - -static void test_client_verify_information_request(DHCP6Message *information_request, size_t len) { - _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; - size_t pos = 0; - bool found_clientid = false, found_elapsed_time = false; - struct in6_addr addr; - uint32_t lt_pref, lt_valid; - - log_debug("/* %s */", __func__); - - assert_se(information_request->type == DHCP6_MESSAGE_INFORMATION_REQUEST); - assert_se(dhcp6_lease_new(&lease) >= 0); - - len -= sizeof(DHCP6Message); - - while (pos < len) { - DHCP6Option *option = (DHCP6Option *)&information_request->options[pos]; - uint16_t optcode = be16toh(option->code); - uint16_t optlen = be16toh(option->len); - uint8_t *optval = option->data; - - switch(optcode) { - case SD_DHCP6_OPTION_CLIENTID: - assert_se(!found_clientid); - found_clientid = true; - - assert_se(optlen == sizeof(test_duid)); - memcpy(&test_duid, optval, sizeof(test_duid)); - - break; - - case SD_DHCP6_OPTION_IA_NA: - case SD_DHCP6_OPTION_SERVERID: assert_not_reached(); - - case SD_DHCP6_OPTION_ELAPSED_TIME: - assert_se(!found_elapsed_time); - found_elapsed_time = true; - - assert_se(optlen == 2); - - break; } - pos += sizeof(*option) + optlen; + break; + + case DHCP6_CLIENT_EVENT_TEST_ADVERTISED: { + sd_dhcp6_lease *lease; + uint8_t preference; + + log_debug("/* %s (event=test-advertised) */", __func__); + + assert_se(test_client_sent_message_count == 2); + + test_lease_managed(client); + + assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0); + assert_se(dhcp6_lease_get_preference(lease, &preference) >= 0); + assert_se(preference == 0xff); + + assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message*) msg_reply)->transaction_id) >= 0); + break; + } + default: + assert_not_reached(); } - - assert_se(pos == len); - assert_se(found_clientid && found_elapsed_time); - - sd_dhcp6_lease_reset_address_iter(lease); - - assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, <_valid) == -ENOMSG); } -int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, - const void *packet, size_t len) { - struct in6_addr mcast = IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; - DHCP6Message *message; +int dhcp6_network_send_udp_socket(int s, struct in6_addr *a, const void *packet, size_t len) { + log_debug("/* %s(count=%u) */", __func__, test_client_sent_message_count); - log_debug("/* %s */", __func__); - - assert_se(s == test_dhcp_fd[0]); - assert_se(server_address); + assert_se(a); + assert_se(in6_addr_equal(a, &mcast_address)); assert_se(packet); - assert_se(len > sizeof(DHCP6Message) + 4); - assert_se(IN6_ARE_ADDR_EQUAL(server_address, &mcast)); + assert_se(len >= sizeof(DHCP6Message)); - message = (DHCP6Message *)packet; + switch (test_client_sent_message_count) { + case 0: + test_client_verify_information_request(packet, len); + assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply)); + break; - assert_se(message->transaction_id & 0x00ffffff); + case 1: + test_client_verify_solicit(packet, len); + assert_se(write(test_fd[1], msg_advertise, sizeof(msg_advertise)) == sizeof(msg_advertise)); + break; - if (test_client_message_num == 0) { - test_client_verify_information_request(message, len); - test_client_send_reply(message); - test_client_message_num++; - } else if (test_client_message_num == 1) { - test_client_verify_solicit(message, len); - test_client_send_advertise(message); - test_client_message_num++; - } else if (test_client_message_num == 2) { - test_client_verify_request(message, len); - test_client_send_reply(message); - test_client_message_num++; + case 2: + test_client_callback(client_ref, DHCP6_CLIENT_EVENT_TEST_ADVERTISED, NULL); + test_client_verify_request(packet, len); + assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply)); + break; + + case 3: + test_client_verify_solicit(packet, len); + assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply)); + break; + + default: + assert_not_reached(); } + test_client_sent_message_count++; return len; } -int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) { +int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *a) { assert_se(ifindex == test_ifindex); + assert_se(a); + assert_se(in6_addr_equal(a, &local_address)); - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_dhcp_fd) < 0) - return -errno; - - return test_dhcp_fd[0]; + assert_se(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) >= 0); + return TAKE_FD(test_fd[0]); } -static void test_client_solicit(sd_event *e) { - sd_dhcp6_client *client; - struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } }; - int val; +static void test_dhcp6_client(void) { + _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; log_debug("/* %s */", __func__); + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_event_add_time_relative(e, NULL, clock_boottime_or_monotonic(), + 2 * USEC_PER_SEC, 0, + NULL, INT_TO_PTR(ETIMEDOUT)) >= 0); + assert_se(sd_dhcp6_client_new(&client) >= 0); - assert_se(client); - assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0); - assert_se(sd_dhcp6_client_set_ifindex(client, test_ifindex) == 0); - assert_se(sd_dhcp6_client_set_mac(client, (const uint8_t *) &mac_addr, - sizeof (mac_addr), - ARPHRD_ETHER) >= 0); - assert_se(sd_dhcp6_client_set_fqdn(client, "host.lab.intra") == 1); + assert_se(sd_dhcp6_client_set_local_address(client, &local_address) >= 0); + assert_se(sd_dhcp6_client_set_fqdn(client, "host.lab.intra") >= 0); + assert_se(sd_dhcp6_client_set_iaid(client, unaligned_read_be32((uint8_t[]) { IA_ID_BYTES })) >= 0); dhcp6_client_set_test_mode(client, true); - assert_se(sd_dhcp6_client_get_information_request(client, &val) >= 0); - assert_se(val == 0); - assert_se(sd_dhcp6_client_set_information_request(client, 42) >= 0); - assert_se(sd_dhcp6_client_get_information_request(client, &val) >= 0); - assert_se(val); - - assert_se(sd_dhcp6_client_set_callback(client, - test_client_information_cb, e) >= 0); - - assert_se(sd_event_add_time_relative(e, &hangcheck, clock_boottime_or_monotonic(), - 2 * USEC_PER_SEC, 0, - test_check_completed_in_2_seconds, NULL) >= 0); - - assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0); + assert_se(sd_dhcp6_client_set_information_request(client, true) >= 0); + assert_se(sd_dhcp6_client_set_callback(client, test_client_callback, NULL) >= 0); assert_se(sd_dhcp6_client_start(client) >= 0); - sd_event_loop(e); + assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message*) msg_reply)->transaction_id) >= 0); - hangcheck = sd_event_source_unref(hangcheck); + assert_se(client_ref = sd_dhcp6_client_ref(client)); - assert_se(!sd_dhcp6_client_unref(client)); + assert_se(sd_event_loop(e) >= 0); - test_dhcp_fd[1] = safe_close(test_dhcp_fd[1]); + assert_se(test_client_sent_message_count == 4); + + test_fd[1] = safe_close(test_fd[1]); } int main(int argc, char *argv[]) { - _cleanup_(sd_event_unrefp) sd_event *e; - - assert_se(sd_event_new(&e) >= 0); - test_setup_logging(LOG_DEBUG); - test_client_basic(e); + test_client_basic(); test_parse_domain(); test_option(); test_option_status(); test_client_parse_message_issue_22099(); - test_advertise_option(e); - test_client_solicit(e); + test_dhcp6_client(); return 0; } diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index ab51b7377d..1293a4d913 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -432,8 +432,20 @@ int dhcp6_start_on_ra(Link *link, bool information_request) { return r; if (inf_req == information_request) + /* The client is already running in the requested mode. */ return 0; + if (!inf_req) { + log_link_debug(link, + "The DHCPv6 client is already running in the managed mode, " + "refusing to start the client in the information requesting mode."); + return 0; + } + + log_link_debug(link, + "The DHCPv6 client is running in the information requesting mode. " + "Restarting the client in the managed mode."); + r = sd_dhcp6_client_stop(link->dhcp6_client); if (r < 0) return r; diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index 0e23c84e64..1bb21e0255 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -251,7 +251,7 @@ int sd_dhcp6_client_set_request_vendor_class( int sd_dhcp6_client_set_prefix_delegation_hint( sd_dhcp6_client *client, uint8_t prefixlen, - const struct in6_addr *pd_address); + const struct in6_addr *pd_prefix); int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegation); int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, @@ -260,8 +260,6 @@ int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request); int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request); -int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, - uint32_t transaction_id); int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *v); diff --git a/test/fuzz/fuzz-dhcp6-client-send/12ad30d317800d7f731c1c8bc0854e531d5ef928 b/test/fuzz/fuzz-dhcp6-client/12ad30d317800d7f731c1c8bc0854e531d5ef928 similarity index 100% rename from test/fuzz/fuzz-dhcp6-client-send/12ad30d317800d7f731c1c8bc0854e531d5ef928 rename to test/fuzz/fuzz-dhcp6-client/12ad30d317800d7f731c1c8bc0854e531d5ef928 diff --git a/test/fuzz/fuzz-dhcp6-client-send/crash-a93b8ba024ada36014c29c25cc90c668fd91ce7f b/test/fuzz/fuzz-dhcp6-client/crash-a93b8ba024ada36014c29c25cc90c668fd91ce7f similarity index 100% rename from test/fuzz/fuzz-dhcp6-client-send/crash-a93b8ba024ada36014c29c25cc90c668fd91ce7f rename to test/fuzz/fuzz-dhcp6-client/crash-a93b8ba024ada36014c29c25cc90c668fd91ce7f diff --git a/test/fuzz/fuzz-dhcp6-client-send/f202c4dff34d15e41c032a66ed25d89154be1f6d b/test/fuzz/fuzz-dhcp6-client/f202c4dff34d15e41c032a66ed25d89154be1f6d similarity index 100% rename from test/fuzz/fuzz-dhcp6-client-send/f202c4dff34d15e41c032a66ed25d89154be1f6d rename to test/fuzz/fuzz-dhcp6-client/f202c4dff34d15e41c032a66ed25d89154be1f6d