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  }