Feature: add shadowsocks simple-obfs support
This commit is contained in:
parent
1c4b253ff0
commit
42b9e0c9e7
4 changed files with 310 additions and 8 deletions
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/simple-obfs"
|
||||||
"github.com/riobard/go-shadowsocks2/core"
|
"github.com/riobard/go-shadowsocks2/core"
|
||||||
"github.com/riobard/go-shadowsocks2/socks"
|
"github.com/riobard/go-shadowsocks2/socks"
|
||||||
)
|
)
|
||||||
|
@ -28,9 +29,11 @@ func (ss *ShadowsocksAdapter) Conn() net.Conn {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShadowSocks struct {
|
type ShadowSocks struct {
|
||||||
server string
|
server string
|
||||||
name string
|
name string
|
||||||
cipher core.Cipher
|
obfs string
|
||||||
|
obfsHost string
|
||||||
|
cipher core.Cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) Name() string {
|
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)
|
return nil, fmt.Errorf("%s connect error", ss.server)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
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)
|
c = ss.cipher.StreamConn(c)
|
||||||
_, err = c.Write(serializesSocksAddr(addr))
|
_, err = c.Write(serializesSocksAddr(addr))
|
||||||
return &ShadowsocksAdapter{conn: c}, err
|
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
|
var key []byte
|
||||||
server, cipher, password, _ := parseURL(ssURL)
|
server, cipher, password, _ := parseURL(ssURL)
|
||||||
ciph, err := core.PickCipher(cipher, key, password)
|
ciph, err := core.PickCipher(cipher, key, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error())
|
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{
|
return &ShadowSocks{
|
||||||
server: server,
|
server: server,
|
||||||
name: name,
|
name: name,
|
||||||
cipher: ciph,
|
cipher: ciph,
|
||||||
|
obfs: obfs,
|
||||||
|
obfsHost: obfsHost,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
88
common/simple-obfs/http.go
Normal file
88
common/simple-obfs/http.go
Normal 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
190
common/simple-obfs/tls.go
Normal 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()
|
||||||
|
}
|
|
@ -223,7 +223,8 @@ func (c *Config) parseProxies(cfg *ini.File) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2])
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue