18 Commits

Author SHA1 Message Date
Bezalel
1bce301125 added keyframes & an rfbFile writer implementation (initial) 2018-08-01 02:04:30 +03:00
Bezalel
f19a75749b updated build script 2018-08-01 02:02:05 +03:00
Bezalel
02dde77daa fixed: listning only to local server connections 2018-08-01 01:29:39 +03:00
betzalel
b46f708726 logger should not be open in debug 2018-02-12 10:14:54 +02:00
amit bezalel
f298567976 added usage examples to readme 2018-02-10 08:43:40 +02:00
amit bezalel
8543af35a4 advanced the version 2018-02-10 01:53:39 +02:00
amit bezalel
f05c96d449 fixed tight encoding, added the QEMU extended keys message (no way to test it) 2018-02-10 01:41:02 +02:00
amit bezalel
37e11cd72e updated gitignore 2017-10-12 01:24:58 +03:00
amit bezalel
47fefaacb6 added missing target host to cmdline params 2017-10-12 01:21:59 +03:00
betzalel
7852f11ceb starting to create a new file format 2017-08-24 14:33:53 +03:00
betzalel
e06ad806db Merge branch 'master' of https://github.com/amitbet/VncProxy 2017-08-09 11:53:06 +03:00
amit bezalel
9fff2b46aa a better build script 2017-08-06 01:47:14 +03:00
Amit Bezalel
aff3a20d4a Update README.md 2017-08-06 00:52:54 +03:00
Amit Bezalel
6f8d591789 Update README.md 2017-08-06 00:52:17 +03:00
Amit Bezalel
c75e9ccc9c Update README.md 2017-08-06 00:51:20 +03:00
Amit Bezalel
b448d0235a Update README.md 2017-08-06 00:48:08 +03:00
Amit Bezalel
57fdfe914d Update README.md 2017-08-04 23:13:28 -04:00
Amit Bezalel
538037aa8f Update README.md 2017-08-04 23:11:41 -04:00
20 changed files with 408 additions and 63 deletions

5
.gitignore vendored
View File

@@ -2,3 +2,8 @@
*.debug *.debug
*.test *.test
debug debug
/dist
vncproxy
prox
cmd
aa

View File

@@ -1,16 +1,14 @@
# VncProxy # VncProxy
An RFB proxy, written in go that can save and replay FBS files An RFB proxy, written in go that can save and replay FBS files
* supports all modern encodings & most useful pseudo-encodings * Supports all modern encodings & most useful pseudo-encodings
* supports multiple VNC client connections & multi servers (chosen by sessionId) * Supports multiple VNC client connections & multi servers (chosen by sessionId)
* supports being a "websockify" proxy (for web clients like NoVnc) * Supports being a "websockify" proxy (for web clients like NoVnc)
* produces FBS files compatible with tightvnc player (while using tight's default 3Byte color format) * Produces FBS files compatible with tightvnc player (while using tight's default 3Byte color format)
* can also be used as: * Can also be used as:
* a screen recorder vnc-client * A screen recorder vnc-client
* a replay server to show fbs recordings to connecting clients * A replay server to show fbs recordings to connecting clients
**This is still a work in progress, and requires some error handling and general tidying up, - Tested on tight encoding with:
but the code is already working (see main, proxy_test & player_test)**
- tested on tight encoding with:
- Tightvnc (client + java client + server) - Tightvnc (client + java client + server)
- FBS player (tightVnc Java player) - FBS player (tightVnc Java player)
- NoVnc(web client) - NoVnc(web client)
@@ -18,13 +16,20 @@ but the code is already working (see main, proxy_test & player_test)**
- VineVnc(server) - VineVnc(server)
- TigerVnc(client) - TigerVnc(client)
## Usage
**Some very usable cmdline executables are Coming Soon...**
Code samples can be found by looking at: ### Executables (see releases)
* main.go (fbs recording vnc client) * proxy - the actual recording proxy, supports listening to tcp & ws ports and recording traffic to fbs files
* Connects, records to FBS file * recorder - connects to a vnc server as a client and records the screen
* Programmed to quit after 10 seconds * player - a toy player that will replay a given fbs file to all incoming connections
## Usage:
recorder -recDir=./recording.rbs -targHost=192.168.0.100 -targPort=5903 -targPass=@@@@@
player -fbsFile=./myrec.fbs -tcpPort=5905
proxy -recDir=./recordings/ -targHost=192.168.0.100 -targPort=5903 -targPass=@@@@@ -tcpPort=5903 -vncPass=@!@!@!
### Code usage examples
* player/main.go (fbs recording vnc client)
   * Connects as client, records to FBS file
* proxy/proxy_test.go (vnc proxy with recording) * proxy/proxy_test.go (vnc proxy with recording)
* Listens to both Tcp and WS ports * Listens to both Tcp and WS ports
* Proxies connections to a hard-coded localhost vnc server * Proxies connections to a hard-coded localhost vnc server
@@ -56,4 +61,4 @@ The Recorder uses channels and runs in parallel to avoid hampering the communica
![Image of Arch](https://github.com/amitbet/vncproxy/blob/master/architecture/player-arch.png?raw=true) ![Image of Arch](https://github.com/amitbet/vncproxy/blob/master/architecture/player-arch.png?raw=true)
The code is based on several implementations of go-vnc including the original one by *Mitchell Hashimoto*, and the recentely active fork which belongs to *Vasiliy Tolstov*. The code is based on several implementations of go-vnc including the original one by *Mitchell Hashimoto*, and the recentely active fork by *Vasiliy Tolstov*.

View File

@@ -1,4 +1,48 @@
export GOOS="darwin" #!/bin/bash
go build -o ./dist/recorder ./recorder/cmd sum="sha1sum"
go build -o ./dist/player ./player/cmd
go build -o ./dist/proxy ./proxy/cmd # VERSION=`date -u +%Y%m%d`
VERSION="v1.02"
LDFLAGS="-X main.VERSION=$VERSION -s -w"
GCFLAGS=""
if ! hash sha1sum 2>/dev/null; then
if ! hash shasum 2>/dev/null; then
echo "I can't see 'sha1sum' or 'shasum'"
echo "Please install one of them!"
exit
fi
sum="shasum"
fi
UPX=false
if hash upx 2>/dev/null; then
UPX=true
fi
OSES=( linux darwin windows )
ARCHS=(amd64 386 )
for os in ${OSES[@]}; do
for arch in ${ARCHS[@]}; do
suffix=""
if [ "$os" == "windows" ]
then
suffix=".exe"
fi
env CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o ./dist/${os}_${arch}/recorder${suffix} ./recorder/cmd
env CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o ./dist/${os}_${arch}/player${suffix} ./player/cmd
env CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o ./dist/${os}_${arch}/proxy${suffix} ./proxy/cmd
if $UPX; then upx -9 client_${os}_${arch}${suffix} server_${os}_${arch}${suffix};fi
# tar -zcf ./dist/vncproxy-${os}-${arch}-$VERSION.tar.gz ./dist/${os}_${arch}/proxy${suffix} ./dist/${os}_${arch}/player${suffix} ./dist/${os}_${arch}/recorder${suffix}
cd dist/${os}_${arch}/
zip -D -q -r ../vncproxy-${os}-${arch}-$VERSION.zip proxy${suffix} player${suffix} recorder${suffix}
cd ../..
$sum ./dist/vncproxy-${os}-${arch}-$VERSION.zip
done
done

View File

@@ -116,9 +116,9 @@ func (c *ClientConn) Read(bytes []byte) (n int, err error) {
return c.conn.Read(bytes) return c.conn.Read(bytes)
} }
func (c *ClientConn) CurrentColorMap() *common.ColorMap { // func (c *ClientConn) CurrentColorMap() *common.ColorMap {
return &c.ColorMap // return &c.ColorMap
} // }
// CutText tells the server that the client has new text in its cut buffer. // CutText tells the server that the client has new text in its cut buffer.
// The text string MUST only contain Latin-1 characters. This encoding // The text string MUST only contain Latin-1 characters. This encoding

View File

@@ -175,9 +175,9 @@ func (m *MsgSetColorMapEntries) Read(c common.IClientConn, r *common.RfbReadHelp
return nil, err return nil, err
} }
} }
cmap := c.CurrentColorMap() // cmap := c.CurrentColorMap()
// Update the connection's color map // // Update the connection's color map
cmap[result.FirstColor+i] = *color // cmap[result.FirstColor+i] = *color
} }
r.SendMessageEnd(common.ServerMessageType(m.Type())) r.SendMessageEnd(common.ServerMessageType(m.Type()))
return &result, nil return &result, nil

View File

@@ -17,7 +17,8 @@ const (
KeyEventMsgType KeyEventMsgType
PointerEventMsgType PointerEventMsgType
ClientCutTextMsgType ClientCutTextMsgType
ClientFenceMsgType = 248 ClientFenceMsgType = 248
QEMUExtendedKeyEventMsgType = 255
) )
// Color represents a single color in a color map. // Color represents a single color in a color map.
@@ -47,6 +48,8 @@ func (cmt ClientMessageType) String() string {
return "FramebufferUpdateRequest" return "FramebufferUpdateRequest"
case KeyEventMsgType: case KeyEventMsgType:
return "KeyEvent" return "KeyEvent"
case QEMUExtendedKeyEventMsgType:
return "QEMUExtendedKeyEvent"
case PointerEventMsgType: case PointerEventMsgType:
return "PointerEvent" return "PointerEvent"
case ClientCutTextMsgType: case ClientCutTextMsgType:

View File

@@ -25,6 +25,6 @@ type IServerConn interface {
type IClientConn interface { type IClientConn interface {
CurrentPixelFormat() *PixelFormat CurrentPixelFormat() *PixelFormat
CurrentColorMap() *ColorMap //CurrentColorMap() *ColorMap
Encodings() []IEncoding Encodings() []IEncoding
} }

View File

@@ -155,7 +155,7 @@ func handleTightFilters(subencoding uint8, pixelFmt *common.PixelFormat, rect *c
if paletteSize == 2 { if paletteSize == 2 {
dataLength = int(rect.Height) * ((int(rect.Width) + 7) / 8) dataLength = int(rect.Height) * ((int(rect.Width) + 7) / 8)
} else { } else {
dataLength = int(rect.Width * rect.Height) dataLength = int(rect.Width) * int(rect.Height)
} }
_, err = r.ReadTightData(dataLength) _, err = r.ReadTightData(dataLength)
if err != nil { if err != nil {

View File

@@ -80,7 +80,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
url := "http://localhost:" + *wsPort + "/" url := "http://0.0.0.0:" + *wsPort + "/"
if *tcpPort != "" && *wsPort != "" { if *tcpPort != "" && *wsPort != "" {
logger.Infof("running two listeners: tcp port: %s, ws url: %s", *tcpPort, url) logger.Infof("running two listeners: tcp port: %s, ws url: %s", *tcpPort, url)

View File

@@ -3,17 +3,25 @@ package player
import ( import (
"encoding/binary" "encoding/binary"
"io"
"time" "time"
"vncproxy/client" "vncproxy/client"
"vncproxy/common" "vncproxy/common"
"vncproxy/logger" "vncproxy/logger"
"vncproxy/server" "vncproxy/server"
) )
type VncStreamFileReader interface {
io.Reader
CurrentTimestamp() int
ReadStartSession() (*common.ServerInit, error)
CurrentPixelFormat() *common.PixelFormat
Encodings() []common.IEncoding
}
type FBSPlayListener struct { type FBSPlayListener struct {
Conn *server.ServerConn Conn *server.ServerConn
Fbs *FbsReader Fbs VncStreamFileReader
serverMessageMap map[uint8]common.ServerMessage serverMessageMap map[uint8]common.ServerMessage
firstSegDone bool firstSegDone bool
startTime int startTime int
@@ -88,7 +96,7 @@ func (h *FBSPlayListener) sendFbsMessage() {
return return
} }
timeSinceStart := int(time.Now().UnixNano()/int64(time.Millisecond)) - h.startTime timeSinceStart := int(time.Now().UnixNano()/int64(time.Millisecond)) - h.startTime
timeToSleep := fbs.currentTimestamp - timeSinceStart timeToSleep := fbs.CurrentTimestamp() - timeSinceStart
if timeToSleep > 0 { if timeToSleep > 0 {
time.Sleep(time.Duration(timeToSleep) * time.Millisecond) time.Sleep(time.Duration(timeToSleep) * time.Millisecond)
} }

View File

@@ -18,6 +18,10 @@ type FbsReader struct {
encodings []common.IEncoding encodings []common.IEncoding
} }
func (fbs *FbsReader) CurrentTimestamp() int {
return fbs.currentTimestamp
}
func (fbs *FbsReader) Read(p []byte) (n int, err error) { func (fbs *FbsReader) Read(p []byte) (n int, err error) {
if fbs.buffer.Len() < len(p) { if fbs.buffer.Len() < len(p) {
seg, err := fbs.ReadSegment() seg, err := fbs.ReadSegment()
@@ -33,8 +37,9 @@ func (fbs *FbsReader) Read(p []byte) (n int, err error) {
} }
func (fbs *FbsReader) CurrentPixelFormat() *common.PixelFormat { return fbs.pixelFormat } func (fbs *FbsReader) CurrentPixelFormat() *common.PixelFormat { return fbs.pixelFormat }
func (fbs *FbsReader) CurrentColorMap() *common.ColorMap { return &common.ColorMap{} }
func (fbs *FbsReader) Encodings() []common.IEncoding { return fbs.encodings } //func (fbs *FbsReader) CurrentColorMap() *common.ColorMap { return &common.ColorMap{} }
func (fbs *FbsReader) Encodings() []common.IEncoding { return fbs.encodings }
func NewFbsReader(fbsFile string) (*FbsReader, error) { func NewFbsReader(fbsFile string) (*FbsReader, error) {

199
player/rfb-reader.go Normal file
View File

@@ -0,0 +1,199 @@
package player
import (
"bytes"
"encoding/binary"
"io"
"os"
"vncproxy/common"
"vncproxy/encodings"
"vncproxy/logger"
)
type RfbReader struct {
reader io.Reader
buffer bytes.Buffer
currentTimestamp int
pixelFormat *common.PixelFormat
encodings []common.IEncoding
}
/**************************************************************
** RFB File documentation:
** Sections:
** 0. header:
* index seek position
*
** 1. init message
** 2. content
* frame message:
* size, timestamp, type, content
** 3. index:
* each frame message start position, full/incremental, timestamp
*
***************************************************************/
func (rfb *RfbReader) CurrentTimestamp() int {
return rfb.currentTimestamp
}
func (rfb *RfbReader) Read(p []byte) (n int, err error) {
if rfb.buffer.Len() < len(p) {
seg, err := rfb.ReadSegment()
if err != nil {
logger.Error("rfbReader.Read: error reading rfbsegment: ", err)
return 0, err
}
rfb.buffer.Write(seg.bytes)
rfb.currentTimestamp = int(seg.timestamp)
}
return rfb.buffer.Read(p)
}
func (rfb *RfbReader) CurrentPixelFormat() *common.PixelFormat { return rfb.pixelFormat }
//func (rfb *rfbReader) CurrentColorMap() *common.ColorMap { return &common.ColorMap{} }
func (rfb *RfbReader) Encodings() []common.IEncoding { return rfb.encodings }
func NewRfbReader(rfbFile string) (*RfbReader, error) {
reader, err := os.OpenFile(rfbFile, os.O_RDONLY, 0644)
if err != nil {
logger.Error("NewrfbReader: can't open rfb file: ", rfbFile)
return nil, err
}
return &RfbReader{reader: reader,
encodings: []common.IEncoding{
&encodings.CopyRectEncoding{},
&encodings.ZLibEncoding{},
&encodings.ZRLEEncoding{},
&encodings.CoRREEncoding{},
&encodings.HextileEncoding{},
&encodings.TightEncoding{},
&encodings.TightPngEncoding{},
&encodings.EncCursorPseudo{},
&encodings.RawEncoding{},
&encodings.RREEncoding{},
},
}, nil
}
func (rfb *RfbReader) ReadStartSession() (*common.ServerInit, error) {
initMsg := common.ServerInit{}
reader := rfb.reader
var framebufferWidth uint16
var framebufferHeight uint16
var SecTypeNone uint32
//read rfb header information (the only part done without the [size|data|timestamp] block wrapper)
//.("rfb 001.000\n")
bytes := make([]byte, 12)
_, err := reader.Read(bytes)
if err != nil {
logger.Error("rfbReader.ReadStartSession: error reading rbs init message - rfb file Version:", err)
return nil, err
}
//read the version message into the buffer so it will be written in the first rbs block
//RFB 003.008\n
bytes = make([]byte, 12)
_, err = rfb.Read(bytes)
if err != nil {
logger.Error("rfbReader.ReadStartSession: error reading rbs init - RFB Version: ", err)
return nil, err
}
//push sec type and fb dimensions
binary.Read(rfb, binary.BigEndian, &SecTypeNone)
if err != nil {
logger.Error("rfbReader.ReadStartSession: error reading rbs init - SecType: ", err)
}
//read frame buffer width, height
binary.Read(rfb, binary.BigEndian, &framebufferWidth)
if err != nil {
logger.Error("rfbReader.ReadStartSession: error reading rbs init - FBWidth: ", err)
return nil, err
}
initMsg.FBWidth = framebufferWidth
binary.Read(rfb, binary.BigEndian, &framebufferHeight)
if err != nil {
logger.Error("rfbReader.ReadStartSession: error reading rbs init - FBHeight: ", err)
return nil, err
}
initMsg.FBHeight = framebufferHeight
//read pixel format
pixelFormat := &common.PixelFormat{}
binary.Read(rfb, binary.BigEndian, pixelFormat)
if err != nil {
logger.Error("rfbReader.ReadStartSession: error reading rbs init - Pixelformat: ", err)
return nil, err
}
initMsg.PixelFormat = *pixelFormat
//read padding
bytes = make([]byte, 3)
rfb.Read(bytes)
rfb.pixelFormat = pixelFormat
//read desktop name
var desknameLen uint32
binary.Read(rfb, binary.BigEndian, &desknameLen)
if err != nil {
logger.Error("rfbReader.ReadStartSession: error reading rbs init - deskname Len: ", err)
return nil, err
}
initMsg.NameLength = desknameLen
bytes = make([]byte, desknameLen)
rfb.Read(bytes)
if err != nil {
logger.Error("rfbReader.ReadStartSession: error reading rbs init - desktopName: ", err)
return nil, err
}
initMsg.NameText = bytes
return &initMsg, nil
}
func (rfb *RfbReader) ReadSegment() (*FbsSegment, error) {
reader := rfb.reader
var bytesLen uint32
//read length
err := binary.Read(reader, binary.BigEndian, &bytesLen)
if err != nil {
logger.Error("rfbReader.ReadStartSession: read len, error reading rbs file: ", err)
return nil, err
}
paddedSize := (bytesLen + 3) & 0x7FFFFFFC
//read bytes
bytes := make([]byte, paddedSize)
_, err = reader.Read(bytes)
if err != nil {
logger.Error("rfbReader.ReadSegment: read bytes, error reading rbs file: ", err)
return nil, err
}
//remove padding
actualBytes := bytes[:bytesLen]
//read timestamp
var timeSinceStart uint32
binary.Read(reader, binary.BigEndian, &timeSinceStart)
if err != nil {
logger.Error("rfbReader.ReadSegment: read timestamp, error reading rbs file: ", err)
return nil, err
}
//timeStamp := time.Unix(timeSinceStart, 0)
seg := &FbsSegment{bytes: actualBytes, timestamp: timeSinceStart}
return seg, nil
}

View File

@@ -12,6 +12,7 @@ func main() {
var vncPass = flag.String("vncPass", "", "password on incoming vnc connections to the proxy, defaults to no password") var vncPass = flag.String("vncPass", "", "password on incoming vnc connections to the proxy, defaults to no password")
var recordDir = flag.String("recDir", "", "path to save FBS recordings WILL NOT RECORD if not defined.") var recordDir = flag.String("recDir", "", "path to save FBS recordings WILL NOT RECORD if not defined.")
var targetVncPort = flag.String("targPort", "", "target vnc server port") var targetVncPort = flag.String("targPort", "", "target vnc server port")
var targetVncHost = flag.String("targHost", "", "target vnc server host")
var targetVncPass = flag.String("targPass", "", "target vnc password") var targetVncPass = flag.String("targPass", "", "target vnc password")
flag.Parse() flag.Parse()
@@ -35,13 +36,18 @@ func main() {
logger.Warn("FBS recording is turned off") logger.Warn("FBS recording is turned off")
} }
tcpUrl := ""
if *tcpPort != "" {
tcpUrl = ":" + string(*tcpPort)
}
proxy := &proxy.VncProxy{ proxy := &proxy.VncProxy{
WsListeningUrl: "http://localhost:" + string(*wsPort) + "/", // empty = not listening on ws WsListeningUrl: "http://0.0.0.0:" + string(*wsPort) + "/", // empty = not listening on ws
RecordingDir: *recordDir, //"/Users/amitbet/vncRec", // empty = no recording RecordingDir: *recordDir, //"/Users/amitbet/vncRec", // empty = no recording
TcpListeningUrl: ":" + string(*tcpPort), TcpListeningUrl: tcpUrl,
ProxyVncPassword: *vncPass, //empty = no auth ProxyVncPassword: *vncPass, //empty = no auth
SingleSession: &proxy.VncSession{ SingleSession: &proxy.VncSession{
TargetHostname: "localhost", TargetHostname: *targetVncHost,
TargetPort: *targetVncPort, TargetPort: *targetVncPort,
TargetPassword: *targetVncPass, //"vncPass", TargetPassword: *targetVncPass, //"vncPass",
ID: "dummySession", ID: "dummySession",

View File

@@ -62,7 +62,7 @@ func (vp *VncProxy) getProxySession(sessionId string) (*VncSession, error) {
} }
func (vp *VncProxy) newServerConnHandler(cfg *server.ServerConfig, sconn *server.ServerConn) error { func (vp *VncProxy) newServerConnHandler(cfg *server.ServerConfig, sconn *server.ServerConn) error {
var err error
session, err := vp.getProxySession(sconn.SessionId) session, err := vp.getProxySession(sconn.SessionId)
if err != nil { if err != nil {
logger.Errorf("Proxy.newServerConnHandler can't get session: %d", sconn.SessionId) logger.Errorf("Proxy.newServerConnHandler can't get session: %d", sconn.SessionId)
@@ -70,10 +70,11 @@ func (vp *VncProxy) newServerConnHandler(cfg *server.ServerConfig, sconn *server
} }
var rec *listeners.Recorder var rec *listeners.Recorder
if session.Type == SessionTypeRecordingProxy { if session.Type == SessionTypeRecordingProxy {
recFile := "recording" + strconv.FormatInt(time.Now().Unix(), 10) + ".rbs" recFile := "recording" + strconv.FormatInt(time.Now().Unix(), 10) + ".rbs"
recPath := path.Join(vp.RecordingDir, recFile) recPath := path.Join(vp.RecordingDir, recFile)
rec, err := listeners.NewRecorder(recPath) rec, err = listeners.NewRecorder(recPath)
if err != nil { if err != nil {
logger.Errorf("Proxy.newServerConnHandler can't open recorder save path: %s", recPath) logger.Errorf("Proxy.newServerConnHandler can't open recorder save path: %s", recPath)
return err return err
@@ -173,7 +174,7 @@ func (vp *VncProxy) StartListening() {
logger.Infof("running two listeners: tcp port: %s, ws url: %s", vp.TcpListeningUrl, vp.WsListeningUrl) logger.Infof("running two listeners: tcp port: %s, ws url: %s", vp.TcpListeningUrl, vp.WsListeningUrl)
go server.WsServe(vp.WsListeningUrl, cfg) go server.WsServe(vp.WsListeningUrl, cfg)
server.TcpServe(":"+vp.TcpListeningUrl, cfg) server.TcpServe(vp.TcpListeningUrl, cfg)
} }
if vp.WsListeningUrl != "" { if vp.WsListeningUrl != "" {

View File

@@ -6,15 +6,15 @@ func TestProxy(t *testing.T) {
//create default session if required //create default session if required
proxy := &VncProxy{ proxy := &VncProxy{
WsListeningUrl: "http://localhost:7777/", // empty = not listening on ws WsListeningUrl: "http://0.0.0.0:7778/", // empty = not listening on ws
RecordingDir: "/Users/amitbet/vncRec", // empty = no recording RecordingDir: "d:\\", // empty = no recording
TcpListeningUrl: ":5904", TcpListeningUrl: ":5904",
//recordingDir: "C:\\vncRec", // empty = no recording //RecordingDir: "C:\\vncRec", // empty = no recording
ProxyVncPassword: "1234", //empty = no auth ProxyVncPassword: "1234", //empty = no auth
SingleSession: &VncSession{ SingleSession: &VncSession{
TargetHostname: "localhost", TargetHostname: "192.168.1.101",
TargetPort: "5903", TargetPort: "5901",
TargetPassword: "Ch_#!T@8", TargetPassword: "123456",
ID: "dummySession", ID: "dummySession",
Status: SessionStatusInit, Status: SessionStatusInit,
Type: SessionTypeRecordingProxy, Type: SessionTypeRecordingProxy,

View File

@@ -19,9 +19,16 @@ func main() {
var recordDir = flag.String("recDir", "", "path to save FBS recordings WILL NOT RECORD IF EMPTY.") var recordDir = flag.String("recDir", "", "path to save FBS recordings WILL NOT RECORD IF EMPTY.")
var targetVncPort = flag.String("targPort", "", "target vnc server port") var targetVncPort = flag.String("targPort", "", "target vnc server port")
var targetVncPass = flag.String("targPass", "", "target vnc password") var targetVncPass = flag.String("targPass", "", "target vnc password")
var targetVncHost = flag.String("targHost", "localhost", "target vnc hostname")
flag.Parse() flag.Parse()
if *targetVncHost == "" {
logger.Error("no target vnc server host defined")
flag.Usage()
os.Exit(1)
}
if *targetVncPort == "" { if *targetVncPort == "" {
logger.Error("no target vnc server port defined") logger.Error("no target vnc server port defined")
flag.Usage() flag.Usage()
@@ -36,7 +43,7 @@ func main() {
} }
//nc, err := net.Dial("tcp", "192.168.1.101:5903") //nc, err := net.Dial("tcp", "192.168.1.101:5903")
nc, err := net.Dial("tcp", "localhost:"+*targetVncPort) nc, err := net.Dial("tcp", *targetVncHost+":"+*targetVncPort)
if err != nil { if err != nil {
logger.Errorf("error connecting to vnc server: %s", err) logger.Errorf("error connecting to vnc server: %s", err)
@@ -60,7 +67,11 @@ func main() {
}) })
clientConn.Listeners.AddListener(rec) clientConn.Listeners.AddListener(rec)
clientConn.Listeners.AddListener(&recorder.RfbRequester{Conn: clientConn, Name: "Rfb Requester"}) clientConn.Listeners.AddListener(&recorder.RfbRequester{
Conn: clientConn,
Name: "Rfb Requester",
FullScreenRefreshInSec: 30, //create a full refresh key frame every 30sec for seeking
})
clientConn.Connect() clientConn.Connect()
if err != nil { if err != nil {
@@ -74,9 +85,9 @@ func main() {
encs := []common.IEncoding{ encs := []common.IEncoding{
&encodings.TightEncoding{}, &encodings.TightEncoding{},
//&encodings.TightPngEncoding{}, //&encodings.TightPngEncoding{},
//rre := encodings.RREEncoding{}, //&encodings.RREEncoding{},
//zlib := encodings.ZLibEncoding{}, //&encodings.ZLibEncoding{},
//zrle := encodings.ZRLEEncoding{}, //&encodings.ZRLEEncoding{},
//&encodings.CopyRectEncoding{}, //&encodings.CopyRectEncoding{},
//coRRE := encodings.CoRREEncoding{}, //coRRE := encodings.CoRREEncoding{},
//hextile := encodings.HextileEncoding{}, //hextile := encodings.HextileEncoding{},

View File

@@ -38,7 +38,7 @@ func NewRecorder(saveFilePath string) (*Recorder, error) {
rec.maxWriteSize = 65535 rec.maxWriteSize = 65535
rec.writer, err = os.OpenFile(saveFilePath, os.O_RDWR|os.O_CREATE, 0755) rec.writer, err = os.OpenFile(saveFilePath, os.O_RDWR|os.O_CREATE, 0644)
if err != nil { if err != nil {
logger.Errorf("unable to open file: %s, error: %v", saveFilePath, err) logger.Errorf("unable to open file: %s, error: %v", saveFilePath, err)
return nil, err return nil, err
@@ -189,6 +189,11 @@ func (r *Recorder) writeToDisk() error {
paddedSize := (bytesLen + 3) & 0x7FFFFFFC paddedSize := (bytesLen + 3) & 0x7FFFFFFC
paddingSize := paddedSize - bytesLen paddingSize := paddedSize - bytesLen
/// KeyFramePos, _ := r.writer.Seek(0, os.SEEK_CUR)
/// fi, err := r.writer.Stat()
/// KeyFramePos := fi.Size() + KeyFramePosInBuffer
// now save the KF pos in some file
//logger.Debugf("paddedSize=%d paddingSize=%d bytesLen=%d", paddedSize, paddingSize, bytesLen) //logger.Debugf("paddedSize=%d paddingSize=%d bytesLen=%d", paddedSize, paddingSize, bytesLen)
//write buffer padded to 32bit //write buffer padded to 32bit
_, err := r.buffer.WriteTo(r.writer) _, err := r.buffer.WriteTo(r.writer)

View File

@@ -8,11 +8,13 @@ import (
) )
type RfbRequester struct { type RfbRequester struct {
Conn *client.ClientConn Conn *client.ClientConn
Name string Name string
Width uint16 Width uint16
Height uint16 Height uint16
lastRequestTime time.Time lastRequestTime time.Time
nextFullScreenRefresh time.Time
FullScreenRefreshInSec int // refresh interval (creates keyframes) if 0, disables keyframe creation
} }
func (p *RfbRequester) Consume(seg *common.RfbSegment) error { func (p *RfbRequester) Consume(seg *common.RfbSegment) error {
@@ -29,6 +31,7 @@ func (p *RfbRequester) Consume(seg *common.RfbSegment) error {
p.Height = serverInitMessage.FBHeight p.Height = serverInitMessage.FBHeight
p.lastRequestTime = time.Now() p.lastRequestTime = time.Now()
p.Conn.FramebufferUpdateRequest(false, 0, 0, p.Width, p.Height) p.Conn.FramebufferUpdateRequest(false, 0, 0, p.Width, p.Height)
p.nextFullScreenRefresh = time.Now().Add(time.Duration(p.FullScreenRefreshInSec) * time.Second)
case common.SegmentMessageStart: case common.SegmentMessageStart:
case common.SegmentRectSeparator: case common.SegmentRectSeparator:
@@ -39,7 +42,20 @@ func (p *RfbRequester) Consume(seg *common.RfbSegment) error {
// timeForNextReq := p.lastRequestTime.Unix() + minTimeBetweenReq.Nanoseconds()/1000 // timeForNextReq := p.lastRequestTime.Unix() + minTimeBetweenReq.Nanoseconds()/1000
// if seg.UpcomingObjectType == int(common.FramebufferUpdate) && time.Now().Unix() > timeForNextReq { // if seg.UpcomingObjectType == int(common.FramebufferUpdate) && time.Now().Unix() > timeForNextReq {
//time.Sleep(300 * time.Millisecond) //time.Sleep(300 * time.Millisecond)
p.Conn.FramebufferUpdateRequest(true, 0, 0, p.Width, p.Height) p.lastRequestTime = time.Now()
incremental := true
if p.FullScreenRefreshInSec > 0 {
// if p.nextFullScreenRefresh.IsZero() {
// p.nextFullScreenRefresh = time.Now().Add(time.Duration(p.FullScreenRefreshInSec) * time.Second)
// }
if time.Now().Sub(p.nextFullScreenRefresh) <= 0 {
logger.Warn(">>Creating keyframe")
p.nextFullScreenRefresh = time.Now().Add(time.Duration(p.FullScreenRefreshInSec) * time.Second)
incremental = false
}
}
p.Conn.FramebufferUpdateRequest(incremental, 0, 0, p.Width, p.Height)
//} //}
default: default:
} }

View File

@@ -165,6 +165,36 @@ func (msg *MsgKeyEvent) Write(c io.Writer) error {
return nil return nil
} }
// MsgKeyEvent holds the wire format message.
type MsgQEMUExtKeyEvent struct {
SubmessageType uint8 // submessage type
DownFlag uint16 // down-flag
KeySym Key // key symbol
KeyCode uint32 // scan code
}
func (*MsgQEMUExtKeyEvent) Type() common.ClientMessageType {
return common.QEMUExtendedKeyEventMsgType
}
func (*MsgQEMUExtKeyEvent) Read(c io.Reader) (common.ClientMessage, error) {
msg := MsgKeyEvent{}
if err := binary.Read(c, binary.BigEndian, &msg); err != nil {
return nil, err
}
return &msg, nil
}
func (msg *MsgQEMUExtKeyEvent) Write(c io.Writer) error {
if err := binary.Write(c, binary.BigEndian, msg.Type()); err != nil {
return err
}
if err := binary.Write(c, binary.BigEndian, msg); err != nil {
return err
}
return nil
}
// PointerEventMessage holds the wire format message. // PointerEventMessage holds the wire format message.
type MsgPointerEvent struct { type MsgPointerEvent struct {
Mask uint8 // button-mask Mask uint8 // button-mask

View File

@@ -6,4 +6,11 @@
* code stuff: * code stuff:
* move encodings to be on the framebufferupdate message object * move encodings to be on the framebufferupdate message object
* clear all messages read functions from updating stuff, move modification logic to another listener * clear all messages read functions from updating stuff, move modification logic to another listener
* message read function should accept only an io.Reader, move read helper logic (readuint8) to an actual helper class * message read function should accept only an io.Reader, move read helper logic (readuint8) to an actual helper class
* new recording format:
* rfb extension
* save FBResponse messages with additional fields
* timestamp
* is incremental
* size (bytes)
* have a header which contains an index of messages, holding timestamps & file positions for seeking