From d22bf51e57733292bfa2f4cd278bb0a997166ed3 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 23 Jun 2026 17:54:12 +0000 Subject: [PATCH] util/cloudenv: detect Hetzner Cloud Detect Hetzner via /sys/class/dmi/id/sys_vendor == "Hetzner" and wire up Hetzner's public recursive DNS resolvers (185.12.64.1, 185.12.64.2) for use as a cloud host resolver. Fixes #20217 Change-Id: I24a4c51956adfdd5731f62c937e3c7a4a733ffc7 Signed-off-by: Brad Fitzpatrick --- util/cloudenv/cloudenv.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/util/cloudenv/cloudenv.go b/util/cloudenv/cloudenv.go index aee4bac72..ace70f225 100644 --- a/util/cloudenv/cloudenv.go +++ b/util/cloudenv/cloudenv.go @@ -47,6 +47,7 @@ Azure = Cloud("azure") // Microsoft Azure GCP = Cloud("gcp") // Google Cloud DigitalOcean = Cloud("digitalocean") // DigitalOcean + Hetzner = Cloud("hetzner") // Hetzner Cloud ) // ResolverIP returns the cloud host's recursive DNS server or the @@ -64,6 +65,8 @@ func (c Cloud) ResolverIP() string { return AzureResolverIP case DigitalOcean: return getDigitalOceanResolver() + case Hetzner: + return getHetznerResolver() } return "" } @@ -82,6 +85,21 @@ func getDigitalOceanResolver() string { }) } +var ( + // https://docs.hetzner.com/robot/dedicated-server/general-information/recursive-name-servers/ + // IPv6 resolvers also exist: 2a01:4ff:ff00::add:1, 2a01:4ff:ff00::add:2. + hetznerResolvers = []string{"185.12.64.1", "185.12.64.2"} + hetznerResolver lazy.SyncValue[string] +) + +func getHetznerResolver() string { + // Randomly select one of the available resolvers so we don't overload + // one of them by sending all traffic there. + return hetznerResolver.Get(func() string { + return hetznerResolvers[rand.IntN(len(hetznerResolvers))] + }) +} + // HasInternalTLD reports whether c is a cloud environment // whose ResolverIP serves *.internal records. func (c Cloud) HasInternalTLD() bool { @@ -128,6 +146,9 @@ func getCloud() Cloud { if sysVendor == "DigitalOcean" { return DigitalOcean } + if sysVendor == "Hetzner" { + return Hetzner + } // TODO(andrew): "Vultr" is also valid if we need it prod := readFileTrimmed("/sys/class/dmi/id/product_name")