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