]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-dhcp6-client: modernize dhcp6_option_parse_ia()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 23 Sep 2021 15:50:01 +0000 (00:50 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 29 Sep 2021 06:29:40 +0000 (15:29 +0900)
This makes
- the function not update the arguments for storing results on error,
- use dhcp6_option_parse() to parse sub options,
- ignore all errors, except for -ENOMEM, in parsing sub options,
- update log messages.

src/libsystemd-network/dhcp6-internal.h
src/libsystemd-network/dhcp6-option.c
src/libsystemd-network/sd-dhcp6-client.c
src/libsystemd-network/test-dhcp6-client.c

index dfb0734520d73d4f6cfb72199d8c89162b5b26c7..5c24692233f53a58fdbd6045a548bd1d598391c4 100644 (file)
@@ -110,7 +110,13 @@ int dhcp6_option_parse(
                 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, DHCP6Option *iaoption, be32_t iaid, DHCP6IA *ia, uint16_t *ret_status_code);
+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_ip6addrs(const 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,
index 35950386b0cec8f1aa69198d10f586efe3b2b0ac..3dd1cb4956d9545ae100c35a9e9f2de27f11fb78 100644 (file)
 #include "strv.h"
 #include "unaligned.h"
 
-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))
@@ -463,279 +451,274 @@ static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t
         return 0;
 }
 
-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;
+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;
 
-        if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option))
-                return -ENOBUFS;
+        assert(data);
+        assert(ret);
 
-        lt_valid = be32toh(addr_option->iaaddr.lifetime_valid);
-        lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred);
+        if (len < sizeof(struct iaaddr))
+                return -EBADMSG;
 
-        if (lt_valid == 0 || lt_pref > lt_valid)
+        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),
-                                              "Valid lifetime of an IA address is zero or "
-                                              "preferred lifetime %"PRIu32" > valid lifetime %"PRIu32,
+                                              "Received an IA address with preferred lifetime %"PRIu32
+                                              " larger than valid lifetime %"PRIu32", ignoring.",
                                               lt_pref, lt_valid);
 
-        if (be16toh(option->len) + offsetof(DHCP6Option, data) > offsetof(DHCP6AddressOption, options)) {
-                r = dhcp6_option_parse_ia_options(client, option->data + sizeof(struct iaaddr), be16toh(option->len) - sizeof(struct iaaddr));
+        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;
         }
 
-        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);
+        LIST_INIT(addresses, a);
+        memcpy(&a->iaaddr, data, sizeof(struct iaaddr));
 
+        *ret = a;
         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;
+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 (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option))
-                return -ENOBUFS;
+        if (len < sizeof(struct iapdprefix))
+                return -ENOMSG;
 
-        lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid);
-        lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred);
+        lt_valid = be32toh(((const struct iapdprefix*) data)->lifetime_valid);
+        lt_pref = be32toh(((const struct iapdprefix*) data)->lifetime_preferred);
 
-        if (lt_valid == 0 || lt_pref > lt_valid)
+        if (lt_valid == 0)
+                return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+                                              "Received a PD prefix with zero valid lifetime, ignoring.");
+        if (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,
+                                              "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) > offsetof(DHCP6PDPrefixOption, options)) {
-                r = dhcp6_option_parse_ia_options(client, option->data + sizeof(struct iapdprefix), be16toh(option->len) - sizeof(struct iapdprefix));
+        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;
         }
 
-        prefix = new0(DHCP6Address, 1);
-        if (!prefix)
+        a = new(DHCP6Address, 1);
+        if (!a)
                 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) {
-
-        uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
-        uint16_t iatype, optlen;
-        size_t iaaddr_offset;
-        size_t i, len;
-        uint16_t opt;
+                uint16_t option_code,
+                size_t option_data_len,
+                const uint8_t *option_data,
+                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;
         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;
-
-                /* 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 (option_data_len < DHCP6_OPTION_IA_NA_LEN)
+                        return -EBADMSG;
 
-                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;
-
-                /* 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 (option_data_len < DHCP6_OPTION_IA_PD_LEN)
+                        return -EBADMSG;
 
-                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");
+                        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_address(client, option, ia, &lt_valid);
-                        if (r < 0 && r != -EINVAL)
+                        r = dhcp6_option_parse_ia_address(client, subdata, subdata_len, &a);
+                        if (r == -ENOMEM)
                                 return r;
-                        if (r >= 0 && lt_valid < lt_min)
-                                lt_min = lt_valid;
+                        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;
 
-                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");
+                        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_pdprefix(client, option, ia, &lt_valid);
-                        if (r < 0 && r != -EINVAL)
+                        r = dhcp6_option_parse_ia_pdprefix(client, subdata, subdata_len, &a);
+                        if (r == -ENOMEM)
                                 return r;
-                        if (r >= 0 && lt_valid < lt_min)
-                                lt_min = lt_valid;
+                        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(option->data, optlen, &msg);
-                        if (r < 0)
+                        r = dhcp6_option_parse_status(subdata, subdata_len, &msg);
+                        if (r == -ENOMEM)
                                 return r;
-                        if (r > 0) {
-                                if (ret_status_code)
-                                        *ret_status_code = r;
-
-                                log_dhcp6_client(client,
-                                                 "Received an IA option with non-zero status: %s%s%s",
-                                                 strempty(msg), isempty(msg) ? "" : ": ",
-                                                 dhcp6_message_status_to_string(r));
-
-                                return 0;
-                        }
+                        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);
                 }
+        }
+
+        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;
 
-                i += sizeof(*option) + optlen;
+                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(iatype) {
+        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(const uint8_t *optval, uint16_t optlen,
index 8955aedc1f04a6c92d4ebd1519963d1f90237976..5063bfa492df91e76be2769893a33bc4a6f0d858 100644 (file)
@@ -1109,8 +1109,7 @@ 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;
@@ -1138,6 +1137,8 @@ static int client_parse_message(
                 if (len < pos + offsetof(DHCP6Option, data) + optlen)
                         return -ENOBUFS;
 
+                pos += offsetof(DHCP6Option, data) + optlen;
+
                 switch (optcode) {
                 case SD_DHCP6_OPTION_CLIENTID:
                         if (clientid)
@@ -1190,50 +1191,60 @@ static int client_parse_message(
                                                               dhcp6_message_status_to_string(r));
                         break;
                 }
-                case SD_DHCP6_OPTION_IA_NA:
+                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, 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 (ia_na_status == DHCP6_STATUS_NO_ADDRS_AVAIL) {
-                                pos += offsetof(DHCP6Option, data) + optlen;
+                        if (r < 0)
                                 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));
+                                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 = {};
 
-                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 (ia_pd_status == DHCP6_STATUS_NO_PREFIX_AVAIL) {
-                                pos += offsetof(DHCP6Option, data) + optlen;
+                        if (r < 0)
                                 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));
+                                log_dhcp6_client(client, "Received duplicate matching IA_PD option, ignoring.");
+                                continue;
                         }
 
-                        break;
+                        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)
@@ -1283,13 +1294,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));
@@ -1299,16 +1305,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);
index fb948ea8a9874127811ff2edcc48fefdfb79c14f..d77b39b0d7461759352d00da3860b8e0d3c6a88a 100644 (file)
@@ -302,7 +302,6 @@ static int test_option_status(sd_event *e) {
         DHCP6Option *option;
         DHCP6IA ia, pd;
         be32_t iaid;
-        uint16_t status;
         int r = 0;
 
         log_debug("/* %s */", __func__);
@@ -313,40 +312,37 @@ static int test_option_status(sd_event *e) {
         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, &status);
-        assert_se(r == 0);
-        assert_se(status == 1);
+        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, &status);
-        assert_se(r == -ENOBUFS);
+        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, &status);
-        assert_se(r == -ENOBUFS);
+        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, option, iaid, &ia, &status);
-        assert_se(r > 0);
-        assert_se(status == 0);
+        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, option, iaid, &ia, &status);
-        assert_se(r > 0);
-        assert_se(status == 0);
+        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);
 
@@ -354,9 +350,8 @@ static int test_option_status(sd_event *e) {
         option = (DHCP6Option*) option4;
         assert_se(sizeof(option4) == sizeof(DHCP6Option) + be16toh(option->len));
 
-        r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, &status);
-        assert_se(r > 0);
-        assert_se(status == 0);
+        r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &pd);
+        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);
@@ -367,9 +362,8 @@ static int test_option_status(sd_event *e) {
         option = (DHCP6Option*) option5;
         assert_se(sizeof(option5) == sizeof(DHCP6Option) + be16toh(option->len));
 
-        r = dhcp6_option_parse_ia(NULL, option, iaid, &pd, &status);
-        assert_se(r > 0);
-        assert_se(status == 0);
+        r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &pd);
+        assert_se(r >= 0);
         assert_se(pd.addresses);
         dhcp6_lease_free_ia(&pd);
 
@@ -488,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;
                 }
@@ -685,7 +679,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;