]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dhcp6: parse the FQDN option 16595/head
authorBeniamino Galvani <bgalvani@redhat.com>
Tue, 28 Jul 2020 05:48:11 +0000 (07:48 +0200)
committerBeniamino Galvani <bgalvani@redhat.com>
Mon, 3 Aug 2020 07:36:18 +0000 (09:36 +0200)
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
src/libsystemd-network/dhcp6-lease-internal.h
src/libsystemd-network/dhcp6-option.c
src/libsystemd-network/sd-dhcp6-client.c
src/libsystemd-network/sd-dhcp6-lease.c
src/libsystemd-network/test-dhcp6-client.c
src/systemd/sd-dhcp6-lease.h

index baf7bb2ef4ad79714fd01f624fdf486b1dc40f1b..9ce6dcd02c3b5611febadfaba091208cd6557f60 100644 (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,
index e004f48b4e24f441bd5eef669a5d9cfe45157d90..df6c95e0b360c0dac6e3ef86ade805c0e4624c76 100644 (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);
index f0a586bca4bf7cc7e32f23d56e2f867619cfc4d2..cfa6bb50086aa7e859468ddf59aa5d933f9cabb8 100644 (file)
@@ -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)
index 5cdb82bc6f30298ec3b3fba0df6f70a04407ee09..66499f7be01feee7d76c959951007ee4ab9218d7 100644 (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;
index 4eee10ea896b7264236181e0f0e2e185001b276f..9aad22124dce6cc9ddb51e36fe040d07e5ed73d3 100644 (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);
 
index 7af7d670b5917ca2879d52c17855839f733391ae..4fd8f4129bc51e459151214e33939415918b0493 100644 (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;
 }
index 4301c6db878b4e5e556507a05f5d76124145e371..240df74af8c520cf707f9bde280ddfcc17d8129d 100644 (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);