diff --git a/go-rvpn-client/main.go b/go-rvpn-client/main.go new file mode 100644 index 0000000..03e4585 --- /dev/null +++ b/go-rvpn-client/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "git.daplie.com/Daplie/go-rvpn-server/rvpn/client" + jwt "github.com/dgrijalva/jwt-go" +) + +func main() { + tokenData := jwt.MapClaims{ + "domains": []string{ + "localhost.foo.daplie.me", + "localhost.bar.daplie.me", + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, tokenData) + tokenStr, err := token.SignedString([]byte("abc123")) + if err != nil { + panic(err) + } + + config := client.Config{ + Server: "wss://localhost.daplie.me:9999", + Services: map[string]int{"https": 8443}, + Token: tokenStr, + Insecure: true, + } + panic(client.Run(&config)) +} diff --git a/rvpn/client/client.go b/rvpn/client/client.go new file mode 100644 index 0000000..c9e2450 --- /dev/null +++ b/rvpn/client/client.go @@ -0,0 +1,61 @@ +package client + +import ( + "crypto/tls" + "net/url" + + "fmt" + + "git.daplie.com/Daplie/go-rvpn-server/rvpn/packer" + "github.com/gorilla/websocket" +) + +type Config struct { + Server string + Token string + Services map[string]int + Insecure bool +} + +func Run(config *Config) error { + serverURL, err := url.Parse(config.Server) + if err != nil { + return fmt.Errorf("Invalid server URL: %v", err) + } + if serverURL.Scheme == "" { + serverURL.Scheme = "wss" + } + serverURL.Path = "" + + query := make(url.Values) + query.Set("access_token", config.Token) + serverURL.RawQuery = query.Encode() + + dialer := websocket.Dialer{} + if config.Insecure { + dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } + + conn, _, err := dialer.Dial(serverURL.String(), nil) + if err != nil { + return fmt.Errorf("First connection to server failed - check auth: %v", err) + } + + localConns := newLocalConns(conn, config.Services) + for { + _, message, err := conn.ReadMessage() + if err != nil { + return fmt.Errorf("websocket read errored: %v", err) + } + + p, err := packer.ReadMessage(message) + if err != nil { + return fmt.Errorf("packer read failed: %v", err) + } + + err = localConns.Write(p) + if err != nil { + return fmt.Errorf("failed to write data: %v", err) + } + } +} diff --git a/rvpn/client/local_conns.go b/rvpn/client/local_conns.go new file mode 100644 index 0000000..d434449 --- /dev/null +++ b/rvpn/client/local_conns.go @@ -0,0 +1,83 @@ +package client + +import ( + "fmt" + "net" + "sync" + + "github.com/gorilla/websocket" + + "io" + + "git.daplie.com/Daplie/go-rvpn-server/rvpn/packer" +) + +type localConns struct { + lock sync.RWMutex + locals map[string]net.Conn + services map[string]int + remote *websocket.Conn +} + +func newLocalConns(remote *websocket.Conn, services map[string]int) *localConns { + l := new(localConns) + l.services = services + l.remote = remote + l.locals = make(map[string]net.Conn) + return l +} + +func (l *localConns) Write(p *packer.Packer) error { + l.lock.RLock() + defer l.lock.RUnlock() + + key := fmt.Sprintf("%s:%d", p.Header.Address(), p.Header.Port) + if conn := l.locals[key]; conn != nil { + _, err := conn.Write(p.Data.Data()) + return err + } + + go l.startConnection(p) + return nil +} + +func (l *localConns) startConnection(orig *packer.Packer) { + key := fmt.Sprintf("%s:%d", orig.Header.Address(), orig.Header.Port) + addr := fmt.Sprintf("127.0.0.1:%d", l.services[orig.Header.Service]) + conn, err := net.Dial("tcp", addr) + if err != nil { + loginfo.Println("failed to open connection to", addr, err) + return + } + loginfo.Println("opened connection to", addr, "with key", key) + defer loginfo.Println("finished connection to", addr, "with key", key) + + conn.Write(orig.Data.Data()) + + l.lock.Lock() + l.locals[key] = conn + l.lock.Unlock() + defer func() { + l.lock.Lock() + delete(l.locals, key) + l.lock.Unlock() + conn.Close() + }() + + buf := make([]byte, 4096) + for { + size, err := conn.Read(buf) + if err != nil { + if err != io.EOF { + loginfo.Println("failed to read from local connection to", addr, err) + } + return + } + + p := packer.NewPacker() + p.Header = orig.Header + p.Data.AppendBytes(buf[:size]) + packed := p.PackV1() + l.remote.WriteMessage(websocket.BinaryMessage, packed.Bytes()) + } +} diff --git a/rvpn/client/setup.go b/rvpn/client/setup.go new file mode 100644 index 0000000..e08f076 --- /dev/null +++ b/rvpn/client/setup.go @@ -0,0 +1,15 @@ +package client + +import ( + "log" + "os" +) + +const ( + logFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile +) + +var ( + loginfo = log.New(os.Stdout, "INFO: client: ", logFlags) + logdebug = log.New(os.Stdout, "DEBUG: client:", logFlags) +)