Feature: add websocket headers support in vmess

This commit is contained in:
Dreamacro 2018-12-11 00:25:05 +08:00
parent 34c8655974
commit 1607d3253f
5 changed files with 118 additions and 34 deletions

View File

@ -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 }

View File

@ -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

View File

@ -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
}

View File

@ -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,
} }

View File

@ -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 {