Feature: refactor vmess & add http network
This commit is contained in:
parent
206767247e
commit
19f809b1c8
8 changed files with 214 additions and 134 deletions
18
README.md
18
README.md
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -2,7 +2,6 @@ package outbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -156,22 +155,18 @@ 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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ShadowSocks{
|
return &ShadowSocks{
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -79,14 +128,8 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,12 @@ import (
|
||||||
// Option is options of websocket obfs
|
// Option is options of websocket obfs
|
||||||
type Option struct {
|
type Option struct {
|
||||||
Host string
|
Host string
|
||||||
|
Port string
|
||||||
Path string
|
Path string
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
TLSConfig *tls.Config
|
TLS bool
|
||||||
|
SkipCertVerify bool
|
||||||
|
SessionCache tls.ClientSessionCache
|
||||||
Mux bool
|
Mux bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,14 +29,16 @@ func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) {
|
||||||
|
|
||||||
config := &vmess.WebsocketConfig{
|
config := &vmess.WebsocketConfig{
|
||||||
Host: option.Host,
|
Host: option.Host,
|
||||||
|
Port: option.Port,
|
||||||
Path: option.Path,
|
Path: option.Path,
|
||||||
TLS: option.TLSConfig != nil,
|
TLS: option.TLS,
|
||||||
Headers: header,
|
Headers: header,
|
||||||
TLSConfig: option.TLSConfig,
|
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
76
component/vmess/http.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -69,10 +68,6 @@ 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
|
||||||
|
@ -80,28 +75,13 @@ type Config struct {
|
||||||
UUID string
|
UUID string
|
||||||
AlterID uint16
|
AlterID uint16
|
||||||
Security string
|
Security string
|
||||||
TLS bool
|
|
||||||
HostName string
|
|
||||||
Port string
|
Port string
|
||||||
NetWork string
|
HostName 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
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,10 +26,12 @@ type websocketConn struct {
|
||||||
|
|
||||||
type WebsocketConfig struct {
|
type WebsocketConfig struct {
|
||||||
Host string
|
Host string
|
||||||
|
Port string
|
||||||
Path string
|
Path string
|
||||||
Headers http.Header
|
Headers http.Header
|
||||||
TLS bool
|
TLS bool
|
||||||
TLSConfig *tls.Config
|
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{
|
||||||
|
|
Loading…
Reference in a new issue