diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index e0894ae..0e31e6e 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -45,7 +45,7 @@ jobs: ext: ".exe" env: - GO_VERSION: "1.24" + GO_VERSION: "1.25" BINARY_NAME: ipv6calculator steps: diff --git a/main.go b/main.go index 92803b7..376eacb 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,7 @@ import ( "sort" "strconv" "strings" + "sync" ) // --------------------------------------------------------------------------- @@ -54,6 +55,11 @@ var ( dhcpScope string dhcpNamePrefix string dhcpDomain string + + // Persistenter Teil 👇 + DuidHostnameList []payload + duidFile string + duidMu sync.RWMutex ) // --------------------------------------------------------------------------- @@ -74,8 +80,6 @@ type rangeData struct { HaveResult bool } -var DuidHostnameList []payload - type payload struct { Hostname string `json:"hostname"` DUIDs []string `json:"duids"` @@ -94,6 +98,98 @@ type payloadHelper struct { Dhcp6Scope string } +func loadDuidList() { + duidMu.Lock() + defer duidMu.Unlock() + + f, err := os.Open(duidFile) + if err != nil { + if os.IsNotExist(err) { + DuidHostnameList = nil + return + } + log.Printf("duid-load: %v", err) + return + } + defer f.Close() + + var tmp []payload + if err := json.NewDecoder(f).Decode(&tmp); err != nil { + log.Printf("duid-load: decode: %v", err) + return + } + DuidHostnameList = tmp + log.Printf("duid-load: %d Einträge geladen", len(DuidHostnameList)) +} + +func saveDuidList() { + duidMu.RLock() + data, err := json.MarshalIndent(DuidHostnameList, "", " ") + duidMu.RUnlock() + if err != nil { + log.Printf("duid-save: marshal: %v", err) + return + } + + tmpFile := duidFile + ".tmp" + if err := os.WriteFile(tmpFile, data, 0o644); err != nil { + log.Printf("duid-save: write tmp: %v", err) + return + } + if err := os.Rename(tmpFile, duidFile); err != nil { + log.Printf("duid-save: rename: %v", err) + return + } +} + +func uniqueStrings(in []string) []string { + m := make(map[string]struct{}, len(in)) + var out []string + for _, s := range in { + if s == "" { + continue + } + if _, ok := m[s]; ok { + continue + } + m[s] = struct{}{} + out = append(out, s) + } + return out +} + +func uniqueU32(in []uint32) []uint32 { + m := make(map[uint32]struct{}, len(in)) + var out []uint32 + for _, v := range in { + if _, ok := m[v]; ok { + continue + } + m[v] = struct{}{} + out = append(out, v) + } + return out +} + +// fügt neu ein ODER aktualisiert bestehenden Host +func upsertPayload(p payload) { + duidMu.Lock() + defer duidMu.Unlock() + + for i := range DuidHostnameList { + if strings.EqualFold(DuidHostnameList[i].Hostname, p.Hostname) { + // merge + DuidHostnameList[i].DUIDs = uniqueStrings(append(DuidHostnameList[i].DUIDs, p.DUIDs...)) + DuidHostnameList[i].IAIDs = uniqueU32(append(DuidHostnameList[i].IAIDs, p.IAIDs...)) + return + } + } + // neu + p.DUIDs = uniqueStrings(p.DUIDs) + p.IAIDs = uniqueU32(p.IAIDs) + DuidHostnameList = append(DuidHostnameList, p) +} + func octetsRaw(ip string) ([]string, error) { parts := strings.Split(ip, ".") if len(parts) != 4 { @@ -118,19 +214,34 @@ func enabled(k string, def bool) bool { } func DhcpHelperFunc(xHostname string, xDUIDs []string, xIAIDs []uint32) []payloadHelper { - /*IPv4*/ - Ipv4Octets, _ := octetsRaw(defaultIP) + Ipv4Octets, _ := octetsRaw(defaultIP) // evtl. pageIP nehmen? rHostname := []rune(xHostname) - qDUID := xDUIDs[0] + if len(rHostname) < 6 { // 2+4 + // fallback: nimm letzte zwei Oktette 0.0 + qCalculatedIPv4 := Ipv4Octets[0] + "." + Ipv4Octets[1] + ".0.0" + qCalculatedIPv6, _ := embedIPv4(qCalculatedIPv4) + return []payloadHelper{{ + Hostname: xHostname, + DUID: firstOrEmpty(xDUIDs), + CalculatedIPv4: qCalculatedIPv4, + CalculatedIPv6: qCalculatedIPv6, + Dhcp4Scope: dhcpScope, + Dhcp6Scope: ulaPrefix, + DomainName: dhcpDomain, + DhcpServer: dhcpServer, + IAID: "0", + }} + } + + qDUID := firstOrEmpty(xDUIDs) qSegment1 := string(rHostname[2:4]) qSegment2 := string(rHostname[4:]) - qCalculatedIPv4 := Ipv4Octets[0] + "." + Ipv4Octets[1] + "." + qSegment1 + "." + qSegment2 qCalculatedIPv6, _ := embedIPv4(qCalculatedIPv4) var res []payloadHelper for _, t := range xIAIDs { - r := payloadHelper{ + res = append(res, payloadHelper{ Hostname: xHostname, DUID: qDUID, CalculatedIPv4: qCalculatedIPv4, @@ -140,17 +251,28 @@ func DhcpHelperFunc(xHostname string, xDUIDs []string, xIAIDs []uint32) []payloa DomainName: dhcpDomain, DhcpServer: dhcpServer, IAID: fmt.Sprintf("%d", t), - } - res = append(res, r) + }) } - return res } +func firstOrEmpty(xs []string) string { + if len(xs) == 0 { + return "" + } + return xs[0] +} + func getDhcp() []payloadHelper { + duidMu.RLock() + defer duidMu.RUnlock() + + cp := make([]payload, len(DuidHostnameList)) + copy(cp, DuidHostnameList) + sortByHostname(cp) + var result []payloadHelper - sortByHostname(DuidHostnameList) - for _, b := range DuidHostnameList { + for _, b := range cp { result = append(result, DhcpHelperFunc(b.Hostname, b.DUIDs, b.IAIDs)...) } return result @@ -166,12 +288,20 @@ func register(w http.ResponseWriter, r *http.Request) { http.Error(w, "ungültiges JSON", http.StatusBadRequest) return } + if p.Hostname == "" { + http.Error(w, "hostname fehlt", http.StatusBadRequest) + return + } + if len(p.DUIDs) == 0 { + // optional: nicht hart abbrechen – aber wenigstens loggen + log.Printf("register: Host %s ohne DUIDs", p.Hostname) + } - // --- hier kannst du speichern, weiterverarbeiten, loggen … --- - log.Printf("neuer Client: %s → DUIDs=%v", p.Hostname, p.DUIDs) - DuidHostnameList = append(DuidHostnameList, p) + upsertPayload(p) + saveDuidList() // 👈 direkt nach Update persistieren - w.WriteHeader(http.StatusNoContent) // 204 + log.Printf("client registriert/aktualisiert: %s → DUIDs=%v IAIDs=%v", p.Hostname, p.DUIDs, p.IAIDs) + w.WriteHeader(http.StatusNoContent) } func getdhcp6(w http.ResponseWriter, r *http.Request) { @@ -194,6 +324,10 @@ func sortByHostname(p []payload) { func main() { initConfigAndTemplates() + // Persistenz initialisieren + duidFile = getenv("DUID_DB_FILE", "duids.json") + loadDuidList() + http.HandleFunc("/", handleSingle) http.HandleFunc("/convert", handleSingleConvert) http.HandleFunc("/range", handleRange) @@ -466,6 +600,7 @@ var rangePageHTML = `
| IPv4 | IPv6 | DHCP-IPv4 | DHCP-IPv6 |
|---|---|---|---|
| {{.IPv4}} | {{.IPv6}} | netsh DHCP Server {{$.DhcpServer}} Scope {{$.DhcpScope}} Add reservedip {{.IPv4}} "{{.Name}}.{{$.DhcpDomain}}" "" "DHCP" | --- |
Fehler: {{.Error}}
{{end}} @@ -492,4 +627,5 @@ var rangeDHCP6HTML = `