├── .github └── workflows │ ├── build-client.yml │ └── build-gtuncli.yml ├── .gitignore ├── LICENSE ├── README.md ├── build_client.sh ├── build_gtuncli.sh ├── build_server.sh ├── build_server_debug.sh ├── common ├── auth.go ├── auth_test.go ├── connection.go ├── constants.go ├── endpoint.go ├── socksserver.go ├── tunnel.go └── util.go ├── gclient ├── Dockerfile ├── builder │ └── gclient_build.go ├── cgo.go └── gClient.go ├── go.mod ├── go.sum ├── grpc ├── admin │ └── admin.proto ├── build_protoc.sh └── client │ └── client.proto ├── gserver ├── Dockerfile ├── Dockerfile.debug ├── gServer.go └── gserverlib │ ├── adminService.go │ ├── auth.go │ ├── clientService.go │ ├── configStore.go │ └── gServerLib.go ├── gtuncli └── gtuncli.go ├── start_server.sh ├── start_server_debug.sh └── testdata ├── .gtunnel.conf ├── cert └── key /.github/workflows/build-client.yml: -------------------------------------------------------------------------------- 1 | name: build-clients 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: 1.20.3 20 | 21 | - name: Set path 22 | run: echo "/protoc/bin:$GOPATH/bin" >> $GITHUB_PATH 23 | 24 | - name: Install system dependencies 25 | run: | 26 | wget https://github.com/protocolbuffers/protobuf/releases/download/v22.3/protoc-22.3-linux-x86_64.zip 27 | sudo unzip protoc-22.3-linux-x86_64.zip -d /protoc 28 | rm protoc-22.3-linux-x86_64.zip 29 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 30 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 31 | 32 | - name: Build protoc 33 | run: | 34 | cd grpc 35 | ./build_protoc.sh 36 | cd .. 37 | 38 | - name: Get packages 39 | run: go get -d -v ./... && go install -v ./... 40 | 41 | - name: Build windows 32 bit exe 42 | run: | 43 | ./build_client.sh -host 127.0.0.1 -name dummy -outputfile dummywin32.exe -bintype exe -platform win -arch x86 44 | 45 | - name: Build windows 32 bit dll 46 | run: | 47 | ./build_client.sh -host 127.0.0.1 -name dummy -outputfile dummywin32.dll -bintype lib -platform win -arch x86 48 | 49 | - name: Build windows 64 bit exe 50 | run: | 51 | ./build_client.sh -host 127.0.0.1 -name dummy -outputfile dummywin64.exe -bintype exe -platform win -arch x64 52 | 53 | - name: Build windows 64 bit dll 54 | run: | 55 | ./build_client.sh -host 127.0.0.1 -name dummy -outputfile dummywin64.dll -bintype lib -platform win -arch x64 56 | 57 | - name: Build linux 32 bit exe 58 | run: | 59 | ./build_client.sh -host 127.0.0.1 -name dummy -outputfile dummylinux32.bin -bintype exe -platform linux -arch x86 60 | 61 | - name: Build linux 32 bit dll 62 | run: | 63 | ./build_client.sh -host 127.0.0.1 -name dummy -outputfile dummylinux32.dll -bintype lib -platform linux -arch x86 64 | 65 | - name: Build linux 64 bit exe 66 | run: | 67 | ./build_client.sh -host 127.0.0.1 -name dummy -outputfile dummylinux64.bin -bintype exe -platform linux -arch x64 68 | 69 | - name: Build linux 64 bit dll 70 | run: | 71 | ./build_client.sh -host 127.0.0.1 -name dummy -outputfile dummylinux64.dll -bintype lib -platform linux -arch x64 72 | 73 | - name: Build darwin 64 bit exe 74 | run: | 75 | ./build_client.sh -host 127.0.0.1 -name dummy -outputfile dummymacx64.bin -bintype exe -platform mac -arch x64 76 | 77 | - name: Publish artifact 78 | uses: actions/upload-artifact@v2 79 | with: 80 | name: clients 81 | path: build/* 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /.github/workflows/build-gtuncli.yml: -------------------------------------------------------------------------------- 1 | name: build-gtuncli 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: 1.20.3 20 | 21 | - name: Set path 22 | run: echo "/protoc/bin:$GOPATH/bin" >> $GITHUB_PATH 23 | 24 | - name: Install system dependencies 25 | run: | 26 | wget https://github.com/protocolbuffers/protobuf/releases/download/v22.3/protoc-22.3-linux-x86_64.zip 27 | sudo unzip protoc-22.3-linux-x86_64.zip -d /protoc 28 | rm protoc-22.3-linux-x86_64.zip 29 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 30 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 31 | 32 | - name: Build protoc 33 | run: | 34 | cd grpc 35 | ./build_protoc.sh 36 | cd .. 37 | 38 | - name: Get packages 39 | run: go get -d -v ./... && go install -v ./... 40 | 41 | - name: Build gtuncli 42 | run: | 43 | mkdir build 44 | cd gtuncli && go build -o ../build/gtuncli gtuncli.go && cd .. 45 | 46 | - name: Publish artifact 47 | uses: actions/upload-artifact@v2 48 | with: 49 | name: gtuncli 50 | path: build/gtuncli 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | configured 2 | .vscode 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 hotnops 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Documentation can be found at: 2 | https://hotnops.gitbook.io/gtunnel/ 3 | -------------------------------------------------------------------------------- /build_client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "$(sudo docker image inspect gclient-build-image:latest 2> /dev/null)" == "[]" ]]; then 4 | docker build --network host -f gclient/Dockerfile -t gclient-build-image . 5 | fi 6 | 7 | docker run -t --name gclient-build gclient-build-image "$@" 8 | 9 | if test $? -eq 0 10 | then 11 | docker cp gclient-build:/output/. build/ 12 | else 13 | echo "[*] Exiting" 14 | fi 15 | docker rm gclient-build -f &> /dev/null || true 16 | -------------------------------------------------------------------------------- /build_gtuncli.sh: -------------------------------------------------------------------------------- 1 | docker build --network host -f gserver/Dockerfile --target gtuncli . -t gtuncli-build-image 2 | docker run --net host --name gtuncli-build -v $PWD/gtuncli:/go/src/gTunnel/gtuncli -v $PWD/build:/build gtuncli-build-image 3 | docker rm gtuncli-build 4 | -------------------------------------------------------------------------------- /build_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build --network host -f gserver/Dockerfile --target gtunserver-prod . -t hotnops/gtunnel-server 4 | 5 | if test $? -eq 0 6 | then 7 | docker rm gtunnel-server -f || true 8 | else 9 | echo "[!] Failed to build gtunnel server image" 10 | exit 1 11 | fi 12 | 13 | docker create --net host -v $PWD/logs:/logs -v $PWD/tls:/tls --name gtunnel-server hotnops/gtunnel-server 14 | 15 | if test $? -eq 0 16 | then 17 | echo "[*] Docker container successfully created" 18 | else 19 | echo "[!] Failed to create gtunnel-server container" 20 | fi 21 | -------------------------------------------------------------------------------- /build_server_debug.sh: -------------------------------------------------------------------------------- 1 | docker build --network host -f gserver/Dockerfile --target gtunserver-debug . -t gtunnel-server-debug-image 2 | -------------------------------------------------------------------------------- /common/auth.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "log" 7 | "math/big" 8 | ) 9 | 10 | const ( 11 | // BearerString is the string used to designate an auth token 12 | BearerString = "Bearer " 13 | // MaxTokenSize is the maximum number of characters for a bearer token 14 | MaxTokenSize = 48 15 | // MinTokenSize is the minimum number of characters for a bearer token 16 | MinTokenSize = 32 17 | ) 18 | 19 | // TokenAuth is a structure repesenting a bearer token for 20 | // client auth 21 | type TokenAuth struct { 22 | token string 23 | } 24 | 25 | // GenerateToken will generate a random string to be 26 | // used as a client authorization. 27 | func GenerateToken() (string, error) { 28 | // Minimum token size is 24, max is 36 29 | reader := rand.Reader 30 | max := big.NewInt(int64(MaxTokenSize - MinTokenSize)) 31 | min := big.NewInt(int64(MinTokenSize)) 32 | 33 | tokenSize, err := rand.Int(reader, max) 34 | 35 | if err != nil { 36 | log.Print("[!] Failed to generate a password size\n") 37 | } 38 | 39 | tokenSize.Add(tokenSize, min) 40 | 41 | var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^&*()=_+[]{};,.<>/?") 42 | 43 | b := make([]rune, tokenSize.Int64()) 44 | for i := range b { 45 | newRand, _ := rand.Int(reader, big.NewInt(int64(len(letters)))) 46 | b[i] = letters[newRand.Int64()] 47 | } 48 | 49 | return string(b), nil 50 | } 51 | 52 | // NewToken will generate a new TokenAuth structure 53 | // with the provided string set to the token member. 54 | func NewToken(token string) *TokenAuth { 55 | t := new(TokenAuth) 56 | if token == "" { 57 | return nil 58 | } 59 | t.token = token 60 | return t 61 | } 62 | 63 | // GetRequestMetadata will return the bearer token as an 64 | // authorization header 65 | func (t *TokenAuth) GetRequestMetadata(ctx context.Context, in ...string) ( 66 | map[string]string, error) { 67 | return map[string]string{ 68 | "authorization": "Bearer " + t.token, 69 | }, nil 70 | } 71 | 72 | // RequireTransportSecurity always returns true 73 | func (t *TokenAuth) RequireTransportSecurity() bool { 74 | return true 75 | } 76 | -------------------------------------------------------------------------------- /common/auth_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io/ioutil" 7 | "testing" 8 | 9 | "google.golang.org/grpc/metadata" 10 | ) 11 | 12 | func TestNewToken(t *testing.T) { 13 | want := "UNITTEST" 14 | bad := "" 15 | got := NewToken(want) 16 | if got.token != want { 17 | t.Errorf("NewToken = %s; want %s", got.token, want) 18 | } 19 | 20 | got = NewToken(bad) 21 | if got != nil { 22 | t.Errorf("NewToken = %s; Needs to be nil", got) 23 | } 24 | 25 | } 26 | 27 | func TestGetRequestMetadata(t *testing.T) { 28 | ctx := context.Background() 29 | newToken := NewToken("UNITTEST") 30 | 31 | metadata, err := newToken.GetRequestMetadata(ctx, "") 32 | if err != nil { 33 | t.Errorf("GetRequestMetadata: GetRequestMetadata failed") 34 | } 35 | 36 | got := metadata["authorization"] 37 | want := "Bearer UNITTEST" 38 | if got != want { 39 | t.Errorf("GetRequestMetadata: Got: %s Want: %s", got, want) 40 | } 41 | 42 | } 43 | 44 | func handler(ctx context.Context, req interface{}) (interface{}, error) { 45 | return nil, nil 46 | } 47 | 48 | func TestAuthInterceptorValid(t *testing.T) { 49 | // Test if valid creds succeed 50 | ctx := context.Background() 51 | 52 | clientConfig := new(ClientConfig) 53 | clientConfig.ID = "UNITTESTID" 54 | clientConfig.IDHash = "UNITTESTHAST" 55 | 56 | authStore, err := InitializeAuthStore("") 57 | 58 | if err != nil { 59 | t.Errorf("[!] Failed to initialize auth store.") 60 | } 61 | 62 | token, _ := authStore.GenerateNewClientConfig("unittestid") 63 | t.Logf("[*] Generated token: %s\n", token) 64 | 65 | t.Run("ValidCreds", func(t *testing.T) { 66 | md := metadata.New(map[string]string{"authorization": bearerString + token}) 67 | ctx = metadata.NewIncomingContext(ctx, md) 68 | _, err := UnaryAuthInterceptor(ctx, nil, nil, handler) 69 | 70 | if err != nil { 71 | t.Errorf("AuthInterceptor error: %s", err) 72 | } 73 | }) 74 | t.Run("InvalidCreds", func(t *testing.T) { 75 | md := metadata.New(map[string]string{"authorization": bearerString + "BADTOKEN"}) 76 | ctx = metadata.NewIncomingContext(ctx, md) 77 | _, err := UnaryAuthInterceptor(ctx, nil, nil, handler) 78 | 79 | if err == nil { 80 | t.Errorf("AuthInterceptor validated non-existent client") 81 | } 82 | }) 83 | } 84 | 85 | func LoadFileToMap(filename string) (map[string]ClientConfig, error) { 86 | fileBytes, err := ioutil.ReadFile(filename) 87 | data := make(map[string]ClientConfig) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | err = json.Unmarshal(fileBytes, &data) 93 | if err != nil { 94 | return nil, err 95 | } 96 | return data, nil 97 | } 98 | 99 | func TestConfigurationFile(t *testing.T) { 100 | // This test will load a configuration file, ensure it has 101 | // the correct entries, add a new entry, and confirm that the entry 102 | // gets saved to file. It will then delete the entry and ensure 103 | // the entry no longer exists 104 | 105 | TestConfigFile := "../testdata/.gtunnel.conf" 106 | authStore, err := InitializeAuthStore(TestConfigFile) 107 | if err != nil { 108 | t.Fatalf("[!] Failed to load test configuration") 109 | } 110 | config1, err := authStore.GetClientConfig("QbcW7ChjefhyB$X[v@Q\u003cF@hWzMnZGuW(X8CBmcF") 111 | 112 | if err != nil { 113 | t.Errorf("Failed to lookup bearer token") 114 | } 115 | 116 | got := config1.ID 117 | want := "test" 118 | 119 | if got != want { 120 | t.Errorf("GetClientConfig: Got: %s Want: %s\n", got, want) 121 | } 122 | 123 | newToken, err := authStore.GenerateNewClientConfig("newClient") 124 | 125 | if err != nil { 126 | t.Errorf("Failed to add new user") 127 | } 128 | 129 | data, err := LoadFileToMap(TestConfigFile) 130 | 131 | if err != nil { 132 | t.Errorf("Failed to load file: %s", err) 133 | } 134 | 135 | newConfig, ok := data[newToken] 136 | if !ok { 137 | t.Errorf("Failed to find new client in file") 138 | } 139 | 140 | if newConfig.ID != "newClient" { 141 | t.Errorf("ClientID lookup failed: Got: %s Want: newClient", 142 | newConfig.ID) 143 | } 144 | 145 | authStore.DeleteClientConfig(newToken) 146 | data, err = LoadFileToMap(TestConfigFile) 147 | 148 | if err != nil { 149 | t.Errorf("Failed to load file: %s", err) 150 | } 151 | 152 | newConfig, ok = data[newToken] 153 | if ok { 154 | t.Errorf("Found client that should have been deleted") 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /common/connection.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | 7 | cs "github.com/hotnops/gTunnel/grpc/client" 8 | ) 9 | 10 | // A structure to handle the TCP connection 11 | // and map them to the gRPC byte stream. 12 | type Connection struct { 13 | ID string 14 | TCPConn net.TCPConn 15 | Kill chan bool 16 | Status int32 17 | Connected chan bool 18 | ingressData chan *cs.BytesMessage 19 | egressData chan *cs.BytesMessage 20 | byteStream ByteStream 21 | bytesTx uint64 22 | bytesRx uint64 23 | remoteClose bool 24 | mutex sync.Mutex 25 | } 26 | 27 | // NewConnection is a constructor function for Connection. 28 | func NewConnection(tcpConn net.TCPConn) *Connection { 29 | c := new(Connection) 30 | c.TCPConn = tcpConn 31 | c.Status = 0 32 | c.Connected = make(chan bool) 33 | c.Kill = make(chan bool) 34 | 35 | return c 36 | } 37 | 38 | // Close will close a TCP connection and close the 39 | // Kill channel. 40 | func (c *Connection) Close() { 41 | c.mutex.Lock() 42 | defer c.mutex.Unlock() 43 | 44 | c.TCPConn.Close() 45 | if c.Status != ConnectionStatusClosed { 46 | close(c.Kill) 47 | c.Status = ConnectionStatusClosed 48 | } 49 | } 50 | 51 | // GetStream will return the byteStream for a connection 52 | func (c *Connection) GetStream() ByteStream { 53 | return c.byteStream 54 | } 55 | 56 | // handleEgressData will listen on the locally 57 | // connected TCP socket and send the data over the gRPC stream. 58 | func (c *Connection) handleEgressData() { 59 | inputChan := make(chan []byte, 4096) 60 | 61 | go func(t net.TCPConn) { 62 | for { 63 | bytes := make([]byte, 4096) 64 | bytesRead, err := t.Read(bytes) 65 | if err != nil { 66 | if !c.remoteClose { 67 | break 68 | } 69 | } 70 | inputChan <- bytes[:bytesRead] 71 | if err != nil { 72 | break 73 | } 74 | } 75 | if inputChan != nil { 76 | close(inputChan) 77 | } 78 | }(c.TCPConn) 79 | 80 | for { 81 | select { 82 | case bytes, ok := <-inputChan: 83 | if !ok { 84 | inputChan = nil 85 | break 86 | } 87 | message := new(cs.BytesMessage) 88 | message.Content = bytes 89 | 90 | c.byteStream.Send(message) 91 | if len(message.Content) == 0 { 92 | inputChan = nil 93 | break 94 | } 95 | case <-c.Kill: 96 | inputChan = nil 97 | break 98 | } 99 | if inputChan == nil { 100 | if !c.remoteClose { 101 | c.SendCloseMessage() 102 | } 103 | break 104 | } 105 | } 106 | c.Close() 107 | } 108 | 109 | // handleIngressData will handle all incoming messages 110 | // on the gRPC byte stream and send them to the locally 111 | // connected socket. 112 | func (c *Connection) handleIngressData() { 113 | 114 | inputChan := make(chan *cs.BytesMessage) 115 | 116 | go func(s ByteStream) { 117 | for { 118 | message, err := s.Recv() 119 | if err != nil { 120 | c.Close() 121 | break 122 | } 123 | inputChan <- message 124 | } 125 | if inputChan != nil { 126 | close(inputChan) 127 | } 128 | }(c.byteStream) 129 | 130 | for { 131 | select { 132 | case bytesMessage, ok := <-inputChan: 133 | if !ok { 134 | inputChan = nil 135 | break 136 | } 137 | if bytesMessage == nil { 138 | inputChan = nil 139 | break 140 | } else if len(bytesMessage.Content) == 0 { 141 | c.remoteClose = true 142 | inputChan = nil 143 | break 144 | } else { 145 | bytesSent, err := c.TCPConn.Write(bytesMessage.Content) 146 | if err != nil { 147 | c.SendCloseMessage() 148 | inputChan = nil 149 | break 150 | } else { 151 | c.bytesTx += uint64(bytesSent) 152 | } 153 | } 154 | case <-c.Kill: 155 | inputChan = nil 156 | break 157 | } 158 | if inputChan == nil { 159 | break 160 | } 161 | } 162 | 163 | } 164 | 165 | // SendCloseMessage will send a zero sized 166 | // message to the remote endpoint, indicating 167 | // that a TCP connection has been closed locally. 168 | func (c *Connection) SendCloseMessage() { 169 | closeMessage := new(cs.BytesMessage) 170 | closeMessage.Content = make([]byte, 0) 171 | c.byteStream.Send(closeMessage) 172 | } 173 | 174 | // SetStream will set the byteStream for a connection 175 | func (c *Connection) SetStream(s ByteStream) { 176 | c.byteStream = s 177 | } 178 | 179 | // Start will start two goroutines for handling the TCP socket 180 | // and the gRPC stream. 181 | func (c *Connection) Start() { 182 | c.mutex.Lock() 183 | defer c.mutex.Unlock() 184 | 185 | if c.Status == ConnectionStatusCreated { 186 | c.Status = ConnectionStatusConnected 187 | go c.handleIngressData() 188 | go c.handleEgressData() 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /common/constants.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const ( 4 | EndpointCtrlDisconnect = iota 5 | EndpointCtrlAddRTunnel 6 | EndpointCtrlAddTunnel 7 | EndpointCtrlSocksProxy 8 | EndpointCtrlSocksProxyAck 9 | EndpointCtrlSocksKill 10 | EndpointCtrlDeleteTunnel 11 | ) 12 | 13 | const ( 14 | TunnelDirectionForward = iota 15 | TunnelDirectionReverse 16 | ) 17 | 18 | const ( 19 | TunnelCtrlConnect = iota 20 | TunnelCtrlAck 21 | TunnelCtrlDisconnect 22 | ) 23 | 24 | const ( 25 | ConnectionStatusCreated = iota 26 | ConnectionStatusConnected 27 | ConnectionStatusClosed 28 | ) 29 | -------------------------------------------------------------------------------- /common/endpoint.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | cs "github.com/hotnops/gTunnel/grpc/client" 5 | ) 6 | 7 | type ByteStream interface { 8 | Send(*cs.BytesMessage) error 9 | Recv() (*cs.BytesMessage, error) 10 | } 11 | 12 | type Endpoint struct { 13 | Id string 14 | killClient chan bool 15 | tunnels map[string]*Tunnel 16 | endpointCtrlStream chan cs.EndpointControlMessage 17 | } 18 | 19 | type gInterface interface { 20 | GetTunnelStream() TunnelControlStream 21 | GetByteStream() ByteStream 22 | } 23 | 24 | type TunnelControlStream interface { 25 | Send(*cs.TunnelControlMessage) error 26 | Recv() (*cs.TunnelControlMessage, error) 27 | } 28 | 29 | // NewEndpoint is a constructor for the endpoint 30 | // class. It takes an endpoint ID string as an argument. 31 | func NewEndpoint() *Endpoint { 32 | e := new(Endpoint) 33 | e.Id = "" 34 | e.endpointCtrlStream = make(chan cs.EndpointControlMessage) 35 | e.tunnels = make(map[string]*Tunnel) 36 | return e 37 | } 38 | 39 | // AddTunnel adds a tunnel instance to the list of tunnels 40 | // maintained by the endpoint 41 | func (e *Endpoint) AddTunnel(id string, t *Tunnel) { 42 | e.tunnels[id] = t 43 | } 44 | 45 | // GetTunnel will take in a tunnel ID string as an argument 46 | // and return a Tunnel pointer of the corresponding ID. 47 | func (e *Endpoint) GetTunnel(tunID string) (*Tunnel, bool) { 48 | t, ok := e.tunnels[tunID] 49 | return t, ok 50 | } 51 | 52 | // GetTunnels returns all of the active tunnels 53 | // maintained by the endpoint 54 | func (e *Endpoint) GetTunnels() map[string]*Tunnel { 55 | return e.tunnels 56 | } 57 | 58 | // SetID takes in a string and will set the Id to that string 59 | func (e *Endpoint) SetID(id string) { 60 | e.Id = id 61 | } 62 | 63 | // Stop will close all tunnels and the associated TCP 64 | // connections with each tunnel. 65 | func (e *Endpoint) Stop() { 66 | for id, _ := range e.tunnels { 67 | e.StopAndDeleteTunnel(id) 68 | } 69 | close(e.endpointCtrlStream) 70 | } 71 | 72 | // StopAndDeleteTunnel takes in a tunnelID as an argument 73 | // and removes the tunnel from the endpoint. Returns 74 | // true if successful and false otherwise. 75 | func (e *Endpoint) StopAndDeleteTunnel(tunID string) bool { 76 | tun, ok := e.tunnels[tunID] 77 | if !ok { 78 | return false 79 | } 80 | tun.Stop() 81 | delete(e.tunnels, tunID) 82 | return true 83 | } 84 | -------------------------------------------------------------------------------- /common/socksserver.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | 8 | "github.com/fangdingjun/socks-go" 9 | ) 10 | 11 | // SocksServer is a structure that handles starting and stopping 12 | // a socks v5 proxy on the gClient. 13 | type SocksServer struct { 14 | listener net.Listener 15 | connections []socks.Conn 16 | servePort uint32 17 | } 18 | 19 | // NewSocksServer is a constructor for the SocksServer struct. 20 | // It takes in a port as an argument, wich will be the port on 21 | // which the socks server listens. 22 | func NewSocksServer(port uint32) *SocksServer { 23 | s := new(SocksServer) 24 | s.servePort = port 25 | s.connections = make([]socks.Conn, 0) 26 | return s 27 | } 28 | 29 | // Start will start the socks server. Simple enough. 30 | func (s *SocksServer) Start() bool { 31 | var err error 32 | s.listener, err = net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", s.servePort)) 33 | 34 | if err != nil { 35 | return false 36 | } 37 | 38 | go func() { 39 | for { 40 | conn, err := s.listener.Accept() 41 | if err != nil { 42 | break 43 | } 44 | d := net.Dialer{Timeout: 10 * time.Second} 45 | newConn := socks.Conn{Conn: conn, Dial: d.Dial} 46 | s.connections = append(s.connections, newConn) 47 | go newConn.Serve() 48 | } 49 | }() 50 | return true 51 | } 52 | 53 | // Stop - You'll never guess what this does. 54 | func (s *SocksServer) Stop() { 55 | s.listener.Close() 56 | for _, conn := range s.connections { 57 | conn.Close() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /common/tunnel.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sync" 7 | 8 | cs "github.com/hotnops/gTunnel/grpc/client" 9 | "github.com/segmentio/ksuid" 10 | ) 11 | 12 | type ConnectionStreamHandler interface { 13 | GetByteStream(tunnel *Tunnel, ctrlMessage *cs.TunnelControlMessage) ByteStream 14 | CloseStream(tunnel *Tunnel, connID string) 15 | Acknowledge(tunnel *Tunnel, ctrlMessage *cs.TunnelControlMessage) ByteStream 16 | } 17 | 18 | type Tunnel struct { 19 | id string 20 | direction uint32 21 | listenIP net.IP 22 | listenPort uint32 23 | destinationIP net.IP 24 | destinationPort uint32 25 | connections map[string]*Connection 26 | listeners []net.TCPListener 27 | Kill chan bool 28 | ctrlStream TunnelControlStream 29 | ConnectionHandler ConnectionStreamHandler 30 | mutex sync.Mutex 31 | } 32 | 33 | // NewTunnel is a constructor for the tunnel struct. It takes 34 | // in id, direction, listenIP, lisetnPort, destinationIP, and 35 | // destinationPort as parameters. 36 | func NewTunnel(id string, 37 | direction uint32, 38 | listenIP net.IP, 39 | listenPort uint32, 40 | destinationIP net.IP, 41 | destinationPort uint32) *Tunnel { 42 | t := new(Tunnel) 43 | t.id = id 44 | t.direction = direction 45 | t.listenIP = listenIP 46 | t.listenPort = listenPort 47 | t.destinationIP = destinationIP 48 | t.destinationPort = destinationPort 49 | t.connections = make(map[string]*Connection) 50 | t.Kill = make(chan bool) 51 | t.listeners = make([]net.TCPListener, 0) 52 | return t 53 | } 54 | 55 | // Addconnection will generate an ID for the connection and 56 | // add it to the map. 57 | func (t *Tunnel) AddConnection(c *Connection) { 58 | t.mutex.Lock() 59 | defer t.mutex.Unlock() 60 | 61 | c.ID = ksuid.New().String() 62 | t.connections[c.ID] = c 63 | } 64 | 65 | // AddListener will start a tcp listener on a specific port and forward 66 | // all accepted TCP connections to the associated tunnel. 67 | func (t *Tunnel) AddListener(clientID string) bool { 68 | 69 | addr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", t.listenIP, t.listenPort)) 70 | ln, err := net.ListenTCP("tcp", addr) 71 | if err != nil { 72 | return false 73 | } 74 | 75 | t.listeners = append(t.listeners, *ln) 76 | 77 | newConns := make(chan *net.TCPConn) 78 | 79 | go func(l *net.TCPListener) { 80 | for { 81 | c, err := l.AcceptTCP() 82 | if err == nil { 83 | newConns <- c 84 | } else { 85 | return 86 | } 87 | } 88 | }(ln) 89 | go func() { 90 | for { 91 | select { 92 | case conn := <-newConns: 93 | gConn := NewConnection(*conn) 94 | t.AddConnection(gConn) 95 | newMessage := new(cs.TunnelControlMessage) 96 | newMessage.Operation = TunnelCtrlConnect 97 | newMessage.TunnelId = t.id 98 | newMessage.ConnectionId = gConn.ID 99 | t.ctrlStream.Send(newMessage) 100 | 101 | case <-t.Kill: 102 | return 103 | } 104 | } 105 | }() 106 | return true 107 | } 108 | 109 | // GetConnection will return a Connection object 110 | // with the given connection id 111 | func (t *Tunnel) GetConnection(connID string) *Connection { 112 | t.mutex.Lock() 113 | defer t.mutex.Unlock() 114 | 115 | if conn, ok := t.connections[connID]; ok { 116 | return conn 117 | } 118 | return nil 119 | } 120 | 121 | // GetControlStream will return the control stream for 122 | // the associated tunnel 123 | func (t *Tunnel) GetControlStream() TunnelControlStream { 124 | return t.ctrlStream 125 | } 126 | 127 | // GetDestinationIP gets the destination IP of the tunnel. 128 | func (t *Tunnel) GetDestinationIP() net.IP { 129 | return t.destinationIP 130 | } 131 | 132 | // GetDestinationPort gets the destination port of the tunnel. 133 | func (t *Tunnel) GetDestinationPort() uint32 { 134 | return t.destinationPort 135 | } 136 | 137 | // GetDirection gets the direction of the tunnel (forward or reverse) 138 | func (t *Tunnel) GetDirection() uint32 { 139 | return t.direction 140 | } 141 | 142 | // GetListenIP gets the ip address that the tunnel is listening on. 143 | func (t *Tunnel) GetListenIP() net.IP { 144 | return t.listenIP 145 | } 146 | 147 | // GetListenPort gets the port that the tunnel is listening on. 148 | func (t *Tunnel) GetListenPort() uint32 { 149 | return t.listenPort 150 | } 151 | 152 | // GetConnections will return the connection map 153 | func (t *Tunnel) GetConnections() map[string]*Connection { 154 | t.mutex.Lock() 155 | defer t.mutex.Unlock() 156 | 157 | return t.connections 158 | } 159 | 160 | // handleIngressCtrlMessages is the loop function responsible 161 | // for receiving control messages from the gRPC stream. 162 | func (t *Tunnel) handleIngressCtrlMessages() { 163 | ingressMessages := make(chan *cs.TunnelControlMessage) 164 | go func(s TunnelControlStream) { 165 | for { 166 | ingressMessage, err := t.ctrlStream.Recv() 167 | if err != nil { 168 | close(ingressMessages) 169 | return 170 | } 171 | ingressMessages <- ingressMessage 172 | } 173 | }(t.ctrlStream) 174 | for { 175 | select { 176 | case ctrlMessage, ok := <-ingressMessages: 177 | if !ok { 178 | ingressMessages = nil 179 | break 180 | } 181 | if ctrlMessage == nil { 182 | ingressMessages = nil 183 | break 184 | } 185 | // handle control message 186 | if ctrlMessage.Operation == TunnelCtrlConnect { 187 | 188 | rAddr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", 189 | t.destinationIP, 190 | t.destinationPort)) 191 | 192 | conn, err := net.DialTCP("tcp", nil, rAddr) 193 | 194 | if err != nil { 195 | ctrlMessage.ErrorStatus = 1 196 | } else { 197 | var gConn *Connection 198 | if gConn, ok = t.connections[ctrlMessage.ConnectionId]; !ok { 199 | gConn = NewConnection(*conn) 200 | gConn.ID = ctrlMessage.ConnectionId 201 | t.connections[ctrlMessage.ConnectionId] = gConn 202 | } 203 | stream := t.ConnectionHandler.GetByteStream(t, ctrlMessage) 204 | gConn.SetStream(stream) 205 | gConn.Start() 206 | 207 | } 208 | 209 | } else if ctrlMessage.Operation == TunnelCtrlAck { 210 | if ctrlMessage.ErrorStatus != 0 { 211 | t.RemoveConnection(ctrlMessage.ConnectionId) 212 | } else { 213 | // Now that we know we are connected, we need to create a new byte 214 | // stream and create a thread to service it 215 | // If this is client side, we need to still create the byte stream 216 | conn := t.GetConnection(ctrlMessage.ConnectionId) 217 | 218 | if conn != nil { 219 | // Waiting until the byte stream gets set up 220 | conn.SetStream(t.ConnectionHandler.Acknowledge(t, ctrlMessage)) 221 | if ok { 222 | conn.Start() 223 | } 224 | } 225 | } 226 | } else if ctrlMessage.Operation == TunnelCtrlDisconnect { 227 | t.RemoveConnection(ctrlMessage.ConnectionId) 228 | } 229 | case <-t.Kill: 230 | break 231 | } 232 | if ingressMessages == nil { 233 | break 234 | } 235 | } 236 | } 237 | 238 | // RemoveConnection will remove the Connection object 239 | // from the connections map. 240 | func (t *Tunnel) RemoveConnection(connID string) { 241 | t.mutex.Lock() 242 | defer t.mutex.Unlock() 243 | 244 | delete(t.connections, connID) 245 | } 246 | 247 | // SetControlStream will set the provided control stream for 248 | // the associated tunnel 249 | func (t *Tunnel) SetControlStream(s TunnelControlStream) { 250 | t.ctrlStream = s 251 | } 252 | 253 | // Start receiving control messages for the tunnel 254 | func (t *Tunnel) Start() { 255 | // A thread for handling the established tcp connections 256 | go t.handleIngressCtrlMessages() 257 | 258 | } 259 | 260 | // Stop will stop all associated goroutines for the tunnel 261 | // and disconnect any associated TCP connections 262 | func (t *Tunnel) Stop() { 263 | t.mutex.Lock() 264 | defer t.mutex.Unlock() 265 | 266 | // First, stop all the listeners 267 | for _, ln := range t.listeners { 268 | ln.Close() 269 | } 270 | 271 | // Close all existing tcp connections 272 | for _, conn := range t.connections { 273 | conn.Close() 274 | } 275 | // Lastly, signal that the tunnel stream should be killed 276 | close(t.Kill) 277 | } 278 | -------------------------------------------------------------------------------- /common/util.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/binary" 5 | "math/rand" 6 | "net" 7 | "time" 8 | ) 9 | 10 | // ClientIDSize is the constant used for the string size 11 | // of a generated client ID 12 | const ClientIDSize = 8 13 | 14 | // TunnelIDSize is the constant used for the string size 15 | // of a generated tunnel ID 16 | const TunnelIDSize = 8 17 | 18 | // GenerateString will generate a random string of length 19 | // Credit to: https://www.calhoun.io/creating-random-strings-in-go/ 20 | func GenerateString(length int) string { 21 | const charset = "abcdefghijklmnopqrstuvwxyz" + 22 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 23 | var seededRand *rand.Rand = rand.New( 24 | rand.NewSource(time.Now().UnixNano())) 25 | 26 | b := make([]byte, length) 27 | for i := range b { 28 | b[i] = charset[seededRand.Intn(len(charset))] 29 | } 30 | return string(b) 31 | } 32 | 33 | // Int32ToIP converts a uint32 to a net.IP 34 | func Int32ToIP(i uint32) net.IP { 35 | ip := make(net.IP, 4) 36 | binary.BigEndian.PutUint32(ip, i) 37 | return ip 38 | } 39 | 40 | // IpToInt32 converts a net.IP to a uint32 41 | // Credit to https://gist.github.com/ammario/ipint.go 42 | func IpToInt32(ip net.IP) uint32 { 43 | if len(ip) == 16 { 44 | return binary.BigEndian.Uint32(ip[12:16]) 45 | } 46 | return binary.BigEndian.Uint32(ip) 47 | } 48 | -------------------------------------------------------------------------------- /gclient/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20.3 2 | 3 | WORKDIR /go/src/gTunnel 4 | ENV PATH=$PATH:/protoc/bin:$GOPATH/bin 5 | 6 | # We need unzip to install protoc 7 | RUN apt update && apt install -y \ 8 | unzip \ 9 | gcc-mingw-w64-i686 \ 10 | gcc-mingw-w64-x86-64 \ 11 | gcc-multilib 12 | 13 | # Install protoc and all dependencies 14 | #RUN go install google.golang.org/grpc/cmd/proto@latest 15 | RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v22.3/protoc-22.3-linux-x86_64.zip &&\ 16 | unzip protoc-22.3-linux-x86_64.zip -d /protoc &&\ 17 | rm protoc-22.3-linux-x86_64.zip &&\ 18 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 &&\ 19 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 20 | 21 | # Copy over all gtunnel files and directories 22 | COPY go.mod . 23 | COPY gclient/ gclient/. 24 | COPY common/ common/. 25 | COPY grpc/ grpc/. 26 | 27 | # Build protoc 28 | RUN cd grpc && ./build_protoc.sh && cd .. 29 | 30 | # Get all gtunnel dependencies 31 | RUN go get -d -v ./... && go install -v ./... 32 | 33 | # Build client builder 34 | RUN mkdir /output 35 | RUN go build -o gclient/gclient_build gclient/builder/gclient_build.go 36 | ENTRYPOINT ["gclient/gclient_build"] 37 | -------------------------------------------------------------------------------- /gclient/builder/gclient_build.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | 10 | "github.com/hotnops/gTunnel/common" 11 | "golang.org/x/exp/slices" 12 | ) 13 | 14 | func GenerateClient( 15 | platform string, 16 | serverAddress string, 17 | serverPort uint16, 18 | clientID string, 19 | binType string, 20 | arch string, 21 | proxyServer string, 22 | outputFile string) error { 23 | 24 | token, err := common.GenerateToken() 25 | if err != nil { 26 | log.Printf("[!] Failed to generate token, I guess?") 27 | return err 28 | } 29 | 30 | outputPath := fmt.Sprintf("/output/%s", outputFile) 31 | tokenOutputPath := outputPath + ".token" 32 | 33 | tokenFile, err := os.Create(tokenOutputPath) 34 | if err != nil { 35 | log.Printf("[!] Could not create token file") 36 | return err 37 | } 38 | 39 | if _, err := tokenFile.WriteString(token); err != nil { 40 | log.Printf("[!] Could not write token to file") 41 | return err 42 | } 43 | 44 | var loaderFlags = "" 45 | 46 | if platform == "win" { 47 | loaderFlags = "-extldflags \"-static\" -s -w -X main.clientToken=%s -X main.serverAddress=%s -X main.serverPort=%d -X main.httpsProxyServer=%s" 48 | } else { 49 | loaderFlags = "-s -w -X main.clientToken=%s -X main.serverAddress=%s -X main.serverPort=%d -X main.httpsProxyServer=%s" 50 | } 51 | 52 | flagString := fmt.Sprintf(loaderFlags, token, serverAddress, serverPort, proxyServer) 53 | var commands []string 54 | 55 | commands = append(commands, "build") 56 | env := os.Environ() 57 | target_files := []string{"gclient/gClient.go"} 58 | 59 | if binType == "lib" { 60 | commands = append(commands, "-buildmode=c-shared") 61 | env = append(env, "CGO_ENABLED=1") 62 | target_files = append(target_files, "gclient/cgo.go") 63 | } 64 | 65 | commands = append(commands, "-ldflags", flagString, "-o", outputPath) 66 | commands = append(commands, target_files...) 67 | 68 | if arch == "x86" { 69 | env = append(env, "GOARCH=386") 70 | if platform == "win" { 71 | env = append(env, "CC=i686-w64-mingw32-gcc") 72 | } 73 | } else if arch == "x64" { 74 | env = append(env, "GOARCH=amd64") 75 | if platform == "win" { 76 | env = append(env, "CC=x86_64-w64-mingw32-gcc") 77 | } 78 | } else { 79 | log.Fatal("[!] Invalid architecture: %s", arch) 80 | } 81 | 82 | if platform == "win" { 83 | env = append(env, "GOOS=windows") 84 | } else if platform == "linux" { 85 | env = append(env, "GOOS=linux") 86 | } else if platform == "mac" { 87 | env = append(env, "GOOS=darwin") 88 | } else { 89 | log.Printf("[!] Invalid platform") 90 | return nil 91 | } 92 | cmd := exec.Command("go", commands...) 93 | log.Printf("[*] Build cmd: %s\n", cmd.String()) 94 | log.Printf("[*] Env: %s\n", env) 95 | 96 | cmd.Env = env 97 | err = cmd.Run() 98 | if err != nil { 99 | log.Printf("[!] Failed to generate client: %s", err) 100 | return err 101 | } 102 | return nil 103 | } 104 | 105 | func main() { 106 | 107 | platform := flag.String("platform", "win", 108 | "The operating system platform") 109 | serverAddress := flag.String("host", "", 110 | "Address to which the client will connect.") 111 | serverPort := flag.Int("port", 443, 112 | "The port to which the client will connect") 113 | clientID := flag.String("name", "", 114 | "The unique ID for the generated client. Can be a friendly name") 115 | outputFile := flag.String("outputfile", "", 116 | "The output file where the client binary will be written") 117 | binType := flag.String("bintype", "exe", 118 | "The type of output file. Options are exe or lib. Exe works on linux.") 119 | 120 | arch := flag.String("arch", "x64", 121 | "The architecture of the binary. Options are x86 or x64") 122 | 123 | proxyServer := flag.String("proxy", "", "A proxy server that the client will call through. Empty by default") 124 | 125 | flag.Parse() 126 | 127 | platforms := []string{"win", "mac", "linux"} 128 | bintypes := []string{"exe", "lib"} 129 | archs := []string{"x86", "x64"} 130 | 131 | if !slices.Contains(platforms, *platform) { 132 | fmt.Println(("[!] Invalid platform provided")) 133 | os.Exit(1) 134 | } 135 | 136 | if !slices.Contains(bintypes, *binType) { 137 | fmt.Println("[!] Invalid bintype") 138 | os.Exit(1) 139 | } 140 | 141 | if !slices.Contains(archs, *arch) { 142 | fmt.Println("[!] Invalid architecture") 143 | os.Exit(1) 144 | } 145 | 146 | if *serverAddress == "" { 147 | fmt.Println("[!] ip not provided") 148 | os.Exit(1) 149 | 150 | } 151 | 152 | if *clientID == "" { 153 | fmt.Println("[!] name not provided") 154 | os.Exit(1) 155 | } 156 | 157 | if *outputFile == "" { 158 | fmt.Println("[!] outputFile not provided") 159 | os.Exit(1) 160 | } 161 | 162 | GenerateClient( 163 | *platform, 164 | *serverAddress, 165 | uint16(*serverPort), 166 | *clientID, 167 | *binType, 168 | *arch, 169 | *proxyServer, 170 | *outputFile) 171 | } 172 | -------------------------------------------------------------------------------- /gclient/cgo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | 5 | //export ExportMain 6 | func ExportMain() { 7 | gclient_main() 8 | } 9 | -------------------------------------------------------------------------------- /gclient/gClient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | "io" 8 | "os" 9 | 10 | cs "github.com/hotnops/gTunnel/grpc/client" 11 | "github.com/segmentio/ksuid" 12 | 13 | "github.com/hotnops/gTunnel/common" 14 | 15 | "google.golang.org/grpc" 16 | "google.golang.org/grpc/credentials" 17 | ) 18 | 19 | var clientToken = "UNCONFIGURED" 20 | var httpProxyServer = "" 21 | var httpsProxyServer = "" 22 | var serverAddress = "UNCONFIGURED" 23 | var serverPort = "" // This needs to be a string to be used with -X 24 | 25 | // ClientStreamHandler manages the context and grpc client for 26 | // a given TCP stream. 27 | type ClientStreamHandler struct { 28 | client cs.ClientServiceClient 29 | gCtx context.Context 30 | ctrlStream common.TunnelControlStream 31 | } 32 | 33 | // gClient is a structure that represents a unique gClient 34 | type gClient struct { 35 | endpoint *common.Endpoint 36 | ctrlStream cs.ClientService_CreateEndpointControlStreamClient 37 | grpcClient cs.ClientServiceClient 38 | killClient chan bool 39 | gCtx context.Context 40 | socksServer *common.SocksServer 41 | } 42 | 43 | // Acknowledge is called to indicate that the TCP connection has been 44 | // established on the remote side of the tunnel. 45 | func (c *ClientStreamHandler) Acknowledge(tunnel *common.Tunnel, 46 | ctrlMessage *cs.TunnelControlMessage) common.ByteStream { 47 | return c.GetByteStream(tunnel, ctrlMessage) 48 | } 49 | 50 | // CloseStream does nothing. 51 | func (c *ClientStreamHandler) CloseStream(tunnel *common.Tunnel, connID string) {} 52 | 53 | // GetByteStream is responsible for returning a bi-directional gRPC 54 | // stream that will be used for relaying TCP data. 55 | func (c *ClientStreamHandler) GetByteStream(tunnel *common.Tunnel, 56 | ctrlMessage *cs.TunnelControlMessage) common.ByteStream { 57 | 58 | stream, err := c.client.CreateConnectionStream(c.gCtx) 59 | if err != nil { 60 | return nil 61 | } 62 | 63 | // Once byte stream is open, send an initial message 64 | // with all the appropriate IDs 65 | bytesMessage := new(cs.BytesMessage) 66 | bytesMessage.TunnelId = ctrlMessage.TunnelId 67 | bytesMessage.ConnectionId = ctrlMessage.ConnectionId 68 | 69 | stream.Send(bytesMessage) 70 | 71 | // Lastly, forward the control message to the 72 | // server to indicate we have acknowledged the connection 73 | ctrlMessage.Operation = common.TunnelCtrlAck 74 | c.ctrlStream.Send(ctrlMessage) 75 | 76 | return stream 77 | } 78 | 79 | // receiveClientControlMessages is responsible for reading 80 | // all control messages and dealing with them appropriately. 81 | func (c *gClient) receiveClientControlMessages() { 82 | ctrlMessageChan := make(chan *cs.EndpointControlMessage) 83 | 84 | go func(c cs.ClientService_CreateEndpointControlStreamClient) { 85 | for { 86 | message, err := c.Recv() 87 | if err == io.EOF { 88 | break 89 | } else if err != nil { 90 | return 91 | } 92 | ctrlMessageChan <- message 93 | } 94 | }(c.ctrlStream) 95 | 96 | for { 97 | select { 98 | case message := <-ctrlMessageChan: 99 | operation := message.Operation 100 | if operation == common.EndpointCtrlAddTunnel { 101 | var direction = 0 102 | if message.ListenPort == 0 { 103 | direction = common.TunnelDirectionForward 104 | } else { 105 | direction = common.TunnelDirectionReverse 106 | } 107 | 108 | newTunnel := common.NewTunnel(message.TunnelId, 109 | uint32(direction), 110 | common.Int32ToIP(message.ListenIp), 111 | message.ListenPort, 112 | common.Int32ToIP(message.DestinationIp), 113 | message.DestinationPort) 114 | 115 | f := new(ClientStreamHandler) 116 | f.client = c.grpcClient 117 | f.gCtx = c.gCtx 118 | 119 | if direction == common.TunnelDirectionReverse { 120 | newTunnel.AddListener(c.endpoint.Id) 121 | } 122 | 123 | tStream, _ := c.grpcClient.CreateTunnelControlStream(c.gCtx) 124 | 125 | // Once we have the control stream, set it in our client handler 126 | f.ctrlStream = tStream 127 | newTunnel.ConnectionHandler = f 128 | newTunnel.SetControlStream(tStream) 129 | 130 | // Send a message through the new stream 131 | // to let the server know the ID specifics 132 | tMsg := new(cs.TunnelControlMessage) 133 | tMsg.TunnelId = message.TunnelId 134 | tStream.Send(tMsg) 135 | 136 | c.endpoint.AddTunnel(message.TunnelId, newTunnel) 137 | newTunnel.Start() 138 | 139 | } else if operation == common.EndpointCtrlDeleteTunnel { 140 | c.endpoint.StopAndDeleteTunnel(message.TunnelId) 141 | } else if operation == common.EndpointCtrlSocksProxy { 142 | message.Operation = common.EndpointCtrlSocksProxyAck 143 | message.ErrorStatus = 0 144 | if c.socksServer != nil { 145 | message.ErrorStatus = 1 146 | } 147 | 148 | c.socksServer = common.NewSocksServer(message.ListenPort) 149 | if !c.socksServer.Start() { 150 | message.ErrorStatus = 2 151 | } 152 | //c.ctrlStream.SendMsg(message) 153 | } else if operation == common.EndpointCtrlSocksKill { 154 | if c.socksServer != nil { 155 | c.socksServer.Stop() 156 | c.socksServer = nil 157 | } 158 | } else if operation == common.EndpointCtrlDisconnect { 159 | close(c.killClient) 160 | } 161 | 162 | case <-c.killClient: 163 | return 164 | } 165 | } 166 | } 167 | 168 | func gclient_main() { 169 | var err error 170 | var cancel context.CancelFunc 171 | 172 | uniqueID := ksuid.New().String() 173 | 174 | config := &tls.Config{ 175 | InsecureSkipVerify: true, 176 | } 177 | 178 | if len(httpProxyServer) > 0 { 179 | os.Setenv("HTTP_PROXY", httpProxyServer) 180 | } 181 | 182 | if len(httpsProxyServer) > 0 { 183 | os.Setenv("HTTPS_PROXY", httpsProxyServer) 184 | } 185 | 186 | var opts []grpc.DialOption 187 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(config)), 188 | grpc.WithPerRPCCredentials(common.NewToken(clientToken+"-"+uniqueID))) 189 | 190 | gClient := new(gClient) 191 | gClient.endpoint = common.NewEndpoint() 192 | gClient.killClient = make(chan bool) 193 | gClient.socksServer = nil 194 | 195 | serverAddr := fmt.Sprintf("%s:%s", serverAddress, serverPort) 196 | 197 | conn, err := grpc.Dial(serverAddr, opts...) 198 | if err != nil { 199 | return 200 | } 201 | defer conn.Close() 202 | 203 | req := new(cs.GetConfigurationMessageRequest) 204 | 205 | req.Hostname, _ = os.Hostname() 206 | 207 | gClient.grpcClient = cs.NewClientServiceClient(conn) 208 | gClient.gCtx, cancel = context.WithCancel(context.Background()) 209 | defer cancel() 210 | 211 | _, err = gClient.grpcClient.GetConfigurationMessage(gClient.gCtx, req) 212 | if err != nil { 213 | return 214 | } 215 | 216 | conMsg := new(cs.EndpointControlMessage) 217 | gClient.ctrlStream, err = gClient.grpcClient.CreateEndpointControlStream(gClient.gCtx, conMsg) 218 | 219 | if err != nil { 220 | return 221 | } 222 | 223 | go gClient.receiveClientControlMessages() 224 | <-gClient.killClient 225 | } 226 | 227 | func main() { 228 | gclient_main() 229 | } 230 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hotnops/gTunnel 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/fangdingjun/socks-go v0.0.0-20220901073602-f35f0e0139ec 7 | github.com/go-redis/redis/v8 v8.11.5 8 | github.com/olekukonko/tablewriter v0.0.5 9 | github.com/segmentio/ksuid v1.0.4 10 | google.golang.org/grpc v1.54.0 11 | google.golang.org/protobuf v1.30.0 12 | ) 13 | 14 | require ( 15 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 16 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 17 | github.com/golang/protobuf v1.5.2 // indirect 18 | github.com/mattn/go-runewidth v0.0.9 // indirect 19 | golang.org/x/net v0.8.0 // indirect 20 | golang.org/x/sys v0.6.0 // indirect 21 | golang.org/x/text v0.8.0 // indirect 22 | google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 2 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 3 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 4 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 5 | github.com/fangdingjun/socks-go v0.0.0-20220901073602-f35f0e0139ec h1:gri5Uh0VMajB6oL9g+dvf/ZwoWSe4F5CaDzOKVQqc6s= 6 | github.com/fangdingjun/socks-go v0.0.0-20220901073602-f35f0e0139ec/go.mod h1:i5fUj/NaF32p2LLmn8EWHF1CQukVH8oMgPyhjC4JxFk= 7 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 8 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= 9 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= 10 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 11 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 12 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 13 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 14 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 15 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 16 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 17 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 18 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 19 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 20 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 21 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= 22 | github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= 23 | github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= 24 | golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= 25 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 26 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 27 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 28 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= 29 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 30 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 31 | google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= 32 | google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= 33 | google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= 34 | google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= 35 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 36 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 37 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 38 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 39 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 40 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 41 | -------------------------------------------------------------------------------- /grpc/admin/admin.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | option java_package = "gtunnel"; 5 | option java_outer_classname = "GTunnel"; 6 | option go_package = "./"; 7 | 8 | package admin; 9 | 10 | /* The AdminService is used for performing administrative actions 11 | * with gServer, such as adding tunnels, configuring clients, and 12 | * listing out the state of gServer. */ 13 | service AdminService { 14 | 15 | // Generates a configred gClient executable 16 | rpc ClientRegister(ClientRegisterRequest) returns (ClientRegisterResponse) {} 17 | 18 | // Disconnects a client from the server 19 | rpc ClientDisconnect(ClientDisconnectRequest) returns (ClientDisconnectResponse) {} 20 | 21 | // Lists all connected gClients 22 | rpc ClientList(ClientListRequest) returns (stream Client) {} 23 | 24 | // List all connections for a tunnel 25 | rpc ConnectionList(ConnectionListRequest) returns (stream Connection) {} 26 | 27 | // Starts a SocksV5 server on a gClient 28 | rpc SocksStart(SocksStartRequest) returns (SocksStartResponse) {} 29 | 30 | // Stops a SocksV5 server on a gClient 31 | rpc SocksStop(SocksStopRequest) returns (SocksStopResponse) {} 32 | 33 | // Add a tunnel 34 | rpc TunnelAdd(TunnelAddRequest) returns (TunnelAddResponse) {} 35 | 36 | // Delete a tunnel 37 | rpc TunnelDelete(TunnelDeleteRequest) returns (TunnelDeleteResponse) {} 38 | 39 | // List all tunnels for an endppoint 40 | rpc TunnelList(TunnelListRequest) returns (stream Tunnel) {} 41 | } 42 | 43 | message ByteStream { 44 | bytes data = 1; 45 | } 46 | 47 | message Client { 48 | string name = 1; 49 | string client_id = 2; 50 | uint32 status = 3; 51 | string remote_address = 4; 52 | string connect_date = 5; 53 | string hostname = 6; 54 | } 55 | 56 | message ClientRegisterRequest { 57 | string client_id = 1; 58 | string token = 2; 59 | string server_endpoint = 3; 60 | uint32 port = 4; 61 | string platform = 5; 62 | string bin_type = 6; 63 | string arch = 7; 64 | string proxyServer = 8; 65 | } 66 | 67 | message ClientRegisterResponse { 68 | string error = 1; 69 | } 70 | 71 | message ClientDisconnectRequest { 72 | string client_id = 1; 73 | } 74 | 75 | message ClientDisconnectResponse {} 76 | 77 | message ClientListRequest {} 78 | 79 | message Connection { 80 | uint32 source_ip = 1; 81 | uint32 source_port = 2; 82 | uint32 destination_ip = 3; 83 | uint32 destination_port = 4; 84 | } 85 | 86 | message ConnectionListRequest { 87 | string client_id = 1; 88 | string tunnel_id = 2; 89 | } 90 | 91 | message SocksStartRequest { 92 | string client_id = 1; 93 | uint32 socks_port = 2; 94 | } 95 | 96 | message SocksStartResponse {} 97 | 98 | message SocksStopRequest { 99 | string client_id = 1; 100 | } 101 | 102 | message SocksStopResponse {} 103 | 104 | message Tunnel { 105 | string id = 1; 106 | uint32 direction = 2; 107 | uint32 listen_ip = 3; 108 | uint32 listen_port = 4; 109 | uint32 destination_ip = 5; 110 | uint32 destination_port = 6; 111 | } 112 | 113 | message TunnelAddRequest { 114 | string client_id = 1; 115 | Tunnel tunnel = 2; 116 | } 117 | 118 | message TunnelAddResponse {} 119 | 120 | message TunnelDeleteRequest { 121 | string client_id = 1; 122 | string tunnel_id = 2; 123 | } 124 | 125 | message TunnelDeleteResponse {} 126 | 127 | message TunnelListRequest { 128 | string client_id = 1; 129 | } 130 | -------------------------------------------------------------------------------- /grpc/build_protoc.sh: -------------------------------------------------------------------------------- 1 | protoc -I=client --go_out=client/ --go-grpc_out=client/ client/client.proto 2 | protoc -I=admin --go_out=admin/ --go-grpc_out=admin/ admin/admin.proto 3 | -------------------------------------------------------------------------------- /grpc/client/client.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | option java_package = "gtunnel"; 5 | option java_outer_classname = "GTunnel"; 6 | option go_package = "./"; 7 | 8 | package client; 9 | 10 | /* The client service is the grpc interface for a gClient to manage tunnels 11 | * and tcp connections with gServer.*/ 12 | service ClientService { 13 | // Gets a stream of control messages from the server 14 | rpc CreateEndpointControlStream(EndpointControlMessage) returns (stream EndpointControlMessage) {} 15 | // Gets a stream of control messages for creating and deleting tcp connections 16 | rpc CreateTunnelControlStream(stream TunnelControlMessage) returns (stream TunnelControlMessage) {} 17 | // Gets a configuration message based on the bearer token 18 | rpc GetConfigurationMessage(GetConfigurationMessageRequest) returns (GetConfigurationMessageResponse) {} 19 | 20 | // Bidirectional stream representing a TCP connection 21 | rpc CreateConnectionStream(stream BytesMessage) returns (stream BytesMessage) {} 22 | } 23 | 24 | message BytesMessage { 25 | string tunnel_id = 1; 26 | string connection_id = 2; 27 | bytes content = 3; 28 | } 29 | 30 | message GetConfigurationMessageRequest { 31 | string hostname = 1; 32 | }; 33 | 34 | message GetConfigurationMessageResponse {} 35 | 36 | message EndpointControlMessage { 37 | int32 operation = 1; 38 | string tunnel_id = 2; 39 | int32 error_status = 3; 40 | uint32 listen_ip = 4; 41 | uint32 listen_port = 5; 42 | uint32 destination_ip = 6; 43 | uint32 destination_port = 7; 44 | } 45 | 46 | message TunnelControlMessage { 47 | int32 operation = 1; 48 | int32 error_status = 2; 49 | string tunnel_id = 3; 50 | string connection_id = 4; 51 | } 52 | -------------------------------------------------------------------------------- /gserver/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20.3 AS gtunbase 2 | 3 | WORKDIR /go/src/gTunnel 4 | ENV PATH=$PATH:/protoc/bin:$GOPATH/bin 5 | 6 | # We need unzip to install protoc 7 | RUN apt update && apt install -y \ 8 | unzip 9 | 10 | # Install protoc and all dependencies 11 | RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v22.3/protoc-22.3-linux-x86_64.zip &&\ 12 | unzip protoc-22.3-linux-x86_64.zip -d /protoc &&\ 13 | rm protoc-22.3-linux-x86_64.zip &&\ 14 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 &&\ 15 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 16 | 17 | # Copy over all gtunnel files and directories 18 | COPY go.mod . 19 | COPY go.sum . 20 | COPY gserver/ gserver/. 21 | COPY common/ common/. 22 | COPY grpc/ grpc/. 23 | 24 | # Build all protoc files 25 | RUN cd grpc && ./build_protoc.sh && cd .. 26 | 27 | # Get all gtunnel dependencies 28 | RUN go get -d -v ./... && go install -v ./... 29 | 30 | # The image for building gtuncli 31 | FROM gtunbase AS gtuncli 32 | RUN mkdir gtuncli 33 | WORKDIR /go/src/gTunnel/gtuncli 34 | CMD go build -o /build/gtuncli gtuncli.go 35 | 36 | # The gserver image used to run the gtunnel server 37 | FROM gtunbase AS gtunserver-build 38 | RUN apt install -y openssl 39 | # gcc-mingw-w64-i686 \ 40 | # gcc-mingw-w64-x86-64 41 | RUN mkdir tls && openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out tls/cert -subj "/C=/ST=/L=/O=/CN=" -keyout tls/key 42 | RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o gserver/gserver gserver/gServer.go 43 | 44 | FROM alpine:3.7 AS gtunserver-prod 45 | RUN apk --update add redis 46 | COPY --from=gtunserver-build /go/src/gTunnel/gserver/gserver . 47 | CMD ./gserver 48 | 49 | 50 | 51 | # The gserver debug image used for debugging gtunnel server 52 | FROM gtunserver AS gtunserver-debug 53 | RUN go get -u github.com/go-delve/delve/cmd/dlv 54 | CMD ["dlv", "--headless", "--listen=0.0.0.0:2345", "--api-version=2", "debug", "gserver/gServer.go"] 55 | -------------------------------------------------------------------------------- /gserver/Dockerfile.debug: -------------------------------------------------------------------------------- 1 | FROM gtunnel-server:latest 2 | RUN go get -u github.com/go-delve/delve/cmd/dlv 3 | 4 | CMD ["dlv", "--headless", "--listen=0.0.0.0:2345", "--api-version=2", "debug", "gserver/gServer.go"] 5 | -------------------------------------------------------------------------------- /gserver/gServer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | "github.com/hotnops/gTunnel/gserver/gserverlib" 12 | ) 13 | 14 | var ( 15 | tls = flag.Bool("tls", true, "Connection uses TLS if true, else plain HTTP") 16 | certFile = flag.String("cert_file", "tls/cert", "The TLS cert file") 17 | keyFile = flag.String("key_file", "tls/key", "The TLS key file") 18 | clientPort = flag.Int("clientPort", 443, "The server port") 19 | adminPort = flag.Int("adminPort", 1337, "The server port") 20 | logfile = flag.String("logFile", "", "The file where log output will be written") 21 | ) 22 | 23 | // What it do 24 | func main() { 25 | flag.Parse() 26 | 27 | var filePath = "" 28 | s := gserverlib.NewGServer() 29 | 30 | if *logfile == "" { 31 | time := strings.ReplaceAll(time.Now().UTC().String(), " ", "") 32 | filePath = fmt.Sprintf("logs/gtunnel_%s.log", time) 33 | } else { 34 | filePath = *logfile 35 | } 36 | 37 | file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) 38 | 39 | if err != nil { 40 | log.Fatalf("[!] Failed to create log file.") 41 | } 42 | 43 | log.Printf("Logging output to : %s\n", file.Name()) 44 | log.SetOutput(file) 45 | 46 | s.Start(*clientPort, *adminPort, *tls, *certFile, *keyFile) 47 | 48 | } 49 | -------------------------------------------------------------------------------- /gserver/gserverlib/adminService.go: -------------------------------------------------------------------------------- 1 | package gserverlib 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net" 8 | 9 | "github.com/hotnops/gTunnel/common" 10 | as "github.com/hotnops/gTunnel/grpc/admin" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/reflection" 14 | "google.golang.org/grpc/status" 15 | ) 16 | 17 | // AdminServiceServer is a structure that implements all of the 18 | // grpc functions for the AdminServiceServer 19 | type AdminServiceServer struct { 20 | as.UnimplementedAdminServiceServer 21 | gServer *GServer 22 | } 23 | 24 | // NewAdminServiceServer is a constructor that returns an AdminServiceServer 25 | // grpc server. 26 | func NewAdminServiceServer(gServer *GServer) *AdminServiceServer { 27 | adminServer := new(AdminServiceServer) 28 | adminServer.gServer = gServer 29 | return adminServer 30 | } 31 | 32 | // ClientRegister will create a gClient binary and send it back in a binary stream. 33 | func (s *AdminServiceServer) ClientRegister(ctx context.Context, req *as.ClientRegisterRequest) ( 34 | *as.ClientRegisterResponse, error) { 35 | log.Printf("[*] ClientRegister called") 36 | 37 | configuredClient := new(ConfiguredClient) 38 | configuredClient.Arch = req.Arch 39 | configuredClient.Name = req.ClientId 40 | configuredClient.Port = req.Port 41 | configuredClient.Server = req.ServerEndpoint 42 | configuredClient.Token = req.Token 43 | configuredClient.BinType = req.BinType 44 | configuredClient.Platform = req.Platform 45 | configuredClient.Proxy = req.ProxyServer 46 | 47 | err := s.gServer.RegisterClient(configuredClient) 48 | resp := new(as.ClientRegisterResponse) 49 | 50 | return resp, err 51 | } 52 | 53 | // ClientDisconnect will disconnect a gClient from gServer. 54 | func (s *AdminServiceServer) ClientDisconnect(ctx context.Context, req *as.ClientDisconnectRequest) ( 55 | *as.ClientDisconnectResponse, error) { 56 | log.Printf("[*] ClientDisconnect called") 57 | 58 | id := req.ClientId 59 | 60 | s.gServer.DisconnectEndpoint(id) 61 | 62 | resp := new(as.ClientDisconnectResponse) 63 | 64 | return resp, nil 65 | } 66 | 67 | // ClientList will list all configured clients for the gServer and their 68 | // connection status as well as the configured ip, port, and bearer token 69 | func (s *AdminServiceServer) ClientList(req *as.ClientListRequest, 70 | stream as.AdminService_ClientListServer) error { 71 | log.Printf("[*] ClientList called") 72 | 73 | clients := s.gServer.connectedClients 74 | 75 | if len(clients) == 0 { 76 | return status.Error(codes.OutOfRange, "no clients exist") 77 | } 78 | 79 | for _, client := range clients { 80 | resp := new(as.Client) 81 | resp.Name = client.configuredClient.Name 82 | resp.ClientId = client.uniqueID 83 | resp.Status = 1 84 | resp.RemoteAddress = client.remoteAddr 85 | resp.Hostname = client.hostname 86 | resp.ConnectDate = client.connectDate.String() 87 | stream.Send(resp) 88 | } 89 | 90 | return nil 91 | } 92 | 93 | // ConnectionList will list all the connections associated with the provided 94 | // tunnel ID. 95 | func (s *AdminServiceServer) ConnectionList(req *as.ConnectionListRequest, 96 | stream as.AdminService_ConnectionListServer) error { 97 | log.Printf("[*] ConnectionList called") 98 | 99 | clientID := req.ClientId 100 | tunnelID := req.TunnelId 101 | 102 | endpoint, ok := s.gServer.GetEndpoint(clientID) 103 | 104 | if !ok { 105 | return status.Errorf(codes.NotFound, 106 | fmt.Sprintf("client %s does not exist", clientID)) 107 | } 108 | 109 | tunnel, ok := endpoint.GetTunnel(tunnelID) 110 | 111 | if !ok { 112 | return status.Errorf(codes.NotFound, 113 | fmt.Sprintf("tunnel %s does not exist", tunnelID)) 114 | } 115 | 116 | connections := tunnel.GetConnections() 117 | 118 | if len(connections) == 0 { 119 | return status.Errorf(codes.OutOfRange, 120 | fmt.Sprint("no connections exist for tunnel %s", tunnelID)) 121 | } 122 | 123 | for _, connection := range connections { 124 | newCon := new(as.Connection) 125 | sourceIP := connection.TCPConn.LocalAddr().(*net.TCPAddr).IP 126 | destIP := connection.TCPConn.RemoteAddr().(*net.TCPAddr).IP 127 | newCon.SourceIp = common.IpToInt32(sourceIP) 128 | newCon.SourcePort = uint32(connection.TCPConn.LocalAddr().(*net.TCPAddr).Port) 129 | newCon.DestinationIp = common.IpToInt32(destIP) 130 | newCon.DestinationPort = uint32(connection.TCPConn.RemoteAddr().(*net.TCPAddr).Port) 131 | stream.Send(newCon) 132 | } 133 | return nil 134 | } 135 | 136 | // SocksStart will start a Socksv5 proxy server on the provided client ID 137 | func (s *AdminServiceServer) SocksStart(ctx context.Context, 138 | req *as.SocksStartRequest) ( 139 | *as.SocksStartResponse, error) { 140 | log.Printf("[*] SocksStart called") 141 | 142 | clientID := req.ClientId 143 | socksPort := req.SocksPort 144 | 145 | err := s.gServer.StartProxy(clientID, socksPort) 146 | 147 | if err != nil { 148 | return nil, status.Errorf(codes.Internal, err.Error()) 149 | } 150 | 151 | return new(as.SocksStartResponse), nil 152 | } 153 | 154 | // SocksStop will stop a SocksV5 proxy server running on the provided client ID. 155 | func (s *AdminServiceServer) SocksStop(ctx context.Context, 156 | req *as.SocksStopRequest) ( 157 | *as.SocksStopResponse, error) { 158 | log.Printf("[*] SocksStart called") 159 | 160 | clientID := req.ClientId 161 | 162 | err := s.gServer.StopProxy(clientID) 163 | 164 | if err != nil { 165 | return nil, status.Errorf(codes.Internal, err.Error()) 166 | } 167 | 168 | return new(as.SocksStopResponse), nil 169 | } 170 | 171 | // Start will start the grpc server 172 | func (s *AdminServiceServer) Start(port int) { 173 | log.Printf("[*] Starting admin grpc server on port: %d\n", port) 174 | grpcServer := grpc.NewServer() 175 | 176 | lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) 177 | if err != nil { 178 | log.Fatalf("failed to listen: %v", err) 179 | } 180 | 181 | as.RegisterAdminServiceServer(grpcServer, s) 182 | reflection.Register(grpcServer) 183 | 184 | grpcServer.Serve(lis) 185 | } 186 | 187 | // TunnelAdd adds a tunnel to an endpoint specified in the request. 188 | func (s *AdminServiceServer) TunnelAdd(ctx context.Context, req *as.TunnelAddRequest) ( 189 | *as.TunnelAddResponse, error) { 190 | log.Printf("[*] TunnelAdd called") 191 | 192 | if req.Tunnel.Id == "" { 193 | req.Tunnel.Id = common.GenerateString(8) 194 | } 195 | 196 | err := s.gServer.AddTunnel( 197 | req.ClientId, 198 | req.Tunnel.Id, 199 | req.Tunnel.Direction, 200 | common.Int32ToIP(req.Tunnel.ListenIp), 201 | req.Tunnel.ListenPort, 202 | common.Int32ToIP(req.Tunnel.DestinationIp), 203 | req.Tunnel.DestinationPort) 204 | 205 | if err != nil { 206 | return nil, status.Errorf(codes.Internal, err.Error()) 207 | } 208 | 209 | return new(as.TunnelAddResponse), nil 210 | } 211 | 212 | // TunnelDelete deletes a tunnel with the provided tunnel ID 213 | func (s *AdminServiceServer) TunnelDelete(ctx context.Context, req *as.TunnelDeleteRequest) ( 214 | *as.TunnelDeleteResponse, error) { 215 | log.Printf("[*] TunnelDelete called") 216 | 217 | err := s.gServer.DeleteTunnel(req.ClientId, req.TunnelId) 218 | 219 | if err != nil { 220 | return nil, status.Errorf(codes.Internal, err.Error()) 221 | } 222 | 223 | return new(as.TunnelDeleteResponse), nil 224 | } 225 | 226 | // TunnelList lists all tunnels associated with the provided client ID. 227 | func (s *AdminServiceServer) TunnelList(req *as.TunnelListRequest, 228 | stream as.AdminService_TunnelListServer) error { 229 | log.Printf("[*] TunnelList called") 230 | 231 | clientID := req.ClientId 232 | 233 | endpoint, ok := s.gServer.GetEndpoint(clientID) 234 | if !ok { 235 | return status.Error(codes.InvalidArgument, 236 | fmt.Sprintf("Client_ID %s does not exist", clientID)) 237 | } 238 | 239 | tunnels := endpoint.GetTunnels() 240 | 241 | if len(tunnels) == 0 { 242 | return status.Error(codes.OutOfRange, 243 | fmt.Sprintf("%s does not have any tunnels", clientID)) 244 | } 245 | 246 | for id, tunnel := range tunnels { 247 | newTun := new(as.Tunnel) 248 | newTun.Id = id 249 | newTun.Direction = tunnel.GetDirection() 250 | newTun.ListenIp = common.IpToInt32(tunnel.GetListenIP()) 251 | newTun.ListenPort = tunnel.GetListenPort() 252 | newTun.DestinationIp = common.IpToInt32(tunnel.GetDestinationIP()) 253 | newTun.DestinationPort = tunnel.GetDestinationPort() 254 | 255 | stream.Send(newTun) 256 | } 257 | 258 | return nil 259 | } 260 | -------------------------------------------------------------------------------- /gserver/gserverlib/auth.go: -------------------------------------------------------------------------------- 1 | package gserverlib 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/hotnops/gTunnel/common" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/metadata" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | // GetClientInfoFromCtx will return the bearer token string 14 | // from the auth header within the ctx structure. 15 | func GetClientInfoFromCtx(ctx context.Context) (string, string, error) { 16 | md, ok := metadata.FromIncomingContext(ctx) 17 | 18 | if !ok { 19 | return "", "", status.Error(codes.InvalidArgument, "Failed to retrieve metadata") 20 | } 21 | 22 | authHeader, ok := md["authorization"] 23 | if !ok { 24 | return "", "", status.Errorf(codes.Unauthenticated, "Authorization token is not provided") 25 | } 26 | 27 | bearerToken := authHeader[0] 28 | if !strings.HasPrefix(bearerToken, common.BearerString) { 29 | return "", "", status.Errorf(codes.InvalidArgument, "Invalid authorization header") 30 | } 31 | 32 | auth := strings.Split(bearerToken, common.BearerString)[1] 33 | token := strings.Split(auth, "-")[0] 34 | uuid := strings.Split(auth, "-")[1] 35 | return token, uuid, nil 36 | } 37 | -------------------------------------------------------------------------------- /gserver/gserverlib/clientService.go: -------------------------------------------------------------------------------- 1 | package gserverlib 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net" 8 | "time" 9 | 10 | cs "github.com/hotnops/gTunnel/grpc/client" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/credentials" 13 | 14 | "github.com/hotnops/gTunnel/common" 15 | "google.golang.org/grpc/peer" 16 | ) 17 | 18 | type ClientServiceServer struct { 19 | cs.UnimplementedClientServiceServer 20 | gServer *GServer 21 | } 22 | 23 | func NewClientServiceServer(gserver *GServer) *ClientServiceServer { 24 | c := new(ClientServiceServer) 25 | c.gServer = gserver 26 | return c 27 | } 28 | 29 | // GetConfigurationMessage returns the client ID and kill date to a 30 | // gClient based on the bearer token provided. 31 | func (s *ClientServiceServer) GetConfigurationMessage(ctx context.Context, req *cs.GetConfigurationMessageRequest) ( 32 | *cs.GetConfigurationMessageResponse, error) { 33 | 34 | token, uuid, err := GetClientInfoFromCtx(ctx) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | clientConfig := s.gServer.configStore.GetConfiguredClient(token) 40 | 41 | if clientConfig == nil { 42 | log.Printf("[!] Failed to lookup configured client with key: %s\n", token) 43 | return nil, fmt.Errorf("token does not exist in client configuration") 44 | } 45 | 46 | peerInfo, ok := peer.FromContext(ctx) 47 | 48 | if !ok { 49 | log.Printf("[!] Failed to get peer info.") 50 | return nil, fmt.Errorf("getting info from peer context failed") 51 | } 52 | 53 | log.Printf("[*] New client connected: %s\n%s\n%s\n", clientConfig.Name, uuid, peerInfo.Addr.String()) 54 | 55 | connectedclient := new(ConnectedClient) 56 | connectedclient.uniqueID = uuid 57 | connectedclient.remoteAddr = peerInfo.Addr.String() 58 | connectedclient.hostname = req.Hostname 59 | connectedclient.configuredClient = clientConfig 60 | connectedclient.connectDate = time.Now() 61 | connectedclient.endpoint = common.NewEndpoint() 62 | connectedclient.endpointInput = make(chan *cs.EndpointControlMessage) 63 | 64 | s.gServer.AddConnectedClient(uuid, connectedclient) 65 | 66 | configMsg := new(cs.GetConfigurationMessageResponse) 67 | 68 | return configMsg, nil 69 | 70 | } 71 | 72 | // CreateEndpointControl stream is a gRPC function that the client 73 | // calls to establish a one way stream that the server uses to issue 74 | // control messages to the remote endpoint. 75 | func (s *ClientServiceServer) CreateEndpointControlStream( 76 | ctrlMessage *cs.EndpointControlMessage, 77 | stream cs.ClientService_CreateEndpointControlStreamServer) error { 78 | 79 | ctx, cancel := context.WithCancel(stream.Context()) 80 | defer cancel() 81 | 82 | _, uuid, err := GetClientInfoFromCtx(ctx) 83 | 84 | if err != nil { 85 | return err 86 | } 87 | 88 | client, ok := s.gServer.connectedClients[uuid] 89 | 90 | if !ok { 91 | log.Printf("[!] UUID does not exist to create control stream") 92 | return fmt.Errorf("uuid does not exist") 93 | } 94 | 95 | for { 96 | select { 97 | 98 | case controlMessage, ok := <-client.endpointInput: 99 | if !ok { 100 | log.Printf( 101 | "Failed to read from EndpointCtrlStream channel. Exiting") 102 | break 103 | } 104 | stream.Send(controlMessage) 105 | case <-ctx.Done(): 106 | log.Printf("Endpoint disconnected: %s", uuid) 107 | client, ok := s.gServer.connectedClients[uuid] 108 | if !ok { 109 | log.Printf("Endpoint already removed: %s", 110 | uuid) 111 | } 112 | client.endpoint.Stop() 113 | delete(s.gServer.connectedClients, uuid) 114 | return nil 115 | } 116 | } 117 | } 118 | 119 | //CreateTunnelControlStream is a gRPC function that the client will call to 120 | // establish a bi-directional stream to relay control messages about new 121 | // and disconnected TCP connections. 122 | func (s *ClientServiceServer) CreateTunnelControlStream( 123 | stream cs.ClientService_CreateTunnelControlStreamServer) error { 124 | 125 | _, uuid, err := GetClientInfoFromCtx(stream.Context()) 126 | 127 | if err != nil { 128 | return err 129 | } 130 | 131 | client, ok := s.gServer.connectedClients[uuid] 132 | 133 | if !ok { 134 | log.Printf("[!] CreateTunnelControl: uuid doesn't exist: %s\n", uuid) 135 | return fmt.Errorf("uuid does not exist") 136 | } 137 | 138 | tunMessage, err := stream.Recv() 139 | if err != nil { 140 | log.Printf("Failed to receive initial tun stream message: %v", err) 141 | } 142 | 143 | tun, ok := client.endpoint.GetTunnel(tunMessage.TunnelId) 144 | 145 | if !ok { 146 | log.Printf("[!] Received tunnel messsage for ID that doesn't exist") 147 | return fmt.Errorf("failed to establish tunnel") 148 | } 149 | 150 | tun.SetControlStream(stream) 151 | tun.Start() 152 | <-tun.Kill 153 | return nil 154 | } 155 | 156 | // CreateConnectionStream is a gRPC function that the client will call to 157 | // create a bi-directional data stream to carry data that gets delivered 158 | // over the TCP connection. 159 | func (s *ClientServiceServer) CreateConnectionStream( 160 | stream cs.ClientService_CreateConnectionStreamServer) error { 161 | 162 | _, uuid, err := GetClientInfoFromCtx(stream.Context()) 163 | 164 | if err != nil { 165 | return err 166 | } 167 | 168 | client, ok := s.gServer.connectedClients[uuid] 169 | 170 | if !ok { 171 | log.Printf("[!] CreateTunnelControl: uuid doesn't exist: %s\n", uuid) 172 | return fmt.Errorf("uuid does not exist") 173 | } 174 | 175 | bytesMessage, _ := stream.Recv() 176 | tunnel, ok := client.endpoint.GetTunnel(bytesMessage.TunnelId) 177 | 178 | if !ok { 179 | log.Printf("[!] Got a ByteMessage for a non-existent tunnel: %s\n", 180 | bytesMessage.TunnelId) 181 | return fmt.Errorf("invalid tunnel id") 182 | } 183 | 184 | conn := tunnel.GetConnection(bytesMessage.ConnectionId) 185 | 186 | conn.SetStream(stream) 187 | close(conn.Connected) 188 | <-conn.Kill 189 | tunnel.RemoveConnection(conn.ID) 190 | return nil 191 | } 192 | 193 | // Start starts the grpc client service. 194 | func (s *ClientServiceServer) Start( 195 | port int, 196 | tls bool, 197 | certFile string, 198 | keyFile string) { 199 | 200 | log.Printf("[*] Starting client grpc server on port: %d\n", port) 201 | var opts []grpc.ServerOption 202 | opts = append(opts, 203 | grpc.UnaryInterceptor(s.gServer.UnaryAuthInterceptor), 204 | grpc.StreamInterceptor(s.gServer.StreamAuthInterceptor), 205 | ) 206 | 207 | lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) 208 | if err != nil { 209 | log.Fatalf("failed to listen: %v", err) 210 | } 211 | 212 | if tls { 213 | creds, err := credentials.NewServerTLSFromFile(certFile, keyFile) 214 | 215 | if err != nil { 216 | log.Fatalf("Failed to load TLS certificates.") 217 | } 218 | 219 | log.Printf("Successfully loaded key/certificate pair") 220 | opts = append(opts, grpc.Creds(creds)) 221 | } else { 222 | log.Printf("[!] Starting gServer without TLS!") 223 | } 224 | 225 | grpcServer := grpc.NewServer(opts...) 226 | 227 | cs.RegisterClientServiceServer(grpcServer, s) 228 | 229 | grpcServer.Serve(lis) 230 | 231 | } 232 | -------------------------------------------------------------------------------- /gserver/gserverlib/configStore.go: -------------------------------------------------------------------------------- 1 | package gserverlib 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "log" 7 | "sync" 8 | 9 | "github.com/go-redis/redis/v8" 10 | ) 11 | 12 | // ConfigStore is a structure that represents all of the configurations of 13 | // the gServer and will keep state to a json file 14 | type ConfigStore struct { 15 | // This map keeps all of the configured clients in a store that 16 | // uses their bearer token as a key for easy auth lookup 17 | configuredClients map[string]*ConfiguredClient 18 | 19 | // The filename where the configuration will save changes and load 20 | // on start 21 | redisClient *redis.Client 22 | context context.Context 23 | mutex sync.Mutex 24 | } 25 | 26 | func NewConfigStore() *ConfigStore { 27 | configStore := new(ConfigStore) 28 | configStore.context = context.Background() 29 | configStore.redisClient = redis.NewClient(&redis.Options{ 30 | Addr: "localhost:6379", 31 | Password: "", 32 | DB: 0, 33 | }) 34 | 35 | configStore.configuredClients = make(map[string]*ConfiguredClient) 36 | 37 | return configStore 38 | } 39 | 40 | // AddConfiguredClient will take a ConfiguredClient structure and 41 | // add it to the redis datastore 42 | func (c *ConfigStore) AddConfiguredClient(client *ConfiguredClient) error { 43 | 44 | clientJSON, err := json.Marshal(client) 45 | 46 | if err != nil { 47 | log.Printf("[!] Failed to convert configured client into json") 48 | return err 49 | } 50 | 51 | // Make sure that our operations are atmoic 52 | c.mutex.Lock() 53 | defer c.mutex.Unlock() 54 | 55 | err = c.redisClient.Set(c.context, client.Token, clientJSON, 0).Err() 56 | if err != nil { 57 | log.Printf("[!] Failed to insert configured client into redis database") 58 | return err 59 | } 60 | 61 | c.configuredClients[client.Token] = client 62 | 63 | return nil 64 | } 65 | 66 | func (c *ConfigStore) GetConfiguredClient(key string) *ConfiguredClient { 67 | client, ok := c.configuredClients[key] 68 | if !ok { 69 | return nil 70 | } 71 | return client 72 | } 73 | 74 | func (c *ConfigStore) DeleteConfiguredClient(key string) error { 75 | c.mutex.Lock() 76 | defer c.mutex.Unlock() 77 | 78 | err := c.redisClient.Del(c.context, key).Err() 79 | if err != nil { 80 | log.Printf("[!] Failed to delete configured client") 81 | return err 82 | } 83 | 84 | delete(c.configuredClients, key) 85 | 86 | return nil 87 | } 88 | 89 | func (c *ConfigStore) Initialize() error { 90 | c.mutex.Lock() 91 | defer c.mutex.Unlock() 92 | 93 | keys, err := c.redisClient.Keys(c.context, "*").Result() 94 | 95 | if err != nil { 96 | log.Printf("[!] Failed to initialize configuration store") 97 | return err 98 | } 99 | 100 | for _, key := range keys { 101 | clientConfig := new(ConfiguredClient) 102 | 103 | value, _ := c.redisClient.Get(c.context, key).Result() 104 | 105 | err = json.Unmarshal([]byte(value), clientConfig) 106 | if err != nil { 107 | log.Printf("[!] Failed to load configured client") 108 | continue 109 | } 110 | 111 | c.configuredClients[key] = clientConfig 112 | } 113 | 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /gserver/gserverlib/gServerLib.go: -------------------------------------------------------------------------------- 1 | package gserverlib 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net" 8 | "time" 9 | 10 | "github.com/hotnops/gTunnel/common" 11 | cs "github.com/hotnops/gTunnel/grpc/client" 12 | "google.golang.org/grpc" 13 | ) 14 | 15 | type contextKey string 16 | 17 | func (c contextKey) String() string { 18 | return string(c) 19 | } 20 | 21 | type ConfiguredClient struct { 22 | Arch string 23 | BinType string 24 | Name string 25 | Platform string 26 | Port uint32 27 | Proxy string 28 | Server string 29 | Token string 30 | } 31 | 32 | type ConnectedClient struct { 33 | configuredClient *ConfiguredClient 34 | hostname string 35 | remoteAddr string 36 | uniqueID string 37 | connectDate time.Time 38 | endpoint *common.Endpoint 39 | endpointInput chan *cs.EndpointControlMessage 40 | } 41 | 42 | type GServer struct { 43 | //endpoints map[string]*common.Endpoint 44 | //endpointInputs map[string]chan *cs.EndpointControlMessage 45 | configStore *ConfigStore 46 | clientServer *ClientServiceServer 47 | adminServer *AdminServiceServer 48 | connectedClients map[string]*ConnectedClient 49 | } 50 | 51 | // ServerConnectionHandler TODO 52 | type ServerConnectionHandler struct { 53 | server *GServer 54 | endpointID string 55 | tunnelID string 56 | } 57 | 58 | // NewGServer is a constructor that will initialize 59 | // all gServer internal data structures and load any 60 | // existing configuration files. 61 | func NewGServer() *GServer { 62 | 63 | newServer := new(GServer) 64 | 65 | // Create and initialize all of the existing configured clients 66 | newServer.configStore = NewConfigStore() 67 | newServer.configStore.Initialize() 68 | 69 | newServer.clientServer = NewClientServiceServer(newServer) 70 | newServer.adminServer = NewAdminServiceServer(newServer) 71 | newServer.connectedClients = make(map[string]*ConnectedClient) 72 | 73 | return newServer 74 | } 75 | 76 | func NewConfiguredClient(clientData map[string]interface{}) *ConfiguredClient { 77 | c := new(ConfiguredClient) 78 | c.Arch = clientData["Arch"].(string) 79 | c.Name = clientData["Name"].(string) 80 | c.Port = clientData["Port"].(uint32) 81 | c.Server = clientData["Server"].(string) 82 | c.Token = clientData["Token"].(string) 83 | return c 84 | } 85 | 86 | // AddConnectedClient will take in a unique ID and a ConnectedClient structure 87 | // and insert them into the connectedClients map with the unique ID as the key. 88 | func (s *GServer) AddConnectedClient(uuid string, client *ConnectedClient) bool { 89 | _, ok := s.connectedClients[uuid] 90 | 91 | if ok { 92 | log.Printf("[!] Attempting to add client that already exists") 93 | return false 94 | } 95 | s.connectedClients[uuid] = client 96 | 97 | return true 98 | } 99 | 100 | // StreamAuthInterceptor will check for proper authorization for all 101 | // stream based gRPC calls. 102 | func (s *GServer) StreamAuthInterceptor(srv interface{}, 103 | ss grpc.ServerStream, 104 | info *grpc.StreamServerInfo, 105 | handler grpc.StreamHandler) error { 106 | 107 | ctx := ss.Context() 108 | 109 | token, uuid, err := GetClientInfoFromCtx(ctx) 110 | 111 | if err != nil { 112 | return err 113 | } 114 | 115 | client := s.configStore.GetConfiguredClient(token) 116 | 117 | if client == nil { 118 | log.Printf("[!] Invalid bearer token\n") 119 | return fmt.Errorf("invalid bearer token") 120 | } 121 | 122 | _, ok := s.connectedClients[uuid] 123 | 124 | if !ok { 125 | log.Printf("[!] UUID not connected\n") 126 | return fmt.Errorf("uuid not connected") 127 | } 128 | 129 | _ = context.WithValue(ctx, contextKey("uuid"), uuid) 130 | 131 | return handler(srv, ss) 132 | } 133 | 134 | // UnaryAuthInterceptor is called for all unary gRPC functions 135 | // to validate that the caller is authorized. 136 | func (s *GServer) UnaryAuthInterceptor(ctx context.Context, 137 | req interface{}, 138 | info *grpc.UnaryServerInfo, 139 | handler grpc.UnaryHandler) (interface{}, error) { 140 | 141 | token, uuid, err := GetClientInfoFromCtx(ctx) 142 | 143 | if err != nil { 144 | return nil, err 145 | } 146 | 147 | client := s.configStore.GetConfiguredClient(token) 148 | 149 | if client == nil { 150 | log.Printf("[!] Invalid bearer token\n") 151 | return nil, fmt.Errorf("invalid bearer token") 152 | } 153 | 154 | _, ok := s.connectedClients[uuid] 155 | 156 | if ok { 157 | log.Printf("[!] gClient with uuid: %s already connected\n", uuid) 158 | } 159 | 160 | ctx = context.WithValue(ctx, contextKey("uuid"), uuid) 161 | 162 | return handler(ctx, req) 163 | } 164 | 165 | // AddTunnel adds a tunnel to the gRPC server and then messages the gclient 166 | // to perform actions on the other end. 167 | func (s *GServer) AddTunnel( 168 | clientID string, 169 | tunnelID string, 170 | direction uint32, 171 | listenIP net.IP, 172 | listenPort uint32, 173 | destinationIP net.IP, 174 | destinationPort uint32) error { 175 | 176 | client, ok := s.connectedClients[clientID] 177 | 178 | if !ok { 179 | log.Printf("[!] client with uuuid: %s does not exist\n", clientID) 180 | return fmt.Errorf("addtunnel failed - client does not exist") 181 | } 182 | 183 | controlMessage := new(cs.EndpointControlMessage) 184 | controlMessage.Operation = common.EndpointCtrlAddTunnel 185 | controlMessage.TunnelId = tunnelID 186 | newTunnel := common.NewTunnel(tunnelID, 187 | direction, 188 | listenIP, 189 | uint32(listenPort), 190 | destinationIP, 191 | uint32(destinationPort)) 192 | 193 | if direction == common.TunnelDirectionForward { 194 | 195 | controlMessage.DestinationIp = common.IpToInt32(destinationIP) 196 | controlMessage.DestinationPort = uint32(destinationPort) 197 | // The client doesn't need to know what port and IP we are 198 | // listening on 199 | controlMessage.ListenIp = 0 200 | controlMessage.ListenPort = 0 201 | } else if direction == common.TunnelDirectionReverse { 202 | // In the case of a reverse tunnel, the client 203 | // doesn't need to know to where we are forwarding 204 | // the connection 205 | controlMessage.DestinationIp = 0 206 | controlMessage.DestinationPort = 0 207 | controlMessage.ListenIp = common.IpToInt32(listenIP) 208 | controlMessage.ListenPort = uint32(listenPort) 209 | } else { 210 | return fmt.Errorf("invalid tunnel direction") 211 | } 212 | 213 | if _, ok := client.endpoint.GetTunnel(tunnelID); ok { 214 | log.Printf("Tunnel ID already exists for this endpoint. Generating ID instead") 215 | tunnelID = common.GenerateString(common.TunnelIDSize) 216 | } 217 | 218 | f := new(ServerConnectionHandler) 219 | f.server = s 220 | f.endpointID = clientID 221 | f.tunnelID = tunnelID 222 | 223 | newTunnel.ConnectionHandler = f 224 | 225 | if direction == common.TunnelDirectionForward { 226 | 227 | if !newTunnel.AddListener(clientID) { 228 | log.Printf("Failed to start listener. Returning") 229 | return fmt.Errorf("failed to listen on port: %d", listenPort) 230 | } 231 | } 232 | 233 | client.endpoint.AddTunnel(tunnelID, newTunnel) 234 | 235 | client.endpointInput <- controlMessage 236 | 237 | return nil 238 | } 239 | 240 | // DeleteTunnel will kill all TCP connections under the tunnel 241 | // and remove them from the list of managed tunnels. 242 | func (s *GServer) DeleteTunnel( 243 | clientID string, 244 | tunnelID string) error { 245 | 246 | client, ok := s.connectedClients[clientID] 247 | 248 | if !ok { 249 | log.Printf("[!] client with uuuid: %s does not exist\n", clientID) 250 | return fmt.Errorf("deletetunnel failed - client does not exist") 251 | } 252 | 253 | if !client.endpoint.StopAndDeleteTunnel(tunnelID) { 254 | return fmt.Errorf("failed to delete tunnel") 255 | } 256 | 257 | controlMessage := new(cs.EndpointControlMessage) 258 | controlMessage.Operation = common.EndpointCtrlDeleteTunnel 259 | controlMessage.TunnelId = tunnelID 260 | 261 | client.endpointInput <- controlMessage 262 | 263 | return nil 264 | } 265 | 266 | // DisconnectEndpoint will send a control message to the 267 | // current endpoint to disconnect and end execution. 268 | func (s *GServer) DisconnectEndpoint( 269 | clientID string) error { 270 | 271 | log.Printf("Disconnecting %s", clientID) 272 | 273 | client, ok := s.connectedClients[clientID] 274 | 275 | if !ok { 276 | log.Printf("[!] client with uuuid: %s does not exist\n", clientID) 277 | return fmt.Errorf("disconnectendpoint failed - client does not exist") 278 | } 279 | 280 | controlMessage := new(cs.EndpointControlMessage) 281 | controlMessage.Operation = common.EndpointCtrlDisconnect 282 | 283 | client.endpointInput <- controlMessage 284 | return nil 285 | } 286 | 287 | // RegisterClient is responsible for building 288 | // a client executable with the provided parameters. 289 | func (s *GServer) RegisterClient(req *ConfiguredClient) error { 290 | 291 | err := s.configStore.AddConfiguredClient(req) 292 | 293 | if err != nil { 294 | log.Printf("[!] Failed to generate client: %s", err) 295 | s.configStore.DeleteConfiguredClient(req.Token) 296 | return err 297 | } 298 | return nil 299 | } 300 | 301 | // GetClientServer gets the grpc client server 302 | func (s *GServer) GetClientServer() *ClientServiceServer { 303 | return s.clientServer 304 | } 305 | 306 | // GetEndpoint will retreive an endpoint struct with the provided endpoint ID. 307 | func (s *GServer) GetEndpoint(clientID string) (*common.Endpoint, bool) { 308 | client, ok := s.connectedClients[clientID] 309 | 310 | if !ok { 311 | log.Printf("[!] client with uuuid: %s does not exist\n", clientID) 312 | return nil, ok 313 | } 314 | 315 | return client.endpoint, ok 316 | } 317 | 318 | // Start will start the client and admin gprc servers. 319 | func (s *GServer) Start( 320 | clientPort int, 321 | adminPort int, 322 | tls bool, 323 | certFile string, 324 | keyFile string) { 325 | 326 | go s.clientServer.Start(clientPort, tls, certFile, keyFile) 327 | s.adminServer.Start(adminPort) 328 | } 329 | 330 | // StartProxy starts a proxy on the provided endpoint ID 331 | func (s *GServer) StartProxy( 332 | clientID string, 333 | socksPort uint32) error { 334 | 335 | client, ok := s.connectedClients[clientID] 336 | 337 | if !ok { 338 | log.Printf("[!] client with uuuid: %s does not exist\n", clientID) 339 | return fmt.Errorf("startproxy failed - client does not exist") 340 | } 341 | 342 | log.Printf("Starting socks proxy on : %d", socksPort) 343 | controlMessage := new(cs.EndpointControlMessage) 344 | controlMessage.Operation = common.EndpointCtrlSocksProxy 345 | controlMessage.ListenPort = uint32(socksPort) 346 | 347 | client.endpointInput <- controlMessage 348 | 349 | return nil 350 | } 351 | 352 | // StopProxy stops a proxy on the provided endpointID 353 | func (s *GServer) StopProxy( 354 | clientID string) error { 355 | 356 | client, ok := s.connectedClients[clientID] 357 | 358 | if !ok { 359 | log.Printf("[!] client with uuuid: %s does not exist\n", clientID) 360 | return fmt.Errorf("stopproxy failed - client does not exist") 361 | } 362 | 363 | controlMessage := new(cs.EndpointControlMessage) 364 | controlMessage.Operation = common.EndpointCtrlSocksKill 365 | 366 | client.endpointInput <- controlMessage 367 | 368 | return nil 369 | } 370 | 371 | // Acknowledge is called when the remote client acknowledges that a tcp connection can 372 | // be established on the remote side. 373 | func (s *ServerConnectionHandler) Acknowledge(tunnel *common.Tunnel, 374 | ctrlMessage *cs.TunnelControlMessage) common.ByteStream { 375 | 376 | conn := tunnel.GetConnection(ctrlMessage.ConnectionId) 377 | 378 | <-conn.Connected 379 | return conn.GetStream() 380 | } 381 | 382 | //CloseStream will kill a TCP connection locally 383 | func (s *ServerConnectionHandler) CloseStream(tunnel *common.Tunnel, connID string) { 384 | 385 | conn := tunnel.GetConnection(connID) 386 | 387 | close(conn.Kill) 388 | 389 | } 390 | 391 | // GetByteStream will return the gRPC stream associated with a particular TCP connection. 392 | func (s *ServerConnectionHandler) GetByteStream(tunnel *common.Tunnel, 393 | ctrlMessage *cs.TunnelControlMessage) common.ByteStream { 394 | 395 | stream := tunnel.GetControlStream() 396 | conn := tunnel.GetConnection(ctrlMessage.ConnectionId) 397 | 398 | message := new(cs.TunnelControlMessage) 399 | message.Operation = common.TunnelCtrlAck 400 | message.TunnelId = s.tunnelID 401 | message.ConnectionId = ctrlMessage.ConnectionId 402 | // Since gRPC is always client to server, we need 403 | // to get the client to make the byte stream connection. 404 | stream.Send(message) 405 | <-conn.Connected 406 | return conn.GetStream() 407 | } 408 | -------------------------------------------------------------------------------- /gtuncli/gtuncli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "net" 12 | "os" 13 | "strconv" 14 | 15 | "github.com/hotnops/gTunnel/common" 16 | as "github.com/hotnops/gTunnel/grpc/admin" 17 | "github.com/olekukonko/tablewriter" 18 | "google.golang.org/grpc" 19 | ) 20 | 21 | // ServerHost constant is the env variable 22 | // used to configure the host for the gtunnel server 23 | const ServerHost = "GTUNNEL_HOST" 24 | 25 | // ServerPort constant is the env variable 26 | // used to configure the port for the gtunnel server 27 | const ServerPort = "GTUNNEL_PORT" 28 | 29 | // ConfigFileName is the filename in which 30 | // configuration parameters will be read 31 | const ConfigFileName = ".gtunnel.conf" 32 | 33 | var commands = []string{ 34 | "clientlist", 35 | "clientregister", 36 | "clientdisconnect", 37 | "tunnelcreate", 38 | "tunneldelete", 39 | "tunnellist", 40 | "connectionlist", 41 | "socksstart", 42 | "socksstop", 43 | "help"} 44 | 45 | func printCommands(progName string) { 46 | fmt.Printf("[*] Available commands: \n") 47 | 48 | for _, command := range commands { 49 | fmt.Printf("\t%s\n", command) 50 | } 51 | } 52 | 53 | func connect(ip string, port uint32) (as.AdminServiceClient, error) { 54 | addr := fmt.Sprintf("%s:%d", ip, port) 55 | 56 | var opts []grpc.DialOption 57 | opts = append(opts, grpc.WithInsecure()) 58 | 59 | conn, err := grpc.Dial(addr, opts...) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | adminClient := as.NewAdminServiceClient(conn) 65 | return adminClient, nil 66 | } 67 | 68 | func clientList(ctx context.Context, adminClient as.AdminServiceClient) { 69 | req := new(as.ClientListRequest) 70 | stream, err := adminClient.ClientList(ctx, req) 71 | if err != nil { 72 | log.Fatalf("[!] ClientList failed: %s", err) 73 | } 74 | table := tablewriter.NewWriter(os.Stdout) 75 | table.SetHeader([]string{"Name", "Unique ID", "Status", "Remote Address", "Hostname", "Date Connected"}) 76 | for { 77 | message, err := stream.Recv() 78 | if err == io.EOF { 79 | break 80 | } else if err != nil { 81 | log.Fatalf("[!] Error receiving: %s", err) 82 | } else { 83 | name := message.Name 84 | status := fmt.Sprintf("%d", message.Status) 85 | row := []string{name, 86 | message.ClientId, 87 | status, 88 | message.RemoteAddress, 89 | message.Hostname, 90 | message.ConnectDate} 91 | table.Append(row) 92 | } 93 | } 94 | table.Render() 95 | } 96 | 97 | func clientRegister(ctx context.Context, 98 | adminClient as.AdminServiceClient, 99 | args []string) { 100 | clientCreateCmd := flag.NewFlagSet(commands[1], flag.ExitOnError) 101 | clientPlatform := clientCreateCmd.String("platform", "", 102 | "The operating system platform") 103 | serverIP := clientCreateCmd.String("ip", "", 104 | "Address to which the client will connect.") 105 | serverPort := clientCreateCmd.Int("port", 443, 106 | "The port to which the client will connect") 107 | name := clientCreateCmd.String("name", "", 108 | "The unique ID for the generated client. Can be a friendly name") 109 | token := clientCreateCmd.String("token", "", "The token used for authentication") 110 | binType := clientCreateCmd.String("bintype", "", 111 | "The type of output file. Options are exe or dll. Exe works on linux.") 112 | 113 | arch := clientCreateCmd.String("arch", "", 114 | "The architecture of the binary. Options are x64 or x64") 115 | 116 | proxyServer := clientCreateCmd.String("proxy", "", "A proxy server that the client will call through. Empty by default") 117 | 118 | clientCreateCmd.Parse(args) 119 | 120 | if *token == "" { 121 | fmt.Println("[!] clientregister failed: token required") 122 | return 123 | } 124 | 125 | clientCreateReq := new(as.ClientRegisterRequest) 126 | clientCreateReq.ClientId = *name 127 | clientCreateReq.ServerEndpoint = *serverIP 128 | clientCreateReq.Port = uint32(*serverPort) 129 | clientCreateReq.Platform = *clientPlatform 130 | clientCreateReq.BinType = *binType 131 | clientCreateReq.Arch = *arch 132 | clientCreateReq.ProxyServer = *proxyServer 133 | clientCreateReq.Token = *token 134 | 135 | resp, err := adminClient.ClientRegister(ctx, clientCreateReq) 136 | 137 | if err != nil { 138 | fmt.Printf("[!] ClientRegister failed: %s\n", err.Error()) 139 | } 140 | if resp.Error != "" { 141 | fmt.Printf("[!] ClientRegister returned an error: %s\n", err.Error()) 142 | } 143 | 144 | } 145 | 146 | func clientDisconnect(ctx context.Context, 147 | adminClient as.AdminServiceClient, 148 | args []string) { 149 | 150 | disconnectCmd := flag.NewFlagSet(commands[2], flag.ExitOnError) 151 | clientID := disconnectCmd.String("clientid", "", 152 | "The client to disconnect") 153 | disconnectCmd.Parse(args) 154 | 155 | disconnectReq := new(as.ClientDisconnectRequest) 156 | disconnectReq.ClientId = *clientID 157 | 158 | _, err := adminClient.ClientDisconnect(ctx, disconnectReq) 159 | if err != nil { 160 | log.Fatalf("[!] Failed to disconnect: %s", err) 161 | } 162 | } 163 | 164 | func tunnelAdd(ctx context.Context, 165 | adminClient as.AdminServiceClient, 166 | args []string) { 167 | 168 | tunnelAddCmd := flag.NewFlagSet(commands[3], flag.ExitOnError) 169 | clientID := tunnelAddCmd.String("clientid", "", 170 | "The ID of the client that will get the new tunnel") 171 | direction := tunnelAddCmd.String("direction", "forward", 172 | "The direction of the tunnel") 173 | listenIP := tunnelAddCmd.String("listenip", "0.0.0.0", 174 | "The IP address on which the listen port will bind to") 175 | listenPort := tunnelAddCmd.Int("listenport", 0, 176 | "The port on which to accept connections.") 177 | destinationIP := tunnelAddCmd.String("destinationip", "", 178 | "The IP to which connections will be forwarded") 179 | destinationPort := tunnelAddCmd.Int("destinationport", 0, 180 | "The port to which the connection will be forwarded") 181 | tunnelID := tunnelAddCmd.String("tunnelid", "", 182 | "A friendly name for the tunnel. A random string will be generated if none is provided") 183 | 184 | tunnelAddCmd.Parse(args) 185 | 186 | tunnelAddReq := new(as.TunnelAddRequest) 187 | tunnel := new(as.Tunnel) 188 | if *direction == "forward" { 189 | tunnel.Direction = common.TunnelDirectionForward 190 | } else if *direction == "reverse" { 191 | tunnel.Direction = common.TunnelDirectionReverse 192 | } else { 193 | log.Fatalf("Invalid direction. Should be 'forward' or 'reverse'") 194 | } 195 | lIP := net.ParseIP(*listenIP) 196 | dIP := net.ParseIP(*destinationIP) 197 | tunnel.DestinationIp = common.IpToInt32(dIP) 198 | tunnel.DestinationPort = uint32(*destinationPort) 199 | tunnel.ListenIp = common.IpToInt32(lIP) 200 | tunnel.ListenPort = uint32(*listenPort) 201 | 202 | if len(*tunnelID) == 0 { 203 | tunnel.Id = common.GenerateString(common.TunnelIDSize) 204 | } else { 205 | tunnel.Id = *tunnelID 206 | } 207 | 208 | tunnelAddReq.ClientId = *clientID 209 | tunnelAddReq.Tunnel = tunnel 210 | 211 | _, err := adminClient.TunnelAdd(ctx, tunnelAddReq) 212 | 213 | if err != nil { 214 | log.Fatalf("[!] TunnelAdd failed: %s", err) 215 | } 216 | 217 | } 218 | 219 | func tunnelDelete(ctx context.Context, 220 | adminClient as.AdminServiceClient, 221 | args []string) { 222 | 223 | tunnelDeleteCmd := flag.NewFlagSet(commands[4], flag.ExitOnError) 224 | clientID := tunnelDeleteCmd.String("clientid", "", 225 | "The ID of the client that has the tunnel to be deleted") 226 | tunnelID := tunnelDeleteCmd.String("tunnelid", "", 227 | "The ID of the tunnel to delete") 228 | 229 | tunnelDeleteCmd.Parse(args) 230 | 231 | req := new(as.TunnelDeleteRequest) 232 | 233 | req.ClientId = *clientID 234 | req.TunnelId = *tunnelID 235 | 236 | _, err := adminClient.TunnelDelete(ctx, req) 237 | 238 | if err != nil { 239 | log.Fatalf("[!] Failed to delete tunnel: %s", err) 240 | } 241 | } 242 | 243 | func tunnelList(ctx context.Context, 244 | adminClient as.AdminServiceClient, 245 | args []string) { 246 | 247 | tunnelListCmd := flag.NewFlagSet(commands[5], flag.ExitOnError) 248 | clientID := tunnelListCmd.String("clientid", "", 249 | "Tunnels will be listed for this client ID") 250 | 251 | tunnelListCmd.Parse(args) 252 | req := new(as.TunnelListRequest) 253 | req.ClientId = *clientID 254 | 255 | stream, err := adminClient.TunnelList(ctx, req) 256 | if err != nil { 257 | log.Fatalf("[!] TunnelList failed: %s", err) 258 | } 259 | table := tablewriter.NewWriter(os.Stdout) 260 | table.SetHeader([]string{"Client ID", 261 | "Tunnel ID", 262 | "Direction", 263 | "Listen IP", 264 | "Listen Port", 265 | "Destination IP", 266 | "Destination Port"}) 267 | 268 | for { 269 | message, err := stream.Recv() 270 | if err == io.EOF { 271 | break 272 | } else if err != nil { 273 | log.Fatalf("[!] Error receiving: %s", err) 274 | } else { 275 | var direction = "" 276 | if message.Direction == common.TunnelDirectionForward { 277 | direction = "forward" 278 | } else if message.Direction == common.TunnelDirectionReverse { 279 | direction = "reverse" 280 | } 281 | 282 | listenIP := common.Int32ToIP(message.ListenIp) 283 | destIP := common.Int32ToIP(message.DestinationIp) 284 | listenPort := fmt.Sprintf("%d", message.ListenPort) 285 | destPort := fmt.Sprintf("%d", message.DestinationPort) 286 | 287 | row := []string{*clientID, 288 | message.Id, 289 | direction, 290 | listenIP.String(), 291 | listenPort, 292 | destIP.String(), 293 | destPort} 294 | table.Append(row) 295 | 296 | } 297 | } 298 | 299 | table.Render() 300 | } 301 | 302 | func connectionList(ctx context.Context, 303 | adminClient as.AdminServiceClient, 304 | args []string) { 305 | 306 | connectionListCmd := flag.NewFlagSet(commands[6], flag.ExitOnError) 307 | clientID := connectionListCmd.String("clientid", "", 308 | "The client for which connections will be listed") 309 | tunnelID := connectionListCmd.String("tunnelid", "", 310 | "The tunnel for which connections will be listed") 311 | 312 | connectionListCmd.Parse(args) 313 | req := new(as.ConnectionListRequest) 314 | req.ClientId = *clientID 315 | req.TunnelId = *tunnelID 316 | 317 | stream, err := adminClient.ConnectionList(ctx, req) 318 | if err != nil { 319 | log.Fatalf("[!] ConnectionList failed: %s", err) 320 | } 321 | for { 322 | message, err := stream.Recv() 323 | if err == io.EOF { 324 | break 325 | } else if err != nil { 326 | log.Fatalf("[!] Error receiving: %s", err) 327 | } else { 328 | 329 | sourceIP := common.Int32ToIP(message.SourceIp) 330 | destIP := common.Int32ToIP(message.DestinationIp) 331 | 332 | log.Printf("%s\t%d\t%s\t%d\n", 333 | sourceIP, 334 | message.SourcePort, 335 | destIP, 336 | message.DestinationPort) 337 | } 338 | } 339 | } 340 | 341 | func socksStart(ctx context.Context, 342 | adminClient as.AdminServiceClient, 343 | args []string) { 344 | 345 | socksStartCmd := flag.NewFlagSet(commands[6], flag.ExitOnError) 346 | clientID := socksStartCmd.String("clientid", "", 347 | "The ID of the client") 348 | socksPort := socksStartCmd.Int("port", 0, 349 | "The port on which to start the socks server") 350 | 351 | socksStartCmd.Parse(args) 352 | 353 | req := new(as.SocksStartRequest) 354 | req.ClientId = *clientID 355 | req.SocksPort = uint32(*socksPort) 356 | 357 | _, err := adminClient.SocksStart(ctx, req) 358 | 359 | if err != nil { 360 | log.Fatalf("[!] Failed to start socks server: %s", err) 361 | } 362 | } 363 | 364 | func socksStop(ctx context.Context, 365 | adminClient as.AdminServiceClient, 366 | args []string) { 367 | 368 | socksStopCmd := flag.NewFlagSet(commands[6], flag.ExitOnError) 369 | clientID := socksStopCmd.String("clientid", "", 370 | "The ID of the client") 371 | 372 | socksStopCmd.Parse(args) 373 | 374 | req := new(as.SocksStopRequest) 375 | req.ClientId = *clientID 376 | 377 | _, err := adminClient.SocksStop(ctx, req) 378 | 379 | if err != nil { 380 | log.Fatalf("[!] Failed to start socks server: %s", err) 381 | } 382 | } 383 | 384 | func loadConfiguration(hostname *string, port *int) { 385 | var configData map[string]interface{} 386 | 387 | data, err := ioutil.ReadFile(ConfigFileName) 388 | if err != nil { 389 | log.Printf("[*] No configuration file found. Using environment variables") 390 | return 391 | } 392 | 393 | err = json.Unmarshal([]byte(data), &configData) 394 | 395 | if err != nil { 396 | log.Printf("[!] Failed to desrialize configuration file") 397 | log.Fatal(err) 398 | } 399 | 400 | if val, ok := configData["host"]; ok { 401 | *hostname = val.(string) 402 | } 403 | 404 | if val, ok := configData["port"]; ok { 405 | *port = int(val.(float64)) 406 | } 407 | } 408 | 409 | func main() { 410 | 411 | /* 412 | connectionListCmd := flag.NewFlagSet(commands[5], flag.ExitOnError) 413 | 414 | socksStopCmd := flag.NewFlagSet(commands[7], flag.ExitOnError) 415 | */ 416 | 417 | if len(os.Args) == 1 { 418 | printCommands(os.Args[0]) 419 | os.Exit(1) 420 | } 421 | 422 | if os.Args[1] == "-h" { 423 | printCommands(os.Args[0]) 424 | os.Exit(1) 425 | } 426 | 427 | if os.Args[1] == "help" { 428 | printCommands(os.Args[0]) 429 | os.Exit(1) 430 | } 431 | 432 | host := "" 433 | port := 0 434 | 435 | loadConfiguration(&host, &port) 436 | 437 | // Environment variables override configuration file 438 | if os.Getenv(ServerHost) != "" { 439 | host = os.Getenv(ServerHost) 440 | } 441 | if os.Getenv(ServerPort) != "" { 442 | var err error 443 | port, err = strconv.Atoi(os.Getenv(ServerPort)) 444 | if err != nil { 445 | fmt.Println("[!] Invalid port specified.") 446 | os.Exit(1) 447 | } 448 | } 449 | 450 | if host == "" { 451 | fmt.Println("[!] No server host specified.") 452 | os.Exit(1) 453 | } 454 | 455 | if port == 0 { 456 | fmt.Println("[*] Defaulting port to 1337") 457 | port = 1337 458 | } 459 | 460 | adminClient, err := connect(host, uint32(port)) 461 | 462 | if err != nil { 463 | log.Fatalf("[!] Failed to connect to server: %s", err) 464 | } 465 | 466 | ctx, _ := context.WithCancel(context.Background()) 467 | 468 | switch os.Args[1] { 469 | case commands[0]: 470 | clientList(ctx, adminClient) 471 | // List out all the configured clients and their connection status 472 | case commands[1]: 473 | clientRegister(ctx, adminClient, os.Args[2:]) 474 | case commands[2]: 475 | clientDisconnect(ctx, adminClient, os.Args[2:]) 476 | case commands[3]: 477 | tunnelAdd(ctx, adminClient, os.Args[2:]) 478 | case commands[4]: 479 | tunnelDelete(ctx, adminClient, os.Args[2:]) 480 | case commands[5]: 481 | tunnelList(ctx, adminClient, os.Args[2:]) 482 | case commands[6]: 483 | connectionList(ctx, adminClient, os.Args[2:]) 484 | case commands[7]: 485 | socksStart(ctx, adminClient, os.Args[2:]) 486 | case commands[8]: 487 | socksStop(ctx, adminClient, os.Args[2:]) 488 | case commands[9]: 489 | printCommands(os.Args[0]) 490 | os.Exit(1) 491 | default: 492 | log.Printf("[*] Command: %s not recognized\n", os.Args[1]) 493 | } 494 | 495 | } 496 | -------------------------------------------------------------------------------- /start_server.sh: -------------------------------------------------------------------------------- 1 | docker start gtunnel-server &> /dev/null 2 | 3 | if test $? -eq 0 4 | then 5 | echo "[*] Server successfully started" 6 | else 7 | echo "[!] Failed to start gtunnel server" 8 | fi 9 | 10 | 11 | -------------------------------------------------------------------------------- /start_server_debug.sh: -------------------------------------------------------------------------------- 1 | docker run -it --rm --net host -v $PWD/configured:/go/src/gTunnel/configured -v $PWD/logs:/go/src/gTunnel/logs --name gtun-server-debug gtunnel-server-debug 2 | -------------------------------------------------------------------------------- /testdata/.gtunnel.conf: -------------------------------------------------------------------------------- 1 | {"QbcW7ChjefhyB$X[v@Q\u003cF@hWzMnZGuW(X8CBmcF":{"ID":"test","IDHash":""}} -------------------------------------------------------------------------------- /testdata/cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE4TCCAsmgAwIBAgIUfu/e9DJa7nIueNzHPWuz80x46JwwDQYJKoZIhvcNAQEL 3 | BQAwADAeFw0yMDEyMjkxODI0MTdaFw0yMTEyMjkxODI0MTdaMAAwggIiMA0GCSqG 4 | SIb3DQEBAQUAA4ICDwAwggIKAoICAQDaA6qGnD0effelssHjhKN+1ZwpLNW+Ms8O 5 | +YbCR6oGOpsupt6yTR0QTxbS0GdEa/IG+a3nV8Catio17WUazm1zl6BqGX2cX84D 6 | rpd5/S3qMgEPg7oE09KVnySps22ZB5sHCqlAG2lLxDrb3z56U/MkZBkCeV0WzB/l 7 | 7AYrcWkDq8R1ryTLe/QcV6gZyaVxM41ObVmtzUbO4WCZdhRH5RasiuKpF2AZ3YNt 8 | ejCG4YcIQ5jkz18OGGNi/rE9i3d3Dqr+mMcJ7RngW9U4KnqRC4cGjnT3K/CVX2FY 9 | 8VzXi2V0QUT8lMCTse42Zwf63aEnf2wIau/iMSZKi9E59S694ilV6k5z+S4nUktJ 10 | QJc98tT+IJlCmWmQwjEV1sME+P7HFbPB81IZuz6ShUmNRZpDfey+47av58WZ8+q3 11 | d3hcy5McnpGCLhBZCSNleQtOZGBDlHffxMWN/ZXn99bbo3tRVM+0vGj6NMRZHr3e 12 | PlNmihzuxsabBk1INUtHNnJKUQ85j8/EBdqbNX7qpShynaEmjm/gj2xU9YznrgtU 13 | xPxs1aFEgTqZgEXHPWugZGOFPISqElTkJfxpZkxtODsTC8s942zFgIsWk7+wF/X/ 14 | pqBu26UOrhDpaiBDpgIC0fd/SFp0wmN14vkzMN14neACyA76VQqJ8xHZG45Of6pL 15 | SigmZGUgyQIDAQABo1MwUTAdBgNVHQ4EFgQUL5UHhIamcLWG7K/elbUewPW8bYYw 16 | HwYDVR0jBBgwFoAUL5UHhIamcLWG7K/elbUewPW8bYYwDwYDVR0TAQH/BAUwAwEB 17 | /zANBgkqhkiG9w0BAQsFAAOCAgEAsEG64rSa8lw1vYWLXZ6eA+V8JQd9IiNemO5a 18 | N+qcDxuiand0sI5RsOb+f0/wcBic80eDN5HIjVcqYuq+jdPC2YYVG1G7lQ6VlcNB 19 | w+zQiCHAig5mvpKY3vNNmsxvQUWWzaGELU5+cCgTfOMtBIMzsiHYpo9dyemhwI2L 20 | ixJ/TgQzMeMwdJtHYeUhvf0I4JVgUfkzePtcZiZHoVrTiPn/7skwsf5zQh/4rt6r 21 | m/GydenFHd182EFfTqekCtzaRZAMIJbZfvMyvlPGiZDnyNlRkQ1SAqjXDKCCI+fd 22 | c+1LLQHNpIk1yxvQXZ2/0U6SB9sELklIu3a7xH1ru3eSXrI9ejmjNOwMr8hbSxt+ 23 | 8Uzvw/6jxCXIJukkIFsvkm+0+oPzQMZffsyqpx/of6gySHCebpjI3S0EynUZB/ba 24 | i7iWghk2loODFe8JzQtt+jm/Ae7pv1C9ml5PWZ5Lgau9ID1Pgn3rSxKJxVPwyzb9 25 | 7rc79QTragwkXH/dFryjFKBC8m8MFFziOJtKNRGi7qJVgTiDtm6PC1KxUEklISSm 26 | aSjbLAdDsBR4zc2yP3DJ33FqWhPDbiMnBbLhaO3GCupzccHa0TSRr+XCdUnCu9lZ 27 | IK+H0xBCyr8fPStfo028oRb4kAnPemoMSCJd2vUstJkD9P2C+yKElPC6ShdD4Ony 28 | Rs1gDPc= 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /testdata/key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDaA6qGnD0effel 3 | ssHjhKN+1ZwpLNW+Ms8O+YbCR6oGOpsupt6yTR0QTxbS0GdEa/IG+a3nV8Catio1 4 | 7WUazm1zl6BqGX2cX84Drpd5/S3qMgEPg7oE09KVnySps22ZB5sHCqlAG2lLxDrb 5 | 3z56U/MkZBkCeV0WzB/l7AYrcWkDq8R1ryTLe/QcV6gZyaVxM41ObVmtzUbO4WCZ 6 | dhRH5RasiuKpF2AZ3YNtejCG4YcIQ5jkz18OGGNi/rE9i3d3Dqr+mMcJ7RngW9U4 7 | KnqRC4cGjnT3K/CVX2FY8VzXi2V0QUT8lMCTse42Zwf63aEnf2wIau/iMSZKi9E5 8 | 9S694ilV6k5z+S4nUktJQJc98tT+IJlCmWmQwjEV1sME+P7HFbPB81IZuz6ShUmN 9 | RZpDfey+47av58WZ8+q3d3hcy5McnpGCLhBZCSNleQtOZGBDlHffxMWN/ZXn99bb 10 | o3tRVM+0vGj6NMRZHr3ePlNmihzuxsabBk1INUtHNnJKUQ85j8/EBdqbNX7qpShy 11 | naEmjm/gj2xU9YznrgtUxPxs1aFEgTqZgEXHPWugZGOFPISqElTkJfxpZkxtODsT 12 | C8s942zFgIsWk7+wF/X/pqBu26UOrhDpaiBDpgIC0fd/SFp0wmN14vkzMN14neAC 13 | yA76VQqJ8xHZG45Of6pLSigmZGUgyQIDAQABAoICABYO/ucF0e1O44rBbnq9xekk 14 | ZduIrMvEykyClM79f0eIEIGE4s15nfIzfmSwL3Kt1Vw9UQoyR8ufmN/B+FPhBRQI 15 | CHQTxjDXRLn8pVjzc/zZOGBAfMbWQ91dr9D31aQBL/9jZcKVw26mGSp8CQ9JhTVu 16 | Z1VnwDJ5TtTtr6YfoHd9nrAlm3x3OzjO6bRKQdIVXDvONp+uQHbYOPTuDJlFpaip 17 | mMtxEQks74pZLaXIFeiRJlwoWz7K2ut+R6KphNnoAwcloCXx9odL42P+cE5Ss0PH 18 | kDAeIwaKEwhzy2gHs6kynW9NAbsv6qFYhWctm4uqidzELSdev7gVM4NAuoz94B6g 19 | dmFY4VTEYGFE4/QT9MW8U5vZumfuYr+yDg21SJWwIZ8xtq8N/Mt7Pp3vYx62zejr 20 | 1Khl4kSerlzIgnfBJMJLCLTzdC1WbZlTTR/MzJ6jKQHRLzmXzMFpzUsy7rAjz9sN 21 | 6QFpElk9bB/Z/rkjAIkmMd05P8tGQH4X1+obU5zWzK4OzldsqQzHO/HKUMsSBXM3 22 | 2qcfJdIRG347VEPLlncgHWmoGGgEllMxnQLUyblLDKx0Ok22RucHxLX8F2s4m59v 23 | Ys3FKoq8Re5UbpCGb28x/LFWfbTC77qXcfJkFl2jsqroJ8jAhRolefPaQ8e/jBhm 24 | WncAmvJlslUXDw2KFWaxAoIBAQDyukQSUzeDNohbAGzQk3ch/hSmxq+lNCglct6f 25 | D3DYmKF2LX6jxfmVGwfv6szctPK4OFGJcIzvl0fifl4Y3GdlFMEtd01EFow7pvSl 26 | SU9/P2sIan+gHb/tSueYRq5pp+2BmHtheQD6L7j0KPHHI800R1u42DsLOE8fn9Py 27 | OQZR2vr5RPWUP8g7yZg9LkN5y8og9myQjC5tY9f15XG5OGmdl31KTOkFGe8ngz2w 28 | SY8qs0dWvoiy1XTBdMYTllXL0wxBAB0+Cj2bJpUxYdzeD0oe7u3jGfpD0A1VdTFb 29 | mmYmpyfioKacV5/zTaFYcopD+7Hl2Tte9yEUG2VmVhRpkqmlAoIBAQDl73XYxrh1 30 | WpFcmsGKTm8N/zPDrkgL2+qwW4xCc2k0mb5qTsXrRbwHsHap//4kQ1n7VDOSTz2M 31 | GxdLMJMKGV6PlHV7UraAy9g9acpJl/+OuqNddYx94qnkYEoM6J4I69PTE4rn/ylc 32 | Sf6Z8YGg0atekhwWyiicoPNdvfbX/rEpIJIX4X/9COATGHjsfaSzGcor/C6qiRLc 33 | ctqNcIEye4NYhFl/l6cDTJDtLyyRzrHdeqObrDeyoVlDZjfvxl0vT5jvJ6Ifr6j3 34 | 8Hp3N6lPFcNE0cjAs3dDDzFR3djKjeEM10RvCvQcdk0BC/CeiD78LYnHYxu1Bo6l 35 | K2rMEnFfDAlVAoIBABWU0Cac9YuaFqeYsxQceHQyRh3J/qX+rC8gNoEH+22WrIx4 36 | YREoshc6zwra4pohEI+mmkFRG4bV+ZM+zxkAx8SqAr8LI3iRVKidIJVtnj7fIaIe 37 | 7fgLjRwJE+xrJGjKgIiMSTQScS7NfXFgDpc5LK6gRZx6xU0rFYXnTQ31WgV/Jd9x 38 | 7S/gN+pw8zNFYMR8hR1HK8HyZNJfFT2Mx/7C8EkNxhsMGY/VjXkC50I8WBlg1U/2 39 | IutbpUTqIA5vyHMV8bn5y5/Pg0i/Nnasj9nCnEGB1u7hSWosHnNhtyDA6qI3LImu 40 | 5QFznQoh9d2KWZbP0zJvjSLVcEB8axidQLVeT+0CggEBAOQJjLKkwqOAYtGYmYF1 41 | MCaNhuT2n3VDcYneAUiV9Sy5nWMJJLikvEpT9Bu9SmrkcQUVCOB9veiJpZxoXCuT 42 | 5Xg4IBtieKVbhOXDWV+LY2RGLWavAdqHRT69UDNlL/ZhC/82TnivUnG44MwD/C9m 43 | QbLU7Q6dB5Hu/e1mTL0CVukW6EzO04Q7nR7TEnpYUVNerGba/90ZfJ8tORs1DKzo 44 | WecbuzcTTAu1j7XrJySj/TpOOpxVPBfodQKnCaq+7APTyTTF9b+/ErByysIZbcyF 45 | JNfHxnV2hzz2gE9U2YBpHV0C0VffYSIpIU18bfKL5Qtsjkw0OnJaQnYEjTI+yDkf 46 | UtECggEBAK95mSaXAymscK8PRE8gXEDDYP3SHqElOYI8kJemsDcXy0ryq/MnEH7Z 47 | cDakuSEwI8mPKEM8CSBPL0mfF9ej59c7EzM/opu+x5B6izpOfxk4hapWlW/BiIjq 48 | re6CsMPeF0qihUsk3mM9Mg1Krc8TWL1XC8TH5rZvx8bjQSshJBIIzZTU7KJYF3Je 49 | bEogAtfCYlxWdfXJPOfz5U0szCqbjr0CRc9iikjCcAe295dpKx2vyA21Cms47Lmu 50 | 7pmFpxPxu62gjMT/7LL5mimbPO8fpJqOwapzGxfpN/BXLPax7befUyDDvSh3wu2m 51 | gQ/LmzySmWw3uOf+GXsu6fh3ntrX34U= 52 | -----END PRIVATE KEY----- 53 | --------------------------------------------------------------------------------