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 }