From 45f9bb0b16bf5f2089d54550dec1332a65411794 Mon Sep 17 00:00:00 2001 From: Daniel dos Santos Pereira Date: Wed, 17 Jul 2024 11:02:30 -0300 Subject: [PATCH] Document troubleshooting of packets using wireshark (#4391) --- docs/Diagnostics.md | 44 ++++++++++++ docs/Sample.md | 4 ++ src/tools/sample/sample.c | 140 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+) diff --git a/docs/Diagnostics.md b/docs/Diagnostics.md index 832f83a19..d1061e21f 100644 --- a/docs/Diagnostics.md +++ b/docs/Diagnostics.md @@ -302,6 +302,50 @@ On the latest version of Windows, these counters are also exposed via PerfMon.ex Counters are also captured at the beginning of MsQuic ETW traces, and unlike PerfMon, includes all MsQuic instances running on the system, both user and kernel mode. +# Network Troubleshooting + +To see what is being transmited on the wire you might use an open-source tool like [Wireshark](https://www.wireshark.org). The packets captured by such tool will be encrypted due to TLS, therefore we must provide the secrets to enable Wireshark to decrypt the packets. + +To enable this we must generate a [SSLKEYLOGFILE](https://www.ietf.org/archive/id/draft-thomson-tls-keylogfile-00.html#name-introduction-10) with information about the secrets used in the TLS connection. With such file we will be able to decrypt the packets. + +For some browsers all you have to do is to set an environment variable `SSLKEYLOGFILE` with the absolute path of the log file to be generated and then you can load it into Wireshark for troubleshooting. For MsQuic applications we need to generate such file. A good practice is to check if the `SSLKEYLOGFILE` env variable is set and if so you write the file. The steps are: + +1. Set the `QUIC_PARAM_CONN_TLS_SECRETS` connection param with a struct to be populated with the TLS secrets by MsQuic. +```c +// Define empty struct for the TLS Secrets +QUIC_TLS_SECRETS ClientSecrets{}; +... + +// Get the value of the env variable to log the secrets +const char* SslKeyLogFile = getenv("SSLKEYLOGFILE"); +... + +// If the variable is set then we have a file to write the TLS secrets thus we +// pass the struct to be filled +if (SslKeyLogFile != NULL) { + MsQuic->SetParam(Connection, QUIC_PARAM_CONN_TLS_SECRETS, sizeof(ClientSecrets), &ClientSecrets); + // Check for errors... +} +``` +2. Write the file when the connection succeeds (event `QUIC_CONNECTION_EVENT_CONNECTED`). +```c +// On your connection callback function +... +if (Event->Type == QUIC_CONNECTION_EVENT_CONNECTED) { + if (SslKeyLogFile != NULL) { + WriteSslKeyLogFile(SslKeyLogFile, ClientSecrets); + } +} +``` +3. Write the `WriteSslKeyLogFile` function. You can just copy the function from [src/inc/msquichelper.h#WriteSslKeyLogFile](https://github.com/microsoft/msquic/blob/main/src/inc/msquichelper.h#L564) if it serves your needs or write your own. +4. Set the `SSLKEYLOGFILE` env variable set to the path of the log file and run the program. Then check the file with the secets. + +5. Load the key log into Wireshark and start capturing to decrypt the packets. To learn how to load such file inside Wireshark refer to this documentation: [Using the (Pre)-Master-Secret](https://wiki.wireshark.org/TLS#using-the-pre-master-secret). + +Using a Wireshark version that supports QUIC is not mandatory but could help when troubleshooting. To know which version supports QUIC refer to https://github.com/quicwg/base-drafts/wiki/Tools#wireshark. + +If you need a working example on how to generate the key log file please refer to the Sample at src/tools/sample/sample.c. + # Detailed Troubleshooting For detailed trouble shooting steps please see the MsQuic [Trouble Shooting Guide](TSG.md). diff --git a/docs/Sample.md b/docs/Sample.md index 1dc5ca7b5..e3cfe4b07 100644 --- a/docs/Sample.md +++ b/docs/Sample.md @@ -33,6 +33,10 @@ Start the client providing the IP address for the server. Here 127.0.0.1 is used quicsample.exe -client -unsecure -target:{127.0.0.1} ``` +## Troubleshooting with Wireshark + +When running the client you can set the environment variable `SSLKEYLOGFILE` to the absolute path of the file that will receive the TLS secrets. Learn how to load such file inside Wireshark and see what is being transmitted on the wire: [Using the (Pre)-Master-Secret](https://wiki.wireshark.org/TLS#using-the-pre-master-secret) + ## Console Output Here is what the console output looks like on the server and client sides after connection is established and data flows: diff --git a/src/tools/sample/sample.c b/src/tools/sample/sample.c index 9410c2399..6eec181eb 100644 --- a/src/tools/sample/sample.c +++ b/src/tools/sample/sample.c @@ -43,6 +43,7 @@ Abstract: // warning here. This is not an MsQuic bug but a Windows SDK bug. // #pragma warning(disable:5105) +#include #endif #include "msquic.h" #include @@ -99,6 +100,19 @@ HQUIC Registration; // HQUIC Configuration; + +// +// The struct to be filled with TLS secrets +// for debugging packet captured with e.g. Wireshark. +// +QUIC_TLS_SECRETS ClientSecrets = {0}; + +// +// The name of the environment variable being +// used to get the path to the ssl key log file. +// +const char* SslKeyLogEnvVar = "SSLKEYLOGFILE"; + void PrintUsage() { printf( @@ -190,6 +204,116 @@ DecodeHexBuffer( return HexBufferLen; } +void +EncodeHexBuffer( + _In_reads_(BufferLen) uint8_t* Buffer, + _In_ uint8_t BufferLen, + _Out_writes_bytes_(2*BufferLen) char* HexString + ) +{ + #define HEX_TO_CHAR(x) ((x) > 9 ? ('a' + ((x) - 10)) : '0' + (x)) + for (uint8_t i = 0; i < BufferLen; i++) { + HexString[i*2] = HEX_TO_CHAR(Buffer[i] >> 4); + HexString[i*2 + 1] = HEX_TO_CHAR(Buffer[i] & 0xf); + } +} + +void +WriteSslKeyLogFile( + _In_z_ const char* FileName, + _In_ QUIC_TLS_SECRETS* TlsSecrets + ) +{ + printf("Writing SSLKEYLOGFILE at %s\n", FileName); + FILE* File = NULL; +#ifdef _WIN32 + File = _fsopen(FileName, "ab", _SH_DENYNO); +#else + File = fopen(FileName, "ab"); +#endif + + if (File == NULL) { + printf("Failed to open sslkeylogfile %s\n", FileName); + return; + } + if (fseek(File, 0, SEEK_END) == 0 && ftell(File) == 0) { + fprintf(File, "# TLS 1.3 secrets log file, generated by msquic\n"); + } + + char ClientRandomBuffer[(2 * sizeof(((QUIC_TLS_SECRETS*)0)->ClientRandom)) + 1] = {0}; + + char TempHexBuffer[(2 * QUIC_TLS_SECRETS_MAX_SECRET_LEN) + 1] = {0}; + if (TlsSecrets->IsSet.ClientRandom) { + EncodeHexBuffer( + TlsSecrets->ClientRandom, + (uint8_t)sizeof(TlsSecrets->ClientRandom), + ClientRandomBuffer); + } + + if (TlsSecrets->IsSet.ClientEarlyTrafficSecret) { + EncodeHexBuffer( + TlsSecrets->ClientEarlyTrafficSecret, + TlsSecrets->SecretLength, + TempHexBuffer); + fprintf( + File, + "CLIENT_EARLY_TRAFFIC_SECRET %s %s\n", + ClientRandomBuffer, + TempHexBuffer); + } + + if (TlsSecrets->IsSet.ClientHandshakeTrafficSecret) { + EncodeHexBuffer( + TlsSecrets->ClientHandshakeTrafficSecret, + TlsSecrets->SecretLength, + TempHexBuffer); + fprintf( + File, + "CLIENT_HANDSHAKE_TRAFFIC_SECRET %s %s\n", + ClientRandomBuffer, + TempHexBuffer); + } + + if (TlsSecrets->IsSet.ServerHandshakeTrafficSecret) { + EncodeHexBuffer( + TlsSecrets->ServerHandshakeTrafficSecret, + TlsSecrets->SecretLength, + TempHexBuffer); + fprintf( + File, + "SERVER_HANDSHAKE_TRAFFIC_SECRET %s %s\n", + ClientRandomBuffer, + TempHexBuffer); + } + + if (TlsSecrets->IsSet.ClientTrafficSecret0) { + EncodeHexBuffer( + TlsSecrets->ClientTrafficSecret0, + TlsSecrets->SecretLength, + TempHexBuffer); + fprintf( + File, + "CLIENT_TRAFFIC_SECRET_0 %s %s\n", + ClientRandomBuffer, + TempHexBuffer); + } + + if (TlsSecrets->IsSet.ServerTrafficSecret0) { + EncodeHexBuffer( + TlsSecrets->ServerTrafficSecret0, + TlsSecrets->SecretLength, + TempHexBuffer); + fprintf( + File, + "SERVER_TRAFFIC_SECRET_0 %s %s\n", + ClientRandomBuffer, + TempHexBuffer); + } + + fflush(File); + fclose(File); +} + // // Allocates and sends some data over a QUIC stream. // @@ -682,6 +806,14 @@ ClientConnectionCallback( ) { UNREFERENCED_PARAMETER(Context); + + if (Event->Type == QUIC_CONNECTION_EVENT_CONNECTED) { + const char* SslKeyLogFile = getenv(SslKeyLogEnvVar); + if (SslKeyLogFile != NULL) { + WriteSslKeyLogFile(SslKeyLogFile, &ClientSecrets); + } + } + switch (Event->Type) { case QUIC_CONNECTION_EVENT_CONNECTED: // @@ -802,6 +934,7 @@ RunClient( QUIC_STATUS Status; const char* ResumptionTicketString = NULL; + const char* SslKeyLogFile = getenv(SslKeyLogEnvVar); HQUIC Connection = NULL; // @@ -825,6 +958,13 @@ RunClient( } } + if (SslKeyLogFile != NULL) { + if (QUIC_FAILED(Status = MsQuic->SetParam(Connection, QUIC_PARAM_CONN_TLS_SECRETS, sizeof(ClientSecrets), &ClientSecrets))) { + printf("SetParam(QUIC_PARAM_CONN_TLS_SECRETS) failed, 0x%x!\n", Status); + goto Error; + } + } + // // Get the target / server name or IP from the command line. //