From 4aaa9f8ca89247639d6ae38faf3ba872bb1e7f8c Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Mon, 25 Apr 2022 19:50:20 +0800 Subject: [PATCH] Fix: http proxy Upgrade behavior (#2097) --- common/net/relay.go | 30 ++++++++++++++++++++ listener/http/proxy.go | 13 +++++---- listener/http/upgrade.go | 61 ++++++++++++++++++++++++++++++++++++++++ listener/http/utils.go | 12 ++++++-- tunnel/connection.go | 33 +--------------------- 5 files changed, 108 insertions(+), 41 deletions(-) create mode 100644 common/net/relay.go create mode 100644 listener/http/upgrade.go diff --git a/common/net/relay.go b/common/net/relay.go new file mode 100644 index 00000000..e7157639 --- /dev/null +++ b/common/net/relay.go @@ -0,0 +1,30 @@ +package net + +import ( + "io" + "net" + "time" + + "github.com/Dreamacro/clash/common/pool" +) + +// Relay copies between left and right bidirectionally. +func Relay(leftConn, rightConn net.Conn) { + ch := make(chan error) + + go func() { + buf := pool.Get(pool.RelayBufferSize) + // Wrapping to avoid using *net.TCPConn.(ReadFrom) + // See also https://github.com/Dreamacro/clash/pull/1209 + _, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf) + pool.Put(buf) + leftConn.SetReadDeadline(time.Now()) + ch <- err + }() + + buf := pool.Get(pool.RelayBufferSize) + io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf) + pool.Put(buf) + rightConn.SetReadDeadline(time.Now()) + <-ch +} diff --git a/listener/http/proxy.go b/listener/http/proxy.go index e8a805a9..720a5655 100644 --- a/listener/http/proxy.go +++ b/listener/http/proxy.go @@ -19,12 +19,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache[string, client := newClient(c.RemoteAddr(), in) defer client.CloseIdleConnections() - var conn *N.BufferedConn - if bufConn, ok := c.(*N.BufferedConn); ok { - conn = bufConn - } else { - conn = N.NewBufferedConn(c) - } + conn := N.NewBufferedConn(c) keepAlive := true trusted := cache == nil // disable authenticate if cache is nil @@ -66,6 +61,12 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache[string, request.RequestURI = "" + if isUpgradeRequest(request) { + handleUpgrade(conn, request, in) + + return // hijack connection + } + removeHopByHopHeaders(request.Header) removeExtraHTTPHostPort(request) diff --git a/listener/http/upgrade.go b/listener/http/upgrade.go new file mode 100644 index 00000000..643de541 --- /dev/null +++ b/listener/http/upgrade.go @@ -0,0 +1,61 @@ +package http + +import ( + "net" + "net/http" + "strings" + + "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/socks5" +) + +func isUpgradeRequest(req *http.Request) bool { + return strings.EqualFold(req.Header.Get("Connection"), "Upgrade") +} + +func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext) { + defer conn.Close() + + removeProxyHeaders(request.Header) + removeExtraHTTPHostPort(request) + + address := request.Host + if _, _, err := net.SplitHostPort(address); err != nil { + address = net.JoinHostPort(address, "80") + } + + dstAddr := socks5.ParseAddr(address) + if dstAddr == nil { + return + } + + left, right := net.Pipe() + + in <- inbound.NewHTTP(dstAddr, conn.RemoteAddr(), right) + + bufferedLeft := N.NewBufferedConn(left) + defer bufferedLeft.Close() + + err := request.Write(bufferedLeft) + if err != nil { + return + } + + resp, err := http.ReadResponse(bufferedLeft.Reader(), request) + if err != nil { + return + } + + removeProxyHeaders(resp.Header) + + err = resp.Write(conn) + if err != nil { + return + } + + if resp.StatusCode == http.StatusSwitchingProtocols { + N.Relay(bufferedLeft, conn) + } +} diff --git a/listener/http/utils.go b/listener/http/utils.go index bcee60f0..63726d51 100644 --- a/listener/http/utils.go +++ b/listener/http/utils.go @@ -8,15 +8,21 @@ import ( "strings" ) +// removeHopByHopHeaders remove Proxy-* headers +func removeProxyHeaders(header http.Header) { + header.Del("Proxy-Connection") + header.Del("Proxy-Authenticate") + header.Del("Proxy-Authorization") +} + // removeHopByHopHeaders remove hop-by-hop header func removeHopByHopHeaders(header http.Header) { // Strip hop-by-hop header based on RFC: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do - header.Del("Proxy-Connection") - header.Del("Proxy-Authenticate") - header.Del("Proxy-Authorization") + removeProxyHeaders(header) + header.Del("TE") header.Del("Trailers") header.Del("Transfer-Encoding") diff --git a/tunnel/connection.go b/tunnel/connection.go index 82443c35..0384e805 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -2,7 +2,6 @@ package tunnel import ( "errors" - "io" "net" "time" @@ -63,35 +62,5 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n } func handleSocket(ctx C.ConnContext, outbound net.Conn) { - relay(ctx.Conn(), outbound) -} - -// relay copies between left and right bidirectionally. -func relay(leftConn, rightConn net.Conn) { - ch := make(chan error) - - tcpKeepAlive(leftConn) - tcpKeepAlive(rightConn) - - go func() { - buf := pool.Get(pool.RelayBufferSize) - // Wrapping to avoid using *net.TCPConn.(ReadFrom) - // See also https://github.com/Dreamacro/clash/pull/1209 - _, err := io.CopyBuffer(N.WriteOnlyWriter{Writer: leftConn}, N.ReadOnlyReader{Reader: rightConn}, buf) - pool.Put(buf) - leftConn.SetReadDeadline(time.Now()) - ch <- err - }() - - buf := pool.Get(pool.RelayBufferSize) - io.CopyBuffer(N.WriteOnlyWriter{Writer: rightConn}, N.ReadOnlyReader{Reader: leftConn}, buf) - pool.Put(buf) - rightConn.SetReadDeadline(time.Now()) - <-ch -} - -func tcpKeepAlive(c net.Conn) { - if tcp, ok := c.(*net.TCPConn); ok { - tcp.SetKeepAlive(true) - } + N.Relay(ctx.Conn(), outbound) }