Files
sultan/pkg/agent/hostinfo.go
T

166 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package agent
import (
"bufio"
"context"
"net"
"os"
"slices"
"strings"
"time"
"sultan/pkg/docker"
"sultan/pkg/registry"
)
// osReleasePath: Quelle des Betriebssystems ohne Docker (systemd-Agent).
var osReleasePath = "/etc/os-release"
// resolver ist der Teil von *net.Resolver, den detectFQDN braucht.
type resolver interface {
LookupCNAME(ctx context.Context, host string) (string, error)
LookupAddr(ctx context.Context, addr string) ([]string, error)
}
// detectFQDN ermittelt den vollständigen Namen des Knotens für Inventar und
// Störfall (Spec 5.3): explizit (--fqdn) > Hostname mit Punkt > Hostname
// über die Suchdomain aufgelöst > Reverse-Lookup der Backend-Adresse >
// kurzer Hostname. Jede Abfrage ist kurz begrenzt; scheitert alles, bleibt
// der Hostname.
func detectFQDN(ctx context.Context, res resolver, explicit, hostname, backend string) string {
if n := normalizeName(explicit); n != "" {
return n
}
short := normalizeName(hostname)
if strings.Contains(short, ".") {
return short
}
lookup := func(f func(context.Context) string) string {
lctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
return f(lctx)
}
if short != "" {
if n := lookup(func(c context.Context) string {
cname, err := res.LookupCNAME(c, short)
if err != nil {
return ""
}
return normalizeName(cname)
}); strings.Contains(n, ".") {
return n
}
}
if net.ParseIP(backend) != nil {
if n := lookup(func(c context.Context) string {
names, err := res.LookupAddr(c, backend)
if err != nil {
return ""
}
for _, name := range names {
if n := normalizeName(name); strings.Contains(n, ".") {
return n
}
}
return ""
}); n != "" {
return n
}
}
return short
}
func normalizeName(s string) string {
return strings.ToLower(strings.TrimSuffix(strings.TrimSpace(s), "."))
}
// cgnat ist 100.64.0.0/10 – der Bereich, aus dem NetBird Adressen vergibt.
var cgnat = &net.IPNet{IP: net.IPv4(100, 64, 0, 0).To4(), Mask: net.CIDRMask(10, 32)}
// netbirdIP wählt die NetBird-Adresse: IPv4 auf wt0, sonst die erste
// IPv4-Adresse aus 100.64.0.0/10 (Interfaces alphabetisch).
func netbirdIP(addrs map[string][]net.IP) string {
for _, ip := range addrs["wt0"] {
if ip4 := ip.To4(); ip4 != nil {
return ip4.String()
}
}
names := make([]string, 0, len(addrs))
for name := range addrs {
names = append(names, name)
}
slices.Sort(names)
for _, name := range names {
for _, ip := range addrs[name] {
if ip4 := ip.To4(); ip4 != nil && cgnat.Contains(ip4) {
return ip4.String()
}
}
}
return ""
}
// interfaceAddrs liest die Adressen aller Interfaces.
func interfaceAddrs() map[string][]net.IP {
ifaces, err := net.Interfaces()
if err != nil {
return nil
}
out := map[string][]net.IP{}
for _, ifc := range ifaces {
addrs, err := ifc.Addrs()
if err != nil {
continue
}
for _, a := range addrs {
if ipnet, ok := a.(*net.IPNet); ok {
out[ifc.Name] = append(out[ifc.Name], ipnet.IP)
}
}
}
return out
}
// osPrettyName liest PRETTY_NAME (ersatzweise NAME) aus einer os-release-Datei.
func osPrettyName(path string) string {
f, err := os.Open(path)
if err != nil {
return ""
}
defer f.Close()
var name string
sc := bufio.NewScanner(f)
for sc.Scan() {
key, value, ok := strings.Cut(strings.TrimSpace(sc.Text()), "=")
if !ok {
continue
}
value = strings.Trim(value, `"'`)
switch key {
case "PRETTY_NAME":
return value
case "NAME":
name = value
}
}
return name
}
// hostInfo sammelt die Angaben zum Knoten. Mit Docker kommt das
// Betriebssystem aus der Docker-API (das des Hosts, auch wenn der Agent in
// einem Container läuft); Fehler lassen Felder nur leer.
func (a *Agent) hostInfo(ctx context.Context, client *docker.Client) *registry.HostInfo {
h := &registry.HostInfo{FQDN: a.fqdn, NetBirdIP: netbirdIP(interfaceAddrs())}
if client != nil {
info, err := client.Info(ctx)
if err != nil {
a.log.Debug("Docker-Info nicht lesbar", "err", err)
}
h.OS, h.DockerVersion = info.OperatingSystem, info.ServerVersion
}
if h.OS == "" {
h.OS = osPrettyName(osReleasePath)
}
return h
}