Feature: refactor vmess & add http network

This commit is contained in:
Dreamacro 2020-03-31 16:07:21 +08:00
parent 206767247e
commit 19f809b1c8
8 changed files with 214 additions and 134 deletions

View file

@ -210,6 +210,24 @@ proxies:
# ws-headers: # ws-headers:
# Host: v2ray.com # Host: v2ray.com
- name: "vmess-http"
type: vmess
server: server
port: 443
uuid: uuid
alterId: 32
cipher: auto
# udp: true
# network: http
# http-opts:
# # method: "GET"
# # path:
# # - '/'
# # - '/video'
# # headers:
# # Connection:
# # - keep-alive
# socks5 # socks5
- name: "socks" - name: "socks"
type: socks5 type: socks5

View file

@ -39,7 +39,12 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
} }
proxy = NewHttp(*httpOption) proxy = NewHttp(*httpOption)
case "vmess": case "vmess":
vmessOption := &VmessOption{} vmessOption := &VmessOption{
HTTPOpts: HTTPOptions{
Method: "GET",
Path: []string{"/"},
},
}
err = decoder.Decode(mapping, vmessOption) err = decoder.Decode(mapping, vmessOption)
if err != nil { if err != nil {
break break

View file

@ -2,7 +2,6 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -156,21 +155,17 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode) return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
} }
obfsMode = opts.Mode obfsMode = opts.Mode
var tlsConfig *tls.Config
if opts.TLS {
tlsConfig = &tls.Config{
ServerName: opts.Host,
InsecureSkipVerify: opts.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
}
}
v2rayOption = &v2rayObfs.Option{ v2rayOption = &v2rayObfs.Option{
Host: opts.Host, Host: opts.Host,
Path: opts.Path, Path: opts.Path,
Headers: opts.Headers, Headers: opts.Headers,
TLSConfig: tlsConfig, Mux: opts.Mux,
Mux: opts.Mux, }
if opts.TLS {
v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.SessionCache = getClientSessionCache()
} }
} }

View file

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/http"
"strconv" "strconv"
"strings" "strings"
@ -17,6 +18,7 @@ import (
type Vmess struct { type Vmess struct {
*Base *Base
client *vmess.Client client *vmess.Client
option *VmessOption
} }
type VmessOption struct { type VmessOption struct {
@ -29,13 +31,60 @@ type VmessOption struct {
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
WSPath string `proxy:"ws-path,omitempty"` WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
type HTTPOptions struct {
Method string `proxy:"method,omitempty"`
Path []string `proxy:"path,omitempty"`
Headers map[string][]string `proxy:"headers,omitempty"`
}
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return v.client.New(c, parseVmessAddr(metadata)) var err error
switch v.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{
Host: host,
Port: port,
Path: v.option.WSPath,
}
if len(v.option.WSHeaders) != 0 {
header := http.Header{}
for key, value := range v.option.WSHeaders {
header.Add(key, value)
}
wsOpts.Headers = header
}
if v.option.TLS {
wsOpts.TLS = true
wsOpts.SessionCache = getClientSessionCache()
wsOpts.SkipCertVerify = v.option.SkipCertVerify
}
c, err = vmess.StreamWebsocketConn(c, wsOpts)
case "http":
host, _, _ := net.SplitHostPort(v.addr)
httpOpts := &vmess.HTTPConfig{
Host: host,
Method: v.option.HTTPOpts.Method,
Path: v.option.HTTPOpts.Path,
Headers: v.option.HTTPOpts.Headers,
}
c, err = vmess.StreamHTTPConn(c, httpOpts), nil
}
if err != nil {
return nil, err
}
return v.client.StreamConn(c, parseVmessAddr(metadata))
} }
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
@ -66,7 +115,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, fmt.Errorf("%s connect error", v.addr) return nil, fmt.Errorf("%s connect error", v.addr)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata)) c, err = v.StreamConn(c, metadata)
if err != nil { if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err) return nil, fmt.Errorf("new vmess client error: %v", err)
} }
@ -76,17 +125,11 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
func NewVmess(option VmessOption) (*Vmess, error) { func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher) security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{ client, err := vmess.NewClient(vmess.Config{
UUID: option.UUID, UUID: option.UUID,
AlterID: uint16(option.AlterID), AlterID: uint16(option.AlterID),
Security: security, Security: security,
TLS: option.TLS, HostName: option.Server,
HostName: option.Server, Port: strconv.Itoa(option.Port),
Port: strconv.Itoa(option.Port),
NetWork: option.Network,
WebSocketPath: option.WSPath,
WebSocketHeaders: option.WSHeaders,
SkipCertVerify: option.SkipCertVerify,
SessionCache: getClientSessionCache(),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -100,6 +143,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
udp: true, udp: true,
}, },
client: client, client: client,
option: &option,
}, nil }, nil
} }

View file

@ -10,11 +10,14 @@ import (
// Option is options of websocket obfs // Option is options of websocket obfs
type Option struct { type Option struct {
Host string Host string
Path string Port string
Headers map[string]string Path string
TLSConfig *tls.Config Headers map[string]string
Mux bool TLS bool
SkipCertVerify bool
SessionCache tls.ClientSessionCache
Mux bool
} }
// NewV2rayObfs return a HTTPObfs // NewV2rayObfs return a HTTPObfs
@ -25,15 +28,17 @@ func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) {
} }
config := &vmess.WebsocketConfig{ config := &vmess.WebsocketConfig{
Host: option.Host, Host: option.Host,
Path: option.Path, Port: option.Port,
TLS: option.TLSConfig != nil, Path: option.Path,
Headers: header, TLS: option.TLS,
TLSConfig: option.TLSConfig, Headers: header,
SkipCertVerify: option.SkipCertVerify,
SessionCache: option.SessionCache,
} }
var err error var err error
conn, err = vmess.NewWebsocketConn(conn, config) conn, err = vmess.StreamWebsocketConn(conn, config)
if err != nil { if err != nil {
return nil, err return nil, err
} }

76
component/vmess/http.go Normal file
View file

@ -0,0 +1,76 @@
package vmess
import (
"bytes"
"fmt"
"math/rand"
"net"
"net/http"
"net/textproto"
)
type httpConn struct {
net.Conn
cfg *HTTPConfig
rhandshake bool
whandshake bool
}
type HTTPConfig struct {
Method string
Host string
Path []string
Headers map[string][]string
}
// Read implements net.Conn.Read()
func (hc *httpConn) Read(b []byte) (int, error) {
if hc.rhandshake {
n, err := hc.Conn.Read(b)
return n, err
}
reader := textproto.NewConn(hc.Conn)
// First line: GET /index.html HTTP/1.0
if _, err := reader.ReadLine(); err != nil {
return 0, err
}
if _, err := reader.ReadMIMEHeader(); err != nil {
return 0, err
}
hc.rhandshake = true
return hc.Conn.Read(b)
}
// Write implements io.Writer.
func (hc *httpConn) Write(b []byte) (int, error) {
if hc.whandshake {
return hc.Conn.Write(b)
}
path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))]
u := fmt.Sprintf("http://%s%s", hc.cfg.Host, path)
req, _ := http.NewRequest("GET", u, bytes.NewBuffer(b))
for key, list := range hc.cfg.Headers {
req.Header.Set(key, list[rand.Intn(len(list))])
}
req.ContentLength = int64(len(b))
if err := req.Write(hc.Conn); err != nil {
return 0, err
}
hc.whandshake = true
return len(b), nil
}
func (hc *httpConn) Close() error {
return hc.Conn.Close()
}
func StreamHTTPConn(conn net.Conn, cfg *HTTPConfig) net.Conn {
return &httpConn{
Conn: conn,
cfg: cfg,
}
}

View file

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"net" "net"
"net/http"
"runtime" "runtime"
"sync" "sync"
@ -66,42 +65,23 @@ type DstAddr struct {
// Client is vmess connection generator // Client is vmess connection generator
type Client struct { type Client struct {
user []*ID user []*ID
uuid *uuid.UUID uuid *uuid.UUID
security Security security Security
tls bool
host string
wsConfig *WebsocketConfig
tlsConfig *tls.Config
} }
// Config of vmess // Config of vmess
type Config struct { type Config struct {
UUID string UUID string
AlterID uint16 AlterID uint16
Security string Security string
TLS bool Port string
HostName string HostName string
Port string
NetWork string
WebSocketPath string
WebSocketHeaders map[string]string
SkipCertVerify bool
SessionCache tls.ClientSessionCache
} }
// New return a Conn with net.Conn and DstAddr // StreamConn return a Conn with net.Conn and DstAddr
func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { func (c *Client) StreamConn(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.wsConfig != nil {
conn, err = NewWebsocketConn(conn, c.wsConfig)
if err != nil {
return nil, err
}
} else if c.tls {
conn = tls.Client(conn, c.tlsConfig)
}
return newConn(conn, c.user[r], dst, c.security) return newConn(conn, c.user[r], dst, c.security)
} }
@ -129,57 +109,9 @@ func NewClient(config Config) (*Client, error) {
return nil, fmt.Errorf("Unknown security type: %s", config.Security) return nil, fmt.Errorf("Unknown security type: %s", config.Security)
} }
if config.NetWork != "" && config.NetWork != "ws" {
return nil, fmt.Errorf("Unknown network type: %s", config.NetWork)
}
header := http.Header{}
for k, v := range config.WebSocketHeaders {
header.Add(k, v)
}
host := net.JoinHostPort(config.HostName, config.Port)
var tlsConfig *tls.Config
if config.TLS {
tlsConfig = &tls.Config{
ServerName: config.HostName,
InsecureSkipVerify: config.SkipCertVerify,
ClientSessionCache: config.SessionCache,
}
if tlsConfig.ClientSessionCache == nil {
tlsConfig.ClientSessionCache = getClientSessionCache()
}
if host := header.Get("Host"); host != "" {
tlsConfig.ServerName = host
}
}
var wsConfig *WebsocketConfig
if config.NetWork == "ws" {
wsConfig = &WebsocketConfig{
Host: host,
Path: config.WebSocketPath,
Headers: header,
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,
host: host,
wsConfig: wsConfig,
tlsConfig: tlsConfig,
}, nil }, nil
} }
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
clientSessionCache = tls.NewLRUClientSessionCache(128)
})
return clientSessionCache
}

View file

@ -25,11 +25,13 @@ type websocketConn struct {
} }
type WebsocketConfig struct { type WebsocketConfig struct {
Host string Host string
Path string Port string
Headers http.Header Path string
TLS bool Headers http.Header
TLSConfig *tls.Config TLS bool
SkipCertVerify bool
SessionCache tls.ClientSessionCache
} }
// Read implements net.Conn.Read() // Read implements net.Conn.Read()
@ -111,7 +113,7 @@ func (wsc *websocketConn) SetWriteDeadline(t time.Time) error {
return wsc.conn.SetWriteDeadline(t) return wsc.conn.SetWriteDeadline(t)
} }
func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
dialer := &websocket.Dialer{ dialer := &websocket.Dialer{
NetDial: func(network, addr string) (net.Conn, error) { NetDial: func(network, addr string) (net.Conn, error) {
return conn, nil return conn, nil
@ -124,17 +126,20 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
scheme := "ws" scheme := "ws"
if c.TLS { if c.TLS {
scheme = "wss" scheme = "wss"
dialer.TLSClientConfig = c.TLSConfig dialer.TLSClientConfig = &tls.Config{
} ServerName: c.Host,
InsecureSkipVerify: c.SkipCertVerify,
ClientSessionCache: c.SessionCache,
}
host, port, _ := net.SplitHostPort(c.Host) if host := c.Headers.Get("Host"); host != "" {
if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { dialer.TLSClientConfig.ServerName = host
host = c.Host }
} }
uri := url.URL{ uri := url.URL{
Scheme: scheme, Scheme: scheme,
Host: host, Host: net.JoinHostPort(c.Host, c.Port),
Path: c.Path, Path: c.Path,
} }
@ -151,7 +156,7 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
if resp != nil { if resp != nil {
reason = resp.Status reason = resp.Status
} }
return nil, fmt.Errorf("Dial %s error: %s", host, reason) return nil, fmt.Errorf("Dial %s error: %s", uri.Host, reason)
} }
return &websocketConn{ return &websocketConn{