package main import ( "fmt" "net" "regexp" ) var hostnameRe = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`) func validateHostname(name string) error { if !hostnameRe.MatchString(name) { return fmt.Errorf("ungültiger Hostname %q (nur Buchstaben, Ziffern, Bindestrich, kein führender/abschließender Bindestrich, keine Domain)", name) } return nil } func ip4ToUint32(ip net.IP) uint32 { b := ip.To4() return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) } // validateIP checks that ip is a valid IPv4 address inside lanCIDR and outside // the dynamic DHCP pool [poolStart, poolEnd] (both inclusive), so static // reservations can never collide with dynamically leased addresses. func validateIP(ip, lanCIDR, poolStart, poolEnd string) error { parsed := net.ParseIP(ip) if parsed == nil || parsed.To4() == nil { return fmt.Errorf("ungültige IPv4-Adresse: %q", ip) } _, cidr, err := net.ParseCIDR(lanCIDR) if err != nil { return fmt.Errorf("internes Problem: ungültiges LAN-CIDR %q: %w", lanCIDR, err) } if !cidr.Contains(parsed) { return fmt.Errorf("IP %s liegt nicht im LAN-Netz %s", ip, lanCIDR) } start := net.ParseIP(poolStart) end := net.ParseIP(poolEnd) if start == nil || end == nil { return fmt.Errorf("internes Problem: ungültiger DHCP-Pool %q-%q", poolStart, poolEnd) } v, s, e := ip4ToUint32(parsed), ip4ToUint32(start), ip4ToUint32(end) if v >= s && v <= e { return fmt.Errorf("IP %s liegt im dynamischen DHCP-Pool (%s-%s) und ist für statische Reservierungen gesperrt", ip, poolStart, poolEnd) } return nil } // normalizeMAC validates a MAC address and returns it in canonical lower-case // colon-separated form. Empty input is accepted and returned as-is. func normalizeMAC(mac string) (string, error) { if mac == "" { return "", nil } hw, err := net.ParseMAC(mac) if err != nil { return "", fmt.Errorf("ungültige MAC-Adresse %q: %w", mac, err) } return hw.String(), nil }