diff --git a/adapters/outbound/shadowsocksr.go b/adapters/outbound/shadowsocksr.go index 1a422cdf..155ec837 100644 --- a/adapters/outbound/shadowsocksr.go +++ b/adapters/outbound/shadowsocksr.go @@ -12,12 +12,13 @@ import ( "github.com/Dreamacro/clash/component/ssr/protocol" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/go-shadowsocks2/core" + "github.com/Dreamacro/go-shadowsocks2/shadowaead" "github.com/Dreamacro/go-shadowsocks2/shadowstream" ) type ShadowSocksR struct { *Base - cipher *core.StreamCipher + cipher core.Cipher obfs obfs.Obfs protocol protocol.Protocol } @@ -36,17 +37,22 @@ type ShadowSocksROption struct { } func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { - c = obfs.NewConn(c, ssr.obfs) + c = ssr.obfs.StreamConn(c) c = ssr.cipher.StreamConn(c) - conn, ok := c.(*shadowstream.Conn) - if !ok { + var ( + iv []byte + err error + ) + switch conn := c.(type) { + case *shadowstream.Conn: + iv, err = conn.ObtainWriteIV() + if err != nil { + return nil, err + } + case *shadowaead.Conn: return nil, fmt.Errorf("invalid connection type") } - iv, err := conn.ObtainWriteIV() - if err != nil { - return nil, err - } - c = protocol.NewConn(c, ssr.protocol, iv) + c = ssr.protocol.StreamConn(c, iv) _, err = c.Write(serializesSocksAddr(metadata)) return c, err } @@ -74,7 +80,7 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { } pc = ssr.cipher.PacketConn(pc) - pc = protocol.NewPacketConn(pc, ssr.protocol) + pc = ssr.protocol.PacketConn(pc) return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil } @@ -90,35 +96,43 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { password := option.Password coreCiph, err := core.PickCipher(cipher, nil, password) if err != nil { - return nil, fmt.Errorf("ssr %s initialize cipher error: %w", addr, err) + return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err) } - ciph, ok := coreCiph.(*core.StreamCipher) - if !ok { - return nil, fmt.Errorf("%s is not a supported stream cipher in ssr", cipher) + var ( + ivSize int + key []byte + ) + if option.Cipher == "dummy" { + ivSize = 0 + key = core.Kdf(option.Password, 16) + } else { + ciph, ok := coreCiph.(*core.StreamCipher) + if !ok { + return nil, fmt.Errorf("%s is not dummy or a supported stream cipher in ssr", cipher) + } + ivSize = ciph.IVSize() + key = ciph.Key } - obfs, err := obfs.PickObfs(option.Obfs, &obfs.Base{ - IVSize: ciph.IVSize(), - Key: ciph.Key, - HeadLen: 30, - Host: option.Server, - Port: option.Port, - Param: option.ObfsParam, + obfs, obfsOverhead, err := obfs.PickObfs(option.Obfs, &obfs.Base{ + Host: option.Server, + Port: option.Port, + Key: key, + IVSize: ivSize, + Param: option.ObfsParam, }) if err != nil { return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err) } protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{ - IV: nil, - Key: ciph.Key, - TCPMss: 1460, - Param: option.ProtocolParam, + Key: key, + Overhead: obfsOverhead, + Param: option.ProtocolParam, }) if err != nil { return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err) } - protocol.SetOverhead(obfs.GetObfsOverhead() + protocol.GetProtocolOverhead()) return &ShadowSocksR{ Base: &Base{ @@ -127,7 +141,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { tp: C.ShadowsocksR, udp: option.UDP, }, - cipher: ciph, + cipher: coreCiph, obfs: obfs, protocol: protocol, }, nil diff --git a/component/ssr/obfs/base.go b/component/ssr/obfs/base.go index 0f58118e..7fd1b84c 100644 --- a/component/ssr/obfs/base.go +++ b/component/ssr/obfs/base.go @@ -1,11 +1,9 @@ package obfs -// Base information for obfs type Base struct { - IVSize int - Key []byte - HeadLen int - Host string - Port int - Param string + Host string + Port int + Key []byte + IVSize int + Param string } diff --git a/component/ssr/obfs/http_post.go b/component/ssr/obfs/http_post.go index e50ece1c..4be6cbe8 100644 --- a/component/ssr/obfs/http_post.go +++ b/component/ssr/obfs/http_post.go @@ -1,7 +1,7 @@ package obfs func init() { - register("http_post", newHTTPPost) + register("http_post", newHTTPPost, 0) } func newHTTPPost(b *Base) Obfs { diff --git a/component/ssr/obfs/http_simple.go b/component/ssr/obfs/http_simple.go index c485a661..86dea947 100644 --- a/component/ssr/obfs/http_simple.go +++ b/component/ssr/obfs/http_simple.go @@ -3,400 +3,405 @@ package obfs import ( "bytes" "encoding/hex" - "fmt" "io" "math/rand" + "net" + "strconv" "strings" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" ) +func init() { + register("http_simple", newHTTPSimple, 0) +} + type httpObfs struct { *Base - firstRequest bool - firstResponse bool - post bool -} - -func init() { - register("http_simple", newHTTPSimple) + post bool } func newHTTPSimple(b *Base) Obfs { return &httpObfs{Base: b} } -func (h *httpObfs) initForConn() Obfs { - return &httpObfs{ - Base: h.Base, - firstRequest: true, - firstResponse: true, - post: h.post, - } +type httpConn struct { + net.Conn + *httpObfs + hasSentHeader bool + hasRecvHeader bool + buf []byte } -func (h *httpObfs) GetObfsOverhead() int { - return 0 +func (h *httpObfs) StreamConn(c net.Conn) net.Conn { + return &httpConn{Conn: c, httpObfs: h} } -func (h *httpObfs) Decode(b []byte) ([]byte, bool, error) { - if h.firstResponse { - idx := bytes.Index(b, []byte("\r\n\r\n")) - if idx == -1 { - return nil, false, io.EOF - } - h.firstResponse = false - return b[idx+4:], false, nil - } - return b, false, nil -} - -func (h *httpObfs) Encode(b []byte) ([]byte, error) { - if h.firstRequest { - bSize := len(b) - var headData []byte - - if headSize := h.IVSize + h.HeadLen; bSize-headSize > 64 { - headData = make([]byte, headSize+rand.Intn(64)) +func (c *httpConn) Read(b []byte) (int, error) { + if c.buf != nil { + n := copy(b, c.buf) + if n == len(c.buf) { + c.buf = nil } else { - headData = make([]byte, bSize) + c.buf = c.buf[n:] } - copy(headData, b[:len(headData)]) - host := h.Host - var customHead string - - if len(h.Param) > 0 { - customHeads := strings.Split(h.Param, "#") - if len(customHeads) > 2 { - customHeads = customHeads[:2] - } - customHosts := h.Param - if len(customHeads) > 1 { - customHosts = customHeads[0] - customHead = customHeads[1] - } - hosts := strings.Split(customHosts, ",") - if len(hosts) > 0 { - host = strings.TrimSpace(hosts[rand.Intn(len(hosts))]) - } - } - - method := "GET /" - if h.post { - method = "POST /" - } - requestPathIndex := rand.Intn(len(requestPath)/2) * 2 - httpBuf := fmt.Sprintf("%s%s%s%s HTTP/1.1\r\nHost: %s:%d\r\n", - method, - requestPath[requestPathIndex], - data2URLEncode(headData), - requestPath[requestPathIndex+1], - host, h.Port) - if len(customHead) > 0 { - httpBuf = httpBuf + strings.Replace(customHead, "\\n", "\r\n", -1) + "\r\n\r\n" - } else { - var contentType string - if h.post { - contentType = "Content-Type: multipart/form-data; boundary=" + boundary() + "\r\n" - } - httpBuf = httpBuf + "User-agent: " + requestUserAgent[rand.Intn(len(requestUserAgent))] + "\r\n" + - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + - "Accept-Language: en-US,en;q=0.8\r\n" + - "Accept-Encoding: gzip, deflate\r\n" + - contentType + - "DNT: 1\r\n" + - "Connection: keep-alive\r\n" + - "\r\n" - } - - var encoded []byte - if len(headData) < bSize { - encoded = make([]byte, len(httpBuf)+(bSize-len(headData))) - copy(encoded, []byte(httpBuf)) - copy(encoded[len(httpBuf):], b[len(headData):]) - } else { - encoded = []byte(httpBuf) - } - h.firstRequest = false - return encoded, nil + return n, nil } - return b, nil -} - -func data2URLEncode(data []byte) (ret string) { - for i := 0; i < len(data); i++ { - ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]})) + if c.hasRecvHeader { + return c.Conn.Read(b) } - return + + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) + n, err := c.Conn.Read(buf) + if err != nil { + return 0, err + } + pos := bytes.Index(buf[:n], []byte("\r\n\r\n")) + if pos == -1 { + return 0, io.EOF + } + c.hasRecvHeader = true + dataLength := n - pos - 4 + n = copy(b, buf[4+pos:n]) + if dataLength > n { + c.buf = append(c.buf, buf[4+pos+n:4+pos+dataLength]...) + } + return n, nil } -func boundary() (ret string) { +func (c *httpConn) Write(b []byte) (int, error) { + if c.hasSentHeader { + return c.Conn.Write(b) + } + // 30: head length + headLength := c.IVSize + 30 + + bLength := len(b) + headDataLength := bLength + if bLength-headLength > 64 { + headDataLength = headLength + rand.Intn(65) + } + headData := b[:headDataLength] + b = b[headDataLength:] + + var body string + host := c.Host + if len(c.Param) > 0 { + pos := strings.Index(c.Param, "#") + if pos != -1 { + body = strings.ReplaceAll(c.Param[pos+1:], "\n", "\r\n") + body = strings.ReplaceAll(body, "\\n", "\r\n") + host = c.Param[:pos] + } else { + host = c.Param + } + } + hosts := strings.Split(host, ",") + host = hosts[rand.Intn(len(hosts))] + + buf := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(buf) + defer buf.Reset() + if c.post { + buf.WriteString("POST /") + } else { + buf.WriteString("GET /") + } + packURLEncodedHeadData(buf, headData) + buf.WriteString(" HTTP/1.1\r\nHost: " + host) + if c.Port != 80 { + buf.WriteString(":" + strconv.Itoa(c.Port)) + } + buf.WriteString("\r\n") + if len(body) > 0 { + buf.WriteString(body + "\r\n\r\n") + } else { + buf.WriteString("User-Agent: ") + buf.WriteString(userAgent[rand.Intn(len(userAgent))]) + buf.WriteString("\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\n") + if c.post { + packBoundary(buf) + } + buf.WriteString("DNT: 1\r\nConnection: keep-alive\r\n\r\n") + } + buf.Write(b) + _, err := c.Conn.Write(buf.Bytes()) + if err != nil { + return 0, nil + } + c.hasSentHeader = true + return bLength, nil +} + +func packURLEncodedHeadData(buf *bytes.Buffer, data []byte) { + dataLength := len(data) + for i := 0; i < dataLength; i++ { + buf.WriteRune('%') + buf.WriteString(hex.EncodeToString(data[i : i+1])) + } +} + +func packBoundary(buf *bytes.Buffer) { + buf.WriteString("Content-Type: multipart/form-data; boundary=") set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" for i := 0; i < 32; i++ { - ret = fmt.Sprintf("%s%c", ret, set[rand.Intn(len(set))]) + buf.WriteByte(set[rand.Intn(62)]) } - return + buf.WriteString("\r\n") } -var ( - requestPath = []string{ - "", "", - "login.php?redir=", "", - "register.php?code=", "", - "?keyword=", "", - "search?src=typd&q=", "&lang=en", - "s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=", - "post.php?id=", "&goto=view.php", - } - requestUserAgent = []string{ - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", - "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36", - "Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0", - "Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36", - "Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", - "Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", - "Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", - "Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", - "Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1", - "Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", - } -) +var userAgent = []string{ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", + "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36", + "Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0", + "Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36", + "Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", + "Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", + "Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", + "Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1", + "Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", +} diff --git a/component/ssr/obfs/obfs.go b/component/ssr/obfs/obfs.go index 1e8c4334..c56acc8a 100644 --- a/component/ssr/obfs/obfs.go +++ b/component/ssr/obfs/obfs.go @@ -3,7 +3,7 @@ package obfs import ( "errors" "fmt" - "strings" + "net" ) var ( @@ -12,26 +12,31 @@ var ( errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed") ) -// Obfs provides methods for decoding and encoding +type authData struct { + clientID [32]byte +} + type Obfs interface { - initForConn() Obfs - GetObfsOverhead() int - Decode(b []byte) ([]byte, bool, error) - Encode(b []byte) ([]byte, error) + StreamConn(net.Conn) net.Conn } type obfsCreator func(b *Base) Obfs -var obfsList = make(map[string]obfsCreator) +var obfsList = make(map[string]struct { + overhead int + new obfsCreator +}) -func register(name string, c obfsCreator) { - obfsList[name] = c +func register(name string, c obfsCreator, o int) { + obfsList[name] = struct { + overhead int + new obfsCreator + }{overhead: o, new: c} } -// PickObfs returns an obfs of the given name -func PickObfs(name string, b *Base) (Obfs, error) { - if obfsCreator, ok := obfsList[strings.ToLower(name)]; ok { - return obfsCreator(b), nil +func PickObfs(name string, b *Base) (Obfs, int, error) { + if choice, ok := obfsList[name]; ok { + return choice.new(b), choice.overhead, nil } - return nil, fmt.Errorf("Obfs %s not supported", name) + return nil, 0, fmt.Errorf("Obfs %s not supported", name) } diff --git a/component/ssr/obfs/plain.go b/component/ssr/obfs/plain.go index 372feff2..eb998a47 100644 --- a/component/ssr/obfs/plain.go +++ b/component/ssr/obfs/plain.go @@ -1,25 +1,15 @@ package obfs +import "net" + type plain struct{} func init() { - register("plain", newPlain) + register("plain", newPlain, 0) } func newPlain(b *Base) Obfs { return &plain{} } -func (p *plain) initForConn() Obfs { return &plain{} } - -func (p *plain) GetObfsOverhead() int { - return 0 -} - -func (p *plain) Encode(b []byte) ([]byte, error) { - return b, nil -} - -func (p *plain) Decode(b []byte) ([]byte, bool, error) { - return b, false, nil -} +func (p *plain) StreamConn(c net.Conn) net.Conn { return c } diff --git a/component/ssr/obfs/random_head.go b/component/ssr/obfs/random_head.go index deaab2bd..b10b01c5 100644 --- a/component/ssr/obfs/random_head.go +++ b/component/ssr/obfs/random_head.go @@ -4,72 +4,68 @@ import ( "encoding/binary" "hash/crc32" "math/rand" + "net" + + "github.com/Dreamacro/clash/common/pool" ) +func init() { + register("random_head", newRandomHead, 0) +} + type randomHead struct { *Base - firstRequest bool - firstResponse bool - headerSent bool - buffer []byte -} - -func init() { - register("random_head", newRandomHead) } func newRandomHead(b *Base) Obfs { return &randomHead{Base: b} } -func (r *randomHead) initForConn() Obfs { - return &randomHead{ - Base: r.Base, - firstRequest: true, - firstResponse: true, - } +type randomHeadConn struct { + net.Conn + *randomHead + hasSentHeader bool + rawTransSent bool + rawTransRecv bool + buf []byte } -func (r *randomHead) GetObfsOverhead() int { - return 0 +func (r *randomHead) StreamConn(c net.Conn) net.Conn { + return &randomHeadConn{Conn: c, randomHead: r} } -func (r *randomHead) Encode(b []byte) (encoded []byte, err error) { - if !r.firstRequest { - return b, nil +func (c *randomHeadConn) Read(b []byte) (int, error) { + if c.rawTransRecv { + return c.Conn.Read(b) } - - bSize := len(b) - if r.headerSent { - if bSize > 0 { - d := make([]byte, len(r.buffer)+bSize) - copy(d, r.buffer) - copy(d[len(r.buffer):], b) - r.buffer = d - } else { - encoded = r.buffer - r.buffer = nil - r.firstRequest = false - } - } else { - size := rand.Intn(96) + 8 - encoded = make([]byte, size) - rand.Read(encoded) - crc := (0xFFFFFFFF - crc32.ChecksumIEEE(encoded[:size-4])) & 0xFFFFFFFF - binary.LittleEndian.PutUint32(encoded[size-4:], crc) - - d := make([]byte, bSize) - copy(d, b) - r.buffer = d - } - r.headerSent = true - return encoded, nil + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) + c.Conn.Read(buf) + c.rawTransRecv = true + c.Write(nil) + return 0, nil } -func (r *randomHead) Decode(b []byte) ([]byte, bool, error) { - if r.firstResponse { - r.firstResponse = false - return b, true, nil +func (c *randomHeadConn) Write(b []byte) (int, error) { + if c.rawTransSent { + return c.Conn.Write(b) } - return b, false, nil + c.buf = append(c.buf, b...) + if !c.hasSentHeader { + c.hasSentHeader = true + dataLength := rand.Intn(96) + 4 + buf := pool.Get(dataLength + 4) + defer pool.Put(buf) + rand.Read(buf[:dataLength]) + binary.LittleEndian.PutUint32(buf[dataLength:], 0xffffffff-crc32.ChecksumIEEE(buf[:dataLength])) + _, err := c.Conn.Write(buf) + return len(b), err + } + if c.rawTransRecv { + _, err := c.Conn.Write(c.buf) + c.buf = nil + c.rawTransSent = true + return len(b), err + } + return len(b), nil } diff --git a/component/ssr/obfs/stream.go b/component/ssr/obfs/stream.go deleted file mode 100644 index f94cf85c..00000000 --- a/component/ssr/obfs/stream.go +++ /dev/null @@ -1,72 +0,0 @@ -package obfs - -import ( - "net" - - "github.com/Dreamacro/clash/common/pool" -) - -// NewConn wraps a stream-oriented net.Conn with obfs decoding/encoding -func NewConn(c net.Conn, o Obfs) net.Conn { - return &Conn{Conn: c, Obfs: o.initForConn()} -} - -// Conn represents an obfs connection -type Conn struct { - net.Conn - Obfs - buf []byte - offset int -} - -func (c *Conn) Read(b []byte) (int, error) { - if c.buf != nil { - n := copy(b, c.buf[c.offset:]) - c.offset += n - if c.offset == len(c.buf) { - pool.Put(c.buf) - c.buf = nil - } - return n, nil - } - - buf := pool.Get(pool.RelayBufferSize) - defer pool.Put(buf) - n, err := c.Conn.Read(buf) - if err != nil { - return 0, err - } - decoded, sendback, err := c.Decode(buf[:n]) - // decoded may be part of buf - decodedData := pool.Get(len(decoded)) - copy(decodedData, decoded) - if err != nil { - pool.Put(decodedData) - return 0, err - } - if sendback { - c.Write(nil) - pool.Put(decodedData) - return 0, nil - } - n = copy(b, decodedData) - if len(decodedData) > len(b) { - c.buf = decodedData - c.offset = n - } else { - pool.Put(decodedData) - } - return n, err -} - -func (c *Conn) Write(b []byte) (int, error) { - encoded, err := c.Encode(b) - if err != nil { - return 0, err - } - _, err = c.Conn.Write(encoded) - if err != nil { - return 0, err - } - return len(b), nil -} diff --git a/component/ssr/obfs/tls1.2_ticket_auth.go b/component/ssr/obfs/tls1.2_ticket_auth.go new file mode 100644 index 00000000..f3c5b456 --- /dev/null +++ b/component/ssr/obfs/tls1.2_ticket_auth.go @@ -0,0 +1,231 @@ +package obfs + +import ( + "bytes" + "crypto/hmac" + "encoding/binary" + "math/rand" + "net" + "strings" + "time" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" +) + +func init() { + register("tls1.2_ticket_auth", newTLS12Ticket, 5) + register("tls1.2_ticket_fastauth", newTLS12Ticket, 5) +} + +type tls12Ticket struct { + *Base + *authData +} + +func newTLS12Ticket(b *Base) Obfs { + r := &tls12Ticket{Base: b, authData: &authData{}} + rand.Read(r.clientID[:]) + return r +} + +type tls12TicketConn struct { + net.Conn + *tls12Ticket + handshakeStatus int + decoded bytes.Buffer + underDecoded bytes.Buffer + sendBuf bytes.Buffer +} + +func (t *tls12Ticket) StreamConn(c net.Conn) net.Conn { + return &tls12TicketConn{Conn: c, tls12Ticket: t} +} + +func (c *tls12TicketConn) Read(b []byte) (int, error) { + if c.decoded.Len() > 0 { + return c.decoded.Read(b) + } + + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) + n, err := c.Conn.Read(buf) + if err != nil { + return 0, err + } + + if c.handshakeStatus == 8 { + c.underDecoded.Write(buf[:n]) + for c.underDecoded.Len() > 5 { + if !bytes.Equal(c.underDecoded.Bytes()[:3], []byte{0x17, 3, 3}) { + c.underDecoded.Reset() + return 0, errTLS12TicketAuthIncorrectMagicNumber + } + size := int(binary.BigEndian.Uint16(c.underDecoded.Bytes()[3:5])) + if c.underDecoded.Len() < 5+size { + break + } + c.underDecoded.Next(5) + c.decoded.Write(c.underDecoded.Next(size)) + } + n, _ = c.decoded.Read(b) + return n, nil + } + + if n < 11+32+1+32 { + return 0, errTLS12TicketAuthTooShortData + } + + if !hmac.Equal(buf[33:43], c.hmacSHA1(buf[11:33])[:10]) || !hmac.Equal(buf[n-10:n], c.hmacSHA1(buf[:n-10])[:10]) { + return 0, errTLS12TicketAuthHMACError + } + + c.Write(nil) + return 0, nil +} + +func (c *tls12TicketConn) Write(b []byte) (int, error) { + length := len(b) + if c.handshakeStatus == 8 { + buf := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(buf) + defer buf.Reset() + for len(b) > 2048 { + size := rand.Intn(4096) + 100 + if len(b) < size { + size = len(b) + } + packData(buf, b[:size]) + b = b[size:] + } + if len(b) > 0 { + packData(buf, b) + } + _, err := c.Conn.Write(buf.Bytes()) + if err != nil { + return 0, err + } + return length, nil + } + + if len(b) > 0 { + packData(&c.sendBuf, b) + } + + if c.handshakeStatus == 0 { + c.handshakeStatus = 1 + + data := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(data) + defer data.Reset() + + data.Write([]byte{3, 3}) + c.packAuthData(data) + data.WriteByte(0x20) + data.Write(c.clientID[:]) + data.Write([]byte{0x00, 0x1c, 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13, 0xc0, 0x0a, 0xc0, 0x14, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x9c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0x0a}) + data.Write([]byte{0x1, 0x0}) + + ext := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(ext) + defer ext.Reset() + + host := c.getHost() + ext.Write([]byte{0xff, 0x01, 0x00, 0x01, 0x00}) + packSNIData(ext, host) + ext.Write([]byte{0, 0x17, 0, 0}) + c.packTicketBuf(ext, host) + ext.Write([]byte{0x00, 0x0d, 0x00, 0x16, 0x00, 0x14, 0x06, 0x01, 0x06, 0x03, 0x05, 0x01, 0x05, 0x03, 0x04, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x03, 0x02, 0x01, 0x02, 0x03}) + ext.Write([]byte{0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00}) + ext.Write([]byte{0x00, 0x12, 0x00, 0x00}) + ext.Write([]byte{0x75, 0x50, 0x00, 0x00}) + ext.Write([]byte{0x00, 0x0b, 0x00, 0x02, 0x01, 0x00}) + ext.Write([]byte{0x00, 0x0a, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, 0x00, 0x18}) + + binary.Write(data, binary.BigEndian, uint16(ext.Len())) + data.ReadFrom(ext) + + ret := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(ret) + defer ret.Reset() + + ret.Write([]byte{0x16, 3, 1}) + binary.Write(ret, binary.BigEndian, uint16(data.Len()+4)) + ret.Write([]byte{1, 0}) + binary.Write(ret, binary.BigEndian, uint16(data.Len())) + ret.ReadFrom(data) + + _, err := c.Conn.Write(ret.Bytes()) + if err != nil { + return 0, err + } + return length, nil + } else if c.handshakeStatus == 1 && len(b) == 0 { + buf := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(buf) + defer buf.Reset() + + buf.Write([]byte{0x14, 3, 3, 0, 1, 1, 0x16, 3, 3, 0, 0x20}) + tools.AppendRandBytes(buf, 22) + buf.Write(c.hmacSHA1(buf.Bytes())[:10]) + buf.ReadFrom(&c.sendBuf) + + c.handshakeStatus = 8 + + _, err := c.Conn.Write(buf.Bytes()) + return 0, err + } + return length, nil +} + +func packData(buf *bytes.Buffer, data []byte) { + buf.Write([]byte{0x17, 3, 3}) + binary.Write(buf, binary.BigEndian, uint16(len(data))) + buf.Write(data) +} + +func (t *tls12Ticket) packAuthData(buf *bytes.Buffer) { + binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix())) + tools.AppendRandBytes(buf, 18) + buf.Write(t.hmacSHA1(buf.Bytes()[buf.Len()-22:])[:10]) +} + +func packSNIData(buf *bytes.Buffer, u string) { + len := uint16(len(u)) + buf.Write([]byte{0, 0}) + binary.Write(buf, binary.BigEndian, len+5) + binary.Write(buf, binary.BigEndian, len+3) + buf.WriteByte(0) + binary.Write(buf, binary.BigEndian, len) + buf.WriteString(u) +} + +func (c *tls12TicketConn) packTicketBuf(buf *bytes.Buffer, u string) { + length := 16 * (rand.Intn(17) + 8) + buf.Write([]byte{0, 0x23}) + binary.Write(buf, binary.BigEndian, uint16(length)) + tools.AppendRandBytes(buf, length) +} + +func (t *tls12Ticket) hmacSHA1(data []byte) []byte { + key := pool.Get(len(t.Key) + 32) + defer pool.Put(key) + copy(key, t.Key) + copy(key[len(t.Key):], t.clientID[:]) + + sha1Data := tools.HmacSHA1(key, data) + return sha1Data[:10] +} + +func (t *tls12Ticket) getHost() string { + host := t.Param + if len(host) == 0 { + host = t.Host + } + if len(host) > 0 && host[len(host)-1] >= '0' && host[len(host)-1] <= '9' { + host = "" + } + hosts := strings.Split(host, ",") + host = hosts[rand.Intn(len(hosts))] + return host +} diff --git a/component/ssr/obfs/tls12_ticket_auth.go b/component/ssr/obfs/tls12_ticket_auth.go deleted file mode 100644 index b4c1089a..00000000 --- a/component/ssr/obfs/tls12_ticket_auth.go +++ /dev/null @@ -1,290 +0,0 @@ -package obfs - -import ( - "bytes" - "crypto/hmac" - "encoding/binary" - "fmt" - "io" - "math/rand" - "strings" - "time" - - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/ssr/tools" - "github.com/Dreamacro/clash/log" -) - -type tlsAuthData struct { - localClientID [32]byte -} - -type tls12Ticket struct { - *Base - *tlsAuthData - handshakeStatus int - sendSaver bytes.Buffer - recvBuffer bytes.Buffer - buffer bytes.Buffer -} - -func init() { - register("tls1.2_ticket_auth", newTLS12Ticket) - register("tls1.2_ticket_fastauth", newTLS12Ticket) -} - -func newTLS12Ticket(b *Base) Obfs { - return &tls12Ticket{ - Base: b, - } -} - -func (t *tls12Ticket) initForConn() Obfs { - r := &tls12Ticket{ - Base: t.Base, - tlsAuthData: &tlsAuthData{}, - } - rand.Read(r.localClientID[:]) - return r -} - -func (t *tls12Ticket) GetObfsOverhead() int { - return 5 -} - -func (t *tls12Ticket) Decode(b []byte) ([]byte, bool, error) { - if t.handshakeStatus == -1 { - return b, false, nil - } - t.buffer.Reset() - if t.handshakeStatus == 8 { - t.recvBuffer.Write(b) - for t.recvBuffer.Len() > 5 { - var h [5]byte - t.recvBuffer.Read(h[:]) - if !bytes.Equal(h[:3], []byte{0x17, 0x3, 0x3}) { - log.Warnln("incorrect magic number %x, 0x170303 is expected", h[:3]) - return nil, false, errTLS12TicketAuthIncorrectMagicNumber - } - size := int(binary.BigEndian.Uint16(h[3:5])) - if t.recvBuffer.Len() < size { - // 不够读,下回再读吧 - unread := t.recvBuffer.Bytes() - t.recvBuffer.Reset() - t.recvBuffer.Write(h[:]) - t.recvBuffer.Write(unread) - break - } - d := pool.Get(size) - t.recvBuffer.Read(d) - t.buffer.Write(d) - pool.Put(d) - } - return t.buffer.Bytes(), false, nil - } - - if len(b) < 11+32+1+32 { - return nil, false, errTLS12TicketAuthTooShortData - } - - hash := t.hmacSHA1(b[11 : 11+22]) - - if !hmac.Equal(b[33:33+tools.HmacSHA1Len], hash) { - return nil, false, errTLS12TicketAuthHMACError - } - return nil, true, nil -} - -func (t *tls12Ticket) Encode(b []byte) ([]byte, error) { - t.buffer.Reset() - switch t.handshakeStatus { - case 8: - if len(b) < 1024 { - d := []byte{0x17, 0x3, 0x3, 0, 0} - binary.BigEndian.PutUint16(d[3:5], uint16(len(b)&0xFFFF)) - t.buffer.Write(d) - t.buffer.Write(b) - return t.buffer.Bytes(), nil - } - start := 0 - var l int - for len(b)-start > 2048 { - l = rand.Intn(4096) + 100 - if l > len(b)-start { - l = len(b) - start - } - packData(&t.buffer, b[start:start+l]) - start += l - } - if len(b)-start > 0 { - l = len(b) - start - packData(&t.buffer, b[start:start+l]) - } - return t.buffer.Bytes(), nil - case 1: - if len(b) > 0 { - if len(b) < 1024 { - packData(&t.sendSaver, b) - } else { - start := 0 - var l int - for len(b)-start > 2048 { - l = rand.Intn(4096) + 100 - if l > len(b)-start { - l = len(b) - start - } - packData(&t.buffer, b[start:start+l]) - start += l - } - if len(b)-start > 0 { - l = len(b) - start - packData(&t.buffer, b[start:start+l]) - } - io.Copy(&t.sendSaver, &t.buffer) - } - return []byte{}, nil - } - hmacData := make([]byte, 43) - handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20") - copy(hmacData, handshakeFinish) - rand.Read(hmacData[11:33]) - h := t.hmacSHA1(hmacData[:33]) - copy(hmacData[33:], h) - t.buffer.Write(hmacData) - io.Copy(&t.buffer, &t.sendSaver) - t.handshakeStatus = 8 - return t.buffer.Bytes(), nil - case 0: - tlsData0 := []byte("\x00\x1c\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xcc\x14\xcc\x13\xc0\x0a\xc0\x14\xc0\x09\xc0\x13\x00\x9c\x00\x35\x00\x2f\x00\x0a\x01\x00") - tlsData1 := []byte("\xff\x01\x00\x01\x00") - tlsData2 := []byte("\x00\x17\x00\x00\x00\x23\x00\xd0") - // tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18\x00\x15\x00\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18") - - var tlsData [2048]byte - tlsDataLen := 0 - copy(tlsData[0:], tlsData1) - tlsDataLen += len(tlsData1) - sni := t.sni(t.getHost()) - copy(tlsData[tlsDataLen:], sni) - tlsDataLen += len(sni) - copy(tlsData[tlsDataLen:], tlsData2) - tlsDataLen += len(tlsData2) - ticketLen := rand.Intn(164)*2 + 64 - tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff) - tlsData[tlsDataLen-2] = uint8(ticketLen >> 8) - //ticketLen := 208 - rand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen]) - tlsDataLen += ticketLen - copy(tlsData[tlsDataLen:], tlsData3) - tlsDataLen += len(tlsData3) - - length := 11 + 32 + 1 + 32 + len(tlsData0) + 2 + tlsDataLen - encodedData := make([]byte, length) - pdata := length - tlsDataLen - l := tlsDataLen - copy(encodedData[pdata:], tlsData[:tlsDataLen]) - encodedData[pdata-1] = uint8(tlsDataLen) - encodedData[pdata-2] = uint8(tlsDataLen >> 8) - pdata -= 2 - l += 2 - copy(encodedData[pdata-len(tlsData0):], tlsData0) - pdata -= len(tlsData0) - l += len(tlsData0) - copy(encodedData[pdata-32:], t.localClientID[:]) - pdata -= 32 - l += 32 - encodedData[pdata-1] = 0x20 - pdata-- - l++ - copy(encodedData[pdata-32:], t.packAuthData()) - pdata -= 32 - l += 32 - encodedData[pdata-1] = 0x3 - encodedData[pdata-2] = 0x3 // tls version - pdata -= 2 - l += 2 - encodedData[pdata-1] = uint8(l) - encodedData[pdata-2] = uint8(l >> 8) - encodedData[pdata-3] = 0 - encodedData[pdata-4] = 1 - pdata -= 4 - l += 4 - encodedData[pdata-1] = uint8(l) - encodedData[pdata-2] = uint8(l >> 8) - pdata -= 2 - l += 2 - encodedData[pdata-1] = 0x1 - encodedData[pdata-2] = 0x3 // tls version - pdata -= 2 - l += 2 - encodedData[pdata-1] = 0x16 // tls handshake - pdata-- - l++ - packData(&t.sendSaver, b) - t.handshakeStatus = 1 - return encodedData, nil - default: - return nil, fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus) - } -} - -func (t *tls12Ticket) hmacSHA1(data []byte) []byte { - key := make([]byte, len(t.Key)+32) - copy(key, t.Key) - copy(key[len(t.Key):], t.localClientID[:]) - - sha1Data := tools.HmacSHA1(key, data) - return sha1Data[:tools.HmacSHA1Len] -} - -func (t *tls12Ticket) sni(u string) []byte { - bURL := []byte(u) - length := len(bURL) - ret := make([]byte, length+9) - copy(ret[9:9+length], bURL) - binary.BigEndian.PutUint16(ret[7:], uint16(length&0xFFFF)) - length += 3 - binary.BigEndian.PutUint16(ret[4:], uint16(length&0xFFFF)) - length += 2 - binary.BigEndian.PutUint16(ret[2:], uint16(length&0xFFFF)) - return ret -} - -func (t *tls12Ticket) getHost() string { - host := t.Host - if len(t.Param) > 0 { - hosts := strings.Split(t.Param, ",") - if len(hosts) > 0 { - - host = hosts[rand.Intn(len(hosts))] - host = strings.TrimSpace(host) - } - } - if len(host) > 0 && host[len(host)-1] >= byte('0') && host[len(host)-1] <= byte('9') && len(t.Param) == 0 { - host = "" - } - return host -} - -func (t *tls12Ticket) packAuthData() (ret []byte) { - retSize := 32 - ret = make([]byte, retSize) - - now := time.Now().Unix() - binary.BigEndian.PutUint32(ret[:4], uint32(now)) - - rand.Read(ret[4 : 4+18]) - - hash := t.hmacSHA1(ret[:retSize-tools.HmacSHA1Len]) - copy(ret[retSize-tools.HmacSHA1Len:], hash) - - return -} - -func packData(buffer *bytes.Buffer, suffix []byte) { - d := []byte{0x17, 0x3, 0x3, 0, 0} - binary.BigEndian.PutUint16(d[3:5], uint16(len(suffix)&0xFFFF)) - buffer.Write(d) - buffer.Write(suffix) -} diff --git a/component/ssr/protocol/auth_aes128_md5.go b/component/ssr/protocol/auth_aes128_md5.go index fc0c41bf..08e350c4 100644 --- a/component/ssr/protocol/auth_aes128_md5.go +++ b/component/ssr/protocol/auth_aes128_md5.go @@ -1,310 +1,18 @@ package protocol -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "encoding/base64" - "encoding/binary" - "math/rand" - "strconv" - "strings" - "time" - - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/ssr/tools" - "github.com/Dreamacro/go-shadowsocks2/core" -) - -type authAES128 struct { - *Base - *recvInfo - *authData - hasSentHeader bool - packID uint32 - userKey []byte - uid [4]byte - salt string - hmac hmacMethod - hashDigest hashDigestMethod -} +import "github.com/Dreamacro/clash/component/ssr/tools" func init() { - register("auth_aes128_md5", newAuthAES128MD5) + register("auth_aes128_md5", newAuthAES128MD5, 9) } func newAuthAES128MD5(b *Base) Protocol { - return &authAES128{ - Base: b, - authData: &authData{}, - salt: "auth_aes128_md5", - hmac: tools.HmacMD5, - hashDigest: tools.MD5Sum, + a := &authAES128{ + Base: b, + authData: &authData{}, + authAES128Function: &authAES128Function{salt: "auth_aes128_md5", hmac: tools.HmacMD5, hashDigest: tools.MD5Sum}, + userData: &userData{}, } -} - -func (a *authAES128) initForConn(iv []byte) Protocol { - return &authAES128{ - Base: &Base{ - IV: iv, - Key: a.Key, - TCPMss: a.TCPMss, - Overhead: a.Overhead, - Param: a.Param, - }, - recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)}, - authData: a.authData, - packID: 1, - salt: a.salt, - hmac: a.hmac, - hashDigest: a.hashDigest, - } -} - -func (a *authAES128) GetProtocolOverhead() int { - return 9 -} - -func (a *authAES128) SetOverhead(overhead int) { - a.Overhead = overhead -} - -func (a *authAES128) Decode(b []byte) ([]byte, int, error) { - a.buffer.Reset() - bSize := len(b) - readSize := 0 - key := pool.Get(len(a.userKey) + 4) - defer pool.Put(key) - copy(key, a.userKey) - for bSize > 4 { - binary.LittleEndian.PutUint32(key[len(key)-4:], a.recvID) - - h := a.hmac(key, b[:2]) - if !bytes.Equal(h[:2], b[2:4]) { - return nil, 0, errAuthAES128IncorrectMAC - } - - length := int(binary.LittleEndian.Uint16(b[:2])) - if length >= 8192 || length < 8 { - return nil, 0, errAuthAES128DataLengthError - } - if length > bSize { - break - } - - h = a.hmac(key, b[:length-4]) - if !bytes.Equal(h[:4], b[length-4:length]) { - return nil, 0, errAuthAES128IncorrectChecksum - } - - a.recvID++ - pos := int(b[4]) - if pos < 255 { - pos += 4 - } else { - pos = int(binary.LittleEndian.Uint16(b[5:7])) + 4 - } - - if pos > length-4 { - return nil, 0, errAuthAES128PositionTooLarge - } - a.buffer.Write(b[pos : length-4]) - b = b[length:] - bSize -= length - readSize += length - } - return a.buffer.Bytes(), readSize, nil -} - -func (a *authAES128) Encode(b []byte) ([]byte, error) { - a.buffer.Reset() - bSize := len(b) - offset := 0 - if bSize > 0 && !a.hasSentHeader { - authSize := bSize - if authSize > 1200 { - authSize = 1200 - } - a.hasSentHeader = true - a.buffer.Write(a.packAuthData(b[:authSize])) - bSize -= authSize - offset += authSize - } - const blockSize = 4096 - for bSize > blockSize { - packSize, randSize := a.packedDataSize(b[offset : offset+blockSize]) - pack := pool.Get(packSize) - a.packData(b[offset:offset+blockSize], pack, randSize) - a.buffer.Write(pack) - pool.Put(pack) - bSize -= blockSize - offset += blockSize - } - if bSize > 0 { - packSize, randSize := a.packedDataSize(b[offset:]) - pack := pool.Get(packSize) - a.packData(b[offset:], pack, randSize) - a.buffer.Write(pack) - pool.Put(pack) - } - return a.buffer.Bytes(), nil -} - -func (a *authAES128) DecodePacket(b []byte) ([]byte, int, error) { - bSize := len(b) - h := a.hmac(a.Key, b[:bSize-4]) - if !bytes.Equal(h[:4], b[bSize-4:]) { - return nil, 0, errAuthAES128IncorrectMAC - } - return b[:bSize-4], bSize - 4, nil -} - -func (a *authAES128) EncodePacket(b []byte) ([]byte, error) { - a.initUserKeyAndID() - - var buf bytes.Buffer - buf.Write(b) - buf.Write(a.uid[:]) - h := a.hmac(a.userKey, buf.Bytes()) - buf.Write(h[:4]) - return buf.Bytes(), nil -} - -func (a *authAES128) initUserKeyAndID() { - if a.userKey == nil { - params := strings.Split(a.Param, ":") - if len(params) >= 2 { - if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { - binary.LittleEndian.PutUint32(a.uid[:], uint32(userID)) - a.userKey = a.hashDigest([]byte(params[1])) - } - } - - if a.userKey == nil { - rand.Read(a.uid[:]) - a.userKey = make([]byte, len(a.Key)) - copy(a.userKey, a.Key) - } - } -} - -func (a *authAES128) packedDataSize(data []byte) (packSize, randSize int) { - dataSize := len(data) - randSize = 1 - if dataSize <= 1200 { - if a.packID > 4 { - randSize += rand.Intn(32) - } else { - if dataSize > 900 { - randSize += rand.Intn(128) - } else { - randSize += rand.Intn(512) - } - } - } - packSize = randSize + dataSize + 8 - return -} - -func (a *authAES128) packData(data, ret []byte, randSize int) { - dataSize := len(data) - retSize := len(ret) - // 0~1, ret_size - binary.LittleEndian.PutUint16(ret[0:], uint16(retSize&0xFFFF)) - // 2~3, hmac - key := pool.Get(len(a.userKey) + 4) - defer pool.Put(key) - copy(key, a.userKey) - binary.LittleEndian.PutUint32(key[len(key)-4:], a.packID) - h := a.hmac(key, ret[:2]) - copy(ret[2:4], h[:2]) - // 4~rand_size+4, rand number - rand.Read(ret[4 : 4+randSize]) - // 4, rand_size - if randSize < 128 { - ret[4] = byte(randSize & 0xFF) - } else { - // 4, magic number 0xFF - ret[4] = 0xFF - // 5~6, rand_size - binary.LittleEndian.PutUint16(ret[5:], uint16(randSize&0xFFFF)) - } - // rand_size+4~ret_size-4, data - if dataSize > 0 { - copy(ret[randSize+4:], data) - } - a.packID++ - h = a.hmac(key, ret[:retSize-4]) - copy(ret[retSize-4:], h[:4]) -} - -func (a *authAES128) packAuthData(data []byte) (ret []byte) { - dataSize := len(data) - var randSize int - - if dataSize > 400 { - randSize = rand.Intn(512) - } else { - randSize = rand.Intn(1024) - } - - dataOffset := randSize + 16 + 4 + 4 + 7 - retSize := dataOffset + dataSize + 4 - ret = make([]byte, retSize) - encrypt := make([]byte, 24) - key := make([]byte, len(a.IV)+len(a.Key)) - copy(key, a.IV) - copy(key[len(a.IV):], a.Key) - - rand.Read(ret[dataOffset-randSize:]) - a.mutex.Lock() - defer a.mutex.Unlock() - a.connectionID++ - if a.connectionID > 0xFF000000 { - a.clientID = nil - } - if len(a.clientID) == 0 { - a.clientID = make([]byte, 8) - rand.Read(a.clientID) - b := make([]byte, 4) - rand.Read(b) - a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF - } - copy(encrypt[4:], a.clientID) - binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) - - now := time.Now().Unix() - binary.LittleEndian.PutUint32(encrypt[:4], uint32(now)) - - binary.LittleEndian.PutUint16(encrypt[12:], uint16(retSize&0xFFFF)) - binary.LittleEndian.PutUint16(encrypt[14:], uint16(randSize&0xFFFF)) - - a.initUserKeyAndID() - - aesCipherKey := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+a.salt, 16) - block, err := aes.NewCipher(aesCipherKey) - if err != nil { - return nil - } - encryptData := make([]byte, 16) - iv := make([]byte, aes.BlockSize) - cbc := cipher.NewCBCEncrypter(block, iv) - cbc.CryptBlocks(encryptData, encrypt[:16]) - copy(encrypt[:4], a.uid[:]) - copy(encrypt[4:4+16], encryptData) - - h := a.hmac(key, encrypt[:20]) - copy(encrypt[20:], h[:4]) - - rand.Read(ret[:1]) - h = a.hmac(key, ret[:1]) - copy(ret[1:], h[:7-1]) - - copy(ret[7:], encrypt) - copy(ret[dataOffset:], data) - - h = a.hmac(a.userKey, ret[:retSize-4]) - copy(ret[retSize-4:], h[:4]) - - return + a.initUserData() + return a } diff --git a/component/ssr/protocol/auth_aes128_sha1.go b/component/ssr/protocol/auth_aes128_sha1.go index d549b588..50383456 100644 --- a/component/ssr/protocol/auth_aes128_sha1.go +++ b/component/ssr/protocol/auth_aes128_sha1.go @@ -2,21 +2,274 @@ package protocol import ( "bytes" + "encoding/binary" + "math" + "math/rand" + "net" + "strconv" + "strings" + "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/ssr/tools" + "github.com/Dreamacro/clash/log" ) +type hmacMethod func(key, data []byte) []byte +type hashDigestMethod func([]byte) []byte + func init() { - register("auth_aes128_sha1", newAuthAES128SHA1) + register("auth_aes128_sha1", newAuthAES128SHA1, 9) +} + +type authAES128Function struct { + salt string + hmac hmacMethod + hashDigest hashDigestMethod +} + +type authAES128 struct { + *Base + *authData + *authAES128Function + *userData + iv []byte + hasSentHeader bool + rawTrans bool + packID uint32 + recvID uint32 } func newAuthAES128SHA1(b *Base) Protocol { - return &authAES128{ - Base: b, - recvInfo: &recvInfo{buffer: new(bytes.Buffer)}, - authData: &authData{}, - salt: "auth_aes128_sha1", - hmac: tools.HmacSHA1, - hashDigest: tools.SHA1Sum, + a := &authAES128{ + Base: b, + authData: &authData{}, + authAES128Function: &authAES128Function{salt: "auth_aes128_sha1", hmac: tools.HmacSHA1, hashDigest: tools.SHA1Sum}, + userData: &userData{}, + } + a.initUserData() + return a +} + +func (a *authAES128) initUserData() { + params := strings.Split(a.Param, ":") + if len(params) > 1 { + if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { + binary.LittleEndian.PutUint32(a.userID[:], uint32(userID)) + a.userKey = a.hashDigest([]byte(params[1])) + } else { + log.Warnln("Wrong protocol-param for %s, only digits are expected before ':'", a.salt) + } + } + if len(a.userKey) == 0 { + a.userKey = a.Key + rand.Read(a.userID[:]) } } + +func (a *authAES128) StreamConn(c net.Conn, iv []byte) net.Conn { + p := &authAES128{ + Base: a.Base, + authData: a.next(), + authAES128Function: a.authAES128Function, + userData: a.userData, + packID: 1, + recvID: 1, + } + p.iv = iv + return &Conn{Conn: c, Protocol: p} +} + +func (a *authAES128) PacketConn(c net.PacketConn) net.PacketConn { + p := &authAES128{ + Base: a.Base, + authAES128Function: a.authAES128Function, + userData: a.userData, + } + return &PacketConn{PacketConn: c, Protocol: p} +} + +func (a *authAES128) Decode(dst, src *bytes.Buffer) error { + if a.rawTrans { + dst.ReadFrom(src) + return nil + } + for src.Len() > 4 { + macKey := pool.Get(len(a.userKey) + 4) + defer pool.Put(macKey) + copy(macKey, a.userKey) + binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.recvID) + if !bytes.Equal(a.hmac(macKey, src.Bytes()[:2])[:2], src.Bytes()[2:4]) { + src.Reset() + return errAuthAES128MACError + } + + length := int(binary.LittleEndian.Uint16(src.Bytes()[:2])) + if length >= 8192 || length < 7 { + a.rawTrans = true + src.Reset() + return errAuthAES128LengthError + } + if length > src.Len() { + break + } + + if !bytes.Equal(a.hmac(macKey, src.Bytes()[:length-4])[:4], src.Bytes()[length-4:length]) { + a.rawTrans = true + src.Reset() + return errAuthAES128ChksumError + } + + a.recvID++ + + pos := int(src.Bytes()[4]) + if pos < 255 { + pos += 4 + } else { + pos = int(binary.LittleEndian.Uint16(src.Bytes()[5:7])) + 4 + } + dst.Write(src.Bytes()[pos : length-4]) + src.Next(length) + } + return nil +} + +func (a *authAES128) Encode(buf *bytes.Buffer, b []byte) error { + fullDataLength := len(b) + if !a.hasSentHeader { + dataLength := getDataLength(b) + a.packAuthData(buf, b[:dataLength]) + b = b[dataLength:] + a.hasSentHeader = true + } + for len(b) > 8100 { + a.packData(buf, b[:8100], fullDataLength) + b = b[8100:] + } + if len(b) > 0 { + a.packData(buf, b, fullDataLength) + } + return nil +} + +func (a *authAES128) DecodePacket(b []byte) ([]byte, error) { + if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) { + return nil, errAuthAES128ChksumError + } + return b[:len(b)-4], nil +} + +func (a *authAES128) EncodePacket(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + buf.Write(a.userID[:]) + buf.Write(a.hmac(a.userKey, buf.Bytes())[:4]) + return nil +} + +func (a *authAES128) packData(poolBuf *bytes.Buffer, data []byte, fullDataLength int) { + dataLength := len(data) + randDataLength := a.getRandDataLengthForPackData(dataLength, fullDataLength) + /* + 2: uint16 LittleEndian packedDataLength + 2: hmac of packedDataLength + 3: maxRandDataLengthPrefix (min:1) + 4: hmac of packedData except the last 4 bytes + */ + packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4 + if randDataLength < 128 { + packedDataLength -= 2 + } + + macKey := pool.Get(len(a.userKey) + 4) + defer pool.Put(macKey) + copy(macKey, a.userKey) + binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID) + a.packID++ + + binary.Write(poolBuf, binary.LittleEndian, uint16(packedDataLength)) + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-2:])[:2]) + a.packRandData(poolBuf, randDataLength) + poolBuf.Write(data) + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:])[:4]) +} + +func trapezoidRandom(max int, d float64) int { + base := rand.Float64() + if d-0 > 1e-6 { + a := 1 - d + base = (math.Sqrt(a*a+4*d*base) - a) / (2 * d) + } + return int(base * float64(max)) +} + +func (a *authAES128) getRandDataLengthForPackData(dataLength, fullDataLength int) int { + if fullDataLength >= 32*1024-a.Overhead { + return 0 + } + // 1460: tcp_mss + revLength := 1460 - dataLength - 9 + if revLength == 0 { + return 0 + } + if revLength < 0 { + if revLength > -1460 { + return trapezoidRandom(revLength+1460, -0.3) + } + return rand.Intn(32) + } + if dataLength > 900 { + return rand.Intn(revLength) + } + return trapezoidRandom(revLength, -0.3) +} + +func (a *authAES128) packAuthData(poolBuf *bytes.Buffer, data []byte) { + if len(data) == 0 { + return + } + dataLength := len(data) + randDataLength := a.getRandDataLengthForPackAuthData(dataLength) + /* + 7: checkHead(1) and hmac of checkHead(6) + 4: userID + 16: encrypted data of authdata(12), uint16 BigEndian packedDataLength(2) and uint16 BigEndian randDataLength(2) + 4: hmac of userID and encrypted data + 4: hmac of packedAuthData except the last 4 bytes + */ + packedAuthDataLength := 7 + 4 + 16 + 4 + randDataLength + dataLength + 4 + + macKey := pool.Get(len(a.iv) + len(a.Key)) + defer pool.Put(macKey) + copy(macKey, a.iv) + copy(macKey[len(a.iv):], a.Key) + + poolBuf.WriteByte(byte(rand.Intn(256))) + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes())[:6]) + poolBuf.Write(a.userID[:]) + err := a.authData.putEncryptedData(poolBuf, a.userKey, [2]int{packedAuthDataLength, randDataLength}, a.salt) + if err != nil { + poolBuf.Reset() + return + } + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[7:])[:4]) + tools.AppendRandBytes(poolBuf, randDataLength) + poolBuf.Write(data) + poolBuf.Write(a.hmac(a.userKey, poolBuf.Bytes())[:4]) +} + +func (a *authAES128) getRandDataLengthForPackAuthData(size int) int { + if size > 400 { + return rand.Intn(512) + } + return rand.Intn(1024) +} + +func (a *authAES128) packRandData(poolBuf *bytes.Buffer, size int) { + if size < 128 { + poolBuf.WriteByte(byte(size + 1)) + tools.AppendRandBytes(poolBuf, size) + return + } + poolBuf.WriteByte(255) + binary.Write(poolBuf, binary.LittleEndian, uint16(size+3)) + tools.AppendRandBytes(poolBuf, size) +} diff --git a/component/ssr/protocol/auth_chain_a.go b/component/ssr/protocol/auth_chain_a.go index 2028ba79..fdad8afe 100644 --- a/component/ssr/protocol/auth_chain_a.go +++ b/component/ssr/protocol/auth_chain_a.go @@ -2,430 +2,308 @@ package protocol import ( "bytes" - "crypto/aes" "crypto/cipher" + "crypto/rand" "crypto/rc4" "encoding/base64" "encoding/binary" - "math/rand" + "net" "strconv" "strings" - "time" "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/ssr/tools" + "github.com/Dreamacro/clash/log" "github.com/Dreamacro/go-shadowsocks2/core" ) -type authChain struct { - *Base - *recvInfo - *authData - randomClient shift128PlusContext - randomServer shift128PlusContext - enc cipher.Stream - dec cipher.Stream - headerSent bool - lastClientHash []byte - lastServerHash []byte - userKey []byte - uid [4]byte - salt string - hmac hmacMethod - hashDigest hashDigestMethod - rnd rndMethod - dataSizeList []int - dataSizeList2 []int - chunkID uint32 +func init() { + register("auth_chain_a", newAuthChainA, 4) } -func init() { - register("auth_chain_a", newAuthChainA) +type randDataLengthMethod func(int, []byte, *tools.XorShift128Plus) int + +type authChainA struct { + *Base + *authData + *userData + iv []byte + salt string + hasSentHeader bool + rawTrans bool + lastClientHash []byte + lastServerHash []byte + encrypter cipher.Stream + decrypter cipher.Stream + randomClient tools.XorShift128Plus + randomServer tools.XorShift128Plus + randDataLength randDataLengthMethod + packID uint32 + recvID uint32 } func newAuthChainA(b *Base) Protocol { - return &authChain{ - Base: b, - authData: &authData{}, - salt: "auth_chain_a", - hmac: tools.HmacMD5, - hashDigest: tools.SHA1Sum, - rnd: authChainAGetRandLen, + a := &authChainA{ + Base: b, + authData: &authData{}, + userData: &userData{}, + salt: "auth_chain_a", } + a.initUserData() + return a } -func (a *authChain) initForConn(iv []byte) Protocol { - r := &authChain{ - Base: &Base{ - IV: iv, - Key: a.Key, - TCPMss: a.TCPMss, - Overhead: a.Overhead, - Param: a.Param, - }, - recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)}, - authData: a.authData, - salt: a.salt, - hmac: a.hmac, - hashDigest: a.hashDigest, - rnd: a.rnd, - } - if r.salt == "auth_chain_b" { - initDataSize(r) - } - return r -} - -func (a *authChain) GetProtocolOverhead() int { - return 4 -} - -func (a *authChain) SetOverhead(overhead int) { - a.Overhead = overhead -} - -func (a *authChain) Decode(b []byte) ([]byte, int, error) { - a.buffer.Reset() - key := pool.Get(len(a.userKey) + 4) - defer pool.Put(key) - readSize := 0 - copy(key, a.userKey) - for len(b) > 4 { - binary.LittleEndian.PutUint32(key[len(a.userKey):], a.recvID) - dataLen := (int)((uint(b[1]^a.lastServerHash[15]) << 8) + uint(b[0]^a.lastServerHash[14])) - randLen := a.getServerRandLen(dataLen, a.Overhead) - length := randLen + dataLen - if length >= 4096 { - return nil, 0, errAuthChainDataLengthError +func (a *authChainA) initUserData() { + params := strings.Split(a.Param, ":") + if len(params) > 1 { + if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { + binary.LittleEndian.PutUint32(a.userID[:], uint32(userID)) + a.userKey = []byte(params[1]) + } else { + log.Warnln("Wrong protocol-param for %s, only digits are expected before ':'", a.salt) } - length += 4 - if length > len(b) { + } + if len(a.userKey) == 0 { + a.userKey = a.Key + rand.Read(a.userID[:]) + } +} + +func (a *authChainA) StreamConn(c net.Conn, iv []byte) net.Conn { + p := &authChainA{ + Base: a.Base, + authData: a.next(), + userData: a.userData, + salt: a.salt, + packID: 1, + recvID: 1, + } + p.iv = iv + p.randDataLength = p.getRandLength + return &Conn{Conn: c, Protocol: p} +} + +func (a *authChainA) PacketConn(c net.PacketConn) net.PacketConn { + p := &authChainA{ + Base: a.Base, + salt: a.salt, + userData: a.userData, + } + return &PacketConn{PacketConn: c, Protocol: p} +} + +func (a *authChainA) Decode(dst, src *bytes.Buffer) error { + if a.rawTrans { + dst.ReadFrom(src) + return nil + } + for src.Len() > 4 { + macKey := pool.Get(len(a.userKey) + 4) + defer pool.Put(macKey) + copy(macKey, a.userKey) + binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.recvID) + + dataLength := int(binary.LittleEndian.Uint16(src.Bytes()[:2]) ^ binary.LittleEndian.Uint16(a.lastServerHash[14:16])) + randDataLength := a.randDataLength(dataLength, a.lastServerHash, &a.randomServer) + length := dataLength + randDataLength + + if length >= 4096 { + a.rawTrans = true + src.Reset() + return errAuthChainLengthError + } + + if 4+length > src.Len() { break } - hash := a.hmac(key, b[:length-2]) - if !bytes.Equal(hash[:2], b[length-2:length]) { - return nil, 0, errAuthChainHMACError + serverHash := tools.HmacMD5(macKey, src.Bytes()[:length+2]) + if !bytes.Equal(serverHash[:2], src.Bytes()[length+2:length+4]) { + a.rawTrans = true + src.Reset() + return errAuthChainChksumError } - var dataPos int - if dataLen > 0 && randLen > 0 { - dataPos = 2 + getRandStartPos(&a.randomServer, randLen) - } else { - dataPos = 2 + a.lastServerHash = serverHash + + pos := 2 + if dataLength > 0 && randDataLength > 0 { + pos += getRandStartPos(randDataLength, &a.randomServer) } - d := pool.Get(dataLen) - a.dec.XORKeyStream(d, b[dataPos:dataPos+dataLen]) - a.buffer.Write(d) - pool.Put(d) + wantedData := src.Bytes()[pos : pos+dataLength] + a.decrypter.XORKeyStream(wantedData, wantedData) if a.recvID == 1 { - a.TCPMss = int(binary.LittleEndian.Uint16(a.buffer.Next(2))) + dst.Write(wantedData[2:]) + } else { + dst.Write(wantedData) } - a.lastServerHash = hash a.recvID++ - b = b[length:] - readSize += length + src.Next(length + 4) } - return a.buffer.Bytes(), readSize, nil + return nil } -func (a *authChain) Encode(b []byte) ([]byte, error) { - a.buffer.Reset() - bSize := len(b) - offset := 0 - if bSize > 0 && !a.headerSent { - headSize := 1200 - if headSize > bSize { - headSize = bSize - } - a.buffer.Write(a.packAuthData(b[:headSize])) - offset += headSize - bSize -= headSize - a.headerSent = true +func (a *authChainA) Encode(buf *bytes.Buffer, b []byte) error { + if !a.hasSentHeader { + dataLength := getDataLength(b) + a.packAuthData(buf, b[:dataLength]) + b = b[dataLength:] + a.hasSentHeader = true } - var unitSize = a.TCPMss - a.Overhead - for bSize > unitSize { - dataLen, randLength := a.packedDataLen(b[offset : offset+unitSize]) - d := pool.Get(dataLen) - a.packData(d, b[offset:offset+unitSize], randLength) - a.buffer.Write(d) - pool.Put(d) - bSize -= unitSize - offset += unitSize + for len(b) > 2800 { + a.packData(buf, b[:2800]) + b = b[2800:] } - if bSize > 0 { - dataLen, randLength := a.packedDataLen(b[offset:]) - d := pool.Get(dataLen) - a.packData(d, b[offset:], randLength) - a.buffer.Write(d) - pool.Put(d) + if len(b) > 0 { + a.packData(buf, b) } - return a.buffer.Bytes(), nil + return nil } -func (a *authChain) DecodePacket(b []byte) ([]byte, int, error) { - bSize := len(b) - if bSize < 9 { - return nil, 0, errAuthChainDataLengthError +func (a *authChainA) DecodePacket(b []byte) ([]byte, error) { + if len(b) < 9 { + return nil, errAuthChainLengthError } - h := a.hmac(a.userKey, b[:bSize-1]) - if h[0] != b[bSize-1] { - return nil, 0, errAuthChainHMACError + if !bytes.Equal(tools.HmacMD5(a.userKey, b[:len(b)-1])[:1], b[len(b)-1:]) { + return nil, errAuthChainChksumError } - hash := a.hmac(a.Key, b[bSize-8:bSize-1]) - cipherKey := a.getRC4CipherKey(hash) - dec, _ := rc4.NewCipher(cipherKey) - randLength := udpGetRandLen(&a.randomServer, hash) - bSize -= 8 + randLength - dec.XORKeyStream(b, b[:bSize]) - return b, bSize, nil + md5Data := tools.HmacMD5(a.Key, b[len(b)-8:len(b)-1]) + + randDataLength := udpGetRandLength(md5Data, &a.randomServer) + + key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16) + rc4Cipher, err := rc4.NewCipher(key) + if err != nil { + return nil, err + } + wantedData := b[:len(b)-8-randDataLength] + rc4Cipher.XORKeyStream(wantedData, wantedData) + return wantedData, nil } -func (a *authChain) EncodePacket(b []byte) ([]byte, error) { - a.initUserKeyAndID() +func (a *authChainA) EncodePacket(buf *bytes.Buffer, b []byte) error { authData := pool.Get(3) defer pool.Put(authData) rand.Read(authData) - hash := a.hmac(a.Key, authData) - uid := pool.Get(4) - defer pool.Put(uid) - for i := 0; i < 4; i++ { - uid[i] = a.uid[i] ^ hash[i] - } - cipherKey := a.getRC4CipherKey(hash) - enc, _ := rc4.NewCipher(cipherKey) - var buf bytes.Buffer - enc.XORKeyStream(b, b) + md5Data := tools.HmacMD5(a.Key, authData) + + randDataLength := udpGetRandLength(md5Data, &a.randomClient) + + key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16) + rc4Cipher, err := rc4.NewCipher(key) + if err != nil { + return err + } + rc4Cipher.XORKeyStream(b, b) + buf.Write(b) - - randLength := udpGetRandLen(&a.randomClient, hash) - randBytes := pool.Get(randLength) - defer pool.Put(randBytes) - buf.Write(randBytes) - + tools.AppendRandBytes(buf, randDataLength) buf.Write(authData) - buf.Write(uid) - - h := a.hmac(a.userKey, buf.Bytes()) - buf.Write(h[:1]) - return buf.Bytes(), nil + binary.Write(buf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(md5Data[:4])) + buf.Write(tools.HmacMD5(a.userKey, buf.Bytes())[:1]) + return nil } -func (a *authChain) getRC4CipherKey(hash []byte) []byte { - base64UserKey := base64.StdEncoding.EncodeToString(a.userKey) - return a.calcRC4CipherKey(hash, base64UserKey) -} +func (a *authChainA) packAuthData(poolBuf *bytes.Buffer, data []byte) { + /* + dataLength := len(data) + 12: checkHead(4) and hmac of checkHead(8) + 4: uint32 LittleEndian uid (uid = userID ^ last client hash) + 16: encrypted data of authdata(12), uint16 LittleEndian overhead(2) and uint16 LittleEndian number zero(2) + 4: last server hash(4) + packedAuthDataLength := 12 + 4 + 16 + 4 + dataLength + */ -func (a *authChain) calcRC4CipherKey(hash []byte, base64UserKey string) []byte { - password := pool.Get(len(base64UserKey) + base64.StdEncoding.EncodedLen(16)) - defer pool.Put(password) - copy(password, base64UserKey) - base64.StdEncoding.Encode(password[len(base64UserKey):], hash[:16]) - return core.Kdf(string(password), 16) -} + macKey := pool.Get(len(a.iv) + len(a.Key)) + defer pool.Put(macKey) + copy(macKey, a.iv) + copy(macKey[len(a.iv):], a.Key) -func (a *authChain) initUserKeyAndID() { - if a.userKey == nil { - params := strings.Split(a.Param, ":") - if len(params) >= 2 { - if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { - binary.LittleEndian.PutUint32(a.uid[:], uint32(userID)) - a.userKey = []byte(params[1]) - } - } - - if a.userKey == nil { - rand.Read(a.uid[:]) - a.userKey = make([]byte, len(a.Key)) - copy(a.userKey, a.Key) - } + // check head + tools.AppendRandBytes(poolBuf, 4) + a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes()) + a.initRC4Cipher() + poolBuf.Write(a.lastClientHash[:8]) + // uid + binary.Write(poolBuf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(a.lastClientHash[8:12])) + // encrypted data + err := a.putEncryptedData(poolBuf, a.userKey, [2]int{a.Overhead, 0}, a.salt) + if err != nil { + poolBuf.Reset() + return } + // last server hash + a.lastServerHash = tools.HmacMD5(a.userKey, poolBuf.Bytes()[12:]) + poolBuf.Write(a.lastServerHash[:4]) + // packed data + a.packData(poolBuf, data) } -func (a *authChain) getClientRandLen(dataLength int, overhead int) int { - return a.rnd(dataLength, &a.randomClient, a.lastClientHash, a.dataSizeList, a.dataSizeList2, overhead) +func (a *authChainA) packData(poolBuf *bytes.Buffer, data []byte) { + a.encrypter.XORKeyStream(data, data) + + macKey := pool.Get(len(a.userKey) + 4) + defer pool.Put(macKey) + copy(macKey, a.userKey) + binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID) + a.packID++ + + length := uint16(len(data)) ^ binary.LittleEndian.Uint16(a.lastClientHash[14:16]) + + originalLength := poolBuf.Len() + binary.Write(poolBuf, binary.LittleEndian, length) + a.putMixedRandDataAndData(poolBuf, data) + a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes()[originalLength:]) + poolBuf.Write(a.lastClientHash[:2]) } -func (a *authChain) getServerRandLen(dataLength int, overhead int) int { - return a.rnd(dataLength, &a.randomServer, a.lastServerHash, a.dataSizeList, a.dataSizeList2, overhead) +func (a *authChainA) putMixedRandDataAndData(poolBuf *bytes.Buffer, data []byte) { + randDataLength := a.randDataLength(len(data), a.lastClientHash, &a.randomClient) + if len(data) == 0 { + tools.AppendRandBytes(poolBuf, randDataLength) + return + } + if randDataLength > 0 { + startPos := getRandStartPos(randDataLength, &a.randomClient) + tools.AppendRandBytes(poolBuf, startPos) + poolBuf.Write(data) + tools.AppendRandBytes(poolBuf, randDataLength-startPos) + return + } + poolBuf.Write(data) } -func (a *authChain) packedDataLen(data []byte) (chunkLength, randLength int) { - dataLength := len(data) - randLength = a.getClientRandLen(dataLength, a.Overhead) - chunkLength = randLength + dataLength + 2 + 2 - return -} - -func (a *authChain) packData(outData []byte, data []byte, randLength int) { - dataLength := len(data) - outLength := randLength + dataLength + 2 - outData[0] = byte(dataLength) ^ a.lastClientHash[14] - outData[1] = byte(dataLength>>8) ^ a.lastClientHash[15] - - { - if dataLength > 0 { - randPart1Length := getRandStartPos(&a.randomClient, randLength) - rand.Read(outData[2 : 2+randPart1Length]) - a.enc.XORKeyStream(outData[2+randPart1Length:], data) - rand.Read(outData[2+randPart1Length+dataLength : outLength]) - } else { - rand.Read(outData[2 : 2+randLength]) - } - } - - userKeyLen := uint8(len(a.userKey)) - key := pool.Get(int(userKeyLen + 4)) - defer pool.Put(key) - copy(key, a.userKey) - a.chunkID++ - binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID) - a.lastClientHash = a.hmac(key, outData[:outLength]) - copy(outData[outLength:], a.lastClientHash[:2]) -} - -const authHeadLength = 4 + 8 + 4 + 16 + 4 - -func (a *authChain) packAuthData(data []byte) (outData []byte) { - outData = make([]byte, authHeadLength, authHeadLength+1500) - a.mutex.Lock() - defer a.mutex.Unlock() - a.connectionID++ - if a.connectionID > 0xFF000000 { - a.clientID = nil - } - if len(a.clientID) == 0 { - a.clientID = make([]byte, 4) - rand.Read(a.clientID) - b := make([]byte, 4) - rand.Read(b) - a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF - } - var key = make([]byte, len(a.IV)+len(a.Key)) - copy(key, a.IV) - copy(key[len(a.IV):], a.Key) - - encrypt := make([]byte, 20) - t := time.Now().Unix() - binary.LittleEndian.PutUint32(encrypt[:4], uint32(t)) - copy(encrypt[4:8], a.clientID) - binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) - binary.LittleEndian.PutUint16(encrypt[12:], uint16(a.Overhead)) - binary.LittleEndian.PutUint16(encrypt[14:], 0) - - // first 12 bytes - { - rand.Read(outData[:4]) - a.lastClientHash = a.hmac(key, outData[:4]) - copy(outData[4:], a.lastClientHash[:8]) - } - var base64UserKey string - // uid & 16 bytes auth data - { - a.initUserKeyAndID() - uid := make([]byte, 4) - for i := 0; i < 4; i++ { - uid[i] = a.uid[i] ^ a.lastClientHash[8+i] - } - base64UserKey = base64.StdEncoding.EncodeToString(a.userKey) - aesCipherKey := core.Kdf(base64UserKey+a.salt, 16) - block, err := aes.NewCipher(aesCipherKey) - if err != nil { - return - } - encryptData := make([]byte, 16) - iv := make([]byte, aes.BlockSize) - cbc := cipher.NewCBCEncrypter(block, iv) - cbc.CryptBlocks(encryptData, encrypt[:16]) - copy(encrypt[:4], uid[:]) - copy(encrypt[4:4+16], encryptData) - } - // final HMAC - { - a.lastServerHash = a.hmac(a.userKey, encrypt[:20]) - - copy(outData[12:], encrypt) - copy(outData[12+20:], a.lastServerHash[:4]) - } - - // init cipher - cipherKey := a.calcRC4CipherKey(a.lastClientHash, base64UserKey) - a.enc, _ = rc4.NewCipher(cipherKey) - a.dec, _ = rc4.NewCipher(cipherKey) - - // data - chunkLength, randLength := a.packedDataLen(data) - if chunkLength+authHeadLength <= cap(outData) { - outData = outData[:authHeadLength+chunkLength] - } else { - newOutData := make([]byte, authHeadLength+chunkLength) - copy(newOutData, outData[:authHeadLength]) - outData = newOutData - } - a.packData(outData[authHeadLength:], data, randLength) - return -} - -func getRandStartPos(random *shift128PlusContext, randLength int) int { - if randLength > 0 { - return int(random.Next() % 8589934609 % uint64(randLength)) - } - return 0 -} - -func authChainAGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int { - if dataLength > 1440 { +func getRandStartPos(length int, random *tools.XorShift128Plus) int { + if length == 0 { return 0 } - random.InitFromBinDatalen(lastHash[:16], dataLength) - if dataLength > 1300 { + return int(random.Next()%8589934609) % length +} + +func (a *authChainA) getRandLength(length int, lastHash []byte, random *tools.XorShift128Plus) int { + if length > 1440 { + return 0 + } + random.InitFromBinAndLength(lastHash, length) + if length > 1300 { return int(random.Next() % 31) } - if dataLength > 900 { + if length > 900 { return int(random.Next() % 127) } - if dataLength > 400 { + if length > 400 { return int(random.Next() % 521) } return int(random.Next() % 1021) } -func udpGetRandLen(random *shift128PlusContext, lastHash []byte) int { - random.InitFromBin(lastHash[:16]) +func (a *authChainA) initRC4Cipher() { + key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(a.lastClientHash), 16) + a.encrypter, _ = rc4.NewCipher(key) + a.decrypter, _ = rc4.NewCipher(key) +} + +func udpGetRandLength(lastHash []byte, random *tools.XorShift128Plus) int { + random.InitFromBin(lastHash) return int(random.Next() % 127) } - -type shift128PlusContext struct { - v [2]uint64 -} - -func (ctx *shift128PlusContext) InitFromBin(bin []byte) { - var fillBin [16]byte - copy(fillBin[:], bin) - - ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8]) - ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:]) -} - -func (ctx *shift128PlusContext) InitFromBinDatalen(bin []byte, datalen int) { - var fillBin [16]byte - copy(fillBin[:], bin) - binary.LittleEndian.PutUint16(fillBin[:2], uint16(datalen)) - - ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8]) - ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:]) - - for i := 0; i < 4; i++ { - ctx.Next() - } -} - -func (ctx *shift128PlusContext) Next() uint64 { - x := ctx.v[0] - y := ctx.v[1] - ctx.v[0] = y - x ^= x << 23 - x ^= y ^ (x >> 17) ^ (y >> 26) - ctx.v[1] = x - return x + y -} diff --git a/component/ssr/protocol/auth_chain_b.go b/component/ssr/protocol/auth_chain_b.go index 4f013929..2f3f8f17 100644 --- a/component/ssr/protocol/auth_chain_b.go +++ b/component/ssr/protocol/auth_chain_b.go @@ -1,71 +1,96 @@ package protocol import ( + "net" "sort" "github.com/Dreamacro/clash/component/ssr/tools" ) func init() { - register("auth_chain_b", newAuthChainB) + register("auth_chain_b", newAuthChainB, 4) +} + +type authChainB struct { + *authChainA + dataSizeList []int + dataSizeList2 []int } func newAuthChainB(b *Base) Protocol { - return &authChain{ - Base: b, - authData: &authData{}, - salt: "auth_chain_b", - hmac: tools.HmacMD5, - hashDigest: tools.SHA1Sum, - rnd: authChainBGetRandLen, + a := &authChainB{ + authChainA: &authChainA{ + Base: b, + authData: &authData{}, + userData: &userData{}, + salt: "auth_chain_b", + }, } + a.initUserData() + return a } -func initDataSize(r *authChain) { - random := &r.randomServer - random.InitFromBin(r.Key) - len := random.Next()%8 + 4 - r.dataSizeList = make([]int, len) - for i := 0; i < int(len); i++ { - r.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440) +func (a *authChainB) StreamConn(c net.Conn, iv []byte) net.Conn { + p := &authChainB{ + authChainA: &authChainA{ + Base: a.Base, + authData: a.next(), + userData: a.userData, + salt: a.salt, + packID: 1, + recvID: 1, + }, } - sort.Ints(r.dataSizeList) - - len = random.Next()%16 + 8 - r.dataSizeList2 = make([]int, len) - for i := 0; i < int(len); i++ { - r.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440) - } - sort.Ints(r.dataSizeList2) + p.iv = iv + p.randDataLength = p.getRandLength + p.initDataSize() + return &Conn{Conn: c, Protocol: p} } -func authChainBGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int { - if dataLength > 1440 { +func (a *authChainB) initDataSize() { + a.dataSizeList = a.dataSizeList[:0] + a.dataSizeList2 = a.dataSizeList2[:0] + + a.randomServer.InitFromBin(a.Key) + length := a.randomServer.Next()%8 + 4 + for ; length > 0; length-- { + a.dataSizeList = append(a.dataSizeList, int(a.randomServer.Next()%2340%2040%1440)) + } + sort.Ints(a.dataSizeList) + + length = a.randomServer.Next()%16 + 8 + for ; length > 0; length-- { + a.dataSizeList2 = append(a.dataSizeList2, int(a.randomServer.Next()%2340%2040%1440)) + } + sort.Ints(a.dataSizeList2) +} + +func (a *authChainB) getRandLength(length int, lashHash []byte, random *tools.XorShift128Plus) int { + if length >= 1440 { return 0 } - random.InitFromBinDatalen(lastHash[:16], dataLength) - pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead }) - finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList)) - if finalPos < uint64(len(dataSizeList)) { - return dataSizeList[finalPos] - dataLength - overhead + random.InitFromBinAndLength(lashHash, length) + pos := sort.Search(len(a.dataSizeList), func(i int) bool { return a.dataSizeList[i] >= length+a.Overhead }) + finalPos := pos + int(random.Next()%uint64(len(a.dataSizeList))) + if finalPos < len(a.dataSizeList) { + return a.dataSizeList[finalPos] - length - a.Overhead } - pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead }) - finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2)) - if finalPos < uint64(len(dataSizeList2)) { - return dataSizeList2[finalPos] - dataLength - overhead + pos = sort.Search(len(a.dataSizeList2), func(i int) bool { return a.dataSizeList2[i] >= length+a.Overhead }) + finalPos = pos + int(random.Next()%uint64(len(a.dataSizeList2))) + if finalPos < len(a.dataSizeList2) { + return a.dataSizeList2[finalPos] - length - a.Overhead } - if finalPos < uint64(pos+len(dataSizeList2)-1) { + if finalPos < pos+len(a.dataSizeList2)-1 { return 0 } - - if dataLength > 1300 { + if length > 1300 { return int(random.Next() % 31) } - if dataLength > 900 { + if length > 900 { return int(random.Next() % 127) } - if dataLength > 400 { + if length > 400 { return int(random.Next() % 521) } return int(random.Next() % 1021) diff --git a/component/ssr/protocol/auth_sha1_v4.go b/component/ssr/protocol/auth_sha1_v4.go index 0cff1c86..0f24c360 100644 --- a/component/ssr/protocol/auth_sha1_v4.go +++ b/component/ssr/protocol/auth_sha1_v4.go @@ -6,248 +6,177 @@ import ( "hash/adler32" "hash/crc32" "math/rand" - "time" + "net" "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/ssr/tools" ) +func init() { + register("auth_sha1_v4", newAuthSHA1V4, 7) +} + type authSHA1V4 struct { *Base *authData - headerSent bool - buffer bytes.Buffer -} - -func init() { - register("auth_sha1_v4", newAuthSHA1V4) + iv []byte + hasSentHeader bool + rawTrans bool } func newAuthSHA1V4(b *Base) Protocol { return &authSHA1V4{Base: b, authData: &authData{}} } -func (a *authSHA1V4) initForConn(iv []byte) Protocol { - return &authSHA1V4{ - Base: &Base{ - IV: iv, - Key: a.Key, - TCPMss: a.TCPMss, - Overhead: a.Overhead, - Param: a.Param, - }, - authData: a.authData, +func (a *authSHA1V4) StreamConn(c net.Conn, iv []byte) net.Conn { + p := &authSHA1V4{Base: a.Base, authData: a.next()} + p.iv = iv + return &Conn{Conn: c, Protocol: p} +} + +func (a *authSHA1V4) PacketConn(c net.PacketConn) net.PacketConn { + return c +} + +func (a *authSHA1V4) Decode(dst, src *bytes.Buffer) error { + if a.rawTrans { + dst.ReadFrom(src) + return nil } -} - -func (a *authSHA1V4) GetProtocolOverhead() int { - return 7 -} - -func (a *authSHA1V4) SetOverhead(overhead int) { - a.Overhead = overhead -} - -func (a *authSHA1V4) Decode(b []byte) ([]byte, int, error) { - a.buffer.Reset() - bSize := len(b) - originalSize := bSize - for bSize > 4 { - crc := crc32.ChecksumIEEE(b[:2]) & 0xFFFF - if binary.LittleEndian.Uint16(b[2:4]) != uint16(crc) { - return nil, 0, errAuthSHA1v4CRC32Error + for src.Len() > 4 { + if uint16(crc32.ChecksumIEEE(src.Bytes()[:2])&0xffff) != binary.LittleEndian.Uint16(src.Bytes()[2:4]) { + src.Reset() + return errAuthSHA1V4CRC32Error } - length := int(binary.BigEndian.Uint16(b[:2])) - if length >= 8192 || length < 8 { - return nil, 0, errAuthSHA1v4DataLengthError + + length := int(binary.BigEndian.Uint16(src.Bytes()[:2])) + if length >= 8192 || length < 7 { + a.rawTrans = true + src.Reset() + return errAuthSHA1V4LengthError } - if length > bSize { + if length > src.Len() { break } - if adler32.Checksum(b[:length-4]) == binary.LittleEndian.Uint32(b[length-4:]) { - pos := int(b[4]) - if pos != 0xFF { - pos += 4 - } else { - pos = int(binary.BigEndian.Uint16(b[5:5+2])) + 4 - } - retSize := length - pos - 4 - a.buffer.Write(b[pos : pos+retSize]) - bSize -= length - b = b[length:] + if adler32.Checksum(src.Bytes()[:length-4]) != binary.LittleEndian.Uint32(src.Bytes()[length-4:length]) { + a.rawTrans = true + src.Reset() + return errAuthSHA1V4Adler32Error + } + + pos := int(src.Bytes()[4]) + if pos < 255 { + pos += 4 } else { - return nil, 0, errAuthSHA1v4IncorrectChecksum + pos = int(binary.BigEndian.Uint16(src.Bytes()[5:7])) + 4 } + dst.Write(src.Bytes()[pos : length-4]) + src.Next(length) } - return a.buffer.Bytes(), originalSize - bSize, nil + return nil } -func (a *authSHA1V4) Encode(b []byte) ([]byte, error) { - a.buffer.Reset() - bSize := len(b) - offset := 0 - if !a.headerSent && bSize > 0 { - headSize := getHeadSize(b, 30) - if headSize > bSize { - headSize = bSize - } - a.buffer.Write(a.packAuthData(b[:headSize])) - offset += headSize - bSize -= headSize - a.headerSent = true +func (a *authSHA1V4) Encode(buf *bytes.Buffer, b []byte) error { + if !a.hasSentHeader { + dataLength := getDataLength(b) + + a.packAuthData(buf, b[:dataLength]) + b = b[dataLength:] + + a.hasSentHeader = true } - const blockSize = 4096 - for bSize > blockSize { - packSize, randSize := a.packedDataSize(b[offset : offset+blockSize]) - pack := pool.Get(packSize) - a.packData(b[offset:offset+blockSize], pack, randSize) - a.buffer.Write(pack) - pool.Put(pack) - offset += blockSize - bSize -= blockSize + for len(b) > 8100 { + a.packData(buf, b[:8100]) + b = b[8100:] } - if bSize > 0 { - packSize, randSize := a.packedDataSize(b[offset:]) - pack := pool.Get(packSize) - a.packData(b[offset:], pack, randSize) - a.buffer.Write(pack) - pool.Put(pack) + if len(b) > 0 { + a.packData(buf, b) } - return a.buffer.Bytes(), nil + + return nil } -func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, int, error) { - return b, len(b), nil +func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, error) { return b, nil } + +func (a *authSHA1V4) EncodePacket(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + return nil } -func (a *authSHA1V4) EncodePacket(b []byte) ([]byte, error) { - return b, nil +func (a *authSHA1V4) packData(poolBuf *bytes.Buffer, data []byte) { + dataLength := len(data) + randDataLength := a.getRandDataLength(dataLength) + /* + 2: uint16 BigEndian packedDataLength + 2: uint16 LittleEndian crc32Data & 0xffff + 3: maxRandDataLengthPrefix (min:1) + 4: adler32Data + */ + packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4 + if randDataLength < 128 { + packedDataLength -= 2 + } + + binary.Write(poolBuf, binary.BigEndian, uint16(packedDataLength)) + binary.Write(poolBuf, binary.LittleEndian, uint16(crc32.ChecksumIEEE(poolBuf.Bytes()[poolBuf.Len()-2:])&0xffff)) + a.packRandData(poolBuf, randDataLength) + poolBuf.Write(data) + binary.Write(poolBuf, binary.LittleEndian, adler32.Checksum(poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:])) } -func (a *authSHA1V4) packedDataSize(data []byte) (packSize, randSize int) { - dataSize := len(data) - randSize = 1 - if dataSize <= 1300 { - if dataSize > 400 { - randSize += rand.Intn(128) - } else { - randSize += rand.Intn(1024) - } +func (a *authSHA1V4) packAuthData(poolBuf *bytes.Buffer, data []byte) { + dataLength := len(data) + randDataLength := a.getRandDataLength(12 + dataLength) + /* + 2: uint16 BigEndian packedAuthDataLength + 4: uint32 LittleEndian crc32Data + 3: maxRandDataLengthPrefix (min: 1) + 12: authDataLength + 10: hmacSHA1DataLength + */ + packedAuthDataLength := 2 + 4 + 3 + randDataLength + 12 + dataLength + 10 + if randDataLength < 128 { + packedAuthDataLength -= 2 } - packSize = randSize + dataSize + 8 - return -} -func (a *authSHA1V4) packData(data, ret []byte, randSize int) { - dataSize := len(data) - retSize := len(ret) - // 0~1, ret size - binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF)) - // 2~3, crc of ret size - crc := crc32.ChecksumIEEE(ret[:2]) & 0xFFFF - binary.LittleEndian.PutUint16(ret[2:4], uint16(crc)) - // 4, rand size - if randSize < 128 { - ret[4] = uint8(randSize & 0xFF) - } else { - ret[4] = uint8(0xFF) - binary.BigEndian.PutUint16(ret[5:7], uint16(randSize&0xFFFF)) - } - // (rand size+4)~(ret size-4), data - if dataSize > 0 { - copy(ret[randSize+4:], data) - } - // (ret size-4)~end, adler32 of full data - adler := adler32.Checksum(ret[:retSize-4]) - binary.LittleEndian.PutUint32(ret[retSize-4:], adler) -} - -func (a *authSHA1V4) packAuthData(data []byte) (ret []byte) { - dataSize := len(data) - randSize := 1 - if dataSize <= 1300 { - if dataSize > 400 { - randSize += rand.Intn(128) - } else { - randSize += rand.Intn(1024) - } - } - dataOffset := randSize + 4 + 2 - retSize := dataOffset + dataSize + 12 + tools.HmacSHA1Len - ret = make([]byte, retSize) - a.mutex.Lock() - defer a.mutex.Unlock() - a.connectionID++ - if a.connectionID > 0xFF000000 { - a.clientID = nil - } - if len(a.clientID) == 0 { - a.clientID = make([]byte, 8) - rand.Read(a.clientID) - b := make([]byte, 4) - rand.Read(b) - a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF - } - // 0~1, ret size - binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF)) - - // 2~6, crc of (ret size+salt+key) salt := []byte("auth_sha1_v4") - crcData := make([]byte, len(salt)+len(a.Key)+2) - copy(crcData[:2], ret[:2]) + crcData := pool.Get(len(salt) + len(a.Key) + 2) + defer pool.Put(crcData) + binary.BigEndian.PutUint16(crcData, uint16(packedAuthDataLength)) copy(crcData[2:], salt) copy(crcData[2+len(salt):], a.Key) - crc := crc32.ChecksumIEEE(crcData) & 0xFFFFFFFF - // 2~6, crc of (ret size+salt+key) - binary.LittleEndian.PutUint32(ret[2:], crc) - // 6~(rand size+6), rand numbers - rand.Read(ret[dataOffset-randSize : dataOffset]) - // 6, rand size - if randSize < 128 { - ret[6] = byte(randSize & 0xFF) - } else { - // 6, magic number 0xFF - ret[6] = 0xFF - // 7~8, rand size - binary.BigEndian.PutUint16(ret[7:9], uint16(randSize&0xFFFF)) - } - // rand size+6~(rand size+10), time stamp - now := time.Now().Unix() - binary.LittleEndian.PutUint32(ret[dataOffset:dataOffset+4], uint32(now)) - // rand size+10~(rand size+14), client ID - copy(ret[dataOffset+4:dataOffset+4+4], a.clientID[:4]) - // rand size+14~(rand size+18), connection ID - binary.LittleEndian.PutUint32(ret[dataOffset+8:dataOffset+8+4], a.connectionID) - // rand size+18~(rand size+18)+data length, data - copy(ret[dataOffset+12:], data) - key := make([]byte, len(a.IV)+len(a.Key)) - copy(key, a.IV) - copy(key[len(a.IV):], a.Key) + key := pool.Get(len(a.iv) + len(a.Key)) + defer pool.Put(key) + copy(key, a.iv) + copy(key[len(a.iv):], a.Key) - h := tools.HmacSHA1(key, ret[:retSize-tools.HmacSHA1Len]) - // (ret size-10)~(ret size)/(rand size)+18+data length~end, hmac - copy(ret[retSize-tools.HmacSHA1Len:], h[:tools.HmacSHA1Len]) - return ret + poolBuf.Write(crcData[:2]) + binary.Write(poolBuf, binary.LittleEndian, crc32.ChecksumIEEE(crcData)) + a.packRandData(poolBuf, randDataLength) + a.putAuthData(poolBuf) + poolBuf.Write(data) + poolBuf.Write(tools.HmacSHA1(key, poolBuf.Bytes()[poolBuf.Len()-packedAuthDataLength+10:])[:10]) } -func getHeadSize(data []byte, defaultValue int) int { - if data == nil || len(data) < 2 { - return defaultValue +func (a *authSHA1V4) packRandData(poolBuf *bytes.Buffer, size int) { + if size < 128 { + poolBuf.WriteByte(byte(size + 1)) + tools.AppendRandBytes(poolBuf, size) + return } - headType := data[0] & 0x07 - switch headType { - case 1: - // IPv4 1+4+2 - return 7 - case 4: - // IPv6 1+16+2 - return 19 - case 3: - // domain name, variant length - return 4 + int(data[1]) - } - - return defaultValue + poolBuf.WriteByte(255) + binary.Write(poolBuf, binary.BigEndian, uint16(size+3)) + tools.AppendRandBytes(poolBuf, size) +} + +func (a *authSHA1V4) getRandDataLength(size int) int { + if size > 1200 { + return 0 + } + if size > 400 { + return rand.Intn(256) + } + return rand.Intn(512) } diff --git a/component/ssr/protocol/base.go b/component/ssr/protocol/base.go index ec7aad5d..ed8ac18f 100644 --- a/component/ssr/protocol/base.go +++ b/component/ssr/protocol/base.go @@ -1,10 +1,77 @@ package protocol -// Base information for protocol +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/binary" + "math/rand" + "sync" + "time" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/go-shadowsocks2/core" +) + type Base struct { - IV []byte Key []byte - TCPMss int Overhead int Param string } + +type userData struct { + userKey []byte + userID [4]byte +} + +type authData struct { + clientID [4]byte + connectionID uint32 + mutex sync.Mutex +} + +func (a *authData) next() *authData { + r := &authData{} + a.mutex.Lock() + defer a.mutex.Unlock() + if a.connectionID > 0xff000000 || a.connectionID == 0 { + rand.Read(a.clientID[:]) + a.connectionID = rand.Uint32() & 0xffffff + } + a.connectionID++ + copy(r.clientID[:], a.clientID[:]) + r.connectionID = a.connectionID + return r +} + +func (a *authData) putAuthData(buf *bytes.Buffer) { + binary.Write(buf, binary.LittleEndian, uint32(time.Now().Unix())) + buf.Write(a.clientID[:]) + binary.Write(buf, binary.LittleEndian, a.connectionID) +} + +func (a *authData) putEncryptedData(b *bytes.Buffer, userKey []byte, paddings [2]int, salt string) error { + encrypt := pool.Get(16) + defer pool.Put(encrypt) + binary.LittleEndian.PutUint32(encrypt, uint32(time.Now().Unix())) + copy(encrypt[4:], a.clientID[:]) + binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) + binary.LittleEndian.PutUint16(encrypt[12:], uint16(paddings[0])) + binary.LittleEndian.PutUint16(encrypt[14:], uint16(paddings[1])) + + cipherKey := core.Kdf(base64.StdEncoding.EncodeToString(userKey)+salt, 16) + block, err := aes.NewCipher(cipherKey) + if err != nil { + log.Warnln("New cipher error: %s", err.Error()) + return err + } + iv := bytes.Repeat([]byte{0}, 16) + cbcCipher := cipher.NewCBCEncrypter(block, iv) + + cbcCipher.CryptBlocks(encrypt, encrypt) + + b.Write(encrypt) + return nil +} diff --git a/component/ssr/protocol/origin.go b/component/ssr/protocol/origin.go index 0d1fe217..80fdfa9a 100644 --- a/component/ssr/protocol/origin.go +++ b/component/ssr/protocol/origin.go @@ -1,36 +1,33 @@ package protocol -type origin struct{ *Base } +import ( + "bytes" + "net" +) -func init() { - register("origin", newOrigin) +type origin struct{} + +func init() { register("origin", newOrigin, 0) } + +func newOrigin(b *Base) Protocol { return &origin{} } + +func (o *origin) StreamConn(c net.Conn, iv []byte) net.Conn { return c } + +func (o *origin) PacketConn(c net.PacketConn) net.PacketConn { return c } + +func (o *origin) Decode(dst, src *bytes.Buffer) error { + dst.ReadFrom(src) + return nil } -func newOrigin(b *Base) Protocol { - return &origin{} +func (o *origin) Encode(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + return nil } -func (o *origin) initForConn(iv []byte) Protocol { return &origin{} } +func (o *origin) DecodePacket(b []byte) ([]byte, error) { return b, nil } -func (o *origin) GetProtocolOverhead() int { - return 0 -} - -func (o *origin) SetOverhead(overhead int) { -} - -func (o *origin) Decode(b []byte) ([]byte, int, error) { - return b, len(b), nil -} - -func (o *origin) Encode(b []byte) ([]byte, error) { - return b, nil -} - -func (o *origin) DecodePacket(b []byte) ([]byte, int, error) { - return b, len(b), nil -} - -func (o *origin) EncodePacket(b []byte) ([]byte, error) { - return b, nil +func (o *origin) EncodePacket(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + return nil } diff --git a/component/ssr/protocol/packet.go b/component/ssr/protocol/packet.go index c3e5c702..1577c788 100644 --- a/component/ssr/protocol/packet.go +++ b/component/ssr/protocol/packet.go @@ -1,30 +1,26 @@ package protocol import ( + "bytes" "net" - "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" ) -// NewPacketConn returns a net.NewPacketConn with protocol decoding/encoding -func NewPacketConn(pc net.PacketConn, p Protocol) net.PacketConn { - return &PacketConn{PacketConn: pc, Protocol: p.initForConn(nil)} -} - -// PacketConn represents a protocol packet connection type PacketConn struct { net.PacketConn Protocol } func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { - buf := pool.Get(pool.RelayBufferSize) - defer pool.Put(buf) - buf, err := c.EncodePacket(b) + buf := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(buf) + defer buf.Reset() + err := c.EncodePacket(buf, b) if err != nil { return 0, err } - _, err = c.PacketConn.WriteTo(buf, addr) + _, err = c.PacketConn.WriteTo(buf.Bytes(), addr) return len(b), err } @@ -33,10 +29,10 @@ func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { if err != nil { return n, addr, err } - bb, length, err := c.DecodePacket(b[:n]) + decoded, err := c.DecodePacket(b[:n]) if err != nil { return n, addr, err } - copy(b, bb) - return length, addr, err + copy(b, decoded) + return len(decoded), addr, nil } diff --git a/component/ssr/protocol/protocol.go b/component/ssr/protocol/protocol.go index 43c98d0d..41bd984c 100644 --- a/component/ssr/protocol/protocol.go +++ b/component/ssr/protocol/protocol.go @@ -4,60 +4,73 @@ import ( "bytes" "errors" "fmt" - "strings" - "sync" + "math/rand" + "net" ) var ( - errAuthAES128IncorrectMAC = errors.New("auth_aes128_* post decrypt incorrect mac") - errAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch") - errAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum") - errAuthAES128PositionTooLarge = errors.New("auth_aes128_* post decrypt position is too large") - errAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error") - errAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error") - errAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum") - errAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch") - errAuthChainHMACError = errors.New("auth_chain_* post decrypt hmac error") + errAuthSHA1V4CRC32Error = errors.New("auth_sha1_v4 decode data wrong crc32") + errAuthSHA1V4LengthError = errors.New("auth_sha1_v4 decode data wrong length") + errAuthSHA1V4Adler32Error = errors.New("auth_sha1_v4 decode data wrong adler32") + errAuthAES128MACError = errors.New("auth_aes128 decode data wrong mac") + errAuthAES128LengthError = errors.New("auth_aes128 decode data wrong length") + errAuthAES128ChksumError = errors.New("auth_aes128 decode data wrong checksum") + errAuthChainLengthError = errors.New("auth_chain decode data wrong length") + errAuthChainChksumError = errors.New("auth_chain decode data wrong checksum") ) -type authData struct { - clientID []byte - connectionID uint32 - mutex sync.Mutex -} - -type recvInfo struct { - recvID uint32 - buffer *bytes.Buffer -} - -type hmacMethod func(key []byte, data []byte) []byte -type hashDigestMethod func(data []byte) []byte -type rndMethod func(dataSize int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int - -// Protocol provides methods for decoding, encoding and iv setting type Protocol interface { - initForConn(iv []byte) Protocol - GetProtocolOverhead() int - SetOverhead(int) - Decode([]byte) ([]byte, int, error) - Encode([]byte) ([]byte, error) - DecodePacket([]byte) ([]byte, int, error) - EncodePacket([]byte) ([]byte, error) + StreamConn(net.Conn, []byte) net.Conn + PacketConn(net.PacketConn) net.PacketConn + Decode(dst, src *bytes.Buffer) error + Encode(buf *bytes.Buffer, b []byte) error + DecodePacket([]byte) ([]byte, error) + EncodePacket(buf *bytes.Buffer, b []byte) error } type protocolCreator func(b *Base) Protocol -var protocolList = make(map[string]protocolCreator) +var protocolList = make(map[string]struct { + overhead int + new protocolCreator +}) -func register(name string, c protocolCreator) { - protocolList[name] = c +func register(name string, c protocolCreator, o int) { + protocolList[name] = struct { + overhead int + new protocolCreator + }{overhead: o, new: c} } -// PickProtocol returns a protocol of the given name func PickProtocol(name string, b *Base) (Protocol, error) { - if protocolCreator, ok := protocolList[strings.ToLower(name)]; ok { - return protocolCreator(b), nil + if choice, ok := protocolList[name]; ok { + b.Overhead += choice.overhead + return choice.new(b), nil } - return nil, fmt.Errorf("Protocol %s not supported", name) + return nil, fmt.Errorf("protocol %s not supported", name) +} + +func getHeadSize(b []byte, defaultValue int) int { + if len(b) < 2 { + return defaultValue + } + headType := b[0] & 7 + switch headType { + case 1: + return 7 + case 4: + return 19 + case 3: + return 4 + int(b[1]) + } + return defaultValue +} + +func getDataLength(b []byte) int { + bLength := len(b) + dataLength := getHeadSize(b, 30) + rand.Intn(32) + if bLength < dataLength { + return bLength + } + return dataLength } diff --git a/component/ssr/protocol/stream.go b/component/ssr/protocol/stream.go index c76d35eb..53f53ead 100644 --- a/component/ssr/protocol/stream.go +++ b/component/ssr/protocol/stream.go @@ -5,31 +5,21 @@ import ( "net" "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" ) -// NewConn wraps a stream-oriented net.Conn with protocol decoding/encoding -func NewConn(c net.Conn, p Protocol, iv []byte) net.Conn { - return &Conn{Conn: c, Protocol: p.initForConn(iv)} -} - -// Conn represents a protocol connection type Conn struct { net.Conn Protocol - buf []byte - offset int + decoded bytes.Buffer underDecoded bytes.Buffer } func (c *Conn) Read(b []byte) (int, error) { - if c.buf != nil { - n := copy(b, c.buf[c.offset:]) - c.offset += n - if c.offset == len(c.buf) { - c.buf = nil - } - return n, nil + if c.decoded.Len() > 0 { + return c.decoded.Read(b) } + buf := pool.Get(pool.RelayBufferSize) defer pool.Put(buf) n, err := c.Conn.Read(buf) @@ -37,32 +27,26 @@ func (c *Conn) Read(b []byte) (int, error) { return 0, err } c.underDecoded.Write(buf[:n]) - underDecoded := c.underDecoded.Bytes() - decoded, length, err := c.Decode(underDecoded) + err = c.Decode(&c.decoded, &c.underDecoded) if err != nil { - c.underDecoded.Reset() - return 0, nil - } - if length == 0 { - return 0, nil - } - c.underDecoded.Next(length) - n = copy(b, decoded) - if len(decoded) > len(b) { - c.buf = decoded - c.offset = n + return 0, err } + n, _ = c.decoded.Read(b) return n, nil } func (c *Conn) Write(b []byte) (int, error) { - encoded, err := c.Encode(b) + bLength := len(b) + buf := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(buf) + defer buf.Reset() + err := c.Encode(buf, b) if err != nil { return 0, err } - _, err = c.Conn.Write(encoded) + _, err = c.Conn.Write(buf.Bytes()) if err != nil { return 0, err } - return len(b), nil + return bLength, nil } diff --git a/component/ssr/tools/bufPool.go b/component/ssr/tools/bufPool.go new file mode 100644 index 00000000..f3c45c46 --- /dev/null +++ b/component/ssr/tools/bufPool.go @@ -0,0 +1,18 @@ +package tools + +import ( + "bytes" + "math/rand" + "sync" + + "github.com/Dreamacro/clash/common/pool" +) + +var BufPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} + +func AppendRandBytes(b *bytes.Buffer, length int) { + randBytes := pool.Get(length) + defer pool.Put(randBytes) + rand.Read(randBytes) + b.Write(randBytes) +} diff --git a/component/ssr/tools/encrypt.go b/component/ssr/tools/crypto.go similarity index 88% rename from component/ssr/tools/encrypt.go rename to component/ssr/tools/crypto.go index 5fef1654..b2a41561 100644 --- a/component/ssr/tools/encrypt.go +++ b/component/ssr/tools/crypto.go @@ -11,13 +11,13 @@ const HmacSHA1Len = 10 func HmacMD5(key, data []byte) []byte { hmacMD5 := hmac.New(md5.New, key) hmacMD5.Write(data) - return hmacMD5.Sum(nil)[:16] + return hmacMD5.Sum(nil) } func HmacSHA1(key, data []byte) []byte { hmacSHA1 := hmac.New(sha1.New, key) hmacSHA1.Write(data) - return hmacSHA1.Sum(nil)[:20] + return hmacSHA1.Sum(nil) } func MD5Sum(b []byte) []byte { diff --git a/component/ssr/tools/random.go b/component/ssr/tools/random.go new file mode 100644 index 00000000..338543ea --- /dev/null +++ b/component/ssr/tools/random.go @@ -0,0 +1,57 @@ +package tools + +import ( + "encoding/binary" + + "github.com/Dreamacro/clash/common/pool" +) + +// XorShift128Plus - a pseudorandom number generator +type XorShift128Plus struct { + s [2]uint64 +} + +func (r *XorShift128Plus) Next() uint64 { + x := r.s[0] + y := r.s[1] + r.s[0] = y + x ^= x << 23 + x ^= y ^ (x >> 17) ^ (y >> 26) + r.s[1] = x + return x + y +} + +func (r *XorShift128Plus) InitFromBin(bin []byte) { + var full []byte + if len(bin) < 16 { + full := pool.Get(16)[:0] + defer pool.Put(full) + full = append(full, bin...) + for len(full) < 16 { + full = append(full, 0) + } + } else { + full = bin + } + r.s[0] = binary.LittleEndian.Uint64(full[:8]) + r.s[1] = binary.LittleEndian.Uint64(full[8:16]) +} + +func (r *XorShift128Plus) InitFromBinAndLength(bin []byte, length int) { + var full []byte + if len(bin) < 16 { + full := pool.Get(16)[:0] + defer pool.Put(full) + full = append(full, bin...) + for len(full) < 16 { + full = append(full, 0) + } + } + full = bin + binary.LittleEndian.PutUint16(full, uint16(length)) + r.s[0] = binary.LittleEndian.Uint64(full[:8]) + r.s[1] = binary.LittleEndian.Uint64(full[8:16]) + for i := 0; i < 4; i++ { + r.Next() + } +}