Compare commits

..

5 Commits

Author SHA1 Message Date
502aa61c0e Fix: vmess small probability invalid auth 2018-11-06 17:34:19 +08:00
cc6d496143 Chore: optimize code structure in vmess websocket (#28)
* Chore: move conn process of ws to websocket.go

* Chore: some routine adjustment
2018-11-04 21:36:20 +08:00
10e0231bc1 Fix: dial IPv6 host (#29) 2018-11-04 21:12:16 +08:00
fd63707399 Optimization: use client session cache for TLS connection (#26) 2018-11-01 11:54:45 +08:00
c5757a9b11 Chore: delete redundant print 2018-10-30 10:50:57 +08:00
7 changed files with 142 additions and 76 deletions

View File

@ -72,7 +72,7 @@ func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter,
} }
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
server := fmt.Sprintf("%s:%d", option.Server, option.Port) server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher cipher := option.Cipher
password := option.Password password := option.Password
ciph, err := core.PickCipher(cipher, nil, password) ciph, err := core.PickCipher(cipher, nil, password)

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strconv"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -32,6 +33,7 @@ type Socks5 struct {
name string name string
tls bool tls bool
skipCertVerify bool skipCertVerify bool
tlsConfig *tls.Config
} }
type Socks5Option struct { type Socks5Option struct {
@ -54,11 +56,9 @@ func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e
c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout)
if err == nil && ss.tls { if err == nil && ss.tls {
tlsConfig := tls.Config{ cc := tls.Client(c, ss.tlsConfig)
InsecureSkipVerify: ss.skipCertVerify, err = cc.Handshake()
MaxVersion: tls.VersionTLS12, c = cc
}
c = tls.Client(c, &tlsConfig)
} }
if err != nil { if err != nil {
@ -103,10 +103,22 @@ func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
} }
func NewSocks5(option Socks5Option) *Socks5 { func NewSocks5(option Socks5Option) *Socks5 {
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
MinVersion: tls.VersionTLS11,
MaxVersion: tls.VersionTLS12,
ServerName: option.Server,
}
}
return &Socks5{ return &Socks5{
addr: fmt.Sprintf("%s:%d", option.Server, option.Port), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
name: option.Name, name: option.Name,
tls: option.TLS, tls: option.TLS,
skipCertVerify: option.SkipCertVerify, skipCertVerify: option.SkipCertVerify,
tlsConfig: tlsConfig,
} }
} }

View File

@ -1,10 +1,12 @@
package adapters package adapters
import ( import (
"crypto/tls"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"sync"
"time" "time"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -14,6 +16,11 @@ const (
tcpTimeout = 5 * time.Second tcpTimeout = 5 * time.Second
) )
var (
globalClientSessionCache tls.ClientSessionCache
once sync.Once
)
// DelayTest get the delay for the specified URL // DelayTest get the delay for the specified URL
func DelayTest(proxy C.Proxy, url string) (t int16, err error) { func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
addr, err := urlToMetadata(url) addr, err := urlToMetadata(url)
@ -95,3 +102,10 @@ func tcpKeepAlive(c net.Conn) {
tcp.SetKeepAlivePeriod(30 * time.Second) tcp.SetKeepAlivePeriod(30 * time.Second)
} }
} }
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
})
return globalClientSessionCache
}

View File

@ -68,10 +68,11 @@ func NewVmess(option VmessOption) (*Vmess, error) {
AlterID: uint16(option.AlterID), AlterID: uint16(option.AlterID),
Security: security, Security: security,
TLS: option.TLS, TLS: option.TLS,
Host: fmt.Sprintf("%s:%d", option.Server, option.Port), Host: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
NetWork: option.Network, NetWork: option.Network,
WebSocketPath: option.WSPath, WebSocketPath: option.WSPath,
SkipCertVerify: option.SkipCertVerify, SkipCertVerify: option.SkipCertVerify,
SessionCacahe: getClientSessionCache(),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -79,7 +80,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
return &Vmess{ return &Vmess{
name: option.Name, name: option.Name,
server: fmt.Sprintf("%s:%d", option.Server, option.Port), server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
client: client, client: client,
}, nil }, nil
} }

View File

@ -65,11 +65,10 @@ func (vc *Conn) Read(b []byte) (int, error) {
} }
func (vc *Conn) sendRequest() error { func (vc *Conn) sendRequest() error {
timestamp := make([]byte, 8) timestamp := time.Now()
binary.BigEndian.PutUint64(timestamp, uint64(time.Now().UTC().Unix()))
h := hmac.New(md5.New, vc.id.UUID.Bytes()) h := hmac.New(md5.New, vc.id.UUID.Bytes())
h.Write(timestamp) binary.Write(h, binary.BigEndian, uint64(timestamp.Unix()))
_, err := vc.Conn.Write(h.Sum(nil)) _, err := vc.Conn.Write(h.Sum(nil))
if err != nil { if err != nil {
return err return err
@ -111,7 +110,7 @@ func (vc *Conn) sendRequest() error {
return err return err
} }
stream := cipher.NewCFBEncrypter(block, hashTimestamp(time.Now().UTC())) stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp))
stream.XORKeyStream(buf.Bytes(), buf.Bytes()) stream.XORKeyStream(buf.Bytes(), buf.Bytes())
_, err = vc.Conn.Write(buf.Bytes()) _, err = vc.Conn.Write(buf.Bytes())
return err return err
@ -145,7 +144,7 @@ func (vc *Conn) recvResponse() error {
func hashTimestamp(t time.Time) []byte { func hashTimestamp(t time.Time) []byte {
md5hash := md5.New() md5hash := md5.New()
ts := make([]byte, 8) ts := make([]byte, 8)
binary.BigEndian.PutUint64(ts, uint64(t.UTC().Unix())) binary.BigEndian.PutUint64(ts, uint64(t.Unix()))
md5hash.Write(ts) md5hash.Write(ts)
md5hash.Write(ts) md5hash.Write(ts)
md5hash.Write(ts) md5hash.Write(ts)

View File

@ -5,12 +5,10 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"net" "net"
"net/url"
"runtime" "runtime"
"time" "sync"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"github.com/gorilla/websocket"
) )
// Version of vmess // Version of vmess
@ -39,6 +37,11 @@ var CipherMapping = map[string]byte{
"chacha20-poly1305": SecurityCHACHA20POLY1305, "chacha20-poly1305": SecurityCHACHA20POLY1305,
} }
var (
clientSessionCache tls.ClientSessionCache
once sync.Once
)
// Command types // Command types
const ( const (
CommandTCP byte = 1 CommandTCP byte = 1
@ -66,9 +69,8 @@ type Client struct {
security Security security Security
tls bool tls bool
host string host string
websocket bool wsConfig *websocketConfig
websocketPath string tlsConfig *tls.Config
skipCertVerify bool
} }
// Config of vmess // Config of vmess
@ -81,54 +83,20 @@ type Config struct {
NetWork string NetWork string
WebSocketPath string WebSocketPath string
SkipCertVerify bool SkipCertVerify bool
SessionCacahe tls.ClientSessionCache
} }
// New return a Conn with net.Conn and DstAddr // New return a Conn with net.Conn and DstAddr
func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) {
var err error
r := rand.Intn(len(c.user)) r := rand.Intn(len(c.user))
if c.websocket { if c.wsConfig != nil {
dialer := &websocket.Dialer{ conn, err = newWebsocketConn(conn, c.wsConfig)
NetDial: func(network, addr string) (net.Conn, error) {
return conn, nil
},
ReadBufferSize: 4 * 1024,
WriteBufferSize: 4 * 1024,
HandshakeTimeout: time.Second * 8,
}
scheme := "ws"
if c.tls {
scheme = "wss"
dialer.TLSClientConfig = &tls.Config{
InsecureSkipVerify: c.skipCertVerify,
}
}
host, port, err := net.SplitHostPort(c.host)
if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") {
host = c.host
}
uri := url.URL{
Scheme: scheme,
Host: host,
Path: c.websocketPath,
}
wsConn, resp, err := dialer.Dial(uri.String(), nil)
if err != nil { if err != nil {
var reason string return nil, err
if resp != nil {
reason = resp.Status
} }
println(uri.String(), err.Error())
return nil, fmt.Errorf("Dial %s error: %s", host, reason)
}
conn = newWebsocketConn(wsConn, conn.RemoteAddr())
} else if c.tls { } else if c.tls {
conn = tls.Client(conn, &tls.Config{ conn = tls.Client(conn, c.tlsConfig)
InsecureSkipVerify: c.skipCertVerify,
})
} }
return newConn(conn, c.user[r], dst, c.security), nil return newConn(conn, c.user[r], dst, c.security), nil
} }
@ -161,13 +129,41 @@ func NewClient(config Config) (*Client, error) {
return nil, fmt.Errorf("Unknown network type: %s", config.NetWork) return nil, fmt.Errorf("Unknown network type: %s", config.NetWork)
} }
var tlsConfig *tls.Config
if config.TLS {
tlsConfig = &tls.Config{
InsecureSkipVerify: config.SkipCertVerify,
ClientSessionCache: config.SessionCacahe,
}
if tlsConfig.ClientSessionCache == nil {
tlsConfig.ClientSessionCache = getClientSessionCache()
}
}
var wsConfig *websocketConfig
if config.NetWork == "ws" {
wsConfig = &websocketConfig{
host: config.Host,
path: config.WebSocketPath,
tls: config.TLS,
tlsConfig: tlsConfig,
}
}
return &Client{ return &Client{
user: newAlterIDs(newID(&uid), config.AlterID), user: newAlterIDs(newID(&uid), config.AlterID),
uuid: &uid, uuid: &uid,
security: security, security: security,
tls: config.TLS, tls: config.TLS,
host: config.Host, host: config.Host,
websocket: config.NetWork == "ws", wsConfig: wsConfig,
websocketPath: config.WebSocketPath, tlsConfig: tlsConfig,
}, nil }, nil
} }
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
clientSessionCache = tls.NewLRUClientSessionCache(128)
})
return clientSessionCache
}

View File

@ -1,9 +1,11 @@
package vmess package vmess
import ( import (
"crypto/tls"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/url"
"strings" "strings"
"time" "time"
@ -16,6 +18,13 @@ type websocketConn struct {
remoteAddr net.Addr remoteAddr net.Addr
} }
type websocketConfig struct {
host string
path string
tls bool
tlsConfig *tls.Config
}
// Read implements net.Conn.Read() // Read implements net.Conn.Read()
func (wsc *websocketConn) Read(b []byte) (int, error) { func (wsc *websocketConn) Read(b []byte) (int, error) {
for { for {
@ -91,9 +100,44 @@ func (wsc *websocketConn) SetWriteDeadline(t time.Time) error {
return wsc.conn.SetWriteDeadline(t) return wsc.conn.SetWriteDeadline(t)
} }
func newWebsocketConn(conn *websocket.Conn, remoteAddr net.Addr) net.Conn { func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) {
return &websocketConn{ dialer := &websocket.Dialer{
conn: conn, NetDial: func(network, addr string) (net.Conn, error) {
remoteAddr: remoteAddr, return conn, nil
},
ReadBufferSize: 4 * 1024,
WriteBufferSize: 4 * 1024,
HandshakeTimeout: time.Second * 8,
} }
scheme := "ws"
if c.tls {
scheme = "wss"
dialer.TLSClientConfig = c.tlsConfig
}
host, port, err := net.SplitHostPort(c.host)
if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") {
host = c.host
}
uri := url.URL{
Scheme: scheme,
Host: host,
Path: c.path,
}
wsConn, resp, err := dialer.Dial(uri.String(), nil)
if err != nil {
var reason string
if resp != nil {
reason = resp.Status
}
return nil, fmt.Errorf("Dial %s error: %s", host, reason)
}
return &websocketConn{
conn: wsConn,
remoteAddr: conn.RemoteAddr(),
}, nil
} }