mirror of
https://github.com/fosrl/newt.git
synced 2026-09-01 19:51:30 +02:00
@@ -51,6 +51,7 @@ func New(cfg Config) *Gateway {
|
||||
func (g *Gateway) RegisterHandlers(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/rdp", g.HandleRDP)
|
||||
mux.HandleFunc("/ssh", g.HandleSSH)
|
||||
mux.HandleFunc("/vnc", g.handleVNC)
|
||||
}
|
||||
|
||||
// HandleRDP is an http.HandlerFunc for RDP-over-WebSocket connections.
|
||||
|
||||
96
browsergateway/vnc.go
Normal file
96
browsergateway/vnc.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package browsergateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
vncDialTimeout = 10 * time.Second
|
||||
vncKeepAlive = 30 * time.Second
|
||||
vncForwardBufSize = 32 * 1024
|
||||
)
|
||||
|
||||
// handleVNC proxies a noVNC WebSocket connection to a raw TCP VNC backend.
|
||||
// It follows the same auth-token-in-query-param pattern as handleSSH.
|
||||
//
|
||||
// Query parameters:
|
||||
//
|
||||
// authToken – shared secret matching the -auth-token flag
|
||||
// host – VNC backend hostname or IP
|
||||
// port – VNC backend port (default: 5900)
|
||||
func (g *Gateway) handleVNC(w http.ResponseWriter, r *http.Request) {
|
||||
if subtle.ConstantTimeCompare([]byte(r.URL.Query().Get("authToken")), []byte(g.authToken)) != 1 {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
host := r.URL.Query().Get("host")
|
||||
port := r.URL.Query().Get("port")
|
||||
if host == "" {
|
||||
http.Error(w, "missing host", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if port == "" {
|
||||
port = "5900"
|
||||
}
|
||||
target := net.JoinHostPort(host, port)
|
||||
|
||||
// Accept the WebSocket. noVNC negotiates the "binary" subprotocol;
|
||||
// fall back gracefully when the client sends "base64" as well.
|
||||
ws, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
||||
InsecureSkipVerify: true,
|
||||
Subprotocols: []string{"binary", "base64"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("vnc: websocket upgrade failed: %v", err)
|
||||
return
|
||||
}
|
||||
ws.SetReadLimit(-1)
|
||||
defer ws.CloseNow() //nolint:errcheck
|
||||
|
||||
ctx := r.Context()
|
||||
if err := serveVNC(ctx, ws, target); err != nil {
|
||||
log.Printf("vnc: session error (%s): %v", target, err)
|
||||
}
|
||||
}
|
||||
|
||||
func serveVNC(ctx context.Context, ws *websocket.Conn, target string) error {
|
||||
// Dial the VNC backend TCP server.
|
||||
dialer := &net.Dialer{
|
||||
Timeout: vncDialTimeout,
|
||||
KeepAlive: vncKeepAlive,
|
||||
}
|
||||
conn, err := dialer.DialContext(ctx, "tcp", target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close() //nolint:errcheck
|
||||
|
||||
// Expose the WebSocket as a plain net.Conn byte stream (binary frames).
|
||||
stream := websocket.NetConn(ctx, ws, websocket.MessageBinary)
|
||||
defer stream.Close() //nolint:errcheck
|
||||
|
||||
// Proxy bidirectionally: VNC backend <-> browser.
|
||||
errc := make(chan error, 2)
|
||||
go func() {
|
||||
buf := make([]byte, vncForwardBufSize)
|
||||
_, err := io.CopyBuffer(conn, stream, buf)
|
||||
errc <- err
|
||||
}()
|
||||
go func() {
|
||||
buf := make([]byte, vncForwardBufSize)
|
||||
_, err := io.CopyBuffer(stream, conn, buf)
|
||||
errc <- err
|
||||
}()
|
||||
|
||||
// Return when either direction closes.
|
||||
err = <-errc
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user