Skip to content

Commit

Permalink
feat: support RDP connection to private IP machines (#75)
Browse files Browse the repository at this point in the history
* feat: support intranet desktop connect

* fix: lint

* fix: lint

* fix: clean code

* fix: clean code

* fix: clean code

* fix: clean code

* fix: clean code

* fix: clean code

* fix: support hot restart client
  • Loading branch information
leo220yuyaodog committed Apr 7, 2024
1 parent 69775e3 commit 4d5adc8
Show file tree
Hide file tree
Showing 16 changed files with 1,002 additions and 28 deletions.
1 change: 1 addition & 0 deletions conf/app.conf
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ clientId = b108dacba027db36ec26
clientSecret = 124140638b4f9de7e78e79ba22d451c17bfa9688
casdoorOrganization = "casbin"
casdoorApplication = "app-casvisor"
gatewayEndpoint =
22 changes: 22 additions & 0 deletions conf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,28 @@
package conf

import (
"net"
"os"
"runtime"
"strings"

"github.com/beego/beego"
"github.com/casvisor/casvisor/util"
)

var GatewayAddr *net.TCPAddr

func init() {
var err error
gatewayEndpoint := GetConfigString("gatewayEndpoint")
if gatewayEndpoint != "" {
GatewayAddr, err = net.ResolveTCPAddr("tcp", gatewayEndpoint)
if err != nil {
panic("failed to resolve gateway address %s")
}
}
}

func GetConfigString(key string) string {
if value, ok := os.LookupEnv(key); ok {
return value
Expand All @@ -33,6 +48,8 @@ func GetConfigString(key string) string {
res = "https://cdn.casbin.org"
} else if key == "logConfig" {
res = "{\"filename\": \"logs/casdoor.log\", \"maxdays\":99999, \"perm\":\"0770\"}"
} else if key == "dbName" {
res = "casvisor"
}
}

Expand All @@ -48,6 +65,11 @@ func GetConfigBool(key string) bool {
}
}

func GetConfigInt(key string) int {
value := GetConfigString(key)
return util.ParseInt(value)
}

func GetConfigDataSourceName() string {
dataSourceName := GetConfigString("dataSourceName")

Expand Down
15 changes: 13 additions & 2 deletions controllers/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/beego/beego"
"github.com/beego/beego/logs"
"github.com/casvisor/casvisor/conf"
"github.com/casvisor/casvisor/object"
"github.com/casvisor/casvisor/util"
"github.com/casvisor/casvisor/util/guacamole"
Expand Down Expand Up @@ -275,8 +276,18 @@ func setConfig(propertyMap map[string]string, asset *object.Asset, configuration
configuration.Protocol = "vnc"
}

configuration.SetParameter("hostname", asset.Endpoint)
configuration.SetParameter("port", strconv.Itoa(asset.Port))
if asset.GatewayPort != 0 {
if conf.GatewayAddr != nil {
configuration.SetParameter("hostname", conf.GatewayAddr.IP.String())
} else {
configuration.SetParameter("hostname", "localhost")
}
configuration.SetParameter("port", strconv.Itoa(asset.GatewayPort))
} else {
configuration.SetParameter("hostname", asset.Endpoint)
configuration.SetParameter("port", strconv.Itoa(asset.Port))
}

configuration.SetParameter("username", asset.Username)
configuration.SetParameter("password", asset.Password)

Expand Down
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.1
github.com/qiangmzsx/string-adapter/v2 v2.2.0
github.com/sheerun/queue v1.0.1
github.com/ua-parser/uap-go v0.0.0-20240113215029-33f8e6d47f38
xorm.io/core v0.7.3
xorm.io/xorm v1.3.8
Expand Down Expand Up @@ -41,14 +42,14 @@ require (
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/xorm-io/builder v0.3.13 // indirect
github.com/xorm-io/xorm v1.1.6 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/oauth2 v0.16.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.9.1 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.32.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/token v1.1.0 // indirect
Expand Down
14 changes: 8 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/sheerun/queue v1.0.1 h1:TIAQyN0aRRvrJcNa2beZFfxwuxrfXBc9Mj+UWDNH7Ao=
github.com/sheerun/queue v1.0.1/go.mod h1:YtjrWT5jymvCLo/lEWDk3sv7A1Kgj0qcl3SZx7Zmcfo=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 h1:v9ezJDHA1XGxViAUSIoO/Id7Fl63u6d0YmsAm+/p2hs=
github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02/go.mod h1:RF16/A3L0xSa0oSERcnhd8Pu3IXSDZSK2gmGIMsttFE=
Expand Down Expand Up @@ -214,8 +216,8 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -252,8 +254,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand Down Expand Up @@ -281,8 +283,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
_ "github.com/beego/beego/session/redis"
"github.com/casvisor/casvisor/authz"
"github.com/casvisor/casvisor/object"
"github.com/casvisor/casvisor/proxy"
"github.com/casvisor/casvisor/routers"
"github.com/casvisor/casvisor/task"
"github.com/casvisor/casvisor/util"
Expand Down Expand Up @@ -56,5 +57,7 @@ func main() {

task.NewTicker().SetupTicker()

go proxy.NewStarter(object.RestartClientChan).Start()

beego.Run()
}
15 changes: 13 additions & 2 deletions object/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ import (
"xorm.io/core"
)

var dataStore *dbgate.JsonLinesDatabase
var (
dataStore *dbgate.JsonLinesDatabase
RestartClientChan chan string
)

func init() {
dataStore = dbgate.NewConnectionDataStore()
RestartClientChan = make(chan string)
}

type Service struct {
Expand Down Expand Up @@ -70,6 +74,7 @@ type Asset struct {
EnableRemoteApp bool `json:"enableRemoteApp"`
RemoteApps []*RemoteApp `json:"remoteApps"`
Services []*Service `json:"services"`
GatewayPort int `json:"gatewayPort"`

Id string `xorm:"varchar(100)" json:"id"`
DatabaseUrl string `xorm:"varchar(200)" json:"databaseUrl"`
Expand Down Expand Up @@ -218,7 +223,7 @@ func DeleteAsset(asset *Asset) (bool, error) {
return affected != 0, nil
}

func (asset *Asset) getId() string {
func (asset *Asset) GetId() string {
return fmt.Sprintf("%s/%s", asset.Owner, asset.Name)
}

Expand Down Expand Up @@ -254,6 +259,12 @@ func (asset *Asset) toConnection() *dbgate.Connection {
}

func AssetHook(asset *Asset, oldAsset *Asset, action string) error {
if action == "update" && asset.Category == "Machine" {
if asset.GatewayPort != oldAsset.GatewayPort {
RestartClientChan <- asset.Name
}
}

if oldAsset != nil {
if oldAsset.Category == "Database" && asset.Category != "Database" {
err := dataStore.Remove(asset.Id)
Expand Down
160 changes: 160 additions & 0 deletions proxy/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright 2024 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
"errors"
"fmt"
"time"

"github.com/casvisor/casvisor/proxy/tunnel"
)

type Client struct {
Name string
LocalPort int
RemoteAddr string
RemotePort int
proxyApps map[string]*tunnel.AppInfo
heartbeatChan chan *tunnel.Message // when get heartbeat msg, put msg in
conn *tunnel.Conn
}

func NewClient(name string, remoteAddr string, remotePort int, appInfo *tunnel.AppInfo) *Client {
client := &Client{
Name: name,
RemoteAddr: remoteAddr,
RemotePort: remotePort,
heartbeatChan: make(chan *tunnel.Message, 1),
proxyApps: make(map[string]*tunnel.AppInfo),
}

if appInfo != nil {
client.proxyApps[appInfo.Name] = appInfo
}
return client
}

func (c *Client) sendInitAppMsg() error {
if c.proxyApps == nil {
return errors.New("proxyApps is nil")
}

msg := tunnel.NewMessage(tunnel.InitApp, "", c.Name, c.proxyApps)
if err := c.conn.SendMessage(msg); err != nil {
return err
}
return nil
}

func (c *Client) storeAppServer(msg *tunnel.Message) {
println("---------- Sever ----------")
for name, app := range c.proxyApps {
fmt.Printf("[%s]:\t%s:%d\n", name, c.conn.GetRemoteIP(), app.ListenPort)
}
println("---------------------------")

// prepared, start first heartbeat
c.heartbeatChan <- msg

// keep Heartbeat
go func() {
for {
select {
case <-c.heartbeatChan:
time.Sleep(tunnel.HeartbeatInterval)
resp := tunnel.NewMessage(tunnel.ClientHeartbeat, "", c.Name, nil)
err := c.conn.SendMessage(resp)
if err != nil {
return
}
case <-time.After(tunnel.HeartbeatTimeout):
if c.conn != nil {
c.conn.Close()
return
}
}
}
}()
}

func (c *Client) handleBindMsg(msg *tunnel.Message) {
appProxyName := msg.Content
if appProxyName == "" {
return
}

appInfo, ok := c.proxyApps[appProxyName]
if !ok {
return
}

localConn, err := tunnel.Dial(appInfo.LocalAddress, appInfo.LocalPort)
if err != nil {
defer localConn.Close()
panic(err)
return
}

remoteConn, err := tunnel.Dial(c.RemoteAddr, appInfo.ListenPort)
if err != nil {
defer remoteConn.Close()
panic(err)
return
}

bindMsg := tunnel.NewMessage(tunnel.ClientBind, msg.Content, c.Name, nil)
err = remoteConn.SendMessage(bindMsg)
if err != nil {
panic(err)
return
}

go tunnel.Bind(localConn, remoteConn)
}

func (c *Client) Start(startClientChan chan string) {
conn, err := tunnel.Dial(c.RemoteAddr, c.RemotePort)
if err != nil {
return
}
c.conn = conn
defer c.conn.Close()

err = c.sendInitAppMsg()
if err != nil {
return
}

for {
msg, err := c.conn.ReadMessage()
if err != nil {
return
}

switch msg.Type {
case tunnel.ServerHeartbeat:
c.heartbeatChan <- msg
case tunnel.AppMsg:
c.storeAppServer(msg)
case tunnel.AppWaitBind:
go c.handleBindMsg(msg)
case tunnel.ClientRestart:
c.conn.Close()
startClientChan <- msg.Name
return
}
}
}

0 comments on commit 4d5adc8

Please sign in to comment.