Skip to content

Commit

Permalink
Fix: http proxy Upgrade behavior (#2097)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kr328 authored Apr 25, 2022
1 parent e010940 commit 6a92c6a
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 32 deletions.
30 changes: 30 additions & 0 deletions common/net/relay.go
Original file line number Diff line number Diff line change
@@ -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
}
13 changes: 7 additions & 6 deletions listener/http/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
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
Expand Down Expand Up @@ -66,6 +61,12 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {

request.RequestURI = ""

if isUpgradeRequest(request) {
handleUpgrade(conn, request, in)

return // hijack connection
}

removeHopByHopHeaders(request.Header)
removeExtraHTTPHostPort(request)

Expand Down
61 changes: 61 additions & 0 deletions listener/http/upgrade.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
12 changes: 9 additions & 3 deletions listener/http/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
24 changes: 1 addition & 23 deletions tunnel/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package tunnel

import (
"errors"
"io"
"net"
"time"

Expand Down Expand Up @@ -63,26 +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)

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
N.Relay(ctx.Conn(), outbound)
}

0 comments on commit 6a92c6a

Please sign in to comment.