diff options
Diffstat (limited to 'transport/failover.go')
| -rw-r--r-- | transport/failover.go | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/transport/failover.go b/transport/failover.go new file mode 100644 index 0000000..77129a3 --- /dev/null +++ b/transport/failover.go @@ -0,0 +1,284 @@ +// Package transport - Multi-transport failover for war zones +package transport + +import ( + "fmt" + "net" + "sync" + "time" +) + +// FailoverTransport implements automatic failover between multiple transports +// Critical for war zones: Tor → Mesh → Bluetooth → Direct +type FailoverTransport struct { + transports []Transport // Ordered list of transports to try + currentIndex int // Currently active transport + mu sync.RWMutex + healthCheck map[TransportType]time.Time // Last successful connection + retryInterval time.Duration + maxRetries int + onFailover func(from, to TransportType) // Callback when failing over +} + +// FailoverConfig holds failover configuration +type FailoverConfig struct { + RetryInterval time.Duration // Time between retry attempts + MaxRetries int // Max retries per transport before failing over + HealthCheckInterval time.Duration // How often to health check transports +} + +// NewFailoverTransport creates a new failover transport +func NewFailoverTransport(transports []Transport, config *FailoverConfig) *FailoverTransport { + if config == nil { + config = &FailoverConfig{ + RetryInterval: 5 * time.Second, + MaxRetries: 3, + HealthCheckInterval: 30 * time.Second, + } + } + + ft := &FailoverTransport{ + transports: transports, + currentIndex: 0, + healthCheck: make(map[TransportType]time.Time), + retryInterval: config.RetryInterval, + maxRetries: config.MaxRetries, + } + + // Start health checking + go ft.healthCheckLoop(config.HealthCheckInterval) + + return ft +} + +// Connect attempts to connect using transports in priority order +func (ft *FailoverTransport) Connect(address string) (net.Conn, error) { + ft.mu.Lock() + startIndex := ft.currentIndex + ft.mu.Unlock() + + // Try each transport in order + for attempt := 0; attempt < len(ft.transports)*ft.maxRetries; attempt++ { + ft.mu.Lock() + index := ft.currentIndex + transport := ft.transports[index] + ft.mu.Unlock() + + transportType := transport.Type() + + // Attempt connection + conn, err := transport.Connect(address) + if err == nil { + // Success! + ft.mu.Lock() + ft.healthCheck[transportType] = time.Now() + ft.mu.Unlock() + + fmt.Printf("✓ Connected via %s\n", transportType) + return conn, nil + } + + fmt.Printf("✗ %s connection failed: %v\n", transportType, err) + + // Move to next transport + ft.mu.Lock() + oldIndex := ft.currentIndex + ft.currentIndex = (ft.currentIndex + 1) % len(ft.transports) + newTransport := ft.transports[ft.currentIndex] + ft.mu.Unlock() + + // Notify failover + if ft.onFailover != nil && oldIndex != ft.currentIndex { + go ft.onFailover(transport.Type(), newTransport.Type()) + } + + // Wait before retry + time.Sleep(ft.retryInterval) + } + + return nil, fmt.Errorf("all transports failed after %d attempts", len(ft.transports)*ft.maxRetries) +} + +// Listen starts listening on the highest priority available transport +func (ft *FailoverTransport) Listen(address string) (net.Listener, error) { + ft.mu.RLock() + defer ft.mu.RUnlock() + + // Try each transport + for _, transport := range ft.transports { + listener, err := transport.Listen(address) + if err == nil { + fmt.Printf("✓ Listening on %s\n", transport.Type()) + return listener, nil + } + fmt.Printf("✗ Listen failed on %s: %v\n", transport.Type(), err) + } + + return nil, fmt.Errorf("all transports failed to listen") +} + +// Type returns the currently active transport type +func (ft *FailoverTransport) Type() TransportType { + ft.mu.RLock() + defer ft.mu.RUnlock() + + if ft.currentIndex < len(ft.transports) { + return ft.transports[ft.currentIndex].Type() + } + return TransportType(-1) +} + +// Close closes all transports +func (ft *FailoverTransport) Close() error { + ft.mu.Lock() + defer ft.mu.Unlock() + + var lastErr error + for _, transport := range ft.transports { + if err := transport.Close(); err != nil { + lastErr = err + } + } + return lastErr +} + +// GetCurrentTransport returns the currently active transport +func (ft *FailoverTransport) GetCurrentTransport() Transport { + ft.mu.RLock() + defer ft.mu.RUnlock() + + if ft.currentIndex < len(ft.transports) { + return ft.transports[ft.currentIndex] + } + return nil +} + +// GetTransports returns all available transports +func (ft *FailoverTransport) GetTransports() []Transport { + ft.mu.RLock() + defer ft.mu.RUnlock() + + return ft.transports +} + +// SetTransportPriority reorders transports by priority +func (ft *FailoverTransport) SetTransportPriority(types []TransportType) error { + ft.mu.Lock() + defer ft.mu.Unlock() + + if len(types) != len(ft.transports) { + return fmt.Errorf("priority list length mismatch") + } + + // Create new ordered list + newTransports := make([]Transport, 0, len(ft.transports)) + for _, targetType := range types { + found := false + for _, transport := range ft.transports { + if transport.Type() == targetType { + newTransports = append(newTransports, transport) + found = true + break + } + } + if !found { + return fmt.Errorf("transport type %s not found", targetType) + } + } + + ft.transports = newTransports + ft.currentIndex = 0 + + return nil +} + +// healthCheckLoop periodically checks transport health +func (ft *FailoverTransport) healthCheckLoop(interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for range ticker.C { + ft.performHealthCheck() + } +} + +// performHealthCheck checks if current transport is still healthy +func (ft *FailoverTransport) performHealthCheck() { + ft.mu.RLock() + current := ft.transports[ft.currentIndex] + lastSuccess, exists := ft.healthCheck[current.Type()] + ft.mu.RUnlock() + + // If no successful connection in last 5 minutes, try next transport + if !exists || time.Since(lastSuccess) > 5*time.Minute { + ft.mu.Lock() + oldIndex := ft.currentIndex + ft.currentIndex = (ft.currentIndex + 1) % len(ft.transports) + ft.mu.Unlock() + + if ft.onFailover != nil && oldIndex != ft.currentIndex { + ft.mu.RLock() + go ft.onFailover(ft.transports[oldIndex].Type(), ft.transports[ft.currentIndex].Type()) + ft.mu.RUnlock() + } + } +} + +// SetOnFailover sets callback for failover events +func (ft *FailoverTransport) SetOnFailover(callback func(from, to TransportType)) { + ft.mu.Lock() + defer ft.mu.Unlock() + ft.onFailover = callback +} + +// GetHealthStatus returns health status of all transports +func (ft *FailoverTransport) GetHealthStatus() map[TransportType]time.Time { + ft.mu.RLock() + defer ft.mu.RUnlock() + + status := make(map[TransportType]time.Time) + for k, v := range ft.healthCheck { + status[k] = v + } + return status +} + +// String returns a string representation +func (ft *FailoverTransport) String() string { + ft.mu.RLock() + defer ft.mu.RUnlock() + + currentType := "none" + if ft.currentIndex < len(ft.transports) { + currentType = ft.transports[ft.currentIndex].Type().String() + } + + return fmt.Sprintf("FailoverTransport(current=%s, transports=%d)", + currentType, len(ft.transports)) +} + +// ConnectWithRetry attempts connection with exponential backoff +func (ft *FailoverTransport) ConnectWithRetry(address string, maxAttempts int) (net.Conn, error) { + var conn net.Conn + var err error + + backoff := 1 * time.Second + + for attempt := 1; attempt <= maxAttempts; attempt++ { + conn, err = ft.Connect(address) + if err == nil { + return conn, nil + } + + fmt.Printf("Attempt %d/%d failed, retrying in %v...\n", attempt, maxAttempts, backoff) + time.Sleep(backoff) + + // Exponential backoff with jitter + backoff *= 2 + if backoff > 60*time.Second { + backoff = 60 * time.Second + } + } + + return nil, fmt.Errorf("failed after %d attempts: %w", maxAttempts, err) +} |
