chiark / gitweb /
sd-dhcp6-client: Add IA Address option parsing
authorPatrik Flykt <patrik.flykt@linux.intel.com>
Thu, 19 Jun 2014 12:39:39 +0000 (15:39 +0300)
committerPatrik Flykt <patrik.flykt@linux.intel.com>
Thu, 19 Jun 2014 12:44:44 +0000 (15:44 +0300)
Add functionality to parse DHCPv6 Identity Association for
Non-temporary (IA_NA) and Temporary Addresses (IA_TA) options.
Both of them contain one or more IA Address (IAADDR) options
and optinally a status code option. Only the IA_NA option
contains lease lifetimes. See RFC 3315, sections 22.4., 22.5.,
22.6., 22.13. and appendix B. for details. If the lease
timeouts are not set, use the ones recommended for servers in
section 22.4.

Factor out common code in the form of an option header parsing
helper function.

src/libsystemd-network/dhcp6-internal.h
src/libsystemd-network/dhcp6-option.c

index 31f5bd2b9b8fc828f6da87e9027cb1702cd39b18..ec1d82abeed938b06f300bf777586c60f97d1cc4 100644 (file)
@@ -66,6 +66,8 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
 int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia);
 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
                        size_t *optlen, uint8_t **optvalue);
+int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
+                          DHCP6IA *ia);
 
 int dhcp6_network_bind_udp_socket(int index, struct in6_addr *address);
 int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
index cc4d261ae718ecb8c426d23573ea4627b12401ef..f488832cf919ca4406c3a192ee124b9b05cb8323 100644 (file)
@@ -129,22 +129,185 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
         return 0;
 }
 
+
+static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *opt,
+                            size_t *optlen) {
+        uint16_t len;
+
+        assert_return(buf, -EINVAL);
+        assert_return(opt, -EINVAL);
+        assert_return(optlen, -EINVAL);
+
+        if (*buflen < 4)
+                return -ENOMSG;
+
+        len = (*buf)[2] << 8 | (*buf)[3];
+
+        if (len > *buflen)
+                return -ENOMSG;
+
+        *opt = (*buf)[0] << 8 | (*buf)[1];
+        *optlen = len;
+
+        *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) {
-        assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
+        int r;
 
-        if (*buflen == 0)
-                return -ENOMSG;
+        assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
 
-        *optcode = (*buf)[0] << 8 | (*buf)[1];
-        *optlen = (*buf)[2] << 8 | (*buf)[3];
+        r = option_parse_hdr(buf, buflen, optcode, optlen);
+        if (r < 0)
+                return r;
 
-        if (*optlen > *buflen - 4)
+        if (*optlen > *buflen)
                 return -ENOBUFS;
 
-        *optvalue = &(*buf)[4];
-        *buflen -= (*optlen + 4);
-        (*buf) += (*optlen + 4);
+        *optvalue = *buf;
+        *buflen -= *optlen;
+        *buf += *optlen;
 
         return 0;
 }
+
+int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
+                          DHCP6IA *ia) {
+        int r;
+        uint16_t opt, status;
+        size_t optlen;
+        size_t iaaddr_offset;
+        DHCP6Address *addr;
+        uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
+
+        assert_return(ia, -EINVAL);
+        assert_return(!ia->addresses, -EINVAL);
+
+        switch (iatype) {
+        case DHCP6_OPTION_IA_NA:
+
+                if (*buflen < DHCP6_OPTION_IA_NA_LEN + DHCP6_OPTION_HDR_LEN +
+                    DHCP6_OPTION_IAADDR_LEN) {
+                        r = -ENOBUFS;
+                        goto error;
+                }
+
+                iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
+                memcpy(&ia->id, *buf, iaaddr_offset);
+
+                lt_t1 = be32toh(ia->lifetime_t1);
+                lt_t2 = be32toh(ia->lifetime_t2);
+
+                if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
+                        log_dhcp6_client(client, "IA T1 %ds > T2 %ds",
+                                         lt_t1, lt_t2);
+                        r = -EINVAL;
+                        goto error;
+                }
+
+                break;
+
+        case DHCP6_OPTION_IA_TA:
+                if (*buflen < DHCP6_OPTION_IA_TA_LEN + DHCP6_OPTION_HDR_LEN +
+                    DHCP6_OPTION_IAADDR_LEN) {
+                        r = -ENOBUFS;
+                        goto error;
+                }
+
+                iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
+                memcpy(&ia->id, *buf, iaaddr_offset);
+
+                ia->lifetime_t1 = 0;
+                ia->lifetime_t2 = 0;
+
+                break;
+
+        default:
+                r = -ENOMSG;
+                goto error;
+        }
+
+        ia->type = iatype;
+
+        *buflen -= iaaddr_offset;
+        *buf += iaaddr_offset;
+
+        while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
+
+                switch (opt) {
+                case DHCP6_OPTION_IAADDR:
+
+                        addr = new0(DHCP6Address, 1);
+                        if (!addr) {
+                                r = -ENOMEM;
+                                goto error;
+                        }
+
+                        LIST_INIT(addresses, addr);
+
+                        memcpy(&addr->address, *buf, DHCP6_OPTION_IAADDR_LEN);
+
+                        lt_valid = be32toh(addr->lifetime_valid);
+                        lt_pref = be32toh(addr->lifetime_valid);
+
+                        if (!lt_valid || lt_pref > lt_valid) {
+                                log_dhcp6_client(client, "IA preferred %ds > valid %ds",
+                                                 lt_pref, lt_valid);
+                                free(addr);
+                        } else {
+                                LIST_PREPEND(addresses, ia->addresses, addr);
+                                if (lt_valid < lt_min)
+                                        lt_min = lt_valid;
+                        }
+
+                        break;
+
+                case DHCP6_OPTION_STATUS_CODE:
+                        if (optlen < sizeof(status))
+                                break;
+
+                        status = (*buf)[0] << 8 | (*buf)[1];
+                        if (status) {
+                                log_dhcp6_client(client, "IA status %d",
+                                                 status);
+                                r = -EINVAL;
+                                goto error;
+                        }
+
+                        break;
+
+                default:
+                        log_dhcp6_client(client, "Unknown IA option %d", opt);
+                        break;
+                }
+
+                *buflen -= optlen;
+                *buf += optlen;
+        }
+
+        if (r == -ENOMSG)
+                r = 0;
+
+        if (!ia->lifetime_t1 && !ia->lifetime_t2) {
+                lt_t1 = lt_min / 2;
+                lt_t2 = lt_min / 10 * 8;
+                ia->lifetime_t1 = htobe32(lt_t1);
+                ia->lifetime_t2 = htobe32(lt_t2);
+
+                log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero",
+                                 lt_t1, lt_t2);
+        }
+
+        if (*buflen)
+                r = -ENOMSG;
+
+error:
+        *buf += *buflen;
+        *buflen = 0;
+
+        return r;
+}