Meilenstein 2: Session-basierte TCP-Tunnel
- Tunnel unterstuetzen mehrere gleichzeitige TCP-Verbindungen - Jede Proxy-Verbindung bekommt eigene Session-ID - Agent: Lazy-Connect (Ziel-Verbindung erst bei Datentransfer) - Backend: Ready-Handshake (wartet auf Agent-Bestaetigung) - Binary WebSocket Messages mit Session-ID statt Tunnel-ID - Connection.Close() Race-Condition behoben (sync.Once) - SendToAgent/SendBinaryToAgent mit Timeout statt Blocking - Hub verarbeitet Agent-Responses (Session-Confirm) - README komplett aktualisiert
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -20,6 +21,7 @@ type Connection struct {
|
||||
Send chan []byte
|
||||
BinarySend chan []byte
|
||||
Hub *Hub
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
@@ -77,7 +79,6 @@ func handleWebSocket(hub *Hub, w http.ResponseWriter, r *http.Request) {
|
||||
func (c *Connection) readPump() {
|
||||
defer func() {
|
||||
c.Hub.UnregisterAgent(c.AgentID, c)
|
||||
c.Conn.Close()
|
||||
}()
|
||||
|
||||
// Read-Timeouts setzen
|
||||
@@ -108,7 +109,7 @@ func (c *Connection) writePump() {
|
||||
ticker := time.NewTicker(54 * time.Second) // Ping alle 54s
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
c.Conn.Close()
|
||||
c.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
@@ -148,6 +149,9 @@ func (c *Connection) writePump() {
|
||||
}
|
||||
|
||||
func (c *Connection) Close() {
|
||||
close(c.Send)
|
||||
close(c.BinarySend)
|
||||
c.closeOnce.Do(func() {
|
||||
close(c.Send)
|
||||
close(c.BinarySend)
|
||||
c.Conn.Close()
|
||||
})
|
||||
}
|
||||
+23
-5
@@ -1,6 +1,7 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -81,7 +82,7 @@ func (h *Hub) unregisterAgent(agent *AgentConnection) {
|
||||
|
||||
if existing, ok := h.agents[agent.AgentID]; ok && existing.Connection == agent.Connection {
|
||||
delete(h.agents, agent.AgentID)
|
||||
agent.Connection.Close()
|
||||
existing.Connection.Close()
|
||||
log.Printf("Agent %s: WebSocket-Verbindung entfernt", agent.AgentID)
|
||||
|
||||
// Alle Tunnel für diesen Agent schließen
|
||||
@@ -119,7 +120,7 @@ func (h *Hub) SendToAgent(agentID string, message []byte) error {
|
||||
select {
|
||||
case agent.Connection.Send <- message:
|
||||
return nil
|
||||
default:
|
||||
case <-time.After(5 * time.Second):
|
||||
return ErrAgentSendBlocked
|
||||
}
|
||||
}
|
||||
@@ -138,7 +139,7 @@ func (h *Hub) SendBinaryToAgent(agentID string, data []byte) error {
|
||||
select {
|
||||
case agent.Connection.BinarySend <- data:
|
||||
return nil
|
||||
default:
|
||||
case <-time.After(5 * time.Second):
|
||||
return ErrAgentSendBlocked
|
||||
}
|
||||
}
|
||||
@@ -185,8 +186,8 @@ func (h *Hub) cleanupInactiveAgents() {
|
||||
for agentID, agent := range h.agents {
|
||||
if now.Sub(agent.LastSeen) > timeout {
|
||||
log.Printf("Agent %s: Timeout, trenne Verbindung", agentID)
|
||||
agent.Connection.Close()
|
||||
delete(h.agents, agentID)
|
||||
agent.Connection.Close()
|
||||
h.tunnelManager.CloseAgentTunnels(agentID)
|
||||
}
|
||||
}
|
||||
@@ -233,6 +234,23 @@ func (h *Hub) ProcessAgentMessage(agentID string, messageType int, data []byte)
|
||||
return h.tunnelManager.ProcessBinaryMessage(agentID, data)
|
||||
}
|
||||
|
||||
// Text Messages sind JSON-Commands/Responses/Events
|
||||
// Text Messages sind JSON-Responses/Events vom Agent
|
||||
var msg struct {
|
||||
Type string `json:"type"`
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &msg); err != nil {
|
||||
return nil // Ignorieren wenn nicht parsebar
|
||||
}
|
||||
|
||||
// Tunnel-Connect Responses verarbeiten
|
||||
if msg.Type == "response" && msg.Status == "ok" && msg.Data != nil {
|
||||
if sessionID, ok := msg.Data["session_id"].(string); ok {
|
||||
h.tunnelManager.ConfirmSession(sessionID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+228
-160
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -12,6 +13,17 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TunnelSession repraesentiert eine einzelne TCP-Verbindung durch einen Tunnel
|
||||
type TunnelSession struct {
|
||||
ID string
|
||||
TunnelID string
|
||||
ProxyConn net.Conn
|
||||
Active bool
|
||||
Ready chan struct{} // Wird geschlossen wenn Agent die Session bestaetigt hat
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// BackendTunnel repraesentiert einen offenen Tunnel (Proxy-Port -> Agent -> Ziel)
|
||||
type BackendTunnel struct {
|
||||
ID string
|
||||
AgentID string
|
||||
@@ -21,17 +33,20 @@ type BackendTunnel struct {
|
||||
Listener net.Listener
|
||||
Active bool
|
||||
CreatedAt time.Time
|
||||
// Aktive Proxy-Verbindungen (fuer Rueckkanal)
|
||||
proxyConn net.Conn
|
||||
proxyMutex sync.Mutex
|
||||
Sessions map[string]*TunnelSession
|
||||
sessMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// TunnelManager verwaltet alle Tunnel und Sessions
|
||||
type TunnelManager struct {
|
||||
hub *Hub
|
||||
tunnels map[string]*BackendTunnel
|
||||
mutex sync.RWMutex
|
||||
|
||||
// Port-Range für dynamische Tunnel-Proxies
|
||||
|
||||
// Session-Index fuer schnellen Lookup bei Binary-Messages
|
||||
sessions map[string]*TunnelSession
|
||||
sessMutex sync.RWMutex
|
||||
|
||||
portStart int
|
||||
portEnd int
|
||||
usedPorts map[int]bool
|
||||
@@ -41,6 +56,7 @@ func NewTunnelManager(hub *Hub) *TunnelManager {
|
||||
return &TunnelManager{
|
||||
hub: hub,
|
||||
tunnels: make(map[string]*BackendTunnel),
|
||||
sessions: make(map[string]*TunnelSession),
|
||||
portStart: 10000,
|
||||
portEnd: 20000,
|
||||
usedPorts: make(map[int]bool),
|
||||
@@ -48,28 +64,23 @@ func NewTunnelManager(hub *Hub) *TunnelManager {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
tunnelID := tm.generateID()
|
||||
|
||||
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,
|
||||
@@ -79,37 +90,19 @@ func (tm *TunnelManager) OpenTunnel(agentID, targetHost string, targetPort int)
|
||||
Listener: listener,
|
||||
Active: true,
|
||||
CreatedAt: time.Now(),
|
||||
Sessions: make(map[string]*TunnelSession),
|
||||
}
|
||||
|
||||
// Tunnel registrieren
|
||||
|
||||
tm.mutex.Lock()
|
||||
tm.tunnels[tunnelID] = tunnel
|
||||
tm.mutex.Unlock()
|
||||
|
||||
// Proxy-Server starten
|
||||
go tm.runTunnelProxy(tunnel)
|
||||
|
||||
// Command an Agent senden — tunnel_id vorgeben damit Backend und Agent dieselbe ID nutzen
|
||||
cmd := map[string]interface{}{
|
||||
"type": "command",
|
||||
"id": tm.generateTunnelID(),
|
||||
"command": "tunnel_open",
|
||||
"params": map[string]interface{}{
|
||||
"tunnel_id": tunnelID,
|
||||
"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)",
|
||||
|
||||
// Proxy-Accept-Loop starten
|
||||
go tm.runProxyAccept(tunnel)
|
||||
|
||||
log.Printf("Tunnel %s geoeffnet: Agent %s -> %s:%d (Proxy: :%d)",
|
||||
tunnelID, agentID, targetHost, targetPort, proxyPort)
|
||||
|
||||
|
||||
return tunnel, nil
|
||||
}
|
||||
|
||||
@@ -120,34 +113,36 @@ func (tm *TunnelManager) CloseTunnel(tunnelID string) error {
|
||||
tm.mutex.Unlock()
|
||||
return fmt.Errorf("Tunnel %s nicht gefunden", tunnelID)
|
||||
}
|
||||
|
||||
|
||||
tunnel.Active = false
|
||||
delete(tm.tunnels, tunnelID)
|
||||
tm.mutex.Unlock()
|
||||
|
||||
// Listener schließen
|
||||
|
||||
// Listener schliessen
|
||||
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,
|
||||
},
|
||||
|
||||
// Alle Sessions schliessen
|
||||
tunnel.sessMutex.Lock()
|
||||
for sessID, sess := range tunnel.Sessions {
|
||||
sess.Active = false
|
||||
if sess.ProxyConn != nil {
|
||||
sess.ProxyConn.Close()
|
||||
}
|
||||
|
||||
cmdJSON, _ := json.Marshal(cmd)
|
||||
tm.hub.SendToAgent(tunnel.AgentID, cmdJSON)
|
||||
// Session aus globalem Index entfernen
|
||||
tm.sessMutex.Lock()
|
||||
delete(tm.sessions, sessID)
|
||||
tm.sessMutex.Unlock()
|
||||
|
||||
// Agent informieren
|
||||
tm.sendSessionDisconnect(tunnel.AgentID, sessID)
|
||||
}
|
||||
|
||||
tunnel.Sessions = make(map[string]*TunnelSession)
|
||||
tunnel.sessMutex.Unlock()
|
||||
|
||||
tm.releasePort(tunnel.ProxyPort)
|
||||
|
||||
log.Printf("Tunnel %s geschlossen", tunnelID)
|
||||
return nil
|
||||
}
|
||||
@@ -155,7 +150,6 @@ func (tm *TunnelManager) CloseTunnel(tunnelID string) error {
|
||||
func (tm *TunnelManager) GetTunnel(tunnelID string) (*BackendTunnel, bool) {
|
||||
tm.mutex.RLock()
|
||||
defer tm.mutex.RUnlock()
|
||||
|
||||
tunnel, exists := tm.tunnels[tunnelID]
|
||||
return tunnel, exists
|
||||
}
|
||||
@@ -163,21 +157,19 @@ func (tm *TunnelManager) GetTunnel(tunnelID string) (*BackendTunnel, bool) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -190,21 +182,22 @@ func (tm *TunnelManager) CloseAgentTunnels(agentID string) {
|
||||
}
|
||||
}
|
||||
tm.mutex.RUnlock()
|
||||
|
||||
|
||||
for _, tunnelID := range tunnelIDs {
|
||||
tm.CloseTunnel(tunnelID)
|
||||
}
|
||||
}
|
||||
|
||||
func (tm *TunnelManager) runTunnelProxy(tunnel *BackendTunnel) {
|
||||
// runProxyAccept nimmt eingehende TCP-Verbindungen am Proxy-Port an
|
||||
func (tm *TunnelManager) runProxyAccept(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)
|
||||
|
||||
|
||||
log.Printf("Tunnel %s: Proxy lauscht auf Port %d", tunnel.ID, tunnel.ProxyPort)
|
||||
|
||||
for tunnel.Active {
|
||||
conn, err := tunnel.Listener.Accept()
|
||||
if err != nil {
|
||||
@@ -213,145 +206,220 @@ func (tm *TunnelManager) runTunnelProxy(tunnel *BackendTunnel) {
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Jede eingehende Verbindung in eigener Goroutine behandeln
|
||||
go tm.handleProxyConnection(tunnel, conn)
|
||||
|
||||
// Neue Session fuer diese TCP-Verbindung
|
||||
go tm.handleNewSession(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())
|
||||
|
||||
// Aktive Proxy-Verbindung registrieren (fuer Rueckkanal vom Agent)
|
||||
tunnel.proxyMutex.Lock()
|
||||
tunnel.proxyConn = conn
|
||||
tunnel.proxyMutex.Unlock()
|
||||
|
||||
// handleNewSession erstellt eine neue Session fuer eine eingehende Proxy-Verbindung
|
||||
func (tm *TunnelManager) handleNewSession(tunnel *BackendTunnel, proxyConn net.Conn) {
|
||||
sessionID := tm.generateID()
|
||||
|
||||
session := &TunnelSession{
|
||||
ID: sessionID,
|
||||
TunnelID: tunnel.ID,
|
||||
ProxyConn: proxyConn,
|
||||
Active: true,
|
||||
Ready: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Session registrieren
|
||||
tunnel.sessMutex.Lock()
|
||||
tunnel.Sessions[sessionID] = session
|
||||
tunnel.sessMutex.Unlock()
|
||||
|
||||
tm.sessMutex.Lock()
|
||||
tm.sessions[sessionID] = session
|
||||
tm.sessMutex.Unlock()
|
||||
|
||||
log.Printf("Tunnel %s: Neue Session %s von %s", tunnel.ID, sessionID, proxyConn.RemoteAddr())
|
||||
|
||||
// Agent auffordern, Ziel-Verbindung fuer diese Session zu oeffnen
|
||||
cmd := map[string]interface{}{
|
||||
"type": "command",
|
||||
"id": tm.generateID(),
|
||||
"command": "tunnel_connect",
|
||||
"params": map[string]interface{}{
|
||||
"session_id": sessionID,
|
||||
"tunnel_id": tunnel.ID,
|
||||
"target_host": tunnel.TargetHost,
|
||||
"target_port": tunnel.TargetPort,
|
||||
},
|
||||
}
|
||||
|
||||
cmdJSON, _ := json.Marshal(cmd)
|
||||
if err := tm.hub.SendToAgent(tunnel.AgentID, cmdJSON); err != nil {
|
||||
log.Printf("Session %s: tunnel_connect senden fehlgeschlagen: %v", sessionID, err)
|
||||
tm.cleanupSession(tunnel, sessionID)
|
||||
proxyConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Warten bis Agent die Session bestaetigt hat (max 10s)
|
||||
select {
|
||||
case <-session.Ready:
|
||||
log.Printf("Session %s: Agent bereit, starte Forwarding", sessionID)
|
||||
case <-time.After(10 * time.Second):
|
||||
log.Printf("Session %s: Timeout beim Warten auf Agent", sessionID)
|
||||
tm.cleanupSession(tunnel, sessionID)
|
||||
proxyConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Proxy -> Agent Data-Forwarding (liest von Proxy-Conn, schickt als Binary an Agent)
|
||||
defer func() {
|
||||
tunnel.proxyMutex.Lock()
|
||||
tunnel.proxyConn = nil
|
||||
tunnel.proxyMutex.Unlock()
|
||||
log.Printf("Tunnel %s: Proxy-Verbindung beendet", tunnel.ID)
|
||||
tm.cleanupSession(tunnel, sessionID)
|
||||
proxyConn.Close()
|
||||
// Agent informieren dass Session beendet ist
|
||||
tm.sendSessionDisconnect(tunnel.AgentID, sessionID)
|
||||
log.Printf("Tunnel %s: Session %s beendet", tunnel.ID, sessionID)
|
||||
}()
|
||||
|
||||
// Buffer fuer TCP-Data
|
||||
|
||||
buffer := make([]byte, 32768)
|
||||
|
||||
for tunnel.Active {
|
||||
n, err := conn.Read(buffer)
|
||||
for session.Active && tunnel.Active {
|
||||
n, err := proxyConn.Read(buffer)
|
||||
if err != nil {
|
||||
if err.Error() != "EOF" && tunnel.Active {
|
||||
log.Printf("Tunnel %s: Proxy-Read-Fehler: %v", tunnel.ID, err)
|
||||
if err != io.EOF && session.Active {
|
||||
log.Printf("Session %s: Proxy-Read-Fehler: %v", sessionID, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
if n > 0 {
|
||||
// Daten ueber WebSocket an Agent weiterleiten
|
||||
if err := tm.sendTunnelData(tunnel.ID, buffer[:n]); err != nil {
|
||||
log.Printf("Tunnel %s: Weiterleitung fehlgeschlagen: %v", tunnel.ID, err)
|
||||
if err := tm.sendSessionData(tunnel.AgentID, sessionID, buffer[:n]); err != nil {
|
||||
log.Printf("Session %s: Weiterleitung fehlgeschlagen: %v", sessionID, err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// ProcessBinaryMessage verarbeitet eingehende Binary-Messages vom Agent (Ziel -> Proxy)
|
||||
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")
|
||||
|
||||
idLen := int(data[0])
|
||||
if len(data) < 1+idLen {
|
||||
return fmt.Errorf("Session-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)
|
||||
|
||||
sessionID := string(data[1 : 1+idLen])
|
||||
payload := data[1+idLen:]
|
||||
|
||||
// Session finden
|
||||
tm.sessMutex.RLock()
|
||||
session, exists := tm.sessions[sessionID]
|
||||
tm.sessMutex.RUnlock()
|
||||
|
||||
if !exists || !session.Active {
|
||||
return fmt.Errorf("Session %s nicht gefunden oder inaktiv", sessionID)
|
||||
}
|
||||
|
||||
// Daten an aktive Proxy-Verbindung weiterleiten
|
||||
tunnel.proxyMutex.Lock()
|
||||
conn := tunnel.proxyConn
|
||||
tunnel.proxyMutex.Unlock()
|
||||
|
||||
if conn == nil {
|
||||
return fmt.Errorf("Tunnel %s: keine aktive Proxy-Verbindung", tunnelID)
|
||||
|
||||
// Daten an Proxy-Verbindung schreiben
|
||||
session.mutex.Lock()
|
||||
defer session.mutex.Unlock()
|
||||
|
||||
if session.ProxyConn == nil {
|
||||
return fmt.Errorf("Session %s: keine Proxy-Verbindung", sessionID)
|
||||
}
|
||||
|
||||
_, writeErr := conn.Write(payload)
|
||||
if writeErr != nil {
|
||||
return fmt.Errorf("Tunnel %s: Proxy-Write fehlgeschlagen: %w", tunnelID, writeErr)
|
||||
|
||||
_, err := session.ProxyConn.Write(payload)
|
||||
return err
|
||||
}
|
||||
|
||||
// sendSessionData schickt Daten als Binary-Message an den Agent
|
||||
func (tm *TunnelManager) sendSessionData(agentID, sessionID string, data []byte) error {
|
||||
idBytes := []byte(sessionID)
|
||||
if len(idBytes) > 255 {
|
||||
return fmt.Errorf("session_id zu lang")
|
||||
}
|
||||
|
||||
message := make([]byte, 1+len(idBytes)+len(data))
|
||||
message[0] = byte(len(idBytes))
|
||||
copy(message[1:], idBytes)
|
||||
copy(message[1+len(idBytes):], data)
|
||||
|
||||
return tm.hub.SendBinaryToAgent(agentID, message)
|
||||
}
|
||||
|
||||
// sendSessionDisconnect informiert den Agent dass eine Session beendet ist
|
||||
func (tm *TunnelManager) sendSessionDisconnect(agentID, sessionID string) {
|
||||
if !tm.hub.IsAgentConnected(agentID) {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := map[string]interface{}{
|
||||
"type": "command",
|
||||
"id": tm.generateID(),
|
||||
"command": "tunnel_disconnect",
|
||||
"params": map[string]interface{}{
|
||||
"session_id": sessionID,
|
||||
},
|
||||
}
|
||||
|
||||
cmdJSON, _ := json.Marshal(cmd)
|
||||
tm.hub.SendToAgent(agentID, cmdJSON)
|
||||
}
|
||||
|
||||
func (tm *TunnelManager) cleanupSession(tunnel *BackendTunnel, sessionID string) {
|
||||
tunnel.sessMutex.Lock()
|
||||
if sess, ok := tunnel.Sessions[sessionID]; ok {
|
||||
sess.Active = false
|
||||
delete(tunnel.Sessions, sessionID)
|
||||
}
|
||||
tunnel.sessMutex.Unlock()
|
||||
|
||||
tm.sessMutex.Lock()
|
||||
delete(tm.sessions, sessionID)
|
||||
tm.sessMutex.Unlock()
|
||||
}
|
||||
|
||||
// ConfirmSession markiert eine Session als bereit (Agent hat Ziel-Verbindung hergestellt)
|
||||
func (tm *TunnelManager) ConfirmSession(sessionID string) {
|
||||
tm.sessMutex.RLock()
|
||||
session, exists := tm.sessions[sessionID]
|
||||
tm.sessMutex.RUnlock()
|
||||
|
||||
if exists && session.Active {
|
||||
select {
|
||||
case <-session.Ready:
|
||||
// Bereits geschlossen
|
||||
default:
|
||||
close(session.Ready)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
return 0, fmt.Errorf("keine freien Ports verfuegbar")
|
||||
}
|
||||
|
||||
func (tm *TunnelManager) releasePort(port int) {
|
||||
tm.mutex.Lock()
|
||||
defer tm.mutex.Unlock()
|
||||
|
||||
delete(tm.usedPorts, port)
|
||||
}
|
||||
|
||||
func (tm *TunnelManager) generateTunnelID() string {
|
||||
func (tm *TunnelManager) generateID() string {
|
||||
bytes := make([]byte, 8)
|
||||
rand.Read(bytes)
|
||||
return hex.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
// CreateHTTPProxy erstellt einen HTTP-Reverse-Proxy für einen Tunnel
|
||||
// CreateHTTPProxy - HTTP-Reverse-Proxy (noch nicht implementiert)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user