From 5f7053c519b16888f9f2ded3c84bef4246274f69 Mon Sep 17 00:00:00 2001 From: H1JK Date: Fri, 24 Nov 2023 13:02:00 +0800 Subject: [PATCH] feat: Add v2ray httpupgrade fast open support --- adapter/outbound/shadowsocks.go | 30 ++++++------- adapter/outbound/trojan.go | 11 ++--- adapter/outbound/vless.go | 17 ++++---- adapter/outbound/vmess.go | 28 +++++++------ transport/trojan/trojan.go | 28 +++++++------ transport/v2ray-plugin/websocket.go | 30 ++++++------- transport/vmess/httpupgrade.go | 65 +++++++++++++++++++++++++++++ transport/vmess/websocket.go | 28 ++++++++----- 8 files changed, 160 insertions(+), 77 deletions(-) create mode 100644 transport/vmess/httpupgrade.go diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index ffc72abb..859a10d6 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -58,15 +58,16 @@ 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"` - V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,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"` + V2rayHttpUpgradeFastOpen bool `obfs:"v2ray-http-upgrade-fast-open,omitempty"` } type shadowTLSOption struct { @@ -260,11 +261,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { } obfsMode = opts.Mode v2rayOption = &v2rayObfs.Option{ - Host: opts.Host, - Path: opts.Path, - Headers: opts.Headers, - Mux: opts.Mux, - V2rayHttpUpgrade: opts.V2rayHttpUpgrade, + Host: opts.Host, + Path: opts.Path, + Headers: opts.Headers, + Mux: opts.Mux, + V2rayHttpUpgrade: opts.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: opts.V2rayHttpUpgradeFastOpen, } if opts.TLS { diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index f03c3be9..b14761a4 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -53,11 +53,12 @@ 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, - V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade, - Headers: http.Header{}, + Host: host, + Port: port, + Path: t.option.WSOpts.Path, + V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen, + Headers: http.Header{}, } if t.option.SNI != "" { diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index dbe8b1a4..ceeb52a5 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -88,14 +88,15 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M case "ws": host, port, _ := net.SplitHostPort(v.addr) wsOpts := &vmess.WebsocketConfig{ - Host: host, - Port: port, - 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{}, + Host: host, + Port: port, + Path: v.option.WSOpts.Path, + MaxEarlyData: v.option.WSOpts.MaxEarlyData, + EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, + V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen, + ClientFingerprint: v.option.ClientFingerprint, + Headers: http.Header{}, } if len(v.option.WSOpts.Headers) != 0 { diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 8811fb0d..c1c981ce 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -87,11 +87,12 @@ type GrpcOptions struct { } type WSOptions struct { - Path string `proxy:"path,omitempty"` - 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"` + Path string `proxy:"path,omitempty"` + 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"` + V2rayHttpUpgradeFastOpen bool `proxy:"v2ray-http-upgrade-fast-open,omitempty"` } // StreamConnContext implements C.ProxyAdapter @@ -106,14 +107,15 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M case "ws": host, port, _ := net.SplitHostPort(v.addr) wsOpts := &mihomoVMess.WebsocketConfig{ - Host: host, - Port: port, - 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{}, + Host: host, + Port: port, + Path: v.option.WSOpts.Path, + MaxEarlyData: v.option.WSOpts.MaxEarlyData, + EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, + V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen, + ClientFingerprint: v.option.ClientFingerprint, + Headers: http.Header{}, } if len(v.option.WSOpts.Headers) != 0 { diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index c4bd1167..09be1124 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -55,11 +55,12 @@ type Option struct { } type WebsocketOption struct { - Host string - Port string - Path string - Headers http.Header - V2rayHttpUpgrade bool + Host string + Port string + Path string + Headers http.Header + V2rayHttpUpgrade bool + V2rayHttpUpgradeFastOpen bool } type Trojan struct { @@ -129,14 +130,15 @@ func (t *Trojan) StreamWebsocketConn(ctx context.Context, conn net.Conn, wsOptio } return vmess.StreamWebsocketConn(ctx, conn, &vmess.WebsocketConfig{ - Host: wsOptions.Host, - Port: wsOptions.Port, - Path: wsOptions.Path, - Headers: wsOptions.Headers, - V2rayHttpUpgrade: wsOptions.V2rayHttpUpgrade, - TLS: true, - TLSConfig: tlsConfig, - ClientFingerprint: t.option.ClientFingerprint, + Host: wsOptions.Host, + Port: wsOptions.Port, + Path: wsOptions.Path, + Headers: wsOptions.Headers, + V2rayHttpUpgrade: wsOptions.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: wsOptions.V2rayHttpUpgradeFastOpen, + TLS: true, + TLSConfig: tlsConfig, + ClientFingerprint: t.option.ClientFingerprint, }) } diff --git a/transport/v2ray-plugin/websocket.go b/transport/v2ray-plugin/websocket.go index 1c7056d6..90ff5efe 100644 --- a/transport/v2ray-plugin/websocket.go +++ b/transport/v2ray-plugin/websocket.go @@ -12,15 +12,16 @@ 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 - V2rayHttpUpgrade bool + Host string + Port string + Path string + Headers map[string]string + TLS bool + SkipCertVerify bool + Fingerprint string + Mux bool + V2rayHttpUpgrade bool + V2rayHttpUpgradeFastOpen bool } // NewV2rayObfs return a HTTPObfs @@ -31,11 +32,12 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn, } config := &vmess.WebsocketConfig{ - Host: option.Host, - Port: option.Port, - Path: option.Path, - V2rayHttpUpgrade: option.V2rayHttpUpgrade, - Headers: header, + Host: option.Host, + Port: option.Port, + Path: option.Path, + V2rayHttpUpgrade: option.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: option.V2rayHttpUpgradeFastOpen, + Headers: header, } if option.TLS { diff --git a/transport/vmess/httpupgrade.go b/transport/vmess/httpupgrade.go new file mode 100644 index 00000000..f7e819db --- /dev/null +++ b/transport/vmess/httpupgrade.go @@ -0,0 +1,65 @@ +package vmess + +import ( + "fmt" + "net/http" + "strings" + "sync" + + "github.com/metacubex/mihomo/common/buf" + "github.com/metacubex/mihomo/common/net" +) + +type httpUpgradeEarlyConn struct { + *net.BufferedConn + create sync.Once + done bool + err error +} + +func (c *httpUpgradeEarlyConn) readResponse() { + var request http.Request + response, err := http.ReadResponse(c.Reader(), &request) + c.done = true + if err != nil { + c.err = err + return + } + if response.StatusCode != http.StatusSwitchingProtocols || + !strings.EqualFold(response.Header.Get("Connection"), "upgrade") || + !strings.EqualFold(response.Header.Get("Upgrade"), "websocket") { + c.err = fmt.Errorf("unexpected status: %s", response.Status) + return + } +} + +func (c *httpUpgradeEarlyConn) Read(p []byte) (int, error) { + c.create.Do(c.readResponse) + if c.err != nil { + return 0, c.err + } + return c.BufferedConn.Read(p) +} + +func (c *httpUpgradeEarlyConn) ReadBuffer(buffer *buf.Buffer) error { + c.create.Do(c.readResponse) + if c.err != nil { + return c.err + } + return c.BufferedConn.ReadBuffer(buffer) +} + +func (c *httpUpgradeEarlyConn) ReaderReplaceable() bool { + return c.done +} + +func (c *httpUpgradeEarlyConn) ReaderPossiblyReplaceable() bool { + return !c.done +} + +func (c *httpUpgradeEarlyConn) ReadCached() *buf.Buffer { + if c.done { + return c.BufferedConn.ReadCached() + } + return nil +} diff --git a/transport/vmess/websocket.go b/transport/vmess/websocket.go index 8e675bb0..e898400c 100644 --- a/transport/vmess/websocket.go +++ b/transport/vmess/websocket.go @@ -49,16 +49,17 @@ type websocketWithEarlyDataConn struct { } type WebsocketConfig struct { - Host string - Port string - Path string - Headers http.Header - TLS bool - TLSConfig *tls.Config - MaxEarlyData int - EarlyDataHeaderName string - ClientFingerprint string - V2rayHttpUpgrade bool + Host string + Port string + Path string + Headers http.Header + TLS bool + TLSConfig *tls.Config + MaxEarlyData int + EarlyDataHeaderName string + ClientFingerprint string + V2rayHttpUpgrade bool + V2rayHttpUpgradeFastOpen bool } // Read implements net.Conn.Read() @@ -415,6 +416,13 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig, return nil, err } bufferedConn := N.NewBufferedConn(conn) + + if c.V2rayHttpUpgrade && c.V2rayHttpUpgradeFastOpen { + return &httpUpgradeEarlyConn{ + BufferedConn: bufferedConn, + }, nil + } + response, err := http.ReadResponse(bufferedConn.Reader(), request) if err != nil { return nil, err