// Copyright (C) 2016 The Protocol Authors. package protocol import ( "context" "crypto/tls" "encoding/binary" "net" "testing" "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/testutils" ) func BenchmarkRequestsRawTCP(b *testing.B) { // Benchmarks the rate at which we can serve requests over a single, // unencrypted TCP channel over the loopback interface. // Get a connected TCP pair conn0, conn1, err := getTCPConnectionPair() if err != nil { b.Fatal(err) } defer conn0.Close() defer conn1.Close() // Bench it benchmarkRequestsConnPair(b, conn0, conn1) } func BenchmarkRequestsTLSoTCP(b *testing.B) { conn0, conn1, err := getTCPConnectionPair() if err != nil { b.Fatal(err) } defer conn0.Close() defer conn1.Close() benchmarkRequestsTLS(b, conn0, conn1) } func benchmarkRequestsTLS(b *testing.B, conn0, conn1 net.Conn) { // Benchmarks the rate at which we can serve requests over a single, // TLS encrypted channel over the loopback interface. // Load a certificate, skipping this benchmark if it doesn't exist cert, err := tls.LoadX509KeyPair("../../test/h1/cert.pem", "../../test/h1/key.pem") if err != nil { b.Skip(err) return } /// TLSify them conn0, conn1 = negotiateTLS(cert, conn0, conn1) // Bench it benchmarkRequestsConnPair(b, conn0, conn1) } func benchmarkRequestsConnPair(b *testing.B, conn0, conn1 net.Conn) { // Start up Connections on them c0 := NewConnection(LocalDeviceID, conn0, conn0, testutils.NoopCloser{}, new(fakeModel), new(mockedConnectionInfo), CompressionMetadata) c0.Start() c1 := NewConnection(LocalDeviceID, conn1, conn1, testutils.NoopCloser{}, new(fakeModel), new(mockedConnectionInfo), CompressionMetadata) c1.Start() // Satisfy the assertions in the protocol by sending an initial cluster config c0.ClusterConfig(ClusterConfig{}) c1.ClusterConfig(ClusterConfig{}) // Report some useful stats and reset the timer for the actual test b.ReportAllocs() b.SetBytes(128 << 10) b.ResetTimer() // Request 128 KiB blocks, which will be satisfied by zero copy from the // other side (we'll get back a full block of zeroes). var buf []byte var err error for i := 0; i < b.N; i++ { // Use c0 and c1 for each alternating request, so we get as much // data flowing in both directions. if i%2 == 0 { buf, err = c0.Request(context.Background(), "folder", "file", i, int64(i), 128<<10, nil, 0, false) } else { buf, err = c1.Request(context.Background(), "folder", "file", i, int64(i), 128<<10, nil, 0, false) } if err != nil { b.Fatal(err) } if len(buf) != 128<<10 { b.Fatal("Incorrect returned buf length", len(buf), "!=", 128<<10) } // The fake model is supposed to tag the end of the buffer with the // requested offset, so we can verify that we get back data for this // block correctly. if binary.BigEndian.Uint64(buf[128<<10-8:]) != uint64(i) { b.Fatal("Bad data returned") } } } // returns the two endpoints of a TCP connection over lo0 func getTCPConnectionPair() (net.Conn, net.Conn, error) { lst, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return nil, nil, err } // We run the Accept in the background since it's blocking, and we use // the channel to make the race thingies happy about writing vs reading // conn0 and err0. var conn0 net.Conn var err0 error done := make(chan struct{}) go func() { conn0, err0 = lst.Accept() close(done) }() // Dial the connection conn1, err := net.Dial("tcp", lst.Addr().String()) if err != nil { return nil, nil, err } // Check any error from accept <-done if err0 != nil { return nil, nil, err0 } // Set the buffer sizes etc as usual dialer.SetTCPOptions(conn0) dialer.SetTCPOptions(conn1) return conn0, conn1, nil } func negotiateTLS(cert tls.Certificate, conn0, conn1 net.Conn) (net.Conn, net.Conn) { cfg := &tls.Config{ Certificates: []tls.Certificate{cert}, NextProtos: []string{"bep/1.0"}, ClientAuth: tls.RequestClientCert, SessionTicketsDisabled: true, InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, }, } tlsc0 := tls.Server(conn0, cfg) tlsc1 := tls.Client(conn1, cfg) return tlsc0, tlsc1 } // The fake model does nothing much type fakeModel struct{} func (m *fakeModel) Index(deviceID DeviceID, folder string, files []FileInfo) error { return nil } func (m *fakeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) error { return nil } func (m *fakeModel) Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) { // We write the offset to the end of the buffer, so the receiver // can verify that it did in fact get some data back over the // connection. buf := make([]byte, size) binary.BigEndian.PutUint64(buf[len(buf)-8:], uint64(offset)) return &fakeRequestResponse{buf}, nil } func (m *fakeModel) ClusterConfig(deviceID DeviceID, config ClusterConfig) error { return nil } func (m *fakeModel) Closed(conn Connection, err error) { } func (m *fakeModel) DownloadProgress(deviceID DeviceID, folder string, updates []FileDownloadProgressUpdate) error { return nil }