diff options
author | Mike Crute <mike@crute.us> | 2023-07-15 16:58:36 -0700 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2023-07-15 16:58:36 -0700 |
commit | fea07831eadd35532055ec16fc43b0cde56a54b1 (patch) | |
tree | 35c304a16e5b6f9acadb9c66f4c48215ad9abd2b | |
parent | 51949f8dc563c7c1ce03d8862abbee4cc1e20943 (diff) | |
download | websocket_proxy-fea07831eadd35532055ec16fc43b0cde56a54b1.tar.bz2 websocket_proxy-fea07831eadd35532055ec16fc43b0cde56a54b1.tar.xz websocket_proxy-fea07831eadd35532055ec16fc43b0cde56a54b1.zip |
Cleanup, add local client
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | client.go | 9 | ||||
-rw-r--r-- | go.mod | 7 | ||||
-rw-r--r-- | localclient.go | 31 | ||||
-rw-r--r-- | main.go | 29 | ||||
-rw-r--r-- | server.go | 9 | ||||
-rw-r--r-- | sockets.go | 90 |
7 files changed, 109 insertions, 68 deletions
@@ -1,7 +1,7 @@ | |||
1 | IMAGE="docker.crute.me/websocket-proxy" | 1 | IMAGE="docker.crute.me/websocket-proxy" |
2 | VERSION="0.1.0" | 2 | VERSION="0.1.0" |
3 | 3 | ||
4 | websocket-proxy: main.go sockets.go server.go client.go | 4 | websocket-proxy: main.go sockets.go server.go client.go localclient.go |
5 | CGO_ENABLED=0 go build -ldflags "-X main.version=$(VERSION)" -o $@ $^ | 5 | CGO_ENABLED=0 go build -ldflags "-X main.version=$(VERSION)" -o $@ $^ |
6 | 6 | ||
7 | .PHONY: docker | 7 | .PHONY: docker |
@@ -26,7 +26,14 @@ func (h *ClientHandler) ServiceConnection(proxyconn net.Conn) { | |||
26 | 26 | ||
27 | log.Println("Connected to server") | 27 | log.Println("Connected to server") |
28 | 28 | ||
29 | serviceBoth(wsconn, proxyconn, h.Context) | 29 | errc := make(chan error) |
30 | ws := &WebsocketReadWriter{wsconn} | ||
31 | |||
32 | go serviceBoth(proxyconn, ws, errc) | ||
33 | go serviceBoth(ws, proxyconn, errc) | ||
34 | |||
35 | <-errc | ||
36 | log.Println("Closing client connection") | ||
30 | } | 37 | } |
31 | 38 | ||
32 | func (h *ClientHandler) Run() { | 39 | func (h *ClientHandler) Run() { |
@@ -1,8 +1,13 @@ | |||
1 | module code.crute.us/mcrute/websocket_proxy | 1 | module code.crute.us/mcrute/websocket_proxy |
2 | 2 | ||
3 | go 1.13 | 3 | go 1.20 |
4 | 4 | ||
5 | require ( | 5 | require ( |
6 | github.com/gorilla/websocket v1.4.1 | 6 | github.com/gorilla/websocket v1.4.1 |
7 | github.com/spf13/cobra v0.0.5 | 7 | github.com/spf13/cobra v0.0.5 |
8 | ) | 8 | ) |
9 | |||
10 | require ( | ||
11 | github.com/inconshreveable/mousetrap v1.0.0 // indirect | ||
12 | github.com/spf13/pflag v1.0.3 // indirect | ||
13 | ) | ||
diff --git a/localclient.go b/localclient.go new file mode 100644 index 0000000..c1f5c38 --- /dev/null +++ b/localclient.go | |||
@@ -0,0 +1,31 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "log" | ||
6 | "os" | ||
7 | |||
8 | "github.com/gorilla/websocket" | ||
9 | ) | ||
10 | |||
11 | type LocalClientHandler struct { | ||
12 | WebsocketServer string | ||
13 | Context context.Context | ||
14 | } | ||
15 | |||
16 | func (h *LocalClientHandler) Run() { | ||
17 | wsconn, _, err := websocket.DefaultDialer.Dial(h.WebsocketServer, nil) | ||
18 | if err != nil { | ||
19 | log.Println(err) | ||
20 | return | ||
21 | } | ||
22 | defer wsconn.Close() | ||
23 | |||
24 | errc := make(chan error) | ||
25 | ws := &WebsocketReadWriter{wsconn} | ||
26 | |||
27 | go serviceBoth(os.Stdout, ws, errc) | ||
28 | go serviceBoth(ws, os.Stdin, errc) | ||
29 | |||
30 | log.Printf("Closing client connection %s", <-errc) | ||
31 | } | ||
@@ -50,6 +50,32 @@ var clientCmd = &cobra.Command{ | |||
50 | }, | 50 | }, |
51 | } | 51 | } |
52 | 52 | ||
53 | var localClientCmd = &cobra.Command{ | ||
54 | Use: "localclient [server host]", | ||
55 | Short: "Act as a client for a websocket-proxy server", | ||
56 | Args: func(cmd *cobra.Command, args []string) error { | ||
57 | if len(args) != 1 || args[0] == "" { | ||
58 | return errors.New("Server host is a required argument") | ||
59 | } | ||
60 | if !strings.HasPrefix(args[0], "ws://") && !strings.HasPrefix(args[0], "wss://") { | ||
61 | return errors.New("Server host format is ws[s]://host[:port]/[path]") | ||
62 | } | ||
63 | return nil | ||
64 | }, | ||
65 | Run: func(cmd *cobra.Command, args []string) { | ||
66 | // TODO: Handle signals | ||
67 | ctx, cancel := context.WithCancel(context.Background()) | ||
68 | defer cancel() | ||
69 | |||
70 | h := &LocalClientHandler{ | ||
71 | WebsocketServer: args[0], | ||
72 | Context: ctx, | ||
73 | } | ||
74 | |||
75 | h.Run() | ||
76 | }, | ||
77 | } | ||
78 | |||
53 | var serverCmd = &cobra.Command{ | 79 | var serverCmd = &cobra.Command{ |
54 | Use: "server [next-hop host]", | 80 | Use: "server [next-hop host]", |
55 | Short: "Serve websocket proxy client", | 81 | Short: "Serve websocket proxy client", |
@@ -69,7 +95,10 @@ var serverCmd = &cobra.Command{ | |||
69 | } | 95 | } |
70 | 96 | ||
71 | func main() { | 97 | func main() { |
98 | log.SetOutput(os.Stderr) | ||
99 | |||
72 | rootCmd.AddCommand(clientCmd) | 100 | rootCmd.AddCommand(clientCmd) |
101 | rootCmd.AddCommand(localClientCmd) | ||
73 | rootCmd.AddCommand(serverCmd) | 102 | rootCmd.AddCommand(serverCmd) |
74 | 103 | ||
75 | clientCmd.Flags().StringP("listen", "l", ":9013", "[address]:port to bind for serving clients") | 104 | clientCmd.Flags().StringP("listen", "l", ":9013", "[address]:port to bind for serving clients") |
@@ -42,5 +42,12 @@ func (h *ServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
42 | 42 | ||
43 | log.Println("Connected to SSH server") | 43 | log.Println("Connected to SSH server") |
44 | 44 | ||
45 | serviceBoth(wsconn, proxyconn, r.Context()) | 45 | errc := make(chan error) |
46 | ws := &WebsocketReadWriter{wsconn} | ||
47 | |||
48 | go serviceBoth(proxyconn, ws, errc) | ||
49 | go serviceBoth(ws, proxyconn, errc) | ||
50 | |||
51 | <-errc | ||
52 | log.Println("Closing client server") | ||
46 | } | 53 | } |
@@ -1,79 +1,41 @@ | |||
1 | package main | 1 | package main |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "context" | 4 | "fmt" |
5 | "io" | 5 | "io" |
6 | "log" | ||
7 | "net" | ||
8 | 6 | ||
9 | "github.com/gorilla/websocket" | 7 | "github.com/gorilla/websocket" |
10 | ) | 8 | ) |
11 | 9 | ||
12 | func wsReader(wsconn *websocket.Conn, out chan []byte, ctx context.Context) { | 10 | type WebsocketReadWriter struct { |
13 | for { | 11 | W *websocket.Conn |
14 | messageType, p, err := wsconn.ReadMessage() | ||
15 | if err != nil { | ||
16 | log.Printf("error: wsReader: %s", err) | ||
17 | return | ||
18 | } | ||
19 | if messageType != websocket.BinaryMessage { | ||
20 | log.Println("error: wsReader: only binary messages are supported") | ||
21 | continue | ||
22 | } | ||
23 | select { | ||
24 | case out <- p: | ||
25 | continue | ||
26 | case <-ctx.Done(): | ||
27 | return | ||
28 | } | ||
29 | } | ||
30 | } | 12 | } |
31 | 13 | ||
32 | func socketReader(proxyconn net.Conn, out chan []byte, ctx context.Context) { | 14 | func (w *WebsocketReadWriter) Read(p []byte) (int, error) { |
33 | for { | 15 | t, m, err := w.W.ReadMessage() |
34 | readBuffer := make([]byte, 2048) | 16 | if err != nil { |
35 | 17 | return 0, fmt.Errorf("error reading websocket: %w", err) | |
36 | i, err := proxyconn.Read(readBuffer) | 18 | } |
37 | if err != nil { | 19 | if t != websocket.BinaryMessage { |
38 | if err == io.EOF { | 20 | return 0, fmt.Errorf("error reading websocket: only binary messages are supported") |
39 | log.Println("info: socketReader: Disconnected") | 21 | } |
40 | } else { | 22 | if cap(p) < len(m) { |
41 | log.Printf("error: socketReader: %s", err) | 23 | return 0, fmt.Errorf("error reading websocket: outputbuffer too short for input") |
42 | } | 24 | } |
43 | return | 25 | if i := copy(p, m); i != len(m) { |
44 | } | 26 | return 0, fmt.Errorf("error reading websocket: copy failed %d of %d", i, len(m)) |
45 | |||
46 | select { | ||
47 | case out <- readBuffer[:i]: | ||
48 | continue | ||
49 | case <-ctx.Done(): | ||
50 | return | ||
51 | } | ||
52 | } | 27 | } |
28 | return len(m), nil | ||
53 | } | 29 | } |
54 | 30 | ||
55 | func serviceBoth(wsconn *websocket.Conn, proxyconn net.Conn, ctx context.Context) { | 31 | func (w *WebsocketReadWriter) Write(p []byte) (int, error) { |
56 | sc := make(chan []byte) | 32 | if err := w.W.WriteMessage(websocket.BinaryMessage, p); err != nil { |
57 | wsc := make(chan []byte) | 33 | return 0, fmt.Errorf("error writing websocket: %w", err) |
58 | |||
59 | go socketReader(proxyconn, sc, ctx) | ||
60 | go wsReader(wsconn, wsc, ctx) | ||
61 | |||
62 | for { | ||
63 | select { | ||
64 | case sd := <-sc: | ||
65 | if err := wsconn.WriteMessage(websocket.BinaryMessage, sd); err != nil { | ||
66 | log.Printf("error: serviceBoth: %s", err) | ||
67 | return | ||
68 | } | ||
69 | |||
70 | case wsd := <-wsc: | ||
71 | if _, err := proxyconn.Write(wsd); err != nil { | ||
72 | log.Printf("error: serviceBoth: %s", err) | ||
73 | return | ||
74 | } | ||
75 | case <-ctx.Done(): | ||
76 | return | ||
77 | } | ||
78 | } | 34 | } |
35 | return len(p), nil | ||
36 | } | ||
37 | |||
38 | func serviceBoth(dst io.Writer, src io.Reader, errc chan<- error) { | ||
39 | _, err := io.Copy(dst, src) | ||
40 | errc <- err | ||
79 | } | 41 | } |