package resolver import ( "encoding/json" "net" "os" "strings" "sync" "time" ) // PeerResolver maps WireGuard peer IPs to peer names type PeerResolver struct { mu sync.RWMutex ipToName map[string]string wgDir string } func NewPeerResolver(wgDir string) *PeerResolver { r := &PeerResolver{wgDir: wgDir, ipToName: make(map[string]string)} r.reload() go r.watchReload() return r } func (r *PeerResolver) PeerForIP(ip net.IP) string { r.mu.RLock() defer r.mu.RUnlock() return r.ipToName[ip.String()] } func (r *PeerResolver) reload() { newMap := make(map[string]string) // WireGuard IPs from conf files (10.1.x.x → peer name) clientsDir := r.wgDir + "/clients" entries, err := os.ReadDir(clientsDir) if err == nil { for _, entry := range entries { if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".conf") { continue } name := strings.TrimSuffix(entry.Name(), ".conf") if ip := parseAddressFromConf(clientsDir + "/" + entry.Name()); ip != "" { newMap[ip] = name } } } // External IPs from endpoint index (external IP → peer name) indexFile := r.wgDir + "/.wgctl/data/peer-history/endpoint_index.json" if data, err := os.ReadFile(indexFile); err == nil { var index map[string]string if json.Unmarshal(data, &index) == nil { for ip, peer := range index { newMap[ip] = peer } } } r.mu.Lock() r.ipToName = newMap r.mu.Unlock() } func (r *PeerResolver) watchReload() { ticker := time.NewTicker(60 * time.Second) defer ticker.Stop() for range ticker.C { r.reload() } } func parseAddressFromConf(path string) string { data, err := os.ReadFile(path) if err != nil { return "" } for _, line := range strings.Split(string(data), "\n") { line = strings.TrimSpace(line) if strings.HasPrefix(line, "Address") { parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { ip := strings.TrimSpace(parts[1]) if idx := strings.Index(ip, "/"); idx != -1 { ip = ip[:idx] } return ip } } } return "" }