Compare commits

..

24 Commits

Author SHA1 Message Date
6c83ff3496 Chore: update dependencies 2021-03-10 21:13:23 +08:00
f7f97ef625 Fix: some HTTP proxy request broken 2021-03-10 16:23:55 +08:00
5acdd72a1d Fix: remove host if host is ip string 2021-03-10 12:49:30 +08:00
f53686103d Chore: reset udp timeout after sending each packet (#1260) 2021-02-26 10:40:55 +08:00
f63c9eb22f Chore: update staticcheck command on actions 2021-02-21 19:37:37 +08:00
a37243cf30 Fix: store cache correctly 2021-02-21 01:07:22 +08:00
b3c1b4a840 Chore: update dependencies 2021-02-19 20:35:10 +08:00
14bbf6eedc Feature: support store group selected node to cache (enable by default) 2021-02-18 23:41:50 +08:00
aa81193d5b Feature: add darwin arm64 to Makefile (Apple Silicon) (#1234) 2021-02-18 18:15:09 +08:00
9eb98e399d Improve: refactor ssr and fix #995 (#1189)
Co-authored-by: goomada <madao@DESKTOP-IOEBS0C.localdomain>
2021-02-15 14:32:03 +08:00
d48cfecf60 Chore: API support patch ipv6 config (#1217) 2021-02-05 16:43:42 +08:00
6036fb63ba Chore: avoid provider unnecessary write file operations (#1210) 2021-02-02 17:52:46 +08:00
cd48f69b1f Fix: wrap net.Conn to avoid using *net.TCPConn.(ReadFrom) (#1209) 2021-02-01 20:06:45 +08:00
fcc594ae26 Chore: use jsdelivr CDN for Country.mmdb (#1057) 2021-01-30 00:40:35 +08:00
f4de055aa1 Refactor: make inbound request contextual 2021-01-23 14:58:09 +08:00
35925cb3da Chore: standardized Dockerfile label (#1191)
Signed-off-by: Junjie Yuan <yuan@junjie.pro>
2021-01-20 16:08:24 +08:00
ff430df845 Fix: connectivity of ssr auth_chain_(ab) protocol (#1180) 2021-01-13 23:35:41 +08:00
e4cdea2111 chore: use singleDo to get interface info 2021-01-13 17:30:54 +08:00
b6ee47a541 Fix: get general should return correct result (#1172) 2021-01-07 13:59:39 +08:00
b25009cde7 Fix: unnecessary write operation on provider (#1170) 2021-01-06 14:20:15 +08:00
6fedd7ec84 Fix: dns client should not bind local address 2021-01-04 00:51:53 +08:00
9619c3fb20 Fix: support unspecified UDP bind address (#1159) 2020-12-31 18:58:03 +08:00
02d029dd2d Fix: close http Response body on provider (#1154) 2020-12-29 11:28:22 +08:00
09c28e0355 Fix: fallback bind fn should not bind global unicast 2020-12-28 22:24:58 +08:00
63 changed files with 2278 additions and 2045 deletions

View File

@ -9,7 +9,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.15.x
go-version: 1.16
- name: Check out code into the Go module directory
uses: actions/checkout@v2
@ -26,7 +26,7 @@ jobs:
run: |
go test ./...
go vet ./...
go get -u honnef.co/go/tools/cmd/staticcheck
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck -- $(go list ./...)
- name: Build

View File

@ -10,7 +10,7 @@ RUN go mod download && \
mv ./bin/clash-docker /clash
FROM alpine:latest
LABEL org.opencontainers.image.source https://github.com/Dreamacro/clash
LABEL org.opencontainers.image.source="https://github.com/Dreamacro/clash"
RUN apk add --no-cache ca-certificates
COPY --from=builder /Country.mmdb /root/.config/clash/

View File

@ -8,6 +8,7 @@ GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clas
PLATFORM_LIST = \
darwin-amd64 \
darwin-arm64 \
linux-386 \
linux-amd64 \
linux-armv5 \
@ -36,6 +37,9 @@ docker:
darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@

View File

@ -6,33 +6,18 @@ import (
"strings"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
)
// HTTPAdapter is a adapter for HTTP connection
type HTTPAdapter struct {
net.Conn
metadata *C.Metadata
R *http.Request
}
// Metadata return destination metadata
func (h *HTTPAdapter) Metadata() *C.Metadata {
return h.metadata
}
// NewHTTP is HTTPAdapter generator
func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter {
// NewHTTP recieve normal http request and return HTTPContext
func NewHTTP(request *http.Request, conn net.Conn) *context.HTTPContext {
metadata := parseHTTPAddr(request)
metadata.Type = C.HTTP
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &HTTPAdapter{
metadata: metadata,
R: request,
Conn: conn,
}
return context.NewHTTPContext(conn, request, metadata)
}
// RemoveHopByHopHeaders remove hop-by-hop header
@ -58,3 +43,19 @@ func RemoveHopByHopHeaders(header http.Header) {
header.Del(strings.TrimSpace(h))
}
}
// RemoveExtraHTTPHostPort remove extra host port (example.com:80 --> example.com)
// It resolves the behavior of some HTTP servers that do not handle host:80 (e.g. baidu.com)
func RemoveExtraHTTPHostPort(req *http.Request) {
host := req.Host
if host == "" {
host = req.URL.Host
}
if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" {
host = pHost
}
req.Host = host
req.URL.Host = host
}

View File

@ -5,18 +5,16 @@ import (
"net/http"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
)
// NewHTTPS is HTTPAdapter generator
func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter {
// NewHTTPS recieve CONNECT request and return ConnContext
func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPCONNECT
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &SocketAdapter{
metadata: metadata,
Conn: conn,
}
return context.NewConnContext(conn, metadata)
}

View File

@ -5,21 +5,11 @@ import (
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
)
// SocketAdapter is a adapter for socks and redir connection
type SocketAdapter struct {
net.Conn
metadata *C.Metadata
}
// Metadata return destination metadata
func (s *SocketAdapter) Metadata() *C.Metadata {
return s.metadata
}
// NewSocket is SocketAdapter generator
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter {
// NewSocket recieve TCP inbound and return ConnContext
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext {
metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP
metadata.Type = source
@ -28,8 +18,5 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter
metadata.SrcPort = port
}
return &SocketAdapter{
Conn: conn,
metadata: metadata,
}
return context.NewConnContext(conn, metadata)
}

View File

@ -12,12 +12,13 @@ import (
"github.com/Dreamacro/clash/component/ssr/protocol"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
)
type ShadowSocksR struct {
*Base
cipher *core.StreamCipher
cipher core.Cipher
obfs obfs.Obfs
protocol protocol.Protocol
}
@ -36,17 +37,22 @@ type ShadowSocksROption struct {
}
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = obfs.NewConn(c, ssr.obfs)
c = ssr.obfs.StreamConn(c)
c = ssr.cipher.StreamConn(c)
conn, ok := c.(*shadowstream.Conn)
if !ok {
var (
iv []byte
err error
)
switch conn := c.(type) {
case *shadowstream.Conn:
iv, err = conn.ObtainWriteIV()
if err != nil {
return nil, err
}
case *shadowaead.Conn:
return nil, fmt.Errorf("invalid connection type")
}
iv, err := conn.ObtainWriteIV()
if err != nil {
return nil, err
}
c = protocol.NewConn(c, ssr.protocol, iv)
c = ssr.protocol.StreamConn(c, iv)
_, err = c.Write(serializesSocksAddr(metadata))
return c, err
}
@ -74,7 +80,7 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
}
pc = ssr.cipher.PacketConn(pc)
pc = protocol.NewPacketConn(pc, ssr.protocol)
pc = ssr.protocol.PacketConn(pc)
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
}
@ -90,35 +96,43 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
password := option.Password
coreCiph, err := core.PickCipher(cipher, nil, password)
if err != nil {
return nil, fmt.Errorf("ssr %s initialize cipher error: %w", addr, err)
return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err)
}
ciph, ok := coreCiph.(*core.StreamCipher)
if !ok {
return nil, fmt.Errorf("%s is not a supported stream cipher in ssr", cipher)
var (
ivSize int
key []byte
)
if option.Cipher == "dummy" {
ivSize = 0
key = core.Kdf(option.Password, 16)
} else {
ciph, ok := coreCiph.(*core.StreamCipher)
if !ok {
return nil, fmt.Errorf("%s is not dummy or a supported stream cipher in ssr", cipher)
}
ivSize = ciph.IVSize()
key = ciph.Key
}
obfs, err := obfs.PickObfs(option.Obfs, &obfs.Base{
IVSize: ciph.IVSize(),
Key: ciph.Key,
HeadLen: 30,
Host: option.Server,
Port: option.Port,
Param: option.ObfsParam,
obfs, obfsOverhead, err := obfs.PickObfs(option.Obfs, &obfs.Base{
Host: option.Server,
Port: option.Port,
Key: key,
IVSize: ivSize,
Param: option.ObfsParam,
})
if err != nil {
return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err)
}
protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{
IV: nil,
Key: ciph.Key,
TCPMss: 1460,
Param: option.ProtocolParam,
Key: key,
Overhead: obfsOverhead,
Param: option.ProtocolParam,
})
if err != nil {
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
}
protocol.SetOverhead(obfs.GetObfsOverhead() + protocol.GetProtocolOverhead())
return &ShadowSocksR{
Base: &Base{
@ -127,7 +141,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
tp: C.ShadowsocksR,
udp: option.UDP,
},
cipher: ciph,
cipher: coreCiph,
obfs: obfs,
protocol: protocol,
}, nil

View File

@ -122,7 +122,21 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
pc.Close()
}()
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindAddr.UDPAddr(), tcpConn: c}, ss), nil
// Support unspecified UDP bind address.
bindUDPAddr := bindAddr.UDPAddr()
if bindUDPAddr == nil {
err = errors.New("invalid UDP bind address")
return
} else if bindUDPAddr.IP.IsUnspecified() {
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
if err != nil {
return nil, err
}
bindUDPAddr.IP = serverAddr.IP
}
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
}
func NewSocks5(option Socks5Option) *Socks5 {

View File

@ -74,7 +74,7 @@ func (f *fetcher) Initial() (interface{}, error) {
}
}
if f.vehicle.Type() != File {
if f.vehicle.Type() != File && !isLocal {
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, err
}
@ -108,8 +108,10 @@ func (f *fetcher) Update() (interface{}, bool, error) {
return nil, false, err
}
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, false, err
if f.vehicle.Type() != File {
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, false, err
}
}
f.updatedAt = &now

View File

@ -107,6 +107,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
if err != nil {
return nil, err
}
defer resp.Body.Close()
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {

11
common/net/io.go Normal file
View File

@ -0,0 +1,11 @@
package net
import "io"
type ReadOnlyReader struct {
io.Reader
}
type WriteOnlyWriter struct {
io.Writer
}

View File

@ -3,8 +3,14 @@ package dialer
import (
"errors"
"net"
"time"
"github.com/Dreamacro/clash/common/singledo"
)
// In some OS, such as Windows, it takes a little longer to get interface information
var ifaceSingle = singledo.NewSingle(time.Second * 20)
var (
errPlatformNotSupport = errors.New("unsupport platform")
)
@ -52,12 +58,18 @@ func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
}
func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error {
iface, err := net.InterfaceByName(name)
if !ip.IsGlobalUnicast() {
return nil
}
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
return net.InterfaceByName(name)
})
if err != nil {
return err
}
addrs, err := iface.Addrs()
addrs, err := iface.(*net.Interface).Addrs()
if err != nil {
return err
}
@ -81,12 +93,14 @@ func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name st
}
func fallbackBindToListenConfig(name string) (string, error) {
iface, err := net.InterfaceByName(name)
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
return net.InterfaceByName(name)
})
if err != nil {
return "", err
}
addrs, err := iface.Addrs()
addrs, err := iface.(*net.Interface).Addrs()
if err != nil {
return "", err
}

View File

@ -29,23 +29,25 @@ func bindControl(ifaceIdx int) controlFn {
}
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
iface, err := net.InterfaceByName(ifaceName)
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
return net.InterfaceByName(ifaceName)
})
if err != nil {
return err
}
dialer.Control = bindControl(iface.Index)
dialer.Control = bindControl(iface.(*net.Interface).Index)
return nil
}
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
iface, err := net.InterfaceByName(ifaceName)
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
return net.InterfaceByName(ifaceName)
})
if err != nil {
return err
}
lc.Control = bindControl(iface.Index)
lc.Control = bindControl(iface.(*net.Interface).Index)
return nil
}

View File

@ -0,0 +1,101 @@
package cachefile
import (
"bytes"
"encoding/gob"
"io/ioutil"
"os"
"sync"
"github.com/Dreamacro/clash/component/profile"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
)
var (
initOnce sync.Once
fileMode os.FileMode = 0666
defaultCache *CacheFile
)
type cache struct {
Selected map[string]string
}
// CacheFile store and update the cache file
type CacheFile struct {
path string
model *cache
buf *bytes.Buffer
mux sync.Mutex
}
func (c *CacheFile) SetSelected(group, selected string) {
if !profile.StoreSelected.Load() {
return
}
c.mux.Lock()
defer c.mux.Unlock()
model := c.element()
model.Selected[group] = selected
c.buf.Reset()
if err := gob.NewEncoder(c.buf).Encode(model); err != nil {
log.Warnln("[CacheFile] encode gob failed: %s", err.Error())
return
}
if err := ioutil.WriteFile(c.path, c.buf.Bytes(), fileMode); err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.path, err.Error())
return
}
}
func (c *CacheFile) SelectedMap() map[string]string {
if !profile.StoreSelected.Load() {
return nil
}
c.mux.Lock()
defer c.mux.Unlock()
model := c.element()
mapping := map[string]string{}
for k, v := range model.Selected {
mapping[k] = v
}
return mapping
}
func (c *CacheFile) element() *cache {
if c.model != nil {
return c.model
}
model := &cache{
Selected: map[string]string{},
}
if buf, err := ioutil.ReadFile(c.path); err == nil {
bufReader := bytes.NewBuffer(buf)
gob.NewDecoder(bufReader).Decode(model)
}
c.model = model
return c.model
}
// Cache return singleton of CacheFile
func Cache() *CacheFile {
initOnce.Do(func() {
defaultCache = &CacheFile{
path: C.Path.Cache(),
buf: &bytes.Buffer{},
}
})
return defaultCache
}

View File

@ -0,0 +1,10 @@
package profile
import (
"go.uber.org/atomic"
)
var (
// StoreSelected is a global switch for storing selected proxy to cache
StoreSelected = atomic.NewBool(true)
)

View File

@ -1,11 +1,9 @@
package obfs
// Base information for obfs
type Base struct {
IVSize int
Key []byte
HeadLen int
Host string
Port int
Param string
Host string
Port int
Key []byte
IVSize int
Param string
}

View File

@ -1,7 +1,7 @@
package obfs
func init() {
register("http_post", newHTTPPost)
register("http_post", newHTTPPost, 0)
}
func newHTTPPost(b *Base) Obfs {

View File

@ -3,400 +3,405 @@ package obfs
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"math/rand"
"net"
"strconv"
"strings"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
)
func init() {
register("http_simple", newHTTPSimple, 0)
}
type httpObfs struct {
*Base
firstRequest bool
firstResponse bool
post bool
}
func init() {
register("http_simple", newHTTPSimple)
post bool
}
func newHTTPSimple(b *Base) Obfs {
return &httpObfs{Base: b}
}
func (h *httpObfs) initForConn() Obfs {
return &httpObfs{
Base: h.Base,
firstRequest: true,
firstResponse: true,
post: h.post,
}
type httpConn struct {
net.Conn
*httpObfs
hasSentHeader bool
hasRecvHeader bool
buf []byte
}
func (h *httpObfs) GetObfsOverhead() int {
return 0
func (h *httpObfs) StreamConn(c net.Conn) net.Conn {
return &httpConn{Conn: c, httpObfs: h}
}
func (h *httpObfs) Decode(b []byte) ([]byte, bool, error) {
if h.firstResponse {
idx := bytes.Index(b, []byte("\r\n\r\n"))
if idx == -1 {
return nil, false, io.EOF
}
h.firstResponse = false
return b[idx+4:], false, nil
}
return b, false, nil
}
func (h *httpObfs) Encode(b []byte) ([]byte, error) {
if h.firstRequest {
bSize := len(b)
var headData []byte
if headSize := h.IVSize + h.HeadLen; bSize-headSize > 64 {
headData = make([]byte, headSize+rand.Intn(64))
func (c *httpConn) Read(b []byte) (int, error) {
if c.buf != nil {
n := copy(b, c.buf)
if n == len(c.buf) {
c.buf = nil
} else {
headData = make([]byte, bSize)
c.buf = c.buf[n:]
}
copy(headData, b[:len(headData)])
host := h.Host
var customHead string
if len(h.Param) > 0 {
customHeads := strings.Split(h.Param, "#")
if len(customHeads) > 2 {
customHeads = customHeads[:2]
}
customHosts := h.Param
if len(customHeads) > 1 {
customHosts = customHeads[0]
customHead = customHeads[1]
}
hosts := strings.Split(customHosts, ",")
if len(hosts) > 0 {
host = strings.TrimSpace(hosts[rand.Intn(len(hosts))])
}
}
method := "GET /"
if h.post {
method = "POST /"
}
requestPathIndex := rand.Intn(len(requestPath)/2) * 2
httpBuf := fmt.Sprintf("%s%s%s%s HTTP/1.1\r\nHost: %s:%d\r\n",
method,
requestPath[requestPathIndex],
data2URLEncode(headData),
requestPath[requestPathIndex+1],
host, h.Port)
if len(customHead) > 0 {
httpBuf = httpBuf + strings.Replace(customHead, "\\n", "\r\n", -1) + "\r\n\r\n"
} else {
var contentType string
if h.post {
contentType = "Content-Type: multipart/form-data; boundary=" + boundary() + "\r\n"
}
httpBuf = httpBuf + "User-agent: " + requestUserAgent[rand.Intn(len(requestUserAgent))] + "\r\n" +
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
"Accept-Language: en-US,en;q=0.8\r\n" +
"Accept-Encoding: gzip, deflate\r\n" +
contentType +
"DNT: 1\r\n" +
"Connection: keep-alive\r\n" +
"\r\n"
}
var encoded []byte
if len(headData) < bSize {
encoded = make([]byte, len(httpBuf)+(bSize-len(headData)))
copy(encoded, []byte(httpBuf))
copy(encoded[len(httpBuf):], b[len(headData):])
} else {
encoded = []byte(httpBuf)
}
h.firstRequest = false
return encoded, nil
return n, nil
}
return b, nil
}
func data2URLEncode(data []byte) (ret string) {
for i := 0; i < len(data); i++ {
ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]}))
if c.hasRecvHeader {
return c.Conn.Read(b)
}
return
buf := pool.Get(pool.RelayBufferSize)
defer pool.Put(buf)
n, err := c.Conn.Read(buf)
if err != nil {
return 0, err
}
pos := bytes.Index(buf[:n], []byte("\r\n\r\n"))
if pos == -1 {
return 0, io.EOF
}
c.hasRecvHeader = true
dataLength := n - pos - 4
n = copy(b, buf[4+pos:n])
if dataLength > n {
c.buf = append(c.buf, buf[4+pos+n:4+pos+dataLength]...)
}
return n, nil
}
func boundary() (ret string) {
func (c *httpConn) Write(b []byte) (int, error) {
if c.hasSentHeader {
return c.Conn.Write(b)
}
// 30: head length
headLength := c.IVSize + 30
bLength := len(b)
headDataLength := bLength
if bLength-headLength > 64 {
headDataLength = headLength + rand.Intn(65)
}
headData := b[:headDataLength]
b = b[headDataLength:]
var body string
host := c.Host
if len(c.Param) > 0 {
pos := strings.Index(c.Param, "#")
if pos != -1 {
body = strings.ReplaceAll(c.Param[pos+1:], "\n", "\r\n")
body = strings.ReplaceAll(body, "\\n", "\r\n")
host = c.Param[:pos]
} else {
host = c.Param
}
}
hosts := strings.Split(host, ",")
host = hosts[rand.Intn(len(hosts))]
buf := tools.BufPool.Get().(*bytes.Buffer)
defer tools.BufPool.Put(buf)
defer buf.Reset()
if c.post {
buf.WriteString("POST /")
} else {
buf.WriteString("GET /")
}
packURLEncodedHeadData(buf, headData)
buf.WriteString(" HTTP/1.1\r\nHost: " + host)
if c.Port != 80 {
buf.WriteString(":" + strconv.Itoa(c.Port))
}
buf.WriteString("\r\n")
if len(body) > 0 {
buf.WriteString(body + "\r\n\r\n")
} else {
buf.WriteString("User-Agent: ")
buf.WriteString(userAgent[rand.Intn(len(userAgent))])
buf.WriteString("\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\n")
if c.post {
packBoundary(buf)
}
buf.WriteString("DNT: 1\r\nConnection: keep-alive\r\n\r\n")
}
buf.Write(b)
_, err := c.Conn.Write(buf.Bytes())
if err != nil {
return 0, nil
}
c.hasSentHeader = true
return bLength, nil
}
func packURLEncodedHeadData(buf *bytes.Buffer, data []byte) {
dataLength := len(data)
for i := 0; i < dataLength; i++ {
buf.WriteRune('%')
buf.WriteString(hex.EncodeToString(data[i : i+1]))
}
}
func packBoundary(buf *bytes.Buffer) {
buf.WriteString("Content-Type: multipart/form-data; boundary=")
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for i := 0; i < 32; i++ {
ret = fmt.Sprintf("%s%c", ret, set[rand.Intn(len(set))])
buf.WriteByte(set[rand.Intn(62)])
}
return
buf.WriteString("\r\n")
}
var (
requestPath = []string{
"", "",
"login.php?redir=", "",
"register.php?code=", "",
"?keyword=", "",
"search?src=typd&q=", "&lang=en",
"s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=",
"post.php?id=", "&goto=view.php",
}
requestUserAgent = []string{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0",
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36",
"Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
"Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
"Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
"Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1",
"Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
}
)
var userAgent = []string{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0",
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36",
"Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
"Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
"Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
"Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1",
"Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
}

View File

@ -3,7 +3,7 @@ package obfs
import (
"errors"
"fmt"
"strings"
"net"
)
var (
@ -12,26 +12,31 @@ var (
errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed")
)
// Obfs provides methods for decoding and encoding
type authData struct {
clientID [32]byte
}
type Obfs interface {
initForConn() Obfs
GetObfsOverhead() int
Decode(b []byte) ([]byte, bool, error)
Encode(b []byte) ([]byte, error)
StreamConn(net.Conn) net.Conn
}
type obfsCreator func(b *Base) Obfs
var obfsList = make(map[string]obfsCreator)
var obfsList = make(map[string]struct {
overhead int
new obfsCreator
})
func register(name string, c obfsCreator) {
obfsList[name] = c
func register(name string, c obfsCreator, o int) {
obfsList[name] = struct {
overhead int
new obfsCreator
}{overhead: o, new: c}
}
// PickObfs returns an obfs of the given name
func PickObfs(name string, b *Base) (Obfs, error) {
if obfsCreator, ok := obfsList[strings.ToLower(name)]; ok {
return obfsCreator(b), nil
func PickObfs(name string, b *Base) (Obfs, int, error) {
if choice, ok := obfsList[name]; ok {
return choice.new(b), choice.overhead, nil
}
return nil, fmt.Errorf("Obfs %s not supported", name)
return nil, 0, fmt.Errorf("Obfs %s not supported", name)
}

View File

@ -1,25 +1,15 @@
package obfs
import "net"
type plain struct{}
func init() {
register("plain", newPlain)
register("plain", newPlain, 0)
}
func newPlain(b *Base) Obfs {
return &plain{}
}
func (p *plain) initForConn() Obfs { return &plain{} }
func (p *plain) GetObfsOverhead() int {
return 0
}
func (p *plain) Encode(b []byte) ([]byte, error) {
return b, nil
}
func (p *plain) Decode(b []byte) ([]byte, bool, error) {
return b, false, nil
}
func (p *plain) StreamConn(c net.Conn) net.Conn { return c }

View File

@ -4,72 +4,68 @@ import (
"encoding/binary"
"hash/crc32"
"math/rand"
"net"
"github.com/Dreamacro/clash/common/pool"
)
func init() {
register("random_head", newRandomHead, 0)
}
type randomHead struct {
*Base
firstRequest bool
firstResponse bool
headerSent bool
buffer []byte
}
func init() {
register("random_head", newRandomHead)
}
func newRandomHead(b *Base) Obfs {
return &randomHead{Base: b}
}
func (r *randomHead) initForConn() Obfs {
return &randomHead{
Base: r.Base,
firstRequest: true,
firstResponse: true,
}
type randomHeadConn struct {
net.Conn
*randomHead
hasSentHeader bool
rawTransSent bool
rawTransRecv bool
buf []byte
}
func (r *randomHead) GetObfsOverhead() int {
return 0
func (r *randomHead) StreamConn(c net.Conn) net.Conn {
return &randomHeadConn{Conn: c, randomHead: r}
}
func (r *randomHead) Encode(b []byte) (encoded []byte, err error) {
if !r.firstRequest {
return b, nil
func (c *randomHeadConn) Read(b []byte) (int, error) {
if c.rawTransRecv {
return c.Conn.Read(b)
}
bSize := len(b)
if r.headerSent {
if bSize > 0 {
d := make([]byte, len(r.buffer)+bSize)
copy(d, r.buffer)
copy(d[len(r.buffer):], b)
r.buffer = d
} else {
encoded = r.buffer
r.buffer = nil
r.firstRequest = false
}
} else {
size := rand.Intn(96) + 8
encoded = make([]byte, size)
rand.Read(encoded)
crc := (0xFFFFFFFF - crc32.ChecksumIEEE(encoded[:size-4])) & 0xFFFFFFFF
binary.LittleEndian.PutUint32(encoded[size-4:], crc)
d := make([]byte, bSize)
copy(d, b)
r.buffer = d
}
r.headerSent = true
return encoded, nil
buf := pool.Get(pool.RelayBufferSize)
defer pool.Put(buf)
c.Conn.Read(buf)
c.rawTransRecv = true
c.Write(nil)
return 0, nil
}
func (r *randomHead) Decode(b []byte) ([]byte, bool, error) {
if r.firstResponse {
r.firstResponse = false
return b, true, nil
func (c *randomHeadConn) Write(b []byte) (int, error) {
if c.rawTransSent {
return c.Conn.Write(b)
}
return b, false, nil
c.buf = append(c.buf, b...)
if !c.hasSentHeader {
c.hasSentHeader = true
dataLength := rand.Intn(96) + 4
buf := pool.Get(dataLength + 4)
defer pool.Put(buf)
rand.Read(buf[:dataLength])
binary.LittleEndian.PutUint32(buf[dataLength:], 0xffffffff-crc32.ChecksumIEEE(buf[:dataLength]))
_, err := c.Conn.Write(buf)
return len(b), err
}
if c.rawTransRecv {
_, err := c.Conn.Write(c.buf)
c.buf = nil
c.rawTransSent = true
return len(b), err
}
return len(b), nil
}

View File

@ -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
}

View 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
}

View File

@ -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)
}

View File

@ -1,310 +1,18 @@
package protocol
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/binary"
"math/rand"
"strconv"
"strings"
"time"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
"github.com/Dreamacro/go-shadowsocks2/core"
)
type authAES128 struct {
*Base
*recvInfo
*authData
hasSentHeader bool
packID uint32
userKey []byte
uid [4]byte
salt string
hmac hmacMethod
hashDigest hashDigestMethod
}
import "github.com/Dreamacro/clash/component/ssr/tools"
func init() {
register("auth_aes128_md5", newAuthAES128MD5)
register("auth_aes128_md5", newAuthAES128MD5, 9)
}
func newAuthAES128MD5(b *Base) Protocol {
return &authAES128{
Base: b,
authData: &authData{},
salt: "auth_aes128_md5",
hmac: tools.HmacMD5,
hashDigest: tools.MD5Sum,
a := &authAES128{
Base: b,
authData: &authData{},
authAES128Function: &authAES128Function{salt: "auth_aes128_md5", hmac: tools.HmacMD5, hashDigest: tools.MD5Sum},
userData: &userData{},
}
}
func (a *authAES128) initForConn(iv []byte) Protocol {
return &authAES128{
Base: &Base{
IV: iv,
Key: a.Key,
TCPMss: a.TCPMss,
Overhead: a.Overhead,
Param: a.Param,
},
recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)},
authData: a.authData,
packID: 1,
salt: a.salt,
hmac: a.hmac,
hashDigest: a.hashDigest,
}
}
func (a *authAES128) GetProtocolOverhead() int {
return 9
}
func (a *authAES128) SetOverhead(overhead int) {
a.Overhead = overhead
}
func (a *authAES128) Decode(b []byte) ([]byte, int, error) {
a.buffer.Reset()
bSize := len(b)
readSize := 0
key := pool.Get(len(a.userKey) + 4)
defer pool.Put(key)
copy(key, a.userKey)
for bSize > 4 {
binary.LittleEndian.PutUint32(key[len(key)-4:], a.recvID)
h := a.hmac(key, b[:2])
if !bytes.Equal(h[:2], b[2:4]) {
return nil, 0, errAuthAES128IncorrectMAC
}
length := int(binary.LittleEndian.Uint16(b[:2]))
if length >= 8192 || length < 8 {
return nil, 0, errAuthAES128DataLengthError
}
if length > bSize {
break
}
h = a.hmac(key, b[:length-4])
if !bytes.Equal(h[:4], b[length-4:length]) {
return nil, 0, errAuthAES128IncorrectChecksum
}
a.recvID++
pos := int(b[4])
if pos < 255 {
pos += 4
} else {
pos = int(binary.LittleEndian.Uint16(b[5:7])) + 4
}
if pos > length-4 {
return nil, 0, errAuthAES128PositionTooLarge
}
a.buffer.Write(b[pos : length-4])
b = b[length:]
bSize -= length
readSize += length
}
return a.buffer.Bytes(), readSize, nil
}
func (a *authAES128) Encode(b []byte) ([]byte, error) {
a.buffer.Reset()
bSize := len(b)
offset := 0
if bSize > 0 && !a.hasSentHeader {
authSize := bSize
if authSize > 1200 {
authSize = 1200
}
a.hasSentHeader = true
a.buffer.Write(a.packAuthData(b[:authSize]))
bSize -= authSize
offset += authSize
}
const blockSize = 4096
for bSize > blockSize {
packSize, randSize := a.packedDataSize(b[offset : offset+blockSize])
pack := pool.Get(packSize)
a.packData(b[offset:offset+blockSize], pack, randSize)
a.buffer.Write(pack)
pool.Put(pack)
bSize -= blockSize
offset += blockSize
}
if bSize > 0 {
packSize, randSize := a.packedDataSize(b[offset:])
pack := pool.Get(packSize)
a.packData(b[offset:], pack, randSize)
a.buffer.Write(pack)
pool.Put(pack)
}
return a.buffer.Bytes(), nil
}
func (a *authAES128) DecodePacket(b []byte) ([]byte, int, error) {
bSize := len(b)
h := a.hmac(a.Key, b[:bSize-4])
if !bytes.Equal(h[:4], b[bSize-4:]) {
return nil, 0, errAuthAES128IncorrectMAC
}
return b[:bSize-4], bSize - 4, nil
}
func (a *authAES128) EncodePacket(b []byte) ([]byte, error) {
a.initUserKeyAndID()
var buf bytes.Buffer
buf.Write(b)
buf.Write(a.uid[:])
h := a.hmac(a.userKey, buf.Bytes())
buf.Write(h[:4])
return buf.Bytes(), nil
}
func (a *authAES128) initUserKeyAndID() {
if a.userKey == nil {
params := strings.Split(a.Param, ":")
if len(params) >= 2 {
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
binary.LittleEndian.PutUint32(a.uid[:], uint32(userID))
a.userKey = a.hashDigest([]byte(params[1]))
}
}
if a.userKey == nil {
rand.Read(a.uid[:])
a.userKey = make([]byte, len(a.Key))
copy(a.userKey, a.Key)
}
}
}
func (a *authAES128) packedDataSize(data []byte) (packSize, randSize int) {
dataSize := len(data)
randSize = 1
if dataSize <= 1200 {
if a.packID > 4 {
randSize += rand.Intn(32)
} else {
if dataSize > 900 {
randSize += rand.Intn(128)
} else {
randSize += rand.Intn(512)
}
}
}
packSize = randSize + dataSize + 8
return
}
func (a *authAES128) packData(data, ret []byte, randSize int) {
dataSize := len(data)
retSize := len(ret)
// 0~1, ret_size
binary.LittleEndian.PutUint16(ret[0:], uint16(retSize&0xFFFF))
// 2~3, hmac
key := pool.Get(len(a.userKey) + 4)
defer pool.Put(key)
copy(key, a.userKey)
binary.LittleEndian.PutUint32(key[len(key)-4:], a.packID)
h := a.hmac(key, ret[:2])
copy(ret[2:4], h[:2])
// 4~rand_size+4, rand number
rand.Read(ret[4 : 4+randSize])
// 4, rand_size
if randSize < 128 {
ret[4] = byte(randSize & 0xFF)
} else {
// 4, magic number 0xFF
ret[4] = 0xFF
// 5~6, rand_size
binary.LittleEndian.PutUint16(ret[5:], uint16(randSize&0xFFFF))
}
// rand_size+4~ret_size-4, data
if dataSize > 0 {
copy(ret[randSize+4:], data)
}
a.packID++
h = a.hmac(key, ret[:retSize-4])
copy(ret[retSize-4:], h[:4])
}
func (a *authAES128) packAuthData(data []byte) (ret []byte) {
dataSize := len(data)
var randSize int
if dataSize > 400 {
randSize = rand.Intn(512)
} else {
randSize = rand.Intn(1024)
}
dataOffset := randSize + 16 + 4 + 4 + 7
retSize := dataOffset + dataSize + 4
ret = make([]byte, retSize)
encrypt := make([]byte, 24)
key := make([]byte, len(a.IV)+len(a.Key))
copy(key, a.IV)
copy(key[len(a.IV):], a.Key)
rand.Read(ret[dataOffset-randSize:])
a.mutex.Lock()
defer a.mutex.Unlock()
a.connectionID++
if a.connectionID > 0xFF000000 {
a.clientID = nil
}
if len(a.clientID) == 0 {
a.clientID = make([]byte, 8)
rand.Read(a.clientID)
b := make([]byte, 4)
rand.Read(b)
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
}
copy(encrypt[4:], a.clientID)
binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID)
now := time.Now().Unix()
binary.LittleEndian.PutUint32(encrypt[:4], uint32(now))
binary.LittleEndian.PutUint16(encrypt[12:], uint16(retSize&0xFFFF))
binary.LittleEndian.PutUint16(encrypt[14:], uint16(randSize&0xFFFF))
a.initUserKeyAndID()
aesCipherKey := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+a.salt, 16)
block, err := aes.NewCipher(aesCipherKey)
if err != nil {
return nil
}
encryptData := make([]byte, 16)
iv := make([]byte, aes.BlockSize)
cbc := cipher.NewCBCEncrypter(block, iv)
cbc.CryptBlocks(encryptData, encrypt[:16])
copy(encrypt[:4], a.uid[:])
copy(encrypt[4:4+16], encryptData)
h := a.hmac(key, encrypt[:20])
copy(encrypt[20:], h[:4])
rand.Read(ret[:1])
h = a.hmac(key, ret[:1])
copy(ret[1:], h[:7-1])
copy(ret[7:], encrypt)
copy(ret[dataOffset:], data)
h = a.hmac(a.userKey, ret[:retSize-4])
copy(ret[retSize-4:], h[:4])
return
a.initUserData()
return a
}

View File

@ -2,21 +2,274 @@ package protocol
import (
"bytes"
"encoding/binary"
"math"
"math/rand"
"net"
"strconv"
"strings"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
"github.com/Dreamacro/clash/log"
)
type hmacMethod func(key, data []byte) []byte
type hashDigestMethod func([]byte) []byte
func init() {
register("auth_aes128_sha1", newAuthAES128SHA1)
register("auth_aes128_sha1", newAuthAES128SHA1, 9)
}
type authAES128Function struct {
salt string
hmac hmacMethod
hashDigest hashDigestMethod
}
type authAES128 struct {
*Base
*authData
*authAES128Function
*userData
iv []byte
hasSentHeader bool
rawTrans bool
packID uint32
recvID uint32
}
func newAuthAES128SHA1(b *Base) Protocol {
return &authAES128{
Base: b,
recvInfo: &recvInfo{buffer: new(bytes.Buffer)},
authData: &authData{},
salt: "auth_aes128_sha1",
hmac: tools.HmacSHA1,
hashDigest: tools.SHA1Sum,
a := &authAES128{
Base: b,
authData: &authData{},
authAES128Function: &authAES128Function{salt: "auth_aes128_sha1", hmac: tools.HmacSHA1, hashDigest: tools.SHA1Sum},
userData: &userData{},
}
a.initUserData()
return a
}
func (a *authAES128) initUserData() {
params := strings.Split(a.Param, ":")
if len(params) > 1 {
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
binary.LittleEndian.PutUint32(a.userID[:], uint32(userID))
a.userKey = a.hashDigest([]byte(params[1]))
} else {
log.Warnln("Wrong protocol-param for %s, only digits are expected before ':'", a.salt)
}
}
if len(a.userKey) == 0 {
a.userKey = a.Key
rand.Read(a.userID[:])
}
}
func (a *authAES128) StreamConn(c net.Conn, iv []byte) net.Conn {
p := &authAES128{
Base: a.Base,
authData: a.next(),
authAES128Function: a.authAES128Function,
userData: a.userData,
packID: 1,
recvID: 1,
}
p.iv = iv
return &Conn{Conn: c, Protocol: p}
}
func (a *authAES128) PacketConn(c net.PacketConn) net.PacketConn {
p := &authAES128{
Base: a.Base,
authAES128Function: a.authAES128Function,
userData: a.userData,
}
return &PacketConn{PacketConn: c, Protocol: p}
}
func (a *authAES128) Decode(dst, src *bytes.Buffer) error {
if a.rawTrans {
dst.ReadFrom(src)
return nil
}
for src.Len() > 4 {
macKey := pool.Get(len(a.userKey) + 4)
defer pool.Put(macKey)
copy(macKey, a.userKey)
binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.recvID)
if !bytes.Equal(a.hmac(macKey, src.Bytes()[:2])[:2], src.Bytes()[2:4]) {
src.Reset()
return errAuthAES128MACError
}
length := int(binary.LittleEndian.Uint16(src.Bytes()[:2]))
if length >= 8192 || length < 7 {
a.rawTrans = true
src.Reset()
return errAuthAES128LengthError
}
if length > src.Len() {
break
}
if !bytes.Equal(a.hmac(macKey, src.Bytes()[:length-4])[:4], src.Bytes()[length-4:length]) {
a.rawTrans = true
src.Reset()
return errAuthAES128ChksumError
}
a.recvID++
pos := int(src.Bytes()[4])
if pos < 255 {
pos += 4
} else {
pos = int(binary.LittleEndian.Uint16(src.Bytes()[5:7])) + 4
}
dst.Write(src.Bytes()[pos : length-4])
src.Next(length)
}
return nil
}
func (a *authAES128) Encode(buf *bytes.Buffer, b []byte) error {
fullDataLength := len(b)
if !a.hasSentHeader {
dataLength := getDataLength(b)
a.packAuthData(buf, b[:dataLength])
b = b[dataLength:]
a.hasSentHeader = true
}
for len(b) > 8100 {
a.packData(buf, b[:8100], fullDataLength)
b = b[8100:]
}
if len(b) > 0 {
a.packData(buf, b, fullDataLength)
}
return nil
}
func (a *authAES128) DecodePacket(b []byte) ([]byte, error) {
if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) {
return nil, errAuthAES128ChksumError
}
return b[:len(b)-4], nil
}
func (a *authAES128) EncodePacket(buf *bytes.Buffer, b []byte) error {
buf.Write(b)
buf.Write(a.userID[:])
buf.Write(a.hmac(a.userKey, buf.Bytes())[:4])
return nil
}
func (a *authAES128) packData(poolBuf *bytes.Buffer, data []byte, fullDataLength int) {
dataLength := len(data)
randDataLength := a.getRandDataLengthForPackData(dataLength, fullDataLength)
/*
2: uint16 LittleEndian packedDataLength
2: hmac of packedDataLength
3: maxRandDataLengthPrefix (min:1)
4: hmac of packedData except the last 4 bytes
*/
packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4
if randDataLength < 128 {
packedDataLength -= 2
}
macKey := pool.Get(len(a.userKey) + 4)
defer pool.Put(macKey)
copy(macKey, a.userKey)
binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID)
a.packID++
binary.Write(poolBuf, binary.LittleEndian, uint16(packedDataLength))
poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-2:])[:2])
a.packRandData(poolBuf, randDataLength)
poolBuf.Write(data)
poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:])[:4])
}
func trapezoidRandom(max int, d float64) int {
base := rand.Float64()
if d-0 > 1e-6 {
a := 1 - d
base = (math.Sqrt(a*a+4*d*base) - a) / (2 * d)
}
return int(base * float64(max))
}
func (a *authAES128) getRandDataLengthForPackData(dataLength, fullDataLength int) int {
if fullDataLength >= 32*1024-a.Overhead {
return 0
}
// 1460: tcp_mss
revLength := 1460 - dataLength - 9
if revLength == 0 {
return 0
}
if revLength < 0 {
if revLength > -1460 {
return trapezoidRandom(revLength+1460, -0.3)
}
return rand.Intn(32)
}
if dataLength > 900 {
return rand.Intn(revLength)
}
return trapezoidRandom(revLength, -0.3)
}
func (a *authAES128) packAuthData(poolBuf *bytes.Buffer, data []byte) {
if len(data) == 0 {
return
}
dataLength := len(data)
randDataLength := a.getRandDataLengthForPackAuthData(dataLength)
/*
7: checkHead(1) and hmac of checkHead(6)
4: userID
16: encrypted data of authdata(12), uint16 BigEndian packedDataLength(2) and uint16 BigEndian randDataLength(2)
4: hmac of userID and encrypted data
4: hmac of packedAuthData except the last 4 bytes
*/
packedAuthDataLength := 7 + 4 + 16 + 4 + randDataLength + dataLength + 4
macKey := pool.Get(len(a.iv) + len(a.Key))
defer pool.Put(macKey)
copy(macKey, a.iv)
copy(macKey[len(a.iv):], a.Key)
poolBuf.WriteByte(byte(rand.Intn(256)))
poolBuf.Write(a.hmac(macKey, poolBuf.Bytes())[:6])
poolBuf.Write(a.userID[:])
err := a.authData.putEncryptedData(poolBuf, a.userKey, [2]int{packedAuthDataLength, randDataLength}, a.salt)
if err != nil {
poolBuf.Reset()
return
}
poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[7:])[:4])
tools.AppendRandBytes(poolBuf, randDataLength)
poolBuf.Write(data)
poolBuf.Write(a.hmac(a.userKey, poolBuf.Bytes())[:4])
}
func (a *authAES128) getRandDataLengthForPackAuthData(size int) int {
if size > 400 {
return rand.Intn(512)
}
return rand.Intn(1024)
}
func (a *authAES128) packRandData(poolBuf *bytes.Buffer, size int) {
if size < 128 {
poolBuf.WriteByte(byte(size + 1))
tools.AppendRandBytes(poolBuf, size)
return
}
poolBuf.WriteByte(255)
binary.Write(poolBuf, binary.LittleEndian, uint16(size+3))
tools.AppendRandBytes(poolBuf, size)
}

View File

@ -2,426 +2,308 @@ package protocol
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rc4"
"encoding/base64"
"encoding/binary"
"math/rand"
"net"
"strconv"
"strings"
"time"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/go-shadowsocks2/core"
)
type authChain struct {
*Base
*recvInfo
*authData
randomClient shift128PlusContext
randomServer shift128PlusContext
enc cipher.Stream
dec cipher.Stream
headerSent bool
lastClientHash []byte
lastServerHash []byte
userKey []byte
uid [4]byte
salt string
hmac hmacMethod
hashDigest hashDigestMethod
rnd rndMethod
dataSizeList []int
dataSizeList2 []int
chunkID uint32
func init() {
register("auth_chain_a", newAuthChainA, 4)
}
func init() {
register("auth_chain_a", newAuthChainA)
type randDataLengthMethod func(int, []byte, *tools.XorShift128Plus) int
type authChainA struct {
*Base
*authData
*userData
iv []byte
salt string
hasSentHeader bool
rawTrans bool
lastClientHash []byte
lastServerHash []byte
encrypter cipher.Stream
decrypter cipher.Stream
randomClient tools.XorShift128Plus
randomServer tools.XorShift128Plus
randDataLength randDataLengthMethod
packID uint32
recvID uint32
}
func newAuthChainA(b *Base) Protocol {
return &authChain{
Base: b,
authData: &authData{},
salt: "auth_chain_a",
hmac: tools.HmacMD5,
hashDigest: tools.SHA1Sum,
rnd: authChainAGetRandLen,
a := &authChainA{
Base: b,
authData: &authData{},
userData: &userData{},
salt: "auth_chain_a",
}
a.initUserData()
return a
}
func (a *authChain) initForConn(iv []byte) Protocol {
r := &authChain{
Base: &Base{
IV: iv,
Key: a.Key,
TCPMss: a.TCPMss,
Overhead: a.Overhead,
Param: a.Param,
},
recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)},
authData: a.authData,
salt: a.salt,
hmac: a.hmac,
hashDigest: a.hashDigest,
rnd: a.rnd,
}
if r.salt == "auth_chain_b" {
initDataSize(r)
}
return r
}
func (a *authChain) GetProtocolOverhead() int {
return 4
}
func (a *authChain) SetOverhead(overhead int) {
a.Overhead = overhead
}
func (a *authChain) Decode(b []byte) ([]byte, int, error) {
a.buffer.Reset()
key := pool.Get(len(a.userKey) + 4)
defer pool.Put(key)
readSize := 0
copy(key, a.userKey)
for len(b) > 4 {
binary.LittleEndian.PutUint32(key[len(a.userKey):], a.recvID)
dataLen := (int)((uint(b[1]^a.lastServerHash[15]) << 8) + uint(b[0]^a.lastServerHash[14]))
randLen := a.getServerRandLen(dataLen, a.Overhead)
length := randLen + dataLen
if length >= 4096 {
return nil, 0, errAuthChainDataLengthError
func (a *authChainA) initUserData() {
params := strings.Split(a.Param, ":")
if len(params) > 1 {
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
binary.LittleEndian.PutUint32(a.userID[:], uint32(userID))
a.userKey = []byte(params[1])
} else {
log.Warnln("Wrong protocol-param for %s, only digits are expected before ':'", a.salt)
}
length += 4
if length > len(b) {
}
if len(a.userKey) == 0 {
a.userKey = a.Key
rand.Read(a.userID[:])
}
}
func (a *authChainA) StreamConn(c net.Conn, iv []byte) net.Conn {
p := &authChainA{
Base: a.Base,
authData: a.next(),
userData: a.userData,
salt: a.salt,
packID: 1,
recvID: 1,
}
p.iv = iv
p.randDataLength = p.getRandLength
return &Conn{Conn: c, Protocol: p}
}
func (a *authChainA) PacketConn(c net.PacketConn) net.PacketConn {
p := &authChainA{
Base: a.Base,
salt: a.salt,
userData: a.userData,
}
return &PacketConn{PacketConn: c, Protocol: p}
}
func (a *authChainA) Decode(dst, src *bytes.Buffer) error {
if a.rawTrans {
dst.ReadFrom(src)
return nil
}
for src.Len() > 4 {
macKey := pool.Get(len(a.userKey) + 4)
defer pool.Put(macKey)
copy(macKey, a.userKey)
binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.recvID)
dataLength := int(binary.LittleEndian.Uint16(src.Bytes()[:2]) ^ binary.LittleEndian.Uint16(a.lastServerHash[14:16]))
randDataLength := a.randDataLength(dataLength, a.lastServerHash, &a.randomServer)
length := dataLength + randDataLength
if length >= 4096 {
a.rawTrans = true
src.Reset()
return errAuthChainLengthError
}
if 4+length > src.Len() {
break
}
hash := a.hmac(key, b[:length-2])
if !bytes.Equal(hash[:2], b[length-2:length]) {
return nil, 0, errAuthChainHMACError
serverHash := tools.HmacMD5(macKey, src.Bytes()[:length+2])
if !bytes.Equal(serverHash[:2], src.Bytes()[length+2:length+4]) {
a.rawTrans = true
src.Reset()
return errAuthChainChksumError
}
var dataPos int
if dataLen > 0 && randLen > 0 {
dataPos = 2 + getRandStartPos(&a.randomServer, randLen)
} else {
dataPos = 2
a.lastServerHash = serverHash
pos := 2
if dataLength > 0 && randDataLength > 0 {
pos += getRandStartPos(randDataLength, &a.randomServer)
}
d := pool.Get(dataLen)
a.dec.XORKeyStream(d, b[dataPos:dataPos+dataLen])
a.buffer.Write(d)
pool.Put(d)
wantedData := src.Bytes()[pos : pos+dataLength]
a.decrypter.XORKeyStream(wantedData, wantedData)
if a.recvID == 1 {
a.TCPMss = int(binary.LittleEndian.Uint16(a.buffer.Next(2)))
dst.Write(wantedData[2:])
} else {
dst.Write(wantedData)
}
a.lastServerHash = hash
a.recvID++
b = b[length:]
readSize += length
src.Next(length + 4)
}
return a.buffer.Bytes(), readSize, nil
return nil
}
func (a *authChain) Encode(b []byte) ([]byte, error) {
a.buffer.Reset()
bSize := len(b)
offset := 0
if bSize > 0 && !a.headerSent {
headSize := 1200
if headSize > bSize {
headSize = bSize
}
a.buffer.Write(a.packAuthData(b[:headSize]))
offset += headSize
bSize -= headSize
a.headerSent = true
func (a *authChainA) Encode(buf *bytes.Buffer, b []byte) error {
if !a.hasSentHeader {
dataLength := getDataLength(b)
a.packAuthData(buf, b[:dataLength])
b = b[dataLength:]
a.hasSentHeader = true
}
var unitSize = a.TCPMss - a.Overhead
for bSize > unitSize {
dataLen, randLength := a.packedDataLen(b[offset : offset+unitSize])
d := pool.Get(dataLen)
a.packData(d, b[offset:offset+unitSize], randLength)
a.buffer.Write(d)
pool.Put(d)
bSize -= unitSize
offset += unitSize
for len(b) > 2800 {
a.packData(buf, b[:2800])
b = b[2800:]
}
if bSize > 0 {
dataLen, randLength := a.packedDataLen(b[offset:])
d := pool.Get(dataLen)
a.packData(d, b[offset:], randLength)
a.buffer.Write(d)
pool.Put(d)
if len(b) > 0 {
a.packData(buf, b)
}
return a.buffer.Bytes(), nil
return nil
}
func (a *authChain) DecodePacket(b []byte) ([]byte, int, error) {
bSize := len(b)
if bSize < 9 {
return nil, 0, errAuthChainDataLengthError
func (a *authChainA) DecodePacket(b []byte) ([]byte, error) {
if len(b) < 9 {
return nil, errAuthChainLengthError
}
h := a.hmac(a.userKey, b[:bSize-1])
if h[0] != b[bSize-1] {
return nil, 0, errAuthChainHMACError
if !bytes.Equal(tools.HmacMD5(a.userKey, b[:len(b)-1])[:1], b[len(b)-1:]) {
return nil, errAuthChainChksumError
}
hash := a.hmac(a.Key, b[bSize-8:bSize-1])
cipherKey := a.getRC4CipherKey(hash)
dec, _ := rc4.NewCipher(cipherKey)
randLength := udpGetRandLen(&a.randomServer, hash)
bSize -= 8 + randLength
dec.XORKeyStream(b, b[:bSize])
return b, bSize, nil
md5Data := tools.HmacMD5(a.Key, b[len(b)-8:len(b)-1])
randDataLength := udpGetRandLength(md5Data, &a.randomServer)
key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16)
rc4Cipher, err := rc4.NewCipher(key)
if err != nil {
return nil, err
}
wantedData := b[:len(b)-8-randDataLength]
rc4Cipher.XORKeyStream(wantedData, wantedData)
return wantedData, nil
}
func (a *authChain) EncodePacket(b []byte) ([]byte, error) {
a.initUserKeyAndID()
func (a *authChainA) EncodePacket(buf *bytes.Buffer, b []byte) error {
authData := pool.Get(3)
defer pool.Put(authData)
rand.Read(authData)
hash := a.hmac(a.Key, authData)
uid := pool.Get(4)
defer pool.Put(uid)
for i := 0; i < 4; i++ {
uid[i] = a.uid[i] ^ hash[i]
}
cipherKey := a.getRC4CipherKey(hash)
enc, _ := rc4.NewCipher(cipherKey)
var buf bytes.Buffer
enc.XORKeyStream(b, b)
md5Data := tools.HmacMD5(a.Key, authData)
randDataLength := udpGetRandLength(md5Data, &a.randomClient)
key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16)
rc4Cipher, err := rc4.NewCipher(key)
if err != nil {
return err
}
rc4Cipher.XORKeyStream(b, b)
buf.Write(b)
randLength := udpGetRandLen(&a.randomClient, hash)
randBytes := pool.Get(randLength)
defer pool.Put(randBytes)
buf.Write(randBytes)
tools.AppendRandBytes(buf, randDataLength)
buf.Write(authData)
buf.Write(uid)
h := a.hmac(a.userKey, buf.Bytes())
buf.Write(h[:1])
return buf.Bytes(), nil
binary.Write(buf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(md5Data[:4]))
buf.Write(tools.HmacMD5(a.userKey, buf.Bytes())[:1])
return nil
}
func (a *authChain) getRC4CipherKey(hash []byte) []byte {
base64UserKey := base64.StdEncoding.EncodeToString(a.userKey)
return a.calcRC4CipherKey(hash, base64UserKey)
}
func (a *authChainA) packAuthData(poolBuf *bytes.Buffer, data []byte) {
/*
dataLength := len(data)
12: checkHead(4) and hmac of checkHead(8)
4: uint32 LittleEndian uid (uid = userID ^ last client hash)
16: encrypted data of authdata(12), uint16 LittleEndian overhead(2) and uint16 LittleEndian number zero(2)
4: last server hash(4)
packedAuthDataLength := 12 + 4 + 16 + 4 + dataLength
*/
func (a *authChain) calcRC4CipherKey(hash []byte, base64UserKey string) []byte {
password := pool.Get(len(base64UserKey) + base64.StdEncoding.EncodedLen(16))
defer pool.Put(password)
copy(password, base64UserKey)
base64.StdEncoding.Encode(password[len(base64UserKey):], hash[:16])
return core.Kdf(string(password), 16)
}
macKey := pool.Get(len(a.iv) + len(a.Key))
defer pool.Put(macKey)
copy(macKey, a.iv)
copy(macKey[len(a.iv):], a.Key)
func (a *authChain) initUserKeyAndID() {
if a.userKey == nil {
params := strings.Split(a.Param, ":")
if len(params) >= 2 {
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
binary.LittleEndian.PutUint32(a.uid[:], uint32(userID))
a.userKey = []byte(params[1])[:len(a.userKey)]
}
}
if a.userKey == nil {
rand.Read(a.uid[:])
a.userKey = make([]byte, len(a.Key))
copy(a.userKey, a.Key)
}
// check head
tools.AppendRandBytes(poolBuf, 4)
a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes())
a.initRC4Cipher()
poolBuf.Write(a.lastClientHash[:8])
// uid
binary.Write(poolBuf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(a.lastClientHash[8:12]))
// encrypted data
err := a.putEncryptedData(poolBuf, a.userKey, [2]int{a.Overhead, 0}, a.salt)
if err != nil {
poolBuf.Reset()
return
}
// last server hash
a.lastServerHash = tools.HmacMD5(a.userKey, poolBuf.Bytes()[12:])
poolBuf.Write(a.lastServerHash[:4])
// packed data
a.packData(poolBuf, data)
}
func (a *authChain) getClientRandLen(dataLength int, overhead int) int {
return a.rnd(dataLength, &a.randomClient, a.lastClientHash, a.dataSizeList, a.dataSizeList2, overhead)
func (a *authChainA) packData(poolBuf *bytes.Buffer, data []byte) {
a.encrypter.XORKeyStream(data, data)
macKey := pool.Get(len(a.userKey) + 4)
defer pool.Put(macKey)
copy(macKey, a.userKey)
binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID)
a.packID++
length := uint16(len(data)) ^ binary.LittleEndian.Uint16(a.lastClientHash[14:16])
originalLength := poolBuf.Len()
binary.Write(poolBuf, binary.LittleEndian, length)
a.putMixedRandDataAndData(poolBuf, data)
a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes()[originalLength:])
poolBuf.Write(a.lastClientHash[:2])
}
func (a *authChain) getServerRandLen(dataLength int, overhead int) int {
return a.rnd(dataLength, &a.randomServer, a.lastServerHash, a.dataSizeList, a.dataSizeList2, overhead)
func (a *authChainA) putMixedRandDataAndData(poolBuf *bytes.Buffer, data []byte) {
randDataLength := a.randDataLength(len(data), a.lastClientHash, &a.randomClient)
if len(data) == 0 {
tools.AppendRandBytes(poolBuf, randDataLength)
return
}
if randDataLength > 0 {
startPos := getRandStartPos(randDataLength, &a.randomClient)
tools.AppendRandBytes(poolBuf, startPos)
poolBuf.Write(data)
tools.AppendRandBytes(poolBuf, randDataLength-startPos)
return
}
poolBuf.Write(data)
}
func (a *authChain) packedDataLen(data []byte) (chunkLength, randLength int) {
dataLength := len(data)
randLength = a.getClientRandLen(dataLength, a.Overhead)
chunkLength = randLength + dataLength + 2 + 2
return
}
func (a *authChain) packData(outData []byte, data []byte, randLength int) {
dataLength := len(data)
outLength := randLength + dataLength + 2
outData[0] = byte(dataLength) ^ a.lastClientHash[14]
outData[1] = byte(dataLength>>8) ^ a.lastClientHash[15]
{
if dataLength > 0 {
randPart1Length := getRandStartPos(&a.randomClient, randLength)
rand.Read(outData[2 : 2+randPart1Length])
a.enc.XORKeyStream(outData[2+randPart1Length:], data)
rand.Read(outData[2+randPart1Length+dataLength : outLength])
} else {
rand.Read(outData[2 : 2+randLength])
}
}
userKeyLen := uint8(len(a.userKey))
key := pool.Get(int(userKeyLen + 4))
defer pool.Put(key)
copy(key, a.userKey)
a.chunkID++
binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID)
a.lastClientHash = a.hmac(key, outData[:outLength])
copy(outData[outLength:], a.lastClientHash[:2])
}
const authHeadLength = 4 + 8 + 4 + 16 + 4
func (a *authChain) packAuthData(data []byte) (outData []byte) {
outData = make([]byte, authHeadLength, authHeadLength+1500)
a.mutex.Lock()
defer a.mutex.Unlock()
a.connectionID++
if a.connectionID > 0xFF000000 {
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 <= 1500 {
outData = outData[:authHeadLength+chunkLength]
} else {
newOutData := make([]byte, authHeadLength+chunkLength)
copy(newOutData, outData[:authHeadLength])
outData = newOutData
}
a.packData(outData[authHeadLength:], data, randLength)
return
}
func getRandStartPos(random *shift128PlusContext, randLength int) int {
if randLength > 0 {
return int(random.Next() % 8589934609 % uint64(randLength))
}
return 0
}
func authChainAGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
if dataLength > 1440 {
func getRandStartPos(length int, random *tools.XorShift128Plus) int {
if length == 0 {
return 0
}
random.InitFromBinDatalen(lastHash[:16], dataLength)
if dataLength > 1300 {
return int(random.Next()%8589934609) % length
}
func (a *authChainA) getRandLength(length int, lastHash []byte, random *tools.XorShift128Plus) int {
if length > 1440 {
return 0
}
random.InitFromBinAndLength(lastHash, length)
if length > 1300 {
return int(random.Next() % 31)
}
if dataLength > 900 {
if length > 900 {
return int(random.Next() % 127)
}
if dataLength > 400 {
if length > 400 {
return int(random.Next() % 521)
}
return int(random.Next() % 1021)
}
func udpGetRandLen(random *shift128PlusContext, lastHash []byte) int {
random.InitFromBin(lastHash[:16])
func (a *authChainA) initRC4Cipher() {
key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(a.lastClientHash), 16)
a.encrypter, _ = rc4.NewCipher(key)
a.decrypter, _ = rc4.NewCipher(key)
}
func udpGetRandLength(lastHash []byte, random *tools.XorShift128Plus) int {
random.InitFromBin(lastHash)
return int(random.Next() % 127)
}
type shift128PlusContext struct {
v [2]uint64
}
func (ctx *shift128PlusContext) InitFromBin(bin []byte) {
var fillBin [16]byte
copy(fillBin[:], bin)
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
}
func (ctx *shift128PlusContext) InitFromBinDatalen(bin []byte, datalen int) {
var fillBin [16]byte
copy(fillBin[:], bin)
binary.LittleEndian.PutUint16(fillBin[:2], uint16(datalen))
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
for i := 0; i < 4; i++ {
ctx.Next()
}
}
func (ctx *shift128PlusContext) Next() uint64 {
x := ctx.v[0]
y := ctx.v[1]
ctx.v[0] = y
x ^= x << 23
x ^= y ^ (x >> 17) ^ (y >> 26)
ctx.v[1] = x
return x + y
}

View File

@ -1,71 +1,96 @@
package protocol
import (
"net"
"sort"
"github.com/Dreamacro/clash/component/ssr/tools"
)
func init() {
register("auth_chain_b", newAuthChainB)
register("auth_chain_b", newAuthChainB, 4)
}
type authChainB struct {
*authChainA
dataSizeList []int
dataSizeList2 []int
}
func newAuthChainB(b *Base) Protocol {
return &authChain{
Base: b,
authData: &authData{},
salt: "auth_chain_b",
hmac: tools.HmacMD5,
hashDigest: tools.SHA1Sum,
rnd: authChainBGetRandLen,
a := &authChainB{
authChainA: &authChainA{
Base: b,
authData: &authData{},
userData: &userData{},
salt: "auth_chain_b",
},
}
a.initUserData()
return a
}
func initDataSize(r *authChain) {
random := &r.randomServer
random.InitFromBin(r.Key)
len := random.Next()%8 + 4
r.dataSizeList = make([]int, len)
for i := 0; i < int(len); i++ {
r.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440)
func (a *authChainB) StreamConn(c net.Conn, iv []byte) net.Conn {
p := &authChainB{
authChainA: &authChainA{
Base: a.Base,
authData: a.next(),
userData: a.userData,
salt: a.salt,
packID: 1,
recvID: 1,
},
}
sort.Ints(r.dataSizeList)
len = random.Next()%16 + 8
r.dataSizeList2 = make([]int, len)
for i := 0; i < int(len); i++ {
r.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440)
}
sort.Ints(r.dataSizeList2)
p.iv = iv
p.randDataLength = p.getRandLength
p.initDataSize()
return &Conn{Conn: c, Protocol: p}
}
func authChainBGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
if dataLength > 1440 {
func (a *authChainB) initDataSize() {
a.dataSizeList = a.dataSizeList[:0]
a.dataSizeList2 = a.dataSizeList2[:0]
a.randomServer.InitFromBin(a.Key)
length := a.randomServer.Next()%8 + 4
for ; length > 0; length-- {
a.dataSizeList = append(a.dataSizeList, int(a.randomServer.Next()%2340%2040%1440))
}
sort.Ints(a.dataSizeList)
length = a.randomServer.Next()%16 + 8
for ; length > 0; length-- {
a.dataSizeList2 = append(a.dataSizeList2, int(a.randomServer.Next()%2340%2040%1440))
}
sort.Ints(a.dataSizeList2)
}
func (a *authChainB) getRandLength(length int, lashHash []byte, random *tools.XorShift128Plus) int {
if length >= 1440 {
return 0
}
random.InitFromBinDatalen(lastHash[:16], dataLength)
pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead })
finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList))
if finalPos < uint64(len(dataSizeList)) {
return dataSizeList[finalPos] - dataLength - overhead
random.InitFromBinAndLength(lashHash, length)
pos := sort.Search(len(a.dataSizeList), func(i int) bool { return a.dataSizeList[i] >= length+a.Overhead })
finalPos := pos + int(random.Next()%uint64(len(a.dataSizeList)))
if finalPos < len(a.dataSizeList) {
return a.dataSizeList[finalPos] - length - a.Overhead
}
pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead })
finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2))
if finalPos < uint64(len(dataSizeList2)) {
return dataSizeList2[finalPos] - dataLength - overhead
pos = sort.Search(len(a.dataSizeList2), func(i int) bool { return a.dataSizeList2[i] >= length+a.Overhead })
finalPos = pos + int(random.Next()%uint64(len(a.dataSizeList2)))
if finalPos < len(a.dataSizeList2) {
return a.dataSizeList2[finalPos] - length - a.Overhead
}
if finalPos < uint64(pos+len(dataSizeList2)-1) {
if finalPos < pos+len(a.dataSizeList2)-1 {
return 0
}
if dataLength > 1300 {
if length > 1300 {
return int(random.Next() % 31)
}
if dataLength > 900 {
if length > 900 {
return int(random.Next() % 127)
}
if dataLength > 400 {
if length > 400 {
return int(random.Next() % 521)
}
return int(random.Next() % 1021)

View File

@ -6,248 +6,177 @@ import (
"hash/adler32"
"hash/crc32"
"math/rand"
"time"
"net"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
)
func init() {
register("auth_sha1_v4", newAuthSHA1V4, 7)
}
type authSHA1V4 struct {
*Base
*authData
headerSent bool
buffer bytes.Buffer
}
func init() {
register("auth_sha1_v4", newAuthSHA1V4)
iv []byte
hasSentHeader bool
rawTrans bool
}
func newAuthSHA1V4(b *Base) Protocol {
return &authSHA1V4{Base: b, authData: &authData{}}
}
func (a *authSHA1V4) initForConn(iv []byte) Protocol {
return &authSHA1V4{
Base: &Base{
IV: iv,
Key: a.Key,
TCPMss: a.TCPMss,
Overhead: a.Overhead,
Param: a.Param,
},
authData: a.authData,
func (a *authSHA1V4) StreamConn(c net.Conn, iv []byte) net.Conn {
p := &authSHA1V4{Base: a.Base, authData: a.next()}
p.iv = iv
return &Conn{Conn: c, Protocol: p}
}
func (a *authSHA1V4) PacketConn(c net.PacketConn) net.PacketConn {
return c
}
func (a *authSHA1V4) Decode(dst, src *bytes.Buffer) error {
if a.rawTrans {
dst.ReadFrom(src)
return nil
}
}
func (a *authSHA1V4) GetProtocolOverhead() int {
return 7
}
func (a *authSHA1V4) SetOverhead(overhead int) {
a.Overhead = overhead
}
func (a *authSHA1V4) Decode(b []byte) ([]byte, int, error) {
a.buffer.Reset()
bSize := len(b)
originalSize := bSize
for bSize > 4 {
crc := crc32.ChecksumIEEE(b[:2]) & 0xFFFF
if binary.LittleEndian.Uint16(b[2:4]) != uint16(crc) {
return nil, 0, errAuthSHA1v4CRC32Error
for src.Len() > 4 {
if uint16(crc32.ChecksumIEEE(src.Bytes()[:2])&0xffff) != binary.LittleEndian.Uint16(src.Bytes()[2:4]) {
src.Reset()
return errAuthSHA1V4CRC32Error
}
length := int(binary.BigEndian.Uint16(b[:2]))
if length >= 8192 || length < 8 {
return nil, 0, errAuthSHA1v4DataLengthError
length := int(binary.BigEndian.Uint16(src.Bytes()[:2]))
if length >= 8192 || length < 7 {
a.rawTrans = true
src.Reset()
return errAuthSHA1V4LengthError
}
if length > bSize {
if length > src.Len() {
break
}
if adler32.Checksum(b[:length-4]) == binary.LittleEndian.Uint32(b[length-4:]) {
pos := int(b[4])
if pos != 0xFF {
pos += 4
} else {
pos = int(binary.BigEndian.Uint16(b[5:5+2])) + 4
}
retSize := length - pos - 4
a.buffer.Write(b[pos : pos+retSize])
bSize -= length
b = b[length:]
if adler32.Checksum(src.Bytes()[:length-4]) != binary.LittleEndian.Uint32(src.Bytes()[length-4:length]) {
a.rawTrans = true
src.Reset()
return errAuthSHA1V4Adler32Error
}
pos := int(src.Bytes()[4])
if pos < 255 {
pos += 4
} else {
return nil, 0, errAuthSHA1v4IncorrectChecksum
pos = int(binary.BigEndian.Uint16(src.Bytes()[5:7])) + 4
}
dst.Write(src.Bytes()[pos : length-4])
src.Next(length)
}
return a.buffer.Bytes(), originalSize - bSize, nil
return nil
}
func (a *authSHA1V4) Encode(b []byte) ([]byte, error) {
a.buffer.Reset()
bSize := len(b)
offset := 0
if !a.headerSent && bSize > 0 {
headSize := getHeadSize(b, 30)
if headSize > bSize {
headSize = bSize
}
a.buffer.Write(a.packAuthData(b[:headSize]))
offset += headSize
bSize -= headSize
a.headerSent = true
func (a *authSHA1V4) Encode(buf *bytes.Buffer, b []byte) error {
if !a.hasSentHeader {
dataLength := getDataLength(b)
a.packAuthData(buf, b[:dataLength])
b = b[dataLength:]
a.hasSentHeader = true
}
const blockSize = 4096
for bSize > blockSize {
packSize, randSize := a.packedDataSize(b[offset : offset+blockSize])
pack := pool.Get(packSize)
a.packData(b[offset:offset+blockSize], pack, randSize)
a.buffer.Write(pack)
pool.Put(pack)
offset += blockSize
bSize -= blockSize
for len(b) > 8100 {
a.packData(buf, b[:8100])
b = b[8100:]
}
if bSize > 0 {
packSize, randSize := a.packedDataSize(b[offset:])
pack := pool.Get(packSize)
a.packData(b[offset:], pack, randSize)
a.buffer.Write(pack)
pool.Put(pack)
if len(b) > 0 {
a.packData(buf, b)
}
return a.buffer.Bytes(), nil
return nil
}
func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, int, error) {
return b, len(b), nil
func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, error) { return b, nil }
func (a *authSHA1V4) EncodePacket(buf *bytes.Buffer, b []byte) error {
buf.Write(b)
return nil
}
func (a *authSHA1V4) EncodePacket(b []byte) ([]byte, error) {
return b, nil
func (a *authSHA1V4) packData(poolBuf *bytes.Buffer, data []byte) {
dataLength := len(data)
randDataLength := a.getRandDataLength(dataLength)
/*
2: uint16 BigEndian packedDataLength
2: uint16 LittleEndian crc32Data & 0xffff
3: maxRandDataLengthPrefix (min:1)
4: adler32Data
*/
packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4
if randDataLength < 128 {
packedDataLength -= 2
}
binary.Write(poolBuf, binary.BigEndian, uint16(packedDataLength))
binary.Write(poolBuf, binary.LittleEndian, uint16(crc32.ChecksumIEEE(poolBuf.Bytes()[poolBuf.Len()-2:])&0xffff))
a.packRandData(poolBuf, randDataLength)
poolBuf.Write(data)
binary.Write(poolBuf, binary.LittleEndian, adler32.Checksum(poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:]))
}
func (a *authSHA1V4) packedDataSize(data []byte) (packSize, randSize int) {
dataSize := len(data)
randSize = 1
if dataSize <= 1300 {
if dataSize > 400 {
randSize += rand.Intn(128)
} else {
randSize += rand.Intn(1024)
}
func (a *authSHA1V4) packAuthData(poolBuf *bytes.Buffer, data []byte) {
dataLength := len(data)
randDataLength := a.getRandDataLength(12 + dataLength)
/*
2: uint16 BigEndian packedAuthDataLength
4: uint32 LittleEndian crc32Data
3: maxRandDataLengthPrefix (min: 1)
12: authDataLength
10: hmacSHA1DataLength
*/
packedAuthDataLength := 2 + 4 + 3 + randDataLength + 12 + dataLength + 10
if randDataLength < 128 {
packedAuthDataLength -= 2
}
packSize = randSize + dataSize + 8
return
}
func (a *authSHA1V4) packData(data, ret []byte, randSize int) {
dataSize := len(data)
retSize := len(ret)
// 0~1, ret size
binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF))
// 2~3, crc of ret size
crc := crc32.ChecksumIEEE(ret[:2]) & 0xFFFF
binary.LittleEndian.PutUint16(ret[2:4], uint16(crc))
// 4, rand size
if randSize < 128 {
ret[4] = uint8(randSize & 0xFF)
} else {
ret[4] = uint8(0xFF)
binary.BigEndian.PutUint16(ret[5:7], uint16(randSize&0xFFFF))
}
// (rand size+4)~(ret size-4), data
if dataSize > 0 {
copy(ret[randSize+4:], data)
}
// (ret size-4)~end, adler32 of full data
adler := adler32.Checksum(ret[:retSize-4])
binary.LittleEndian.PutUint32(ret[retSize-4:], adler)
}
func (a *authSHA1V4) packAuthData(data []byte) (ret []byte) {
dataSize := len(data)
randSize := 1
if dataSize <= 1300 {
if dataSize > 400 {
randSize += rand.Intn(128)
} else {
randSize += rand.Intn(1024)
}
}
dataOffset := randSize + 4 + 2
retSize := dataOffset + dataSize + 12 + tools.HmacSHA1Len
ret = make([]byte, retSize)
a.mutex.Lock()
defer a.mutex.Unlock()
a.connectionID++
if a.connectionID > 0xFF000000 {
a.clientID = nil
}
if len(a.clientID) == 0 {
a.clientID = make([]byte, 8)
rand.Read(a.clientID)
b := make([]byte, 4)
rand.Read(b)
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
}
// 0~1, ret size
binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF))
// 2~6, crc of (ret size+salt+key)
salt := []byte("auth_sha1_v4")
crcData := make([]byte, len(salt)+len(a.Key)+2)
copy(crcData[:2], ret[:2])
crcData := pool.Get(len(salt) + len(a.Key) + 2)
defer pool.Put(crcData)
binary.BigEndian.PutUint16(crcData, uint16(packedAuthDataLength))
copy(crcData[2:], salt)
copy(crcData[2+len(salt):], a.Key)
crc := crc32.ChecksumIEEE(crcData) & 0xFFFFFFFF
// 2~6, crc of (ret size+salt+key)
binary.LittleEndian.PutUint32(ret[2:], crc)
// 6~(rand size+6), rand numbers
rand.Read(ret[dataOffset-randSize : dataOffset])
// 6, rand size
if randSize < 128 {
ret[6] = byte(randSize & 0xFF)
} else {
// 6, magic number 0xFF
ret[6] = 0xFF
// 7~8, rand size
binary.BigEndian.PutUint16(ret[7:9], uint16(randSize&0xFFFF))
}
// rand size+6~(rand size+10), time stamp
now := time.Now().Unix()
binary.LittleEndian.PutUint32(ret[dataOffset:dataOffset+4], uint32(now))
// rand size+10~(rand size+14), client ID
copy(ret[dataOffset+4:dataOffset+4+4], a.clientID[:4])
// rand size+14~(rand size+18), connection ID
binary.LittleEndian.PutUint32(ret[dataOffset+8:dataOffset+8+4], a.connectionID)
// rand size+18~(rand size+18)+data length, data
copy(ret[dataOffset+12:], data)
key := make([]byte, len(a.IV)+len(a.Key))
copy(key, a.IV)
copy(key[len(a.IV):], a.Key)
key := pool.Get(len(a.iv) + len(a.Key))
defer pool.Put(key)
copy(key, a.iv)
copy(key[len(a.iv):], a.Key)
h := tools.HmacSHA1(key, ret[:retSize-tools.HmacSHA1Len])
// (ret size-10)~(ret size)/(rand size)+18+data length~end, hmac
copy(ret[retSize-tools.HmacSHA1Len:], h[:tools.HmacSHA1Len])
return ret
poolBuf.Write(crcData[:2])
binary.Write(poolBuf, binary.LittleEndian, crc32.ChecksumIEEE(crcData))
a.packRandData(poolBuf, randDataLength)
a.putAuthData(poolBuf)
poolBuf.Write(data)
poolBuf.Write(tools.HmacSHA1(key, poolBuf.Bytes()[poolBuf.Len()-packedAuthDataLength+10:])[:10])
}
func getHeadSize(data []byte, defaultValue int) int {
if data == nil || len(data) < 2 {
return defaultValue
func (a *authSHA1V4) packRandData(poolBuf *bytes.Buffer, size int) {
if size < 128 {
poolBuf.WriteByte(byte(size + 1))
tools.AppendRandBytes(poolBuf, size)
return
}
headType := data[0] & 0x07
switch headType {
case 1:
// IPv4 1+4+2
return 7
case 4:
// IPv6 1+16+2
return 19
case 3:
// domain name, variant length
return 4 + int(data[1])
}
return defaultValue
poolBuf.WriteByte(255)
binary.Write(poolBuf, binary.BigEndian, uint16(size+3))
tools.AppendRandBytes(poolBuf, size)
}
func (a *authSHA1V4) getRandDataLength(size int) int {
if size > 1200 {
return 0
}
if size > 400 {
return rand.Intn(256)
}
return rand.Intn(512)
}

View File

@ -1,10 +1,77 @@
package protocol
// Base information for protocol
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/binary"
"math/rand"
"sync"
"time"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/go-shadowsocks2/core"
)
type Base struct {
IV []byte
Key []byte
TCPMss int
Overhead int
Param string
}
type userData struct {
userKey []byte
userID [4]byte
}
type authData struct {
clientID [4]byte
connectionID uint32
mutex sync.Mutex
}
func (a *authData) next() *authData {
r := &authData{}
a.mutex.Lock()
defer a.mutex.Unlock()
if a.connectionID > 0xff000000 || a.connectionID == 0 {
rand.Read(a.clientID[:])
a.connectionID = rand.Uint32() & 0xffffff
}
a.connectionID++
copy(r.clientID[:], a.clientID[:])
r.connectionID = a.connectionID
return r
}
func (a *authData) putAuthData(buf *bytes.Buffer) {
binary.Write(buf, binary.LittleEndian, uint32(time.Now().Unix()))
buf.Write(a.clientID[:])
binary.Write(buf, binary.LittleEndian, a.connectionID)
}
func (a *authData) putEncryptedData(b *bytes.Buffer, userKey []byte, paddings [2]int, salt string) error {
encrypt := pool.Get(16)
defer pool.Put(encrypt)
binary.LittleEndian.PutUint32(encrypt, uint32(time.Now().Unix()))
copy(encrypt[4:], a.clientID[:])
binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID)
binary.LittleEndian.PutUint16(encrypt[12:], uint16(paddings[0]))
binary.LittleEndian.PutUint16(encrypt[14:], uint16(paddings[1]))
cipherKey := core.Kdf(base64.StdEncoding.EncodeToString(userKey)+salt, 16)
block, err := aes.NewCipher(cipherKey)
if err != nil {
log.Warnln("New cipher error: %s", err.Error())
return err
}
iv := bytes.Repeat([]byte{0}, 16)
cbcCipher := cipher.NewCBCEncrypter(block, iv)
cbcCipher.CryptBlocks(encrypt, encrypt)
b.Write(encrypt)
return nil
}

View File

@ -1,36 +1,33 @@
package protocol
type origin struct{ *Base }
import (
"bytes"
"net"
)
func init() {
register("origin", newOrigin)
type origin struct{}
func init() { register("origin", newOrigin, 0) }
func newOrigin(b *Base) Protocol { return &origin{} }
func (o *origin) StreamConn(c net.Conn, iv []byte) net.Conn { return c }
func (o *origin) PacketConn(c net.PacketConn) net.PacketConn { return c }
func (o *origin) Decode(dst, src *bytes.Buffer) error {
dst.ReadFrom(src)
return nil
}
func newOrigin(b *Base) Protocol {
return &origin{}
func (o *origin) Encode(buf *bytes.Buffer, b []byte) error {
buf.Write(b)
return nil
}
func (o *origin) initForConn(iv []byte) Protocol { return &origin{} }
func (o *origin) DecodePacket(b []byte) ([]byte, error) { return b, nil }
func (o *origin) GetProtocolOverhead() int {
return 0
}
func (o *origin) SetOverhead(overhead int) {
}
func (o *origin) Decode(b []byte) ([]byte, int, error) {
return b, len(b), nil
}
func (o *origin) Encode(b []byte) ([]byte, error) {
return b, nil
}
func (o *origin) DecodePacket(b []byte) ([]byte, int, error) {
return b, len(b), nil
}
func (o *origin) EncodePacket(b []byte) ([]byte, error) {
return b, nil
func (o *origin) EncodePacket(buf *bytes.Buffer, b []byte) error {
buf.Write(b)
return nil
}

View File

@ -1,30 +1,26 @@
package protocol
import (
"bytes"
"net"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
)
// NewPacketConn returns a net.NewPacketConn with protocol decoding/encoding
func NewPacketConn(pc net.PacketConn, p Protocol) net.PacketConn {
return &PacketConn{PacketConn: pc, Protocol: p.initForConn(nil)}
}
// PacketConn represents a protocol packet connection
type PacketConn struct {
net.PacketConn
Protocol
}
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
buf := pool.Get(pool.RelayBufferSize)
defer pool.Put(buf)
buf, err := c.EncodePacket(b)
buf := tools.BufPool.Get().(*bytes.Buffer)
defer tools.BufPool.Put(buf)
defer buf.Reset()
err := c.EncodePacket(buf, b)
if err != nil {
return 0, err
}
_, err = c.PacketConn.WriteTo(buf, addr)
_, err = c.PacketConn.WriteTo(buf.Bytes(), addr)
return len(b), err
}
@ -33,10 +29,10 @@ func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
if err != nil {
return n, addr, err
}
bb, length, err := c.DecodePacket(b[:n])
decoded, err := c.DecodePacket(b[:n])
if err != nil {
return n, addr, err
}
copy(b, bb)
return length, addr, err
copy(b, decoded)
return len(decoded), addr, nil
}

View File

@ -4,60 +4,73 @@ import (
"bytes"
"errors"
"fmt"
"strings"
"sync"
"math/rand"
"net"
)
var (
errAuthAES128IncorrectMAC = errors.New("auth_aes128_* post decrypt incorrect mac")
errAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch")
errAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum")
errAuthAES128PositionTooLarge = errors.New("auth_aes128_* post decrypt position is too large")
errAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error")
errAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error")
errAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum")
errAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch")
errAuthChainHMACError = errors.New("auth_chain_* post decrypt hmac error")
errAuthSHA1V4CRC32Error = errors.New("auth_sha1_v4 decode data wrong crc32")
errAuthSHA1V4LengthError = errors.New("auth_sha1_v4 decode data wrong length")
errAuthSHA1V4Adler32Error = errors.New("auth_sha1_v4 decode data wrong adler32")
errAuthAES128MACError = errors.New("auth_aes128 decode data wrong mac")
errAuthAES128LengthError = errors.New("auth_aes128 decode data wrong length")
errAuthAES128ChksumError = errors.New("auth_aes128 decode data wrong checksum")
errAuthChainLengthError = errors.New("auth_chain decode data wrong length")
errAuthChainChksumError = errors.New("auth_chain decode data wrong checksum")
)
type authData struct {
clientID []byte
connectionID uint32
mutex sync.Mutex
}
type recvInfo struct {
recvID uint32
buffer *bytes.Buffer
}
type hmacMethod func(key []byte, data []byte) []byte
type hashDigestMethod func(data []byte) []byte
type rndMethod func(dataSize int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int
// Protocol provides methods for decoding, encoding and iv setting
type Protocol interface {
initForConn(iv []byte) Protocol
GetProtocolOverhead() int
SetOverhead(int)
Decode([]byte) ([]byte, int, error)
Encode([]byte) ([]byte, error)
DecodePacket([]byte) ([]byte, int, error)
EncodePacket([]byte) ([]byte, error)
StreamConn(net.Conn, []byte) net.Conn
PacketConn(net.PacketConn) net.PacketConn
Decode(dst, src *bytes.Buffer) error
Encode(buf *bytes.Buffer, b []byte) error
DecodePacket([]byte) ([]byte, error)
EncodePacket(buf *bytes.Buffer, b []byte) error
}
type protocolCreator func(b *Base) Protocol
var protocolList = make(map[string]protocolCreator)
var protocolList = make(map[string]struct {
overhead int
new protocolCreator
})
func register(name string, c protocolCreator) {
protocolList[name] = c
func register(name string, c protocolCreator, o int) {
protocolList[name] = struct {
overhead int
new protocolCreator
}{overhead: o, new: c}
}
// PickProtocol returns a protocol of the given name
func PickProtocol(name string, b *Base) (Protocol, error) {
if protocolCreator, ok := protocolList[strings.ToLower(name)]; ok {
return protocolCreator(b), nil
if choice, ok := protocolList[name]; ok {
b.Overhead += choice.overhead
return choice.new(b), nil
}
return nil, fmt.Errorf("Protocol %s not supported", name)
return nil, fmt.Errorf("protocol %s not supported", name)
}
func getHeadSize(b []byte, defaultValue int) int {
if len(b) < 2 {
return defaultValue
}
headType := b[0] & 7
switch headType {
case 1:
return 7
case 4:
return 19
case 3:
return 4 + int(b[1])
}
return defaultValue
}
func getDataLength(b []byte) int {
bLength := len(b)
dataLength := getHeadSize(b, 30) + rand.Intn(32)
if bLength < dataLength {
return bLength
}
return dataLength
}

View File

@ -5,31 +5,21 @@ import (
"net"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
)
// NewConn wraps a stream-oriented net.Conn with protocol decoding/encoding
func NewConn(c net.Conn, p Protocol, iv []byte) net.Conn {
return &Conn{Conn: c, Protocol: p.initForConn(iv)}
}
// Conn represents a protocol connection
type Conn struct {
net.Conn
Protocol
buf []byte
offset int
decoded bytes.Buffer
underDecoded bytes.Buffer
}
func (c *Conn) Read(b []byte) (int, error) {
if c.buf != nil {
n := copy(b, c.buf[c.offset:])
c.offset += n
if c.offset == len(c.buf) {
c.buf = nil
}
return n, nil
if c.decoded.Len() > 0 {
return c.decoded.Read(b)
}
buf := pool.Get(pool.RelayBufferSize)
defer pool.Put(buf)
n, err := c.Conn.Read(buf)
@ -37,32 +27,26 @@ func (c *Conn) Read(b []byte) (int, error) {
return 0, err
}
c.underDecoded.Write(buf[:n])
underDecoded := c.underDecoded.Bytes()
decoded, length, err := c.Decode(underDecoded)
err = c.Decode(&c.decoded, &c.underDecoded)
if err != nil {
c.underDecoded.Reset()
return 0, nil
}
if length == 0 {
return 0, nil
}
c.underDecoded.Next(length)
n = copy(b, decoded)
if len(decoded) > len(b) {
c.buf = decoded
c.offset = n
return 0, err
}
n, _ = c.decoded.Read(b)
return n, nil
}
func (c *Conn) Write(b []byte) (int, error) {
encoded, err := c.Encode(b)
bLength := len(b)
buf := tools.BufPool.Get().(*bytes.Buffer)
defer tools.BufPool.Put(buf)
defer buf.Reset()
err := c.Encode(buf, b)
if err != nil {
return 0, err
}
_, err = c.Conn.Write(encoded)
_, err = c.Conn.Write(buf.Bytes())
if err != nil {
return 0, err
}
return len(b), nil
return bLength, nil
}

View 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)
}

View File

@ -11,13 +11,13 @@ const HmacSHA1Len = 10
func HmacMD5(key, data []byte) []byte {
hmacMD5 := hmac.New(md5.New, key)
hmacMD5.Write(data)
return hmacMD5.Sum(nil)[:16]
return hmacMD5.Sum(nil)
}
func HmacSHA1(key, data []byte) []byte {
hmacSHA1 := hmac.New(sha1.New, key)
hmacSHA1.Write(data)
return hmacSHA1.Sum(nil)[:20]
return hmacSHA1.Sum(nil)
}
func MD5Sum(b []byte) []byte {

View 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()
}
}

View File

@ -46,6 +46,7 @@ func (hc *h2Conn) establishConn() error {
},
}
// it will be close at : `func (hc *h2Conn) Close() error`
res, err := hc.ClientConn.RoundTrip(&req)
if err != nil {
return err

View File

@ -30,7 +30,7 @@ type General struct {
Mode T.TunnelMode `json:"mode"`
LogLevel log.LogLevel `json:"log-level"`
IPv6 bool `json:"ipv6"`
Interface string `json:"interface-name"`
Interface string `json:"-"`
}
// Inbound
@ -73,6 +73,11 @@ type FallbackFilter struct {
Domain []string `yaml:"domain"`
}
// Profile config
type Profile struct {
StoreSelected bool `yaml:"store-selected"`
}
// Experimental config
type Experimental struct{}
@ -82,6 +87,7 @@ type Config struct {
DNS *DNS
Experimental *Experimental
Hosts *trie.DomainTrie
Profile *Profile
Rules []C.Rule
Users []auth.AuthUser
Proxies map[string]C.Proxy
@ -129,6 +135,7 @@ type RawConfig struct {
Hosts map[string]string `yaml:"hosts"`
DNS RawDNS `yaml:"dns"`
Experimental Experimental `yaml:"experimental"`
Profile Profile `yaml:"profile"`
Proxy []map[string]interface{} `yaml:"proxies"`
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
Rule []string `yaml:"rules"`
@ -145,7 +152,7 @@ func Parse(buf []byte) (*Config, error) {
}
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
// config with some default value
// config with default value
rawCfg := &RawConfig{
AllowLan: false,
BindAddress: "*",
@ -169,6 +176,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
"8.8.8.8",
},
},
Profile: Profile{
StoreSelected: true,
},
}
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
@ -182,6 +192,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config := &Config{}
config.Experimental = &rawCfg.Experimental
config.Profile = &rawCfg.Profile
general, err := parseGeneral(rawCfg)
if err != nil {

View File

@ -12,7 +12,7 @@ import (
)
func downloadMMDB(path string) (err error) {
resp, err := http.Get("https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb")
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Dreamacro/maxmind-geoip@release/Country.mmdb")
if err != nil {
return
}

View File

@ -27,11 +27,6 @@ const (
LoadBalance
)
type ServerAdapter interface {
net.Conn
Metadata() *Metadata
}
type Connection interface {
Chains() Chain
AppendToChains(adapter ProxyAdapter)
@ -50,6 +45,15 @@ func (c Chain) String() string {
}
}
func (c Chain) Last() string {
switch len(c) {
case 0:
return ""
default:
return c[0]
}
}
type Conn interface {
net.Conn
Connection

23
constant/context.go Normal file
View File

@ -0,0 +1,23 @@
package constant
import (
"net"
"github.com/gofrs/uuid"
)
type PlainContext interface {
ID() uuid.UUID
}
type ConnContext interface {
PlainContext
Metadata() *Metadata
Conn() net.Conn
}
type PacketConnContext interface {
PlainContext
Metadata() *Metadata
PacketConn() net.PacketConn
}

View File

@ -56,3 +56,7 @@ func (p *path) Resolve(path string) string {
func (p *path) MMDB() string {
return P.Join(p.homeDir, "Country.mmdb")
}
func (p *path) Cache() string {
return P.Join(p.homeDir, ".cache")
}

39
context/conn.go Normal file
View File

@ -0,0 +1,39 @@
package context
import (
"net"
C "github.com/Dreamacro/clash/constant"
"github.com/gofrs/uuid"
)
type ConnContext struct {
id uuid.UUID
metadata *C.Metadata
conn net.Conn
}
func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext {
id, _ := uuid.NewV4()
return &ConnContext{
id: id,
metadata: metadata,
conn: conn,
}
}
// ID implement C.ConnContext ID
func (c *ConnContext) ID() uuid.UUID {
return c.id
}
// Metadata implement C.ConnContext Metadata
func (c *ConnContext) Metadata() *C.Metadata {
return c.metadata
}
// Conn implement C.ConnContext Conn
func (c *ConnContext) Conn() net.Conn {
return c.conn
}

41
context/dns.go Normal file
View File

@ -0,0 +1,41 @@
package context
import (
"github.com/gofrs/uuid"
"github.com/miekg/dns"
)
const (
DNSTypeHost = "host"
DNSTypeFakeIP = "fakeip"
DNSTypeRaw = "raw"
)
type DNSContext struct {
id uuid.UUID
msg *dns.Msg
tp string
}
func NewDNSContext(msg *dns.Msg) *DNSContext {
id, _ := uuid.NewV4()
return &DNSContext{
id: id,
msg: msg,
}
}
// ID implement C.PlainContext ID
func (c *DNSContext) ID() uuid.UUID {
return c.id
}
// SetType set type of response
func (c *DNSContext) SetType(tp string) {
c.tp = tp
}
// Type return type of response
func (c *DNSContext) Type() string {
return c.tp
}

47
context/http.go Normal file
View File

@ -0,0 +1,47 @@
package context
import (
"net"
"net/http"
C "github.com/Dreamacro/clash/constant"
"github.com/gofrs/uuid"
)
type HTTPContext struct {
id uuid.UUID
metadata *C.Metadata
conn net.Conn
req *http.Request
}
func NewHTTPContext(conn net.Conn, req *http.Request, metadata *C.Metadata) *HTTPContext {
id, _ := uuid.NewV4()
return &HTTPContext{
id: id,
metadata: metadata,
conn: conn,
req: req,
}
}
// ID implement C.ConnContext ID
func (hc *HTTPContext) ID() uuid.UUID {
return hc.id
}
// Metadata implement C.ConnContext Metadata
func (hc *HTTPContext) Metadata() *C.Metadata {
return hc.metadata
}
// Conn implement C.ConnContext Conn
func (hc *HTTPContext) Conn() net.Conn {
return hc.conn
}
// Request return the http request struct
func (hc *HTTPContext) Request() *http.Request {
return hc.req
}

43
context/packetconn.go Normal file
View File

@ -0,0 +1,43 @@
package context
import (
"net"
C "github.com/Dreamacro/clash/constant"
"github.com/gofrs/uuid"
)
type PacketConnContext struct {
id uuid.UUID
metadata *C.Metadata
packetConn net.PacketConn
}
func NewPacketConnContext(metadata *C.Metadata) *PacketConnContext {
id, _ := uuid.NewV4()
return &PacketConnContext{
id: id,
metadata: metadata,
}
}
// ID implement C.PacketConnContext ID
func (pc *PacketConnContext) ID() uuid.UUID {
return pc.id
}
// Metadata implement C.PacketConnContext Metadata
func (pc *PacketConnContext) Metadata() *C.Metadata {
return pc.metadata
}
// PacketConn implement C.PacketConnContext PacketConn
func (pc *PacketConnContext) PacketConn() net.PacketConn {
return pc.packetConn
}
// InjectPacketConn injectPacketConn manually
func (pc *PacketConnContext) InjectPacketConn(pconn C.PacketConn) {
pc.packetConn = pconn
}

View File

@ -39,7 +39,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err
return nil, err
}
if dialer.DialHook != nil {
if ip != nil && ip.IsGlobalUnicast() && dialer.DialHook != nil {
network := "udp"
if strings.HasPrefix(c.Client.Net, "tcp") {
network = "tcp"

View File

@ -8,26 +8,27 @@ import (
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/trie"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
)
type handler func(r *D.Msg) (*D.Msg, error)
type handler func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error)
type middleware func(next handler) handler
func withHosts(hosts *trie.DomainTrie) middleware {
return func(next handler) handler {
return func(r *D.Msg) (*D.Msg, error) {
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
q := r.Question[0]
if !isIPRequest(q) {
return next(r)
return next(ctx, r)
}
record := hosts.Search(strings.TrimRight(q.Name, "."))
if record == nil {
return next(r)
return next(ctx, r)
}
ip := record.Data.(net.IP)
@ -46,9 +47,10 @@ func withHosts(hosts *trie.DomainTrie) middleware {
msg.Answer = []D.RR{rr}
} else {
return next(r)
return next(ctx, r)
}
ctx.SetType(context.DNSTypeHost)
msg.SetRcode(r, D.RcodeSuccess)
msg.Authoritative = true
msg.RecursionAvailable = true
@ -60,14 +62,14 @@ func withHosts(hosts *trie.DomainTrie) middleware {
func withMapping(mapping *cache.LruCache) middleware {
return func(next handler) handler {
return func(r *D.Msg) (*D.Msg, error) {
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
q := r.Question[0]
if !isIPRequest(q) {
return next(r)
return next(ctx, r)
}
msg, err := next(r)
msg, err := next(ctx, r)
if err != nil {
return nil, err
}
@ -99,12 +101,12 @@ func withMapping(mapping *cache.LruCache) middleware {
func withFakeIP(fakePool *fakeip.Pool) middleware {
return func(next handler) handler {
return func(r *D.Msg) (*D.Msg, error) {
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
q := r.Question[0]
host := strings.TrimRight(q.Name, ".")
if fakePool.LookupHost(host) {
return next(r)
return next(ctx, r)
}
switch q.Qtype {
@ -113,7 +115,7 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
}
if q.Qtype != D.TypeA {
return next(r)
return next(ctx, r)
}
rr := &D.A{}
@ -123,6 +125,7 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
msg := r.Copy()
msg.Answer = []D.RR{rr}
ctx.SetType(context.DNSTypeFakeIP)
setMsgTTL(msg, 1)
msg.SetRcode(r, D.RcodeSuccess)
msg.Authoritative = true
@ -134,7 +137,8 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
}
func withResolver(resolver *Resolver) handler {
return func(r *D.Msg) (*D.Msg, error) {
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
ctx.SetType(context.DNSTypeRaw)
q := r.Question[0]
// return a empty AAAA msg when ipv6 disabled

View File

@ -212,7 +212,7 @@ func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) {
fallbackMsg := r.asyncExchange(r.fallback, m)
res := <-msgCh
if res.Error == nil {
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
if ips := msgToIP(res.Msg); len(ips) != 0 {
if !r.shouldIPFallback(ips[0]) {
msg = res.Msg // no need to wait for fallback result
err = res.Error
@ -247,7 +247,7 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
return nil, err
}
ips := r.msgToIP(msg)
ips := msgToIP(msg)
ipLength := len(ips)
if ipLength == 0 {
return nil, resolver.ErrIPNotFound
@ -257,21 +257,6 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
return
}
func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
ips := []net.IP{}
for _, answer := range msg.Answer {
switch ans := answer.(type) {
case *D.AAAA:
ips = append(ips, ans.AAAA)
case *D.A:
ips = append(ips, ans.A)
}
}
return ips
}
func (r *Resolver) msgToDomain(msg *D.Msg) string {
if len(msg.Question) > 0 {
return strings.TrimRight(msg.Question[0].Name, ".")

View File

@ -1,9 +1,11 @@
package dns
import (
"errors"
"net"
"github.com/Dreamacro/clash/common/sockopt"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
@ -21,21 +23,25 @@ type Server struct {
handler handler
}
// ServeDNS implement D.Handler ServeDNS
func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
if len(r.Question) == 0 {
D.HandleFailed(w, r)
return
}
msg, err := s.handler(r)
msg, err := handlerWithContext(s.handler, r)
if err != nil {
D.HandleFailed(w, r)
return
}
w.WriteMsg(msg)
}
func handlerWithContext(handler handler, msg *D.Msg) (*D.Msg, error) {
if len(msg.Question) == 0 {
return nil, errors.New("at least one question is required")
}
ctx := context.NewDNSContext(msg)
return handler(ctx, msg)
}
func (s *Server) setHandler(handler handler) {
s.handler = handler
}

View File

@ -153,3 +153,18 @@ func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
return msg
}
func msgToIP(msg *D.Msg) []net.IP {
ips := []net.IP{}
for _, answer := range msg.Answer {
switch ans := answer.(type) {
case *D.AAAA:
ips = append(ips, ans.AAAA)
case *D.A:
ips = append(ips, ans.A)
}
}
return ips
}

20
go.mod
View File

@ -1,22 +1,22 @@
module github.com/Dreamacro/clash
go 1.15
go 1.16
require (
github.com/Dreamacro/go-shadowsocks2 v0.1.6
github.com/go-chi/chi v4.1.2+incompatible
github.com/go-chi/cors v1.1.1
github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v3.3.0+incompatible
github.com/gofrs/uuid v4.0.0+incompatible
github.com/gorilla/websocket v1.4.2
github.com/miekg/dns v1.1.35
github.com/oschwald/geoip2-golang v1.4.0
github.com/sirupsen/logrus v1.7.0
github.com/stretchr/testify v1.6.1
github.com/miekg/dns v1.1.40
github.com/oschwald/geoip2-golang v1.5.0
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
go.uber.org/atomic v1.7.0
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20201223074533-0d417f636930
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2
gopkg.in/yaml.v2 v2.4.0
)

43
go.sum
View File

@ -9,54 +9,52 @@ github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -65,7 +63,6 @@ golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapK
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=

View File

@ -6,9 +6,13 @@ import (
"os"
"sync"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/outboundgroup"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/profile"
"github.com/Dreamacro/clash/component/profile/cachefile"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/trie"
"github.com/Dreamacro/clash/config"
@ -72,6 +76,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateDNS(cfg.DNS)
updateHosts(cfg.Hosts)
updateExperimental(cfg)
updateProfile(cfg)
}
func GetGeneral() *config.General {
@ -94,6 +99,7 @@ func GetGeneral() *config.General {
},
Mode: tunnel.Mode(),
LogLevel: log.Level(),
IPv6: !resolver.DisableIPv6,
}
return general
@ -208,3 +214,38 @@ func updateUsers(users []auth.AuthUser) {
log.Infoln("Authentication of local server updated")
}
}
func updateProfile(cfg *config.Config) {
profileCfg := cfg.Profile
profile.StoreSelected.Store(profileCfg.StoreSelected)
if profileCfg.StoreSelected {
patchSelectGroup(cfg.Proxies)
}
}
func patchSelectGroup(proxies map[string]C.Proxy) {
mapping := cachefile.Cache().SelectedMap()
if mapping == nil {
return
}
for name, proxy := range proxies {
outbound, ok := proxy.(*outbound.Proxy)
if !ok {
continue
}
selector, ok := outbound.ProxyAdapter.(*outboundgroup.Selector)
if !ok {
continue
}
selected, exist := mapping[name]
if !exist {
continue
}
selector.Set(selected)
}
}

View File

@ -4,6 +4,7 @@ import (
"net/http"
"path/filepath"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/log"
@ -32,6 +33,7 @@ type configSchema struct {
BindAddress *string `json:"bind-address"`
Mode *tunnel.TunnelMode `json:"mode"`
LogLevel *log.LogLevel `json:"log-level"`
IPv6 *bool `json:"ipv6"`
}
func getConfigs(w http.ResponseWriter, r *http.Request) {
@ -78,6 +80,10 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
log.SetLevel(*general.LogLevel)
}
if general.IPv6 != nil {
resolver.DisableIPv6 = !*general.IPv6
}
render.NoContent(w, r)
}

View File

@ -7,7 +7,7 @@ import (
"strconv"
"time"
T "github.com/Dreamacro/clash/tunnel"
"github.com/Dreamacro/clash/tunnel/statistic"
"github.com/gorilla/websocket"
"github.com/go-chi/chi"
@ -24,7 +24,7 @@ func connectionRouter() http.Handler {
func getConnections(w http.ResponseWriter, r *http.Request) {
if !websocket.IsWebSocketUpgrade(r) {
snapshot := T.DefaultManager.Snapshot()
snapshot := statistic.DefaultManager.Snapshot()
render.JSON(w, r, snapshot)
return
}
@ -50,7 +50,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
buf := &bytes.Buffer{}
sendSnapshot := func() error {
buf.Reset()
snapshot := T.DefaultManager.Snapshot()
snapshot := statistic.DefaultManager.Snapshot()
if err := json.NewEncoder(buf).Encode(snapshot); err != nil {
return err
}
@ -73,7 +73,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
func closeConnection(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
snapshot := T.DefaultManager.Snapshot()
snapshot := statistic.DefaultManager.Snapshot()
for _, c := range snapshot.Connections {
if id == c.ID() {
c.Close()
@ -84,7 +84,7 @@ func closeConnection(w http.ResponseWriter, r *http.Request) {
}
func closeAllConnections(w http.ResponseWriter, r *http.Request) {
snapshot := T.DefaultManager.Snapshot()
snapshot := statistic.DefaultManager.Snapshot()
for _, c := range snapshot.Connections {
c.Close()
}

View File

@ -9,6 +9,7 @@ import (
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/outboundgroup"
"github.com/Dreamacro/clash/component/profile/cachefile"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel"
@ -91,6 +92,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
return
}
cachefile.Cache().SetSelected(proxy.Name(), req.Name)
render.NoContent(w, r)
}

View File

@ -9,7 +9,7 @@ import (
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
T "github.com/Dreamacro/clash/tunnel"
"github.com/Dreamacro/clash/tunnel/statistic"
"github.com/go-chi/chi"
"github.com/go-chi/cors"
@ -143,7 +143,7 @@ func traffic(w http.ResponseWriter, r *http.Request) {
tick := time.NewTicker(time.Second)
defer tick.Stop()
t := T.DefaultManager
t := statistic.DefaultManager
buf := &bytes.Buffer{}
var err error
for range tick.C {

View File

@ -10,18 +10,22 @@ import (
"time"
"github.com/Dreamacro/clash/adapters/inbound"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
)
func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) {
req := request.R
host := req.Host
func handleHTTP(ctx *context.HTTPContext, outbound net.Conn) {
req := ctx.Request()
conn := ctx.Conn()
inboundReader := bufio.NewReader(request)
inboundReader := bufio.NewReader(conn)
outboundReader := bufio.NewReader(outbound)
inbound.RemoveExtraHTTPHostPort(req)
host := req.Host
for {
keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive"
@ -34,6 +38,8 @@ func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) {
}
handleResponse:
// resp will be closed after we call resp.Write()
// see https://golang.org/pkg/net/http/#Response.Write
resp, err := http.ReadResponse(outboundReader, req)
if err != nil {
break
@ -41,7 +47,7 @@ func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) {
inbound.RemoveHopByHopHeaders(resp.Header)
if resp.StatusCode == http.StatusContinue {
err = resp.Write(request)
err = resp.Write(conn)
if err != nil {
break
}
@ -56,14 +62,14 @@ func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) {
} else {
resp.Close = true
}
err = resp.Write(request)
err = resp.Write(conn)
if err != nil || resp.Close {
break
}
// even if resp.Write write body to the connection, but some http request have to Copy to close it
buf := pool.Get(pool.RelayBufferSize)
_, err = io.CopyBuffer(request, resp.Body, buf)
_, err = io.CopyBuffer(conn, resp.Body, buf)
pool.Put(buf)
if err != nil && err != io.EOF {
break
@ -74,6 +80,7 @@ func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) {
break
}
inbound.RemoveExtraHTTPHostPort(req)
// Sometimes firefox just open a socket to process multiple domains in HTTP
// The temporary solution is close connection when encountering different HOST
if req.Host != host {
@ -99,8 +106,13 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
return errors.New("udp addr invalid")
}
_, err := pc.WriteTo(packet.Data(), addr)
return err
if _, err := pc.WriteTo(packet.Data(), addr); err != nil {
return err
}
// reset timeout
pc.SetReadDeadline(time.Now().Add(udpTimeout))
return nil
}
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {
@ -127,8 +139,8 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n
}
}
func handleSocket(request C.ServerAdapter, outbound net.Conn) {
relay(request, outbound)
func handleSocket(ctx C.ConnContext, outbound net.Conn) {
relay(ctx.Conn(), outbound)
}
// relay copies between left and right bidirectionally.
@ -137,14 +149,16 @@ func relay(leftConn, rightConn net.Conn) {
go func() {
buf := pool.Get(pool.RelayBufferSize)
_, err := io.CopyBuffer(leftConn, rightConn, buf)
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
// See also https://github.com/Dreamacro/clash/pull/1209
_, err := io.CopyBuffer(N.WriteOnlyWriter{Writer: leftConn}, N.ReadOnlyReader{Reader: rightConn}, buf)
pool.Put(buf)
leftConn.SetReadDeadline(time.Now())
ch <- err
}()
buf := pool.Get(pool.RelayBufferSize)
io.CopyBuffer(rightConn, leftConn, buf)
io.CopyBuffer(N.WriteOnlyWriter{Writer: rightConn}, N.ReadOnlyReader{Reader: leftConn}, buf)
pool.Put(buf)
rightConn.SetReadDeadline(time.Now())
<-ch

View File

@ -1,4 +1,4 @@
package tunnel
package statistic
import (
"sync"

View File

@ -1,4 +1,4 @@
package tunnel
package statistic
import (
"net"
@ -57,7 +57,7 @@ func (tt *tcpTracker) Close() error {
return tt.Conn.Close()
}
func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker {
func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker {
uuid, _ := uuid.NewV4()
t := &tcpTracker{
@ -114,7 +114,7 @@ func (ut *udpTracker) Close() error {
return ut.PacketConn.Close()
}
func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker {
func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker {
uuid, _ := uuid.NewV4()
ut := &udpTracker{

View File

@ -12,11 +12,13 @@ import (
"github.com/Dreamacro/clash/component/nat"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel/statistic"
)
var (
tcpQueue = make(chan C.ServerAdapter, 200)
tcpQueue = make(chan C.ConnContext, 200)
udpQueue = make(chan *inbound.PacketAdapter, 200)
natTable = nat.New()
rules []C.Rule
@ -36,8 +38,8 @@ func init() {
}
// Add request to queue
func Add(req C.ServerAdapter) {
tcpQueue <- req
func Add(ctx C.ConnContext) {
tcpQueue <- ctx
}
// AddPacket add udp Packet to queue
@ -119,6 +121,7 @@ func preHandleMetadata(metadata *C.Metadata) error {
// handle IP string on host
if ip := net.ParseIP(metadata.Host); ip != nil {
metadata.DstIP = ip
metadata.Host = ""
}
// preprocess enhanced-mode metadata
@ -141,9 +144,7 @@ func preHandleMetadata(metadata *C.Metadata) error {
return nil
}
func resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
var proxy C.Proxy
var rule C.Rule
func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) {
switch mode {
case Direct:
proxy = proxies["DIRECT"]
@ -151,13 +152,9 @@ func resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
proxy = proxies["GLOBAL"]
// Rule
default:
var err error
proxy, rule, err = match(metadata)
if err != nil {
return nil, nil, err
}
}
return proxy, rule, nil
return
}
func handleUDPConn(packet *inbound.PacketAdapter) {
@ -210,7 +207,8 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
cond.Broadcast()
}()
proxy, rule, err := resolveMetadata(metadata)
ctx := context.NewPacketConnContext(metadata)
proxy, rule, err := resolveMetadata(ctx, metadata)
if err != nil {
log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
return
@ -225,7 +223,8 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
}
return
}
pc := newUDPTracker(rawPc, DefaultManager, metadata, rule)
ctx.InjectPacketConn(rawPc)
pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule)
switch true {
case rule != nil:
@ -245,10 +244,10 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
}()
}
func handleTCPConn(localConn C.ServerAdapter) {
defer localConn.Close()
func handleTCPConn(ctx C.ConnContext) {
defer ctx.Conn().Close()
metadata := localConn.Metadata()
metadata := ctx.Metadata()
if !metadata.Valid() {
log.Warnln("[Metadata] not valid: %#v", metadata)
return
@ -259,7 +258,7 @@ func handleTCPConn(localConn C.ServerAdapter) {
return
}
proxy, rule, err := resolveMetadata(metadata)
proxy, rule, err := resolveMetadata(ctx, metadata)
if err != nil {
log.Warnln("[Metadata] parse failed: %s", err.Error())
return
@ -274,7 +273,7 @@ func handleTCPConn(localConn C.ServerAdapter) {
}
return
}
remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule)
remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule)
defer remoteConn.Close()
switch true {
@ -288,11 +287,11 @@ func handleTCPConn(localConn C.ServerAdapter) {
log.Infoln("[TCP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String())
}
switch adapter := localConn.(type) {
case *inbound.HTTPAdapter:
handleHTTP(adapter, remoteConn)
case *inbound.SocketAdapter:
handleSocket(adapter, remoteConn)
switch c := ctx.(type) {
case *context.HTTPContext:
handleHTTP(c, remoteConn)
default:
handleSocket(ctx, remoteConn)
}
}