Files
Xray-core/proxy/shadowsocks_2022/shadowsocks_2022_test.go
T
2026-09-24 22:00:36 +08:00

795 lines
22 KiB
Go

package shadowsocks_2022_test
import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"encoding/binary"
"io"
gonet "net"
"sync"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/antireplay"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"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/routing"
. "github.com/xtls/xray-core/proxy/shadowsocks_2022"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet/stat"
)
func newTestContext() context.Context {
v, err := core.New(&core.Config{})
common.Must(err)
ctx := context.WithValue(context.Background(), core.XrayKey(1), v)
ctx = session.ContextWithInbound(ctx, &session.Inbound{})
return ctx
}
func generateRandomKey(size int) string {
b := make([]byte, size)
_, _ = rand.Read(b)
return base64.StdEncoding.EncodeToString(b)
}
func TestKDF(t *testing.T) {
// Test ParseKey
if _, err := ParseKey("", 16); err != ErrBadKey {
t.Fatalf("expected ErrBadKey for empty key, got %v", err)
}
shortKey := base64.StdEncoding.EncodeToString([]byte("short"))
if _, err := ParseKey(shortKey, 16); err != ErrBadKey {
t.Fatalf("expected ErrBadKey for short key, got %v", err)
}
exactKey := []byte("0123456789abcdef")
exactKeyB64 := base64.StdEncoding.EncodeToString(exactKey)
normExact, err := ParseKey(exactKeyB64, 16)
if err != nil || !bytes.Equal(normExact, exactKey) {
t.Fatalf("unexpected parsed exact key: %v, err: %v", normExact, err)
}
longKey := base64.StdEncoding.EncodeToString([]byte("0123456789abcdef_longer_key_for_testing"))
if _, err := ParseKey(longKey, 16); err != ErrBadKey {
t.Fatalf("expected ErrBadKey for long key, got %v", err)
}
// Test Session Subkey determinism
salt := []byte("random_salt_1234")
k1 := DeriveSessionSubKey(normExact, salt, 16)
k2 := DeriveSessionSubKey(normExact, salt, 16)
if !bytes.Equal(k1, k2) {
t.Fatal("DeriveSessionSubKey should be deterministic")
}
// Identity subkey must differ from session subkey with same inputs
idKey := DeriveIdentitySubKey(normExact, salt, 16)
if bytes.Equal(k1, idKey) {
t.Fatal("DeriveIdentitySubKey must differ from DeriveSessionSubKey")
}
// User PSK hash
h1 := DeriveUserPSKHash(normExact)
h2 := DeriveUserPSKHash(normExact)
if h1 != h2 {
t.Fatal("DeriveUserPSKHash should be deterministic")
}
}
func TestReplayFilter(t *testing.T) {
filter := antireplay.NewMapFilter[string](60)
salt1 := []byte("test_salt_111111")
salt2 := []byte("test_salt_222222")
if !filter.Check(string(salt1)) {
t.Fatal("first check on salt1 should be true")
}
if filter.Check(string(salt1)) {
t.Fatal("second check on salt1 should be false (replay detected)")
}
if !filter.Check(string(salt2)) {
t.Fatal("first check on salt2 should be true")
}
// Test SlidingWindow
var window SlidingWindow
if !window.Check(1) {
t.Fatal("packet 1 should be accepted")
}
window.Add(1)
if window.Check(1) {
t.Fatal("duplicate packet 1 should be rejected")
}
if !window.Check(100) {
t.Fatal("packet 100 should be accepted")
}
window.Add(100)
if window.Check(100) {
t.Fatal("duplicate packet 100 should be rejected")
}
if !window.Check(50) {
t.Fatal("out-of-order packet 50 within window should be accepted")
}
window.Add(50)
if window.Check(50) {
t.Fatal("duplicate packet 50 should be rejected")
}
// Check packet far behind window (> 8128)
window.Add(10000)
if window.Check(1) {
t.Fatal("packet 1 should be rejected as behind window")
}
}
func TestTCPStreamAndHandshake(t *testing.T) {
methods := []struct {
name string
keySize int
}{
{MethodAES128GCM, 16},
{MethodAES256GCM, 32},
{MethodChaCha20Poly1305, 32},
}
dest := net.TCPDestination(net.LocalHostIP, net.Port(8080))
testPayload := []byte("Hello, Shadowsocks 2022 Native Implementation!")
for _, m := range methods {
t.Run(m.name, func(t *testing.T) {
rawKey := make([]byte, m.keySize)
_, _ = rand.Read(rawKey)
method, err := GetCipherMethod(m.name)
common.Must(err)
clientConn, serverConn := gonet.Pipe()
defer clientConn.Close()
defer serverConn.Close()
var wg sync.WaitGroup
wg.Add(2)
var receivedDest net.Destination
var receivedPayload []byte
// Server goroutine
go func() {
defer wg.Done()
salt := make([]byte, method.KeySaltLength)
_, err := io.ReadFull(serverConn, salt)
common.Must(err)
sessionKey := DeriveSessionSubKey(rawKey, salt, method.KeySaltLength)
aead, err := method.NewAEAD(sessionKey)
common.Must(err)
reader := NewStreamReader(serverConn, aead)
// Read fixed chunk (11 + 16 bytes)
var fixedBuf [RequestHeaderFixedChunkLength + AEADTagSize]byte
_, err = io.ReadFull(serverConn, fixedBuf[:])
common.Must(err)
plainFixed, err := aead.Open(fixedBuf[:0], reader.Nonce(), fixedBuf[:], nil)
common.Must(err)
IncreaseNonce(reader.Nonce())
if plainFixed[0] != HeaderTypeClient {
t.Errorf("expected client header type, got %d", plainFixed[0])
}
// Read variable chunk
varLen := int(plainFixed[9])<<8 | int(plainFixed[10])
varBuf := make([]byte, varLen+AEADTagSize)
_, err = io.ReadFull(serverConn, varBuf)
common.Must(err)
plainVar, err := aead.Open(varBuf[:0], reader.Nonce(), varBuf, nil)
common.Must(err)
IncreaseNonce(reader.Nonce())
vBuf := buf.New()
vBuf.Write(plainVar)
receivedDest, err = ReadAddressPort(vBuf)
common.Must(err)
// Skip padding
var padBytes [2]byte
_, _ = vBuf.Read(padBytes[:])
padLen := int(padBytes[0])<<8 | int(padBytes[1])
vBuf.Advance(int32(padLen))
receivedPayload = make([]byte, vBuf.Len())
copy(receivedPayload, vBuf.Bytes())
vBuf.Release()
// Server sends response handshake
serverSalt := make([]byte, method.KeySaltLength)
_, _ = rand.Read(serverSalt)
respKey := DeriveSessionSubKey(rawKey, serverSalt, method.KeySaltLength)
respAead, err := method.NewAEAD(respKey)
writer := NewStreamWriter(serverConn, respAead)
_, _ = serverConn.Write(serverSalt)
fixedResp := make([]byte, 1+8+method.KeySaltLength+2)
fixedResp[0] = HeaderTypeServer
binary.BigEndian.PutUint64(fixedResp[1:9], uint64(time.Now().Unix()))
copy(fixedResp[9:9+method.KeySaltLength], salt)
binary.BigEndian.PutUint16(fixedResp[9+method.KeySaltLength:11+method.KeySaltLength], 0)
fixedChunk := respAead.Seal(nil, writer.Nonce(), fixedResp, nil)
IncreaseNonce(writer.Nonce())
_, _ = serverConn.Write(fixedChunk)
// Echo stream data
mb, err := reader.ReadMultiBuffer()
common.Must(err)
_ = writer.WriteMultiBuffer(mb)
}()
// Client goroutine
go func() {
defer wg.Done()
clientSalt, writer, err := ClientHandshake(clientConn, method, [][]byte{rawKey}, dest, testPayload)
common.Must(err)
reader, _, err := ClientVerifyServerResponse(clientConn, method, rawKey, clientSalt)
common.Must(err)
// Send additional stream data
streamData := []byte("stream chunk test")
_ = writer.WriteChunk(streamData)
mb, err := reader.ReadMultiBuffer()
common.Must(err)
if !bytes.Equal(mb[0].Bytes(), streamData) {
t.Errorf("echoed stream data mismatch: got %s, want %s", mb[0].Bytes(), streamData)
}
buf.ReleaseMulti(mb)
}()
wg.Wait()
if receivedDest.NetAddr() != dest.NetAddr() {
t.Errorf("destination mismatch: got %s, want %s", receivedDest.NetAddr(), dest.NetAddr())
}
if diff := cmp.Diff(receivedPayload, testPayload); diff != "" {
t.Errorf("payload mismatch: %s", diff)
}
})
}
}
func TestUDPCodec(t *testing.T) {
methods := []string{
MethodAES128GCM,
MethodAES256GCM,
MethodChaCha20Poly1305,
}
dest := net.UDPDestination(net.LocalHostIP, net.Port(53))
payload := []byte("DNS query payload")
for _, methodName := range methods {
t.Run(methodName, func(t *testing.T) {
method, err := GetCipherMethod(methodName)
common.Must(err)
psk := make([]byte, method.KeySaltLength)
_, _ = rand.Read(psk)
clientCodec, err := NewUDPPacketCodec(method, psk)
common.Must(err)
serverCodec, err := NewUDPServerCodec(method, psk, time.Minute)
common.Must(err)
pktBuf, err := clientCodec.EncodeClientPacket(dest, payload)
common.Must(err)
defer pktBuf.Release()
decoded, err := serverCodec.DecodePacket(pktBuf.Bytes())
common.Must(err)
if decoded.HeaderType != HeaderTypeClient {
t.Errorf("expected header type %d, got %d", HeaderTypeClient, decoded.HeaderType)
}
if decoded.Destination.Port != dest.Port {
t.Errorf("port mismatch: got %d, want %d", decoded.Destination.Port, dest.Port)
}
if !bytes.Equal(decoded.Payload, payload) {
t.Errorf("payload mismatch: got %s, want %s", decoded.Payload, payload)
}
})
}
}
func TestMultiUserManager(t *testing.T) {
masterKey := generateRandomKey(16)
userKey1 := generateRandomKey(16)
userKey2 := generateRandomKey(16)
config := &MultiUserServerConfig{
Method: MethodAES128GCM,
Key: masterKey,
Users: []*protocol.User{
{
Email: "user1@example.com",
Account: serial.ToTypedMessage(&Account{Key: userKey1}),
},
},
}
inbound, err := NewMultiServer(newTestContext(), config)
common.Must(err)
if inbound.GetUsersCount(context.Background()) != 1 {
t.Fatalf("expected 1 user, got %d", inbound.GetUsersCount(context.Background()))
}
u1 := inbound.GetUser(context.Background(), "user1@example.com")
if u1 == nil || u1.Email != "user1@example.com" {
t.Fatal("user1 not found")
}
// Add User 2
rawKey2, _ := base64.StdEncoding.DecodeString(userKey2)
u2 := &protocol.MemoryUser{
Email: "user2@example.com",
Account: &MemoryAccount{
Key: rawKey2,
},
}
err = inbound.AddUser(context.Background(), u2)
common.Must(err)
if inbound.GetUsersCount(context.Background()) != 2 {
t.Fatalf("expected 2 users, got %d", inbound.GetUsersCount(context.Background()))
}
// Remove User 1
err = inbound.RemoveUser(context.Background(), "user1@example.com")
common.Must(err)
if inbound.GetUsersCount(context.Background()) != 1 {
t.Fatalf("expected 1 user, got %d", inbound.GetUsersCount(context.Background()))
}
if inbound.GetUser(context.Background(), "user1@example.com") != nil {
t.Fatal("user1 should have been removed")
}
}
type dummyDispatcher struct {
onDispatch func(ctx context.Context, dest net.Destination) (*transport.Link, error)
}
func (d *dummyDispatcher) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) {
if d.onDispatch != nil {
return d.onDispatch(ctx, dest)
}
return nil, errors.New("not handled")
}
func (d *dummyDispatcher) DispatchLink(ctx context.Context, dest net.Destination, link *transport.Link) error {
return nil
}
func (d *dummyDispatcher) Start() error { return nil }
func (d *dummyDispatcher) Close() error { return nil }
func (d *dummyDispatcher) Type() interface{} { return routing.DispatcherType() }
type dummyStatConn struct {
gonet.Conn
}
func (c *dummyStatConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
b := buf.New()
_, err := b.ReadFrom(c.Conn)
return buf.MultiBuffer{b}, err
}
func (c *dummyStatConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
defer buf.ReleaseMulti(mb)
for _, b := range mb {
if _, err := c.Conn.Write(b.Bytes()); err != nil {
return err
}
}
return nil
}
func TestMultiUserTCPConnection(t *testing.T) {
masterKey := generateRandomKey(16)
userKey1 := generateRandomKey(16)
userKey2 := generateRandomKey(16)
config := &MultiUserServerConfig{
Method: MethodAES128GCM,
Key: masterKey,
Users: []*protocol.User{
{
Email: "user1@example.com",
Account: serial.ToTypedMessage(&Account{Key: userKey1}),
},
{
Email: "user2@example.com",
Account: serial.ToTypedMessage(&Account{Key: userKey2}),
},
},
}
testCtx := newTestContext()
inbound, err := NewMultiServer(testCtx, config)
common.Must(err)
clientConn, serverConn := gonet.Pipe()
defer clientConn.Close()
defer serverConn.Close()
dest := net.TCPDestination(net.LocalHostIP, 443)
method, err := GetCipherMethod(MethodAES128GCM)
common.Must(err)
masterRaw, _ := base64.StdEncoding.DecodeString(masterKey)
user2Raw, _ := base64.StdEncoding.DecodeString(userKey2)
clientPSKList := [][]byte{masterRaw, user2Raw}
dispatchedUserChan := make(chan string, 1)
disp := &dummyDispatcher{
onDispatch: func(ctx context.Context, d net.Destination) (*transport.Link, error) {
inbound := session.InboundFromContext(ctx)
if inbound != nil && inbound.User != nil {
dispatchedUserChan <- inbound.User.Email
}
link := &transport.Link{
Reader: buf.NewReader(bytes.NewReader(nil)),
Writer: buf.Discard,
}
return link, nil
},
}
go func() {
_ = inbound.Process(testCtx, net.Network_TCP, &dummyStatConn{Conn: serverConn}, disp)
}()
clientSalt, writer, err := ClientHandshake(clientConn, method, clientPSKList, dest, []byte("ping"))
common.Must(err)
reader, _, err := ClientVerifyServerResponse(clientConn, method, user2Raw, clientSalt)
common.Must(err)
_ = writer
_ = reader
select {
case email := <-dispatchedUserChan:
if email != "user2@example.com" {
t.Fatalf("expected user2@example.com, got %s", email)
}
case <-time.After(2 * time.Second):
t.Fatal("timeout waiting for dispatched user")
}
}
func TestUDPReaderWriter(t *testing.T) {
for _, methodName := range []string{MethodAES128GCM, MethodAES256GCM, MethodChaCha20Poly1305} {
t.Run(methodName, func(t *testing.T) {
method, err := GetCipherMethod(methodName)
common.Must(err)
rawPSK := make([]byte, method.KeySaltLength)
_, _ = rand.Read(rawPSK)
clientCodec, err := NewUDPPacketCodec(method, rawPSK)
common.Must(err)
serverCodec, err := NewUDPServerCodec(method, rawPSK, time.Minute)
common.Must(err)
dest := net.UDPDestination(net.LocalHostIP, 53)
// Client to Server
clientPacketBuf, err := clientCodec.EncodeClientPacket(dest, []byte("hello dns"))
common.Must(err)
defer clientPacketBuf.Release()
serverDecoded, err := serverCodec.DecodePacket(clientPacketBuf.Bytes())
common.Must(err)
if string(serverDecoded.Payload) != "hello dns" {
t.Fatalf("unexpected server decoded payload: %s", string(serverDecoded.Payload))
}
// Server to Client
serverPacket, err := serverCodec.EncodePacket(serverDecoded.SessionID, dest, []byte("dns response"))
common.Must(err)
clientDecoded, err := clientCodec.DecodePacket(serverPacket)
common.Must(err)
if string(clientDecoded.Payload) != "dns response" {
t.Fatalf("unexpected client decoded payload: %s", string(clientDecoded.Payload))
}
// Test UDPWriter and UDPReader pipeline
pipeR, pipeW := gonet.Pipe()
defer pipeR.Close()
defer pipeW.Close()
writer := &UDPWriter{
Writer: pipeW,
Destination: dest,
Codec: clientCodec,
}
reader := &UDPReader{
Reader: pipeR,
Codec: clientCodec,
}
go func() {
// Simulate server echoing back as server response
buf := make([]byte, 2048)
n, err := pipeR.Read(buf)
if err != nil {
return
}
dec, err := serverCodec.DecodePacket(buf[:n])
if err != nil {
return
}
resp, err := serverCodec.EncodePacket(dec.SessionID, dest, dec.Payload)
if err != nil {
return
}
_, _ = pipeW.Write(resp)
}()
b := buf.New()
b.WriteString("piped udp packet")
common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b}))
received, err := reader.ReadMultiBuffer()
common.Must(err)
if received[0].String() != "piped udp packet" {
t.Fatalf("expected 'piped udp packet', got '%s'", received[0].String())
}
})
}
}
func TestTCPRequestResponse(t *testing.T) {
for _, methodName := range []string{MethodAES128GCM, MethodAES256GCM, MethodChaCha20Poly1305} {
t.Run(methodName, func(t *testing.T) {
method, err := GetCipherMethod(methodName)
common.Must(err)
rawPSK := make([]byte, method.KeySaltLength)
_, _ = rand.Read(rawPSK)
clientConn, serverConn := gonet.Pipe()
defer clientConn.Close()
defer serverConn.Close()
clientSalt := make([]byte, method.KeySaltLength)
_, _ = rand.Read(clientSalt)
dest := net.TCPDestination(net.LocalHostIP, 80)
go func() {
// Server side: read handshake and verify clientSalt
salt := make([]byte, method.KeySaltLength)
if _, err := io.ReadFull(serverConn, salt); err != nil {
t.Errorf("server read salt error: %v", err)
return
}
sessionKey := DeriveSessionSubKey(rawPSK, salt, method.KeySaltLength)
aead, err := method.NewAEAD(sessionKey)
if err != nil {
t.Errorf("server AEAD error: %v", err)
return
}
sReader := NewStreamReader(serverConn, aead)
var fixedBuf [RequestHeaderFixedChunkLength + AEADTagSize]byte
if _, err := io.ReadFull(serverConn, fixedBuf[:]); err != nil {
t.Errorf("server read fixed error: %v", err)
return
}
plainFixed, err := aead.Open(fixedBuf[:0], sReader.Nonce(), fixedBuf[:], nil)
if err != nil {
t.Errorf("server decrypt fixed error: %v", err)
return
}
IncreaseNonce(sReader.Nonce())
varLen := int(binary.BigEndian.Uint16(plainFixed[9:11]))
varBuf := make([]byte, varLen+AEADTagSize)
if _, err := io.ReadFull(serverConn, varBuf); err != nil {
t.Errorf("server read var error: %v", err)
return
}
plainVar, err := aead.Open(varBuf[:0], sReader.Nonce(), varBuf, nil)
if err != nil {
t.Errorf("server decrypt var error: %v", err)
return
}
IncreaseNonce(sReader.Nonce())
vBuf := buf.New()
vBuf.Write(plainVar)
receivedDest, err := ReadAddressPort(vBuf)
if err != nil || receivedDest != dest {
t.Errorf("dest mismatch: %v vs %v, err: %v", receivedDest, dest, err)
return
}
// Echo client salt back to client using WriteTCPResponse
sWriter, err := WriteTCPResponse(serverConn, method, rawPSK, salt, []byte("early-reply"))
if err != nil {
t.Errorf("server response error: %v", err)
return
}
_ = sWriter
}()
bodyWriter, err := WriteTCPRequest(clientConn, method, [][]byte{rawPSK}, dest, clientSalt, nil)
common.Must(err)
_ = bodyWriter
responseReader, err := ReadTCPResponse(clientConn, method, rawPSK, clientSalt)
common.Must(err)
mb, err := responseReader.ReadMultiBuffer()
common.Must(err)
if mb[0].String() != "early-reply" {
t.Fatalf("expected early-reply, got %s", mb[0].String())
}
})
}
}
func TestUDPReplayProtection(t *testing.T) {
for _, methodName := range []string{MethodAES128GCM, MethodAES256GCM, MethodChaCha20Poly1305} {
t.Run(methodName, func(t *testing.T) {
method, err := GetCipherMethod(methodName)
common.Must(err)
rawPSK := make([]byte, method.KeySaltLength)
_, _ = rand.Read(rawPSK)
clientCodec, err := NewUDPPacketCodec(method, rawPSK)
common.Must(err)
serverCodec, err := NewUDPServerCodec(method, rawPSK, time.Minute)
common.Must(err)
dest := net.UDPDestination(net.LocalHostIP, 53)
pktBuf, err := clientCodec.EncodeClientPacket(dest, []byte("dns 1"))
common.Must(err)
defer pktBuf.Release()
rawCopy := make([]byte, pktBuf.Len())
copy(rawCopy, pktBuf.Bytes())
// First decode should succeed
_, err = serverCodec.DecodePacket(pktBuf.Bytes())
if err != nil {
t.Fatalf("first decode failed: %v", err)
}
// Replay same packet wire bytes should fail with ErrPacketIdNotUnique
_, err = serverCodec.DecodePacket(rawCopy)
if err != ErrPacketIdNotUnique {
t.Fatalf("expected ErrPacketIdNotUnique on replay, got: %v", err)
}
})
}
}
func TestServerUDPSessionStabilityAndMonotonicPacketID(t *testing.T) {
for _, methodName := range []string{MethodAES128GCM, MethodAES256GCM, MethodChaCha20Poly1305} {
t.Run(methodName, func(t *testing.T) {
method, err := GetCipherMethod(methodName)
common.Must(err)
rawPSK := make([]byte, method.KeySaltLength)
_, _ = rand.Read(rawPSK)
clientCodec, err := NewUDPPacketCodec(method, rawPSK)
common.Must(err)
serverCodec, err := NewUDPServerCodec(method, rawPSK, time.Minute)
common.Must(err)
dest := net.UDPDestination(net.LocalHostIP, 53)
// Client sends packet 1
pkt1, err := clientCodec.EncodeClientPacket(dest, []byte("request 1"))
common.Must(err)
defer pkt1.Release()
dec1, err := serverCodec.DecodePacket(pkt1.Bytes())
common.Must(err)
// Server sends response 1
resp1, err := serverCodec.EncodePacket(dec1.SessionID, dest, []byte("response 1"))
common.Must(err)
// Server sends response 2 to the same client session
resp2, err := serverCodec.EncodePacket(dec1.SessionID, dest, []byte("response 2"))
common.Must(err)
// Decode both on client
cDec1, err := clientCodec.DecodePacket(resp1)
common.Must(err)
cDec2, err := clientCodec.DecodePacket(resp2)
common.Must(err)
if cDec1.SessionID != cDec2.SessionID {
t.Fatalf("expected stable server session ID, got %d and %d", cDec1.SessionID, cDec2.SessionID)
}
if cDec2.PacketID <= cDec1.PacketID {
t.Fatalf("expected monotonically increasing packet ID, got %d then %d", cDec1.PacketID, cDec2.PacketID)
}
if string(cDec1.Payload) != "response 1" || string(cDec2.Payload) != "response 2" {
t.Fatalf("payload mismatch")
}
})
}
}
type mockDialer struct {
dial func(ctx context.Context, dest net.Destination) (stat.Connection, error)
}
func (d *mockDialer) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
if d.dial != nil {
return d.dial(ctx, dest)
}
c1, c2 := gonet.Pipe()
_ = c2.Close()
return &dummyStatConn{Conn: c1}, nil
}
func (d *mockDialer) DestIpAddress() net.IP {
return net.IP{127, 0, 0, 1}
}
func (d *mockDialer) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {}
func TestOutboundProcess(t *testing.T) {
testCtx := newTestContext()
key := generateRandomKey(16)
clientConfig := &ClientConfig{
Address: &net.IPOrDomain{Address: &net.IPOrDomain_Ip{Ip: []byte{127, 0, 0, 1}}},
Port: 1080,
Method: MethodAES128GCM,
Key: key,
}
outbound, err := NewClient(testCtx, clientConfig)
common.Must(err)
ctx := session.ContextWithOutbounds(testCtx, []*session.Outbound{
{
Target: net.TCPDestination(net.LocalHostIP, 80),
},
})
link := &transport.Link{
Reader: buf.NewReader(bytes.NewReader(nil)),
Writer: buf.Discard,
}
dialer := &mockDialer{}
err = outbound.Process(ctx, link, dialer)
if err == nil {
t.Fatal("expected error from closed mock dialer pipe, got nil")
}
}