blob: 978176aed66e9dbfcf5f47f123830c01b36dde82 [file] [log] [blame]
/*
*
* 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 <config.h>
#endif
#include <linux/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#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() \
const 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(const 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(const 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;
}
LIB_EXPORT uint32_t l_dhcp6_lease_get_valid_lifetime(
const struct l_dhcp6_lease *lease)
{
if (unlikely(!lease))
return 0;
{
PICK_IA();
return ia->info.valid_lifetime;
}
}
LIB_EXPORT uint32_t l_dhcp6_lease_get_preferred_lifetime(
const struct l_dhcp6_lease *lease)
{
if (unlikely(!lease))
return 0;
{
PICK_IA();
return ia->info.preferred_lifetime;
}
}
/* Get the reception timestamp, i.e. when lifetimes are counted from */
LIB_EXPORT uint64_t l_dhcp6_lease_get_start_time(
const struct l_dhcp6_lease *lease)
{
if (unlikely(!lease))
return 0;
return lease->start_time;
}