mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 00:01:40 +01:00 
			
		
		
		
	This updates all source files to use a new standard header for copyright and license declaration. Notably, copyright no longer includes a date, and we now use the standard SPDX-License-Identifier header. This commit was done almost entirely mechanically with perl, and then some minimal manual fixes. Updates #6865 Signed-off-by: Will Norris <will@tailscale.com>
		
			
				
	
	
		
			410 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			410 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| // Create two wgengine instances and pass data through them, measuring
 | |
| // throughput, latency, and packet loss.
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"io"
 | |
| 	"log"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"net/http/pprof"
 | |
| 	"net/netip"
 | |
| 	"os"
 | |
| 	"strconv"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"tailscale.com/types/logger"
 | |
| )
 | |
| 
 | |
| const PayloadSize = 1000
 | |
| const ICMPMinSize = 24
 | |
| 
 | |
| var Addr1 = netip.MustParsePrefix("100.64.1.1/32")
 | |
| var Addr2 = netip.MustParsePrefix("100.64.1.2/32")
 | |
| 
 | |
| func main() {
 | |
| 	var logf logger.Logf = log.Printf
 | |
| 	log.SetFlags(0)
 | |
| 
 | |
| 	debugMux := newDebugMux()
 | |
| 	go runDebugServer(debugMux, "0.0.0.0:8999")
 | |
| 
 | |
| 	mode, err := strconv.Atoi(os.Args[1])
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("%q: %v", os.Args[1], err)
 | |
| 	}
 | |
| 
 | |
| 	traf := NewTrafficGen(nil)
 | |
| 
 | |
| 	// Sample test results below are using GOMAXPROCS=2 (for some
 | |
| 	// tests, including wireguard-go, higher GOMAXPROCS goes slower)
 | |
| 	// on apenwarr's old Linux box:
 | |
| 	//   Intel(R) Core(TM) i7-4785T CPU @ 2.20GHz
 | |
| 	// My 2019 Mac Mini is about 20% faster on most tests.
 | |
| 
 | |
| 	switch mode {
 | |
| 	// tx=8786325 rx=8786326 (0 = 0.00% loss) (70768.7 Mbits/sec)
 | |
| 	case 1:
 | |
| 		setupTrivialNoAllocTest(logf, traf)
 | |
| 
 | |
| 	// tx=6476293 rx=6476293 (0 = 0.00% loss) (52249.7 Mbits/sec)
 | |
| 	case 2:
 | |
| 		setupTrivialTest(logf, traf)
 | |
| 
 | |
| 	// tx=1957974 rx=1958379 (0 = 0.00% loss) (15939.8 Mbits/sec)
 | |
| 	case 11:
 | |
| 		setupBlockingChannelTest(logf, traf)
 | |
| 
 | |
| 	// tx=728621 rx=701825 (26620 = 3.65% loss) (5525.2 Mbits/sec)
 | |
| 	// (much faster on macOS??)
 | |
| 	case 12:
 | |
| 		setupNonblockingChannelTest(logf, traf)
 | |
| 
 | |
| 	// tx=1024260 rx=941098 (83334 = 8.14% loss) (7516.6 Mbits/sec)
 | |
| 	// (much faster on macOS??)
 | |
| 	case 13:
 | |
| 		setupDoubleChannelTest(logf, traf)
 | |
| 
 | |
| 	// tx=265468 rx=263189 (2279 = 0.86% loss) (2162.0 Mbits/sec)
 | |
| 	case 21:
 | |
| 		setupUDPTest(logf, traf)
 | |
| 
 | |
| 	// tx=1493580 rx=1493580 (0 = 0.00% loss) (12210.4 Mbits/sec)
 | |
| 	case 31:
 | |
| 		setupBatchTCPTest(logf, traf)
 | |
| 
 | |
| 	// tx=134236 rx=133166 (1070 = 0.80% loss) (1088.9 Mbits/sec)
 | |
| 	case 101:
 | |
| 		setupWGTest(nil, logf, traf, Addr1, Addr2)
 | |
| 
 | |
| 	default:
 | |
| 		log.Fatalf("provide a valid test number (0..n)")
 | |
| 	}
 | |
| 
 | |
| 	logf("initialized ok.")
 | |
| 	traf.Start(Addr1.Addr(), Addr2.Addr(), PayloadSize+ICMPMinSize, 0)
 | |
| 
 | |
| 	var cur, prev Snapshot
 | |
| 	var pps int64
 | |
| 	i := 0
 | |
| 	for {
 | |
| 		i += 1
 | |
| 		time.Sleep(10 * time.Millisecond)
 | |
| 
 | |
| 		if (i % 100) == 0 {
 | |
| 			prev = cur
 | |
| 			cur = traf.Snap()
 | |
| 			d := cur.Sub(prev)
 | |
| 
 | |
| 			if prev.WhenNsec == 0 {
 | |
| 				logf("tx=%-6d rx=%-6d", d.TxPackets, d.RxPackets)
 | |
| 			} else {
 | |
| 				logf("%v @%7d pkt/s", d, pps)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		pps = traf.Adjust()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func newDebugMux() *http.ServeMux {
 | |
| 	mux := http.NewServeMux()
 | |
| 	mux.HandleFunc("/debug/pprof/", pprof.Index)
 | |
| 	mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
 | |
| 	mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
 | |
| 	mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
 | |
| 	mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
 | |
| 	return mux
 | |
| }
 | |
| 
 | |
| func runDebugServer(mux *http.ServeMux, addr string) {
 | |
| 	srv := &http.Server{
 | |
| 		Addr:    addr,
 | |
| 		Handler: mux,
 | |
| 	}
 | |
| 	if err := srv.ListenAndServe(); err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // The absolute minimal test of the traffic generator: have it fill
 | |
| // a packet buffer, then absorb it again. Zero packet loss.
 | |
| func setupTrivialNoAllocTest(logf logger.Logf, traf *TrafficGen) {
 | |
| 	go func() {
 | |
| 		b := make([]byte, 1600)
 | |
| 		for {
 | |
| 			n := traf.Generate(b, 16)
 | |
| 			if n == 0 {
 | |
| 				break
 | |
| 			}
 | |
| 			traf.GotPacket(b[0:n+16], 16)
 | |
| 		}
 | |
| 	}()
 | |
| }
 | |
| 
 | |
| // Almost the same, but this time allocate a fresh buffer each time
 | |
| // through the loop. Still zero packet loss. Runs about 2/3 as fast for me.
 | |
| func setupTrivialTest(logf logger.Logf, traf *TrafficGen) {
 | |
| 	go func() {
 | |
| 		for {
 | |
| 			b := make([]byte, 1600)
 | |
| 			n := traf.Generate(b, 16)
 | |
| 			if n == 0 {
 | |
| 				break
 | |
| 			}
 | |
| 			traf.GotPacket(b[0:n+16], 16)
 | |
| 		}
 | |
| 	}()
 | |
| }
 | |
| 
 | |
| // Pass packets through a blocking channel between sender and receiver.
 | |
| // Still zero packet loss since the sender stops when the channel is full.
 | |
| // Max speed depends on channel length (I'm not sure why).
 | |
| func setupBlockingChannelTest(logf logger.Logf, traf *TrafficGen) {
 | |
| 	ch := make(chan []byte, 1000)
 | |
| 
 | |
| 	go func() {
 | |
| 		// transmitter
 | |
| 		for {
 | |
| 			b := make([]byte, 1600)
 | |
| 			n := traf.Generate(b, 16)
 | |
| 			if n == 0 {
 | |
| 				close(ch)
 | |
| 				break
 | |
| 			}
 | |
| 			ch <- b[0 : n+16]
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	go func() {
 | |
| 		// receiver
 | |
| 		for b := range ch {
 | |
| 			traf.GotPacket(b, 16)
 | |
| 		}
 | |
| 	}()
 | |
| }
 | |
| 
 | |
| // Same as setupBlockingChannelTest, but now we drop packets whenever the
 | |
| // channel is full. Max speed is about the same as the above test, but
 | |
| // now with nonzero packet loss.
 | |
| func setupNonblockingChannelTest(logf logger.Logf, traf *TrafficGen) {
 | |
| 	ch := make(chan []byte, 1000)
 | |
| 
 | |
| 	go func() {
 | |
| 		// transmitter
 | |
| 		for {
 | |
| 			b := make([]byte, 1600)
 | |
| 			n := traf.Generate(b, 16)
 | |
| 			if n == 0 {
 | |
| 				close(ch)
 | |
| 				break
 | |
| 			}
 | |
| 			select {
 | |
| 			case ch <- b[0 : n+16]:
 | |
| 			default:
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	go func() {
 | |
| 		// receiver
 | |
| 		for b := range ch {
 | |
| 			traf.GotPacket(b, 16)
 | |
| 		}
 | |
| 	}()
 | |
| }
 | |
| 
 | |
| // Same as above, but at an intermediate blocking channel and goroutine
 | |
| // to make things a little more like wireguard-go. Roughly 20% slower than
 | |
| // the single-channel version.
 | |
| func setupDoubleChannelTest(logf logger.Logf, traf *TrafficGen) {
 | |
| 	ch := make(chan []byte, 1000)
 | |
| 	ch2 := make(chan []byte, 1000)
 | |
| 
 | |
| 	go func() {
 | |
| 		// transmitter
 | |
| 		for {
 | |
| 			b := make([]byte, 1600)
 | |
| 			n := traf.Generate(b, 16)
 | |
| 			if n == 0 {
 | |
| 				close(ch)
 | |
| 				break
 | |
| 			}
 | |
| 			select {
 | |
| 			case ch <- b[0 : n+16]:
 | |
| 			default:
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	go func() {
 | |
| 		// intermediary
 | |
| 		for b := range ch {
 | |
| 			ch2 <- b
 | |
| 		}
 | |
| 		close(ch2)
 | |
| 	}()
 | |
| 
 | |
| 	go func() {
 | |
| 		// receiver
 | |
| 		for b := range ch2 {
 | |
| 			traf.GotPacket(b, 16)
 | |
| 		}
 | |
| 	}()
 | |
| }
 | |
| 
 | |
| // Instead of a channel, pass packets through a UDP socket.
 | |
| func setupUDPTest(logf logger.Logf, traf *TrafficGen) {
 | |
| 	la, err := net.ResolveUDPAddr("udp", ":0")
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("resolve: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	s1, err := net.ListenUDP("udp", la)
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("listen1: %v", err)
 | |
| 	}
 | |
| 	s2, err := net.ListenUDP("udp", la)
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("listen2: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	a2 := s2.LocalAddr()
 | |
| 
 | |
| 	// On macOS (but not Linux), you can't transmit to 0.0.0.0:port,
 | |
| 	// which is what returns from .LocalAddr() above. We have to
 | |
| 	// force it to localhost instead.
 | |
| 	a2.(*net.UDPAddr).IP = net.ParseIP("127.0.0.1")
 | |
| 
 | |
| 	s1.SetWriteBuffer(1024 * 1024)
 | |
| 	s2.SetReadBuffer(1024 * 1024)
 | |
| 
 | |
| 	go func() {
 | |
| 		// transmitter
 | |
| 		b := make([]byte, 1600)
 | |
| 		for {
 | |
| 			n := traf.Generate(b, 16)
 | |
| 			if n == 0 {
 | |
| 				break
 | |
| 			}
 | |
| 			s1.WriteTo(b[16:n+16], a2)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	go func() {
 | |
| 		// receiver
 | |
| 		b := make([]byte, 1600)
 | |
| 		for traf.Running() {
 | |
| 			// Use ReadFrom instead of Read, to be more like
 | |
| 			// how wireguard-go does it, even though we're not
 | |
| 			// going to actually look at the address.
 | |
| 			n, _, err := s2.ReadFrom(b)
 | |
| 			if err != nil {
 | |
| 				log.Fatalf("s2.Read: %v", err)
 | |
| 			}
 | |
| 			traf.GotPacket(b[:n], 0)
 | |
| 		}
 | |
| 	}()
 | |
| }
 | |
| 
 | |
| // Instead of a channel, pass packets through a TCP socket.
 | |
| // TCP is a single stream, so we can amortize one syscall across
 | |
| // multiple packets. 10x amortization seems to make it go ~10x faster,
 | |
| // as expected, getting us close to the speed of the channel tests above.
 | |
| // There's also zero packet loss.
 | |
| func setupBatchTCPTest(logf logger.Logf, traf *TrafficGen) {
 | |
| 	sl, err := net.Listen("tcp", ":0")
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("listen: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	var slCloseOnce sync.Once
 | |
| 	slClose := func() {
 | |
| 		slCloseOnce.Do(func() {
 | |
| 			sl.Close()
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	s1, err := net.Dial("tcp", sl.Addr().String())
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("dial: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	s2, err := sl.Accept()
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("accept: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	s1.(*net.TCPConn).SetWriteBuffer(1024 * 1024)
 | |
| 	s2.(*net.TCPConn).SetReadBuffer(1024 * 1024)
 | |
| 
 | |
| 	ch := make(chan int)
 | |
| 
 | |
| 	go func() {
 | |
| 		// transmitter
 | |
| 		defer slClose()
 | |
| 		defer s1.Close()
 | |
| 
 | |
| 		bs1 := bufio.NewWriterSize(s1, 1024*1024)
 | |
| 
 | |
| 		b := make([]byte, 1600)
 | |
| 		i := 0
 | |
| 		for {
 | |
| 			i += 1
 | |
| 			n := traf.Generate(b, 16)
 | |
| 			if n == 0 {
 | |
| 				break
 | |
| 			}
 | |
| 			if i == 1 {
 | |
| 				ch <- n
 | |
| 			}
 | |
| 			bs1.Write(b[16 : n+16])
 | |
| 
 | |
| 			// TODO: this is a pretty half-baked batching
 | |
| 			// function, which we'd never want to employ in
 | |
| 			// a real-life program.
 | |
| 			//
 | |
| 			// In real life, we'd probably want to flush
 | |
| 			// immediately when there are no more packets to
 | |
| 			// generate, and queue up only if we fall behind.
 | |
| 			//
 | |
| 			// In our case however, we just want to see the
 | |
| 			// technical benefits of batching 10 syscalls
 | |
| 			// into 1, so a fixed ratio makes more sense.
 | |
| 			if (i % 10) == 0 {
 | |
| 				bs1.Flush()
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	go func() {
 | |
| 		// receiver
 | |
| 		defer slClose()
 | |
| 		defer s2.Close()
 | |
| 
 | |
| 		bs2 := bufio.NewReaderSize(s2, 1024*1024)
 | |
| 
 | |
| 		// Find out the packet size (we happen to know they're
 | |
| 		// all the same size)
 | |
| 		packetSize := <-ch
 | |
| 
 | |
| 		b := make([]byte, packetSize)
 | |
| 		for traf.Running() {
 | |
| 			// TODO: can't use ReadFrom() here, which is
 | |
| 			// unfair compared to UDP. (ReadFrom for UDP
 | |
| 			// apparently allocates memory per packet, which
 | |
| 			// this test does not.)
 | |
| 			n, err := io.ReadFull(bs2, b)
 | |
| 			if err != nil {
 | |
| 				log.Fatalf("s2.Read: %v", err)
 | |
| 			}
 | |
| 			traf.GotPacket(b[:n], 0)
 | |
| 		}
 | |
| 	}()
 | |
| }
 |