diff --git a/main.go b/main.go index 8b70ff3..c153df7 100644 --- a/main.go +++ b/main.go @@ -1,88 +1,210 @@ +// client.go package main import ( + "bufio" "bytes" + "encoding/binary" "encoding/hex" + "encoding/json" "errors" "fmt" "io/fs" + "net/http" "os" "path/filepath" + "regexp" "runtime" + "strconv" "strings" + "time" "golang.org/x/sys/windows/registry" ) -// ---------- Linux ---------- +type iaidInfo struct { + Source string // Pfad oder Registry-Key + IAID uint32 +} -func duidsLinux() (map[string][]byte, error) { +// ---------- Linux ---------- +var ( + reDhclientIAID = regexp.MustCompile(`\biaid\s+(\d+);`) // dhclient, NM + reKVIAID = regexp.MustCompile(`^IAID=(\d+)`) // systemd-networkd +) + +func iaidsLinux() ([]iaidInfo, error) { + candidates := []string{ + "/var/lib/NetworkManager", + "/var/lib/dhclient", // manche Distros + "/run/systemd/netif/leases", // systemd-networkd + "/var/lib/dhcpcd", "/var/lib/dhcpcd5", + } + + var out []iaidInfo + + visit := func(path string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() { + return nil + } + f, err := os.Open(path) + if err != nil { + return nil + } + defer f.Close() + + sc := bufio.NewScanner(f) + for sc.Scan() { + line := sc.Text() + + if m := reDhclientIAID.FindStringSubmatch(line); len(m) == 2 { + if v, _ := strconv.ParseUint(m[1], 10, 32); v != 0 { + out = append(out, iaidInfo{path, uint32(v)}) + break + } + } + if m := reKVIAID.FindStringSubmatch(line); len(m) == 2 { + if v, _ := strconv.ParseUint(m[1], 10, 32); v != 0 { + out = append(out, iaidInfo{path, uint32(v)}) + break + } + } + } + return nil + } + + for _, dir := range candidates { + _ = filepath.WalkDir(dir, visit) + } + if len(out) == 0 { + return nil, fmt.Errorf("keine IAID-Datei gefunden (root-Rechte?)") + } + return out, nil +} + +// ---------- Windows ---------- +/*func iaidsWindows() ([]iaidInfo, error) { + base := `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces` + root, err := registry.OpenKey(registry.LOCAL_MACHINE, base, registry.READ) + if err != nil { + return nil, err + } + defer root.Close() + + names, _ := root.ReadSubKeyNames(-1) + var out []iaidInfo + + for _, n := range names { + k, err := registry.OpenKey(root, n, registry.READ) + if err != nil { + continue + } + val, _, err := k.GetIntegerValue("IAID") + k.Close() + if err == nil { + out = append(out, iaidInfo{ + Source: base + `\` + n + `\IAID`, + IAID: uint32(val), + }) + } + } + if len(out) == 0 { + return nil, fmt.Errorf("kein IAID-Wert in der Registry") + } + return out, nil +}*/ + +func iaidsWindows() ([]iaidInfo, error) { + const base = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces` + + root, err := registry.OpenKey(registry.LOCAL_MACHINE, base, registry.READ) + if err != nil { + return nil, err + } + defer root.Close() + + var out []iaidInfo + + guidKeys, _ := root.ReadSubKeyNames(-1) + for _, g := range guidKeys { + k, err := registry.OpenKey(root, g, registry.READ) + if err != nil { + continue + } + + if v, _, err := k.GetIntegerValue("Dhcpv6Iaid"); err == nil { + out = append(out, iaidInfo{Source: base + `\` + g, IAID: uint32(v)}) + k.Close() + continue + } + + // --- Fallback: MAC → IAID (letzte 3 Bytes), falls DWORD fehlt --- + if mac, _, err := k.GetBinaryValue("NetworkAddress"); err == nil && len(mac) >= 6 { + iaid := uint32(mac[3])<<16 | uint32(mac[4])<<8 | uint32(mac[5]) + out = append(out, iaidInfo{Source: base + `\` + g + " (computed)", IAID: iaid}) + } + k.Close() + } + if len(out) == 0 { + return nil, fmt.Errorf("weder Dhcpv6Iaid noch MAC-basierten Fallback gefunden - nutzt der Client überhaupt DHCPv6?") + } + return out, nil +} + +// ---------- Fallback: IAID selbst ableiten (MAC → 3 Byte) ---------- +func iaidFromMAC(mac []byte, ifindex int) uint32 { + if len(mac) >= 3 { + return binary.BigEndian.Uint32([]byte{0, mac[len(mac)-3], mac[len(mac)-2], mac[len(mac)-1]}) + } + // Notlösung: Interface-Index + 0xA0 im obersten Byte + return uint32(0xA0000000 | (ifindex & 0x00FFFFFF)) +} + +/*######################################################################################################*/ +/*######################################################################################################*/ +/*######################################################################################################*/ +/*######################################################################################################*/ +/*######################################################################################################*/ + +// ---------- Datentyp, der an den Server geschickt wird ---------- +type payload struct { + Hostname string `json:"hostname"` + DUIDs []string `json:"duids"` // hex-codiert + IAIDs []uint32 +} + +// ---------- Linux ---------- +func duidsLinux() ([][]byte, error) { candidates := []string{ "/var/lib/NetworkManager", "/var/lib/dhcp", "/var/lib/systemd/network", "/run/systemd/netif/leases", } - out := map[string][]byte{} - + var out [][]byte for _, dir := range candidates { _ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { return nil } - - // lease/duid files are small, read entire file buf, err := os.ReadFile(path) if err != nil { return nil } - - duid := extractDUID(buf) - if len(duid) > 0 { - out[path] = duid + if duid := extractDUID(buf); len(duid) > 0 { + out = append(out, duid) } return nil }) } if len(out) == 0 { - return nil, errors.New("keine DUID-Datei gefunden") + return nil, errors.New("keine DUID gefunden") } return out, nil } -// very tolerant parser: tries common patterns -func extractDUID(b []byte) []byte { - // Pattern „default-duid "\x00\x03\x00\x01\xaa…"“ - if bytes.Contains(b, []byte("default-duid")) || bytes.Contains(b, []byte("dhclient.duid")) { - start := bytes.IndexByte(b, '"') - end := bytes.LastIndexByte(b, '"') - if start >= 0 && end > start { - hexEsc := b[start+1 : end] - hexEsc = bytes.ReplaceAll(hexEsc, []byte(`\x`), nil) - duid, _ := hex.DecodeString(string(hexEsc)) - return duid - } - } - // systemd-networkd file: plain hex without 0x - if allHex(b) { - duid, _ := hex.DecodeString(strings.TrimSpace(string(b))) - return duid - } - return nil -} - -func allHex(b []byte) bool { - for _, c := range bytes.TrimSpace(b) { - if !strings.ContainsRune("0123456789abcdefABCDEF", rune(c)) { - return false - } - } - return true -} - // ---------- Windows ---------- - -func duidsWindows() (map[string][]byte, error) { +func duidsWindows() ([][]byte, error) { k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters`, registry.READ) @@ -95,35 +217,91 @@ func duidsWindows() (map[string][]byte, error) { if err != nil { return nil, err } - return map[string][]byte{"registry": val}, nil + return [][]byte{val}, nil } -// ---------- macOS (scutil) ---------- -// (Beispielskizze – nicht implementiert) -// func duidsDarwin() ... +// ---------- Hilfsfunktionen ---------- +func extractDUID(b []byte) []byte { + if idx := bytes.Index(b, []byte("default-duid")); idx >= 0 { + start := bytes.IndexByte(b[idx:], '"') + idx + 1 + end := bytes.IndexByte(b[start:], '"') + start + hexEsc := bytes.ReplaceAll(b[start:end], []byte(`\x`), nil) + duid, _ := hex.DecodeString(string(hexEsc)) + return duid + } + hexOnly := strings.TrimSpace(string(b)) + if len(hexOnly) >= 28 && allHex(hexOnly) { // plausibel? + duid, _ := hex.DecodeString(hexOnly) + return duid + } + return nil +} +func allHex(s string) bool { + for _, c := range s { + if !strings.ContainsRune("0123456789abcdefABCDEF", c) { + return false + } + } + return true +} // ---------- main ---------- - func main() { - var duids map[string][]byte + var raw [][]byte + var all []iaidInfo var err error - + var err1 error switch runtime.GOOS { case "windows": - duids, err = duidsWindows() - default: // linux, darwin, ... - duids, err = duidsLinux() + raw, err = duidsWindows() + all, err1 = iaidsWindows() + default: + raw, err = duidsLinux() + all, err1 = iaidsLinux() + } + if err != nil { + fmt.Fprintln(os.Stderr, err) } + if err1 != nil { + fmt.Fprintln(os.Stderr, err1) + } + + for _, ia := range all { + fmt.Printf("%-60s 0x%08x (%d)\n", ia.Source, ia.IAID, ia.IAID) + } + + var gh []uint32 + + for _, bh := range all { + gh = append(gh, bh.IAID) + } + + //host, _ := os.Hostname() + host := "PC1020" + p := payload{ + Hostname: host, + DUIDs: make([]string, len(raw)), + IAIDs: gh, + } + for i, d := range raw { + p.DUIDs[i] = hex.EncodeToString(d) + } + + // --- JSON kodieren --- + body, _ := json.Marshal(p) + + // --- HTTP senden --- + url := "http://localhost:8080/register" // <-- ggf. anpassen + req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{Timeout: 5 * time.Second} + resp, err := client.Do(req) if err != nil { - fmt.Fprintln(os.Stderr, "Fehler:", err) + fmt.Fprintln(os.Stderr, "HTTP-Fehler:", err) os.Exit(1) } - - hostName, _ := os.Hostname() - - for _, d := range duids { - fmt.Printf("%s", hex.EncodeToString(d)) - fmt.Printf("%s", hostName) - } + defer resp.Body.Close() + fmt.Println("Server-Antwort:", resp.Status) }