github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/modules/l4socks/socks5_handler_test.go (about) 1 package l4socks 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/binary" 7 "io" 8 "net" 9 "strconv" 10 "testing" 11 12 "github.com/caddyserver/caddy/v2" 13 "go.uber.org/zap" 14 15 "github.com/mholt/caddy-l4/layer4" 16 ) 17 18 func replay(t *testing.T, handler *Socks5Handler, expectedError string, messages [][]byte) { 19 t.Helper() 20 in, out := net.Pipe() 21 cx := layer4.WrapConnection(out, []byte{}, zap.NewNop()) 22 defer func() { 23 _ = in.Close() 24 _, _ = io.Copy(io.Discard, out) 25 _ = out.Close() 26 _ = cx.Close() 27 }() 28 29 go func() { 30 err := handler.Handle(cx, nil) 31 if expectedError != "" && err != nil && err.Error() != expectedError { 32 t.Errorf("Unexpected error: %s\n", err) 33 } else if expectedError != "" && err == nil { 34 t.Errorf("Missing error: %s\n", expectedError) 35 } 36 }() 37 38 for i := 0; i < len(messages); i += 2 { 39 _, err := in.Write(messages[i]) 40 assertNoError(t, err) 41 42 if i+1 < len(messages) { 43 buf := make([]byte, len(messages[i+1])) 44 _, err = io.ReadFull(in, buf) 45 assertNoError(t, err) 46 47 if bytes.Compare(messages[i+1], buf) != 0 { 48 t.Fatalf("Expected % x but received % x\n", messages[i+1], buf) 49 } 50 } 51 } 52 } 53 54 func TestSocks5Handler_Defaults(t *testing.T) { 55 ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) 56 defer cancel() 57 58 handler := &Socks5Handler{} // no config 59 60 err := handler.Provision(ctx) 61 assertNoError(t, err) 62 63 // target for the socks handler to connect to (using free random port) 64 listener, err := net.Listen("tcp", "127.0.0.1:0") 65 assertNoError(t, err) 66 defer func() { _ = listener.Close() }() 67 68 // transform random listening port into bytes 69 _, portStr, err := net.SplitHostPort(listener.Addr().String()) 70 assertNoError(t, err) 71 port, err := strconv.ParseUint(portStr, 10, 16) 72 assertNoError(t, err) 73 portBytes := make([]byte, 2) 74 binary.BigEndian.PutUint16(portBytes, uint16(port)) 75 76 replay(t, handler, "", [][]byte{ 77 {0x05, 0x01, 0x00}, // -> request no auth 78 {0x05, 0x00}, // <- accept no auth 79 {0x05, 0x01, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, portBytes[0], portBytes[1]}, // -> CONNECT 127.0.0.1 [random port] 80 {0x05, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01}, // <- success (ignoring last 2 bytes containing random port) 81 }) 82 83 replay(t, handler, "", [][]byte{ 84 {0x05, 0x01, 0x02}, // -> request auth with password 85 {0x05, 0xff}, // <- NO ACCEPTABLE METHODS 86 }) 87 88 replay(t, handler, "", [][]byte{ 89 {0x05, 0x01, 0x00}, // -> request no auth 90 {0x05, 0x00}, // <- accept no auth 91 {0x05, 0x02, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x50}, // -> BIND 127.0.0.1 80 92 {0x05, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // <- connection not allowed by ruleset 93 }) 94 } 95 96 func TestSocks5Handler_Commands(t *testing.T) { 97 ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) 98 defer cancel() 99 100 handler := &Socks5Handler{Commands: []string{"BIND"}} // only allow BIND 101 102 err := handler.Provision(ctx) 103 assertNoError(t, err) 104 105 replay(t, handler, "bind to 127.0.0.1:80 blocked by rules", [][]byte{ 106 {0x05, 0x01, 0x00}, // -> request no auth 107 {0x05, 0x00}, // <- accept no auth 108 {0x05, 0x01, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x50}, // -> CONNECT 127.0.0.1 80 109 {0x05, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // <- connection not allowed by ruleset 110 }) 111 112 replay(t, handler, "bind to 127.0.0.1:80 blocked by rules", [][]byte{ 113 {0x05, 0x01, 0x00}, // -> request no auth 114 {0x05, 0x00}, // <- accept no auth 115 {0x05, 0x03, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x50}, // -> UDP ASSOCIATE 127.0.0.1 80 116 {0x05, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // <- connection not allowed by ruleset 117 }) 118 119 replay(t, handler, "", [][]byte{ 120 {0x05, 0x01, 0x00}, // -> request no auth 121 {0x05, 0x00}, // <- accept no auth 122 {0x05, 0x02, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x50}, // -> BIND 127.0.0.1 80 123 {0x05, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // <- command not supported 124 }) 125 } 126 127 func TestSocks5Handler_Credentials(t *testing.T) { 128 ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) 129 defer cancel() 130 131 handler := &Socks5Handler{Credentials: map[string]string{"alice": "alice", "bob": "bob"}} 132 133 err := handler.Provision(ctx) 134 assertNoError(t, err) 135 136 replay(t, handler, "", [][]byte{ 137 {0x05, 0x01, 0x00}, // -> request no auth 138 {0x05, 0xff}, // <- NO ACCEPTABLE METHODS 139 }) 140 141 replay(t, handler, "", [][]byte{ 142 {0x05, 0x01, 0x02}, // -> request auth with password 143 {0x05, 0x02}, // <- select auth with password 144 {0x01, 0x05, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x05, 0x61, 0x6c, 0x69, 0x63, 0x65}, // -> send alice:alice 145 {0x01, 0x00}, // <- accept user 146 }) 147 148 replay(t, handler, "", [][]byte{ 149 {0x05, 0x02, 0x00, 0x02}, // -> request auth with password & no auth 150 {0x05, 0x02}, // <- select auth with password 151 {0x01, 0x03, 0x62, 0x6f, 0x62, 0x03, 0x62, 0x6f, 0x62}, // -> send bob:bob 152 {0x01, 0x00}, // <- accept user 153 }) 154 155 replay(t, handler, "", [][]byte{ 156 {0x05, 0x01, 0x02}, // -> request auth with password 157 {0x05, 0x02}, // <- select auth with password 158 {0x01, 0x05, 0x63, 0x61, 0x72, 0x6f, 0x6c, 0x05, 0x63, 0x61, 0x72, 0x6f, 0x6c}, // -> send carol:carol 159 {0x01, 0x01}, // <- reject user 160 }) 161 } 162 163 func TestSocks5Handler_InvalidCommand(t *testing.T) { 164 ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) 165 defer cancel() 166 167 handler := &Socks5Handler{Commands: []string{"Foo"}} 168 err := handler.Provision(ctx) 169 170 if err == nil || err.Error() != "unknown command \"Foo\" has to be one of [\"CONNECT\", \"ASSOCIATE\", \"BIND\"]" { 171 t.Fatalf("Wrong error: %v\n", err) 172 } 173 }