WebSocket-Infrastruktur: bidirektionaler Command-Kanal + TCP-Tunnel

- Agent: WS-Client mit Reconnect, Command-Handler (exec, tunnel_open/close/data)
- Backend: WS-Hub, Tunnel-Manager mit dynamischen Proxy-Ports (10000-20000)
- API: /agents/{id}/exec, /tunnel, /tunnels, /proxy/{tunnel_id}
- Binary WebSocket Messages fuer effiziente Tunnel-Daten
- gorilla/websocket (pure Go, kein CGO)
- README aktualisiert
This commit is contained in:
cynfo3000
2026-02-28 07:42:33 +01:00
parent 6120d0cffa
commit f47154787d
12 changed files with 1211 additions and 198 deletions
+6 -1
View File
@@ -11,6 +11,7 @@ import (
"github.com/cynfo/rmm-backend/db"
"github.com/cynfo/rmm-backend/models"
"github.com/cynfo/rmm-backend/ws"
)
type Handler struct {
@@ -22,13 +23,17 @@ func NewHandler(database *db.Database) *Handler {
}
// SetupRoutes - Alle API-Routes registrieren
func (h *Handler) SetupRoutes(mux *http.ServeMux) {
func (h *Handler) SetupRoutes(mux *http.ServeMux, hub *ws.Hub) {
// Bestehende Agent-Routes
mux.HandleFunc("POST /api/v1/agent/register", h.registerAgent)
mux.HandleFunc("POST /api/v1/agent/heartbeat", h.heartbeat)
mux.HandleFunc("GET /api/v1/agents", h.listAgents)
mux.HandleFunc("GET /api/v1/agents/{id}", h.getAgent)
mux.HandleFunc("GET /api/v1/agents/{id}/system", h.getSystemData)
mux.HandleFunc("DELETE /api/v1/agents/{id}", h.deleteAgent)
// WebSocket und Tunnel-Routes
h.setupTunnelRoutes(mux, hub)
}
func generateID() string {
+195
View File
@@ -0,0 +1,195 @@
package api
import (
"encoding/json"
"log"
"net/http"
"time"
"github.com/cynfo/rmm-backend/ws"
)
func (h *Handler) setupTunnelRoutes(mux *http.ServeMux, hub *ws.Hub) {
// Command-Endpoints
mux.HandleFunc("POST /api/v1/agents/{id}/exec", h.executeCommand(hub))
mux.HandleFunc("POST /api/v1/agents/{id}/tunnel", h.openTunnel(hub))
mux.HandleFunc("DELETE /api/v1/agents/{id}/tunnel/{tunnel_id}", h.closeTunnel(hub))
mux.HandleFunc("GET /api/v1/agents/{id}/tunnels", h.listTunnels(hub))
// HTTP-Proxy durch Tunnel
mux.HandleFunc("GET /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
mux.HandleFunc("POST /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
mux.HandleFunc("PUT /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
mux.HandleFunc("DELETE /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
// WebSocket-Endpoint
mux.HandleFunc("GET /api/v1/agent/ws", ws.NewWebSocketHandler(hub))
// Hub-Status
mux.HandleFunc("GET /api/v1/hub/status", h.hubStatus(hub))
}
func (h *Handler) executeCommand(hub *ws.Hub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
agentID := r.PathValue("id")
// Request parsen
var req struct {
Command string `json:"command"`
Timeout int `json:"timeout,omitempty"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "Ungültige Anfrage")
return
}
if req.Command == "" {
writeError(w, http.StatusBadRequest, "Parameter 'command' erforderlich")
return
}
if req.Timeout == 0 {
req.Timeout = 30
}
// Prüfen ob Agent verbunden ist
if !hub.IsAgentConnected(agentID) {
writeError(w, http.StatusServiceUnavailable, "Agent nicht verbunden")
return
}
// Command-Message erstellen
cmd := map[string]interface{}{
"type": "command",
"id": generateID(),
"command": "exec",
"params": map[string]interface{}{
"command": req.Command,
"timeout": req.Timeout,
},
}
cmdJSON, _ := json.Marshal(cmd)
// Command an Agent senden
if err := hub.SendToAgent(agentID, cmdJSON); err != nil {
log.Printf("Command senden fehlgeschlagen: %v", err)
writeError(w, http.StatusInternalServerError, "Command senden fehlgeschlagen")
return
}
// Response zurückgeben
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "Command gesendet",
"command_id": cmd["id"],
})
}
}
func (h *Handler) openTunnel(hub *ws.Hub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
agentID := r.PathValue("id")
// Request parsen
var req struct {
TargetHost string `json:"target_host"`
TargetPort int `json:"target_port"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "Ungültige Anfrage")
return
}
if req.TargetHost == "" || req.TargetPort <= 0 {
writeError(w, http.StatusBadRequest, "target_host und target_port erforderlich")
return
}
// Tunnel öffnen
tunnel, err := hub.GetTunnelManager().OpenTunnel(agentID, req.TargetHost, req.TargetPort)
if err != nil {
log.Printf("Tunnel öffnen fehlgeschlagen: %v", err)
writeError(w, http.StatusInternalServerError, err.Error())
return
}
// Response
writeJSON(w, http.StatusCreated, map[string]interface{}{
"tunnel_id": tunnel.ID,
"proxy_port": tunnel.ProxyPort,
"target_host": tunnel.TargetHost,
"target_port": tunnel.TargetPort,
"created_at": tunnel.CreatedAt,
})
}
}
func (h *Handler) closeTunnel(hub *ws.Hub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tunnelID := r.PathValue("tunnel_id")
// Tunnel schließen
if err := hub.GetTunnelManager().CloseTunnel(tunnelID); err != nil {
writeError(w, http.StatusNotFound, err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]string{
"message": "Tunnel geschlossen",
})
}
}
func (h *Handler) listTunnels(hub *ws.Hub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
agentID := r.PathValue("id")
// Tunnel für Agent abrufen
tunnels := hub.GetTunnelManager().GetAgentTunnels(agentID)
// Response formatieren
result := make([]map[string]interface{}, len(tunnels))
for i, tunnel := range tunnels {
result[i] = map[string]interface{}{
"tunnel_id": tunnel.ID,
"proxy_port": tunnel.ProxyPort,
"target_host": tunnel.TargetHost,
"target_port": tunnel.TargetPort,
"active": tunnel.Active,
"created_at": tunnel.CreatedAt,
}
}
writeJSON(w, http.StatusOK, result)
}
}
func (h *Handler) httpProxy(hub *ws.Hub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tunnelID := r.PathValue("tunnel_id")
// HTTP-Proxy durch Tunnel
if err := hub.GetTunnelManager().CreateHTTPProxy(tunnelID, w, r); err != nil {
writeError(w, http.StatusNotFound, err.Error())
return
}
}
}
func (h *Handler) hubStatus(hub *ws.Hub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
stats := hub.GetAgentStats()
connectedAgents := hub.GetConnectedAgents()
response := map[string]interface{}{
"status": "running",
"connected_agents": connectedAgents,
"stats": stats,
"timestamp": time.Now(),
}
writeJSON(w, http.StatusOK, response)
}
}
+1 -1
View File
@@ -3,6 +3,7 @@ module github.com/cynfo/rmm-backend
go 1.22
require (
github.com/gorilla/websocket v1.5.3
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.29.1
)
@@ -10,7 +11,6 @@ require (
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
+6 -1
View File
@@ -19,6 +19,7 @@ import (
"github.com/cynfo/rmm-backend/api"
"github.com/cynfo/rmm-backend/config"
"github.com/cynfo/rmm-backend/db"
"github.com/cynfo/rmm-backend/ws"
)
func main() {
@@ -44,10 +45,14 @@ func main() {
}
defer database.Close()
// WebSocket Hub starten
hub := ws.NewHub()
go hub.Run()
// Router aufbauen
mux := http.NewServeMux()
handler := api.NewHandler(database)
handler.SetupRoutes(mux)
handler.SetupRoutes(mux, hub)
// Middleware-Chain: Logging -> Auth -> Handler
var chain http.Handler = mux
+153
View File
@@ -0,0 +1,153 @@
package ws
import (
"errors"
"log"
"net/http"
"time"
"github.com/gorilla/websocket"
)
var (
ErrAgentNotConnected = errors.New("Agent nicht verbunden")
ErrAgentSendBlocked = errors.New("Agent Send-Channel blockiert")
)
type Connection struct {
AgentID string
Conn *websocket.Conn
Send chan []byte
BinarySend chan []byte
Hub *Hub
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
// Hier könnte Origin-Checking implementiert werden
return true
},
}
func NewWebSocketHandler(hub *Hub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
handleWebSocket(hub, w, r)
}
}
func handleWebSocket(hub *Hub, w http.ResponseWriter, r *http.Request) {
// Query-Parameter extrahieren
agentID := r.URL.Query().Get("agent_id")
apiKey := r.URL.Query().Get("api_key")
if agentID == "" || apiKey == "" {
http.Error(w, "agent_id und api_key erforderlich", http.StatusBadRequest)
return
}
// API-Key validieren (sollte bereits durch Middleware geschehen)
// Hier könnten zusätzliche Agent-spezifische Prüfungen stattfinden
// WebSocket-Upgrade
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("WebSocket-Upgrade fehlgeschlagen: %v", err)
return
}
// Connection-Objekt erstellen
connection := &Connection{
AgentID: agentID,
Conn: conn,
Send: make(chan []byte, 256),
BinarySend: make(chan []byte, 256),
Hub: hub,
}
// Verbindung bei Hub registrieren
hub.RegisterAgent(agentID, connection)
// Goroutines für Read/Write starten
go connection.writePump()
go connection.readPump()
}
func (c *Connection) readPump() {
defer func() {
c.Hub.UnregisterAgent(c.AgentID, c)
c.Conn.Close()
}()
// Read-Timeouts setzen
c.Conn.SetReadLimit(512 * 1024) // 512KB max message size
c.Conn.SetReadDeadline(time.Now().Add(60 * time.Second))
c.Conn.SetPongHandler(func(string) error {
c.Conn.SetReadDeadline(time.Now().Add(60 * time.Second))
return nil
})
for {
messageType, message, err := c.Conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("Agent %s: WebSocket-Fehler: %v", c.AgentID, err)
}
break
}
// Message an Hub weiterleiten
if err := c.Hub.ProcessAgentMessage(c.AgentID, messageType, message); err != nil {
log.Printf("Agent %s: Message-Verarbeitung fehlgeschlagen: %v", c.AgentID, err)
}
}
}
func (c *Connection) writePump() {
ticker := time.NewTicker(54 * time.Second) // Ping alle 54s
defer func() {
ticker.Stop()
c.Conn.Close()
}()
for {
select {
case message, ok := <-c.Send:
c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
if !ok {
c.Conn.WriteMessage(websocket.CloseMessage, nil)
return
}
if err := c.Conn.WriteMessage(websocket.TextMessage, message); err != nil {
log.Printf("Agent %s: Write-Fehler: %v", c.AgentID, err)
return
}
case binaryMessage, ok := <-c.BinarySend:
c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
if !ok {
c.Conn.WriteMessage(websocket.CloseMessage, nil)
return
}
if err := c.Conn.WriteMessage(websocket.BinaryMessage, binaryMessage); err != nil {
log.Printf("Agent %s: Binary-Write-Fehler: %v", c.AgentID, err)
return
}
case <-ticker.C:
c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
log.Printf("Agent %s: Ping-Fehler: %v", c.AgentID, err)
return
}
}
}
}
func (c *Connection) Close() {
close(c.Send)
close(c.BinarySend)
}
+238
View File
@@ -0,0 +1,238 @@
package ws
import (
"log"
"sync"
"time"
)
type AgentConnection struct {
AgentID string
Connection *Connection
LastSeen time.Time
}
type Hub struct {
agents map[string]*AgentConnection
agentsMux sync.RWMutex
register chan *AgentConnection
unregister chan *AgentConnection
broadcast chan []byte
tunnelManager *TunnelManager
}
func NewHub() *Hub {
hub := &Hub{
agents: make(map[string]*AgentConnection),
register: make(chan *AgentConnection),
unregister: make(chan *AgentConnection),
broadcast: make(chan []byte, 256),
}
hub.tunnelManager = NewTunnelManager(hub)
return hub
}
func (h *Hub) Run() {
log.Println("WebSocket Hub startet...")
// Cleanup-Timer für inaktive Verbindungen
cleanupTicker := time.NewTicker(5 * time.Minute)
defer cleanupTicker.Stop()
for {
select {
case agent := <-h.register:
h.registerAgent(agent)
case agent := <-h.unregister:
h.unregisterAgent(agent)
case message := <-h.broadcast:
h.broadcastMessage(message)
case <-cleanupTicker.C:
h.cleanupInactiveAgents()
}
}
}
func (h *Hub) registerAgent(agent *AgentConnection) {
h.agentsMux.Lock()
defer h.agentsMux.Unlock()
// Bestehende Verbindung schließen falls vorhanden
if existing, ok := h.agents[agent.AgentID]; ok {
log.Printf("Agent %s: Ersetzt bestehende Verbindung", agent.AgentID)
existing.Connection.Close()
}
h.agents[agent.AgentID] = agent
agent.LastSeen = time.Now()
log.Printf("Agent %s: WebSocket-Verbindung registriert", agent.AgentID)
}
func (h *Hub) unregisterAgent(agent *AgentConnection) {
h.agentsMux.Lock()
defer h.agentsMux.Unlock()
if existing, ok := h.agents[agent.AgentID]; ok && existing.Connection == agent.Connection {
delete(h.agents, agent.AgentID)
agent.Connection.Close()
log.Printf("Agent %s: WebSocket-Verbindung entfernt", agent.AgentID)
// Alle Tunnel für diesen Agent schließen
h.tunnelManager.CloseAgentTunnels(agent.AgentID)
}
}
func (h *Hub) broadcastMessage(message []byte) {
h.agentsMux.RLock()
defer h.agentsMux.RUnlock()
for agentID, agent := range h.agents {
select {
case agent.Connection.Send <- message:
default:
// Agent kann keine Nachrichten mehr empfangen
log.Printf("Agent %s: Send-Channel blockiert, trenne Verbindung", agentID)
close(agent.Connection.Send)
delete(h.agents, agentID)
}
}
}
func (h *Hub) SendToAgent(agentID string, message []byte) error {
h.agentsMux.RLock()
agent, exists := h.agents[agentID]
h.agentsMux.RUnlock()
if !exists {
return ErrAgentNotConnected
}
agent.LastSeen = time.Now()
select {
case agent.Connection.Send <- message:
return nil
default:
return ErrAgentSendBlocked
}
}
func (h *Hub) SendBinaryToAgent(agentID string, data []byte) error {
h.agentsMux.RLock()
agent, exists := h.agents[agentID]
h.agentsMux.RUnlock()
if !exists {
return ErrAgentNotConnected
}
agent.LastSeen = time.Now()
select {
case agent.Connection.BinarySend <- data:
return nil
default:
return ErrAgentSendBlocked
}
}
func (h *Hub) IsAgentConnected(agentID string) bool {
h.agentsMux.RLock()
defer h.agentsMux.RUnlock()
_, exists := h.agents[agentID]
return exists
}
func (h *Hub) GetConnectedAgents() []string {
h.agentsMux.RLock()
defer h.agentsMux.RUnlock()
agents := make([]string, 0, len(h.agents))
for agentID := range h.agents {
agents = append(agents, agentID)
}
return agents
}
func (h *Hub) GetAgentStats() map[string]interface{} {
h.agentsMux.RLock()
defer h.agentsMux.RUnlock()
stats := map[string]interface{}{
"connected_agents": len(h.agents),
"active_tunnels": h.tunnelManager.GetActiveTunnelCount(),
}
return stats
}
func (h *Hub) cleanupInactiveAgents() {
h.agentsMux.Lock()
defer h.agentsMux.Unlock()
timeout := 10 * time.Minute
now := time.Now()
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)
h.tunnelManager.CloseAgentTunnels(agentID)
}
}
}
// GetTunnelManager gibt den TunnelManager zurück (für API-Handler)
func (h *Hub) GetTunnelManager() *TunnelManager {
return h.tunnelManager
}
// RegisterAgent registriert eine neue Agent-Verbindung
func (h *Hub) RegisterAgent(agentID string, conn *Connection) {
agent := &AgentConnection{
AgentID: agentID,
Connection: conn,
LastSeen: time.Now(),
}
h.register <- agent
}
// UnregisterAgent entfernt eine Agent-Verbindung
func (h *Hub) UnregisterAgent(agentID string, conn *Connection) {
agent := &AgentConnection{
AgentID: agentID,
Connection: conn,
}
h.unregister <- agent
}
// ProcessAgentMessage verarbeitet eingehende Nachrichten von Agents
func (h *Hub) ProcessAgentMessage(agentID string, messageType int, data []byte) error {
h.agentsMux.RLock()
agent, exists := h.agents[agentID]
h.agentsMux.RUnlock()
if exists {
agent.LastSeen = time.Now()
}
// Binary Messages sind normalerweise Tunnel-Daten
if messageType == 2 { // websocket.BinaryMessage
return h.tunnelManager.ProcessBinaryMessage(agentID, data)
}
// Text Messages sind JSON-Commands/Responses/Events
return nil
}
+336
View File
@@ -0,0 +1,336 @@
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
}