summaryrefslogtreecommitdiff
path: root/src/libsystemd-network/dhcp6-option.c
diff options
context:
space:
mode:
authorPatrik Flykt <patrik.flykt@linux.intel.com>2014-06-19 15:39:39 +0300
committerPatrik Flykt <patrik.flykt@linux.intel.com>2014-06-19 15:44:44 +0300
commitc6affce8740bb0cee42eebf6d1d44dd518035e88 (patch)
treef482951415805a9c4722179cde5b662fe747142e /src/libsystemd-network/dhcp6-option.c
parent3fb2c57038cf8dad396421989f43697fcf4ac4a4 (diff)
sd-dhcp6-client: Add IA Address option parsing
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.
Diffstat (limited to 'src/libsystemd-network/dhcp6-option.c')
-rw-r--r--src/libsystemd-network/dhcp6-option.c181
1 files changed, 172 insertions, 9 deletions
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;
+}