]> go.fuhry.dev Git - runtime.git/commitdiff
hostname: support /etc/hosts, /etc/resolv.conf; add container detection
authorDan Fuhry <dan@fuhry.com>
Fri, 25 Oct 2024 15:34:43 +0000 (11:34 -0400)
committerDan Fuhry <dan@fuhry.com>
Fri, 25 Oct 2024 15:42:44 +0000 (11:42 -0400)
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
go.sum
net/dns/dns_cache.go
net/dns/hosts.go [new file with mode: 0644]
utils/hostname/hostname_common.go [new file with mode: 0644]
utils/hostname/hostname_generic.go
utils/hostname/hostname_linux.go

diff --git a/go.mod b/go.mod
index 9e2df02f449810552ef3b86943aaaef18f62c927..255a7fbfc370abc1a25eda32bda58db76866fbf3 100644 (file)
--- 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 d14f2417b8d6f79a38d12c2e0c4b8ffd161d46c1..58c594297d0f9edc1e6d8371340ee6ef9bfced62 100644 (file)
--- 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=
index 4700e5bf48bbcf00c05e1bdea59fde39ac4ad157..6f63f2a6f14bce7b0add453854f1196b34859c4f 100644 (file)
@@ -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 (file)
index 0000000..99bc6ca
--- /dev/null
@@ -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 (file)
index 0000000..86e21dd
--- /dev/null
@@ -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"
+}
index e7fc315456bf9b890f94328c47e5ae7a025e426a..8796b22300b1c624fbc68eaaeede4216e87c0185 100644 (file)
@@ -8,3 +8,7 @@ func Fqdn() string {
        panic("fqdn is not implemented on your platform (" + runtime.GOOS + ")")
        return ""
 }
+
+func Containerization() ContainerType {
+       return ContainerNone
+}
index 19aaee3158eacc72c32f9af072d6540b4cd064a3..50d0066f23301e96a0eeef55fd4dc218f1781fc3 100644 (file)
@@ -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)