summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/libsystemd-network/dhcp6-internal.h2
-rw-r--r--src/libsystemd-network/dhcp6-option.c181
2 files changed, 174 insertions, 9 deletions
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index 31f5bd2b9b..ec1d82abee 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -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,
diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c
index cc4d261ae7..f488832cf9 100644
--- a/src/libsystemd-network/dhcp6-option.c
+++ b/src/libsystemd-network/dhcp6-option.c
@@ -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;
+}