Co-authored-by: goomada <madao@DESKTOP-IOEBS0C.localdomain>
This commit is contained in:
parent
d48cfecf60
commit
9eb98e399d
23 changed files with 1667 additions and 1865 deletions
|
@ -12,12 +12,13 @@ import (
|
||||||
"github.com/Dreamacro/clash/component/ssr/protocol"
|
"github.com/Dreamacro/clash/component/ssr/protocol"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||||
|
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||||
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
|
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowSocksR struct {
|
type ShadowSocksR struct {
|
||||||
*Base
|
*Base
|
||||||
cipher *core.StreamCipher
|
cipher core.Cipher
|
||||||
obfs obfs.Obfs
|
obfs obfs.Obfs
|
||||||
protocol protocol.Protocol
|
protocol protocol.Protocol
|
||||||
}
|
}
|
||||||
|
@ -36,17 +37,22 @@ type ShadowSocksROption struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
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)
|
c = ssr.cipher.StreamConn(c)
|
||||||
conn, ok := c.(*shadowstream.Conn)
|
var (
|
||||||
if !ok {
|
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")
|
return nil, fmt.Errorf("invalid connection type")
|
||||||
}
|
}
|
||||||
iv, err := conn.ObtainWriteIV()
|
c = ssr.protocol.StreamConn(c, iv)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c = protocol.NewConn(c, ssr.protocol, iv)
|
|
||||||
_, err = c.Write(serializesSocksAddr(metadata))
|
_, err = c.Write(serializesSocksAddr(metadata))
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
@ -74,7 +80,7 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pc = ssr.cipher.PacketConn(pc)
|
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
|
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,35 +96,43 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||||
password := option.Password
|
password := option.Password
|
||||||
coreCiph, err := core.PickCipher(cipher, nil, password)
|
coreCiph, err := core.PickCipher(cipher, nil, password)
|
||||||
if err != nil {
|
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)
|
var (
|
||||||
if !ok {
|
ivSize int
|
||||||
return nil, fmt.Errorf("%s is not a supported stream cipher in ssr", cipher)
|
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{
|
obfs, obfsOverhead, err := obfs.PickObfs(option.Obfs, &obfs.Base{
|
||||||
IVSize: ciph.IVSize(),
|
Host: option.Server,
|
||||||
Key: ciph.Key,
|
Port: option.Port,
|
||||||
HeadLen: 30,
|
Key: key,
|
||||||
Host: option.Server,
|
IVSize: ivSize,
|
||||||
Port: option.Port,
|
Param: option.ObfsParam,
|
||||||
Param: option.ObfsParam,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err)
|
return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{
|
protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{
|
||||||
IV: nil,
|
Key: key,
|
||||||
Key: ciph.Key,
|
Overhead: obfsOverhead,
|
||||||
TCPMss: 1460,
|
Param: option.ProtocolParam,
|
||||||
Param: option.ProtocolParam,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
|
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
|
||||||
}
|
}
|
||||||
protocol.SetOverhead(obfs.GetObfsOverhead() + protocol.GetProtocolOverhead())
|
|
||||||
|
|
||||||
return &ShadowSocksR{
|
return &ShadowSocksR{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
|
@ -127,7 +141,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||||
tp: C.ShadowsocksR,
|
tp: C.ShadowsocksR,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
},
|
},
|
||||||
cipher: ciph,
|
cipher: coreCiph,
|
||||||
obfs: obfs,
|
obfs: obfs,
|
||||||
protocol: protocol,
|
protocol: protocol,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package obfs
|
package obfs
|
||||||
|
|
||||||
// Base information for obfs
|
|
||||||
type Base struct {
|
type Base struct {
|
||||||
IVSize int
|
Host string
|
||||||
Key []byte
|
Port int
|
||||||
HeadLen int
|
Key []byte
|
||||||
Host string
|
IVSize int
|
||||||
Port int
|
Param string
|
||||||
Param string
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package obfs
|
package obfs
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
register("http_post", newHTTPPost)
|
register("http_post", newHTTPPost, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHTTPPost(b *Base) Obfs {
|
func newHTTPPost(b *Base) Obfs {
|
||||||
|
|
|
@ -3,400 +3,405 @@ package obfs
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("http_simple", newHTTPSimple, 0)
|
||||||
|
}
|
||||||
|
|
||||||
type httpObfs struct {
|
type httpObfs struct {
|
||||||
*Base
|
*Base
|
||||||
firstRequest bool
|
post bool
|
||||||
firstResponse bool
|
|
||||||
post bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("http_simple", newHTTPSimple)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHTTPSimple(b *Base) Obfs {
|
func newHTTPSimple(b *Base) Obfs {
|
||||||
return &httpObfs{Base: b}
|
return &httpObfs{Base: b}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpObfs) initForConn() Obfs {
|
type httpConn struct {
|
||||||
return &httpObfs{
|
net.Conn
|
||||||
Base: h.Base,
|
*httpObfs
|
||||||
firstRequest: true,
|
hasSentHeader bool
|
||||||
firstResponse: true,
|
hasRecvHeader bool
|
||||||
post: h.post,
|
buf []byte
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpObfs) GetObfsOverhead() int {
|
func (h *httpObfs) StreamConn(c net.Conn) net.Conn {
|
||||||
return 0
|
return &httpConn{Conn: c, httpObfs: h}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpObfs) Decode(b []byte) ([]byte, bool, error) {
|
func (c *httpConn) Read(b []byte) (int, error) {
|
||||||
if h.firstResponse {
|
if c.buf != nil {
|
||||||
idx := bytes.Index(b, []byte("\r\n\r\n"))
|
n := copy(b, c.buf)
|
||||||
if idx == -1 {
|
if n == len(c.buf) {
|
||||||
return nil, false, io.EOF
|
c.buf = nil
|
||||||
}
|
|
||||||
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))
|
|
||||||
} else {
|
} else {
|
||||||
headData = make([]byte, bSize)
|
c.buf = c.buf[n:]
|
||||||
}
|
}
|
||||||
copy(headData, b[:len(headData)])
|
return n, nil
|
||||||
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 b, nil
|
if c.hasRecvHeader {
|
||||||
}
|
return c.Conn.Read(b)
|
||||||
|
|
||||||
func data2URLEncode(data []byte) (ret string) {
|
|
||||||
for i := 0; i < len(data); i++ {
|
|
||||||
ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]}))
|
|
||||||
}
|
}
|
||||||
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"
|
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
for i := 0; i < 32; i++ {
|
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 (
|
var userAgent = []string{
|
||||||
requestPath = []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",
|
||||||
"login.php?redir=", "",
|
"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",
|
||||||
"register.php?code=", "",
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||||
"?keyword=", "",
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||||
"search?src=typd&q=", "&lang=en",
|
"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",
|
||||||
"s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=",
|
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||||
"post.php?id=", "&goto=view.php",
|
"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",
|
||||||
requestUserAgent = []string{
|
"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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 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 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 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 (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.0; 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/68.0.3440.106 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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 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 (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.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 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 (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 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 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 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 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 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 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 (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 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 (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.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 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 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 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; 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.2; 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/55.0.2883.87 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 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 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 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 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; 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 10.0) 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/52.0.2743.116 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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 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; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
||||||
"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 (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; 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; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36",
|
||||||
"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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 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; 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 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
"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 (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 (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 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
"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; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 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; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
"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 (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 (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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 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; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 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; 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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
|
||||||
"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 (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 (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 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||||
"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 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 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.1) 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/62.0.3202.89 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 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 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 (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.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 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 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 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/32.0.1700.107 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 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 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 (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.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 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 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 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) 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/69.0.3497.100 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 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 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 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; 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/48.0.2564.116 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/50.0.2661.102 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; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 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 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
|
||||||
"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 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 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 (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 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30",
|
"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/69.0.3497.100 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/46.0.2490.86 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; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 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/48.0.2564.116 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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 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; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 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 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
|
"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 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
"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 (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 (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 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 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; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 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 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 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 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 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.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 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; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0",
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||||
"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 (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/41.0.2272.89 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 (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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 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 (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; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 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.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 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/57.0.2987.133 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/47.0.2526.111 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 (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/56.0.2924.87 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 (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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
"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 (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 (Linux; Android 7.0; SM-G610M) 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/49.0.2623.110 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 (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; 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; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
"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.3; WOW64) 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/59.0.3071.115 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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.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 (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.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 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; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36",
|
||||||
"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 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile 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 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 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 NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 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 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||||
"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 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 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 (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 10.0; Win64; x64) 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/60.0.3112.90 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 (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 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 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 (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; 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 (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 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 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 6.0; Win64; x64) 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/56.0.2924.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 10.0; WOW64) 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 NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 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 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
"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.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 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 (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 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
|
||||||
"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.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 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.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 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.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 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 (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 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; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 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 (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 (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 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 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 (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 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 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 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
|
"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 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile 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 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 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 (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 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 (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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 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 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1",
|
||||||
"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 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 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-J710F Build/NRD90M) 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 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.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 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.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 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 (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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 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 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 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/50.0.2661.94 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 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 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 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 (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 (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 (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 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 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 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 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) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 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 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/61.0.3163.79 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 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 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; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 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 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 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 (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 NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||||
"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 10.0) 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/51.0.2704.63 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 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 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.3; 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/43.0.2357.65 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 (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 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; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 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 6.3; 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/54.0.2840.99 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 NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
"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 10.0) 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/62.0.3202.89 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 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/70.0.3538.67 Safari/537.36",
|
"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 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 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 (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/63.0.3239.132 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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 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.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/65.0.3325.181 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; 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.2; Win64; x64) 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/36.0.1985.125 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 6.1; 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.86 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 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||||
"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 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 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; WOW64) 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/63.0.3239.132 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.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 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.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 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.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 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 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 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/61.0.3163.100 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 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 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 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 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 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 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; WOW64) 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/64.0.3282.186 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 (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.3; 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/58.0.3029.110 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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 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; 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 (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; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 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.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 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 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 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 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 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 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 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 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 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 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/67.0.3396.87 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 (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.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 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.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 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/63.0.3239.132 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 10.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.90 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 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 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 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 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 5.1; 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/67.0.3396.99 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/61.0.3163.100 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.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 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 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.132 Safari/537.36",
|
"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/57.0.2987.133 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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 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/70.0.3538.77 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 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/59.0.3071.115 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/68.0.3440.106 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) 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/70.0.3538.77 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 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.36 (KHTML, like Gecko) Chrome/46.0.2490.80 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 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36",
|
||||||
"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 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/55.0.2883.87 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 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/65.0.3325.181 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/66.0.3359.181 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 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; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 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) 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/51.0.2704.106 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.3; Win64; x64) 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.80 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/36.0.1985.143 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 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/66.0.3359.117 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/66.0.3359.139 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 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 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/70.0.3538.67 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 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 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 (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 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 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 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 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 (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/41.0.2272.101 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 6.1) 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/66.0.3359.117 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",
|
||||||
"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",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package obfs
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -12,26 +12,31 @@ var (
|
||||||
errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed")
|
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 {
|
type Obfs interface {
|
||||||
initForConn() Obfs
|
StreamConn(net.Conn) net.Conn
|
||||||
GetObfsOverhead() int
|
|
||||||
Decode(b []byte) ([]byte, bool, error)
|
|
||||||
Encode(b []byte) ([]byte, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type obfsCreator func(b *Base) Obfs
|
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) {
|
func register(name string, c obfsCreator, o int) {
|
||||||
obfsList[name] = c
|
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, int, error) {
|
||||||
func PickObfs(name string, b *Base) (Obfs, error) {
|
if choice, ok := obfsList[name]; ok {
|
||||||
if obfsCreator, ok := obfsList[strings.ToLower(name)]; ok {
|
return choice.new(b), choice.overhead, nil
|
||||||
return obfsCreator(b), nil
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Obfs %s not supported", name)
|
return nil, 0, fmt.Errorf("Obfs %s not supported", name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,15 @@
|
||||||
package obfs
|
package obfs
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
type plain struct{}
|
type plain struct{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
register("plain", newPlain)
|
register("plain", newPlain, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPlain(b *Base) Obfs {
|
func newPlain(b *Base) Obfs {
|
||||||
return &plain{}
|
return &plain{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *plain) initForConn() Obfs { return &plain{} }
|
func (p *plain) StreamConn(c net.Conn) net.Conn { return c }
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,72 +4,68 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("random_head", newRandomHead, 0)
|
||||||
|
}
|
||||||
|
|
||||||
type randomHead struct {
|
type randomHead struct {
|
||||||
*Base
|
*Base
|
||||||
firstRequest bool
|
|
||||||
firstResponse bool
|
|
||||||
headerSent bool
|
|
||||||
buffer []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("random_head", newRandomHead)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRandomHead(b *Base) Obfs {
|
func newRandomHead(b *Base) Obfs {
|
||||||
return &randomHead{Base: b}
|
return &randomHead{Base: b}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *randomHead) initForConn() Obfs {
|
type randomHeadConn struct {
|
||||||
return &randomHead{
|
net.Conn
|
||||||
Base: r.Base,
|
*randomHead
|
||||||
firstRequest: true,
|
hasSentHeader bool
|
||||||
firstResponse: true,
|
rawTransSent bool
|
||||||
}
|
rawTransRecv bool
|
||||||
|
buf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *randomHead) GetObfsOverhead() int {
|
func (r *randomHead) StreamConn(c net.Conn) net.Conn {
|
||||||
return 0
|
return &randomHeadConn{Conn: c, randomHead: r}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *randomHead) Encode(b []byte) (encoded []byte, err error) {
|
func (c *randomHeadConn) Read(b []byte) (int, error) {
|
||||||
if !r.firstRequest {
|
if c.rawTransRecv {
|
||||||
return b, nil
|
return c.Conn.Read(b)
|
||||||
}
|
}
|
||||||
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
bSize := len(b)
|
defer pool.Put(buf)
|
||||||
if r.headerSent {
|
c.Conn.Read(buf)
|
||||||
if bSize > 0 {
|
c.rawTransRecv = true
|
||||||
d := make([]byte, len(r.buffer)+bSize)
|
c.Write(nil)
|
||||||
copy(d, r.buffer)
|
return 0, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *randomHead) Decode(b []byte) ([]byte, bool, error) {
|
func (c *randomHeadConn) Write(b []byte) (int, error) {
|
||||||
if r.firstResponse {
|
if c.rawTransSent {
|
||||||
r.firstResponse = false
|
return c.Conn.Write(b)
|
||||||
return b, true, nil
|
|
||||||
}
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
231
component/ssr/obfs/tls1.2_ticket_auth.go
Normal file
231
component/ssr/obfs/tls1.2_ticket_auth.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,310 +1,18 @@
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
import (
|
import "github.com/Dreamacro/clash/component/ssr/tools"
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
register("auth_aes128_md5", newAuthAES128MD5)
|
register("auth_aes128_md5", newAuthAES128MD5, 9)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAuthAES128MD5(b *Base) Protocol {
|
func newAuthAES128MD5(b *Base) Protocol {
|
||||||
return &authAES128{
|
a := &authAES128{
|
||||||
Base: b,
|
Base: b,
|
||||||
authData: &authData{},
|
authData: &authData{},
|
||||||
salt: "auth_aes128_md5",
|
authAES128Function: &authAES128Function{salt: "auth_aes128_md5", hmac: tools.HmacMD5, hashDigest: tools.MD5Sum},
|
||||||
hmac: tools.HmacMD5,
|
userData: &userData{},
|
||||||
hashDigest: tools.MD5Sum,
|
|
||||||
}
|
}
|
||||||
}
|
a.initUserData()
|
||||||
|
return a
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,21 +2,274 @@ package protocol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"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/component/ssr/tools"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type hmacMethod func(key, data []byte) []byte
|
||||||
|
type hashDigestMethod func([]byte) []byte
|
||||||
|
|
||||||
func init() {
|
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 {
|
func newAuthAES128SHA1(b *Base) Protocol {
|
||||||
return &authAES128{
|
a := &authAES128{
|
||||||
Base: b,
|
Base: b,
|
||||||
recvInfo: &recvInfo{buffer: new(bytes.Buffer)},
|
authData: &authData{},
|
||||||
authData: &authData{},
|
authAES128Function: &authAES128Function{salt: "auth_aes128_sha1", hmac: tools.HmacSHA1, hashDigest: tools.SHA1Sum},
|
||||||
salt: "auth_aes128_sha1",
|
userData: &userData{},
|
||||||
hmac: tools.HmacSHA1,
|
}
|
||||||
hashDigest: tools.SHA1Sum,
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -2,430 +2,308 @@ package protocol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/rc4"
|
"crypto/rc4"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math/rand"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
type authChain struct {
|
func init() {
|
||||||
*Base
|
register("auth_chain_a", newAuthChainA, 4)
|
||||||
*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() {
|
type randDataLengthMethod func(int, []byte, *tools.XorShift128Plus) int
|
||||||
register("auth_chain_a", newAuthChainA)
|
|
||||||
|
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 {
|
func newAuthChainA(b *Base) Protocol {
|
||||||
return &authChain{
|
a := &authChainA{
|
||||||
Base: b,
|
Base: b,
|
||||||
authData: &authData{},
|
authData: &authData{},
|
||||||
salt: "auth_chain_a",
|
userData: &userData{},
|
||||||
hmac: tools.HmacMD5,
|
salt: "auth_chain_a",
|
||||||
hashDigest: tools.SHA1Sum,
|
|
||||||
rnd: authChainAGetRandLen,
|
|
||||||
}
|
}
|
||||||
|
a.initUserData()
|
||||||
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authChain) initForConn(iv []byte) Protocol {
|
func (a *authChainA) initUserData() {
|
||||||
r := &authChain{
|
params := strings.Split(a.Param, ":")
|
||||||
Base: &Base{
|
if len(params) > 1 {
|
||||||
IV: iv,
|
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
|
||||||
Key: a.Key,
|
binary.LittleEndian.PutUint32(a.userID[:], uint32(userID))
|
||||||
TCPMss: a.TCPMss,
|
a.userKey = []byte(params[1])
|
||||||
Overhead: a.Overhead,
|
} else {
|
||||||
Param: a.Param,
|
log.Warnln("Wrong protocol-param for %s, only digits are expected before ':'", a.salt)
|
||||||
},
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := a.hmac(key, b[:length-2])
|
serverHash := tools.HmacMD5(macKey, src.Bytes()[:length+2])
|
||||||
if !bytes.Equal(hash[:2], b[length-2:length]) {
|
if !bytes.Equal(serverHash[:2], src.Bytes()[length+2:length+4]) {
|
||||||
return nil, 0, errAuthChainHMACError
|
a.rawTrans = true
|
||||||
|
src.Reset()
|
||||||
|
return errAuthChainChksumError
|
||||||
}
|
}
|
||||||
var dataPos int
|
a.lastServerHash = serverHash
|
||||||
if dataLen > 0 && randLen > 0 {
|
|
||||||
dataPos = 2 + getRandStartPos(&a.randomServer, randLen)
|
pos := 2
|
||||||
} else {
|
if dataLength > 0 && randDataLength > 0 {
|
||||||
dataPos = 2
|
pos += getRandStartPos(randDataLength, &a.randomServer)
|
||||||
}
|
}
|
||||||
d := pool.Get(dataLen)
|
wantedData := src.Bytes()[pos : pos+dataLength]
|
||||||
a.dec.XORKeyStream(d, b[dataPos:dataPos+dataLen])
|
a.decrypter.XORKeyStream(wantedData, wantedData)
|
||||||
a.buffer.Write(d)
|
|
||||||
pool.Put(d)
|
|
||||||
if a.recvID == 1 {
|
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++
|
a.recvID++
|
||||||
b = b[length:]
|
src.Next(length + 4)
|
||||||
readSize += length
|
|
||||||
}
|
}
|
||||||
return a.buffer.Bytes(), readSize, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authChain) Encode(b []byte) ([]byte, error) {
|
func (a *authChainA) Encode(buf *bytes.Buffer, b []byte) error {
|
||||||
a.buffer.Reset()
|
if !a.hasSentHeader {
|
||||||
bSize := len(b)
|
dataLength := getDataLength(b)
|
||||||
offset := 0
|
a.packAuthData(buf, b[:dataLength])
|
||||||
if bSize > 0 && !a.headerSent {
|
b = b[dataLength:]
|
||||||
headSize := 1200
|
a.hasSentHeader = true
|
||||||
if headSize > bSize {
|
|
||||||
headSize = bSize
|
|
||||||
}
|
|
||||||
a.buffer.Write(a.packAuthData(b[:headSize]))
|
|
||||||
offset += headSize
|
|
||||||
bSize -= headSize
|
|
||||||
a.headerSent = true
|
|
||||||
}
|
}
|
||||||
var unitSize = a.TCPMss - a.Overhead
|
for len(b) > 2800 {
|
||||||
for bSize > unitSize {
|
a.packData(buf, b[:2800])
|
||||||
dataLen, randLength := a.packedDataLen(b[offset : offset+unitSize])
|
b = b[2800:]
|
||||||
d := pool.Get(dataLen)
|
|
||||||
a.packData(d, b[offset:offset+unitSize], randLength)
|
|
||||||
a.buffer.Write(d)
|
|
||||||
pool.Put(d)
|
|
||||||
bSize -= unitSize
|
|
||||||
offset += unitSize
|
|
||||||
}
|
}
|
||||||
if bSize > 0 {
|
if len(b) > 0 {
|
||||||
dataLen, randLength := a.packedDataLen(b[offset:])
|
a.packData(buf, b)
|
||||||
d := pool.Get(dataLen)
|
|
||||||
a.packData(d, b[offset:], randLength)
|
|
||||||
a.buffer.Write(d)
|
|
||||||
pool.Put(d)
|
|
||||||
}
|
}
|
||||||
return a.buffer.Bytes(), nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authChain) DecodePacket(b []byte) ([]byte, int, error) {
|
func (a *authChainA) DecodePacket(b []byte) ([]byte, error) {
|
||||||
bSize := len(b)
|
if len(b) < 9 {
|
||||||
if bSize < 9 {
|
return nil, errAuthChainLengthError
|
||||||
return nil, 0, errAuthChainDataLengthError
|
|
||||||
}
|
}
|
||||||
h := a.hmac(a.userKey, b[:bSize-1])
|
if !bytes.Equal(tools.HmacMD5(a.userKey, b[:len(b)-1])[:1], b[len(b)-1:]) {
|
||||||
if h[0] != b[bSize-1] {
|
return nil, errAuthChainChksumError
|
||||||
return nil, 0, errAuthChainHMACError
|
|
||||||
}
|
}
|
||||||
hash := a.hmac(a.Key, b[bSize-8:bSize-1])
|
md5Data := tools.HmacMD5(a.Key, b[len(b)-8:len(b)-1])
|
||||||
cipherKey := a.getRC4CipherKey(hash)
|
|
||||||
dec, _ := rc4.NewCipher(cipherKey)
|
randDataLength := udpGetRandLength(md5Data, &a.randomServer)
|
||||||
randLength := udpGetRandLen(&a.randomServer, hash)
|
|
||||||
bSize -= 8 + randLength
|
key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16)
|
||||||
dec.XORKeyStream(b, b[:bSize])
|
rc4Cipher, err := rc4.NewCipher(key)
|
||||||
return b, bSize, nil
|
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) {
|
func (a *authChainA) EncodePacket(buf *bytes.Buffer, b []byte) error {
|
||||||
a.initUserKeyAndID()
|
|
||||||
authData := pool.Get(3)
|
authData := pool.Get(3)
|
||||||
defer pool.Put(authData)
|
defer pool.Put(authData)
|
||||||
rand.Read(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)
|
md5Data := tools.HmacMD5(a.Key, authData)
|
||||||
enc, _ := rc4.NewCipher(cipherKey)
|
|
||||||
var buf bytes.Buffer
|
randDataLength := udpGetRandLength(md5Data, &a.randomClient)
|
||||||
enc.XORKeyStream(b, b)
|
|
||||||
|
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)
|
buf.Write(b)
|
||||||
|
tools.AppendRandBytes(buf, randDataLength)
|
||||||
randLength := udpGetRandLen(&a.randomClient, hash)
|
|
||||||
randBytes := pool.Get(randLength)
|
|
||||||
defer pool.Put(randBytes)
|
|
||||||
buf.Write(randBytes)
|
|
||||||
|
|
||||||
buf.Write(authData)
|
buf.Write(authData)
|
||||||
buf.Write(uid)
|
binary.Write(buf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(md5Data[:4]))
|
||||||
|
buf.Write(tools.HmacMD5(a.userKey, buf.Bytes())[:1])
|
||||||
h := a.hmac(a.userKey, buf.Bytes())
|
return nil
|
||||||
buf.Write(h[:1])
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authChain) getRC4CipherKey(hash []byte) []byte {
|
func (a *authChainA) packAuthData(poolBuf *bytes.Buffer, data []byte) {
|
||||||
base64UserKey := base64.StdEncoding.EncodeToString(a.userKey)
|
/*
|
||||||
return a.calcRC4CipherKey(hash, base64UserKey)
|
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 {
|
macKey := pool.Get(len(a.iv) + len(a.Key))
|
||||||
password := pool.Get(len(base64UserKey) + base64.StdEncoding.EncodedLen(16))
|
defer pool.Put(macKey)
|
||||||
defer pool.Put(password)
|
copy(macKey, a.iv)
|
||||||
copy(password, base64UserKey)
|
copy(macKey[len(a.iv):], a.Key)
|
||||||
base64.StdEncoding.Encode(password[len(base64UserKey):], hash[:16])
|
|
||||||
return core.Kdf(string(password), 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authChain) initUserKeyAndID() {
|
// check head
|
||||||
if a.userKey == nil {
|
tools.AppendRandBytes(poolBuf, 4)
|
||||||
params := strings.Split(a.Param, ":")
|
a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes())
|
||||||
if len(params) >= 2 {
|
a.initRC4Cipher()
|
||||||
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
|
poolBuf.Write(a.lastClientHash[:8])
|
||||||
binary.LittleEndian.PutUint32(a.uid[:], uint32(userID))
|
// uid
|
||||||
a.userKey = []byte(params[1])
|
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 {
|
||||||
if a.userKey == nil {
|
poolBuf.Reset()
|
||||||
rand.Read(a.uid[:])
|
return
|
||||||
a.userKey = make([]byte, len(a.Key))
|
|
||||||
copy(a.userKey, a.Key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 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 {
|
func (a *authChainA) packData(poolBuf *bytes.Buffer, data []byte) {
|
||||||
return a.rnd(dataLength, &a.randomClient, a.lastClientHash, a.dataSizeList, a.dataSizeList2, overhead)
|
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 {
|
func (a *authChainA) putMixedRandDataAndData(poolBuf *bytes.Buffer, data []byte) {
|
||||||
return a.rnd(dataLength, &a.randomServer, a.lastServerHash, a.dataSizeList, a.dataSizeList2, overhead)
|
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) {
|
func getRandStartPos(length int, random *tools.XorShift128Plus) int {
|
||||||
dataLength := len(data)
|
if length == 0 {
|
||||||
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 {
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
random.InitFromBinDatalen(lastHash[:16], dataLength)
|
return int(random.Next()%8589934609) % length
|
||||||
if dataLength > 1300 {
|
}
|
||||||
|
|
||||||
|
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)
|
return int(random.Next() % 31)
|
||||||
}
|
}
|
||||||
if dataLength > 900 {
|
if length > 900 {
|
||||||
return int(random.Next() % 127)
|
return int(random.Next() % 127)
|
||||||
}
|
}
|
||||||
if dataLength > 400 {
|
if length > 400 {
|
||||||
return int(random.Next() % 521)
|
return int(random.Next() % 521)
|
||||||
}
|
}
|
||||||
return int(random.Next() % 1021)
|
return int(random.Next() % 1021)
|
||||||
}
|
}
|
||||||
|
|
||||||
func udpGetRandLen(random *shift128PlusContext, lastHash []byte) int {
|
func (a *authChainA) initRC4Cipher() {
|
||||||
random.InitFromBin(lastHash[:16])
|
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)
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,71 +1,96 @@
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
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 {
|
func newAuthChainB(b *Base) Protocol {
|
||||||
return &authChain{
|
a := &authChainB{
|
||||||
Base: b,
|
authChainA: &authChainA{
|
||||||
authData: &authData{},
|
Base: b,
|
||||||
salt: "auth_chain_b",
|
authData: &authData{},
|
||||||
hmac: tools.HmacMD5,
|
userData: &userData{},
|
||||||
hashDigest: tools.SHA1Sum,
|
salt: "auth_chain_b",
|
||||||
rnd: authChainBGetRandLen,
|
},
|
||||||
}
|
}
|
||||||
|
a.initUserData()
|
||||||
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func initDataSize(r *authChain) {
|
func (a *authChainB) StreamConn(c net.Conn, iv []byte) net.Conn {
|
||||||
random := &r.randomServer
|
p := &authChainB{
|
||||||
random.InitFromBin(r.Key)
|
authChainA: &authChainA{
|
||||||
len := random.Next()%8 + 4
|
Base: a.Base,
|
||||||
r.dataSizeList = make([]int, len)
|
authData: a.next(),
|
||||||
for i := 0; i < int(len); i++ {
|
userData: a.userData,
|
||||||
r.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440)
|
salt: a.salt,
|
||||||
|
packID: 1,
|
||||||
|
recvID: 1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
sort.Ints(r.dataSizeList)
|
p.iv = iv
|
||||||
|
p.randDataLength = p.getRandLength
|
||||||
len = random.Next()%16 + 8
|
p.initDataSize()
|
||||||
r.dataSizeList2 = make([]int, len)
|
return &Conn{Conn: c, Protocol: p}
|
||||||
for i := 0; i < int(len); i++ {
|
|
||||||
r.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440)
|
|
||||||
}
|
|
||||||
sort.Ints(r.dataSizeList2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func authChainBGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
|
func (a *authChainB) initDataSize() {
|
||||||
if dataLength > 1440 {
|
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
|
return 0
|
||||||
}
|
}
|
||||||
random.InitFromBinDatalen(lastHash[:16], dataLength)
|
random.InitFromBinAndLength(lashHash, length)
|
||||||
pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead })
|
pos := sort.Search(len(a.dataSizeList), func(i int) bool { return a.dataSizeList[i] >= length+a.Overhead })
|
||||||
finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList))
|
finalPos := pos + int(random.Next()%uint64(len(a.dataSizeList)))
|
||||||
if finalPos < uint64(len(dataSizeList)) {
|
if finalPos < len(a.dataSizeList) {
|
||||||
return dataSizeList[finalPos] - dataLength - overhead
|
return a.dataSizeList[finalPos] - length - a.Overhead
|
||||||
}
|
}
|
||||||
|
|
||||||
pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead })
|
pos = sort.Search(len(a.dataSizeList2), func(i int) bool { return a.dataSizeList2[i] >= length+a.Overhead })
|
||||||
finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2))
|
finalPos = pos + int(random.Next()%uint64(len(a.dataSizeList2)))
|
||||||
if finalPos < uint64(len(dataSizeList2)) {
|
if finalPos < len(a.dataSizeList2) {
|
||||||
return dataSizeList2[finalPos] - dataLength - overhead
|
return a.dataSizeList2[finalPos] - length - a.Overhead
|
||||||
}
|
}
|
||||||
if finalPos < uint64(pos+len(dataSizeList2)-1) {
|
if finalPos < pos+len(a.dataSizeList2)-1 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
if length > 1300 {
|
||||||
if dataLength > 1300 {
|
|
||||||
return int(random.Next() % 31)
|
return int(random.Next() % 31)
|
||||||
}
|
}
|
||||||
if dataLength > 900 {
|
if length > 900 {
|
||||||
return int(random.Next() % 127)
|
return int(random.Next() % 127)
|
||||||
}
|
}
|
||||||
if dataLength > 400 {
|
if length > 400 {
|
||||||
return int(random.Next() % 521)
|
return int(random.Next() % 521)
|
||||||
}
|
}
|
||||||
return int(random.Next() % 1021)
|
return int(random.Next() % 1021)
|
||||||
|
|
|
@ -6,248 +6,177 @@ import (
|
||||||
"hash/adler32"
|
"hash/adler32"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("auth_sha1_v4", newAuthSHA1V4, 7)
|
||||||
|
}
|
||||||
|
|
||||||
type authSHA1V4 struct {
|
type authSHA1V4 struct {
|
||||||
*Base
|
*Base
|
||||||
*authData
|
*authData
|
||||||
headerSent bool
|
iv []byte
|
||||||
buffer bytes.Buffer
|
hasSentHeader bool
|
||||||
}
|
rawTrans bool
|
||||||
|
|
||||||
func init() {
|
|
||||||
register("auth_sha1_v4", newAuthSHA1V4)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAuthSHA1V4(b *Base) Protocol {
|
func newAuthSHA1V4(b *Base) Protocol {
|
||||||
return &authSHA1V4{Base: b, authData: &authData{}}
|
return &authSHA1V4{Base: b, authData: &authData{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authSHA1V4) initForConn(iv []byte) Protocol {
|
func (a *authSHA1V4) StreamConn(c net.Conn, iv []byte) net.Conn {
|
||||||
return &authSHA1V4{
|
p := &authSHA1V4{Base: a.Base, authData: a.next()}
|
||||||
Base: &Base{
|
p.iv = iv
|
||||||
IV: iv,
|
return &Conn{Conn: c, Protocol: p}
|
||||||
Key: a.Key,
|
}
|
||||||
TCPMss: a.TCPMss,
|
|
||||||
Overhead: a.Overhead,
|
func (a *authSHA1V4) PacketConn(c net.PacketConn) net.PacketConn {
|
||||||
Param: a.Param,
|
return c
|
||||||
},
|
}
|
||||||
authData: a.authData,
|
|
||||||
|
func (a *authSHA1V4) Decode(dst, src *bytes.Buffer) error {
|
||||||
|
if a.rawTrans {
|
||||||
|
dst.ReadFrom(src)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
for src.Len() > 4 {
|
||||||
|
if uint16(crc32.ChecksumIEEE(src.Bytes()[:2])&0xffff) != binary.LittleEndian.Uint16(src.Bytes()[2:4]) {
|
||||||
func (a *authSHA1V4) GetProtocolOverhead() int {
|
src.Reset()
|
||||||
return 7
|
return errAuthSHA1V4CRC32Error
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
length := int(binary.BigEndian.Uint16(b[:2]))
|
|
||||||
if length >= 8192 || length < 8 {
|
length := int(binary.BigEndian.Uint16(src.Bytes()[:2]))
|
||||||
return nil, 0, errAuthSHA1v4DataLengthError
|
if length >= 8192 || length < 7 {
|
||||||
|
a.rawTrans = true
|
||||||
|
src.Reset()
|
||||||
|
return errAuthSHA1V4LengthError
|
||||||
}
|
}
|
||||||
if length > bSize {
|
if length > src.Len() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if adler32.Checksum(b[:length-4]) == binary.LittleEndian.Uint32(b[length-4:]) {
|
if adler32.Checksum(src.Bytes()[:length-4]) != binary.LittleEndian.Uint32(src.Bytes()[length-4:length]) {
|
||||||
pos := int(b[4])
|
a.rawTrans = true
|
||||||
if pos != 0xFF {
|
src.Reset()
|
||||||
pos += 4
|
return errAuthSHA1V4Adler32Error
|
||||||
} else {
|
}
|
||||||
pos = int(binary.BigEndian.Uint16(b[5:5+2])) + 4
|
|
||||||
}
|
pos := int(src.Bytes()[4])
|
||||||
retSize := length - pos - 4
|
if pos < 255 {
|
||||||
a.buffer.Write(b[pos : pos+retSize])
|
pos += 4
|
||||||
bSize -= length
|
|
||||||
b = b[length:]
|
|
||||||
} else {
|
} 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) {
|
func (a *authSHA1V4) Encode(buf *bytes.Buffer, b []byte) error {
|
||||||
a.buffer.Reset()
|
if !a.hasSentHeader {
|
||||||
bSize := len(b)
|
dataLength := getDataLength(b)
|
||||||
offset := 0
|
|
||||||
if !a.headerSent && bSize > 0 {
|
a.packAuthData(buf, b[:dataLength])
|
||||||
headSize := getHeadSize(b, 30)
|
b = b[dataLength:]
|
||||||
if headSize > bSize {
|
|
||||||
headSize = bSize
|
a.hasSentHeader = true
|
||||||
}
|
|
||||||
a.buffer.Write(a.packAuthData(b[:headSize]))
|
|
||||||
offset += headSize
|
|
||||||
bSize -= headSize
|
|
||||||
a.headerSent = true
|
|
||||||
}
|
}
|
||||||
const blockSize = 4096
|
for len(b) > 8100 {
|
||||||
for bSize > blockSize {
|
a.packData(buf, b[:8100])
|
||||||
packSize, randSize := a.packedDataSize(b[offset : offset+blockSize])
|
b = b[8100:]
|
||||||
pack := pool.Get(packSize)
|
|
||||||
a.packData(b[offset:offset+blockSize], pack, randSize)
|
|
||||||
a.buffer.Write(pack)
|
|
||||||
pool.Put(pack)
|
|
||||||
offset += blockSize
|
|
||||||
bSize -= blockSize
|
|
||||||
}
|
}
|
||||||
if bSize > 0 {
|
if len(b) > 0 {
|
||||||
packSize, randSize := a.packedDataSize(b[offset:])
|
a.packData(buf, b)
|
||||||
pack := pool.Get(packSize)
|
|
||||||
a.packData(b[offset:], pack, randSize)
|
|
||||||
a.buffer.Write(pack)
|
|
||||||
pool.Put(pack)
|
|
||||||
}
|
}
|
||||||
return a.buffer.Bytes(), nil
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, int, error) {
|
func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, error) { return b, nil }
|
||||||
return b, len(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) {
|
func (a *authSHA1V4) packData(poolBuf *bytes.Buffer, data []byte) {
|
||||||
return b, nil
|
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) {
|
func (a *authSHA1V4) packAuthData(poolBuf *bytes.Buffer, data []byte) {
|
||||||
dataSize := len(data)
|
dataLength := len(data)
|
||||||
randSize = 1
|
randDataLength := a.getRandDataLength(12 + dataLength)
|
||||||
if dataSize <= 1300 {
|
/*
|
||||||
if dataSize > 400 {
|
2: uint16 BigEndian packedAuthDataLength
|
||||||
randSize += rand.Intn(128)
|
4: uint32 LittleEndian crc32Data
|
||||||
} else {
|
3: maxRandDataLengthPrefix (min: 1)
|
||||||
randSize += rand.Intn(1024)
|
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")
|
salt := []byte("auth_sha1_v4")
|
||||||
crcData := make([]byte, len(salt)+len(a.Key)+2)
|
crcData := pool.Get(len(salt) + len(a.Key) + 2)
|
||||||
copy(crcData[:2], ret[:2])
|
defer pool.Put(crcData)
|
||||||
|
binary.BigEndian.PutUint16(crcData, uint16(packedAuthDataLength))
|
||||||
copy(crcData[2:], salt)
|
copy(crcData[2:], salt)
|
||||||
copy(crcData[2+len(salt):], a.Key)
|
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))
|
key := pool.Get(len(a.iv) + len(a.Key))
|
||||||
copy(key, a.IV)
|
defer pool.Put(key)
|
||||||
copy(key[len(a.IV):], a.Key)
|
copy(key, a.iv)
|
||||||
|
copy(key[len(a.iv):], a.Key)
|
||||||
|
|
||||||
h := tools.HmacSHA1(key, ret[:retSize-tools.HmacSHA1Len])
|
poolBuf.Write(crcData[:2])
|
||||||
// (ret size-10)~(ret size)/(rand size)+18+data length~end, hmac
|
binary.Write(poolBuf, binary.LittleEndian, crc32.ChecksumIEEE(crcData))
|
||||||
copy(ret[retSize-tools.HmacSHA1Len:], h[:tools.HmacSHA1Len])
|
a.packRandData(poolBuf, randDataLength)
|
||||||
return ret
|
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 {
|
func (a *authSHA1V4) packRandData(poolBuf *bytes.Buffer, size int) {
|
||||||
if data == nil || len(data) < 2 {
|
if size < 128 {
|
||||||
return defaultValue
|
poolBuf.WriteByte(byte(size + 1))
|
||||||
|
tools.AppendRandBytes(poolBuf, size)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
headType := data[0] & 0x07
|
poolBuf.WriteByte(255)
|
||||||
switch headType {
|
binary.Write(poolBuf, binary.BigEndian, uint16(size+3))
|
||||||
case 1:
|
tools.AppendRandBytes(poolBuf, size)
|
||||||
// IPv4 1+4+2
|
}
|
||||||
return 7
|
|
||||||
case 4:
|
func (a *authSHA1V4) getRandDataLength(size int) int {
|
||||||
// IPv6 1+16+2
|
if size > 1200 {
|
||||||
return 19
|
return 0
|
||||||
case 3:
|
}
|
||||||
// domain name, variant length
|
if size > 400 {
|
||||||
return 4 + int(data[1])
|
return rand.Intn(256)
|
||||||
}
|
}
|
||||||
|
return rand.Intn(512)
|
||||||
return defaultValue
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,77 @@
|
||||||
package protocol
|
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 {
|
type Base struct {
|
||||||
IV []byte
|
|
||||||
Key []byte
|
Key []byte
|
||||||
TCPMss int
|
|
||||||
Overhead int
|
Overhead int
|
||||||
Param string
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -1,36 +1,33 @@
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
type origin struct{ *Base }
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
type origin struct{}
|
||||||
register("origin", newOrigin)
|
|
||||||
|
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 {
|
func (o *origin) Encode(buf *bytes.Buffer, b []byte) error {
|
||||||
return &origin{}
|
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 {
|
func (o *origin) EncodePacket(buf *bytes.Buffer, b []byte) error {
|
||||||
return 0
|
buf.Write(b)
|
||||||
}
|
return nil
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,26 @@
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"net"
|
"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 {
|
type PacketConn struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
Protocol
|
Protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
buf := pool.Get(pool.RelayBufferSize)
|
buf := tools.BufPool.Get().(*bytes.Buffer)
|
||||||
defer pool.Put(buf)
|
defer tools.BufPool.Put(buf)
|
||||||
buf, err := c.EncodePacket(b)
|
defer buf.Reset()
|
||||||
|
err := c.EncodePacket(buf, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
_, err = c.PacketConn.WriteTo(buf, addr)
|
_, err = c.PacketConn.WriteTo(buf.Bytes(), addr)
|
||||||
return len(b), err
|
return len(b), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,10 +29,10 @@ func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, addr, err
|
return n, addr, err
|
||||||
}
|
}
|
||||||
bb, length, err := c.DecodePacket(b[:n])
|
decoded, err := c.DecodePacket(b[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, addr, err
|
return n, addr, err
|
||||||
}
|
}
|
||||||
copy(b, bb)
|
copy(b, decoded)
|
||||||
return length, addr, err
|
return len(decoded), addr, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,60 +4,73 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"math/rand"
|
||||||
"sync"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errAuthAES128IncorrectMAC = errors.New("auth_aes128_* post decrypt incorrect mac")
|
errAuthSHA1V4CRC32Error = errors.New("auth_sha1_v4 decode data wrong crc32")
|
||||||
errAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch")
|
errAuthSHA1V4LengthError = errors.New("auth_sha1_v4 decode data wrong length")
|
||||||
errAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum")
|
errAuthSHA1V4Adler32Error = errors.New("auth_sha1_v4 decode data wrong adler32")
|
||||||
errAuthAES128PositionTooLarge = errors.New("auth_aes128_* post decrypt position is too large")
|
errAuthAES128MACError = errors.New("auth_aes128 decode data wrong mac")
|
||||||
errAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error")
|
errAuthAES128LengthError = errors.New("auth_aes128 decode data wrong length")
|
||||||
errAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error")
|
errAuthAES128ChksumError = errors.New("auth_aes128 decode data wrong checksum")
|
||||||
errAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum")
|
errAuthChainLengthError = errors.New("auth_chain decode data wrong length")
|
||||||
errAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch")
|
errAuthChainChksumError = errors.New("auth_chain decode data wrong checksum")
|
||||||
errAuthChainHMACError = errors.New("auth_chain_* post decrypt hmac error")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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 {
|
type Protocol interface {
|
||||||
initForConn(iv []byte) Protocol
|
StreamConn(net.Conn, []byte) net.Conn
|
||||||
GetProtocolOverhead() int
|
PacketConn(net.PacketConn) net.PacketConn
|
||||||
SetOverhead(int)
|
Decode(dst, src *bytes.Buffer) error
|
||||||
Decode([]byte) ([]byte, int, error)
|
Encode(buf *bytes.Buffer, b []byte) error
|
||||||
Encode([]byte) ([]byte, error)
|
DecodePacket([]byte) ([]byte, error)
|
||||||
DecodePacket([]byte) ([]byte, int, error)
|
EncodePacket(buf *bytes.Buffer, b []byte) error
|
||||||
EncodePacket([]byte) ([]byte, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type protocolCreator func(b *Base) Protocol
|
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) {
|
func register(name string, c protocolCreator, o int) {
|
||||||
protocolList[name] = c
|
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) {
|
func PickProtocol(name string, b *Base) (Protocol, error) {
|
||||||
if protocolCreator, ok := protocolList[strings.ToLower(name)]; ok {
|
if choice, ok := protocolList[name]; ok {
|
||||||
return protocolCreator(b), nil
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,31 +5,21 @@ import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"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 {
|
type Conn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
Protocol
|
Protocol
|
||||||
buf []byte
|
decoded bytes.Buffer
|
||||||
offset int
|
|
||||||
underDecoded bytes.Buffer
|
underDecoded bytes.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Read(b []byte) (int, error) {
|
func (c *Conn) Read(b []byte) (int, error) {
|
||||||
if c.buf != nil {
|
if c.decoded.Len() > 0 {
|
||||||
n := copy(b, c.buf[c.offset:])
|
return c.decoded.Read(b)
|
||||||
c.offset += n
|
|
||||||
if c.offset == len(c.buf) {
|
|
||||||
c.buf = nil
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := pool.Get(pool.RelayBufferSize)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
defer pool.Put(buf)
|
defer pool.Put(buf)
|
||||||
n, err := c.Conn.Read(buf)
|
n, err := c.Conn.Read(buf)
|
||||||
|
@ -37,32 +27,26 @@ func (c *Conn) Read(b []byte) (int, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
c.underDecoded.Write(buf[:n])
|
c.underDecoded.Write(buf[:n])
|
||||||
underDecoded := c.underDecoded.Bytes()
|
err = c.Decode(&c.decoded, &c.underDecoded)
|
||||||
decoded, length, err := c.Decode(underDecoded)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.underDecoded.Reset()
|
return 0, err
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
n, _ = c.decoded.Read(b)
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (int, error) {
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
_, err = c.Conn.Write(encoded)
|
_, err = c.Conn.Write(buf.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return len(b), nil
|
return bLength, nil
|
||||||
}
|
}
|
||||||
|
|
18
component/ssr/tools/bufPool.go
Normal file
18
component/ssr/tools/bufPool.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -11,13 +11,13 @@ const HmacSHA1Len = 10
|
||||||
func HmacMD5(key, data []byte) []byte {
|
func HmacMD5(key, data []byte) []byte {
|
||||||
hmacMD5 := hmac.New(md5.New, key)
|
hmacMD5 := hmac.New(md5.New, key)
|
||||||
hmacMD5.Write(data)
|
hmacMD5.Write(data)
|
||||||
return hmacMD5.Sum(nil)[:16]
|
return hmacMD5.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HmacSHA1(key, data []byte) []byte {
|
func HmacSHA1(key, data []byte) []byte {
|
||||||
hmacSHA1 := hmac.New(sha1.New, key)
|
hmacSHA1 := hmac.New(sha1.New, key)
|
||||||
hmacSHA1.Write(data)
|
hmacSHA1.Write(data)
|
||||||
return hmacSHA1.Sum(nil)[:20]
|
return hmacSHA1.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MD5Sum(b []byte) []byte {
|
func MD5Sum(b []byte) []byte {
|
57
component/ssr/tools/random.go
Normal file
57
component/ssr/tools/random.go
Normal file
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue