diff options
author | Mike Crute <mike@crute.us> | 2019-12-07 11:21:07 -0800 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2019-12-07 11:44:27 -0800 |
commit | cbd6bf57c506da924d707657427a90123d9a9614 (patch) | |
tree | 487b8babb840d14ec4529e218ed3ff81f2f90900 | |
download | websocket_proxy-cbd6bf57c506da924d707657427a90123d9a9614.tar.bz2 websocket_proxy-cbd6bf57c506da924d707657427a90123d9a9614.tar.xz websocket_proxy-cbd6bf57c506da924d707657427a90123d9a9614.zip |
Initial import
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 7 | ||||
-rw-r--r-- | client.go | 45 | ||||
-rw-r--r-- | go.mod | 8 | ||||
-rw-r--r-- | go.sum | 35 | ||||
-rw-r--r-- | main.go | 74 | ||||
-rw-r--r-- | server.go | 46 | ||||
-rw-r--r-- | sockets.go | 66 |
8 files changed, 282 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b301d2 --- /dev/null +++ b/.gitignore | |||
@@ -0,0 +1 @@ | |||
/websocket-proxy | |||
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..17cd4ec --- /dev/null +++ b/Makefile | |||
@@ -0,0 +1,7 @@ | |||
1 | websocket-proxy: main.go sockets.go server.go client.go | ||
2 | go build -o $@ $^ | ||
3 | |||
4 | |||
5 | .PHONY: clean | ||
6 | clean: | ||
7 | rm -rf websocket-proxy | ||
diff --git a/client.go b/client.go new file mode 100644 index 0000000..00455ab --- /dev/null +++ b/client.go | |||
@@ -0,0 +1,45 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "log" | ||
5 | "net" | ||
6 | |||
7 | "github.com/gorilla/websocket" | ||
8 | ) | ||
9 | |||
10 | type ClientHandler struct { | ||
11 | SocketListenOn string | ||
12 | WebsocketServer string | ||
13 | } | ||
14 | |||
15 | func (h *ClientHandler) ServiceConnection(proxyconn net.Conn) { | ||
16 | defer proxyconn.Close() | ||
17 | |||
18 | wsconn, _, err := websocket.DefaultDialer.Dial(h.WebsocketServer, nil) | ||
19 | if err != nil { | ||
20 | log.Println(err) | ||
21 | return | ||
22 | } | ||
23 | defer wsconn.Close() | ||
24 | |||
25 | log.Println("Connected to server") | ||
26 | |||
27 | serviceBoth(wsconn, proxyconn) | ||
28 | } | ||
29 | |||
30 | func (h *ClientHandler) Run() { | ||
31 | listener, err := net.Listen("tcp", h.SocketListenOn) | ||
32 | if err != nil { | ||
33 | log.Printf("error: Run: %s", err) | ||
34 | return | ||
35 | } | ||
36 | |||
37 | for { | ||
38 | conn, err := listener.Accept() | ||
39 | if err != nil { | ||
40 | log.Printf("error: Run: %s", err) | ||
41 | continue | ||
42 | } | ||
43 | go h.ServiceConnection(conn) | ||
44 | } | ||
45 | } | ||
@@ -0,0 +1,8 @@ | |||
1 | module code.crute.us/mcrute/websocket_proxy | ||
2 | |||
3 | go 1.13 | ||
4 | |||
5 | require ( | ||
6 | github.com/gorilla/websocket v1.4.1 | ||
7 | github.com/spf13/cobra v0.0.5 | ||
8 | ) | ||
@@ -0,0 +1,35 @@ | |||
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||
2 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= | ||
3 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= | ||
4 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= | ||
5 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= | ||
6 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= | ||
7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
8 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||
9 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= | ||
10 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||
11 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | ||
12 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= | ||
13 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= | ||
14 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||
15 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||
16 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | ||
17 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= | ||
18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
19 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= | ||
20 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= | ||
21 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | ||
22 | github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= | ||
23 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= | ||
24 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= | ||
25 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= | ||
26 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||
27 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= | ||
28 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||
29 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= | ||
30 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= | ||
31 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||
32 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
33 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
34 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
35 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
@@ -0,0 +1,74 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "errors" | ||
5 | "fmt" | ||
6 | "log" | ||
7 | "net/http" | ||
8 | "os" | ||
9 | "strings" | ||
10 | |||
11 | "github.com/spf13/cobra" | ||
12 | ) | ||
13 | |||
14 | var rootCmd = &cobra.Command{ | ||
15 | Use: "websocket-proxy", | ||
16 | Version: "0.1.0", | ||
17 | Short: "Proxy TCP connections over a websocket", | ||
18 | } | ||
19 | |||
20 | var clientCmd = &cobra.Command{ | ||
21 | Use: "client [server host]", | ||
22 | Short: "Act as a client for a websocket-proxy server", | ||
23 | Args: func(cmd *cobra.Command, args []string) error { | ||
24 | if len(args) != 1 || args[0] == "" { | ||
25 | return errors.New("Server host is a required argument") | ||
26 | } | ||
27 | if !strings.HasPrefix(args[0], "ws://") && !strings.HasPrefix(args[0], "wss://") { | ||
28 | return errors.New("Server host format is ws[s]://host[:port]/[path]") | ||
29 | } | ||
30 | return nil | ||
31 | }, | ||
32 | Run: func(cmd *cobra.Command, args []string) { | ||
33 | listenOn := cmd.Flag("listen").Value.String() | ||
34 | |||
35 | h := &ClientHandler{ | ||
36 | SocketListenOn: listenOn, | ||
37 | WebsocketServer: args[0], | ||
38 | } | ||
39 | |||
40 | log.Printf("Serving on %s", listenOn) | ||
41 | h.Run() | ||
42 | }, | ||
43 | } | ||
44 | |||
45 | var serverCmd = &cobra.Command{ | ||
46 | Use: "server [next-hop host]", | ||
47 | Short: "Serve websocket proxy client", | ||
48 | Args: func(cmd *cobra.Command, args []string) error { | ||
49 | if len(args) != 1 || args[0] == "" { | ||
50 | return errors.New("Next-hop host is a required argument") | ||
51 | } | ||
52 | return nil | ||
53 | }, | ||
54 | Run: func(cmd *cobra.Command, args []string) { | ||
55 | listenOn := cmd.Flag("listen").Value.String() | ||
56 | log.Printf("Serving on %s", listenOn) | ||
57 | |||
58 | http.Handle("/", NewServerHandler(args[0])) | ||
59 | log.Fatal(http.ListenAndServe(listenOn, nil)) | ||
60 | }, | ||
61 | } | ||
62 | |||
63 | func main() { | ||
64 | rootCmd.AddCommand(clientCmd) | ||
65 | rootCmd.AddCommand(serverCmd) | ||
66 | |||
67 | clientCmd.Flags().StringP("listen", "l", ":9013", "[address]:port to bind for serving clients") | ||
68 | serverCmd.Flags().StringP("listen", "l", ":9012", "[address]:port to bind for serving clients") | ||
69 | |||
70 | if err := rootCmd.Execute(); err != nil { | ||
71 | fmt.Println(err) | ||
72 | os.Exit(1) | ||
73 | } | ||
74 | } | ||
diff --git a/server.go b/server.go new file mode 100644 index 0000000..47eb3e5 --- /dev/null +++ b/server.go | |||
@@ -0,0 +1,46 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "log" | ||
5 | "net" | ||
6 | "net/http" | ||
7 | |||
8 | "github.com/gorilla/websocket" | ||
9 | ) | ||
10 | |||
11 | type ServerHandler struct { | ||
12 | ProxyToHost string | ||
13 | upgrader websocket.Upgrader | ||
14 | } | ||
15 | |||
16 | func NewServerHandler(proxyToHost string) *ServerHandler { | ||
17 | return &ServerHandler{ | ||
18 | ProxyToHost: proxyToHost, | ||
19 | upgrader: websocket.Upgrader{ | ||
20 | ReadBufferSize: 1024, | ||
21 | WriteBufferSize: 1024, | ||
22 | }, | ||
23 | } | ||
24 | } | ||
25 | |||
26 | func (h *ServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
27 | log.Println("Got new HTTP connection") | ||
28 | |||
29 | wsconn, err := h.upgrader.Upgrade(w, r, nil) | ||
30 | if err != nil { | ||
31 | log.Printf("error: ServeHTTP: %s", err) | ||
32 | return | ||
33 | } | ||
34 | defer wsconn.Close() | ||
35 | |||
36 | proxyconn, err := net.Dial("tcp", h.ProxyToHost) | ||
37 | if err != nil { | ||
38 | log.Printf("error: ServeHTTP: %s", err) | ||
39 | return | ||
40 | } | ||
41 | defer proxyconn.Close() | ||
42 | |||
43 | log.Println("Connected to SSH server") | ||
44 | |||
45 | serviceBoth(wsconn, proxyconn) | ||
46 | } | ||
diff --git a/sockets.go b/sockets.go new file mode 100644 index 0000000..0ebbe43 --- /dev/null +++ b/sockets.go | |||
@@ -0,0 +1,66 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "io" | ||
5 | "log" | ||
6 | "net" | ||
7 | |||
8 | "github.com/gorilla/websocket" | ||
9 | ) | ||
10 | |||
11 | func wsReader(wsconn *websocket.Conn, out chan []byte) { | ||
12 | for { | ||
13 | messageType, p, err := wsconn.ReadMessage() | ||
14 | if err != nil { | ||
15 | log.Printf("error: wsReader: %s", err) | ||
16 | return | ||
17 | } | ||
18 | if messageType != websocket.BinaryMessage { | ||
19 | log.Println("error: wsReader: only binary messages are supported") | ||
20 | continue | ||
21 | } | ||
22 | out <- p | ||
23 | } | ||
24 | } | ||
25 | |||
26 | func socketReader(proxyconn net.Conn, out chan []byte) { | ||
27 | for { | ||
28 | readBuffer := make([]byte, 2048) | ||
29 | |||
30 | i, err := proxyconn.Read(readBuffer) | ||
31 | if err != nil { | ||
32 | if err == io.EOF { | ||
33 | log.Println("info: socketReader: Disconnected") | ||
34 | } else { | ||
35 | log.Printf("error: socketReader: %s", err) | ||
36 | } | ||
37 | return | ||
38 | } | ||
39 | |||
40 | out <- readBuffer[:i] | ||
41 | } | ||
42 | } | ||
43 | |||
44 | func serviceBoth(wsconn *websocket.Conn, proxyconn net.Conn) { | ||
45 | sc := make(chan []byte) | ||
46 | wsc := make(chan []byte) | ||
47 | |||
48 | go socketReader(proxyconn, sc) | ||
49 | go wsReader(wsconn, wsc) | ||
50 | |||
51 | for { | ||
52 | select { | ||
53 | case sd := <-sc: | ||
54 | if err := wsconn.WriteMessage(websocket.BinaryMessage, sd); err != nil { | ||
55 | log.Printf("error: serviceBoth: %s", err) | ||
56 | return | ||
57 | } | ||
58 | |||
59 | case wsd := <-wsc: | ||
60 | if _, err := proxyconn.Write(wsd); err != nil { | ||
61 | log.Printf("error: serviceBoth: %s", err) | ||
62 | return | ||
63 | } | ||
64 | } | ||
65 | } | ||
66 | } | ||