Feature: add v2ray-plugin

This commit is contained in:
Dreamacro 2019-02-11 15:25:10 +08:00
parent 53b5ef199f
commit 2383cca2ce
6 changed files with 332 additions and 44 deletions

View File

@ -2,12 +2,15 @@ package adapters
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/common/structure"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/core"
@ -16,34 +19,60 @@ import (
type ShadowSocks struct {
*Base
server string
obfs string
obfsHost string
cipher core.Cipher
server string
cipher core.Cipher
// obfs
obfsMode string
obfsOption *simpleObfsOption
wsOption *v2rayObfs.WebsocketOption
}
type ShadowSocksOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"`
// deprecated when bump to 1.0
Obfs string `proxy:"obfs,omitempty"`
ObfsHost string `proxy:"obfs-host,omitempty"`
}
type simpleObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
}
type v2rayObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
}
func (ss *ShadowSocks) Generator(metadata *C.Metadata) (net.Conn, error) {
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout)
if err != nil {
return nil, fmt.Errorf("%s connect error", ss.server)
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
}
tcpKeepAlive(c)
switch ss.obfs {
switch ss.obfsMode {
case "tls":
c = obfs.NewTLSObfs(c, ss.obfsHost)
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
case "http":
_, port, _ := net.SplitHostPort(ss.server)
c = obfs.NewHTTPObfs(c, ss.obfsHost, port)
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket":
var err error
c, err = v2rayObfs.NewWebsocketObfs(c, ss.wsOption)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
}
}
c = ss.cipher.StreamConn(c)
_, err = c.Write(serializesSocksAddr(metadata))
@ -65,10 +94,49 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error())
}
obfs := option.Obfs
obfsHost := "bing.com"
if option.ObfsHost != "" {
obfsHost = option.ObfsHost
var wsOption *v2rayObfs.WebsocketOption
var obfsOption *simpleObfsOption
obfsMode := ""
// forward compatibility before 1.0
if option.Obfs != "" {
obfsMode = option.Obfs
obfsOption = &simpleObfsOption{
Host: "bing.com",
}
if option.ObfsHost != "" {
obfsOption.Host = option.ObfsHost
}
}
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
if option.Plugin == "obfs" {
opts := simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize obfs error: %s", server, err.Error())
}
obfsMode = opts.Mode
obfsOption = &opts
} else if option.Plugin == "v2ray-plugin" {
opts := v2rayObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error())
}
obfsMode = opts.Mode
var tlsConfig *tls.Config
if opts.TLS {
tlsConfig = &tls.Config{
ServerName: opts.Host,
InsecureSkipVerify: opts.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
}
}
wsOption = &v2rayObfs.WebsocketOption{
Host: opts.Host,
Path: opts.Path,
TLSConfig: tlsConfig,
}
}
return &ShadowSocks{
@ -76,10 +144,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
name: option.Name,
tp: C.Shadowsocks,
},
server: server,
cipher: ciph,
obfs: obfs,
obfsHost: obfsHost,
server: server,
cipher: ciph,
obfsMode: obfsMode,
wsOption: wsOption,
obfsOption: obfsOption,
}, nil
}

View File

@ -74,6 +74,8 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error
return d.decodeSlice(name, data, val)
case reflect.Map:
return d.decodeMap(name, data, val)
case reflect.Interface:
return d.setInterface(name, data, val)
default:
return fmt.Errorf("type %s not support", val.Kind().String())
}
@ -229,3 +231,9 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle
return nil
}
func (d *Decoder) setInterface(name string, data interface{}, val reflect.Value) (err error) {
dataVal := reflect.ValueOf(data)
val.Set(dataVal)
return nil
}

View File

@ -0,0 +1,173 @@
package obfs
import (
"bytes"
"encoding/binary"
"errors"
"io"
"net"
)
type SessionStatus = byte
const (
SessionStatusNew SessionStatus = 0x01
SessionStatusKeep SessionStatus = 0x02
SessionStatusEnd SessionStatus = 0x03
SessionStatusKeepAlive SessionStatus = 0x04
)
const (
OptionNone = byte(0x00)
OptionData = byte(0x01)
OptionError = byte(0x02)
)
type MuxOption struct {
ID [2]byte
Port uint16
Host string
Type string
}
// Mux is an mux-compatible client for v2ray-plugin, not a complete implementation
type Mux struct {
net.Conn
buf bytes.Buffer
id [2]byte
length [2]byte
status [2]byte
otb []byte
remain int
}
func (m *Mux) Read(b []byte) (int, error) {
if m.remain != 0 {
length := m.remain
if len(b) < m.remain {
length = len(b)
}
n, err := m.Conn.Read(b[:length])
if err != nil {
return 0, err
}
m.remain = m.remain - n
return n, nil
}
for {
_, err := io.ReadFull(m.Conn, m.length[:])
if err != nil {
return 0, err
}
length := binary.BigEndian.Uint16(m.length[:])
if length > 512 {
return 0, errors.New("invalid metalen")
}
_, err = io.ReadFull(m.Conn, m.id[:])
if err != nil {
return 0, err
}
_, err = m.Conn.Read(m.status[:])
if err != nil {
return 0, err
}
opcode := m.status[0]
if opcode == SessionStatusKeepAlive {
continue
}
opts := m.status[1]
if opts != OptionData {
continue
}
_, err = io.ReadFull(m.Conn, m.length[:])
if err != nil {
return 0, err
}
dataLen := int(binary.BigEndian.Uint16(m.length[:]))
m.remain = dataLen
if dataLen > len(b) {
dataLen = len(b)
}
n, err := m.Conn.Read(b[:dataLen])
m.remain -= n
return n, err
}
}
func (m *Mux) Write(b []byte) (int, error) {
if m.otb != nil {
// create a sub connection
if _, err := m.Conn.Write(m.otb); err != nil {
return 0, err
}
m.otb = nil
}
m.buf.Reset()
binary.Write(&m.buf, binary.BigEndian, uint16(4))
m.buf.Write(m.id[:])
m.buf.WriteByte(SessionStatusKeep)
m.buf.WriteByte(OptionData)
binary.Write(&m.buf, binary.BigEndian, uint16(len(b)))
m.buf.Write(b)
return m.Conn.Write(m.buf.Bytes())
}
func (m *Mux) Close() error {
_, err := m.Conn.Write([]byte{0x0, 0x4, m.id[0], m.id[1], SessionStatusEnd, OptionNone})
if err != nil {
return err
}
return m.Conn.Close()
}
func NewMux(conn net.Conn, option MuxOption) *Mux {
buf := &bytes.Buffer{}
// fill empty length
buf.Write([]byte{0x0, 0x0})
buf.Write(option.ID[:])
buf.WriteByte(SessionStatusNew)
buf.WriteByte(OptionNone)
// tcp
netType := byte(0x1)
if option.Type == "udp" {
netType = byte(0x2)
}
buf.WriteByte(netType)
// port
binary.Write(buf, binary.BigEndian, option.Port)
// address
ip := net.ParseIP(option.Host)
if ip == nil {
buf.WriteByte(0x2)
buf.WriteString(option.Host)
} else if ipv4 := ip.To4(); ipv4 != nil {
buf.WriteByte(0x1)
buf.Write(ipv4)
} else {
buf.WriteByte(0x3)
buf.Write(ip.To16())
}
metadata := buf.Bytes()
binary.BigEndian.PutUint16(metadata[:2], uint16(len(metadata)-2))
return &Mux{
Conn: conn,
id: option.ID,
otb: metadata,
}
}

View File

@ -0,0 +1,37 @@
package obfs
import (
"crypto/tls"
"net"
"github.com/Dreamacro/clash/component/vmess"
)
// WebsocketOption is options of websocket obfs
type WebsocketOption struct {
Host string
Path string
TLSConfig *tls.Config
}
// NewWebsocketObfs return a HTTPObfs
func NewWebsocketObfs(conn net.Conn, option *WebsocketOption) (net.Conn, error) {
config := &vmess.WebsocketConfig{
Host: option.Host,
Path: option.Path,
TLS: option.TLSConfig != nil,
TLSConfig: option.TLSConfig,
}
var err error
conn, err = vmess.NewWebsocketConn(conn, config)
if err != nil {
return nil, err
}
conn = NewMux(conn, MuxOption{
ID: [2]byte{0, 0},
Host: "127.0.0.1",
Port: 0,
})
return conn, nil
}

View File

@ -69,7 +69,7 @@ type Client struct {
security Security
tls bool
host string
wsConfig *websocketConfig
wsConfig *WebsocketConfig
tlsConfig *tls.Config
}
@ -93,7 +93,7 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) {
var err error
r := rand.Intn(len(c.user))
if c.wsConfig != nil {
conn, err = newWebsocketConn(conn, c.wsConfig)
conn, err = NewWebsocketConn(conn, c.wsConfig)
if err != nil {
return nil, err
}
@ -145,14 +145,14 @@ func NewClient(config Config) (*Client, error) {
}
}
var wsConfig *websocketConfig
var wsConfig *WebsocketConfig
if config.NetWork == "ws" {
wsConfig = &websocketConfig{
host: host,
path: config.WebSocketPath,
headers: config.WebSocketHeaders,
tls: config.TLS,
tlsConfig: tlsConfig,
wsConfig = &WebsocketConfig{
Host: host,
Path: config.WebSocketPath,
Headers: config.WebSocketHeaders,
TLS: config.TLS,
TLSConfig: tlsConfig,
}
}

View File

@ -19,12 +19,12 @@ type websocketConn struct {
remoteAddr net.Addr
}
type websocketConfig struct {
host string
path string
headers map[string]string
tls bool
tlsConfig *tls.Config
type WebsocketConfig struct {
Host string
Path string
Headers map[string]string
TLS bool
TLSConfig *tls.Config
}
// Read implements net.Conn.Read()
@ -102,7 +102,7 @@ func (wsc *websocketConn) SetWriteDeadline(t time.Time) error {
return wsc.conn.SetWriteDeadline(t)
}
func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) {
func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
dialer := &websocket.Dialer{
NetDial: func(network, addr string) (net.Conn, error) {
return conn, nil
@ -113,25 +113,25 @@ func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) {
}
scheme := "ws"
if c.tls {
if c.TLS {
scheme = "wss"
dialer.TLSClientConfig = c.tlsConfig
dialer.TLSClientConfig = c.TLSConfig
}
host, port, _ := net.SplitHostPort(c.host)
host, port, _ := net.SplitHostPort(c.Host)
if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") {
host = c.host
host = c.Host
}
uri := url.URL{
Scheme: scheme,
Host: host,
Path: c.path,
Path: c.Path,
}
headers := http.Header{}
if c.headers != nil {
for k, v := range c.headers {
if c.Headers != nil {
for k, v := range c.Headers {
headers.Set(k, v)
}
}