From c43eea9f2effbb066901a61eafef473558d37b0f Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 28 Jul 2020 07:48:11 +0200 Subject: [PATCH] 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. --- src/libsystemd-network/dhcp6-internal.h | 5 +- src/libsystemd-network/dhcp6-lease-internal.h | 2 + src/libsystemd-network/dhcp6-option.c | 118 ++++++++++++------ src/libsystemd-network/sd-dhcp6-client.c | 7 ++ src/libsystemd-network/sd-dhcp6-lease.c | 39 +++++- src/libsystemd-network/test-dhcp6-client.c | 59 ++++++++- src/systemd/sd-dhcp6-lease.h | 1 + 7 files changed, 186 insertions(+), 45 deletions(-) diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index baf7bb2ef4a..9ce6dcd02c3 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -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, diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index e004f48b4e2..df6c95e0b36 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -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); diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index f0a586bca4b..cfa6bb50086 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -642,61 +642,103 @@ 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; - _cleanup_strv_free_ char **names = NULL; +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; - if (optval[optlen - 1] != '\0') - return -EINVAL; - while (pos < optlen) { - _cleanup_free_ char *ret = NULL; - size_t n = 0, allocated = 0; - bool first = true; - - for (;;) { - const char *label; - uint8_t c; + for (;;) { + const char *label; + uint8_t c; - c = optval[pos++]; + if (optlen == 0) + break; - if (c == 0) - /* End of name */ - break; - if (c > 63) - return -EBADMSG; + c = *optval; + optval++; + optlen--; - /* Literal label */ - label = (const char *)&optval[pos]; - pos += c; - if (pos >= optlen) - return -EMSGSIZE; + if (c == 0) + /* End label */ + break; + if (c > 63) + return -EBADMSG; + if (c > optlen) + return -EMSGSIZE; - if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; + /* Literal label */ + label = (const char *)optval; + optval += c; + optlen -= c; - if (first) - first = false; - else - ret[n++] = '.'; + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; - r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; + if (first) + first = false; + else + ret[n++] = '.'; - n += r; - } + r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; - if (n == 0) - continue; + 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; + + if (optlen <= 1) + return -ENODATA; + if (optval[optlen - 1] != '\0') + return -EINVAL; + + while (optlen > 0) { + _cleanup_free_ char *ret = NULL; + + r = parse_domain(&optval, &optlen, &ret); + if (r < 0) + return r; + if (r == 0) + continue; r = strv_extend(&names, ret); if (r < 0) diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 5cdb82bc6f3..66499f7be01 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -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; diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 4eee10ea896..9aad22124dc 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -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); diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index 7af7d670b59..4fd8f4129bc 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -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; } diff --git a/src/systemd/sd-dhcp6-lease.h b/src/systemd/sd-dhcp6-lease.h index 4301c6db878..240df74af8c 100644 --- a/src/systemd/sd-dhcp6-lease.h +++ b/src/systemd/sd-dhcp6-lease.h @@ -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); -- 2.47.3