summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/basic/hostname-util.c2
-rw-r--r--src/libsystemd-network/dhcp-protocol.h10
-rw-r--r--src/libsystemd-network/sd-dhcp-client.c53
-rw-r--r--src/shared/dns-domain.c34
-rw-r--r--src/shared/dns-domain.h2
-rw-r--r--src/test/test-dns-domain.c31
6 files changed, 122 insertions, 10 deletions
diff --git a/src/basic/hostname-util.c b/src/basic/hostname-util.c
index ea0528c6fc..c57a3cbd60 100644
--- a/src/basic/hostname-util.c
+++ b/src/basic/hostname-util.c
@@ -72,7 +72,7 @@ static bool hostname_valid_char(char c) {
* allow_trailing_dot is true and at least two components are present
* in the name. Note that due to the restricted charset and length
* this call is substantially more conservative than
- * dns_domain_is_valid().
+ * dns_name_is_valid().
*/
bool hostname_is_valid(const char *s, bool allow_trailing_dot) {
unsigned n_dots = 0;
diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h
index 88a81d2866..5cf4e9e306 100644
--- a/src/libsystemd-network/dhcp-protocol.h
+++ b/src/libsystemd-network/dhcp-protocol.h
@@ -137,6 +137,7 @@ enum {
DHCP_OPTION_REBINDING_T2_TIME = 59,
DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60,
DHCP_OPTION_CLIENT_IDENTIFIER = 61,
+ DHCP_OPTION_FQDN = 81,
DHCP_OPTION_NEW_POSIX_TIMEZONE = 100,
DHCP_OPTION_NEW_TZDB_TIMEZONE = 101,
DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121,
@@ -144,3 +145,12 @@ enum {
DHCP_OPTION_PRIVATE_LAST = 254,
DHCP_OPTION_END = 255,
};
+
+#define DHCP_MAX_FQDN_LENGTH 255
+
+enum {
+ DHCP_FQDN_FLAG_S = (1 << 0),
+ DHCP_FQDN_FLAG_O = (1 << 1),
+ DHCP_FQDN_FLAG_E = (1 << 2),
+ DHCP_FQDN_FLAG_N = (1 << 3),
+};
diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c
index 137537253a..5ec0e661f7 100644
--- a/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/libsystemd-network/sd-dhcp-client.c
@@ -34,6 +34,8 @@
#include "dhcp-internal.h"
#include "dhcp-lease-internal.h"
#include "dhcp-protocol.h"
+#include "dns-domain.h"
+#include "hostname-util.h"
#include "random-util.h"
#include "string-util.h"
#include "util.h"
@@ -298,6 +300,9 @@ int sd_dhcp_client_set_hostname(sd_dhcp_client *client,
assert_return(client, -EINVAL);
+ if (!hostname_is_valid(hostname, false) && !dns_name_is_valid(hostname))
+ return -EINVAL;
+
if (streq_ptr(client->hostname, hostname))
return 0;
@@ -539,6 +544,24 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret,
return 0;
}
+static int client_append_fqdn_option(DHCPMessage *message, size_t optlen, size_t *optoffset,
+ const char *fqdn) {
+ uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH];
+ int r;
+
+ buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */
+ DHCP_FQDN_FLAG_E; /* Canonical wire format */
+ buffer[1] = 0; /* RCODE1 (deprecated) */
+ buffer[2] = 0; /* RCODE2 (deprecated) */
+
+ r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3);
+ if (r > 0)
+ r = dhcp_option_append(message, optlen, optoffset, 0,
+ DHCP_OPTION_FQDN, 3 + r, buffer);
+
+ return r;
+}
+
static int dhcp_client_send_raw(sd_dhcp_client *client, DHCPPacket *packet,
size_t len) {
dhcp_packet_append_ip_headers(packet, INADDR_ANY, DHCP_PORT_CLIENT,
@@ -576,13 +599,21 @@ static int client_send_discover(sd_dhcp_client *client) {
return r;
}
- /* it is unclear from RFC 2131 if client should send hostname in
- DHCPDISCOVER but dhclient does and so we do as well
- */
if (client->hostname) {
- r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
- DHCP_OPTION_HOST_NAME,
- strlen(client->hostname), client->hostname);
+ /* According to RFC 4702 "clients that send the Client FQDN option in
+ their messages MUST NOT also send the Host Name option". Just send
+ one of the two depending on the hostname type.
+ */
+ if (dns_name_single_label(client->hostname)) {
+ /* it is unclear from RFC 2131 if client should send hostname in
+ DHCPDISCOVER but dhclient does and so we do as well
+ */
+ r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+ DHCP_OPTION_HOST_NAME,
+ strlen(client->hostname), client->hostname);
+ } else
+ r = client_append_fqdn_option(&discover->dhcp, optlen, &optoffset,
+ client->hostname);
if (r < 0)
return r;
}
@@ -688,9 +719,13 @@ static int client_send_request(sd_dhcp_client *client) {
}
if (client->hostname) {
- r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
- DHCP_OPTION_HOST_NAME,
- strlen(client->hostname), client->hostname);
+ if (dns_name_single_label(client->hostname))
+ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+ DHCP_OPTION_HOST_NAME,
+ strlen(client->hostname), client->hostname);
+ else
+ r = client_append_fqdn_option(&request->dhcp, optlen, &optoffset,
+ client->hostname);
if (r < 0)
return r;
}
diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c
index 7af15e0098..423ccca9cc 100644
--- a/src/shared/dns-domain.c
+++ b/src/shared/dns-domain.c
@@ -715,3 +715,37 @@ int dns_name_single_label(const char *name) {
return r == 0 && *name == 0;
}
+
+/* Encode a domain name according to RFC 1035 Section 3.1 */
+int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len) {
+ uint8_t *label_length;
+ uint8_t *out;
+ int r;
+
+ assert_return(buffer, -EINVAL);
+ assert_return(domain, -EINVAL);
+ assert_return(domain[0], -EINVAL);
+
+ out = buffer;
+
+ do {
+ /* reserve a byte for label length */
+ if (len == 0)
+ return -ENOBUFS;
+ len--;
+ label_length = out;
+ out++;
+
+ /* convert and copy a single label */
+ r = dns_label_unescape(&domain, (char *) out, len);
+ if (r < 0)
+ return r;
+
+ /* fill label length, move forward */
+ *label_length = r;
+ out += r;
+ len -= r;
+ } while (r != 0);
+
+ return out - buffer;
+}
diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h
index 1f0d242c18..b214897440 100644
--- a/src/shared/dns-domain.h
+++ b/src/shared/dns-domain.h
@@ -67,3 +67,5 @@ int dns_name_address(const char *p, int *family, union in_addr_union *a);
int dns_name_root(const char *name);
int dns_name_single_label(const char *name);
+
+int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len);
diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c
index d5778748a0..407c7d8ef7 100644
--- a/src/test/test-dns-domain.c
+++ b/src/test/test-dns-domain.c
@@ -52,6 +52,36 @@ static void test_dns_label_unescape(void) {
test_dns_label_unescape_one("foobar.", "foobar", 20, 6);
}
+static void test_dns_name_to_wire_format_one(const char *what, const char *expect, size_t buffer_sz, int ret) {
+ uint8_t buffer[buffer_sz];
+ int r;
+
+ r = dns_name_to_wire_format(what, buffer, buffer_sz);
+ assert_se(r == ret);
+
+ if (r < 0)
+ return;
+
+ assert_se(!memcmp(buffer, expect, r));
+}
+
+static void test_dns_name_to_wire_format(void) {
+ const char out1[] = { 3, 'f', 'o', 'o', 0 };
+ const char out2[] = { 5, 'h', 'a', 'l', 'l', 'o', 3, 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 };
+ const char out3[] = { 4, ' ', 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 };
+
+ test_dns_name_to_wire_format_one("", NULL, 0, -EINVAL);
+
+ test_dns_name_to_wire_format_one("foo", out1, sizeof(out1), sizeof(out1));
+ test_dns_name_to_wire_format_one("foo", out1, sizeof(out1) + 1, sizeof(out1));
+ test_dns_name_to_wire_format_one("foo", out1, sizeof(out1) - 1, -ENOBUFS);
+
+ test_dns_name_to_wire_format_one("hallo.foo.bar", out2, sizeof(out2), sizeof(out2));
+ test_dns_name_to_wire_format_one("hallo.foo..bar", NULL, 32, -EINVAL);
+
+ test_dns_name_to_wire_format_one("\\032foo.bar", out3, sizeof(out3), sizeof(out3));
+}
+
static void test_dns_label_unescape_suffix_one(const char *what, const char *expect1, const char *expect2, size_t buffer_sz, int ret1, int ret2) {
char buffer[buffer_sz];
const char *label;
@@ -300,6 +330,7 @@ int main(int argc, char *argv[]) {
test_dns_name_reverse();
test_dns_name_concat();
test_dns_name_is_valid();
+ test_dns_name_to_wire_format();
return 0;
}