/* * * Embedded Linux library * * Copyright (C) 2020 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "strv.h" #include "private.h" #include "useful.h" #include "dhcp6-private.h" #include "dhcp6.h" #include "net-private.h" static inline char *get_ip(const uint8_t a[static 16]) { struct in6_addr addr; char buf[INET6_ADDRSTRLEN]; memcpy(addr.s6_addr, a, 16); return l_strdup(inet_ntop(AF_INET6, &addr, buf, sizeof(buf))); } struct l_dhcp6_lease *_dhcp6_lease_new(void) { struct l_dhcp6_lease *ret = l_new(struct l_dhcp6_lease, 1); return ret; } void _dhcp6_lease_free(struct l_dhcp6_lease *lease) { if (!lease) return; l_free(lease->server_id); l_free(lease->dns); l_strfreev(lease->domain_list); l_free(lease); } static char **convert_ipv6_addresses(const void *a, uint16_t l) { uint16_t i = 0; char **r; l >>= 4; r = l_new(char *, l + 1); while (i < l) { r[i++] = get_ip(a); a += 16; } return r; } static int parse_ia_address(const void *ia_addr, uint16_t ia_addr_len, struct dhcp6_address_info *out) { struct dhcp6_option_iter iter; uint16_t t; uint16_t l; const void *v; uint32_t preferred_lifetime; uint32_t valid_lifetime; if (ia_addr_len < 24) return -EBADMSG; preferred_lifetime = l_get_be32(ia_addr + 16); valid_lifetime = l_get_be32(ia_addr + 20); /* * TODO: The RFC allows valid_lifetime to be 0 in the case that the * DHCP server might want to invalidate or take back an address. * However, since the address space is so large, the use case for doing * so is a bit unclear. For now we do not treat this as a possibility * that can happen and simply reject the option */ if (preferred_lifetime > valid_lifetime || !valid_lifetime) return -EINVAL; __dhcp6_option_iter_init(&iter, ia_addr + 24, ia_addr_len - 24); while (_dhcp6_option_iter_next(&iter, &t, &l, &v)) { uint16_t status; switch (t) { case DHCP6_OPTION_STATUS_CODE: if (l < 2) return -EBADMSG; status = l_get_be16(v); if (status != 0) return -EINVAL; break; } } memset(out, 0, sizeof(*out)); memcpy(out->addr, ia_addr, sizeof(out->addr)); out->preferred_lifetime = preferred_lifetime; out->valid_lifetime = valid_lifetime; return 0; } static int parse_ia_prefix(const void *ia_prefix, uint16_t ia_prefix_len, struct dhcp6_address_info *out) { struct dhcp6_option_iter iter; uint16_t t; uint16_t l; const void *v; uint32_t preferred_lifetime; uint32_t valid_lifetime; if (ia_prefix_len < 25) return -EBADMSG; preferred_lifetime = l_get_be32(ia_prefix); valid_lifetime = l_get_be32(ia_prefix + 4); if (preferred_lifetime > valid_lifetime || !valid_lifetime) return -EINVAL; __dhcp6_option_iter_init(&iter, ia_prefix + 25, ia_prefix_len - 25); while (_dhcp6_option_iter_next(&iter, &t, &l, &v)) { uint16_t status; switch (t) { case DHCP6_OPTION_STATUS_CODE: if (l < 2) return -EBADMSG; status = l_get_be16(v); if (status != 0) return -EINVAL; break; } } memset(out, 0, sizeof(*out)); out->prefix_len = l_get_u8(ia_prefix + 8); memcpy(out->addr, ia_prefix + 9, sizeof(out->addr)); out->preferred_lifetime = preferred_lifetime; out->valid_lifetime = valid_lifetime; return 0; } static int parse_ia(const void *ia, uint16_t ia_len, uint16_t tag, const uint8_t expected_iaid[static 4], struct dhcp6_ia *out) { struct dhcp6_option_iter iter; uint16_t t; uint16_t l; const void *v; uint32_t t1; uint32_t t2; struct dhcp6_address_info info; bool have_info = false; if (ia_len < 12) return -EBADMSG; if (memcmp(ia, expected_iaid, 4)) return -EINVAL; t1 = l_get_be32(ia + 4); t2 = l_get_be32(ia + 8); /* * RFC 8415, Section 21.4: * "If a client receives an IA_NA with T1 greater than T2 and both T1 * and T2 are greater than 0, the client discards the IA_NA option and * processes the remainder of the message as though the server had not * included the invalid IA_NA option." */ if (t1 > t2 && t2) return -EINVAL; __dhcp6_option_iter_init(&iter, ia + 12, ia_len - 12); while (_dhcp6_option_iter_next(&iter, &t, &l, &v)) { uint16_t status; switch (t) { case DHCP6_OPTION_STATUS_CODE: if (l < 2) return -EBADMSG; status = l_get_be16(v); if (status != 0) return -EINVAL; break; case DHCP6_OPTION_IA_ADDR: if (tag != DHCP6_OPTION_IA_NA) return -EBADMSG; if (have_info || parse_ia_address(v, l, &info) < 0) continue; have_info = true; break; case DHCP6_OPTION_IA_PREFIX: if (tag != DHCP6_OPTION_IA_PD) return -EBADMSG; if (have_info || parse_ia_prefix(v, l, &info) < 0) continue; have_info = true; break; default: break; } } if (!have_info) return -EINVAL; memcpy(out->iaid, expected_iaid, 4); out->t1 = t1; out->t2 = t2; memcpy(&out->info, &info, sizeof(info)); return 0; } struct l_dhcp6_lease *_dhcp6_lease_parse_options( struct dhcp6_option_iter *iter, const uint8_t expected_iaid[static 4]) { struct l_dhcp6_lease *lease = _dhcp6_lease_new(); uint16_t t; uint16_t l; const void *v; while (_dhcp6_option_iter_next(iter, &t, &l, &v)) { switch (t) { case DHCP6_OPTION_SERVER_ID: lease->server_id = l_memdup(v, l); lease->server_id_len = l; break; case DHCP6_OPTION_PREFERENCE: if (l != 1) goto error; lease->preference = l_get_u8(v); break; case DHCP6_OPTION_IA_NA: if (lease->have_na || parse_ia(v, l, t, expected_iaid, &lease->ia_na) < 0) continue; lease->have_na = true; break; case DHCP6_OPTION_IA_PD: if (lease->have_pd || parse_ia(v, l, t, expected_iaid, &lease->ia_pd) < 0) continue; lease->have_pd = true; break; case L_DHCP6_OPTION_DNS_SERVERS: if (!l || l % sizeof(struct in6_addr)) goto error; lease->dns = l_memdup(v, l); lease->dns_len = l; break; case DHCP6_OPTION_RAPID_COMMIT: if (l != 0) goto error; lease->rapid_commit = true; break; case L_DHCP6_OPTION_DOMAIN_LIST: lease->domain_list = net_domain_list_parse(v, l); if (!lease->domain_list) goto error; break; } } return lease; error: _dhcp6_lease_free(lease); return NULL; } LIB_EXPORT char *l_dhcp6_lease_get_address(const struct l_dhcp6_lease *lease) { if (unlikely(!lease)) return NULL; if (!lease->have_na) return NULL; return get_ip(lease->ia_na.info.addr); } LIB_EXPORT char **l_dhcp6_lease_get_dns(const struct l_dhcp6_lease *lease) { if (unlikely(!lease)) return NULL; if (!lease->dns) return NULL; return convert_ipv6_addresses(lease->dns, lease->dns_len); } LIB_EXPORT char **l_dhcp6_lease_get_domains(const struct l_dhcp6_lease *lease) { if (unlikely(!lease)) return NULL; return l_strv_copy(lease->domain_list); } LIB_EXPORT uint8_t l_dhcp6_lease_get_prefix_length( const struct l_dhcp6_lease *lease) { if (unlikely(!lease)) return 0; if (lease->have_na) return 128; if (lease->have_pd) return lease->ia_pd.info.prefix_len; return 0; } #define PICK_IA() \ struct dhcp6_ia *ia; \ \ if (lease->have_na) \ ia = &lease->ia_na; \ else if (lease->have_pd) \ ia = &lease->ia_pd; \ else \ return 0 \ uint32_t _dhcp6_lease_get_t1(struct l_dhcp6_lease *lease) { PICK_IA(); if (ia->t1) return ia->t1; if (ia->info.valid_lifetime == 0xffffffffu) return ia->info.valid_lifetime; return ia->info.valid_lifetime / 2; } uint32_t _dhcp6_lease_get_t2(struct l_dhcp6_lease *lease) { PICK_IA(); if (ia->t2) return ia->t2; if (ia->info.valid_lifetime == 0xffffffffu) return ia->info.valid_lifetime; return ia->info.valid_lifetime / 10 * 8; } uint32_t _dhcp6_lease_get_valid_lifetime(struct l_dhcp6_lease *lease) { PICK_IA(); return ia->info.valid_lifetime; } uint32_t _dhcp6_lease_get_preferred_lifetime(struct l_dhcp6_lease *lease) { PICK_IA(); return ia->info.preferred_lifetime; }