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 }