From 42b9e0c9e7d73965443bd86e2b382b7bf3ddf36b Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Sun, 16 Sep 2018 23:02:32 +0800 Subject: [PATCH] Feature: add shadowsocks simple-obfs support --- adapters/remote/shadowsocks.go | 37 +++++-- common/simple-obfs/http.go | 88 +++++++++++++++ common/simple-obfs/tls.go | 190 +++++++++++++++++++++++++++++++++ config/config.go | 3 +- 4 files changed, 310 insertions(+), 8 deletions(-) create mode 100644 common/simple-obfs/http.go create mode 100644 common/simple-obfs/tls.go diff --git a/adapters/remote/shadowsocks.go b/adapters/remote/shadowsocks.go index 07590c9d..47035c60 100644 --- a/adapters/remote/shadowsocks.go +++ b/adapters/remote/shadowsocks.go @@ -9,6 +9,7 @@ import ( C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/common/simple-obfs" "github.com/riobard/go-shadowsocks2/core" "github.com/riobard/go-shadowsocks2/socks" ) @@ -28,9 +29,11 @@ func (ss *ShadowsocksAdapter) Conn() net.Conn { } type ShadowSocks struct { - server string - name string - cipher core.Cipher + server string + name string + obfs string + obfsHost string + cipher core.Cipher } func (ss *ShadowSocks) Name() string { @@ -47,22 +50,42 @@ func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err erro return nil, fmt.Errorf("%s connect error", ss.server) } tcpKeepAlive(c) + switch ss.obfs { + case "tls": + c = obfs.NewTLSObfs(c, ss.obfsHost) + case "http": + _, port, _ := net.SplitHostPort(ss.server) + c = obfs.NewHTTPObfs(c, ss.obfsHost, port) + } c = ss.cipher.StreamConn(c) _, err = c.Write(serializesSocksAddr(addr)) return &ShadowsocksAdapter{conn: c}, err } -func NewShadowSocks(name string, ssURL string) (*ShadowSocks, error) { +func NewShadowSocks(name string, ssURL string, option map[string]string) (*ShadowSocks, error) { var key []byte server, cipher, password, _ := parseURL(ssURL) ciph, err := core.PickCipher(cipher, key, password) if err != nil { return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) } + + obfs := "" + obfsHost := "bing.com" + if value, ok := option["obfs"]; ok { + obfs = value + } + + if value, ok := option["obfs-host"]; ok { + obfsHost = value + } + return &ShadowSocks{ - server: server, - name: name, - cipher: ciph, + server: server, + name: name, + cipher: ciph, + obfs: obfs, + obfsHost: obfsHost, }, nil } diff --git a/common/simple-obfs/http.go b/common/simple-obfs/http.go new file mode 100644 index 00000000..4c7c60dd --- /dev/null +++ b/common/simple-obfs/http.go @@ -0,0 +1,88 @@ +package obfs + +import ( + "bytes" + "encoding/base64" + "fmt" + "io" + "math/rand" + "net" + "net/http" +) + +// HTTPObfs is shadowsocks http simple-obfs implementation +type HTTPObfs struct { + net.Conn + host string + port string + buf []byte + offset int + firstRequest bool + firstResponse bool +} + +func (ho *HTTPObfs) Read(b []byte) (int, error) { + if ho.buf != nil { + n := copy(b, ho.buf[ho.offset:]) + ho.offset += n + if ho.offset == len(ho.buf) { + ho.buf = nil + } + return n, nil + } + + if ho.firstResponse { + buf := bufPool.Get().([]byte) + n, err := ho.Conn.Read(buf) + if err != nil { + bufPool.Put(buf[:cap(buf)]) + return 0, err + } + idx := bytes.Index(buf[:n], []byte("\r\n\r\n")) + if idx == -1 { + bufPool.Put(buf[:cap(buf)]) + return 0, io.EOF + } + ho.firstResponse = false + length := n - (idx + 4) + n = copy(b, buf[idx+4:n]) + if length > n { + ho.buf = buf[:idx+4+length] + ho.offset = idx + 4 + n + } else { + bufPool.Put(buf[:cap(buf)]) + } + return n, nil + } + return ho.Conn.Read(b) +} + +func (ho *HTTPObfs) Write(b []byte) (int, error) { + if ho.firstRequest { + randBytes := make([]byte, 16) + rand.Read(randBytes) + req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:])) + req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2)) + req.Header.Set("Upgrade", "websocket") + req.Header.Set("Connection", "Upgrade") + req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port) + req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes)) + req.ContentLength = int64(len(b)) + err := req.Write(ho.Conn) + ho.firstRequest = false + return len(b), err + } + + return ho.Conn.Write(b) +} + +// NewHTTPObfs return a HTTPObfs +func NewHTTPObfs(conn net.Conn, host string, port string) net.Conn { + return &HTTPObfs{ + Conn: conn, + firstRequest: true, + firstResponse: true, + host: host, + port: port, + } +} diff --git a/common/simple-obfs/tls.go b/common/simple-obfs/tls.go new file mode 100644 index 00000000..6cb52627 --- /dev/null +++ b/common/simple-obfs/tls.go @@ -0,0 +1,190 @@ +package obfs + +import ( + "bytes" + "encoding/binary" + "io" + "math/rand" + "net" + "sync" + "time" +) + +func init() { + rand.Seed(time.Now().Unix()) +} + +var bufPool = sync.Pool{New: func() interface{} { return make([]byte, 2048) }} + +// TLSObfs is shadowsocks tls simple-obfs implementation +type TLSObfs struct { + net.Conn + server string + remain int + firstRequest bool + firstResponse bool +} + +func (to *TLSObfs) read(b []byte, discardN int) (int, error) { + buf := bufPool.Get().([]byte) + _, err := io.ReadFull(to.Conn, buf[:discardN]) + if err != nil { + return 0, err + } + bufPool.Put(buf[:cap(buf)]) + + sizeBuf := make([]byte, 2) + _, err = io.ReadFull(to.Conn, sizeBuf) + if err != nil { + return 0, nil + } + + length := int(binary.BigEndian.Uint16(sizeBuf)) + if length > len(b) { + n, err := to.Conn.Read(b) + if err != nil { + return n, err + } + to.remain = length - n + return n, nil + } + + return io.ReadFull(to.Conn, b[:length]) +} + +func (to *TLSObfs) Read(b []byte) (int, error) { + if to.remain > 0 { + length := to.remain + if length > len(b) { + length = len(b) + } + + n, err := io.ReadFull(to.Conn, b[:length]) + to.remain -= n + return n, err + } + + if to.firstResponse { + // type + ver + lensize + 91 = 96 + // type + ver + lensize + 1 = 6 + // type + ver = 3 + to.firstResponse = false + return to.read(b, 105) + } + + // type + ver = 3 + return to.read(b, 3) +} + +func (to *TLSObfs) Write(b []byte) (int, error) { + if to.firstRequest { + helloMsg := makeClientHelloMsg(b, to.server) + _, err := to.Conn.Write(helloMsg) + to.firstRequest = false + return len(b), err + } + + size := bufPool.Get().([]byte) + binary.BigEndian.PutUint16(size[:2], uint16(len(b))) + + buf := &bytes.Buffer{} + buf.Write([]byte{0x17, 0x03, 0x03}) + buf.Write(size[:2]) + buf.Write(b) + _, err := to.Conn.Write(buf.Bytes()) + bufPool.Put(size[:cap(size)]) + return len(b), err +} + +// NewTLSObfs return a SimpleObfs +func NewTLSObfs(conn net.Conn, server string) net.Conn { + return &TLSObfs{ + Conn: conn, + server: server, + firstRequest: true, + firstResponse: true, + } +} + +func makeClientHelloMsg(data []byte, server string) []byte { + random := make([]byte, 32) + sessionID := make([]byte, 32) + size := make([]byte, 2) + rand.Read(random) + rand.Read(sessionID) + + buf := &bytes.Buffer{} + + // handshake, TLS 1.0 version, length + buf.WriteByte(22) + buf.Write([]byte{0x03, 0x01}) + length := uint16(212 + len(data) + len(server)) + buf.WriteByte(byte(length >> 8)) + buf.WriteByte(byte(length & 0xff)) + + // clientHello, length, TLS 1.2 version + buf.WriteByte(1) + binary.BigEndian.PutUint16(size, uint16(208+len(data)+len(server))) + buf.WriteByte(0) + buf.Write(size) + buf.Write([]byte{0x03, 0x03}) + + // random, sid len, sid + buf.Write(random) + buf.WriteByte(32) + buf.Write(sessionID) + + // cipher suites + buf.Write([]byte{0x00, 0x38}) + buf.Write([]byte{ + 0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, + 0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, + 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d, + 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff, + }) + + // compression + buf.Write([]byte{0x01, 0x00}) + + // extension length + binary.BigEndian.PutUint16(size, uint16(79+len(data)+len(server))) + buf.Write(size) + + // session ticket + buf.Write([]byte{0x00, 0x23}) + binary.BigEndian.PutUint16(size, uint16(len(data))) + buf.Write(size) + buf.Write(data) + + // server name + buf.Write([]byte{0x00, 0x00}) + binary.BigEndian.PutUint16(size, uint16(len(server)+5)) + buf.Write(size) + binary.BigEndian.PutUint16(size, uint16(len(server)+3)) + buf.Write(size) + buf.WriteByte(0) + binary.BigEndian.PutUint16(size, uint16(len(server))) + buf.Write(size) + buf.Write([]byte(server)) + + // ec_point + buf.Write([]byte{0x00, 0x0b, 0x00, 0x04, 0x03, 0x01, 0x00, 0x02}) + + // groups + buf.Write([]byte{0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18}) + + // signature + buf.Write([]byte{ + 0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, + 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01, + 0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, + }) + + // encrypt then mac + buf.Write([]byte{0x00, 0x16, 0x00, 0x00}) + + // extended master secret + buf.Write([]byte{0x00, 0x17, 0x00, 0x00}) + + return buf.Bytes() +} diff --git a/config/config.go b/config/config.go index 847c2327..47d68935 100644 --- a/config/config.go +++ b/config/config.go @@ -223,7 +223,8 @@ func (c *Config) parseProxies(cfg *ini.File) error { continue } ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2]) - ss, err := adapters.NewShadowSocks(key.Name(), ssURL) + option := parseOptions(5, proxy...) + ss, err := adapters.NewShadowSocks(key.Name(), ssURL, option) if err != nil { return err }