95 lines
2.4 KiB
Go
95 lines
2.4 KiB
Go
package agent
|
||
|
||
import (
|
||
"fmt"
|
||
"net"
|
||
"net/url"
|
||
"regexp"
|
||
"strings"
|
||
|
||
"sultan/pkg/policy"
|
||
)
|
||
|
||
// backendHost bestimmt die Adresse, unter der der Ingress die Dienste
|
||
// dieses Hosts erreicht: explizit gesetzt, sonst die IP des angegebenen
|
||
// Interfaces (z. B. wt0 für NetBird), sonst die lokale IP, über die der
|
||
// Server erreicht wird.
|
||
func backendHost(explicit, iface, serverURL string) (string, error) {
|
||
if explicit != "" {
|
||
return explicit, nil
|
||
}
|
||
if iface != "" {
|
||
return interfaceIP(iface)
|
||
}
|
||
addr, err := hostPort(serverURL)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
// UDP-"Verbindung" sendet nichts, ermittelt aber die ausgehende Adresse.
|
||
conn, err := net.Dial("udp", addr)
|
||
if err != nil {
|
||
return "", fmt.Errorf("agent: Backend-Adresse ermitteln: %w", err)
|
||
}
|
||
defer conn.Close()
|
||
return conn.LocalAddr().(*net.UDPAddr).IP.String(), nil
|
||
}
|
||
|
||
func interfaceIP(name string) (string, error) {
|
||
ifc, err := net.InterfaceByName(name)
|
||
if err != nil {
|
||
return "", fmt.Errorf("agent: Interface %s: %w", name, err)
|
||
}
|
||
addrs, err := ifc.Addrs()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
var v6 string
|
||
for _, a := range addrs {
|
||
ipnet, ok := a.(*net.IPNet)
|
||
if !ok {
|
||
continue
|
||
}
|
||
if ip4 := ipnet.IP.To4(); ip4 != nil {
|
||
return ip4.String(), nil
|
||
}
|
||
if v6 == "" && !ipnet.IP.IsLinkLocalUnicast() {
|
||
v6 = ipnet.IP.String()
|
||
}
|
||
}
|
||
if v6 != "" {
|
||
return v6, nil
|
||
}
|
||
return "", fmt.Errorf("agent: Interface %s hat keine nutzbare IP", name)
|
||
}
|
||
|
||
// hostPort liefert host:port der Server-URL (Default-Port 443).
|
||
func hostPort(serverURL string) (string, error) {
|
||
u, err := url.Parse(serverURL)
|
||
if err != nil || u.Hostname() == "" {
|
||
return "", fmt.Errorf("agent: Server-URL %q ungültig", serverURL)
|
||
}
|
||
if u.Port() == "" {
|
||
return net.JoinHostPort(u.Hostname(), "443"), nil
|
||
}
|
||
return u.Host, nil
|
||
}
|
||
|
||
var (
|
||
nodeIDUnsafe = regexp.MustCompile(`[^a-z0-9_-]+`)
|
||
nodeIDDashes = regexp.MustCompile(`-{2,}`)
|
||
nodeIDUnders = regexp.MustCompile(`_{2,}`)
|
||
)
|
||
|
||
// DefaultNodeID leitet aus dem Hostnamen eine gültige Knoten-ID ab
|
||
// (klein, a–z/0–9/-/_, kein "__").
|
||
func DefaultNodeID(hostname string) (string, error) {
|
||
id := nodeIDUnsafe.ReplaceAllString(strings.ToLower(strings.TrimSpace(hostname)), "-")
|
||
id = nodeIDDashes.ReplaceAllString(id, "-")
|
||
id = nodeIDUnders.ReplaceAllString(id, "_")
|
||
id = strings.Trim(id, "-_")
|
||
if !policy.ValidID(id) {
|
||
return "", fmt.Errorf("agent: aus Hostname %q lässt sich keine Knoten-ID ableiten – --node-id angeben", hostname)
|
||
}
|
||
return id, nil
|
||
}
|