package ws import ( "crypto/rand" "encoding/hex" "encoding/json" "fmt" "log" "net" "net/http" "sync" "time" ) type BackendTunnel struct { ID string AgentID string TargetHost string TargetPort int ProxyPort int Listener net.Listener Active bool CreatedAt time.Time } type TunnelManager struct { hub *Hub tunnels map[string]*BackendTunnel mutex sync.RWMutex // Port-Range für dynamische Tunnel-Proxies portStart int portEnd int usedPorts map[int]bool } func NewTunnelManager(hub *Hub) *TunnelManager { return &TunnelManager{ hub: hub, tunnels: make(map[string]*BackendTunnel), portStart: 10000, portEnd: 20000, usedPorts: make(map[int]bool), } } func (tm *TunnelManager) OpenTunnel(agentID, targetHost string, targetPort int) (*BackendTunnel, error) { // Prüfen ob Agent verbunden ist if !tm.hub.IsAgentConnected(agentID) { return nil, ErrAgentNotConnected } // Tunnel-ID generieren tunnelID := tm.generateTunnelID() // Freien Port finden proxyPort, err := tm.allocatePort() if err != nil { return nil, fmt.Errorf("Proxy-Port allokieren fehlgeschlagen: %w", err) } // TCP-Listener für Proxy erstellen listener, err := net.Listen("tcp", fmt.Sprintf(":%d", proxyPort)) if err != nil { tm.releasePort(proxyPort) return nil, fmt.Errorf("Proxy-Listener erstellen fehlgeschlagen: %w", err) } // Tunnel-Objekt erstellen tunnel := &BackendTunnel{ ID: tunnelID, AgentID: agentID, TargetHost: targetHost, TargetPort: targetPort, ProxyPort: proxyPort, Listener: listener, Active: true, CreatedAt: time.Now(), } // Tunnel registrieren tm.mutex.Lock() tm.tunnels[tunnelID] = tunnel tm.mutex.Unlock() // Proxy-Server starten go tm.runTunnelProxy(tunnel) // Command an Agent senden cmd := map[string]interface{}{ "type": "command", "id": tm.generateTunnelID(), "command": "tunnel_open", "params": map[string]interface{}{ "target_host": targetHost, "target_port": targetPort, }, } cmdJSON, _ := json.Marshal(cmd) if err := tm.hub.SendToAgent(agentID, cmdJSON); err != nil { tm.CloseTunnel(tunnelID) return nil, fmt.Errorf("Tunnel-Command senden fehlgeschlagen: %w", err) } log.Printf("Tunnel %s geöffnet: Agent %s -> %s:%d (Proxy: :%d)", tunnelID, agentID, targetHost, targetPort, proxyPort) return tunnel, 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() // Listener schließen if tunnel.Listener != nil { tunnel.Listener.Close() } // Port freigeben tm.releasePort(tunnel.ProxyPort) // Command an Agent senden if tm.hub.IsAgentConnected(tunnel.AgentID) { cmd := map[string]interface{}{ "type": "command", "id": tm.generateTunnelID(), "command": "tunnel_close", "params": map[string]interface{}{ "tunnel_id": tunnelID, }, } cmdJSON, _ := json.Marshal(cmd) tm.hub.SendToAgent(tunnel.AgentID, cmdJSON) } log.Printf("Tunnel %s geschlossen", tunnelID) return nil } func (tm *TunnelManager) GetTunnel(tunnelID string) (*BackendTunnel, bool) { tm.mutex.RLock() defer tm.mutex.RUnlock() tunnel, exists := tm.tunnels[tunnelID] return tunnel, exists } func (tm *TunnelManager) GetAgentTunnels(agentID string) []*BackendTunnel { tm.mutex.RLock() defer tm.mutex.RUnlock() var tunnels []*BackendTunnel for _, tunnel := range tm.tunnels { if tunnel.AgentID == agentID { tunnels = append(tunnels, tunnel) } } return tunnels } func (tm *TunnelManager) GetActiveTunnelCount() int { tm.mutex.RLock() defer tm.mutex.RUnlock() return len(tm.tunnels) } func (tm *TunnelManager) CloseAgentTunnels(agentID string) { tm.mutex.RLock() var tunnelIDs []string for id, tunnel := range tm.tunnels { if tunnel.AgentID == agentID { tunnelIDs = append(tunnelIDs, id) } } tm.mutex.RUnlock() for _, tunnelID := range tunnelIDs { tm.CloseTunnel(tunnelID) } } func (tm *TunnelManager) runTunnelProxy(tunnel *BackendTunnel) { defer func() { if tunnel.Active { tm.CloseTunnel(tunnel.ID) } }() log.Printf("Tunnel %s: Proxy läuft auf Port %d", tunnel.ID, tunnel.ProxyPort) for tunnel.Active { conn, err := tunnel.Listener.Accept() if err != nil { if tunnel.Active { log.Printf("Tunnel %s: Accept-Fehler: %v", tunnel.ID, err) } break } // Jede eingehende Verbindung in eigener Goroutine behandeln go tm.handleProxyConnection(tunnel, conn) } } func (tm *TunnelManager) handleProxyConnection(tunnel *BackendTunnel, conn net.Conn) { defer conn.Close() log.Printf("Tunnel %s: Proxy-Verbindung von %s", tunnel.ID, conn.RemoteAddr()) // Buffer für TCP-Data buffer := make([]byte, 32768) for { n, err := conn.Read(buffer) if err != nil { if err.Error() != "EOF" { log.Printf("Tunnel %s: Proxy-Read-Fehler: %v", tunnel.ID, err) } break } if n > 0 { // Daten über WebSocket an Agent weiterleiten if err := tm.sendTunnelData(tunnel.ID, buffer[:n]); err != nil { log.Printf("Tunnel %s: Weiterleitung fehlgeschlagen: %v", tunnel.ID, err) break } } } log.Printf("Tunnel %s: Proxy-Verbindung beendet", tunnel.ID) } func (tm *TunnelManager) sendTunnelData(tunnelID string, data []byte) error { tunnel, exists := tm.GetTunnel(tunnelID) if !exists || !tunnel.Active { return fmt.Errorf("Tunnel nicht aktiv") } // Binary-Message-Format: [tunnel_id_length:1][tunnel_id][data] tunnelIDBytes := []byte(tunnelID) if len(tunnelIDBytes) > 255 { return fmt.Errorf("tunnel_id zu lang") } message := make([]byte, 1+len(tunnelIDBytes)+len(data)) message[0] = byte(len(tunnelIDBytes)) copy(message[1:], tunnelIDBytes) copy(message[1+len(tunnelIDBytes):], data) return tm.hub.SendBinaryToAgent(tunnel.AgentID, message) } func (tm *TunnelManager) ProcessBinaryMessage(agentID string, data []byte) error { // Binary-Message parsen: [tunnel_id_length:1][tunnel_id][data] if len(data) < 1 { return fmt.Errorf("Binary-Message zu kurz") } tunnelIDLen := int(data[0]) if len(data) < 1+tunnelIDLen { return fmt.Errorf("Tunnel-ID zu kurz") } tunnelID := string(data[1 : 1+tunnelIDLen]) payload := data[1+tunnelIDLen:] // Tunnel finden und Daten weiterleiten tunnel, exists := tm.GetTunnel(tunnelID) if !exists || !tunnel.Active || tunnel.AgentID != agentID { return fmt.Errorf("Tunnel %s nicht gefunden oder inaktiv", tunnelID) } // Hier würden wir die Daten an aktive Proxy-Verbindungen weiterleiten // Da das komplexer ist, implementieren wir erstmal nur das Logging log.Printf("Tunnel %s: %d Bytes von Agent erhalten", tunnelID, len(payload)) // TODO: Implement actual forwarding to active proxy connections // Das würde einen Connection-Pool pro Tunnel erfordern return nil } func (tm *TunnelManager) allocatePort() (int, error) { tm.mutex.Lock() defer tm.mutex.Unlock() for port := tm.portStart; port <= tm.portEnd; port++ { if !tm.usedPorts[port] { tm.usedPorts[port] = true return port, nil } } return 0, fmt.Errorf("keine freien Ports verfügbar") } func (tm *TunnelManager) releasePort(port int) { tm.mutex.Lock() defer tm.mutex.Unlock() delete(tm.usedPorts, port) } func (tm *TunnelManager) generateTunnelID() string { bytes := make([]byte, 8) rand.Read(bytes) return hex.EncodeToString(bytes) } // CreateHTTPProxy erstellt einen HTTP-Reverse-Proxy für einen Tunnel func (tm *TunnelManager) CreateHTTPProxy(tunnelID string, w http.ResponseWriter, r *http.Request) error { tunnel, exists := tm.GetTunnel(tunnelID) if !exists || !tunnel.Active { return fmt.Errorf("Tunnel nicht gefunden oder inaktiv") } // TODO: Implement HTTP-Reverse-Proxy durch Tunnel // Das ist komplex und erfordert HTTP-Request-Serialisierung über WebSocket http.Error(w, "HTTP-Proxy noch nicht implementiert", http.StatusNotImplemented) return nil }