From f8ad4dd45d761d839de49ddff9d7fbf46892c148 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:46 +0200 Subject: [PATCH] dhcp6: Parse IA PD and PD Prefix options Parse IA PD options and the prefixes in one or more PD Prefix options. As the PD option contains identical data as the IA NA option, re-use the same general data structures and sub-option parsing logic. Similar to IA NA addresses, PD and associated prefixes are stored in the address list of the IA PD lease. An IA sub-option Status code will affect the IA NA and IA PD option in question and cause those options to be ignored. A Status code option in an IA Address or IA PD Prefix option affects only that IA Address or Prefix. --- src/libsystemd-network/dhcp6-internal.h | 21 ++- src/libsystemd-network/dhcp6-lease-internal.h | 1 + src/libsystemd-network/dhcp6-option.c | 133 ++++++++++++++++-- 3 files changed, 144 insertions(+), 11 deletions(-) diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 8691c2ca02..b1d7d63932 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -43,12 +43,23 @@ struct iaaddr { 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); - struct iaaddr iaaddr; + union { + struct iaaddr iaaddr; + struct iapdprefix iapdprefix; + }; }; /* Non-temporary Address option */ @@ -58,6 +69,13 @@ struct ia_na { 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; @@ -67,6 +85,7 @@ struct DHCP6IA { uint16_t type; union { struct ia_na ia_na; + struct ia_pd ia_pd; struct ia_ta ia_ta; }; sd_event_source *timeout_t1; diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index a3f00d4a5d..ba9e9945dc 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -36,6 +36,7 @@ struct sd_dhcp6_lease { bool rapid_commit; DHCP6IA ia; + DHCP6IA pd; DHCP6Address *addr_iter; diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index c196078a71..c894ab4b34 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -46,8 +46,15 @@ typedef struct DHCP6AddressOption { uint8_t options[]; } _packed_ DHCP6AddressOption; -#define DHCP6_OPTION_IA_NA_LEN 12 -#define DHCP6_OPTION_IA_TA_LEN 4 +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)) static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, size_t optlen) { @@ -269,6 +276,46 @@ static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia, return 0; } +static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia, + uint32_t *lifetime_valid) { + DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option; + DHCP6Address *prefix; + uint32_t lt_valid, lt_pref; + int r; + + if (be16toh(option->len) + sizeof(DHCP6Option) < 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) { + log_dhcp6_client(client, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d", + lt_pref, lt_valid); + + return 0; + } + + if (be16toh(option->len) + sizeof(DHCP6Option) > sizeof(*pdprefix_option)) { + r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options); + if (r != 0) + return r < 0 ? r: 0; + } + + 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); + + *lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid); + + return 0; +} + int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { uint16_t iatype, optlen; size_t i, len; @@ -298,7 +345,29 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { lt_t2 = be32toh(ia->ia_na.lifetime_t2); if (lt_t1 && lt_t2 && lt_t1 > lt_t2) { - log_dhcp6_client(client, "IA T1 %ds > T2 %ds", + log_dhcp6_client(client, "IA NA T1 %ds > T2 %ds", + lt_t1, lt_t2); + r = -EINVAL; + goto error; + } + + break; + + case SD_DHCP6_OPTION_IA_PD: + + if (len < sizeof(ia->ia_pd)) { + r = -ENOBUFS; + goto error; + } + + 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 && lt_t1 > lt_t2) { + log_dhcp6_client(client, "IA PD T1 %ds > T2 %ds", lt_t1, lt_t2); r = -EINVAL; goto error; @@ -339,6 +408,12 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { switch (opt) { case SD_DHCP6_OPTION_IAADDR: + if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) { + log_dhcp6_client(client, "IA Address option not in IA NA or TA option"); + r = -EINVAL; + goto error; + } + r = dhcp6_option_parse_address(option, ia, <_valid); if (r < 0) goto error; @@ -348,6 +423,23 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { break; + case SD_DHCP6_OPTION_IA_PD_PREFIX: + + if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_PD)) { + log_dhcp6_client(client, "IA PD Prefix option not in IA PD option"); + r = -EINVAL; + goto error; + } + + r = dhcp6_option_parse_pdprefix(option, ia, <_valid); + if (r < 0) + goto error; + + if (lt_valid < lt_min) + lt_min = lt_valid; + + break; + case SD_DHCP6_OPTION_STATUS_CODE: status = dhcp6_option_parse_status(option); @@ -371,14 +463,35 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { i += sizeof(*option) + optlen; } - if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) { - 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); + switch(iatype) { + case SD_DHCP6_OPTION_IA_NA: + if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) { + 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 %ds and T2 %ds as both were zero", + lt_t1, lt_t2); + } + + break; + + case SD_DHCP6_OPTION_IA_PD: + if (!ia->ia_pd.lifetime_t1 && !ia->ia_pd.lifetime_t2) { + 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 %ds and T2 %ds as both were zero", + lt_t1, lt_t2); + } - log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero", - lt_t1, lt_t2); + break; + + default: + break; } error: -- 2.25.1