#include "dhcp-client-id-internal.h"
#include "dhcp-message.h"
#include "dhcp-protocol.h"
+#include "dns-domain.h"
+#include "errno-util.h"
#include "ether-addr-util.h"
+#include "hostname-util.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
#include "ip-util.h"
return dhcp_message_append_option(message, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, len, buf);
}
+static int dhcp_message_append_option_fqdn(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *fqdn) {
+ int r;
+
+ assert(message);
+ assert(fqdn);
+
+ /* FIXME: Allow long fqdn, as now we support long option. */
+ uint8_t buf[3 + DHCP_MAX_FQDN_LENGTH];
+
+ /* RFC 4702 section 2.1
+ * The "E" bit indicates the encoding of the Domain Name field. 1 indicates canonical wire format,
+ * without compression. This encoding SHOULD be used by clients and MUST be supported by servers.
+ * A server MUST use the same encoding as that used by the client. A server that does not support
+ * the deprecated ASCII encoding MUST ignore Client FQDN options that use that encoding.
+ *
+ * Here, we unconditionally set the 'E' flag. Hence, sd_dhcp_server must ignore the option if a
+ * client does not set the 'E' flag in the request. */
+ buf[0] = flags | DHCP_FQDN_FLAG_E;
+
+ /* RFC 4702 section 2.2
+ * The two 1-octet RCODE1 and RCODE2 fields are deprecated. A client SHOULD set these to 0 when
+ * sending the option and SHOULD ignore them on receipt. A server SHOULD set these to 255 when
+ * sending the option and MUST ignore them on receipt. */
+ buf[1] = is_client ? 0 : 255;
+ buf[2] = is_client ? 0 : 255;
+
+ r = dns_name_to_wire_format(fqdn, buf + 3, sizeof(buf) - 3, false);
+ if (r <= 0)
+ return r;
+
+ return dhcp_message_append_option(message, SD_DHCP_OPTION_FQDN, 3 + r, buf);
+}
+
+int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname) {
+ assert(message);
+
+ /* Hostname (12) or FQDN (81)
+ *
+ * RFC 4702 section 3.1
+ * clients that send the Client FQDN option in their messages MUST NOT also send the Host Name option. */
+
+ if (isempty(hostname))
+ return 0;
+
+ if (dhcp_message_has_option(message, SD_DHCP_OPTION_HOST_NAME))
+ return -EEXIST;
+
+ if (dhcp_message_has_option(message, SD_DHCP_OPTION_FQDN))
+ return -EEXIST;
+
+ if (dns_name_is_single_label(hostname))
+ return dhcp_message_append_option_string(message, SD_DHCP_OPTION_HOST_NAME, hostname);
+
+ return dhcp_message_append_option_fqdn(message, flags, is_client, hostname);
+}
+
int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) {
int r;
return 0;
}
+static int normalize_dns_name(const char *name, char **ret) {
+ int r;
+
+ assert(name);
+
+ _cleanup_free_ char *normalized = NULL;
+ r = dns_name_normalize(name, /* flags= */ 0, &normalized);
+ if (r < 0)
+ return r;
+
+ if (is_localhost(normalized))
+ return -EINVAL;
+
+ if (dns_name_is_root(normalized))
+ return -EINVAL;
+
+ if (ret)
+ *ret = TAKE_PTR(normalized);
+ return 0;
+}
+
+int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn) {
+ int r;
+
+ assert(message);
+
+ _cleanup_(iovec_done) struct iovec iov = {};
+ r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_FQDN, &iov);
+ if (r < 0)
+ return r;
+
+ if (iov.iov_len <= 3)
+ return -EBADMSG;
+
+ uint8_t flags = *(uint8_t*) iov.iov_base;
+ if (!FLAGS_SET(flags, DHCP_FQDN_FLAG_E))
+ return -EOPNOTSUPP;
+
+ struct iovec i;
+ iovec_shift(&iov, 3, &i);
+
+ _cleanup_free_ char *name = NULL;
+ const uint8_t *p = i.iov_base;
+ r = dns_name_from_wire_format(&p, &i.iov_len, &name);
+ if (r < 0)
+ return r;
+ if (i.iov_len > 0) /* trailing garbage? */
+ return -EBADMSG;
+
+ if (isempty(name))
+ return -ENODATA;
+
+ if (!string_is_safe(name, /* flags= */ 0))
+ return -EBADMSG;
+
+ _cleanup_free_ char *normalized = NULL;
+ r = normalize_dns_name(name, &normalized);
+ if (r < 0)
+ return r;
+
+ if (ret_flags)
+ *ret_flags = flags;
+ if (ret_fqdn)
+ *ret_fqdn = TAKE_PTR(normalized);
+ return 0;
+}
+
+int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret) {
+ int r;
+
+ assert(message);
+
+ /* Mainly for Host Name or Domain Name options. */
+
+ _cleanup_free_ char *name = NULL;
+ r = dhcp_message_get_option_string(message, code, &name);
+ if (r < 0)
+ return r;
+
+ _cleanup_free_ char *normalized = NULL;
+ r = normalize_dns_name(name, &normalized);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = TAKE_PTR(normalized);
+ return 0;
+}
+
+int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret) {
+ int r;
+
+ assert(message);
+
+ /* FQDN option always takes precedence. */
+ r = dhcp_message_get_option_fqdn(message, /* ret_flags= */ NULL, ret);
+ if (ERRNO_IS_NEG_RESOURCE(r))
+ return r;
+ if (r >= 0)
+ return 0;
+
+ /* Then, fall back to Host Name option. */
+ return dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_HOST_NAME, ret);
+}
+
static int dhcp_message_verify_header(
const struct iovec *iov,
uint8_t op,
int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data);
int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id);
int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl);
+int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname);
int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret);
int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret);
int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret);
int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret);
int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set **ret);
+int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn);
+int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret);
+int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret);
int dhcp_message_parse(
const struct iovec *iov,
ASSERT_TRUE(set_equal(set, expected));
}
+static void verify_hostname(sd_dhcp_message *m, const char *expected) {
+ _cleanup_free_ char *s = NULL;
+ ASSERT_OK(dhcp_message_get_option_hostname(m, &s));
+ ASSERT_STREQ(s, expected);
+}
+
TEST(dhcp_message) {
_cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL;
for (uint8_t i = SD_DHCP_OPTION_PRIVATE_BASE; i <= SD_DHCP_OPTION_PRIVATE_LAST; i++)
ASSERT_OK(set_ensure_put(&prl, /* hash_ops= */ NULL, UINT_TO_PTR(i)));
+ const char *hostname = "test-node.example.com";
const char *vendor_class = "hogehoge";
char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo");
ASSERT_OK(dhcp_message_append_option_parameter_request_list(m, prl));
verify_prl(m, prl);
+ /* hostname */
+ ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, "hogehoge"));
+ ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST);
+ dhcp_message_remove_option(m, SD_DHCP_OPTION_FQDN);
+ ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST);
+ dhcp_message_remove_option(m, SD_DHCP_OPTION_HOST_NAME);
+ ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, "hogehoge.example.com"));
+ ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST);
+ dhcp_message_remove_option(m, SD_DHCP_OPTION_HOST_NAME);
+ ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST);
+ dhcp_message_remove_option(m, SD_DHCP_OPTION_FQDN);
+ ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname));
+ verify_hostname(m, hostname);
+
/* build and parse */
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
ASSERT_OK(dhcp_message_build(m, &iovw));
verify_string(m2, vendor_class);
verify_client_id(m2, &id);
verify_prl(m2, prl);
+ verify_hostname(m2, hostname);
/* build again, and verify the packet */
_cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {};