mirror of
https://github.com/tailscale/tailscale.git
synced 2026-09-19 01:50:35 -04:00
This commit bumps the wireguard-go dependency to incorporate changes to the packet memory model and the tun.Device.Read and conn.ReceiveFunc I/O interfaces. It updates their implementations accordingly. These changes improve throughput in all measured benchmarks and reduce peak RSS in six of eight cases. The two regressions will be addressed in a follow-up commit that reduces peak RSS below the baseline measured at1e69418. That work is kept separate to simplify review. The following throughput and peak RSS benchmarks were performed with iperf3 between two Intel i5-12400 nodes running Ubuntu 24.04 (Linux 6.8). The UDP benchmarks did not use UDP GSO on the sender, so they were roughly equivalent to single packet I/O through wireguard-go. TCP/1 signifies one TCP stream; TCP/128 signifies 128 parallel TCP streams. Throughput (Mb/s) Test1e69418After Change TCP/1 10,371 11,354 +9.5% TCP/128 7,886 8,404 +6.6% UDP/1 2,111 2,853 +35.1% UDP/128 1,747 2,235 +28.0% Peak memory (VmHWM, kB) Test Side1e69418After Change TCP/1 TX 98,240 52,596 -46.5% RX 287,748 73,384 -74.5% TCP/128 TX 101,196 52,812 -47.8% RX 290,420 63,620 -78.1% UDP/1 TX 58,864 160,840 +173.2% RX 137,516 49,900 -63.7% UDP/128 TX 66,148 116,096 +75.5% RX 154,384 56,556 -63.4% Updates tailscale/corp#46716 Updates tailscale/corp#22467 Updates tailscale/corp#36989 Updates tailscale/corp#37878 Signed-off-by: Jordan Whited <jordan@tailscale.com>
144 lines
4.1 KiB
Go
144 lines
4.1 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package wgcfg
|
|
|
|
import (
|
|
"io"
|
|
"net/netip"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/tailscale/wireguard-go/conn"
|
|
"github.com/tailscale/wireguard-go/device"
|
|
"github.com/tailscale/wireguard-go/tun"
|
|
"tailscale.com/types/key"
|
|
)
|
|
|
|
func TestNewPeerLookupFunc(t *testing.T) {
|
|
k1, _ := newK()
|
|
|
|
k2, _ := newK()
|
|
ip2 := netip.MustParsePrefix("10.0.0.2/32")
|
|
|
|
k3, _ := newK()
|
|
|
|
dev := NewDevice(newNilTun(), new(noopBind), device.NewLogger(device.LogLevelError, "test"))
|
|
defer dev.Close()
|
|
|
|
// peers is the live per-peer config source, standing in for what
|
|
// LocalBackend provides via wgengine.Engine.SetPeerConfigFunc.
|
|
psk2 := device.NoisePresharedKey{1, 2, 3}
|
|
peers := map[device.NoisePublicKey]PeerConfig{
|
|
k2.Raw32(): {AllowedIPs: []netip.Prefix{ip2}, PresharedKey: psk2},
|
|
}
|
|
dev.SetPeerLookupFunc(NewPeerLookupFunc(dev.Bind(), t.Logf, func(pubk device.NoisePublicKey) (PeerConfig, bool) {
|
|
conf, ok := peers[pubk]
|
|
return conf, ok
|
|
}))
|
|
|
|
t.Run("lazy-creation", func(t *testing.T) {
|
|
// A peer known to the config source should be creatable on
|
|
// demand via LookupPeer.
|
|
if p := dev.LookupPeer(k2.Raw32()); p == nil {
|
|
t.Fatal("expected peer k2 to exist via LookupPeer")
|
|
}
|
|
got, err := dev.IpcGet()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if want := "preshared_key=0102030000000000000000000000000000000000000000000000000000000000"; !strings.Contains(got, want) {
|
|
t.Fatalf("device config does not contain %q:\n%s", want, got)
|
|
}
|
|
// An unknown peer should not be found.
|
|
if p := dev.LookupPeer(k3.Raw32()); p != nil {
|
|
t.Fatal("expected unknown peer k3 to not exist")
|
|
}
|
|
})
|
|
|
|
t.Run("remove-peer", func(t *testing.T) {
|
|
delete(peers, k2.Raw32())
|
|
dev.RemoveMatchingPeers(func(pk device.NoisePublicKey) bool {
|
|
_, ok := peers[pk]
|
|
return !ok
|
|
})
|
|
if p := dev.LookupPeer(k2.Raw32()); p != nil {
|
|
t.Fatal("expected peer k2 to not exist after removal")
|
|
}
|
|
})
|
|
|
|
t.Run("self-key-not-peer", func(t *testing.T) {
|
|
// The device's own key should not be a peer.
|
|
if p := dev.LookupPeer(k1.Raw32()); p != nil {
|
|
t.Fatal("expected own key to not be a peer")
|
|
}
|
|
})
|
|
}
|
|
|
|
func newK() (key.NodePublic, key.NodePrivate) {
|
|
k := key.NewNode()
|
|
return k.Public(), k
|
|
}
|
|
|
|
// TODO: replace with a loopback tunnel
|
|
type nilTun struct {
|
|
events chan tun.Event
|
|
closed chan struct{}
|
|
}
|
|
|
|
func newNilTun() tun.Device {
|
|
return &nilTun{
|
|
events: make(chan tun.Event),
|
|
closed: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (t *nilTun) File() *os.File { return nil }
|
|
func (t *nilTun) Flush() error { return nil }
|
|
func (t *nilTun) MTU() (int, error) { return 1420, nil }
|
|
func (t *nilTun) Name() (string, error) { return "niltun", nil }
|
|
func (t *nilTun) Events() <-chan tun.Event { return t.events }
|
|
|
|
func (t *nilTun) Read(slab []byte, packets []tun.ReadPacket) (int, error) {
|
|
<-t.closed
|
|
return 0, io.EOF
|
|
}
|
|
|
|
func (t *nilTun) Write(data [][]byte, offset int) (int, error) {
|
|
<-t.closed
|
|
return 0, io.EOF
|
|
}
|
|
|
|
func (t *nilTun) Close() error {
|
|
close(t.events)
|
|
close(t.closed)
|
|
return nil
|
|
}
|
|
|
|
func (t *nilTun) BatchSize() int { return 1 }
|
|
|
|
// A noopBind is a conn.Bind that does no actual binding work.
|
|
type noopBind struct{}
|
|
|
|
func (noopBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
|
return nil, 1, nil
|
|
}
|
|
func (noopBind) Close() error { return nil }
|
|
func (noopBind) SetMark(mark uint32) error { return nil }
|
|
func (noopBind) Send(b [][]byte, ep conn.Endpoint, offset int) error { return nil }
|
|
func (noopBind) ParseEndpoint(s string) (conn.Endpoint, error) {
|
|
return dummyEndpoint(s), nil
|
|
}
|
|
func (noopBind) BatchSize() int { return 1 }
|
|
|
|
// A dummyEndpoint is a string holding the endpoint destination.
|
|
type dummyEndpoint string
|
|
|
|
func (e dummyEndpoint) ClearSrc() {}
|
|
func (e dummyEndpoint) SrcToString() string { return "" }
|
|
func (e dummyEndpoint) DstToString() string { return string(e) }
|
|
func (e dummyEndpoint) DstToBytes() []byte { return nil }
|
|
func (e dummyEndpoint) DstIP() netip.Addr { return netip.Addr{} }
|
|
func (dummyEndpoint) SrcIP() netip.Addr { return netip.Addr{} }
|