mirror of
https://github.com/amnezia-vpn/amneziawg-go.git
synced 2026-10-12 22:24:18 +03:00
623 lines
14 KiB
Go
623 lines
14 KiB
Go
package conn
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"io"
|
|
"net"
|
|
"net/netip"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/net/ipv4"
|
|
"golang.org/x/net/ipv6"
|
|
)
|
|
|
|
const benchmarkConnMaxPacketSize = 65535
|
|
|
|
func BenchmarkConnEndpointDstToBytes(b *testing.B) {
|
|
cases := []struct {
|
|
name string
|
|
ep Endpoint
|
|
}{
|
|
{
|
|
name: "StdNetEndpoint/IPv4",
|
|
ep: &StdNetEndpoint{
|
|
AddrPort: netip.MustParseAddrPort("127.0.0.1:51820"),
|
|
},
|
|
},
|
|
{
|
|
name: "StdNetEndpoint/IPv6",
|
|
ep: &StdNetEndpoint{
|
|
AddrPort: netip.MustParseAddrPort("[2001:db8::1]:51820"),
|
|
},
|
|
},
|
|
{
|
|
name: "streamEndpoint/IPv4",
|
|
ep: &streamEndpoint{
|
|
dst: netip.MustParseAddrPort("127.0.0.1:51820"),
|
|
},
|
|
},
|
|
{
|
|
name: "streamEndpoint/IPv6",
|
|
ep: &streamEndpoint{
|
|
dst: netip.MustParseAddrPort("[2001:db8::1]:51820"),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
b.Run(tc.name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = tc.ep.DstToBytes()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkConnCoalesceMessages(b *testing.B) {
|
|
addr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 51820}
|
|
ep := &StdNetEndpoint{
|
|
AddrPort: addr.AddrPort(),
|
|
src: make([]byte, stickyControlSize),
|
|
}
|
|
cases := []struct {
|
|
name string
|
|
bufs [][]byte
|
|
}{
|
|
{
|
|
name: "batch8/equal_transport",
|
|
bufs: benchmarkConnEqualPayloads(8, 256),
|
|
},
|
|
{
|
|
name: "batch8/mixed_transport",
|
|
bufs: benchmarkConnMixedPayloads(8),
|
|
},
|
|
{
|
|
name: "batch64/equal_transport",
|
|
bufs: benchmarkConnEqualPayloads(64, 256),
|
|
},
|
|
{
|
|
name: "batch64/mixed_transport",
|
|
bufs: benchmarkConnMixedPayloads(64),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
b.Run(tc.name, func(b *testing.B) {
|
|
msgs := benchmarkConnMessages(len(tc.bufs), 2)
|
|
benchmarkConnRunLoop(b, benchmarkConnTotalBytes(tc.bufs), nil, func() error {
|
|
_ = coalesceMessages(addr, ep, tc.bufs, msgs, benchmarkConnSetGSOSize)
|
|
return nil
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkConnSplitCoalescedMessages(b *testing.B) {
|
|
cases := []struct {
|
|
name string
|
|
build func() []ipv6.Message
|
|
firstMsgAt int
|
|
bytesPerOp int
|
|
reset func([]ipv6.Message)
|
|
}{
|
|
{
|
|
name: "gso0",
|
|
build: func() []ipv6.Message { return benchmarkConnSplitMessages(1, 256, 0, 0) },
|
|
firstMsgAt: 0,
|
|
bytesPerOp: 256,
|
|
},
|
|
{
|
|
name: "gso16_8msgs",
|
|
build: func() []ipv6.Message { return benchmarkConnSplitMessages(8, 16, 16, 7) },
|
|
firstMsgAt: 7,
|
|
bytesPerOp: 8 * 16,
|
|
reset: func(msgs []ipv6.Message) {
|
|
for i := 0; i < len(msgs)-1; i++ {
|
|
msgs[i].N = 0
|
|
msgs[i].NN = 0
|
|
}
|
|
msgs[7].N = 8 * 16
|
|
msgs[7].NN = 2
|
|
},
|
|
},
|
|
{
|
|
name: "gso128_64msgs",
|
|
build: func() []ipv6.Message { return benchmarkConnSplitMessages(64, 128, 128, 63) },
|
|
firstMsgAt: 63,
|
|
bytesPerOp: 64 * 128,
|
|
reset: func(msgs []ipv6.Message) {
|
|
for i := 0; i < len(msgs)-1; i++ {
|
|
msgs[i].N = 0
|
|
msgs[i].NN = 0
|
|
}
|
|
msgs[63].N = 64 * 128
|
|
msgs[63].NN = 2
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
b.Run(tc.name, func(b *testing.B) {
|
|
if tc.reset == nil {
|
|
msgs := tc.build()
|
|
benchmarkConnRunLoop(b, tc.bytesPerOp, nil, func() error {
|
|
_, err := splitCoalescedMessages(msgs, tc.firstMsgAt, benchmarkConnGetGSOSize)
|
|
return err
|
|
})
|
|
return
|
|
}
|
|
|
|
const fixtureRingSize = 256
|
|
fixtures := make([][]ipv6.Message, fixtureRingSize)
|
|
for i := range fixtures {
|
|
fixtures[i] = tc.build()
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
if tc.bytesPerOp > 0 {
|
|
b.SetBytes(int64(tc.bytesPerOp))
|
|
}
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
if i > 0 && i%fixtureRingSize == 0 {
|
|
b.StopTimer()
|
|
for j := range fixtures {
|
|
tc.reset(fixtures[j])
|
|
}
|
|
b.StartTimer()
|
|
}
|
|
|
|
_, err := splitCoalescedMessages(fixtures[i%fixtureRingSize], tc.firstMsgAt, benchmarkConnGetGSOSize)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkConnStdNetBindSend(b *testing.B) {
|
|
cases := []struct {
|
|
name string
|
|
batchLen int
|
|
offload bool
|
|
}{
|
|
{name: "gso_off/batch1", batchLen: 1, offload: false},
|
|
{name: "gso_off/batch8", batchLen: 8, offload: false},
|
|
{name: "gso_on/batch8", batchLen: 8, offload: true},
|
|
{name: "gso_on/batch64", batchLen: 64, offload: true},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
b.Run(tc.name, func(b *testing.B) {
|
|
bind := NewStdNetBind().(*StdNetBind)
|
|
bind.ipv4 = &benchmarkConnUDPConn{}
|
|
bind.ipv4PC = &benchmarkConnLinuxPacketConn{}
|
|
bind.ipv4TxOffload = tc.offload
|
|
|
|
endpoint := &StdNetEndpoint{
|
|
AddrPort: netip.MustParseAddrPort("127.0.0.1:51820"),
|
|
src: make([]byte, stickyControlSize),
|
|
}
|
|
|
|
if !tc.offload {
|
|
bufs := benchmarkConnSendPayloads(tc.batchLen, 256, false)
|
|
benchmarkConnRunLoop(b, benchmarkConnTotalBytes(bufs), nil, func() error {
|
|
return bind.Send(bufs, endpoint)
|
|
})
|
|
return
|
|
}
|
|
|
|
fixtures := make([]benchmarkConnSendFixture, benchmarkConnFixtureRingSize)
|
|
for i := range fixtures {
|
|
fixtures[i] = newBenchmarkConnSendFixture(tc.batchLen, 256, true)
|
|
}
|
|
benchmarkConnRunLoopWithFixtureRing(b, benchmarkConnTotalBytes(fixtures[0].bufs), fixtures, func(f benchmarkConnSendFixture) {
|
|
benchmarkConnResetPayloadLens(f.bufs, f.lens)
|
|
}, func(f benchmarkConnSendFixture) error {
|
|
return bind.Send(f.bufs, endpoint)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkConnStdNetBindReceive(b *testing.B) {
|
|
cases := []struct {
|
|
name string
|
|
batchSize int
|
|
rxOffload bool
|
|
reader *benchmarkConnLinuxPacketConn
|
|
conn *benchmarkConnUDPConn
|
|
}{
|
|
{
|
|
name: "single",
|
|
batchSize: 1,
|
|
rxOffload: false,
|
|
reader: &benchmarkConnLinuxPacketConn{
|
|
readBatches: [][]benchmarkConnPacket{benchmarkConnBatchPackets(1, 256)},
|
|
},
|
|
conn: &benchmarkConnUDPConn{
|
|
readPayloads: [][]byte{benchmarkConnPayload(256)},
|
|
},
|
|
},
|
|
{
|
|
name: "batch",
|
|
batchSize: 8,
|
|
rxOffload: false,
|
|
reader: &benchmarkConnLinuxPacketConn{
|
|
readBatches: [][]benchmarkConnPacket{benchmarkConnBatchPackets(8, 256)},
|
|
},
|
|
conn: &benchmarkConnUDPConn{
|
|
readPayloads: [][]byte{benchmarkConnPayload(256)},
|
|
},
|
|
},
|
|
{
|
|
name: "batch_rx_offload",
|
|
batchSize: 64,
|
|
rxOffload: true,
|
|
reader: &benchmarkConnLinuxPacketConn{
|
|
readBatches: [][]benchmarkConnPacket{benchmarkConnCoalescedBatch(64, 256)},
|
|
},
|
|
conn: &benchmarkConnUDPConn{
|
|
readPayloads: [][]byte{benchmarkConnPayload(256)},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
b.Run(tc.name, func(b *testing.B) {
|
|
bind := NewStdNetBind().(*StdNetBind)
|
|
bufs := make([][]byte, tc.batchSize)
|
|
for i := range bufs {
|
|
bufs[i] = make([]byte, benchmarkConnMaxPacketSize)
|
|
}
|
|
sizes := make([]int, tc.batchSize)
|
|
eps := make([]Endpoint, tc.batchSize)
|
|
|
|
benchmarkConnRunLoop(b, tc.batchSize*256, nil, func() error {
|
|
_, err := bind.receiveIP(tc.reader, tc.conn, tc.rxOffload, bufs, sizes, eps)
|
|
return err
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func benchmarkConnRunLoop(b *testing.B, bytesPerOp int, reset func(), op func() error) {
|
|
b.Helper()
|
|
b.ReportAllocs()
|
|
if bytesPerOp > 0 {
|
|
b.SetBytes(int64(bytesPerOp))
|
|
}
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
if i > 0 && reset != nil {
|
|
b.StopTimer()
|
|
reset()
|
|
b.StartTimer()
|
|
}
|
|
if err := op(); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func benchmarkConnRunLoopWithFixtureRing[T any](b *testing.B, bytesPerOp int, fixtures []T, reset func(T), op func(T) error) {
|
|
b.Helper()
|
|
if len(fixtures) == 0 {
|
|
b.Fatal("benchmark fixture ring must not be empty")
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
if bytesPerOp > 0 {
|
|
b.SetBytes(int64(bytesPerOp))
|
|
}
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
if i > 0 && i%len(fixtures) == 0 && reset != nil {
|
|
b.StopTimer()
|
|
for _, fixture := range fixtures {
|
|
reset(fixture)
|
|
}
|
|
b.StartTimer()
|
|
}
|
|
if err := op(fixtures[i%len(fixtures)]); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func benchmarkConnPayload(size int) []byte {
|
|
payload := make([]byte, size)
|
|
for i := range payload {
|
|
payload[i] = byte(i)
|
|
}
|
|
return payload
|
|
}
|
|
|
|
func benchmarkConnEqualPayloads(count, size int) [][]byte {
|
|
bufs := make([][]byte, count)
|
|
for i := range bufs {
|
|
capacity := size
|
|
if i == 0 {
|
|
capacity = size * count
|
|
}
|
|
buf := make([]byte, size, capacity)
|
|
for j := range buf {
|
|
buf[j] = byte(j)
|
|
}
|
|
bufs[i] = buf
|
|
}
|
|
return bufs
|
|
}
|
|
|
|
func benchmarkConnMixedPayloads(count int) [][]byte {
|
|
bufs := make([][]byte, count)
|
|
for i := range bufs {
|
|
size := 256
|
|
if i%2 == 1 {
|
|
size = 1280
|
|
}
|
|
capacity := size
|
|
if i == 0 {
|
|
capacity = size * count
|
|
}
|
|
buf := make([]byte, size, capacity)
|
|
for j := range buf {
|
|
buf[j] = byte(j)
|
|
}
|
|
bufs[i] = buf
|
|
}
|
|
return bufs
|
|
}
|
|
|
|
func benchmarkConnSendPayloads(count, size int, offload bool) [][]byte {
|
|
bufs := make([][]byte, count)
|
|
for i := range bufs {
|
|
capacity := size
|
|
if offload && i == 0 {
|
|
capacity = size * count
|
|
}
|
|
buf := make([]byte, size, capacity)
|
|
for j := range buf {
|
|
buf[j] = byte(j)
|
|
}
|
|
bufs[i] = buf
|
|
}
|
|
return bufs
|
|
}
|
|
|
|
func benchmarkConnLengths(bufs [][]byte) []int {
|
|
lens := make([]int, len(bufs))
|
|
for i := range bufs {
|
|
lens[i] = len(bufs[i])
|
|
}
|
|
return lens
|
|
}
|
|
|
|
func benchmarkConnResetPayloadLens(bufs [][]byte, lens []int) {
|
|
for i := range bufs {
|
|
bufs[i] = bufs[i][:lens[i]]
|
|
}
|
|
}
|
|
|
|
const benchmarkConnFixtureRingSize = 256
|
|
|
|
type benchmarkConnSendFixture struct {
|
|
bufs [][]byte
|
|
lens []int
|
|
}
|
|
|
|
func newBenchmarkConnSendFixture(count, size int, offload bool) benchmarkConnSendFixture {
|
|
bufs := benchmarkConnSendPayloads(count, size, offload)
|
|
return benchmarkConnSendFixture{
|
|
bufs: bufs,
|
|
lens: benchmarkConnLengths(bufs),
|
|
}
|
|
}
|
|
|
|
func benchmarkConnTotalBytes(bufs [][]byte) int {
|
|
total := 0
|
|
for _, buf := range bufs {
|
|
total += len(buf)
|
|
}
|
|
return total
|
|
}
|
|
|
|
func benchmarkConnMessages(count int, oobCap int) []ipv6.Message {
|
|
msgs := make([]ipv6.Message, count)
|
|
for i := range msgs {
|
|
msgs[i].Buffers = make([][]byte, 1)
|
|
msgs[i].OOB = make([]byte, 0, oobCap)
|
|
}
|
|
return msgs
|
|
}
|
|
|
|
func benchmarkConnResetMessages(msgs []ipv6.Message) {
|
|
for i := range msgs {
|
|
msgs[i].Addr = nil
|
|
msgs[i].N = 0
|
|
msgs[i].NN = 0
|
|
msgs[i].OOB = msgs[i].OOB[:0]
|
|
msgs[i].Buffers[0] = nil
|
|
}
|
|
}
|
|
|
|
func benchmarkConnSetGSOSize(control *[]byte, gsoSize uint16) {
|
|
*control = (*control)[:cap(*control)]
|
|
binary.LittleEndian.PutUint16(*control, gsoSize)
|
|
}
|
|
|
|
func benchmarkConnGetGSOSize(control []byte) (int, error) {
|
|
if len(control) < 2 {
|
|
return 0, nil
|
|
}
|
|
return int(binary.LittleEndian.Uint16(control)), nil
|
|
}
|
|
|
|
func benchmarkConnSplitMessages(numMsgs, segmentSize, gsoSize, sourceIndex int) []ipv6.Message {
|
|
msgs := make([]ipv6.Message, numMsgs)
|
|
for i := range msgs {
|
|
bufSize := segmentSize
|
|
if i == sourceIndex {
|
|
bufSize = numMsgs * segmentSize
|
|
}
|
|
msgs[i].Buffers = [][]byte{make([]byte, bufSize)}
|
|
msgs[i].OOB = make([]byte, 0, 2)
|
|
}
|
|
for i := 0; i < numMsgs*segmentSize; i++ {
|
|
msgs[sourceIndex].Buffers[0][i] = byte(i)
|
|
}
|
|
msgs[sourceIndex].N = numMsgs * segmentSize
|
|
if gsoSize > 0 {
|
|
msgs[sourceIndex].OOB = msgs[sourceIndex].OOB[:2]
|
|
binary.LittleEndian.PutUint16(msgs[sourceIndex].OOB, uint16(gsoSize))
|
|
msgs[sourceIndex].NN = 2
|
|
}
|
|
return msgs
|
|
}
|
|
|
|
type benchmarkConnPacket struct {
|
|
payload []byte
|
|
oob []byte
|
|
}
|
|
|
|
func benchmarkConnBatchPackets(count, size int) []benchmarkConnPacket {
|
|
packets := make([]benchmarkConnPacket, count)
|
|
for i := range packets {
|
|
packets[i].payload = benchmarkConnPayload(size)
|
|
}
|
|
return packets
|
|
}
|
|
|
|
func benchmarkConnCoalescedBatch(count, segmentSize int) []benchmarkConnPacket {
|
|
payload := make([]byte, count*segmentSize)
|
|
for i := range payload {
|
|
payload[i] = byte(i)
|
|
}
|
|
oob := make([]byte, 2)
|
|
binary.LittleEndian.PutUint16(oob, uint16(segmentSize))
|
|
return []benchmarkConnPacket{
|
|
{
|
|
payload: payload,
|
|
oob: oob,
|
|
},
|
|
}
|
|
}
|
|
|
|
type benchmarkConnLinuxPacketConn struct {
|
|
readBatches [][]benchmarkConnPacket
|
|
readIndex int
|
|
}
|
|
|
|
func (c *benchmarkConnLinuxPacketConn) ResetRead() {
|
|
c.readIndex = 0
|
|
}
|
|
|
|
func (c *benchmarkConnLinuxPacketConn) ReadBatch(ms []ipv4.Message, flags int) (int, error) {
|
|
if len(c.readBatches) == 0 {
|
|
return 0, io.EOF
|
|
}
|
|
batch := c.readBatches[c.readIndex]
|
|
c.readIndex++
|
|
if c.readIndex == len(c.readBatches) {
|
|
c.readIndex = 0
|
|
}
|
|
for i := range batch {
|
|
msg := &ms[i]
|
|
packet := batch[i]
|
|
msg.N = copy(msg.Buffers[0], packet.payload)
|
|
if cap(msg.OOB) < len(packet.oob) {
|
|
msg.OOB = make([]byte, len(packet.oob))
|
|
}
|
|
msg.OOB = msg.OOB[:len(packet.oob)]
|
|
copy(msg.OOB, packet.oob)
|
|
msg.NN = len(packet.oob)
|
|
msg.Addr = &net.UDPAddr{
|
|
IP: net.IPv4(127, 0, 0, 1),
|
|
Port: 51820,
|
|
}
|
|
}
|
|
for i := len(batch); i < len(ms); i++ {
|
|
ms[i].N = 0
|
|
ms[i].NN = 0
|
|
ms[i].Addr = nil
|
|
ms[i].OOB = ms[i].OOB[:0]
|
|
}
|
|
return len(batch), nil
|
|
}
|
|
|
|
func (c *benchmarkConnLinuxPacketConn) WriteBatch(ms []ipv4.Message, flags int) (int, error) {
|
|
return len(ms), nil
|
|
}
|
|
|
|
type benchmarkConnUDPConn struct {
|
|
readPayloads [][]byte
|
|
readIndex int
|
|
}
|
|
|
|
func (c *benchmarkConnUDPConn) ResetRead() {
|
|
c.readIndex = 0
|
|
}
|
|
|
|
func (c *benchmarkConnUDPConn) ReadFrom(p []byte) (int, net.Addr, error) {
|
|
n, _, _, addr, err := c.ReadMsgUDP(p, nil)
|
|
return n, addr, err
|
|
}
|
|
|
|
func (c *benchmarkConnUDPConn) WriteTo(p []byte, addr net.Addr) (int, error) {
|
|
udpAddr, _ := addr.(*net.UDPAddr)
|
|
n, _, err := c.WriteMsgUDP(p, nil, udpAddr)
|
|
return n, err
|
|
}
|
|
|
|
func (c *benchmarkConnUDPConn) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (c *benchmarkConnUDPConn) LocalAddr() net.Addr {
|
|
return &net.UDPAddr{
|
|
IP: net.IPv4(127, 0, 0, 1),
|
|
Port: 51820,
|
|
}
|
|
}
|
|
|
|
func (c *benchmarkConnUDPConn) SetDeadline(time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *benchmarkConnUDPConn) SetReadDeadline(time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *benchmarkConnUDPConn) SetWriteDeadline(time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *benchmarkConnUDPConn) SyscallConn() (syscall.RawConn, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (c *benchmarkConnUDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
|
|
if len(c.readPayloads) == 0 {
|
|
return 0, 0, 0, nil, io.EOF
|
|
}
|
|
payload := c.readPayloads[c.readIndex]
|
|
c.readIndex++
|
|
if c.readIndex == len(c.readPayloads) {
|
|
c.readIndex = 0
|
|
}
|
|
n = copy(b, payload)
|
|
return n, 0, 0, &net.UDPAddr{
|
|
IP: net.IPv4(127, 0, 0, 1),
|
|
Port: 51820,
|
|
}, nil
|
|
}
|
|
|
|
func (c *benchmarkConnUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (int, int, error) {
|
|
return len(b), len(oob), nil
|
|
}
|