From 7bba2fa78b84c5633058dddecdfcb2213e57c573 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Tue, 28 Jan 2025 19:47:46 -0600 Subject: [PATCH 1/5] supporting multiple networks in config --- pkg/agent/sweep_service.go | 347 ++++++++++++++++++++++--------------- 1 file changed, 204 insertions(+), 143 deletions(-) diff --git a/pkg/agent/sweep_service.go b/pkg/agent/sweep_service.go index e489a02..19c1bec 100644 --- a/pkg/agent/sweep_service.go +++ b/pkg/agent/sweep_service.go @@ -1,4 +1,3 @@ -// Package agent pkg/agent/sweep_service.go package agent import ( @@ -6,7 +5,7 @@ import ( "encoding/json" "fmt" "log" - "strings" + "net" "sync" "time" @@ -16,7 +15,7 @@ import ( "github.com/mfreeman451/serviceradar/proto" ) -// SweepService implements sweeper.SweepService and provides network scanning capabilities. +// SweepService implements sweeper.SweepService for network scanning type SweepService struct { scanner scan.Scanner store sweeper.Store @@ -26,7 +25,50 @@ type SweepService struct { config *models.Config } +// ScanStats holds statistics for a network sweep +type ScanStats struct { + totalResults int + successCount int + icmpSuccess int + tcpSuccess int + uniqueHosts map[string]struct{} + startTime time.Time +} + +// NewSweepService creates a new sweep service with the provided configuration +func NewSweepService(config *models.Config) (Service, error) { + // Apply default configuration + config = applyDefaultConfig(config) + + // Create scanner with config settings + scanner := scan.NewCombinedScanner( + config.Timeout, + config.Concurrency, + config.ICMPCount, + ) + + // Create processor instance + processor := sweeper.NewBaseProcessor() + + // Create an in-memory store + store := sweeper.NewInMemoryStore(processor) + + service := &SweepService{ + scanner: scanner, + store: store, + processor: processor, + config: config, + closed: make(chan struct{}), + } + + return service, nil +} + func applyDefaultConfig(config *models.Config) *models.Config { + if config == nil { + config = &models.Config{} + } + // Ensure we have default sweep modes if len(config.SweepModes) == 0 { config.SweepModes = []models.SweepMode{ @@ -37,7 +79,7 @@ func applyDefaultConfig(config *models.Config) *models.Config { // Set reasonable defaults if config.Timeout == 0 { - config.Timeout = 2 * time.Second + config.Timeout = 10 * time.Second } if config.Concurrency == 0 { @@ -48,6 +90,10 @@ func applyDefaultConfig(config *models.Config) *models.Config { config.ICMPCount = 3 } + if config.Interval == 0 { + config.Interval = 5 * time.Minute + } + return config } @@ -75,64 +121,6 @@ func (s *SweepService) Start(ctx context.Context) error { } } -func (s *SweepService) generateTargets() ([]models.Target, error) { - var allTargets []models.Target - - for _, network := range s.config.Networks { - // First generate all IP addresses - ips, err := scan.GenerateIPsFromCIDR(network) - if err != nil { - return nil, fmt.Errorf("failed to generate IPs for %s: %w", network, err) - } - - // For each IP, create ICMP target if enabled - if containsMode(s.config.SweepModes, models.ModeICMP) { - for _, ip := range ips { - allTargets = append(allTargets, models.Target{ - Host: ip.String(), - Mode: models.ModeICMP, - }) - } - } - - // For each IP, create TCP targets for each port if enabled - if containsMode(s.config.SweepModes, models.ModeTCP) { - for _, ip := range ips { - for _, port := range s.config.Ports { - allTargets = append(allTargets, models.Target{ - Host: ip.String(), - Port: port, - Mode: models.ModeTCP, - }) - } - } - } - } - - log.Printf("Generated %d targets from %d networks (%d ports, modes: %v)", - len(allTargets), - len(s.config.Networks), - len(s.config.Ports), - s.config.SweepModes) - - return allTargets, nil -} - -// Helper function to check if a mode is in the list of modes. -func containsMode(modes []models.SweepMode, mode models.SweepMode) bool { - for _, m := range modes { - if m == mode { - return true - } - } - - return false -} - -func (*SweepService) Name() string { - return "network_sweep" -} - func (s *SweepService) performSweep(ctx context.Context) error { // Generate targets targets, err := s.generateTargets() @@ -140,7 +128,13 @@ func (s *SweepService) performSweep(ctx context.Context) error { return fmt.Errorf("failed to generate targets: %w", err) } - log.Printf("Starting sweep with %d targets", len(targets)) + // Initialize scan statistics + stats := &ScanStats{ + uniqueHosts: make(map[string]struct{}), + startTime: time.Now(), + } + + log.Printf("Starting network sweep at %s", stats.startTime.Format(time.RFC3339)) // Start the scan results, err := s.scanner.Scan(ctx, targets) @@ -150,120 +144,149 @@ func (s *SweepService) performSweep(ctx context.Context) error { // Process results as they come in for result := range results { + // Update statistics + stats.totalResults++ + if result.Available { + stats.successCount++ + stats.uniqueHosts[result.Target.Host] = struct{}{} + + switch result.Target.Mode { + case models.ModeICMP: + stats.icmpSuccess++ + case models.ModeTCP: + stats.tcpSuccess++ + } + } + // Process the result if err := s.processor.Process(&result); err != nil { log.Printf("Failed to process result: %v", err) + continue } // Store the result if err := s.store.SaveResult(ctx, &result); err != nil { log.Printf("Failed to save result: %v", err) + continue + } + + // Log successful results + if result.Available { + switch result.Target.Mode { + case models.ModeICMP: + log.Printf("Host %s responded to ICMP ping (%.2fms) - Network: %s", + result.Target.Host, + float64(result.RespTime)/float64(time.Millisecond), + result.Target.Metadata["network"]) + case models.ModeTCP: + log.Printf("Host %s has port %d open (%.2fms) - Network: %s", + result.Target.Host, + result.Target.Port, + float64(result.RespTime)/float64(time.Millisecond), + result.Target.Metadata["network"]) + } } } + // Log final scan statistics + scanDuration := time.Since(stats.startTime) + log.Printf("Sweep completed in %.2f seconds: %d total results, %d successful (%d ICMP, %d TCP), %d unique hosts", + scanDuration.Seconds(), + stats.totalResults, + stats.successCount, + stats.icmpSuccess, + stats.tcpSuccess, + len(stats.uniqueHosts)) + return nil } -// NewSweepService now creates a persistent service with a single processor instance. -func NewSweepService(config *models.Config) (*SweepService, error) { - // Apply default configuration - if config.ICMPCount <= 0 { - config.ICMPCount = 3 // Set reasonable default for ICMP ping count - } +func (s *SweepService) generateTargets() ([]models.Target, error) { + var allTargets []models.Target - // Create scanner with config settings - scanner := scan.NewCombinedScanner( - config.Timeout, - config.Concurrency, - config.ICMPCount, - ) + // Track total unique IPs for logging + uniqueIPs := make(map[string]struct{}) - // Create processor instance (which now handles both processing and storage) - processor := sweeper.NewBaseProcessor() + for _, network := range s.config.Networks { + // Parse the CIDR + _, ipNet, err := net.ParseCIDR(network) + if err != nil { + return nil, fmt.Errorf("failed to parse CIDR %s: %w", network, err) + } - // Create an in-memory store that references the same processor if needed: - store := sweeper.NewInMemoryStore(processor) + // Generate IPs - include ALL addresses in range including network/broadcast + var ips []net.IP + for ip := cloneIP(ipNet.IP); ipNet.Contains(ip); incrementIP(ip) { + ips = append(ips, cloneIP(ip)) + } - service := &SweepService{ - scanner: scanner, - store: store, - processor: processor, - config: config, - closed: make(chan struct{}), + // Add targets for each IP + for _, ip := range ips { + ipStr := ip.String() + uniqueIPs[ipStr] = struct{}{} + + // Add ICMP target if enabled + if containsMode(s.config.SweepModes, models.ModeICMP) { + allTargets = append(allTargets, models.Target{ + Host: ipStr, + Mode: models.ModeICMP, + Metadata: map[string]interface{}{ + "network": network, + }, + }) + } + + // Add TCP targets for each port if enabled + if containsMode(s.config.SweepModes, models.ModeTCP) { + for _, port := range s.config.Ports { + allTargets = append(allTargets, models.Target{ + Host: ipStr, + Port: port, + Mode: models.ModeTCP, + Metadata: map[string]interface{}{ + "network": network, + }, + }) + } + } + } } - return service, nil + log.Printf("Generated %d targets across %d networks (%d unique IPs, %d ports, modes: %v)", + len(allTargets), + len(s.config.Networks), + len(uniqueIPs), + len(s.config.Ports), + s.config.SweepModes) + + return allTargets, nil } func (s *SweepService) Stop() error { close(s.closed) - return s.scanner.Stop() } -// identifyService maps common port numbers to service names. -/* -func identifyService(port int) string { - commonPorts := map[int]string{ - 21: "FTP", - 22: "SSH", - 23: "Telnet", - 25: "SMTP", - 53: "DNS", - 80: "HTTP", - 110: "POP3", - 143: "IMAP", - 443: "HTTPS", - 3306: "MySQL", - 5432: "PostgreSQL", - 6379: "Redis", - 8080: "HTTP-Alt", - 8443: "HTTPS-Alt", - 9000: "Kadcast", // Dusk network port - } - - if service, ok := commonPorts[port]; ok { - return service - } - - return fmt.Sprintf("Port-%d", port) +func (s *SweepService) Name() string { + return "network_sweep" } -*/ func (s *SweepService) GetStatus(ctx context.Context) (*proto.StatusResponse, error) { summary, err := s.processor.GetSummary(ctx) if err != nil { - log.Printf("Error getting sweep summary: %v", err) return nil, fmt.Errorf("failed to get sweep summary: %w", err) } - // For each host in the summary, properly format ICMP status - for i := range summary.Hosts { - host := &summary.Hosts[i] - if host.ICMPStatus != nil { - log.Printf("Processing ICMP status for host %s: available=%v rtt=%v loss=%v", - host.Host, - host.ICMPStatus.Available, - host.ICMPStatus.RoundTrip, - host.ICMPStatus.PacketLoss) - - // Ensure RoundTrip is properly set in nanoseconds - if host.ICMPStatus.Available { - host.ResponseTime = host.ICMPStatus.RoundTrip - } - } - } - - // Convert to JSON for the message field + // Format sweep data for response data := struct { - Network string `json:"network"` + Networks []string `json:"networks"` TotalHosts int `json:"total_hosts"` AvailableHosts int `json:"available_hosts"` LastSweep int64 `json:"last_sweep"` Ports []models.PortCount `json:"ports"` Hosts []models.HostResult `json:"hosts"` }{ - Network: strings.Join(s.config.Networks, ","), + Networks: s.config.Networks, TotalHosts: summary.TotalHosts, AvailableHosts: summary.AvailableHosts, LastSweep: summary.LastSweep, @@ -285,19 +308,57 @@ func (s *SweepService) GetStatus(ctx context.Context) (*proto.StatusResponse, er }, nil } -// UpdateConfig updates the sweep configuration. -func (s *SweepService) UpdateConfig(config *models.Config) error { +func (s *SweepService) UpdateConfig(config models.Config) error { s.mu.Lock() defer s.mu.Unlock() - // Apply default configuration - config = applyDefaultConfig(config) - s.config = config + newConfig := applyDefaultConfig(&config) + s.config = newConfig return nil } -// Close implements io.Closer. -func (s *SweepService) Close() error { - return s.Stop() +// Helper functions for IP address manipulation +func cloneIP(ip net.IP) net.IP { + clone := make(net.IP, len(ip)) + copy(clone, ip) + return clone +} + +func incrementIP(ip net.IP) { + for i := len(ip) - 1; i >= 0; i-- { + ip[i]++ + if ip[i] != 0 { + break + } + } +} + +func containsMode(modes []models.SweepMode, mode models.SweepMode) bool { + for _, m := range modes { + if m == mode { + return true + } + } + return false +} + +// isValidNetwork checks if a network string is a valid CIDR +func isValidNetwork(network string) bool { + _, _, err := net.ParseCIDR(network) + return err == nil +} + +func validateNetworks(networks []string) error { + if len(networks) == 0 { + return fmt.Errorf("no networks specified") + } + + for _, network := range networks { + if !isValidNetwork(network) { + return fmt.Errorf("invalid network CIDR: %s", network) + } + } + + return nil } From e7d89d43f9f7372acd9b317c2e22f2b287a79652 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Tue, 28 Jan 2025 20:26:31 -0600 Subject: [PATCH 2/5] fixed UI ipv4 network sweep device list ordering --- buildCloud.sh | 2 + pkg/agent/ipv4_sorter.go | 73 ++++++++++++ pkg/agent/sweep_service.go | 150 ++++++++++++++---------- pkg/cloud/api/web/dist/index.html | 2 +- setup-deb-cloud.sh | 2 +- web/dist/index.html | 2 +- web/src/components/NetworkSweepView.jsx | 30 +++-- 7 files changed, 184 insertions(+), 77 deletions(-) create mode 100644 pkg/agent/ipv4_sorter.go diff --git a/buildCloud.sh b/buildCloud.sh index ce6f3fb..a2d1e2f 100755 --- a/buildCloud.sh +++ b/buildCloud.sh @@ -1,6 +1,8 @@ #!/bin/bash set -e +export VERSION=${VERSION} + # Build the builder image docker build -t serviceradar-builder -f Dockerfile.build . diff --git a/pkg/agent/ipv4_sorter.go b/pkg/agent/ipv4_sorter.go new file mode 100644 index 0000000..31d8a4f --- /dev/null +++ b/pkg/agent/ipv4_sorter.go @@ -0,0 +1,73 @@ +package agent + +import ( + "net" + "sort" + + "github.com/mfreeman451/serviceradar/pkg/models" +) + +// IPSorter is a type for sorting IP addresses +type IPSorter []string + +func (s IPSorter) Len() int { return len(s) } + +func (s IPSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s IPSorter) Less(i, j int) bool { + ip1 := net.ParseIP(s[i]) + ip2 := net.ParseIP(s[j]) + + // Handle nil cases (invalid IPs) by falling back to string comparison + if ip1 == nil || ip2 == nil { + return s[i] < s[j] + } + + // Convert to 4-byte representation for IPv4 + ip1 = ip1.To4() + ip2 = ip2.To4() + if ip1 == nil || ip2 == nil { + return s[i] < s[j] + } + + // Compare each byte + for i := 0; i < 4; i++ { + if ip1[i] != ip2[i] { + return ip1[i] < ip2[i] + } + } + return false +} + +// SortIPList sorts a list of IP addresses in natural order +func SortIPList(ips []string) { + sort.Sort(IPSorter(ips)) +} + +func SortHostResults(hosts []models.HostResult) { + // Create a custom sorter type for HostResults + sort.Slice(hosts, func(i, j int) bool { + ip1 := net.ParseIP(hosts[i].Host) + ip2 := net.ParseIP(hosts[j].Host) + + // Handle nil cases + if ip1 == nil || ip2 == nil { + return hosts[i].Host < hosts[j].Host + } + + // Convert to 4-byte representation + ip1 = ip1.To4() + ip2 = ip2.To4() + if ip1 == nil || ip2 == nil { + return hosts[i].Host < hosts[j].Host + } + + // Compare each byte + for k := 0; k < 4; k++ { + if ip1[k] != ip2[k] { + return ip1[k] < ip2[k] + } + } + return false + }) +} diff --git a/pkg/agent/sweep_service.go b/pkg/agent/sweep_service.go index 19c1bec..df546d4 100644 --- a/pkg/agent/sweep_service.go +++ b/pkg/agent/sweep_service.go @@ -6,6 +6,9 @@ import ( "fmt" "log" "net" + "sort" + "strconv" + "strings" "sync" "time" @@ -121,6 +124,67 @@ func (s *SweepService) Start(ctx context.Context) error { } } +func (s *SweepService) generateTargets() ([]models.Target, error) { + var allTargets []models.Target + + // Track total unique IPs for logging + uniqueIPs := make(map[string]struct{}) + + for _, network := range s.config.Networks { + // Parse the CIDR + _, ipNet, err := net.ParseCIDR(network) + if err != nil { + return nil, fmt.Errorf("failed to parse CIDR %s: %w", network, err) + } + + // Generate IPs - include ALL addresses in range including network/broadcast + var ips []net.IP + for ip := cloneIP(ipNet.IP); ipNet.Contains(ip); incrementIP(ip) { + ips = append(ips, cloneIP(ip)) + } + + // Add targets for each IP + for _, ip := range ips { + ipStr := ip.String() + uniqueIPs[ipStr] = struct{}{} + + // Add ICMP target if enabled + if containsMode(s.config.SweepModes, models.ModeICMP) { + allTargets = append(allTargets, models.Target{ + Host: ipStr, + Mode: models.ModeICMP, + Metadata: map[string]interface{}{ + "network": network, + }, + }) + } + + // Add TCP targets for each port if enabled + if containsMode(s.config.SweepModes, models.ModeTCP) { + for _, port := range s.config.Ports { + allTargets = append(allTargets, models.Target{ + Host: ipStr, + Port: port, + Mode: models.ModeTCP, + Metadata: map[string]interface{}{ + "network": network, + }, + }) + } + } + } + } + + log.Printf("Generated %d targets across %d networks (%d unique IPs, %d ports, modes: %v)", + len(allTargets), + len(s.config.Networks), + len(uniqueIPs), + len(s.config.Ports), + s.config.SweepModes) + + return allTargets, nil +} + func (s *SweepService) performSweep(ctx context.Context) error { // Generate targets targets, err := s.generateTargets() @@ -201,67 +265,6 @@ func (s *SweepService) performSweep(ctx context.Context) error { return nil } -func (s *SweepService) generateTargets() ([]models.Target, error) { - var allTargets []models.Target - - // Track total unique IPs for logging - uniqueIPs := make(map[string]struct{}) - - for _, network := range s.config.Networks { - // Parse the CIDR - _, ipNet, err := net.ParseCIDR(network) - if err != nil { - return nil, fmt.Errorf("failed to parse CIDR %s: %w", network, err) - } - - // Generate IPs - include ALL addresses in range including network/broadcast - var ips []net.IP - for ip := cloneIP(ipNet.IP); ipNet.Contains(ip); incrementIP(ip) { - ips = append(ips, cloneIP(ip)) - } - - // Add targets for each IP - for _, ip := range ips { - ipStr := ip.String() - uniqueIPs[ipStr] = struct{}{} - - // Add ICMP target if enabled - if containsMode(s.config.SweepModes, models.ModeICMP) { - allTargets = append(allTargets, models.Target{ - Host: ipStr, - Mode: models.ModeICMP, - Metadata: map[string]interface{}{ - "network": network, - }, - }) - } - - // Add TCP targets for each port if enabled - if containsMode(s.config.SweepModes, models.ModeTCP) { - for _, port := range s.config.Ports { - allTargets = append(allTargets, models.Target{ - Host: ipStr, - Port: port, - Mode: models.ModeTCP, - Metadata: map[string]interface{}{ - "network": network, - }, - }) - } - } - } - } - - log.Printf("Generated %d targets across %d networks (%d unique IPs, %d ports, modes: %v)", - len(allTargets), - len(s.config.Networks), - len(uniqueIPs), - len(s.config.Ports), - s.config.SweepModes) - - return allTargets, nil -} - func (s *SweepService) Stop() error { close(s.closed) return s.scanner.Stop() @@ -277,16 +280,15 @@ func (s *SweepService) GetStatus(ctx context.Context) (*proto.StatusResponse, er return nil, fmt.Errorf("failed to get sweep summary: %w", err) } - // Format sweep data for response data := struct { - Networks []string `json:"networks"` + Network string `json:"network"` TotalHosts int `json:"total_hosts"` AvailableHosts int `json:"available_hosts"` LastSweep int64 `json:"last_sweep"` Ports []models.PortCount `json:"ports"` Hosts []models.HostResult `json:"hosts"` }{ - Networks: s.config.Networks, + Network: strings.Join(s.config.Networks, ","), TotalHosts: summary.TotalHosts, AvailableHosts: summary.AvailableHosts, LastSweep: summary.LastSweep, @@ -294,11 +296,31 @@ func (s *SweepService) GetStatus(ctx context.Context) (*proto.StatusResponse, er Hosts: summary.Hosts, } + // Sort hosts based on IP address numeric values + sort.Slice(data.Hosts, func(i, j int) bool { + // Split IP addresses into their numeric components + ip1Parts := strings.Split(data.Hosts[i].Host, ".") + ip2Parts := strings.Split(data.Hosts[j].Host, ".") + + // Compare each octet numerically + for k := 0; k < 4; k++ { + n1, _ := strconv.Atoi(ip1Parts[k]) + n2, _ := strconv.Atoi(ip2Parts[k]) + if n1 != n2 { + return n1 < n2 + } + } + return false + }) + statusJSON, err := json.Marshal(data) if err != nil { return nil, fmt.Errorf("failed to marshal sweep status: %w", err) } + // Log the response data for debugging + log.Printf("Sweep status response: %s", string(statusJSON)) + return &proto.StatusResponse{ Available: true, Message: string(statusJSON), diff --git a/pkg/cloud/api/web/dist/index.html b/pkg/cloud/api/web/dist/index.html index 699ccda..08bf9cd 100644 --- a/pkg/cloud/api/web/dist/index.html +++ b/pkg/cloud/api/web/dist/index.html @@ -12,7 +12,7 @@ - + diff --git a/setup-deb-cloud.sh b/setup-deb-cloud.sh index db8c260..4f8c3ae 100755 --- a/setup-deb-cloud.sh +++ b/setup-deb-cloud.sh @@ -4,7 +4,7 @@ set -e # Exit on any error echo "Setting up package structure..." -VERSION=${VERSION:-1.0.9} +VERSION=${VERSION:-1.0.10} # Create package directory structure PKG_ROOT="serviceradar-cloud_${VERSION}" diff --git a/web/dist/index.html b/web/dist/index.html index 699ccda..08bf9cd 100644 --- a/web/dist/index.html +++ b/web/dist/index.html @@ -12,7 +12,7 @@ - + diff --git a/web/src/components/NetworkSweepView.jsx b/web/src/components/NetworkSweepView.jsx index b8c5680..0f9404d 100644 --- a/web/src/components/NetworkSweepView.jsx +++ b/web/src/components/NetworkSweepView.jsx @@ -1,6 +1,20 @@ import React, { useState } from 'react'; import ExportButton from './ExportButton'; +const compareIPAddresses = (ip1, ip2) => { + // Split IPs into their octets and convert to numbers + const ip1Parts = ip1.split('.').map(Number); + const ip2Parts = ip2.split('.').map(Number); + + // Compare each octet + for (let i = 0; i < 4; i++) { + if (ip1Parts[i] !== ip2Parts[i]) { + return ip1Parts[i] - ip2Parts[i]; + } + } + return 0; +}; + // Host details subcomponent with ICMP and port results const HostDetailsView = ({ host }) => { const formatResponseTime = (ns) => { @@ -79,15 +93,11 @@ const NetworkSweepView = ({ nodeId, service, standalone = false }) => { : service.details; // Sort and filter hosts - const sortHosts = (hosts) => { - return [...hosts].sort((a, b) => { - const aMatch = a.host.match(/(\d+)$/); - const bMatch = b.host.match(/(\d+)$/); - if (aMatch && bMatch) { - return parseInt(aMatch[1]) - parseInt(bMatch[1]); - } - return a.host.localeCompare(b.host); - }); + const sortAndFilterHosts = (hosts) => { + if (!hosts) return []; + return [...hosts] + .filter(host => host.host.toLowerCase().includes(searchTerm.toLowerCase())) + .sort((a, b) => compareIPAddresses(a.host, b.host)); }; // Get responding hosts only @@ -116,7 +126,7 @@ const NetworkSweepView = ({ nodeId, service, standalone = false }) => { // Filter and sort hosts for display const filteredHosts = sweepDetails.hosts - ? sortHosts(respondingHosts).filter(host => + ? sortAndFilterHosts(respondingHosts).filter(host => host.host.toLowerCase().includes(searchTerm.toLowerCase()) ) : []; From 303329844c34ac3febdf4b59c9a24fe9536ae33d Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Tue, 28 Jan 2025 20:48:56 -0600 Subject: [PATCH 3/5] fixed ordering bug --- pkg/agent/sweep_service.go | 121 ++++++++++++++++++++++++++++--------- 1 file changed, 92 insertions(+), 29 deletions(-) diff --git a/pkg/agent/sweep_service.go b/pkg/agent/sweep_service.go index df546d4..7128e62 100644 --- a/pkg/agent/sweep_service.go +++ b/pkg/agent/sweep_service.go @@ -124,27 +124,32 @@ func (s *SweepService) Start(ctx context.Context) error { } } +// Replace the generateTargets function in pkg/agent/sweep_service.go with this version: +// Replace the generateTargets function in pkg/agent/sweep_service.go with this version: func (s *SweepService) generateTargets() ([]models.Target, error) { var allTargets []models.Target - - // Track total unique IPs for logging uniqueIPs := make(map[string]struct{}) + globalTotalHosts := 0 for _, network := range s.config.Networks { // Parse the CIDR - _, ipNet, err := net.ParseCIDR(network) + ip, ipNet, err := net.ParseCIDR(network) if err != nil { return nil, fmt.Errorf("failed to parse CIDR %s: %w", network, err) } - // Generate IPs - include ALL addresses in range including network/broadcast - var ips []net.IP - for ip := cloneIP(ipNet.IP); ipNet.Contains(ip); incrementIP(ip) { - ips = append(ips, cloneIP(ip)) + // Calculate total hosts for this network + ones, bits := ipNet.Mask.Size() + var networkSize int + if ones == 32 { + networkSize = 1 // /32 network is just one host + } else { + networkSize = (1 << (bits - ones)) - 2 // Subtract network and broadcast for non-/32 } + globalTotalHosts += networkSize - // Add targets for each IP - for _, ip := range ips { + // For /32, just add the single IP + if ones == 32 { ipStr := ip.String() uniqueIPs[ipStr] = struct{}{} @@ -154,7 +159,8 @@ func (s *SweepService) generateTargets() ([]models.Target, error) { Host: ipStr, Mode: models.ModeICMP, Metadata: map[string]interface{}{ - "network": network, + "network": network, + "total_hosts": globalTotalHosts, }, }) } @@ -167,7 +173,47 @@ func (s *SweepService) generateTargets() ([]models.Target, error) { Port: port, Mode: models.ModeTCP, Metadata: map[string]interface{}{ - "network": network, + "network": network, + "total_hosts": globalTotalHosts, + }, + }) + } + } + continue + } + + // For non-/32 networks, iterate through the range + for ip := incrementIP(cloneIP(ipNet.IP)); ipNet.Contains(ip); incrementIP(ip) { + // Skip network and broadcast addresses + if isFirstOrLastAddress(ip, ipNet) { + continue + } + + ipStr := ip.String() + uniqueIPs[ipStr] = struct{}{} + + // Add ICMP target if enabled + if containsMode(s.config.SweepModes, models.ModeICMP) { + allTargets = append(allTargets, models.Target{ + Host: ipStr, + Mode: models.ModeICMP, + Metadata: map[string]interface{}{ + "network": network, + "total_hosts": globalTotalHosts, + }, + }) + } + + // Add TCP targets for each port if enabled + if containsMode(s.config.SweepModes, models.ModeTCP) { + for _, port := range s.config.Ports { + allTargets = append(allTargets, models.Target{ + Host: ipStr, + Port: port, + Mode: models.ModeTCP, + Metadata: map[string]interface{}{ + "network": network, + "total_hosts": globalTotalHosts, }, }) } @@ -175,16 +221,49 @@ func (s *SweepService) generateTargets() ([]models.Target, error) { } } - log.Printf("Generated %d targets across %d networks (%d unique IPs, %d ports, modes: %v)", + log.Printf("Generated %d targets (%d unique IPs, total hosts: %d, ports: %d, modes: %v)", len(allTargets), - len(s.config.Networks), len(uniqueIPs), + globalTotalHosts, len(s.config.Ports), s.config.SweepModes) return allTargets, nil } +// Helper function to check if an IP is a network or broadcast address +func isFirstOrLastAddress(ip net.IP, network *net.IPNet) bool { + if ip.Equal(network.IP) { + return true + } + + // Calculate broadcast address + broadcast := make(net.IP, len(network.IP)) + for i := range network.IP { + broadcast[i] = network.IP[i] | ^network.Mask[i] + } + + return ip.Equal(broadcast) +} + +// Helper function to clone an IP address +func cloneIP(ip net.IP) net.IP { + clone := make(net.IP, len(ip)) + copy(clone, ip) + return clone +} + +// Helper function to increment an IP address +func incrementIP(ip net.IP) net.IP { + for i := len(ip) - 1; i >= 0; i-- { + ip[i]++ + if ip[i] != 0 { + break + } + } + return ip +} + func (s *SweepService) performSweep(ctx context.Context) error { // Generate targets targets, err := s.generateTargets() @@ -340,22 +419,6 @@ func (s *SweepService) UpdateConfig(config models.Config) error { return nil } -// Helper functions for IP address manipulation -func cloneIP(ip net.IP) net.IP { - clone := make(net.IP, len(ip)) - copy(clone, ip) - return clone -} - -func incrementIP(ip net.IP) { - for i := len(ip) - 1; i >= 0; i-- { - ip[i]++ - if ip[i] != 0 { - break - } - } -} - func containsMode(modes []models.SweepMode, mode models.SweepMode) bool { for _, m := range modes { if m == mode { From ffbabe7224f2fd71ed7c765b4a2b498106a4f9d3 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Tue, 28 Jan 2025 21:27:01 -0600 Subject: [PATCH 4/5] fixing linter issues --- pkg/agent/ipv4_sorter.go | 40 +--- pkg/agent/sweep_service.go | 437 +++++++++++++++++++++---------------- 2 files changed, 248 insertions(+), 229 deletions(-) diff --git a/pkg/agent/ipv4_sorter.go b/pkg/agent/ipv4_sorter.go index 31d8a4f..ecde0bb 100644 --- a/pkg/agent/ipv4_sorter.go +++ b/pkg/agent/ipv4_sorter.go @@ -2,12 +2,9 @@ package agent import ( "net" - "sort" - - "github.com/mfreeman451/serviceradar/pkg/models" ) -// IPSorter is a type for sorting IP addresses +// IPSorter is a type for sorting IP addresses. type IPSorter []string func (s IPSorter) Len() int { return len(s) } @@ -26,6 +23,7 @@ func (s IPSorter) Less(i, j int) bool { // Convert to 4-byte representation for IPv4 ip1 = ip1.To4() ip2 = ip2.To4() + if ip1 == nil || ip2 == nil { return s[i] < s[j] } @@ -36,38 +34,6 @@ func (s IPSorter) Less(i, j int) bool { return ip1[i] < ip2[i] } } - return false -} - -// SortIPList sorts a list of IP addresses in natural order -func SortIPList(ips []string) { - sort.Sort(IPSorter(ips)) -} - -func SortHostResults(hosts []models.HostResult) { - // Create a custom sorter type for HostResults - sort.Slice(hosts, func(i, j int) bool { - ip1 := net.ParseIP(hosts[i].Host) - ip2 := net.ParseIP(hosts[j].Host) - // Handle nil cases - if ip1 == nil || ip2 == nil { - return hosts[i].Host < hosts[j].Host - } - - // Convert to 4-byte representation - ip1 = ip1.To4() - ip2 = ip2.To4() - if ip1 == nil || ip2 == nil { - return hosts[i].Host < hosts[j].Host - } - - // Compare each byte - for k := 0; k < 4; k++ { - if ip1[k] != ip2[k] { - return ip1[k] < ip2[k] - } - } - return false - }) + return false } diff --git a/pkg/agent/sweep_service.go b/pkg/agent/sweep_service.go index 7128e62..40c0e06 100644 --- a/pkg/agent/sweep_service.go +++ b/pkg/agent/sweep_service.go @@ -18,7 +18,13 @@ import ( "github.com/mfreeman451/serviceradar/proto" ) -// SweepService implements sweeper.SweepService for network scanning +const ( + cidr32 = 32 + networkStart = 1 + networkNext = 2 +) + +// SweepService implements sweeper.SweepService for network scanning. type SweepService struct { scanner scan.Scanner store sweeper.Store @@ -28,7 +34,7 @@ type SweepService struct { config *models.Config } -// ScanStats holds statistics for a network sweep +// ScanStats holds statistics for a network sweep. type ScanStats struct { totalResults int successCount int @@ -38,7 +44,7 @@ type ScanStats struct { startTime time.Time } -// NewSweepService creates a new sweep service with the provided configuration +// NewSweepService creates a new sweep service with the provided configuration. func NewSweepService(config *models.Config) (Service, error) { // Apply default configuration config = applyDefaultConfig(config) @@ -100,6 +106,7 @@ func applyDefaultConfig(config *models.Config) *models.Config { return config } +// Start launches the periodic sweeps. func (s *SweepService) Start(ctx context.Context) error { ticker := time.NewTicker(s.config.Interval) defer ticker.Stop() @@ -107,6 +114,7 @@ func (s *SweepService) Start(ctx context.Context) error { // Do initial sweep if err := s.performSweep(ctx); err != nil { log.Printf("Initial sweep failed: %v", err) + return err } @@ -124,101 +132,46 @@ func (s *SweepService) Start(ctx context.Context) error { } } -// Replace the generateTargets function in pkg/agent/sweep_service.go with this version: -// Replace the generateTargets function in pkg/agent/sweep_service.go with this version: +// performSweep is split into smaller functions to reduce complexity. +func (s *SweepService) performSweep(ctx context.Context) error { + // Generate targets + targets, err := s.generateTargets() + if err != nil { + return fmt.Errorf("failed to generate targets: %w", err) + } + + stats := newScanStats() + logStartSweep(stats.startTime) + + // Start the scan + results, err := s.scanner.Scan(ctx, targets) + if err != nil { + return fmt.Errorf("scan failed: %w", err) + } + + // Process results + s.processScanResults(ctx, results, stats) + + // Final statistics logging + s.logScanCompletion(stats) + + return nil +} + +// generateTargets is split into smaller helpers to reduce cognitive complexity. func (s *SweepService) generateTargets() ([]models.Target, error) { var allTargets []models.Target + uniqueIPs := make(map[string]struct{}) globalTotalHosts := 0 - for _, network := range s.config.Networks { - // Parse the CIDR - ip, ipNet, err := net.ParseCIDR(network) + for _, networkCIDR := range s.config.Networks { + networkTargets, err := s.generateTargetsForNetwork(networkCIDR, &globalTotalHosts, uniqueIPs) if err != nil { - return nil, fmt.Errorf("failed to parse CIDR %s: %w", network, err) - } - - // Calculate total hosts for this network - ones, bits := ipNet.Mask.Size() - var networkSize int - if ones == 32 { - networkSize = 1 // /32 network is just one host - } else { - networkSize = (1 << (bits - ones)) - 2 // Subtract network and broadcast for non-/32 - } - globalTotalHosts += networkSize - - // For /32, just add the single IP - if ones == 32 { - ipStr := ip.String() - uniqueIPs[ipStr] = struct{}{} - - // Add ICMP target if enabled - if containsMode(s.config.SweepModes, models.ModeICMP) { - allTargets = append(allTargets, models.Target{ - Host: ipStr, - Mode: models.ModeICMP, - Metadata: map[string]interface{}{ - "network": network, - "total_hosts": globalTotalHosts, - }, - }) - } - - // Add TCP targets for each port if enabled - if containsMode(s.config.SweepModes, models.ModeTCP) { - for _, port := range s.config.Ports { - allTargets = append(allTargets, models.Target{ - Host: ipStr, - Port: port, - Mode: models.ModeTCP, - Metadata: map[string]interface{}{ - "network": network, - "total_hosts": globalTotalHosts, - }, - }) - } - } - continue + return nil, err } - // For non-/32 networks, iterate through the range - for ip := incrementIP(cloneIP(ipNet.IP)); ipNet.Contains(ip); incrementIP(ip) { - // Skip network and broadcast addresses - if isFirstOrLastAddress(ip, ipNet) { - continue - } - - ipStr := ip.String() - uniqueIPs[ipStr] = struct{}{} - - // Add ICMP target if enabled - if containsMode(s.config.SweepModes, models.ModeICMP) { - allTargets = append(allTargets, models.Target{ - Host: ipStr, - Mode: models.ModeICMP, - Metadata: map[string]interface{}{ - "network": network, - "total_hosts": globalTotalHosts, - }, - }) - } - - // Add TCP targets for each port if enabled - if containsMode(s.config.SweepModes, models.ModeTCP) { - for _, port := range s.config.Ports { - allTargets = append(allTargets, models.Target{ - Host: ipStr, - Port: port, - Mode: models.ModeTCP, - Metadata: map[string]interface{}{ - "network": network, - "total_hosts": globalTotalHosts, - }, - }) - } - } - } + allTargets = append(allTargets, networkTargets...) } log.Printf("Generated %d targets (%d unique IPs, total hosts: %d, ports: %d, modes: %v)", @@ -231,7 +184,181 @@ func (s *SweepService) generateTargets() ([]models.Target, error) { return allTargets, nil } -// Helper function to check if an IP is a network or broadcast address +// generateTargetsForNetwork parses a single CIDR, enumerates its IPs, and builds targets. +func (s *SweepService) generateTargetsForNetwork( + networkCIDR string, + globalTotalHosts *int, + uniqueIPs map[string]struct{}, +) ([]models.Target, error) { + ip, ipNet, err := net.ParseCIDR(networkCIDR) + if err != nil { + return nil, fmt.Errorf("failed to parse CIDR %s: %w", networkCIDR, err) + } + + ones, bits := ipNet.Mask.Size() + networkSize := calculateNetworkSize(ones, bits) + *globalTotalHosts += networkSize + + if ones == cidr32 { + // Just one IP (/32) + return s.generateSingleHostTargets(ip, networkCIDR, *globalTotalHosts, uniqueIPs), nil + } + + // Enumerate non-/32 + return s.generateRangeTargets(ipNet, networkCIDR, *globalTotalHosts, uniqueIPs), nil +} + +// generateSingleHostTargets returns the targets for a /32 network. +func (s *SweepService) generateSingleHostTargets( + ip net.IP, + networkCIDR string, + totalHosts int, + uniqueIPs map[string]struct{}, +) []models.Target { + ipStr := ip.String() + uniqueIPs[ipStr] = struct{}{} + + return s.buildTargets(ipStr, networkCIDR, totalHosts) +} + +// generateRangeTargets enumerates addresses in a given net.IPNet, skipping +// network and broadcast addresses, then builds the target list. +func (s *SweepService) generateRangeTargets( + ipNet *net.IPNet, + networkCIDR string, + totalHosts int, + uniqueIPs map[string]struct{}, +) []models.Target { + var targets []models.Target + + for addr := incrementIP(cloneIP(ipNet.IP)); ipNet.Contains(addr); incrementIP(addr) { + if isFirstOrLastAddress(addr, ipNet) { + continue + } + + ipStr := addr.String() + uniqueIPs[ipStr] = struct{}{} + + targets = append(targets, s.buildTargets(ipStr, networkCIDR, totalHosts)...) + } + + return targets +} + +// buildTargets creates ICMP/TCP targets for a single IP. +func (s *SweepService) buildTargets(ipStr, network string, totalHosts int) []models.Target { + var targets []models.Target + + if containsMode(s.config.SweepModes, models.ModeICMP) { + targets = append(targets, models.Target{ + Host: ipStr, + Mode: models.ModeICMP, + Metadata: map[string]interface{}{ + "network": network, + "total_hosts": totalHosts, + }, + }) + } + + if containsMode(s.config.SweepModes, models.ModeTCP) { + for _, port := range s.config.Ports { + targets = append(targets, models.Target{ + Host: ipStr, + Port: port, + Mode: models.ModeTCP, + Metadata: map[string]interface{}{ + "network": network, + "total_hosts": totalHosts, + }, + }) + } + } + + return targets +} + +// processScanResults pulls results from the channel and updates stats/store. +func (s *SweepService) processScanResults(ctx context.Context, results <-chan models.Result, stats *ScanStats) { + for result := range results { + updateStats(stats, &result) + s.handleResult(ctx, &result) + } +} + +// handleResult handles post-scan steps: processing, storing, and logging. +func (s *SweepService) handleResult(ctx context.Context, result *models.Result) { + if err := s.processor.Process(result); err != nil { + log.Printf("Failed to process result: %v", err) + + return + } + + if err := s.store.SaveResult(ctx, result); err != nil { + log.Printf("Failed to save result: %v", err) + + return + } + + s.logSuccessfulResult(result) +} + +// logSuccessfulResult logs successful (Available) results. +func (*SweepService) logSuccessfulResult(result *models.Result) { + if !result.Available { + return + } + + switch result.Target.Mode { + case models.ModeICMP: + log.Printf("Host %s responded to ICMP ping (%.2fms) - Network: %s", + result.Target.Host, + float64(result.RespTime)/float64(time.Millisecond), + result.Target.Metadata["network"]) + case models.ModeTCP: + log.Printf("Host %s has port %d open (%.2fms) - Network: %s", + result.Target.Host, + result.Target.Port, + float64(result.RespTime)/float64(time.Millisecond), + result.Target.Metadata["network"]) + } +} + +// newScanStats initializes ScanStats for a new sweep. +func newScanStats() *ScanStats { + return &ScanStats{ + uniqueHosts: make(map[string]struct{}), + startTime: time.Now(), + } +} + +// logStartSweep logs the beginning of a sweep. +func logStartSweep(start time.Time) { + log.Printf("Starting network sweep at %s", start.Format(time.RFC3339)) +} + +// logScanCompletion logs final sweep statistics. +func (*SweepService) logScanCompletion(stats *ScanStats) { + scanDuration := time.Since(stats.startTime) + log.Printf("Sweep completed in %.2f seconds: %d total results, %d successful (%d ICMP, %d TCP), %d unique hosts", + scanDuration.Seconds(), + stats.totalResults, + stats.successCount, + stats.icmpSuccess, + stats.tcpSuccess, + len(stats.uniqueHosts)) +} + +// calculateNetworkSize calculates how many usable IP addresses exist in the subnet. +func calculateNetworkSize(ones, bits int) int { + if ones == cidr32 { + return networkStart + } + + // Subtract network and broadcast addresses for typical subnets + return (networkStart << (bits - ones)) - networkNext +} + +// isFirstOrLastAddress checks if IP is the network or broadcast address. func isFirstOrLastAddress(ip net.IP, network *net.IPNet) bool { if ip.Equal(network.IP) { return true @@ -246,14 +373,15 @@ func isFirstOrLastAddress(ip net.IP, network *net.IPNet) bool { return ip.Equal(broadcast) } -// Helper function to clone an IP address +// cloneIP returns a copy of the given net.IP. func cloneIP(ip net.IP) net.IP { clone := make(net.IP, len(ip)) copy(clone, ip) + return clone } -// Helper function to increment an IP address +// incrementIP increments an IP address by one. func incrementIP(ip net.IP) net.IP { for i := len(ip) - 1; i >= 0; i-- { ip[i]++ @@ -261,98 +389,40 @@ func incrementIP(ip net.IP) net.IP { break } } + return ip } -func (s *SweepService) performSweep(ctx context.Context) error { - // Generate targets - targets, err := s.generateTargets() - if err != nil { - return fmt.Errorf("failed to generate targets: %w", err) - } - - // Initialize scan statistics - stats := &ScanStats{ - uniqueHosts: make(map[string]struct{}), - startTime: time.Now(), - } - - log.Printf("Starting network sweep at %s", stats.startTime.Format(time.RFC3339)) - - // Start the scan - results, err := s.scanner.Scan(ctx, targets) - if err != nil { - return fmt.Errorf("scan failed: %w", err) - } - - // Process results as they come in - for result := range results { - // Update statistics - stats.totalResults++ - if result.Available { - stats.successCount++ - stats.uniqueHosts[result.Target.Host] = struct{}{} - - switch result.Target.Mode { - case models.ModeICMP: - stats.icmpSuccess++ - case models.ModeTCP: - stats.tcpSuccess++ - } - } +// updateStats updates statistics for each scan result. +func updateStats(stats *ScanStats, result *models.Result) { + stats.totalResults++ - // Process the result - if err := s.processor.Process(&result); err != nil { - log.Printf("Failed to process result: %v", err) - continue - } + if result.Available { + stats.successCount++ + stats.uniqueHosts[result.Target.Host] = struct{}{} - // Store the result - if err := s.store.SaveResult(ctx, &result); err != nil { - log.Printf("Failed to save result: %v", err) - continue - } - - // Log successful results - if result.Available { - switch result.Target.Mode { - case models.ModeICMP: - log.Printf("Host %s responded to ICMP ping (%.2fms) - Network: %s", - result.Target.Host, - float64(result.RespTime)/float64(time.Millisecond), - result.Target.Metadata["network"]) - case models.ModeTCP: - log.Printf("Host %s has port %d open (%.2fms) - Network: %s", - result.Target.Host, - result.Target.Port, - float64(result.RespTime)/float64(time.Millisecond), - result.Target.Metadata["network"]) - } + switch result.Target.Mode { + case models.ModeICMP: + stats.icmpSuccess++ + case models.ModeTCP: + stats.tcpSuccess++ } } - - // Log final scan statistics - scanDuration := time.Since(stats.startTime) - log.Printf("Sweep completed in %.2f seconds: %d total results, %d successful (%d ICMP, %d TCP), %d unique hosts", - scanDuration.Seconds(), - stats.totalResults, - stats.successCount, - stats.icmpSuccess, - stats.tcpSuccess, - len(stats.uniqueHosts)) - - return nil } +// Stop stops any in-progress scans and closes the service. func (s *SweepService) Stop() error { close(s.closed) + return s.scanner.Stop() } -func (s *SweepService) Name() string { +// Name returns the service name. +func (*SweepService) Name() string { return "network_sweep" } +// GetStatus returns a status summary of the sweep. func (s *SweepService) GetStatus(ctx context.Context) (*proto.StatusResponse, error) { summary, err := s.processor.GetSummary(ctx) if err != nil { @@ -377,18 +447,18 @@ func (s *SweepService) GetStatus(ctx context.Context) (*proto.StatusResponse, er // Sort hosts based on IP address numeric values sort.Slice(data.Hosts, func(i, j int) bool { - // Split IP addresses into their numeric components ip1Parts := strings.Split(data.Hosts[i].Host, ".") ip2Parts := strings.Split(data.Hosts[j].Host, ".") - // Compare each octet numerically for k := 0; k < 4; k++ { n1, _ := strconv.Atoi(ip1Parts[k]) n2, _ := strconv.Atoi(ip2Parts[k]) + if n1 != n2 { return n1 < n2 } } + return false }) @@ -409,41 +479,24 @@ func (s *SweepService) GetStatus(ctx context.Context) (*proto.StatusResponse, er }, nil } -func (s *SweepService) UpdateConfig(config models.Config) error { +// UpdateConfig applies new configuration and resets default values as needed. +func (s *SweepService) UpdateConfig(config *models.Config) error { s.mu.Lock() defer s.mu.Unlock() - newConfig := applyDefaultConfig(&config) + newConfig := applyDefaultConfig(config) s.config = newConfig return nil } +// containsMode checks if the mode slice includes a specific SweepMode. func containsMode(modes []models.SweepMode, mode models.SweepMode) bool { for _, m := range modes { if m == mode { return true } } - return false -} -// isValidNetwork checks if a network string is a valid CIDR -func isValidNetwork(network string) bool { - _, _, err := net.ParseCIDR(network) - return err == nil -} - -func validateNetworks(networks []string) error { - if len(networks) == 0 { - return fmt.Errorf("no networks specified") - } - - for _, network := range networks { - if !isValidNetwork(network) { - return fmt.Errorf("invalid network CIDR: %s", network) - } - } - - return nil + return false } From 95f83bd046b02a95e85e2b6cd751246bbf9ca287 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Tue, 28 Jan 2025 21:39:39 -0600 Subject: [PATCH 5/5] bump --- README.md | 34 +++++++++++++++---------------- buildAll.sh | 2 +- pkg/cloud/api/web/dist/index.html | 4 ++-- pkg/cloud/server.go | 2 +- setup-deb-agent.sh | 2 +- setup-deb-dusk-checker.sh | 2 +- setup-deb-poller.sh | 2 +- web/dist/index.html | 4 ++-- web/package.json | 2 +- web/src/components/Navbar.jsx | 2 +- 10 files changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 5c59ec4..54b968b 100644 --- a/README.md +++ b/README.md @@ -31,24 +31,24 @@ ServiceRadar can be installed via direct downloads from GitHub releases. Install these components on your monitored host: ```bash # Download and install core components -curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.3/serviceradar-agent_1.0.9.deb \ - -O https://github.com/mfreeman451/serviceradar/releases/download/1.0.3/serviceradar-poller_1.0.9.deb +curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.3/serviceradar-agent_1.0.10.deb \ + -O https://github.com/mfreeman451/serviceradar/releases/download/1.0.3/serviceradar-poller_1.0.10.deb -sudo dpkg -i serviceradar-agent_1.0.9.deb serviceradar-poller_1.0.9.deb +sudo dpkg -i serviceradar-agent_1.0.10.deb serviceradar-poller_1.0.10.deb ``` On a separate machine (recommended) or the same host: ```bash # Download and install cloud service -curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.3/serviceradar-cloud_1.0.9.deb -sudo dpkg -i serviceradar-cloud_1.0.9.deb +curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.3/serviceradar-cloud_1.0.10.deb +sudo dpkg -i serviceradar-cloud_1.0.10.deb ``` #### Optional: Dusk Node Monitoring If you're running a [Dusk](https://dusk.network/) node and want specialized monitoring: ```bash -curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.9/serviceradar-dusk-checker_1.0.9.deb -sudo dpkg -i serviceradar-dusk-checker_1.0.9.deb +curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.10/serviceradar-dusk-checker_1.0.10.deb +sudo dpkg -i serviceradar-dusk-checker_1.0.10.deb ``` #### Distributed Setup @@ -56,20 +56,20 @@ For larger deployments where components run on different hosts: 1. On monitored hosts: ```bash -curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.9/serviceradar-agent_1.0.9.deb -sudo dpkg -i serviceradar-agent_1.0.9.deb +curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.10/serviceradar-agent_1.0.10.deb +sudo dpkg -i serviceradar-agent_1.0.10.deb ``` 2. On monitoring host: ```bash -curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.3/serviceradar-poller_1.0.9.deb -sudo dpkg -i serviceradar-poller_1.0.9.deb +curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.3/serviceradar-poller_1.0.10.deb +sudo dpkg -i serviceradar-poller_1.0.10.deb ``` 3. On cloud host: ```bash -curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.3/serviceradar-cloud_1.0.9.deb -sudo dpkg -i serviceradar-cloud_1.0.9.deb +curl -LO https://github.com/mfreeman451/serviceradar/releases/download/1.0.3/serviceradar-cloud_1.0.10.deb +sudo dpkg -i serviceradar-cloud_1.0.10.deb ``` ## Architecture @@ -171,19 +171,19 @@ cd serviceradar 1. **Agent Installation** (on monitored hosts): ```bash -sudo dpkg -i serviceradar-dusk-checker_1.0.9.deb # For Dusk nodes +sudo dpkg -i serviceradar-dusk-checker_1.0.10.deb # For Dusk nodes # or -sudo dpkg -i serviceradar-agent_1.0.9.deb # For other hosts +sudo dpkg -i serviceradar-agent_1.0.10.deb # For other hosts ``` 2. **Poller Installation** (on any host in your network): ```bash -sudo dpkg -i serviceradar-poller_1.0.9.deb +sudo dpkg -i serviceradar-poller_1.0.10.deb ``` 3. **Cloud Installation** (on a reliable host): ```bash -sudo dpkg -i serviceradar-cloud_1.0.9.deb +sudo dpkg -i serviceradar-cloud_1.0.10.deb ``` ## Configuration diff --git a/buildAll.sh b/buildAll.sh index 7bd9658..f9233dc 100755 --- a/buildAll.sh +++ b/buildAll.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION=${VERSION:-1.0.9} +VERSION=${VERSION:-1.0.10} ./setup-deb-poller.sh diff --git a/pkg/cloud/api/web/dist/index.html b/pkg/cloud/api/web/dist/index.html index 08bf9cd..f969e3d 100644 --- a/pkg/cloud/api/web/dist/index.html +++ b/pkg/cloud/api/web/dist/index.html @@ -12,8 +12,8 @@ - - + +
diff --git a/pkg/cloud/server.go b/pkg/cloud/server.go index 5a0d877..debdbd4 100644 --- a/pkg/cloud/server.go +++ b/pkg/cloud/server.go @@ -252,7 +252,7 @@ func (s *Server) sendStartupNotification(ctx context.Context) error { Timestamp: time.Now().UTC().Format(time.RFC3339), NodeID: "cloud", Details: map[string]any{ - "version": "1.0.9", + "version": "1.0.10", "hostname": getHostname(), }, } diff --git a/setup-deb-agent.sh b/setup-deb-agent.sh index d89ef52..bb7ca6d 100755 --- a/setup-deb-agent.sh +++ b/setup-deb-agent.sh @@ -2,7 +2,7 @@ # setup-deb-agent.sh set -e # Exit on any error -VERSION=${VERSION:-1.0.9} +VERSION=${VERSION:-1.0.10} echo "Building serviceradar-agent version ${VERSION}" echo "Setting up package structure..." diff --git a/setup-deb-dusk-checker.sh b/setup-deb-dusk-checker.sh index ca37375..df8d894 100755 --- a/setup-deb-dusk-checker.sh +++ b/setup-deb-dusk-checker.sh @@ -4,7 +4,7 @@ set -e # Exit on any error echo "Setting up package structure..." -VERSION=${VERSION:-1.0.9} +VERSION=${VERSION:-1.0.10} # Create package directory structure PKG_ROOT="serviceradar-dusk-checker_${VERSION}" diff --git a/setup-deb-poller.sh b/setup-deb-poller.sh index 72da646..c46f4b0 100755 --- a/setup-deb-poller.sh +++ b/setup-deb-poller.sh @@ -4,7 +4,7 @@ set -e # Exit on any error echo "Setting up package structure..." -VERSION=${VERSION:-1.0.9} +VERSION=${VERSION:-1.0.10} # Create package directory structure PKG_ROOT="serviceradar-poller_${VERSION}" diff --git a/web/dist/index.html b/web/dist/index.html index 08bf9cd..f969e3d 100644 --- a/web/dist/index.html +++ b/web/dist/index.html @@ -12,8 +12,8 @@ - - + +
diff --git a/web/package.json b/web/package.json index 6abefb5..b7693b5 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "serviceradar-web", - "version": "1.0.9", + "version": "1.0.10", "private": true, "type": "module", "dependencies": { diff --git a/web/src/components/Navbar.jsx b/web/src/components/Navbar.jsx index ac5f12c..42544cf 100644 --- a/web/src/components/Navbar.jsx +++ b/web/src/components/Navbar.jsx @@ -8,7 +8,7 @@ function Navbar() {
- logo + logo ServiceRadar