package ws import ( "crypto/rand" "encoding/hex" "fmt" "io" "log" "net" "sync" "time" ) type Tunnel struct { ID string TargetHost string TargetPort int Conn net.Conn Active bool CreatedAt time.Time } type TunnelManager struct { client *Client tunnels map[string]*Tunnel mutex sync.RWMutex } func NewTunnelManager(client *Client) *TunnelManager { return &TunnelManager{ client: client, tunnels: make(map[string]*Tunnel), } } func (tm *TunnelManager) OpenTunnel(targetHost string, targetPort int, tunnelID ...string) (string, error) { // Tunnel-ID vom Backend uebernehmen oder selbst generieren id := "" if len(tunnelID) > 0 && tunnelID[0] != "" { id = tunnelID[0] } else { id = tm.generateTunnelID() } // Verbindung zum Ziel aufbauen target := fmt.Sprintf("%s:%d", targetHost, targetPort) conn, err := net.DialTimeout("tcp", target, 10*time.Second) if err != nil { return "", fmt.Errorf("Verbindung zu %s fehlgeschlagen: %w", target, err) } // Tunnel erstellen tunnel := &Tunnel{ ID: id, TargetHost: targetHost, TargetPort: targetPort, Conn: conn, Active: true, CreatedAt: time.Now(), } // Tunnel registrieren tm.mutex.Lock() tm.tunnels[id] = tunnel tm.mutex.Unlock() // Data-Forwarding starten go tm.forwardData(tunnel) return id, nil } func (tm *TunnelManager) CloseTunnel(tunnelID string) error { tm.mutex.Lock() tunnel, exists := tm.tunnels[tunnelID] if !exists { tm.mutex.Unlock() return fmt.Errorf("Tunnel %s nicht gefunden", tunnelID) } tunnel.Active = false delete(tm.tunnels, tunnelID) tm.mutex.Unlock() // Verbindung schließen if tunnel.Conn != nil { tunnel.Conn.Close() } return nil } func (tm *TunnelManager) GetActiveTunnels() []string { tm.mutex.RLock() defer tm.mutex.RUnlock() tunnels := make([]string, 0, len(tm.tunnels)) for id := range tm.tunnels { tunnels = append(tunnels, id) } return tunnels } func (tm *TunnelManager) SendDataToTunnel(tunnelID string, data []byte) error { tm.mutex.RLock() tunnel, exists := tm.tunnels[tunnelID] tm.mutex.RUnlock() if !exists || !tunnel.Active { return fmt.Errorf("Tunnel %s nicht aktiv", tunnelID) } if tunnel.Conn == nil { return fmt.Errorf("Tunnel %s hat keine Verbindung", tunnelID) } _, err := tunnel.Conn.Write(data) if err != nil { // Bei Fehler Tunnel schließen tm.CloseTunnel(tunnelID) return fmt.Errorf("Daten schreiben fehlgeschlagen: %w", err) } return nil } func (tm *TunnelManager) forwardData(tunnel *Tunnel) { defer func() { if tunnel.Active { tm.CloseTunnel(tunnel.ID) } // Event senden: Tunnel geschlossen event := Message{ Type: "event", Event: "tunnel_closed", Data: map[string]interface{}{ "tunnel_id": tunnel.ID, }, } tm.client.SendMessage(event) log.Printf("Tunnel %s: Data-Forwarding beendet", tunnel.ID) }() buffer := make([]byte, 32768) // 32KB Buffer for tunnel.Active { // Read-Timeout setzen um regelmäßig Active-Status zu prüfen tunnel.Conn.SetReadDeadline(time.Now().Add(5 * time.Second)) n, err := tunnel.Conn.Read(buffer) if err != nil { if netErr, ok := err.(net.Error); ok && netErr.Timeout() { // Timeout ist OK, einfach weiter versuchen continue } if err == io.EOF { log.Printf("Tunnel %s: Ziel-Verbindung geschlossen", tunnel.ID) } else { log.Printf("Tunnel %s: Read-Fehler: %v", tunnel.ID, err) } break } if n > 0 { // Daten über WebSocket an Backend weiterleiten data := buffer[:n] if err := tm.client.messageHandler.SendTunnelData(tunnel.ID, data); err != nil { log.Printf("Tunnel %s: WebSocket senden fehlgeschlagen: %v", tunnel.ID, err) break } } } } func (tm *TunnelManager) generateTunnelID() string { bytes := make([]byte, 8) rand.Read(bytes) return hex.EncodeToString(bytes) } // ProcessBinaryMessage verarbeitet eingehende Binary-Messages vom Backend func (tm *TunnelManager) ProcessBinaryMessage(data []byte) error { tunnelID, payload, err := ParseTunnelData(data) if err != nil { return fmt.Errorf("Binary-Message parsen: %w", err) } return tm.SendDataToTunnel(tunnelID, payload) } // CleanupInactiveTunnels schließt inaktive Tunnel (Cleanup-Task) func (tm *TunnelManager) CleanupInactiveTunnels() { tm.mutex.Lock() defer tm.mutex.Unlock() now := time.Now() for id, tunnel := range tm.tunnels { // Tunnel die länger als 1 Stunde inaktiv sind, schließen if !tunnel.Active || now.Sub(tunnel.CreatedAt) > time.Hour { tunnel.Active = false if tunnel.Conn != nil { tunnel.Conn.Close() } delete(tm.tunnels, id) log.Printf("Inaktiver Tunnel %s bereinigt", id) } } }