mirror of
https://github.com/fatedier/frp.git
synced 2026-08-02 09:52:53 +08:00
feat: use binary codec for SUDP packets (#5461)
This commit is contained in:
+62
-6
@@ -328,10 +328,18 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
|
||||
|
||||
func (pxy *BaseProxy) joinUserConnection(local io.ReadWriteCloser, userConn net.Conn, proxyType string, xl *xlog.Logger) (int64, int64, []error) {
|
||||
visitorWireProtocol := wireProtocolFromConn(userConn)
|
||||
if proxyType == string(v1.ProxyTypeSUDP) && isMixedWireProtocol(pxy.wireProtocol, visitorWireProtocol) {
|
||||
xl.Infof("bridge mixed SUDP payload codecs, proxy wireProtocol [%s], visitor wireProtocol [%s]",
|
||||
normalizeWireProtocol(pxy.wireProtocol), normalizeWireProtocol(visitorWireProtocol))
|
||||
return joinSUDPMessageBridge(local, userConn, pxy.wireProtocol, visitorWireProtocol, xl)
|
||||
visitorUDPPacketCodec := udpPacketCodecFromConn(userConn)
|
||||
if proxyType == string(v1.ProxyTypeSUDP) {
|
||||
mixed, err := isMixedSUDPPacketEncoding(pxy.wireProtocol, pxy.udpPacketCodec, visitorWireProtocol, visitorUDPPacketCodec)
|
||||
if err != nil {
|
||||
return 0, 0, []error{err}
|
||||
}
|
||||
if mixed {
|
||||
xl.Infof("bridge mixed SUDP payload codecs, proxy [%s/%s], visitor [%s/%s]",
|
||||
normalizeWireProtocol(pxy.wireProtocol), pxy.udpPacketCodec,
|
||||
normalizeWireProtocol(visitorWireProtocol), visitorUDPPacketCodec)
|
||||
return joinSUDPMessageBridge(local, userConn, pxy.wireProtocol, pxy.udpPacketCodec, visitorWireProtocol, visitorUDPPacketCodec, xl)
|
||||
}
|
||||
}
|
||||
return libio.Join(local, userConn)
|
||||
}
|
||||
@@ -340,6 +348,10 @@ type wireProtocolGetter interface {
|
||||
WireProtocol() string
|
||||
}
|
||||
|
||||
type udpPacketCodecGetter interface {
|
||||
UDPPacketCodec() string
|
||||
}
|
||||
|
||||
func wireProtocolFromConn(conn net.Conn) string {
|
||||
if getter, ok := conn.(wireProtocolGetter); ok {
|
||||
return getter.WireProtocol()
|
||||
@@ -347,10 +359,46 @@ func wireProtocolFromConn(conn net.Conn) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func udpPacketCodecFromConn(conn net.Conn) string {
|
||||
if getter, ok := conn.(udpPacketCodecGetter); ok {
|
||||
return getter.UDPPacketCodec()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isMixedWireProtocol(left, right string) bool {
|
||||
return normalizeWireProtocol(left) != normalizeWireProtocol(right)
|
||||
}
|
||||
|
||||
func isMixedSUDPPacketEncoding(leftWire, leftCodec, rightWire, rightCodec string) (bool, error) {
|
||||
leftCodec, err := normalizeUDPPacketCodec(leftWire, leftCodec)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid left SUDP packet encoding: %w", err)
|
||||
}
|
||||
rightCodec, err = normalizeUDPPacketCodec(rightWire, rightCodec)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid right SUDP packet encoding: %w", err)
|
||||
}
|
||||
return normalizeWireProtocol(leftWire) != normalizeWireProtocol(rightWire) || leftCodec != rightCodec, nil
|
||||
}
|
||||
|
||||
func normalizeUDPPacketCodec(wireProtocol, codec string) (string, error) {
|
||||
switch wireProtocol {
|
||||
case "", wire.ProtocolV1:
|
||||
if codec != "" {
|
||||
return "", fmt.Errorf("UDP packet codec %q requires wire protocol v2", codec)
|
||||
}
|
||||
return "", nil
|
||||
case wire.ProtocolV2:
|
||||
if codec == "" || codec == wire.UDPPacketCodecBinary {
|
||||
return codec, nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported UDP packet codec %q", codec)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported wire protocol %q", wireProtocol)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeWireProtocol(wireProtocol string) string {
|
||||
if wireProtocol == wire.ProtocolV2 {
|
||||
return wire.ProtocolV2
|
||||
@@ -362,13 +410,21 @@ func joinSUDPMessageBridge(
|
||||
proxyConn io.ReadWriteCloser,
|
||||
visitorConn io.ReadWriteCloser,
|
||||
proxyWireProtocol string,
|
||||
proxyUDPPacketCodec string,
|
||||
visitorWireProtocol string,
|
||||
visitorUDPPacketCodec string,
|
||||
xl *xlog.Logger,
|
||||
) (inCount int64, outCount int64, errs []error) {
|
||||
// The mixed bridge decodes and re-encodes messages, so raw framed byte counts
|
||||
// are not available. Count UDP payload bytes and ignore heartbeat traffic.
|
||||
proxyRW := msg.NewReadWriter(proxyConn, proxyWireProtocol)
|
||||
visitorRW := msg.NewReadWriter(visitorConn, visitorWireProtocol)
|
||||
proxyRW, err := msg.NewUDPPacketReadWriter(proxyConn, proxyWireProtocol, proxyUDPPacketCodec)
|
||||
if err != nil {
|
||||
return 0, 0, []error{err}
|
||||
}
|
||||
visitorRW, err := msg.NewUDPPacketReadWriter(visitorConn, visitorWireProtocol, visitorUDPPacketCodec)
|
||||
if err != nil {
|
||||
return 0, 0, []error{err}
|
||||
}
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
// 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.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/wire"
|
||||
)
|
||||
|
||||
type sudpPathBenchmarkCase struct {
|
||||
name string
|
||||
packet *msg.UDPPacket
|
||||
}
|
||||
|
||||
var sudpPathBenchmarkBytesSink []byte
|
||||
|
||||
func sudpPathBenchmarkCases(payloadSize int) []sudpPathBenchmarkCase {
|
||||
content := bytes.Repeat([]byte{0x5a}, payloadSize)
|
||||
return []sudpPathBenchmarkCase{
|
||||
{
|
||||
name: "ipv4-remote",
|
||||
packet: &msg.UDPPacket{
|
||||
Content: content,
|
||||
RemoteAddr: &net.UDPAddr{
|
||||
IP: net.ParseIP("192.0.2.1"), Port: 12345,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ipv4-local-remote",
|
||||
packet: &msg.UDPPacket{
|
||||
Content: content,
|
||||
LocalAddr: &net.UDPAddr{
|
||||
IP: net.ParseIP("192.0.2.2"), Port: 23456,
|
||||
},
|
||||
RemoteAddr: &net.UDPAddr{
|
||||
IP: net.ParseIP("192.0.2.1"), Port: 12345,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ipv6-remote",
|
||||
packet: &msg.UDPPacket{
|
||||
Content: content,
|
||||
RemoteAddr: &net.UDPAddr{
|
||||
IP: net.ParseIP("2001:db8::1"), Port: 12345,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ipv6-local-remote",
|
||||
packet: &msg.UDPPacket{
|
||||
Content: content,
|
||||
LocalAddr: &net.UDPAddr{
|
||||
IP: net.ParseIP("2001:db8::2"), Port: 23456, Zone: "bench0",
|
||||
},
|
||||
RemoteAddr: &net.UDPAddr{
|
||||
IP: net.ParseIP("2001:db8::1"), Port: 12345, Zone: "bench1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type sudpPathReadWriter struct {
|
||||
reader bytes.Reader
|
||||
}
|
||||
|
||||
func (rw *sudpPathReadWriter) Read(p []byte) (int, error) { return rw.reader.Read(p) }
|
||||
func (rw *sudpPathReadWriter) Write(p []byte) (int, error) { return len(p), nil }
|
||||
func (rw *sudpPathReadWriter) Reset(p []byte) { rw.reader.Reset(p) }
|
||||
|
||||
func sudpPathWireBytes(b testing.TB, packet *msg.UDPPacket, codec string) []byte {
|
||||
b.Helper()
|
||||
var buf bytes.Buffer
|
||||
rw, err := msg.NewUDPPacketReadWriter(&buf, wire.ProtocolV2, codec)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := rw.WriteMsg(packet); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
return append([]byte(nil), buf.Bytes()...)
|
||||
}
|
||||
|
||||
func sudpPathCopyFrame(dst, src []byte) []byte {
|
||||
copy(dst, src)
|
||||
return dst
|
||||
}
|
||||
|
||||
func BenchmarkSUDPInMemoryFrameCopy(b *testing.B) {
|
||||
// This is an in-memory copy of an already encoded frame. It is a proxy for
|
||||
// frame-size-dependent copy work, not a benchmark of libio.Join or sockets.
|
||||
for _, payloadSize := range []int{64, 512, 1200, 1472} {
|
||||
for _, tc := range sudpPathBenchmarkCases(payloadSize) {
|
||||
for _, codec := range []struct {
|
||||
name string
|
||||
value string
|
||||
}{
|
||||
{name: "json", value: ""},
|
||||
{name: "binary", value: wire.UDPPacketCodecBinary},
|
||||
} {
|
||||
b.Run(fmt.Sprintf("payload-%d/%s/%s", payloadSize, tc.name, codec.name), func(b *testing.B) {
|
||||
encoded := sudpPathWireBytes(b, tc.packet, codec.value)
|
||||
dst := make([]byte, len(encoded))
|
||||
b.SetBytes(int64(len(encoded)))
|
||||
for b.Loop() {
|
||||
dst = sudpPathCopyFrame(dst, encoded)
|
||||
}
|
||||
if !bytes.Equal(dst, encoded) {
|
||||
b.Fatal("copied frame does not match source")
|
||||
}
|
||||
sudpPathBenchmarkBytesSink = dst
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSUDPEndpointCodecPair(b *testing.B) {
|
||||
// This measures an in-memory decode and re-encode with the same codec. It
|
||||
// does not include the live SUDP server path, sockets, goroutines, or I/O.
|
||||
for _, payloadSize := range []int{64, 512, 1200, 1472} {
|
||||
for _, tc := range sudpPathBenchmarkCases(payloadSize) {
|
||||
for _, codec := range []struct {
|
||||
name string
|
||||
value string
|
||||
}{
|
||||
{name: "json", value: ""},
|
||||
{name: "binary", value: wire.UDPPacketCodecBinary},
|
||||
} {
|
||||
b.Run(fmt.Sprintf("payload-%d/%s/%s", payloadSize, tc.name, codec.name), func(b *testing.B) {
|
||||
encoded := sudpPathWireBytes(b, tc.packet, codec.value)
|
||||
from := &sudpPathReadWriter{}
|
||||
to := &bytes.Buffer{}
|
||||
fromRW, err := msg.NewUDPPacketReadWriter(from, wire.ProtocolV2, codec.value)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
toRW, err := msg.NewUDPPacketReadWriter(to, wire.ProtocolV2, codec.value)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.SetBytes(int64(len(encoded)))
|
||||
for b.Loop() {
|
||||
from.Reset(encoded)
|
||||
to.Reset()
|
||||
m, err := fromRW.ReadMsg()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := toRW.WriteMsg(m); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(to.Bytes(), encoded) {
|
||||
b.Fatalf("re-encoded packet mismatch: got %d bytes, want %d", to.Len(), len(encoded))
|
||||
}
|
||||
sudpPathBenchmarkBytesSink = to.Bytes()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSUDPMixedCodecTranscodeModel(b *testing.B) {
|
||||
// This exercises the real codec decode/re-encode pair used by the mixed
|
||||
// bridge, excluding sockets, goroutines, crypto, compression, and framing I/O.
|
||||
for _, payloadSize := range []int{64, 512, 1200, 1472} {
|
||||
for _, tc := range sudpPathBenchmarkCases(payloadSize) {
|
||||
for _, direction := range []struct {
|
||||
name string
|
||||
from string
|
||||
to string
|
||||
}{
|
||||
{name: "json-to-binary", from: "", to: wire.UDPPacketCodecBinary},
|
||||
{name: "binary-to-json", from: wire.UDPPacketCodecBinary, to: ""},
|
||||
} {
|
||||
b.Run(fmt.Sprintf("payload-%d/%s/%s", payloadSize, tc.name, direction.name), func(b *testing.B) {
|
||||
encoded := sudpPathWireBytes(b, tc.packet, direction.from)
|
||||
expected := sudpPathWireBytes(b, tc.packet, direction.to)
|
||||
from := &sudpPathReadWriter{}
|
||||
to := &bytes.Buffer{}
|
||||
fromRW, err := msg.NewUDPPacketReadWriter(from, wire.ProtocolV2, direction.from)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
toRW, err := msg.NewUDPPacketReadWriter(to, wire.ProtocolV2, direction.to)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.SetBytes(int64(len(encoded)))
|
||||
for b.Loop() {
|
||||
from.Reset(encoded)
|
||||
to.Reset()
|
||||
m, err := fromRW.ReadMsg()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := toRW.WriteMsg(m); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(to.Bytes(), expected) {
|
||||
b.Fatalf("transcoded packet mismatch: got %d bytes, want %d", to.Len(), len(expected))
|
||||
}
|
||||
sudpPathBenchmarkBytesSink = to.Bytes()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _ io.ReadWriter = (*sudpPathReadWriter)(nil)
|
||||
+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