diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 91418ad351..4eb56aa8c9 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -101,15 +101,29 @@ int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn); int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class); int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *user_class); int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options); -int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, - size_t *optlen, uint8_t **optvalue); -int dhcp6_option_parse_status(DHCP6Option *option, size_t len); -int dhcp6_option_parse_ia(sd_dhcp6_client *client, DHCP6Option *iaoption, be32_t iaid, DHCP6IA *ia, uint16_t *ret_status_code); -int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen, - struct in6_addr **addrs, size_t count); -int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, - char ***str_arr); -int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str); + +int dhcp6_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, diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index 391b4f1fa9..8801497b72 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -28,11 +28,11 @@ struct sd_dhcp6_lease { struct in6_addr *dns; size_t dns_count; char **domains; - size_t domains_count; struct in6_addr *ntp; size_t ntp_count; char **ntp_fqdn; - size_t ntp_fqdn_count; + struct in6_addr *sntp; + size_t sntp_count; char *fqdn; }; @@ -50,12 +50,10 @@ int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit); int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid); int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid); -int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen); -int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval, - size_t optlen); -int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen); -int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, - size_t optlen) ; +int dhcp6_lease_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); +int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); +int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) ; int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); int dhcp6_lease_new(sd_dhcp6_lease **ret); diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 34d7e997dd..0276ce693c 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -14,29 +14,12 @@ #include "dhcp6-lease-internal.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" -typedef struct DHCP6StatusOption { - struct DHCP6Option option; - be16_t status; - char msg[]; -} _packed_ DHCP6StatusOption; - -typedef struct DHCP6AddressOption { - struct DHCP6Option option; - struct iaaddr iaaddr; - uint8_t options[]; -} _packed_ DHCP6AddressOption; - -typedef struct DHCP6PDPrefixOption { - struct DHCP6Option option; - struct iapdprefix iapdprefix; - uint8_t options[]; -} _packed_ DHCP6PDPrefixOption; - #define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na)) #define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd)) #define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta)) @@ -370,367 +353,410 @@ int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_CLASS, total, p); } -static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) { - DHCP6Option *option = (DHCP6Option*) *buf; - uint16_t len; +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) { - assert_return(buf, -EINVAL); - assert_return(optcode, -EINVAL); - assert_return(optlen, -EINVAL); + const DHCP6Option *option; + size_t len; - if (*buflen < offsetof(DHCP6Option, data)) - return -ENOMSG; + assert(buf); + assert(offset); + assert(ret_option_code); + assert(ret_option_data_len); + assert(ret_option_data); + if (buflen < offsetof(DHCP6Option, data)) + return -EBADMSG; + + if (*offset >= buflen - offsetof(DHCP6Option, data)) + return -EBADMSG; + + option = (const DHCP6Option*) (buf + *offset); len = be16toh(option->len); - if (len > *buflen) + if (len > buflen - offsetof(DHCP6Option, data) - *offset) + return -EBADMSG; + + *offset += offsetof(DHCP6Option, data) + len; + *ret_option_code = be16toh(option->code); + *ret_option_data_len = len; + *ret_option_data = option->data; + + return 0; +} + +int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message) { + assert(data); + + if (data_len < sizeof(uint16_t)) + return -EBADMSG; + + if (ret_status_message) { + char *msg; + + /* The status message MUST NOT be null-terminated. See section 21.13 of RFC8415. + * Let's escape unsafe characters for safety. */ + msg = cescape_length((const char*) (data + sizeof(uint16_t)), data_len - sizeof(uint16_t)); + if (!msg) + return -ENOMEM; + + *ret_status_message = msg; + } + + return unaligned_read_be16(data); +} + +static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) { + int r; + + assert(buf); + + for(size_t offset = 0; offset < buflen;) { + const uint8_t *data; + size_t data_len; + uint16_t code; + + r = dhcp6_option_parse(buf, buflen, &offset, &code, &data_len, &data); + if (r < 0) + return r; + + switch(code) { + case SD_DHCP6_OPTION_STATUS_CODE: { + _cleanup_free_ char *msg = NULL; + + 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) + 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)); + break; + } + default: + log_dhcp6_client(client, "Received an unknown sub option %u in IA address or PD prefix, ignoring.", code); + } + } + + return 0; +} + +static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, const uint8_t *data, size_t len, DHCP6Address **ret) { + uint32_t lt_valid, lt_pref; + DHCP6Address *a; + int r; + + assert(data); + assert(ret); + + 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); + + if (lt_valid == 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received an IA address with zero valid lifetime, ignoring."); + if (lt_pref > lt_valid) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received an IA address with preferred lifetime %"PRIu32 + " larger than valid lifetime %"PRIu32", ignoring.", + lt_pref, lt_valid); + + if (len > sizeof(struct iaaddr)) { + r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iaaddr), len - sizeof(struct iaaddr)); + if (r < 0) + return r; + } + + a = new(DHCP6Address, 1); + if (!a) + return -ENOMEM; + + LIST_INIT(addresses, a); + memcpy(&a->iaaddr, data, sizeof(struct iaaddr)); + + *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; - *optcode = be16toh(option->code); - *optlen = len; + lt_valid = be32toh(((const struct iapdprefix*) data)->lifetime_valid); + lt_pref = be32toh(((const struct iapdprefix*) data)->lifetime_preferred); - *buf += 4; - *buflen -= 4; - - return 0; -} - -int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, - size_t *optlen, uint8_t **optvalue) { - int r; - - assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL); - - r = option_parse_hdr(buf, buflen, optcode, optlen); - if (r < 0) - return r; - - if (*optlen > *buflen) - return -ENOBUFS; - - *optvalue = *buf; - *buflen -= *optlen; - *buf += *optlen; - - return 0; -} - -int dhcp6_option_parse_status(DHCP6Option *option, size_t len) { - DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option; - - if (len < sizeof(DHCP6StatusOption) || - be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption)) - return -ENOBUFS; - - return be16toh(statusopt->status); -} - -static int dhcp6_option_parse_address(sd_dhcp6_client *client, DHCP6Option *option, DHCP6IA *ia, uint32_t *ret_lifetime_valid) { - DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option; - DHCP6Address *addr; - uint32_t lt_valid, lt_pref; - int r; - - if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option)) - return -ENOBUFS; - - lt_valid = be32toh(addr_option->iaaddr.lifetime_valid); - lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred); - - if (lt_valid == 0 || lt_pref > lt_valid) + if (lt_valid == 0) return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), - "Valid lifetime of an IA address is zero or " - "preferred lifetime %"PRIu32" > valid lifetime %"PRIu32, + "Received a PD prefix with zero valid lifetime, ignoring."); + if (lt_pref > lt_valid) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received a PD prefix with preferred lifetime %"PRIu32 + " larger than valid lifetime %"PRIu32", ignoring.", lt_pref, lt_valid); - if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) { - r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option)); + if (len > sizeof(struct iapdprefix)) { + r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iapdprefix), len - sizeof(struct iapdprefix)); if (r < 0) return r; - if (r > 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), - "Non-zero status code '%s' for address is received", - dhcp6_message_status_to_string(r)); } - addr = new0(DHCP6Address, 1); - if (!addr) + a = new(DHCP6Address, 1); + if (!a) return -ENOMEM; - LIST_INIT(addresses, addr); - memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr)); - - LIST_PREPEND(addresses, ia->addresses, addr); - - *ret_lifetime_valid = be32toh(addr->iaaddr.lifetime_valid); - - return 0; -} - -static int dhcp6_option_parse_pdprefix(sd_dhcp6_client *client, DHCP6Option *option, DHCP6IA *ia, uint32_t *ret_lifetime_valid) { - DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option; - DHCP6Address *prefix; - uint32_t lt_valid, lt_pref; - int r; - - if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option)) - return -ENOBUFS; - - lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid); - lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred); - - if (lt_valid == 0 || lt_pref > lt_valid) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), - "Valid lifetieme of a PD prefix is zero or " - "preferred lifetime %"PRIu32" > valid lifetime %"PRIu32, - lt_pref, lt_valid); - - if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) { - r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option)); - if (r < 0) - return r; - if (r > 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), - "Non-zero status code '%s' for PD prefix is received", - dhcp6_message_status_to_string(r)); - } - - prefix = new0(DHCP6Address, 1); - if (!prefix) - return -ENOMEM; - - LIST_INIT(addresses, prefix); - memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix)); - - LIST_PREPEND(addresses, ia->addresses, prefix); - - *ret_lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid); + LIST_INIT(addresses, a); + memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix)); + *ret = a; return 0; } int dhcp6_option_parse_ia( sd_dhcp6_client *client, - DHCP6Option *iaoption, be32_t iaid, - DHCP6IA *ia, - uint16_t *ret_status_code) { + uint16_t option_code, + size_t option_data_len, + const uint8_t *option_data, + DHCP6IA *ret) { - uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX; - uint16_t iatype, optlen; - size_t iaaddr_offset; - int r = 0, status; - size_t i, len; - uint16_t opt; + _cleanup_(dhcp6_lease_free_ia) DHCP6IA ia = {}; + uint32_t lt_t1, lt_t2, lt_min = UINT32_MAX; + be32_t received_iaid; + size_t offset; + int r; - assert_return(ia, -EINVAL); - assert_return(!ia->addresses, -EINVAL); + assert(IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA, SD_DHCP6_OPTION_IA_PD)); + assert(option_data); + assert(ret); - iatype = be16toh(iaoption->code); - len = be16toh(iaoption->len); + /* This will return the following: + * -ENOMEM: memory allocation error, + * -ENOANO: unmatching IAID, + * -EINVAL: non-zero status code, or invalid lifetime, + * -EBADMSG: invalid message format, + * -ENODATA: no valid address or PD prefix, + * 0: success. */ - switch (iatype) { + switch (option_code) { case SD_DHCP6_OPTION_IA_NA: - if (len < DHCP6_OPTION_IA_NA_LEN) - return -ENOBUFS; + if (option_data_len < DHCP6_OPTION_IA_NA_LEN) + return -EBADMSG; - /* 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 (((const struct ia_na*) iaoption->data)->id != iaid) - /* ENOANO indicates the option should be ignored. */ - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENOANO), - "Received an IA_NA option with a different IAID " - "from the one chosen by the client, ignoring."); - - iaaddr_offset = DHCP6_OPTION_IA_NA_LEN; - memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na)); - - lt_t1 = be32toh(ia->ia_na.lifetime_t1); - lt_t2 = be32toh(ia->ia_na.lifetime_t2); - - if (lt_t1 > lt_t2) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), - "IA NA T1 %"PRIu32"sec > T2 %"PRIu32"sec", - lt_t1, lt_t2); + 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 (len < sizeof(ia->ia_pd)) - return -ENOBUFS; + if (option_data_len < DHCP6_OPTION_IA_PD_LEN) + return -EBADMSG; - /* 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 (((const struct ia_pd*) iaoption->data)->id != iaid) - /* ENOANO indicates the option should be ignored. */ - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENOANO), - "Received an IA_PD option with a different IAID " - "from the one chosen by the client, ignoring."); - - iaaddr_offset = sizeof(ia->ia_pd); - memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd)); - - lt_t1 = be32toh(ia->ia_pd.lifetime_t1); - lt_t2 = be32toh(ia->ia_pd.lifetime_t2); - - if (lt_t1 > lt_t2) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), - "IA PD T1 %"PRIu32"sec > T2 %"PRIu32"sec", - lt_t1, lt_t2); + 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); break; case SD_DHCP6_OPTION_IA_TA: - if (len < DHCP6_OPTION_IA_TA_LEN) - return -ENOBUFS; + if (option_data_len < DHCP6_OPTION_IA_TA_LEN) + return -ENOMSG; - /* 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 (((const struct ia_ta*) iaoption->data)->id != iaid) - /* ENOANO indicates the option should be ignored. */ - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENOANO), - "Received an IA_TA option with a different IAID " - "from the one chosen by the client, ignoring."); - - iaaddr_offset = DHCP6_OPTION_IA_TA_LEN; - memcpy(&ia->ia_ta, iaoption->data, sizeof(ia->ia_ta)); + 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. */ break; default: - return -EINVAL; + assert_not_reached(); } - ia->type = iatype; - i = iaaddr_offset; + /* 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) + 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."); - while (i < len) { - DHCP6Option *option = (DHCP6Option *)&iaoption->data[i]; + 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 (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len)) - return -ENOBUFS; + for (; offset < option_data_len;) { + const uint8_t *subdata; + size_t subdata_len; + uint16_t subopt; - opt = be16toh(option->code); - optlen = be16toh(option->len); + r = dhcp6_option_parse(option_data, option_data_len, &offset, &subopt, &subdata_len, &subdata); + if (r < 0) + return r; - switch (opt) { - case SD_DHCP6_OPTION_IAADDR: + switch (subopt) { + case SD_DHCP6_OPTION_IAADDR: { + DHCP6Address *a; - if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), - "IA Address option not in IA NA or TA option"); - - r = dhcp6_option_parse_address(client, option, ia, <_valid); - if (r < 0 && r != -EINVAL) - return r; - if (r >= 0 && lt_valid < lt_min) - lt_min = lt_valid; - - break; - - case SD_DHCP6_OPTION_IA_PD_PREFIX: - - if (ia->type != SD_DHCP6_OPTION_IA_PD) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), - "IA PD Prefix option not in IA PD option"); - - r = dhcp6_option_parse_pdprefix(client, option, ia, <_valid); - if (r < 0 && r != -EINVAL) - return r; - if (r >= 0 && lt_valid < lt_min) - lt_min = lt_valid; - - break; - - case SD_DHCP6_OPTION_STATUS_CODE: - - status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data)); - if (status < 0) - return status; - - if (status > 0) { - if (ret_status_code) - *ret_status_code = status; - - log_dhcp6_client(client, "IA status %s", - dhcp6_message_status_to_string(status)); - - return 0; + 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; } - break; + r = dhcp6_option_parse_ia_address(client, subdata, subdata_len, &a); + if (r == -ENOMEM) + return r; + if (r < 0) + /* Ignore the sub-option on non-critical errors. */ + continue; + lt_min = MIN(lt_min, a->iaaddr.lifetime_valid); + LIST_PREPEND(addresses, ia.addresses, a); + 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); + if (r == -ENOMEM) + return r; + if (r < 0) + /* Ignore the sub-option on non-critical errors. */ + continue; + + lt_min = MIN(lt_min, a->iapdprefix.lifetime_valid); + LIST_PREPEND(addresses, ia.addresses, a); + break; + } + case SD_DHCP6_OPTION_STATUS_CODE: { + _cleanup_free_ char *msg = NULL; + + 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) + 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)); + break; + } default: - log_dhcp6_client(client, "Unknown IA option %d", opt); - break; + log_dhcp6_client(client, "Received an IA option with an unknown sub-option %u, ignoring", subopt); } - - i += sizeof(*option) + optlen; } - switch(iatype) { + 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: - if (ia->ia_na.lifetime_t1 == 0 && ia->ia_na.lifetime_t2 == 0 && lt_min != UINT32_MAX) { - lt_t1 = lt_min / 2; - lt_t2 = lt_min / 10 * 8; - ia->ia_na.lifetime_t1 = htobe32(lt_t1); - ia->ia_na.lifetime_t2 = htobe32(lt_t2); - - log_dhcp6_client(client, "Computed IA NA T1 %"PRIu32"sec and T2 %"PRIu32"sec as both were zero", - lt_t1, lt_t2); - } - + *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: - if (ia->ia_pd.lifetime_t1 == 0 && ia->ia_pd.lifetime_t2 == 0 && lt_min != UINT32_MAX) { - lt_t1 = lt_min / 2; - lt_t2 = lt_min / 10 * 8; - ia->ia_pd.lifetime_t1 = htobe32(lt_t1); - ia->ia_pd.lifetime_t2 = htobe32(lt_t2); - - log_dhcp6_client(client, "Computed IA PD T1 %"PRIu32"sec and T2 %"PRIu32"sec as both were zero", - lt_t1, lt_t2); - } - + *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: - break; + assert_not_reached(); } - if (ret_status_code) - *ret_status_code = 0; - - return 1; + return 0; } -int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen, - struct in6_addr **addrs, size_t count) { +int dhcp6_option_parse_addresses( + const uint8_t *optval, + size_t optlen, + struct in6_addr **addrs, + size_t *count) { + + assert(optval); + assert(addrs); + assert(count); if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0) - return -EINVAL; + return -EBADMSG; - if (!GREEDY_REALLOC(*addrs, count * sizeof(struct in6_addr) + optlen)) + if (!GREEDY_REALLOC(*addrs, *count + optlen / sizeof(struct in6_addr))) return -ENOMEM; - memcpy(*addrs + count, optval, optlen); + memcpy(*addrs + *count, optval, optlen); + *count += optlen / sizeof(struct in6_addr); - count += optlen / sizeof(struct in6_addr); - - return count; + return 0; } -static int parse_domain(const uint8_t **data, uint16_t *len, char **out_domain) { - _cleanup_free_ char *ret = NULL; - const uint8_t *optval = *data; - uint16_t optlen = *len; - bool first = true; - size_t n = 0; +static int parse_domain(const uint8_t **data, size_t *len, char **ret) { + _cleanup_free_ char *domain = NULL; + const uint8_t *optval; + size_t optlen, n = 0; int r; + assert(data); + assert(*data); + assert(len); + assert(ret); + + optval = *data; + optlen = *len; + if (optlen <= 1) return -ENODATA; @@ -754,42 +780,44 @@ static int parse_domain(const uint8_t **data, uint16_t *len, char **out_domain) return -EMSGSIZE; /* Literal label */ - label = (const char *)optval; + label = (const char*) optval; optval += c; optlen -= c; - if (!GREEDY_REALLOC(ret, n + !first + DNS_LABEL_ESCAPED_MAX)) + if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX)) return -ENOMEM; - if (first) - first = false; - else - ret[n++] = '.'; + if (n != 0) + domain[n++] = '.'; - r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX); if (r < 0) return r; n += r; } - if (n) { - if (!GREEDY_REALLOC(ret, n + 1)) + if (n > 0) { + if (!GREEDY_REALLOC(domain, n + 1)) return -ENOMEM; - ret[n] = 0; + + domain[n] = '\0'; } - *out_domain = TAKE_PTR(ret); + *ret = TAKE_PTR(domain); *data = optval; *len = optlen; return n; } -int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str) { +int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret) { _cleanup_free_ char *domain = NULL; int r; + assert(optval); + assert(ret); + r = parse_domain(&optval, &optlen, &domain); if (r < 0) return r; @@ -798,39 +826,38 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char * if (optlen != 0) return -EINVAL; - *str = TAKE_PTR(domain); + *ret = TAKE_PTR(domain); return 0; } -int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, char ***str_arr) { - size_t idx = 0; +int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, char ***ret) { _cleanup_strv_free_ char **names = NULL; int r; + assert(optval); + assert(ret); + if (optlen <= 1) return -ENODATA; if (optval[optlen - 1] != '\0') return -EINVAL; while (optlen > 0) { - _cleanup_free_ char *ret = NULL; + _cleanup_free_ char *name = NULL; - r = parse_domain(&optval, &optlen, &ret); + r = parse_domain(&optval, &optlen, &name); if (r < 0) return r; if (r == 0) continue; - r = strv_extend(&names, ret); + r = strv_consume(&names, TAKE_PTR(name)); if (r < 0) return r; - - idx++; } - *str_arr = TAKE_PTR(names); - - return idx; + *ret = TAKE_PTR(names); + return 0; } static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) { diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 298a89b086..87dd8c84d1 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -1109,11 +1109,9 @@ static int client_parse_message( size_t len, sd_dhcp6_lease *lease) { - uint16_t ia_na_status = 0, ia_pd_status = 0; - uint32_t lt_t1 = ~0, lt_t2 = ~0; + uint32_t lt_t1 = UINT32_MAX, lt_t2 = UINT32_MAX; usec_t irt = IRT_DEFAULT; bool clientid = false; - size_t pos = 0; int r; assert(client); @@ -1122,22 +1120,14 @@ static int client_parse_message( assert(lease); len -= sizeof(DHCP6Message); + for (size_t offset = 0; offset < len;) { + uint16_t optcode; + size_t optlen; + const uint8_t *optval; - while (pos < len) { - DHCP6Option *option = (DHCP6Option *) &message->options[pos]; - uint16_t optcode, optlen; - int status; - uint8_t *optval; - - if (len < pos + offsetof(DHCP6Option, data)) - return -ENOBUFS; - - optcode = be16toh(option->code); - optlen = be16toh(option->len); - optval = option->data; - - if (len < pos + offsetof(DHCP6Option, data) + optlen) - return -ENOBUFS; + r = dhcp6_option_parse(message->options, len, &offset, &optcode, &optlen, &optval); + if (r < 0) + return r; switch (optcode) { case SD_DHCP6_OPTION_CLIENTID: @@ -1176,62 +1166,75 @@ static int client_parse_message( break; - case SD_DHCP6_OPTION_STATUS_CODE: - status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option)); - if (status < 0) - return status; + case SD_DHCP6_OPTION_STATUS_CODE: { + _cleanup_free_ char *msg = NULL; - if (status > 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s Status %s", + 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), - dhcp6_message_status_to_string(status)); - + strempty(msg), isempty(msg) ? "" : ": ", + dhcp6_message_status_to_string(r)); break; + } + case SD_DHCP6_OPTION_IA_NA: { + _cleanup_(dhcp6_lease_free_ia) DHCP6IA ia = {}; - case SD_DHCP6_OPTION_IA_NA: 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, option, client->ia_pd.ia_na.id, &lease->ia, &ia_na_status); - if (r < 0 && r != -ENOANO) + 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 (ia_na_status == DHCP6_STATUS_NO_ADDRS_AVAIL) { - pos += offsetof(DHCP6Option, data) + optlen; + if (lease->ia.addresses) { + log_dhcp6_client(client, "Received duplicate matching IA_NA option, ignoring."); continue; } - if (lease->ia.addresses) { - lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1)); - lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t2)); - } + 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 = {}; - case SD_DHCP6_OPTION_IA_PD: 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, option, client->ia_pd.ia_pd.id, &lease->pd, &ia_pd_status); - if (r < 0 && r != -ENOANO) + 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 (ia_pd_status == DHCP6_STATUS_NO_PREFIX_AVAIL) { - pos += offsetof(DHCP6Option, data) + optlen; + if (lease->pd.addresses) { + log_dhcp6_client(client, "Received duplicate matching IA_PD option, ignoring."); continue; } - if (lease->pd.addresses) { - lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1)); - lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2)); - } + 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) @@ -1240,28 +1243,28 @@ static int client_parse_message( break; case SD_DHCP6_OPTION_DNS_SERVERS: - r = dhcp6_lease_set_dns(lease, optval, optlen); + r = dhcp6_lease_add_dns(lease, optval, optlen); if (r < 0) return r; break; case SD_DHCP6_OPTION_DOMAIN_LIST: - r = dhcp6_lease_set_domains(lease, optval, optlen); + r = dhcp6_lease_add_domains(lease, optval, optlen); if (r < 0) return r; break; case SD_DHCP6_OPTION_NTP_SERVER: - r = dhcp6_lease_set_ntp(lease, optval, optlen); + r = dhcp6_lease_add_ntp(lease, optval, optlen); if (r < 0) return r; break; case SD_DHCP6_OPTION_SNTP_SERVERS: - r = dhcp6_lease_set_sntp(lease, optval, optlen); + r = dhcp6_lease_add_sntp(lease, optval, optlen); if (r < 0) return r; @@ -1281,13 +1284,8 @@ static int client_parse_message( irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC; break; } - - pos += offsetof(DHCP6Option, data) + optlen; } - if (ia_na_status > 0 && ia_pd_status > 0) - return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "No IA_PD prefix or IA_NA address received. Ignoring."); - if (!clientid) return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s has incomplete options", dhcp6_message_type_to_string(message->type)); @@ -1297,16 +1295,19 @@ static int client_parse_message( 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->ia.ia_na.lifetime_t1 = htobe32(lt_t1); - lease->ia.ia_na.lifetime_t2 = htobe32(lt_t2); - } + 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->pd.addresses) { - lease->pd.ia_pd.lifetime_t1 = htobe32(lt_t1); - lease->pd.ia_pd.lifetime_t2 = htobe32(lt_t2); + 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); diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 9082185bca..c55b06d2f7 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -193,86 +193,69 @@ void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) { lease->prefix_iter = lease->pd.addresses; } -int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { - int r; - +int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { assert_return(lease, -EINVAL); assert_return(optval, -EINVAL); - if (!optlen) + if (optlen == 0) return 0; - r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->dns, - lease->dns_count); - if (r < 0) - return r; - - lease->dns_count = r; - - return 0; + return dhcp6_option_parse_addresses(optval, optlen, &lease->dns, &lease->dns_count); } -int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs) { +int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret) { assert_return(lease, -EINVAL); - assert_return(addrs, -EINVAL); + assert_return(ret, -EINVAL); - if (lease->dns_count) { - *addrs = lease->dns; - return lease->dns_count; - } + if (!lease->dns) + return -ENOENT; - return -ENOENT; + *ret = lease->dns; + return lease->dns_count; } -int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval, - size_t optlen) { +int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { + _cleanup_strv_free_ char **domains = NULL; int r; - char **domains; assert_return(lease, -EINVAL); assert_return(optval, -EINVAL); - if (!optlen) + if (optlen == 0) return 0; r = dhcp6_option_parse_domainname_list(optval, optlen, &domains); if (r < 0) - return 0; + return r; - strv_free_and_replace(lease->domains, domains); - lease->domains_count = r; - - return r; + return strv_extend_strv(&lease->domains, domains, true); } -int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains) { +int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) { assert_return(lease, -EINVAL); - assert_return(domains, -EINVAL); + assert_return(ret, -EINVAL); - if (lease->domains_count) { - *domains = lease->domains; - return lease->domains_count; - } + if (!lease->domains) + return -ENOENT; - return -ENOENT; + *ret = lease->domains; + return strv_length(lease->domains); } -int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { +int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { int r; - uint16_t subopt; - size_t sublen; - uint8_t *subval; assert_return(lease, -EINVAL); assert_return(optval, -EINVAL); - lease->ntp = mfree(lease->ntp); - lease->ntp_count = 0; + for (size_t offset = 0; offset < optlen;) { + const uint8_t *subval; + size_t sublen; + uint16_t subopt; - while ((r = dhcp6_option_parse(&optval, &optlen, &subopt, &sublen, - &subval)) >= 0) { - int s; - char **servers; + r = dhcp6_option_parse(optval, optlen, &offset, &subopt, &sublen, &subval); + if (r < 0) + return r; switch(subopt) { case DHCP6_NTP_SUBOPTION_SRV_ADDR: @@ -280,86 +263,74 @@ int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { if (sublen != 16) return 0; - s = dhcp6_option_parse_ip6addrs(subval, sublen, - &lease->ntp, - lease->ntp_count); - if (s < 0) - return s; - - lease->ntp_count = s; - - break; - - case DHCP6_NTP_SUBOPTION_SRV_FQDN: - r = dhcp6_option_parse_domainname_list(subval, sublen, - &servers); + r = dhcp6_option_parse_addresses(subval, sublen, &lease->ntp, &lease->ntp_count); if (r < 0) - return 0; - - strv_free_and_replace(lease->ntp_fqdn, servers); - lease->ntp_fqdn_count = r; + return r; break; - } - } - if (r != -ENOMSG) - return r; + case DHCP6_NTP_SUBOPTION_SRV_FQDN: { + _cleanup_free_ char *server = NULL; + + r = dhcp6_option_parse_domainname(subval, sublen, &server); + if (r < 0) + return r; + + if (strv_contains(lease->ntp_fqdn, server)) + continue; + + r = strv_consume(&lease->ntp_fqdn, TAKE_PTR(server)); + if (r < 0) + return r; + + break; + }} + } return 0; } -int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { - int r; - +int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { assert_return(lease, -EINVAL); assert_return(optval, -EINVAL); - if (!optlen) + if (optlen == 0) return 0; - if (lease->ntp || lease->ntp_fqdn) - return -EEXIST; - - /* Using deprecated SNTP information */ - - r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->ntp, - lease->ntp_count); - if (r < 0) - return r; - - lease->ntp_count = r; - - return 0; + /* SNTP option is defined in RFC4075, and deprecated by RFC5908. */ + return dhcp6_option_parse_addresses(optval, optlen, &lease->sntp, &lease->sntp_count); } -int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, - const struct in6_addr **addrs) { +int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret) { assert_return(lease, -EINVAL); - assert_return(addrs, -EINVAL); + assert_return(ret, -EINVAL); - if (lease->ntp_count) { - *addrs = lease->ntp; + if (lease->ntp) { + *ret = lease->ntp; return lease->ntp_count; } - return -ENOENT; -} - -int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn) { - assert_return(lease, -EINVAL); - assert_return(ntp_fqdn, -EINVAL); - - if (lease->ntp_fqdn_count) { - *ntp_fqdn = lease->ntp_fqdn; - return lease->ntp_fqdn_count; + if (lease->sntp && !lease->ntp_fqdn) { + /* Fallback to the deprecated SNTP option. */ + *ret = lease->sntp; + return lease->sntp_count; } return -ENOENT; } -int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, - size_t optlen) { +int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) { + assert_return(lease, -EINVAL); + assert_return(ret, -EINVAL); + + if (!lease->ntp_fqdn) + return -ENOENT; + + *ret = lease->ntp_fqdn; + return strv_length(lease->ntp_fqdn); +} + +int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { int r; char *fqdn; @@ -378,33 +349,31 @@ int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, return free_and_replace(lease->fqdn, fqdn); } -int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn) { +int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret) { assert_return(lease, -EINVAL); - assert_return(fqdn, -EINVAL); + assert_return(ret, -EINVAL); - if (lease->fqdn) { - *fqdn = lease->fqdn; - return 0; - } + if (!lease->fqdn) + return -ENOENT; - return -ENOENT; + *ret = lease->fqdn; + return 0; } static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) { - assert(lease); + if (!lease) + return NULL; free(lease->serverid); dhcp6_lease_free_ia(&lease->ia); dhcp6_lease_free_ia(&lease->pd); - free(lease->dns); free(lease->fqdn); - - lease->domains = strv_free(lease->domains); - + strv_free(lease->domains); free(lease->ntp); + strv_free(lease->ntp_fqdn); + free(lease->sntp); - lease->ntp_fqdn = strv_free(lease->ntp_fqdn); return mfree(lease); } diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index 22ab25b2b0..b725f5c305 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -137,7 +137,7 @@ static int test_parse_domain(sd_event *e) { data = (uint8_t []) { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 6, 'f', 'o', 'o', 'b', 'a', 'r', 0 }; r = dhcp6_option_parse_domainname_list(data, 21, &list); - assert_se(r == 2); + assert_se(r == 0); assert_se(list); assert_se(streq(list[0], "example.com")); assert_se(streq(list[1], "foobar")); @@ -156,7 +156,7 @@ static int test_parse_domain(sd_event *e) { static int test_option(sd_event *e) { uint8_t packet[] = { - 'F', 'O', 'O', + 'F', 'O', 'O', 'H', 'O', 'G', 'E', 0x00, SD_DHCP6_OPTION_ORO, 0x00, 0x07, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 0x00, SD_DHCP6_OPTION_VENDOR_CLASS, 0x00, 0x09, @@ -164,53 +164,66 @@ static int test_option(sd_event *e) { 'B', 'A', 'R', }; uint8_t result[] = { - 'F', 'O', 'O', + 'F', 'O', 'O', 'H', 'O', 'G', 'E', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'B', 'A', 'R', }; + _cleanup_free_ uint8_t *buf = NULL; + size_t offset, pos, optlen, outlen = sizeof(result); + const uint8_t *optval; uint16_t optcode; - size_t optlen; - uint8_t *optval, *buf, *out; - size_t zero = 0, pos = 3; - size_t buflen = sizeof(packet), outlen = sizeof(result); + uint8_t *out; log_debug("/* %s */", __func__); - assert_se(buflen == outlen); + assert_se(sizeof(packet) == sizeof(result)); - assert_se(dhcp6_option_parse(&buf, &zero, &optcode, &optlen, - &optval) == -ENOMSG); + offset = 0; + assert_se(dhcp6_option_parse(packet, 0, &offset, &optcode, &optlen, &optval) == -EBADMSG); - buflen -= 3; - buf = &packet[3]; - outlen -= 3; - out = &result[3]; + offset = 3; + assert_se(dhcp6_option_parse(packet, 0, &offset, &optcode, &optlen, &optval) == -EBADMSG); + + /* Tests for reading unaligned data. */ + assert_se(buf = new(uint8_t, sizeof(packet))); + for (size_t i = 0; i <= 7; i++) { + memcpy(buf, packet + i, sizeof(packet) - i); + offset = 7 - i; + assert_se(dhcp6_option_parse(buf, sizeof(packet), &offset, &optcode, &optlen, &optval) >= 0); + + assert_se(optcode == SD_DHCP6_OPTION_ORO); + assert_se(optlen == 7); + assert_se(optval == buf + 11 - i); + } + + offset = 7; + assert_se(dhcp6_option_parse(packet, sizeof(packet), &offset, &optcode, &optlen, &optval) >= 0); - assert_se(dhcp6_option_parse(&buf, &buflen, &optcode, &optlen, - &optval) >= 0); - pos += 4 + optlen; - assert_se(buf == &packet[pos]); assert_se(optcode == SD_DHCP6_OPTION_ORO); assert_se(optlen == 7); - assert_se(buflen + pos == sizeof(packet)); + assert_se(optval == packet + 11); - assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen, - optval) >= 0); + pos = 7; + outlen -= 7; + out = &result[pos]; + + assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen, optval) >= 0); + + pos += 4 + optlen; assert_se(out == &result[pos]); assert_se(*out == 0x00); - assert_se(dhcp6_option_parse(&buf, &buflen, &optcode, &optlen, - &optval) >= 0); - pos += 4 + optlen; - assert_se(buf == &packet[pos]); + assert_se(dhcp6_option_parse(packet, sizeof(packet), &offset, &optcode, &optlen, &optval) >= 0); + assert_se(optcode == SD_DHCP6_OPTION_VENDOR_CLASS); assert_se(optlen == 9); - assert_se(buflen + pos == sizeof(packet)); + assert_se(optval == packet + 22); - assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen, - optval) >= 0); + assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen, optval) >= 0); + + pos += 4 + optlen; assert_se(out == &result[pos]); assert_se(*out == 'B'); @@ -236,7 +249,7 @@ static int test_option_status(sd_event *e) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d, - /* status option */ + /* IA address status option */ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x01, }; static const uint8_t option3[] = { @@ -248,7 +261,7 @@ static int test_option_status(sd_event *e) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d, - /* status option */ + /* IA address status option */ 0x00, 0x0d, 0x00, 0x08, 0x00, 0x00, 'f', 'o', 'o', 'b', 'a', 'r', }; @@ -262,7 +275,7 @@ static int test_option_status(sd_event *e) { 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* status option */ + /* PD prefix status option */ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, }; static const uint8_t option5[] = { @@ -275,7 +288,7 @@ static int test_option_status(sd_event *e) { 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* status option */ + /* PD prefix status option */ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, /* IA PD Prefix #2 */ 0x00, 0x1a, 0x00, 0x1f, @@ -283,6 +296,7 @@ static int test_option_status(sd_event *e) { 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x0l, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* PD prefix status option */ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, }; DHCP6Option *option; @@ -295,62 +309,62 @@ static int test_option_status(sd_event *e) { memcpy(&iaid, option1 + 4, sizeof(iaid)); zero(ia); - option = (DHCP6Option *)option1; + option = (DHCP6Option*) option1; assert_se(sizeof(option1) == sizeof(DHCP6Option) + be16toh(option->len)); - r = dhcp6_option_parse_ia(NULL, option, 0, &ia, NULL); + r = dhcp6_option_parse_ia(NULL, 0, be16toh(option->code), be16toh(option->len), option->data, &ia); assert_se(r == -ENOANO); - r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL); - assert_se(r == 0); - assert_se(ia.addresses == NULL); + 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, option, iaid, &ia, NULL); - assert_se(r == -ENOBUFS); - assert_se(ia.addresses == NULL); + 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, option, iaid, &ia, NULL); - assert_se(r == -ENOBUFS); - assert_se(ia.addresses == NULL); + 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; + option = (DHCP6Option*) option2; assert_se(sizeof(option2) == sizeof(DHCP6Option) + be16toh(option->len)); - r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL); - assert_se(r >= 0); - assert_se(ia.addresses == NULL); + 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; + option = (DHCP6Option*) option3; assert_se(sizeof(option3) == sizeof(DHCP6Option) + be16toh(option->len)); - r = dhcp6_option_parse_ia(NULL, option, iaid, &ia, NULL); + r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia); assert_se(r >= 0); - assert_se(ia.addresses != NULL); + assert_se(ia.addresses); dhcp6_lease_free_ia(&ia); zero(pd); - option = (DHCP6Option *)option4; + option = (DHCP6Option*) option4; assert_se(sizeof(option4) == sizeof(DHCP6Option) + be16toh(option->len)); - r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, NULL); + r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &pd); assert_se(r >= 0); - assert_se(pd.addresses != NULL); + 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); zero(pd); - option = (DHCP6Option *)option5; + option = (DHCP6Option*) option5; assert_se(sizeof(option5) == sizeof(DHCP6Option) + be16toh(option->len)); - r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, NULL); + r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &pd); assert_se(r >= 0); - assert_se(pd.addresses != NULL); + assert_se(pd.addresses); dhcp6_lease_free_ia(&pd); return 0; @@ -468,7 +482,7 @@ static int test_advertise_option(sd_event *e) { val = htobe32(120); assert_se(!memcmp(optval + 8, &val, sizeof(val))); - assert_se(dhcp6_option_parse_ia(NULL, option, iaid, &lease->ia, NULL) >= 0); + assert_se(dhcp6_option_parse_ia(NULL, iaid, optcode, optlen, optval, &lease->ia) >= 0); break; } @@ -496,20 +510,17 @@ static int test_advertise_option(sd_event *e) { case SD_DHCP6_OPTION_DNS_SERVERS: assert_se(optlen == 16); - assert_se(dhcp6_lease_set_dns(lease, optval, - optlen) >= 0); + 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_set_domains(lease, optval, - optlen) >= 0); + 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_set_sntp(lease, optval, - optlen) >= 0); + assert_se(dhcp6_lease_add_sntp(lease, optval, optlen) >= 0); break; default: @@ -665,7 +676,7 @@ static int test_client_verify_request(DHCP6Message *request, size_t len) { assert_se(!memcmp(optval + 8, &val, sizeof(val))); /* Then, this should refuse all addresses. */ - assert_se(dhcp6_option_parse_ia(NULL, option, test_iaid, &lease->ia, NULL) >= 0); + assert_se(dhcp6_option_parse_ia(NULL, test_iaid, optcode, optlen, optval, &lease->ia) == -ENODATA); break; diff --git a/src/systemd/sd-dhcp6-lease.h b/src/systemd/sd-dhcp6-lease.h index f77b31acf9..a10901e5cb 100644 --- a/src/systemd/sd-dhcp6-lease.h +++ b/src/systemd/sd-dhcp6-lease.h @@ -39,11 +39,11 @@ int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix, uint32_t *lifetime_preferred, uint32_t *lifetime_valid); -int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs); -int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains); -int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **addrs); -int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn); -int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn); +int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret); +int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret); +int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret); +int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret); +int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret); sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease); sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease);