diff --git a/.azure/azure-pipelines.perf.yml b/.azure/azure-pipelines.perf.yml index 2481171b2..6bbd669b4 100644 --- a/.azure/azure-pipelines.perf.yml +++ b/.azure/azure-pipelines.perf.yml @@ -40,9 +40,9 @@ stages: tls: schannel config: Release ${{ if eq(parameters.mode, 'PGO') }}: - extraBuildArgs: -DisableTest -PGO + extraBuildArgs: -DisableTest -DisableTools -PGO ${{ if ne(parameters.mode, 'PGO') }}: - extraBuildArgs: -DisableTest + extraBuildArgs: -DisableTest -DisableTools - template: ./templates/build-config-user.yml parameters: image: windows-latest @@ -51,9 +51,9 @@ stages: tls: schannel config: Release ${{ if eq(parameters.mode, 'PGO') }}: - extraBuildArgs: -DisableTest -PGO + extraBuildArgs: -DisableTest -DisableTools -PGO ${{ if ne(parameters.mode, 'PGO') }}: - extraBuildArgs: -DisableTest + extraBuildArgs: -DisableTest -DisableTools # # Performance Tests diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c6a741c8..874bf2f11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,7 @@ endif() option(QUIC_BUILD_TOOLS "Builds the tools code" ON) option(QUIC_BUILD_TEST "Builds the test code" ON) +option(QUIC_BUILD_PERF "Builds the perf code" ON) option(QUIC_ENABLE_LOGGING "Enables logging" ON) option(QUIC_SANITIZE_ADDRESS "Enables address sanitizer" OFF) option(QUIC_STATIC_LINK_CRT "Statically links the C runtime" ON) @@ -359,6 +360,12 @@ if(QUIC_BUILD_TOOLS) add_subdirectory(src/tools) endif() +# Performance code +if(QUIC_BUILD_PERF) + add_subdirectory(src/perf/lib) + add_subdirectory(src/perf/bin) +endif() + # Test code if(QUIC_BUILD_TEST) # Build the googletest framework. diff --git a/msquic.kernel.sln b/msquic.kernel.sln index 7ae75fe02..4441e68a3 100644 --- a/msquic.kernel.sln +++ b/msquic.kernel.sln @@ -18,6 +18,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "msquictest.kernel", "src\te EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "manifest.kernel", "src\manifest\manifest.kernel.vcxproj", "{C1ADB76F-7005-4516-BADB-2A60797EF912}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "perflib.kernel", "src\perf\lib\perflib.kernel.vcxproj", "{11633785-79CC-4C7D-AB6A-AECDF29A1FA7}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "performance.kernel", "src\perf\bin\performance.kernel.vcxproj", "{2BE64DBF-60E6-4FE8-96B0-5F2526405096}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM = Debug|ARM @@ -174,6 +178,54 @@ Global {C1ADB76F-7005-4516-BADB-2A60797EF912}.Release|x86.ActiveCfg = Release|Win32 {C1ADB76F-7005-4516-BADB-2A60797EF912}.Release|x86.Build.0 = Release|Win32 {C1ADB76F-7005-4516-BADB-2A60797EF912}.Release|x86.Deploy.0 = Release|Win32 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|ARM.ActiveCfg = Debug|ARM + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|ARM.Build.0 = Debug|ARM + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|ARM.Deploy.0 = Debug|ARM + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|ARM64.Build.0 = Debug|ARM64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|x64.ActiveCfg = Debug|x64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|x64.Build.0 = Debug|x64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|x64.Deploy.0 = Debug|x64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|x86.ActiveCfg = Debug|Win32 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|x86.Build.0 = Debug|Win32 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Debug|x86.Deploy.0 = Debug|Win32 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|ARM.ActiveCfg = Release|ARM + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|ARM.Build.0 = Release|ARM + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|ARM.Deploy.0 = Release|ARM + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|ARM64.ActiveCfg = Release|ARM64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|ARM64.Build.0 = Release|ARM64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|ARM64.Deploy.0 = Release|ARM64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|x64.ActiveCfg = Release|x64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|x64.Build.0 = Release|x64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|x64.Deploy.0 = Release|x64 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|x86.ActiveCfg = Release|Win32 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|x86.Build.0 = Release|Win32 + {11633785-79CC-4C7D-AB6A-AECDF29A1FA7}.Release|x86.Deploy.0 = Release|Win32 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|ARM.ActiveCfg = Debug|ARM + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|ARM.Build.0 = Debug|ARM + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|ARM.Deploy.0 = Debug|ARM + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|ARM64.Build.0 = Debug|ARM64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|x64.ActiveCfg = Debug|x64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|x64.Build.0 = Debug|x64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|x64.Deploy.0 = Debug|x64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|x86.ActiveCfg = Debug|Win32 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|x86.Build.0 = Debug|Win32 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Debug|x86.Deploy.0 = Debug|Win32 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|ARM.ActiveCfg = Release|ARM + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|ARM.Build.0 = Release|ARM + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|ARM.Deploy.0 = Release|ARM + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|ARM64.ActiveCfg = Release|ARM64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|ARM64.Build.0 = Release|ARM64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|ARM64.Deploy.0 = Release|ARM64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|x64.ActiveCfg = Release|x64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|x64.Build.0 = Release|x64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|x64.Deploy.0 = Release|x64 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|x86.ActiveCfg = Release|Win32 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|x86.Build.0 = Release|Win32 + {2BE64DBF-60E6-4FE8-96B0-5F2526405096}.Release|x86.Deploy.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/scripts/RemoteTests.json b/scripts/RemoteTests.json index d6c03d61e..d9206f54f 100644 --- a/scripts/RemoteTests.json +++ b/scripts/RemoteTests.json @@ -5,9 +5,9 @@ "Platform": "Windows", "Tls": ["stub", "schannel", "mitls"], "Arch": ["x64", "x86", "arm", "arm64"], - "Exe": "quicping", + "Exe": "quicperf", "Arguments": { - "All": "-listen:* -port:4433 -peer_uni:1 -connections:10", + "All": "-TestName:Throughput -ServerMode:1 -listen:* -port:4433 -peer_uni:1 -connections:10", "Loopback": "-selfsign:1", "Remote": "-thumbprint:$Thumbprint -machine_cert:1 -cert_store:My" } @@ -16,16 +16,16 @@ "Platform": "Windows", "Tls": ["stub", "schannel", "mitls"], "Arch": ["x64", "x86", "arm", "arm64"], - "Exe": "quicping", + "Exe": "quicperf", "Arguments": { - "All": "-target:$RemoteAddress -port:4433 -sendbuf:0 -bind:$LocalAddress:4434 -ip:4 -core:0 -uni:1 -length:2000000000", + "All": "-TestName:Throughput -target:$RemoteAddress -port:4433 -sendbuf:0 -bind:$LocalAddress:4434 -ip:4 -core:0 -uni:1 -length:2000000000", "Loopback": "", "Remote": "" } }, "Iterations": 10, - "RemoteReadyMatcher": "Ready For Connections!", - "ResultsMatcher": "Closed.*\\(TX.*bytes @ (.*) kbps \\|" + "RemoteReadyMatcher": "Started!", + "ResultsMatcher": "Closed.*\\(TX.*bytes @ (.*) kbps\\)" }, { "TestName": "Throughput", @@ -33,9 +33,9 @@ "Platform": "Linux", "Tls": ["stub", "openssl"], "Arch": ["x64", "arm"], - "Exe": "quicping", + "Exe": "quicperf", "Arguments": { - "All": "-listen:* -port:4433 -selfsign:1 -peer_uni:1 -connections:10", + "All": "-TestName:Throughput -ServerMode:1 -listen:* -port:4433 -selfsign:1 -peer_uni:1 -connections:10", "Loopback": "", "Remote": "" } @@ -44,16 +44,16 @@ "Platform": "linux", "Tls": ["stub", "openssl"], "Arch": ["x64", "arm"], - "Exe": "quicping", + "Exe": "quicperf", "Arguments": { - "All": "-target:$RemoteAddress -port:4433 -sendbuf:0 -uni:1 -length:2000000000", + "All": "-TestName:Throughput -target:$RemoteAddress -port:4433 -sendbuf:0 -uni:1 -length:2000000000", "Loopback": "", "Remote": "" } }, "Iterations": 10, - "RemoteReadyMatcher": "Ready For Connections!", - "ResultsMatcher": "Closed.*\\(TX.*bytes @ (.*) kbps \\|" + "RemoteReadyMatcher": "Started!", + "ResultsMatcher": "Closed.*\\(TX.*bytes @ (.*) kbps\\)" }, { "TestName": "ThroughputNoEncryption", @@ -61,9 +61,9 @@ "Platform": "Windows", "Tls": ["stub", "schannel", "mitls"], "Arch": ["x64", "x86", "arm", "arm64"], - "Exe": "quicping", + "Exe": "quicperf", "Arguments": { - "All": "-listen:* -port:4433 -peer_uni:1 -connections:10", + "All": "-TestName:Throughput -ServerMode:1 -listen:* -port:4433 -peer_uni:1 -connections:10", "Loopback": "-selfsign:1", "Remote": "-thumbprint:$Thumbprint -machine_cert:1 -cert_store:My" } @@ -72,16 +72,16 @@ "Platform": "Windows", "Tls": ["stub", "schannel", "mitls"], "Arch": ["x64", "x86", "arm", "arm64"], - "Exe": "quicping", + "Exe": "quicperf", "Arguments": { - "All": "-target:$RemoteAddress -port:4433 -sendbuf:0 -bind:$LocalAddress:4434 -ip:4 -core:0 -uni:1 -encrypt:0 -length:2000000000", + "All": "-TestName:Throughput -target:$RemoteAddress -port:4433 -sendbuf:0 -bind:$LocalAddress:4434 -ip:4 -core:0 -uni:1 -encrypt:0 -length:2000000000", "Loopback": "", "Remote": "" } }, "Iterations": 10, - "RemoteReadyMatcher": "Ready For Connections!", - "ResultsMatcher": "Closed.*\\(TX.*bytes @ (.*) kbps \\|" + "RemoteReadyMatcher": "Started!", + "ResultsMatcher": "Closed.*\\(TX.*bytes @ (.*) kbps\\)" }, { "TestName": "ThroughputNoEncryption", @@ -89,9 +89,9 @@ "Platform": "Linux", "Tls": ["stub", "openssl"], "Arch": ["x64", "arm"], - "Exe": "quicping", + "Exe": "quicperf", "Arguments": { - "All": "-listen:* -port:4433 -selfsign:1 -peer_uni:1 -connections:10", + "All": "-TestName:Throughput -ServerMode:1 -listen:* -port:4433 -selfsign:1 -peer_uni:1 -connections:10", "Loopback": "", "Remote": "" } @@ -100,15 +100,15 @@ "Platform": "linux", "Tls": ["stub", "openssl"], "Arch": ["x64", "arm"], - "Exe": "quicping", + "Exe": "quicperf", "Arguments": { - "All": "-target:$RemoteAddress -port:4433 -sendbuf:0 -uni:1 -encrypt:0 -length:2000000000", + "All": "-TestName:Throughput -target:$RemoteAddress -port:4433 -sendbuf:0 -uni:1 -encrypt:0 -length:2000000000", "Loopback": "", "Remote": "" } }, "Iterations": 10, - "RemoteReadyMatcher": "Ready For Connections!", - "ResultsMatcher": "Closed.*\\(TX.*bytes @ (.*) kbps \\|" + "RemoteReadyMatcher": "Started!", + "ResultsMatcher": "Closed.*\\(TX.*bytes @ (.*) kbps\\)" } ] diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 5f3db79d4..c5fc2682d 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -30,6 +30,9 @@ This script provides helpers for building msquic. .PARAMETER DisableTest Don't build the test directory. +.PARAMETER DisablePerf + Don't build the perf directory. + .PARAMETER Clean Deletes all previous build and configuration. @@ -93,6 +96,9 @@ param ( [Parameter(Mandatory = $false)] [switch]$DisableTest = $false, + + [Parameter(Mandatory = $false)] + [switch]$DisablePerf = $false, [Parameter(Mandatory = $false)] [switch]$Clean = $false, @@ -220,6 +226,9 @@ function CMake-Generate { if ($DisableTest) { $Arguments += " -DQUIC_BUILD_TEST=off" } + if ($DisablePerf) { + $Arguments += " -DQUIC_BUILD_PERF=off" + } if ($IsLinux) { $Arguments += " -DCMAKE_BUILD_TYPE=" + $Config } diff --git a/src/inc/msquic.hpp b/src/inc/msquic.hpp index 1ab141ac3..75968a5a2 100644 --- a/src/inc/msquic.hpp +++ b/src/inc/msquic.hpp @@ -52,18 +52,111 @@ struct QuicAddr { template class UniquePtr { - T* ptr; public: - UniquePtr() : ptr(nullptr) { } - UniquePtr(T* _ptr) : ptr(_ptr) { } - ~UniquePtr() { delete ptr; } - T* get() { return ptr; } - const T* get() const { return ptr; } + UniquePtr() noexcept = default; + + explicit UniquePtr(T* _ptr) : ptr{_ptr} { } + UniquePtr(const UniquePtr& other) = delete; + UniquePtr& operator=(const UniquePtr& other) = delete; + + UniquePtr(UniquePtr&& other) noexcept { + this->ptr = other->ptr; + other->ptr = nullptr; + } + + UniquePtr& operator=(UniquePtr&& other) noexcept { + if (this->ptr) { + delete this->ptr; + } + this->ptr = other->ptr; + other->ptr = nullptr; + } + + ~UniquePtr() noexcept { + if (this->ptr) { + delete this->ptr; + } + } + + void reset(T* lptr) noexcept { + if (this->ptr) { + delete this->ptr; + } + this->ptr = lptr; + } + + T* release() noexcept { + T* tmp = ptr; + ptr = nullptr; + return tmp; + } + + T* get() const noexcept { return ptr; } + T& operator*() const { return *ptr; } - T* operator->() const { return ptr; } - operator bool() const { return ptr != nullptr; } - bool operator == (T* _ptr) const { return ptr == _ptr; } - bool operator != (T* _ptr) const { return ptr != _ptr; } + T* operator->() const noexcept { return ptr; } + operator bool() const noexcept { return ptr != nullptr; } + bool operator == (T* _ptr) const noexcept { return ptr == _ptr; } + bool operator != (T* _ptr) const noexcept { return ptr != _ptr; } + +private: + T* ptr = nullptr; +}; + +template +class UniquePtr { +public: + UniquePtr() noexcept = default; + + explicit UniquePtr(T* _ptr) : ptr{_ptr} { } + + UniquePtr(const UniquePtr& other) = delete; + UniquePtr& operator=(const UniquePtr& other) = delete; + + UniquePtr(UniquePtr&& other) noexcept { + this->ptr = other->ptr; + other->ptr = nullptr; + } + + UniquePtr& operator=(UniquePtr&& other) noexcept { + if (this->ptr) { + delete[] this->ptr; + } + this->ptr = other->ptr; + other->ptr = nullptr; + } + + ~UniquePtr() noexcept { + if (this->ptr) { + delete[] this->ptr; + } + } + + void reset(T* _ptr) noexcept { + if (this->ptr) { + delete[] this->ptr; + } + this->ptr = _ptr; + } + + T* release() noexcept { + T* tmp = ptr; + ptr = nullptr; + return tmp; + } + + T* get() const noexcept { return ptr; } + + T& operator[](size_t i) const { + return *(ptr + i); + } + + operator bool() const noexcept { return ptr != nullptr; } + bool operator == (T* _ptr) const noexcept { return ptr == _ptr; } + bool operator != (T* _ptr) const noexcept { return ptr != _ptr; } + +private: + T* ptr = nullptr; }; template @@ -84,18 +177,21 @@ public: class QuicApiTable : public QUIC_API_TABLE { QUIC_STATUS Init; + const QUIC_API_TABLE* ApiTable{nullptr}; public: QuicApiTable() { - const QUIC_API_TABLE* table; - if (QUIC_SUCCEEDED(Init = MsQuicOpen(&table))) { + if (QUIC_SUCCEEDED(Init = MsQuicOpen(&ApiTable))) { QUIC_API_TABLE* thisTable = this; - QuicCopyMemory(thisTable, table, sizeof(*table)); + QuicCopyMemory(thisTable, ApiTable, sizeof(*ApiTable)); } } ~QuicApiTable() { if (QUIC_SUCCEEDED(Init)) { - MsQuicClose(this); + MsQuicClose(ApiTable); + ApiTable = nullptr; + QUIC_API_TABLE* thisTable = this; + QuicZeroMemory(thisTable, sizeof(*thisTable)); } } @@ -106,10 +202,15 @@ public: class MsQuicRegistration { HQUIC Registration; + QUIC_STATUS InitStatus; public: MsQuicRegistration() { QuicZeroMemory(&Registration, sizeof(Registration)); - if (QUIC_FAILED(MsQuic->RegistrationOpen(nullptr, &Registration))) { + if (QUIC_FAILED( + InitStatus = + MsQuic->RegistrationOpen( + nullptr, + &Registration))) { Registration = nullptr; } } @@ -118,29 +219,58 @@ public: MsQuic->RegistrationClose(Registration); } } + QUIC_STATUS GetInitStatus() const { return InitStatus; } bool IsValid() const { return Registration != nullptr; } MsQuicRegistration(MsQuicRegistration& other) = delete; MsQuicRegistration operator=(MsQuicRegistration& Other) = delete; - operator HQUIC () { + operator HQUIC () const { return Registration; } }; -struct MsQuicSession { - HQUIC Handle; - bool CloseAllConnectionsOnDelete; +class MsQuicSession { + bool CloseAllConnectionsOnDelete {false}; + QUIC_STATUS InitStatus; +public: + HQUIC Handle {nullptr}; + MsQuicSession( + _In_ const MsQuicRegistration& Reg, + _In_z_ const char* RawAlpn = "MsQuicTest") + : Handle(nullptr), CloseAllConnectionsOnDelete(false) { + if (!Reg.IsValid()) { + InitStatus = Reg.GetInitStatus(); + return; + } + QUIC_BUFFER Alpn; + Alpn.Buffer = (uint8_t*)RawAlpn; + Alpn.Length = (uint32_t)strlen(RawAlpn); + if (QUIC_FAILED( + InitStatus = + MsQuic->SessionOpen( + Reg, + &Alpn, + 1, + nullptr, + &Handle))) { + Handle = nullptr; + } + } + +#ifndef QUIC_SKIP_GLOBAL_CONSTRUCTORS + MsQuicSession(_In_z_ const char* RawAlpn = "MsQuicTest") : Handle(nullptr), CloseAllConnectionsOnDelete(false) { QUIC_BUFFER Alpn; Alpn.Buffer = (uint8_t*)RawAlpn; Alpn.Length = (uint32_t)strlen(RawAlpn); if (QUIC_FAILED( - MsQuic->SessionOpen( - Registration, - &Alpn, - 1, - nullptr, - &Handle))) { + InitStatus = + MsQuic->SessionOpen( + Registration, + &Alpn, + 1, + nullptr, + &Handle))) { Handle = nullptr; } } @@ -152,15 +282,17 @@ struct MsQuicSession { Alpns[1].Buffer = (uint8_t*)RawAlpn2; Alpns[1].Length = (uint32_t)strlen(RawAlpn2); if (QUIC_FAILED( - MsQuic->SessionOpen( - Registration, - Alpns, - ARRAYSIZE(Alpns), - nullptr, - &Handle))) { + InitStatus = + MsQuic->SessionOpen( + Registration, + Alpns, + ARRAYSIZE(Alpns), + nullptr, + &Handle))) { Handle = nullptr; } } +#endif ~MsQuicSession() { if (Handle != nullptr) { if (CloseAllConnectionsOnDelete) { @@ -172,12 +304,13 @@ struct MsQuicSession { MsQuic->SessionClose(Handle); } } + QUIC_STATUS GetInitStatus() const { return InitStatus; } bool IsValid() const { return Handle != nullptr; } MsQuicSession(MsQuicSession& other) = delete; MsQuicSession operator=(MsQuicSession& Other) = delete; - operator HQUIC () { + operator HQUIC () const { return Handle; } void SetAutoCleanup() { @@ -289,11 +422,71 @@ struct MsQuicSession { } }; +struct MsQuicListener { + HQUIC Handle { nullptr }; + QUIC_STATUS InitStatus; + QUIC_LISTENER_CALLBACK_HANDLER Handler { nullptr }; + void* Context{ nullptr }; + + MsQuicListener(const MsQuicSession& Session) { + if (!Session.IsValid()) { + InitStatus = Session.GetInitStatus(); + return; + } + if (QUIC_FAILED( + InitStatus = + MsQuic->ListenerOpen( + Session, + [](HQUIC Handle, void* Context, QUIC_LISTENER_EVENT* Event) -> QUIC_STATUS { + MsQuicListener* Listener = (MsQuicListener*)Context; + return Listener->Handler(Handle, Listener->Context, Event); + }, + this, + &Handle))) { + Handle = nullptr; + } + } + ~MsQuicListener() noexcept { + if (Handler != nullptr) { + MsQuic->ListenerStop(Handle); + } + if (Handle) { + MsQuic->ListenerClose(Handle); + } + } + + QUIC_STATUS + Start( + _In_ QUIC_ADDR* Address, + _In_ QUIC_LISTENER_CALLBACK_HANDLER _Handler, + _In_ void* _Context) { + Handler = _Handler; + Context = _Context; + return MsQuic->ListenerStart(Handle, Address); + } + + QUIC_STATUS + ListenerCallback(HQUIC Listener, QUIC_LISTENER_EVENT* Event) { + return Handler(Listener, Context, Event); + } + + QUIC_STATUS GetInitStatus() const { return InitStatus; } + bool IsValid() const { + return Handle != nullptr; + } + MsQuicListener(MsQuicListener& other) = delete; + MsQuicListener operator=(MsQuicListener& Other) = delete; + operator HQUIC () const { + return Handle; + } +}; + struct ListenerScope { HQUIC Handle; ListenerScope() : Handle(nullptr) { } ListenerScope(HQUIC handle) : Handle(handle) { } ~ListenerScope() { if (Handle) { MsQuic->ListenerClose(Handle); } } + operator HQUIC() const { return Handle; } }; struct ConnectionScope { @@ -301,6 +494,7 @@ struct ConnectionScope { ConnectionScope() : Handle(nullptr) { } ConnectionScope(HQUIC handle) : Handle(handle) { } ~ConnectionScope() { if (Handle) { MsQuic->ConnectionClose(Handle); } } + operator HQUIC() const { return Handle; } }; struct StreamScope { @@ -308,6 +502,7 @@ struct StreamScope { StreamScope() : Handle(nullptr) { } StreamScope(HQUIC handle) : Handle(handle) { } ~StreamScope() { if (Handle) { MsQuic->StreamClose(Handle); } } + operator HQUIC() const { return Handle; } }; struct EventScope { @@ -315,6 +510,7 @@ struct EventScope { EventScope() { QuicEventInitialize(&Handle, FALSE, FALSE); } EventScope(QUIC_EVENT event) : Handle(event) { } ~EventScope() { QuicEventUninitialize(Handle); } + operator QUIC_EVENT() const { return Handle; } }; struct QuicBufferScope { diff --git a/src/inc/msquic_linux.h b/src/inc/msquic_linux.h index 60a0bec2d..3ff525bd5 100644 --- a/src/inc/msquic_linux.h +++ b/src/inc/msquic_linux.h @@ -20,6 +20,7 @@ Environment: #define _MSQUIC_LINUX_ #include +#include #include #include #include diff --git a/src/inc/msquic_winkernel.h b/src/inc/msquic_winkernel.h index 1bcacd2aa..05a5a381c 100644 --- a/src/inc/msquic_winkernel.h +++ b/src/inc/msquic_winkernel.h @@ -284,4 +284,58 @@ QuicAddrHash( #define QUIC_LOCALHOST_FOR_AF(Af) "localhost" +inline +BOOLEAN +QuicAddrFromString( + _In_z_ const char* AddrStr, + _In_ uint16_t Port, // Host byte order + _Out_ QUIC_ADDR* Addr + ) +{ + Addr->Ipv4.sin_port = QuicNetByteSwapShort(Port); + if (RtlIpv4StringToAddressExA(AddrStr, FALSE, &Addr->Ipv4.sin_addr, &Addr->Ipv4.sin_port) == STATUS_SUCCESS) { + Addr->si_family = AF_INET; + } else if (RtlIpv6StringToAddressExA(AddrStr, &Addr->Ipv6.sin6_addr, &Addr->Ipv6.sin6_scope_id, &Addr->Ipv6.sin6_port) == STATUS_SUCCESS) { + Addr->si_family = AF_INET6; + } else { + return FALSE; + } + return TRUE; +} + +// +// Represents an IP address and (optionally) port number as a string. +// +typedef struct QUIC_ADDR_STR { + char Address[64]; +} QUIC_ADDR_STR; + +inline +BOOLEAN +QuicAddrToString( + _In_ const QUIC_ADDR* Addr, + _Out_ QUIC_ADDR_STR* AddrStr + ) +{ + LONG Status; + ULONG AddrStrLen = ARRAYSIZE(AddrStr->Address); + if (Addr->si_family == AF_INET) { + Status = + RtlIpv4AddressToStringExA( + &Addr->Ipv4.sin_addr, + Addr->Ipv4.sin_port, + AddrStr->Address, + &AddrStrLen); + } else { + Status = + RtlIpv6AddressToStringExA( + &Addr->Ipv6.sin6_addr, + 0, + Addr->Ipv6.sin6_port, + AddrStr->Address, + &AddrStrLen); + } + return Status == STATUS_SUCCESS; +} + #endif // _MSQUIC_WINKERNEL_ diff --git a/src/inc/msquichelper.h b/src/inc/msquichelper.h index 9c9cc0939..322f4f024 100644 --- a/src/inc/msquichelper.h +++ b/src/inc/msquichelper.h @@ -54,6 +54,7 @@ QuicStatusToString( case QUIC_STATUS_CONNECTION_REFUSED: return "CONNECTION_REFUSED"; case QUIC_STATUS_PROTOCOL_ERROR: return "PROTOCOL_ERROR"; case QUIC_STATUS_VER_NEG_ERROR: return "VER_NEG_ERROR"; + case QUIC_STATUS_PENDING: return "PENDING"; } return "UNKNOWN"; @@ -158,6 +159,10 @@ ConvertArgToAddress( ) { if (strcmp("*", Arg) == 0) { + // + // Explicitly zero, otherwise kernel mode errors + // + QuicZeroMemory(Address, sizeof(*Address)); QuicAddrSetFamily(Address, AF_UNSPEC); QuicAddrSetPort(Address, Port); return TRUE; diff --git a/src/inc/quic_driver_helpers.h b/src/inc/quic_driver_helpers.h new file mode 100644 index 000000000..3645eda17 --- /dev/null +++ b/src/inc/quic_driver_helpers.h @@ -0,0 +1,380 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + This file contains helpers for interacting with a kernel mode driver service. +--*/ + +#pragma once + +#define QUIC_TEST_APIS 1 +#include "quic_platform.h" +#include "quic_trace.h" + +#ifdef _WIN32 + +#define QUIC_DRIVER_FILE_NAME QUIC_DRIVER_NAME ".sys" +#define QUIC_IOCTL_PATH "\\\\.\\\\" QUIC_DRIVER_NAME + +class QuicDriverService { + SC_HANDLE ScmHandle; + SC_HANDLE ServiceHandle; +public: + QuicDriverService() : + ScmHandle(nullptr), + ServiceHandle(nullptr) { + } + bool Initialize() { + uint32_t Error; + ScmHandle = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS); + if (ScmHandle == nullptr) { + Error = GetLastError(); + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "GetFullPathName failed"); + return false; + } + QueryService: + ServiceHandle = + OpenServiceA( + ScmHandle, + QUIC_DRIVER_NAME, + SERVICE_ALL_ACCESS); + if (ServiceHandle == nullptr) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + GetLastError(), + "OpenService failed"); + char DriverFilePath[MAX_PATH] = {0}; + GetModuleFileNameA(NULL, DriverFilePath, MAX_PATH); + char* PathEnd = strrchr(DriverFilePath, '\\'); + if (!PathEnd || + sizeof(DriverFilePath) - (PathEnd - DriverFilePath) < sizeof(QUIC_DRIVER_FILE_NAME)) { + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "Can't build " QUIC_DRIVER_FILE_NAME " full path"); + return false; + } + memcpy(PathEnd + 1, QUIC_DRIVER_FILE_NAME, sizeof(QUIC_DRIVER_FILE_NAME)); + if (GetFileAttributesA(DriverFilePath) == INVALID_FILE_ATTRIBUTES) { + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "Failed to find " QUIC_DRIVER_FILE_NAME); + return false; + } + ServiceHandle = + CreateServiceA( + ScmHandle, + QUIC_DRIVER_NAME, + QUIC_DRIVER_NAME, + SC_MANAGER_ALL_ACCESS, + SERVICE_KERNEL_DRIVER, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + DriverFilePath, + nullptr, + nullptr, + "msquic\0", + nullptr, + nullptr); + if (ServiceHandle == nullptr) { + Error = GetLastError(); + if (Error == ERROR_SERVICE_EXISTS) { + goto QueryService; + } + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "CreateService failed"); + return false; + } + } + return true; + } + void Uninitialize() { + if (ServiceHandle != nullptr) { + CloseServiceHandle(ServiceHandle); + } + if (ScmHandle != nullptr) { + CloseServiceHandle(ScmHandle); + } + } + bool Start() { + if (!StartServiceA(ServiceHandle, 0, nullptr)) { + uint32_t Error = GetLastError(); + if (Error != ERROR_SERVICE_ALREADY_RUNNING) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "StartService failed"); + return false; + } + } + return true; + } +}; + +class QuicDriverClient { + HANDLE DeviceHandle; +public: + QuicDriverClient() : DeviceHandle(INVALID_HANDLE_VALUE) { } + bool Initialize( + _In_ QUIC_SEC_CONFIG_PARAMS* SecConfigParams + ) { + uint32_t Error; + DeviceHandle = + CreateFileA( + QUIC_IOCTL_PATH, + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, // no SECURITY_ATTRIBUTES structure + OPEN_EXISTING, // No special create flags + FILE_FLAG_OVERLAPPED, // Allow asynchronous requests + nullptr); + if (DeviceHandle == INVALID_HANDLE_VALUE) { + Error = GetLastError(); + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "CreateFile failed"); + return false; + } + if (!Run(IOCTL_QUIC_SEC_CONFIG, SecConfigParams->Thumbprint, sizeof(SecConfigParams->Thumbprint), 30000)) { + CloseHandle(DeviceHandle); + DeviceHandle = INVALID_HANDLE_VALUE; + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "Run(IOCTL_QUIC_SEC_CONFIG) failed"); + return false; + } + return true; + } + void Uninitialize() { + if (DeviceHandle != INVALID_HANDLE_VALUE) { + CloseHandle(DeviceHandle); + } + } + bool Run( + _In_ uint32_t IoControlCode, + _In_reads_bytes_opt_(InBufferSize) + void* InBuffer, + _In_ uint32_t InBufferSize, + _In_ uint32_t TimeoutMs = 30000 + ) { + uint32_t Error; + OVERLAPPED Overlapped = { 0 }; + Overlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (Overlapped.hEvent == nullptr) { + Error = GetLastError(); + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "CreateEvent failed"); + return false; + } + QuicTraceLogVerbose( + TestSendIoctl, + "[test] Sending IOCTL %u with %u bytes.", + IoGetFunctionCodeFromCtlCode(IoControlCode), + InBufferSize); + if (!DeviceIoControl( + DeviceHandle, + IoControlCode, + InBuffer, InBufferSize, + nullptr, 0, + nullptr, + &Overlapped)) { + Error = GetLastError(); + if (Error != ERROR_IO_PENDING) { + CloseHandle(Overlapped.hEvent); + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "DeviceIoControl failed"); + return false; + } + } + DWORD dwBytesReturned; + if (!GetOverlappedResultEx( + DeviceHandle, + &Overlapped, + &dwBytesReturned, + TimeoutMs, + FALSE)) { + Error = GetLastError(); + if (Error == WAIT_TIMEOUT) { + Error = ERROR_TIMEOUT; + CancelIoEx(DeviceHandle, &Overlapped); + } + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "GetOverlappedResultEx failed"); + } else { + Error = ERROR_SUCCESS; + } + CloseHandle(Overlapped.hEvent); + return Error == ERROR_SUCCESS; + } + bool Run( + _In_ uint32_t IoControlCode, + _In_ uint32_t TimeoutMs = 30000 + ) { + return Run(IoControlCode, nullptr, 0, TimeoutMs); + } + template + bool Run( + _In_ uint32_t IoControlCode, + _In_ const T& Data, + _In_ uint32_t TimeoutMs = 30000 + ) { + return Run(IoControlCode, (void*)&Data, sizeof(Data), TimeoutMs); + } + bool Read( + _In_ uint32_t IoControlCode, + _Out_writes_bytes_opt_(OutBufferSize) + void* OutBuffer, + _In_ uint32_t OutBufferSize, + _Out_opt_ uint32_t* OutBufferWritten, + _In_ uint32_t TimeoutMs = 30000 + ) { + uint32_t Error; + OVERLAPPED Overlapped = { 0 }; + Overlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (!Overlapped.hEvent) { + Error = GetLastError(); + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "CreateEvent failed"); + return false; + } + QuicTraceLogVerbose( + TestSendIoctl, + "[test] Sending IOCTL %u.", + IoGetFunctionCodeFromCtlCode(IoControlCode)); + if (!DeviceIoControl( + DeviceHandle, + IoControlCode, + nullptr, 0, + OutBuffer, OutBufferSize, + nullptr, + &Overlapped)) { + Error = GetLastError(); + if (Error != ERROR_IO_PENDING) { + CloseHandle(Overlapped.hEvent); + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "DeviceIoControl failed"); + return false; + } + } + DWORD dwBytesReturned; + if (!GetOverlappedResultEx( + DeviceHandle, + &Overlapped, + &dwBytesReturned, + TimeoutMs, + FALSE)) { + Error = GetLastError(); + if (Error == WAIT_TIMEOUT) { + Error = ERROR_TIMEOUT; + CancelIoEx(DeviceHandle, &Overlapped); + } else { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "GetOverlappedResultEx failed"); + } + } else { + Error = ERROR_SUCCESS; + *OutBufferWritten = dwBytesReturned; + } + CloseHandle(Overlapped.hEvent); + return Error == ERROR_SUCCESS; + } +}; + +#else + +class QuicDriverService { +public: + bool Initialize() { return false; } + void Uninitialize() { } + bool Start() { return false; } +}; + +class QuicDriverClient { +public: + bool Initialize( + _In_ QUIC_SEC_CONFIG_PARAMS* SecConfigParams + ) { + UNREFERENCED_PARAMETER(SecConfigParams); + return false; + } + void Uninitialize() { } + bool Run( + _In_ uint32_t IoControlCode, + _In_ void* InBuffer, + _In_ uint32_t InBufferSize, + _In_ uint32_t TimeoutMs = 30000 + ) { + UNREFERENCED_PARAMETER(IoControlCode); + UNREFERENCED_PARAMETER(InBuffer); + UNREFERENCED_PARAMETER(InBufferSize); + UNREFERENCED_PARAMETER(TimeoutMs); + return false; + } + bool + Run( + _In_ uint32_t IoControlCode, + _In_ uint32_t TimeoutMs = 30000 + ) { + return Run(IoControlCode, nullptr, 0, TimeoutMs); + } + template + bool + Run( + _In_ uint32_t IoControlCode, + _In_ const T& Data, + _In_ uint32_t TimeoutMs = 30000 + ) { + return Run(IoControlCode, (void*)&Data, sizeof(Data), TimeoutMs); + } + bool Read( + _In_ uint32_t IoControlCode, + _Out_writes_bytes_opt_(OutBufferSize) + void* OutBuffer, + _In_ uint32_t OutBufferSize, + _Out_ uint32_t* OutBufferWritten, + _In_ uint32_t TimeoutMs = 30000 + ) { + UNREFERENCED_PARAMETER(IoControlCode); + UNREFERENCED_PARAMETER(OutBuffer); + UNREFERENCED_PARAMETER(OutBufferSize); + UNREFERENCED_PARAMETER(OutBufferWritten); + UNREFERENCED_PARAMETER(TimeoutMs); + return false; + } +}; + +#endif diff --git a/src/inc/quic_driver_main.h b/src/inc/quic_driver_main.h new file mode 100644 index 000000000..d48f48278 --- /dev/null +++ b/src/inc/quic_driver_main.h @@ -0,0 +1,30 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Perf Main common driver code. + +--*/ + +#pragma once + +#include "quic_platform.h" +#include "PerfHelpers.h" + +extern +QUIC_STATUS +QuicMainStart( + _In_ int argc, + _In_reads_(argc) _Null_terminated_ char* argv[], + _In_ QUIC_EVENT StopEvent, + _In_ PerfSelfSignedConfiguration* SelfSignedConfig + ); + +extern +QUIC_STATUS +QuicMainStop( + _In_ int Timeout + ); diff --git a/src/inc/quic_platform_winkernel.h b/src/inc/quic_platform_winkernel.h index 8fc038708..b9cc9835b 100644 --- a/src/inc/quic_platform_winkernel.h +++ b/src/inc/quic_platform_winkernel.h @@ -929,6 +929,8 @@ NdisSetThreadObjectCompartmentId( IN NET_IF_COMPARTMENT_ID CompartmentId ); +#define QuicSetCurrentThreadAffinityMask(Mask) KeSetSystemAffinityThreadEx(Mask) + #define QuicCompartmentIdGetCurrent() NdisGetThreadObjectCompartmentId(PsGetCurrentThread()) #define QuicCompartmentIdSetCurrent(CompartmentId) \ NdisSetThreadObjectCompartmentId(PsGetCurrentThread(), CompartmentId) diff --git a/src/inc/quic_platform_winuser.h b/src/inc/quic_platform_winuser.h index d4a998c03..28085e51b 100644 --- a/src/inc/quic_platform_winuser.h +++ b/src/inc/quic_platform_winuser.h @@ -899,6 +899,8 @@ QuicRandom( #define QUIC_UNSPECIFIED_COMPARTMENT_ID NET_IF_COMPARTMENT_ID_UNSPECIFIED #define QUIC_DEFAULT_COMPARTMENT_ID NET_IF_COMPARTMENT_ID_PRIMARY +#define QuicSetCurrentThreadAffinityMask(Mask) SetThreadAffinityMask(GetCurrentThread(), Mask) + #define QuicCompartmentIdGetCurrent() GetCurrentThreadCompartmentId() #define QuicCompartmentIdSetCurrent(CompartmentId) \ HRESULT_FROM_WIN32(SetCurrentThreadCompartmentId(CompartmentId)) diff --git a/src/perf/bin/CMakeLists.txt b/src/perf/bin/CMakeLists.txt new file mode 100644 index 000000000..5f22e0ac2 --- /dev/null +++ b/src/perf/bin/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +set(SOURCES + appmain.cpp +) + +# Allow CLOG to preprocess all the source files. +CLOG_GENERATE_TARGET(perfbin.clog ${SOURCES}) + +add_executable(quicperf ${SOURCES}) + +set_property(TARGET quicperf PROPERTY FOLDER "perf") + +target_link_libraries(quicperf perflib msquic platform perfbin.clog) diff --git a/src/perf/bin/appmain.cpp b/src/perf/bin/appmain.cpp new file mode 100644 index 000000000..236ce65f1 --- /dev/null +++ b/src/perf/bin/appmain.cpp @@ -0,0 +1,224 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Perf Main execution runner. + +--*/ + +#define QUIC_TEST_APIS 1 +#include "quic_driver_main.h" +#include "PerfHelpers.h" +#include +#ifdef QUIC_CLOG +#include "appmain.cpp.clog.h" +#endif + +#ifdef _WIN32 + +// +// Name of the driver service for quicperf.sys. +// Must be defined before quic_driver_helpers.h is included +// +#define QUIC_DRIVER_NAME "quicperf" + +#include +#include "PerfIoctls.h" +#include "quic_driver_helpers.h" + +#endif + +extern "C" _IRQL_requires_max_(PASSIVE_LEVEL) void QuicTraceRundown(void) { } + +QUIC_STATUS +QuicUserMain( + _In_ int argc, + _In_reads_(argc) _Null_terminated_ char* argv[], + _In_ bool KeyboardWait, + _In_ PerfSelfSignedConfiguration* SelfSignedConfig + ) { + QUIC_EVENT StopEvent; + QuicEventInitialize(&StopEvent, true, false); + + QUIC_STATUS Status = QuicMainStart(argc, argv, StopEvent, SelfSignedConfig); + if (Status != 0) { + return Status; + } + + printf("Started!\n\n"); + fflush(stdout); + + if (KeyboardWait) { + printf("Press enter to exit\n"); + getchar(); + QuicEventSet(StopEvent); + } + + Status = QuicMainStop(0); + QuicEventUninitialize(StopEvent); + return Status; +} + +#ifdef _WIN32 + +QUIC_STATUS +QuicKernelMain( + _In_ int argc, + _In_reads_(argc) _Null_terminated_ char* argv[], + _In_ bool KeyboardWait, + _In_ QUIC_SEC_CONFIG_PARAMS* SelfSignedParams + ) { + size_t TotalLength = sizeof(argc); + + // + // Get total length + // + for (int i = 0; i < argc; ++i) { + TotalLength += strlen(argv[i]) + 1; + } + + if (TotalLength > UINT_MAX) { + printf("Too many arguments to pass to the driver\n"); + return QUIC_STATUS_OUT_OF_MEMORY; + } + + char* Data = static_cast(QUIC_ALLOC_NONPAGED(TotalLength)); + if (!Data) { + printf("Failed to allocate arguments to pass\n"); + return QUIC_STATUS_OUT_OF_MEMORY; + } + + char* DataCurrent = Data; + + QuicCopyMemory(DataCurrent, &argc, sizeof(TotalLength)); + + DataCurrent += sizeof(argc); + + for (int i = 0; i < argc; ++i) { + size_t ArgLen = strlen(argv[i]) + 1; + QuicCopyMemory(DataCurrent, argv[i], ArgLen); + DataCurrent += ArgLen; + DataCurrent[0] = '\0'; + ++DataCurrent; + } + + QUIC_DBG_ASSERT(DataCurrent == (Data + TotalLength)); + + constexpr uint32_t OutBufferSize = 1024 * 1000; + char* OutBuffer = (char*)QUIC_ALLOC_NONPAGED(OutBufferSize); // 1 MB + if (!OutBuffer) { + printf("Failed to allocate space for output buffer\n"); + QUIC_FREE(Data); + return QUIC_STATUS_OUT_OF_MEMORY; + } + + QuicDriverService DriverService; + QuicDriverClient DriverClient; + if (!DriverService.Initialize()) { + printf("Failed to initialize driver service\n"); + QUIC_FREE(Data); + return QUIC_STATUS_INVALID_STATE; + } + DriverService.Start(); + + if (!DriverClient.Initialize(SelfSignedParams)) { + printf("Failed to initialize driver client\n"); + QUIC_FREE(Data); + return QUIC_STATUS_INVALID_STATE; + } + + if (!DriverClient.Run(IOCTL_QUIC_RUN_PERF, Data, (uint32_t)TotalLength)) { + QUIC_FREE(Data); + QUIC_FREE(OutBuffer); + return QUIC_STATUS_INVALID_STATE; + } + printf("Started!\n\n"); + fflush(stdout); + + uint32_t OutBufferWritten = 0; + bool RunSuccess = + DriverClient.Read( + IOCTL_QUIC_READ_DATA, + OutBuffer, + OutBufferSize, + &OutBufferWritten); + if (RunSuccess) { + printf("%s", OutBuffer); + } + + QUIC_FREE(Data); + QUIC_FREE(OutBuffer); + + return RunSuccess ? QUIC_STATUS_SUCCESS : QUIC_STATUS_INTERNAL_ERROR; +} + +#endif + +int +QUIC_MAIN_EXPORT +main( + _In_ int argc, + _In_reads_(argc) _Null_terminated_ char* argv[] + ) { + QUIC_SEC_CONFIG_PARAMS* SelfSignedParams = nullptr; + PerfSelfSignedConfiguration SelfSignedConfig; + QUIC_STATUS RetVal = 0; + bool TestingKernelMode = false; + bool KeyboardWait = false; + + QuicPlatformSystemLoad(); + if (QUIC_FAILED(QuicPlatformInitialize())) { + printf("Platform failed to initialize\n"); + goto Exit; + } + + for (int i = 0; i < argc; ++i) { + if (strcmp("--kernel", argv[i]) == 0) { +#ifdef _WIN32 + TestingKernelMode = true; +#else + printf("Cannot run kernel mode tests on non windows platforms\n"); + RetVal = QUIC_STATUS_NOT_SUPPORTED; + goto Exit; +#endif + } else if (strcmp("--kbwait", argv[i]) == 0) { + KeyboardWait = true; + } + } + + SelfSignedParams = + QuicPlatGetSelfSignedCert( + TestingKernelMode ? + QUIC_SELF_SIGN_CERT_MACHINE : + QUIC_SELF_SIGN_CERT_USER); + if (!SelfSignedParams) { + printf("Creating self signed certificate failed\n"); + RetVal = QUIC_STATUS_INTERNAL_ERROR; + goto Exit; + } + + SelfSignedConfig.SelfSignedParams = SelfSignedParams; + + if (TestingKernelMode) { +#ifdef _WIN32 + RetVal = QuicKernelMain(argc, argv, KeyboardWait, SelfSignedParams); +#else + QUIC_FRE_ASSERT(FALSE); +#endif + } else { + RetVal = QuicUserMain(argc, argv, KeyboardWait, &SelfSignedConfig); + } + +Exit: + if (SelfSignedParams) { + QuicPlatFreeSelfSignedCert(SelfSignedParams); + } + + QuicPlatformUninitialize(); + QuicPlatformSystemUnload(); + + return RetVal; +} diff --git a/src/perf/bin/drvmain.cpp b/src/perf/bin/drvmain.cpp new file mode 100644 index 000000000..d009dacb3 --- /dev/null +++ b/src/perf/bin/drvmain.cpp @@ -0,0 +1,561 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Kernel Mode Performance Driver + +--*/ + +#include +#include +#include "msquic.h" + +#include "PerfIoctls.h" + +#include "quic_trace.h" +#ifdef QUIC_CLOG +#include "driver.cpp.clog.h" +#endif + +#define QUIC_PERF_TAG 'frPQ' // QPrf + +DECLARE_CONST_UNICODE_STRING(QuicPerfCtlDeviceName, L"\\Device\\quicperformance"); +DECLARE_CONST_UNICODE_STRING(QuicPerfCtlDeviceSymLink, L"\\DosDevices\\quicperformance"); + +typedef struct QUIC_DEVICE_EXTENSION { + EX_PUSH_LOCK Lock; + + _Guarded_by_(Lock) + LIST_ENTRY ClientList; + ULONG ClientListSize; + +} QUIC_DEVICE_EXTENSION; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUIC_DEVICE_EXTENSION, QuicPerfCtlGetDeviceContext); + +typedef struct QUIC_DRIVER_CLIENT { + LIST_ENTRY Link; + bool TestFailure; + +} QUIC_DRIVER_CLIENT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUIC_DRIVER_CLIENT, QuicPerfCtlGetFileContext); + +EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL QuicPerfCtlEvtIoDeviceControl; +EVT_WDF_IO_QUEUE_IO_CANCELED_ON_QUEUE QuicPerfCtlEvtIoCanceled; + +PAGEDX EVT_WDF_DEVICE_FILE_CREATE QuicPerfCtlEvtFileCreate; +PAGEDX EVT_WDF_FILE_CLOSE QuicPerfCtlEvtFileClose; +PAGEDX EVT_WDF_FILE_CLEANUP QuicPerfCtlEvtFileCleanup; + +WDFDEVICE QuicPerfCtlDevice = nullptr; +QUIC_DEVICE_EXTENSION* QuicPerfCtlExtension = nullptr; +QUIC_DRIVER_CLIENT* QuicPerfClient = nullptr; + +EVT_WDF_DRIVER_UNLOAD QuicPerfDriverUnload; + +_No_competing_thread_ +INITCODE +NTSTATUS +QuicPerfCtlInitialize( + _In_ WDFDRIVER Driver + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicPerfCtlUninitialize( + void + ); + +void* __cdecl operator new (size_t Size) { + return ExAllocatePool2(POOL_FLAG_NON_PAGED, Size, QUIC_PERF_TAG); +} + +void __cdecl operator delete (_In_opt_ void* Mem) { + if (Mem != nullptr) { + ExFreePoolWithTag(Mem, QUIC_PERF_TAG); + } +} + +void __cdecl operator delete (_In_opt_ void* Mem, _In_opt_ size_t) { + if (Mem != nullptr) { + ExFreePoolWithTag(Mem, QUIC_PERF_TAG); + } +} + +void* __cdecl operator new[](size_t Size) { + return ExAllocatePool2(POOL_FLAG_NON_PAGED, Size, QUIC_PERF_TAG); +} + +void __cdecl operator delete[](_In_opt_ void* Mem) { + if (Mem != nullptr) { + ExFreePoolWithTag(Mem, QUIC_PERF_TAG); + } +} + +extern "C" _IRQL_requires_max_(PASSIVE_LEVEL) void QuicTraceRundown(void) { } + +extern "C" +INITCODE +_Function_class_(DRIVER_INITIALIZE) +_IRQL_requires_same_ +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) +{ + NTSTATUS Status; + WDF_DRIVER_CONFIG Config; + WDFDRIVER Driver; + BOOLEAN PlatformInitialized = FALSE; + + QuicPlatformSystemLoad(DriverObject, RegistryPath); + + Status = QuicPlatformInitialize(); + if (!NT_SUCCESS(Status)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "QuicPlatformInitialize failed"); + goto Error; + } + PlatformInitialized = TRUE; + + // + // Create the WdfDriver Object + // + WDF_DRIVER_CONFIG_INIT(&Config, NULL); + Config.EvtDriverUnload = QuicPerfDriverUnload; + Config.DriverInitFlags = WdfDriverInitNonPnpDriver; + Config.DriverPoolTag = QUIC_PERF_TAG; + + Status = + WdfDriverCreate( + DriverObject, + RegistryPath, + WDF_NO_OBJECT_ATTRIBUTES, + &Config, + &Driver); + if (!NT_SUCCESS(Status)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "WdfDriverCreate failed"); + goto Error; + } + + // + // Initialize the device control interface. + // + Status = QuicPerfCtlInitialize(Driver); + if (!NT_SUCCESS(Status)) { + goto Error; + } + + QuicTraceLogInfo( + PerfDriverStarted, + "[perf] Started"); + +Error: + + if (!NT_SUCCESS(Status)) { + if (PlatformInitialized) { + QuicPlatformUninitialize(); + } + QuicPlatformSystemUnload(); + } + + return Status; +} + +_Function_class_(EVT_WDF_DRIVER_UNLOAD) +_IRQL_requires_same_ +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicPerfDriverUnload( + _In_ WDFDRIVER /*Driver*/ + ) +{ + NT_ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); + + QuicPerfCtlUninitialize(); + + QuicTraceLogInfo( + PerfDriverStopped, + "[perf] Stopped"); + + QuicPlatformUninitialize(); + QuicPlatformSystemUnload(); +} + +_No_competing_thread_ +INITCODE +NTSTATUS +QuicPerfCtlInitialize( + _In_ WDFDRIVER Driver + ) +{ + NTSTATUS Status = STATUS_SUCCESS; + PWDFDEVICE_INIT DeviceInit = nullptr; + WDF_FILEOBJECT_CONFIG FileConfig; + WDF_OBJECT_ATTRIBUTES Attribs; + WDFDEVICE Device; + QUIC_DEVICE_EXTENSION* DeviceContext; + WDF_IO_QUEUE_CONFIG QueueConfig; + WDFQUEUE Queue; + + DeviceInit = + WdfControlDeviceInitAllocate( + Driver, + &SDDL_DEVOBJ_SYS_ALL_ADM_ALL); + if (DeviceInit == nullptr) { + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "WdfControlDeviceInitAllocate failed"); + Status = STATUS_INSUFFICIENT_RESOURCES; + goto Error; + } + + Status = + WdfDeviceInitAssignName( + DeviceInit, + &QuicPerfCtlDeviceName); + if (!NT_SUCCESS(Status)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "WdfDeviceInitAssignName failed"); + goto Error; + } + + WDF_FILEOBJECT_CONFIG_INIT( + &FileConfig, + QuicPerfCtlEvtFileCreate, + QuicPerfCtlEvtFileClose, + QuicPerfCtlEvtFileCleanup); + FileConfig.FileObjectClass = WdfFileObjectWdfCanUseFsContext2; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attribs, QUIC_DRIVER_CLIENT); + WdfDeviceInitSetFileObjectConfig( + DeviceInit, + &FileConfig, + &Attribs); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attribs, QUIC_DEVICE_EXTENSION); + + Status = + WdfDeviceCreate( + &DeviceInit, + &Attribs, + &Device); + if (!NT_SUCCESS(Status)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "WdfDeviceCreate failed"); + goto Error; + } + + DeviceContext = QuicPerfCtlGetDeviceContext(Device); + RtlZeroMemory(DeviceContext, sizeof(QUIC_DEVICE_EXTENSION)); + ExInitializePushLock(&DeviceContext->Lock); + InitializeListHead(&DeviceContext->ClientList); + + Status = WdfDeviceCreateSymbolicLink(Device, &QuicPerfCtlDeviceSymLink); + if (!NT_SUCCESS(Status)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "WdfDeviceCreateSymbolicLink failed"); + goto Error; + } + + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&QueueConfig, WdfIoQueueDispatchParallel); + QueueConfig.EvtIoDeviceControl = QuicPerfCtlEvtIoDeviceControl; + QueueConfig.EvtIoCanceledOnQueue = QuicPerfCtlEvtIoCanceled; + + __analysis_assume(QueueConfig.EvtIoStop != 0); + Status = + WdfIoQueueCreate( + Device, + &QueueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &Queue); + __analysis_assume(QueueConfig.EvtIoStop == 0); + + if (!NT_SUCCESS(Status)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "WdfIoQueueCreate failed"); + goto Error; + } + + QuicPerfCtlDevice = Device; + QuicPerfCtlExtension = DeviceContext; + + WdfControlFinishInitializing(Device); + + QuicTraceLogVerbose( + PerfControlInitialized, + "[perf] Control interface initialized"); + +Error: + + if (DeviceInit) { + WdfDeviceInitFree(DeviceInit); + } + + return Status; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicPerfCtlUninitialize( + void + ) +{ + QuicTraceLogVerbose( + PerfControlUninitializing, + "[perf] Control interface uninitializing"); + + if (QuicPerfCtlDevice != nullptr) { + NT_ASSERT(QuicPerfCtlExtension != nullptr); + QuicPerfCtlExtension = nullptr; + + WdfObjectDelete(QuicPerfCtlDevice); + QuicPerfCtlDevice = nullptr; + } + + QuicTraceLogVerbose( + PerfControlUninitialized, + "[perf] Control interface uninitialized"); +} + +PAGEDX +_Use_decl_annotations_ +void +QuicPerfCtlEvtFileCreate( + _In_ WDFDEVICE /* Device */, + _In_ WDFREQUEST Request, + _In_ WDFFILEOBJECT FileObject + ) +{ + NTSTATUS Status = STATUS_SUCCESS; + + PAGED_CODE(); + + KeEnterGuardedRegion(); + ExfAcquirePushLockExclusive(&QuicPerfCtlExtension->Lock); + + do { + if (QuicPerfCtlExtension->ClientListSize >= 1) { + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "Already have max clients"); + Status = STATUS_TOO_MANY_SESSIONS; + break; + } + + QUIC_DRIVER_CLIENT* Client = QuicPerfCtlGetFileContext(FileObject); + if (Client == nullptr) { + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "nullptr File context in FileCreate"); + Status = STATUS_INVALID_PARAMETER; + break; + } + + RtlZeroMemory(Client, sizeof(QUIC_DRIVER_CLIENT)); + + // + // Insert into the client list + // + InsertTailList(&QuicPerfCtlExtension->ClientList, &Client->Link); + QuicPerfCtlExtension->ClientListSize++; + + QuicTraceLogInfo( + TestControlClientCreated, + "[test] Client %p created", + Client); + + // + // Update globals. (TODO: Add multiple device client support) + // + QuicPerfClient = Client; + } while (false); + + ExfReleasePushLockExclusive(&QuicPerfCtlExtension->Lock); + KeLeaveGuardedRegion(); + + WdfRequestComplete(Request, Status); +} + +PAGEDX +_Use_decl_annotations_ +void +QuicPerfCtlEvtFileClose( + _In_ WDFFILEOBJECT /* FileObject */ + ) +{ + PAGED_CODE(); +} + +PAGEDX +_Use_decl_annotations_ +void +QuicPerfCtlEvtFileCleanup( + _In_ WDFFILEOBJECT FileObject + ) +{ + PAGED_CODE(); + + KeEnterGuardedRegion(); + + QUIC_DRIVER_CLIENT* Client = QuicPerfCtlGetFileContext(FileObject); + if (Client != nullptr) { + + ExfAcquirePushLockExclusive(&QuicPerfCtlExtension->Lock); + + // + // Remove the device client from the list + // + RemoveEntryList(&Client->Link); + QuicPerfCtlExtension->ClientListSize--; + + ExfReleasePushLockExclusive(&QuicPerfCtlExtension->Lock); + + QuicTraceLogInfo( + TestControlClientCleaningUp, + "[test] Client %p cleaning up", + Client); + + // + // Clean up globals. + // + QuicPerfClient = nullptr; + } + + KeLeaveGuardedRegion(); +} + +VOID +QuicPerfCtlEvtIoCanceled( + _In_ WDFQUEUE /* Queue */, + _In_ WDFREQUEST Request + ) +{ + NTSTATUS Status; + + WDFFILEOBJECT FileObject = WdfRequestGetFileObject(Request); + if (FileObject == nullptr) { + Status = STATUS_DEVICE_NOT_READY; + goto error; + } + + QUIC_DRIVER_CLIENT* Client = QuicPerfCtlGetFileContext(FileObject); + if (Client == nullptr) { + Status = STATUS_DEVICE_NOT_READY; + goto error; + } + + QuicTraceLogWarning( + TestControlClientCanceledRequest, + "[test] Client %p canceled request %p", + Client, + Request); + + Status = STATUS_CANCELLED; + +error: + + WdfRequestComplete(Request, Status); +} + +size_t QUIC_IOCTL_BUFFER_SIZES[] = +{ + 0, + sizeof(QUIC_CERTIFICATE_HASH), + 0, + 0 +}; + +static_assert( + QUIC_PERF_MAX_IOCTL_FUNC_CODE + 1 == (sizeof(QUIC_IOCTL_BUFFER_SIZES) / sizeof(size_t)), + "QUIC_IOCTL_BUFFER_SIZES must be kept in sync with the IOTCLs"); + +VOID +QuicPerfCtlEvtIoDeviceControl( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request, + _In_ size_t OutputBufferLength, + _In_ size_t InputBufferLength, + _In_ ULONG IoControlCode + ) +{ + QUIC_STATUS Status = QUIC_STATUS_SUCCESS; + WDFFILEOBJECT FileObject = nullptr; + QUIC_DRIVER_CLIENT* Client = nullptr; + ULONG FunctionCode = 0; + + if (KeGetCurrentIrql() > PASSIVE_LEVEL) { + Status = STATUS_NOT_SUPPORTED; + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "IOCTL not supported greater than PASSIVE_LEVEL"); + goto Error; + } + + FileObject = WdfRequestGetFileObject(Request); + if (FileObject == nullptr) { + Status = STATUS_DEVICE_NOT_READY; + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "WdfRequestGetFileObject failed"); + goto Error; + } + + Client = QuicPerfCtlGetFileContext(FileObject); + if (Client == nullptr) { + Status = STATUS_DEVICE_NOT_READY; + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "QuicTestCtlGetFileContext failed"); + goto Error; + } + + FunctionCode = IoGetFunctionCodeFromCtlCode(IoControlCode); + if (FunctionCode == 0 || FunctionCode > QUIC_PERF_MAX_IOCTL_FUNC_CODE) { + Status = STATUS_NOT_IMPLEMENTED; + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + FunctionCode, + "Invalid FunctionCode"); + goto Error; + } + + // TODO Input Buffer Length + + +Error: + UNREFERENCED_PARAMETER(InputBufferLength); + UNREFERENCED_PARAMETER(OutputBufferLength); + UNREFERENCED_PARAMETER(Queue); +} diff --git a/src/perf/bin/perfioctls.h b/src/perf/bin/perfioctls.h new file mode 100644 index 000000000..7ad726723 --- /dev/null +++ b/src/perf/bin/perfioctls.h @@ -0,0 +1,29 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Perf Kernel Mode IOCTL definitions + +--*/ + +#pragma once + +#define QUIC_CTL_CODE(request, method, access) \ + CTL_CODE(FILE_DEVICE_NETWORK, request, method, access) + +#define IoGetFunctionCodeFromCtlCode( ControlCode ) (\ + ( ControlCode >> 2) & 0x00000FFF ) + +#define IOCTL_QUIC_SEC_CONFIG \ + QUIC_CTL_CODE(1, METHOD_BUFFERED, FILE_WRITE_DATA) + +#define IOCTL_QUIC_RUN_PERF \ + QUIC_CTL_CODE(2, METHOD_BUFFERED, FILE_WRITE_DATA) + +#define IOCTL_QUIC_READ_DATA \ + QUIC_CTL_CODE(3, METHOD_BUFFERED, FILE_READ_DATA) + +#define QUIC_PERF_MAX_IOCTL_FUNC_CODE 3 diff --git a/src/perf/bin/performance.kernel.vcxproj b/src/perf/bin/performance.kernel.vcxproj new file mode 100644 index 000000000..327e605f1 --- /dev/null +++ b/src/perf/bin/performance.kernel.vcxproj @@ -0,0 +1,121 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + + + + + {5f99f713-bf5f-44eb-90fe-fea03906bba9} + + + {11633785-79cc-4c7d-ab6a-aecdf29a1fa7} + + + {C31B028C-E91C-4CF7-A8E7-F385B2AF5F85} + + + + {2be64dbf-60e6-4fe8-96b0-5f2526405096} + {1bc93793-694f-48fe-9372-81e2b05556fd} + v4.5 + 12.0 + + + + Windows10 + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + true + + + false + + + + + + + + + quicperf + DbgengKernelDebugger + false + $(SolutionDir)artifacts\bin\winkernel\$(Platform)_$(Configuration)_schannel\ + $(SolutionDir)build\winkernel\$(Platform)_$(Configuration)_schannel\obj\$(ProjectName)\ + Off + + + + ..\lib;..\..;..\..\inc;$(SolutionDir)build\winkernel\$(Platform)_$(Configuration)_schannel\inc;$(IntDir);%(AdditionalIncludeDirectories) + Speed + true + /Gw /kernel /ZH:SHA_256 + /Gw /kernel /ZH:SHA_256 -d2jumptablerdata -d2epilogunwindrequirev2 + 4748;5040;4459;%(DisableSpecificWarnings) + + + $(SolutionDir)artifacts\bin\winkernel\$(Platform)_$(Configuration)_schannel\ + cng.lib;ksecdd.lib;msnetioid.lib;netio.lib;wdmsec.lib;uuid.lib;msquic.lib;%(AdditionalDependencies) + UseLinkTimeCodeGeneration + + + + + MultiThreadedDebugDLL + QUIC_EVENTS_MANIFEST_ETW;QUIC_LOGS_MANIFEST_ETW;QUIC_DISABLE_0RTT_TESTS;SECURITY_KERNEL;SECURITY_WIN32;_DEBUG;%(PreprocessorDefinitions) + + + + + QUIC_EVENTS_MANIFEST_ETW;QUIC_LOGS_MANIFEST_ETW;QUIC_DISABLE_0RTT_TESTS;SECURITY_KERNEL;SECURITY_WIN32;%(PreprocessorDefinitions) + + + + + + + true + true + + + + + diff --git a/src/perf/lib/CMakeLists.txt b/src/perf/lib/CMakeLists.txt new file mode 100644 index 000000000..38e6f9dcb --- /dev/null +++ b/src/perf/lib/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +set(SOURCES + quicmain.cpp + ThroughputServer.cpp + ThroughputClient.cpp +) + +# Allow CLOG to preprocess all the source files. +CLOG_GENERATE_TARGET(perflib.clog ${SOURCES}) + +add_library(perflib ${SOURCES}) + +set_property(TARGET perflib PROPERTY FOLDER "perf") + +target_link_libraries(perflib PUBLIC perflib.clog) + +target_link_libraries(perflib PRIVATE inc warnings) + +target_include_directories(perflib PUBLIC ${CMAKE_CURRENT_LIST_DIR}) + +if (MSVC) + target_compile_options(perflib PUBLIC /wd4459) +else() + target_compile_options(perflib PUBLIC -Wno-switch) +endif() + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + add_dependencies(perflib MsQuicEtw) +endif() diff --git a/src/perf/lib/PerfBase.h b/src/perf/lib/PerfBase.h new file mode 100644 index 000000000..0e463841b --- /dev/null +++ b/src/perf/lib/PerfBase.h @@ -0,0 +1,55 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Perf Base blass declaration. Defines the base class for all perf + executions. + +--*/ + +#pragma once + +#include "msquic.h" +#include "quic_platform.h" + +struct PerfBase { + // + // Virtual destructor so we can destruct the base class + // + virtual + ~PerfBase() = default; + + // + // Called to initialize the runner. + // + virtual + QUIC_STATUS + Init( + _In_ int argc, + _In_reads_(argc) _Null_terminated_ char* argv[] + ) = 0; + + // + // Start the runner. The StopEvent can be triggered to stop early. Passed + // here rather then Wait so we can synchronize off of it. This event must + // be kept alive until Wait is called. + // + virtual + QUIC_STATUS + Start( + _In_ QUIC_EVENT StopEvent + ) = 0; + + // + // Wait for a run to finish, until timeout. + // If 0 or less, wait forever + // + virtual + QUIC_STATUS + Wait( + int Timeout + ) = 0; +}; diff --git a/src/perf/lib/PerfHelpers.h b/src/perf/lib/PerfHelpers.h new file mode 100644 index 000000000..41be2ddc6 --- /dev/null +++ b/src/perf/lib/PerfHelpers.h @@ -0,0 +1,280 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + MsQuic API Perf Helpers + +--*/ + +#pragma once + +#ifdef QUIC_CLOG +#include "PerfHelpers.h.clog.h" +#endif + +#ifndef _KERNEL_MODE +#define QUIC_TEST_APIS 1 +#endif + +class QuicApiTable; + +extern const QuicApiTable* MsQuic; + +#define QUIC_SKIP_GLOBAL_CONSTRUCTORS + +#include +#include + +#include +#include +#include + +#ifndef _KERNEL_MODE +#include // Needed for placement new +#else +#include +#endif + +struct PerfSelfSignedConfiguration { +#ifdef _KERNEL_MODE + uint8_t SelfSignedSecurityHash[20]; +#else + QUIC_SEC_CONFIG_PARAMS* SelfSignedParams; +#endif +}; + +#define QUIC_TEST_SESSION_CLOSED 1 + +inline +int +WriteOutput( + _In_z_ const char* format + ... + ) +{ +#ifndef _KERNEL_MODE + va_list args; + va_start(args, format); + int rval = vprintf(format, args); + va_end(args); + return rval; +#else + UNREFERENCED_PARAMETER(format); + return 0; +#endif +} + +struct PerfSecurityConfig { + QUIC_STATUS Initialize(int argc, char** argv, const MsQuicRegistration& Registration, PerfSelfSignedConfiguration* Config) { + uint16_t useSelfSigned = 0; + if (TryGetValue(argc, argv, "selfsign", &useSelfSigned)) { +#ifdef _KERNEL_MODE + CreateSecConfigHelper Helper; + SecurityConfig = + Helper.Create( + MsQuic, + Registration, + QUIC_SEC_CONFIG_FLAG_CERTIFICATE_HASH, + &Config->SelfSignedSecurityHash, + nullptr); +#else + SecurityConfig = + GetSecConfigForSelfSigned( + MsQuic, + Registration, + Config->SelfSignedParams); +#endif + if (!SecurityConfig) { + WriteOutput("Failed to create security config for self signed certificate\n"); + return QUIC_STATUS_INVALID_PARAMETER; + } + } else { + const char* certThumbprint; + if (!TryGetValue(argc, argv, "thumbprint", &certThumbprint)) { + WriteOutput("Must specify -thumbprint: for server mode.\n"); + return QUIC_STATUS_INVALID_PARAMETER; + } + const char* certStoreName; + if (!TryGetValue(argc, argv, "cert_store", &certStoreName)) { + SecurityConfig = GetSecConfigForThumbprint(MsQuic, Registration, certThumbprint); + if (SecurityConfig == nullptr) { + WriteOutput("Failed to create security configuration for thumbprint:'%s'.\n", certThumbprint); + return QUIC_STATUS_INVALID_PARAMETER; + } + } else { + uint32_t machineCert = 0; + TryGetValue(argc, argv, "machine_cert", &machineCert); + QUIC_CERTIFICATE_HASH_STORE_FLAGS flags = + machineCert ? QUIC_CERTIFICATE_HASH_STORE_FLAG_MACHINE_STORE : QUIC_CERTIFICATE_HASH_STORE_FLAG_NONE; + + SecurityConfig = GetSecConfigForThumbprintAndStore(MsQuic, Registration, flags, certThumbprint, certStoreName); + if (SecurityConfig == nullptr) { + WriteOutput( + "Failed to create security configuration for thumbprint:'%s' and store: '%s'.\n", + certThumbprint, + certStoreName); + return QUIC_STATUS_INVALID_PARAMETER; + } + } + } + return QUIC_STATUS_SUCCESS; + } + + ~PerfSecurityConfig() { + if (SecurityConfig) { + MsQuic->SecConfigDelete(SecurityConfig); + } + } + + operator QUIC_SEC_CONFIG*() const { return SecurityConfig; } + + QUIC_SEC_CONFIG* SecurityConfig {nullptr}; +}; + +struct CountHelper { + long RefCount; + + QUIC_EVENT Done; + + CountHelper() : + RefCount{1}, Done{} {} + + CountHelper(QUIC_EVENT Done) : + RefCount{1}, Done{Done} { } + + bool + Wait( + uint32_t Milliseconds + ) { + if (InterlockedDecrement(&RefCount) == 0) { + return true; + } else { + return !QuicEventWaitWithTimeout(Done, Milliseconds); + } + } + + void + WaitForever( + ) { + if (InterlockedDecrement(&RefCount) == 0) { + return; + } else { + QuicEventWaitForever(Done); + } + } + + void + AddItem( + ) { + InterlockedIncrement(&RefCount); + } + + void + CompleteItem( + ) { + if (InterlockedDecrement(&RefCount) == 0) { + QuicEventSet(Done); + } + } +}; + +// +// Implementation of std::forward, to allow use in kernel mode. +// Based on reference implementation in MSVC's STL +// + +template +struct QuicRemoveReference { + using type = _Ty; + using _Const_thru_ref_type = const _Ty; +}; + +template +using QuicRemoveReferenceT = typename QuicRemoveReference<_Ty>::type; + +template +constexpr _Ty&& QuicForward( + QuicRemoveReferenceT<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue + return static_cast<_Ty&&>(_Arg); +} + +class QuicPoolBufferAllocator { + QUIC_POOL Pool; + bool Initialized {false}; +public: + QuicPoolBufferAllocator() { + QuicZeroMemory(&Pool, sizeof(Pool)); + } + + ~QuicPoolBufferAllocator() { + if (Initialized) { + QuicPoolUninitialize(&Pool); + Initialized = false; + } + } + + void Initialize(uint32_t Size, bool Paged = false) { + QUIC_DBG_ASSERT(Initialized == false); + QuicPoolInitialize(Paged, Size, &Pool); + Initialized = true; + } + + uint8_t* Alloc() { + return static_cast(QuicPoolAlloc(&Pool)); + } + + void Free(uint8_t* Buf) { + if (Buf == nullptr) { + return; + } + QuicPoolFree(&Pool, Buf); + } +}; + +template +class QuicPoolAllocator { + QUIC_POOL Pool; +public: + QuicPoolAllocator() { + QuicPoolInitialize(Paged, sizeof(T), &Pool); + } + + ~QuicPoolAllocator() { + QuicPoolUninitialize(&Pool); + } + + template + T* Alloc(Args&&... args) { + void* Raw = QuicPoolAlloc(&Pool); + if (Raw == nullptr) { + return nullptr; + } + return new (Raw) T (QuicForward(args)...); + } + + void Free(T* Obj) { + if (Obj == nullptr) { + return; + } + Obj->~T(); + QuicPoolFree(&Pool, Obj); + } +}; + +// +// Arg Value Parsers +// + +inline +_Success_(return != false) +bool +IsValue( + _In_z_ const char* name, + _In_z_ const char* toTestAgainst + ) +{ + return _strnicmp(name, toTestAgainst, min(strlen(name), strlen(toTestAgainst))) == 0; +} diff --git a/src/perf/lib/SendRequest.h b/src/perf/lib/SendRequest.h new file mode 100644 index 000000000..5dd244d2b --- /dev/null +++ b/src/perf/lib/SendRequest.h @@ -0,0 +1,49 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Perf Send Request Wrapper. + +--*/ + +#pragma once + +#include "PerfHelpers.h" + +struct SendRequest { + QUIC_SEND_FLAGS Flags {QUIC_SEND_FLAG_NONE}; + QUIC_BUFFER QuicBuffer; + QuicPoolBufferAllocator* BufferAllocator; + uint32_t IoSize; + SendRequest( + QuicPoolBufferAllocator* BufferAllocator, + uint32_t IoSize, + bool FillBuffer + ) { + this->BufferAllocator = BufferAllocator; + this->IoSize = IoSize; + QuicBuffer.Buffer = BufferAllocator->Alloc(); + if (FillBuffer) { + memset(QuicBuffer.Buffer, 0xBF, IoSize); + } + QuicBuffer.Length = 0; + } + + ~SendRequest() { + BufferAllocator->Free(QuicBuffer.Buffer); + } + + void SetLength( + uint64_t BytesLeftToSend + ) { + if (BytesLeftToSend > IoSize) { + QuicBuffer.Length = IoSize; + } else { + Flags |= QUIC_SEND_FLAG_FIN; + QuicBuffer.Length = (uint32_t)BytesLeftToSend; + } + } +}; diff --git a/src/perf/lib/ThroughputClient.cpp b/src/perf/lib/ThroughputClient.cpp new file mode 100644 index 000000000..878fca47b --- /dev/null +++ b/src/perf/lib/ThroughputClient.cpp @@ -0,0 +1,387 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Perf Throughput Client Implementation. + +--*/ + +#ifdef QUIC_CLOG +#include "ThroughputClient.cpp.clog.h" +#endif + +#define QUIC_API_ENABLE_INSECURE_FEATURES 1 + +#include "ThroughputClient.h" +#include "ThroughputCommon.h" +#include "quic_trace.h" + +static +void +PrintHelp( + ) { + WriteOutput("Usage: quicperf -TestName:Throughput [options]\n\n" +#if _WIN32 + " -comp:<####> The compartment ID to run in.\n" + " -core:<####> The CPU core to use for the main thread.\n" +#endif + " -bind: A local IP address to bind to.\n" + " -port:<####> The UDP port of the server. (def:%u)\n" + " -ip:<0/4/6> A hint for the resolving the hostname to an IP address. (def:0)\n" + " -encrypt:<0/1> Enables/disables encryption. (def:%u)\n" + " -sendbuf:<0/1> Whether to use send buffering. (def:%u)\n" + " -length:<####> The length of streams opened locally. (def:0)\n" + " -iosize:<####> The size of each send request queued. (buffered def:%u) (nonbuffered def:%u)\n" + " -iocount:<####> The number of outstanding send requests to queue per stream. (buffered def:%u) (nonbuffered def:%u)\n", + THROUGHPUT_DEFAULT_PORT, + THROUGHPUT_DEFAULT_IO_SIZE_BUFFERED, THROUGHPUT_DEFAULT_IO_SIZE_NONBUFFERED, + THROGHTPUT_DEFAULT_SEND_COUNT_BUFFERED, THROUGHPUT_DEFAULT_SEND_COUNT_NONBUFFERED + ); +} + +ThroughputClient::ThroughputClient( + ) { + QuicZeroMemory(&LocalIpAddr, sizeof(LocalIpAddr)); + if (Session.IsValid()) { + Session.SetAutoCleanup(); + } +} + +QUIC_STATUS +ThroughputClient::Init( + _In_ int argc, + _In_reads_(argc) _Null_terminated_ char* argv[] + ) { + if (!Session.IsValid()) { + return Session.GetInitStatus(); + } + + Port = THROUGHPUT_DEFAULT_PORT; + TryGetValue(argc, argv, "port", &Port); + + TryGetValue(argc, argv, "encrypt", &UseEncryption); + + const char* Target; + if (!TryGetValue(argc, argv, "target", &Target)) { + WriteOutput("Must specify '-target' argument!\n"); + PrintHelp(); + return QUIC_STATUS_INVALID_PARAMETER; + } + + uint16_t Ip; + if (TryGetValue(argc, argv, "ip", &Ip)) { + switch (Ip) { + case 4: RemoteFamily = AF_INET; break; + case 6: RemoteFamily = AF_INET6; break; + } + } + + TryGetValue(argc, argv, "length", &Length); + + const char* LocalAddress = nullptr; + if (TryGetValue(argc, argv, "bind", &LocalAddress)) { + if (!ConvertArgToAddress(LocalAddress, 0, &LocalIpAddr)) { + WriteOutput("Failed to decode IP address: '%s'!\nMust be *, a IPv4 or a IPv6 address.\n", LocalAddress); + PrintHelp(); + return QUIC_STATUS_INVALID_PARAMETER; + } + } + + // TODO: Core, since we need to support kernel mode +#ifdef QUIC_COMPARTMENT_ID + uint16_t CompartmentId; + if (TryGetValue(argc, argv, "comp", &CompartmentId)) { + NETIO_STATUS status; + if (!NETIO_SUCCESS(status = QuicCompartmentIdSetCurrent(CompartmentId))) { + WriteOutput("Failed to set compartment ID = %d: 0x%x\n", CompartmentId, status); + return QUIC_STATUS_INVALID_PARAMETER; + } else { + WriteOutput("Running in Compartment %d\n", CompartmentId); + } + } +#endif + +#ifdef QuicSetCurrentThreadAffinityMask + uint8_t CpuCore; + if (TryGetValue(argc, argv, "core", &CpuCore)) { + QuicSetCurrentThreadAffinityMask((DWORD_PTR)(1ull << CpuCore)); + } +#endif + + TryGetValue(argc, argv, "sendbuf", &UseSendBuffer); + + IoSize = UseSendBuffer ? THROUGHPUT_DEFAULT_IO_SIZE_BUFFERED : THROUGHPUT_DEFAULT_IO_SIZE_NONBUFFERED; + TryGetValue(argc, argv, "iosize", &IoSize); + + IoCount = UseSendBuffer ? THROGHTPUT_DEFAULT_SEND_COUNT_BUFFERED : THROUGHPUT_DEFAULT_SEND_COUNT_NONBUFFERED; + TryGetValue(argc, argv, "iocount", &IoCount); + + size_t Len = strlen(Target); + TargetData.reset(new char[Len + 1]); + QuicCopyMemory(TargetData.get(), Target, Len); + TargetData[Len] = '\0'; + + BufferAllocator.Initialize(IoSize); + + return QUIC_STATUS_SUCCESS; +} + +struct ShutdownWrapper { + HQUIC ConnHandle {nullptr}; + ~ShutdownWrapper() { + if (ConnHandle) { + MsQuic->ConnectionShutdown(ConnHandle, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); + } + } +}; + +QUIC_STATUS +ThroughputClient::Start( + _In_ QUIC_EVENT StopEvnt + ) { + ShutdownWrapper Shutdown; + ConnectionData* ConnData = ConnectionDataAllocator.Alloc(this); + if (!ConnData) { + return QUIC_STATUS_OUT_OF_MEMORY; + } + QUIC_STATUS Status = + MsQuic->ConnectionOpen( + Session, + [](HQUIC Handle, void* Context, QUIC_CONNECTION_EVENT* Event) -> QUIC_STATUS { + ConnectionData* ConnData = (ConnectionData*)Context; + return ConnData->Client-> + ConnectionCallback( + Handle, + Event, + ConnData); + }, + ConnData, + &ConnData->Connection.Handle); + if (QUIC_FAILED(Status)) { + WriteOutput("Failed ConnectionOpen 0x%x\n", Status); + ConnectionDataAllocator.Free(ConnData); + return Status; + } + + Shutdown.ConnHandle = ConnData->Connection.Handle; + + uint32_t SecFlags = QUIC_CERTIFICATE_FLAG_DISABLE_CERT_VALIDATION; + Status = + MsQuic->SetParam( + ConnData->Connection, + QUIC_PARAM_LEVEL_CONNECTION, + QUIC_PARAM_CONN_CERT_VALIDATION_FLAGS, + sizeof(SecFlags), + &SecFlags); + if (QUIC_FAILED(Status)) { + WriteOutput("Failed Cert Validation Disable 0x%x\n", Status); + return Status; + } + + if (!UseSendBuffer) { + BOOLEAN Opt = FALSE; + Status = + MsQuic->SetParam( + ConnData->Connection, + QUIC_PARAM_LEVEL_CONNECTION, + QUIC_PARAM_CONN_SEND_BUFFERING, + sizeof(Opt), + &Opt); + if (QUIC_FAILED(Status)) { + WriteOutput("Failed Disable Send Buffering 0x%x\n", Status); + return Status; + } + } + + if (!UseEncryption) { + BOOLEAN value = TRUE; + Status = + MsQuic->SetParam( + ConnData->Connection, + QUIC_PARAM_LEVEL_CONNECTION, + QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION, + sizeof(value), + &value); + if (QUIC_FAILED(Status)) { + WriteOutput("MsQuic->SetParam (CONN_DISABLE_1RTT_ENCRYPTION) failed!\n", Status); + return Status; + } + } + + if (QuicAddrGetFamily(&LocalIpAddr) != AF_UNSPEC) { + MsQuic->SetParam( + ConnData->Connection, + QUIC_PARAM_LEVEL_CONNECTION, + QUIC_PARAM_CONN_LOCAL_ADDRESS, + sizeof(LocalIpAddr), + &LocalIpAddr); + } + + Status = + MsQuic->ConnectionStart( + ConnData->Connection, + RemoteFamily, + TargetData.get(), + Port); + if (QUIC_FAILED(Status)) { + WriteOutput("Failed ConnectionStart 0x%x\n", Status); + return Status; + } + + StreamData* StrmData = StreamDataAllocator.Alloc(this, ConnData->Connection); + + Status = + MsQuic->StreamOpen( + ConnData->Connection, + QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL, + [](HQUIC Handle, void* Context, QUIC_STREAM_EVENT* Event) -> QUIC_STATUS { + return ((StreamData*)Context)->Client-> + StreamCallback( + Handle, + Event, + (StreamData*)Context); + }, + StrmData, + &StrmData->Stream.Handle); + if (QUIC_FAILED(Status)) { + WriteOutput("Failed StreamOpen 0x%x\n", Status); + StreamDataAllocator.Free(StrmData); + return Status; + } + + Status = + MsQuic->StreamStart( + StrmData->Stream.Handle, + QUIC_STREAM_START_FLAG_NONE); + if (QUIC_FAILED(Status)) { + WriteOutput("Failed StreamStart 0x%x\n", Status); + StreamDataAllocator.Free(StrmData); + return Status; + } + + this->StopEvent = StopEvnt; + StrmData->StartTime = QuicTimeUs64(); + + if (Length == 0) { + Status = + MsQuic->StreamShutdown( + StrmData->Stream.Handle, + QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, + 0); + return Status; + } + + uint32_t SendRequestCount = 0; + while (StrmData->BytesSent < Length && SendRequestCount < IoCount) { + SendRequest* SendReq = SendRequestAllocator.Alloc(&BufferAllocator, IoSize, true); + SendReq->SetLength(Length - StrmData->BytesSent); + StrmData->BytesSent += SendReq->QuicBuffer.Length; + ++SendRequestCount; + Status = + MsQuic->StreamSend( + StrmData->Stream, + &SendReq->QuicBuffer, + 1, + SendReq->Flags, + SendReq); + if (QUIC_FAILED(Status)) { + WriteOutput("Failed StreamSend 0x%x\n", Status); + SendRequestAllocator.Free(SendReq); + return Status; + } + } + WriteOutput("Started!\n"); + Shutdown.ConnHandle = nullptr; + return Status; +} + +QUIC_STATUS +ThroughputClient::Wait( + _In_ int Timeout + ) { + if (Timeout > 0) { + QuicEventWaitWithTimeout(StopEvent, Timeout); + } else { + QuicEventWaitForever(StopEvent); + } + return QUIC_STATUS_SUCCESS; +} + +QUIC_STATUS +ThroughputClient::ConnectionCallback( + _In_ HQUIC /*ConnectionHandle*/, + _Inout_ QUIC_CONNECTION_EVENT* Event, + _Inout_ ConnectionData* ConnData + ) { + switch (Event->Type) { + case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE: + ConnectionDataAllocator.Free(ConnData); + QuicEventSet(StopEvent); + break; + default: + break; + } + return QUIC_STATUS_SUCCESS; +} + +QUIC_STATUS +ThroughputClient::StreamCallback( + _In_ HQUIC StreamHandle, + _Inout_ QUIC_STREAM_EVENT* Event, + _Inout_ StreamData* StrmData + ) { + switch (Event->Type) { + case QUIC_STREAM_EVENT_SEND_COMPLETE: { + SendRequest* Req = (SendRequest*)Event->SEND_COMPLETE.ClientContext; + if (!Event->SEND_COMPLETE.Canceled) { + uint64_t BytesLeftToSend = Length - StrmData->BytesSent; + StrmData->BytesCompleted += Req->QuicBuffer.Length; + if (BytesLeftToSend != 0) { + Req->SetLength(BytesLeftToSend); + StrmData->BytesSent += Req->QuicBuffer.Length; + + if (QUIC_SUCCEEDED( + MsQuic->StreamSend( + StrmData->Stream.Handle, + &Req->QuicBuffer, + 1, + Req->Flags, + Req))) { + Req = nullptr; + } + } + } + if (Req) { + SendRequestAllocator.Free(Req); + } + break; + } + case QUIC_STREAM_EVENT_PEER_SEND_ABORTED: + case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED: + MsQuic->StreamShutdown( + StreamHandle, + QUIC_STREAM_SHUTDOWN_FLAG_ABORT_SEND | QUIC_STREAM_SHUTDOWN_FLAG_ABORT_RECEIVE, + 0); + break; + case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: { + StrmData->EndTime = QuicTimeUs64(); + uint64_t ElapsedMicroseconds = StrmData->EndTime - StrmData->StartTime; + uint32_t SendRate = (uint32_t)((StrmData->BytesCompleted * 1000 * 1000 * 8) / (1000 * ElapsedMicroseconds)); + + WriteOutput("[%p][%llu] Closed [%s] after %u.%u ms. (TX %llu bytes @ %u kbps).\n", + StrmData->Connection, + GetStreamID(MsQuic, StreamHandle), + "Complete", + (uint32_t)(ElapsedMicroseconds / 1000), + (uint32_t)(ElapsedMicroseconds % 1000), + StrmData->BytesCompleted, SendRate); + + StreamDataAllocator.Free(StrmData); + break; + } + } + return QUIC_STATUS_SUCCESS; +} diff --git a/src/perf/lib/ThroughputClient.h b/src/perf/lib/ThroughputClient.h new file mode 100644 index 000000000..03bfc66bf --- /dev/null +++ b/src/perf/lib/ThroughputClient.h @@ -0,0 +1,101 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Perf Throughput Client declaration. Defines the functions and + variables used in the ThroughputClient class. + +--*/ + + +#pragma once + +#include "PerfHelpers.h" +#include "PerfBase.h" +#include "ThroughputCommon.h" +#include "SendRequest.h" + +class ThroughputClient : public PerfBase { +public: + ThroughputClient(); + + QUIC_STATUS + Init( + _In_ int argc, + _In_reads_(argc) _Null_terminated_ char* argv[] + ) override; + + QUIC_STATUS + Start( + _In_ QUIC_EVENT StopEvent + ) override; + + QUIC_STATUS + Wait( + _In_ int Timeout + ) override; + +private: + + struct ConnectionData { + ConnectionData( + _In_ ThroughputClient* Client) + : Client{Client} { + + } + ThroughputClient* Client{ nullptr }; + ConnectionScope Connection; + uint8_t Padding[16]; // Padding for Pools + }; + + struct StreamData { + StreamData( + _In_ ThroughputClient* Client, + _In_ HQUIC Connection) + : Client{Client}, Connection{Connection} { + + } + ThroughputClient* Client{ nullptr }; + HQUIC Connection; + StreamScope Stream; + uint64_t BytesSent{0}; + uint64_t BytesCompleted{0}; + uint64_t StartTime{0}; + uint64_t EndTime{0}; + }; + + QUIC_STATUS + ConnectionCallback( + _In_ HQUIC ConnectionHandle, + _Inout_ QUIC_CONNECTION_EVENT* Event, + _Inout_ ConnectionData* ConnectionData + ); + + QUIC_STATUS + StreamCallback( + _In_ HQUIC StreamHandle, + _Inout_ QUIC_STREAM_EVENT* Event, + _Inout_ StreamData* StrmData + ); + + MsQuicRegistration Registration; + MsQuicSession Session{Registration, THROUGHPUT_ALPN}; + QuicPoolAllocator StreamDataAllocator; + QuicPoolAllocator ConnectionDataAllocator; + QuicPoolAllocator SendRequestAllocator; + QuicPoolBufferAllocator BufferAllocator; + UniquePtr TargetData; + uint16_t Port{ 0 }; + QUIC_EVENT StopEvent{}; + uint64_t Length{0}; + bool ConstructionSuccess {false}; + uint8_t UseSendBuffer{1}; + QUIC_ADDR LocalIpAddr{}; + uint16_t RemoteFamily{AF_UNSPEC}; + uint32_t IoSize{0}; + uint32_t IoCount{0}; + uint8_t UseEncryption{1}; +}; diff --git a/src/perf/lib/ThroughputCommon.h b/src/perf/lib/ThroughputCommon.h new file mode 100644 index 000000000..7fafff2c1 --- /dev/null +++ b/src/perf/lib/ThroughputCommon.h @@ -0,0 +1,24 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Perf Throughput Common definitions. + +--*/ + + +#pragma once + +#define THROUGHPUT_DEFAULT_PORT 4433 +#define THROUGHPUT_ALPN "Throughput" +#define THROUGHPUT_DEFAULT_DISCONNECT_TIMEOUT (10 * 1000) +#define THROUGHPUT_DEFAULT_IDLE_TIMEOUT 1000 +#define THROUGHPUT_SERVER_PEER_UNI 1 +#define THROUGHPUT_CLIENT_UNI 1 +#define THROUGHPUT_DEFAULT_IO_SIZE_BUFFERED 0x10000 +#define THROUGHPUT_DEFAULT_IO_SIZE_NONBUFFERED 0x100000 +#define THROGHTPUT_DEFAULT_SEND_COUNT_BUFFERED 1 +#define THROUGHPUT_DEFAULT_SEND_COUNT_NONBUFFERED 8 diff --git a/src/perf/lib/ThroughputServer.cpp b/src/perf/lib/ThroughputServer.cpp new file mode 100644 index 000000000..ff2efd042 --- /dev/null +++ b/src/perf/lib/ThroughputServer.cpp @@ -0,0 +1,205 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Perf Throughput Server Implementation. + +--*/ + +#ifdef QUIC_CLOG +#include "ThroughputServer.cpp.clog.h" +#endif + +#ifndef _KERNEL_MODE +#define QUIC_TEST_APIS 1 +#endif +#define QUIC_API_ENABLE_INSECURE_FEATURES 1 +#include "msquichelper.h" +#include "quic_trace.h" +#include "ThroughputServer.h" +#include "ThroughputCommon.h" + +static +void +PrintHelp( + ) { + WriteOutput("Usage: quicperf -TestName:Throughput -ServerMode:1 [options]\n\n" + " -listen: The local IP address to listen on, or * for all IP addresses.\n" + " -thumbprint: The hash or thumbprint of the certificate to use.\n" + " -cert_store: The certificate store to search for the thumbprint in.\n" + " -machine_cert:<0/1> Use the machine, or current user's, certificate store. (def:0)\n" + " -connections:<####> The number of connections to create. (def:0)\n" + " -port:<####> The UDP port of the server. (def:%u)\n", + THROUGHPUT_DEFAULT_PORT + ); +} + +ThroughputServer::ThroughputServer( + _In_ PerfSelfSignedConfiguration* SelfSignedConfig + ) : SelfSignedConfig{SelfSignedConfig} { + QuicZeroMemory(&Address, sizeof(Address)); + if (Session.IsValid()) { + Session.SetAutoCleanup(); + Session.SetPeerUnidiStreamCount(THROUGHPUT_SERVER_PEER_UNI); + Session.SetDisconnectTimeout(THROUGHPUT_DEFAULT_DISCONNECT_TIMEOUT); + Session.SetIdleTimeout(THROUGHPUT_DEFAULT_IDLE_TIMEOUT); + } +} + +QUIC_STATUS +ThroughputServer::Init( + _In_ int argc, + _In_reads_(argc) _Null_terminated_ char* argv[] + ) { + if (!Listener.IsValid()) { + return Listener.GetInitStatus(); + } + + uint16_t port = THROUGHPUT_DEFAULT_PORT; + TryGetValue(argc, argv, "port", &port); + + const char* localAddress = nullptr; + if (!TryGetValue(argc, argv, "listen", &localAddress)) { + WriteOutput("Server mode must have -listen\n"); + PrintHelp(); + return QUIC_STATUS_INVALID_PARAMETER; + } + if (!ConvertArgToAddress(localAddress, port, &Address)) { + WriteOutput("Failed to decode IP address: '%s'!\nMust be *, a IPv4 or a IPv6 address.\n", localAddress); + PrintHelp(); + return QUIC_STATUS_INVALID_PARAMETER; + } + + TryGetValue(argc, argv, "connections", &NumberOfConnections); + + QUIC_STATUS Status = SecurityConfig.Initialize(argc, argv, Registration, SelfSignedConfig); + return Status; +} + +QUIC_STATUS +ThroughputServer::Start( + _In_ QUIC_EVENT StopEvent + ) { + QUIC_STATUS Status = + Listener.Start( + &Address, + [](HQUIC Handle, void* Context, QUIC_LISTENER_EVENT* Event) -> QUIC_STATUS { + return ((ThroughputServer*)Context)->ListenerCallback(Handle, Event); + }, + this); + if (QUIC_FAILED(Status)) { + return Status; + } + RefCount = CountHelper{StopEvent}; + if (NumberOfConnections > 0) { + for (uint32_t i = 0; i < NumberOfConnections; i++) { + RefCount.AddItem(); + } + } else { + // + // Add a single item so we can wait on the Count Helper + // + RefCount.AddItem(); + } + return QUIC_STATUS_SUCCESS; +} + +QUIC_STATUS +ThroughputServer::Wait( + _In_ int Timeout + ) { + if (Timeout > 0) { + RefCount.Wait(Timeout); + } else { + RefCount.WaitForever(); + } + return QUIC_STATUS_SUCCESS; +} + +QUIC_STATUS +ThroughputServer::ListenerCallback( + _In_ HQUIC /*ListenerHandle*/, + _Inout_ QUIC_LISTENER_EVENT* Event + ) { + switch (Event->Type) { + case QUIC_LISTENER_EVENT_NEW_CONNECTION: { + Event->NEW_CONNECTION.SecurityConfig = SecurityConfig; + QUIC_CONNECTION_CALLBACK_HANDLER Handler = + [](HQUIC Conn, void* Context, QUIC_CONNECTION_EVENT* Event) -> QUIC_STATUS { + return ((ThroughputServer*)Context)-> + ConnectionCallback( + Conn, + Event); + }; + MsQuic->SetCallbackHandler( + Event->NEW_CONNECTION.Connection, + (void*)Handler, + this); + BOOLEAN value = TRUE; + if (QUIC_FAILED( + MsQuic->SetParam( + Event->NEW_CONNECTION.Connection, + QUIC_PARAM_LEVEL_CONNECTION, + QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION, + sizeof(value), + &value))) { + WriteOutput("MsQuic->SetParam (CONN_DISABLE_1RTT_ENCRYPTION) failed!\n"); + } + break; + } + } + return QUIC_STATUS_SUCCESS; +} + +QUIC_STATUS +ThroughputServer::ConnectionCallback( + _In_ HQUIC ConnectionHandle, + _Inout_ QUIC_CONNECTION_EVENT* Event + ) { + switch (Event->Type) { + case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE: + MsQuic->ConnectionClose(ConnectionHandle); + if (NumberOfConnections > 0) { + RefCount.CompleteItem(); + } + break; + case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED: { + QUIC_STREAM_CALLBACK_HANDLER Handler = + [](HQUIC Stream, void* Context, QUIC_STREAM_EVENT* Event) -> QUIC_STATUS { + return ((ThroughputServer*)Context)-> + StreamCallback( + Stream, + Event); + }; + MsQuic->SetCallbackHandler(Event->PEER_STREAM_STARTED.Stream, (void*)Handler, this); + break; + } + default: + break; + } + return QUIC_STATUS_SUCCESS; +} + +QUIC_STATUS +ThroughputServer::StreamCallback( + _In_ HQUIC StreamHandle, + _Inout_ QUIC_STREAM_EVENT* Event + ) { + switch (Event->Type) { + case QUIC_STREAM_EVENT_PEER_SEND_ABORTED: + case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED: + MsQuic->StreamShutdown( + StreamHandle, + QUIC_STREAM_SHUTDOWN_FLAG_ABORT_SEND | QUIC_STREAM_SHUTDOWN_FLAG_ABORT_RECEIVE, + 0); + break; + case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: { + MsQuic->StreamClose(StreamHandle); + break; + } + } + return QUIC_STATUS_SUCCESS; +} diff --git a/src/perf/lib/ThroughputServer.h b/src/perf/lib/ThroughputServer.h new file mode 100644 index 000000000..8acec4d5f --- /dev/null +++ b/src/perf/lib/ThroughputServer.h @@ -0,0 +1,69 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Perf Throughput Server declaration. Defines the functions and + variables used in the ThroughputServer class. + +--*/ + +#pragma once + +#include "PerfHelpers.h" +#include "PerfBase.h" +#include "ThroughputCommon.h" + +class ThroughputServer : public PerfBase { +public: + ThroughputServer( + _In_ PerfSelfSignedConfiguration* SelfSignedConfig + ); + + QUIC_STATUS + Init( + _In_ int argc, + _In_reads_(argc) _Null_terminated_ char* argv[] + ) override; + + QUIC_STATUS + Start( + _In_ QUIC_EVENT StopEvent + ) override; + + QUIC_STATUS + Wait( + int Timeout + ) override; + +private: + + QUIC_STATUS + ListenerCallback( + _In_ HQUIC ListenerHandle, + _Inout_ QUIC_LISTENER_EVENT* Event + ); + + QUIC_STATUS + ConnectionCallback( + _In_ HQUIC ConnectionHandle, + _Inout_ QUIC_CONNECTION_EVENT* Event + ); + + QUIC_STATUS + StreamCallback( + _In_ HQUIC StreamHandle, + _Inout_ QUIC_STREAM_EVENT* Event + ); + + MsQuicRegistration Registration; + MsQuicSession Session{Registration, THROUGHPUT_ALPN}; + MsQuicListener Listener{Session}; + PerfSelfSignedConfiguration* SelfSignedConfig; + PerfSecurityConfig SecurityConfig; + QUIC_ADDR Address{}; + uint32_t NumberOfConnections {0}; + CountHelper RefCount; +}; diff --git a/src/perf/lib/perflib.kernel.vcxproj b/src/perf/lib/perflib.kernel.vcxproj new file mode 100644 index 000000000..3579d619f --- /dev/null +++ b/src/perf/lib/perflib.kernel.vcxproj @@ -0,0 +1,107 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + + + + + + + + + {11633785-79cc-4c7d-ab6a-aecdf29a1fa7} + {0a049372-4c4d-4ea0-a64e-dc6ad88ceca1} + v4.5 + 12.0 + KMDF + + + + Windows10 + WindowsKernelModeDriver10.0 + StaticLibrary + Universal + + + true + + + false + + + + + + + + + perflib + $(SolutionDir)build\winkernel\$(Platform)_$(Configuration)_schannel\obj\$(ProjectName)\ + $(SolutionDir)build\winkernel\$(Platform)_$(Configuration)_schannel\bin\ + + + + ..\;..\..\inc;..\..\..\submodules\wil\include;$(SolutionDir)build\winkernel\$(Platform)_$(Configuration)_schannel\inc;$(IntDir);%(AdditionalIncludeDirectories) + Speed + true + /Gw /kernel /ZH:SHA_256 + /Gw /kernel /ZH:SHA_256 -d2jumptablerdata -d2epilogunwindrequirev2 + + + true + + + + + MultiThreadedDebugDLL + QUIC_EVENTS_MANIFEST_ETW;QUIC_LOGS_MANIFEST_ETW;QUIC_DISABLE_0RTT_TESTS;SECURITY_KERNEL;SECURITY_WIN32;_DEBUG;%(PreprocessorDefinitions) + true + stdcpp17 + 4748;5040;4459;%(DisableSpecificWarnings) + + + + + QUIC_EVENTS_MANIFEST_ETW;QUIC_LOGS_MANIFEST_ETW;QUIC_DISABLE_0RTT_TESTS;SECURITY_KERNEL;SECURITY_WIN32;%(PreprocessorDefinitions) + stdcpp17 + true + 4748;5040;4459;%(DisableSpecificWarnings) + + + + + diff --git a/src/perf/lib/quicmain.cpp b/src/perf/lib/quicmain.cpp new file mode 100644 index 000000000..179659946 --- /dev/null +++ b/src/perf/lib/quicmain.cpp @@ -0,0 +1,106 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Perf Main execution engine. + +--*/ + +#ifndef _KERNEL_MODE +#define QUIC_TEST_APIS 1 +#endif +#include "quic_driver_main.h" + +#include "ThroughputServer.h" +#include "ThroughputClient.h" + +#include "quic_trace.h" +#ifdef QUIC_CLOG +#include "quicmain.cpp.clog.h" +#endif + +const QuicApiTable* MsQuic; + +PerfBase* TestToRun; + +static +void +PrintHelp( + ) { + WriteOutput("Usage: quicperf -TestName:[Throughput|] [options]\n" \ + "\n" \ + " -ServerMode:<1:0> default: '0'\n" \ + "\n\n" \ + "Run a test without arguments to see it's specific help\n" + ); +} + +QUIC_STATUS +QuicMainStart( + _In_ int argc, + _In_reads_(argc) _Null_terminated_ char* argv[], + _In_ QUIC_EVENT StopEvent, + _In_ PerfSelfSignedConfiguration* SelfSignedConfig + ) { + const char* TestName = GetValue(argc, argv, "TestName"); + if (!TestName) { + WriteOutput("Must have a TestName specified. Ex: -TestName:Throughput\n"); + PrintHelp(); + return QUIC_STATUS_INVALID_PARAMETER; + } + + uint8_t ServerMode = 0; + TryGetValue(argc, argv, "ServerMode", &ServerMode); + + QUIC_STATUS Status; + MsQuic = new QuicApiTable{}; + if (QUIC_FAILED(Status = MsQuic->InitStatus())) { + delete MsQuic; + MsQuic = nullptr; + return Status; + } + + if (IsValue(TestName, "Throughput")) { + if (ServerMode) { + TestToRun = new ThroughputServer{SelfSignedConfig}; + } else { + TestToRun = new ThroughputClient{}; + } + } else { + delete MsQuic; + return QUIC_STATUS_INVALID_PARAMETER; + } + + if (TestToRun != nullptr) { + Status = TestToRun->Init(argc, argv); + if (QUIC_SUCCEEDED(Status)) { + Status = TestToRun->Start(StopEvent); + if (QUIC_SUCCEEDED(Status)) { + return QUIC_STATUS_SUCCESS; + } + } + } else { + Status = QUIC_STATUS_OUT_OF_MEMORY; + } + + delete TestToRun; + delete MsQuic; + return Status; +} + +QUIC_STATUS +QuicMainStop( + _In_ int Timeout + ) { + if (TestToRun == nullptr) { + return QUIC_STATUS_SUCCESS; + } + + QUIC_STATUS Status = TestToRun->Wait(Timeout); + delete TestToRun; + delete MsQuic; + return Status; +} diff --git a/src/test/MsQuicTests.h b/src/test/MsQuicTests.h index 8fd55066e..b6ae583b3 100644 --- a/src/test/MsQuicTests.h +++ b/src/test/MsQuicTests.h @@ -302,12 +302,10 @@ LogTestFailure( // // Name of the driver service for msquictest.sys. // -#define QUIC_TEST_DRIVER_NAME "msquictest" +#define QUIC_DRIVER_NAME "msquictest" #ifdef _WIN32 -#define QUIC_TEST_IOCTL_PATH "\\\\.\\\\" QUIC_TEST_DRIVER_NAME - // // {85C2D886-FA01-4DDA-AAED-9A16CC7DA6CE} // diff --git a/src/test/bin/quic_gtest.h b/src/test/bin/quic_gtest.h index a9673edcf..775040170 100644 --- a/src/test/bin/quic_gtest.h +++ b/src/test/bin/quic_gtest.h @@ -11,6 +11,7 @@ #include #include #include "quic_trace.h" +#include "quic_driver_helpers.h" #undef min // gtest headers conflict with previous definitions of min/max. #undef max #include "gtest/gtest.h" @@ -511,293 +512,3 @@ std::ostream& operator << (std::ostream& o, const DrillInitialPacketTokenArgs& a class WithDrillInitialPacketTokenArgs: public testing::Test, public testing::WithParamInterface { }; - -// -// Windows Kernel Mode Helpers -// - -#ifdef _WIN32 - -class QuicDriverService { - SC_HANDLE ScmHandle; - SC_HANDLE ServiceHandle; -public: - QuicDriverService() : - ScmHandle(nullptr), - ServiceHandle(nullptr) { - } - bool Initialize() { - uint32_t Error; - ScmHandle = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS); - if (ScmHandle == nullptr) { - Error = GetLastError(); - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Error, - "GetFullPathName failed"); - return false; - } - QueryService: - ServiceHandle = - OpenServiceA( - ScmHandle, - QUIC_TEST_DRIVER_NAME, - SERVICE_ALL_ACCESS); - if (ServiceHandle == nullptr) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - GetLastError(), - "OpenService failed"); - char DriverFilePath[MAX_PATH] = {0}; - GetModuleFileNameA(NULL, DriverFilePath, MAX_PATH); - char* PathEnd = strrchr(DriverFilePath, '\\'); - if (!PathEnd || - sizeof(DriverFilePath) - (PathEnd - DriverFilePath) < sizeof("msquictest.sys")) { - QuicTraceEvent( - LibraryError, - "[ lib] ERROR, %s.", - "Can't build msquictest.sys full path"); - return false; - } - memcpy(PathEnd + 1, "msquictest.sys", sizeof("msquictest.sys")); - if (GetFileAttributesA(DriverFilePath) == INVALID_FILE_ATTRIBUTES) { - QuicTraceEvent( - LibraryError, - "[ lib] ERROR, %s.", - "Failed to find msquictest.sys"); - return false; - } - ServiceHandle = - CreateServiceA( - ScmHandle, - QUIC_TEST_DRIVER_NAME, - QUIC_TEST_DRIVER_NAME, - SC_MANAGER_ALL_ACCESS, - SERVICE_KERNEL_DRIVER, - SERVICE_DEMAND_START, - SERVICE_ERROR_NORMAL, - DriverFilePath, - nullptr, - nullptr, - "msquic\0", - nullptr, - nullptr); - if (ServiceHandle == nullptr) { - Error = GetLastError(); - if (Error == ERROR_SERVICE_EXISTS) { - goto QueryService; - } - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Error, - "CreateService failed"); - return false; - } - } - return true; - } - void Uninitialize() { - if (ServiceHandle != nullptr) { - CloseServiceHandle(ServiceHandle); - } - if (ScmHandle != nullptr) { - CloseServiceHandle(ScmHandle); - } - } - bool Start() { - if (!StartServiceA(ServiceHandle, 0, nullptr)) { - uint32_t Error = GetLastError(); - if (Error != ERROR_SERVICE_ALREADY_RUNNING) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Error, - "StartService failed"); - return false; - } - } - return true; - } -}; - -#else - -class QuicDriverService { -public: - bool Initialize() { return false; } - void Uninitialize() { } - bool Start() { return false; } -}; - -#endif // _WIN32 - -#ifdef _WIN32 - -class QuicDriverClient { - HANDLE DeviceHandle; -public: - QuicDriverClient() : DeviceHandle(INVALID_HANDLE_VALUE) { } - bool Initialize( - _In_ QUIC_SEC_CONFIG_PARAMS* SecConfigParams - ) { - uint32_t Error; - DeviceHandle = - CreateFileA( - QUIC_TEST_IOCTL_PATH, - GENERIC_READ | GENERIC_WRITE, - 0, - nullptr, // no SECURITY_ATTRIBUTES structure - OPEN_EXISTING, // No special create flags - FILE_FLAG_OVERLAPPED, // Allow asynchronous requests - nullptr); - if (DeviceHandle == INVALID_HANDLE_VALUE) { - Error = GetLastError(); - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Error, - "CreateFile failed"); - return false; - } - if (!Run(IOCTL_QUIC_SEC_CONFIG, SecConfigParams->Thumbprint, sizeof(SecConfigParams->Thumbprint), 30000)) { - CloseHandle(DeviceHandle); - DeviceHandle = INVALID_HANDLE_VALUE; - QuicTraceEvent( - LibraryError, - "[ lib] ERROR, %s.", - "Run(IOCTL_QUIC_SEC_CONFIG) failed"); - return false; - } - return true; - } - void Uninitialize() { - if (DeviceHandle != INVALID_HANDLE_VALUE) { - CloseHandle(DeviceHandle); - } - } - bool Run( - _In_ uint32_t IoControlCode, - _In_reads_bytes_opt_(InBufferSize) - void* InBuffer, - _In_ uint32_t InBufferSize, - _In_ uint32_t TimeoutMs = 30000 - ) { - uint32_t Error; - OVERLAPPED Overlapped = { 0 }; - Overlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (Overlapped.hEvent == nullptr) { - Error = GetLastError(); - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Error, - "CreateEvent failed"); - return false; - } - QuicTraceLogVerbose( - TestSendIoctl, - "[test] Sending IOCTL %u with %u bytes.", - IoGetFunctionCodeFromCtlCode(IoControlCode), - InBufferSize); - if (!DeviceIoControl( - DeviceHandle, - IoControlCode, - InBuffer, InBufferSize, - nullptr, 0, - nullptr, - &Overlapped)) { - Error = GetLastError(); - if (Error != ERROR_IO_PENDING) { - CloseHandle(Overlapped.hEvent); - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Error, - "DeviceIoControl failed"); - return false; - } - } - DWORD dwBytesReturned; - if (!GetOverlappedResultEx( - DeviceHandle, - &Overlapped, - &dwBytesReturned, - TimeoutMs, - FALSE)) { - Error = GetLastError(); - if (Error == WAIT_TIMEOUT) { - Error = ERROR_TIMEOUT; - CancelIoEx(DeviceHandle, &Overlapped); - } - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Error, - "GetOverlappedResultEx failed"); - } else { - Error = ERROR_SUCCESS; - } - CloseHandle(Overlapped.hEvent); - return Error == ERROR_SUCCESS; - } - bool Run( - _In_ uint32_t IoControlCode, - _In_ uint32_t TimeoutMs = 30000 - ) { - return Run(IoControlCode, nullptr, 0, TimeoutMs); - } - template - bool Run( - _In_ uint32_t IoControlCode, - _In_ const T& Data, - _In_ uint32_t TimeoutMs = 30000 - ) { - return Run(IoControlCode, (void*)&Data, sizeof(Data), TimeoutMs); - } -}; - -#else - -class QuicDriverClient { -public: - bool Initialize( - _In_ QUIC_SEC_CONFIG_PARAMS* SecConfigParams - ) { - UNREFERENCED_PARAMETER(SecConfigParams); - return false; - } - void Uninitialize() { } - bool Run( - _In_ uint32_t IoControlCode, - _In_ void* InBuffer, - _In_ uint32_t InBufferSize, - _In_ uint32_t TimeoutMs = 30000 - ) { - UNREFERENCED_PARAMETER(IoControlCode); - UNREFERENCED_PARAMETER(InBuffer); - UNREFERENCED_PARAMETER(InBufferSize); - UNREFERENCED_PARAMETER(TimeoutMs); - return false; - } - bool - Run( - _In_ uint32_t IoControlCode, - _In_ uint32_t TimeoutMs = 30000 - ) { - return Run(IoControlCode, nullptr, 0, TimeoutMs); - } - template - bool - Run( - _In_ uint32_t IoControlCode, - _In_ const T& Data, - _In_ uint32_t TimeoutMs = 30000 - ) { - return Run(IoControlCode, (void*)&Data, sizeof(Data), TimeoutMs); - } -}; - -#endif // _WIN32