diff --git a/README.md b/README.md index a7d6a766..f1d8165e 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,24 @@ proxies: # ws-path: /path # ws-headers: # 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 - name: "socks" diff --git a/adapters/outbound/parser.go b/adapters/outbound/parser.go index 90b77760..86ee68b5 100644 --- a/adapters/outbound/parser.go +++ b/adapters/outbound/parser.go @@ -39,7 +39,12 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { } proxy = NewHttp(*httpOption) case "vmess": - vmessOption := &VmessOption{} + vmessOption := &VmessOption{ + HTTPOpts: HTTPOptions{ + Method: "GET", + Path: []string{"/"}, + }, + } err = decoder.Decode(mapping, vmessOption) if err != nil { break diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index fca0e606..fdf5ca0e 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -2,7 +2,6 @@ package outbound import ( "context" - "crypto/tls" "encoding/json" "errors" "fmt" @@ -156,21 +155,17 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, 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{ - Host: opts.Host, - Path: opts.Path, - Headers: opts.Headers, - TLSConfig: tlsConfig, - Mux: opts.Mux, + Host: opts.Host, + Path: opts.Path, + Headers: opts.Headers, + Mux: opts.Mux, + } + + if opts.TLS { + v2rayOption.TLS = true + v2rayOption.SkipCertVerify = opts.SkipCertVerify + v2rayOption.SessionCache = getClientSessionCache() } } diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 4420514f..5f543dff 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "net/http" "strconv" "strings" @@ -17,6 +18,7 @@ import ( type Vmess struct { *Base client *vmess.Client + option *VmessOption } type VmessOption struct { @@ -29,13 +31,60 @@ type VmessOption struct { TLS bool `proxy:"tls,omitempty"` UDP bool `proxy:"udp,omitempty"` Network string `proxy:"network,omitempty"` + HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` WSPath string `proxy:"ws-path,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,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) { - 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) { @@ -66,7 +115,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { return nil, fmt.Errorf("%s connect error", v.addr) } tcpKeepAlive(c) - c, err = v.client.New(c, parseVmessAddr(metadata)) + c, err = v.StreamConn(c, metadata) if err != nil { 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) { security := strings.ToLower(option.Cipher) client, err := vmess.NewClient(vmess.Config{ - UUID: option.UUID, - AlterID: uint16(option.AlterID), - Security: security, - TLS: option.TLS, - HostName: option.Server, - Port: strconv.Itoa(option.Port), - NetWork: option.Network, - WebSocketPath: option.WSPath, - WebSocketHeaders: option.WSHeaders, - SkipCertVerify: option.SkipCertVerify, - SessionCache: getClientSessionCache(), + UUID: option.UUID, + AlterID: uint16(option.AlterID), + Security: security, + HostName: option.Server, + Port: strconv.Itoa(option.Port), }) if err != nil { return nil, err @@ -100,6 +143,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { udp: true, }, client: client, + option: &option, }, nil } diff --git a/component/v2ray-plugin/websocket.go b/component/v2ray-plugin/websocket.go index df6d05da..fbd1f3e3 100644 --- a/component/v2ray-plugin/websocket.go +++ b/component/v2ray-plugin/websocket.go @@ -10,11 +10,14 @@ import ( // Option is options of websocket obfs type Option struct { - Host string - Path string - Headers map[string]string - TLSConfig *tls.Config - Mux bool + Host string + Port string + Path string + Headers map[string]string + TLS bool + SkipCertVerify bool + SessionCache tls.ClientSessionCache + Mux bool } // NewV2rayObfs return a HTTPObfs @@ -25,15 +28,17 @@ func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { } config := &vmess.WebsocketConfig{ - Host: option.Host, - Path: option.Path, - TLS: option.TLSConfig != nil, - Headers: header, - TLSConfig: option.TLSConfig, + Host: option.Host, + Port: option.Port, + Path: option.Path, + TLS: option.TLS, + Headers: header, + SkipCertVerify: option.SkipCertVerify, + SessionCache: option.SessionCache, } var err error - conn, err = vmess.NewWebsocketConn(conn, config) + conn, err = vmess.StreamWebsocketConn(conn, config) if err != nil { return nil, err } diff --git a/component/vmess/http.go b/component/vmess/http.go new file mode 100644 index 00000000..92fe3389 --- /dev/null +++ b/component/vmess/http.go @@ -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, + } +} diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 49890354..9b7ef906 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -5,7 +5,6 @@ import ( "fmt" "math/rand" "net" - "net/http" "runtime" "sync" @@ -66,42 +65,23 @@ type DstAddr struct { // Client is vmess connection generator type Client struct { - user []*ID - uuid *uuid.UUID - security Security - tls bool - host string - wsConfig *WebsocketConfig - tlsConfig *tls.Config + user []*ID + uuid *uuid.UUID + security Security } // Config of vmess type Config struct { - UUID string - AlterID uint16 - Security string - TLS bool - HostName string - Port string - NetWork string - WebSocketPath string - WebSocketHeaders map[string]string - SkipCertVerify bool - SessionCache tls.ClientSessionCache + UUID string + AlterID uint16 + Security string + Port string + HostName string } -// New return a Conn with net.Conn and DstAddr -func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { - var err error +// StreamConn return a Conn with net.Conn and DstAddr +func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) { 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) } @@ -129,57 +109,9 @@ func NewClient(config Config) (*Client, error) { 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{ - user: newAlterIDs(newID(&uid), config.AlterID), - uuid: &uid, - security: security, - tls: config.TLS, - host: host, - wsConfig: wsConfig, - tlsConfig: tlsConfig, + user: newAlterIDs(newID(&uid), config.AlterID), + uuid: &uid, + security: security, }, nil } - -func getClientSessionCache() tls.ClientSessionCache { - once.Do(func() { - clientSessionCache = tls.NewLRUClientSessionCache(128) - }) - return clientSessionCache -} diff --git a/component/vmess/websocket.go b/component/vmess/websocket.go index 30d2c3ae..625905b8 100644 --- a/component/vmess/websocket.go +++ b/component/vmess/websocket.go @@ -25,11 +25,13 @@ type websocketConn struct { } type WebsocketConfig struct { - Host string - Path string - Headers http.Header - TLS bool - TLSConfig *tls.Config + Host string + Port string + Path string + Headers http.Header + TLS bool + SkipCertVerify bool + SessionCache tls.ClientSessionCache } // Read implements net.Conn.Read() @@ -111,7 +113,7 @@ func (wsc *websocketConn) SetWriteDeadline(t time.Time) error { 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{ NetDial: func(network, addr string) (net.Conn, error) { return conn, nil @@ -124,17 +126,20 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { scheme := "ws" if c.TLS { 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 (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { - host = c.Host + if host := c.Headers.Get("Host"); host != "" { + dialer.TLSClientConfig.ServerName = host + } } uri := url.URL{ Scheme: scheme, - Host: host, + Host: net.JoinHostPort(c.Host, c.Port), Path: c.Path, } @@ -151,7 +156,7 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { if resp != nil { 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{