├── .gitignore ├── go.mod ├── go.sum ├── crossbuild.sh ├── main.go ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── README.md ├── LICENSE ├── client.go └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | binaries -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rinsuki/wstunnel 2 | 3 | go 1.13 4 | 5 | require github.com/gorilla/websocket v1.4.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= 2 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 3 | -------------------------------------------------------------------------------- /crossbuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir binaries 4 | gox -os="windows darwin linux" -arch="amd64 386 arm arm64" -osarch="!darwin/arm !darwin/arm64 !darwin/386" -output "binaries/{{.Dir}}_{{.OS}}_{{.Arch}}" -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | if len(os.Args) == 4 { 10 | switch os.Args[1] { 11 | case "server": 12 | server(os.Args[2], os.Args[3]) 13 | os.Exit(0) 14 | case "client": 15 | client(os.Args[2], os.Args[3]) 16 | os.Exit(0) 17 | } 18 | } 19 | fmt.Println("Usage: ") 20 | fmt.Printf(" %s server \n", os.Args[0]) 21 | fmt.Printf(" %s client \n", os.Args[0]) 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/setup-go@v1 15 | with: 16 | go-version: 1.13 17 | - run: go get github.com/mitchellh/gox 18 | - uses: actions/checkout@v2 19 | - run: PATH=$HOME/go/bin:$PATH ./crossbuild.sh 20 | - uses: actions/upload-artifact@v1 21 | with: 22 | name: binaries 23 | path: binaries -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wstunnel 2 | 3 | TCP over WebSocket 4 | 5 | ``` 6 | [something tcp server] 7 | | 8 | | <= TCP 9 | | 10 | [wstunnel server] 11 | || 12 | || <= WebSocket 13 | || 14 | [you can add some reverse-proxy or other to here] 15 | || 16 | || <= WebSocket 17 | || 18 | [wstunnel client] 19 | | 20 | | <= TCP 21 | | 22 | [something tcp client] 23 | ``` 24 | 25 | ## How to Use 26 | 27 | 1. Launch `wstunnel server`. e.g. `wstunnel server 0.0.0.0:8888 127.0.0.1:25565` 28 | 1. Launch `wstunnel client`. e.g. `wstunnel client 127.0.0.1:25565 ws://server:8888/ws` 29 | 1. Connect to your local port. 30 | 1. :tada: -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Get the version 12 | id: get_version 13 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 14 | - uses: actions/setup-go@v1 15 | with: 16 | go-version: 1.13 17 | - run: go get github.com/mitchellh/gox 18 | - uses: actions/checkout@v2 19 | - run: PATH=$HOME/go/bin:$PATH ./crossbuild.sh 20 | - uses: actions/create-release@v1 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | with: 24 | tag_name: ${{ github.ref }} 25 | release_name: Release ${{ steps.get_version.outputs.VERSION }} 26 | draft: true 27 | - uses: svenstaro/upload-release-action@1.1.0 28 | with: 29 | repo_token: ${{ secrets.GITHUB_TOKEN }} 30 | file: binaries/* 31 | tag: ${{ github.ref }} 32 | file_glob: "true" 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 rinsuki 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 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | "github.com/gorilla/websocket" 8 | ) 9 | 10 | func client(bindIP string, websocketURL string) { 11 | listener, err := net.Listen("tcp", bindIP) 12 | if err != nil { 13 | panic(err) 14 | } 15 | for { 16 | tcp, err := listener.Accept() 17 | if err != nil { 18 | log.Println(err) 19 | return 20 | } 21 | go func() { 22 | 23 | defer tcp.Close() 24 | ws, _, err := websocket.DefaultDialer.Dial(websocketURL, nil) 25 | if err != nil { 26 | log.Println(err) 27 | return 28 | } 29 | defer ws.Close() 30 | go func() { 31 | buf := make([]byte, 1024) 32 | for { 33 | len, err := tcp.Read(buf) 34 | if err != nil { 35 | log.Println(err) 36 | tcp.Close() 37 | ws.Close() 38 | break 39 | } 40 | log.Printf("C→S %d", len) 41 | ws.WriteMessage(websocket.BinaryMessage, buf[0:len]) 42 | } 43 | }() 44 | for { 45 | msgType, buf, err := ws.ReadMessage() 46 | if err != nil { 47 | log.Println(err) 48 | tcp.Close() 49 | ws.Close() 50 | break 51 | } 52 | if msgType != websocket.BinaryMessage { 53 | log.Println("unknown msgType") 54 | } 55 | log.Printf("S→C %d", len(buf)) 56 | tcp.Write(buf) 57 | } 58 | }() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "net/http" 7 | 8 | "github.com/gorilla/websocket" 9 | ) 10 | 11 | var upgrader = websocket.Upgrader{ 12 | ReadBufferSize: 1024, 13 | WriteBufferSize: 1024, 14 | } 15 | 16 | type Server struct { 17 | DestAddress string 18 | } 19 | 20 | func (s *Server) handler(w http.ResponseWriter, r *http.Request) { 21 | log.Println("Incoming --> ", r.RemoteAddr, r.Header) 22 | conn, err := upgrader.Upgrade(w, r, nil) 23 | if err != nil { 24 | log.Println(err) 25 | return 26 | } 27 | defer conn.Close() 28 | tcp, err := net.Dial("tcp", s.DestAddress) 29 | if err != nil { 30 | log.Println(err) 31 | return 32 | } 33 | defer tcp.Close() 34 | go func() { 35 | buf := make([]byte, 1024) 36 | for { 37 | len, err := tcp.Read(buf) 38 | if err != nil { 39 | log.Println(err) 40 | conn.Close() 41 | tcp.Close() 42 | break 43 | } 44 | conn.WriteMessage(websocket.BinaryMessage, buf[0:len]) 45 | } 46 | }() 47 | for { 48 | msgType, buf, err := conn.ReadMessage() 49 | if err != nil { 50 | log.Println(err) 51 | conn.Close() 52 | tcp.Close() 53 | break 54 | } 55 | if msgType != websocket.BinaryMessage { 56 | log.Println("unknown msgType") 57 | } 58 | tcp.Write(buf) 59 | } 60 | } 61 | 62 | func server(bindIP string, destIP string) { 63 | s := Server{ 64 | DestAddress: destIP, 65 | } 66 | http.HandleFunc("/ws", s.handler) 67 | log.Fatal(http.ListenAndServe(bindIP, nil)) 68 | } 69 | --------------------------------------------------------------------------------