github.com/igoogolx/clash@v1.19.8/adapter/outbound/shadowsocks.go (about) 1 package outbound 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net" 8 "strconv" 9 10 "github.com/igoogolx/clash/common/structure" 11 "github.com/igoogolx/clash/component/dialer" 12 C "github.com/igoogolx/clash/constant" 13 "github.com/igoogolx/clash/transport/shadowsocks/core" 14 obfs "github.com/igoogolx/clash/transport/simple-obfs" 15 "github.com/igoogolx/clash/transport/socks5" 16 v2rayObfs "github.com/igoogolx/clash/transport/v2ray-plugin" 17 ) 18 19 type ShadowSocks struct { 20 *Base 21 cipher core.Cipher 22 23 // obfs 24 obfsMode string 25 obfsOption *simpleObfsOption 26 v2rayOption *v2rayObfs.Option 27 } 28 29 type ShadowSocksOption struct { 30 BasicOption 31 Name string `proxy:"name"` 32 Server string `proxy:"server"` 33 Port int `proxy:"port"` 34 Password string `proxy:"password"` 35 Cipher string `proxy:"cipher"` 36 UDP bool `proxy:"udp,omitempty"` 37 Plugin string `proxy:"plugin,omitempty"` 38 PluginOpts map[string]any `proxy:"plugin-opts,omitempty"` 39 } 40 41 type simpleObfsOption struct { 42 Mode string `obfs:"mode,omitempty"` 43 Host string `obfs:"host,omitempty"` 44 } 45 46 type v2rayObfsOption struct { 47 Mode string `obfs:"mode"` 48 Host string `obfs:"host,omitempty"` 49 Path string `obfs:"path,omitempty"` 50 TLS bool `obfs:"tls,omitempty"` 51 Headers map[string]string `obfs:"headers,omitempty"` 52 SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` 53 Mux bool `obfs:"mux,omitempty"` 54 } 55 56 // StreamConn implements C.ProxyAdapter 57 func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { 58 switch ss.obfsMode { 59 case "tls": 60 c = obfs.NewTLSObfs(c, ss.obfsOption.Host) 61 case "http": 62 _, port, _ := net.SplitHostPort(ss.addr) 63 c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port) 64 case "websocket": 65 var err error 66 c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption) 67 if err != nil { 68 return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) 69 } 70 } 71 c = ss.cipher.StreamConn(c) 72 _, err := c.Write(serializesSocksAddr(metadata)) 73 return c, err 74 } 75 76 // DialContext implements C.ProxyAdapter 77 func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { 78 c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...) 79 if err != nil { 80 return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) 81 } 82 tcpKeepAlive(c) 83 84 defer func(c net.Conn) { 85 safeConnClose(c, err) 86 }(c) 87 88 c, err = ss.StreamConn(c, metadata) 89 return NewConn(c, ss), err 90 } 91 92 // ListenPacketContext implements C.ProxyAdapter 93 func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { 94 pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...) 95 if err != nil { 96 return nil, err 97 } 98 99 addr, err := resolveUDPAddr("udp", ss.addr) 100 if err != nil { 101 pc.Close() 102 return nil, err 103 } 104 105 pc = ss.cipher.PacketConn(pc) 106 return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil 107 } 108 109 func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { 110 addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) 111 cipher := option.Cipher 112 password := option.Password 113 ciph, err := core.PickCipher(cipher, nil, password) 114 if err != nil { 115 return nil, fmt.Errorf("ss %s initialize error: %w", addr, err) 116 } 117 118 var v2rayOption *v2rayObfs.Option 119 var obfsOption *simpleObfsOption 120 obfsMode := "" 121 122 decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) 123 if option.Plugin == "obfs" { 124 opts := simpleObfsOption{Host: "bing.com"} 125 if err := decoder.Decode(option.PluginOpts, &opts); err != nil { 126 return nil, fmt.Errorf("ss %s initialize obfs error: %w", addr, err) 127 } 128 129 if opts.Mode != "tls" && opts.Mode != "http" { 130 return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode) 131 } 132 obfsMode = opts.Mode 133 obfsOption = &opts 134 } else if option.Plugin == "v2ray-plugin" { 135 opts := v2rayObfsOption{Host: "bing.com", Mux: true} 136 if err := decoder.Decode(option.PluginOpts, &opts); err != nil { 137 return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", addr, err) 138 } 139 140 if opts.Mode != "websocket" { 141 return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode) 142 } 143 obfsMode = opts.Mode 144 v2rayOption = &v2rayObfs.Option{ 145 Host: opts.Host, 146 Path: opts.Path, 147 Headers: opts.Headers, 148 Mux: opts.Mux, 149 } 150 151 if opts.TLS { 152 v2rayOption.TLS = true 153 v2rayOption.SkipCertVerify = opts.SkipCertVerify 154 } 155 } 156 157 return &ShadowSocks{ 158 Base: &Base{ 159 name: option.Name, 160 addr: addr, 161 tp: C.Shadowsocks, 162 udp: option.UDP, 163 iface: option.Interface, 164 rmark: option.RoutingMark, 165 }, 166 cipher: ciph, 167 168 obfsMode: obfsMode, 169 v2rayOption: v2rayOption, 170 obfsOption: obfsOption, 171 }, nil 172 } 173 174 type ssPacketConn struct { 175 net.PacketConn 176 rAddr net.Addr 177 } 178 179 func (spc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { 180 packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) 181 if err != nil { 182 return 183 } 184 return spc.PacketConn.WriteTo(packet[3:], spc.rAddr) 185 } 186 187 func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 188 n, _, e := spc.PacketConn.ReadFrom(b) 189 if e != nil { 190 return 0, nil, e 191 } 192 193 addr := socks5.SplitAddr(b[:n]) 194 if addr == nil { 195 return 0, nil, errors.New("parse addr error") 196 } 197 198 udpAddr := addr.UDPAddr() 199 if udpAddr == nil { 200 return 0, nil, errors.New("parse addr error") 201 } 202 203 copy(b, b[len(addr):]) 204 return n - len(addr), udpAddr, e 205 }