github.com/yaling888/clash@v1.53.0/adapter/outbound/snell.go (about)

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"strconv"
     8  
     9  	"github.com/yaling888/clash/common/structure"
    10  	"github.com/yaling888/clash/component/dialer"
    11  	C "github.com/yaling888/clash/constant"
    12  	obfs "github.com/yaling888/clash/transport/simple-obfs"
    13  	"github.com/yaling888/clash/transport/snell"
    14  )
    15  
    16  var _ C.ProxyAdapter = (*Snell)(nil)
    17  
    18  type Snell struct {
    19  	*Base
    20  	psk        []byte
    21  	pool       *snell.Pool
    22  	obfsOption *simpleObfsOption
    23  	version    int
    24  }
    25  
    26  type SnellOption struct {
    27  	BasicOption
    28  	Name             string         `proxy:"name"`
    29  	Server           string         `proxy:"server"`
    30  	Port             int            `proxy:"port"`
    31  	Psk              string         `proxy:"psk"`
    32  	UDP              bool           `proxy:"udp,omitempty"`
    33  	Version          int            `proxy:"version,omitempty"`
    34  	ObfsOpts         map[string]any `proxy:"obfs-opts,omitempty"`
    35  	RandomHost       bool           `proxy:"rand-host,omitempty"`
    36  	RemoteDnsResolve bool           `proxy:"remote-dns-resolve,omitempty"`
    37  }
    38  
    39  type streamOption struct {
    40  	psk        []byte
    41  	version    int
    42  	addr       string
    43  	obfsOption *simpleObfsOption
    44  }
    45  
    46  func streamConn(c net.Conn, option streamOption) *snell.Snell {
    47  	switch option.obfsOption.Mode {
    48  	case "tls":
    49  		c = obfs.NewTLSObfs(c, option.obfsOption.Host)
    50  	case "http":
    51  		_, port, _ := net.SplitHostPort(option.addr)
    52  		c = obfs.NewHTTPObfs(c, option.obfsOption.Host, port, option.obfsOption.RandomHost)
    53  	}
    54  	return snell.StreamConn(c, option.psk, option.version)
    55  }
    56  
    57  // StreamConn implements C.ProxyAdapter
    58  func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
    59  	c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
    60  	err := snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version)
    61  	return c, err
    62  }
    63  
    64  // StreamPacketConn implements C.ProxyAdapter
    65  func (s *Snell) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) {
    66  	c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
    67  
    68  	err := snell.WriteUDPHeader(c, s.version)
    69  	if err != nil {
    70  		return c, err
    71  	}
    72  
    73  	return WrapConn(snell.PacketConn(c)), nil
    74  }
    75  
    76  // DialContext implements C.ProxyAdapter
    77  func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
    78  	if s.version == snell.Version2 && len(opts) == 0 {
    79  		c, err := s.pool.Get()
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  
    84  		if err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version); err != nil {
    85  			_ = c.Close()
    86  			return nil, err
    87  		}
    88  		return NewConn(c, s), err
    89  	}
    90  
    91  	c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
    92  	if err != nil {
    93  		return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
    94  	}
    95  	tcpKeepAlive(c)
    96  
    97  	defer func(cc net.Conn, e error) {
    98  		safeConnClose(cc, e)
    99  	}(c, err)
   100  
   101  	c, err = s.StreamConn(c, metadata)
   102  	return NewConn(c, s), err
   103  }
   104  
   105  // ListenPacketContext implements C.ProxyAdapter
   106  func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
   107  	c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	tcpKeepAlive(c)
   112  
   113  	pc, err := s.StreamPacketConn(c, metadata)
   114  	if err != nil {
   115  		_ = c.Close()
   116  		return nil, err
   117  	}
   118  
   119  	return NewPacketConn(pc.(net.PacketConn), s), nil
   120  }
   121  
   122  func NewSnell(option SnellOption) (*Snell, error) {
   123  	addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
   124  	psk := []byte(option.Psk)
   125  
   126  	decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
   127  	obfsOption := &simpleObfsOption{Host: "bing.com", RandomHost: option.RandomHost}
   128  	if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
   129  		return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
   130  	}
   131  
   132  	switch obfsOption.Mode {
   133  	case "tls", "http", "":
   134  		break
   135  	default:
   136  		return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
   137  	}
   138  
   139  	// backward compatible
   140  	if option.Version == 0 {
   141  		option.Version = snell.DefaultSnellVersion
   142  	}
   143  	switch option.Version {
   144  	case snell.Version1, snell.Version2:
   145  		if option.UDP {
   146  			return nil, fmt.Errorf("snell version %d not support UDP", option.Version)
   147  		}
   148  	case snell.Version3:
   149  	default:
   150  		return nil, fmt.Errorf("snell version error: %d", option.Version)
   151  	}
   152  
   153  	s := &Snell{
   154  		Base: &Base{
   155  			name:  option.Name,
   156  			addr:  addr,
   157  			tp:    C.Snell,
   158  			udp:   option.UDP,
   159  			iface: option.Interface,
   160  			rmark: option.RoutingMark,
   161  			dns:   option.RemoteDnsResolve,
   162  		},
   163  		psk:        psk,
   164  		obfsOption: obfsOption,
   165  		version:    option.Version,
   166  	}
   167  
   168  	if option.Version == snell.Version2 {
   169  		s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
   170  			c, err := dialer.DialContext(ctx, "tcp", addr, s.Base.DialOptions()...)
   171  			if err != nil {
   172  				return nil, err
   173  			}
   174  
   175  			tcpKeepAlive(c)
   176  			return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
   177  		})
   178  	}
   179  	return s, nil
   180  }