- Backend gibt tunnel_id vor, Agent uebernimmt sie - Proxy-Verbindung bidirektional: Daten vom Agent zurueck an TCP-Client - Middleware akzeptiert api_key als Query-Parameter (fuer WebSocket) - Getestet: OPNsense WebUI erreichbar ueber Tunnel
212 lines
4.6 KiB
Go
212 lines
4.6 KiB
Go
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)
|
|
}
|
|
}
|
|
} |