github.com/igoogolx/clash@v1.19.8/adapter/outbound/snell.go (about)

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"strconv"
     8  
     9  	"github.com/igoogolx/clash/common/structure"
    10  	"github.com/igoogolx/clash/component/dialer"
    11  	C "github.com/igoogolx/clash/constant"
    12  	obfs "github.com/igoogolx/clash/transport/simple-obfs"
    13  	"github.com/igoogolx/clash/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  	UDP      bool           `proxy:"udp,omitempty"`
    31  	Version  int            `proxy:"version,omitempty"`
    32  	ObfsOpts map[string]any `proxy:"obfs-opts,omitempty"`
    33  }
    34  
    35  type streamOption struct {
    36  	psk        []byte
    37  	version    int
    38  	addr       string
    39  	obfsOption *simpleObfsOption
    40  }
    41  
    42  func streamConn(c net.Conn, option streamOption) *snell.Snell {
    43  	switch option.obfsOption.Mode {
    44  	case "tls":
    45  		c = obfs.NewTLSObfs(c, option.obfsOption.Host)
    46  	case "http":
    47  		_, port, _ := net.SplitHostPort(option.addr)
    48  		c = obfs.NewHTTPObfs(c, option.obfsOption.Host, port)
    49  	}
    50  	return snell.StreamConn(c, option.psk, option.version)
    51  }
    52  
    53  // StreamConn implements C.ProxyAdapter
    54  func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
    55  	c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
    56  	err := snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), 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  		if err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version); err != nil {
    69  			c.Close()
    70  			return nil, err
    71  		}
    72  		return NewConn(c, s), err
    73  	}
    74  
    75  	c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
    76  	if err != nil {
    77  		return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
    78  	}
    79  	tcpKeepAlive(c)
    80  
    81  	defer func(c net.Conn) {
    82  		safeConnClose(c, err)
    83  	}(c)
    84  
    85  	c, err = s.StreamConn(c, metadata)
    86  	return NewConn(c, s), err
    87  }
    88  
    89  // ListenPacketContext implements C.ProxyAdapter
    90  func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
    91  	c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	tcpKeepAlive(c)
    96  	c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
    97  
    98  	err = snell.WriteUDPHeader(c, s.version)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	pc := snell.PacketConn(c)
   104  	return newPacketConn(pc, s), nil
   105  }
   106  
   107  func NewSnell(option SnellOption) (*Snell, error) {
   108  	addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
   109  	psk := []byte(option.Psk)
   110  
   111  	decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
   112  	obfsOption := &simpleObfsOption{Host: "bing.com"}
   113  	if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
   114  		return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
   115  	}
   116  
   117  	switch obfsOption.Mode {
   118  	case "tls", "http", "":
   119  		break
   120  	default:
   121  		return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
   122  	}
   123  
   124  	// backward compatible
   125  	if option.Version == 0 {
   126  		option.Version = snell.DefaultSnellVersion
   127  	}
   128  	switch option.Version {
   129  	case snell.Version1, snell.Version2:
   130  		if option.UDP {
   131  			return nil, fmt.Errorf("snell version %d not support UDP", option.Version)
   132  		}
   133  	case snell.Version3:
   134  	default:
   135  		return nil, fmt.Errorf("snell version error: %d", option.Version)
   136  	}
   137  
   138  	s := &Snell{
   139  		Base: &Base{
   140  			name:  option.Name,
   141  			addr:  addr,
   142  			tp:    C.Snell,
   143  			udp:   option.UDP,
   144  			iface: option.Interface,
   145  			rmark: option.RoutingMark,
   146  		},
   147  		psk:        psk,
   148  		obfsOption: obfsOption,
   149  		version:    option.Version,
   150  	}
   151  
   152  	if option.Version == snell.Version2 {
   153  		s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
   154  			c, err := dialer.DialContext(ctx, "tcp", addr, s.Base.DialOptions()...)
   155  			if err != nil {
   156  				return nil, err
   157  			}
   158  
   159  			tcpKeepAlive(c)
   160  			return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
   161  		})
   162  	}
   163  	return s, nil
   164  }