diff --git a/go.mod b/go.mod index 0eb6a09b9d..7c225e9510 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.20 require ( berty.tech/go-libtor v1.0.385 + github.com/Dreamacro/clash v1.17.0 github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.6 github.com/cretz/bine v0.2.0 @@ -56,6 +57,7 @@ require ( //replace github.com/sagernet/sing => ../sing require ( + github.com/Dreamacro/protobytes v0.0.0-20230617041236-6500a9f4f158 // indirect github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 099412dcec..73817fc036 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= +github.com/Dreamacro/clash v1.17.0 h1:LWtp6KcnrCiujY58ufI8pylI+hbCBgSCsLI90EWhpi4= +github.com/Dreamacro/clash v1.17.0/go.mod h1:PtcAft7sdsK325BD6uwm8wvhOkMV3TCeED6dfZ/lnfE= +github.com/Dreamacro/protobytes v0.0.0-20230617041236-6500a9f4f158 h1:JFnwKplz9hj8ubqYjm8HkgZS1Rvz9yW+u/XCNNTxr0k= +github.com/Dreamacro/protobytes v0.0.0-20230617041236-6500a9f4f158/go.mod h1:QvmEZ/h6KXszPOr2wUFl7Zn3hfFNYdfbXwPVDTyZs6k= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= diff --git a/outbound/shadowsocksr.go b/outbound/shadowsocksr.go index 615a71e4e4..f8e4e4b346 100644 --- a/outbound/shadowsocksr.go +++ b/outbound/shadowsocksr.go @@ -4,15 +4,191 @@ package outbound import ( "context" - "os" + "errors" + "fmt" + "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/clashssr/obfs" + "github.com/sagernet/sing-box/transport/clashssr/protocol" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "github.com/Dreamacro/clash/transport/shadowsocks/core" + "github.com/Dreamacro/clash/transport/shadowsocks/shadowstream" + "github.com/Dreamacro/clash/transport/socks5" ) -var _ int = "ShadowsocksR is deprecated and removed in sing-box 1.6.0" +var _ adapter.Outbound = (*ShadowsocksR)(nil) + +type ShadowsocksR struct { + myOutboundAdapter + dialer N.Dialer + serverAddr M.Socksaddr + cipher core.Cipher + obfs obfs.Obfs + protocol protocol.Protocol +} + +func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (*ShadowsocksR, error) { + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } + outbound := &ShadowsocksR{ + myOutboundAdapter: myOutboundAdapter{ + protocol: C.TypeShadowsocksR, + network: options.Network.Build(), + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), + }, + dialer: outboundDialer, + serverAddr: options.ServerOptions.Build(), + } + var cipher string + switch options.Method { + case "none": + cipher = "dummy" + default: + cipher = options.Method + } + outbound.cipher, err = core.PickCipher(cipher, nil, options.Password) + if err != nil { + return nil, err + } + var ( + ivSize int + key []byte + ) + if cipher == "dummy" { + ivSize = 0 + key = core.Kdf(options.Password, 16) + } else { + streamCipher, ok := outbound.cipher.(*core.StreamCipher) + if !ok { + return nil, fmt.Errorf("%s is not none or a supported stream cipher in ssr", cipher) + } + ivSize = streamCipher.IVSize() + key = streamCipher.Key + } + obfs, obfsOverhead, err := obfs.PickObfs(options.Obfs, &obfs.Base{ + Host: options.Server, + Port: int(options.ServerPort), + Key: key, + IVSize: ivSize, + Param: options.ObfsParam, + }) + if err != nil { + return nil, E.Cause(err, "initialize obfs") + } + protocol, err := protocol.PickProtocol(options.Protocol, &protocol.Base{ + Key: key, + Overhead: obfsOverhead, + Param: options.ProtocolParam, + }) + if err != nil { + return nil, E.Cause(err, "initialize protocol") + } + outbound.obfs = obfs + outbound.protocol = protocol + return outbound, nil +} + +func (h *ShadowsocksR) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.AppendContext(ctx) + metadata.Outbound = h.tag + metadata.Destination = destination + switch network { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + conn, err := h.dialer.DialContext(ctx, network, h.serverAddr) + if err != nil { + return nil, err + } + conn = h.cipher.StreamConn(h.obfs.StreamConn(conn)) + writeIv, err := conn.(*shadowstream.Conn).ObtainWriteIV() + if err != nil { + conn.Close() + return nil, err + } + conn = h.protocol.StreamConn(conn, writeIv) + err = M.SocksaddrSerializer.WriteAddrPort(conn, destination) + if err != nil { + conn.Close() + return nil, E.Cause(err, "write request") + } + return conn, nil + case N.NetworkUDP: + conn, err := h.ListenPacket(ctx, destination) + if err != nil { + return nil, err + } + return bufio.NewBindPacketConn(conn, destination), nil + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } +} + +func (h *ShadowsocksR) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.AppendContext(ctx) + metadata.Outbound = h.tag + metadata.Destination = destination + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr) + if err != nil { + return nil, err + } + packetConn := h.cipher.PacketConn(bufio.NewUnbindPacketConn(outConn)) + packetConn = h.protocol.PacketConn(packetConn) + packetConn = &ssPacketConn{packetConn, outConn.RemoteAddr()} + return packetConn, nil +} + +func (h *ShadowsocksR) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return NewConnection(ctx, h, conn, metadata) +} + +func (h *ShadowsocksR) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return NewPacketConnection(ctx, h, conn, metadata) +} + +type ssPacketConn struct { + net.PacketConn + rAddr net.Addr +} + +func (spc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { + packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) + if err != nil { + return + } + return spc.PacketConn.WriteTo(packet[3:], spc.rAddr) +} + +func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, _, e := spc.PacketConn.ReadFrom(b) + if e != nil { + return 0, nil, e + } + + addr := socks5.SplitAddr(b[:n]) + if addr == nil { + return 0, nil, errors.New("parse addr error") + } + + udpAddr := addr.UDPAddr() + if udpAddr == nil { + return 0, nil, errors.New("parse addr error") + } -func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { - return nil, os.ErrInvalid + copy(b, b[len(addr):]) + return n - len(addr), udpAddr, e } diff --git a/outbound/shadowsocksr_stub.go b/outbound/shadowsocksr_stub.go index 94971da0bc..d362587614 100644 --- a/outbound/shadowsocksr_stub.go +++ b/outbound/shadowsocksr_stub.go @@ -12,5 +12,5 @@ import ( ) func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { - return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") + return nil, E.New(`ShadowsocksR is not included in this build, rebuild with -tags with_shadowsocksr`) } diff --git a/test/clash_test.go b/test/clash_test.go index ffd3e10c78..1f627dc432 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -36,6 +36,7 @@ const ( ImageHysteria2 = "tobyxdd/hysteria:v2" ImageNginx = "nginx:stable" ImageShadowTLS = "ghcr.io/ihciah/shadow-tls:latest" + ImageShadowsocksR = "teddysun/shadowsocks-r:latest" ImageXRayCore = "teddysun/xray:latest" ImageShadowsocksLegacy = "mritd/shadowsocks:latest" ImageTUICServer = "kilvn/tuic-server:latest" @@ -53,6 +54,7 @@ var allImages = []string{ ImageHysteria2, ImageNginx, ImageShadowTLS, + ImageShadowsocksR, ImageXRayCore, ImageShadowsocksLegacy, ImageTUICServer, diff --git a/test/go.mod b/test/go.mod index 339bd6f2df..3e794efcca 100644 --- a/test/go.mod +++ b/test/go.mod @@ -24,6 +24,8 @@ require ( require ( berty.tech/go-libtor v1.0.385 // indirect + github.com/Dreamacro/clash v1.17.0 // indirect + github.com/Dreamacro/protobytes v0.0.0-20230617041236-6500a9f4f158 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect diff --git a/test/go.sum b/test/go.sum index ccd3672d5f..236173a1a8 100644 --- a/test/go.sum +++ b/test/go.sum @@ -1,6 +1,10 @@ berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Dreamacro/clash v1.17.0 h1:LWtp6KcnrCiujY58ufI8pylI+hbCBgSCsLI90EWhpi4= +github.com/Dreamacro/clash v1.17.0/go.mod h1:PtcAft7sdsK325BD6uwm8wvhOkMV3TCeED6dfZ/lnfE= +github.com/Dreamacro/protobytes v0.0.0-20230617041236-6500a9f4f158 h1:JFnwKplz9hj8ubqYjm8HkgZS1Rvz9yW+u/XCNNTxr0k= +github.com/Dreamacro/protobytes v0.0.0-20230617041236-6500a9f4f158/go.mod h1:QvmEZ/h6KXszPOr2wUFl7Zn3hfFNYdfbXwPVDTyZs6k= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= diff --git a/test/shadowsocksr_test.go b/test/shadowsocksr_test.go new file mode 100644 index 0000000000..fa034b56bf --- /dev/null +++ b/test/shadowsocksr_test.go @@ -0,0 +1,48 @@ +package main + +import ( + "net/netip" + "testing" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" +) + +func TestShadowsocksR(t *testing.T) { + startDockerContainer(t, DockerOptions{ + Image: ImageShadowsocksR, + Ports: []uint16{serverPort, testPort}, + Bind: map[string]string{ + "shadowsocksr.json": "/etc/shadowsocks-r/config.json", + }, + }) + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.IPv4Unspecified()), + ListenPort: clientPort, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeShadowsocksR, + ShadowsocksROptions: option.ShadowsocksROutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Method: "aes-256-cfb", + Password: "password0", + Obfs: "plain", + Protocol: "origin", + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} diff --git a/transport/clashssr/obfs/base.go b/transport/clashssr/obfs/base.go new file mode 100644 index 0000000000..7fd1b84cf7 --- /dev/null +++ b/transport/clashssr/obfs/base.go @@ -0,0 +1,9 @@ +package obfs + +type Base struct { + Host string + Port int + Key []byte + IVSize int + Param string +} diff --git a/transport/clashssr/obfs/http_post.go b/transport/clashssr/obfs/http_post.go new file mode 100644 index 0000000000..4be6cbe8b1 --- /dev/null +++ b/transport/clashssr/obfs/http_post.go @@ -0,0 +1,9 @@ +package obfs + +func init() { + register("http_post", newHTTPPost, 0) +} + +func newHTTPPost(b *Base) Obfs { + return &httpObfs{Base: b, post: true} +} diff --git a/transport/clashssr/obfs/http_simple.go b/transport/clashssr/obfs/http_simple.go new file mode 100644 index 0000000000..c1ea76738f --- /dev/null +++ b/transport/clashssr/obfs/http_simple.go @@ -0,0 +1,405 @@ +package obfs + +import ( + "bytes" + "encoding/hex" + "io" + "math/rand" + "net" + "strconv" + "strings" + + "github.com/Dreamacro/clash/common/pool" +) + +func init() { + register("http_simple", newHTTPSimple, 0) +} + +type httpObfs struct { + *Base + post bool +} + +func newHTTPSimple(b *Base) Obfs { + return &httpObfs{Base: b} +} + +type httpConn struct { + net.Conn + *httpObfs + hasSentHeader bool + hasRecvHeader bool + buf []byte +} + +func (h *httpObfs) StreamConn(c net.Conn) net.Conn { + return &httpConn{Conn: c, httpObfs: h} +} + +func (c *httpConn) Read(b []byte) (int, error) { + if c.buf != nil { + n := copy(b, c.buf) + if n == len(c.buf) { + c.buf = nil + } else { + c.buf = c.buf[n:] + } + return n, nil + } + + if c.hasRecvHeader { + return c.Conn.Read(b) + } + + 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 (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 := pool.GetBuffer() + defer pool.PutBuffer(buf) + if c.post { + buf.WriteString("POST /") + } else { + buf.WriteString("GET /") + } + packURLEncodedHeadData(buf, headData) + buf.WriteString(" HTTP/1.1\r\nHost: " + host) + if c.Port != 80 { + buf.WriteString(":" + strconv.Itoa(c.Port)) + } + buf.WriteString("\r\n") + if len(body) > 0 { + buf.WriteString(body + "\r\n\r\n") + } else { + buf.WriteString("User-Agent: ") + buf.WriteString(userAgent[rand.Intn(len(userAgent))]) + buf.WriteString("\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\n") + if c.post { + packBoundary(buf) + } + buf.WriteString("DNT: 1\r\nConnection: keep-alive\r\n\r\n") + } + buf.Write(b) + _, err := c.Conn.Write(buf.Bytes()) + if err != nil { + return 0, nil + } + c.hasSentHeader = true + return bLength, nil +} + +func packURLEncodedHeadData(buf *bytes.Buffer, data []byte) { + dataLength := len(data) + for i := 0; i < dataLength; i++ { + buf.WriteRune('%') + buf.WriteString(hex.EncodeToString(data[i : i+1])) + } +} + +func packBoundary(buf *bytes.Buffer) { + buf.WriteString("Content-Type: multipart/form-data; boundary=") + set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + for i := 0; i < 32; i++ { + buf.WriteByte(set[rand.Intn(62)]) + } + buf.WriteString("\r\n") +} + +var userAgent = []string{ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", + "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36", + "Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0", + "Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36", + "Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", + "Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", + "Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", + "Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1", + "Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", +} diff --git a/transport/clashssr/obfs/obfs.go b/transport/clashssr/obfs/obfs.go new file mode 100644 index 0000000000..c56acc8adb --- /dev/null +++ b/transport/clashssr/obfs/obfs.go @@ -0,0 +1,42 @@ +package obfs + +import ( + "errors" + "fmt" + "net" +) + +var ( + errTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number") + errTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data") + errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed") +) + +type authData struct { + clientID [32]byte +} + +type Obfs interface { + StreamConn(net.Conn) net.Conn +} + +type obfsCreator func(b *Base) Obfs + +var obfsList = make(map[string]struct { + overhead int + new obfsCreator +}) + +func register(name string, c obfsCreator, o int) { + obfsList[name] = struct { + overhead int + new obfsCreator + }{overhead: o, new: c} +} + +func PickObfs(name string, b *Base) (Obfs, int, error) { + if choice, ok := obfsList[name]; ok { + return choice.new(b), choice.overhead, nil + } + return nil, 0, fmt.Errorf("Obfs %s not supported", name) +} diff --git a/transport/clashssr/obfs/plain.go b/transport/clashssr/obfs/plain.go new file mode 100644 index 0000000000..eb998a47b4 --- /dev/null +++ b/transport/clashssr/obfs/plain.go @@ -0,0 +1,15 @@ +package obfs + +import "net" + +type plain struct{} + +func init() { + register("plain", newPlain, 0) +} + +func newPlain(b *Base) Obfs { + return &plain{} +} + +func (p *plain) StreamConn(c net.Conn) net.Conn { return c } diff --git a/transport/clashssr/obfs/random_head.go b/transport/clashssr/obfs/random_head.go new file mode 100644 index 0000000000..b10b01c56b --- /dev/null +++ b/transport/clashssr/obfs/random_head.go @@ -0,0 +1,71 @@ +package obfs + +import ( + "encoding/binary" + "hash/crc32" + "math/rand" + "net" + + "github.com/Dreamacro/clash/common/pool" +) + +func init() { + register("random_head", newRandomHead, 0) +} + +type randomHead struct { + *Base +} + +func newRandomHead(b *Base) Obfs { + return &randomHead{Base: b} +} + +type randomHeadConn struct { + net.Conn + *randomHead + hasSentHeader bool + rawTransSent bool + rawTransRecv bool + buf []byte +} + +func (r *randomHead) StreamConn(c net.Conn) net.Conn { + return &randomHeadConn{Conn: c, randomHead: r} +} + +func (c *randomHeadConn) Read(b []byte) (int, error) { + if c.rawTransRecv { + return c.Conn.Read(b) + } + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) + c.Conn.Read(buf) + c.rawTransRecv = true + c.Write(nil) + return 0, nil +} + +func (c *randomHeadConn) Write(b []byte) (int, error) { + if c.rawTransSent { + return c.Conn.Write(b) + } + c.buf = append(c.buf, b...) + if !c.hasSentHeader { + c.hasSentHeader = true + dataLength := rand.Intn(96) + 4 + buf := pool.Get(dataLength + 4) + defer pool.Put(buf) + rand.Read(buf[:dataLength]) + binary.LittleEndian.PutUint32(buf[dataLength:], 0xffffffff-crc32.ChecksumIEEE(buf[:dataLength])) + _, err := c.Conn.Write(buf) + return len(b), err + } + if c.rawTransRecv { + _, err := c.Conn.Write(c.buf) + c.buf = nil + c.rawTransSent = true + return len(b), err + } + return len(b), nil +} diff --git a/transport/clashssr/obfs/tls1.2_ticket_auth.go b/transport/clashssr/obfs/tls1.2_ticket_auth.go new file mode 100644 index 0000000000..10f2786add --- /dev/null +++ b/transport/clashssr/obfs/tls1.2_ticket_auth.go @@ -0,0 +1,226 @@ +package obfs + +import ( + "bytes" + "crypto/hmac" + "encoding/binary" + "math/rand" + "net" + "strings" + "time" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/transport/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 := pool.GetBuffer() + defer pool.PutBuffer(buf) + 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 := pool.GetBuffer() + defer pool.PutBuffer(data) + + 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 := pool.GetBuffer() + defer pool.PutBuffer(ext) + + 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 := pool.GetBuffer() + defer pool.PutBuffer(ret) + + 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 := pool.GetBuffer() + defer pool.PutBuffer(buf) + + buf.Write([]byte{0x14, 3, 3, 0, 1, 1, 0x16, 3, 3, 0, 0x20}) + tools.AppendRandBytes(buf, 22) + buf.Write(c.hmacSHA1(buf.Bytes())[:10]) + buf.ReadFrom(&c.sendBuf) + + c.handshakeStatus = 8 + + _, err := c.Conn.Write(buf.Bytes()) + return 0, err + } + return length, nil +} + +func packData(buf *bytes.Buffer, data []byte) { + buf.Write([]byte{0x17, 3, 3}) + binary.Write(buf, binary.BigEndian, uint16(len(data))) + buf.Write(data) +} + +func (t *tls12Ticket) packAuthData(buf *bytes.Buffer) { + binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix())) + tools.AppendRandBytes(buf, 18) + buf.Write(t.hmacSHA1(buf.Bytes()[buf.Len()-22:])[:10]) +} + +func packSNIData(buf *bytes.Buffer, u string) { + len := uint16(len(u)) + buf.Write([]byte{0, 0}) + binary.Write(buf, binary.BigEndian, len+5) + binary.Write(buf, binary.BigEndian, len+3) + buf.WriteByte(0) + binary.Write(buf, binary.BigEndian, len) + buf.WriteString(u) +} + +func (c *tls12TicketConn) packTicketBuf(buf *bytes.Buffer, u string) { + length := 16 * (rand.Intn(17) + 8) + buf.Write([]byte{0, 0x23}) + binary.Write(buf, binary.BigEndian, uint16(length)) + tools.AppendRandBytes(buf, length) +} + +func (t *tls12Ticket) hmacSHA1(data []byte) []byte { + key := pool.Get(len(t.Key) + 32) + defer pool.Put(key) + copy(key, t.Key) + copy(key[len(t.Key):], t.clientID[:]) + + sha1Data := tools.HmacSHA1(key, data) + return sha1Data[:10] +} + +func (t *tls12Ticket) getHost() string { + host := t.Param + if len(host) == 0 { + host = t.Host + } + if len(host) > 0 && host[len(host)-1] >= '0' && host[len(host)-1] <= '9' { + host = "" + } + hosts := strings.Split(host, ",") + host = hosts[rand.Intn(len(hosts))] + return host +} diff --git a/transport/clashssr/protocol/auth_aes128_md5.go b/transport/clashssr/protocol/auth_aes128_md5.go new file mode 100644 index 0000000000..d3bc941727 --- /dev/null +++ b/transport/clashssr/protocol/auth_aes128_md5.go @@ -0,0 +1,18 @@ +package protocol + +import "github.com/Dreamacro/clash/transport/ssr/tools" + +func init() { + register("auth_aes128_md5", newAuthAES128MD5, 9) +} + +func newAuthAES128MD5(b *Base) Protocol { + a := &authAES128{ + Base: b, + authData: &authData{}, + authAES128Function: &authAES128Function{salt: "auth_aes128_md5", hmac: tools.HmacMD5, hashDigest: tools.MD5Sum}, + userData: &userData{}, + } + a.initUserData() + return a +} diff --git a/transport/clashssr/protocol/auth_aes128_sha1.go b/transport/clashssr/protocol/auth_aes128_sha1.go new file mode 100644 index 0000000000..99c2f6f8b2 --- /dev/null +++ b/transport/clashssr/protocol/auth_aes128_sha1.go @@ -0,0 +1,277 @@ +package protocol + +import ( + "bytes" + "encoding/binary" + "math" + "math/rand" + "net" + "strconv" + "strings" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/transport/ssr/tools" +) + +type ( + hmacMethod func(key, data []byte) []byte + hashDigestMethod func([]byte) []byte +) + +func init() { + 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 { + a := &authAES128{ + Base: b, + authData: &authData{}, + authAES128Function: &authAES128Function{salt: "auth_aes128_sha1", hmac: tools.HmacSHA1, hashDigest: tools.SHA1Sum}, + userData: &userData{}, + } + a.initUserData() + return a +} + +func (a *authAES128) initUserData() { + params := strings.Split(a.Param, ":") + if len(params) > 1 { + if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { + binary.LittleEndian.PutUint32(a.userID[:], uint32(userID)) + a.userKey = a.hashDigest([]byte(params[1])) + } + } + 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 len(b) < 4 { + return nil, errAuthAES128LengthError + } + if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) { + return nil, errAuthAES128ChksumError + } + return b[:len(b)-4], nil +} + +func (a *authAES128) EncodePacket(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + buf.Write(a.userID[:]) + buf.Write(a.hmac(a.userKey, buf.Bytes())[:4]) + return nil +} + +func (a *authAES128) packData(poolBuf *bytes.Buffer, data []byte, fullDataLength int) { + dataLength := len(data) + randDataLength := a.getRandDataLengthForPackData(dataLength, fullDataLength) + /* + 2: uint16 LittleEndian packedDataLength + 2: hmac of packedDataLength + 3: maxRandDataLengthPrefix (min:1) + 4: hmac of packedData except the last 4 bytes + */ + packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4 + if randDataLength < 128 { + packedDataLength -= 2 + } + + macKey := pool.Get(len(a.userKey) + 4) + defer pool.Put(macKey) + copy(macKey, a.userKey) + binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID) + a.packID++ + + binary.Write(poolBuf, binary.LittleEndian, uint16(packedDataLength)) + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-2:])[:2]) + a.packRandData(poolBuf, randDataLength) + poolBuf.Write(data) + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:])[:4]) +} + +func trapezoidRandom(max int, d float64) int { + base := rand.Float64() + if d-0 > 1e-6 { + a := 1 - d + base = (math.Sqrt(a*a+4*d*base) - a) / (2 * d) + } + return int(base * float64(max)) +} + +func (a *authAES128) getRandDataLengthForPackData(dataLength, fullDataLength int) int { + if fullDataLength >= 32*1024-a.Overhead { + return 0 + } + // 1460: tcp_mss + revLength := 1460 - dataLength - 9 + if revLength == 0 { + return 0 + } + if revLength < 0 { + if revLength > -1460 { + return trapezoidRandom(revLength+1460, -0.3) + } + return rand.Intn(32) + } + if dataLength > 900 { + return rand.Intn(revLength) + } + return trapezoidRandom(revLength, -0.3) +} + +func (a *authAES128) packAuthData(poolBuf *bytes.Buffer, data []byte) { + if len(data) == 0 { + return + } + dataLength := len(data) + randDataLength := a.getRandDataLengthForPackAuthData(dataLength) + /* + 7: checkHead(1) and hmac of checkHead(6) + 4: userID + 16: encrypted data of authdata(12), uint16 BigEndian packedDataLength(2) and uint16 BigEndian randDataLength(2) + 4: hmac of userID and encrypted data + 4: hmac of packedAuthData except the last 4 bytes + */ + packedAuthDataLength := 7 + 4 + 16 + 4 + randDataLength + dataLength + 4 + + macKey := pool.Get(len(a.iv) + len(a.Key)) + defer pool.Put(macKey) + copy(macKey, a.iv) + copy(macKey[len(a.iv):], a.Key) + + poolBuf.WriteByte(byte(rand.Intn(256))) + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes())[:6]) + poolBuf.Write(a.userID[:]) + err := a.authData.putEncryptedData(poolBuf, a.userKey, [2]int{packedAuthDataLength, randDataLength}, a.salt) + if err != nil { + poolBuf.Reset() + return + } + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[7:])[:4]) + tools.AppendRandBytes(poolBuf, randDataLength) + poolBuf.Write(data) + poolBuf.Write(a.hmac(a.userKey, poolBuf.Bytes())[:4]) +} + +func (a *authAES128) getRandDataLengthForPackAuthData(size int) int { + if size > 400 { + return rand.Intn(512) + } + return rand.Intn(1024) +} + +func (a *authAES128) packRandData(poolBuf *bytes.Buffer, size int) { + if size < 128 { + poolBuf.WriteByte(byte(size + 1)) + tools.AppendRandBytes(poolBuf, size) + return + } + poolBuf.WriteByte(255) + binary.Write(poolBuf, binary.LittleEndian, uint16(size+3)) + tools.AppendRandBytes(poolBuf, size) +} diff --git a/transport/clashssr/protocol/auth_chain_a.go b/transport/clashssr/protocol/auth_chain_a.go new file mode 100644 index 0000000000..0cbdfd70c2 --- /dev/null +++ b/transport/clashssr/protocol/auth_chain_a.go @@ -0,0 +1,306 @@ +package protocol + +import ( + "bytes" + "crypto/cipher" + "crypto/rand" + "crypto/rc4" + "encoding/base64" + "encoding/binary" + "net" + "strconv" + "strings" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/transport/shadowsocks/core" + "github.com/Dreamacro/clash/transport/ssr/tools" +) + +func init() { + register("auth_chain_a", newAuthChainA, 4) +} + +type randDataLengthMethod func(int, []byte, *tools.XorShift128Plus) int + +type authChainA struct { + *Base + *authData + *userData + iv []byte + salt string + hasSentHeader bool + rawTrans bool + lastClientHash []byte + lastServerHash []byte + encrypter cipher.Stream + decrypter cipher.Stream + randomClient tools.XorShift128Plus + randomServer tools.XorShift128Plus + randDataLength randDataLengthMethod + packID uint32 + recvID uint32 +} + +func newAuthChainA(b *Base) Protocol { + a := &authChainA{ + Base: b, + authData: &authData{}, + userData: &userData{}, + salt: "auth_chain_a", + } + a.initUserData() + return a +} + +func (a *authChainA) initUserData() { + params := strings.Split(a.Param, ":") + if len(params) > 1 { + if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { + binary.LittleEndian.PutUint32(a.userID[:], uint32(userID)) + a.userKey = []byte(params[1]) + } + } + 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 + } + + serverHash := tools.HmacMD5(macKey, src.Bytes()[:length+2]) + if !bytes.Equal(serverHash[:2], src.Bytes()[length+2:length+4]) { + a.rawTrans = true + src.Reset() + return errAuthChainChksumError + } + a.lastServerHash = serverHash + + pos := 2 + if dataLength > 0 && randDataLength > 0 { + pos += getRandStartPos(randDataLength, &a.randomServer) + } + wantedData := src.Bytes()[pos : pos+dataLength] + a.decrypter.XORKeyStream(wantedData, wantedData) + if a.recvID == 1 { + dst.Write(wantedData[2:]) + } else { + dst.Write(wantedData) + } + a.recvID++ + src.Next(length + 4) + } + return nil +} + +func (a *authChainA) Encode(buf *bytes.Buffer, b []byte) error { + if !a.hasSentHeader { + dataLength := getDataLength(b) + a.packAuthData(buf, b[:dataLength]) + b = b[dataLength:] + a.hasSentHeader = true + } + for len(b) > 2800 { + a.packData(buf, b[:2800]) + b = b[2800:] + } + if len(b) > 0 { + a.packData(buf, b) + } + return nil +} + +func (a *authChainA) DecodePacket(b []byte) ([]byte, error) { + if len(b) < 9 { + return nil, errAuthChainLengthError + } + if !bytes.Equal(tools.HmacMD5(a.userKey, b[:len(b)-1])[:1], b[len(b)-1:]) { + return nil, errAuthChainChksumError + } + md5Data := tools.HmacMD5(a.Key, b[len(b)-8:len(b)-1]) + + randDataLength := udpGetRandLength(md5Data, &a.randomServer) + + key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16) + rc4Cipher, err := rc4.NewCipher(key) + if err != nil { + return nil, err + } + wantedData := b[:len(b)-8-randDataLength] + rc4Cipher.XORKeyStream(wantedData, wantedData) + return wantedData, nil +} + +func (a *authChainA) EncodePacket(buf *bytes.Buffer, b []byte) error { + authData := pool.Get(3) + defer pool.Put(authData) + rand.Read(authData) + + md5Data := tools.HmacMD5(a.Key, authData) + + randDataLength := udpGetRandLength(md5Data, &a.randomClient) + + key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16) + rc4Cipher, err := rc4.NewCipher(key) + if err != nil { + return err + } + rc4Cipher.XORKeyStream(b, b) + + buf.Write(b) + tools.AppendRandBytes(buf, randDataLength) + buf.Write(authData) + binary.Write(buf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(md5Data[:4])) + buf.Write(tools.HmacMD5(a.userKey, buf.Bytes())[:1]) + return nil +} + +func (a *authChainA) packAuthData(poolBuf *bytes.Buffer, data []byte) { + /* + dataLength := len(data) + 12: checkHead(4) and hmac of checkHead(8) + 4: uint32 LittleEndian uid (uid = userID ^ last client hash) + 16: encrypted data of authdata(12), uint16 LittleEndian overhead(2) and uint16 LittleEndian number zero(2) + 4: last server hash(4) + packedAuthDataLength := 12 + 4 + 16 + 4 + dataLength + */ + + macKey := pool.Get(len(a.iv) + len(a.Key)) + defer pool.Put(macKey) + copy(macKey, a.iv) + copy(macKey[len(a.iv):], a.Key) + + // check head + tools.AppendRandBytes(poolBuf, 4) + a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes()) + a.initRC4Cipher() + poolBuf.Write(a.lastClientHash[:8]) + // uid + binary.Write(poolBuf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(a.lastClientHash[8:12])) + // encrypted data + err := a.putEncryptedData(poolBuf, a.userKey, [2]int{a.Overhead, 0}, a.salt) + if err != nil { + poolBuf.Reset() + return + } + // last server hash + a.lastServerHash = tools.HmacMD5(a.userKey, poolBuf.Bytes()[12:]) + poolBuf.Write(a.lastServerHash[:4]) + // packed data + a.packData(poolBuf, data) +} + +func (a *authChainA) packData(poolBuf *bytes.Buffer, data []byte) { + a.encrypter.XORKeyStream(data, data) + + macKey := pool.Get(len(a.userKey) + 4) + defer pool.Put(macKey) + copy(macKey, a.userKey) + binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID) + a.packID++ + + length := uint16(len(data)) ^ binary.LittleEndian.Uint16(a.lastClientHash[14:16]) + + originalLength := poolBuf.Len() + binary.Write(poolBuf, binary.LittleEndian, length) + a.putMixedRandDataAndData(poolBuf, data) + a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes()[originalLength:]) + poolBuf.Write(a.lastClientHash[:2]) +} + +func (a *authChainA) putMixedRandDataAndData(poolBuf *bytes.Buffer, data []byte) { + randDataLength := a.randDataLength(len(data), a.lastClientHash, &a.randomClient) + if len(data) == 0 { + tools.AppendRandBytes(poolBuf, randDataLength) + return + } + if randDataLength > 0 { + startPos := getRandStartPos(randDataLength, &a.randomClient) + tools.AppendRandBytes(poolBuf, startPos) + poolBuf.Write(data) + tools.AppendRandBytes(poolBuf, randDataLength-startPos) + return + } + poolBuf.Write(data) +} + +func getRandStartPos(length int, random *tools.XorShift128Plus) int { + if length == 0 { + return 0 + } + return int(int64(random.Next()%8589934609) % int64(length)) +} + +func (a *authChainA) getRandLength(length int, lastHash []byte, random *tools.XorShift128Plus) int { + if length > 1440 { + return 0 + } + random.InitFromBinAndLength(lastHash, length) + if length > 1300 { + return int(random.Next() % 31) + } + if length > 900 { + return int(random.Next() % 127) + } + if length > 400 { + return int(random.Next() % 521) + } + return int(random.Next() % 1021) +} + +func (a *authChainA) initRC4Cipher() { + key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(a.lastClientHash), 16) + a.encrypter, _ = rc4.NewCipher(key) + a.decrypter, _ = rc4.NewCipher(key) +} + +func udpGetRandLength(lastHash []byte, random *tools.XorShift128Plus) int { + random.InitFromBin(lastHash) + return int(random.Next() % 127) +} diff --git a/transport/clashssr/protocol/auth_chain_b.go b/transport/clashssr/protocol/auth_chain_b.go new file mode 100644 index 0000000000..857b2a3aa1 --- /dev/null +++ b/transport/clashssr/protocol/auth_chain_b.go @@ -0,0 +1,97 @@ +package protocol + +import ( + "net" + "sort" + + "github.com/Dreamacro/clash/transport/ssr/tools" +) + +func init() { + register("auth_chain_b", newAuthChainB, 4) +} + +type authChainB struct { + *authChainA + dataSizeList []int + dataSizeList2 []int +} + +func newAuthChainB(b *Base) Protocol { + a := &authChainB{ + authChainA: &authChainA{ + Base: b, + authData: &authData{}, + userData: &userData{}, + salt: "auth_chain_b", + }, + } + a.initUserData() + return a +} + +func (a *authChainB) StreamConn(c net.Conn, iv []byte) net.Conn { + p := &authChainB{ + authChainA: &authChainA{ + Base: a.Base, + authData: a.next(), + userData: a.userData, + salt: a.salt, + packID: 1, + recvID: 1, + }, + } + p.iv = iv + p.randDataLength = p.getRandLength + p.initDataSize() + return &Conn{Conn: c, Protocol: p} +} + +func (a *authChainB) initDataSize() { + a.dataSizeList = a.dataSizeList[:0] + a.dataSizeList2 = a.dataSizeList2[:0] + + a.randomServer.InitFromBin(a.Key) + length := a.randomServer.Next()%8 + 4 + for ; length > 0; length-- { + a.dataSizeList = append(a.dataSizeList, int(a.randomServer.Next()%2340%2040%1440)) + } + sort.Ints(a.dataSizeList) + + length = a.randomServer.Next()%16 + 8 + for ; length > 0; length-- { + a.dataSizeList2 = append(a.dataSizeList2, int(a.randomServer.Next()%2340%2040%1440)) + } + sort.Ints(a.dataSizeList2) +} + +func (a *authChainB) getRandLength(length int, lashHash []byte, random *tools.XorShift128Plus) int { + if length >= 1440 { + return 0 + } + random.InitFromBinAndLength(lashHash, length) + pos := sort.Search(len(a.dataSizeList), func(i int) bool { return a.dataSizeList[i] >= length+a.Overhead }) + finalPos := pos + int(random.Next()%uint64(len(a.dataSizeList))) + if finalPos < len(a.dataSizeList) { + return a.dataSizeList[finalPos] - length - a.Overhead + } + + pos = sort.Search(len(a.dataSizeList2), func(i int) bool { return a.dataSizeList2[i] >= length+a.Overhead }) + finalPos = pos + int(random.Next()%uint64(len(a.dataSizeList2))) + if finalPos < len(a.dataSizeList2) { + return a.dataSizeList2[finalPos] - length - a.Overhead + } + if finalPos < pos+len(a.dataSizeList2)-1 { + return 0 + } + if length > 1300 { + return int(random.Next() % 31) + } + if length > 900 { + return int(random.Next() % 127) + } + if length > 400 { + return int(random.Next() % 521) + } + return int(random.Next() % 1021) +} diff --git a/transport/clashssr/protocol/auth_sha1_v4.go b/transport/clashssr/protocol/auth_sha1_v4.go new file mode 100644 index 0000000000..30392c9e77 --- /dev/null +++ b/transport/clashssr/protocol/auth_sha1_v4.go @@ -0,0 +1,182 @@ +package protocol + +import ( + "bytes" + "encoding/binary" + "hash/adler32" + "hash/crc32" + "math/rand" + "net" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/transport/ssr/tools" +) + +func init() { + register("auth_sha1_v4", newAuthSHA1V4, 7) +} + +type authSHA1V4 struct { + *Base + *authData + iv []byte + hasSentHeader bool + rawTrans bool +} + +func newAuthSHA1V4(b *Base) Protocol { + return &authSHA1V4{Base: b, authData: &authData{}} +} + +func (a *authSHA1V4) StreamConn(c net.Conn, iv []byte) net.Conn { + p := &authSHA1V4{Base: a.Base, authData: a.next()} + p.iv = iv + return &Conn{Conn: c, Protocol: p} +} + +func (a *authSHA1V4) PacketConn(c net.PacketConn) net.PacketConn { + return c +} + +func (a *authSHA1V4) Decode(dst, src *bytes.Buffer) error { + if a.rawTrans { + dst.ReadFrom(src) + return nil + } + for src.Len() > 4 { + if uint16(crc32.ChecksumIEEE(src.Bytes()[:2])&0xffff) != binary.LittleEndian.Uint16(src.Bytes()[2:4]) { + src.Reset() + return errAuthSHA1V4CRC32Error + } + + length := int(binary.BigEndian.Uint16(src.Bytes()[:2])) + if length >= 8192 || length < 7 { + a.rawTrans = true + src.Reset() + return errAuthSHA1V4LengthError + } + if length > src.Len() { + break + } + + if adler32.Checksum(src.Bytes()[:length-4]) != binary.LittleEndian.Uint32(src.Bytes()[length-4:length]) { + a.rawTrans = true + src.Reset() + return errAuthSHA1V4Adler32Error + } + + pos := int(src.Bytes()[4]) + if pos < 255 { + pos += 4 + } else { + pos = int(binary.BigEndian.Uint16(src.Bytes()[5:7])) + 4 + } + dst.Write(src.Bytes()[pos : length-4]) + src.Next(length) + } + return nil +} + +func (a *authSHA1V4) Encode(buf *bytes.Buffer, b []byte) error { + if !a.hasSentHeader { + dataLength := getDataLength(b) + + a.packAuthData(buf, b[:dataLength]) + b = b[dataLength:] + + a.hasSentHeader = true + } + for len(b) > 8100 { + a.packData(buf, b[:8100]) + b = b[8100:] + } + if len(b) > 0 { + a.packData(buf, b) + } + + return nil +} + +func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, error) { return b, nil } + +func (a *authSHA1V4) EncodePacket(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + return nil +} + +func (a *authSHA1V4) packData(poolBuf *bytes.Buffer, data []byte) { + dataLength := len(data) + randDataLength := a.getRandDataLength(dataLength) + /* + 2: uint16 BigEndian packedDataLength + 2: uint16 LittleEndian crc32Data & 0xffff + 3: maxRandDataLengthPrefix (min:1) + 4: adler32Data + */ + packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4 + if randDataLength < 128 { + packedDataLength -= 2 + } + + binary.Write(poolBuf, binary.BigEndian, uint16(packedDataLength)) + binary.Write(poolBuf, binary.LittleEndian, uint16(crc32.ChecksumIEEE(poolBuf.Bytes()[poolBuf.Len()-2:])&0xffff)) + a.packRandData(poolBuf, randDataLength) + poolBuf.Write(data) + binary.Write(poolBuf, binary.LittleEndian, adler32.Checksum(poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:])) +} + +func (a *authSHA1V4) packAuthData(poolBuf *bytes.Buffer, data []byte) { + dataLength := len(data) + randDataLength := a.getRandDataLength(12 + dataLength) + /* + 2: uint16 BigEndian packedAuthDataLength + 4: uint32 LittleEndian crc32Data + 3: maxRandDataLengthPrefix (min: 1) + 12: authDataLength + 10: hmacSHA1DataLength + */ + packedAuthDataLength := 2 + 4 + 3 + randDataLength + 12 + dataLength + 10 + if randDataLength < 128 { + packedAuthDataLength -= 2 + } + + salt := []byte("auth_sha1_v4") + crcData := pool.Get(len(salt) + len(a.Key) + 2) + defer pool.Put(crcData) + binary.BigEndian.PutUint16(crcData, uint16(packedAuthDataLength)) + copy(crcData[2:], salt) + copy(crcData[2+len(salt):], a.Key) + + key := pool.Get(len(a.iv) + len(a.Key)) + defer pool.Put(key) + copy(key, a.iv) + copy(key[len(a.iv):], a.Key) + + poolBuf.Write(crcData[:2]) + binary.Write(poolBuf, binary.LittleEndian, crc32.ChecksumIEEE(crcData)) + a.packRandData(poolBuf, randDataLength) + a.putAuthData(poolBuf) + poolBuf.Write(data) + poolBuf.Write(tools.HmacSHA1(key, poolBuf.Bytes()[poolBuf.Len()-packedAuthDataLength+10:])[:10]) +} + +func (a *authSHA1V4) 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.BigEndian, uint16(size+3)) + tools.AppendRandBytes(poolBuf, size) +} + +func (a *authSHA1V4) getRandDataLength(size int) int { + if size > 1200 { + return 0 + } + if size > 400 { + return rand.Intn(256) + } + return rand.Intn(512) +} diff --git a/transport/clashssr/protocol/base.go b/transport/clashssr/protocol/base.go new file mode 100644 index 0000000000..e187fcb7fa --- /dev/null +++ b/transport/clashssr/protocol/base.go @@ -0,0 +1,75 @@ +package 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/transport/shadowsocks/core" +) + +type Base struct { + Key []byte + Overhead int + Param string +} + +type userData struct { + userKey []byte + userID [4]byte +} + +type authData struct { + clientID [4]byte + connectionID uint32 + mutex sync.Mutex +} + +func (a *authData) next() *authData { + r := &authData{} + a.mutex.Lock() + defer a.mutex.Unlock() + if a.connectionID > 0xff000000 || a.connectionID == 0 { + rand.Read(a.clientID[:]) + a.connectionID = rand.Uint32() & 0xffffff + } + a.connectionID++ + copy(r.clientID[:], a.clientID[:]) + r.connectionID = a.connectionID + return r +} + +func (a *authData) putAuthData(buf *bytes.Buffer) { + binary.Write(buf, binary.LittleEndian, uint32(time.Now().Unix())) + buf.Write(a.clientID[:]) + binary.Write(buf, binary.LittleEndian, a.connectionID) +} + +func (a *authData) putEncryptedData(b *bytes.Buffer, userKey []byte, paddings [2]int, salt string) error { + encrypt := pool.Get(16) + defer pool.Put(encrypt) + binary.LittleEndian.PutUint32(encrypt, uint32(time.Now().Unix())) + copy(encrypt[4:], a.clientID[:]) + binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) + binary.LittleEndian.PutUint16(encrypt[12:], uint16(paddings[0])) + binary.LittleEndian.PutUint16(encrypt[14:], uint16(paddings[1])) + + cipherKey := core.Kdf(base64.StdEncoding.EncodeToString(userKey)+salt, 16) + block, err := aes.NewCipher(cipherKey) + if err != nil { + return err + } + iv := bytes.Repeat([]byte{0}, 16) + cbcCipher := cipher.NewCBCEncrypter(block, iv) + + cbcCipher.CryptBlocks(encrypt, encrypt) + + b.Write(encrypt) + return nil +} diff --git a/transport/clashssr/protocol/origin.go b/transport/clashssr/protocol/origin.go new file mode 100644 index 0000000000..80fdfa9a1b --- /dev/null +++ b/transport/clashssr/protocol/origin.go @@ -0,0 +1,33 @@ +package protocol + +import ( + "bytes" + "net" +) + +type origin struct{} + +func init() { register("origin", newOrigin, 0) } + +func newOrigin(b *Base) Protocol { return &origin{} } + +func (o *origin) StreamConn(c net.Conn, iv []byte) net.Conn { return c } + +func (o *origin) PacketConn(c net.PacketConn) net.PacketConn { return c } + +func (o *origin) Decode(dst, src *bytes.Buffer) error { + dst.ReadFrom(src) + return nil +} + +func (o *origin) Encode(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + return nil +} + +func (o *origin) DecodePacket(b []byte) ([]byte, error) { return b, nil } + +func (o *origin) EncodePacket(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + return nil +} diff --git a/transport/clashssr/protocol/packet.go b/transport/clashssr/protocol/packet.go new file mode 100644 index 0000000000..249db70a26 --- /dev/null +++ b/transport/clashssr/protocol/packet.go @@ -0,0 +1,36 @@ +package protocol + +import ( + "net" + + "github.com/Dreamacro/clash/common/pool" +) + +type PacketConn struct { + net.PacketConn + Protocol +} + +func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) + err := c.EncodePacket(buf, b) + if err != nil { + return 0, err + } + _, err = c.PacketConn.WriteTo(buf.Bytes(), addr) + return len(b), err +} + +func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, addr, err := c.PacketConn.ReadFrom(b) + if err != nil { + return n, addr, err + } + decoded, err := c.DecodePacket(b[:n]) + if err != nil { + return n, addr, err + } + copy(b, decoded) + return len(decoded), addr, nil +} diff --git a/transport/clashssr/protocol/protocol.go b/transport/clashssr/protocol/protocol.go new file mode 100644 index 0000000000..41bd984c8b --- /dev/null +++ b/transport/clashssr/protocol/protocol.go @@ -0,0 +1,76 @@ +package protocol + +import ( + "bytes" + "errors" + "fmt" + "math/rand" + "net" +) + +var ( + errAuthSHA1V4CRC32Error = errors.New("auth_sha1_v4 decode data wrong crc32") + errAuthSHA1V4LengthError = errors.New("auth_sha1_v4 decode data wrong length") + errAuthSHA1V4Adler32Error = errors.New("auth_sha1_v4 decode data wrong adler32") + errAuthAES128MACError = errors.New("auth_aes128 decode data wrong mac") + errAuthAES128LengthError = errors.New("auth_aes128 decode data wrong length") + errAuthAES128ChksumError = errors.New("auth_aes128 decode data wrong checksum") + errAuthChainLengthError = errors.New("auth_chain decode data wrong length") + errAuthChainChksumError = errors.New("auth_chain decode data wrong checksum") +) + +type Protocol interface { + StreamConn(net.Conn, []byte) net.Conn + PacketConn(net.PacketConn) net.PacketConn + Decode(dst, src *bytes.Buffer) error + Encode(buf *bytes.Buffer, b []byte) error + DecodePacket([]byte) ([]byte, error) + EncodePacket(buf *bytes.Buffer, b []byte) error +} + +type protocolCreator func(b *Base) Protocol + +var protocolList = make(map[string]struct { + overhead int + new protocolCreator +}) + +func register(name string, c protocolCreator, o int) { + protocolList[name] = struct { + overhead int + new protocolCreator + }{overhead: o, new: c} +} + +func PickProtocol(name string, b *Base) (Protocol, error) { + if choice, ok := protocolList[name]; ok { + b.Overhead += choice.overhead + return choice.new(b), nil + } + return nil, fmt.Errorf("protocol %s not supported", name) +} + +func getHeadSize(b []byte, defaultValue int) int { + if len(b) < 2 { + return defaultValue + } + headType := b[0] & 7 + switch headType { + case 1: + return 7 + case 4: + return 19 + case 3: + return 4 + int(b[1]) + } + return defaultValue +} + +func getDataLength(b []byte) int { + bLength := len(b) + dataLength := getHeadSize(b, 30) + rand.Intn(32) + if bLength < dataLength { + return bLength + } + return dataLength +} diff --git a/transport/clashssr/protocol/stream.go b/transport/clashssr/protocol/stream.go new file mode 100644 index 0000000000..3c846157a7 --- /dev/null +++ b/transport/clashssr/protocol/stream.go @@ -0,0 +1,50 @@ +package protocol + +import ( + "bytes" + "net" + + "github.com/Dreamacro/clash/common/pool" +) + +type Conn struct { + net.Conn + Protocol + decoded bytes.Buffer + underDecoded bytes.Buffer +} + +func (c *Conn) 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 + } + c.underDecoded.Write(buf[:n]) + err = c.Decode(&c.decoded, &c.underDecoded) + if err != nil { + return 0, err + } + n, _ = c.decoded.Read(b) + return n, nil +} + +func (c *Conn) Write(b []byte) (int, error) { + bLength := len(b) + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) + err := c.Encode(buf, b) + if err != nil { + return 0, err + } + _, err = c.Conn.Write(buf.Bytes()) + if err != nil { + return 0, err + } + return bLength, nil +}