mirror of
https://github.com/slackhq/nebula.git
synced 2025-11-22 08:24:25 +01:00
225 lines
6.2 KiB
Go
225 lines
6.2 KiB
Go
package vhostnet_test
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/gopacket/gopacket/afpacket"
|
|
"github.com/hetznercloud/virtio-go/internal/testsupport"
|
|
"github.com/hetznercloud/virtio-go/tuntap"
|
|
"github.com/hetznercloud/virtio-go/vhostnet"
|
|
"github.com/hetznercloud/virtio-go/virtio"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// Here is the general idea of how the following tests work to verify the
|
|
// correct communication with the vhost-net device within the kernel:
|
|
//
|
|
// +-----------------------------------+
|
|
// | go test running in user space |
|
|
// +-----------------------------------+
|
|
// ^ ^
|
|
// | |
|
|
// capture / write transmit / receive
|
|
// using AF_PACKET using this package
|
|
// | |
|
|
// v v
|
|
// +----------------+ +-----------+
|
|
// | tun (TAP mode) |<---->| vhost-net |
|
|
// +----------------+ +-----------+
|
|
//
|
|
|
|
func TestDevice_TransmitPacket(t *testing.T) {
|
|
testsupport.VirtrunOnly(t)
|
|
|
|
fx := NewTestFixture(t)
|
|
|
|
for _, length := range []int{64, 1514, 9014, 64100} {
|
|
t.Run(fmt.Sprintf("%d byte packet", length), func(t *testing.T) {
|
|
vnethdr, pkt := testsupport.TestPacket(t, fx.TAPDevice.MAC(), length)
|
|
|
|
// Transmit the packet over the vhost-net device.
|
|
require.NoError(t, fx.NetDevice.TransmitPacket(vnethdr, pkt))
|
|
|
|
// Check if the packet arrived at the TAP device. The virtio-net
|
|
// header should have been stripped by the TAP device.
|
|
data, _, err := fx.TPacket.ReadPacketData()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, pkt, data)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDevice_ReceivePacket(t *testing.T) {
|
|
testsupport.VirtrunOnly(t)
|
|
|
|
fx := NewTestFixture(t)
|
|
|
|
for _, length := range []int{64, 1514, 9014, 64100} {
|
|
t.Run(fmt.Sprintf("%d byte packet", length), func(t *testing.T) {
|
|
vnethdr, pkt := testsupport.TestPacket(t, fx.TAPDevice.MAC(), length)
|
|
prependedPkt := testsupport.PrependPacket(t, vnethdr, pkt)
|
|
|
|
// Write the prepended packet to the TAP device.
|
|
require.NoError(t, fx.TPacket.WritePacketData(prependedPkt))
|
|
|
|
// Try to receive the packet on the vhost-net device.
|
|
vnethdr, data, err := fx.NetDevice.ReceivePacket()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, pkt, data)
|
|
|
|
// Large packets should have been received as multiple buffers.
|
|
assert.Equal(t, (len(prependedPkt)/os.Getpagesize())+1, int(vnethdr.NumBuffers))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDevice_TransmitManyPackets(t *testing.T) {
|
|
testsupport.VirtrunOnly(t)
|
|
|
|
fx := NewTestFixture(t)
|
|
|
|
// Test with a packet which does not fit into a single memory page.
|
|
vnethdr, pkt := testsupport.TestPacket(t, fx.TAPDevice.MAC(), 9014)
|
|
|
|
const count = 1024
|
|
var received int
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Go(func() {
|
|
for range count {
|
|
err := fx.NetDevice.TransmitPacket(vnethdr, pkt)
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
}
|
|
})
|
|
wg.Go(func() {
|
|
for range count {
|
|
data, _, err := fx.TPacket.ReadPacketData()
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
assert.Equal(t, pkt, data)
|
|
received++
|
|
}
|
|
})
|
|
wg.Wait()
|
|
|
|
assert.Equal(t, count, received)
|
|
}
|
|
|
|
func TestDevice_ReceiveManyPackets(t *testing.T) {
|
|
testsupport.VirtrunOnly(t)
|
|
|
|
fx := NewTestFixture(t)
|
|
|
|
// Test with a packet which does not fit into a single memory page.
|
|
vnethdr, pkt := testsupport.TestPacket(t, fx.TAPDevice.MAC(), 9014)
|
|
prependedPkt := testsupport.PrependPacket(t, vnethdr, pkt)
|
|
|
|
const count = 1024
|
|
var received int
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Go(func() {
|
|
for range count {
|
|
err := fx.TPacket.WritePacketData(prependedPkt)
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
}
|
|
})
|
|
wg.Go(func() {
|
|
for range count {
|
|
_, data, err := fx.NetDevice.ReceivePacket()
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
assert.Equal(t, pkt, data)
|
|
received++
|
|
}
|
|
})
|
|
wg.Wait()
|
|
|
|
assert.Equal(t, count, received)
|
|
}
|
|
|
|
type TestFixture struct {
|
|
TAPDevice *tuntap.Device
|
|
NetDevice *vhostnet.Device
|
|
TPacket *afpacket.TPacket
|
|
}
|
|
|
|
func NewTestFixture(t *testing.T) *TestFixture {
|
|
testsupport.VirtrunOnly(t)
|
|
|
|
// In case something doesn't work, some more debug logging from the kernel
|
|
// modules may be very helpful.
|
|
testsupport.EnableDynamicDebug(t, "module tun")
|
|
testsupport.EnableDynamicDebug(t, "module vhost")
|
|
testsupport.EnableDynamicDebug(t, "module vhost_net")
|
|
|
|
// Make sure the Linux kernel does not send router solicitations that may
|
|
// interfere with these tests.
|
|
testsupport.SetSysctl(t, "net.ipv6.conf.all.disable_ipv6", "1")
|
|
|
|
var (
|
|
fx TestFixture
|
|
err error
|
|
)
|
|
|
|
// Create a TAP device.
|
|
fx.TAPDevice, err = tuntap.NewDevice(
|
|
tuntap.WithDeviceType(tuntap.DeviceTypeTAP),
|
|
// Helps to stop the Linux kernel from sending packets on this
|
|
// interface.
|
|
tuntap.WithInterfaceFlags(unix.IFF_NOARP),
|
|
// Packets going over this device are prepended with a virtio-net
|
|
// header. When this is not set, then packets written to the TAP device
|
|
// will be passed to the Linux network stack without their virtio-net
|
|
// header stripped.
|
|
tuntap.WithVirtioNetHdr(true),
|
|
// When writing packets into the TAP device using the RAW socket, we
|
|
// don't want the offloads to be applied by the kernel. Advertising
|
|
// offload support makes the kernel pass the offload request along to
|
|
// our vhost-net device.
|
|
tuntap.WithOffloads(unix.TUN_F_CSUM|unix.TUN_F_USO4|unix.TUN_F_USO6),
|
|
)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
assert.NoError(t, fx.TAPDevice.Close())
|
|
})
|
|
|
|
// Create a vhost-net device that uses the TAP device as the backend.
|
|
fx.NetDevice, err = vhostnet.NewDevice(
|
|
vhostnet.WithQueueSize(32),
|
|
vhostnet.WithBackendDevice(fx.TAPDevice),
|
|
)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
assert.NoError(t, fx.NetDevice.Close())
|
|
})
|
|
|
|
// Open a RAW socket to capture packets arriving at the TAP device or
|
|
// write packets into it.
|
|
fx.TPacket, err = afpacket.NewTPacket(
|
|
afpacket.SocketRaw,
|
|
afpacket.TPacketVersion3,
|
|
afpacket.OptInterface(fx.TAPDevice.Name()),
|
|
|
|
// Tell the kernel that packets written to this socket are prepended
|
|
// with a virto-net header. This is used to communicate the use of GSO
|
|
// for large packets.
|
|
afpacket.OptVNetHdrSize(virtio.NetHdrSize),
|
|
)
|
|
require.NoError(t, err)
|
|
t.Cleanup(fx.TPacket.Close)
|
|
|
|
return &fx
|
|
}
|