From 234d95a6fc75208266049fa2f57641d6f4e5bd2a Mon Sep 17 00:00:00 2001 From: Michael Weiss Date: Mon, 23 Dec 2019 15:44:14 +0100 Subject: [PATCH 1/3] nixos/networking: Add the FQDN and hostname to /etc/hosts This fixes the output of "hostname --fqdn" (previously the domain name was not appended). Additionally it's now possible to use the FQDN. This works by unconditionally adding two entries to /etc/hosts: 127.0.0.1 localhost ::1 localhost These are the first two entries and therefore gethostbyaddr() will always resolve "127.0.0.1" and "::1" back to "localhost" [0]. This works because nscd (or rather the nss-files module) returns the first matching row from /etc/hosts (and ignores the rest). The FQDN and hostname entries are appended later to /etc/hosts, e.g.: 127.0.0.2 nixos-unstable.test.tld nixos-unstable ::1 nixos-unstable.test.tld nixos-unstable Note: We use 127.0.0.2 here to follow nss-myhostname (systemd) as close as possible. This has the advantage that 127.0.0.2 can be resolved back to the FQDN but also the drawback that applications that only listen to 127.0.0.1 (and not additionally ::1) cannot be reached via the FQDN. If you would like this to work you can use the following configuration: ```nix networking.hosts."127.0.0.1" = [ "${config.networking.hostName}.${config.networking.domain}" config.networking.hostName ]; ``` Therefore gethostbyname() resolves "nixos-unstable" to the FQDN (canonical name): "nixos-unstable.test.tld". Advantages over the previous behaviour: - The FQDN will now also be resolved correctly (the entry was missing). - E.g. the command "hostname --fqdn" will now work as expected. Drawbacks: - Overrides entries form the DNS (an issue if e.g. $FQDN should resolve to the public IP address instead of 127.0.0.1) - Note: This was already partly an issue as there's an entry for $HOSTNAME (without the domain part) that resolves to 127.0.1.1 (!= 127.0.0.1). - Unknown (could potentially cause other unexpected issues, but special care was taken). [0]: Some applications do apparently depend on this behaviour (see c578924) and this is typically the expected behaviour. Co-authored-by: Florian Klink --- nixos/doc/manual/release-notes/rl-2009.xml | 16 +++++++++++ nixos/modules/config/networking.nix | 33 ++++++++++++---------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2009.xml b/nixos/doc/manual/release-notes/rl-2009.xml index 3bbb7d71d49..3166f98907c 100644 --- a/nixos/doc/manual/release-notes/rl-2009.xml +++ b/nixos/doc/manual/release-notes/rl-2009.xml @@ -415,6 +415,22 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ]; continue to work through Breezy. + + + In addition to the hostname, the fully qualified domain name (FQDN), + which consists of ${cfg.hostName} and + ${cfg.domain} is now added to + /etc/hosts, to allow local FQDN resolution, as used by the + hostname --fqdn command and other applications that + try to determine the FQDN. These new entries take precedence over entries + from the DNS which could cause regressions in some very specific setups. + Additionally the hostname is now resolved to 127.0.0.2 + instead of 127.0.1.1 to be consistent with what + nss-myhostname (from systemd) returns. + The old behaviour can e.g. be restored by using + networking.hosts = lib.mkForce { "127.0.1.1" = [ config.networking.hostName ]; };. + + diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix index 03944de8249..4cb7d81c997 100644 --- a/nixos/modules/config/networking.nix +++ b/nixos/modules/config/networking.nix @@ -8,9 +8,6 @@ let cfg = config.networking; - localhostMapped4 = cfg.hosts ? "127.0.0.1" && elem "localhost" cfg.hosts."127.0.0.1"; - localhostMapped6 = cfg.hosts ? "::1" && elem "localhost" cfg.hosts."::1"; - localhostMultiple = any (elem "localhost") (attrValues (removeAttrs cfg.hosts [ "127.0.0.1" "::1" ])); in @@ -147,12 +144,6 @@ in config = { assertions = [{ - assertion = localhostMapped4; - message = ''`networking.hosts` doesn't map "127.0.0.1" to "localhost"''; - } { - assertion = !cfg.enableIPv6 || localhostMapped6; - message = ''`networking.hosts` doesn't map "::1" to "localhost"''; - } { assertion = !localhostMultiple; message = '' `networking.hosts` maps "localhost" to something other than "127.0.0.1" @@ -161,22 +152,34 @@ in ''; }]; - networking.hosts = { - "127.0.0.1" = [ "localhost" ]; - } // optionalAttrs (cfg.hostName != "") { - "127.0.1.1" = [ cfg.hostName ]; + # These entries are required for "hostname -f" and to resolve both the + # hostname and FQDN correctly: + networking.hosts = let + hostnames = # Note: The FQDN (canonical hostname) has to come first: + optional (cfg.hostName != "" && cfg.domain != null) "${cfg.hostName}.${cfg.domain}" + ++ optional (cfg.hostName != "") cfg.hostName; # Then the hostname (without the domain) + in { + "127.0.0.2" = hostnames; } // optionalAttrs cfg.enableIPv6 { - "::1" = [ "localhost" ]; + "::1" = hostnames; }; networking.hostFiles = let + # Note: localhostHosts has to appear first in /etc/hosts so that 127.0.0.1 + # resolves back to "localhost" (as some applications assume) instead of + # the FQDN! By default "networking.hosts" also contains entries for the + # FQDN so that e.g. "hostname -f" works correctly. + localhostHosts = pkgs.writeText "localhost-hosts" '' + 127.0.0.1 localhost + ${optionalString cfg.enableIPv6 "::1 localhost"} + ''; stringHosts = let oneToString = set: ip: ip + " " + concatStringsSep " " set.${ip} + "\n"; allToString = set: concatMapStrings (oneToString set) (attrNames set); in pkgs.writeText "string-hosts" (allToString (filterAttrs (_: v: v != []) cfg.hosts)); extraHosts = pkgs.writeText "extra-hosts" cfg.extraHosts; - in mkBefore [ stringHosts extraHosts ]; + in mkBefore [ localhostHosts stringHosts extraHosts ]; environment.etc = { # /etc/services: TCP/UDP port assignments. From 837ec31493bc1daf5fbbbf651199b4cee4d073b7 Mon Sep 17 00:00:00 2001 From: Julian Stecklina Date: Tue, 12 May 2020 23:48:27 +0200 Subject: [PATCH 2/3] nixos/tests/hostname: init (check system's host name) NixOS currently has issues with setting the FQDN of a system in a way where standard tools work. In order to help with experimentation and avoid regressions, add a test that checks that the hostname is reported as the user wanted it to be. Co-authored-by: Michael Weiss --- nixos/tests/all-tests.nix | 1 + nixos/tests/hostname.nix | 66 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 nixos/tests/hostname.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 58120987364..47ca015632c 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -134,6 +134,7 @@ in hitch = handleTest ./hitch {}; hocker-fetchdocker = handleTest ./hocker-fetchdocker {}; home-assistant = handleTest ./home-assistant.nix {}; + hostname = handleTest ./hostname.nix {}; hound = handleTest ./hound.nix {}; hydra = handleTest ./hydra {}; hydra-db-migration = handleTest ./hydra/db-migration.nix {}; diff --git a/nixos/tests/hostname.nix b/nixos/tests/hostname.nix new file mode 100644 index 00000000000..3b87303d73e --- /dev/null +++ b/nixos/tests/hostname.nix @@ -0,0 +1,66 @@ +{ system ? builtins.currentSystem, + config ? {}, + pkgs ? import ../.. { inherit system config; } +}: + +with import ../lib/testing-python.nix { inherit system pkgs; }; +with pkgs.lib; + +let + makeHostNameTest = hostName: domain: + let + fqdn = hostName + (optionalString (domain != null) ".${domain}"); + in + makeTest { + name = "hostname-${fqdn}"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ primeos blitz ]; + }; + + machine = { lib, ... }: { + networking.hostName = hostName; + networking.domain = domain; + + environment.systemPackages = with pkgs; [ + inetutils + ]; + }; + + testScript = '' + start_all() + + machine = ${hostName} + + machine.wait_for_unit("network-online.target") + + # The FQDN, domain name, and hostname detection should work as expected: + assert "${fqdn}" == machine.succeed("hostname --fqdn").strip() + assert "${optionalString (domain != null) domain}" == machine.succeed("dnsdomainname").strip() + assert ( + "${hostName}" + == machine.succeed( + 'hostnamectl status | grep "Static hostname" | cut -d: -f2' + ).strip() + ) + + # 127.0.0.1 and ::1 should resolve back to "localhost": + assert ( + "localhost" == machine.succeed("getent hosts 127.0.0.1 | awk '{print $2}'").strip() + ) + assert "localhost" == machine.succeed("getent hosts ::1 | awk '{print $2}'").strip() + + # 127.0.0.2 should resolve back to the FQDN and hostname: + fqdn_and_host_name = "${optionalString (domain != null) "${hostName}.${domain} "}${hostName}" + assert ( + fqdn_and_host_name + == machine.succeed("getent hosts 127.0.0.2 | awk '{print $2,$3}'").strip() + ) + ''; + }; + +in +{ + noExplicitDomain = makeHostNameTest "ahost" null; + + explicitDomain = makeHostNameTest "ahost" "adomain"; +} From 993baa587c4b82e791686f6ce711bcd4ee8ef933 Mon Sep 17 00:00:00 2001 From: Michael Weiss Date: Mon, 25 May 2020 14:01:26 +0200 Subject: [PATCH 3/3] nixos: Require networking.hostName to be a valid DNS label This also means that the hostname must not contain the domain name part anymore (i.e. must not be a FQDN). See RFC 1035 [0], "man 5 hostname", or the kernel documentation [1]. Note: For legacy reasons we also allow underscores inside of the label but this is not recommended and intentionally left undocumented. [0]: https://tools.ietf.org/html/rfc1035 [1]: https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#domainname-hostname Co-authored-by: zimbatm --- nixos/doc/manual/release-notes/rl-2009.xml | 10 ++++++++++ nixos/modules/tasks/network-interfaces.nix | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2009.xml b/nixos/doc/manual/release-notes/rl-2009.xml index 3166f98907c..e17e8ac24d1 100644 --- a/nixos/doc/manual/release-notes/rl-2009.xml +++ b/nixos/doc/manual/release-notes/rl-2009.xml @@ -431,6 +431,16 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ]; networking.hosts = lib.mkForce { "127.0.1.1" = [ config.networking.hostName ]; };. + + + The hostname (networking.hostName) must now be a valid + DNS label (see RFC 1035) and as such must not contain the domain part. + This means that the hostname must start with a letter, end with a letter + or digit, and have as interior characters only letters, digits, and + hyphen. The maximum length is 63 characters. Additionally it is + recommended to only use lower-case characters. + + diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 44677d417ea..12cff6b038f 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -376,10 +376,20 @@ in networking.hostName = mkOption { default = "nixos"; - type = types.str; + # Only allow hostnames without the domain name part (i.e. no FQDNs, see + # e.g. "man 5 hostname") and require valid DNS labels (recommended + # syntax). Note: We also allow underscores for compatibility/legacy + # reasons (as undocumented feature): + type = types.strMatching + "^[[:alpha:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$"; description = '' - The name of the machine. Leave it empty if you want to obtain - it from a DHCP server (if using DHCP). + The name of the machine. Leave it empty if you want to obtain it from a + DHCP server (if using DHCP). The hostname must be a valid DNS label (see + RFC 1035 section 2.3.1: "Preferred name syntax") and as such must not + contain the domain part. This means that the hostname must start with a + letter, end with a letter or digit, and have as interior characters only + letters, digits, and hyphen. The maximum length is 63 characters. + Additionally it is recommended to only use lower-case characters. ''; };