dhcp6: parse the FQDN option

Parse option 39 (Client Fully Qualified Domain Name, RFC 4704) from the DHCP
reply, which specifies the FQDN assigned by the server to the client.
This commit is contained in:
Beniamino Galvani 2020-07-28 07:48:11 +02:00
parent af710b535b
commit c43eea9f2e
7 changed files with 192 additions and 51 deletions

View file

@ -109,8 +109,9 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_stat
int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
struct in6_addr **addrs, size_t count,
size_t *allocated);
int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen,
char ***str_arr);
int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen,
char ***str_arr);
int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str);
int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address);
int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,

View file

@ -35,6 +35,7 @@ struct sd_dhcp6_lease {
size_t ntp_allocated;
char **ntp_fqdn;
size_t ntp_fqdn_count;
char *fqdn;
};
int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire);
@ -57,5 +58,6 @@ int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval,
int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen);
int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval,
size_t optlen) ;
int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
int dhcp6_lease_new(sd_dhcp6_lease **ret);

View file

@ -642,8 +642,87 @@ int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
return count;
}
int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
size_t pos = 0, idx = 0;
static int parse_domain(const uint8_t **data, uint16_t *len, char **out_domain) {
_cleanup_free_ char *ret = NULL;
size_t n = 0, allocated = 0;
const uint8_t *optval = *data;
uint16_t optlen = *len;
bool first = true;
int r;
if (optlen <= 1)
return -ENODATA;
for (;;) {
const char *label;
uint8_t c;
if (optlen == 0)
break;
c = *optval;
optval++;
optlen--;
if (c == 0)
/* End label */
break;
if (c > 63)
return -EBADMSG;
if (c > optlen)
return -EMSGSIZE;
/* Literal label */
label = (const char *)optval;
optval += c;
optlen -= c;
if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
return -ENOMEM;
if (first)
first = false;
else
ret[n++] = '.';
r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
if (r < 0)
return r;
n += r;
}
if (n) {
if (!GREEDY_REALLOC(ret, allocated, n + 1))
return -ENOMEM;
ret[n] = 0;
}
*out_domain = TAKE_PTR(ret);
*data = optval;
*len = optlen;
return n;
}
int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str) {
_cleanup_free_ char *domain = NULL;
int r;
r = parse_domain(&optval, &optlen, &domain);
if (r < 0)
return r;
if (r == 0)
return -ENODATA;
if (optlen != 0)
return -EINVAL;
*str = TAKE_PTR(domain);
return 0;
}
int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
size_t idx = 0;
_cleanup_strv_free_ char **names = NULL;
int r;
@ -652,52 +731,15 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char *
if (optval[optlen - 1] != '\0')
return -EINVAL;
while (pos < optlen) {
while (optlen > 0) {
_cleanup_free_ char *ret = NULL;
size_t n = 0, allocated = 0;
bool first = true;
for (;;) {
const char *label;
uint8_t c;
c = optval[pos++];
if (c == 0)
/* End of name */
break;
if (c > 63)
return -EBADMSG;
/* Literal label */
label = (const char *)&optval[pos];
pos += c;
if (pos >= optlen)
return -EMSGSIZE;
if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
return -ENOMEM;
if (first)
first = false;
else
ret[n++] = '.';
r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
if (r < 0)
return r;
n += r;
}
if (n == 0)
r = parse_domain(&optval, &optlen, &ret);
if (r < 0)
return r;
if (r == 0)
continue;
if (!GREEDY_REALLOC(ret, allocated, n + 1))
return -ENOMEM;
ret[n] = 0;
r = strv_extend(&names, ret);
if (r < 0)
return r;

View file

@ -1282,6 +1282,13 @@ static int client_parse_message(
break;
case SD_DHCP6_OPTION_FQDN:
r = dhcp6_lease_set_fqdn(lease, optval, optlen);
if (r < 0)
return r;
break;
case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
if (optlen != 4)
return -EINVAL;

View file

@ -236,7 +236,7 @@ int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval,
if (!optlen)
return 0;
r = dhcp6_option_parse_domainname(optval, optlen, &domains);
r = dhcp6_option_parse_domainname_list(optval, optlen, &domains);
if (r < 0)
return 0;
@ -294,8 +294,8 @@ int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
break;
case DHCP6_NTP_SUBOPTION_SRV_FQDN:
r = dhcp6_option_parse_domainname(subval, sublen,
&servers);
r = dhcp6_option_parse_domainname_list(subval, sublen,
&servers);
if (r < 0)
return 0;
@ -365,6 +365,38 @@ int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn) {
return -ENOENT;
}
int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval,
size_t optlen) {
int r;
char *fqdn;
assert_return(lease, -EINVAL);
assert_return(optval, -EINVAL);
if (optlen < 2)
return -ENODATA;
/* Ignore the flags field, it doesn't carry any useful
information for clients. */
r = dhcp6_option_parse_domainname(optval + 1, optlen - 1, &fqdn);
if (r < 0)
return r;
return free_and_replace(lease->fqdn, fqdn);
}
int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn) {
assert_return(lease, -EINVAL);
assert_return(fqdn, -EINVAL);
if (lease->fqdn) {
*fqdn = lease->fqdn;
return 0;
}
return -ENOENT;
}
static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
assert(lease);
@ -373,6 +405,7 @@ static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
dhcp6_lease_free_ia(&lease->pd);
free(lease->dns);
free(lease->fqdn);
lease->domains = strv_free(lease->domains);

View file

@ -20,6 +20,8 @@
#include "macro.h"
#include "memory-util.h"
#include "socket-util.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
#include "time-util.h"
#include "virt.h"
@ -106,6 +108,52 @@ static int test_client_basic(sd_event *e) {
return 0;
}
static int test_parse_domain(sd_event *e) {
uint8_t *data;
char *domain;
char **list;
int r;
log_debug("/* %s */", __func__);
data = (uint8_t []) { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0 };
r = dhcp6_option_parse_domainname(data, 13, &domain);
assert_se(r == 0);
assert_se(domain);
assert_se(streq(domain, "example.com"));
free(domain);
data = (uint8_t []) { 4, 't', 'e', 's', 't' };
r = dhcp6_option_parse_domainname(data, 5, &domain);
assert_se(r == 0);
assert_se(domain);
assert_se(streq(domain, "test"));
free(domain);
data = (uint8_t []) { 0 };
r = dhcp6_option_parse_domainname(data, 1, &domain);
assert_se(r < 0);
data = (uint8_t []) { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0,
6, 'f', 'o', 'o', 'b', 'a', 'r', 0 };
r = dhcp6_option_parse_domainname_list(data, 21, &list);
assert_se(r == 2);
assert_se(list);
assert_se(streq(list[0], "example.com"));
assert_se(streq(list[1], "foobar"));
strv_free(list);
data = (uint8_t []) { 1, 'a', 0, 20, 'b', 'c' };
r = dhcp6_option_parse_domainname_list(data, 6, &list);
assert_se(r < 0);
data = (uint8_t []) { 0 , 0 };
r = dhcp6_option_parse_domainname_list(data, 2, &list);
assert_se(r < 0);
return 0;
}
static int test_option(sd_event *e) {
uint8_t packet[] = {
'F', 'O', 'O',
@ -330,7 +378,7 @@ static uint8_t msg_advertise[198] = {
0x53, 0x00, 0x07, 0x00, 0x01, 0x00
};
static uint8_t msg_reply[173] = {
static uint8_t msg_reply[191] = {
0x07, 0xf7, 0x4e, 0x57, 0x00, 0x02, 0x00, 0x0e,
0x00, 0x01, 0x00, 0x01, 0x19, 0x40, 0x5c, 0x53,
0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, 0x00, 0x01,
@ -352,7 +400,9 @@ static uint8_t msg_reply[173] = {
0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, 0x72, 0x61,
0x00, 0x00, 0x1f, 0x00, 0x10, 0x20, 0x01, 0x0d,
0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x27, 0x00,
0x0e, 0x01, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x05, 0x69, 0x6e, 0x74, 0x72, 0x61
};
static uint8_t fqdn_wire[16] = {
@ -747,6 +797,7 @@ static void test_client_information_cb(sd_dhcp6_client *client, int event,
const struct in6_addr *addrs;
struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } };
char **domains;
const char *fqdn;
log_debug("/* %s */", __func__);
@ -759,6 +810,9 @@ static void test_client_information_cb(sd_dhcp6_client *client, int event,
assert_se(!strcmp("lab.intra", domains[0]));
assert_se(domains[1] == NULL);
assert_se(sd_dhcp6_lease_get_fqdn(lease, &fqdn) >= 0);
assert_se(streq(fqdn, "client.intra"));
assert_se(sd_dhcp6_lease_get_dns(lease, &addrs) == 1);
assert_se(!memcmp(addrs, &msg_advertise[124], 16));
@ -945,6 +999,7 @@ int main(int argc, char *argv[]) {
test_option_status(e);
test_advertise_option(e);
test_client_solicit(e);
test_parse_domain(e);
return 0;
}

View file

@ -43,6 +43,7 @@ int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs)
int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains);
int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **addrs);
int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn);
int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn);
sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease);
sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease);