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

     1  package l4socks
     2  
     3  import (
     4  	"io"
     5  	"strconv"
     6  
     7  	"github.com/caddyserver/caddy/v2"
     8  	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
     9  
    10  	"github.com/mholt/caddy-l4/layer4"
    11  )
    12  
    13  func init() {
    14  	caddy.RegisterModule(&Socks5Matcher{})
    15  }
    16  
    17  // Socks5Matcher matches SOCKSv5 connections according to RFC 1928 (https://www.rfc-editor.org/rfc/rfc1928.html).
    18  // Since the SOCKSv5 header is very short it could produce a lot of false positives,
    19  // use AuthMethods to exactly specify which METHODS you expect your clients to send.
    20  // By default, only the most common methods are matched NO AUTH, GSSAPI & USERNAME/PASSWORD.
    21  type Socks5Matcher struct {
    22  	AuthMethods []uint16 `json:"auth_methods,omitempty"`
    23  }
    24  
    25  func (*Socks5Matcher) CaddyModule() caddy.ModuleInfo {
    26  	return caddy.ModuleInfo{
    27  		ID:  "layer4.matchers.socks5",
    28  		New: func() caddy.Module { return new(Socks5Matcher) },
    29  	}
    30  }
    31  
    32  func (m *Socks5Matcher) Provision(_ caddy.Context) error {
    33  	if len(m.AuthMethods) == 0 {
    34  		m.AuthMethods = []uint16{0, 1, 2} // NO AUTH, GSSAPI, USERNAME/PASSWORD
    35  	}
    36  	return nil
    37  }
    38  
    39  // Match returns true if the connection looks like it is using the SOCKSv5 protocol.
    40  func (m *Socks5Matcher) Match(cx *layer4.Connection) (bool, error) {
    41  	// read the version byte
    42  	buf := []byte{0}
    43  	if _, err := io.ReadFull(cx, buf); err != nil {
    44  		return false, err
    45  	}
    46  	if buf[0] != 5 {
    47  		return false, nil
    48  	}
    49  
    50  	// read number of auth methods
    51  	if _, err := io.ReadFull(cx, buf); err != nil {
    52  		return false, err
    53  	}
    54  
    55  	// read auth methods
    56  	methods := make([]byte, buf[0])
    57  	_, err := io.ReadFull(cx, methods)
    58  	if err != nil {
    59  		return false, err
    60  	}
    61  
    62  	// match auth methods
    63  	for _, requestedMethod := range methods {
    64  		if !contains(m.AuthMethods, uint16(requestedMethod)) {
    65  			return false, nil
    66  		}
    67  	}
    68  
    69  	return true, nil
    70  }
    71  
    72  // UnmarshalCaddyfile sets up the Socks5Matcher from Caddyfile tokens. Syntax:
    73  //
    74  //	socks5 {
    75  //		auth_methods <auth_methods...>
    76  //	}
    77  //
    78  // socks5
    79  func (m *Socks5Matcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
    80  	_, wrapper := d.Next(), d.Val() // consume wrapper name
    81  
    82  	// No same-line options are supported
    83  	if d.CountRemainingArgs() > 0 {
    84  		return d.ArgErr()
    85  	}
    86  
    87  	for nesting := d.Nesting(); d.NextBlock(nesting); {
    88  		optionName := d.Val()
    89  		switch optionName {
    90  		case "auth_methods":
    91  			if d.CountRemainingArgs() == 0 {
    92  				return d.ArgErr()
    93  			}
    94  			for d.NextArg() {
    95  				authMethod, err := strconv.ParseUint(d.Val(), 10, 8)
    96  				if err != nil {
    97  					return d.WrapErr(err)
    98  				}
    99  				m.AuthMethods = append(m.AuthMethods, uint16(authMethod))
   100  			}
   101  		default:
   102  			return d.ArgErr()
   103  		}
   104  
   105  		// No nested blocks are supported
   106  		if d.NextBlock(nesting + 1) {
   107  			return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
   108  		}
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  var (
   115  	_ layer4.ConnMatcher    = (*Socks5Matcher)(nil)
   116  	_ caddy.Provisioner     = (*Socks5Matcher)(nil)
   117  	_ caddyfile.Unmarshaler = (*Socks5Matcher)(nil)
   118  )
   119  
   120  func contains(values []uint16, search uint16) bool {
   121  	for _, value := range values {
   122  		if value == search {
   123  			return true
   124  		}
   125  	}
   126  	return false
   127  }