rmm2/agent/ws/handler.go
cynfo3000 68e712b3a3 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
2026-02-28 08:53:10 +01:00

153 lines
3.6 KiB
Go

package ws
import (
"context"
"fmt"
"log"
"os/exec"
"strconv"
"time"
)
type Handler struct {
client *Client
}
func NewHandler(client *Client) *Handler {
return &Handler{client: client}
}
func (h *Handler) HandleCommand(msg Message) {
log.Printf("Command erhalten: %s (ID: %s)", msg.Command, msg.ID)
var response Message
response.Type = "response"
response.ID = msg.ID
switch msg.Command {
case "exec":
response = h.handleExec(msg)
case "tunnel_open":
// Legacy: wird nicht mehr verwendet, aber fuer Kompatibilitaet
response.Status = "ok"
response.Data = map[string]interface{}{"message": "tunnel_open deprecated, use tunnel_connect"}
case "tunnel_connect":
response = h.handleTunnelConnect(msg)
case "tunnel_disconnect":
h.handleTunnelDisconnect(msg)
return // Keine Response noetig
case "tunnel_close":
// Legacy: alle Sessions fuer diesen Tunnel schliessen
response.Status = "ok"
default:
response.Status = "error"
response.Error = fmt.Sprintf("Unbekanntes Command: %s", msg.Command)
}
if err := h.client.SendMessage(response); err != nil {
log.Printf("Response senden fehlgeschlagen: %v", err)
}
}
func (h *Handler) handleExec(msg Message) Message {
response := Message{
Type: "response",
ID: msg.ID,
}
command, ok := msg.Params["command"].(string)
if !ok || command == "" {
response.Status = "error"
response.Error = "Parameter 'command' erforderlich"
return response
}
timeoutSec := 30
if t, ok := msg.Params["timeout"]; ok {
if ts, ok := t.(float64); ok {
timeoutSec = int(ts)
} else if ts, ok := t.(string); ok {
if parsed, err := strconv.Atoi(ts); err == nil {
timeoutSec = parsed
}
}
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSec)*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "/bin/sh", "-c", command)
output, err := cmd.CombinedOutput()
if err != nil {
response.Status = "error"
response.Error = err.Error()
response.Data = map[string]interface{}{
"output": string(output),
}
} else {
response.Status = "ok"
response.Data = map[string]interface{}{
"output": string(output),
}
}
return response
}
func (h *Handler) handleTunnelConnect(msg Message) Message {
response := Message{
Type: "response",
ID: msg.ID,
}
sessionID, _ := msg.Params["session_id"].(string)
tunnelID, _ := msg.Params["tunnel_id"].(string)
targetHost, _ := msg.Params["target_host"].(string)
targetPortFloat, _ := msg.Params["target_port"].(float64)
targetPort := int(targetPortFloat)
if sessionID == "" || targetHost == "" || targetPort <= 0 {
response.Status = "error"
response.Error = "session_id, target_host und target_port erforderlich"
return response
}
if err := h.client.tunnelManager.ConnectSession(sessionID, tunnelID, targetHost, targetPort); err != nil {
response.Status = "error"
response.Error = fmt.Sprintf("Session-Connect fehlgeschlagen: %v", err)
return response
}
response.Status = "ok"
response.Data = map[string]interface{}{
"session_id": sessionID,
}
return response
}
func (h *Handler) handleTunnelDisconnect(msg Message) {
sessionID, _ := msg.Params["session_id"].(string)
if sessionID == "" {
return
}
h.client.tunnelManager.DisconnectSession(sessionID)
}
// SendSessionData schickt Daten als Binary-Message zurueck zum Backend
func (h *Handler) SendSessionData(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 h.client.SendBinaryData(message)
}