Compare commits

..

51 Commits
1.0 ... master

Author SHA1 Message Date
Amit Bezalel
ea8f9b5109
Merge pull request #16 from t-yuki/patch-1
Fix nil reference error for color map message
2020-01-18 10:43:10 +02:00
Yukinari Toyota
faa9021021
Update server-messages.go
Fix nil reference error
2020-01-17 13:21:09 +09:00
Bezalel
1c052273de Fix noVnc 1.1.0 problem (bad message type 248) 2019-12-19 07:56:00 +02:00
amit b
f07be04814 use circle 2019-06-16 20:38:05 +03:00
amit b
55cf83519a remove temp build dirs 2019-06-16 20:30:12 +03:00
amit b
92ee157817 fixing artifact collection 2019-06-16 20:20:10 +03:00
amit b
f635a73757 Merge branch 'master' of https://github.com/amitbet/VncProxy 2019-06-16 20:17:21 +03:00
amit b
149e26b6f7 added packaging to build 2019-06-16 20:16:22 +03:00
Amit Bezalel
3d42d4fa5f
changed badge style 2019-06-16 03:11:33 +03:00
Amit Bezalel
f66f550c77
added circleCI badge 2019-06-16 03:09:29 +03:00
amit b
c7ac36d15f remove steps in circleCI config 2019-06-16 03:04:55 +03:00
amit b
0ba10fab76 fixed cicrleCI testing 2019-06-16 02:59:17 +03:00
amit b
76299f1bc3 fixed error found in circleCI testing 2019-06-16 02:51:50 +03:00
amit b
a9dce0e6c6 Merge branch 'master' of https://github.com/amitbet/VncProxy 2019-06-16 02:37:07 +03:00
amit b
fa416220ea add circleCI config 2019-06-16 02:36:39 +03:00
Amit Bezalel
efc5741057
Update README.md
Added qemu to tested servers list
2018-12-04 19:03:16 +02:00
amit b
82e1549c4f fixed proxy cmdline parameters and logs 2018-12-01 14:59:32 +02:00
Amit Bezalel
33be77dedc
Merge pull request #9 from exoscale/go-mod-compatible
global: make the whole package go mod ready
2018-12-01 14:21:53 +02:00
Yoan Blanc
11b1d45ce9
global: make the whole package go mod ready
Signed-off-by: Yoan Blanc <yoan.blanc@exoscale.ch>
2018-11-30 09:40:45 +01:00
Amit Bezalel
42873297d2
Merge pull request #8 from exoscale/master
Fix recording bug in proxy
2018-11-14 13:45:02 +02:00
Marc Falzon
f00e2c33e7
Merge pull request #1 from exoscale/feature/unixsock-target
Add support for UNIX socket VNC target
2018-11-14 11:12:53 +01:00
Marc Falzon
9a52a433ea Add support for UNIX socket VNC target
This change add support for targeting a VNC server via a local UNIX
socket. It introduces a new `-target` CLI flag able to handle both TCP
"address:port" and "/path/to/unix.socket" formats, and deprecates the
previous `-targHost` and `-targPort` flags for future removal.
2018-11-01 10:14:15 +01:00
Marc Falzon
06e555775f Fix recording bug in proxy
This change fixes a bug in the proxy session recording management, where
the proxy would record sessions even if the user didn't provide a value
for the `-recDir` CLI flag.
2018-11-01 10:04:29 +01:00
Amit Bezalel
3e882d6140
Update README.md 2018-10-27 08:42:36 +03:00
Amit Bezalel
5a488bcd0f
Update README.md 2018-10-27 08:40:48 +03:00
amitb
1596f0daee fixed qemu extended keys 2018-10-25 01:53:57 +03:00
amit b
7cecffecf2 added log level 2018-10-20 02:29:36 +03:00
amit b
f57b698430 added logger trace level and set it to write all bytes 2018-10-19 08:13:16 +03:00
amit b
e03c85f7ab added logLevel to cmdline, moved pesky info lines to debug 2018-10-19 07:55:16 +03:00
amit b
931b08376b Merge branch 'master' of https://github.com/amitbet/VncProxy 2018-08-20 23:05:23 +03:00
amit b
2ccc4009f4 added initial support for qemu led state 2018-08-20 23:03:39 +03:00
Amit Bezalel
6e1a343b5f
Update README.md 2018-08-17 09:19:47 +03:00
Amit Bezalel
446bc0e537
Merge pull request #2 from wentianle/patch-1
fix vncproxy on windows 2008 rfb proto parse bug
2018-08-11 00:15:54 +03:00
wentianle
a870f3cbb9
fix vncproxy on windows 2008 rfb proto parse bug
Uint8 value range out of bounds causes problems in the parsing protocol
2018-08-10 01:42:52 +08: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
47 changed files with 578 additions and 174 deletions

129
.circleci/config.yml Normal file
View File

@ -0,0 +1,129 @@
version: 2 # use CircleCI 2.0
jobs: # basic units of work in a run
build: # runs not using Workflows must have a `build` job as entry point
docker: # run the steps with Docker
# CircleCI Go images available at: https://hub.docker.com/r/circleci/golang/
- image: circleci/golang:1.12
# CircleCI PostgreSQL images available at: https://hub.docker.com/r/circleci/postgres/
- image: circleci/postgres:9.6-alpine
environment: # environment variables for primary container
POSTGRES_USER: circleci-demo-go
POSTGRES_DB: circle_test
parallelism: 2
environment: # environment variables for the build itself
TEST_RESULTS: /tmp/test-results # path to where test results will be saved
steps: # steps that comprise the `build` job
- checkout # check out source code to working directory
- run: mkdir -p $TEST_RESULTS # create the test results directory
- restore_cache: # restores saved cache if no changes are detected since last run
keys:
- go-mod-v4-{{ checksum "go.sum" }}
# Wait for Postgres to be ready before proceeding
- run:
name: Waiting for Postgres to be ready
command: dockerize -wait tcp://localhost:5432 -timeout 1m
- run:
name: Run unit tests
environment: # environment variables for the database url and path to migration files
CONTACTS_DB_URL: "postgres://circleci-demo-go@localhost:5432/circle_test?sslmode=disable"
CONTACTS_DB_MIGRATIONS: /home/circleci/project/db/migrations
# store the results of our tests in the $TEST_RESULTS directory
command: |
PACKAGE_NAMES=$(go list ./... | circleci tests split --split-by=timings --timings-type=classname)
gotestsum --junitfile ${TEST_RESULTS}/gotestsum-report.xml -- $PACKAGE_NAMES
# Build
- run:
name: Build and package all OS flavors
command: |
#!/bin/bash
sum="sha1sum"
# VERSION=`date -u +%Y%m%d`
VERSION="v1.11"
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/${CIRCLE_PROJECT_REPONAME}-${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 ../${CIRCLE_PROJECT_REPONAME}-${os}-${arch}-$VERSION.zip proxy${suffix} player${suffix} recorder${suffix}
cd ../..
export
$sum ./dist/${CIRCLE_PROJECT_REPONAME}-${os}-${arch}-$VERSION.zip
rm -rf ./dist/${os}_${arch}/
done
done
- store_artifacts: # upload test summary for display in Artifacts
path: ./dist
destination: release-artifacts
# - run: make # pull and build dependencies for the project
# - save_cache:
# key: go-mod-v4-{{ checksum "go.sum" }}
# paths:
# - "/go/pkg/mod"
# - run:
# name: Start service
# environment:
# CONTACTS_DB_URL: "postgres://circleci-demo-go@localhost:5432/circle_test?sslmode=disable"
# CONTACTS_DB_MIGRATIONS: /home/circleci/project/db/migrations
# command: ./workdir/contacts
# background: true # keep service running and proceed to next step
# - run:
# name: Validate service is working
# command: |
# sleep 5
# curl --retry 10 --retry-delay 1 -X POST --header "Content-Type: application/json" -d '{"email":"test@example.com","name":"Test User"}' http://localhost:8080/contacts
# - store_artifacts: # upload test summary for display in Artifacts
# path: /tmp/test-results
# destination: raw-test-output
# - store_test_results: # upload test results for display in Test Summary
# path: /tmp/test-results
workflows:
version: 2
build-workflow:
jobs:
- build

5
.gitignore vendored
View File

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

View File

@ -1,30 +1,37 @@
# VncProxy # VncProxy [![CircleCI](https://circleci.com/gh/amitbet/vncproxy/tree/master.svg?style=shield)](https://circleci.com/gh/amitbet/vncproxy/tree/master) [![MIT Licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/CircleCI-Public/circleci-demo-go/master/LICENSE.md)
An RFB proxy, written in go that can save and replay FBS files
* supports all modern encodings & most useful pseudo-encodings
* supports multiple VNC client connections & multi servers (chosen by sessionId)
* 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)
* can also be used as:
* a screen recorder vnc-client
* 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, An RFB proxy, written in go that can save and replay FBS files
but the code is already working (see main, proxy_test & player_test)** * Supports all modern encodings & most useful pseudo-encodings
- tested on tight encoding with: * Supports multiple VNC client connections & multi servers (chosen by sessionId)
* Supports being a "websockify" proxy (for web clients like NoVnc)
* Produces FBS files compatible with [tightvnc's rfb player](https://www.tightvnc.com/rfbplayer.php) (while using tight's default 3Byte color format)
* Can also be used as:
* A screen recorder vnc-client
* A replay server to show fbs recordings to connecting clients
- 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) => use -wsPort to open a websocket
- ChickenOfTheVnc(client) - ChickenOfTheVnc(client)
- VineVnc(server) - VineVnc(server)
- TigerVnc(client) - TigerVnc(client)
- Qemu vnc(server)
## 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 -recFile=./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 -wsPort=5905 -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 +63,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.11"
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

@ -4,11 +4,11 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/amitbet/vncproxy/common"
"github.com/amitbet/vncproxy/logger"
"io" "io"
"net" "net"
"unicode" "unicode"
"vncproxy/common"
"vncproxy/logger"
) )
// A ServerMessage implements a message sent from the server to the client. // A ServerMessage implements a message sent from the server to the client.
@ -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
@ -146,7 +146,7 @@ func (c *ClientConn) CutText(text string) error {
for _, char := range text { for _, char := range text {
if char > unicode.MaxLatin1 { if char > unicode.MaxLatin1 {
return fmt.Errorf("Character '%s' is not valid Latin-1", char) return fmt.Errorf("Character '%v' is not valid Latin-1", char)
} }
if err := binary.Write(&buf, binary.BigEndian, uint8(char)); err != nil { if err := binary.Write(&buf, binary.BigEndian, uint8(char)); err != nil {
@ -474,6 +474,7 @@ func (c *ClientConn) mainLoop() {
new(MsgSetColorMapEntries), new(MsgSetColorMapEntries),
new(MsgBell), new(MsgBell),
new(MsgServerCutText), new(MsgServerCutText),
new(MsgServerFence),
} }
for _, msg := range defaultMessages { for _, msg := range defaultMessages {
@ -506,7 +507,7 @@ func (c *ClientConn) mainLoop() {
// Unsupported message type! Bad! // Unsupported message type! Bad!
break break
} }
logger.Infof("ClientConn.MainLoop: got ServerMessage:%s", common.ServerMessageType(messageType)) logger.Debugf("ClientConn.MainLoop: got ServerMessage:%s", common.ServerMessageType(messageType))
reader.SendMessageStart(common.ServerMessageType(messageType)) reader.SendMessageStart(common.ServerMessageType(messageType))
reader.PublishBytes([]byte{byte(messageType)}) reader.PublishBytes([]byte{byte(messageType)})

View File

@ -4,7 +4,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
func readPixelFormat(r io.Reader, result *common.PixelFormat) error { func readPixelFormat(r io.Reader, result *common.PixelFormat) error {

View File

@ -6,9 +6,9 @@ import (
"fmt" "fmt"
"io" "io"
"strings" "strings"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/encodings" "github.com/amitbet/vncproxy/encodings"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
) )
// MsgFramebufferUpdate consists of a sequence of rectangles of // MsgFramebufferUpdate consists of a sequence of rectangles of
@ -63,7 +63,7 @@ func (fbm *MsgFramebufferUpdate) Read(c common.IClientConn, r *common.RfbReadHel
// We must always support the raw encoding // We must always support the raw encoding
rawEnc := new(encodings.RawEncoding) rawEnc := new(encodings.RawEncoding)
encMap[rawEnc.Type()] = rawEnc encMap[rawEnc.Type()] = rawEnc
logger.Infof("MsgFramebufferUpdate.Read: numrects= %d", numRects) logger.Debugf("MsgFramebufferUpdate.Read: numrects= %d", numRects)
rects := make([]common.Rectangle, numRects) rects := make([]common.Rectangle, numRects)
for i := uint16(0); i < numRects; i++ { for i := uint16(0); i < numRects; i++ {
@ -90,7 +90,7 @@ func (fbm *MsgFramebufferUpdate) Read(c common.IClientConn, r *common.RfbReadHel
encType := common.EncodingType(encodingTypeInt) encType := common.EncodingType(encodingTypeInt)
logger.Infof("MsgFramebufferUpdate.Read: rect# %d, rect hdr data: enctype=%s, data: %s", i, encType, string(jBytes)) logger.Debugf("MsgFramebufferUpdate.Read: rect# %d, rect hdr data: enctype=%s, data: %s", i, encType, string(jBytes))
enc, supported := encMap[encodingTypeInt] enc, supported := encMap[encodingTypeInt]
if supported { if supported {
var err error var err error
@ -129,7 +129,7 @@ type MsgSetColorMapEntries struct {
} }
func (fbm *MsgSetColorMapEntries) CopyTo(r io.Reader, w io.Writer, c common.IClientConn) error { func (fbm *MsgSetColorMapEntries) CopyTo(r io.Reader, w io.Writer, c common.IClientConn) error {
reader := &common.RfbReadHelper{Reader: r} reader := common.NewRfbReadHelper(r)
writeTo := &WriteTo{w, "MsgSetColorMapEntries.CopyTo"} writeTo := &WriteTo{w, "MsgSetColorMapEntries.CopyTo"}
reader.Listeners.AddListener(writeTo) reader.Listeners.AddListener(writeTo)
_, err := fbm.Read(c, reader) _, err := fbm.Read(c, reader)
@ -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

@ -2,8 +2,8 @@ package client
import ( import (
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
) )
type WriteTo struct { type WriteTo struct {

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

@ -73,6 +73,8 @@ func (enct EncodingType) String() string {
return "EncJPEGQualityLevelPseudo1" return "EncJPEGQualityLevelPseudo1"
case EncCursorPseudo: case EncCursorPseudo:
return "EncCursorPseudo" return "EncCursorPseudo"
case EncLedStatePseudo:
return "EncLedStatePseudo"
case EncDesktopSizePseudo: case EncDesktopSizePseudo:
return "EncDesktopSizePseudo" return "EncDesktopSizePseudo"
case EncLastRectPseudo: case EncLastRectPseudo:
@ -183,6 +185,7 @@ const (
EncQEMUPointerMotionChangePseudo EncodingType = -257 EncQEMUPointerMotionChangePseudo EncodingType = -257
EncQEMUExtendedKeyEventPseudo EncodingType = -258 EncQEMUExtendedKeyEventPseudo EncodingType = -258
EncTightPng EncodingType = -260 EncTightPng EncodingType = -260
EncLedStatePseudo EncodingType = -261
EncExtendedDesktopSizePseudo EncodingType = -308 EncExtendedDesktopSizePseudo EncodingType = -308
EncXvpPseudo EncodingType = -309 EncXvpPseudo EncodingType = -309
EncFencePseudo EncodingType = -312 EncFencePseudo EncodingType = -312

View File

@ -5,7 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
) )
var TightMinToCompress = 12 var TightMinToCompress = 12

View File

@ -3,7 +3,7 @@ package encodings
import ( import (
"encoding/binary" "encoding/binary"
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
type CopyRectEncoding struct { type CopyRectEncoding struct {

View File

@ -3,7 +3,7 @@ package encodings
import ( import (
"encoding/binary" "encoding/binary"
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
type CoRREEncoding struct { type CoRREEncoding struct {

View File

@ -3,7 +3,7 @@ package encodings
import ( import (
"io" "io"
"math" "math"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
type EncCursorPseudo struct { type EncCursorPseudo struct {

View File

@ -2,8 +2,8 @@ package encodings
import ( import (
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
) )
const ( const (

View File

@ -0,0 +1,31 @@
package encodings
import (
"io"
"github.com/amitbet/vncproxy/common"
"github.com/amitbet/vncproxy/logger"
)
type EncLedStatePseudo struct {
LedState uint8
}
func (pe *EncLedStatePseudo) Type() int32 {
return int32(common.EncLedStatePseudo)
}
func (pe *EncLedStatePseudo) WriteTo(w io.Writer) (n int, err error) {
w.Write([]byte{pe.LedState})
return 1, nil
}
func (pe *EncLedStatePseudo) Read(pf *common.PixelFormat, rect *common.Rectangle, r *common.RfbReadHelper) (common.IEncoding, error) {
if rect.Width*rect.Height == 0 {
return pe, nil
}
u8, err := r.ReadUint8()
pe.LedState = u8
if err != nil {
logger.Error("error while reading led state: ", err)
return pe, err
}
return pe, nil
}

View File

@ -2,7 +2,7 @@ package encodings
import ( import (
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
type PseudoEncoding struct { type PseudoEncoding struct {

View File

@ -3,7 +3,7 @@ package encodings
import ( import (
"bytes" "bytes"
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
// RawEncoding is raw pixel data sent by the server. // RawEncoding is raw pixel data sent by the server.

View File

@ -3,7 +3,7 @@ package encodings
import ( import (
"encoding/binary" "encoding/binary"
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
type RREEncoding struct { type RREEncoding struct {

View File

@ -4,8 +4,8 @@ import (
"bytes" "bytes"
"errors" "errors"
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
) )
var TightMinToCompress int = 12 var TightMinToCompress int = 12
@ -142,7 +142,7 @@ func handleTightFilters(subencoding uint8, pixelFmt *common.PixelFormat, rect *c
return return
} }
paletteSize := colorCount + 1 // add one more paletteSize := int(colorCount) + 1 // add one more
logger.Debugf("handleTightFilters: ----PALETTE_FILTER: paletteSize=%d bytesPixel=%d\n", paletteSize, bytesPixel) logger.Debugf("handleTightFilters: ----PALETTE_FILTER: paletteSize=%d bytesPixel=%d\n", paletteSize, bytesPixel)
//complete palette //complete palette
_, err = r.ReadBytes(int(paletteSize) * bytesPixel) _, err = r.ReadBytes(int(paletteSize) * bytesPixel)
@ -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

@ -3,8 +3,8 @@ package encodings
import ( import (
"fmt" "fmt"
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
) )
type TightPngEncoding struct { type TightPngEncoding struct {

View File

@ -4,7 +4,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
type ZLibEncoding struct { type ZLibEncoding struct {

View File

@ -4,7 +4,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
type ZRLEEncoding struct { type ZRLEEncoding struct {

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/amitbet/vncproxy
require golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76 h1:xx5MUFyRQRbPk6VjWjIE1epE/K5AoDD8QUN116NCy8k=
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

View File

@ -4,6 +4,30 @@ import "fmt"
var simpleLogger = SimpleLogger{LogLevelInfo} var simpleLogger = SimpleLogger{LogLevelInfo}
func SetLogLevel(logLevel string) {
level := GetLogLevel(logLevel)
fmt.Println("Log level set to: ", logLevel)
simpleLogger = SimpleLogger{level}
}
func GetLogLevel(logLevel string) LogLevel {
switch logLevel {
case "trace":
return LogLevelTrace
case "debug":
return LogLevelDebug
case "info":
return LogLevelInfo
case "warn":
return LogLevelWarn
case "error":
return LogLevelError
case "fatal":
return LogLevelFatal
}
return LogLevelInfo
}
type Logger interface { type Logger interface {
Debug(v ...interface{}) Debug(v ...interface{})
Debugf(format string, v ...interface{}) Debugf(format string, v ...interface{})
@ -127,6 +151,13 @@ func Debugf(format string, v ...interface{}) {
simpleLogger.Debugf(format, v...) simpleLogger.Debugf(format, v...)
} }
func Trace(v ...interface{}) {
simpleLogger.Trace(v...)
}
func Tracef(format string, v ...interface{}) {
simpleLogger.Tracef(format, v...)
}
func Info(v ...interface{}) { func Info(v ...interface{}) {
simpleLogger.Info(v...) simpleLogger.Info(v...)
} }

View File

@ -4,19 +4,21 @@ import (
"flag" "flag"
"fmt" "fmt"
"os" "os"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/encodings" "github.com/amitbet/vncproxy/encodings"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
"vncproxy/player" "github.com/amitbet/vncproxy/player"
"vncproxy/server" "github.com/amitbet/vncproxy/server"
) )
func main() { func main() {
wsPort := flag.String("wsPort", "", "websocket port for player to listen to client connections") wsPort := flag.String("wsPort", "", "websocket port for player to listen to client connections")
tcpPort := flag.String("tcpPort", "", "tcp port for player to listen to client connections") tcpPort := flag.String("tcpPort", "", "tcp port for player to listen to client connections")
fbsFile := flag.String("fbsFile", "", "fbs file to serve to all connecting clients") fbsFile := flag.String("fbsFile", "", "fbs file to serve to all connecting clients")
logLevel := flag.String("logLevel", "info", "change logging level")
flag.Parse() flag.Parse()
logger.SetLogLevel(*logLevel)
fmt.Println("**************************************************************************") fmt.Println("**************************************************************************")
fmt.Println("*** This is a toy server that replays a single FBS file to all clients ***") fmt.Println("*** This is a toy server that replays a single FBS file to all clients ***")
@ -80,7 +82,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" "github.com/amitbet/vncproxy/client"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"github.com/amitbet/vncproxy/logger"
"vncproxy/logger" "github.com/amitbet/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

@ -5,9 +5,9 @@ import (
"encoding/binary" "encoding/binary"
"io" "io"
"os" "os"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/encodings" "github.com/amitbet/vncproxy/encodings"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
) )
type FbsReader struct { type FbsReader struct {
@ -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) {
@ -53,6 +58,7 @@ func NewFbsReader(fbsFile string) (*FbsReader, error) {
&encodings.TightEncoding{}, &encodings.TightEncoding{},
&encodings.TightPngEncoding{}, &encodings.TightPngEncoding{},
&encodings.EncCursorPseudo{}, &encodings.EncCursorPseudo{},
&encodings.EncLedStatePseudo{},
&encodings.RawEncoding{}, &encodings.RawEncoding{},
&encodings.RREEncoding{}, &encodings.RREEncoding{},
}, },

View File

@ -3,14 +3,15 @@ package player
import ( import (
"testing" "testing"
"time" "time"
"vncproxy/common"
"vncproxy/encodings" "github.com/amitbet/vncproxy/common"
"vncproxy/logger" "github.com/amitbet/vncproxy/encodings"
"vncproxy/server" "github.com/amitbet/vncproxy/logger"
"github.com/amitbet/vncproxy/server"
) )
func TestServer(t *testing.T) { func TestServer(t *testing.T) {
t.Skip("this isn't an automated test, just an entrypoint for debugging")
//chServer := make(chan common.ClientMessage) //chServer := make(chan common.ClientMessage)
//chClient := make(chan common.ServerMessage) //chClient := make(chan common.ServerMessage)
@ -18,6 +19,7 @@ func TestServer(t *testing.T) {
&encodings.RawEncoding{}, &encodings.RawEncoding{},
&encodings.TightEncoding{}, &encodings.TightEncoding{},
&encodings.EncCursorPseudo{}, &encodings.EncCursorPseudo{},
&encodings.EncLedStatePseudo{},
//encodings.TightPngEncoding{}, //encodings.TightPngEncoding{},
&encodings.RREEncoding{}, &encodings.RREEncoding{},
&encodings.ZLibEncoding{}, &encodings.ZLibEncoding{},

View File

@ -1,9 +1,13 @@
package main package main
import "vncproxy/proxy" import (
import "flag" "flag"
import "vncproxy/logger" "os"
import "os" "path/filepath"
"github.com/amitbet/vncproxy/logger"
vncproxy "github.com/amitbet/vncproxy/proxy"
)
func main() { func main() {
//create default session if required //create default session if required
@ -11,10 +15,14 @@ func main() {
var wsPort = flag.String("wsPort", "", "websocket port") var wsPort = flag.String("wsPort", "", "websocket port")
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 targetVnc = flag.String("target", "", "target vnc server (host:port or /path/to/unix.socket)")
var targetVncPort = flag.String("targPort", "", "target vnc server port (deprecated, use -target)")
var targetVncHost = flag.String("targHost", "", "target vnc server host (deprecated, use -target)")
var targetVncPass = flag.String("targPass", "", "target vnc password") var targetVncPass = flag.String("targPass", "", "target vnc password")
var logLevel = flag.String("logLevel", "info", "change logging level")
flag.Parse() flag.Parse()
logger.SetLogLevel(*logLevel)
if *tcpPort == "" && *wsPort == "" { if *tcpPort == "" && *wsPort == "" {
logger.Error("no listening port defined") logger.Error("no listening port defined")
@ -22,8 +30,8 @@ func main() {
os.Exit(1) os.Exit(1)
} }
if *targetVncPort == "" { if *targetVnc == "" && *targetVncPort == "" {
logger.Error("no target vnc server port defined") logger.Error("no target vnc server host/port or socket defined")
flag.Usage() flag.Usage()
os.Exit(1) os.Exit(1)
} }
@ -31,25 +39,42 @@ func main() {
if *vncPass == "" { if *vncPass == "" {
logger.Warn("proxy will have no password") logger.Warn("proxy will have no password")
} }
if *recordDir == "" {
logger.Warn("FBS recording is turned off")
}
proxy := &proxy.VncProxy{ tcpURL := ""
WsListeningUrl: "http://localhost:" + string(*wsPort) + "/", // empty = not listening on ws if *tcpPort != "" {
RecordingDir: *recordDir, //"/Users/amitbet/vncRec", // empty = no recording tcpURL = ":" + string(*tcpPort)
TcpListeningUrl: ":" + string(*tcpPort), }
wsURL := ""
if *wsPort != "" {
wsURL = "http://0.0.0.0:" + string(*wsPort) + "/"
}
proxy := &vncproxy.VncProxy{
WsListeningURL: wsURL, // empty = not listening on ws
TCPListeningURL: tcpURL,
ProxyVncPassword: *vncPass, //empty = no auth ProxyVncPassword: *vncPass, //empty = no auth
SingleSession: &proxy.VncSession{ SingleSession: &vncproxy.VncSession{
TargetHostname: "localhost", Target: *targetVnc,
TargetHostname: *targetVncHost,
TargetPort: *targetVncPort, TargetPort: *targetVncPort,
TargetPassword: *targetVncPass, //"vncPass", TargetPassword: *targetVncPass, //"vncPass",
ID: "dummySession", ID: "dummySession",
Status: proxy.SessionStatusInit, Status: vncproxy.SessionStatusInit,
Type: proxy.SessionTypeRecordingProxy, Type: vncproxy.SessionTypeProxyPass,
}, // to be used when not using sessions }, // to be used when not using sessions
UsingSessions: false, //false = single session - defined in the var above UsingSessions: false, //false = single session - defined in the var above
} }
if *recordDir != "" {
fullPath, err := filepath.Abs(*recordDir)
if err != nil {
logger.Error("bad recording path: ", err)
}
logger.Info("FBS recording is turned on, writing to dir: ", fullPath)
proxy.RecordingDir = fullPath
proxy.SingleSession.Type = vncproxy.SessionTypeRecordingProxy
} else {
logger.Info("FBS recording is turned off")
}
proxy.StartListening() proxy.StartListening()
} }

View File

@ -1,10 +1,10 @@
package proxy package proxy
import ( import (
"vncproxy/client" "github.com/amitbet/vncproxy/client"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
"vncproxy/server" "github.com/amitbet/vncproxy/server"
) )
type ClientUpdater struct { type ClientUpdater struct {
@ -13,7 +13,7 @@ type ClientUpdater struct {
// Consume recieves vnc-server-bound messages (Client messages) and updates the server part of the proxy // Consume recieves vnc-server-bound messages (Client messages) and updates the server part of the proxy
func (cc *ClientUpdater) Consume(seg *common.RfbSegment) error { func (cc *ClientUpdater) Consume(seg *common.RfbSegment) error {
//logger.Debugf("ClientUpdater.Consume (vnc-server-bound): got segment type=%s bytes: %v", seg.SegmentType, seg.Bytes) logger.Tracef("ClientUpdater.Consume (vnc-server-bound): got segment type=%s bytes: %v", seg.SegmentType, seg.Bytes)
switch seg.SegmentType { switch seg.SegmentType {
case common.SegmentFullyParsedClientMessage: case common.SegmentFullyParsedClientMessage:

View File

@ -5,18 +5,19 @@ import (
"path" "path"
"strconv" "strconv"
"time" "time"
"vncproxy/client"
"vncproxy/common" "github.com/amitbet/vncproxy/client"
"vncproxy/encodings" "github.com/amitbet/vncproxy/common"
"vncproxy/logger" "github.com/amitbet/vncproxy/encodings"
"vncproxy/player" "github.com/amitbet/vncproxy/logger"
listeners "vncproxy/recorder" "github.com/amitbet/vncproxy/player"
"vncproxy/server" listeners "github.com/amitbet/vncproxy/recorder"
"github.com/amitbet/vncproxy/server"
) )
type VncProxy struct { type VncProxy struct {
TcpListeningUrl string // empty = not listening on tcp TCPListeningURL string // empty = not listening on tcp
WsListeningUrl string // empty = not listening on ws WsListeningURL string // empty = not listening on ws
RecordingDir string // empty = no recording RecordingDir string // empty = no recording
ProxyVncPassword string //empty = no auth ProxyVncPassword string //empty = no auth
SingleSession *VncSession // to be used when not using sessions SingleSession *VncSession // to be used when not using sessions
@ -24,8 +25,17 @@ type VncProxy struct {
sessionManager *SessionManager sessionManager *SessionManager
} }
func (vp *VncProxy) createClientConnection(targetServerUrl string, vncPass string) (*client.ClientConn, error) { func (vp *VncProxy) createClientConnection(target string, vncPass string) (*client.ClientConn, error) {
nc, err := net.Dial("tcp", targetServerUrl) var (
nc net.Conn
err error
)
if target[0] == '/' {
nc, err = net.Dial("unix", target)
} else {
nc, err = net.Dial("tcp", target)
}
if err != nil { if err != nil {
logger.Errorf("error connecting to vnc server: %s", err) logger.Errorf("error connecting to vnc server: %s", err)
@ -62,7 +72,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 +80,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
@ -84,7 +95,12 @@ func (vp *VncProxy) newServerConnHandler(cfg *server.ServerConfig, sconn *server
session.Status = SessionStatusInit session.Status = SessionStatusInit
if session.Type == SessionTypeProxyPass || session.Type == SessionTypeRecordingProxy { if session.Type == SessionTypeProxyPass || session.Type == SessionTypeRecordingProxy {
cconn, err := vp.createClientConnection(session.TargetHostname+":"+session.TargetPort, session.TargetPassword) target := session.Target
if session.TargetHostname != "" && session.TargetPort != "" {
target = session.TargetHostname + ":" + session.TargetPort
}
cconn, err := vp.createClientConnection(target, session.TargetPassword)
if err != nil { if err != nil {
session.Status = SessionStatusError session.Status = SessionStatusError
logger.Errorf("Proxy.newServerConnHandler error creating connection: %s", err) logger.Errorf("Proxy.newServerConnHandler error creating connection: %s", err)
@ -117,6 +133,7 @@ func (vp *VncProxy) newServerConnHandler(cfg *server.ServerConfig, sconn *server
&encodings.RawEncoding{}, &encodings.RawEncoding{},
&encodings.TightEncoding{}, &encodings.TightEncoding{},
&encodings.EncCursorPseudo{}, &encodings.EncCursorPseudo{},
&encodings.EncLedStatePseudo{},
&encodings.TightPngEncoding{}, &encodings.TightPngEncoding{},
&encodings.RREEncoding{}, &encodings.RREEncoding{},
&encodings.ZLibEncoding{}, &encodings.ZLibEncoding{},
@ -169,19 +186,19 @@ func (vp *VncProxy) StartListening() {
UseDummySession: !vp.UsingSessions, UseDummySession: !vp.UsingSessions,
} }
if vp.TcpListeningUrl != "" && vp.WsListeningUrl != "" { if vp.TCPListeningURL != "" && vp.WsListeningURL != "" {
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 != "" {
logger.Infof("running ws listener url: %s", vp.WsListeningUrl) logger.Infof("running ws listener url: %s", vp.WsListeningURL)
server.WsServe(vp.WsListeningUrl, cfg) server.WsServe(vp.WsListeningURL, cfg)
} }
if vp.TcpListeningUrl != "" { if vp.TCPListeningURL != "" {
logger.Infof("running tcp listener on port: %s", vp.TcpListeningUrl) logger.Infof("running tcp listener on port: %s", vp.TCPListeningURL)
server.TcpServe(":"+vp.TcpListeningUrl, cfg) server.TcpServe(vp.TCPListeningURL, cfg)
} }
} }

View File

@ -4,17 +4,18 @@ import "testing"
func TestProxy(t *testing.T) { func TestProxy(t *testing.T) {
//create default session if required //create default session if required
t.Skip("this isn't an automated test, just an entrypoint for debugging")
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

@ -16,6 +16,7 @@ const (
) )
type VncSession struct { type VncSession struct {
Target string
TargetHostname string TargetHostname string
TargetPort string TargetPort string
TargetPassword string TargetPassword string

View File

@ -5,22 +5,31 @@ import (
"net" "net"
"os" "os"
"time" "time"
"vncproxy/client" "github.com/amitbet/vncproxy/client"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/encodings" "github.com/amitbet/vncproxy/encodings"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
"vncproxy/recorder" "github.com/amitbet/vncproxy/recorder"
) )
func main() { func main() {
// var tcpPort = flag.String("tcpPort", "", "tcp port") // var tcpPort = flag.String("tcpPort", "", "tcp port")
// var wsPort = flag.String("wsPort", "", "websocket port") // var wsPort = flag.String("wsPort", "", "websocket port")
// 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 EMPTY.") var recordDir = flag.String("recFile", "", "FBS file to create, 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")
var logLevel = flag.String("logLevel", "info", "change logging level")
flag.Parse() flag.Parse()
logger.SetLogLevel(*logLevel)
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")
@ -33,10 +42,12 @@ func main() {
} }
if *recordDir == "" { if *recordDir == "" {
logger.Warn("FBS recording is turned off") logger.Warn("FBS recording is turned off")
} else {
logger.Infof("Recording rfb stream into file: '%s'", *recordDir)
} }
//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)
@ -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

@ -5,9 +5,9 @@ import (
"encoding/binary" "encoding/binary"
"os" "os"
"time" "time"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
"vncproxy/server" "github.com/amitbet/vncproxy/server"
) )
type Recorder struct { type Recorder struct {
@ -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

View File

@ -2,9 +2,9 @@ package recorder
import ( import (
"time" "time"
"vncproxy/client" "github.com/amitbet/vncproxy/client"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
) )
type RfbRequester struct { type RfbRequester struct {

View File

@ -3,7 +3,7 @@ package server
import ( import (
"encoding/binary" "encoding/binary"
"io" "io"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
// Key represents a VNC key press. // Key represents a VNC key press.
@ -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
@ -280,3 +310,34 @@ func (msg *MsgClientCutText) Write(c io.Writer) error {
return nil return nil
} }
// MsgClientQemuExtendedKey holds the wire format message, for qemu keys
type MsgClientQemuExtendedKey struct {
SubType uint8 // sub type
IsDown uint16 // button down indicator
KeySym uint32 // key symbol
KeyCode uint32 // key code
}
func (*MsgClientQemuExtendedKey) Type() common.ClientMessageType {
return common.QEMUExtendedKeyEventMsgType
}
func (*MsgClientQemuExtendedKey) Read(c io.Reader) (common.ClientMessage, error) {
msg := MsgClientQemuExtendedKey{}
if err := binary.Read(c, binary.BigEndian, &msg); err != nil {
return nil, err
}
return &msg, nil
}
func (msg *MsgClientQemuExtendedKey) 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
}

View File

@ -3,10 +3,10 @@ package server
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"io" "io"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
) )
const ProtoVersionLength = 12 const ProtoVersionLength = 12
@ -95,7 +95,7 @@ func ServerSecurityHandler(cfg *ServerConfig, c *ServerConn) error {
sType, ok := secTypes[secType] sType, ok := secTypes[secType]
if !ok { if !ok {
return fmt.Errorf("server type %d not implemented") return fmt.Errorf("server type %d not implemented", secType)
} }
var authCode uint32 var authCode uint32
@ -135,7 +135,7 @@ func ServerServerInitHandler(cfg *ServerConfig, c *ServerConn) error {
NameLength: uint32(len(cfg.DesktopName)), NameLength: uint32(len(cfg.DesktopName)),
NameText: []byte(cfg.DesktopName), NameText: []byte(cfg.DesktopName),
} }
logger.Infof("Server.ServerServerInitHandler initMessage: %v", srvInit) logger.Debugf("Server.ServerServerInitHandler initMessage: %v", srvInit)
if err := binary.Write(c, binary.BigEndian, srvInit.FBWidth); err != nil { if err := binary.Write(c, binary.BigEndian, srvInit.FBWidth); err != nil {
return err return err
} }

View File

@ -6,7 +6,7 @@ import (
"crypto/rand" "crypto/rand"
"errors" "errors"
"log" "log"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
type SecurityType uint8 type SecurityType uint8

View File

@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"io" "io"
"sync" "sync"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
) )
type ServerConn struct { type ServerConn struct {
@ -168,16 +168,17 @@ func (c *ServerConn) handle() error {
default: default:
var messageType common.ClientMessageType var messageType common.ClientMessageType
if err := binary.Read(c, binary.BigEndian, &messageType); err != nil { if err := binary.Read(c, binary.BigEndian, &messageType); err != nil {
logger.Errorf("IServerConn.handle error: %v", err) logger.Errorf("ServerConn.handle error: %v", err)
return err return err
} }
logger.Debugf("ServerConn.handle: got messagetype, %d", messageType)
msg, ok := clientMessages[messageType] msg, ok := clientMessages[messageType]
logger.Debugf("ServerConn.handle: found message type, %v", ok)
if !ok { if !ok {
return fmt.Errorf("IServerConn.Handle: unsupported message-type: %v", messageType) logger.Errorf("ServerConn.handle: unsupported message-type: %v", messageType)
} }
parsedMsg, err := msg.Read(c) parsedMsg, err := msg.Read(c)
logger.Debugf("ServerConn.handle: got parsed messagetype, %v", parsedMsg)
//update connection for pixel format / color map changes //update connection for pixel format / color map changes
switch parsedMsg.Type() { switch parsedMsg.Type() {
case common.SetPixelFormatMsgType: case common.SetPixelFormatMsgType:
@ -196,7 +197,7 @@ func (c *ServerConn) handle() error {
return err return err
} }
logger.Infof("IServerConn.Handle got ClientMessage: %s, %v", parsedMsg.Type(), parsedMsg) logger.Debugf("IServerConn.Handle got ClientMessage: %s, %v", parsedMsg.Type(), parsedMsg)
//TODO: treat set encodings by allowing only supported encoding in proxy configurations //TODO: treat set encodings by allowing only supported encoding in proxy configurations
//// if parsedMsg.Type() == common.SetEncodingsMsgType{ //// if parsedMsg.Type() == common.SetEncodingsMsgType{
//// c.cfg.Encodings //// c.cfg.Encodings

View File

@ -5,7 +5,7 @@ import (
"io" "io"
"log" "log"
"net" "net"
"vncproxy/common" "github.com/amitbet/vncproxy/common"
) )
var DefaultClientMessages = []common.ClientMessage{ var DefaultClientMessages = []common.ClientMessage{
@ -15,6 +15,7 @@ var DefaultClientMessages = []common.ClientMessage{
&MsgKeyEvent{}, &MsgKeyEvent{},
&MsgPointerEvent{}, &MsgPointerEvent{},
&MsgClientCutText{}, &MsgClientCutText{},
&MsgClientQemuExtendedKey{},
} }
// FramebufferUpdate holds a FramebufferUpdate wire format message. // FramebufferUpdate holds a FramebufferUpdate wire format message.

View File

@ -3,8 +3,9 @@ package server
import ( import (
"log" "log"
"testing" "testing"
"vncproxy/common"
"vncproxy/encodings" "github.com/amitbet/vncproxy/common"
"github.com/amitbet/vncproxy/encodings"
) )
func newServerConnHandler(cfg *ServerConfig, conn *ServerConn) error { func newServerConnHandler(cfg *ServerConfig, conn *ServerConn) error {
@ -13,6 +14,7 @@ func newServerConnHandler(cfg *ServerConfig, conn *ServerConn) error {
} }
func TestServer(t *testing.T) { func TestServer(t *testing.T) {
t.Skip("this isn't an automated test, just an entrypoint for debugging")
//chServer := make(chan common.ClientMessage) //chServer := make(chan common.ClientMessage)
chClient := make(chan common.ServerMessage) chClient := make(chan common.ServerMessage)

View File

@ -4,7 +4,7 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"vncproxy/logger" "github.com/amitbet/vncproxy/logger"
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
) )

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