├── .dockerignore ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── client └── client.go ├── docker-compose.yml ├── go.mod ├── go.sum ├── main.go ├── proxy └── proxy.go ├── server └── server.go └── wsconnadapter └── wsconnadapter.go /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | docker-compose.yml 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/go,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=go,visualstudiocode 4 | 5 | ### Go ### 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | 22 | ### Go Patch ### 23 | /vendor/ 24 | /Godeps/ 25 | 26 | ### VisualStudioCode ### 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | 33 | ### VisualStudioCode Patch ### 34 | # Ignore all local history of files 35 | .history 36 | 37 | # End of https://www.gitignore.io/api/go,visualstudiocode -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | services: 3 | - docker 4 | 5 | script: 6 | - docker login -u $DOCKER_USER -p $DOCKER_PASS 7 | - docker build -t xandout/soxy . 8 | - docker push xandout/soxy -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang as builder 2 | 3 | WORKDIR /go/src/github.com/xandout/soxy 4 | COPY . ./ 5 | 6 | RUN go get -d -v ./... 7 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/soxy 8 | RUN chmod +x /go/bin/soxy 9 | 10 | 11 | FROM alpine 12 | COPY --from=builder /go/bin/soxy / 13 | RUN apk add --no-cache \ 14 | libc6-compat && \ 15 | chmod +x /soxy 16 | ENTRYPOINT ["/soxy"] 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SOXY [![Build Status](https://travis-ci.com/xandout/soxy.svg?branch=master)](https://travis-ci.com/xandout/soxy) 2 | 3 | Soxy has a simple design. Run a `soxy` server behind your Ingress or other HTTP reverse proxy such as nginx. 4 | 5 | When a client connects to the `soxy` server, the server opens a connection to the backend service and pipes the data back to a local port. Traffic is over a websocket tunnel so it is just regular HTTP traffic. 6 | 7 | My test rig is a k8s cluster with `ingress-nginx` providing HTTPS termination. This makes it easy to secure the traffic on your tunnels. 8 | 9 | 10 | ## Server mode 11 | 12 | This will start a `soxy` server which listens on port 8080 for HTTP connections. 13 | ``` 14 | soxy serve -p :8080 15 | ``` 16 | 17 | 18 | 19 | ## Client mode 20 | 21 | This will start a client on your local laptop, proxying connections on port 8479 to `mongodb-service:27017` as seen from the proxy. 22 | 23 | ``` 24 | soxy proxy -U ws://soxy-server.com:8080 -L :8479 -R mongodb-service:27017 25 | ``` 26 | 27 | If you have `soxy` behind an HTTPS ingress or reverse proxy, you need to use 28 | 29 | ``` 30 | soxy proxy -U wss://soxy-server.com:8080 -L :8479 -R mongodb-service:27017 31 | ``` 32 | > Notice the extra `s` in `wss` 33 | 34 | 35 | You can now connect to the remote service from your workstation as follows 36 | 37 | ``` 38 | mongo --host 127.0.0.1 --port 8479 39 | ``` 40 | 41 | 42 | ## DEMO 43 | 44 | ### docker-compose 45 | The included [docker-compose.yml](docker-compose.yml) sets up a demo environment with 46 | 47 | * Frontend and backend networks to mimic real world topologies 48 | * Backend Redis DB service 49 | * Backend `soxy` service 50 | * Frontend nginx service 51 | * Frontend `soxy` client exposes `backend-redis:6379` 52 | 53 | 54 | Data path is redis-cli -> local TCP socket(soxy) -> websocket connection through nginx -> soxy server -> redis container. 55 | 56 | ``` 57 | ✔ ~/go/src/github.com/xandout/soxy [master|✔] 58 | 02:10 # docker-compose up -d 59 | Creating network "soxy_back" with the default driver 60 | Creating network "soxy_front" with the default driver 61 | Creating soxy_backend-redis_1 ... done 62 | Creating soxy_backend-soxy-server_1 ... done 63 | Creating soxy_frontend-nginx_1 ... done 64 | Creating soxy_frontend-soxy-client_1 ... done 65 | ✔ ~/go/src/github.com/xandout/soxy [master|✔] 66 | 02:14 # docker run --rm -it redis redis-cli -h 192.168.0.250 INFO CPU 67 | # CPU 68 | used_cpu_sys:0.025931 69 | used_cpu_user:0.153783 70 | used_cpu_sys_children:0.001324 71 | used_cpu_user_children:0.001367 72 | ``` 73 | 74 | 75 | ### Just a quick local demo proxying redis 76 | ``` 77 | ✔ ~/go/src/github.com/xandout/soxy [master|✔] 78 | 01:24 # docker run --rm -d -p 6379:6379 redis 79 | 7cefb999a6a23e03883a41776b74304506249ae0368b81cd8308865f826fe404 80 | ✔ ~/go/src/github.com/xandout/soxy [master|✔] 81 | 01:22 # docker run --rm -d -p 8080:8080 xandout/soxy serve -p :8080 82 | 6690096b456e64baf3223df80f590ea2e0962c8c3f07bcdebcb9b0f25dadb3e6 83 | ✔ ~/go/src/github.com/xandout/soxy [master|✔] 84 | 01:22 # docker run --rm -d -p 8081:8081 xandout/soxy proxy -U ws://192.168.0.250:8080 -L :8081 -R 192.168.0.250:6379 85 | 423ccbbfa4b6d961f4fb1b740e93bf38bfda9a96d03c56e3f8188b87d4a88d5b 86 | ✔ ~/go/src/github.com/xandout/soxy [master|✔] 87 | 01:22 # docker run --rm -it redis redis-cli -h 192.168.0.250 -p 8081 88 | 192.168.0.250:8081> INFO CPU 89 | # CPU 90 | used_cpu_sys:86.098951 91 | used_cpu_user:74.249437 92 | used_cpu_sys_children:0.001757 93 | used_cpu_user_children:0.000798 94 | 192.168.0.250:8081> 95 | ``` 96 | 97 | ### Secured traffic over the Internet to a k8s cluster 98 | ``` 99 | 01:25 # docker run --rm -d -p 8082:8082 xandout/soxy proxy -U wss://soxy.my-kubernetes-cluster.com -L :8082 -R mongodb-service:27017 100 | f7393b5b5254bd5c4ad0b4c8cb0ed3ac1cd0dc7c73bef909eca4cdf896bb8865 101 | ✔ ~/go/src/github.com/xandout/soxy [master|✔] 102 | 01:26 # mongo --host 192.168.0.250 --port 8082 103 | MongoDB shell version v4.0.3 104 | connecting to: mongodb://192.168.0.250:8082/ 105 | WARNING: No implicit session: Logical Sessions are only supported on server versions 3.6 and greater. 106 | Implicit session: dummy session 107 | MongoDB server version: 3.0.12 108 | WARNING: shell and server versions do not match 109 | > show databases; 110 | local 0.078GB 111 | > 112 | ``` 113 | ## Usage 114 | 115 | ``` 116 | # soxy -h 117 | NAME: 118 | soxy - fight the loneliness! 119 | 120 | USAGE: 121 | soxy [global options] command [command options] [arguments...] 122 | 123 | COMMANDS: 124 | serve Start proxying traffic(server) 125 | proxy Start proxying traffic(client) 126 | help, h Shows a list of commands or help for one command 127 | 128 | GLOBAL OPTIONS: 129 | --help, -h show help (default: false) 130 | 131 | 132 | # soxy proxy -h 133 | NAME: 134 | soxy proxy - Start proxying traffic(client) 135 | 136 | USAGE: 137 | soxy proxy [command options] [arguments...] 138 | 139 | OPTIONS: 140 | --soxy-url value, -U value ws://soxy-daemon.com:8080 141 | --local value, -L value Which local port to listen on. 142 | Example: :3306 or 0.0.0.0:3306 143 | --remote value, -R value Where should the daemon proxy traffic to? 144 | Example: mysql-service:3306 145 | --help, -h show help (default: false) 146 | 147 | 148 | # soxy serve -h 149 | NAME: 150 | soxy serve - Start proxying traffic(server) 151 | 152 | USAGE: 153 | soxy serve [command options] [arguments...] 154 | 155 | OPTIONS: 156 | --port value, -p value 157 | --help, -h show help (default: false) 158 | 159 | ``` -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | 8 | "github.com/gorilla/websocket" 9 | log "github.com/sirupsen/logrus" 10 | "github.com/urfave/cli/v2" 11 | "github.com/xandout/soxy/proxy" 12 | ) 13 | 14 | // Start starts a soxy client 15 | func Start(c *cli.Context) error { 16 | 17 | l, err := net.Listen("tcp", c.String("local")) 18 | if err != nil { 19 | log.Errorf("TCP LISTENER: %v", err.Error()) 20 | os.Exit(1) 21 | } 22 | // Close the listener when the application closes. 23 | defer l.Close() 24 | log.Info("Listening on " + c.String("local")) 25 | for { 26 | // Listen for an incoming connection. 27 | tcpConn, err := l.Accept() 28 | if err != nil { 29 | log.Errorf("TCP ACCEPT: %v", err.Error()) 30 | } 31 | fmtString := "%s/?remote=%s" 32 | fmted := fmt.Sprintf(fmtString, c.String("soxy-url"), c.String("remote")) 33 | 34 | clientWsConn, _, err := websocket.DefaultDialer.Dial(fmted, nil) 35 | if err != nil { 36 | log.Errorf("DIALER: %v", err.Error()) 37 | return err 38 | } 39 | // Handle connections in a new goroutine. 40 | log.Infof("Proxying traffic to %v via %v for %v", c.String("remote"), clientWsConn.RemoteAddr(), tcpConn.RemoteAddr()) 41 | go proxy.Copy(clientWsConn, tcpConn) 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.4' 2 | services: 3 | backend-redis: 4 | image: redis 5 | networks: 6 | - back 7 | backend-soxy-server: 8 | build: 9 | context: . 10 | image: xandout/soxy 11 | networks: 12 | - back 13 | command: serve -p :8080 14 | depends_on: 15 | - backend-redis 16 | frontend-nginx: 17 | image: nginx 18 | ports: 19 | - 8080:8080 20 | networks: 21 | - front 22 | - back 23 | command: 24 | - /bin/bash 25 | - -c 26 | - | 27 | echo $$NGINX_CONF > /etc/nginx/conf.d/soxy.conf 28 | nginx -g "daemon off;" 29 | environment: 30 | - NGINX_CONF= 31 | server { 32 | server_name localhost; 33 | listen 8080; 34 | location / { 35 | proxy_set_header Upgrade $$http_upgrade; 36 | proxy_set_header Connection "upgrade"; 37 | proxy_pass http://backend-soxy-server:8080; 38 | } 39 | } 40 | depends_on: 41 | - backend-redis 42 | - backend-soxy-server 43 | frontend-soxy-client: 44 | build: 45 | context: . 46 | image: xandout/soxy 47 | networks: 48 | - front 49 | command: proxy -U ws://frontend-nginx:8080 -L :6379 -R backend-redis:6379 50 | ports: 51 | - 6379:6379 52 | depends_on: 53 | - backend-redis 54 | - backend-soxy-server 55 | - frontend-nginx 56 | networks: 57 | front: 58 | back: -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xandout/soxy 2 | 3 | require ( 4 | github.com/gorilla/websocket v1.4.1 5 | github.com/isobit/ws-tcp-relay v0.2.0 // indirect 6 | github.com/jpillora/backoff v1.0.0 // indirect 7 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect 8 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 9 | github.com/modern-go/reflect2 v1.0.1 // indirect 10 | github.com/prometheus/client_golang v1.3.0 // indirect 11 | github.com/recws-org/recws v1.0.1 // indirect 12 | github.com/sirupsen/logrus v1.4.2 13 | github.com/stretchr/objx v0.2.0 // indirect 14 | github.com/stretchr/testify v1.4.0 // indirect 15 | github.com/urfave/cli v1.22.2 16 | github.com/urfave/cli/v2 v2.1.1 17 | github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 // indirect 18 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa // indirect 19 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 // indirect 20 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 21 | gopkg.in/yaml.v2 v2.2.7 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 3 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 4 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 5 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 7 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 8 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 9 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 10 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 11 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 12 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 13 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 17 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 18 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 19 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 20 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 21 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 22 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 23 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 24 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 25 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 26 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 27 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 28 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 29 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= 30 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 31 | github.com/isobit/ws-tcp-relay v0.2.0 h1:rTHsVpmDwzaIcB8RkUHPAmulxSXNS8FQ5j8RlENd2TE= 32 | github.com/isobit/ws-tcp-relay v0.2.0/go.mod h1:oCDkjU3FE3YcD3HmtzQYoOAb5M8lv0XRkMkhY+Lw/r4= 33 | github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 h1:K//n/AqR5HjG3qxbrBCL4vJPW0MVFSs9CPK1OOJdRME= 34 | github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= 35 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 36 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 37 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 38 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 39 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 40 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 41 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 42 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 43 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 44 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 45 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 46 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 47 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 48 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 49 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 50 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 51 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 52 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 53 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 54 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 55 | github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc= 56 | github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= 57 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 58 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 59 | github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE= 60 | github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 61 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 62 | github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= 63 | github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= 64 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 65 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 66 | github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= 67 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 68 | github.com/recws-org/recws v1.0.1 h1:/878vAx0KHlfbm+qH0+xRuAvT+jAFUTWn2y8gYZl1Hg= 69 | github.com/recws-org/recws v1.0.1/go.mod h1:MPJ3Kbcw2pJQjfYJONfkqwv7Cl0oAEW1MDduN0yo3v4= 70 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 71 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 72 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 73 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 74 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 75 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 76 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 77 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 78 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 79 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 80 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 81 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 82 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 83 | github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= 84 | github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 85 | github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= 86 | github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= 87 | github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 h1:1+FQ4Ns+UZtUiQ4lP0sTCyKSQ0EXoiwAdHZB0Pd5t9Q= 88 | github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997/go.mod h1:DIGbh/f5XMAessMV/uaIik81gkDVjUeQ9ApdaU7wRKE= 89 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 90 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 91 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 92 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 93 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= 94 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 95 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 96 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 97 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 98 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 99 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 100 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 101 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= 102 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 103 | golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= 105 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 106 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 107 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 108 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 109 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 110 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 111 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 112 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 113 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Mitchell Turner contact@opsdepartment.com 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import ( 19 | log "github.com/sirupsen/logrus" 20 | "github.com/urfave/cli/v2" 21 | "github.com/xandout/soxy/client" 22 | "github.com/xandout/soxy/server" 23 | "os" 24 | ) 25 | 26 | func main() { 27 | 28 | app := &cli.App{ 29 | Name: os.Args[0], 30 | Usage: "fight the loneliness!", 31 | } 32 | 33 | app.Commands = []*cli.Command{ 34 | { 35 | Name: "serve", 36 | Usage: "Start proxying traffic(server)", 37 | Flags: []cli.Flag{ 38 | &cli.StringFlag{Name: "port", Aliases: []string{"p"}}, 39 | }, 40 | Action: server.Start, 41 | }, 42 | { 43 | Name: "proxy", 44 | Usage: "Start proxying traffic(client)", 45 | Flags: []cli.Flag{ 46 | &cli.StringFlag{Name: "soxy-url", Aliases: []string{"U"}, Usage: "ws://soxy-daemon.com:8080"}, 47 | &cli.StringFlag{Name: "local", Aliases: []string{"L"}, Usage: "Which local port to listen on.\n\tExample: :3306 or 0.0.0.0:3306"}, 48 | &cli.StringFlag{Name: "remote", Aliases: []string{"R"}, Usage: "Where should the daemon proxy traffic to?\n\tExample: mysql-service:3306"}, 49 | }, 50 | Action: client.Start, 51 | }, 52 | } 53 | 54 | err := app.Run(os.Args) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/gorilla/websocket" 7 | log "github.com/sirupsen/logrus" 8 | "github.com/xandout/soxy/wsconnadapter" 9 | ) 10 | 11 | func chanFromConn(conn net.Conn) chan []byte { 12 | c := make(chan []byte) 13 | 14 | go func() { 15 | b := make([]byte, 1024) 16 | 17 | for { 18 | n, err := conn.Read(b) 19 | if n > 0 { 20 | res := make([]byte, n) 21 | // Copy the buffer so it doesn't get changed while read by the recipient. 22 | copy(res, b[:n]) 23 | c <- res 24 | } 25 | if err != nil { 26 | c <- nil 27 | break 28 | } 29 | } 30 | }() 31 | 32 | return c 33 | } 34 | 35 | // Copy accepts a websocket connection and TCP connection and copies data between them 36 | func Copy(gwsConn *websocket.Conn, tcpConn net.Conn) { 37 | wsConn := wsconnadapter.New(gwsConn) 38 | wsChan := chanFromConn(wsConn) 39 | tcpChan := chanFromConn(tcpConn) 40 | 41 | defer wsConn.Close() 42 | defer tcpConn.Close() 43 | for { 44 | select { 45 | case wsData := <-wsChan: 46 | if wsData == nil { 47 | log.Infof("TCP connection closed: D: %v, S: %v", tcpConn.LocalAddr(), wsConn.RemoteAddr()) 48 | return 49 | } else { 50 | tcpConn.Write(wsData) 51 | } 52 | case tcpData := <-tcpChan: 53 | if tcpData == nil { 54 | log.Infof("TCP connection closed: D: %v, S: %v", tcpConn.LocalAddr(), wsConn.LocalAddr()) 55 | return 56 | } else { 57 | wsConn.Write(tcpData) 58 | } 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/http" 7 | 8 | "github.com/gorilla/websocket" 9 | log "github.com/sirupsen/logrus" 10 | "github.com/urfave/cli/v2" 11 | "github.com/xandout/soxy/proxy" 12 | ) 13 | 14 | // Start starts the http server 15 | func Start(c *cli.Context) error { 16 | port := c.String("port") 17 | http.HandleFunc("/", handler) 18 | err := http.ListenAndServe(port, nil) 19 | log.Errorf("HTTP SERVER: %v", err.Error()) 20 | return err 21 | 22 | } 23 | 24 | var upgrader = websocket.Upgrader{ 25 | ReadBufferSize: 1024, 26 | WriteBufferSize: 1024, 27 | } 28 | 29 | func handler(w http.ResponseWriter, r *http.Request) { 30 | q := r.URL.Query() 31 | var useTLS bool 32 | if q.Get("useTLS") != "" { 33 | useTLS = true 34 | } 35 | remote := q.Get("remote") 36 | if remote == "" { 37 | w.WriteHeader(http.StatusInternalServerError) 38 | w.Write([]byte("remote not set")) 39 | log.Errorf("HTTP SERVER: %v", "remote not set") 40 | return 41 | } 42 | wsConn, err := upgrader.Upgrade(w, r, nil) 43 | if err != nil { 44 | w.WriteHeader(http.StatusInternalServerError) 45 | w.Write([]byte(err.Error())) 46 | log.Errorf("HTTP SERVER, WS Connection Upgrade: %v", err.Error()) 47 | return 48 | } 49 | var remoteTCPConn net.Conn 50 | if useTLS { 51 | remoteTCPConn, err = tls.Dial("tcp", remote, &tls.Config{ 52 | InsecureSkipVerify: true, 53 | }) 54 | } else { 55 | remoteTCPConn, err = net.Dial("tcp", remote) 56 | } 57 | if err != nil { 58 | w.WriteHeader(http.StatusInternalServerError) 59 | w.Write([]byte(err.Error())) 60 | log.Errorf("HTTP SERVER, TCP Write: %v", err.Error()) 61 | return 62 | } 63 | log.Infof("Proxying traffic to %v on behalf of %v", remoteTCPConn.RemoteAddr(), wsConn.RemoteAddr()) 64 | go proxy.Copy(wsConn, remoteTCPConn) 65 | } 66 | -------------------------------------------------------------------------------- /wsconnadapter/wsconnadapter.go: -------------------------------------------------------------------------------- 1 | package wsconnadapter 2 | 3 | import ( 4 | "errors" 5 | "github.com/gorilla/websocket" 6 | "io" 7 | "net" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | // an adapter for representing WebSocket connection as a net.Conn 13 | // some caveats apply: https://github.com/gorilla/websocket/issues/441 14 | 15 | type Adapter struct { 16 | conn *websocket.Conn 17 | readMutex sync.Mutex 18 | writeMutex sync.Mutex 19 | reader io.Reader 20 | } 21 | 22 | func New(conn *websocket.Conn) *Adapter { 23 | return &Adapter{ 24 | conn: conn, 25 | } 26 | } 27 | 28 | func (a *Adapter) Read(b []byte) (int, error) { 29 | // Read() can be called concurrently, and we mutate some internal state here 30 | a.readMutex.Lock() 31 | defer a.readMutex.Unlock() 32 | 33 | if a.reader == nil { 34 | messageType, reader, err := a.conn.NextReader() 35 | if err != nil { 36 | return 0, err 37 | } 38 | 39 | if messageType != websocket.BinaryMessage { 40 | return 0, errors.New("unexpected websocket message type") 41 | } 42 | 43 | a.reader = reader 44 | } 45 | 46 | bytesRead, err := a.reader.Read(b) 47 | if err != nil { 48 | a.reader = nil 49 | 50 | // EOF for the current Websocket frame, more will probably come so.. 51 | if err == io.EOF { 52 | // .. we must hide this from the caller since our semantics are a 53 | // stream of bytes across many frames 54 | err = nil 55 | } 56 | } 57 | 58 | return bytesRead, err 59 | } 60 | 61 | func (a *Adapter) Write(b []byte) (int, error) { 62 | a.writeMutex.Lock() 63 | defer a.writeMutex.Unlock() 64 | 65 | nextWriter, err := a.conn.NextWriter(websocket.BinaryMessage) 66 | if err != nil { 67 | return 0, err 68 | } 69 | 70 | bytesWritten, err := nextWriter.Write(b) 71 | nextWriter.Close() 72 | 73 | return bytesWritten, err 74 | } 75 | 76 | func (a *Adapter) Close() error { 77 | return a.conn.Close() 78 | } 79 | 80 | func (a *Adapter) LocalAddr() net.Addr { 81 | return a.conn.LocalAddr() 82 | } 83 | 84 | func (a *Adapter) RemoteAddr() net.Addr { 85 | return a.conn.RemoteAddr() 86 | } 87 | 88 | func (a *Adapter) SetDeadline(t time.Time) error { 89 | if err := a.SetReadDeadline(t); err != nil { 90 | return err 91 | } 92 | 93 | return a.SetWriteDeadline(t) 94 | } 95 | 96 | func (a *Adapter) SetReadDeadline(t time.Time) error { 97 | return a.conn.SetReadDeadline(t) 98 | } 99 | 100 | func (a *Adapter) SetWriteDeadline(t time.Time) error { 101 | return a.conn.SetWriteDeadline(t) 102 | } 103 | --------------------------------------------------------------------------------