mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-29 06:26:59 +00:00
188 lines
5.8 KiB
Go
188 lines
5.8 KiB
Go
package responder_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/app/proxyman"
|
|
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
|
"github.com/xtls/xray-core/common/buf"
|
|
"github.com/xtls/xray-core/common/net"
|
|
"github.com/xtls/xray-core/common/serial"
|
|
"github.com/xtls/xray-core/common/session"
|
|
"github.com/xtls/xray-core/core"
|
|
"github.com/xtls/xray-core/features/outbound"
|
|
"github.com/xtls/xray-core/proxy/responder"
|
|
"github.com/xtls/xray-core/transport"
|
|
"github.com/xtls/xray-core/transport/pipe"
|
|
)
|
|
|
|
type writerFunc func(buf.MultiBuffer) error
|
|
|
|
func (f writerFunc) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|
return f(mb)
|
|
}
|
|
|
|
func newHandler(t *testing.T, response []byte) *responder.Handler {
|
|
t.Helper()
|
|
handler, err := responder.New(context.Background(), &responder.Config{Response: response})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return handler
|
|
}
|
|
|
|
func responseContext(network net.Network) context.Context {
|
|
return session.ContextWithOutbounds(context.Background(), []*session.Outbound{{
|
|
Target: net.Destination{Network: network, Address: net.DomainAddress("responder.invalid"), Port: 80},
|
|
}})
|
|
}
|
|
|
|
func TestResponderResponse(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
response []byte
|
|
}{
|
|
{name: "empty"},
|
|
{name: "binary", response: []byte{0, 255, 128, 1}},
|
|
{name: "large", response: bytes.Repeat([]byte("response\x00\xff"), buf.Size)},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
handler := newHandler(t, test.response)
|
|
want := string(test.response)
|
|
for _, network := range []net.Network{net.Network_TCP, net.Network_UDP} {
|
|
t.Run(network.String(), func(t *testing.T) {
|
|
for range 2 {
|
|
var output buf.MultiBufferContainer
|
|
writes := 0
|
|
writer := writerFunc(func(mb buf.MultiBuffer) error {
|
|
writes++
|
|
return output.WriteMultiBuffer(mb)
|
|
})
|
|
ctx := responseContext(network)
|
|
// No reader or dialer: the handler must respond without using either.
|
|
if err := handler.Process(ctx, &transport.Link{Writer: writer}, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if writes != 1 || output.MultiBuffer.String() != want {
|
|
t.Fatalf("writes = %d, response length = %d, want one write of %d bytes", writes, output.MultiBuffer.Len(), len(want))
|
|
}
|
|
if got := session.OutboundsFromContext(ctx)[0].Name; got != "responder" {
|
|
t.Fatalf("outbound name = %q, want responder", got)
|
|
}
|
|
if len(output.MultiBuffer) > 0 {
|
|
output.MultiBuffer[0].Bytes()[0] ^= 0xff
|
|
}
|
|
output.Close()
|
|
if string(test.response) != want {
|
|
t.Fatal("sending or modifying the response changed the configured payload")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResponderConcurrent(t *testing.T) {
|
|
response := bytes.Repeat([]byte("concurrent response\x00\xff"), buf.Size)
|
|
want := string(response)
|
|
handler := newHandler(t, response)
|
|
for i := range 8 {
|
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
t.Parallel()
|
|
var output buf.MultiBufferContainer
|
|
defer output.Close()
|
|
if err := handler.Process(responseContext(net.Network_TCP), &transport.Link{Writer: &output}, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if output.MultiBuffer.String() != want {
|
|
t.Fatal("concurrent response mismatch")
|
|
}
|
|
output.MultiBuffer[0].Bytes()[0] ^= 0xff
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResponderWriteError(t *testing.T) {
|
|
handler := newHandler(t, []byte("response"))
|
|
want := errors.New("write failed")
|
|
writer := writerFunc(func(mb buf.MultiBuffer) error {
|
|
buf.ReleaseMulti(mb)
|
|
return want
|
|
})
|
|
if err := handler.Process(responseContext(net.Network_TCP), &transport.Link{Writer: writer}, nil); !errors.Is(err, want) {
|
|
t.Fatalf("Process returned %v, want %v", err, want)
|
|
}
|
|
}
|
|
|
|
func TestResponderDispatch(t *testing.T) {
|
|
for _, response := range [][]byte{nil, bytes.Repeat([]byte("response\x00\xff"), buf.Size)} {
|
|
name := "large"
|
|
if len(response) == 0 {
|
|
name = "empty"
|
|
}
|
|
t.Run(name, func(t *testing.T) {
|
|
instance, err := core.New(&core.Config{
|
|
App: []*serial.TypedMessage{serial.ToTypedMessage(&proxyman.OutboundConfig{})},
|
|
Outbound: []*core.OutboundHandlerConfig{{
|
|
Tag: "reply",
|
|
ProxySettings: serial.ToTypedMessage(&responder.Config{Response: response}),
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer instance.Close()
|
|
manager := instance.GetFeature(outbound.ManagerType()).(outbound.Manager)
|
|
handler := manager.GetHandler("reply")
|
|
if handler == nil {
|
|
t.Fatal("responder handler not registered")
|
|
}
|
|
for _, network := range []net.Network{net.Network_TCP, net.Network_UDP} {
|
|
t.Run(network.String(), func(t *testing.T) {
|
|
input, upload := pipe.New()
|
|
output, download := pipe.New(pipe.WithoutSizeLimit())
|
|
defer input.Interrupt()
|
|
defer output.Interrupt()
|
|
done := make(chan struct{})
|
|
go func() {
|
|
handler.Dispatch(responseContext(network), &transport.Link{Reader: input, Writer: download})
|
|
close(done)
|
|
}()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("responder did not finish without incoming data")
|
|
}
|
|
// Read after Dispatch returns: normal closure must preserve queued data.
|
|
var actual []byte
|
|
for {
|
|
mb, err := output.ReadMultiBufferTimeout(time.Second)
|
|
actual = append(actual, []byte(mb.String())...)
|
|
buf.ReleaseMulti(mb)
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if !bytes.Equal(actual, response) {
|
|
t.Fatalf("response mismatch: got %d bytes, want %d", len(actual), len(response))
|
|
}
|
|
if err := upload.WriteMultiBuffer(buf.MergeBytes(nil, []byte("late request"))); err != io.ErrClosedPipe {
|
|
t.Fatalf("input was not interrupted: got %v, want %v", err, io.ErrClosedPipe)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|