summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2023-07-15 16:58:36 -0700
committerMike Crute <mike@crute.us>2023-07-15 16:58:36 -0700
commitfea07831eadd35532055ec16fc43b0cde56a54b1 (patch)
tree35c304a16e5b6f9acadb9c66f4c48215ad9abd2b
parent51949f8dc563c7c1ce03d8862abbee4cc1e20943 (diff)
downloadwebsocket_proxy-fea07831eadd35532055ec16fc43b0cde56a54b1.tar.bz2
websocket_proxy-fea07831eadd35532055ec16fc43b0cde56a54b1.tar.xz
websocket_proxy-fea07831eadd35532055ec16fc43b0cde56a54b1.zip
Cleanup, add local client
-rw-r--r--Makefile2
-rw-r--r--client.go9
-rw-r--r--go.mod7
-rw-r--r--localclient.go31
-rw-r--r--main.go29
-rw-r--r--server.go9
-rw-r--r--sockets.go90
7 files changed, 109 insertions, 68 deletions
diff --git a/Makefile b/Makefile
index 7a77930..3e599d0 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
1IMAGE="docker.crute.me/websocket-proxy" 1IMAGE="docker.crute.me/websocket-proxy"
2VERSION="0.1.0" 2VERSION="0.1.0"
3 3
4websocket-proxy: main.go sockets.go server.go client.go 4websocket-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
diff --git a/client.go b/client.go
index d0716b1..d9a0f25 100644
--- a/client.go
+++ b/client.go
@@ -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
32func (h *ClientHandler) Run() { 39func (h *ClientHandler) Run() {
diff --git a/go.mod b/go.mod
index 423ea5c..3f372f6 100644
--- a/go.mod
+++ b/go.mod
@@ -1,8 +1,13 @@
1module code.crute.us/mcrute/websocket_proxy 1module code.crute.us/mcrute/websocket_proxy
2 2
3go 1.13 3go 1.20
4 4
5require ( 5require (
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
10require (
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 @@
1package main
2
3import (
4 "context"
5 "log"
6 "os"
7
8 "github.com/gorilla/websocket"
9)
10
11type LocalClientHandler struct {
12 WebsocketServer string
13 Context context.Context
14}
15
16func (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}
diff --git a/main.go b/main.go
index 00fe063..1ac3351 100644
--- a/main.go
+++ b/main.go
@@ -50,6 +50,32 @@ var clientCmd = &cobra.Command{
50 }, 50 },
51} 51}
52 52
53var 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
53var serverCmd = &cobra.Command{ 79var 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
71func main() { 97func 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")
diff --git a/server.go b/server.go
index 1faf33d..9a9d897 100644
--- a/server.go
+++ b/server.go
@@ -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}
diff --git a/sockets.go b/sockets.go
index 2ded258..319b548 100644
--- a/sockets.go
+++ b/sockets.go
@@ -1,79 +1,41 @@
1package main 1package main
2 2
3import ( 3import (
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
12func wsReader(wsconn *websocket.Conn, out chan []byte, ctx context.Context) { 10type 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
32func socketReader(proxyconn net.Conn, out chan []byte, ctx context.Context) { 14func (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
55func serviceBoth(wsconn *websocket.Conn, proxyconn net.Conn, ctx context.Context) { 31func (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
38func serviceBoth(dst io.Writer, src io.Reader, errc chan<- error) {
39 _, err := io.Copy(dst, src)
40 errc <- err
79} 41}