mirror of
https://github.com/v2fly/v2ray-core.git
synced 2026-04-08 14:55:28 -04:00
460 lines
12 KiB
Go
460 lines
12 KiB
Go
package gvisorstack
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
|
|
"github.com/v2fly/v2ray-core/v5/common/packetswitch"
|
|
)
|
|
|
|
// fakeDevice implements packetswitch.NetworkLayerDevice for testing.
|
|
type fakeDevice struct {
|
|
mu sync.Mutex
|
|
writer packetswitch.NetworkLayerPacketWriter
|
|
writes [][]byte
|
|
closed bool
|
|
onAttach func(packetswitch.NetworkLayerPacketWriter) error
|
|
}
|
|
|
|
func (f *fakeDevice) OnAttach(w packetswitch.NetworkLayerPacketWriter) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.writer = w
|
|
if f.onAttach != nil {
|
|
return f.onAttach(w)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeDevice) Write(packet []byte) (int, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if f.closed {
|
|
return 0, errors.New("closed")
|
|
}
|
|
// make a copy to avoid aliasing test buffer.
|
|
cp := make([]byte, len(packet))
|
|
copy(cp, packet)
|
|
f.writes = append(f.writes, cp)
|
|
return len(packet), nil
|
|
}
|
|
|
|
func (f *fakeDevice) Close() error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.closed = true
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeDevice) getWriter() packetswitch.NetworkLayerPacketWriter {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.writer
|
|
}
|
|
|
|
func (f *fakeDevice) lastWrite() []byte {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if len(f.writes) == 0 {
|
|
return nil
|
|
}
|
|
return f.writes[len(f.writes)-1]
|
|
}
|
|
|
|
// fakeDispatcher implements stack.NetworkDispatcher, capturing delivered packets.
|
|
type fakeDispatcher struct {
|
|
mu sync.Mutex
|
|
protocols []tcpip.NetworkProtocolNumber
|
|
pkts [][]byte
|
|
}
|
|
|
|
func (d *fakeDispatcher) DeliverNetworkPacket(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
|
// capture packet payload safely by copying from AsSlices
|
|
slices := pkt.AsSlices()
|
|
total := 0
|
|
for _, s := range slices {
|
|
total += len(s)
|
|
}
|
|
cp := make([]byte, total)
|
|
off := 0
|
|
for _, s := range slices {
|
|
copy(cp[off:], s)
|
|
off += len(s)
|
|
}
|
|
// record
|
|
d.mu.Lock()
|
|
d.protocols = append(d.protocols, protocol)
|
|
d.pkts = append(d.pkts, cp)
|
|
d.mu.Unlock()
|
|
// release the packet
|
|
pkt.DecRef()
|
|
}
|
|
|
|
func (d *fakeDispatcher) DeliverLinkPacket(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
|
// not used in these tests
|
|
pkt.DecRef()
|
|
}
|
|
|
|
func (d *fakeDispatcher) last() (tcpip.NetworkProtocolNumber, []byte) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
if len(d.protocols) == 0 {
|
|
return 0, nil
|
|
}
|
|
return d.protocols[len(d.protocols)-1], d.pkts[len(d.pkts)-1]
|
|
}
|
|
|
|
func TestAttachAndInboundIPv4IPv6(t *testing.T) {
|
|
dev := &fakeDevice{}
|
|
a := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(context.Background(), 1500, dev)
|
|
d := &fakeDispatcher{}
|
|
// Initially not attached
|
|
if a.IsAttached() {
|
|
t.Fatal("expected not attached")
|
|
}
|
|
|
|
// Attach should call device.OnAttach and store writer
|
|
a.Attach(d)
|
|
w := dev.getWriter()
|
|
if w == nil {
|
|
t.Fatal("device did not receive writer on attach")
|
|
}
|
|
if !a.IsAttached() {
|
|
t.Fatal("expected attached after successful OnAttach")
|
|
}
|
|
|
|
// Send an IPv4 packet (first byte 0x45 = version 4, IHL 5)
|
|
ipv4pkt := []byte{0x45, 0x00, 0x00, 0x04}
|
|
if _, err := w.Write(ipv4pkt); err != nil {
|
|
t.Fatalf("writer.Write failed: %v", err)
|
|
}
|
|
// Check dispatcher received it
|
|
proto, payload := d.last()
|
|
if proto != ipv4.ProtocolNumber {
|
|
t.Fatalf("expected ipv4 protocol, got %v", proto)
|
|
}
|
|
if !reflect.DeepEqual(payload, ipv4pkt) {
|
|
t.Fatalf("unexpected payload: %v", payload)
|
|
}
|
|
|
|
// IPv6 packet (first byte 0x60)
|
|
ipv6pkt := []byte{0x60, 0x00, 0x00, 0x00}
|
|
if _, err := w.Write(ipv6pkt); err != nil {
|
|
t.Fatalf("writer.Write failed: %v", err)
|
|
}
|
|
proto2, payload2 := d.last()
|
|
if proto2 != ipv6.ProtocolNumber {
|
|
t.Fatalf("expected ipv6 protocol, got %v", proto2)
|
|
}
|
|
if !reflect.DeepEqual(payload2, ipv6pkt) {
|
|
t.Fatalf("unexpected payload: %v", payload2)
|
|
}
|
|
}
|
|
|
|
func TestInboundNonIPIsDropped(t *testing.T) {
|
|
dev := &fakeDevice{}
|
|
a := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(context.Background(), 1500, dev)
|
|
d := &fakeDispatcher{}
|
|
a.Attach(d)
|
|
w := dev.getWriter()
|
|
if w == nil {
|
|
t.Fatal("device did not receive writer on attach")
|
|
}
|
|
|
|
// Non-IP packet: first nibble 0
|
|
pkt := []byte{0x00, 0x01, 0x02}
|
|
if _, err := w.Write(pkt); err != nil {
|
|
t.Fatalf("writer.Write failed: %v", err)
|
|
}
|
|
// Dispatcher should not have any packets
|
|
proto, payload := d.last()
|
|
if payload != nil || proto != 0 {
|
|
t.Fatalf("expected no delivery for non-ip packet, got proto=%v payload=%v", proto, payload)
|
|
}
|
|
}
|
|
|
|
func makePacketBufferPayload(b []byte) *stack.PacketBuffer {
|
|
buf := buffer.MakeWithData(b)
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Payload: buf,
|
|
})
|
|
return pkt
|
|
}
|
|
|
|
func TestOutboundWritePacketsOk(t *testing.T) {
|
|
dev := &fakeDevice{}
|
|
a := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(context.Background(), 1500, dev)
|
|
|
|
// Prepare a PacketBufferList with one packet
|
|
list := stack.PacketBufferList{}
|
|
payload := []byte{0x45, 0x01, 0x02, 0x03}
|
|
pkt := makePacketBufferPayload(payload)
|
|
list.PushBack(pkt)
|
|
|
|
written, err := a.WritePackets(list)
|
|
if err != nil {
|
|
t.Fatalf("WritePackets returned error: %v", err)
|
|
}
|
|
if written != 1 {
|
|
t.Fatalf("expected 1 written, got %d", written)
|
|
}
|
|
// Device should have received the payload
|
|
lw := dev.lastWrite()
|
|
if !reflect.DeepEqual(lw, payload) {
|
|
t.Fatalf("device write mismatch: got %v, want %v", lw, payload)
|
|
}
|
|
}
|
|
|
|
func TestWritePacketsMTUExceeded(t *testing.T) {
|
|
dev := &fakeDevice{}
|
|
// mtu set to 2
|
|
a := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(context.Background(), 2, dev)
|
|
|
|
list := stack.PacketBufferList{}
|
|
payload := []byte{0x45, 0x01, 0x02} // len 3 > mtu 2
|
|
pkt := makePacketBufferPayload(payload)
|
|
list.PushBack(pkt)
|
|
|
|
written, err := a.WritePackets(list)
|
|
if err == nil {
|
|
t.Fatalf("expected error due to message too long, got nil")
|
|
}
|
|
if _, ok := err.(*tcpip.ErrMessageTooLong); !ok {
|
|
t.Fatalf("expected ErrMessageTooLong, got %T", err)
|
|
}
|
|
if written != 0 {
|
|
t.Fatalf("expected 0 written, got %d", written)
|
|
}
|
|
}
|
|
|
|
func TestCloseAndOnCloseActionAndWait(t *testing.T) {
|
|
dev := &fakeDevice{}
|
|
a := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(context.Background(), 1500, dev)
|
|
called := make(chan struct{})
|
|
a.SetOnCloseAction(func() { close(called) })
|
|
|
|
// Wait should block until Close is called. Run Wait in goroutine.
|
|
done := make(chan struct{})
|
|
go func() {
|
|
a.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
// Give goroutine a moment to start
|
|
time.Sleep(5 * time.Millisecond)
|
|
// Now close
|
|
a.Close()
|
|
|
|
// onClose action should be called
|
|
select {
|
|
case <-called:
|
|
// ok
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Fatal("onClose was not called")
|
|
}
|
|
|
|
// Wait should return
|
|
select {
|
|
case <-done:
|
|
// ok
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Fatal("Wait did not return after Close")
|
|
}
|
|
}
|
|
|
|
func TestAttachOnAttachFailLeavesNotAttached(t *testing.T) {
|
|
dev := &fakeDevice{}
|
|
// make OnAttach return error
|
|
dev.onAttach = func(w packetswitch.NetworkLayerPacketWriter) error {
|
|
return errors.New("attach fail")
|
|
}
|
|
a := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(context.Background(), 1500, dev)
|
|
d := &fakeDispatcher{}
|
|
a.Attach(d)
|
|
if a.IsAttached() {
|
|
t.Fatal("expected not attached when OnAttach fails")
|
|
}
|
|
}
|
|
|
|
func TestWritePacketsWhenNoDevice(t *testing.T) {
|
|
// Create adaptor with nil device
|
|
a := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(context.Background(), 1500, nil)
|
|
list := stack.PacketBufferList{}
|
|
payload := []byte{0x45}
|
|
pkt := makePacketBufferPayload(payload)
|
|
list.PushBack(pkt)
|
|
written, err := a.WritePackets(list)
|
|
if err == nil {
|
|
t.Fatalf("expected ErrClosedForSend, got nil")
|
|
}
|
|
if _, ok := err.(*tcpip.ErrClosedForSend); !ok {
|
|
t.Fatalf("expected ErrClosedForSend, got %T", err)
|
|
}
|
|
if written != 0 {
|
|
t.Fatalf("expected 0 written, got %d", written)
|
|
}
|
|
}
|
|
|
|
func TestSetMTUAndCapsAndHeaders(t *testing.T) {
|
|
// Create adaptor and test MTU setter
|
|
a := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(context.Background(), 1500, nil)
|
|
if a.MTU() != 1500 {
|
|
t.Fatalf("initial MTU mismatch: %d", a.MTU())
|
|
}
|
|
a.SetMTU(9000)
|
|
if a.MTU() != 9000 {
|
|
t.Fatalf("MTU not updated: %d", a.MTU())
|
|
}
|
|
|
|
// Caps and headers
|
|
if a.MaxHeaderLength() != 0 {
|
|
t.Fatalf("MaxHeaderLength expected 0, got %d", a.MaxHeaderLength())
|
|
}
|
|
if a.Capabilities() != stack.CapabilityNone {
|
|
t.Fatalf("Capabilities expected CapabilityNone, got %v", a.Capabilities())
|
|
}
|
|
if a.LinkAddress() != "" {
|
|
t.Fatalf("LinkAddress expected empty, got %v", a.LinkAddress())
|
|
}
|
|
// AddHeader/ParseHeader should not panic and parse returns true
|
|
pkt := makePacketBufferPayload([]byte{0x45})
|
|
// Should be no-op
|
|
a.AddHeader(pkt)
|
|
if !a.ParseHeader(pkt) {
|
|
t.Fatalf("ParseHeader expected true")
|
|
}
|
|
pkt.DecRef()
|
|
}
|
|
|
|
// errDevice fails after a certain number of writes to simulate partial failures.
|
|
type errDevice struct {
|
|
mu sync.Mutex
|
|
writer packetswitch.NetworkLayerPacketWriter
|
|
writes [][]byte
|
|
failAfter int
|
|
calls int
|
|
closed bool
|
|
}
|
|
|
|
func (e *errDevice) OnAttach(w packetswitch.NetworkLayerPacketWriter) error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
e.writer = w
|
|
return nil
|
|
}
|
|
|
|
func (e *errDevice) Write(packet []byte) (int, error) {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
if e.closed {
|
|
return 0, errors.New("closed")
|
|
}
|
|
e.calls++
|
|
if e.failAfter > 0 && e.calls > e.failAfter {
|
|
return 0, errors.New("injected write error")
|
|
}
|
|
cp := make([]byte, len(packet))
|
|
copy(cp, packet)
|
|
e.writes = append(e.writes, cp)
|
|
return len(packet), nil
|
|
}
|
|
|
|
func (e *errDevice) Close() error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
e.closed = true
|
|
return nil
|
|
}
|
|
|
|
func TestWritePacketsPartialOnError(t *testing.T) {
|
|
e := &errDevice{failAfter: 1}
|
|
a := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(context.Background(), 1500, e)
|
|
|
|
list := stack.PacketBufferList{}
|
|
p1 := makePacketBufferPayload([]byte{0x45, 0x01})
|
|
p2 := makePacketBufferPayload([]byte{0x45, 0x02})
|
|
list.PushBack(p1)
|
|
list.PushBack(p2)
|
|
|
|
written, err := a.WritePackets(list)
|
|
if err == nil {
|
|
t.Fatalf("expected error due to injected write error")
|
|
}
|
|
// We mapped device write errors to ErrNoBufferSpace
|
|
if _, ok := err.(*tcpip.ErrNoBufferSpace); !ok {
|
|
t.Fatalf("expected ErrNoBufferSpace, got %T", err)
|
|
}
|
|
if written != 1 {
|
|
t.Fatalf("expected 1 written, got %d", written)
|
|
}
|
|
}
|
|
|
|
func TestMultiplePacketsWrite(t *testing.T) {
|
|
dev := &fakeDevice{}
|
|
a := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(context.Background(), 1500, dev)
|
|
|
|
list := stack.PacketBufferList{}
|
|
p1 := makePacketBufferPayload([]byte{0x45, 0x01})
|
|
p2 := makePacketBufferPayload([]byte{0x45, 0x02})
|
|
p3 := makePacketBufferPayload([]byte{0x45, 0x03})
|
|
list.PushBack(p1)
|
|
list.PushBack(p2)
|
|
list.PushBack(p3)
|
|
|
|
written, err := a.WritePackets(list)
|
|
if err != nil {
|
|
t.Fatalf("WritePackets returned error: %v", err)
|
|
}
|
|
if written != 3 {
|
|
t.Fatalf("expected 3 written, got %d", written)
|
|
}
|
|
// last write should be p3
|
|
lw := dev.lastWrite()
|
|
if !reflect.DeepEqual(lw, []byte{0x45, 0x03}) {
|
|
t.Fatalf("unexpected last write: %v", lw)
|
|
}
|
|
}
|
|
|
|
func TestConcurrentWritePacketsAndClose(t *testing.T) {
|
|
dev := &fakeDevice{}
|
|
a := NewNetworkLayerDeviceToGvisorLinkEndpointAdaptor(context.Background(), 1500, dev)
|
|
|
|
wg := sync.WaitGroup{}
|
|
nWorkers := 5
|
|
nPerWorker := 50
|
|
wg.Add(nWorkers)
|
|
|
|
for i := 0; i < nWorkers; i++ {
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
for j := 0; j < nPerWorker; j++ {
|
|
list := stack.PacketBufferList{}
|
|
payload := []byte{0x45, byte(id), byte(j)}
|
|
pkt := makePacketBufferPayload(payload)
|
|
list.PushBack(pkt)
|
|
_, _ = a.WritePackets(list)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
// Close after a short delay
|
|
go func() {
|
|
time.Sleep(10 * time.Millisecond)
|
|
a.Close()
|
|
}()
|
|
|
|
wg.Wait()
|
|
// Wait should return quickly after Close
|
|
a.Wait()
|
|
}
|