Files
hmac-file-server/cmd/server/client_network_handler.go
Alexander Renz 3887feb12c Add enhanced configuration templates for adaptive I/O features
- Introduced a comprehensive configuration template (config-adaptive.toml) for adaptive I/O, enabling improved upload/download dual stack with various performance optimizations, security settings, and network resilience features.
- Created a test configuration template (test-config.toml) mirroring the adaptive configuration for testing purposes.
- Added a simple test configuration (test-simple-config.toml) for basic adaptive features testing with essential parameters.
- Included an empty Jupyter notebook (xep0363_analysis.ipynb) for future analysis related to XEP-0363.
2025-08-23 12:07:31 +00:00

310 lines
9.4 KiB
Go

// client_network_handler.go - Handles clients with multiple network interfaces
// This is the CORRECT implementation focusing on CLIENT multi-interface support
package main
import (
"fmt"
"net"
"net/http"
"strings"
"sync"
"time"
)
// ClientConnectionTracker manages clients that switch between network interfaces
type ClientConnectionTracker struct {
sessions map[string]*ClientSession // sessionID -> session info
ipToSession map[string]string // IP -> sessionID for quick lookup
mutex sync.RWMutex
config *ClientNetworkConfig
}
// ClientSession represents a client that may connect from multiple IPs/interfaces
type ClientSession struct {
SessionID string
ClientIPs []string // All IPs this session has used
ConnectionType string // mobile, wifi, ethernet, unknown
LastSeen time.Time
UploadInfo *UploadSessionInfo
NetworkQuality float64 // 0-100 quality score
mutex sync.RWMutex
}
// UploadSessionInfo tracks upload progress across network switches
type UploadSessionInfo struct {
FileName string
TotalSize int64
UploadedBytes int64
ChunkSize int64
LastChunkID int
Chunks map[int]bool // chunkID -> received
Started time.Time
LastActivity time.Time
}
// ClientNetworkConfig holds configuration for client network handling
type ClientNetworkConfig struct {
SessionBasedTracking bool `toml:"session_based_tracking" mapstructure:"session_based_tracking"`
AllowIPChanges bool `toml:"allow_ip_changes" mapstructure:"allow_ip_changes"`
SessionMigrationTimeout time.Duration // Will be parsed from string in main.go
MaxIPChangesPerSession int `toml:"max_ip_changes_per_session" mapstructure:"max_ip_changes_per_session"`
ClientConnectionDetection bool `toml:"client_connection_detection" mapstructure:"client_connection_detection"`
AdaptToClientNetwork bool `toml:"adapt_to_client_network" mapstructure:"adapt_to_client_network"`
}
// ConnectionType represents different client connection types
type ConnectionType int
const (
ConnectionUnknown ConnectionType = iota
ConnectionMobile // LTE/5G
ConnectionWiFi // WiFi
ConnectionEthernet // Wired
)
func (ct ConnectionType) String() string {
switch ct {
case ConnectionMobile:
return "mobile"
case ConnectionWiFi:
return "wifi"
case ConnectionEthernet:
return "ethernet"
default:
return "unknown"
}
}
// NewClientConnectionTracker creates a new tracker for multi-interface clients
func NewClientConnectionTracker(config *ClientNetworkConfig) *ClientConnectionTracker {
return &ClientConnectionTracker{
sessions: make(map[string]*ClientSession),
ipToSession: make(map[string]string),
config: config,
}
}
// DetectClientConnectionType analyzes the request to determine client connection type
func (cct *ClientConnectionTracker) DetectClientConnectionType(r *http.Request) string {
// Check User-Agent for mobile indicators
userAgent := strings.ToLower(r.Header.Get("User-Agent"))
// Mobile detection
if containsAny(userAgent, "mobile", "android", "iphone", "ipad", "phone") {
return "mobile"
}
// Check for specific network indicators in headers
if xForwardedFor := r.Header.Get("X-Forwarded-For"); xForwardedFor != "" {
// This might indicate the client is behind a mobile carrier NAT
// Additional logic could be added here
}
// Check connection patterns (this would need more sophisticated logic)
clientIP := getClientIP(r)
if cct.isLikelyMobileIP(clientIP) {
return "mobile"
}
// Default assumption for unknown
return "unknown"
}
// TrackClientSession tracks a client session across potential IP changes
func (cct *ClientConnectionTracker) TrackClientSession(sessionID string, clientIP string, r *http.Request) *ClientSession {
cct.mutex.Lock()
defer cct.mutex.Unlock()
// Check if this IP is already associated with a different session
if existingSessionID, exists := cct.ipToSession[clientIP]; exists && existingSessionID != sessionID {
// This IP was previously used by a different session
// This could indicate a client that switched networks
if cct.config.AllowIPChanges {
// Remove old association
delete(cct.ipToSession, clientIP)
}
}
// Get or create session
session, exists := cct.sessions[sessionID]
if !exists {
session = &ClientSession{
SessionID: sessionID,
ClientIPs: []string{clientIP},
ConnectionType: cct.DetectClientConnectionType(r),
LastSeen: time.Now(),
NetworkQuality: 100.0, // Start with good quality
}
cct.sessions[sessionID] = session
} else {
session.mutex.Lock()
// Add this IP if it's not already tracked
if !contains(session.ClientIPs, clientIP) {
if len(session.ClientIPs) < cct.config.MaxIPChangesPerSession {
session.ClientIPs = append(session.ClientIPs, clientIP)
fmt.Printf("Client session %s now using new IP: %s (total IPs: %d)\n",
sessionID, clientIP, len(session.ClientIPs))
}
}
session.LastSeen = time.Now()
session.mutex.Unlock()
}
// Update IP to session mapping
cct.ipToSession[clientIP] = sessionID
return session
}
// GetOptimalChunkSize returns the optimal chunk size for a client's connection type
func (cct *ClientConnectionTracker) GetOptimalChunkSize(session *ClientSession) int64 {
switch session.ConnectionType {
case "mobile":
return 256 * 1024 // 256KB for mobile/LTE
case "wifi":
return 2 * 1024 * 1024 // 2MB for WiFi
case "ethernet":
return 8 * 1024 * 1024 // 8MB for ethernet
default:
return 1 * 1024 * 1024 // 1MB default
}
}
// GetOptimalTimeout returns the optimal timeout for a client's connection type
func (cct *ClientConnectionTracker) GetOptimalTimeout(session *ClientSession, baseTimeout time.Duration) time.Duration {
switch session.ConnectionType {
case "mobile":
return time.Duration(float64(baseTimeout) * 2.0) // 2x timeout for mobile
case "wifi":
return baseTimeout // Standard timeout for WiFi
case "ethernet":
return time.Duration(float64(baseTimeout) * 0.8) // 0.8x timeout for ethernet
default:
return baseTimeout
}
}
// HandleClientReconnection handles when a client reconnects from a different IP
func (cct *ClientConnectionTracker) HandleClientReconnection(sessionID string, newIP string, r *http.Request) error {
cct.mutex.Lock()
defer cct.mutex.Unlock()
session, exists := cct.sessions[sessionID]
if !exists {
return fmt.Errorf("session %s not found", sessionID)
}
session.mutex.Lock()
defer session.mutex.Unlock()
// Check if this is actually a new IP
if contains(session.ClientIPs, newIP) {
// Client reconnected from known IP
session.LastSeen = time.Now()
return nil
}
// This is a new IP for this session - client likely switched networks
if len(session.ClientIPs) >= cct.config.MaxIPChangesPerSession {
return fmt.Errorf("session %s exceeded maximum IP changes (%d)",
sessionID, cct.config.MaxIPChangesPerSession)
}
// Add new IP and update connection type
session.ClientIPs = append(session.ClientIPs, newIP)
session.ConnectionType = cct.DetectClientConnectionType(r)
session.LastSeen = time.Now()
// Update IP mapping
cct.ipToSession[newIP] = sessionID
fmt.Printf("Client session %s reconnected from new IP %s (connection type: %s)\n",
sessionID, newIP, session.ConnectionType)
return nil
}
// ResumeUpload handles resuming an upload when client switches networks
func (cct *ClientConnectionTracker) ResumeUpload(sessionID string, uploadInfo *UploadSessionInfo) error {
cct.mutex.RLock()
session, exists := cct.sessions[sessionID]
cct.mutex.RUnlock()
if !exists {
return fmt.Errorf("session %s not found for upload resume", sessionID)
}
session.mutex.Lock()
session.UploadInfo = uploadInfo
session.LastSeen = time.Now()
session.mutex.Unlock()
fmt.Printf("Resumed upload for session %s: %s (%d/%d bytes)\n",
sessionID, uploadInfo.FileName, uploadInfo.UploadedBytes, uploadInfo.TotalSize)
return nil
}
// CleanupStaleSession removes sessions that haven't been seen recently
func (cct *ClientConnectionTracker) CleanupStaleSessions() {
cct.mutex.Lock()
defer cct.mutex.Unlock()
cutoff := time.Now().Add(-cct.config.SessionMigrationTimeout)
for sessionID, session := range cct.sessions {
if session.LastSeen.Before(cutoff) {
// Remove from IP mappings
for _, ip := range session.ClientIPs {
delete(cct.ipToSession, ip)
}
// Remove session
delete(cct.sessions, sessionID)
fmt.Printf("Cleaned up stale session: %s\n", sessionID)
}
}
}
// isLikelyMobileIP attempts to determine if an IP is from a mobile carrier
func (cct *ClientConnectionTracker) isLikelyMobileIP(ip string) bool {
// This is a simplified check - in practice, you'd check against
// known mobile carrier IP ranges
parsedIP := net.ParseIP(ip)
if parsedIP == nil {
return false
}
// Example: Some mobile carriers use specific IP ranges
// This would need to be populated with actual carrier ranges
mobileRanges := []string{
"10.0.0.0/8", // Some carriers use 10.x for mobile
"172.16.0.0/12", // Some carriers use 172.x for mobile
}
for _, rangeStr := range mobileRanges {
_, cidr, err := net.ParseCIDR(rangeStr)
if err != nil {
continue
}
if cidr.Contains(parsedIP) {
return true
}
}
return false
}
// Helper function to start cleanup routine
func (cct *ClientConnectionTracker) StartCleanupRoutine() {
go func() {
ticker := time.NewTicker(5 * time.Minute) // Clean up every 5 minutes
defer ticker.Stop()
for range ticker.C {
cct.CleanupStaleSessions()
}
}()
}