Feature: add v2ray-plugin
This commit is contained in:
parent
53b5ef199f
commit
2383cca2ce
6 changed files with 332 additions and 44 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
173
component/v2ray-plugin/mux.go
Normal file
173
component/v2ray-plugin/mux.go
Normal 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,
|
||||
}
|
||||
}
|
37
component/v2ray-plugin/websocket.go
Normal file
37
component/v2ray-plugin/websocket.go
Normal 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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue