Feature: add shadowsocks simple-obfs support

This commit is contained in:
Dreamacro 2018-09-16 23:02:32 +08:00
parent 4082a2c700
commit 5d34cba681
4 changed files with 310 additions and 8 deletions

View File

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

View File

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

190
common/simple-obfs/tls.go Normal file
View File

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

View File

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