2024-04-06 04:03:38 +00:00
|
|
|
|
package guac
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"bytes"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"mayfly-go/internal/machine/config"
|
2024-04-06 18:19:17 +08:00
|
|
|
|
"mayfly-go/pkg/errorx"
|
2024-04-06 04:03:38 +00:00
|
|
|
|
"mayfly-go/pkg/logx"
|
|
|
|
|
|
"net"
|
|
|
|
|
|
"net/url"
|
|
|
|
|
|
"strconv"
|
2024-04-06 18:19:17 +08:00
|
|
|
|
|
|
|
|
|
|
"github.com/gorilla/websocket"
|
2024-04-06 04:03:38 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// creates the tunnel to the remote machine (via guacd)
|
2024-04-09 12:55:51 +08:00
|
|
|
|
func DoConnect(query url.Values, parameters map[string]string, ac string) (Tunnel, error) {
|
2024-04-06 04:03:38 +00:00
|
|
|
|
conf := NewGuacamoleConfiguration()
|
|
|
|
|
|
|
|
|
|
|
|
parameters["enable-wallpaper"] = "true" // 允许显示墙纸
|
|
|
|
|
|
//parameters["resize-method"] = "reconnect"
|
|
|
|
|
|
parameters["enable-font-smoothing"] = "true"
|
|
|
|
|
|
parameters["enable-desktop-composition"] = "true"
|
|
|
|
|
|
parameters["enable-menu-animations"] = "false"
|
|
|
|
|
|
parameters["disable-bitmap-caching"] = "true"
|
|
|
|
|
|
parameters["disable-offscreen-caching"] = "true"
|
|
|
|
|
|
parameters["force-lossless"] = "true" // 无损压缩
|
|
|
|
|
|
parameters["color-depth"] = "32" //32 真彩(32位);24 真彩(24位);16 低色(16位);8 256色
|
|
|
|
|
|
|
|
|
|
|
|
// drive
|
|
|
|
|
|
parameters["enable-drive"] = "true"
|
|
|
|
|
|
parameters["drive-name"] = "Filesystem"
|
|
|
|
|
|
parameters["create-drive-path"] = "true"
|
2024-04-09 12:55:51 +08:00
|
|
|
|
parameters["drive-path"] = fmt.Sprintf("/rdp-file/%s", ac)
|
2024-04-06 04:03:38 +00:00
|
|
|
|
|
|
|
|
|
|
conf.Protocol = parameters["scheme"]
|
|
|
|
|
|
conf.Parameters = parameters
|
|
|
|
|
|
conf.OptimalScreenWidth = 800
|
|
|
|
|
|
conf.OptimalScreenHeight = 600
|
|
|
|
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
|
|
if query.Get("width") != "" {
|
|
|
|
|
|
conf.OptimalScreenWidth, err = strconv.Atoi(query.Get("width"))
|
|
|
|
|
|
if err != nil || conf.OptimalScreenWidth == 0 {
|
|
|
|
|
|
logx.Error("Invalid width")
|
|
|
|
|
|
conf.OptimalScreenWidth = 800
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if query.Get("height") != "" {
|
|
|
|
|
|
conf.OptimalScreenHeight, err = strconv.Atoi(query.Get("height"))
|
|
|
|
|
|
if err != nil || conf.OptimalScreenHeight == 0 {
|
|
|
|
|
|
logx.Error("Invalid height")
|
|
|
|
|
|
conf.OptimalScreenHeight = 600
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//conf.ConnectionID = uuid.New().String()
|
|
|
|
|
|
|
|
|
|
|
|
conf.AudioMimetypes = []string{"audio/L8", "audio/L16"}
|
|
|
|
|
|
conf.ImageMimetypes = []string{"image/jpeg", "image/png", "image/webp"}
|
|
|
|
|
|
|
|
|
|
|
|
logx.Debug("Connecting to guacd")
|
2024-04-06 18:19:17 +08:00
|
|
|
|
|
|
|
|
|
|
machineConfig := config.GetMachine()
|
|
|
|
|
|
if machineConfig.GuacdHost == "" {
|
|
|
|
|
|
return nil, errorx.NewBiz("请前往'系统配置-机器配置'中配置guacd相关信息")
|
|
|
|
|
|
}
|
|
|
|
|
|
guacdAddr := fmt.Sprintf("%v:%v", machineConfig.GuacdHost, machineConfig.GuacdPort)
|
2024-04-06 04:03:38 +00:00
|
|
|
|
addr, err := net.ResolveTCPAddr("tcp", guacdAddr)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logx.Error("error resolving guacd address", err)
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
conn, err := net.DialTCP("tcp", nil, addr)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logx.Error("error while connecting to guacd", err)
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stream := NewStream(conn, SocketTimeout)
|
|
|
|
|
|
|
|
|
|
|
|
logx.Debug("Connected to guacd")
|
|
|
|
|
|
//conf.ConnectionID = uuid.New().String()
|
|
|
|
|
|
|
|
|
|
|
|
logx.Debugf("Starting handshake with %#v", conf)
|
|
|
|
|
|
err = stream.Handshake(conf)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
logx.Debug("Socket configured")
|
|
|
|
|
|
return NewSimpleTunnel(stream), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func WsToGuacd(ws *websocket.Conn, tunnel Tunnel, guacd io.Writer) {
|
|
|
|
|
|
for {
|
|
|
|
|
|
_, data, err := ws.ReadMessage()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logx.Warnf("Error reading message from ws: %v", err)
|
|
|
|
|
|
_ = tunnel.Close()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if bytes.HasPrefix(data, internalOpcodeIns) {
|
|
|
|
|
|
// messages starting with the InternalDataOpcode are never sent to guacd
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if _, err = guacd.Write(data); err != nil {
|
|
|
|
|
|
logx.Warnf("Failed writing to guacd: %v", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func GuacdToWs(ws *websocket.Conn, tunnel Tunnel, guacd InstructionReader) {
|
|
|
|
|
|
buf := bytes.NewBuffer(make([]byte, 0, MaxGuacMessage*2))
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
ins, err := guacd.ReadSome()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logx.Warnf("Error reading message from guacd: %v", err)
|
|
|
|
|
|
_ = tunnel.Close()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if bytes.HasPrefix(ins, internalOpcodeIns) {
|
|
|
|
|
|
// messages starting with the InternalDataOpcode are never sent to the websocket
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
logx.Debugf("guacd msg: %s", string(ins))
|
|
|
|
|
|
if _, err = buf.Write(ins); err != nil {
|
|
|
|
|
|
logx.Warnf("Failed to buffer guacd to ws: %v", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// if the buffer has more data in it or we've reached the max buffer size, send the data and reset
|
|
|
|
|
|
if !guacd.Available() || buf.Len() >= MaxGuacMessage {
|
|
|
|
|
|
if err = ws.WriteMessage(1, buf.Bytes()); err != nil {
|
|
|
|
|
|
if errors.Is(err, websocket.ErrCloseSent) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
logx.Warnf("Failed sending message to ws: %v", err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
buf.Reset()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|