mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
a92b9647c5
This tries to generate traffic at a rate that will saturate the receiver, without overdoing it, even in the event of packet loss. It's unrealistically more aggressive than TCP (which will back off quickly in case of packet loss) but less silly than a blind test that just generates packets as fast as it can (which can cause all the CPU to be absorbed by the transmitter, giving an incorrect impression of how much capacity the total system has). Initial indications are that a syscall about every 10 packets (TCP bulk delivery) is roughly the same speed as sending every packet through a channel. A syscall per packet is about 5x-10x slower than that. The whole tailscale wireguard-go + magicsock + packet filter combination is about 4x slower again, which is better than I thought we'd do, but probably has room for improvement. Note that in "full" tailscale, there is also a tundev read/write for every packet, effectively doubling the syscall overhead per packet. Given these numbers, it seems like read/write syscalls are only 25-40% of the total CPU time used in tailscale proper, so we do have significant non-syscall optimization work to do too. Sample output: $ GOMAXPROCS=2 go test -bench . -benchtime 5s ./cmd/tailbench goos: linux goarch: amd64 pkg: tailscale.com/cmd/tailbench cpu: Intel(R) Core(TM) i7-4785T CPU @ 2.20GHz BenchmarkTrivialNoAlloc/32-2 56340248 93.85 ns/op 340.98 MB/s 0 %lost 0 B/op 0 allocs/op BenchmarkTrivialNoAlloc/124-2 57527490 99.27 ns/op 1249.10 MB/s 0 %lost 0 B/op 0 allocs/op BenchmarkTrivialNoAlloc/1024-2 52537773 111.3 ns/op 9200.39 MB/s 0 %lost 0 B/op 0 allocs/op BenchmarkTrivial/32-2 41878063 135.6 ns/op 236.04 MB/s 0 %lost 0 B/op 0 allocs/op BenchmarkTrivial/124-2 41270439 138.4 ns/op 896.02 MB/s 0 %lost 0 B/op 0 allocs/op BenchmarkTrivial/1024-2 36337252 154.3 ns/op 6635.30 MB/s 0 %lost 0 B/op 0 allocs/op BenchmarkBlockingChannel/32-2 12171654 494.3 ns/op 64.74 MB/s 0 %lost 1791 B/op 0 allocs/op BenchmarkBlockingChannel/124-2 12149956 507.8 ns/op 244.17 MB/s 0 %lost 1792 B/op 1 allocs/op BenchmarkBlockingChannel/1024-2 11034754 528.8 ns/op 1936.42 MB/s 0 %lost 1792 B/op 1 allocs/op BenchmarkNonlockingChannel/32-2 8960622 2195 ns/op 14.58 MB/s 8.825 %lost 1792 B/op 1 allocs/op BenchmarkNonlockingChannel/124-2 3014614 2224 ns/op 55.75 MB/s 11.18 %lost 1792 B/op 1 allocs/op BenchmarkNonlockingChannel/1024-2 3234915 1688 ns/op 606.53 MB/s 3.765 %lost 1792 B/op 1 allocs/op BenchmarkDoubleChannel/32-2 8457559 764.1 ns/op 41.88 MB/s 5.945 %lost 1792 B/op 1 allocs/op BenchmarkDoubleChannel/124-2 5497726 1030 ns/op 120.38 MB/s 12.14 %lost 1792 B/op 1 allocs/op BenchmarkDoubleChannel/1024-2 7985656 1360 ns/op 752.86 MB/s 13.57 %lost 1792 B/op 1 allocs/op BenchmarkUDP/32-2 1652134 3695 ns/op 8.66 MB/s 0 %lost 176 B/op 3 allocs/op BenchmarkUDP/124-2 1621024 3765 ns/op 32.94 MB/s 0 %lost 176 B/op 3 allocs/op BenchmarkUDP/1024-2 1553750 3825 ns/op 267.72 MB/s 0 %lost 176 B/op 3 allocs/op BenchmarkTCP/32-2 11056336 503.2 ns/op 63.60 MB/s 0 %lost 0 B/op 0 allocs/op BenchmarkTCP/124-2 11074869 533.7 ns/op 232.32 MB/s 0 %lost 0 B/op 0 allocs/op BenchmarkTCP/1024-2 8934968 671.4 ns/op 1525.20 MB/s 0 %lost 0 B/op 0 allocs/op BenchmarkWireGuardTest/32-2 1403702 4547 ns/op 7.04 MB/s 14.37 %lost 467 B/op 3 allocs/op BenchmarkWireGuardTest/124-2 780645 7927 ns/op 15.64 MB/s 1.537 %lost 420 B/op 3 allocs/op BenchmarkWireGuardTest/1024-2 512671 11791 ns/op 86.85 MB/s 0.5206 %lost 411 B/op 3 allocs/op PASS ok tailscale.com/wgengine/bench 195.724s Updates #414. Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
206 lines
4.9 KiB
Go
206 lines
4.9 KiB
Go
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/tailscale/wireguard-go/tun"
|
|
"inet.af/netaddr"
|
|
|
|
"tailscale.com/net/dns"
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/types/logger"
|
|
"tailscale.com/types/netmap"
|
|
"tailscale.com/types/wgkey"
|
|
"tailscale.com/wgengine"
|
|
"tailscale.com/wgengine/filter"
|
|
"tailscale.com/wgengine/router"
|
|
"tailscale.com/wgengine/wgcfg"
|
|
)
|
|
|
|
func setupWGTest(logf logger.Logf, traf *TrafficGen, a1, a2 netaddr.IPPrefix) {
|
|
l1 := logger.WithPrefix(logf, "e1: ")
|
|
k1, err := wgcfg.NewPrivateKey()
|
|
if err != nil {
|
|
log.Fatalf("e1 NewPrivateKey: %v", err)
|
|
}
|
|
c1 := wgcfg.Config{
|
|
Name: "e1",
|
|
PrivateKey: k1,
|
|
Addresses: []netaddr.IPPrefix{a1},
|
|
}
|
|
t1 := &sourceTun{
|
|
logf: logger.WithPrefix(logf, "tun1: "),
|
|
traf: traf,
|
|
}
|
|
e1, err := wgengine.NewUserspaceEngine(l1, wgengine.Config{
|
|
Router: router.NewFake(l1),
|
|
LinkMonitor: nil,
|
|
ListenPort: 0,
|
|
Tun: t1,
|
|
})
|
|
if err != nil {
|
|
log.Fatalf("e1 init: %v", err)
|
|
}
|
|
|
|
l2 := logger.WithPrefix(logf, "e2: ")
|
|
k2, err := wgcfg.NewPrivateKey()
|
|
if err != nil {
|
|
log.Fatalf("e2 NewPrivateKey: %v", err)
|
|
}
|
|
c2 := wgcfg.Config{
|
|
Name: "e2",
|
|
PrivateKey: k2,
|
|
Addresses: []netaddr.IPPrefix{a2},
|
|
}
|
|
t2 := &sinkTun{
|
|
logf: logger.WithPrefix(logf, "tun2: "),
|
|
traf: traf,
|
|
}
|
|
e2, err := wgengine.NewUserspaceEngine(l2, wgengine.Config{
|
|
Router: router.NewFake(l2),
|
|
LinkMonitor: nil,
|
|
ListenPort: 0,
|
|
Tun: t2,
|
|
})
|
|
if err != nil {
|
|
log.Fatalf("e2 init: %v", err)
|
|
}
|
|
|
|
e1.SetFilter(filter.NewAllowAllForTest(l1))
|
|
e2.SetFilter(filter.NewAllowAllForTest(l2))
|
|
|
|
var wait sync.WaitGroup
|
|
wait.Add(2)
|
|
|
|
e1.SetStatusCallback(func(st *wgengine.Status, err error) {
|
|
if err != nil {
|
|
log.Fatalf("e1 status err: %v", err)
|
|
}
|
|
logf("e1 status: %v", *st)
|
|
|
|
var eps []string
|
|
for _, ep := range st.LocalAddrs {
|
|
eps = append(eps, ep.Addr.String())
|
|
}
|
|
|
|
n := tailcfg.Node{
|
|
ID: tailcfg.NodeID(0),
|
|
Name: "n1",
|
|
Addresses: []netaddr.IPPrefix{a1},
|
|
AllowedIPs: []netaddr.IPPrefix{a1},
|
|
Endpoints: eps,
|
|
}
|
|
e2.SetNetworkMap(&netmap.NetworkMap{
|
|
NodeKey: tailcfg.NodeKey(k2),
|
|
PrivateKey: wgkey.Private(k2),
|
|
Peers: []*tailcfg.Node{&n},
|
|
})
|
|
|
|
p := wgcfg.Peer{
|
|
PublicKey: c1.PrivateKey.Public(),
|
|
AllowedIPs: []netaddr.IPPrefix{a1},
|
|
Endpoints: strings.Join(eps, ","),
|
|
}
|
|
c2.Peers = []wgcfg.Peer{p}
|
|
e2.Reconfig(&c2, &router.Config{}, new(dns.Config))
|
|
wait.Done()
|
|
})
|
|
|
|
e2.SetStatusCallback(func(st *wgengine.Status, err error) {
|
|
if err != nil {
|
|
log.Fatalf("e2 status err: %v", err)
|
|
}
|
|
logf("e2 status: %v", *st)
|
|
|
|
var eps []string
|
|
for _, ep := range st.LocalAddrs {
|
|
eps = append(eps, ep.Addr.String())
|
|
}
|
|
|
|
n := tailcfg.Node{
|
|
ID: tailcfg.NodeID(0),
|
|
Name: "n2",
|
|
Addresses: []netaddr.IPPrefix{a2},
|
|
AllowedIPs: []netaddr.IPPrefix{a2},
|
|
Endpoints: eps,
|
|
}
|
|
e1.SetNetworkMap(&netmap.NetworkMap{
|
|
NodeKey: tailcfg.NodeKey(k1),
|
|
PrivateKey: wgkey.Private(k1),
|
|
Peers: []*tailcfg.Node{&n},
|
|
})
|
|
|
|
p := wgcfg.Peer{
|
|
PublicKey: c2.PrivateKey.Public(),
|
|
AllowedIPs: []netaddr.IPPrefix{a2},
|
|
Endpoints: strings.Join(eps, ","),
|
|
}
|
|
c1.Peers = []wgcfg.Peer{p}
|
|
e1.Reconfig(&c1, &router.Config{}, new(dns.Config))
|
|
wait.Done()
|
|
})
|
|
|
|
// Not using DERP in this test (for now?).
|
|
e1.SetDERPMap(&tailcfg.DERPMap{})
|
|
e2.SetDERPMap(&tailcfg.DERPMap{})
|
|
|
|
wait.Wait()
|
|
}
|
|
|
|
type sourceTun struct {
|
|
logf logger.Logf
|
|
traf *TrafficGen
|
|
}
|
|
|
|
func (t *sourceTun) Close() error { return nil }
|
|
func (t *sourceTun) Events() chan tun.Event { return nil }
|
|
func (t *sourceTun) File() *os.File { return nil }
|
|
func (t *sourceTun) Flush() error { return nil }
|
|
func (t *sourceTun) MTU() (int, error) { return 1500, nil }
|
|
func (t *sourceTun) Name() (string, error) { return "source", nil }
|
|
|
|
func (t *sourceTun) Write(b []byte, ofs int) (int, error) {
|
|
// Discard all writes
|
|
return len(b) - ofs, nil
|
|
}
|
|
|
|
func (t *sourceTun) Read(b []byte, ofs int) (int, error) {
|
|
// Continually generate "input" packets
|
|
n := t.traf.Generate(b, ofs)
|
|
if n == 0 {
|
|
return 0, io.EOF
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
type sinkTun struct {
|
|
logf logger.Logf
|
|
traf *TrafficGen
|
|
}
|
|
|
|
func (t *sinkTun) Close() error { return nil }
|
|
func (t *sinkTun) Events() chan tun.Event { return nil }
|
|
func (t *sinkTun) File() *os.File { return nil }
|
|
func (t *sinkTun) Flush() error { return nil }
|
|
func (t *sinkTun) MTU() (int, error) { return 1500, nil }
|
|
func (t *sinkTun) Name() (string, error) { return "sink", nil }
|
|
|
|
func (t *sinkTun) Read(b []byte, ofs int) (int, error) {
|
|
// Never returns
|
|
select {}
|
|
}
|
|
|
|
func (t *sinkTun) Write(b []byte, ofs int) (int, error) {
|
|
// Count packets, but discard them
|
|
t.traf.GotPacket(b, ofs)
|
|
return len(b) - ofs, nil
|
|
}
|