mirror of
https://github.com/fatedier/frp.git
synced 2026-10-15 03:25:56 +08:00
fix(vhost): preserve buffered HTTP CONNECT tunnel data (#5556)
* fix(vhost): preserve buffered HTTP CONNECT tunnel data * test(vhost): cover pipelined CONNECT data and bidirectional tunnel traffic
This commit is contained in:
@@ -212,7 +212,7 @@ func (rp *HTTPReverseProxy) connectHandler(rw http.ResponseWriter, req *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
client, _, err := hj.Hijack()
|
client, buffered, err := hj.Hijack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rw.WriteHeader(http.StatusInternalServerError)
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -225,7 +225,7 @@ func (rp *HTTPReverseProxy) connectHandler(rw http.ResponseWriter, req *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = req.Write(remote)
|
_ = req.Write(remote)
|
||||||
go libio.Join(remote, client)
|
go libio.Join(remote, libio.WrapReadWriteCloser(buffered.Reader, client, client.Close))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRequestRouteUser(req *http.Request) string {
|
func getRequestRouteUser(req *http.Request) string {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package vhost
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -12,8 +13,87 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestHTTPConnectTunnel(t *testing.T) {
|
||||||
|
for _, pipelined := range []bool{false, true} {
|
||||||
|
t.Run(fmt.Sprintf("pipelined=%v", pipelined), func(t *testing.T) {
|
||||||
|
remote, backend := net.Pipe()
|
||||||
|
defer remote.Close()
|
||||||
|
defer backend.Close()
|
||||||
|
require.NoError(t, backend.SetDeadline(time.Now().Add(5*time.Second)))
|
||||||
|
|
||||||
|
rp := NewHTTPReverseProxy(HTTPReverseProxyOptions{}, NewRouters())
|
||||||
|
require.NoError(t, rp.Register(RouteConfig{
|
||||||
|
Domain: "target.example.com",
|
||||||
|
CreateConnFn: func(string) (net.Conn, error) {
|
||||||
|
return remote, nil
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
listener := netpkg.NewInternalListener()
|
||||||
|
server := &http.Server{Handler: rp, ReadHeaderTimeout: time.Second}
|
||||||
|
serveErr := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
serveErr <- server.Serve(listener)
|
||||||
|
}()
|
||||||
|
defer func() {
|
||||||
|
require.NoError(t, server.Close())
|
||||||
|
require.ErrorIs(t, <-serveErr, http.ErrServerClosed)
|
||||||
|
}()
|
||||||
|
|
||||||
|
client, incoming := net.Pipe()
|
||||||
|
defer client.Close()
|
||||||
|
defer incoming.Close()
|
||||||
|
require.NoError(t, client.SetDeadline(time.Now().Add(5*time.Second)))
|
||||||
|
require.NoError(t, listener.PutConn(incoming))
|
||||||
|
|
||||||
|
request := "CONNECT target.example.com:443 HTTP/1.1\r\nHost: target.example.com:443\r\n\r\n"
|
||||||
|
if pipelined {
|
||||||
|
// One pipe write puts the tunnel data in net/http's request buffer.
|
||||||
|
request += "early data"
|
||||||
|
}
|
||||||
|
_, err := io.WriteString(client, request)
|
||||||
|
require.NoError(t, err)
|
||||||
|
backendReader := bufio.NewReader(backend)
|
||||||
|
req, err := http.ReadRequest(backendReader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer req.Body.Close()
|
||||||
|
require.Equal(t, http.MethodConnect, req.Method)
|
||||||
|
require.Equal(t, "target.example.com:443", req.Host)
|
||||||
|
if pipelined {
|
||||||
|
data := make([]byte, len("early data"))
|
||||||
|
_, err = io.ReadFull(backendReader, data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "early data", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
writeErr := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
_, err := io.WriteString(backend, "HTTP/1.1 200 Connection Established\r\n\r\nreply")
|
||||||
|
writeErr <- err
|
||||||
|
}()
|
||||||
|
clientReader := bufio.NewReader(client)
|
||||||
|
response, err := http.ReadResponse(clientReader, req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer response.Body.Close()
|
||||||
|
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||||
|
reply := make([]byte, len("reply"))
|
||||||
|
_, err = io.ReadFull(clientReader, reply)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "reply", string(reply))
|
||||||
|
require.NoError(t, <-writeErr)
|
||||||
|
|
||||||
|
_, err = io.WriteString(client, "later data")
|
||||||
|
require.NoError(t, err)
|
||||||
|
data := make([]byte, len("later data"))
|
||||||
|
_, err = io.ReadFull(backendReader, data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "later data", string(data))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHTTPServerProtocols(t *testing.T) {
|
func TestHTTPServerProtocols(t *testing.T) {
|
||||||
rp := NewHTTPReverseProxy(HTTPReverseProxyOptions{}, NewRouters())
|
rp := NewHTTPReverseProxy(HTTPReverseProxyOptions{}, NewRouters())
|
||||||
protocols := new(http.Protocols)
|
protocols := new(http.Protocols)
|
||||||
|
|||||||
Reference in New Issue
Block a user