]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dns-domain: accept encoded domain names without terminating zero label
authorBeniamino Galvani <bgalvani@redhat.com>
Wed, 29 Jan 2025 10:51:18 +0000 (11:51 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Thu, 30 Jan 2025 16:18:49 +0000 (16:18 +0000)
Commit 1be9b30a3b17 ("dhcp6: use dns_name_from_wire_format") introduced a
stricter validation of domains received via DHCPv6, by using function
dns_name_from_wire_format() which rejects the domain when it is missing the
terminating zero label. According to RFC 4704 § 4.2, DHCPv6 servers should
always add the zero label:

   To send a fully qualified domain name, the Domain Name field is set
   to the DNS-encoded domain name including the terminating zero-length
   label.  To send a partial name, the Domain Name field is set to the
   DNS-encoded domain name without the terminating zero-length label.

   [...]

   Servers SHOULD send the complete fully qualified domain name in
   Client FQDN options.

In practice, there is at least on common DHCPv6 server implementation (dnsmasq)
that sends the FQDN option without the ending zero-length label; after
upgrading to the new systemd, the client cannot parse the option and therefore
the machine doesn't get the hostname provided by dnsmasq.

This commit restores the old behavior that considers a domain valid even when
it's missing the terminating zero label.

Here's a quick reproducer:

--8<--

ip link add veth0 type veth peer name veth1
ip netns add ns1
ip link set veth1 netns ns1
ip link set veth0 address 00:11:22:33:44:55
ip link set veth0 up
ip -n ns1 link set veth1 up
ip -n ns1 address add dev veth1 fd01::1/64

ip netns exec ns1 dnsmasq \
   --pid-file=/tmp/dnsmasq.pid --no-hosts \
   --bind-interfaces --interface veth1 --except-interface lo \
   --dhcp-range=fd01::100,fd01::200 --enable-ra \
   --dhcp-host 00:11:22:33:44:55,foobar &

cat <<EOF > /etc/systemd/network/veth0.network
[Match]
Name=veth0

[Network]
DHCP=ipv6
EOF

networkctl reload
networkctl up veth0
sleep 5
hostname

--8<--

Without this change, systemd-networkd prints the following message and doesn't
set the hostname from DHCP:

  veth0: DHCPv6 client: Failed to parse FQDN option, ignoring: Bad message

src/libsystemd-network/test-dhcp6-client.c
src/shared/dns-domain.c
src/test/test-dns-domain.c

index 882c04afbb354560b05bd18ac8b4612925d75f1d..195aa145949bee97576ed28572c39c9a62ec3195 100644 (file)
@@ -165,7 +165,9 @@ TEST(parse_domain) {
         domain = mfree(domain);
 
         data = (uint8_t []) { 4, 't', 'e', 's', 't' };
-        assert_se(dhcp6_option_parse_domainname(data, 5, &domain) < 0);
+        ASSERT_OK(dhcp6_option_parse_domainname(data, 5, &domain));
+        ASSERT_STREQ(domain, "test");
+        domain = mfree(domain);
 
         data = (uint8_t []) { 0 };
         assert_se(dhcp6_option_parse_domainname(data, 1, &domain) < 0);
index e91284177c50469a56b9dda87667454f16d825cc..9f72b38c8ce66c445479fc0cf42af3c80ac07186 100644 (file)
@@ -924,9 +924,12 @@ int dns_name_from_wire_format(const uint8_t **data, size_t *len, char **ret) {
                 const char *label;
                 uint8_t c;
 
-                /* Unterminated name */
+                /* RFC 4704 § 4: fully qualified domain names include the terminating
+                 * zero-length label, partial names don't. According to the RFC, DHCPv6
+                 * servers should always send the fully qualified name, but that's not
+                 * true in practice. Also accept partial names. */
                 if (optlen == 0)
-                        return -EBADMSG;
+                        break;
 
                 /* RFC 1035 § 3.1 total length of encoded name is limited to 255 octets */
                 if (*len - optlen > 255)
index 053a93663c49997a02d128652243084847d75707..302fcfb7fbe6c534ee524ce351ff353cada3585b 100644 (file)
@@ -197,7 +197,7 @@ TEST(dns_name_from_wire_format) {
         test_dns_name_from_wire_format_one("", in0, sizeof(in0), strlen(""));
 
         test_dns_name_from_wire_format_one("foo", in1, sizeof(in1), strlen("foo"));
-        test_dns_name_from_wire_format_one(NULL, in1, sizeof(in1) - 1, -EBADMSG);
+        test_dns_name_from_wire_format_one("foo", in1, sizeof(in1) - 1, strlen("foo"));
 
         test_dns_name_from_wire_format_one("hallo.foo.bar", in2, sizeof(in2), strlen("hallo.foo.bar"));
         test_dns_name_from_wire_format_one("hallo.foo", in2_1, sizeof(in2_1), strlen("hallo.foo"));