mirror of
https://github.com/fatedier/frp.git
synced 2026-08-01 13:32:54 +08:00
feat: use binary codec for SUDP packets (#5461)
This commit is contained in:
+238
-21
@@ -18,22 +18,27 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/wire"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
func TestSUDPBridgeTranscodesProxyV1ToVisitorV2(t *testing.T) {
|
||||
var in, out bytes.Buffer
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV1, &msg.UDPPacket{Content: []byte("proxy-to-visitor")})
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV1, "", &msg.UDPPacket{Content: []byte("proxy-to-visitor")})
|
||||
|
||||
var count int64
|
||||
err := bridgeSUDPProxyToVisitor(
|
||||
msg.NewReadWriter(&in, wire.ProtocolV1),
|
||||
msg.NewReadWriter(&out, wire.ProtocolV2),
|
||||
newSUDPBridgeRW(t, &in, wire.ProtocolV1, ""),
|
||||
newSUDPBridgeRW(t, &out, wire.ProtocolV2, ""),
|
||||
&count,
|
||||
nil,
|
||||
)
|
||||
@@ -53,12 +58,12 @@ func TestSUDPBridgeTranscodesProxyV1ToVisitorV2(t *testing.T) {
|
||||
|
||||
func TestSUDPBridgeTranscodesVisitorV2ToProxyV1(t *testing.T) {
|
||||
var in, out bytes.Buffer
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV2, &msg.UDPPacket{Content: []byte("visitor-to-proxy")})
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV2, "", &msg.UDPPacket{Content: []byte("visitor-to-proxy")})
|
||||
|
||||
var count int64
|
||||
err := bridgeSUDPVisitorToProxy(
|
||||
msg.NewReadWriter(&in, wire.ProtocolV2),
|
||||
msg.NewReadWriter(&out, wire.ProtocolV1),
|
||||
newSUDPBridgeRW(t, &in, wire.ProtocolV2, ""),
|
||||
newSUDPBridgeRW(t, &out, wire.ProtocolV1, ""),
|
||||
&count,
|
||||
nil,
|
||||
)
|
||||
@@ -76,33 +81,67 @@ func TestSUDPBridgeTranscodesVisitorV2ToProxyV1(t *testing.T) {
|
||||
require.Equal(t, []byte("visitor-to-proxy"), got.Content)
|
||||
}
|
||||
|
||||
func TestSUDPBridgeForwardsProxyPing(t *testing.T) {
|
||||
func TestSUDPBridgeTranscodesProxyV2BinaryToVisitorV2JSON(t *testing.T) {
|
||||
var in, out bytes.Buffer
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV1, &msg.Ping{})
|
||||
packet := newSUDPBridgeUDPPacket("proxy-binary-to-json")
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV2, wire.UDPPacketCodecBinary, packet)
|
||||
|
||||
var count int64
|
||||
err := bridgeSUDPProxyToVisitor(
|
||||
msg.NewReadWriter(&in, wire.ProtocolV1),
|
||||
msg.NewReadWriter(&out, wire.ProtocolV2),
|
||||
newSUDPBridgeRW(t, &in, wire.ProtocolV2, wire.UDPPacketCodecBinary),
|
||||
newSUDPBridgeRW(t, &out, wire.ProtocolV2, ""),
|
||||
&count,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(len(packet.Content)), count)
|
||||
requireV2UDPPacketFrame(t, &out, msg.V2TypeUDPPacket, packet)
|
||||
}
|
||||
|
||||
func TestSUDPBridgeTranscodesVisitorV2JSONToProxyV2Binary(t *testing.T) {
|
||||
var in, out bytes.Buffer
|
||||
packet := newSUDPBridgeUDPPacket("visitor-json-to-binary")
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV2, "", packet)
|
||||
|
||||
var count int64
|
||||
err := bridgeSUDPVisitorToProxy(
|
||||
newSUDPBridgeRW(t, &in, wire.ProtocolV2, ""),
|
||||
newSUDPBridgeRW(t, &out, wire.ProtocolV2, wire.UDPPacketCodecBinary),
|
||||
&count,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(len(packet.Content)), count)
|
||||
requireV2UDPPacketFrame(t, &out, msg.V2TypeUDPPacketBinary, packet)
|
||||
}
|
||||
|
||||
func TestSUDPBridgeForwardsProxyPing(t *testing.T) {
|
||||
var in, out bytes.Buffer
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV1, "", &msg.Ping{})
|
||||
|
||||
var count int64
|
||||
err := bridgeSUDPProxyToVisitor(
|
||||
newSUDPBridgeRW(t, &in, wire.ProtocolV1, ""),
|
||||
newSUDPBridgeRW(t, &out, wire.ProtocolV2, ""),
|
||||
&count,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, count)
|
||||
|
||||
rawMsg, err := msg.NewReadWriter(&out, wire.ProtocolV2).ReadMsg()
|
||||
rawMsg, err := newSUDPBridgeRW(t, &out, wire.ProtocolV2, "").ReadMsg()
|
||||
require.NoError(t, err)
|
||||
require.IsType(t, &msg.Ping{}, rawMsg)
|
||||
}
|
||||
|
||||
func TestSUDPBridgeDropsVisitorPing(t *testing.T) {
|
||||
var in, out bytes.Buffer
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV2, &msg.Ping{})
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV2, "", &msg.Ping{})
|
||||
|
||||
var count int64
|
||||
err := bridgeSUDPVisitorToProxy(
|
||||
msg.NewReadWriter(&in, wire.ProtocolV2),
|
||||
msg.NewReadWriter(&out, wire.ProtocolV1),
|
||||
newSUDPBridgeRW(t, &in, wire.ProtocolV2, ""),
|
||||
newSUDPBridgeRW(t, &out, wire.ProtocolV1, ""),
|
||||
&count,
|
||||
nil,
|
||||
)
|
||||
@@ -113,12 +152,12 @@ func TestSUDPBridgeDropsVisitorPing(t *testing.T) {
|
||||
|
||||
func TestSUDPBridgeRejectsUnknownVisitorMessage(t *testing.T) {
|
||||
var in, out bytes.Buffer
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV2, &msg.Pong{})
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV2, "", &msg.Pong{})
|
||||
|
||||
var count int64
|
||||
err := bridgeSUDPVisitorToProxy(
|
||||
msg.NewReadWriter(&in, wire.ProtocolV2),
|
||||
msg.NewReadWriter(&out, wire.ProtocolV1),
|
||||
newSUDPBridgeRW(t, &in, wire.ProtocolV2, ""),
|
||||
newSUDPBridgeRW(t, &out, wire.ProtocolV1, ""),
|
||||
&count,
|
||||
nil,
|
||||
)
|
||||
@@ -127,6 +166,22 @@ func TestSUDPBridgeRejectsUnknownVisitorMessage(t *testing.T) {
|
||||
require.Empty(t, out.Bytes())
|
||||
}
|
||||
|
||||
func TestSUDPBridgeRejectsMismatchedPacketCodecOnStream(t *testing.T) {
|
||||
var in, out bytes.Buffer
|
||||
writeSUDPBridgeMsg(t, &in, wire.ProtocolV2, "", newSUDPBridgeUDPPacket("json-on-binary-stream"))
|
||||
|
||||
var count int64
|
||||
err := bridgeSUDPProxyToVisitor(
|
||||
newSUDPBridgeRW(t, &in, wire.ProtocolV2, wire.UDPPacketCodecBinary),
|
||||
newSUDPBridgeRW(t, &out, wire.ProtocolV2, ""),
|
||||
&count,
|
||||
nil,
|
||||
)
|
||||
require.ErrorContains(t, err, "received JSON UDP packet after binary codec negotiation")
|
||||
require.Zero(t, count)
|
||||
require.Empty(t, out.Bytes())
|
||||
}
|
||||
|
||||
func TestSUDPBridgeDetectsMixedWireProtocol(t *testing.T) {
|
||||
require.False(t, isMixedWireProtocol("", wire.ProtocolV1))
|
||||
require.False(t, isMixedWireProtocol(wire.ProtocolV2, wire.ProtocolV2))
|
||||
@@ -134,8 +189,170 @@ func TestSUDPBridgeDetectsMixedWireProtocol(t *testing.T) {
|
||||
require.True(t, isMixedWireProtocol(wire.ProtocolV2, wire.ProtocolV1))
|
||||
}
|
||||
|
||||
func writeSUDPBridgeMsg(t *testing.T, buf *bytes.Buffer, wireProtocol string, m msg.Message) {
|
||||
t.Helper()
|
||||
|
||||
require.NoError(t, msg.NewReadWriter(buf, wireProtocol).WriteMsg(m))
|
||||
func TestSUDPBridgeDetectsMixedPacketEncoding(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
leftWire string
|
||||
leftCodec string
|
||||
rightWire string
|
||||
rightCodec string
|
||||
mixed bool
|
||||
}{
|
||||
{name: "legacy v1 aliases explicit v1", leftWire: "", rightWire: wire.ProtocolV1},
|
||||
{name: "v2 json matches v2 json", leftWire: wire.ProtocolV2, rightWire: wire.ProtocolV2},
|
||||
{
|
||||
name: "v2 binary matches v2 binary",
|
||||
leftWire: wire.ProtocolV2,
|
||||
leftCodec: wire.UDPPacketCodecBinary,
|
||||
rightWire: wire.ProtocolV2,
|
||||
rightCodec: wire.UDPPacketCodecBinary,
|
||||
},
|
||||
{name: "v1 json differs from v2 json", leftWire: wire.ProtocolV1, rightWire: wire.ProtocolV2, mixed: true},
|
||||
{
|
||||
name: "v2 json differs from v2 binary",
|
||||
leftWire: wire.ProtocolV2,
|
||||
rightWire: wire.ProtocolV2,
|
||||
rightCodec: wire.UDPPacketCodecBinary,
|
||||
mixed: true,
|
||||
},
|
||||
{
|
||||
name: "v2 binary differs from v1 json",
|
||||
leftWire: wire.ProtocolV2,
|
||||
leftCodec: wire.UDPPacketCodecBinary,
|
||||
rightWire: wire.ProtocolV1,
|
||||
mixed: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mixed, err := isMixedSUDPPacketEncoding(tc.leftWire, tc.leftCodec, tc.rightWire, tc.rightCodec)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.mixed, mixed)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSUDPBridgeRejectsInvalidEncodingMetadata(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
leftWire string
|
||||
leftCodec string
|
||||
rightWire string
|
||||
rightCodec string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "left v1 binary",
|
||||
leftWire: wire.ProtocolV1,
|
||||
leftCodec: wire.UDPPacketCodecBinary,
|
||||
rightWire: wire.ProtocolV1,
|
||||
wantErr: "invalid left SUDP packet encoding",
|
||||
},
|
||||
{
|
||||
name: "right unknown v2 codec",
|
||||
leftWire: wire.ProtocolV2,
|
||||
rightWire: wire.ProtocolV2,
|
||||
rightCodec: "snappy",
|
||||
wantErr: "invalid right SUDP packet encoding",
|
||||
},
|
||||
{name: "left unknown wire", leftWire: "v3", rightWire: wire.ProtocolV2, wantErr: "unsupported wire protocol"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mixed, err := isMixedSUDPPacketEncoding(tc.leftWire, tc.leftCodec, tc.rightWire, tc.rightCodec)
|
||||
require.False(t, mixed)
|
||||
require.ErrorContains(t, err, tc.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSUDPJoinUsesRawPathForSameEncodingState(t *testing.T) {
|
||||
proxyClient, proxyServer := net.Pipe()
|
||||
visitorClient, visitorServer := net.Pipe()
|
||||
t.Cleanup(func() {
|
||||
_ = proxyClient.Close()
|
||||
_ = proxyServer.Close()
|
||||
_ = visitorClient.Close()
|
||||
_ = visitorServer.Close()
|
||||
})
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
require.NoError(t, proxyClient.SetDeadline(deadline))
|
||||
require.NoError(t, proxyServer.SetDeadline(deadline))
|
||||
require.NoError(t, visitorClient.SetDeadline(deadline))
|
||||
require.NoError(t, visitorServer.SetDeadline(deadline))
|
||||
|
||||
pxy := &BaseProxy{
|
||||
configurer: &v1.SUDPProxyConfig{},
|
||||
wireProtocol: wire.ProtocolV2,
|
||||
udpPacketCodec: wire.UDPPacketCodecBinary,
|
||||
}
|
||||
visitorConn := &metadataConn{Conn: visitorServer, wireProtocol: wire.ProtocolV2, udpPacketCodec: wire.UDPPacketCodecBinary}
|
||||
joinDone := make(chan []error, 1)
|
||||
go func() {
|
||||
_, _, errs := pxy.joinUserConnection(proxyServer, visitorConn, string(v1.ProxyTypeSUDP), xlog.New())
|
||||
joinDone <- errs
|
||||
}()
|
||||
|
||||
raw := []byte{0, 16, 0, 0, 0, 4, 0xde, 0xad, 0xbe, 0xef}
|
||||
_, err := proxyClient.Write(raw)
|
||||
require.NoError(t, err)
|
||||
got := make([]byte, len(raw))
|
||||
_, err = io.ReadFull(visitorClient, got)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, raw, got)
|
||||
|
||||
_ = proxyClient.Close()
|
||||
_ = visitorClient.Close()
|
||||
<-joinDone
|
||||
}
|
||||
|
||||
func newSUDPBridgeRW(t *testing.T, buf *bytes.Buffer, wireProtocol, udpPacketCodec string) msg.ReadWriter {
|
||||
t.Helper()
|
||||
rw, err := msg.NewUDPPacketReadWriter(buf, wireProtocol, udpPacketCodec)
|
||||
require.NoError(t, err)
|
||||
return rw
|
||||
}
|
||||
|
||||
func writeSUDPBridgeMsg(t *testing.T, buf *bytes.Buffer, wireProtocol, udpPacketCodec string, m msg.Message) {
|
||||
t.Helper()
|
||||
require.NoError(t, newSUDPBridgeRW(t, buf, wireProtocol, udpPacketCodec).WriteMsg(m))
|
||||
}
|
||||
|
||||
func newSUDPBridgeUDPPacket(content string) *msg.UDPPacket {
|
||||
return &msg.UDPPacket{
|
||||
Content: []byte(content),
|
||||
RemoteAddr: &net.UDPAddr{IP: net.ParseIP("192.0.2.1"), Port: 12345},
|
||||
}
|
||||
}
|
||||
|
||||
func requireV2UDPPacketFrame(t *testing.T, buf *bytes.Buffer, wantType uint16, want *msg.UDPPacket) {
|
||||
t.Helper()
|
||||
frame, err := wire.NewConn(buf).ReadFrame()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, wire.FrameTypeMessage, frame.Type)
|
||||
require.GreaterOrEqual(t, len(frame.Payload), 2)
|
||||
require.Equal(t, wantType, binary.BigEndian.Uint16(frame.Payload[:2]))
|
||||
var got *msg.UDPPacket
|
||||
if wantType == msg.V2TypeUDPPacketBinary {
|
||||
got, err = msg.DecodeUDPPacketBinary(frame.Payload[2:])
|
||||
} else {
|
||||
var decoded msg.UDPPacket
|
||||
err = msg.DecodeV2MessageFrameInto(frame, &decoded)
|
||||
got = &decoded
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, want.Content, got.Content)
|
||||
require.Equal(t, want.RemoteAddr.String(), got.RemoteAddr.String())
|
||||
}
|
||||
|
||||
type metadataConn struct {
|
||||
net.Conn
|
||||
wireProtocol string
|
||||
udpPacketCodec string
|
||||
}
|
||||
|
||||
func (c *metadataConn) WireProtocol() string {
|
||||
return c.wireProtocol
|
||||
}
|
||||
|
||||
func (c *metadataConn) UDPPacketCodec() string {
|
||||
return c.udpPacketCodec
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user