github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/modules/l4socks/socks4_matcher_test.go (about)

     1  package l4socks
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net"
     7  	"testing"
     8  
     9  	"github.com/caddyserver/caddy/v2"
    10  	"go.uber.org/zap"
    11  
    12  	"github.com/mholt/caddy-l4/layer4"
    13  )
    14  
    15  func assertNoError(t *testing.T, err error) {
    16  	t.Helper()
    17  	if err != nil {
    18  		t.Fatalf("Unexpected error: %s\n", err)
    19  	}
    20  }
    21  
    22  func TestSocks4Matcher_Match(t *testing.T) {
    23  	var curlSocks4Example1 = []byte{0x04, 0x01, 0x00, 0x50, 0x5d, 0xb8, 0xd8, 0x22, 0x00}
    24  	var curlSocks4Example2 = []byte{0x04, 0x01, 0x01, 0xbb, 0xa5, 0xe3, 0x14, 0xcf, 0x00}
    25  
    26  	var curlSocks4aExample1 = []byte{0x04, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00, 0x01, 0x00, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x00}
    27  	var curlSocks4aExample2 = []byte{0x04, 0x01, 0x01, 0xbb, 0x00, 0x00, 0x00, 0x01, 0x00, 0x63, 0x61, 0x64, 0x64, 0x79, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x00}
    28  
    29  	type test struct {
    30  		matcher     *Socks4Matcher
    31  		data        []byte
    32  		shouldMatch bool
    33  	}
    34  
    35  	tests := []test{
    36  		// match with defaults
    37  		{matcher: &Socks4Matcher{}, data: curlSocks4Example1, shouldMatch: true},
    38  		{matcher: &Socks4Matcher{}, data: curlSocks4aExample1, shouldMatch: true},
    39  		{matcher: &Socks4Matcher{}, data: curlSocks4Example2, shouldMatch: true},
    40  		{matcher: &Socks4Matcher{}, data: curlSocks4aExample2, shouldMatch: true},
    41  		{matcher: &Socks4Matcher{}, data: []byte("Hello World"), shouldMatch: false},
    42  
    43  		// match only BIND
    44  		{matcher: &Socks4Matcher{Commands: []string{"BIND"}}, data: curlSocks4Example1, shouldMatch: false},
    45  		{matcher: &Socks4Matcher{Commands: []string{"BIND"}}, data: curlSocks4aExample1, shouldMatch: false},
    46  
    47  		// match destination ip
    48  		{matcher: &Socks4Matcher{Networks: []string{"127.0.0.1"}}, data: curlSocks4Example1, shouldMatch: false},
    49  		{matcher: &Socks4Matcher{Networks: []string{"127.0.0.1"}}, data: curlSocks4Example2, shouldMatch: false},
    50  
    51  		{matcher: &Socks4Matcher{Networks: []string{"165.227.0.0/8"}}, data: curlSocks4Example1, shouldMatch: false},
    52  		{matcher: &Socks4Matcher{Networks: []string{"165.227.0.0/8"}}, data: curlSocks4Example2, shouldMatch: true},
    53  
    54  		{matcher: &Socks4Matcher{Networks: []string{"165.227.0.0/8", "::1"}}, data: curlSocks4Example1, shouldMatch: false},
    55  		{matcher: &Socks4Matcher{Networks: []string{"165.227.0.0/8", "::1"}}, data: curlSocks4Example2, shouldMatch: true},
    56  
    57  		{matcher: &Socks4Matcher{Networks: []string{"127.0.0.1"}}, data: curlSocks4aExample1, shouldMatch: false},
    58  		{matcher: &Socks4Matcher{Networks: []string{"0.0.0.0/0"}}, data: curlSocks4aExample2, shouldMatch: true},
    59  
    60  		// match destination port
    61  		{matcher: &Socks4Matcher{Ports: []uint16{80, 1234}}, data: curlSocks4Example1, shouldMatch: true},
    62  		{matcher: &Socks4Matcher{Ports: []uint16{80, 1234}}, data: curlSocks4Example2, shouldMatch: false},
    63  		{matcher: &Socks4Matcher{Ports: []uint16{80, 1234}}, data: curlSocks4aExample1, shouldMatch: true},
    64  		{matcher: &Socks4Matcher{Ports: []uint16{80, 1234}}, data: curlSocks4aExample2, shouldMatch: false},
    65  	}
    66  
    67  	ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
    68  	defer cancel()
    69  
    70  	for i, tc := range tests {
    71  		func() {
    72  			err := tc.matcher.Provision(ctx)
    73  			assertNoError(t, err)
    74  
    75  			in, out := net.Pipe()
    76  			defer func() {
    77  				_, _ = io.Copy(io.Discard, out)
    78  				_ = out.Close()
    79  			}()
    80  
    81  			cx := layer4.WrapConnection(out, []byte{}, zap.NewNop())
    82  			go func() {
    83  				_, err := in.Write(tc.data)
    84  				assertNoError(t, err)
    85  				_ = in.Close()
    86  			}()
    87  
    88  			matched, err := tc.matcher.Match(cx)
    89  			assertNoError(t, err)
    90  
    91  			if matched != tc.shouldMatch {
    92  				if tc.shouldMatch {
    93  					t.Fatalf("test %d: matcher did not match | %+v\n", i, tc.matcher)
    94  				} else {
    95  					t.Fatalf("test %d: matcher should not match | %+v\n", i, tc.matcher)
    96  				}
    97  			}
    98  		}()
    99  	}
   100  }
   101  
   102  func TestSocks4Matcher_InvalidCommand(t *testing.T) {
   103  	ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
   104  	defer cancel()
   105  
   106  	handler := &Socks4Matcher{Commands: []string{"Foo"}}
   107  	err := handler.Provision(ctx)
   108  
   109  	if err == nil || err.Error() != "unknown command \"Foo\" has to be one of [\"CONNECT\", \"BIND\"]" {
   110  		t.Fatalf("Wrong error: %v\n", err)
   111  	}
   112  }