Feature: add websocket headers support in vmess
This commit is contained in:
parent
34c8655974
commit
1607d3253f
@ -5,7 +5,7 @@
|
|||||||
<br>
|
<br>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<h4 align="center">A rule based tunnel in Go.</h4>
|
<h4 align="center">A rule-based tunnel in Go.</h4>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://travis-ci.org/Dreamacro/clash">
|
<a href="https://travis-ci.org/Dreamacro/clash">
|
||||||
@ -126,8 +126,8 @@ Proxy:
|
|||||||
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true }
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true }
|
||||||
# with tls and skip-cert-verify
|
# with tls and skip-cert-verify
|
||||||
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true }
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true }
|
||||||
# with ws
|
# with ws-path and ws-headers
|
||||||
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path }
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, ws-headers: { Host: v2ray.com } }
|
||||||
# with ws + tls
|
# with ws + tls
|
||||||
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true }
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true }
|
||||||
|
|
||||||
|
@ -32,16 +32,17 @@ type Vmess struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
UUID string `proxy:"uuid"`
|
UUID string `proxy:"uuid"`
|
||||||
AlterID int `proxy:"alterId"`
|
AlterID int `proxy:"alterId"`
|
||||||
Cipher string `proxy:"cipher"`
|
Cipher string `proxy:"cipher"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
WSPath string `proxy:"ws-path,omitempty"`
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vmess) Name() string {
|
func (v *Vmess) Name() string {
|
||||||
@ -71,16 +72,17 @@ func (v *Vmess) MarshalJSON() ([]byte, 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,
|
TLS: option.TLS,
|
||||||
HostName: option.Server,
|
HostName: option.Server,
|
||||||
Port: strconv.Itoa(option.Port),
|
Port: strconv.Itoa(option.Port),
|
||||||
NetWork: option.Network,
|
NetWork: option.Network,
|
||||||
WebSocketPath: option.WSPath,
|
WebSocketPath: option.WSPath,
|
||||||
SkipCertVerify: option.SkipCertVerify,
|
WebSocketHeaders: option.WSHeaders,
|
||||||
SessionCacahe: getClientSessionCache(),
|
SkipCertVerify: option.SkipCertVerify,
|
||||||
|
SessionCacahe: getClientSessionCache(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package structure
|
package structure
|
||||||
|
|
||||||
|
// references: https://github.com/mitchellh/mapstructure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -70,6 +72,8 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error
|
|||||||
return d.decodeBool(name, data, val)
|
return d.decodeBool(name, data, val)
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
return d.decodeSlice(name, data, val)
|
return d.decodeSlice(name, data, val)
|
||||||
|
case reflect.Map:
|
||||||
|
return d.decodeMap(name, data, val)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("type %s not support", val.Kind().String())
|
return fmt.Errorf("type %s not support", val.Kind().String())
|
||||||
}
|
}
|
||||||
@ -158,3 +162,70 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
|
|||||||
val.Set(valSlice)
|
val.Set(valSlice)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
|
||||||
|
valType := val.Type()
|
||||||
|
valKeyType := valType.Key()
|
||||||
|
valElemType := valType.Elem()
|
||||||
|
|
||||||
|
valMap := val
|
||||||
|
|
||||||
|
if valMap.IsNil() {
|
||||||
|
mapType := reflect.MapOf(valKeyType, valElemType)
|
||||||
|
valMap = reflect.MakeMap(mapType)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataVal := reflect.Indirect(reflect.ValueOf(data))
|
||||||
|
if dataVal.Kind() != reflect.Map {
|
||||||
|
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.decodeMapFromMap(name, dataVal, val, valMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
|
||||||
|
valType := val.Type()
|
||||||
|
valKeyType := valType.Key()
|
||||||
|
valElemType := valType.Elem()
|
||||||
|
|
||||||
|
errors := make([]string, 0)
|
||||||
|
|
||||||
|
if dataVal.Len() == 0 {
|
||||||
|
if dataVal.IsNil() {
|
||||||
|
if !val.IsNil() {
|
||||||
|
val.Set(dataVal)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val.Set(valMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range dataVal.MapKeys() {
|
||||||
|
fieldName := fmt.Sprintf("%s[%s]", name, k)
|
||||||
|
|
||||||
|
currentKey := reflect.Indirect(reflect.New(valKeyType))
|
||||||
|
if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
|
||||||
|
errors = append(errors, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v := dataVal.MapIndex(k).Interface()
|
||||||
|
currentVal := reflect.Indirect(reflect.New(valElemType))
|
||||||
|
if err := d.decode(fieldName, v, currentVal); err != nil {
|
||||||
|
errors = append(errors, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
valMap.SetMapIndex(currentKey, currentVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
val.Set(valMap)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return fmt.Errorf(strings.Join(errors, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -75,16 +75,17 @@ type Client struct {
|
|||||||
|
|
||||||
// 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
|
TLS bool
|
||||||
HostName string
|
HostName string
|
||||||
Port string
|
Port string
|
||||||
NetWork string
|
NetWork string
|
||||||
WebSocketPath string
|
WebSocketPath string
|
||||||
SkipCertVerify bool
|
WebSocketHeaders map[string]string
|
||||||
SessionCacahe tls.ClientSessionCache
|
SkipCertVerify bool
|
||||||
|
SessionCacahe tls.ClientSessionCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// New return a Conn with net.Conn and DstAddr
|
// New return a Conn with net.Conn and DstAddr
|
||||||
@ -149,6 +150,7 @@ func NewClient(config Config) (*Client, error) {
|
|||||||
wsConfig = &websocketConfig{
|
wsConfig = &websocketConfig{
|
||||||
host: host,
|
host: host,
|
||||||
path: config.WebSocketPath,
|
path: config.WebSocketPath,
|
||||||
|
headers: config.WebSocketHeaders,
|
||||||
tls: config.TLS,
|
tls: config.TLS,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -21,6 +22,7 @@ type websocketConn struct {
|
|||||||
type websocketConfig struct {
|
type websocketConfig struct {
|
||||||
host string
|
host string
|
||||||
path string
|
path string
|
||||||
|
headers map[string]string
|
||||||
tls bool
|
tls bool
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
}
|
}
|
||||||
@ -127,7 +129,14 @@ func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) {
|
|||||||
Path: c.path,
|
Path: c.path,
|
||||||
}
|
}
|
||||||
|
|
||||||
wsConn, resp, err := dialer.Dial(uri.String(), nil)
|
headers := http.Header{}
|
||||||
|
if c.headers != nil {
|
||||||
|
for k, v := range c.headers {
|
||||||
|
headers.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wsConn, resp, err := dialer.Dial(uri.String(), headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var reason string
|
var reason string
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
|
Reference in New Issue
Block a user