├── tls ├── client.key.pem ├── server.key.pem ├── client.cert.pem ├── server.cert.pem └── chain.cert.pem ├── README.md ├── perf.services.h ├── perf.tls.h ├── perf.cpp ├── perf.tcp.h ├── perf.quiche.h ├── perf.lsquic.h ├── CMakeLists.txt ├── perf.picoquic.h ├── perf.networking.h └── perf.ngtcp2.h /tls/client.key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VwBCIEIKQa1CxpsaBcSM8R9iA23gg0li1BELffj1o4Z+6QCqJt 3 | -----END PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /tls/server.key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VwBCIEII5JZMrpgISEw19BCB6mbu80AlK3jwrObdwRNuHcj/8p 3 | -----END PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # *** will fix build errors + update all libraries to current versions soon *** 2 | 3 | # quicperf 4 | 5 | this project is meant to facilitate quic server performance research and to establish absolute benchmarks of performance amongst implementations. 6 | 7 | #### **building** 8 | 9 | cd quicperf && cmake -S . -B build && cmake --build build 10 | 11 | this will create a build folder in the project directory then download and compile all dependencies and finally compile and link the binaries (**lsperf**, **picoperf**, **quicheperf**, **ngtcp2perf**, **tcpperf**) and copy them into the project directory 12 | 13 | #### **running** 14 | 15 | first run a server instance, then run a client instance. the binaries follow the below pattern: 16 | 17 | ./binary mode (client or server) networking (iouring or syscall) serverIpAddress (any, loopback, or ipv6) 18 | 19 | for example... 20 | 21 | ./picoperf server syscall loopback 22 | ./picoperf client syscall loopback 23 | 24 | #### **results** 25 | 26 | the client will print the results.... 27 | 28 | root@clr-df9e289c0de04eb2a0cfc75803a0b93e~/quicperf # ./picoperf client syscall 29 | 1.804000 seconds 30 | 4.434590 Gb/s 31 | 32 | root@clr-df9e289c0de04eb2a0cfc75803a0b93e~/quicperf # ./picoperf client iouring 33 | 2.012000 seconds 34 | 3.976143 Gb/s 35 | 36 | #### **current limitations** 37 | 38 | 1) Linux only 39 | 2) tcp+tls performance is 30% higher when letting boringssl manage the socket IO and BIOs than when doing so manually. suspect they might be avoiding copies. 40 | 3) no GRO or GSO but will be added 41 | -------------------------------------------------------------------------------- /tls/client.cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4097 (0x1001) 5 | Signature Algorithm: ED25519 6 | Issuer: C=US, ST=NY, O=Test Inc., OU=Test Intermediary CA, CN=1nt3r/emailAddress=security@test.com 7 | Validity 8 | Not Before: Jul 7 13:38:39 2021 GMT 9 | Not After : Sep 23 13:38:39 2029 GMT 10 | Subject: C=US, ST=NY, L=NYC, O=Test Inc., OU=Test Intermediary CA, CN=client/emailAddress=security@test.com 11 | Subject Public Key Info: 12 | Public Key Algorithm: ED25519 13 | ED25519 Public-Key: 14 | pub: 15 | 80:43:52:3c:02:a3:12:a1:2f:8b:5c:81:ee:22:44: 16 | cb:ff:0c:56:06:9f:30:05:d8:e0:68:3b:6b:53:bf: 17 | bf:5a 18 | X509v3 extensions: 19 | X509v3 Basic Constraints: 20 | CA:FALSE 21 | X509v3 Subject Key Identifier: 22 | 03:0C:18:75:AC:4F:5D:98:46:EE:32:74:10:59:6A:BE:29:CC:14:9F 23 | X509v3 Authority Key Identifier: 24 | keyid:6B:65:62:CD:02:D7:ED:23:B5:5A:79:BB:88:37:9A:D9:8A:20:94:D0 25 | 26 | X509v3 Key Usage: critical 27 | Digital Signature, Key Encipherment 28 | X509v3 Extended Key Usage: 29 | TLS Web Server Authentication 30 | Signature Algorithm: ED25519 31 | 2b:e8:66:51:99:0b:91:fa:af:ea:13:df:f6:49:fc:57:5e:eb: 32 | 28:b9:30:88:98:c7:7b:db:8a:93:4b:f9:c3:c9:a6:df:81:e0: 33 | 63:23:cf:3b:8d:44:33:a5:13:5a:58:87:be:c1:1c:2a:87:8a: 34 | d5:ce:46:03:04:d5:b0:78:40:0c 35 | -----BEGIN CERTIFICATE----- 36 | MIICMDCCAeKgAwIBAgICEAEwBQYDK2VwMH8xCzAJBgNVBAYTAlVTMQswCQYDVQQI 37 | DAJOWTESMBAGA1UECgwJVGVzdCBJbmMuMR0wGwYDVQQLDBRUZXN0IEludGVybWVk 38 | aWFyeSBDQTEOMAwGA1UEAwwFMW50M3IxIDAeBgkqhkiG9w0BCQEWEXNlY3VyaXR5 39 | QHRlc3QuY29tMB4XDTIxMDcwNzEzMzgzOVoXDTI5MDkyMzEzMzgzOVowgY4xCzAJ 40 | BgNVBAYTAlVTMQswCQYDVQQIDAJOWTEMMAoGA1UEBwwDTllDMRIwEAYDVQQKDAlU 41 | ZXN0IEluYy4xHTAbBgNVBAsMFFRlc3QgSW50ZXJtZWRpYXJ5IENBMQ8wDQYDVQQD 42 | DAZjbGllbnQxIDAeBgkqhkiG9w0BCQEWEXNlY3VyaXR5QHRlc3QuY29tMCowBQYD 43 | K2VwAyEAgENSPAKjEqEvi1yB7iJEy/8MVgafMAXY4Gg7a1O/v1qjcjBwMAkGA1Ud 44 | EwQCMAAwHQYDVR0OBBYEFAMMGHWsT12YRu4ydBBZar4pzBSfMB8GA1UdIwQYMBaA 45 | FGtlYs0C1+0jtVp5u4g3mtmKIJTQMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAK 46 | BggrBgEFBQcDATAFBgMrZXADQQAr6GZRmQuR+q/qE9/2SfxXXusouTCImMd724qT 47 | S/nDyabfgeBjI887jUQzpRNaWIe+wRwqh4rVzkYDBNWweEAM 48 | -----END CERTIFICATE----- 49 | -------------------------------------------------------------------------------- /perf.services.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | enum class Mode : uint16_t { 12 | 13 | client = 0b0000'0000'0000'0001, 14 | server = 0b0000'0000'0000'0010, 15 | iouring = 0b0000'0000'0000'0100, 16 | syscall = 0b0000'0000'0000'1000 17 | }; 18 | 19 | constexpr bool operator &(Mode lhs, Mode rhs) 20 | { 21 | return static_cast((static_cast(lhs) & static_cast(rhs)) == static_cast(rhs)); 22 | } 23 | 24 | constexpr Mode operator |(Mode lhs, Mode rhs) 25 | { 26 | return static_cast (static_cast(lhs) | static_cast(rhs)); 27 | } 28 | 29 | // static const char * modeToString(Mode mode) 30 | // { 31 | // if (mode & Mode::server) return "server"; 32 | // else return "client"; 33 | // } 34 | 35 | template 36 | class NetworkHub; 37 | 38 | template 39 | class QuicLibrary { 40 | public: 41 | 42 | NetworkHub *networkHub; 43 | 44 | virtual void instanceSetup(uint16_t localPort, int argc, char *argv[]) = 0; 45 | 46 | virtual void connectToServer(struct sockaddr *address) = 0; 47 | virtual void openStream(void) = 0; 48 | virtual void startPerfTest(uint64_t nBytes = 0) = 0; 49 | }; 50 | 51 | #ifdef LSPERF 52 | #include "perf.networking.h" 53 | #include "perf.tls.h" 54 | #include "perf.lsquic.h" 55 | #endif 56 | 57 | #ifdef PICOPERF 58 | #include "perf.networking.h" 59 | #include "perf.picoquic.h" 60 | #endif 61 | 62 | #ifdef QUICHEPERF 63 | #include "perf.networking.h" 64 | #include "perf.tls.h" 65 | #include "perf.quiche.h" 66 | #endif 67 | 68 | #ifdef NGTCP2PERF 69 | #include "perf.networking.h" 70 | #include "perf.tls.h" 71 | #include "perf.ngtcp2.h" 72 | #endif 73 | 74 | #ifdef TCPPERF 75 | #include "perf.tls.h" 76 | #include "perf.tcp.h" 77 | #endif 78 | 79 | template 80 | static void globalSetup(void) 81 | { 82 | int cpu_pin = sched_getcpu(); 83 | 84 | cpu_set_t affinity; 85 | CPU_SET(cpu_pin, &affinity); 86 | sched_setaffinity(0, sizeof(affinity), &affinity); 87 | 88 | #ifdef LSPERF 89 | Lsquic::globalSetup(); 90 | #endif 91 | } 92 | 93 | template 94 | static QuicLibrary* libraryForChoice(void) 95 | { 96 | #ifdef LSPERF 97 | return new Lsquic(); 98 | #endif 99 | #ifdef PICOPERF 100 | return new Picoquic(); 101 | #endif 102 | #ifdef QUICHEPERF 103 | return new Quiche(); 104 | #endif 105 | #ifdef NGTCP2PERF 106 | return new Ngtcp2(); 107 | #endif 108 | #ifdef TCPPERF 109 | return new TCPTLS(); 110 | #endif 111 | } 112 | -------------------------------------------------------------------------------- /perf.tls.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #pragma once 5 | 6 | class TLS { 7 | private: 8 | 9 | static constexpr uint8_t alpn[5] = {4, 'p', 'e', 'r', 'f'}; 10 | 11 | static int boringSSLPrintError(const char *str, size_t len, void *ctx) 12 | { 13 | printf("boringSSLPrintError -> %.*s\n", len ,str); 14 | return 1; 15 | } 16 | 17 | static int select_alpn(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) 18 | { 19 | if (SSL_select_next_proto((unsigned char **) out, outlen, in, inlen, (unsigned char *) alpn, sizeof(alpn)) == OPENSSL_NPN_NEGOTIATED) 20 | { 21 | return SSL_TLSEXT_ERR_OK; 22 | } 23 | else 24 | { 25 | return SSL_TLSEXT_ERR_ALERT_FATAL; 26 | } 27 | } 28 | 29 | static int verifyCallback(int ok, X509_STORE_CTX *store_ctx) 30 | { 31 | //printf("verifyCallback\n"); 32 | return 1; 33 | } 34 | 35 | static int certVerifyCallback(X509_STORE_CTX *store_ctx, void *ctx) 36 | { 37 | //printf("certVerifyCallback\n"); 38 | return 1; 39 | } 40 | 41 | static ssl_verify_result_t customVerifyCallback(SSL *ssl, uint8_t *out_alert) 42 | { 43 | //printf("customVerifyCallback\n"); 44 | return ssl_verify_ok; 45 | } 46 | 47 | public: 48 | 49 | static int verifyCert(void *verify_ctx, struct stack_st_X509 *chain) 50 | { 51 | //printf("lstls %s: verifyCert\n"); 52 | } 53 | 54 | static struct ssl_ctx_st* getTLSCtx(void *peer_ctx = NULL, const struct sockaddr *address = NULL) 55 | { 56 | struct ssl_ctx_st *context = SSL_CTX_new(TLS_method()); 57 | SSL_CTX_set_min_proto_version(context, TLS1_3_VERSION); 58 | 59 | //SSL_CTX_set_verify(context, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verifyCallback); 60 | //SSL_CTX_set_verify(context, SSL_VERIFY_NONE, verifyCallback); 61 | 62 | //SSL_CTX_set_cert_verify_callback(context, certVerifyCallback, NULL); 63 | 64 | //SSL_CTX_set_custom_verify(context, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, customVerifyCallback); 65 | 66 | SSL_CTX_use_certificate_file(context, tls_cert, SSL_FILETYPE_PEM); 67 | SSL_CTX_use_PrivateKey_file(context, tls_key, SSL_FILETYPE_PEM); 68 | 69 | SSL_CTX_load_verify_locations(context, tls_chain, NULL); 70 | 71 | static const int X25519Only = NID_X25519; 72 | SSL_CTX_set1_curves(context, &X25519Only, 1); 73 | 74 | static const uint16_t ED25519Only = SSL_SIGN_ED25519; 75 | SSL_CTX_set_signing_algorithm_prefs(context, &ED25519Only, 1); 76 | SSL_CTX_set_verify_algorithm_prefs(context, &ED25519Only, 1); 77 | 78 | SSL_CTX_set_alpn_protos(context, alpn, sizeof(alpn)); 79 | SSL_CTX_set_alpn_select_cb(context, select_alpn, NULL); 80 | 81 | return context; 82 | } 83 | 84 | static void printErrorsIfAny(void) 85 | { 86 | ERR_print_errors_cb(boringSSLPrintError, NULL); 87 | } 88 | }; -------------------------------------------------------------------------------- /tls/server.cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4096 (0x1000) 5 | Signature Algorithm: ED25519 6 | Issuer: C=US, ST=NY, O=Test Inc., OU=Test Intermediary CA, CN=1nt3r/emailAddress=security@test.com 7 | Validity 8 | Not Before: Jul 7 13:37:59 2021 GMT 9 | Not After : Sep 23 13:37:59 2029 GMT 10 | Subject: C=US, ST=NY, L=NYC, O=Test Inc., OU=Test Intermediary CA, CN=server/emailAddress=security@test.com 11 | Subject Public Key Info: 12 | Public Key Algorithm: ED25519 13 | ED25519 Public-Key: 14 | pub: 15 | 1e:ff:9b:b5:a6:65:1f:7d:43:1f:91:6a:5b:14:7b: 16 | f2:99:e8:a5:21:d9:a7:fd:5e:be:4a:d9:cf:8c:7a: 17 | 88:cf 18 | X509v3 extensions: 19 | X509v3 Basic Constraints: 20 | CA:FALSE 21 | X509v3 Subject Key Identifier: 22 | 4B:D8:13:D3:03:1E:80:40:4F:01:47:C0:B1:30:F3:CE:A1:69:25:36 23 | X509v3 Authority Key Identifier: 24 | keyid:6B:65:62:CD:02:D7:ED:23:B5:5A:79:BB:88:37:9A:D9:8A:20:94:D0 25 | DirName:/C=US/ST=NY/L=NYC/O=Test Inc./OU=Testing Root CA/CN=r00t/emailAddress=security@test.com 26 | serial:10:00 27 | 28 | X509v3 Key Usage: critical 29 | Digital Signature, Key Encipherment 30 | X509v3 Extended Key Usage: 31 | TLS Web Client Authentication 32 | X509v3 Subject Alternative Name: 33 | IP Address:0.0.0.0, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, DNS:localhost, DNS:0.0.0.0, DNS:127.0.0.1, DNS:::1 34 | Signature Algorithm: ED25519 35 | 8a:ba:bb:ae:e4:24:c6:1c:19:a2:14:06:68:2e:e0:b6:a4:40: 36 | 2d:d9:10:3b:1d:3a:5f:86:e6:06:23:0e:8a:e5:4d:c3:e3:3b: 37 | a3:0f:3a:d5:3c:31:d9:0e:03:68:d3:be:4a:04:ca:3f:b4:8d: 38 | 82:50:4f:f6:8f:9c:98:4a:c1:09 39 | -----BEGIN CERTIFICATE----- 40 | MIIDGDCCAsqgAwIBAgICEAAwBQYDK2VwMH8xCzAJBgNVBAYTAlVTMQswCQYDVQQI 41 | DAJOWTESMBAGA1UECgwJVGVzdCBJbmMuMR0wGwYDVQQLDBRUZXN0IEludGVybWVk 42 | aWFyeSBDQTEOMAwGA1UEAwwFMW50M3IxIDAeBgkqhkiG9w0BCQEWEXNlY3VyaXR5 43 | QHRlc3QuY29tMB4XDTIxMDcwNzEzMzc1OVoXDTI5MDkyMzEzMzc1OVowgY4xCzAJ 44 | BgNVBAYTAlVTMQswCQYDVQQIDAJOWTEMMAoGA1UEBwwDTllDMRIwEAYDVQQKDAlU 45 | ZXN0IEluYy4xHTAbBgNVBAsMFFRlc3QgSW50ZXJtZWRpYXJ5IENBMQ8wDQYDVQQD 46 | DAZzZXJ2ZXIxIDAeBgkqhkiG9w0BCQEWEXNlY3VyaXR5QHRlc3QuY29tMCowBQYD 47 | K2VwAyEAHv+btaZlH31DH5FqWxR78pnopSHZp/1evkrZz4x6iM+jggFYMIIBVDAJ 48 | BgNVHRMEAjAAMB0GA1UdDgQWBBRL2BPTAx6AQE8BR8CxMPPOoWklNjCBtQYDVR0j 49 | BIGtMIGqgBRrZWLNAtftI7VaebuIN5rZiiCU0KGBjaSBijCBhzELMAkGA1UEBhMC 50 | VVMxCzAJBgNVBAgMAk5ZMQwwCgYDVQQHDANOWUMxEjAQBgNVBAoMCVRlc3QgSW5j 51 | LjEYMBYGA1UECwwPVGVzdGluZyBSb290IENBMQ0wCwYDVQQDDARyMDB0MSAwHgYJ 52 | KoZIhvcNAQkBFhFzZWN1cml0eUB0ZXN0LmNvbYICEAAwDgYDVR0PAQH/BAQDAgWg 53 | MBMGA1UdJQQMMAoGCCsGAQUFBwMCMEsGA1UdEQREMEKHBAAAAACHBH8AAAGHEAAA 54 | AAAAAAAAAAAAAAAAAAGCCWxvY2FsaG9zdIIHMC4wLjAuMIIJMTI3LjAuMC4xggM6 55 | OjEwBQYDK2VwA0EAirq7ruQkxhwZohQGaC7gtqRALdkQOx06X4bmBiMOiuVNw+M7 56 | ow861Twx2Q4DaNO+SgTKP7SNglBP9o+cmErBCQ== 57 | -----END CERTIFICATE----- 58 | -------------------------------------------------------------------------------- /tls/chain.cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4096 (0x1000) 5 | Signature Algorithm: ED25519 6 | Issuer: C=US, ST=NY, L=NYC, O=Test Inc., OU=Testing Root CA, CN=r00t/emailAddress=security@test.com 7 | Validity 8 | Not Before: Jul 7 13:36:43 2021 GMT 9 | Not After : May 16 13:36:43 2031 GMT 10 | Subject: C=US, ST=NY, O=Test Inc., OU=Test Intermediary CA, CN=1nt3r/emailAddress=security@test.com 11 | Subject Public Key Info: 12 | Public Key Algorithm: ED25519 13 | ED25519 Public-Key: 14 | pub: 15 | 2c:aa:e0:bf:25:b2:53:9d:91:f1:0c:84:be:45:55: 16 | 9e:9e:cf:1b:41:b8:b8:b0:30:c2:50:cd:32:f0:c7: 17 | cb:a7 18 | X509v3 extensions: 19 | X509v3 Subject Key Identifier: 20 | 6B:65:62:CD:02:D7:ED:23:B5:5A:79:BB:88:37:9A:D9:8A:20:94:D0 21 | X509v3 Authority Key Identifier: 22 | keyid:82:9E:09:FF:42:F0:3C:39:FE:E0:6E:22:29:95:F7:A0:F6:CC:99:CD 23 | 24 | X509v3 Basic Constraints: critical 25 | CA:TRUE, pathlen:0 26 | X509v3 Key Usage: critical 27 | Digital Signature, Certificate Sign 28 | Signature Algorithm: ED25519 29 | 2a:19:40:c4:af:d8:44:8d:33:a3:24:f3:d1:23:2e:ef:21:16: 30 | 24:36:fe:a1:5b:f1:86:a3:6e:10:4c:ab:03:70:c1:73:52:84: 31 | e6:d5:f8:1e:3c:25:a3:69:1f:b0:f8:28:65:b0:e1:f6:31:6f: 32 | 85:d6:f4:fe:c2:56:8b:01:09:08 33 | -----BEGIN CERTIFICATE----- 34 | MIICHTCCAc+gAwIBAgICEAAwBQYDK2VwMIGHMQswCQYDVQQGEwJVUzELMAkGA1UE 35 | CAwCTlkxDDAKBgNVBAcMA05ZQzESMBAGA1UECgwJVGVzdCBJbmMuMRgwFgYDVQQL 36 | DA9UZXN0aW5nIFJvb3QgQ0ExDTALBgNVBAMMBHIwMHQxIDAeBgkqhkiG9w0BCQEW 37 | EXNlY3VyaXR5QHRlc3QuY29tMB4XDTIxMDcwNzEzMzY0M1oXDTMxMDUxNjEzMzY0 38 | M1owfzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMRIwEAYDVQQKDAlUZXN0IElu 39 | Yy4xHTAbBgNVBAsMFFRlc3QgSW50ZXJtZWRpYXJ5IENBMQ4wDAYDVQQDDAUxbnQz 40 | cjEgMB4GCSqGSIb3DQEJARYRc2VjdXJpdHlAdGVzdC5jb20wKjAFBgMrZXADIQAs 41 | quC/JbJTnZHxDIS+RVWens8bQbi4sDDCUM0y8MfLp6NmMGQwHQYDVR0OBBYEFGtl 42 | Ys0C1+0jtVp5u4g3mtmKIJTQMB8GA1UdIwQYMBaAFIKeCf9C8Dw5/uBuIimV96D2 43 | zJnNMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgKEMAUGAytlcANB 44 | ACoZQMSv2ESNM6Mk89EjLu8hFiQ2/qFb8YajbhBMqwNwwXNShObV+B48JaNpH7D4 45 | KGWw4fYxb4XW9P7CVosBCQg= 46 | -----END CERTIFICATE----- 47 | -----BEGIN CERTIFICATE----- 48 | MIICNTCCAeegAwIBAgIUC4+IE/Xy+mNest36Rw7WqanEAAIwBQYDK2VwMIGHMQsw 49 | CQYDVQQGEwJVUzELMAkGA1UECAwCTlkxDDAKBgNVBAcMA05ZQzESMBAGA1UECgwJ 50 | VGVzdCBJbmMuMRgwFgYDVQQLDA9UZXN0aW5nIFJvb3QgQ0ExDTALBgNVBAMMBHIw 51 | MHQxIDAeBgkqhkiG9w0BCQEWEXNlY3VyaXR5QHRlc3QuY29tMB4XDTIxMDcwNzEz 52 | MzMxM1oXDTMxMDcwNTEzMzMxM1owgYcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJO 53 | WTEMMAoGA1UEBwwDTllDMRIwEAYDVQQKDAlUZXN0IEluYy4xGDAWBgNVBAsMD1Rl 54 | c3RpbmcgUm9vdCBDQTENMAsGA1UEAwwEcjAwdDEgMB4GCSqGSIb3DQEJARYRc2Vj 55 | dXJpdHlAdGVzdC5jb20wKjAFBgMrZXADIQBxuxflg7rBVYIzRAMwK6Q7QjZNeBYG 56 | EvqCCnIDt/LPjKNjMGEwHQYDVR0OBBYEFIKeCf9C8Dw5/uBuIimV96D2zJnNMB8G 57 | A1UdIwQYMBaAFIKeCf9C8Dw5/uBuIimV96D2zJnNMA8GA1UdEwEB/wQFMAMBAf8w 58 | DgYDVR0PAQH/BAQDAgKEMAUGAytlcANBAHRKfM/vTHoBCCWHSZalGa0RSCADhUGp 59 | YTvNN0TpHWTmxSBAcGoE0KtfBlHLjh1i2tqCtIn6PuUXVkkoFDkHEAw= 60 | -----END CERTIFICATE----- 61 | -------------------------------------------------------------------------------- /perf.cpp: -------------------------------------------------------------------------------- 1 | #define _1GB (1 * 1024 * 1024 * 1024) 2 | 3 | #include 4 | 5 | static const char *tls_cert; 6 | static const char *tls_key; 7 | static const char *tls_chain; 8 | static struct in6_addr serverAddress = {}; 9 | 10 | #include 11 | #include "perf.services.h" 12 | 13 | // mode (client or server) networking (iouring or syscall) serverIpAddress (any, loopback, or ipv6) 14 | int main (int argc, char *argv[]) 15 | { 16 | constexpr uint32_t bytesForTest = _1GB; 17 | 18 | if (strcmp(argv[3], "any") == 0) 19 | { 20 | serverAddress = in6addr_any; 21 | } 22 | else if (strcmp(argv[3], "loopback") == 0) 23 | { 24 | serverAddress = in6addr_loopback; 25 | } 26 | else 27 | { 28 | inet_pton(AF_INET6, argv[3], &serverAddress); 29 | } 30 | 31 | if (strcmp(argv[1], "server") == 0) 32 | { 33 | tls_cert = "tls/server.cert.pem"; 34 | tls_key = "tls/server.key.pem"; 35 | tls_chain = "tls/chain.cert.pem"; 36 | 37 | globalSetup(); 38 | 39 | auto runServerTest = [&] (QuicLibrary *server) -> void { 40 | 41 | server->instanceSetup(4433, argc - 4, argv + 4); 42 | server->startPerfTest(); 43 | }; 44 | 45 | if (strcmp(argv[2], "iouring") == 0) 46 | { 47 | runServerTest(libraryForChoice()); 48 | } 49 | else if (strcmp(argv[2], "syscall") == 0) 50 | { 51 | runServerTest(libraryForChoice()); 52 | } 53 | } 54 | else 55 | { 56 | tls_cert = "tls/client.cert.pem"; 57 | tls_key = "tls/client.key.pem"; 58 | tls_chain = "tls/chain.cert.pem"; 59 | 60 | globalSetup(); 61 | 62 | // multiple client threads disabled for now 63 | 64 | uint16_t nThreads = 1; 65 | if (nThreads == 0) nThreads = std::thread::hardware_concurrency(); 66 | 67 | std::vector threads; 68 | double seconds[nThreads]; 69 | 70 | std::atomic clientsReady = 0; 71 | 72 | struct sockaddr_in6 *server_in6 = (struct sockaddr_in6 *)calloc(1, sizeof(struct sockaddr_in6)); 73 | server_in6->sin6_family = AF_INET6; 74 | server_in6->sin6_flowinfo = 0; 75 | server_in6->sin6_port = htons(4433); 76 | server_in6->sin6_addr = serverAddress; 77 | 78 | auto runClientTest = [=] (QuicLibrary *client, uint16_t threadIndex, std::atomic& clientsReady, double *seconds) -> void { 79 | 80 | client->instanceSetup(1111 + threadIndex, argc - 4, argv + 4); 81 | 82 | client->connectToServer((struct sockaddr *)server_in6); 83 | client->openStream(); 84 | // signal we're ready and wait for other clients 85 | clientsReady += 1; 86 | while (clientsReady != nThreads) {} 87 | 88 | // everyone is ready, blast the server 89 | 90 | auto start = std::chrono::high_resolution_clock::now(); 91 | 92 | client->startPerfTest(bytesForTest); 93 | 94 | auto end = std::chrono::high_resolution_clock::now(); 95 | 96 | double time = (double)std::chrono::duration_cast(end - start).count(); 97 | 98 | printf("%f seconds\n", time / 1000.0f); 99 | 100 | seconds[threadIndex] = time / 1000.0f; 101 | }; 102 | 103 | for (uint16_t threadIndex = 0; threadIndex < nThreads; ++threadIndex) 104 | { 105 | threads.emplace_back([&, threadIndex] (std::stop_token st) { 106 | 107 | if (strcmp(argv[2], "iouring") == 0) 108 | { 109 | runClientTest(libraryForChoice(), threadIndex, clientsReady, seconds); 110 | } 111 | else if (strcmp(argv[2], "syscall") == 0) 112 | { 113 | runClientTest(libraryForChoice(), threadIndex, clientsReady, seconds); 114 | } 115 | }); 116 | } 117 | 118 | for (auto& thread : threads) 119 | { 120 | thread.join(); 121 | } 122 | 123 | float totalSeconds = 0; 124 | 125 | for (uint16_t threadIndex = 0; threadIndex < nThreads; ++threadIndex) 126 | { 127 | totalSeconds += seconds[threadIndex]; 128 | } 129 | 130 | printf("%f Gb/s\n", (nThreads / totalSeconds) * 8.0); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /perf.tcp.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #pragma once 6 | 7 | #define SOL_TCP IPPROTO_TCP 8 | 9 | template 10 | class NetworkHub 11 | { 12 | 13 | }; 14 | 15 | class TCPSocket { 16 | public: 17 | 18 | struct sockaddr_in6 *address6; 19 | socklen_t addressLen; 20 | int fd; 21 | 22 | TCPSocket(uint16_t port) 23 | { 24 | fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); 25 | 26 | setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const uint32_t[]){ 1'000 * 1500 }, sizeof(uint32_t)); 27 | setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const uint32_t[]){ 1'000 * 1500 }, sizeof(uint32_t)); 28 | 29 | setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const int[]){ 1 }, sizeof(int)); 30 | setsockopt(fd, SOL_TCP, TCP_NODELAY, (const int[]){ 1 }, sizeof(int)); 31 | 32 | addressLen = sizeof(struct sockaddr_in6); 33 | 34 | address6 = (struct sockaddr_in6 *)calloc(1, addressLen); 35 | address6->sin6_family = AF_INET6; 36 | address6->sin6_flowinfo = 0; 37 | address6->sin6_port = htons(port); 38 | address6->sin6_addr = serverAddress; 39 | 40 | bind(fd, (struct sockaddr *)address6, addressLen); 41 | } 42 | }; 43 | 44 | template 45 | class TCPTLS : public QuicLibrary { 46 | private: 47 | 48 | TCPSocket *socket; 49 | SSL *ssl; 50 | BIO *rbio; 51 | BIO *wbio; 52 | 53 | static constexpr uint32_t bufferBase = 16 * 1500; 54 | 55 | uint8_t wBuffer[bufferBase]; 56 | uint8_t rBuffer[bufferBase * 2]; 57 | uint8_t buffer[bufferBase * 8]; 58 | 59 | // static int boringSSLPrintError(const char *str, size_t len, void *ctx) 60 | // { 61 | // printf("boringSSLPrintError -> %.*s\n", len ,str); 62 | // return 1; 63 | // } 64 | 65 | int write(int byteCount) 66 | { 67 | int bytesSent = SSL_write(ssl, wBuffer, byteCount); 68 | // BIO_read always fully consumes the rbio with these buffer sizes 69 | send(socket->fd, buffer, BIO_read(rbio, buffer, sizeof(buffer)), 0); 70 | 71 | return bytesSent; 72 | } 73 | 74 | int read(void) 75 | { 76 | int bytesRead = 0; 77 | int result; 78 | 79 | result = recv(socket->fd, rBuffer, sizeof(rBuffer), 0); 80 | 81 | if (result > 0) 82 | { 83 | result = BIO_write(wbio, rBuffer, result); 84 | 85 | while (BIO_ctrl_pending(wbio) > 0) 86 | { 87 | result = SSL_read(ssl, buffer, sizeof(buffer)); 88 | if (result > 0) bytesRead += result; 89 | } 90 | } 91 | 92 | return bytesRead; 93 | } 94 | 95 | public: 96 | 97 | void instanceSetup(uint16_t localPort, int argc, char *argv[]) 98 | { 99 | socket = new TCPSocket(localPort); 100 | 101 | ssl = SSL_new(TLS::getTLSCtx()); 102 | 103 | rbio = BIO_new(BIO_s_mem()); 104 | wbio = BIO_new(BIO_s_mem()); 105 | 106 | BIO_set_mem_eof_return(rbio, -1); 107 | BIO_set_mem_eof_return(wbio, -1); 108 | 109 | SSL_set_bio(ssl, wbio, rbio); 110 | 111 | if constexpr (mode & Mode::server) 112 | { 113 | SSL_set_accept_state(ssl); 114 | listen(socket->fd, SOMAXCONN); 115 | } 116 | else 117 | { 118 | SSL_set_connect_state(ssl); 119 | } 120 | } 121 | 122 | void connectToServer(struct sockaddr *address) 123 | { 124 | // establish TCP connection 125 | connect(socket->fd, address, sizeof(struct sockaddr_in6)); 126 | } 127 | 128 | void openStream(void) 129 | { 130 | // establish TLS connection 131 | 132 | write(0); 133 | read(); 134 | write(0); 135 | sleep(1); 136 | } 137 | 138 | void startPerfTest(uint64_t nBytes) 139 | { 140 | if constexpr (mode & Mode::client) 141 | { 142 | uint64_t bytesInFlight = nBytes; 143 | 144 | *(uint64_t *)wBuffer = nBytes; 145 | write(8); 146 | 147 | int result; 148 | 149 | do 150 | { 151 | result = read(); 152 | if (result > 0) bytesInFlight -= result; 153 | 154 | } while (bytesInFlight > 0); 155 | } 156 | else 157 | { 158 | // establish TCP connection 159 | int peerfd = accept(socket->fd, NULL, NULL); 160 | 161 | // hack for now lol. 162 | socket->fd = peerfd; 163 | 164 | // establish TLS connection 165 | 166 | read(); 167 | write(0); 168 | read(); 169 | 170 | // wait for bytes in flight 171 | 172 | read(); 173 | uint64_t bytesToSend = *(uint64_t *)buffer; 174 | 175 | // send that many bytes 176 | 177 | int result; 178 | 179 | do 180 | { 181 | result = write(sizeof(wBuffer) > bytesToSend ? bytesToSend : sizeof(wBuffer)); 182 | if (result > 0) bytesToSend -= result; 183 | 184 | } while (bytesToSend > 0); 185 | } 186 | } 187 | }; -------------------------------------------------------------------------------- /perf.quiche.h: -------------------------------------------------------------------------------- 1 | #include "quiche.h" 2 | 3 | #pragma once 4 | 5 | #define LOCAL_CONN_ID_LEN 16 6 | 7 | #define MAX_TOKEN_LEN \ 8 | sizeof("quiche") - 1 + \ 9 | sizeof(struct sockaddr_storage) + \ 10 | LOCAL_CONN_ID_LEN 11 | 12 | template 13 | class Quiche : public QuicLibrary { 14 | private: 15 | 16 | using QuicLibrary::networkHub; 17 | 18 | int64_t bytesInFlight = -1; 19 | int64_t bytesSent = -1; 20 | int64_t lingeringBytes = -1; 21 | quiche_config *config; 22 | quiche_conn *conn; 23 | struct sockaddr_in6 *peerAddress; 24 | bool connected; 25 | 26 | uint64_t flushPackets(void) 27 | { 28 | if (unlikely(conn == NULL)) return 0; 29 | 30 | MultiUDPContext *packets = networkHub->sendPool.get(); 31 | UDPContext *packet; 32 | 33 | do 34 | { 35 | packet = &packets->msgs[packets->count]; 36 | 37 | ssize_t written = quiche_conn_send(conn, packet->buffer(), MAX_IPV6_UDP_PACKET_SIZE); 38 | 39 | if (written == QUICHE_ERR_DONE) break; 40 | 41 | packet->setLength(written); 42 | packet->copyInAddress((struct sockaddr *)peerAddress); 43 | 44 | packets->count++; 45 | 46 | if (packets->isFull()) 47 | { 48 | networkHub->sendBatch(packets); 49 | 50 | // printf("networkHub->sendPool.howManyLeft() = %lu\n", networkHub->sendPool.howManyLeft()); 51 | 52 | //packets = networkHub->sendPool.get(); 53 | //if (unlikely(packets == NULL)) return 10000; 54 | } 55 | 56 | } while (true); 57 | 58 | if (packets->count > 0) networkHub->sendBatch(packets); 59 | 60 | skip: 61 | return quiche_conn_timeout_as_nanos(conn) / 1'000; 62 | } 63 | 64 | void advance(int32_t count = 0) 65 | { 66 | do 67 | { 68 | uint64_t usTil = flushPackets(); 69 | 70 | bool timedout = networkHub->recvmsgWithTimeout(usTil, [&] (UDPContext *msg) -> void { 71 | 72 | if constexpr (mode & Mode::server) 73 | { 74 | if (conn == NULL) 75 | { 76 | uint8_t serverscid[8]; 77 | RAND_bytes(serverscid, sizeof(serverscid)); 78 | 79 | conn = quiche_conn_new_with_tls(serverscid, sizeof(serverscid), NULL, 0, config, SSL_new(TLS::getTLSCtx()), true); 80 | 81 | peerAddress = (struct sockaddr_in6 *)malloc(sizeof(struct sockaddr_in6)); 82 | memcpy(peerAddress, msg->address(), sizeof(struct sockaddr_in6)); 83 | } 84 | } 85 | 86 | ssize_t result = quiche_conn_recv(conn, msg->buffer(), msg->msg_len); 87 | 88 | //printf("quiche_conn_recv result = %lld\n", result); 89 | 90 | if (quiche_conn_is_established(conn)) 91 | { 92 | //printf("quiche_conn_stream_capacity = %lld\n", quiche_conn_stream_capacity(conn, 0)); 93 | 94 | if constexpr (mode & Mode::client) 95 | { 96 | connected = true; 97 | } 98 | 99 | uint64_t streamID = 0; 100 | quiche_stream_iter *readable = quiche_conn_readable(conn); 101 | 102 | while (quiche_stream_iter_next(readable, &streamID)) 103 | { 104 | static uint8_t buf[65535]; 105 | 106 | bool fin = false; 107 | ssize_t recv_len = quiche_conn_stream_recv(conn, streamID, buf, sizeof(buf), &fin); 108 | 109 | if constexpr (mode & Mode::client) 110 | { 111 | // throw bytes away 112 | bytesInFlight -= recv_len; 113 | //printf("received %.1f%%\n", 100.0 * (double)(_1GB - bytesInFlight)/(double)_1GB); 114 | } 115 | else 116 | { 117 | // receive the bytes in flight 118 | bytesInFlight = bswap_64(*(uint64_t *)buf); 119 | } 120 | } 121 | 122 | quiche_stream_iter_free(readable); 123 | } 124 | }); 125 | 126 | if (timedout) quiche_conn_on_timeout(conn); 127 | 128 | if constexpr (mode & Mode::server) 129 | { 130 | if (conn == NULL) continue; 131 | 132 | if (quiche_conn_stream_capacity(conn, 0) > 0) 133 | { 134 | do 135 | { 136 | ssize_t sent = quiche_conn_stream_send(conn, 0, (const uint8_t *)networkHub->junk, bytesInFlight > sizeof(networkHub->junk) ? sizeof(networkHub->junk) : bytesInFlight, false); 137 | 138 | if (sent > 0) bytesInFlight -= sent; 139 | else break; 140 | 141 | } while (true); 142 | 143 | if (bytesInFlight == 0) flushPackets(); 144 | } 145 | } 146 | 147 | } while (bytesInFlight != 0 && (count == 0 || --count > 0)); 148 | } 149 | 150 | public: 151 | 152 | // static void log(const char *line, void *argp) 153 | // { 154 | // printf("%s\n", line); 155 | // } 156 | 157 | void instanceSetup(uint16_t localPort, int argc, char *argv[]) 158 | { 159 | networkHub = new NetworkHub(localPort); 160 | 161 | config = quiche_config_new(QUICHE_PROTOCOL_VERSION); 162 | 163 | quiche_config_set_max_idle_timeout(config, 5000); 164 | quiche_config_set_max_recv_udp_payload_size(config, MAX_IPV6_UDP_PACKET_SIZE); 165 | quiche_config_set_max_send_udp_payload_size(config, MAX_IPV6_UDP_PACKET_SIZE); 166 | quiche_config_set_initial_max_data(config, 10000000); 167 | quiche_config_set_initial_max_stream_data_bidi_local(config, 1000000); 168 | quiche_config_set_initial_max_stream_data_bidi_remote(config, 1000000); 169 | quiche_config_set_initial_max_stream_data_uni(config, 10000); 170 | quiche_config_set_initial_max_streams_bidi(config, 10); 171 | quiche_config_set_initial_max_streams_uni(config, 10); 172 | // quiche_config_set_ack_delay_exponent(config, 5); 173 | // quiche_config_set_max_ack_delay(config, 50); 174 | // quiche_config_set_disable_active_migration(config, true); 175 | //quiche_config_set_cc_algorithm(config, QUICHE_CC_RENO); 176 | //quiche_config_enable_hystart(config, true); 177 | 178 | //quiche_enable_debug_logging(log, NULL); 179 | } 180 | 181 | void connectToServer(struct sockaddr *address) 182 | { 183 | peerAddress = (struct sockaddr_in6 *)address; 184 | 185 | uint8_t scid[8]; 186 | RAND_bytes(scid, sizeof(scid)); 187 | 188 | conn = quiche_conn_new_with_tls((const uint8_t *)scid, sizeof(scid), NULL, 0, config, SSL_new(TLS::getTLSCtx()), false); 189 | 190 | do 191 | { 192 | advance(1); 193 | 194 | } while (connected == false); 195 | } 196 | 197 | void openStream(void) 198 | { 199 | // just nop this for now 200 | 201 | // do 202 | // { 203 | // advance(1); 204 | 205 | // } while (ready == false); 206 | } 207 | 208 | void startPerfTest(uint64_t nBytes) 209 | { 210 | if constexpr (mode & Mode::client) 211 | { 212 | bytesInFlight = nBytes; 213 | 214 | uint64_t swappedBytes = bswap_64(bytesInFlight); 215 | quiche_conn_stream_send(conn, 0, (const uint8_t *)&swappedBytes, 8, false); 216 | } 217 | 218 | advance(); 219 | } 220 | }; -------------------------------------------------------------------------------- /perf.lsquic.h: -------------------------------------------------------------------------------- 1 | #include "lsquic.h" 2 | 3 | #pragma once 4 | 5 | struct lsquic_conn_ctx { }; 6 | struct lsquic_stream_ctx { }; 7 | 8 | template 9 | class Lsquic : public QuicLibrary { 10 | private: 11 | 12 | using QuicLibrary::networkHub; 13 | 14 | int64_t bytesInFlight = -1; 15 | struct ssl_ctx_st *tlsCtx; 16 | lsquic_conn_t *connection; 17 | lsquic_stream_t *stream; 18 | lsquic_engine_t *engine; 19 | 20 | static lsquic_conn_ctx_t* connectionOpen(void *stream_if_ctx, lsquic_conn_t *connection) 21 | { 22 | //printf("lsquic %s: connectionOpen\n", modeToString(mode)); 23 | 24 | if constexpr (mode & Mode::client) 25 | { 26 | ((Lsquic *)stream_if_ctx)->connection = connection; 27 | } 28 | 29 | return (lsquic_conn_ctx_t *)stream_if_ctx; 30 | } 31 | 32 | static void connectionClose(lsquic_conn_t *conn) 33 | { 34 | //printf("lsquic %s: connectionClose\n", modeToString(mode)); 35 | } 36 | 37 | static lsquic_stream_ctx_t* streamOpen(void *stream_if_ctx, lsquic_stream_t *stream) 38 | { 39 | //printf("lsquic %s: streamOpen\n", modeToString(mode)); 40 | 41 | lsquic_stream_wantread(stream, 1); 42 | 43 | if constexpr (mode & Mode::client) 44 | { 45 | ((Lsquic *)stream_if_ctx)->stream = stream; 46 | } 47 | 48 | return (lsquic_stream_ctx_t *)stream_if_ctx; 49 | } 50 | 51 | static void streamClose(lsquic_stream_t *stream, lsquic_stream_ctx_t *context) 52 | { 53 | //printf("lsquic %s: streamClose\n", modeToString(mode)); 54 | } 55 | 56 | static size_t streamRead(void *context, const unsigned char *data, size_t len, int fin) 57 | { 58 | //printf("lsquic %s: streamRead\n", modeToString(mode)); 59 | 60 | if constexpr (mode & Mode::client) 61 | { 62 | // throw away the bytes 63 | ((Lsquic *)context)->bytesInFlight -= len; 64 | } 65 | else 66 | { 67 | // the client is telling us how many bytes to send it 68 | ((Lsquic *)context)->bytesInFlight = bswap_64(*(uint64_t *)data); 69 | } 70 | 71 | return len; 72 | } 73 | 74 | static void streamReadTrigger(lsquic_stream_t *stream, lsquic_stream_ctx_t *context) 75 | { 76 | //printf("lsquic %s: streamReadTrigger\n", modeToString(mode)); 77 | 78 | lsquic_stream_readf(stream, streamRead, context); 79 | 80 | if constexpr (mode & Mode::client) 81 | { 82 | //printf("received = %.1f\n", (_1GB - ((Lsquic *)context)->bytesInFlight)/_1GB); 83 | 84 | // we're done 85 | } 86 | else 87 | { 88 | // start sending the client bytes 89 | lsquic_stream_wantwrite(stream, 1); 90 | } 91 | } 92 | 93 | static void streamWrite(lsquic_stream_t *stream, lsquic_stream_ctx_t *context) 94 | { 95 | //printf("lsquic %s: streamWrite\n", modeToString(mode)); 96 | 97 | if constexpr (mode & Mode::client) 98 | { 99 | // tell the server we want bytesInFlight 100 | uint64_t bytesInUgly = bswap_64(((Lsquic *)context)->bytesInFlight); 101 | lsquic_stream_write(stream, &bytesInUgly, sizeof(uint64_t)); 102 | lsquic_stream_wantwrite(stream, 0); 103 | lsquic_stream_flush(stream); 104 | } 105 | else 106 | { 107 | NetworkHub *networkHub = ((Lsquic *)context)->networkHub; 108 | int64_t& bytesToSend = ((Lsquic *)context)->bytesInFlight; 109 | 110 | // send more junk to the client 111 | bytesToSend -= lsquic_stream_write(stream, networkHub->junk, (sizeof(networkHub->junk) > bytesToSend ? bytesToSend : sizeof(networkHub->junk))); 112 | 113 | if (unlikely(bytesToSend == 0)) 114 | { 115 | // the server is done 116 | lsquic_stream_wantwrite(stream, 0); 117 | lsquic_stream_flush(stream); 118 | } 119 | } 120 | } 121 | 122 | static int packetsOut(void *context, const struct lsquic_out_spec *specs, unsigned n_specs) 123 | { 124 | // printf("lsquic %s: packetsOut -> n_specs = %lu\n", modeToString(mode), n_specs); 125 | 126 | NetworkHub *networkHub = ((Lsquic *)context)->networkHub; 127 | 128 | MultiUDPContext *packets = networkHub->sendPool.get(); 129 | 130 | for (uint32_t index = 0; index < n_specs; index++) 131 | { 132 | const struct lsquic_out_spec& spec = specs[index]; 133 | 134 | for (uint32_t subIndex = 0; subIndex < spec.iovlen; subIndex++) 135 | { 136 | struct iovec& vec = spec.iov[subIndex]; 137 | 138 | UDPContext *packet = packets->nextPacket(); 139 | 140 | if (packet == NULL) 141 | { 142 | networkHub->sendBatch(packets); 143 | packets = networkHub->sendPool.get(); 144 | packet = packets->nextPacket(); 145 | } 146 | 147 | packet->copyInAddress(spec.dest_sa); 148 | packet->copyInIov(vec); 149 | } 150 | } 151 | 152 | networkHub->sendBatch(packets); 153 | return n_specs; 154 | } 155 | 156 | void advance(int32_t count = 0) 157 | { 158 | //printf("lsquic %s: advance(%d)\n", modeToString(mode), count); 159 | 160 | do 161 | { 162 | lsquic_engine_process_conns(engine); 163 | 164 | int usTil = 0; 165 | lsquic_engine_earliest_adv_tick(engine, &usTil); 166 | 167 | networkHub->recvmsgWithTimeout(usTil, [&] (UDPContext *msg) -> void { 168 | 169 | int result = lsquic_engine_packet_in(engine, (const unsigned char *)msg->buffer(), msg->msg_len, (const struct sockaddr *)networkHub->socket.address6, (const struct sockaddr *)msg->address(), this, 0); 170 | }); 171 | 172 | } while (bytesInFlight != 0 && (count == 0 || --count > 0)); 173 | } 174 | 175 | public: 176 | 177 | static int lslogger(void *ctx, const char *buf, size_t len) 178 | { 179 | printf("%.*s", len, buf); 180 | return 0; 181 | } 182 | 183 | static void globalSetup(void) 184 | { 185 | // printf("lsquic::globalSetup() \n"); 186 | 187 | // static const struct lsquic_logger_if logger_if = { lslogger }; 188 | // lsquic_logger_init(&logger_if, NULL, LLTS_HHMMSSUS); 189 | 190 | // lsquic_set_log_level("debug"); 191 | 192 | if constexpr (mode & Mode::server) 193 | { 194 | lsquic_global_init(LSQUIC_GLOBAL_SERVER); 195 | } 196 | else 197 | { 198 | lsquic_global_init(LSQUIC_GLOBAL_CLIENT); 199 | } 200 | } 201 | 202 | void instanceSetup(uint16_t localPort, int argc, char *argv[]) 203 | { 204 | networkHub = new NetworkHub(localPort); 205 | 206 | //printf("lsquic %s: setup\n", modeToString(mode)); 207 | 208 | static struct lsquic_engine_settings settings; 209 | memset(&settings, 0, sizeof(struct lsquic_engine_settings)); 210 | 211 | if constexpr (mode & Mode::server) 212 | { 213 | lsquic_engine_init_settings(&settings, LSENG_SERVER); 214 | } 215 | else 216 | { 217 | lsquic_engine_init_settings(&settings, 0); 218 | } 219 | 220 | settings.es_sfcw = 1024 * 1024; 221 | settings.es_cfcw = 1024 * 1024; 222 | settings.es_max_sfcw = 8 * settings.es_sfcw; 223 | settings.es_max_cfcw = 8 * settings.es_cfcw; 224 | settings.es_max_inchoate = 10'000; 225 | settings.es_versions = (1 << LSQVER_I001); 226 | settings.es_pace_packets = 1; 227 | settings.es_cc_algo = 1; // 2 for bbr 228 | settings.es_idle_timeout = 600; 229 | settings.es_ecn = 0; 230 | settings.es_ql_bits = 2; 231 | settings.es_spin = 1; 232 | settings.es_scid_len = 8; 233 | settings.es_delayed_acks = 1; 234 | settings.es_max_udp_payload_size_rx = 1500; 235 | settings.es_dplpmtud = 1; 236 | settings.es_base_plpmtu = 1400; 237 | settings.es_max_plpmtu = 1500; 238 | settings.es_max_batch_size = 50; 239 | 240 | static struct lsquic_stream_if streamConfig = { .on_new_conn = connectionOpen, 241 | .on_conn_closed = connectionClose, 242 | .on_new_stream = streamOpen, 243 | .on_read = streamReadTrigger, 244 | .on_write = streamWrite, 245 | .on_close = streamClose 246 | }; 247 | 248 | static struct lsquic_engine_api config = { .ea_settings = &settings, 249 | .ea_stream_if = &streamConfig, 250 | .ea_stream_if_ctx = this, 251 | .ea_packets_out = packetsOut, 252 | .ea_packets_out_ctx = this, 253 | .ea_get_ssl_ctx = TLS::getTLSCtx, 254 | .ea_verify_cert = TLS::verifyCert, 255 | .ea_verify_ctx = this, 256 | .ea_alpn = "perf" 257 | }; 258 | 259 | if constexpr (mode & Mode::server) 260 | { 261 | engine = lsquic_engine_new(LSENG_SERVER, &config); 262 | } 263 | else 264 | { 265 | engine = lsquic_engine_new(0, &config); 266 | } 267 | } 268 | 269 | void connectToServer(struct sockaddr *address) 270 | { 271 | //printf("lsquic %s: connect\n", modeToString(mode)); 272 | 273 | lsquic_conn_t *conn = lsquic_engine_connect(engine, LSQVER_I001, networkHub->socket.address(), address, this, (lsquic_conn_ctx_t *)this, NULL, 1400, NULL, 0, NULL, 0); 274 | 275 | do 276 | { 277 | advance(1); 278 | 279 | } while (connection == NULL); 280 | } 281 | 282 | void openStream(void) 283 | { 284 | //printf("lsquic %s: openStream\n", modeToString(mode)); 285 | 286 | lsquic_conn_make_stream(connection); 287 | 288 | do 289 | { 290 | advance(1); 291 | 292 | } while (stream == NULL); 293 | } 294 | 295 | void startPerfTest(uint64_t nBytes) 296 | { 297 | //printf("lsquic %s: startPerfTest\n", modeToString(mode)); 298 | 299 | if constexpr (mode & Mode::client) 300 | { 301 | bytesInFlight = nBytes; 302 | lsquic_stream_wantwrite(stream, 1); 303 | } 304 | 305 | advance(); 306 | } 307 | }; 308 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.16) 2 | 3 | project(quicperf LANGUAGES C CXX) 4 | 5 | set(VERBOSE ON) 6 | 7 | set(CMAKE_CXX_STANDARD 20) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | set(CMAKE_CXX_EXTENSIONS ON) # gnu 10 | 11 | if(NOT DEFINED BUILD_PICOPERF OR BUILD_LSPERF OR BUILD_QUICHEPERF OR BUILD_NGTCP2PERF OR BUILD_TCPPERF) 12 | if(NOT DEFINED BUILD_PICOPERF) 13 | set(BUILD_PICOPERF 1) 14 | add_executable(picoperf perf.cpp) 15 | endif() 16 | 17 | if(NOT DEFINED BUILD_LSPERF) 18 | set(BUILD_LSPERF 1) 19 | add_executable(lsperf perf.cpp) 20 | endif() 21 | 22 | if(NOT DEFINED BUILD_QUICHEPERF) 23 | set(BUILD_QUICHEPERF 1) 24 | add_executable(quicheperf perf.cpp) 25 | endif() 26 | 27 | if(NOT DEFINED BUILD_NGTCP2PERF) 28 | set(BUILD_NGTCP2PERF 1) 29 | add_executable(ngtcp2perf perf.cpp) 30 | endif() 31 | 32 | if(NOT DEFINED BUILD_TCPPERF) 33 | set(BUILD_TCPPERF 1) 34 | add_executable(tcpperf perf.cpp) 35 | endif() 36 | else() 37 | 38 | if(BUILD_PICOPERF) 39 | add_executable(picoperf perf.cpp) 40 | endif() 41 | 42 | if(BUILD_LSPERF) 43 | add_executable(lsperf perf.cpp) 44 | endif() 45 | 46 | if(BUILD_QUICHEPERF) 47 | add_executable(quicheperf perf.cpp) 48 | endif() 49 | 50 | if(BUILD_NGTCP2PERF) 51 | add_executable(ngtcp2perf perf.cpp) 52 | endif() 53 | 54 | if(BUILD_TCPPERF) 55 | add_executable(tcpperf perf.cpp) 56 | endif() 57 | endif() 58 | 59 | include(ExternalProject) 60 | 61 | set_directory_properties(PROPERTIES EP_BASE "${CMAKE_BINARY_DIR}/3rdparty") 62 | set(3rdParty_SDIR ${CMAKE_BINARY_DIR}/3rdparty/Source) 63 | set(3rdParty_BDIR ${CMAKE_BINARY_DIR}/3rdparty/Build) 64 | 65 | ExternalProject_Add(liburing 66 | GIT_REPOSITORY https://github.com/axboe/liburing.git 67 | GIT_TAG master 68 | BUILD_IN_SOURCE ON 69 | GIT_SHALLOW ON 70 | CONFIGURE_COMMAND ${3rdParty_SDIR}/liburing/configure 71 | BUILD_COMMAND make -j4 72 | INSTALL_COMMAND "" 73 | ) 74 | 75 | if (BUILD_LSPERF OR BUILD_NGTCP2PERF OR BUILD_TCPPERF) 76 | 77 | ExternalProject_Add(zlib 78 | GIT_REPOSITORY https://github.com/madler/zlib.git 79 | GIT_TAG "cacf7f1d4e3d44d871b605da3b647f07d718623f" 80 | GIT_SHALLOW ON 81 | CMAKE_ARGS 82 | -DBUILD_EXAMPLES:BOOL=OFF 83 | -DBUILD_SHARED_LIBS:BOOL=OFF 84 | -DSKIP_INSTALL_FILES:BOOL=ON 85 | INSTALL_COMMAND "" 86 | ) 87 | 88 | # you'll need to have the go executable on your system 89 | find_program(GO_EXECUTABLE go REQUIRED) 90 | 91 | ExternalProject_Add(boringssl 92 | DEPENDS zlib 93 | GIT_REPOSITORY https://github.com/google/boringssl.git 94 | GIT_TAG master 95 | GIT_SHALLOW ON 96 | BUILD_COMMAND cmake --build . --config Release -- -j 4 97 | UPDATE_COMMAND "" 98 | CMAKE_ARGS 99 | -DCMAKE_C_FLAGS="-w" 100 | -DCMAKE_BUILD_TYPE=Release 101 | -DGO_EXECUTABLE:FILEPATH=${GO_EXECUTABLE} 102 | INSTALL_COMMAND "" 103 | ) 104 | add_dependencies(tcpperf boringssl) 105 | 106 | if (BUILD_LSPERF) 107 | 108 | add_dependencies(lsperf liburing) 109 | 110 | ExternalProject_Add(lsquic 111 | DEPENDS boringssl 112 | #GIT_REPOSITORY https://github.com/litespeedtech/lsquic.git 113 | GIT_REPOSITORY https://github.com/victorstewart/lsquic.git 114 | GIT_TAG master 115 | GIT_SHALLOW ON 116 | BUILD_IN_SOURCE ON 117 | CMAKE_ARGS 118 | -DCMAKE_BUILD_TYPE=Release 119 | -DBORINGSSL_INCLUDE=${3rdParty_SDIR}/boringssl/include 120 | -DBORINGSSL_DIR=${3rdParty_BDIR}/boringssl 121 | INSTALL_COMMAND "" 122 | ) 123 | add_dependencies(lsperf lsquic) 124 | target_compile_options(lsperf PUBLIC -DLSPERF -w -mavx2 -flto -Ofast -frename-registers -fno-signed-zeros -fno-trapping-math) 125 | 126 | target_include_directories(lsperf PUBLIC ${3rdParty_SDIR}/boringssl/include 127 | ${3rdParty_SDIR}/liburing/src/include 128 | ${3rdParty_SDIR}/lsquic/include 129 | ) 130 | 131 | target_link_libraries(lsperf pthread 132 | ${3rdParty_BDIR}/boringssl/crypto/libcrypto.a 133 | ${3rdParty_BDIR}/boringssl/ssl/libssl.a 134 | ${3rdParty_BDIR}/zlib/libz.a 135 | ${3rdParty_SDIR}/lsquic/src/liblsquic/liblsquic.a 136 | ${3rdParty_SDIR}/liburing/src/liburing.a 137 | ) 138 | 139 | add_custom_command(TARGET lsperf 140 | POST_BUILD 141 | COMMAND ${CMAKE_COMMAND} -E copy $ ..) 142 | endif() 143 | 144 | if (BUILD_NGTCP2PERF) 145 | 146 | add_dependencies(ngtcp2perf liburing) 147 | set(boringssl_libs "-L${3rdParty_BDIR}/boringssl/ssl -lssl -L${3rdParty_BDIR}/boringssl/crypto -lcrypto -pthread") 148 | ExternalProject_Add(ngtcp2 149 | DEPENDS boringssl 150 | GIT_REPOSITORY https://github.com/ngtcp2/ngtcp2.git 151 | GIT_TAG main 152 | GIT_SHALLOW OFF 153 | 154 | CMAKE_ARGS 155 | -DBORINGSSL_LIBRARIES=${boringssl_libs} 156 | -DBORINGSSL_INCLUDE_DIR=${3rdParty_SDIR}/boringssl/include 157 | INSTALL_COMMAND "" 158 | ) 159 | add_dependencies(ngtcp2perf ngtcp2) 160 | target_compile_options(ngtcp2perf PUBLIC -DNGTCP2PERF -w -mavx2 -flto -Ofast -frename-registers -fno-signed-zeros -fno-trapping-math) 161 | 162 | target_include_directories(ngtcp2perf PUBLIC ${3rdParty_SDIR}/boringssl/include 163 | ${3rdParty_SDIR}/liburing/src/include 164 | ${3rdParty_SDIR}/ngtcp2/lib/includes 165 | ${3rdParty_BDIR}/ngtcp2/lib/includes 166 | ${3rdParty_SDIR}/ngtcp2/crypto/includes 167 | ) 168 | 169 | target_link_libraries(ngtcp2perf pthread 170 | ${3rdParty_BDIR}/boringssl/crypto/libcrypto.a 171 | ${3rdParty_BDIR}/boringssl/ssl/libssl.a 172 | ${3rdParty_BDIR}/zlib/libz.a 173 | ${3rdParty_BDIR}/ngtcp2/lib/libngtcp2.a 174 | ${3rdParty_BDIR}/ngtcp2/crypto/boringssl/libngtcp2_crypto_boringssl.a 175 | ${3rdParty_SDIR}/liburing/src/liburing.a 176 | ) 177 | add_custom_command(TARGET ngtcp2perf 178 | POST_BUILD 179 | COMMAND ${CMAKE_COMMAND} -E copy $ ..) 180 | endif() 181 | 182 | if (BUILD_TCPPERF) 183 | 184 | target_compile_options(tcpperf PUBLIC -DTCPPERF -w -mavx2 -flto -Ofast -frename-registers -fno-signed-zeros -fno-trapping-math) 185 | target_include_directories(tcpperf PUBLIC ${3rdParty_SDIR}/boringssl/include ) 186 | 187 | target_link_libraries(tcpperf pthread 188 | ${3rdParty_BDIR}/boringssl/crypto/libcrypto.a 189 | ${3rdParty_BDIR}/boringssl/ssl/libssl.a 190 | ) 191 | add_custom_command(TARGET tcpperf 192 | POST_BUILD 193 | COMMAND ${CMAKE_COMMAND} -E copy $ ..) 194 | endif() 195 | 196 | endif() 197 | 198 | if (BUILD_QUICHEPERF) 199 | 200 | add_dependencies(quicheperf liburing) 201 | ExternalProject_Add(rust 202 | URL https://static.rust-lang.org/dist/rust-1.51.0-x86_64-unknown-linux-gnu.tar.gz 203 | DOWNLOAD_NAME rust 204 | CONFIGURE_COMMAND "" 205 | BUILD_COMMAND "" 206 | INSTALL_COMMAND "" 207 | ) 208 | 209 | ExternalProject_Add(quiche 210 | DEPENDS rust 211 | GIT_REPOSITORY https://github.com/cloudflare/quiche.git 212 | GIT_TAG 0.8.1 213 | GIT_SHALLOW ON 214 | CONFIGURE_COMMAND "" 215 | BUILD_IN_SOURCE ON 216 | BUILD_COMMAND ${CMAKE_COMMAND} -E env RUSTC=${3rdParty_SDIR}/rust/rustc/bin/rustc ${CMAKE_COMMAND} -E env RUSTFLAGS=-L${3rdParty_SDIR}/rust/rust-std-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib ${3rdParty_SDIR}/rust/cargo/bin/cargo build --features ffi --release 217 | INSTALL_COMMAND "" 218 | ) 219 | 220 | add_dependencies(quicheperf quiche) 221 | target_compile_options(quicheperf PUBLIC -DQUICHEPERF -w -mavx2 -flto -Ofast -frename-registers -fno-signed-zeros -fno-trapping-math) 222 | 223 | target_include_directories(quicheperf PUBLIC ${3rdParty_SDIR}/liburing/src/include 224 | ${3rdParty_SDIR}/boringssl/include 225 | ${3rdParty_SDIR}/quiche/include 226 | ) 227 | 228 | target_link_libraries(quicheperf pthread 229 | dl 230 | ${3rdParty_SDIR}/quiche/target/release/libquiche.a 231 | ${3rdParty_SDIR}/liburing/src/liburing.a 232 | ) 233 | add_custom_command(TARGET quicheperf 234 | POST_BUILD 235 | COMMAND ${CMAKE_COMMAND} -E copy $ ..) 236 | endif() 237 | 238 | if (BUILD_PICOPERF) 239 | 240 | find_package(OpenSSL REQUIRED) 241 | add_dependencies(picoperf liburing) 242 | 243 | # at the moment picotls is still finding the system openssl... 244 | ExternalProject_Add(picotls 245 | 246 | GIT_REPOSITORY https://github.com/h2o/picotls.git 247 | GIT_TAG master 248 | GIT_SHALLOW ON 249 | BUILD_COMMAND make -j4 250 | INSTALL_COMMAND "" 251 | ) 252 | 253 | ExternalProject_Add(picoquic 254 | DEPENDS picotls 255 | 256 | GIT_REPOSITORY https://github.com/private-octopus/picoquic.git 257 | GIT_TAG master 258 | GIT_SHALLOW ON 259 | CMAKE_ARGS 260 | -DPTLS_INCLUDE_DIR=${3rdParty_SDIR}/picotls/include 261 | -DPTLS_LIBRARIES=${3rdParty_BDIR}/picotls 262 | INSTALL_COMMAND "" 263 | ) 264 | add_dependencies(picoperf picoquic) 265 | target_compile_options(picoperf PUBLIC -DPICOPERF -w -mavx2 -flto -Ofast -frename-registers -fno-signed-zeros -fno-trapping-math) 266 | 267 | target_include_directories(picoperf PUBLIC ${3rdParty_SDIR}/liburing/src/include 268 | ${3rdParty_SDIR}/picoquic/picoquic 269 | ${3rdParty_SDIR}/picotls/include 270 | ) 271 | 272 | target_link_libraries(picoperf pthread 273 | dl 274 | OpenSSL::Crypto 275 | OpenSSL::SSL 276 | ${3rdParty_BDIR}/picotls/libpicotls-core.a 277 | ${3rdParty_BDIR}/picotls/libpicotls-fusion.a 278 | ${3rdParty_BDIR}/picotls/libpicotls-openssl.a 279 | ${3rdParty_BDIR}/picoquic/libpicoquic-core.a 280 | ${3rdParty_SDIR}/liburing/src/liburing.a 281 | ) 282 | 283 | add_custom_command(TARGET picoperf 284 | POST_BUILD 285 | COMMAND ${CMAKE_COMMAND} -E copy $ ..) 286 | endif() 287 | -------------------------------------------------------------------------------- /perf.picoquic.h: -------------------------------------------------------------------------------- 1 | #include "picoquic.h" 2 | #include "picoquic_utils.h" 3 | #include "picoquic_binlog.h" 4 | #include "picoquic_logger.h" 5 | #include "picoquic_unified_log.h" 6 | #include "picoquic_config.h" 7 | #include "picotls.h" 8 | 9 | #pragma once 10 | 11 | template 12 | class Picoquic : public QuicLibrary { 13 | private: 14 | 15 | using QuicLibrary::networkHub; 16 | 17 | picoquic_quic_t *engine; 18 | picoquic_cnx_t *cnx; 19 | int64_t bytesInFlight = -1; 20 | bool ready; 21 | 22 | // static const char* picoeventToString(picoquic_call_back_event_t fin_or_event) 23 | // { 24 | // switch (fin_or_event) 25 | // { 26 | // case picoquic_callback_stream_data: return "picoquic_callback_stream_data"; 27 | // case picoquic_callback_stream_fin: return "picoquic_callback_stream_fin"; 28 | // case picoquic_callback_stream_reset: return "picoquic_callback_stream_reset"; 29 | // case picoquic_callback_stop_sending: return "picoquic_callback_stop_sending"; 30 | // case picoquic_callback_stateless_reset: return "picoquic_callback_stateless_reset"; 31 | // case picoquic_callback_close: return "picoquic_callback_close"; 32 | // case picoquic_callback_application_close: return "picoquic_callback_application_close"; 33 | // case picoquic_callback_stream_gap: return "picoquic_callback_stream_gap"; 34 | // case picoquic_callback_prepare_to_send: return "picoquic_callback_prepare_to_send"; 35 | // case picoquic_callback_almost_ready: return "picoquic_callback_almost_ready"; 36 | // case picoquic_callback_ready: return "picoquic_callback_ready"; 37 | // case picoquic_callback_datagram: return "picoquic_callback_datagram"; 38 | // case picoquic_callback_version_negotiation: return "picoquic_callback_version_negotiation"; 39 | // case picoquic_callback_request_alpn_list: return "picoquic_callback_request_alpn_list"; 40 | // case picoquic_callback_set_alpn: return "picoquic_callback_set_alpn"; 41 | // case picoquic_callback_pacing_changed: return "picoquic_callback_pacing_changed"; 42 | // default: break; 43 | // } 44 | 45 | // printf("got bad picoevent value = %d\n", fin_or_event); 46 | // return ""; 47 | // } 48 | 49 | static int datain(picoquic_cnx_t *cnx, uint64_t stream_id, uint8_t *bytes, size_t length, picoquic_call_back_event_t fin_or_event, void *callback_ctx, void *stream_ctx) 50 | { 51 | //printf("datain -> %s\n", picoeventToString(fin_or_event)); 52 | 53 | Picoquic *instance = (Picoquic *)callback_ctx; 54 | 55 | switch (fin_or_event) 56 | { 57 | // Data received from peer on stream N 58 | case picoquic_callback_stream_data: 59 | // Fin received from peer on stream N; data is optional 60 | case picoquic_callback_stream_fin: 61 | { 62 | if constexpr (mode & Mode::client) 63 | { 64 | instance->bytesInFlight -= length; 65 | //if ((rand() % 250) == 0) printf("received %.1f%%\n", 100.0 * (double)(_1GB - instance->bytesInFlight)/(double)_1GB ); 66 | } 67 | else 68 | { 69 | instance->bytesInFlight = bswap_64(*(uint64_t *)bytes); 70 | picoquic_mark_active_stream(cnx, stream_id, true, instance); 71 | } 72 | 73 | break; 74 | } 75 | // Ask application to send data in frame, see picoquic_provide_stream_data_buffer for details 76 | case picoquic_callback_prepare_to_send: 77 | { 78 | if constexpr (mode & Mode::client) 79 | { 80 | if (instance->bytesInFlight) 81 | { 82 | uint8_t* buffer = picoquic_provide_stream_data_buffer(bytes, 8, 1, 0); 83 | *(uint64_t *)buffer = bswap_64(instance->bytesInFlight); 84 | picoquic_mark_active_stream(cnx, stream_id, false, instance); 85 | } 86 | else 87 | { 88 | instance->ready = true; 89 | } 90 | } 91 | else 92 | { 93 | size_t bytesSending = instance->bytesInFlight > length ? length : instance->bytesInFlight; 94 | uint8_t *buffer = picoquic_provide_stream_data_buffer(bytes, bytesSending, false, true); 95 | memset(buffer, 7, bytesSending); 96 | instance->bytesInFlight -= bytesSending; 97 | } 98 | 99 | break; 100 | } 101 | // Data can be sent, but the connection is not fully established 102 | case picoquic_callback_almost_ready: 103 | // Data can be sent and received, connection migration can be initiated 104 | case picoquic_callback_ready: 105 | // version negotiation requested 106 | case picoquic_callback_version_negotiation: 107 | // Provide the list of supported ALPN 108 | case picoquic_callback_request_alpn_list: 109 | // Set ALPN to negotiated value 110 | case picoquic_callback_set_alpn: 111 | // Pacing rate for the connection changed 112 | case picoquic_callback_pacing_changed: 113 | // Reset Stream received from peer on stream N; bytes=NULL, len = 0 114 | case picoquic_callback_stream_reset: 115 | // Stop sending received from peer on stream N; bytes=NULL, len = 0 116 | case picoquic_callback_stop_sending: 117 | // Stateless reset received from peer. Stream=0, bytes=NULL, len=0 118 | case picoquic_callback_stateless_reset: 119 | // Connection close. Stream=0, bytes=NULL, len=0 120 | case picoquic_callback_close: 121 | // Application closed by peer. Stream=0, bytes=NULL, len=0 122 | case picoquic_callback_application_close: 123 | // bytes=NULL, len = length-of-gap or 0 (if unknown) 124 | case picoquic_callback_stream_gap: 125 | // Datagram frame has been received 126 | case picoquic_callback_datagram: 127 | default: 128 | break; 129 | } 130 | 131 | return 0; 132 | } 133 | 134 | void advance(int32_t count = 0) 135 | { 136 | //printf("picoquic %s: advance(%d)\n", modeToString(mode), count); 137 | 138 | MultiUDPContext *packets; 139 | UDPContext *packet; 140 | 141 | size_t send_length; 142 | int result; 143 | int interfaceIndex; 144 | int64_t usTil; 145 | 146 | // max sendBatches to push 147 | uint16_t metaBatchSize = 1; // tried values of 2 and 3, makes no difference for syscalls 148 | 149 | if constexpr (mode & Mode::iouring) 150 | { 151 | metaBatchSize = 1; 152 | } 153 | 154 | // max we push per sendBatch 155 | uint16_t batchSize = MultiUDPContext::batchSize; 156 | 157 | if constexpr (mode & Mode::iouring) 158 | { 159 | batchSize = 125; 160 | } 161 | 162 | do 163 | { 164 | do 165 | { 166 | if constexpr (mode & Mode::iouring) 167 | { 168 | // considering iouring is async, sometimes the recvs outrun the sends completions that refill the pool 169 | if (likely(networkHub->sendPool.howManyLeft() == 0)) goto skip; 170 | } 171 | 172 | packets = networkHub->sendPool.get(); 173 | 174 | do 175 | { 176 | packet = &packets->msgs[packets->count]; 177 | 178 | result = picoquic_prepare_next_packet_ex(engine, timeNowUs(), packet->buffer(), MAX_IPV6_UDP_PACKET_SIZE, &send_length, packet->address(), NULL, &interfaceIndex, NULL, NULL, NULL); 179 | 180 | if (result == 0 && send_length > 0) 181 | { 182 | packet->msg_hdr.msg_iov[0].iov_len = send_length; 183 | ++packets->count; 184 | } 185 | else 186 | { 187 | metaBatchSize = 1; // terminate outer loop too 188 | break; 189 | } 190 | 191 | } while (packets->count < batchSize); 192 | 193 | if (packets->count > 0) networkHub->sendBatch(packets); 194 | else networkHub->sendPool.relinquish(packets); 195 | 196 | } while (--metaBatchSize > 0); 197 | 198 | skip: 199 | usTil = picoquic_get_next_wake_delay(engine, timeNowUs(), 300'000); 200 | if (usTil > 300'000) usTil = 300'000; 201 | 202 | networkHub->recvmsgWithTimeout(usTil, [&] (UDPContext *msg) -> void { 203 | 204 | picoquic_incoming_packet(engine, msg->buffer(), msg->msg_len, msg->address(), NULL, 0, 0, timeNowUs()); 205 | }); 206 | 207 | } while (bytesInFlight != 0 && (count == 0 || --count > 0)); 208 | } 209 | 210 | public: 211 | 212 | void instanceSetup(uint16_t localPort, int argc, char *argv[]) 213 | { 214 | //printf("picoquic %s: instanceSetup\n", modeToString(mode)); 215 | 216 | networkHub = new NetworkHub(localPort); 217 | 218 | engine = picoquic_create(1000, tls_cert, tls_key, tls_chain, "perf", datain, this, NULL, NULL, NULL, timeNowUs(), NULL, NULL, NULL, 0); 219 | 220 | static constexpr int x25519 = 20; 221 | picoquic_set_key_exchange(engine, x25519); 222 | 223 | // performacne of aes-128 over aes-256 holds up for picotls-fusion as well 224 | /* 225 | type 2 bytes 31 bytes 136 bytes 1024 bytes 8192 bytes 16384 bytes 226 | ---- ------- -------- --------- ---------- ---------- ----------- 227 | aes-128-gcm 2326.81k 29137.11k 108788.53k 360005.63k 482115.58k 492399.27k 228 | aes-256-gcm 2256.78k 28163.19k 102470.97k 320374.44k 420995.35k 423843.16k 229 | chacha20-poly1305 807.76k 11608.37k 32482.65k 129967.34k 167075.84k 169951.23k 230 | */ 231 | 232 | /* Set cipher suite, for tests. 233 | * 0: default values 234 | * 20: chacha20poly1305sha256 235 | * 128: aes128gcmsha256 236 | * 256: aes256gcmsha384 237 | * returns 0 if OK, -1 if the specified ciphersuite is not supported. 238 | */ 239 | static constexpr int aes128gcmsha256 = 128; 240 | picoquic_set_cipher_suite(engine, aes128gcmsha256); 241 | 242 | // picoquic_set_default_congestion_algorithm(quic, picoquic_bbr_algorithm); 243 | 244 | picoquic_set_packet_train_mode(engine, 0); 245 | //picoquic_set_log_level(engine, 1); 246 | //picoquic_set_textlog(engine, "/dev/stdout"); 247 | //picoquic_set_client_authentication(engine, 1); 248 | } 249 | 250 | void connectToServer(struct sockaddr *address) 251 | { 252 | //printf("picoquic %s: connect\n", modeToString(mode)); 253 | 254 | // picoquic_cnx_t* picoquic_create_cnx(picoquic_quic_t* quic, picoquic_connection_id_t initial_cnx_id, picoquic_connection_id_t remote_cnx_id, const struct sockaddr* addr_to, uint64_t start_time, uint32_t preferred_version, char const* sni, char const* alpn, char client_mode); 255 | 256 | cnx = picoquic_create_cnx(engine, picoquic_null_connection_id, picoquic_null_connection_id, address, timeNowUs(), 0, "localhost", "perf", true); 257 | 258 | picoquic_set_callback(cnx, datain, this); 259 | 260 | st_picoquic_tp_t parameters = {}; 261 | picoquic_init_transport_parameters(¶meters, mode == Mode::client ? true : false); 262 | 263 | // parameters.initial_max_data = 9'000'000; 264 | // parameters.idle_timeout = 30'000; // milliseconds 265 | // parameters.max_packet_size = 1500; 266 | // //parameters.max_ack_delay = 6'400'000; // microseconds 267 | // //parameters.ack_delay_exponent = 8; 268 | // parameters.migration_disabled = false; 269 | 270 | picoquic_set_transport_parameters(cnx, ¶meters); 271 | 272 | picoquic_start_client_cnx(cnx); 273 | } 274 | 275 | void openStream(void) 276 | { 277 | //printf("picoquic %s: openStream\n", modeToString(mode)); 278 | 279 | // picoquic_mark_active_stream(cnx, 0, true, this); 280 | 281 | // do 282 | // { 283 | // advance(1); 284 | 285 | // } while (ready == false); 286 | 287 | // picoquic_mark_active_stream(cnx, 0, false, this); 288 | } 289 | 290 | void startPerfTest(uint64_t nBytes) 291 | { 292 | //printf("picoquic %s: startPerfTest\n", modeToString(mode)); 293 | 294 | if constexpr (mode & Mode::client) 295 | { 296 | bytesInFlight = nBytes; 297 | picoquic_mark_active_stream(cnx, 0, true, this); 298 | } 299 | 300 | advance(); 301 | } 302 | }; 303 | -------------------------------------------------------------------------------- /perf.networking.h: -------------------------------------------------------------------------------- 1 | #include "liburing.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #pragma once 8 | 9 | #define likely(x) __builtin_expect((x),1) 10 | #define unlikely(x) __builtin_expect((x),0) 11 | 12 | 13 | uint64_t timeNowUs(void) 14 | { 15 | return std::chrono::duration_cast((std::chrono::system_clock::now().time_since_epoch())).count(); 16 | } 17 | 18 | struct MultiUDPContext; 19 | 20 | template 21 | class Pool { 22 | private: 23 | 24 | uint32_t capacity; 25 | uint32_t watermark; 26 | 27 | T *base; 28 | std::vector available; 29 | 30 | public: 31 | 32 | Pool(uint32_t count) 33 | { 34 | capacity = count; 35 | watermark = 0; 36 | base = new T[count]; 37 | available.reserve(count); 38 | } 39 | 40 | uint32_t howManyLeft(void) 41 | { 42 | return (capacity - watermark) + available.size(); 43 | } 44 | 45 | T* get(void) 46 | { 47 | T *item = NULL; 48 | 49 | if (watermark == capacity) 50 | { 51 | if (available.size()) 52 | { 53 | item = available.back(); 54 | available.pop_back(); 55 | } 56 | } 57 | else 58 | { 59 | item = &base[watermark++]; 60 | } 61 | 62 | return item; 63 | } 64 | 65 | void relinquish(T *item) 66 | { 67 | available.emplace_back(item); 68 | } 69 | }; 70 | 71 | class UDPSocket { 72 | public: 73 | 74 | struct sockaddr_in6 *address6; 75 | socklen_t addressLen; 76 | int fd; 77 | 78 | template 79 | T* address(void) 80 | { 81 | return (T *)address6; 82 | } 83 | 84 | UDPSocket(uint16_t port) 85 | { 86 | fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); 87 | 88 | setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const uint32_t[]){ 10'000 * 1500 }, sizeof(uint32_t)); 89 | auto val = 1; 90 | setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, 91 | static_cast(sizeof(val))); 92 | 93 | addressLen = sizeof(struct sockaddr_in6); 94 | 95 | address6 = (struct sockaddr_in6 *)calloc(1, addressLen); 96 | address6->sin6_family = AF_INET6; 97 | address6->sin6_flowinfo = 0; 98 | address6->sin6_port = htons(port); 99 | address6->sin6_addr = serverAddress; 100 | 101 | bind(fd, (struct sockaddr *)address6, addressLen); 102 | } 103 | }; 104 | 105 | #define MAX_IPV6_UDP_PACKET_SIZE (1500 - 40 - 8) 106 | #define MAX_GRO_SIZE (MAX_IPV6_UDP_PACKET_SIZE * 64) 107 | 108 | struct UDPContext { 109 | 110 | struct msghdr msg_hdr; 111 | unsigned int msg_len; 112 | 113 | UDPContext() 114 | { 115 | memset(&msg_hdr, 0, sizeof(struct msghdr)); 116 | msg_hdr.msg_namelen = sizeof(struct sockaddr_in6); 117 | msg_hdr.msg_name = malloc(msg_hdr.msg_namelen); 118 | 119 | msg_hdr.msg_iov = (struct iovec *)malloc(sizeof(struct iovec)); 120 | msg_hdr.msg_iov[0].iov_len = MAX_GRO_SIZE; 121 | msg_hdr.msg_iov[0].iov_base = malloc(MAX_GRO_SIZE); 122 | msg_hdr.msg_iovlen = 1; 123 | 124 | // add the interface to every message 125 | 126 | // else if (cmsg->cmsg_level == IPPROTO_IPV6) { 127 | // if (cmsg->cmsg_type == IPV6_PKTINFO) { 128 | // if (addr_dest != NULL) { 129 | // struct in6_pktinfo* pPktInfo6 = (struct in6_pktinfo*)CMSG_DATA(cmsg); 130 | 131 | // ((struct sockaddr_in6*)addr_dest)->sin6_family = AF_INET6; 132 | // ((struct sockaddr_in6*)addr_dest)->sin6_port = 0; 133 | // memcpy(&((struct sockaddr_in6*)addr_dest)->sin6_addr, &pPktInfo6->ipi6_addr, sizeof(struct in6_addr)); 134 | 135 | // if (dest_if != NULL) { 136 | // *dest_if = (int)pPktInfo6->ipi6_ifindex; 137 | // } 138 | // } 139 | // } 140 | } 141 | 142 | template 143 | T* address(void) 144 | { 145 | return (T *)msg_hdr.msg_name; 146 | } 147 | 148 | uint8_t* buffer(void) 149 | { 150 | return (uint8_t *)msg_hdr.msg_iov[0].iov_base; 151 | } 152 | 153 | void setLength(uint16_t length) 154 | { 155 | msg_hdr.msg_iov[0].iov_len = length; 156 | msg_len = length; 157 | } 158 | 159 | void reset(void) 160 | { 161 | msg_len = 0; 162 | setLength(MAX_GRO_SIZE); 163 | } 164 | 165 | void copyInIov(struct iovec& opposingVec) 166 | { 167 | msg_len = opposingVec.iov_len; 168 | msg_hdr.msg_iov[0].iov_len = opposingVec.iov_len; 169 | 170 | memcpy(buffer(), opposingVec.iov_base, msg_len); 171 | } 172 | 173 | void copyInAddress(const struct sockaddr *destination) 174 | { 175 | memcpy(address(), destination, sizeof(struct sockaddr_in6)); 176 | } 177 | }; 178 | 179 | struct MultiUDPContext { 180 | 181 | static constexpr uint16_t batchSize = 150; 182 | 183 | UDPContext msgs[batchSize]; 184 | uint16_t count; 185 | 186 | UDPContext* nextPacket(void) 187 | { 188 | return (count < batchSize ? &msgs[count++] : NULL); 189 | } 190 | 191 | bool isFull(void) 192 | { 193 | return (count == batchSize); 194 | } 195 | 196 | void reset(void) 197 | { 198 | count = 0; 199 | 200 | for (auto i = 0; i < batchSize; i++) 201 | { 202 | msgs[i].reset(); 203 | } 204 | } 205 | }; 206 | 207 | struct Timeout { 208 | 209 | struct __kernel_timespec timeout = {}; // same as struct timespec; 210 | 211 | void setTimeout(uint32_t microseconds) 212 | { 213 | if (microseconds > 0) 214 | { 215 | timeout.tv_sec = microseconds / 1'000'000; 216 | timeout.tv_nsec = (microseconds % 1'000'000) * 1'000; 217 | } 218 | } 219 | 220 | float timeoutInSeconds(void) 221 | { 222 | return ((double)timeout.tv_sec + (double)timeout.tv_nsec / (double)1'000'000'000); 223 | } 224 | }; 225 | 226 | template 227 | class NetworkHub { 228 | private: 229 | 230 | struct io_uring ring; 231 | 232 | void setCallbackData(struct io_uring_sqe *sqe, uint8_t op, void *data) 233 | { 234 | sqe->user_data = ((uint64_t)op << 48) | (uint64_t)data; 235 | } 236 | 237 | MultiUDPContext recvContext; // for syscall recv-ing 238 | Pool recvPool; // for iouring recv-ing 239 | Timeout recvTimeout; 240 | 241 | public: 242 | 243 | uint8_t junk[94 * 1024]; 244 | 245 | UDPSocket socket; 246 | Pool sendPool; 247 | 248 | NetworkHub(uint16_t port) : socket(port), recvTimeout(), sendPool(50), recvPool(25) 249 | { 250 | if constexpr (mode & Mode::server) 251 | { 252 | RAND_bytes(junk, sizeof(junk)); 253 | } 254 | 255 | if constexpr (mode & Mode::iouring) 256 | { 257 | struct io_uring_params params = {}; 258 | 259 | io_uring_queue_init_params(16000, &ring, ¶ms); 260 | 261 | io_uring_register_files(&ring, &socket.fd, 1); 262 | } 263 | 264 | if constexpr (mode & Mode::server) 265 | { 266 | listen(socket.fd, SOMAXCONN); 267 | } 268 | } 269 | 270 | void sendBatch(MultiUDPContext *packets) 271 | { 272 | if constexpr (mode & Mode::syscall) 273 | { 274 | int result = sendmmsg(socket.fd, (struct mmsghdr *)packets->msgs, packets->count, 0); 275 | 276 | //if (result < 0) printf("syscall sendBatch -> errno = %d\n", errno); 277 | 278 | packets->reset(); 279 | sendPool.relinquish(packets); 280 | } 281 | else 282 | { 283 | struct io_uring_sqe *sqe; 284 | 285 | //printf("(A) sqe space left = %ld\n", *(ring.sq.kring_entries) - io_uring_sq_ready(&ring)); 286 | 287 | for (uint16_t i = 0; i < packets->count; i++) 288 | { 289 | struct msghdr& msg = packets->msgs[i].msg_hdr; 290 | 291 | sqe = io_uring_get_sqe(&ring); 292 | io_uring_prep_sendmsg(sqe, 0, &msg, 0); 293 | io_uring_sqe_set_flags(sqe, IOSQE_FIXED_FILE); 294 | } 295 | 296 | //printf("(B) sqe space left = %ld\n", *(ring.sq.kring_entries) - io_uring_sq_ready(&ring)); 297 | 298 | setCallbackData(sqe, IORING_OP_SENDMSG, packets); 299 | } 300 | } 301 | 302 | template 303 | bool recvmsgWithTimeout(int64_t timeoutus, Consumer&& msgConsumer) // timeout in microseconds 304 | { 305 | if constexpr (mode & Mode::syscall) 306 | { 307 | if (timeoutus > 0) 308 | { 309 | struct timeval t = {.tv_sec = timeoutus / 1'000'000, .tv_usec = (timeoutus % 1'000'000)}; 310 | 311 | fd_set read_fds; 312 | FD_ZERO(&read_fds); 313 | FD_SET(socket.fd, &read_fds); 314 | 315 | if (select(socket.fd + 1, &read_fds, NULL, NULL, &t) == 0) return true; 316 | } 317 | 318 | int result = recvmmsg(socket.fd, (struct mmsghdr *)recvContext.msgs, 150, MSG_WAITFORONE, NULL); 319 | 320 | //if (result < 0) printf("syscall sendBatch -> errno = %d\n", errno); 321 | 322 | for (auto i = 0; i < result; i++) 323 | { 324 | UDPContext *packet = &recvContext.msgs[i]; 325 | msgConsumer(packet); 326 | packet->reset(); 327 | } 328 | } 329 | else 330 | { 331 | // recvPool keep max in play at all times 332 | while (recvPool.howManyLeft() > 0) 333 | { 334 | UDPContext *context = recvPool.get(); 335 | context->setLength(MAX_IPV6_UDP_PACKET_SIZE); 336 | 337 | struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); 338 | io_uring_prep_recvmsg(sqe, 0, &context->msg_hdr, 0); 339 | io_uring_sqe_set_flags(sqe, IOSQE_FIXED_FILE); 340 | setCallbackData(sqe, IORING_OP_RECVMSG, context); 341 | } 342 | 343 | struct io_uring_cqe *cqe; 344 | uint64_t user_data; 345 | void *callbackBuffer; 346 | int op; 347 | int result; 348 | uint32_t head; 349 | uint32_t count; 350 | 351 | // printf("unconsumed cqes = %ld\n", io_uring_cq_ready(&ring)); 352 | // printf("unsubmitted sqes = %ld\n", io_uring_sq_ready(&ring)); 353 | // printf("cqe space left = %ld\n", *(ring.cq.kring_entries) - io_uring_cq_ready(&ring)); 354 | //if ((rand() % 250) == 0) printf("sqe space left = %ld\n", *(ring.sq.kring_entries) - io_uring_sq_ready(&ring)); 355 | 356 | io_uring_submit(&ring); 357 | 358 | recvTimeout.setTimeout(timeoutus); 359 | if (io_uring_wait_cqe_timeout(&ring, &cqe, &recvTimeout.timeout) < 0) return true; 360 | 361 | io_uring_for_each_cqe(&ring, head, cqe) 362 | { 363 | ++count; 364 | user_data = (uint64_t)io_uring_cqe_get_data(cqe); 365 | op = user_data >> 48; 366 | callbackBuffer = (void *)((user_data << 16) >> 16); 367 | result = cqe->res; 368 | 369 | switch (op) 370 | { 371 | case IORING_OP_RECVMSG: 372 | { 373 | //if (result < 0) printf("IORING_OP_RECVMSG, result = %d\n", result); 374 | 375 | if (result > 0) 376 | { 377 | UDPContext *packet = (UDPContext *)callbackBuffer; 378 | packet->msg_len = result; 379 | msgConsumer(packet); 380 | recvPool.relinquish(packet); 381 | } 382 | 383 | break; 384 | } 385 | case IORING_OP_SENDMSG: 386 | { 387 | //if (result < 0) printf("IORING_OP_SENDMMSG, result = %d\n", result); 388 | 389 | if (callbackBuffer) 390 | { 391 | MultiUDPContext *packets = (MultiUDPContext *)callbackBuffer; 392 | packets->reset(); 393 | sendPool.relinquish(packets); 394 | } 395 | 396 | break; 397 | } 398 | default: 399 | if (result < 0) 400 | { 401 | // printf("IORING_OP_SENDMSG, result = %d\n", result); 402 | // printf("unconsumed cqes = %ld\n", io_uring_cq_ready(&ring)); 403 | // printf("unsubmitted sqes = %ld\n", io_uring_sq_ready(&ring)); 404 | // printf("cqe space left = %ld\n", *(ring.cq.kring_entries) - io_uring_cq_ready(&ring)); 405 | // printf("sqe space left = %ld\n", *(ring.sq.kring_entries) - io_uring_sq_ready(&ring)); 406 | } 407 | break; 408 | } 409 | } 410 | 411 | // uint32_t cqesAvailable = io_uring_cq_ready(&ring); 412 | 413 | io_uring_cq_advance(&ring, count); 414 | } 415 | 416 | return false; 417 | } 418 | }; -------------------------------------------------------------------------------- /perf.ngtcp2.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #pragma once 11 | 12 | template class Ngtcp2 : public QuicLibrary { 13 | private: 14 | using QuicLibrary::networkHub; 15 | 16 | SSL_CTX *ssl_ctx = nullptr; 17 | SSL *ssl = nullptr; 18 | ngtcp2_conn *conn = nullptr; 19 | uint8_t alert = 0; 20 | int64_t bytesInFlight = -1; 21 | bool data_ready = false; 22 | std::array reqsizebuf; 23 | size_t reqsizebuflen = 0; 24 | size_t reqsizebufoffset = 0; 25 | bool stream_opened = false; 26 | 27 | static int set_read_secret(SSL *ssl, enum ssl_encryption_level_t ssl_level, 28 | const SSL_CIPHER *cipher, const uint8_t *secret, 29 | size_t secretlen) { 30 | auto c = static_cast *>(SSL_get_app_data(ssl)); 31 | auto level = ngtcp2_crypto_boringssl_from_ssl_encryption_level(ssl_level); 32 | 33 | if (ngtcp2_crypto_derive_and_install_rx_key( 34 | c->conn, nullptr, nullptr, nullptr, level, secret, secretlen) != 0) 35 | { 36 | return 0; 37 | } 38 | 39 | return 1; 40 | } 41 | 42 | static int set_write_secret(SSL *ssl, enum ssl_encryption_level_t ssl_level, 43 | const SSL_CIPHER *cipher, const uint8_t *secret, 44 | size_t secretlen) { 45 | auto c = static_cast *>(SSL_get_app_data(ssl)); 46 | auto level = ngtcp2_crypto_boringssl_from_ssl_encryption_level(ssl_level); 47 | 48 | if (ngtcp2_crypto_derive_and_install_tx_key( 49 | c->conn, nullptr, nullptr, nullptr, level, secret, secretlen) != 0) 50 | { 51 | return 0; 52 | } 53 | 54 | return 1; 55 | } 56 | 57 | static int add_handshake_data(SSL *ssl, enum ssl_encryption_level_t ssl_level, 58 | const uint8_t *data, size_t len) { 59 | auto c = static_cast *>(SSL_get_app_data(ssl)); 60 | auto level = ngtcp2_crypto_boringssl_from_ssl_encryption_level(ssl_level); 61 | 62 | if (auto rv = ngtcp2_conn_submit_crypto_data(c->conn, level, data, len); 63 | rv != 0) 64 | { 65 | std::cerr << "ngtcp2_conn_submit_crypto_data: " << ngtcp2_strerror(rv) 66 | << std::endl; 67 | assert(0); 68 | abort(); 69 | } 70 | 71 | return 1; 72 | } 73 | 74 | constexpr static int flush_flight(SSL *ssl) { return 1; } 75 | 76 | static int send_alert(SSL *ssl, enum ssl_encryption_level_t level, 77 | uint8_t alert) { 78 | auto c = static_cast *>(SSL_get_app_data(ssl)); 79 | 80 | c->alert = alert; 81 | 82 | return 1; 83 | } 84 | 85 | constexpr static auto quic_method = SSL_QUIC_METHOD{ 86 | set_read_secret, set_write_secret, add_handshake_data, 87 | flush_flight, send_alert, 88 | }; 89 | 90 | static void rand(uint8_t *dest, size_t destlen, 91 | const ngtcp2_rand_ctx *rand_ctx) { 92 | RAND_bytes(dest, static_cast(destlen)); 93 | } 94 | 95 | static int extend_max_stream_data_server(ngtcp2_conn *conn, int64_t stream_id, 96 | uint64_t max_data, void *user_data, 97 | void *stream_user_data) { 98 | if (stream_id != 0) 99 | { 100 | return 0; 101 | } 102 | 103 | auto c = static_cast *>(user_data); 104 | 105 | if (c->bytesInFlight > 0) 106 | { 107 | c->data_ready = true; 108 | } 109 | 110 | return 0; 111 | } 112 | 113 | static int recv_stream_data_server(ngtcp2_conn *conn, uint32_t flags, 114 | int64_t stream_id, uint64_t offset, 115 | const uint8_t *data, size_t datalen, 116 | void *user_data, void *stream_user_data) { 117 | if (stream_id != 0) 118 | { 119 | return 0; 120 | } 121 | 122 | auto c = static_cast *>(user_data); 123 | 124 | if (c->reqsizebuf.size() == c->reqsizebuflen) 125 | { 126 | return 0; 127 | } 128 | 129 | auto n = std::min(c->reqsizebuf.size() - c->reqsizebuflen, datalen); 130 | std::copy_n(data, n, c->reqsizebuf.data()); 131 | c->reqsizebuflen += n; 132 | 133 | if (c->reqsizebuf.size() > c->reqsizebuflen) 134 | { 135 | return 0; 136 | } 137 | 138 | memcpy(&c->bytesInFlight, c->reqsizebuf.data(), c->reqsizebuf.size()); 139 | c->bytesInFlight = bswap_64(c->bytesInFlight); 140 | 141 | c->data_ready = true; 142 | 143 | return 0; 144 | } 145 | 146 | static int recv_stream_data_client(ngtcp2_conn *conn, uint32_t flags, 147 | int64_t stream_id, uint64_t offset, 148 | const uint8_t *data, size_t datalen, 149 | void *user_data, void *stream_user_data) { 150 | ngtcp2_conn_extend_max_offset(conn, datalen); 151 | 152 | if (stream_id != 0) 153 | { 154 | return 0; 155 | } 156 | 157 | auto c = static_cast *>(user_data); 158 | 159 | if (c->bytesInFlight < datalen) 160 | { 161 | c->bytesInFlight = 0; 162 | } else 163 | { 164 | c->bytesInFlight -= datalen; 165 | 166 | ngtcp2_conn_extend_max_stream_offset(conn, stream_id, datalen); 167 | } 168 | 169 | return 0; 170 | } 171 | 172 | static int extend_max_streams_bidi_client(ngtcp2_conn *conn, 173 | uint64_t max_streams, 174 | void *user_data) { 175 | auto c = static_cast *>(user_data); 176 | 177 | if (c->stream_opened) 178 | { 179 | return 0; 180 | } 181 | 182 | int64_t stream_id; 183 | if (auto rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, nullptr); 184 | rv != 0) 185 | { 186 | assert(NGTCP2_ERR_STREAM_ID_BLOCKED == rv); 187 | return 0; 188 | } 189 | 190 | assert(0 == stream_id); 191 | 192 | c->stream_opened = true; 193 | 194 | return 0; 195 | } 196 | 197 | void init_conn_server(UDPContext *msg) { 198 | ngtcp2_pkt_hd hd; 199 | if (auto rv = ngtcp2_accept(&hd, msg->buffer(), msg->msg_len); rv != 0) 200 | { 201 | std::cerr << "ngtcp2_accept: " << rv << std::endl; 202 | assert(0); 203 | abort(); 204 | } 205 | 206 | auto callbacks = ngtcp2_callbacks{ 207 | nullptr, // client_initial 208 | ngtcp2_crypto_recv_client_initial_cb, 209 | ngtcp2_crypto_recv_crypto_data_cb, 210 | nullptr, // handshake_completed 211 | nullptr, // recv_version_negotiation 212 | ngtcp2_crypto_encrypt_cb, 213 | ngtcp2_crypto_decrypt_cb, 214 | ngtcp2_crypto_hp_mask_cb, 215 | recv_stream_data_server, 216 | nullptr, // acked_stream_data_offset 217 | nullptr, // stream_open 218 | nullptr, // stream_close 219 | nullptr, // recv_stateless_reset 220 | nullptr, // recv_retry 221 | nullptr, // extend_max_streams_bidi 222 | nullptr, // extend_max_streams_uni 223 | rand, 224 | nullptr, // get_new_connection_id 225 | nullptr, // remove_connection_id 226 | ngtcp2_crypto_update_key_cb, 227 | nullptr, // path_validation 228 | nullptr, // select_preferred_addr 229 | nullptr, // stream_reset 230 | nullptr, // extend_max_remote_streams_bidi 231 | nullptr, // extend_max_remote_streams_uni 232 | extend_max_stream_data_server, 233 | nullptr, // dcid_status 234 | nullptr, // handshake_confirmed 235 | nullptr, // recv_new_token 236 | ngtcp2_crypto_delete_crypto_aead_ctx_cb, 237 | ngtcp2_crypto_delete_crypto_cipher_ctx_cb, 238 | nullptr, // recv_datagram 239 | nullptr, // ack_datagram 240 | nullptr, // lost_datagram 241 | ngtcp2_crypto_get_path_challenge_data_cb, 242 | }; 243 | 244 | ngtcp2_settings settings; 245 | ngtcp2_settings_default(&settings); 246 | settings.initial_ts = timeNowUs() * NGTCP2_MICROSECONDS; 247 | settings.max_stream_window = 8 * 1024 * 1024; 248 | settings.max_window = 8 * 1024 * 1024; 249 | settings.max_udp_payload_size = MAX_IPV6_UDP_PACKET_SIZE; 250 | settings.no_udp_payload_size_shaping = 1; 251 | 252 | ngtcp2_transport_params params; 253 | ngtcp2_transport_params_default(¶ms); 254 | params.initial_max_streams_bidi = 100; 255 | params.initial_max_stream_data_bidi_remote = 1024 * 1024; 256 | params.initial_max_data = 1024 * 1024; 257 | params.original_dcid = hd.dcid; 258 | 259 | auto path = 260 | ngtcp2_path{{sizeof(struct sockaddr_in6), 261 | reinterpret_cast(networkHub->socket.address6)}, 262 | {sizeof(struct sockaddr_in6), msg->address()}}; 263 | 264 | ngtcp2_cid scid; 265 | ngtcp2_cid_init(&scid, nullptr, 0); 266 | 267 | if (auto rv = ngtcp2_conn_server_new(&conn, &hd.scid, &scid, &path, 268 | hd.version, &callbacks, &settings, 269 | ¶ms, nullptr, this); 270 | rv != 0) 271 | { 272 | std::cerr << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv) 273 | << std::endl; 274 | assert(0); 275 | abort(); 276 | } 277 | 278 | ssl = SSL_new(ssl_ctx); 279 | SSL_set_app_data(ssl, this); 280 | SSL_set_accept_state(ssl); 281 | SSL_set_quic_use_legacy_codepoint(ssl, 0); 282 | 283 | ngtcp2_conn_set_tls_native_handle(conn, ssl); 284 | } 285 | 286 | void init_conn_client(struct sockaddr *address) { 287 | auto callbacks = ngtcp2_callbacks{ 288 | ngtcp2_crypto_client_initial_cb, 289 | nullptr, // recv_client_initial 290 | ngtcp2_crypto_recv_crypto_data_cb, 291 | nullptr, // handshake_completed 292 | nullptr, // recv_version_negotiation 293 | ngtcp2_crypto_encrypt_cb, 294 | ngtcp2_crypto_decrypt_cb, 295 | ngtcp2_crypto_hp_mask_cb, 296 | recv_stream_data_client, 297 | nullptr, // acked_stream_data_offset 298 | nullptr, // stream_open 299 | nullptr, // stream_close 300 | nullptr, // recv_stateless_reset 301 | ngtcp2_crypto_recv_retry_cb, 302 | extend_max_streams_bidi_client, 303 | nullptr, // extend_max_streams_uni 304 | rand, 305 | nullptr, // get_new_connection_id 306 | nullptr, // remove_connection_id 307 | ngtcp2_crypto_update_key_cb, 308 | nullptr, // path_validation 309 | nullptr, // select_preferred_addr 310 | nullptr, // stream_reset 311 | nullptr, // extend_max_remote_streams_bidi 312 | nullptr, // extend_max_remote_streams_uni 313 | nullptr, // extend_max_stream_data 314 | nullptr, // dcid_status 315 | nullptr, // handshake_confirmed 316 | nullptr, // recv_new_token 317 | ngtcp2_crypto_delete_crypto_aead_ctx_cb, 318 | ngtcp2_crypto_delete_crypto_cipher_ctx_cb, 319 | nullptr, // recv_datagram 320 | nullptr, // ack_datagram 321 | nullptr, // lost_datagram 322 | ngtcp2_crypto_get_path_challenge_data_cb, 323 | }; 324 | 325 | ngtcp2_settings settings; 326 | ngtcp2_settings_default(&settings); 327 | settings.initial_ts = timeNowUs() * NGTCP2_MICROSECONDS; 328 | settings.max_stream_window = 8 * 1024 * 1024; 329 | settings.max_window = 8 * 1024 * 1024; 330 | settings.max_udp_payload_size = MAX_IPV6_UDP_PACKET_SIZE; 331 | settings.no_udp_payload_size_shaping = 1; 332 | 333 | ngtcp2_transport_params params; 334 | ngtcp2_transport_params_default(¶ms); 335 | params.initial_max_stream_data_bidi_local = 1024 * 1024; 336 | params.initial_max_data = 1024 * 1024; 337 | 338 | auto path = 339 | ngtcp2_path{{sizeof(struct sockaddr_in6), 340 | reinterpret_cast(networkHub->socket.address6)}, 341 | {sizeof(struct sockaddr_in6), address}}; 342 | 343 | ngtcp2_cid scid; 344 | ngtcp2_cid_init(&scid, nullptr, 0); 345 | 346 | ngtcp2_cid dcid; 347 | dcid.datalen = NGTCP2_MIN_INITIAL_DCIDLEN; 348 | RAND_bytes(dcid.data, static_cast(dcid.datalen)); 349 | 350 | if (auto rv = ngtcp2_conn_client_new(&conn, &dcid, &scid, &path, 351 | NGTCP2_PROTO_VER_V1, &callbacks, 352 | &settings, ¶ms, nullptr, this); 353 | rv != 0) 354 | { 355 | std::cerr << "ngtcp2_conn_client_new: " << ngtcp2_strerror(rv) 356 | << std::endl; 357 | assert(0); 358 | abort(); 359 | } 360 | 361 | ssl = SSL_new(ssl_ctx); 362 | SSL_set_app_data(ssl, this); 363 | SSL_set_connect_state(ssl); 364 | SSL_set_quic_use_legacy_codepoint(ssl, 0); 365 | 366 | ngtcp2_conn_set_tls_native_handle(conn, ssl); 367 | } 368 | 369 | std::tuple get_stream_data_server() { 370 | int64_t stream_id = -1; 371 | size_t vcnt = 0; 372 | ngtcp2_vec vec{}; 373 | uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; 374 | 375 | if (data_ready && bytesInFlight >= 0 && ngtcp2_conn_get_max_data_left(conn)) 376 | { 377 | auto n = std::min(static_cast(sizeof(networkHub->junk)), 378 | bytesInFlight); 379 | vec.len = n; 380 | vec.base = networkHub->junk; 381 | vcnt = 1; 382 | stream_id = 0; 383 | 384 | if (n == bytesInFlight) 385 | { 386 | flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; 387 | } 388 | } 389 | 390 | return {stream_id, vec, vcnt, flags}; 391 | } 392 | 393 | std::tuple get_stream_data_client() { 394 | int64_t stream_id = -1; 395 | size_t vcnt = 0; 396 | ngtcp2_vec vec{}; 397 | uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; 398 | 399 | if (data_ready && ngtcp2_conn_get_max_data_left(conn)) 400 | { 401 | vec.len = reqsizebuflen - reqsizebufoffset; 402 | vec.base = reqsizebuf.data() + reqsizebufoffset; 403 | vcnt = 1; 404 | stream_id = 0; 405 | flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; 406 | } 407 | 408 | return {stream_id, vec, vcnt, flags}; 409 | } 410 | 411 | std::tuple get_stream_data() { 412 | if constexpr (mode & Mode::server) 413 | { 414 | return get_stream_data_server(); 415 | } else 416 | { return get_stream_data_client(); } 417 | } 418 | 419 | void stream_data_sent_server(size_t datalen) { 420 | bytesInFlight -= datalen; 421 | if (bytesInFlight == 0) 422 | { 423 | data_ready = false; 424 | } 425 | } 426 | 427 | void stream_data_sent_client(size_t datalen) { 428 | reqsizebufoffset += datalen; 429 | if (reqsizebufoffset == reqsizebuflen) 430 | { 431 | data_ready = false; 432 | } 433 | } 434 | 435 | void stream_data_sent(size_t datalen) { 436 | if constexpr (mode & Mode::server) 437 | { 438 | return stream_data_sent_server(datalen); 439 | } else 440 | { return stream_data_sent_client(datalen); } 441 | } 442 | 443 | void send_packet(ngtcp2_tstamp ts) { 444 | auto packets = networkHub->sendPool.get(); 445 | 446 | if (ts >= ngtcp2_conn_get_expiry(conn)) 447 | { 448 | if (auto rv = ngtcp2_conn_handle_expiry(conn, ts); rv != 0) 449 | { 450 | std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv) 451 | << std::endl; 452 | assert(0); 453 | abort(); 454 | } 455 | } 456 | 457 | ngtcp2_ssize nwrite; 458 | 459 | do 460 | { 461 | auto packet = &packets->msgs[packets->count]; 462 | auto remote_addr = packet->address(); 463 | 464 | for (;;) 465 | { 466 | sockaddr_storage local_addr; 467 | 468 | auto [stream_id, vec, vcnt, flags] = get_stream_data(); 469 | auto path = ngtcp2_path{ 470 | {0, reinterpret_cast(&local_addr)}, 471 | {0, reinterpret_cast(remote_addr)}, 472 | }; 473 | 474 | ngtcp2_ssize ndatalen; 475 | nwrite = ngtcp2_conn_writev_stream( 476 | conn, &path, nullptr, packet->buffer(), MAX_IPV6_UDP_PACKET_SIZE, 477 | &ndatalen, flags, stream_id, &vec, vcnt, ts); 478 | if (nwrite < 0) 479 | { 480 | switch (nwrite) 481 | { 482 | case NGTCP2_ERR_STREAM_DATA_BLOCKED: 483 | case NGTCP2_ERR_STREAM_SHUT_WR: 484 | data_ready = false; 485 | continue; 486 | case NGTCP2_ERR_WRITE_MORE: 487 | stream_data_sent(static_cast(ndatalen)); 488 | continue; 489 | } 490 | 491 | std::cerr << "ngtcp2_conn_writev_stream: " 492 | << ngtcp2_strerror(static_cast(nwrite)) << std::endl; 493 | assert(0); 494 | abort(); 495 | } else if (ndatalen >= 0) 496 | { stream_data_sent(static_cast(ndatalen)); } 497 | 498 | if (nwrite == 0) 499 | { 500 | break; 501 | } 502 | 503 | packet->msg_hdr.msg_iov[0].iov_len = nwrite; 504 | ++packets->count; 505 | 506 | break; 507 | } 508 | } while (nwrite > 0 && packets->count < MultiUDPContext::batchSize); 509 | 510 | if (packets->count > 0) 511 | { 512 | networkHub->sendBatch(packets); 513 | } else 514 | { networkHub->sendPool.relinquish(packets); } 515 | } 516 | 517 | void advance(int32_t count = 0) { 518 | do 519 | { 520 | int64_t usTil = 0; 521 | if (conn) 522 | { 523 | auto now = timeNowUs() * NGTCP2_MICROSECONDS; 524 | 525 | send_packet(now); 526 | 527 | auto expiry = ngtcp2_conn_get_expiry(conn); 528 | if (expiry != std::numeric_limits::max() && now < expiry) 529 | { 530 | usTil = static_cast(std::max( 531 | (expiry - now) / NGTCP2_MICROSECONDS, static_cast(1))); 532 | } 533 | } 534 | 535 | networkHub->recvmsgWithTimeout(usTil, [&](UDPContext *msg) -> void { 536 | if constexpr (mode & Mode::server) 537 | { 538 | if (!conn) 539 | { 540 | init_conn_server(msg); 541 | } 542 | } 543 | 544 | auto path = ngtcp2_path{ 545 | {sizeof(struct sockaddr_in6), 546 | reinterpret_cast(networkHub->socket.address6)}, 547 | {sizeof(struct sockaddr_in6), msg->address()}}; 548 | auto pi = ngtcp2_pkt_info{}; 549 | 550 | if (auto rv = ngtcp2_conn_read_pkt(conn, &path, &pi, msg->buffer(), 551 | msg->msg_len, 552 | timeNowUs() * NGTCP2_MICROSECONDS); 553 | rv != 0) 554 | { 555 | std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) 556 | << std::endl; 557 | assert(0); 558 | abort(); 559 | } 560 | }); 561 | } while (bytesInFlight != 0 && (count == 0 || --count > 0)); 562 | } 563 | 564 | public: 565 | void instanceSetup(uint16_t localPort, int argc, char *argv[]) { 566 | networkHub = new NetworkHub(localPort); 567 | 568 | ssl_ctx = TLS::getTLSCtx(); 569 | 570 | SSL_CTX_set_quic_method(ssl_ctx, &quic_method); 571 | } 572 | 573 | void connectToServer(struct sockaddr *address) { init_conn_client(address); } 574 | 575 | void openStream(void) {} 576 | 577 | void startPerfTest(uint64_t nBytes) { 578 | if constexpr (mode & Mode::client) 579 | { 580 | bytesInFlight = nBytes; 581 | data_ready = true; 582 | 583 | auto n = bswap_64(bytesInFlight); 584 | reqsizebuflen = sizeof(n); 585 | memcpy(reqsizebuf.data(), &n, reqsizebuflen); 586 | } 587 | 588 | advance(); 589 | } 590 | }; 591 | --------------------------------------------------------------------------------