diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 945c3b9721..f64382c3ca 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 f8056dbc4b..0d4f404a3e 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 2487c470ab..975d35023f 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 d55d0c80cc..d7252524cd 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 bd289fa802..8949864e29 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 6ba2d170e7..ee28d7ac39 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 7819f0d2de..0749c05c51 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);