From: Stefan Agner Date: Thu, 16 Nov 2017 09:07:07 +0000 (+0100) Subject: sd-dhcp6-client: Implement FQDN Option (#7309) X-Git-Tag: v236~204 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8006aa32ee29538ef1d7978d60d6427a0bf31e80;p=thirdparty%2Fsystemd.git sd-dhcp6-client: Implement FQDN Option (#7309) Implement DHCPv6 option to exchange information about the Fully Qualified Domain Name (FQDN) according to RFC 4704. The RFC 4704 describes two models of operations in section 3, currently only the second model is supported (DHCPv6 server updates both the AAAA and the PTR RRs). The existing DHCP Section Options SendHostname and Hostname are sent as FQDN to the server. According to section 4.2 sending only parts of its FQDN is allowed. Fixes #4682. --- diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 945c3b97214..f64382c3ca0 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -61,6 +61,7 @@ typedef struct DHCP6IA DHCP6IA; int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, size_t optlen, const void *optval); int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia); +int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn); int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen, uint8_t **optvalue); int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index f8056dbc4b7..0d4f404a3e9 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -136,6 +136,33 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) { return 0; } +int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) { + uint8_t buffer[1 + DNS_WIRE_FOMAT_HOSTNAME_MAX]; + int r; + + assert_return(buf && *buf && buflen && fqdn, -EINVAL); + + buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */ + + /* Store domain name after flags field */ + r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1, false); + if (r <= 0) + return r; + + /* + * According to RFC 4704, chapter 4.2 only add terminating zero-length + * label in case a FQDN is provided. Since dns_name_to_wire_format + * always adds terminating zero-length label remove if only a hostname + * is provided. + */ + if (dns_name_is_single_label(fqdn)) + r--; + + r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer); + + return r; +} + static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) { DHCP6Option *option = (DHCP6Option*) *buf; diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h index 2487c470ab7..975d35023fa 100644 --- a/src/libsystemd-network/dhcp6-protocol.h +++ b/src/libsystemd-network/dhcp6-protocol.h @@ -104,3 +104,9 @@ enum { DHCP6_STATUS_USE_MULTICAST = 5, _DHCP6_STATUS_MAX = 6, }; + +enum { + DHCP6_FQDN_FLAG_S = (1 << 0), + DHCP6_FQDN_FLAG_O = (1 << 1), + DHCP6_FQDN_FLAG_N = (1 << 2), +}; diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index d55d0c80cca..d7252524cdc 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -29,7 +29,9 @@ #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" #include "dhcp6-protocol.h" +#include "dns-domain.h" #include "fd-util.h" +#include "hostname-util.h" #include "in-addr-util.h" #include "network-internal.h" #include "random-util.h" @@ -59,6 +61,7 @@ struct sd_dhcp6_client { be16_t *req_opts; size_t req_opts_allocated; size_t req_opts_len; + char *fqdn; sd_event_source *receive_message; usec_t retransmit_time; uint8_t retransmit_count; @@ -231,6 +234,20 @@ int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) { return 0; } +int sd_dhcp6_client_set_fqdn( + sd_dhcp6_client *client, + const char *fqdn) { + + assert_return(client, -EINVAL); + + /* Make sure FQDN qualifies as DNS and as Linux hostname */ + if (fqdn && + !(hostname_is_valid(fqdn, false) && dns_name_is_valid(fqdn) > 0)) + return -EINVAL; + + return free_and_strdup(&client->fqdn, fqdn); +} + int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) { assert_return(client, -EINVAL); assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); @@ -389,6 +406,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { if (r < 0) return r; + if (client->fqdn) { + r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); + if (r < 0) + return r; + } + break; case DHCP6_STATE_REQUEST: @@ -409,6 +432,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { if (r < 0) return r; + if (client->fqdn) { + r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); + if (r < 0) + return r; + } + break; case DHCP6_STATE_REBIND: @@ -418,6 +447,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { if (r < 0) return r; + if (client->fqdn) { + r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); + if (r < 0) + return r; + } + break; case DHCP6_STATE_STOPPED: @@ -1300,6 +1335,7 @@ sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) { sd_dhcp6_client_detach_event(client); free(client->req_opts); + free(client->fqdn); return mfree(client); } diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index bd289fa8021..8949864e29a 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -68,6 +68,12 @@ static int test_client_basic(sd_event *e) { sizeof (mac_addr), ARPHRD_ETHER) >= 0); + assert_se(sd_dhcp6_client_set_fqdn(client, "host") == 1); + assert_se(sd_dhcp6_client_set_fqdn(client, "host.domain") == 1); + assert_se(sd_dhcp6_client_set_fqdn(client, NULL) == 1); + assert_se(sd_dhcp6_client_set_fqdn(client, "~host") == -EINVAL); + assert_se(sd_dhcp6_client_set_fqdn(client, "~host.domain") == -EINVAL); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_CLIENTID) == -EINVAL); assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVERS) == -EEXIST); assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER) == -EEXIST); @@ -202,6 +208,11 @@ static uint8_t msg_reply[173] = { 0x00, 0x00, 0x00, 0x00, 0x01 }; +static uint8_t fqdn_wire[16] = { + 0x04, 'h', 'o', 's', 't', 0x03, 'l', 'a', 'b', + 0x05, 'i', 'n', 't', 'r', 'a', 0x00 +}; + static int test_advertise_option(sd_event *e) { _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; DHCP6Message *advertise = (DHCP6Message *)msg_advertise; @@ -410,7 +421,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, uint16_t optcode; size_t optlen; bool found_clientid = false, found_iana = false, found_serverid = false, - found_elapsed_time = false; + found_elapsed_time = false, found_fqdn = false; int r; struct in6_addr addr; be32_t val; @@ -466,6 +477,15 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, assert_se(optlen == 2); + break; + case SD_DHCP6_OPTION_FQDN: + assert_se(!found_fqdn); + found_fqdn = true; + + assert_se(optlen == 17); + + assert_se(optval[0] == 0x01); + assert_se(!memcmp(optval + 1, fqdn_wire, sizeof(fqdn_wire))); break; } } @@ -511,7 +531,7 @@ static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, uint16_t optcode; size_t optlen; bool found_clientid = false, found_iana = false, - found_elapsed_time = false; + found_elapsed_time = false, found_fqdn = false; int r; assert_se(solicit->type == DHCP6_SOLICIT); @@ -544,6 +564,17 @@ static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, assert_se(optlen == 2); + break; + + case SD_DHCP6_OPTION_FQDN: + assert_se(!found_fqdn); + found_fqdn = true; + + assert_se(optlen == 17); + + assert_se(optval[0] == 0x01); + assert_se(!memcmp(optval + 1, fqdn_wire, sizeof(fqdn_wire))); + break; } } @@ -716,6 +747,7 @@ static int test_client_solicit(sd_event *e) { assert_se(sd_dhcp6_client_set_mac(client, (const uint8_t *) &mac_addr, sizeof (mac_addr), ARPHRD_ETHER) >= 0); + assert_se(sd_dhcp6_client_set_fqdn(client, "host.lab.intra") == 1); assert_se(sd_dhcp6_client_get_information_request(client, &val) >= 0); assert_se(val == false); diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 6ba2d170e79..ee28d7ac39e 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -22,6 +22,7 @@ #include "sd-dhcp6-client.h" +#include "hostname-util.h" #include "network-internal.h" #include "networkd-link.h" #include "networkd-manager.h" @@ -211,6 +212,28 @@ int dhcp6_request_address(Link *link, int ir) { return 0; } +static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) { + _cleanup_free_ char *hostname = NULL; + const char *hn; + int r; + + assert(link); + + if (!link->network->dhcp_send_hostname) + hn = NULL; + else if (link->network->dhcp_hostname) + hn = link->network->dhcp_hostname; + else { + r = gethostname_strict(&hostname); + if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */ + return r; + + hn = hostname; + } + + return sd_dhcp6_client_set_fqdn(client, hn); +} + int dhcp6_configure(Link *link) { sd_dhcp6_client *client = NULL; int r; @@ -247,6 +270,11 @@ int dhcp6_configure(Link *link) { if (r < 0) goto error; + r = dhcp6_set_hostname(client, link); + log_link_warning(link, "dhcp6_set_hostname: %d", r); + if (r < 0) + return r; + r = sd_dhcp6_client_set_ifindex(client, link->ifindex); if (r < 0) goto error; diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index 7819f0d2de5..0749c05c511 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -68,6 +68,8 @@ enum { /* option code 35 is unassigned */ + SD_DHCP6_OPTION_FQDN = 39, /* RFC 4704 */ + SD_DHCP6_OPTION_NTP_SERVER = 56, /* RFC 5908 */ /* option codes 89-142 are unassigned */ @@ -101,6 +103,9 @@ int sd_dhcp6_client_set_duid( int sd_dhcp6_client_set_iaid( sd_dhcp6_client *client, uint32_t iaid); +int sd_dhcp6_client_set_fqdn( + sd_dhcp6_client *client, + const char *fqdn); int sd_dhcp6_client_set_information_request( sd_dhcp6_client *client, int enabled);