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 }