@@ -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 , 0 o644 ) ; 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,54 @@ func enabled(k string, def bool) bool {
}
func DhcpHelperFunc ( xHostname string , xDUIDs [ ] string , xIAIDs [ ] uint32 ) [ ] payloadHelper {
/*IPv4*/
Ipv4Octets , _ := octetsRaw ( defaultIP )
Ipv4Octets , _ := octetsRaw ( pageIP ) // evtl. pageIP nehmen?
// 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 )
qDUID := xDUIDs [ 0 ]
qSegment1 := string ( rHostname [ 2 : 4 ] )
qSegment2 := string ( rHostname [ 4 : ] )
if len ( rHostname ) < 6 { // 2+4
fmt . Println ( "DhcpHelperFunc::1" )
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
qCalculatedIPv6 , _ := embedIPv4 ( qCalculatedIPv4 )
qCalculatedIPv6 , qerr := embedIPv4 ( qCalculatedIPv4 )
if qerr != nil {
fmt . Println ( qerr )
}
var res [ ] payloadHelper
for _ , t := range xIAIDs {
r := payloadHelper {
res = append ( res , payloadHelper {
Hostname : xHostname ,
DUID : qDUID ,
CalculatedIPv4 : qCalculatedIPv4 ,
@@ -140,19 +271,31 @@ 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 ) ... )
}
fmt . Println ( "getDhcp:Result::" , result )
return result
}
@@ -163,15 +306,26 @@ func register(w http.ResponseWriter, r *http.Request) {
}
var p payload
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 )
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 … ---
log . Printf ( "neuer Client: %s → DUIDs=%v" , p . Hostname , p . DUIDs )
DuidHostnameList = append ( DuidHostnameList , p )
upsertPayload ( p )
saveDuidList ( ) // 👈 direkt nach Update persistieren
w . W riteHeader ( http . StatusNoContent ) // 204
log . P rintf ( "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 ) {
@@ -194,6 +348,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 )
@@ -312,6 +470,7 @@ func convertRange(start, end string) ([]addrPair, error) {
func embedIPv4 ( ipv4 string ) ( string , error ) {
ip := net . ParseIP ( ipv4 ) . To4 ( )
if ip == nil {
fmt . Printf ( "%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 ] )
@@ -428,16 +587,17 @@ var singlePageHTML = `<!DOCTYPE html>
</form>
{{ if .HaveResult }}
<div>
<h2>Windows 11 -Eingaben</h2>
<h2>Windows-Eingaben</h2>
<p style="color:#b00">Verwenden Sie ausschließlich den 'alten' Dialog über die klassische Systemsteuerung! Nicht das 'moderne' UI über Einstellungen, da sonst zusätzliche IP-Adressen gelöscht werden!</p>
<div class="row"><label>IP-Adresse</label><input readonly id="ip" value=" {{ .IPv6 }} "><button type="button" class="copy" id="ipBtn" onclick="copy('ip')">Copy</button></div>
<div class="row"><label>Subnetzpräfix</label><input readonly id="pl" value="9 6"><button type="button" class="copy" id="plBtn" onclick="copy('pl')">Copy</button></div>
<div class="row"><label>Subnetzpräfix</label><input readonly id="pl" value="64 "><button type="button" class="copy" id="plBtn" onclick="copy('pl')">Copy</button></div>
<div class="row"><label>Gateway</label><input readonly id="gw" value=" {{ .Gateway }} "><button type="button" class="copy" id="gwBtn" onclick="copy('gw')">Copy</button></div>
<div class="row"><label>DNS bevorzugt</label><input readonly id="dns1" value=" {{ .DNS1 }} "><button type="button" class="copy" id="dns1Btn" onclick="copy('dns1')">Copy</button></div>
<div class="row"><label>DNS alternativ</label><input readonly id="dns2" value=" {{ .DNS2 }} "><button type="button" class="copy" id="dns2Btn" onclick="copy('dns2')">Copy</button></div>
</div>
{{ end }}
{{ if .Error }} <p style="color:#b00">Fehler: {{ .Error }} </p> {{ end }}
<p style="margin-top:1rem">Aktives Präfix: <code>%s</code> (/9 6)</p>
<p style="margin-top:1rem">Aktives Präfix: <code>%s</code> (/64 )</p>
</body></html> `
var rangePageHTML = ` <!DOCTYPE html>
@@ -465,6 +625,7 @@ var rangePageHTML = `<!DOCTYPE html>
<table>
<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 }}
//Add-DhcpServerv4Reservation -ComputerName $Srv -ScopeId $Prefix -IPAddress IPv6Address -ClientId $l.ClientDuid -Name $l.HostName -Description "Auto-reserved after rollout" -Type Dhcp
</table>
{{ end }}
{{ if .Error }} <p style="color:#b00">Fehler: {{ .Error }} </p> {{ end }}
@@ -491,4 +652,5 @@ var rangeDHCP6HTML = `<!DOCTYPE html>
</table>
</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"