github.com/chwjbn/xclash@v0.2.0/adapter/outbound/snell.go (about)

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"strconv"
     8  
     9  	"github.com/chwjbn/xclash/common/structure"
    10  	"github.com/chwjbn/xclash/component/dialer"
    11  	C "github.com/chwjbn/xclash/constant"
    12  	obfs "github.com/chwjbn/xclash/transport/simple-obfs"
    13  	"github.com/chwjbn/xclash/transport/snell"
    14  )
    15  
    16  type Snell struct {
    17  	*Base
    18  	psk        []byte
    19  	pool       *snell.Pool
    20  	obfsOption *simpleObfsOption
    21  	version    int
    22  }
    23  
    24  type SnellOption struct {
    25  	BasicOption
    26  	Name     string                 `proxy:"name"`
    27  	Server   string                 `proxy:"server"`
    28  	Port     int                    `proxy:"port"`
    29  	Psk      string                 `proxy:"psk"`
    30  	Version  int                    `proxy:"version,omitempty"`
    31  	ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
    32  }
    33  
    34  type streamOption struct {
    35  	psk        []byte
    36  	version    int
    37  	addr       string
    38  	obfsOption *simpleObfsOption
    39  }
    40  
    41  func streamConn(c net.Conn, option streamOption) *snell.Snell {
    42  	switch option.obfsOption.Mode {
    43  	case "tls":
    44  		c = obfs.NewTLSObfs(c, option.obfsOption.Host)
    45  	case "http":
    46  		_, port, _ := net.SplitHostPort(option.addr)
    47  		c = obfs.NewHTTPObfs(c, option.obfsOption.Host, port)
    48  	}
    49  	return snell.StreamConn(c, option.psk, option.version)
    50  }
    51  
    52  // StreamConn implements C.ProxyAdapter
    53  func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
    54  	c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
    55  	port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
    56  	err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
    57  	return c, err
    58  }
    59  
    60  // DialContext implements C.ProxyAdapter
    61  func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
    62  	if s.version == snell.Version2 && len(opts) == 0 {
    63  		c, err := s.pool.Get()
    64  		if err != nil {
    65  			return nil, err
    66  		}
    67  
    68  		port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
    69  		if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
    70  			c.Close()
    71  			return nil, err
    72  		}
    73  		return NewConn(c, s), err
    74  	}
    75  
    76  	c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
    77  	if err != nil {
    78  		return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
    79  	}
    80  	tcpKeepAlive(c)
    81  
    82  	defer safeConnClose(c, err)
    83  
    84  	c, err = s.StreamConn(c, metadata)
    85  	return NewConn(c, s), err
    86  }
    87  
    88  func NewSnell(option SnellOption) (*Snell, error) {
    89  	addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
    90  	psk := []byte(option.Psk)
    91  
    92  	decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
    93  	obfsOption := &simpleObfsOption{Host: "bing.com"}
    94  	if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
    95  		return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
    96  	}
    97  
    98  	switch obfsOption.Mode {
    99  	case "tls", "http", "":
   100  		break
   101  	default:
   102  		return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
   103  	}
   104  
   105  	// backward compatible
   106  	if option.Version == 0 {
   107  		option.Version = snell.DefaultSnellVersion
   108  	}
   109  	if option.Version != snell.Version1 && option.Version != snell.Version2 {
   110  		return nil, fmt.Errorf("snell version error: %d", option.Version)
   111  	}
   112  
   113  	s := &Snell{
   114  		Base: &Base{
   115  			name:  option.Name,
   116  			addr:  addr,
   117  			tp:    C.Snell,
   118  			iface: option.Interface,
   119  		},
   120  		psk:        psk,
   121  		obfsOption: obfsOption,
   122  		version:    option.Version,
   123  	}
   124  
   125  	if option.Version == snell.Version2 {
   126  		s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
   127  			c, err := dialer.DialContext(ctx, "tcp", addr, s.Base.DialOptions()...)
   128  			if err != nil {
   129  				return nil, err
   130  			}
   131  
   132  			tcpKeepAlive(c)
   133  			return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
   134  		})
   135  	}
   136  	return s, nil
   137  }