chiark / gitweb /
sd-dhcp6-lease: Name the structure containing IAADDR data
[elogind.git] / src / libsystemd-network / dhcp6-option.c
index cc4d261ae718ecb8c426d23573ea4627b12401ef..e6a31778f47a7c3e68ddf1b3f5e74d3bb9e1ad12 100644 (file)
@@ -32,7 +32,6 @@
 #define DHCP6_OPTION_HDR_LEN                    4
 #define DHCP6_OPTION_IA_NA_LEN                  12
 #define DHCP6_OPTION_IA_TA_LEN                  4
-#define DHCP6_OPTION_IAADDR_LEN                 24
 
 static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
                              size_t optlen) {
@@ -58,13 +57,14 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
                         size_t optlen, const void *optval) {
         int r;
 
-        assert_return(optval, -EINVAL);
+        assert_return(optval || optlen == 0, -EINVAL);
 
         r = option_append_hdr(buf, buflen, code, optlen);
         if (r < 0)
                 return r;
 
-        memcpy(*buf, optval, optlen);
+        if (optval)
+                memcpy(*buf, optval, optlen);
 
         *buf += optlen;
         *buflen -= optlen;
@@ -110,16 +110,16 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
 
         LIST_FOREACH(addresses, addr, ia->addresses) {
                 r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR,
-                                      DHCP6_OPTION_IAADDR_LEN);
+                                      sizeof(addr->iaaddr));
                 if (r < 0)
                         return r;
 
-                memcpy(*buf, &addr->address, DHCP6_OPTION_IAADDR_LEN);
+                memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
 
-                *buf += DHCP6_OPTION_IAADDR_LEN;
-                *buflen -= DHCP6_OPTION_IAADDR_LEN;
+                *buf += sizeof(addr->iaaddr);
+                *buflen -= sizeof(addr->iaaddr);
 
-                ia_addrlen += DHCP6_OPTION_HDR_LEN + DHCP6_OPTION_IAADDR_LEN;
+                ia_addrlen += DHCP6_OPTION_HDR_LEN + sizeof(addr->iaaddr);
         }
 
         r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
@@ -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 +
+                    sizeof(addr->iaaddr)) {
+                        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 +
+                    sizeof(addr->iaaddr)) {
+                        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->iaaddr, *buf, sizeof(addr->iaaddr));
+
+                        lt_valid = be32toh(addr->iaaddr.lifetime_valid);
+                        lt_pref = be32toh(addr->iaaddr.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;
+}