From f398ebe6e206703e145f93a1e6efe92cc2d9964a Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Thu, 2 Nov 2023 11:11:19 +0800 Subject: [PATCH] feat: add `v2ray-http-upgrade` support --- adapter/outbound/shadowsocks.go | 26 ++++++++++--------- adapter/outbound/trojan.go | 7 ++--- adapter/outbound/vless.go | 1 + adapter/outbound/vmess.go | 2 ++ docs/config.yaml | 40 ++++++++++++++++------------- transport/trojan/trojan.go | 10 +++++--- transport/v2ray-plugin/websocket.go | 26 ++++++++++--------- transport/vmess/websocket.go | 34 ++++++++++++++++++++++++ 8 files changed, 97 insertions(+), 49 deletions(-) diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 40868d04..1ae16c43 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -58,14 +58,15 @@ type simpleObfsOption struct { } type v2rayObfsOption struct { - Mode string `obfs:"mode"` - Host string `obfs:"host,omitempty"` - Path string `obfs:"path,omitempty"` - TLS bool `obfs:"tls,omitempty"` - Fingerprint string `obfs:"fingerprint,omitempty"` - Headers map[string]string `obfs:"headers,omitempty"` - SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` - Mux bool `obfs:"mux,omitempty"` + Mode string `obfs:"mode"` + Host string `obfs:"host,omitempty"` + Path string `obfs:"path,omitempty"` + TLS bool `obfs:"tls,omitempty"` + Fingerprint string `obfs:"fingerprint,omitempty"` + Headers map[string]string `obfs:"headers,omitempty"` + SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` + Mux bool `obfs:"mux,omitempty"` + V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,omitempty"` } type shadowTLSOption struct { @@ -259,10 +260,11 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { } obfsMode = opts.Mode v2rayOption = &v2rayObfs.Option{ - Host: opts.Host, - Path: opts.Path, - Headers: opts.Headers, - Mux: opts.Mux, + Host: opts.Host, + Path: opts.Path, + Headers: opts.Headers, + Mux: opts.Mux, + V2rayHttpUpgrade: opts.V2rayHttpUpgrade, } if opts.TLS { diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index 337f2a38..d3c14e8a 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -53,9 +53,10 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) if t.option.Network == "ws" { host, port, _ := net.SplitHostPort(t.addr) wsOpts := &trojan.WebsocketOption{ - Host: host, - Port: port, - Path: t.option.WSOpts.Path, + Host: host, + Port: port, + Path: t.option.WSOpts.Path, + V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade, } if t.option.SNI != "" { diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 037f3367..5f54153b 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -93,6 +93,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M Path: v.option.WSOpts.Path, MaxEarlyData: v.option.WSOpts.MaxEarlyData, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, + V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade, ClientFingerprint: v.option.ClientFingerprint, Headers: http.Header{}, } diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 326f0c6f..aed61aa3 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -91,6 +91,7 @@ type WSOptions struct { Headers map[string]string `proxy:"headers,omitempty"` MaxEarlyData int `proxy:"max-early-data,omitempty"` EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"` + V2rayHttpUpgrade bool `proxy:"v2ray-http-upgrade,omitempty"` } // StreamConnContext implements C.ProxyAdapter @@ -110,6 +111,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M Path: v.option.WSOpts.Path, MaxEarlyData: v.option.WSOpts.MaxEarlyData, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, + V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade, ClientFingerprint: v.option.ClientFingerprint, Headers: http.Header{}, } diff --git a/docs/config.yaml b/docs/config.yaml index 80fc2995..61a2dee9 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -352,16 +352,17 @@ proxies: # socks5 plugin: v2ray-plugin plugin-opts: mode: websocket # no QUIC now - # tls: true # wss - # 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取 - # 配置指纹将实现 SSL Pining 效果 - # fingerprint: xxxx - # skip-cert-verify: true - # host: bing.com - # path: "/" - # mux: true - # headers: - # custom: value + # tls: true # wss + # 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取 + # 配置指纹将实现 SSL Pining 效果 + # fingerprint: xxxx + # skip-cert-verify: true + # host: bing.com + # path: "/" + # mux: true + # headers: + # custom: value + # v2ray-http-upgrade: false - name: "ss4-shadow-tls" type: ss @@ -434,11 +435,12 @@ proxies: # socks5 # servername: example.com # priority over wss host # network: ws # ws-opts: - # path: /path - # headers: - # Host: v2ray.com - # max-early-data: 2048 - # early-data-header-name: Sec-WebSocket-Protocol + # path: /path + # headers: + # Host: v2ray.com + # max-early-data: 2048 + # early-data-header-name: Sec-WebSocket-Protocol + # v2ray-http-upgrade: false - name: "vmess-h2" type: vmess @@ -566,6 +568,7 @@ proxies: # socks5 path: "/" headers: Host: example.com + # v2ray-http-upgrade: false # Trojan - name: "trojan" @@ -606,9 +609,10 @@ proxies: # socks5 # fingerprint: xxxx udp: true # ws-opts: - # path: /path - # headers: - # Host: example.com + # path: /path + # headers: + # Host: example.com + # v2ray-http-upgrade: false - name: "trojan-xtls" type: trojan diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 6dfcfe11..20ba80b3 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -55,10 +55,11 @@ type Option struct { } type WebsocketOption struct { - Host string - Port string - Path string - Headers http.Header + Host string + Port string + Path string + Headers http.Header + V2rayHttpUpgrade bool } type Trojan struct { @@ -132,6 +133,7 @@ func (t *Trojan) StreamWebsocketConn(ctx context.Context, conn net.Conn, wsOptio Port: wsOptions.Port, Path: wsOptions.Path, Headers: wsOptions.Headers, + V2rayHttpUpgrade: wsOptions.V2rayHttpUpgrade, TLS: true, TLSConfig: tlsConfig, ClientFingerprint: t.option.ClientFingerprint, diff --git a/transport/v2ray-plugin/websocket.go b/transport/v2ray-plugin/websocket.go index 066a3e2a..9cb4420c 100644 --- a/transport/v2ray-plugin/websocket.go +++ b/transport/v2ray-plugin/websocket.go @@ -12,14 +12,15 @@ import ( // Option is options of websocket obfs type Option struct { - Host string - Port string - Path string - Headers map[string]string - TLS bool - SkipCertVerify bool - Fingerprint string - Mux bool + Host string + Port string + Path string + Headers map[string]string + TLS bool + SkipCertVerify bool + Fingerprint string + Mux bool + V2rayHttpUpgrade bool } // NewV2rayObfs return a HTTPObfs @@ -30,10 +31,11 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn, } config := &vmess.WebsocketConfig{ - Host: option.Host, - Port: option.Port, - Path: option.Path, - Headers: header, + Host: option.Host, + Port: option.Port, + Path: option.Path, + V2rayHttpUpgrade: option.V2rayHttpUpgrade, + Headers: header, } if option.TLS { diff --git a/transport/vmess/websocket.go b/transport/vmess/websocket.go index 1117edaf..9b325ee9 100644 --- a/transport/vmess/websocket.go +++ b/transport/vmess/websocket.go @@ -55,6 +55,7 @@ type WebsocketConfig struct { MaxEarlyData int EarlyDataHeaderName string ClientFingerprint string + V2rayHttpUpgrade bool } // Read implements net.Conn.Read() @@ -352,6 +353,39 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig, RawQuery: u.RawQuery, } + if c.V2rayHttpUpgrade { + if c.TLS { + if dialer.TLSClient != nil { + conn = dialer.TLSClient(conn, uri.Host) + } else { + conn = tls.Client(conn, dialer.TLSConfig) + } + } + request := &http.Request{ + Method: http.MethodGet, + URL: &uri, + Header: c.Headers.Clone(), + Host: c.Host, + } + request.Header.Set("Connection", "Upgrade") + request.Header.Set("Upgrade", "websocket") + err = request.Write(conn) + if err != nil { + return nil, err + } + bufferedConn := N.NewBufferedConn(conn) + response, err := http.ReadResponse(bufferedConn.Reader(), request) + if err != nil { + return nil, err + } + if response.StatusCode != 101 || + !strings.EqualFold(response.Header.Get("Connection"), "upgrade") || + !strings.EqualFold(response.Header.Get("Upgrade"), "websocket") { + return nil, fmt.Errorf("unexpected status: %s", response.Status) + } + return bufferedConn, nil + } + headers := http.Header{} headers.Set("User-Agent", "Go-http-client/1.1") // match golang's net/http if c.Headers != nil {