From c039df3457a8d9c2b2bcfd6406ee0f9a414ee53e Mon Sep 17 00:00:00 2001 From: Dan Fuhry Date: Fri, 25 Oct 2024 11:34:43 -0400 Subject: [PATCH] hostname: support /etc/hosts, /etc/resolv.conf; add container detection Needed for Kubernetes compatibility. Kubernetes doesn't publish DNS records for pods. So the runtime can no longer assume that A/AAAA records exist for the host. As a first step we need to be able to detect the hostname and kubernetes domain name. K8s is pretty good about populating `/etc/hosts` and `/etc/resolv.conf`, so we parse those when k8s is detected. --- go.mod | 16 +++--- go.sum | 33 ++++++----- net/dns/dns_cache.go | 62 +++++++++++++++++--- net/dns/hosts.go | 91 ++++++++++++++++++++++++++++++ utils/hostname/hostname_common.go | 25 ++++++++ utils/hostname/hostname_generic.go | 4 ++ utils/hostname/hostname_linux.go | 70 +++++++++++++++++++++++ 7 files changed, 267 insertions(+), 34 deletions(-) create mode 100644 net/dns/hosts.go create mode 100644 utils/hostname/hostname_common.go diff --git a/go.mod b/go.mod index 9e2df02..255a7fb 100644 --- a/go.mod +++ b/go.mod @@ -7,18 +7,18 @@ require ( github.com/google/go-attestation v0.4.3 github.com/google/go-tpm v0.3.3 // indirect github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/sys v0.20.0 + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/sys v0.22.0 ) require ( github.com/ThalesIgnite/crypto11 v1.2.5 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/distribution/distribution/v3 v3.0.0-20230621170613-87b280718d38 - github.com/eclipse/paho.mqtt.golang v1.4.2 + github.com/eclipse/paho.mqtt.golang v1.5.0 github.com/go-kit/log v0.2.1 github.com/godbus/dbus/v5 v5.1.0 - github.com/gorilla/websocket v1.5.0 + github.com/gorilla/websocket v1.5.3 github.com/hashicorp/golang-lru v0.5.4 github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100 github.com/mdlayher/apcupsd v0.0.0-20230802135538-48f5030bcd58 @@ -38,7 +38,7 @@ require ( go.fuhry.dev/grpc-quic v0.1.2 golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d golang.org/x/sync v0.7.0 - golang.org/x/term v0.20.0 + golang.org/x/term v0.22.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -98,7 +98,7 @@ require ( go.uber.org/mock v0.3.0 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/tools v0.21.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect @@ -119,8 +119,8 @@ require ( go.uber.org/atomic v1.11.0 go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.21.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/text v0.15.0 + golang.org/x/net v0.27.0 // indirect + golang.org/x/text v0.16.0 google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/grpc v1.59.0 google.golang.org/protobuf v1.34.1 diff --git a/go.sum b/go.sum index d14f241..58c5942 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,8 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4= -github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA= +github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o= +github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -278,8 +278,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= @@ -617,8 +617,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -684,7 +684,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -694,8 +693,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -766,19 +765,19 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -834,8 +833,8 @@ golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= -golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/net/dns/dns_cache.go b/net/dns/dns_cache.go index 4700e5b..6f63f2a 100644 --- a/net/dns/dns_cache.go +++ b/net/dns/dns_cache.go @@ -12,6 +12,7 @@ import ( "github.com/miekg/dns" "go.fuhry.dev/runtime/utils/fsutil" + "go.fuhry.dev/runtime/utils/hostname" "go.fuhry.dev/runtime/utils/log" ) @@ -38,7 +39,7 @@ var localIPv6 = net.IPNet{ Mask: net.CIDRMask(64, 128), } -func ResolveDualStack(hostname string) (string, string, error) { +func ResolveDualStack(name string) (string, string, error) { var msg *dns.Msg var err error @@ -50,20 +51,30 @@ func ResolveDualStack(hostname string) (string, string, error) { } }) - hostname = dns.Fqdn(hostname) + name = dns.Fqdn(name) - if entry, ok := dnsCache.Get(hostname); ok { + if entry, ok := dnsCache.Get(name); ok { if msg, ok = entry.(*dns.Msg); !ok { return "", "", fmt.Errorf("lru cache is corrupt: expected entry to be *dns.Msg, got %T", entry) } - log.V(3).Debugf("cache hit for hostname %q: %+v", hostname, entry) + log.V(3).Debugf("cache hit for hostname %q: %+v", name, entry) } else { - msg, err = doDualStackQuery(hostname) - if err != nil { - return "", "", err + if name == hostname.Fqdn()+"." { + log.V(3).Debugf("requested name %q matches our own FQDN, checking /etc/hosts", name) + if nonlocal := queryHostsForNonlocalIPs(); nonlocal != nil { + msg = &dns.Msg{ + Answer: nonlocal, + } + } + } + if msg == nil { + msg, err = doDualStackQuery(name) + if err != nil { + return "", "", err + } } - dnsCache.Add(hostname, msg) + dnsCache.Add(name, msg) } var ( @@ -87,7 +98,7 @@ func ResolveDualStack(hostname string) (string, string, error) { if ip4 == "" && ip6 == "" { return "", "", fmt.Errorf( "did not receive any valid, non-loopback/local-scope answers for %q", - hostname, + name, ) } @@ -193,3 +204,36 @@ func newEDNSCookie() dns.RR { }, } } + +func queryHostsForNonlocalIPs() []dns.RR { + ips, _ := QueryHosts(hostname.Hostname()) + ipsLong, _ := QueryHosts(hostname.Fqdn()) + ips = append(ips, ipsLong...) + var nonlocal []dns.RR + + if len(ips) > 0 { + for _, ip := range ips { + if ip4 := ip.To4(); ip4 != nil { + if !loopbackIPv4.Contains(ip4) && !autoPrivateIPv4.Contains(ip4) { + log.V(3).Debugf("found IPv4 in /etc/hosts: %s", ip4) + nonlocal = append(nonlocal, &dns.A{ + A: ip4, + }) + } else { + log.V(3).Debugf("rejecting loopback or auto-private IPv4 IP in /etc/hosts: %s", ip4) + } + } else { + if !ip.IsLoopback() && !localIPv6.Contains(ip) { + log.V(3).Debugf("found IPv6 in /etc/hosts: %s", ip) + nonlocal = append(nonlocal, &dns.AAAA{ + AAAA: ip, + }) + } else { + log.V(3).Debugf("rejecting loopback or link-local IPv6 IP in /etc/hosts: %s", ip) + } + } + } + } + + return nonlocal +} diff --git a/net/dns/hosts.go b/net/dns/hosts.go new file mode 100644 index 0000000..99bc6ca --- /dev/null +++ b/net/dns/hosts.go @@ -0,0 +1,91 @@ +package dns + +import ( + "fmt" + "net" + "os" + "regexp" + "runtime" + "strings" + "sync" +) + +type HostsEntry struct { + IP net.IP + HostNames []string +} + +var spaceExp = regexp.MustCompile(`\s+`) +var hashCommentExp = regexp.MustCompile(`#.*$`) + +var hosts []*HostsEntry +var parseHostsOnce sync.Once + +func ParseHosts() (_ []*HostsEntry, err error) { + parseHostsOnce.Do(func() { + hosts, err = parseHosts() + }) + if err != nil { + parseHostsOnce = sync.Once{} + return nil, err + } + return hosts, nil +} + +func parseHosts() ([]*HostsEntry, error) { + hostsPath := "/etc/hosts" + if runtime.GOOS == "win32" { + hostsPath = fmt.Sprintf(`%s\system32\drivers\etc\hosts`, os.Getenv("SystemRoot")) + } + + contents, err := os.ReadFile(hostsPath) + if err != nil { + return nil, err + } + + lines := strings.Split(strings.ReplaceAll(string(contents), "\r\n", "\n"), "\n") + + var entries []*HostsEntry + for _, line := range lines { + line = hashCommentExp.ReplaceAllLiteralString(line, "") + words := spaceExp.Split(line, -1) + if len(words) < 2 { + continue + } + + ip := net.ParseIP(words[0]) + if ip == nil { + continue + } + + entry := &HostsEntry{ + IP: ip, + HostNames: words[1:], + } + + entries = append(entries, entry) + } + + return entries, nil +} + +func QueryHosts(name string) ([]net.IP, error) { + hosts, err := ParseHosts() + if err != nil { + return nil, err + } + + name = strings.TrimSuffix(strings.ToLower(name), ".") + var ips []net.IP + + for _, entry := range hosts { + for _, n := range entry.HostNames { + n = strings.ToLower(n) + if n == name || n == name+"." { + ips = append(ips, entry.IP) + } + } + } + + return ips, nil +} diff --git a/utils/hostname/hostname_common.go b/utils/hostname/hostname_common.go new file mode 100644 index 0000000..86e21dd --- /dev/null +++ b/utils/hostname/hostname_common.go @@ -0,0 +1,25 @@ +package hostname + +type ContainerType uint + +const ( + ContainerUnknown ContainerType = iota + ContainerNone + ContainerSystemdNspawn + ContainerDocker + ContainerKubernetes +) + +func (c ContainerType) String() string { + switch c { + case ContainerNone: + return "metal" + case ContainerSystemdNspawn: + return "systemd-nspawn" + case ContainerDocker: + return "docker" + case ContainerKubernetes: + return "kubernetes" + } + return "unknown" +} diff --git a/utils/hostname/hostname_generic.go b/utils/hostname/hostname_generic.go index e7fc315..8796b22 100644 --- a/utils/hostname/hostname_generic.go +++ b/utils/hostname/hostname_generic.go @@ -8,3 +8,7 @@ func Fqdn() string { panic("fqdn is not implemented on your platform (" + runtime.GOOS + ")") return "" } + +func Containerization() ContainerType { + return ContainerNone +} diff --git a/utils/hostname/hostname_linux.go b/utils/hostname/hostname_linux.go index 19aaee3..50d0066 100644 --- a/utils/hostname/hostname_linux.go +++ b/utils/hostname/hostname_linux.go @@ -4,9 +4,14 @@ package hostname import ( "fmt" + "os" + "regexp" "strings" "sync" "syscall" + + "go.fuhry.dev/runtime/utils/fsutil" + "go.fuhry.dev/runtime/utils/log" ) type i8 = interface { @@ -15,6 +20,8 @@ type i8 = interface { var utsname syscall.Utsname var utsnameOnce sync.Once +var spaceExp = regexp.MustCompile(`\s+`) +var hashCommentExp = regexp.MustCompile(`#.*$`) func Hostname() string { return strings.Split(nodeName(), ".")[0] @@ -35,6 +42,12 @@ func DomainName() string { } } + if c := Containerization(); c == ContainerKubernetes { + if d := domainNameFromResolvConf(); d != "" { + return d + } + } + err := fmt.Errorf( "could not determine domain name from (uname.Nodename=%v) (uname.Domainname=%v)", int8ToString(uname.Nodename), @@ -42,6 +55,37 @@ func DomainName() string { panic(err) } +func domainNameFromResolvConf() string { + contents, err := os.ReadFile("/etc/resolv.conf") + if err != nil { + return "" + } + + lines := strings.Split(strings.ReplaceAll(string(contents), "\r\n", "\n"), "\n") + + for _, line := range lines { + line = hashCommentExp.ReplaceAllLiteralString(line, "") + words := spaceExp.Split(line, -1) + if len(words) < 2 { + continue + } + if words[0] != "search" { + continue + } + + shortestDomain := words[1] + for i := 1; i < len(words); i++ { + if len(words[i]) < len(shortestDomain) { + shortestDomain = words[i] + } + } + + return shortestDomain + } + + return "" +} + func RegionName() string { domain := DomainName() @@ -56,6 +100,32 @@ func Fqdn() string { return strings.Join([]string{Hostname(), DomainName()}, ".") } +func Containerization() ContainerType { + c := containerization() + log.Default().Infof("Detected container type: %s", c) + + return c +} + +func containerization() ContainerType { + if v := os.Getenv("KUBERNETES_SERVICE_HOST"); v != "" { + return ContainerKubernetes + } + if err := fsutil.FileExistsAndIsReadable("/.dockerenv"); err == nil { + return ContainerDocker + } + if c, err := os.ReadFile("/run/host/container-manager"); err == nil { + container := strings.Trim(string(c), "\r\n ") + switch container { + case "systemd-nspawn": + return ContainerSystemdNspawn + default: + return ContainerUnknown + } + } + return ContainerNone +} + func uname() syscall.Utsname { utsnameOnce.Do(func() { err := syscall.Uname(&utsname) -- 2.50.1