mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-23 01:27:04 +00:00
https://github.com/XTLS/Xray-core/pull/6327#issuecomment-5645958010 https://github.com/XTLS/Xray-core/pull/6754#issuecomment-5720818254 https://github.com/XTLS/Xray-core/pull/6754#issuecomment-5751585906
320 lines
7.3 KiB
Go
320 lines
7.3 KiB
Go
package finalmask_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
gonet "net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/common/net"
|
|
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
|
)
|
|
|
|
func mustSendRecvTcp(
|
|
t *testing.T,
|
|
from net.Conn,
|
|
to net.Conn,
|
|
msg []byte,
|
|
) {
|
|
t.Helper()
|
|
|
|
waitCh := make(chan error)
|
|
|
|
go func() {
|
|
_, err := from.Write(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
close(waitCh)
|
|
}()
|
|
|
|
buf := make([]byte, 1024)
|
|
n, err := io.ReadFull(to, buf[:len(msg)])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if n != len(msg) {
|
|
t.Fatalf("unexpected size: %d", n)
|
|
}
|
|
|
|
if !bytes.Equal(buf[:n], msg) {
|
|
t.Fatalf("unexpected data %q", buf[:n])
|
|
}
|
|
|
|
<-waitCh
|
|
}
|
|
|
|
type layerMaskTcp struct {
|
|
name string
|
|
mask finalmask.TCPMask
|
|
}
|
|
|
|
type failingWrapMask struct{}
|
|
|
|
func (failingWrapMask) TCP() {}
|
|
func (f failingWrapMask) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
|
|
return conn, nil
|
|
}
|
|
|
|
func (f failingWrapMask) WrapConnServer(conn net.Conn) (net.Conn, error) {
|
|
return nil, io.ErrClosedPipe
|
|
}
|
|
|
|
func TestConnReadWrite(t *testing.T) {
|
|
cases := []layerMaskTcp{
|
|
{
|
|
name: "custom",
|
|
mask: &custom.TCPConfig{
|
|
Clients: []*custom.TCPSequence{
|
|
{
|
|
Sequence: []*custom.TCPItem{
|
|
{
|
|
Packet: []byte{1},
|
|
},
|
|
{
|
|
Rand: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Servers: []*custom.TCPSequence{
|
|
{
|
|
Sequence: []*custom.TCPItem{
|
|
{
|
|
Packet: []byte{2},
|
|
},
|
|
{
|
|
Rand: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
mask := c.mask
|
|
|
|
dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
|
|
return net.Dial("tcp", dest.NetAddr())
|
|
}
|
|
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
|
|
return net.Listen("tcp", addr.String())
|
|
}
|
|
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{mask}, nil, dialTCP, listen, nil, nil)
|
|
|
|
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { listener.Close() })
|
|
|
|
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { client.Close() })
|
|
|
|
server, err := listener.Accept()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { server.Close() })
|
|
|
|
_ = client.SetDeadline(time.Now().Add(time.Second))
|
|
_ = server.SetDeadline(time.Now().Add(time.Second))
|
|
|
|
mustSendRecvTcp(t, client, server, []byte("client -> server"))
|
|
mustSendRecvTcp(t, server, client, []byte("server -> client"))
|
|
|
|
mustSendRecvTcp(t, client, server, []byte{})
|
|
mustSendRecvTcp(t, server, client, []byte{})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTCPcustomStaticHandshakeRoundTrip(t *testing.T) {
|
|
cfg := &custom.TCPConfig{
|
|
Clients: []*custom.TCPSequence{
|
|
{
|
|
Sequence: []*custom.TCPItem{
|
|
{Packet: []byte("cli")},
|
|
{Rand: 2, RandMin: 0x10, RandMax: 0x20},
|
|
},
|
|
},
|
|
},
|
|
Servers: []*custom.TCPSequence{
|
|
{
|
|
Sequence: []*custom.TCPItem{
|
|
{Packet: []byte("srv")},
|
|
{Rand: 1, RandMin: 0x30, RandMax: 0x40},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
|
|
return net.Dial("tcp", dest.NetAddr())
|
|
}
|
|
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
|
|
return net.Listen("tcp", addr.String())
|
|
}
|
|
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{cfg}, nil, dialTCP, listen, nil, nil)
|
|
|
|
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer client.Close()
|
|
|
|
server, err := listener.Accept()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer server.Close()
|
|
|
|
_ = client.SetDeadline(time.Now().Add(time.Second))
|
|
_ = server.SetDeadline(time.Now().Add(time.Second))
|
|
|
|
mustSendRecvTcp(t, client, server, []byte("custom tcp payload"))
|
|
mustSendRecvTcp(t, server, client, []byte("custom tcp response"))
|
|
}
|
|
|
|
func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) {
|
|
clientCfg := &custom.TCPConfig{
|
|
Clients: []*custom.TCPSequence{
|
|
{
|
|
Sequence: []*custom.TCPItem{
|
|
{Packet: []byte{0x01}},
|
|
},
|
|
},
|
|
},
|
|
Servers: []*custom.TCPSequence{
|
|
{
|
|
Sequence: []*custom.TCPItem{
|
|
{Packet: []byte{0x02}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
serverCfg := &custom.TCPConfig{
|
|
Clients: []*custom.TCPSequence{
|
|
{
|
|
Sequence: []*custom.TCPItem{
|
|
{Packet: []byte{0x01}},
|
|
},
|
|
},
|
|
},
|
|
Servers: []*custom.TCPSequence{
|
|
{
|
|
Sequence: []*custom.TCPItem{
|
|
{Packet: []byte{0x03}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
clientRaw, serverRaw := gonet.Pipe()
|
|
defer clientRaw.Close()
|
|
defer serverRaw.Close()
|
|
|
|
client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
server, err := serverCfg.WrapConnServer(serverRaw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_ = client.SetDeadline(time.Now().Add(time.Second))
|
|
_ = server.SetDeadline(time.Now().Add(time.Second))
|
|
|
|
writeErr := make(chan error, 1)
|
|
go func() {
|
|
_, err := client.Write([]byte("boom"))
|
|
writeErr <- err
|
|
}()
|
|
|
|
buf := make([]byte, 4)
|
|
_, readErr := server.Read(buf)
|
|
|
|
if err := <-writeErr; err == nil || !strings.Contains(err.Error(), "header auth failed") {
|
|
t.Fatalf("expected client header auth failure, got %v", err)
|
|
}
|
|
if readErr == nil {
|
|
t.Fatal("expected server read to fail")
|
|
}
|
|
if ne, ok := readErr.(net.Error); !ok || !ne.Timeout() {
|
|
t.Fatalf("expected server timeout after client auth failure, got %v", readErr)
|
|
}
|
|
}
|
|
|
|
func TestTCPWrapListenerRejectsImmediateWrapErrors(t *testing.T) {
|
|
dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
|
|
return net.Dial("tcp", dest.NetAddr())
|
|
}
|
|
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
|
|
return net.Listen("tcp", addr.String())
|
|
}
|
|
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{failingWrapMask{}}, nil, dialTCP, listen, nil, nil)
|
|
|
|
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
accepted := make(chan struct {
|
|
conn net.Conn
|
|
err error
|
|
}, 1)
|
|
go func() {
|
|
conn, err := listener.Accept()
|
|
accepted <- struct {
|
|
conn net.Conn
|
|
err error
|
|
}{conn: conn, err: err}
|
|
}()
|
|
|
|
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer client.Close()
|
|
|
|
_ = client.SetDeadline(time.Now().Add(time.Second))
|
|
|
|
writeErr := make(chan error, 1)
|
|
go func() {
|
|
_, err := client.Write([]byte("payload"))
|
|
writeErr <- err
|
|
}()
|
|
|
|
result := <-accepted
|
|
if result.err == nil {
|
|
if result.conn != nil {
|
|
result.conn.Close()
|
|
}
|
|
t.Fatal("expected wrapped listener accept to fail")
|
|
}
|
|
if result.conn != nil {
|
|
result.conn.Close()
|
|
t.Fatalf("expected no raw conn on wrapped listener failure, got %T", result.conn)
|
|
}
|
|
<-writeErr
|
|
}
|