mirror of
https://github.com/masterking32/MasterHttpRelayVPN.git
synced 2026-05-18 07:44:47 +03:00
261 lines
8.4 KiB
Go
261 lines
8.4 KiB
Go
package client
|
|
|
|
import (
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"masterhttprelayvpn/internal/config"
|
|
"masterhttprelayvpn/internal/protocol"
|
|
)
|
|
|
|
func TestSOCKSConnectionStoreDeleteClearsTransportState(t *testing.T) {
|
|
store := NewSOCKSConnectionStore()
|
|
chunkPolicy := ChunkPolicy{
|
|
MaxChunkSize: 1024,
|
|
MaxPacketsPerBatch: 4,
|
|
MaxBatchBytes: 4096,
|
|
WorkerCount: 1,
|
|
MaxQueueBytesPerSOCKS: 4096,
|
|
}
|
|
|
|
localConn, peerConn := net.Pipe()
|
|
defer peerConn.Close()
|
|
|
|
socksConn := store.New("client-session", "127.0.0.1:1000", chunkPolicy)
|
|
socksConn.LocalConn = localConn
|
|
socksConn.InitialPayload = []byte("initial-payload")
|
|
socksConn.BufferedBytes = len(socksConn.InitialPayload)
|
|
|
|
if err := socksConn.EnqueuePacket(socksConn.BuildSOCKSDataPacket([]byte("hello"), false)); err != nil {
|
|
t.Fatalf("enqueue first packet: %v", err)
|
|
}
|
|
if err := socksConn.EnqueuePacket(socksConn.BuildSOCKSDataPacket([]byte("world"), false)); err != nil {
|
|
t.Fatalf("enqueue second packet: %v", err)
|
|
}
|
|
|
|
item := socksConn.DequeuePacket()
|
|
if item == nil {
|
|
t.Fatal("expected dequeued item")
|
|
}
|
|
socksConn.MarkInFlight([]*SOCKSOutboundQueueItem{item})
|
|
|
|
store.Delete(socksConn.ID)
|
|
|
|
if got := store.Get(socksConn.ID); got != nil {
|
|
t.Fatal("expected connection to be removed from store")
|
|
}
|
|
if len(socksConn.OutboundQueue) != 0 {
|
|
t.Fatalf("expected empty outbound queue, got %d items", len(socksConn.OutboundQueue))
|
|
}
|
|
if socksConn.QueuedBytes != 0 {
|
|
t.Fatalf("expected zero queued bytes, got %d", socksConn.QueuedBytes)
|
|
}
|
|
if len(socksConn.InFlight) != 0 {
|
|
t.Fatalf("expected empty inflight map, got %d items", len(socksConn.InFlight))
|
|
}
|
|
if socksConn.InitialPayload != nil {
|
|
t.Fatal("expected initial payload to be cleared")
|
|
}
|
|
if socksConn.BufferedBytes != 0 {
|
|
t.Fatalf("expected buffered bytes to be reset, got %d", socksConn.BufferedBytes)
|
|
}
|
|
|
|
select {
|
|
case <-socksConn.closedC:
|
|
default:
|
|
t.Fatal("expected local connection close signal")
|
|
}
|
|
}
|
|
|
|
func TestSOCKSConnectionInboundReorderQueuesAndDrainsInOrder(t *testing.T) {
|
|
socksConn := &SOCKSConnection{
|
|
ConnectAccepted: true,
|
|
PendingInbound: make(map[uint64]PendingInboundPacket),
|
|
}
|
|
|
|
packet2 := protocol.NewPacket("client-session", protocol.PacketTypeSOCKSData)
|
|
packet2.SOCKSID = 1
|
|
packet2.Sequence = 2
|
|
packet2.Payload = []byte("two")
|
|
|
|
ready, duplicate, overflow := socksConn.queueInboundPacket(packet2, 8)
|
|
if duplicate || overflow {
|
|
t.Fatalf("unexpected duplicate=%t overflow=%t", duplicate, overflow)
|
|
}
|
|
if len(ready) != 0 {
|
|
t.Fatalf("expected no ready packets before gap is filled, got %d", len(ready))
|
|
}
|
|
|
|
packet1 := protocol.NewPacket("client-session", protocol.PacketTypeSOCKSData)
|
|
packet1.SOCKSID = 1
|
|
packet1.Sequence = 1
|
|
packet1.Payload = []byte("one")
|
|
|
|
ready, duplicate, overflow = socksConn.queueInboundPacket(packet1, 8)
|
|
if duplicate || overflow {
|
|
t.Fatalf("unexpected duplicate=%t overflow=%t", duplicate, overflow)
|
|
}
|
|
if len(ready) != 2 {
|
|
t.Fatalf("expected 2 ready packets after filling gap, got %d", len(ready))
|
|
}
|
|
if ready[0].Sequence != 1 || ready[1].Sequence != 2 {
|
|
t.Fatalf("expected ordered sequences [1 2], got [%d %d]", ready[0].Sequence, ready[1].Sequence)
|
|
}
|
|
}
|
|
|
|
func TestSOCKSConnectionInboundGapTimeout(t *testing.T) {
|
|
socksConn := &SOCKSConnection{
|
|
PendingInbound: make(map[uint64]PendingInboundPacket),
|
|
}
|
|
socksConn.PendingInbound[5] = PendingInboundPacket{
|
|
Packet: protocol.Packet{Sequence: 5},
|
|
QueuedAt: time.Now().Add(-2 * time.Second),
|
|
}
|
|
|
|
if !socksConn.hasExpiredInboundGap(500 * time.Millisecond) {
|
|
t.Fatal("expected inbound gap timeout to trigger")
|
|
}
|
|
if len(socksConn.PendingInbound) != 0 {
|
|
t.Fatalf("expected pending inbound buffer to be cleared, got %d items", len(socksConn.PendingInbound))
|
|
}
|
|
}
|
|
|
|
func TestSOCKSConnectionInboundDataWaitsForConnectAck(t *testing.T) {
|
|
socksConn := &SOCKSConnection{
|
|
PendingInbound: make(map[uint64]PendingInboundPacket),
|
|
}
|
|
|
|
packet1 := protocol.NewPacket("client-session", protocol.PacketTypeSOCKSData)
|
|
packet1.SOCKSID = 1
|
|
packet1.Sequence = 1
|
|
packet1.Payload = []byte("one")
|
|
|
|
ready, duplicate, overflow := socksConn.queueInboundPacket(packet1, 8)
|
|
if duplicate || overflow {
|
|
t.Fatalf("unexpected duplicate=%t overflow=%t", duplicate, overflow)
|
|
}
|
|
if len(ready) != 0 {
|
|
t.Fatalf("expected buffered packet before connect ack, got %d ready packets", len(ready))
|
|
}
|
|
|
|
socksConn.ConnectAccepted = true
|
|
ready = socksConn.activateInboundDrain()
|
|
if len(ready) != 1 {
|
|
t.Fatalf("expected 1 ready packet after connect ack, got %d", len(ready))
|
|
}
|
|
if ready[0].Sequence != 1 {
|
|
t.Fatalf("expected sequence 1, got %d", ready[0].Sequence)
|
|
}
|
|
}
|
|
|
|
func TestBuildNextBatchRotatesAcrossConnections(t *testing.T) {
|
|
cfg := config.Config{
|
|
MaxChunkSize: 1024,
|
|
MaxPacketsPerBatch: 1,
|
|
MaxBatchBytes: 4096,
|
|
WorkerCount: 1,
|
|
MaxConcurrentBatches: 1,
|
|
MaxPacketsPerSOCKSPerBatch: 1,
|
|
MuxRotateEveryBatches: 1,
|
|
MuxBurstThresholdBytes: 1024,
|
|
MaxQueueBytesPerSOCKS: 4096,
|
|
HTTPBatchRandomize: false,
|
|
}
|
|
|
|
client := New(cfg, nil)
|
|
client.chunkPolicy = newChunkPolicy(cfg)
|
|
|
|
conn1 := client.socksConnections.New(client.clientSessionKey, "127.0.0.1:1001", client.chunkPolicy)
|
|
conn2 := client.socksConnections.New(client.clientSessionKey, "127.0.0.1:1002", client.chunkPolicy)
|
|
conn3 := client.socksConnections.New(client.clientSessionKey, "127.0.0.1:1003", client.chunkPolicy)
|
|
|
|
for _, socksConn := range []*SOCKSConnection{conn1, conn2, conn3} {
|
|
if err := socksConn.EnqueuePacket(socksConn.BuildSOCKSDataPacket([]byte("x"), false)); err != nil {
|
|
t.Fatalf("enqueue packet for socks_id=%d: %v", socksConn.ID, err)
|
|
}
|
|
}
|
|
|
|
seen := make(map[uint64]bool)
|
|
for i := 0; i < 3; i++ {
|
|
connections := client.socksConnections.Snapshot()
|
|
batch, selected := client.buildNextBatch(connections, queuedBytesAcross(connections))
|
|
if len(batch.Packets) != 1 || len(selected) != 1 {
|
|
t.Fatalf("iteration %d: expected one selected packet, got packets=%d selected=%d", i, len(batch.Packets), len(selected))
|
|
}
|
|
got := batch.Packets[0].SOCKSID
|
|
if seen[got] {
|
|
t.Fatalf("iteration %d: duplicate socks_id=%d selected before all queues were drained", i, got)
|
|
}
|
|
seen[got] = true
|
|
}
|
|
if len(seen) != 3 {
|
|
t.Fatalf("expected all 3 socks connections to be selected once, got %d unique selections", len(seen))
|
|
}
|
|
}
|
|
|
|
func TestBuildNextBatchHonorsPerSOCKSPacketLimit(t *testing.T) {
|
|
cfg := config.Config{
|
|
MaxChunkSize: 1024,
|
|
MaxPacketsPerBatch: 4,
|
|
MaxBatchBytes: 4096,
|
|
WorkerCount: 2,
|
|
MaxConcurrentBatches: 2,
|
|
MaxPacketsPerSOCKSPerBatch: 1,
|
|
MuxRotateEveryBatches: 1,
|
|
MuxBurstThresholdBytes: 1024,
|
|
MaxQueueBytesPerSOCKS: 4096,
|
|
HTTPBatchRandomize: false,
|
|
}
|
|
|
|
client := New(cfg, nil)
|
|
client.chunkPolicy = newChunkPolicy(cfg)
|
|
|
|
conn1 := client.socksConnections.New(client.clientSessionKey, "127.0.0.1:1001", client.chunkPolicy)
|
|
conn2 := client.socksConnections.New(client.clientSessionKey, "127.0.0.1:1002", client.chunkPolicy)
|
|
|
|
for i := 0; i < 3; i++ {
|
|
if err := conn1.EnqueuePacket(conn1.BuildSOCKSDataPacket([]byte("a"), false)); err != nil {
|
|
t.Fatalf("enqueue conn1 packet %d: %v", i, err)
|
|
}
|
|
}
|
|
if err := conn2.EnqueuePacket(conn2.BuildSOCKSDataPacket([]byte("b"), false)); err != nil {
|
|
t.Fatalf("enqueue conn2 packet: %v", err)
|
|
}
|
|
|
|
connections := client.socksConnections.Snapshot()
|
|
batch, selected := client.buildNextBatch(connections, queuedBytesAcross(connections))
|
|
if len(batch.Packets) != 2 || len(selected) != 2 {
|
|
t.Fatalf("expected 2 selected packets, got packets=%d selected=%d", len(batch.Packets), len(selected))
|
|
}
|
|
|
|
counts := map[uint64]int{}
|
|
for _, packet := range batch.Packets {
|
|
counts[packet.SOCKSID]++
|
|
}
|
|
if counts[conn1.ID] != 1 {
|
|
t.Fatalf("expected conn1 to contribute exactly 1 packet, got %d", counts[conn1.ID])
|
|
}
|
|
if counts[conn2.ID] != 1 {
|
|
t.Fatalf("expected conn2 to contribute exactly 1 packet, got %d", counts[conn2.ID])
|
|
}
|
|
}
|
|
|
|
func TestEffectiveConcurrentBatchesUsesBurstThreshold(t *testing.T) {
|
|
cfg := config.Config{
|
|
WorkerCount: 4,
|
|
MaxConcurrentBatches: 3,
|
|
MaxPacketsPerSOCKSPerBatch: 2,
|
|
MuxRotateEveryBatches: 1,
|
|
MuxBurstThresholdBytes: 4096,
|
|
}
|
|
|
|
client := New(cfg, nil)
|
|
if got := client.effectiveConcurrentBatches(1024); got != 1 {
|
|
t.Fatalf("expected low-load concurrency of 1, got %d", got)
|
|
}
|
|
if got := client.effectiveConcurrentBatches(4096); got != 3 {
|
|
t.Fatalf("expected burst concurrency of 3, got %d", got)
|
|
}
|
|
}
|