From e55640d6a7a69bdeaf531b1dfb4dd85072f17622 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Thu, 19 Mar 2026 15:29:11 +0100 Subject: [PATCH] fix: Enrollment-Duplikate, IPv6-Adresse, Agent-Status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Backend: Enroll-Upsert via MAC-Adresse (kein Duplikat bei Neustart) HeartbeatInterval auf 30s reduziert - Go collector: IPv4 bevorzugen, Loopback/Teredo/ISATAP überspringen, Link-Local IPv6 ignorieren - Go main.go: Enrollment nimmt beste IPv4-Schnittstelle statt Networks[0] --- Agent/cmd/agent/main.go | 17 ++++++++-- Agent/internal/collector/collector.go | 34 +++++++++++++++++-- .../GrpcServices/AgentGrpcService.cs | 24 ++++++++++--- 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/Agent/cmd/agent/main.go b/Agent/cmd/agent/main.go index a20db53..44b7631 100644 --- a/Agent/cmd/agent/main.go +++ b/Agent/cmd/agent/main.go @@ -8,6 +8,7 @@ import ( "os" "os/signal" "runtime" + "strings" "syscall" "time" @@ -40,10 +41,20 @@ func main() { if cfg.AgentID == "" { hostname, _ := os.Hostname() metrics, _ := collector.Collect() + // Beste Netzwerkschnittstelle für Enrollment wählen (bevorzuge IPv4) mac, ip := "", "" - if len(metrics.Networks) > 0 { - mac = metrics.Networks[0].MAC - ip = metrics.Networks[0].IPAddress + for _, n := range metrics.Networks { + if n.MAC == "" || n.IPAddress == "" { + continue + } + if mac == "" { + mac, ip = n.MAC, n.IPAddress + } + // Sobald IPv4 gefunden: nehmen und fertig + if !strings.Contains(n.IPAddress, ":") { + mac, ip = n.MAC, n.IPAddress + break + } } resp, err := client.Client.Enroll(context.Background(), &pb.EnrollRequest{ diff --git a/Agent/internal/collector/collector.go b/Agent/internal/collector/collector.go index 68c3463..15c8669 100644 --- a/Agent/internal/collector/collector.go +++ b/Agent/internal/collector/collector.go @@ -1,6 +1,8 @@ package collector import ( + "net" + "strings" "time" "github.com/shirou/gopsutil/v3/cpu" @@ -79,15 +81,41 @@ func Collect() (*Metrics, error) { counterMap[c.Name] = c } for _, iface := range interfaces { - if len(iface.Addrs) == 0 { + if len(iface.Addrs) == 0 || iface.HardwareAddr == "" { + continue + } + // Loopback und virtuelle Adapter überspringen + nameLower := strings.ToLower(iface.Name) + if strings.Contains(nameLower, "loopback") || strings.Contains(nameLower, "teredo") || + strings.Contains(nameLower, "isatap") || strings.Contains(nameLower, "6to4") { continue } ni := NetInfo{ Name: iface.Name, MAC: iface.HardwareAddr, } - if len(iface.Addrs) > 0 { - ni.IPAddress = iface.Addrs[0].Addr + // IPv4 bevorzugen, IPv6 als Fallback + for _, addr := range iface.Addrs { + ip, _, err := net.ParseCIDR(addr.Addr) + if err != nil { + // Versuche direkte IP-Adresse (ohne CIDR) + ip = net.ParseIP(addr.Addr) + } + if ip == nil || ip.IsLoopback() || ip.IsLinkLocalUnicast() { + continue + } + if ip.To4() != nil { + // IPv4 gefunden — nehmen und abbrechen + ni.IPAddress = addr.Addr + break + } + if ni.IPAddress == "" { + // IPv6 als vorläufiger Fallback + ni.IPAddress = addr.Addr + } + } + if ni.IPAddress == "" { + continue } if c, ok := counterMap[iface.Name]; ok { ni.BytesSent = c.BytesSent diff --git a/Backend/src/NexusRMM.Api/GrpcServices/AgentGrpcService.cs b/Backend/src/NexusRMM.Api/GrpcServices/AgentGrpcService.cs index 58485bb..c936856 100644 --- a/Backend/src/NexusRMM.Api/GrpcServices/AgentGrpcService.cs +++ b/Backend/src/NexusRMM.Api/GrpcServices/AgentGrpcService.cs @@ -29,6 +29,24 @@ public class AgentGrpcService : AgentService.AgentServiceBase public override async Task Enroll(EnrollRequest request, ServerCallContext context) { + // Upsert via MAC-Adresse — verhindert Duplikate bei Agent-Neustart + var existing = !string.IsNullOrEmpty(request.MacAddress) + ? await _db.Agents.FirstOrDefaultAsync(a => a.MacAddress == request.MacAddress) + : null; + + if (existing is not null) + { + existing.Hostname = request.Hostname; + existing.OsVersion = request.OsVersion; + existing.IpAddress = request.IpAddress; + existing.AgentVersion = request.AgentVersion; + existing.Status = AgentStatus.Online; + existing.LastSeen = DateTime.UtcNow; + await _db.SaveChangesAsync(); + _logger.LogInformation("Agent re-enrolled: {AgentId} ({Hostname})", existing.Id, existing.Hostname); + return new EnrollResponse { AgentId = existing.Id.ToString(), HeartbeatInterval = 30 }; + } + var agent = new AgentModel { Id = Guid.NewGuid(), @@ -47,11 +65,7 @@ public class AgentGrpcService : AgentService.AgentServiceBase await _db.SaveChangesAsync(); _logger.LogInformation("Agent enrolled: {AgentId} ({Hostname})", agent.Id, agent.Hostname); - return new EnrollResponse - { - AgentId = agent.Id.ToString(), - HeartbeatInterval = 60 - }; + return new EnrollResponse { AgentId = agent.Id.ToString(), HeartbeatInterval = 30 }; } public override async Task Heartbeat(HeartbeatRequest request, ServerCallContext context)