mirror of
https://github.com/xjasonlyu/tun2socks.git
synced 2025-10-06 01:07:03 +08:00
Feature: add simple-obfs support for ss
This commit is contained in:
94
component/simple-obfs/http.go
Normal file
94
component/simple-obfs/http.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package obfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/xjasonlyu/tun2socks/common/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
pool.Put(ho.buf)
|
||||||
|
ho.buf = nil
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ho.firstResponse {
|
||||||
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
|
n, err := ho.Conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
pool.Put(buf)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
|
||||||
|
if idx == -1 {
|
||||||
|
pool.Put(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 {
|
||||||
|
pool.Put(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 = ho.host
|
||||||
|
if ho.port != "80" {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
4
component/simple-obfs/obfs.go
Normal file
4
component/simple-obfs/obfs.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Package obfs provides obfuscation functionality for Shadowsocks protocol.
|
||||||
|
package obfs
|
||||||
|
|
||||||
|
// Ref: github.com/Dreamacro/clash/component/simple-obfs
|
198
component/simple-obfs/tls.go
Normal file
198
component/simple-obfs/tls.go
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
package obfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xjasonlyu/tun2socks/common/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 := pool.Get(discardN)
|
||||||
|
_, err := io.ReadFull(to.Conn, buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
pool.Put(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 + len_size + 91 = 96
|
||||||
|
// type + ver + len_size + 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) {
|
||||||
|
length := len(b)
|
||||||
|
for i := 0; i < length; i += chunkSize {
|
||||||
|
end := i + chunkSize
|
||||||
|
if end > length {
|
||||||
|
end = length
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := to.write(b[i:end])
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
buf.Write([]byte{0x17, 0x03, 0x03})
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(b)))
|
||||||
|
buf.Write(b)
|
||||||
|
_, err := to.Conn.Write(buf.Bytes())
|
||||||
|
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, 28)
|
||||||
|
sessionID := make([]byte, 32)
|
||||||
|
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)
|
||||||
|
buf.WriteByte(0)
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(208+len(data)+len(server)))
|
||||||
|
buf.Write([]byte{0x03, 0x03})
|
||||||
|
|
||||||
|
// random with timestamp, sid len, sid
|
||||||
|
binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix()))
|
||||||
|
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.Write(buf, binary.BigEndian, uint16(79+len(data)+len(server)))
|
||||||
|
|
||||||
|
// session ticket
|
||||||
|
buf.Write([]byte{0x00, 0x23})
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(data)))
|
||||||
|
buf.Write(data)
|
||||||
|
|
||||||
|
// server name
|
||||||
|
buf.Write([]byte{0x00, 0x00})
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(server)+5))
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(server)+3))
|
||||||
|
buf.WriteByte(0)
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(server)))
|
||||||
|
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()
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -50,20 +51,56 @@ func parseProxy(s string) (proxy.Dialer, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := u.Host
|
|
||||||
user := u.User.Username()
|
|
||||||
pass, _ := u.User.Password()
|
|
||||||
proto := strings.ToLower(u.Scheme)
|
proto := strings.ToLower(u.Scheme)
|
||||||
|
|
||||||
switch proto {
|
switch proto {
|
||||||
case "direct":
|
case "direct":
|
||||||
return proxy.NewDirect(), nil
|
return proxy.NewDirect(), nil
|
||||||
case "socks5":
|
case "socks5":
|
||||||
return proxy.NewSocks5(addr, user, pass)
|
return proxy.NewSocks5(parseSocks(u))
|
||||||
case "ss", "shadowsocks":
|
case "ss":
|
||||||
method, password := user, pass
|
return proxy.NewShadowSocks(parseShadowSocks(u))
|
||||||
return proxy.NewShadowSocks(addr, method, password)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unsupported protocol: %s", proto)
|
return nil, fmt.Errorf("unsupported protocol: %s", proto)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseSocks(u *url.URL) (address, username, password string) {
|
||||||
|
address = u.Host
|
||||||
|
username = u.User.Username()
|
||||||
|
password, _ = u.User.Password()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseShadowSocks(u *url.URL) (address, method, password, obfsMode, obfsHost string) {
|
||||||
|
address = u.Host
|
||||||
|
|
||||||
|
if pass, set := u.User.Password(); set {
|
||||||
|
method = u.User.Username()
|
||||||
|
password = pass
|
||||||
|
} else {
|
||||||
|
data, _ := base64.RawURLEncoding.DecodeString(u.User.String())
|
||||||
|
userInfo := strings.SplitN(string(data), ":", 2)
|
||||||
|
method = userInfo[0]
|
||||||
|
password = userInfo[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
rawQuery, _ := url.QueryUnescape(u.RawQuery)
|
||||||
|
for _, s := range strings.Split(rawQuery, ";") {
|
||||||
|
data := strings.SplitN(s, "=", 2)
|
||||||
|
if len(data) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := data[0]
|
||||||
|
value := data[1]
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "obfs":
|
||||||
|
obfsMode = value
|
||||||
|
case "obfs-host":
|
||||||
|
obfsHost = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/common/adapter"
|
"github.com/xjasonlyu/tun2socks/common/adapter"
|
||||||
"github.com/xjasonlyu/tun2socks/component/dialer"
|
"github.com/xjasonlyu/tun2socks/component/dialer"
|
||||||
|
obfs "github.com/xjasonlyu/tun2socks/component/simple-obfs"
|
||||||
"github.com/xjasonlyu/tun2socks/component/socks5"
|
"github.com/xjasonlyu/tun2socks/component/socks5"
|
||||||
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||||
@@ -17,17 +18,22 @@ type ShadowSocks struct {
|
|||||||
*Base
|
*Base
|
||||||
|
|
||||||
cipher core.Cipher
|
cipher core.Cipher
|
||||||
|
|
||||||
|
// simple-obfs plugin
|
||||||
|
obfsMode, obfsHost string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewShadowSocks(addr, method, password string) (*ShadowSocks, error) {
|
func NewShadowSocks(addr, method, password, obfsMode, obfsHost string) (*ShadowSocks, error) {
|
||||||
cipher, err := core.PickCipher(method, nil, password)
|
cipher, err := core.PickCipher(method, nil, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ss initialize: %w", err)
|
return nil, fmt.Errorf("ss initialize: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ShadowSocks{
|
return &ShadowSocks{
|
||||||
Base: NewBase(addr),
|
Base: NewBase(addr),
|
||||||
cipher: cipher,
|
cipher: cipher,
|
||||||
|
obfsMode: obfsMode,
|
||||||
|
obfsHost: obfsHost,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +50,14 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *adapter.Metada
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
switch ss.obfsMode {
|
||||||
|
case "tls":
|
||||||
|
c = obfs.NewTLSObfs(c, ss.obfsHost)
|
||||||
|
case "http":
|
||||||
|
_, port, _ := net.SplitHostPort(ss.addr)
|
||||||
|
c = obfs.NewHTTPObfs(c, ss.obfsHost, port)
|
||||||
|
}
|
||||||
|
|
||||||
c = ss.cipher.StreamConn(c)
|
c = ss.cipher.StreamConn(c)
|
||||||
_, err = c.Write(metadata.SerializesSocksAddr())
|
_, err = c.Write(metadata.SerializesSocksAddr())
|
||||||
return
|
return
|
||||||
|
Reference in New Issue
Block a user