Compare commits

...

6 Commits

Author SHA1 Message Date
40277a7f94 Potenziell: Problem mit führenden Nullen behoben
All checks were successful
build-binaries / build (, amd64, linux) (push) Has been skipped
build-binaries / build (, arm, 7, linux) (push) Has been skipped
build-binaries / build (, arm64, linux) (push) Has been skipped
build-binaries / build (.exe, amd64, windows) (push) Has been skipped
build-binaries / release (push) Has been skipped
release-tag / release-image (push) Successful in 2m27s
2025-11-02 16:58:37 +01:00
990565b556 Added Logging
All checks were successful
build-binaries / build (, amd64, linux) (push) Has been skipped
build-binaries / build (, arm, 7, linux) (push) Has been skipped
build-binaries / build (, arm64, linux) (push) Has been skipped
build-binaries / build (.exe, amd64, windows) (push) Has been skipped
build-binaries / release (push) Has been skipped
release-tag / release-image (push) Successful in 2m18s
2025-11-02 16:43:29 +01:00
1939136471 main.go aktualisiert
All checks were successful
build-binaries / build (, arm, 7, linux) (push) Has been skipped
build-binaries / build (, amd64, linux) (push) Has been skipped
build-binaries / build (, arm64, linux) (push) Has been skipped
build-binaries / build (.exe, amd64, windows) (push) Has been skipped
build-binaries / release (push) Has been skipped
release-tag / release-image (push) Successful in 2m18s
2025-10-31 10:52:58 +00:00
ce3d1cef4e main.go aktualisiert
All checks were successful
build-binaries / build (, amd64, linux) (push) Has been skipped
build-binaries / build (, arm, 7, linux) (push) Has been skipped
build-binaries / build (, arm64, linux) (push) Has been skipped
build-binaries / build (.exe, amd64, windows) (push) Has been skipped
build-binaries / release (push) Has been skipped
release-tag / release-image (push) Successful in 2m19s
2025-10-31 10:42:48 +00:00
4740c5d6f9 Anpassungen und Persistenz
All checks were successful
build-binaries / build (, amd64, linux) (push) Has been skipped
build-binaries / build (, arm, 7, linux) (push) Has been skipped
build-binaries / build (.exe, amd64, windows) (push) Has been skipped
build-binaries / build (, arm64, linux) (push) Has been skipped
build-binaries / release (push) Has been skipped
release-tag / release-image (push) Successful in 3m28s
2025-10-30 17:17:38 +01:00
d700061822 Update to Go-1.25.3
All checks were successful
build-binaries / build (, amd64, linux) (push) Has been skipped
build-binaries / build (, arm, 7, linux) (push) Has been skipped
build-binaries / build (.exe, amd64, windows) (push) Has been skipped
build-binaries / build (, arm64, linux) (push) Has been skipped
build-binaries / release (push) Has been skipped
release-tag / release-image (push) Successful in 3m16s
2025-10-21 07:24:14 +02:00
4 changed files with 182 additions and 21 deletions

View File

@@ -45,7 +45,7 @@ jobs:
ext: ".exe" ext: ".exe"
env: env:
GO_VERSION: "1.24" GO_VERSION: "1.25"
BINARY_NAME: ipv6calculator BINARY_NAME: ipv6calculator
steps: steps:

View File

@@ -1,4 +1,4 @@
FROM golang:1.24.1 FROM golang:1.25
WORKDIR /app WORKDIR /app
COPY go.mod ./ COPY go.mod ./
RUN go mod download RUN go mod download

2
go.mod
View File

@@ -1,3 +1,3 @@
module git.send.nrw/sendnrw/ipv6calculator module git.send.nrw/sendnrw/ipv6calculator
go 1.23.1 go 1.25.3

197
main.go
View File

@@ -30,6 +30,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync"
) )
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -54,6 +55,11 @@ var (
dhcpScope string dhcpScope string
dhcpNamePrefix string dhcpNamePrefix string
dhcpDomain string dhcpDomain string
// Persistenter Teil 👇
DuidHostnameList []payload
duidFile string
duidMu sync.RWMutex
) )
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -74,8 +80,6 @@ type rangeData struct {
HaveResult bool HaveResult bool
} }
var DuidHostnameList []payload
type payload struct { type payload struct {
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
DUIDs []string `json:"duids"` DUIDs []string `json:"duids"`
@@ -94,6 +98,98 @@ type payloadHelper struct {
Dhcp6Scope string 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) { func octetsRaw(ip string) ([]string, error) {
parts := strings.Split(ip, ".") parts := strings.Split(ip, ".")
if len(parts) != 4 { if len(parts) != 4 {
@@ -118,19 +214,54 @@ func enabled(k string, def bool) bool {
} }
func DhcpHelperFunc(xHostname string, xDUIDs []string, xIAIDs []uint32) []payloadHelper { func DhcpHelperFunc(xHostname string, xDUIDs []string, xIAIDs []uint32) []payloadHelper {
/*IPv4*/ Ipv4Octets, _ := octetsRaw(pageIP) // evtl. pageIP nehmen?
Ipv4Octets, _ := octetsRaw(defaultIP)
// wandelt "00", "01", "09" -> "0", "1", "9"
segment := func(s string) string {
n, err := strconv.Atoi(s)
if err != nil {
// Fallback: wenn es doch mal kein Zahlensring ist
return "0"
}
return strconv.Itoa(n)
}
rHostname := []rune(xHostname) rHostname := []rune(xHostname)
qDUID := xDUIDs[0] if len(rHostname) < 6 { // 2+4
qSegment1 := string(rHostname[2:4]) fmt.Println("DhcpHelperFunc::1")
qSegment2 := string(rHostname[4:]) qCalculatedIPv4 := Ipv4Octets[0] + "." + Ipv4Octets[1] + ".0.0"
qCalculatedIPv6, qerr := embedIPv4(qCalculatedIPv4)
if qerr != nil {
fmt.Println(qerr)
}
return []payloadHelper{{
Hostname: xHostname,
DUID: firstOrEmpty(xDUIDs),
CalculatedIPv4: qCalculatedIPv4,
CalculatedIPv6: qCalculatedIPv6,
Dhcp4Scope: dhcpScope,
Dhcp6Scope: ulaPrefix,
DomainName: dhcpDomain,
DhcpServer: dhcpServer,
IAID: "0",
}}
}
fmt.Println("DhcpHelperFunc::2")
qDUID := firstOrEmpty(xDUIDs)
qSegment1 := segment(string(rHostname[2:4]))
qSegment2 := segment(string(rHostname[4:]))
qCalculatedIPv4 := Ipv4Octets[0] + "." + Ipv4Octets[1] + "." + qSegment1 + "." + qSegment2 qCalculatedIPv4 := Ipv4Octets[0] + "." + Ipv4Octets[1] + "." + qSegment1 + "." + qSegment2
qCalculatedIPv6, _ := embedIPv4(qCalculatedIPv4) qCalculatedIPv6, qerr := embedIPv4(qCalculatedIPv4)
if qerr != nil {
fmt.Println(qerr)
}
var res []payloadHelper var res []payloadHelper
for _, t := range xIAIDs { for _, t := range xIAIDs {
r := payloadHelper{ res = append(res, payloadHelper{
Hostname: xHostname, Hostname: xHostname,
DUID: qDUID, DUID: qDUID,
CalculatedIPv4: qCalculatedIPv4, CalculatedIPv4: qCalculatedIPv4,
@@ -140,19 +271,31 @@ func DhcpHelperFunc(xHostname string, xDUIDs []string, xIAIDs []uint32) []payloa
DomainName: dhcpDomain, DomainName: dhcpDomain,
DhcpServer: dhcpServer, DhcpServer: dhcpServer,
IAID: fmt.Sprintf("%d", t), IAID: fmt.Sprintf("%d", t),
} })
res = append(res, r)
} }
return res return res
} }
func firstOrEmpty(xs []string) string {
if len(xs) == 0 {
return ""
}
return xs[0]
}
func getDhcp() []payloadHelper { func getDhcp() []payloadHelper {
duidMu.RLock()
defer duidMu.RUnlock()
cp := make([]payload, len(DuidHostnameList))
copy(cp, DuidHostnameList)
sortByHostname(cp)
var result []payloadHelper var result []payloadHelper
sortByHostname(DuidHostnameList) for _, b := range cp {
for _, b := range DuidHostnameList {
result = append(result, DhcpHelperFunc(b.Hostname, b.DUIDs, b.IAIDs)...) result = append(result, DhcpHelperFunc(b.Hostname, b.DUIDs, b.IAIDs)...)
} }
fmt.Println("getDhcp:Result::", result)
return result return result
} }
@@ -163,15 +306,26 @@ func register(w http.ResponseWriter, r *http.Request) {
} }
var p payload var p payload
if err := json.NewDecoder(r.Body).Decode(&p); err != nil { if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
fmt.Println("register:NewDecoder::ungültiges JSON")
http.Error(w, "ungültiges JSON", http.StatusBadRequest) http.Error(w, "ungültiges JSON", http.StatusBadRequest)
return return
} }
if p.Hostname == "" {
fmt.Println("register:Hostname::hostname fehlt")
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 … --- upsertPayload(p)
log.Printf("neuer Client: %s → DUIDs=%v", p.Hostname, p.DUIDs) saveDuidList() // 👈 direkt nach Update persistieren
DuidHostnameList = append(DuidHostnameList, p)
w.WriteHeader(http.StatusNoContent) // 204 log.Printf("client registriert/aktualisiert: %s → DUIDs=%v IAIDs=%v", p.Hostname, p.DUIDs, p.IAIDs)
log.Println(p)
w.WriteHeader(http.StatusNoContent)
} }
func getdhcp6(w http.ResponseWriter, r *http.Request) { func getdhcp6(w http.ResponseWriter, r *http.Request) {
@@ -194,6 +348,10 @@ func sortByHostname(p []payload) {
func main() { func main() {
initConfigAndTemplates() initConfigAndTemplates()
// Persistenz initialisieren
duidFile = getenv("DUID_DB_FILE", "duids.json")
loadDuidList()
http.HandleFunc("/", handleSingle) http.HandleFunc("/", handleSingle)
http.HandleFunc("/convert", handleSingleConvert) http.HandleFunc("/convert", handleSingleConvert)
http.HandleFunc("/range", handleRange) http.HandleFunc("/range", handleRange)
@@ -312,6 +470,7 @@ func convertRange(start, end string) ([]addrPair, error) {
func embedIPv4(ipv4 string) (string, error) { func embedIPv4(ipv4 string) (string, error) {
ip := net.ParseIP(ipv4).To4() ip := net.ParseIP(ipv4).To4()
if ip == nil { if ip == nil {
fmt.Printf("%s ist keine gültige IPv4-Adresse", ipv4)
return "", fmt.Errorf("%s ist keine gültige IPv4-Adresse", ipv4) return "", fmt.Errorf("%s ist keine gültige IPv4-Adresse", ipv4)
} }
hi := uint16(ip[0])<<8 | uint16(ip[1]) hi := uint16(ip[0])<<8 | uint16(ip[1])
@@ -466,6 +625,7 @@ var rangePageHTML = `<!DOCTYPE html>
<table> <table>
<tr><th>IPv4</th><th>IPv6</th><th>DHCP-IPv4</th><th>DHCP-IPv6</th></tr> <tr><th>IPv4</th><th>IPv6</th><th>DHCP-IPv4</th><th>DHCP-IPv6</th></tr>
{{range .Rows}}<tr><td>{{.IPv4}}</td><td>{{.IPv6}}</td><td>netsh DHCP Server {{$.DhcpServer}} Scope {{$.DhcpScope}} Add reservedip {{.IPv4}} "{{.Name}}.{{$.DhcpDomain}}" "" "DHCP"</td><td>---</td></tr>{{end}} {{range .Rows}}<tr><td>{{.IPv4}}</td><td>{{.IPv6}}</td><td>netsh DHCP Server {{$.DhcpServer}} Scope {{$.DhcpScope}} Add reservedip {{.IPv4}} "{{.Name}}.{{$.DhcpDomain}}" "" "DHCP"</td><td>---</td></tr>{{end}}
//Add-DhcpServerv4Reservation -ComputerName $Srv -ScopeId $Prefix -IPAddress IPv6Address -ClientId $l.ClientDuid -Name $l.HostName -Description "Auto-reserved after rollout" -Type Dhcp
</table> </table>
{{end}} {{end}}
{{if .Error}}<p style="color:#b00">Fehler: {{.Error}}</p>{{end}} {{if .Error}}<p style="color:#b00">Fehler: {{.Error}}</p>{{end}}
@@ -492,4 +652,5 @@ var rangeDHCP6HTML = `<!DOCTYPE html>
</table> </table>
</body></html>` </body></html>`
//Add-DhcpServerv4Reservation -ComputerName $Srv -ScopeId $Prefix -IPAddress IPv6Address -ClientId $l.ClientDuid -Name $l.HostName -Description "Auto-reserved after rollout" -Type Dhcp
//Add-DhcpServerv6Reservation -ComputerName $Srv -Prefix $Prefix -IPAddress IPv6Address -ClientDuid $l.ClientDuid -Iaid $l.Iaid -Name $l.HostName -Description "Auto-reserved after rollout" //Add-DhcpServerv6Reservation -ComputerName $Srv -Prefix $Prefix -IPAddress IPv6Address -ClientDuid $l.ClientDuid -Iaid $l.Iaid -Name $l.HostName -Description "Auto-reserved after rollout"