mirror of
https://github.com/fatedier/frp.git
synced 2026-04-29 20:49:09 +08:00
plugin/http_proxy: fix fragmented CONNECT method detection and add read deadline (#5296)
This commit is contained in:
@@ -8,9 +8,9 @@ For mixed-version deployments, upgrade frps first, then upgrade frpc. This keeps
|
|||||||
|
|
||||||
This release introduces wire protocol v2 as a transition path for future frpc/frps protocol changes. The existing wire protocol is difficult to extend without compatibility risk, and upcoming changes, including replacing deprecated stream encryption methods, require a versioned protocol.
|
This release introduces wire protocol v2 as a transition path for future frpc/frps protocol changes. The existing wire protocol is difficult to extend without compatibility risk, and upcoming changes, including replacing deprecated stream encryption methods, require a versioned protocol.
|
||||||
|
|
||||||
**The default value of `transport.wireProtocol` remains `v1` in this release, but it will switch to `v2` in the next release.** Users can keep the default for now. To test v2 early, upgrade both frpc and frps to versions that support it, then set `transport.wireProtocol = "v2"` in frpc. A v2-enabled frpc cannot connect to an older frps.
|
**The default value of `transport.wireProtocol` remains `v1` in this release.** Users can keep the default for now. To test v2 early, upgrade both frpc and frps to versions that support it, then set `transport.wireProtocol = "v2"` in frpc. A v2-enabled frpc cannot connect to an older frps.
|
||||||
|
|
||||||
v1 will be deprecated when v2 becomes the default in the next release. It will continue to be supported until v0.78.0 is released, and may be removed in v0.78.0 or later.
|
v1 will be deprecated when v2 becomes the default in a future release. It will continue to be supported until v0.78.0 is released, and may be removed in v0.78.0 or later.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ type HTTPProxy struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const httpProxyReadHeaderTimeout = 60 * time.Second
|
||||||
|
|
||||||
func NewHTTPProxyPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTPProxyPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTPProxyPluginOptions)
|
opts := options.(*v1.HTTPProxyPluginOptions)
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
@@ -56,7 +58,7 @@ func NewHTTPProxyPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin
|
|||||||
|
|
||||||
hp.s = &http.Server{
|
hp.s = &http.Server{
|
||||||
Handler: hp,
|
Handler: hp,
|
||||||
ReadHeaderTimeout: 60 * time.Second,
|
ReadHeaderTimeout: httpProxyReadHeaderTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -73,16 +75,19 @@ func (hp *HTTPProxy) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
|||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
|
|
||||||
sc, rd := libnet.NewSharedConn(wrapConn)
|
sc, rd := libnet.NewSharedConn(wrapConn)
|
||||||
firstBytes := make([]byte, 7)
|
firstBytes := make([]byte, len(http.MethodConnect))
|
||||||
_, err := rd.Read(firstBytes)
|
_ = wrapConn.SetReadDeadline(time.Now().Add(httpProxyReadHeaderTimeout))
|
||||||
|
_, err := io.ReadFull(rd, firstBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = wrapConn.SetReadDeadline(time.Time{})
|
||||||
wrapConn.Close()
|
wrapConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToUpper(string(firstBytes)) == "CONNECT" {
|
if strings.EqualFold(string(firstBytes), http.MethodConnect) {
|
||||||
bufRd := bufio.NewReader(sc)
|
bufRd := bufio.NewReader(sc)
|
||||||
request, err := http.ReadRequest(bufRd)
|
request, err := http.ReadRequest(bufRd)
|
||||||
|
_ = wrapConn.SetReadDeadline(time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wrapConn.Close()
|
wrapConn.Close()
|
||||||
return
|
return
|
||||||
@@ -91,6 +96,7 @@ func (hp *HTTPProxy) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = wrapConn.SetReadDeadline(time.Time{})
|
||||||
_ = hp.l.PutConn(sc)
|
_ = hp.l.PutConn(sc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
107
pkg/plugin/client/http_proxy_test.go
Normal file
107
pkg/plugin/client/http_proxy_test.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright 2026 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !frps
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHTTPProxyHandleFragmentedConnectMethod(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
require.NoError(err)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
const payload = "ping"
|
||||||
|
echoErr := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
echoErr <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, len(payload))
|
||||||
|
if _, err = io.ReadFull(conn, buf); err != nil {
|
||||||
|
echoErr <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if string(buf) != payload {
|
||||||
|
echoErr <- fmt.Errorf("unexpected payload %q", string(buf))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = conn.Write([]byte("echo:" + payload))
|
||||||
|
echoErr <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
hp := &HTTPProxy{
|
||||||
|
opts: &v1.HTTPProxyPluginOptions{},
|
||||||
|
l: NewProxyListener(),
|
||||||
|
}
|
||||||
|
|
||||||
|
clientConn, serverConn := net.Pipe()
|
||||||
|
defer clientConn.Close()
|
||||||
|
|
||||||
|
go hp.Handle(context.Background(), &ConnectionInfo{
|
||||||
|
Conn: serverConn,
|
||||||
|
UnderlyingConn: serverConn,
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(clientConn.SetDeadline(time.Now().Add(5 * time.Second)))
|
||||||
|
|
||||||
|
targetAddr := ln.Addr().String()
|
||||||
|
req := "CONNECT " + targetAddr + " HTTP/1.1\r\nHost: " + targetAddr + "\r\n\r\n"
|
||||||
|
_, err = clientConn.Write([]byte("CON"))
|
||||||
|
require.NoError(err)
|
||||||
|
_, err = clientConn.Write([]byte(req[len("CON"):]))
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
rd := bufio.NewReader(clientConn)
|
||||||
|
status, err := rd.ReadString('\n')
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("HTTP/1.1 200 OK\r\n", status)
|
||||||
|
line, err := rd.ReadString('\n')
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("\r\n", line)
|
||||||
|
|
||||||
|
_, err = clientConn.Write([]byte(payload))
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
got := make([]byte, len("echo:"+payload))
|
||||||
|
_, err = io.ReadFull(rd, got)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("echo:"+payload, string(got))
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-echoErr:
|
||||||
|
require.NoError(err)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("timed out waiting for echo server")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user